Skip to content

Commit cac594f

Browse files
committed
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
1 parent 0486f1a commit cac594f

File tree

6 files changed

+124
-1
lines changed

6 files changed

+124
-1
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyApply.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ extension ApplyInst : OnoneSimplifiable, SILCombineSimplifiable {
2828
if tryRemoveArrayCast(apply: self, context) {
2929
return
3030
}
31+
if tryOptimizeEnumComparison(apply: self, context) {
32+
return
33+
}
3134
if !context.preserveDebugInfo {
3235
_ = tryReplaceExistentialArchetype(of: self, context)
3336
}
@@ -110,6 +113,48 @@ private func tryRemoveArrayCast(apply: ApplyInst, _ context: SimplifyContext) ->
110113
return true
111114
}
112115

116+
/// Optimize (the very inefficient) RawRepresentable comparison to a simple compare of enum tags.
117+
/// For example,
118+
/// ```
119+
/// enum E: String {
120+
/// case a, b, c
121+
/// }
122+
/// ```
123+
/// is compared by getting the raw values of both operands and doing a string compare.
124+
/// This peephole optimizations replaces the call to such a comparison function with a direct compare of
125+
/// the enum tags, which boils down to a single integer comparison instruction.
126+
///
127+
private func tryOptimizeEnumComparison(apply: ApplyInst, _ context: SimplifyContext) -> Bool {
128+
guard let callee = apply.referencedFunction,
129+
apply.arguments.count == 2,
130+
callee.hasSemanticsAttribute("rawrepresentable.is_equal"),
131+
apply.type.isStruct
132+
else {
133+
return false
134+
}
135+
let lhs = apply.arguments[0]
136+
let rhs = apply.arguments[1]
137+
guard let enumDecl = lhs.type.nominal as? EnumDecl,
138+
!enumDecl.isResilient(in: apply.parentFunction),
139+
!enumDecl.hasClangNode,
140+
lhs.type.isAddress,
141+
lhs.type == rhs.type
142+
else {
143+
return false
144+
}
145+
let builder = Builder(before: apply, context)
146+
let tagType = context.getBuiltinIntegerType(bitWidth: 32)
147+
let lhsTag = builder.createBuiltin(name: "getEnumTag", type: tagType,
148+
substitutions: apply.substitutionMap, arguments: [lhs])
149+
let rhsTag = builder.createBuiltin(name: "getEnumTag", type: tagType,
150+
substitutions: apply.substitutionMap, arguments: [rhs])
151+
let builtinBoolType = context.getBuiltinIntegerType(bitWidth: 1)
152+
let cmp = builder.createBuiltin(name: "cmp_eq_Int32", type: builtinBoolType, arguments: [lhsTag, rhsTag])
153+
let booleanResult = builder.createStruct(type: apply.type, elements: [cmp])
154+
apply.replace(with: booleanResult, context)
155+
return true
156+
}
157+
113158
/// If the apply uses an existential archetype (`@opened("...")`) and the concrete type is known,
114159
/// replace the existential archetype with the concrete type
115160
/// 1. in the apply's substitution map

