Skip to content

Commit 3bee326

Browse files
committed
LifetimeDependenceDefUseWalker: use LocalVariableReachableUses.
1 parent ffe9c48 commit 3bee326

File tree

3 files changed

+148
-47
lines changed

3 files changed

+148
-47
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,14 @@ private struct LifetimeVariable {
317317
private struct DiagnoseDependenceWalker {
318318
let context: Context
319319
var diagnostics: DiagnoseDependence
320+
let localReachabilityCache = LocalVariableReachabilityCache()
320321
var visitedValues: ValueSet
321322

322323
var function: Function { diagnostics.function }
323324

324325
init(_ diagnostics: DiagnoseDependence, _ context: Context) {
325-
self.diagnostics = diagnostics
326326
self.context = context
327+
self.diagnostics = diagnostics
327328
self.visitedValues = ValueSet(context)
328329
}
329330

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,47 +33,66 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
3333
{ (function: Function, context: FunctionPassContext) in
3434
log(" --- Scope fixup for lifetime dependence in \(function.name)")
3535

36+
let localReachabilityCache = LocalVariableReachabilityCache()
37+
3638
for instruction in function.instructions {
3739
guard let markDep = instruction as? MarkDependenceInst else {
3840
continue
3941
}
4042
guard let lifetimeDep = LifetimeDependence(markDep, context) else {
4143
continue
4244
}
43-
if let arg = extendAccessScopes(dependence: lifetimeDep, context) {
45+
if let arg = extendAccessScopes(dependence: lifetimeDep, localReachabilityCache, context) {
4446
markDep.baseOperand.set(to: arg, context)
4547
}
4648
}
4749
}
4850

4951
/// Extend all access scopes that enclose `dependence`. If dependence is on an access scope in the caller, then return
5052
/// the function argument that represents the dependence scope.
51-
private func extendAccessScopes(dependence: LifetimeDependence, _ context: FunctionPassContext) -> FunctionArgument? {
53+
private func extendAccessScopes(dependence: LifetimeDependence,
54+
_ localReachabilityCache: LocalVariableReachabilityCache,
55+
_ context: FunctionPassContext) -> FunctionArgument? {
5256
log("Scope fixup for lifetime dependent instructions: \(dependence)")
5357

54-
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 {
5566
return nil
5667
}
57-
let function = bai.parentFunction
58-
var range = InstructionRange(begin: bai, context)
59-
var walker = LifetimeDependenceScopeFixupWalker(function, context) {
60-
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+
}
6179
return .continueWalk
6280
}
6381
defer {walker.deinitialize()}
82+
6483
_ = walker.walkDown(root: dependence.dependentValue)
65-
defer {range.deinitialize()}
6684

67-
// Lifetime dependenent uses may no be dominated by the access. The dependent value may be used by a phi or stored
68-
// into a memory location. The access may be conditional relative to such uses. If this is the case, then the
69-
// instruction range must include the function entry.
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.
7088
let firstInst = function.entryBlock.instructions.first!
71-
if firstInst != bai, range.contains(firstInst) {
89+
if firstInst != beginAccess, accessRange.contains(firstInst) {
7290
return nil
7391
}
74-
if let arg = extendAccessScope(beginAccess: bai, range: &range, context) {
92+
if let arg = extendAccessScope(beginAccess: beginAccess, range: &accessRange, context) {
7593
// If the dependent value is returned, then return the FunctionArgument that it depends on.
76-
return walker.dependsOnCaller ? arg : nil
94+
assert(walker.dependsOnCaller)
95+
return arg
7796
}
7897
return nil
7998
}
@@ -126,9 +145,20 @@ private func extendAccessScope(beginAccess: BeginAccessInst, range: inout Instru
126145
assert(!range.ends.isEmpty)
127146

128147
// Create new end_access at the end of extended uses
148+
var dependsOnCaller = false
129149
for end in range.ends {
130-
let endBuilder = Builder(after: end, context)
131-
endBuilder.createEndAccess(beginAccess: beginAccess)
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
156+
}
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)
161+
}
132162
}
133163
// Delete original end_access instructions
134164
for endAccess in endAcceses {
@@ -139,7 +169,7 @@ private func extendAccessScope(beginAccess: BeginAccessInst, range: inout Instru
139169
case let .scope(enclosingBeginAccess):
140170
return extendAccessScope(beginAccess: enclosingBeginAccess, range: &range, context)
141171
case let .base(accessBase):
142-
if case let .argument(arg) = accessBase {
172+
if case let .argument(arg) = accessBase, dependsOnCaller {
143173
return arg
144174
}
145175
return nil
@@ -150,14 +180,18 @@ private struct LifetimeDependenceScopeFixupWalker : LifetimeDependenceDefUseWalk
150180
let function: Function
151181
let context: Context
152182
let visitor: (Operand) -> WalkResult
183+
let localReachabilityCache: LocalVariableReachabilityCache
153184
var visitedValues: ValueSet
185+
154186
/// Set to true if the dependence is returned from the current function.
155187
var dependsOnCaller = false
156188

157-
init(_ function: Function, _ context: Context, visitor: @escaping (Operand) -> WalkResult) {
189+
init(_ function: Function, _ localReachabilityCache: LocalVariableReachabilityCache, _ context: Context,
190+
visitor: @escaping (Operand) -> WalkResult) {
158191
self.function = function
159192
self.context = context
160193
self.visitor = visitor
194+
self.localReachabilityCache = localReachabilityCache
161195
self.visitedValues = ValueSet(context)
162196
}
163197

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@
5454

5555
import SIL
5656

57+
private let verbose = true
58+
59+
private func log(_ message: @autoclosure () -> String) {
60+
if verbose {
61+
print("### \(message())")
62+
}
63+
}
64+
5765
/// Walk up the value dependence chain to find the best-effort
5866
/// variable declaration. Typically called while diagnosing an error.
5967
///
@@ -351,7 +359,8 @@ extension LifetimeDependence.Scope {
351359
case let .argument(arg):
352360
if arg.convention.isIndirectIn {
353361
self = .initialized(initialAddress: arg, initializingStore: nil)
354-
} else if arg.convention.isInout {
362+
} else if arg.convention.isIndirectOut {
363+
// TODO: verify that @out values are never reassigned.
355364
self = .caller(arg)
356365
} else {
357366
// Note: we do not expect arg.convention.isInout because
@@ -788,18 +797,26 @@ protocol LifetimeDependenceDefUseWalker : ForwardingDefUseWalker,
788797
AddressUseVisitor {
789798
var function: Function { get }
790799

800+
/// Dependence tracking through local variables.
801+
var localReachabilityCache: LocalVariableReachabilityCache { get }
802+
791803
mutating func leafUse(of operand: Operand) -> WalkResult
792804

793805
mutating func escapingDependence(on operand: Operand) -> WalkResult
794806

795807
mutating func returnedDependence(result: Operand) -> WalkResult
796808

797-
mutating func returnedDependence(address: FunctionArgument, using: Operand)
798-
-> WalkResult
809+
mutating func returnedDependence(address: FunctionArgument, using: Operand) -> WalkResult
799810

800811
mutating func yieldedDependence(result: Operand) -> WalkResult
801812
}
802813

814+
extension LifetimeDependenceDefUseWalker {
815+
// Use a distict context name to avoid rdar://123424566 (Unable to open existential)
816+
var walkerContext: Context { context }
817+
}
818+
819+
// Start a forward walk.
803820
extension LifetimeDependenceDefUseWalker {
804821
mutating func walkDown(root: Value) -> WalkResult {
805822
if root.type.isAddress {
@@ -1062,42 +1079,91 @@ extension LifetimeDependenceDefUseWalker {
10621079
into address: Value) -> WalkResult {
10631080
assert(address.type.isAddress)
10641081

1065-
// TODO_reachingdef: Call reaching-def analysis on the local
1066-
// variable that defines `address` (the analysis can be limited to
1067-
// this store). Then find all reachable uses from this store.
1082+
var allocation: Value?
10681083
switch address.accessBase {
10691084
case let .box(projectBox):
1070-
if let allocBox = projectBox.box as? AllocBoxInst {
1071-
if !needWalk(for: allocBox) {
1072-
return .continueWalk
1073-
}
1074-
return walkDownUses(of: allocBox, using: operand)
1075-
}
1076-
break
1085+
allocation = projectBox.box.referenceRoot
10771086
case let .stack(allocStack):
1078-
if !needWalk(for: allocStack) {
1079-
return .continueWalk
1080-
}
1081-
return walkDownAddressUses(of: allocStack)
1087+
allocation = allocStack
10821088
case let .argument(arg):
1083-
if arg.convention.isIndirectIn {
1084-
if !needWalk(for: arg) {
1085-
return .continueWalk
1086-
}
1087-
return walkDownAddressUses(of: arg)
1088-
}
1089-
if arg.convention.isIndirectOut, !arg.type.isEscapable {
1089+
if arg.convention.isIndirectIn || arg.convention.isInout {
1090+
allocation = arg
1091+
} else if arg.convention.isIndirectOut, !arg.type.isEscapable {
10901092
return returnedDependence(address: arg, using: operand)
10911093
}
10921094
break
10931095
case .global, .class, .tail, .yield, .pointer, .unidentified:
10941096
break
10951097
}
1098+
if let allocation = allocation {
1099+
if !allocation.type.objectType.isEscapable {
1100+
return visitLocalStore(allocation: allocation, storedOperand: operand, storeAddress: address)
1101+
}
1102+
}
1103+
if address.type.objectType.isEscapable {
1104+
return .continueWalk
1105+
}
10961106
return escapingDependence(on: operand)
10971107
}
10981108

1099-
private mutating func visitAppliedUse(of operand: Operand,
1100-
by apply: FullApplySite) -> WalkResult {
1109+
private mutating func visitLocalStore(allocation: Value, storedOperand: Operand, storeAddress: Value) -> WalkResult {
1110+
guard let localReachability = localReachabilityCache.reachability(for: allocation, walkerContext) else {
1111+
return escapingDependence(on: storedOperand)
1112+
}
1113+
var accessStack = Stack<LocalVariableAccess>(walkerContext)
1114+
defer { accessStack.deinitialize() }
1115+
1116+
// Get the local variable access that encloses this store.
1117+
var storeAccess = storedOperand.instruction
1118+
if case let .scope(beginAccess) = storeAddress.enclosingAccessScope {
1119+
storeAccess = beginAccess
1120+
}
1121+
if !localReachability.gatherAllReachableUses(of: storeAccess, in: &accessStack) {
1122+
return escapingDependence(on: storedOperand)
1123+
}
1124+
for localAccess in accessStack {
1125+
if visitLocalAccess(allocation: allocation, localAccess: localAccess, initialValue: storedOperand) == .abortWalk {
1126+
return .abortWalk
1127+
}
1128+
}
1129+
return .continueWalk
1130+
}
1131+
1132+
private mutating func visitLocalAccess(allocation: Value, localAccess: LocalVariableAccess, initialValue: Operand)
1133+
-> WalkResult {
1134+
switch localAccess.kind {
1135+
case .beginAccess:
1136+
return scopedAddressUse(of: localAccess.operand!)
1137+
case .load:
1138+
switch localAccess.instruction! {
1139+
case let load as LoadInst:
1140+
return loadedAddressUse(of: localAccess.operand!, into: load)
1141+
case let load as LoadBorrowInst:
1142+
return loadedAddressUse(of: localAccess.operand!, into: load)
1143+
case let copyAddr as SourceDestAddrInstruction:
1144+
return loadedAddressUse(of: localAccess.operand!, into: copyAddr.destinationOperand)
1145+
default:
1146+
return .abortWalk
1147+
}
1148+
case .store:
1149+
let si = localAccess.operand!.instruction as! StoringInstruction
1150+
assert(si.sourceOperand == initialValue, "the only reachable store should be the current assignment")
1151+
case .apply:
1152+
return visitAppliedUse(of: localAccess.operand!, by: localAccess.instruction as! FullApplySite)
1153+
case .escape:
1154+
log("Local variable: \(allocation)\n escapes at: \(localAccess.instruction!)")
1155+
return escapingDependence(on: localAccess.operand!)
1156+
case .outgoingArgument:
1157+
let arg = allocation as! FunctionArgument
1158+
assert(arg.type.isAddress, "returned local must be allocated with an indirect argument")
1159+
return returnedDependence(address: arg, using: initialValue)
1160+
case .incomingArgument:
1161+
fatalError("Incoming arguments are never reachable")
1162+
}
1163+
return .continueWalk
1164+
}
1165+
1166+
private mutating func visitAppliedUse(of operand: Operand, by apply: FullApplySite) -> WalkResult {
11011167
if let conv = apply.convention(of: operand), conv.isIndirectOut {
11021168
return leafUse(of: operand)
11031169
}
@@ -1169,9 +1235,9 @@ let lifetimeDependenceRootTest = FunctionTest("lifetime_dependence_root") {
11691235

11701236
private struct LifetimeDependenceUsePrinter : LifetimeDependenceDefUseWalker {
11711237
let context: Context
1238+
let function: Function
1239+
let localReachabilityCache = LocalVariableReachabilityCache()
11721240
var visitedValues: ValueSet
1173-
1174-
var function: Function
11751241

11761242
init(function: Function, _ context: Context) {
11771243
self.context = context

0 commit comments

Comments
 (0)