use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, MutexGuard, OnceLock};
use crate::device::Device;
use crate::error::CudaResult;
use crate::ffi::CUcontext;
use crate::loader::try_driver;
static CTX_GENERATION: AtomicU64 = AtomicU64::new(1);
fn live_ctxs() -> &'static Mutex<HashMap<usize, u64>> {
static LIVE: OnceLock<Mutex<HashMap<usize, u64>>> = OnceLock::new();
LIVE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub(crate) fn lock_live_ctxs() -> MutexGuard<'static, HashMap<usize, u64>> {
live_ctxs().lock().unwrap_or_else(|e| e.into_inner())
}
pub(crate) type CtxOwner = Option<(usize, u64)>;
pub(crate) fn current_ctx_owner() -> CtxOwner {
let driver = try_driver().ok()?;
let mut ctx = CUcontext::default();
let rc = unsafe { (driver.cu_ctx_get_current)(&mut ctx) };
if rc != 0 || ctx.is_null() {
return None;
}
let addr = ctx.0 as usize;
let map = lock_live_ctxs();
map.get(&addr).map(|&generation| (addr, generation))
}
pub(crate) fn owner_is_live(map: &HashMap<usize, u64>, owner: CtxOwner) -> bool {
match owner {
None => true,
Some((addr, generation)) => map.get(&addr) == Some(&generation),
}
}
pub mod flags {
pub const SCHED_AUTO: u32 = 0x00;
pub const SCHED_SPIN: u32 = 0x01;
pub const SCHED_YIELD: u32 = 0x02;
pub const SCHED_BLOCKING_SYNC: u32 = 0x04;
pub const MAP_HOST: u32 = 0x08;
pub const LMEM_RESIZE_TO_MAX: u32 = 0x10;
}
struct CurrentCtxRestore {
prev: CUcontext,
}
impl Drop for CurrentCtxRestore {
fn drop(&mut self) {
if let Ok(driver) = try_driver() {
if let Err(e) = crate::error::check(unsafe { (driver.cu_ctx_set_current)(self.prev) }) {
tracing::warn!("failed to restore previous context: {e}");
}
}
}
}
pub struct Context {
raw: CUcontext,
device: Device,
generation: u64,
owned: bool,
}
impl Context {
pub fn new(device: &Device) -> CudaResult<Self> {
Self::with_flags(device, flags::SCHED_AUTO)
}
pub fn with_flags(device: &Device, flags: u32) -> CudaResult<Self> {
let driver = try_driver()?;
let mut raw = CUcontext::default();
crate::error::check(unsafe { (driver.cu_ctx_create_v2)(&mut raw, flags, device.raw()) })?;
let generation = CTX_GENERATION.fetch_add(1, Ordering::Relaxed);
lock_live_ctxs().insert(raw.0 as usize, generation);
Ok(Self {
raw,
device: *device,
generation,
owned: true,
})
}
#[must_use]
pub unsafe fn from_raw_borrowed(raw: CUcontext, device: Device) -> Self {
Self {
raw,
device,
generation: 0,
owned: false,
}
}
pub fn set_current(&self) -> CudaResult<()> {
let driver = try_driver()?;
crate::error::check(unsafe { (driver.cu_ctx_set_current)(self.raw) })
}
pub fn current_raw() -> CudaResult<Option<CUcontext>> {
let driver = try_driver()?;
let mut ctx = CUcontext::default();
crate::error::check(unsafe { (driver.cu_ctx_get_current)(&mut ctx) })?;
if ctx.is_null() {
Ok(None)
} else {
Ok(Some(ctx))
}
}
pub(crate) fn pop_current() -> CudaResult<()> {
let driver = try_driver()?;
let mut popped = CUcontext::default();
crate::error::check(unsafe { (driver.cu_ctx_pop_current_v2)(&mut popped) })
}
pub fn synchronize(&self) -> CudaResult<()> {
self.set_current()?;
let driver = try_driver()?;
crate::error::check(unsafe { (driver.cu_ctx_synchronize)() })
}
pub fn scoped<F, R>(&self, f: F) -> CudaResult<R>
where
F: FnOnce() -> CudaResult<R>,
{
let prev = Self::current_raw()?;
self.set_current()?;
let _restore = CurrentCtxRestore {
prev: prev.unwrap_or_default(),
};
f()
}
#[inline]
pub fn device(&self) -> &Device {
&self.device
}
#[inline]
pub fn raw(&self) -> CUcontext {
self.raw
}
pub fn is_current(&self) -> CudaResult<bool> {
match Self::current_raw()? {
Some(ctx) => Ok(ctx == self.raw),
None => Ok(false),
}
}
}
impl Drop for Context {
fn drop(&mut self) {
if !self.owned {
return;
}
let mut map = lock_live_ctxs();
let addr = self.raw.0 as usize;
if map.get(&addr) == Some(&self.generation) {
map.remove(&addr);
}
if let Ok(driver) = try_driver() {
let result = unsafe { (driver.cu_ctx_destroy_v2)(self.raw) };
if result != 0 {
tracing::warn!(
"cuCtxDestroy_v2 failed with error code {result} during Context drop \
(device ordinal {})",
self.device.ordinal()
);
}
}
}
}
impl std::fmt::Debug for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Context")
.field("raw", &self.raw)
.field("device", &self.device)
.finish()
}
}