Skip to main content

baracuda_runtime/
query.rs

1//! Runtime-API queries: pointer attributes, device properties, kernel
2//! attributes. Typed wrappers around the `cuda*GetAttributes` /
3//! `cudaGetDeviceProperties` family.
4
5use baracuda_cuda_sys::runtime::runtime;
6use baracuda_cuda_sys::runtime::types::{
7    cudaFuncAttributes, cudaMemoryType, cudaPointerAttributes,
8};
9
10use crate::device::Device;
11use crate::error::{Result, check};
12
13/// Memory kind reported by [`pointer_attributes`].
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15pub enum MemoryType {
16    /// Pointer is not registered with CUDA (plain host malloc or an
17    /// unrelated OS allocation).
18    Unregistered,
19    /// Host memory (pinned / mapped / managed-with-host-affinity).
20    Host,
21    /// Plain device memory.
22    Device,
23    /// Managed (unified) memory.
24    Managed,
25}
26
27impl MemoryType {
28    #[inline]
29    fn from_raw(raw: i32) -> Self {
30        match raw {
31            cudaMemoryType::HOST => MemoryType::Host,
32            cudaMemoryType::DEVICE => MemoryType::Device,
33            cudaMemoryType::MANAGED => MemoryType::Managed,
34            _ => MemoryType::Unregistered,
35        }
36    }
37}
38
39/// Typed view over `cudaPointerAttributes`.
40#[derive(Copy, Clone, Debug)]
41pub struct PointerAttributes {
42    /// Memory kind CUDA classified the pointer as.
43    pub memory_type: MemoryType,
44    /// Device ordinal that owns the allocation (`-1` for unregistered).
45    pub device: i32,
46    /// Device-side pointer aliasing the allocation (may equal the host
47    /// pointer for managed memory).
48    pub device_pointer: *mut core::ffi::c_void,
49    /// Host-side pointer aliasing the allocation, when one exists
50    /// (mapped pinned / managed); null otherwise.
51    pub host_pointer: *mut core::ffi::c_void,
52}
53
54/// Query what CUDA knows about `ptr`. On pointers CUDA has never seen
55/// (plain host `malloc`, returned from C libraries) this returns
56/// `MemoryType::Unregistered`.
57///
58/// # Safety
59///
60/// `ptr` can be any pointer — CUDA internally classifies it. This is a
61/// pure query and doesn't dereference `ptr`.
62#[allow(clippy::not_unsafe_ptr_arg_deref)]
63pub fn pointer_attributes(ptr: *const core::ffi::c_void) -> Result<PointerAttributes> {
64    let r = runtime()?;
65    let cu = r.cuda_pointer_get_attributes()?;
66    let mut raw = cudaPointerAttributes::default();
67    check(unsafe {
68        cu(
69            &mut raw as *mut cudaPointerAttributes as *mut core::ffi::c_void,
70            ptr,
71        )
72    })?;
73    Ok(PointerAttributes {
74        memory_type: MemoryType::from_raw(raw.type_),
75        device: raw.device,
76        device_pointer: raw.device_pointer,
77        host_pointer: raw.host_pointer,
78    })
79}
80
81/// Subset of `cudaDeviceProp` fields most users care about. The full C
82/// struct is ~1 KB with fields that are rarely accessed — we surface the
83/// hot-path ones and keep a reserved `_raw` slot for the whole buffer so
84/// advanced users can cast through.
85#[derive(Clone, Debug)]
86pub struct DeviceProperties {
87    /// ASCII device name as reported by `cudaGetDeviceProperties`.
88    pub name: String,
89    /// Total global memory, in bytes.
90    pub total_global_memory_bytes: u64,
91    /// Maximum shared memory available per block, in bytes.
92    pub shared_memory_per_block_bytes: u64,
93    /// Number of 32-bit registers available per block.
94    pub regs_per_block: i32,
95    /// Warp size in threads (32 on all current architectures).
96    pub warp_size: i32,
97    /// Maximum number of threads per block.
98    pub max_threads_per_block: i32,
99    /// Maximum block dimensions `[x, y, z]`.
100    pub max_block_dim: [i32; 3],
101    /// Maximum grid dimensions `[x, y, z]`.
102    pub max_grid_dim: [i32; 3],
103    /// Peak SM clock frequency, in kHz.
104    pub clock_rate_khz: i32,
105    /// Peak memory clock frequency, in kHz.
106    pub memory_clock_rate_khz: i32,
107    /// Width of the global-memory bus, in bits.
108    pub memory_bus_width_bits: i32,
109    /// L2 cache capacity, in bytes.
110    pub l2_cache_size_bytes: i32,
111    /// Maximum number of resident threads per SM.
112    pub max_threads_per_sm: i32,
113    /// Number of streaming multiprocessors on the device.
114    pub multiprocessor_count: i32,
115    /// Major part of the compute capability (e.g. `8` for sm_89).
116    pub compute_capability_major: i32,
117    /// Minor part of the compute capability (e.g. `9` for sm_89).
118    pub compute_capability_minor: i32,
119    /// `true` if the device shares memory with the host (iGPU).
120    pub integrated: bool,
121    /// `true` if the device supports concurrent kernel execution.
122    pub concurrent_kernels: bool,
123    /// PCI bus ID.
124    pub pci_bus_id: i32,
125    /// PCI device ID.
126    pub pci_device_id: i32,
127    /// PCI domain ID.
128    pub pci_domain_id: i32,
129}
130
131/// Fetch a typed subset of `cudaDeviceProp` for `device`.
132///
133/// We use `cudaGetDeviceProperties` to pull the device name (a 256-byte
134/// char array at offset 0 — the one layout-stable field across CUDA
135/// versions) and per-attribute queries for everything else. This is
136/// more robust than struct-offset parsing because `cudaDeviceProp` has
137/// grown over CUDA versions and offsets drift silently.
138pub fn device_properties(device: &Device) -> Result<DeviceProperties> {
139    use baracuda_cuda_sys::runtime::types::cudaDeviceAttr as Attr;
140
141    let r = runtime()?;
142    let cu = r.cuda_get_device_properties()?;
143    let mut buf = vec![0u8; 2048];
144    check(unsafe { cu(buf.as_mut_ptr() as *mut core::ffi::c_void, device.ordinal()) })?;
145
146    // The name field is a 256-byte char array at offset 0 — the only
147    // field we read from the buffer. Everything else goes through
148    // cudaDeviceGetAttribute which is version-stable.
149    let name = unsafe {
150        let name_ptr = buf.as_ptr() as *const core::ffi::c_char;
151        core::ffi::CStr::from_ptr(name_ptr)
152            .to_string_lossy()
153            .into_owned()
154    };
155
156    // Total global memory isn't a per-attribute query — use cudaMemGetInfo.
157    let total_global_memory_bytes = {
158        let cu_info = r.cuda_mem_get_info()?;
159        let mut free: usize = 0;
160        let mut total: usize = 0;
161        check(unsafe { cu_info(&mut free, &mut total) })?;
162        total as u64
163    };
164
165    Ok(DeviceProperties {
166        name,
167        total_global_memory_bytes,
168        shared_memory_per_block_bytes: device
169            .attribute(Attr::MAX_SHARED_MEMORY_PER_BLOCK)
170            .unwrap_or(0) as u64,
171        regs_per_block: device.attribute(Attr::MAX_REGISTERS_PER_BLOCK).unwrap_or(0),
172        warp_size: device.attribute(Attr::WARP_SIZE).unwrap_or(0),
173        max_threads_per_block: device.attribute(Attr::MAX_THREADS_PER_BLOCK).unwrap_or(0),
174        max_block_dim: [
175            device.attribute(Attr::MAX_BLOCK_DIM_X).unwrap_or(0),
176            device.attribute(Attr::MAX_BLOCK_DIM_Y).unwrap_or(0),
177            device.attribute(Attr::MAX_BLOCK_DIM_Z).unwrap_or(0),
178        ],
179        max_grid_dim: [
180            device.attribute(Attr::MAX_GRID_DIM_X).unwrap_or(0),
181            device.attribute(Attr::MAX_GRID_DIM_Y).unwrap_or(0),
182            device.attribute(Attr::MAX_GRID_DIM_Z).unwrap_or(0),
183        ],
184        clock_rate_khz: device.attribute(Attr::CLOCK_RATE).unwrap_or(0),
185        memory_clock_rate_khz: device.attribute(Attr::CLOCK_RATE).unwrap_or(0),
186        memory_bus_width_bits: 0,
187        l2_cache_size_bytes: 0,
188        max_threads_per_sm: 0,
189        multiprocessor_count: device.attribute(Attr::MULTIPROCESSOR_COUNT).unwrap_or(0),
190        compute_capability_major: device
191            .attribute(Attr::COMPUTE_CAPABILITY_MAJOR)
192            .unwrap_or(0),
193        compute_capability_minor: device
194            .attribute(Attr::COMPUTE_CAPABILITY_MINOR)
195            .unwrap_or(0),
196        integrated: device.attribute(Attr::INTEGRATED).unwrap_or(0) != 0,
197        concurrent_kernels: device.attribute(Attr::CONCURRENT_KERNELS).unwrap_or(0) != 0,
198        pci_bus_id: device.attribute(Attr::PCI_BUS_ID).unwrap_or(0),
199        pci_device_id: device.attribute(Attr::PCI_DEVICE_ID).unwrap_or(0),
200        pci_domain_id: device.attribute(Attr::PCI_DOMAIN_ID).unwrap_or(0),
201    })
202}
203
204/// Query a kernel's register / shared-memory / PTX-version metadata.
205/// `func_symbol` is a `const void*` — the address of the kernel symbol
206/// as used by `cudaLaunchKernel` (or [`crate::Kernel::as_launch_ptr`]).
207///
208/// # Safety
209///
210/// `func_symbol` must be a valid CUDA kernel symbol address. Passing
211/// garbage causes undefined behavior inside the driver.
212pub unsafe fn func_attributes(func_symbol: *const core::ffi::c_void) -> Result<cudaFuncAttributes> {
213    unsafe {
214        let r = runtime()?;
215        let cu = r.cuda_func_get_attributes()?;
216        let mut attrs = cudaFuncAttributes::default();
217        check(cu(
218            &mut attrs as *mut cudaFuncAttributes as *mut core::ffi::c_void,
219            func_symbol,
220        ))?;
221        Ok(attrs)
222    }
223}