Browser JIT: compile SX lambdas to bytecode VM in WASM kernel

- Wire up jit_call_hook in sx_browser.ml (same pattern as server)
- Deferred JIT: _jit_enabled flag, enabled after boot-init completes
  (prevents "Undefined symbol" errors from compiling during .sx loading)
- enable-jit! native function called by sx-platform.js after boot
- sx-platform.js: async WASM kernel polling + JIT enable after init
- Error logging for JIT compile failures and runtime fallbacks

Performance: 858ms → 431ms (WASM CEK) → 101ms (WASM JIT)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 17:04:39 +00:00
parent c72a5af04d
commit 226c01bbf6
12 changed files with 76 additions and 5 deletions

View File

@@ -16,8 +16,7 @@
(function() {
"use strict";
var K = globalThis.SxKernel;
if (!K) { console.error("[sx-platform] SxKernel not found"); return; }
function boot(K) {
// ================================================================
// 8 FFI Host Primitives
@@ -340,6 +339,7 @@
var _doInit = function() {
loadWebStack();
Sx.init();
K.eval('(enable-jit!)');
};
if (document.readyState === "loading") {
@@ -349,4 +349,16 @@
}
}
} // end boot
// SxKernel is available synchronously (js_of_ocaml) or after async
// WASM init. Poll briefly to handle both cases.
var K = globalThis.SxKernel;
if (K) { boot(K); return; }
var tries = 0;
var poll = setInterval(function() {
K = globalThis.SxKernel;
if (K) { clearInterval(poll); boot(K); }
else if (++tries > 100) { clearInterval(poll); console.error("[sx-platform] SxKernel not found after 5s"); }
}, 50);
})();

View File

@@ -555,7 +555,62 @@ let () =
bind "request-arg" (fun args -> match args with [_; d] -> d | _ -> Nil);
bind "request-method" (fun _ -> String "GET");
bind "ctx" (fun _ -> Nil);
bind "helper" (fun _ -> Nil)
bind "helper" (fun _ -> Nil);
()
(* ================================================================== *)
(* JIT compilation hook *)
(* *)
(* On first call to a named lambda, try to compile it to bytecode via *)
(* compiler.sx (loaded as an .sx platform file). Compiled closures run *)
(* on the bytecode VM; failures fall back to the CEK interpreter. *)
(* ================================================================== *)
let _jit_compiling = ref false
let _jit_enabled = ref false
let () =
(* Convert int-keyed env.bindings to string-keyed Hashtbl for VM globals *)
let env_to_vm_globals env =
let g = Hashtbl.create (Hashtbl.length env.bindings) in
Hashtbl.iter (fun id v -> Hashtbl.replace g (unintern id) v) env.bindings;
g
in
Sx_ref.jit_call_hook := Some (fun f args ->
match f with
| Lambda l when !_jit_enabled ->
(match l.l_compiled with
| Some cl when not (Sx_vm.is_jit_failed cl) ->
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
with e ->
let fn_name = match l.l_name with Some n -> n | None -> "?" in
Printf.eprintf "[jit] DISABLED %s — %s\n%!" fn_name (Printexc.to_string e);
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
None)
| Some _ -> None
| None ->
if !_jit_compiling then None
else begin
_jit_compiling := true;
let globals = env_to_vm_globals global_env in
let compiled = Sx_vm.jit_compile_lambda l globals in
_jit_compiling := false;
let fn_name = match l.l_name with Some n -> n | None -> "?" in
(match compiled with
| Some cl ->
l.l_compiled <- Some cl;
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
with e ->
Printf.eprintf "[jit] DISABLED %s — %s\n%!" fn_name (Printexc.to_string e);
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
None)
| None ->
Printf.eprintf "[jit] FAIL %s\n%!" fn_name;
None)
end)
| _ -> None)
let () = ignore (env_bind global_env "enable-jit!" (NativeFn ("enable-jit!", fun _ -> _jit_enabled := true; Nil)))
(* ================================================================== *)
(* Register global SxKernel object *)