@@ -31,7 +31,9 @@ private func log(_ message: @autoclosure () -> String) {
31
31
let lifetimeDependenceScopeFixupPass = FunctionPass (
32
32
name: " lifetime-dependence-scope-fixup " )
33
33
{ ( function: Function , context: FunctionPassContext ) in
34
- log ( " Scope fixup for lifetime dependence in \( function. name) " )
34
+ log ( " --- Scope fixup for lifetime dependence in \( function. name) " )
35
+
36
+ let localReachabilityCache = LocalVariableReachabilityCache ( )
35
37
36
38
for instruction in function. instructions {
37
39
guard let markDep = instruction as? MarkDependenceInst else {
@@ -40,98 +42,156 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
40
42
guard let lifetimeDep = LifetimeDependence ( markDep, context) else {
41
43
continue
42
44
}
43
- guard let beginAccess = extendAccessScopes ( dependence: lifetimeDep,
44
- context) else {
45
- continue
45
+ if let arg = extendAccessScopes ( dependence: lifetimeDep, localReachabilityCache, context) {
46
+ markDep. baseOperand. set ( to: arg, context)
46
47
}
47
- extendDependenceBase ( dependenceInstruction: markDep,
48
- beginAccess: beginAccess, context)
49
48
}
50
49
}
51
50
52
- // Extend all access scopes that enclose `dependence` and return the
53
- // outermost access .
51
+ /// Extend all access scopes that enclose `dependence`. If dependence is on an access scope in the caller, then return
52
+ /// the function argument that represents the dependence scope .
54
53
private func extendAccessScopes( dependence: LifetimeDependence ,
55
- _ context: FunctionPassContext ) -> BeginAccessInst ? {
54
+ _ localReachabilityCache: LocalVariableReachabilityCache ,
55
+ _ context: FunctionPassContext ) -> FunctionArgument ? {
56
56
log ( " Scope fixup for lifetime dependent instructions: \( dependence) " )
57
57
58
- guard case . access( let bai) = dependence. scope else {
58
+ guard case . access( let beginAccess) = dependence. scope else {
59
+ return nil
60
+ }
61
+ let function = beginAccess. parentFunction
62
+
63
+ // Get the range accessBase lifetime. The accessRange cannot exceed this without producing invalid SIL.
64
+ guard var ownershipRange = AddressOwnershipLiveRange . compute ( for: beginAccess. address, at: beginAccess,
65
+ localReachabilityCache, context) else {
59
66
return nil
60
67
}
61
- var range = InstructionRange ( begin: bai, context)
62
- var walker = LifetimeDependenceScopeFixupWalker ( bai. parentFunction, context) {
63
- range. insert ( $0. instruction)
68
+ defer { ownershipRange. deinitialize ( ) }
69
+
70
+ var accessRange = InstructionRange ( begin: beginAccess, context)
71
+ defer { accessRange. deinitialize ( ) }
72
+
73
+ var walker = LifetimeDependenceScopeFixupWalker ( function, localReachabilityCache, context) {
74
+ // Do not extend the accessRange past the ownershipRange.
75
+ let dependentInst = $0. instruction
76
+ if ownershipRange. coversUse ( dependentInst) {
77
+ accessRange. insert ( dependentInst)
78
+ }
64
79
return . continueWalk
65
80
}
66
81
defer { walker. deinitialize ( ) }
82
+
67
83
_ = walker. walkDown ( root: dependence. dependentValue)
68
- defer { range. deinitialize ( ) }
69
-
70
- var beginAccess = bai
71
- while ( true ) {
72
- var endAcceses = [ Instruction] ( )
73
- // Collect original end_access instructions
74
- for end in beginAccess. endInstructions {
75
- endAcceses. append ( end)
76
- }
77
84
78
- // Insert original end_access instructions to prevent access scope shortening
79
- range. insert ( contentsOf: endAcceses)
80
- assert ( !range. ends. isEmpty)
85
+ // Lifetime dependenent uses may not be dominated by the access. The dependent value may be used by a phi or stored
86
+ // into a memory location. The access may be conditional relative to such uses. If any use was not dominated, then
87
+ // `accessRange` will include the function entry.
88
+ let firstInst = function. entryBlock. instructions. first!
89
+ if firstInst != beginAccess, accessRange. contains ( firstInst) {
90
+ return nil
91
+ }
92
+ if let arg = extendAccessScope ( beginAccess: beginAccess, range: & accessRange, context) {
93
+ // If the dependent value is returned, then return the FunctionArgument that it depends on.
94
+ assert ( walker. dependsOnCaller)
95
+ return arg
96
+ }
97
+ return nil
98
+ }
81
99
82
- // Create new end_access at the end of extended uses
83
- for end in range. ends {
84
- let endBuilder = Builder ( after: end, context)
85
- _ = endBuilder. createEndAccess ( beginAccess: beginAccess)
100
+ /// Extend this access scope to cover the dependent uses. Recursively extend outer accesses to maintain nesting.
101
+ ///
102
+ /// Note that we cannot simply rewrite the `mark_dependence` to depend on an outer access scope. For 'read' access, this
103
+ /// could let us avoid extending the inner scope, but that would not accomplish anything useful because inner 'read's
104
+ /// can always be extended up to the extent of their outer 'read' (ignoring the special case when the dependence is on a
105
+ /// caller scope, which is handled separately). A nested 'read' access can never interfere with another access in the
106
+ /// same outer 'read', because it is impossible to nest a 'modify' access within a 'read'. For 'modify' accesses,
107
+ /// however, the inner scope must be extended for correctness. A 'modify' access can interfere with other 'modify'
108
+ /// accesss in the same scope. We rely on exclusivity diagnostics to report these interferences. For example:
109
+ ///
110
+ /// sil @foo : $(@inout C) -> () {
111
+ /// bb0(%0 : $*C):
112
+ /// %a1 = begin_access [modify] %0
113
+ /// %d = apply @getDependent(%a1)
114
+ /// mark_dependence [unresolved] %d on %a1
115
+ /// end_access %a1
116
+ /// %a2 = begin_access [modify] %0
117
+ /// ...
118
+ /// end_access %a2
119
+ /// apply @useDependent(%d) // exclusivity violation
120
+ /// return
121
+ /// }
122
+ ///
123
+ /// The call to `@useDependent` is an exclusivity violation because it uses a value that depends on a 'modify'
124
+ /// access. This scope fixup pass must extend '%a1' to cover the `@useDependent` but must not extend the base of the
125
+ /// `mark_dependence` to the outer access `%0`. This ensures that exclusivity diagnostics correctly reports the
126
+ /// violation, and that subsequent optimizations do not shrink the inner access `%a1`.
127
+ private func extendAccessScope( beginAccess: BeginAccessInst , range: inout InstructionRange ,
128
+ _ context: FunctionPassContext ) -> FunctionArgument ? {
129
+ var endAcceses = [ Instruction] ( )
130
+ // Collect the original end_access instructions and extend the range to to cover them. The resulting access scope must
131
+ // cover the original scope because it may protect other memory operations.
132
+ var requiresExtension = false
133
+ for end in beginAccess. endInstructions {
134
+ endAcceses. append ( end)
135
+ if range. contains ( end) {
136
+ // If any end_access is inside the new range, then all end_accesses must be rewritten.
137
+ requiresExtension = true
138
+ } else {
139
+ range. insert ( end)
86
140
}
141
+ }
142
+ if !requiresExtension {
143
+ return nil
144
+ }
145
+ assert ( !range. ends. isEmpty)
87
146
88
- // Delete original end_access instructions
89
- for endAccess in endAcceses {
90
- context. erase ( instruction: endAccess)
147
+ // Create new end_access at the end of extended uses
148
+ var dependsOnCaller = false
149
+ for end in range. ends {
150
+ let location = end. location. autoGenerated
151
+ if end is ReturnInst {
152
+ dependsOnCaller = true
153
+ let endAccess = Builder ( before: end, location: location, context) . createEndAccess ( beginAccess: beginAccess)
154
+ range. insert ( endAccess)
155
+ continue
91
156
}
92
-
93
- // TODO: Add SIL support for lifetime dependence and write unit test
94
- // for nested access scopes
95
- guard case let . scope( enclosingBeginAccess) = beginAccess. address. enclosingAccessScope else {
96
- break
157
+ Builder . insert ( after: end, location: location, context) {
158
+ let endAccess = $0. createEndAccess ( beginAccess: beginAccess)
159
+ // This scope should be nested in any outer scopes.
160
+ range. insert ( endAccess)
97
161
}
98
- beginAccess = enclosingBeginAccess
99
162
}
100
- return beginAccess
101
- }
102
-
103
- /// Rewrite the mark_dependence to depend on the outermost access
104
- /// scope now that the nested scopes have all been extended.
105
- private func extendDependenceBase( dependenceInstruction: MarkDependenceInst ,
106
- beginAccess: BeginAccessInst ,
107
- _ context: FunctionPassContext ) {
108
- guard case let . base( accessBase) = beginAccess. address. enclosingAccessScope
109
- else {
110
- fatalError ( " this must be the outer-most access scope " )
111
- }
112
- // If the outermost access is in the caller, then depende on the
113
- // address argument.
114
- let baseAddress : Value
115
- switch accessBase {
116
- case let . argument( arg) :
117
- assert ( arg. type. isAddress)
118
- baseAddress = arg
119
- default :
120
- baseAddress = beginAccess
121
- }
122
- dependenceInstruction. baseOperand. set ( to: baseAddress, context)
163
+ // Delete original end_access instructions
164
+ for endAccess in endAcceses {
165
+ context. erase ( instruction: endAccess)
166
+ }
167
+ // TODO: Add SIL support for lifetime dependence and write unit test for nested access scopes
168
+ switch beginAccess. address. enclosingAccessScope {
169
+ case let . scope( enclosingBeginAccess) :
170
+ return extendAccessScope ( beginAccess: enclosingBeginAccess, range: & range, context)
171
+ case let . base( accessBase) :
172
+ if case let . argument( arg) = accessBase, dependsOnCaller {
173
+ return arg
174
+ }
175
+ return nil
176
+ }
123
177
}
124
178
125
179
private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalker {
126
180
let function : Function
127
181
let context : Context
128
182
let visitor : ( Operand ) -> WalkResult
183
+ let localReachabilityCache : LocalVariableReachabilityCache
129
184
var visitedValues : ValueSet
130
185
131
- init ( _ function: Function , _ context: Context , visitor: @escaping ( Operand ) -> WalkResult ) {
186
+ /// Set to true if the dependence is returned from the current function.
187
+ var dependsOnCaller = false
188
+
189
+ init ( _ function: Function , _ localReachabilityCache: LocalVariableReachabilityCache , _ context: Context ,
190
+ visitor: @escaping ( Operand ) -> WalkResult ) {
132
191
self . function = function
133
192
self . context = context
134
193
self . visitor = visitor
194
+ self . localReachabilityCache = localReachabilityCache
135
195
self . visitedValues = ValueSet ( context)
136
196
}
137
197
@@ -157,16 +217,21 @@ private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalk
157
217
158
218
mutating func escapingDependence( on operand: Operand ) -> WalkResult {
159
219
_ = visitor ( operand)
160
- return . abortWalk
220
+ // Make a best-effort attempt to extend the access scope regardless of escapes. It is possible that some mandatory
221
+ // pass between scope fixup and diagnostics will make it possible for the LifetimeDependenceDefUseWalker to analyze
222
+ // this use.
223
+ return . continueWalk
161
224
}
162
225
163
- mutating func returnedDependence( result: Operand ) -> WalkResult {
164
- return . continueWalk
226
+ mutating func returnedDependence( result operand: Operand ) -> WalkResult {
227
+ dependsOnCaller = true
228
+ return visitor ( operand)
165
229
}
166
230
167
231
mutating func returnedDependence( address: FunctionArgument ,
168
232
using operand: Operand ) -> WalkResult {
169
- return . continueWalk
233
+ dependsOnCaller = true
234
+ return visitor ( operand)
170
235
}
171
236
172
237
mutating func yieldedDependence( result: Operand ) -> WalkResult {
0 commit comments