Skip to content

Commit 4b69851

Browse files
committed
WIP
Modified-by: Tomasz Andrzejak <[email protected]>
1 parent 8687504 commit 4b69851

File tree

12 files changed

+4537
-117
lines changed

12 files changed

+4537
-117
lines changed

Cargo.lock

Lines changed: 193 additions & 112 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_common/Cargo.toml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ Hyperlight's components common to host and guest.
1515
workspace = true
1616

1717
[dependencies]
18-
flatbuffers = { version = "25.9.23", default-features = false }
18+
allocator-api2 = "0.3.1"
19+
arbitrary = {version = "1.4.2", optional = true, features = ["derive"]}
1920
anyhow = { version = "1.0.100", default-features = false }
21+
bitflags = "2.10.0"
22+
bytemuck = { version = "1.24", features = ["derive"] }
23+
fixedbitset = { version = "0.5.7", default-features = false }
24+
flatbuffers = { version = "25.9.23", default-features = false }
2025
log = "0.4.29"
21-
tracing = { version = "0.1.43", optional = true }
22-
arbitrary = {version = "1.4.2", optional = true, features = ["derive"]}
26+
smallvec = "1.15.1"
2327
spin = "0.10.0"
2428
thiserror = { version = "2.0.16", default-features = false }
29+
tracing = { version = "0.1.43", optional = true }
2530

2631
[features]
2732
default = ["tracing"]
@@ -32,6 +37,16 @@ mem_profile = []
3237
std = ["thiserror/std", "log/std", "tracing/std"]
3338
init-paging = []
3439

