Skip to content

Commit 309b93c

Browse files
committed
Optimizer: add the isBarrierForDestroy utility
It checks if a destroy of a type must not be moved across an instruction.
1 parent 6f1936f commit 309b93c

File tree

3 files changed

+244
-1
lines changed

3 files changed

+244
-1
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/FunctionTest.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public func registerOptimizerTests() {
5151
localVariableReachableUsesTest,
5252
localVariableReachingAssignmentsTest,
5353
rangeOverlapsPathTest,
54-
variableIntroducerTest
54+
variableIntroducerTest,
55+
destroyBarrierTest
5556
)
5657

5758
// Finally register the thunk they all call through.

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,54 @@ extension Instruction {
546546
// Eventually we want to replace the SILCombine implementation with this one.
547547
return nil
548548
}
549+
550+
/// Returns true if a destroy of `type` must not be moved across this instruction.
551+
func isBarrierForDestroy(of type: Type, _ context: some Context) -> Bool {
552+
let instEffects = memoryEffects
553+
if instEffects == .noEffects || type.isTrivial(in: parentFunction) {
554+
return false
555+
}
556+
guard type.isMoveOnly else {
557+
// Non-trivial copyable types only have to consider deinit-barriers for classes.
558+
return isDeinitBarrier(context.calleeAnalysis)
559+
}
560+
561+
// Check side-effects of non-copyable deinits.
562+
563+
guard let nominal = type.nominal else {
564+
return true
565+
}
566+
if nominal.valueTypeDestructor != nil {
567+
guard let deinitFunc = context.lookupDeinit(ofNominal: nominal) else {
568+
return true
569+
}
570+
let destructorEffects = deinitFunc.getSideEffects().memory
571+
if instEffects.write || destructorEffects.write {
572+
return true
573+
}
574+
}
575+
576+
switch nominal {
577+
case is StructDecl:
578+
guard let fields = type.getNominalFields(in: parentFunction) else {
579+
return true
580+
}
581+
return fields.contains { isBarrierForDestroy(of: $0, context) }
582+
case is EnumDecl:
583+
guard let enumCases = type.getEnumCases(in: parentFunction) else {
584+
return true
585+
}
586+
return enumCases.contains {
587+
if let payload = $0.payload {
588+
return isBarrierForDestroy(of: payload, context)
589+
}
590+
return false
591+
}
592+
default:
593+
return true
594+
}
595+
}
596+
549597
}
550598

