Skip to content

Commit 636a19d

Browse files
authored
Merge pull request #74707 from jckarter/consume-during-borrow-checks
MoveOnlyAddressChecker: More robust checking for consume-during-borrow.
2 parents 8dbcd11 + 27a8852 commit 636a19d

File tree

13 files changed

+153
-28
lines changed

13 files changed

+153
-28
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4591,14 +4591,29 @@ class StoreInst
45914591
/// instruction in its use-def list.
45924592
class LoadBorrowInst :
45934593
public UnaryInstructionBase<SILInstructionKind::LoadBorrowInst,
4594-
SingleValueInstruction> {
4594+
SingleValueInstruction>
4595+
{
45954596
friend class SILBuilder;
45964597

4598+
bool Unchecked = false;
4599+
45974600
public:
45984601
LoadBorrowInst(SILDebugLocation DebugLoc, SILValue LValue)
45994602
: UnaryInstructionBase(DebugLoc, LValue,
46004603
LValue->getType().getObjectType()) {}
46014604

4605+
// True if the invariants on `load_borrow` have not been checked and
4606+
// should not be strictly enforced.
4607+
//
4608+
// This can only occur during raw SIL before move-only checking occurs.
4609+
// Developers can write incorrect code using noncopyable types that
4610+
// consumes or mutates a memory location while that location is borrowed,
4611+
// but the move-only checker must diagnose those problems before canonical
4612+
// SIL is formed.
4613+
bool isUnchecked() const { return Unchecked; }
4614+
4615+
void setUnchecked(bool value) { Unchecked = value; }
4616+
46024617
using EndBorrowRange =
46034618
decltype(std::declval<ValueBase>().getUsersOfType<EndBorrowInst>());
46044619

lib/SIL/IR/SILPrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,9 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
17341734
}
17351735

17361736
void visitLoadBorrowInst(LoadBorrowInst *LBI) {
1737+
if (LBI->isUnchecked()) {
1738+
*this << "[unchecked] ";
1739+
}
17371740
*this << getIDAndType(LBI->getOperand());
17381741
}
17391742

lib/SIL/Parser/ParseSIL.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3734,12 +3734,28 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,
37343734

37353735
case SILInstructionKind::LoadBorrowInst: {
37363736
SourceLoc AddrLoc;
3737+
3738+
bool IsUnchecked = false;
3739+
StringRef AttrName;
3740+
SourceLoc AttrLoc;
3741+
if (parseSILOptional(AttrName, AttrLoc, *this)) {
3742+
if (AttrName == "unchecked") {
3743+
IsUnchecked = true;
3744+
} else {
3745+
P.diagnose(InstLoc.getSourceLoc(),
3746+
diag::sil_invalid_attribute_for_instruction, AttrName,
3747+
"load_borrow");
3748+
return true;
3749+
}
3750+
}
37373751

37383752
if (parseTypedValueRef(Val, AddrLoc, B) ||
37393753
parseSILDebugLocation(InstLoc, B))
37403754
return true;
37413755

3742-
ResultVal = B.createLoadBorrow(InstLoc, Val);
3756+
auto LB = B.createLoadBorrow(InstLoc, Val);
3757+
LB->setUnchecked(IsUnchecked);
3758+
ResultVal = LB;
37433759
break;
37443760
}
37453761

lib/SIL/Verifier/MemoryLifetimeVerifier.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,9 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
789789
requireBitsSet(bits, sbi->getDest(), &I);
790790
locations.clearBits(bits, sbi->getDest());
791791
} else if (auto *lbi = dyn_cast<LoadBorrowInst>(ebi->getOperand())) {
792-
requireBitsSet(bits, lbi->getOperand(), &I);
792+
if (!lbi->isUnchecked()) {
793+
requireBitsSet(bits, lbi->getOperand(), &I);
794+
}
793795
}
794796
break;
795797
}

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2640,8 +2640,13 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
26402640
requireSameType(LBI->getOperand()->getType().getObjectType(),
26412641
LBI->getType(),
26422642
"Load operand type and result type mismatch");
2643-
require(loadBorrowImmutabilityAnalysis.isImmutable(LBI),
2644-
"Found load borrow that is invalidated by a local write?!");
2643+
if (LBI->isUnchecked()) {
2644+
require(LBI->getModule().getStage() == SILStage::Raw,
2645+
"load_borrow can only be [unchecked] in raw SIL");
2646+
} else {
2647+
require(loadBorrowImmutabilityAnalysis.isImmutable(LBI),
2648+
"Found load borrow that is invalidated by a local write?!");
2649+
}
26452650
}
26462651

26472652
void checkBeginBorrowInst(BeginBorrowInst *bbi) {

lib/SILGen/SILGenLValue.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,11 @@ namespace {
12321232
return base;
12331233
}
12341234
auto result = SGF.B.createLoadBorrow(loc, base.getValue());
1235+
// Mark the load_borrow as unchecked. We can't stop the source code from
1236+
// trying to mutate or consume the same lvalue during this borrow, so
1237+
// we don't want verifiers to trip before the move checker gets a chance
1238+
// to diagnose these situations.
1239+
result->setUnchecked(true);
12351240
return SGF.emitFormalEvaluationManagedBorrowedRValueWithCleanup(loc,
12361241
base.getValue(), result);
12371242
}

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,23 +1259,29 @@ void UseState::initializeLiveness(
12591259
<< *livenessInstAndValue.first;
12601260
liveness.print(llvm::dbgs()));
12611261
}
1262+
1263+
auto updateForLivenessAccess = [&](BeginAccessInst *beginAccess,
1264+
const SmallBitVector &livenessMask) {
1265+
for (auto *endAccess : beginAccess->getEndAccesses()) {
1266+
liveness.updateForUse(endAccess, livenessMask, false /*lifetime ending*/);
1267+
}
1268+
};
12621269

