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:
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);
|
||||
};
|
||||
Reference in New Issue
Block a user