Repository navigation
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 %0inscarlet dis); the VM ignored it, with a comment saying a fresh cell is always right.How it works. A
Dropthe compiler paired with a later constructor lowers toInstr::DropReuse. When it is the last reference,Heap::hollowgives up everything the cell holds and keeps the allocation — header, kind and size intact — in the register. The pairedInstr::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_ctorguards 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:
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/internalgainscells_madeandcells_reused, besidestack_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.scrlinexamples/andtests/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.