12631270
for (auto livenessInstAndValue : nonconsumingUses) {
12641271
if (auto *lbi = dyn_cast<LoadBorrowInst>(livenessInstAndValue.first)) {
12651272
auto accessPathWithBase =
12661273
AccessPathWithBase::computeInScope(lbi->getOperand());
12671274
if (auto *beginAccess =
12681275
dyn_cast_or_null<BeginAccessInst>(accessPathWithBase.base)) {
1269-
for (auto *endAccess : beginAccess->getEndAccesses()) {
1270-
liveness.updateForUse(endAccess, livenessInstAndValue.second,
1271-
false /*lifetime ending*/);
1272-
}
1276+
updateForLivenessAccess(beginAccess, livenessInstAndValue.second);
12731277
} else {
12741278
for (auto *ebi : lbi->getEndBorrows()) {
12751279
liveness.updateForUse(ebi, livenessInstAndValue.second,
12761280
false /*lifetime ending*/);
12771281
}
12781282
}
1283+
} else if (auto *bai = dyn_cast<BeginAccessInst>(livenessInstAndValue.first)) {
1284+
updateForLivenessAccess(bai, livenessInstAndValue.second);
12791285
} else {
12801286
liveness.updateForUse(livenessInstAndValue.first,
12811287
livenessInstAndValue.second,

lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,22 @@ void MoveOnlyChecker::checkAddresses() {
205205

206206
namespace {
207207

208+
static bool canonicalizeLoadBorrows(SILFunction *F) {
209+
bool changed = false;
210+
for (auto &block : *F) {
211+
for (auto &inst : block) {
212+
if (auto *lbi = dyn_cast<LoadBorrowInst>(&inst)) {
213+
if (lbi->isUnchecked()) {
214+
changed = true;
215+
lbi->setUnchecked(false);
216+
}
217+
}
218+
}
219+
}
220+
221+
return changed;
222+
}
223+
208224
class MoveOnlyCheckerPass : public SILFunctionTransform {
209225
void run() override {
210226
auto *fn = getFunction();
@@ -219,8 +235,11 @@ class MoveOnlyCheckerPass : public SILFunctionTransform {
219235
// If an earlier pass told use to not emit diagnostics for this function,
220236
// clean up any copies, invalidate the analysis, and return early.
221237
if (fn->hasSemanticsAttr(semantics::NO_MOVEONLY_DIAGNOSTICS)) {
222-
if (cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction()))
238+
bool didChange = canonicalizeLoadBorrows(fn);
239+
didChange |= cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction());
240+
if (didChange) {
223241
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
242+
}
224243
return;
225244
}
226245

@@ -243,6 +262,11 @@ class MoveOnlyCheckerPass : public SILFunctionTransform {
243262
checker.diagnosticEmitter);
244263
}
245264

265+
// Remaining borrows
266+
// should be correctly immutable. We can canonicalize any remaining
267+
// `load_borrow [unchecked]` instructions.
268+
checker.madeChange |= canonicalizeLoadBorrows(fn);
269+
246270
checker.madeChange |=
247271
cleanupNonCopyableCopiesAfterEmittingDiagnostic(fn);
248272

lib/Serialization/DeserializeSIL.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2261,7 +2261,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
22612261
UNARY_INSTRUCTION(EndLifetime)
22622262
UNARY_INSTRUCTION(ExtendLifetime)
22632263
UNARY_INSTRUCTION(CopyBlock)
2264-
UNARY_INSTRUCTION(LoadBorrow)
22652264
UNARY_INSTRUCTION(EndInitLetRef)
22662265
REFCOUNTING_INSTRUCTION(StrongRetain)
22672266
REFCOUNTING_INSTRUCTION(StrongRelease)
@@ -2272,6 +2271,17 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
22722271
#undef UNARY_INSTRUCTION
22732272
#undef REFCOUNTING_INSTRUCTION
22742273

2274+
case SILInstructionKind::LoadBorrowInst: {
2275+
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
2276+
auto LB = Builder.createLoadBorrow(
2277+
Loc, getLocalValue(Builder.maybeGetFunction(), ValID,
2278+
getSILType(MF->getType(TyID),
2279+
(SILValueCategory)TyCategory, Fn)));
2280+
LB->setUnchecked(Attr != 0);
2281+
ResultInst = LB;
2282+
break;
2283+
}
2284+
22752285
case SILInstructionKind::BeginBorrowInst: {
22762286
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
22772287
auto isLexical = IsLexical_t(Attr & 0x1);

lib/Serialization/SerializeSIL.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,8 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
16021602
} else if (auto *I = dyn_cast<CopyableToMoveOnlyWrapperValueInst>(&SI)) {
16031603
Attr = I->getForwardingOwnershipKind() == OwnershipKind::Owned ? true
16041604
: false;
1605+
} else if (auto *LB = dyn_cast<LoadBorrowInst>(&SI)) {
1606+
Attr = LB->isUnchecked();
16051607
}
16061608
writeOneOperandLayout(SI.getKind(), Attr, SI.getOperand(0));
16071609
break;

0 commit comments

Comments
 (0)