Skip to content

Commit a2f9dc7

Browse files
committed
- getBorrowIntroducers and getEnclosingValues with iterators
1 parent 034cd7c commit a2f9dc7

File tree

6 files changed

+178
-160
lines changed

6 files changed

+178
-160
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,24 +236,16 @@ private struct LifetimeVariable {
236236
self = Self(accessBase: value.accessBase, context)
237237
return
238238
}
239-
if let firstIntroducer = getFirstBorrowIntroducer(of: value, context) {
239+
// FUTURE: consider diagnosing multiple variable introducers. It's
240+
// unclear how more than one can happen.
241+
if let firstIntroducer = value.getBorrowIntroducers(context).first {
240242
self = Self(introducer: firstIntroducer)
241243
return
242244
}
243245
self.varDecl = nil
244246
self.sourceLoc = nil
245247
}
246248

247-
// FUTURE: consider diagnosing multiple variable introducers. It's
248-
// unclear how more than one can happen.
249-
private func getFirstBorrowIntroducer(of value: Value,
250-
_ context: some Context)
251-
-> Value? {
252-
var introducers = Stack<Value>(context)
253-
gatherBorrowIntroducers(for: value, in: &introducers, context)
254-
return introducers.pop()
255-
}
256-
257249
private func getFirstLifetimeIntroducer(of value: Value,
258250
_ context: some Context)
259251
-> Value? {

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 162 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -388,138 +388,172 @@ extension Value {
388388
}
389389
}
390390

