Skip to content

Commit df9e3c2

Browse files
committed
refactor: move deferred_hshm and counter_taken to UninitializedSandbox
ExclusiveSharedMemory's purpose is lock-free bulk memory operations. Storing deferred_hshm (Arc<Mutex<Option<HostSharedMemory>>>) and counter_taken (AtomicBool) on it added state that conceptually belongs to the sandbox lifetime, not the memory region lifecycle. This commit moves both fields to UninitializedSandbox, populates deferred_hshm in evolve_impl_multi_use() after build() returns the HostSharedMemory, and adds as_host_shared_memory() on ExclusiveSharedMemory (test-only) so UninitializedSandbox::simulate_build() can construct the HostSharedMemory without accessing private fields across modules. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent d4d5fac commit df9e3c2

3 files changed

Lines changed: 74 additions & 70 deletions

File tree

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 14 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ use std::io::Error;
2020
use std::mem::{align_of, size_of};
2121
#[cfg(target_os = "linux")]
2222
use std::ptr::null_mut;
23-
#[cfg(feature = "nanvix-unstable")]
24-
use std::sync::atomic::AtomicBool;
25-
use std::sync::{Arc, Mutex, RwLock};
23+
use std::sync::{Arc, RwLock};
2624

2725
use hyperlight_common::mem::PAGE_SIZE_USIZE;
2826
use tracing::{Span, instrument};
@@ -138,17 +136,7 @@ impl Drop for HostMapping {
138136
/// and taking snapshots.
139137
#[derive(Debug)]
140138
pub struct ExclusiveSharedMemory {
141-
pub(crate) region: Arc<HostMapping>,
142-
/// Populated by [`build()`](Self::build) with a [`HostSharedMemory`]
143-
/// view of this region. Code that needs host-style volatile access
144-
/// before `build()` (e.g. `GuestCounter`) can clone this `Arc` and
145-
/// will see `Some` once `build()` completes.
146-
pub(crate) deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
147-
/// Set to `true` once a [`GuestCounter`] has been handed out via
148-
/// [`UninitializedSandbox::guest_counter()`]. Prevents creating
149-
/// multiple counters that would have divergent cached values.
150-
#[cfg(feature = "nanvix-unstable")]
151-
pub(crate) counter_taken: AtomicBool,
139+
region: Arc<HostMapping>,
152140
}
153141
unsafe impl Send for ExclusiveSharedMemory {}
154142

@@ -436,9 +424,6 @@ impl ExclusiveSharedMemory {
436424
ptr: addr as *mut u8,
437425
size: total_size,
438426
}),
439-
deferred_hshm: Arc::new(Mutex::new(None)),
440-
#[cfg(feature = "nanvix-unstable")]
441-
counter_taken: AtomicBool::new(false),
442427
})
443428
}
444429

@@ -557,9 +542,6 @@ impl ExclusiveSharedMemory {
557542
size: total_size,
558543
handle,
559544
}),
560-
deferred_hshm: Arc::new(Mutex::new(None)),
561-
#[cfg(feature = "nanvix-unstable")]
562-
counter_taken: AtomicBool::new(false),
563545
})
564546
}
565547

