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

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