Skip to main content

singe_cusparse/
context.rs

1use std::{mem::ManuallyDrop, path::Path, ptr, sync::Arc};
2
3use singe_core::path_to_cstring;
4use singe_cuda::{
5    context::Context as CudaContext,
6    stream::{BorrowedStream, Stream, StreamBinding},
7};
8
9use crate::{
10    error::{Error, Result},
11    scalar::Scalar,
12    sys, try_ffi,
13    types::PointerMode,
14};
15
16/// A stateful cuSPARSE handle.
17///
18/// Use one context per host thread or concurrent task. The handle is movable
19/// between threads, but it is intentionally not `Clone` or `Sync`.
20#[derive(Debug)]
21pub struct Context {
22    handle: Handle,
23}
24
25#[derive(Debug)]
26struct Handle {
27    raw: sys::cusparseHandle_t,
28    cuda_ctx: Arc<CudaContext>,
29}
30
31// cuSPARSE handles carry mutable pointer-mode and stream state. Ownership can
32// move between threads, but the wrapper requires exclusive access for mutation.
33unsafe impl Send for Handle {}
34
35impl Context {
36    /// Initializes the cuSPARSE library and creates a cuSPARSE handle.
37    /// The handle must be created before using other cuSPARSE operations through this wrapper.
38    /// It allocates hardware resources needed to access the GPU.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the CUDA context cannot be bound, if cuSPARSE cannot
43    /// create a handle, or if cuSPARSE returns a null handle.
44    pub fn create(cuda_ctx: &Arc<CudaContext>) -> Result<Self> {
45        cuda_ctx.bind()?;
46
47        let mut handle = ptr::null_mut();
48        unsafe {
49            try_ffi!(sys::cusparseCreate(&raw mut handle))?;
50        }
51
52        if handle.is_null() {
53            return Err(Error::NullHandle);
54        }
55
56        Ok(Self {
57            handle: Handle {
58                raw: handle,
59                cuda_ctx: Arc::clone(cuda_ctx),
60            },
61        })
62    }
63
64    /// Wraps an existing cuSPARSE handle and takes ownership of it.
65    ///
66    /// # Safety
67    ///
68    /// `handle` must be a valid cuSPARSE handle associated with `cuda_ctx`.
69    /// Ownership of `handle` is transferred to the returned context, and the
70    /// handle must not be destroyed elsewhere after calling this function.
71    pub unsafe fn from_raw(
72        handle: sys::cusparseHandle_t,
73        cuda_ctx: Arc<CudaContext>,
74    ) -> Result<Self> {
75        if handle.is_null() {
76            return Err(Error::NullHandle);
77        }
78
79        Ok(Self {
80            handle: Handle {
81                raw: handle,
82                cuda_ctx,
83            },
84        })
85    }
86
87    /// Returns the underlying CUDA context used by this cuSPARSE handle.
88    pub fn cuda_context(&self) -> &Arc<CudaContext> {
89        &self.handle.cuda_ctx
90    }
91
92    /// Binds the underlying CUDA context associated with this handle.
93    ///
94    /// # Errors
95    ///
96    /// Returns an error if the CUDA context cannot be bound.
97    pub fn bind(&self) -> Result<()> {
98        Ok(self.cuda_context().bind()?)
99    }
100
101    /// Ensures `stream` belongs to the same CUDA context as this handle.
102    ///
103    /// Returns an error if the stream belongs to a different context.
104    pub fn ensure_stream(&self, stream: &Stream) -> Result<()> {
105        if self.cuda_context().as_ref() != stream.context() {
106            return Err(Error::StreamContextMismatch);
107        }
108
109        self.bind()
110    }
111
112    /// Returns the version number of the cuSPARSE library.
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if the CUDA context cannot be bound or if cuSPARSE
117    /// cannot report the version for this handle.
118    pub fn version(&self) -> Result<i32> {
119        self.bind()?;
120
121        let mut version = 0;
122        unsafe {
123            try_ffi!(sys::cusparseGetVersion(self.as_raw(), &raw mut version))?;
124        }
125        Ok(version)
126    }
127
128    /// Returns the stream used for cuSPARSE operations on this handle.
129    /// If no explicit stream has been set, cuSPARSE uses the CUDA default stream.
130    ///
131    /// # Errors
132    ///
133    /// Returns an error if the CUDA context cannot be bound or if cuSPARSE
134    /// cannot report the current stream.
135    pub fn stream(&self) -> Result<StreamBinding> {
136        self.bind()?;
137
138        let mut stream = ptr::null_mut();
139        unsafe {
140            try_ffi!(sys::cusparseGetStream(self.as_raw(), &raw mut stream))?;
141        }
142
143        Ok(if stream.is_null() {
144            StreamBinding::Default(Arc::clone(self.cuda_context()))
145        } else {
146            StreamBinding::Borrowed(unsafe {
147                BorrowedStream::from_raw(stream, Arc::clone(self.cuda_context()))
148            })
149        })
150    }
151
152    /// Sets the stream used by cuSPARSE operations on this handle.
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if `stream` belongs to another CUDA context, if the CUDA
157    /// context cannot be bound, or if cuSPARSE rejects the stream.
158    pub fn set_stream(&self, stream: Option<&Stream>) -> Result<()> {
159        if let Some(stream) = stream {
160            self.ensure_stream(stream)?;
161        } else {
162            self.bind()?;
163        }
164
165        unsafe {
166            try_ffi!(sys::cusparseSetStream(
167                self.as_raw(),
168                match stream {
169                    Some(stream) => stream.as_raw(),
170                    None => ptr::null_mut(),
171                },
172            ))?;
173        }
174        Ok(())
175    }
176
177    /// Returns the context-global scalar pointer mode used by cuSPARSE operations on this handle.
178    /// See [`PointerMode`] for scalar pointer semantics.
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if the CUDA context cannot be bound or if cuSPARSE
183    /// cannot report the pointer mode.
184    pub fn scalar_pointer_mode(&self) -> Result<PointerMode> {
185        self.bind()?;
186
187        let mut mode = sys::cusparsePointerMode_t::CUSPARSE_POINTER_MODE_HOST;
188        unsafe {
189            try_ffi!(sys::cusparseGetPointerMode(self.as_raw(), &raw mut mode))?;
190        }
191        Ok(mode.into())
192    }
193
194    /// Sets the context-global scalar pointer mode used by cuSPARSE operations on this handle.
195    /// The default mode reads scalar values from host memory.
196    /// See [`PointerMode`] for scalar pointer semantics.
197    ///
198    /// # Errors
199    ///
200    /// Returns an error if the CUDA context cannot be bound or if cuSPARSE
201    /// rejects the pointer mode.
202    pub fn set_scalar_pointer_mode(&self, mode: PointerMode) -> Result<()> {
203        self.bind()?;
204        unsafe {
205            try_ffi!(sys::cusparseSetPointerMode(self.as_raw(), mode.into()))?;
206        }
207        Ok(())
208    }
209
210    pub(crate) fn require_scalar_pointer_mode<T>(
211        &self,
212        alpha: Scalar<'_, T>,
213        beta: Scalar<'_, T>,
214    ) -> Result<()> {
215        let alpha_mode = alpha.pointer_mode();
216        let beta_mode = beta.pointer_mode();
217        if alpha_mode != beta_mode {
218            return Err(Error::ScalarPointerModeMismatch);
219        }
220        if self.scalar_pointer_mode()? != alpha_mode {
221            self.set_scalar_pointer_mode(alpha_mode)?;
222        }
223        Ok(())
224    }
225
226    /// Experimental: sets the logging callback function.
227    ///
228    /// The callback must use the cuSPARSE logger callback ABI.
229    ///
230    /// # Safety
231    ///
232    /// `callback`, if present, must remain valid for use by cuSPARSE and must
233    /// follow the callback ABI expected by the library.
234    ///
235    /// # Errors
236    ///
237    /// Returns an error if cuSPARSE rejects the callback.
238    pub unsafe fn set_logger_callback(callback: sys::cusparseLoggerCallback_t) -> Result<()> {
239        unsafe {
240            try_ffi!(sys::cusparseLoggerSetCallback(callback))?;
241        }
242        Ok(())
243    }
244
245    /// Experimental: sets the logging level.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error if cuSPARSE rejects the logging level.
250    pub fn set_logger_level(level: i32) -> Result<()> {
251        unsafe {
252            try_ffi!(sys::cusparseLoggerSetLevel(level))?;
253        }
254        Ok(())
255    }
256
257    /// Experimental: sets the logging mask.
258    ///
259    /// # Errors
260    ///
261    /// Returns an error if cuSPARSE rejects the logging mask.
262    pub fn set_logger_mask(mask: i32) -> Result<()> {
263        unsafe {
264            try_ffi!(sys::cusparseLoggerSetMask(mask))?;
265        }
266        Ok(())
267    }
268
269    /// Experimental: sets the logging output file.
270    /// Once registered, the provided file handle must remain open until another
271    /// file handle is registered.
272    ///
273    /// # Safety
274    ///
275    /// `file` must be a valid `FILE` handle for as long as cuSPARSE may write to it.
276    ///
277    /// # Errors
278    ///
279    /// Returns an error if cuSPARSE rejects the file handle.
280    pub unsafe fn set_logger_file(file: *mut sys::FILE) -> Result<()> {
281        unsafe {
282            try_ffi!(sys::cusparseLoggerSetFile(file))?;
283        }
284        Ok(())
285    }
286
287    /// Experimental: sets the logging output file by path.
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if `path` cannot be converted to a C string or if
292    /// cuSPARSE cannot open the log file.
293    pub fn set_logger_path(path: impl AsRef<Path>) -> Result<()> {
294        let path = path_to_cstring(path.as_ref())?;
295        unsafe {
296            try_ffi!(sys::cusparseLoggerOpenFile(path.as_ptr()))?;
297        }
298        Ok(())
299    }
300
301    /// Disables cuSPARSE logging.
302    ///
303    /// # Errors
304    ///
305    /// Returns an error if cuSPARSE cannot disable logging.
306    pub fn disable_logger() -> Result<()> {
307        unsafe {
308            try_ffi!(sys::cusparseLoggerForceDisable())?;
309        }
310        Ok(())
311    }
312
313    /// Returns the raw cuSPARSE handle.
314    ///
315    /// The returned handle is borrowed and remains valid only while this
316    /// context and its underlying CUDA context are alive.
317    pub fn as_raw(&self) -> sys::cusparseHandle_t {
318        self.handle.raw
319    }
320
321    /// Consumes the context and returns the raw cuSPARSE handle without
322    /// destroying it.
323    ///
324    /// The caller becomes responsible for eventually destroying the returned
325    /// handle with cuSPARSE.
326    pub fn into_raw(self) -> sys::cusparseHandle_t {
327        let context = ManuallyDrop::new(self);
328        context.handle.raw
329    }
330}
331
332impl Drop for Handle {
333    fn drop(&mut self) {
334        if let Err(err) = self.cuda_ctx.bind() {
335            #[cfg(debug_assertions)]
336            eprintln!("failed to bind cuda context before destroying cusparse handle: {err}");
337        }
338
339        unsafe {
340            if let Err(err) = try_ffi!(sys::cusparseDestroy(self.raw)) {
341                #[cfg(debug_assertions)]
342                eprintln!("failed to destroy cusparse context: {err}");
343            }
344        }
345    }
346}