-
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
with mir-opt-level=0
#139042
base: master
Are you sure you want to change the base?
Do not remove trivial SwitchInt
with mir-opt-level=0
#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.
☀️ Try build successful - checks-actions |
@craterbot check |
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
01ee75d
to
6fa280b
Compare
🎉 Experiment
|
Oh good. Looks like nobody relies on this behavior. @RalfJung: I took your suggested approach and generalized the flag into I can confirm that causes the example that @tmiasko shared to fail to compile (tho I'll add a test). I'll write up a better description and then probably FCP this with lang? |
6fa280b
to
1fed5da
Compare
1fed5da
to
e5531b3
Compare
@rustbot label: -T-compiler +T-lang +I-lang-nominated +I-lang-easy-decision Hi T-lang, this needs an FCP because it's technically breakage. See the above PR description for details; this isn't something we should've ever accepted, IMO. |
Could not assign reviewer from: |
Ah, on vacation 🌴 |
//! We must be extremely careful to only apply optimizations that preserve the semantics of the | ||
//! code, since changes here can affect which programs compile in an insta-stable way. |
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.
Removing UB is generally considered "preserving the semantics", since UB programs have "all behaviors". So I think the wording here is potentially confusing.
//! We must be extremely careful to only apply optimizations that preserve the semantics of the | |
//! code, since changes here can affect which programs compile in an insta-stable way. | |
//! We must be extremely careful to only apply optimizations that preserve UB and | |
//! all non-determinism, since changes here can affect which programs compile in | |
//! an insta-stable way. The normal logic that a program with UB can be changed | |
//! to do anything does not apply to pre-"runtime" MIR! |
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 could also use a comment explaining the purpose of the test.
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.: