use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, Ordering};
pub const HOST_HELPED: u32 = u32::MAX;
pub const PROBE_BURST: u32 = 8;
pub const PROBE_MIN: u32 = 64;
pub const PROBE_MAX: u32 = 1024;
pub const PROBE_INDICES: usize = 8;
pub type HostParallelForFn =
unsafe fn(host: *mut c_void, total: usize, body: &(dyn Fn(usize) + Sync));
#[derive(Clone, Copy)]
pub struct HostParallel {
host: *mut c_void,
run: HostParallelForFn,
probe: *const AtomicU32,
}
impl core::fmt::Debug for HostParallel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HostParallel")
.field("host", &self.host)
.finish_non_exhaustive()
}
}
impl HostParallel {
pub const unsafe fn new(
host: *mut c_void,
run: HostParallelForFn,
probe: *const AtomicU32,
) -> Self {
Self { host, run, probe }
}
pub fn prefer_host(&self) -> bool {
if self.helped() {
return true;
}
if !self.probe_due() {
return false;
}
self.run(PROBE_INDICES, &|_| ());
self.helped()
}
fn probe_due(&self) -> bool {
let Some(cell) = self.probe_cell() else {
return false;
};
let mut seen = cell.load(Ordering::Relaxed);
loop {
if seen == HOST_HELPED {
return false;
}
let period = seen >> 16;
let countdown = seen & 0xFFFF;
let (next, probe) = if period == 0 {
let asked = countdown + 1;
let next = if asked >= PROBE_BURST {
(PROBE_MIN << 16) | PROBE_MIN
} else {
asked
};
(next, true)
} else if countdown == 0 {
let period = period.saturating_mul(2).min(PROBE_MAX);
((period << 16) | period, true)
} else {
((period << 16) | (countdown - 1), false)
};
match cell.compare_exchange_weak(seen, next, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => return probe,
Err(current) => seen = current,
}
}
}
#[must_use]
pub fn helped(&self) -> bool {
self.probe_cell()
.is_none_or(|cell| cell.load(Ordering::Relaxed) == HOST_HELPED)
}
fn probe_cell(&self) -> Option<&AtomicU32> {
(!self.probe.is_null()).then(|| unsafe { &*self.probe })
}
pub fn run(&self, total: usize, body: &(dyn Fn(usize) + Sync)) {
match total {
0 => (),
1 => {
let _task = TaskGuard::enter();
body(0);
}
_ => {
let marked = |index: usize| {
let _task = TaskGuard::enter();
body(index);
};
unsafe { (self.run)(self.host, total, &marked) }
}
}
}
}
thread_local! {
static CURRENT: core::cell::Cell<Option<HostParallel>> =
const { core::cell::Cell::new(None) };
static IN_TASK: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
pub struct Installed {
prev: Option<HostParallel>,
}
impl Installed {
pub fn new(host: HostParallel) -> Self {
Self {
prev: CURRENT.with(|c| c.replace(Some(host))),
}
}
}
impl Drop for Installed {
fn drop(&mut self) {
CURRENT.with(|c| c.set(self.prev));
}
}
pub fn scope<T>(host: HostParallel, f: impl FnOnce() -> T) -> T {
let _installed = Installed::new(host);
f()
}
pub fn without<T>(f: impl FnOnce() -> T) -> T {
struct Restore(Option<HostParallel>);
impl Drop for Restore {
fn drop(&mut self) {
CURRENT.with(|c| c.set(self.0));
}
}
let _restore = Restore(CURRENT.with(|c| c.replace(None)));
f()
}
#[inline]
pub fn current() -> Option<HostParallel> {
CURRENT.with(core::cell::Cell::get)
}
#[inline]
pub fn in_host_task() -> bool {
IN_TASK.with(core::cell::Cell::get)
}
struct TaskGuard(bool);
impl TaskGuard {
fn enter() -> Self {
Self(IN_TASK.with(|c| c.replace(true)))
}
}
impl Drop for TaskGuard {
fn drop(&mut self) {
IN_TASK.with(|c| c.set(self.0));
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
unsafe fn serial_host(_host: *mut c_void, total: usize, body: &(dyn Fn(usize) + Sync)) {
for index in 0..total {
body(index);
}
}
fn serial() -> HostParallel {
unsafe { HostParallel::new(core::ptr::null_mut(), serial_host, core::ptr::null()) }
}
fn with_probe(cell: &AtomicU32) -> HostParallel {
unsafe {
HostParallel::new(
core::ptr::null_mut(),
serial_host,
core::ptr::from_ref(cell),
)
}
}
#[test]
fn a_handle_without_a_probe_cell_always_uses_the_host() {
assert!(serial().prefer_host());
assert!(serial().helped());
}
#[test]
fn the_opening_burst_probes_every_dispatch() {
let cell = AtomicU32::new(0);
let host = with_probe(&cell);
for step in 0..PROBE_BURST {
assert!(host.probe_due(), "dispatch {step} of the burst");
}
assert!(!host.probe_due(), "the burst has to end somewhere");
}
#[test]
fn a_probe_does_not_carry_the_callers_work() {
let cell = AtomicU32::new(0);
static COUNT: AtomicUsize = AtomicUsize::new(0);
unsafe fn counting_host(_host: *mut c_void, total: usize, body: &(dyn Fn(usize) + Sync)) {
COUNT.fetch_add(total, Ordering::Relaxed);
for index in 0..total {
body(index);
}
}
COUNT.store(0, Ordering::Relaxed);
let host = unsafe {
HostParallel::new(
core::ptr::null_mut(),
counting_host,
core::ptr::from_ref(&cell),
)
};
assert!(!host.prefer_host(), "an inline host never proves itself");
assert_eq!(
COUNT.load(Ordering::Relaxed),
PROBE_INDICES,
"the probe dispatch is empty and fixed-size"
);
}
#[test]
fn an_unhelpful_host_is_asked_less_and_less_often() {
let cell = AtomicU32::new(0);
let host = with_probe(&cell);
let mut gaps = Vec::new();
let mut gap = 0u32;
for _ in 0..8400 {
if host.probe_due() {
gaps.push(gap);
gap = 0;
} else {
gap += 1;
}
}
let burst = usize::try_from(PROBE_BURST).unwrap();
assert!(
gaps[..burst].iter().all(|&g| g == 0),
"the opening burst asks on every dispatch"
);
assert_eq!(
&gaps[burst..burst + 4],
&[PROBE_MIN, PROBE_MIN * 2, PROBE_MIN * 4, PROBE_MIN * 8],
"probes should back off geometrically once the burst is over"
);
assert!(
gaps.iter().all(|&g| g <= PROBE_MAX),
"the gap must stay bounded so a wrong guess still self-corrects"
);
assert_eq!(
*gaps.last().unwrap(),
PROBE_MAX,
"and it should settle at the cap"
);
}
#[test]
fn a_helping_host_is_used_from_then_on() {
let cell = AtomicU32::new(0);
let host = with_probe(&cell);
assert!(!host.helped());
cell.store(HOST_HELPED, Ordering::Relaxed);
assert!(host.helped());
for _ in 0..1000 {
assert!(host.prefer_host());
assert!(!host.probe_due(), "a latched host is never probed again");
}
assert_eq!(cell.load(Ordering::Relaxed), HOST_HELPED);
}
#[test]
fn concurrent_dispatches_keep_the_cell_sane() {
let cell = AtomicU32::new(0);
std::thread::scope(|scope| {
for _ in 0..4 {
scope.spawn(|| {
let host = with_probe(&cell);
for _ in 0..5000 {
host.probe_due();
}
});
}
});
let seen = cell.load(Ordering::Relaxed);
assert_ne!(seen, HOST_HELPED, "no thread may invent the sentinel");
assert!((seen >> 16) <= PROBE_MAX);
assert!((seen & 0xFFFF) <= PROBE_MAX);
}
#[test]
fn no_host_is_installed_by_default() {
assert!(current().is_none());
assert!(!in_host_task());
}
#[test]
fn scope_installs_and_restores() {
scope(serial(), || {
assert!(current().is_some());
without(|| assert!(current().is_none()));
assert!(current().is_some());
});
assert!(current().is_none());
}
#[test]
fn scope_restores_on_unwind() {
let unwound = std::panic::catch_unwind(|| {
scope(serial(), || panic!("kernel failed"));
});
assert!(unwound.is_err());
assert!(current().is_none(), "a leaked handle would dangle");
}
#[test]
fn run_covers_every_index_exactly_once() {
let seen: Vec<AtomicUsize> = (0..7).map(|_| AtomicUsize::new(0)).collect();
serial().run(seen.len(), &|index| {
seen[index].fetch_add(1, Ordering::Relaxed);
});
assert!(seen.iter().all(|c| c.load(Ordering::Relaxed) == 1));
}
#[test]
fn empty_total_runs_nothing() {
let calls = AtomicUsize::new(0);
serial().run(0, &|_| {
calls.fetch_add(1, Ordering::Relaxed);
});
assert_eq!(calls.load(Ordering::Relaxed), 0);
}
#[test]
fn a_single_index_runs_inline_and_is_still_marked() {
let marked = AtomicUsize::new(0);
serial().run(1, &|_| {
marked.fetch_add(usize::from(in_host_task()), Ordering::Relaxed);
});
assert_eq!(marked.load(Ordering::Relaxed), 1);
}
#[test]
fn bodies_run_marked_and_the_mark_does_not_leak() {
assert!(!in_host_task());
let marked = AtomicUsize::new(0);
serial().run(4, &|_| {
marked.fetch_add(usize::from(in_host_task()), Ordering::Relaxed);
});
assert_eq!(marked.load(Ordering::Relaxed), 4);
assert!(!in_host_task());
}
#[test]
fn the_mark_survives_a_panicking_body() {
let unwound = std::panic::catch_unwind(|| {
serial().run(4, &|index| assert_ne!(index, 2, "body failed"));
});
assert!(unwound.is_err());
assert!(!in_host_task());
}
#[test]
fn a_handle_is_not_visible_from_another_thread() {
scope(serial(), || {
assert!(std::thread::spawn(|| current().is_none()).join().unwrap());
});
}
}