Skip to content

Commit 2d01286

Browse files
authored
Merge pull request swiftlang#36211 from eeckstein/memory-lifetime
SIL: support store_borrow and partial_apply in memory lifetime verification
2 parents a8b1ce2 + aa16864 commit 2d01286

File tree

12 files changed

+316
-28
lines changed

12 files changed

+316
-28
lines changed

docs/SIL.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3547,6 +3547,27 @@ by an `end_borrow`_ instruction. All `load_borrow`_ instructions must be
35473547
paired with exactly one `end_borrow`_ instruction along any path through the
35483548
program. Until `end_borrow`_, it is illegal to invalidate or store to ``%0``.
35493549

3550+
store_borrow
3551+
````````````
3552+
3553+
::
3554+
3555+
sil-instruction ::= 'store_borrow' sil-value 'to' sil-operand
3556+
3557+
store_borrow %0 to %1 : $*T
3558+
// $T must be a loadable type
3559+
// %1 must be an alloc_stack $T
3560+
3561+
Stores the value ``%0`` to a stack location ``%1``, which must be an
3562+
``alloc_stack $T``.
3563+
The stored value is alive until the ``dealloc_stack`` or until another
3564+
``store_borrow`` overwrites the value. During the its lifetime, the stored
3565+
value must not be modified or destroyed.
3566+
The source value ``%0`` is borrowed (i.e. not copied) and it's borrow scope
3567+
must outlive the lifetime of the stored value.
3568+
3569+
Note: This is the current implementation and the design is not final.
3570+
35503571
begin_borrow
35513572
````````````
35523573

include/swift/SIL/ApplySite.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,37 @@ class ApplySite {
348348
return getSubstCalleeConv().getSILArgumentConvention(calleeArgIdx);
349349
}
350350

