wasmerio/wasmer · error

internal error: entered unreachable code

Error message

internal error: entered unreachable code

What it means

A bare `unreachable!()` panic in the signed-division/remainder lowering of `translate_integer_arithmetic_operator` (lib/compiler-llvm/src/translator/code.rs). The code builds i32::MIN / -1 and i64::MIN / -1 overflow-trap constants and asserts the operand's LLVM int type is exactly i32 or i64; any other type (i8/i16/i128/floating) hitting the srem/sdiv path violates the translator's invariant and aborts with "internal error: entered unreachable code".

Source

Thrown at lib/compiler-llvm/src/translator/code.rs:4962

                let res = err!(self.builder.build_int_unsigned_div(v1, v2, ""));
                self.state.push1(res);
            }
            Operator::I32RemS | Operator::I64RemS => {
                let ((v1, i1), (v2, i2)) = self.state.pop2_extra()?;
                let v1 = self.apply_pending_canonicalization(v1, i1)?;
                let v2 = self.apply_pending_canonicalization(v2, i2)?;
                let (v1, v2) = (v1.into_int_value(), v2.into_int_value());
                let int_type = v1.get_type();
                let (min_value, neg_one_value) = if int_type == self.intrinsics.i32_ty {
                    let min_value = int_type.const_int(i32::MIN as u64, false);
                    let neg_one_value = int_type.const_int(-1i32 as u32 as u64, false);
                    (min_value, neg_one_value)
                } else if int_type == self.intrinsics.i64_ty {
                    let min_value = int_type.const_int(i64::MIN as u64, false);
                    let neg_one_value = int_type.const_int(-1i64 as u64, false);
                    (min_value, neg_one_value)
                } else {
                    unreachable!()
                };

                self.trap_if_zero(v2)?;

                // "Overflow also leads to undefined behavior; this is a rare
                // case, but can occur, for example, by taking the remainder of
                // a 32-bit division of -2147483648 by -1. (The remainder
                // doesn’t actually overflow, but this rule lets srem be
                // implemented using instructions that return both the result
                // of the division and the remainder.)"
                //   -- https://llvm.org/docs/LangRef.html#srem-instruction
                //
                // In Wasm, the i32.rem_s i32.const -2147483648 i32.const -1 is
                // i32.const 0. We implement this by swapping out the left value
                // for 0 in this case.
                let will_overflow = err!(self.builder.build_and(
                    err!(self.builder.build_int_compare(
                        IntPredicate::EQ,

View on GitHub (pinned to 8c4b9ee9d3)

Solutions

  1. Use an official released wasmtime version that matches the feature set of your modules (enable the `wide-arithmetic` Cargo feature or upgrade, rather than patching the div/rem path ad hoc).
  2. If you modified the translator, ensure new integer types (i128) are handled before this arm, e.g. lower wide div/rem in the I64Add128/I64MulWide-style arms instead.
  3. Rebuild cleanly with all wasmtime crates at one version to rule out enum/type mismatches.
  4. Report a minimal .wasm repro to the Wasmtime maintainers if it occurs on an unmodified release.

Example fix

// before
let (min_value, neg_one_value) = if int_type == self.intrinsics.i32_ty {
    (...)
} else if int_type == self.intrinsics.i64_ty {
    (...)
} else {
    unreachable!()
};
// after: handle i128 explicitly (or reject it earlier with a proper error)
} else if int_type == self.intrinsics.i128_ty {
    let min_value = int_type.const_int(i128::MIN as u128, false);
    let neg_one_value = int_type.const_int((-1i128) as u128, false);
    (min_value, neg_one_value)
} else {
    unreachable!()
};
Defensive patterns

Strategy: validation

Validate before calling

// Ensure only integer types the translator expects reach div/rem lowering;
// at the API level, validate the module and reject experimental numeric opcodes.
let features = wasmparser::WasmFeatures::default(); // no wide-arithmetic on stock releases
wasmparser::Validator::new_with_features(features)
    .validate_all(&wasm_bytes)
    .map_err(|e| CompileError::InvalidModule(e))?;

Type guard

fn is_compiler_panic(err: &anyhow::Error) -> bool {
    err.to_string().contains("entered unreachable code")
}

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| Module::new(&engine, &wasm_bytes)));
result.map_err(|p| CompilerCrash::from_panic(p))?

Prevention

When it happens

Trigger: Compiling a Wasm `i32.rem_s`/`i64.rem_s`-style operator (div/rem with overflow trap) when the value on the stack has an unexpected LLVM integer type — only possible through an internal bug: a new numeric proposal (e.g. wide-arithmetic i128) routed into the scalar div/rem path, or a refactoring that changed the type of the pushed operand.

Common situations: Running a nightly/fork of wasmtime with partial wide-arithmetic support and a module that does `i128`-wide arithmetic; fuzzing the compiler with hand-built modules; mixing incompatible wasmtime subcrate versions so operator-to-type assumptions drift.

Related errors


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