Skip to main content

mlx_c_rust/types/
device.rs

1//! The MLX device domain: device handles, device information, and the
2//! process default device.
3
4use std::ffi::{CStr, CString};
5
6use crate::MlxCError;
7use crate::error::check_status;
8use crate::raw;
9use crate::types::vectors::MlxVectorString;
10
11/// Owned MLX device handle released exactly once through the official C API.
12#[derive(Debug)]
13pub struct MlxDeviceHandle(raw::mlx_device);
14
15impl MlxDeviceHandle {
16    /// Creates an empty device handle for an output parameter.
17    pub(crate) fn empty() -> Self {
18        // SAFETY: The returned handle enters RAII ownership immediately.
19        let raw_device = unsafe { raw::mlx_device_new() };
20        Self(raw_device)
21    }
22
23    /// Creates an owned device handle for one backend device type and index.
24    pub fn of_type(
25        device_type: raw::mlx_device_type,
26        device_index: i32,
27    ) -> Result<Self, MlxCError> {
28        let operation: &'static str = "create an MLX device handle";
29        // SAFETY: The device request takes plain values and the returned
30        // handle enters RAII ownership.
31        let raw_device = unsafe { raw::mlx_device_new_type(device_type, device_index) };
32        Self::from_live_raw(raw_device, operation)
33    }
34
35    /// The process's default device.
36    pub fn default_device() -> Result<Self, MlxCError> {
37        let operation: &'static str = "acquire the MLX default device";
38        let mut output = Self::empty();
39        // SAFETY: The output pointer is valid writable storage for one
40        // device handle.
41        let status = unsafe { raw::mlx_get_default_device(output.raw_mut()) };
42        check_status(status, operation)?;
43        output.require_populated(operation)?;
44        Ok(output)
45    }
46
47    /// Replaces the process's default device.
48    pub fn set_as_default(&self) -> Result<(), MlxCError> {
49        // SAFETY: The device handle is live.
50        let status = unsafe { raw::mlx_set_default_device(self.0) };
51        check_status(status, "set the MLX default device")
52    }
53
54    /// Copies the source device handle into this handle.
55    pub fn set(&mut self, source: &Self) -> Result<(), MlxCError> {
56        // SAFETY: Both handles are live and this call only replaces the
57        // owned device value.
58        let status = unsafe { raw::mlx_device_set(&mut self.0, source.0) };
59        check_status(status, "copy an MLX device handle")
60    }
61
62    /// Whether both handles reference the same device.
63    #[must_use]
64    pub fn equals(&self, other: &Self) -> bool {
65        // SAFETY: Both handles are live; equality is a pure query.
66        unsafe { raw::mlx_device_equal(self.0, other.0) }
67    }
68
69    /// The device's index within its backend.
70    pub fn index(&self) -> Result<i32, MlxCError> {
71        let operation: &'static str = "read the MLX device index";
72        let mut device_index: i32 = 0;
73        // SAFETY: `self` owns a live device and the output pointer is valid
74        // writable storage.
75        let status = unsafe { raw::mlx_device_get_index(&mut device_index, self.0) };
76        check_status(status, operation)?;
77        Ok(device_index)
78    }
79
80    /// The device's backend type.
81    pub fn device_type(&self) -> Result<raw::mlx_device_type, MlxCError> {
82        let operation: &'static str = "read the MLX device type";
83        let mut device_type = raw::mlx_device_type__MLX_CPU;
84        // SAFETY: `self` owns a live device and the output pointer is valid
85        // writable storage.
86        let status = unsafe { raw::mlx_device_get_type(&mut device_type, self.0) };
87        check_status(status, operation)?;
88        Ok(device_type)
89    }
90
91    /// Whether the backend device is available in this process.
92    pub fn is_available(&self) -> Result<bool, MlxCError> {
93        let operation: &'static str = "query MLX device availability";
94        let mut available = false;
95        // SAFETY: `self` owns a live device and the output pointer is valid
96        // writable storage.
97        let status = unsafe { raw::mlx_device_is_available(&mut available, self.0) };
98        check_status(status, operation)?;
99        Ok(available)
100    }
101
102    /// How many devices the backend exposes.
103    pub fn count_of_type(device_type: raw::mlx_device_type) -> Result<i32, MlxCError> {
104        let operation: &'static str = "count MLX devices";
105        let mut device_count: i32 = 0;
106        // SAFETY: The output pointer is valid writable storage.
107        let status = unsafe { raw::mlx_device_count(&mut device_count, device_type) };
108        check_status(status, operation)?;
109        Ok(device_count)
110    }
111
112    /// Renders the device description text.
113    pub fn to_string_lossy(&self) -> Result<String, MlxCError> {
114        let operation: &'static str = "describe an MLX device";
115        let mut output = crate::MlxString::empty();
116        // SAFETY: `self` owns a live device and the output pointer is valid
117        // writable storage for one string handle.
118        let status = unsafe { raw::mlx_device_tostring(output.raw_mut(), self.0) };
119        check_status(status, operation)?;
120        output.require_populated(operation)?;
121        Ok(output.to_string_lossy())
122    }
123
124    /// The device's key/value information table.
125    pub fn information(&self) -> Result<MlxDeviceInfo, MlxCError> {
126        let operation: &'static str = "read MLX device information";
127        let mut output = MlxDeviceInfo::empty();
128        // SAFETY: `self` owns a live device and the output pointer is valid
129        // writable storage for one device-info handle.
130        let status = unsafe { raw::mlx_device_info_get(output.raw_mut(), self.0) };
131        check_status(status, operation)?;
132        output.require_populated(operation)?;
133        Ok(output)
134    }
135
136    pub(crate) const fn raw(&self) -> raw::mlx_device {
137        self.0
138    }
139
140    pub(crate) fn raw_mut(&mut self) -> *mut raw::mlx_device {
141        &mut self.0
142    }
143
144    pub(crate) fn from_live_raw(
145        raw_device: raw::mlx_device,
146        operation: &'static str,
147    ) -> Result<Self, MlxCError> {
148        if raw_device.ctx.is_null() {
149            return Err(MlxCError {
150                operation,
151                description: "MLX returned an empty device handle".to_owned(),
152            });
153        }
154        Ok(Self(raw_device))
155    }
156
157    fn require_populated(&self, operation: &'static str) -> Result<(), MlxCError> {
158        if self.0.ctx.is_null() {
159            return Err(MlxCError {
160                operation,
161                description: "MLX returned an empty device handle".to_owned(),
162            });
163        }
164        Ok(())
165    }
166}
167
168impl Drop for MlxDeviceHandle {
169    fn drop(&mut self) {
170        // SAFETY: This owner releases its live device exactly once and never
171        // accesses the handle afterward.
172        unsafe {
173            raw::mlx_device_free(self.0);
174        }
175    }
176}
177
178/// Owned MLX device information table released exactly once.
179#[derive(Debug)]
180pub struct MlxDeviceInfo(raw::mlx_device_info);
181
182impl MlxDeviceInfo {
183    /// Creates an empty device-info handle for an output parameter.
184    pub(crate) fn empty() -> Self {
185        // SAFETY: The returned handle enters RAII ownership immediately.
186        let raw_info = unsafe { raw::mlx_device_info_new() };
187        Self(raw_info)
188    }
189
190    /// Whether the information table contains the key.
191    pub fn has_key(&self, key: &str) -> Result<bool, MlxCError> {
192        let operation: &'static str = "query MLX device information keys";
193        let key_argument = CString::new(key).map_err(|_| MlxCError {
194            operation,
195            description: "key contains an interior null byte".to_owned(),
196        })?;
197        let mut exists = false;
198        // SAFETY: `self` owns a live info table, the key outlives the call,
199        // and the output pointer is valid writable storage.
200        let status =
201            unsafe { raw::mlx_device_info_has_key(&mut exists, self.0, key_argument.as_ptr()) };
202        check_status(status, operation)?;
203        Ok(exists)
204    }
205
206    /// Whether the value stored for the key is a string.
207    pub fn is_string(&self, key: &str) -> Result<bool, MlxCError> {
208        let operation: &'static str = "query MLX device information value types";
209        let key_argument = CString::new(key).map_err(|_| MlxCError {
210            operation,
211            description: "key contains an interior null byte".to_owned(),
212        })?;
213        let mut is_string = false;
214        // SAFETY: `self` owns a live info table, the key outlives the call,
215        // and the output pointer is valid writable storage.
216        let status = unsafe {
217            raw::mlx_device_info_is_string(&mut is_string, self.0, key_argument.as_ptr())
218        };
219        check_status(status, operation)?;
220        Ok(is_string)
221    }
222
223    /// Copies the string value stored for the key.
224    pub fn string_value(&self, key: &str) -> Result<String, MlxCError> {
225        let operation: &'static str = "read an MLX device information string";
226        let key_argument = CString::new(key).map_err(|_| MlxCError {
227            operation,
228            description: "key contains an interior null byte".to_owned(),
229        })?;
230        let mut value_pointer: *const std::os::raw::c_char = std::ptr::null();
231        // SAFETY: `self` owns a live info table, the key outlives the call,
232        // and the output pointer is valid writable storage.
233        let status = unsafe {
234            raw::mlx_device_info_get_string(&mut value_pointer, self.0, key_argument.as_ptr())
235        };
236        check_status(status, operation)?;
237        if value_pointer.is_null() {
238            return Err(MlxCError {
239                operation,
240                description: "MLX returned a null device information string".to_owned(),
241            });
242        }
243        // SAFETY: MLX loans a null-terminated value owned by the live info
244        // table; the text is copied before this call returns.
245        let borrowed = unsafe { CStr::from_ptr(value_pointer) };
246        Ok(borrowed.to_string_lossy().into_owned())
247    }
248
249    /// Copies the size value stored for the key.
250    pub fn size_value(&self, key: &str) -> Result<usize, MlxCError> {
251        let operation: &'static str = "read an MLX device information size";
252        let key_argument = CString::new(key).map_err(|_| MlxCError {
253            operation,
254            description: "key contains an interior null byte".to_owned(),
255        })?;
256        let mut value: usize = 0;
257        // SAFETY: `self` owns a live info table, the key outlives the call,
258        // and the output pointer is valid writable storage.
259        let status =
260            unsafe { raw::mlx_device_info_get_size(&mut value, self.0, key_argument.as_ptr()) };
261        check_status(status, operation)?;
262        Ok(value)
263    }
264
265    /// Every key present in the information table.
266    pub fn keys(&self) -> Result<MlxVectorString, MlxCError> {
267        let operation: &'static str = "list MLX device information keys";
268        let mut output = MlxVectorString::empty();
269        // SAFETY: `self` owns a live info table and the output pointer is
270        // valid writable storage for one string vector handle.
271        let status = unsafe { raw::mlx_device_info_get_keys(output.raw_mut(), self.0) };
272        check_status(status, operation)?;
273        Ok(output)
274    }
275
276    pub(crate) fn raw_mut(&mut self) -> *mut raw::mlx_device_info {
277        &mut self.0
278    }
279
280    fn require_populated(&self, operation: &'static str) -> Result<(), MlxCError> {
281        if self.0.ctx.is_null() {
282            return Err(MlxCError {
283                operation,
284                description: "MLX returned an empty device info handle".to_owned(),
285            });
286        }
287        Ok(())
288    }
289}
290
291impl Drop for MlxDeviceInfo {
292    fn drop(&mut self) {
293        // SAFETY: This owner releases its live info table exactly once and
294        // never accesses the handle afterward.
295        unsafe {
296            raw::mlx_device_info_free(self.0);
297        }
298    }
299}