wcc/ww: module-scope the cgen mangle-hint (#40)

use_hint/usehint were unit-global first-leaf-match: two directory-
packages exporting the same fn leaf, each imported by a different module
aliasing the same bareword, mis-routed every qualified call to whichever
use was collected first. Identically wrong on both stages (byte-id-green
#263-class). Key the hint on (owner-module, alias) and prefer cur_mod,
mirroring the checker's use_path curmod-preference (55f54fb).

989_m1usehint_run: two same-leaf pick() across a.math/b.math, each
module's call routes to its own import (111/222) + cs.s==ww.s.
This commit is contained in:
2026-06-15 19:01:51 +09:00
parent f308818b4b
commit e9c11cb5ae
6 changed files with 364 additions and 44 deletions

View File

@@ -267,6 +267,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_chainidx_run \
$(BIN)/test_m1mangle_run \
$(BIN)/test_m1mangle_sym \
$(BIN)/test_m1usehint_run \
$(BIN)/test_m1union_run \
$(BIN)/test_tupfieldsize_run \
$(BIN)/test_tagtupfieldsize_run \
@@ -837,6 +838,18 @@ $(BIN)/test_m1mangle_sym: test/wcc/989_m1mangle_sym.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
# 989_m1usehint_run (M1, #40): the cgen mangle-hint must be MODULE-scoped.
# Two same-leaf exported fns across directory-packages, each `import`ed by a
# different module aliasing `math`; every `math.pick()` must route to its own
# import (not the file-global first-match). Builds+runs on BOTH stages + pins
# cstage.s==wwstage.s — the gate-VISIBLE #263-class hint miscompile.
$(BIN)/test_m1usehint_run: test/wcc/989_m1usehint_run.c \
$(BIN)/ww $(BIN)/ww_ww \
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
# 989_m1union_run (M1, #199b): a `match` on a cross-module fn's tagged-union
# return must keep each nominal void-alias variant on a distinct tag. Builds+
# runs the utf8.next 4-arm match per-arm on BOTH stages (rule-10) and pins

View File

@@ -1175,33 +1175,36 @@ typedef struct Use Use;
struct Use {
const char *alias;
const char *path;
const char *module; /* owning module of the `use` decl (#40) */
Use *next;
};
static Use *use_map;
/*
* Retained divergence (M1 #22): this map is file-global — when two
* modules in the same unit bind the same leaf alias to different paths
* (sha256's `import crypto.math` vs strconv's `import math`), the first
* match wins. The checker's twin (use_path, check.c) is module-scoped
* to fix exactly this for qualified-NAME resolution; the cgen mangle
* hint is NOT, because it is unreachable with an ambiguous alias: a
* cross-module qualified ref resolves only to an EXPORTED symbol, and
* exported non-fn decls stay bare (mod_collect skips them, so the hint
* is moot) while an exported-fn collision would require two same-leaf
* packages to export the same fn name AND a third caller — not present
* (the tree links cleanly). Left file-global to stay byte-identical
* with wwstage's symmetric usehint (cgen.ww), which is also file-global
* (rule 10). Module-scope both stages together if a real collision
* surfaces — filed as the M1 cgen-hint twin follow-up.
* use_hint — map a `use` alias to its dotted import path for the
* qualified-ref mangle hint. NOT file-global: two modules in one unit
* may bind the same leaf alias to different paths (#40 — module one's
* `import a.math` and module two's `import b.math` both alias `math`).
* The import declared in the SAME module as the reference (curmod) is
* authoritative; preferring it routes each `math.pick()` to its own
* package. Falls back to any matching alias when curmod has no own
* import (single-occurrence case). Mirrors the checker's use_path
* curmod-preference (check.c, M1 55f54fb). Returns the alias unchanged
* when no `use` matches.
*/
static const char *
use_hint(const char *alias)
use_hint(const char *curmod, const char *alias)
{
const char *any = NULL;
if (alias == NULL) return alias;
for (Use *u = use_map; u; u = u->next)
if (strcmp(u->alias, alias) == 0) return u->path;
return alias;
for (Use *u = use_map; u; u = u->next) {
if (strcmp(u->alias, alias) != 0) continue;
int same = (u->module == NULL) ? (curmod == NULL)
: (curmod != NULL && strcmp(u->module, curmod) == 0);
if (same) return u->path;
if (any == NULL) any = u->path;
}
return any ? any : alias;
}
/* Top-level `let` map. Populated alongside mod_map; consulted by the
@@ -1428,6 +1431,7 @@ mod_collect(Cg *c, Node *file)
Use *u = amalloc(c->a, sizeof *u);
u->alias = d->str;
u->path = d->usepath;
u->module = d->module;
u->next = use_map;
use_map = u;
continue;
@@ -4371,7 +4375,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (lu && lu->kind == TY_FN)
ins2(c, A_LEAQ,
mafn(c, opnd->str,
use_hint(opnd->lhs->str)),
use_hint(c->cur_mod, opnd->lhs->str)),
areg(D_AX));
else
/* #229: dotted-module value
@@ -4380,7 +4384,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* same-leaf collision. */
ins2(c, A_LEAQ,
mahint(c, opnd->str,
use_hint(opnd->lhs->str)),
use_hint(c->cur_mod, opnd->lhs->str)),
areg(D_AX));
break;
}
@@ -10232,7 +10236,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* same-leaf exports resolve correctly. */
ins1(c, A_CALL,
mafn(c, n->lhs->str,
use_hint(n->lhs->lhs->str)));
use_hint(c->cur_mod, n->lhs->lhs->str)));
} else {
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
ins1(c, A_CALL, areg(D_AX));
@@ -11108,7 +11112,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
/* `mod.fn` address-of via N_DOT — pass the
* module bareword as the disambiguation hint. */
ins2(c, A_LEAQ,
mafn(c, n->str, use_hint(n->lhs->str)),
mafn(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
areg(D_AX));
break;
}
@@ -11125,7 +11129,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
for (s = sdefs; s; s = s->next) {
if (strcmp(s->name, n->str) != 0)
continue;
if (sdef_mod_match_hint(s, use_hint(n->lhs->str)))
if (sdef_mod_match_hint(s, use_hint(c->cur_mod, n->lhs->str)))
break;
}
if (s == NULL) {
@@ -11157,11 +11161,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
* branch above already uses n->lhs->str via mafn. */
if (mqop == A_MOVQ) {
ins2(c, A_MOVQ,
mahint(c, n->str, use_hint(n->lhs->str)),
mahint(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
areg(D_AX));
} else {
ins2(c, A_LEAQ,
mahint(c, n->str, use_hint(n->lhs->str)),
mahint(c, n->str, use_hint(c->cur_mod, n->lhs->str)),
areg(D_CX));
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
}
@@ -16049,7 +16053,7 @@ node_fnptr_sym(Cg *c, Node *ev)
return NULL;
Type *du = type_chase_named(opnd->type);
if (du == NULL || du->kind != TY_FN) return NULL;
return mod_mangle_fn(c, opnd->str, use_hint(opnd->lhs->str));
return mod_mangle_fn(c, opnd->str, use_hint(c->cur_mod, opnd->lhs->str));
}
if (opnd == NULL || opnd->kind != N_IDENT) return NULL;
Type *ou = type_chase_named(opnd->type);

View File

@@ -44986,6 +44986,7 @@ fn defisaddressable(c: *cgen, opnd: *node) bool = {
type modent = struct {
mname: str, // the bare ident as it appears in source
nmod: str, // the originating module (`// MODULE: foo`)
omod: str, // owning module of a `use` decl (#40); unused for mods
mnext: *modent,
};
@@ -44998,7 +44999,7 @@ fn collectmods(c: *cgen, file: *node) void = {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, omod=d.nmod, mnext=c.uses})!;
c.uses = um;
};
};
@@ -45022,7 +45023,7 @@ fn collectmods(c: *cgen, file: *node) void = {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45031,7 +45032,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_DEF) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45039,7 +45040,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_TYPEDECL) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45047,7 +45048,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_LET) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45072,12 +45073,27 @@ fn modlookup(c: *cgen, name: str) str = {
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
//
// NOT file-global (#40): two modules in one unit may bind the same leaf
// alias to different paths (module one's `import a.math` vs module two's
// `import b.math`, both alias `math`). The `use` declared in the SAME
// module as the reference (c.curmod) is authoritative; preferring it
// routes each `math.pick()` to its own package. Falls back to any
// matching alias when c.curmod has no own import. Mirrors the checker's
// use_path curmod-preference (cstage check.c, M1 55f54fb).
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
let any: str;
any.ptr = nil;
any.len = 0;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
if (streq(m.mname, alias)) {
if (streq(m.omod, c.curmod)) { return m.nmod; };
if (any.ptr == nil && any.len == 0) { any = m.nmod; };
};
m = m.mnext;
};
if (any.ptr != nil || any.len != 0) { return any; };
return alias;
};