551599
// Match the pattern:
@@ -1133,3 +1181,14 @@ func cloneAndSpecializeFunction(from originalFunction: Function,
11331181
defer { cloner.deinitialize() }
11341182
cloner.cloneFunctionBody()
11351183
}
1184+
1185+
let destroyBarrierTest = FunctionTest("destroy_barrier") { function, arguments, context in
1186+
let type = arguments.takeValue().type
1187+
for inst in function.instructions {
1188+
if inst.isBarrierForDestroy(of: type, context) {
1189+
print("barrier: \(inst)")
1190+
} else {
1191+
print("transparent: \(inst)")
1192+
}
1193+
}
1194+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// RUN: %target-sil-opt -test-runner %s -enable-experimental-feature MoveOnlyEnumDeinits -o /dev/null | %FileCheck %s
2+
3+
// REQUIRES: swift_feature_MoveOnlyEnumDeinits
4+
5+
sil_stage canonical
6+
7+
import Builtin
8+
import Swift
9+
10+
struct S1: ~Copyable {
11+
deinit
12+
}
13+
14+
struct S2: ~Copyable {
15+
deinit
16+
}
17+
18+
struct S3<T: ~Copyable>: ~Copyable {
19+
@_hasStorage let t: T
20+
deinit
21+
}
22+
23+
enum E<T: ~Copyable>: ~Copyable {
24+
case A(Int)
25+
case B
26+
case C(T)
27+
deinit
28+
}
29+
30+
struct StrWithoutDeinit: ~Copyable {
31+
@_hasStorage var a: S1
32+
@_hasStorage var b: S2
33+
@_hasStorage let c: Int
34+
}
35+
36+
struct S4: ~Copyable {
37+
@_hasStorage var a: S1
38+
@_hasStorage var b: S2
39+
}
40+
41+
struct S5: ~Copyable {
42+
@_hasStorage let a: Int
43+
@_hasStorage let b: AnyObject
44+
}
45+
46+
struct UnknownDeinit: ~Copyable {
47+
deinit
48+
}
49+
50+
sil @s1_deinit : $@convention(method) (@owned S1) -> () {
51+
[global: read]
52+
}
53+
54+
sil @s2_deinit : $@convention(method) (@owned S2) -> () {
55+
[global: write]
56+
}
57+
58+
sil @s3_deinit : $@convention(method) <T> (@in S3<T>) -> () {
59+
[global: read]
60+
}
61+
62+
sil @e_deinit : $@convention(method) (@owned S4) -> () {
63+
[global: read]
64+
}
65+
66+
sil_moveonlydeinit S1 {
67+
@s1_deinit
68+
}
69+
70+
sil_moveonlydeinit S2 {
71+
@s2_deinit
72+
}
73+
74+
sil_moveonlydeinit S3 {
75+
@s3_deinit
76+
}
77+
78+
sil_moveonlydeinit E {
79+
@e_deinit
80+
}
81+
82+
sil_global @g : $Int
83+
84+
sil [ossa] @global_load_store : $@convention(thin) (@inout_aliasable Int, @guaranteed S1, @guaranteed S2, @guaranteed S3<S1>, @guaranteed S3<S2>, @guaranteed S4, @guaranteed S5, @guaranteed E<S4>, @guaranteed E<Int>, @guaranteed UnknownDeinit) -> () {
85+
bb0(%0 : $*Int, %1 : @guaranteed $S1, %2 : @guaranteed $S2, %3 : @guaranteed $S3<S1>, %4 : @guaranteed $S3<S2>, %5 : @guaranteed $S4, %6 : @guaranteed $S5, %7 : @guaranteed $E<S4>, %8 : @guaranteed $E<Int>, %9 : @guaranteed $UnknownDeinit):
86+
87+
// --- Int ---
88+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %0
89+
// CHECK: transparent: %{{[0-9]+}} = load
90+
// CHECK: transparent: store
91+
// CHECK: transparent: %{{[0-9]+}} = apply
92+
// CHECK: transparent: %{{[0-9]+}} = tuple
93+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %0
94+
specify_test "destroy_barrier %0"
95+
96+
// --- S1 ---
97+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %1
98+
// CHECK: transparent: %{{[0-9]+}} = load
99+
// CHECK: barrier: store
100+
// CHECK: barrier: %{{[0-9]+}} = apply
101+
// CHECK: transparent: %{{[0-9]+}} = tuple
102+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %1
103+
specify_test "destroy_barrier %1"
104+
105+
// --- S2 ---
106+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %2
107+
// CHECK: barrier: %{{[0-9]+}} = load
108+
// CHECK: barrier: store
109+
// CHECK: barrier: %{{[0-9]+}} = apply
110+
// CHECK: transparent: %{{[0-9]+}} = tuple
111+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %2
112+
specify_test "destroy_barrier %2"
113+
114+
// --- S3<S1> ---
115+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %3
116+
// CHECK: transparent: %{{[0-9]+}} = load
117+
// CHECK: barrier: store
118+
// CHECK: barrier: %{{[0-9]+}} = apply
119+
// CHECK: transparent: %{{[0-9]+}} = tuple
120+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %3
121+
specify_test "destroy_barrier %3"
122+
123+
// --- S3<S2> ---
124+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %4
125+
// CHECK: barrier: %{{[0-9]+}} = load
126+
// CHECK: barrier: store
127+
// CHECK: barrier: %{{[0-9]+}} = apply
128+
// CHECK: transparent: %{{[0-9]+}} = tuple
129+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %4
130+
specify_test "destroy_barrier %4"
131+
132+
// --- S4 ---
133+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %5
134+
// CHECK: barrier: %{{[0-9]+}} = load
135+
// CHECK: barrier: store
136+
// CHECK: barrier: %{{[0-9]+}} = apply
137+
// CHECK: transparent: %{{[0-9]+}} = tuple
138+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %5
139+
specify_test "destroy_barrier %5"
140+
141+
// --- S5 ---
142+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %6
143+
// CHECK: transparent: %{{[0-9]+}} = load
144+
// CHECK: transparent: store
145+
// CHECK: barrier: %{{[0-9]+}} = apply
146+
// CHECK: transparent: %{{[0-9]+}} = tuple
147+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %6
148+
specify_test "destroy_barrier %6"
149+
150+
// --- E<S4> ---
151+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %7
152+
// CHECK: barrier: %{{[0-9]+}} = load
153+
// CHECK: barrier: store
154+
// CHECK: barrier: %{{[0-9]+}} = apply
155+
// CHECK: transparent: %{{[0-9]+}} = tuple
156+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %7
157+
specify_test "destroy_barrier %7"
158+
159+
// --- E<Int> ---
160+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %8
161+
// CHECK: transparent: %{{[0-9]+}} = load
162+
// CHECK: barrier: store
163+
// CHECK: barrier: %{{[0-9]+}} = apply
164+
// CHECK: transparent: %{{[0-9]+}} = tuple
165+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %8
166+
specify_test "destroy_barrier %8"
167+
168+
// --- UnknownDeinit ---
169+
// CHECK-LABEL: begin running test {{.*}} on global_load_store: destroy_barrier with: %9
170+
// CHECK: barrier: %{{[0-9]+}} = load
171+
// CHECK: barrier: store
172+
// CHECK: barrier: %{{[0-9]+}} = apply
173+
// CHECK: transparent: %{{[0-9]+}} = tuple
174+
// CHECK: end running test {{.*}} on global_load_store: destroy_barrier with: %9
175+
specify_test "destroy_barrier %9"
176+
177+
%l = load [trivial] %0
178+
store %l to [trivial] %0
179+
apply undef() : $() -> ()
180+
%r = tuple ()
181+
return %r
182+
}
183+

0 commit comments

Comments
 (0)