use core::ffi::c_void;
use core::ptr::NonNull;
use core::time::Duration;
use libloading::Library;
use super::mapped::{Line, LineDriver, LineError};
use crate::core::line::{CompletionLine, Slot};
use crate::core::time::DurNs;
use crate::core::wait::CompletionPort;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CuStream(u64);
impl CuStream {
#[must_use]
pub const unsafe fn from_raw(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn raw(self) -> u64 {
self.0
}
}
type AllocHost = unsafe extern "C" fn(*mut *mut c_void, usize) -> i32;
type HostDevPtr = unsafe extern "C" fn(*mut u64, *mut c_void, u32) -> i32;
type FreeHost = unsafe extern "C" fn(*mut c_void) -> i32;
type CtxGet = unsafe extern "C" fn(*mut u64) -> i32;
type CtxGetDevice = unsafe extern "C" fn(*mut i32) -> i32;
type PrimaryRetain = unsafe extern "C" fn(*mut u64, i32) -> i32;
type PrimaryRelease = unsafe extern "C" fn(i32) -> i32;
type WriteValue64 = unsafe extern "C" fn(u64, u64, u64, u32) -> i32;
type CtxPush = unsafe extern "C" fn(u64) -> i32;
type CtxPop = unsafe extern "C" fn(*mut u64) -> i32;
type CtxSync = unsafe extern "C" fn() -> i32;
type StreamCtx = unsafe extern "C" fn(u64, *mut u64) -> i32;
type StreamQuery = unsafe extern "C" fn(u64) -> i32;
type StreamSync = unsafe extern "C" fn(u64) -> i32;
const NOT_READY: i32 = 600;
pub(crate) struct CudaDriver {
alloc: AllocHost,
dev_ptr: HostDevPtr,
free: FreeHost,
ctx_get: CtxGet,
ctx_device: CtxGetDevice,
retain: PrimaryRetain,
release: PrimaryRelease,
write: WriteValue64,
push: CtxPush,
pop: CtxPop,
sync: CtxSync,
stream_ctx: StreamCtx,
query: StreamQuery,
stream_sync: StreamSync,
_lib: Library,
}
macro_rules! sym {
($lib:ident, $ty:ty, $name:literal) => {
*unsafe { $lib.get::<$ty>($name) }.ok()?
};
}
impl CudaDriver {
pub(crate) fn resolve() -> Option<Self> {
let lib = unsafe { Library::new("libcuda.so.1") }.ok()?;
Some(Self {
alloc: sym!(lib, AllocHost, b"cuMemAllocHost_v2"),
dev_ptr: sym!(lib, HostDevPtr, b"cuMemHostGetDevicePointer_v2"),
free: sym!(lib, FreeHost, b"cuMemFreeHost"),
ctx_get: sym!(lib, CtxGet, b"cuCtxGetCurrent"),
ctx_device: sym!(lib, CtxGetDevice, b"cuCtxGetDevice"),
retain: sym!(lib, PrimaryRetain, b"cuDevicePrimaryCtxRetain"),
release: sym!(lib, PrimaryRelease, b"cuDevicePrimaryCtxRelease_v2"),
write: sym!(lib, WriteValue64, b"cuStreamWriteValue64_v2"),
push: sym!(lib, CtxPush, b"cuCtxPushCurrent_v2"),
pop: sym!(lib, CtxPop, b"cuCtxPopCurrent_v2"),
sync: sym!(lib, CtxSync, b"cuCtxSynchronize"),
stream_ctx: sym!(lib, StreamCtx, b"cuStreamGetCtx"),
query: sym!(lib, StreamQuery, b"cuStreamQuery"),
stream_sync: sym!(lib, StreamSync, b"cuStreamSynchronize"),
_lib: lib,
})
}
}
fn ok(status: i32) -> Result<(), LineError> {
if status == 0 { Ok(()) } else { Err(LineError::Status(status)) }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct PrimaryCtx {
ctx: u64,
device: i32,
}
impl CudaDriver {
fn in_ctx<T>(&self, ctx: u64, f: impl FnOnce() -> T) -> Result<T, LineError> {
ok(unsafe { (self.push)(ctx) })?;
let r = f();
let mut popped = 0u64;
ok(unsafe { (self.pop)(&raw mut popped) })?;
Ok(r)
}
}
impl LineDriver for CudaDriver {
type Stream = CuStream;
type Domain = PrimaryCtx;
fn retain_current(&self) -> Result<PrimaryCtx, LineError> {
let mut current = 0u64;
ok(unsafe { (self.ctx_get)(&raw mut current) })?;
if current == 0 {
return Err(LineError::NoDomain);
}
let mut device = 0i32;
ok(unsafe { (self.ctx_device)(&raw mut device) })?;
let mut ctx = 0u64;
ok(unsafe { (self.retain)(&raw mut ctx, device) })?;
let primary = PrimaryCtx { ctx, device };
if ctx == 0 {
self.release(primary);
return Err(LineError::NoDomain);
}
Ok(primary)
}
fn release(&self, primary: PrimaryCtx) {
let _ = unsafe { (self.release)(primary.device) };
}
fn alloc_host(&self, primary: PrimaryCtx, bytes: usize) -> Result<NonNull<c_void>, LineError> {
let mut page: *mut c_void = core::ptr::null_mut();
ok(self.in_ctx(primary.ctx, || unsafe { (self.alloc)(&raw mut page, bytes) })?)?;
NonNull::new(page).ok_or(LineError::Unavailable)
}
fn device_address(&self, primary: PrimaryCtx, host: NonNull<c_void>) -> Result<u64, LineError> {
let mut device = 0u64;
ok(self.in_ctx(primary.ctx, || unsafe {
(self.dev_ptr)(&raw mut device, host.as_ptr(), 0)
})?)?;
Ok(device)
}
fn free_host(&self, primary: PrimaryCtx, host: NonNull<c_void>) {
let _ = self.in_ctx(primary.ctx, || unsafe { (self.free)(host.as_ptr()) });
}
fn stream_in(&self, stream: CuStream, primary: PrimaryCtx) -> Result<bool, LineError> {
let mut owner = 0u64;
ok(unsafe { (self.stream_ctx)(stream.0, &raw mut owner) })?;
Ok(owner == primary.ctx)
}
fn write_value(&self, stream: CuStream, addr: u64, value: u64) -> Result<(), LineError> {
ok(unsafe { (self.write)(stream.0, addr, value, 0) })
}
fn settle(&self, primary: PrimaryCtx) -> bool {
matches!(self.in_ctx(primary.ctx, || unsafe { (self.sync)() }), Ok(0))
}
}
pub struct MappedLine(Line<CudaDriver>);
impl MappedLine {
pub fn new() -> Result<Self, LineError> {
Line::with_driver(CudaDriver::resolve().ok_or(LineError::Unavailable)?).map(Self)
}
#[must_use]
pub fn line(&self) -> &CompletionLine {
self.0.line()
}
#[must_use]
pub fn device_address(&self) -> u64 {
self.0.device_address()
}
pub fn enqueue_store(&self, stream: CuStream, slot: Slot, value: u64) -> Result<(), LineError> {
self.0.enqueue_store(stream, slot, value)
}
fn driver(&self) -> &CudaDriver {
self.0.driver()
}
}
pub struct CudaPort<'a> {
line: &'a MappedLine,
stream: CuStream,
slot: Slot,
seq: &'a mut u64,
wait: Option<super::wordwait::WordWait>,
}
impl<'a> CudaPort<'a> {
pub fn new(
line: &'a MappedLine,
stream: CuStream,
slot: Slot,
seq: &'a mut u64,
wait: Option<super::wordwait::WordWait>,
) -> Self {
Self {
line,
stream,
slot,
seq,
wait,
}
}
}
impl CompletionPort for CudaPort<'_> {
type Error = crate::Error;
fn query(&mut self) -> crate::Result<bool> {
match unsafe { (self.line.driver().query)(self.stream.0) } {
0 => Ok(true),
NOT_READY => Ok(false),
s => Err(crate::error!(Backend, "cuStreamQuery", s)),
}
}
async fn block(&mut self) -> crate::Result<()> {
match unsafe { (self.line.driver().stream_sync)(self.stream.0) } {
0 => Ok(()),
s => Err(crate::error!(Backend, "cuStreamSynchronize", s)),
}
}
async fn poll(&mut self) -> crate::Result<()> {
while !self.query()? {
core::hint::spin_loop();
}
Ok(())
}
fn arm(&mut self) -> crate::Result<()> {
*self.seq = self.seq.wrapping_add(1);
Ok(self.line.enqueue_store(self.stream, self.slot, *self.seq)?)
}
async fn park(&mut self, bound: DurNs) -> crate::Result<bool> {
let (slot, seq) = (self.slot, *self.seq);
let ready = |l: &CompletionLine| crate::core::line::reached(l, slot, seq);
Ok(match &self.wait {
Some(w) => w.park(self.line.line(), ready, Duration::from_nanos(bound.0)),
None => ready(self.line.line()),
})
}
}