Skip to content

Commit f373f6e

Browse files
committed
[ownership] Add an exhaustive load borrow invalidation checker.
This verifier validates that while a load_borrow's value is live (that is until it is invalidated by its end_borrow), the load_borrow's address source is never written to. The reason why this verifier is especially important now is that I am adding many optimizations that convert `load [copy]` -> `load_borrow`. If that optimization messes up, we break this invariant [in fact, an optimization I am working on right now violated the invariant =--(]. So by adding this verifier I am checking that semantic arc opts doesn't break it as well as eliminating any other such bugs from the compiler (in the future).
1 parent b2a742c commit f373f6e

File tree

9 files changed

+948
-19
lines changed

9 files changed

+948
-19
lines changed

include/swift/SIL/SILValue.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,18 @@ class ValueBase : public SILNode, public SILAllocated<ValueBase> {
316316
}
317317
SILInstruction *getDefiningInstruction();
318318

319+
/// Return the SIL instruction that can be used to describe the first time
320+
/// this value is available.
321+
///
322+
/// For instruction results, this returns getDefiningInstruction(). For
323+
/// arguments, this returns SILBasicBlock::begin() for the argument's parent
324+
/// block. Returns nullptr for SILUndef.
325+
const SILInstruction *getDefiningInsertionPoint() const {
326+
return const_cast<ValueBase *>(this)->getDefiningInsertionPoint();
327+
}
328+
329+
SILInstruction *getDefiningInsertionPoint();
330+
319331
struct DefiningInstructionResult {
320332
SILInstruction *Instruction;
321333
size_t ResultIndex;

lib/SIL/IR/SILValue.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ SILInstruction *ValueBase::getDefiningInstruction() {
7272
return nullptr;
7373
}
7474

75+
SILInstruction *ValueBase::getDefiningInsertionPoint() {
76+
if (auto *inst = getDefiningInstruction())
77+
return inst;
78+
if (auto *arg = dyn_cast<SILArgument>(this))
79+
return &*arg->getParentBlock()->begin();
80+
return nullptr;
81+
}
82+
7583
Optional<ValueBase::DefiningInstructionResult>
7684
ValueBase::getDefiningInstructionResult() {
7785
if (auto *inst = dyn_cast<SingleValueInstruction>(this))

lib/SIL/Verifier/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
sil_register_sources(
2+
LoadBorrowInvalidationChecker.cpp
23
LinearLifetimeChecker.cpp
34
MemoryLifetime.cpp
45
SILOwnershipVerifier.cpp

lib/SIL/Verifier/LinearLifetimeChecker.cpp

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,15 @@ struct State {
4444
/// parent block of the value.
4545
Optional<SILValue> value;
4646

47-
/// The block where the live range begins. If the field value is not None,
48-
/// then this is value->getParentBlock();
49-
SILBasicBlock *beginBlock;
47+
/// The insertion point where the live range begins. If the field value is not
48+
/// None, then:
49+
//
50+
// 1. If this value is defined by an instruction, it will be
51+
// value->getDefiningInstruction().
52+
//
53+
// 2. If this is an argument, this is the first instruction in the argument's
54+
// defining block.
55+
SILInstruction *beginInst;
5056

5157
/// The result error object that use to signal either that no errors were
5258
/// found or if errors are found the specific type of error that was found.
@@ -84,8 +90,8 @@ struct State {
8490
LinearLifetimeChecker::ErrorBehaviorKind errorBehavior,
8591
Optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
8692
ArrayRef<Operand *> consumingUses, ArrayRef<Operand *> nonConsumingUses)
87-
: value(value), beginBlock(value->getParentBlock()), error(errorBehavior),
88-
visitedBlocks(visitedBlocks),
93+
: value(value), beginInst(value->getDefiningInsertionPoint()),
94+
error(errorBehavior), visitedBlocks(visitedBlocks),
8995
leakingBlockCallback(leakingBlockCallback),
9096
consumingUses(consumingUses), nonConsumingUses(nonConsumingUses) {}
9197

@@ -94,11 +100,13 @@ struct State {
94100
LinearLifetimeChecker::ErrorBehaviorKind errorBehavior,
95101
Optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
96102
ArrayRef<Operand *> consumingUses, ArrayRef<Operand *> nonConsumingUses)
97-
: value(), beginBlock(beginBlock), error(errorBehavior),
103+
: value(), beginInst(&*beginBlock->begin()), error(errorBehavior),
98104
visitedBlocks(visitedBlocks),
99105
leakingBlockCallback(leakingBlockCallback),
100106
consumingUses(consumingUses), nonConsumingUses(nonConsumingUses) {}
101107

108+
SILBasicBlock *getBeginBlock() const { return beginInst->getParent(); }
109+
102110
void initializeAllNonConsumingUses(ArrayRef<Operand *> nonConsumingUsers);
103111
void initializeAllConsumingUses(
104112
ArrayRef<Operand *> consumingUsers,
@@ -159,6 +167,30 @@ void State::initializeAllNonConsumingUses(
159167
ArrayRef<Operand *> nonConsumingUsers) {
160168
for (Operand *use : nonConsumingUsers) {
161169
auto *userBlock = use->getUser()->getParent();
170+
171+
// First check if this non consuming user is in our definition block. If so,
172+
// validate that it is strictly after our defining instruction. If so, just
173+
// emit an error now and exit.
174+
if (userBlock == getBeginBlock()) {
175+
if (std::find_if(beginInst->getIterator(), userBlock->end(),
176+
[&use](const SILInstruction &inst) -> bool {
177+
return use->getUser() == &inst;
178+
}) == userBlock->end()) {
179+
error.handleUseAfterFree([&] {
180+
llvm::errs() << "Function: '"
181+
<< getBeginBlock()->getParent()->getName() << "'\n"
182+
<< "Found use before def?!\n"
183+
<< "Value: ";
184+
if (auto v = value) {
185+
llvm::errs() << *v;
186+
} else {
187+
llvm::errs() << "N/A. \n";
188+
}
189+
});
190+
return;
191+
}
192+
}
193+
162194
// First try to associate User with User->getParent().
163195
auto result =
164196
blocksWithNonConsumingUses.insert(std::make_pair(userBlock, use));
@@ -213,7 +245,7 @@ void State::initializeAllConsumingUses(
213245
// If this user is in the same block as the value, do not visit
214246
// predecessors. We must be extra tolerant here since we allow for
215247
// unreachable code.
216-
if (userBlock == beginBlock)
248+
if (userBlock == getBeginBlock())
217249
continue;
218250

219251
// Then for each predecessor of this block...
@@ -233,7 +265,8 @@ void State::initializeConsumingUse(Operand *consumingUse,
233265
return;
234266

235267
error.handleOverConsume([&] {
236-
llvm::errs() << "Function: '" << beginBlock->getParent()->getName() << "'\n"
268+
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
269+
<< "'\n"
237270
<< "Found over consume?!\n";
238271
if (auto v = value) {
239272
llvm::errs() << "Value: " << *v;
@@ -266,7 +299,7 @@ void State::checkForSameBlockUseAfterFree(Operand *consumingUse,
266299
return nonConsumingUse->getUser() == &i;
267300
}) != userBlock->end()) {
268301
error.handleUseAfterFree([&] {
269-
llvm::errs() << "Function: '" << beginBlock->getParent()->getName()
302+
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
270303
<< "'\n"
271304
<< "Found use after free?!\n"
272305
<< "Value: ";
@@ -304,7 +337,8 @@ void State::checkPredsForDoubleConsume(Operand *consumingUse,
304337
}
305338

306339
error.handleOverConsume([&] {
307-
llvm::errs() << "Function: '" << beginBlock->getParent()->getName() << "'\n"
340+
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
341+
<< "'\n"
308342
<< "Found over consume?!\n"
309343
<< "Value: ";
310344
if (auto v = value) {
@@ -335,7 +369,8 @@ void State::checkPredsForDoubleConsume(SILBasicBlock *userBlock) {
335369
}
336370

337371
error.handleOverConsume([&] {
338-
llvm::errs() << "Function: '" << beginBlock->getParent()->getName() << "'\n"
372+
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
373+
<< "'\n"
339374
<< "Found over consume?!\n"
340375
<< "Value: ";
341376
if (auto v = value) {
@@ -402,7 +437,7 @@ void State::performDataflow(DeadEndBlocks &deBlocks) {
402437
// further to do since we do not want to visit the predecessors of our
403438
// dominating block. On the other hand, we do want to add its successors to
404439
// the successorBlocksThatMustBeVisited set.
405-
if (block == beginBlock)
440+
if (block == getBeginBlock())
406441
continue;
407442

408443
// Then for each predecessor of this block:
@@ -438,7 +473,7 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
438473

439474
// If we are supposed to error on leaks, do so now.
440475
error.handleLeak([&] {
441-
llvm::errs() << "Function: '" << beginBlock->getParent()->getName()
476+
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
442477
<< "'\n"
443478
<< "Error! Found a leak due to a consuming post-dominance "
444479
"failure!\n";
@@ -472,7 +507,7 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
472507
}
473508

474509
error.handleUseAfterFree([&] {
475-
llvm::errs() << "Function: '" << beginBlock->getParent()->getName()
510+
llvm::errs() << "Function: '" << getBeginBlock()->getParent()->getName()
476511
<< "'\n"
477512
<< "Found use after free due to unvisited non lifetime "
478513
"ending uses?!\n"

0 commit comments

Comments
 (0)