use crate::id::KernelId;
use cubecl_common::hash::StableHash;
use cubecl_environment::collections::{HashMap, HashSet};
use cubecl_environment::sync::{AtomicUsize, LazyLock, Mutex, Ordering};
static COLLECTING: AtomicUsize = AtomicUsize::new(0);
static OPEN: LazyLock<Mutex<Collections>> = LazyLock::new(|| Mutex::new(Collections::default()));
#[derive(Default)]
struct Collections {
next: u64,
open: HashMap<u64, HashSet<StableHash>>,
}
#[must_use = "the kernels are collected until `finish`"]
#[derive(Debug)]
pub struct LaunchedKernels {
id: u64,
}
impl LaunchedKernels {
#[allow(
clippy::new_without_default,
reason = "starting a collection is not a value"
)]
pub fn new() -> Self {
let mut collections = OPEN.lock();
let id = collections.next;
collections.next += 1;
collections.open.insert(id, HashSet::default());
COLLECTING.fetch_add(1, Ordering::AcqRel);
Self { id }
}
pub fn finish(self) -> HashSet<StableHash> {
let launched = close(self.id);
core::mem::forget(self);
launched
}
}
impl Drop for LaunchedKernels {
fn drop(&mut self) {
close(self.id);
}
}
fn close(id: u64) -> HashSet<StableHash> {
let mut collections = OPEN.lock();
let launched = collections.open.remove(&id).unwrap_or_default();
COLLECTING.fetch_sub(1, Ordering::AcqRel);
launched
}
pub(crate) fn note(kernel: impl FnOnce() -> KernelId) {
if COLLECTING.load(Ordering::Relaxed) == 0 {
return;
}
let hash = kernel().stable_hash();
let mut collections = OPEN.lock();
for launched in collections.open.values_mut() {
launched.insert(hash);
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Launched;
#[test]
fn a_collection_names_what_was_launched_while_it_was_open() {
let before = KernelId::new::<Launched>().info(0u32);
let during = KernelId::new::<Launched>().info(1u32);
note(|| before.clone());
let collection = LaunchedKernels::new();
note(|| during.clone());
note(|| during.clone());
let launched = collection.finish();
assert!(launched.contains(&during.stable_hash()));
assert!(!launched.contains(&before.stable_hash()));
}
#[test]
fn overlapping_collections_each_see_their_own_launches() {
let early = KernelId::new::<Launched>().info(10u32);
let late = KernelId::new::<Launched>().info(11u32);
let outer = LaunchedKernels::new();
note(|| early.clone());
let inner = LaunchedKernels::new();
note(|| late.clone());
let inner = inner.finish();
let outer = outer.finish();
assert!(inner.contains(&late.stable_hash()));
assert!(!inner.contains(&early.stable_hash()));
assert!(outer.contains(&early.stable_hash()) && outer.contains(&late.stable_hash()));
}
}