|
54 | 54 |
|
55 | 55 | import SIL
|
56 | 56 |
|
| 57 | +private let verbose = true |
| 58 | + |
| 59 | +private func log(_ message: @autoclosure () -> String) { |
| 60 | + if verbose { |
| 61 | + print("### \(message())") |
| 62 | + } |
| 63 | +} |
| 64 | + |
57 | 65 | /// Walk up the value dependence chain to find the best-effort
|
58 | 66 | /// variable declaration. Typically called while diagnosing an error.
|
59 | 67 | ///
|
@@ -351,7 +359,8 @@ extension LifetimeDependence.Scope {
|
351 | 359 | case let .argument(arg):
|
352 | 360 | if arg.convention.isIndirectIn {
|
353 | 361 | 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. |
355 | 364 | self = .caller(arg)
|
356 | 365 | } else {
|
357 | 366 | // Note: we do not expect arg.convention.isInout because
|
@@ -788,18 +797,26 @@ protocol LifetimeDependenceDefUseWalker : ForwardingDefUseWalker,
|
788 | 797 | AddressUseVisitor {
|
789 | 798 | var function: Function { get }
|
790 | 799 |
|
| 800 | + /// Dependence tracking through local variables. |
| 801 | + var localReachabilityCache: LocalVariableReachabilityCache { get } |
| 802 | + |
791 | 803 | mutating func leafUse(of operand: Operand) -> WalkResult
|
792 | 804 |
|
793 | 805 | mutating func escapingDependence(on operand: Operand) -> WalkResult
|
794 | 806 |
|
795 | 807 | mutating func returnedDependence(result: Operand) -> WalkResult
|
796 | 808 |
|
797 |
| - mutating func returnedDependence(address: FunctionArgument, using: Operand) |
798 |
| - -> WalkResult |
| 809 | + mutating func returnedDependence(address: FunctionArgument, using: Operand) -> WalkResult |
799 | 810 |
|
800 | 811 | mutating func yieldedDependence(result: Operand) -> WalkResult
|
801 | 812 | }
|
802 | 813 |
|
| 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. |
803 | 820 | extension LifetimeDependenceDefUseWalker {
|
804 | 821 | mutating func walkDown(root: Value) -> WalkResult {
|
805 | 822 | if root.type.isAddress {
|
@@ -1062,42 +1079,91 @@ extension LifetimeDependenceDefUseWalker {
|
1062 | 1079 | into address: Value) -> WalkResult {
|
1063 | 1080 | assert(address.type.isAddress)
|
1064 | 1081 |
|
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? |
1068 | 1083 | switch address.accessBase {
|
1069 | 1084 | 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 |
1077 | 1086 | case let .stack(allocStack):
|
1078 |
| - if !needWalk(for: allocStack) { |
1079 |
| - return .continueWalk |
1080 |
| - } |
1081 |
| - return walkDownAddressUses(of: allocStack) |
| 1087 | + allocation = allocStack |
1082 | 1088 | 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 { |
1090 | 1092 | return returnedDependence(address: arg, using: operand)
|
1091 | 1093 | }
|
1092 | 1094 | break
|
1093 | 1095 | case .global, .class, .tail, .yield, .pointer, .unidentified:
|
1094 | 1096 | break
|
1095 | 1097 | }
|
| 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 | + } |
1096 | 1106 | return escapingDependence(on: operand)
|
1097 | 1107 | }
|
1098 | 1108 |
|
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 { |
1101 | 1167 | if let conv = apply.convention(of: operand), conv.isIndirectOut {
|
1102 | 1168 | return leafUse(of: operand)
|
1103 | 1169 | }
|
@@ -1169,9 +1235,9 @@ let lifetimeDependenceRootTest = FunctionTest("lifetime_dependence_root") {
|
1169 | 1235 |
|
1170 | 1236 | private struct LifetimeDependenceUsePrinter : LifetimeDependenceDefUseWalker {
|
1171 | 1237 | let context: Context
|
| 1238 | + let function: Function |
| 1239 | + let localReachabilityCache = LocalVariableReachabilityCache() |
1172 | 1240 | var visitedValues: ValueSet
|
1173 |
| - |
1174 |
| - var function: Function |
1175 | 1241 |
|
1176 | 1242 | init(function: Function, _ context: Context) {
|
1177 | 1243 | self.context = context
|
|
0 commit comments