From 7126afea5ededf02ad752bb70e9053ff335ca1d5 Mon Sep 17 00:00:00 2001 From: bunnie Date: Thu, 8 Feb 2024 14:35:07 +0800 Subject: [PATCH] PERF: improve getrandom performance on smaller requests use scalar messages to fill buffer smaller than 64 bytes. --- imports/getrandom/src/xous.rs | 124 ++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/imports/getrandom/src/xous.rs b/imports/getrandom/src/xous.rs index 944380562..69da27413 100644 --- a/imports/getrandom/src/xous.rs +++ b/imports/getrandom/src/xous.rs @@ -11,9 +11,9 @@ /// In general, `getrandom` will be fast enough for interactive usage, though /// significantly slower than a user-space CSPRNG; for the latter consider /// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). - use core::sync::atomic::AtomicU32; use core::sync::atomic::Ordering; + use xous_ipc::Buffer; static TRNG_CONN: AtomicU32 = AtomicU32::new(0); @@ -28,10 +28,8 @@ fn ensure_trng_conn() { if TRNG_CONN.load(Ordering::SeqCst) == 0 { let xns = xous_names::XousNames::new().unwrap(); TRNG_CONN.store( - xns - .request_connection_blocking("_TRNG manager_") - .expect("Can't connect to TRNG server"), - Ordering::SeqCst + xns.request_connection_blocking("_TRNG manager_").expect("Can't connect to TRNG server"), + Ordering::SeqCst, ); } } @@ -46,10 +44,7 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), crate::error::Error> { } pub fn fill_buf(data: &mut [u32]) { - let mut tb = TrngBuf { - data: [0; 1024], - len: 0, - }; + let mut tb = TrngBuf { data: [0; 1024], len: 0 }; assert!(data.len() <= tb.data.len()); tb.len = data.len() as u16; let mut buf = Buffer::into_buf(tb).unwrap(); @@ -58,39 +53,84 @@ pub fn fill_buf(data: &mut [u32]) { assert!(rtb.len as usize == data.len()); data.copy_from_slice(&rtb.data); } -/// this is less efficient that the implementation in TRNG, but has fewer dependencies -/// In particular, it will always use a memory message to fetch a TRNG value, even if it's just a u32 or u64 -fn fill_bytes(dest: &mut [u8]) { - // big chunks handled here, using in-place transformations - for chunk in dest.chunks_exact_mut(4096) { - let chunk_u32 = unsafe { - core::slice::from_raw_parts_mut(chunk.as_mut_ptr() as *mut u32, chunk.len() / 4) - }; - fill_buf(chunk_u32); - } - // smaller chunks, we absorb the fill_buf routine above here so we amortize the cost of - // initializing the empty 4k-page... - let remainder = dest.chunks_exact_mut(4096).into_remainder(); - if remainder.len() != 0 { - let mut tb = TrngBuf { - data: [0; 1024], - len: 0, - }; - tb.len = if remainder.len() % 4 == 0 { - (remainder.len() / 4) as u16 - } else { - 1 + (remainder.len() / 4) as u16 - }; - let mut buf = Buffer::into_buf(tb).unwrap(); - buf.lend_mut(TRNG_CONN.load(Ordering::SeqCst), 1 /* FillTrng */).unwrap(); - let rtb = buf.as_flat::().unwrap(); - // transform the whole buffer into a ret_u8 slice (including trailing zeroes) - let ret_u8 = unsafe { - core::slice::from_raw_parts(rtb.data.as_ptr() as *mut u8, rtb.data.len()*4) - }; - // we've allocated an extra remainder word to handle the last word overflow, if anything - // we'll end up throwing away a couple of unused bytes, but better than copying zeroes! - remainder.copy_from_slice(&ret_u8[..remainder.len()]); +pub fn next_u32() -> u32 { + let response = xous::send_message( + TRNG_CONN.load(Ordering::SeqCst), + xous::Message::new_blocking_scalar(0 /* GetTrng */, 1 /* count */, 0, 0, 0), + ) + .expect("TRNG|LIB: can't get_u32"); + if let xous::Result::Scalar2(trng, _) = response { + trng as u32 + } else { + panic!("unexpected return value: {:#?}", response); + } +} + +pub fn next_u64() -> u64 { + let response = xous::send_message( + TRNG_CONN.load(Ordering::SeqCst), + xous::Message::new_blocking_scalar(0 /* GetTrng */, 2 /* count */, 0, 0, 0), + ) + .expect("TRNG|LIB: can't get_u32"); + if let xous::Result::Scalar2(lo, hi) = response { + lo as u64 | ((hi as u64) << 32) + } else { + panic!("unexpected return value: {:#?}", response); + } +} + +pub fn fill_bytes_via_next(dest: &mut [u8]) { + use core::mem::transmute; + let mut left = dest; + while left.len() >= 8 { + let (l, r) = { left }.split_at_mut(8); + left = r; + let chunk: [u8; 8] = unsafe { transmute(next_u64().to_le()) }; + l.copy_from_slice(&chunk); + } + let n = left.len(); + if n > 4 { + let chunk: [u8; 8] = unsafe { transmute(next_u64().to_le()) }; + left.copy_from_slice(&chunk[..n]); + } else if n > 0 { + let chunk: [u8; 4] = unsafe { transmute(next_u32().to_le()) }; + left.copy_from_slice(&chunk[..n]); + } +} + +/// This implementation will try to fill bytes using the more efficient but smaller scalar messages, +/// until it becomes faster to use a memory message. +fn fill_bytes(dest: &mut [u8]) { + if dest.len() < 64 { + fill_bytes_via_next(dest); + } else { + // big chunks handled here, using in-place transformations + for chunk in dest.chunks_exact_mut(4096) { + let chunk_u32 = + unsafe { core::slice::from_raw_parts_mut(chunk.as_mut_ptr() as *mut u32, chunk.len() / 4) }; + fill_buf(chunk_u32); + } + // smaller chunks, we absorb the fill_buf routine above here so we amortize the cost of + // initializing the empty 4k-page... + let remainder = dest.chunks_exact_mut(4096).into_remainder(); + if remainder.len() != 0 { + let mut tb = TrngBuf { data: [0; 1024], len: 0 }; + tb.len = if remainder.len() % 4 == 0 { + (remainder.len() / 4) as u16 + } else { + 1 + (remainder.len() / 4) as u16 + }; + let mut buf = Buffer::into_buf(tb).unwrap(); + buf.lend_mut(TRNG_CONN.load(Ordering::SeqCst), 1 /* FillTrng */).unwrap(); + let rtb = buf.as_flat::().unwrap(); + + // transform the whole buffer into a ret_u8 slice (including trailing zeroes) + let ret_u8 = + unsafe { core::slice::from_raw_parts(rtb.data.as_ptr() as *mut u8, rtb.data.len() * 4) }; + // we've allocated an extra remainder word to handle the last word overflow, if anything + // we'll end up throwing away a couple of unused bytes, but better than copying zeroes! + remainder.copy_from_slice(&ret_u8[..remainder.len()]); + } } }