Skip to main content

mlx_c_rust/runtime/
stream_defaults.rs

1//! The complete MLX stream domain: streams, thread-local slots, and the
2//! process-wide stream registry.
3
4use crate::MlxCError;
5use crate::error::check_status;
6use crate::raw;
7use crate::types::device::MlxDeviceHandle;
8use crate::types::vectors::MlxVectorStream;
9
10/// Owned MLX stream handle used to preserve runtime thread affinity.
11#[derive(Debug)]
12pub struct MlxStream(raw::mlx_stream);
13
14impl MlxStream {
15    pub fn default_cpu() -> Result<Self, MlxCError> {
16        // SAFETY: The runtime error handler is installed and the returned
17        // stream is placed immediately under RAII ownership.
18        let raw_stream = unsafe { raw::mlx_default_cpu_stream_new() };
19        Self::from_raw(raw_stream, "acquire the default MLX CPU stream")
20    }
21
22    pub fn default_gpu() -> Result<Self, MlxCError> {
23        // SAFETY: The runtime error handler is installed and the returned
24        // stream is placed immediately under RAII ownership.
25        let raw_stream = unsafe { raw::mlx_default_gpu_stream_new() };
26        Self::from_raw(raw_stream, "acquire the default MLX GPU stream")
27    }
28
29    /// Renders the stream description text.
30    pub fn to_string_lossy(&self) -> Result<String, MlxCError> {
31        let operation: &'static str = "describe an MLX stream";
32        let mut output = crate::MlxString::empty();
33        // SAFETY: `self` owns a live stream and the output pointer is valid
34        // writable storage for one string handle.
35        let status = unsafe { raw::mlx_stream_tostring(output.raw_mut(), self.0) };
36        check_status(status, operation)?;
37        output.require_populated(operation)?;
38        Ok(output.to_string_lossy())
39    }
40
41    pub const fn raw(&self) -> raw::mlx_stream {
42        self.0
43    }
44
45    fn from_raw(raw_stream: raw::mlx_stream, operation: &'static str) -> Result<Self, MlxCError> {
46        if raw_stream.ctx.is_null() {
47            return Err(MlxCError {
48                operation,
49                description: "MLX returned an empty stream handle".to_owned(),
50            });
51        }
52        Ok(Self(raw_stream))
53    }
54
55    pub(crate) fn from_live_raw(
56        raw_stream: raw::mlx_stream,
57        operation: &'static str,
58    ) -> Result<Self, MlxCError> {
59        Self::from_raw(raw_stream, operation)
60    }
61}
62
63impl Drop for MlxStream {
64    fn drop(&mut self) {
65        // SAFETY: This owner releases its live stream handle exactly once.
66        unsafe {
67            raw::mlx_stream_free(self.0);
68        }
69    }
70}
71
72/// Per-thread MLX stream slot for one device.
73#[derive(Debug)]
74pub struct MlxStreamThreadLocal(raw::mlx_stream_thread_local);
75
76impl MlxStreamThreadLocal {
77    /// Creates the thread-local slot for one device's stream.
78    pub fn for_device(device: &MlxDeviceHandle) -> Result<Self, MlxCError> {
79        let operation: &'static str = "create an MLX thread-local stream slot";
80        // SAFETY: The device handle is live and the returned slot enters
81        // RAII ownership.
82        let raw_slot = unsafe { raw::mlx_stream_thread_local_new(device.raw()) };
83        if raw_slot.ctx.is_null() {
84            return Err(MlxCError {
85                operation,
86                description: "MLX returned an empty thread-local stream handle".to_owned(),
87            });
88        }
89        Ok(Self(raw_slot))
90    }
91
92    /// Copies the source slot handle into this slot.
93    pub fn set(&mut self, source: &Self) -> Result<(), MlxCError> {
94        // SAFETY: Both handles are live and this call only replaces the
95        // owned slot value.
96        let status = unsafe { raw::mlx_stream_thread_local_set(&mut self.0, source.0) };
97        check_status(status, "copy an MLX thread-local stream slot")
98    }
99
100    /// Copies the slot's current stream into an owned handle.
101    pub fn stream(&self) -> Result<MlxStream, MlxCError> {
102        let operation: &'static str = "read the MLX thread-local stream";
103        let mut raw_stream = raw::mlx_stream {
104            ctx: std::ptr::null_mut(),
105        };
106        // SAFETY: `self` owns a live slot and the output pointer is valid
107        // writable storage for one stream handle.
108        let status = unsafe { raw::mlx_stream_from_thread_local(&mut raw_stream, self.0) };
109        check_status(status, operation)?;
110        MlxStream::from_live_raw(raw_stream, operation)
111    }
112
113    /// Synchronizes the slot's current stream.
114    pub fn synchronize(&self) -> Result<(), MlxCError> {
115        // SAFETY: `self` owns a live slot handle.
116        let status = unsafe { raw::mlx_synchronize_thread_local(self.0) };
117        check_status(status, "synchronize the MLX thread-local stream")
118    }
119}
120
121impl Drop for MlxStreamThreadLocal {
122    fn drop(&mut self) {
123        // SAFETY: This owner releases its live slot exactly once and never
124        // accesses the handle afterward.
125        unsafe {
126            raw::mlx_stream_thread_local_free(self.0);
127        }
128    }
129}
130
131impl MlxStream {
132    /// Creates an owned stream on the given device.
133    pub fn on_device(device: &MlxDeviceHandle) -> Result<Self, MlxCError> {
134        // SAFETY: The device handle is live and the returned stream enters
135        // RAII ownership.
136        let raw_stream = unsafe { raw::mlx_stream_new_device(device.raw()) };
137        Self::from_raw(raw_stream, "create an MLX stream on a device")
138    }
139
140    /// Copies the source stream handle into this handle.
141    pub fn set(&mut self, source: &Self) -> Result<(), MlxCError> {
142        // SAFETY: Both handles are live and this call only replaces the
143        // owned stream value.
144        let status = unsafe { raw::mlx_stream_set(&mut self.0, source.0) };
145        check_status(status, "copy an MLX stream")
146    }
147
148    /// Whether both handles reference the same underlying stream.
149    #[must_use]
150    pub fn equals(&self, other: &Self) -> bool {
151        // SAFETY: Both handles are live; equality is a pure query.
152        unsafe { raw::mlx_stream_equal(self.0, other.0) }
153    }
154
155    /// The device this stream submits work to.
156    pub fn device(&self) -> Result<MlxDeviceHandle, MlxCError> {
157        let operation: &'static str = "read the MLX stream device";
158        let mut raw_device = raw::mlx_device {
159            ctx: std::ptr::null_mut(),
160        };
161        // SAFETY: `self` owns a live stream and the output pointer is valid
162        // writable storage for one device handle.
163        let status = unsafe { raw::mlx_stream_get_device(&mut raw_device, self.0) };
164        check_status(status, operation)?;
165        MlxDeviceHandle::from_live_raw(raw_device, operation)
166    }
167
168    /// The stream's index on its device.
169    pub fn index(&self) -> Result<i32, MlxCError> {
170        let operation: &'static str = "read the MLX stream index";
171        let mut stream_index: i32 = 0;
172        // SAFETY: `self` owns a live stream and the output pointer is valid
173        // writable storage.
174        let status = unsafe { raw::mlx_stream_get_index(&mut stream_index, self.0) };
175        check_status(status, operation)?;
176        Ok(stream_index)
177    }
178
179    /// Synchronizes this stream's pending work.
180    pub fn synchronize(&self) -> Result<(), MlxCError> {
181        // SAFETY: `self` owns a live stream handle.
182        let status = unsafe { raw::mlx_synchronize(self.0) };
183        check_status(status, "synchronize an MLX stream")
184    }
185
186    /// The device's default stream.
187    pub fn default_for_device(device: &MlxDeviceHandle) -> Result<Self, MlxCError> {
188        let operation: &'static str = "acquire the MLX device default stream";
189        let mut raw_stream = raw::mlx_stream {
190            ctx: std::ptr::null_mut(),
191        };
192        // SAFETY: The device handle is live and the output pointer is valid
193        // writable storage for one stream handle.
194        let status = unsafe { raw::mlx_get_default_stream(&mut raw_stream, device.raw()) };
195        check_status(status, operation)?;
196        Self::from_raw(raw_stream, operation)
197    }
198
199    /// Every stream created in this process.
200    pub fn all_streams() -> Result<MlxVectorStream, MlxCError> {
201        let operation: &'static str = "list the MLX streams";
202        let mut output = MlxVectorStream::empty();
203        // SAFETY: The output pointer is valid writable storage for one
204        // stream vector handle.
205        let status = unsafe { raw::mlx_get_streams(output.raw_mut()) };
206        check_status(status, operation)?;
207        Ok(output)
208    }
209}
210
211/// Frees every stream created in this process except the defaults in use.
212pub fn clear_streams() -> Result<(), MlxCError> {
213    // SAFETY: Clearing takes no inputs and reports status through the
214    // captured-error machinery.
215    let status = unsafe { raw::mlx_clear_streams() };
216    check_status(status, "clear the MLX streams")
217}
218
219/// Replaces the process's default stream.
220pub fn set_default_stream(stream: &MlxStream) -> Result<(), MlxCError> {
221    // SAFETY: The stream handle is live.
222    let status = unsafe { raw::mlx_set_default_stream(stream.raw()) };
223    check_status(status, "set the MLX default stream")
224}
225
226/// Synchronizes work across all devices' default streams.
227pub fn synchronize_all_defaults() -> Result<(), MlxCError> {
228    // SAFETY: Synchronization takes no inputs and reports status through the
229    // captured-error machinery.
230    let status = unsafe { raw::mlx_synchronize_default() };
231    check_status(status, "synchronize the MLX default streams")
232}