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

@@ -218,6 +218,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_at_test $(BIN)/test_let_global \
$(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \
$(BIN)/test_amp_dot $(BIN)/test_arr_elem_field \
$(BIN)/test_arr_elem_field_write \
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
@@ -303,6 +304,12 @@ $(BIN)/test_arr_elem_field: test/wcc/680_arr_elem_field.c $(BIN)/ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_arr_elem_field_write: test/wcc/681_arr_elem_field_write.c $(BIN)/ww \
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_field_signed: test/wcc/660_field_signed.c $(BIN)/ww \
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \

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

View File

@@ -11289,6 +11289,216 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
// write-side of the cgdot N_INDEX-lhs branch added for task #8.
// Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for
// `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the
// element is `*Struct`, then store rhs at field.offset(addr).
// Without this both shapes silently drop the store — there is no
// existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the
// N_INDEX-lhs branch above handles bare `arr[i] = v`, not the
// field write).
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_INDEX) {
let idxbase: *node = lhs.lhs.lhs;
let idx: *node = lhs.lhs.rhs;
let fld2: str = lhs.str;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
if (idx != nil) {
let lc: *local = localfindnode(c, idxbase.str);
if (lc != nil) { if (lc.tnode != nil) {
let tn: *node = lc.tnode;
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
let sname: str;
sname.ptr = nil; sname.len = 0;
let viaptr: bool = false;
if (elemt != nil) {
if (elemt.kind == nkind.N_TPTR) {
let inner: *node = elemt.lhs;
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
sname = inner.str;
viaptr = true;
};};
} else { if (elemt.kind == nkind.N_TNAME) {
sname = elemt.str;
};};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld2)) {
let esz: i32 = elemsizeofc(c, tn);
// f64/f32: rhs in X0. Spill to stack,
// compute &arr[i] in BX (deref if *T),
// then reload X0 and MOVSD/MOVSS.
if (n.op == tkind.TK_ASSIGN) {
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, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), 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;
};
// str rhs: AX=ptr, BX=len. Stash both,
// compute addr in CX so the pop pair
// restores AX/BX intact.
if (isstrtype(c, fi.tnode)) {
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), CX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), CX\n");
};
emitline("\tADDQ\tAX, CX\n");
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
emitline("\tPOPQ\tAX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "CX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "CX");
emitline("\n");
return;
};
// scalar plain `=`
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
emitline("\tPOPQ\tAX\n");
let sop: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
// 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);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
emitline("\tPUSHQ\tBX\n");
let lop: str = fieldloadop(c, fi);
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tCX\n");
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
let sop2: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop2);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
fi = fi.finext;
};
};
};
};};
};
};};
};
};
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
// `p.f = expr;`. Only plain `=` is wired (compound on field
// is rare and not yet needed by our fixtures).

View File

@@ -3246,6 +3246,216 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
// write-side of the cgdot N_INDEX-lhs branch added for task #8.
// Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for
// `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the
// element is `*Struct`, then store rhs at field.offset(addr).
// Without this both shapes silently drop the store — there is no
// existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the
// N_INDEX-lhs branch above handles bare `arr[i] = v`, not the
// field write).
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_INDEX) {
let idxbase: *node = lhs.lhs.lhs;
let idx: *node = lhs.lhs.rhs;
let fld2: str = lhs.str;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
if (idx != nil) {
let lc: *local = localfindnode(c, idxbase.str);
if (lc != nil) { if (lc.tnode != nil) {
let tn: *node = lc.tnode;
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
let sname: str;
sname.ptr = nil; sname.len = 0;
let viaptr: bool = false;
if (elemt != nil) {
if (elemt.kind == nkind.N_TPTR) {
let inner: *node = elemt.lhs;
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
sname = inner.str;
viaptr = true;
};};
} else { if (elemt.kind == nkind.N_TNAME) {
sname = elemt.str;
};};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld2)) {
let esz: i32 = elemsizeofc(c, tn);
// f64/f32: rhs in X0. Spill to stack,
// compute &arr[i] in BX (deref if *T),
// then reload X0 and MOVSD/MOVSS.
if (n.op == tkind.TK_ASSIGN) {
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, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), 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;
};
// str rhs: AX=ptr, BX=len. Stash both,
// compute addr in CX so the pop pair
// restores AX/BX intact.
if (isstrtype(c, fi.tnode)) {
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), CX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), CX\n");
};
emitline("\tADDQ\tAX, CX\n");
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
emitline("\tPOPQ\tAX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "CX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "CX");
emitline("\n");
return;
};
// scalar plain `=`
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
emitline("\tPOPQ\tAX\n");
let sop: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
// 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);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
emitline("\tPUSHQ\tBX\n");
let lop: str = fieldloadop(c, fi);
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tCX\n");
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
let sop2: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop2);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
fi = fi.finext;
};
};
};
};};
};
};};
};
};
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
// `p.f = expr;`. Only plain `=` is wired (compound on field
// is rare and not yet needed by our fixtures).

