Skip to content

C API wasmtime_val_t layout mismatch on 32-bit platforms (x86/armv7a) due to union alignment #13733

Description

@crowforkotlin

Issue Description

On 32-bit platforms (e.g., i686-linux-android, armv7-linux-androideabi), the C API header wasmtime/val.h contains static_assert checks that require wasmtime_valunion_t and wasmtime_val_raw_t to be 8-byte aligned:

static_assert(__alignof(wasmtime_valunion_t) == 8, "should be 8-byte aligned");
static_assert(__alignof(wasmtime_val_raw_t) == 8, "should be 8-byte aligned");

However, on 32-bit platforms, int64_t and double only have 4-byte alignment in the C ABI, causing these assertions to fail at compile time.

Additionally, even if the assertions are bypassed, there is a layout mismatch between Rust and C on 32-bit platforms:

Rust #[repr(C)] C (original val.h)
union alignment 8 (Rust's i64/u64 are always 8-byte aligned) 4 (C ABI int64_t on 32-bit)
wasmtime_val_t.of offset 8 4
wasmtime_val_t size 32 20

This causes the C side to read garbage data for the kind field (e.g., unknown wasmtime_valkind_t: 16), leading to a SIGABRT crash at runtime when calling wasmtime_func_call.

Reproduction

  1. Build libwasmtime.a for i686-linux-android (32-bit x86 Android)
  2. Include wasmtime/val.h from C++ code compiled with the Android NDK (x86 target)
  3. Compile fails with static_assert errors
  4. If assertions are removed, runtime crash occurs: Abort message: 'unknown wasmtime_valkind_t: <garbage>'

Crash Log (x86 Android Emulator)

Abort message: 'unknown wasmtime_valkind_t: 16'
signal 6 (SIGABRT), code -1 (SI_QUEUE)
backtrace:
  #14 pc ... libwasmline.so (wasmtime_func_call+192)
  #15 pc ... libwasmline.so (wasmline::Session::invokeInbound+465)
  ...

Proposed Fix

Force 8-byte alignment on both C and Rust sides to ensure consistent layout across all platforms:

crates/c-api/include/wasmtime/val.h:

-typedef union wasmtime_valunion {
+typedef union alignas(8) wasmtime_valunion {

-typedef union wasmtime_val_raw {
+typedef union alignas(8) wasmtime_val_raw {

-typedef struct wasmtime_val {
+typedef struct alignas(8) wasmtime_val {

crates/c-api/src/val.rs:

-#[repr(C)]
+#[repr(C, align(8))]
 pub struct wasmtime_val_t {

-#[repr(C)]
+#[repr(C, align(8))]
 pub union wasmtime_val_union {

And update the compile-time assertion:

 const _: () = {
-    assert!(std::mem::size_of::<wasmtime_val_union>() <= 24);
-    assert!(std::mem::align_of::<wasmtime_val_union>() == std::mem::align_of::<u64>());
+    assert!(std::mem::size_of::<wasmtime_val_union>() <= 32);
+    assert!(std::mem::align_of::<wasmtime_val_union>() == 8);
 };

Impact

Platform Before Fix After Fix
x86_64 / aarch64 (64-bit) align=8, works ✅ align=8, no change ✅
x86 / armv7a (32-bit) align=4, compile fails / runtime crash ❌ align=8, works ✅
  • 64-bit platforms: zero impact (alignas(8) is redundant, natural alignment is already 8)
  • 32-bit platforms: fixes compile-time assertion failure and runtime layout mismatch
  • Memory overhead on 32-bit: ~12 bytes per wasmtime_val_t (negligible for typical usage)
  • API compatibility: no changes to type names or function signatures

Environment

  • Forked Wasmtime version: 45.0.6 (custom features pulley-min [x86/armv7a] )
  • Affected targets: i686-linux-android, armv7-linux-androideabi (and potentially other 32-bit targets)
  • Discovered while building wasmline (a Wasm plugin framework) with Pulley interpreter on 32-bit Android

Fix

Result After Fix

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions