How to Fix: Cranelift: unimplemented for > 64 bits

6 min read

Cranelift “unimplemented for > 64 bits”: why this test fails and how to fix it

This failure happens when a Cranelift pipeline reaches an operation, legalization path, or ABI-related lowering step that requires handling an integer or value type wider than 64 bits, but the selected backend path only implements support up to native machine widths. In practice, the bug often appears in .clif test cases targeting x86_64 when multi-value returns, aggregate lowering, stack passing, or legalization produces an i128 or another oversized value that the backend does not know how to lower.

Understanding the Root Cause

The key detail is that x86_64 is a 64-bit target, but intermediate representations can still contain values wider than 64 bits. Cranelift can represent such values in IR, yet not every backend stage supports them directly. The message “unimplemented for > 64 bits” usually means one of these things happened:

  • A legalization rule for an operation on i128 or a larger integer does not exist for the target.
  • An ABI lowering path attempted to pass or return a value larger than the backend currently supports in that form.
  • A test option such as enable_multi_ret_implicit_sret=true changed how values are returned, exposing an unsupported wide-value path.
  • An aggregate, vector, or bitcast was transformed into a wide scalar that exceeds the implemented lowering width.

In your test configuration, several flags matter:

  • target x86_64 means the legal native integer width is centered around 64-bit lowering behavior.
  • enable_multi_ret_implicit_sret=true can force certain return-value handling through an implicit structure-return convention, which may expose backend code paths for oversized values.
  • opt_level=none can preserve IR shapes that optimized pipelines might otherwise split, simplify, or rewrite before lowering.

So the root cause is not that Cranelift cannot represent the value at all. The problem is that the selected backend phase does not implement the required lowering strategy for values wider than 64 bits in that exact context.

Step-by-Step Solution

The correct fix depends on whether you are writing the test, debugging Cranelift, or patching the backend. Start by identifying which instruction or function signature introduces the oversized value.

1. Reduce the failing test to the smallest reproducer

Strip the .clif file down until only the failing function remains. Look for:

  • i128 parameters or returns
  • bitcast or concat/split-style transformations
  • multi-value returns
  • stack loads/stores involving aggregate-like wide values
test run

set enable_nan_canonicalization=true
set preserve_frame_pointers=true
set enable_multi_ret_implicit_sret=true
set opt_level=none

target x86_64 sse42 has_avx

function %problem() -> i128 {
block0:
    v0 = iconst.i128 1
    return v0
}

If a minimal case like this fails, you have confirmed that the unsupported path is directly tied to wide integer return lowering.

2. Check whether the failure is caused by a return, argument, or instruction legalization

Change one dimension at a time:

  • Replace an i128 return with two i64 returns.
  • Replace an i128 argument with two i64 arguments.
  • Replace wide arithmetic with explicit low/high-half operations.

For example, this rewrite often avoids the unsupported path:

function %problem_fixed() -> i64, i64 {
block0:
    lo = iconst.i64 1
    hi = iconst.i64 0
    return lo, hi
}

If this version works, the issue is specifically in single-value >64-bit ABI lowering, not general multi-value return support.

3. If you only need the test to pass, avoid unsupported wide scalar types

For many test scenarios, the simplest resolution is to express the same intent using multiple 64-bit values instead of one value wider than 64 bits.

; Instead of:
function %mul128(a: i128, b: i128) -> i128

; Use:
function %mul128_parts(a_lo: i64, a_hi: i64, b_lo: i64, b_hi: i64) -> i64, i64

This is the best approach if your goal is to validate surrounding logic rather than backend support for wide integers.

4. If you are fixing Cranelift itself, add or route legalization for >64-bit values

When the bug is in the compiler, the backend must either:

  • Legalize the wide value into smaller machine-supported pieces, or
  • Reject the IR earlier with a clearer verifier or frontend error, or
  • Lower the operation through helper sequences or runtime conventions such as explicit stack passing.

The typical implementation strategy is to split i128 into two i64 halves:

// Pseudocode for legalization strategy
if value_type.bits() > 64 {
    let lo = lower_half(value);
    let hi = upper_half(value);
    // Lower arithmetic, moves, args, or returns using lo/hi pairs.
}

For ABI handling, use explicit decomposition:

// Pseudocode
match abi_value_type {
    I128 => {
        assign_ret_reg_or_stack(lo_i64);
        assign_ret_reg_or_stack(hi_i64);
    }
    _ => lower_normally()
}

If the failure occurs in instruction selection rather than ABI code, add a legalization rule that rewrites the unsupported operation into 64-bit pieces before final lowering.

5. Re-run the test with and without implicit sret

Because your issue includes enable_multi_ret_implicit_sret=true, test both behaviors.

set enable_multi_ret_implicit_sret=true
set enable_multi_ret_implicit_sret=false

If only one configuration fails, the bug is likely inside the return convention lowering path rather than the core arithmetic or value representation.

6. Validate with a narrower equivalent test

Create a control case using i64 only. If the i64 version passes and the i128 version fails, your diagnosis is confirmed.

function %control() -> i64 {
block0:
    v0 = iconst.i64 1
    return v0
}

7. Write a regression test after the fix

Once you fix lowering or rewrite the test, preserve the behavior with a focused regression case. Keep the test minimal so future failures clearly identify unsupported >64-bit handling.

test run
set enable_multi_ret_implicit_sret=true
set opt_level=none
target x86_64

function %regression() -> i64, i64 {
block0:
    lo = iconst.i64 42
    hi = iconst.i64 0
    return lo, hi
}

Common Edge Cases

  • Bitcasts to oversized scalars: You may not see an explicit i128 in source form, but a transform can still create one internally.
  • ABI mismatch across platforms: A test might behave differently on another target where wide values are split differently or passed on the stack.
  • Multi-return interactions: Returning two i64 values may work while returning one i128 does not, even though both represent 128 bits overall.
  • Verifier passes but backend fails: IR validity does not guarantee backend support for every legal-looking type and operation combination.
  • Optimization-level differences: With opt_level=none, unsupported forms may survive longer. At higher optimization levels, they may be rewritten away, hiding the underlying bug.
  • Stack-slot lowering: Values larger than 64 bits may fail when moved between registers and stack slots if the move/assign code assumes a single machine word.

FAQ

Why does Cranelift allow i128 in IR if x86_64 cannot handle it directly?

Because IR expressiveness and backend lowering support are different concerns. The IR can model wide values, but the backend still needs explicit rules to split or lower them into machine-supported operations.

Is splitting i128 into two i64 values a real fix or just a workaround?

It is both, depending on your goal. For application code or tests, it is often a practical workaround. For Cranelift itself, the real fix is implementing proper legalization and ABI lowering so wide values are handled automatically.

Why does enable_multi_ret_implicit_sret make this issue more visible?

That setting changes how return values are represented and passed through the calling convention. If the backend has incomplete support for oversized values in the sret or multi-return path, the failure appears immediately.

The most reliable resolution is to either avoid single values wider than 64 bits in the test or implement backend splitting/legalization for those values. If you are preparing a patch for Cranelift, focus first on the exact phase that emits “unimplemented for > 64 bits”: instruction legalization, register/stack assignment, or ABI return lowering.

Leave a Reply

Your email address will not be published. Required fields are marked *