-
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
Another mir-opt-level=2 miscompilation #67862
Comments
Reduced to: fn e220() -> (i64, i64) {
#[inline(never)]
fn get_displacement() -> [i64; 2] {
[139776, 963904]
}
let res = get_displacement();
match (&res[0], &res[1]) {
(arg0, arg1) => (*arg0, *arg1),
}
}
fn main() {
assert_eq!(e220(), (139776, 963904));
} |
Mir of fn e220::get_displacement() -> [i64; 2] {
let mut _0: [i64; 2];
bb0: {
_0 = [const 139776i64, const 963904i64];
return;
}
}
fn e220() -> (i64, i64) {
let mut _0: (i64, i64);
let _1: [i64; 2];
let mut _2: (&i64, &i64);
let mut _3: &i64;
let _4: usize;
let mut _5: &i64;
let _6: usize;
let mut _9: i64;
let mut _10: i64;
scope 1 {
debug res => _1;
let _7: &i64;
let _8: &i64;
scope 2 {
debug arg0 => _7;
debug arg1 => _8;
}
}
bb0: {
StorageLive(_1);
_1 = const e220::get_displacement() -> bb1;
}
bb1: {
StorageLive(_2);
StorageLive(_3);
StorageLive(_4);
_4 = const 0usize;
_3 = &_1[_4];
StorageLive(_5);
StorageLive(_6);
_6 = const 1usize;
_5 = &_1[_6];
(_2.0: &i64) = move _3;
(_2.1: &i64) = move _5;
StorageDead(_5);
StorageDead(_3);
StorageLive(_7);
_7 = (_2.0: &i64);
StorageLive(_8);
_8 = (_2.1: &i64);
StorageLive(_9);
_9 = (*_7);
StorageLive(_10);
_10 = (*_8);
(_0.0: i64) = move _9;
(_0.1: i64) = move _10;
StorageDead(_10);
StorageDead(_9);
StorageDead(_8);
StorageDead(_7);
StorageDead(_1);
StorageDead(_6);
StorageDead(_4);
StorageDead(_2);
return;
}
} Mir of fn e220::get_displacement() -> [i64; 2] {
let mut _0: [i64; 2];
bb0: {
_0 = [const 139776i64, const 963904i64];
return;
}
}
fn e220() -> (i64, i64) {
let mut _0: (i64, i64);
let _1: [i64; 2];
let mut _2: (&i64, &i64);
let mut _3: &i64;
let _4: usize;
let mut _5: &i64;
let _6: usize;
let mut _9: i64;
let mut _10: i64;
scope 1 {
debug res => _1;
let _7: &i64;
let _8: &i64;
scope 2 {
debug arg0 => _7;
debug arg1 => _8;
}
}
bb0: {
StorageLive(_1);
_1 = const e220::get_displacement() -> bb1;
}
bb1: {
StorageLive(_2);
StorageLive(_3);
_4 = const 0usize;
_3 = &_1[_4];
StorageLive(_5);
StorageLive(_6);
_6 = const 1usize;
_5 = &_1[_6];
(_2.0: &i64) = const Scalar(AllocId(15).0x0) : &i64;
(_2.1: &i64) = const Scalar(AllocId(15).0x8) : &i64;
StorageDead(_5);
StorageDead(_3);
StorageLive(_7);
_7 = (_2.0: &i64);
StorageLive(_8);
_8 = (_2.1: &i64);
StorageLive(_9);
_9 = (*_7);
StorageLive(_10);
_10 = (*_8);
(_0.0: i64) = move _9;
(_0.1: i64) = move _10;
StorageDead(_10);
StorageDead(_9);
StorageDead(_8);
StorageDead(_7);
StorageDead(_1);
StorageDead(_6);
StorageDead(_2);
return;
}
}
|
The are a couple of things going on here: The immediate cause is the fact that we don't try to propagate operands in Normally, this would not be a problem - const-prop explicitly bails out when trying to access an uninitialized local, so failing to evaluate Unfortunately, it's possible for an uninitialized local to be incorrectly marked as initialized. Inside While implementing const-propagation for I see a few ways different of solving this:
cc @RalfJung |
Without having digested all the details yet, my first thought is: this is caused by const-prop trying to shoehorn the Miri engine (built for evaluation of whole programs) into an engine for partial evaluation. The
In terms of the Rust Abstract Machine (which the Miri engine models), I don't know what an "uninitialized local" is -- but a local that's marked The issue is that const-prop is taking the difference between What exactly is it that const-prop needs? |
Zero cost uninitialized locals is the base feature we need. Essentially if we decide we don't need a local, reading its memory should yield an uninit value and its memory should not require any heap allocations. I get the shoehorning, we so are doing this a lot in const prop, mainly to keep the engine modifications minimal. I guess we could add some more machine hooks to get rid of hacks. |
Even with |
Well... it shouldn't be. The
This is not worded correctly. The uninitialized local is marked as live its memory is still uninitialized. But that doesn't change the fact that the analysis is spot on. We take references to locals that may never get a value propagated into. The early bail out in rust/src/librustc_mir/transform/const_prop.rs Line 222 in 87540bd
Yea, so basically whenever we don't fully propagate into a local, but later take a reference to that local, we end up with a const pointer to an uninitialized const allocation. Basically unless the local's memory is valid (as per the validation rules for statics), we should not permit taking references to it. |
Doesn't this also affect normal uses of an un-propagated local? We'll end up copying data from an uninitialized allocation, which will result in the same issue. |
Yes, any local that we don't fully propagate into but that is referred to by a local that we marked as "could be propagated". |
I'd say it's less about validity and more about whether we actually did all the computation for this local. That's not the same thing. Basically, const-prop needs a state representing "unknown". This is distinct from "(known to be) uninitialized". And that's not surprising at all, static analyses and optimizations almost always have such a state. This is a first step towards supporting abstract interpretation in Miri. |
We inverted this by treating any initialized values as "!unknown". The issue here is that taking a reference to a value works even if the value is not fully initialized. We have no concept of fully initialized, and in case the local is of union type, we can't rely on the uninitialized state to signal "maybe unknown". So yea, the validity check won't work here either. @wesleywiser and I talked about using dataflow for const prop, so we'd mark locals only as known when they have been successfully propagated into instead of relying on the initialization state. Until we do this we should probably just turn off propagating reference taking. |
I agree disabling ref propagation makes sense. We have quite a few bugs that I believe would be resolved by that. In addition, I believe there's only 1 or 2 MIR tests that would be affected. Going forward, perhaps we should try to find some way to get more coverage of mir-opt=2? |
The other problem is (as you hinted at) that things, at least in theory, might be "partially known", such as in
where |
A general worry about this is how our const-prop looks not at all like the usual "compiler construction literature const-prop": the usual thing is to do a fixed-point (static) analysis on a domain including all possible values as well as "unknown". That could also handle loops, which I suppose you currently have to carefully avoid. It is certainly possible to implement constant propagation differently, but that means none of the standard intuition about it applies. |
On the last Nightly:
The text was updated successfully, but these errors were encountered: