diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index cfb23d2e..bcc5c507 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1160,29 +1160,49 @@ cgexpr(Cg *c, Node *n, Local *locals) if (f == NULL) break; /* Tagged-union field: synthesise tag and store * value bytes. Compound ops on tagged fields are - * not meaningful, so only plain `=` is wired. */ + * not meaningful, so only plain `=` is wired. + * Three base shapes: + * - via_ptr: base is *struct local; address + * pre-loaded into BX. Buggy with a str + * variant since cgexpr will overwrite BX, + * but matches the existing pre-global + * behaviour. + * - is_global: struct global. LEAQ after + * cgexpr drops the slot address into CX + * without touching AX/BX, so str variants + * work cleanly. + * - else: struct local, BP-relative. */ Type *ft = f->type; Type *fu = (ft && ft->kind == TY_NAMED) ? ft->under : ft; if (fu && fu->kind == TY_TAGGED && n->op == TK_ASSIGN) { int boff = localfind(locals, base->str); + int is_global = (boff == 0 && !via_ptr + && let_islet(base->str)); int foff = (int)f->offset; Type *vt = n->rhs ? n->rhs->type : NULL; int tag = cg_tag_for_variant(fu, vt); - /* Resolve the slot's base address - * (either struct local or *struct). */ int addr_in_bx = via_ptr; if (via_ptr) ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX)); cgexpr(c, n->rhs, locals); + if (is_global) + ins2(c, A_LEAQ, + masym(c, base->str), + areg(D_CX)); if (type_isstr(vt)) { if (addr_in_bx) { ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, foff + 8)); ins2(c, A_MOVQ, areg(D_BX), amem(D_BX, foff + 16)); + } else if (is_global) { + ins2(c, A_MOVQ, areg(D_AX), + amem(D_CX, foff + 8)); + ins2(c, A_MOVQ, areg(D_BX), + amem(D_CX, foff + 16)); } else { ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 8)); @@ -1193,6 +1213,9 @@ cgexpr(Cg *c, Node *n, Local *locals) if (addr_in_bx) ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, foff + 8)); + else if (is_global) + ins2(c, A_MOVQ, areg(D_AX), + amem(D_CX, foff + 8)); else ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 8)); @@ -1201,6 +1224,10 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BX, foff + 0)); + else if (is_global) + ins2(c, A_MOVQ, + aimm(tag < 0 ? 0 : tag), + amem(D_CX, foff + 0)); else ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), @@ -2636,9 +2663,28 @@ cgexpr(Cg *c, Node *n, Local *locals) base_reg = D_CX; base_disp = 0; } - (void)is_global; for (Tfield *f = u->fields; f; f = f->next) { if (strcmp(f->name, n->str) != 0) continue; + /* tagged-union field: load AX=tag, DX=val0, + * CX=val1 (last, since for globals CX is also + * the base addr). Mirrors the tagged-return + * ABI so the let-init / match dispatch shapes + * just work. */ + Type *tag_fu = (f->type && f->type->kind == TY_NAMED) + ? f->type->under : f->type; + if (tag_fu && tag_fu->kind == TY_TAGGED) { + int fo = base_disp + (int)f->offset; + ins2(c, A_MOVQ, + amem(base_reg, fo + 0), areg(D_AX)); + ins2(c, A_MOVQ, + amem(base_reg, fo + 8), areg(D_DX)); + if (tag_fu->size > 16) + ins2(c, A_MOVQ, + amem(base_reg, fo + 16), + areg(D_CX)); + (void)is_global; + break; + } /* str field: load (ptr, len) into (AX, BX) so the * value flows through the str-rhs convention. */ Type *str_fu = (f->type && f->type->kind == TY_NAMED) diff --git a/test/wcc/630_let_global.c b/test/wcc/630_let_global.c index 6397d9fc..e2812d01 100644 --- a/test/wcc/630_let_global.c +++ b/test/wcc/630_let_global.c @@ -232,6 +232,61 @@ static const struct fixture fixtures[] = { "};\n", 11, }, + { + "struct-tagged-field-i64", + /* Tagged-union field on a struct global. Write goes + * LEAQ name(SB),CX + MOVQ to slot+foff+8 (value) and + * slot+foff+0 (tag). Read pulls AX=tag, DX=val0, + * CX=val1 from those same slots and match dispatches. */ + "type tok = (i64 | str | void);\n" + "type ent = struct { id: i32, t: tok };\n" + "let e: ent;\n" + "fn main() i32 = {\n" + "\te.t = 42i64: tok;\n" + "\tlet v: tok = e.t;\n" + "\tmatch (v) {\n" + "\tcase let n: i64 => return n: i32;\n" + "\tcase let s: str => return 1;\n" + "\tcase void => return 2;\n" + "\t};\n" + "\treturn 0;\n" + "};\n", + 42, + }, + { + "struct-tagged-field-str", + "type tok = (i64 | str | void);\n" + "type ent = struct { id: i32, t: tok };\n" + "let e: ent;\n" + "fn main() i32 = {\n" + "\te.t = \"hello\";\n" + "\tlet v: tok = e.t;\n" + "\tmatch (v) {\n" + "\tcase let n: i64 => return 1;\n" + "\tcase let s: str => return s.len: i32;\n" + "\tcase void => return 2;\n" + "\t};\n" + "\treturn 0;\n" + "};\n", + 5, + }, + { + "struct-tagged-field-void", + "type tok = (i64 | str | void);\n" + "type ent = struct { id: i32, t: tok };\n" + "let e: ent;\n" + "fn main() i32 = {\n" + "\te.t = void;\n" + "\tlet v: tok = e.t;\n" + "\tmatch (v) {\n" + "\tcase let n: i64 => return 1;\n" + "\tcase let s: str => return 2;\n" + "\tcase void => return 7;\n" + "\t};\n" + "\treturn 0;\n" + "};\n", + 7, + }, { "struct-narrow-field", /* u8 field on a struct global. Read uses MOVZBQ, store