View File

@@ -11289,6 +11289,216 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
// write-side of the cgdot N_INDEX-lhs branch added for task #8.
// Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for
// `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the
// element is `*Struct`, then store rhs at field.offset(addr).
// Without this both shapes silently drop the store — there is no
// existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the
// N_INDEX-lhs branch above handles bare `arr[i] = v`, not the
// field write).
if (lhs != nil) {
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
&& lhs.lhs.kind == nkind.N_INDEX) {
let idxbase: *node = lhs.lhs.lhs;
let idx: *node = lhs.lhs.rhs;
let fld2: str = lhs.str;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
if (idx != nil) {
let lc: *local = localfindnode(c, idxbase.str);
if (lc != nil) { if (lc.tnode != nil) {
let tn: *node = lc.tnode;
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
let sname: str;
sname.ptr = nil; sname.len = 0;
let viaptr: bool = false;
if (elemt != nil) {
if (elemt.kind == nkind.N_TPTR) {
let inner: *node = elemt.lhs;
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
sname = inner.str;
viaptr = true;
};};
} else { if (elemt.kind == nkind.N_TNAME) {
sname = elemt.str;
};};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld2)) {
let esz: i32 = elemsizeofc(c, tn);
// f64/f32: rhs in X0. Spill to stack,
// compute &arr[i] in BX (deref if *T),
// then reload X0 and MOVSD/MOVSS.
if (n.op == tkind.TK_ASSIGN) {
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, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), 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;
};
// str rhs: AX=ptr, BX=len. Stash both,
// compute addr in CX so the pop pair
// restores AX/BX intact.
if (isstrtype(c, fi.tnode)) {
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), CX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), CX\n");
};
emitline("\tADDQ\tAX, CX\n");
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
emitline("\tPOPQ\tAX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "CX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "CX");
emitline("\n");
return;
};
// scalar plain `=`
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
emitline("\tPOPQ\tAX\n");
let sop: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
// 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);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
emitline("\tPUSHQ\tBX\n");
let lop: str = fieldloadop(c, fi);
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tPOPQ\tBX\n");
emitline("\tPOPQ\tCX\n");
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
let sop2: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop2);
emitline("\tAX, ");
emitdispreg(fi.foff: i64, "BX");
emitline("\n");
return;
};
fi = fi.finext;
};
};
};
};};
};
};};
};
};
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
// `p.f = expr;`. Only plain `=` is wired (compound on field
// is rare and not yet needed by our fixtures).

View File

