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:
2026-05-06 21:45:44 +00:00
parent e85a828de8
commit e80e655b51
3 changed files with 54 additions and 2 deletions

View File

@@ -182,7 +182,7 @@ these when operands are known numbers/lists.
| Step | Status | Commit |
|------|--------|--------|
| 1 — JIT combinator bug | [x] | 882a4b76 |
| 2 — letrec+resume | [ ] | |
| 2 — letrec+resume | [x] | (pending) |
| 3 — tokenizer :end/:line | [ ] | — |
| 4 — parser spans complete | [ ] | — |
| 5 — OCaml AdtValue + define-type + match | [ ] | — |