w6c+w6c_ww: for-range over a non-ident slice base — bound from len, base ptr spilled (#70)

The N_FORRANGE header's non-ident arm stored cgexpr's AX into the
single bound temp — but a slice-valued cgexpr leaves AX=ptr, BX=len,
CX=cap, so the loop compared i against the DATA POINTER; and the
per-iteration element address had no non-ident base arm at all, so
the bound reload doubled as the base. One slot, two roles, holding
the wrong word. An empty slice coincidentally exited (ptr==0), which
is how regex.finish's `for (let charset .. re.charsets)` — planted
verbatim in fold 1 — stayed latent until fold 4 produced the first
non-empty charsets and SEGV'd. Byte-id both stages (the 989 M_ID
entry held on both-wrong-identical); first-consumer surfacing, the
kwtab/#8 pattern.

Fix mirrors the correct local-base arm: bound = BX (len), base ptr
spilled to a dedicated .rgb slot and reloaded per iteration. Covers
field-chain, indexed-element (the task-#57 shape) and call-result
bases. Two shapes whose cgexpr does NOT deliver the header convention
stay LOUD instead of silently wrong (rule 7): deref bases (*p — the
#11 deref-spine family) and non-ident ARRAY bases.

test/937: field (value+ptr roots), 24B-str-header field (the finish
shape), indexed, call, empty-header, eval-once (header captured at
loop entry, not re-read per iteration) rows + the two reject pins,
per-row cs==ww byte-id; verified failing 14/22 at the #66 parent
bb8a44a.
This commit is contained in:
2026-06-04 21:08:59 +09:00
parent bb8a44a564
commit 0055ac2cd3
6 changed files with 596 additions and 14 deletions

View File

@@ -33747,11 +33747,54 @@ fn cgforrange(c: *cgen, n: *node) void = {
};
let destruct: bool = (n.list != nil);
// .rgi (counter) + .rgl (length) scratch slots.
// .rgi (counter) + .rgl (length) scratch slots. #70: a NON-IDENT
// slice/str base (field chain, indexed element, call) also needs
// a .rgb base spill — pre-#70 the init stored cgexpr's AX (the
// DATA POINTER — a slice-valued cgexpr leaves AX=ptr, BX=len,
// CX=cap) into .rgl, and the per-iteration code had no non-ident
// base arm, so the bound-reload BX doubled as the base: i was
// compared against the POINTER and walked off the end
// (regex.finish, SEGV on the first non-empty charsets; empty
// slices coincidentally exited on ptr==0 — latent since fold 1,
// byte-id both stages). A non-ident ARRAY base is loud (rule 7):
// its cgexpr shape is not the slice header.
let iname: str = mkscratchname(c, "rgi");
let lname: str = mkscratchname(c, "rgl");
let ioff: i32 = localalloc(c, iname, 8, nil);
let loff: i32 = localalloc(c, lname, 8, nil);
let baseoff: i32 = 0;
if (slc != nil) {
if (slc.kind != nkind.N_IDENT) {
let stu70: *tinfo = slc.type_: *tinfo;
for (stu70 != nil && stu70.kind == tykind.TY_NAMED) {
stu70 = stu70.under;
};
let arr70: bool = false;
if (stu70 != nil) {
if (stu70.kind == tykind.TY_ARRAY) {
arr70 = true;
};
};
if (arr70) {
let m70: str = "for-range over a non-ident array base unwired (#70)\n";
os.write(2, m70.ptr, m70.len: u64);
os.exit(1);
};
// #11: cgexpr on a slice DEREF (*p) does not deliver
// the AX/BX/CX header convention the spill assumes
// (the deref-spine load family) — keep it LOUD until
// #11 wires the deref load.
if (slc.kind == nkind.N_UN) {
if (slc.op == tkind.TK_STAR) {
let m11: str = "for-range over a deref base unwired (#11)\n";
os.write(2, m11.ptr, m11.len: u64);
os.exit(1);
};
};
let bname: str = mkscratchname(c, "rgb");
baseoff = localalloc(c, bname, 8, nil);
};
};
// Per-binding (up to 8 — matches the C array). Parallel arrays so
// we don't depend on local-struct cgen.
@@ -33859,9 +33902,22 @@ fn cgforrange(c: *cgen, n: *node) void = {
emitline("(BP)\n");
} else {
cgexpr(c, slc);
emitline("\tMOVQ\tAX, ");
emitoff(loff: i64);
emitline("(BP)\n");
if (baseoff != 0) {
// #70: slice/str header from cgexpr is AX=ptr,
// BX=len, CX=cap — bound is LEN; spill the base ptr
// for the per-iteration element address.
emitline("\tMOVQ\tBX, ");
emitoff(loff: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tAX, ");
emitoff(baseoff: i64);
emitline("(BP)\n");
} else {
// ident with unresolved type — legacy path, unchanged.
emitline("\tMOVQ\tAX, ");
emitoff(loff: i64);
emitline("(BP)\n");
};
};};
let loopl: str = mklabel(c, "rloop");
@@ -33907,6 +33963,12 @@ fn cgforrange(c: *cgen, n: *node) void = {
emitline("(BP), BX\n");
};
};
} else {
// #70: non-ident slice/str base — reload the spilled data
// pointer (pre-#70 BX held the bound reload).
emitline("\tMOVQ\t");
emitoff(baseoff: i64);
emitline("(BP), BX\n");
};
emitline("\tADDQ\tAX, BX\n");