Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.
Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).
Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.
zig build test: 426/426; examples suite: 595/595.
2.8 KiB
0066 — match-as-value with a negated-literal arm builds a mismatched phi
RESOLVED.
lowerMatch's value path (has_value_merge) now lowers each arm body withtarget_type = result_type, so literals and negated literals in the arms pick the merge's width instead of leaking a narrower one. The phi operands are uniform;coerceToTypestill runs afterward as a backstop. Regression:examples/0043-basic-match-value-mixed-width.sx.
Symptom
A value-position match (the if subject == { case ... } sugar) returning a
small integer type, where one arm is a negated integer literal (-1) and
others are plain positive literals, fails LLVM verification:
LLVM verification failed: PHI node operands are not the same type as the result!
%bp = phi i64 [ 100, %match.arm.1 ], [ 10, %match.arm.2 ], [ -1, %match.arm.3 ]
The negated arm lowers its value at a different integer width (it emits an i32
that is then sign-extended) than the positive-literal arms (i64), so the merge
phi's operands disagree with its result type. Positive literals in every arm
work; if/else with -1 works. So it is specific to a negated literal in a
match arm value.
This is orthogonal to the trailing-; block-value rule — the repro uses the
exempt case …: expr; arm form (unchanged by that migration) and reproduces
with ordinary semicolon-terminated arms. Filed while writing the block-value
regression example (0040); the example sidesteps it with positive arm values.
Reproduction
classify :: (n: i32) -> i32 {
if n == {
case 0: 100;
case 1: 10;
else: -1; // ← negated literal arm → phi width mismatch
}
}
main :: () -> i32 { classify(1) }
Expected: compiles, classify(1) returns 10. Actual: LLVM verification failure.
Workaround: give the arm an explicitly-typed value (else: { x : i32 = -1; x })
or avoid the negation.
Investigation prompt
Look at the match-as-value lowering in src/ir/lower.zig (lowerMatch, the
has_value_merge path ~line 4500, and the arm result_type inference
~line 11698). The arm value is lowered via lowerBlockValue(arm.body) then
coerceToType(v, v_ty, result_type). For a negated-literal arm the value's
type comes out narrower than result_type (i64 here), and the coercion path
that should widen it before the br merge_bb, {v} either runs against the wrong
target or is skipped — so the phi gets an i32 operand under an i64 result. Make
the arm value coerce to the merge's result_type consistently (mirror how the
positive-literal arms are handled), or set target_type = result_type while
lowering each arm body so the negate picks the right width. Verify with the
repro above (expect exit 10) and add a regression example.
Status
OPEN. Pre-existing match-value codegen quirk, surfaced (not caused) by the block-value work.