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
|
||||
|
||||
Reference in New Issue
Block a user