use crate::device_context::with_default_device_policy;
use crate::device_future::DeviceFuture;
use crate::device_operation::{DeviceOp, ExecutionContext};
use crate::error::DeviceError;
use anyhow::{Context, Result};
use cuda_core::sys::CUdeviceptr;
use cuda_core::{launch_kernel, DType, Function, LaunchConfig, Stream};
use std::ffi::c_void;
use std::fmt::Debug;
use std::future::IntoFuture;
use std::sync::Arc;
use std::vec::Vec;
#[derive(Debug)]
pub struct AsyncKernelLaunch {
pub func: Arc<Function>,
args: KernelArgStorage,
cfg: Option<LaunchConfig>,
}
unsafe impl Send for AsyncKernelLaunch {}
#[derive(Default)]
struct KernelArgStorage {
values: Vec<u128>,
offsets: Vec<usize>,
ptrs: Vec<*mut c_void>,
}
impl Debug for KernelArgStorage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KernelArgStorage")
.field("len", &self.offsets.len())
.field("offsets", &self.offsets)
.finish()
}
}
impl KernelArgStorage {
fn push<T: Copy + Send>(&mut self, arg: T) {
const SLOT: usize = std::mem::size_of::<u128>();
const {
assert!(std::mem::align_of::<T>() <= SLOT);
}
let slots = std::mem::size_of::<T>().div_ceil(SLOT).max(1);
let offset = self.values.len();
self.values.resize(offset + slots, 0);
unsafe { std::ptr::write(self.values.as_mut_ptr().add(offset) as *mut T, arg) };
self.offsets.push(offset);
}
fn as_mut_slice(&mut self) -> &mut [*mut c_void] {
let base = self.values.as_mut_ptr();
self.ptrs.clear();
self.ptrs.extend(
self.offsets
.iter()
.map(|&offset| unsafe { base.add(offset) } as *mut c_void),
);
&mut self.ptrs
}
}
impl AsyncKernelLaunch {
pub fn new(func: Arc<Function>) -> AsyncKernelLaunch {
AsyncKernelLaunch {
func,
args: KernelArgStorage::default(),
cfg: None,
}
}
#[inline(always)]
pub fn push_arg<T: KernelArgument>(&mut self, arg: T) -> &mut Self {
arg.push_arg(self);
self
}
#[inline(always)]
pub fn push_arg_arc<T: ArcKernelArgument>(&mut self, arg: &Arc<T>) -> &mut Self {
arg.push_arg_arc(self);
self
}
pub unsafe fn push_device_ptr(&mut self, ptr: CUdeviceptr) -> &mut Self {
self.push_arg_raw(ptr)
}
unsafe fn push_arg_raw<T: Copy + Send>(&mut self, arg: T) -> &mut Self {
self.args.push(arg);
self
}
pub fn set_launch_config(&mut self, cfg: LaunchConfig) -> &mut Self {
self.cfg = Some(cfg);
self
}
unsafe fn launch(mut self, stream: &Arc<Stream>) -> Result<(), DeviceError> {
let cfg = self.cfg.ok_or_else(|| {
DeviceError::Launch("Await called before launching the kernel.".to_string())
})?;
launch_kernel(
self.func.cu_function(),
cfg.grid_dim,
cfg.block_dim,
cfg.shared_mem_bytes,
stream.cu_stream(),
self.args.as_mut_slice(),
)
.with_context(|| {
format!(
r#"
Failed to launch kernel.
args: {:#?}
cfg: {:#?}"#,
self.args, cfg
)
})?;
Ok(())
}
}
pub trait ArcKernelArgument {
fn push_arg_arc(self: &Arc<Self>, launcher: &mut AsyncKernelLaunch);
}
pub trait KernelArgument {
fn push_arg(self, launcher: &mut AsyncKernelLaunch);
}
impl<T: DType> KernelArgument for T {
fn push_arg(self, launcher: &mut AsyncKernelLaunch) {
unsafe {
launcher.push_arg_raw(self);
}
}
}
impl DeviceOp for AsyncKernelLaunch {
type Output = ();
unsafe fn execute(
self,
ctx: &ExecutionContext,
) -> Result<<Self as DeviceOp>::Output, DeviceError> {
self.launch(ctx.get_cuda_stream())
}
}
impl IntoFuture for AsyncKernelLaunch {
type Output = Result<(), DeviceError>;
type IntoFuture = DeviceFuture<(), AsyncKernelLaunch>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
let mut f = DeviceFuture::new();
f.device_operation = Some(self);
f.execution_context = Some(ExecutionContext::new(stream));
Ok(f)
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
#[cfg(test)]
mod arg_storage_tests {
use super::*;
#[test]
fn values_roundtrip_and_survive_arena_growth() {
let mut storage = KernelArgStorage::default();
storage.push(7u8);
storage.push(0x1122_3344_5566_7788u64);
storage.push(-5i32);
for i in 0..64u64 {
storage.push(i);
}
let ptrs = storage.as_mut_slice();
assert_eq!(ptrs.len(), 3 + 64);
assert_eq!(unsafe { *(ptrs[0] as *const u8) }, 7);
assert_eq!(unsafe { *(ptrs[1] as *const u64) }, 0x1122_3344_5566_7788);
assert_eq!(unsafe { *(ptrs[2] as *const i32) }, -5);
for i in 0..64usize {
assert_eq!(unsafe { *(ptrs[3 + i] as *const u64) }, i as u64);
}
}
#[test]
fn slots_are_sixteen_byte_aligned() {
let mut storage = KernelArgStorage::default();
storage.push(1u8);
storage.push(2u128);
let ptrs = storage.as_mut_slice();
assert!(ptrs.iter().all(|&p| (p as usize).is_multiple_of(16)));
assert_eq!(unsafe { *(ptrs[1] as *const u128) }, 2);
}
}