From da8d34e0d480507888fcb4f42cb992f7530d9470 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 13 May 2026 01:25:51 +0900 Subject: [PATCH] w6c+selfhost: route f64/f32 struct-field load/store through X0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- cmd/w6c/cgen.c | 144 ++++++++++++++++++++++++++++- selfhost/cmd/wcc/cgenexpr.ww | 172 ++++++++++++++++++++++++++++++++--- selfhost/cmd/wcc/cgenstmt.ww | 27 ++++-- 3 files changed, 322 insertions(+), 21 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index b928acc2..85c03735 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -113,6 +113,24 @@ node_isf32(Node *n) return n && type_isf32(n->type); } +/* fld_isfloat — true iff f's underlying type is f32 or f64. The cgen + * passes float values in X0 (via MOVSD/MOVSS), integer/ptr values in + * AX (via MOVQ). Without this check, a field store/load on an f64 slot + * runs through AX and the bits never reach the SSE side — see the + * vfloat / L.curfval traps documented in examples/lisp/CLAUDE.md. + * Sets *isf32 to 1 for f32, 0 for f64. */ +static int +fld_isfloat(Type *t, int *isf32) +{ + if (isf32) *isf32 = 0; + if (t == NULL) return 0; + if (t->kind == TY_NAMED) t = t->under; + if (t == NULL) return 0; + if (t->kind == TY_F64) return 1; + if (t->kind == TY_F32) { if (isf32) *isf32 = 1; return 1; } + return 0; +} + /* struct ≤16B all-INTEGER: 1 or 2 eightbyte regs. * Returns 0 if not a struct or too large. */ static int @@ -1324,6 +1342,28 @@ cgexpr(Cg *c, Node *n, Local *locals) default: break; /* others rare */ } } + /* f64/f32 field, plain `=`: cgexpr left the value in + * X0, not AX. Route the store via MOVSD/MOVSS. + * Compound ops on float fields aren't wired here — + * see CLAUDE.md #8 in examples/lisp; same in the + * structlit-init path below. */ + int b_isf32 = 0; + if (n->op == TK_ASSIGN + && fld_isfloat(f->type, &b_isf32)) { + int mov = b_isf32 ? A_MOVSS : A_MOVSD; + if (via_ptr) { + ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX)); + ins2(c, mov, areg(D_X0), amem(D_BX, foff)); + } else if (is_global) { + ins2(c, A_LEAQ, + masym(c, base->str), areg(D_BX)); + ins2(c, mov, areg(D_X0), amem(D_BX, foff)); + } else { + ins2(c, mov, areg(D_X0), + amem(D_BP, boff + foff)); + } + break; + } /* now store AX into target */ if (via_ptr) { ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX)); @@ -1370,6 +1410,28 @@ cgexpr(Cg *c, Node *n, Local *locals) else if (fsz == 4) store_op = A_MOVL; int foff = (int)f->offset; if (n->op == TK_ASSIGN) { + int c_isf32 = 0; + if (fld_isfloat(ft, &c_isf32)) { + /* f64/f32 chained-store: cgexpr rhs + * left the value in X0. Spill to stack + * so cgexpr on the inner pointer can + * use AX, then reload into X0 and + * MOVSD/MOVSS into the slot. */ + int mov = c_isf32 ? A_MOVSS : A_MOVSD; + cgexpr(c, n->rhs, locals); + ins2(c, A_SUBQ, aimm(8), areg(D_SP)); + ins2(c, mov, areg(D_X0), + amem(D_SP, 0)); + cgexpr(c, n->lhs->lhs, locals); + ins2(c, A_MOVQ, areg(D_AX), + areg(D_BX)); + ins2(c, mov, amem(D_SP, 0), + areg(D_X0)); + ins2(c, A_ADDQ, aimm(8), areg(D_SP)); + ins2(c, mov, areg(D_X0), + amem(D_BX, foff)); + break; + } if (fu && fu->kind == TY_STR) { /* str rhs: (AX=ptr, BX=len). Stash * both, then load the struct ptr @@ -1555,6 +1617,23 @@ cgexpr(Cg *c, Node *n, Local *locals) Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt; Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL; if (vt && vt->kind == TY_NAMED) vt = vt->under; + /* `*p = v` for *f64 / *f32: cgexpr leaves the value in X0, + * not AX. Spill X0 to the stack, evaluate the pointer + * (clobbers AX/BX freely), then reload X0 and MOVSD/MOVSS + * through the pointer. */ + int deref_isf32 = 0; + if (vt && fld_isfloat(vt, &deref_isf32)) { + int mov = deref_isf32 ? A_MOVSS : A_MOVSD; + cgexpr(c, n->rhs, locals); + ins2(c, A_SUBQ, aimm(8), areg(D_SP)); + ins2(c, mov, areg(D_X0), amem(D_SP, 0)); + cgexpr(c, n->lhs->lhs, locals); + ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); + ins2(c, mov, amem(D_SP, 0), areg(D_X0)); + ins2(c, A_ADDQ, aimm(8), areg(D_SP)); + ins2(c, mov, areg(D_X0), amem(D_BX, 0)); + break; + } cgexpr(c, n->rhs, locals); /* AX = value (BX too if str) */ ins1(c, A_PUSHQ, areg(D_AX)); if (vt && vt->kind == TY_STR) ins1(c, A_PUSHQ, areg(D_BX)); @@ -1839,14 +1918,37 @@ cgexpr(Cg *c, Node *n, Local *locals) for (Node *f = v->list; f; f = f->next) { u64 foff = 0; int fsz = 8; + Type *ftype = NULL; for (Tfield *fl = u->fields; fl; fl = fl->next) { if (strcmp(fl->name, f->str) == 0) { foff = fl->offset; fsz = (int)(fl->type ? fl->type->size : 8); + ftype = fl->type; break; } } - cgexpr(c, f->lhs, locals); /* AX = field val */ + cgexpr(c, f->lhs, locals); /* AX or (AX,BX) or X0 */ + int f_isf32 = 0; + if (fld_isfloat(ftype, &f_isf32)) { + int mov = f_isf32 ? A_MOVSS : A_MOVSD; + ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); + ins2(c, mov, areg(D_X0), + amem(D_BX, (int)foff)); + continue; + } + /* str-typed field: cgexpr leaves (AX=ptr, BX=len). + * Route the heap base through CX so both halves + * survive — using BX would clobber len. */ + Type *fu = (ftype && ftype->kind == TY_NAMED) + ? ftype->under : ftype; + if (fu && fu->kind == TY_STR) { + ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_CX, (int)foff + 0)); + ins2(c, A_MOVQ, areg(D_BX), + amem(D_CX, (int)foff + 8)); + continue; + } ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX)); int op = A_MOVQ; if (fsz == 1) op = A_MOVB; @@ -2765,6 +2867,19 @@ cgexpr(Cg *c, Node *n, Local *locals) areg(D_BX)); break; } + /* f64/f32 field: route through X0 (MOVSD/MOVSS). + * Loading via MOVQ AX would put the bits in the + * integer reg, and any downstream consumer that + * reads X0 (arg pass, return, arithmetic) would see + * stale data. */ + int e_isf32 = 0; + if (fld_isfloat(f->type, &e_isf32)) { + int mov = e_isf32 ? A_MOVSS : A_MOVSD; + ins2(c, mov, + amem(base_reg, base_disp + (int)f->offset), + areg(D_X0)); + break; + } int fsz = (int)(f->type ? f->type->size : 8); int signed_field = f->type && ( f->type->kind == TY_I8 || @@ -2822,6 +2937,17 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_MOVQ, areg(D_CX), areg(D_BX)); break; } + /* f64/f32 field via *struct: load into X0. + * BX already holds the struct pointer from + * the MOVQ amem(D_BP,off) above. */ + int f_isf32 = 0; + if (fld_isfloat(f->type, &f_isf32)) { + int mov = f_isf32 ? A_MOVSS : A_MOVSD; + ins2(c, mov, + amem(D_BX, (int)f->offset), + areg(D_X0)); + break; + } int fsz = (int)(f->type ? f->type->size : 8); int signed_field = f->type && ( f->type->kind == TY_I8 || @@ -2867,6 +2993,15 @@ cgexpr(Cg *c, Node *n, Local *locals) areg(D_AX)); goto dot_done; } + /* f64/f32 chained field: read into X0. */ + int g_isf32 = 0; + if (fld_isfloat(ft, &g_isf32)) { + int mov = g_isf32 ? A_MOVSS : A_MOVSD; + ins2(c, mov, + amem(D_AX, (int)f->offset), + areg(D_X0)); + goto dot_done; + } int fsz = (int)(ft ? ft->size : 8); int signed_field = ft && ( ft->kind == TY_I8 || @@ -3291,6 +3426,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) continue; } cgexpr(c, f->lhs, *locals); + int sl_isf32 = 0; + if (fld_isfloat(ft, &sl_isf32)) { + int mov = sl_isf32 ? A_MOVSS : A_MOVSD; + ins2(c, mov, areg(D_X0), + amem(D_BP, off + (int)foff)); + continue; + } int op = A_MOVQ; if (fsz == 1) op = A_MOVB; else if (fsz == 4) op = A_MOVL; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 7eb74bbf..52f4b729 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 34198b81..e48cda92 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -452,13 +452,26 @@ fn cglet(c: *cgen, n: *node) void = { let fn_: str = fi.fname; if (streq(fn_, fname)) { cgexpr(c, fieldnode.lhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((off + fi.foff): i64); - emitline("(BP)\n"); - fi = nil; + // f64/f32 struct-literal field init: cgexpr left + // the value in X0, store via MOVSD/MOVSS. + if (isfloattype(c, fi.tnode)) { + let mov: str = "MOVSD"; + if (isf32type(c, fi.tnode)) { mov = "MOVSS"; }; + emitline("\t"); + emitline(mov); + emitline("\tX0, "); + emitoff((off + fi.foff): i64); + emitline("(BP)\n"); + fi = nil; + } else { + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((off + fi.foff): i64); + emitline("(BP)\n"); + fi = nil; + }; } else { fi = fi.finext; };