Skip to content

Commit cc60815

Browse files
committed
ComputeSideEffects: fix wrong side effect computation of releases/destroys
Only global side effects of the destructor were considered, but side effects weren't attributed to the released value. rdar://105237110
1 parent 7059248 commit cc60815

File tree

2 files changed

+78
-34
lines changed

2 files changed

+78
-34
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeSideEffects.swift

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,10 @@ private struct CollectedEffects {
9595
addEffects(.copy, to: inst.operands[0].value, fromInitialPath: SmallProjectionPath(.anyValueFields))
9696

9797
case is DestroyValueInst, is ReleaseValueInst, is StrongReleaseInst:
98-
addDestroyEffects(of: inst.operands[0].value)
98+
addDestroyEffects(ofValue: inst.operands[0].value)
9999

100100
case let da as DestroyAddrInst:
101-
// A destroy_addr also involves a read from the address. It's equivalent to a `%x = load [take]` and `destroy_value %x`.
102-
addEffects(.read, to: da.operand)
103-
// Conceptually, it's also a write, because the stored value is not available anymore after the destroy
104-
addEffects(.write, to: da.operand)
105-
106-
addDestroyEffects(of: da.operand)
101+
addDestroyEffects(ofAddress: da.operand)
107102

108103
case let copy as CopyAddrInst:
109104
addEffects(.read, to: copy.source)
@@ -113,17 +108,13 @@ private struct CollectedEffects {
113108
addEffects(.copy, to: copy.source)
114109
}
115110
if !copy.isInitializationOfDest {
116-
// Like for destroy_addr, the destroy also involves a read.
117-
addEffects(.read, to: copy.destination)
118-
addDestroyEffects(of: copy.destination)
111+
addDestroyEffects(ofAddress: copy.destination)
119112
}
120113

121114
case let store as StoreInst:
122115
addEffects(.write, to: store.destination)
123116
if store.destinationOwnership == .assign {
124-
// Like for destroy_addr, the destroy also involves a read.
125-
addEffects(.read, to: store.destination)
126-
addDestroyEffects(of: store.destination)
117+
addDestroyEffects(ofAddress: store.destination)
127118
}
128119

129120
case let store as StoreWeakInst:
@@ -137,9 +128,9 @@ private struct CollectedEffects {
137128
addEffects(.read, to: addr)
138129

139130
case let apply as FullApplySite:
140-
let calleeValue = apply.callee
141131
if apply.callee.type.isCalleeConsumedFunction {
142-
addDestroyEffects(of: calleeValue)
132+
addEffects(.destroy, to: apply.callee)
133+
globalEffects = .worstEffects
143134
}
144135
handleApply(apply)
145136
checkedIfDeinitBarrier = true
@@ -230,11 +221,58 @@ private struct CollectedEffects {
230221

231222
private mutating func handleApply(_ apply: ApplySite) {
232223
let callees = calleeAnalysis.getCallees(callee: apply.callee)
224+
let args = apply.arguments.enumerated().lazy.map {
225+
(calleeArgumentIndex: apply.calleeArgIndex(callerArgIndex: $0.0),
226+
callerArgument: $0.1)
227+
}
228+
addEffects(ofFunctions: callees, withArguments: args)
229+
}
230+
231+
private mutating func addDestroyEffects(ofValue value: Value) {
232+
// First thing: add the destroy effect itself.
233+
addEffects(.destroy, to: value)
234+
235+
if value.type.isClass {
236+
// Treat destroying a class value just like a call to it's destructor(s).
237+
let destructors = calleeAnalysis.getDestructors(of: value.type)
238+
let theSelfArgument = CollectionOfOne((calleeArgumentIndex: 0, callerArgument: value))
239+
addEffects(ofFunctions: destructors, withArguments: theSelfArgument)
240+
} else {
241+
// TODO: dig into the type and check for destructors of individual class fields
242+
addEffects(.worstEffects, to: value)
243+
globalEffects = .worstEffects
244+
}
245+
}
246+
247+
private mutating func addDestroyEffects(ofAddress address: Value) {
248+
// First thing: add the destroy effect itself.
249+
addEffects(.destroy, to: address)
250+
251+
// A destroy also involves a read from the address.
252+
// E.g. a `destroy_addr` is equivalent to a `%x = load [take]` and `destroy_value %x`.
253+
addEffects(.read, to: address)
254+
// Conceptually, it's also a write, because the stored value is not available anymore after the destroy
255+
addEffects(.write, to: address)
256+
257+
// Second: add all effects of (potential) destructors which might be called if the destroy deallocates an object.
258+
// Note that we don't need to add any effects specific to the `address`, because the memory location is not
259+
// affected by a destructor of the stored value (and effects don't include anything which is loaded from memory).
260+
if let destructors = calleeAnalysis.getDestructors(of: address.type) {
261+
for destructor in destructors {
262+
globalEffects.merge(with: destructor.getSideEffects())
263+
}
264+
} else {
265+
globalEffects = .worstEffects
266+
}
267+
}
233268

269+
private mutating func addEffects<Arguments: Sequence>(ofFunctions callees: FunctionArray?,
270+
withArguments arguments: Arguments)
271+
where Arguments.Element == (calleeArgumentIndex: Int, callerArgument: Value) {
234272
guard let callees = callees else {
235273
// We don't know which function(s) are called.
236274
globalEffects = .worstEffects
237-
for argument in apply.arguments {
275+
for (_, argument) in arguments {
238276
addEffects(.worstEffects, to: argument)
239277
}
240278
return
@@ -249,8 +287,7 @@ private struct CollectedEffects {
249287
}
250288
}
251289

252-
for (argumentIdx, argument) in apply.arguments.enumerated() {
253-
let calleeArgIdx = apply.calleeArgIndex(callerArgIndex: argumentIdx)
290+
for (calleeArgIdx, argument) in arguments {
254291
for callee in callees {
255292
if let sideEffects = callee.effects.sideEffects {
256293
let calleeEffect = sideEffects.getArgumentEffects(for: calleeArgIdx)
@@ -272,21 +309,6 @@ private struct CollectedEffects {
272309
}
273310
}
274311

275-
private mutating func addDestroyEffects(of addressOrValue: Value) {
276-
// First thing: add the destroy effect itself.
277-
addEffects(.destroy, to: addressOrValue)
278-
279-
// Second: add all effects of (potential) destructors which might be called if the destroy
280-
// deallocates an object.
281-
if let destructors = calleeAnalysis.getDestructors(of: addressOrValue.type) {
282-
for destructor in destructors {
283-
globalEffects.merge(with: destructor.getSideEffects())
284-
}
285-
} else {
286-
globalEffects = .worstEffects
287-
}
288-
}
289-
290312
/// Adds effects to a specific value.
291313
///
292314
/// If the value comes from an argument (or mutliple arguments), then the effects are added

test/SILOptimizer/side_effects.sil

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ final class X {
2222
class Y {
2323
}
2424

25+
final class Z {
26+
@_hasStorage var a: Int32
27+
28+
init()
29+
deinit
30+
}
31+
2532
struct SP {
2633
var value: X
2734
}
@@ -73,6 +80,10 @@ sil @$s4test1XCfD : $@convention(method) (@owned X) -> () {
7380
[global: copy, write]
7481
}
7582

83+
sil @$s4test1ZCfD : $@convention(method) (@owned Z) -> () {
84+
[%0: write c0.v**]
85+
}
86+
7687
// CHECK-LABEL: sil @load_store_to_args
7788
// CHECK-NEXT: [%0: read v**]
7889
// CHECK-NEXT: [%1: write v**]
@@ -495,7 +506,7 @@ bb0(%0 : $*X, %1 : $*X):
495506
}
496507

497508
// CHECK-LABEL: sil [ossa] @destroy_value_effects1
498-
// CHECK-NEXT: [%0: destroy v**.c*.v**]
509+
// CHECK-NEXT: [%0: read v**.c*.v**, write v**.c*.v**, copy v**.c*.v**, destroy v**.c*.v**]
499510
// CHECK-NEXT: [global: read,write,copy,destroy,allocate,deinit_barrier]
500511
// CHECK-NEXT: {{^[^[]}}
501512
sil [ossa] @destroy_value_effects1 : $@convention(thin) (@owned SP) -> () {
@@ -517,6 +528,17 @@ bb0(%0 : @owned $SP):
517528
return %2 : $()
518529
}
519530

531+
// CHECK-LABEL: sil [ossa] @destroy_value_effects3
532+
// CHECK-NEXT: [%0: write c0.v**, destroy c*.v**]
533+
// CHECK-NEXT: [global: ]
534+
// CHECK-NEXT: {{^[^[]}}
535+
sil [ossa] @destroy_value_effects3 : $@convention(thin) (@owned Z) -> () {
536+
bb0(%0 : @owned $Z):
537+
destroy_value %0 : $Z
538+
%2 = tuple ()
539+
return %2 : $()
540+
}
541+
520542
// CHECK-LABEL: sil [ossa] @destroy_addr_effects
521543
// CHECK-NEXT: [%0: read v**, write v**, destroy v**]
522544
// CHECK-NEXT: [global: write,copy]

0 commit comments

Comments
 (0)