w6c: lower append() to rt_ensure + inline store (hare model)

This commit is contained in:
2026-05-11 17:02:32 +09:00
parent 5408160d49
commit 9e383b7ba0
4 changed files with 100 additions and 65 deletions

View File

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

View File

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

View File

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

49
rt/ensure.ww Normal file
View File

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