From ab0976571bf80101e3a359d8663b4fc54fbbd102 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 14:37:09 +0900 Subject: [PATCH] =?UTF-8?q?selfhost:=20port=20slice=20reassignment=20?= =?UTF-8?q?=E2=80=94=20N=5FSLICE=20cgexpr=20+=20N=5FASSIGN=20triple=20stor?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror of 548547a in the wwstage cgen. cgexpr learns N_SLICE: `base[lo:hi]` leaves (AX=ptr, BX=len, CX=cap) so callers (return, arg push, reassignment, let init for fn-returning- slice / slice-ident) share one triple ABI. Without this, the existing let-init that just forwards (AX,BX,CX) to the slot was silently storing junk on a base[lo:hi] rhs. cgassign gains a slice branch parallel to str: for a local slice ident lhs, store all three halves to off+0/+8/+16; for a slice global, stash CX into DI before LEAQ-ing the symbol address into CX (CX is both the incoming cap and the LEAQ scratch), then store AX/BX/DI at +0/+8/+16. N_IDENT slice-local triple load was already in place from the earlier selfhost port; only the cgexpr and cgassign halves needed adding. Byte-identical to C w6c on the corpus — make test 26/26, 994_w6c_ww passes on 10 inputs, 990_selfhost + 995_self_rebuild reach fixed point. --- selfhost/cmd/w6c/main.combined.ww | 120 +++++++++++++++++++++++++-- selfhost/cmd/wcc/cgenexpr.ww | 120 +++++++++++++++++++++++++-- selfhost/cmd/wwdump/main.combined.ww | 120 +++++++++++++++++++++++++-- 3 files changed, 339 insertions(+), 21 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 7578bf1e..aa0e7268 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -6042,6 +6042,8 @@ fn cgexpr(c: *cgen, n: *node) void = { if (k == nkind.N_INDEX) { cgindex(c, n); return; }; + if (k == nkind.N_SLICE) { cgslice(c, n); return; }; + if (k == nkind.N_MATCH) { cgmatch(c, n); return; }; if (k == nkind.N_CAST) { cgcast(c, n); return; }; @@ -6540,6 +6542,88 @@ fn cgindex(c: *cgen, n: *node) void = { return; }; +// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, +// BX=hi-lo, CX=hi-lo) so callers can route to a slice slot, +// return, or arg with the same triple ABI. Cap defaults to the +// new length; no syntax for a wider cap yet. Element scaling +// on the ptr isn't wired — non-u8 slices need a follow-up audit. +fn cgslice(c: *cgen, n: *node) void = { + let base: *node = n.lhs; + let lo: *node = n.rhs; + let hi: *node = n.cond; + let baselocal: *local = nil; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + baselocal = localfindnode(c, base.str); + }; + }; + // base address + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { isarray = true; }; + }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); + }; + } else { if (base != nil) { + cgexpr(c, base); + };}; + emitline("\tPUSHQ\tAX\n"); + // lo (default 0) + if (lo != nil) { cgexpr(c, lo); } + else { emitline("\tMOVQ\t$0, AX\n"); }; + emitline("\tPUSHQ\tAX\n"); + // hi (default base length) + if (hi != nil) { + cgexpr(c, hi); + } else { if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let handled: bool = false; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + let lenn: *node = tn.rhs; + if (lenn != nil) { + if (lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + handled = true; + }; + }; + } else { if (tn.kind == nkind.N_TSLICE) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + } else { if (tn.kind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + }; + };};}; + }; + if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; + } else { + emitline("\tMOVQ\t$0, AX\n"); + };}; + emitline("\tMOVQ\tAX, BX\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\tADDQ\tCX, AX\n"); + emitline("\tSUBQ\tCX, BX\n"); + emitline("\tMOVQ\tBX, CX\n"); +}; + fn cgmatch(c: *cgen, n: *node) void = { // match (e) { case let v: T => stmt; ... } // @@ -7896,9 +7980,11 @@ fn cgassign(c: *cgen, n: *node) void = { if (off == 0) { // Top-level let target: RIP-relative store // for `=`, or load→combine→store for the - // compound forms. For a str global, take its - // address into CX and store both halves; the - // asm has no `name+8(SB)` operand form. + // compound forms. For a str/slice global, + // take its address into CX and store both + // halves (plus cap for slice — stashed via + // DI since LEAQ overwrites CX); the asm has + // no `name+8(SB)` operand form. if (!isletvar(c, nm)) { return; }; // Float global: rhs lands in X0; store via // LEAQ+indirect since MOVSS/MOVSD have no @@ -7937,6 +8023,16 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tBX, 8(CX)\n"); return; }; + if (letvarisslice(c, nm)) { + emitline("\tMOVQ\tCX, DI\n"); + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), CX\n"); + emitline("\tMOVQ\tAX, (CX)\n"); + emitline("\tMOVQ\tBX, 8(CX)\n"); + emitline("\tMOVQ\tDI, 16(CX)\n"); + return; + }; emitline("\tMOVQ\tAX, "); emitsymname(c, nm); emitline("(SB)\n"); @@ -7976,11 +8072,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - // Detect str-typed local — assignment must store both - // halves (AX=ptr at +0, BX=len at +8). + // Detect str/slice-typed local — assignment must store + // both halves (AX=ptr at +0, BX=len at +8) for str, + // plus the cap (CX at +16) for slice. let lcstr: bool = false; + let lcsl: bool = false; let lcn: *local = localfindnode(c, nm); - if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; + if (lcn != nil) { + lcstr = isstrtype(c, lcn.tnode); + lcsl = isslicetype(c, lcn.tnode); + }; let lcf: bool = false; let lcf32: bool = false; if (lcn != nil) { @@ -8006,11 +8107,16 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); - if (lcstr) { + if (lcstr || lcsl) { emitline("\tMOVQ\tBX, "); emitoff((off + 8): i64); emitline("(BP)\n"); }; + if (lcsl) { + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + }; return; }; if (n.op == tkind.TK_PLUSEQ) { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index e3eb3d23..750a9ccb 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -80,6 +80,8 @@ fn cgexpr(c: *cgen, n: *node) void = { if (k == nkind.N_INDEX) { cgindex(c, n); return; }; + if (k == nkind.N_SLICE) { cgslice(c, n); return; }; + if (k == nkind.N_MATCH) { cgmatch(c, n); return; }; if (k == nkind.N_CAST) { cgcast(c, n); return; }; @@ -578,6 +580,88 @@ fn cgindex(c: *cgen, n: *node) void = { return; }; +// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, +// BX=hi-lo, CX=hi-lo) so callers can route to a slice slot, +// return, or arg with the same triple ABI. Cap defaults to the +// new length; no syntax for a wider cap yet. Element scaling +// on the ptr isn't wired — non-u8 slices need a follow-up audit. +fn cgslice(c: *cgen, n: *node) void = { + let base: *node = n.lhs; + let lo: *node = n.rhs; + let hi: *node = n.cond; + let baselocal: *local = nil; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + baselocal = localfindnode(c, base.str); + }; + }; + // base address + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { isarray = true; }; + }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); + }; + } else { if (base != nil) { + cgexpr(c, base); + };}; + emitline("\tPUSHQ\tAX\n"); + // lo (default 0) + if (lo != nil) { cgexpr(c, lo); } + else { emitline("\tMOVQ\t$0, AX\n"); }; + emitline("\tPUSHQ\tAX\n"); + // hi (default base length) + if (hi != nil) { + cgexpr(c, hi); + } else { if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let handled: bool = false; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + let lenn: *node = tn.rhs; + if (lenn != nil) { + if (lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + handled = true; + }; + }; + } else { if (tn.kind == nkind.N_TSLICE) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + } else { if (tn.kind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + }; + };};}; + }; + if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; + } else { + emitline("\tMOVQ\t$0, AX\n"); + };}; + emitline("\tMOVQ\tAX, BX\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\tADDQ\tCX, AX\n"); + emitline("\tSUBQ\tCX, BX\n"); + emitline("\tMOVQ\tBX, CX\n"); +}; + fn cgmatch(c: *cgen, n: *node) void = { // match (e) { case let v: T => stmt; ... } // @@ -1934,9 +2018,11 @@ fn cgassign(c: *cgen, n: *node) void = { if (off == 0) { // Top-level let target: RIP-relative store // for `=`, or load→combine→store for the - // compound forms. For a str global, take its - // address into CX and store both halves; the - // asm has no `name+8(SB)` operand form. + // compound forms. For a str/slice global, + // take its address into CX and store both + // halves (plus cap for slice — stashed via + // DI since LEAQ overwrites CX); the asm has + // no `name+8(SB)` operand form. if (!isletvar(c, nm)) { return; }; // Float global: rhs lands in X0; store via // LEAQ+indirect since MOVSS/MOVSD have no @@ -1975,6 +2061,16 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tBX, 8(CX)\n"); return; }; + if (letvarisslice(c, nm)) { + emitline("\tMOVQ\tCX, DI\n"); + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), CX\n"); + emitline("\tMOVQ\tAX, (CX)\n"); + emitline("\tMOVQ\tBX, 8(CX)\n"); + emitline("\tMOVQ\tDI, 16(CX)\n"); + return; + }; emitline("\tMOVQ\tAX, "); emitsymname(c, nm); emitline("(SB)\n"); @@ -2014,11 +2110,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - // Detect str-typed local — assignment must store both - // halves (AX=ptr at +0, BX=len at +8). + // Detect str/slice-typed local — assignment must store + // both halves (AX=ptr at +0, BX=len at +8) for str, + // plus the cap (CX at +16) for slice. let lcstr: bool = false; + let lcsl: bool = false; let lcn: *local = localfindnode(c, nm); - if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; + if (lcn != nil) { + lcstr = isstrtype(c, lcn.tnode); + lcsl = isslicetype(c, lcn.tnode); + }; let lcf: bool = false; let lcf32: bool = false; if (lcn != nil) { @@ -2044,11 +2145,16 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); - if (lcstr) { + if (lcstr || lcsl) { emitline("\tMOVQ\tBX, "); emitoff((off + 8): i64); emitline("(BP)\n"); }; + if (lcsl) { + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + }; return; }; if (n.op == tkind.TK_PLUSEQ) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3ae38f9a..54da9bb3 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -6042,6 +6042,8 @@ fn cgexpr(c: *cgen, n: *node) void = { if (k == nkind.N_INDEX) { cgindex(c, n); return; }; + if (k == nkind.N_SLICE) { cgslice(c, n); return; }; + if (k == nkind.N_MATCH) { cgmatch(c, n); return; }; if (k == nkind.N_CAST) { cgcast(c, n); return; }; @@ -6540,6 +6542,88 @@ fn cgindex(c: *cgen, n: *node) void = { return; }; +// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, +// BX=hi-lo, CX=hi-lo) so callers can route to a slice slot, +// return, or arg with the same triple ABI. Cap defaults to the +// new length; no syntax for a wider cap yet. Element scaling +// on the ptr isn't wired — non-u8 slices need a follow-up audit. +fn cgslice(c: *cgen, n: *node) void = { + let base: *node = n.lhs; + let lo: *node = n.rhs; + let hi: *node = n.cond; + let baselocal: *local = nil; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + baselocal = localfindnode(c, base.str); + }; + }; + // base address + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { isarray = true; }; + }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); + }; + } else { if (base != nil) { + cgexpr(c, base); + };}; + emitline("\tPUSHQ\tAX\n"); + // lo (default 0) + if (lo != nil) { cgexpr(c, lo); } + else { emitline("\tMOVQ\t$0, AX\n"); }; + emitline("\tPUSHQ\tAX\n"); + // hi (default base length) + if (hi != nil) { + cgexpr(c, hi); + } else { if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let handled: bool = false; + if (tn != nil) { + if (tn.kind == nkind.N_TARRAY) { + let lenn: *node = tn.rhs; + if (lenn != nil) { + if (lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + handled = true; + }; + }; + } else { if (tn.kind == nkind.N_TSLICE) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + } else { if (tn.kind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + }; + };};}; + }; + if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; + } else { + emitline("\tMOVQ\t$0, AX\n"); + };}; + emitline("\tMOVQ\tAX, BX\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\tADDQ\tCX, AX\n"); + emitline("\tSUBQ\tCX, BX\n"); + emitline("\tMOVQ\tBX, CX\n"); +}; + fn cgmatch(c: *cgen, n: *node) void = { // match (e) { case let v: T => stmt; ... } // @@ -7896,9 +7980,11 @@ fn cgassign(c: *cgen, n: *node) void = { if (off == 0) { // Top-level let target: RIP-relative store // for `=`, or load→combine→store for the - // compound forms. For a str global, take its - // address into CX and store both halves; the - // asm has no `name+8(SB)` operand form. + // compound forms. For a str/slice global, + // take its address into CX and store both + // halves (plus cap for slice — stashed via + // DI since LEAQ overwrites CX); the asm has + // no `name+8(SB)` operand form. if (!isletvar(c, nm)) { return; }; // Float global: rhs lands in X0; store via // LEAQ+indirect since MOVSS/MOVSD have no @@ -7937,6 +8023,16 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tBX, 8(CX)\n"); return; }; + if (letvarisslice(c, nm)) { + emitline("\tMOVQ\tCX, DI\n"); + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), CX\n"); + emitline("\tMOVQ\tAX, (CX)\n"); + emitline("\tMOVQ\tBX, 8(CX)\n"); + emitline("\tMOVQ\tDI, 16(CX)\n"); + return; + }; emitline("\tMOVQ\tAX, "); emitsymname(c, nm); emitline("(SB)\n"); @@ -7976,11 +8072,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; - // Detect str-typed local — assignment must store both - // halves (AX=ptr at +0, BX=len at +8). + // Detect str/slice-typed local — assignment must store + // both halves (AX=ptr at +0, BX=len at +8) for str, + // plus the cap (CX at +16) for slice. let lcstr: bool = false; + let lcsl: bool = false; let lcn: *local = localfindnode(c, nm); - if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; + if (lcn != nil) { + lcstr = isstrtype(c, lcn.tnode); + lcsl = isslicetype(c, lcn.tnode); + }; let lcf: bool = false; let lcf32: bool = false; if (lcn != nil) { @@ -8006,11 +8107,16 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, "); emitoff(off: i64); emitline("(BP)\n"); - if (lcstr) { + if (lcstr || lcsl) { emitline("\tMOVQ\tBX, "); emitoff((off + 8): i64); emitline("(BP)\n"); }; + if (lcsl) { + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + }; return; }; if (n.op == tkind.TK_PLUSEQ) {