Skip to content

Commit 05e643b

Browse files
committed
[DI] Fixup alloc_box borrow scope ends.
After #40793, alloc_boxes all have their lifetimes protected by a lexical borrow scope. In that PR, DI had been updated to see through begin_borrow instructions from a project_box to a mark_uninitialized. It did not, however, correct the end_borrow instructions when destroy_values of mark_uninitializeds were replaced with destroy_addrs of project_boxes. That is done here. In a bit more detail, in the following context %box = alloc_box %mark_uninit = mark_uninitialized %box %lifetime = begin_borrow [lexical] %mark_uninit %proj_box = project_box %lifetime When it is not statically known whether a field is initialized, we are replacing the instruction // before destroy_value %mark_uninit // after with the following diamond // before %initialized = load cond_br %initialized, yes, no yes: destroy_addr %proj_box br bottom no: br bottom bottom: dealloc_box %box br keep_going keep_going: // after Doing so is problematic, though, because by SILGen construction the destroy_value is always preceded by an end_borrow: end_borrow %lifetime destroy_value %mark_uninit Previously, that end_borrow remained above the %initialized = load instruction in the above. That was invalid because the the newly introduced destroy_addr %proj_box was a use of the borrow scope (%proj_box is a projection of the begin_borrow) and consequently must be within the borrow scope. Note also that it would not be sufficient to simply emit the diamond before the end_borrow. The end_borrow must come before the destruction of the value whose lifetime it is protecting (%box), and the diamond contains the instruction to destroy that value (dealloc_box) in its bottom block. To resolve this issue, just move the end_borrow instruction from where it was to before the dealloc box. (This is actually done by moving it to the top of the diamond's "continue" block prior to the emission of that dealloc_box instruction.) rdar://89984216
1 parent ac70bf7 commit 05e643b

File tree

2 files changed

+130
-8
lines changed

2 files changed

+130
-8
lines changed

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2777,6 +2777,104 @@ SILValue LifetimeChecker::handleConditionalInitAssign() {
27772777
return ControlVariableAddr;
27782778
}
27792779

2780+
/// Move the end_borrow that guards an alloc_box's lifetime to before the
2781+
/// dealloc_box in the CFG diamond that is created for destruction when it is
2782+
/// not statically known whether the value is initialized.
2783+
///
2784+
/// In the following context
2785+
///
2786+
/// %box = alloc_box
2787+
/// %mark_uninit = mark_uninitialized %box
2788+
/// %lifetime = begin_borrow [lexical] %mark_uninit
2789+
/// %proj_box = project_box %lifetime
2790+
///
2791+
/// We are replacing a
2792+
///
2793+
/// destroy_value %mark_uninit
2794+
///
2795+
/// with a
2796+
///
2797+
/// destroy_addr %proj_box
2798+
///
2799+
/// That's a problem, though, because by SILGen construction the
2800+
/// destroy_value is always preceded by an end_borrow
2801+
///
2802+
/// end_borrow %lifetime
2803+
/// destroy_value %mark_uninit
2804+
///
2805+
/// Consequently, it's not sufficient to just replace the destroy_value
2806+
/// %mark_uninit with a destroy_addr %proj_box (or to replace it with a diamond
2807+
/// where one branch has that destroy_addr) because the destroy_addr is a use
2808+
/// of %proj_box which must be within the lexical lifetime of the box.
2809+
///
2810+
/// On the other side, we are hemmed in by the fact that the end_borrow must
2811+
/// precede the dealloc_box which will be created in the diamond. So we
2812+
/// couldn't simply start inserting before the end_borrow (because the bottom
2813+
/// of the diamond contains a dealloc_box, so we would have an end_borrow after
2814+
/// the dealloc_box).
2815+
///
2816+
/// At this point, we have the following code:
2817+
///
2818+
/// end_borrow %lifetime
2819+
/// %initialized = load %addr
2820+
/// cond_br %initialized, yes, no
2821+
///
2822+
/// yes:
2823+
/// destroy_addr %proj_box
2824+
/// br bottom
2825+
///
2826+
/// no:
2827+
/// br bottom
2828+
///
2829+
/// bottom:
2830+
/// br keep_going
2831+
///
2832+
/// keep_going:
2833+
///
2834+
/// So just move the end_borrow to the right position, at the top of the bottom
2835+
/// block. The caller will then add the dealloc_box.
2836+
static bool adjustAllocBoxEndBorrow(SILInstruction *previous,
2837+
SILValue destroyedAddr,
2838+
SILBuilderWithScope &builder) {
2839+
// This fixup only applies if we're destroying a project_box.
2840+
auto *pbi = dyn_cast<ProjectBoxInst>(destroyedAddr);
2841+
if (!pbi)
2842+
return false;
2843+
2844+
// This fixup only applies if we're destroying a project_box of the lexical
2845+
// lifetime of an alloc_box.
2846+
auto *lifetime = dyn_cast<BeginBorrowInst>(pbi->getOperand());
2847+
if (!lifetime)
2848+
return false;
2849+
assert(lifetime->isLexical());
2850+
assert(isa<AllocBoxInst>(
2851+
cast<MarkUninitializedInst>(lifetime->getOperand())->getOperand()));
2852+
2853+
// Scan the block backwards from previous, looking for an end_borrow. SILGen
2854+
// will emit the sequence
2855+
//
2856+
// end_borrow %lifetime
2857+
// destroy_value %mark_uninit
2858+
//
2859+
// but other passes may have moved them apart.
2860+
EndBorrowInst *ebi = nullptr;
2861+
for (auto *instruction = previous; instruction;
2862+
instruction = instruction->getPreviousInstruction()) {
2863+
auto *candidate = dyn_cast<EndBorrowInst>(instruction);
2864+
if (!candidate)
2865+
continue;
2866+
auto *bbi = dyn_cast<BeginBorrowInst>(candidate->getOperand());
2867+
if (bbi != lifetime)
2868+
continue;
2869+
ebi = candidate;
2870+
}
2871+
if (!ebi)
2872+
return false;
2873+
2874+
ebi->moveBefore(&*builder.getInsertionPoint());
2875+
return true;
2876+
}
2877+
27802878
/// Process any destroy_addr and strong_release instructions that are invoked on
27812879
/// a partially initialized value. This generates code to destroy the elements
27822880
/// that are known to be alive, ignore the ones that are known to be dead, and
@@ -2797,7 +2895,7 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
27972895

