WIP: bytecode module pre-compilation and loading infrastructure

- compile-modules.js: Node.js build tool, all 23 .sx files compile to .sxbc.json
- api_load_module with shared globals (beginModuleLoad/endModuleLoad batch API)
- api_compile_module for runtime compilation
- sx-platform.js: bytecode-first loader with source fallback, JSON deserializer
- Deferred JIT enable (setTimeout after boot)

Known issues:
- WASM browser: loadModule loads but functions not accessible (env writeback
  issue with interned keys)
- WASM browser: compileModule fails ("Not callable: nil" — compile-module
  function from bytecode not working correctly in WASM context)
- Node.js js_of_ocaml: full roundtrip works (compile → load → call)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 17:54:55 +00:00
parent cb7bbc9557
commit 00de248ee9
32 changed files with 351 additions and 28 deletions

View File

@@ -231,17 +231,46 @@ let api_load src_js =
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
| Parse_error msg -> Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
(* Shared globals table for batch module loading.
Created by beginModuleLoad, accumulated across loadModule calls,
flushed to env by endModuleLoad. Ensures closures from early
modules can see definitions from later modules. *)
let _module_globals : (string, value) Hashtbl.t option ref = ref None
let api_begin_module_load () =
let g = Hashtbl.create 512 in
Hashtbl.iter (fun id v -> Hashtbl.replace g (unintern id) v) global_env.bindings;
_module_globals := Some g;
Js.Unsafe.inject true
let api_end_module_load () =
(match !_module_globals with
| Some g ->
Hashtbl.iter (fun k v ->
Hashtbl.replace global_env.bindings (intern k) v
) g;
_module_globals := None
| None -> ());
Js.Unsafe.inject true
let api_load_module module_js =
try
let code_val = js_to_value module_js in
let code = Sx_vm.code_from_value code_val in
let globals = Hashtbl.create 256 in
Hashtbl.iter (fun id v -> Hashtbl.replace globals (unintern id) v) global_env.bindings;
let globals = match !_module_globals with
| Some g -> g (* use shared table *)
| None ->
(* standalone mode: create temp table *)
let g = Hashtbl.create 256 in
Hashtbl.iter (fun id v -> Hashtbl.replace g (unintern id) v) global_env.bindings;
g
in
let _result = Sx_vm.execute_module code globals in
(* Copy all globals back into env — new defines + unchanged values *)
Hashtbl.iter (fun k v ->
Hashtbl.replace global_env.bindings (intern k) v
) globals;
(* If standalone (no batch), copy back immediately *)
if !_module_globals = None then
Hashtbl.iter (fun k v ->
Hashtbl.replace global_env.bindings (intern k) v
) globals;
Js.Unsafe.inject (Hashtbl.length globals)
with
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
@@ -653,6 +682,8 @@ let () =
Js.Unsafe.set sx (Js.string "renderToHtml") (Js.wrap_callback api_render_to_html);
Js.Unsafe.set sx (Js.string "load") (Js.wrap_callback api_load);
Js.Unsafe.set sx (Js.string "loadModule") (Js.wrap_callback api_load_module);
Js.Unsafe.set sx (Js.string "beginModuleLoad") (Js.wrap_callback (fun () -> api_begin_module_load ()));
Js.Unsafe.set sx (Js.string "endModuleLoad") (Js.wrap_callback (fun () -> api_end_module_load ()));
Js.Unsafe.set sx (Js.string "compileModule") (wrap api_compile_module);
Js.Unsafe.set sx (Js.string "typeOf") (Js.wrap_callback api_type_of);
Js.Unsafe.set sx (Js.string "inspect") (Js.wrap_callback api_inspect);