Stream: t-lang/wg-unsafe-code-guidelines

Topic: What level are alignment requirements expressed?


Jake Goulding (May 14 2020 at 18:48, on Zulip):

For example, &u32 has forbidden values for address 0 (the null pointer) and for every address that is not a multiple of 4 (because u32 must be aligned to a multiple of 4 bytes).

This statement surprised me. I didn't think that the language said that u32 needed to be aligned to 4 bytes, but that was a requirement of the platform (e.g. x64 or arm). I thought that Rust just said "it must be aligned" without stating what the alignment requirements are.

Hanna Kruppe (May 14 2020 at 18:50, on Zulip):

I think this wording is just sloppy because in practice most platforms set alignment of u32 to 4, even if some obscure ones may set it to something different. Certainly the alignment of u64 varies among common platforms.

Connor Horman (May 15 2020 at 02:25, on Zulip):

I know 65816 uses alignment of 2 for everything larger than 1 byte, except pointers which have alignment 4(so that single loads/stores can't cross bank bounderies which is never correct) If rust required 4 for i32, that would have linkage issues, and cause soundness problems in my support library, which does create references to some 2-byte (but not 4-byte) aligned i32s

Lokathor (May 15 2020 at 03:13, on Zulip):

oof, I'll be more careful to not assume align4 on even primitives XD

RalfJ (May 16 2020 at 08:55, on Zulip):

Crucially though, whatever the platform says the alignment is is required even when just creating a reference that doesn't get used. I've seen Rust code that used cfg arguing "well on x86 we can do unaligned stuff". Such code is UB.
(You are probably all aware, just making sure this is clear to anyone reading this.^^)

Jake Goulding (May 16 2020 at 11:22, on Zulip):

Hmm, I think that has reduced my confidence in my understanding, @RalfJ ;-)

whatever the platform says the alignment is is required even when just creating a reference that doesn't get used

This makes sense to me.

used cfg arguing "well on x86 we can do unaligned stuff".

This seems like it should have been OK; assuming that the x86 platform doesn't require the pointer to be aligned. Your sentence seems to indicate that more than current platform matters.

RalfJ (May 16 2020 at 11:23, on Zulip):

I guess the question is, what does it mean for "the x86 platform/target to require the ptr to be aligned"

RalfJ (May 16 2020 at 11:23, on Zulip):

some people might say this is a property of the CPU and has to do with whether an unaligned load/store instruction is permitted

RalfJ (May 16 2020 at 11:24, on Zulip):

and some people might say it has to do with what align_of::<i32>() will return on x86

RalfJ (May 16 2020 at 11:24, on Zulip):

if people check align_of::<i32>() == 1 and then do unaligned stuff, they are indeed fine. no need to make any platform assumptions.

RalfJ (May 16 2020 at 11:25, on Zulip):

but that's not what I meant above, they were doing unaligned loads even though align_of::<i32>() == 4. they were thinking that "load/store instruction does not require alignment" would matter. it does not. the only thing that matters is what align_of says.

RalfJ (May 16 2020 at 11:26, on Zulip):

@Jake Goulding does that help?

Jake Goulding (May 16 2020 at 11:30, on Zulip):

RalfJ said:

I guess the question is, what does it mean for "the x86 platform/target to require the ptr to be aligned"

Yes, this is probably a key point. It sounds like you are saying that it's tied to what align_of returns. I guess that opens up my next question — how is the return value of align_of decided upon?

Jake Goulding (May 16 2020 at 11:31, on Zulip):

This is a non-academic question for me, I think, as I'm helping a little with the AVR port.

RalfJ (May 16 2020 at 11:31, on Zulip):

how is the return value of align_of decided upon?

no idea. I think this is part of that magic string that you can find in every target definition. LLVM might also come into play.

RalfJ (May 16 2020 at 11:31, on Zulip):

it might be part of the stuff one writes down when defining a "system ABI"

RalfJ (May 16 2020 at 11:31, on Zulip):

together with calling conventions and the like

Hanna Kruppe (May 16 2020 at 11:33, on Zulip):

On most platforms, for most primitive types types, it is determined by the C ABI we're trying to match. We can't set a lower ABI alignment than C because then struct/union layouts will be different and C code will run into UB when you hand it pointers to Rust-allocated data, and we can't set it lower for the same reasons in the other direction.

RalfJ (May 16 2020 at 11:35, on Zulip):

one "lower" should be "higher" I think

Hanna Kruppe (May 16 2020 at 11:35, on Zulip):

This "only" applies to types which are (directly or indirectly) allowed in FFI, but that turns out to cover almost all primitives, and for aggregates there's not really a good alternative to to taking the maximum of the alignments of the fields as the alignment of the aggregate.

Hanna Kruppe (May 16 2020 at 11:35, on Zulip):

Right, edited.

Jake Goulding (May 16 2020 at 11:38, on Zulip):

See, we typed that magic LLVM string for AVR at one point. In our case, we were still trying to match what GCC did. Now I wonder what it would look like for a total blank slate.

Hanna Kruppe (May 16 2020 at 11:46, on Zulip):

I think on modern x86, UB for unaligned accesses probably does more harm than good, but there's still incentives to generally align your data when you can. So the "blank slate" state I'm curious about is if we just had "preferred alignment" that influenced data layout but didn't lead to language level UB.

Hanna Kruppe (May 16 2020 at 11:47, on Zulip):

(we could actually kind of do that in Rust: leave align_of as-is for compatibility but don't add align n to references and memory accesses, other than atomics)

Hanna Kruppe (May 16 2020 at 11:48, on Zulip):

(more precisely: introduce a second notion of alignment that can be smaller than ABI alignment and couple all our alignment related UB to this number instead of align_of)

RalfJ (May 16 2020 at 11:50, on Zulip):

OTOH, other platforms still trap on unaligned accesses, so this could be a portability hazard

Hanna Kruppe (May 16 2020 at 11:54, on Zulip):

The way I see it, it's better if code is fine on x86 and doesn't compile on MIPS than if it's UB everywhere but appears to work on x86

Hanna Kruppe (May 16 2020 at 11:54, on Zulip):

Or even if it compiles but traps in MIPS

RalfJ (May 16 2020 at 11:56, on Zulip):

oh so your suggestion for removal of UB was for all platforms, not just x86?

RalfJ (May 16 2020 at 11:57, on Zulip):

wouldn't this effectively mean codegen'ing every load and store like read/write_unaligned?

Hanna Kruppe (May 16 2020 at 12:02, on Zulip):

It would mean that, and that's too expensive on some platforms, so I'm not proposing that. I just wonder if we can reduce the incidence of programmers causing UB by doing unaligned accesses the "natural" way that they "know" is fine (at machine code level) on x86

RalfJ (May 16 2020 at 12:03, on Zulip):

we could do the change you are proposing but just for x86.

RalfJ (May 16 2020 at 12:03, on Zulip):

would changing align_of be such a problem, as long as allocators still followed "preferred alignment"? I admit I never understood that "preferred" thing. Miri entirely ignores it.

Hanna Kruppe (May 16 2020 at 12:04, on Zulip):

(Well, I'm not just worried about accidential UB, I would also like to see if algorithms that rely on unaligned accesses get much mor ergonomic to write if you don't need to carefully handle raw pointers and std::ptr functions.)

RalfJ (May 16 2020 at 12:04, on Zulip):

Hanna Kruppe said:

(Well, I'm not just worried about accidential UB, I would also like to see if algorithms that rely on unaligned accesses get much mor ergonomic to write if you don't need to carefully handle raw pointers and std::ptr functions.)

is that ergonomic win worth it if it's just for one platform though?

RalfJ (May 16 2020 at 12:05, on Zulip):

maybe this should be filed into the general category of "raw ptr ergonomics", which already has a bunch of other stuff in it

Hanna Kruppe (May 16 2020 at 12:05, on Zulip):

Well, align_of is what all Rust code talking to allocator uses, i don't think the preferred align is even exposed outside of the compiler.

RalfJ (May 16 2020 at 12:05, on Zulip):

as in, dont change what references do, but make raw (in particular, unaligned) ptrs less annoying to use

RalfJ (May 16 2020 at 12:06, on Zulip):

Hanna Kruppe said:

Well, align_of is what all Rust code talking to allocator uses, i don't think the preferred align is even exposed outside of the compiler.

only unstably

RalfJ (May 16 2020 at 12:06, on Zulip):

not sure why that still exists then, TBH^^

Hanna Kruppe (May 16 2020 at 12:10, on Zulip):

is that ergonomic win worth it if it's just for one platform though?

It's plausibly multiple platforms, but anyway, these kinds of code paths are generally platform-specific anyway because they're written that way for performance reasons and the performance profile of unaligned accesses varies wildly between platforms. If you have a code path using unaligned accesses, you probably also have a different code path not using them, or you don't support any platforms where this is an issue.

Amanieu (May 16 2020 at 12:35, on Zulip):

Compilers can and do take advantage of alignment information, even on x86: https://pzemtsov.github.io/2016/11/06/bug-story-alignment-on-x86.html

Amanieu (May 16 2020 at 12:37, on Zulip):

If anyone tries to claim "but x86 supports unaligned memory access", just show them this counter-example.

RalfJ (May 16 2020 at 12:38, on Zulip):

Amanieu said:

If anyone tries to claim "but x86 supports unaligned memory access", just show them this counter-example.

thanks, I have a bookmark folder for stuff like that :)

