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}