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:
364
test/byteid/asmwindow_test.ww
Normal file
364
test/byteid/asmwindow_test.ww
Normal 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");
|
||||
};
|
||||
144
test/byteid/freenoop_test.ww
Normal file
144
test/byteid/freenoop_test.ww
Normal 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
114
test/byteid/mangle_test.ww
Normal 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);
|
||||
};
|
||||
240
test/byteid/structabi_test.ww
Normal file
240
test/byteid/structabi_test.ww
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user