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 ->
|
with Parse_error msg ->
|
||||||
Js.Unsafe.inject (Js.string ("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 api_eval src_js =
|
||||||
let src = Js.to_string src_js in
|
let src = Js.to_string src_js in
|
||||||
try
|
try
|
||||||
@@ -296,7 +323,25 @@ let api_load src_js =
|
|||||||
let exprs = Sx_parser.parse_all src in
|
let exprs = Sx_parser.parse_all src in
|
||||||
let env = Env global_env in
|
let env = Env global_env in
|
||||||
let count = ref 0 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 ();
|
sync_env_to_vm ();
|
||||||
Js.Unsafe.inject !count
|
Js.Unsafe.inject !count
|
||||||
with
|
with
|
||||||
@@ -343,6 +388,37 @@ let api_load_module module_js =
|
|||||||
sync_vm_to_env ();
|
sync_vm_to_env ();
|
||||||
Js.Unsafe.inject (Hashtbl.length _vm_globals)
|
Js.Unsafe.inject (Hashtbl.length _vm_globals)
|
||||||
with
|
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))
|
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
|
||||||
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))
|
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user