View File

@@ -3787,6 +3787,7 @@ fn defisaddressable(c: *cgen, opnd: *node) bool = {
type modent = struct {
mname: str, // the bare ident as it appears in source
nmod: str, // the originating module (`// MODULE: foo`)
omod: str, // owning module of a `use` decl (#40); unused for mods
mnext: *modent,
};
@@ -3799,7 +3800,7 @@ fn collectmods(c: *cgen, file: *node) void = {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, omod=d.nmod, mnext=c.uses})!;
c.uses = um;
};
};
@@ -3823,7 +3824,7 @@ fn collectmods(c: *cgen, file: *node) void = {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -3832,7 +3833,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_DEF) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -3840,7 +3841,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_TYPEDECL) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -3848,7 +3849,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_LET) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -3873,12 +3874,27 @@ fn modlookup(c: *cgen, name: str) str = {
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
//
// NOT file-global (#40): two modules in one unit may bind the same leaf
// alias to different paths (module one's `import a.math` vs module two's
// `import b.math`, both alias `math`). The `use` declared in the SAME
// module as the reference (c.curmod) is authoritative; preferring it
// routes each `math.pick()` to its own package. Falls back to any
// matching alias when c.curmod has no own import. Mirrors the checker's
// use_path curmod-preference (cstage check.c, M1 55f54fb).
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
let any: str;
any.ptr = nil;
any.len = 0;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
if (streq(m.mname, alias)) {
if (streq(m.omod, c.curmod)) { return m.nmod; };
if (any.ptr == nil && any.len == 0) { any = m.nmod; };
};
m = m.mnext;
};
if (any.ptr != nil || any.len != 0) { return any; };
return alias;
};