391-
/// Find the borrow introducers for `value`. This gives you a set of
392-
/// OSSA lifetimes that directly include `value`. If `value` is owned,
393-
/// or introduces a borrow scope, then `value` is the single
394-
/// introducer for itself.
395-
///
396-
/// If `value` is an address or any trivial type, then it has no introducers.
397-
///
398-
/// Example: // introducers:
399-
/// // ~~~~~~~~~~~~
400-
/// bb0(%0 : @owned $Class, // %0
401-
/// %1 : @guaranteed $Class): // %1
402-
/// %borrow0 = begin_borrow %0 // %borrow0
403-
/// %pair = struct $Pair(%borrow0, %1) // %borrow0, %1
404-
/// %first = struct_extract %pair // %borrow0, %1
405-
/// %field = ref_element_addr %first // (none)
406-
/// %load = load_borrow %field : $*C // %load
407-
func gatherBorrowIntroducers(for initialValue: Value,
408-
in borrowIntroducers: inout Stack<Value>,
409-
_ context: some Context)
410-
{
411-
var worklist = ValueWorklist(context)
412-
defer { worklist.deinitialize() }
413-
414-
worklist.pushIfNotVisited(initialValue)
415-
while let value = worklist.pop() {
416-
switch value.ownership {
417-
case .none, .unowned:
418-
break
419-
420-
case .owned:
421-
borrowIntroducers.append(value)
422-
423-
case .guaranteed:
424-
if BeginBorrowValue(value) != nil {
425-
borrowIntroducers.append(value)
426-
} else if let bfi = value as? BorrowedFromInst,
427-
let phi = Phi(bfi.borrowedValue)
428-
{
429-
if phi.isReborrow {
430-
worklist.pushIfNotVisited(phi.value)
391+
struct BorrowIntroducers<Ctxt: Context> : CollectionLikeSequence {
392+
let initialValue: Value
393+
let context: Ctxt
394+
395+
func makeIterator() -> EnclosingValueIterator {
396+
EnclosingValueIterator(forBorrowIntroducers: initialValue, context)
397+
}
398+
}
399+
400+
struct EnclosingValues<Ctxt: Context> : CollectionLikeSequence {
401+
let initialValue: Value
402+
let context: Ctxt
403+
404+
func makeIterator() -> EnclosingValueIterator {
405+
EnclosingValueIterator(forEnclosingValues: initialValue, context)
406+
}
407+
}
408+
409+
// This iterator must be a class because we need a deinit.
410+
// It shouldn't be a performance problem because the optimizer should always be able to stack promote the iterator.
411+
// TODO: Make it a struct once this is possible with non-copyable types.
412+
final class EnclosingValueIterator : IteratorProtocol {
413+
var worklist: ValueWorklist
414+
415+
init(forBorrowIntroducers value: Value, _ context: some Context) {
416+
self.worklist = ValueWorklist(context)
417+
self.worklist.pushIfNotVisited(value)
418+
}
419+
420+
init(forEnclosingValues value: Value, _ context: some Context) {
421+
self.worklist = ValueWorklist(context)
422+
if value is Undef || value.ownership != .guaranteed {
423+
return
424+
}
425+
if let beginBorrow = BeginBorrowValue(value.lookThroughBorrowedFrom) {
426+
switch beginBorrow {
427+
case let .beginBorrow(bbi):
428+
// Gather the outer enclosing borrow scope.
429+
worklist.pushIfNotVisited(bbi.borrowedValue)
430+
case .loadBorrow, .beginApply, .functionArgument:
431+
// There is no enclosing value on this path.
432+
break
433+
case .reborrow(let phi):
434+
worklist.pushIfNotVisited(contentsOf: phi.borrowedFrom!.enclosingValues)
435+
}
436+
} else {
437+
// Handle forwarded guaranteed values.
438+
worklist.pushIfNotVisited(value)
439+
}
440+
}
441+
442+
deinit {
443+
worklist.deinitialize()
444+
}
445+
446+
func next() -> Value? {
447+
while let value = worklist.pop() {
448+
switch value.ownership {
449+
case .none, .unowned:
450+
break
451+
452+
case .owned:
453+
return value
454+
455+
case .guaranteed:
456+
if BeginBorrowValue(value) != nil {
457+
return value
458+
} else if let bfi = value as? BorrowedFromInst {
459+
if bfi.borrowedPhi.isReborrow {
460+
worklist.pushIfNotVisited(bfi.borrowedValue)
461+
} else {
462+
worklist.pushIfNotVisited(contentsOf: bfi.enclosingValues)
463+
}
464+
} else if let forwardingInst = value.forwardingInstruction {
465+
// Recurse through guaranteed forwarding non-phi instructions.
466+
let ops = forwardingInst.forwardedOperands
467+
worklist.pushIfNotVisited(contentsOf: ops.lazy.map { $0.value })
431468
} else {
432-
worklist.pushIfNotVisited(contentsOf: bfi.enclosingValues)
469+
fatalError("cannot get borrow introducers for unknown guaranteed value")
433470
}
434-
} else if let forwardingInst = value.forwardingInstruction {
435-
// Recurse through guaranteed forwarding non-phi instructions.
436-
let ops = forwardingInst.forwardedOperands
437-
worklist.pushIfNotVisited(contentsOf: ops.lazy.map { $0.value })
438-
} else {
439-
fatalError("cannot get borrow introducers for unknown guaranteed value")
440471
}
441472
}
473+
return nil
442474
}
443475
}
444476

445-
/// Find each "enclosing value" whose OSSA lifetime immediately
446-
/// encloses a guaranteed value. The guaranteed `value` being enclosed
447-
/// effectively keeps these enclosing values alive. This lets you walk
448-
/// up the levels of nested OSSA lifetimes to determine all the
449-
/// lifetimes that are kept alive by a given SILValue. In particular,
450-
/// it discovers "outer-adjacent phis": phis that are kept alive by
451-
/// uses of another phi in the same block.
452-
///
453-
/// If `value` is a forwarded guaranteed value, then this finds the
454-
/// introducers of the current borrow scope, which is never an empty
455-
/// set.
456-
///
457-
/// If `value` introduces a borrow scope, then this finds the
458-
/// introducers of the outer enclosing borrow scope that contains this
459-
/// inner scope.
460-
///
461-
/// If `value` is a `begin_borrow`, then this returns its operand.
462-
///
463-
/// If `value` is an owned value, a function argument, or a
464-
/// load_borrow, then this is an empty set.
465-
///
466-
/// If `value` is a reborrow, then this either returns a dominating
467-
/// enclosing value or an outer adjacent phi.
468-
///
469-
/// Example: // enclosing value:
470-
/// // ~~~~~~~~~~~~
471-
/// bb0(%0 : @owned $Class, // (none)
472-
/// %1 : @guaranteed $Class): // (none)
473-
/// %borrow0 = begin_borrow %0 // %0
474-
/// %pair = struct $Pair(%borrow0, %1) // %borrow0, %1
475-
/// %first = struct_extract %pair // %borrow0, %1
476-
/// %field = ref_element_addr %first // (none)
477-
/// %load = load_borrow %field : $*C // %load
478-
///
479-
/// Example: // enclosing value:
480-
/// // ~~~~~~~~~~~~
481-
/// %outerBorrow = begin_borrow %0 // %0
482-
/// %innerBorrow = begin_borrow %outerBorrow // %outerBorrow
483-
/// br bb1(%outerBorrow, %innerBorrow)
484-
/// bb1(%outerReborrow : @reborrow, // %0
485-
/// %innerReborrow : @reborrow) // %outerReborrow
486-
///
487-
func gatherEnclosingValues(for value: Value,
488-
in enclosingValues: inout Stack<Value>,
489-
_ context: some Context)
490-
{
491-
if value is Undef || value.ownership != .guaranteed {
492-
return
477+
478+
extension Value {
479+
/// Get the borrow introducers for `value`. This gives you a set of
480+
/// OSSA lifetimes that directly include `value`. If `value` is owned,
481+
/// or introduces a borrow scope, then `value` is the single
482+
/// introducer for itself.
483+
///
484+
/// If `value` is an address or any trivial type, then it has no introducers.
485+
///
486+
/// Example: // introducers:
487+
/// // ~~~~~~~~~~~~
488+
/// bb0(%0 : @owned $Class, // %0
489+
/// %1 : @guaranteed $Class): // %1
490+
/// %borrow0 = begin_borrow %0 // %borrow0
491+
/// %pair = struct $Pair(%borrow0, %1) // %borrow0, %1
492+
/// %first = struct_extract %pair // %borrow0, %1
493+
/// %field = ref_element_addr %first // (none)
494+
/// %load = load_borrow %field : $*C // %load
495+
func getBorrowIntroducers<Ctxt: Context>(_ context: Ctxt) -> BorrowIntroducers<Ctxt> {
496+
BorrowIntroducers(initialValue: self, context: context)
493497
}
494-
if let beginBorrow = BeginBorrowValue(value.lookThroughBorrowedFrom) {
495-
switch beginBorrow {
496-
case let .beginBorrow(bbi):
497-
// Gather the outer enclosing borrow scope.
498-
gatherBorrowIntroducers(for: bbi.borrowedValue, in: &enclosingValues, context)
499-
case .loadBorrow, .beginApply, .functionArgument:
500-
// There is no enclosing value on this path.
501-
break
502-
case .reborrow(let phi):
503-
enclosingValues.append(contentsOf: phi.borrowedFrom!.enclosingValues)
504-
}
505-
} else {
506-
// Handle forwarded guaranteed values.
507-
gatherBorrowIntroducers(for: value, in: &enclosingValues, context)
498+
499+
/// Get "enclosing values" whose OSSA lifetime immediately
500+
/// encloses a guaranteed value. The guaranteed `value` being enclosed
501+
/// effectively keeps these enclosing values alive. This lets you walk
502+
/// up the levels of nested OSSA lifetimes to determine all the
503+
/// lifetimes that are kept alive by a given SILValue. In particular,
504+
/// it discovers "outer-adjacent phis": phis that are kept alive by
505+
/// uses of another phi in the same block.
506+
///
507+
/// If `value` is a forwarded guaranteed value, then this finds the
508+
/// introducers of the current borrow scope, which is never an empty
509+
/// set.
510+
///
511+
/// If `value` introduces a borrow scope, then this finds the
512+
/// introducers of the outer enclosing borrow scope that contains this
513+
/// inner scope.
514+
///
515+
/// If `value` is a `begin_borrow`, then this returns its operand.
516+
///
517+
/// If `value` is an owned value, a function argument, or a
518+
/// load_borrow, then this is an empty set.
519+
///
520+
/// If `value` is a reborrow, then this either returns a dominating
521+
/// enclosing value or an outer adjacent phi.
522+
///
523+
/// Example: // enclosing value:
524+
/// // ~~~~~~~~~~~~
525+
/// bb0(%0 : @owned $Class, // (none)
526+
/// %1 : @guaranteed $Class): // (none)
527+
/// %borrow0 = begin_borrow %0 // %0
528+
/// %pair = struct $Pair(%borrow0, %1) // %borrow0, %1
529+
/// %first = struct_extract %pair // %borrow0, %1
530+
/// %field = ref_element_addr %first // (none)
531+
/// %load = load_borrow %field : $*C // %load
532+
///
533+
/// Example: // enclosing value:
534+
/// // ~~~~~~~~~~~~
535+
/// %outerBorrow = begin_borrow %0 // %0
536+
/// %innerBorrow = begin_borrow %outerBorrow // %outerBorrow
537+
/// br bb1(%outerBorrow, %innerBorrow)
538+
/// bb1(%outerReborrow : @reborrow, // %0
539+
/// %innerReborrow : @reborrow) // %outerReborrow
540+
///
541+
func getEnclosingValues<Ctxt: Context>(_ context: Ctxt) -> EnclosingValues<Ctxt> {
542+
EnclosingValues(initialValue: self, context: context)
508543
}
509544
}
510545

511-
/// Find inner adjacent phis in the same block as `enclosingPhi`.
512-
/// These keep the enclosing (outer adjacent) phi alive.
513-
func gatherInnerAdjacentPhis(for enclosingPhi: Phi,
514-
in innerAdjacentPhis: inout Stack<Phi>,
515-
_ context: Context)
516-
{
517-
for use in enclosingPhi.value.uses {
518-
if let bfi = use.instruction as? BorrowedFromInst,
519-
use.index != 0,
520-
let innerPhi = Phi(bfi.borrowedValue)
521-
{
522-
innerAdjacentPhis.append(innerPhi)
546+
extension Phi {
547+
/// Inner adjacent phis in the same block as `enclosingPhi`.
548+
/// These keep the enclosing (outer adjacent) phi alive.
549+
var innerAdjacentPhis: some Sequence<Phi> {
550+
value.uses.lazy.compactMap { use in
551+
if let bfi = use.instruction as? BorrowedFromInst,
552+
use.index != 0
553+
{
554+
return Phi(bfi.borrowedValue)
555+
}
556+
return nil
523557
}
524558
}
525559
}
@@ -535,11 +569,7 @@ func gatherEnclosingValuesFromPredecessors(
535569
for predecessor in phi.predecessors {
536570
let incomingOperand = phi.incomingOperand(inPredecessor: predecessor)
537571

538-
var predecessorEVs = Stack<Value>(context)
539-
defer { predecessorEVs.deinitialize() }
540-
gatherEnclosingValues(for: incomingOperand.value, in: &predecessorEVs, context)
541-
542-
for predEV in predecessorEVs {
572+
for predEV in incomingOperand.value.getEnclosingValues(context) {
543573
let ev = predecessor.mapToPhiInSuccessor(incomingEnclosingValue: predEV)
544574
if alreadyAdded.insert(ev) {
545575
enclosingValues.push(ev)
@@ -569,8 +599,9 @@ let borrowIntroducersTest = FunctionTest("borrow_introducers") {
569599
defer {
570600
introducers.deinitialize()
571601
}
572-
gatherBorrowIntroducers(for: value.lookThroughBorrowedFromUser, in: &introducers, context)
573-
introducers.forEach { print($0) }
602+
for bi in value.lookThroughBorrowedFromUser.getBorrowIntroducers(context) {
603+
print(bi)
604+
}
574605
}
575606

576607
let enclosingValuesTest = FunctionTest("enclosing_values") {
@@ -582,8 +613,9 @@ let enclosingValuesTest = FunctionTest("enclosing_values") {
582613
defer {
583614
enclosing.deinitialize()
584615
}
585-
gatherEnclosingValues(for: value.lookThroughBorrowedFromUser, in: &enclosing, context)
586-
enclosing.forEach { print($0) }
616+
for ev in value.lookThroughBorrowedFromUser.getEnclosingValues(context) {
617+
print(ev)
618+
}
587619
}
588620

589621
extension Value {

SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -364,15 +364,14 @@ extension LifetimeDependence.Scope {
364364
}
365365

366366
private init?(guaranteed base: Value, _ context: some Context) {
367-
var introducers = Stack<Value>(context)
368-
gatherBorrowIntroducers(for: base, in: &introducers, context)
369367
// If introducers is empty, then the dependence is on a trivial value, so
370368
// there is no dependence scope.
371369
//
372370
// TODO: Add a SIL verifier check that a mark_dependence [nonescaping]
373371
// base is never a guaranteed phi.
374-
guard let introducer = introducers.pop() else { return nil }
375-
assert(introducers.isEmpty,
372+
let iter = base.getBorrowIntroducers(context).makeIterator()
373+
guard let introducer = iter.next() else { return nil }
374+
assert(iter.next() == nil,
376375
"guaranteed phis not allowed when diagnosing lifetime dependence")
377376
guard let beginBorrow = BeginBorrowValue(introducer) else {
378377
fatalError("unknown borrow introducer")

0 commit comments

Comments
 (0)