w6c+selfhost: cgen chained N_DOT/N_ASSIGN spine walk

Loop-shaped spine walker for value-struct chains (o.i.a) and slice/str
pseudo-fields (s.buf.len), read+write, both stages. SB-fallback at the
catch-all preserved for unresolved module-qualified idents.

Follow-ups filed: tasks #7-#10 (wwstage >6-arg frame over-alloc, chained
array-elem field BX loss, & through chained DOT, signed sub-word field
loads zero-extend).
This commit is contained in:
2026-05-13 21:44:50 +09:00
parent 036b2c851f
commit 461a448d5f
7 changed files with 1593 additions and 1 deletions

View File

@@ -1943,6 +1943,153 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
}
}
/* Chained `<chain>.field = v` where <chain> spans value-struct
* dots ending at a root ident — `o.i.a = 10`, `v.a.b.c = …`.
* Also handles a slice/str pseudo-field leaf (`b.buf.len = 5`):
* spine walks down to the slice/str header, then the +0/+8/+16
* delta selects ptr/len/cap. Sibling of the chained-pointer-
* field branch above; without this the LHS is silently dropped
* (the existing 1-deep branch only fires for `ident.field = …`).
* Only plain `=` is wired — compound on a chained value-struct
* field is rare and stays unhandled. */
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
&& n->lhs->lhs->kind == N_DOT && n->op == TK_ASSIGN) {
struct { Type *pu; const char *name; } steps[16];
int nsteps = 0;
Node *cur = n->lhs;
int abort = 0;
while (cur && cur->kind == N_DOT && cur->lhs) {
Type *pt = cur->lhs->type;
Type *pu = (pt && pt->kind == TY_NAMED)
? pt->under : pt;
if (!pu) { abort = 1; break; }
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) {
abort = 1;
break;
}
if (nsteps >= 16) { abort = 1; break; }
steps[nsteps].pu = pu;
steps[nsteps].name = cur->str;
nsteps++;
cur = cur->lhs;
}
if (!abort && cur && cur->kind == N_IDENT
&& nsteps > 0) {
int total_off = 0;
Type *leaf_type = NULL;
int slice_delta = -1;
int ok = 1;
for (int i = nsteps - 1; i >= 0; i--) {
Type *pu = steps[i].pu;
if (pu->kind == TY_SLICE
|| pu->kind == TY_STR) {
if (strcmp(steps[i].name, "ptr") == 0)
slice_delta = 0;
else if (strcmp(steps[i].name, "len") == 0)
slice_delta = 8;
else if (strcmp(steps[i].name, "cap") == 0)
slice_delta = 16;
else { ok = 0; break; }
} else {
Tfield *f = NULL;
for (Tfield *fl = pu->fields; fl; fl = fl->next)
if (strcmp(fl->name, steps[i].name) == 0)
{ f = fl; break; }
if (!f) { ok = 0; break; }
total_off += (int)f->offset;
leaf_type = f->type;
}
}
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);
if (!root_resolved && let_islet(cur->str)) {
root_resolved = 1;
is_global = 1;
}
if (root_resolved) {
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));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_CX, total_off + slice_delta));
} else {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, base_disp + total_off + slice_delta));
}
break;
}
Type *fu = (leaf_type
&& leaf_type->kind == TY_NAMED)
? leaf_type->under : leaf_type;
int fsz = (int)(leaf_type
? leaf_type->size : 8);
int store_op = A_MOVQ;
if (fsz == 1) store_op = A_MOVB;
else if (fsz == 4) store_op = A_MOVL;
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));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_CX, total_off + 0));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_CX, total_off + 8));
} else {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, base_disp + total_off + 0));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, base_disp + total_off + 8));
}
break;
}
int sf32 = 0;
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));
ins2(c, mov, areg(D_X0),
amem(D_CX, total_off));
} else {
ins2(c, mov, areg(D_X0),
amem(D_BP, base_disp + total_off));
}
break;
}
cgexpr(c, n->rhs, locals);
if (is_global) {
ins2(c, A_LEAQ,
masym(c, cur->str),
areg(D_CX));
ins2(c, store_op, areg(D_AX),
amem(D_CX, total_off));
} else {
ins2(c, store_op, areg(D_AX),
amem(D_BP, base_disp + total_off));
}
break;
}
}
}
}
/* float assignment to a local or top-level global. Globals
* route through LEAQ+indirect (no D_EXTERN SSE in w6a).
* Compound (`acc += d` etc.) loads slot into X1, combines
@@ -3561,6 +3708,138 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, masym(c, n->str), areg(D_AX));
goto dot_done;
}
/* Chained N_DOT spine through value-struct fields. Handles any
* depth `root.f0.f1.…leaf` where every intermediate field is a
* value struct, plus the slice/str pseudo-field tail (`s.buf.len`)
* where the innermost field is a slice/str header. Walks inward
* collecting (parent_struct, field_name); reverses to sum field
* offsets; emits one load at (base + total_off). Placed BEFORE
* the slice/str pseudo-field branch so its else-arm (cgexpr lhs
* + shuffle BX→AX) doesn't mis-handle `b.buf.len` — cgexpr on a
* value-struct→slice chain only loads .ptr into AX, leaving BX
* stale. Sibling of the pointer-chain branch further down. */
if (n->lhs && n->lhs->kind == N_DOT) {
Type *lt0 = n->lhs->type;
Type *lu0 = (lt0 && lt0->kind == TY_NAMED) ? lt0->under : lt0;
int leaf_is_pseudo = lu0 && n->str
&& (lu0->kind == TY_SLICE || lu0->kind == TY_STR)
&& (strcmp(n->str, "ptr") == 0
|| strcmp(n->str, "len") == 0
|| strcmp(n->str, "cap") == 0);
int leaf_in_struct = lu0 && lu0->kind == TY_STRUCT;
if (leaf_is_pseudo || leaf_in_struct) {
struct { Type *pu; const char *name; } steps[16];
int nsteps = 0;
Node *cur = n;
int abort = 0;
while (cur && cur->kind == N_DOT && cur->lhs) {
Type *pt = cur->lhs->type;
Type *pu = (pt && pt->kind == TY_NAMED)
? pt->under : pt;
if (!pu) { abort = 1; break; }
if (cur == n && (pu->kind == TY_SLICE
|| pu->kind == TY_STR)) {
/* leaf pseudo on slice/str header */
} else if (pu->kind != TY_STRUCT) {
abort = 1;
break;
}
if (nsteps >= 16) { abort = 1; break; }
steps[nsteps].pu = pu;
steps[nsteps].name = cur->str;
nsteps++;
cur = cur->lhs;
}
if (!abort && cur && cur->kind == N_IDENT
&& nsteps > 0) {
int total_off = 0;
Type *leaf_type = NULL;
int slice_delta = -1;
int ok = 1;
for (int i = nsteps - 1; i >= 0; i--) {
Type *pu = steps[i].pu;
if (pu->kind == TY_SLICE
|| pu->kind == TY_STR) {
if (strcmp(steps[i].name, "ptr") == 0)
slice_delta = 0;
else if (strcmp(steps[i].name, "len") == 0)
slice_delta = 8;
else if (strcmp(steps[i].name, "cap") == 0)
slice_delta = 16;
else { ok = 0; break; }
} else {
Tfield *f = NULL;
for (Tfield *fl = pu->fields; fl; fl = fl->next)
if (strcmp(fl->name, steps[i].name) == 0)
{ f = fl; break; }
if (!f) { ok = 0; break; }
total_off += (int)f->offset;
leaf_type = f->type;
}
}
if (ok) {
int root_off = localfind(locals, cur->str);
int base_reg = D_BP;
int base_disp = root_off;
int root_resolved = (root_off != 0);
if (!root_resolved && let_islet(cur->str)) {
ins2(c, A_LEAQ,
masym(c, cur->str), areg(D_CX));
base_reg = D_CX;
base_disp = 0;
root_resolved = 1;
}
if (root_resolved) {
if (slice_delta >= 0) {
ins2(c, A_MOVQ,
amem(base_reg,
base_disp + total_off + slice_delta),
areg(D_AX));
goto dot_done;
}
Type *fu = (leaf_type
&& leaf_type->kind == TY_NAMED)
? leaf_type->under : leaf_type;
if (fu && fu->kind == TY_STR) {
ins2(c, A_MOVQ,
amem(base_reg,
base_disp + total_off + 0),
areg(D_AX));
ins2(c, A_MOVQ,
amem(base_reg,
base_disp + total_off + 8),
areg(D_BX));
goto dot_done;
}
int g_isf32 = 0;
if (fld_isfloat(leaf_type, &g_isf32)) {
int mov = g_isf32 ? A_MOVSS : A_MOVSD;
ins2(c, mov,
amem(base_reg,
base_disp + total_off),
areg(D_X0));
goto dot_done;
}
int fsz = (int)(leaf_type
? leaf_type->size : 8);
int signed_field = leaf_type && (
leaf_type->kind == TY_I8
|| leaf_type->kind == TY_I16
|| leaf_type->kind == TY_I32);
int op = A_MOVQ;
if (fsz == 1) op = A_MOVZBQ;
else if (fsz == 4)
op = signed_field ? A_MOVSXD : A_MOVL;
ins2(c, op,
amem(base_reg,
base_disp + total_off),
areg(D_AX));
goto dot_done;
}
}
}
}
}
int lenfld = (n->str && strcmp(n->str, "len") == 0);
int capfld = (n->str && strcmp(n->str, "cap") == 0);
int ptrfld = (n->str && strcmp(n->str, "ptr") == 0);