diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 19464afc..b564f4e4 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6542,6 +6542,14 @@ fn cgstmt(c: *cgen, n: *node) void = { if (k == N_YIELD) { cgyield(c, n); return; }; + if (k == N_DEFER) { + if (c.defertop < DEFER_MAX) { + c.deferbuf[c.defertop] = n.lhs; + c.defertop += 1; + }; + return; + }; + c.lastwasreturn = 0; }; @@ -6569,7 +6577,19 @@ fn cgblock(c: *cgen, n: *node) void = { return; }; +// rundefers — emit cgexpr for every queued defer in LIFO order. +// Called from cgreturn and the cgfn implicit-return path. +fn rundefers(c: *cgen) void = { + let i: i32 = c.defertop - 1; + for (i >= 0) { + cgexpr(c, c.deferbuf[i]); + i -= 1; + }; + return; +}; + fn cgreturn(c: *cgen, n: *node) void = { + rundefers(c); let rhs: *node = n.lhs; if (rhs != nil) { // Tuple return `return a, b;`: @@ -7463,6 +7483,9 @@ fn cgfn(c: *cgen, fn_: *node) void = { if (fn_.body != nil) { cgstmt(c, fn_.body); }; if (c.lastwasreturn == 0) { + // Run any registered defers in LIFO order before the + // implicit return. + rundefers(c); // Zero AX before the fall-through return — matches C cgen, // which always emits this so void-returning fns don't leak // a stale callee value to their caller. @@ -7646,6 +7669,7 @@ type ffi = struct { }; def LOOP_MAX: i32 = 16; +def DEFER_MAX: i32 = 16; type cgen = struct { a: *arena, @@ -7668,6 +7692,8 @@ type cgen = struct { loopcontbuf: *str, // stack of cont labels for continue yieldtop: i32, yieldbuf: *str, // stack of match end labels for yield + defertop: i32, + deferbuf: **node, // stack of deferred exprs (LIFO at return) }; fn cgeninit(c: *cgen, a: *arena) void = { @@ -7684,6 +7710,8 @@ fn cgeninit(c: *cgen, a: *arena) void = { c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; c.yieldtop = 0; c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + c.defertop = 0; + c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node; }; // localalloc — append a slot for `name` without dedup. Used for diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 82fe3393..fe2387c6 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -145,6 +145,7 @@ type ffi = struct { }; def LOOP_MAX: i32 = 16; +def DEFER_MAX: i32 = 16; type cgen = struct { a: *arena, @@ -167,6 +168,8 @@ type cgen = struct { loopcontbuf: *str, // stack of cont labels for continue yieldtop: i32, yieldbuf: *str, // stack of match end labels for yield + defertop: i32, + deferbuf: **node, // stack of deferred exprs (LIFO at return) }; fn cgeninit(c: *cgen, a: *arena) void = { @@ -183,6 +186,8 @@ fn cgeninit(c: *cgen, a: *arena) void = { c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; c.yieldtop = 0; c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + c.defertop = 0; + c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node; }; // localalloc — append a slot for `name` without dedup. Used for diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 5631125e..5a4b3f13 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -258,6 +258,9 @@ fn cgfn(c: *cgen, fn_: *node) void = { if (fn_.body != nil) { cgstmt(c, fn_.body); }; if (c.lastwasreturn == 0) { + // Run any registered defers in LIFO order before the + // implicit return. + rundefers(c); // Zero AX before the fall-through return — matches C cgen, // which always emits this so void-returning fns don't leak // a stale callee value to their caller. diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 6a0db10e..a1ede22b 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -43,6 +43,14 @@ fn cgstmt(c: *cgen, n: *node) void = { if (k == N_YIELD) { cgyield(c, n); return; }; + if (k == N_DEFER) { + if (c.defertop < DEFER_MAX) { + c.deferbuf[c.defertop] = n.lhs; + c.defertop += 1; + }; + return; + }; + c.lastwasreturn = 0; }; @@ -70,7 +78,19 @@ fn cgblock(c: *cgen, n: *node) void = { return; }; +// rundefers — emit cgexpr for every queued defer in LIFO order. +// Called from cgreturn and the cgfn implicit-return path. +fn rundefers(c: *cgen) void = { + let i: i32 = c.defertop - 1; + for (i >= 0) { + cgexpr(c, c.deferbuf[i]); + i -= 1; + }; + return; +}; + fn cgreturn(c: *cgen, n: *node) void = { + rundefers(c); let rhs: *node = n.lhs; if (rhs != nil) { // Tuple return `return a, b;`: diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index d3280381..a051cd69 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6542,6 +6542,14 @@ fn cgstmt(c: *cgen, n: *node) void = { if (k == N_YIELD) { cgyield(c, n); return; }; + if (k == N_DEFER) { + if (c.defertop < DEFER_MAX) { + c.deferbuf[c.defertop] = n.lhs; + c.defertop += 1; + }; + return; + }; + c.lastwasreturn = 0; }; @@ -6569,7 +6577,19 @@ fn cgblock(c: *cgen, n: *node) void = { return; }; +// rundefers — emit cgexpr for every queued defer in LIFO order. +// Called from cgreturn and the cgfn implicit-return path. +fn rundefers(c: *cgen) void = { + let i: i32 = c.defertop - 1; + for (i >= 0) { + cgexpr(c, c.deferbuf[i]); + i -= 1; + }; + return; +}; + fn cgreturn(c: *cgen, n: *node) void = { + rundefers(c); let rhs: *node = n.lhs; if (rhs != nil) { // Tuple return `return a, b;`: @@ -7463,6 +7483,9 @@ fn cgfn(c: *cgen, fn_: *node) void = { if (fn_.body != nil) { cgstmt(c, fn_.body); }; if (c.lastwasreturn == 0) { + // Run any registered defers in LIFO order before the + // implicit return. + rundefers(c); // Zero AX before the fall-through return — matches C cgen, // which always emits this so void-returning fns don't leak // a stale callee value to their caller. @@ -7646,6 +7669,7 @@ type ffi = struct { }; def LOOP_MAX: i32 = 16; +def DEFER_MAX: i32 = 16; type cgen = struct { a: *arena, @@ -7668,6 +7692,8 @@ type cgen = struct { loopcontbuf: *str, // stack of cont labels for continue yieldtop: i32, yieldbuf: *str, // stack of match end labels for yield + defertop: i32, + deferbuf: **node, // stack of deferred exprs (LIFO at return) }; fn cgeninit(c: *cgen, a: *arena) void = { @@ -7684,6 +7710,8 @@ fn cgeninit(c: *cgen, a: *arena) void = { c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; c.yieldtop = 0; c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + c.defertop = 0; + c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node; }; // localalloc — append a slot for `name` without dedup. Used for diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 318963d0..22e50c5f 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -538,6 +538,35 @@ static const struct row rows[] = { " };\n" " return 0;\n" "};", 3 }, + /* defer runs queued exprs in LIFO order, before the return + * expression is evaluated. Each call appends a decimal digit + * to acc via *&acc — return reads the post-defer state. */ + { "fn rec(p: *i32, c: i32) i32 = {\n" + " *p = *p * 10 + c;\n" + " return 0;\n" + "};\n" + "fn main() i32 = {\n" + " let acc: i32 = 0;\n" + " defer rec(&acc, 1);\n" + " defer rec(&acc, 2);\n" + " defer rec(&acc, 3);\n" + " return acc;\n" + "};", 65 }, /* 321 mod 256 */ + /* defer also fires on an implicit fall-through return (void fn). */ + { "fn rec(p: *i32, c: i32) i32 = {\n" + " *p = *p * 10 + c;\n" + " return 0;\n" + "};\n" + "fn run(p: *i32) void = {\n" + " defer rec(p, 7);\n" + " defer rec(p, 8);\n" + " // no explicit return — implicit fall-through path\n" + "};\n" + "fn main() i32 = {\n" + " let acc: i32 = 0;\n" + " run(&acc);\n" + " return acc;\n" + "};", 87 }, /* defers fire 8, 7 → 8 then 87 */ /* yield from match-as-expression: each arm yields a value; * the match itself is bound to a let. */ { "fn pick(b: bool) (i32 | str) = {\n"