Files
ww/test/wcc/943_subslice_ptresz_run.c
Hojun-Cho ab9de65b8e cgen: sub-slice ptr = base + lo*esz (both stages, #76)
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.
2026-05-25 02:36:54 +09:00

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