-
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
Conversation
@swift-ci please test |
NOTE: to land this correctly you'll have to revert a commit in #65425 if that one is merged first. |
discard
@swift-ci please test |
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.
Some small nits. Can you fix then LGTM.
if (!getBuilder().hasOwnership()) { | ||
return recordFoldedValue(Inst, getOpValue(Inst->getOperand())); | ||
} | ||
auto *MVI = getBuilder().createDropDeinit(getOpLocation(Inst->getLoc()), |
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.
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.
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.
// d d . . | ||
// | ||
BasicBlockSet visited(destroyPoint->getFunction()); | ||
std::deque<SILBasicBlock *> bfsWorklist = {destroyPoint->getParent()}; |
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.
We generally do not use std::deque in the code base. Does this really need to be a BFS? Couldn't it be a DFS and you use a BasicBlockWorklist? If you really need a queue, my suggestion is to use two std::vectors and swap them.
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.
Reading this, I am pretty sure that this doesn't need a DFS. Can you use a BasicBlockWorklist here? It should be more efficient and we use it throughout the optimizer in this situation.
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.
I did see uses of std::deque for BFS in the code base. As the comment mentions, I'm looking for the nearest exit of the function so a DFS will not work. I don't see a reason to overcomplicate this queue in an effort to optimize it, when it cannot be a performance issue: this code is for emitting an error diagnostic.
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.
Does it really matter if we emit the closest one in terms of a CFG walk? The notion of closeness in terms of the CFG doesn't necessarily correspond to closeness in the source code (which is what I believe you are trying to approximate, no?). For example consider a situation where one has a split CFG like the followings:
bb0
/ \
v v
bb1 bb2
|
v
bb3
where bb1 is a huge basic block containing 5 pages of code and bb2/bb3 are two small blocks due to an early exit. In this case a BFS would give an apparent location far away from where-ever we are emitting errors from. I have definitely seen cases like this in real world code. If one accepts that CFG closeness correspond to closeness in source code, whether or not one uses a DFS or a BFS becomes immaterial.
In terms of using deque, this is about using standardized patterns throughout the optimizer/code base. In general, we have a set of standardized ways of doing things in the code base. One of them is when iterating over blocks to use BasicBlockWorklist if possible. It isn't just about performance (even though that is nice)... it is also about standardizing our code into standard patterns/utilities.
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.
That's a good point. Neither approach is perfect and some other strategy is probably better. I settled on BFS because in practice it gives better locations, for example, from one of the regression tests:
struct Basics: ~Copyable {
borrowing func thrower() throws {}
consuming func test12(_ c: Color) throws { // with DFS the error ends up here
guard case .red = c else {
discard self
return
}
try thrower() // with BFS the error is nicely located here
print("hi")
_ = consume self
}
This is an important situation, since it's not always obvious to people that a try
is a possible function exit.
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.
I am sure I can come up with a similar test case where one could get the other behavior. That being said, it seems like you feel very strongly about this. I am fine with this.
// 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 comment
The 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 comment
The 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 const
is only beneficial to prevent mistakes.
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.
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Using const
is fine.
continue; | ||
|
||
auto *dropDeinit = addressUseState.dropDeinitInsts.front(); | ||
diagnosticEmitter.emitMissingConsumeInDiscardingContext(destroyPair.first, |
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.
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 comment
The 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 comment
The 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.
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.
I think this is in a good enough state to land.
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.
I am also happy with this.
@swift-ci please test |
@swift-ci smoke test linux platform |
@swift-ci please test |
his instruction is a marker for a following destroy instruction to suppress the call of the move-only type's deinitializer.
…eeded by a drop_deinit rdar://105798769
…ry-picked yet." This reverts commit 22a9c77.
As part of SE-390, you're required to write either: - `consume self` - pass self as a `consuming` parameter to a function - `discard self` before the function ends in a context that contains a `discard self` somewhere. This prevents people from accidentally invoking the deinit due to implicit destruction of `self` before exiting the function. rdar://106099027 (cherry picked from commit 88d35a0)
@swift-ci please test |
@swift-ci clean test macOS platform |
Cherry-pick of
drop_deinit
instruction #65060self
indiscard
-ing contexts. #66190• Description: Adds a missing piece of SE-390 where exits from a method that uses
discard self
that did not consumeself
explicitly are now flagged as errors. The purpose is to prevent mistakenly invoking the deinit in such contexts.• Risk: Medium. Will introduce a source break in functions that are using
discard self
and do not consumeself
on all possible exits of the function (seetest/SILOptimizer/discard_checking.swift
for examples). The set of programs usingdiscard
is probably quite small currently and we'd want to introduce this change early, though the error could be downgraded into a warning for 5.9 upon request.• Original PR: #66190 + dependency #65060
• Reviewed By: pending
• Testing: regression tests included
• Resolves: rdar://106099027