From 9e383b7ba0aff9677cec829362732d3c63f97e88 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 11 May 2026 17:02:32 +0900 Subject: [PATCH] w6c: lower append() to rt_ensure + inline store (hare model) --- Makefile | 2 +- cmd/w6c/cgen.c | 65 ++++++++++++++++++++++++++++++++++++++------------ rt/append.ww | 49 ------------------------------------- rt/ensure.ww | 49 +++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 65 deletions(-) delete mode 100644 rt/append.ww create mode 100644 rt/ensure.ww diff --git a/Makefile b/Makefile index b3536ca6..9cd715f5 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ W6L_SRC = cmd/w6l/main.c cmd/w6l/obj.c cmd/w6l/sym.c cmd/w6l/pass.c cmd/w6l/out. W6L_OBJ = $(W6L_SRC:cmd/w6l/%.c=$(OBJ)/w6l/%.o) RT_S = rt/start.s rt/syscall.s rt/alloc.s rt/streq.s rt/abort.s -RT_WW = rt/append.ww +RT_WW = rt/ensure.ww RT_OBJ = $(RT_S:rt/%.s=$(OBJ)/rt/%.o) $(RT_WW:rt/%.ww=$(OBJ)/rt/%.o) # Cstage: one-time C bootstrap (see BOOTSTRAP.md). Built by `cc`. diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 7b8925a6..692f727e 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1304,23 +1304,36 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && strcmp(n->lhs->str, "append") == 0 && n->list && n->list->next) { - /* append(s, v1, v2, ...) — one CALL per item. - * append(s, items...) — runtime loop over items. */ + /* append(s, v) lowering — Hare's rt::ensure model. + * ; AX = value + * ; PUSHQ AX ; save + * ; ADDQ $1, sn_off+8(BP) ; s.len += 1 + * ; LEAQ sn_off(BP), DI ; arg1 = &s + * ; MOVQ esz, SI ; arg2 = membsz + * ; CALL rt_ensure(SB) ; may realloc s.ptr + * ; MOVQ sn_off+8(BP), CX ; CX = new len + * ; SUBQ $1, CX ; slot index + * ; [IMULQ esz, CX] ; byte offset (esz>1) + * ; MOVQ sn_off(BP), BX ; reread s.ptr + * ; ADDQ CX, BX ; BX = target + * ; POPQ AX ; v + * ; MOV* AX, (BX) ; store (MOVB / MOVQ) + * + * Spread form `append(s, items...)` runs this same body + * in a counted loop over items. */ Node *sn = n->list; Type *st = sn->type; Type *su = (st && st->kind == TY_NAMED) ? st->under : st; int esz = (su && su->sub) ? (int)su->sub->size : 1; - const char *fn = (esz == 1) ? "appendu8" : "appendi64"; int sn_off = (sn->kind == N_IDENT) ? localfind(locals, sn->str) : 0; + int store_op = (esz == 1) ? A_MOVB : A_MOVQ; for (Node *vn = sn->next; vn; vn = vn->next) { if (vn->kind == N_SPREAD && vn->lhs && vn->lhs->kind == N_IDENT) { - /* iterate items; items_off is the - * spread-source slice's BP offset. */ int it_off = localfind(locals, vn->lhs->str); int load_op = (esz == 1) ? A_MOVZBQ : A_MOVQ; - /* push counter on stack */ + /* push counter (i) on stack */ ins2(c, A_SUBQ, aimm(8), areg(D_SP)); ins2(c, A_MOVQ, aimm(0), amem(D_SP, 0)); char *ll = mklabel(c, "spr_l"); @@ -1330,6 +1343,7 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_MOVQ, amem(D_BP, it_off + 8), areg(D_DX)); ins2(c, A_CMPQ, areg(D_DX), areg(D_CX)); ins1(c, A_JGE, abranch(le)); + /* AX = items.ptr[i] */ ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX)); if (esz > 1) { ins2(c, A_MOVQ, aimm(esz), areg(D_AX)); @@ -1337,11 +1351,23 @@ cgexpr(Cg *c, Node *n, Local *locals) } ins2(c, A_ADDQ, areg(D_CX), areg(D_BX)); ins2(c, load_op, amem(D_BX, 0), areg(D_AX)); + /* ensure + store one element */ ins1(c, A_PUSHQ, areg(D_AX)); - ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_AX)); - ins1(c, A_POPQ, areg(D_SI)); - ins2(c, A_MOVQ, areg(D_AX), areg(D_DI)); - ins1(c, A_CALL, masym(c, fn)); + ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8)); + ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI)); + ins2(c, A_MOVQ, aimm(esz), areg(D_SI)); + ins1(c, A_CALL, masym(c, "rt_ensure")); + ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX)); + ins2(c, A_SUBQ, aimm(1), areg(D_CX)); + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_AX)); + ins2(c, A_IMULQ, areg(D_AX), areg(D_CX)); + } + ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX)); + ins2(c, A_ADDQ, areg(D_CX), areg(D_BX)); + ins1(c, A_POPQ, areg(D_AX)); + ins2(c, store_op, areg(D_AX), amem(D_BX, 0)); + /* loop tail */ ins2(c, A_ADDQ, aimm(1), amem(D_SP, 0)); ins1(c, A_JMP, abranch(ll)); label(c, le); @@ -1350,11 +1376,20 @@ cgexpr(Cg *c, Node *n, Local *locals) } cgexpr(c, vn, locals); /* val → AX */ ins1(c, A_PUSHQ, areg(D_AX)); - if (sn->kind == N_IDENT) - ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_AX)); - ins1(c, A_POPQ, areg(D_SI)); - ins2(c, A_MOVQ, areg(D_AX), areg(D_DI)); - ins1(c, A_CALL, masym(c, fn)); + ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8)); + ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI)); + ins2(c, A_MOVQ, aimm(esz), areg(D_SI)); + ins1(c, A_CALL, masym(c, "rt_ensure")); + ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX)); + ins2(c, A_SUBQ, aimm(1), areg(D_CX)); + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_AX)); + ins2(c, A_IMULQ, areg(D_AX), areg(D_CX)); + } + ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX)); + ins2(c, A_ADDQ, areg(D_CX), areg(D_BX)); + ins1(c, A_POPQ, areg(D_AX)); + ins2(c, store_op, areg(D_AX), amem(D_BX, 0)); } break; } diff --git a/rt/append.ww b/rt/append.ww deleted file mode 100644 index 312f2567..00000000 --- a/rt/append.ww +++ /dev/null @@ -1,49 +0,0 @@ -// rt/append.ww — slice runtime helpers, archived into libwwrt.a. -// -// The compiler lowers `append(s, v)` to a CALL to one of these by -// element size (1 byte → appendu8, else → appendi64). User code -// never `use`s this — the symbol comes in via the runtime archive, -// like rt_alloc and rt_streq. - -@symbol("rt_alloc") fn alloc(n: u64) *void; -@symbol("rt_free") fn free(p: *void, n: u64) void; - -export fn appendu8(s: *[]u8, v: u8) void = { - if (s.len >= s.cap) { - let nc: i32 = s.cap * 2; - if (nc < 8) { nc = 8; }; - let np: *u8 = alloc(nc: u64): *u8; - let i: i32 = 0; - for (i < s.len) { - np[i] = s.ptr[i]; - i += 1; - }; - if (s.cap > 0) { - free(s.ptr: *void, s.cap: u64); - }; - s.ptr = np; - s.cap = nc; - }; - s.ptr[s.len] = v; - s.len += 1; -}; - -export fn appendi64(s: *[]i64, v: i64) void = { - if (s.len >= s.cap) { - let nc: i32 = s.cap * 2; - if (nc < 8) { nc = 8; }; - let np: *i64 = alloc((nc * 8): u64): *i64; - let i: i32 = 0; - for (i < s.len) { - np[i] = s.ptr[i]; - i += 1; - }; - if (s.cap > 0) { - free(s.ptr: *void, (s.cap * 8): u64); - }; - s.ptr = np; - s.cap = nc; - }; - s.ptr[s.len] = v; - s.len += 1; -}; diff --git a/rt/ensure.ww b/rt/ensure.ww new file mode 100644 index 00000000..48c1d5f8 --- /dev/null +++ b/rt/ensure.ww @@ -0,0 +1,49 @@ +// rt/ensure.ww — slice growth helper, archived into libwwrt.a. +// +// Companion to the `append(s, v)` builtin. The compiler lowers +// `append(s, v)` to: +// +// ; push v +// ; s.len += 1 +// ; CALL rt_ensure(&s, sizeof(elem)) +// ; ; ensure may have realloc'd, so re-read s.ptr +// ; pop v +// ; *(s.ptr + (s.len - 1) * elem_size) = v +// +// One helper handles every element width via the membsz parameter — +// no per-type wrapper functions (appendu8 / appendi64) needed. +// +// User code never `use`s this — the symbol is resolved at link time +// from libwwrt.a, like rt_alloc and rt_streq. + +@symbol("rt_alloc") fn alloc(n: u64) *void; +@symbol("rt_free") fn free(p: *void, n: u64) void; + +// Mirrors ww's []T header layout: 24 bytes with 8-byte slots. +// ww's source uses i32 for len/cap but the compiler stores them in +// 8-byte slots; declaring as i64 here keeps the field offsets right +// for this polymorphic alias. +type slice = struct { + ptr: *u8, + len: i64, + cap: i64, +}; + +export fn rt_ensure(s: *slice, membsz: u64) void = { + if (s.cap >= s.len) { return; }; + let nc: i64 = s.cap * 2i64; + if (nc < 8i64) { nc = 8i64; }; + for (nc < s.len) { nc *= 2i64; }; + let np: *u8 = alloc((nc: u64) * membsz): *u8; + let n: u64 = (s.cap: u64) * membsz; + let i: u64 = 0u64; + for (i < n) { + np[i] = s.ptr[i]; + i += 1u64; + }; + if (s.cap > 0i64) { + free(s.ptr: *void, (s.cap: u64) * membsz); + }; + s.ptr = np; + s.cap = nc; +};