Release a consumed tuple or Option box on the VM - #1436
Merged
Merged
Conversation
The generated loop hands an answer module's state out of the run in an Option, inside a tuple, and takes the answer back in another tuple. On the VM the Option box and the tuples kept no holder count, so after a match had taken them apart they still counted as holders of the state, and every answer copied the module's Maps. #1424 fixed this on the Rust side only. Tuples and the boxes of Option.Some, Result.Ok and Result.Err now count their off-stack holders the way maps, vectors and records do: every entry, global and constant that stores one registers itself. A match whose subject nothing reads afterwards (a temporary, or a local at its last use) now releases what it takes apart: POP_CONSUMED after a tuple arm empties the tuple, and MATCH_UNWRAP with the consume bit empties the box, each only when nothing off the stack and no stack cell holds the tuple or box, and only when a Map or Vector inside makes it worth the walk. `?` does the same for an Ok box through PROPAGATE_ERR_CONSUMED. aver run --profile now also prints how many map entries the writes that were not in place copied. Release build without LTO, run_owned_answer_state scaled to 2000 requests against a 100k-entry Map: 1.84 s -> 0.13 s. The fixture as checked in (200 requests, 1k entries) is 0.06 s either way. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # src/vm/execute/slots.rs # src/vm/opcode.rs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
jasisz
added a commit
that referenced
this pull request
Sep 25, 2026
__bump read the run whole (__versionOf(run, owner)) inside the update that sets run.versions, and __park did the same with moved.now and __versionOf(moved, owner) inside the update that sets moved.slots, so on the VM neither record update could take its Map field out and every answer copied both Maps. perf-shared-update reports both when it is allowed to look at generated functions. Both now read everything else first and update the run at its last use. __take<Module> reads the state before it updates the run, so the update moves the run's other fields instead of leaving the old run holding them, and __seatFamily<P> lists the retired keys before it hands the retired Map to __unretire<P>. With the generated-function filter lifted, perf-shared-update reports nothing in the loop fixtures. On the VM the gain shows together with the consumed-tuple release (#1436), since the run reaches __serve<P><Kind> through the __take tuple: on run_owned_answer_state the writes refused because something else held the Map go from 403 to 4, and on its 2000-request version from 4003 to 4. The Maps are small, so wall time does not move. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
On the VM, the generated loop's
Option.Some(state)box and the tuples around it kept no holder count. After a match had taken them apart, they still counted as holders of the state, so every answer copied the answer module's Maps. #1424 fixed this for Rust only, and VM time onrun_owned_answer_statedid not change.What changes
I took the second option from the issue:
Optionboxes (and tuples) now take part in the holder count. The generated loop stays as it is, and any program that carries a record throughOption,Resultor a tuple benefits too.ArenaEntry::TupleandArenaEntry::Boxednow carry aholder_count, likeMap,VectorandRecord. Every entry, global and constant that stores one registers itself, through a single predicate (NanValue::counts_holders).POP_CONSUMEDafter a tuple arm empties the tuple.MATCH_UNWRAPwith the consume bit empties the box.?does the same for anOkbox, viaPROPAGATE_ERR_CONSUMED.WALK_SLACKbound map writes use). Anything else leaves it holding its value.aver run --profilenow also prints how many map entries were copied by writes that were not in place.Measurements
Release build without LTO, best of 3:
run_owned_answer_stateas checked in (200 requests, 1k-entry Map)On the checked-in fixture, main copied 1802 map writes' worth of entries and this branch copies 1399. The remaining copies are the run's own small Maps (
versions,slots), which the next PR addresses.aver bench bench/scenarios/(VM, p50, best of two rounds, machine under load from other jobs): most scenarios are within ±2%.fractal_seahorseandrecordcome out 2–3.5% slower across three runs. The fractal hot loop runs none of the new code (no tuples, wrappers or matches on them in its opcode mix), so I read this as dispatch-loop layout. The release paths are already#[inline(never)]and outside the loop.Tests
tests/vm_consumed_destructure.rs:?on a Result, and a box inside a tuple.--profile.?The consume-only rule is backed by the runtime guards: a local still live holds a stack cell, so the stack-cell check refuses. The marking switch only matters in a program whose only aggregates are tuples or boxes.
mir_vm_codegen:?on a temporary emitsPROPAGATE_ERR_CONSUMED, and on a live local it emitsPROPAGATE_ERR.cargo test -p aver-lang(native features) passes, run withoutlakeon PATH so the Lean-backed tests skipped.cargo test --libfor the other workspace crates,run_all_specandrust_work_specalso pass.cargo fmtand both non-wasm clippy commands are clean.