w6c,ww: mangle an imported package's fn main under separate compilation (M4 E3, #99)
The bare-`main` carve-out (which keeps the link entry's main unmangled) keyed on `leaf == "main" && imported == 0`. Under the combined path a dependency's body folds in with imported==1, so only the root's main stayed bare. Under separate compilation each package is its own unit and a dependency's body carries a path-mangling module-reset but imported==0 (#57) — so an imported `fn main` matched the carve-out, emitted a bare `TEXT main`, and collided with the root entry (`w6l: duplicate symbol main`). The combined path was unaffected, so this only surfaced under sep. Gate the carve-out with sep_isdep = (wwiout != NULL): the producer emits a .wwi output only for dependency units, never for the root/link-entry unit (root stripped, #69), symmetric on both stages. Only the root unit's main now stays bare; an imported package's main mangles on its import path (e.g. aa.bb.main). Both stages. Gate: test/wcc/989_depmain_sep.c (table-driven, dotted + single-component shapes, both stages; asserts the mangled dep main + a single bare root main + cs==ww byte-id; combined path stays neutral).
This commit is contained in:
14
Makefile
14
Makefile
@@ -573,6 +573,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_c6soak_run \
|
$(BIN)/test_c6soak_run \
|
||||||
$(BIN)/test_septest_run \
|
$(BIN)/test_septest_run \
|
||||||
$(BIN)/test_coloimport_sep \
|
$(BIN)/test_coloimport_sep \
|
||||||
|
$(BIN)/test_depmain_sep \
|
||||||
$(BIN)/test_floatlit_run \
|
$(BIN)/test_floatlit_run \
|
||||||
$(BIN)/test_checked_run \
|
$(BIN)/test_checked_run \
|
||||||
$(BIN)/test_floatarr_run \
|
$(BIN)/test_floatarr_run \
|
||||||
@@ -3270,6 +3271,19 @@ $(BIN)/test_coloimport_sep: test/wcc/989_coloimport_sep.c $(BIN)/ww $(BIN)/ww_ww
|
|||||||
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
# 989_depmain_sep — #99 E3 flip-blocker: an imported dir-package's non-exported
|
||||||
|
# `fn main` must mangle on its import path under `ww build --sep`, not emit a
|
||||||
|
# bare `TEXT main` that collides with the root unit's entry (= w6l dup-symbol).
|
||||||
|
# Drives BOTH driver stages on dotted- and single-component import fixtures,
|
||||||
|
# asserting sep build+link+run exit (pre-fix the bare-main collision makes w6l
|
||||||
|
# reject = the non-vacuity teeth), the dep's main mangles with exactly one bare
|
||||||
|
# root main, cs==ww per-pkg .s/.wwi (rule 10), and combined unaffected. Needs
|
||||||
|
# both driver + compiler + asm + linker stages.
|
||||||
|
$(BIN)/test_depmain_sep: test/wcc/989_depmain_sep.c $(BIN)/ww $(BIN)/ww_ww \
|
||||||
|
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \
|
||||||
|
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
||||||
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
@@ -1477,8 +1477,14 @@ mod_collect(Cg *c, Node *file)
|
|||||||
* primary's decls are MODULED "main", so main is NOT bare here;
|
* primary's decls are MODULED "main", so main is NOT bare here;
|
||||||
* skip it (cgfn force-emits the bare `main` label). M1 #32: only
|
* skip it (cgfn force-emits the bare `main` label). M1 #32: only
|
||||||
* the ROOT main (imported==0) stays bare; an IMPORTED `fn main`
|
* the ROOT main (imported==0) stays bare; an IMPORTED `fn main`
|
||||||
* mangles on its path (closes #31's dup-main by construction). */
|
* mangles on its path (closes #31's dup-main by construction).
|
||||||
if (d->str && strcmp(d->str, "main") == 0 && !d->imported)
|
* #99: under sep a dep unit's main is imported==0 too (its body
|
||||||
|
* is composed with a path-carrying `//ww:module-reset`, #57), so
|
||||||
|
* imported==0 no longer means "root unit" per-unit. Gate on
|
||||||
|
* !sep_isdep (wwiout==NULL <=> root/link-entry unit, #69) so only
|
||||||
|
* the root's main stays bare; a dep's main mangles on its path. */
|
||||||
|
if (d->str && strcmp(d->str, "main") == 0 && !d->imported
|
||||||
|
&& !c->sep_isdep)
|
||||||
continue;
|
continue;
|
||||||
Mod *m = amalloc(c->a, sizeof *m);
|
Mod *m = amalloc(c->a, sizeof *m);
|
||||||
m->name = d->str;
|
m->name = d->str;
|
||||||
@@ -14960,8 +14966,12 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
|||||||
* NOT a bare-module decl — removing this carve-out mangles it to
|
* NOT a bare-module decl — removing this carve-out mangles it to
|
||||||
* `main.main` (undefined `main`). #84's bare-module handling is the
|
* `main.main` (undefined `main`). #84's bare-module handling is the
|
||||||
* orthogonal axis (a package-LESS `//ww:module-reset` fn, cur_mod
|
* orthogonal axis (a package-LESS `//ww:module-reset` fn, cur_mod
|
||||||
* NULL), resolved in mod_lookup_for_fn, NOT here. */
|
* NULL), resolved in mod_lookup_for_fn, NOT here. #99: gate on
|
||||||
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported)
|
* !sep_isdep — under sep a dep unit's main is imported==0 (path-
|
||||||
|
* carrying `//ww:module-reset`, #57); only the root/link-entry unit
|
||||||
|
* (wwiout==NULL, #69) keeps the bare label. */
|
||||||
|
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported
|
||||||
|
&& !c->sep_isdep)
|
||||||
text->to = asym("main");
|
text->to = asym("main");
|
||||||
else
|
else
|
||||||
text->to = mafn(c, fn->str, c->cur_mod);
|
text->to = mafn(c, fn->str, c->cur_mod);
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ struct Cg {
|
|||||||
* the dep's real definition (link collision).
|
* the dep's real definition (link collision).
|
||||||
* Off on the combined path (every existing
|
* Off on the combined path (every existing
|
||||||
* invocation) so M3 is a pure addition. */
|
* invocation) so M3 is a pure addition. */
|
||||||
|
int sep_isdep; /* #99: this unit is a sep DEPENDENCY, not the
|
||||||
|
* root/link-entry unit. Set from `wwiout != NULL`
|
||||||
|
* in main: the producer passes -I (.wwi output)
|
||||||
|
* to DEP units only — the root's .wwi is stripped
|
||||||
|
* (#69), so wwiout==NULL <=> root/link-entry unit.
|
||||||
|
* Gates the bare-`main` carve-out: a dep's `fn
|
||||||
|
* main` must mangle on its path like any decl;
|
||||||
|
* only the root entry stays bare. */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* cgen.c */
|
/* cgen.c */
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ main(int argc, char **argv)
|
|||||||
|
|
||||||
cg_init(&cg, a);
|
cg_init(&cg, a);
|
||||||
cg.sep_mode = sepmode;
|
cg.sep_mode = sepmode;
|
||||||
|
/* #99: wwiout != NULL <=> this is a sep DEP unit (the producer passes
|
||||||
|
* -I to deps only; the root's .wwi is stripped per #69). Gates the
|
||||||
|
* bare-`main` carve-out so only the root/link-entry main stays bare. */
|
||||||
|
cg.sep_isdep = (wwiout != NULL);
|
||||||
cg_file(&cg, of, file);
|
cg_file(&cg, of, file);
|
||||||
|
|
||||||
if (of != stdout) fclose(of);
|
if (of != stdout) fclose(of);
|
||||||
|
|||||||
@@ -41326,8 +41326,11 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
|||||||
// (undefined `main`). #84's bare-module handling is the orthogonal
|
// (undefined `main`). #84's bare-module handling is the orthogonal
|
||||||
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
|
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
|
||||||
// in modlookupforfn, NOT here.
|
// in modlookupforfn, NOT here.
|
||||||
|
// #99: gate on sepisdep — under sep a dep unit's main is imported==0
|
||||||
|
// (path-carrying `//ww:module-reset`, #57); only the root/link-entry
|
||||||
|
// unit (wwiout==nil, #69) keeps the bare label.
|
||||||
emitline("TEXT ");
|
emitline("TEXT ");
|
||||||
if (syntax.streq(fn_.str, "main") && fn_.imported == 0) {
|
if (syntax.streq(fn_.str, "main") && fn_.imported == 0 && c.sepisdep == 0) {
|
||||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||||
} else {
|
} else {
|
||||||
emitfnname(c, fn_.str, fn_.nmod);
|
emitfnname(c, fn_.str, fn_.nmod);
|
||||||
@@ -41932,6 +41935,14 @@ type cgen = struct {
|
|||||||
// must survive to the post-loop emitletdataw/emitdefconstants pass,
|
// must survive to the post-loop emitletdataw/emitdefconstants pass,
|
||||||
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
|
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
|
||||||
sepmode: i32,
|
sepmode: i32,
|
||||||
|
// #99: this unit is a sep DEPENDENCY, not the root/link-entry unit.
|
||||||
|
// Set from `wwiout != nil` in main — the producer passes -I (.wwi
|
||||||
|
// output) to DEP units only; the root's .wwi is stripped (#69), so
|
||||||
|
// wwiout==nil <=> root/link-entry unit. Gates the bare-`main` carve-
|
||||||
|
// out: a dep's `fn main` mangles on its path like any decl; only the
|
||||||
|
// root entry stays bare. Like sepmode, NOT reset by cgeninit (per-fn).
|
||||||
|
// Symmetric with cstage Cg.sep_isdep.
|
||||||
|
sepisdep: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||||
@@ -45263,7 +45274,12 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
|||||||
// label). M1 #32: only ROOT main (imported==0) stays bare;
|
// label). M1 #32: only ROOT main (imported==0) stays bare;
|
||||||
// an IMPORTED `fn main` mangles on its path. Mirrors
|
// an IMPORTED `fn main` mangles on its path. Mirrors
|
||||||
// cstage cgen.c mod_collect.
|
// cstage cgen.c mod_collect.
|
||||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
// #99: under sep a dep unit's main is imported==0 too (its
|
||||||
|
// body is composed with a path-carrying `//ww:module-reset`,
|
||||||
|
// #57), so imported==0 no longer means "root unit" per-unit.
|
||||||
|
// sepisdep (wwiout==nil <=> root/link-entry unit, #69)
|
||||||
|
// re-mangles a dep's main; only the root's stays bare.
|
||||||
|
if (!syntax.streq(d.str, "main") || d.imported != 0 || c.sepisdep != 0) {
|
||||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=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;
|
c.mods = m;
|
||||||
};
|
};
|
||||||
@@ -46339,6 +46355,10 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
let cg: cgen;
|
let cg: cgen;
|
||||||
cgeninit(&cg);
|
cgeninit(&cg);
|
||||||
cg.sepmode = sepmode;
|
cg.sepmode = sepmode;
|
||||||
|
// #99: wwiout != nil <=> this is a sep DEP unit (the producer passes
|
||||||
|
// -I to deps only; the root's .wwi is stripped per #69). Gates the
|
||||||
|
// bare-`main` carve-out so only the root/link-entry main stays bare.
|
||||||
|
if (wwiout != nil) { cg.sepisdep = 1i32; };
|
||||||
cgfile(&cg, f);
|
cgfile(&cg, f);
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -195,6 +195,10 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
let cg: cgen;
|
let cg: cgen;
|
||||||
cgeninit(&cg);
|
cgeninit(&cg);
|
||||||
cg.sepmode = sepmode;
|
cg.sepmode = sepmode;
|
||||||
|
// #99: wwiout != nil <=> this is a sep DEP unit (the producer passes
|
||||||
|
// -I to deps only; the root's .wwi is stripped per #69). Gates the
|
||||||
|
// bare-`main` carve-out so only the root/link-entry main stays bare.
|
||||||
|
if (wwiout != nil) { cg.sepisdep = 1i32; };
|
||||||
cgfile(&cg, f);
|
cgfile(&cg, f);
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -544,6 +544,14 @@ type cgen = struct {
|
|||||||
// must survive to the post-loop emitletdataw/emitdefconstants pass,
|
// must survive to the post-loop emitletdataw/emitdefconstants pass,
|
||||||
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
|
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
|
||||||
sepmode: i32,
|
sepmode: i32,
|
||||||
|
// #99: this unit is a sep DEPENDENCY, not the root/link-entry unit.
|
||||||
|
// Set from `wwiout != nil` in main — the producer passes -I (.wwi
|
||||||
|
// output) to DEP units only; the root's .wwi is stripped (#69), so
|
||||||
|
// wwiout==nil <=> root/link-entry unit. Gates the bare-`main` carve-
|
||||||
|
// out: a dep's `fn main` mangles on its path like any decl; only the
|
||||||
|
// root entry stays bare. Like sepmode, NOT reset by cgeninit (per-fn).
|
||||||
|
// Symmetric with cstage Cg.sep_isdep.
|
||||||
|
sepisdep: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||||
@@ -3875,7 +3883,12 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
|||||||
// label). M1 #32: only ROOT main (imported==0) stays bare;
|
// label). M1 #32: only ROOT main (imported==0) stays bare;
|
||||||
// an IMPORTED `fn main` mangles on its path. Mirrors
|
// an IMPORTED `fn main` mangles on its path. Mirrors
|
||||||
// cstage cgen.c mod_collect.
|
// cstage cgen.c mod_collect.
|
||||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
// #99: under sep a dep unit's main is imported==0 too (its
|
||||||
|
// body is composed with a path-carrying `//ww:module-reset`,
|
||||||
|
// #57), so imported==0 no longer means "root unit" per-unit.
|
||||||
|
// sepisdep (wwiout==nil <=> root/link-entry unit, #69)
|
||||||
|
// re-mangles a dep's main; only the root's stays bare.
|
||||||
|
if (!syntax.streq(d.str, "main") || d.imported != 0 || c.sepisdep != 0) {
|
||||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=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;
|
c.mods = m;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -607,8 +607,11 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
|||||||
// (undefined `main`). #84's bare-module handling is the orthogonal
|
// (undefined `main`). #84's bare-module handling is the orthogonal
|
||||||
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
|
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
|
||||||
// in modlookupforfn, NOT here.
|
// in modlookupforfn, NOT here.
|
||||||
|
// #99: gate on sepisdep — under sep a dep unit's main is imported==0
|
||||||
|
// (path-carrying `//ww:module-reset`, #57); only the root/link-entry
|
||||||
|
// unit (wwiout==nil, #69) keeps the bare label.
|
||||||
emitline("TEXT ");
|
emitline("TEXT ");
|
||||||
if (syntax.streq(fn_.str, "main") && fn_.imported == 0) {
|
if (syntax.streq(fn_.str, "main") && fn_.imported == 0 && c.sepisdep == 0) {
|
||||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||||
} else {
|
} else {
|
||||||
emitfnname(c, fn_.str, fn_.nmod);
|
emitfnname(c, fn_.str, fn_.nmod);
|
||||||
|
|||||||
@@ -41326,8 +41326,11 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = {
|
|||||||
// (undefined `main`). #84's bare-module handling is the orthogonal
|
// (undefined `main`). #84's bare-module handling is the orthogonal
|
||||||
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
|
// axis (a package-LESS `//ww:module-reset` fn, nmod empty), resolved
|
||||||
// in modlookupforfn, NOT here.
|
// in modlookupforfn, NOT here.
|
||||||
|
// #99: gate on sepisdep — under sep a dep unit's main is imported==0
|
||||||
|
// (path-carrying `//ww:module-reset`, #57); only the root/link-entry
|
||||||
|
// unit (wwiout==nil, #69) keeps the bare label.
|
||||||
emitline("TEXT ");
|
emitline("TEXT ");
|
||||||
if (syntax.streq(fn_.str, "main") && fn_.imported == 0) {
|
if (syntax.streq(fn_.str, "main") && fn_.imported == 0 && c.sepisdep == 0) {
|
||||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||||
} else {
|
} else {
|
||||||
emitfnname(c, fn_.str, fn_.nmod);
|
emitfnname(c, fn_.str, fn_.nmod);
|
||||||
@@ -41932,6 +41935,14 @@ type cgen = struct {
|
|||||||
// must survive to the post-loop emitletdataw/emitdefconstants pass,
|
// must survive to the post-loop emitletdataw/emitdefconstants pass,
|
||||||
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
|
// like strlits/ffis. Symmetric with cstage Cg.sep_mode.
|
||||||
sepmode: i32,
|
sepmode: i32,
|
||||||
|
// #99: this unit is a sep DEPENDENCY, not the root/link-entry unit.
|
||||||
|
// Set from `wwiout != nil` in main — the producer passes -I (.wwi
|
||||||
|
// output) to DEP units only; the root's .wwi is stripped (#69), so
|
||||||
|
// wwiout==nil <=> root/link-entry unit. Gates the bare-`main` carve-
|
||||||
|
// out: a dep's `fn main` mangles on its path like any decl; only the
|
||||||
|
// root entry stays bare. Like sepmode, NOT reset by cgeninit (per-fn).
|
||||||
|
// Symmetric with cstage Cg.sep_isdep.
|
||||||
|
sepisdep: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
// Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar.
|
||||||
@@ -45263,7 +45274,12 @@ fn collectmods(c: *cgen, file: *syntax.node) void = {
|
|||||||
// label). M1 #32: only ROOT main (imported==0) stays bare;
|
// label). M1 #32: only ROOT main (imported==0) stays bare;
|
||||||
// an IMPORTED `fn main` mangles on its path. Mirrors
|
// an IMPORTED `fn main` mangles on its path. Mirrors
|
||||||
// cstage cgen.c mod_collect.
|
// cstage cgen.c mod_collect.
|
||||||
if (!syntax.streq(d.str, "main") || d.imported != 0) {
|
// #99: under sep a dep unit's main is imported==0 too (its
|
||||||
|
// body is composed with a path-carrying `//ww:module-reset`,
|
||||||
|
// #57), so imported==0 no longer means "root unit" per-unit.
|
||||||
|
// sepisdep (wwiout==nil <=> root/link-entry unit, #69)
|
||||||
|
// re-mangles a dep's main; only the root's stays bare.
|
||||||
|
if (!syntax.streq(d.str, "main") || d.imported != 0 || c.sepisdep != 0) {
|
||||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, omod=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;
|
c.mods = m;
|
||||||
};
|
};
|
||||||
|
|||||||
285
test/wcc/989_depmain_sep.c
Normal file
285
test/wcc/989_depmain_sep.c
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* 989_depmain_sep — #99 E3 flip-blocker: an IMPORTED dir-package's non-
|
||||||
|
* exported `fn main` must mangle on its import path under `ww build --sep`,
|
||||||
|
* not emit a bare `TEXT main` that collides with the root unit's entry.
|
||||||
|
*
|
||||||
|
* The bare-`main` carve-out (cgen mod_collect + cgfn label, both stages)
|
||||||
|
* keys on `leaf=="main" && imported==0`. Under COMBINED that predicate is
|
||||||
|
* exact — only the root's main is imported==0. Under SEP each package is its
|
||||||
|
* own w6c unit, composed with a path-carrying `//ww:module-reset <path>`
|
||||||
|
* (#57) that mangles decls but leaves imported==0 — so a dep unit's `fn
|
||||||
|
* main` was imported==0 too, the carve-out fired, and it emitted a bare
|
||||||
|
* `TEXT main` → `w6l: duplicate symbol main` against the root's real main.
|
||||||
|
*
|
||||||
|
* The fix adds a NON-ROOT gate `&& !sep_isdep` to the carve-out. sep_isdep
|
||||||
|
* is set from `wwiout != NULL`: the producer passes -I (the .wwi output) to
|
||||||
|
* DEP units ONLY — the root/link-entry unit's .wwi is stripped (#69) — so
|
||||||
|
* `wwiout==NULL` is the exact "this is the root/link-entry unit" oracle. A
|
||||||
|
* dep's main now mangles on its path; only the root's stays bare.
|
||||||
|
*
|
||||||
|
* Fixtures (test/wcc/data/depmain99/, srcd-relative dir-package resolution):
|
||||||
|
* main.ww package main, import aa.bb — root; calls bb.run() → 7.
|
||||||
|
* aa/bb/bb.ww package bb, `fn main`→7 + `export fn run` calls it — the
|
||||||
|
* DOTTED-path imported dep whose main must mangle aa.bb.main.
|
||||||
|
* main_cc.ww package main, import cc — root; calls cc.run() → 9.
|
||||||
|
* cc/cc.ww package cc, `fn main`→9 + `export fn run` — the SINGLE-
|
||||||
|
* component imported dep whose main must mangle cc.main.
|
||||||
|
*
|
||||||
|
* Asserts (both driver stages, COLD per-stage WW_PKGCACHE):
|
||||||
|
* 1. SEP build+link+run exit == the dep's main's return — pre-fix the
|
||||||
|
* bare-main collision makes w6l reject. This is the non-vacuity teeth
|
||||||
|
* (verified: revert `&& !sep_isdep` → `w6l: duplicate symbol main`).
|
||||||
|
* 2. MANGLED — the dep's .s shows `TEXT <path>.main`; exactly ONE bare
|
||||||
|
* `TEXT main,` across the whole sep build (the root's __root.s).
|
||||||
|
* 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the
|
||||||
|
* cstage `ww` and wwstage `ww_ww` sep-drivers.
|
||||||
|
* 4. COMBINED unaffected — `ww build` (no --sep) still links+runs to the
|
||||||
|
* same exit on both stages (the fix must not change combined behavior).
|
||||||
|
*
|
||||||
|
* Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is
|
||||||
|
* `-o`-redirected to /tmp, so phase-1 parallel-safe. Models 989_coloimport_sep
|
||||||
|
* conventions; 989 prefix per the sep-gate precedent.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
absbin(void)
|
||||||
|
{
|
||||||
|
const char *b = getenv("BIN");
|
||||||
|
if (!b) b = "out/bin";
|
||||||
|
if (b[0] == '/') return b;
|
||||||
|
static char buf[2048];
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||||
|
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
slurp(const char *path, char **outbuf, size_t *outlen)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long n = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
if (n < 0) { fclose(f); return -1; }
|
||||||
|
char *b = malloc((size_t)n + 1);
|
||||||
|
if (!b) { fclose(f); return -1; }
|
||||||
|
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||||
|
b[n] = '\0';
|
||||||
|
fclose(f);
|
||||||
|
*outbuf = b;
|
||||||
|
*outlen = (size_t)n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
files_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
char *ba = NULL, *bb = NULL;
|
||||||
|
size_t na = 0, nb = 0;
|
||||||
|
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
||||||
|
free(ba); free(bb);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
||||||
|
free(ba); free(bb);
|
||||||
|
return eq ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cs==ww over a sep build's per-package output: every .s/.wwi the cstage
|
||||||
|
* driver produced in `csdir` must be byte-identical to the wwstage driver's
|
||||||
|
* same-named file in `wwdir`. Returns the count of mismatches. */
|
||||||
|
static int
|
||||||
|
cmp_sepwork(const char *csdir, const char *wwdir, const char *label)
|
||||||
|
{
|
||||||
|
DIR *d = opendir(csdir);
|
||||||
|
if (!d) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s — no cs sepwork %s\n", label, csdir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int bad = 0, seen = 0;
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(d)) != NULL) {
|
||||||
|
const char *nm = ent->d_name;
|
||||||
|
size_t nl = strlen(nm);
|
||||||
|
int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0);
|
||||||
|
int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0);
|
||||||
|
if (!is_s && !is_wwi) continue;
|
||||||
|
seen++;
|
||||||
|
char a[2048], b[2048];
|
||||||
|
snprintf(a, sizeof a, "%s/%s", csdir, nm);
|
||||||
|
snprintf(b, sizeof b, "%s/%s", wwdir, nm);
|
||||||
|
if (files_eq(a, b) != 0) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s — cs!=ww for %s (rule 10)\n",
|
||||||
|
label, nm);
|
||||||
|
bad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
if (seen == 0) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s — no .s/.wwi in %s\n", label, csdir);
|
||||||
|
bad++;
|
||||||
|
}
|
||||||
|
return bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* count_bare_main — number of bare `TEXT main,` labels across every .s in
|
||||||
|
* `dir`. Exactly 1 is correct (the root entry); the bug produces 2 (the
|
||||||
|
* dep's mis-bared main collides). */
|
||||||
|
static int
|
||||||
|
count_bare_main(const char *dir)
|
||||||
|
{
|
||||||
|
DIR *d = opendir(dir);
|
||||||
|
if (!d) return -1;
|
||||||
|
int n = 0;
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(d)) != NULL) {
|
||||||
|
const char *nm = ent->d_name;
|
||||||
|
size_t nl = strlen(nm);
|
||||||
|
if (!(nl > 2 && strcmp(nm + nl - 2, ".s") == 0)) continue;
|
||||||
|
char p[2048];
|
||||||
|
snprintf(p, sizeof p, "%s/%s", dir, nm);
|
||||||
|
char *b = NULL;
|
||||||
|
size_t bn = 0;
|
||||||
|
if (slurp(p, &b, &bn) < 0) continue;
|
||||||
|
/* a bare label is `TEXT main,` at column 0 of any line. */
|
||||||
|
for (char *s = b; (s = strstr(s, "TEXT main,")) != NULL; s++)
|
||||||
|
if (s == b || s[-1] == '\n') n++;
|
||||||
|
free(b);
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* file_has — 0 if `needle` occurs in `path`, 1 if absent, -1 on read err. */
|
||||||
|
static int
|
||||||
|
file_has(const char *path, const char *needle)
|
||||||
|
{
|
||||||
|
char *b = NULL;
|
||||||
|
size_t bn = 0;
|
||||||
|
if (slurp(path, &b, &bn) < 0) return -1;
|
||||||
|
int found = (strstr(b, needle) != NULL);
|
||||||
|
free(b);
|
||||||
|
return found ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tcase {
|
||||||
|
const char *label;
|
||||||
|
const char *entry; /* root entry built under --sep */
|
||||||
|
int exit; /* the dep main's return, == the program exit */
|
||||||
|
const char *deps; /* the dep's .s basename in .sepwork */
|
||||||
|
const char *mangled; /* the mangled TEXT label the dep must emit */
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct tcase cases[] = {
|
||||||
|
/* dotted-path import: dep main mangles on the full path. */
|
||||||
|
{ "aabb", "test/wcc/data/depmain99/main.ww", 7,
|
||||||
|
"aa.bb.s", "TEXT aa.bb.main," },
|
||||||
|
/* single-component import: dep main mangles on the leaf package. */
|
||||||
|
{ "cc", "test/wcc/data/depmain99/main_cc.ww", 9,
|
||||||
|
"cc.s", "TEXT cc.main," },
|
||||||
|
{ NULL, NULL, 0, NULL, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = absbin();
|
||||||
|
if (!bin) return 1;
|
||||||
|
|
||||||
|
char td[64], cmd[8192];
|
||||||
|
int fail = 0;
|
||||||
|
snprintf(td, sizeof td, "/tmp/wwdepmain_%d", getpid());
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||||
|
runwait(cmd);
|
||||||
|
mkdir(td, 0755);
|
||||||
|
|
||||||
|
for (int i = 0; cases[i].label; i++) {
|
||||||
|
struct tcase *t = &cases[i];
|
||||||
|
|
||||||
|
struct { const char *drv, *tag; int rc; }
|
||||||
|
stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } };
|
||||||
|
|
||||||
|
/* 1. SEP build+link+run exit == the dep main's return, both stages. */
|
||||||
|
for (int s = 0; s < 2; s++) {
|
||||||
|
char prog[1024];
|
||||||
|
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag);
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"WW_PKGCACHE='%s/cache.%s.%s' %s/%s build --sep -o %s %s "
|
||||||
|
">/dev/null 2>&1 && %s",
|
||||||
|
td, t->label, stg[s].tag, bin, stg[s].drv, prog, t->entry, prog);
|
||||||
|
stg[s].rc = runwait(cmd);
|
||||||
|
if (stg[s].rc != t->exit) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s sep %s exit=%d (expected %d)\n",
|
||||||
|
t->label, stg[s].tag, stg[s].rc, t->exit);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char csdir[1024], wwdir[1024];
|
||||||
|
snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label);
|
||||||
|
snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label);
|
||||||
|
|
||||||
|
/* 2. MANGLED: the dep's .s emits `TEXT <path>.main`; exactly one bare
|
||||||
|
* `TEXT main,` across the whole sep build (the root's __root.s). */
|
||||||
|
char deps[2048];
|
||||||
|
snprintf(deps, sizeof deps, "%s/%s", csdir, t->deps);
|
||||||
|
if (file_has(deps, t->mangled) != 0) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s — %s lacks `%s` (dep main not "
|
||||||
|
"mangled)\n", t->label, deps, t->mangled);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
int nbare = count_bare_main(csdir);
|
||||||
|
if (nbare != 1) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s — %d bare `TEXT main,` in %s "
|
||||||
|
"(expected 1, the root entry)\n", t->label, nbare, csdir);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */
|
||||||
|
fail += cmp_sepwork(csdir, wwdir, t->label);
|
||||||
|
|
||||||
|
/* 4. COMBINED unaffected: `ww build` (no --sep) still runs to exit. */
|
||||||
|
for (int s = 0; s < 2; s++) {
|
||||||
|
char prog[1024];
|
||||||
|
snprintf(prog, sizeof prog, "%s/%s.%s.cmb", td, t->label, stg[s].tag);
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"%s/%s build -o %s %s >/dev/null 2>&1 && %s",
|
||||||
|
bin, stg[s].drv, prog, t->entry, prog);
|
||||||
|
int rc = runwait(cmd);
|
||||||
|
if (rc != t->exit) {
|
||||||
|
fprintf(stderr, "depmain FAIL: %s combined %s exit=%d "
|
||||||
|
"(expected %d)\n", t->label, stg[s].tag, rc, t->exit);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||||
|
runwait(cmd);
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "depmain: %d check(s) failed\n", fail);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("depmain: imported dir-package `fn main` mangles on its import path "
|
||||||
|
"under `ww build --sep` (only the root entry stays bare) on both driver "
|
||||||
|
"stages + cs==ww per-pkg .s/.wwi + combined unaffected\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
3
test/wcc/data/depmain99/aa/bb/bb.ww
Normal file
3
test/wcc/data/depmain99/aa/bb/bb.ww
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package bb;
|
||||||
|
fn main() int = { return 7; };
|
||||||
|
export fn run() int = { return main(); };
|
||||||
3
test/wcc/data/depmain99/cc/cc.ww
Normal file
3
test/wcc/data/depmain99/cc/cc.ww
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package cc;
|
||||||
|
fn main() int = { return 9; };
|
||||||
|
export fn run() int = { return main(); };
|
||||||
3
test/wcc/data/depmain99/main.ww
Normal file
3
test/wcc/data/depmain99/main.ww
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package main;
|
||||||
|
import aa.bb;
|
||||||
|
export fn main() int = { return bb.run(); };
|
||||||
3
test/wcc/data/depmain99/main_cc.ww
Normal file
3
test/wcc/data/depmain99/main_cc.ww
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package main;
|
||||||
|
import cc;
|
||||||
|
export fn main() int = { return cc.run(); };
|
||||||
Reference in New Issue
Block a user