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