Step 5b browser wiring: VmSuspended handling in WASM kernel

Adds IO suspension support to the browser WASM kernel. When the VM
hits OP_PERFORM or the CEK suspends (e.g. from import), the kernel
now handles it instead of crashing.

sx_browser.ml additions:
- make_js_suspension: creates JS object {suspended, op, request, resume}
  for the platform to handle asynchronously
- handle_import_suspension: checks library registry, returns Some result
  if already loaded (immediate resume), None if fetch needed
- api_load_module: catches VmSuspended, resolves imports locally if
  library is loaded, returns suspension marker to JS if not
- api_load: IO-aware CEK loop using cek_step_loop/cek_suspended?/
  cek_resume — handles import suspensions during .sx file loading

This enables the lazy loading pattern: when a module's (import ...)
encounters an unloaded library, the suspension propagates to JS which
can async-fetch the .sxbc and call resume(). Currently all libraries
are pre-loaded so suspensions resolve immediately.

2608/2608 OCaml tests passing. WASM kernel builds clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 23:07:52 +00:00
parent 2d7dd7d582
commit 6008a1be30

View File

@@ -241,6 +241,33 @@ let api_parse src_js =
with Parse_error msg ->
Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
(** 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 obj = Js.Unsafe.obj [||] in
Js.Unsafe.set obj (Js.string "suspended") (Js.Unsafe.inject (Js.bool true));
let op = match request with
| Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "unknown")
| _ -> "unknown" in
Js.Unsafe.set obj (Js.string "op") (Js.Unsafe.inject (Js.string op));
Js.Unsafe.set obj (Js.string "request") (value_to_js request);
Js.Unsafe.set obj (Js.string "resume") (Js.wrap_callback (fun result_js ->
let result = js_to_value result_js in
resume_fn result));
obj
(** Handle an import suspension: load the library from the library registry
or return a suspension marker to JS for async loading. *)
let handle_import_suspension request =
let lib_spec = match request with
| Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil)
| _ -> Nil in
let key = Sx_ref.library_name_key lib_spec in
if Sx_types.sx_truthy (Sx_ref.library_loaded_p key) then
Some Nil (* Already loaded — resume immediately *)
else
None (* Not loaded — JS platform must fetch it *)
let api_eval src_js =
let src = Js.to_string src_js in
try
@@ -296,7 +323,25 @@ let api_load src_js =
let exprs = Sx_parser.parse_all src in
let env = Env global_env in
let count = ref 0 in
List.iter (fun expr -> ignore (Sx_ref.eval_expr expr env); incr count) exprs;
List.iter (fun expr ->
(* Use IO-aware eval for each expression to handle import suspensions *)
let state = Sx_ref.make_cek_state expr env (List []) in
let final = ref (Sx_ref.cek_step_loop state) in
while Sx_types.sx_truthy (Sx_ref.cek_suspended_p !final) do
let request = Sx_ref.cek_io_request !final in
let op = match request with
| Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "")
| _ -> "" in
let response = if op = "import" then begin
match handle_import_suspension request with
| Some v -> v
| None -> Nil (* Library not found — resume with nil, import will use what's in env *)
end else Nil in
final := Sx_ref.cek_resume !final response
done;
ignore (Sx_ref.cek_value !final);
incr count
) exprs;
sync_env_to_vm ();
Js.Unsafe.inject !count
with
@@ -343,6 +388,37 @@ let api_load_module module_js =
sync_vm_to_env ();
Js.Unsafe.inject (Hashtbl.length _vm_globals)
with
| Sx_vm.VmSuspended (request, vm) ->
(* VM hit OP_PERFORM — check if we can resolve locally *)
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 VM and continue *)
(try
let final = Sx_vm.resume_vm vm result in
sync_vm_to_env ();
Js.Unsafe.inject (value_to_js final)
with Sx_vm.VmSuspended (req2, vm2) ->
make_js_suspension req2 (fun result ->
let v = Sx_vm.resume_vm vm2 result in
sync_vm_to_env ();
value_to_js v))
| None ->
(* Library not loaded — return suspension to JS for async fetch *)
make_js_suspension request (fun result ->
ignore result;
(* After JS loads the library file, resume the VM *)
let v = Sx_vm.resume_vm vm Nil in
sync_vm_to_env ();
value_to_js v)
else
make_js_suspension request (fun result ->
let v = Sx_vm.resume_vm vm result in
sync_vm_to_env ();
value_to_js v)
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))