sx: step 2 — restore frame locals on browser VmSuspension resume
In `resume_vm`'s `restore_reuse`, the saved sp captured by `call_closure_reuse` was ignored when restoring the caller frame after the async callback finished. The suspended callee's locals/temps stayed on the value stack above saved_sp, so subsequent LOCAL_GET/SET in the caller frame (e.g. letrec sibling bindings waiting on the suspending call) read stale callee data instead of their own slots. Sibling bindings appeared nil after a perform/resume cycle on the JIT path used by the WASM browser kernel. Fix: after popping the callback result and restoring saved_frames, reset `vm.sp <- saved_sp` (when sp is above), then push the callback result. Mirrors the OP_RETURN+sp-reset discipline that sync `call_closure_reuse` already follows. New tests in `spec/tests/test-letrec-resume.sx` cover single binding, sibling bindings, mutual recursion siblings, and nested letrec — all four pass. Full OCaml run_tests: 4529/5868 (was 4525/5864), zero regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -896,9 +896,17 @@ let resume_vm vm result =
|
||||
let rec restore_reuse pending =
|
||||
match pending with
|
||||
| [] -> ()
|
||||
| (saved_frames, _saved_sp) :: rest ->
|
||||
| (saved_frames, saved_sp) :: rest ->
|
||||
let callback_result = pop vm in
|
||||
vm.frames <- saved_frames;
|
||||
(* Restore sp to the value captured before the suspended callee was
|
||||
pushed. The callee's locals/temps may still be on the stack above
|
||||
saved_sp; without this reset, subsequent LOCAL_GET/SET in the
|
||||
caller frame (e.g. letrec sibling bindings waiting on the call)
|
||||
see stale callee data instead of their own slots. Mirrors the
|
||||
OP_RETURN+sp-reset semantics that sync `call_closure_reuse`
|
||||
relies on for clean caller-frame state. *)
|
||||
if saved_sp < vm.sp then vm.sp <- saved_sp;
|
||||
push vm callback_result;
|
||||
(try
|
||||
run vm;
|
||||
|
||||
Reference in New Issue
Block a user