//===--- BorrowUtils.swift - Utilities for borrow scopes ------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // Utilities that model Ownership SSA (OSSA) borrow scopes. // // A BorrowingInstruction borrows one or more operands over a new // borrow scope, up to its scope-ending uses. This is typically // checked during a def-use walk. // // %val = some owned value // %store = store_borrow %val to %addr // borrowing instruction // ... // borrow scope // end_borrow %store // scope-ending use // // A BeginBorrowValue introduces a guaranteed OSSA lifetime. It // begins a new borrow scope that ends at its scope-ending uses. A // begin-borrow value may be defined by a borrowing instruction: // // %begin = begin_borrow %val // %begin borrows %val // ... // borrow scope // end_borrow %begin // scope-ending use // // Other kinds of BeginBorrowValues, however, like block arguments and // `load_borrow`, are not borrowing instructions. BeginBorrowValues // are typically checked during a use-def walk. Here, walking up from // `%forward` finds `%begin` as the introducer of its guaranteed // lifetime: // // %begin = load_borrow %addr // BeginBorrowValue // %forward = struct (%begin) // forwards a guaranteed value // ... // end_borrow %begin // scope-ending use // // Every guaranteed OSSA value has a set of borrow introducers, each // of which dominates the value and introduces a borrow scope that // encloses all forwarded uses of the guaranteed value. // // %1 = begin_borrow %0 // borrow introducer for %2 // %2 = begin_borrow %1 // borrow introducer for %3 // %3 = struct (%1, %2) // forwards two guaranteed values // ... all forwarded uses of %3 // end_borrow %1 // scope-ending use // end_borrow %2 // scope-ending use // // Inner borrow scopes may be nested in outer borrow scopes: // // %1 = begin_borrow %0 // borrow introducer for %2 // %2 = begin_borrow %1 // borrow introducer for %3 // %3 = struct (%2) // ... all forwarded uses of %3 // end_borrow %2 // scope-ending use of %2 // end_borrow %1 // scope-ending use of %1 // // Walking up the nested OSSA lifetimes requires iteratively querying // "enclosing values" until either a guaranteed function argument or // owned value is reached. Like a borrow introducer, an enclosing // value dominates all values that it encloses. // // Borrow Introducer Enclosing Value // ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~ // %0 = some owned value invalid none // %1 = begin_borrow %0 %1 %0 // %2 = begin_borrow %1 %2 %1 // %3 = struct (%2) %2 %2 // // The borrow introducer of a guaranteed phi is not directly // determined by a use-def walk because an introducer must dominate // all uses in its scope: // // Borrow Introducer Enclosing Value // ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~ // // cond_br ..., bb1, bb2 // bb1: // %2 = begin_borrow %0 %2 %0 // %3 = struct (%2) %2 %2 // br bb3(%2, %3) // bb2: // %6 = begin_borrow %0 %6 %0 // %7 = struct (%6) %6 %6 // br bb3(%6, %7) // bb3(%reborrow: @reborrow, %reborrow %0 // %phi: @guaranteed): %phi %reborrow // // `%reborrow` is an outer-adjacent phi to `%phi` because it encloses // `%phi`. `%phi` is an inner-adjacent phi to `%reborrow` because its // uses keep `%reborrow` alive. An outer-adjacent phi is either an // owned value or a reborrow. An inner-adjacent phi is either a // reborrow or a guaranteed forwarding phi. Here is an example of an // owned outer-adjacent phi with an inner-adjacent reborrow: // // Borrow Introducer Enclosing Value // ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~ // // cond_br ..., bb1, bb2 // bb1: // %1 = owned value // %2 = begin_borrow %1 %2 %1 // br bb3(%1, %2) // bb2: // %5 = owned value // %6 = begin_borrow %5 %6 %5 // br bb3(%5, %6) // bb3(%phi: @owned, invalid none // %reborrow: @reborrow): %reborrow %phi // // In OSSA, each owned value defines a separate lifetime. It is // consumed on all paths by a direct use. Owned lifetimes can, // however, be nested within a borrow scope. In this case, finding the // scope-ending uses requires traversing owned forwarding // instructions: // // %1 = partial_apply %f(%0) // borrowing instruction borrows %0 and produces // // an owned closure value. // %2 = struct (%1) // end owned lifetime %1, begin owned lifetime %2 // destroy_value %2 // end owned lifetime %2, scope-ending use of %1 // // // TODO: These utilities should be integrated with OSSA SIL verification and // guaranteed to be compelete (produce known results for all legal SIL // patterns). // ===----------------------------------------------------------------------===// import SIL /// A scoped instruction that borrows one or more operands. /// /// If this instruction produces a borrowed value, then /// BeginBorrowValue(resultOf: self) != nil. /// /// This does not include instructions like `apply` and `try_apply` that /// instantaneously borrow a value from the caller. /// /// This does not include `load_borrow` because it borrows a memory /// location, not the value of its operand. /// /// Note: This must handle all instructions with a .borrow operand ownership. /// /// Note: mark_dependence is a BorrowingInstruction because it creates /// a borrow scope for its base operand. Its result, however, is not a /// BeginBorrowValue. It is instead a ForwardingInstruction relative /// to its value operand. /// /// TODO: replace BorrowIntroducingInstruction /// /// TODO: Add non-escaping MarkDependence. enum BorrowingInstruction : CustomStringConvertible, Hashable { case beginBorrow(BeginBorrowInst) case storeBorrow(StoreBorrowInst) case beginApply(BeginApplyInst) case partialApply(PartialApplyInst) case markDependence(MarkDependenceInst) case startAsyncLet(BuiltinInst) init?(_ inst: Instruction) { switch inst { case let bbi as BeginBorrowInst: self = .beginBorrow(bbi) case let sbi as StoreBorrowInst: self = .storeBorrow(sbi) case let bai as BeginApplyInst: self = .beginApply(bai) case let pai as PartialApplyInst where pai.isOnStack: self = .partialApply(pai) case let mdi as MarkDependenceInst: self = .markDependence(mdi) case let bi as BuiltinInst where bi.id == .StartAsyncLetWithLocalBuffer: self = .startAsyncLet(bi) default: return nil } } var instruction: Instruction { switch self { case .beginBorrow(let bbi): return bbi case .storeBorrow(let sbi): return sbi case .beginApply(let bai): return bai case .partialApply(let pai): return pai case .markDependence(let mdi): return mdi case .startAsyncLet(let bi): return bi } } /// Visit the operands that end the local borrow scope. /// /// Note: When this instruction's result is BeginBorrowValue the /// scopeEndingOperand may include reborrows. To find all uses that /// contribute to liveness, the caller needs to determine whether an /// incoming value dominates or is consumed by an outer adjacent /// phi. See InteriorLiveness. /// /// TODO: to hande reborrow-extended uses, migrate ExtendedLiveness /// to SwiftCompilerSources. /// /// TODO: Handle .partialApply and .markDependence forwarded uses /// that are phi operands. Currently, partial_apply [on_stack] /// and mark_dependence [nonescaping] cannot be cloned, so walking /// through the phi safely returns dominated scope-ending operands. /// Instead, this could report the phi as a scope-ending use, and /// the client could decide whether to walk through them or to /// construct reborrow-extended liveness. /// /// TODO: For instructions that are not a BeginBorrowValue, verify /// that scope ending instructions exist on all paths. These /// instructions should be complete after SILGen and never cloned to /// produce phis. func visitScopeEndingOperands(_ context: Context, visitor: @escaping (Operand) -> WalkResult) -> WalkResult { switch self { case .beginBorrow, .storeBorrow: let svi = instruction as! SingleValueInstruction return svi.uses.filterUsers(ofType: EndBorrowInst.self).walk { visitor($0) } case .beginApply(let bai): return bai.token.uses.walk { return visitor($0) } case .partialApply, .markDependence: let svi = instruction as! SingleValueInstruction assert(svi.ownership == .owned) return visitForwardedUses(introducer: svi, context) { switch $0 { case let .operand(operand): if operand.endsLifetime { return visitor(operand) } return .continueWalk case let .deadValue(_, operand): if let operand = operand { assert(!operand.endsLifetime, "a dead forwarding instruction cannot end a lifetime") } return .continueWalk } } case .startAsyncLet(let builtin): return builtin.uses.walk { if let builtinUser = $0.instruction as? BuiltinInst, builtinUser.id == .EndAsyncLetLifetime { return visitor($0) } return .continueWalk } } } var description: String { instruction.description } } /// A value that introduces a borrow scope: /// begin_borrow, load_borrow, reborrow, guaranteed function argument. /// /// If the value introduces a local scope, then that scope is /// terminated by scope ending operands. Function arguments do not /// introduce a local scope because the caller owns the scope. /// /// If the value is a begin_apply result, then it may be the token or /// one of the yielded values. In any case, the scope ending operands /// are on the end_apply or abort_apply intructions that use the /// token. /// /// Note: equivalent to C++ BorrowedValue, but also handles begin_apply. enum BeginBorrowValue { case beginBorrow(BeginBorrowInst) case loadBorrow(LoadBorrowInst) case beginApply(Value) case functionArgument(FunctionArgument) case reborrow(Phi) init?(_ value: Value) { switch value { case let bbi as BeginBorrowInst: self = .beginBorrow(bbi) case let lbi as LoadBorrowInst: self = .loadBorrow(lbi) case let arg as FunctionArgument: self = .functionArgument(arg) case let arg as Argument where arg.isReborrow: self = .reborrow(Phi(arg)!) default: if value.definingInstruction is BeginApplyInst { self = .beginApply(value) break } return nil } } var value: Value { switch self { case .beginBorrow(let bbi): return bbi case .loadBorrow(let lbi): return lbi case .beginApply(let v): return v case .functionArgument(let arg): return arg case .reborrow(let phi): return phi.value } } init?(using operand: Operand) { switch operand.instruction { case is BeginBorrowInst, is LoadBorrowInst: let inst = operand.instruction as! SingleValueInstruction self = BeginBorrowValue(inst)! case is BranchInst: guard let phi = Phi(using: operand) else { return nil } guard phi.isReborrow else { return nil } self = .reborrow(phi) default: return nil } } init?(resultOf borrowInstruction: BorrowingInstruction) { switch borrowInstruction { case let .beginBorrow(beginBorrow): self = BeginBorrowValue(beginBorrow)! case let .beginApply(beginApply): self = BeginBorrowValue(beginApply.token)! case .storeBorrow, .partialApply, .markDependence, .startAsyncLet: return nil } } var hasLocalScope: Bool { switch self { case .beginBorrow, .loadBorrow, .beginApply, .reborrow: return true case .functionArgument: return false } } // Return the value borrowed by begin_borrow or address borrowed by // load_borrow. // // Return nil for begin_apply and reborrow, which need special handling. var baseOperand: Operand? { switch self { case let .beginBorrow(beginBorrow): return beginBorrow.operand case let .loadBorrow(loadBorrow): return loadBorrow.operand case .beginApply, .functionArgument, .reborrow: return nil } } /// The EndBorrows, reborrows (phis), and consumes (of closures) /// that end the local borrow scope. Empty if hasLocalScope is false. var scopeEndingOperands: LazyFilterSequence { switch self { case let .beginApply(value): return (value.definingInstruction as! BeginApplyInst).token.uses.endingLifetime default: return value.uses.endingLifetime } } } /// Find the borrow introducers for `value`. This gives you a set of /// OSSA lifetimes that directly include `value`. If `value` is owned, /// or introduces a borrow scope, then `value` is the single /// introducer for itself. /// /// If `value` is an address or any trivial type, then it has no introducers. /// /// Example: // introducers: /// // ~~~~~~~~~~~~ /// bb0(%0 : @owned $Class, // %0 /// %1 : @guaranteed $Class): // %1 /// %borrow0 = begin_borrow %0 // %borrow0 /// %pair = struct $Pair(%borrow0, %1) // %borrow0, %1 /// %first = struct_extract %pair // %borrow0, %1 /// %field = ref_element_addr %first // (none) /// %load = load_borrow %field : $*C // %load func gatherBorrowIntroducers(for value: Value, in borrowIntroducers: inout Stack, _ context: Context) { // Cache introducers across multiple instances of BorrowIntroducers. var cache = BorrowIntroducers.Cache(context) defer { cache.deinitialize() } BorrowIntroducers.gather(for: value, in: &borrowIntroducers, &cache, context) } private struct BorrowIntroducers { typealias CachedIntroducers = SingleInlineArray struct Cache { // Cache the introducers already found for each SILValue. var valueIntroducers: Dictionary // Record recursively followed phis to avoid infinite cycles. // Phis are removed from this set when they are cached. var pendingPhis: ValueSet init(_ context: Context) { valueIntroducers = Dictionary() pendingPhis = ValueSet(context) } mutating func deinitialize() { pendingPhis.deinitialize() } } let context: Context // BorrowIntroducers instances are recursively nested in order to // find outer adjacent phis. Each instance populates a separate // 'introducers' set. The same value may occur in 'introducers' at // multiple levels. Each instance, therefore, needs a separate // introducer set to avoid adding duplicates. var visitedIntroducers: Set = Set() static func gather(for value: Value, in introducers: inout Stack, _ cache: inout Cache, _ context: Context) { var borrowIntroducers = BorrowIntroducers(context: context) borrowIntroducers.gather(for: value, in: &introducers, &cache) } private mutating func push(_ introducer: Value, in introducers: inout Stack) { if visitedIntroducers.insert(introducer.hashable).inserted { introducers.push(introducer) } } private mutating func push(contentsOf other: S, in introducers: inout Stack) where S.Element == Value { for elem in other { push(elem, in: &introducers) } } // This is the identity function (i.e. just adds `value` to `introducers`) // when: // - `value` is owned // - `value` introduces a borrow scope (begin_borrow, load_borrow, reborrow) // // Otherwise recurse up the use-def chain to find all introducers. private mutating func gather(for value: Value, in introducers: inout Stack, _ cache: inout Cache) { // Check if this value's introducers have already been added to // 'introducers' to avoid duplicates and avoid exponential // recursion on aggregates. if let cachedIntroducers = cache.valueIntroducers[value.hashable] { push(contentsOf: cachedIntroducers, in: &introducers) return } introducers.withMarker( pushElements: { introducers in gatherUncached(for: value, in: &introducers, &cache) }, withNewElements: { newIntroducers in { cachedIntroducers in newIntroducers.forEach { cachedIntroducers.push($0) } }(&cache.valueIntroducers[value.hashable, default: CachedIntroducers()]) }) } private mutating func gatherUncached(for value: Value, in introducers: inout Stack, _ cache: inout Cache) { switch value.ownership { case .none, .unowned: return case .owned: push(value, in: &introducers); return case .guaranteed: break } // BeginBorrowedValue handles the initial scope introducers: begin_borrow, // load_borrow, & reborrow. if BeginBorrowValue(value) != nil { push(value, in: &introducers) return } // Handle guaranteed forwarding phis if let phi = Phi(value) { gather(forPhi: phi, in: &introducers, &cache) return } // Recurse through guaranteed forwarding non-phi instructions. guard let forwardingInst = value.forwardingInstruction else { fatalError("guaranteed value must be forwarding") } for operand in forwardingInst.forwardedOperands { if operand.value.ownership == .guaranteed { gather(for: operand.value, in: &introducers, &cache); } } } // Find the introducers of a guaranteed forwarding phi's borrow // scope. The introducers are either dominating values or reborrows // in the same block as the forwarding phi. // // Recurse along the use-def phi web until a begin_borrow is reached. At each // level, find the outer-adjacent phi, if one exists, otherwise return the // dominating definition. // // Example: // // bb1(%reborrow_1 : @reborrow) // %field = struct_extract %reborrow_1 // br bb2(%reborrow_1, %field) // bb2(%reborrow_2 : @reborrow, %forward_2 : @guaranteed) // end_borrow %reborrow_2 // // Calling `gather(forPhi: %forward_2)` // recursively computes these introducers: // // %field is the only value incoming to %forward_2. // // %field is introduced by %reborrow_1 via // gather(for: %field). // // %reborrow_1 is remapped to %reborrow_2 in bb2 via // mapToPhi(bb1, %reborrow_1)). // // %reborrow_2 is returned. // private mutating func gather(forPhi phi: Phi, in introducers: inout Stack, _ cache: inout Cache) { // Phi cycles are skipped. They cannot contribute any new introducer. if !cache.pendingPhis.insert(phi.value) { return } for (pred, value) in zip(phi.predecessors, phi.incomingValues) { // Each phi operand requires a new introducer list and visited // values set. These values will be remapped to successor phis // before adding them to the caller's introducer list. It may be // necessary to revisit a value that was already visited by the // caller before remapping to phis. var incomingIntroducers = Stack(context) defer { incomingIntroducers.deinitialize() } BorrowIntroducers.gather(for: value, in: &incomingIntroducers, &cache, context) // Map the incoming introducers to an outer-adjacent phi if one exists. push(contentsOf: mapToPhi(predecessor: pred, incomingValues: incomingIntroducers), in: &introducers) } // Remove this phi from the pending set. This phi may be visited // again at a different level of phi recursion. In that case, we // should return the cached introducers so that they can be // remapped. cache.pendingPhis.erase(phi.value) } } // Given incoming values on a predecessor path, return the // corresponding values on the successor block. Each incoming value is // either used by a phi in the successor block, or it must dominate // the successor block. private func mapToPhi> ( predecessor: BasicBlock, incomingValues: PredecessorSequence) -> LazyMapSequence { let branch = predecessor.terminator as! BranchInst // Gather the new introducers for the successor block. return incomingValues.lazy.map { incomingValue in // Find an outer adjacent phi in the successor block. if let incomingOp = branch.operands.first(where: { $0.value == incomingValue }) { return branch.getArgument(for: incomingOp) } // No candidates phi are outer-adjacent phis. The incoming // `predDef` must dominate the current guaranteed phi. return incomingValue } } /// Find each "enclosing value" whose OSSA lifetime immediately /// encloses a guaranteed value. The guaranteed `value` being enclosed /// effectively keeps these enclosing values alive. This lets you walk /// up the levels of nested OSSA lifetimes to determine all the /// lifetimes that are kept alive by a given SILValue. In particular, /// it discovers "outer-adjacent phis": phis that are kept alive by /// uses of another phi in the same block. /// /// If `value` is a forwarded guaranteed value, then this finds the /// introducers of the current borrow scope, which is never an empty /// set. /// /// If `value` introduces a borrow scope, then this finds the /// introducers of the outer enclosing borrow scope that contains this /// inner scope. /// /// If `value` is a `begin_borrow`, then this returns its operand. /// /// If `value` is an owned value, a function argument, or a /// load_borrow, then this is an empty set. /// /// If `value` is a reborrow, then this either returns a dominating /// enclosing value or an outer adjacent phi. /// /// Example: // enclosing value: /// // ~~~~~~~~~~~~ /// bb0(%0 : @owned $Class, // (none) /// %1 : @guaranteed $Class): // (none) /// %borrow0 = begin_borrow %0 // %0 /// %pair = struct $Pair(%borrow0, %1) // %borrow0, %1 /// %first = struct_extract %pair // %borrow0, %1 /// %field = ref_element_addr %first // (none) /// %load = load_borrow %field : $*C // %load /// /// Example: // enclosing value: /// // ~~~~~~~~~~~~ /// %outerBorrow = begin_borrow %0 // %0 /// %innerBorrow = begin_borrow %outerBorrow // %outerBorrow /// br bb1(%outerBorrow, %innerBorrow) /// bb1(%outerReborrow : @reborrow, // %0 /// %innerReborrow : @reborrow) // %outerReborrow /// func gatherEnclosingValues(for value: Value, in enclosingValues: inout Stack, _ context: some Context) { var cache = EnclosingValues.Cache(context) defer { cache.deinitialize() } EnclosingValues.gather(for: value, in: &enclosingValues, &cache, context) } /// Find inner adjacent phis in the same block as `enclosingPhi`. /// These keep the enclosing (outer adjacent) phi alive. func gatherInnerAdjacentPhis(for enclosingPhi: Phi, in innerAdjacentPhis: inout Stack, _ context: Context) { for candidatePhi in enclosingPhi.successor.arguments { var enclosingValues = Stack(context) defer { enclosingValues.deinitialize() } gatherEnclosingValues(for: candidatePhi, in: &enclosingValues, context) if enclosingValues.contains(where: { $0 == enclosingPhi.value}) { innerAdjacentPhis.push(Phi(candidatePhi)!) } } } // Find the enclosing values for any value, including reborrows. private struct EnclosingValues { typealias CachedEnclosingValues = SingleInlineArray struct Cache { // Cache the enclosing values already found for each Reborrow. var reborrowToEnclosingValues: Dictionary // Record recursively followed reborrows to avoid infinite cycles. // Reborrows are removed from this set when they are cached. var pendingReborrows: ValueSet var borrowIntroducerCache: BorrowIntroducers.Cache init(_ context: Context) { reborrowToEnclosingValues = Dictionary() pendingReborrows = ValueSet(context) borrowIntroducerCache = BorrowIntroducers.Cache(context) } mutating func deinitialize() { pendingReborrows.deinitialize() borrowIntroducerCache.deinitialize() } } var context: Context // EnclosingValues instances are recursively nested in order to // find outer adjacent phis. Each instance populates a separate // 'enclosingValeus' set. The same value may occur in 'enclosingValues' at // multiple levels. Each instance, therefore, needs a separate // visited set to avoid adding duplicates. var visitedEnclosingValues: Set = Set() static func gather(for value: Value, in enclosingValues: inout Stack, _ cache: inout Cache, _ context: Context) { var gatherValues = EnclosingValues(context: context) gatherValues.gather(for: value, in: &enclosingValues, &cache) } private mutating func push(_ enclosingValue: Value, in enclosingValues: inout Stack) { if visitedEnclosingValues.insert(enclosingValue.hashable).inserted { enclosingValues.push(enclosingValue) } } private mutating func push(contentsOf other: S, in enclosingValues: inout Stack) where S.Element == Value { for elem in other { push(elem, in: &enclosingValues) } } mutating func gather(for value: Value, in enclosingValues: inout Stack, _ cache: inout Cache) { if value is Undef || value.ownership != .guaranteed { return } if let beginBorrow = BeginBorrowValue(value) { switch beginBorrow { case let .beginBorrow(bbi): // Gather the outer enclosing borrow scope. BorrowIntroducers.gather(for: bbi.operand.value, in: &enclosingValues, &cache.borrowIntroducerCache, context) case .loadBorrow, .beginApply, .functionArgument: // There is no enclosing value on this path. break case let .reborrow(reborrow): gather(forReborrow: reborrow, in: &enclosingValues, &cache) } } else { // Handle forwarded guaranteed values. BorrowIntroducers.gather(for: value, in: &enclosingValues, &cache.borrowIntroducerCache, context) } } // Given a reborrow, find the enclosing values. Each enclosing value // is represented by one of the following cases, which refer to the // example below: // // dominating owned value -> %value encloses %reborrow_1 // owned outer-adjacent phi -> %phi_3 encloses %reborrow_3 // dominating outer borrow introducer -> %outerBorrowB encloses %reborrow // outer-adjacent reborrow -> %outerReborrow encloses %reborrow // // Recurse along the use-def phi web until a begin_borrow is // reached. Then find all introducers of the begin_borrow's // operand. At each level, find the outer adjacent phi, if one // exists, otherwise return the most recently found dominating // definition. // // If `reborrow` was already encountered because of a phi cycle, // then no enclosingDefs are added. // // Example: // // %value = ... // %borrow = begin_borrow %value // br one(%borrow) // one(%reborrow_1 : @reborrow) // br two(%value, %reborrow_1) // two(%phi_2 : @owned, %reborrow_2 : @reborrow) // br three(%value, %reborrow_2) // three(%phi_3 : @owned, %reborrow_3 : @reborrow) // end_borrow %reborrow_3 // destroy_value %phi_3 // // gather(forReborrow: %reborrow_3) finds %phi_3 by computing // enclosing defs in this order // (inner -> outer): // // %reborrow_1 -> %value // %reborrow_2 -> %phi_2 // %reborrow_3 -> %phi_3 // // Example: // // %outerBorrowA = begin_borrow // %outerBorrowB = begin_borrow // %struct = struct (%outerBorrowA, outerBorrowB) // %borrow = begin_borrow %struct // br one(%outerBorrowA, %borrow) // one(%outerReborrow : @reborrow, %reborrow : @reborrow) // // gather(forReborrow: %reborrow) finds (%outerReborrow, %outerBorrowB). // // This implementation mirrors BorrowIntroducers.gather(forPhi:in:). // The difference is that this performs use-def recursion over // reborrows rather, and at each step, it finds the enclosing values // of the reborrow operands rather than the borrow introducers of // the guaranteed phi. private mutating func gather(forReborrow reborrow: Phi, in enclosingValues: inout Stack, _ cache: inout Cache) { // Phi cycles are skipped. They cannot contribute any new introducer. if !cache.pendingReborrows.insert(reborrow.value) { return } if let cachedEnclosingValues = cache.reborrowToEnclosingValues[reborrow.value.hashable] { push(contentsOf: cachedEnclosingValues, in: &enclosingValues) return } assert(enclosingValues.isEmpty) // Find the enclosing introducer for each reborrow operand, and // remap it to the enclosing introducer for the successor block. for (pred, incomingValue) in zip(reborrow.predecessors, reborrow.incomingValues) { var incomingEnclosingValues = Stack(context) defer { incomingEnclosingValues.deinitialize() } EnclosingValues.gather(for: incomingValue, in: &incomingEnclosingValues, &cache, context) push(contentsOf: mapToPhi(predecessor: pred, incomingValues: incomingEnclosingValues), in: &enclosingValues) } { cachedIntroducers in enclosingValues.forEach { cachedIntroducers.push($0) } }(&cache.reborrowToEnclosingValues[reborrow.value.hashable, default: CachedEnclosingValues()]) // Remove this reborrow from the pending set. It may be visited // again at a different level of recursion. cache.pendingReborrows.erase(reborrow.value) } } let borrowIntroducersTest = FunctionTest("borrow_introducers") { function, arguments, context in let value = arguments.takeValue() print(function) print("Borrow introducers for: \(value)") var introducers = Stack(context) defer { introducers.deinitialize() } gatherBorrowIntroducers(for: value, in: &introducers, context) introducers.forEach { print($0) } } let enclosingValuesTest = FunctionTest("enclosing_values") { function, arguments, context in let value = arguments.takeValue() print(function) print("Enclosing values for: \(value)") var enclosing = Stack(context) defer { enclosing.deinitialize() } gatherEnclosingValues(for: value, in: &enclosing, context) enclosing.forEach { print($0) } }