Skip to content

Commit c1ab7cb

Browse files
committed
Add a reborrow cache to EnclosingValues.
Along with motivating unit tests.
1 parent 1c6ac61 commit c1ab7cb

File tree

3 files changed

+195
-25
lines changed

3 files changed

+195
-25
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/BorrowUtils.swift

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,9 @@ func gatherEnclosingValues(for value: Value,
612612
in enclosingValues: inout Stack<Value>,
613613
_ context: some Context) {
614614

615-
var gatherValues = EnclosingValues(context)
616-
defer { gatherValues.deinitialize() }
617-
var cache = BorrowIntroducers.Cache(context)
615+
var cache = EnclosingValues.Cache(context)
618616
defer { cache.deinitialize() }
619-
gatherValues.gather(for: value, in: &enclosingValues, &cache)
617+
EnclosingValues.gather(for: value, in: &enclosingValues, &cache, context)
620618
}
621619

622620
/// Find inner adjacent phis in the same block as `enclosingPhi`.
@@ -636,21 +634,61 @@ func gatherInnerAdjacentPhis(for enclosingPhi: Phi,
636634

637635
// Find the enclosing values for any value, including reborrows.
638636
private struct EnclosingValues {
637+
typealias CachedEnclosingValues = SingleInlineArray<Value>
638+
struct Cache {
639+
// Cache the enclosing values already found for each Reborrow.
640+
var reborrowToEnclosingValues: Dictionary<HashableValue,
641+
CachedEnclosingValues>
642+
// Record recursively followed reborrows to avoid infinite cycles.
643+
// Reborrows are removed from this set when they are cached.
644+
var pendingReborrows: ValueSet
645+
646+
var borrowIntroducerCache: BorrowIntroducers.Cache
647+
648+
init(_ context: Context) {
649+
reborrowToEnclosingValues =
650+
Dictionary<HashableValue, CachedEnclosingValues>()
651+
pendingReborrows = ValueSet(context)
652+
borrowIntroducerCache = BorrowIntroducers.Cache(context)
653+
}
654+
655+
mutating func deinitialize() {
656+
pendingReborrows.deinitialize()
657+
borrowIntroducerCache.deinitialize()
658+
}
659+
}
660+
639661
var context: Context
640-
var visitedReborrows : ValueSet
662+
// EnclosingValues instances are recursively nested in order to
663+
// find outer adjacent phis. Each instance populates a separate
664+
// 'enclosingValeus' set. The same value may occur in 'enclosingValues' at
665+
// multiple levels. Each instance, therefore, needs a separate
666+
// visited set to avoid adding duplicates.
667+
var visitedEnclosingValues: Set<HashableValue> = Set()
668+
669+
static func gather(for value: Value, in enclosingValues: inout Stack<Value>,
670+
_ cache: inout Cache, _ context: Context) {
671+
var gatherValues = EnclosingValues(context: context)
672+
gatherValues.gather(for: value, in: &enclosingValues, &cache)
673+
}
641674

642-
init(_ context: Context) {
643-
self.context = context
644-
self.visitedReborrows = ValueSet(context)
675+
private mutating func push(_ enclosingValue: Value,
676+
in enclosingValues: inout Stack<Value>) {
677+
if visitedEnclosingValues.insert(enclosingValue.hashable).inserted {
678+
enclosingValues.push(enclosingValue)
679+
}
645680
}
646681

647-
mutating func deinitialize() {
648-
visitedReborrows.deinitialize()
682+
private mutating func push<S: Sequence>(contentsOf other: S,
683+
in enclosingValues: inout Stack<Value>) where S.Element == Value {
684+
for elem in other {
685+
push(elem, in: &enclosingValues)
686+
}
649687
}
650688

651689
mutating func gather(for value: Value,
652690
in enclosingValues: inout Stack<Value>,
653-
_ cache: inout BorrowIntroducers.Cache) {
691+
_ cache: inout Cache) {
654692
if value is Undef || value.ownership != .guaranteed {
655693
return
656694
}
@@ -659,7 +697,7 @@ private struct EnclosingValues {
659697
case let .beginBorrow(bbi):
660698
// Gather the outer enclosing borrow scope.
661699
BorrowIntroducers.gather(for: bbi.operand.value, in: &enclosingValues,
662-
&cache, context)
700+
&cache.borrowIntroducerCache, context)
663701
case .loadBorrow, .beginApply, .functionArgument:
664702
// There is no enclosing value on this path.
665703
break
@@ -669,7 +707,7 @@ private struct EnclosingValues {
669707
} else {
670708
// Handle forwarded guaranteed values.
671709
BorrowIntroducers.gather(for: value, in: &enclosingValues,
672-
&cache, context)
710+
&cache.borrowIntroducerCache, context)
673711
}
674712
}
675713

@@ -723,16 +761,25 @@ private struct EnclosingValues {
723761
//
724762
// gather(forReborrow: %reborrow) finds (%outerReborrow, %outerBorrowB).
725763
//
764+
// This implementation mirrors BorrowIntroducers.gather(forPhi:in:).
765+
// The difference is that this performs use-def recursion over
766+
// reborrows rather, and at each step, it finds the enclosing values
767+
// of the reborrow operands rather than the borrow introducers of
768+
// the guaranteed phi.
726769
private mutating func gather(forReborrow reborrow: Phi,
727770
in enclosingValues: inout Stack<Value>,
728-
_ cache: inout BorrowIntroducers.Cache) {
771+
_ cache: inout Cache) {
729772

730-
guard visitedReborrows.insert(reborrow.value) else {
773+
// Phi cycles are skipped. They cannot contribute any new introducer.
774+
if !cache.pendingReborrows.insert(reborrow.value) {
775+
return
776+
}
777+
if let cachedEnclosingValues =
778+
cache.reborrowToEnclosingValues[reborrow.value.hashable] {
779+
push(contentsOf: cachedEnclosingValues, in: &enclosingValues)
731780
return
732781
}
733-
// avoid duplicates in the enclosingValues set.
734-
var pushedEnclosingValues = ValueSet(context)
735-
defer { pushedEnclosingValues.deinitialize() }
782+
assert(enclosingValues.isEmpty)
736783

737784
// Find the enclosing introducer for each reborrow operand, and
738785
// remap it to the enclosing introducer for the successor block.
@@ -742,14 +789,20 @@ private struct EnclosingValues {
742789
defer {
743790
incomingEnclosingValues.deinitialize()
744791
}
745-
gather(for: incomingValue, in: &incomingEnclosingValues, &cache)
746-
mapToPhi(predecessor: pred,
747-
incomingValues: incomingEnclosingValues).forEach {
748-
if pushedEnclosingValues.insert($0) {
749-
enclosingValues.append($0)
750-
}
751-
}
792+
EnclosingValues.gather(for: incomingValue, in: &incomingEnclosingValues,
793+
&cache, context)
794+
push(contentsOf: mapToPhi(predecessor: pred,
795+
incomingValues: incomingEnclosingValues),
796+
in: &enclosingValues)
752797
}
798+
{ cachedIntroducers in
799+
enclosingValues.forEach { cachedIntroducers.push($0) }
800+
}(&cache.reborrowToEnclosingValues[reborrow.value.hashable,
801+
default: CachedEnclosingValues()])
802+
803+
// Remove this reborrow from the pending set. It may be visited
804+
// again at a different level of recursion.
805+
cache.pendingReborrows.erase(reborrow.value)
753806
}
754807
}
755808

test/SILOptimizer/borrow_introducer_unit.sil

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,66 @@ bb0(%0 : @owned $C,
381381
%retval = tuple ()
382382
return %retval : $()
383383
}
384+
385+
// CHECK-LABEL: testSelfIntroducer: borrow_introducers with: %phi
386+
// CHECK: Borrow introducers for: %{{.*}} = argument of bb1 : $D
387+
// CHECK-NEXT: %{{.*}} = argument of bb1 : $C
388+
// CHECK-NEXT: testSelfIntroducer: borrow_introducers with: %phi
389+
390+
// CHECK-LABEL: testSelfIntroducer: enclosing_values with: %reborrow
391+
// CHECK: Enclosing values for: %4 = argument of bb1 : $C // users: %8, %7
392+
// CHECK-NEXT: %0 = argument of bb0 : $C // user: %1
393+
// CHECK-NEXT: testSelfIntroducer: enclosing_values with: %reborrow
394+
sil [ossa] @testSelfIntroducer : $@convention(thin) (@guaranteed C) -> () {
395+
bb0(%0 : @guaranteed $C):
396+
%borrow = begin_borrow %0 : $C
397+
%d = unconditional_checked_cast %borrow : $C to D
398+
br bb1(%borrow : $C, %d : $D)
399+
400+
bb1(%reborrow : @guaranteed $C, %phi : @guaranteed $D):
401+
specify_test "borrow_introducers %phi"
402+
specify_test "enclosing_values %reborrow"
403+
cond_br undef, bb2, bb3
404+
405+
bb2:
406+
br bb1(%reborrow : $C, %phi : $D)
407+
408+
bb3:
409+
end_borrow %reborrow : $C
410+
%99 = tuple()
411+
return %99 : $()
412+
}
413+
414+
// Test the reborrow cache in EnclosingValues. Here, %reborrow must be
415+
// visited once on each path, and each time the recursive algorithm
416+
// must find the enclosing def %0, which maps to a %outer0 on one path
417+
// and %outer1 on another path. If the cache fails, then we only find
418+
// one of the outer adjacent phis as and enclosing value for %inner.
419+
//
420+
// CHECK-LABEL: testEnclosingRevisitReborrow: enclosing_values with: %inner
421+
// CHECK: Enclosing values for: %10 = argument of bb4 : $C // user: %11
422+
// CHECK-NEXT: %{{.*}} = argument of bb4 : $C
423+
// CHECK-NEXT: %{{.*}} = argument of bb4 : $C
424+
// CHECK-NEXT: end running test 1 of 1 on testEnclosingRevisitReborrow: enclosing_values with: %inner
425+
sil [ossa] @testEnclosingRevisitReborrow : $@convention(thin) (@owned C, @owned C) -> () {
426+
bb0(%0 : @owned $C, %1 : @owned $C):
427+
%borrow = begin_borrow %0 : $C
428+
br bb1(%borrow : $C)
429+
430+
bb1(%reborrow : @guaranteed $C):
431+
cond_br undef, bb2, bb3
432+
433+
bb2:
434+
br bb4(%0 : $C, %1 : $C, %reborrow : $C)
435+
436+
bb3:
437+
br bb4(%1 : $C, %0 : $C, %reborrow : $C)
438+
439+
bb4(%outer0 : @owned $C, %outer1 : @owned $C, %inner : @guaranteed $C):
440+
specify_test "enclosing_values %inner"
441+
end_borrow %inner : $C
442+
destroy_value %outer0 : $C
443+
destroy_value %outer1 : $C
444+
%99 = tuple()
445+
return %99 : $()
446+
}

test/SILOptimizer/ownership_liveness_unit.sil

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,60 @@ bb1(%reborrow : @guaranteed $C):
304304
return %99 : $()
305305
}
306306

307+
// CHECK-LABEL: testInteriorNondominatedReborrow: interior_liveness_swift with: %borrow1
308+
// CHECK-LABEL: sil [ossa] @testInteriorNondominatedReborrow : $@convention(thin) (@guaranteed C) -> () {
309+
// CHECK: Interior liveness: %{{.*}} = begin_borrow %0 : $C
310+
// CHECK-NEXT: begin: %{{.*}} = begin_borrow %0 : $C
311+
// CHECK-NEXT: ends: br bb1(%{{.*}} : $C, %{{.*}} : $D)
312+
// CHECK-NEXT: exits:
313+
// CHECK-NEXT: interiors: %{{.*}} = unchecked_ref_cast %{{.*}} : $C to $D
314+
// CHECK-NEXT: Unenclosed phis {
315+
// CHECK-NEXT: }
316+
// CHECK-NEXT: last user: br bb1(%{{.*}} : $C, %{{.*}} : $D)
317+
// CHECK-NEXT: testInteriorNondominatedReborrow: interior_liveness_swift with: %borrow1
318+
sil [ossa] @testInteriorNondominatedReborrow : $@convention(thin) (@guaranteed C) -> () {
319+
bb0(%0 : @guaranteed $C):
320+
%borrow1 = begin_borrow %0 : $C
321+
specify_test "interior_liveness_swift %borrow1"
322+
%d1 = unchecked_ref_cast %borrow1 : $C to $D
323+
%borrow2 = begin_borrow %d1 : $D
324+
br bb3(%borrow1 : $C, %borrow2 : $D)
325+
326+
bb3(%reborrow1 : @guaranteed $C, %reborrow2 : @guaranteed $D):
327+
end_borrow %reborrow2 : $D
328+
end_borrow %reborrow1 : $C
329+
%99 = tuple()
330+
return %99 : $()
331+
}
332+
333+
// CHECK-LABEL: testInteriorDominatedReborrow: interior_liveness_swift with: %borrow1
334+
// CHECK: sil [ossa] @testInteriorDominatedReborrow : $@convention(thin) (@guaranteed C) -> () {
335+
// CHECK: Interior liveness: %{{.*}} = begin_borrow %0 : $C
336+
// CHECK-NEXT: begin: %{{.*}} = begin_borrow %0 : $C
337+
// CHECK-NEXT: ends: end_borrow %{{.*}} : $C
338+
// CHECK-NEXT: exits:
339+
// CHECK-NEXT: interiors: end_borrow %{{.*}} : $D
340+
// CHECK-NEXT: br bb1(%{{.*}} : $D)
341+
// CHECK-NEXT: %{{.*}} = unchecked_ref_cast %{{.*}} : $C to $D
342+
// CHECK-NEXT: Unenclosed phis {
343+
// CHECK-NEXT: }
344+
// CHECK-NEXT: last user: end_borrow %{{.*}} : $C
345+
// CHECK-NEXT: testInteriorDominatedReborrow: interior_liveness_swift with: %borrow1
346+
sil [ossa] @testInteriorDominatedReborrow : $@convention(thin) (@guaranteed C) -> () {
347+
bb0(%0 : @guaranteed $C):
348+
%borrow1 = begin_borrow %0 : $C
349+
specify_test "interior_liveness_swift %borrow1"
350+
%d1 = unchecked_ref_cast %borrow1 : $C to $D
351+
%borrow2 = begin_borrow %d1 : $D
352+
br bb3(%borrow2 : $D)
353+
354+
bb3(%reborrow2 : @guaranteed $D):
355+
end_borrow %reborrow2 : $D
356+
end_borrow %borrow1 : $C
357+
%99 = tuple()
358+
return %99 : $()
359+
}
360+
307361
// CHECK-LABEL: testInteriorDominatedGuaranteedForwardingPhi: interior-liveness with: @argument[0]
308362
// CHECK: Complete liveness
309363
// CHECK: last user: %{{.*}} load [copy]

0 commit comments

Comments
 (0)