-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Do not remove trivial SwitchInt
in analysis MIR
#139042
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
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 |
☀️ 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 |
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. |
I am surprised this PR is adding more complexity instead of removing all uses of SimplifyCfg outside of the optimization pass list. Why did you decide to go for this approach? |
🤷 seems pretty weird not to run the whole |
Also, existing analysis requires that rust/compiler/rustc_mir_transform/src/simplify.rs Lines 11 to 13 in 934880f
rust/compiler/rustc_mir_dataflow/src/framework/cursor.rs Lines 143 to 144 in 934880f
|
Why? If we are running optimizations outside of the optimization passes turning off MIR optimizations doesn't turn off all MIR optimizations. That seems bad to me. Right? |
@rfcbot reviewed |
I'll repeat myself so the status of this PR is clear: I tried implementing a separate, rudimentary I think this PR should land in the state it's in today. |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
I'll look into this more on my own time, but I think I've come around to agree with @compiler-errors. @bors r+ |
…enton Rollup of 7 pull requests Successful merges: - rust-lang#139042 (Do not remove trivial `SwitchInt` in analysis MIR) - rust-lang#139533 (add next_index to Enumerate) - rust-lang#139843 (Setup editor file associations for non-rs extensions) - rust-lang#140000 (skip llvm-config in autodiff check builds, when its unavailable) - rust-lang#140008 (Improve `clean_maybe_renamed_item` function code a bit) - rust-lang#140024 (Remove early exits from JumpThreading.) - rust-lang#140039 (Add option for stable backport poll) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of rust-lang#139042 - compiler-errors:do-not-optimize-switchint, r=saethlin 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; } ```
…r=saethlin 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/rust#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; } ```
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.: