test: port the asm-pattern observers to ww; byteid carrier partition empty

Four ww tests carry the last eight native byteid carriers' assertions:
asmwindow (753 convwrap beta/main/alpha order + window polarity, 754
slice stride + negative scale scan, 755 amp-dot-idx four rows, 758
first-CALL-line extraction + tab-framed disp literals, direct
w6c/w6c_ww never ww build), freenoop (930 byte-id strengthened to
per-stream compare + negative 'free' grep), structabi (946 param/ret
MOVSD windows with polarity tables and SEND/RECV agreement), mangle
(989_m1mangle needles + concat byte-id, glob order strengthened to
byte-lexicographic). Dead want/stage_mask row fields documented, not
invented into runtime legs; the w6c_ww-absent skip gates drop because
the Make target declares the tools.

BYTEID_WRAPPER_SOURCES, its bins, and test-native-byteid are deleted —
the native byte/artifact partition is EMPTY (11 -> 0 this session);
docs counts move to 123 carriers.
This commit is contained in:
2026-08-08 04:41:42 +09:00
parent fa4b9a134b
commit 81e7f95548
14 changed files with 890 additions and 2297 deletions

View File

@@ -0,0 +1,364 @@
package asmwindow_test;
// Direct-w6c asm-window gate. Port of the retired native carriers
// test/wcc/753_convwrap_audit.c, 754_slice_of_slice_index.c,
// 755_amp_dot_idx.c and 758_cgalloc_str_field.c; every assertion
// preserved.
//
// Every row compiles an inline single-file source DIRECTLY through
// w6c and w6c_ww (`w6c -o out.s src.ww`), never `ww build` — the 758
// noscope rows exist precisely because `ww build` combines
// lib/rt/malloc.ww into the fixture and always supplies the @symbol
// decl, masking the hardcoded-rt_malloc regression they pin.
//
// Windowed search: a pattern must fall between "TEXT <sym>" and the
// first RET needle after it — positional index arithmetic, never a
// whole-file contains (753's want/bad pair discriminates the cgmlet
// wrong-module shape probe; main's own code must not satisfy a row).
// 754 row ptr_to_byte and 755 row byte_field_1 are NEGATIVE rows: no
// complete line `MOVQ $imm, CX` (a stride scale) may appear inside
// the window — a per-line scan, exactly the retired carriers' shape.
//
// Byte-identity legs (testenv.same over both .s bodies) ride every
// 754 and 755 row as in the C. 753 carries NONE by design (the
// `let a, b = call()` cs-vs-ws PUSHQ/POPQ DX cosmetic divergence is
// out of #17 scope — see the retired carrier); 758 carries NONE by
// design (task #24 part B history — its retained coverage is the
// CALL-line equality plus the exact-framing disp-line asserts).
//
// 753's single-file THREE-package source order beta/main/alpha is
// load-bearing (alpha registers last → head of c.fnrets) and is
// copied verbatim.
//
// Dropped C machinery, not assertions: the w6c_ww-absent skip gates
// (the Make target declares both tools), getpid()-keyed /tmp names
// and unlink-errno accounting (testenv.fresh/clean own the per-row
// scratch and assert cleanup), and the 16KB/1MB 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("asmwindow 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 (753) and widens the window to EOF
// otherwise (754/755).
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);
};
// Stride-1 rows must ELIDE the scale multiply: no complete line in the
// window may end `MOVQ $imm, CX`. An unterminated trailing chunk is
// skipped exactly as the carriers' eol-bounded scan skipped it.
fn nostridescale(label: str, stage: str, w: str) void = {
let i: i32 = 0;
for (i < w.len) {
let rest: str = strings.sub(w, i, w.len);
let e: i32 = testenv.pos(rest, "\n");
if (e < 0) { break; };
let line: str = strings.sub(w, i, i + e);
if (testenv.has(line, "\tMOVQ\t$")
&& strings.hassuffix(line, ", CX")) {
fail(label, strings.concat(stage,
": unexpected MOVQ $imm, CX scale inside the window"));
};
i = i + e + 1;
};
};
fn convcheck(label: str, stage: str, s: str) void = {
let w: str = asmwindow(label, stage, s, "TEXT main.run", "\tRET", 1);
if (!testenv.has(w, "MOVQ\tAX,")) {
fail(label, strings.concat(stage,
": want_imm MOVQ AX, missing inside TEXT main.run"));
};
if (testenv.has(w, "MOVQ\tCX,")) {
fail(label, strings.concat(stage,
": bad_imm MOVQ CX, present inside TEXT main.run -- ",
"wrong-module shape probe fired"));
};
};
// 753 row cgmlet_tuple_modshadow: beta.foo returns (i64, i64); a
// wrong-module probe grabbing alpha.foo's (i64, str) fires the
// (scalar, str) emit branch whose str.len store (MOVQ CX,) has no
// defined source on this ABI. No byte-id leg (see header).
@test fn convwrap() void = {
let label: str = "cgmlet_tuple_modshadow";
let td: str = testenv.fresh();
testenv.writefile(strings.concat(td, "/src.ww"), strings.concat(
"package beta;\n",
"export fn foo() (i64, i64) = { return (10i64, 20i64); };\n",
"package main;\n",
"import alpha;\n",
"import beta;\n",
"export fn run() i32 = {\n",
"\tlet a, b = beta.foo();\n",
"\tif (a != 10i64) { return 1; };\n",
"\tif (b != 20i64) { return 2; };\n",
"\treturn 0;\n",
"};\n",
"export fn main() i32 = { return run(); };\n",
"package alpha;\n",
"export fn foo() (i64, str) = { return (10i64, \"hi\"); };\n"));
emitstage(td, label, "cstage", testenv.driver("w6c"), "cs.s");
emitstage(td, label, "wwstage", testenv.driver("w6c_ww"), "ws.s");
convcheck(label, "cstage",
testenv.readfile(strings.concat(td, "/cs.s")));
convcheck(label, "wwstage",
testenv.readfile(strings.concat(td, "/ws.s")));
testenv.clean(td);
};
fn stridecheck(label: str, stage: str, s: str, stride: str) void = {
let w: str = asmwindow(label, stage, s, "TEXT main.probe", "\tRET\n",
0);
if (stride.len != 0) {
if (!testenv.has(w, stride)) {
fail(label, strings.concat(stage, ": expected `",
stride, "` inside TEXT main.probe"));
};
} else {
nostridescale(label, stage, w);
};
};
// One 754/755 row: emit both stages, window-check both, then rule-10
// byte-id. stride == "" selects the negative stride-scale scan.
fn striderow(label: str, src: str, stride: 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"));
stridecheck(label, "cstage", cs, stride);
stridecheck(label, "wwstage", ws, stride);
if (!testenv.same(cs, ws)) {
fail(label, "cstage vs wwstage asm differs");
};
testenv.clean(td);
};
// 754: `p[i]` for p: *[]T is []T (stride 24, slice header); p: *u8
// stays on the default `*T -> T` path with no scale at all. The
// `package main;` line reproduces the carrier's wwtest_fputs prefix.
@test fn slicestride() void = {
striderow("ptr_to_slice", strings.concat(
"package main;\n",
"fn probe(p: *[]u8) i32 = {\n",
"\tlet r: []u8 = p[0];\n",
"\treturn r.len;\n",
"};\n",
"export fn main() i32 = { return 0; };\n"),
"MOVQ\t$24, CX");
striderow("ptr_to_byte", strings.concat(
"package main;\n",
"fn probe(p: *u8) i32 = {\n",
"\tlet x: u8 = p[0];\n",
"\treturn x: i32;\n",
"};\n",
"export fn main() i32 = { return 0; };\n"),
"");
};
// 755: `&N_DOT[N_INDEX]` strides — 24 (slice header via the `.ptr`
// pseudo-field arm), 16 (struct slot), elided (u8, the Shape-B
// `.ptr`-arm overreach row), 8 (generic struct-field arm).
@test fn ampdotidx() void = {
striderow("slice_elem_24", strings.concat(
"package main;\n",
"fn probe(s: *[][]u8, i: i32) *[]u8 = {\n",
"\treturn &s.ptr[i];\n",
"};\n",
"export fn main() i32 = { return 0; };\n"),
"MOVQ\t$24, CX");
striderow("struct_field_16", strings.concat(
"package main;\n",
"type box16 = struct { a: i64, b: i64 };\n",
"type holder = struct { items: *box16 };\n",
"fn probe(p: *holder, i: i32) *box16 = {\n",
"\treturn &p.items[i];\n",
"};\n",
"export fn main() i32 = { return 0; };\n"),
"MOVQ\t$16, CX");
striderow("byte_field_1", strings.concat(
"package main;\n",
"type holder = struct { ptr: *u8 };\n",
"fn probe(p: *holder, i: i32) *u8 = {\n",
"\treturn &p.ptr[i];\n",
"};\n",
"export fn main() i32 = { return 0; };\n"),
"");
striderow("i64_field_8", strings.concat(
"package main;\n",
"type holder = struct { qs: *i64 };\n",
"fn probe(p: *holder, i: i32) *i64 = {\n",
"\treturn &p.qs[i];\n",
"};\n",
"export fn main() i32 = { return 0; };\n"),
"MOVQ\t$8, CX");
};
// FIRST line carrying a CALL to alloc(SB)/rt_malloc(SB), newline
// included (fgets semantics); "" when none.
fn firstcallline(s: str) str = {
let i: i32 = 0;
for (i < s.len) {
let rest: str = strings.sub(s, i, s.len);
let e: i32 = testenv.pos(rest, "\n");
let lend: i32 = s.len;
if (e >= 0) { lend = i + e + 1; };
let line: str = strings.sub(s, i, lend);
if (testenv.has(line, "\tCALL\t")) {
if (testenv.has(line, "alloc(SB)")
|| testenv.has(line, "rt_malloc(SB)")) {
return line;
};
};
i = lend;
};
return "";
};
// One 758 asm_row: both stages' first alloc-CALL lines must exist, be
// byte-equal to each other, and equal the exact expected line.
fn callrow(label: str, src: str, wantsym: 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 cline: str = firstcallline(
testenv.readfile(strings.concat(td, "/cs.s")));
let wline: str = firstcallline(
testenv.readfile(strings.concat(td, "/ws.s")));
if (cline.len == 0 || wline.len == 0) {
fail(label, "no CALL alloc line found");
};
if (!testenv.same(cline, wline)) {
fail(label, "cstage vs wwstage CALL line differs");
};
let want: str = strings.concat("\tCALL\t", wantsym, "(SB)\n");
if (!testenv.same(cline, want)) {
fail(label, strings.concat("CALL line is not CALL ", wantsym,
"(SB)"));
};
testenv.clean(td);
};
// 758 ffiresolve CALL-line parity: without an @symbol decl in scope
// ffiresolve("malloc") misses and both stages must say CALL
// malloc(SB); with the decl both must say CALL rt_malloc(SB).
@test fn calllines() void = {
callrow("noscope_intfield", strings.concat(
"package main;\n",
"type holder = struct { n: i32 };\n",
"fn dummy() *holder = { return alloc(holder { n = 42 })!; };\n"),
"malloc");
callrow("noscope_strfield", strings.concat(
"package main;\n",
"type holder = struct { s: str };\n",
"fn dummy() *holder = { return alloc(holder { s = \"hi\" })!; };\n"),
"malloc");
callrow("noscope_let_then_assign", strings.concat(
"package main;\n",
"type holder = struct { n: i32 };\n",
"fn dummy() *holder = {\n",
" let p: *holder = alloc(holder { n = 0 })!;\n",
" p.n = 7;\n",
" return p;\n",
"};\n"),
"malloc");
callrow("withsym_intfield", strings.concat(
"package main;\n",
"@symbol(\"rt_malloc\") export fn malloc(n: u64) *void;\n",
"type holder = struct { n: i32 };\n",
"fn dummy() *holder = { return alloc(holder { n = 42 })!; };\n"),
"rt_malloc");
};
// One 758 asm_disp_row: the literal line (exact tab/newline framing,
// so `(DX)` never collides with `0(DX)`) must appear in BOTH .s.
fn disprow(label: str, src: str, wantline: 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");
if (!testenv.has(testenv.readfile(strings.concat(td, "/cs.s")),
wantline)) {
fail(label, "want_line missing from the cstage .s");
};
if (!testenv.has(testenv.readfile(strings.concat(td, "/ws.s")),
wantline)) {
fail(label, "want_line missing from the wwstage .s");
};
testenv.clean(td);
};
// 758 foff displacement formatting (#24 part B): zero displacement
// prints `(REG)`, non-zero keeps its offset. str fields route the
// heap base through DX (str IS []u8, cap in CX); int/f64 use BX.
@test fn displines() void = {
disprow("alloc_str_at_offset0", strings.concat(
"package main;\n",
"type holder = struct { s: str };\n",
"fn dummy() *holder = { return alloc(holder { s = \"x\" })!; };\n"),
"\tMOVQ\tAX, (DX)\n");
disprow("alloc_str_at_nonzero_offset", strings.concat(
"package main;\n",
"type holder = struct { pad: i64, s: str };\n",
"fn dummy() *holder = {\n",
" return alloc(holder { pad = 0, s = \"x\" })!;\n",
"};\n"),
"\tMOVQ\tAX, 8(DX)\n");
disprow("alloc_int_at_offset0", strings.concat(
"package main;\n",
"type holder = struct { n: i64 };\n",
"fn dummy() *holder = { return alloc(holder { n = 42 })!; };\n"),
"\tMOVQ\tAX, (BX)\n");
disprow("alloc_f64_at_offset0", strings.concat(
"package main;\n",
"type holder = struct { f: f64 };\n",
"fn dummy() *holder = { return alloc(holder { f = 1.0f64 })!; };\n"),
"\tMOVSD\tX0, (BX)\n");
};