40+
[dev-dependencies]
41+
criterion = "0.8.1"
42+
hyperlight-testing = { workspace = true }
43+
quickcheck = "1.0.3"
44+
rand = "0.9.2"
45+
3546
[lib]
3647
bench = false # see https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
3748
doctest = false # reduce noise in test output
49+
50+
[[bench]]
51+
name = "buffer_pool"
52+
harness = false
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
use std::hint::black_box;
2+
3+
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
4+
use hyperlight_common::ring::{BufferPool, BufferProvider};
5+
6+
// Helper to create a pool for benchmarking
7+
fn make_pool<const L: usize, const U: usize>(size: usize) -> BufferPool<L, U> {
8+
let base = 0x10000;
9+
BufferPool::<L, U>::new(base, size).unwrap()
10+
}
11+
12+
// Single allocation performance
13+
fn bench_alloc_single(c: &mut Criterion) {
14+
let mut group = c.benchmark_group("alloc_single");
15+
16+
for size in [64, 128, 256, 512, 1024, 1500, 4096].iter() {
17+
group.throughput(Throughput::Elements(1));
18+
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
19+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
20+
b.iter(|| {
21+
let alloc = pool.alloc(black_box(size)).unwrap();
22+
pool.dealloc(alloc).unwrap();
23+
});
24+
});
25+
}
26+
group.finish();
27+
}
28+
29+
// LIFO recycling
30+
fn bench_alloc_lifo(c: &mut Criterion) {
31+
let mut group = c.benchmark_group("alloc_lifo");
32+
33+
for size in [256, 1500, 4096].iter() {
34+
group.throughput(Throughput::Elements(100));
35+
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
36+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
37+
b.iter(|| {
38+
for _ in 0..100 {
39+
let alloc = pool.alloc(black_box(size)).unwrap();
40+
pool.dealloc(alloc).unwrap();
41+
}
42+
});
43+
});
44+
}
45+
group.finish();
46+
}
47+
48+
// Fragmented allocation worst case
49+
fn bench_alloc_fragmented(c: &mut Criterion) {
50+
let mut group = c.benchmark_group("alloc_fragmented");
51+
52+
group.bench_function("fragmented_256", |b| {
53+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
54+
55+
// Create fragmentation pattern: allocate many, free every other
56+
let mut allocations = Vec::new();
57+
for _ in 0..100 {
58+
allocations.push(pool.alloc(128).unwrap());
59+
}
60+
for i in (0..100).step_by(2) {
61+
pool.dealloc(allocations[i]).unwrap();
62+
}
63+
64+
b.iter(|| {
65+
let alloc = pool.alloc(black_box(256)).unwrap();
66+
pool.dealloc(alloc).unwrap();
67+
});
68+
});
69+
70+
group.finish();
71+
}
72+
73+
// Realloc operations
74+
fn bench_realloc(c: &mut Criterion) {
75+
let mut group = c.benchmark_group("realloc");
76+
77+
// In-place grow (same tier)
78+
group.bench_function("grow_inplace", |b| {
79+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
80+
b.iter(|| {
81+
let alloc = pool.alloc(256).unwrap();
82+
let grown = pool.resize(alloc, black_box(512)).unwrap();
83+
pool.dealloc(grown).unwrap();
84+
});
85+
});
86+
87+
// Relocate grow (cross tier)
88+
group.bench_function("grow_relocate", |b| {
89+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
90+
b.iter(|| {
91+
let alloc = pool.alloc(128).unwrap();
92+
// Block in-place growth
93+
let blocker = pool.alloc(256).unwrap();
94+
let grown = pool.resize(alloc, black_box(1500)).unwrap();
95+
pool.dealloc(grown).unwrap();
96+
pool.dealloc(blocker).unwrap();
97+
});
98+
});
99+
100+
// Shrink
101+
group.bench_function("shrink", |b| {
102+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
103+
b.iter(|| {
104+
let alloc = pool.alloc(1500).unwrap();
105+
let shrunk = pool.resize(alloc, black_box(256)).unwrap();
106+
pool.dealloc(shrunk).unwrap();
107+
});
108+
});
109+
110+
group.finish();
111+
}
112+
113+
// Free performance
114+
fn bench_free(c: &mut Criterion) {
115+
let mut group = c.benchmark_group("free");
116+
117+
for size in [256, 1500, 4096].iter() {
118+
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
119+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
120+
b.iter(|| {
121+
let alloc = pool.alloc(size).unwrap();
122+
pool.dealloc(black_box(alloc)).unwrap();
123+
});
124+
});
125+
}
126+
127+
group.finish();
128+
}
129+
130+
// Cursor optimization
131+
fn bench_last_free_run(c: &mut Criterion) {
132+
let mut group = c.benchmark_group("last_free_run");
133+
134+
// With cursor optimization (LIFO)
135+
group.bench_function("lifo_pattern", |b| {
136+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
137+
b.iter(|| {
138+
let alloc = pool.alloc(256).unwrap();
139+
pool.dealloc(alloc).unwrap();
140+
let alloc2 = pool.alloc(black_box(256)).unwrap();
141+
pool.dealloc(alloc2).unwrap();
142+
});
143+
});
144+
145+
// Without cursor benefit (FIFO-like)
146+
group.bench_function("fifo_pattern", |b| {
147+
let pool = make_pool::<256, 4096>(4 * 1024 * 1024);
148+
let mut queue = Vec::new();
149+
150+
// Pre-fill queue
151+
for _ in 0..10 {
152+
queue.push(pool.alloc(256).unwrap());
153+
}
154+
155+
b.iter(|| {
156+
// FIFO: free oldest, allocate new
157+
let old = queue.remove(0);
158+
pool.dealloc(old).unwrap();
159+
queue.push(pool.alloc(black_box(256)).unwrap());
160+
});
161+
});
162+
163+
group.finish();
164+
}
165+
166+
criterion_group!(
167+
benches,
168+
bench_alloc_single,
169+
bench_alloc_lifo,
170+
bench_alloc_fragmented,
171+
bench_realloc,
172+
bench_free,
173+
bench_last_free_run,
174+
);
175+
176+
criterion_main!(benches);

src/hyperlight_common/src/flatbuffer_wrappers/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ pub fn estimate_flatbuffer_capacity(function_name: &str, args: &[ParameterValue]
272272
estimated_capacity.next_power_of_two()
273273
}
274274

