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

@@ -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 = {

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

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