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:
2026-08-08 14:36:27 +09:00
parent 90b1005478
commit a50429138c
10 changed files with 389 additions and 1931 deletions

View File

@@ -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
View 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);
};

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}