-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Do not remove trivial SwitchInt
in analysis MIR
#139042
base: master
Are you sure you want to change the base?
Do not remove trivial SwitchInt
in analysis MIR
#139042
Conversation
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt The Miri subtree was changed cc @rust-lang/miri |
_ => return false, | ||
// Removing a `SwitchInt` terminator may remove reads that result in UB, | ||
// so we must not apply this optimization when mir-opt-level = 0. | ||
if self.mir_opt_level == 0 { |
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 could perhaps optimize trivial operands... Like plain locals. Maybe consts?
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.
Well I guess even plain locals could have UB if they're uninitialized. But Rust would never produce that 🤔
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.
SimplifyCfg runs before borrowck, so the following compiles:
fn main() {
let bad_ref: &i32;
let &(0 | _) = bad_ref;
}
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.
oh wonderful, so this is a load bearing optimization then
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.
Ah, not just a bug for Miri then but a bigger problem. Certainly we shouldn't optimize before borrowck.
So instead of checking the mir_opt_level, this should be told which "phase" it is in (or it can maybe even get that info from the MIR body, not sure), and then not do this transformation in the "analysis" phase.
This comment has been minimized.
This comment has been minimized.
well i do wonder if anything relies on this today @bors try |
…chint, r=<try> Do not remove trivial `SwitchInt` with mir-opt-level=0 When mir-opt-level=0, do not optimize out `SwitchInt` terminators that all have the same terminator since it may remove a read which affects miri's ability to detect UB on that operand. cc `@RalfJung` Fixes rust-lang/miri#4237 This affects some tests... I guess I could mark them as `mir-opt-level=1`? Not sure.
@rustbot author |
@@ -0,0 +1,8 @@ | |||
use std::mem::MaybeUninit; |
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.
Please add a top-level doc comment explaining what this is testing and referencing the issue.
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 comment is still unresolved.
☀️ Try build successful - checks-actions |
@craterbot check |
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
Pull request doesn't resolve #139042 (comment) yet and mir-opt-level=0 is never used by default. Are we getting ahead of ourselves with crater? |
Yeah I think we do. The PR first needs to be changed to never do this Alternatively, instead of checking the opt level, we could have a separate flag. We already have a similar problem with |
🗑️ Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
6bad178
to
01ee75d
Compare
@bors try |
…chint, r=<try> Do not remove trivial `SwitchInt` with mir-opt-level=0 When mir-opt-level=0, do not optimize out `SwitchInt` terminators that all have the same terminator since it may remove a read which affects miri's ability to detect UB on that operand. cc `@RalfJung` Fixes rust-lang/miri#4237 This affects some tests... I guess I could mark them as `mir-opt-level=1`? Not sure.
This comment has been minimized.
This comment has been minimized.
Ah, on vacation 🌴 |
SwitchInt
with mir-opt-level=0SwitchInt
in analysis MIR
e5531b3
to
3ee62a9
Compare
I buy it. @rfcbot fcp merge |
Team member @traviscross has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
@rfcbot reviewed |
@bors rollup=never due to potential perf impacts |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
There should be no perf impacts here, but we can just test that rather than guessing 😄 @bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
…chint, r=<try> Do not remove trivial `SwitchInt` in analysis MIR This PR ensures that we don't prematurely remove trivial `SwitchInt` terminators which affects both the borrow-checking and runtime semantics (i.e. UB) of the code. Previously the `SimplifyCfg` optimization was removing `SwitchInt` terminators when they was "trivial", i.e. when all arms branched to the same basic block, even if that `SwitchInt` terminator had the side-effect of reading an operand which (for example) may not be initialized or may point to an invalid place in memory. This behavior is unlike all other optimizations, which are only applied after "analysis" (i.e. borrow-checking) is finished, and which Miri disables to make sure the compiler doesn't silently remove UB. Fixing this code "breaks" (i.e. unmasks) code that used to borrow-check but no longer does, like: ```rust fn foo() { let x; let (0 | _) = x; } ``` This match expression should perform a read because `_` does not shadow the `0` literal pattern, and the compiler should have to read the match scrutinee to compare it to 0. I've checked that this behavior does not actually manifest in practice via a crater run which came back clean: rust-lang#139042 (comment) As a side-note, it may be tempting to suggest that this is actually a good thing or that we should preserve this behavior. If we wanted to make this work (i.e. trivially optimize out reads from matches that are redundant like `0 | _`), then we should be enabling this behavior *after* fixing this. However, I think it's kinda unprincipled, and for example other variations of the code don't even work today, e.g.: ```rust fn foo() { let x; let (0.. | _) = x; } ```
⌛ Trying commit 3ee62a9 with merge 2f722cba6738caa1d971f6ef4229d7d86891b5af... |
Agreed this is something that should never have worked, and it's just a bug in mir transform. |
☀️ Try build successful - checks-actions |
This comment has been minimized.
This comment has been minimized.
Finished benchmarking commit (2f722cb): comparison URL. Overall result: no relevant changes - no action neededBenchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR may lead to changes in compiler perf. @bors rollup=never Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)Results (primary 0.7%, secondary -8.1%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResults (secondary 2.6%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Binary sizeResults (primary -0.1%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Bootstrap: 779.721s -> 780.808s (0.14%) |
@bors rollup- |
My return from vacation hasn't processed yet but I'm quite interested in this PR so I've changed the assignment manually. |
This PR ensures that we don't prematurely remove trivial
SwitchInt
terminators which affects both the borrow-checking and runtime semantics (i.e. UB) of the code. Previously theSimplifyCfg
optimization was removingSwitchInt
terminators when they was "trivial", i.e. when all arms branched to the same basic block, even if thatSwitchInt
terminator had the side-effect of reading an operand which (for example) may not be initialized or may point to an invalid place in memory.This behavior is unlike all other optimizations, which are only applied after "analysis" (i.e. borrow-checking) is finished, and which Miri disables to make sure the compiler doesn't silently remove UB.
Fixing this code "breaks" (i.e. unmasks) code that used to borrow-check but no longer does, like:
This match expression should perform a read because
_
does not shadow the0
literal pattern, and the compiler should have to read the match scrutinee to compare it to 0. I've checked that this behavior does not actually manifest in practice via a crater run which came back clean: #139042 (comment)As a side-note, it may be tempting to suggest that this is actually a good thing or that we should preserve this behavior. If we wanted to make this work (i.e. trivially optimize out reads from matches that are redundant like
0 | _
), then we should be enabling this behavior after fixing this. However, I think it's kinda unprincipled, and for example other variations of the code don't even work today, e.g.: