wwstage: C-FFI variadic call codegen parity with cstage (#10)
Mirror cstage's C-variadic call handling in the ww self-host: parse a bare `...` param (decl.ww), skip param-keyed desugar for it to avoid a nil-deref (check.ww), and emit AL = XMM-reg count plus CVTSS2SD promotion of f32 args in the variadic tail (cgenutil.ww, cgenexpr.ww). Closes the cat-A wwstage silent miscompile (AL=0, unpromoted f32 tail). Parse/check/cgen are one atomic align-up (parse alone miscompiles, so not bisect-splittable). 989_ffivariadic now runs dual-stage (cstage ww + wwstage ww_ww), 12/12; w6c==w6c_ww byte-identical. Byte-id alone is blind here (the bootstrap calls no float-bearing C variadic), so the ww_ww runtime rows are the real net.
This commit is contained in:
@@ -7774,12 +7774,22 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// C-variadic (bare `...`) detection: drives the SysV §3.5.7 AL=
|
||||
// XMM-count emit (below, before CALL) and the f32→f64 variadic-
|
||||
// tail promotion in pushargsrev (#14). cvarnfixed is the fixed-
|
||||
// param count, or -1 when the callee is not C-variadic. Mirror of
|
||||
// cstage's `cu && cu->kind == TY_FN && cu->variadic` gate.
|
||||
let cvarnfixed: i32 = -1;
|
||||
{
|
||||
let nfx: i32 = 0;
|
||||
if (calleecvariadic(c, callee, &nfx)) { cvarnfixed = nfx; };
|
||||
};
|
||||
// #38b: two-phase push — MEMORY-class (>48B tagged) args staged
|
||||
// first so they sit BELOW every register-class word; the pop loop
|
||||
// drains a strict prefix and never touches them. memwords feeds
|
||||
// the caller-cleanup ADDQ (with the mix guard below).
|
||||
let memwords: i32 = pushargsrev(c, n.list, calleeparams, true);
|
||||
let nargs: i32 = pushargsrev(c, n.list, calleeparams, false);
|
||||
let memwords: i32 = pushargsrev(c, n.list, calleeparams, true, 0, cvarnfixed);
|
||||
let nargs: i32 = pushargsrev(c, n.list, calleeparams, false, 0, cvarnfixed);
|
||||
// sret call (#23): callee returns plain TY_STRUCT > 24B. The
|
||||
// dest pointer lands in RDI; start intidx at 1 to skip RDI in
|
||||
// the user-arg pop loop and emit `LEAQ off(BP), DI` AFTER all
|
||||
@@ -7818,7 +7828,13 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
||||
let dparam: *syntax.node = calleeparams;
|
||||
let popped: i32 = 0;
|
||||
let stackslots: i32 = 0;
|
||||
let argidx: i32 = 0;
|
||||
for (a != nil) {
|
||||
// argidx is this arg's 0-based position; captured before any
|
||||
// continue so the C-variadic f32-promotion check below tracks
|
||||
// the push-side argidx for every arg shape (#14).
|
||||
let curargidx: i32 = argidx;
|
||||
argidx += 1;
|
||||
// #38b: MEMORY-class arg — its words sit below the pop
|
||||
// region and stay on the stack for the callee; nothing to
|
||||
// drain. Same param-keyed-else-arg-keyed detection as
|
||||
@@ -7879,6 +7895,12 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
||||
if (fk != 0) {
|
||||
let mov: str = "MOVSD";
|
||||
if (fk == 1) { mov = "MOVSS"; };
|
||||
// #14: a C-variadic-tail f32 was promoted to f64 at push
|
||||
// (CVTSS2SD + MOVSD), so its slot reloads MOVSD. Mirror
|
||||
// cstage cmd/w6c/cgen.c promote_f32 pop.
|
||||
if (cvarnfixed >= 0 && curargidx >= cvarnfixed && fk == 1) {
|
||||
mov = "MOVSD";
|
||||
};
|
||||
if (fpidx < 8) {
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
@@ -8200,6 +8222,23 @@ fn cgcall(c: *cgen, n: *syntax.node) void = {
|
||||
emitline("(BP), DI\n");
|
||||
};};
|
||||
};
|
||||
// SysV §3.5.7: a C-variadic call sets AL to the number of vector
|
||||
// (XMM) regs used to pass the variable float args — the callee
|
||||
// gates its xmm-save-area stores on `test %al,%al`, so a wrong AL
|
||||
// makes va_arg(double) read garbage. fpidx is the XMM cursor
|
||||
// (capped at 8 in the pop loop). Emitted after any sret-RDI LEAQ,
|
||||
// right before CALL. Mirror cstage cmd/w6c/cgen.c (the imm→reg MOVQ
|
||||
// idiom carries AL since MOVL-imm has no w6a encoding; AL = low byte,
|
||||
// fpidx <= 8). Ref ref/qbe/amd64/sysv.c:384.
|
||||
if (cvarnfixed >= 0) {
|
||||
if (fpidx > 0) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(fpidx: i64);
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
};
|
||||
};
|
||||
if (isfnptrcall) {
|
||||
// Load fn-ptr field value into AX; CALL AX. We emit the
|
||||
// load AFTER the args have been popped (so AX/BX/etc
|
||||
|
||||
Reference in New Issue
Block a user