Skip to content

fix(thumb): keep SP 8-byte aligned for native calls with >4 argv words#5007

Open
SupposedlySam wants to merge 1 commit into
bytecodealliance:mainfrom
SupposedlySam:pr/thumb-sp-align
Open

fix(thumb): keep SP 8-byte aligned for native calls with >4 argv words#5007
SupposedlySam wants to merge 1 commit into
bytecodealliance:mainfrom
SupposedlySam:pr/thumb-sp-align

Conversation

@SupposedlySam

Copy link
Copy Markdown

Summary

invokeNative_thumb.s leaves SP at 4 mod 8 at the native call for any native invoked with more than 4 packed argv words, violating AAPCS 8-byte stack alignment.

Root cause

The stack reservation for arguments beyond r0–r3 is rounded up to a multiple of 8 (add #7 / bic #7), then an unconditional add r6, r6, #4 adds one more word. SP is already 8-byte aligned at that point — the prologue pushes r4–r7 + lr (20 bytes) and does sub sp, #4, 24 bytes total (.cfi_def_cfa_offset 24) — so the reservation must be a multiple of 8 and nothing more. The extra word makes SP 4 mod 8 at blx ip whenever argc > 4.

Effect

An outgoing 8-byte argument (e.g. a vararg i64) is placed by the compiler at a slot it models as 8-aligned but whose real address is 4 mod 8; the callee's va_arg realigns to the true boundary 4 bytes lower and reads the low word paired with a zero — so a %lld in such a native prints value * 2^32. The value itself arrives intact in r2:r3, which is why it looks correct at the register level.

Fix

Remove the stray add r6, r6, #4. The restore path (add sp, sp, r6) is symmetric, so nothing else changes.

Testing

  • Assembles cleanly for cortex-m33 (arm-none-eabi-gcc -x assembler-with-cpp -mthumb -mcpu=cortex-m33).
  • Alignment verified against the prologue: the reservation is a multiple of 8, so SP is 0 mod 8 at blx.
  • Reproduced on a Cortex-M33 target: a native taking >4 packed words with an i64 vararg printed value * 2^32 before this change, correct value after.

invokeNative_thumb.s rounds the outgoing-stack reservation up to 8 bytes and
then unconditionally added another 4 ("+4 because odd(5) registers are in
stack"). But SP is already 8-byte aligned at that point: the prologue pushes
r4-r7 + lr (20 bytes) and does sub sp,bytecodealliance#4, i.e. 24 bytes total
(.cfi_def_cfa_offset 24). The reservation must therefore be a multiple of 8 and
nothing more; the extra word left SP at 4 mod 8 at the blx for any native
called with more than 4 packed argv words. AAPCS requires 8-byte stack
alignment at a public interface.

Effect: the compiler places an outgoing 8-byte argument (e.g. a vararg i64) at
a slot it models as 8-aligned but whose real address is 4 mod 8; the callee's
va_arg realigns to the true 8-byte boundary 4 bytes lower and reads the low
word paired with a zero, so a %lld in such a native printed value * 2^32. The
value itself arrives intact in r2:r3, which is why register-level inspection
looked clean.

Remove the stray add r6, r6, bytecodealliance#4; the reservation is already correctly rounded,
and the restore path (add sp, sp, r6) is symmetric, so nothing else changes.

Signed-off-by: Jonah Walker <supposedlysam@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SupposedlySam

Copy link
Copy Markdown
Author

The one red check — build_iwasm_on_nuttx (risc-v/qemu-rv/rv-virt/nsh64, …AOT) — failed in the runner's Docker pull (failed to register layer: … no space left on device), not in the build. It's unrelated to this change, which only touches the ARM Thumb invokeNative_thumb.s (the RISC-V build doesn't compile that file). A re-run should clear it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant