cstage+selfhost+test: zero unused ABI words in cgreturn variant-widen (#18)
cgreturn's variant-widen arm only filled the registers each variant's payload needed: scalar variants left CX and R8 stale; str variant left R8 stale. The receiver (cg_widen_tagged_store non-N_IDENT branch) writes all four ABI words to the dst slot unconditionally, so caller-side residue in CX/R8 (the array-index IMULQ being the canonical primer) landed at slot+16 and slot+24. Worker-fmtparser surfaced this through fprintf's loop body where array indexing primed CX and a 24B-return helper failed to clear it; bug isn't loop-specific — straight-line repro at /tmp/wcrs_repro/ repro8.ww confirms. Patch: emit `MOVQ $0, CX` after the variant's register shuffle when the slot exceeds 16B and the variant doesn't fill CX; same for R8 when the slot exceeds 24B. Symmetric across cstage cgen.c and wwstage cgenstmt.ww. Order: zero-MOVQs precede `MOVQ $tag, AX` so AX-as-staging stays safe. Inline comment at cg_widen_tagged_store non-N_IDENT branch documents the producer-zero contract. Test 707 (cgreturn_variant_zero): 6 rows × both stages = 12 fixtures. Covers scalar/bool/str returns after array-index priming in straight-line / single-loop body / nested-loop body / mixed-variant loop. Asm byte-identity check intentionally omitted; wwstage taggedvariantindex divergence on str/bool N_IDENT is filed as task #20. 995_self_rebuild covers the broader cross-stage drift surface. ww2 == ww3 == ww4 byte-identical post-fix. 67/67 green. Deferred (followups filed): #20 wwstage taggedvariantindex, #21 [N]str/[N]bool array-literal non-pointer-half writes, #22 consolidate variant-widen into uniform scratch-slot path.
This commit is contained in:
@@ -14663,6 +14663,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||
// Tagged-return ABI: AX=tag, DX=word0, CX=word1,
|
||||
// R8=word2. Receiver (cgwidentaggedstore call-source
|
||||
// arm) writes AX/DX/CX/R8 unconditionally sized by the
|
||||
// dst slot; unused ABI words must be zeroed here so a
|
||||
// stale CX/R8 from the caller (e.g. a slice-stride
|
||||
// IMULQ before the call) does not land in slot+16 /
|
||||
// slot+24. (Task #18.)
|
||||
let rsz: i32 = slotsize(c, c.fnret);
|
||||
if (nodeisslice(c, rhs)) {
|
||||
// cgexpr leaves (AX=ptr, BX=len, CX=cap).
|
||||
// Shuffle into return ABI: DX=ptr, CX=len,
|
||||
@@ -14673,8 +14681,20 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
} else { if (nodeisstr(c, rhs)) {
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// str fills DX,CX. Zero R8 if dst covers slot+24.
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// scalar fills DX only. Zero CX / R8 if dst
|
||||
// covers slot+16 / slot+24.
|
||||
if (rsz > 16) {
|
||||
emitline("\tMOVQ\t$0, CX\n");
|
||||
};
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
};};
|
||||
emitline("\tMOVQ\t$");
|
||||
if (idx < 0) { idx = 0; };
|
||||
|
||||
@@ -230,6 +230,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||
// Tagged-return ABI: AX=tag, DX=word0, CX=word1,
|
||||
// R8=word2. Receiver (cgwidentaggedstore call-source
|
||||
// arm) writes AX/DX/CX/R8 unconditionally sized by the
|
||||
// dst slot; unused ABI words must be zeroed here so a
|
||||
// stale CX/R8 from the caller (e.g. a slice-stride
|
||||
// IMULQ before the call) does not land in slot+16 /
|
||||
// slot+24. (Task #18.)
|
||||
let rsz: i32 = slotsize(c, c.fnret);
|
||||
if (nodeisslice(c, rhs)) {
|
||||
// cgexpr leaves (AX=ptr, BX=len, CX=cap).
|
||||
// Shuffle into return ABI: DX=ptr, CX=len,
|
||||
@@ -240,8 +248,20 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
} else { if (nodeisstr(c, rhs)) {
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// str fills DX,CX. Zero R8 if dst covers slot+24.
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// scalar fills DX only. Zero CX / R8 if dst
|
||||
// covers slot+16 / slot+24.
|
||||
if (rsz > 16) {
|
||||
emitline("\tMOVQ\t$0, CX\n");
|
||||
};
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
};};
|
||||
emitline("\tMOVQ\t$");
|
||||
if (idx < 0) { idx = 0; };
|
||||
|
||||
@@ -14663,6 +14663,14 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
let idx: i32 = taggedvariantindex(c, c.fnret, rhs);
|
||||
// Tagged-return ABI: AX=tag, DX=word0, CX=word1,
|
||||
// R8=word2. Receiver (cgwidentaggedstore call-source
|
||||
// arm) writes AX/DX/CX/R8 unconditionally sized by the
|
||||
// dst slot; unused ABI words must be zeroed here so a
|
||||
// stale CX/R8 from the caller (e.g. a slice-stride
|
||||
// IMULQ before the call) does not land in slot+16 /
|
||||
// slot+24. (Task #18.)
|
||||
let rsz: i32 = slotsize(c, c.fnret);
|
||||
if (nodeisslice(c, rhs)) {
|
||||
// cgexpr leaves (AX=ptr, BX=len, CX=cap).
|
||||
// Shuffle into return ABI: DX=ptr, CX=len,
|
||||
@@ -14673,8 +14681,20 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
} else { if (nodeisstr(c, rhs)) {
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// str fills DX,CX. Zero R8 if dst covers slot+24.
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// scalar fills DX only. Zero CX / R8 if dst
|
||||
// covers slot+16 / slot+24.
|
||||
if (rsz > 16) {
|
||||
emitline("\tMOVQ\t$0, CX\n");
|
||||
};
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
};};
|
||||
emitline("\tMOVQ\t$");
|
||||
if (idx < 0) { idx = 0; };
|
||||
|
||||
Reference in New Issue
Block a user