← All issues

Wasm Wide Arithmetic Proposal: 128-bit Operations Across All JIT Tiers

bcd47e7

JSTests/wasm/stress/wide-arithmetic.js

0xfc, 0x13, // i64.add128 (extOp 19)
0xfc, 0x14, // i64.sub128 (extOp 20)
0xfc, 0x15, // i64.mul_wide_s (extOp 21)
0xfc, 0x16, // i64.mul_wide_u (extOp 22)

This commit implements the WebAssembly wide-arithmetic proposal, adding four new instructions — i64.add128, i64.sub128, i64.mul_wide_s, i64.mul_wide_u — behind the --useWasmWideArithmetic flag. All three execution tiers (IPInt, BBQ, OMG) are supported with platform-specific lowering for x86_64 and ARM64.

Wasm i64.mul_wide_u (x86_64)

  Stack: [..., lhs, rhs]
         │
  BBQ:   consume rhs → any reg
         consume lhs → rax  (implicit mul source)
         emit: mul rhs_reg      ─► rdx:rax = lhs * rhs (unsigned)
         clobber eax, edx       ← explicit: between consume & allocate
         allocate result_lo ← rax
         allocate result_hi ← rdx
         push [result_hi, result_lo] onto value stack

  OMG:   patchpoint with constraints:
         input:  rax = lhs, any = rhs
         clobber: rdx
         output:  rax = lo, rdx = hi

Wide arithmetic is especially complex because 128-bit results don't map to a single register: add128/sub128 require carry-flag chaining (add+adc on x86_64; adds+adc on ARM64), and mul_wide uses the rdx:rax implicit output pair on x86_64, requiring eax/edx to be explicitly clobbered between the "consume" and "allocate" phases of BBQ's register allocator to avoid conflicts. A new offset parameter was added to BBQJIT::topValue() to let multi-result operations name their stack slots cleanly, which also affects the existing returnValuesFromCall path.

This is a substantial addition of new JIT code paths across every WebKit Wasm execution tier, with non-trivial register aliasing workarounds and multi-result stack slot management — exactly the kind of change where subtle bugs hide.

The explicit eax/edx clobbering workaround in BBQ's mul_wide lowering is the most delicate piece: if the clobber window is mis-ordered or the register allocator reuses those physical registers before the clobber marker, you get silent value corruption. The topValue() offset parameter change also touches returnValuesFromCall — existing multi-result call sites need verification. IPInt's carry-flag-dependent adcq/sbcq offlineasm macros must guarantee the flag state from the preceding addqs/subqs is preserved across any intervening ops.

🔒

New multi-tier JIT lowering with flag-dependent carry chains and register aliasing workarounds — several edge cases worth auditing.

Subscribe to read more