@@ -0,0 +1,284 @@
/*
* 681_arr_elem_field_write — `arr[i].field = v` (write-side counterpart
* of 680_arr_elem_field). Both stages had a silent store-drop:
*
* cstage's chained-pointer-field-write branch (cgen.c near 1986) caught
* `[N]*Struct` writes via its `!= N_IDENT` guard but skipped `[N]Struct`
* value-arrays (TY_STRUCT element fails the TY_PTR guard).
*
* wwstage had no N_DOT(N_INDEX,...) lhs branch at all in cgassign
* (cgenexpr.ww). Both shapes silently emitted no store.
*
* Closed in task #16 by mirroring task #8's read-side N_INDEX-lhs branch
* onto the write side (fldstoreop, str/float leaf coverage, deref-or-not
* for `*Struct` vs `Struct` element).
*
* Coverage — both array shapes, scalar/sub-word/str/float leaves, dyn
* idx, and read-modify-write compound (`arr[i].x += 5`). Cstage and
* wwstage on every fixture; wwstage gated on access(X_OK).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
/* [N]*Struct, i32 field write. Sole write into stk[0].x; read it
* back. Returns 42. */
{ "ptr_arr_i32_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.name = \"hi\"; v.x = 0;\n"
" let stk: [16]*nd; stk[0] = &v;\n"
" stk[0].x = 42;\n"
" return stk[0].x;\n"
"};\n",
42 },
/* [N]*Struct, u8 field write. Sub-word store via MOVB through
* fieldstoreop. Returns 9. */
{ "ptr_arr_u8_write",
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.tag = 0u8; v.pad = 0u8; v.x = 0;\n"
" let stk: [4]*nd; stk[0] = &v;\n"
" stk[0].tag = 9u8;\n"
" return stk[0].tag: i32;\n"
"};\n",
9 },
/* [N]*Struct, str field write — 16B store of both ptr+len.
* Returns len("hello") = 5. */
{ "ptr_arr_str_write",
"type nd = struct { a: i32, b: i32, name: str };\n"
"fn main() i32 = {\n"
" let v: nd; v.a = 0; v.b = 0; v.name = \"old\";\n"
" let stk: [16]*nd; stk[0] = &v;\n"
" stk[0].name = \"hello\";\n"
" return stk[0].name.len: i32;\n"
"};\n",
5 },
/* [N]Struct, i32 field write (value-array). The cstage chained-
* pointer-field branch's TY_PTR guard skips this; without the new
* value-array path the store silently drops. Returns 50. */
{ "val_arr_i32_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[2].x = 50;\n"
" return arr[2].x;\n"
"};\n",
50 },
/* [N]Struct, u8 field write — value-array sub-word store. Returns 7. */
{ "val_arr_u8_write",
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].tag = 7u8;\n"
" return arr[1].tag: i32;\n"
"};\n",
7 },
/* [N]Struct, str field write — value-array 16B store. Returns
* len("world") = 5. */
{ "val_arr_str_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].name = \"world\";\n"
" return arr[1].name.len: i32;\n"
"};\n",
5 },
/* Sub-word signed: write -3i8 then read back as i32. If MOVB
* stored the truncated low byte and the field's fldloadop sign-
* extends correctly, the i32 read returns -3. Returns 42 when
* the equality check passes, 0 otherwise. */
{ "ptr_arr_i8_signed_write",
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.tag = 0i8; v.pad = 0u8; v.x = 0;\n"
" let stk: [4]*nd; stk[0] = &v;\n"
" stk[0].tag = -3i8;\n"
" let t: i32 = stk[0].tag: i32;\n"
" if (t == -3) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Value-array sub-word signed: distinct from ptr_arr_i8_signed —
* this path takes LEAQ &arr[i] (no MOVQ-to-deref) and stores the
* truncated byte via fldstoreop MOVB. Read back via fldloadop
* MOVSBQ sign-extends to i32; -3 round-trips. Returns 42. */
{ "val_arr_i8_signed_write",
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].tag = -3i8;\n"
" let t: i32 = arr[1].tag: i32;\n"
" if (t == -3) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* f64 field write through value-array — pins MOVSD from X0 into
* field offset of &arr[i]. Returns 23. */
{ "val_arr_f64_write",
"type nd = struct { x: i32, d: f64 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].d = 23.0f64;\n"
" let v: f64 = arr[1].d;\n"
" return v: i32;\n"
"};\n",
23 },
/* Dyn idx through [N]*Struct write — index isn't a literal, so
* the IMULQ path fires. Returns 99. */
{ "ptr_arr_dyn_idx_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let a: nd; a.name = \"a\"; a.x = 0;\n"
" let b: nd; b.name = \"b\"; b.x = 0;\n"
" let c: nd; c.name = \"c\"; c.x = 0;\n"
" let stk: [4]*nd;\n"
" stk[0] = &a; stk[1] = &b; stk[2] = &c;\n"
" let i: i32 = 2;\n"
" stk[i].x = 99;\n"
" return stk[2].x;\n"
"};\n",
99 },
/* Read-modify-write compound on [N]Struct — exercises BOTH the
* read (load old field) and the write (store combined). For
* `arr[1].x += 5` with arr[1].x = 37 → 42. */
{ "val_arr_compound_plus",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].x = 37;\n"
" arr[1].x += 5;\n"
" return arr[1].x;\n"
"};\n",
42 },
/* Non-PLUSEQ compound (CARETEQ) on [N]Struct — exercises the XORQ
* arm of the compound switch. Worker wired all six integer
* compound ops; this pins one of the non-PLUSEQ arms so a future
* regression in the switch table is caught. 0x2A ^ 0x14 = 0x3E
* (62), then return 62 - 20 = 42. */
{ "val_arr_compound_xor",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[2].x = 42;\n"
" arr[2].x ^= 20;\n"
" return arr[2].x - 20;\n"
"};\n",
42 },
/* Compound through [N]*Struct — same shape but viaptr is true,
* so the address compute deref-loads (BX), and the load_op picks
* MOVSXD for i32. 10 += 22 → 32. */
{ "ptr_arr_compound_plus",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.name = \"a\"; v.x = 10;\n"
" let stk: [4]*nd; stk[0] = &v;\n"
" stk[0].x += 22;\n"
" return stk[0].x;\n"
"};\n",
32 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/waew_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
tmpdir, driver, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); rmdir(tmpdir);
return -1;
}
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
return got;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arr_elem_field_write: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arr_elem_field_write[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr,
"arr_elem_field_write: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arr_elem_field_write: %d/%d ok\n", total, total);
return 0;
}