@@ -40,19 +40,15 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
40
40
guard let lifetimeDep = LifetimeDependence ( markDep, context) else {
41
41
continue
42
42
}
43
- guard let beginAccess = extendAccessScopes ( dependence: lifetimeDep,
44
- context) else {
45
- continue
43
+ if let arg = extendAccessScopes ( dependence: lifetimeDep, context) {
44
+ markDep. baseOperand. set ( to: arg, context)
46
45
}
47
- extendDependenceBase ( dependenceInstruction: markDep,
48
- beginAccess: beginAccess, context)
49
46
}
50
47
}
51
48
52
- // Extend all access scopes that enclose `dependence` and return the
53
- // outermost access.
54
- private func extendAccessScopes( dependence: LifetimeDependence ,
55
- _ context: FunctionPassContext ) -> BeginAccessInst ? {
49
+ // Extend all access scopes that enclose `dependence`. If dependence is on an access scope in the caller, then return
50
+ // the function argument that represents the dependence scope.
51
+ private func extendAccessScopes( dependence: LifetimeDependence , _ context: FunctionPassContext ) -> FunctionArgument ? {
56
52
log ( " Scope fixup for lifetime dependent instructions: \( dependence) " )
57
53
58
54
guard case . access( let bai) = dependence. scope else {
@@ -67,66 +63,79 @@ private func extendAccessScopes(dependence: LifetimeDependence,
67
63
_ = walker. walkDown ( root: dependence. dependentValue)
68
64
defer { range. deinitialize ( ) }
69
65
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
-
78
- // Insert original end_access instructions to prevent access scope shortening
79
- range. insert ( contentsOf: endAcceses)
80
- assert ( !range. ends. isEmpty)
81
-
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)
86
- }
87
-
88
- // Delete original end_access instructions
89
- for endAccess in endAcceses {
90
- context. erase ( instruction: endAccess)
91
- }
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
97
- }
98
- beginAccess = enclosingBeginAccess
66
+ if let arg = extendAccessScope ( beginAccess: bai, range: & range, context) {
67
+ // If the dependent value is returned, then return the FunctionArgument that it depends on.
68
+ return walker. dependsOnCaller ? arg : nil
99
69
}
100
- return beginAccess
70
+ return nil
101
71
}
102
72
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)
73
+ /// Extend this access scope to cover the dependent uses. Recursively extend outer accesses to maintain nesting.
74
+ ///
75
+ /// Note that we cannot simply rewrite the `mark_dependence` to depend on an outer access scope. For 'read' access, this
76
+ /// could let us avoid extending the inner scope, but that would not accomplish anything useful because inner 'read's
77
+ /// can always be extended up to the extent of their outer 'read' (ignoring the special case when the dependence is on a
78
+ /// caller scope, which is handled separately). A nested 'read' access can never interfere with another access in the
79
+ /// same outer 'read', because it is impossible to nest a 'modify' access within a 'read'. For 'modify' accesses,
80
+ /// however, the inner scope must be extended for correctness. A 'modify' access can interfere with other 'modify'
81
+ /// accesss in the same scope. We rely on exclusivity diagnostics to report these interferences. For example:
82
+ ///
83
+ /// sil @foo : $(@inout C) -> () {
84
+ /// bb0(%0 : $*C):
85
+ /// %a1 = begin_access [modify] %0
86
+ /// %d = apply @getDependent(%a1)
87
+ /// mark_dependence [unresolved] %d on %a1
88
+ /// end_access %a1
89
+ /// %a2 = begin_access [modify] %0
90
+ /// ...
91
+ /// end_access %a2
92
+ /// apply @useDependent(%d) // exclusivity violation
93
+ /// return
94
+ /// }
95
+ ///
96
+ /// The call to `@useDependent` is an exclusivity violation because it uses a value that depends on a 'modify'
97
+ /// access. This scope fixup pass must extend '%a1' to cover the `@useDependent` but must not extend the base of the
98
+ /// `mark_dependence` to the outer access `%0`. This ensures that exclusivity diagnostics correctly reports the
99
+ /// violation, and that subsequent optimizations do not shrink the inner access `%a1`.
100
+ private func extendAccessScope( beginAccess: BeginAccessInst , range: inout InstructionRange ,
101
+ _ context: FunctionPassContext ) -> FunctionArgument ? {
102
+ var endAcceses = [ Instruction] ( )
103
+ // Collect the original end_access instructions and extend the range to to cover them. The access scope should only be
104
+ // extended here; it may protect other memory operations.
105
+ for end in beginAccess. endInstructions {
106
+ endAcceses. append ( end)
107
+ range. insert ( end)
108
+ }
109
+ assert ( !range. ends. isEmpty)
110
+
111
+ // Create new end_access at the end of extended uses
112
+ for end in range. ends {
113
+ let endBuilder = Builder ( after: end, context)
114
+ _ = endBuilder. createEndAccess ( beginAccess: beginAccess)
115
+ }
116
+ // Delete original end_access instructions
117
+ for endAccess in endAcceses {
118
+ context. erase ( instruction: endAccess)
119
+ }
120
+ // TODO: Add SIL support for lifetime dependence and write unit test for nested access scopes
121
+ switch beginAccess. address. enclosingAccessScope {
122
+ case let . scope( enclosingBeginAccess) :
123
+ return extendAccessScope ( beginAccess: enclosingBeginAccess, range: & range, context)
124
+ case let . base( accessBase) :
125
+ if case let . argument( arg) = accessBase {
126
+ return arg
127
+ }
128
+ return nil
129
+ }
123
130
}
124
131
125
132
private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalker {
126
133
let function : Function
127
134
let context : Context
128
135
let visitor : ( Operand ) -> WalkResult
129
136
var visitedValues : ValueSet
137
+ /// Set to true if the dependence is returned from the current function.
138
+ var dependsOnCaller = false
130
139
131
140
init ( _ function: Function , _ context: Context , visitor: @escaping ( Operand ) -> WalkResult ) {
132
141
self . function = function
@@ -157,16 +166,21 @@ private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalk
157
166
158
167
mutating func escapingDependence( on operand: Operand ) -> WalkResult {
159
168
_ = visitor ( operand)
160
- return . abortWalk
169
+ // Make a best-effort attempt to extend the access scope regardless of escapes. It is possible that some mandatory
170
+ // pass between scope fixup and diagnostics will make it possible for the LifetimeDependenceDefUseWalker to analyze
171
+ // this use.
172
+ return . continueWalk
161
173
}
162
174
163
- mutating func returnedDependence( result: Operand ) -> WalkResult {
164
- return . continueWalk
175
+ mutating func returnedDependence( result operand: Operand ) -> WalkResult {
176
+ dependsOnCaller = true
177
+ return visitor ( operand)
165
178
}
166
179
167
180
mutating func returnedDependence( address: FunctionArgument ,
168
181
using operand: Operand ) -> WalkResult {
169
- return . continueWalk
182
+ dependsOnCaller = true
183
+ return visitor ( operand)
170
184
}
171
185
172
186
mutating func yieldedDependence( result: Operand ) -> WalkResult {
0 commit comments