351+
/// Return the SILArgumentConvention for the given applied argument operand at
352+
/// the apply instruction.
353+
///
354+
/// For full applies, this is equivalent to `getArgumentConvention`. But for
355+
/// a partial_apply, the argument ownership convention at the partial_apply
356+
/// instruction itself is different from the argument convention of the callee.
357+
/// For details see the partial_apply documentation in SIL.rst.
358+
SILArgumentConvention getArgumentOperandConvention(const Operand &oper) const {
359+
SILArgumentConvention conv = getArgumentConvention(oper);
360+
auto *pai = dyn_cast<PartialApplyInst>(Inst);
361+
if (!pai)
362+
return conv;
363+
switch (conv) {
364+
case SILArgumentConvention::Indirect_Inout:
365+
case SILArgumentConvention::Indirect_InoutAliasable:
366+
return conv;
367+
case SILArgumentConvention::Direct_Owned:
368+
case SILArgumentConvention::Direct_Unowned:
369+
case SILArgumentConvention::Direct_Guaranteed:
370+
return pai->isOnStack() ? SILArgumentConvention::Direct_Guaranteed
371+
: SILArgumentConvention::Direct_Owned;
372+
case SILArgumentConvention::Indirect_In:
373+
case SILArgumentConvention::Indirect_In_Constant:
374+
case SILArgumentConvention::Indirect_In_Guaranteed:
375+
return pai->isOnStack() ? SILArgumentConvention::Indirect_In_Guaranteed
376+
: SILArgumentConvention::Indirect_In;
377+
case SILArgumentConvention::Indirect_Out:
378+
llvm_unreachable("partial_apply cannot have an @out operand");
379+
}
380+
}
381+
351382
/// Return true if 'self' is an applied argument.
352383
bool hasSelfArgument() const {
353384
switch (ApplySiteKind(Inst->getKind())) {

lib/SIL/Verifier/MemoryLifetime.cpp

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,11 @@ bool MemoryLocations::analyzeLocationUsesRecursively(SILValue V, unsigned locIdx
347347
break;
348348
case SILInstructionKind::LoadInst:
349349
case SILInstructionKind::StoreInst:
350+
case SILInstructionKind::StoreBorrowInst:
350351
case SILInstructionKind::EndAccessInst:
351352
case SILInstructionKind::LoadBorrowInst:
352353
case SILInstructionKind::DestroyAddrInst:
354+
case SILInstructionKind::PartialApplyInst:
353355
case SILInstructionKind::ApplyInst:
354356
case SILInstructionKind::TryApplyInst:
355357
case SILInstructionKind::BeginApplyInst:
@@ -607,6 +609,9 @@ class MemoryLifetimeVerifier {
607609
SILFunction *function;
608610
MemoryLocations locations;
609611

612+
/// alloc_stack memory locations which are used for store_borrow.
613+
Bits storeBorrowLocations;
614+
610615
/// Returns true if the enum location \p locIdx can be proven to hold a
611616
/// hold a trivial value (e non-payload case) at \p atInst.
612617
bool isEnumTrivialAt(int locIdx, SILInstruction *atInst);
@@ -639,6 +644,18 @@ class MemoryLifetimeVerifier {
639644
/// \p addr, are set in \p bits.
640645
void requireBitsSet(const Bits &bits, SILValue addr, SILInstruction *where);
641646

647+
bool isStoreBorrowLocation(SILValue addr) {
648+
auto *loc = locations.getLocation(addr);
649+
return loc && storeBorrowLocations.anyCommon(loc->subLocations);
650+
}
651+
652+
/// Require that the location of addr is not an alloc_stack used for a
653+
/// store_borrow.
654+
void requireNoStoreBorrowLocation(SILValue addr, SILInstruction *where);
655+
656+
/// Register the destination address of a store_borrow as borrowed location.
657+
void registerStoreBorrowLocation(SILValue addr);
658+
642659
/// Handles locations of the predecessor's terminator, which are only valid
643660
/// in \p block.
644661
/// Example: @out results of try_apply. They are only valid in the
@@ -799,6 +816,21 @@ void MemoryLifetimeVerifier::requireBitsSet(const Bits &bits, SILValue addr,
799816
}
800817
}
801818

819+
void MemoryLifetimeVerifier::requireNoStoreBorrowLocation(SILValue addr,
820+
SILInstruction *where) {
821+
if (isStoreBorrowLocation(addr)) {
822+
reportError("store-borrow location cannot be written",
823+
locations.getLocation(addr)->selfAndParents.find_first(), where);
824+
}
825+
}
826+
827+
void MemoryLifetimeVerifier::registerStoreBorrowLocation(SILValue addr) {
828+
if (auto *loc = locations.getLocation(addr)) {
829+
storeBorrowLocations.resize(locations.getNumLocations());
830+
storeBorrowLocations |= loc->subLocations;
831+
}
832+
}
833+
802834
void MemoryLifetimeVerifier::initDataflow(MemoryDataflow &dataFlow) {
803835
// Initialize the entry and exit sets to all-bits-set. Except for the function
804836
// entry.
@@ -848,6 +880,12 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
848880
case SILInstructionKind::StoreInst:
849881
state.genBits(cast<StoreInst>(&I)->getDest(), locations);
850882
break;
883+
case SILInstructionKind::StoreBorrowInst: {
884+
SILValue destAddr = cast<StoreBorrowInst>(&I)->getDest();
885+
state.genBits(destAddr, locations);
886+
registerStoreBorrowLocation(destAddr);
887+
break;
888+
}
851889
case SILInstructionKind::CopyAddrInst: {
852890
auto *CAI = cast<CopyAddrInst>(&I);
853891
if (CAI->isTakeOfSrc())
@@ -870,12 +908,13 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
870908
case SILInstructionKind::DeallocStackInst:
871909
state.killBits(I.getOperand(0), locations);
872910
break;
911+
case SILInstructionKind::PartialApplyInst:
873912
case SILInstructionKind::ApplyInst:
874913
case SILInstructionKind::TryApplyInst: {
875-
FullApplySite FAS(&I);
914+
ApplySite AS(&I);
876915
for (Operand &op : I.getAllOperands()) {
877-
if (FAS.isArgumentOperand(op)) {
878-
setFuncOperandBits(state, op, FAS.getArgumentConvention(op),
916+
if (AS.isArgumentOperand(op)) {
917+
setFuncOperandBits(state, op, AS.getArgumentOperandConvention(op),
879918
isa<TryApplyInst>(&I));
880919
}
881920
}
@@ -1017,6 +1056,7 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
10171056
switch (LI->getOwnershipQualifier()) {
10181057
case LoadOwnershipQualifier::Take:
10191058
locations.clearBits(bits, LI->getOperand());
1059+
requireNoStoreBorrowLocation(LI->getOperand(), &I);
10201060
break;
10211061
case LoadOwnershipQualifier::Copy:
10221062
case LoadOwnershipQualifier::Trivial:
@@ -1042,19 +1082,29 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
10421082
case StoreOwnershipQualifier::Unqualified:
10431083
llvm_unreachable("unqualified store shouldn't be in ownership SIL");
10441084
}
1085+
requireNoStoreBorrowLocation(SI->getDest(), &I);
1086+
break;
1087+
}
1088+
case SILInstructionKind::StoreBorrowInst: {
1089+
SILValue destAddr = cast<StoreBorrowInst>(&I)->getDest();
1090+
locations.setBits(bits, destAddr);
1091+
registerStoreBorrowLocation(destAddr);
10451092
break;
10461093
}
10471094
case SILInstructionKind::CopyAddrInst: {
10481095
auto *CAI = cast<CopyAddrInst>(&I);
10491096
requireBitsSet(bits, CAI->getSrc(), &I);
1050-
if (CAI->isTakeOfSrc())
1097+
if (CAI->isTakeOfSrc()) {
10511098
locations.clearBits(bits, CAI->getSrc());
1099+
requireNoStoreBorrowLocation(CAI->getSrc(), &I);
1100+
}
10521101
if (CAI->isInitializationOfDest()) {
10531102
requireBitsClear(bits & nonTrivialLocations, CAI->getDest(), &I);
10541103
} else {
10551104
requireBitsSet(bits | ~nonTrivialLocations, CAI->getDest(), &I);
10561105
}
10571106
locations.setBits(bits, CAI->getDest());
1107+
requireNoStoreBorrowLocation(CAI->getDest(), &I);
10581108
break;
10591109
}
10601110
case SILInstructionKind::InjectEnumAddrInst: {
@@ -1066,38 +1116,45 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
10661116
requireBitsClear(bits & nonTrivialLocations, IEAI->getOperand(), &I);
10671117
locations.setBits(bits, IEAI->getOperand());
10681118
}
1119+
requireNoStoreBorrowLocation(IEAI->getOperand(), &I);
10691120
break;
10701121
}
1071-
case SILInstructionKind::InitEnumDataAddrInst:
1072-
requireBitsClear(bits, cast<InitEnumDataAddrInst>(&I)->getOperand(), &I);
1122+
case SILInstructionKind::InitEnumDataAddrInst: {
1123+
SILValue enumAddr = cast<InitEnumDataAddrInst>(&I)->getOperand();
1124+
requireBitsClear(bits, enumAddr, &I);
1125+
requireNoStoreBorrowLocation(enumAddr, &I);
10731126
break;
1127+
}
10741128
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
10751129
// Note that despite the name, unchecked_take_enum_data_addr does _not_
10761130
// "take" the payload of the Swift.Optional enum. This is a terrible
10771131
// hack in SIL.
1078-
auto *UTEDAI = cast<UncheckedTakeEnumDataAddrInst>(&I);
1079-
int enumIdx = locations.getLocationIdx(UTEDAI->getOperand());
1132+
SILValue enumAddr = cast<UncheckedTakeEnumDataAddrInst>(&I)->getOperand();
1133+
int enumIdx = locations.getLocationIdx(enumAddr);
10801134
if (enumIdx >= 0)
1081-
requireBitsSet(bits, UTEDAI->getOperand(), &I);
1135+
requireBitsSet(bits, enumAddr, &I);
1136+
requireNoStoreBorrowLocation(enumAddr, &I);
10821137
break;
10831138
}
10841139
case SILInstructionKind::DestroyAddrInst: {
10851140
SILValue opVal = cast<DestroyAddrInst>(&I)->getOperand();
10861141
requireBitsSet(bits | ~nonTrivialLocations, opVal, &I);
10871142
locations.clearBits(bits, opVal);
1143+
requireNoStoreBorrowLocation(opVal, &I);
10881144
break;
10891145
}
10901146
case SILInstructionKind::EndBorrowInst: {
10911147
if (SILValue orig = cast<EndBorrowInst>(&I)->getSingleOriginalValue())
10921148
requireBitsSet(bits, orig, &I);
10931149
break;
10941150
}
1151+
case SILInstructionKind::PartialApplyInst:
10951152
case SILInstructionKind::ApplyInst:
10961153
case SILInstructionKind::TryApplyInst: {
1097-
FullApplySite FAS(&I);
1154+
ApplySite AS(&I);
10981155
for (Operand &op : I.getAllOperands()) {
1099-
if (FAS.isArgumentOperand(op))
1100-
checkFuncArgument(bits, op, FAS.getArgumentConvention(op), &I);
1156+
if (AS.isArgumentOperand(op))
1157+
checkFuncArgument(bits, op, AS.getArgumentOperandConvention(op), &I);
11011158
}
11021159
break;
11031160
}
@@ -1114,7 +1171,11 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
11141171
break;
11151172
case SILInstructionKind::DeallocStackInst: {
11161173
SILValue opVal = cast<DeallocStackInst>(&I)->getOperand();
1117-
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
1174+
if (isStoreBorrowLocation(opVal)) {
1175+
requireBitsSet(bits, opVal, &I);
1176+
} else {
1177+
requireBitsClear(bits & nonTrivialLocations, opVal, &I);
1178+
}
11181179
// Needed to clear any bits of trivial locations (which are not required
11191180
// to be zero).
11201181
locations.clearBits(bits, opVal);
@@ -1129,6 +1190,9 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
11291190
void MemoryLifetimeVerifier::checkFuncArgument(Bits &bits, Operand &argumentOp,
11301191
SILArgumentConvention argumentConvention,
11311192
SILInstruction *applyInst) {
1193+
if (argumentConvention != SILArgumentConvention::Indirect_In_Guaranteed)
1194+
requireNoStoreBorrowLocation(argumentOp.get(), applyInst);
1195+
11321196
switch (argumentConvention) {
11331197
case SILArgumentConvention::Indirect_In:
11341198
case SILArgumentConvention::Indirect_In_Constant:
@@ -1166,6 +1230,7 @@ void MemoryLifetimeVerifier::verify() {
11661230
}
11671231
// Second step: handle single-block locations.
11681232
locations.handleSingleBlockLocations([this](SILBasicBlock *block) {
1233+
storeBorrowLocations.clear();
11691234
Bits bits(locations.getNumLocations());
11701235
checkBlock(block, bits);
11711236
});

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,6 +2193,23 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
21932193
}
21942194
}
21952195

2196+
void checkStoreBorrowInst(StoreBorrowInst *SI) {
2197+
require(SI->getSrc()->getType().isObject(),
2198+
"Can't store from an address source");
2199+
require(!fnConv.useLoweredAddresses()
2200+
|| SI->getSrc()->getType().isLoadable(*SI->getFunction()),
2201+
"Can't store a non loadable type");
2202+
require(SI->getDest()->getType().isAddress(),
2203+
"Must store to an address dest");
2204+
requireSameType(SI->getDest()->getType().getObjectType(),
2205+
SI->getSrc()->getType(),
2206+
"Store operand type and dest type mismatch");
2207+
2208+
// Note: This is the current implementation and the design is not final.
2209+
require(isa<AllocStackInst>(SI->getDest()),
2210+
"store_borrow destination can only be an alloc_stack");
2211+
}
2212+
21962213
void checkAssignInst(AssignInst *AI) {
21972214
SILValue Src = AI->getSrc(), Dest = AI->getDest();
21982215
require(AI->getModule().getStage() == SILStage::Raw,

lib/SILOptimizer/Mandatory/RawSILInstLowering.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "swift/SIL/SILInstruction.h"
1717
#include "swift/SILOptimizer/PassManager/Passes.h"
1818
#include "swift/SILOptimizer/PassManager/Transforms.h"
19+
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
1920
#include "llvm/ADT/Statistic.h"
2021

2122
STATISTIC(numAssignRewritten, "Number of assigns rewritten");
@@ -177,6 +178,7 @@ static void lowerAssignByWrapperInstruction(SILBuilderWithScope &b,
177178
SILValue dest = inst->getDest();
178179
SILLocation loc = inst->getLoc();
179180
SILBuilderWithScope forCleanup(std::next(inst->getIterator()));
181+
SingleValueInstruction *closureToDelete = nullptr;
180182

181183
switch (inst->getAssignDestination()) {
182184
case AssignByWrapperInst::Destination::BackingWrapper: {
@@ -199,8 +201,7 @@ static void lowerAssignByWrapperInstruction(SILBuilderWithScope &b,
199201
b.createStore(loc, wrappedSrc, dest, StoreOwnershipQualifier::Assign);
200202
}
201203
}
202-
// TODO: remove the unused setter function, which usually is a dead
203-
// partial_apply.
204+
closureToDelete = dyn_cast<SingleValueInstruction>(inst->getSetter());
204205
break;
205206
}
206207
case AssignByWrapperInst::Destination::WrappedValue: {
@@ -218,12 +219,13 @@ static void lowerAssignByWrapperInstruction(SILBuilderWithScope &b,
218219
// nested access violation.
219220
if (auto *BA = dyn_cast<BeginAccessInst>(dest))
220221
accessMarkers.push_back(BA);
221-
// TODO: remove the unused init function, which usually is a dead
222-
// partial_apply.
222+
closureToDelete = dyn_cast<SingleValueInstruction>(inst->getInitializer());
223223
break;
224224
}
225225
}
226226
inst->eraseFromParent();
227+
if (closureToDelete)
228+
tryDeleteDeadClosure(closureToDelete);
227229
}
228230

229231
static void deleteDeadAccessMarker(BeginAccessInst *BA) {

lib/SILOptimizer/Transforms/DestroyHoisting.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,10 @@ void DestroyHoisting::getUsedLocationsOfInst(Bits &bits, SILInstruction *I) {
350350
break;
351351
case SILInstructionKind::LoadInst:
352352
case SILInstructionKind::StoreInst:
353+
case SILInstructionKind::StoreBorrowInst:
353354
case SILInstructionKind::CopyAddrInst:
354355
case SILInstructionKind::InjectEnumAddrInst:
356+
case SILInstructionKind::PartialApplyInst:
355357
case SILInstructionKind::ApplyInst:
356358
case SILInstructionKind::TryApplyInst:
357359
case SILInstructionKind::YieldInst:

test/Reflection/capture_descriptors.sil

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ bb0(%t : $*T):
111111
return %12 : $()
112112
}
113113

114-
sil [ossa] @generic_caller3 : $@convention(thin) <A, B, C> () -> @owned @callee_guaranteed () -> () {
115-
bb0:
114+
sil [ossa] @generic_caller3 : $@convention(thin) <A, B, C> (@owned Optional<@callee_guaranteed @substituted <X, Y> (@in_guaranteed X) -> @out Y for <A, B>>) -> @owned @callee_guaranteed () -> () {
115+
bb0(%0 : @owned $Optional<@callee_guaranteed @substituted <X, Y> (@in_guaranteed X) -> @out Y for <A, B>>):
116116
%f = function_ref @generic_callee3 : $@convention(thin) <T, U> (@in_guaranteed T) -> ()
117117
%t = alloc_stack $Optional<@callee_guaranteed @substituted <X, Y> (@in_guaranteed X) -> @out Y for <A, B>>
118+
store %0 to [init] %t : $*Optional<@callee_guaranteed @substituted <X, Y> (@in_guaranteed X) -> @out Y for <A, B>>
118119
%c = partial_apply [callee_guaranteed] %f<Optional<(A) -> B>, (B, (C) -> Int)>(%t) : $@convention(thin) <T, U> (@in_guaranteed T) -> ()
119120
dealloc_stack %t : $*Optional<@callee_guaranteed @substituted <X, Y> (@in_guaranteed X) -> @out Y for <A, B>>
120121
return %c : $@callee_guaranteed () -> ()

0 commit comments

Comments
 (0)