Skip to content

Commit d5e8625

Browse files
Merge pull request #62442 from nate-chandler/opaque-values/1/20221201
[AddressLowering] Handle indirectly yielded values used outside of coroutine range.
2 parents 186e55f + 867143d commit d5e8625

File tree

11 files changed

+449
-15
lines changed

11 files changed

+449
-15
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ SILValue findOwnershipReferenceRoot(SILValue ref);
206206

207207
/// Look through all ownership forwarding instructions to find the values which
208208
/// were originally borrowed.
209-
void findGuaranteedReferenceRoots(SILValue value,
209+
void findGuaranteedReferenceRoots(SILValue value, bool lookThroughNestedBorrows,
210210
SmallVectorImpl<SILValue> &roots);
211211

212212
/// Find the aggregate containing the first owned root of the

include/swift/SIL/SILArgumentConvention.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ struct SILArgumentConvention {
9494
bool isOwnedConvention() const {
9595
switch (Value) {
9696
case SILArgumentConvention::Indirect_In:
97+
case SILArgumentConvention::Indirect_In_Constant:
9798
case SILArgumentConvention::Direct_Owned:
9899
return true;
99100
case SILArgumentConvention::Indirect_In_Guaranteed:
100101
case SILArgumentConvention::Direct_Guaranteed:
101102
case SILArgumentConvention::Indirect_Inout:
102-
case SILArgumentConvention::Indirect_In_Constant:
103103
case SILArgumentConvention::Indirect_Out:
104104
case SILArgumentConvention::Indirect_InoutAliasable:
105105
case SILArgumentConvention::Direct_Unowned:

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ SILValue swift::findOwnershipReferenceRoot(SILValue ref) {
861861
}
862862

863863
void swift::findGuaranteedReferenceRoots(SILValue value,
864+
bool lookThroughNestedBorrows,
864865
SmallVectorImpl<SILValue> &roots) {
865866
GraphNodeWorklist<SILValue, 4> worklist;
866867
auto addAllOperandsToWorklist = [&worklist](SILInstruction *inst) -> bool {
@@ -882,7 +883,16 @@ void swift::findGuaranteedReferenceRoots(SILValue value,
882883
}
883884
}
884885
} else if (auto *inst = value->getDefiningInstruction()) {
885-
if (auto *result =
886+
if (auto *bbi = dyn_cast<BeginBorrowInst>(inst)) {
887+
auto borrowee = bbi->getOperand();
888+
if (lookThroughNestedBorrows &&
889+
borrowee->getOwnershipKind() == OwnershipKind::Guaranteed) {
890+
// A nested borrow, the root guaranteed earlier in the use-def chain.
891+
worklist.insert(borrowee);
892+
}
893+
// The borrowee isn't guaranteed or we aren't looking through nested
894+
// borrows. Fall through to add the begin_borrow to roots.
895+
} else if (auto *result =
886896
dyn_cast<FirstArgOwnershipForwardingSingleValueInst>(inst)) {
887897
if (result->getNumOperands() > 0) {
888898
worklist.insert(result->getOperand(0));

lib/SIL/Utils/MemoryLocations.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ void MemoryLocations::analyzeLocations(SILFunction *function) {
173173
}
174174
}
175175
}
176+
if (auto *BAI = dyn_cast<BeginApplyInst>(&I)) {
177+
auto convention = BAI->getSubstCalleeConv();
178+
auto yields = convention.getYields();
179+
auto yieldedValues = BAI->getYieldedValues();
180+
for (auto index : indices(yields)) {
181+
if (convention.isSILIndirect(yields[index])) {
182+
analyzeLocation(yieldedValues[index]);
183+
}
184+
}
185+
}
176186
}
177187
}
178188
}

lib/SIL/Verifier/MemoryLifetimeVerifier.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,39 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
401401
}
402402
break;
403403
}
404+
case SILInstructionKind::BeginApplyInst: {
405+
auto *BAI = cast<BeginApplyInst>(&I);
406+
auto yieldedValues = BAI->getYieldedValues();
407+
for (auto index : indices(yieldedValues)) {
408+
auto fnType = BAI->getSubstCalleeType();
409+
SILArgumentConvention argConv(
410+
fnType->getYields()[index].getConvention());
411+
if (argConv.isIndirectConvention()) {
412+
genBits(state, yieldedValues[index]);
413+
}
414+
}
415+
break;
416+
}
417+
case SILInstructionKind::EndApplyInst:
418+
case SILInstructionKind::AbortApplyInst: {
419+
auto *BAI = [&]() {
420+
if (auto *EAI = dyn_cast<EndApplyInst>(&I)) {
421+
return EAI->getBeginApply();
422+
}
423+
auto *AAI = dyn_cast<AbortApplyInst>(&I);
424+
return AAI->getBeginApply();
425+
}();
426+
auto yieldedValues = BAI->getYieldedValues();
427+
for (auto index : indices(yieldedValues)) {
428+
auto fnType = BAI->getSubstCalleeType();
429+
SILArgumentConvention argConv(
430+
fnType->getYields()[index].getConvention());
431+
if (argConv.isIndirectConvention()) {
432+
killBits(state, yieldedValues[index]);
433+
}
434+
}
435+
break;
436+
}
404437
case SILInstructionKind::YieldInst: {
405438
auto *YI = cast<YieldInst>(&I);
406439
for (Operand &op : YI->getAllOperands()) {
@@ -694,6 +727,48 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
694727
}
695728
break;
696729
}
730+
case SILInstructionKind::BeginApplyInst: {
731+
auto *BAI = cast<BeginApplyInst>(&I);
732+
auto yieldedValues = BAI->getYieldedValues();
733+
for (auto index : indices(yieldedValues)) {
734+
auto fnType = BAI->getSubstCalleeType();
735+
SILArgumentConvention argConv(
736+
fnType->getYields()[index].getConvention());
737+
if (argConv.isIndirectConvention()) {
738+
requireBitsClear(bits, yieldedValues[index], &I);
739+
locations.setBits(bits, yieldedValues[index]);
740+
}
741+
}
742+
break;
743+
}
744+
case SILInstructionKind::EndApplyInst:
745+
case SILInstructionKind::AbortApplyInst: {
746+
auto *BAI = [&]() {
747+
if (auto *EAI = dyn_cast<EndApplyInst>(&I)) {
748+
return EAI->getBeginApply();
749+
}
750+
auto *AAI = dyn_cast<AbortApplyInst>(&I);
751+
return AAI->getBeginApply();
752+
}();
753+
auto yieldedValues = BAI->getYieldedValues();
754+
for (auto index : indices(yieldedValues)) {
755+
auto fnType = BAI->getSubstCalleeType();
756+
SILArgumentConvention argConv(
757+
fnType->getYields()[index].getConvention());
758+
if (argConv.isIndirectConvention()) {
759+
if (argConv.isInoutConvention() ||
760+
argConv.isGuaranteedConvention()) {
761+
requireBitsSet(bits | ~nonTrivialLocations, yieldedValues[index],
762+
&I);
763+
} else if (argConv.isOwnedConvention()) {
764+
requireBitsClear(bits & nonTrivialLocations, yieldedValues[index],
765+
&I);
766+
}
767+
locations.clearBits(bits, yieldedValues[index]);
768+
}
769+
}
770+
break;
771+
}
697772
case SILInstructionKind::YieldInst: {
698773
auto *YI = cast<YieldInst>(&I);
699774
for (Operand &op : YI->getAllOperands()) {

lib/SILOptimizer/Mandatory/AddressLowering.cpp

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
#include "swift/SIL/BasicBlockUtils.h"
142142
#include "swift/SIL/DebugUtils.h"
143143
#include "swift/SIL/DynamicCasts.h"
144+
#include "swift/SIL/MemAccessUtils.h"
144145
#include "swift/SIL/OwnershipUtils.h"
145146
#include "swift/SIL/PrettyStackTrace.h"
146147
#include "swift/SIL/PrunedLiveness.h"
@@ -157,6 +158,7 @@
157158
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
158159
#include "swift/SILOptimizer/Utils/StackNesting.h"
159160
#include "llvm/ADT/DenseMap.h"
161+
#include "llvm/ADT/STLExtras.h"
160162
#include "llvm/ADT/SetVector.h"
161163
#include "llvm/Support/CommandLine.h"
162164
#include "llvm/Support/Debug.h"
@@ -345,6 +347,22 @@ static bool isStoreCopy(SILValue value) {
345347
if (!copyInst->hasOneUse())
346348
return false;
347349

350+
auto source = copyInst->getOperand();
351+
if (source->getOwnershipKind() == OwnershipKind::Guaranteed) {
352+
// [in_guaranteed_begin_apply_results] If any root of the source is a
353+
// begin_apply, we can't rely on projecting from the (rewritten) source:
354+
// The store may not be in the coroutine's range. The result would be
355+
// attempting to access invalid storage.
356+
SmallVector<SILValue, 4> roots;
357+
findGuaranteedReferenceRoots(source, /*lookThroughNestedBorrows=*/true,
358+
roots);
359+
if (llvm::any_of(roots, [](SILValue root) {
360+
return isa<BeginApplyInst>(root->getDefiningInstruction());
361+
})) {
362+
return false;
363+
}
364+
}
365+
348366
auto *user = value->getSingleUse()->getUser();
349367
return isa<StoreInst>(user);
350368
}
@@ -933,8 +951,19 @@ static bool doesNotNeedStackAllocation(SILValue value) {
933951
auto *defInst = value->getDefiningInstruction();
934952
if (!defInst)
935953
return false;
936-
937-
if (isa<LoadBorrowInst>(defInst) || isa<BeginApplyInst>(defInst))
954+
// [in_guaranteed_begin_apply_results] OSSA ensures that every use of a
955+
// guaranteed value resulting from a begin_apply will occur in the
956+
// coroutine's range (i.e. "before" the end_apply/abort apply).
957+
// AddressLowering takes advantage of this lack of uses outside of the
958+
// coroutine's range to directly use the storage that is yielded by the
959+
// coroutine rather than moving it to local storage.
960+
//
961+
// It is, however, valid in OSSA to have uses of an owned value produced by a
962+
// begin_apply outside of the coroutine range. So in that case, it is
963+
// necessary to introduce new storage and move to it.
964+
if (isa<LoadBorrowInst>(defInst) ||
965+
(isa<BeginApplyInst>(defInst) &&
966+
value.getOwnershipKind() == OwnershipKind::Guaranteed))
938967
return true;
939968

940969
return false;
@@ -2230,9 +2259,32 @@ void ApplyRewriter::convertBeginApplyWithOpaqueYield() {
22302259
continue;
22312260
}
22322261
if (oldResult.getType().isAddressOnly(*pass.function)) {
2233-
// Remap storage when an address-only type is yielded as an opaque value.
2234-
pass.valueStorageMap.setStorageAddress(&oldResult, &newResult);
2235-
pass.valueStorageMap.getStorage(&oldResult).markRewritten();
2262+
auto info = newCall->getSubstCalleeConv().getYieldInfoForOperandIndex(i);
2263+
assert(info.isFormalIndirect());
2264+
if (info.isConsumed()) {
2265+
// Because it is legal to have uses of an owned value produced by a
2266+
// begin_apply after a coroutine's range, AddressLowering must move the
2267+
// value into local storage so that such out-of-coroutine-range uses can
2268+
// be rewritten in terms of that address (instead of being rewritten in
2269+
// terms of the yielded owned storage which is no longer valid beyond
2270+
// the coroutine's range).
2271+
auto &storage = pass.valueStorageMap.getStorage(&oldResult);
2272+
auto destAddr = addrMat.materializeAddress(&oldResult);
2273+
storage.storageAddress = destAddr;
2274+
storage.markRewritten();
2275+
resultBuilder.createCopyAddr(callLoc, &newResult, destAddr,
2276+
info.isConsumed() ? IsTake : IsNotTake,
2277+
IsInitialization);
2278+
} else {
2279+
// [in_guaranteed_begin_apply_results] Because OSSA ensure that all uses
2280+
// of a guaranteed value produced by a begin_apply are used within the
2281+
// coroutine's range, AddressLowering will not introduce uses of
2282+
// invalid memory by rewriting the uses of a yielded guaranteed opaque
2283+
// value as uses of yielded guaranteed storage. However, it must
2284+
// allocate storage for copies of [projections of] such values.
2285+
pass.valueStorageMap.setStorageAddress(&oldResult, &newResult);
2286+
pass.valueStorageMap.getStorage(&oldResult).markRewritten();
2287+
}
22362288
continue;
22372289
}
22382290
assert(oldResult.getType().isObject());
@@ -3271,7 +3323,7 @@ static void emitEndBorrows(SILValue value, AddressLoweringState &pass) {
32713323
SSAPrunedLiveness liveness(&discoveredBlocks);
32723324
liveness.initializeDef(value);
32733325
for (auto *use : usePoints) {
3274-
assert(!use->isLifetimeEnding());
3326+
assert(!use->isLifetimeEnding() || isa<EndBorrowInst>(use->getUser()));
32753327
liveness.updateForUse(use->getUser(), /*lifetimeEnding*/ false);
32763328
}
32773329
PrunedLivenessBoundary guaranteedBoundary;

lib/SILOptimizer/Transforms/DeadCodeElimination.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ void DCE::markLive() {
292292
// Visit the end_borrows of all the borrow scopes that this
293293
// begin_borrow could be borrowing.
294294
SmallVector<SILValue, 4> roots;
295-
findGuaranteedReferenceRoots(borrowInst->getOperand(), roots);
295+
findGuaranteedReferenceRoots(borrowInst->getOperand(),
296+
/*lookThroughNestedBorrows=*/false,
297+
roots);
296298
for (auto root : roots) {
297299
visitTransitiveEndBorrows(root,
298300
[&](EndBorrowInst *endBorrow) {

test/SIL/memory_lifetime.sil

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,3 +683,46 @@ bb3:
683683
return %14 : $()
684684
}
685685

686+
sil [ossa] @begin_apply_in_destroy : $@convention(thin) () -> () {
687+
entry:
688+
(%instance, %token) = begin_apply undef<Inner>() : $@yield_once @convention(thin) <U> () -> @yields @in U
689+
destroy_addr %instance : $*Inner
690+
end_apply %token
691+
%retval = tuple ()
692+
return %retval : $()
693+
}
694+
695+
sil [ossa] @begin_apply_in_constant_destroy : $@convention(thin) () -> () {
696+
entry:
697+
(%instance, %token) = begin_apply undef<Inner>() : $@yield_once @convention(thin) <U> () -> @yields @in_constant U
698+
destroy_addr %instance : $*Inner
699+
end_apply %token
700+
%retval = tuple ()
701+
return %retval : $()
702+
}
703+
704+
sil [ossa] @begin_apply_in_guaranteed_no_destroy : $@convention(thin) () -> () {
705+
entry:
706+
(%instance, %token) = begin_apply undef<Inner>() : $@yield_once @convention(thin) <U> () -> @yields @in_guaranteed U
707+
end_apply %token
708+
%retval = tuple ()
709+
return %retval : $()
710+
}
711+
712+
// CHECK: SIL memory lifetime failure in @begin_apply_inout_destroy: memory is not initialized, but should be
713+
sil [ossa] @begin_apply_inout_no_destroy : $@convention(thin) () -> () {
714+
entry:
715+
(%instance, %token) = begin_apply undef<Inner>() : $@yield_once @convention(thin) <U> () -> @yields @inout U
716+
end_apply %token
717+
%retval = tuple ()
718+
return %retval : $()
719+
}
720+
721+
// CHECK: SIL memory lifetime failure in @begin_apply_inout_aliasable_destroy: memory is not initialized, but should be
722+
sil [ossa] @begin_apply_inout_aliasable_no_destroy : $@convention(thin) () -> () {
723+
entry:
724+
(%instance, %token) = begin_apply undef<Inner>() : $@yield_once @convention(thin) <U> () -> @yields @inout_aliasable U
725+
end_apply %token
726+
%retval = tuple ()
727+
return %retval : $()
728+
}

0 commit comments

Comments
 (0)