use crate::simt::device_context::with_default_device_policy;
use crate::simt::device_future::DeviceFuture;
use crate::simt::error::DeviceError;
use crate::simt::scheduling_policies::SchedulingPolicy;
use cuda_core::{CudaContext, CudaStream};
use std::cell::UnsafeCell;
use std::future::IntoFuture;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
pub type Device = usize;
#[derive(Debug, Clone)]
pub struct ExecutionContext {
device: Device,
cuda_stream: Arc<CudaStream>,
cuda_context: Arc<CudaContext>,
}
impl ExecutionContext {
pub fn new(cuda_stream: Arc<CudaStream>) -> Self {
let cuda_context = Arc::clone(cuda_stream.context());
let device = cuda_context.ordinal();
Self {
cuda_stream,
cuda_context,
device,
}
}
pub fn get_cuda_stream(&self) -> &Arc<CudaStream> {
&self.cuda_stream
}
pub fn get_cuda_context(&self) -> &Arc<CudaContext> {
&self.cuda_context
}
pub fn get_device_id(&self) -> Device {
self.device
}
}
pub trait DeviceOperation:
Send + Sized + IntoFuture<Output = Result<<Self as DeviceOperation>::Output, DeviceError>>
{
type Output: Send + 'static;
unsafe fn execute(
self,
context: &ExecutionContext,
) -> Result<<Self as DeviceOperation>::Output, DeviceError>;
fn schedule<P: SchedulingPolicy>(
self,
policy: &P,
) -> Result<DeviceFuture<<Self as DeviceOperation>::Output, Self>, DeviceError> {
policy.schedule(self)
}
fn and_then<O: Send, DO, F>(
self,
f: F,
) -> AndThen<<Self as DeviceOperation>::Output, Self, O, DO, F>
where
DO: DeviceOperation<Output = O>,
F: FnOnce(<Self as DeviceOperation>::Output) -> DO,
{
AndThen {
op: self,
closure: f,
}
}
fn and_then_with_context<O: Send, DO, F>(
self,
f: F,
) -> AndThenWithContext<<Self as DeviceOperation>::Output, Self, O, DO, F>
where
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext, <Self as DeviceOperation>::Output) -> DO,
{
AndThenWithContext {
op: self,
closure: f,
}
}
fn arc(self) -> DeviceOperationArc<<Self as DeviceOperation>::Output, Self>
where
<Self as DeviceOperation>::Output: Sync,
{
DeviceOperationArc { op: self }
}
fn apply<O: Send, DO, F>(
self,
f: F,
) -> AndThen<<Self as DeviceOperation>::Output, Self, O, DO, F>
where
DO: DeviceOperation<Output = O>,
F: FnOnce(<Self as DeviceOperation>::Output) -> DO,
{
self.and_then(f)
}
fn sync(self) -> Result<<Self as DeviceOperation>::Output, DeviceError> {
with_default_device_policy(|policy| policy.sync(self))?
}
unsafe fn async_on(
self,
stream: &Arc<CudaStream>,
) -> Result<<Self as DeviceOperation>::Output, DeviceError> {
let ctx = ExecutionContext::new(Arc::clone(stream));
unsafe { self.execute(&ctx) }
}
fn sync_on(
self,
stream: &Arc<CudaStream>,
) -> Result<<Self as DeviceOperation>::Output, DeviceError> {
let ctx = ExecutionContext::new(Arc::clone(stream));
let res = unsafe { self.execute(&ctx) };
finish_sync(res, stream.synchronize())
}
}
fn finish_sync<T>(
operation_result: Result<T, DeviceError>,
synchronize_result: Result<(), cuda_core::DriverError>,
) -> Result<T, DeviceError> {
let output = operation_result?;
synchronize_result.map_err(DeviceError::Driver)?;
Ok(output)
}
pub struct DeviceOperationArc<I: Send + Sync, DI: DeviceOperation<Output = I>> {
op: DI,
}
unsafe impl<I: Send + Sync, DI: DeviceOperation<Output = I>> Send for DeviceOperationArc<I, DI> {}
impl<I: Send + Sync + 'static, DI: DeviceOperation<Output = I>> DeviceOperation
for DeviceOperationArc<I, DI>
{
type Output = Arc<I>;
unsafe fn execute(self, context: &ExecutionContext) -> Result<Arc<I>, DeviceError> {
unsafe {
let val = self.op.execute(context)?;
Ok(Arc::new(val))
}
}
}
impl<I: Send + Sync + 'static, DI: DeviceOperation<Output = I>> IntoFuture
for DeviceOperationArc<I, DI>
{
type Output = Result<Arc<I>, DeviceError>;
type IntoFuture = DeviceFuture<Arc<I>, DeviceOperationArc<I, DI>>;
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),
}
}
}
pub struct AndThen<I: Send, DI, O: Send, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(I) -> DO,
{
op: DI,
closure: F,
}
unsafe impl<I: Send, DI, O: Send, DO, F> Send for AndThen<I, DI, O, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(I) -> DO + Send,
{
}
impl<I: Send, DI, O: Send + 'static, DO, F> DeviceOperation for AndThen<I, DI, O, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(I) -> DO + Send,
{
type Output = O;
unsafe fn execute(self, context: &ExecutionContext) -> Result<O, DeviceError> {
unsafe {
let input = self.op.execute(context)?;
let output_op = (self.closure)(input);
output_op.execute(context)
}
}
}
impl<I: Send, DI, O: Send + 'static, DO, F> IntoFuture for AndThen<I, DI, O, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(I) -> DO + Send,
{
type Output = Result<O, DeviceError>;
type IntoFuture = DeviceFuture<O, AndThen<I, DI, O, DO, F>>;
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),
}
}
}
pub struct AndThenWithContext<I: Send, DI, O: Send, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext, I) -> DO,
{
op: DI,
closure: F,
}
unsafe impl<I: Send, DI, O: Send, DO, F> Send for AndThenWithContext<I, DI, O, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext, I) -> DO + Send,
{
}
impl<I: Send, DI, O: Send + 'static, DO, F> DeviceOperation for AndThenWithContext<I, DI, O, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext, I) -> DO + Send,
{
type Output = O;
unsafe fn execute(self, context: &ExecutionContext) -> Result<O, DeviceError> {
unsafe {
let input = self.op.execute(context)?;
let output_op = (self.closure)(context, input);
output_op.execute(context)
}
}
}
impl<I: Send, DI, O: Send + 'static, DO, F> IntoFuture for AndThenWithContext<I, DI, O, DO, F>
where
DI: DeviceOperation<Output = I>,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext, I) -> DO + Send,
{
type Output = Result<O, DeviceError>;
type IntoFuture = DeviceFuture<O, AndThenWithContext<I, DI, O, DO, F>>;
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),
}
}
}
pub struct Value<T>(T);
unsafe impl<T> Send for Value<T> {}
impl<T: Send + 'static> DeviceOperation for Value<T> {
type Output = T;
unsafe fn execute(self, _context: &ExecutionContext) -> Result<T, DeviceError> {
Ok(self.0)
}
}
impl<T: Send + 'static> IntoFuture for Value<T> {
type Output = Result<T, DeviceError>;
type IntoFuture = DeviceFuture<T, Value<T>>;
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),
}
}
}
pub fn value<T: Send>(x: T) -> Value<T> {
Value(x)
}
pub trait IntoDeviceOperation<T: Send> {
fn device_operation(self) -> Value<T>;
}
impl<T: Send> IntoDeviceOperation<T> for T {
fn device_operation(self) -> Value<T> {
value(self)
}
}
pub struct Empty<O: Send, DO: DeviceOperation<Output = O>, F: FnOnce() -> DO> {
closure: F,
}
pub fn empty<O: Send, DO: DeviceOperation<Output = O>, F: FnOnce() -> DO>(
closure: F,
) -> Empty<O, DO, F> {
Empty { closure }
}
unsafe impl<O: Send, DO: DeviceOperation<Output = O>, F: FnOnce() -> DO> Send for Empty<O, DO, F> {}
impl<O: Send + 'static, DO: DeviceOperation<Output = O>, F: FnOnce() -> DO> DeviceOperation
for Empty<O, DO, F>
{
type Output = O;
unsafe fn execute(self, context: &ExecutionContext) -> Result<O, DeviceError> {
unsafe {
let op = (self.closure)();
op.execute(context)
}
}
}
impl<O: Send + 'static, DO: DeviceOperation<Output = O>, F: FnOnce() -> DO> IntoFuture
for Empty<O, DO, F>
{
type Output = Result<O, DeviceError>;
type IntoFuture = DeviceFuture<O, Empty<O, DO, F>>;
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),
}
}
}
pub struct Zip<T1: Send, T2: Send, A: DeviceOperation<Output = T1>, B: DeviceOperation<Output = T2>>
{
phantom: PhantomData<(T1, T2)>,
a: A,
b: B,
}
unsafe impl<T1: Send, T2: Send, A: DeviceOperation<Output = T1>, B: DeviceOperation<Output = T2>>
Send for Zip<T1, T2, A, B>
{
}
fn _zip<T1: Send, T2: Send, A: DeviceOperation<Output = T1>, B: DeviceOperation<Output = T2>>(
a: A,
b: B,
) -> Zip<T1, T2, A, B> {
Zip {
phantom: PhantomData,
a,
b,
}
}
impl<
T1: Send + 'static,
T2: Send + 'static,
A: DeviceOperation<Output = T1>,
B: DeviceOperation<Output = T2>,
> DeviceOperation for Zip<T1, T2, A, B>
{
type Output = (T1, T2);
unsafe fn execute(self, context: &ExecutionContext) -> Result<(T1, T2), DeviceError> {
unsafe {
let a = self.a.execute(context)?;
let b = self.b.execute(context)?;
Ok((a, b))
}
}
}
impl<
T1: Send + 'static,
T2: Send + 'static,
A: DeviceOperation<Output = T1>,
B: DeviceOperation<Output = T2>,
> IntoFuture for Zip<T1, T2, A, B>
{
type Output = Result<(T1, T2), DeviceError>;
type IntoFuture = DeviceFuture<(T1, T2), Zip<T1, T2, A, B>>;
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),
}
}
}
pub trait Zippable<I, O: Send> {
fn zip(self) -> impl DeviceOperation<Output = O>;
}
impl<
T0: Send + 'static,
T1: Send + 'static,
DI0: DeviceOperation<Output = T0>,
DI1: DeviceOperation<Output = T1>,
> Zippable<(DI0, DI1), (T0, T1)> for (DI0, DI1)
{
fn zip(self) -> impl DeviceOperation<Output = (T0, T1)> {
_zip(self.0, self.1)
}
}
impl<
T0: Send + 'static,
T1: Send + 'static,
T2: Send + 'static,
DI0: DeviceOperation<Output = T0>,
DI1: DeviceOperation<Output = T1>,
DI2: DeviceOperation<Output = T2>,
> Zippable<(DI0, DI1, DI2), (T0, T1, T2)> for (DI0, DI1, DI2)
{
fn zip(self) -> impl DeviceOperation<Output = (T0, T1, T2)> {
let cons = _zip(self.1, self.2);
let cons = _zip(self.0, cons);
cons.and_then(|(arg0, (arg1, arg2))| value((arg0, arg1, arg2)))
}
}
#[allow(unused_macros)] macro_rules! zip {
($arg0:expr) => {
$arg0
};
($arg0:expr, $arg1:expr) => {
($arg0, $arg1).zip()
};
($arg0:expr, $arg1:expr, $arg2:expr) => {
($arg0, $arg1, $arg2).zip()
};
}
#[allow(unused_imports)] pub(crate) use zip;
pub struct StreamOperation<
O: Send,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext) -> DO + Send,
> {
f: F,
}
impl<
O: Send + 'static,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext) -> DO + Send,
> DeviceOperation for StreamOperation<O, DO, F>
{
type Output = O;
unsafe fn execute(self, context: &ExecutionContext) -> Result<O, DeviceError> {
unsafe {
let op = (self.f)(context);
op.execute(context)
}
}
}
pub fn with_context<
O: Send + 'static,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext) -> DO + Send,
>(
f: F,
) -> impl DeviceOperation<Output = O> {
StreamOperation { f }
}
impl<
O: Send + 'static,
DO: DeviceOperation<Output = O>,
F: FnOnce(&ExecutionContext) -> DO + Send,
> IntoFuture for StreamOperation<O, DO, F>
{
type Output = Result<O, DeviceError>;
type IntoFuture = DeviceFuture<O, StreamOperation<O, DO, F>>;
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),
}
}
}
pub struct Select<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>> {
computed: AtomicBool,
input: UnsafeCell<Option<DI>>,
left: UnsafeCell<Option<T1>>,
right: UnsafeCell<Option<T2>>,
}
impl<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>> Select<T1, T2, DI> {
unsafe fn execute(self: &Arc<Self>, context: &ExecutionContext) -> Result<(), DeviceError> {
unsafe {
if !self.computed.load(Ordering::Acquire) {
let input = self.input.get();
let input = input.as_mut();
let input = input.unwrap().take().ok_or_else(|| {
crate::simt::error::device_error(
context.get_device_id(),
"Select operation failed.",
)
})?;
let (left, right) = input.execute(context)?;
*self.left.get() = Some(left);
*self.right.get() = Some(right);
self.computed.store(true, Ordering::Release);
}
Ok(())
}
}
unsafe fn left(&self) -> T1 {
let cell = self.left.get();
let cell = unsafe { cell.as_mut() };
cell.unwrap().take().unwrap()
}
unsafe fn right(&self) -> T2 {
let cell = self.right.get();
let cell = unsafe { cell.as_mut() };
cell.unwrap().take().unwrap()
}
}
pub struct SelectLeft<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>> {
select: Arc<Select<T1, T2, DI>>,
}
unsafe impl<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>> Send
for SelectLeft<T1, T2, DI>
{
}
impl<T1: Send + 'static, T2: Send + 'static, DI: DeviceOperation<Output = (T1, T2)>> DeviceOperation
for SelectLeft<T1, T2, DI>
{
type Output = T1;
unsafe fn execute(self, context: &ExecutionContext) -> Result<T1, DeviceError> {
unsafe {
self.select.execute(context)?;
Ok(self.select.left())
}
}
}
impl<T1: Send + 'static, T2: Send + 'static, DI: DeviceOperation<Output = (T1, T2)>> IntoFuture
for SelectLeft<T1, T2, DI>
{
type Output = Result<T1, DeviceError>;
type IntoFuture = DeviceFuture<T1, SelectLeft<T1, T2, DI>>;
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),
}
}
}
pub struct SelectRight<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>> {
select: Arc<Select<T1, T2, DI>>,
}
unsafe impl<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>> Send
for SelectRight<T1, T2, DI>
{
}
impl<T1: Send + 'static, T2: Send + 'static, DI: DeviceOperation<Output = (T1, T2)>> DeviceOperation
for SelectRight<T1, T2, DI>
{
type Output = T2;
unsafe fn execute(self, context: &ExecutionContext) -> Result<T2, DeviceError> {
unsafe {
self.select.execute(context)?;
Ok(self.select.right())
}
}
}
impl<T1: Send + 'static, T2: Send + 'static, DI: DeviceOperation<Output = (T1, T2)>> IntoFuture
for SelectRight<T1, T2, DI>
{
type Output = Result<T2, DeviceError>;
type IntoFuture = DeviceFuture<T2, SelectRight<T1, T2, DI>>;
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),
}
}
}
fn _unzip<T1: Send, T2: Send, DI: DeviceOperation<Output = (T1, T2)>>(
input: DI,
) -> (SelectLeft<T1, T2, DI>, SelectRight<T1, T2, DI>) {
let select = Select {
computed: AtomicBool::new(false),
input: UnsafeCell::new(Some(input)),
left: UnsafeCell::new(None),
right: UnsafeCell::new(None),
};
let select = Arc::new(select);
let out1 = SelectLeft {
select: Arc::clone(&select),
};
let out2 = SelectRight { select };
(out1, out2)
}
pub trait Unzippable2<T0: Send + 'static, T1: Send + 'static>
where
Self: DeviceOperation<Output = (T0, T1)>,
{
fn unzip(
self,
) -> (
impl DeviceOperation<Output = T0>,
impl DeviceOperation<Output = T1>,
) {
_unzip(self)
}
}
impl<T0: Send + 'static, T1: Send + 'static, DI: DeviceOperation<Output = (T0, T1)>>
Unzippable2<T0, T1> for DI
{
}
#[allow(unused_macros)] macro_rules! unzip {
($arg0:expr) => {
$arg0.unzip()
};
}
#[allow(unused_imports)] pub(crate) use unzip;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finish_sync_returns_operation_result_after_successful_synchronize() {
let result = finish_sync::<u32>(Ok(7), Ok(()));
assert_eq!(result, Ok(7));
}
#[test]
fn finish_sync_preserves_operation_error_after_successful_synchronize() {
let operation_error = DeviceError::Launch("launch failed".to_string());
let result = finish_sync::<u32>(Err(operation_error.clone()), Ok(()));
assert_eq!(result, Err(operation_error));
}
#[test]
fn finish_sync_propagates_synchronize_error_instead_of_panicking() {
let driver_error =
cuda_core::DriverError(cuda_bindings::cudaError_enum_CUDA_ERROR_INVALID_VALUE);
let result = finish_sync::<u32>(Ok(7), Err(driver_error));
assert_eq!(result, Err(DeviceError::Driver(driver_error)));
}
#[test]
fn finish_sync_preserves_operation_error_when_synchronize_also_fails() {
let operation_error = DeviceError::Launch("launch failed".to_string());
let driver_error =
cuda_core::DriverError(cuda_bindings::cudaError_enum_CUDA_ERROR_INVALID_VALUE);
let result = finish_sync::<u32>(Err(operation_error.clone()), Err(driver_error));
assert_eq!(result, Err(operation_error));
}
}