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 *)

View File

@@ -339,6 +339,7 @@
var _doInit = function() {
loadWebStack();
Sx.init();
K.eval('(enable-jit!)');
};
if (document.readyState === "loading") {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1792,7 +1792,7 @@
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
}
(globalThis))
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-ea36a0db",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-4feaf1fd",[2,3,5]],["std_exit-10fb8830",[2]],["start-9e868ccd",0]],"generated":(b=>{var
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-ea36a0db",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-cafa954c",[2,3,5]],["std_exit-10fb8830",[2]],["start-29cf9a72",0]],"generated":(b=>{var
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new