A value-position match's arms are now lowered with `target_type` set to
the merge's `result_type`, so positive and negated integer literals pick
the same width. Fixes the `PHI node operands are not the same type as the
result` failure for `if n == { case 0: 100; else: -1; }`-style returns.
Regression: examples/0043-basic-match-value-mixed-width.sx.
Gates: zig build, zig build test, run_examples.sh -> 345 passed.
67 lines
2.8 KiB
Markdown
67 lines
2.8 KiB
Markdown
# 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 with `target_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; `coerceToType` still 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
|
|
|
|
```sx
|
|
classify :: (n: s32) -> s32 {
|
|
if n == {
|
|
case 0: 100;
|
|
case 1: 10;
|
|
else: -1; // ← negated literal arm → phi width mismatch
|
|
}
|
|
}
|
|
main :: () -> s32 { classify(1) }
|
|
```
|
|
|
|
Expected: compiles, `classify(1)` returns 10. Actual: LLVM verification failure.
|
|
|
|
Workaround: give the arm an explicitly-typed value (`else: { x : s32 = -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` (s64 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.
|