Skip to content

Function alignment does not work on wasm #143368

Open

Description

@RalfJung

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?)

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jul 3, 2025
  2. RalfJung commented on Jul 3, 2025

    @RalfJung
    MemberAuthor

    @folkertdev

    So, should we just apply a target-specific minimum of #[align(1)] for wasm? (much like on windows targets the limit will be 8192)

    I assume you mean "target-specific maxmimum"?

  3. changed the title [-]#[align] on functions does not work on wasm[/-] [+]Function alignment does not work on wasm[/+] on Jul 3, 2025
  4. folkertdev commented on Jul 3, 2025

    @folkertdev
    Contributor

    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.

  5. RalfJung commented on Jul 3, 2025

    @RalfJung
    MemberAuthor

    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.

  6. added
    T-langRelevant to the language team
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    O-wasmTarget: WASM (WebAssembly), http://webassembly.org/
    F-fn_align`#![feature(fn_align)]`
    A-alignArea: alignment control (`repr(align(N))` and so on)
    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jul 3, 2025
  7. nikic commented on Jul 3, 2025

    @nikic
    Contributor

    Error when the attribute is used on wasm, since relying on fn ptr being divisible by N will just not work

    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 Fi8 in the data layout...)

  8. hanna-kruppe commented on Jul 3, 2025

    @hanna-kruppe
    Contributor

    I haven’t followed all the discussions around this attribute and what it should mean. But I’m not sure if this:

    Error when the attribute is used on wasm, since relying on fn ptr being divisible by N will just not work

    … 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)

  9. 2 remaining items

  10. folkertdev commented on Jul 3, 2025

    @folkertdev
    Contributor

    For manual pointer tagging, or the use in vector tables, I think we should guarantee something. I think that this should work?

    (my_fn as usize & !(INSTRUCTION_ALIGN - 1)).is_multiple_of(N)

    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) (where addr() masks out the low bits based on the target's instruction alignment).

  11. Jules-Bertholet commented on Jul 3, 2025

    @Jules-Bertholet
    Contributor

    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).

    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 on align for 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).

  12. hanna-kruppe commented on Jul 3, 2025

    @hanna-kruppe
    Contributor

    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 setting INSTRUCTION_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.

  13. hanna-kruppe commented on Jul 3, 2025

    @hanna-kruppe
    Contributor

    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 on align for 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).

    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)

  14. folkertdev commented on Jul 3, 2025

    @folkertdev
    Contributor

    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.

  15. RalfJung commented on Jul 3, 2025

    @RalfJung
    MemberAuthor

    Let's focus on wasm in this issue, ARM is mostly off-topic and so needs its own issue / discussion elsewhere.

  16. hanna-kruppe commented on Jul 3, 2025

    @hanna-kruppe
    Contributor

    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.

  17. workingjubilee commented on Jul 3, 2025

    @workingjubilee
    Member

    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!

  18. hanna-kruppe commented on Jul 3, 2025

    @hanna-kruppe
    Contributor

    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?

  19. alexcrichton commented on Jul 3, 2025

    @alexcrichton
    Member

    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=16 doesn'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.

    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

    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=N seems 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)

  20. Jules-Bertholet commented on Jul 3, 2025

    @Jules-Bertholet
    Contributor

    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

    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.

    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.

  21. alexcrichton commented on Jul 3, 2025

    @alexcrichton
    Member

    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.

  22. workingjubilee commented on Jul 3, 2025

    @workingjubilee
    Member

    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) - 1 table 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.

  23. riking commented on Jul 14, 2025

    @riking

    Did the "wasm32 DataLayout missing -Fi8" issue ever get upstreamed here?

  24. Jules-Bertholet commented on Sep 16, 2025

    @Jules-Bertholet
    Contributor

    I don’t think the data layout string matters here?
    The docs say:

    F<type><abi>

    This specifies the alignment for function pointers. The options for are:

    • i: The alignment of function pointers is independent of the alignment of functions, and is a multiple of <abi>.

    • n: The alignment of function pointers is a multiple of the explicit alignment specified on the function, and is a multiple of <abi>.

    If functions can’t have explicit alignments at all on the target, none of this is relevant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.A-alignArea: alignment control (`repr(align(N))` and so on)C-bugCategory: This is a bug.F-fn_align`#![feature(fn_align)]`O-wasmTarget: WASM (WebAssembly), http://webassembly.org/T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions