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
marked this pull request as ready for review
October 8, 2026 02:06
ludfjig
requested review from
andreiltd,
danbugs,
dblnz,
devigned,
jprendes,
jsturtevant,
simongdavies,
squillace and
syntactically
as code owners
October 8, 2026 02:06
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
It changes snapshot ABI and guest memory ownership across multiple hypervisor backends, including unbenchmarked paths.
2 open findings
What changed in this PR
Adds incremental snapshots that share immutable memory layers, reducing capture and restore costs.
Changes:
- Introduces layered snapshot memory and page-table storage.
- Installs captures for incremental reuse across snapshots.
- Updates OCI ABI, hypervisor mappings, tests, docs, and benchmarks.
| File | Description |
|---|---|
src/tests/rust_guests/simpleguest/src/main.rs |
Adds zero-page mapping fixture. |
src/hyperlight_host/tests/snapshot_goldens/goldens_version.rs |
Bumps goldens to v6. |
src/hyperlight_host/src/sandbox/uninitialized.rs |
Updates mapping size semantics and docs. |
src/hyperlight_host/src/sandbox/trace/mem_profile.rs |
Reads layered snapshot GPAs. |
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 reads snapshot layers. |
src/hyperlight_host/src/sandbox/snapshot/file/mod.rs |
Persists and loads layered OCI snapshots. |
src/hyperlight_host/src/sandbox/snapshot/file/media_types.rs |
Adds v4/v2/page-table media types. |
src/hyperlight_host/src/sandbox/snapshot/file/config.rs |
Defines layered config schema. |
src/hyperlight_host/src/sandbox/snapshot/file_tests.rs |
Expands layered-format tests. |
src/hyperlight_host/src/sandbox/mod.rs |
Exports snapshot memory reader. |
src/hyperlight_host/src/sandbox/initialized.rs |
Installs captured snapshots incrementally. |
src/hyperlight_host/src/mem/virtq/tests.rs |
Updates scratch restore expectations. |
src/hyperlight_host/src/mem/shared_mem.rs |
Adds immutable memory freezing and range mapping. |
src/hyperlight_host/src/mem/mgr.rs |
Resolves layered physical memory. |
src/hyperlight_host/src/mem/layout.rs |
Removes flat snapshot layout fields. |
src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs |
Disables KVM slot-zap quirk. |
src/hyperlight_host/src/hypervisor/virtual_machine/hvf/mod.rs |
Adapts HVF auxiliary mapping. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs |
Uses layered mappings on x86-64. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/test_support.rs |
Updates mapping fault support. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs |
Manages multiple snapshot slots. |
src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs |
Uses layered mappings on AArch64. |
src/hyperlight_host/src/hypervisor/gdb/mod.rs |
Resolves debugger access by backing. |
src/hyperlight_host/build.rs |
Removes obsolete memory cfg. |
src/hyperlight_host/benches/benchmarks.rs |
Adds incremental snapshot benchmarks. |
src/hyperlight_common/src/vmem.rs |
Coalesces adjacent walk 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 incremental 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 page capture. |
CHANGELOG.md |
Records breaking snapshot changes. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| let quirks = vm_fd.check_extension_raw(KVM_CAP_DISABLE_QUIRKS2.into()); | ||
| // `quirks` has one bit per quirk this kernel can disable. | ||
| if quirks <= 0 || quirks & KVM_X86_QUIRK_SLOT_ZAP_ALL as i32 == 0 { | ||
| tracing::warn!("KVM does not support disabling KVM_X86_QUIRK_SLOT_ZAP_ALL"); |
Comment on lines
+26
to
+27
| Page tables have their own `MT_PAGE_TABLES_V1` descriptor after the | ||
| data layers. |
Benchmark ResultsMeasured commit: kvm / amd (Linux) (❌ 1.51x | 🚀 6.11x)Top improvements
Top regressions
Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
kvm / intel (Linux) (❌ 1.71x | 🚀 4.95x)Top improvements
Top regressions
Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
mshv3 / amd (Linux) (❌ 1.56x)Top regressions
Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
mshv3 / intel (Linux) (❌ 1.50x)Top regressions
Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
hyperv-ws2025 / amd (Windows) (🚀 6.51x)Top improvements
Benchmark Resultsfunction_call_codec
payload_allocation
sandboxes
slot_pool
snapshot_files
virtq_readonly
virtq_readwrite
hyperv-ws2025 / intel (Windows) (❌ 1.51x | 🚀 6.89x)Top improvements
Top regressions
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.


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(), restore and sandbox creation much faster for larger sandboxes, and lets related snapshots share memory. 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
Highlights
snapshots/create/defaultsnapshots/create/largesnapshots/call_and_create/mediumsnapshots/restore/largesandboxes/create_initialized/largesnapshot_files/cold_start_via_evolve/largeGuest calls and restores to the current snapshot show no consistent change.
Regressions
snapshot_files/load_snapshot_unverified/defaultsnapshot_files/load_snapshot/defaultsnapshot_files/save_snapshot/defaultsandboxes/sandbox_from_snapshot/largesnapshots/restore_alternate/defaultsnapshot_files/save_snapshot/smallsandboxes/sandbox_from_snapshot/defaultsnapshots/restore_alternate/smallsnapshot_files/cold_start_via_snapshot_unverified/smallsandbox_from_snapshotcosts combinedKVM
sandbox_from_snapshotis slower at every size (+0.14 to +0.56 ms) andrestore_alternateup tomedium. KVM numbers are from Linux 6.17. Kernels before 6.12 cannot disableKVM_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 reused layer keeps the pages its descendants overwrote, so a single snapshot can be slightly larger on disk (44 KiB fordefault).Full results
All benchmarks on all three hosts.
Full results (click to expand)
Before → after (change). Negative is faster. Bold is a regression. ~ is not significant or under ±3%. KVM AMD compares against
mainfor benchmarks that existed before this PR.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