Files
ww/test/wcc/946_const_slice_aggregate_run.c
Hojun-Cho 761cfa4524 wcc/cgen: #117 pin scalar-tuple-slice row (broad-arm in-suite net)
rob A-corrected: the broad TY_TUPLE arm stays; pin an accepted non-(str,*fn)
scalar-tuple slice in-suite. const [](i64,str) with TYPED 1i64 (untyped int
is ww-checker-rejected, #120); scalar-first so the i64 reads back via the
word0-correct whole-element cursor (#121: .1/word3 truncated). Distinct
values + len(t) + cs==ww byte-id lock the scalar-in-slice-backing path.
Base f8be2ae has no TY_TUPLE arm -> louds. Test-only; 17/17 ok.
2026-06-06 21:13:32 +09:00

308 lines
10 KiB
C

/*
* 946_const_slice_aggregate_run — #117/#119: const slice of (str,*fn)
* tuple rows + scalar &fn globals emit DATA (was both-stage LOUD at
* emit_slice_data / silent-no-DATA for the scalar &fn).
*
* #117: a `const [](str,*fn)` (fold-6's charclass_map shape) emitted NO
* DATA — emit_slice_data rode the scalar emit_array_lit_bytes backing and
* loud-stopped on the str/tuple/fn elements. Now the backing is k tuple
* rows (emit_tuple_row_*): each str element a 24B header + ptr→char DATAR,
* each &fn element an 8B slot + the BRAND-NEW &fn→DATAR reloc, at the 8B
* tuple-slot offsets (str@row+0/24B, *fn@row+24/8B = 32B/row, ken-confirmed
* layout). #119: a scalar `let p: *fn = &f` global wires the same reloc
* helper at the emit_lets 8B-scalar site (pre-#119: no DATA → undefined
* ref / garbage deref).
*
* POLARITY: align-BOTH — both stages were LOUD at base, no runtime
* reference. byte-id is BLIND (#263): the runtime READ-BACK is the net.
* The fn-reloc (the genuinely new machinery) is pinned at runtime with
* DISTINCT fns per row, so a wrong reloc is caught.
*
* READ-PATH NOTE (honest boundary): the indexed/ptr tuple-element FIELD
* reads off a const slice (`tbl[i].0`, `(&tbl[i]).0`) and the whole-element
* register-cursor read are pre-existing-broken (the #37/#58 index-cursor
* truncation + "unsupported field-read shape" — separate mechanisms, filed
* siblings; fold-6's compile arm also needs them). The one runtime
* observation available of the BACKING is the whole-element bind's word0
* (loaded correctly even by the truncating cursor): so the fn-reloc rows
* put the *fn FIRST (word0) to read it back, and the consumer-ordering
* (str,*fn) rows pin `len(tbl)` (header) + cs==ww byte-id on the DATA. The
* str-element header+reloc reuses the proven emit_tuple_data machinery
* (941 t3_global_elem runtime-pinned) — verified here by byte-id.
*
* NNN<950, self-contained (/tmp, no imports) — rule-14's selfhost-sibling
* race does not apply (941/944/945 precedent). Every K_RUN row also pins
* cstage/wwstage asm byte-id.
*/
#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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static const struct row rows[] = {
/* #117 fn-reloc readback: *fn FIRST so the whole-element bind's
* word0 (the only backing word the truncating index-cursor loads
* correctly) IS the fn ptr. DISTINCT fns per row — a wrong reloc
* (or a swapped per-row offset) is caught by calling each back. */
{ "fnfirst_reloc",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"fn fz(c: rune) bool = { return c == 'z'; };\n"
"const tbl: [](*fn(c: rune) bool, str) = [(&fa, \"aa\"), (&fz, \"zzz\")];\n"
"export fn main() i32 = {\n"
" let e0 = tbl[0];\n"
" let e1 = tbl[1];\n"
" let f0 = e0.0;\n"
" let f1 = e1.0;\n"
" if (!(*f0)('a')) { return 1; };\n"
" if ((*f0)('z')) { return 2; };\n"
" if (!(*f1)('z')) { return 3; };\n"
" if ((*f1)('a')) { return 4; };\n"
" if (len(tbl) != 2) { return 5; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* fold-6's exact consumer ordering: (str,*fn). The str + fn element
* relocs at the consumer-ordering slot offsets are pinned by cs==ww
* byte-id on the DATA; len(tbl) confirms the slice header resolves. */
{ "consumer_str_fn",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"fn fz(c: rune) bool = { return c == 'z'; };\n"
"const tbl: [](str, *fn(c: rune) bool) = [(\"aa\", &fa), (\"zzz\", &fz)];\n"
"export fn main() i32 = {\n"
" if (len(tbl) != 2) { return 1; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* multi-row str backing: 3 rows of distinct str lengths — pins the
* per-row 32B stride of the str-header backing (byte-id) + header. */
{ "consumer_3row",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"const tbl: [](str, *fn(c: rune) bool) = "
"[(\"a\", &fa), (\"bb\", &fa), (\"ccc\", &fa)];\n"
"export fn main() i32 = {\n"
" if (len(tbl) != 3) { return 1; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #117 broad-arm pin (rob): the TY_TUPLE arm also backs an ACCEPTED
* non-(str,*fn) scalar-tuple slice — (i64,str) with a TYPED i64 (a bare
* untyped int is ww-checker-rejected, #120). Scalar FIRST so the i64
* reads back via the word0-correct whole-element cursor (#121 read-path:
* .1/word3 is truncated, same constraint as the fn-first row). DISTINCT
* values catch a wrong per-row stride/offset; len(t) pins the header; the
* str element rides the 32B backing (its DATAR + len pinned by cs==ww
* byte-id). Base f8be2ae has no TY_TUPLE arm → louds (broad arm is new). */
{ "scalar_tuple_slice",
"package main;\n"
"const t: [](i64, str) = [(10i64, \"a\"), (20i64, \"bb\")];\n"
"export fn main() i32 = {\n"
" let e0 = t[0];\n"
" let e1 = t[1];\n"
" let v0 = e0.0;\n"
" let v1 = e1.0;\n"
" if (v0 != 10) { return 1; };\n"
" if (v1 != 20) { return 2; };\n"
" if (len(t) != 2) { return 3; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* #119: scalar &fn module-globals — DISTINCT fns, each called back
* through the reloc (pre-#119: no DATA → undefined ref / garbage). */
{ "scalar_fnptr",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"fn fz(c: rune) bool = { return c == 'z'; };\n"
"let pf: *fn(c: rune) bool = &fa;\n"
"let pg: *fn(c: rune) bool = &fz;\n"
"export fn main() i32 = {\n"
" if (!(*pf)('a')) { return 1; };\n"
" if ((*pf)('z')) { return 2; };\n"
" if (!(*pg)('z')) { return 3; };\n"
" if ((*pg)('a')) { return 4; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* honest boundary (rule 7): a non-tuple aggregate element (here a
* bare []str) stays LOUD, symmetric both stages — #117 is NARROW to
* the (str,*fn) tuple form, not the full slice-of-aggregate family. */
{ "loud_slice_of_str",
"package main;\n"
"const xs: []str = [\"a\", \"b\"];\n"
"export fn main() i32 = { return len(xs): i32; };\n", 0,
K_BUILDERR, "slice-of-{str,slice,tagged}" },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[96], tmpdir[96], errf[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/csa_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/csa_%d_d_%d", getpid(), i);
snprintf(errf, sizeof errf, "/tmp/csa_%d_e_%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 >/dev/null 2>%s",
tmpdir, driver, src, errf);
int brc = runwait(cmd);
if (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || errlog_has(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud builderr "
"\"%s\" (brc=%d)\n", r->label, driver,
r->experr ? r->experr : "", brc);
unlink(src); unlink(errf); rmdir(tmpdir);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); unlink(errf); rmdir(tmpdir);
return -1;
}
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[256];
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); unlink(errf); rmdir(tmpdir);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/csa_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/csa_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/csa_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
if (rows[i].kind == K_BUILDERR)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "const_slice_aggregate: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("const_slice_aggregate: %d/%d ok\n", total, total);
return 0;
}