Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] MoveOnlyAddressChecker: More robust checking for consume-during-borrow. #74711

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
MoveOnlyAddressChecker: More robust checking for consume-during-borrow.
- While an opaque borrow access occurs to part of a value, the entire scope of
  the access needs to be treated as a liveness range, so add the `EndAccess`es
  to the liveness range.
- The SIL verifier may crash the compiler on SILGen-generated code when the
  developer's source contains consume-during-borrow code patterns. Allow
  `load_borrow` instructions to be marked `[unchecked]`, which suppresses
  verifier checks until the move checker runs and gets a chance to properly
  diagnose these errors.

Fixes rdar:https://124360175.
  • Loading branch information
jckarter committed Jun 25, 2024
commit 8518c719608d3b373951dfccc3b7d2b0736b2bea
17 changes: 16 additions & 1 deletion include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -4588,14 +4588,29 @@ class EndBorrowInst;
/// instruction in its use-def list.
class LoadBorrowInst :
public UnaryInstructionBase<SILInstructionKind::LoadBorrowInst,
SingleValueInstruction> {
SingleValueInstruction>
{
friend class SILBuilder;

bool Unchecked = false;

public:
LoadBorrowInst(SILDebugLocation DebugLoc, SILValue LValue)
: UnaryInstructionBase(DebugLoc, LValue,
LValue->getType().getObjectType()) {}

// True if the invariants on `load_borrow` have not been checked and
// should not be strictly enforced.
//
// This can only occur during raw SIL before move-only checking occurs.
// Developers can write incorrect code using noncopyable types that
// consumes or mutates a memory location while that location is borrowed,
// but the move-only checker must diagnose those problems before canonical
// SIL is formed.
bool isUnchecked() const { return Unchecked; }

void setUnchecked(bool value) { Unchecked = value; }

using EndBorrowRange =
decltype(std::declval<ValueBase>().getUsersOfType<EndBorrowInst>());

Expand Down
3 changes: 3 additions & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1738,6 +1738,9 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
}

void visitLoadBorrowInst(LoadBorrowInst *LBI) {
if (LBI->isUnchecked()) {
*this << "[unchecked] ";
}
*this << getIDAndType(LBI->getOperand());
}

Expand Down
18 changes: 17 additions & 1 deletion lib/SIL/Parser/ParseSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3727,12 +3727,28 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B,

case SILInstructionKind::LoadBorrowInst: {
SourceLoc AddrLoc;

bool IsUnchecked = false;
StringRef AttrName;
SourceLoc AttrLoc;
if (parseSILOptional(AttrName, AttrLoc, *this)) {
if (AttrName == "unchecked") {
IsUnchecked = true;
} else {
P.diagnose(InstLoc.getSourceLoc(),
diag::sil_invalid_attribute_for_instruction, AttrName,
"load_borrow");
return true;
}
}

if (parseTypedValueRef(Val, AddrLoc, B) ||
parseSILDebugLocation(InstLoc, B))
return true;

ResultVal = B.createLoadBorrow(InstLoc, Val);
auto LB = B.createLoadBorrow(InstLoc, Val);
LB->setUnchecked(IsUnchecked);
ResultVal = LB;
break;
}

Expand Down
4 changes: 3 additions & 1 deletion lib/SIL/Verifier/MemoryLifetimeVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,9 @@ void MemoryLifetimeVerifier::checkBlock(SILBasicBlock *block, Bits &bits) {
requireBitsSet(bits, sbi->getDest(), &I);
locations.clearBits(bits, sbi->getDest());
} else if (auto *lbi = dyn_cast<LoadBorrowInst>(ebi->getOperand())) {
requireBitsSet(bits, lbi->getOperand(), &I);
if (!lbi->isUnchecked()) {
requireBitsSet(bits, lbi->getOperand(), &I);
}
}
break;
}
Expand Down
9 changes: 7 additions & 2 deletions lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2626,8 +2626,13 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
requireSameType(LBI->getOperand()->getType().getObjectType(),
LBI->getType(),
"Load operand type and result type mismatch");
require(loadBorrowImmutabilityAnalysis.isImmutable(LBI),
"Found load borrow that is invalidated by a local write?!");
if (LBI->isUnchecked()) {
require(LBI->getModule().getStage() == SILStage::Raw,
"load_borrow can only be [unchecked] in raw SIL");
} else {
require(loadBorrowImmutabilityAnalysis.isImmutable(LBI),
"Found load borrow that is invalidated by a local write?!");
}
}

