Skip to content

reuse cells instead of allocating - #124

Merged
alii merged 1 commit into
masterfrom
vm-reuse
Sep 24, 2026
Merged

alii merged 1 commit into
masterfrom
vm-reuse

Conversation

@alii

@alii alii commented Sep 23, 2026

Copy link
Copy Markdown
Member

The VM now does what Perceus reference counting is for: a constructor overwrites a cell nothing else holds any more, instead of allocating a new one. The compiler has emitted the pairing all along (ctor 768.0(%4, %5) reuse %0 in scarlet dis); the VM ignored it, with a comment saying a fresh cell is always right.

How it works. A Drop the compiler paired with a later constructor lowers to Instr::DropReuse. When it is the last reference, Heap::hollow gives up everything the cell holds and keeps the allocation — header, kind and size intact — in the register. The paired Instr::Ctor { reuse } takes it and writes over it. Anything else in that register is a value the frame is done with, released as the register would have.

Heap::fits_ctor guards the overwrite on the count, the kind and the exact size, so a cell someone else still holds is never written over, and a constructor of another shape allocates instead.

Loops. A call to self in tail position reuses its frame, and used to release every register. It now keeps the ones a constructor reuses (Body::carried), so a loop can take the cell its last turn gave up. Without that, the cell is freed at the back edge and the next turn allocates: 1002 allocations rather than 2.

Numbers. A 1000-turn loop over a two-field constructor:

allocations reused
gives the cell up before building 2 999
gives it up after, so the next turn takes it 1 1000

At 3,000,000 turns the same loop goes from 0.29 s to 0.22 s, best of three, release build.

Where it does not fire yet. Reuse waits on the last reference, so a walk over a list still allocates: a call shares its argument and gives it up only after returning, so the caller's reference is still there when the callee drops its own. "Moves on last use" is what turns it on for those, and it is the next step in docs/vm-design.md.

Seeing it. scarlet/internal gains cells_made and cells_reused, beside stack_depth — the same kind of hook, for debugging and for the tests here.

Tests. Three in crates/scarlet_vm/tests/run.rs, each pinning the saving rather than just the answer: reuse within a turn, reuse carried across the call that ends one, and a pair held in a list that allocates every turn and still reads back as written. Every .scrl in examples/ and tests/programs/ prints exactly what it printed before.

All seven CI gates pass locally. hawk ran for the host target, and nothing here is gated on a cfg.

@alii
alii merged commit ddcf514 into master Sep 24, 2026
4 checks passed
@alii
alii deleted the vm-reuse branch September 24, 2026 00:36
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.

1 participant