From 6e1d958d9ba3da82446ba4ca5f5d531494f45b92 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 8 Jun 2026 09:58:22 +0900 Subject: [PATCH] =?UTF-8?q?wcc/cgen:=20#145=20slice-copy-assign=20LHS=20s.?= =?UTF-8?q?arr[lo:hi]=3Dbs=20=E2=80=94=20N=5FSLICE-LHS=20arm,=20runtime=20?= =?UTF-8?q?byte-copy=20loop,=20esz=20via=20type=20table=20(both=20stages)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Probe-first find for the path c2 appendlit (buf.buf[lo..hi]=bs): a slice-copy-assign into a struct-field array sub-range emitted ZERO code — silent NO-OP, both stages, both-wrong-identical (#263), so runtime is the only net. N_ASSIGN gains an N_SLICE-LHS arm (cgen.c + cgenexpr.ww slicebaseesz twin) reusing the N_SLICE-read base/esz cascade and copying (hi-lo)*esz bytes from rhs.ptr via a runtime loop (len is runtime; no REP/MOVSB). esz routed through the type table (rule 13; [N]u8->1). Hare len(bs)==hi-lo assert deferred to #149. --- Makefile | 6 + cmd/w6c/cgen.c | 51 ++++++ selfhost/cmd/w6c/main.combined.ww | 74 ++++++++ selfhost/cmd/wcc/cgenexpr.ww | 74 ++++++++ selfhost/cmd/wwdump/main.combined.ww | 74 ++++++++ test/wcc/952_slicecopy_assign_run.c | 262 +++++++++++++++++++++++++++ 6 files changed, 541 insertions(+) create mode 100644 test/wcc/952_slicecopy_assign_run.c diff --git a/Makefile b/Makefile index dc2becd4..42f6ece9 100644 --- a/Makefile +++ b/Makefile @@ -466,6 +466,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_ptrarr_index_run \ $(BIN)/test_dotbase_arr_run \ $(BIN)/test_dotbase_addr_slice_run \ + $(BIN)/test_slicecopy_assign_run \ $(BIN)/test_structlit_arrfield_run \ $(BIN)/test_defdim_struct_run \ $(BIN)/test_arraytoslice_run \ @@ -2129,6 +2130,11 @@ $(BIN)/test_dotbase_addr_slice_run: test/wcc/949_dotbase_addr_slice_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_slicecopy_assign_run: test/wcc/952_slicecopy_assign_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_structlit_arrfield_run: test/wcc/949_structlit_arrfield_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index b602c1ea..1c8ec280 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -4771,6 +4771,57 @@ cgexpr(Cg *c, Node *n, Local *locals) break; } case N_ASSIGN: { + /* #145 (c1.5a): bulk slice-copy-assign into a range place + * `s.arr[lo:hi] = bs` (LHS is N_SLICE). No legacy N_ASSIGN arm + * catches N_SLICE (they gate INDEX/STAR/DOT/ident), so the + * statement fell through to the scalar tail and emitted NOTHING + * — a silent no-op, both stages, byte-id-green (#263-class). + * Reuse the N_SLICE READ lowering: cgexpr(lhs) leaves AX = dst + * ptr (base+lo*esz), BX = hi-lo (element count), CX = cap. The + * element width is the SAME read-path esz (rule-13: chased base + * element tinfo, [N]u8 -> 1); multiply BX by it for the byte + * count, then a runtime-counted byte-granular copy from bs.ptr. + * Byte loop because the length is RUNTIME — the #265/#268 memcpy + * emitters are compile-time-sz unrolled and w6a has no REP/MOVSB. + * Only plain `=`; a compound op on a range place is meaningless. + * + * Hare asserts len(bs)==hi-lo (ref/hare/path/stack.ha:72, + * appendlit). We copy exactly hi-lo elems and do NOT runtime- + * check len(bs). The length-equality assert is task #149 + * (rule-7: documented, not a c2 blocker — appendlit's lengths + * are equal by construction). */ + if (n->op == TK_ASSIGN && n->lhs && n->lhs->kind == N_SLICE) { + Node *sl = n->lhs; + Node *sbase = sl->lhs; + Type *sbu = type_chase_named(sbase ? sbase->type : NULL); + int esz = (sbase && (sbase->kind == N_IDENT + || sbase->kind == N_DOT || sbase->kind == N_ARRLIT) + && sbu && sbu->sub) ? (int)sbu->sub->size : 1; + cgexpr(c, sl, locals); /* AX=dst ptr, BX=hi-lo */ + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_DX)); + ins2(c, A_IMULQ, areg(D_DX), areg(D_BX)); + } + ins1(c, A_PUSHQ, areg(D_AX)); /* dst ptr */ + ins1(c, A_PUSHQ, areg(D_BX)); /* byte count */ + cgexpr(c, n->rhs, locals); /* AX=src ptr */ + ins2(c, A_MOVQ, areg(D_AX), areg(D_SI)); + ins1(c, A_POPQ, areg(D_CX)); /* byte count */ + ins1(c, A_POPQ, areg(D_DI)); /* dst ptr */ + char *top = mklabel(c, "scpy"); + char *end = mklabel(c, "scpe"); + label(c, top); + ins2(c, A_CMPQ, aimm(0), areg(D_CX)); + ins1(c, A_JLE, abranch(end)); + ins2(c, A_MOVB, amem(D_SI, 0), areg(D_AX)); + ins2(c, A_MOVB, areg(D_AX), amem(D_DI, 0)); + ins2(c, A_ADDQ, aimm(1), areg(D_SI)); + ins2(c, A_ADDQ, aimm(1), areg(D_DI)); + ins2(c, A_SUBQ, aimm(1), areg(D_CX)); + ins1(c, A_JMP, abranch(top)); + label(c, end); + break; + } /* Discard lvalue `_ = expr;` — evaluate rhs for side effects, * write nothing. */ if (n->lhs && n->lhs->kind == N_IDENT && diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 90527e11..d099ef6a 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -23835,6 +23835,39 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 // membsz-unit); esz from the type table, mirroring the cgindex idiom. +// slicebaseesz — element width of a sliceable base, exactly mirroring the +// esz cascade cgslice computes inline for ptr-scaling (baselocal/globaltn → +// elemsizeofc; N_DOT field → dotbu.sub.size; N_ARRLIT → elemsizeofc; alias- +// NAMED override → chased sub.size). The #145 slice-copy-assign arm scales +// the COUNT by this same width, so it MUST match cgslice's ptr scaling +// byte-for-byte (cgslice supplies the dst ptr). Cstage twin: the N_SLICE-LHS +// arm in cgen.c N_ASSIGN reuses the read-path one-liner directly. +fn slicebaseesz(c: *cgen, base: *node) i32 = { + if (base == nil) { return 1; }; + let esz: i32 = 1; + if (base.kind == nkind.N_IDENT) { + let bl: *local = localfindnode(c, base.str); + if (bl != nil) { + esz = elemsizeofc(c, bl.tnode); + } else { + let gt: *node = letvartnode(c, base.str); + if (gt != nil) { esz = elemsizeofc(c, gt); }; + }; + let bt: *tinfo = base.type_: *tinfo; + if (bt != nil) { if (bt.kind == tykind.TY_NAMED) { + let bu60: *tinfo = tichase(bt); + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + };}; + } else { if (base.kind == nkind.N_DOT) { + let db: *tinfo = tichase(base.type_: *tinfo); + if (db != nil) { if (db.sub != nil) { esz = db.sub.size: i32; }; }; + } else { if (base.kind == nkind.N_ARRLIT) { + esz = elemsizeofc(c, base.lhs); + };};}; + return esz; +}; + fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -29259,6 +29292,47 @@ fn cgcall(c: *cgen, n: *node) void = { fn cgassign(c: *cgen, n: *node) void = { let lhs: *node = n.lhs; + // #145 (c1.5a): bulk slice-copy-assign `s.arr[lo:hi] = bs` (LHS is + // N_SLICE). No legacy cgassign arm catches N_SLICE — the statement + // silently emitted nothing (both stages, byte-id-green, #263-class). + // Twin of cstage cgen.c N_ASSIGN N_SLICE arm (full WHY there). + // cgexpr(lhs) routes to cgslice and leaves AX = dst ptr (base+lo*esz), + // BX = hi-lo (element count), CX = cap; slicebaseesz gives the SAME + // esz cgslice scaled the ptr by, so BX*esz is the byte count. Then a + // runtime-counted byte-granular copy from bs.ptr — byte loop because + // the length is RUNTIME (w6a has no REP/MOVSB). Only plain `=`. The + // Hare len(bs)==hi-lo assert is task #149 (rule-7: documented, not a + // c2 blocker — appendlit's lengths are equal by construction). + if (lhs != nil) { if (lhs.kind == nkind.N_SLICE + && n.op == tkind.TK_ASSIGN) { + let esz: i32 = slicebaseesz(c, lhs.lhs); + cgexpr(c, lhs); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", DX\n"); + emitline("\tIMULQ\tDX, BX\n"); + }; + emitline("\tPUSHQ\tAX\n"); + emitline("\tPUSHQ\tBX\n"); + cgexpr(c, n.rhs); + emitline("\tMOVQ\tAX, SI\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tDI\n"); + let top: str = mklabel(c, "scpy"); + let end: str = mklabel(c, "scpe"); + emitlabel(top); + emitline("\tCMPQ\t$0, CX\n"); + emitline("\tJLE\t"); emitline(end); emitline("\n"); + emitline("\tMOVB\t(SI), AX\n"); + emitline("\tMOVB\tAX, (DI)\n"); + emitline("\tADDQ\t$1, SI\n"); + emitline("\tADDQ\t$1, DI\n"); + emitline("\tSUBQ\t$1, CX\n"); + emitline("\tJMP\t"); emitline(top); emitline("\n"); + emitlabel(end); + return; + };}; // #20 (task): struct-lit rhs into an INDEXED struct element — // `a[i] = pt{...}`, `(*ts)[i].caps[k] = capture{...}` — a // DEREF place (`*p = pt{...}`) or an indexed-base FIELD place diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index bc096fd3..7a9377dc 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2336,6 +2336,39 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 // membsz-unit); esz from the type table, mirroring the cgindex idiom. +// slicebaseesz — element width of a sliceable base, exactly mirroring the +// esz cascade cgslice computes inline for ptr-scaling (baselocal/globaltn → +// elemsizeofc; N_DOT field → dotbu.sub.size; N_ARRLIT → elemsizeofc; alias- +// NAMED override → chased sub.size). The #145 slice-copy-assign arm scales +// the COUNT by this same width, so it MUST match cgslice's ptr scaling +// byte-for-byte (cgslice supplies the dst ptr). Cstage twin: the N_SLICE-LHS +// arm in cgen.c N_ASSIGN reuses the read-path one-liner directly. +fn slicebaseesz(c: *cgen, base: *node) i32 = { + if (base == nil) { return 1; }; + let esz: i32 = 1; + if (base.kind == nkind.N_IDENT) { + let bl: *local = localfindnode(c, base.str); + if (bl != nil) { + esz = elemsizeofc(c, bl.tnode); + } else { + let gt: *node = letvartnode(c, base.str); + if (gt != nil) { esz = elemsizeofc(c, gt); }; + }; + let bt: *tinfo = base.type_: *tinfo; + if (bt != nil) { if (bt.kind == tykind.TY_NAMED) { + let bu60: *tinfo = tichase(bt); + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + };}; + } else { if (base.kind == nkind.N_DOT) { + let db: *tinfo = tichase(base.type_: *tinfo); + if (db != nil) { if (db.sub != nil) { esz = db.sub.size: i32; }; }; + } else { if (base.kind == nkind.N_ARRLIT) { + esz = elemsizeofc(c, base.lhs); + };};}; + return esz; +}; + fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -7760,6 +7793,47 @@ fn cgcall(c: *cgen, n: *node) void = { fn cgassign(c: *cgen, n: *node) void = { let lhs: *node = n.lhs; + // #145 (c1.5a): bulk slice-copy-assign `s.arr[lo:hi] = bs` (LHS is + // N_SLICE). No legacy cgassign arm catches N_SLICE — the statement + // silently emitted nothing (both stages, byte-id-green, #263-class). + // Twin of cstage cgen.c N_ASSIGN N_SLICE arm (full WHY there). + // cgexpr(lhs) routes to cgslice and leaves AX = dst ptr (base+lo*esz), + // BX = hi-lo (element count), CX = cap; slicebaseesz gives the SAME + // esz cgslice scaled the ptr by, so BX*esz is the byte count. Then a + // runtime-counted byte-granular copy from bs.ptr — byte loop because + // the length is RUNTIME (w6a has no REP/MOVSB). Only plain `=`. The + // Hare len(bs)==hi-lo assert is task #149 (rule-7: documented, not a + // c2 blocker — appendlit's lengths are equal by construction). + if (lhs != nil) { if (lhs.kind == nkind.N_SLICE + && n.op == tkind.TK_ASSIGN) { + let esz: i32 = slicebaseesz(c, lhs.lhs); + cgexpr(c, lhs); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", DX\n"); + emitline("\tIMULQ\tDX, BX\n"); + }; + emitline("\tPUSHQ\tAX\n"); + emitline("\tPUSHQ\tBX\n"); + cgexpr(c, n.rhs); + emitline("\tMOVQ\tAX, SI\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tDI\n"); + let top: str = mklabel(c, "scpy"); + let end: str = mklabel(c, "scpe"); + emitlabel(top); + emitline("\tCMPQ\t$0, CX\n"); + emitline("\tJLE\t"); emitline(end); emitline("\n"); + emitline("\tMOVB\t(SI), AX\n"); + emitline("\tMOVB\tAX, (DI)\n"); + emitline("\tADDQ\t$1, SI\n"); + emitline("\tADDQ\t$1, DI\n"); + emitline("\tSUBQ\t$1, CX\n"); + emitline("\tJMP\t"); emitline(top); emitline("\n"); + emitlabel(end); + return; + };}; // #20 (task): struct-lit rhs into an INDEXED struct element — // `a[i] = pt{...}`, `(*ts)[i].caps[k] = capture{...}` — a // DEREF place (`*p = pt{...}`) or an indexed-base FIELD place diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 26a60ac8..19d92a39 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -23835,6 +23835,39 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 // membsz-unit); esz from the type table, mirroring the cgindex idiom. +// slicebaseesz — element width of a sliceable base, exactly mirroring the +// esz cascade cgslice computes inline for ptr-scaling (baselocal/globaltn → +// elemsizeofc; N_DOT field → dotbu.sub.size; N_ARRLIT → elemsizeofc; alias- +// NAMED override → chased sub.size). The #145 slice-copy-assign arm scales +// the COUNT by this same width, so it MUST match cgslice's ptr scaling +// byte-for-byte (cgslice supplies the dst ptr). Cstage twin: the N_SLICE-LHS +// arm in cgen.c N_ASSIGN reuses the read-path one-liner directly. +fn slicebaseesz(c: *cgen, base: *node) i32 = { + if (base == nil) { return 1; }; + let esz: i32 = 1; + if (base.kind == nkind.N_IDENT) { + let bl: *local = localfindnode(c, base.str); + if (bl != nil) { + esz = elemsizeofc(c, bl.tnode); + } else { + let gt: *node = letvartnode(c, base.str); + if (gt != nil) { esz = elemsizeofc(c, gt); }; + }; + let bt: *tinfo = base.type_: *tinfo; + if (bt != nil) { if (bt.kind == tykind.TY_NAMED) { + let bu60: *tinfo = tichase(bt); + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + };}; + } else { if (base.kind == nkind.N_DOT) { + let db: *tinfo = tichase(base.type_: *tinfo); + if (db != nil) { if (db.sub != nil) { esz = db.sub.size: i32; }; }; + } else { if (base.kind == nkind.N_ARRLIT) { + esz = elemsizeofc(c, base.lhs); + };};}; + return esz; +}; + fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -29259,6 +29292,47 @@ fn cgcall(c: *cgen, n: *node) void = { fn cgassign(c: *cgen, n: *node) void = { let lhs: *node = n.lhs; + // #145 (c1.5a): bulk slice-copy-assign `s.arr[lo:hi] = bs` (LHS is + // N_SLICE). No legacy cgassign arm catches N_SLICE — the statement + // silently emitted nothing (both stages, byte-id-green, #263-class). + // Twin of cstage cgen.c N_ASSIGN N_SLICE arm (full WHY there). + // cgexpr(lhs) routes to cgslice and leaves AX = dst ptr (base+lo*esz), + // BX = hi-lo (element count), CX = cap; slicebaseesz gives the SAME + // esz cgslice scaled the ptr by, so BX*esz is the byte count. Then a + // runtime-counted byte-granular copy from bs.ptr — byte loop because + // the length is RUNTIME (w6a has no REP/MOVSB). Only plain `=`. The + // Hare len(bs)==hi-lo assert is task #149 (rule-7: documented, not a + // c2 blocker — appendlit's lengths are equal by construction). + if (lhs != nil) { if (lhs.kind == nkind.N_SLICE + && n.op == tkind.TK_ASSIGN) { + let esz: i32 = slicebaseesz(c, lhs.lhs); + cgexpr(c, lhs); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", DX\n"); + emitline("\tIMULQ\tDX, BX\n"); + }; + emitline("\tPUSHQ\tAX\n"); + emitline("\tPUSHQ\tBX\n"); + cgexpr(c, n.rhs); + emitline("\tMOVQ\tAX, SI\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tDI\n"); + let top: str = mklabel(c, "scpy"); + let end: str = mklabel(c, "scpe"); + emitlabel(top); + emitline("\tCMPQ\t$0, CX\n"); + emitline("\tJLE\t"); emitline(end); emitline("\n"); + emitline("\tMOVB\t(SI), AX\n"); + emitline("\tMOVB\tAX, (DI)\n"); + emitline("\tADDQ\t$1, SI\n"); + emitline("\tADDQ\t$1, DI\n"); + emitline("\tSUBQ\t$1, CX\n"); + emitline("\tJMP\t"); emitline(top); emitline("\n"); + emitlabel(end); + return; + };}; // #20 (task): struct-lit rhs into an INDEXED struct element — // `a[i] = pt{...}`, `(*ts)[i].caps[k] = capture{...}` — a // DEREF place (`*p = pt{...}`) or an indexed-base FIELD place diff --git a/test/wcc/952_slicecopy_assign_run.c b/test/wcc/952_slicecopy_assign_run.c new file mode 100644 index 00000000..a64e665e --- /dev/null +++ b/test/wcc/952_slicecopy_assign_run.c @@ -0,0 +1,262 @@ +/* + * 952_slicecopy_assign_run — runtime + cs==ww byte-id net for #145 + * (c1.5a): bulk slice-copy-assign into a range place `s.arr[lo:hi] = bs` + * (the LHS is an N_SLICE). No legacy N_ASSIGN arm caught an N_SLICE LHS, + * so the statement fell through to the scalar tail and emitted NOTHING — + * a silent no-op, BOTH stages, byte-id-green (#263-class). It blocked + * the path c2 port's appendlit (ref/hare/path/stack.ha:72 + * `buf.buf[newend..newend+len(bs)] = bs`). + * + * The fix reuses the N_SLICE READ lowering (AX = base+lo*esz, BX = hi-lo, + * CX = cap), scales BX by the SAME read-path element width (esz; [N]u8 -> + * 1, [N]i32 -> 4), and emits a runtime-counted byte-granular copy loop + * from bs.ptr (the length is RUNTIME — w6a has no REP/MOVSB and the + * #265/#268 memcpy emitters are compile-time-sz unrolled). The Hare + * len(bs)==hi-lo assert is deferred to task #149 (documented, not a c2 + * blocker — appendlit's lengths are equal by construction). + * + * WRITE-twin rows assert: the copy lands the right bytes in [lo,hi) AND + * leaves bytes OUTSIDE the range untouched (every row sums members both + * inside and outside the range, so a mis-strided or over-wide copy + * fails). esz coverage: [N]u8 (esz=1) and [N]i32 (esz=4). Base coverage: + * a struct-field array (N_DOT, the appendlit shape), a struct field via a + * *struct param (via_ptr), and a bare-local array (N_IDENT). READ-twin + * rows (drew's #5, already clean) lock the reslice-consume of the field + * array. byteid=1 throughout: post-fix the emission is byte-identical + * (the loop labels share the mklabel prefix/count/order across stages). + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want_exit; int byteid; }; + +static const struct row rows[] = { + /* WRITE twin — struct-field [N]u8 base (the appendlit shape). */ + { "w_u8_lo0", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "export fn main() i32 = {\n" + " let s: buffer;\n" + " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" + " s.buf[0:3] = bs[0:3];\n" + " return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n" + " +s.buf[4]+s.buf[5]): i32;\n" + "};\n", 60, 1 }, + { "w_u8_lo2", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "export fn main() i32 = {\n" + " let s: buffer;\n" + " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" + " s.buf[2:5] = bs[0:3];\n" + " return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n" + " +s.buf[4]+s.buf[5]): i32;\n" + "};\n", 60, 1 }, + { "w_u8_single", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "export fn main() i32 = {\n" + " let s: buffer;\n" + " let bs: [1]u8; bs[0]=42u8;\n" + " s.buf[3:4] = bs[0:1];\n" + " return (s.buf[2]+s.buf[3]+s.buf[4]): i32;\n" + "};\n", 42, 1 }, + /* empty range hi==lo — count=0, the loop's CMPQ $0 guard must + * JLE-exit before the first MOVB (off-by-one underflow would + * clobber buf[lo] and run away). Nothing in buf may change. */ + { "w_empty", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "export fn main() i32 = {\n" + " let s: buffer; s.buf[2]=5u8; s.buf[3]=7u8;\n" + " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" + " s.buf[2:2] = bs[0:0];\n" + " return (s.buf[1]+s.buf[2]+s.buf[3]+s.buf[4]): i32;\n" + "};\n", 12, 1 }, + /* esz=4 — [N]i32 field strides by the element width, not 1. */ + { "w_i32", + "package main;\n" + "type box = struct { o: [8]i32 };\n" + "export fn main() i32 = {\n" + " let s: box;\n" + " let bs: [2]i32; bs[0]=40; bs[1]=88;\n" + " s.o[1:3] = bs[0:2];\n" + " return (s.o[0]+s.o[1]+s.o[2]+s.o[3]): i32;\n" + "};\n", 128, 1 }, + /* via_ptr — the field write through a *struct param (appendlit's + * `buf: *buffer` shape). */ + { "w_viaptr", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "fn wr(s: *buffer, src: []u8) void = { s.buf[1:4] = src; };\n" + "export fn main() i32 = {\n" + " let s: buffer;\n" + " let bs: [3]u8; bs[0]=11u8; bs[1]=22u8; bs[2]=33u8;\n" + " wr(&s, bs[0:3]);\n" + " return (s.buf[0]+s.buf[1]+s.buf[2]+s.buf[3]\n" + " +s.buf[4]): i32;\n" + "};\n", 66, 1 }, + /* N_IDENT base — a bare-local array place (slicebaseesz IDENT leg). */ + { "w_local", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [8]u8;\n" + " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" + " a[2:5] = bs[0:3];\n" + " return (a[0]+a[1]+a[2]+a[3]+a[4]+a[5]): i32;\n" + "};\n", 60, 1 }, + /* READ twin (drew's #5, already CLEAN) — reslice the field array with + * a runtime hi bound, consume as []u8. Locks the read companion. */ + { "rd_reslice", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "export fn main() i32 = {\n" + " let s: buffer;\n" + " let bs: [3]u8; bs[0]=10u8; bs[1]=20u8; bs[2]=30u8;\n" + " s.buf[0:3] = bs[0:3];\n" + " s.end = 3;\n" + " let v: []u8 = s.buf[0:s.end];\n" + " return (v[0]: i32 + v[1]: i32 + v[2]: i32 + (v.len: i32)*100);\n" + "};\n", 104, 1 }, + { "rd_len", + "package main;\n" + "type buffer = struct { buf: [8]u8, end: i32 };\n" + "export fn main() i32 = {\n" + " let s: buffer;\n" + " s.end = 5;\n" + " let v: []u8 = s.buf[0:s.end];\n" + " return v.len: i32;\n" + "};\n", 5, 1 }, + { NULL, NULL, 0, 0 } +}; + +static int +slurp_eq(const char *a, const char *b) +{ + FILE *fa = fopen(a, "rb"); + FILE *fb = fopen(b, "rb"); + if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } + int rc = 0; + for (;;) { + int ca = fgetc(fa); + int cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char w6c[1100], w6c_ww[1100]; + snprintf(w6c, sizeof w6c, "%s/w6c", bin); + snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); + if (access(w6c_ww, X_OK) != 0) { + fprintf(stderr, "slicecopy: w6c_ww missing — cannot run the " + "cs==ww byte-id gate (the whole point of this test)\n"); + return 1; + } + + int n = 0, fail = 0; + for (int i = 0; rows[i].src; i++, n++) { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwsca_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(rows[i].src, f); + fclose(f); + + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsca_%d_d_%d", + getpid(), i); + mkdir(tmpdir, 0755); + + char cmd[2048]; + snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", + tmpdir, bin, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: cstage build failed\n", + rows[i].label); + fail++; + unlink(src); rmdir(tmpdir); + continue; + } + + char outbin[128]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + + int got = runwait(outbin); + if (got != rows[i].want_exit) { + fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", + rows[i].label, got, rows[i].want_exit); + fail++; + } + unlink(outbin); rmdir(tmpdir); + + if (!rows[i].byteid) { unlink(src); continue; } + + char cs_s[64], ws_s[64]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwsca_%d_%d_cs.s", + getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwsca_%d_%d_ww.s", + getpid(), i); + + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c, cs_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); + fail++; unlink(src); continue; + } + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c_ww, ws_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww failed\n", + rows[i].label); + fail++; unlink(src); unlink(cs_s); continue; + } + if (slurp_eq(cs_s, ws_s) != 0) { + fprintf(stderr, + "row[%s]: cstage/wwstage .s DIFFER (rule-10 " + "byte-id violation)\n", rows[i].label); + fail++; + } + unlink(src); unlink(cs_s); unlink(ws_s); + } + + if (fail) { + fprintf(stderr, "%d/%d slicecopy-assign tests failed\n", + fail, n); + return 1; + } + printf("slicecopy: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); + return 0; +}