test: port the call-arg asm observers to ww
720/723/727/743 -> test/asm/callarg_test.ww. Ordered PUSHQ windows, tag-synth + stack-overflow-cleanup needles and the variadic eightbyte store-count floors preserved. Strengthened: 720's 4-variant row gains the byte-id leg the C skipped behind the then-open #22 zero-init gate — #22 landed (724 pins the fix), and the leg verifies green on both stages.
This commit is contained in:
331
test/asm/callarg_test.ww
Normal file
331
test/asm/callarg_test.ww
Normal file
@@ -0,0 +1,331 @@
|
||||
package callarg_test;
|
||||
|
||||
// Direct-w6c asm-window gate over caller-side argument lowering.
|
||||
// Port of the retired native carriers test/wcc/720_tagged_call_arg.c,
|
||||
// 723_composite_call_arg.c, 727_modcall_widen_slice.c and
|
||||
// 743_variadic_pack.c; every assertion preserved. Runtime rows are
|
||||
// owned elsewhere (test/lang/{tagged_call_arg,composite_call_arg}
|
||||
// _test.ww, lib/strings concat vectors) — the ordered-push windows,
|
||||
// tag-synth/cleanup needles and store-count floors here are the
|
||||
// unowned remainder.
|
||||
//
|
||||
// 720 (#21): after EVERY CALL main.yield_* site a PUSHQ DX must land
|
||||
// before the next CALL and precede the following PUSHQ AX (payload
|
||||
// before tag, natural-push order). The C skipped the 4-variant row's
|
||||
// byte-id leg behind the then-open #22 !void zero-init gate; #22
|
||||
// landed (724's cmp is its fix pin), so that leg is ENABLED here — a
|
||||
// strengthening over the retired carrier.
|
||||
//
|
||||
// 723 (#24): between CALL main.view and the next CALL all three
|
||||
// slice-composite pushes must appear in strict CX < BX < AX order.
|
||||
//
|
||||
// 727 (#28): before CALL needle.want the widened slice arg's
|
||||
// tag-synth `MOVQ $1, AX` + `PUSHQ AX` pair must appear inside TEXT
|
||||
// main, and the 7-arg-word stack-overflow cleanup `ADDQ $8, SP` must
|
||||
// follow the CALL before the next TEXT.
|
||||
//
|
||||
// 743 (STATUS-3 #16): the variadic gather in [TEXT main, .. next
|
||||
// TEXT) must store >= 3 ptr eightbytes (MOVQ AX, -) AND >= 3 len
|
||||
// eightbytes (MOVQ BX, - — the bug pin; pre-fix cstage stored none).
|
||||
//
|
||||
// Dropped C machinery, not assertions: w6c_ww-absent skip gates,
|
||||
// getpid()-keyed /tmp names, slurp caps. The `package main;` source
|
||||
// prefix reproduces wwtest_fputs where the carrier injected it.
|
||||
|
||||
import os;
|
||||
import os.exec;
|
||||
import strings;
|
||||
import testenv;
|
||||
import time;
|
||||
|
||||
fn fail(label: str, why: str) void = {
|
||||
let m: str = strings.concat("callarg 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 occurrence of `needle` at or after `start`, absolute; -1 when
|
||||
// missing (the carriers' find_after).
|
||||
fn posafter(s: str, start: i32, needle: str) i32 = {
|
||||
let p: i32 = testenv.pos(strings.sub(s, start, s.len), needle);
|
||||
if (p < 0) { return -1; };
|
||||
return start + p;
|
||||
};
|
||||
|
||||
// 720: ordered-offset window per yield_ CALL site.
|
||||
fn pushdxcheck(label: str, stage: str, s: str) void = {
|
||||
let off: i32 = 0;
|
||||
let saw: bool = false;
|
||||
for (off < s.len) {
|
||||
let call: i32 = posafter(s, off, "CALL\tmain.yield_");
|
||||
if (call < 0) { break; };
|
||||
saw = true;
|
||||
let pdx: i32 = posafter(s, call, "PUSHQ\tDX");
|
||||
let pax: i32 = posafter(s, call, "PUSHQ\tAX");
|
||||
let nextcall: i32 = posafter(s, call + 1, "CALL\t");
|
||||
if (pdx < 0 || (nextcall >= 0 && pdx > nextcall)) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no PUSHQ DX between CALL yield_ and next CALL"));
|
||||
};
|
||||
if (pax < 0 || pdx > pax) {
|
||||
fail(label, strings.concat(stage,
|
||||
": PUSHQ DX does not precede PUSHQ AX after CALL ",
|
||||
"yield_"));
|
||||
};
|
||||
off = call + 1;
|
||||
};
|
||||
if (!saw) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no CALL yield_ site found"));
|
||||
};
|
||||
};
|
||||
|
||||
fn taggedrow(label: str, src: 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"));
|
||||
pushdxcheck(label, "cstage", cs);
|
||||
pushdxcheck(label, "wwstage", ws);
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
@test fn taggedcallarg() void = {
|
||||
taggedrow("4variant_call_source", strings.concat(
|
||||
"package main;\n",
|
||||
"type done = !void; type more = !void; type invalid = !void;\n",
|
||||
"fn yield_rune() (rune | done | more | invalid) = {\n",
|
||||
" return 65u32: rune;\n",
|
||||
"};\n",
|
||||
"fn dispatch(v: (rune | done | more | invalid)) i32 = {\n",
|
||||
" match (v) {\n",
|
||||
" case let r: rune => return r: i32;\n",
|
||||
" case let d: done => return -1;\n",
|
||||
" case let m: more => return -2;\n",
|
||||
" case let e: invalid => return -3;\n",
|
||||
" };\n",
|
||||
" return -99;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" if (dispatch(yield_rune()) != 65) { return 11; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"));
|
||||
taggedrow("2variant_call_source_ptr", strings.concat(
|
||||
"package main;\n",
|
||||
"type oserror = !i32;\n",
|
||||
"fn yield_ptr() (*u8 | oserror) = {\n",
|
||||
" let p: *u8 = nil;\n",
|
||||
" return p;\n",
|
||||
"};\n",
|
||||
"fn dispatch(v: (*u8 | oserror)) i32 = {\n",
|
||||
" match (v) {\n",
|
||||
" case let p: *u8 => return 7;\n",
|
||||
" case let e: oserror => return e: i32;\n",
|
||||
" };\n",
|
||||
" return -99;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" if (dispatch(yield_ptr()) != 7) { return 11; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"));
|
||||
};
|
||||
|
||||
// 723: the three slice-composite pushes between CALL view and the
|
||||
// next CALL, high -> low.
|
||||
fn threepushcheck(label: str, stage: str, s: str) void = {
|
||||
let call: i32 = posafter(s, 0, "CALL\tmain.view");
|
||||
if (call < 0) {
|
||||
fail(label, strings.concat(stage, ": no CALL view site"));
|
||||
};
|
||||
let nextcall: i32 = posafter(s, call + 1, "CALL\t");
|
||||
if (nextcall < 0) {
|
||||
fail(label, strings.concat(stage, ": no follow-up CALL"));
|
||||
};
|
||||
let pcx: i32 = posafter(s, call, "PUSHQ\tCX");
|
||||
let pbx: i32 = posafter(s, call, "PUSHQ\tBX");
|
||||
let pax: i32 = posafter(s, call, "PUSHQ\tAX");
|
||||
if (pcx < 0 || pcx > nextcall) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no PUSHQ CX between CALL view and next CALL"));
|
||||
};
|
||||
if (pbx < 0 || pbx > nextcall) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no PUSHQ BX between CALL view and next CALL"));
|
||||
};
|
||||
if (pax < 0 || pax > nextcall) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no PUSHQ AX between CALL view and next CALL"));
|
||||
};
|
||||
if (!(pcx < pbx && pbx < pax)) {
|
||||
fail(label, strings.concat(stage,
|
||||
": PUSHQ order != CX,BX,AX"));
|
||||
};
|
||||
};
|
||||
|
||||
@test fn compositecallarg() void = {
|
||||
let label: str = "slice_call_into_2slice_arg";
|
||||
let td: str = testenv.fresh();
|
||||
testenv.writefile(strings.concat(td, "/src.ww"), strings.concat(
|
||||
"package main;\n",
|
||||
"fn view(s: str) []u8 = {\n",
|
||||
" let r: []u8;\n",
|
||||
" r.ptr = s.ptr;\n",
|
||||
" r.len = s.len;\n",
|
||||
" r.cap = s.len;\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"fn check(a: []u8, b: []u8) bool = {\n",
|
||||
" if (a.len != b.len) { return false; };\n",
|
||||
" return true;\n",
|
||||
"};\n",
|
||||
"export fn caller(in: str, p: []u8) bool = {\n",
|
||||
" return check(view(in), p);\n",
|
||||
"};\n"));
|
||||
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"));
|
||||
threepushcheck(label, "cstage", cs);
|
||||
threepushcheck(label, "wwstage", ws);
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
// 727: tag-synth pair before the CALL ($1 = the slice-variant tag in
|
||||
// (u8 | []u8)) and the stack-overflow cleanup after it.
|
||||
fn tagpushcheck(label: str, stage: str, s: str) void = {
|
||||
let fnp: i32 = testenv.pos(s, "TEXT main");
|
||||
if (fnp < 0) {
|
||||
fail(label, strings.concat(stage, ": no TEXT main in .s"));
|
||||
};
|
||||
let call: i32 = posafter(s, fnp, "CALL\tneedle.want");
|
||||
if (call < 0) {
|
||||
fail(label, strings.concat(stage, ": no CALL needle.want"));
|
||||
};
|
||||
let found: bool = false;
|
||||
let p: i32 = fnp;
|
||||
for (p < call) {
|
||||
let m: i32 = posafter(s, p, "MOVQ\t$1, AX");
|
||||
if (m < 0 || m >= call) { break; };
|
||||
let nx: i32 = posafter(s, m, "PUSHQ\tAX");
|
||||
if (nx >= 0 && nx < call) { found = true; break; };
|
||||
p = m + 1;
|
||||
};
|
||||
if (!found) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no tag-synth `MOVQ $1, AX; PUSHQ AX` before CALL ",
|
||||
"needle.want"));
|
||||
};
|
||||
let cleanup: i32 = posafter(s, call, "ADDQ\t$8, SP");
|
||||
let nexttext: i32 = posafter(s, call, "TEXT ");
|
||||
if (cleanup < 0 || (nexttext >= 0 && cleanup > nexttext)) {
|
||||
fail(label, strings.concat(stage,
|
||||
": no `ADDQ $8, SP` overflow cleanup after CALL ",
|
||||
"needle.want"));
|
||||
};
|
||||
};
|
||||
|
||||
@test fn modcallwidenslice() void = {
|
||||
let label: str = "dot_callee_slice_widen";
|
||||
let td: str = testenv.fresh();
|
||||
testenv.writefile(strings.concat(td, "/src.ww"), strings.concat(
|
||||
"package needle;\n",
|
||||
"export fn want(haystack: []u8, needle: (u8 | []u8)) i32 = {\n",
|
||||
" let r: i32 = haystack.len;\n",
|
||||
" match (needle) {\n",
|
||||
" case let b: u8 => r = r + (b: i32);\n",
|
||||
" case let s: []u8 => r = r + s.len;\n",
|
||||
" };\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"package caller;\n",
|
||||
"import needle;\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let h: []u8;\n",
|
||||
" let n: []u8 = h;\n",
|
||||
" return needle.want(h, n);\n",
|
||||
"};\n"));
|
||||
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"));
|
||||
tagpushcheck(label, "cstage", cs);
|
||||
tagpushcheck(label, "wwstage", ws);
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
// 743: eightbyte store-count floors over the caller gather window.
|
||||
fn storescheck(label: str, stage: str, s: str) void = {
|
||||
let body: i32 = testenv.pos(s, "TEXT main,");
|
||||
if (body < 0) {
|
||||
fail(label, strings.concat(stage, ": no TEXT main label"));
|
||||
};
|
||||
let end: i32 = posafter(s, body, "\nTEXT ");
|
||||
if (end < 0) { end = s.len; };
|
||||
let w: str = strings.sub(s, body, end);
|
||||
if (testenv.occurrences(w, "MOVQ\tBX, -") < 3) {
|
||||
fail(label, strings.concat(stage,
|
||||
": fewer than 3 len-stores (MOVQ BX, -K(BP)) in main"));
|
||||
};
|
||||
if (testenv.occurrences(w, "MOVQ\tAX, -") < 3) {
|
||||
fail(label, strings.concat(stage,
|
||||
": fewer than 3 ptr-stores in main"));
|
||||
};
|
||||
};
|
||||
|
||||
@test fn variadicpack() void = {
|
||||
let label: str = "variadic_pack";
|
||||
let td: str = testenv.fresh();
|
||||
testenv.writefile(strings.concat(td, "/src.ww"), strings.concat(
|
||||
"package main;\n",
|
||||
"fn sumlen(parts: str...) i32 = {\n",
|
||||
" let z: i32 = 0;\n",
|
||||
" let i: i32 = 0;\n",
|
||||
" for (i < parts.len) {\n",
|
||||
" z += parts[i].len;\n",
|
||||
" i += 1;\n",
|
||||
" };\n",
|
||||
" return z;\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" return sumlen(\"a\", \"bb\", \"ccc\");\n",
|
||||
"};\n"));
|
||||
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"));
|
||||
storescheck(label, "cstage", cs);
|
||||
storescheck(label, "wwstage", ws);
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
Reference in New Issue
Block a user