Skip to content

Commit 187f419

Browse files
committed
vmem: Add support for lazily zero-initialized memory regions
This is useful for hyperlight-wasm, although it can't fully provide for all use cases in a uniform way until we have better support for hugepages. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent 43cf353 commit 187f419

7 files changed

Lines changed: 228 additions & 125 deletions

File tree

‎src/hyperlight_common/src/arch/aarch64/vmem.rs‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ const VA_BITS: usize = 48;
2626
pub const ATTR_INDEX_NORMAL: u8 = 0;
2727
const SOFTWARE_USE_COW: u8 = 0b1;
2828

29+
const INVALID_KIND_MIN_BIT: u8 = 2;
30+
const INVALID_KIND_MAX_BIT: u8 = 2;
31+
const INVALID_KIND_ZERO_INIT: u8 = 0b1;
32+
const INVALID_KIND_ZERO_INIT_PERM_READABLE_BIT: u8 = 8;
33+
const INVALID_KIND_ZERO_INIT_PERM_WRITABLE_BIT: u8 = 9;
34+
const INVALID_KIND_ZERO_INIT_PERM_EXECUTABLE_BIT: u8 = 10;
35+
2936
// Utility structures
3037
impl<
3138
Op: TableOps<TableMovability = crate::vmem::MayMoveTable>,
@@ -172,6 +179,19 @@ unsafe fn map_page<
172179
false,
173180
),
174181
MappingKind::Unmapped => 0,
182+
MappingKind::ZeroInit(bm) => {
183+
let mut desc = (INVALID_KIND_ZERO_INIT as u64) << INVALID_KIND_MIN_BIT;
184+
if bm.readable {
185+
desc |= 1 << INVALID_KIND_ZERO_INIT_PERM_READABLE_BIT
186+
}
187+
if bm.writable {
188+
desc |= 1 << INVALID_KIND_ZERO_INIT_PERM_WRITABLE_BIT
189+
}
190+
if bm.executable {
191+
desc |= 1 << INVALID_KIND_ZERO_INIT_PERM_EXECUTABLE_BIT
192+
}
193+
desc
194+
}
175195
};
176196
unsafe {
177197
write_entry_updating(op, r.update_parent, r.entry_ptr, desc);
@@ -497,7 +517,24 @@ unsafe fn internal_walk_va_spaces<'a, Op: TableReadOps + 'a>(
497517
kind,
498518
}))
499519
} else {
500-
None // do nothing - there is no mapping to record here
520+
let ik = bits::<INVALID_KIND_MAX_BIT, INVALID_KIND_MIN_BIT>(desc);
521+
match ik as u8 {
522+
INVALID_KIND_ZERO_INIT => Some(SpaceAwareMapping::ThisSpace(Mapping {
523+
phys_base: 0,
524+
virt_base: rq.vmin,
525+
len: PAGE_SIZE as u64,
526+
kind: MappingKind::ZeroInit(BasicMapping {
527+
readable: desc & (1 << INVALID_KIND_ZERO_INIT_PERM_READABLE_BIT)
528+
!= 0,
529+
writable: desc & (1 << INVALID_KIND_ZERO_INIT_PERM_WRITABLE_BIT)
530+
!= 0,
531+
executable: desc
532+
& (1 << INVALID_KIND_ZERO_INIT_PERM_EXECUTABLE_BIT)
533+
!= 0,
534+
}),
535+
})),
536+
_ => None, // do nothing - there is no mapping to record here
537+
}
501538
}
502539
});
503540
(root_id, iter)

‎src/hyperlight_common/src/arch/amd64/vmem.rs‎

Lines changed: 74 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ const PAGE_PAT_WB: u64 = 0 << 7; // PAT - page attribute table index bit (0 for
102102
const PTE_AVL_MASK: u64 = 0x0000_0000_0000_0E00;
103103
const PAGE_AVL_COW: u64 = 1 << 9;
104104

