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 `<value>; 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) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 18:15:44 +00:00
parent a746b6ab59
commit be071d5631

View File

@@ -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)))))