How to Fix: Cranelift: s390x: regalloc checker error UnknownValueInAllocation

7 min read

Cranelift s390x regalloc checker error: fixing UnknownValueInAllocation in the 0.91 transition

This failure usually points to one thing: the register allocator and the checker no longer agree about where a value lives after instruction lowering on s390x. When compiling the Rust standard library with the upcoming Cranelift 0.91 release, that disagreement can surface as UnknownValueInAllocation, which means a value expected by the checker was never given a valid tracked location in the final allocation state.

Symptoms and reproduction context

The issue appears when building code paths large enough to stress lowering, liveness, and register allocation on the IBM Z backend. A standard library build is a perfect trigger because it generates many functions with spills, rematerialization opportunities, ABI-sensitive arguments, and multi-block control flow.

The checker error typically means one of these backend invariants was broken:

  • A lowered instruction uses a value not declared in its operand constraints.
  • A backend-generated move, spill, reload, or tied operand fails to preserve the value mapping.
  • A s390x-specific instruction encoding or pseudo-instruction clobbers a register not modeled in regalloc.
  • A value crosses a block edge or safepoint-like boundary and the checker cannot find its assigned location.

Understanding the Root Cause

UnknownValueInAllocation is not usually the first bug. It is the checker catching a mismatch that happened earlier in the pipeline.

In Cranelift, the flow is broadly:

  1. Cranelift IR defines values and their uses.
  2. The backend lowers IR into machine-level instructions.
  3. regalloc2 computes physical register and stack-slot assignments.
  4. The checker validates that every use and def is consistent with those assignments.

On s390x, this class of issue commonly appears when lowering introduces machine instructions with backend-specific constraints that are not fully reflected to regalloc. For example:

  • A value is expected in a fixed register, but the instruction definition does not declare that requirement.
  • A temporary register is used as if it still contains an SSA value, but regalloc only knows it as a scratch register.
  • A multi-result or reused register operand is modeled incorrectly, so the checker sees a use of a value that has no current allocation.
  • A spill or reload sequence around branches, calls, or flag-producing instructions loses the logical mapping between the original value and the physical location.

Why this is especially visible during the 0.91 upgrade: allocator and checker behavior tends to get stricter over time. A backend bug that passed accidentally in an earlier release may become visible once validation improves. In other words, the upgrade often reveals a latent backend inconsistency rather than creating one from scratch.

For s390x, the highest-probability root cause is a lowering rule or instruction definition that uses an implicit register or temporary without declaring it, causing the checker to encounter a value it cannot trace in the final allocation map.

Step-by-Step Solution

The most reliable fix is to isolate the exact instruction or lowering rule that breaks the allocation invariant, then correct the operand, clobber, or fixed-register modeling.

1. Build with backend diagnostics enabled

First, rebuild with logs or debug output that show the final lowered instructions and register allocation state. If you are working inside Wasmtime or cg_clif, keep a debug build and enable verbose backend dumps where available.

cargo build -p cranelift-codegen --features trace-log

If your integration supports environment-based logging, run the failing compilation with backend logs enabled:

RUST_LOG=cranelift_codegen=trace cargo build -Zbuild-std --target s390x-unknown-linux-gnu

The exact logging knobs vary by integration, but the goal is always the same: capture the function just before or during register allocation.

2. Reduce to a single failing function

The issue description mentions the standard library build, but fixing this class of bug is much faster with a single function reproducer. Dump the problematic function if your toolchain exposes IR or VCode output. If available, use flags that emit Cranelift IR and machine lowering artifacts.

cargo rustc --target s390x-unknown-linux-gnu -- --emit=mir

Then search logs for the first function that triggers UnknownValueInAllocation. Once you have that function, inspect:

  • Its block parameters
  • Any call sequences
  • Any integer extension, bitcast, or multi-value lowering
  • Backend-generated moves around branches

3. Inspect the instruction at the checker failure

Look for an instruction where the checker reports a use but cannot find the value allocation. Typical red flags include:

  • Fixed registers on input or output
  • Implicit scratch registers
  • Operand reuse constraints
  • Late clobbers or call clobbers

If your backend has instruction definitions similar to pseudo-code like this:

inst my_op(a: GPR) -> GPR uses r1 temp r0

but only a is declared to regalloc while r1 or r0 are assumed implicitly, the checker may fail because the allocator was never told that the value must flow through those registers.

