fix(lower): drop dead statements after a return/raise terminator (issue 0061)

A bare `return X;` / `raise` in the middle of a block closed the current
LLVM basic block, but lowerBlock / lowerBlockValue only stopped the
statement loop on the `block_terminated` flag — which lowerReturn
deliberately never sets (it would leak past an `if cond { return }` merge
block). So trailing dead statements were emitted into the already-closed
block, tripping the LLVM verifier with "Terminator found in the middle of
a basic block".

Fix: also stop the statement loop when currentBlockHasTerminator() is
true. That is CFG-level termination of the *current* block, which is
naturally false at an if / inline-if merge block, so conditional returns
still fall through to their trailing statements.

This unblocks ERR E5.1: the canonical failable-closure form
`closure((x) -> (s32,!) { raise error.X; return x; })` has a dead
`return x;` after the unconditional raise and tripped the verifier.

Regression: examples/0038-basic-dead-code-after-terminator.sx.
This commit is contained in:
agra
2026-06-01 21:42:20 +03:00
parent b113e03fa3
commit 0e1afa3eba
6 changed files with 119 additions and 0 deletions

View File

@@ -1468,6 +1468,13 @@ pub const Lowering = struct {
for (blk.stmts) |stmt| {
if (self.block_terminated) break;
self.lowerStmt(stmt);
// A bare `return`/`raise` mid-block terminates the current
// basic block but deliberately does NOT set `block_terminated`
// (that flag would leak past an `if cond { return }` merge
// block, skipping its trailing statements — see lowerReturn).
// Stop here so dead statements after the terminator aren't
// emitted into an already-closed block (invalid LLVM IR).
if (self.currentBlockHasTerminator()) break;
}
},
else => {
@@ -1517,6 +1524,10 @@ pub const Lowering = struct {
for (blk.stmts[0 .. blk.stmts.len - 1]) |stmt| {
if (self.block_terminated) return null;
self.lowerStmt(stmt);
// A bare `return`/`raise` mid-block closes the current basic
// block (without setting `block_terminated`); the remaining
// statements — including the value-expr — are dead.
if (self.currentBlockHasTerminator()) return null;
}
if (self.block_terminated) return null;
// Last statement: if it's an expression, return its value