use crate::simt::device_context::with_default_device_policy;
use crate::simt::device_future::DeviceFuture;
use crate::simt::device_operation::{DeviceOperation, ExecutionContext};
use crate::simt::error::DeviceError;
use crate::simt::scheduling_policies::SchedulingPolicy;
use cuda_core::simt::LaunchConfig;
use cuda_core::{CudaFunction, CudaStream};
use std::ffi::c_void;
use std::future::IntoFuture;
use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Debug)]
pub struct AsyncKernelLaunchBuilder<'a> {
func: Arc<CudaFunction>,
args: KernelArgStorage,
cluster_dim: Option<(u32, u32, u32)>,
cooperative: bool,
_borrows: PhantomData<&'a mut ()>,
}
#[derive(Debug)]
pub struct AsyncKernelLaunch<'a> {
func: Arc<CudaFunction>,
args: KernelArgStorage,
cfg: LaunchConfig,
cluster_dim: Option<(u32, u32, u32)>,
cooperative: bool,
_borrows: PhantomData<&'a mut ()>,
}
unsafe impl<'a> Send for AsyncKernelLaunch<'a> {}
#[derive(Debug, Default)]
struct KernelArgStorage {
ptrs: Vec<*mut c_void>,
drops: Vec<unsafe fn(*mut c_void)>,
}
impl Drop for KernelArgStorage {
fn drop(&mut self) {
for (arg, drop_arg) in self.ptrs.drain(..).zip(self.drops.drain(..)) {
unsafe { drop_arg(arg) };
}
}
}
impl KernelArgStorage {
fn push_send_arg<T: Send>(&mut self, arg: Box<T>) {
unsafe fn drop_box<T>(arg: *mut c_void) {
let _ = unsafe { Box::from_raw(arg as *mut T) };
}
self.ptrs.push(Box::into_raw(arg) as *mut c_void);
self.drops.push(drop_box::<T>);
}
fn push_scalar_arg<T: Copy>(&mut self, arg: T) {
unsafe fn drop_copy_box<T: Copy>(arg: *mut c_void) {
let _ = unsafe { Box::from_raw(arg as *mut T) };
}
self.ptrs.push(Box::into_raw(Box::new(arg)) as *mut c_void);
self.drops.push(drop_copy_box::<T>);
}
fn as_mut_slice(&mut self) -> &mut [*mut c_void] {
&mut self.ptrs
}
}
impl<'a> AsyncKernelLaunchBuilder<'a> {
pub fn new(func: Arc<CudaFunction>) -> Self {
Self {
func,
args: KernelArgStorage::default(),
cluster_dim: None,
cooperative: false,
_borrows: PhantomData,
}
}
#[inline(always)]
pub fn push_scalar_arg<T: Copy + 'a>(&mut self, arg: T) -> &mut Self {
self.args.push_scalar_arg(arg);
self
}
#[inline(always)]
pub fn push_arg<T: KernelArgument>(&mut self, arg: T) -> &mut Self {
arg.push_arg(self);
self
}
#[inline(always)]
pub fn push_args<T: KernelArguments>(&mut self, args: T) -> &mut Self {
args.push_args(self);
self
}
pub fn set_cluster_dim(&mut self, cluster_dim: (u32, u32, u32)) -> &mut Self {
self.cluster_dim = Some(cluster_dim);
self
}
pub fn set_cooperative(&mut self, cooperative: bool) -> &mut Self {
self.cooperative = cooperative;
self
}
pub unsafe fn finalize_unchecked(self, cfg: LaunchConfig) -> AsyncKernelLaunch<'a> {
AsyncKernelLaunch {
func: self.func,
args: self.args,
cfg,
cluster_dim: self.cluster_dim,
cooperative: self.cooperative,
_borrows: self._borrows,
}
}
}
impl<'a> AsyncKernelLaunch<'a> {
unsafe fn launch(mut self, stream: &Arc<CudaStream>) -> Result<(), DeviceError> {
let cfg = self.cfg;
let result = match (self.cluster_dim, self.cooperative) {
(Some(cluster_dim), true) => unsafe {
cuda_core::launch_kernel_ex_cooperative_on_stream(
self.func.as_ref(),
cfg.grid_dim,
cfg.block_dim,
cfg.shared_mem_bytes,
cluster_dim,
stream.as_ref(),
self.args.as_mut_slice(),
)
},
(Some(cluster_dim), false) => unsafe {
cuda_core::launch_kernel_ex_on_stream(
self.func.as_ref(),
cfg.grid_dim,
cfg.block_dim,
cfg.shared_mem_bytes,
cluster_dim,
stream.as_ref(),
self.args.as_mut_slice(),
)
},
(None, true) => unsafe {
cuda_core::launch_kernel_cooperative_on_stream(
self.func.as_ref(),
cfg.grid_dim,
cfg.block_dim,
cfg.shared_mem_bytes,
stream.as_ref(),
self.args.as_mut_slice(),
)
},
(None, false) => unsafe {
cuda_core::launch_kernel_on_stream(
self.func.as_ref(),
cfg.grid_dim,
cfg.block_dim,
cfg.shared_mem_bytes,
stream.as_ref(),
self.args.as_mut_slice(),
)
},
};
result.map_err(DeviceError::Driver)?;
Ok(())
}
}
#[derive(Debug)]
pub struct OwnedAsyncKernelLaunch<R: Send> {
launch: AsyncKernelLaunch<'static>,
resources: R,
}
unsafe impl<R: Send> Send for OwnedAsyncKernelLaunch<R> {}
impl<R: Send> OwnedAsyncKernelLaunch<R> {
pub fn new(launch: AsyncKernelLaunch<'static>, resources: R) -> Self {
Self { launch, resources }
}
}
pub trait KernelArgument: Send {
fn push_arg(self, launcher: &mut AsyncKernelLaunchBuilder<'_>);
}
impl<T: Send + 'static> KernelArgument for Box<T> {
fn push_arg(self, launcher: &mut AsyncKernelLaunchBuilder<'_>) {
launcher.args.push_send_arg(self);
}
}
macro_rules! impl_scalar_kernel_arg {
($($t:ty),*) => {
$(
impl KernelArgument for $t {
#[inline(always)]
fn push_arg(self, launcher: &mut AsyncKernelLaunchBuilder<'_>) {
launcher.push_scalar_arg(self);
}
}
)*
};
}
impl_scalar_kernel_arg!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64, bool);
#[diagnostic::on_unimplemented(
message = "cannot push `{Self}` as kernel arguments",
note = "KernelArguments is implemented for tuples of KernelArgument types up to 32 elements"
)]
pub trait KernelArguments {
fn push_args(self, launcher: &mut AsyncKernelLaunchBuilder<'_>);
}
macro_rules! impl_kernel_args_tuple {
() => {
impl KernelArguments for () {
#[inline(always)]
fn push_args(self, _launcher: &mut AsyncKernelLaunchBuilder<'_>) {}
}
};
($($idx:tt : $T:ident),+) => {
impl<$($T: KernelArgument),+> KernelArguments for ($($T,)+) {
#[inline(always)]
fn push_args(self, launcher: &mut AsyncKernelLaunchBuilder<'_>) {
$(launcher.push_arg(self.$idx);)+
}
}
};
}
impl_kernel_args_tuple!();
impl_kernel_args_tuple!(0: A);
impl_kernel_args_tuple!(0: A, 1: B);
impl_kernel_args_tuple!(0: A, 1: B, 2: C);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z, 26: AA);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z, 26: AA, 27: AB);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z, 26: AA, 27: AB, 28: AC);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z, 26: AA, 27: AB, 28: AC, 29: AD);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z, 26: AA, 27: AB, 28: AC, 29: AD, 30: AE);
impl_kernel_args_tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T, 20: U, 21: V, 22: W, 23: X, 24: Y, 25: Z, 26: AA, 27: AB, 28: AC, 29: AD, 30: AE, 31: AF);
impl<'a> DeviceOperation for AsyncKernelLaunch<'a> {
type Output = ();
unsafe fn execute(self, ctx: &ExecutionContext) -> Result<(), DeviceError> {
unsafe { self.launch(ctx.get_cuda_stream()) }
}
}
impl<'a> IntoFuture for AsyncKernelLaunch<'a> {
type Output = Result<(), DeviceError>;
type IntoFuture = DeviceFuture<(), AsyncKernelLaunch<'a>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| policy.schedule(self)) {
Ok(Ok(future)) => future,
Ok(Err(e)) | Err(e) => DeviceFuture::failed(e),
}
}
}
impl<R: Send + 'static> DeviceOperation for OwnedAsyncKernelLaunch<R> {
type Output = R;
unsafe fn execute(self, ctx: &ExecutionContext) -> Result<R, DeviceError> {
let Self { launch, resources } = self;
unsafe { launch.launch(ctx.get_cuda_stream()) }?;
Ok(resources)
}
}
impl<R: Send + 'static> IntoFuture for OwnedAsyncKernelLaunch<R> {
type Output = Result<R, DeviceError>;
type IntoFuture = DeviceFuture<R, OwnedAsyncKernelLaunch<R>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| policy.schedule(self)) {
Ok(Ok(future)) => future,
Ok(Err(e)) | Err(e) => DeviceFuture::failed(e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
struct TestParams {
scale: f32,
bias: i32,
}
#[test]
fn scalar_arg_storage_accepts_custom_copy_value() {
let mut storage = KernelArgStorage::default();
let params = TestParams {
scale: 2.0,
bias: 3,
};
storage.push_scalar_arg(params);
assert_eq!(storage.ptrs.len(), 1);
assert_eq!(unsafe { *(storage.ptrs[0] as *const TestParams) }, params);
}
#[test]
fn arg_storage_drops_values_with_their_original_type() {
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
let drops = Arc::new(AtomicUsize::new(0));
let mut storage = KernelArgStorage::default();
storage.push_send_arg(Box::new(DropCounter(Arc::clone(&drops))));
assert_eq!(drops.load(Ordering::Relaxed), 0);
drop(storage);
assert_eq!(drops.load(Ordering::Relaxed), 1);
}
}