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:
2026-06-04 09:09:56 +09:00
parent c801aa7954
commit e3e6b5a820
6 changed files with 1679 additions and 10 deletions

View File

@@ -21209,6 +21209,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
@@ -28076,7 +28194,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.
@@ -28710,6 +28839,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;
};

View File

@@ -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;
};

View File

@@ -21209,6 +21209,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
@@ -28076,7 +28194,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.
@@ -28710,6 +28839,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;
};