Files
sx/issues/0045-pack-fn-call-llvm-verifier-failure.md
agra c21b683b08 docs(issues): mark 17 already-fixed issues RESOLVED with verified banners
Each banner was re-verified against the current binary (repro now behaves
correctly) and cites the actual fix location in current src/** plus the covering
regression example. Closes the stale-but-fixed backlog: 0019, 0042-0056, 0131.
No compiler change.
2026-06-21 09:25:52 +03:00

138 lines
5.3 KiB
Markdown

**FIXED.** `lowerComptimeCall` now allocates a result slot when
> **RESOLVED.** A comptime/pack-fn with a non-void return type and a block body
> containing `return X;` previously emitted a `ret` in the middle of the caller's
> basic block (LLVM verifier: "Terminator found in the middle of a basic block").
> Fixed in `lowerComptimeCall` (src/ir/lower/comptime.zig:822-869): when the body
> has a `return`, it allocates a result slot + shared `ct.ret_done` block and sets
> `inline_return_target`, so `lowerReturn` (src/ir/lower/stmt.zig:442) stores into
> the slot and branches to `ret_done` instead of emitting an inline `ret`.
> Regression test: examples/0525-packs-pack-fn-comptime-return.sx (prints "42").
the body contains a `return` statement and reroutes
`lowerReturn` to store into it instead of emitting `ret` into the
caller's basic block. Regression test:
[examples/issue-0045.sx](../examples/issue-0045.sx).
# Symptom
Calling a fn declared with `..$args` (variadic heterogeneous type
pack, parser-accepted as of commit `a51fe26`) — even with zero
positional arguments — emits LLVM IR that fails verification:
```
LLVM verification failed: Terminator found in the middle of a basic block!
label %entry
```
No IR is printed by `sx ir`; the `sx run` JIT exits 1 immediately.
Expected: at minimum, the empty-pack call site should compile and
execute the fn body. Plan step 2 ("Runtime indexing + mono expansion")
specifies per-mono mangling and `..$args` expansion to N positional
IR params; until that lands, calling such a fn should at minimum
emit a clear "pack-fn calls not yet implemented" diagnostic rather
than corrupt IR.
# Reproduction
```sx
foo :: (..$args) -> i64 { return 42; }
main :: () -> i32 {
n : i64 = foo();
return 0;
}
```
```
$ ./zig-out/bin/sx run repro.sx
LLVM verification failed: Terminator found in the middle of a basic block!
label %entry
```
`foo()` with zero args, one arg (`foo(1)`), or multiple args
(`foo(1, "hello")`) all produce the same crash.
# Background
After M5.A.next.1b (commit `a51fe26`), `parseParams` accepts
`..$args` as a parameter declaration. The Param is recorded with
`is_variadic = true`, `is_comptime = true`, `type_expr = inferred_type`.
`parseFnDecl`'s `collectTypeParams` then registers `args` as a
type-param (because `is_comptime = true`), so `fd.type_params.len > 0`.
This routes the fn through the existing generic-fn path:
`lowerFnDecl` skips eager lowering, expecting calls to monomorphise
at first use. But the existing monomorphisation machinery binds a
single TypeId per `$T` name — it has no notion of a *pack* (a
variable-length list of TypeIds bound positionally). When the
call site tries to monomorphise with the call's args, the body's
`args` parameter gets resolved to a single (probably default `.i64`)
TypeId, but the call-site arg-packing path (`packVariadicCallArgs`)
treats it as a regular `..T` slice — the two views disagree and
the emitted IR is malformed.
The bug isn't in step 1's code itself; it's the gap between
"step 1 made the syntax parseable" and "step 2 hasn't made the
calls executable yet."
# Investigation prompt
For a fresh session picking this up:
Plan step 2 ("Runtime indexing + mono expansion") in
`~/.claude/plans/lets-see-options-for-merry-dijkstra.md` is the
intended fix:
1. Detect pack-fns at declaration: the fn has a trailing param
with `is_variadic && is_comptime` (no concrete type annotation
distinguishes it from a regular `args: ..T` variadic).
2. Per-call monomorphisation: bind `$args := [T1, ..., Tn]`
from the call site's concrete arg types. Each unique
`(arg-type-tuple, $ret)` combination gets its own mono.
3. Expand the pack into N positional IR params in the mono's
signature; mangling encodes the pack shape so distinct
monos get distinct symbols.
4. Body `args[$i]` at comptime-known `$i` lowers to the i-th
expanded param load (return type from `$args[$i]`).
Key files:
- `src/ir/lower.zig`:
- `lowerFnDecl` (around line 949 — generic skip) needs to keep
skipping pack-fns.
- `monomorphizeFunction` (line 7834) needs a pack-aware path
that binds `pack_bindings` (the field added in commit
`08feb60` for impl matching) instead of just `type_bindings`.
- `packVariadicCallArgs` (line 7275) should NOT run for pack
fns — args stay positional, not slice-packed.
- Index-expression lowering needs an `args[$i]` arm that reads
the i-th positional param.
- `src/ir/types.zig`: `FunctionInfo`/`ClosureInfo` have
`pack_start` already (added in commit `6582449`); the mono's
expanded signature should NOT carry `pack_start` (it's a
concrete shape).
Verification: the repro above compiles and prints "42" when run
as `./zig-out/bin/sx run repro.sx`. A new
`examples/156-pack-fn-mono.sx` (number depends on next free slot)
should be added per the FFI cadence rule (xfail-lock-in then
green).
Alternative interim option: if step 2 is too large to land in
one session, gate `parseFnDecl` to reject pack params with an
explicit "pack-fn body lowering not yet implemented; only impl
target types accept `..$args` today" diagnostic. Lets the
parser accept the syntax in impl headers (step 1's payoff) while
preventing the LLVM verifier crash. The diagnostic disappears
when step 2 lands.
# Verification
Once the fix is in:
```sh
./zig-out/bin/sx run examples/156-pack-fn-mono.sx
# Expected: prints "42"
```
Full suite + zig test must still pass.