#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use core::mem::MaybeUninit;
use std::ffi::{c_char, CStr};
use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use once_cell::sync::Lazy;
use suitesparse_graphblas_sys::{
GrB_BinaryOp, GrB_BinaryOp_error, GrB_Descriptor, GrB_Descriptor_error, GrB_IndexUnaryOp,
GrB_IndexUnaryOp_error, GrB_Info_GxB_JIT_ERROR, GrB_Info_GxB_OUTPUT_IS_READONLY, GrB_Matrix,
GrB_Matrix_error, GrB_Monoid, GrB_Monoid_error, GrB_Scalar, GrB_Scalar_error, GrB_Semiring,
GrB_Semiring_error, GrB_Type, GrB_Type_error, GrB_UnaryOp, GrB_UnaryOp_error, GrB_Vector,
GrB_Vector_error, GrB_finalize, GxB_init,
};
use crate::context::{MemoryAllocator, MemoryAllocatorFuctionPointers};
use crate::graphblas_bindings::{
GrB_Info,
GrB_Info_GrB_DIMENSION_MISMATCH,
GrB_Info_GrB_DOMAIN_MISMATCH,
GrB_Info_GrB_EMPTY_OBJECT,
GrB_Info_GrB_INDEX_OUT_OF_BOUNDS,
GrB_Info_GrB_INSUFFICIENT_SPACE,
GrB_Info_GrB_INVALID_INDEX,
GrB_Info_GrB_INVALID_OBJECT,
GrB_Info_GrB_INVALID_VALUE,
GrB_Info_GrB_NOT_IMPLEMENTED,
GrB_Info_GrB_NO_VALUE,
GrB_Info_GrB_NULL_POINTER,
GrB_Info_GrB_OUTPUT_NOT_EMPTY,
GrB_Info_GrB_OUT_OF_MEMORY,
GrB_Info_GrB_PANIC,
GrB_Info_GrB_SUCCESS,
GrB_Info_GrB_UNINITIALIZED_OBJECT,
GrB_Info_GxB_EXHAUSTED,
GrB_Mode,
GrB_Mode_GrB_BLOCKING,
GrB_Mode_GrB_NONBLOCKING,
};
use crate::error::SparseLinearAlgebraError;
use crate::error::{GraphblasError, GraphblasErrorType};
use crate::error::{SystemError, SystemErrorType};
use super::{MatrixStorageFormat, SetMatrixFormat};
static NUMBER_OF_READY_CONTEXTS: Lazy<Mutex<AtomicIsize>> =
Lazy::new(|| Mutex::new(AtomicIsize::new(0)));
static IS_GRAPHBLAS_BUSY: Lazy<Mutex<AtomicBool>> =
Lazy::new(|| Mutex::new(AtomicBool::new(false)));
pub trait GetContext {
fn context(&self) -> Arc<Context>;
fn context_ref(&self) -> &Arc<Context>;
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Mode {
Blocking,
NonBlocking,
}
impl From<GrB_Mode> for Mode {
fn from(mode: GrB_Mode) -> Self {
match mode {
GrB_Mode_GrB_BLOCKING => Self::Blocking,
GrB_Mode_GrB_NONBLOCKING => Self::NonBlocking,
_ => panic!("Context mode not supported: {}", mode),
}
}
}
impl Into<GrB_Mode> for Mode {
fn into(self) -> GrB_Mode {
match self {
Self::Blocking => GrB_Mode_GrB_BLOCKING,
Self::NonBlocking => GrB_Mode_GrB_NONBLOCKING,
}
}
}
impl Into<i32> for Mode {
fn into(self) -> i32 {
match self {
Self::Blocking => GrB_Mode_GrB_BLOCKING as i32,
Self::NonBlocking => GrB_Mode_GrB_NONBLOCKING as i32,
}
}
}
#[derive(Debug, PartialEq)]
pub struct Context {
pub(crate) mode: Mode,
pub(crate) memory_allocator_function_pointers: MemoryAllocatorFuctionPointers
}
impl Context {
pub fn init(
mode: Mode,
matrix_storage_format: MatrixStorageFormat,
) -> Result<Arc<Self>, SparseLinearAlgebraError> {
let mut context = Context::start(mode, MemoryAllocator::SystemDefault)?;
context.set_matrix_format(matrix_storage_format)?;
Ok(Arc::new(context))
}
pub fn init_default() -> Result<Arc<Self>, SparseLinearAlgebraError> {
let mut context = Context::start(Mode::NonBlocking, MemoryAllocator::SystemDefault)?;
context.set_matrix_format(MatrixStorageFormat::ByRow)?;
Ok(Arc::new(context))
}
pub unsafe fn init_with_allocator(
mode: Mode,
matrix_storage_format: MatrixStorageFormat,
allocator: MemoryAllocator,
) -> Result<Arc<Self>, SparseLinearAlgebraError> {
let mut context = Context::start(mode, allocator)?;
context.set_matrix_format(matrix_storage_format)?;
Ok(Arc::new(context))
}
fn start(
mode: Mode,
allocator: MemoryAllocator,
) -> Result<Self, SparseLinearAlgebraError> {
let number_of_ready_contexts = NUMBER_OF_READY_CONTEXTS.lock().unwrap();
let memory_allocator_function_pointers = allocator.memory_allocator_function_pointers();
if number_of_ready_contexts.load(Ordering::SeqCst) == 0 {
let _status = initialize(mode, allocator, number_of_ready_contexts)?;
} else {
number_of_ready_contexts.fetch_add(1, Ordering::SeqCst);
}
Ok(Self {
mode,
memory_allocator_function_pointers
})
}
pub fn call_without_detailed_error_information<F>(
&self,
function_to_call: F,
) -> Result<Status, SparseLinearAlgebraError>
where
F: FnMut() -> GrB_Info,
{
call_graphblas_implementation_without_detailed_error_information(function_to_call)
}
}
fn initialize(
mode: Mode,
allocator: MemoryAllocator,
number_of_ready_contexts: MutexGuard<AtomicIsize>,
) -> Result<Status, SparseLinearAlgebraError> {
let memory_allocator_function_pointers = allocator.memory_allocator_function_pointers();
let status = unsafe {
graphblas_result(
GxB_init(
mode.into(),
Some(memory_allocator_function_pointers.malloc),
Some(memory_allocator_function_pointers.calloc),
Some(memory_allocator_function_pointers.realloc),
Some(memory_allocator_function_pointers.free),
),
|| String::from("Failed to initialise GraphBLAS context with custom allocator"),
)?
};
number_of_ready_contexts.fetch_add(1, Ordering::SeqCst);
Ok(status)
}
pub trait CallGraphBlasContext<T> {
fn call<F>(
&self,
function_to_call: F,
reference_to_debug_info: &T,
) -> Result<Status, SparseLinearAlgebraError>
where
F: FnMut() -> GrB_Info;
}
fn call_graphblas_implementation_without_detailed_error_information<F>(
mut function_to_call: F,
) -> Result<Status, SparseLinearAlgebraError>
where
F: FnMut() -> GrB_Info,
{
graphblas_result(function_to_call(), || -> String {
String::from("Failed to execute GraphBLAS function")
})
}
impl Context {
fn finalize_context(&self) -> Result<Status, SparseLinearAlgebraError> {
Ok(self.call_without_detailed_error_information(|| unsafe { GrB_finalize() })?)
}
}
impl Drop for Context {
fn drop(&mut self) -> () {
let number_of_ready_contexts = NUMBER_OF_READY_CONTEXTS.lock().unwrap();
if number_of_ready_contexts.load(Ordering::SeqCst) == 0 {
self.finalize_context().unwrap();
}
}
}
macro_rules! implement_CallGraphBlasContext {
($graphblas_type: ty, $error_retrieval_function: ident) => {
paste::paste! {
impl CallGraphBlasContext<$graphblas_type> for Context {
fn call<F>(
&self,
mut function_to_call: F,
reference_to_debug_info: &$graphblas_type,
) -> Result<Status, SparseLinearAlgebraError>
where
F: FnMut() -> GrB_Info,
{
let get_detailed_error_information =
[<generate_closure_to_retrieve_detailed_error_message_ $graphblas_type>](reference_to_debug_info);
graphblas_result(function_to_call(), get_detailed_error_information)
}
}
fn [<generate_closure_to_retrieve_detailed_error_message_ $graphblas_type>]<'a>(
struct_with_debugging_info: &'a $graphblas_type,
) -> impl Fn() -> String + 'a {
return || -> String {
let mut graphblas_error_message: MaybeUninit<*const c_char> = MaybeUninit::uninit();
let graphblas_call_status;
unsafe {
graphblas_call_status = $error_retrieval_function(
graphblas_error_message.as_mut_ptr(),
*struct_with_debugging_info,
);
}
let graphblas_error_message = unsafe { graphblas_error_message.assume_init() };
match graphblas_call_status {
GrB_Info_GrB_SUCCESS => {
let message;
unsafe {
message = CStr::from_ptr(graphblas_error_message).to_str();
}
match message {
Ok(message) => message.to_owned(),
Err(error) => format!("Failed to execute GraphBLAS function. Unable to parse detailed error message due to: {}", error)
}
}
_ => return String::from("Failed to execute GraphBLAS function. Unable to retrieve more detailed error information.")
}
};
}
}
};
}
implement_CallGraphBlasContext!(GrB_Type, GrB_Type_error);
implement_CallGraphBlasContext!(GrB_Scalar, GrB_Scalar_error);
implement_CallGraphBlasContext!(GrB_Vector, GrB_Vector_error);
implement_CallGraphBlasContext!(GrB_Matrix, GrB_Matrix_error);
implement_CallGraphBlasContext!(GrB_Descriptor, GrB_Descriptor_error);
implement_CallGraphBlasContext!(GrB_UnaryOp, GrB_UnaryOp_error);
implement_CallGraphBlasContext!(GrB_BinaryOp, GrB_BinaryOp_error);
implement_CallGraphBlasContext!(GrB_Semiring, GrB_Semiring_error);
implement_CallGraphBlasContext!(GrB_Monoid, GrB_Monoid_error);
implement_CallGraphBlasContext!(GrB_IndexUnaryOp, GrB_IndexUnaryOp_error);
fn graphblas_result<F>(
grb_info: GrB_Info,
get_detailed_error_information: F,
) -> Result<Status, SparseLinearAlgebraError>
where
F: Fn() -> String,
{
let status = Status::from(grb_info);
match status {
Status::Success => Ok(Status::Success),
_ => Err(status.into_sparse_linear_algebra_error(get_detailed_error_information())),
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Status {
Success,
NoValue,
UnitializedObject,
InvalidObject,
NotImplemented,
NullPointer,
InvalidValue,
InvalidIndex,
DomainMismatch,
DimensionMismatch,
EmptyObject,
OutputNotEmpty,
OutOfMemory,
InsufficientSpace,
IndexOutOfBounds,
IteratorExhausted,
Panic,
JITError,
ReadOnlyOutput,
UnknownStatusType,
}
impl From<GrB_Info> for Status {
fn from(status: GrB_Info) -> Self {
match status {
GrB_Info_GrB_SUCCESS => Self::Success,
GrB_Info_GrB_NO_VALUE => Self::NoValue,
GrB_Info_GrB_UNINITIALIZED_OBJECT => Self::UnitializedObject,
GrB_Info_GrB_INVALID_OBJECT => Self::InvalidObject,
GrB_Info_GrB_NOT_IMPLEMENTED => Self::NotImplemented,
GrB_Info_GrB_NULL_POINTER => Self::NullPointer,
GrB_Info_GrB_INVALID_VALUE => Self::InvalidValue,
GrB_Info_GrB_INVALID_INDEX => Self::InvalidIndex,
GrB_Info_GrB_DOMAIN_MISMATCH => Self::DomainMismatch,
GrB_Info_GrB_DIMENSION_MISMATCH => Self::DimensionMismatch,
GrB_Info_GrB_EMPTY_OBJECT => Self::EmptyObject,
GrB_Info_GrB_OUTPUT_NOT_EMPTY => Self::OutputNotEmpty,
GrB_Info_GrB_OUT_OF_MEMORY => Self::OutOfMemory,
GrB_Info_GrB_INSUFFICIENT_SPACE => Self::InsufficientSpace,
GrB_Info_GrB_INDEX_OUT_OF_BOUNDS => Self::IndexOutOfBounds,
GrB_Info_GxB_EXHAUSTED => Self::IteratorExhausted,
GrB_Info_GrB_PANIC => Self::Panic,
GrB_Info_GxB_JIT_ERROR => Self::JITError,
GrB_Info_GxB_OUTPUT_IS_READONLY => Self::ReadOnlyOutput,
_ => Self::UnknownStatusType,
}
}
}
impl Status {
fn into_sparse_linear_algebra_error(
self,
detailed_error_information: String,
) -> SparseLinearAlgebraError {
match self {
Status::Success => SystemError::new(
SystemErrorType::CreateGraphBlasErrorOnSuccessValue,
format!("Logic error, called into<GraphBlasError> for success status"),
None,
)
.into(),
Status::NoValue => {
GraphblasError::new(GraphblasErrorType::NoValue, detailed_error_information).into()
}
Status::UnitializedObject => GraphblasError::new(
GraphblasErrorType::UnitializedObject,
detailed_error_information,
)
.into(),
Status::InvalidObject => GraphblasError::new(
GraphblasErrorType::InvalidObject,
detailed_error_information,
)
.into(),
Status::NotImplemented => GraphblasError::new(
GraphblasErrorType::NotImplemented,
detailed_error_information,
)
.into(),
Status::NullPointer => {
GraphblasError::new(GraphblasErrorType::NullPointer, detailed_error_information)
.into()
}
Status::InvalidValue => {
GraphblasError::new(GraphblasErrorType::InvalidValue, detailed_error_information)
.into()
}
Status::InvalidIndex => {
GraphblasError::new(GraphblasErrorType::InvalidIndex, detailed_error_information)
.into()
}
Status::DomainMismatch => GraphblasError::new(
GraphblasErrorType::DomainMismatch,
detailed_error_information,
)
.into(),
Status::DimensionMismatch => GraphblasError::new(
GraphblasErrorType::DimensionMismatch,
detailed_error_information,
)
.into(),
Status::EmptyObject => {
GraphblasError::new(GraphblasErrorType::EmptyObject, detailed_error_information)
.into()
}
Status::OutputNotEmpty => GraphblasError::new(
GraphblasErrorType::OutputNotEmpty,
detailed_error_information,
)
.into(),
Status::OutOfMemory => {
GraphblasError::new(GraphblasErrorType::OutOfMemory, detailed_error_information)
.into()
}
Status::InsufficientSpace => GraphblasError::new(
GraphblasErrorType::InsufficientSpace,
detailed_error_information,
)
.into(),
Status::IndexOutOfBounds => GraphblasError::new(
GraphblasErrorType::IndexOutOfBounds,
detailed_error_information,
)
.into(),
Status::IteratorExhausted => GraphblasError::new(
GraphblasErrorType::IteratorExhausted,
detailed_error_information,
)
.into(),
Status::Panic => {
GraphblasError::new(GraphblasErrorType::Panic, detailed_error_information).into()
}
Status::JITError => {
GraphblasError::new(GraphblasErrorType::JITError, detailed_error_information).into()
}
Status::ReadOnlyOutput => GraphblasError::new(
GraphblasErrorType::ReadOnlyOutput,
detailed_error_information,
)
.into(),
Status::UnknownStatusType => SystemError::new(
SystemErrorType::UnsupportedGraphBlasErrorValue,
String::from("Something went wrong while calling the GrapBLAS implementation"),
None,
)
.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn start_and_drop_context() {
let _context = Context::start(Mode::NonBlocking, MemoryAllocator::SystemDefault)
.unwrap();
}
#[test]
fn start_and_drop_context_2() {
let _context = Context::start(Mode::NonBlocking, MemoryAllocator::SystemDefault)
.unwrap();
}
#[test]
fn start_and_drop_context_3() {
let _context = Context::init_default().unwrap();
}
#[test]
fn start_and_drop_context_4() {
let _context = Context::init_default().unwrap();
}
#[test]
fn start_and_drop_context_5() {
let _context = Context::init_default().unwrap();
}
#[test]
fn custom_matrix_storage_format() {
let _context = Context::init(Mode::NonBlocking, MatrixStorageFormat::ByColumn).unwrap();
}
#[test]
fn custom_mode() {
let _context = Context::init(Mode::Blocking, MatrixStorageFormat::ByColumn).unwrap();
}
}