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

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