w6c+selfhost: cgassign N_DOT N_INDEX-lhs branch (closes #16)

Symmetric write-side counterpart of #8, bundled across both stages.
cstage [N]Struct write was broken (1986 gated on TY_PTR); wwstage had
no N_DOT(N_INDEX) write branch at all. New branch covers both
[N]*Struct and [N]Struct via viaptr flag, uses fldstoreop for
scalar/sub-word, MOVSS/MOVSD for float, two-MOVQ for str rhs.
Compound (PLUSEQ etc.) wired for integer scalar.
This commit is contained in:
2026-05-14 00:09:46 +09:00
parent dd92938eff
commit 6354f5267c
6 changed files with 1177 additions and 0 deletions

View File

@@ -1976,6 +1976,262 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
}
/* `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
* write-side of the cgdot N_INDEX-lhs branch. Compute &arr[i]
* inline (LEAQ for `[N]Struct`, MOVQ-load for `[N]*Struct` /
* `[]Struct` / `*Struct`), deref once when the element is
* `*Struct`, then store rhs at `field.offset(addr)`. The
* chained-pointer-field branch below catches `[N]*Struct`
* writes via its `!= N_IDENT` guard, but `[N]Struct` value-arrays
* fall through and silently drop the store. Placed before the
* `!= N_IDENT` branch so both shapes share one path. */
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
&& n->lhs->lhs->kind == N_INDEX) {
Node *idxbase = n->lhs->lhs->lhs;
Node *idx = n->lhs->lhs->rhs;
if (idxbase && idxbase->kind == N_IDENT && idx) {
Type *elemt = n->lhs->lhs->type;
Type *elemu = (elemt && elemt->kind == TY_NAMED)
? elemt->under : elemt;
Type *struct_t = NULL;
int viaptr = 0;
if (elemu && elemu->kind == TY_PTR) {
Type *inner = elemu->sub;
if (inner && inner->kind == TY_NAMED)
inner = inner->under;
if (inner && inner->kind == TY_STRUCT) {
struct_t = inner;
viaptr = 1;
}
} else if (elemu && elemu->kind == TY_STRUCT) {
struct_t = elemu;
}
if (struct_t) {
Tfield *f = NULL;
for (Tfield *fl = struct_t->fields; fl;
fl = fl->next)
if (strcmp(fl->name,
n->lhs->str) == 0)
{ f = fl; break; }
Type *bt = idxbase->type;
Type *bu = (bt && bt->kind == TY_NAMED)
? bt->under : bt;
int is_arr = bu && bu->kind == TY_ARRAY;
int is_sl = bu && bu->kind == TY_SLICE;
int is_ptr = bu && bu->kind == TY_PTR;
int off = localfind(locals, idxbase->str);
if (f != NULL && (is_arr || is_sl || is_ptr)
&& off != 0) {
Type *ft = f->type;
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
int fsz = (int)(ft ? ft->size : 8);
int store_op = fldstoreop(ft, fsz);
int foff = (int)f->offset;
int esz = (int)elemt->size;
int h_isf32 = 0;
if (n->op == TK_ASSIGN
&& fld_isfloat(ft, &h_isf32)) {
int mov = h_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, idx, locals);
if (esz > 1) {
ins2(c, A_MOVQ,
aimm(esz),
areg(D_CX));
ins2(c, A_IMULQ,
areg(D_CX),
areg(D_AX));
}
if (is_arr)
ins2(c, A_LEAQ,
amem(D_BP, off),
areg(D_BX));
else
ins2(c, A_MOVQ,
amem(D_BP, off),
areg(D_BX));
ins2(c, A_ADDQ,
areg(D_AX),
areg(D_BX));
if (viaptr)
ins2(c, A_MOVQ,
amem(D_BX, 0),
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 (n->op == TK_ASSIGN
&& fu && fu->kind == TY_STR) {
/* str rhs: AX=ptr, BX=len.
* Stash both, compute addr
* in CX so the pop pair
* restores AX/BX cleanly. */
cgexpr(c, n->rhs, locals);
ins1(c, A_PUSHQ,
areg(D_BX));
ins1(c, A_PUSHQ,
areg(D_AX));
cgexpr(c, idx, locals);
if (esz > 1) {
ins2(c, A_MOVQ,
aimm(esz),
areg(D_CX));
ins2(c, A_IMULQ,
areg(D_CX),
areg(D_AX));
}
if (is_arr)
ins2(c, A_LEAQ,
amem(D_BP, off),
areg(D_CX));
else
ins2(c, A_MOVQ,
amem(D_BP, off),
areg(D_CX));
ins2(c, A_ADDQ,
areg(D_AX),
areg(D_CX));
if (viaptr)
ins2(c, A_MOVQ,
amem(D_CX, 0),
areg(D_CX));
ins1(c, A_POPQ,
areg(D_AX));
ins1(c, A_POPQ,
areg(D_BX));
ins2(c, A_MOVQ,
areg(D_AX),
amem(D_CX, foff + 0));
ins2(c, A_MOVQ,
areg(D_BX),
amem(D_CX, foff + 8));
break;
}
if (n->op == TK_ASSIGN) {
cgexpr(c, n->rhs, locals);
ins1(c, A_PUSHQ,
areg(D_AX));
cgexpr(c, idx, locals);
if (esz > 1) {
ins2(c, A_MOVQ,
aimm(esz),
areg(D_CX));
ins2(c, A_IMULQ,
areg(D_CX),
areg(D_AX));
}
if (is_arr)
ins2(c, A_LEAQ,
amem(D_BP, off),
areg(D_BX));
else
ins2(c, A_MOVQ,
amem(D_BP, off),
areg(D_BX));
ins2(c, A_ADDQ,
areg(D_AX),
areg(D_BX));
if (viaptr)
ins2(c, A_MOVQ,
amem(D_BX, 0),
areg(D_BX));
ins1(c, A_POPQ,
areg(D_AX));
ins2(c, store_op,
areg(D_AX),
amem(D_BX, foff));
break;
}
/* compound: rhs→push; compute
* struct addr→BX (deref if *T);
* push addr; load old field→AX;
* pop addr→BX, rhs→CX; combine;
* store. Float/str compound
* not wired. */
cgexpr(c, n->rhs, locals);
ins1(c, A_PUSHQ, areg(D_AX));
cgexpr(c, idx, locals);
if (esz > 1) {
ins2(c, A_MOVQ,
aimm(esz),
areg(D_CX));
ins2(c, A_IMULQ,
areg(D_CX),
areg(D_AX));
}
if (is_arr)
ins2(c, A_LEAQ,
amem(D_BP, off),
areg(D_BX));
else
ins2(c, A_MOVQ,
amem(D_BP, off),
areg(D_BX));
ins2(c, A_ADDQ, areg(D_AX),
areg(D_BX));
if (viaptr)
ins2(c, A_MOVQ,
amem(D_BX, 0),
areg(D_BX));
ins1(c, A_PUSHQ, areg(D_BX));
int load_op = fldloadop(ft, fsz);
ins2(c, load_op,
amem(D_BX, foff),
areg(D_AX));
ins1(c, A_POPQ, areg(D_BX));
ins1(c, A_POPQ, areg(D_CX));
switch (n->op) {
case TK_PLUSEQ:
ins2(c, A_ADDQ,
areg(D_CX),
areg(D_AX));
break;
case TK_MINUSEQ:
ins2(c, A_SUBQ,
areg(D_CX),
areg(D_AX));
break;
case TK_STAREQ:
ins2(c, A_IMULQ,
areg(D_CX),
areg(D_AX));
break;
case TK_AMPEQ:
ins2(c, A_ANDQ,
areg(D_CX),
areg(D_AX));
break;
case TK_PIPEEQ:
ins2(c, A_ORQ,
areg(D_CX),
areg(D_AX));
break;
case TK_CARETEQ:
ins2(c, A_XORQ,
areg(D_CX),
areg(D_AX));
break;
default: break;
}
ins2(c, store_op, areg(D_AX),
amem(D_BX, foff));
break;
}
}
}
}
/* Chained `<expr>.field = v` where <expr> evaluates to a *struct.
* cgexpr on the inner expression already returns the pointer;
* we then store at (ptr + field.offset). Without this, only the