w6c+selfhost: localloadop helper for sign-aware ident loads (closes #19)
Read-side fix dual to fldloadop: signed-narrow local/global ident loads now MOVSXD/MOVSWQ/MOVSBQ from the slot instead of raw MOVQ. Deref-stores (MOVL/MOVW/MOVB) no longer corrupt downstream i64 widens. Compound RMW restructured to gate direct-mem ADDQ/SUBQ on load_op == MOVQ. Top-level lets use LEAQ+indirect (w6a doesn't expose MOVSXD/MOVSWQ/MOVSBQ for D_EXTERN). dotchainresolve out-params restored to natural *i32 (workaround retired). selfhost/CLAUDE.md graduated.
This commit is contained in:
@@ -168,6 +168,24 @@ fldstoreop(Type *t, int sz)
|
||||
return A_MOVQ;
|
||||
}
|
||||
|
||||
/* localloadop — read instruction for a scalar local/let load. Same
|
||||
* dispatch as fldloadop, but keyed on the value's own type. Lets the
|
||||
* caller emit MOVSXD / MOVSWQ / MOVSBQ on a signed-narrow slot instead
|
||||
* of a raw MOVQ, so a slot that was last written by a narrow deref-
|
||||
* store (`*p: *i32 = v` lowers to MOVL, only 4B) reads back as a
|
||||
* properly-sign-extended i64. The natural N_ASSIGN / N_LET paths
|
||||
* already store the value as a sign-extended 8B word so a MOVQ read
|
||||
* accidentally works; deref-stores are the only path that touches
|
||||
* fewer bytes than MOVQ reads. Fixing the read makes the slot's
|
||||
* representation honest regardless of which store path wrote it. */
|
||||
static int
|
||||
localloadop(Type *t)
|
||||
{
|
||||
int sz = (t && t->size > 0) ? (int)t->size : 8;
|
||||
if (sz != 1 && sz != 2 && sz != 4) return A_MOVQ;
|
||||
return fldloadop(t, sz);
|
||||
}
|
||||
|
||||
/* struct ≤16B all-INTEGER: 1 or 2 eightbyte regs.
|
||||
* Returns 0 if not a struct or too large. */
|
||||
static int
|
||||
@@ -1212,7 +1230,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_CX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
||||
ins2(c, localloadop(n->type),
|
||||
amem(D_BP, off), areg(D_AX));
|
||||
}
|
||||
} else {
|
||||
/* Non-local: function symbols load by address (LEAQ),
|
||||
@@ -1265,7 +1284,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, op, amem(D_CX, 0), areg(D_X0));
|
||||
goto ident_done;
|
||||
}
|
||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||
/* Top-level lets can be the target of `*p` deref-stores
|
||||
* (via `&letname: *iN`), so a signed-narrow scalar let
|
||||
* needs MOVSXD/MOVSWQ/MOVSBQ on the read. Defs are
|
||||
* read-only constants — their address cannot escape,
|
||||
* so they keep the simpler MOVQ shape (and the wwstage
|
||||
* defent registry, which doesn't track the declared
|
||||
* type, agrees byte-for-byte). */
|
||||
int gop = let_islet(n->str)
|
||||
? localloadop(n->type) : A_MOVQ;
|
||||
if (gop == A_MOVQ) {
|
||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||
} else {
|
||||
/* w6a has no MOVSXD/MOVSWQ/MOVSBQ D_EXTERN
|
||||
* source form, so route through a LEAQ scratch
|
||||
* the same way top-level str/slice/float lets
|
||||
* do. */
|
||||
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
||||
ins2(c, gop, amem(D_CX, 0), areg(D_AX));
|
||||
}
|
||||
}
|
||||
ident_done:
|
||||
break;
|
||||
@@ -2853,9 +2890,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* Compound: BX = load; combine with AX; store
|
||||
* BX. The asm has no RIP-relative ADDQ/SUBQ
|
||||
* mem-form, so we use the explicit load→
|
||||
* combine→store sequence uniformly. */
|
||||
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
* combine→store sequence uniformly. Narrow
|
||||
* lets go through LEAQ + indirect load with
|
||||
* localloadop so a prior `*(&letname): *iN`
|
||||
* deref-store doesn't leave stale upper bytes
|
||||
* in the read. */
|
||||
int glop = localloadop(n->lhs->type);
|
||||
if (glop == A_MOVQ) {
|
||||
ins2(c, A_MOVQ, masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
} else {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
ins2(c, glop, amem(D_CX, 0),
|
||||
areg(D_BX));
|
||||
}
|
||||
int did_compound = 1;
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
|
||||
@@ -2894,17 +2943,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* Compound: load → combine into BX → store. The two
|
||||
* direct mem-form combines (ADDQ/SUBQ) are kept for
|
||||
* the simple cases; the rest go through the generic
|
||||
* register form. */
|
||||
if (n->op == TK_PLUSEQ) {
|
||||
* register form. Signed-narrow slots take the explicit
|
||||
* load-combine-store path so the load can sign-extend
|
||||
* through localloadop — ADDQ/SUBQ on amem would read
|
||||
* the raw 8B, which is wrong when the slot was last
|
||||
* written by a 4B deref-store. */
|
||||
int lop = localloadop(n->lhs->type);
|
||||
if (lop == A_MOVQ && n->op == TK_PLUSEQ) {
|
||||
ins2(c, A_ADDQ, areg(D_AX), amem(D_BP, off));
|
||||
break;
|
||||
}
|
||||
if (n->op == TK_MINUSEQ) {
|
||||
if (lop == A_MOVQ && n->op == TK_MINUSEQ) {
|
||||
ins2(c, A_SUBQ, areg(D_AX), amem(D_BP, off));
|
||||
break;
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
||||
ins2(c, lop, amem(D_BP, off), areg(D_BX));
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ: ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); break;
|
||||
case TK_MINUSEQ: ins2(c, A_SUBQ, areg(D_AX), areg(D_BX)); break;
|
||||
case TK_STAREQ: ins2(c, A_IMULQ, areg(D_AX), areg(D_BX)); break;
|
||||
case TK_AMPEQ: ins2(c, A_ANDQ, areg(D_AX), areg(D_BX)); break;
|
||||
case TK_PIPEEQ: ins2(c, A_ORQ, areg(D_AX), areg(D_BX)); break;
|
||||
@@ -4100,7 +4156,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
areg(D_BX));
|
||||
goto dot_done;
|
||||
}
|
||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||
/* Same gating as the bare-ident catch-all: lets route
|
||||
* through localloadop (their slot can be the target of
|
||||
* a narrow deref-store via `&letname: *iN`); defs and
|
||||
* unresolved symbols stay on MOVQ so wwstage's defent-
|
||||
* registry-without-tnode shape agrees byte-for-byte. */
|
||||
int mqop = let_islet(n->str)
|
||||
? localloadop(n->type) : A_MOVQ;
|
||||
if (mqop == A_MOVQ) {
|
||||
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
|
||||
} else {
|
||||
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
||||
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
|
||||
}
|
||||
goto dot_done;
|
||||
}
|
||||
/* Chained N_DOT spine through value-struct fields. Handles any
|
||||
|
||||
Reference in New Issue
Block a user