From be071d5631274243e1c1682074c00a667155ef2b Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 30 Jun 2026 18:15:44 +0000 Subject: [PATCH] jit: fix compile-let/compile-letrec stack residue (non-tail miscompile) Both compile-let (regular + dict-destructure bindings) and compile-letrec (named-let / letrec slot-init + value-assign) emitted `; LOCAL_SET slot` with NO POP. Slots are pre-allocated and LOCAL_SET peeks (doesn't pop), so each binding value was left as stack residue. In TAIL position this was masked (OP_TAIL_CALL resets sp to frame.base); in NON-TAIL position the residue leaked to the enclosing frame, so the caller popped the wrong value ("not callable: 7", "rest: 1 list arg", "first: expected list, got true") under the serving JIT. Fix: emit POP (op 5) after each binding's LOCAL_SET. Broad correctness win for any let/letrec in non-tail position under JIT (and the precondition for landing the HO-loop desugar, which builds on named-let recur). Ported from loops/sx-vm-extensions 3126d728 + 41d46f1b (compiler.sx hunks only; spike commit and worktree .mcp.json/lock noise excluded). --jit conformance in the source branch: 4842/1102 -> 4847/1097 (+5 fixed, zero regressions). Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/compiler.sx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/compiler.sx b/lib/compiler.sx index 392115cf..28a8a415 100644 --- a/lib/compiler.sx +++ b/lib/compiler.sx @@ -602,6 +602,7 @@ ((temp-slot (scope-define-local let-scope "__dict_src"))) (emit-op em 17) (emit-byte em temp-slot) + (emit-op em 5) (for-each (fn (k) @@ -623,7 +624,8 @@ (let ((slot (scope-define-local let-scope (if (= (type-of var-name) "symbol") (symbol-name var-name) var-name)))) (emit-op em 17) - (emit-byte em slot)))) + (emit-byte em slot) + (emit-op em 5)))) (keys pattern)) (compile-begin em body let-scope tail?))) (let @@ -641,7 +643,8 @@ (let ((slot (scope-define-local let-scope (symbol-name name)))) (emit-op em 17) - (emit-byte em slot)))) + (emit-byte em slot) + (emit-op em 5)))) bindings) (compile-begin em body let-scope tail?)))))) (define @@ -655,7 +658,7 @@ (let-scope (make-scope scope))) (dict-set! let-scope "next-slot" (get scope "next-slot")) (let - ((slots (map (fn (binding) (let ((name (if (= (type-of (first binding)) "symbol") (symbol-name (first binding)) (first binding)))) (let ((slot (scope-define-local let-scope name))) (emit-op em 2) (emit-op em 17) (emit-byte em slot) slot))) bindings))) + ((slots (map (fn (binding) (let ((name (if (= (type-of (first binding)) "symbol") (symbol-name (first binding)) (first binding)))) (let ((slot (scope-define-local let-scope name))) (emit-op em 2) (emit-op em 17) (emit-byte em slot) (emit-op em 5) slot))) bindings))) (for-each (fn (pair) @@ -663,7 +666,8 @@ ((binding (first pair)) (slot (nth pair 1))) (compile-expr em (nth binding 1) let-scope false) (emit-op em 17) - (emit-byte em slot))) + (emit-byte em slot) + (emit-op em 5))) (map (fn (i) (list (nth bindings i) (nth slots i))) (range 0 (len bindings)))))