VM: VmClosure value type + iterative run loop + define hoisting + SSR fixes
Core VM changes: - Add VmClosure value variant — inner closures created by OP_CLOSURE are first-class VM values, not NativeFn wrappers around call_closure - Convert `run` from recursive to while-loop — zero OCaml stack growth, true TCO for VmClosure tail calls - vm_call handles VmClosure by pushing frame on current VM (no new VM allocation per call) - Forward ref _vm_call_closure_ref for cross-boundary calls (CEK/primitives) Compiler (spec/compiler.sx): - Define hoisting in compile-begin: pre-allocate local slots for all define forms before compiling any values. Fixes forward references between inner functions (e.g. read-expr referencing skip-ws in sx-parse) - scope-define-local made idempotent (skip if slot already exists) Server (sx_server.ml): - JIT fail-once sentinel: mark l_compiled as failed after first VM runtime error. Eliminates thousands of retry attempts per page render. - HTML tag bindings: register all HTML tags as pass-through NativeFns so eval-expr can handle (div ...) etc. in island component bodies. - Log VM FAIL errors with function name before disabling JIT. SSR fixes: - adapter-html.sx letrec handler: evaluate bindings in proper letrec scope (pre-bind nil, then evaluate), render body with render-to-html instead of eval-expr. Fixes island SSR for components using letrec. - Add `init` primitive to OCaml kernel (all-but-last of list). - VmClosure handling in sx_runtime.ml sx_call dispatch. Tests: 971/971 OCaml (+19 new), 0 failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -622,6 +622,14 @@ let make_server_env () =
|
||||
| [String s] -> Bool (Sx_render.is_html_tag s)
|
||||
| _ -> Bool false);
|
||||
|
||||
(* HTML tag functions — when eval-expr encounters (div :class "foo" ...),
|
||||
it calls the tag function which returns the list as-is. The render path
|
||||
then handles it. Same approach as the DOM adapter in the browser. *)
|
||||
List.iter (fun tag ->
|
||||
ignore (env_bind env tag
|
||||
(NativeFn ("html:" ^ tag, fun args -> List (Symbol tag :: args))))
|
||||
) Sx_render.html_tags;
|
||||
|
||||
(* Spec evaluator helpers needed by render.sx when loaded at runtime *)
|
||||
bind "random-int" (fun args ->
|
||||
match args with
|
||||
@@ -907,9 +915,14 @@ let register_jit_hook env =
|
||||
| Lambda l ->
|
||||
(match l.l_compiled with
|
||||
| Some cl when not (Sx_vm.is_jit_failed cl) ->
|
||||
(* Cached bytecode — run on VM, fall back to CEK on runtime error *)
|
||||
(* Cached bytecode — run on VM, fall back to CEK on runtime error.
|
||||
Mark as failed so we don't retry on every call. *)
|
||||
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
|
||||
with _ -> None)
|
||||
with e ->
|
||||
let fn_name = match l.l_name with Some n -> n | None -> "?" in
|
||||
Printf.eprintf "[jit-hook] VM FAIL %s: %s (disabling JIT)\n%!" fn_name (Printexc.to_string e);
|
||||
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
|
||||
None)
|
||||
| Some _ -> None (* compile failed — CEK handles *)
|
||||
| None ->
|
||||
if !_jit_compiling then None
|
||||
@@ -927,7 +940,11 @@ let register_jit_hook env =
|
||||
l.l_compiled <- Some cl;
|
||||
(* Run on VM, fall back to CEK on runtime error *)
|
||||
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
|
||||
with _ -> None)
|
||||
with e ->
|
||||
let fn_name = match l.l_name with Some n -> n | None -> "?" in
|
||||
Printf.eprintf "[jit-hook] VM FAIL (first call) %s: %s (disabling JIT)\n%!" fn_name (Printexc.to_string e);
|
||||
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
|
||||
None)
|
||||
| None -> None
|
||||
end)
|
||||
| _ -> None)
|
||||
|
||||
Reference in New Issue
Block a user