stdlib/public/core/CompilerProtocols.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ public protocol RawRepresentable<RawValue> {
149149
/// - lhs: A raw-representable instance.
150150
/// - rhs: A second raw-representable instance.
151151
@inlinable // trivial-implementation
152+
@_semantics("rawrepresentable.is_equal")
152153
public func == <T: RawRepresentable>(lhs: T, rhs: T) -> Bool
153154
where T.RawValue: Equatable {
154155
return lhs.rawValue == rhs.rawValue

test/SILGen/protocol_operators_local_conformance.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func test6() {
6464
// CHECK: function_ref @$[[TEST6_EQUALS_WITNESS:[_0-9a-zA-Z]+]]
6565
// CHECK: }
6666

67-
// 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
67+
// 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
6868

6969
func test7() {
7070
struct Outer {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -O -module-name=test %s -o %t/a.out
3+
// RUN: %target-build-swift -O -module-name=test %s -emit-ir | %FileCheck %s
4+
// RUN: %target-codesign %t/a.out
5+
// RUN: %target-run %t/a.out | %FileCheck %s --check-prefix=OUT
6+
7+
// REQUIRES: executable_test,optimized_stdlib
8+
9+
enum E: String {
10+
case a, b, c, long_case_name_for_testing, d, e
11+
}
12+
13+
// CHECK-LABEL: define {{.*}} i1 @"$s4test9compareeqySbAA1EO_ADtF"(i8 %0, i8 %1)
14+
// CHECK: %2 = icmp eq i8 %0, %1
15+
// CHECK-NEXT: ret i1 %2
16+
@inline(never)
17+
func compareeq(_ a: E, _ b: E) -> Bool {
18+
return a == b
19+
}
20+
21+
// CHECK-LABEL: define {{.*}} i1 @"$s4test9compareneySbAA1EO_ADtF"(i8 %0, i8 %1)
22+
// CHECK: %2 = icmp ne i8 %0, %1
23+
// CHECK-NEXT: ret i1 %2
24+
@inline(never)
25+
func comparene(_ a: E, _ b: E) -> Bool {
26+
return a != b
27+
}
28+
29+
30+
// OUT: 1: false
31+
print("1: \(compareeq(.c, .long_case_name_for_testing))")
32+
33+
// OUT: 2: true
34+
print("2: \(compareeq(.c, .c))")
35+
36+
// OUT: 3: true
37+
print("3: \(comparene(.c, .long_case_name_for_testing))")
38+
39+
// OUT: 4: false
40+
print("4: \(comparene(.c, .c))")
41+

test/SILOptimizer/simplify_apply.sil

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ struct GenS<T> {
2626
var x: Int
2727
}
2828

29+
enum E: String {
30+
case a, b, c, d, e
31+
}
32+
2933
sil @cl : $@convention(thin) () -> Int
3034

3135
// CHECK-LABEL: sil [ossa] @thick_to_thin :
@@ -165,3 +169,31 @@ bb0(%0 : @guaranteed $Array<Any>):
165169
return %2
166170
}
167171

172+
sil [_semantics "rawrepresentable.is_equal"] @rawrepresentable_is_equal : $@convention(thin) <T where T : RawRepresentable, T.RawValue : Equatable> (@in_guaranteed T, @in_guaranteed T) -> Bool
173+
sil [_semantics "rawrepresentable.is_equal"] @rawrepresentable_is_equal_wrong_convention : $@convention(thin) (E, E) -> Bool
174+
175+
// CHECK-LABEL: sil [ossa] @string_enum_is_equal :
176+
// CHECK: %2 = builtin "getEnumTag"<E>(%0) : $Builtin.Int32
177+
// CHECK: %3 = builtin "getEnumTag"<E>(%1) : $Builtin.Int32
178+
// CHECK: %4 = builtin "cmp_eq_Int32"(%2, %3) : $Builtin.Int1
179+
// CHECK: %5 = struct $Bool (%4)
180+
// CHECK: return %5
181+
// CHECK: } // end sil function 'string_enum_is_equal'
182+
sil [ossa] @string_enum_is_equal : $@convention(thin) (@in_guaranteed E, @in_guaranteed E) -> Bool {
183+
bb0(%0 : $*E, %1 : $*E):
184+
%2 = function_ref @rawrepresentable_is_equal : $@convention(thin) <T where T : RawRepresentable, T.RawValue : Equatable> (@in_guaranteed T, @in_guaranteed T) -> Bool
185+
%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
186+
return %3
187+
}
188+
189+
// CHECK-LABEL: sil [ossa] @string_enum_is_equal_wrong_convention :
190+
// CHECK: function_ref
191+
// CHECK: apply
192+
// CHECK: } // end sil function 'string_enum_is_equal_wrong_convention'
193+
sil [ossa] @string_enum_is_equal_wrong_convention : $@convention(thin) (E, E) -> Bool {
194+
bb0(%0 : $E, %1 : $E):
195+
%2 = function_ref @rawrepresentable_is_equal_wrong_convention : $@convention(thin) (E, E) -> Bool
196+
%3 = apply %2(%0, %1) : $@convention(thin) (E, E) -> Bool
197+
return %3
198+
}
199+

test/api-digester/stability-stdlib-abi-without-asserts.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,10 @@ Var UnsafeMutableBufferPointer.indices has mangled name changing from 'Swift.Uns
857857
Var UnsafeMutableBufferPointer.indices is now with @_preInverseGenerics
858858
Func !=(_:_:) has been removed
859859
Func ==(_:_:) has been removed
860+
Func ==(_:_:) has generic signature change from to <T where T : Swift.RawRepresentable, T.RawValue : Swift.Equatable>
861+
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'
862+
Func ==(_:_:) has parameter 0 type change from (any Any.Type)? to τ_0_0
863+
Func ==(_:_:) has parameter 1 type change from (any Any.Type)? to τ_0_0
860864
Func type(of:) has been removed
861865

862866
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)

0 commit comments

Comments
 (0)