How to Fix: Cranelift: hit unreachable case while lowering `fcvt_to_uint`
Cranelift crash on fcvt_to_uint: why the lowering hits an unreachable case and how to fix it
This bug shows up when Cranelift tries to lower a floating-point-to-unsigned-integer conversion for a SIMD vector shape it does not actually support in that backend path. Instead of rejecting the IR cleanly or expanding it to a legal sequence, the lowering code falls through to an unreachable arm and panics.
Table of Contents
If your .clif test includes something like converting f32x4 lanes into i32x4 with unsigned semantics, the failure usually means one thing: the legalization or ISA lowering rules do not cover that exact opcode/type combination.
Understanding the Root Cause
The issue centers on fcvt_to_uint, which represents a conversion from floating-point values to unsigned integer values. In scalar form, many backends can lower this directly or emulate it. In vector form such as f32x4 -> i32x4, support depends on:
- the target ISA,
- the available SIMD instructions,
- Cranelift’s legalization rules, and
- whether the backend implements a fallback expansion for unsupported lane-wise unsigned conversion.
The panic happens because the lowering path assumes the type reaching it has already been legalized into a supported form. When that assumption is false, a match arm or backend-specific lowering helper receives an unhandled vector conversion and triggers an unreachable case.
In practical terms, the failure usually comes from one of these conditions:
- Missing legalization:
fcvt_to_uintonf32x4is allowed into lowering without being transformed into a legal sequence. - Backend mismatch: the selected backend supports scalar conversion but not the vector variant.
- Incomplete pattern coverage: integer lane width or vector width combinations like
x4are not covered in the lowering implementation. - Unsigned conversion complexity: converting float to unsigned int is trickier than signed conversion because values above
INT_MAXneed special handling, saturation, masking, or bias/subtract tricks depending on the backend.
That last point matters. A signed conversion path may exist for fcvt_to_sint, while fcvt_to_uint still needs custom expansion. If the backend authors only implemented one path, the other can reach a panic when vector IR is presented.
Step-by-Step Solution
The correct fix is to ensure unsupported vector unsigned float conversions are legalized before final lowering, or to add explicit backend lowering support for the missing type combination.
Use the following workflow to diagnose and fix the issue.
1. Reproduce with a minimal .clif test
Reduce the test case so the failure is isolated to the conversion.
function %f13(v0: f32x4) -> i32x4 system_v {
block0(v0: f32x4):
v1 = fcvt_to_uint.i32x4 v0
return v1
}
Run the Cranelift test command used in your workspace so you can verify the panic consistently.
2. Confirm whether the type is legal for the target ISA
Inspect the backend lowering and legalization code for handling of:
fcvt_to_uintf32x4i32x4- the active target such as x86-64, AArch64, or another ISA
You are looking for one of two outcomes:
- a direct lowering rule exists for this vector conversion, or
- the instruction should have been expanded before reaching lowering.
3. Add a legalization rule if lowering support is missing
If the backend cannot lower fcvt_to_uint for vectors directly, add a legalization step that rewrites it into a supported sequence. The exact implementation depends on Cranelift internals, but the logic typically follows one of these patterns:
- Split the vector into scalar lanes, convert lane-by-lane, and rebuild the vector.
- Lower to a backend-supported signed conversion trick if the ISA has a known unsigned emulation sequence.
- Reject invalid type combinations earlier with a verifier or legalization error instead of panicking.
Pseudocode for a legalization-oriented fix:
match opcode {
Opcode::FcvtToUint => {
match (input_type, output_type) {
(F32X4, I32X4) => {
// Option A: expand into per-lane scalar conversions
// Option B: lower via backend-specific unsigned conversion sequence
return expand_fcvt_to_uint_vector(inst);
}
_ => {
// existing legal cases
}
}
}
_ => {}
}
4. If the backend should support it, implement the missing lowering arm
When the target ISA has a valid instruction sequence, add it explicitly instead of relying on a fallback that does not exist. For example, update the lowering code so it handles the vector type rather than hitting unreachable!().
fn lower_fcvt_to_uint(ctx: &mut LowerCtx, ty_in: Type, ty_out: Type, val: Value) -> Value {
match (ty_in, ty_out) {
(types::F32X4, types::I32X4) => {
return lower_fcvt_to_uint_f32x4_i32x4(ctx, val);
}
_ => {
panic!("unexpected type combination should have been legalized earlier");
}
}
}
In a mature fix, replace panic-based assumptions with either:
- a proper legalization fallback, or
- a compile-time error path with a precise message.
5. Add regression tests
This issue needs a test that proves Cranelift no longer reaches the unreachable case.
function %fcvt_to_uint_vec(v0: f32x4) -> i32x4 {
block0(v0: f32x4):
v1 = fcvt_to_uint.i32x4 v0
return v1
}
Add neighboring tests too:
f32x2 -> i32x2f64x2 -> i64x2if relevant- scalar
f32 -> i32to verify the existing path still works - negative, NaN, and out-of-range inputs where semantics matter
6. Validate semantics, not just crash behavior
Unsigned conversion has subtle behavior around NaN, negative values, and overflow. After the panic is fixed, verify the result matches Cranelift’s intended semantics for trapping, saturating, or implementation-defined lowering behavior.
// Example test ideas
// 0.0 -> 0
// 1.0 -> 1
// -1.0 -> behavior per IR semantics
// 4294967295.0 -> max u32 if representable by the chosen semantics
// NaN -> behavior per IR semantics
7. Replace unreachable assumptions with defensive handling
Even if you implement support, this class of bug is easier to maintain when impossible states are verified earlier. A good long-term patch often includes:
- type assertions in legalization,
- clear verifier diagnostics, and
- graceful fallback expansion instead of backend panic paths.
That prevents future SIMD opcode additions from silently reaching unsupported lowering code.
Common Edge Cases
- NaN inputs: some conversion paths treat NaN specially; make sure your legalized sequence preserves the intended behavior.
- Negative floating-point lanes: unsigned conversion from negative values can expose differences between scalar and vector fallback logic.
- Large positive values: values larger than the destination unsigned integer range may overflow differently depending on the ISA or legalization strategy.
- Different SIMD widths: fixing
f32x4 -> i32x4does not automatically fixf32x8,f64x2, or narrower vector forms. - Backend-specific support gaps: x86 and AArch64 may require different lowering sequences even for the same Cranelift IR opcode.
- Signed vs unsigned confusion: a backend may support
fcvt_to_sintbut notfcvt_to_uint. Do not assume one implies the other. - Verifier blind spots: if verification allows the IR but legalization cannot handle it, the crash may simply move later in the pipeline.
FAQ
Why does fcvt_to_sint work while fcvt_to_uint crashes?
Because unsigned float-to-int conversion usually needs extra lowering logic. Many backends have a native or simpler path for signed conversion, while the unsigned vector form requires a custom expansion that may be missing.
Is this a verifier bug or a lowering bug?
Most often it is primarily a lowering/legalization bug. If the IR opcode and types are accepted, the pipeline should either legalize them or reject them cleanly. Hitting unreachable means an internal assumption was violated.
What is the safest fix for maintainability?
The safest fix is to add a legalization rule for unsupported vector fcvt_to_uint forms and keep backend lowering limited to known-legal cases. That makes unsupported combinations explicit and reduces the chance of future panic paths.
The key takeaway is simple: do not let vector fcvt_to_uint reach backend lowering unless that exact type combination is known to be legal. Either lower it properly, expand it during legalization, or reject it with a precise diagnostic. That change fixes the immediate crash and hardens Cranelift against similar SIMD lowering bugs.