Repository navigation
Description
The u128 and i128 types on the wasm targets has alignment of 8. This is inconsistent with the alignment on all Tier-1 targets.
Compile this code:
#![crate_type = "cdylib"]
#[no_mangle]
pub extern "C" fn align_of_u128() -> usize {
std::mem::align_of::<u128>()
}The body of this function on all Tier-1 targets is "return 16", while on wasm32-unknown-unknown, wasm32-unknown-emscripten, and wasm32-wasi it compiles to "return 8".
This causes arrays that are aligned on wasm to no longer be aligned when pointers are passed through FFI to the host. For example, creating a Vec<u128> in wasm and returning it as slice to the host causes misalignment.
I am not sure if this is a compiler bug or if this is for some reason intentional, but it is very annoying. A workaround is to declare your own this wrapper to force alignment and use it throughout:
#[repr(C, align(16))]
struct au128(u128);however that doesn't play well with external libraries that use u128.
Meta
This happens both on stable 1.83 (see the Godbolt link above) and my nightly from 2024-12-05
rustc --version --verbose:
rustc 1.85.0-nightly (c94848c04 2024-12-05)
binary: rustc
commit-hash: c94848c046d29f9a80c09aae758e27e418a289f2
commit-date: 2024-12-05
host: x86_64-unknown-linux-gnu
release: 1.85.0-nightly
LLVM version: 19.1.5
Activity
hanna-kruppe commentedon Dec 7, 2024
Quite a few non-wasm targets have eight byte alignment for u128 and i128 (e.g., 32-bit Arm and RISC-V targets). I don't know of any that are tier 1 right now, but tiers can change over time, tier 1 is a very high bar, and many tier 2 targets are widely used. In fact, some tier 1 targets had 8-byte-aligned u128 until earlier this year. So I would strongly advice against assuming any particular alignment for those types.
That said, in general i128 and u128 should be interoperable with C's 128-bit integer types when they exist. I don't know when Clang started supporting these types in wasm, but WebAssembly/tool-conventions#240 indicates that they're supported now with 16 byte alignment. So Rust may have to increase the alignment of these types on the wasm targets anyway for interop with C code that's also compiled to wasm. That doesn't change the fact that non-wasm code shouldn't assume it agrees with wasm code on the alignment.
Another solution is to use
u128on the wasm side but not on the host side, at least not for talking about slices pointing into wasm memory. You can avoid assuming any alignment for things in wasm memory and do unaligned reads viauN::from_le_bytes. Explicitly using little endian byte order also makes the host code portable to big endian platforms (wasm is always little-endian).V0ldek commentedon Dec 7, 2024
The reason I mention Tier 1 targets is that wasm is a bit "special", especially wasi, in that it often needs to interop with host functions. Different alignments between x86 and idk PowerPC are unlikely to matter, whereas wasm is by necessity embedded in another target.
hanna-kruppe commentedon Dec 7, 2024
Sure, but the host code isn't limited to tier 1 Rust targets. As a concrete example, wasmtime explicitly supports several targets that are or were tier 2 in rustc (currently Linux s390x and Linux riscv64, also macOS aarch64 was only promoted to Rust tier 1 recently). Rust target tiers are basically never relevant for "do I need to worry about this target when writing portable code?" because the tiers don't have any inherent meaning, they only reflect how much a target is currently tested in rust-lang/rust CI.
workingjubilee commentedon Dec 8, 2024
Yes.
We do not intend to alter our wasm ABI conformance based on non-wasm targets.
We do have to alter the alignment to match the ABI documented by tool-conventions.
workingjubilee commentedon Dec 8, 2024
cc @sunfishcode and @alexcrichton Should this be done upstream in LLVM? Like was done in llvm/llvm-project@dbad963 for SPARC.
sunfishcode commentedon Dec 9, 2024
It seems the situation is that Clang defaults to aligning
__int128_tto 16 bytes, while LLVMdatalayoutstrings default to aligningi128to 8 bytes. Wasm is currently using the defaults for both, so it's inconsistent.It appears the fix for SPARC was to add an explicit
i128entry to thedatalayoutstring to override the default. That sounds like it would be a reasonable fix for Wasm too.Another option would be to change clang, to align
__int128_tto 8 bytes. Wasm doesn't have ai128type and seems unlikely to add one for the foreseeable future (there is a discussion abouti128arithmetic, but that would use pairs ofi64s). That would be a C ABI break, but__int128_tisn't that widely used, and technically it's not documented ABI yet, as WebAssembly/tool-conventions#240 hasn't landed yet. However, if Wasm ever adds a 128-bit atomic, it seems likely to require 16-byte alignment, since aarch64 and x86_64 both do, and clang would want to use it for_Atomic __int128_tandstd::atomic<__int128_t>, and I don't know of any precedent for having alignment differ between atomic and non-atomic types.So overall, adding
-i128:128to Wasm's datalayout string sounds like the best path to me. I've now posted llvm/llvm-project#119204 with a patch similar to llvm/llvm-project@dbad963, for WebAssembly.workingjubilee commentedon Dec 9, 2024
Thanks!
[WebAssembly] Add `-i128:128` to the `datalayout` string.
[WebAssembly] Add `-i128:128` to the `datalayout` string.
[WebAssembly] Add `-i128:128` to the `datalayout` string. (#119204)
[WebAssembly] Add `-i128:128` to the `datalayout` string. (#119204)
[WebAssembly] Add `-i128:128` to the `datalayout` string. (llvm#119204)