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.
308 lines
10 KiB
C
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;
|
|
}
|