use std::cell::UnsafeCell;
use std::hint::black_box;
use std::num::NonZeroU16;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
use bytemuck::Pod;
use hyperlight_common::virtq::{
BufferPool, BufferProvider, Descriptor, Layout, MemOps, Notifier, QueueStats, RecyclePool,
ReplyChain, UsedChain, VirtqConsumer, VirtqProducer,
};
pub const LOWER_SLOT: usize = 256;
pub const UPPER_SLOT: usize = 4096;
pub const POOL_SIZE: usize = 8 * 1024 * 1024;
pub type RunBufferPool = BufferPool<LOWER_SLOT, UPPER_SLOT>;
#[derive(Clone)]
struct BenchMem {
inner: Arc<BenchMemInner>,
}
struct BenchMemInner {
storage: UnsafeCell<Vec<u8>>,
base_addr: u64,
}
unsafe impl Send for BenchMemInner {}
unsafe impl Sync for BenchMemInner {}
impl BenchMem {
fn new(size: usize) -> Self {
let storage = vec![0u8; size];
let base_addr = storage.as_ptr() as u64;
Self {
inner: Arc::new(BenchMemInner {
storage: UnsafeCell::new(storage),
base_addr,
}),
}
}
fn base_addr(&self) -> u64 {
self.inner.base_addr
}
fn ptr_for_addr(&self, addr: u64) -> *mut u8 {
let storage = unsafe { &mut *self.inner.storage.get() };
let offset = (addr - self.inner.base_addr) as usize;
storage.as_mut_ptr().wrapping_add(offset)
}
}
unsafe impl MemOps for BenchMem {
type Error = core::convert::Infallible;
fn read(&self, addr: u64, dst: &mut [u8]) -> Result<(), Self::Error> {
let src = self.ptr_for_addr(addr);
unsafe {
ptr::copy_nonoverlapping(src, dst.as_mut_ptr(), dst.len());
}
Ok(())
}
fn write(&self, addr: u64, src: &[u8]) -> Result<(), Self::Error> {
let dst = self.ptr_for_addr(addr);
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len());
}
Ok(())
}
fn read_val<T: Pod>(&self, addr: u64) -> Result<T, Self::Error> {
let ptr = self.ptr_for_addr(addr).cast::<T>();
Ok(unsafe { ptr::read_volatile(ptr) })
}
fn write_val<T: Pod>(&self, addr: u64, val: T) -> Result<(), Self::Error> {
let ptr = self.ptr_for_addr(addr).cast::<T>();
unsafe { ptr::write_volatile(ptr, val) };
Ok(())
}
fn load_acquire(&self, addr: u64) -> Result<u16, Self::Error> {
let ptr = self.ptr_for_addr(addr).cast::<AtomicU16>();
Ok(unsafe { (*ptr).load(Ordering::Acquire) })
}
fn store_release(&self, addr: u64, val: u16) -> Result<(), Self::Error> {
let ptr = self.ptr_for_addr(addr).cast::<AtomicU16>();
unsafe { (*ptr).store(val, Ordering::Release) };
Ok(())
}
unsafe fn as_slice(&self, addr: u64, len: usize) -> Result<&[u8], Self::Error> {
let ptr = self.ptr_for_addr(addr);
Ok(unsafe { core::slice::from_raw_parts(ptr, len) })
}
unsafe fn as_mut_slice(&self, addr: u64, len: usize) -> Result<&mut [u8], Self::Error> {
let ptr = self.ptr_for_addr(addr);
Ok(unsafe { core::slice::from_raw_parts_mut(ptr, len) })
}
}
#[derive(Clone)]
struct BenchNotifier {
count: Arc<AtomicUsize>,
}
impl BenchNotifier {
fn new() -> Self {
Self {
count: Arc::new(AtomicUsize::new(0)),
}
}
}
impl Notifier for BenchNotifier {
fn notify(&self, _stats: QueueStats) {
self.count.fetch_add(1, Ordering::Relaxed);
}
}
pub struct BenchPair<P> {
producer: VirtqProducer<BenchMem, BenchNotifier, P>,
consumer: VirtqConsumer<BenchMem, BenchNotifier>,
}
fn align_up(value: usize, align: usize) -> usize {
value.div_ceil(align) * align
}
pub fn make_pair<P>(descs: usize, make_pool: impl FnOnce(u64, usize) -> P) -> BenchPair<P>
where
P: BufferProvider + Clone,
{
let ring_size = Layout::query_size(descs);
let mem = BenchMem::new(ring_size + POOL_SIZE + 0x20000);
let ring_base = align_up(mem.base_addr() as usize, Descriptor::ALIGN) as u64;
let descs_nz = NonZeroU16::new(descs as u16).unwrap();
let layout = unsafe { Layout::from_base(ring_base, descs_nz).unwrap() };
let pool_base = align_up((ring_base + ring_size as u64 + 0x1000) as usize, UPPER_SLOT) as u64;
let pool = make_pool(pool_base, POOL_SIZE);
let notifier = BenchNotifier::new();
let producer = VirtqProducer::new(layout, mem.clone(), notifier.clone(), pool);
let consumer = VirtqConsumer::new(layout, mem, notifier);
BenchPair { producer, consumer }
}
pub fn run_buffer_pool(base: u64, size: usize) -> RunBufferPool {
BufferPool::new(base, size).unwrap()
}
pub fn fragmented_run_buffer_pool(base: u64, size: usize, payload_size: usize) -> RunBufferPool {
let pool = run_buffer_pool(base, size);
let payload_slots = payload_size.div_ceil(UPPER_SLOT);
let prefix_slots = 32;
let suffix_slots = 32;
let allocated: Vec<_> = (0..prefix_slots + payload_slots + suffix_slots)
.map(|_| pool.alloc(UPPER_SLOT).unwrap())
.collect();
for alloc in &allocated[prefix_slots..prefix_slots + payload_slots] {
pool.dealloc(alloc.addr).unwrap();
}
pool
}
pub fn recycle_pool(base: u64, size: usize) -> RecyclePool {
RecyclePool::new(base, size, UPPER_SLOT).unwrap()
}
pub fn fragmented_recycle_pool(base: u64, size: usize, payload_size: usize) -> RecyclePool {
let pool = recycle_pool(base, size);
let payload_slots = payload_size.div_ceil(UPPER_SLOT);
let allocated: Vec<_> = (0..payload_slots * 2 + 16)
.map(|_| pool.alloc(UPPER_SLOT).unwrap())
.collect();
for alloc in allocated.iter().step_by(2).take(payload_slots) {
pool.dealloc(alloc.addr).unwrap();
}
pool
}
pub fn readonly_roundtrip<P>(pair: &mut BenchPair<P>, payload: &[u8]) -> UsedChain
where
P: BufferProvider + Clone + Send + 'static,
{
let mut chain = pair
.producer
.chain()
.readable(payload.len())
.build()
.unwrap();
chain.write_all(payload).unwrap();
let token = pair.producer.submit(chain).unwrap();
let (recv, reply) = pair.consumer.poll(payload.len()).unwrap().unwrap();
black_box(recv.segments().segment_count());
pair.consumer.complete(reply).unwrap();
let used = pair.producer.poll().unwrap().unwrap();
debug_assert_eq!(used.token(), token);
used
}
pub fn readwrite_roundtrip<P>(pair: &mut BenchPair<P>, request: &[u8], response: &[u8]) -> UsedChain
where
P: BufferProvider + Clone + Send + 'static,
{
let mut chain = pair
.producer
.chain()
.readable(request.len())
.writable(response.len())
.build()
.unwrap();
chain.write_all(request).unwrap();
let token = pair.producer.submit(chain).unwrap();
let (recv, reply) = pair.consumer.poll(request.len()).unwrap().unwrap();
black_box(recv.segments().segment_count());
let ReplyChain::Writable(mut writable) = reply else {
panic!("expected writable reply");
};
writable.write_all(response).unwrap();
pair.consumer.complete(writable).unwrap();
let used = pair.producer.poll().unwrap().unwrap();
debug_assert_eq!(used.token(), token);
used
}