use core::alloc::Layout;
const ALIGN: usize = 64;
fn region_bytes(elems: usize, esize: usize) -> usize {
elems
.checked_mul(esize)
.and_then(|b| b.checked_next_multiple_of(ALIGN))
.unwrap_or_else(|| workspace_too_large())
}
#[cold]
#[inline(never)]
fn workspace_too_large() -> ! {
panic!("gemmkit: GEMM is too large; the pack workspace size overflows usize")
}
pub struct Workspace {
ptr: *mut u8,
cap: usize,
}
unsafe impl Send for Workspace {}
impl Workspace {
pub const fn new() -> Self {
Self {
ptr: core::ptr::null_mut(),
cap: 0,
}
}
pub fn with_capacity(bytes: usize) -> Self {
let mut ws = Self::new();
if bytes > 0 {
ws.ensure(bytes);
}
ws
}
fn ensure(&mut self, bytes: usize) {
if bytes <= self.cap {
return;
}
let new_cap = bytes.next_power_of_two().max(ALIGN);
unsafe {
let layout = Layout::from_size_align(new_cap, ALIGN).expect("valid layout");
let p = if self.ptr.is_null() {
alloc::alloc::alloc(layout)
} else {
let old = Layout::from_size_align(self.cap, ALIGN).expect("valid layout");
alloc::alloc::realloc(self.ptr, old, new_cap)
};
if p.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
self.ptr = p;
self.cap = new_cap;
}
}
pub(crate) fn regions<T>(
&mut self,
a_elems_per_region: usize,
a_regions: usize,
b_elems: usize,
) -> Regions<T> {
let esize = core::mem::size_of::<T>().max(1);
let a_bytes_per_region = region_bytes(a_elems_per_region, esize);
let a_total = a_bytes_per_region
.checked_mul(a_regions.max(1))
.unwrap_or_else(|| workspace_too_large());
let b_bytes = region_bytes(b_elems, esize);
self.ensure(
a_total
.checked_add(b_bytes)
.unwrap_or_else(|| workspace_too_large()),
);
let base = self.ptr;
let b_base = unsafe { base.add(a_total) };
Regions {
a_base: base as *mut T,
a_stride: a_bytes_per_region / esize,
b_base: b_base as *mut T,
}
}
}
impl Default for Workspace {
fn default() -> Self {
Self::new()
}
}
impl Drop for Workspace {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
let layout = Layout::from_size_align(self.cap, ALIGN).expect("valid layout");
alloc::alloc::dealloc(self.ptr, layout);
}
}
}
}
pub(crate) struct Regions<T> {
pub a_base: *mut T,
pub a_stride: usize,
pub b_base: *mut T,
}
#[cfg(feature = "std")]
std::thread_local! {
static POOL: core::cell::RefCell<Workspace> = const { core::cell::RefCell::new(Workspace::new()) };
}
#[cfg(feature = "std")]
pub(crate) fn with_thread_pool<R>(f: impl FnOnce(&mut Workspace) -> R) -> R {
POOL.with(|p| match p.try_borrow_mut() {
Ok(mut ws) => f(&mut ws),
Err(_) => f(&mut Workspace::new()),
})
}
#[cfg(not(feature = "std"))]
pub(crate) fn with_thread_pool<R>(f: impl FnOnce(&mut Workspace) -> R) -> R {
f(&mut Workspace::new())
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::{ALIGN, region_bytes};
fn panic_msg(f: impl FnOnce() + std::panic::UnwindSafe) -> String {
match std::panic::catch_unwind(f) {
Ok(()) => String::new(),
Err(e) => e
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| e.downcast_ref::<String>().cloned())
.unwrap_or_default(),
}
}
#[test]
fn region_bytes_normal() {
assert_eq!(region_bytes(0, 4), 0);
assert_eq!(
region_bytes(1000, 4),
(1000usize * 4).next_multiple_of(ALIGN)
);
assert_eq!(region_bytes(7, 1), ALIGN); }
#[test]
fn region_bytes_byte_product_overflow_fails_closed() {
let elems = 1usize << (usize::BITS - 1); let msg = panic_msg(|| {
region_bytes(elems, 2); });
assert!(
msg.contains("too large"),
"expected too-large panic, got {msg:?}"
);
}
#[test]
fn region_bytes_roundup_overflow_fails_closed() {
let msg = panic_msg(|| {
region_bytes(usize::MAX, 1); });
assert!(
msg.contains("too large"),
"expected too-large panic, got {msg:?}"
);
}
}