cgen: sub-slice cap = base_cap - lo

A sub-slice `base[lo:hi]` now sets cap to base_cap - lo (the storage
remaining to the underlying end; Go/Hare-identical) instead of hi - lo
(== len). base_cap is the array length N for [N]T, or the .capacity
word carried in a slice/str header at +16. Authored once per stage in
the cg_base_cap / cgbasecap helper, applied at both cap sites: the
N_SLICE value path (which serves let-init since the prior commit) and
the call-arg push. Both stages stay byte-identical (find-4 closed).

cap arithmetic per ref/harec/src/eval.c:1017 (slice: slice.cap -=
start) and eval.c:1024 (array: cap = array.length - start); capacity
is a distinct field per ref/hare/rt/ensure.ha:4-8 and cap >= len per
ref/harec/src/check.c:596. Only the cap arithmetic transfers: the ptr
stays unscaled (lo*esz is #76) and eval.c's stricter start>=end bound
is not ported (ww's runtime bound is start>end).

str[lo:hi] yields str with a real .capacity (D1), so the str base uses
the same +16 load -- no downgrade to []u8. base_cap falls back to len
(prior behavior) where it isn't cleanly available: a non-ident base
(its header cap was discarded by cgexpr; len is likewise wrong for a
defaulted hi there, pre-existing) and a global str base (wwstage
cgslice has no global-str load, #73 -- the carve-out keeps both
stages byte-identical).

Test: 942_subslice_cap_run, table-driven over both drivers, array /
slice / str base + an append-no-realloc row, each shape chosen so
base_cap-lo != hi-lo.

Fold in three pre-existing fixtures that asserted the old cap == len
and so failed under the corrected semantics (project #20):
681_arr_elem_field_write (slice_field_value_write,
slice_field_ptr_write, slice_field_distinct_bytes),
693_dot_tagged_source (local_struct_slice_variant,
via_ptr_slice_variant, letinit_slice_roundtrip, top_level_global_slice),
and 695_match_bind_struct (slice_neg_control). Each cap word updated to
base_cap - lo: a [8]u8 base sliced at lo=0 yields cap 8 (5->8, 3->8);
distinct_bytes slices a [16]u8 at lo=0, yielding cap 16 (6->16). len /
mark / ptr assertions are unchanged -- only the cap word moved.
This commit is contained in:
2026-05-25 01:12:38 +09:00
parent 324df92e1d
commit 8b23ff3517
10 changed files with 595 additions and 39 deletions

View File

@@ -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 $@ $<

View File

@@ -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));
/* 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:

View File

@@ -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");
// 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 = {

View File

@@ -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");
// 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 = {

View File

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

View File

@@ -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");
// 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 = {

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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;
}