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 ->
|
| Lambda l ->
|
||||||
(match l.l_compiled with
|
(match l.l_compiled with
|
||||||
| Some cl when not (is_jit_failed cl) ->
|
| Some cl when not (is_jit_failed cl) ->
|
||||||
(* Cached bytecode — run on VM, fall back to CEK on runtime error *)
|
(* Cached bytecode — run on VM using the closure's captured env,
|
||||||
(try push vm (call_closure cl args vm.globals)
|
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)))
|
with _ -> push vm (Sx_ref.cek_call f (List args)))
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
(* Compile failed — CEK *)
|
(* Compile failed — CEK *)
|
||||||
@@ -159,7 +160,7 @@ and vm_call vm f args =
|
|||||||
match !jit_compile_ref l vm.globals with
|
match !jit_compile_ref l vm.globals with
|
||||||
| Some cl ->
|
| Some cl ->
|
||||||
l.l_compiled <- 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 _ ->
|
with _ ->
|
||||||
l.l_compiled <- Some jit_failed_sentinel;
|
l.l_compiled <- Some jit_failed_sentinel;
|
||||||
push vm (Sx_ref.cek_call f (List args)))
|
push vm (Sx_ref.cek_call f (List args)))
|
||||||
@@ -400,36 +401,48 @@ and run vm =
|
|||||||
let v = peek vm in
|
let v = peek vm in
|
||||||
Hashtbl.replace vm.globals name v
|
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 *) ->
|
| 160 (* OP_ADD *) ->
|
||||||
let b = pop vm and a = pop vm in
|
let b = pop vm and a = pop vm in
|
||||||
push vm (match a, b with
|
push vm (match a, b with
|
||||||
| Number x, Number y -> Number (x +. y)
|
| Number x, Number y -> Number (x +. y)
|
||||||
| String x, String y -> String (x ^ y)
|
| _ -> (Hashtbl.find Sx_primitives.primitives "+") [a; b])
|
||||||
| _ -> Sx_primitives.(get_primitive "+" |> function NativeFn (_, f) -> f [a; b] | _ -> Nil))
|
|
||||||
| 161 (* OP_SUB *) ->
|
| 161 (* OP_SUB *) ->
|
||||||
let b = pop vm and a = pop vm in
|
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 *) ->
|
| 162 (* OP_MUL *) ->
|
||||||
let b = pop vm and a = pop vm in
|
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 *) ->
|
| 163 (* OP_DIV *) ->
|
||||||
let b = pop vm and a = pop vm in
|
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 *) ->
|
| 164 (* OP_EQ *) ->
|
||||||
let b = pop vm and a = pop vm in
|
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
|
let rec norm = function
|
||||||
| ListRef { contents = l } -> List (List.map norm l)
|
| ListRef { contents = l } -> List (List.map norm l)
|
||||||
| List l -> List (List.map norm l) | v -> v in
|
| List l -> List (List.map norm l) | v -> v in
|
||||||
push vm (Bool (norm a = norm b))
|
push vm (Bool (norm a = norm b))
|
||||||
| 165 (* OP_LT *) ->
|
| 165 (* OP_LT *) ->
|
||||||
let b = pop vm and a = pop vm in
|
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 *) ->
|
| 166 (* OP_GT *) ->
|
||||||
let b = pop vm and a = pop vm in
|
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 *) ->
|
| 167 (* OP_NOT *) ->
|
||||||
let v = pop vm in
|
let v = pop vm in
|
||||||
push vm (Bool (not (sx_truthy v)))
|
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))
|
| List l | ListRef { contents = l } -> Number (float_of_int (List.length l))
|
||||||
| String s -> Number (float_of_int (String.length s))
|
| String s -> Number (float_of_int (String.length s))
|
||||||
| Dict d -> Number (float_of_int (Hashtbl.length d))
|
| 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 *) ->
|
| 169 (* OP_FIRST *) ->
|
||||||
let v = pop vm in
|
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 *) ->
|
| 170 (* OP_REST *) ->
|
||||||
let v = pop vm in
|
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 *) ->
|
| 171 (* OP_NTH *) ->
|
||||||
let n = pop vm and coll = pop vm in
|
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, n with
|
||||||
push vm (match coll with
|
| (List l | ListRef { contents = l }), Number f ->
|
||||||
| List l | ListRef { contents = l } ->
|
(try List.nth l (int_of_float f) with _ -> Nil)
|
||||||
(try List.nth l i with _ -> Nil)
|
| String s, Number f ->
|
||||||
| _ -> Nil)
|
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 *) ->
|
| 172 (* OP_CONS *) ->
|
||||||
let coll = pop vm and x = pop vm in
|
let coll = pop vm and x = pop vm in
|
||||||
push vm (match coll with
|
push vm (match coll with
|
||||||
| List l -> List (x :: l)
|
| List l -> List (x :: l)
|
||||||
| ListRef { contents = l } -> List (x :: l)
|
| ListRef { contents = l } -> List (x :: l)
|
||||||
| Nil -> List [x]
|
| Nil -> List [x]
|
||||||
| _ -> List [x])
|
| _ -> (Hashtbl.find Sx_primitives.primitives "cons") [x; coll])
|
||||||
| 173 (* OP_NEG *) ->
|
| 173 (* OP_NEG *) ->
|
||||||
let v = pop vm in
|
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 *) ->
|
| 174 (* OP_INC *) ->
|
||||||
let v = pop vm in
|
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 *) ->
|
| 175 (* OP_DEC *) ->
|
||||||
let v = pop vm in
|
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 ->
|
| opcode ->
|
||||||
raise (Eval_error (Printf.sprintf "VM: unknown opcode %d at ip=%d"
|
raise (Eval_error (Printf.sprintf "VM: unknown opcode %d at ip=%d"
|
||||||
|
|||||||
@@ -219,23 +219,17 @@
|
|||||||
;; Component expressions handled by lake's reactive render
|
;; Component expressions handled by lake's reactive render
|
||||||
nil))
|
nil))
|
||||||
(swap! step-idx inc)
|
(swap! step-idx inc)
|
||||||
(update-code-highlight)))))
|
(update-code-highlight)
|
||||||
(rebuild-preview (fn (target)
|
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))))
|
||||||
(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))))))
|
|
||||||
(do-back (fn ()
|
(do-back (fn ()
|
||||||
(when (> (deref step-idx) 0)
|
(when (> (deref step-idx) 0)
|
||||||
(let ((target (- (deref step-idx) 1)))
|
(let ((target (- (deref step-idx) 1))
|
||||||
(rebuild-preview target)
|
(container (get-preview)))
|
||||||
(reset! step-idx target)
|
(when container (dom-set-prop container "innerHTML" ""))
|
||||||
(update-code-highlight)
|
(set-stack (list (get-preview)))
|
||||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper"))))))))
|
(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 for persistence — same mechanism, cookie storage
|
||||||
(freeze-scope "home-stepper" (fn ()
|
(freeze-scope "home-stepper" (fn ()
|
||||||
(freeze-signal "step" step-idx)))
|
(freeze-signal "step" step-idx)))
|
||||||
@@ -260,7 +254,13 @@
|
|||||||
(let ((_eff (effect (fn ()
|
(let ((_eff (effect (fn ()
|
||||||
(schedule-idle (fn ()
|
(schedule-idle (fn ()
|
||||||
(build-code-dom)
|
(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)
|
(update-code-highlight)
|
||||||
(run-post-render-hooks)))))))
|
(run-post-render-hooks)))))))
|
||||||
(div :class "space-y-4"
|
(div :class "space-y-4"
|
||||||
@@ -282,9 +282,7 @@
|
|||||||
(deref code-tokens)))
|
(deref code-tokens)))
|
||||||
;; Controls
|
;; Controls
|
||||||
(div :class "flex items-center justify-center gap-2 md:gap-3"
|
(div :class "flex items-center justify-center gap-2 md:gap-3"
|
||||||
(button :on-click (fn (e)
|
(button :on-click (fn (e) (do-back))
|
||||||
(do-back)
|
|
||||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))
|
|
||||||
:class (str "px-2 py-1 rounded text-3xl "
|
:class (str "px-2 py-1 rounded text-3xl "
|
||||||
(if (> (deref step-idx) 0)
|
(if (> (deref step-idx) 0)
|
||||||
"text-stone-600 hover:text-stone-800 hover:bg-stone-100"
|
"text-stone-600 hover:text-stone-800 hover:bg-stone-100"
|
||||||
@@ -292,9 +290,7 @@
|
|||||||
"\u25c0")
|
"\u25c0")
|
||||||
(span :class "text-sm text-stone-500 font-mono tabular-nums"
|
(span :class "text-sm text-stone-500 font-mono tabular-nums"
|
||||||
(deref step-idx) " / " (len (deref steps)))
|
(deref step-idx) " / " (len (deref steps)))
|
||||||
(button :on-click (fn (e)
|
(button :on-click (fn (e) (do-step))
|
||||||
(do-step)
|
|
||||||
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))
|
|
||||||
:class (str "px-2 py-1 rounded text-3xl "
|
:class (str "px-2 py-1 rounded text-3xl "
|
||||||
(if (< (deref step-idx) (len (deref steps)))
|
(if (< (deref step-idx) (len (deref steps)))
|
||||||
"text-violet-600 hover:text-violet-800 hover:bg-violet-50"
|
"text-violet-600 hover:text-violet-800 hover:bg-violet-50"
|
||||||
@@ -303,6 +299,6 @@
|
|||||||
;; Live preview — shows partial result up to current step.
|
;; Live preview — shows partial result up to current step.
|
||||||
;; Same SX rendered by server (HTML) and client (DOM).
|
;; Same SX rendered by server (HTML) and client (DOM).
|
||||||
(lake :id "home-preview"
|
(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