View File

@@ -0,0 +1,144 @@
package freenoop_test;
// free()-is-a-no-op byte-identity gate. Port of the retired native
// carrier test/wcc/930_free_noop_run.c; every assertion preserved.
//
// ww has no free by design (rt/alloc.s:30 — drop-amalloc); post-#27
// both stages evaluate free's operand for side effects and emit
// NOTHING else. Per row the inline source (prefixed exactly as the
// carrier wrote it, `package main;` + blank line) compiles through
// w6c AND w6c_ww with the asm on stdout; the stages must agree
// byte-for-byte and the NEGATIVE gate holds: no substring 'free'
// anywhere in the cstage output — no CALL free, no symbol reference
// (byte identity extends the proof to wwstage, as in the carrier).
//
// The carrier captured stdout+stderr MERGED (`> cs.s 2>&1`) and
// compared/grepped that one file; here the streams land separately
// and EACH is byte-compared and 'free'-grepped — at least as strict
// on both axes.
//
// Path discipline (the carrier's #8 note): no scratch path may carry
// the substring 'free' or the whole-output grep false-fails.
// testenv.fresh() is /tmp/<16 lowercase hex> — 'free' needs an 'r',
// which the hex alphabet cannot produce — and the source basename
// stays noopfr.ww, mirroring the carrier's naming.
//
// Dead data preserved as dead: the carrier's row table carried a
// `want` exit-code field its main() never read (no run leg existed);
// none is invented here. Runtime behavior lives in the r930_free_*
// wwfixtures per the retired carrier's header. Dropped machinery,
// not assertions: getpid()-keyed tmpdirs + rm/mkdir accounting
// (testenv.fresh/clean own scratch and assert cleanup).
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("freenoop 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;
};
// `drv noopfr.ww` with the asm on stdout — the carrier's exact shape
// (no -o), so the captured stdout IS the .s under comparison.
fn runstage(td: str, label: str, stage: str, drv: str,
out: *testenv.commandout) void = {
let av: []str = [];
append(av, drv);
append(av, "noopfr.ww");
testenv.runcommand(td, td, stage, av, tmo(), out);
let ok: bool = out.termination == exec.termination.EXIT
&& out.code == 0;
if (!ok) { fail(label, strings.concat(stage, " compile failed")); };
};
fn nooprow(label: str, src: str) void = {
let td: str = testenv.fresh();
testenv.writefile(strings.concat(td, "/noopfr.ww"),
strings.concat("package main;\n\n", src));
let cs: testenv.commandout;
runstage(td, label, "cstage", testenv.driver("w6c"), &cs);
let ws: testenv.commandout;
runstage(td, label, "wwstage", testenv.driver("w6c_ww"), &ws);
if (!testenv.same(cs.stdout, ws.stdout)) {
fail(label, "cs != ww .s");
};
if (!testenv.same(cs.stderr, ws.stderr)) {
fail(label, "cs != ww compile stderr");
};
if (testenv.has(cs.stdout, "free")
|| testenv.has(cs.stderr, "free")) {
fail(label, "'free' survives in the .s -- lowering is not a no-op");
};
testenv.clean(td);
};
// The five carrier rows: deref-after-free leak semantics, the N_DOT
// operand shape, operand side effects run per call, no stack damage
// across 1M no-op frees, and the verbatim Hare-port alloc/free
// round-trip (i64 sidesteps the filed narrow-pointee alloc(value)
// size divergence).
@test fn asmrows() void = {
nooprow("free_local_ptr_deref_after", strings.concat(
"export fn main() i32 = {\n",
" let x: i32 = 5;\n",
" let p: *i32 = &x;\n",
" free(p);\n",
" if (*p != 5) { return 1; };\n",
" return 0;\n",
"};\n"));
nooprow("free_struct_field", strings.concat(
"type holder = struct { p: *i32, n: i32 };\n",
"export fn main() i32 = {\n",
" let x: i32 = 3;\n",
" let h: holder = holder { p = &x, n = 4 };\n",
" free(h.p);\n",
" if (h.n != 4) { return 2; };\n",
" return 0;\n",
"};\n"));
nooprow("free_call_operand_effects", strings.concat(
"let g: i32 = 0;\n",
"fn bump(p: *i32) *i32 = {\n",
" g = g + 2;\n",
" return p;\n",
"};\n",
"export fn main() i32 = {\n",
" let x: i32 = 1;\n",
" free(bump(&x));\n",
" free(bump(&x));\n",
" if (g != 4) { return 3; };\n",
" return 0;\n",
"};\n"));
nooprow("free_loop_no_stack_damage", strings.concat(
"let g: i32 = 0;\n",
"fn bump(p: *i32) *i32 = {\n",
" g = g + 1;\n",
" return p;\n",
"};\n",
"export fn main() i32 = {\n",
" let x: i32 = 1;\n",
" let i: i32 = 0;\n",
" for (i < 1000000) {\n",
" free(bump(&x));\n",
" i += 1;\n",
" };\n",
" if (g != 1000000) { return 5; };\n",
" return 0;\n",
"};\n"));
nooprow("free_alloc_roundtrip", strings.concat(
"import rt;\n",
"export fn main() i32 = {\n",
" let p: *i64 = alloc(11i64)!;\n",
" free(p);\n",
" if (*p != 11i64) { return 4; };\n",
" return 0;\n",
"};\n"));
};

