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
- Upgrade wasmtime — newer singlepass versions split large immediates into register-loaded constants
- Reduce the module's stack frame size (fewer/smaller locals, fewer spilled slots)
- Lower `StackListing`/slot pressure by splitting the function or reducing inlined code size
- Use the Cranelift backend (`Strategy::Cranelift`) which handles large immediates correctly
- 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
- On arm64/riscv targets prefer Cranelift over singlepass for large or generated modules
- Upgrade wasmtime before deploying singlepass — large-immediate handling improves over time
- Limit function size and local counts when targeting singlepass
- Compile off the main thread and catch_unwind so panics are recoverable
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
- Symbol {} in DWARF not recognized
- unsupported location
- duplicate function
- Can't form ExtraInfo with two pending canonicalization
- not yet implemented
AI-assisted analysis of wasmerio/wasmer@8c4b9ee9d3 (2026-09-01).
Data as JSON: /api/errors/7e93bbce0f972d4b.
Report an issue: GitHub.