From 0cf66cc3dd586c7d826bca49b2c3ce7a37a615c5 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 14 Jun 2026 18:33:15 -0400 Subject: [PATCH] jit: save/restore loop_slots across nested named-let MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When inner (let loop ((i ...))) shadows an outer (let loop ((a ...) (b ...))), the inner named-let block was overwriting j->loop_slots[] without saving the outer's slot positions. After the inner block restored loop_sym / loop_nparams / loop_params, the outer's recursive call (loop new-a new-b) would write the new args into the inner's stale slot positions instead of the outer's slots, causing the outer body to see stale binding values or trigger 'set! undefined' on tail-call args. loop_slots is a member ARRAY (not pointer) — memcpy'd at line 876 when setting up loop context — so the existing pointer save/restore for loop_params didn't cover it. Verified: - jit_named_let_factorial still PASS (3628800) - new nested-shadowing test: outer 3-param + 4 inner 1-param now PASS - exact replica of squaring.lsp round84-fold structure now PASS Bug surfaced when investigating test-round84-keep-quotient-product failure in www.foxhop.net/ecdsa tests. Test still has a deeper substrate bug beyond this JIT fix (width 0 ancilla in non-fast mode), but this fix is independently correct + closes the loop_slots scope leak. --- c/jit.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/c/jit.c b/c/jit.c index 1525127..1a25708 100644 --- a/c/jit.c +++ b/c/jit.c @@ -839,6 +839,18 @@ static bool emit_expr(JitCtx *j, Value expr, bool tail) { int saved_loop_start = j->loop_start; int saved_loop_nparams = j->loop_nparams; Value *saved_loop_params = j->loop_params; + /* 2026-06-14: loop_slots is an ARRAY (memcpy'd in below); save it + * for nested named-let restore. Without this, inner (let loop ...) + * overwrites outer's slots and outer recursive call writes args to + * inner's slots → outer's bindings appear stale → "set! undefined" + * or wrong values at recursion. Bug surfaced in squaring.lsp's + * round84-fold-hi-into-lo-aggregate with outer terms-keyed loop + + * 4 inner (let loop ((i 0))). */ + int saved_loop_slots[MAX_JIT_LOCALS]; + if (saved_loop_nparams > 0 && saved_loop_nparams <= MAX_JIT_LOCALS) { + memcpy(saved_loop_slots, j->loop_slots, + sizeof(int) * saved_loop_nparams); + } /* Pre-compute stack slots (but don't register yet — inits use outer scope) */ Value loop_var_syms[MAX_JIT_LOCALS]; @@ -899,6 +911,10 @@ static bool emit_expr(JitCtx *j, Value expr, bool tail) { j->loop_start = saved_loop_start; j->loop_nparams = saved_loop_nparams; j->loop_params = saved_loop_params; + if (saved_loop_nparams > 0 && saved_loop_nparams <= MAX_JIT_LOCALS) { + memcpy(j->loop_slots, saved_loop_slots, + sizeof(int) * saved_loop_nparams); + } return true; }