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.
222 lines
7.4 KiB
C
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;
|
|
}
|