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.
365 lines
13 KiB
Plaintext
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");
|
|
};
|