Skip to content

Commit 1aa400c

Browse files
authored
Merge pull request swiftlang#63568 from eeckstein/fix-compute-side-effects
Fix two bugs in side effect computation
2 parents 10e487b + dfde580 commit 1aa400c

File tree

6 files changed

+196
-69
lines changed

6 files changed

+196
-69
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeSideEffects.swift

Lines changed: 73 additions & 49 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)
@@ -262,7 +299,7 @@ private struct CollectedEffects {
262299
if let calleePath = calleeEffect.destroy { addEffects(.destroy, to: argument, fromInitialPath: calleePath) }
263300
} else {
264301
let convention = callee.getArgumentConvention(for: calleeArgIdx)
265-
let wholeArgument = argument.at(SmallProjectionPath(.anything))
302+
let wholeArgument = argument.at(defaultPath(for: argument))
266303
let calleeEffects = callee.getSideEffects(forArgument: wholeArgument,
267304
atIndex: calleeArgIdx,
268305
withConvention: convention)
@@ -272,27 +309,16 @@ 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
293315
/// to the corrseponding `argumentEffects`. Otherwise they are added to the `global` effects.
316+
private mutating func addEffects(_ effects: SideEffects.GlobalEffects, to value: Value) {
317+
addEffects(effects, to: value, fromInitialPath: defaultPath(for: value))
318+
}
319+
294320
private mutating func addEffects(_ effects: SideEffects.GlobalEffects, to value: Value,
295-
fromInitialPath: SmallProjectionPath? = nil) {
321+
fromInitialPath: SmallProjectionPath) {
296322

297323
/// Collects the (non-address) roots of a value.
298324
struct GetRootsWalker : ValueUseDefWalker {
@@ -324,10 +350,7 @@ private struct CollectedEffects {
324350

325351
var findRoots = GetRootsWalker(context)
326352
if value.type.isAddress {
327-
// If there is no initial path provided, select all value fields.
328-
let path = fromInitialPath ?? SmallProjectionPath(.anyValueFields)
329-
330-
let accessPath = value.getAccessPath(fromInitialPath: path)
353+
let accessPath = value.getAccessPath(fromInitialPath: fromInitialPath)
331354
switch accessPath.base {
332355
case .stack:
333356
// We don't care about read and writes from/to stack locations (because they are
@@ -347,16 +370,7 @@ private struct CollectedEffects {
347370
}
348371
}
349372
} else {
350-
// Handle non-address `value`s which are projections from a direct arguments.
351-
let path: SmallProjectionPath
352-
if let fromInitialPath = fromInitialPath {
353-
path = fromInitialPath
354-
} else if value.type.isClass {
355-
path = SmallProjectionPath(.anyValueFields).push(.anyClassField)
356-
} else {
357-
path = SmallProjectionPath(.anyValueFields).push(.anyClassField).push(.anyValueFields)
358-
}
359-
_ = findRoots.walkUp(value: value, path: path)
373+
_ = findRoots.walkUp(value: value, path: fromInitialPath)
360374
}
361375
// Because of phi-arguments, a single (non-address) `value` can come from multiple arguments.
362376
while let (arg, path) = findRoots.roots.pop() {
@@ -369,6 +383,16 @@ private struct CollectedEffects {
369383
}
370384
}
371385

386+
private func defaultPath(for value: Value) -> SmallProjectionPath {
387+
if value.type.isAddress {
388+
return SmallProjectionPath(.anyValueFields)
389+
}
390+
if value.type.isClass {
391+
return SmallProjectionPath(.anyValueFields).push(.anyClassField)
392+
}
393+
return SmallProjectionPath(.anyValueFields).push(.anyClassField).push(.anyValueFields)
394+
}
395+
372396
/// Checks if an argument escapes to some unknown user.
373397
private struct ArgumentEscapingWalker : ValueDefUseWalker, AddressDefUseWalker {
374398
var walkDownCache = WalkerCache<UnusedWalkingPath>()

SwiftCompilerSources/Sources/Optimizer/Utilities/EscapeUtils.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
382382

383383
// Class references, which are directly located in the array elements cannot escape,
384384
// because those are passed as `self` to their deinits - and `self` cannot escape in a deinit.
385-
if path.projectionPath.hasNoClassProjection {
385+
if !path.projectionPath.mayHaveClassProjection {
386386
return .continueWalk
387387
}
388388
return isEscaping
@@ -818,7 +818,7 @@ fileprivate struct EscapeWalker<V: EscapeVisitor> : ValueDefUseWalker,
818818
private func followLoads(at path: SmallProjectionPath) -> Bool {
819819
return visitor.followLoads ||
820820
// When part of a class field we have to follow loads.
821-
!path.hasNoClassProjection
821+
path.mayHaveClassProjection
822822
}
823823

824824
private func pathForArgumentEscapeChecking(_ path: SmallProjectionPath) -> SmallProjectionPath {

SwiftCompilerSources/Sources/SIL/Effects.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,41 @@ extension Function {
8888
public func getSideEffects(forArgument argument: ProjectedValue,
8989
atIndex argumentIndex: Int,
9090
withConvention convention: ArgumentConvention) -> SideEffects.GlobalEffects {
91+
var result = SideEffects.GlobalEffects()
92+
93+
// Effects are only defined for operations which don't involve a load.
94+
// In case the argument's path involves a load we need to return the global effects.
95+
if argument.value.type.isAddress {
96+
// Indirect arguments:
97+
if argument.path.mayHaveClassProjection {
98+
// For example:
99+
// bb0(%0: $*C):
100+
// %1 = load %0 // the involved load
101+
// %2 = ref_element_addr %1 // class projection
102+
return getSideEffects()
103+
}
104+
} else {
105+
// Direct arguments:
106+
if argument.path.mayHaveTwoClassProjections {
107+
// For example:
108+
// bb0(%0: $C):
109+
// %1 = ref_element_addr %1 // first class projection
110+
// %2 = load %1 // the involved load
111+
// %3 = ref_element_addr %2 // second class projection
112+
return getSideEffects()
113+
114+
} else if argument.path.mayHaveClassProjection {
115+
// For example:
116+
// bb0(%0: $C):
117+
// %1 = ref_element_addr %1 // class projection
118+
// %2 = load [take] %1 // the involved load
119+
// destroy_value %2
120+
result.ownership = getSideEffects().ownership
121+
}
122+
}
123+
91124
if let sideEffects = effects.sideEffects {
92125
/// There are computed side effects.
93-
var result = SideEffects.GlobalEffects()
94126
let argEffect = sideEffects.getArgumentEffects(for: argumentIndex)
95127
if let effectPath = argEffect.read, effectPath.mayOverlap(with: argument.path) {
96128
result.memory.read = true
@@ -499,7 +531,7 @@ public struct SideEffects : CustomStringConvertible, NoReflectionChildren {
499531
case .directGuaranteed:
500532
// Note that `directGuaranteed` still has a "destroy" effect, because an object stored in
501533
// a class property could be destroyed.
502-
if argument.path.hasNoClassProjection {
534+
if !argument.path.mayHaveClassProjection {
503535
result.ownership.destroy = false
504536
}
505537
fallthrough

SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,6 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
244244
}
245245
}
246246

247-
/// Returns true if the path does not have any class projections.
248-
/// For example:
249-
/// returns true for `v**`
250-
/// returns false for `c0`
251-
/// returns false for `**` (because '**' can have any number of class projections)
252-
public var hasNoClassProjection: Bool {
253-
return matches(pattern: Self(.anyValueFields))
254-
}
255-
256247
/// Returns true if the path has at least one class projection.
257248
/// For example:
258249
/// returns false for `v**`
@@ -268,6 +259,27 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
268259
}
269260
}
270261

262+
/// Returns true if the path may have a class projection.
263+
/// For example:
264+
/// returns false for `v**`
265+
/// returns true for `c0`
266+
/// returns true for `**` (because '**' can have any number of class projections)
267+
public var mayHaveClassProjection: Bool {
268+
return !matches(pattern: Self(.anyValueFields))
269+
}
270+
271+
/// Returns true if the path may have a class projection.
272+
/// For example:
273+
/// returns false for `v**`
274+
/// returns false for `c0`
275+
/// returns true for `c0.c1`
276+
/// returns true for `c0.**` (because '**' can have any number of class projections)
277+
/// returns true for `**` (because '**' can have any number of class projections)
278+
public var mayHaveTwoClassProjections: Bool {
279+
return !matches(pattern: Self(.anyValueFields)) &&
280+
!matches(pattern: Self(.anyValueFields).push(.anyClassField).push(.anyValueFields))
281+
}
282+
271283
/// Pops all value field components from the beginning of the path.
272284
/// For example:
273285
/// `s0.e2.3.c4.s1` -> `c4.s1`
@@ -679,17 +691,24 @@ extension SmallProjectionPath {
679691
}
680692

681693
func predicates() {
682-
testPredicate("v**", \.hasNoClassProjection, expect: true)
683-
testPredicate("c0", \.hasNoClassProjection, expect: false)
684-
testPredicate("1", \.hasNoClassProjection, expect: true)
685-
testPredicate("**", \.hasNoClassProjection, expect: false)
686-
687694
testPredicate("v**", \.hasClassProjection, expect: false)
688695
testPredicate("v**.c0.s1.v**", \.hasClassProjection, expect: true)
689696
testPredicate("c0.**", \.hasClassProjection, expect: true)
690697
testPredicate("c0.c1", \.hasClassProjection, expect: true)
691698
testPredicate("ct", \.hasClassProjection, expect: true)
692699
testPredicate("s0", \.hasClassProjection, expect: false)
700+
701+
testPredicate("v**", \.mayHaveClassProjection, expect: false)
702+
testPredicate("c0", \.mayHaveClassProjection, expect: true)
703+
testPredicate("1", \.mayHaveClassProjection, expect: false)
704+
testPredicate("**", \.mayHaveClassProjection, expect: true)
705+
706+
testPredicate("v**", \.mayHaveTwoClassProjections, expect: false)
707+
testPredicate("c0", \.mayHaveTwoClassProjections, expect: false)
708+
testPredicate("**", \.mayHaveTwoClassProjections, expect: true)
709+
testPredicate("v**.c*.s2.1.c0", \.mayHaveTwoClassProjections, expect: true)
710+
testPredicate("c*.s2.1.c0.v**", \.mayHaveTwoClassProjections, expect: true)
711+
testPredicate("v**.c*.**", \.mayHaveTwoClassProjections, expect: true)
693712
}
694713

695714
func testPredicate(_ pathStr: String, _ property: (SmallProjectionPath) -> Bool, expect: Bool) {

test/SILOptimizer/mem-behavior.sil

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class X {
1616
class Derived : X { }
1717

1818
class C {
19-
@_hasStorage @_hasInitialValue final let prop: Builtin.Int32 { get }
19+
@_hasStorage @_hasInitialValue final var prop: Builtin.Int32 { get }
2020
}
2121

2222
class Parent {
@@ -950,3 +950,33 @@ bb0 (%0 : $*C):
950950
return %3 : $()
951951
}
952952

953+
sil @do_store : $@convention(thin) (@guaranteed Parent) -> () {
954+
[%0: noescape **, read c0.v**]
955+
[global: write]
956+
}
957+
958+
// CHECK-LABEL: @test_double_class_indirection
959+
// CHECK: PAIR #4.
960+
// CHECK-NEXT: %8 = apply %7(%1) : $@convention(thin) (@guaranteed Parent) -> ()
961+
// CHECK-NEXT: %3 = ref_element_addr %1 : $Parent, #Parent.child
962+
// CHECK-NEXT: r=1,w=0
963+
// CHECK-NEXT: PAIR #5.
964+
// CHECK-NEXT: %8 = apply %7(%1) : $@convention(thin) (@guaranteed Parent) -> ()
965+
// CHECK-NEXT: %5 = ref_element_addr %2 : $C, #C.prop
966+
// CHECK-NEXT: r=1,w=1
967+
sil @test_double_class_indirection : $@convention(thin) (Builtin.Int32) -> () {
968+
bb0(%0 : $Builtin.Int32):
969+
%1 = alloc_ref $Parent
970+
%2 = alloc_ref $C
971+
%3 = ref_element_addr %1 : $Parent, #Parent.child
972+
store %2 to %3 : $*C
973+
%5 = ref_element_addr %2 : $C, #C.prop
974+
store %0 to %5 : $*Builtin.Int32
975+
976+
%f = function_ref @do_store : $@convention(thin) (@guaranteed Parent) -> ()
977+
%a = apply %f(%1) : $@convention(thin) (@guaranteed Parent) -> ()
978+
979+
%r = tuple ()
980+
return %r : $()
981+
}
982+

0 commit comments

Comments
 (0)