Skip to main content

mlx_rs/utils/
mod.rs

1//! Utility functions and types.
2
3use guard::Guarded;
4use mlx_sys::mlx_vector_array;
5
6use crate::error::{set_closure_error, set_closure_panic, StateProjectionError};
7use crate::module::ModuleParameters;
8use crate::{complex64, error::Exception, Array, FromNested};
9use std::collections::HashMap;
10use std::panic::{catch_unwind, AssertUnwindSafe};
11use std::{marker::PhantomData, rc::Rc};
12
13/// Success status code from the c binding
14pub(crate) const SUCCESS: i32 = 0;
15pub(crate) const FAILURE: i32 = 1;
16
17pub(crate) mod guard;
18pub(crate) mod io;
19
20pub(crate) fn resolve_index_signed_unchecked(index: i32, len: i32) -> i32 {
21    if index < 0 {
22        len.saturating_add(index)
23    } else {
24        index
25    }
26}
27
28pub(crate) fn resolve_index_unchecked(index: i32, len: usize) -> usize {
29    if index.is_negative() {
30        (len as i32 + index) as usize
31    } else {
32        index as usize
33    }
34}
35
36/// Helper method to convert an optional slice of axes to a Vec covering all axes.
37pub(crate) fn axes_or_default_to_all<'a>(axes: impl IntoOption<&'a [i32]>, ndim: i32) -> Vec<i32> {
38    match axes.into_option() {
39        Some(axes) => axes.to_vec(),
40        None => {
41            let axes: Vec<i32> = (0..ndim).collect();
42            axes
43        }
44    }
45}
46
47pub(crate) struct VectorArray {
48    c_vec: mlx_sys::mlx_vector_array,
49}
50
51impl VectorArray {
52    pub(crate) fn as_ptr(&self) -> mlx_sys::mlx_vector_array {
53        self.c_vec
54    }
55
56    pub(crate) fn try_from_iter(
57        iter: impl Iterator<Item = impl AsRef<Array>>,
58    ) -> Result<Self, Exception> {
59        VectorArray::try_from_op(|res| unsafe {
60            let mut status = SUCCESS;
61            for arr in iter {
62                status = mlx_sys::mlx_vector_array_append_value(*res, arr.as_ref().as_ptr());
63                if status != SUCCESS {
64                    return status;
65                }
66            }
67            status
68        })
69    }
70
71    pub(crate) fn try_into_values<T>(self) -> Result<T, Exception>
72    where
73        T: FromIterator<Array>,
74    {
75        unsafe {
76            let size = mlx_sys::mlx_vector_array_size(self.c_vec);
77            (0..size)
78                .map(|i| {
79                    Array::try_from_op(|res| mlx_sys::mlx_vector_array_get(res, self.c_vec, i))
80                })
81                .collect::<Result<T, Exception>>()
82        }
83    }
84}
85
86impl Drop for VectorArray {
87    fn drop(&mut self) {
88        let status = unsafe { mlx_sys::mlx_vector_array_free(self.c_vec) };
89        debug_assert_eq!(status, SUCCESS);
90    }
91}
92
93/// A helper trait that is just like `Into<Option<T>>` but improves ergonomics by allowing
94/// implicit conversion from &[T; N] to &[T].
95pub trait IntoOption<T> {
96    /// Convert into an [`Option`].
97    fn into_option(self) -> Option<T>;
98}
99
100impl<T> IntoOption<T> for Option<T> {
101    fn into_option(self) -> Option<T> {
102        self
103    }
104}
105
106impl<T> IntoOption<T> for T {
107    fn into_option(self) -> Option<T> {
108        Some(self)
109    }
110}
111
112impl<'a, T, const N: usize> IntoOption<&'a [T]> for &'a [T; N] {
113    fn into_option(self) -> Option<&'a [T]> {
114        Some(self)
115    }
116}
117
118impl<'a, T> IntoOption<&'a [T]> for &'a Vec<T> {
119    fn into_option(self) -> Option<&'a [T]> {
120        Some(self)
121    }
122}
123
124/// A trait for a scalar or an array.
125pub trait ScalarOrArray<'a> {
126    /// The reference type of the array.
127    type Array: AsRef<Array> + 'a;
128
129    /// Convert to an owned or reference array.
130    fn into_owned_or_ref_array(self) -> Self::Array;
131}
132
133impl ScalarOrArray<'_> for Array {
134    type Array = Array;
135
136    fn into_owned_or_ref_array(self) -> Array {
137        self
138    }
139}
140
141impl<'a> ScalarOrArray<'a> for &'a Array {
142    type Array = &'a Array;
143
144    // TODO: clippy would complain about `as_array`. Is there a better name?
145    fn into_owned_or_ref_array(self) -> &'a Array {
146        self
147    }
148}
149
150impl ScalarOrArray<'static> for bool {
151    type Array = Array;
152
153    fn into_owned_or_ref_array(self) -> Array {
154        Array::from_bool(self)
155    }
156}
157
158impl ScalarOrArray<'static> for i32 {
159    type Array = Array;
160
161    fn into_owned_or_ref_array(self) -> Array {
162        Array::from_int(self)
163    }
164}
165
166impl ScalarOrArray<'static> for f32 {
167    type Array = Array;
168
169    fn into_owned_or_ref_array(self) -> Array {
170        Array::from_f32(self)
171    }
172}
173
174// TODO: this is bugged right now. See https://github.com/ml-explore/mlx/issues/1994
175// impl ScalarOrArray<'static> for f64 {
176//     type Array = Array;
177
178//     fn into_owned_or_ref_array(self) -> Array {
179//         Array::from_f64(self)
180//     }
181// }
182
183impl ScalarOrArray<'static> for complex64 {
184    type Array = Array;
185
186    fn into_owned_or_ref_array(self) -> Array {
187        Array::from_complex(self)
188    }
189}
190
191impl<T> ScalarOrArray<'static> for T
192where
193    Array: FromNested<T>,
194{
195    type Array = Array;
196
197    fn into_owned_or_ref_array(self) -> Array {
198        Array::from_nested(self)
199    }
200}
201
202#[derive(Debug)]
203pub(crate) struct Closure<'a> {
204    c_closure: mlx_sys::mlx_closure,
205    lt_marker: PhantomData<&'a ()>,
206}
207
208impl<'a> Closure<'a> {
209    pub(crate) fn as_ptr(&self) -> mlx_sys::mlx_closure {
210        self.c_closure
211    }
212
213    pub(crate) fn new<F>(closure: F) -> Self
214    where
215        F: FnMut(&[Array]) -> Vec<Array> + 'a,
216    {
217        let c_closure = new_mlx_closure(closure);
218        Self {
219            c_closure,
220            lt_marker: PhantomData,
221        }
222    }
223
224    pub(crate) fn new_fallible<F>(closure: F) -> Self
225    where
226        F: FnMut(&[Array]) -> Result<Vec<Array>, Exception> + 'a,
227    {
228        let c_closure = new_mlx_fallible_closure(closure);
229        Self {
230            c_closure,
231            lt_marker: PhantomData,
232        }
233    }
234}
235
236impl Drop for Closure<'_> {
237    fn drop(&mut self) {
238        let status = unsafe { mlx_sys::mlx_closure_free(self.c_closure) };
239        if !std::thread::panicking() {
240            crate::error::resume_closure_panic();
241        }
242        debug_assert_eq!(status, SUCCESS);
243    }
244}
245
246/// Helper method to create a mlx_closure from a Rust closure.
247fn new_mlx_closure<'a, F>(closure: F) -> mlx_sys::mlx_closure
248where
249    F: FnMut(&[Array]) -> Vec<Array> + 'a,
250{
251    // Box the closure to keep it on the heap
252    let boxed = Box::new(closure);
253
254    // Create a raw pointer from the Box, transferring ownership to C
255    let raw = Box::into_raw(boxed);
256    let payload = raw as *mut std::ffi::c_void;
257
258    unsafe {
259        mlx_sys::mlx_closure_new_func_payload(
260            Some(trampoline::<F>),
261            payload,
262            Some(closure_dtor::<F>),
263        )
264    }
265}
266
267fn new_mlx_fallible_closure<'a, F>(closure: F) -> mlx_sys::mlx_closure
268where
269    F: FnMut(&[Array]) -> Result<Vec<Array>, Exception> + 'a,
270{
271    let boxed = Box::new(closure);
272    let raw = Box::into_raw(boxed);
273    let payload = raw as *mut std::ffi::c_void;
274
275    unsafe {
276        mlx_sys::mlx_closure_new_func_payload(
277            Some(trampoline_fallible::<F>),
278            payload,
279            Some(closure_dtor::<F>),
280        )
281    }
282}
283
284/// Function to create a new (+1 reference) mlx_vector_array from a vector of Array
285fn new_mlx_vector_array(arrays: Vec<Array>) -> mlx_sys::mlx_vector_array {
286    unsafe {
287        let result = mlx_sys::mlx_vector_array_new();
288        let ctx_ptrs: Vec<mlx_sys::mlx_array> = arrays.iter().map(|array| array.as_ptr()).collect();
289        mlx_sys::mlx_vector_array_append_data(result, ctx_ptrs.as_ptr(), arrays.len());
290        result
291    }
292}
293
294fn mlx_vector_array_values(
295    vector_array: mlx_sys::mlx_vector_array,
296) -> Result<Vec<Array>, Exception> {
297    unsafe {
298        let size = mlx_sys::mlx_vector_array_size(vector_array);
299        (0..size)
300            .map(|index| {
301                Array::try_from_op(|res| mlx_sys::mlx_vector_array_get(res, vector_array, index))
302            })
303            .collect()
304    }
305}
306
307extern "C" fn trampoline<'a, F>(
308    ret: *mut mlx_vector_array,
309    vector_array: mlx_vector_array,
310    payload: *mut std::ffi::c_void,
311) -> i32
312where
313    F: FnMut(&[Array]) -> Vec<Array> + 'a,
314{
315    let result = catch_unwind(AssertUnwindSafe(|| unsafe {
316        let raw_closure: *mut F = payload as *mut _;
317        let closure = &mut *raw_closure;
318        let arrays = match mlx_vector_array_values(vector_array) {
319            Ok(arrays) => arrays,
320            Err(_) => return None,
321        };
322        let result = closure(&arrays);
323
324        *ret = new_mlx_vector_array(result);
325        Some(())
326    }));
327
328    match result {
329        Ok(Some(())) => SUCCESS,
330        Ok(None) => FAILURE,
331        Err(payload) => {
332            set_closure_panic(payload);
333            FAILURE
334        }
335    }
336}
337
338extern "C" fn trampoline_fallible<'a, F>(
339    ret: *mut mlx_vector_array,
340    vector_array: mlx_vector_array,
341    payload: *mut std::ffi::c_void,
342) -> i32
343where
344    F: FnMut(&[Array]) -> Result<Vec<Array>, Exception> + 'a,
345{
346    let result = catch_unwind(AssertUnwindSafe(|| unsafe {
347        let raw_closure: *mut F = payload as *mut _;
348        let closure = &mut *raw_closure;
349        let arrays = match mlx_vector_array_values(vector_array) {
350            Ok(arrays) => arrays,
351            Err(e) => {
352                set_closure_error(e);
353                return FAILURE;
354            }
355        };
356        let result = closure(&arrays);
357
358        match result {
359            Ok(result) => {
360                *ret = new_mlx_vector_array(result);
361                SUCCESS
362            }
363            Err(err) => {
364                set_closure_error(err);
365                FAILURE
366            }
367        }
368    }));
369
370    match result {
371        Ok(status) => status,
372        Err(payload) => {
373            set_closure_panic(payload);
374            FAILURE
375        }
376    }
377}
378
379// extern "C" fn noop_dtor(_data: *mut std::ffi::c_void) {}
380
381extern "C" fn closure_dtor<F>(payload: *mut std::ffi::c_void) {
382    if payload.is_null() {
383        return;
384    }
385    let result = catch_unwind(AssertUnwindSafe(|| unsafe {
386        drop(Box::from_raw(payload as *mut F));
387    }));
388    if let Err(payload) = result {
389        set_closure_panic(payload);
390    }
391}
392
393pub(crate) fn get_mut_or_insert_with<'a, T>(
394    map: &'a mut HashMap<Rc<str>, T>,
395    key: &Rc<str>,
396    f: impl FnOnce() -> T,
397) -> &'a mut T {
398    if !map.contains_key(key) {
399        map.insert(key.clone(), f());
400    }
401
402    map.get_mut(key).unwrap()
403}
404
405#[derive(Debug)]
406enum ProjectedSlot<'a> {
407    Required(&'a mut Array),
408    Optional(&'a mut Option<Array>),
409}
410
411impl ProjectedSlot<'_> {
412    fn value(&self) -> Option<&Array> {
413        match self {
414            Self::Required(value) => Some(value),
415            Self::Optional(value) => value.as_ref(),
416        }
417    }
418
419    fn value_mut(&mut self) -> Option<&mut Array> {
420        match self {
421            Self::Required(value) => Some(value),
422            Self::Optional(value) => value.as_mut(),
423        }
424    }
425
426    fn restore(&mut self, key: &Rc<str>, value: Option<Array>) -> Result<(), StateProjectionError> {
427        match (self, value) {
428            (Self::Required(target), Some(value)) => {
429                **target = value;
430                Ok(())
431            }
432            (Self::Required(_), None) => {
433                Err(StateProjectionError::RequiredSlotAbsent(key.to_string()))
434            }
435            (Self::Optional(target), value) => {
436                **target = value;
437                Ok(())
438            }
439        }
440    }
441
442    fn reset(&mut self) -> Result<(), StateProjectionError> {
443        match self {
444            Self::Required(value) => {
445                **value = crate::ops::zeros_like(&**value)?;
446            }
447            Self::Optional(Some(value)) => {
448                *value = crate::ops::zeros_like(&*value)?;
449            }
450            Self::Optional(None) => {}
451        }
452        Ok(())
453    }
454}
455
456impl<'a> ProjectedSlot<'a> {
457    fn into_value(self) -> Option<&'a Array> {
458        match self {
459            Self::Required(value) => Some(value),
460            Self::Optional(value) => value.as_ref(),
461        }
462    }
463
464    fn into_value_mut(self) -> Option<&'a mut Array> {
465        match self {
466            Self::Required(value) => Some(value),
467            Self::Optional(value) => value.as_mut(),
468        }
469    }
470}
471
472#[derive(Debug)]
473struct ProjectedEntry<'a> {
474    key: Rc<str>,
475    slot: ProjectedSlot<'a>,
476}
477
478/// A stable, keyed declaration of mutable array state.
479///
480/// Required and optional slots are declared once. All derived views use the same sorted keys and
481/// preserve whether every optional slot is present.
482#[derive(Debug)]
483pub struct StateProjection<'a> {
484    entries: Vec<ProjectedEntry<'a>>,
485}
486
487impl<'a> StateProjection<'a> {
488    /// Create an empty projection.
489    pub fn new() -> Self {
490        Self {
491            entries: Vec::new(),
492        }
493    }
494
495    /// Declare a required keyed slot.
496    pub fn required(
497        &mut self,
498        key: impl Into<Rc<str>>,
499        value: &'a mut Array,
500    ) -> Result<(), StateProjectionError> {
501        self.insert(key.into(), ProjectedSlot::Required(value))
502    }
503
504    /// Declare an optional keyed slot, retaining its key when the value is absent.
505    pub fn optional(
506        &mut self,
507        key: impl Into<Rc<str>>,
508        value: &'a mut Option<Array>,
509    ) -> Result<(), StateProjectionError> {
510        self.insert(key.into(), ProjectedSlot::Optional(value))
511    }
512
513    fn insert(
514        &mut self,
515        key: Rc<str>,
516        slot: ProjectedSlot<'a>,
517    ) -> Result<(), StateProjectionError> {
518        match self.entries.binary_search_by(|entry| entry.key.cmp(&key)) {
519            Ok(_) => Err(StateProjectionError::DuplicateKey(key.to_string())),
520            Err(index) => {
521                self.entries.insert(index, ProjectedEntry { key, slot });
522                Ok(())
523            }
524        }
525    }
526
527    fn extend_prefixed(
528        &mut self,
529        prefix: &str,
530        projection: StateProjection<'a>,
531    ) -> Result<(), StateProjectionError> {
532        for entry in projection.entries {
533            self.insert(Rc::from(format!("{prefix}{}", entry.key)), entry.slot)?;
534        }
535        Ok(())
536    }
537
538    /// Return the number of declared slots, including absent optional slots.
539    pub fn len(&self) -> usize {
540        self.entries.len()
541    }
542
543    /// Return whether no slots are declared.
544    pub fn is_empty(&self) -> bool {
545        self.entries.is_empty()
546    }
547
548    /// Return the number of currently present arrays.
549    pub fn present_len(&self) -> usize {
550        self.entries
551            .iter()
552            .filter(|entry| entry.slot.value().is_some())
553            .count()
554    }
555
556    /// Traverse present arrays in stable key order.
557    pub fn values(&self) -> impl Iterator<Item = &Array> {
558        self.entries.iter().filter_map(|entry| entry.slot.value())
559    }
560
561    /// Traverse present arrays mutably in stable key order.
562    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Array> + use<'_, 'a> {
563        self.entries
564            .iter_mut()
565            .filter_map(|entry| entry.slot.value_mut())
566    }
567
568    /// Traverse every key and optional-presence tag in stable key order.
569    pub fn iter(&self) -> impl Iterator<Item = (&str, Option<&Array>)> {
570        self.entries
571            .iter()
572            .map(|entry| (entry.key.as_ref(), entry.slot.value()))
573    }
574
575    /// Traverse every key and mutable optional-presence tag in stable key order.
576    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, Option<&mut Array>)> + use<'_, 'a> {
577        self.entries
578            .iter_mut()
579            .map(|entry| (entry.key.as_ref(), entry.slot.value_mut()))
580    }
581
582    /// Consume the projection into present immutable entries in stable key order.
583    pub fn into_entries(self) -> impl Iterator<Item = (Rc<str>, &'a Array)> {
584        self.entries
585            .into_iter()
586            .filter_map(|entry| entry.slot.into_value().map(|value| (entry.key, value)))
587    }
588
589    /// Consume the projection into present mutable entries in stable key order.
590    pub fn into_entries_mut(self) -> impl Iterator<Item = (Rc<str>, &'a mut Array)> {
591        self.entries
592            .into_iter()
593            .filter_map(|entry| entry.slot.into_value_mut().map(|value| (entry.key, value)))
594    }
595
596    /// Capture every declared key and optional-presence tag.
597    pub fn snapshot(&self) -> StateSnapshot {
598        StateSnapshot {
599            entries: self
600                .entries
601                .iter()
602                .map(|entry| (entry.key.clone(), entry.slot.value().cloned()))
603                .collect(),
604        }
605    }
606
607    /// Derive the key, presence, dtype, and shape layout.
608    pub fn layout(&self) -> Vec<StateLayoutEntry> {
609        self.entries
610            .iter()
611            .map(|entry| StateLayoutEntry {
612                key: entry.key.clone(),
613                dtype: entry.slot.value().map(Array::dtype),
614                shape: entry.slot.value().map(|array| array.shape().to_vec()),
615            })
616            .collect()
617    }
618
619    /// Restore a keyed snapshot.
620    ///
621    /// When `reset_new` is true, slots created after the snapshot are retained and zeroed. All
622    /// other keys and optional-presence tags are restored exactly.
623    pub fn restore(
624        &mut self,
625        snapshot: StateSnapshot,
626        reset_new: bool,
627    ) -> Result<(), StateProjectionError> {
628        let mut saved = snapshot.entries.into_iter().collect::<HashMap<_, _>>();
629        for entry in &mut self.entries {
630            if let Some(value) = saved.remove(&entry.key) {
631                entry.slot.restore(&entry.key, value)?;
632            } else if reset_new {
633                entry.slot.reset()?;
634            } else {
635                return Err(StateProjectionError::MissingKey(entry.key.to_string()));
636            }
637        }
638        if !saved.is_empty() {
639            let mut keys = saved
640                .into_keys()
641                .map(|key| key.to_string())
642                .collect::<Vec<_>>();
643            keys.sort();
644            return Err(StateProjectionError::UnknownKeys(keys));
645        }
646        Ok(())
647    }
648}
649
650impl Default for StateProjection<'_> {
651    fn default() -> Self {
652        Self::new()
653    }
654}
655
656/// A presence-preserving snapshot produced by [`StateProjection`].
657#[derive(Debug, Clone)]
658pub struct StateSnapshot {
659    entries: Vec<(Rc<str>, Option<Array>)>,
660}
661
662impl StateSnapshot {
663    /// Return the number of declared slots, including absent optional slots.
664    pub fn len(&self) -> usize {
665        self.entries.len()
666    }
667
668    /// Return whether no slots are declared.
669    pub fn is_empty(&self) -> bool {
670        self.entries.is_empty()
671    }
672
673    /// Traverse keys and present values in stable key order.
674    pub fn iter(&self) -> impl Iterator<Item = (&str, Option<&Array>)> {
675        self.entries
676            .iter()
677            .map(|(key, value)| (key.as_ref(), value.as_ref()))
678    }
679
680    pub(crate) fn present_values(&self) -> impl Iterator<Item = &Array> {
681        self.entries.iter().filter_map(|(_, value)| value.as_ref())
682    }
683
684    pub(crate) fn layout(&self) -> Vec<StateLayoutEntry> {
685        self.entries
686            .iter()
687            .map(|(key, value)| StateLayoutEntry::new(key.clone(), value.as_ref()))
688            .collect()
689    }
690
691    pub(crate) fn from_layout_and_values(
692        layout: &[StateLayoutEntry],
693        values: &[Array],
694    ) -> Result<Self, StateProjectionError> {
695        let expected = layout.iter().filter(|entry| entry.is_present()).count();
696        if values.len() != expected {
697            return Err(StateProjectionError::Cardinality {
698                expected,
699                actual: values.len(),
700            });
701        }
702        let mut values = values.iter();
703        let entries = layout
704            .iter()
705            .map(|entry| {
706                let value = entry.is_present().then(|| values.next().unwrap().clone());
707                (entry.key.clone(), value)
708            })
709            .collect();
710        Ok(Self { entries })
711    }
712}
713
714/// One entry in the compiled layout derived from a [`StateProjection`].
715#[derive(Debug, Clone, PartialEq, Eq)]
716pub struct StateLayoutEntry {
717    key: Rc<str>,
718    dtype: Option<crate::Dtype>,
719    shape: Option<Vec<i32>>,
720}
721
722impl StateLayoutEntry {
723    fn new(key: Rc<str>, value: Option<&Array>) -> Self {
724        Self {
725            key,
726            dtype: value.map(Array::dtype),
727            shape: value.map(|array| array.shape().to_vec()),
728        }
729    }
730
731    /// Return the stable state key.
732    pub fn key(&self) -> &str {
733        &self.key
734    }
735
736    /// Return whether the slot is present.
737    pub fn is_present(&self) -> bool {
738        self.dtype.is_some()
739    }
740
741    /// Return the dtype when the slot is present.
742    pub fn dtype(&self) -> Option<crate::Dtype> {
743        self.dtype
744    }
745
746    /// Return the shape when the slot is present.
747    pub fn shape(&self) -> Option<&[i32]> {
748        self.shape.as_deref()
749    }
750}
751
752/// A type whose mutable arrays are declared by one keyed projection.
753pub trait Updatable {
754    /// Declare all required and optional state slots.
755    fn state_projection(&mut self) -> Result<StateProjection<'_>, StateProjectionError>;
756}
757
758impl<T> Updatable for T
759where
760    T: ModuleParameters,
761{
762    fn state_projection(&mut self) -> Result<StateProjection<'_>, StateProjectionError> {
763        let mut projection = StateProjection::new();
764        for (key, value) in self.parameters_mut().flatten() {
765            projection.required(key, value)?;
766        }
767        Ok(projection)
768    }
769}
770
771impl<T1, T2> Updatable for (T1, T2)
772where
773    T1: Updatable,
774    T2: Updatable,
775{
776    fn state_projection(&mut self) -> Result<StateProjection<'_>, StateProjectionError> {
777        let (first, second) = self;
778        let first = first.state_projection()?;
779        let second = second.state_projection()?;
780        let mut projection = StateProjection::new();
781        projection.extend_prefixed("0.", first)?;
782        projection.extend_prefixed("1.", second)?;
783        Ok(projection)
784    }
785}
786
787impl Updatable for Vec<Array> {
788    fn state_projection(&mut self) -> Result<StateProjection<'_>, StateProjectionError> {
789        let mut projection = StateProjection::new();
790        for (index, value) in self.iter_mut().enumerate() {
791            projection.required(index.to_string(), value)?;
792        }
793        Ok(projection)
794    }
795}
796
797/// Helper type to represent either a single value or a pair of values.
798#[derive(Debug, Clone, Copy, PartialEq, Eq)]
799pub enum SingleOrPair<T = i32> {
800    /// Single value.
801    Single(T),
802
803    /// Pair of values.
804    Pair(T, T),
805}
806
807impl<T: Clone> SingleOrPair<T> {
808    /// Returns the first value.
809    pub fn first(&self) -> T {
810        match self {
811            SingleOrPair::Single(v) => v.clone(),
812            SingleOrPair::Pair(v1, _) => v1.clone(),
813        }
814    }
815
816    /// Returns the second value.
817    pub fn second(&self) -> T {
818        match self {
819            SingleOrPair::Single(v) => v.clone(),
820            SingleOrPair::Pair(_, v2) => v2.clone(),
821        }
822    }
823}
824
825impl<T> From<T> for SingleOrPair<T> {
826    fn from(value: T) -> Self {
827        SingleOrPair::Single(value)
828    }
829}
830
831impl<T> From<(T, T)> for SingleOrPair<T> {
832    fn from(value: (T, T)) -> Self {
833        SingleOrPair::Pair(value.0, value.1)
834    }
835}
836
837impl<T: Clone> From<SingleOrPair<T>> for (T, T) {
838    fn from(value: SingleOrPair<T>) -> Self {
839        match value {
840            SingleOrPair::Single(v) => (v.clone(), v),
841            SingleOrPair::Pair(v1, v2) => (v1, v2),
842        }
843    }
844}
845
846/// Helper type to represent either a single value or a triple of values.
847#[derive(Debug, Clone, Copy, PartialEq, Eq)]
848pub enum SingleOrTriple<T = i32> {
849    /// Single value.
850    Single(T),
851
852    /// Triple of values.
853    Triple(T, T, T),
854}
855
856impl<T: Clone> SingleOrTriple<T> {
857    /// Returns the first value.
858    pub fn first(&self) -> T {
859        match self {
860            SingleOrTriple::Single(v) => v.clone(),
861            SingleOrTriple::Triple(v1, _, _) => v1.clone(),
862        }
863    }
864
865    /// Returns the second value.
866    pub fn second(&self) -> T {
867        match self {
868            SingleOrTriple::Single(v) => v.clone(),
869            SingleOrTriple::Triple(_, v2, _) => v2.clone(),
870        }
871    }
872
873    /// Returns the third value.
874    pub fn third(&self) -> T {
875        match self {
876            SingleOrTriple::Single(v) => v.clone(),
877            SingleOrTriple::Triple(_, _, v3) => v3.clone(),
878        }
879    }
880}
881
882impl<T> From<T> for SingleOrTriple<T> {
883    fn from(value: T) -> Self {
884        SingleOrTriple::Single(value)
885    }
886}
887
888impl<T> From<(T, T, T)> for SingleOrTriple<T> {
889    fn from(value: (T, T, T)) -> Self {
890        SingleOrTriple::Triple(value.0, value.1, value.2)
891    }
892}
893
894impl<T: Clone> From<SingleOrTriple<T>> for (T, T, T) {
895    fn from(value: SingleOrTriple<T>) -> Self {
896        match value {
897            SingleOrTriple::Single(v) => (v.clone(), v.clone(), v),
898            SingleOrTriple::Triple(v1, v2, v3) => (v1, v2, v3),
899        }
900    }
901}
902
903/// Helper type to represent either a single value or a vector of values.
904#[derive(Debug, Clone, PartialEq, Eq)]
905pub enum SingleOrVec<T> {
906    /// Single value.
907    Single(T),
908
909    /// Vector of values.
910    Vec(Vec<T>),
911}
912
913impl<T> From<T> for SingleOrVec<T> {
914    fn from(value: T) -> Self {
915        SingleOrVec::Single(value)
916    }
917}
918
919impl<T> From<Vec<T>> for SingleOrVec<T> {
920    fn from(value: Vec<T>) -> Self {
921        SingleOrVec::Vec(value)
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928    use std::any::Any;
929    use std::process::Command;
930
931    const PANIC_CHILD: &str = "MLX_RS_TRAMPOLINE_PANIC_CHILD";
932    const DROP_DURING_UNWIND_CHILD: &str = "MLX_RS_DROP_DURING_UNWIND_CHILD";
933
934    struct ProjectedState {
935        required: Array,
936        optional: Option<Array>,
937    }
938
939    impl Updatable for ProjectedState {
940        fn state_projection(&mut self) -> Result<StateProjection<'_>, StateProjectionError> {
941            let mut projection = StateProjection::new();
942            projection.optional("z.optional", &mut self.optional)?;
943            projection.required("a.required", &mut self.required)?;
944            Ok(projection)
945        }
946    }
947
948    #[test]
949    fn state_projection_preserves_keys_and_optional_presence() {
950        crate::with_device(crate::Device::cpu(), || {
951            let mut state = ProjectedState {
952                required: Array::from_int(3),
953                optional: None,
954            };
955
956            let snapshot = state.state_projection().unwrap().snapshot();
957            let layout = state.state_projection().unwrap().layout();
958            assert_eq!(
959                layout.iter().map(StateLayoutEntry::key).collect::<Vec<_>>(),
960                vec!["a.required", "z.optional"]
961            );
962            assert!(layout[0].is_present());
963            assert!(!layout[1].is_present());
964
965            state.optional = Some(Array::from_int(9));
966            state
967                .state_projection()
968                .unwrap()
969                .restore(snapshot, false)
970                .unwrap();
971            assert!(state.optional.is_none());
972            assert_eq!(state.required.item_exact::<i32>(), 3);
973        });
974    }
975
976    #[test]
977    fn closure_trampoline_panics_resume_in_rust() {
978        if std::env::var_os(PANIC_CHILD).is_some() {
979            run_trampoline_panic_child();
980            return;
981        }
982
983        let output = Command::new(std::env::current_exe().unwrap())
984            .args([
985                "--exact",
986                "utils::tests::closure_trampoline_panics_resume_in_rust",
987                "--nocapture",
988                "--test-threads=1",
989            ])
990            .env(PANIC_CHILD, "1")
991            .output()
992            .unwrap();
993
994        assert!(
995            output.status.success(),
996            "child status: {:?}\nstdout:\n{}\nstderr:\n{}",
997            output.status,
998            String::from_utf8_lossy(&output.stdout),
999            String::from_utf8_lossy(&output.stderr)
1000        );
1001    }
1002
1003    #[test]
1004    fn closure_drop_during_unwind_preserves_pending_panic() {
1005        if std::env::var_os(DROP_DURING_UNWIND_CHILD).is_some() {
1006            run_drop_during_unwind_child();
1007            return;
1008        }
1009
1010        let output = Command::new(std::env::current_exe().unwrap())
1011            .args([
1012                "--exact",
1013                "utils::tests::closure_drop_during_unwind_preserves_pending_panic",
1014                "--nocapture",
1015                "--test-threads=1",
1016            ])
1017            .env(DROP_DURING_UNWIND_CHILD, "1")
1018            .output()
1019            .unwrap();
1020
1021        assert!(
1022            output.status.success(),
1023            "child status: {:?}\nstdout:\n{}\nstderr:\n{}",
1024            output.status,
1025            String::from_utf8_lossy(&output.stdout),
1026            String::from_utf8_lossy(&output.stderr)
1027        );
1028    }
1029
1030    fn run_drop_during_unwind_child() {
1031        let payload = catch_unwind(AssertUnwindSafe(|| {
1032            let _closure = Closure::new(|_| Vec::new());
1033            set_closure_panic(Box::new("pending closure panic"));
1034            panic!("unrelated unwind");
1035        }))
1036        .expect_err("the unrelated panic should remain catchable");
1037        assert_eq!(panic_message(payload).as_deref(), Some("unrelated unwind"));
1038        assert_captured_panic("pending closure panic");
1039    }
1040
1041    fn run_trampoline_panic_child() {
1042        type Infallible = fn(&[Array]) -> Vec<Array>;
1043        let payload = Box::into_raw(Box::new(panic_infallible as Infallible)).cast();
1044        let input = unsafe { mlx_sys::mlx_vector_array_new() };
1045        let mut output = unsafe { mlx_sys::mlx_vector_array_new() };
1046        let status = trampoline::<Infallible>(&mut output, input, payload);
1047        assert_eq!(status, FAILURE);
1048        assert_captured_panic("infallible trampoline panic");
1049        closure_dtor::<Infallible>(payload);
1050        unsafe {
1051            mlx_sys::mlx_vector_array_free(input);
1052            mlx_sys::mlx_vector_array_free(output);
1053        }
1054
1055        let payload = Box::into_raw(Box::new(PanicOnDrop)).cast();
1056        closure_dtor::<PanicOnDrop>(payload);
1057        assert_captured_panic("closure destructor panic");
1058
1059        type Fallible = fn(&[Array]) -> Result<Vec<Array>, Exception>;
1060        let payload = Box::into_raw(Box::new(panic_fallible as Fallible)).cast();
1061        let input = unsafe { mlx_sys::mlx_vector_array_new() };
1062        let mut output = unsafe { mlx_sys::mlx_vector_array_new() };
1063        let status = trampoline_fallible::<Fallible>(&mut output, input, payload);
1064        assert_eq!(status, FAILURE);
1065        assert_captured_panic("fallible trampoline panic");
1066        closure_dtor::<Fallible>(payload);
1067        unsafe {
1068            mlx_sys::mlx_vector_array_free(input);
1069            mlx_sys::mlx_vector_array_free(output);
1070        }
1071    }
1072
1073    fn panic_infallible(_: &[Array]) -> Vec<Array> {
1074        panic!("infallible trampoline panic")
1075    }
1076
1077    fn panic_fallible(_: &[Array]) -> Result<Vec<Array>, Exception> {
1078        panic!("fallible trampoline panic")
1079    }
1080
1081    struct PanicOnDrop;
1082
1083    impl Drop for PanicOnDrop {
1084        fn drop(&mut self) {
1085            panic!("closure destructor panic")
1086        }
1087    }
1088
1089    fn assert_captured_panic(expected: &str) {
1090        let payload = catch_unwind(crate::error::resume_closure_panic)
1091            .expect_err("the trampoline panic should resume in Rust");
1092        assert_eq!(panic_message(payload).as_deref(), Some(expected));
1093    }
1094
1095    fn panic_message(payload: Box<dyn Any + Send>) -> Option<String> {
1096        payload
1097            .downcast_ref::<&'static str>()
1098            .map(|s| (*s).to_owned())
1099            .or_else(|| payload.downcast_ref::<String>().cloned())
1100    }
1101}