From 1726bcef182424bb048bd4d42c9ba0e307fc4c67 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 14 May 2026 18:16:51 +0900 Subject: [PATCH] w6c+selfhost: cgen N_ASSIGN TY_STRUCT field branch (closes #25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parallel to TY_STR/TY_SLICE branches at cgen.c:1939/1962. Word-copy from src slot to field+k*8 via AX, MOVL/MOVB ragged tail. N_IDENT rhs only — struct-call-result and struct-literal rhs are different code paths, filed as task #27. Symmetric in N_ASSIGN field case AND spine-walker terminal; mirrored in wwstage cgassign four sub-shapes (*struct base, direct local, global, spine terminal). --- cmd/w6c/cgen.c | 84 +++++++++++ selfhost/cmd/w6c/main.combined.ww | 204 +++++++++++++++++++++++++++ selfhost/cmd/wcc/cgenexpr.ww | 204 +++++++++++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 204 +++++++++++++++++++++++++++ test/wcc/681_arr_elem_field_write.c | 147 +++++++++++++++++++ 5 files changed, 843 insertions(+) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 3af35649..c7b16b1b 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1978,6 +1978,43 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + /* struct-typed field, struct-ident rhs: cgexpr cannot + * materialise a whole struct value in registers, so + * word-copy from the rhs slot directly to the dest + * field. Other rhs shapes (struct-returning call, + * struct literal) stay broken at the cgexpr level — + * tracked as #27. */ + if (n->op == TK_ASSIGN && str_fu + && str_fu->kind == TY_STRUCT + && n->rhs && n->rhs->kind == N_IDENT + && localfind(locals, n->rhs->str) != 0) { + int soff = localfind(locals, n->rhs->str); + int ssz = (int)str_fu->size; + if (via_ptr) + ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX)); + else if (is_global) + ins2(c, A_LEAQ, masym(c, base->str), areg(D_BX)); + int k = 0; + while (k + 8 <= ssz) { + ins2(c, A_MOVQ, amem(D_BP, soff + k), areg(D_AX)); + if (via_ptr || is_global) + ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, foff + k)); + else + ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + k)); + k += 8; + } + if (k < ssz) { + int tail = ssz - k; + int lop = (tail == 4) ? A_MOVL + : (tail == 1 ? A_MOVB : A_MOVQ); + ins2(c, lop, amem(D_BP, soff + k), areg(D_AX)); + if (via_ptr || is_global) + ins2(c, lop, areg(D_AX), amem(D_BX, foff + k)); + else + ins2(c, lop, areg(D_AX), amem(D_BP, boff + foff + k)); + } + break; + } /* compound: load current value into BX */ if (n->op != TK_ASSIGN) { if (via_ptr) { @@ -2557,6 +2594,53 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + /* TY_STRUCT terminal: word-copy the rhs slot onto + * base+total_off. Only N_IDENT rhs is wired — + * other shapes route through #27. */ + if (fu && fu->kind == TY_STRUCT + && n->rhs && n->rhs->kind == N_IDENT + && localfind(locals, n->rhs->str) != 0) { + int soff = localfind(locals, n->rhs->str); + int ssz = fsz; + if (via_cx) { + if (ptr_root) + ins2(c, A_MOVQ, + amem(D_BP, base_disp), + areg(D_CX)); + else + ins2(c, A_LEAQ, + masym(c, cur->str), + areg(D_CX)); + } + int k = 0; + while (k + 8 <= ssz) { + ins2(c, A_MOVQ, + amem(D_BP, soff + k), + areg(D_AX)); + if (via_cx) + ins2(c, A_MOVQ, areg(D_AX), + amem(D_CX, total_off + k)); + else + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, base_disp + total_off + k)); + k += 8; + } + if (k < ssz) { + int tail = ssz - k; + int lop = (tail == 4) ? A_MOVL + : (tail == 1 ? A_MOVB : A_MOVQ); + ins2(c, lop, + amem(D_BP, soff + k), + areg(D_AX)); + if (via_cx) + ins2(c, lop, areg(D_AX), + amem(D_CX, total_off + k)); + else + ins2(c, lop, areg(D_AX), + amem(D_BP, base_disp + total_off + k)); + } + break; + } int sf32 = 0; if (fld_isfloat(leaf_type, &sf32)) { int mov = sf32 ? A_MOVSS : A_MOVSD; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0cb53acf..64dcea5c 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -11619,6 +11619,53 @@ fn cgassign(c: *cgen, n: *node) void = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { + // struct-typed field, struct-ident rhs through + // *struct base: cgexpr can't materialise a whole + // struct value, so word-copy from the rhs slot + // to *(struct_ptr + fi.foff). Other rhs shapes + // (call result, struct literal) tracked as #27. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + }; + return; + };}; + }; if (n.op != tkind.TK_ASSIGN) { // compound: load current value emitline("\tMOVQ\t"); @@ -11722,6 +11769,49 @@ fn cgassign(c: *cgen, n: *node) void = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { + // struct-typed field, struct-ident rhs: + // word-copy direct, skipping cgexpr (no + // register convention for a whole struct + // value). #27 covers non-ident rhs. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitoff((lc.off + fi.foff + k): i64); + emitline("(BP)\n"); + }; + return; + };}; + }; cgexpr(c, n.rhs); // str field: cgexpr left (AX=ptr, BX=len); // store both halves at +0/+8. Without this, @@ -11861,6 +11951,52 @@ fn cgassign(c: *cgen, n: *node) void = { let fi: *fieldinfo = si.fields; for (fi != nil) { if (streq(fi.fname, fld)) { + // struct-typed field, struct-ident rhs on global + // struct base: word-copy direct. Skips cgexpr — + // no register convention for a whole struct value. + // #27 covers non-ident rhs. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, bn); + emitline("(SB), BX\n"); + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + }; + return; + };}; + }; if (n.op == tkind.TK_ASSIGN) { cgexpr(c, n.rhs); if (isstrtype(c, fi.tnode)) { @@ -12105,6 +12241,74 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; + // TY_STRUCT terminal: word-copy the rhs slot onto + // base+totaloff. Only N_IDENT rhs is wired — other + // shapes (call result, struct literal) route through + // #27. cgexpr is skipped (no whole-struct register + // convention); reads come straight from the rhs slot. + if (n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && leaffi.tnode != nil + && leaffi.tnode.kind == nkind.N_TNAME + && primsize(leaffi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, leaffi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + if (viacx) { + if (ptrroot) { + emitline("\tMOVQ\t"); + emitoff(rootoff: i64); + emitline("(BP), CX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, rootname); + emitline("(SB), CX\n"); + }; + }; + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + if (viacx) { + emitline("\tMOVQ\tAX, "); + emitdispreg((totaloff + k): i64, "CX"); + emitline("\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((rootoff + totaloff + k): i64); + emitline("(BP)\n"); + }; + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + if (viacx) { + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((totaloff + k): i64, "CX"); + emitline("\n"); + } else { + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitoff((rootoff + totaloff + k): i64); + emitline("(BP)\n"); + }; + }; + return; + };}; + }; if (isfloattype(c, leaffi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index b5a131f5..4ee5f668 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -3571,6 +3571,53 @@ fn cgassign(c: *cgen, n: *node) void = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { + // struct-typed field, struct-ident rhs through + // *struct base: cgexpr can't materialise a whole + // struct value, so word-copy from the rhs slot + // to *(struct_ptr + fi.foff). Other rhs shapes + // (call result, struct literal) tracked as #27. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + }; + return; + };}; + }; if (n.op != tkind.TK_ASSIGN) { // compound: load current value emitline("\tMOVQ\t"); @@ -3674,6 +3721,49 @@ fn cgassign(c: *cgen, n: *node) void = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { + // struct-typed field, struct-ident rhs: + // word-copy direct, skipping cgexpr (no + // register convention for a whole struct + // value). #27 covers non-ident rhs. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitoff((lc.off + fi.foff + k): i64); + emitline("(BP)\n"); + }; + return; + };}; + }; cgexpr(c, n.rhs); // str field: cgexpr left (AX=ptr, BX=len); // store both halves at +0/+8. Without this, @@ -3813,6 +3903,52 @@ fn cgassign(c: *cgen, n: *node) void = { let fi: *fieldinfo = si.fields; for (fi != nil) { if (streq(fi.fname, fld)) { + // struct-typed field, struct-ident rhs on global + // struct base: word-copy direct. Skips cgexpr — + // no register convention for a whole struct value. + // #27 covers non-ident rhs. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, bn); + emitline("(SB), BX\n"); + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + }; + return; + };}; + }; if (n.op == tkind.TK_ASSIGN) { cgexpr(c, n.rhs); if (isstrtype(c, fi.tnode)) { @@ -4057,6 +4193,74 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; + // TY_STRUCT terminal: word-copy the rhs slot onto + // base+totaloff. Only N_IDENT rhs is wired — other + // shapes (call result, struct literal) route through + // #27. cgexpr is skipped (no whole-struct register + // convention); reads come straight from the rhs slot. + if (n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && leaffi.tnode != nil + && leaffi.tnode.kind == nkind.N_TNAME + && primsize(leaffi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, leaffi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + if (viacx) { + if (ptrroot) { + emitline("\tMOVQ\t"); + emitoff(rootoff: i64); + emitline("(BP), CX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, rootname); + emitline("(SB), CX\n"); + }; + }; + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + if (viacx) { + emitline("\tMOVQ\tAX, "); + emitdispreg((totaloff + k): i64, "CX"); + emitline("\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((rootoff + totaloff + k): i64); + emitline("(BP)\n"); + }; + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + if (viacx) { + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((totaloff + k): i64, "CX"); + emitline("\n"); + } else { + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitoff((rootoff + totaloff + k): i64); + emitline("(BP)\n"); + }; + }; + return; + };}; + }; if (isfloattype(c, leaffi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3dd22a92..927a6fe5 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -11619,6 +11619,53 @@ fn cgassign(c: *cgen, n: *node) void = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { + // struct-typed field, struct-ident rhs through + // *struct base: cgexpr can't materialise a whole + // struct value, so word-copy from the rhs slot + // to *(struct_ptr + fi.foff). Other rhs shapes + // (call result, struct literal) tracked as #27. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + }; + return; + };}; + }; if (n.op != tkind.TK_ASSIGN) { // compound: load current value emitline("\tMOVQ\t"); @@ -11722,6 +11769,49 @@ fn cgassign(c: *cgen, n: *node) void = { for (fi != nil) { let fn_: str = fi.fname; if (streq(fn_, fld)) { + // struct-typed field, struct-ident rhs: + // word-copy direct, skipping cgexpr (no + // register convention for a whole struct + // value). #27 covers non-ident rhs. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitoff((lc.off + fi.foff + k): i64); + emitline("(BP)\n"); + }; + return; + };}; + }; cgexpr(c, n.rhs); // str field: cgexpr left (AX=ptr, BX=len); // store both halves at +0/+8. Without this, @@ -11861,6 +11951,52 @@ fn cgassign(c: *cgen, n: *node) void = { let fi: *fieldinfo = si.fields; for (fi != nil) { if (streq(fi.fname, fld)) { + // struct-typed field, struct-ident rhs on global + // struct base: word-copy direct. Skips cgexpr — + // no register convention for a whole struct value. + // #27 covers non-ident rhs. + if (n.op == tkind.TK_ASSIGN + && n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && fi.tnode != nil + && fi.tnode.kind == nkind.N_TNAME + && primsize(fi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, fi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, bn); + emitline("(SB), BX\n"); + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((fi.foff + k): i64, "BX"); + emitline("\n"); + }; + return; + };}; + }; if (n.op == tkind.TK_ASSIGN) { cgexpr(c, n.rhs); if (isstrtype(c, fi.tnode)) { @@ -12105,6 +12241,74 @@ fn cgassign(c: *cgen, n: *node) void = { }; return; }; + // TY_STRUCT terminal: word-copy the rhs slot onto + // base+totaloff. Only N_IDENT rhs is wired — other + // shapes (call result, struct literal) route through + // #27. cgexpr is skipped (no whole-struct register + // convention); reads come straight from the rhs slot. + if (n.rhs != nil + && n.rhs.kind == nkind.N_IDENT + && leaffi.tnode != nil + && leaffi.tnode.kind == nkind.N_TNAME + && primsize(leaffi.tnode.str) == 0) { + let ssi: *structinfo = structlookup(c, leaffi.tnode.str); + let srhs: *local = localfindnode(c, n.rhs.str); + if (ssi != nil) { if (srhs != nil) { + if (viacx) { + if (ptrroot) { + emitline("\tMOVQ\t"); + emitoff(rootoff: i64); + emitline("(BP), CX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, rootname); + emitline("(SB), CX\n"); + }; + }; + let ssz: i32 = ssi.totsize; + let k: i32 = 0; + for (k + 8 <= ssz) { + emitline("\tMOVQ\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + if (viacx) { + emitline("\tMOVQ\tAX, "); + emitdispreg((totaloff + k): i64, "CX"); + emitline("\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((rootoff + totaloff + k): i64); + emitline("(BP)\n"); + }; + k += 8; + }; + if (k < ssz) { + let tail: i32 = ssz - k; + let lop: str = "MOVQ"; + if (tail == 4) { lop = "MOVL"; } + else { if (tail == 1) { lop = "MOVB"; }; }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitoff((srhs.off + k): i64); + emitline("(BP), AX\n"); + if (viacx) { + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitdispreg((totaloff + k): i64, "CX"); + emitline("\n"); + } else { + emitline("\t"); + emitline(lop); + emitline("\tAX, "); + emitoff((rootoff + totaloff + k): i64); + emitline("(BP)\n"); + }; + }; + return; + };}; + }; if (isfloattype(c, leaffi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; diff --git a/test/wcc/681_arr_elem_field_write.c b/test/wcc/681_arr_elem_field_write.c index 5a279a03..c53a6636 100644 --- a/test/wcc/681_arr_elem_field_write.c +++ b/test/wcc/681_arr_elem_field_write.c @@ -276,6 +276,153 @@ static const struct row rows[] = { " return 42;\n" "};\n", 42 }, + /* Whole-struct rhs to struct field (task #25). cgen N_ASSIGN had + * special-cases for TY_STR (2 words) and TY_SLICE (3 words) but no + * TY_STRUCT branch — the fallthrough fldstoreop wrote only AX, + * dropping every trailing word. Fixed by a word-copy branch that + * runs MOVQ from rhs slot to dest field, plus a 4/1-byte ragged + * tail. The 16B row is the BORDER case: same total size as TY_STR + * but must NOT take the str branch (str field has 16B header; this + * is a 2-word struct value, structurally identical but type- + * dispatched). Distinct byte values per word so a stray store at + * the wrong offset surfaces as a misvalue, not coincidental zero. */ + { "struct_field_value_write_16B", + "type inner = struct { a: i64, b: i64 };\n" + "type outer = struct { i: inner, mark: i32 };\n" + "fn main() i32 = {\n" + " let o: inner;\n" + " o.a = 0x1122334455667788i64;\n" + " o.b = 0x99aabbccddeeff00i64;\n" + " let x: outer;\n" + " x.mark = 0x55;\n" + " x.i = o;\n" + " if (x.i.a != 0x1122334455667788i64) { return 1; };\n" + " if (x.i.b != 0x99aabbccddeeff00i64) { return 2; };\n" + " if (x.mark != 0x55) { return 3; };\n" + " return 42;\n" + "};\n", + 42 }, + /* 24B struct rhs (the original #25 probe case). Three words at +0/+8/+16 + * exercise the word-copy loop with no ragged tail. Distinct byte + * patterns pin each word independently. */ + { "struct_field_value_write_24B", + "type inner = struct { a: i64, b: i64, c: i64 };\n" + "type outer = struct { i: inner, mark: i32 };\n" + "fn main() i32 = {\n" + " let o: inner;\n" + " o.a = 0x0a0a0a0a0a0a0a0ai64;\n" + " o.b = 0x1b1b1b1b1b1b1b1bi64;\n" + " o.c = 0x2c2c2c2c2c2c2c2ci64;\n" + " let x: outer;\n" + " x.mark = 7;\n" + " x.i = o;\n" + " if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n" + " if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n" + " if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n" + " if (x.mark != 7) { return 4; };\n" + " return 42;\n" + "};\n", + 42 }, + /* 32B struct rhs — four-word copy, no ragged tail. Trailing mark in + * the outer struct catches a stray fifth-word store. */ + { "struct_field_value_write_32B", + "type inner = struct { a: i64, b: i64, c: i64, d: i64 };\n" + "type outer = struct { i: inner, mark: i32 };\n" + "fn main() i32 = {\n" + " let o: inner;\n" + " o.a = 0x0a0a0a0a0a0a0a0ai64;\n" + " o.b = 0x1b1b1b1b1b1b1b1bi64;\n" + " o.c = 0x2c2c2c2c2c2c2c2ci64;\n" + " o.d = 0x3d3d3d3d3d3d3d3di64;\n" + " let x: outer;\n" + " x.mark = 0x33;\n" + " x.i = o;\n" + " if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n" + " if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n" + " if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n" + " if (x.i.d != 0x3d3d3d3d3d3d3d3di64) { return 4; };\n" + " if (x.mark != 0x33) { return 5; };\n" + " return 42;\n" + "};\n", + 42 }, + /* Nested struct field write through chained N_DOT spine + * (`b.inner.deep = other_deep`). Confirms #22's spine walker + * handles a TY_STRUCT terminal, not just scalar/str/slice. The + * 24B inner struct payload must survive both the spine walk and + * the word-copy. */ + { "struct_field_value_write_nested_chain", + "type deep = struct { a: i64, b: i64, c: i64 };\n" + "type mid = struct { d: deep, mark: i32 };\n" + "type outer = struct { m: mid, tag: i32 };\n" + "fn main() i32 = {\n" + " let other: deep;\n" + " other.a = 0x0a0a0a0a0a0a0a0ai64;\n" + " other.b = 0x1b1b1b1b1b1b1b1bi64;\n" + " other.c = 0x2c2c2c2c2c2c2c2ci64;\n" + " let v: outer;\n" + " v.m.mark = 5;\n" + " v.tag = 9;\n" + " v.m.d = other;\n" + " if (v.m.d.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n" + " if (v.m.d.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n" + " if (v.m.d.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n" + " if (v.m.mark != 5) { return 4; };\n" + " if (v.tag != 9) { return 5; };\n" + " return 42;\n" + "};\n", + 42 }, + /* Whole-struct rhs to a struct field via a *struct base — exercises + * the via_ptr arm of the new TY_STRUCT branch. Stages the dest addr + * in BX (MOVQ off(BP),BX) before iterating, then writes at fi.foff+k(BX). */ + { "struct_field_value_write_via_ptr", + "type inner = struct { a: i64, b: i64, c: i64 };\n" + "type outer = struct { i: inner, mark: i32 };\n" + "fn fill(x: *outer) void = {\n" + " let o: inner;\n" + " o.a = 0x0a0a0a0a0a0a0a0ai64;\n" + " o.b = 0x1b1b1b1b1b1b1b1bi64;\n" + " o.c = 0x2c2c2c2c2c2c2c2ci64;\n" + " x.mark = 7;\n" + " x.i = o;\n" + "};\n" + "fn main() i32 = {\n" + " let x: outer;\n" + " fill(&x);\n" + " if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n" + " if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n" + " if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n" + " if (x.mark != 7) { return 4; };\n" + " return 42;\n" + "};\n", + 42 }, + /* Ragged tail — 12B inner struct (3 i32 fields, maxalign=4, no 8B + * round-up) lands the word-copy loop at k=8 with 4 bytes left and + * pins the MOVL tail branch. Tail-only structs are reachable for + * any TY_STRUCT whose maxalign < 8: this is the smallest such + * shape. The trailing i32 mark in the outer struct catches a stray + * MOVQ tail (which would overwrite +4 of the mark slot). The 5/3/2 + * byte tails (struct of just i8 fields) fall back to MOVQ in cgen + * and would overwrite past the field boundary; the language permits + * align==1 structs but they're not exercised here — filed as a + * sub-followup if a real callsite surfaces. */ + { "struct_field_value_write_ragged_tail_12B", + "type inner = struct { a: i32, b: i32, c: i32 };\n" + "type outer = struct { i: inner, mark: i32 };\n" + "fn main() i32 = {\n" + " let o: inner;\n" + " o.a = 0x11223344;\n" + " o.b = 0x55667788;\n" + " o.c = 0x29aabbcc;\n" + " let x: outer;\n" + " x.mark = 0x33;\n" + " x.i = o;\n" + " if (x.i.a != 0x11223344) { return 1; };\n" + " if (x.i.b != 0x55667788) { return 2; };\n" + " if (x.i.c != 0x29aabbcc) { return 3; };\n" + " if (x.mark != 0x33) { return 4; };\n" + " return 42;\n" + "};\n", + 42 }, }; static int