Repository navigation
Conversation
call_and_create times a guest call plus snapshot creation, so the call pays for memory remapping done by the previous snapshot. restore_alternate restores two snapshots in turn, so each restore targets memory that is not current. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Share immutable data blobs and store page tables separately. Persist layered snapshots as OCI image layouts. Capture copies only pages that changed since the snapshot the sandbox was created from or last restored to. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Snapshot capture switches the running sandbox to the captured snapshot. The next capture reuses its layers and copies only pages changed after it. Scratch pages used before the capture become free again. They are zeroed so guest allocations return zeroed pages, as after restore. Capture removes map_region and map_file_cow regions. Pages of these regions that the guest has not mapped are lost. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
walk_va_spaces merges mappings that have contiguous physical and virtual addresses and the same kind. Snapshot capture classifies and rebuilds each merged mapping once. Sandbox::snapshot on KVM, release build: * large heap: 22% to 37% faster * 1 GiB heap: 26% to 43% faster Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
A page-table walk reads every entry through GuestPhysicalMemoryView, which resolves the backing and copies 8 bytes through a locked volatile copy. The reader keeps a copy of the last table page it read, so each table costs one copy. Snapshot capture and crash dumps use the reader. The walk of a default sandbox drops from 187 us to 44 us. Criterion snapshots/create on MSHV: * default: 425 us to 252 us (-36%) * medium: 977 us to 645 us (-34%) Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
By default, deleting a memslot flushes the guest mappings of every memslot. KVM keeps that flush as a workaround for VMs with assigned GPUs. Hyperlight assigns no devices and disables the quirk when the kernel supports it (Linux 6.12 and later). Deleting a memslot then flushes only that memslot's mappings. Snapshot capture deletes a memslot when a layer drops out of the snapshot. In a loop of one call and one snapshot on KVM (Linux 6.18, nested under WSL2): * call after snapshot: 914-993 us to 143-185 us * snapshot: 595-722 us to 414-640 us Restore to another snapshot and region unmapping also delete memslots. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
ludfjig
requested review from
jprendes,
jsturtevant and
syntactically
as code owners
October 12, 2026 20:00
ludfjig
added this pull request to stack #1929
October 12, 2026 20:01
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The VMM memory boundary changes substantially, and OCI loading maps oversized blobs before enforcing resource limits.
1 open finding
What changed in this PR
Adds layered incremental snapshots that share immutable memory, persist OCI data and page-table layers, and optimize restore mappings.
Changes:
- Adds shared snapshot layers with incremental capture and installation.
- Introduces snapshot ABI 6 and OCI config v4.
- Optimizes page-table walks, KVM invalidation, tests, and benchmarks.
| File | Description |
|---|---|
src/tests/rust_guests/simpleguest/src/main.rs |
Adds zero-init mapping test support. |
src/hyperlight_host/tests/snapshot_goldens/goldens_version.rs |
Bumps golden snapshots to v6. |
src/hyperlight_host/src/sandbox/uninitialized.rs |
Documents capture behavior and uses GPA spans. |
src/hyperlight_host/src/sandbox/trace/mem_profile.rs |
Reads layered memory by GPA. |
src/hyperlight_host/src/sandbox/snapshot/tripwires.rs |
Pins ABI 6 media types. |
src/hyperlight_host/src/sandbox/snapshot/memory/mod.rs |
Defines layered snapshot memory. |
src/hyperlight_host/src/sandbox/snapshot/memory/backing.rs |
Maps and accesses snapshot layers. |
src/hyperlight_host/src/sandbox/snapshot/file/mod.rs |
Saves and loads layered OCI snapshots. |
src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs |
Adds config v4 and new layer media types. |
src/hyperlight_host/src/sandbox/snapshot/file/config.rs |
Defines the layered snapshot schema. |
src/hyperlight_host/src/sandbox/snapshot/file_tests.rs |
Tests layered persistence and validation. |
src/hyperlight_host/src/sandbox/mod.rs |
Exports SnapshotMemoryReader. |
src/hyperlight_host/src/sandbox/initialized.rs |
Installs captures and updates snapshot APIs. |
src/hyperlight_host/src/mem/virtq/tests.rs |
Updates scratch transport tests. |
src/hyperlight_host/src/mem/shared_mem.rs |
Adds immutable memory and range mappings. |
src/hyperlight_host/src/mem/layout.rs |
Removes flat snapshot layout state. |
src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs |
Disables the slot-wide KVM flush quirk. |
src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs |
Uses range-based snapshot mappings. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs |
Adopts layered VM memory. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/test_support.rs |
Updates mapping-state fault tests. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs |
Manages multiple snapshot mappings. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs |
Adopts layered VM memory. |
src/hyperlight_host/src/hypervisor/gdb/mod.rs |
Resolves layered debug memory. |
src/hyperlight_host/build.rs |
Removes obsolete memory-read configuration. |
src/hyperlight_host/benches/benchmarks.rs |
Adds incremental snapshot benchmarks. |
src/hyperlight_common/src/vmem.rs |
Coalesces adjacent mappings. |
src/hyperlight_common/src/arch/amd64/vmem.rs |
Applies mapping coalescing on AMD64. |
src/hyperlight_common/src/arch/aarch64/vmem.rs |
Applies mapping coalescing on AArch64. |
proposals/0003-incremental-snapshots/README.md |
Updates the snapshot design. |
docs/snapshot-versioning.md |
Documents ABI 6 versioning. |
docs/snapshot-oci-format.md |
Documents layered OCI storage. |
docs/paging-development-notes.md |
Describes incremental capture behavior. |
CHANGELOG.md |
Records breaking API and format changes. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+901
to
+905
| let mut snap_files = data_descs | ||
| .iter() | ||
| .chain([pt_desc]) | ||
| .map(|desc| open_snapshot_blob(&blobs_dir, desc)) | ||
| .collect::<crate::Result<Vec<_>>>()?; |
Benchmark ResultsMeasured commit: kvm / amd (Linux) (❌ 1.55x)Top regressions
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) (❌ 1.62x)Top regressions
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 branch has not been deployed
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.

