1use 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
13pub(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
36pub(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
93pub trait IntoOption<T> {
96 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
124pub trait ScalarOrArray<'a> {
126 type Array: AsRef<Array> + 'a;
128
129 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 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
174impl 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
246fn new_mlx_closure<'a, F>(closure: F) -> mlx_sys::mlx_closure
248where
249 F: FnMut(&[Array]) -> Vec<Array> + 'a,
250{
251 let boxed = Box::new(closure);
253
254 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
284fn 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
379extern "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#[derive(Debug)]
483pub struct StateProjection<'a> {
484 entries: Vec<ProjectedEntry<'a>>,
485}
486
487impl<'a> StateProjection<'a> {
488 pub fn new() -> Self {
490 Self {
491 entries: Vec::new(),
492 }
493 }
494
495 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 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 pub fn len(&self) -> usize {
540 self.entries.len()
541 }
542
543 pub fn is_empty(&self) -> bool {
545 self.entries.is_empty()
546 }
547
548 pub fn present_len(&self) -> usize {
550 self.entries
551 .iter()
552 .filter(|entry| entry.slot.value().is_some())
553 .count()
554 }
555
556 pub fn values(&self) -> impl Iterator<Item = &Array> {
558 self.entries.iter().filter_map(|entry| entry.slot.value())
559 }
560
561 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 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 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 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 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 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 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 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#[derive(Debug, Clone)]
658pub struct StateSnapshot {
659 entries: Vec<(Rc<str>, Option<Array>)>,
660}
661
662impl StateSnapshot {
663 pub fn len(&self) -> usize {
665 self.entries.len()
666 }
667
668 pub fn is_empty(&self) -> bool {
670 self.entries.is_empty()
671 }
672
673 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#[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 pub fn key(&self) -> &str {
733 &self.key
734 }
735
736 pub fn is_present(&self) -> bool {
738 self.dtype.is_some()
739 }
740
741 pub fn dtype(&self) -> Option<crate::Dtype> {
743 self.dtype
744 }
745
746 pub fn shape(&self) -> Option<&[i32]> {
748 self.shape.as_deref()
749 }
750}
751
752pub trait Updatable {
754 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
799pub enum SingleOrPair<T = i32> {
800 Single(T),
802
803 Pair(T, T),
805}
806
807impl<T: Clone> SingleOrPair<T> {
808 pub fn first(&self) -> T {
810 match self {
811 SingleOrPair::Single(v) => v.clone(),
812 SingleOrPair::Pair(v1, _) => v1.clone(),
813 }
814 }
815
816 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
848pub enum SingleOrTriple<T = i32> {
849 Single(T),
851
852 Triple(T, T, T),
854}
855
856impl<T: Clone> SingleOrTriple<T> {
857 pub fn first(&self) -> T {
859 match self {
860 SingleOrTriple::Single(v) => v.clone(),
861 SingleOrTriple::Triple(v1, _, _) => v1.clone(),
862 }
863 }
864
865 pub fn second(&self) -> T {
867 match self {
868 SingleOrTriple::Single(v) => v.clone(),
869 SingleOrTriple::Triple(_, v2, _) => v2.clone(),
870 }
871 }
872
873 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#[derive(Debug, Clone, PartialEq, Eq)]
905pub enum SingleOrVec<T> {
906 Single(T),
908
909 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}