@@ -670,15 +652,6 @@ impl ExclusiveSharedMemory {
670652
region: self.region.clone(),
671653
lock: lock.clone(),
672654
};
673-
// Publish the HostSharedMemory so any pre-existing GuestCounter
674-
// can begin issuing volatile writes via the proper protocol.
675-
// The mutex is only locked here and during GuestCounter
676-
// operations; since build() consumes self, there is no
677-
// concurrent access that could poison it.
678-
#[allow(clippy::unwrap_used)]
679-
{
680-
*self.deferred_hshm.lock().unwrap() = Some(hshm.clone());
681-
}
682655
(
683656
hshm,
684657
GuestSharedMemory {
@@ -688,24 +661,23 @@ impl ExclusiveSharedMemory {
688661
)
689662
}
690663

691-
/// Populate the deferred `HostSharedMemory` slot without consuming
692-
/// `self`. Used in tests where `evolve()` / full `build()` is not
693-
/// available.
694-
#[cfg(all(test, feature = "nanvix-unstable"))]
695-
pub(crate) fn simulate_build(&self) {
696-
let lock = Arc::new(RwLock::new(()));
697-
let hshm = HostSharedMemory {
698-
region: self.region.clone(),
699-
lock,
700-
};
701-
*self.deferred_hshm.lock().unwrap() = Some(hshm);
702-
}
703-
704664
/// Gets the file handle of the shared memory region for this Sandbox
705665
#[cfg(target_os = "windows")]
706666
pub fn get_mmap_file_handle(&self) -> HANDLE {
707667
self.region.handle
708668
}
669+
670+
/// Create a [`HostSharedMemory`] view of this region without
671+
/// consuming `self`. Used in tests where the full `build()` /
672+
/// `evolve()` pipeline is not available.
673+
#[cfg(all(test, feature = "nanvix-unstable"))]
674+
pub(crate) fn as_host_shared_memory(&self) -> HostSharedMemory {
675+
let lock = Arc::new(RwLock::new(()));
676+
HostSharedMemory {
677+
region: self.region.clone(),
678+
lock,
679+
}
680+
}
709681
}
710682

711683
impl GuestSharedMemory {
@@ -888,9 +860,6 @@ impl SharedMemory for GuestSharedMemory {
888860
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
889861
let mut excl = ExclusiveSharedMemory {
890862
region: self.region.clone(),
891-
deferred_hshm: Arc::new(Mutex::new(None)),
892-
#[cfg(feature = "nanvix-unstable")]
893-
counter_taken: AtomicBool::new(false),
894863
};
895864
let ret = f(&mut excl);
896865
drop(excl);
@@ -1256,9 +1225,6 @@ impl SharedMemory for HostSharedMemory {
12561225
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
12571226
let mut excl = ExclusiveSharedMemory {
12581227
region: self.region.clone(),
1259-
deferred_hshm: Arc::new(Mutex::new(None)),
1260-
#[cfg(feature = "nanvix-unstable")]
1261-
counter_taken: AtomicBool::new(false),
12621228
};
12631229
let ret = f(&mut excl);
12641230
drop(excl);

src/hyperlight_host/src/sandbox/uninitialized.rs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ use crate::mem::memory_region::{DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegionFlags}
3333
use crate::mem::mgr::SandboxMemoryManager;
3434
use crate::mem::shared_mem::ExclusiveSharedMemory;
3535
#[cfg(feature = "nanvix-unstable")]
36+
use crate::mem::shared_mem::HostSharedMemory;
37+
#[cfg(feature = "nanvix-unstable")]
3638
use crate::mem::shared_mem::SharedMemory;
3739
use crate::sandbox::SandboxConfiguration;
3840
use crate::{MultiUseSandbox, Result, new_error};
@@ -70,11 +72,9 @@ pub(crate) struct SandboxRuntimeConfig {
7072
/// `decrement()` take `&self` rather than `&mut self`.
7173
///
7274
/// The counter holds an `Arc<Mutex<Option<HostSharedMemory>>>` that is
73-
/// shared with [`ExclusiveSharedMemory`]. The `Option` is `None` until
74-
/// [`build()`](ExclusiveSharedMemory::build) populates it, at which point
75-
/// the counter can issue volatile writes via the proper protocol. This
76-
/// avoids adding lock overhead to `ExclusiveSharedMemory` during the
77-
/// exclusive setup phase.
75+
/// shared with [`UninitializedSandbox`]. The `Option` is `None` until
76+
/// [`evolve()`](UninitializedSandbox::evolve) populates it, at which point
77+
/// the counter can issue volatile writes via the proper protocol.
7878
///
7979
/// Only one `GuestCounter` may be created per sandbox; a second call to
8080
/// [`UninitializedSandbox::guest_counter()`] returns an error.
@@ -85,7 +85,7 @@ pub struct GuestCounter {
8585

8686
#[cfg(feature = "nanvix-unstable")]
8787
struct GuestCounterInner {
88-
deferred_hshm: Arc<Mutex<Option<crate::mem::shared_mem::HostSharedMemory>>>,
88+
deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
8989
offset: usize,
9090
value: u64,
9191
}
@@ -111,11 +111,12 @@ impl GuestCounter {
111111
})?
112112
.clone()
113113
};
114-
inner.value = inner
114+
let new_value = inner
115115
.value
116116
.checked_add(1)
117117
.ok_or_else(|| new_error!("GuestCounter overflow"))?;
118-
shm.write::<u64>(inner.offset, inner.value)?;
118+
shm.write::<u64>(inner.offset, new_value)?;
119+
inner.value = new_value;
119120
Ok(())
120121
}
121122

@@ -131,11 +132,12 @@ impl GuestCounter {
131132
})?
132133
.clone()
133134
};
134-
inner.value = inner
135+
let new_value = inner
135136
.value
136137
.checked_sub(1)
137138
.ok_or_else(|| new_error!("GuestCounter underflow"))?;
138-
shm.write::<u64>(inner.offset, inner.value)?;
139+
shm.write::<u64>(inner.offset, new_value)?;
140+
inner.value = new_value;
139141
Ok(())
140142
}
141143

