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.
332 lines
11 KiB
Plaintext
332 lines
11 KiB
Plaintext
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);
|
|
};
|