Closed Bug 2043013 Opened 3 months ago Closed 3 months ago

Differential behavior: wasm `switch` opcode traps Unimplemented instead of executing (stack-switching)

Categories

(Core :: JavaScript: WebAssembly, defect)

x86_64
Linux
defect

Tracking

()

RESOLVED INVALID

People

(Reporter: decoder, Unassigned)

References

(Blocks 1 open bug)

Details

(4 keywords)

Attachments

(2 files)

Summary

The wasm switch instruction (from the stack-switching / typed-continuations proposal) passes validation and the module compiles successfully, but SpiderMonkey's Ion wasm compiler has only a stub for it: FunctionCompiler::emitSwitch unconditionally emits Trap::Unimplemented after decoding the operands. Any module that dynamically executes switch raises WebAssembly.RuntimeError: unimplemented instruction executed, whereas V8 (d8 --experimental-wasm-wasmfx) correctly executes the opcode.

This is a behavioral divergence, not a memory-corruption bug (see memory-safety analysis below).

Root Cause

js/src/wasm/WasmIonCompile.cpp, FunctionCompiler::emitSwitch (line 9972):

bool FunctionCompiler::emitSwitch() {
  uint32_t contTypeIndex;
  uint32_t tagIndex;
  MDefinition* cont;
  DefVector args;
  HandlerExprVector handlers;
  if (!iter().readSwitch(&contTypeIndex, &tagIndex, &args, &cont)) {
    return false;
  }
  if (inDeadCode()) {
    return true;
  }
  // TODO: Not yet implemented.
  unimplementedTrap();          // <-- always emits Trap::Unimplemented
  return true;
}

The entire stack-switching opcode family was landed in Bug 2023217 with several Ion operations left as TODO stubs that call unimplementedTrap(): emitContBind (line 9623), emitResumeThrow (line 9934), emitResumeThrowRef (line 9953), emitSwitch (line 9972), and the (on $tag switch) handler kind inside emitResume (line 9787). The validator accepts these opcodes (so modules are well-formed and instantiable), but execution traps.

Reproduction

Tested on mozilla-central rev b754e06486cf (2026-05-21), fuzzing-asan-opt build.

# SpiderMonkey (Ion) -- traps
dist/bin/js --fuzzing-safe --setpref=wasm_stack_switching=true --wasm-compiler=optimizing poc.js
# => WebAssembly.RuntimeError: unimplemented instruction executed (exit 3)

# V8 -- executes the switch opcode
d8 --experimental-wasm-wasmfx poc.js
# => PASS (exit 0)

Differential evaluator result:

differs: true
message: Discrepancy: shell B succeeded but shell A failed (A=FAIL exit 3, B=OK exit 0)

Note: --setpref=wasm_stack_switching=true is required (defaults to false on all channels). Without it the module is rejected at compile time.

Two test artifacts are attached:

  • poc.js -- minimal module isolating emitSwitch specifically (primary)
  • poc_coroutine.js -- complete switch-based coroutine demonstrating V8 runs it end-to-end

Memory-Safety Analysis

The stub is memory-safe. Two non-obvious aspects were checked:

  1. Iterator/MIR desync: readSwitch does not mark the iterator unreachable, but unimplementedTrap() sets curBlock_ = nullptr. This "iterator live / MIR dead" state is absorbed by Ion's structured dead-code handling -- all MIR helpers guard on inDeadCode()/curBlock_, and dead defs cannot reach live MIR. Confirmed: the debug ASan build compiles all tested switch modules without assertions.

  2. Stale inlinedCallerOffsetsIndex_: The phantom-frame bug family in this file is unreachable via switch because cross-function inlining only runs in CompileMode::LazyTiering, while stack-switching requires the optimizing compiler to be force-selected -- the two prerequisites are mutually exclusive.

Suggested Fix

Implement switch in Ion (build the switch/landing-pad MIR analogously to emitResume/emitSuspend), or reject the instruction at validation time when the optimizing compiler cannot yet handle it. The same applies to the sibling stubs emitContBind, emitResumeThrow, emitResumeThrowRef, and the (on $tag switch) handler kind in emitResume.

Unimplemented means we haven't implemented it yet :) The normal stack-switching bug will track finishing the feature.

Status: NEW → RESOLVED
Closed: 3 months ago
Resolution: --- → INVALID

I think it's a duplicate of Implement switch

Group: javascript-core-security
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: