selfhost: mirror defer; e2e tests for LIFO ordering
C cgen has carried defer for a while (defers[] global + reverse walk on every return). Selfhost cgen now mirrors: - cgen struct: deferbuf (**node, LIFO stack) + defertop counter. - cgstmt N_DEFER: push n.lhs. - cgreturn: rundefers() at entry — same as the C cgen pattern. - cgfn fall-through return: rundefers() before zero-AX+RET. DEFER_MAX = 16 matches C cgen. Two new e2e rows: defer with an explicit `return acc;` (321 mod 256 = 65), and defer firing on an implicit void-fn fall-through (87). Both rows verified via the wwstage cgen too. Defer's semantics: queued exprs fire LIFO before the return expr is evaluated, so a return that reads memory mutated by a deferred call sees the post-defer state. Matches C cgen and Hare.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;`:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user