27982896
// Utilities.
27992897

2800-
auto destroyMemoryElement = [&](SILLocation Loc, unsigned Elt) {
2898+
auto destroyMemoryElement = [&](SILLocation Loc, unsigned Elt) -> SILValue {
28012899
using EndScopeKind = DIMemoryObjectInfo::EndScopeKind;
28022900
SmallVector<std::pair<SILValue, EndScopeKind>, 4> EndScopeList;
28032901
SILValue EltPtr =
@@ -2820,12 +2918,14 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
28202918
}
28212919
llvm_unreachable("Covered switch isn't covered!");
28222920
}
2921+
return EltPtr;
28232922
};
28242923

28252924
// Destroy all the allocation's fields, not including the allocation
28262925
// itself, if we have a class initializer.
2827-
auto destroyMemoryElements = [&](SILLocation Loc,
2926+
auto destroyMemoryElements = [&](SILInstruction *Release, SILLocation Loc,
28282927
AvailabilitySet Availability) {
2928+
auto *Previous = Release->getPreviousInstruction();
28292929
// Delegating initializers don't model the fields of the class.
28302930
if (TheMemory.isClassInitSelf() && TheMemory.isDelegatingInit())
28312931
return;
@@ -2865,9 +2965,10 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
28652965

28662966
// Set up the initialized release block.
28672967
B.setInsertionPoint(ReleaseBlock->begin());
2868-
destroyMemoryElement(Loc, Elt);
2968+
auto EltPtr = destroyMemoryElement(Loc, Elt);
28692969

28702970
B.setInsertionPoint(ContBlock->begin());
2971+
adjustAllocBoxEndBorrow(Previous, EltPtr, B);
28712972
}
28722973
};
28732974

@@ -2944,10 +3045,10 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
29443045
// If false, self is uninitialized and must be freed.
29453046
B.setInsertionPoint(DeallocBlock->begin());
29463047
B.setCurrentDebugScope(DeallocBlock->begin()->getDebugScope());
2947-
destroyMemoryElements(Loc, Availability);
3048+
destroyMemoryElements(Release, Loc, Availability);
29483049
processUninitializedRelease(Release, false, B.getInsertionPoint());
29493050
} else {
2950-
destroyMemoryElements(Loc, Availability);
3051+
destroyMemoryElements(Release, Loc, Availability);
29513052
processUninitializedRelease(Release, false, B.getInsertionPoint());
29523053

29533054
// The original strong_release or destroy_addr instruction is
@@ -2972,7 +3073,7 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
29723073

29733074
// self.init or super.init was not called. If we're in the super.init
29743075
// case, destroy any initialized fields.
2975-
destroyMemoryElements(Loc, Availability);
3076+
destroyMemoryElements(Release, Loc, Availability);
29763077
processUninitializedRelease(Release, false, B.getInsertionPoint());
29773078
break;
29783079

@@ -3019,7 +3120,7 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
30193120
// If false, self is uninitialized and must be freed.
30203121
B.setInsertionPoint(DeallocBlock->begin());
30213122
B.setCurrentDebugScope(DeallocBlock->begin()->getDebugScope());
3022-
destroyMemoryElements(Loc, Availability);
3123+
destroyMemoryElements(Release, Loc, Availability);
30233124
processUninitializedRelease(Release, false, B.getInsertionPoint());
30243125

30253126
break;
@@ -3054,7 +3155,7 @@ handleConditionalDestroys(SILValue ControlVariableAddr) {
30543155
// If false, self is uninitialized and must be freed.
30553156
B.setInsertionPoint(DeallocBlock->begin());
30563157
B.setCurrentDebugScope(DeallocBlock->begin()->getDebugScope());
3057-
destroyMemoryElements(Loc, Availability);
3158+
destroyMemoryElements(Release, Loc, Availability);
30583159
processUninitializedRelease(Release, false, B.getInsertionPoint());
30593160

30603161
break;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-frontend -emit-sil %s -o /dev/null
2+
3+
// For OSLogTestHelper.
4+
// REQUIRES: VENDOR=apple
5+
6+
import OSLogTestHelper
7+
8+
struct Thing {
9+
let guts: AnyObject
10+
}
11+
12+
func getThings() -> [Thing] { [] }
13+
14+
func run() {
15+
var things: [Thing]
16+
while(true) {
17+
things = getThings()
18+
OSLogMessage("count: \(things.count)")
19+
}
20+
}
21+

0 commit comments

Comments
 (0)