Skip to content

Commit a966c1c

Browse files
committed
[AddressLowering] Move bound pattern into storage.
In a switch statement like ``` switch (x, y) { case (.foo(let a), .bar(let b)): ... ``` both `a` and `b` have lexical lifetimes which are represented with lexical borrow scopes. When lowering, those lexical lifetimes need to be preserved. Introduce lexical `alloc_stacks` for target blocks of `switch_enum` instructions and move the terminator result values into those alloc_stacks. These will naturally be marked lexical when the begin_borrow [lexical] is removed.
1 parent f62cc03 commit a966c1c

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

lib/SILOptimizer/Mandatory/AddressLowering.cpp

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,19 @@ static Operand *getProjectedDefOperand(SILValue value) {
808808
}
809809
}
810810

811+
/// If \p value is lexically borrowed, when it is lowered, it must be stored in
812+
/// an alloc_stack [lexical] of the same type so that its lifetime is preserved.
813+
static bool needsLexicalAllocStack(SILValue value) {
814+
for (auto *use : value->getUses()) {
815+
auto *user = use->getUser();
816+
if (auto *bbi = dyn_cast<BeginBorrowInst>(user)) {
817+
if (bbi->isLexical())
818+
return true;
819+
}
820+
}
821+
return false;
822+
}
823+
811824
/// If \p value is a an existential or enum, then return the existential or enum
812825
/// operand. These operations are always rewritten by the UseRewriter and always
813826
/// reuse the same storage as their operand. Note that if the operation's result
@@ -834,6 +847,9 @@ static Operand *getReusedStorageOperand(SILValue value) {
834847
return &cast<SingleValueInstruction>(value)->getOperandRef(0);
835848

836849
case ValueKind::SILPhiArgument: {
850+
if (needsLexicalAllocStack(value)) {
851+
return nullptr;
852+
}
837853
if (auto *term = cast<SILPhiArgument>(value)->getTerminatorForResult()) {
838854
if (auto *switchEnum = dyn_cast<SwitchEnumInst>(term)) {
839855
return &switchEnum->getAllOperands()[0];
@@ -2931,20 +2947,34 @@ void UseRewriter::visitSwitchEnumInst(SwitchEnumInst * switchEnum) {
29312947
assert(caseBB->getArguments().size() == 1);
29322948
SILArgument *caseArg = caseBB->getArguments()[0];
29332949

2934-
assert(&switchEnum->getOperandRef(0) == getReusedStorageOperand(caseArg));
29352950
assert(caseDecl->hasAssociatedValues() && "caseBB has a payload argument");
29362951

29372952
SILBuilder caseBuilder = pass.getBuilder(caseBB->begin());
2938-
auto *caseAddr =
2953+
auto *enumDataAddr =
29392954
caseBuilder.createUncheckedTakeEnumDataAddr(loc, enumAddr, caseDecl);
2955+
2956+
SILValue addrToLoad;
2957+
if (needsLexicalAllocStack(caseArg)) {
2958+
// This case has a variable bound to its payload. To preserve the lexical
2959+
// lifetime that variable introduces, it must be moved into the
2960+
// alloc_stack created for it, which will subsequently be marked lexical.
2961+
assert(!getReusedStorageOperand(caseArg));
2962+
auto caseAddr = pass.valueStorageMap.getStorage(caseArg).storageAddress;
2963+
caseBuilder.createCopyAddr(loc, enumDataAddr, caseAddr, IsTake,
2964+
IsInitialization);
2965+
addrToLoad = caseAddr;
2966+
} else {
2967+
assert(&switchEnum->getOperandRef(0) == getReusedStorageOperand(caseArg));
2968+
addrToLoad = enumDataAddr;
2969+
}
29402970
auto *caseLoad = caseBuilder.createTrivialLoadOr(
2941-
loc, caseAddr, LoadOwnershipQualifier::Take);
2971+
loc, addrToLoad, LoadOwnershipQualifier::Take);
29422972
caseArg->replaceAllUsesWith(caseLoad);
29432973
if (caseArg->getType().isAddressOnly(*pass.function)) {
29442974
// Remap caseArg to the new dummy load which will be deleted during
29452975
// deleteRewrittenInstructions.
29462976
pass.valueStorageMap.replaceValue(caseArg, caseLoad);
2947-
markRewritten(caseLoad, caseAddr);
2977+
markRewritten(caseLoad, addrToLoad);
29482978
}
29492979
caseBB->eraseArgument(0);
29502980
};

test/SILOptimizer/opaque_values_Onone.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@ struct Outer<T : Equatable> {
1919

2020
extension Outer.Inner {
2121
// CHECK-LABEL: sil hidden @outer_inner_compare
22-
// CHECK: switch_enum_addr {{%[^,]+}} : $*Outer<T>.Inner, case #Outer.Inner.inside!enumelt: {{bb[0-9]+}}, case #Outer.Inner.outside!enumelt: {{bb[0-9]+}}
22+
// CHECK: [[L_ADDR:%[^,]+]] = alloc_stack [lexical] $T
23+
// CHECK: [[R_ADDR:%[^,]+]] = alloc_stack [lexical] $T
24+
// CHECK: switch_enum_addr [[LHS_ADDR:%[^,]+]] : $*Outer<T>.Inner, case #Outer.Inner.inside!enumelt: [[LHS_IS_INSIDE:bb[0-9]+]], case #Outer.Inner.outside!enumelt: [[BASIC_BLOCK4:bb[0-9]+]]
25+
// CHECK: [[LHS_IS_INSIDE]]:
26+
// CHECK: [[LHS_INSIDE_ADDR:%[^,]+]] = unchecked_take_enum_data_addr [[LHS_ADDR]]
27+
// CHECK: copy_addr [take] [[LHS_INSIDE_ADDR]] to [initialization] [[L_ADDR]]
28+
// CHECK: switch_enum_addr [[RHS_ADDR:%[^,]+]] : $*Outer<T>.Inner, case #Outer.Inner.inside!enumelt: [[LHS_AND_RHS_ARE_INSIDE:bb[0-9]+]], default {{bb[0-9]+}}
29+
// CHECK: [[LHS_AND_RHS_ARE_INSIDE]]:
30+
// CHECK: [[RHS_INSIDE_ADDR:%[^,]+]] = unchecked_take_enum_data_addr [[RHS_ADDR]]
31+
// CHECK: copy_addr [take] [[RHS_INSIDE_ADDR]] to [initialization] [[R_ADDR]]
2332
// CHECK-LABEL: } // end sil function 'outer_inner_compare'
2433
@_silgen_name("outer_inner_compare")
2534
static func compare(_ lhs: Outer.Inner, _ rhs: Outer.Inner) -> Bool {

0 commit comments

Comments
 (0)