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:
2026-05-12 03:11:13 +09:00
parent f267f99a2b
commit 404705b6fd
6 changed files with 113 additions and 0 deletions

View File

@@ -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