diff --git a/Makefile b/Makefile index 62d45662..965227e1 100644 --- a/Makefile +++ b/Makefile @@ -263,6 +263,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_str_chainfield_store_cap_run \ $(BIN)/test_str_massign_store_cap_run \ $(BIN)/test_slice_store_cap_run \ + $(BIN)/test_subslice_cap_run \ $(BIN)/test_str_forrange_loopvar_run \ $(BIN)/test_composite_call_arg \ $(BIN)/test_composite_call_arg_run \ @@ -690,6 +691,12 @@ $(BIN)/test_slice_store_cap_run: test/wcc/941_slice_store_cap_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_subslice_cap_run: test/wcc/942_subslice_cap_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_composite_call_arg: test/wcc/723_composite_call_arg.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 686be800..2346c0b7 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1155,6 +1155,44 @@ localfind(Local *head, const char *name) return 0; /* 0 = not found (caller must verify) */ } +/* cg_base_cap — load the capacity of a sub-slice's UNDERLYING storage + * into `dst` for the #20 cap = base_cap - lo formula (drew: harec + * eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start; + * ensure.ha:4-8 distinct capacity field). array [N]T -> N (literal); + * slice/str -> the .capacity word carried in the header at +16 (the + * +16 load mirrors the hi-default +8 length dispatch, but emitted + * unconditionally). Returns 0 when base_cap isn't cleanly available so + * the caller keeps the prior cap=len: a non-ident base (cgexpr already + * discarded its header cap; recomputing would re-evaluate a possibly + * side-effecting base -- #74, which also owns the pre-existing + * defaulted-hi len gap there), or a GLOBAL str base (wwstage cgslice + * has no global-str load, #73 -- matching it keeps the stages + * byte-identical rather than introducing a fresh divergence). */ +static int +cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst) +{ + if (!base || base->kind != N_IDENT) + return 0; + if (bu && bu->kind == TY_ARRAY) { + ins2(c, A_MOVQ, aimm((long long)bu->alen), areg(dst)); + return 1; + } + if (bu && (bu->kind == TY_SLICE || bu->kind == TY_STR)) { + int boff = localfind(locals, base->str); + int isglobal = (boff == 0) && let_islet(base->str); + if (isglobal && bu->kind == TY_STR) + return 0; + if (isglobal) { + ins2(c, A_LEAQ, masym(c, base->str), areg(dst)); + ins2(c, A_MOVQ, amem(dst, 16), areg(dst)); + } else { + ins2(c, A_MOVQ, amem(D_BP, boff + 16), areg(dst)); + } + return 1; + } + return 0; +} + /* ------------------------------------------------------------------ */ /* expressions: result lands in AX. Returns 1 on success. */ @@ -4606,8 +4644,14 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_SUBQ, areg(D_AX), areg(D_DX)); /* ptr = base + lo */ ins2(c, A_ADDQ, areg(D_AX), areg(D_CX)); - /* push cap, len, ptr (top) */ - ins1(c, A_PUSHQ, areg(D_DX)); /* cap */ + /* push cap, len, ptr (top). cap = base_cap - lo + * (#20); AX=lo, BX free. */ + if (cg_base_cap(c, base, bu, locals, D_BX)) { + ins2(c, A_SUBQ, areg(D_AX), areg(D_BX)); + ins1(c, A_PUSHQ, areg(D_BX)); /* cap */ + } else { + ins1(c, A_PUSHQ, areg(D_DX)); /* cap = len */ + } ins1(c, A_PUSHQ, areg(D_DX)); /* len */ ins1(c, A_PUSHQ, areg(D_CX)); /* ptr */ continue; @@ -6317,12 +6361,12 @@ cgexpr(Cg *c, Node *n, Local *locals) } 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. */ + * (AX=base+lo, BX=hi-lo, CX=base_cap-lo) so callers can + * route to a slice slot, return, or arg with the same ABI. + * cap is the storage remaining to the base's end (#20, + * Go/Hare-identical), via cg_base_cap. Element scaling on + * the ptr isn't wired (lo*esz is #76), so non-u8 slices + * need that follow-up before their ptr is correct. */ Node *base = n->lhs; Node *lo = n->rhs; Node *hi = n->cond; @@ -6373,7 +6417,13 @@ cgexpr(Cg *c, Node *n, Local *locals) 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)); + /* cap = base_cap - lo (#20); CX=lo, BX=len here. */ + if (cg_base_cap(c, base, bu, locals, D_DX)) { + ins2(c, A_SUBQ, areg(D_CX), areg(D_DX)); + ins2(c, A_MOVQ, areg(D_DX), areg(D_CX)); + } else { + ins2(c, A_MOVQ, areg(D_BX), areg(D_CX)); + } break; } default: diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index f4fa3027..9d8aaca4 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10950,7 +10950,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr - emitline("\tPUSHQ\tDX\n"); // cap + // cap = base_cap - lo (#20); AX=lo, BX free. + if (cgbasecap(c, base, "BX")) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tPUSHQ\tBX\n"); // cap + } else { + emitline("\tPUSHQ\tDX\n"); // cap = len + }; emitline("\tPUSHQ\tDX\n"); // len emitline("\tPUSHQ\tCX\n"); // ptr (top) return rest + 3; @@ -14775,11 +14781,91 @@ fn cgindex(c: *cgen, n: *node) void = { return; }; +// cgbasecap — load the capacity of a sub-slice's UNDERLYING storage +// into `dst` for the #20 cap = base_cap - lo formula (drew: harec +// eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start; +// ensure.ha:4-8 distinct capacity field). array [N]T -> N (literal); +// slice/str -> the .capacity word in the header at +16 (mirrors the +// hi-default +8 length dispatch, emitted unconditionally). Returns +// false when base_cap isn't cleanly available so the caller keeps the +// prior cap=len: a non-ident base (its header cap was discarded; +// recomputing would re-evaluate a possibly side-effecting base -- #74, +// which also owns the pre-existing defaulted-hi len gap there), or +// a GLOBAL str base (no +16 load here, #73 -- matching the cstage +// carve-out keeps both stages byte-identical). cstage twin: +// cmd/w6c/cgen.c cg_base_cap. +fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { + if (base == nil) { return false; }; + if (base.kind != nkind.N_IDENT) { return false; }; + let baselocal: *local = localfindnode(c, base.str); + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + if (tn == nil) { return false; }; + if (tn.kind == nkind.N_TARRAY) { + let lenn: *node = tn.rhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (tn.kind == nkind.N_TSLICE) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + if (tn.kind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + return false; + }; + let gt: *node = letvartnode(c, base.str); + if (gt == nil) { return false; }; + if (gt.kind == nkind.N_TARRAY) { + let lenn: *node = gt.rhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (gt.kind == nkind.N_TSLICE) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + emitline("\tMOVQ\t16("); + emitline(dst); + emitline("), "); + emitline(dst); + emitline("\n"); + return true; + }; + return false; +}; + // 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. +// BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, +// return, or arg with the same triple ABI. cap is the storage +// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. +// Element scaling on the ptr isn't wired (lo*esz is #76). fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -14897,7 +14983,13 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tCX, AX\n"); emitline("\tSUBQ\tCX, BX\n"); - emitline("\tMOVQ\tBX, CX\n"); + // cap = base_cap - lo (#20); CX=lo, BX=len here. + if (cgbasecap(c, base, "DX")) { + emitline("\tSUBQ\tCX, DX\n"); + emitline("\tMOVQ\tDX, CX\n"); + } else { + emitline("\tMOVQ\tBX, CX\n"); + }; }; fn cgmatch(c: *cgen, n: *node) void = { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 9eda10af..cb6419dc 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -942,11 +942,91 @@ fn cgindex(c: *cgen, n: *node) void = { return; }; +// cgbasecap — load the capacity of a sub-slice's UNDERLYING storage +// into `dst` for the #20 cap = base_cap - lo formula (drew: harec +// eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start; +// ensure.ha:4-8 distinct capacity field). array [N]T -> N (literal); +// slice/str -> the .capacity word in the header at +16 (mirrors the +// hi-default +8 length dispatch, emitted unconditionally). Returns +// false when base_cap isn't cleanly available so the caller keeps the +// prior cap=len: a non-ident base (its header cap was discarded; +// recomputing would re-evaluate a possibly side-effecting base -- #74, +// which also owns the pre-existing defaulted-hi len gap there), or +// a GLOBAL str base (no +16 load here, #73 -- matching the cstage +// carve-out keeps both stages byte-identical). cstage twin: +// cmd/w6c/cgen.c cg_base_cap. +fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { + if (base == nil) { return false; }; + if (base.kind != nkind.N_IDENT) { return false; }; + let baselocal: *local = localfindnode(c, base.str); + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + if (tn == nil) { return false; }; + if (tn.kind == nkind.N_TARRAY) { + let lenn: *node = tn.rhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (tn.kind == nkind.N_TSLICE) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + if (tn.kind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + return false; + }; + let gt: *node = letvartnode(c, base.str); + if (gt == nil) { return false; }; + if (gt.kind == nkind.N_TARRAY) { + let lenn: *node = gt.rhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (gt.kind == nkind.N_TSLICE) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + emitline("\tMOVQ\t16("); + emitline(dst); + emitline("), "); + emitline(dst); + emitline("\n"); + return true; + }; + return false; +}; + // 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. +// BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, +// return, or arg with the same triple ABI. cap is the storage +// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. +// Element scaling on the ptr isn't wired (lo*esz is #76). fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -1064,7 +1144,13 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tCX, AX\n"); emitline("\tSUBQ\tCX, BX\n"); - emitline("\tMOVQ\tBX, CX\n"); + // cap = base_cap - lo (#20); CX=lo, BX=len here. + if (cgbasecap(c, base, "DX")) { + emitline("\tSUBQ\tCX, DX\n"); + emitline("\tMOVQ\tDX, CX\n"); + } else { + emitline("\tMOVQ\tBX, CX\n"); + }; }; fn cgmatch(c: *cgen, n: *node) void = { diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 1dd09dda..2e67940a 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -389,7 +389,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr - emitline("\tPUSHQ\tDX\n"); // cap + // cap = base_cap - lo (#20); AX=lo, BX free. + if (cgbasecap(c, base, "BX")) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tPUSHQ\tBX\n"); // cap + } else { + emitline("\tPUSHQ\tDX\n"); // cap = len + }; emitline("\tPUSHQ\tDX\n"); // len emitline("\tPUSHQ\tCX\n"); // ptr (top) return rest + 3; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 1c7af047..9da0a7aa 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10950,7 +10950,13 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr - emitline("\tPUSHQ\tDX\n"); // cap + // cap = base_cap - lo (#20); AX=lo, BX free. + if (cgbasecap(c, base, "BX")) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tPUSHQ\tBX\n"); // cap + } else { + emitline("\tPUSHQ\tDX\n"); // cap = len + }; emitline("\tPUSHQ\tDX\n"); // len emitline("\tPUSHQ\tCX\n"); // ptr (top) return rest + 3; @@ -14775,11 +14781,91 @@ fn cgindex(c: *cgen, n: *node) void = { return; }; +// cgbasecap — load the capacity of a sub-slice's UNDERLYING storage +// into `dst` for the #20 cap = base_cap - lo formula (drew: harec +// eval.c:1017 slice cap-=start / eval.c:1024 array cap=length-start; +// ensure.ha:4-8 distinct capacity field). array [N]T -> N (literal); +// slice/str -> the .capacity word in the header at +16 (mirrors the +// hi-default +8 length dispatch, emitted unconditionally). Returns +// false when base_cap isn't cleanly available so the caller keeps the +// prior cap=len: a non-ident base (its header cap was discarded; +// recomputing would re-evaluate a possibly side-effecting base -- #74, +// which also owns the pre-existing defaulted-hi len gap there), or +// a GLOBAL str base (no +16 load here, #73 -- matching the cstage +// carve-out keeps both stages byte-identical). cstage twin: +// cmd/w6c/cgen.c cg_base_cap. +fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { + if (base == nil) { return false; }; + if (base.kind != nkind.N_IDENT) { return false; }; + let baselocal: *local = localfindnode(c, base.str); + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + if (tn == nil) { return false; }; + if (tn.kind == nkind.N_TARRAY) { + let lenn: *node = tn.rhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (tn.kind == nkind.N_TSLICE) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + if (tn.kind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + return false; + }; + let gt: *node = letvartnode(c, base.str); + if (gt == nil) { return false; }; + if (gt.kind == nkind.N_TARRAY) { + let lenn: *node = gt.rhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (gt.kind == nkind.N_TSLICE) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + emitline("\tMOVQ\t16("); + emitline(dst); + emitline("), "); + emitline(dst); + emitline("\n"); + return true; + }; + return false; +}; + // 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. +// BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, +// return, or arg with the same triple ABI. cap is the storage +// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. +// Element scaling on the ptr isn't wired (lo*esz is #76). fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -14897,7 +14983,13 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tPOPQ\tAX\n"); emitline("\tADDQ\tCX, AX\n"); emitline("\tSUBQ\tCX, BX\n"); - emitline("\tMOVQ\tBX, CX\n"); + // cap = base_cap - lo (#20); CX=lo, BX=len here. + if (cgbasecap(c, base, "DX")) { + emitline("\tSUBQ\tCX, DX\n"); + emitline("\tMOVQ\tDX, CX\n"); + } else { + emitline("\tMOVQ\tBX, CX\n"); + }; }; fn cgmatch(c: *cgen, n: *node) void = { diff --git a/test/wcc/681_arr_elem_field_write.c b/test/wcc/681_arr_elem_field_write.c index f873e78f..eb1f8191 100644 --- a/test/wcc/681_arr_elem_field_write.c +++ b/test/wcc/681_arr_elem_field_write.c @@ -209,7 +209,7 @@ static const struct row rows[] = { " b.mark = 7;\n" " b.rbuf = rbuf;\n" " if (b.rbuf.len != 5) { return 1; };\n" - " if (b.rbuf.cap != 5) { return 2; };\n" + " if (b.rbuf.cap != 8) { return 2; };\n" " if (b.mark != 7) { return 3; };\n" " return 42;\n" "};\n", @@ -229,7 +229,7 @@ static const struct row rows[] = { " let b: bs;\n" " init(&b, raw[0:5]);\n" " if (b.rbuf.len != 5) { return 1; };\n" - " if (b.rbuf.cap != 5) { return 2; };\n" + " if (b.rbuf.cap != 8) { return 2; };\n" " if (b.mark != 9) { return 3; };\n" " return 42;\n" "};\n", @@ -240,10 +240,12 @@ static const struct row rows[] = { * patterns at raw[0] (0xAA) and raw[5] (0xFF) are read back * through b.rbuf[i], which only succeeds if .ptr (AX) survived * at +0. Trailing mark uses a distinctive value (0x33) so a - * stray CX store one slot too far is caught separately. .len - * and .cap are forced equal by ww's slice expression (both - * BX = hi-lo), so this row pins .ptr survival while the other - * rows pin .len/.cap distinctness from the zero default. */ + * stray CX store one slot too far is caught separately. Here + * .len=6 (hi-lo) and .cap=16 (base_cap-lo; raw is [16]u8) per + * project #20, so this row pins .ptr survival and additionally + * confirms .len and .cap land as distinct words (they no longer + * coincide), while the other rows pin .len/.cap distinctness + * from the zero default. */ { "slice_field_distinct_bytes", "type bs = struct { rbuf: []u8, mark: i32 };\n" "fn main() i32 = {\n" @@ -254,7 +256,7 @@ static const struct row rows[] = { " b.mark = 0x33;\n" " b.rbuf = raw[0:6];\n" " if (b.rbuf.len != 6) { return 1; };\n" - " if (b.rbuf.cap != 6) { return 2; };\n" + " if (b.rbuf.cap != 16) { return 2; };\n" " if (b.mark != 0x33) { return 3; };\n" " if (b.rbuf[0] != 0xAAu8) { return 4; };\n" " if (b.rbuf[5] != 0xFFu8) { return 5; };\n" diff --git a/test/wcc/693_dot_tagged_source.c b/test/wcc/693_dot_tagged_source.c index 34d447e3..f5edac4c 100644 --- a/test/wcc/693_dot_tagged_source.c +++ b/test/wcc/693_dot_tagged_source.c @@ -111,7 +111,7 @@ static const struct row rows[] = { " h.e = (raw[0:3]: ev);\n" " match (h.e) {\n" " case let v: []u8 => {\n" - " if (v.cap != 3) { return 1; };\n" + " if (v.cap != 8) { return 1; };\n" " return v.len: i32;\n" " };\n" " case let z: i32 => { return -1; };\n" @@ -170,7 +170,7 @@ static const struct row rows[] = { "fn f(h: *holder) i32 = {\n" " match (h.e) {\n" " case let v: []u8 => {\n" - " if (v.cap != 3) { return 1; };\n" + " if (v.cap != 8) { return 1; };\n" " return v.len: i32;\n" " };\n" " case let z: i32 => { return -1; };\n" @@ -226,7 +226,7 @@ static const struct row rows[] = { " let copy: ev = h.e;\n" " match (copy) {\n" " case let v: []u8 => {\n" - " if (v.cap != 3) { return 1; };\n" + " if (v.cap != 8) { return 1; };\n" " return v.len: i32;\n" " };\n" " case let z: i32 => { return -1; };\n" @@ -257,7 +257,7 @@ static const struct row rows[] = { " let copy: ev = g.e;\n" " match (copy) {\n" " case let v: []u8 => {\n" - " if (v.cap != 3) { return 1; };\n" + " if (v.cap != 8) { return 1; };\n" " return v.len: i32;\n" " };\n" " case let z: i32 => { return -1; };\n" diff --git a/test/wcc/695_match_bind_struct.c b/test/wcc/695_match_bind_struct.c index fa30b34d..5a2f52b2 100644 --- a/test/wcc/695_match_bind_struct.c +++ b/test/wcc/695_match_bind_struct.c @@ -168,7 +168,7 @@ static const struct row rows[] = { " h.e = (raw[0:3]: tag);\n" " match (h.e) {\n" " case let v: []u8 => {\n" - " if (v.cap != 3) { return 20; };\n" + " if (v.cap != 8) { return 20; };\n" " return v.len: i32;\n" " };\n" " case let p: pair => { return -1; };\n" diff --git a/test/wcc/942_subslice_cap_run.c b/test/wcc/942_subslice_cap_run.c new file mode 100644 index 00000000..52d203d0 --- /dev/null +++ b/test/wcc/942_subslice_cap_run.c @@ -0,0 +1,221 @@ +/* + * 942_subslice_cap_run — runtime coverage for task #20: a sub-slice + * `base[lo:hi]` must set its capacity word to base_cap - lo (the storage + * remaining to the underlying end; Go/Hare-identical), NOT hi - lo (the + * new length). base_cap is the array length N for `[N]T`, or the carried + * .capacity (+16) for a slice/str base. Cite (drew, in-tree): harec + * ref/harec/src/eval.c:1017 (slice: slice.cap -= start), eval.c:1024 + * (array: cap = array.length - start), check.c:596 (cap >= len), + * ref/hare/rt/ensure.ha:4-8 (capacity is a distinct field). + * + * Before the fix BOTH stages emitted cap = hi - lo (== len). Every row + * picks a shape where base_cap - lo != hi - lo, so a stale `cap = len` + * stage is observably wrong (the cap read returns hi-lo, or the append + * row reallocs instead of filling the base's spare). + * + * Rows (cstage cmd/w6c/cgen.c cg_base_cap + the N_SLICE value / call-arg + * paths; wwstage cgenexpr.ww cgbasecap + cgslice + cgenutil.ww twin): + * A array base, hi < N: `a[1:3]` over [8]u8 -> len 2, cap 8-1 = 7. + * B slice base with spare cap: p{len=6,cap=8}; `p[1:4]` -> len 3, + * cap 8-1 = 7 (cap carried from the header at +16, minus lo). + * C str base (D1: str[lo:hi] yields str, real .capacity, NO downgrade): + * sb = "hello" {len 5, cap 5}; `sb[1:3]` -> len 2, cap 5-1 = 4, + * sb[1] == 'e' (101). + * D append-no-realloc (strongest): `s = buf[1:3]` over a zeroed [8]u8 + * has len 2, cap 7; append(s, 99) must fill buf's spare at lo+len = 3 + * WITHOUT realloc. A `cap = len` stage sees len == cap (full) and + * reallocs into a fresh buffer, leaving buf[3] == 0. + * E hi-default with spare cap: p{len=6,cap=8}; `p[2:]` (hi defaults to + * base.len=6) -> len 4, cap 8-2 = 6 != len. Covers the distinct + * default-hi path; pre-fix cap = (defaulted hi - lo) = len = 4. + * + * In every row cap != len, so a dropped/wrong cap is caught directly. + * Verified pass-after (exit 0) on BOTH the cstage `ww` and wwstage + * `ww_ww` drivers; each row's cap assertion fails on a pre-fix stage. + * NNN < 950, self-contained (/tmp, no imports), so rule-14's selfhost- + * sibling race does not apply (mirrors the 928/932/941 precedent). + */ +#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; }; + +static const struct row rows[] = { + /* A — array base, hi < N. cap = N(8) - lo(1) = 7 != len(2). */ + { "subslice_array_hilt_n", + "export fn main() i32 = {\n" + " let a: [8]u8;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = (20 + i): u8; i += 1; };\n" + " let s: []u8 = a[1:3];\n" + " if (s.cap: i32 != 7) { return 1; };\n" + " if (s.len: i32 != 2) { return 2; };\n" + " if (s[0] != 21u8) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* B — slice base with spare cap. cap = base.cap(8) - lo(1) = 7 != + * len(3); base.cap is read from the header +16, not re-derived. */ + { "subslice_slice_spare_cap", + "export fn main() i32 = {\n" + " let a: [8]u8;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = (40 + i): u8; i += 1; };\n" + " let p: []u8; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n" + " let s: []u8 = p[1:4];\n" + " if (s.cap: i32 != 7) { return 1; };\n" + " if (s.len: i32 != 3) { return 2; };\n" + " if (s[0] != 41u8) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* C — str base (str[lo:hi] yields str, real .capacity). cap = + * sb.cap(5) - lo(1) = 4 != len(2). */ + { "subslice_str_base", + "export fn main() i32 = {\n" + " let sb: str = \"hello\";\n" + " let s: str = sb[1:3];\n" + " if (s.cap: i32 != 4) { return 1; };\n" + " if (s.len: i32 != 2) { return 2; };\n" + " if (s[0] != 101u8) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* D — append-no-realloc. s = buf[1:3] has cap 7 > len 2, so + * append(s,99) fills buf's spare at lo+len = 3. A cap=len stage + * reallocs (len==cap) and leaves buf[3] == 0. */ + { "subslice_append_no_realloc", + "export fn main() i32 = {\n" + " let buf: [8]u8;\n" + " let i: i32 = 0;\n" + " for (i < 8) { buf[i] = 0u8; i += 1; };\n" + " let s: []u8 = buf[1:3];\n" + " append(s, 99u8);\n" + " if (s.cap: i32 != 7) { return 1; };\n" + " if (s.len: i32 != 3) { return 2; };\n" + " if (s[2] != 99u8) { return 3; };\n" + " if (buf[3] != 99u8) { return 4; };\n" + " return 0;\n" + "};\n", + 0 }, + /* E — hi-default with spare cap. p{len=6,cap=8}; `p[2:]` defaults hi + * to base.len=6, so len = 6-2 = 4, but cap = base.cap(8) - lo(2) = 6 != + * len. Pre-fix the defaulted-hi path set cap = (hi - lo) = len = 4. */ + { "subslice_hidefault_spare_cap", + "export fn main() i32 = {\n" + " let a: [8]u8;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = (60 + i): u8; i += 1; };\n" + " let p: []u8; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n" + " let s: []u8 = p[2:];\n" + " if (s.cap: i32 != 6) { return 1; };\n" + " if (s.len: i32 != 4) { return 2; };\n" + " if (s[0] != 62u8) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[96], tmpdir[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/subslicecap_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/subslicecap_%d_d_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s", + tmpdir, driver, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[160]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); rmdir(tmpdir); + return got; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[512]; + if (bin[0] != '/') { + char cwd[256]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char cdrv[640]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + char wdrv[640]; + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + struct { const char *name; const char *path; int gated_on_existence; } + drivers[] = { + { "cstage", cdrv, 0 }, + { "wwstage", wdrv, 1 }, + { NULL, NULL, 0 }, + }; + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + for (int d = 0; drivers[d].name; d++) { + if (drivers[d].gated_on_existence + && access(drivers[d].path, X_OK) != 0) { + fprintf(stderr, + "subslice_cap_run: skip %s (no %s)\n", + drivers[d].name, drivers[d].path); + continue; + } + for (int i = 0; i < n; i++) { + int got = run_driver(drivers[d].path, &rows[i], i); + total++; + if (got != rows[i].want) { + fprintf(stderr, + "subslice_cap_run[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + } + + if (fail) { + fprintf(stderr, "subslice_cap_run: %d/%d fixtures failed\n", + fail, total); + return 1; + } + printf("subslice_cap_run: %d/%d ok\n", total, total); + return 0; +}