pub fn helpers() -> usize {
imp::helpers()
}
pub fn par_for<F: Fn(usize) + Sync>(chunks: usize, body: F) {
imp::par_for(chunks, &body)
}
#[inline]
pub fn split(len: usize, parts: usize, i: usize) -> usize {
len * i / parts.max(1)
}
pub fn chunks_for(len: usize, min: usize) -> usize {
let threads = helpers() + 1;
if threads == 1 || len == 0 {
return 1;
}
(len / min.max(1)).clamp(1, threads * 4)
}
#[derive(Clone, Copy)]
pub struct SyncPtr<T>(pub *mut T);
unsafe impl<T> Sync for SyncPtr<T> {}
unsafe impl<T> Send for SyncPtr<T> {}
impl<T> SyncPtr<T> {
#[inline]
pub fn get(self) -> *mut T {
self.0
}
}
pub fn worker_loop() -> ! {
imp::worker_loop()
}
#[cfg(target_feature = "atomics")]
mod imp {
use core::arch::wasm32::{memory_atomic_notify, memory_atomic_wait32};
use core::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering::SeqCst};
static EPOCH: AtomicU32 = AtomicU32::new(0);
static HELPERS: AtomicUsize = AtomicUsize::new(0);
static ACTIVE: AtomicUsize = AtomicUsize::new(0);
static BUSY: AtomicBool = AtomicBool::new(false);
static JOB_DATA: AtomicUsize = AtomicUsize::new(0);
static JOB_CALL: AtomicUsize = AtomicUsize::new(0);
static JOB_CHUNKS: AtomicUsize = AtomicUsize::new(0);
static NEXT: AtomicUsize = AtomicUsize::new(0);
static PENDING: AtomicUsize = AtomicUsize::new(0);
pub fn helpers() -> usize {
HELPERS.load(SeqCst)
}
unsafe fn trampoline<F: Fn(usize) + Sync>(data: usize, i: usize) {
(*(data as *const F))(i)
}
fn run_chunks(data: usize, call: usize, chunks: usize) {
let f: unsafe fn(usize, usize) = unsafe { core::mem::transmute(call) };
loop {
let i = NEXT.fetch_add(1, SeqCst);
if i >= chunks {
return;
}
unsafe { f(data, i) };
PENDING.fetch_sub(1, SeqCst);
}
}
pub fn par_for<F: Fn(usize) + Sync>(chunks: usize, body: &F) {
if chunks <= 1 || HELPERS.load(SeqCst) == 0 || BUSY.swap(true, SeqCst) {
(0..chunks).for_each(body);
return;
}
EPOCH.fetch_add(1, SeqCst);
while ACTIVE.load(SeqCst) != 0 {
core::hint::spin_loop();
}
let data = body as *const F as usize;
let call = trampoline::<F> as unsafe fn(usize, usize) as usize;
JOB_DATA.store(data, SeqCst);
JOB_CALL.store(call, SeqCst);
JOB_CHUNKS.store(chunks, SeqCst);
PENDING.store(chunks, SeqCst);
NEXT.store(0, SeqCst);
EPOCH.fetch_add(1, SeqCst);
unsafe { memory_atomic_notify(EPOCH.as_ptr() as *mut i32, u32::MAX) };
run_chunks(data, call, chunks);
while PENDING.load(SeqCst) != 0 {
core::hint::spin_loop();
}
BUSY.store(false, SeqCst);
}
pub fn worker_loop() -> ! {
HELPERS.fetch_add(1, SeqCst);
let mut seen = EPOCH.load(SeqCst);
loop {
let e = EPOCH.load(SeqCst);
if e == seen || e & 1 == 1 {
unsafe { memory_atomic_wait32(EPOCH.as_ptr() as *mut i32, e as i32, -1) };
continue;
}
ACTIVE.fetch_add(1, SeqCst);
if EPOCH.load(SeqCst) == e {
seen = e;
run_chunks(
JOB_DATA.load(SeqCst),
JOB_CALL.load(SeqCst),
JOB_CHUNKS.load(SeqCst),
);
}
ACTIVE.fetch_sub(1, SeqCst);
}
}
}
#[cfg(all(feature = "rayon", not(target_feature = "atomics")))]
mod imp {
use rayon::prelude::*;
pub fn helpers() -> usize {
rayon::current_num_threads().saturating_sub(1)
}
pub fn par_for<F: Fn(usize) + Sync>(chunks: usize, body: &F) {
if chunks <= 1 {
(0..chunks).for_each(body);
} else {
(0..chunks).into_par_iter().for_each(body);
}
}
pub fn worker_loop() -> ! {
panic!("worker_loop is the threaded wasm build's; native helpers are rayon's threads")
}
}
#[cfg(all(not(feature = "rayon"), not(target_feature = "atomics")))]
mod imp {
pub fn helpers() -> usize {
0
}
pub fn par_for<F: Fn(usize) + Sync>(chunks: usize, body: &F) {
(0..chunks).for_each(body)
}
pub fn worker_loop() -> ! {
panic!("worker_loop requires the threaded core build")
}
}