wasmerio/wasmer · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

`emit_add` in singlepass's ARM64 emitter panics with `unreachable!()` when asked to add an `Imm32` immediate of 0x1000 (4096) or larger. ARM64 `add` encoded via this path only supports small immediates; larger constants must be loaded into a register first. The emitter asserts the invariant instead of materializing the constant, so any caller that hands it a big immediate crashes the compiler.

Source

Thrown at lib/compiler-singlepass/src/emitter_arm64.rs:1197

        src1: Location,
        src2: Location,
        dst: Location,
    ) -> Result<(), CompileError> {
        match (sz, src1, src2, dst) {
            (Size::S64, Location::GPR(src1), Location::GPR(src2), Location::GPR(dst)) => {
                dynasm!(self ; add XSP(dst), XSP(src1), X(src2), UXTX);
            }
            (Size::S32, Location::GPR(src1), Location::GPR(src2), Location::GPR(dst)) => {
                dynasm!(self ; add WSP(dst), WSP(src1), W(src2), UXTX);
            }
            (Size::S64, Location::GPR(src1), Location::Imm8(imm), Location::GPR(dst))
            | (Size::S64, Location::Imm8(imm), Location::GPR(src1), Location::GPR(dst)) => {
                dynasm!(self ; add XSP(dst), XSP(src1), imm as _);
            }
            (Size::S64, Location::GPR(src1), Location::Imm32(imm), Location::GPR(dst))
            | (Size::S64, Location::Imm32(imm), Location::GPR(src1), Location::GPR(dst)) => {
                if imm >= 0x1000 {
                    unreachable!();
                }
                dynasm!(self ; add XSP(dst), XSP(src1), imm);
            }
            (Size::S64, Location::GPR(src1), Location::Imm64(imm), Location::GPR(dst))
            | (Size::S64, Location::Imm64(imm), Location::GPR(src1), Location::GPR(dst)) => {
                if imm >= 0x1000 {
                    unreachable!();
                }
                let imm = imm as u32;
                dynasm!(self ; add XSP(dst), XSP(src1), imm);
            }
            (Size::S32, Location::GPR(src1), Location::Imm8(imm), Location::GPR(dst))
            | (Size::S32, Location::Imm8(imm), Location::GPR(src1), Location::GPR(dst)) => {
                dynasm!(self ; add WSP(dst), WSP(src1), imm as u32);
            }
            (Size::S32, Location::GPR(src1), Location::Imm32(imm), Location::GPR(dst))
            | (Size::S32, Location::Imm32(imm), Location::GPR(src1), Location::GPR(dst)) => {
                if imm >= 0x1000 {

View on GitHub (pinned to 8c4b9ee9d3)

Solutions

  1. Upgrade wasmtime — newer singlepass versions split large immediates into register-loaded constants
  2. Reduce the module's stack frame size (fewer/smaller locals, fewer spilled slots)
  3. Lower `StackListing`/slot pressure by splitting the function or reducing inlined code size
  4. Use the Cranelift backend (`Strategy::Cranelift`) which handles large immediates correctly
  5. File an upstream issue with the module; this is a compiler limitation hit at an internal boundary

Example fix

// before
config.strategy(Strategy::Singlepass);
// after
config.strategy(Strategy::Cranelift); // handles imm >= 0x1000 in add/sub
Defensive patterns

Strategy: validation

Validate before calling

// keep frames small enough that SP adjustments stay below the 4096 immediate limit
const MAX_FRAME: i64 = 0x1000;
// estimate from module locals/params before choosing singlepass on arm64
if estimated_frame_size(bytes) >= MAX_FRAME {
    config.strategy(wasmtime::Strategy::Cranelift);
}

Type guard

fn imm_fits_encoding(imm: i64) -> bool { (0..0x1000).contains(&imm) }

Try / catch

let module = std::panic::catch_unwind(|| Module::new(&engine, &bytes).map_err(|e| anyhow::anyhow!(e)))
    .map_err(|_| anyhow::anyhow!("singlepass emit_add immediate panic"))??; // then retry with Cranelift

Prevention

When it happens

Trigger: Any singlepass codegen path (stack adjust `emit_pop`, trampolines like `gen_std_trampoline_*`, `gen_import_call_trampoline_*`, dynamic import trampolines) that performs `emit_add` with a GPR destination and an `Imm32` operand >= 0x1000 — e.g. frame/stack adjustments or trampoline offsets larger than 4096 bytes.

Common situations: Modules with very large stack frames (big locals/slots) exceeding the 4096-byte immediate window when using `Strategy::Singlepass` on arm64/riscv; deep recursion setups with oversized configured stacks causing large SP adjustments.

Related errors


AI-assisted analysis of wasmerio/wasmer@8c4b9ee9d3 (2026-09-01). Data as JSON: /api/errors/7e93bbce0f972d4b. Report an issue: GitHub.