Skip to content

Commit 1122cc4

Browse files
committed
LifetimeDependenceScopeFixup: fix handling of returned dependence
Only rewrite the mark_dependence to depend on the function argument when the dependent value is actually returned. Also, find all uses even if an escaping use is seen.
1 parent 8aa1d91 commit 1122cc4

File tree

1 file changed

+77
-63
lines changed

1 file changed

+77
-63
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 77 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,15 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
4040
guard let lifetimeDep = LifetimeDependence(markDep, context) else {
4141
continue
4242
}
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)
4645
}
47-
extendDependenceBase(dependenceInstruction: markDep,
48-
beginAccess: beginAccess, context)
4946
}
5047
}
5148

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? {
5652
log("Scope fixup for lifetime dependent instructions: \(dependence)")
5753

5854
guard case .access(let bai) = dependence.scope else {
@@ -67,66 +63,79 @@ private func extendAccessScopes(dependence: LifetimeDependence,
6763
_ = walker.walkDown(root: dependence.dependentValue)
6864
defer {range.deinitialize()}
6965

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
9969
}
100-
return beginAccess
70+
return nil
10171
}
10272

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+
}
123130
}
124131

125132
private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalker {
126133
let function: Function
127134
let context: Context
128135
let visitor: (Operand) -> WalkResult
129136
var visitedValues: ValueSet
137+
/// Set to true if the dependence is returned from the current function.
138+
var dependsOnCaller = false
130139

131140
init(_ function: Function, _ context: Context, visitor: @escaping (Operand) -> WalkResult) {
132141
self.function = function
@@ -157,16 +166,21 @@ private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalk
157166

158167
mutating func escapingDependence(on operand: Operand) -> WalkResult {
159168
_ = 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
161173
}
162174

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)
165178
}
166179

167180
mutating func returnedDependence(address: FunctionArgument,
168181
using operand: Operand) -> WalkResult {
169-
return .continueWalk
182+
dependsOnCaller = true
183+
return visitor(operand)
170184
}
171185

172186
mutating func yieldedDependence(result: Operand) -> WalkResult {

0 commit comments

Comments
 (0)