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:
@@ -1299,6 +1299,124 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
|
||||
// cgplaceaddr: 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). All type keys
|
||||
// come off the checker-STAMPED tinfo (.type_), never tnode names —
|
||||
// the #209/#211 discipline. Ident-rooted spines stay with the
|
||||
// enumerated cgassign arms so this resolver never perturbs their
|
||||
// asm. 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; dstreg must
|
||||
// not be AX or CX.
|
||||
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind == nkind.N_UN) {
|
||||
if (n.op != tkind.TK_STAR) { return false; };
|
||||
// &(*e) is e's value — no load.
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_INDEX) {
|
||||
let base: *node = n.lhs;
|
||||
let idx: *node = n.rhs;
|
||||
if (base == nil || idx == nil) { return false; };
|
||||
// Deref base only: ident/dot index bases all have
|
||||
// enumerated arms; routing them here would change
|
||||
// their asm.
|
||||
if (base.kind != nkind.N_UN) { return false; };
|
||||
if (base.op != tkind.TK_STAR) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
if (bu.kind != tykind.TY_SLICE && bu.kind != tykind.TY_ARRAY) {
|
||||
return false;
|
||||
};
|
||||
let et: *tinfo = n.type_: *tinfo;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
if (et == nil) { return false; };
|
||||
let esz: i32 = et.size: i32;
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
// 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 == tykind.TY_SLICE) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
if (base == nil) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let st: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let p: *tinfo = bu.sub;
|
||||
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
|
||||
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
|
||||
st = p;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
st = bu;
|
||||
}; };
|
||||
if (st == nil) { return false; };
|
||||
let f: *tfield = st.fields;
|
||||
let foff: i64 = -1;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, n.str)) {
|
||||
foff = f.offset: i64;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -8166,7 +8284,18 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// halves (plus cap for slice — stashed via
|
||||
// DI since LEAQ overwrites CX); the asm has
|
||||
// no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// C1: a name that is neither a local nor a
|
||||
// let dies LOUD — the pre-C1 return dropped
|
||||
// the whole statement silently (cstage twin:
|
||||
// the N_ASSIGN IDENT-tail fatal).
|
||||
if (!isletvar(c, nm)) {
|
||||
let mi1: str = "unsupported assign target: unresolved identifier '";
|
||||
os.write(2, mi1.ptr, mi1.len: u64);
|
||||
os.write(2, nm.ptr, nm.len: u64);
|
||||
let mi2: str = "'\n";
|
||||
os.write(2, mi2.ptr, mi2.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
@@ -8800,6 +8929,147 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// 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 fall-off-the-function tail silently emitted NOTHING
|
||||
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let ft: *tinfo = lhs.type_: *tinfo;
|
||||
let fu: *tinfo = ft;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) {
|
||||
fu = fu.under;
|
||||
};
|
||||
let fsz: i32 = 8;
|
||||
if (ft != nil) { fsz = ft.size: i32; };
|
||||
if (typeisfloat(ft)) {
|
||||
let mf: str = "assign-resolver: float field not wired (rule-7)\n";
|
||||
os.write(2, mf.ptr, mf.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_TAGGED) {
|
||||
let mt: str = "assign-resolver: tagged field not wired (rule-7)\n";
|
||||
os.write(2, mt.ptr, mt.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu.kind == tykind.TY_STRUCT
|
||||
|| fu.kind == tykind.TY_ARRAY
|
||||
|| fu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "assign-resolver: aggregate field not wired (rule-7)\n";
|
||||
os.write(2, ma.ptr, ma.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
let fstrsl: bool = false;
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_STR
|
||||
|| fu.kind == tykind.TY_SLICE) {
|
||||
fstrsl = true;
|
||||
};
|
||||
};
|
||||
if (fstrsl) {
|
||||
if (n.op != tkind.TK_ASSIGN) {
|
||||
let ms: str = "assign-resolver: compound on str/slice field not wired (rule-7)\n";
|
||||
os.write(2, ms.ptr, ms.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// 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);
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "DX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
emitline("\tMOVQ\tAX, (DX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tMOVQ\tCX, 16(DX)\n");
|
||||
return;
|
||||
};
|
||||
} else { if (n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
// Compound: AX=old, CX=rhs, BX=addr — the same
|
||||
// register roles as the chained-ptr-field
|
||||
// compound template above.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
let lop: str = loadopsz(typeissigned(ft), fsz);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t(BX), AX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
let unsignd: bool = typeisunsigned(ft);
|
||||
let wired: bool = false;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_SLASHEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_PERCENTEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (!wired) {
|
||||
let mu: str = "assign-resolver: unknown compound op (rule-7)\n";
|
||||
os.write(2, mu.ptr, mu.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
}; };
|
||||
let mtl: str = "unsupported assign target shape\n";
|
||||
os.write(2, mtl.ptr, mtl.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// C1 residual (task #22): a non-DOT lvalue no arm above matched
|
||||
// still falls out SILENT here — known member: 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.
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user