test: port 989_ffivariadic_run to ww; retire the C carrier
This commit is contained in:
127
test/tool/ffivariadic_test.ww
Normal file
127
test/tool/ffivariadic_test.ww
Normal file
@@ -0,0 +1,127 @@
|
||||
package ffivariadic_test;
|
||||
|
||||
// SysV variadic-FFI AL contract, runtime-gated on both driver stages.
|
||||
// Port of the retired native carrier test/wcc/989_ffivariadic_run.c;
|
||||
// every assertion preserved.
|
||||
//
|
||||
// A ww caller of a C variadic fn (`@symbol("fixture") fn fixture(n:
|
||||
// i64, ...) f64;`) must set AL to the count of XMM regs used for the
|
||||
// variadic FLOAT args: the C callee gates its xmm-save-area stores on
|
||||
// `test %al,%al`, so a wrong AL makes va_arg(double) read garbage.
|
||||
// The pre-fix bug hardcoded AL=0 (`XORQ AX,AX`) — correct only for a
|
||||
// zero-float call. Ref SysV §3.5.7, ref/qbe/amd64/sysv.c:384.
|
||||
//
|
||||
// Byte-id can never see AL correctness, so each row builds a ww
|
||||
// caller against the host-cc-built va_arg(double) summer
|
||||
// (test/wcc/data/ffivariadic/fixture.c archived as
|
||||
// out/ffivariadic/libffifix.a, self-contained: zero relocs/undef
|
||||
// syms, no libc, so w6l links it hermetically) and asserts the
|
||||
// runtime sum via the binary's exit code, on BOTH `ww` and `ww_ww`.
|
||||
//
|
||||
// NON-VACUITY: fixture(2,1.0,2.0) is vacuous on this box — with AL=0
|
||||
// the two skipped xmm slots alias stale stack already holding
|
||||
// 1.0/2.0. At >=3 floats the coincidence breaks deterministically,
|
||||
// so every f64 row uses >=3 floats and reverting the fix FAILS here.
|
||||
//
|
||||
// f32 rows (#14): C default argument promotion widens each f32 arg
|
||||
// to f64. f32three — exact values, unpromoted MOVSS leaves stale
|
||||
// high 4B, sum misses 7.0. f32inexact — 0.1 is INEXACT in f32; the
|
||||
// expected side `(x: f64)` is the SAME var widened at runtime, so
|
||||
// equality holds iff the f32-ROUNDED value was promoted (not the f64
|
||||
// literal, not garbage); single f32 arg also lands the fi==1
|
||||
// boundary. f32mixed — only the f32 args promote; the native-f64 arg
|
||||
// passes at width, never double-promoted.
|
||||
//
|
||||
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
|
||||
// (the Make target declares both drivers) and the unlink/rmdir
|
||||
// accounting (testenv.clean asserts the removal).
|
||||
|
||||
import os;
|
||||
import os.exec;
|
||||
import strings;
|
||||
import testenv;
|
||||
import time;
|
||||
|
||||
fn fail(label: str, why: str) void = {
|
||||
let m: str = strings.concat("ffivariadic FAIL: ", label, " -- ",
|
||||
why, "\n");
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
assert(false);
|
||||
};
|
||||
|
||||
fn tmo() time.duration = {
|
||||
return (240i64 * (time.second: i64)): time.duration;
|
||||
};
|
||||
|
||||
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
|
||||
fn runcode(dir: str, name: str, argv: []str) i32 = {
|
||||
let co: testenv.commandout;
|
||||
testenv.runcommand(dir, dir, name, argv, tmo(), &co);
|
||||
if (co.termination != exec.termination.EXIT) { return -1; };
|
||||
return co.code;
|
||||
};
|
||||
|
||||
fn caller(call: str, want: str) str = {
|
||||
return strings.concat(
|
||||
"package main;\n",
|
||||
"@symbol(\"fixture\") fn fixture(n: i64, ...) f64;\n",
|
||||
"export fn main() int = {\n",
|
||||
"\tlet r: f64 = ", call, ";\n",
|
||||
"\tif (r == ", want, ") { return 0; };\n",
|
||||
"\treturn 1;\n",
|
||||
"};\n");
|
||||
};
|
||||
|
||||
@test fn variadic_al() void = {
|
||||
let labels: []str = ["three", "five", "eight", "f32three",
|
||||
"f32inexact", "f32mixed"];
|
||||
let srcs: []str = [
|
||||
caller("fixture(3, 1.0, 2.0, 3.0)", "6.0"),
|
||||
caller("fixture(5, 1.0, 2.0, 3.0, 4.0, 5.0)", "15.0"),
|
||||
caller("fixture(8, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0)",
|
||||
"36.0"),
|
||||
caller("fixture(3, 1.5: f32, 2.5: f32, 3.0: f32)", "7.0"),
|
||||
strings.concat(
|
||||
"package main;\n",
|
||||
"@symbol(\"fixture\") fn fixture(n: i64, ...) f64;\n",
|
||||
"export fn main() int = {\n",
|
||||
"\tlet x: f32 = 0.1: f32;\n",
|
||||
"\tlet r: f64 = fixture(1, x);\n",
|
||||
"\tif (r == (x: f64)) { return 0; };\n",
|
||||
"\treturn 1;\n",
|
||||
"};\n"),
|
||||
caller("fixture(3, 1.5: f32, 2.0, 3.5: f32)", "7.0"),
|
||||
];
|
||||
let libdir: str = strings.concat(testenv.repo(), "/out/ffivariadic");
|
||||
let drvs: []str = ["ww", "ww_ww"];
|
||||
let td: str = testenv.fresh();
|
||||
let i: i32 = 0;
|
||||
for (i < labels.len) {
|
||||
let src: str = strings.concat(td, "/", labels[i], ".ww");
|
||||
testenv.writefile(src, srcs[i]);
|
||||
let s: i32 = 0;
|
||||
for (s < 2) {
|
||||
// "out." prefix: a bare <label>.<drv> stem collides with
|
||||
// the <label>.ww SOURCE name on the cstage driver leg
|
||||
let out: str = strings.concat(td, "/out.", labels[i], ".",
|
||||
drvs[s]);
|
||||
let av: []str = [testenv.driver(drvs[s]), "build",
|
||||
strings.concat("-L", libdir), "-lffifix", "-o", out,
|
||||
src];
|
||||
if (runcode(td, strings.concat("build_", labels[i], "_",
|
||||
drvs[s]), av) != 0) {
|
||||
fail(labels[i], strings.concat(drvs[s],
|
||||
" build failed (-lffifix link)"));
|
||||
};
|
||||
let rav: []str = [out];
|
||||
if (runcode(td, strings.concat("run_", labels[i], "_",
|
||||
drvs[s]), rav) != 0) {
|
||||
fail(labels[i], strings.concat(drvs[s],
|
||||
" wrong variadic sum (AL/promotion contract)"));
|
||||
};
|
||||
s += 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
Reference in New Issue
Block a user