wcc: float arr[i]= spills X0 across index eval (#125)
Fix value-loss bug introduced as a #122 boundary in the float arr[i]=v store: when the index sub-expr clobbers X0 (e.g. `a[geti()]=1.5f32`), the value is lost. Mirror the scalar-deref X0-spill template (cstage cgen.c:4187; line shifted from the brief's stale :3859 cite by intervening #133/#135/#138 commits): for float element only, replace PUSHQ AX (junk for floats — value is in X0) with SUBQ $8,SP + MOVSS/MOVSD X0,(SP) before the idx/base eval; mirror replace POPQ AX with MOVSS/MOVSD (SP),X0 + ADDQ $8,SP after. Wwstage parallel. Non-float keeps PUSHQ/POPQ AX so the str/slice 3-word {ptr,len,cap} pop order at the end of the branch is preserved. #122 trailing-store comment updated from "Deferred to #125" to a positive cite. Test 916: 5 rows — f64_call_index + f32_call_index canonical repros (geti's body clobbers X0; pre-fix exit=2 from post-call residue, post-fix exit=1 from the spilled 1.5) + f64_lit_index / _localvar / _arith control rows for non-X0-clobbering index paths. f32_call_index uses an int-arg call to dodge the sibling cs/ww f32-arg-push MOVSD-vs-MOVSS divergence (#143, task #36 — orthogonal, filed). Bootstrap NEUTRAL (zero current float arr[i]= callers in lib; only [N]u8 byte-buffers like f64tos_buf). cs==ww byte-identical both stages (990-997 + 916 inline cmp). Closes the #122 boundary-doc loose end; completes the #122 family.
This commit is contained in:
@@ -19001,7 +19001,22 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
// Float element: spill X0 (not AX — AX is junk for
|
||||
// floats) across the idx/base eval. A call-index
|
||||
// (a[geti()]=v) clobbers X0 and would otherwise lose
|
||||
// the value. Mirrors the *p=v float deref store
|
||||
// twin in cgassign (#125).
|
||||
let spisfloat: bool = isfloattype(c, elemtn);
|
||||
let spmov: str = "MOVSD";
|
||||
if (isf32type(c, elemtn)) { spmov = "MOVSS"; };
|
||||
if (spisfloat) {
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(spmov);
|
||||
emitline("\tX0, (SP)\n");
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
};
|
||||
cgexpr(c, idx); // idx → AX
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -19039,7 +19054,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
// Reload value: float reloads X0 from the spill slot;
|
||||
// non-float pops AX. Twin of the value-spill site
|
||||
// above (#125).
|
||||
if (spisfloat) {
|
||||
emitline("\t");
|
||||
emitline(spmov);
|
||||
emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
} else {
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
};
|
||||
// str/slice: pop the saved len + cap and store
|
||||
// all three words. Kind-gate, not size — see the
|
||||
// spill site above (#1/Phase 3, #7/754).
|
||||
@@ -19052,11 +19077,9 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
|
||||
// left the value in X0. X0 survives the index/base
|
||||
// eval only for literal/local-var indices; a call-
|
||||
// index (a[geti()]=v) clobbers X0 and loses the value
|
||||
// — this path does not spill X0 across the eval,
|
||||
// unlike the *p=v float deref store. Deferred to #125.
|
||||
// left the value in X0, and the value-spill pair
|
||||
// above keeps X0 live across the idx/base eval so
|
||||
// a call-index (a[geti()]=v) doesn't lose it (#125).
|
||||
// For f32 the #104 CVTSD2SS narrowing only touches X0,
|
||||
// so the AX store below would write raw double low-
|
||||
// bits, garbage for f32 (#122, mirrors cstage cgen.c
|
||||
|
||||
@@ -4382,7 +4382,22 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
// Float element: spill X0 (not AX — AX is junk for
|
||||
// floats) across the idx/base eval. A call-index
|
||||
// (a[geti()]=v) clobbers X0 and would otherwise lose
|
||||
// the value. Mirrors the *p=v float deref store
|
||||
// twin in cgassign (#125).
|
||||
let spisfloat: bool = isfloattype(c, elemtn);
|
||||
let spmov: str = "MOVSD";
|
||||
if (isf32type(c, elemtn)) { spmov = "MOVSS"; };
|
||||
if (spisfloat) {
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(spmov);
|
||||
emitline("\tX0, (SP)\n");
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
};
|
||||
cgexpr(c, idx); // idx → AX
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -4420,7 +4435,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
// Reload value: float reloads X0 from the spill slot;
|
||||
// non-float pops AX. Twin of the value-spill site
|
||||
// above (#125).
|
||||
if (spisfloat) {
|
||||
emitline("\t");
|
||||
emitline(spmov);
|
||||
emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
} else {
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
};
|
||||
// str/slice: pop the saved len + cap and store
|
||||
// all three words. Kind-gate, not size — see the
|
||||
// spill site above (#1/Phase 3, #7/754).
|
||||
@@ -4433,11 +4458,9 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
|
||||
// left the value in X0. X0 survives the index/base
|
||||
// eval only for literal/local-var indices; a call-
|
||||
// index (a[geti()]=v) clobbers X0 and loses the value
|
||||
// — this path does not spill X0 across the eval,
|
||||
// unlike the *p=v float deref store. Deferred to #125.
|
||||
// left the value in X0, and the value-spill pair
|
||||
// above keeps X0 live across the idx/base eval so
|
||||
// a call-index (a[geti()]=v) doesn't lose it (#125).
|
||||
// For f32 the #104 CVTSD2SS narrowing only touches X0,
|
||||
// so the AX store below would write raw double low-
|
||||
// bits, garbage for f32 (#122, mirrors cstage cgen.c
|
||||
|
||||
@@ -19001,7 +19001,22 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
// Float element: spill X0 (not AX — AX is junk for
|
||||
// floats) across the idx/base eval. A call-index
|
||||
// (a[geti()]=v) clobbers X0 and would otherwise lose
|
||||
// the value. Mirrors the *p=v float deref store
|
||||
// twin in cgassign (#125).
|
||||
let spisfloat: bool = isfloattype(c, elemtn);
|
||||
let spmov: str = "MOVSD";
|
||||
if (isf32type(c, elemtn)) { spmov = "MOVSS"; };
|
||||
if (spisfloat) {
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(spmov);
|
||||
emitline("\tX0, (SP)\n");
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
};
|
||||
cgexpr(c, idx); // idx → AX
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -19039,7 +19054,17 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
// Reload value: float reloads X0 from the spill slot;
|
||||
// non-float pops AX. Twin of the value-spill site
|
||||
// above (#125).
|
||||
if (spisfloat) {
|
||||
emitline("\t");
|
||||
emitline(spmov);
|
||||
emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
} else {
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
};
|
||||
// str/slice: pop the saved len + cap and store
|
||||
// all three words. Kind-gate, not size — see the
|
||||
// spill site above (#1/Phase 3, #7/754).
|
||||
@@ -19052,11 +19077,9 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
|
||||
// left the value in X0. X0 survives the index/base
|
||||
// eval only for literal/local-var indices; a call-
|
||||
// index (a[geti()]=v) clobbers X0 and loses the value
|
||||
// — this path does not spill X0 across the eval,
|
||||
// unlike the *p=v float deref store. Deferred to #125.
|
||||
// left the value in X0, and the value-spill pair
|
||||
// above keeps X0 live across the idx/base eval so
|
||||
// a call-index (a[geti()]=v) doesn't lose it (#125).
|
||||
// For f32 the #104 CVTSD2SS narrowing only touches X0,
|
||||
// so the AX store below would write raw double low-
|
||||
// bits, garbage for f32 (#122, mirrors cstage cgen.c
|
||||
|
||||
Reference in New Issue
Block a user