-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
Don't perform unsigned comparisons for signed integers #124122
Conversation
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
@@ -399,7 +401,10 @@ impl<'tcx> SimplifyMatch<'tcx> for SimplifyToExp { | |||
if ((f_c.const_.ty().is_signed() || discr_ty.is_signed()) |
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.
if ((f_c.const_.ty().is_signed() || discr_ty.is_signed()) | |
if ((f_c.const_.ty().is_signed() && discr_ty.is_signed()) |
Otherwise you're still potentially treating something as signed that is unsigned, or vice versa.
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.
Honestly I think it's best to move this entire thing into a helper, not just int_equal
.
And also, why is int_equal
so different from what happens for unsigned? They should be entirely identical except for using int
vs uint
functions.
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 I see, it's about converting the u128
from the SwitchInt
to a ScalarInt
. But that should be uniform -- do it once before the comparison. The input is not sign extended so it's always SwitchInt::try_from_uint
.
So already further up, you should convert first_val
and second_val
to ScalarInt
.
In fact we should probably change SwitchInt to store a ScalarInt
rather than a raw u128
, or at least make it easy to get the SwitchTarget values as ScalarInt
.
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.
Hold on a sec... isn't what you actually want to do here some sort of cast? I don't know the right direction, but -- basically you want to cast the discriminant value to the type of the constant (or the other way around), and then check they are equal, right?
The interpreter has the int_to_int_or_float
method for that. But really you just care about this match arm. That should probably be turned into a helper somewhere it can be used by mir-opts.
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.
Otherwise you're still potentially treating something as signed that is unsigned, or vice versa.
Everything looks fine here. Any signed integer will be converted to a signed comparison.
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 fact we should probably change SwitchInt to store a
ScalarInt
rather than a rawu128
, or at least make it easy to get the SwitchTarget values asScalarInt
.
I have seen your new 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.
The interpreter has the
int_to_int_or_float
method for that. But really you just care about this match arm. That should probably be turned into a helper somewhere it can be used by mir-opts.
Perhaps this could be a separate PR?
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.
Everything looks fine here. Any signed integer will be converted to a signed comparison.
You're treating both values as signed if either of them is signed. That means you can be treating unsigned values as signed, which is wrong.
Perhaps this could be a separate PR?
Perhaps, but I don't understand you current PR, so it may also be a way to turn this code into something that makes sense to more than one person. ;)
Feel free to pick a different reviewer, but I can't make sense of what this code is trying to achieve. The comments don't explain the high-level picture (what are we even trying to achieve with this complicated series of checks) and the low-level details are clearly still mixing up signedness.
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.
Everything looks fine here. Any signed integer will be converted to a signed comparison.
You're treating both values as signed if either of them is signed. That means you can be treating unsigned values as signed, which is wrong.
In the known test cases, it is correct. But I must carefully check the edge cases here. For safety reasons, I will later consider only signed-to-signed conversions in this PR. :)
cc @rust-lang/wg-mir-opt Perhaps someone else will directly point out this specific problem?
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 the known test cases, it is correct.
That's a very low bar. The comments should give convincing reasons why it is correct for all possible MIR ever.
Co-authored-by: Ralf Jung <[email protected]>
@@ -368,6 +368,8 @@ impl<'tcx> SimplifyMatch<'tcx> for SimplifyToExp { | |||
return None; | |||
} |
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.
Why does it make sense to compare the lengths of the basic blocks...?!?
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.
Because we are currently only merging simple BBs, we have not considered BBs that could potentially be merged even though they have different numbers of instructions.
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's a "simple BB"? If both BBs have 24 instructions, how is that okay but one having 20 and the other 24 is not? The same number of instructions tells you absolutely nothing about what the BBs are doing...
There needs to be a comment here, at first sight this seems entirely nonsensical.
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.
Sorry for the unclear expression. I mean the merging of basic blocks that only consider simple scenarios.
This is just an early bail-out. We assume that BBs with different the lengths of BBs cannot be merged.
Sorry, I don't have time to figure out how this optimization is supposed to behave, which is clearly required to be able to review this. This code is not clear enough to do review based on just a local diff, it needs someone to holistically understand the entire thing. And the first version of this optimization was unsound already, so maybe we should start by turning this into something obviously correct (e.g. full equality of the relevant values, no casting between different bitwdiths) and then slowly and systematically extend to more cases while adding comments that explain why this is actually correct. The invariants involved here are non-trivial and not reflected in the type system (because reflecting program equivalence in the type system is not possible in Rust^^). MIR optimizations are among the most subtle code we have in rustc (in the sense that even if the code type-checks and does not panic, it is still easy to introduce very subtle bugs), they need to be treated with the utmost care. r? mir-opt |
It's very interesting that you mentioned not understanding these codes, yet you pointed out the issues here from the code review. It's like one of my classmates scored full marks but told me that he actually didn't understand the exam questions. :3 |
I understand enough to find problems, not enough to be certain in its correctness. :) |
Folks: if T-compiler can be of any support in reviewing this, please don't hesitate to nominate for discussion. Thanks for working on this! |
Disable MatchBranchSimplification Due to the miscompilation mentioned in rust-lang#124150, We need to disable MatchBranchSimplification temporarily. To fully resolve this issue, my plan is: 1. Disable MatchBranchSimplification (this PR). 2. Remove all potentially unclear transforms in rust-lang#124122. 3. Gradually add back the removed transforms (possibly multiple PRs). r? `@Nilstrieb` or `@oli-obk`
…fJung Disable SimplifyToExp in MatchBranchSimplification Due to the miscompilation mentioned in rust-lang#124150, We need to disable MatchBranchSimplification temporarily. To fully resolve this issue, my plan is: 1. Disable SimplifyToExp in MatchBranchSimplification (this PR). 2. Remove all potentially unclear transforms in rust-lang#124122. 3. Gradually add back the removed transforms (possibly multiple PRs). r? `@Nilstrieb` or `@oli-obk`
I'll probably update this PR in about two weeks. |
I found a simpler and more straightforward solution. In fact, I just need to check if the cast result using |
Fixes (partial) #124150. (There are still some potential miscompilation with unsigned and signed integer transformation.)
Fixes a mis-compilation mentioned in #120614 (comment). We must handle signed and unsigned comparisons separately. We cannot cast
-1i8
to255i16
.This PR breaks the test case for unsigned and signed transformation, but I think this could go into a separate PR.
r? RalfJung or mir-opt