Fix JIT closure scoping: use vm_env_ref not caller's globals
When the VM called a JIT-compiled lambda, it passed vm.globals (the caller's global env) instead of cl.vm_env_ref (the closure's captured env that was merged at compile time). Closure-captured variables like code-tokens from island let/letrec scopes were invisible at runtime, causing "Undefined symbol" errors that cascaded to disable render-to-html globally. Fix: call_closure uses cl.vm_env_ref at both call sites (cached bytecode and fresh compilation). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,8 +145,9 @@ and vm_call vm f args =
|
||||
| Lambda l ->
|
||||
(match l.l_compiled with
|
||||
| Some cl when not (is_jit_failed cl) ->
|
||||
(* Cached bytecode — run on VM, fall back to CEK on runtime error *)
|
||||
(try push vm (call_closure cl args vm.globals)
|
||||
(* Cached bytecode — run on VM using the closure's captured env,
|
||||
not the caller's globals. Closure vars were merged at compile time. *)
|
||||
(try push vm (call_closure cl args cl.vm_env_ref)
|
||||
with _ -> push vm (Sx_ref.cek_call f (List args)))
|
||||
| Some _ ->
|
||||
(* Compile failed — CEK *)
|
||||
@@ -159,7 +160,7 @@ and vm_call vm f args =
|
||||
match !jit_compile_ref l vm.globals with
|
||||
| Some cl ->
|
||||
l.l_compiled <- Some cl;
|
||||
(try push vm (call_closure cl args vm.globals)
|
||||
(try push vm (call_closure cl args cl.vm_env_ref)
|
||||
with _ ->
|
||||
l.l_compiled <- Some jit_failed_sentinel;
|
||||
push vm (Sx_ref.cek_call f (List args)))
|
||||
@@ -400,36 +401,48 @@ and run vm =
|
||||
let v = peek vm in
|
||||
Hashtbl.replace vm.globals name v
|
||||
|
||||
(* ---- Inline primitives (no hashtable lookup) ---- *)
|
||||
(* ---- Inline primitives ----
|
||||
Fast path for common types; fallback to actual primitive
|
||||
for edge cases (type coercion, thunks, RawHTML, etc.)
|
||||
to guarantee behavioral parity with CALL_PRIM. *)
|
||||
| 160 (* OP_ADD *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with
|
||||
| Number x, Number y -> Number (x +. y)
|
||||
| String x, String y -> String (x ^ y)
|
||||
| _ -> Sx_primitives.(get_primitive "+" |> function NativeFn (_, f) -> f [a; b] | _ -> Nil))
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "+") [a; b])
|
||||
| 161 (* OP_SUB *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with Number x, Number y -> Number (x -. y) | _ -> Nil)
|
||||
push vm (match a, b with
|
||||
| Number x, Number y -> Number (x -. y)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "-") [a; b])
|
||||
| 162 (* OP_MUL *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with Number x, Number y -> Number (x *. y) | _ -> Nil)
|
||||
push vm (match a, b with
|
||||
| Number x, Number y -> Number (x *. y)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "*") [a; b])
|
||||
| 163 (* OP_DIV *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with Number x, Number y -> Number (x /. y) | _ -> Nil)
|
||||
push vm (match a, b with
|
||||
| Number x, Number y -> Number (x /. y)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "/") [a; b])
|
||||
| 164 (* OP_EQ *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
(* Must normalize ListRef→List before structural compare,
|
||||
same as the "=" primitive in sx_primitives.ml *)
|
||||
let rec norm = function
|
||||
| ListRef { contents = l } -> List (List.map norm l)
|
||||
| List l -> List (List.map norm l) | v -> v in
|
||||
push vm (Bool (norm a = norm b))
|
||||
| 165 (* OP_LT *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with Number x, Number y -> Bool (x < y) | String x, String y -> Bool (x < y) | _ -> Bool false)
|
||||
push vm (match a, b with
|
||||
| Number x, Number y -> Bool (x < y)
|
||||
| String x, String y -> Bool (x < y)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "<") [a; b])
|
||||
| 166 (* OP_GT *) ->
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with Number x, Number y -> Bool (x > y) | String x, String y -> Bool (x > y) | _ -> Bool false)
|
||||
push vm (match a, b with
|
||||
| Number x, Number y -> Bool (x > y)
|
||||
| String x, String y -> Bool (x > y)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives ">") [a; b])
|
||||
| 167 (* OP_NOT *) ->
|
||||
let v = pop vm in
|
||||
push vm (Bool (not (sx_truthy v)))
|
||||
@@ -439,36 +452,52 @@ and run vm =
|
||||
| List l | ListRef { contents = l } -> Number (float_of_int (List.length l))
|
||||
| String s -> Number (float_of_int (String.length s))
|
||||
| Dict d -> Number (float_of_int (Hashtbl.length d))
|
||||
| Nil -> Number 0.0 | _ -> Number 0.0)
|
||||
| Nil -> Number 0.0
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "len") [v])
|
||||
| 169 (* OP_FIRST *) ->
|
||||
let v = pop vm in
|
||||
push vm (match v with List (x :: _) | ListRef { contents = x :: _ } -> x | _ -> Nil)
|
||||
push vm (match v with
|
||||
| List (x :: _) | ListRef { contents = x :: _ } -> x
|
||||
| List [] | ListRef { contents = [] } | Nil -> Nil
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "first") [v])
|
||||
| 170 (* OP_REST *) ->
|
||||
let v = pop vm in
|
||||
push vm (match v with List (_ :: xs) | ListRef { contents = _ :: xs } -> List xs | _ -> List [])
|
||||
push vm (match v with
|
||||
| List (_ :: xs) | ListRef { contents = _ :: xs } -> List xs
|
||||
| List [] | ListRef { contents = [] } | Nil -> List []
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "rest") [v])
|
||||
| 171 (* OP_NTH *) ->
|
||||
let n = pop vm and coll = pop vm in
|
||||
let i = match n with Number f -> int_of_float f | _ -> 0 in
|
||||
push vm (match coll with
|
||||
| List l | ListRef { contents = l } ->
|
||||
(try List.nth l i with _ -> Nil)
|
||||
| _ -> Nil)
|
||||
push vm (match coll, n with
|
||||
| (List l | ListRef { contents = l }), Number f ->
|
||||
(try List.nth l (int_of_float f) with _ -> Nil)
|
||||
| String s, Number f ->
|
||||
let i = int_of_float f in
|
||||
if i >= 0 && i < String.length s then String (String.make 1 s.[i])
|
||||
else Nil
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "nth") [coll; n])
|
||||
| 172 (* OP_CONS *) ->
|
||||
let coll = pop vm and x = pop vm in
|
||||
push vm (match coll with
|
||||
| List l -> List (x :: l)
|
||||
| ListRef { contents = l } -> List (x :: l)
|
||||
| Nil -> List [x]
|
||||
| _ -> List [x])
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "cons") [x; coll])
|
||||
| 173 (* OP_NEG *) ->
|
||||
let v = pop vm in
|
||||
push vm (match v with Number x -> Number (-.x) | _ -> Nil)
|
||||
push vm (match v with
|
||||
| Number x -> Number (-.x)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "-") [v])
|
||||
| 174 (* OP_INC *) ->
|
||||
let v = pop vm in
|
||||
push vm (match v with Number x -> Number (x +. 1.0) | _ -> Nil)
|
||||
push vm (match v with
|
||||
| Number x -> Number (x +. 1.0)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "inc") [v])
|
||||
| 175 (* OP_DEC *) ->
|
||||
let v = pop vm in
|
||||
push vm (match v with Number x -> Number (x -. 1.0) | _ -> Nil)
|
||||
push vm (match v with
|
||||
| Number x -> Number (x -. 1.0)
|
||||
| _ -> (Hashtbl.find Sx_primitives.primitives "dec") [v])
|
||||
|
||||
| opcode ->
|
||||
raise (Eval_error (Printf.sprintf "VM: unknown opcode %d at ip=%d"
|
||||
|
||||
@@ -219,23 +219,17 @@
|
||||
;; Component expressions handled by lake's reactive render
|
||||
nil))
|
||||
(swap! step-idx inc)
|
||||
(update-code-highlight)))))
|
||||
(rebuild-preview (fn (target)
|
||||
(let ((container (get-preview)))
|
||||
(when container
|
||||
(dom-set-prop container "innerHTML" "")
|
||||
(let ((expr (steps-to-preview (deref steps) target)))
|
||||
(when expr
|
||||
(let ((dom (render-to-dom expr (get-render-env nil) nil)))
|
||||
(when dom (dom-append container dom)))))
|
||||
(set-stack (list container))))))
|
||||
(update-code-highlight)
|
||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))))
|
||||
(do-back (fn ()
|
||||
(when (> (deref step-idx) 0)
|
||||
(let ((target (- (deref step-idx) 1)))
|
||||
(rebuild-preview target)
|
||||
(reset! step-idx target)
|
||||
(update-code-highlight)
|
||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper"))))))))
|
||||
(let ((target (- (deref step-idx) 1))
|
||||
(container (get-preview)))
|
||||
(when container (dom-set-prop container "innerHTML" ""))
|
||||
(set-stack (list (get-preview)))
|
||||
(reset! step-idx 0)
|
||||
(for-each (fn (_) (do-step)) (slice (deref steps) 0 target))
|
||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))))))
|
||||
;; Freeze scope for persistence — same mechanism, cookie storage
|
||||
(freeze-scope "home-stepper" (fn ()
|
||||
(freeze-signal "step" step-idx)))
|
||||
@@ -260,7 +254,13 @@
|
||||
(let ((_eff (effect (fn ()
|
||||
(schedule-idle (fn ()
|
||||
(build-code-dom)
|
||||
(rebuild-preview (deref step-idx))
|
||||
(let ((preview (get-preview)))
|
||||
(when preview (dom-set-prop preview "innerHTML" "")))
|
||||
(batch (fn ()
|
||||
(let ((target (deref step-idx)))
|
||||
(reset! step-idx 0)
|
||||
(set-stack (list (get-preview)))
|
||||
(for-each (fn (_) (do-step)) (slice (deref steps) 0 target)))))
|
||||
(update-code-highlight)
|
||||
(run-post-render-hooks)))))))
|
||||
(div :class "space-y-4"
|
||||
@@ -282,9 +282,7 @@
|
||||
(deref code-tokens)))
|
||||
;; Controls
|
||||
(div :class "flex items-center justify-center gap-2 md:gap-3"
|
||||
(button :on-click (fn (e)
|
||||
(do-back)
|
||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))
|
||||
(button :on-click (fn (e) (do-back))
|
||||
:class (str "px-2 py-1 rounded text-3xl "
|
||||
(if (> (deref step-idx) 0)
|
||||
"text-stone-600 hover:text-stone-800 hover:bg-stone-100"
|
||||
@@ -292,9 +290,7 @@
|
||||
"\u25c0")
|
||||
(span :class "text-sm text-stone-500 font-mono tabular-nums"
|
||||
(deref step-idx) " / " (len (deref steps)))
|
||||
(button :on-click (fn (e)
|
||||
(do-step)
|
||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))
|
||||
(button :on-click (fn (e) (do-step))
|
||||
:class (str "px-2 py-1 rounded text-3xl "
|
||||
(if (< (deref step-idx) (len (deref steps)))
|
||||
"text-violet-600 hover:text-violet-800 hover:bg-violet-50"
|
||||
@@ -303,6 +299,6 @@
|
||||
;; Live preview — shows partial result up to current step.
|
||||
;; Same SX rendered by server (HTML) and client (DOM).
|
||||
(lake :id "home-preview"
|
||||
(steps-to-preview (deref steps) (deref step-idx))))))
|
||||
(steps-to-preview (deref steps) (deref step-idx))))))))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user