ww: finish initialization validation parity

This commit is contained in:
2026-08-14 20:03:11 +09:00
parent 25da54936d
commit c59a7c11de
26 changed files with 528 additions and 143 deletions

View File

@@ -2,15 +2,15 @@ package collide_test;
// Cross-module leaf-name collision observers on both driver stages.
// Ports of the retired native carriers test/wcc/989_fnptrcollide_run.c
// and 989_barefn_collide_run.c; every assertion preserved.
// and 989_barefn_collide_run.c; their collision teeth remain explicit under
// the package-initialization contract.
//
// fnptrcollide (#14 F7-c7) — a data global `slot: i64` whose LEAF
// collides with the imported fn bar.slot must FAIL to build on BOTH
// stages: type-keyed nodefnptr refuses to fold `&slot` into the fn's
// TEXT reloc. Pre-fix wwstage was name-keyed and silently BUILT it
// (a DATAR to the fn symbol — cat-A: cs loud-fails, ww builds). The
// collision REQUIRES the import path (imported fn leaf vs local data
// global), so a single-file fixture cannot express it.
// fnptrcollide (#14 F7-c7) — a data global `slot: i64` whose leaf
// collides with imported fn bar.slot. `let fp: *i64 = &slot` is valid
// runtime package initialization: both stages must address main.slot,
// initialize fp before main, and run 7. The old loud-reject expectation
// predated runtime package lets. The collision still requires an import,
// and name-keyed lowering to bar.slot is rejected by exact asm/runtime pins.
//
// barefn (#84/#24a) — a `package main;` root `fn run` coexists with
// imported aa.run: build+run exit 9 on BOTH stages while an explicit
@@ -52,32 +52,86 @@ fn runcode(dir: str, name: str, argv: []str) i32 = {
};
@test fn fnptrcollide() void = {
let td: str = testenv.fresh();
let bar: str = strings.concat(td, "/bar");
let main: str = strings.concat(td, "/main");
assert(os.mkdir(bar, 493) == 0);
assert(os.mkdir(main, 493) == 0);
testenv.writefile(strings.concat(bar, "/bar.ww"), strings.concat(
"package bar;\n",
"export fn slot() i64 = { return 99; };\n"));
testenv.writefile(strings.concat(main, "/main.ww"), strings.concat(
"package main;\n",
"import bar;\n",
"let slot: i64 = 7;\n",
"let fp: *i64 = &slot;\n",
"export fn main() int = { let _ = bar.slot(); ",
"return (*fp): int; };\n"));
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let works: []str = [strings.concat(td, "/work-c"),
strings.concat(td, "/work-ww")];
let progs: []str = [strings.concat(td, "/prog.cs"),
strings.concat(td, "/prog.ww")];
let i: i32 = 0;
for (i < 2) {
let td: str = testenv.fresh();
assert(os.mkdir(strings.concat(td, "/bar"), 493) == 0);
testenv.writefile(strings.concat(td, "/bar/bar.ww"),
strings.concat(
"package bar;\n",
"export fn slot() i64 = { return 99; };\n"));
testenv.writefile(strings.concat(td, "/main.ww"), strings.concat(
"package main;\n",
"import bar;\n",
"let slot: i64 = 7;\n",
"let fp: *i64 = &slot;\n",
"export fn main() int = { let _ = bar.slot(); ",
"return (*fp): int; };\n"));
let av: []str = [testenv.driver(drvs[i]), "build", "-I", "bar",
"main.ww"];
if (runcode(td, strings.concat("build_", drvs[i]), av) == 0) {
fail("fnptrcollide", strings.concat(drvs[i], " built ok, ",
"expected the leaf-name collision to be rejected (#14 -- ",
"&slot mis-folded to the fn TEXT reloc)"));
assert(os.mkdir(works[i], 493) == 0);
let av: []str = [testenv.driver(drvs[i]), "build", "-w", works[i],
"-I", td, "-o", progs[i], "main"];
if (runcode(td, strings.concat("build_", tags[i]), av) != 0) {
fail("fnptrcollide", strings.concat(drvs[i], " build failed"));
};
let rav: []str = [progs[i]];
if (runcode(td, strings.concat("run_", tags[i]), rav) != 7) {
fail("fnptrcollide", strings.concat(drvs[i], " exit != 7"));
};
let sourceasm: str = testenv.readfile(strings.concat(works[i],
"/main.s"));
let initasm: str = testenv.readfile(strings.concat(works[i],
"/main.init.s"));
let barasm: str = testenv.readfile(strings.concat(works[i], "/bar.s"));
if (testenv.occurrences(sourceasm, "DATAW main.slot(SB)") != 1
|| testenv.occurrences(sourceasm, "DATAW main.fp(SB)") != 1
|| testenv.occurrences(sourceasm,
"CALL\t__ww..dispatch(SB)") != 1
|| testenv.occurrences(sourceasm,
"LEAQ\tmain.slot(SB), AX") != 1
|| testenv.occurrences(sourceasm,
"MOVQ\tAX, main.fp(SB)") != 1
|| testenv.has(sourceasm, "LEAQ\tbar.slot(SB)")
|| testenv.has(sourceasm, "DATAR main.fp")
|| testenv.occurrences(initasm,
"CALL\t__ww..pkg.p.bar.v0.r0.init(SB)") != 1
|| testenv.occurrences(initasm,
"CALL\t__ww..pkg.p.main.v0.r0.init(SB)") != 1
|| testenv.occurrences(barasm, "TEXT bar.slot,") != 1) {
fail("fnptrcollide", "runtime initializer selected the colliding fn");
};
testenv.clean(td);
i += 1;
};
let wantdispatch: str = strings.concat(
"//ww:init-root __ww..pkg.p.main.v0.r0.init\n",
"//ww:init-call __ww..pkg.p.bar.v0.r0.init\n",
"//ww:init-call __ww..pkg.p.main.v0.r0.init\n");
if (!testenv.same(wantdispatch, testenv.readfile(strings.concat(works[0],
"/main.init.unit.ww")))) {
fail("fnptrcollide", "dispatcher did not initialize dependency first");
};
let names: []str = ["bar.unit.ww", "bar.wwi", "bar.s", "bar.o", "bar.a",
"main.unit.ww", "main.wwi", "main.s", "main.o", "main.a",
"main.init.unit.ww", "main.init.s", "main.init.o"];
i = 0;
for (i < names.len) {
if (!testenv.same(testenv.readfile(strings.concat(works[0], "/", names[i])),
testenv.readfile(strings.concat(works[1], "/", names[i])))) {
fail("fnptrcollide", strings.concat(names[i], " differs by stage"));
};
i += 1;
};
if (!testenv.same(testenv.readfile(progs[0]), testenv.readfile(progs[1]))) {
fail("fnptrcollide", "Cstage/WWstage binaries differ");
};
testenv.clean(td);
};
// __root.s then aa.s: the carrier's fixed #93 sep concat order.