Skip to content

[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

Merged
merged 7 commits into from
Jun 6, 2023

Conversation

kavon
Copy link
Member

@kavon kavon commented Apr 26, 2023

Cherry-pick of

• Description: Adds a missing piece of SE-390 where exits from a method that uses discard self that did not consume self 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 consume self on all possible exits of the function (see test/SILOptimizer/discard_checking.swift for examples). The set of programs using discard 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

@kavon kavon requested a review from a team as a code owner April 26, 2023 22:13
@kavon
Copy link
Member Author

kavon commented Apr 26, 2023

@swift-ci please test

@kavon
Copy link
Member Author

kavon commented Apr 27, 2023

NOTE: to land this correctly you'll have to revert a commit in #65425 if that one is merged first.

@AnthonyLatsis AnthonyLatsis added 🍒 release cherry pick Flag: Release branch cherry picks swift 5.9 labels May 3, 2023
@kavon kavon force-pushed the 5.9-drop-deinit branch from c8b016d to b8c7540 Compare June 5, 2023 05:17
@kavon kavon changed the title [5.9🍒] Add the drop_deinit instruction [5.9🍒] Add the drop_deinit instruction & consume-on-all-paths checking for discard Jun 5, 2023
@kavon kavon requested a review from jckarter June 5, 2023 05:59
@kavon
Copy link
Member Author

kavon commented Jun 5, 2023

@swift-ci please test

Copy link
Contributor

@gottesmm gottesmm left a 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()),
Copy link
Contributor

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.

Copy link
Member Author

@kavon kavon Jun 5, 2023

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.

Copy link
Contributor

@gottesmm gottesmm Jun 5, 2023

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.

Copy link
Member Author

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.

Copy link
Contributor

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()};
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

@gottesmm gottesmm Jun 5, 2023

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.

Copy link
Member Author

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.

Copy link
Contributor

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();
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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,
Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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.

Copy link
Contributor

@jckarter jckarter left a 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.

Copy link
Contributor

@gottesmm gottesmm left a 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.

@kavon
Copy link
Member Author

kavon commented Jun 5, 2023

@swift-ci please test

@kavon kavon force-pushed the 5.9-drop-deinit branch from f44a90c to 070f348 Compare June 5, 2023 22:35
@kavon
Copy link
Member Author

kavon commented Jun 5, 2023

@swift-ci smoke test linux platform

@kavon
Copy link
Member Author

kavon commented Jun 6, 2023

@swift-ci please test

eeckstein and others added 7 commits June 5, 2023 19:39
his instruction is a marker for a following destroy instruction to suppress the call of the move-only type's deinitializer.
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)
@kavon kavon force-pushed the 5.9-drop-deinit branch from 070f348 to a515cbd Compare June 6, 2023 02:39
@kavon
Copy link
Member Author

kavon commented Jun 6, 2023

@swift-ci please test

@kavon
Copy link
Member Author

kavon commented Jun 6, 2023

@swift-ci clean test macOS platform

@kavon kavon merged commit a515cbd into swiftlang:release/5.9 Jun 6, 2023
@kavon kavon deleted the 5.9-drop-deinit branch June 6, 2023 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🍒 release cherry pick Flag: Release branch cherry picks swift 5.9
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants