Skip to content

Commit 196ae17

Browse files
committed
Add a reborrow cache to EnclosingValues.
Along with motivating unit tests.
1 parent c10c545 commit 196ae17

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
@@ -613,11 +613,9 @@ func gatherEnclosingValues(for value: Value,
613613
in enclosingValues: inout Stack<Value>,
614614
_ context: some Context) {
615615

616-
var gatherValues = EnclosingValues(context)
617-
defer { gatherValues.deinitialize() }
618-
var cache = BorrowIntroducers.Cache(context)
616+
var cache = EnclosingValues.Cache(context)
619617
defer { cache.deinitialize() }
620-
gatherValues.gather(for: value, in: &enclosingValues, &cache)
618+
EnclosingValues.gather(for: value, in: &enclosingValues, &cache, context)
621619
}
622620

623621
/// Find inner adjacent phis in the same block as `enclosingPhi`.
@@ -637,21 +635,61 @@ func gatherInnerAdjacentPhis(for enclosingPhi: Phi,
637635

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

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

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

652690
mutating func gather(for value: Value,
653691
in enclosingValues: inout Stack<Value>,
654-
_ cache: inout BorrowIntroducers.Cache) {
692+
_ cache: inout Cache) {
655693
if value is Undef || value.ownership != .guaranteed {
656694
return
657695
}
@@ -660,7 +698,7 @@ private struct EnclosingValues {
660698
case let .beginBorrow(bbi):
661699
// Gather the outer enclosing borrow scope.
662700
BorrowIntroducers.gather(for: bbi.operand.value, in: &enclosingValues,
663-
&cache, context)
701+
&cache.borrowIntroducerCache, context)
664702
case .loadBorrow, .beginApply, .functionArgument:
665703
// There is no enclosing value on this path.
666704
break
@@ -670,7 +708,7 @@ private struct EnclosingValues {
670708
} else {
671709
// Handle forwarded guaranteed values.
672710
BorrowIntroducers.gather(for: value, in: &enclosingValues,
673-
&cache, context)
711+
&cache.borrowIntroducerCache, context)
674712
}
675713
}
676714

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

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

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

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)