From d66aef382ad9c2a4052071287966535ad81c8883 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 14 May 2026 02:44:57 +0900 Subject: [PATCH] w6c+selfhost: cgen *T-rooted chained N_DOT (closes #22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spine walker now accepts *T root at the last hop (cur->lhs->kind == N_IDENT, pu->kind == TY_PTR), substitutes pointee struct, emits MOVQ off(BP), CX before offset arithmetic. Symmetric in N_DOT and N_ASSIGN. Distinct gate from existing mid-chain *T-field branch (cgen.c:4615) — no shadow. Unblocks task #18 (bufio writer first-field embed). Pre-existing *T-field mid-chain path unchanged. --- cmd/w6c/cgen.c | 114 +++++++++++--- selfhost/cmd/w6c/main.combined.ww | 153 ++++++++++++++----- selfhost/cmd/wcc/cgenexpr.ww | 136 ++++++++++++----- selfhost/cmd/wcc/cgenutil.ww | 17 ++- selfhost/cmd/wwdump/main.combined.ww | 153 ++++++++++++++----- test/wcc/650_dot_chain.c | 213 +++++++++++++++++++++++++++ 6 files changed, 659 insertions(+), 127 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 8060de35..69ba728f 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2400,6 +2400,7 @@ cgexpr(Cg *c, Node *n, Local *locals) && n->lhs->lhs->kind == N_DOT && n->op == TK_ASSIGN) { struct { Type *pu; const char *name; } steps[16]; int nsteps = 0; + int ptr_root = 0; Node *cur = n->lhs; int abort = 0; while (cur && cur->kind == N_DOT && cur->lhs) { @@ -2410,7 +2411,23 @@ cgexpr(Cg *c, Node *n, Local *locals) if (cur == n->lhs && (pu->kind == TY_SLICE || pu->kind == TY_STR)) { /* leaf pseudo-field on slice/str header */ - } else if (pu->kind != TY_STRUCT) { + } else if (pu->kind == TY_STRUCT) { + /* value-struct hop */ + } else if (pu->kind == TY_PTR && pu->sub + && cur->lhs->kind == N_IDENT) { + /* `*T` root: dereference at emit time; + * walk through pointee struct fields. + * Last-hop only (root is a bare ident). */ + Type *sub = (pu->sub->kind == TY_NAMED) + ? pu->sub->under : pu->sub; + if (sub && sub->kind == TY_STRUCT) { + pu = sub; + ptr_root = 1; + } else { + abort = 1; + break; + } + } else { abort = 1; break; } @@ -2449,7 +2466,6 @@ cgexpr(Cg *c, Node *n, Local *locals) } if (ok) { int root_off = localfind(locals, cur->str); - int base_reg = D_BP; int base_disp = root_off; int is_global = 0; int root_resolved = (root_off != 0); @@ -2458,16 +2474,26 @@ cgexpr(Cg *c, Node *n, Local *locals) is_global = 1; } if (root_resolved) { + /* `*T` root and global both store via CX as + * the base register; only the loader differs + * (LEAQ name(SB) vs MOVQ off(BP)). Compute it + * AFTER cgexpr(rhs) so AX/BX/X0 stay intact. */ + int via_cx = is_global || ptr_root; if (slice_delta >= 0) { /* slice/str pseudo-field store. .ptr writes * 8 bytes; .len / .cap write 8 bytes each * (matches the existing N_IDENT pseudo- * field branch). */ cgexpr(c, n->rhs, locals); - if (is_global) { - ins2(c, A_LEAQ, - masym(c, cur->str), - areg(D_CX)); + 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)); ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, total_off + slice_delta)); } else { @@ -2484,10 +2510,15 @@ cgexpr(Cg *c, Node *n, Local *locals) int store_op = fldstoreop(leaf_type, fsz); if (fu && fu->kind == TY_STR) { cgexpr(c, n->rhs, locals); - if (is_global) { - ins2(c, A_LEAQ, - masym(c, cur->str), - areg(D_CX)); + 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)); ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, total_off + 0)); ins2(c, A_MOVQ, areg(D_BX), @@ -2504,10 +2535,15 @@ cgexpr(Cg *c, Node *n, Local *locals) if (fld_isfloat(leaf_type, &sf32)) { int mov = sf32 ? A_MOVSS : A_MOVSD; cgexpr(c, n->rhs, locals); - if (is_global) { - ins2(c, A_LEAQ, - masym(c, cur->str), - areg(D_CX)); + 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)); ins2(c, mov, areg(D_X0), amem(D_CX, total_off)); } else { @@ -2517,10 +2553,15 @@ cgexpr(Cg *c, Node *n, Local *locals) break; } cgexpr(c, n->rhs, locals); - if (is_global) { - ins2(c, A_LEAQ, - masym(c, cur->str), - areg(D_CX)); + 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)); ins2(c, store_op, areg(D_AX), amem(D_CX, total_off)); } else { @@ -4193,6 +4234,7 @@ cgexpr(Cg *c, Node *n, Local *locals) if (leaf_is_pseudo || leaf_in_struct) { struct { Type *pu; const char *name; } steps[16]; int nsteps = 0; + int ptr_root = 0; Node *cur = n; int abort = 0; while (cur && cur->kind == N_DOT && cur->lhs) { @@ -4203,7 +4245,26 @@ cgexpr(Cg *c, Node *n, Local *locals) if (cur == n && (pu->kind == TY_SLICE || pu->kind == TY_STR)) { /* leaf pseudo on slice/str header */ - } else if (pu->kind != TY_STRUCT) { + } else if (pu->kind == TY_STRUCT) { + /* value-struct hop */ + } else if (pu->kind == TY_PTR && pu->sub + && cur->lhs->kind == N_IDENT) { + /* `*T` root: dereference once at emit + * time, then walk offsets through the + * pointee. Only at the last hop (root + * is a bare ident) — `*T`-field mid- + * chain keeps its cgexpr-based pointer- + * field branch further down. */ + Type *sub = (pu->sub->kind == TY_NAMED) + ? pu->sub->under : pu->sub; + if (sub && sub->kind == TY_STRUCT) { + pu = sub; + ptr_root = 1; + } else { + abort = 1; + break; + } + } else { abort = 1; break; } @@ -4252,6 +4313,21 @@ cgexpr(Cg *c, Node *n, Local *locals) base_disp = 0; root_resolved = 1; } + if (root_resolved && ptr_root) { + /* `*T` root: load the pointer value + * once; field accesses then index at + * total_off off the pointer. */ + if (base_reg == D_BP) { + ins2(c, A_MOVQ, + amem(D_BP, base_disp), + areg(D_CX)); + } else { + ins2(c, A_MOVQ, + amem(D_CX, 0), areg(D_CX)); + } + base_reg = D_CX; + base_disp = 0; + } if (root_resolved) { if (slice_delta >= 0) { ins2(c, A_MOVQ, diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 83c55e73..e038597f 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -7934,10 +7934,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: export fn dotchainresolve(c: *cgen, n: *node, outrootname: *str, outrootoff: *i32, outtotaloff: *i32, outleaffi: **fieldinfo, outslicedelta: *i32, - outisglobal: *bool) bool = { + outisglobal: *bool, outptrroot: *bool) bool = { *outrootname = ""; *outrootoff = 0; *outisglobal = false; + *outptrroot = false; *outtotaloff = 0; *outleaffi = nil; *outslicedelta = -1; @@ -7965,6 +7966,20 @@ export fn dotchainresolve(c: *cgen, n: *node, rootstruct = lc.tnode.str; *outrootoff = lc.off; }; + // `*T` root (param/local): dereference at emit time; + // pointee struct supplies the field layout. Callers + // that opt in via *outptrroot emit a MOVQ load of the + // slot before indexing. + if (lc.tnode.kind == nkind.N_TPTR) { + let pe: *node = lc.tnode.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { + rootstruct = pe.str; + *outrootoff = lc.off; + *outptrroot = true; + }; + }; + }; }; }; if (rootstruct.len == 0) { @@ -9648,15 +9663,27 @@ fn cgdot(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let pok: bool = dotchainresolve(c, n, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, &ptrroot); if (pok) { + // `*T` root: load the pointer slot once into CX, + // then index every leaf at total_off off CX. Same + // emit shape as the global path (LEAQ → CX) — only + // the loader instruction differs. + let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\t"); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline(", AX\n"); @@ -9668,10 +9695,16 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; if (isstrtype(c, leaffi.tnode)) { - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\t"); emitdispreg(totaloff: i64, "CX"); emitline(", AX\n"); @@ -9691,10 +9724,16 @@ fn cgdot(c: *cgen, n: *node) void = { if (isfloattype(c, leaffi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(mov); emitline("\t"); @@ -9710,10 +9749,16 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; let lop: str = fieldloadop(c, leaffi); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(lop); emitline("\t"); @@ -10005,9 +10050,16 @@ fn cgun(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let pok: bool = dotchainresolve(c, opnd, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, + &ptrroot); + // `&` through a `*T`-rooted chain is a + // separate shape (would need MOVQ + LEAQ + // disp(CX), AX). Not exercised by current + // callers — skip and fall through. + if (ptrroot) { pok = false; }; if (pok) { let extra: i32 = 0; if (slicedelta >= 0) { extra = slicedelta; }; @@ -11957,16 +12009,27 @@ fn cgassign(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let yok: bool = dotchainresolve(c, lhs, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, &ptrroot); if (yok) { + // `*T` root and global share the CX-based emit: + // loader runs AFTER cgexpr(rhs) so AX/BX/X0 stay + // intact, then stores at total_off off CX. + let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\tAX, "); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline("\n"); @@ -11979,10 +12042,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; if (isstrtype(c, leaffi.tnode)) { cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\tAX, "); emitdispreg(totaloff: i64, "CX"); emitline("\n"); @@ -12003,10 +12072,16 @@ fn cgassign(c: *cgen, n: *node) void = { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(mov); emitline("\tX0, "); @@ -12023,10 +12098,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; let sop: str = fieldstoreop(c, leaffi); cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(sop); emitline("\tAX, "); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index e0bfb5cc..3ce6875d 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1615,15 +1615,27 @@ fn cgdot(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let pok: bool = dotchainresolve(c, n, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, &ptrroot); if (pok) { + // `*T` root: load the pointer slot once into CX, + // then index every leaf at total_off off CX. Same + // emit shape as the global path (LEAQ → CX) — only + // the loader instruction differs. + let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\t"); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline(", AX\n"); @@ -1635,10 +1647,16 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; if (isstrtype(c, leaffi.tnode)) { - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\t"); emitdispreg(totaloff: i64, "CX"); emitline(", AX\n"); @@ -1658,10 +1676,16 @@ fn cgdot(c: *cgen, n: *node) void = { if (isfloattype(c, leaffi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(mov); emitline("\t"); @@ -1677,10 +1701,16 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; let lop: str = fieldloadop(c, leaffi); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(lop); emitline("\t"); @@ -1972,9 +2002,16 @@ fn cgun(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let pok: bool = dotchainresolve(c, opnd, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, + &ptrroot); + // `&` through a `*T`-rooted chain is a + // separate shape (would need MOVQ + LEAQ + // disp(CX), AX). Not exercised by current + // callers — skip and fall through. + if (ptrroot) { pok = false; }; if (pok) { let extra: i32 = 0; if (slicedelta >= 0) { extra = slicedelta; }; @@ -3924,16 +3961,27 @@ fn cgassign(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let yok: bool = dotchainresolve(c, lhs, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, &ptrroot); if (yok) { + // `*T` root and global share the CX-based emit: + // loader runs AFTER cgexpr(rhs) so AX/BX/X0 stay + // intact, then stores at total_off off CX. + let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\tAX, "); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline("\n"); @@ -3946,10 +3994,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; if (isstrtype(c, leaffi.tnode)) { cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\tAX, "); emitdispreg(totaloff: i64, "CX"); emitline("\n"); @@ -3970,10 +4024,16 @@ fn cgassign(c: *cgen, n: *node) void = { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(mov); emitline("\tX0, "); @@ -3990,10 +4050,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; let sop: str = fieldstoreop(c, leaffi); cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(sop); emitline("\tAX, "); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index fa3c07ec..8153cbd0 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -2364,10 +2364,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: export fn dotchainresolve(c: *cgen, n: *node, outrootname: *str, outrootoff: *i32, outtotaloff: *i32, outleaffi: **fieldinfo, outslicedelta: *i32, - outisglobal: *bool) bool = { + outisglobal: *bool, outptrroot: *bool) bool = { *outrootname = ""; *outrootoff = 0; *outisglobal = false; + *outptrroot = false; *outtotaloff = 0; *outleaffi = nil; *outslicedelta = -1; @@ -2395,6 +2396,20 @@ export fn dotchainresolve(c: *cgen, n: *node, rootstruct = lc.tnode.str; *outrootoff = lc.off; }; + // `*T` root (param/local): dereference at emit time; + // pointee struct supplies the field layout. Callers + // that opt in via *outptrroot emit a MOVQ load of the + // slot before indexing. + if (lc.tnode.kind == nkind.N_TPTR) { + let pe: *node = lc.tnode.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { + rootstruct = pe.str; + *outrootoff = lc.off; + *outptrroot = true; + }; + }; + }; }; }; if (rootstruct.len == 0) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 98290cbf..d56d8183 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -7934,10 +7934,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz: export fn dotchainresolve(c: *cgen, n: *node, outrootname: *str, outrootoff: *i32, outtotaloff: *i32, outleaffi: **fieldinfo, outslicedelta: *i32, - outisglobal: *bool) bool = { + outisglobal: *bool, outptrroot: *bool) bool = { *outrootname = ""; *outrootoff = 0; *outisglobal = false; + *outptrroot = false; *outtotaloff = 0; *outleaffi = nil; *outslicedelta = -1; @@ -7965,6 +7966,20 @@ export fn dotchainresolve(c: *cgen, n: *node, rootstruct = lc.tnode.str; *outrootoff = lc.off; }; + // `*T` root (param/local): dereference at emit time; + // pointee struct supplies the field layout. Callers + // that opt in via *outptrroot emit a MOVQ load of the + // slot before indexing. + if (lc.tnode.kind == nkind.N_TPTR) { + let pe: *node = lc.tnode.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { + rootstruct = pe.str; + *outrootoff = lc.off; + *outptrroot = true; + }; + }; + }; }; }; if (rootstruct.len == 0) { @@ -9648,15 +9663,27 @@ fn cgdot(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let pok: bool = dotchainresolve(c, n, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, &ptrroot); if (pok) { + // `*T` root: load the pointer slot once into CX, + // then index every leaf at total_off off CX. Same + // emit shape as the global path (LEAQ → CX) — only + // the loader instruction differs. + let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\t"); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline(", AX\n"); @@ -9668,10 +9695,16 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; if (isstrtype(c, leaffi.tnode)) { - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\t"); emitdispreg(totaloff: i64, "CX"); emitline(", AX\n"); @@ -9691,10 +9724,16 @@ fn cgdot(c: *cgen, n: *node) void = { if (isfloattype(c, leaffi.tnode)) { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(mov); emitline("\t"); @@ -9710,10 +9749,16 @@ fn cgdot(c: *cgen, n: *node) void = { return; }; let lop: str = fieldloadop(c, leaffi); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(lop); emitline("\t"); @@ -10005,9 +10050,16 @@ fn cgun(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let pok: bool = dotchainresolve(c, opnd, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, + &ptrroot); + // `&` through a `*T`-rooted chain is a + // separate shape (would need MOVQ + LEAQ + // disp(CX), AX). Not exercised by current + // callers — skip and fall through. + if (ptrroot) { pok = false; }; if (pok) { let extra: i32 = 0; if (slicedelta >= 0) { extra = slicedelta; }; @@ -11957,16 +12009,27 @@ fn cgassign(c: *cgen, n: *node) void = { let leaffi: *fieldinfo = nil; let slicedelta: i32 = -1; let isglobal: bool = false; + let ptrroot: bool = false; let yok: bool = dotchainresolve(c, lhs, &rootname, &rootoff, &totaloff, - &leaffi, &slicedelta, &isglobal); + &leaffi, &slicedelta, &isglobal, &ptrroot); if (yok) { + // `*T` root and global share the CX-based emit: + // loader runs AFTER cgexpr(rhs) so AX/BX/X0 stay + // intact, then stores at total_off off CX. + let viacx: bool = isglobal || ptrroot; if (slicedelta >= 0) { cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\tAX, "); emitdispreg((totaloff + slicedelta): i64, "CX"); emitline("\n"); @@ -11979,10 +12042,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; if (isstrtype(c, leaffi.tnode)) { cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\tMOVQ\tAX, "); emitdispreg(totaloff: i64, "CX"); emitline("\n"); @@ -12003,10 +12072,16 @@ fn cgassign(c: *cgen, n: *node) void = { let mov: str = "MOVSD"; if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; }; cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(mov); emitline("\tX0, "); @@ -12023,10 +12098,16 @@ fn cgassign(c: *cgen, n: *node) void = { }; let sop: str = fieldstoreop(c, leaffi); cgexpr(c, n.rhs); - if (isglobal) { - emitline("\tLEAQ\t"); - emitsymname(c, rootname); - emitline("(SB), CX\n"); + 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"); + }; emitline("\t"); emitline(sop); emitline("\tAX, "); diff --git a/test/wcc/650_dot_chain.c b/test/wcc/650_dot_chain.c index f679fec5..79fe78d3 100644 --- a/test/wcc/650_dot_chain.c +++ b/test/wcc/650_dot_chain.c @@ -129,6 +129,219 @@ static const struct row rows[] = { " return g.i.a + g.x;\n" "};\n", 14 }, + /* `*T` parameter, single-dot read: `fn f(o: *outer) i32 = o.f`. + * Already worked via the existing pointer-to-struct-field + * branch; pinned here so future refactors don't regress it. */ + { "ptr_root_single_read", + "type outer = struct { f: i32, g: i32 };\n" + "fn get(o: *outer) i32 = { return o.f; };\n" + "fn main() i32 = {\n" + " let x: outer; x.f = 42; x.g = 0;\n" + " return get(&x);\n" + "};\n", + 42 }, + /* `*T` parameter, single-dot write: `fn f(o: *outer) void = …`. + * Already worked via the via_ptr branch in N_ASSIGN; pinned + * for the same reason. */ + { "ptr_root_single_write", + "type outer = struct { f: i32, g: i32 };\n" + "fn set(o: *outer) void = { o.f = 42; };\n" + "fn main() i32 = {\n" + " let x: outer; x.f = 0; x.g = 0;\n" + " set(&x);\n" + " return x.f;\n" + "};\n", + 42 }, + /* Drew's report shape — `*T` parameter, 2-deep read. + * Used to lower to `MOVQ a(SB), AX` (link-time undefined ref). + * Now the spine walker dereferences the root pointer. */ + { "ptr_root_2deep_read", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn get(o: *outer) i32 = { return o.i.a; };\n" + "fn main() i32 = {\n" + " let v: outer; v.i.a = 7; v.i.b = 0; v.x = 0;\n" + " return get(&v);\n" + "};\n", + 7 }, + /* `*T` parameter, 2-deep write — bufio.init shape. + * Previously silently dropped; now MOVQ off(BP), CX + + * store at total_off(CX). */ + { "ptr_root_2deep_write", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn set(o: *outer) void = { o.i.a = 5; };\n" + "fn main() i32 = {\n" + " let v: outer; v.i.a = 0; v.i.b = 0; v.x = 0;\n" + " set(&v);\n" + " return v.i.a;\n" + "};\n", + 5 }, + /* 3-deep `*T`-rooted: proves the spine walker loops past the + * pointer-root hop and isn't hardcoded at depth 2. */ + { "ptr_root_3deep", + "type a3 = struct { x: i32 };\n" + "type a2 = struct { a: a3 };\n" + "type a1 = struct { a: a2 };\n" + "fn put(p: *a1) void = { p.a.a.x = 9; };\n" + "fn get(p: *a1) i32 = { return p.a.a.x; };\n" + "fn main() i32 = {\n" + " let v: a1; v.a.a.x = 0;\n" + " put(&v);\n" + " return get(&v);\n" + "};\n", + 9 }, + /* `*T` *local* (not just param) — same emit path through + * localfind, but exercised separately for symmetry. */ + { "ptr_root_local", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn main() i32 = {\n" + " let v: outer; v.i.a = 0; v.i.b = 0; v.x = 0;\n" + " let p: *outer = &v;\n" + " p.i.a = 13;\n" + " return p.i.a;\n" + "};\n", + 13 }, + /* str leaf field through a `*T`-rooted chain — exercises the + * (AX=ptr, BX=len) convention through the CX-base load. */ + { "ptr_root_str_leaf", + "type holder = struct { s: str, pad: i32 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.s = \"abc\"; };\n" + "fn main() i32 = {\n" + " let b: box;\n" + " let empty: str; b.h.s = empty; b.h.pad = 0; b.tag = 0;\n" + " put(&b);\n" + " if (b.h.s.len == 3) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* slice pseudo-field on a `*T`-rooted chain. .len of an empty + * slice round-trips through the CX-base load. */ + { "ptr_root_slice_pseudo", + "type bag = struct { buf: []u8, x: i32 };\n" + "fn setlen(b: *bag, n: i32) void = { b.buf.len = n; };\n" + "fn main() i32 = {\n" + " let b: bag;\n" + " let nope: []u8; b.buf = nope; b.x = 0;\n" + " setlen(&b, 5);\n" + " return b.buf.len: i32;\n" + "};\n", + 5 }, + /* f64 leaf field through a `*T`-rooted chain — exercises the + * X0 spill / MOVSD store path off the CX base. */ + { "ptr_root_f64_leaf", + "type holder = struct { v: f64, pad: i32 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = 0.5f64; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0.0f64; b.h.pad = 0; b.tag = 0;\n" + " put(&b);\n" + " if (b.h.v == 0.5f64) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Sub-word leaf widths through a `*T`-rooted chain — pins + * fldloadop / fldstoreop on the CX-base path. u8/i8/u16/i16/i32 + * all stored then read back through the inner pointer. */ + { "ptr_root_subword_u8", + "type holder = struct { v: u8, pad: u8 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = 99u8; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0u8; b.h.pad = 0u8; b.tag = 0;\n" + " put(&b);\n" + " return b.h.v: i32;\n" + "};\n", + 99 }, + { "ptr_root_subword_i32", + "type holder = struct { v: i32, pad: i32 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = 0x12345; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0; b.h.pad = 0; b.tag = 0;\n" + " put(&b);\n" + " if (b.h.v == 0x12345) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + { "ptr_root_subword_i16", + "type holder = struct { v: i16, pad: i16 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = 4321i16; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0i16; b.h.pad = 0i16; b.tag = 0;\n" + " put(&b);\n" + " if (b.h.v == 4321i16) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Negative signed leaf widths through a `*T`-rooted chain. + * Stored value's sign bit must round-trip — pins localloadop / + * fldloadop sign-extension on the CX-base path so `*T`-rooted + * chains don't silently widen to a positive bit pattern. i8, i16 + * and i32 each store −7 and check `< 0` after the chained read. */ + { "ptr_root_subword_i8_neg", + "type holder = struct { v: i8, pad: i8 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = -7i8; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0i8; b.h.pad = 0i8; b.tag = 0;\n" + " put(&b);\n" + " let r: i32 = b.h.v: i32;\n" + " if (r == -7) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + { "ptr_root_subword_i16_neg", + "type holder = struct { v: i16, pad: i16 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = -7i16; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0i16; b.h.pad = 0i16; b.tag = 0;\n" + " put(&b);\n" + " let r: i32 = b.h.v: i32;\n" + " if (r == -7) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + { "ptr_root_subword_i32_neg", + "type holder = struct { v: i32, pad: i32 };\n" + "type box = struct { h: holder, tag: i32 };\n" + "fn put(b: *box) void = { b.h.v = -7; };\n" + "fn main() i32 = {\n" + " let b: box; b.h.v = 0; b.h.pad = 0; b.tag = 0;\n" + " put(&b);\n" + " if (b.h.v < 0) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, + /* Round-trip across the aliasing seam: caller writes via the + * local value, callee reads via the `*T`-rooted chain — and + * vice versa. Pins that both branches index the SAME slot + * (same field offsets resolved off the right base). */ + { "ptr_root_roundtrip_local_write_ptr_read", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn get(o: *outer) i32 = { return o.i.a; };\n" + "fn main() i32 = {\n" + " let o: outer; o.i.a = 0; o.i.b = 0; o.x = 0;\n" + " let p: *outer = &o;\n" + " o.i.a = 42;\n" + " return get(p);\n" + "};\n", + 42 }, + { "ptr_root_roundtrip_ptr_write_local_read", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn main() i32 = {\n" + " let o: outer; o.i.a = 0; o.i.b = 0; o.x = 0;\n" + " let p: *outer = &o;\n" + " p.i.a = 42;\n" + " return o.i.a;\n" + "};\n", + 42 }, }; static int