275-
#[cfg(test)]
275+
#[cfg(all(test, not(miri)))]
276276
mod tests {
277277
use alloc::string::ToString;
278278
use alloc::vec;

src/hyperlight_common/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ limitations under the License.
1818
#![cfg_attr(not(any(test, debug_assertions)), warn(clippy::expect_used))]
1919
#![cfg_attr(not(any(test, debug_assertions)), warn(clippy::unwrap_used))]
2020
// We use Arbitrary during fuzzing, which requires std
21-
#![cfg_attr(not(feature = "fuzzing"), no_std)]
21+
#![cfg_attr(not(any(feature = "fuzzing", test, miri)), no_std)]
2222

2323
extern crate alloc;
2424

@@ -41,3 +41,6 @@ pub mod func;
4141
// cbindgen:ignore
4242
#[cfg(feature = "init-paging")]
4343
pub mod vmem;
44+
45+
/// cbindgen:ignore
46+
pub mod ring;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use core::marker::PhantomData;
2+
use core::ptr::NonNull;
3+
4+
use bytemuck::Pod;
5+
6+
pub trait MmioAccess: Pod {
7+
/// Acquire-ordered read from base pointer
8+
///
9+
/// # Safety
10+
///
11+
/// `base` must point to a valid Self in MMIO.
12+
unsafe fn read_acquire(base: core::ptr::NonNull<Self>) -> Self;
13+
14+
/// Plain volatile read from base pointer (no ordering)
15+
///
16+
/// # Safety
17+
///
18+
/// `base` must point to a valid Self in MMIO.
19+
unsafe fn read_volatile(base: core::ptr::NonNull<Self>) -> Self {
20+
unsafe { core::ptr::read_volatile(base.as_ptr()) }
21+
}
22+
23+
/// Release-ordered write to base pointer
24+
///
25+
/// # Safety
26+
///
27+
/// `base` must point to a valid Self in MMIO.
28+
unsafe fn write_release(base: core::ptr::NonNull<Self>, val: Self);
29+
30+
/// Plain volatile write to base pointer (no ordering)
31+
///
32+
/// # Safety
33+
///
34+
/// `base` must point to a valid Self in MMIO.
35+
unsafe fn write_volatile(base: core::ptr::NonNull<Self>, val: Self) {
36+
unsafe { core::ptr::write_volatile(base.as_ptr(), val) }
37+
}
38+
}
39+
40+
/// A view into a `T` stored in shared memory.
41+
#[derive(Debug)]
42+
pub struct MmioView<'a, T: MmioAccess> {
43+
base: NonNull<T>,
44+
owner: PhantomData<&'a T>,
45+
}
46+
47+
impl<'a, T: MmioAccess> MmioView<'a, T> {
48+
/// # Safety
49+
///
50+
/// `base` must be valid for 'a and point to a valid T in MMIO.
51+
pub unsafe fn new(base: NonNull<T>) -> Self {
52+
Self {
53+
base,
54+
owner: PhantomData,
55+
}
56+
}
57+
58+
#[inline(always)]
59+
pub fn read_acquire(&self) -> T {
60+
unsafe { T::read_acquire(self.base) }
61+
}
62+
63+
#[inline(always)]
64+
pub fn read_volatile(&self) -> T {
65+
unsafe { T::read_volatile(self.base) }
66+
}
67+
}
68+
69+
/// A mutable view into a `T` stored in shared memory.
70+
#[derive(Debug)]
71+
pub struct MmioViewMut<'a, T: MmioAccess> {
72+
base: NonNull<T>,
73+
owner: PhantomData<&'a mut T>,
74+
}
75+
76+
impl<'a, T: MmioAccess> MmioViewMut<'a, T> {
77+
/// # Safety
78+
///
79+
/// `base` must be valid for 'a and point to a valid T in MMIO.
80+
pub unsafe fn new(base: NonNull<T>) -> Self {
81+
Self {
82+
base,
83+
owner: PhantomData,
84+
}
85+
}
86+
87+
#[inline(always)]
88+
pub fn read_acquire(&self) -> T {
89+
unsafe { T::read_acquire(self.base) }
90+
}
91+
92+
#[inline(always)]
93+
pub fn read_volatile(&self) -> T {
94+
unsafe { T::read_volatile(self.base) }
95+
}
96+
97+
#[inline(always)]
98+
pub fn write_release(&mut self, val: T) {
99+
unsafe { T::write_release(self.base, val) }
100+
}
101+
102+
#[inline(always)]
103+
pub fn write_volatile(&mut self, val: T) {
104+
unsafe { T::write_volatile(self.base, val) }
105+
}
106+
107+
#[inline(always)]
108+
pub fn as_view(&self) -> MmioView<'_, T> {
109+
unsafe { MmioView::new(self.base) }
110+
}
111+
}

0 commit comments

Comments
 (0)