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:
@@ -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))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user