diff --git a/Makefile b/Makefile index 2baec585..b4f6a4e6 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index d688d184..f9af05cc 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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 `.field = v` where evaluates to a *struct. * cgexpr on the inner expression already returns the pointer; * we then store at (ptr + field.offset). Without this, only the diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 260080f5..06cedb14 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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). diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index a48fb172..181a51d9 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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). diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 528e4899..20ef4805 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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). diff --git a/test/wcc/681_arr_elem_field_write.c b/test/wcc/681_arr_elem_field_write.c new file mode 100644 index 00000000..97644498 --- /dev/null +++ b/test/wcc/681_arr_elem_field_write.c @@ -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 +#include +#include +#include +#include +#include + +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; +}