-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
const-eval: load of partially initialized scalar produces entirely uninitialized result #69488
Comments
I adjusted the title: this is not about a "move" happening during const-eval, this is specifically about taking the result of const-eval and using it elsewhere in the program. If you continue to use |
Cc @rust-lang/wg-const-eval and specifically @oli-obk: I think what is happening here is (again) "const-eval result normalization" going wrong. Specifically, here we decide to treat |
When making pub const fn check_bag<T: Copy>(bag: &BagOfBits<T>) {
let val = unsafe { *(bag as *const _ as *const u8) };
assert!(val == 1);
}
const _: () = check_bag(&make_1u8_bag::<usize>()); Compiling playground v0.0.1 (/playground) error: any use of this value will cause an error --> src/main.rs:21:13 | 21 | assert!(val == 1); | ^^^^^^^^ | | | attempted to read undefined bytes | inside call to `check_bag::` at src/main.rs:35:19 ... 35 | const _: () = check_bag(&make_1u8_bag::()); | -------------------------------------------------- | = note: `#[deny(const_err)]` on by default |
Hm... that is interesting. The same assumption (" And indeed what I wrote above is not correct, that code contains another layer of a hack to work around mistreatment of uninitialized scalars from earlier. Layers upon layers.^^ So, the concrete bug is likely caused by |
Yea we have the same optimization for scalar immediates during const eval. Basically this means we can't trust just the Alternatively we can make the |
Oh interesting, not having to touch the logic deciding what can be an immediate, but just adding a check for full initialization should suffice. I don't see how this would allow a simplification though. |
Wow... so then we could properly represent initializedness in intermediates. That is pretty awesome! I'd live this. But it's also non-trivial and might impact performance.
Yeah, something like that would be the simpler fix. However,
Potentially, |
More precisely, pretty much every single call of |
I just thought of an alternative to adding a full initialization check that makes So the alternative would be to adjust this check (and possibly others) to test if the type we are working at is a "bag of bytes" or not. If it is an integer or a newtyped integer, it should be treated as a bag of bytes. If it involves a union, it should not. That would solve the issue here while still permitting an |
I am now starting to wonder if we should really fix this bug in Miri... A function like this pub union BagOfBits<T: Copy> {
uninit: (),
_storage: T,
}
pub fn foo(x: BagOfBits<usize>) {} gets translated to LLVM IR like
Note the argument type: This might change on the LLVM side -- from what I know, poison being all-or-nothing is one of the main contentious points for why the "killing undef and spreading poison" proposal has not been adopted yet -- but we certainly must tread carefully here. |
Are the LLVM semantics those which we want, considering that |
Fair.
I think that is an open question. The LLVM way to say "bag of 64 bits that can individually be poison" would be |
A great way to represent the desired semantics of |
1: Remove miri hack r=taiki-e a=taiki-e Use currently use a hack to avoid rust-lang/rust#69488 and to make sure that Miri errors for atomic load/store of integers containing uninitialized bytes (which is probably not a problem and uncharted territory at best [1] [2] [3], and can be detected by `-Zmiri-check-number-validity` [4]), do not mask Miri errors for the use of uninitialized bytes (which is definitely a problem). https://github.com/taiki-e/atomic-memcpy/blob/3507fef17534e4825b2b303d04702b4678e29dd0/src/lib.rs#L426-L450 [1]: crossbeam-rs/crossbeam#315 [2]: rust-lang/unsafe-code-guidelines#158 [3]: rust-lang/unsafe-code-guidelines#71 [4]: rust-lang/miri#1904 However, this actually causes another "unsupported operation" Miri error. ``` error: unsupported operation: unable to turn pointer into raw bytes --> /Users/taiki/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:701:9 | 701 | copy_nonoverlapping(src, tmp.as_mut_ptr(), 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes | = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support ``` Co-authored-by: Taiki Endo <te316e89@gmail.com>
…oli-obk For MIRI, cfg out the swap vectorization logic from 94212 Because of rust-lang#69488 the swap logic from rust-lang#94212 doesn't currently work in MIRI. Copying in smaller pieces is probably much worse for its performance anyway, so it'd probably rather just use the simple path regardless. Part of rust-lang#94371, though another PR will be needed for the CTFE aspect. r? `@oli-obk` cc `@RalfJung`
As mentioned before, a strategy to fix this problem would be to adjust Miri such that it no longer attempts to put union values into its In #94411 I started working on this, but recursively traversing the type is actually quite annoying, and also usually something that Miri avoids doing. It would be much better if this information was encoded in the |
…i-obk For MIRI, cfg out the swap vectorization logic from 94212 Because of rust-lang#69488 the swap logic from rust-lang#94212 doesn't currently work in MIRI. Copying in smaller pieces is probably much worse for its performance anyway, so it'd probably rather just use the simple path regardless. Part of rust-lang#94371, though another PR will be needed for the CTFE aspect. r? `@oli-obk` cc `@RalfJung`
The constant evaluation of a move of a union differs from the semantics of non-constant evaluation. The union instance is not regarded as a bag-of-bits as aluded to in the reference and also this
unsafe-code-guidelines
issue. This occurs only if the unions has a single scalar scalar field and otherwise ZSTs, which is pretty specific but occurs forMaybeUninit
if the type parameter is a scalar. If the field is of other types such as arrays ([scalar; N]
) or if the other field is not a ZST then the semantics seem fine.I tried this code:
Playground
I expected to see this happen: All assertions succeed.
Instead, this happened: The check where a union of type
BagOfBits<usize>
is moved during constant evaluation, panics. When putting everything through Miri it panics with the message of trying to read an uninitialized byte. The check where the union of typeBagOfBits<[usize; 1]>
is used instead runs fine in both cases.As far as I am aware, when constant evaluation reads scalars then it propagates uninitialized bytes. If that were to happen then it would explain the observed behaviour but it seems weird why the union copy would use a scalar read.
cc @RalfJung
The text was updated successfully, but these errors were encountered: