From 6456bd927a9adecb22f0dfb3f9f3b9da41fbf95e Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 8 Apr 2026 21:34:10 +0000 Subject: [PATCH] Fix bytecode when/do/perform: snapshot pending_cek in resume closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: nested cek_call_or_suspend calls on the same VM (from synchronous callbacks like dom-listen firing handler immediately) overwrote pending_cek before the first resume ran. Fix: _vm_suspension_to_dict snapshots pending_cek at capture time and restores it in the resume closure before calling resume_vm. This ensures each suspension's CEK state is preserved regardless of nested overwrite. test_bytecode_repeat.js: 4/4 pass (was 3/4). Source: 6 suspensions ✓ Bytecode: 6 suspensions ✓ Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/lib/sx_vm.ml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hosts/ocaml/lib/sx_vm.ml b/hosts/ocaml/lib/sx_vm.ml index bda0b056..c16edff8 100644 --- a/hosts/ocaml/lib/sx_vm.ml +++ b/hosts/ocaml/lib/sx_vm.ml @@ -814,12 +814,10 @@ and run vm = 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)) | _ -> @@ -948,12 +946,18 @@ let () = _vm_call_closure_ref := (fun cl args -> call_closure_reuse cl args) let () = _vm_suspension_to_dict := (fun exn -> match exn with | VmSuspended (request, vm) -> + (* Snapshot pending_cek NOW — a nested cek_call_or_suspend on the same VM + may overwrite it before our resume function is called. *) + let saved_cek = vm.pending_cek in let d = Hashtbl.create 3 in Hashtbl.replace d "__vm_suspended" (Bool true); Hashtbl.replace d "request" request; Hashtbl.replace d "resume" (NativeFn ("vm-resume", fun args -> match args with | [result] -> + (* Restore the saved pending_cek before resuming — it may have been + overwritten by a nested suspension on the same VM. *) + vm.pending_cek <- saved_cek; (try resume_vm vm result with exn2 -> match !_vm_suspension_to_dict exn2 with