From a50429138cc00b01ccb7dd7f702ae4291e0f678e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:36:27 +0900 Subject: [PATCH] test: port the modshadow asm-window observers to ww; birth test/asm 726/731/732/733/734/735/745/747 -> test/asm/modshadow_test.ww, one @test per carrier, every want/anti window, occurrence floor and byte-id leg preserved. New ASM_WW_TESTS block runs the test/asm observers under test-compiler beside the residual carriers. --- Makefile | 22 +- test/asm/modshadow_test.ww | 368 +++++++++++++++++++++++ test/wcc/726_alias_leaf_collision.c | 278 ----------------- test/wcc/731_fnret_bare_leaf_shadow.c | 251 ---------------- test/wcc/732_fnparams_bare_leaf_shadow.c | 240 --------------- test/wcc/733_enum_modshadow.c | 236 --------------- test/wcc/734_struct_modshadow.c | 247 --------------- test/wcc/735_def_modshadow.c | 230 -------------- test/wcc/745_fnret34_modshadow.c | 227 -------------- test/wcc/747_def_modqual_modshadow.c | 221 -------------- 10 files changed, 389 insertions(+), 1931 deletions(-) create mode 100644 test/asm/modshadow_test.ww delete mode 100644 test/wcc/726_alias_leaf_collision.c delete mode 100644 test/wcc/731_fnret_bare_leaf_shadow.c delete mode 100644 test/wcc/732_fnparams_bare_leaf_shadow.c delete mode 100644 test/wcc/733_enum_modshadow.c delete mode 100644 test/wcc/734_struct_modshadow.c delete mode 100644 test/wcc/735_def_modshadow.c delete mode 100644 test/wcc/745_fnret34_modshadow.c delete mode 100644 test/wcc/747_def_modqual_modshadow.c diff --git a/Makefile b/Makefile index 87a87f56..aa914cfa 100644 --- a/Makefile +++ b/Makefile @@ -376,6 +376,16 @@ XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \ test/xmod/label_test.ww test/xmod/typecheck_test.ww \ test/xmod/enumcap_test.ww test/xmod/modreset_test.ww XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) + +# Ww-native asm-window observers: single-file ww tests under test/asm/ +# on the test/testenv helper package, porting the retired 7xx +# assembly-window C carriers (positional TEXT..RET windows, +# needle/anti-needle pairs, occurrence counts, per-row cs-vs-ws +# byte-id legs). They observe compiler BEHAVIOR by driving w6c and +# w6c_ww per row — not identity gates — so they run under +# test-compiler beside the surviving residual carriers. +ASM_WW_TESTS = test/asm/modshadow_test.ww +ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \ @@ -461,7 +471,7 @@ test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww $(BIN)/w6a_ww \ # gates that cannot be represented by a single compiler fixture, plus the # ww-native sep observers that replaced the 989_sep* carriers. test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \ - $(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) + $(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) $(ASM_WW_TARGETS) @$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS) @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ @@ -521,6 +531,16 @@ $(XMOD_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ -I $(CURDIR)/test $* +# The asm observers launch only the per-stage compilers (w6c, w6c_ww); +# the remaining tools build and run the test binary itself. +$(ASM_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a $(BIN)/w6c_ww + @echo "ww test $*" + @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) + @WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \ + -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ + -I $(CURDIR)/test $* + test-lang: $(LANG_TEST_TARGETS) test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \ diff --git a/test/asm/modshadow_test.ww b/test/asm/modshadow_test.ww new file mode 100644 index 00000000..4fcabdf0 --- /dev/null +++ b/test/asm/modshadow_test.ww @@ -0,0 +1,368 @@ +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); +}; diff --git a/test/wcc/726_alias_leaf_collision.c b/test/wcc/726_alias_leaf_collision.c deleted file mode 100644 index e3186bde..00000000 --- a/test/wcc/726_alias_leaf_collision.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 726_alias_leaf_collision — sentinel for #27. Pins wwstage's - * aliaslookup to a same-module-first leaf walk so a bare `invalid` - * inside module M (with `type invalid = !void;`) resolves to M's - * underlying void, not to some other module's `type invalid = !i32;` - * registered later in c.aliases. - * - * Pre-fix wwstage's `aliaslookup` (selfhost/cmd/wcc/cgen.ww) walked - * c.aliases head-first by leaf name, returning the FIRST match. With - * combined-source build orderings where a `type invalid = !i32;` from - * lib/strconv sits ahead of utf8's `type invalid = !void;` in the - * chain, `localloadop` resolved invalid → !i32 (size 4, signed) and - * emitted MOVSXD on a slot the let-decl prologue zero-inited 8B-wide - * (typeis8byteprimitive walks to void → MOVQ $0). Silent-correct-by- - * zero-init: the upper 4B happened to be zero from the zero-init, so - * the 4B sign-extend produced the right value, but the asm diverged - * from cstage's MOVQ and 995_self_rebuild broke once any caller of - * lib/strings/strings.byteindex (the (str|rune) match-arm shape that - * dragged utf8 into the bootstrap closure) bumped the alias-chain - * ordering. - * - * Cstage's `resolve_typename` (cmd/wcc/check.c:65) calls - * `scope_lookup_prefer(cur, cur_mod, nm)` — same-module-first then - * fallback. The fix mirrors that discipline in wwstage's aliaslookup. - * Asserts byte-id between cstage and wwstage on a probe shape that - * loses the asm divergence under the fix. - */ -#include -#include -#include -#include -#include -#include "wwtestpkg.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 row { - const char *label; - const char *src; - /* `want` selects which assertion family runs on the emitted .s: - * "movq_invalid" — post-zero-init load on beta.yield_more's - * invalid-slot must be MOVQ (the collision case). - * "movsxd_i32" — local i32 read in promote must be MOVSXD on - * BOTH stages. Guards future "fix" attempts - * that pessimize localloadop and would break - * legitimate signed-narrow loads. */ - const char *want; -}; - -/* Module ordering picked so the colliding `type invalid = !i32;` lands - * later in c.aliases (head-prepend → at the head), masking module M's - * own `type invalid = !void;`. `gamma` brings both modules into scope - * via use directives so decl_mod tags both as imports — that gates - * scope_define_in_module's per-mod dedup path on cstage's side. */ -static const struct row rows[] = { - { "void_invalid_under_i32_collision", - "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", - "movq_invalid" }, - /* Regression guard: a real signed-narrow `let i: i32 = ...;` read - * MUST stay on MOVSXD. Any future "fix" that pessimizes - * localloadop to blanket-MOVQ in the name of #27 would silently - * regress this — losing sign-extension on i32 → i64 promotion - * (also drops the high half of a deref-store-narrowed slot). */ - { "i32_local_read_keeps_movsxd", - "fn promote(x: i32) i64 = {\n" - " let i: i32 = x;\n" - " return (i: i64);\n" - "};\n", - "movsxd_i32" }, -}; - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/alc_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/alc_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - wwtest_fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -/* The yield_more body must read its tagged-return payload through an - * 8B MOVQ. A MOVSXD on the same slot is the pre-fix silent-correct- - * by-zero-init shape. */ -static int -check_movq_after_zeroinit(const char *spath, const struct row *r, - const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, "TEXT beta.yield_more"); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no TEXT beta.yield_more in %s\n", - r->label, stage, spath); - return -1; - } - const char *zi = strstr(fn, "MOVQ\t$0, "); - if (!zi) { - fprintf(stderr, "row[%s][%s]: no zero-init store\n", - r->label, stage); - return -1; - } - const char *bad = strstr(zi, "MOVSXD\t"); - const char *good = strstr(zi, "\n\tMOVQ\t-"); - const char *ret = strstr(zi, "RET"); - if (bad && (ret == NULL || bad < ret)) { - fprintf(stderr, - "row[%s][%s]: post-zero-init slot read uses MOVSXD\n", - r->label, stage); - return -1; - } - if (!good || (ret && good > ret)) { - fprintf(stderr, - "row[%s][%s]: post-zero-init slot read missing MOVQ\n", - r->label, stage); - return -1; - } - return 0; -} - -/* Real i32 local read MUST stay MOVSXD. Any MOVQ load of `i` (the - * narrow signed local) inside promote means a later "fix" pessimized - * localloadop and silently dropped sign-extension. */ -static int -check_movsxd_kept(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, "TEXT main.promote"); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no TEXT promote in %s\n", - r->label, stage, spath); - return -1; - } - const char *ret = strstr(fn, "RET"); - const char *bp = fn; - int sxd = 0; - for (;;) { - const char *p = strstr(bp, "MOVSXD\t-"); - if (!p || (ret && p > ret)) break; - sxd++; - bp = p + 1; - } - if (sxd < 2) { - fprintf(stderr, - "row[%s][%s]: expected MOVSXD on i32 local reads, found %d\n", - r->label, stage, sxd); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "alias_leaf_collision[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - int rc; - if (strcmp(rows[i].want, "movsxd_i32") == 0) { - rc = check_movsxd_kept(cs_path, &rows[i], "cstage"); - } else { - rc = check_movq_after_zeroinit(cs_path, &rows[i], "cstage"); - } - if (rc != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "alias_leaf_collision[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (strcmp(rows[i].want, "movsxd_i32") == 0) { - rc = check_movsxd_kept(ws_path, &rows[i], "wwstage"); - } else { - rc = check_movq_after_zeroinit(ws_path, &rows[i], "wwstage"); - } - if (rc != 0) fail++; - - /* byte-id between stages on the same input — the principled - * sentinel for the polarity catalog. */ - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "alias_leaf_collision[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "alias_leaf_collision: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("alias_leaf_collision: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/731_fnret_bare_leaf_shadow.c b/test/wcc/731_fnret_bare_leaf_shadow.c deleted file mode 100644 index 3eac9476..00000000 --- a/test/wcc/731_fnret_bare_leaf_shadow.c +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 731_fnret_bare_leaf_shadow — sentinel for the trio leaf-name - * pattern's 8th and FINAL leaf: wwstage's bare-leaf fnretlookup - * (selfhost/cmd/wcc/cgen.ww). Pins bare-leaf `foo()` calls (N_IDENT - * callee) to a same-module-first walk so cgcall's str-pair shuffle - * decision fires against the caller's own foo's return type, not - * another module's same-leaf-name foo sitting at the head of - * c.fnrets. - * - * Pre-fix wwstage's `fnretlookup` walked c.fnrets head-first by - * fname and returned the FIRST match's rtype. cgcall (cgenexpr.ww - * cgcall:3249) handed it `calleename` (the bare leaf from an - * N_IDENT callee) and the head-pick silently picked a sibling - * module's same-leaf foo. When that sibling foo returned `str` - * and the caller's own foo returned a scalar (i64 here), the - * `isstrtype(c, rt)` guard fired against the wrong type and - * emitted a spurious `MOVQ DX, BX` shuffle after the CALL — the - * SysV (AX, DX) → ww str (AX, BX) shape — corrupting BX even - * though the callee never returned an str pair. Every other bare- - * leaf consumer (taggedcallslot, callsretsize, exprfloatkind, - * rhstaggedabicall, tuple destructure in cglet / cgmlet, fn-rvalue - * LEAQ in cgident, cgtryprop/cgtryunw success-shuffle) keys on - * the same fnretlookup return and was silently miscompiling under - * the same collision shape. - * - * Cstage carries no sister bug: cmd/wcc/check.c N_CALL routes - * `cexpr(c, n->lhs)` through scope_lookup_prefer for an N_IDENT - * callee, then cmd/w6c/cgen.c reads the return type from the - * typed `n->lhs->type`'s TY_FN sig — module-aware via the typed - * AST, sidestepping any bare-leaf table. Same shape as #4a/#4b/ - * #4c/#4d: cs vs ws diverge on every same-leaf fn return-type - * collision but no in-tree corpus declares two same-leaf fns - * with diverging return-type *categories* (str vs scalar, tagged - * vs not, tuple vs not, float vs int) today, so 995_self_rebuild - * stays green — the in-corpus leaf collisions (utf8.next vs - * strings.next, strconv.invalid vs utf8.invalid, etc.) are all - * routed through the existing fnretlookupmod N_DOT variant and - * the bare-leaf path never sees them on the present corpus. - * - * Eighth and FINAL leaf of the trio graduation (after #27 - * aliaslookup, #28 fnparams *mod*-variant, #31 fnret *mod*-variant, - * #4a enum, #4b struct, #4c def, #4d fnparams bare-leaf). - * fnretlookupmod (the N_DOT consumer at cgen.ww:1585) already - * exists post-#31; this commit graduates only the BARE-LEAF entry - * point with a same-module-first walk mirroring fnparamslookup's - * two-pass shape (#4d). Twelve+ bare-leaf callsites consume the - * graduated lookup uniformly (cgenutil.ww:468/494/611/1428/1674/ - * 2286/2795, cgenexpr.ww:160/204/565/1709/3249, cgenstmt.ww:173/ - * 1017, cgendecl.ww:104) — none are independently re-routed to - * fnretlookupmod in this commit. The in-tree N_DOT cross-module - * fn collisions (utf8.next vs strings.next; bytes.hasprefix vs - * strings.hasprefix and equivalents) all have invariant return - * shape across the colliding overloads (rune-or-done tagged on - * both next, bool on both hasprefix), so the consumer behaviour - * is invariant either way for the N_DOT-feeding sites (e.g. the - * cgcall:3249 callsite reads callee.kind == N_DOT and feeds the - * bare leaf — invariant on the present corpus). A future stdlib - * port introducing a return-category-divergent same-leaf N_DOT - * collision will need the *mod re-routing — file at that - * surfacing. - * - * Pin: row 1 sentinel-flips the bare-leaf graduation on wwstage — - * revert the prefer pass and row 1 fails on wwstage (a spurious - * `MOVQ DX, BX` for the wrong-module str shuffle sneaks in after - * the CALL). Cstage sees no shuffle either way (typed AST is - * module-aware). Asserts CALL alpha.foo present inside the right - * TEXT sym + bad_imm (`MOVQ\tDX, BX`) anti-check on each stage - * plus cs-vs-ws byte-id per row. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the call */ - const char *want_imm; /* substring that MUST appear */ - const char *bad_imm; /* substring that MUST NOT appear */ -}; - -/* Source ordering picks which module's `fn foo` sits at the head of - * c.fnrets after collectfnrets' head-prepend walk. The LAST `fn foo` - * in source order ends at the head — that's the leaf-collision the - * same-module-first walk must beat. Caller is inside alpha; alpha's - * foo returns i64 (no str shuffle), beta's foo returns str (head- - * pick would falsely emit MOVQ DX, BX). */ -static const struct row rows[] = { - { "bare_leaf_same_module", - "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" }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/frb_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/frb_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, the want_imm MUST - * appear and the bad_imm MUST NOT. bad_imm flags pre-fix bare-leaf - * head-walk picking beta's str-returning foo and emitting a spurious - * MOVQ DX, BX after the CALL to a scalar-returning fn. */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module foo str-shuffled\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "fnret_bare_leaf_shadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "fnret_bare_leaf_shadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "fnret_bare_leaf_shadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "fnret_bare_leaf_shadow: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("fnret_bare_leaf_shadow: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/732_fnparams_bare_leaf_shadow.c b/test/wcc/732_fnparams_bare_leaf_shadow.c deleted file mode 100644 index c28e68cf..00000000 --- a/test/wcc/732_fnparams_bare_leaf_shadow.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 732_fnparams_bare_leaf_shadow — sentinel for the trio leaf-name - * pattern's 7th leaf: wwstage's bare-leaf fnparamslookup - * (selfhost/cmd/wcc/cgen.ww). Pins bare-leaf `foo(x)` calls (N_IDENT - * callee) to a same-module-first walk so cgcall's widening detection - * fires against the caller's own foo's param-type, not another - * module's same-leaf-name foo sitting at the head of c.fnrets. - * - * Pre-fix wwstage's `fnparamslookup` walked c.fnrets head-first by - * fname and returned the FIRST match's params. cgcall handed it - * `callee.str` (the bare leaf from an N_IDENT callee) and the head- - * pick silently picked a sibling module's same-leaf foo. When that - * foo had a tagged-union param and the caller's own foo had a plain - * scalar param, pushargsrev's `istaggedtype(c, pt)` then fired - * against the wrong type and re-laid the i32 arg into a 2-word - * tagged slot (MOVQ $7 push + MOVQ $0 tag push + 2 POPs into DI/SI) - * instead of the caller-intended single push (MOVQ $7 push + POPQ DI). - * - * Cstage carries no sister bug: cmd/wcc/check.c N_CALL routes - * `cexpr(c, n->lhs)` through scope_lookup_prefer for an N_IDENT - * callee, then cmd/w6c/cgen.c reads params from the typed - * `n->lhs->type`'s TY_FN sig — module-aware via the typed AST, - * sidestepping any bare-leaf table. Same shape as #4a/#4b/#4c: cs vs - * ws diverge on every same-leaf fn collision, but no in-tree corpus - * declares two same-leaf fns with diverging tagged-vs-scalar param - * shapes today, so 995_self_rebuild stays green. - * - * Seventh leaf of the trio graduation (after #27 aliaslookup, #28 - * fnparams *mod*-variant, #31 fnret *mod*-variant, #4a enum, #4b - * struct, #4c def). fnparamslookupmod (the N_DOT consumer at - * cgenexpr.ww:2876) already exists post-#28; this commit graduates - * only the BARE-LEAF entry point. The two other bare-leaf callsites - * (cgendecl.ww:420 scratch-reservation, cgenutil.ww:66 - * callee_variadic_param) consume the graduated lookup uniformly for - * N_IDENT callees and stay structurally aligned with cgcall's - * N_IDENT-branch — the cstage-natively-correct path. No - * fnparamslookupmod re-routing of those two sites in this commit: - * their N_DOT consumer surface (utf8.next called from strings, etc.) - * has non-tagged param shape on both sides of every in-tree leaf - * collision today, so the pre-existing head-pick stays accidentally - * correct. A future stdlib port introducing a tagged-vs-scalar N_DOT - * leaf collision will need the *mod re-routing — file at that - * surfacing. - * - * Pin: row 1 sentinel-flips the bare-leaf graduation on wwstage — - * revert the prefer pass and row 1 fails on wwstage (the extra - * `POPQ SI` for the wrong-param-shape widening sneaks in). Cstage - * sees one POPQ DI either way (typed AST is module-aware). Asserts - * the single-pop shape inside the right TEXT sym + bad_imm - * (`POPQ\tSI`) anti-check on each stage plus cs-vs-ws byte-id per - * row. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the call */ - const char *want_imm; /* substring that MUST appear */ - const char *bad_imm; /* substring that MUST NOT appear */ -}; - -/* Source ordering picks which module's `fn foo` sits at the head of - * c.fnrets after collectfnrets' head-prepend walk. The LAST `fn foo` - * in source order ends at the head — that's the leaf-collision the - * same-module-first walk must beat. Caller is inside alpha; alpha's - * foo has plain i32 param (no widening), beta's foo has tagged - * `(i32 | void)` param (widening would re-lay 7 into a 2-word slot - * + emit a tag push + 2 POPs). */ -static const struct row rows[] = { - { "bare_leaf_same_module", - "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" }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/fpb_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/fpb_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, the want_imm MUST - * appear and the bad_imm MUST NOT. bad_imm flags pre-fix bare-leaf - * head-walk picking beta's (i32|void)-param foo and emitting the - * extra POPQ SI for the wrongly-widened 2-word tagged slot. */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module foo widened\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "fnparams_bare_leaf_shadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "fnparams_bare_leaf_shadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "fnparams_bare_leaf_shadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "fnparams_bare_leaf_shadow: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("fnparams_bare_leaf_shadow: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/733_enum_modshadow.c b/test/wcc/733_enum_modshadow.c deleted file mode 100644 index d1534f07..00000000 --- a/test/wcc/733_enum_modshadow.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 733_enum_modshadow — sentinel for the trio leaf-name pattern's - * 4th leaf: wwstage's enumlookup. Pins (a) bare-leaf `Color.MEMBER` - * to a same-module-first walk so the constant folds against the - * caller's own Color rather than another module's same-leaf-name - * Color sitting at the head of c.enums, and (b) `pkg.Color.MEMBER` - * to the new enumlookupmod variant so the explicit qualifier wins - * the leaf-collision on the same shape as fnparamslookupmod (#28) - * and fnretlookupmod (#31). - * - * Pre-fix wwstage's `enumlookup` (selfhost/cmd/wcc/cgen.ww) walked - * c.enums head-first by ename, returning the FIRST match. The - * cgdot enum branch handed it `lhs.str` (bare leaf) for both the - * `Color.MEMBER` shape (lhs N_IDENT) and the `pkg.Color.MEMBER` - * shape (lhs N_DOT) — explicit pkg silently dropped. Either - * collision direction folded `Color.RED` to the wrong constant. - * Silent miscompile, not byte-id: cstage folds at the checker via - * scope_lookup_prefer (cur_mod, "Color") for the bare case and via - * the inner N_DOT's typed SK_USE.SK_TYPE lookup for the qualified - * case, so cs vs ws asm diverged on every collision shape — but no - * stdlib in-tree currently declares two same-leaf enums, so 995 - * stayed green. - * - * Asserts the post-fix constants land inside the matching TEXT - * sym on BOTH stages and that cs vs ws stay byte-identical on - * each row. Sentinel-flips if any of the three pieces revert: - * the enumlookup same-module-first walk, the enumlookupmod - * variant, or the cgdot N_DOT routing. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the load */ - const char *want_imm; /* the immediate that MUST appear */ - const char *bad_imm; /* the immediate that MUST NOT appear */ -}; - -/* Source orderings pick which module's Color sits at the head of - * c.enums after collectenums' head-prepend walk. The LAST `type - * Color = enum ...` in source order ends at the head — that's the - * leaf-collision the same-module-first walk must beat. */ -static const struct row rows[] = { - { "bare_leaf_same_module", - "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," }, - { "dot_qualified_explicit_module", - "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's module is gamma — neither alpha nor beta — so the - * enumlookup same-module-first walk finds NO Color match in - * gamma. Without the cgdot etmod tracking + enumlookupmod - * variant, enumlookup falls back to head-walk and picks beta's - * Color (last declared → prepended → head). With the new path, - * enumlookupmod("Color", "alpha") prefers alpha. Pins the - * second piece of the trio-leaf fix independent of row 1. */ - { "dot_qualified_cross_module", - "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," }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/ems_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/ems_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, the want_imm - * MUST appear and the bad_imm MUST NOT. bad_imm flags pre-fix - * head-walk picking the wrong-module Color. */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module Color\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "enum_modshadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "enum_modshadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "enum_modshadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "enum_modshadow: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("enum_modshadow: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/734_struct_modshadow.c b/test/wcc/734_struct_modshadow.c deleted file mode 100644 index 0d56625e..00000000 --- a/test/wcc/734_struct_modshadow.c +++ /dev/null @@ -1,247 +0,0 @@ -/* - * 734_struct_modshadow — sentinel for the trio leaf-name pattern's - * 5th leaf: wwstage's structlookup. Pins bare-leaf `*S` field access - * to a same-module-first walk so the load picks the caller's own - * `S` (correct field offset / totsize) rather than another module's - * same-leaf-name `S` sitting at the head of c.structs. - * - * Pre-fix wwstage's `structlookup` (selfhost/cmd/wcc/cgenutil.ww) - * walked c.structs head-first by sname, returning the FIRST match. - * The cgdot `*struct` field-load branch handed it `inner.str` (the - * bare leaf from a parsed N_TPTR whose inner is N_TNAME) and the - * head-pick silently emitted the wrong-module field offset — a - * displacement against BX that loaded whatever the colliding-module - * struct happened to align there. Silent miscompile, not byte-id: - * cstage's resolve_typename uses scope_lookup_prefer(cur, cur_mod, - * leaf) so check.c already binds the correct Type, and cgen.c reads - * fi.foff off the typed Sym — cs vs ws asm diverged on every bare- - * leaf collision but no in-tree corpus declares two same-leaf - * structs, so 995 stayed green and the latent miscompile only - * surfaces once a stdlib port introduces the collision (same shape - * as #4a enumlookup surfacing post-strings). - * - * Trio's structural close per task #4 leaves: structlookup grows a - * same-module-first walk before the head-walk fallback, mirroring - * aliaslookup (#27), fnparamslookupmod (#28), fnretlookupmod (#31) - * and enumlookup (#4a). NO structlookupmod variant: the consumer - * surface for `pkg.S` is fully captured by the parser collapsing - * dotted N_TNAME into a single str with embedded `.`, which routes - * through structlookup's existing smod==pkg embedded-dot branch. - * Cstage carries no sister bug — resolve_typename already routes - * the bare-leaf TY_STRUCT name through scope_lookup_prefer per - * c->cur_mod (cmd/wcc/check.c:60). - * - * Asserts the post-fix field-offset land inside the matching TEXT - * sym on BOTH stages and that cs vs ws stay byte-identical on each - * row. Row 1 sentinel-flips this commit's new same-module-first - * walk (revert the structlookup change → row 1 fails on wwstage - * + cs-vs-ws diff). Row 2 exercises the pre-existing embedded-dot - * smod==pkg branch (unchanged here) — defensive coverage that - * guards the second structlookup path against future regression; - * does not sentinel-flip on this commit's change. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the load */ - const char *want_imm; /* the displacement that MUST appear */ - const char *bad_imm; /* the displacement that MUST NOT appear */ -}; - -/* Field layout (registerstruct: 8B alignment for >=8B, 4B for >=4). - * Offsets chosen so neither digit-string is a substring of the - * other (12 vs 32) — strstr-based needle matching would otherwise - * spuriously hit "4(BX)," inside "24(BX),". - * beta.S = { tag1, tag2, tag3, mark: i32 } → mark foff = 12, totsize = 16 - * alpha.S = { p1, p2, p3, p4: i64, mark: i32 } - * → mark foff = 32, totsize = 40 - * - * Source orderings pick which module's S sits at the head of - * c.structs after collectstructs' head-prepend walk. The LAST `type - * S = struct ...` in source order ends at the head — that's the - * leaf-collision the same-module-first walk must beat. */ -static const struct row rows[] = { - { "bare_leaf_same_module", - "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)," }, - /* `alpha.S` collapses into a single N_TNAME str at parse time - * (lib/ww/parse/parse.ww joindotted), so structlookup is called - * with "alpha.S" and falls through the new same-module-first - * walk (no s.sname == "alpha.S") + head walk into the existing - * embedded-dot branch (leaf="S", pkg="alpha", filter smod==pkg). - * Pre/post-fix both pick alpha — row 2 sentinel-flips ONLY if - * the smod==pkg filter regresses, pinning the second lookup - * path independent of row 1's same-module-first walk. */ - { "dot_qualified_explicit_module", - "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)," }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/sms_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/sms_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, the want_imm - * MUST appear and the bad_imm MUST NOT. bad_imm flags pre-fix - * head-walk picking the wrong-module S's field offset. */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module S\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "struct_modshadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "struct_modshadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "struct_modshadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "struct_modshadow: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("struct_modshadow: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/735_def_modshadow.c b/test/wcc/735_def_modshadow.c deleted file mode 100644 index f33274bc..00000000 --- a/test/wcc/735_def_modshadow.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 735_def_modshadow — sentinel for the trio leaf-name pattern's 6th - * leaf: wwstage's deflookuprhs (selfhost/cmd/wcc/cgen.ww). Pins - * bare-leaf `MSG.len`/`MSG.ptr` on a `def MSG: str = "..."` constant - * to a same-module-first walk so the field-fold inlines the caller's - * own strlit length / address rather than another module's same-leaf- - * name def sitting at the head of c.defs. - * - * Pre-fix wwstage's `deflookuprhs` walked c.defs head-first by dname - * and returned the FIRST match's drhs. cgdot's str-def `.ptr`/`.len` - * branch handed it `lhs.str` (the bare leaf from a parsed N_IDENT) - * and the head-pick silently inlined the wrong-module strlit — a - * LEAQ of the other module's interned strlit label for `.ptr` and a - * MOVQ of the other module's byte-length for `.len`. Cstage's - * symmetric Sdef walk in cmd/w6c/cgen.c (N_DOT `.ptr`/`.len` field - * fold, off==0 branch) did the same head-first pick. The checker's - * resolve-side already routes through scope_lookup_prefer (cur, - * cur_mod, MSG) so the typed Sym is right, but cgen's Sdef table is - * keyed off the bare-leaf string with no module attribution — cs and - * ws diverged on every same-leaf def collision but no in-tree corpus - * declares two same-leaf str defs (same surfacing pattern as #4a - * enumlookup post-strings and #4b structlookup post-strings: latent - * until a stdlib port introduces the collision). - * - * Sixth leaf of the trio graduation (after #27 aliaslookup, #28 - * fnparams, #31 fnret, #4a enum, #4b struct): defent grows a dmod - * field, deflookup/deflookuprhs walk same-module-first against - * c.curmod before the head-walk fallback. Cstage Sdef grows a mod - * field; the symmetric N_DOT `.ptr`/`.len` field-fold walk gains the - * matching prefer pass keyed off c->cur_mod via sdef_mod_match. The - * two other cstage Sdef walks (N_IDENT bare-str-def load, N_DOT - * module-qualified fallback) stay on head-walk in this commit — - * both have a pre-existing wwstage byte-id break (wwstage emits a - * bogus MOVQ (SB) symbol load for bare/qualified str-def - * reference instead of strlit-inline), so a graduating prefer pass - * on the cstage side alone would not sentinel-flip via the cs-vs-ws - * byte-id check. Deferred to follow-ups #4c-tail-{bare,modqual} + - * wwstage str-def symbol-load fix. NO deflookupmod variant — same - * scope rationale as #4b's structlookup-no-mod (no in-tree consumer - * for `pkg.def.field`). - * - * Pin: row 1 sentinel-flips this commit's new same-module-first walk - * on BOTH stages — revert the prefer pass and row 1 fails on both - * cstage and wwstage. Asserts the .len fold lands inside the matching - * TEXT sym with the want_imm present + bad_imm anti-check on each - * stage plus cs-vs-ws byte-id per row. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the load */ - const char *want_imm; /* the immediate that MUST appear */ - const char *bad_imm; /* the immediate that MUST NOT appear */ -}; - -/* Source orderings pick which module's `def MSG` sits at the head of - * c.defs / sdefs after the collectdefs / sdef_collect head-prepend - * walk. The LAST `def MSG = ...` in source order ends at the head — - * that's the leaf-collision the same-module-first walk must beat. - * - * Strlit lengths chosen so neither digit-string is a substring of the - * other (41 vs 27) and neither aliases a common frame/offset constant - * the asm emits (0/8/16/24/32/48 etc) — strstr-based needle matching - * would otherwise spuriously hit "$1" inside "$16," etc. */ -static const struct row rows[] = { - { "bare_leaf_same_module", - "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," }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dms_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/dms_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, the want_imm - * MUST appear and the bad_imm MUST NOT. bad_imm flags pre-fix - * head-walk picking the wrong-module MSG's strlit length. */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module MSG\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "def_modshadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "def_modshadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "def_modshadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "def_modshadow: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("def_modshadow: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/745_fnret34_modshadow.c b/test/wcc/745_fnret34_modshadow.c deleted file mode 100644 index 72b59136..00000000 --- a/test/wcc/745_fnret34_modshadow.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 745_fnret34_modshadow — sentinel for task #34 (sub-bug of #4e): - * wwstage's cgcall return-ABI fixup keyed bare-leaf fnretlookup, not - * fnretlookupmod. For N_DOT cross-module callees the post-#4e same- - * module-first walk grabbed the caller-module's same-leaf fn's - * return type — when that caller-side fn returned `str` and the - * cross-module callee returned a non-str ([]u8, struct, scalar) the - * `isstrtype(c, rt)` guard fired against the wrong type and emitted - * a spurious `MOVQ DX, BX` shuffle after the cross-module CALL - * (selfhost/cmd/wcc/cgenexpr.ww cgcall return-ABI fixup). - * - * Sibling: nodeisslice / nodeisstr N_CALL arms were N_IDENT-only - * (selfhost/cmd/wcc/cgenutil.ww), so pushargsrev's slice/str word- - * count fell through to the 1-word natural-push for any cross- - * module N_DOT call result — dropping `.len` (and `.cap` for - * slices). Hare's `strings.slice` body - * (`fromutf8_unsafe(utf8.slice(begin, end))`, ref/hare/strings/ - * iter.ha:75-77) hits both bugs at once because strings.slice - * itself returns str (str-shuffle phantom on the inner utf8.slice - * call) AND passes the inner []u8 to a same-module fn (.len/.cap - * dropped on the push). - * - * Cstage carries no sister bug: cmd/w6c/cgen.c reads the return - * shape from the typed `n->lhs->type` (TY_FN sig), module-aware via - * the typed AST. Both cgcall's str-shuffle and the slice/str-arg - * push count come off n->type directly. Mirror of #4e: cstage - * sidesteps every fnretlookup-keyed bare-leaf table. - * - * Pin: row 1 — caller-module exports `fn slice(s: str) str` (same - * leaf as the cross-module callee, return shape diverges); calls - * `myutf8.slice(&d)` returning []u8. Pre-fix wwstage emits a phantom - * `MOVQ DX, BX` after the CALL (and drops the slice 3-word push if - * the result is consumed as a slice arg). Post-fix the TEXT sym - * for caller.run carries no `MOVQ DX, BX` between the CALL and the - * RET. Asserts CALL myutf8.slice present + bad_imm anti-check on - * each stage plus cs-vs-ws byte-id. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the call */ - const char *want_imm; /* substring that MUST appear */ - const char *bad_imm; /* substring that MUST NOT appear */ -}; - -/* caller.slice (same leaf as myutf8.slice, returns str) lives in the - * same source so caller's fnret table holds a slice-named fn returning - * str. caller.run calls the cross-module myutf8.slice (returning []u8). - * Pre-fix wwstage's cgcall fnretlookup picks caller.slice (same-module- - * first) and emits a phantom MOVQ DX, BX after the cross-module CALL. */ -static const struct row rows[] = { - { "cross_module_same_leaf_str_shuffle", - "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" }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/fr34_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/fr34_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, want_imm MUST - * appear and bad_imm MUST NOT. bad_imm flags pre-fix wwstage str- - * shuffle firing on a non-str cross-module callee. */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module str-shuffle phantom fired\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "fnret34_modshadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "fnret34_modshadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "fnret34_modshadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "fnret34_modshadow: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("fnret34_modshadow: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/747_def_modqual_modshadow.c b/test/wcc/747_def_modqual_modshadow.c deleted file mode 100644 index 17bf7195..00000000 --- a/test/wcc/747_def_modqual_modshadow.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 747_def_modqual_modshadow — sentinel for task #11: cstage Sdef walk #2 - * (cmd/w6c/cgen.c N_DOT mod-qualified bare-value load) + wwstage - * deflookuprhsmod (selfhost/cmd/wcc/cgenexpr.ww cgdot N_DOT branch). - * Pins `alpha.MSG` from a third module gamma to resolve via n->lhs->str - * (= "alpha") rather than head-pick of c.defs / sdefs so the inlined - * strlit address + length come from alpha's MSG even when beta declares - * a same-leaf MSG sitting at the head of the table. - * - * Pre-fix cstage walk #2 had no prefer-pass at all — head-pick of sdefs - * regardless of the explicit qualifier. Pre-fix wwstage called the bare- - * leaf deflookuprhs (#4c) which prefers c.curmod and falls back to head - * on mismatch — for a 3rd-module qualifier `alpha.MSG` from gamma the - * curmod hint matches nothing and the head-pick lands on beta.MSG. - * Sister of #4c's deflookuprhs / Sdef walk #3 graduation: the *mod - * variant pattern that #4a (enumlookupmod), #28 (fnparamslookupmod) and - * #31 (fnretlookupmod) established for N_DOT qualifier disambiguation - * — the dotted LHS module name is the right hint source, not c.curmod. - * - * Cstage walk #2 grows the sdef_mod_match_hint helper (sister of the - * existing sdef_mod_match which keys c->cur_mod) and routes the walk - * through it with n->lhs->str. Wwstage grows deflookuprhsmod(c, name, - * mod) (sister of deflookuprhs) and cgdot routes the mod-qualified - * branch through it with lhs.str. Both stages converge on the same - * polarity → byte-id holds on the 3rd-module qualifier shape. - * - * Was blocked solely on #12 (wwstage str-def value-load shape); #12 - * landed in d985622 so the sentinel-flip byte-id check now passes. - * - * Pin: row 1 sentinel-flips both stages' new prefer-pass — revert and - * row 1 fails on both (LEAQ of beta.MSG's strlit label instead of - * alpha.MSG's, and MOVQ $27 instead of $38). Asserts the matching - * `MOVQ $38,` immediate inside gamma.gfn TEXT + bad_imm `MOVQ $27,` - * anti-check on each stage plus cs-vs-ws byte-id per row. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - const char *textsym; /* TEXT sym containing the load */ - const char *want_imm; /* the immediate that MUST appear */ - const char *bad_imm; /* the immediate that MUST NOT appear */ -}; - -/* Source orderings: alpha declared before beta so beta.MSG sits at the - * HEAD of c.defs / sdefs after the prepend-walk of collectdefs / - * sdef_collect. Caller lives in a third module gamma (not alpha, not - * beta) so c.curmod / c->cur_mod doesn't accidentally match either - * source module — only the lhs.str / n->lhs->str hint resolves to - * alpha. Strlit lengths 39 vs 27 chosen so neither digit-string is a - * substring of the other and neither aliases a common frame/offset - * immediate the asm emits (0/8/16/24/32/40/48 etc). - * - * The TY_FN branch above walk #2 in cstage already uses n->lhs->str via - * mafn — this row pins the parallel polarity on the Sdef walk just - * below it. */ -static const struct row rows[] = { - { "third_module_qualifier_shadow", - "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," }, -}; - -static int -slurp(const char *path, char *buf, size_t cap) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - size_t n = fread(buf, 1, cap - 1, f); - fclose(f); - buf[n] = '\0'; - return (int)n; -} - -static int -emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/dmq_%d_%d.ww", getpid(), i); - snprintf(out_s, cap, "/tmp/dmq_%d_%d_%s.s", - getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -/* Inside the named TEXT sym, before its first RET, want_imm MUST appear - * and bad_imm MUST NOT. bad_imm flags pre-fix head-walk picking - * beta.MSG's strlit length (27) instead of alpha.MSG's (39). */ -static int -check_imm(const char *spath, const struct row *r, const char *stage) -{ - char buf[1 << 14]; - if (slurp(spath, buf, sizeof buf) < 0) { - fprintf(stderr, "row[%s][%s]: cannot read %s\n", - r->label, stage, spath); - return -1; - } - const char *fn = strstr(buf, r->textsym); - if (!fn) { - fprintf(stderr, "row[%s][%s]: no %s in %s\n", - r->label, stage, r->textsym, spath); - return -1; - } - const char *ret = strstr(fn, "\tRET"); - if (!ret) { - fprintf(stderr, "row[%s][%s]: no RET inside %s\n", - r->label, stage, r->textsym); - return -1; - } - const char *good = strstr(fn, r->want_imm); - if (!good || good >= ret) { - fprintf(stderr, - "row[%s][%s]: want_imm %s missing inside %s\n", - r->label, stage, r->want_imm, r->textsym); - return -1; - } - const char *bad = strstr(fn, r->bad_imm); - if (bad && bad < ret) { - fprintf(stderr, - "row[%s][%s]: bad_imm %s present inside %s — wrong-module MSG\n", - r->label, stage, r->bad_imm, r->textsym); - return -1; - } - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[512]; - if (bin[0] != '/') { - char cwd[256]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[640], w6c_ww[640]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - - int have_ww = (access(w6c_ww, X_OK) == 0); - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - char cs_path[128], ws_path[128]; - - if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) { - fprintf(stderr, - "def_modqual_modshadow[cstage][%s]: w6c failed\n", - rows[i].label); - fail++; total++; continue; - } - total++; - if (check_imm(cs_path, &rows[i], "cstage") != 0) fail++; - - if (!have_ww) { unlink(cs_path); continue; } - - if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) { - fprintf(stderr, - "def_modqual_modshadow[wwstage][%s]: w6c_ww failed\n", - rows[i].label); - fail++; total++; - unlink(cs_path); continue; - } - total++; - if (check_imm(ws_path, &rows[i], "wwstage") != 0) fail++; - - total++; - char cmd[512]; - snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path); - if (runwait(cmd) != 0) { - fprintf(stderr, - "def_modqual_modshadow[%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - - unlink(cs_path); unlink(ws_path); - } - - if (fail) { - fprintf(stderr, - "def_modqual_modshadow: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("def_modqual_modshadow: %d/%d ok\n", total, total); - return 0; -}