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