Replaces #1902, which moved to an upstream branch so follow-up PRs can stack on it. Earlier review is on #1902.
Summary
Snapshots share immutable memory layers. A capture copies only the pages changed since its parent snapshot, and
Sandbox::snapshotswitches the sandbox to the capture so the next one is incremental too. This makessnapshot()much faster for larger sandboxes. It also improves memory density: each snapshot holds only its changed pages and shares the rest with its parent, wheremainholds a full copy of guest memory per snapshot. See HIP 0003.Deviations from the merged HIP 0003 draft (the HIP is updated):
Sandbox::snapshotinstalls the capture, because otherwise every later capture copies all pages changed since the last restore again. Installing removesmap_regionandmap_file_cowregions, because the snapshot now holds their mapped pages and later blobs may reuse their addresses.Changes
Sandbox::snapshotinstalls the capture into the running sandbox. Scratch pages it frees are zeroed.KVM_X86_QUIRK_SLOT_ZAP_ALLis disabled where supported, so deleting a memory slot flushes only that slot.snapshots/call_and_createandsnapshots/restore_alternatecover the cost moved into the next call and restores to a non-current snapshot.Breaking changes
Sandbox::snapshotremovesmap_regionandmap_file_cowregions if guest has not mapped them in. Pages of these regions the guest has not mapped are lost.PtRootFinderreceives aSnapshotMemoryReaderand returnsResult<Vec<u64>>.Commits
call_and_createandrestore_alternatesnapshot benchmarks. Lands first so the benchmarks run before and after.walk_va_spaces.PageTableReader.KVM_X86_QUIRK_SLOT_ZAP_ALL.Benchmarks
Compared to
main.Highlights
snapshots/create/defaultsnapshots/create/largesnapshots/call_and_create/mediumsnapshots/restore_alternate/largeGuest calls show no consistent change.
Regressions
snapshot_files/load_snapshot_unverified/defaultsnapshot_files/load_snapshot/defaultsnapshot_files/save_snapshot/smallsandboxes/sandbox_from_snapshot/smallsnapshots/restore_alternate/defaultsnapshot_files/cold_start_via_snapshot_unverified/smallsandboxes/create_initialized/mediumKernels before 6.12 cannot disable
KVM_X86_QUIRK_SLOT_ZAP_ALL, so each memory slot change flushes all guest mappings. With the quirk enabled, the first call aftersnapshot()measured about +650 µs instead of +60 µs. WHP and aarch64 were not benchmarked.A saved snapshot stores each layer in full. Base layer pages that a newer layer overwrote stay in the file, which adds 44 KiB for
default.Most of the KVM
sandbox_from_snapshotregression comes from the guest heap allocator. Each live range is a KVM memory slot, set with oneKVM_SET_USER_MEMORY_REGIONioctl.buddy_system_allocatorwrites a free-list pointer at the start of every power-of-two heap block during guest init. Each written page leaves a hole in the base layer, so a snapshot needs 5 to 16 slots depending on heap size. A later PR in this stack switches the guest allocator totalc, which keeps the heap one live range, and every size needs 4 slots. That makes KVMsandbox_from_snapshot20 to 26% faster for small to large heaps. Four slots remain against one onmain, so a smaller regression remains.Full results
All benchmarks on both hosts (click to expand)
Before → after (change). Negative is faster. Bold is a regression. ~ is no consistent change.
snapshots
call_and_createcall_and_createcall_and_createcall_and_createcreatecreatecreatecreaterestorerestorerestorerestorerestore_alternaterestore_alternaterestore_alternaterestore_alternateguest_calls
callcallcallcallcall_with_host_functioncall_with_host_functioncall_with_host_functioncall_with_host_functioncall_with_restorecall_with_restorecall_with_restorecall_with_restoredifferent_threadinterrupt_latencyguest_functions_with_large_parameters
guest_call_with_large_parameterssample_workloads
24K_in_8K_out_c24K_in_8K_out_rustsandboxes
create_initializedcreate_initializedcreate_initializedcreate_initializedcreate_initialized_and_dropcreate_initialized_and_dropcreate_initialized_and_dropcreate_initialized_and_dropsandbox_from_snapshotsandbox_from_snapshotsandbox_from_snapshotsandbox_from_snapshotsnapshot_files
cold_start_via_evolvecold_start_via_evolvecold_start_via_evolvecold_start_via_evolvecold_start_via_snapshotcold_start_via_snapshotcold_start_via_snapshotcold_start_via_snapshotcold_start_via_snapshot_unverifiedcold_start_via_snapshot_unverifiedcold_start_via_snapshot_unverifiedcold_start_via_snapshot_unverifiedload_snapshotload_snapshotload_snapshotload_snapshotload_snapshot_unverifiedload_snapshot_unverifiedload_snapshot_unverifiedload_snapshot_unverifiedsave_snapshotsave_snapshotsave_snapshotsave_snapshot