4. Fix operand and clobber modeling in s390x lowering

The backend fix usually falls into one of these patterns:

  • Declare fixed-register operands explicitly.
  • Add missing clobber information for scratch or call-like instructions.
  • Use a real temporary allocated through the backend interface instead of assuming an implicit live value in a hard-coded register.
  • Split complex lowering into smaller instructions so value flow is explicit.

Conceptually, the wrong code often looks like this:

// Problematic lowering pattern: assumes tmp already carries a tracked value.let tmp = regs::r1;emit(Inst::Foo { src, tmp, dst });

And the corrected version looks more like this:

// Correct pattern: model tmp as an explicit temp or fixed-reg operand.let tmp = alloc_tmp_gpr();emit(Inst::Move { src, dst: tmp });emit(Inst::Foo { src: tmp, dst, clobbers: [...] });

If the instruction truly requires a hardware-fixed register, model that requirement in the instruction constraints rather than relying on backend convention.

5. Verify branch and block-parameter handling

Another frequent source is block-edge value transfer. On s390x, if a branch lowering sequence rewrites operands or inserts moves, ensure the destination block parameter mapping remains valid.

// Verify that each branch argument is either:// 1. in the expected register, or// 2. moved to the expected stack/register location before the edge.

If a value is consumed after a branch move sequence but before the checker sees a matching assignment, you can get UnknownValueInAllocation.

6. Re-run with checker validation

After patching the lowering or instruction definition, re-run the original failing build and confirm both the minimal reproducer and the std build pass.

cargo test -p cranelift-codegen s390x -- --nocapture
cargo build -Zbuild-std --target s390x-unknown-linux-gnu

If available in your environment, keep regalloc checker validation enabled during testing. The checker is the fastest way to catch backend invariant regressions before they become silent miscompiles.

7. Add a regression test

Once fixed, add the smallest possible backend regression test that exercises the exact lowering shape. Good candidates include:

  • A function with a call and integer arguments in fixed registers
  • A branch with block parameters and a spill/reload around the edge
  • A select-like or compare-and-branch pattern that uses backend temporaries
; Pseudo-test idea for a backend regressionfunction %f(i64, i64) -> i64 {block0(v0: i64, v1: i64):    v2 = iadd v0, v1    brif v2, block1(v2), block2(v1)block1(v3: i64):    return v3block2(v4: i64):    return v4}

The key is to preserve the instruction pattern that originally confused allocation tracking.

Common Edge Cases

  • Call ABI mismatches: s390x calling convention rules may require specific argument or return registers. If instruction lowering does not mirror ABI constraints exactly, values can disappear from the checker’s view.
  • Implicit flags or condition-code dependencies: if a branch or compare relies on condition state that is not modeled correctly, backend rewrites may insert moves or reorder operations unexpectedly.
  • Two-address instruction forms: if an output reuses an input register but the constraint is not declared, regalloc may assign separate locations while lowering assumes aliasing.
  • Spill-slot only values: some values may legally live on the stack after pressure increases. If a lowered instruction assumes a register-only source without forcing a reload, the checker may report an allocation mismatch.
  • Debug versus release differences: optimized builds often expose different register pressure and instruction selection paths, so a bug may appear only under std compilation or only with certain optimization levels.
  • Multi-block phis/block params: block parameters are a common place for hidden mismatches because the backend may need explicit edge moves that do not exist in the original SSA form.

FAQ

Is UnknownValueInAllocation a regalloc2 bug or an s390x backend bug?

Usually it is a backend modeling bug, not the allocator itself. The checker is reporting that an instruction or value flow was not described consistently enough for allocation validation.

Why does this show up when compiling the Rust standard library but not small examples?

The standard library creates more register pressure, deeper control flow, more calls, and more opportunities for spills and fixed-register constraints. Small tests may never exercise the broken lowering path.

What is the fastest path to a real fix?

Reduce the failure to a single function, dump the lowered machine instructions, find the first instruction where a value is used without a valid tracked location, and then fix the missing operand, temp, clobber, or fixed-register constraint in the s390x backend.

The practical takeaway is simple: UnknownValueInAllocation means the checker lost the value because the backend failed to describe it precisely. On s390x in the 0.91 upgrade window, the right fix is almost always in lowering or instruction constraints, not in the Rust code being compiled.

Leave a Reply

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