VM import suspension for browser lazy loading

Bytecode compiler now emits OP_PERFORM for (import ...) and compiles
(define-library ...) bodies. The VM stores the import request in
globals["__io_request"] and stops the run loop — no exceptions needed.
vm-execute-module returns a suspension dict, vm-resume-module continues.

Browser: sx_browser.ml detects suspension dicts from execute_module and
returns JS {suspended, op, request, resume} objects. The sx-platform.js
while loop handles cascading suspensions via handleImportSuspension.

13 modules load via .sxbc bytecode in 226ms (manifest-driven), both
islands hydrate, all handlers wired. 2650/2650 tests pass including
6 new vm-import-suspension tests.

Also: consolidated sx-platform-2.js → sx-platform.js, fixed
vm-execute-module missing code-from-value call, fixed bootstrap.py
protocol registry transpiler issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 17:11:12 +00:00
parent efd0d9168f
commit 2727577702
43 changed files with 4672 additions and 3991 deletions

View File

@@ -243,7 +243,7 @@ let api_parse src_js =
(** Build a JS suspension marker for the platform to handle.
Returns {suspended: true, op: string, request: obj, resume: fn(result)} *)
let make_js_suspension request resume_fn =
let _make_js_suspension request resume_fn =
let obj = Js.Unsafe.obj [||] in
Js.Unsafe.set obj (Js.string "suspended") (Js.Unsafe.inject (Js.bool true));
let op = match request with
@@ -380,44 +380,36 @@ let sync_vm_to_env () =
end
) _vm_globals
(** Recursive suspension handler: resumes VM, catches further suspensions,
resolves imports locally when possible, otherwise returns JS suspension
objects that the platform's while loop can process. *)
let rec resume_with_suspensions vm result =
try
let v = Sx_vm.resume_vm vm result in
(** Convert a VM suspension dict to a JS suspension object for the platform. *)
let rec make_js_import_suspension (d : (string, value) Hashtbl.t) =
let obj = Js.Unsafe.obj [||] in
Js.Unsafe.set obj (Js.string "suspended") (Js.Unsafe.inject Js._true);
Js.Unsafe.set obj (Js.string "op") (Js.Unsafe.inject (Js.string "import"));
let request = match Hashtbl.find_opt d "request" with Some v -> v | None -> Nil in
Js.Unsafe.set obj (Js.string "request") (value_to_js request);
(* resume callback: clears __io_request, pushes nil, re-runs VM *)
Js.Unsafe.set obj (Js.string "resume") (Js.wrap_callback (fun _result_js ->
let resumed = Sx_vm_ref.resume_module (Dict d) in
sync_vm_to_env ();
value_to_js v
with Sx_vm.VmSuspended (request, vm2) ->
handle_suspension request vm2
and handle_suspension request vm =
let op = match request with
| Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "")
| _ -> "" in
if op = "import" then
match handle_import_suspension request with
| Some result ->
(* Library already loaded — resume and handle further suspensions *)
resume_with_suspensions vm result
| None ->
(* Library not loaded — return suspension to JS for async fetch *)
Js.Unsafe.inject (make_js_suspension request (fun _result ->
resume_with_suspensions vm Nil))
else
Js.Unsafe.inject (make_js_suspension request (fun result ->
resume_with_suspensions vm result))
match resumed with
| Dict d2 when (match Hashtbl.find_opt d2 "suspended" with Some (Bool true) -> true | _ -> false) ->
Js.Unsafe.inject (make_js_import_suspension d2)
| result -> value_to_js result));
obj
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 _result = Sx_vm_ref.execute_module code _vm_globals in
sync_vm_to_env ();
Js.Unsafe.inject (Hashtbl.length _vm_globals)
let result = Sx_vm_ref.execute_module code _vm_globals in
match result with
| Dict d when (match Hashtbl.find_opt d "suspended" with Some (Bool true) -> true | _ -> false) ->
(* VM suspended on OP_PERFORM (import) — return JS suspension object *)
Js.Unsafe.inject (make_js_import_suspension d)
| _ ->
sync_vm_to_env ();
Js.Unsafe.inject (Hashtbl.length _vm_globals)
with
| Sx_vm.VmSuspended (request, vm) ->
handle_suspension request vm
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))
@@ -628,7 +620,7 @@ let () =
in
let module_val = convert_code code_form in
let code = Sx_vm.code_from_value module_val in
let _result = Sx_vm_ref.execute_module code _vm_globals in
let _result = Sx_vm.execute_module code _vm_globals in
sync_vm_to_env ();
Number (float_of_int (Hashtbl.length _vm_globals))
| _ -> raise (Eval_error "load-sxbc: expected (sxbc version hash (code ...))"));