use crate::error::PoolError;
pub struct AmxTaskPool {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
inner: imp::Inner,
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
_marker: core::marker::PhantomData<()>,
}
impl AmxTaskPool {
pub fn new(num_workers: usize) -> Result<Self, PoolError> {
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
let _ = num_workers;
Err(PoolError::Unsupported)
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
if num_workers == 0 {
return Err(PoolError::Unsupported);
}
let inner = imp::Inner::new(num_workers)?;
Ok(Self { inner })
}
}
pub fn spawn<F, T>(&self, task: F) -> std::sync::mpsc::Receiver<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
let _ = task;
let (_, rx) = std::sync::mpsc::channel();
rx
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
self.inner.spawn(task)
}
}
pub fn worker_count(&self) -> usize {
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
0
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
self.inner.workers.len()
}
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
mod imp {
use super::PoolError;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
type BoxedTask = Box<dyn FnOnce() + Send + 'static>;
pub(super) struct Inner {
pub(super) workers: Vec<Worker>,
next: AtomicUsize,
}
pub(super) struct Worker {
sender: mpsc::Sender<BoxedTask>,
handle: Option<thread::JoinHandle<()>>,
}
impl Inner {
pub(super) fn new(num_workers: usize) -> Result<Self, PoolError> {
let mut workers = Vec::with_capacity(num_workers);
for _ in 0..num_workers {
let (tx, rx) = mpsc::channel::<BoxedTask>();
let handle = thread::spawn(move || {
let _matrix = match acpu::matrix::Matrix::new() {
Ok(m) => m,
Err(_) => return,
};
while let Ok(task) = rx.recv() {
task();
}
});
workers.push(Worker {
sender: tx,
handle: Some(handle),
});
}
Ok(Self {
workers,
next: AtomicUsize::new(0),
})
}
pub(super) fn spawn<F, T>(&self, task: F) -> mpsc::Receiver<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let (result_tx, result_rx) = mpsc::channel::<T>();
let wrapped: BoxedTask = Box::new(move || {
let out = task();
let _ = result_tx.send(out);
});
let idx = self.next.fetch_add(1, Ordering::Relaxed) % self.workers.len();
let _ = self.workers[idx].sender.send(wrapped);
result_rx
}
}
impl Drop for Inner {
fn drop(&mut self) {
for w in self.workers.drain(..) {
let Worker { sender, mut handle } = w;
drop(sender);
if let Some(h) = handle.take() {
let _ = h.join();
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
fn spawn_returns_task_result() {
let pool = AmxTaskPool::new(2).expect("Apple Silicon supports AMX");
let rx = pool.spawn(|| 42u32);
assert_eq!(rx.recv().unwrap(), 42);
}
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
fn many_tasks_distribute_across_workers() {
let pool = AmxTaskPool::new(4).unwrap();
let receivers: Vec<_> = (0..16).map(|i| pool.spawn(move || i * 2)).collect();
let mut results: Vec<u32> = receivers.into_iter().map(|r| r.recv().unwrap()).collect();
results.sort();
assert_eq!(results, (0..16).map(|i| i * 2).collect::<Vec<_>>());
}
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
fn worker_count_matches_request() {
let pool = AmxTaskPool::new(3).unwrap();
assert_eq!(pool.worker_count(), 3);
}
#[test]
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
fn zero_workers_is_unsupported() {
assert!(matches!(AmxTaskPool::new(0), Err(PoolError::Unsupported)));
}
#[test]
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
fn unsupported_on_non_apple_silicon() {
assert!(matches!(AmxTaskPool::new(4), Err(PoolError::Unsupported)));
}
}