w6c+selfhost: route f64/f32 struct-field load/store through X0

cgexpr leaves float results in X0, not AX, but the struct-field paths
emitted MOVQ AX,off(BX) and MOVQ off(BX),AX — so every store wrote
garbage and every load read garbage, except by accidental register
survival across an unrelated call. examples/lisp only worked because
parsef's X0 happened to live across the broken MOVQ shuffle into
vfloat; any inserted f64 op between them would silently corrupt.

Wire MOVSD/MOVSS X0,… (and the matching loads) into eight field
sites on both compilers: alloc(T{...}), p.x = v through local/ptr/
global, chained r.sub.x = v, *p = v for *f64, let v: T = T{...},
base.x reads, *T.x reads, and chained a.b.c.x reads.

83/83 lisp_test probes still pass; bootstrap reaches a byte-stable
fixed point at ww3 == ww4.
This commit is contained in:
2026-05-13 01:25:51 +09:00
parent 1c184ee6aa
commit da8d34e0d4
3 changed files with 322 additions and 21 deletions

View File

@@ -937,6 +937,19 @@ fn cgdot(c: *cgen, n: *node) void = {
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 via *struct: route through X0.
// MOVQ into AX leaves the SSE reg stale
// and any downstream consumer (arg
// pass, return, arithmetic) reads
// garbage.
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", X0\n");
} else {
let op: str = fieldloadop(fi);
emitline("\t");
@@ -944,7 +957,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
};
}; };
return;
};
fi = fi.finext;
@@ -970,6 +983,15 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 field: route through X0.
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), X0\n");
} else {
let op: str = fieldloadop(fi);
emitline("\t");
@@ -977,7 +999,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
};
}; };
return;
};
fi = fi.finext;
@@ -1164,6 +1186,15 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "CX");
emitline(", BX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 global field: route through X0.
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(fi.foff: i64, "CX");
emitline(", X0\n");
} else {
let op: str = fieldloadop(fi);
emitline("\t");
@@ -1171,7 +1202,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "CX");
emitline(", AX\n");
};
}; };
return;
};
fi = fi.finext;
@@ -1221,7 +1252,6 @@ fn cgdot(c: *cgen, n: *node) void = {
for (fi != nil) {
if (streq(fi.fname, fld)) {
cgexpr(c, lhs); // AX = ptr to inner struct
let lop: str = fieldloadop(fi);
// str field: load both halves.
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
@@ -1232,6 +1262,18 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", AX\n");
return;
};
// f64/f32 chained field: route through X0.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", X0\n");
return;
};
let lop: str = fieldloadop(fi);
emitline("\t");
emitline(lop);
emitline("\t");
@@ -1486,14 +1528,28 @@ fn cgalloc(c: *cgen, n: *node) void = {
let fn_: str = fi.fname;
if (streq(fn_, fname)) {
cgexpr(c, f.lhs);
emitline("\tMOVQ\t(SP), BX\n");
let sop: str = fieldstoreop(fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitint(fi.foff: i64);
emitline("(BX)\n");
fi = nil;
// alloc(T{ fval = v }) for f64/f32 field: cgexpr left
// the value in X0, not AX — route the store via MOVSD/MOVSS.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\tMOVQ\t(SP), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitint(fi.foff: i64);
emitline("(BX)\n");
fi = nil;
} else {
emitline("\tMOVQ\t(SP), BX\n");
let sop: str = fieldstoreop(fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitint(fi.foff: i64);
emitline("(BX)\n");
fi = nil;
};
} else {
fi = fi.finext;
};
@@ -1850,6 +1906,8 @@ fn cgassign(c: *cgen, n: *node) void = {
if (n.op == tkind.TK_ASSIGN) {
let inner: *node = lhs.lhs;
let elemstr: bool = false;
let elemfloat: bool = false;
let elemf32: bool = false;
let storeop: str = "MOVQ";
if (inner != nil) {
if (inner.kind == nkind.N_IDENT) {
@@ -1862,11 +1920,13 @@ fn cgassign(c: *cgen, n: *node) void = {
if (pe != nil) {
if (pe.kind == nkind.N_TNAME) {
if (streq(pe.str, "str")) { elemstr = true; }
else { if (streq(pe.str, "f64")) { elemfloat = true; }
else { if (streq(pe.str, "f32")) { elemfloat = true; elemf32 = true; }
else {
let ps: i32 = primsize(pe.str);
if (ps == 1) { storeop = "MOVB"; }
else { if (ps == 4) { storeop = "MOVL"; }; };
};
}; }; };
};
};
};
@@ -1875,6 +1935,27 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
cgexpr(c, n.rhs);
// `*p = v` for *f64 / *f32: value sits in X0. Spill
// to the stack, evaluate the pointer (clobbers AX),
// then reload X0 and MOVSD/MOVSS through the pointer.
if (elemfloat) {
let mov: str = "MOVSD";
if (elemf32) { mov = "MOVSS"; };
emitline("\tSUBQ\t$8, SP\n");
emitline("\t");
emitline(mov);
emitline("\tX0, (SP)\n");
cgexpr(c, inner);
emitline("\tMOVQ\tAX, BX\n");
emitline("\t");
emitline(mov);
emitline("\t(SP), X0\n");
emitline("\tADDQ\t$8, SP\n");
emitline("\t");
emitline(mov);
emitline("\tX0, (BX)\n");
return;
};
// Push order matches C cgen
// (cmd/w6c/cgen.c:1033-1041): PUSHQ AX
// (ptr) first, then PUSHQ BX (len) if
@@ -2037,6 +2118,21 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\n");
return;
};
// f64/f32 plain `=` via *struct: cgexpr left the
// value in X0. Reload struct ptr and MOVSD/MOVSS.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
};
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
@@ -2064,6 +2160,17 @@ fn cgassign(c: *cgen, n: *node) void = {
let fn_: str = fi.fname;
if (streq(fn_, fld)) {
cgexpr(c, n.rhs);
// f64/f32 direct struct local store: route via X0.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
return;
};
let sop: str = fieldstoreop(fi);
emitline("\t");
emitline(sop);
@@ -2174,6 +2281,21 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\n");
return;
};
// f64/f32 plain `=` on global struct field: value is
// in X0; LEAQ the base into BX and MOVSD/MOVSS.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
let sop: str = fieldstoreop(fi);
emitline("\tLEAQ\t");
emitsymname(c, bn);
@@ -2268,6 +2390,30 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\n");
return;
};
// f64/f32 chained plain `=`: cgexpr rhs left value in
// X0. Spill to stack so cgexpr(base) can use AX, then
// reload and MOVSD/MOVSS into the slot.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
cgexpr(c, n.rhs);
emitline("\tSUBQ\t$8, SP\n");
emitline("\t");
emitline(mov);
emitline("\tX0, (SP)\n");
cgexpr(c, base);
emitline("\tMOVQ\tAX, BX\n");
emitline("\t");
emitline(mov);
emitline("\t(SP), X0\n");
emitline("\tADDQ\t$8, SP\n");
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);