Files
ww/test/byteid/asmwindow_test.ww
Hojun-Cho 81e7f95548 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.
2026-08-08 04:41:42 +09:00

365 lines
13 KiB
Plaintext

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");
};