Skip to content

encore_vm: clear registers when a call returns - #15

Merged
vbergeron merged 1 commit into
mainfrom
claude/brave-galileo-x97dr3
Sep 23, 2026
Merged

vbergeron merged 1 commit into
mainfrom
claude/brave-galileo-x97dr3

Conversation

@vbergeron

Copy link
Copy Markdown
Owner

Problem

The 256-register file is scanned as GC roots, but a call that returned (or failed) left whatever it last held in its registers. Those values stayed live until a later call happened to overwrite each register, so a host calling the VM repeatedly carried the previous call's data through the next call's collections.

Found in encore-benchmarks: W6 (COBS, 1024-byte frame) failed its timed run at 40 KiB of heap even though the same run on the untimed pass fit. The previous run's input, encoding and decoded lists were still reachable from stale registers.

Fix

call_raw and call_address now go through run_to_completion, which runs enter() and then resets the registers, on success and on error. The reset is skipped inside an extern, whose caller's registers are still in use.

Tests

  • New test_gc_ignores_registers_of_finished_calls: a 3-word heap that holds only one ctor; each call must reclaim the previous call's ctor, which only a stale register points to. It fails with HeapOverflow before the fix and passes after.
  • cargo test --workspace and cargo test --workspace --all-features pass.
  • encore-benchmarks with this branch patched in: W0, W4 and W6 (variants R and E, both QEMU boards, timing and memory profiles) all pass, with 0 mismatches against the R oracle. cargo xtask minheap -w w6_cobs -v e drops from 53.5 KiB to 33,024 B.

Once this is released (0.1.6?), encore-benchmarks will bump to it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ALRGkKEshzfh8jxNFEeAmB


Generated by Claude Code

The register file is a GC root set, and a call that returned (or failed)
left whatever it last held in its registers. Those values stayed live
until a later call happened to overwrite each register, so a host that
calls the VM repeatedly carried the previous call's data through the next
call's collections.

In encore-benchmarks, W6 (COBS, 1024-byte frame) needed 53.5 KiB of heap
for its timed run, although a run on a fresh VM fits in 40 KiB: the
previous run's input, encoding and decoded lists were still reachable
from stale registers. With the registers cleared, the case list fits in
32.25 KiB.

The registers are reset after the top-level run of call_raw and
call_address, on success and on error. They are left alone inside an
extern, whose caller's registers are still in use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ALRGkKEshzfh8jxNFEeAmB
@vbergeron
vbergeron merged commit 14230b1 into main Sep 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants