Fix letrec thunk resolution + compiler letrec support + closure JIT check

Root cause: sf-letrec returns a thunk (for TCO) but the CEK dispatch
wrapped it as a value without evaluating. The thunk leaked as the
return value of letrec expressions, breaking sx-parse and any function
using letrec.

Fix: step-sf-letrec unwraps the thunk into a CEK state, so the last
letrec body expression is properly evaluated by the CEK machine.

Also:
- compile-letrec: two-phase (nil-init then assign) for mutual recursion
- Skip JIT for inner functions (closure.bindings != globals) in both
  vm_call and JIT hook
- vm-reset-fn for sx-parse removed (no longer needed)
- Parser regression test: letrec with mutable pos + recursive sublists

Test results: JS 943/17, OCaml 955/0, Python 747/0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 21:04:47 +00:00
parent ffe849df8e
commit 30cfbf777a
7 changed files with 72 additions and 12 deletions

View File

@@ -913,9 +913,8 @@ let register_jit_hook env =
| Some _ -> None (* compile failed — CEK handles *)
| None ->
if !_jit_compiling then None
else if Hashtbl.length l.l_closure.bindings > 0
|| l.l_closure.parent <> None then
(* Skip JIT for lambdas with closure bindings *)
else if l.l_closure.bindings != env.bindings then
(* Skip JIT for inner functions — closure != globals *)
None
else begin
let fn_name = match l.l_name with Some n -> n | None -> "?" in

File diff suppressed because one or more lines are too long

View File

@@ -328,11 +328,10 @@ and vm_call vm f args =
push vm (Sx_ref.cek_call f (List args))
| None ->
if l.l_name <> None
(* Skip JIT for lambdas with closure bindings — the closure
merging into vm_env_ref produces incorrect variable resolution
for inner functions (e.g. parser's read-list-loop). *)
&& Hashtbl.length l.l_closure.bindings = 0
&& l.l_closure.parent = None
(* Skip JIT for inner functions (closure != globals).
The closure merging produces incorrect variable resolution
for functions that capture letrec/let-local bindings. *)
&& l.l_closure.bindings == vm.globals
then begin
(* Pre-mark before compile attempt to prevent re-entrancy *)
l.l_compiled <- Some jit_failed_sentinel;