From ce5d66e18a634971b84d02daa07836d2a368ce5f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 16:04:17 +0900 Subject: [PATCH] =?UTF-8?q?selfhost:=20port=20append=20+=20spread=20?= =?UTF-8?q?=E2=80=94=20N=5FSPREAD=20parser=20+=20rt=5Fensure=20builtin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ww/parse/expr.ww | 8 ++ selfhost/cmd/w6c/main.combined.ww | 164 ++++++++++++++++++++++++++- selfhost/cmd/wcc/cgenexpr.ww | 145 +++++++++++++++++++++++ selfhost/cmd/wcc/check.ww | 11 +- selfhost/cmd/wwdump/main.combined.ww | 164 ++++++++++++++++++++++++++- test/wcc/990_selfhost.c | 29 +++++ test/wcc/994_w6c_ww.c | 19 ++++ 7 files changed, 525 insertions(+), 15 deletions(-) diff --git a/lib/ww/parse/expr.ww b/lib/ww/parse/expr.ww index 3976e70f..a133952d 100644 --- a/lib/ww/parse/expr.ww +++ b/lib/ww/parse/expr.ww @@ -217,6 +217,14 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = { let tail: *node = nil; for (true) { let e: *node = parseexpr(p); + // Hare-style spread: `expr...` in an arg slot becomes a + // marker the callee/builtin can iterate over. Mirrors + // cmd/wcc/parse.c. The only consumer today is `append`. + if (accepttok(p, tkind.TK_ELLIPSIS)) { + let sp: *node = newnode(p.a, nkind.N_SPREAD, e.file, e.line, e.col); + sp.lhs = e; + e = sp; + }; if (head == nil) { head = e; tail = e; } else { tail.next = e; tail = e; }; if (!accepttok(p, tkind.TK_COMMA)) { break; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index f49679c6..113d1f56 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -2333,6 +2333,14 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = { let tail: *node = nil; for (true) { let e: *node = parseexpr(p); + // Hare-style spread: `expr...` in an arg slot becomes a + // marker the callee/builtin can iterate over. Mirrors + // cmd/wcc/parse.c. The only consumer today is `append`. + if (accepttok(p, tkind.TK_ELLIPSIS)) { + let sp: *node = newnode(p.a, nkind.N_SPREAD, e.file, e.line, e.col); + sp.lhs = e; + e = sp; + }; if (head == nil) { head = e; tail = e; } else { tail.next = e; tail = e; }; if (!accepttok(p, tkind.TK_COMMA)) { break; }; @@ -4013,11 +4021,12 @@ fn seedprimitives(c: *checker) void = { scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil); // `nil`, `true`, `false` are keywords — handled at the lex/parser // level, no symbol needed. - // `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so - // their use sites resolve. The actual semantics live in cgen. - scopedefine(c.top, "len", skind.SK_FN, nil, nil); - scopedefine(c.top, "alloc", skind.SK_FN, nil, nil); - scopedefine(c.top, "free", skind.SK_FN, nil, nil); + // `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine + // them so their use sites resolve. The actual semantics live in cgen. + scopedefine(c.top, "len", skind.SK_FN, nil, nil); + scopedefine(c.top, "alloc", skind.SK_FN, nil, nil); + scopedefine(c.top, "free", skind.SK_FN, nil, nil); + scopedefine(c.top, "append", skind.SK_FN, nil, nil); }; // installdecl — install the top-level decl's name into the top scope. @@ -7413,7 +7422,152 @@ fn cgbin(c: *cgen, n: *node) void = { return; }; +// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering. +// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model). +// Each value gets: +// ; cgexpr → AX +// ; PUSHQ AX +// ; ADDQ $1, s.len(BP) +// ; LEAQ s(BP), DI ; arg1 = &s +// ; MOVQ esz, SI ; arg2 = membsz +// ; CALL rt_ensure(SB) +// ; MOVQ s.len(BP), CX ; CX = new len +// ; SUBQ $1, CX ; CX = slot index +// ; [IMULQ esz, CX] ; byte offset (esz>1) +// ; MOVQ s.ptr(BP), BX +// ; ADDQ CX, BX +// ; POPQ AX +// ; MOV* AX, (BX) ; store (MOVB / MOVQ) +// nkind.N_SPREAD wraps the same body in a counted loop over items.len. +fn cgappend(c: *cgen, n: *node) void = { + let sn: *node = n.list; + if (sn == nil) { return; }; + if (sn.kind != nkind.N_IDENT) { return; }; + let snlocal: *local = localfindnode(c, sn.str); + if (snlocal == nil) { return; }; + let sn_off: i32 = snlocal.off; + let esz: i32 = elemsizeof(snlocal.tnode); + let store_op: str = "MOVQ"; + if (esz == 1) { store_op = "MOVB"; }; + + let vn: *node = sn.next; + for (vn != nil) { + if (vn.kind == nkind.N_SPREAD) { + let it: *node = vn.lhs; + if (it == nil) { vn = vn.next; continue; }; + if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; + let itlocal: *local = localfindnode(c, it.str); + if (itlocal == nil) { vn = vn.next; continue; }; + let it_off: i32 = itlocal.off; + let load_op: str = "MOVQ"; + if (esz == 1) { load_op = "MOVZBQ"; }; + emitline("\tSUBQ\t$8, SP\n"); + emitline("\tMOVQ\t$0, (SP)\n"); + let ll: str = mklabel(c, "spr_l"); + let le: str = mklabel(c, "spr_e"); + emitlabel(ll); + emitline("\tMOVQ\t(SP), CX\n"); + emitline("\tMOVQ\t"); + emitoff((it_off + 8): i64); + emitline("(BP), DX\n"); + emitline("\tCMPQ\tDX, CX\n"); + emitline("\tJGE\t"); emitline(le); emitline("\n"); + emitline("\tMOVQ\t"); + emitoff(it_off: i64); + emitline("(BP), BX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tADDQ\tCX, BX\n"); + emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tADDQ\t$1, "); + emitoff((sn_off + 8): i64); + emitline("(BP)\n"); + emitline("\tLEAQ\t"); + emitoff(sn_off: i64); + emitline("(BP), DI\n"); + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", SI\n"); + emitline("\tCALL\trt_ensure(SB)\n"); + emitline("\tMOVQ\t"); + emitoff((sn_off + 8): i64); + emitline("(BP), CX\n"); + emitline("\tSUBQ\t$1, CX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(sn_off: i64); + emitline("(BP), BX\n"); + emitline("\tADDQ\tCX, BX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); + emitline("\tADDQ\t$1, (SP)\n"); + emitline("\tJMP\t"); emitline(ll); emitline("\n"); + emitlabel(le); + emitline("\tADDQ\t$8, SP\n"); + vn = vn.next; + continue; + }; + cgexpr(c, vn); + emitline("\tPUSHQ\tAX\n"); + emitline("\tADDQ\t$1, "); + emitoff((sn_off + 8): i64); + emitline("(BP)\n"); + emitline("\tLEAQ\t"); + emitoff(sn_off: i64); + emitline("(BP), DI\n"); + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", SI\n"); + emitline("\tCALL\trt_ensure(SB)\n"); + emitline("\tMOVQ\t"); + emitoff((sn_off + 8): i64); + emitline("(BP), CX\n"); + emitline("\tSUBQ\t$1, CX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(sn_off: i64); + emitline("(BP), BX\n"); + emitline("\tADDQ\tCX, BX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); + vn = vn.next; + }; + return; +}; + fn cgcall(c: *cgen, n: *node) void = { + // Hare-style `append(s, v)` / `append(s, items...)` builtin — + // special-cased before pushargsrev so the spread variant can run + // a counted loop over the items slice instead of a normal call. + let callee: *node = n.lhs; + if (callee != nil) { + if (callee.kind == nkind.N_IDENT) { + if (streq(callee.str, "append")) { + if (n.list != nil) { + if (n.list.next != nil) { + cgappend(c, n); + return; + }; + }; + }; + }; + }; + let nargs: i32 = pushargsrev(c, n.list); // Pop forward. Float args were pushed as 8 bytes from X0 via // SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 750a9ccb..29e3a238 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1389,7 +1389,152 @@ fn cgbin(c: *cgen, n: *node) void = { return; }; +// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering. +// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model). +// Each value gets: +// ; cgexpr → AX +// ; PUSHQ AX +// ; ADDQ $1, s.len(BP) +// ; LEAQ s(BP), DI ; arg1 = &s +// ; MOVQ esz, SI ; arg2 = membsz +// ; CALL rt_ensure(SB) +// ; MOVQ s.len(BP), CX ; CX = new len +// ; SUBQ $1, CX ; CX = slot index +// ; [IMULQ esz, CX] ; byte offset (esz>1) +// ; MOVQ s.ptr(BP), BX +// ; ADDQ CX, BX +// ; POPQ AX +// ; MOV* AX, (BX) ; store (MOVB / MOVQ) +// nkind.N_SPREAD wraps the same body in a counted loop over items.len. +fn cgappend(c: *cgen, n: *node) void = { + let sn: *node = n.list; + if (sn == nil) { return; }; + if (sn.kind != nkind.N_IDENT) { return; }; + let snlocal: *local = localfindnode(c, sn.str); + if (snlocal == nil) { return; }; + let sn_off: i32 = snlocal.off; + let esz: i32 = elemsizeof(snlocal.tnode); + let store_op: str = "MOVQ"; + if (esz == 1) { store_op = "MOVB"; }; + + let vn: *node = sn.next; + for (vn != nil) { + if (vn.kind == nkind.N_SPREAD) { + let it: *node = vn.lhs; + if (it == nil) { vn = vn.next; continue; }; + if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; + let itlocal: *local = localfindnode(c, it.str); + if (itlocal == nil) { vn = vn.next; continue; }; + let it_off: i32 = itlocal.off; + let load_op: str = "MOVQ"; + if (esz == 1) { load_op = "MOVZBQ"; }; + emitline("\tSUBQ\t$8, SP\n"); + emitline("\tMOVQ\t$0, (SP)\n"); + let ll: str = mklabel(c, "spr_l"); + let le: str = mklabel(c, "spr_e"); + emitlabel(ll); + emitline("\tMOVQ\t(SP), CX\n"); + emitline("\tMOVQ\t"); + emitoff((it_off + 8): i64); + emitline("(BP), DX\n"); + emitline("\tCMPQ\tDX, CX\n"); + emitline("\tJGE\t"); emitline(le); emitline("\n"); + emitline("\tMOVQ\t"); + emitoff(it_off: i64); + emitline("(BP), BX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tADDQ\tCX, BX\n"); + emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tADDQ\t$1, "); + emitoff((sn_off + 8): i64); + emitline("(BP)\n"); + emitline("\tLEAQ\t"); + emitoff(sn_off: i64); + emitline("(BP), DI\n"); + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", SI\n"); + emitline("\tCALL\trt_ensure(SB)\n"); + emitline("\tMOVQ\t"); + emitoff((sn_off + 8): i64); + emitline("(BP), CX\n"); + emitline("\tSUBQ\t$1, CX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(sn_off: i64); + emitline("(BP), BX\n"); + emitline("\tADDQ\tCX, BX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); + emitline("\tADDQ\t$1, (SP)\n"); + emitline("\tJMP\t"); emitline(ll); emitline("\n"); + emitlabel(le); + emitline("\tADDQ\t$8, SP\n"); + vn = vn.next; + continue; + }; + cgexpr(c, vn); + emitline("\tPUSHQ\tAX\n"); + emitline("\tADDQ\t$1, "); + emitoff((sn_off + 8): i64); + emitline("(BP)\n"); + emitline("\tLEAQ\t"); + emitoff(sn_off: i64); + emitline("(BP), DI\n"); + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", SI\n"); + emitline("\tCALL\trt_ensure(SB)\n"); + emitline("\tMOVQ\t"); + emitoff((sn_off + 8): i64); + emitline("(BP), CX\n"); + emitline("\tSUBQ\t$1, CX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(sn_off: i64); + emitline("(BP), BX\n"); + emitline("\tADDQ\tCX, BX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); + vn = vn.next; + }; + return; +}; + fn cgcall(c: *cgen, n: *node) void = { + // Hare-style `append(s, v)` / `append(s, items...)` builtin — + // special-cased before pushargsrev so the spread variant can run + // a counted loop over the items slice instead of a normal call. + let callee: *node = n.lhs; + if (callee != nil) { + if (callee.kind == nkind.N_IDENT) { + if (streq(callee.str, "append")) { + if (n.list != nil) { + if (n.list.next != nil) { + cgappend(c, n); + return; + }; + }; + }; + }; + }; + let nargs: i32 = pushargsrev(c, n.list); // Pop forward. Float args were pushed as 8 bytes from X0 via // SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index f5d1fb98..4a57a99b 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -56,11 +56,12 @@ fn seedprimitives(c: *checker) void = { scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil); // `nil`, `true`, `false` are keywords — handled at the lex/parser // level, no symbol needed. - // `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so - // their use sites resolve. The actual semantics live in cgen. - scopedefine(c.top, "len", skind.SK_FN, nil, nil); - scopedefine(c.top, "alloc", skind.SK_FN, nil, nil); - scopedefine(c.top, "free", skind.SK_FN, nil, nil); + // `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine + // them so their use sites resolve. The actual semantics live in cgen. + scopedefine(c.top, "len", skind.SK_FN, nil, nil); + scopedefine(c.top, "alloc", skind.SK_FN, nil, nil); + scopedefine(c.top, "free", skind.SK_FN, nil, nil); + scopedefine(c.top, "append", skind.SK_FN, nil, nil); }; // installdecl — install the top-level decl's name into the top scope. diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index ed16e1c0..5844aa16 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -2333,6 +2333,14 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = { let tail: *node = nil; for (true) { let e: *node = parseexpr(p); + // Hare-style spread: `expr...` in an arg slot becomes a + // marker the callee/builtin can iterate over. Mirrors + // cmd/wcc/parse.c. The only consumer today is `append`. + if (accepttok(p, tkind.TK_ELLIPSIS)) { + let sp: *node = newnode(p.a, nkind.N_SPREAD, e.file, e.line, e.col); + sp.lhs = e; + e = sp; + }; if (head == nil) { head = e; tail = e; } else { tail.next = e; tail = e; }; if (!accepttok(p, tkind.TK_COMMA)) { break; }; @@ -4013,11 +4021,12 @@ fn seedprimitives(c: *checker) void = { scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil); // `nil`, `true`, `false` are keywords — handled at the lex/parser // level, no symbol needed. - // `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so - // their use sites resolve. The actual semantics live in cgen. - scopedefine(c.top, "len", skind.SK_FN, nil, nil); - scopedefine(c.top, "alloc", skind.SK_FN, nil, nil); - scopedefine(c.top, "free", skind.SK_FN, nil, nil); + // `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine + // them so their use sites resolve. The actual semantics live in cgen. + scopedefine(c.top, "len", skind.SK_FN, nil, nil); + scopedefine(c.top, "alloc", skind.SK_FN, nil, nil); + scopedefine(c.top, "free", skind.SK_FN, nil, nil); + scopedefine(c.top, "append", skind.SK_FN, nil, nil); }; // installdecl — install the top-level decl's name into the top scope. @@ -7413,7 +7422,152 @@ fn cgbin(c: *cgen, n: *node) void = { return; }; +// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering. +// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model). +// Each value gets: +// ; cgexpr → AX +// ; PUSHQ AX +// ; ADDQ $1, s.len(BP) +// ; LEAQ s(BP), DI ; arg1 = &s +// ; MOVQ esz, SI ; arg2 = membsz +// ; CALL rt_ensure(SB) +// ; MOVQ s.len(BP), CX ; CX = new len +// ; SUBQ $1, CX ; CX = slot index +// ; [IMULQ esz, CX] ; byte offset (esz>1) +// ; MOVQ s.ptr(BP), BX +// ; ADDQ CX, BX +// ; POPQ AX +// ; MOV* AX, (BX) ; store (MOVB / MOVQ) +// nkind.N_SPREAD wraps the same body in a counted loop over items.len. +fn cgappend(c: *cgen, n: *node) void = { + let sn: *node = n.list; + if (sn == nil) { return; }; + if (sn.kind != nkind.N_IDENT) { return; }; + let snlocal: *local = localfindnode(c, sn.str); + if (snlocal == nil) { return; }; + let sn_off: i32 = snlocal.off; + let esz: i32 = elemsizeof(snlocal.tnode); + let store_op: str = "MOVQ"; + if (esz == 1) { store_op = "MOVB"; }; + + let vn: *node = sn.next; + for (vn != nil) { + if (vn.kind == nkind.N_SPREAD) { + let it: *node = vn.lhs; + if (it == nil) { vn = vn.next; continue; }; + if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; }; + let itlocal: *local = localfindnode(c, it.str); + if (itlocal == nil) { vn = vn.next; continue; }; + let it_off: i32 = itlocal.off; + let load_op: str = "MOVQ"; + if (esz == 1) { load_op = "MOVZBQ"; }; + emitline("\tSUBQ\t$8, SP\n"); + emitline("\tMOVQ\t$0, (SP)\n"); + let ll: str = mklabel(c, "spr_l"); + let le: str = mklabel(c, "spr_e"); + emitlabel(ll); + emitline("\tMOVQ\t(SP), CX\n"); + emitline("\tMOVQ\t"); + emitoff((it_off + 8): i64); + emitline("(BP), DX\n"); + emitline("\tCMPQ\tDX, CX\n"); + emitline("\tJGE\t"); emitline(le); emitline("\n"); + emitline("\tMOVQ\t"); + emitoff(it_off: i64); + emitline("(BP), BX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tADDQ\tCX, BX\n"); + emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n"); + emitline("\tPUSHQ\tAX\n"); + emitline("\tADDQ\t$1, "); + emitoff((sn_off + 8): i64); + emitline("(BP)\n"); + emitline("\tLEAQ\t"); + emitoff(sn_off: i64); + emitline("(BP), DI\n"); + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", SI\n"); + emitline("\tCALL\trt_ensure(SB)\n"); + emitline("\tMOVQ\t"); + emitoff((sn_off + 8): i64); + emitline("(BP), CX\n"); + emitline("\tSUBQ\t$1, CX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(sn_off: i64); + emitline("(BP), BX\n"); + emitline("\tADDQ\tCX, BX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); + emitline("\tADDQ\t$1, (SP)\n"); + emitline("\tJMP\t"); emitline(ll); emitline("\n"); + emitlabel(le); + emitline("\tADDQ\t$8, SP\n"); + vn = vn.next; + continue; + }; + cgexpr(c, vn); + emitline("\tPUSHQ\tAX\n"); + emitline("\tADDQ\t$1, "); + emitoff((sn_off + 8): i64); + emitline("(BP)\n"); + emitline("\tLEAQ\t"); + emitoff(sn_off: i64); + emitline("(BP), DI\n"); + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", SI\n"); + emitline("\tCALL\trt_ensure(SB)\n"); + emitline("\tMOVQ\t"); + emitoff((sn_off + 8): i64); + emitline("(BP), CX\n"); + emitline("\tSUBQ\t$1, CX\n"); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", AX\n"); + emitline("\tIMULQ\tAX, CX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(sn_off: i64); + emitline("(BP), BX\n"); + emitline("\tADDQ\tCX, BX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n"); + vn = vn.next; + }; + return; +}; + fn cgcall(c: *cgen, n: *node) void = { + // Hare-style `append(s, v)` / `append(s, items...)` builtin — + // special-cased before pushargsrev so the spread variant can run + // a counted loop over the items slice instead of a normal call. + let callee: *node = n.lhs; + if (callee != nil) { + if (callee.kind == nkind.N_IDENT) { + if (streq(callee.str, "append")) { + if (n.list != nil) { + if (n.list.next != nil) { + cgappend(c, n); + return; + }; + }; + }; + }; + }; + let nargs: i32 = pushargsrev(c, n.list); // Pop forward. Float args were pushed as 8 bytes from X0 via // SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else diff --git a/test/wcc/990_selfhost.c b/test/wcc/990_selfhost.c index 6388cd34..8035ea26 100644 --- a/test/wcc/990_selfhost.c +++ b/test/wcc/990_selfhost.c @@ -345,6 +345,35 @@ probe_ww_compile(const char *bin) " dec(&c); dec(&c); dec(&c);\n" " return c.x;\n" "};", 2 }, + /* append(s, v, ...) builtin — Hare's rt::ensure model. + * Each value: PUSHQ AX, ADDQ $1 to s.len, LEAQ s/MOVQ esz + * args for rt_ensure, then write into the freshly-grown + * slot. Returns s.len = 3 after appending three u8s. */ + { "use os;\n" + "fn main() i32 = {\n" + " let s: []u8;\n" + " s.ptr = nil; s.len = 0; s.cap = 0;\n" + " append(s, 65u8, 66u8, 67u8);\n" + " return s.len: i32;\n" + "};", 3 }, + /* append(s, items...) spread variant — wraps the per-value + * body in a counted loop over items.len. Combined with + * single-value appends in the same fn. dst ends up with + * [1, 10, 20, 30] — sum = 61. */ + { "use os;\n" + "fn main() i32 = {\n" + " let src: []i64;\n" + " src.ptr = nil; src.len = 0; src.cap = 0;\n" + " append(src, 10i64, 20i64, 30i64);\n" + " let dst: []i64;\n" + " dst.ptr = nil; dst.len = 0; dst.cap = 0;\n" + " append(dst, 1i64);\n" + " append(dst, src...);\n" + " let total: i64 = 0i64;\n" + " let i: i32 = 0;\n" + " for (i < dst.len) { total += dst[i]; i += 1; };\n" + " return total: i32;\n" + "};", 61 }, /* switch with multi-expr cases + default. Lowers to a chain * of CMPQ + JE; the scrutinee lands in a fresh 8B local slot * (".sw_N") so case bodies can spill SP without losing it. diff --git a/test/wcc/994_w6c_ww.c b/test/wcc/994_w6c_ww.c index 12d4efc7..5057840a 100644 --- a/test/wcc/994_w6c_ww.c +++ b/test/wcc/994_w6c_ww.c @@ -159,6 +159,25 @@ main(void) " return -1;\n" "};\n" "fn main() i32 = { return classify(2); };" }, + { "append", + "use os;\n" + "fn main() i32 = {\n" + " let s: []u8;\n" + " s.ptr = nil; s.len = 0; s.cap = 0;\n" + " append(s, 65u8, 66u8, 67u8);\n" + " return s.len: i32;\n" + "};" }, + { "append_spread", + "use os;\n" + "fn main() i32 = {\n" + " let src: []i64;\n" + " src.ptr = nil; src.len = 0; src.cap = 0;\n" + " append(src, 10i64, 20i64);\n" + " let dst: []i64;\n" + " dst.ptr = nil; dst.len = 0; dst.cap = 0;\n" + " append(dst, src...);\n" + " return dst.len: i32;\n" + "};" }, { NULL, NULL }, };