singe-cusparse 0.1.0-alpha.5

Safe Rust wrappers for the NVIDIA cuSPARSE sparse linear algebra library.
Documentation
use std::{
    ffi::{CStr, NulError},
    fmt::{self, Display, Formatter},
    result,
};

use num_enum::{IntoPrimitive, TryFromPrimitive};
use singe_core::impl_enum_conversion;
use singe_cuda::error::Error as CudaError;
use singe_cusparse_sys as sys;
use thiserror::Error;

/// [`Status`] is the cuSPARSE status code returned by cuSPARSE operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
#[repr(u32)]
pub enum Status {
    /// The operation completed successfully.
    Success = sys::cusparseStatus_t::CUSPARSE_STATUS_SUCCESS as _,
    /// The cuSPARSE library was not initialized.
    /// Common causes include a missing [`Context::create`](crate::context::Context::create) call, a CUDA runtime error reported by cuSPARSE, or an error in the hardware setup.
    /// The error also applies when a matrix or vector descriptor is not initialized.
    NotInitialized = sys::cusparseStatus_t::CUSPARSE_STATUS_NOT_INITIALIZED as _,
    /// Resource allocation failed inside the cuSPARSE library.
    /// Usually caused by a device memory allocation (`cudaMalloc()`) or host memory allocation failure.
    AllocFailed = sys::cusparseStatus_t::CUSPARSE_STATUS_ALLOC_FAILED as _,
    /// An unsupported value or parameter was passed to the operation, such as a negative vector size.
    InvalidValue = sys::cusparseStatus_t::CUSPARSE_STATUS_INVALID_VALUE as _,
    /// The operation requires a feature absent from the device architecture.
    ArchMismatch = sys::cusparseStatus_t::CUSPARSE_STATUS_ARCH_MISMATCH as _,
    /// An access to GPU memory space failed, which is usually caused by a
    /// failure to bind a texture or an invalid device pointer.
    /// To correct: check that all device pointers are valid and that no texture
    /// is bound that could interfere with the operation.
    MappingError = sys::cusparseStatus_t::CUSPARSE_STATUS_MAPPING_ERROR as _,
    /// The GPU program failed to execute.
    /// A kernel launch failure on the GPU is a common cause.
    ExecutionFailed = sys::cusparseStatus_t::CUSPARSE_STATUS_EXECUTION_FAILED as _,
    /// An internal cuSPARSE operation failed.
    /// Also check that memory passed to the operation is not released before the operation completes.
    InternalError = sys::cusparseStatus_t::CUSPARSE_STATUS_INTERNAL_ERROR as _,
    /// The matrix type is not supported by this operation.
    /// Usually caused by passing an invalid matrix descriptor to the operation.
    /// Check that the fields in [`MatrixDescriptor`](crate::matrix::MatrixDescriptor) were set correctly.
    MatrixTypeNotSupported = sys::cusparseStatus_t::CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED as _,
    /// A zero pivot was encountered during the factorization.
    /// The operation cannot proceed because a diagonal element is zero.
    /// To correct: ensure the input matrix is non-singular.
    ZeroPivot = sys::cusparseStatus_t::CUSPARSE_STATUS_ZERO_PIVOT as _,
    /// The operation or data type combination is currently not supported.
    NotSupported = sys::cusparseStatus_t::CUSPARSE_STATUS_NOT_SUPPORTED as _,
    /// The resources for the computation, such as GPU global or shared memory, are not sufficient to complete the operation.
    /// The error can also indicate that the current computation mode, such as sparse matrix index bit size, cannot handle the given input.
    InsufficientResources = sys::cusparseStatus_t::CUSPARSE_STATUS_INSUFFICIENT_RESOURCES as _,
}

impl_enum_conversion!(sys::cusparseStatus_t, Status);

impl Status {
    pub const fn description(self) -> &'static str {
        match self {
            Self::Success => "success",
            Self::NotInitialized => "not initialized",
            Self::AllocFailed => "allocation failed",
            Self::InvalidValue => "invalid value",
            Self::ArchMismatch => "architecture mismatch",
            Self::MappingError => "mapping error",
            Self::ExecutionFailed => "execution failed",
            Self::InternalError => "internal error",
            Self::MatrixTypeNotSupported => "matrix type not supported",
            Self::ZeroPivot => "zero pivot",
            Self::NotSupported => "not supported",
            Self::InsufficientResources => "insufficient resources",
        }
    }
}

impl Display for Status {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Self::Success => write!(f, "CUSPARSE_STATUS_SUCCESS"),
            Self::NotInitialized => write!(f, "CUSPARSE_STATUS_NOT_INITIALIZED"),
            Self::AllocFailed => write!(f, "CUSPARSE_STATUS_ALLOC_FAILED"),
            Self::InvalidValue => write!(f, "CUSPARSE_STATUS_INVALID_VALUE"),
            Self::ArchMismatch => write!(f, "CUSPARSE_STATUS_ARCH_MISMATCH"),
            Self::MappingError => write!(f, "CUSPARSE_STATUS_MAPPING_ERROR"),
            Self::ExecutionFailed => write!(f, "CUSPARSE_STATUS_EXECUTION_FAILED"),
            Self::InternalError => write!(f, "CUSPARSE_STATUS_INTERNAL_ERROR"),
            Self::MatrixTypeNotSupported => {
                write!(f, "CUSPARSE_STATUS_MATRIX_TYPE_NOT_SUPPORTED")
            }
            Self::ZeroPivot => write!(f, "CUSPARSE_STATUS_ZERO_PIVOT"),
            Self::NotSupported => write!(f, "CUSPARSE_STATUS_NOT_SUPPORTED"),
            Self::InsufficientResources => {
                write!(f, "CUSPARSE_STATUS_INSUFFICIENT_RESOURCES")
            }
        }
    }
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("cuda error: {0}")]
    Cuda(#[from] CudaError),

    #[error("cusparse error ({code}): {message}")]
    Cusparse { code: Status, message: String },

    #[error("string contains interior nul byte")]
    InteriorNul,

    #[error("unexpected null handle")]
    NullHandle,

    #[error("`{name}` is out of range")]
    OutOfRange { name: String },

    #[error("scalar pointer modes do not match")]
    ScalarPointerModeMismatch,

    #[error("plan belongs to a different cusparse context")]
    PlanContextMismatch,

    #[error("stream belongs to a different cuda context")]
    StreamContextMismatch,
}

pub type Result<T> = result::Result<T, Error>;

impl From<sys::cusparseStatus_t> for Error {
    fn from(status: sys::cusparseStatus_t) -> Self {
        debug_assert_ne!(status, sys::cusparseStatus_t::CUSPARSE_STATUS_SUCCESS);

        let message = unsafe {
            let c_ptr = sys::cusparseGetErrorString(status);
            if c_ptr.is_null() {
                String::from("unknown cusparse error")
            } else {
                CStr::from_ptr(c_ptr).to_string_lossy().into_owned()
            }
        };

        Self::Cusparse {
            code: status.into(),
            message,
        }
    }
}

impl From<NulError> for Error {
    fn from(_: NulError) -> Self {
        Self::InteriorNul
    }
}

#[macro_export]
macro_rules! try_ffi {
    ($expr:expr) => {{
        let status = { $expr };
        if status != singe_cusparse_sys::cusparseStatus_t::CUSPARSE_STATUS_SUCCESS {
            Err($crate::error::Error::from(status))
        } else {
            Ok(())
        }
    }};
}