Step 5: CEK IO suspension + R7RS modules (define-library/import)

Third CEK phase "io-suspended": perform suspends evaluation, host
resolves IO, cek-resume feeds result back. VM OP_PERFORM (opcode 112)
enables JIT-compiled functions to suspend. VM→CEK→suspend chain
propagates suspension across the JIT/CEK boundary via pending_cek.

R7RS define-library creates isolated environments with export control.
import checks the library registry and suspends for unknown libraries,
enabling lazy on-demand loading. Import qualifiers: only, prefix.

Server-side cek_run_with_io handles suspension by dispatching IO
requests to the Python bridge and resuming. guard composes cleanly
with perform for structured error recovery across IO boundaries.

2598/2598 tests (30 new: 15 core suspension, 3 JIT, 1 cross-boundary,
9 modules, 2 error handling). Zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 18:55:43 +00:00
parent 9b8a8dd272
commit 1dd4c87d64
14 changed files with 3980 additions and 2477 deletions

File diff suppressed because one or more lines are too long

View File

@@ -26,8 +26,14 @@ type vm = {
mutable sp : int;
mutable frames : frame list;
globals : (string, value) Hashtbl.t; (* live reference to kernel env *)
mutable pending_cek : value option; (* suspended CEK state from Component/Lambda call *)
}
(** Raised when OP_PERFORM is executed. Carries the IO request dict
and a reference to the VM (which is in a resumable state:
ip past OP_PERFORM, stack ready for a result push). *)
exception VmSuspended of value * vm
(** Forward reference for JIT compilation — set after definition. *)
let jit_compile_ref : (lambda -> (string, value) Hashtbl.t -> vm_closure option) ref =
ref (fun _ _ -> None)
@@ -49,7 +55,7 @@ let is_jit_failed cl = cl.vm_code.vc_arity = -1
let _active_vm : vm option ref = ref None
let create globals =
{ stack = Array.make 4096 Nil; sp = 0; frames = []; globals }
{ stack = Array.make 4096 Nil; sp = 0; frames = []; globals; pending_cek = None }
(** Stack ops — inlined for speed. *)
let push vm v =
@@ -128,6 +134,19 @@ let code_from_value v =
{ vc_arity = arity; vc_locals = arity + 16; vc_bytecode = bc_list; vc_constants = constants }
| _ -> { vc_arity = 0; vc_locals = 16; vc_bytecode = [||]; vc_constants = [||] }
(** Call an SX value via CEK, detecting suspension instead of erroring.
Returns the result value, or raises VmSuspended if CEK suspends.
Saves the suspended CEK state in vm.pending_cek for later resume. *)
let cek_call_or_suspend vm f args =
let a = match args with Nil -> [] | List l -> l | _ -> [args] in
let state = Sx_ref.continue_with_call f (List a) (Env (Sx_types.make_env ())) (List a) (List []) in
let final = Sx_ref.cek_step_loop state in
match Sx_runtime.get_val final (String "phase") with
| String "io-suspended" ->
vm.pending_cek <- Some final;
raise (VmSuspended (Sx_runtime.get_val final (String "request"), vm))
| _ -> Sx_ref.cek_value final
(** Execute a closure with arguments — creates a fresh VM.
Used for entry points: JIT Lambda calls, module execution, cross-boundary. *)
let rec call_closure cl args globals =
@@ -165,12 +184,11 @@ and vm_call vm f args =
not the caller's globals. Closure vars were merged at compile time. *)
(try push vm (call_closure cl args cl.vm_env_ref)
with _e ->
(* Fallback to CEK — data-dependent error, not a JIT bug.
Dedup logging happens in register_jit_hook. *)
push vm (Sx_ref.cek_call f (List args)))
(* Fallback to CEK — suspension-aware *)
push vm (cek_call_or_suspend vm f (List args)))
| Some _ ->
(* Compile failed — CEK *)
push vm (Sx_ref.cek_call f (List args))
(* Compile failed — CEK, suspension-aware *)
push vm (cek_call_or_suspend vm f (List args))
| None ->
if l.l_name <> None
then begin
@@ -180,17 +198,16 @@ and vm_call vm f args =
| Some cl ->
l.l_compiled <- Some cl;
(try push vm (call_closure cl args cl.vm_env_ref)
with _e -> push vm (Sx_ref.cek_call f (List args)))
with _e -> push vm (cek_call_or_suspend vm f (List args)))
| None ->
push vm (Sx_ref.cek_call f (List args))
push vm (cek_call_or_suspend vm f (List args))
end
else
push vm (Sx_ref.cek_call f (List args)))
push vm (cek_call_or_suspend vm f (List args)))
| Component _ | Island _ ->
(* Components use keyword-arg parsing — CEK handles this *)
(* Components use keyword-arg parsing — CEK handles this, suspension-aware *)
incr _vm_cek_count;
let result = Sx_ref.cek_call f (List args) in
push vm result
push vm (cek_call_or_suspend vm f (List args))
| _ ->
raise (Eval_error ("VM: not callable: " ^ Sx_runtime.value_to_str f))
@@ -534,6 +551,11 @@ and run vm =
| Number x -> Number (x -. 1.0)
| _ -> (Hashtbl.find Sx_primitives.primitives "dec") [v])
(* ---- IO Suspension ---- *)
| 112 (* OP_PERFORM *) ->
let request = pop vm in
raise (VmSuspended (request, vm))
| opcode ->
raise (Eval_error (Printf.sprintf "VM: unknown opcode %d at ip=%d"
opcode (frame.ip - 1)))
@@ -546,6 +568,26 @@ and run vm =
end
done
(** Resume a suspended VM by pushing the IO result and continuing.
May raise VmSuspended again if the VM hits another OP_PERFORM. *)
let resume_vm vm result =
(match vm.pending_cek with
| Some cek_state ->
(* Resume the suspended CEK evaluation first *)
vm.pending_cek <- None;
let final = Sx_ref.cek_resume cek_state result in
(match Sx_runtime.get_val final (String "phase") with
| String "io-suspended" ->
(* CEK suspended again — re-suspend the VM *)
vm.pending_cek <- Some final;
raise (VmSuspended (Sx_runtime.get_val final (String "request"), vm))
| _ ->
push vm (Sx_ref.cek_value final))
| None ->
push vm result);
run vm;
pop vm
(** Execute a compiled module (top-level bytecode). *)
let execute_module code globals =
let cl = { vm_code = code; vm_upvalues = [||]; vm_name = Some "module"; vm_env_ref = globals; vm_closure_env = None } in