Skip to content

Commit 4eddc1d

Browse files
authored
Merge pull request #70494 from eeckstein/instruction-simplifications
Add some instruction simplifications
2 parents f2d2a28 + ee71376 commit 4eddc1d

File tree

6 files changed

+341
-0
lines changed

6 files changed

+341
-0
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ swift_compiler_sources(Optimizer
2828
SimplifyRetainReleaseValue.swift
2929
SimplifyStrongRetainRelease.swift
3030
SimplifyStructExtract.swift
31+
SimplifySwitchEnum.swift
3132
SimplifyTupleExtract.swift
3233
SimplifyUncheckedEnumData.swift
3334
SimplifyValueToBridgeObject.swift)

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyBuiltin.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ extension BuiltinInst : OnoneSimplifyable {
4949
if context.options.enableEmbeddedSwift {
5050
optimizeArgumentToThinMetatype(argument: 1, context)
5151
}
52+
case .ICMP_EQ:
53+
constantFoldIntegerEquality(isEqual: true, context)
54+
case .ICMP_NE:
55+
constantFoldIntegerEquality(isEqual: false, context)
5256
default:
5357
if let literal = constantFold(context) {
5458
uses.replaceAll(with: literal, context)
@@ -200,6 +204,51 @@ private extension BuiltinInst {
200204
let newMetatype = builder.createMetatype(of: instanceType, representation: .Thin)
201205
operands[argument].set(to: newMetatype, context)
202206
}
207+
208+
func constantFoldIntegerEquality(isEqual: Bool, _ context: SimplifyContext) {
209+
if constantFoldStringNullPointerCheck(isEqual: isEqual, context) {
210+
return
211+
}
212+
if let literal = constantFold(context) {
213+
uses.replaceAll(with: literal, context)
214+
}
215+
}
216+
217+
func constantFoldStringNullPointerCheck(isEqual: Bool, _ context: SimplifyContext) -> Bool {
218+
if operands[1].value.isZeroInteger &&
219+
operands[0].value.lookThroughScalarCasts is StringLiteralInst
220+
{
221+
let builder = Builder(before: self, context)
222+
let result = builder.createIntegerLiteral(isEqual ? 0 : 1, type: type)
223+
uses.replaceAll(with: result, context)
224+
context.erase(instruction: self)
225+
return true
226+
}
227+
return false
228+
}
229+
}
230+
231+
private extension Value {
232+
var isZeroInteger: Bool {
233+
if let literal = self as? IntegerLiteralInst,
234+
let value = literal.value
235+
{
236+
return value == 0
237+
}
238+
return false
239+
}
240+
241+
var lookThroughScalarCasts: Value {
242+
guard let bi = self as? BuiltinInst else {
243+
return self
244+
}
245+
switch bi.id {
246+
case .ZExt, .ZExtOrBitCast, .PtrToInt:
247+
return bi.operands[0].value.lookThroughScalarCasts
248+
default:
249+
return self
250+
}
251+
}
203252
}
204253

205254
private func hasSideEffectForBuiltinOnce(_ instruction: Instruction) -> Bool {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//===--- SimplifySwitchEnum.swift -----------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
// Removes an `enum` - `switch_enum` pair:
16+
// ```
17+
// %1 = enum $E, #someCase, %payload
18+
// switch_enum %1, case #someCase: bb1, ...
19+
// bb1(%payloadArgument):
20+
// ```
21+
// ->
22+
// ```
23+
// br bb1(%payload)
24+
// bb1(%payloadArgument):
25+
// ```
26+
//
27+
// Other case blocks of the switch_enum become dead.
28+
//
29+
extension SwitchEnumInst : OnoneSimplifyable {
30+
func simplify(_ context: SimplifyContext) {
31+
guard let enumInst = enumOp as? EnumInst,
32+
let caseBlock = getUniqueSuccessor(forCaseIndex: enumInst.caseIndex) else
33+
{
34+
return
35+
}
36+
37+
let singleUse = context.preserveDebugInfo ? enumInst.uses.singleUse : enumInst.uses.ignoreDebugUses.singleUse
38+
let canEraseEnumInst = singleUse?.instruction == self
39+
40+
if !canEraseEnumInst && parentFunction.hasOwnership && enumInst.ownership == .owned {
41+
// We cannot add more uses to the `enum` instruction without inserting a copy.
42+
return
43+
}
44+
45+
let builder = Builder(before: self, context)
46+
switch caseBlock.arguments.count {
47+
case 0:
48+
precondition(enumInst.payload == nil || !parentFunction.hasOwnership,
49+
"missing payload argument in switch_enum case block")
50+
builder.createBranch(to: caseBlock)
51+
case 1:
52+
builder.createBranch(to: caseBlock, arguments: [enumInst.payload!])
53+
default:
54+
fatalError("case block of switch_enum cannot have more than 1 argument")
55+
}
56+
context.erase(instruction: self)
57+
58+
if canEraseEnumInst {
59+
context.erase(instructionIncludingDebugUses: enumInst)
60+
}
61+
}
62+
}

test/IRGen/section_structs.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,14 @@ struct MyStruct5 {
3838
}
3939
@_section("__TEXT,__mysection") var g_MyStruct5: MyStruct5 = MyStruct5(q: MyStruct4(a: 42, s: MyStruct1(a: 43, b: 44)), r: MyStruct4(a: 42, s: MyStruct1(a: 43, b: 44)))
4040

41+
@_section("__TEXT,__mysection") let utf8OfStaticString = StaticString("hello").utf8Start
42+
4143
// CHECK: @"{{.*}}g_MyStruct1{{.*}}Vvp" = hidden global {{.*}} <{ %TSi <{ {{(i32|i64)}} 42 }>, %TSi <{ {{(i32|i64)}} 66 }> }>
4244
// CHECK: @"{{.*}}g_MyStruct2{{.*}}Vvp" = hidden global {{.*}} <{ %TSi <{ {{(i32|i64)}} 42 }>, <{ %TSi, %TSi }> <{ %TSi <{ {{(i32|i64)}} 66 }>, %TSi <{ {{(i32|i64)}} 67 }> }> }>
4345
// CHECK: @"{{.*}}g_MyStruct3{{.*}}Vvp" = hidden global {{.*}} <{ %TSi <{ {{(i32|i64)}} 42 }>, %TSi <{ {{(i32|i64)}} 77 }> }>
4446
// CHECK: @"{{.*}}g_MyStruct4{{.*}}Vvp" = hidden global {{.*}} <{ %TSi <{ {{(i32|i64)}} 42 }>, {{.*}} <{ %TSi <{ {{(i32|i64)}} 43 }>, %TSi <{ {{(i32|i64)}} 44 }> }> }>
4547
// CHECK: @"{{.*}}g_MyStruct5{{.*}}Vvp" = hidden global {{.*}} <{ {{.*}} <{ %TSi <{ {{(i32|i64)}} 42 }>, {{.*}} <{ %TSi <{ {{(i32|i64)}} 43 }>, %TSi <{ {{(i32|i64)}} 44 }> }> }>, {{.*}} <{ %TSi <{ {{(i32|i64)}} 42 }>, {{.*}} <{ %TSi <{ {{(i32|i64)}} 43 }>, %TSi <{ {{(i32|i64)}} 44 }> }> }> }>
48+
49+
// CHECK: [[HELLOSTR:@.*]] = private {{.*}}constant [6 x i8] c"hello\00"
50+
// CHECK: @"{{.*}}utf8OfStaticString{{.*}}VGvp" = hidden constant {{.*}} <{ ptr [[HELLOSTR]] }>
51+

test/SILOptimizer/simplify_builtin.sil

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,74 @@ bb0:
383383
return %4 : $Int8
384384
}
385385

386+
// CHECK-LABEL: sil @string_null_pointer_check :
387+
// CHECK: %0 = integer_literal $Builtin.Int1, 0
388+
// CHECK-NEXT: return %0
389+
// CHECK: } // end sil function 'string_null_pointer_check'
390+
sil @string_null_pointer_check : $@convention(thin) () -> Builtin.Int1 {
391+
bb0:
392+
%0 = string_literal utf8 "hello"
393+
%1 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word
394+
%2 = builtin "zextOrBitCast_Word_Int64"(%1 : $Builtin.Word) : $Builtin.Int64
395+
%3 = integer_literal $Builtin.Int64, 0
396+
%4 = builtin "cmp_eq_Int64"(%2 : $Builtin.Int64, %3 : $Builtin.Int64) : $Builtin.Int1
397+
return %4 : $Builtin.Int1
398+
}
399+
400+
// CHECK-LABEL: sil @string_null_pointer_check_ne :
401+
// CHECK: %0 = integer_literal $Builtin.Int1, -1
402+
// CHECK-NEXT: return %0
403+
// CHECK: } // end sil function 'string_null_pointer_check_ne'
404+
sil @string_null_pointer_check_ne : $@convention(thin) () -> Builtin.Int1 {
405+
bb0:
406+
%0 = string_literal utf8 "hello"
407+
%1 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word
408+
%2 = builtin "zextOrBitCast_Word_Int64"(%1 : $Builtin.Word) : $Builtin.Int64
409+
%3 = integer_literal $Builtin.Int64, 0
410+
%4 = builtin "cmp_ne_Int64"(%2 : $Builtin.Int64, %3 : $Builtin.Int64) : $Builtin.Int1
411+
return %4 : $Builtin.Int1
412+
}
413+
414+
// CHECK-LABEL: sil @string_null_pointer_check_no_integer_literal :
415+
// CHECK: %4 = builtin "cmp_eq_Int64"(%3 : $Builtin.Int64, %0 : $Builtin.Int64)
416+
// CHECK-NEXT: return %4
417+
// CHECK: } // end sil function 'string_null_pointer_check_no_integer_literal'
418+
sil @string_null_pointer_check_no_integer_literal : $@convention(thin) (Builtin.Int64) -> Builtin.Int1 {
419+
bb0(%0 : $Builtin.Int64):
420+
%1 = string_literal utf8 "hello"
421+
%2 = builtin "ptrtoint_Word"(%1 : $Builtin.RawPointer) : $Builtin.Word
422+
%3 = builtin "zextOrBitCast_Word_Int64"(%2 : $Builtin.Word) : $Builtin.Int64
423+
%4 = builtin "cmp_eq_Int64"(%3 : $Builtin.Int64, %0 : $Builtin.Int64) : $Builtin.Int1
424+
return %4 : $Builtin.Int1
425+
}
426+
427+
// CHECK-LABEL: sil @string_null_pointer_check_not_zero :
428+
// CHECK: %4 = builtin "cmp_eq_Int64"(%2 : $Builtin.Int64, %3 : $Builtin.Int64)
429+
// CHECK-NEXT: return %4
430+
// CHECK: } // end sil function 'string_null_pointer_check_not_zero'
431+
sil @string_null_pointer_check_not_zero : $@convention(thin) () -> Builtin.Int1 {
432+
bb0:
433+
%0 = string_literal utf8 "hello"
434+
%1 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word
435+
%2 = builtin "zextOrBitCast_Word_Int64"(%1 : $Builtin.Word) : $Builtin.Int64
436+
%3 = integer_literal $Builtin.Int64, 123
437+
%4 = builtin "cmp_eq_Int64"(%2 : $Builtin.Int64, %3 : $Builtin.Int64) : $Builtin.Int1
438+
return %4 : $Builtin.Int1
439+
}
440+
441+
// CHECK-LABEL: sil @string_null_pointer_check_no_string_literal :
442+
// CHECK: %4 = builtin "cmp_eq_Int64"(%2 : $Builtin.Int64, %3 : $Builtin.Int64)
443+
// CHECK-NEXT: return %4
444+
// CHECK: } // end sil function 'string_null_pointer_check_no_string_literal'
445+
sil @string_null_pointer_check_no_string_literal : $@convention(thin) (Builtin.RawPointer) -> Builtin.Int1 {
446+
bb0(%0 : $Builtin.RawPointer):
447+
%1 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word
448+
%2 = builtin "zextOrBitCast_Word_Int64"(%1 : $Builtin.Word) : $Builtin.Int64
449+
%3 = integer_literal $Builtin.Int64, 0
450+
%4 = builtin "cmp_eq_Int64"(%2 : $Builtin.Int64, %3 : $Builtin.Int64) : $Builtin.Int1
451+
return %4 : $Builtin.Int1
452+
}
453+
386454
// CHECK-LABEL: sil @convert_thick_to_thin1
387455
// CHECK-NOT: metatype $@thick C1<Int>.Type
388456
// CHECK: [[L:%.*]] = metatype $@thin C1<Int>.Type
@@ -490,3 +558,4 @@ bb0(%0 : $Builtin.RawPointer, %1 : $Builtin.Word):
490558
%3 = builtin "destroyArray"<C1<Int>>(%2 : $@thick C1<Int>.Type, %0 : $Builtin.RawPointer, %1 : $Builtin.Word) : $()
491559
return %0 : $Builtin.RawPointer
492560
}
561+
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all %s -onone-simplification -simplify-instruction=switch_enum | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-ONONE
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -simplification -simplify-instruction=switch_enum | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O
3+
4+
// REQUIRES: swift_in_compiler
5+
6+
import Swift
7+
import Builtin
8+
9+
enum E {
10+
case A
11+
case B(String)
12+
case C(Int)
13+
}
14+
15+
// CHECK-LABEL: sil @test_simple :
16+
// CHECK: bb0(%0 : $String):
17+
// CHECK-NEXT: br bb1(%0 : $String)
18+
// CHECK: bb1(%2 : $String):
19+
// CHECK-NEXT: return %2 : $String
20+
// CHECK-NOT: bb2
21+
// CHECK: } // end sil function 'test_simple'
22+
sil @test_simple : $@convention(thin) (@owned String) -> @owned String {
23+
bb0(%0 : $String):
24+
%1 = enum $E, #E.B!enumelt, %0 : $String
25+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
26+
27+
bb1(%2 : $String):
28+
return %2 : $String
29+
30+
bb2:
31+
unreachable
32+
}
33+
34+
// CHECK-LABEL: sil @no_payload_argument :
35+
// CHECK: bb0(%0 : $String):
36+
// CHECK-NEXT: br bb1
37+
// CHECK: bb1:
38+
// CHECK-NEXT: tuple
39+
// CHECK-NEXT: return
40+
// CHECK-NOT: bb2
41+
// CHECK: } // end sil function 'no_payload_argument'
42+
sil @no_payload_argument : $@convention(thin) (@guaranteed String) -> () {
43+
bb0(%0 : $String):
44+
%1 = enum $E, #E.B!enumelt, %0 : $String
45+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
46+
47+
bb1:
48+
%2 = tuple ()
49+
return %2 : $()
50+
51+
bb2:
52+
unreachable
53+
}
54+
55+
// CHECK-LABEL: sil [ossa] @owned_ossa :
56+
// CHECK: bb0(%0 : @owned $String):
57+
// CHECK-NEXT: br bb1(%0 : $String)
58+
// CHECK: bb1(%2 : @owned $String):
59+
// CHECK-NEXT: return %2 : $String
60+
// CHECK-NOT: bb2
61+
// CHECK: } // end sil function 'owned_ossa'
62+
sil [ossa] @owned_ossa : $@convention(thin) (@owned String) -> @owned String {
63+
bb0(%0 : @owned $String):
64+
%1 = enum $E, #E.B!enumelt, %0 : $String
65+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
66+
67+
bb1(%2 : @owned $String):
68+
return %2 : $String
69+
70+
bb2(%4 : @owned $E):
71+
unreachable
72+
}
73+
74+
// CHECK-LABEL: sil [ossa] @guaranteed_ossa :
75+
// CHECK: bb0(%0 : @guaranteed $String):
76+
// CHECK-NEXT: br bb1(%0 : $String)
77+
// CHECK: bb1(%2 : @guaranteed $String):
78+
// CHECK-NEXT: %3 = copy_value %2
79+
// CHECK-NEXT: return %3 : $String
80+
// CHECK-NOT: bb2
81+
// CHECK: } // end sil function 'guaranteed_ossa'
82+
sil [ossa] @guaranteed_ossa : $@convention(thin) (@guaranteed String) -> @owned String {
83+
bb0(%0 : @guaranteed $String):
84+
%1 = enum $E, #E.B!enumelt, %0 : $String
85+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
86+
87+
bb1(%2 : @guaranteed $String):
88+
%3 = copy_value %2 : $String
89+
return %3 : $String
90+
91+
bb2(%4 : @guaranteed $E):
92+
unreachable
93+
}
94+
95+
// CHECK-LABEL: sil [ossa] @owned_with_additional_uses :
96+
// CHECK: switch_enum
97+
// CHECK: } // end sil function 'owned_with_additional_uses'
98+
sil [ossa] @owned_with_additional_uses : $@convention(thin) (@owned String) -> @owned String {
99+
bb0(%0 : @owned $String):
100+
%1 = enum $E, #E.B!enumelt, %0 : $String
101+
%2 = begin_borrow %1 : $E
102+
end_borrow %2 : $E
103+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
104+
105+
bb1(%5 : @owned $String):
106+
return %5 : $String
107+
108+
bb2(%7 : @owned $E):
109+
unreachable
110+
}
111+
112+
// CHECK-LABEL: sil [ossa] @guaranteed_with_additional_uses :
113+
// CHECK: enum
114+
// CHECK-NEXT: begin_borrow
115+
// CHECK-NEXT: end_borrow
116+
// CHECK-NEXT: br bb1(%0 : $String)
117+
// CHECK: bb1([[A:%.*]] : @guaranteed $String):
118+
// CHECK-NEXT: [[C:%.*]] = copy_value [[A]]
119+
// CHECK-NEXT: return [[C]] : $String
120+
// CHECK-NOT: bb2
121+
// CHECK: } // end sil function 'guaranteed_with_additional_uses'
122+
sil [ossa] @guaranteed_with_additional_uses : $@convention(thin) (@guaranteed String) -> @owned String {
123+
bb0(%0 : @guaranteed $String):
124+
%1 = enum $E, #E.B!enumelt, %0 : $String
125+
%2 = begin_borrow %1 : $E
126+
end_borrow %2 : $E
127+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
128+
129+
bb1(%5 : @guaranteed $String):
130+
%6 = copy_value %5 : $String
131+
return %6 : $String
132+
133+
bb2(%7 : @guaranteed $E):
134+
unreachable
135+
}
136+
137+
// CHECK-LABEL: sil [ossa] @owned_with_debug_uses :
138+
// CHECK: bb0(%0 : @owned $String):
139+
// CHECK-O: br bb1(%0 : $String)
140+
// CHECK-ONONE: switch_enum
141+
// CHECK: } // end sil function 'owned_with_debug_uses'
142+
sil [ossa] @owned_with_debug_uses : $@convention(thin) (@owned String) -> @owned String {
143+
bb0(%0 : @owned $String):
144+
%1 = enum $E, #E.B!enumelt, %0 : $String
145+
debug_value %1 : $E, let, name "e"
146+
switch_enum %1 : $E, case #E.B!enumelt: bb1, default bb2
147+
148+
bb1(%2 : @owned $String):
149+
return %2 : $String
150+
151+
bb2(%4 : @owned $E):
152+
unreachable
153+
}
154+

0 commit comments

Comments
 (0)