-
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
repr(packed) allows invalid unaligned loads #27060
Comments
I... honestly thought that was expected behaviour. What can possibly be done about this is general, other than making references into packed fields a different type (or forbidding them)? |
This is UB, so it's clearly not expected... but yes. it's more of a design flaw than an implementation issue. Note that it's possible to make code like this misbehave even without SIMD, although it's a bit trickier; LLVM performs optimizations based on the alignment of loads. |
Yeah, I only used SIMD because it was the simplest way to demonstrate the problem on x86. I believe platforms like ARM are generally stricter about load alignments, so even, say, |
Is there a way to tell LLVM that the value could be unaligned, and so LLVM should emit code that tries to read it in a safe way for the given architecture, even if it results in slower code? |
@retep998: That's exactly what LLVM should have done for a packed struct. This looks like a LLVM codegen bug to me. |
@vadimcn this is entirely our (or rather: my) fault. We used to not emit alignment data at all, which caused misaligned accesses for small aggregates (see #23431). But now we unconditionally emit alignment data purely based on the type that we're loading, ignoring where that value is stored. In fact at the point where we create the load, we currently don't even know where that pointer comes from and can't properly handle packed structs. |
Perhaps we need some sort of alignment attribute that we can attach to pointers? |
In general we have no idea where a reference comes from, e.g. |
Can we make creation of references into packed structs unsafe? |
triage: P-high |
Seems clear there's a bug here. First step is to evaluate how widely used packed is, so huon is going to do a crater run with |
There are some correctness issues due to unaligned internal fields and references. cc rust-lang#27060.
There's some correctness issues with this, so there may be breaking changes in future. See rust-lang/rust#27060.
This struct is laid out the same way with or without `packed`, since it's just a few bytes. The removal is good because there's some correctness issues with it, so there may be breaking changes to it in future and removing it now will avoid them all together. See rust-lang/rust#27060.
This struct never seems to be used in a way that requires being packed. The removal is good because there's some correctness issues with it, so there may be breaking changes to it in future and removing it now will avoid them all together. See rust-lang/rust#27060.
This struct is laid out the same way with or without `packed`, since it is empty. The removal is good because there's some correctness issues with it, so there may be breaking changes to it in future and removing it now will avoid them all together. See rust-lang/rust#27060.
These structs are laid out the same way with or without `packed`, since they're just repeats of a single element type. That is, they're always going to be three contiguous `f32`s. The removal is good because there's some correctness issues with it, so there may be breaking changes to it in future and removing it now will avoid them all together. See rust-lang/rust#27060.
With #82525, we are finally reaching the finish line to fixing this problem: once that lands, creating a reference to a packed field will cause a future-incompat warning in any context, even inside |
…ochenkov make unaligned_references future-incompat lint warn-by-default and also remove the safe_packed_borrows lint that it replaces. `std::ptr::addr_of!` has hit beta now and will hit stable in a month, so I propose we start fixing rust-lang#27060 for real: creating a reference to a field of a packed struct needs to eventually become a hard error; this PR makes it a warn-by-default future-incompat lint. (The lint already existed, this just raises its default level.) At the same time I removed the corresponding code from unsafety checking; really there's no reason an `unsafe` block should make any difference here. For references to packed fields outside `unsafe` blocks, this means `unaligned_refereces` replaces the previous `safe_packed_borrows` warning with a link to rust-lang#82523 (and no more talk about unsafe blocks making any difference). So behavior barely changes, the warning is just worded differently. For references to packed fields inside `unsafe` blocks, this PR shows a new future-incompat warning. Closes rust-lang#46043 because that lint no longer exists.
This issue here is only truly fixed once that lint has been turned into a hard error. |
Instead of a warning/error, why can't rust generate unaligned loads and stores automatically? When using zerocopy structs I think |
This would mean that all reads and writes through |
I guess defining getters/setters for all fields isn't that bad. Is there something that can be done to optimize unaligned accesses on architectures were it happens to be aligned? Or does that also give a 2x penalty? |
Even if it doesn't have that penalty, you now have:
Both of which are very bad in tight loops (instruction cache pressure and branch prediction shenanigans). Speculation is also probably poking its head in here somewhere. I think if you want to pass around unaligned references, the best you have is: let local_copy = packed_struct.some_u64;
f(&local_copy);
let mut local_copy = packed_struct.some_u64;
f_mut(&mut local_copy);
packed_struct.some_u64 = local_copy; to ensure that any unaligned loads or stores are explicit (well, assuming one knows that |
Sorry for my dumb questions let local_copy = packed_struct.some_u64; packed_struct.some_u64 = local_copy; These are not undefined behaviour? I'd expect the EDIT: I guess I'll just assume that's not the case. Thanks! |
Those are indeed not UB. Accessing packed places is okay since the required alignment is directly visible in the place expression. What is UB is to do the same through a reference, where there is no more any indication in the code that things might not be aligned. |
That is architecture-dependent, though. On platforms that do not have a strict alignment requirement at the hardware level, there should be a way to take advantage of this. |
FWIW, and I don't know to what extend that is actually relevant in terms of performance, but proper alignment is not just exploited by load/store instructions (which might require this on the hardware level), it also informs the optimizer about possible values -- the compiler will assume that the last few bits of a pointer are zero, and use that for its optimizations. There are also proposal to make e.g. all the values 0-3 a "nice" in And there is unsafe code that likes to pack a pointer and a few bits of information together in one pointer-sized datum, which it can by exploiting proper alignment. If |
So…Rust code will become a maze of
You'll probably need pointers rather than references, but that should give an idea of the knives you're juggling at that point :) . |
The main case for this I can think of is SIMD code, which is architecture-specific anyway. Nevertheless, such code could use a suitable wrapper around a raw pointer. This is just one instance of a larger problem: there are many APIs that take references, but which might not need the full guarantees references provide. For instance, |
Aren't SIMD instructions typically even more alignment-sensitive (at least for performance)? As for externally-managed resources, Rust doesn't have a good way to manage them other than sticking |
I can't help but feel very disappointed by the current state of affairs regarding packed structs and the direction Rust is taking here. Coming from a C++ background, I just started learning Rust for a reverse engineering weekend project where I needed to be able to read proprietary file formats with all kinds of weird layouts and I wanted to be declarative, which meant using packed structs. Unfortunately rustc would throw warnings about references to fields of packed structs, which is how I came across this issue. It has been argued that you can always use <something else> instead of packed structs. That is true, but the same argument can be made for anything. Why use Rust at all, when I could just use assembly? The point is that packed structs are the most well-known, obvious, convenient, maintainable, easy to teach way to interact with externally given data layouts and if Rust wants to be a good language for low-level tasks it needs to support packed structs well. The current situation is not good enough and turning those warnings into errors makes it even worse. From my personal experience working with LLVM I know that it supports an explicit alignment specifier on loads and stores. Therefore, it can in principle generate the correct instruction sequences for any load - including unaligned ones - portably. Hence this a Rust issue, not an LLVM issue. A solution has already been suggested, but seemingly ignored: Add alignment to the type system as an additional attribute, e.g. I would be happy to create an RFC around this idea if the Rust team is interested, though I'm not sure that I'm the right person for this, given my limited experience with Rust. Regardless, I do hope that something can be done about this. It would be unfortunate if Rust's support for packed structs was just as lacking as C/C++'s. |
Yes, this would be a principled approach to solving the problem. That is also a very heavy hammer for a niche feature, so the question is whether that is complexity budget well spent. Meanwhile in this issue we are working on at least fixing the soundness gap while other people (if they want) can work on more fundamental approaches to handling insufficiently aligned data in Rust. Put differently: even if we had alignment annotation on reference types, we would still have to break old code that creates references to packed fields. So your proposal does not even help make progress on the issue that is being discussed here. This is not an issue to discuss "making packed structs more ergonomic in Rust in general", it is specifically about rejecting code that creates references (with the current reference types, i.e. fully aligned references) to fields of packed structs. If people care about having support for unaligned references in Rust, that is certainly worth discussing, but not in this issue. :) This is a language extension idea so it could be discussed on Zulip or IRLO to gauge what would have to go into a pre-RFC.
As you say yourself later, Rust is no worse off than C/C++ -- if anything, Rust is better off since at least the compiler tells you when you are using packed structs the wrong way. So are you saying C/C++ are not "good language[s] for low-level tasks"? No this is not a question I actually would like to see discussed in this thread, I just want to ask you to please refrain from unnecessary rhetoric like this. (There are also tons of other great ways to deal with externally defined data layouts, and with serde Rust has world-class support for them.) |
In fact, given that we have #82523 to track the future-incompat issue, I wonder if we should close this one? |
Makes sense. I was under the impression that the Rust team wants to disallow references to packed struct fields simply because it is considered "wrong", not because of soundness. I now understand that this is not actually the case and there is still room for discussion on this topic, which I will try to take to the other channels you mentioned. Apologies for being off-topic here, I had difficulties finding the right place to ask about this as someone new to the Rust project. |
Using |
This comment was marked as outdated.
This comment was marked as outdated.
Issue rust-lang/rust#27060 has been resolved, so it is no longer possible to safely create unaligned pointers to packed struct fields.
This is now a tracking issue for RFC 1240, "Taking a reference into a struct marked
repr(packed)
should becomeunsafe
", but originally it was a bug report that led to the development of that RFC. The original miscompilation did not involve references, but it has been fixed; the reference case is the one that remains open. A lot of the discussion is hence outdated; this is the current state.Original Issue Description
(This code actually works fine on today's nightly.)
Will print, on playpen:
The assembly for the
ok
load is:But for the
bad
one, it isSpecifically, the
movups
(unaligned) became amovaps
(aligned), but the pointer isn't actually aligned, hence the CPU faults.The text was updated successfully, but these errors were encountered: