wcc: #152 let-initializer scope — defer the binding's localfind link past its own init (both stages)
A let's own name was visible during its OWN initializer: cgen prepended the
new local into the name-keyed localfind chain BEFORE emitting the init, so
`let x = f(x)` read the fresh UNINIT slot, not the outer/param x. Both-wrong-
identical silent miscompile (gate-blind byte-id). Surfaced by path
dirname/basename (was the c3-posix path->p rename).
Align to Hare (harec check.c:1439 evals the init, then scope_insert). Fix,
both stages, IDENTICAL asm: reserve the frame slot BEFORE the init emits,
link the binding's name into the localfind chain only AFTER.
- cstage cgen.c: split localoff -> localslot(reserve)+link; N_LET's 12
case-level breaks -> goto letlink (tail links once); the inner-for break
is preserved; the 4 fatal() arms untouched.
- wwstage cgen.ww/cgenstmt.ww: new localreserve (= localalloc minus the
chain-link); cglet -> cgletbody(c,n,off) + a cglet wrapper that
reserves -> calls body -> links after.
Byte-id-safe on existing code: localfind is by-name, so deferring the link
is a no-op on every non-self-shadow let (grep = 0 self-shadow sites) — 990-997
stay green. Because both stages emit identical now-correct asm, byte-id
CANNOT catch this; the pin is a RUNTIME test, teeth-proven (revert -> pin
fails). test/wcc/989_letshadow{.ww,_run.c}: param-shadow, let-in-init shadow,
rename control, arrlit self-ref.
Embedded regen: selfhost/cmd/{w6c,wwdump}/main.combined.ww. Gate: all 325
passed, byte-id 990-997 green, w6c c587f4a1 / w6c_ww 7a69f898 (deterministic).
This commit is contained in:
@@ -35509,7 +35509,29 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// #152: reserve the let's frame slot, emit its initializer against the
|
||||
// PRE-binding locals chain, then link the binding. A self-shadowing init
|
||||
// (`let x = f(x)`) resolves x in the OUTER scope because nm is not yet in
|
||||
// c.locals while cgletbody runs (Hare evals the init in the outer scope:
|
||||
// harec check.c clet runs cexpr before scope_define). localreserve bumps
|
||||
// the frame now so off + nested-let offsets stay stable.
|
||||
fn cglet(c: *cgen, n: *node) void = {
|
||||
let nm: str = n.str;
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
let tn: *node = n.lhs;
|
||||
if (tn == nil) { tn = inferletcalltype(c, n.rhs); };
|
||||
let letloc: *local = localreserve(c, nm, sz, tn);
|
||||
cgletbody(c, n, letloc.off);
|
||||
letloc.lnext = c.locals;
|
||||
c.locals = letloc;
|
||||
};
|
||||
|
||||
// #152: cgletbody emits the initializer into the reserved slot `off`.
|
||||
// The wrapper cglet reserves the slot BEFORE this runs and links the
|
||||
// binding into c.locals only AFTER, so a self-shadowing init
|
||||
// (`let x = f(x)`) resolves x in the OUTER scope (Hare evals the init in
|
||||
// the outer scope: harec check.c clet runs cexpr before scope_define).
|
||||
fn cgletbody(c: *cgen, n: *node, off: i32) void = {
|
||||
let nm: str = n.str;
|
||||
let sz: i32 = letslotsize(c, n);
|
||||
// `let x = f()?` has no annotation but the cgen's struct-field
|
||||
@@ -35517,7 +35539,6 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// success variant — see inferletcalltype.
|
||||
let tn: *node = n.lhs;
|
||||
if (tn == nil) { tn = inferletcalltype(c, n.rhs); };
|
||||
let off: i32 = localadd(c, nm, sz, tn);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||
@@ -38766,6 +38787,21 @@ fn localalloc(c: *cgen, name: str, sz: i32, tnode: *node) i32 = {
|
||||
return off;
|
||||
};
|
||||
|
||||
// localreserve — localalloc minus the chain-link. #152: cglet reserves
|
||||
// the slot (frame bump + offset) before its initializer emits, then links
|
||||
// the binding into c.locals only AFTER, so a self-shadowing init
|
||||
// (`let x = f(x)`) resolves x in the OUTER scope (Hare evals the init in
|
||||
// the outer scope: harec check.c clet runs cexpr before scope_define).
|
||||
fn localreserve(c: *cgen, name: str, sz: i32, tnode: *node) *local = {
|
||||
let asz: i32 = sz;
|
||||
if (asz < 8) { asz = 8; };
|
||||
if ((asz & 7) != 0) { asz = (asz + 7) & ~7; };
|
||||
c.frame += asz;
|
||||
let off: i32 = 0 - c.frame;
|
||||
let l: *local = alloc(local{name=name, off=off, sz=asz, tnode=tnode, lnext=nil})!;
|
||||
return l;
|
||||
};
|
||||
|
||||
// localaddstack — register a param at a positive BP offset. Used for
|
||||
// args that overflow the 6 SysV int / 8 float reg windows; the caller
|
||||
// pushes them in reverse, so each spilled arg lives at 16(BP), 24(BP),
|
||||
|
||||
Reference in New Issue
Block a user