test: port the sret/struct-return asm observers to ww
718/721/730 -> test/asm/sret_test.ww. Frame-size and max--K(BP) stomp sentinels, line-adjacency sret triangle (hidden-RDI, return- the-pointer, no-AX-capture, forwarding reload) and narrow-trailing- field windows preserved, including 718's #15 byte-id gate on the three-return row. 718's whole-suite ww_ww-executable gate dropped with the other skip gates (the Make target declares the tools).
This commit is contained in:
557
test/asm/sret_test.ww
Normal file
557
test/asm/sret_test.ww
Normal file
@@ -0,0 +1,557 @@
|
||||
package sret_test;
|
||||
|
||||
// Direct-w6c asm-window gate over struct-return lowering. Port of the
|
||||
// retired native carriers test/wcc/718_struct_multi_return_scratch.c,
|
||||
// 721_sret_struct_return.c and 730_sret_narrow_field.c; every
|
||||
// assertion preserved. Runtime rows are owned elsewhere
|
||||
// (test/wcc/data/r718_struct_return_*, test/lang/
|
||||
// sret_struct_return_test.ww, test/lang/sret_narrow_field_test.ww) —
|
||||
// the asm windows here are the unowned remainder byte-id cannot
|
||||
// subsume by design (both stages could stomp or widen identically).
|
||||
//
|
||||
// 718 (#14 single-slot @retscr): frame value parsed off the first
|
||||
// `TEXT f,$N` line must be 64 and no `-K(BP)` operand anywhere in the
|
||||
// .s may exceed 64 (the below-SP stomp sentinel). Row
|
||||
// three_returns_16B carries no byte-id leg — pre-existing #15
|
||||
// label-counter skew for nested-if shapes, exactly the C's gate; the
|
||||
// tagged row pins byte-id only (@tagscr family, frame unchecked).
|
||||
// The C additionally gated ALL rows on an executable ww_ww — a skip
|
||||
// gate, dropped like the w6c_ww ones (the Make target declares the
|
||||
// tools it launches).
|
||||
//
|
||||
// 721 (#23 sret ABI + #9 forwarding): line-adjacency sentinels — the
|
||||
// line before CALL main.mk(SB) must LEAQ the hidden dest into DI, one
|
||||
// of the last 8 lines of mk before RET must reload @sretarg into AX,
|
||||
// no MOVQ AX,(BP) capture within 3 lines after the CALL, and fwd rows
|
||||
// reload (not LEAQ) DI before CALL main.inner(SB).
|
||||
//
|
||||
// 730 (#33 narrow trailing field): the field copy at offset 32 in mk
|
||||
// must use the row's narrow MOV on both the (BP) load and (BX) store
|
||||
// sides, and the pre-fix 8-byte `MOVQ AX, 32(BX)` must be absent. The
|
||||
// trailing-field offset 32 is structurally fixed by the shared
|
||||
// `{ a: i32, s: []u8, r: NARROW }` row shape.
|
||||
//
|
||||
// The `package main;` source prefix reproduces the carriers'
|
||||
// wwtest_fputs injection.
|
||||
|
||||
import os;
|
||||
import os.exec;
|
||||
import strings;
|
||||
import testenv;
|
||||
import time;
|
||||
|
||||
fn fail(label: str, why: str) void = {
|
||||
let m: str = strings.concat("sret 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")); };
|
||||
};
|
||||
|
||||
// End index of the line holding `i`, INCLUDING its newline (fgets
|
||||
// framing, so `\tRET\n` needles only hit whole RET lines).
|
||||
fn lineend(s: str, i: i32) i32 = {
|
||||
let rest: str = strings.sub(s, i, s.len);
|
||||
let e: i32 = testenv.pos(rest, "\n");
|
||||
if (e < 0) { return s.len; };
|
||||
return i + e + 1;
|
||||
};
|
||||
|
||||
fn digits(s: str, i: i32) i32 = {
|
||||
let j: i32 = i;
|
||||
let k: i32 = 0;
|
||||
for (j < s.len) {
|
||||
let c: u8 = s[j];
|
||||
if (c < '0' || c > '9') { break; };
|
||||
k = k * 10 + ((c - '0'): i32);
|
||||
j += 1;
|
||||
};
|
||||
return k;
|
||||
};
|
||||
|
||||
// $N off the first `TEXT f,$N` line (718 read_frame).
|
||||
fn readframe(label: str, stage: str, s: str) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let le: i32 = lineend(s, i);
|
||||
let line: str = strings.sub(s, i, le);
|
||||
if (strings.hasprefix(line, "TEXT ")) {
|
||||
let dp: i32 = testenv.pos(line, "$");
|
||||
if (dp >= 0) { return digits(line, dp + 1); };
|
||||
};
|
||||
i = le;
|
||||
};
|
||||
fail(label, strings.concat(stage, ": no TEXT frame line in .s"));
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Largest K over every `-K(BP)` operand in the .s (718
|
||||
// read_max_neg_off): the below-SP stomp sentinel.
|
||||
fn maxnegoff(s: str) i32 = {
|
||||
let maxk: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let rest: str = strings.sub(s, i, s.len);
|
||||
let p: i32 = testenv.pos(rest, "(BP)");
|
||||
if (p < 0) { break; };
|
||||
let ap: i32 = i + p;
|
||||
let q: i32 = ap;
|
||||
for (q > 0) {
|
||||
let c: u8 = s[q - 1];
|
||||
if (c < '0' || c > '9') { break; };
|
||||
q -= 1;
|
||||
};
|
||||
if (q > 0 && s[q - 1] == '-') {
|
||||
let k: i32 = digits(s, q);
|
||||
if (k > maxk) { maxk = k; };
|
||||
};
|
||||
i = ap + 4;
|
||||
};
|
||||
return maxk;
|
||||
};
|
||||
|
||||
// One 718 row: byte-id (when not #15-gated), frame == want on both
|
||||
// stages, deepest -K(BP) <= bound on both stages.
|
||||
fn framerow(label: str, src: str, frame: i32, maxoff: i32,
|
||||
byteid: bool) 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"));
|
||||
if (byteid) {
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
};
|
||||
if (frame > 0) {
|
||||
if (readframe(label, "cstage", cs) != frame
|
||||
|| readframe(label, "wwstage", ws) != frame) {
|
||||
fail(label, "frame size mismatch (single-slot dedup)");
|
||||
};
|
||||
};
|
||||
if (maxoff > 0) {
|
||||
if (maxnegoff(cs) > maxoff || maxnegoff(ws) > maxoff) {
|
||||
fail(label, "stomp regression: -K(BP) deeper than frame");
|
||||
};
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
// 718 rows 1-3: frame = i/x/k args + r(16) + retscr(24) = $64; row 3
|
||||
// pins that the one-return base case still allocates retscr once.
|
||||
// Row 4 pins the orthogonal @tagscr family byte-id only.
|
||||
@test fn retscratch() void = {
|
||||
framerow("two_returns_16B", strings.concat(
|
||||
"package main;\n",
|
||||
"type inst = struct { sec: i64, nsec: i64 };\n",
|
||||
"fn f(i: inst, x: i64) inst = {\n",
|
||||
" let r: inst;\n",
|
||||
" if (x > 0i64) {\n",
|
||||
" r.sec = i.sec + x;\n",
|
||||
" r.nsec = i.nsec + x;\n",
|
||||
" return r;\n",
|
||||
" };\n",
|
||||
" r.sec = i.sec - x;\n",
|
||||
" r.nsec = i.nsec - x;\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let i: inst = inst { sec = 10i64, nsec = 20i64 };\n",
|
||||
" let p: inst = f(i, 5i64);\n",
|
||||
" if (p.sec != 15i64) { return 1; };\n",
|
||||
" if (p.nsec != 25i64) { return 2; };\n",
|
||||
" let q: inst = f(i, -3i64);\n",
|
||||
" if (q.sec != 13i64) { return 3; };\n",
|
||||
" if (q.nsec != 23i64) { return 4; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), 64, 64, true);
|
||||
// Byte-id disabled: pre-existing #15 label-counter skew between
|
||||
// stages for nested-if shapes; frame+stomp asserts still pin #14.
|
||||
framerow("three_returns_16B", strings.concat(
|
||||
"package main;\n",
|
||||
"type inst = struct { sec: i64, nsec: i64 };\n",
|
||||
"fn f(i: inst, k: i32) inst = {\n",
|
||||
" let r: inst;\n",
|
||||
" if (k == 1i32) {\n",
|
||||
" r.sec = i.sec + 1i64;\n",
|
||||
" r.nsec = i.nsec + 1i64;\n",
|
||||
" return r;\n",
|
||||
" };\n",
|
||||
" if (k == 2i32) {\n",
|
||||
" r.sec = i.sec * 2i64;\n",
|
||||
" r.nsec = i.nsec * 2i64;\n",
|
||||
" return r;\n",
|
||||
" };\n",
|
||||
" r.sec = i.sec;\n",
|
||||
" r.nsec = i.nsec;\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let i: inst = inst { sec = 10i64, nsec = 20i64 };\n",
|
||||
" let a: inst = f(i, 1i32);\n",
|
||||
" if (a.sec != 11i64) { return 1; };\n",
|
||||
" if (a.nsec != 21i64) { return 2; };\n",
|
||||
" let b: inst = f(i, 2i32);\n",
|
||||
" if (b.sec != 20i64) { return 3; };\n",
|
||||
" if (b.nsec != 40i64) { return 4; };\n",
|
||||
" let c: inst = f(i, 9i32);\n",
|
||||
" if (c.sec != 10i64) { return 5; };\n",
|
||||
" if (c.nsec != 20i64) { return 6; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), 64, 64, false);
|
||||
framerow("single_return_16B", strings.concat(
|
||||
"package main;\n",
|
||||
"type inst = struct { sec: i64, nsec: i64 };\n",
|
||||
"fn f(i: inst) inst = {\n",
|
||||
" let r: inst;\n",
|
||||
" r.sec = i.sec + 1i64;\n",
|
||||
" r.nsec = i.nsec + 1i64;\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let i: inst = inst { sec = 100i64, nsec = 200i64 };\n",
|
||||
" let r: inst = f(i);\n",
|
||||
" if (r.sec != 101i64) { return 1; };\n",
|
||||
" if (r.nsec != 201i64) { return 2; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), 64, 64, true);
|
||||
framerow("tagged_multi_return", strings.concat(
|
||||
"package main;\n",
|
||||
"fn f(k: i32) (i64 | i32) = {\n",
|
||||
" if (k > 0i32) { return 1i64; };\n",
|
||||
" return 2i32;\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let r: (i64 | i32) = f(5i32);\n",
|
||||
" match (r) {\n",
|
||||
" case let v: i64 => if (v != 1i64) { return 1; };\n",
|
||||
" case let v: i32 => return 2;\n",
|
||||
" };\n",
|
||||
" let s: (i64 | i32) = f(-1i32);\n",
|
||||
" match (s) {\n",
|
||||
" case let v: i64 => return 3;\n",
|
||||
" case let v: i32 => if (v != 2i32) { return 4; };\n",
|
||||
" };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), 0, 0, true);
|
||||
};
|
||||
|
||||
// 721 (a): the line immediately before CALL main.mk(SB) passes the
|
||||
// hidden sret dest pointer in RDI.
|
||||
fn leaqdicheck(label: str, stage: str, s: str) void = {
|
||||
let prev: str = "";
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let le: i32 = lineend(s, i);
|
||||
let line: str = strings.sub(s, i, le);
|
||||
if (testenv.has(line, "CALL\tmain.mk(SB)")) {
|
||||
if (testenv.has(prev, "LEAQ\t")
|
||||
&& testenv.has(prev, "(BP), DI")) {
|
||||
return;
|
||||
};
|
||||
break;
|
||||
};
|
||||
prev = line;
|
||||
i = le;
|
||||
};
|
||||
fail(label, strings.concat(stage,
|
||||
": LEAQ -K(BP), DI before CALL mk(SB) missing"));
|
||||
};
|
||||
|
||||
// 721 (b): the @sretarg reload (SysV return-the-pointer) must sit in
|
||||
// the last 8 lines of mk before its first RET.
|
||||
fn sretretloadcheck(label: str, stage: str, s: str) void = {
|
||||
let tp: i32 = testenv.pos(s, "TEXT main.mk,");
|
||||
if (tp < 0) {
|
||||
fail(label, strings.concat(stage, ": no TEXT main.mk in .s"));
|
||||
};
|
||||
let win: []str = alloc([], 8u64)!;
|
||||
win.len = 8;
|
||||
let k: i32 = 0;
|
||||
for (k < 8) { win[k] = ""; k += 1; };
|
||||
let wi: i32 = 0;
|
||||
let i: i32 = lineend(s, tp);
|
||||
for (i < s.len) {
|
||||
let le: i32 = lineend(s, i);
|
||||
let line: str = strings.sub(s, i, le);
|
||||
if (testenv.has(line, "\tRET\n")) {
|
||||
k = 0;
|
||||
for (k < 8) {
|
||||
if (testenv.has(win[k], "MOVQ\t")
|
||||
&& testenv.has(win[k], "(BP), AX")) {
|
||||
return;
|
||||
};
|
||||
k += 1;
|
||||
};
|
||||
break;
|
||||
};
|
||||
win[wi] = line;
|
||||
wi += 1;
|
||||
if (wi == 8) { wi = 0; };
|
||||
i = le;
|
||||
};
|
||||
fail(label, strings.concat(stage,
|
||||
": MOVQ -K(BP), AX before RET in mk missing"));
|
||||
};
|
||||
|
||||
// 721 (c) NEGATIVE: no MOVQ AX, -K(BP) capture within 3 lines after
|
||||
// the CALL — the pre-#23 wwstage truncation pattern. A missing CALL
|
||||
// passes here exactly as in the C; (a) already fails on it.
|
||||
fn noaxcapturecheck(label: str, stage: str, s: str) void = {
|
||||
let cp: i32 = testenv.pos(s, "CALL\tmain.mk(SB)");
|
||||
if (cp < 0) { return; };
|
||||
let i: i32 = lineend(s, cp);
|
||||
let peek: i32 = 0;
|
||||
for (i < s.len && peek < 3) {
|
||||
let le: i32 = lineend(s, i);
|
||||
let line: str = strings.sub(s, i, le);
|
||||
if (testenv.has(line, "MOVQ\tAX,")
|
||||
&& testenv.has(line, "(BP)")) {
|
||||
fail(label, strings.concat(stage,
|
||||
": MOVQ AX, -K(BP) after CALL mk(SB) -- ",
|
||||
"pre-#23 truncation pattern"));
|
||||
};
|
||||
peek += 1;
|
||||
i = le;
|
||||
};
|
||||
};
|
||||
|
||||
// 721 (d), fwd rows only: mk reloads its own @sretarg (MOVQ, not
|
||||
// LEAQ of a local dest) into DI before forwarding to inner.
|
||||
fn fwdreloadcheck(label: str, stage: str, s: str) void = {
|
||||
let tp: i32 = testenv.pos(s, "TEXT main.mk,");
|
||||
if (tp < 0) {
|
||||
fail(label, strings.concat(stage, ": no TEXT main.mk in .s"));
|
||||
};
|
||||
let prev: str = strings.sub(s, tp, lineend(s, tp));
|
||||
let i: i32 = lineend(s, tp);
|
||||
for (i < s.len) {
|
||||
let le: i32 = lineend(s, i);
|
||||
let line: str = strings.sub(s, i, le);
|
||||
if (testenv.has(line, "CALL\tmain.inner(SB)")) {
|
||||
if (testenv.has(prev, "MOVQ\t")
|
||||
&& testenv.has(prev, "(BP), DI")
|
||||
&& !testenv.has(prev, "LEAQ")) {
|
||||
return;
|
||||
};
|
||||
break;
|
||||
};
|
||||
prev = line;
|
||||
i = le;
|
||||
};
|
||||
fail(label, strings.concat(stage,
|
||||
": MOVQ -K(BP), DI (sret-forward) before CALL inner(SB) ",
|
||||
"missing"));
|
||||
};
|
||||
|
||||
fn sretrow(label: str, src: str, fwd: bool) 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"));
|
||||
leaqdicheck(label, "cstage", cs);
|
||||
sretretloadcheck(label, "cstage", cs);
|
||||
noaxcapturecheck(label, "cstage", cs);
|
||||
leaqdicheck(label, "wwstage", ws);
|
||||
sretretloadcheck(label, "wwstage", ws);
|
||||
noaxcapturecheck(label, "wwstage", ws);
|
||||
if (fwd) {
|
||||
fwdreloadcheck(label, "cstage", cs);
|
||||
fwdreloadcheck(label, "wwstage", ws);
|
||||
};
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
@test fn sretsentinels() void = {
|
||||
sretrow("quad_i64", strings.concat(
|
||||
"package main;\n",
|
||||
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n",
|
||||
"fn mk() quad = {\n",
|
||||
" return quad { a = 1i64, b = 2i64, c = 3i64, d = 4i64 };\n",
|
||||
"};\n",
|
||||
"fn main() i32 = { let q: quad = mk(); return 0; };\n"), false);
|
||||
// The utf8 decoder shape that surfaced #23: the []u8 slice tail
|
||||
// crosses the AX/DX/CX boundary the truncation bug dropped.
|
||||
sretrow("decoder", strings.concat(
|
||||
"package main;\n",
|
||||
"type decoder = struct { offs: i64, src: []u8 };\n",
|
||||
"fn mk(s: []u8) decoder = {\n",
|
||||
" let r: decoder;\n",
|
||||
" r.offs = 0i64;\n",
|
||||
" r.src = s;\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let b: [1]u8;\n",
|
||||
" let d: decoder = mk(b[0:1]);\n",
|
||||
" return 0;\n",
|
||||
"};\n"), false);
|
||||
sretrow("five_i64", strings.concat(
|
||||
"package main;\n",
|
||||
"type five = struct { a: i64, b: i64, c: i64, d: i64, e: i64 };\n",
|
||||
"fn mk() five = {\n",
|
||||
" return five { a = 1i64, b = 2i64, c = 3i64, d = 4i64, e = 5i64 };\n",
|
||||
"};\n",
|
||||
"fn main() i32 = { let f: five = mk(); return 0; };\n"), false);
|
||||
sretrow("forward_quad", strings.concat(
|
||||
"package main;\n",
|
||||
"type quad = struct { a: i64, b: i64, c: i64, d: i64 };\n",
|
||||
"fn inner(x: i64) quad = {\n",
|
||||
" return quad { a = x, b = x + 1i64, c = x + 2i64, d = x + 3i64 };\n",
|
||||
"};\n",
|
||||
"fn mk(x: i64) quad = {\n",
|
||||
" return inner(x);\n",
|
||||
"};\n",
|
||||
"fn main() i32 = { let q: quad = mk(10i64); return 0; };\n"), true);
|
||||
sretrow("forward_decoder", strings.concat(
|
||||
"package main;\n",
|
||||
"type decoder = struct { offs: i64, src: []u8 };\n",
|
||||
"fn inner(s: []u8) decoder = {\n",
|
||||
" let r: decoder;\n",
|
||||
" r.offs = 0i64;\n",
|
||||
" r.src = s;\n",
|
||||
" return r;\n",
|
||||
"};\n",
|
||||
"fn mk(s: []u8) decoder = {\n",
|
||||
" return inner(s);\n",
|
||||
"};\n",
|
||||
"fn main() i32 = {\n",
|
||||
" let b: [1]u8;\n",
|
||||
" let d: decoder = mk(b[0:1]);\n",
|
||||
" return 0;\n",
|
||||
"};\n"), true);
|
||||
};
|
||||
|
||||
// 730: mk's window (TEXT main.mk, .. first RET line) must copy the
|
||||
// trailing narrow field with the row's MOV width on both the (BP)
|
||||
// load and the 32(BX) store, and never with an 8-byte MOVQ.
|
||||
fn narrowcheck(label: str, stage: str, s: str, mov: str) void = {
|
||||
let tp: i32 = testenv.pos(s, "TEXT main.mk,");
|
||||
if (tp < 0) {
|
||||
fail(label, strings.concat(stage, ": no TEXT main.mk in .s"));
|
||||
};
|
||||
let store: str = strings.concat(mov, "\tAX, 32(BX)\n");
|
||||
let loadkey: str = strings.concat(mov, "\t");
|
||||
let sawstore: bool = false;
|
||||
let sawload: bool = false;
|
||||
let i: i32 = lineend(s, tp);
|
||||
for (i < s.len) {
|
||||
let le: i32 = lineend(s, i);
|
||||
let line: str = strings.sub(s, i, le);
|
||||
if (testenv.has(line, "\tRET\n")) { break; };
|
||||
if (testenv.has(line, store)) { sawstore = true; };
|
||||
if (testenv.has(line, loadkey)
|
||||
&& testenv.has(line, "(BP), AX")) {
|
||||
sawload = true;
|
||||
};
|
||||
if (testenv.has(line, "MOVQ\tAX, 32(BX)\n")) {
|
||||
fail(label, strings.concat(stage,
|
||||
": MOVQ AX, 32(BX) in mk -- pre-#33 over-wide ",
|
||||
"pattern"));
|
||||
};
|
||||
i = le;
|
||||
};
|
||||
if (!sawstore) {
|
||||
fail(label, strings.concat(stage, ": expected `", mov,
|
||||
" AX, 32(BX)` store in mk body"));
|
||||
};
|
||||
if (!sawload) {
|
||||
fail(label, strings.concat(stage, ": expected narrow `", mov,
|
||||
" -K(BP), AX` load in mk"));
|
||||
};
|
||||
};
|
||||
|
||||
fn narrowrow(label: str, src: str, mov: 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"));
|
||||
narrowcheck(label, "cstage", cs, mov);
|
||||
narrowcheck(label, "wwstage", ws, mov);
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
// The i16 tail falls to 2x MOVB per the shared MOVQ/MOVL/MOVB chain
|
||||
// (no MOVW arm in either stage) — narrow + byte-id is the pin, the
|
||||
// bug-check is the no-MOVQ-at-32 negative.
|
||||
@test fn sretnarrowfield() void = {
|
||||
narrowrow("trailing_bool", strings.concat(
|
||||
"package main;\n",
|
||||
"type t = struct { a: i32, s: []u8, r: bool };\n",
|
||||
"export fn mk() t = {\n",
|
||||
" let v: t; let z: []u8;\n",
|
||||
" v.s = z; v.a = 0; v.r = false;\n",
|
||||
" return v;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let x: t = mk();\n",
|
||||
" if (x.r) { return 1; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), "MOVB");
|
||||
narrowrow("trailing_u8", strings.concat(
|
||||
"package main;\n",
|
||||
"type t = struct { a: i32, s: []u8, r: u8 };\n",
|
||||
"export fn mk() t = {\n",
|
||||
" let v: t; let z: []u8;\n",
|
||||
" v.s = z; v.a = 0; v.r = 0u8;\n",
|
||||
" return v;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let x: t = mk();\n",
|
||||
" if (x.r != 0u8) { return 1; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), "MOVB");
|
||||
narrowrow("trailing_i16", strings.concat(
|
||||
"package main;\n",
|
||||
"type t = struct { a: i32, s: []u8, r: i16 };\n",
|
||||
"export fn mk() t = {\n",
|
||||
" let v: t; let z: []u8;\n",
|
||||
" v.s = z; v.a = 0; v.r = 0i16;\n",
|
||||
" return v;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let x: t = mk();\n",
|
||||
" if (x.r != 0i16) { return 1; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), "MOVB");
|
||||
narrowrow("trailing_i32", strings.concat(
|
||||
"package main;\n",
|
||||
"type t = struct { a: i32, s: []u8, r: i32 };\n",
|
||||
"export fn mk() t = {\n",
|
||||
" let v: t; let z: []u8;\n",
|
||||
" v.s = z; v.a = 0; v.r = 0;\n",
|
||||
" return v;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let x: t = mk();\n",
|
||||
" if (x.r != 0) { return 1; };\n",
|
||||
" return 0;\n",
|
||||
"};\n"), "MOVL");
|
||||
};
|
||||
Reference in New Issue
Block a user