Skip to content

Commit 5ff4488

Browse files
committed
Snapshot persistence to disk
Save snapshots to disk via Snapshot::to_file(), load them back via Snapshot::from_file(). Create sandboxes directly from loaded snapshots via MultiUseSandbox::from_snapshot(), bypassing ELF parsing and guest init. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 35dae5b commit 5ff4488

8 files changed

Lines changed: 1415 additions & 22 deletions

File tree

src/hyperlight_host/benches/benchmarks.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,93 @@ fn shared_memory_benchmark(c: &mut Criterion) {
556556
group.finish();
557557
}
558558

559+
// ============================================================================
560+
// Benchmark Category: Snapshot Files
561+
// ============================================================================
562+
563+
fn snapshot_file_benchmark(c: &mut Criterion) {
564+
use hyperlight_host::sandbox::snapshot::Snapshot;
565+
566+
let mut group = c.benchmark_group("snapshot_files");
567+
568+
// Pre-create snapshot files for all sizes
569+
let dirs: Vec<_> = SandboxSize::all()
570+
.iter()
571+
.map(|size| {
572+
let dir = tempfile::tempdir().unwrap();
573+
let snap_path = dir.path().join(format!("{}.hls", size.name()));
574+
let snapshot = {
575+
let mut sbox = create_multiuse_sandbox_with_size(*size);
576+
sbox.snapshot().unwrap()
577+
};
578+
snapshot.to_file(&snap_path).unwrap();
579+
(dir, snapshot)
580+
})
581+
.collect();
582+
583+
// Benchmark: save_snapshot
584+
for (i, size) in SandboxSize::all().iter().enumerate() {
585+
let snap_dir = tempfile::tempdir().unwrap();
586+
let path = snap_dir.path().join("bench.hls");
587+
let snapshot = &dirs[i].1;
588+
group.bench_function(format!("save_snapshot/{}", size.name()), |b| {
589+
b.iter(|| {
590+
snapshot.to_file(&path).unwrap();
591+
});
592+
});
593+
}
594+
595+
// Benchmark: load_snapshot (mmap + header parse + hash verify)
596+
for (i, size) in SandboxSize::all().iter().enumerate() {
597+
let snap_path = dirs[i].0.path().join(format!("{}.hls", size.name()));
598+
group.bench_function(format!("load_snapshot/{}", size.name()), |b| {
599+
b.iter(|| {
600+
let _ = Snapshot::from_file(&snap_path).unwrap();
601+
});
602+
});
603+
}
604+
605+
// Benchmark: cold_start_via_evolve (new + evolve + call)
606+
for size in SandboxSize::all() {
607+
group.bench_function(format!("cold_start_via_evolve/{}", size.name()), |b| {
608+
b.iter(|| {
609+
let mut sbox = create_multiuse_sandbox_with_size(size);
610+
sbox.call::<String>("Echo", "hello\n".to_string()).unwrap();
611+
});
612+
});
613+
}
614+
615+
// Benchmark: cold_start_via_snapshot (load + from_snapshot + call)
616+
for (i, size) in SandboxSize::all().iter().enumerate() {
617+
let snap_path = dirs[i].0.path().join(format!("{}.hls", size.name()));
618+
group.bench_function(format!("cold_start_via_snapshot/{}", size.name()), |b| {
619+
b.iter(|| {
620+
let loaded = Snapshot::from_file(&snap_path).unwrap();
621+
let mut sbox = MultiUseSandbox::from_snapshot(std::sync::Arc::new(loaded)).unwrap();
622+
sbox.call::<String>("Echo", "hello\n".to_string()).unwrap();
623+
});
624+
});
625+
}
626+
627+
// Benchmark: cold_start_via_snapshot_unchecked (no hash verify)
628+
for (i, size) in SandboxSize::all().iter().enumerate() {
629+
let snap_path = dirs[i].0.path().join(format!("{}.hls", size.name()));
630+
group.bench_function(
631+
format!("cold_start_via_snapshot_unchecked/{}", size.name()),
632+
|b| {
633+
b.iter(|| {
634+
let loaded = Snapshot::from_file_unchecked(&snap_path).unwrap();
635+
let mut sbox =
636+
MultiUseSandbox::from_snapshot(std::sync::Arc::new(loaded)).unwrap();
637+
sbox.call::<String>("Echo", "hello\n".to_string()).unwrap();
638+
});
639+
},
640+
);
641+
}
642+
643+
group.finish();
644+
}
645+
559646
criterion_group! {
560647
name = benches;
561648
config = Criterion::default();
@@ -566,6 +653,7 @@ criterion_group! {
566653
guest_call_benchmark_large_param,
567654
function_call_serialization_benchmark,
568655
sample_workloads_benchmark,
569-
shared_memory_benchmark
656+
shared_memory_benchmark,
657+
snapshot_file_benchmark
570658
}
571659
criterion_main!(benches);

src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,20 +352,27 @@ impl HyperlightVm {
352352
self.vm.set_debug_regs(&CommonDebugRegs::default())?;
353353
self.vm.reset_xsave()?;
354354

355+
self.apply_sregs(cr3, sregs)
356+
}
357+
358+
/// Apply special registers and mark TLB for flush. Used by both
359+
/// `reset_vcpu` (which also resets GPRs/debug/FPU) and
360+
/// `from_snapshot` (which skips the redundant resets).
361+
pub(crate) fn apply_sregs(
362+
&mut self,
363+
cr3: u64,
364+
sregs: &CommonSpecialRegisters,
365+
) -> std::result::Result<(), RegisterError> {
355366
#[cfg(not(feature = "nanvix-unstable"))]
356367
{
357-
// Restore the full special registers from snapshot, but update CR3
358-
// to point to the new (relocated) page tables
359368
let mut sregs = *sregs;
360369
sregs.cr3 = cr3;
361370
self.pending_tlb_flush = true;
362371
self.vm.set_sregs(&sregs)?;
363372
}
364373
#[cfg(feature = "nanvix-unstable")]
365374
{
366-
let _ = (cr3, sregs); // suppress unused warnings
367-
// TODO: This is probably not correct.
368-
// Let's deal with it when we clean up the nanvix-unstable feature
375+
let _ = (cr3, sregs);
369376
self.vm
370377
.set_sregs(&CommonSpecialRegisters::standard_real_mode_defaults())?;
371378
}

src/hyperlight_host/src/hypervisor/surrogate_process.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ impl SurrogateProcess {
9494
Ok(oe.get().surrogate_base)
9595
}
9696
Entry::Vacant(ve) => {
97-
// Derive the page protection from the mapping type
9897
let page_protection = match mapping {
9998
SurrogateMapping::SandboxMemory => PAGE_READWRITE,
10099
SurrogateMapping::ReadOnlyFile => PAGE_READONLY,
@@ -123,9 +122,6 @@ impl SurrogateProcess {
123122
);
124123
}
125124

126-
// Only set guard pages for SandboxMemory mappings.
127-
// File-backed read-only mappings do not need guard pages
128-
// because the host does not write to them.
129125
if *mapping == SurrogateMapping::SandboxMemory {
130126
let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0);
131127

src/hyperlight_host/src/mem/layout.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,16 @@ pub(crate) struct SandboxMemoryLayout {
225225
/// The size of the guest code section.
226226
pub(crate) code_size: usize,
227227
/// The size of the init data section (guest blob).
228-
init_data_size: usize,
228+
pub(crate) init_data_size: usize,
229229
/// Permission flags for the init data region.
230230
#[cfg_attr(feature = "nanvix-unstable", allow(unused))]
231-
init_data_permissions: Option<MemoryRegionFlags>,
231+
pub(crate) init_data_permissions: Option<MemoryRegionFlags>,
232232
/// The size of the scratch region in physical memory.
233-
scratch_size: usize,
233+
pub(crate) scratch_size: usize,
234234
/// The size of the snapshot region in physical memory.
235-
snapshot_size: usize,
235+
pub(crate) snapshot_size: usize,
236236
/// The size of the page tables (None if not yet set).
237-
pt_size: Option<usize>,
237+
pub(crate) pt_size: Option<usize>,
238238
}
239239

240240
impl SandboxMemoryLayout {
@@ -394,12 +394,6 @@ impl SandboxMemoryLayout {
394394
self.peb_file_mappings_offset()
395395
}
396396

397-
/// Get the offset in guest memory to the file_mappings pointer field.
398-
#[cfg(feature = "nanvix-unstable")]
399-
fn get_file_mappings_pointer_offset(&self) -> usize {
400-
self.get_file_mappings_size_offset() + size_of::<u64>()
401-
}
402-
403397
/// Get the offset in snapshot memory where the FileMappingInfo array starts
404398
/// (immediately after the PEB struct, within the same page).
405399
#[cfg(feature = "nanvix-unstable")]

src/hyperlight_host/src/mem/memory_region.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ impl MemoryRegionType {
158158
/// shared memory mapping with guard pages.
159159
pub fn surrogate_mapping(&self) -> SurrogateMapping {
160160
match self {
161-
MemoryRegionType::MappedFile => SurrogateMapping::ReadOnlyFile,
161+
MemoryRegionType::MappedFile | MemoryRegionType::Snapshot => {
162+
SurrogateMapping::ReadOnlyFile
163+
}
162164
_ => SurrogateMapping::SandboxMemory,
163165
}
164166
}

0 commit comments

Comments
 (0)