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:
@@ -4967,6 +4967,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int boff = localfind(locals, base->str);
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff),
|
||||
areg(D_BX));
|
||||
} else if (cg_dotbase_addr(c, base, D_BX, locals)) {
|
||||
/* #259: N_DOT base resolved inline to the
|
||||
* field address; cgexpr fallback would
|
||||
* auto-deref + load the array field as a
|
||||
* VALUE (the broken shape). dst BX keeps the
|
||||
* scaled index live in AX (spill contract). */
|
||||
} else {
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, base, locals);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -480,6 +480,71 @@ static const struct row rows[] = {
|
||||
" q.v = \"hello\";\n"
|
||||
" return rd(q.v[1:4]);\n"
|
||||
"};\n", 3, 1 },
|
||||
/* #259 tagged-union array-field indexed STORE via N_DOT base. The
|
||||
* tagged-element store arm computed &arr[i] from a non-ident base
|
||||
* (`x.o`) 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. Last unrouted cell of the array-field-base-
|
||||
* address family (#135/#252/#253/#255/#257 proof-grep residual). Fix
|
||||
* routes the store base through cg_dotbase_addr/dotbaseaddr (array-
|
||||
* gated; viaptr + chained handled by the shared helper). cs==ww
|
||||
* broken identically pre-fix (gate-blind correctness).
|
||||
*
|
||||
* STORE byte-id rows (byteid=1): store-only into a tagged array field
|
||||
* (own value-struct + via *struct param), return a constant. These
|
||||
* gate the #259 store base-address emission cs==ww — the store
|
||||
* portion is byte-identical post-fix (LEAQ base, not auto-deref). */
|
||||
{ "tagged_store_own",
|
||||
"package main;\n"
|
||||
"type e = struct { o: [4](i32 | void) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: e;\n"
|
||||
" x.o[1] = 66;\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 1 },
|
||||
{ "tagged_store_ptr",
|
||||
"package main;\n"
|
||||
"type e = struct { o: [4](i32 | void) };\n"
|
||||
"fn wr(x: *e) void = { x.o[2] = 77; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: e;\n"
|
||||
" wr(&x);\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, 1 },
|
||||
/* #259 STORE-correctness rows (byteid=0, run-only on cstage): store
|
||||
* then read the element back via match to confirm the store wrote the
|
||||
* right slot (66/77, not garbage) and no longer segfaults. byte-id is
|
||||
* BLOCKED here by an ORTHOGONAL newly-surfaced divergence in the
|
||||
* N_DOT-base tagged-element READ materialization (sibling of #255 in
|
||||
* the same N_DOT-base index fallback: wwstage loads ONE word + zeroes
|
||||
* the tag where cstage copies the full 16-byte slot) — the store base
|
||||
* is already byte-id (see the store-only rows above); only the
|
||||
* read-back diverges. Filed separately; do not gate byte-id here. */
|
||||
{ "tagged_store_own_rd",
|
||||
"package main;\n"
|
||||
"type e = struct { o: [4](i32 | void) };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: e;\n"
|
||||
" x.o[1] = 66;\n"
|
||||
" let v: (i32 | void) = x.o[1];\n"
|
||||
" return match (v) {\n"
|
||||
" case let n: i32 => yield n;\n"
|
||||
" case void => yield 0: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 66, 0 },
|
||||
{ "tagged_store_ptr_rd",
|
||||
"package main;\n"
|
||||
"type e = struct { o: [4](i32 | void) };\n"
|
||||
"fn wr(x: *e) void = { x.o[2] = 77; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: e;\n"
|
||||
" wr(&x);\n"
|
||||
" let v: (i32 | void) = x.o[2];\n"
|
||||
" return match (v) {\n"
|
||||
" case let n: i32 => yield n;\n"
|
||||
" case void => yield 0: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 77, 0 },
|
||||
{ NULL, NULL, 0, 0 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user