wcc: struct-return float fields via SSE return regs (#171a)
The RETURN twin of #165: a qualifying float-struct was returned GP-only (struct{f64,f64} in AX/DX instead of X0/X1) — value-correct via GP transport but not SysV register-class conformant. Route each float eightbyte through the SSE return cursor (X0/X1) and each integer eightbyte through GP (AX/DX) via independent cursors, at the struct-return SEND and RECV, both stages, reusing struct_float_class verbatim. Closes the temporary tuple-SSE/struct-GP divergence opened across #164/#165. A qualifying struct has >=1 lone f64 so maxalign is 8 and the ABI slot is an 8-multiple — no sub-8 tail — so #169's sized tail is unreachable here and the integer eightbyte uses a full MOVQ (cstage agrees, proven by the f64i32 cs==ww byte-id). f32 / multi-float-per-eightbyte stays GP (deferred #171b); >16B stays sret. Gate-blind and value-correct, so the discriminator is the SEND/RECV register class (MOVSD X0/X1 vs MOVQ AX/DX) — covered by probe 946_structret_run.
This commit is contained in:
6
Makefile
6
Makefile
@@ -355,6 +355,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_tuprecv_f64_run \
|
$(BIN)/test_tuprecv_f64_run \
|
||||||
$(BIN)/test_tupparam_run \
|
$(BIN)/test_tupparam_run \
|
||||||
$(BIN)/test_structparam_run \
|
$(BIN)/test_structparam_run \
|
||||||
|
$(BIN)/test_structret_run \
|
||||||
$(BIN)/test_floats_run \
|
$(BIN)/test_floats_run \
|
||||||
$(BIN)/test_size_type_run \
|
$(BIN)/test_size_type_run \
|
||||||
$(BIN)/test_types_sizelim_run \
|
$(BIN)/test_types_sizelim_run \
|
||||||
@@ -1278,6 +1279,11 @@ $(BIN)/test_structparam_run: test/wcc/946_structparam_run.c $(BIN)/ww \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_structret_run: test/wcc/946_structret_run.c $(BIN)/ww \
|
||||||
|
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||||
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
sizelint:
|
sizelint:
|
||||||
@sh tools/sizelint
|
@sh tools/sizelint
|
||||||
|
|
||||||
|
|||||||
@@ -7717,6 +7717,40 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
* excludes them so they fall through to the existing scalar
|
* excludes them so they fall through to the existing scalar
|
||||||
* path rather than emit a stomping MOVQ tail. Sizes >24B also
|
* path rather than emit a stomping MOVQ tail. Sizes >24B also
|
||||||
* fall through (sret deferred, same constraint as #4). */
|
* fall through (sret deferred, same constraint as #4). */
|
||||||
|
/* #171a: float-bearing struct RECEIVE (the return twin of
|
||||||
|
* #165's param recv). cgexpr leaves each float eightbyte in
|
||||||
|
* its SSE return reg (X0,X1 = tuple_sse_seq) and each INT
|
||||||
|
* eightbyte in its INTEGER return reg (AX,DX = tuple_rseq),
|
||||||
|
* on INDEPENDENT cursors per SysV (ref/qbe/amd64/sysv.c retr)
|
||||||
|
* — so a float is read from the next XMM regardless of its
|
||||||
|
* positional eightbyte (struct{f64,i32}: e0←X0, e1←AX). A
|
||||||
|
* qualifying struct's size is maxalign-rounded to a multiple
|
||||||
|
* of 8 (an f64 forces align 8), so every eightbyte is a full
|
||||||
|
* word — the #169 sized tail (MOVL/MOVB) is unreachable here.
|
||||||
|
* struct_float_class gates to qualifying structs; all-int +
|
||||||
|
* f32 fall through to the GP recv below (byte-id / #171b). */
|
||||||
|
if (n->rhs && n->rhs->kind == N_CALL && lu
|
||||||
|
&& lu->kind == TY_STRUCT) {
|
||||||
|
int sclass[2], snb;
|
||||||
|
if ((snb = struct_float_class(lu, sclass)) > 0) {
|
||||||
|
cgexpr(c, n->rhs, *locals);
|
||||||
|
int gpcur = 0, ssecur = 0;
|
||||||
|
for (int e = 0; e < snb; e++) {
|
||||||
|
if (sclass[e]) {
|
||||||
|
ins2(c, A_MOVSD,
|
||||||
|
areg(tuple_sse_seq[ssecur]),
|
||||||
|
amem(D_BP, off + e * 8));
|
||||||
|
ssecur++;
|
||||||
|
} else {
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
areg(tuple_rseq[gpcur]),
|
||||||
|
amem(D_BP, off + e * 8));
|
||||||
|
gpcur++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (n->rhs && n->rhs->kind == N_CALL && lu
|
if (n->rhs && n->rhs->kind == N_CALL && lu
|
||||||
&& lu->kind == TY_STRUCT && sz <= 24
|
&& lu->kind == TY_STRUCT && sz <= 24
|
||||||
&& (sz % 8 == 0 || sz % 8 == 1
|
&& (sz % 8 == 0 || sz % 8 == 1
|
||||||
@@ -8290,12 +8324,44 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
k += 1;
|
k += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
|
/* #171a: float-bearing struct RETURN (the return
|
||||||
areg(D_AX));
|
* twin of #165's param recv). A qualifying struct's
|
||||||
ins2(c, A_MOVQ, amem(D_BP, scr + 8),
|
* float eightbytes ride the SSE return row (X0,X1 =
|
||||||
areg(D_DX));
|
* tuple_sse_seq), its INT eightbytes the INTEGER
|
||||||
ins2(c, A_MOVQ, amem(D_BP, scr + 16),
|
* return row (AX,DX = tuple_rseq), on INDEPENDENT
|
||||||
areg(D_CX));
|
* cursors per SysV (ref/qbe/amd64/sysv.c retr) — so a
|
||||||
|
* float lands in the next XMM regardless of its
|
||||||
|
* positional eightbyte (struct{f64,i32}: e0→X0, e1→AX,
|
||||||
|
* NOT DX). The scr slot is zero-padded to 24B, so a
|
||||||
|
* full MOVQ on a trailing INT eightbyte reads no
|
||||||
|
* garbage — the #169 sized tail is a RECV-only concern.
|
||||||
|
* struct_float_class gates to qualifying structs (>=1
|
||||||
|
* f64, every eightbyte lone-f64 or pure-INT); all-int +
|
||||||
|
* f32 keep the AX/DX/CX transport (byte-id / #171b). */
|
||||||
|
int sclass[2], snb;
|
||||||
|
if ((snb = struct_float_class(rt, sclass)) > 0) {
|
||||||
|
int gpcur = 0, ssecur = 0;
|
||||||
|
for (int e = 0; e < snb; e++) {
|
||||||
|
if (sclass[e]) {
|
||||||
|
ins2(c, A_MOVSD,
|
||||||
|
amem(D_BP, scr + e * 8),
|
||||||
|
areg(tuple_sse_seq[ssecur]));
|
||||||
|
ssecur++;
|
||||||
|
} else {
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, scr + e * 8),
|
||||||
|
areg(tuple_rseq[gpcur]));
|
||||||
|
gpcur++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, scr + 8),
|
||||||
|
areg(D_DX));
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, scr + 16),
|
||||||
|
areg(D_CX));
|
||||||
|
}
|
||||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
ins1(c, A_POPQ, areg(D_BP));
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
ins0(c, A_RET);
|
ins0(c, A_RET);
|
||||||
|
|||||||
@@ -25061,15 +25061,57 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// #171a: float-bearing struct RETURN (return
|
||||||
emitoff(scroff: i64);
|
// twin of #165's param recv). A qualifying
|
||||||
emitline("(BP), AX\n");
|
// struct's float eightbytes ride the SSE return
|
||||||
emitline("\tMOVQ\t");
|
// row (X0,X1 = tupsse), its INT eightbytes the
|
||||||
emitoff((scroff + 8): i64);
|
// INTEGER return row (AX,DX = tupreg), on
|
||||||
emitline("(BP), DX\n");
|
// INDEPENDENT cursors per SysV (ref/qbe/amd64/
|
||||||
emitline("\tMOVQ\t");
|
// sysv.c retr) — so a float lands in the next
|
||||||
emitoff((scroff + 16): i64);
|
// XMM regardless of its positional eightbyte
|
||||||
emitline("(BP), CX\n");
|
// (struct{f64,i32}: e0→X0, e1→AX, NOT DX). The
|
||||||
|
// scratch is zero-padded to 24B so a full MOVQ
|
||||||
|
// on a trailing INT eightbyte reads no garbage
|
||||||
|
// (the #169 sized tail is a RECV concern).
|
||||||
|
// structfloatclass gates to qualifying structs;
|
||||||
|
// all-int + f32 keep the AX/DX/CX transport
|
||||||
|
// (byte-id / #171b).
|
||||||
|
let sfc: i32 = structfloatclass(c, c.fnret);
|
||||||
|
if (sfc != 0) {
|
||||||
|
let nb: i32 = sfc & 15;
|
||||||
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
|
let e: i32 = 0;
|
||||||
|
for (e < nb) {
|
||||||
|
let issse: bool = (sfc & (16 << e)) != 0;
|
||||||
|
if (issse) {
|
||||||
|
emitline("\tMOVSD\t");
|
||||||
|
emitoff((scroff + e*8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline("\n");
|
||||||
|
ssecur += 1;
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + e*8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupreg(gpcur));
|
||||||
|
emitline("\n");
|
||||||
|
gpcur += 1;
|
||||||
|
};
|
||||||
|
e += 1;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scroff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + 8): i64);
|
||||||
|
emitline("(BP), DX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + 16): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
};
|
||||||
emitline("\tMOVQ\tBP, SP\n");
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -25539,6 +25581,49 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||||
// deferred, same constraint as #4). Mirrors the cstage
|
// deferred, same constraint as #4). Mirrors the cstage
|
||||||
// cgen.c N_LET receive branch.
|
// cgen.c N_LET receive branch.
|
||||||
|
// #171a: float-bearing struct RECEIVE (return twin of #165's
|
||||||
|
// param recv). cgexpr leaves each float eightbyte in its SSE
|
||||||
|
// return reg (X0,X1 = tupsse) and each INT eightbyte in its
|
||||||
|
// INTEGER return reg (AX,DX = tupreg), on INDEPENDENT cursors
|
||||||
|
// per SysV (ref/qbe/amd64/sysv.c retr) — so a float is read
|
||||||
|
// from the next XMM regardless of its positional eightbyte
|
||||||
|
// (struct{f64,i32}: e0←X0, e1←AX). A qualifying struct's
|
||||||
|
// abisize is maxalign-rounded to a multiple of 8 (an f64
|
||||||
|
// forces align 8), so every eightbyte is a full word — the
|
||||||
|
// #169 sized tail is unreachable here. structfloatclass gates
|
||||||
|
// to qualifying structs; all-int + f32 fall to the GP recv
|
||||||
|
// below (byte-id / #171b).
|
||||||
|
if (rhs.kind == nkind.N_CALL && tn != nil) {
|
||||||
|
let sfc: i32 = structfloatclass(c, tn);
|
||||||
|
if (sfc != 0) {
|
||||||
|
cgexpr(c, rhs);
|
||||||
|
let nb: i32 = sfc & 15;
|
||||||
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
|
let e: i32 = 0;
|
||||||
|
for (e < nb) {
|
||||||
|
let issse: bool = (sfc & (16 << e)) != 0;
|
||||||
|
if (issse) {
|
||||||
|
emitline("\tMOVSD\t");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((off + e*8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ssecur += 1;
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitline(tupreg(gpcur));
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((off + e*8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
gpcur += 1;
|
||||||
|
};
|
||||||
|
e += 1;
|
||||||
|
};
|
||||||
|
c.lastwasreturn = 0;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
if (rhs.kind == nkind.N_CALL) {
|
if (rhs.kind == nkind.N_CALL) {
|
||||||
let sname: str;
|
let sname: str;
|
||||||
sname.ptr = nil; sname.len = 0;
|
sname.ptr = nil; sname.len = 0;
|
||||||
|
|||||||
@@ -728,15 +728,57 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// #171a: float-bearing struct RETURN (return
|
||||||
emitoff(scroff: i64);
|
// twin of #165's param recv). A qualifying
|
||||||
emitline("(BP), AX\n");
|
// struct's float eightbytes ride the SSE return
|
||||||
emitline("\tMOVQ\t");
|
// row (X0,X1 = tupsse), its INT eightbytes the
|
||||||
emitoff((scroff + 8): i64);
|
// INTEGER return row (AX,DX = tupreg), on
|
||||||
emitline("(BP), DX\n");
|
// INDEPENDENT cursors per SysV (ref/qbe/amd64/
|
||||||
emitline("\tMOVQ\t");
|
// sysv.c retr) — so a float lands in the next
|
||||||
emitoff((scroff + 16): i64);
|
// XMM regardless of its positional eightbyte
|
||||||
emitline("(BP), CX\n");
|
// (struct{f64,i32}: e0→X0, e1→AX, NOT DX). The
|
||||||
|
// scratch is zero-padded to 24B so a full MOVQ
|
||||||
|
// on a trailing INT eightbyte reads no garbage
|
||||||
|
// (the #169 sized tail is a RECV concern).
|
||||||
|
// structfloatclass gates to qualifying structs;
|
||||||
|
// all-int + f32 keep the AX/DX/CX transport
|
||||||
|
// (byte-id / #171b).
|
||||||
|
let sfc: i32 = structfloatclass(c, c.fnret);
|
||||||
|
if (sfc != 0) {
|
||||||
|
let nb: i32 = sfc & 15;
|
||||||
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
|
let e: i32 = 0;
|
||||||
|
for (e < nb) {
|
||||||
|
let issse: bool = (sfc & (16 << e)) != 0;
|
||||||
|
if (issse) {
|
||||||
|
emitline("\tMOVSD\t");
|
||||||
|
emitoff((scroff + e*8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline("\n");
|
||||||
|
ssecur += 1;
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + e*8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupreg(gpcur));
|
||||||
|
emitline("\n");
|
||||||
|
gpcur += 1;
|
||||||
|
};
|
||||||
|
e += 1;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scroff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + 8): i64);
|
||||||
|
emitline("(BP), DX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + 16): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
};
|
||||||
emitline("\tMOVQ\tBP, SP\n");
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -1206,6 +1248,49 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||||
// deferred, same constraint as #4). Mirrors the cstage
|
// deferred, same constraint as #4). Mirrors the cstage
|
||||||
// cgen.c N_LET receive branch.
|
// cgen.c N_LET receive branch.
|
||||||
|
// #171a: float-bearing struct RECEIVE (return twin of #165's
|
||||||
|
// param recv). cgexpr leaves each float eightbyte in its SSE
|
||||||
|
// return reg (X0,X1 = tupsse) and each INT eightbyte in its
|
||||||
|
// INTEGER return reg (AX,DX = tupreg), on INDEPENDENT cursors
|
||||||
|
// per SysV (ref/qbe/amd64/sysv.c retr) — so a float is read
|
||||||
|
// from the next XMM regardless of its positional eightbyte
|
||||||
|
// (struct{f64,i32}: e0←X0, e1←AX). A qualifying struct's
|
||||||
|
// abisize is maxalign-rounded to a multiple of 8 (an f64
|
||||||
|
// forces align 8), so every eightbyte is a full word — the
|
||||||
|
// #169 sized tail is unreachable here. structfloatclass gates
|
||||||
|
// to qualifying structs; all-int + f32 fall to the GP recv
|
||||||
|
// below (byte-id / #171b).
|
||||||
|
if (rhs.kind == nkind.N_CALL && tn != nil) {
|
||||||
|
let sfc: i32 = structfloatclass(c, tn);
|
||||||
|
if (sfc != 0) {
|
||||||
|
cgexpr(c, rhs);
|
||||||
|
let nb: i32 = sfc & 15;
|
||||||
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
|
let e: i32 = 0;
|
||||||
|
for (e < nb) {
|
||||||
|
let issse: bool = (sfc & (16 << e)) != 0;
|
||||||
|
if (issse) {
|
||||||
|
emitline("\tMOVSD\t");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((off + e*8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ssecur += 1;
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitline(tupreg(gpcur));
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((off + e*8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
gpcur += 1;
|
||||||
|
};
|
||||||
|
e += 1;
|
||||||
|
};
|
||||||
|
c.lastwasreturn = 0;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
if (rhs.kind == nkind.N_CALL) {
|
if (rhs.kind == nkind.N_CALL) {
|
||||||
let sname: str;
|
let sname: str;
|
||||||
sname.ptr = nil; sname.len = 0;
|
sname.ptr = nil; sname.len = 0;
|
||||||
|
|||||||
@@ -25061,15 +25061,57 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
emitline("\tMOVQ\t");
|
// #171a: float-bearing struct RETURN (return
|
||||||
emitoff(scroff: i64);
|
// twin of #165's param recv). A qualifying
|
||||||
emitline("(BP), AX\n");
|
// struct's float eightbytes ride the SSE return
|
||||||
emitline("\tMOVQ\t");
|
// row (X0,X1 = tupsse), its INT eightbytes the
|
||||||
emitoff((scroff + 8): i64);
|
// INTEGER return row (AX,DX = tupreg), on
|
||||||
emitline("(BP), DX\n");
|
// INDEPENDENT cursors per SysV (ref/qbe/amd64/
|
||||||
emitline("\tMOVQ\t");
|
// sysv.c retr) — so a float lands in the next
|
||||||
emitoff((scroff + 16): i64);
|
// XMM regardless of its positional eightbyte
|
||||||
emitline("(BP), CX\n");
|
// (struct{f64,i32}: e0→X0, e1→AX, NOT DX). The
|
||||||
|
// scratch is zero-padded to 24B so a full MOVQ
|
||||||
|
// on a trailing INT eightbyte reads no garbage
|
||||||
|
// (the #169 sized tail is a RECV concern).
|
||||||
|
// structfloatclass gates to qualifying structs;
|
||||||
|
// all-int + f32 keep the AX/DX/CX transport
|
||||||
|
// (byte-id / #171b).
|
||||||
|
let sfc: i32 = structfloatclass(c, c.fnret);
|
||||||
|
if (sfc != 0) {
|
||||||
|
let nb: i32 = sfc & 15;
|
||||||
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
|
let e: i32 = 0;
|
||||||
|
for (e < nb) {
|
||||||
|
let issse: bool = (sfc & (16 << e)) != 0;
|
||||||
|
if (issse) {
|
||||||
|
emitline("\tMOVSD\t");
|
||||||
|
emitoff((scroff + e*8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline("\n");
|
||||||
|
ssecur += 1;
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + e*8): i64);
|
||||||
|
emitline("(BP), ");
|
||||||
|
emitline(tupreg(gpcur));
|
||||||
|
emitline("\n");
|
||||||
|
gpcur += 1;
|
||||||
|
};
|
||||||
|
e += 1;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scroff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + 8): i64);
|
||||||
|
emitline("(BP), DX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((scroff + 16): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
};
|
||||||
emitline("\tMOVQ\tBP, SP\n");
|
emitline("\tMOVQ\tBP, SP\n");
|
||||||
emitline("\tPOPQ\tBP\n");
|
emitline("\tPOPQ\tBP\n");
|
||||||
emitline("\tRET\n");
|
emitline("\tRET\n");
|
||||||
@@ -25539,6 +25581,49 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
// stomping MOVQ tail. Sizes >24B also fall through (sret
|
||||||
// deferred, same constraint as #4). Mirrors the cstage
|
// deferred, same constraint as #4). Mirrors the cstage
|
||||||
// cgen.c N_LET receive branch.
|
// cgen.c N_LET receive branch.
|
||||||
|
// #171a: float-bearing struct RECEIVE (return twin of #165's
|
||||||
|
// param recv). cgexpr leaves each float eightbyte in its SSE
|
||||||
|
// return reg (X0,X1 = tupsse) and each INT eightbyte in its
|
||||||
|
// INTEGER return reg (AX,DX = tupreg), on INDEPENDENT cursors
|
||||||
|
// per SysV (ref/qbe/amd64/sysv.c retr) — so a float is read
|
||||||
|
// from the next XMM regardless of its positional eightbyte
|
||||||
|
// (struct{f64,i32}: e0←X0, e1←AX). A qualifying struct's
|
||||||
|
// abisize is maxalign-rounded to a multiple of 8 (an f64
|
||||||
|
// forces align 8), so every eightbyte is a full word — the
|
||||||
|
// #169 sized tail is unreachable here. structfloatclass gates
|
||||||
|
// to qualifying structs; all-int + f32 fall to the GP recv
|
||||||
|
// below (byte-id / #171b).
|
||||||
|
if (rhs.kind == nkind.N_CALL && tn != nil) {
|
||||||
|
let sfc: i32 = structfloatclass(c, tn);
|
||||||
|
if (sfc != 0) {
|
||||||
|
cgexpr(c, rhs);
|
||||||
|
let nb: i32 = sfc & 15;
|
||||||
|
let gpcur: i32 = 0;
|
||||||
|
let ssecur: i32 = 0;
|
||||||
|
let e: i32 = 0;
|
||||||
|
for (e < nb) {
|
||||||
|
let issse: bool = (sfc & (16 << e)) != 0;
|
||||||
|
if (issse) {
|
||||||
|
emitline("\tMOVSD\t");
|
||||||
|
emitline(tupsse(ssecur));
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((off + e*8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ssecur += 1;
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitline(tupreg(gpcur));
|
||||||
|
emitline(", ");
|
||||||
|
emitoff((off + e*8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
gpcur += 1;
|
||||||
|
};
|
||||||
|
e += 1;
|
||||||
|
};
|
||||||
|
c.lastwasreturn = 0;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
if (rhs.kind == nkind.N_CALL) {
|
if (rhs.kind == nkind.N_CALL) {
|
||||||
let sname: str;
|
let sname: str;
|
||||||
sname.ptr = nil; sname.len = 0;
|
sname.ptr = nil; sname.len = 0;
|
||||||
|
|||||||
319
test/wcc/946_structret_run.c
Normal file
319
test/wcc/946_structret_run.c
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* 946_structret_run — runtime + byte-id + asm-pattern net for #171a, the
|
||||||
|
* float-bearing struct-RETURN SysV ABI (the return twin of #165's struct
|
||||||
|
* PARAM, and the struct counterpart of #164's per-element tuple RETURN).
|
||||||
|
*
|
||||||
|
* THE BUG (#171a, cs==ww but SysV-non-conformant on master): a <=16B
|
||||||
|
* struct returned by value materialised into a zero-padded 24B scratch and
|
||||||
|
* then loaded unconditionally into the INTEGER return regs (AX/DX/CX); the
|
||||||
|
* let-init receive MOVQ'd them back from the same GP regs. So a
|
||||||
|
* `struct { a: f64, b: f64 }` return rode AX/DX instead of X0/X1. For a
|
||||||
|
* pure INTERNAL ww call (both ends compiled by the same stage) the f64
|
||||||
|
* bits still round-trip through the GP regs intact, so the runtime VALUE
|
||||||
|
* was correct AND both stages were symmetric-GP — the cs==ww gate and a
|
||||||
|
* value check are therefore NECESSARY-NOT-SUFFICIENT here (cf.
|
||||||
|
* 946_structparam_run, the param twin). The genuine defect is SysV
|
||||||
|
* register-CLASS conformance, observable only in the emitted asm (and at a
|
||||||
|
* real ABI boundary). The discriminating dimension is the asm pattern (c).
|
||||||
|
*
|
||||||
|
* THE FIX: per-EIGHTBYTE SysV classification (reusing struct_float_class /
|
||||||
|
* structfloatclass verbatim from #165). Each 8-byte eightbyte that is a
|
||||||
|
* lone f64 rides the SSE return cursor (X0,X1); a pure-INTEGER eightbyte
|
||||||
|
* rides the INTEGER return cursor (AX,DX) — on INDEPENDENT counters, so a
|
||||||
|
* float lands in the next XMM regardless of its positional eightbyte. SEND
|
||||||
|
* (cgreturn) loads the float eightbyte off the scratch into the next XMM;
|
||||||
|
* RECV (let-init) stores the XMM into the destination slot. Symmetric
|
||||||
|
* across cstage (cmd/w6c/cgen.c) and wwstage (selfhost/cmd/wcc/
|
||||||
|
* cgenstmt.ww).
|
||||||
|
*
|
||||||
|
* THE FALLBACK GATE: only a struct whose every eightbyte is pure-INT or a
|
||||||
|
* lone f64 qualifies. An f32 field packs two f32 into ONE SSE eightbyte
|
||||||
|
* (needs packing — deferred #171b); struct{f32,f32} therefore STAYS on the
|
||||||
|
* GP transport (correct + byte-identical current behavior). The f32f32 row
|
||||||
|
* asserts BOTH the correct round-tripped value AND the ABSENCE of an SSE
|
||||||
|
* return/receive, proving the gate caught it.
|
||||||
|
*
|
||||||
|
* Each row carries THREE dimensions:
|
||||||
|
* (a) cstage `ww build` + run — exit code (end-to-end round-trip).
|
||||||
|
* (b) w6c vs w6c_ww `.s` cmp — rule-10 byte-id (both stages identical).
|
||||||
|
* (c) asm-pattern: the producer `main.mk` returns a lone-f64 eightbyte
|
||||||
|
* as `MOVSD <off>(BP), Xn` (scratch -> XMM; a GP eightbyte is
|
||||||
|
* `MOVQ <off>(BP), AX`), and the consumer `main` receives it as
|
||||||
|
* `MOVSD Xn, -off(BP)` (XMM -> slot). The producer marker is scoped
|
||||||
|
* to `TEXT main.mk` (its scratch fill loads f64 literals via
|
||||||
|
* `MOVSD (SP), X0`, never `(BP), X0`); the consumer marker to
|
||||||
|
* `TEXT main,` (a GP recv MOVQ's instead). PRESENT for the SSE-routed
|
||||||
|
* rows, ABSENT for the GP / fallback rows. A pre-fix (or GP-regressed)
|
||||||
|
* build routes every eightbyte via AX/DX/CX -> both markers absent,
|
||||||
|
* which is exactly what (c) catches.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct row {
|
||||||
|
const char *label;
|
||||||
|
const char *src;
|
||||||
|
int want_exit;
|
||||||
|
int want_sse; /* 1 = struct return rides an XMM eightbyte */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
/* HEADLINE — struct{f64,f64}. Both eightbytes lone f64 -> X0,X1. mk
|
||||||
|
* returns `MOVSD ..,X0` + `MOVSD ..,X1`; main receives `MOVSD X0,..`
|
||||||
|
* + `MOVSD X1,..`. s.a + s.b = 8.0. */
|
||||||
|
{ "f64f64_ret",
|
||||||
|
"package main;\n"
|
||||||
|
"type pff = struct { a: f64, b: f64 };\n"
|
||||||
|
"fn mk() pff = { return pff { a = 3.0, b = 5.0 }; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet s: pff = mk();\n"
|
||||||
|
"\tif ((s.a: i64) + (s.b: i64) != 8) { return 1; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* CURSOR INDEPENDENCE — struct{f64,i32}: eb0 lone f64 (X0), eb1
|
||||||
|
* pure-INT (the i32 rides AX, NOT DX — the GP cursor starts at 0
|
||||||
|
* regardless of the float ahead of it). (s.a:i64)+(s.b:i64) = 8. */
|
||||||
|
{ "f64i32_ret",
|
||||||
|
"package main;\n"
|
||||||
|
"type pfi = struct { a: f64, b: i32 };\n"
|
||||||
|
"fn mk() pfi = { return pfi { a = 3.0, b = 5 }; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet s: pfi = mk();\n"
|
||||||
|
"\tif ((s.a: i64) + (s.b: i64) != 8) { return 1; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* ORDER SWAP — struct{i64,f64}: eb0 pure-INT (AX), eb1 lone f64 (X0,
|
||||||
|
* the first float still gets X0). Confirms the float lands in the
|
||||||
|
* next XMM regardless of position. s.a + (s.b:i64) = 8. */
|
||||||
|
{ "i64f64_ret",
|
||||||
|
"package main;\n"
|
||||||
|
"type pif = struct { a: i64, b: f64 };\n"
|
||||||
|
"fn mk() pif = { return pif { a = 3, b = 5.0 }; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet s: pif = mk();\n"
|
||||||
|
"\tif (s.a + (s.b: i64) != 8) { return 1; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 1 },
|
||||||
|
/* GP REGRESSION — struct{i64,i64}: every eightbyte pure-INT, no float
|
||||||
|
* to route, so it STAYS on the AX/DX/CX transport unchanged (the
|
||||||
|
* in-tree byte-id case). No SSE return/receive marker. */
|
||||||
|
{ "i64i64_ret",
|
||||||
|
"package main;\n"
|
||||||
|
"type pii = struct { a: i64, b: i64 };\n"
|
||||||
|
"fn mk() pii = { return pii { a = 3, b = 5 }; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet s: pii = mk();\n"
|
||||||
|
"\tif (s.a + s.b != 8) { return 1; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 0 },
|
||||||
|
/* FALLBACK GATE — struct{f32,f32}: two f32 pack into ONE SSE eightbyte
|
||||||
|
* (8B struct). The gate rejects f32, so the struct stays GP-routed
|
||||||
|
* (returned/received via AX). The value still round-trips (the 8B MOVQ
|
||||||
|
* carries both f32) -> (s.a:i64)+(s.b:i64) = 8. Asserts BOTH the
|
||||||
|
* correct value AND no SSE return/receive, proving the gate caught it
|
||||||
|
* (NOT routed to an SSE reg). The 2-f32-per-eightbyte packing is
|
||||||
|
* #171b. */
|
||||||
|
{ "f32f32_ret_fallback",
|
||||||
|
"package main;\n"
|
||||||
|
"type pf32 = struct { a: f32, b: f32 };\n"
|
||||||
|
"fn mk() pf32 = { return pf32 { a = 3.0f32, b = 5.0f32 }; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet s: pf32 = mk();\n"
|
||||||
|
"\tif ((s.a: i64) + (s.b: i64) != 8) { return 1; };\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n", 0, 0 },
|
||||||
|
{ NULL, NULL, 0, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
slurp_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
FILE *fa = fopen(a, "rb");
|
||||||
|
FILE *fb = fopen(b, "rb");
|
||||||
|
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||||
|
int rc = 0;
|
||||||
|
for (;;) {
|
||||||
|
int ca = fgetc(fa);
|
||||||
|
int cb = fgetc(fb);
|
||||||
|
if (ca != cb) { rc = -1; break; }
|
||||||
|
if (ca == EOF) break;
|
||||||
|
}
|
||||||
|
fclose(fa); fclose(fb);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Isolate one function's TEXT block: from `head` to the next `\nTEXT `.
|
||||||
|
* Returns a malloc'd copy the caller frees, or NULL. */
|
||||||
|
static char *
|
||||||
|
isolate(const char *buf, const char *head)
|
||||||
|
{
|
||||||
|
const char *start = strstr(buf, head);
|
||||||
|
if (!start) return NULL;
|
||||||
|
const char *end = strstr(start + 1, "\nTEXT ");
|
||||||
|
size_t len = end ? (size_t)(end - start) : strlen(start);
|
||||||
|
char *out = malloc(len + 1);
|
||||||
|
if (!out) return NULL;
|
||||||
|
memcpy(out, start, len);
|
||||||
|
out[len] = '\0';
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Does the struct return ride an XMM on BOTH ends?
|
||||||
|
* - producer `main.mk`: a lone-f64 eightbyte is loaded scratch -> XMM as
|
||||||
|
* `MOVSD\t<off>(BP), X0` (the only `(BP), X0` in mk; its f64-literal
|
||||||
|
* fill loads via `MOVSD (SP), X0`). A GP-routed return is `MOVQ ..,AX`.
|
||||||
|
* - consumer `main`: the eightbyte is stored XMM -> slot as
|
||||||
|
* `MOVSD\tX0, -<off>(BP)`. A GP recv is `MOVQ AX, ..`.
|
||||||
|
* Returns 1 iff both markers present, 0 iff both absent, -1 on a split or
|
||||||
|
* scan error (which would itself be a bug). */
|
||||||
|
static int
|
||||||
|
ret_rides_sse(const char *path)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return -1;
|
||||||
|
static char buf[1 << 18];
|
||||||
|
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[n] = '\0';
|
||||||
|
|
||||||
|
char *mk = isolate(buf, "TEXT main.mk");
|
||||||
|
char *mn = isolate(buf, "TEXT main,");
|
||||||
|
if (!mk || !mn) { free(mk); free(mn); return -1; }
|
||||||
|
|
||||||
|
int send = strstr(mk, "(BP), X0") != NULL;
|
||||||
|
int recv = strstr(mn, "MOVSD\tX0, -") != NULL;
|
||||||
|
free(mk); free(mn);
|
||||||
|
if (send != recv) return -1; /* SEND and RECV must agree */
|
||||||
|
return send;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = getenv("BIN");
|
||||||
|
if (!bin) bin = "out/bin";
|
||||||
|
char absbin[2200];
|
||||||
|
if (bin[0] != '/') {
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||||
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||||
|
bin = absbin;
|
||||||
|
}
|
||||||
|
|
||||||
|
char w6c[2300], w6c_ww[2300];
|
||||||
|
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||||
|
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||||
|
if (access(w6c_ww, X_OK) != 0) {
|
||||||
|
fprintf(stderr, "structret: w6c_ww missing — cannot run the "
|
||||||
|
"cs==ww byte-id gate\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = 0, fail = 0;
|
||||||
|
for (int i = 0; rows[i].src; i++, n++) {
|
||||||
|
char src[64];
|
||||||
|
snprintf(src, sizeof src, "/tmp/wwsrt_%d_%d.ww", getpid(), i);
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (f == NULL) { fail++; continue; }
|
||||||
|
fputs(rows[i].src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
char cmd[4096];
|
||||||
|
|
||||||
|
/* (a) cstage build + run in a scratch dir. */
|
||||||
|
char tmpdir[64];
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsrt_%d_d_%d",
|
||||||
|
getpid(), i);
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||||||
|
tmpdir, bin, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||||||
|
rows[i].label);
|
||||||
|
fail++;
|
||||||
|
unlink(src); rmdir(tmpdir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char outbin[128];
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
|
||||||
|
int got = runwait(outbin);
|
||||||
|
if (got != rows[i].want_exit) {
|
||||||
|
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
|
||||||
|
rows[i].label, got, rows[i].want_exit);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
unlink(outbin); rmdir(tmpdir);
|
||||||
|
|
||||||
|
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
|
||||||
|
char cs_s[64], ws_s[64];
|
||||||
|
snprintf(cs_s, sizeof cs_s, "/tmp/wwsrt_%d_%d_cs.s",
|
||||||
|
getpid(), i);
|
||||||
|
snprintf(ws_s, sizeof ws_s, "/tmp/wwsrt_%d_%d_ww.s",
|
||||||
|
getpid(), i);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||||
|
w6c, cs_s, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
|
||||||
|
fail++; unlink(src); continue;
|
||||||
|
}
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||||
|
w6c_ww, ws_s, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: w6c_ww failed\n",
|
||||||
|
rows[i].label);
|
||||||
|
fail++; unlink(src); unlink(cs_s); continue;
|
||||||
|
}
|
||||||
|
if (slurp_eq(cs_s, ws_s) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
|
||||||
|
"byte-id violation)\n", rows[i].label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (c) asm-pattern discriminator on the return/receive. byte-id
|
||||||
|
* (b) proves ww_s mirrors cs_s, so checking cs_s suffices. */
|
||||||
|
int sse = ret_rides_sse(cs_s);
|
||||||
|
if (sse < 0) {
|
||||||
|
fprintf(stderr, "row[%s]: cannot scan .s (or SEND/RECV "
|
||||||
|
"disagree)\n", rows[i].label);
|
||||||
|
fail++;
|
||||||
|
} else if (sse != rows[i].want_sse) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"row[%s]: struct return SSE %s, want %s "
|
||||||
|
"(register-class discriminator)\n",
|
||||||
|
rows[i].label, sse ? "present" : "absent",
|
||||||
|
rows[i].want_sse ? "present" : "absent");
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "%d/%d struct-return tests failed\n", fail, n);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("structret: %d/%d ok (cstage run + cs==ww byte-id + "
|
||||||
|
"SSE-return asm)\n", n, n);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user