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.
This commit is contained in:
22
Makefile
22
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/label_test.ww test/xmod/typecheck_test.ww \
|
||||||
test/xmod/enumcap_test.ww test/xmod/modreset_test.ww
|
test/xmod/enumcap_test.ww test/xmod/modreset_test.ww
|
||||||
XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%)
|
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 \
|
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||||
test/wcc/991_w6a_ww.c \
|
test/wcc/991_w6a_ww.c \
|
||||||
test/wcc/992_w6l_ww.c test/wcc/993_ww_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
|
# gates that cannot be represented by a single compiler fixture, plus the
|
||||||
# ww-native sep observers that replaced the 989_sep* carriers.
|
# ww-native sep observers that replaced the 989_sep* carriers.
|
||||||
test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \
|
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)
|
@$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS)
|
||||||
@set -e; for t in $(COMPILER_WRAPPER_BINS); do \
|
@set -e; for t in $(COMPILER_WRAPPER_BINS); do \
|
||||||
echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \
|
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 /,_,$*) \
|
-w $(WWBUILD)/wwtest/$(subst /,_,$*) \
|
||||||
-I $(CURDIR)/test $*
|
-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-lang: $(LANG_TEST_TARGETS)
|
||||||
|
|
||||||
test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \
|
test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \
|
||||||
|
|||||||
368
test/asm/modshadow_test.ww
Normal file
368
test/asm/modshadow_test.ww
Normal file
@@ -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 <sym>"
|
||||||
|
// 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);
|
||||||
|
};
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
@@ -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 <mangled>(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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
@@ -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 <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
runwait(const char *cmd)
|
|
||||||
{
|
|
||||||
int rc = system(cmd);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct 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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user