Skip to content

Commit b1c5631

Browse files
Merge pull request #67384 from nate-chandler/opaque-values/20230718/1/emit_end_borrows/destructure_and_extract
[AddressLowering] End borrow scopes for extracts/destructures at enclosing guaranteed boundary.
2 parents a1aeacf + 4c5ce62 commit b1c5631

File tree

3 files changed

+135
-7
lines changed

3 files changed

+135
-7
lines changed

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1371,7 +1371,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
13711371
// guaranteed address-only values are forwarded with the same
13721372
// representation. Non-destructive projection is allowed. Aggregation and
13731373
// destructive disaggregation is not allowed. See SIL.rst, Forwarding
1374-
// Addres-Only Values.
1374+
// Address-Only Values.
13751375
if (ownership == OwnershipKind::Guaranteed && fwdOp.isAddressOnly()) {
13761376
require(fwdOp.hasSameRepresentation(),
13771377
"Forwarding a guaranteed address-only value requires the same "

lib/SILOptimizer/Mandatory/AddressLowering.cpp

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
#include "swift/SIL/SILInstruction.h"
151151
#include "swift/SIL/SILValue.h"
152152
#include "swift/SIL/SILVisitor.h"
153+
#include "swift/SIL/StackList.h"
153154
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
154155
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
155156
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -2259,6 +2260,13 @@ void ApplyRewriter::rewriteApply(ArrayRef<SILValue> newCallArgs) {
22592260
/// ending uses.
22602261
static void emitEndBorrows(SILValue value, AddressLoweringState &pass);
22612262

2263+
/// Emit end_borrows at the lifetime ends of the guaranteed value which
2264+
/// encloses \p enclosingValue.
2265+
static void
2266+
emitEndBorrowsAtEnclosingGuaranteedBoundary(SILValue lifetimeToEnd,
2267+
SILValue enclosingValue,
2268+
AddressLoweringState &pass);
2269+
22622270
void ApplyRewriter::convertBeginApplyWithOpaqueYield() {
22632271
// Avoid revisiting this apply.
22642272
bool erased = pass.indirectApplies.erase(apply);
@@ -3268,7 +3276,8 @@ void UseRewriter::rewriteDestructure(SILInstruction *destructure) {
32683276
}
32693277
result->replaceAllUsesWith(loadElement);
32703278
if (guaranteed) {
3271-
emitEndBorrows(loadElement, pass);
3279+
emitEndBorrowsAtEnclosingGuaranteedBoundary(
3280+
loadElement, destructure->getOperand(0), pass);
32723281
}
32733282
}
32743283
}
@@ -3395,8 +3404,56 @@ static void emitEndBorrows(SILValue value, AddressLoweringState &pass) {
33953404
});
33963405
}
33973406

