From 548547a1d0303505e2bce35c18e94dbb6958a9bc Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 13:37:31 +0900 Subject: [PATCH] =?UTF-8?q?w6c:=20slice=20reassignment=20=E2=80=94=20full?= =?UTF-8?q?=20triple=20flow=20through=20N=5FIDENT/N=5FSLICE/N=5FASSIGN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit N_IDENT for a slice local now loads (AX=ptr, BX=len, CX=cap), matching the existing global-slice load. cgexpr learns N_SLICE: `base[lo:hi]` leaves the same triple in registers, so callers (return, arg push, reassignment) all share one shape. The let-init's pre-existing N_SLICE direct-store path stays as a specialisation; the new generic slice let-init catches fn-returning-slice and slice-ident initialisers. N_ASSIGN gains a TY_SLICE branch parallel to TY_STR: store all three halves to the local slot or, for globals, stash CX into DI before LEAQ-ing the address (CX is both the new cap and the address scratch). --- cmd/w6c/cgen.c | 91 +++++++++++++++++++++++++++++++++++++++ test/wcc/630_let_global.c | 55 ++++++++++++++++++++--- 2 files changed, 141 insertions(+), 5 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index bcc5c507..64224885 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -785,6 +785,14 @@ cgexpr(Cg *c, Node *n, Local *locals) * call-arg stack uniformly. */ ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX)); ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX)); + } else if (node_isslice(n)) { + /* slice values flow as (AX=ptr, BX=len, CX=cap) + * — mirror the global-slice load so a slice + * local can be reassigned, returned, or copied + * with the same triple convention. */ + ins2(c, A_MOVQ, amem(D_BP, off + 0), areg(D_AX)); + ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX)); + ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_CX)); } else { ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX)); } @@ -1577,6 +1585,31 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + /* Slice reassignment: cgexpr produces (AX=ptr, BX=len, + * CX=cap). Store all three at off+0/+8/+16 (local) or + * via &name(SB) → DI scratch (global — CX holds the + * cap, so we need a different address register). */ + if (lu && lu->kind == TY_SLICE) { + int off = localfind(locals, n->lhs->str); + if (off != 0) { + cgexpr(c, n->rhs, locals); + ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0)); + ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8)); + ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16)); + break; + } + if (let_islet(n->lhs->str)) { + cgexpr(c, n->rhs, locals); + ins2(c, A_MOVQ, areg(D_CX), areg(D_DI)); + ins2(c, A_LEAQ, masym(c, n->lhs->str), + areg(D_CX)); + ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, 0)); + ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, 8)); + ins2(c, A_MOVQ, areg(D_DI), amem(D_CX, 16)); + break; + } + break; + } } if (n->lhs->kind == N_IDENT) { int off = localfind(locals, n->lhs->str); @@ -2895,6 +2928,52 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + case N_SLICE: { + /* base[lo:hi] as a slice value. Leaves the triple in + * (AX=base+lo, BX=hi-lo, CX=hi-lo) so callers can route + * to a slice slot, return, or arg with the same ABI. Cap + * defaults to the new length — there's no syntax for a + * larger cap yet. Element scaling on the ptr isn't wired + * (matches the let-init path), so non-u8 slices need a + * follow-up audit when fixtures exercise them. */ + Node *base = n->lhs; + Node *lo = n->rhs; + Node *hi = n->cond; + Type *bt = base ? base->type : NULL; + Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + if (base && base->kind == N_IDENT) { + int boff = localfind(locals, base->str); + if (bu && bu->kind == TY_ARRAY) { + ins2(c, A_LEAQ, amem(D_BP, boff), areg(D_AX)); + } else { + ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX)); + } + } else if (base) { + cgexpr(c, base, locals); + } + ins1(c, A_PUSHQ, areg(D_AX)); + if (lo) cgexpr(c, lo, locals); + else cgexpr_int(c, 0); + ins1(c, A_PUSHQ, areg(D_AX)); + if (hi) { + cgexpr(c, hi, locals); + } else if (bu && bu->kind == TY_ARRAY) { + cgexpr_int(c, (long long)bu->alen); + } else if (base && base->kind == N_IDENT && bu && + (bu->kind == TY_SLICE || bu->kind == TY_STR)) { + int boff = localfind(locals, base->str); + ins2(c, A_MOVQ, amem(D_BP, boff + 8), areg(D_AX)); + } else { + cgexpr_int(c, 0); + } + ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); + ins1(c, A_POPQ, areg(D_CX)); + ins1(c, A_POPQ, areg(D_AX)); + ins2(c, A_ADDQ, areg(D_CX), areg(D_AX)); + ins2(c, A_SUBQ, areg(D_CX), areg(D_BX)); + ins2(c, A_MOVQ, areg(D_BX), areg(D_CX)); + break; + } default: cgexpr_int(c, 0); break; @@ -3091,6 +3170,18 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 16)); break; } + /* Generic slice rhs (e.g. fn returning []u8, slice ident, + * slice-typed param). cgexpr leaves (AX=ptr, BX=len, CX= + * cap); store all three into the local slot. Runs after + * the alloc and N_SLICE specialisations above so they keep + * their direct-store shape. */ + if (n->rhs && lu && lu->kind == TY_SLICE && sz == 24) { + cgexpr(c, n->rhs, *locals); + ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0)); + ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8)); + ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16)); + break; + } /* struct literal initialiser: field-by-field store. The * literal carries op == TK_ELLIPSIS when the source ends in * `..., ...` — in that case zero-fill the entire slot first, diff --git a/test/wcc/630_let_global.c b/test/wcc/630_let_global.c index e2812d01..7ef8abf6 100644 --- a/test/wcc/630_let_global.c +++ b/test/wcc/630_let_global.c @@ -146,11 +146,10 @@ static const struct fixture fixtures[] = { }, { "slice-cap-via-raw-pointer", - /* Drive the slice header through a raw u64 pointer cast - * — the language has no slice-reassignment expression - * yet, so this is the only way to fill the header from - * ww source today. .cap reads back as the value we - * wrote. */ + /* Drive the slice header through a raw u64 pointer cast. + * Pre-fix for slice reassignment this was the only way to + * fill the header from ww source; kept because the path + * still has to work. */ "let buf: []u8;\n" "fn main() i32 = {\n" "\tlet p: *u64 = (&buf): *u64;\n" @@ -159,6 +158,52 @@ static const struct fixture fixtures[] = { "};\n", 99, }, + { + "slice-global-reassign-from-global", + /* `dst = src` for slice globals — both halves of the + * triple (ptr, len, cap) must propagate. */ + "let dst: []u8;\n" + "let src: []u8;\n" + "fn main() i32 = {\n" + "\tlet p: *u64 = (&src): *u64;\n" + "\tp[0] = 0x1000u64;\n" + "\tp[1] = 7u64;\n" + "\tp[2] = 99u64;\n" + "\tdst = src;\n" + "\treturn dst.len: i32 + dst.cap: i32;\n" + "};\n", + 106, + }, + { + "slice-local-reassign-from-slice-expr", + /* `s = arr[lo:hi]` reassigns the slice local; cgexpr now + * emits N_SLICE as a triple. Previously the let-init + * specific N_SLICE path worked but reassignment dropped + * len/cap. */ + "fn main() i32 = {\n" + "\tlet arr: [16]u8;\n" + "\tlet i: i32 = 0;\n" + "\tfor (i < 16) { arr[i] = i: u8; i += 1; };\n" + "\tlet s: []u8 = arr[3:10];\n" + "\ts = arr[1:5];\n" + "\treturn s.len: i32;\n" + "};\n", + 4, + }, + { + "slice-local-from-fn-return", + /* fn returning []u8 leaves (AX,BX,CX) at return; the let- + * init slice fallback stores the triple. */ + "fn mkslice() []u8 = {\n" + "\tlet arr: [16]u8;\n" + "\treturn arr[3:9];\n" + "};\n" + "fn main() i32 = {\n" + "\tlet s: []u8 = mkslice();\n" + "\treturn s.len: i32;\n" + "};\n", + 6, + }, { "struct-field-readwrite", /* Top-level struct global. Field writes hit the global