-
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
std::ptr::swap_nonoverlapping
is not always untyped
#134713
Comments
This is the classic problem that
So copying as |
I don't see any part of the |
Redo the swap code for better tail & padding handling A couple of parts here ## Fixes rust-lang#134713 Actually using the type passed to `ptr::swap_nonoverlapping` for anything other than its size + align turns out to not work, so this redo goes back to *always* swapping via not-`!noundef` integers. (Except in `const`, which keeps doing the same thing as before to preserve `@RalfJung's` fix from rust-lang#134689) ## Fixes rust-lang#134946 I'd previously moved the swapping to use auto-vectorization, but someone pointed out on Discord that the tail loop handling from that left a whole bunch of byte-by-byte swapping around. This PR goes back to a manual chunk, with at most logarithmic more instructions for the tail. (There are other ways that could potentially handle the tail even better, but this seems to be pretty good, since it's how LLVM ends up lowering operations on types like `i56`.) ## Polymorphization Since it's silly to have separate copies of swapping -- especially *untyped* swapping! -- for `u32`, `i32`, `f32`, `[u16; 2]`, etc, this sends everything to byte versions, but still mono'd by alignment. That should make it more ok that the code is somewhat more complex, since we only get 7 monomorphizations of the complicated bit. (One day we'll be able to remove some of the hacks by being able to just call `foo::<{align_of::<T>()}>`, but since alignments are only powers of two, the manual dispatch out isn't a big deal.)
Uninitialized and padding bytes are different. I believe this code is UB: reading from padding bytes. It would probably be good to document this behavior on the copy functions, though. "Copies bytes" is potentially misleading |
The code snippet above never reads padding bytes; it should read fully initialized arrays of usize. Copying padding bytes around has the effect of making the target uninitialized (https://github.com/rust-lang/unsafe-code-guidelines/blob/master/reference/src/glossary.md#padding), but swap_nonoverlapping is documented specifically not to do that. |
Cc @rust-lang/opsem MaybeUninit ABI guarantee striking again... |
Code
I tried this code:
godbolt link
I expected to see this happen: The two
assert_eq!
s should succeed;b1
andb2
should be completely swapped, sincestd::ptr::swap_nonoverlapping::<T>
claims to swap bytes, notT
s.Instead, this happened: They are not entirely swapped.
swap_nonoverlapping
appears to be doing a typed swap atFoo
, skipping/zeroing/not-correctly-swapping the 7 padding bytes at the end ofFoo
.I think this only happens with types where
size_of::<T>() > size_of::<usize>()
from looking at the implementation, but I'm not sure.In debug mode:
Rust 1.61-1.70 appear to consistently give
[3000, 160]
.Rust 1.71-nightly appear to consistently give
[3000, 1952]
.4000 is
0x0fa0
160 is
0x00a0
1952 is
0x07a0
2000 is
0x07d0
so it looks like Rust 1.61-1.70 are zeroing the padding bytes, and Rust 1.71-nightly are ignoring them.
Version it worked on
It most recently worked on: Rust 1.60
Version with regression
rustc --version --verbose
:@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged
The text was updated successfully, but these errors were encountered: