Repository navigation
Open
Labels
A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.A-alignArea: alignment control (`repr(align(N))` and so on)Area: alignment control (`repr(align(N))` and so on)C-bugCategory: This is a bug.Category: This is a bug.F-fn_align`#![feature(fn_align)]``#![feature(fn_align)]`O-wasmTarget: WASM (WebAssembly), http://webassembly.org/Target: WASM (WebAssembly), http://webassembly.org/T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamRelevant to the language team
Description
For #[align], see the error here and the discussion here. I assume this also affects -Zmin-function-align.
In wasm, functions don't have an "address", it's not a von-Neumann architecture. Instead, there is a dedicated table and one can pick an integer index into the table when doing a dynamic call. So the actual address of a function cannot be observed and "aligning" it has no meaning. However, the fn ptr value can be observed by being cast to an integer, and then one will see the index into that table. One could attempt to align that but it seems not very useful...
Apart from the usefulness, I guess the question is what can we do?
- Error when the attribute is used on wasm, since relying on fn ptr being divisible by N will just not work
- Try to convince LLVM to implement this (is there an LLVM issue?)
Reacted by José Luis Cruz
Metadata
Metadata
Assignees
Labels
A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.A-alignArea: alignment control (`repr(align(N))` and so on)Area: alignment control (`repr(align(N))` and so on)C-bugCategory: This is a bug.Category: This is a bug.F-fn_align`#![feature(fn_align)]``#![feature(fn_align)]`O-wasmTarget: WASM (WebAssembly), http://webassembly.org/Target: WASM (WebAssembly), http://webassembly.org/T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamRelevant to the language team
Activity
RalfJung commentedon Jul 3, 2025
@folkertdev
I assume you mean "target-specific maxmimum"?
[-]#[align] on functions does not work on wasm[/-][+]Function alignment does not work on wasm[/+]folkertdev commentedon Jul 3, 2025
Right so an additional option is to allow only
#[align(1)]and error on any higher alignment for the wasm targets. It is just the highest alignment we can guarantee. If LLVM ever moves on this, we can bump that limit.RalfJung commentedon Jul 3, 2025
Right, that's morally equivalent to just erroring (since why would you ever use
align(1)but fits better into the existing framework.Either way it makes lots of code with reasonable small alignments in the range of 2..=64 non-portable, which is a bummer... but seems hard to avoid.
nikic commentedon Jul 3, 2025
Something to keep in mind is that function alignment != function pointer alignment. The fact that functions are aligned to N does not imply that function pointers are also aligned to N (e.g. on targets like ARM).
Wasm sounds like another architecture where this applies. (Probably it should have
Fi8in the data layout...)hanna-kruppe commentedon Jul 3, 2025
I haven’t followed all the discussions around this attribute and what it should mean. But I’m not sure if this:
… is the right framing. With the function pointer tagging done on ARM (LSB indicates whether callee is thumb mode) it’s already probably not true that the fn ptr is divisible by N if the function’s code is aligned to N bytes. See also discussion at rust-lang/rfcs#3806 (comment)
2 remaining items
folkertdev commentedon Jul 3, 2025
For manual pointer tagging, or the use in vector tables, I think we should guarantee something. I think that this should work?
still you need the limits on the alignment (so 1 for wasm, 8192 for windows).
There is also my earlier suggestion that maybe a function pointer's integer value should be distinct from its actual address, so that you could guarantee that
my_fn.addr().is_multiple_of(N)(whereaddr()masks out the low bits based on the target's instruction alignment).Jules-Bertholet commentedon Jul 3, 2025
Yes, but that doesn’t mean there are no guarantees. On ARM, you can determine the tag bit from the function’s
instruction_set, and if you mask out that bit, then the resulting value is aligned as expected. You can still rely onalignfor function pointer tagging, it just takes some special handling for that specific platform. (And the RFC future possibilities include adding APIs to abstract these target-specific quirks away).hanna-kruppe commentedon Jul 3, 2025
The potential guarantee described by @folkertdev covers ARM. I’m not sure if it’ll generalize to other (future and/or niche) targets without bending the meaning of
INSTRUCTION_ALIGN. At least it can cover the extreme case of “code alignment tells you absolutely nothing about fn ptr alignment” by settingINSTRUCTION_ALIGN=MAX_FN_ALIGN(e.g., 4096 or 8192) which won’t be the actual instruction alignment but should be fine as long as this constant is only used for fn ptr tagging and not for anything else.hanna-kruppe commentedon Jul 3, 2025
However, also keep in mind that this is a relatively obscure part of ISA semantics that may not be immutable forever. There was a time where ARM didn’t have Thumb mode, and there was a time where 64 bit virtual addresses were really only 48 bits so there was plenty of room for pointer tagging in the most significant bits. Rust should be cautious about making guarantees to programmers that can easily be broken by new instruction set extensions, OS features, security mitigations, etc. (sometimes this is solved by making a new target (modifier) but when possible it’s preferable to stay ABI compatible)
folkertdev commentedon Jul 3, 2025
I'm fully aware that my proposed guarantee is not very elegant. We can alternatively split out the guarantee by target.
But my sense is that we must guarantee something about how
#[align(N)]relates to the function address (that is to say, location in memory), otherwise what is even the point? The whole goal here is to be able to guarantee the alignment of e.g. functions that are part of a vector table.RalfJung commentedon Jul 3, 2025
Let's focus on wasm in this issue, ARM is mostly off-topic and so needs its own issue / discussion elsewhere.
hanna-kruppe commentedon Jul 3, 2025
Insofar
align(N)is about target specific mechanisms like interrupt vector tables or how Linux implements function tracing, the guarantees provided can also be target specific (including the simplifying assumption that code alignment is the same as fn ptr alignment, which avoids many problems). Wasm doesn’t have such target specific mechanisms. There’s also the use case of Rust programs doing their own pointer tagging, which is sensible in theory on Wasm, but that’s entirely about the fn ptr value (table index) and not about the location of the function’s code. Not sure if that distinction matters for Wasm.workingjubilee commentedon Jul 3, 2025
I don't think we should attempt to enforce alignment on architectures where the concept is inherently nonsensical. The attribute, as I understand it, has always been motivated by a need for code alignment for specific kinds of functions on architectures where the hardware has some real concept of such alignment.
The argument that you have to have this for function pointer tagging purposes on wasm32 is silly because you are probably executing on a 64-bit architecture and thus can have fast operations on 8-byte values, and tables have a defined range of
0..=u32::MAX. The pointer tagging has been done for you already by forcing all functions into a 4-byte address space, giving you an entire 4 bytes!hanna-kruppe commentedon Jul 3, 2025
Stealing a bit or two for other purposes is most useful for keeping data-at-rest compact, not for faster operations after you've loaded things into registers anyway. IME, for data pointers, the desire to do that is often relatively target-independent, so it might be neat if Rust could also support that with function pointers in a way that can be almost entirely platform-agnostic. If I wanted function pointer tagging specifically for wasm, I'd rather use the most significant bits of the table index because it's much easier to ensure those will be zeros. But if I have a general pointer tagging solution in portable Rust code, I'm unlikely to bend over backwards to make it maximally efficient on wasm as well, unless I myself desperately want that code to work (efficiently) on wasm.
That said: I agree that it's questionable to take a concept originally motivated by hardware where code alignment has meaning, and try to make it fit into wasm's function table indices just because those are what you get when casting
fn()to usize in Rust. Especially if LLVM and LLD don't support it currently. Is there a specific known use case for function pointer tagging that isn't already platform-specific in ways that preclude compiling to wasm? If not, setting "max fn align" to 1 on wasm targets seems like a fine solution until a use case emerges. Raising the fn align limit is backwards compatible, right?alexcrichton commentedon Jul 3, 2025
I have no problem with the conclusion "let's not support this on wasm", but I do want to clarify a few things as well. First, AFAIK, I don't think this is supported at all in LLVM right now. Locally attempting to compile something with
-falign-functions=16doesn't seem to have any affect on the output, notably the table index 1 is still used for an indirect function. That alone is enough to make me conclude this shouldn't be supported on Wasm in Rust at this time, but I'm going to continue further by imagining a hypothetical world where this is indeed supported in LLVM/LLD.Personally I do think that you can make a case to support this on wasm a la the function tagging argument. For example if you wanted some particular function pointers to guarantee their low bits are set it seems like you could have the same use case for that in wasm as you do on native. To be clear though this is all from the perspective of the wasm sandbox, so the guarantee to provide is "the wasm-appearing function pointer, a table index, has low bits clear". The actual address of the code in host physical memory is not possible to witness in wasm and is basically irrelevant. Function alignment would have nothing to do with that and would entirely be a model within the guest program itself and its table indices.
To clarify this the function pointer, within wasm itself, is a 32-bit value on wasm32. That means wasm doesn't actually have access to 4 upper bytes. What a runtime might use as a function pointer, for example a
funcref, has no bit-value observable in wasm so the byte-width nor alignment has no meaning in the guest. Also FWIW on wasm64 function pointers are 64-bit integers where the table's maximum value is 2^64-1, basically function pointers are inflated the same way linear memory pointers are. In such a situation using the upper 4 bytes is likely much more plausible though as it's pretty unlikely you have more than 4 billion indirect functions.After writing all that down though I'm also realizing that the C argument
-falign-functions=Nseems like it aligns every single function. If you did that in wasm then you'd have a very sparsely populated function table which would be quite inefficient from a runtime perspective so it's unlikely anyone would actually want to do that. Only tagging a subset of functions with a higher alignment makes sense to me because then alignement-1 functions could fill in the gaps between the higher-aligned functions. (I realize the Rust attribute supports this, but figured I could write this down anyway)Jules-Bertholet commentedon Jul 3, 2025
I think what @workingjubilee is saying is that you can cast your function pointer to a 32-bit integer, and the upcast that to 64 bits, and use that 64-bit value as the tag carrier.
alexcrichton commentedon Jul 3, 2025
Aha good point, that would indeed work! That may not work so well on wasm64, but that's a much more niche architecture at this time.
workingjubilee commentedon Jul 3, 2025
Yes.
I think we can cross the wasm64 bridge when we come to it. If it is truly useful to steal a byte then one could present the argument to the wasm spec that there should be a limit of
2.pow(56) - 1table entries.As nikic said, the wasm targets should probably explicitly indicate their function address space does not meaningfully support alignment by having "-Fi8" in the datalayout string, as all the Arm targets with Thumb support do.
riking commentedon Jul 14, 2025
Did the "wasm32 DataLayout missing -Fi8" issue ever get upstreamed here?
Jules-Bertholet commentedon Sep 16, 2025
I don’t think the data layout string matters here?
The docs say:
If functions can’t have explicit alignments at all on the target, none of this is relevant.