-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[5.9🍒] Add the drop_deinit instruction & consume-on-all-paths checking for discard
#65449
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
Changes from all commits
c16f5a0
df245a6
6200129
98352d7
a710276
6dfc9c5
a515cbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -691,6 +691,9 @@ struct UseState { | |
/// [assign] that are reinits that we will convert to inits and true reinits. | ||
llvm::SmallMapVector<SILInstruction *, TypeTreeLeafTypeRange, 4> reinitInsts; | ||
|
||
/// The set of drop_deinits of this mark_must_check | ||
SmallSetVector<SILInstruction *, 2> dropDeinitInsts; | ||
|
||
/// A "inout terminator use" is an implicit liveness use of the entire value | ||
/// placed on a terminator. We use this both so we add liveness for the | ||
/// terminator user and so that we can use the set to quickly identify later | ||
|
@@ -712,6 +715,31 @@ struct UseState { | |
return inoutTermUsers.count(inst); | ||
} | ||
|
||
/// Returns true if the given instruction is within the same block as a reinit | ||
/// and precedes a reinit instruction in that block. | ||
bool precedesReinitInSameBlock(SILInstruction *inst) const { | ||
SILBasicBlock *block = inst->getParent(); | ||
SmallSetVector<SILInstruction *, 8> sameBlockReinits; | ||
|
||
// First, search for all reinits that are within the same block. | ||
for (auto &reinit : reinitInsts) { | ||
if (reinit.first->getParent() != block) | ||
continue; | ||
sameBlockReinits.insert(reinit.first); | ||
} | ||
|
||
if (sameBlockReinits.empty()) | ||
return false; | ||
|
||
// Walk down from the given instruction to see if we encounter a reinit. | ||
for (auto ii = std::next(inst->getIterator()); ii != block->end(); ++ii) { | ||
if (sameBlockReinits.contains(&*ii)) | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
void clear() { | ||
address = nullptr; | ||
destroys.clear(); | ||
|
@@ -721,6 +749,7 @@ struct UseState { | |
takeInsts.clear(); | ||
initInsts.clear(); | ||
reinitInsts.clear(); | ||
dropDeinitInsts.clear(); | ||
inoutTermUsers.clear(); | ||
debugValue = nullptr; | ||
} | ||
|
@@ -755,6 +784,10 @@ struct UseState { | |
for (auto pair : reinitInsts) { | ||
llvm::dbgs() << *pair.first; | ||
} | ||
llvm::dbgs() << "DropDeinits:\n"; | ||
for (auto *inst : dropDeinitInsts) { | ||
llvm::dbgs() << *inst; | ||
} | ||
llvm::dbgs() << "InOut Term Users:\n"; | ||
for (auto *inst : inoutTermUsers) { | ||
llvm::dbgs() << *inst; | ||
|
@@ -1737,6 +1770,12 @@ bool GatherUsesVisitor::visitUse(Operand *op) { | |
LLVM_DEBUG(llvm::dbgs() << "Running copy propagation!\n"); | ||
moveChecker.changed |= moveChecker.canonicalizer.canonicalize(); | ||
|
||
// Export the drop_deinit's discovered by the ObjectChecker into the | ||
// AddressChecker to preserve it for later use. We need to do this since | ||
// the ObjectChecker's state gets cleared after running on this LoadInst. | ||
for (auto *dropDeinit : moveChecker.canonicalizer.getDropDeinitUses()) | ||
kavon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
moveChecker.addressUseState.dropDeinitInsts.insert(dropDeinit); | ||
|
||
// If we are asked to perform no_consume_or_assign checking or | ||
// assignable_but_not_consumable checking, if we found any consumes of our | ||
// load, then we need to emit an error. | ||
|
@@ -2458,10 +2497,41 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses( | |
FieldSensitiveMultiDefPrunedLiveRange &liveness, | ||
const FieldSensitivePrunedLivenessBoundary &boundary) { | ||
LLVM_DEBUG(llvm::dbgs() << "MoveOnlyAddressChecker Rewrite Uses!\n"); | ||
// First remove all destroy_addr that have not been claimed. | ||
|
||
/// Whether the marked value appeared in a discard statement. | ||
const bool isDiscardingContext = !addressUseState.dropDeinitInsts.empty(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC there really isn't a point in doing a const bool. We (if you look through the code base) generally do not use const with value types like bool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whether it's a discarding context should not change, so expressing that via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, in our code base we do not put constants on types like this. It was definitely against LLVM style for a long time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That being said, given that we need to get stuff in, I am fine letting this through for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
|
||
// Process destroys | ||
for (auto destroyPair : addressUseState.destroys) { | ||
if (!consumes.claimConsume(destroyPair.first, destroyPair.second)) { | ||
/// Is this destroy instruction a final consuming use? | ||
bool isFinalConsume = | ||
consumes.claimConsume(destroyPair.first, destroyPair.second); | ||
|
||
// Remove destroys that are not the final consuming use. | ||
if (!isFinalConsume) { | ||
destroyPair.first->eraseFromParent(); | ||
continue; | ||
} | ||
|
||
// Otherwise, if we're in a discarding context, flag this final destroy_addr | ||
// as a point where we're missing an explicit `consume self`. The reasoning | ||
// here is that if a destroy of self is the final consuming use, | ||
// then these are the points where we implicitly destroy self to clean-up | ||
// that self var before exiting the scope. An explicit 'consume self' | ||
// that is thrown away is a consume of this mark_must_check'd var and not a | ||
// destroy of it, according to the use classifier. | ||
if (isDiscardingContext) { | ||
|
||
// Since the boundary computations treat a newly-added destroy prior to | ||
// a reinit within that same block as a "final consuming use", exclude | ||
// such destroys-before-reinit. We are only interested in the final | ||
// destroy of a var, not intermediate destroys of the var. | ||
if (addressUseState.precedesReinitInSameBlock(destroyPair.first)) | ||
continue; | ||
|
||
auto *dropDeinit = addressUseState.dropDeinitInsts.front(); | ||
diagnosticEmitter.emitMissingConsumeInDiscardingContext(destroyPair.first, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another example of a place where it seems like you didn't git-clang-format. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What in particular about the way I've formatted this is bad? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am pretty sure that git-clang-format would put the arguments on the next line. We should all just be using git-clang-format. |
||
dropDeinit); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you use git-clang-format here? I am pretty sure this isn't formatted correctly.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What in particular about the formatting is bad? It's even identical to
visitMoveValueInst
just above, so I don't see a problem here.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you always please use git-clang-format. That way we all use consistent formatting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like everyone uses git-clang-format, since Erik wrote this bit of code that I'm cherry-picking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I am going to let this go for now since you are cherry-picking. But I think as a team/project we need to have a larger conversation around this. Generally it is considered to be a good citizen thing to do: https://forums.swift.org/t/using-git-clang-format-in-the-swift-compiler-code-base/4996.