Files
ww/test/wcc/942_subslice_cap_run.c
Hojun-Cho 8b23ff3517 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.
2026-05-25 01:59:14 +09:00

222 lines
7.4 KiB
C

/*
* 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;
}