105+
const INVALID_KIND_MIN_BIT: u8 = 2;
106+
const INVALID_KIND_MAX_BIT: u8 = 2;
107+
const INVALID_KIND_ZERO_INIT: u8 = 0b1;
108+
const INVALID_KIND_ZERO_INIT_PERM_READABLE_BIT: u8 = 8;
109+
const INVALID_KIND_ZERO_INIT_PERM_WRITABLE_BIT: u8 = 9;
110+
const INVALID_KIND_ZERO_INIT_PERM_EXECUTABLE_BIT: u8 = 10;
111+
105112
/// Returns PAGE_RW if writable is true, 0 otherwise
106113
#[inline(always)]
107114
const fn page_rw_flag(writable: bool) -> u64 {
@@ -275,6 +282,19 @@ unsafe fn map_page<
275282
PAGE_PRESENT // P - this entry is present
276283
}
277284
MappingKind::Unmapped => 0,
285+
MappingKind::ZeroInit(bm) => {
286+
let mut pte = (INVALID_KIND_ZERO_INIT as u64) << INVALID_KIND_MIN_BIT;
287+
if bm.readable {
288+
pte |= 1 << INVALID_KIND_ZERO_INIT_PERM_READABLE_BIT
289+
}
290+
if bm.writable {
291+
pte |= 1 << INVALID_KIND_ZERO_INIT_PERM_WRITABLE_BIT
292+
}
293+
if bm.executable {
294+
pte |= 1 << INVALID_KIND_ZERO_INIT_PERM_EXECUTABLE_BIT
295+
}
296+
pte
297+
}
278298
};
279299
unsafe {
280300
write_entry_updating(op, r.update_parent, r.entry_ptr, pte);
@@ -339,34 +359,9 @@ pub unsafe fn walk_va_spaces<Op: TableReadOps>(
339359
.flat_map(modify_ptes::<20, 12, Op, _>);
340360

341361
for r in iter {
342-
let Some(pte) = (unsafe { read_pte_if_present(op, r.entry_ptr) }) else {
343-
continue;
344-
};
345-
let phys_addr = pte & PTE_ADDR_MASK;
346-
let sgn_bit = r.vmin >> (VA_BITS - 1);
347-
let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
348-
let virt_addr = sgn_bits | r.vmin;
349-
350-
let executable = (pte & PAGE_NX) == 0;
351-
let avl = pte & PTE_AVL_MASK;
352-
let kind = if avl == PAGE_AVL_COW {
353-
MappingKind::Cow(CowMapping {
354-
readable: true,
355-
executable,
356-
})
357-
} else {
358-
MappingKind::Basic(BasicMapping {
359-
readable: true,
360-
writable: (pte & PAGE_RW) != 0,
361-
executable,
362-
})
363-
};
364-
mappings.push(crate::vmem::SpaceAwareMapping::ThisSpace(Mapping {
365-
phys_base: phys_addr,
366-
virt_base: virt_addr,
367-
len: PAGE_SIZE as u64,
368-
kind,
369-
}));
362+
if let Some(mapping) = unsafe { read_and_decode_pte(op, &r) } {
363+
mappings.push(crate::vmem::SpaceAwareMapping::ThisSpace(mapping));
364+
}
370365
}
371366

372367
out.push((root_id, mappings));
@@ -404,6 +399,56 @@ pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
404399
.for_each(drop);
405400
}
406401

402+
unsafe fn read_and_decode_pte<Op: TableReadOps, P: UpdateParent<Op>>(
403+
op: &Op,
404+
r: &MapResponse<Op, P>,
405+
) -> Option<Mapping> {
406+
// Re-do the sign extension
407+
let sgn_bit = r.vmin >> (VA_BITS - 1);
408+
let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
409+
let virt_addr = sgn_bits | r.vmin;
410+
411+
let Some(pte) = (unsafe { read_pte_if_present(op, r.entry_ptr) }) else {
412+
let pte: u64 = unsafe { op.read_entry(r.entry_ptr) };
413+
let ik = crate::vmem::bits::<INVALID_KIND_MAX_BIT, INVALID_KIND_MIN_BIT>(pte);
414+
return match ik as u8 {
415+
INVALID_KIND_ZERO_INIT => Some(Mapping {
416+
phys_base: 0,
417+
virt_base: virt_addr,
418+
len: PAGE_SIZE as u64,
419+
kind: MappingKind::ZeroInit(BasicMapping {
420+
readable: pte & (1 << INVALID_KIND_ZERO_INIT_PERM_READABLE_BIT) != 0,
421+
writable: pte & (1 << INVALID_KIND_ZERO_INIT_PERM_WRITABLE_BIT) != 0,
422+
executable: pte & (1 << INVALID_KIND_ZERO_INIT_PERM_EXECUTABLE_BIT) != 0,
423+
}),
424+
}),
425+
_ => None,
426+
};
427+
};
428+
let phys_addr = pte & PTE_ADDR_MASK;
429+
430+
let executable = (pte & PAGE_NX) == 0;
431+
let avl = pte & PTE_AVL_MASK;
432+
let kind = if avl == PAGE_AVL_COW {
433+
MappingKind::Cow(CowMapping {
434+
readable: true,
435+
executable,
436+
})
437+
} else {
438+
MappingKind::Basic(BasicMapping {
439+
readable: true,
440+
writable: (pte & PAGE_RW) != 0,
441+
executable,
442+
})
443+
};
444+
Some(Mapping {
445+
phys_base: phys_addr,
446+
virt_base: virt_addr,
447+
len: PAGE_SIZE as u64,
448+
kind,
449+
})
450+
}
451+
407452
// There are no notable architecture-specific safety considerations
408453
// here, and the general conditions are documented in the
409454
// architecture-independent re-export in vmem.rs
@@ -446,35 +491,7 @@ pub unsafe fn virt_to_phys<'a, Op: TableReadOps + 'a>(
446491
.flat_map(modify_ptes::<29, 21, Op, _>)
447492
.filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
448493
.flat_map(modify_ptes::<20, 12, Op, _>)
449-
.filter_map(move |r| {
450-
let pte = unsafe { read_pte_if_present(op.as_ref(), r.entry_ptr) }?;
451-
let phys_addr = pte & PTE_ADDR_MASK;
452-
// Re-do the sign extension
453-
let sgn_bit = r.vmin >> (VA_BITS - 1);
454-
let sgn_bits = 0u64.wrapping_sub(sgn_bit) << VA_BITS;
455-
let virt_addr = sgn_bits | r.vmin;
456-
457-
let executable = (pte & PAGE_NX) == 0;
458-
let avl = pte & PTE_AVL_MASK;
459-
let kind = if avl == PAGE_AVL_COW {
460-
MappingKind::Cow(CowMapping {
461-
readable: true,
462-
executable,
463-
})
464-
} else {
465-
MappingKind::Basic(BasicMapping {
466-
readable: true,
467-
writable: (pte & PAGE_RW) != 0,
468-
executable,
469-
})
470-
};
471-
Some(Mapping {
472-
phys_base: phys_addr,
473-
virt_base: virt_addr,
474-
len: PAGE_SIZE as u64,
475-
kind,
476-
})
477-
})
494+
.filter_map(move |r| unsafe { read_and_decode_pte(op.as_ref(), &r) })
478495
}
479496

480497
const VA_BITS: usize = 48; // We use 48-bit virtual addresses at the moment.

‎src/hyperlight_common/src/vmem.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,13 +413,14 @@ pub struct CowMapping {
413413
#[derive(Debug, PartialEq, Clone, Copy)]
414414
pub enum MappingKind {
415415
Unmapped,
416+
ZeroInit(BasicMapping),
416417
Basic(BasicMapping),
417418
Cow(CowMapping),
418419
/* TODO: What useful things other than basic mappings actually
419420
* require touching the tables? */
420421
}
421422

422-
#[derive(Debug)]
423+
#[derive(Debug, Clone, Copy)]
423424
pub struct Mapping {
424425
pub phys_base: u64,
425426
pub virt_base: u64,

‎src/hyperlight_guest_bin/src/arch/aarch64/exception/handle.rs‎

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
use core::fmt::Write;
44

55
use hyperlight_common::arch::exn::{DataFault, DataFaultKind, Exception, decode_syndrome};
6-
use hyperlight_common::vmem::{
7-
BasicMapping, CowMapping, MappingKind, PAGE_SIZE, PhysAddr, VirtAddr,
8-
};
6+
use hyperlight_common::vmem::{BasicMapping, Mapping, MappingKind, PAGE_SIZE, PhysAddr, VirtAddr};
97
use hyperlight_guest::error::ErrorCode;
108
use hyperlight_guest::exit::write_abort;
119
use hyperlight_guest::layout::{MAIN_STACK_LIMIT_GVA, MAIN_STACK_TOP_GVA};
@@ -37,7 +35,7 @@ fn handle_stack_fault(far: u64) {
3735
}
3836
}
3937

40-
fn handle_cow_fault(_orig_phys: PhysAddr, virt: VirtAddr, perms: CowMapping) {
38+
fn handle_cow_fault(orig_phys: Option<PhysAddr>, virt: VirtAddr, perms: BasicMapping) {
4139
unsafe {
4240
let new_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1);
4341
let target_virt = virt as *mut u8;
@@ -50,22 +48,17 @@ fn handle_cow_fault(_orig_phys: PhysAddr, virt: VirtAddr, perms: CowMapping) {
5048
// should never reach beyond this call.
5149
unreachable!();
5250
};
53-
core::ptr::copy(target_virt, scratch_mapping_access, PAGE_SIZE);
51+
// Only copy if there is an original page from which to copy
52+
if orig_phys.is_some() {
53+
core::ptr::copy(target_virt, scratch_mapping_access, PAGE_SIZE);
54+
}
5455
// todo(multithreading): this will definitely require a
5556
// break-before-make sequence
5657
crate::paging::map_region(
5758
new_page,
5859
target_virt,
5960
PAGE_SIZE as u64,
60-
MappingKind::Basic(BasicMapping {
61-
// Inherit R bit from the original mapping (always 1 at the moment)
62-
readable: perms.readable,
63-
// If we got here, the original marking was marked
64-
// CoW, so the copied mapping should always be
65-
// writable
66-
writable: true,
67-
executable: perms.executable,
68-
}),
61+
MappingKind::Basic(perms),
6962
);
7063
// This is updating an entry that was already valid, changing
7164
// its OA, so we need to actually invalidate the TLB for it.
@@ -86,7 +79,7 @@ pub extern "Rust" fn _debug_print(x: &str) {
8679
hyperlight_guest::exit::debug_print(x);
8780
}
8881

89-
fn handle_internal_fault(exn: Exception, far: u64) -> bool {
82+
fn handle_internal_fault(exn: Exception, far: u64, orig_mapping: Option<Mapping>) -> bool {
9083
match exn {
9184
Exception::DataFault(DataFault {
9285
from_lower_el: false,
@@ -96,6 +89,15 @@ fn handle_internal_fault(exn: Exception, far: u64) -> bool {
9689
if (MAIN_STACK_LIMIT_GVA..MAIN_STACK_TOP_GVA).contains(&far) {
9790
handle_stack_fault(far);
9891
true
92+
} else if let Some(
93+
m @ Mapping {
94+
kind: MappingKind::ZeroInit(bm),
95+
..
96+
},
97+
) = orig_mapping
98+
{
99+
handle_cow_fault(None, m.virt_base, bm);
100+
true
99101
} else {
100102
false
101103
}
@@ -106,12 +108,23 @@ fn handle_internal_fault(exn: Exception, far: u64) -> bool {
106108
kind: DataFaultKind::PermissionFault(_),
107109
..
108110
}) => {
109-
let mut orig_mappings = crate::paging::virt_to_phys(far);
110-
if let Some(mapping) = orig_mappings.next()
111-
&& let None = orig_mappings.next()
112-
&& let MappingKind::Cow(cm) = mapping.kind
111+
if let Some(
112+
m @ Mapping {
113+
kind: MappingKind::Cow(cm),
114+
..
115+
},
116+
) = orig_mapping
113117
{
114-
handle_cow_fault(mapping.phys_base, mapping.virt_base, cm);
118+
let bm = BasicMapping {
119+
// Inherit R bit from the original mapping (always 1 at the moment)
120+
readable: cm.readable,
121+
// If we got here, the original marking was marked
122+
// CoW, so the copied mapping should always be
123+
// writable
124+
writable: true,
125+
executable: cm.executable,
126+
};
127+
handle_cow_fault(Some(m.phys_base), m.virt_base, bm);
115128
true
116129
} else {
117130
false
@@ -131,7 +144,9 @@ pub(super) extern "C" fn handle_exception(
131144

132145
if typ == ExceptionType::Synchronous && from == ExceptionFrom::CurrentSP0 {
133146
let exn = decode_syndrome(esr);
134-
if handle_internal_fault(exn, far) {
147+
let mut orig_mappings = crate::paging::virt_to_phys(far);
148+
let orig_mapping = orig_mappings.next();
149+
if handle_internal_fault(exn, far, orig_mapping) {
135150
return;
136151
}
137152
}

0 commit comments

Comments
 (0)