mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Optimizer: peephole optimization for raw-value enum comparsions
Optimize (the very inefficient) RawRepresentable comparison function call to a simple compare of enum tags.
For example,
```
enum E: String {
case a, b, c
}
```
is compared by getting the raw values of both operands and doing a string compare.
This peephole optimizations replaces the call to such a comparison function with a direct compare of the enum tags, which boils down to a single integer comparison instruction.
rdar://151788987
This commit is contained in:
@@ -28,6 +28,9 @@ extension ApplyInst : OnoneSimplifiable, SILCombineSimplifiable {
|
||||
if tryRemoveArrayCast(apply: self, context) {
|
||||
return
|
||||
}
|
||||
if tryOptimizeEnumComparison(apply: self, context) {
|
||||
return
|
||||
}
|
||||
if !context.preserveDebugInfo {
|
||||
_ = tryReplaceExistentialArchetype(of: self, context)
|
||||
}
|
||||
@@ -110,6 +113,48 @@ private func tryRemoveArrayCast(apply: ApplyInst, _ context: SimplifyContext) ->
|
||||
return true
|
||||
}
|
||||
|
||||
/// Optimize (the very inefficient) RawRepresentable comparison to a simple compare of enum tags.
|
||||
/// For example,
|
||||
/// ```
|
||||
/// enum E: String {
|
||||
/// case a, b, c
|
||||
/// }
|
||||
/// ```
|
||||
/// is compared by getting the raw values of both operands and doing a string compare.
|
||||
/// This peephole optimizations replaces the call to such a comparison function with a direct compare of
|
||||
/// the enum tags, which boils down to a single integer comparison instruction.
|
||||
///
|
||||
private func tryOptimizeEnumComparison(apply: ApplyInst, _ context: SimplifyContext) -> Bool {
|
||||
guard let callee = apply.referencedFunction,
|
||||
apply.arguments.count == 2,
|
||||
callee.hasSemanticsAttribute("rawrepresentable.is_equal"),
|
||||
apply.type.isStruct
|
||||
else {
|
||||
return false
|
||||
}
|
||||
let lhs = apply.arguments[0]
|
||||
let rhs = apply.arguments[1]
|
||||
guard let enumDecl = lhs.type.nominal as? EnumDecl,
|
||||
!enumDecl.isResilient(in: apply.parentFunction),
|
||||
!enumDecl.hasClangNode,
|
||||
lhs.type.isAddress,
|
||||
lhs.type == rhs.type
|
||||
else {
|
||||
return false
|
||||
}
|
||||
let builder = Builder(before: apply, context)
|
||||
let tagType = context.getBuiltinIntegerType(bitWidth: 32)
|
||||
let lhsTag = builder.createBuiltin(name: "getEnumTag", type: tagType,
|
||||
substitutions: apply.substitutionMap, arguments: [lhs])
|
||||
let rhsTag = builder.createBuiltin(name: "getEnumTag", type: tagType,
|
||||
substitutions: apply.substitutionMap, arguments: [rhs])
|
||||
let builtinBoolType = context.getBuiltinIntegerType(bitWidth: 1)
|
||||
let cmp = builder.createBuiltin(name: "cmp_eq_Int32", type: builtinBoolType, arguments: [lhsTag, rhsTag])
|
||||
let booleanResult = builder.createStruct(type: apply.type, elements: [cmp])
|
||||
apply.replace(with: booleanResult, context)
|
||||
return true
|
||||
}
|
||||
|
||||
/// If the apply uses an existential archetype (`@opened("...")`) and the concrete type is known,
|
||||
/// replace the existential archetype with the concrete type
|
||||
/// 1. in the apply's substitution map
|
||||
|
||||
@@ -149,6 +149,7 @@ public protocol RawRepresentable<RawValue> {
|
||||
/// - lhs: A raw-representable instance.
|
||||
/// - rhs: A second raw-representable instance.
|
||||
@inlinable // trivial-implementation
|
||||
@_semantics("rawrepresentable.is_equal")
|
||||
public func == <T: RawRepresentable>(lhs: T, rhs: T) -> Bool
|
||||
where T.RawValue: Equatable {
|
||||
return lhs.rawValue == rhs.rawValue
|
||||
|
||||
@@ -64,7 +64,7 @@ func test6() {
|
||||
// CHECK: function_ref @$[[TEST6_EQUALS_WITNESS:[_0-9a-zA-Z]+]]
|
||||
// CHECK: }
|
||||
|
||||
// CHECK: sil [serialized] @$[[TEST6_EQUALS_WITNESS]] : $@convention(thin) <τ_0_0 where τ_0_0 : RawRepresentable, τ_0_0.RawValue : Equatable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> Bool
|
||||
// CHECK: sil [serialized] {{.*}}@$[[TEST6_EQUALS_WITNESS]] : $@convention(thin) <τ_0_0 where τ_0_0 : RawRepresentable, τ_0_0.RawValue : Equatable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> Bool
|
||||
|
||||
func test7() {
|
||||
struct Outer {
|
||||
|
||||
41
test/SILOptimizer/enum-comparison.swift
Normal file
41
test/SILOptimizer/enum-comparison.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift -O -module-name=test %s -o %t/a.out
|
||||
// RUN: %target-build-swift -O -module-name=test %s -emit-ir | %FileCheck %s
|
||||
// RUN: %target-codesign %t/a.out
|
||||
// RUN: %target-run %t/a.out | %FileCheck %s --check-prefix=OUT
|
||||
|
||||
// REQUIRES: executable_test,optimized_stdlib
|
||||
|
||||
enum E: String {
|
||||
case a, b, c, long_case_name_for_testing, d, e
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} i1 @"$s4test9compareeqySbAA1EO_ADtF"(i8 %0, i8 %1)
|
||||
// CHECK: %2 = icmp eq i8 %0, %1
|
||||
// CHECK-NEXT: ret i1 %2
|
||||
@inline(never)
|
||||
func compareeq(_ a: E, _ b: E) -> Bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// CHECK-LABEL: define {{.*}} i1 @"$s4test9compareneySbAA1EO_ADtF"(i8 %0, i8 %1)
|
||||
// CHECK: %2 = icmp ne i8 %0, %1
|
||||
// CHECK-NEXT: ret i1 %2
|
||||
@inline(never)
|
||||
func comparene(_ a: E, _ b: E) -> Bool {
|
||||
return a != b
|
||||
}
|
||||
|
||||
|
||||
// OUT: 1: false
|
||||
print("1: \(compareeq(.c, .long_case_name_for_testing))")
|
||||
|
||||
// OUT: 2: true
|
||||
print("2: \(compareeq(.c, .c))")
|
||||
|
||||
// OUT: 3: true
|
||||
print("3: \(comparene(.c, .long_case_name_for_testing))")
|
||||
|
||||
// OUT: 4: false
|
||||
print("4: \(comparene(.c, .c))")
|
||||
|
||||
@@ -26,6 +26,10 @@ struct GenS<T> {
|
||||
var x: Int
|
||||
}
|
||||
|
||||
enum E: String {
|
||||
case a, b, c, d, e
|
||||
}
|
||||
|
||||
sil @cl : $@convention(thin) () -> Int
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @thick_to_thin :
|
||||
@@ -165,3 +169,31 @@ bb0(%0 : @guaranteed $Array<Any>):
|
||||
return %2
|
||||
}
|
||||
|
||||
sil [_semantics "rawrepresentable.is_equal"] @rawrepresentable_is_equal : $@convention(thin) <T where T : RawRepresentable, T.RawValue : Equatable> (@in_guaranteed T, @in_guaranteed T) -> Bool
|
||||
sil [_semantics "rawrepresentable.is_equal"] @rawrepresentable_is_equal_wrong_convention : $@convention(thin) (E, E) -> Bool
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @string_enum_is_equal :
|
||||
// CHECK: %2 = builtin "getEnumTag"<E>(%0) : $Builtin.Int32
|
||||
// CHECK: %3 = builtin "getEnumTag"<E>(%1) : $Builtin.Int32
|
||||
// CHECK: %4 = builtin "cmp_eq_Int32"(%2, %3) : $Builtin.Int1
|
||||
// CHECK: %5 = struct $Bool (%4)
|
||||
// CHECK: return %5
|
||||
// CHECK: } // end sil function 'string_enum_is_equal'
|
||||
sil [ossa] @string_enum_is_equal : $@convention(thin) (@in_guaranteed E, @in_guaranteed E) -> Bool {
|
||||
bb0(%0 : $*E, %1 : $*E):
|
||||
%2 = function_ref @rawrepresentable_is_equal : $@convention(thin) <T where T : RawRepresentable, T.RawValue : Equatable> (@in_guaranteed T, @in_guaranteed T) -> Bool
|
||||
%3 = apply %2<E>(%0, %1) : $@convention(thin) <τ_0_0 where τ_0_0 : RawRepresentable, τ_0_0.RawValue : Equatable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> Bool
|
||||
return %3
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @string_enum_is_equal_wrong_convention :
|
||||
// CHECK: function_ref
|
||||
// CHECK: apply
|
||||
// CHECK: } // end sil function 'string_enum_is_equal_wrong_convention'
|
||||
sil [ossa] @string_enum_is_equal_wrong_convention : $@convention(thin) (E, E) -> Bool {
|
||||
bb0(%0 : $E, %1 : $E):
|
||||
%2 = function_ref @rawrepresentable_is_equal_wrong_convention : $@convention(thin) (E, E) -> Bool
|
||||
%3 = apply %2(%0, %1) : $@convention(thin) (E, E) -> Bool
|
||||
return %3
|
||||
}
|
||||
|
||||
|
||||
@@ -857,6 +857,10 @@ Var UnsafeMutableBufferPointer.indices has mangled name changing from 'Swift.Uns
|
||||
Var UnsafeMutableBufferPointer.indices is now with @_preInverseGenerics
|
||||
Func !=(_:_:) has been removed
|
||||
Func ==(_:_:) has been removed
|
||||
Func ==(_:_:) has generic signature change from to <T where T : Swift.RawRepresentable, T.RawValue : Swift.Equatable>
|
||||
Func ==(_:_:) has mangled name changing from 'Swift.== infix(Swift.Optional<Any.Type>, Swift.Optional<Any.Type>) -> Swift.Bool' to 'Swift.== infix<A where A: Swift.RawRepresentable, A.RawValue: Swift.Equatable>(A, A) -> Swift.Bool'
|
||||
Func ==(_:_:) has parameter 0 type change from (any Any.Type)? to τ_0_0
|
||||
Func ==(_:_:) has parameter 1 type change from (any Any.Type)? to τ_0_0
|
||||
Func type(of:) has been removed
|
||||
|
||||
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)
|
||||
|
||||
Reference in New Issue
Block a user