Skip to content

Commit b619d30

Browse files
committed
[ownership] Track /all/ non consuming uses and emit errors for all of them instead of relying on jsut the last one in a block.
Beyond allowing us to emit better errors, this will allow me to (in a subsequent commit) count the amount of uses that are "outside" of the linear lifetime. I can then compare that against a passed in set of "non consuming uses". If the count of the number of uses "outside" of the linear lifetime equals the count of the passed in set of "non consuming uses", then I know that /all/ non consuming uses that I am testing against are not co-incident with the linear lifetime, meaning that they can not effect (in a local, direct sense) the linear lifetime. I am going to use that information to determine when it is safe to convert an inout form a load [copy] to a load_borrow in the face of local mutations. I can pass the set of local mutations as non-consuming uses to a linear lifetime consisting of the load [copy]/destroy_values and thus prove that no writes occur in between the load [copy]/destroy_value meaning it is safe to conver them to borrow form. NOTE: The aforementioned optimization is an extension of an optimization already in tree that just bails if we have any writes to an inout locally, which is so unfortunate.
1 parent bdbcc47 commit b619d30

File tree

4 files changed

+141
-155
lines changed

4 files changed

+141
-155
lines changed

include/swift/Basic/FrozenMultiMap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ class FrozenMultiMap {
231231

232232
/// Return a range of (key, ArrayRef<Value>) pairs. The keys are guaranteed to
233233
/// be in key sorted order and the ArrayRef<Value> are in insertion order.
234+
///
235+
/// The range skips all erased (key, ArrayRef<Value>) entries.
234236
RangeType getRange() const {
235237
assert(isFrozen() &&
236238
"Can not create range until data structure is frozen?!");

lib/SIL/Verifier/LinearLifetimeChecker.cpp

Lines changed: 63 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
#define DEBUG_TYPE "sil-linear-lifetime-checker"
2424
#include "LinearLifetimeCheckerPrivate.h"
25+
#include "swift/Basic/BlotMapVector.h"
26+
#include "swift/Basic/Defer.h"
27+
#include "swift/Basic/FrozenMultiMap.h"
2528
#include "swift/SIL/BasicBlockUtils.h"
2629
#include "swift/SIL/OwnershipUtils.h"
2730
#include "swift/SIL/SILBasicBlock.h"
@@ -80,7 +83,13 @@ struct State {
8083

8184
/// The set of blocks with non-consuming uses and the associated
8285
/// non-consuming use SILInstruction.
83-
llvm::SmallDenseMap<SILBasicBlock *, Operand *, 8> blocksWithNonConsumingUses;
86+
///
87+
/// NOTE: This is initialized in initializeAllNonConsumingUses after which it
88+
/// is frozen. Before this is frozen, one can only add new (Key, Value) pairs
89+
/// to the map. Once frozen, map operations and blot-erase operations can be
90+
/// performed. Additionally it provides a getRange() operation that can be
91+
/// used to iterate over all (Key, [Value]) pairs ignoring erased keys.
92+
SmallFrozenMultiMap<SILBasicBlock *, Operand *, 8> blocksWithNonConsumingUses;
8493

8594
/// The worklist that we use when performing our block dataflow.
8695
SmallVector<SILBasicBlock *, 32> worklist;
@@ -168,58 +177,39 @@ struct State {
168177

169178
void State::initializeAllNonConsumingUses(
170179
ArrayRef<Operand *> nonConsumingUsers) {
180+
SWIFT_DEFER {
181+
// Once we have finished initializing blocksWithNonConsumingUses, we need to
182+
// freeze it. By using a defer here, we can make sure we don't forget to do
183+
// this below.
184+
blocksWithNonConsumingUses.setFrozen();
185+
};
186+
171187
for (Operand *use : nonConsumingUsers) {
172188
auto *userBlock = use->getUser()->getParent();
173189

174-
// First check if this non consuming user is in our definition block. If so,
175-
// validate that it is strictly after our defining instruction. If so, just
176-
// emit an error now and exit.
177-
if (userBlock == getBeginBlock()) {
178-
if (std::find_if(beginInst->getIterator(), userBlock->end(),
179-
[&use](const SILInstruction &inst) -> bool {
180-
return use->getUser() == &inst;
181-
}) == userBlock->end()) {
182-
183-
errorBuilder.handleUseAfterFree([&] {
184-
llvm::errs() << "Found use before def?!\n"
185-
<< "Value: ";
186-
if (auto v = value) {
187-
llvm::errs() << *v;
188-
} else {
189-
llvm::errs() << "N/A. \n";
190-
}
191-
});
192-
return;
193-
}
194-
}
195-
196-
// First try to associate User with User->getParent().
197-
auto result =
198-
blocksWithNonConsumingUses.insert(std::make_pair(userBlock, use));
199-
200-
// If the insertion succeeds, then we know that there is no more work to
201-
// be done, so process the next use.
202-
if (result.second)
203-
continue;
204-
205-
// If the insertion fails, then we have at least two non-consuming uses in
206-
// the same block. Since we are performing a liveness type of dataflow, we
207-
// only need the last non-consuming use to show that all consuming uses post
208-
// dominate both. Since we do not need to deal with cond_br that have
209-
// non-trivial values, we now can check if Use is after Result.first->second
210-
// in the use list. If Use is not later, then we wish to keep the already
211-
// mapped value, not use, so continue.
212-
if (std::find_if(result.first->second->getUser()->getIterator(),
213-
userBlock->end(),
190+
// Make sure that this non consuming user is in either not in our definition
191+
// block or is strictly after our defining instruction. If so, stash the use
192+
// and continue.
193+
if (userBlock != getBeginBlock() ||
194+
std::find_if(beginInst->getIterator(), userBlock->end(),
214195
[&use](const SILInstruction &inst) -> bool {
215196
return use->getUser() == &inst;
216-
}) == userBlock->end()) {
197+
}) != userBlock->end()) {
198+
blocksWithNonConsumingUses.insert(userBlock, use);
217199
continue;
218200
}
219201

220-
// At this point, we know that user is later in the Block than
221-
// result.first->second, so store user instead.
222-
result.first->second = use;
202+
// Otherwise, we emit an error since we found a use before our def. We do
203+
// not bail early here since we want to gather up /all/ that we find.
204+
errorBuilder.handleUseAfterFree([&] {
205+
llvm::errs() << "Found use before def?!\n"
206+
<< "Value: ";
207+
if (auto v = value) {
208+
llvm::errs() << *v;
209+
} else {
210+
llvm::errs() << "N/A. \n";
211+
}
212+
});
223213
}
224214
}
225215

@@ -284,20 +274,27 @@ void State::checkForSameBlockUseAfterFree(Operand *consumingUse,
284274
// If we do not have any consuming uses in the same block as our
285275
// consuming user, then we can not have a same block use-after-free.
286276
auto iter = blocksWithNonConsumingUses.find(userBlock);
287-
if (iter == blocksWithNonConsumingUses.end())
277+
if (!iter.hasValue()) {
288278
return;
279+
}
289280

290-
Operand *nonConsumingUse = iter->second;
281+
auto nonConsumingUsesInBlock = *iter;
291282

292-
// Make sure that the non-consuming use is before the consuming
283+
// Make sure that all of our non-consuming uses are before the consuming
293284
// use. Otherwise, we have a use after free. Since we do not allow for cond_br
294285
// anymore, we know that both of our users are non-cond branch users and thus
295286
// must be instructions in the given block. Make sure that the non consuming
296287
// user is strictly before the consuming user.
297-
if (std::find_if(consumingUse->getUser()->getIterator(), userBlock->end(),
298-
[&nonConsumingUse](const SILInstruction &i) -> bool {
299-
return nonConsumingUse->getUser() == &i;
300-
}) != userBlock->end()) {
288+
for (auto *nonConsumingUse : nonConsumingUsesInBlock) {
289+
if (std::find_if(consumingUse->getUser()->getIterator(), userBlock->end(),
290+
[&nonConsumingUse](const SILInstruction &i) -> bool {
291+
return nonConsumingUse->getUser() == &i;
292+
}) == userBlock->end()) {
293+
continue;
294+
}
295+
296+
// NOTE: We do not exit here since we want to catch /all/ errors that we can
297+
// find.
301298
errorBuilder.handleUseAfterFree([&] {
302299
llvm::errs() << "Found use after free?!\n"
303300
<< "Value: ";
@@ -307,15 +304,14 @@ void State::checkForSameBlockUseAfterFree(Operand *consumingUse,
307304
llvm::errs() << "N/A. \n";
308305
}
309306
llvm::errs() << "Consuming User: " << *consumingUse->getUser()
310-
<< "Non Consuming User: " << *iter->second->getUser()
307+
<< "Non Consuming User: " << *nonConsumingUse->getUser()
311308
<< "Block: bb" << userBlock->getDebugID() << "\n\n";
312309
});
313-
return;
314310
}
315311

316-
// Erase the use since we know that it is properly joint post-dominated.
317-
blocksWithNonConsumingUses.erase(iter);
318-
return;
312+
// Erase the use since we know that it is either properly joint post-dominated
313+
// or it was not and we emitted use after free errors.
314+
blocksWithNonConsumingUses.erase(userBlock);
319315
}
320316

321317
void State::checkPredsForDoubleConsume(Operand *consumingUse,
@@ -485,35 +481,30 @@ void State::checkDataflowEndState(DeadEndBlocks &deBlocks) {
485481
// wants us to tell it where to insert compensating destroys.
486482
}
487483

488-
// Make sure that we do not have any lifetime ending uses left to visit that
489-
// are not transitively unreachable blocks.... so return early.
490-
if (blocksWithNonConsumingUses.empty()) {
491-
return;
492-
}
493-
494484
// If we do have remaining blocks, then these non lifetime ending uses must be
495485
// outside of our "alive" blocks implying a use-after free.
496-
for (auto &pair : blocksWithNonConsumingUses) {
497-
if (deBlocks.isDeadEnd(pair.first)) {
486+
for (auto pair : blocksWithNonConsumingUses.getRange()) {
487+
auto *block = pair.first;
488+
if (deBlocks.isDeadEnd(block)) {
498489
continue;
499490
}
500491

501492
errorBuilder.handleUseAfterFree([&] {
502-
llvm::errs() << "Found use after free due to unvisited non lifetime "
503-
"ending uses?!\n"
493+
llvm::errs() << "Found outside of lifetime uses!\n"
504494
<< "Value: ";
505495
if (auto v = value) {
506496
llvm::errs() << *v;
507497
} else {
508498
llvm::errs() << "N/A. \n";
509499
}
510500

511-
llvm::errs() << "Remaining Users:\n";
512-
for (auto &pair : blocksWithNonConsumingUses) {
513-
llvm::errs() << "User:" << *pair.second->getUser() << "Block: bb"
514-
<< pair.first->getDebugID() << "\n";
501+
auto uses = pair.second;
502+
llvm::errs() << "User List:\n";
503+
for (auto *op : uses) {
504+
llvm::errs() << "User:" << *op->getUser() << "Block: bb"
505+
<< block->getDebugID() << "\n";
506+
llvm::errs() << "\n";
515507
}
516-
llvm::errs() << "\n";
517508
});
518509
}
519510
}

0 commit comments

Comments
 (0)