Repository navigation
Conversation
This comment has been minimized.
This comment has been minimized.
f718561 to
36eda9c
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Alias page-table sizing and zero-capacity checkpoint cases can prevent supported sandboxes from initializing or continuing after restore.
Review effort: Balanced
Findings: 2
Open (3)
What changed in this PR
Preserves transport-backed guest buffers across snapshot capture and restore using stable aliases and pinned pages.
Changes:
- Adds alias mapping, pruning, and mailbox checkpoint states.
- Allows partially prefilled H2G rings in snapshots.
- Adds restore tests, benchmarks, ABI 6, and documentation.
| File | Description |
|---|---|
src/tests/rust_guests/simpleguest/src/main.rs |
Adds retained buffer readers. |
src/tests/rust_guests/Cargo.lock |
Locks the new dependency. |
src/tests/c_guests/c_simpleguest/main.c |
Adds C retention fixtures. |
src/hyperlight_host/tests/snapshot_goldens/goldens_version.rs |
Advances goldens to v6. |
src/hyperlight_host/tests/sandbox_host_tests.rs |
Exercises repeated fragmented calls. |
src/hyperlight_host/tests/integration_test.rs |
Tests retained data restoration. |
src/hyperlight_host/src/sandbox/uninitialized_evolve.rs |
Completes initial checkpoints. |
src/hyperlight_host/src/sandbox/snapshot/tripwires.rs |
Pins ABI 6. |
src/hyperlight_host/src/sandbox/snapshot/file/transport.rs |
Updates malformed transport testing. |
src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs |
Bumps the snapshot ABI. |
src/hyperlight_host/src/sandbox/snapshot/file/config.rs |
Updates pinned schemas. |
src/hyperlight_host/src/sandbox/snapshot/file_tests.rs |
Updates ABI rejection assertions. |
src/hyperlight_host/src/sandbox/initialized.rs |
Enables retained-buffer snapshots. |
src/hyperlight_host/src/sandbox/config.rs |
Clarifies scratch usage. |
src/hyperlight_host/src/mem/virtq/tests.rs |
Tests mailbox and partial prefill restore. |
src/hyperlight_host/src/mem/virtq/mod.rs |
Accepts bounded H2G prefixes. |
src/hyperlight_host/src/mem/mgr.rs |
Implements mailbox checkpoint states. |
src/hyperlight_host/benches/benchmarks.rs |
Benchmarks retained-buffer restores. |
src/hyperlight_guest/src/transport/mod.rs |
Exposes refresh and checkpoint entry points. |
src/hyperlight_guest/src/transport/mem.rs |
Maps completed buffers through aliases. |
src/hyperlight_guest/src/transport/context.rs |
Prunes and refreshes pool aliases. |
src/hyperlight_guest/src/transport/backing.rs |
Implements alias paging and pinning. |
src/hyperlight_guest/Cargo.toml |
Adds fixed bitset support. |
src/hyperlight_guest_bin/src/transport.rs |
Updates transport imports. |
src/hyperlight_guest_bin/src/lib.rs |
Checkpoints after guest initialization. |
src/hyperlight_guest_bin/src/guest_function/call.rs |
Refreshes aliases before dispatch. |
src/hyperlight_common/src/virtq/ring/canonical.rs |
Adds prefix validation. |
src/hyperlight_common/src/virtq/pool/tests.rs |
Tests slot exclusion behavior. |
src/hyperlight_common/src/virtq/pool/slot.rs |
Adds persistent slot exclusions. |
src/hyperlight_common/src/virtq/pool/fuzz.rs |
Removes obsolete free-slot checks. |
src/hyperlight_common/src/transport.rs |
Defines mailbox wire values. |
docs/virtio-host-guest-communication.md |
Documents retained-buffer snapshots. |
docs/snapshot-versioning.md |
Documents ABI 6. |
CHANGELOG.md |
Records the feature and ABI break. |
Cargo.lock |
Locks the guest dependency. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This comment has been minimized.
This comment has been minimized.
36eda9c to
534cad5
Compare
This comment has been minimized.
This comment has been minimized.
ludfjig
left a comment
There was a problem hiding this comment.
lgtm. Does this impact the performance of the first guest call after a restore? Do you happen to have any perf numers?
534cad5 to
9395642
Compare
9395642 to
d2751f4
Compare
d2751f4 to
255b9ab
Compare
This comment has been minimized.
This comment has been minimized.
I will get some numbers. |
|
Hey @ludfjig, here are some numbers: That is going to scale with the size of the data that is retained because we are copying it back to scratch. Although I think we may do some tricks to avoid copying altogether. |
255b9ab to
ad8fc12
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
ad8fc12 to
9de91ff
Compare
This comment has been minimized.
This comment has been minimized.
syntactically
left a comment
There was a problem hiding this comment.
This looks pretty good, but I think it could be simplified a lot by removing the invariant that the whole "pool" range is 1:1 mapped early, and instead doing the mappings when necessary. (There is a slight problem in ensuring that dropping a view/"returning" a buffer after snapshot does not actually return said buffer, since it is not scratch backed anymore---but this I think can be solved by keeping track of the generation of the view and no-op'ing it (or rather, changing it to just unmap the alias pages) if the generation is different). Then there is no need for the prune_pool/map_pool dance across restore.
I guess that lazily allocating the aliases (and only to guest-owned/used buffers) like I suggest could be a tiny bit worse for bytechunks performance, but because of the relatively light tlb invalidations needed, I would guess it would not really be super noticeable in the current workload, and it would trade off by needing a lot less work for initialisation. So i would expect that to be an overall win, probably.
|
|
||
| ### Retained virtual addresses | ||
|
|
||
| Each pool has a stable virtual alias range, mapped eagerly at initialization. |
There was a problem hiding this comment.
Why map eagerly instead of when actually using a buffer? I think if doing the latter one can avoid the need to explicitly look for and unmap the buffers on checkpoint. And, it means we do not need to spend the memory on page tables (alias_table_len) unless references are actually created for a given buffer (which I think only happens in the bytechunks case right now iirc?)
There was a problem hiding this comment.
Perf being main reason, although please see the numbers below.
|
|
||
| /// Translate a scratch address into its guest physical address. | ||
| const fn scratch_gpa(addr: u64) -> u64 { | ||
| addr - (SCRATCH_TOP_GVA - SCRATCH_TOP_GPA) as u64 |
There was a problem hiding this comment.
Do we need to make this 1:1 assumption here (i.e. is walking the range too slow)? I don't think any other code makes that assumption, so maybe mention it in paging or layout docs if we are going to start making that an invariant.
There was a problem hiding this comment.
I can try walking here, yes.
|
|
||
| /// Prepare pool aliases after checkpointing or a generation change. | ||
| /// | ||
| /// After restore, retained slots are copied from captured memory into |
There was a problem hiding this comment.
Why is this copying back into the original scratch pool? The guest users of the page are already accessing it through their alias mappings.
I envision this functionality being useful mostly to allow the lifecycle of large data blobs to be independent from snapshot/restore (for example to allow replacing the init data section and as a more portable alternative to loading wasm binaries via mapping before the first snapshot). But, I think this large copy on restore would be quite bad for performance in that case?
There was a problem hiding this comment.
Pool slots that are less then page size would make neigbor slots unusable if they share a fixed alias page.
There was a problem hiding this comment.
I'm still not clear why it is helpful to copy here. Everything accessing the retained data is doing so through the old alias which has now been disassociated from the buffer pool backing---why do we want to re-associate them?
|
Hey @syntactically ! I agree that the lazy model is a bit cleaner:
I think I disagree. The complexity will move but I don't see how lazy mapping will simplify the implementation drastically:
That being said, I we think the numbers are acceptable, I'm happy to give the lazy mapping a go? |
I am surprised the overhead is quite so large, but for a lot of the use cases we have that are just restore+call I think it is just shifting the overhead from restore to call?
Good point about the lack of reclamation for the old page tables limiting the bump allocator semantics. They do get reclaimed on snapshot, but I could certainly see it being a problem in some workloads. We can't just try to switch to reclaiming them easily either, unless we change the whole physical page allocator to have a freelist, hm.. I see what you mean about how this basically makes the allocation for VA space used for data copies 1:1 with the buffer allocation itself, simplifying that problem, but I think it is quite undesirable to have the old buffers taking up space in the new pool. Apart from everything else, this means if you do send data + snapshot retaining data + send data you have to size the buffer pool appropriately for 2 rounds of data, which increases the size of the scratch region, which is quite unpleasant (it's important to minimize the size of the scratch region for a given workload, since the scratch region has to be zeroed on restore and directly contributes to restore latency).
I think this is important to have anyway. In fact it seems like a big /dis/advantage to me that in the current PR we end up taking up space in the live descriptor pool for data that was read more than a snapshot ago (which I imagine is likely to survive for some time, a la the generational hypothesis). And, having that association means that we end up having the corresponding scratch pages which there is nothing obvious to do with, so we end up (I think unnecessarily?) copying the data into them just to keep them in sync. I do think it is pretty important for perf to avoid that extra copy that is happening on restore and I don't think should be necessary. |
f1dd2a9 to
0d0576f
Compare
|
I believe last commit addresses all the issues. |
This comment has been minimized.
This comment has been minimized.
| pub fn min_scratch_size(transport_len: usize) -> usize { | ||
| arch::min_scratch_size() | ||
| .and_then(|fixed| fixed.checked_add(transport_len)) | ||
| .and_then(|size| size.checked_add(alias_table_len(transport_len)?)) |
There was a problem hiding this comment.
Is this still needed with lazy aliases?
There was a problem hiding this comment.
Yeah, the transport length is no longer a reliable bound. I removed it.
| anyhow = { version = "1.0.102", default-features = false } | ||
| serde_json = { version = "1.0", default-features = false, features = ["alloc"] } | ||
| hyperlight-common = { workspace = true, default-features = false } | ||
| fixedbitset = { version = "0.5.7", default-features = false } |
db7b79c to
f9fb01b
Compare
This comment has been minimized.
This comment has been minimized.
Keep transport pools and aliases stable. Pin pages backing live buffers and exclude overlapping slots until a checkpoint finds those pages empty. Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
f9fb01b to
d1e7001
Compare
Benchmark ResultsMeasured commit: kvm / amd (Linux) (➖ stable)No benchmark improved or regressed. Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
kvm / intel (Linux) (➖ stable)No benchmark improved or regressed. Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
mshv3 / amd (Linux) (➖ stable)No benchmark improved or regressed. Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
mshv3 / intel (Linux) (➖ stable)No benchmark improved or regressed. Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
hyperv-ws2025 / amd (Windows) (➖ stable)No benchmark improved or regressed. Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
hyperv-ws2025 / intel (Windows) (➖ stable)No benchmark improved or regressed. Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
Reported by |


This patch lets the guest keep referencing virtqueue data after a snapshot. Retained data is any guest value that keeps a transport pool slot alive:
ByteChunks, which hold a host requestBytesDuring normal execution:
At checkpoint (
prepare_snapshot):prune_poolrecords the live slot ranges and unmaps every alias page they don't touchAt snapshot:
After checkpoint, the first transport entry (
maybe_refresh):Closes #1884