w6c+wwstage: tagged-element indexed store via dotbaseaddr + align dotchainaddr guard (#259,#256)

#259: the tagged-union array-field indexed STORE arm computed &arr[i]
from a non-ident base (`x.o[1]=v` where o:[N](T|void)) with a plain
cgexpr(base) — the N_DOT array field auto-derefs (loads the field's
first 8 bytes AS a pointer) -> garbage dest -> SEGFAULT. Route the base
through the array-gated helper cg_dotbase_addr/dotbaseaddr (dst BX keeps
the scaled index live in AX; viaptr + chained handled by the shared
helper), mirroring #257. Symmetric both stages. This was the last
unrouted cgexpr(base) cell in the array-field-base-address family
(#135/#252/#253/#255/#257) — proof-grep of both stages now shows ZERO
unrouted base cells in the slice/decay/addr/index/store builders, so the
family is closed by construction. (The chained-ptr-field scalar/str/
float store sites at cgenexpr.ww:6489+ / cgen.c:4379+ correctly cgexpr
the pointer spine and are the #133 family, not array-field-address.)

#256: align wwstage dotchainaddr's N_IDENT non-local arm to carry
cstage cg_dotchain_addr's `let_islet || def_isstructdef` guard (here
isletvar || deflookup) instead of emitting LEAQ name(SB) unconditionally.
Unreachable on valid input (a struct-typed chain root is always local /
let-global / struct def) so zero divergent asm — never-silent ethos only.

Tests (949): store-only byte-id rows (tagged_store_own/_ptr) gate the
#259 store base-address emission cs==ww; store+readback rows
(tagged_store_*_rd) are run-only (cstage) proving the store wrote the
right slot (66/77) and no longer segfaults. byte-id on the readback rows
is blocked by an ORTHOGONAL newly-surfaced divergence in the N_DOT-base
tagged-element READ materialization (sibling of #255: wwstage loads one
word + zeroes the tag where cstage copies the full 16-byte slot) — the
store base is already byte-id; only the read-back diverges. Reported
separately for triage.

combined.ww regen'd (w6c + wwdump embed cgen).
This commit is contained in:
2026-06-02 05:17:45 +09:00
parent a2659c7942
commit 6bcb0929f8
5 changed files with 140 additions and 21 deletions

View File

@@ -20231,12 +20231,22 @@ fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = {
emitline("\n");
return true;
};
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
// #256: carry cstage cg_dotchain_addr's `let_islet ||
// def_isstructdef` guard (never-silent ethos). Unreachable on
// valid input — a struct-typed chain root is always local, a
// let-global, or a struct def — so this adds zero divergent
// asm; it just refuses to LEAQ name(SB) for a name that names
// neither. deflookup is the broader def-registry twin (ww has no
// struct-specific def predicate; harmless given unreachability).
if (isletvar(c, n.str) || deflookup(c, n.str)) {
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
return false;
};
if (n.kind != nkind.N_DOT) { return false; };
let x: *node = n.lhs;
@@ -24337,12 +24347,18 @@ fn cgassign(c: *cgen, n: *node) void = {
emitoff(baselocal.off: i64);
emitline("(BP), BX\n");
};
} else { if (dotbaseaddr(c, base, "BX")) {
// #259: N_DOT base resolved inline to
// the field address; cgexpr fallback
// would auto-deref + load the array
// field as a VALUE (broken shape). dst
// BX keeps the scaled index live in AX.
} else {
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);
emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tAX\n");
};};};
};};};};
emitline("\tADDQ\tAX, BX\n");
let cc: i32 = 0;
for (cc < slot_sz) {

View File

@@ -913,12 +913,22 @@ fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = {
emitline("\n");
return true;
};
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
// #256: carry cstage cg_dotchain_addr's `let_islet ||
// def_isstructdef` guard (never-silent ethos). Unreachable on
// valid input — a struct-typed chain root is always local, a
// let-global, or a struct def — so this adds zero divergent
// asm; it just refuses to LEAQ name(SB) for a name that names
// neither. deflookup is the broader def-registry twin (ww has no
// struct-specific def predicate; harmless given unreachability).
if (isletvar(c, n.str) || deflookup(c, n.str)) {
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
return false;
};
if (n.kind != nkind.N_DOT) { return false; };
let x: *node = n.lhs;
@@ -5019,12 +5029,18 @@ fn cgassign(c: *cgen, n: *node) void = {
emitoff(baselocal.off: i64);
emitline("(BP), BX\n");
};
} else { if (dotbaseaddr(c, base, "BX")) {
// #259: N_DOT base resolved inline to
// the field address; cgexpr fallback
// would auto-deref + load the array
// field as a VALUE (broken shape). dst
// BX keeps the scaled index live in AX.
} else {
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);
emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tAX\n");
};};};
};};};};
emitline("\tADDQ\tAX, BX\n");
let cc: i32 = 0;
for (cc < slot_sz) {

View File

@@ -20231,12 +20231,22 @@ fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = {
emitline("\n");
return true;
};
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
// #256: carry cstage cg_dotchain_addr's `let_islet ||
// def_isstructdef` guard (never-silent ethos). Unreachable on
// valid input — a struct-typed chain root is always local, a
// let-global, or a struct def — so this adds zero divergent
// asm; it just refuses to LEAQ name(SB) for a name that names
// neither. deflookup is the broader def-registry twin (ww has no
// struct-specific def predicate; harmless given unreachability).
if (isletvar(c, n.str) || deflookup(c, n.str)) {
emitline("\tLEAQ\t");
emitsymname(c, n.str);
emitline("(SB), ");
emitline(dstreg);
emitline("\n");
return true;
};
return false;
};
if (n.kind != nkind.N_DOT) { return false; };
let x: *node = n.lhs;
@@ -24337,12 +24347,18 @@ fn cgassign(c: *cgen, n: *node) void = {
emitoff(baselocal.off: i64);
emitline("(BP), BX\n");
};
} else { if (dotbaseaddr(c, base, "BX")) {
// #259: N_DOT base resolved inline to
// the field address; cgexpr fallback
// would auto-deref + load the array
// field as a VALUE (broken shape). dst
// BX keeps the scaled index live in AX.
} else {
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);
emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tAX\n");
};};};
};};};};
emitline("\tADDQ\tAX, BX\n");
let cc: i32 = 0;
for (cc < slot_sz) {