void checkBeginBorrowInst(BeginBorrowInst *bbi) {
Expand Down
5 changes: 5 additions & 0 deletions lib/SILGen/SILGenLValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,11 @@ namespace {
return base;
}
auto result = SGF.B.createLoadBorrow(loc, base.getValue());
// Mark the load_borrow as unchecked. We can't stop the source code from
// trying to mutate or consume the same lvalue during this borrow, so
// we don't want verifiers to trip before the move checker gets a chance
// to diagnose these situations.
result->setUnchecked(true);
return SGF.emitFormalEvaluationManagedBorrowedRValueWithCleanup(loc,
base.getValue(), result);
}
Expand Down
16 changes: 11 additions & 5 deletions lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1257,23 +1257,29 @@ void UseState::initializeLiveness(
<< *livenessInstAndValue.first;
liveness.print(llvm::dbgs()));
}

auto updateForLivenessAccess = [&](BeginAccessInst *beginAccess,
const SmallBitVector &livenessMask) {
for (auto *endAccess : beginAccess->getEndAccesses()) {
liveness.updateForUse(endAccess, livenessMask, false /*lifetime ending*/);
}
};

for (auto livenessInstAndValue : nonconsumingUses) {
if (auto *lbi = dyn_cast<LoadBorrowInst>(livenessInstAndValue.first)) {
auto accessPathWithBase =
AccessPathWithBase::computeInScope(lbi->getOperand());
if (auto *beginAccess =
dyn_cast<BeginAccessInst>(accessPathWithBase.base)) {
for (auto *endAccess : beginAccess->getEndAccesses()) {
liveness.updateForUse(endAccess, livenessInstAndValue.second,
false /*lifetime ending*/);
}
dyn_cast_or_null<BeginAccessInst>(accessPathWithBase.base)) {
updateForLivenessAccess(beginAccess, livenessInstAndValue.second);
} else {
for (auto *ebi : lbi->getEndBorrows()) {
liveness.updateForUse(ebi, livenessInstAndValue.second,
false /*lifetime ending*/);
}
}
} else if (auto *bai = dyn_cast<BeginAccessInst>(livenessInstAndValue.first)) {
updateForLivenessAccess(bai, livenessInstAndValue.second);
} else {
liveness.updateForUse(livenessInstAndValue.first,
livenessInstAndValue.second,
Expand Down
26 changes: 25 additions & 1 deletion lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,22 @@ void MoveOnlyChecker::checkAddresses() {

namespace {

static bool canonicalizeLoadBorrows(SILFunction *F) {
bool changed = false;
for (auto &block : *F) {
for (auto &inst : block) {
if (auto *lbi = dyn_cast<LoadBorrowInst>(&inst)) {
if (lbi->isUnchecked()) {
changed = true;
lbi->setUnchecked(false);
}
}
}
}

return changed;
}

class MoveOnlyCheckerPass : public SILFunctionTransform {
void run() override {
auto *fn = getFunction();
Expand All @@ -217,8 +233,11 @@ class MoveOnlyCheckerPass : public SILFunctionTransform {
// If an earlier pass told use to not emit diagnostics for this function,
// clean up any copies, invalidate the analysis, and return early.
if (fn->hasSemanticsAttr(semantics::NO_MOVEONLY_DIAGNOSTICS)) {
if (cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction()))
bool didChange = canonicalizeLoadBorrows(fn);
didChange |= cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction());
if (didChange) {
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
return;
}

Expand All @@ -241,6 +260,11 @@ class MoveOnlyCheckerPass : public SILFunctionTransform {
checker.diagnosticEmitter);
}

// Remaining borrows
// should be correctly immutable. We can canonicalize any remaining
// `load_borrow [unchecked]` instructions.
checker.madeChange |= canonicalizeLoadBorrows(fn);

checker.madeChange |=
cleanupNonCopyableCopiesAfterEmittingDiagnostic(fn);

Expand Down
12 changes: 11 additions & 1 deletion lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2257,7 +2257,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
UNARY_INSTRUCTION(FixLifetime)
UNARY_INSTRUCTION(EndLifetime)
UNARY_INSTRUCTION(CopyBlock)
UNARY_INSTRUCTION(LoadBorrow)
UNARY_INSTRUCTION(EndInitLetRef)
REFCOUNTING_INSTRUCTION(StrongRetain)
REFCOUNTING_INSTRUCTION(StrongRelease)
Expand All @@ -2269,6 +2268,17 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn,
#undef UNARY_INSTRUCTION
#undef REFCOUNTING_INSTRUCTION

case SILInstructionKind::LoadBorrowInst: {
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
auto LB = Builder.createLoadBorrow(
Loc, getLocalValue(Builder.maybeGetFunction(), ValID,
getSILType(MF->getType(TyID),
(SILValueCategory)TyCategory, Fn)));
LB->setUnchecked(Attr != 0);
ResultInst = LB;
break;
}

case SILInstructionKind::BeginBorrowInst: {
assert(RecordKind == SIL_ONE_OPERAND && "Layout should be OneOperand.");
auto isLexical = IsLexical_t(Attr & 0x1);
Expand Down
2 changes: 2 additions & 0 deletions lib/Serialization/SerializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,8 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) {
} else if (auto *I = dyn_cast<CopyableToMoveOnlyWrapperValueInst>(&SI)) {
Attr = I->getForwardingOwnershipKind() == OwnershipKind::Owned ? true
: false;
} else if (auto *LB = dyn_cast<LoadBorrowInst>(&SI)) {
Attr = LB->isUnchecked();
}
writeOneOperandLayout(SI.getKind(), Attr, SI.getOperand(0));
break;
Expand Down
34 changes: 17 additions & 17 deletions test/SILGen/moveonly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ public struct LoadableSubscriptGetOnlyTester : ~Copyable {
// The get call
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP]]
// CHECK: apply {{%.*}}([[TEMP_MARK]], {{%.*}}, [[LOAD_BORROW]])
Expand All @@ -989,7 +989,7 @@ public struct LoadableSubscriptGetOnlyTester : ~Copyable {
// CHECK: [[M2_PROJECT:%.*]] = project_box [[M2_BORROW]]
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: apply {{%.*}}([[M2_PROJECT]], {{%.*}}, [[LOAD]])
// CHECK: end_borrow [[LOAD]]
// CHECK: end_access [[ACCESS]]
Expand Down Expand Up @@ -1030,7 +1030,7 @@ public func testSubscriptGetOnly_BaseLoadable_ResultAddressOnly_Let() {
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[ARG]]
//
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[ACCESS]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP]]
// CHECK: apply {{%.*}}([[TEMP_MARK]], {{%.*}}, [[LOAD_BORROW]])
Expand All @@ -1049,7 +1049,7 @@ public func testSubscriptGetOnly_BaseLoadable_ResultAddressOnly_InOut(m: inout L
// The get call
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[GLOBAL_ADDR]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP]]
// CHECK: apply {{%.*}}([[TEMP_MARK]], {{%.*}}, [[LOAD_BORROW]])
Expand Down Expand Up @@ -1078,7 +1078,7 @@ public struct LoadableSubscriptGetOnlyTesterNonCopyableStructParent : ~Copyable
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[GEP:%.*]] = struct_element_addr [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[GEP]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[GEP]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP]]
// CHECK: apply {{%.*}}([[TEMP_MARK]], {{%.*}}, [[LOAD_BORROW]])
Expand All @@ -1090,7 +1090,7 @@ public struct LoadableSubscriptGetOnlyTesterNonCopyableStructParent : ~Copyable
// The second get call.
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: [[VALUE:%.*]] = apply {{%.*}}([[LOAD_BORROW]])
//
// CHECK: [[BORROWED_VALUE:%.*]] = begin_borrow [[VALUE]]
Expand Down Expand Up @@ -1137,7 +1137,7 @@ public func testSubscriptGetOnlyThroughNonCopyableParentStruct_BaseLoadable_Resu
//
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[MARK]]
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]]
// CHECK: [[LOAD:%.*]] = load_borrow [[GEP]]
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[GEP]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP]]
// CHECK: apply {{%.*}}([[TEMP_MARK]], {{%.*}}, [[LOAD]])
Expand All @@ -1155,7 +1155,7 @@ public func testSubscriptGetOnlyThroughNonCopyableParentStruct_BaseLoadable_Resu
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[GLOBAL]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[GEP:%.*]] = struct_element_addr [[MARK]]
// CHECK: [[LOAD:%.*]] = load_borrow [[GEP]]
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[GEP]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP]]
// CHECK: apply {{%.*}}([[TEMP_MARK]], {{%.*}}, [[LOAD]])
Expand Down Expand Up @@ -1192,7 +1192,7 @@ public class LoadableSubscriptGetOnlyTesterClassParent {
// CHECK: [[TEMP:%.*]] = alloc_stack $LoadableSubscriptGetOnlyTester
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[TEMP]]
// CHECK: [[TEMP_MARK_BORROW:%.*]] = store_borrow [[CORO_RESULT]] to [[TEMP_MARK]]
// CHECK: [[LOAD:%.*]] = load_borrow [[TEMP_MARK_BORROW]]
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[TEMP_MARK_BORROW]]
// CHECK: [[TEMP2:%.*]] = alloc_stack $
// CHECK: [[TEMP2_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP2]]
// CHECK: apply {{%.*}}([[TEMP2_MARK]], {{%.*}}, [[LOAD]])
Expand All @@ -1214,7 +1214,7 @@ public class LoadableSubscriptGetOnlyTesterClassParent {
// CHECK: [[TEMP_MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[TEMP]]
// CHECK: [[TEMP_MARK_BORROW:%.*]] = store_borrow [[CORO_RESULT]] to [[TEMP_MARK]]
// CHECK: [[GEP:%.*]] = struct_element_addr [[TEMP_MARK_BORROW]]
// CHECK: [[LOAD:%.*]] = load_borrow [[GEP]]
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[GEP]]
// CHECK: [[TEMP2:%.*]] = alloc_stack $
// CHECK: [[TEMP2_MARK:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[TEMP2]]
// CHECK: apply {{%.*}}([[TEMP2_MARK]], {{%.*}}, [[LOAD]])
Expand All @@ -1226,7 +1226,7 @@ public class LoadableSubscriptGetOnlyTesterClassParent {
// Third read.
//
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[LOAD:%.*]] = load_borrow [[ACCESS]]
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[ACCESS]]
// CHECK: ([[CORO_RESULT_ORIG:%.*]], [[CORO_TOKEN:%.*]]) = begin_apply {{%.*}}([[LOAD]])
// CHECK: [[CORO_RESULT_CP:%.*]] = copy_value [[CORO_RESULT_ORIG]]
// CHECK: [[CORO_RESULT_MK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[CORO_RESULT_CP]]
Expand Down Expand Up @@ -1455,7 +1455,7 @@ public struct LoadableSubscriptGetSetTesterNonCopyableStructParent : ~Copyable {
// The second get call.
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: [[VALUE:%.*]] = apply {{%.*}}([[LOAD_BORROW]])
// CHECK: [[BORROWED_VALUE:%.*]] = begin_borrow [[VALUE]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
Expand Down Expand Up @@ -1652,7 +1652,7 @@ public class LoadableSubscriptGetSetTesterClassParent {
// Third read.
//
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[CLASS:%.*]] = load_borrow [[ACCESS]]
// CHECK: [[CLASS:%.*]] = load_borrow [unchecked] [[ACCESS]]
// CHECK: ([[CORO_RESULT_ORIG:%.*]], [[CORO_TOKEN:%.*]]) = begin_apply {{%.*}}([[CLASS]])
// CHECK: [[CORO_RESULT_CP:%.*]] = copy_value [[CORO_RESULT_ORIG]]
// CHECK: [[CORO_RESULT_MK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[CORO_RESULT_CP]]
Expand Down Expand Up @@ -1911,7 +1911,7 @@ public struct LoadableSubscriptReadModifyTesterNonCopyableStructParent : ~Copyab
// The second get call.
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: [[VALUE:%.*]] = apply {{%.*}}([[LOAD_BORROW]])
// CHECK: [[BORROWED_VALUE:%.*]] = begin_borrow [[VALUE]]
// CHECK: ([[CORO_RESULT_ORIG:%.*]], [[CORO_TOKEN:%.*]]) = begin_apply {{%.*}}({{%.*}}, [[BORROWED_VALUE]])
Expand Down Expand Up @@ -2092,7 +2092,7 @@ public class LoadableSubscriptReadModifyTesterClassParent {
// Third read.
//
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[CLASS:%.*]] = load_borrow [[ACCESS]]
// CHECK: [[CLASS:%.*]] = load_borrow [unchecked] [[ACCESS]]
// CHECK: ([[CORO_RESULT_ORIG:%.*]], [[CORO_TOKEN:%.*]]) = begin_apply {{%.*}}([[CLASS]])
// CHECK: [[CORO_RESULT_CP:%.*]] = copy_value [[CORO_RESULT_ORIG]]
// CHECK: [[CORO_RESULT_MK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[CORO_RESULT_CP]]
Expand Down Expand Up @@ -2348,7 +2348,7 @@ public struct LoadableSubscriptGetModifyTesterNonCopyableStructParent : ~Copyabl
// The second get call.
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [unchecked] [[MARK]]
// CHECK: [[VALUE:%.*]] = apply {{%.*}}([[LOAD_BORROW]])
// CHECK: [[BORROWED_VALUE:%.*]] = begin_borrow [[VALUE]]
// CHECK: [[TEMP:%.*]] = alloc_stack $AddressOnlyProtocol
Expand Down Expand Up @@ -2502,7 +2502,7 @@ public class LoadableSubscriptGetModifyTesterClassParent {
// Third read.
//
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
// CHECK: [[CLASS:%.*]] = load_borrow [[ACCESS]]
// CHECK: [[CLASS:%.*]] = load_borrow [unchecked] [[ACCESS]]
// CHECK: ([[CORO_RESULT_ORIG:%.*]], [[CORO_TOKEN:%.*]]) = begin_apply {{%.*}}([[CLASS]])
// CHECK: [[CORO_RESULT_CP:%.*]] = copy_value [[CORO_RESULT_ORIG]]
// CHECK: [[CORO_RESULT_MK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[CORO_RESULT_CP]]
Expand Down
15 changes: 15 additions & 0 deletions test/SILOptimizer/moveonly_consume_during_borrow_1.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// RUN: %target-swift-frontend -emit-sil -verify %s

func foo(x: consuming Foo) { // expected-error{{'x' used after consume}}
x.bar.foo(x) // expected-error{{overlapping accesses to 'x'}} expected-note 3 {{}}
}

struct Foo: ~Copyable {
var bar: Bar {
_read { fatalError() }
}
}

struct Bar: ~Copyable {
func foo(_: consuming Foo) {}
}
Loading