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:
@@ -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");
|
||||
|
||||
|
||||
@@ -3305,11 +3305,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.
|
||||
@@ -3417,9 +3460,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");
|
||||
@@ -3465,6 +3521,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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user