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:
13
Makefile
13
Makefile
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
255
test/wcc/989_m1usehint_run.c
Normal file
255
test/wcc/989_m1usehint_run.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user