1use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::hash::{BuildHasherDefault, Hasher};
6use std::panic::Location;
7use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, PoisonError};
8use std::thread::ThreadId;
9
10use smallvec::SmallVec;
11
12use crate::buffer::Buffer;
13use crate::primitive::Primitive;
14use crate::shape::{self, Flags, Shape, Strides};
15use crate::transforms::TraceId;
16use crate::{DType, Device, Element, Error, Result, Stream, default_stream};
17
18#[derive(Clone)]
29#[must_use = "arrays are lazy: an unused result computes nothing (and may hold an error)"]
30pub struct Array {
31 pub(crate) node: Arc<Node>,
32 pub(crate) output: u32,
33}
34
35pub(crate) struct Node {
37 pub(crate) stream: Stream,
38 pub(crate) trace: Option<TraceId>,
41 pub(crate) outputs: SmallVec<[Output; 1]>,
42 pub(crate) state: Mutex<State>,
43 pub(crate) failed: bool,
46}
47
48pub(crate) struct Output {
50 pub(crate) dtype: DType,
51 pub(crate) shape: Shape,
52 pub(crate) ishape: SmallVec<[i32; 4]>,
54 pub(crate) strides: Strides,
55 pub(crate) flags: Flags,
56 pub(crate) size: usize,
57 pub(crate) slot: OnceLock<Result<Data>>,
59 pub(crate) inflight: InFlight,
62}
63
64#[derive(Default)]
68pub(crate) struct InFlight {
69 state: Mutex<InFlightState>,
70 chunk: OnceLock<ChunkStatus>,
73}
74
75pub(crate) type ChunkStatus = Arc<OnceLock<Error>>;
77
78#[derive(Default)]
79pub(crate) enum InFlightState {
80 #[default]
81 Unset,
82 #[cfg_attr(not(all(target_os = "macos", not(miri))), allow(dead_code))]
83 Set(Data),
84 Donated,
86}
87
88impl InFlight {
89 pub(crate) fn lock(&self) -> MutexGuard<'_, InFlightState> {
90 self.state.lock().unwrap_or_else(PoisonError::into_inner)
91 }
92
93 #[cfg_attr(not(all(target_os = "macos", not(miri))), allow(dead_code))]
95 pub(crate) fn set_from(&self, data: Data, chunk: &ChunkStatus) {
96 let _ = self.chunk.set(chunk.clone());
97 self.set(data);
98 }
99
100 #[cfg_attr(not(all(target_os = "macos", not(miri))), allow(dead_code))]
102 pub(crate) fn chunk(&self) -> Option<ChunkStatus> {
103 self.chunk.get().cloned()
104 }
105
106 #[cfg_attr(not(all(target_os = "macos", not(miri))), allow(dead_code))]
108 pub(crate) fn set(&self, data: Data) {
109 let mut state = self.lock();
110 if matches!(*state, InFlightState::Unset) {
111 *state = InFlightState::Set(data);
112 }
113 }
114
115 pub(crate) fn get(&self) -> Option<Data> {
117 match &*self.lock() {
118 InFlightState::Set(data) => Some(data.clone()),
119 _ => None,
120 }
121 }
122
123 pub(crate) fn clear(&mut self) {
125 let state = self.state.get_mut().unwrap_or_else(PoisonError::into_inner);
126 if matches!(state, InFlightState::Set(_)) {
127 *state = InFlightState::Unset;
128 }
129 }
130}
131
132#[derive(Clone)]
135pub(crate) struct Data {
136 pub(crate) buffer: Arc<Buffer>,
137 pub(crate) offset: isize,
139}
140
141impl Data {
142 pub(crate) fn new(buffer: Buffer) -> Self {
143 Self {
144 buffer: Arc::new(buffer),
145 offset: 0,
146 }
147 }
148}
149
150pub(crate) enum State {
157 Pending {
158 primitive: Primitive,
159 inputs: Vec<Array>,
160 },
161 Running {
165 owner: ThreadId,
166 done: Arc<Completion>,
167 },
168 Retained {
169 primitive: Primitive,
170 inputs: Vec<Array>,
171 },
172 Done,
173 Failed {
175 error: Error,
176 },
177}
178
179#[derive(Default)]
181pub(crate) struct Completion {
182 done: Mutex<bool>,
183 cv: Condvar,
184}
185
186impl Completion {
187 pub(crate) fn finish(&self) {
189 *self.done.lock().unwrap_or_else(PoisonError::into_inner) = true;
190 self.cv.notify_all();
191 }
192
193 pub(crate) fn wait(&self) {
195 let mut done = self.done.lock().unwrap_or_else(PoisonError::into_inner);
196 while !*done {
197 done = self.cv.wait(done).unwrap_or_else(PoisonError::into_inner);
198 }
199 }
200}
201
202#[derive(Default, Clone, Copy)]
205pub(crate) struct NodeHasher(u64);
206
207impl Hasher for NodeHasher {
208 fn finish(&self) -> u64 {
209 self.0.rotate_left(26)
212 }
213 fn write(&mut self, bytes: &[u8]) {
214 for &b in bytes {
215 self.write_u64(u64::from(b));
216 }
217 }
218 fn write_u64(&mut self, n: u64) {
219 self.0 = (self.0.rotate_left(5) ^ n).wrapping_mul(0x9E37_79B9_7F4A_7C15);
220 }
221 fn write_u32(&mut self, n: u32) {
222 self.write_u64(u64::from(n));
223 }
224 fn write_usize(&mut self, n: usize) {
225 self.write_u64(n as u64);
226 }
227}
228
229pub(crate) type NodeSet = HashSet<*const Node, BuildHasherDefault<NodeHasher>>;
231pub(crate) type NodeMap<K, V> = HashMap<K, V, BuildHasherDefault<NodeHasher>>;
233
234const _: () = {
235 const fn assert_send_sync<T: Send + Sync>() {}
236 assert_send_sync::<Array>();
237};
238
239impl Node {
240 pub(crate) fn lock(&self) -> MutexGuard<'_, State> {
241 self.state.lock().unwrap_or_else(PoisonError::into_inner)
242 }
243
244 pub(crate) fn is_tracer(&self) -> bool {
246 self.trace.is_some_and(crate::transforms::is_live)
247 }
248}
249
250impl Output {
251 #[cold]
253 fn invalid(shape: &[usize], dtype: DType) -> Error {
254 if shape.iter().any(|&d| i32::try_from(d).is_err()) {
255 Error::invalid_shape("shape", shape.iter().map(|&d| d as i64))
256 } else {
257 shape::checked_nbytes(shape, dtype.size())
258 .err()
259 .unwrap_or_else(|| Error::invalid_shape("shape", shape.iter().map(|&d| d as i64)))
260 }
261 }
262
263 pub(crate) fn new(
264 dtype: DType,
265 shape: Shape,
266 strides: Option<Strides>,
267 slot: OnceLock<Result<Data>>,
268 ) -> Result<Self> {
269 let mut size = 1usize;
272 let mut ishape = SmallVec::<[i32; 4]>::new();
273 for &d in &shape {
274 let (Some(n), Ok(di)) = (size.checked_mul(d), i32::try_from(d)) else {
275 return Err(Self::invalid(&shape, dtype));
276 };
277 size = n;
278 ishape.push(di);
279 }
280 if size
281 .checked_mul(dtype.size())
282 .is_none_or(|n| n > isize::MAX as usize)
283 {
284 return Err(Self::invalid(&shape, dtype));
285 }
286 let strides = strides.unwrap_or_else(|| shape::row_contiguous_strides(&shape));
287 let flags = shape::flags_for(&shape, &strides);
288 Ok(Self {
289 dtype,
290 shape,
291 ishape,
292 strides,
293 flags,
294 size,
295 slot,
296 inflight: InFlight::default(),
297 })
298 }
299}
300
301impl Array {
302 pub(crate) fn from_buffer(buffer: Buffer, dtype: DType, shape: &[usize]) -> Result<Self> {
306 Self::leaf(Ok(Data::new(buffer)), dtype, shape.into())
307 }
308
309 pub(crate) fn leaf(data: Result<Data>, dtype: DType, shape: Shape) -> Result<Self> {
311 let output = Output::new(dtype, shape, None, OnceLock::from(data))?;
312 Ok(Self::from_node(Node {
313 stream: default_stream(),
314 trace: None,
315 outputs: SmallVec::from_buf([output]),
316 state: Mutex::new(State::Done),
317 failed: false,
318 }))
319 }
320
321 #[cold]
324 pub(crate) fn failed(error: Error, dtype: DType, shape: &[usize]) -> Self {
325 let output = Output::new(
326 dtype,
327 shape.into(),
328 None,
329 OnceLock::from(Err(error.clone())),
330 )
331 .unwrap_or_else(|_| {
332 Output::new(dtype, Shape::new(), None, OnceLock::from(Err(error)))
333 .unwrap_or_else(|_| unreachable!("a 0-D output is always valid"))
334 });
335 Self::from_node(Node {
336 stream: default_stream(),
337 trace: None,
338 outputs: SmallVec::from_buf([output]),
339 state: Mutex::new(State::Done),
340 failed: true,
341 })
342 }
343
344 #[inline]
350 pub(crate) fn sticky(
351 op: &'static str,
352 at: &'static Location<'static>,
353 inputs: &[&Array],
354 f: impl FnOnce() -> Result<Array>,
355 ) -> Array {
356 match f() {
357 Ok(out) => out,
358 Err(error) => Self::failed_like(Self::sticky_error(op, at, inputs, error), inputs),
359 }
360 }
361
362 #[inline]
364 pub(crate) fn sticky_n<const N: usize>(
365 op: &'static str,
366 at: &'static Location<'static>,
367 inputs: &[&Array],
368 f: impl FnOnce() -> Result<[Array; N]>,
369 ) -> [Array; N] {
370 match f() {
371 Ok(out) => out,
372 Err(error) => {
373 let error = Self::sticky_error(op, at, inputs, error);
374 std::array::from_fn(|_| Self::failed_like(error.clone(), inputs))
375 }
376 }
377 }
378
379 #[inline]
382 pub(crate) fn sticky_vec(
383 op: &'static str,
384 at: &'static Location<'static>,
385 inputs: &[&Array],
386 count: usize,
387 f: impl FnOnce() -> Result<Vec<Array>>,
388 ) -> Vec<Array> {
389 match f() {
390 Ok(out) => out,
391 Err(error) => {
392 let error = Self::sticky_error(op, at, inputs, error);
393 (0..count)
394 .map(|_| Self::failed_like(error.clone(), inputs))
395 .collect()
396 }
397 }
398 }
399
400 #[cold]
403 fn sticky_error(
404 op: &'static str,
405 at: &'static Location<'static>,
406 inputs: &[&Array],
407 error: Error,
408 ) -> Error {
409 inputs
410 .iter()
411 .find_map(|a| a.slot_error())
412 .unwrap_or_else(|| error.locate(op, at))
413 }
414
415 #[cold]
417 pub(crate) fn failed_like(error: Error, inputs: &[&Array]) -> Self {
418 match inputs.first() {
419 Some(a) => Self::failed(error, a.dtype(), a.dims()),
420 None => Self::failed(error, DType::F32, &[]),
421 }
422 }
423
424 pub(crate) fn from_op(primitive: Primitive, inputs: Vec<Array>) -> Result<Vec<Array>> {
428 let trace = crate::transforms::inherited(&inputs);
429 Self::record(primitive, inputs, trace)
430 }
431
432 pub(crate) fn record(
439 primitive: Primitive,
440 inputs: Vec<Array>,
441 trace: Option<TraceId>,
442 ) -> Result<Vec<Array>> {
443 if let Some(err) = inputs.iter().find_map(Array::slot_error) {
444 return Self::record_failed(&primitive, &inputs, err);
445 }
446 Self::record_unchecked(primitive, inputs, trace)
447 }
448
449 #[cold]
450 fn record_failed(primitive: &Primitive, inputs: &[Array], err: Error) -> Result<Vec<Array>> {
451 let specs = primitive.output_specs(inputs).map_err(|_| err.clone())?;
452 Ok(specs
453 .into_iter()
454 .map(|spec| Self::failed(err.clone(), spec.dtype, &spec.shape))
455 .collect())
456 }
457
458 pub(crate) fn record_unchecked(
461 primitive: Primitive,
462 inputs: Vec<Array>,
463 trace: Option<TraceId>,
464 ) -> Result<Vec<Array>> {
465 let outputs = primitive
466 .output_specs(&inputs)?
467 .into_iter()
468 .map(|spec| Output::new(spec.dtype, spec.shape, spec.strides, OnceLock::new()))
469 .collect::<Result<SmallVec<_>>>()?;
470 let count = outputs.len() as u32;
471 let node = Arc::new(Node {
472 stream: default_stream(),
473 trace,
474 outputs,
475 state: Mutex::new(State::Pending { primitive, inputs }),
476 failed: false,
477 });
478 Ok((0..count)
479 .map(|output| Self {
480 node: node.clone(),
481 output,
482 })
483 .collect())
484 }
485
486 fn from_node(node: Node) -> Self {
487 Self {
488 node: Arc::new(node),
489 output: 0,
490 }
491 }
492
493 pub(crate) fn placeholder(dtype: DType, shape: &[usize]) -> Result<Array> {
499 let failed = Self::failed(
500 Error::InvalidArgument(
501 "arrays cannot be evaluated while a function is traced (compile)".into(),
502 ),
503 dtype,
504 &[],
505 );
506 let primitive = Primitive::Broadcast(shape.to_vec());
507 let trace = Some(crate::transforms::PLACEHOLDER);
508 Ok(Self::record_unchecked(primitive, vec![failed], trace)?.swap_remove(0))
509 }
510
511 pub(crate) fn out(&self) -> &Output {
512 &self.node.outputs[self.output as usize]
513 }
514
515 pub(crate) fn slot_error(&self) -> Option<Error> {
517 match self.out().slot.get() {
518 Some(Err(e)) => Some(e.clone()),
519 _ => None,
520 }
521 }
522
523 pub fn is_failed(&self) -> bool {
529 self.node.failed
530 }
531
532 #[track_caller]
543 pub fn failure(op: &'static str, kind: crate::OpErrorKind) -> Array {
544 Self::failed(
545 Error::op(op, kind).locate(op, Location::caller()),
546 DType::F32,
547 &[],
548 )
549 }
550
551 pub fn error(&self) -> Option<&Error> {
553 match (self.node.failed, self.out().slot.get()) {
554 (true, Some(Err(e))) => Some(e),
555 _ => None,
556 }
557 }
558
559 pub fn check(self) -> Result<Array> {
563 if self.node.failed
564 && let Some(error) = self.slot_error()
565 {
566 return Err(error);
567 }
568 Ok(self)
569 }
570
571 pub fn shape(&self) -> &[i32] {
579 &self.out().ishape
580 }
581
582 #[track_caller]
595 pub fn shape_n<const N: usize>(&self) -> Result<[i32; N]> {
596 if let Some(e) = self.error() {
597 return Err(e.clone());
598 }
599 <[i32; N]>::try_from(self.shape()).map_err(|_| {
600 Error::InvalidArgument(format!(
601 "shape_n: expected {N} dimensions, the array has shape {:?}",
602 self.shape()
603 ))
604 .locate("shape_n", Location::caller())
605 })
606 }
607
608 pub(crate) fn dims(&self) -> &[usize] {
610 &self.out().shape
611 }
612
613 pub fn ndim(&self) -> usize {
615 self.out().shape.len()
616 }
617
618 pub fn size(&self) -> usize {
620 self.out().size
621 }
622
623 pub fn dtype(&self) -> DType {
625 self.out().dtype
626 }
627
628 pub fn itemsize(&self) -> usize {
630 self.dtype().size()
631 }
632
633 pub fn nbytes(&self) -> usize {
635 self.size() * self.itemsize()
636 }
637
638 pub(crate) fn flags(&self) -> Flags {
640 self.out().flags
641 }
642
643 pub(crate) fn strides(&self) -> &[isize] {
645 &self.out().strides
646 }
647
648 pub fn stream(&self) -> Stream {
650 self.node.stream
651 }
652
653 pub fn device(&self) -> Device {
655 self.node.stream.device
656 }
657
658 pub fn is_evaluated(&self) -> bool {
660 self.out().slot.get().is_some()
661 }
662
663 pub fn eval(&self) -> Result<()> {
667 crate::eval(std::slice::from_ref(self))
668 }
669
670 pub fn to_vec<T: Element>(&self) -> Result<Vec<T>> {
672 if let Some(e) = self.error() {
673 return Err(e.clone());
674 }
675 if T::DTYPE != self.dtype() {
676 return Err(Error::dtype_mismatch("to_vec", self.dtype(), T::DTYPE));
677 }
678 self.eval()?;
679 let data = self.data().expect("eval succeeded, so data is set");
680 let out = self.out();
681 if out.size == 0 {
682 Ok(Vec::new())
683 } else if out.flags.row_contiguous || out.size == 1 {
684 let start = data.offset.max(0) as usize * out.dtype.size();
685 Ok(T::read_bytes(
686 &data.buffer.bytes()[start..start + self.nbytes()],
687 ))
688 } else {
689 let gathered = crate::kernels::contiguous(out, data)?;
690 Ok(T::read_bytes(gathered.bytes()))
691 }
692 }
693
694 pub fn item<T: Element>(&self) -> Result<T> {
696 if let Some(e) = self.error() {
697 return Err(e.clone());
698 }
699 if self.size() != 1 {
700 return Err(Error::InvalidArgument(format!(
701 "item() needs exactly one element, the array has shape {:?}",
702 self.dims()
703 )));
704 }
705 if T::DTYPE != self.dtype() {
706 return Err(Error::dtype_mismatch("item", self.dtype(), T::DTYPE));
707 }
708 Ok(self.to_vec::<T>()?[0])
709 }
710
711 pub(crate) fn data(&self) -> Option<&Data> {
713 self.out().slot.get().and_then(|r| r.as_ref().ok())
714 }
715}
716
717impl fmt::Debug for Array {
720 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
721 let mut d = f.debug_struct("Array");
722 d.field("shape", &self.shape())
723 .field("dtype", &self.dtype())
724 .field("device", &self.device());
725 match self.out().slot.get() {
726 None => d.field("status", &"unevaluated"),
727 Some(Ok(_)) => match crate::display::values(self) {
728 Ok(values) => d.field("values", &format_args!("{values}")),
729 Err(_) => d.field("status", &"evaluated"),
730 },
731 Some(Err(e)) if self.node.failed => d.field("failed", &format_args!("{e}")),
732 Some(Err(e)) => d.field("error", &format_args!("{e}")),
733 };
734 d.finish()
735 }
736}
737
738impl fmt::Display for Array {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743 match crate::display::values(self) {
744 Ok(values) => write!(f, "array({values}, dtype={})", self.dtype()),
745 Err(e) => write!(f, "<failed: {e}>"),
746 }
747 }
748}
749
750impl Default for Array {
754 fn default() -> Self {
755 crate::creation::from_data(&[0.0f32], &[])
756 .unwrap_or_else(|e| Self::failed(e, DType::F32, &[]))
757 }
758}
759
760impl Drop for Node {
764 fn drop(&mut self) {
765 let mut stack = take_inputs(self);
766 while let Some(Array { node, .. }) = stack.pop() {
767 if let Some(mut node) = Arc::into_inner(node) {
768 stack.append(&mut take_inputs(&mut node));
769 }
770 }
771 }
772}
773
774fn take_inputs(node: &mut Node) -> Vec<Array> {
775 let state = node.state.get_mut().unwrap_or_else(PoisonError::into_inner);
776 match std::mem::replace(state, State::Done) {
777 State::Pending { inputs, .. } | State::Retained { inputs, .. } => inputs,
778 State::Running { .. } | State::Done | State::Failed { .. } => Vec::new(),
779 }
780}
781
782#[cfg(test)]
784impl Array {
785 pub(crate) fn primitive_t(&self) -> Option<Primitive> {
786 crate::graph::primitive(self)
787 }
788 pub(crate) fn inputs_t(&self) -> Vec<Array> {
789 crate::graph::inputs(self)
790 }
791 pub(crate) fn id_t(&self) -> usize {
792 crate::graph::id(self)
793 }
794 pub(crate) fn siblings_t(&self) -> Vec<Array> {
795 crate::graph::siblings(self)
796 }
797 pub(crate) fn output_index_t(&self) -> usize {
798 crate::graph::output_index(self)
799 }
800 pub(crate) fn is_tracer_t(&self) -> bool {
801 crate::graph::is_tracer(self)
802 }
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808 use crate::OpErrorKind;
809
810 #[test]
811 fn graph_accessors_describe_pending_nodes() {
812 let a = Array::from_slice(&[1i32, 2], [2]);
813 let b = Array::from_slice(&[5i32, 7], [2]);
814 let outs =
815 crate::graph::from_primitive(Primitive::AddMultiply, vec![a.clone(), b]).unwrap();
816 let (sum, product) = (&outs[0], &outs[1]);
817 assert_eq!(sum.id_t(), product.id_t(), "siblings share their operation");
818 assert_ne!(sum.id_t(), a.id_t());
819 assert_eq!((sum.output_index_t(), product.output_index_t()), (0, 1));
820 assert_eq!(product.siblings_t().len(), 2);
821 assert_eq!(sum.inputs_t()[0].id_t(), a.id_t());
822 assert!(a.inputs_t().is_empty(), "host data has no inputs");
823 assert!(!sum.is_tracer_t());
824 product.eval().unwrap();
825 assert!(sum.inputs_t().is_empty(), "evaluated: detached");
826
827 let p = Array::placeholder(DType::F32, &[3]).unwrap();
828 let q = &p + 1.0f32;
829 assert!(p.is_tracer_t() && q.is_tracer_t());
830 assert_eq!(q.dims(), [3]);
831 assert!(q.eval().is_err(), "placeholders cannot be evaluated");
832 }
833
834 #[test]
835 fn failed_leaves_are_sticky_and_typed() {
836 let error = Error::size_mismatch("from_slice", 6, 5);
837 let f = Array::failed(error.clone(), DType::F16, &[2, 3]);
838 assert!(f.is_failed());
839 assert_eq!(f.shape(), [2, 3]);
840 assert_eq!(f.dtype(), DType::F16);
841 assert_eq!(f.eval(), Err(error.clone()));
842 assert_eq!(f.clone().check().unwrap_err(), error);
843 let g = crate::ops::imp::reshape(&f, &[3, 2]).unwrap();
845 assert!(g.is_failed() && g.primitive_t().is_none());
846 assert_eq!(g.shape(), [3, 2]);
847 assert_eq!(g.eval(), Err(error));
848 let ok = &Array::from(1.0f32) + 1.0;
850 assert!(!ok.is_failed());
851 let ok = ok.check().unwrap();
852 assert!(!ok.is_evaluated());
853 }
854
855 #[test]
856 fn shapes_are_i32_and_bounded() {
857 let a = crate::ops::imp::zeros(&[2, 3, 4], DType::U8).unwrap();
858 let shape: &[i32] = a.shape();
859 assert_eq!(shape, [2, 3, 4]);
860 assert_eq!(a.shape_n::<3>().unwrap(), [2, 3, 4]);
861 assert!(a.shape_n::<2>().is_err(), "rank mismatch");
862 let big = crate::ops::imp::broadcast_to(&Array::from(0u8), &[1usize << 31]).unwrap_err();
864 assert!(
865 matches!(big.kind(), Some(OpErrorKind::InvalidShape(_))),
866 "{big}"
867 );
868 let fits = crate::ops::imp::broadcast_to(&Array::from(0u8), &[i32::MAX as usize]).unwrap();
869 assert_eq!(fits.shape(), [i32::MAX]);
870 }
871
872 #[test]
873 fn node_hasher_spreads_aligned_addresses() {
874 use std::hash::BuildHasher;
875 let build = BuildHasherDefault::<NodeHasher>::default();
876 let buckets: HashSet<u64> = (0..1024u64)
878 .map(|i| {
879 build.hash_one(std::ptr::without_provenance::<Node>(
880 0x6000_0000_0000 + i as usize * 336,
881 )) & 1023
882 })
883 .collect();
884 assert!(
886 buckets.len() > 600,
887 "only {} of 1024 buckets used",
888 buckets.len()
889 );
890 }
891
892 #[test]
893 fn metadata_without_eval() {
894 let a = Array::from_slice(&[1i32, 2, 3, 4, 5, 6], [2, 3]);
895 let c = crate::ops::imp::add(&a, &a).unwrap();
896 assert!(!c.is_evaluated());
897 assert_eq!(c.dims(), &[2, 3]);
898 assert_eq!(c.ndim(), 2);
899 assert_eq!(c.size(), 6);
900 assert_eq!(c.dtype(), DType::I32);
901 assert_eq!(c.itemsize(), 4);
902 assert_eq!(c.nbytes(), 24);
903 assert_eq!(c.strides(), &[3, 1]);
904 assert!(c.flags().row_contiguous);
905 assert_eq!(c.device(), crate::default_device());
906 assert_eq!(
907 c.primitive_t(),
908 Some(Primitive::Binary(crate::graph::BinaryOp::Add))
909 );
910 assert!(!c.is_evaluated(), "metadata queries must not evaluate");
911 }
912
913 #[test]
914 fn item_on_scalar_and_non_scalar() {
915 assert_eq!(Array::from(7u16).item::<u16>().unwrap(), 7);
916 let a = Array::from_slice(&[1.0f32, 2.0], [2]);
917 assert!(matches!(a.item::<f32>(), Err(Error::InvalidArgument(_))));
918 assert!(matches!(
919 Array::from(1.0f32).item::<f64>(),
920 Err(Error::Op {
921 kind: OpErrorKind::DTypeMismatch { .. },
922 ..
923 })
924 ));
925 }
926
927 #[test]
928 fn debug_does_not_evaluate() {
929 let a = Array::from(1i8);
930 let c = crate::ops::imp::add(&a, &a).unwrap();
931 assert!(format!("{c:?}").contains("unevaluated"));
932 assert!(!c.is_evaluated());
933 }
934}