Fixes the regression locked in by 2a.C (commit 6b7a66b).
issue-0045's original fix set `block_terminated = true` after
each inline `return X;` to skip dead code in the inlined body.
But the flag leaked past structured control flow — an `if cond
{ return X; }` whose merge block continued to subsequent
statements would short-circuit the trailing code at the
`lowerBlockValue` loop's `if (self.block_terminated) return
null;` check.
Switched to the classical SSA "return-done block" shape:
- `InlineReturnInfo` carries a third field `done_bb: BlockId`
— a fresh basic block allocated by `lowerComptimeCall` per
comptime-call instance.
- `lowerReturn`'s inline path stores into the slot, drains
defers, and emits `br done_bb`. The basic block's terminator
is what carries the "no fall-through" signal; the
`block_terminated` flag is no longer touched.
- `lowerComptimeCall` allocates the slot + done_bb, lowers the
body, then switches to done_bb and loads the slot. Tail-
expression bodies that fall through (rare when has_return is
true) get a synthetic store + br so the CFG is well-formed.
For `if cond { return 42; }; return -1;`:
- cond=true: then's `return 42` stores 42, br done_bb. Merge
block has only the false predecessor, doesn't run the
trailing return. Load done_bb → 42.
- cond=false: condBr skips to merge. Merge runs `return -1;`
→ store -1, br done_bb. Load → -1.
`examples/157-pack-if-return.sx` flips from `8354116000` (the
uninitialised slot load on the false path) to `-1`. A
three-way `classify(..$args)` smoke confirms multi-path
inline-return works for any of the three branches.
Dead-code-after-return inside the inlined body still trips the
LLVM verifier (same shape as a regular `return X; print("dead");`
which also crashes today). Acceptable consistency — user code
shouldn't write unreachable code in either context.
196/196 example tests + `zig build test` green.
Follow-up to issue-0045's fix (commit 9e78790). The fix routes
inline-comptime-body `return X;` into a result slot but sets
`block_terminated = true` after the inline return — and that
flag leaks past the enclosing `if`'s merge block.
Body shape:
maybe :: (..$args) -> s64 {
if args.len > 0 { return 42; }
return -1;
}
For `maybe()` (zero call-args), the false-condition path skips
the then-branch's `return 42;` and should fall through to
`return -1;`. Today's flow:
- Then-branch's `return 42;` stores 42 to slot and sets
block_terminated = true.
- if lowering switches to merge_bb. block_terminated stays
true (never reset across the if/merge boundary).
- lowerBlockValue's loop sees block_terminated and returns
null without processing the trailing `return -1;`.
- lowerComptimeCall loads slot — slot was never written on
the false-condition path → garbage (8354116000 on this
machine; stable across runs).
`maybe(99)` works because the cond is true; the then-branch's
store wins.
Next commit reshapes the inline-return mechanism to use a
dedicated "return-done" basic block: each inline `return X;`
stores to slot and branches to ret_done; after the body
lowers, lowerComptimeCall switches to ret_done and loads. The
basic block CFG carries the control-flow termination — no
need for the leaking `block_terminated` flag.
196/196 example tests + `zig build test` green (the new test
captures the wrong value as the snapshot to flip).