@@ -170,6 +172,17 @@ pub struct UninitializedSandbox {
170172
// This is needed to convey the stack pointer between the snapshot
171173
// and the HyperlightVm creation
172174
pub(crate) stack_top_gva: u64,
175+
/// Populated by [`evolve()`](Self::evolve) with a [`HostSharedMemory`]
176+
/// view of scratch memory. Code that needs host-style volatile access
177+
/// before `evolve()` (e.g. `GuestCounter`) can clone this `Arc` and
178+
/// will see `Some` once `evolve()` completes.
179+
#[cfg(feature = "nanvix-unstable")]
180+
pub(crate) deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
181+
/// Set to `true` once a [`GuestCounter`] has been handed out via
182+
/// [`guest_counter()`](Self::guest_counter). Prevents creating
183+
/// multiple counters that would have divergent cached values.
184+
#[cfg(feature = "nanvix-unstable")]
185+
counter_taken: std::sync::atomic::AtomicBool,
173186
}
174187

175188
impl Debug for UninitializedSandbox {
@@ -267,10 +280,9 @@ impl UninitializedSandbox {
267280
/// the top of scratch memory, so both host and guest can locate it
268281
/// without an explicit GPA parameter.
269282
///
270-
/// The returned counter holds an `Arc` clone of the scratch memory's
283+
/// The returned counter holds an `Arc` clone of the sandbox's
271284
/// `deferred_hshm`, so it will automatically gain access to the
272-
/// [`HostSharedMemory`] once [`build()`](ExclusiveSharedMemory::build)
273-
/// is called during [`evolve()`](Self::evolve).
285+
/// [`HostSharedMemory`] once [`evolve()`](Self::evolve) completes.
274286
///
275287
/// This method can only be called once; a second call returns an error
276288
/// because multiple counters would have divergent cached values.
@@ -280,12 +292,7 @@ impl UninitializedSandbox {
280292

281293
use hyperlight_common::layout::SCRATCH_TOP_GUEST_COUNTER_OFFSET;
282294

283-
if self
284-
.mgr
285-
.scratch_mem
286-
.counter_taken
287-
.swap(true, Ordering::Relaxed)
288-
{
295+
if self.counter_taken.swap(true, Ordering::Relaxed) {
289296
return Err(new_error!(
290297
"GuestCounter has already been created for this sandbox"
291298
));
@@ -301,7 +308,7 @@ impl UninitializedSandbox {
301308
}
302309

303310
let offset = scratch_size - SCRATCH_TOP_GUEST_COUNTER_OFFSET as usize;
304-
let deferred_hshm = self.mgr.scratch_mem.deferred_hshm.clone();
311+
let deferred_hshm = self.deferred_hshm.clone();
305312

306313
Ok(GuestCounter {
307314
inner: Mutex::new(GuestCounterInner {
@@ -370,6 +377,10 @@ impl UninitializedSandbox {
370377
rt_cfg,
371378
load_info: snapshot.load_info(),
372379
stack_top_gva: snapshot.stack_top_gva(),
380+
#[cfg(feature = "nanvix-unstable")]
381+
deferred_hshm: Arc::new(Mutex::new(None)),
382+
#[cfg(feature = "nanvix-unstable")]
383+
counter_taken: std::sync::atomic::AtomicBool::new(false),
373384
};
374385

375386
// If we were passed a writer for host print register it otherwise use the default.
@@ -449,6 +460,18 @@ impl UninitializedSandbox {
449460
) -> Result<()> {
450461
self.register("HostPrint", print_func)
451462
}
463+
464+
/// Populate the deferred `HostSharedMemory` slot without running
465+
/// the full `evolve()` pipeline. Used in tests where guest boot
466+
/// is not available.
467+
#[cfg(all(test, feature = "nanvix-unstable"))]
468+
fn simulate_build(&self) {
469+
let hshm = self.mgr.scratch_mem.as_host_shared_memory();
470+
#[allow(clippy::unwrap_used)]
471+
{
472+
*self.deferred_hshm.lock().unwrap() = Some(hshm);
473+
}
474+
}
452475
}
453476
// Check to see if the current version of Windows is supported
454477
// Hyperlight is only supported on Windows 11 and Windows Server 2022 and later
@@ -1500,7 +1523,7 @@ mod tests {
15001523
fn increment_decrement() {
15011524
let mut sandbox = make_sandbox();
15021525
let counter = sandbox.guest_counter().unwrap();
1503-
sandbox.mgr.scratch_mem.simulate_build();
1526+
sandbox.simulate_build();
15041527

15051528
counter.increment().unwrap();
15061529
assert_eq!(counter.value().unwrap(), 1);
@@ -1514,7 +1537,7 @@ mod tests {
15141537
fn underflow_returns_error() {
15151538
let mut sandbox = make_sandbox();
15161539
let counter = sandbox.guest_counter().unwrap();
1517-
sandbox.mgr.scratch_mem.simulate_build();
1540+
sandbox.simulate_build();
15181541

15191542
assert_eq!(counter.value().unwrap(), 0);
15201543
let result = counter.decrement();

src/hyperlight_host/src/sandbox/uninitialized_evolve.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ use crate::{MultiUseSandbox, Result, UninitializedSandbox};
3838
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
3939
pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result<MultiUseSandbox> {
4040
let (mut hshm, gshm) = u_sbox.mgr.build()?;
41+
42+
// Publish the HostSharedMemory for scratch so any pre-existing
43+
// GuestCounter can begin issuing volatile writes.
44+
#[cfg(feature = "nanvix-unstable")]
45+
{
46+
#[allow(clippy::unwrap_used)]
47+
// The mutex can only be poisoned if a previous lock holder
48+
// panicked. Since we are the only writer at this point (the
49+
// GuestCounter only reads inside `increment`/`decrement`),
50+
// poisoning cannot happen.
51+
{
52+
*u_sbox.deferred_hshm.lock().unwrap() = Some(hshm.scratch_mem.clone());
53+
}
54+
}
55+
4156
let mut vm = set_up_hypervisor_partition(
4257
gshm,
4358
&u_sbox.config,

0 commit comments

Comments
 (0)