View File

@@ -44986,6 +44986,7 @@ fn defisaddressable(c: *cgen, opnd: *node) bool = {
type modent = struct {
mname: str, // the bare ident as it appears in source
nmod: str, // the originating module (`// MODULE: foo`)
omod: str, // owning module of a `use` decl (#40); unused for mods
mnext: *modent,
};
@@ -44998,7 +44999,7 @@ fn collectmods(c: *cgen, file: *node) void = {
// M1 #22: record alias→path for the qualified-ref hint.
if (d.kind == nkind.N_USE) {
if (d.usepath.len > 0) {
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, omod=d.nmod, mnext=c.uses})!;
c.uses = um;
};
};
@@ -45022,7 +45023,7 @@ fn collectmods(c: *cgen, file: *node) void = {
// M1 #32: the ROOT main (imported==0) stays bare;
// an IMPORTED `fn main` mangles on its path.
if (!streq(d.str, "main") || d.imported != 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45031,7 +45032,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_DEF) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45039,7 +45040,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_TYPEDECL) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45047,7 +45048,7 @@ fn collectmods(c: *cgen, file: *node) void = {
if (d.kind == nkind.N_LET) {
if (d.exported == 0) {
if (d.nmod.len > 0) {
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=d.nmod, mnext=c.mods})!;
c.mods = m;
};
};
@@ -45072,12 +45073,27 @@ fn modlookup(c: *cgen, name: str) str = {
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
// mods map. For single-level packages alias == path (no-op). Returns the
// alias unchanged when no matching `use` exists.
//
// NOT file-global (#40): two modules in one unit may bind the same leaf
// alias to different paths (module one's `import a.math` vs module two's
// `import b.math`, both alias `math`). The `use` declared in the SAME
// module as the reference (c.curmod) is authoritative; preferring it
// routes each `math.pick()` to its own package. Falls back to any
// matching alias when c.curmod has no own import. Mirrors the checker's
// use_path curmod-preference (cstage check.c, M1 55f54fb).
fn usehint(c: *cgen, alias: str) str = {
let m: *modent = c.uses;
let any: str;
any.ptr = nil;
any.len = 0;
for (m != nil) {
if (streq(m.mname, alias)) { return m.nmod; };
if (streq(m.mname, alias)) {
if (streq(m.omod, c.curmod)) { return m.nmod; };
if (any.ptr == nil && any.len == 0) { any = m.nmod; };
};
m = m.mnext;
};
if (any.ptr != nil || any.len != 0) { return any; };
return alias;
};

View File

@@ -0,0 +1,255 @@
/*
* 989_m1usehint_run — M1 (#40): the cgen mangle-hint must be MODULE-scoped,
* not unit-global. When two directory-packages in the same compilation unit
* export the same leaf fn (a.math and b.math both `export fn pick`), and two
* referencing modules each `import <X>.math` aliasing `math`, every
* `math.pick()` call must mangle to the package its OWN module imported —
* not to whichever `use` the file-global hint collected first.
*
* Pre-#40 the hint (use_hint / usehint) was file-global first-match: both
* modules' `math.pick()` resolved to the same package, so one call landed
* the wrong body and returned the wrong value — IDENTICALLY on both stages
* (a byte-id-green #263-class miscompile). Gate-visible: each module's call
* has a distinct expected value, so a mis-route changes the runtime exit.
*
* row | proves
* -----------------+--------------------------------------------------
* leaf_collision | two same-leaf exported fns; each module's qualified
* | call mangles to its OWN import (111 / 222) -> 0
* collision_diff | same, with mismatched signatures — the checker's
* | curmod-preferenced resolve agrees with the hint
* | (pick(int) vs pick()) -> 0
*
* Plus a byte-id gate: cstage.s == wwstage.s on the leaf_collision unit
* (rule-10).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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 file { const char *path; const char *content; };
struct row {
const char *label;
struct file files[6]; /* {NULL,NULL}-terminated package files */
const char *root; /* root main.ww content */
int want_exit;
};
static const struct row rows[] = {
{ "leaf_collision",
{ { "a/math/math.ww",
"package math;\n"
"export fn pick() int = { return 111; };\n" },
{ "b/math/math.ww",
"package math;\n"
"export fn pick() int = { return 222; };\n" },
{ "one/one.ww",
"package one;\n"
"import a.math;\n"
"export fn getone() int = { return math.pick(); };\n" },
{ "two/two.ww",
"package two;\n"
"import b.math;\n"
"export fn gettwo() int = { return math.pick(); };\n" },
{ NULL, NULL } },
"package main;\n"
"import one;\n"
"import two;\n"
"export fn main() int = {\n"
" if (one.getone() != 111) { return 1; };\n"
" if (two.gettwo() != 222) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
{ "collision_diff",
{ { "a/math/math.ww",
"package math;\n"
"export fn pick(x: int) int = { return x + 100; };\n" },
{ "b/math/math.ww",
"package math;\n"
"export fn pick() int = { return 222; };\n" },
{ "one/one.ww",
"package one;\n"
"import a.math;\n"
"export fn getone() int = { return math.pick(11); };\n" },
{ "two/two.ww",
"package two;\n"
"import b.math;\n"
"export fn gettwo() int = { return math.pick(); };\n" },
{ NULL, NULL } },
"package main;\n"
"import one;\n"
"import two;\n"
"export fn main() int = {\n"
" if (one.getone() != 111) { return 1; };\n"
" if (two.gettwo() != 222) { return 2; };\n"
" return 0;\n"
"};\n",
0 },
};
/* write_file — create `dir/rel` (mkdir -p its parents) with `content`. */
static int
write_file(const char *dir, const char *rel, const char *content)
{
char path[512], cmd[1024];
snprintf(path, sizeof path, "%s/%s", dir, rel);
char parent[512];
snprintf(parent, sizeof parent, "%s", path);
char *slash = strrchr(parent, '/');
if (slash) {
*slash = '\0';
snprintf(cmd, sizeof cmd, "mkdir -p '%s'", parent);
if (runwait(cmd) != 0) return -1;
}
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(content, f);
fclose(f);
return 0;
}
/* layout — lay out the row's package tree + root under `dir`. */
static int
layout(const char *dir, const struct row *r)
{
char cmd[1024];
snprintf(cmd, sizeof cmd, "rm -rf '%s' && mkdir -p '%s'", dir, dir);
if (runwait(cmd) != 0) return -1;
for (int k = 0; r->files[k].path; k++)
if (write_file(dir, r->files[k].path, r->files[k].content) != 0)
return -1;
return write_file(dir, "main.ww", r->root);
}
/* run_build — build+run the row's root via `driver`; return the binary's
* exit code, or -1 on a build failure. */
static int
run_build(const char *driver, const struct row *r, int i)
{
char dir[64], cmd[2048];
snprintf(dir, sizeof dir, "/tmp/m1usehint_%d_%d", getpid(), i);
if (layout(dir, r) != 0) return -2;
snprintf(cmd, sizeof cmd, "cd '%s' && %s build main.ww 2>/dev/null",
dir, driver);
int brc = runwait(cmd);
int got = -1;
if (brc == 0) {
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/main", dir);
got = runwait(outbin);
}
snprintf(cmd, sizeof cmd, "rm -rf '%s'", dir);
runwait(cmd);
return brc == 0 ? got : -1;
}
/* byteid — build the leaf_collision unit with both drivers and cmp the
* emitted combined .s (rule-10). Returns 0 on byte-identical, nonzero else. */
static int
byteid(const char *cdrv, const char *wdrv, const struct row *r)
{
char dir[64], cmd[2048];
char cs[96], ws[96];
snprintf(dir, sizeof dir, "/tmp/m1usehint_bid_%d", getpid());
if (layout(dir, r) != 0) return -1;
snprintf(cs, sizeof cs, "/tmp/m1usehint_c_%d.s", getpid());
snprintf(ws, sizeof ws, "/tmp/m1usehint_w_%d.s", getpid());
int rc = -1;
snprintf(cmd, sizeof cmd, "cd '%s' && %s build -o '/tmp/m1usehint_c_%d' "
"main.ww 2>/dev/null", dir, cdrv, getpid());
int cb = runwait(cmd);
snprintf(cmd, sizeof cmd, "cd '%s' && %s build -o '/tmp/m1usehint_w_%d' "
"main.ww 2>/dev/null", dir, wdrv, getpid());
int wb = runwait(cmd);
if (cb == 0 && wb == 0) {
snprintf(cmd, sizeof cmd, "cmp -s '%s' '%s'", cs, ws);
rc = runwait(cmd);
} else {
fprintf(stderr, "m1usehint_run: byte-id build failed "
"(cstage=%d wwstage=%d)\n", cb, wb);
}
snprintf(cmd, sizeof cmd, "rm -rf '%s' '/tmp/m1usehint_c_%d'* "
"'/tmp/m1usehint_w_%d'*", dir, getpid(), getpid());
runwait(cmd);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
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[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *drv; int gated; }
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 && access(drivers[d].drv, X_OK) != 0) {
fprintf(stderr, "m1usehint_run: skip %s (no %s)\n",
drivers[d].name, drivers[d].drv);
continue;
}
for (int i = 0; i < n; i++) {
total++;
int got = run_build(drivers[d].drv, &rows[i], i);
if (got != rows[i].want_exit) {
fprintf(stderr, "m1usehint_run[%s][%s]: exit=%d "
"want=%d\n", drivers[d].name, rows[i].label,
got, rows[i].want_exit);
fail++;
}
}
}
/* rule-10 byte-id gate on the leaf_collision unit. */
if (access(wdrv, X_OK) == 0) {
total++;
if (byteid(cdrv, wdrv, &rows[0]) != 0) {
fprintf(stderr, "m1usehint_run: cstage.s != wwstage.s "
"(byte-id break)\n");
fail++;
}
}
if (fail) {
fprintf(stderr, "m1usehint_run: %d/%d failed\n", fail, total);
return 1;
}
printf("m1usehint_run: %d/%d ok\n", total, total);
return 0;
}