fix(ir): open a fresh defer window when lowering a lambda body (issue 0073)
A closure literal declared inside a `defer` body segfaulted the compiler. Root cause: lowerLambda never opened its own `func_defer_base` window. Every other function-lowering entry (lowerFunction / monomorphizeFunction / monomorphizePackFn) saves func_defer_base, sets it to defer_stack.items.len, and restores it — lowerLambda didn't. So a lambda's `return` drained the ENCLOSING function's defers; when the defer body itself declared the lambda, draining re-lowered the lambda, which returned, which drained again → infinite recursion → stack-overflow SIGSEGV (the failable variant surfaced one frame out, in expandCallDefaults→lookupFn reading a clobbered scope). Fix: lowerLambda now saves func_defer_base + the defer_stack length, sets the base to the current length (a fresh window), and restores both on exit — so a lambda's `return` drains only its own defers. Regression: examples/0310-closures-closure-literal-in-defer.sx — a closure declared and called inside a `defer`; verifies `body` then `defer closure: 42` at scope exit (exit 0). Issue 0073 marked RESOLVED; repro promoted from issues/0073-*.sx. zig build, zig build test, tests/run_examples.sh (358/0) all green.
This commit is contained in:
22
examples/0310-closures-closure-literal-in-defer.sx
Normal file
22
examples/0310-closures-closure-literal-in-defer.sx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Closure literal declared (and used) inside a `defer` body.
|
||||||
|
//
|
||||||
|
// Regression (issue 0073): this used to segfault lowering. A lambda inherited
|
||||||
|
// the enclosing function's `func_defer_base`, so the lambda's `return` re-drained
|
||||||
|
// the enclosing function's defers — and when the defer body itself declared the
|
||||||
|
// lambda, that re-lowered the lambda forever (infinite recursion). A lambda now
|
||||||
|
// opens its own defer window (like every other function-lowering entry).
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
run :: () {
|
||||||
|
defer {
|
||||||
|
cb := (n: s32) -> s32 { return n * 2; };
|
||||||
|
print("defer closure: {}\n", cb(21)); // 42, at scope exit
|
||||||
|
}
|
||||||
|
print("body\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
body
|
||||||
|
defer closure: 42
|
||||||
@@ -1,5 +1,18 @@
|
|||||||
# issue 0073 — closure literal inside a `defer` body segfaults the compiler
|
# issue 0073 — closure literal inside a `defer` body segfaults the compiler
|
||||||
|
|
||||||
|
> **✅ RESOLVED (2026-06-02).** Root cause: `lowerLambda` never opened its own
|
||||||
|
> `defer` window. Every other function-lowering entry (`lowerFunction`,
|
||||||
|
> `monomorphizeFunction`, `monomorphizePackFn`) saves `func_defer_base`, sets it
|
||||||
|
> to `defer_stack.items.len`, and restores it — but `lowerLambda` didn't, so a
|
||||||
|
> lambda's `return` drained the *enclosing* function's defers. When the defer
|
||||||
|
> body itself declared the lambda, draining re-lowered the lambda, which `return`ed,
|
||||||
|
> which drained again → infinite recursion → stack-overflow SIGSEGV.
|
||||||
|
> Fix: `lowerLambda` now opens a fresh defer window (save `func_defer_base` +
|
||||||
|
> `defer_stack` length, set base to the current length, restore both on exit) —
|
||||||
|
> `src/ir/lower.zig`. Regression test: `examples/0310-closures-closure-literal-in-defer.sx`
|
||||||
|
> (a closure declared + called inside a `defer`; verifies `body` then
|
||||||
|
> `defer closure: 42` at scope exit). Suite 358/0.
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
|
|
||||||
One-line: declaring a **closure literal inside a `defer` body** crashes the
|
One-line: declaring a **closure literal inside a `defer` body** crashes the
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
// Repro for issue 0073 — a closure literal declared inside a `defer` body
|
|
||||||
// segfaults the compiler during lowering. Minimal: the closure is non-failable,
|
|
||||||
// unused, and never called; merely declaring it in the defer body crashes.
|
|
||||||
//
|
|
||||||
// Expected: either lowers fine, or a clean diagnostic — NEVER a segfault.
|
|
||||||
// Observed: SIGSEGV in `lowerLambda` (src/ir/lower.zig) during `work`'s lowering.
|
|
||||||
|
|
||||||
#import "modules/std.sx";
|
|
||||||
|
|
||||||
work :: () {
|
|
||||||
defer { cb := () { return; }; }
|
|
||||||
}
|
|
||||||
|
|
||||||
main :: () -> s32 {
|
|
||||||
work();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -8313,6 +8313,19 @@ pub const Lowering = struct {
|
|||||||
defer self.current_ctx_ref = saved_ctx_ref_lam;
|
defer self.current_ctx_ref = saved_ctx_ref_lam;
|
||||||
if (lambda_wants_ctx) self.current_ctx_ref = Ref.fromIndex(0);
|
if (lambda_wants_ctx) self.current_ctx_ref = Ref.fromIndex(0);
|
||||||
|
|
||||||
|
// A lambda is its own function: its `return` must drain only ITS OWN
|
||||||
|
// `defer`s, not the enclosing function's. Open a fresh defer window
|
||||||
|
// (like `lowerFunction`/`monomorphizeFunction`) and restore on exit —
|
||||||
|
// otherwise lowering a closure literal inside a `defer` body re-enters
|
||||||
|
// the enclosing function's defer drain (infinite recursion — issue 0073).
|
||||||
|
const saved_func_defer_base = self.func_defer_base;
|
||||||
|
const saved_defer_len = self.defer_stack.items.len;
|
||||||
|
defer {
|
||||||
|
self.func_defer_base = saved_func_defer_base;
|
||||||
|
self.defer_stack.shrinkRetainingCapacity(saved_defer_len);
|
||||||
|
}
|
||||||
|
self.func_defer_base = saved_defer_len;
|
||||||
|
|
||||||
// Create entry block
|
// Create entry block
|
||||||
const entry_name = self.module.types.internString("entry");
|
const entry_name = self.module.types.internString("entry");
|
||||||
const entry = self.builder.appendBlock(entry_name, &.{});
|
const entry = self.builder.appendBlock(entry_name, &.{});
|
||||||
|
|||||||
Reference in New Issue
Block a user