94a of the C-mig2 split (rob rule-11): 13 Pattern-A gates that fed a .combined.ww to w6c/w6c_ww now drive two --sep builds (ww / ww_ww) and byte-diff the concatenated per-package .sepwork/*.s, mirroring #93's convention. Lands pre-flip while combined.ww still exists as the reversible safety net. Test-only; all 5 binary pins HOLD. 989_enumcap_run deferred to 94b (its decisive assertion is a combined.ww content diff, not a .s diff).
353 lines
11 KiB
C
353 lines
11 KiB
C
/*
|
|
* 784_xmod_alias_struct_collide_run — project #223 runtime + byte-id
|
|
* net. A struct-field access through a pointer-ALIAS receiver
|
|
* (`s.<field>` where `s` is a `type s = *vtable` alias) miscompiled in
|
|
* wwstage when the alias leaf collided with a DIFFERENT module's
|
|
* same-leaf STRUCT (the eFinal io.stream = *vtable alias vs the
|
|
* memio.stream struct). cgen's #191 alias-peel loop
|
|
* (selfhost/cmd/wcc/cgenexpr.ww cgdot) broke on the name-keyed
|
|
* structlookup's any-module fallback — it found the FOREIGN struct,
|
|
* halted the peel with the receiver still N_TNAME, skipped the
|
|
* pointer-to-struct field-load arm, and the field load fell through to
|
|
* the SB-global fallback: `MOVQ <field>(SB), AX` (an undefined symbol →
|
|
* w6l link-fail / garbage). cstage is correct — type_chase_named
|
|
* (cmd/w6c/cgen.c) follows the resolved NAMED.under POINTER chain
|
|
* (module-correct), never a name re-lookup. Sibling of #208 (lossy
|
|
* name-keyed resolution leaking to a global leaf, but in the CHECKER).
|
|
*
|
|
* Fix (wwstage-only align-down, cgenexpr.ww:1769 peel loop): the
|
|
* struct-break is now MODULE-AWARE — break ONLY on a struct that THIS
|
|
* module defines (structsamemod), a same-module ALIAS keeps peeling
|
|
* (aliassamemod), and a foreign leaf falls back to the prior
|
|
* any-module heuristic.
|
|
*
|
|
* GATE-BLIND in the bootstrap: the io.stream-alias vs memio.stream-
|
|
* struct collision is the only same-leaf alias-vs-struct cross-module
|
|
* pair, and it only entered the corpus at the #94 eFinal FLIP (master
|
|
* never had it). A single-module synthetic does NOT trigger — the
|
|
* collision needs a SECOND module contributing the same-leaf struct
|
|
* into the corpus. Hence this 2-module probe.
|
|
*
|
|
* row | shape | exit | byte-id
|
|
* -----------+----------------------------------------+------+--------
|
|
* collision | sa: type s=*vtable + vtable{reader} + | 42 | cs==ww
|
|
* | dispatcher `read(x:s) match(x.reader)`| |
|
|
* | sb: type s=struct{} (same leaf, NO | |
|
|
* | reader field) — the #223 trigger | |
|
|
* symmetric | sa: type s=struct{} + value-receiver | 21 | cs==ww
|
|
* | `geta(x:s)`; sb: type s=*vtable alias | |
|
|
* | — guards the same-module-struct break | |
|
|
* | still fires (a naive alias-first | |
|
|
* | reorder would mis-peel A's struct) | |
|
|
* control | sa: alias+vtable+dispatcher, NO foreign | 30 | cs==ww
|
|
* | same-leaf struct — common io-like case| |
|
|
*
|
|
* The dispatcher uses a BRANCHED callee (void arm → -1, fn-ptr arm →
|
|
* v+100) so the field load actually feeds control flow (gate-blind
|
|
* discipline). cstage `ww build` + run pins runtime; raw w6c vs w6c_ww
|
|
* `.s` cmp on the driver-produced combined.ww pins rule-10 byte-id
|
|
* (the discriminating net for #223 — pre-fix the dispatcher diverges
|
|
* `MOVQ reader(SB)` vs the pointer-field load).
|
|
*
|
|
* Filed-not-fixed here: #224 (the broader cross-module same-leaf STRUCT
|
|
* name-keying at structlookupchain) — distinct, not FLIP-triggered.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.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);
|
|
int cb = fgetc(fb);
|
|
if (ca != cb) { rc = -1; break; }
|
|
if (ca == EOF) break;
|
|
}
|
|
fclose(fa); fclose(fb);
|
|
return rc;
|
|
}
|
|
|
|
struct file { const char *name; const char *src; };
|
|
|
|
struct scenario {
|
|
const char *label;
|
|
const struct file *files; /* name==NULL terminates */
|
|
int want_exit;
|
|
};
|
|
|
|
/* ---- collision: the #223 bug ------------------------------------- */
|
|
static const struct file collision_files[] = {
|
|
{ "sa.ww",
|
|
"package sa;\n"
|
|
"\n"
|
|
"export type reader = fn(x: s, v: i32) i32;\n"
|
|
"\n"
|
|
"export type vtable = struct {\n"
|
|
" reader: (*reader | void),\n"
|
|
"};\n"
|
|
"\n"
|
|
"export type s = *vtable;\n"
|
|
"\n"
|
|
"export fn read(x: s, v: i32) i32 = {\n"
|
|
" match (x.reader) {\n"
|
|
" case void => return -1;\n"
|
|
" case let f: *reader => return (*f)(x, v);\n"
|
|
" };\n"
|
|
"};\n"
|
|
"\n"
|
|
"export fn mkvt(f: *reader) vtable = {\n"
|
|
" return vtable { reader = f };\n"
|
|
"};\n"
|
|
"\n"
|
|
"export fn mkempty() vtable = {\n"
|
|
" return vtable { reader = void };\n"
|
|
"};\n" },
|
|
/* sb's `s` is a STRUCT sharing the leaf `s` with sa's alias — the
|
|
* name-keyed structlookup any-module fallback used to halt sa.read's
|
|
* alias-peel here. No `reader` field, so the mis-resolved field walk
|
|
* fell through to `MOVQ reader(SB)`. */
|
|
{ "sb.ww",
|
|
"package sb;\n"
|
|
"\n"
|
|
"export type s = struct {\n"
|
|
" a: i32,\n"
|
|
" b: i32,\n"
|
|
"};\n" },
|
|
{ "main.ww",
|
|
"package main;\n"
|
|
"\n"
|
|
"import sa;\n"
|
|
"import sb;\n"
|
|
"\n"
|
|
"fn cb(x: sa.s, v: i32) i32 = { return v + 100; };\n"
|
|
"\n"
|
|
"export fn main() i32 = {\n"
|
|
" let vt = sa.mkvt((&cb): *sa.reader);\n"
|
|
" let st: sa.s = &vt;\n"
|
|
" let r = sa.read(st, 5);\n"
|
|
" let vt2 = sa.mkempty();\n"
|
|
" let st2: sa.s = &vt2;\n"
|
|
" let r2 = sa.read(st2, 5);\n"
|
|
" if (r != 105) { return 11; };\n"
|
|
" if (r2 != -1) { return 12; };\n"
|
|
" return 42;\n"
|
|
"};\n" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
/* ---- symmetric: A struct, B alias; receiver is A's struct value -- */
|
|
static const struct file symmetric_files[] = {
|
|
{ "sa.ww",
|
|
"package sa;\n"
|
|
"\n"
|
|
"export type s = struct {\n"
|
|
" a: i32,\n"
|
|
" b: i32,\n"
|
|
"};\n"
|
|
"\n"
|
|
"export fn mk(av: i32, bv: i32) s = {\n"
|
|
" return s { a = av, b = bv };\n"
|
|
"};\n"
|
|
"\n"
|
|
"export fn geta(x: s) i32 = {\n"
|
|
" return x.a;\n"
|
|
"};\n" },
|
|
{ "sb.ww",
|
|
"package sb;\n"
|
|
"\n"
|
|
"export type vtable = struct {\n"
|
|
" q: i32,\n"
|
|
"};\n"
|
|
"\n"
|
|
"export type s = *vtable;\n" },
|
|
{ "main.ww",
|
|
"package main;\n"
|
|
"\n"
|
|
"import sa;\n"
|
|
"import sb;\n"
|
|
"\n"
|
|
"export fn main() i32 = {\n"
|
|
" let o = sa.mk(21, 8);\n"
|
|
" let r = sa.geta(o);\n"
|
|
" if (r != 21) { return 11; };\n"
|
|
" return 21;\n"
|
|
"};\n" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
/* ---- control: same-module alias+vtable, no foreign same-leaf ----- */
|
|
static const struct file control_files[] = {
|
|
{ "sa.ww",
|
|
"package sa;\n"
|
|
"\n"
|
|
"export type reader = fn(x: s, v: i32) i32;\n"
|
|
"\n"
|
|
"export type vtable = struct {\n"
|
|
" reader: (*reader | void),\n"
|
|
"};\n"
|
|
"\n"
|
|
"export type s = *vtable;\n"
|
|
"\n"
|
|
"export fn read(x: s, v: i32) i32 = {\n"
|
|
" match (x.reader) {\n"
|
|
" case void => return -1;\n"
|
|
" case let f: *reader => return (*f)(x, v);\n"
|
|
" };\n"
|
|
"};\n"
|
|
"\n"
|
|
"export fn mkvt(f: *reader) vtable = {\n"
|
|
" return vtable { reader = f };\n"
|
|
"};\n" },
|
|
{ "main.ww",
|
|
"package main;\n"
|
|
"\n"
|
|
"import sa;\n"
|
|
"\n"
|
|
"fn cb(x: sa.s, v: i32) i32 = { return v + 100; };\n"
|
|
"\n"
|
|
"export fn main() i32 = {\n"
|
|
" let vt = sa.mkvt((&cb): *sa.reader);\n"
|
|
" let st: sa.s = &vt;\n"
|
|
" let r = sa.read(st, 5);\n"
|
|
" if (r != 105) { return 11; };\n"
|
|
" return 30;\n"
|
|
"};\n" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static const struct scenario scenarios[] = {
|
|
{ "collision", collision_files, 42 },
|
|
{ "symmetric", symmetric_files, 21 },
|
|
{ "control", control_files, 30 },
|
|
};
|
|
|
|
static int
|
|
run_scenario(const char *bin, const char *cdrv, const char *wdrv,
|
|
const struct scenario *sc)
|
|
{
|
|
(void)bin;
|
|
/* Private fixture dir — #215: srcd is this dir (not shared /tmp),
|
|
* so the driver's srcd-first import search can't pick up a
|
|
* polluting same-name file. mkdtemp gives a unique path. */
|
|
char dir[] = "/tmp/ww784_XXXXXX";
|
|
if (mkdtemp(dir) == NULL) {
|
|
fprintf(stderr, "784[%s]: mkdtemp failed\n", sc->label);
|
|
return -1;
|
|
}
|
|
|
|
char path[1024], cmd[4096];
|
|
int rc = 0;
|
|
|
|
for (int i = 0; sc->files[i].name; i++) {
|
|
snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name);
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) { fprintf(stderr, "784[%s]: write %s\n", sc->label,
|
|
sc->files[i].name); rc = -1; goto done; }
|
|
fputs(sc->files[i].src, f);
|
|
fclose(f);
|
|
}
|
|
|
|
/* #94 sep layout: cstage --sep build + run pins runtime; pin
|
|
* WW_PKGCACHE under the scratch dir so out/.pkgcache is untouched. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && WW_PKGCACHE=%s/pkgc_c %s build --sep -I %s -o %s/main %s/main.ww",
|
|
dir, dir, cdrv, dir, dir, dir);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "784[%s]: cstage build failed\n", sc->label);
|
|
rc = -1; goto done;
|
|
}
|
|
snprintf(path, sizeof path, "%s/main", dir);
|
|
int got = runwait(path);
|
|
if (got != sc->want_exit) {
|
|
fprintf(stderr, "784[%s]: cstage exit %d, want %d\n",
|
|
sc->label, got, sc->want_exit);
|
|
rc = -1;
|
|
}
|
|
|
|
/* Byte-id net (the #223 discriminator): the wwstage driver's --sep
|
|
* build emits per-package w6c_ww asm; pre-fix the dispatcher diverges.
|
|
* The root + each imported pkg compile to separate <stem>.sepwork/<pkg>.s;
|
|
* concat (sorted glob, identical set both stages) for the compare. */
|
|
char cs_s[1024], ws_s[1024];
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && WW_PKGCACHE=%s/pkgc_w %s build --sep -I %s -o %s/mainww %s/main.ww",
|
|
dir, dir, wdrv, dir, dir, dir);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "784[%s]: ww_ww build failed\n", sc->label);
|
|
rc = -1; goto done;
|
|
}
|
|
snprintf(cs_s, sizeof cs_s, "%s/all_cs.s", dir);
|
|
snprintf(ws_s, sizeof ws_s, "%s/all_ww.s", dir);
|
|
snprintf(cmd, sizeof cmd, "cat %s/main.sepwork/*.s > %s 2>/dev/null",
|
|
dir, cs_s); if (system(cmd)) {}
|
|
snprintf(cmd, sizeof cmd, "cat %s/mainww.sepwork/*.s > %s 2>/dev/null",
|
|
dir, ws_s); if (system(cmd)) {}
|
|
if (slurp_eq(cs_s, ws_s) != 0) {
|
|
fprintf(stderr, "784[%s]: cs.s/ww.s DIFFER (rule-10 byte-id "
|
|
"violation — #223 regression)\n", sc->label);
|
|
rc = -1;
|
|
}
|
|
|
|
done:
|
|
/* Best-effort cleanup of the private dir. */
|
|
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
|
(void)runwait(cmd);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = getenv("BIN");
|
|
if (!bin) bin = "out/bin";
|
|
char absbin[2048];
|
|
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[2100], wdrv[2100];
|
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
if (access(wdrv, X_OK) != 0) {
|
|
fprintf(stderr, "784: ww_ww missing — cannot run the cs==ww "
|
|
"byte-id gate (the whole point of this test)\n");
|
|
return 1;
|
|
}
|
|
|
|
int n = (int)(sizeof scenarios / sizeof scenarios[0]);
|
|
int fail = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
if (run_scenario(bin, cdrv, wdrv, &scenarios[i]) != 0)
|
|
fail++;
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "784 xmod_alias_struct_collide: %d/%d "
|
|
"scenarios failed\n", fail, n);
|
|
return 1;
|
|
}
|
|
printf("xmod_alias_struct_collide: %d/%d ok (cstage run + cs==ww "
|
|
"byte-id)\n", n, n);
|
|
return 0;
|
|
}
|