114
test/byteid/mangle_test.ww Normal file
View File

@@ -0,0 +1,114 @@
package mangle_test;
// M1 (#22) symbol-name proof. Port of the retired native carrier
// test/wcc/989_m1mangle_sym.c; every assertion preserved. Builds a
// fixture importing the real nested DIRECTORY package encoding.utf8
// to .s through BOTH driver stages and asserts the symbol table:
//
// needle | want | proves
// --------------------------+------+---------------------------------
// "encoding.utf8.runesz" | yes | nested pkg path-mangles (the M1
// | | DELTA: leaf utf8 -> path
// | | encoding.utf8)
// "TEXT main," | yes | root entry stays BARE (#32)
// "TEXT utf8.runesz," | no | pre-M1 leaf-only mangle is gone
//
// plus cstage.s == wwstage.s byte-for-byte (rule 10) over the
// re-baselined names.
//
// #93 sep layout: `build -S -o <stem>` splits the asm across
// <stem>.sepwork/<pkg>.s (root in __root.s, the import in
// encoding.utf8.s), so each stage's per-package .s are concatenated
// before the needle scans and the byte-id compare. Concat order is
// strengthened from the carrier's shell glob to explicit
// byte-lexicographic (the libbyteid_test move); an empty concat fails
// loudly exactly as the carrier's `cat <stem>.sepwork/*.s` did.
//
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
// (the Make target declares both drivers), getpid /tmp keying +
// rm accounting (testenv.fresh/clean), and the shell `timeout 180`
// wrapper (runcommand's 180s deadline carries it).
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("mangle 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 buildstage(td: str, stage: str, drv: str, stem: str) void = {
let av: []str = [];
append(av, drv);
append(av, "build");
append(av, "-S");
append(av, "-o");
append(av, stem);
append(av, "m1sym.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(stage, "build failed"); };
};
// Every per-package .s under `sepdir`, concatenated in byte-sorted
// order; "" when the directory is missing or holds no .s.
fn catasm(sepdir: str) str = {
if (!testenv.isdir(sepdir)) { return ""; };
let names: []str = testenv.listdir(sepdir);
let out: str = "";
let i: i32 = 0;
for (i < names.len) {
if (strings.hassuffix(names[i], ".s")) {
let body: str = testenv.readfile(
strings.concat(sepdir, "/", names[i]));
out = strings.concat(out, body);
};
i += 1;
};
return out;
};
fn checksyms(stage: str, s: str) void = {
if (!testenv.has(s, "encoding.utf8.runesz")) {
fail(stage,
"'encoding.utf8.runesz' missing -- nested pkg path-mangle absent");
};
if (!testenv.has(s, "TEXT main,")) {
fail(stage, "'TEXT main,' missing -- root entry no longer bare");
};
if (testenv.has(s, "TEXT utf8.runesz,")) {
fail(stage,
"'TEXT utf8.runesz,' present -- pre-M1 leaf-only mangle returned");
};
};
@test fn m1mangle() void = {
let td: str = testenv.fresh();
testenv.writefile(strings.concat(td, "/m1sym.ww"), strings.concat(
"package main;\n",
"import encoding.utf8;\n",
"export fn main() int = { return utf8.runesz('A'): int; };\n"));
let cstem: str = strings.concat(td, "/c_m1sym");
let wstem: str = strings.concat(td, "/w_m1sym");
buildstage(td, "cstage", testenv.driver("ww"), cstem);
buildstage(td, "wwstage", testenv.driver("ww_ww"), wstem);
let cs: str = catasm(strings.concat(cstem, ".sepwork"));
let ws: str = catasm(strings.concat(wstem, ".sepwork"));
if (cs.len == 0 || ws.len == 0) { fail("m1mangle", "empty .s concat"); };
checksyms("cstage", cs);
checksyms("wwstage", ws);
if (!testenv.same(cs, ws)) {
fail("m1mangle", "cstage.s != wwstage.s (byte-id break)");
};
testenv.clean(td);
};

View File

@@ -0,0 +1,240 @@
package structabi_test;
// Float-bearing struct-ABI byte-id + register-class gate. Port of the
// retired native carriers test/wcc/946_structparam_run.c (#165, the
// struct-PARAM SysV net) and test/wcc/946_structret_run.c (#171a, the
// struct-RETURN twin); every assertion preserved.
//
// For a pure internal ww call both ends ride the same (possibly
// wrong) registers, so runtime values AND cs==ww byte-id are
// necessary-not-sufficient: the genuine defect is SysV register-CLASS
// conformance, observable only in the emitted asm. Each row therefore
// compiles its inline source directly through w6c and w6c_ww
// (`-o out.s src.ww`), byte-ids the two .s (rule 10), and scans the
// cstage .s (byte-id makes the wwstage scan redundant, as in the C)
// for the SSE markers, PRESENT or ABSENT per row:
//
// param rows — callee receive marker "MOVSD\tX0, -" scoped to the
// TEXT main.use block. Scoping is load-bearing: main's own
// float-literal init also emits MOVSD X0, -off(BP), so a
// whole-file grep would not discriminate.
// ret rows — producer marker "(BP), X0" scoped to TEXT main.mk
// (its literal fills load via (SP), X0, never (BP)) plus consumer
// marker "MOVSD\tX0, -" scoped to TEXT main,; SEND and RECV
// presence must AGREE before comparing against the row polarity.
//
// The ABSENT rows prove the classification gate: pure-INT structs
// stay on the GP transport, and struct{f32,f32} (two f32 packing into
// ONE SSE eightbyte — deferred #165b/#171b) falls back to GP instead
// of wrongly riding two SSE regs.
//
// No runtime leg existed in the carriers (the sources embed value
// checks but were only ever compiled to .s; end-to-end values live in
// the r946_structparam_* / r946_structret_* fixtures) and none is
// invented here. Dropped machinery, not assertions: the hard
// w6c_ww-missing precondition (the Make target declares both tools; a
// missing driver still fails loudly in runcommand), getpid tmpdirs +
// rm/mkdir accounting (testenv.fresh/clean), and the 256KiB scan-
// buffer refusal (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("structabi 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")); };
};
// One function's TEXT block: [first `head` .. next "\nTEXT " after
// it), to EOF when it is the last block. A missing head is a scan
// error, never a silent ABSENT verdict.
fn textblock(label: str, s: str, head: str) str = {
let hp: i32 = testenv.pos(s, head);
if (hp < 0) {
fail(label, strings.concat("cannot scan .s: no ", head));
};
let tail: str = strings.sub(s, hp, s.len);
let rest: str = strings.sub(tail, 1, tail.len);
let ep: i32 = testenv.pos(rest, "\nTEXT ");
if (ep < 0) { return tail; };
return strings.sub(tail, 0, ep + 1);
};
// Compile both stages and gate rule-10 byte identity; returns the
// cstage .s body for the marker scan.
fn buildpair(label: str, src: str, td: str) str = {
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 (!testenv.same(cs, ws)) {
fail(label, "cstage/wwstage .s DIFFER (rule-10 byte-id violation)");
};
return cs;
};
fn paramrow(label: str, src: str, wantsse: i32) void = {
let td: str = testenv.fresh();
let cs: str = buildpair(label, src, td);
let callee: str = textblock(label, cs, "TEXT main.use");
let sse: i32 = 0;
if (testenv.has(callee, "MOVSD\tX0, -")) { sse = 1; };
if (sse != wantsse) {
fail(label,
"callee SSE receive polarity wrong (register-class discriminator)");
};
testenv.clean(td);
};
fn retrow(label: str, src: str, wantsse: i32) void = {
let td: str = testenv.fresh();
let cs: str = buildpair(label, src, td);
let mk: str = textblock(label, cs, "TEXT main.mk");
let mn: str = textblock(label, cs, "TEXT main,");
let send: i32 = 0;
if (testenv.has(mk, "(BP), X0")) { send = 1; };
let recv: i32 = 0;
if (testenv.has(mn, "MOVSD\tX0, -")) { recv = 1; };
if (send != recv) {
fail(label, "SEND and RECV SSE markers disagree");
};
if (send != wantsse) {
fail(label,
"struct return SSE polarity wrong (register-class discriminator)");
};
testenv.clean(td);
};
// #165 param rows: lone-f64 eightbytes ride X0/X1 on independent
// cursors; pure-INT and the f32f32 fallback stay GP-routed.
@test fn structparam() void = {
paramrow("f64f64_arg", strings.concat(
"package main;\n",
"type pff = struct { a: f64, b: f64 };\n",
"fn use(s: pff) f64 = { return s.a + s.b; };\n",
"export fn main() i32 = {\n",
"\tlet s: pff = pff { a = 3.0, b = 5.0 };\n",
"\tif (use(s) != 8.0) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
paramrow("f64i64_arg", strings.concat(
"package main;\n",
"type pfi = struct { a: f64, b: i64 };\n",
"fn use(s: pfi) i64 = { return (s.a: i64) + s.b; };\n",
"export fn main() i32 = {\n",
"\tlet s: pfi = pfi { a = 3.0, b = 5 };\n",
"\tif (use(s) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
paramrow("i64f64_arg", strings.concat(
"package main;\n",
"type pif = struct { a: i64, b: f64 };\n",
"fn use(s: pif) i64 = { return s.a + (s.b: i64); };\n",
"export fn main() i32 = {\n",
"\tlet s: pif = pif { a = 3, b = 5.0 };\n",
"\tif (use(s) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
paramrow("i64i64_arg", strings.concat(
"package main;\n",
"type pii = struct { a: i64, b: i64 };\n",
"fn use(s: pii) i64 = { return s.a + s.b; };\n",
"export fn main() i32 = {\n",
"\tlet s: pii = pii { a = 3, b = 5 };\n",
"\tif (use(s) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 0);
paramrow("f32f32_arg_fallback", strings.concat(
"package main;\n",
"type pf32 = struct { a: f32, b: f32 };\n",
"fn use(s: pf32) i64 = { return (s.a: i64) + (s.b: i64); };\n",
"export fn main() i32 = {\n",
"\tlet s: pf32 = pf32 { a = 3.0f32, b = 5.0f32 };\n",
"\tif (use(s) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 0);
paramrow("scalar_plus_f64f64", strings.concat(
"package main;\n",
"type pff = struct { a: f64, b: f64 };\n",
"fn use(n: i64, s: pff) i64 = {\n",
"\treturn n + (s.a: i64) + (s.b: i64);\n",
"};\n",
"export fn main() i32 = {\n",
"\tlet s: pff = pff { a = 3.0, b = 5.0 };\n",
"\tif (use(2, s) != 10) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
};
// #171a return rows: the return twin — lone-f64 eightbytes ride the
// SSE return cursor, GP cursor independent (f64i32's i32 rides AX).
@test fn structret() void = {
retrow("f64f64_ret", strings.concat(
"package main;\n",
"type pff = struct { a: f64, b: f64 };\n",
"fn mk() pff = { return pff { a = 3.0, b = 5.0 }; };\n",
"export fn main() i32 = {\n",
"\tlet s: pff = mk();\n",
"\tif ((s.a: i64) + (s.b: i64) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
retrow("f64i32_ret", strings.concat(
"package main;\n",
"type pfi = struct { a: f64, b: i32 };\n",
"fn mk() pfi = { return pfi { a = 3.0, b = 5 }; };\n",
"export fn main() i32 = {\n",
"\tlet s: pfi = mk();\n",
"\tif ((s.a: i64) + (s.b: i64) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
retrow("i64f64_ret", strings.concat(
"package main;\n",
"type pif = struct { a: i64, b: f64 };\n",
"fn mk() pif = { return pif { a = 3, b = 5.0 }; };\n",
"export fn main() i32 = {\n",
"\tlet s: pif = mk();\n",
"\tif (s.a + (s.b: i64) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 1);
retrow("i64i64_ret", strings.concat(
"package main;\n",
"type pii = struct { a: i64, b: i64 };\n",
"fn mk() pii = { return pii { a = 3, b = 5 }; };\n",
"export fn main() i32 = {\n",
"\tlet s: pii = mk();\n",
"\tif (s.a + s.b != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 0);
retrow("f32f32_ret_fallback", strings.concat(
"package main;\n",
"type pf32 = struct { a: f32, b: f32 };\n",
"fn mk() pf32 = { return pf32 { a = 3.0f32, b = 5.0f32 }; };\n",
"export fn main() i32 = {\n",
"\tlet s: pf32 = mk();\n",
"\tif ((s.a: i64) + (s.b: i64) != 8) { return 1; };\n",
"\treturn 0;\n",
"};\n"), 0);
};