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:
@@ -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);
|
||||
})();
|
||||
|
||||
@@ -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 *)
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
var _doInit = function() {
|
||||
loadWebStack();
|
||||
Sx.init();
|
||||
K.eval('(enable-jit!)');
|
||||
};
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/start-29cf9a72.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/start-29cf9a72.wasm
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user