3407+
/// Emit end_borrows at the lifetime ends of the guaranteed value which
3408+
/// encloses \p enclosingValue.
3409+
///
3410+
/// precondition: \p enclosingValue must be of opaque type
3411+
static void
3412+
emitEndBorrowsAtEnclosingGuaranteedBoundary(SILValue lifetimeToEnd,
3413+
SILValue enclosingValue,
3414+
AddressLoweringState &pass) {
3415+
/// It's necessary that enclosingValue be of opaque type. In practice, this
3416+
/// is the only situation in which AddressLowering needs to create a borrow
3417+
/// scope corresponding to some guaranteed value: the enclosingValue is the
3418+
/// opaque value that AddressLowering is rewriting.
3419+
///
3420+
/// It's required in order to ensure that no introducer of enclosingValue has
3421+
/// a phi scope-ending use. That enables just visiting the local scope ending
3422+
/// uses.
3423+
///
3424+
/// Given: enclosingValue is opaque.
3425+
///
3426+
/// Then every introducer is opaque. In fact every introducer's type is some
3427+
/// aggregate in which enclosingValue's type appears. The reason is that
3428+
/// representation-changing forwarding guaranteed operations are not allowed
3429+
/// on opaque values. (See SIL.rst, Forwarding Address-Only Values).
3430+
///
3431+
/// Thus no introducer is reborrowed. For suppose that some introducer were
3432+
/// reborrowed. Then it would appear as an operand of some phi with
3433+
/// guaranteed ownership. And that phi would be of opaque type. But that's
3434+
/// invalid! (See SILVerifier::visitSILPhiArgument.)
3435+
assert(enclosingValue->getType().isAddressOnly(*pass.function));
3436+
TinyPtrVector<SILValue> introducers;
3437+
visitBorrowIntroducers(enclosingValue, [&](SILValue introducer) {
3438+
introducers.push_back(introducer);
3439+
return true;
3440+
});
3441+
assert(introducers.size() == 1);
3442+
for (auto introducer : introducers) {
3443+
assert(introducer->getType().isAddressOnly(*pass.function));
3444+
auto borrow = BorrowedValue(introducer);
3445+
borrow.visitLocalScopeEndingUses([&](Operand *use) {
3446+
assert(!PhiOperand(use));
3447+
pass.getBuilder(use->getUser()->getIterator())
3448+
.createEndBorrow(pass.genLoc(), lifetimeToEnd);
3449+
return true;
3450+
});
3451+
}
3452+
}
3453+
33983454
// Extract from an opaque struct or tuple.
33993455
void UseRewriter::emitExtract(SingleValueInstruction *extractInst) {
3456+
auto source = extractInst->getOperand(0);
34003457
SILValue extractAddr = addrMat.materializeDefProjection(extractInst);
34013458

34023459
if (extractInst->getType().isAddressOnly(*pass.function)) {
@@ -3427,7 +3484,7 @@ void UseRewriter::emitExtract(SingleValueInstruction *extractInst) {
34273484
SILValue loadElement =
34283485
builder.emitLoadBorrowOperation(extractInst->getLoc(), extractAddr);
34293486
replaceUsesWithLoad(extractInst, loadElement);
3430-
emitEndBorrows(loadElement, pass);
3487+
emitEndBorrowsAtEnclosingGuaranteedBoundary(loadElement, source, pass);
34313488
}
34323489

34333490
void UseRewriter::visitStructExtractInst(StructExtractInst *extractInst) {
@@ -3808,12 +3865,22 @@ static void rewriteFunction(AddressLoweringState &pass) {
38083865
if (valueDef->getType().isAddress())
38093866
continue;
38103867

3868+
// Collect end_borrows and rewrite them last so that when rewriting other
3869+
// users the lifetime ends of a borrow introducer can be used.
3870+
StackList<EndBorrowInst *> endBorrows(pass.function);
38113871
SmallPtrSet<Operand *, 8> originalUses;
38123872
SmallVector<Operand *, 8> uses(valueDef->getUses());
38133873
for (auto *oper : uses) {
38143874
originalUses.insert(oper);
3875+
if (auto *ebi = dyn_cast<EndBorrowInst>(oper->getUser())) {
3876+
endBorrows.push_back(ebi);
3877+
continue;
3878+
}
38153879
UseRewriter::rewriteUse(oper, pass);
38163880
}
3881+
for (auto *ebi : endBorrows) {
3882+
UseRewriter::rewriteUse(&ebi->getOperandRef(), pass);
3883+
}
38173884
// Rewrite every new use that was added.
38183885
uses.clear();
38193886
for (auto *use : valueDef->getUses()) {

test/SILOptimizer/address_lowering.sil

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ enum Mixed<T> {
6060
case i(Int)
6161
case t(T)
6262
case o(AnyObject)
63-
};
63+
}
64+
65+
indirect enum RE<T> {
66+
case recursive(RE, T)
67+
case other(AnyObject)
68+
}
6469

6570
class TestGeneric<T> {
6671
init()
@@ -756,9 +761,9 @@ bb0(%0 : @owned $SRef<T>):
756761
// CHECK: [[L:%.*]] = load_borrow [[E0]] : $*AnyObject
757762
// CHECK: apply %{{.*}}([[L]]) : $@convention(thin) (@guaranteed AnyObject) -> ()
758763
// CHECK: [[C:%.*]] = copy_value [[L]] : $AnyObject
759-
// CHECK: end_borrow [[L]] : $AnyObject
760764
// CHECK: [[E1:%.*]] = struct_element_addr %2 : $*SRef<T>, #SRef.element
761765
// CHECK: copy_addr [[E1]] to [init] %1 : $*T
766+
// CHECK: end_borrow [[L]] : $AnyObject
762767
// CHECK: destroy_addr %2 : $*SRef<T>
763768
// CHECK: store [[C]] to [init] %0 : $*AnyObject
764769
// CHECK-NOT: dealloc_stack
@@ -808,9 +813,9 @@ bb0(%0 : @owned $(AnyObject, T)):
808813
// CHECK: [[L:%.*]] = load_borrow %3 : $*AnyObject
809814
// CHECK: apply %{{.*}}([[L]]) : $@convention(thin) (@guaranteed AnyObject) -> ()
810815
// CHECK: [[C:%.*]] = copy_value [[L]] : $AnyObject
811-
// CHECK: end_borrow [[L]] : $AnyObject
812816
// CHECK: [[E1:%.*]] = tuple_element_addr %2 : $*(AnyObject, T), 1
813817
// CHECK: copy_addr [[E1]] to [init] %1 : $*T
818+
// CHECK: end_borrow [[L]] : $AnyObject
814819
// CHECK: destroy_addr %2 : $*(AnyObject, T)
815820
// CHECK: store [[C]] to [init] %0 : $*AnyObject
816821
// CHECK-NOT: dealloc_stack
@@ -2224,9 +2229,9 @@ exit:
22242229
// CHECK: [[INSTANCE:%[^,]+]] = unchecked_take_enum_data_addr
22252230
// CHECK: [[KLASS_ADDR:%[^,]+]] = tuple_element_addr [[INSTANCE]] {{.*}}, 0
22262231
// CHECK: [[KLASS:%[^,]+]] = load_borrow [[KLASS_ADDR]]
2227-
// CHECK: end_borrow [[KLASS]]
22282232
// CHECK: [[ANY_ADDR:%[^,]+]] = tuple_element_addr [[INSTANCE]] {{.*}}, 1
22292233
// CHECK: apply undef<Any>([[ANY_ADDR]])
2234+
// CHECK: end_borrow [[KLASS]]
22302235
// CHECK: destroy_addr [[INSTANCE]]
22312236
// CHECK-LABEL: } // end sil function 'test_destructure_tuple_1_guaranteed'
22322237
sil [ossa] @test_destructure_tuple_1_guaranteed : $@convention(thin) () -> () {
@@ -2249,6 +2254,62 @@ exit:
22492254
return %505 : $()
22502255
}
22512256

2257+
// Check that the end_borrows created on behalf of the destructure_tuple end
2258+
// after the use of the load_borrow of the projection of the terminator result
2259+
// of the switch_enum that is guaranteed by that load_borrow.
2260+
//
2261+
// CHECK-LABEL: sil [ossa] @test_destructure_tuple_load_borrow_projected_box : {{.*}} {
2262+
// CHECK: {{bb[0-9]+}}([[OUTER:%[^,]+]] :
2263+
// CHECK: switch_enum [[OUTER]] : $RE<T>, case #RE.recursive!enumelt: [[OUTER_RECURSIVE:bb[0-9]+]], case #RE.other!enumelt: [[OUTER_OTHER:bb[0-9]+]]
2264+
// CHECK: [[OUTER_OTHER]]({{%[^,]+}} :
2265+
// CHECK: br [[EXIT:bb[0-9]+]]
2266+
// CHECK: [[OUTER_RECURSIVE]]([[TUPLE_BOX:%[^,]+]] :
2267+
// CHECK-NEXT: [[TUPLE_ADDR:%[^,]+]] = project_box [[TUPLE_BOX]]
2268+
// CHECK: [[INNER_ADDR:%[^,]+]] = tuple_element_addr [[TUPLE_ADDR]]
2269+
// CHECK: [[INNER:%[^,]+]] = load_borrow [[INNER_ADDR]]
2270+
// CHECK: switch_enum [[INNER]] : $RE<T>, case #RE.recursive!enumelt: [[INNER_RECURSIVE:bb[0-9]+]], case #RE.other!enumelt: [[INNER_OTHER:bb[0-9]+]]
2271+
// CHECK: [[INNER_OTHER]]([[OTHER_BOX:%[^,]+]] :
2272+
// CHECK: [[OTHER_ADDR:%[^,]+]] = project_box [[OTHER_BOX]]
2273+
// CHECK: [[OTHER:%[^,]+]] = load_borrow [[OTHER_ADDR]]
2274+
// CHECK: [[OTHER_COPY:%[^,]+]] = copy_value [[OTHER]]
2275+
// CHECK: end_borrow [[OTHER]]
2276+
// CHECK: end_borrow [[INNER]]
2277+
// CHECK: destroy_value [[OTHER_COPY]]
2278+
// CHECK: br [[EXIT]]
2279+
// CHECK: [[INNER_RECURSIVE]]
2280+
// CHECK: end_borrow [[INNER]]
2281+
// CHECK: br [[EXIT]]
2282+
// CHECK-LABEL: } // end sil function 'test_destructure_tuple_load_borrow_projected_box'
2283+
sil [ossa] @test_destructure_tuple_load_borrow_projected_box : $@convention(thin) <T> (@guaranteed RE<T>) -> () {
2284+
entry(%o : @guaranteed $RE<T>):
2285+
switch_enum %o : $RE<T>, case #RE.recursive!enumelt: outer_recursive, case #RE.other!enumelt: outer_other
2286+
2287+
outer_other(%other : @guaranteed $<τ_0_0> { var AnyObject } <T>):
2288+
br exit
2289+
2290+
outer_recursive(%tuple_box : @guaranteed $<Tau> { var (RE<Tau>, Tau) } <T>):
2291+
%tuple_addr = project_box %tuple_box : $<Tau> { var (RE<Tau>, Tau) } <T>, 0
2292+
%tuple = load_borrow %tuple_addr : $*(RE<T>, T)
2293+
(%i, %c) = destructure_tuple %tuple : $(RE<T>, T)
2294+
switch_enum %i : $RE<T>, case #RE.recursive!enumelt: inner_recursive, case #RE.other!enumelt: inner_other
2295+
2296+
inner_recursive(%in_tuple_box : @guaranteed $<Tau> { var (RE<Tau>, Tau) } <T>):
2297+
end_borrow %tuple : $(RE<T>, T)
2298+
br exit
2299+
2300+
inner_other(%s_box : @guaranteed $<Tau> { var AnyObject } <T>):
2301+
%s_addr = project_box %s_box : $<Tau> { var AnyObject } <T>, 0
2302+
%s = load_borrow %s_addr : $*AnyObject
2303+
%sc = copy_value %s : $AnyObject
2304+
end_borrow %s : $AnyObject
2305+
end_borrow %tuple : $(RE<T>, T)
2306+
destroy_value %sc : $AnyObject
2307+
br exit
2308+
2309+
exit:
2310+
%retval = tuple ()
2311+
return %retval : $()
2312+
}
22522313

22532314
// CHECK-LABEL: sil hidden [ossa] @test_store_1 : {{.*}} {
22542315
// CHECK: [[MAYBE_ADDR:%[^,]+]] = alloc_stack $Optional<Self>

0 commit comments

Comments
 (0)