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:
7
Makefile
7
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 $@ $<
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
221
test/wcc/942_subslice_cap_run.c
Normal file
221
test/wcc/942_subslice_cap_run.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user