1use std::cell::RefCell;
12use std::os::raw::c_void;
13
14use crate::error::check_status;
15use crate::raw;
16use crate::types::maps::MlxMapStringToArray;
17use crate::{MlxArray, MlxArrayVector, MlxCError};
18
19pub(crate) fn owned_input_arrays(input: raw::mlx_vector_array) -> Result<Vec<MlxArray>, MlxCError> {
24 let element_count = unsafe { raw::mlx_vector_array_size(input) };
25 let mut elements = Vec::with_capacity(element_count);
26 for element_index in 0..element_count {
27 let mut element = MlxArray::empty();
28 let status = unsafe { raw::mlx_vector_array_get(element.raw_mut(), input, element_index) };
31 check_status(status, "read a closure input array")?;
32 elements.push(element);
33 }
34 Ok(elements)
35}
36
37pub(crate) fn write_output_arrays(
42 output: *mut raw::mlx_vector_array,
43 outputs: Vec<MlxArray>,
44) -> Result<(), MlxCError> {
45 let raw_outputs: Vec<raw::mlx_array> = outputs.iter().map(MlxArray::raw).collect();
46 let status =
49 unsafe { raw::mlx_vector_array_set_data(output, raw_outputs.as_ptr(), raw_outputs.len()) };
50 check_status(status, "write closure output arrays")
51}
52
53pub(crate) fn run_payload_closure<F, R>(
55 payload: *mut c_void,
56 operation: &'static str,
57 invoke: impl FnOnce(&mut F) -> R,
58) -> Option<R> {
59 let payload_cell = unsafe { &*(payload.cast::<RefCell<F>>()) };
60 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
61 let mut callback = payload_cell
62 .try_borrow_mut()
63 .expect("closure payload is not reentrant");
64 invoke(&mut callback)
65 }));
66 match outcome {
67 Ok(value) => Some(value),
68 Err(panic_payload) => {
69 crate::error::set_closure_error(MlxCError {
70 operation,
71 description: panic_message(panic_payload),
72 });
73 None
74 }
75 }
76}
77
78fn panic_message(panic_payload: Box<dyn std::any::Any + Send>) -> String {
79 panic_payload
80 .downcast_ref::<&str>()
81 .map(|message| (*message).to_owned())
82 .or_else(|| panic_payload.downcast_ref::<String>().cloned())
83 .unwrap_or_else(|| "closure panicked".to_owned())
84}
85
86#[derive(Debug)]
88pub struct MlxClosure(pub(crate) raw::mlx_closure);
89
90impl MlxClosure {
91 pub fn from_fn<F>(callback: F) -> Result<Self, MlxCError>
94 where
95 F: FnMut(&[MlxArray]) -> Result<Vec<MlxArray>, MlxCError> + 'static,
96 {
97 let operation: &'static str = "create an MLX closure from a Rust callable";
98 let payload = Box::into_raw(Box::new(RefCell::new(callback)));
99 let raw_closure = unsafe {
103 raw::mlx_closure_new_func_payload(
104 Some(vector_callback_trampoline::<F>),
105 payload.cast(),
106 Some(drop_payload::<F>),
107 )
108 };
109 let closure = Self(raw_closure);
110 closure.require_populated(operation)?;
111 Ok(closure)
112 }
113
114 pub fn empty() -> Self {
116 let raw_closure = unsafe { raw::mlx_closure_new() };
118 Self(raw_closure)
119 }
120
121 pub fn set(&mut self, source: &Self) -> Result<(), MlxCError> {
123 let status = unsafe { raw::mlx_closure_set(&mut self.0, source.0) };
126 check_status(status, "copy an MLX closure")
127 }
128
129 pub fn apply(&self, input: &MlxArrayVector) -> Result<MlxArrayVector, MlxCError> {
134 let operation: &'static str = "apply an MLX closure";
135 let mut output = MlxArrayVector::empty(operation)?;
136 let status = unsafe { raw::mlx_closure_apply(output.raw_mut(), self.0, input.raw()) };
139 check_status(status, operation)?;
140 Ok(output)
141 }
142
143 pub(crate) const fn raw(&self) -> raw::mlx_closure {
144 self.0
145 }
146
147 pub(crate) fn raw_mut(&mut self) -> *mut raw::mlx_closure {
148 &mut self.0
149 }
150
151 pub(crate) fn require_populated(&self, operation: &'static str) -> Result<(), MlxCError> {
152 if self.0.ctx.is_null() {
153 return Err(MlxCError {
154 operation,
155 description: "MLX returned an empty closure handle".to_owned(),
156 });
157 }
158 Ok(())
159 }
160}
161
162impl Drop for MlxClosure {
163 fn drop(&mut self) {
164 unsafe {
167 raw::mlx_closure_free(self.0);
168 }
169 }
170}
171
172unsafe extern "C" fn vector_callback_trampoline<F>(
173 output: *mut raw::mlx_vector_array,
174 input: raw::mlx_vector_array,
175 payload: *mut c_void,
176) -> i32
177where
178 F: FnMut(&[MlxArray]) -> Result<Vec<MlxArray>, MlxCError> + 'static,
179{
180 let Some(outputs) = run_payload_closure::<F, Result<Vec<MlxArray>, MlxCError>>(
181 payload,
182 "apply a Rust closure",
183 |callback| owned_input_arrays(input).and_then(|inputs| callback(&inputs)),
184 ) else {
185 return 1;
186 };
187 match outputs.and_then(|owned_outputs| write_output_arrays(output, owned_outputs)) {
188 Ok(()) => 0,
189 Err(failure) => {
190 crate::error::set_closure_error(failure);
191 1
192 }
193 }
194}
195
196pub(crate) unsafe extern "C" fn drop_payload<F>(payload: *mut c_void) {
197 if payload.is_null() {
198 return;
199 }
200 drop(unsafe { Box::from_raw(payload.cast::<RefCell<F>>()) });
203}
204
205#[derive(Debug)]
207pub struct MlxClosureKwargs(raw::mlx_closure_kwargs);
208
209impl MlxClosureKwargs {
210 pub fn from_fn<F>(callback: F) -> Result<Self, MlxCError>
213 where
214 F: FnMut(&[MlxArray], &MlxMapStringToArray) -> Result<Vec<MlxArray>, MlxCError> + 'static,
215 {
216 let operation: &'static str = "create an MLX kwargs closure from a Rust callable";
217 let payload = Box::into_raw(Box::new(RefCell::new(callback)));
218 let raw_closure = unsafe {
221 raw::mlx_closure_kwargs_new_func_payload(
222 Some(kwargs_callback_trampoline::<F>),
223 payload.cast(),
224 Some(drop_payload::<F>),
225 )
226 };
227 let closure = Self(raw_closure);
228 if closure.0.ctx.is_null() {
229 return Err(MlxCError {
230 operation,
231 description: "MLX returned an empty closure handle".to_owned(),
232 });
233 }
234 Ok(closure)
235 }
236
237 pub fn empty() -> Self {
239 let raw_closure = unsafe { raw::mlx_closure_kwargs_new() };
241 Self(raw_closure)
242 }
243
244 pub fn set(&mut self, source: &Self) -> Result<(), MlxCError> {
246 let status = unsafe { raw::mlx_closure_kwargs_set(&mut self.0, source.0) };
249 check_status(status, "copy an MLX kwargs closure")
250 }
251
252 pub fn apply(
257 &self,
258 input: &MlxArrayVector,
259 keyword_arguments: &MlxMapStringToArray,
260 ) -> Result<MlxArrayVector, MlxCError> {
261 let operation: &'static str = "apply an MLX kwargs closure";
262 let mut output = MlxArrayVector::empty(operation)?;
263 let status = unsafe {
266 raw::mlx_closure_kwargs_apply(
267 output.raw_mut(),
268 self.0,
269 input.raw(),
270 keyword_arguments.raw(),
271 )
272 };
273 check_status(status, operation)?;
274 Ok(output)
275 }
276}
277
278impl MlxClosureKwargs {
279 pub(crate) const fn raw(&self) -> raw::mlx_closure_kwargs {
280 self.0
281 }
282}
283
284impl Drop for MlxClosureKwargs {
285 fn drop(&mut self) {
286 unsafe {
289 raw::mlx_closure_kwargs_free(self.0);
290 }
291 }
292}
293
294unsafe extern "C" fn kwargs_callback_trampoline<F>(
295 output: *mut raw::mlx_vector_array,
296 input: raw::mlx_vector_array,
297 keyword_arguments: raw::mlx_map_string_to_array,
298 payload: *mut c_void,
299) -> i32
300where
301 F: FnMut(&[MlxArray], &MlxMapStringToArray) -> Result<Vec<MlxArray>, MlxCError> + 'static,
302{
303 let Some(outputs) = run_payload_closure::<F, Result<Vec<MlxArray>, MlxCError>>(
304 payload,
305 "apply a Rust kwargs closure",
306 |callback| {
307 owned_input_arrays(input).and_then(|inputs| {
308 let borrowed_keywords = MlxMapStringToArray::from_borrowed_raw(keyword_arguments);
309 callback(&inputs, &borrowed_keywords)
310 })
311 },
312 ) else {
313 return 1;
314 };
315 match outputs.and_then(|owned_outputs| write_output_arrays(output, owned_outputs)) {
316 Ok(()) => 0,
317 Err(failure) => {
318 crate::error::set_closure_error(failure);
319 1
320 }
321 }
322}
323
324#[derive(Debug)]
326pub struct MlxClosureValueAndGrad(raw::mlx_closure_value_and_grad);
327
328impl MlxClosureValueAndGrad {
329 pub fn from_fn<F>(callback: F) -> Result<Self, MlxCError>
332 where
333 F: FnMut(&[MlxArray]) -> Result<(Vec<MlxArray>, Vec<MlxArray>), MlxCError> + 'static,
334 {
335 let operation: &'static str = "create an MLX value-and-gradient closure";
336 let payload = Box::into_raw(Box::new(RefCell::new(callback)));
337 let raw_closure = unsafe {
340 raw::mlx_closure_value_and_grad_new_func_payload(
341 Some(value_and_grad_callback_trampoline::<F>),
342 payload.cast(),
343 Some(drop_payload::<F>),
344 )
345 };
346 let closure = Self(raw_closure);
347 if closure.0.ctx.is_null() {
348 return Err(MlxCError {
349 operation,
350 description: "MLX returned an empty closure handle".to_owned(),
351 });
352 }
353 Ok(closure)
354 }
355
356 pub fn empty() -> Self {
358 let raw_closure = unsafe { raw::mlx_closure_value_and_grad_new() };
360 Self(raw_closure)
361 }
362
363 pub fn set(&mut self, source: &Self) -> Result<(), MlxCError> {
365 let status = unsafe { raw::mlx_closure_value_and_grad_set(&mut self.0, source.0) };
368 check_status(status, "copy an MLX value-and-gradient closure")
369 }
370
371 pub fn apply(
376 &self,
377 input: &MlxArrayVector,
378 ) -> Result<(MlxArrayVector, MlxArrayVector), MlxCError> {
379 let operation: &'static str = "apply an MLX value-and-gradient closure";
380 let mut outputs = MlxArrayVector::empty(operation)?;
381 let mut gradients = MlxArrayVector::empty(operation)?;
382 let status = unsafe {
385 raw::mlx_closure_value_and_grad_apply(
386 outputs.raw_mut(),
387 gradients.raw_mut(),
388 self.0,
389 input.raw(),
390 )
391 };
392 check_status(status, operation)?;
393 Ok((outputs, gradients))
394 }
395}
396
397impl MlxClosureValueAndGrad {
398 pub(crate) fn raw_mut(&mut self) -> *mut raw::mlx_closure_value_and_grad {
399 &mut self.0
400 }
401}
402
403impl Drop for MlxClosureValueAndGrad {
404 fn drop(&mut self) {
405 unsafe {
408 raw::mlx_closure_value_and_grad_free(self.0);
409 }
410 }
411}
412
413unsafe extern "C" fn value_and_grad_callback_trampoline<F>(
414 outputs: *mut raw::mlx_vector_array,
415 gradients: *mut raw::mlx_vector_array,
416 input: raw::mlx_vector_array,
417 payload: *mut c_void,
418) -> i32
419where
420 F: FnMut(&[MlxArray]) -> Result<(Vec<MlxArray>, Vec<MlxArray>), MlxCError> + 'static,
421{
422 let Some(result) = run_payload_closure::<F, Result<(Vec<MlxArray>, Vec<MlxArray>), MlxCError>>(
423 payload,
424 "apply a Rust value-and-gradient closure",
425 |callback| owned_input_arrays(input).and_then(|inputs| callback(&inputs)),
426 ) else {
427 return 1;
428 };
429 let outcome = result.and_then(|(owned_outputs, owned_gradients)| {
430 write_output_arrays(outputs, owned_outputs)?;
431 write_output_arrays(gradients, owned_gradients)
432 });
433 match outcome {
434 Ok(()) => 0,
435 Err(failure) => {
436 crate::error::set_closure_error(failure);
437 1
438 }
439 }
440}