package modshadow_test; // Direct-w6c asm-window gate over the same-leaf-name module-collision // lookup family. Port of the retired native carriers // test/wcc/726_alias_leaf_collision.c, 731_fnret_bare_leaf_shadow.c, // 732_fnparams_bare_leaf_shadow.c, 733_enum_modshadow.c, // 734_struct_modshadow.c, 735_def_modshadow.c, 745_fnret34_modshadow.c // and 747_def_modqual_modshadow.c; every assertion preserved. // // Each row compiles an inline single-file multi-package source // directly through w6c and w6c_ww. The package ORDER inside each // source is load-bearing (the collect* walks head-prepend, so the // LAST same-leaf declaration heads its table — that is the collision // the same-module-first / *mod-variant lookups must beat) and is // copied verbatim from the carriers. // // Windowed search: want/anti needles must fall between "TEXT " // and the first "\tRET" after it — positional index arithmetic, never // a whole-file contains. The anti needle catches a both-stages- // identically-wrong head-pick that the byte-id leg alone cannot; // byte-id legs (testenv.same over both .s bodies) ride every row as // in the C. // // Dropped C machinery, not assertions: the w6c_ww-absent skip gates // (the Make target declares both tools), getpid()-keyed /tmp names // and unlink accounting (testenv.fresh/clean own the per-row // scratch), and the 16KB slurp caps (testenv.readfile is unbounded). import os; import os.exec; import strings; import testenv; import time; fn fail(label: str, why: str) void = { let m: str = strings.concat("modshadow FAIL: ", label, " -- ", why, "\n"); os.write(2, m.ptr, m.len: u64); assert(false); }; fn tmo() time.duration = { return (180i64 * (time.second: i64)): time.duration; }; fn emitstage(td: str, label: str, stage: str, drv: str, outname: str) void = { let av: []str = []; append(av, drv); append(av, "-o"); append(av, outname); append(av, "src.ww"); let co: testenv.commandout; testenv.runcommand(td, td, stage, av, tmo(), &co); let ok: bool = co.termination == exec.termination.EXIT && co.code == 0; if (!ok) { fail(label, strings.concat(stage, " compile failed")); }; }; // [first `sym` .. first `retneedle` after it). A missing retneedle // fails when mustret != 0 and widens the window to EOF otherwise. fn asmwindow(label: str, stage: str, s: str, sym: str, retneedle: str, mustret: i32) str = { let fp: i32 = testenv.pos(s, sym); if (fp < 0) { fail(label, strings.concat(stage, ": no ", sym, " in .s")); }; let tail: str = strings.sub(s, fp, s.len); let rp: i32 = testenv.pos(tail, retneedle); if (rp < 0) { if (mustret != 0) { fail(label, strings.concat(stage, ": no RET inside ", sym)); }; return tail; }; return strings.sub(tail, 0, rp); }; fn wantanticheck(label: str, stage: str, s: str, sym: str, want: str, bad: str) void = { let w: str = asmwindow(label, stage, s, sym, "\tRET", 1); if (!testenv.has(w, want)) { fail(label, strings.concat(stage, ": want_imm ", want, " missing inside ", sym)); }; if (testenv.has(w, bad)) { fail(label, strings.concat(stage, ": bad_imm ", bad, " present inside ", sym, " -- wrong-module pick")); }; }; // One 731/732/733/734/735/745/747 row: both stages carry `want` and // not `bad` inside [TEXT sym .. RET), then rule-10 byte-id. fn wantantirow(label: str, src: str, sym: str, want: str, bad: str) void = { let td: str = testenv.fresh(); testenv.writefile(strings.concat(td, "/src.ww"), src); emitstage(td, label, "cstage", testenv.driver("w6c"), "cs.s"); emitstage(td, label, "wwstage", testenv.driver("w6c_ww"), "ws.s"); let cs: str = testenv.readfile(strings.concat(td, "/cs.s")); let ws: str = testenv.readfile(strings.concat(td, "/ws.s")); wantanticheck(label, "cstage", cs, sym, want, bad); wantanticheck(label, "wwstage", ws, sym, want, bad); if (!testenv.same(cs, ws)) { fail(label, "cstage vs wwstage asm differs"); }; testenv.clean(td); }; // 731: bare-leaf fnretlookup same-module-first. beta.foo (str) is // declared LAST so it heads c.fnrets; a head-pick fires the // SysV(AX,DX)->str(AX,BX) shuffle after CALL alpha.foo (i64). @test fn fnretbareleaf() void = { wantantirow("bare_leaf_same_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package alpha;\n", "export fn foo() i64 = { return 0; };\n", "export fn alphacaller() i64 = { return foo(); };\n", "package beta;\n", "export fn foo() str = { return \"x\"; };\n"), "TEXT alpha.alphacaller", "CALL\talpha.foo", "MOVQ\tDX, BX"); }; // 732: bare-leaf fnparamslookup same-module-first. beta.foo's tagged // (i32|void) param heads c.fnrets; a head-pick re-lays the i32 arg // into a 2-word tagged slot and pops the extra word into SI. @test fn fnparamsbareleaf() void = { wantantirow("bare_leaf_same_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package alpha;\n", "export fn foo(x: i32) i32 = { return x; };\n", "export fn alphacaller() i32 = { return foo(7); };\n", "package beta;\n", "export fn foo(x: (i32 | void)) i32 = {\n", " match (x) {\n", " case let v: i32 => return v;\n", " case void => return 0;\n", " };\n", "};\n"), "TEXT alpha.alphacaller", "POPQ\tDI", "POPQ\tSI"); }; // 733: enumlookup same-module-first plus the enumlookupmod qualified // variant. Enum values 7 vs 100 chosen so neither digit string // substring-collides with the other. @test fn enummodshadow() void = { wantantirow("bare_leaf_same_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package beta;\n", "type Color = enum i32 { RED = 7, };\n", "export fn readred() Color = { return Color.RED; };\n", "package alpha;\n", "type Color = enum i32 { RED = 100, };\n"), "TEXT beta.readred", "$7,", "$100,"); wantantirow("dot_qualified_explicit_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package alpha;\n", "type Color = enum i32 { RED = 100, };\n", "export fn readalphared() Color = { return alpha.Color.RED; };\n", "package beta;\n", "type Color = enum i32 { RED = 7, };\n"), "TEXT alpha.readalphared", "$100,", "$7,"); // Caller module gamma matches neither Color, so only the cgdot // etmod tracking + enumlookupmod variant beats the head-walk — // pins the qualified piece independent of row 1. wantantirow("dot_qualified_cross_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn readalpharedgamma() i32 = { return alpha.Color.RED: i32; };\n", "export fn main() i32 = { return 0; };\n", "package alpha;\n", "type Color = enum i32 { RED = 100, };\n", "package beta;\n", "type Color = enum i32 { RED = 7, };\n"), "TEXT gamma.readalpharedgamma", "$100,", "$7,"); }; // 734: structlookup same-module-first (row 1) and the pre-existing // embedded-dot smod==pkg branch for `alpha.S` (row 2). Field offsets // 12 vs 32 chosen substring-safe. @test fn structmodshadow() void = { wantantirow("bare_leaf_same_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package beta;\n", "type S = struct { tag1: i32, tag2: i32, tag3: i32, mark: i32, };\n", "export fn readbetamark(s: *S) i32 = { return s.mark; };\n", "package alpha;\n", "type S = struct { p1: i64, p2: i64, p3: i64, p4: i64, mark: i32, };\n"), "TEXT beta.readbetamark", "12(BX),", "32(BX),"); wantantirow("dot_qualified_explicit_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package alpha;\n", "type S = struct { p1: i64, p2: i64, p3: i64, p4: i64, mark: i32, };\n", "export fn readalphamark(s: *alpha.S) i32 = { return s.mark; };\n", "package beta;\n", "type S = struct { tag1: i32, tag2: i32, tag3: i32, mark: i32, };\n"), "TEXT alpha.readalphamark", "32(BX),", "12(BX),"); }; // 735: deflookuprhs same-module-first on the str-def .len fold. // Strlit lengths 41 vs 27 chosen substring-safe and off common // frame/offset immediates. @test fn defmodshadow() void = { wantantirow("bare_leaf_same_module", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package alpha;\n", "def MSG: str = \"alpha_msg_for_modshadow_test_pin_45_AAAAA\";\n", "export fn alphalen() i32 = { return MSG.len: i32; };\n", "package beta;\n", "def MSG: str = \"beta_msg_short_27_chr_pin_X\";\n"), "TEXT alpha.alphalen", "$41,", "$27,"); }; // 745 (#34, sub-bug of #4e): the caller module itself exports the // colliding same-leaf fn, so the post-#4e same-module-first walk is // the wrong hint for a cross-module N_DOT callee — the phantom // MOVQ DX, BX str-shuffle after CALL myutf8.slice is the regression. @test fn fnret34modshadow() void = { wantantirow("cross_module_same_leaf_str_shuffle", strings.concat( "package caller;\n", "import myutf8;\n", "export fn main() i32 = { return 0; };\n", "export fn slice(s: str) str = { return s; };\n", "export fn run() i32 = {\n", "\tlet d: myutf8.decoder;\n", "\td.x = 0;\n", "\tlet s: []u8 = myutf8.slice(&d);\n", "\treturn s.len;\n", "};\n", "package myutf8;\n", "export type decoder = struct { x: i32 };\n", "export fn slice(d: *myutf8.decoder) []u8 = {\n", "\tlet r: []u8;\n", "\tr.ptr = nil: *u8;\n", "\tr.len = d.x;\n", "\tr.cap = d.x;\n", "\treturn r;\n", "};\n"), "TEXT caller.run", "CALL\tmyutf8.slice", "MOVQ\tDX, BX"); }; // 747 (#11): third-module `alpha.MSG` qualifier must resolve via the // dotted-lhs module hint, not c.curmod (gamma matches neither def) // and not the head-pick (beta.MSG heads sdefs: alpha declared first). // Strlit lengths 38 vs 27 substring-safe. @test fn defmodqualmodshadow() void = { wantantirow("third_module_qualifier_shadow", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "export fn gfn() str = { return alpha.MSG; };\n", "package alpha;\n", "export def MSG: str = \"alpha_modqual_pin_38chr_aaaaaaaaaaaaaa\";\n", "package beta;\n", "export def MSG: str = \"beta_modqual_pin_27chr_BBBB\";\n"), "TEXT gamma.gfn", "$38,", "$27,"); }; // 726 row 1: from the zero-init store to the first RET the invalid- // slot read must be an 8B MOVQ; a MOVSXD there is the pre-#27 // silent-correct-by-zero-init 4B sign-extend (wrong-module !i32 // alias picked over the caller's own !void). fn movqinvalidcheck(label: str, stage: str, s: str) void = { let fp: i32 = testenv.pos(s, "TEXT beta.yield_more"); if (fp < 0) { fail(label, strings.concat(stage, ": no TEXT beta.yield_more in .s")); }; let tail: str = strings.sub(s, fp, s.len); let zp: i32 = testenv.pos(tail, "MOVQ\t$0, "); if (zp < 0) { fail(label, strings.concat(stage, ": no zero-init store")); }; let zt: str = strings.sub(tail, zp, tail.len); let rp: i32 = testenv.pos(zt, "RET"); let w: str = zt; if (rp >= 0) { w = strings.sub(zt, 0, rp); }; if (testenv.has(w, "MOVSXD\t")) { fail(label, strings.concat(stage, ": post-zero-init slot read uses MOVSXD")); }; if (!testenv.has(w, "\n\tMOVQ\t-")) { fail(label, strings.concat(stage, ": post-zero-init slot read missing MOVQ")); }; }; // 726 row 2 regression guard: a real signed-narrow i32 local read // must stay MOVSXD — a blanket-MOVQ "fix" of the collision would // silently drop sign-extension on legitimate reads. fn movsxdkeptcheck(label: str, stage: str, s: str) void = { let w: str = asmwindow(label, stage, s, "TEXT main.promote", "RET", 0); if (testenv.occurrences(w, "MOVSXD\t-") < 2) { fail(label, strings.concat(stage, ": expected MOVSXD on i32 local reads")); }; }; fn aliasrow(label: str, src: str, movqinvalid: bool) void = { let td: str = testenv.fresh(); testenv.writefile(strings.concat(td, "/src.ww"), src); emitstage(td, label, "cstage", testenv.driver("w6c"), "cs.s"); emitstage(td, label, "wwstage", testenv.driver("w6c_ww"), "ws.s"); let cs: str = testenv.readfile(strings.concat(td, "/cs.s")); let ws: str = testenv.readfile(strings.concat(td, "/ws.s")); if (movqinvalid) { movqinvalidcheck(label, "cstage", cs); movqinvalidcheck(label, "wwstage", ws); } else { movsxdkeptcheck(label, "cstage", cs); movsxdkeptcheck(label, "wwstage", ws); }; if (!testenv.same(cs, ws)) { fail(label, "cstage vs wwstage asm differs"); }; testenv.clean(td); }; // 726 (#27): aliaslookup same-module-first. alpha's `type invalid = // !i32` registers last (head of c.aliases) masking beta's `!void` in // a head-first walk. Row 2's `package main;` line reproduces the // carrier's wwtest_fputs prefix. @test fn aliasleafcollision() void = { aliasrow("void_invalid_under_i32_collision", strings.concat( "package gamma;\n", "import alpha;\n", "import beta;\n", "export fn main() i32 = { return 0; };\n", "package beta;\n", "type more = void;\n", "type invalid = !void;\n", "fn yield_more() (rune | more | invalid) = {\n", " let e: invalid;\n", " return e;\n", "};\n", "package alpha;\n", "export type invalid = !i32;\n"), true); aliasrow("i32_local_read_keeps_movsxd", strings.concat( "package main;\n", "fn promote(x: i32) i64 = {\n", " let i: i32 = x;\n", " return (i: i64);\n", "};\n"), false); };