w6c+selfhost: TK_AMP through chained N_DOT (closes #9)

Three shapes handled: value-struct chains (&o.i.a), pointer-field
(&p.f), slice/str pseudo-fields (&s.len). cstage inlines #6's spine
walker; wwstage reuses dotchainresolve unchanged. Silent-drop fallback
preserved.

Slice-header width mismatch in *&s.len writes filed as task #13.
This commit is contained in:
2026-05-13 23:17:15 +09:00
parent 5b098c4a99
commit d053560b80
6 changed files with 865 additions and 0 deletions

View File

@@ -1287,6 +1287,134 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
break;
}
if (opnd && opnd->kind == N_DOT) {
/* Address-of through a DOT chain. The early-exit
* above handled `&ident` and `&base[i]`; everything
* else was silently dropped. Three shapes converge
* here, all returning an 8B address (so no
* fldloadop dispatch — just LEAQ).
*
* 1. Value-struct fields, any depth (`&o.f`,
* `&o.i.a`, `&o.a.b.c`): walk the spine to a
* root ident, sum field offsets, emit LEAQ at
* base + sum. Mirror of the read at line 3722.
* 2. Slice/str pseudo-field tail (`&s.len`,
* `&b.buf.len`): folds into the spine walk
* with slice_delta 0/8/16.
* 3. Pointer-field (`&p.f` where p:*T): the spine
* walk aborts at the *T base; the fallback
* below loads p into AX and adds field_off.
*/
int amped = 0;
/* Spine walk — same shape as the read at 3722.
* Records (parent_struct, field_name) leaf-first,
* then iterates root-first to sum offsets. */
struct { Type *pu; const char *name; } steps[16];
int nsteps = 0;
Node *cur = opnd;
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 == opnd && (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;
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;
}
}
if (ok) {
int extra = (slice_delta >= 0)
? slice_delta : 0;
int root_off = localfind(locals, cur->str);
if (root_off != 0) {
ins2(c, A_LEAQ,
amem(D_BP,
root_off + total_off + extra),
areg(D_AX));
amped = 1;
} else if (let_islet(cur->str)) {
/* Two-step global form mirrors the
* read path's `LEAQ name,CX → MOVQ
* disp(CX),AX`, swapping the MOVQ
* for LEAQ. */
ins2(c, A_LEAQ,
masym(c, cur->str), areg(D_CX));
ins2(c, A_LEAQ,
amem(D_CX, total_off + extra),
areg(D_AX));
amped = 1;
}
}
}
/* Pointer-field fallback for `&p.f` where p:*T —
* the spine walker aborts on the *T base. Load p
* into AX, then LEAQ field_off(AX),AX. Mirror of
* the read at line 4033. */
if (!amped && opnd->lhs
&& opnd->lhs->kind == N_IDENT) {
Type *bt = opnd->lhs->type;
Type *bu = (bt && bt->kind == TY_NAMED)
? bt->under : bt;
if (bu && bu->kind == TY_PTR && bu->sub) {
Type *inner = bu->sub;
if (inner->kind == TY_NAMED)
inner = inner->under;
if (inner && inner->kind == TY_STRUCT) {
for (Tfield *f = inner->fields;
f; f = f->next) {
if (strcmp(f->name, opnd->str) != 0)
continue;
int off = localfind(locals,
opnd->lhs->str);
ins2(c, A_MOVQ,
amem(D_BP, off),
areg(D_AX));
ins2(c, A_LEAQ,
amem(D_AX, (int)f->offset),
areg(D_AX));
amped = 1;
break;
}
}
}
}
if (amped) break;
/* Fall through to silent-drop fallback below. */
}
if (opnd && opnd->kind == N_INDEX) {
/* &base[i] = base + i*esz, no dereference. */
Node *base = opnd->lhs;