RalfJ (May 16 2020 at 12:43, on Zulip):

oh wow I didnt know C also has UB in unaligned ptr casts.

Hanna Kruppe (May 16 2020 at 12:47, on Zulip):

This is a fine example for demonstrating that this UB can lead to actual problems and not just "de jure" incorrectness, but I want to point out that it isn't a good argument for why unaligned accesess ought to be UB on x86. Just using movdqu instead of movdqa makes the problem go away, and has zero performance impact (when data is aligned, i.e., when the *a variant works at all) on any uarch anyone cares about.

There are also other examples of compiler optimizations that are enabled by the assumption of alignment (e.g. optimizing out alignment checks in generic routines after a caller, or being able to prove some accesses must be non-overlapping because they're at least X bytes apart and aligned to Y > X), but as far as I know there are all very niche and very small wins. Not enough to justify UB or to justify jumping through hoops when you want unaligned accesses.

Peter Rabbit (May 16 2020 at 13:57, on Zulip):

For a bit of prior art, VC++ has an __unaligned attribute that can be slapped on pointers to let you trivially manipulate unaligned data safely.

Connor Horman (May 17 2020 at 03:56, on Zulip):

Having the alignment requirement is good on 65816, because if I don't know the access is aligned, I have to copy the data 8-bits at a time into a dedicated memory address. This has performance and size issues. This is because the 65816 wraps accesses at 64k (bank) boundaries, even though it can address 16M.

Peter Rabbit (May 17 2020 at 04:47, on Zulip):

Also don't forget that Windows has separate concepts for default alignment vs required alignment. A type's actual alignment is the greater of its default alignment and its required alignment. By default all types have a required alignment of 1, and the default alignment is the type's normal natural alignment. If you use #pragma pack you are lowering the default alignment of any fields in that struct. If you use __declspec(align) you are raising the required alignment of any fields in the struct. Notably this means if you have any __declspec(align) types inside a #pragma pack struct, the alignment of those specific fields will not be lowered below their required alignment. This is completely contrary to what gcc does.

RalfJ (May 17 2020 at 08:40, on Zulip):

@Peter Rabbit that last point seems relevant when compiling/interoperating with MSVC, but for Rust itself, it's just not how repr(packed) behaves -- right? What we do with reference UB is a purely Rust-internal decision as far as I can see.

Peter Rabbit (May 17 2020 at 08:43, on Zulip):

RalfJ said:

Peter Rabbit that last point seems relevant when compiling/interoperating with MSVC, but for Rust itself, it's just not how repr(packed) behaves -- right? What we do with reference UB is a purely Rust-internal decision as far as I can see.

It's the reason Rust currently forbids repr(align) types inside repr(packed), but yes Rust choosing to make insufficiently aligned references UB is purely Rust's decision to make. I'm perfectly fine with it being UB as long as we have the tools to write reasonably ergonomic code that works with packed unaligned data.

Frank McSherry (Jun 01 2020 at 13:22, on Zulip):

Amanieu said:

If anyone tries to claim "but x86 supports unaligned memory access", just show them this counter-example.

I didn't read that in the article, but I might have misunderstood. The article seemed to be about how a language spec allowed UB for unaligned accesses, even though the underlying platform permitted it, and how this was the source of bugs. (( sorry for the drive-by; this just happened to be the focus post when I logged in, and as someone who wants to do zero-copy work on data received over the network I have a horse in this race. ))

Last update: Jun 05 2020 at 23:15UTC