Files
ww/test/asm/callarg_test.ww
Hojun-Cho 6a871cf541 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.
2026-08-08 14:45:02 +09:00

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