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