wcc+w6c_ww: cgplaceaddr resolver — deref-base assign stores (F6)
(*ts)[i].field = v / OP= v (the regex run_thread hot shape, task #4) compiled to NOTHING in both stages, byte-identically: the N_DOT lhs roots at N_UN(STAR), so the arr[i].field arm (idxbase must be IDENT) and the chained-ptr-field arm (base must be *struct) both miss and the N_ASSIGN dispatch fell off the switch silently, rhs unevaluated. cgplaceaddr (one per stage) is ADDRESS COMPUTATION ONLY — N_UN(STAR) root, N_INDEX hop over a slice/array place (.ptr hop for slice), N_DOT struct-field hop with one deref for a *struct base. Call-sites keep their own emission: scalar fldstoreop store, str/slice 3-word header store staged through DX, 10-op compound template with the chained-ptr-field register roles. Ident-rooted spines stay with the enumerated arms — verified asm-neutral over the 84 fold2b probe sources against fresh master-HEAD binaries (7 diffs = the F6 family now emitting stores; 2 verdict flips = aggregate-field stores, now loud). Silent dispatch tails go LOUD for N_DOT lvalues the resolver can't address and for unresolved-identifier targets (cstage float-ident arm aligned to wwstage's resolve-first order). Aggregate-field stores loud-reject pending the follow-up resolver commit (task #23, ≤24B N_CALL rhs split to #24). The non-DOT tail stays silent deliberately: going loud there would asymmetrically surface the pre-existing str-base element-store divergence — task #22, cited at both sites. test/805: 17 rows x 2 drivers + 12 cs==ww byte-id fixtures — widths (incl narrow-compound fldloadop sign/zero-extension), all 10 compound ops (DIVQ/IDIVQ/SHLQ/SARQ/SHRQ), str + slice 3-word stores, *[N]T base, runtime call index, ident-base neutrality pins, and 5 reject rows asserting exact diagnostic text.
This commit is contained in:
251
cmd/w6c/cgen.c
251
cmd/w6c/cgen.c
@@ -1989,6 +1989,88 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
* expression into dst_reg; returns 1 when the shape is wired, 0
|
||||
* otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
* F6 resolver, commit C1: only the deref-rooted spine is wired —
|
||||
* `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a slice/array
|
||||
* place, N_DOT struct-field hop with one deref for a *struct base.
|
||||
* Ident-rooted spines stay with the enumerated N_ASSIGN arms so this
|
||||
* resolver never perturbs their asm; the F4 read-walker and F5
|
||||
* let-copy accrete here in follow-up commits. ADDRESS COMPUTATION
|
||||
* ONLY — every call-site keeps its own load/store/copy emission.
|
||||
* Clobbers AX/CX (cgexpr on index / pointer operands) and balances
|
||||
* its own PUSHQ/POPQ; dst_reg must not be AX or CX. */
|
||||
static int
|
||||
cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
|
||||
{
|
||||
if (n == NULL) return 0;
|
||||
if (n->kind == N_UN && n->op == TK_STAR) {
|
||||
/* &(*e) is e's value — no load. */
|
||||
cgexpr(c, n->lhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(dst_reg));
|
||||
return 1;
|
||||
}
|
||||
if (n->kind == N_INDEX) {
|
||||
Node *base = n->lhs;
|
||||
Node *idx = n->rhs;
|
||||
if (base == NULL || idx == NULL) return 0;
|
||||
/* Deref base only: ident/dot index bases all have
|
||||
* enumerated arms; routing them here would change
|
||||
* their asm. */
|
||||
if (!(base->kind == N_UN && base->op == TK_STAR))
|
||||
return 0;
|
||||
Type *bu = type_chase_named(base->type);
|
||||
if (bu == NULL) return 0;
|
||||
if (bu->kind != TY_SLICE && bu->kind != TY_ARRAY)
|
||||
return 0;
|
||||
Type *et = type_chase_named(n->type);
|
||||
if (et == NULL) return 0;
|
||||
int esz = (int)et->size;
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
||||
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (!cgplaceaddr(c, base, dst_reg, locals)) return 0;
|
||||
/* A slice place holds the {ptr,len,cap} header — the
|
||||
* element base is its .ptr word; an array place IS the
|
||||
* element storage. */
|
||||
if (bu->kind == TY_SLICE)
|
||||
ins2(c, A_MOVQ, amem(dst_reg, 0), areg(dst_reg));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, A_ADDQ, areg(D_AX), areg(dst_reg));
|
||||
return 1;
|
||||
}
|
||||
if (n->kind == N_DOT) {
|
||||
Node *base = n->lhs;
|
||||
if (base == NULL) return 0;
|
||||
Type *bu = type_chase_named(base->type);
|
||||
if (bu == NULL) return 0;
|
||||
int viaptr = 0;
|
||||
Type *st = NULL;
|
||||
if (bu->kind == TY_PTR) {
|
||||
Type *p = type_chase_named(bu->sub);
|
||||
if (p && p->kind == TY_STRUCT) { st = p; viaptr = 1; }
|
||||
} else if (bu->kind == TY_STRUCT) {
|
||||
st = bu;
|
||||
}
|
||||
if (st == NULL) return 0;
|
||||
Tfield *f = NULL;
|
||||
for (Tfield *fl = st->fields; fl; fl = fl->next)
|
||||
if (strcmp(fl->name, n->str) == 0) { f = fl; break; }
|
||||
if (f == NULL) return 0;
|
||||
if (!cgplaceaddr(c, base, dst_reg, locals)) return 0;
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ, amem(dst_reg, 0), areg(dst_reg));
|
||||
if ((int)f->offset != 0)
|
||||
ins2(c, A_ADDQ, aimm((int)f->offset), areg(dst_reg));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cg_structlit_fill modes — see helper docstring. */
|
||||
enum {
|
||||
DST_BP = 0,
|
||||
@@ -5167,7 +5249,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int divop = op_for(n, A_DIVSD, A_DIVSS);
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
int isglobal = (off == 0) && let_islet(n->lhs->str);
|
||||
if (off == 0 && !isglobal) break;
|
||||
/* Loud twin of the IDENT-tail unresolved-name stop
|
||||
* below (C1): wwstage resolves the name BEFORE its
|
||||
* float dispatch, so a silent break here would make
|
||||
* the stages disagree on the build verdict. */
|
||||
if (off == 0 && !isglobal)
|
||||
fatal("unsupported assign target: "
|
||||
"unresolved identifier '%s'",
|
||||
n->lhs->str);
|
||||
if (n->op == TK_ASSIGN) {
|
||||
if (off != 0) {
|
||||
ins2(c, mvop, areg(D_X0), amem(D_BP, off));
|
||||
@@ -5946,16 +6035,157 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
* enumerated arms above matched — today the deref-rooted
|
||||
* spine `(*p)[i].f = v` / `OP= v`. Base-address derivation
|
||||
* routes through cgplaceaddr; the load/store emission stays
|
||||
* here. Any N_DOT shape the resolver can't address dies
|
||||
* LOUD below: the pre-C1 dispatch tail silently emitted
|
||||
* NOTHING (rhs unevaluated) for every such shape. */
|
||||
if (n->lhs && n->lhs->kind == N_DOT) {
|
||||
Type *ft = n->lhs->type;
|
||||
Type *fu = type_chase_named(ft);
|
||||
int fsz = (int)(ft ? ft->size : 8);
|
||||
int pa_isf32 = 0;
|
||||
if (fld_isfloat(ft, &pa_isf32))
|
||||
fatal("assign-resolver: float field not "
|
||||
"wired (rule-7)");
|
||||
if (fu && fu->kind == TY_TAGGED)
|
||||
fatal("assign-resolver: tagged field not "
|
||||
"wired (rule-7)");
|
||||
if (fu && (fu->kind == TY_STRUCT
|
||||
|| fu->kind == TY_ARRAY
|
||||
|| fu->kind == TY_TUPLE))
|
||||
fatal("assign-resolver: aggregate field not "
|
||||
"wired (rule-7)");
|
||||
if (fu && (fu->kind == TY_STR
|
||||
|| fu->kind == TY_SLICE)) {
|
||||
if (n->op != TK_ASSIGN)
|
||||
fatal("assign-resolver: compound on "
|
||||
"str/slice field not wired "
|
||||
"(rule-7)");
|
||||
/* str IS []u8: store the whole {ptr,len,cap}
|
||||
* triple from (AX,BX,CX); the place address
|
||||
* goes in DX so the three pops survive
|
||||
* (#1/Phase 3). */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (cgplaceaddr(c, n->lhs, D_DX, locals)) {
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_DX, 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_DX, 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, 16));
|
||||
break;
|
||||
}
|
||||
} else if (n->op == TK_ASSIGN) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (cgplaceaddr(c, n->lhs, D_BX, locals)) {
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, fldstoreop(ft, fsz),
|
||||
areg(D_AX), amem(D_BX, 0));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Compound: AX=old, CX=rhs, BX=addr — the
|
||||
* same register roles as the chained-ptr-
|
||||
* field compound template above. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (cgplaceaddr(c, n->lhs, D_BX, locals)) {
|
||||
ins2(c, fldloadop(ft, fsz),
|
||||
amem(D_BX, 0), areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
int unsignd = type_isunsigned(ft);
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ:
|
||||
ins2(c, A_ADDQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_MINUSEQ:
|
||||
ins2(c, A_SUBQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_STAREQ:
|
||||
ins2(c, A_IMULQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_AMPEQ:
|
||||
ins2(c, A_ANDQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_PIPEEQ:
|
||||
ins2(c, A_ORQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_CARETEQ:
|
||||
ins2(c, A_XORQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_SLASHEQ:
|
||||
if (unsignd)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(0),
|
||||
areg(D_DX));
|
||||
else
|
||||
ins0(c, A_CQO);
|
||||
ins1(c, unsignd ? A_DIVQ
|
||||
: A_IDIVQ, areg(D_CX));
|
||||
break;
|
||||
case TK_PERCENTEQ:
|
||||
if (unsignd)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(0),
|
||||
areg(D_DX));
|
||||
else
|
||||
ins0(c, A_CQO);
|
||||
ins1(c, unsignd ? A_DIVQ
|
||||
: A_IDIVQ, areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_LSHIFTEQ:
|
||||
ins2(c, A_SHLQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_RSHIFTEQ:
|
||||
ins2(c, unsignd ? A_SHRQ
|
||||
: A_SARQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
default:
|
||||
fatal("assign-resolver: "
|
||||
"unknown compound op "
|
||||
"(rule-7)");
|
||||
}
|
||||
ins2(c, fldstoreop(ft, fsz),
|
||||
areg(D_AX), amem(D_BX, 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
fatal("unsupported assign target shape");
|
||||
}
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) {
|
||||
/* Top-level let target — RIP-relative store
|
||||
* (or load→combine→store for compound). Names
|
||||
* we don't recognise as scalar lets fall through
|
||||
* to the existing drop behaviour, which produces
|
||||
* a clean link-time undefined-symbol error if
|
||||
* the binding was ever supposed to exist. */
|
||||
if (!let_islet(n->lhs->str)) break;
|
||||
* (or load→combine→store for compound). A
|
||||
* name that is neither a local nor a let
|
||||
* dies LOUD: the pre-C1 break dropped the
|
||||
* whole statement silently (no symbol was
|
||||
* ever referenced, so not even a link error
|
||||
* surfaced). */
|
||||
if (!let_islet(n->lhs->str))
|
||||
fatal("unsupported assign target: "
|
||||
"unresolved identifier '%s'",
|
||||
n->lhs->str);
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (n->op == TK_ASSIGN) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
@@ -6113,6 +6343,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off));
|
||||
skip_assign_store: ;
|
||||
}
|
||||
/* C1 residual (task #22): a non-DOT lvalue no arm above
|
||||
* matched still falls out SILENT here — the known member is
|
||||
* the str-base element store family (`s[i] = v`: cstage
|
||||
* drops, wwstage emits MOVB; pre-existing gate-blind
|
||||
* divergence) plus tuple-member writes. The tail goes loud
|
||||
* for the remaining kinds with #22, after the family gets a
|
||||
* symmetric verdict. */
|
||||
break;
|
||||
}
|
||||
case N_CALL: {
|
||||
|
||||
Reference in New Issue
Block a user