wwstage: make the cgdot alias-peel struct-break module-aware (#223)
The wwstage cgdot #191 alias-peel loop broke on a name-keyed any-module structlookup, so a receiver whose alias name collides with a struct of the same name in ANOTHER module resolved to the foreign struct and fell through to an undefined `name(SB)` global instead of the field load. The eFinal FLIP renames io's `vstream` -> `stream`, which collides with memio's `stream` struct, so io.read/io.write/io.close's `match (s.reader)` emitted `MOVQ reader(SB), AX` (reader is also a type-alias) -> cs != ww (cstage chases the nominal TY_NAMED.under pointer chain, module-correct). Gate-blind: on master both stages emit the same wrong store so byte-id stays green; the FLIP corpus is the first to put the io-alias/memio-struct collision in one build. Fix (wwstage-only align-down; cstage is the authority and is untouched): make the peel's struct-break MODULE-AWARE — break only on a same-module struct (a genuine struct-value receiver); a same-module alias keeps peeling to its underlying (io.stream -> *vtable -> the pointer field-load arm); a foreign leaf keeps the prior any-module heuristic. New structsamemod / aliassamemod mirror the same-module-first pass already in structlookup / aliaslookup. This is not a naive alias-first reorder (which would reintroduce the mirror collision: a same-module struct plus a foreign same-leaf alias). Peel-only — the direct- struct arm's broader cross-module same-leaf-STRUCT name-keying is filed as #224. #208-family (name-keyed resolution dropping to a wrong global) but in cgen, not the checker; #213 is distinct (cosmetic local-struct-match divergence). test/wcc/784_xmod_alias_struct_collide_run: collision (cross-module alias-vs- struct, same leaf), symmetric (guards the same-module-struct break against a naive reorder), and a no-collision control — branched callee. The discriminating net is cs.s == ww.s (the path is gate-blind and cstage is correct, so byte-id flips when wwstage is fixed); confirmed by source-revert. The FLIP's combined.ww is now cs.s == ww.s byte-identical.
This commit is contained in:
12
Makefile
12
Makefile
@@ -335,6 +335,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_fmt_vstream_compositions_run \
|
||||
$(BIN)/test_fieldfn_leaf_collide_run \
|
||||
$(BIN)/test_amp_fn_assign_run \
|
||||
$(BIN)/test_xmod_alias_struct_collide_run \
|
||||
$(BIN)/test_bufio_vstream_run \
|
||||
$(BIN)/test_log_vstream_run \
|
||||
$(BIN)/test_use_promote_alias \
|
||||
@@ -728,6 +729,17 @@ $(BIN)/test_amp_fn_assign_run: test/wcc/783_amp_fn_assign_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #223: a struct-field access through a pointer-ALIAS receiver whose
|
||||
# leaf collides with another module's same-leaf STRUCT. cstage driver
|
||||
# build + run for runtime, raw w6c vs w6c_ww .s cmp on the combined.ww
|
||||
# for rule-10 byte-id (the #223 discriminator). Builds its own 2-module
|
||||
# fixtures in a private mktemp dir — not a selfhost-driver test.
|
||||
$(BIN)/test_xmod_alias_struct_collide_run: test/wcc/784_xmod_alias_struct_collide_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_fmt_vstream_mods_run: test/wcc/780_fmt_vstream_mods_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -16142,6 +16142,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// #223: same-module-ONLY struct lookup. structlookup's any-module
|
||||
// fallback returns a foreign same-leaf struct; the cgdot alias-peel
|
||||
// needs to break ONLY on a struct that THIS module defines (a genuine
|
||||
// struct-value receiver), not on a foreign struct that merely shares a
|
||||
// leaf with a same-module alias (io.stream alias vs memio.stream
|
||||
// struct). Returns the struct only when it lives in c.curmod.
|
||||
fn structsamemod(c: *cgen, name: str) *structinfo = {
|
||||
let s: *structinfo = c.structs;
|
||||
for (s != nil) {
|
||||
if (streq(s.sname, name)) {
|
||||
if (streq(s.smod, c.curmod)) { return s; };
|
||||
};
|
||||
s = s.sinext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
||||
// recognised as a primitive — the caller falls back to other paths).
|
||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
||||
@@ -20129,10 +20146,30 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// builds NAMED chains (project_tinfo_lossy_nominal). Stops
|
||||
// at struct aliases so the existing direct-struct arm below
|
||||
// stays byte-id with pre-fix #22 callers. #191.
|
||||
//
|
||||
// #223: the break is MODULE-AWARE. cstage type_chase_named
|
||||
// follows the resolved NAMED.under pointer (module-correct);
|
||||
// wwstage re-resolves by name (lossy), so a same-module
|
||||
// alias whose leaf collides with a FOREIGN struct of the
|
||||
// same name (io.stream = *vtable vs memio.stream struct)
|
||||
// would wrongly halt the peel at the foreign struct via
|
||||
// structlookup's any-module fallback → field load drops to a
|
||||
// bogus `MOVQ <fld>(SB)`. Sibling of #208 (lossy name-keyed
|
||||
// resolution leaking to a global leaf). Break ONLY on a
|
||||
// same-module struct (a genuine struct-value receiver); a
|
||||
// same-module alias keeps peeling; a foreign leaf (in
|
||||
// neither registry for c.curmod) falls back to the prior
|
||||
// any-module heuristic. The broader cross-module same-leaf
|
||||
// STRUCT name-keying at the direct-struct arm below is
|
||||
// filed separately as #224 (not FLIP-triggered).
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
if (structsamemod(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliassamemod(c, tn.str);
|
||||
if (nx == nil) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
nx = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
};
|
||||
tn = nx;
|
||||
};
|
||||
if (tn == nil) { return; };
|
||||
@@ -28373,6 +28410,22 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// #223: same-module-ONLY alias resolution. aliaslookup's any-module
|
||||
// fallback can return a foreign same-leaf alias; the alias-peel in
|
||||
// cgdot needs to know whether THIS module defines the name as an alias
|
||||
// (so the peel continues) without that cross-module fallback. Returns
|
||||
// the alias target only when an alias of `name` lives in c.curmod.
|
||||
fn aliassamemod(c: *cgen, name: str) *node = {
|
||||
let a: *aliasent = c.aliases;
|
||||
for (a != nil) {
|
||||
if (streq(a.aname, name)) {
|
||||
if (streq(a.amod, c.curmod)) { return a.target; };
|
||||
};
|
||||
a = a.aanext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// ---- enum registry --------------------------------------------------
|
||||
//
|
||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
||||
|
||||
@@ -139,6 +139,22 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// #223: same-module-ONLY alias resolution. aliaslookup's any-module
|
||||
// fallback can return a foreign same-leaf alias; the alias-peel in
|
||||
// cgdot needs to know whether THIS module defines the name as an alias
|
||||
// (so the peel continues) without that cross-module fallback. Returns
|
||||
// the alias target only when an alias of `name` lives in c.curmod.
|
||||
fn aliassamemod(c: *cgen, name: str) *node = {
|
||||
let a: *aliasent = c.aliases;
|
||||
for (a != nil) {
|
||||
if (streq(a.aname, name)) {
|
||||
if (streq(a.amod, c.curmod)) { return a.target; };
|
||||
};
|
||||
a = a.aanext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// ---- enum registry --------------------------------------------------
|
||||
//
|
||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
||||
|
||||
@@ -1766,10 +1766,30 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// builds NAMED chains (project_tinfo_lossy_nominal). Stops
|
||||
// at struct aliases so the existing direct-struct arm below
|
||||
// stays byte-id with pre-fix #22 callers. #191.
|
||||
//
|
||||
// #223: the break is MODULE-AWARE. cstage type_chase_named
|
||||
// follows the resolved NAMED.under pointer (module-correct);
|
||||
// wwstage re-resolves by name (lossy), so a same-module
|
||||
// alias whose leaf collides with a FOREIGN struct of the
|
||||
// same name (io.stream = *vtable vs memio.stream struct)
|
||||
// would wrongly halt the peel at the foreign struct via
|
||||
// structlookup's any-module fallback → field load drops to a
|
||||
// bogus `MOVQ <fld>(SB)`. Sibling of #208 (lossy name-keyed
|
||||
// resolution leaking to a global leaf). Break ONLY on a
|
||||
// same-module struct (a genuine struct-value receiver); a
|
||||
// same-module alias keeps peeling; a foreign leaf (in
|
||||
// neither registry for c.curmod) falls back to the prior
|
||||
// any-module heuristic. The broader cross-module same-leaf
|
||||
// STRUCT name-keying at the direct-struct arm below is
|
||||
// filed separately as #224 (not FLIP-triggered).
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
if (structsamemod(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliassamemod(c, tn.str);
|
||||
if (nx == nil) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
nx = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
};
|
||||
tn = nx;
|
||||
};
|
||||
if (tn == nil) { return; };
|
||||
|
||||
@@ -1381,6 +1381,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// #223: same-module-ONLY struct lookup. structlookup's any-module
|
||||
// fallback returns a foreign same-leaf struct; the cgdot alias-peel
|
||||
// needs to break ONLY on a struct that THIS module defines (a genuine
|
||||
// struct-value receiver), not on a foreign struct that merely shares a
|
||||
// leaf with a same-module alias (io.stream alias vs memio.stream
|
||||
// struct). Returns the struct only when it lives in c.curmod.
|
||||
fn structsamemod(c: *cgen, name: str) *structinfo = {
|
||||
let s: *structinfo = c.structs;
|
||||
for (s != nil) {
|
||||
if (streq(s.sname, name)) {
|
||||
if (streq(s.smod, c.curmod)) { return s; };
|
||||
};
|
||||
s = s.sinext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
||||
// recognised as a primitive — the caller falls back to other paths).
|
||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
||||
|
||||
@@ -16142,6 +16142,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// #223: same-module-ONLY struct lookup. structlookup's any-module
|
||||
// fallback returns a foreign same-leaf struct; the cgdot alias-peel
|
||||
// needs to break ONLY on a struct that THIS module defines (a genuine
|
||||
// struct-value receiver), not on a foreign struct that merely shares a
|
||||
// leaf with a same-module alias (io.stream alias vs memio.stream
|
||||
// struct). Returns the struct only when it lives in c.curmod.
|
||||
fn structsamemod(c: *cgen, name: str) *structinfo = {
|
||||
let s: *structinfo = c.structs;
|
||||
for (s != nil) {
|
||||
if (streq(s.sname, name)) {
|
||||
if (streq(s.smod, c.curmod)) { return s; };
|
||||
};
|
||||
s = s.sinext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
||||
// recognised as a primitive — the caller falls back to other paths).
|
||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
||||
@@ -20129,10 +20146,30 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// builds NAMED chains (project_tinfo_lossy_nominal). Stops
|
||||
// at struct aliases so the existing direct-struct arm below
|
||||
// stays byte-id with pre-fix #22 callers. #191.
|
||||
//
|
||||
// #223: the break is MODULE-AWARE. cstage type_chase_named
|
||||
// follows the resolved NAMED.under pointer (module-correct);
|
||||
// wwstage re-resolves by name (lossy), so a same-module
|
||||
// alias whose leaf collides with a FOREIGN struct of the
|
||||
// same name (io.stream = *vtable vs memio.stream struct)
|
||||
// would wrongly halt the peel at the foreign struct via
|
||||
// structlookup's any-module fallback → field load drops to a
|
||||
// bogus `MOVQ <fld>(SB)`. Sibling of #208 (lossy name-keyed
|
||||
// resolution leaking to a global leaf). Break ONLY on a
|
||||
// same-module struct (a genuine struct-value receiver); a
|
||||
// same-module alias keeps peeling; a foreign leaf (in
|
||||
// neither registry for c.curmod) falls back to the prior
|
||||
// any-module heuristic. The broader cross-module same-leaf
|
||||
// STRUCT name-keying at the direct-struct arm below is
|
||||
// filed separately as #224 (not FLIP-triggered).
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
if (structsamemod(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliassamemod(c, tn.str);
|
||||
if (nx == nil) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
nx = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
};
|
||||
tn = nx;
|
||||
};
|
||||
if (tn == nil) { return; };
|
||||
@@ -28373,6 +28410,22 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// #223: same-module-ONLY alias resolution. aliaslookup's any-module
|
||||
// fallback can return a foreign same-leaf alias; the alias-peel in
|
||||
// cgdot needs to know whether THIS module defines the name as an alias
|
||||
// (so the peel continues) without that cross-module fallback. Returns
|
||||
// the alias target only when an alias of `name` lives in c.curmod.
|
||||
fn aliassamemod(c: *cgen, name: str) *node = {
|
||||
let a: *aliasent = c.aliases;
|
||||
for (a != nil) {
|
||||
if (streq(a.aname, name)) {
|
||||
if (streq(a.amod, c.curmod)) { return a.target; };
|
||||
};
|
||||
a = a.aanext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// ---- enum registry --------------------------------------------------
|
||||
//
|
||||
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
||||
|
||||
348
test/wcc/784_xmod_alias_struct_collide_run.c
Normal file
348
test/wcc/784_xmod_alias_struct_collide_run.c
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* 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 *w6c, const char *w6c_ww,
|
||||
const struct scenario *sc)
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* cstage driver build + run: pins runtime. */
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build -I %s %s/main.ww",
|
||||
dir, bin, 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): raw w6c vs w6c_ww on the
|
||||
* driver-produced combined.ww. Pre-fix the dispatcher diverges. */
|
||||
char cs_s[1024], ws_s[1024], comb[1024];
|
||||
snprintf(comb, sizeof comb, "%s/main.combined.ww", dir);
|
||||
snprintf(cs_s, sizeof cs_s, "%s/cs.s", dir);
|
||||
snprintf(ws_s, sizeof ws_s, "%s/ww.s", dir);
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, comb);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "784[%s]: w6c on combined failed\n", sc->label);
|
||||
rc = -1; goto done;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, comb);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "784[%s]: w6c_ww on combined failed\n",
|
||||
sc->label);
|
||||
rc = -1; goto done;
|
||||
}
|
||||
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 w6c[2100], w6c_ww[2100];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
if (access(w6c_ww, X_OK) != 0) {
|
||||
fprintf(stderr, "784: w6c_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, w6c, w6c_ww, &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;
|
||||
}
|
||||
Reference in New Issue
Block a user