A sub-slice base[lo:hi] advanced its data pointer by lo (element COUNT) instead of lo*esz (BYTES), so the base pointer was wrong for any esz>1 element. Pointer arithmetic is membsz-unit per the rt invariant (ref/hare/rt/ensure.ha:30); esz==1 (u8/str) is unchanged. Four emission sites, fixed byte-identically across stages (rule 10): - value path: cmd/w6c/cgen.c N_SLICE <-> cgenexpr.ww cgslice - call-arg: cmd/w6c/cgen.c:4646 <-> cgenutil.ww pushargsrev Scaling mirrors the cgindex idiom: esz from the type table (rule 13; cstage bu->sub->size, wwstage elemsizeofc) gated to an N_IDENT base, uniform IMULQ (no SHL special-case, no immediate form -- w6a is reg-reg only). The live lo reg is the multiplicand so the one free GP (DX value / BX arg) holds esz*lo; lo is preserved for len (hi-lo) and cap (base_cap-lo, #20). The esz==1 path keeps the single ADDQ, byte-identical to before (#75/#20/str unaffected). Non-ident bases stay unscaled in both stages (wwstage has no tnode there), tracked as a #76 residual alongside #74. New 943_subslice_ptresz_run: table-driven, dual-driver (ww/ww_ww), esz in {2,4,8} array+slice base, lo>0, let-form + call-arg form; asserts s[0]==base[lo] & s[1]==base[lo+1]. Fails on every fixture pre-fix on both stages, passes post-fix. Registered in Makefile (TESTS + target) so test/run builds and runs it.
224 lines
7.1 KiB
C
224 lines
7.1 KiB
C
/*
|
|
* 943_subslice_ptresz_run — runtime coverage for project #76: a sub-slice
|
|
* `base[lo:hi]` must advance its DATA pointer by lo*esz (BYTES), not by lo
|
|
* (element COUNT). The rt invariant is membsz-unit pointer arithmetic
|
|
* (ref/hare/rt/ensure.ha:30). For esz==1 (u8/str) lo*1 == lo, so those
|
|
* paths are unaffected; the bug only bites esz>1 elements.
|
|
*
|
|
* Before the fix BOTH stages emitted ptr = base + lo (unscaled), so the
|
|
* first element of the sub-slice was read at byte offset `lo` into the
|
|
* base storage — garbage straddling base[0]/base[1] for any esz>1. Each
|
|
* row picks values where the unscaled read cannot alias the scaled one,
|
|
* so a stale `+lo` stage is observably wrong (returns garbage, not the
|
|
* expected element).
|
|
*
|
|
* Sites exercised (all four; cstage cmd/w6c/cgen.c N_SLICE value path +
|
|
* N_SLICE call-arg fast-path; wwstage cgenexpr.ww cgslice + cgenutil.ww
|
|
* pushargsrev twin) — esz scaled via the type table, mirroring the
|
|
* cgindex idiom (rule 13):
|
|
* A esz=4 array base, let-form (value path): `a[2:5]` over [8]i32,
|
|
* a[i]=1000+i -> s[0]==1002, s[1]==1003 (pre-fix reads byte off 2).
|
|
* B esz=2 array base, let-form: `a[3:6]` over [8]i16 -> s[0]==103.
|
|
* C esz=8 array base, let-form: `a[1:4]` over [8]i64 -> s[0]==5001.
|
|
* D esz=4 slice base, let-form: p{ptr=&a,len=6,cap=8}; `p[2:5]` ->
|
|
* s[0]==1002 (base ptr carried from the header, then +lo*esz).
|
|
* E esz=4 call-arg (the pushargsrev/cgen.c:4646 twin): pass `a[3:6]`
|
|
* to a fn reading s[0]/s[1] -> 1003 / 1004.
|
|
* F esz=8 call-arg: pass `a[2:5]` over [8]i64 -> s[0]==5002, s[1]==5003.
|
|
*
|
|
* NNN < 950, self-contained (/tmp, no imports), so rule-14's selfhost-
|
|
* sibling race does not apply (mirrors the 928/932/941/942 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 — esz=4 array base, let-form (value path). s[0]=a[2]=1002. */
|
|
{ "subslice_esz4_array_let",
|
|
"export fn main() i32 = {\n"
|
|
" let a: [8]i32;\n"
|
|
" let i: i32 = 0;\n"
|
|
" for (i < 8) { a[i] = 1000 + i; i += 1; };\n"
|
|
" let s: []i32 = a[2:5];\n"
|
|
" if (s.len: i32 != 3) { return 1; };\n"
|
|
" if (s[0] != 1002) { return 2; };\n"
|
|
" if (s[1] != 1003) { return 3; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* B — esz=2 array base, let-form. s[0]=a[3]=103. */
|
|
{ "subslice_esz2_array_let",
|
|
"export fn main() i32 = {\n"
|
|
" let a: [8]i16;\n"
|
|
" let i: i32 = 0;\n"
|
|
" for (i < 8) { a[i] = (100 + i): i16; i += 1; };\n"
|
|
" let s: []i16 = a[3:6];\n"
|
|
" if (s.len: i32 != 3) { return 1; };\n"
|
|
" if (s[0]: i32 != 103) { return 2; };\n"
|
|
" if (s[1]: i32 != 104) { return 3; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* C — esz=8 array base, let-form. s[0]=a[1]=5001. */
|
|
{ "subslice_esz8_array_let",
|
|
"export fn main() i32 = {\n"
|
|
" let a: [8]i64;\n"
|
|
" let i: i32 = 0;\n"
|
|
" for (i < 8) { a[i] = (5000 + i): i64; i += 1; };\n"
|
|
" let s: []i64 = a[1:4];\n"
|
|
" if (s.len: i32 != 3) { return 1; };\n"
|
|
" if (s[0]: i32 != 5001) { return 2; };\n"
|
|
" if (s[1]: i32 != 5002) { return 3; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* D — esz=4 slice base, let-form. base ptr from header + lo*esz.
|
|
* s[0]=a[2]=1002. */
|
|
{ "subslice_esz4_slice_let",
|
|
"export fn main() i32 = {\n"
|
|
" let a: [8]i32;\n"
|
|
" let i: i32 = 0;\n"
|
|
" for (i < 8) { a[i] = 1000 + i; i += 1; };\n"
|
|
" let p: []i32; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n"
|
|
" let s: []i32 = p[2:5];\n"
|
|
" if (s.len: i32 != 3) { return 1; };\n"
|
|
" if (s[0] != 1002) { return 2; };\n"
|
|
" if (s[1] != 1003) { return 3; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* E — esz=4 call-arg (pushargsrev / cgen.c:4646 twin). The fn reads
|
|
* s[0]/s[1] of `a[3:6]` -> 1003 / 1004. */
|
|
{ "subslice_esz4_call_arg",
|
|
"fn e0(s: []i32) i32 = { return s[0]; };\n"
|
|
"fn e1(s: []i32) i32 = { return s[1]; };\n"
|
|
"export fn main() i32 = {\n"
|
|
" let a: [8]i32;\n"
|
|
" let i: i32 = 0;\n"
|
|
" for (i < 8) { a[i] = 1000 + i; i += 1; };\n"
|
|
" if (e0(a[3:6]) != 1003) { return 1; };\n"
|
|
" if (e1(a[3:6]) != 1004) { return 2; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
0 },
|
|
/* F — esz=8 call-arg. The fns read s[0]/s[1] of `a[2:5]` -> 5002 / 5003. */
|
|
{ "subslice_esz8_call_arg",
|
|
"fn f0(s: []i64) i64 = { return s[0]; };\n"
|
|
"fn f1(s: []i64) i64 = { return s[1]; };\n"
|
|
"export fn main() i32 = {\n"
|
|
" let a: [8]i64;\n"
|
|
" let i: i32 = 0;\n"
|
|
" for (i < 8) { a[i] = (5000 + i): i64; i += 1; };\n"
|
|
" if (f0(a[2:5]): i32 != 5002) { return 1; };\n"
|
|
" if (f1(a[2:5]): i32 != 5003) { return 2; };\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/subsliceptr_%d_%d.ww", getpid(), i);
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/subsliceptr_%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_ptresz_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_ptresz_run[%s][%s]: exit=%d want=%d\n",
|
|
drivers[d].name, rows[i].label,
|
|
got, rows[i].want);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "subslice_ptresz_run: %d/%d fixtures failed\n",
|
|
fail, total);
|
|
return 1;
|
|
}
|
|
printf("subslice_ptresz_run: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|