1use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::hash::{BuildHasherDefault, Hasher};
6use std::sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError};
7
8use smallvec::SmallVec;
9
10use crate::buffer::Buffer;
11use crate::primitive::Primitive;
12use crate::shape::{self, Flags, Shape, Strides};
13use crate::{DType, Device, Element, Error, Result, Stream, default_stream};
14
15#[derive(Clone)]
21pub struct Array {
22 pub(crate) node: Arc<Node>,
23 pub(crate) output: u32,
24}
25
26pub(crate) struct Node {
28 pub(crate) stream: Stream,
29 pub(crate) tracer: bool,
32 pub(crate) outputs: SmallVec<[Output; 1]>,
33 pub(crate) state: Mutex<State>,
34}
35
36pub(crate) struct Output {
38 pub(crate) dtype: DType,
39 pub(crate) shape: Shape,
40 pub(crate) strides: Strides,
41 pub(crate) flags: Flags,
42 pub(crate) size: usize,
43 pub(crate) slot: OnceLock<Result<Data>>,
45 pub(crate) inflight: InFlight,
48}
49
50#[derive(Default)]
54pub(crate) struct InFlight(Mutex<InFlightState>);
55
56#[derive(Default)]
57pub(crate) enum InFlightState {
58 #[default]
59 Unset,
60 #[cfg_attr(not(all(target_os = "macos", not(miri))), allow(dead_code))]
61 Set(Data),
62 Donated,
64}
65
66impl InFlight {
67 pub(crate) fn lock(&self) -> MutexGuard<'_, InFlightState> {
68 self.0.lock().unwrap_or_else(PoisonError::into_inner)
69 }
70
71 #[cfg_attr(not(all(target_os = "macos", not(miri))), allow(dead_code))]
73 pub(crate) fn set(&self, data: Data) {
74 let mut state = self.lock();
75 if matches!(*state, InFlightState::Unset) {
76 *state = InFlightState::Set(data);
77 }
78 }
79
80 pub(crate) fn get(&self) -> Option<Data> {
82 match &*self.lock() {
83 InFlightState::Set(data) => Some(data.clone()),
84 _ => None,
85 }
86 }
87
88 pub(crate) fn clear(&mut self) {
90 let state = self.0.get_mut().unwrap_or_else(PoisonError::into_inner);
91 if matches!(state, InFlightState::Set(_)) {
92 *state = InFlightState::Unset;
93 }
94 }
95}
96
97#[derive(Clone)]
100pub(crate) struct Data {
101 pub(crate) buffer: Arc<Buffer>,
102 pub(crate) offset: isize,
104}
105
106impl Data {
107 pub(crate) fn new(buffer: Buffer) -> Self {
108 Self {
109 buffer: Arc::new(buffer),
110 offset: 0,
111 }
112 }
113}
114
115pub(crate) enum State {
118 Pending {
119 primitive: Primitive,
120 inputs: Vec<Array>,
121 },
122 Retained {
123 primitive: Primitive,
124 inputs: Vec<Array>,
125 },
126 Done,
127}
128
129#[derive(Default, Clone, Copy)]
132pub(crate) struct NodeHasher(u64);
133
134impl Hasher for NodeHasher {
135 fn finish(&self) -> u64 {
136 self.0.rotate_left(26)
139 }
140 fn write(&mut self, bytes: &[u8]) {
141 for &b in bytes {
142 self.write_u64(u64::from(b));
143 }
144 }
145 fn write_u64(&mut self, n: u64) {
146 self.0 = (self.0.rotate_left(5) ^ n).wrapping_mul(0x9E37_79B9_7F4A_7C15);
147 }
148 fn write_u32(&mut self, n: u32) {
149 self.write_u64(u64::from(n));
150 }
151 fn write_usize(&mut self, n: usize) {
152 self.write_u64(n as u64);
153 }
154}
155
156pub(crate) type NodeSet = HashSet<*const Node, BuildHasherDefault<NodeHasher>>;
158pub(crate) type NodeMap<K, V> = HashMap<K, V, BuildHasherDefault<NodeHasher>>;
160
161const _: () = {
162 const fn assert_send_sync<T: Send + Sync>() {}
163 assert_send_sync::<Array>();
164};
165
166impl Node {
167 pub(crate) fn lock(&self) -> MutexGuard<'_, State> {
168 self.state.lock().unwrap_or_else(PoisonError::into_inner)
169 }
170}
171
172impl Output {
173 pub(crate) fn new(
174 dtype: DType,
175 shape: Shape,
176 strides: Option<Strides>,
177 slot: OnceLock<Result<Data>>,
178 ) -> Result<Self> {
179 shape::checked_nbytes(&shape, dtype.size())?;
180 let size = shape::checked_size(&shape)?;
181 let strides = strides.unwrap_or_else(|| shape::row_contiguous_strides(&shape));
182 let flags = shape::flags_for(&shape, &strides);
183 Ok(Self {
184 dtype,
185 shape,
186 strides,
187 flags,
188 size,
189 slot,
190 inflight: InFlight::default(),
191 })
192 }
193}
194
195impl Array {
196 pub fn from_slice<T: Element>(data: &[T], shape: &[usize]) -> Result<Self> {
202 let expected = shape::checked_size(shape)?;
203 if data.len() != expected {
204 return Err(Error::SizeMismatch {
205 expected,
206 actual: data.len(),
207 });
208 }
209 let mut buffer = Buffer::try_alloc(shape::checked_nbytes(shape, T::DTYPE.size())?)?;
210 T::write_bytes(data, buffer.bytes_mut());
211 Self::leaf(Ok(Data::new(buffer)), T::DTYPE, shape.into())
212 }
213
214 pub fn full<T: Element>(shape: &[usize], value: T) -> Result<Self> {
216 let itemsize = T::DTYPE.size();
217 let mut buffer = Buffer::try_alloc(shape::checked_nbytes(shape, itemsize)?)?;
218 let mut element = vec![0u8; itemsize];
219 T::write_bytes(&[value], &mut element);
220 for chunk in buffer.bytes_mut().chunks_exact_mut(itemsize) {
221 chunk.copy_from_slice(&element);
222 }
223 Self::leaf(Ok(Data::new(buffer)), T::DTYPE, shape.into())
224 }
225
226 pub fn scalar<T: Element>(value: T) -> Self {
228 Self::from_slice(&[value], &[]).expect("a scalar always matches its shape")
229 }
230
231 pub(crate) fn from_buffer(buffer: Buffer, dtype: DType, shape: &[usize]) -> Result<Self> {
233 Self::leaf(Ok(Data::new(buffer)), dtype, shape.into())
234 }
235
236 pub(crate) fn leaf(data: Result<Data>, dtype: DType, shape: Shape) -> Result<Self> {
238 let output = Output::new(dtype, shape, None, OnceLock::from(data))?;
239 Ok(Self::from_node(Node {
240 stream: default_stream(),
241 tracer: false,
242 outputs: SmallVec::from_buf([output]),
243 state: Mutex::new(State::Done),
244 }))
245 }
246
247 pub(crate) fn failed(error: Error, dtype: DType, shape: &[usize]) -> Self {
250 Self::leaf(Err(error.clone()), dtype, shape.into())
251 .unwrap_or_else(|_| Self::leaf(Err(error), dtype, Shape::new()).expect("scalar shape"))
252 }
253
254 pub(crate) fn from_op(primitive: Primitive, inputs: Vec<Array>) -> Result<Vec<Array>> {
258 let tracer = inputs.iter().any(|a| a.node.tracer);
259 Self::record(primitive, inputs, tracer)
260 }
261
262 pub(crate) fn record(
264 primitive: Primitive,
265 inputs: Vec<Array>,
266 tracer: bool,
267 ) -> Result<Vec<Array>> {
268 if let Some(err) = inputs.iter().find_map(Array::error) {
269 return Err(err);
270 }
271 Self::record_unchecked(primitive, inputs, tracer)
272 }
273
274 pub(crate) fn record_unchecked(
277 primitive: Primitive,
278 inputs: Vec<Array>,
279 tracer: bool,
280 ) -> Result<Vec<Array>> {
281 let outputs = primitive
282 .output_specs(&inputs)?
283 .into_iter()
284 .map(|spec| Output::new(spec.dtype, spec.shape, spec.strides, OnceLock::new()))
285 .collect::<Result<SmallVec<_>>>()?;
286 let count = outputs.len() as u32;
287 let node = Arc::new(Node {
288 stream: default_stream(),
289 tracer,
290 outputs,
291 state: Mutex::new(State::Pending { primitive, inputs }),
292 });
293 Ok((0..count)
294 .map(|output| Self {
295 node: node.clone(),
296 output,
297 })
298 .collect())
299 }
300
301 fn from_node(node: Node) -> Self {
302 Self {
303 node: Arc::new(node),
304 output: 0,
305 }
306 }
307
308 pub fn from_primitive(primitive: Primitive, inputs: Vec<Array>) -> Result<Vec<Array>> {
314 Self::from_op(primitive, inputs)
315 }
316
317 pub fn placeholder(dtype: DType, shape: &[usize]) -> Result<Array> {
321 let failed = Self::failed(
322 Error::InvalidArgument(
323 "arrays cannot be evaluated while a function is traced (compile)".into(),
324 ),
325 dtype,
326 &[],
327 );
328 let primitive = Primitive::Broadcast(shape.to_vec());
329 Ok(Self::record_unchecked(primitive, vec![failed], true)?.swap_remove(0))
330 }
331
332 pub fn inputs(&self) -> Vec<Array> {
335 match &*self.node.lock() {
336 State::Pending { inputs, .. } | State::Retained { inputs, .. } => inputs.clone(),
337 State::Done => Vec::new(),
338 }
339 }
340
341 pub fn siblings(&self) -> Vec<Array> {
344 (0..self.node.outputs.len() as u32)
345 .map(|output| Self {
346 node: self.node.clone(),
347 output,
348 })
349 .collect()
350 }
351
352 pub fn output_index(&self) -> usize {
354 self.output as usize
355 }
356
357 pub fn id(&self) -> usize {
360 Arc::as_ptr(&self.node) as usize
361 }
362
363 pub fn is_tracer(&self) -> bool {
366 self.node.tracer
367 }
368
369 pub(crate) fn out(&self) -> &Output {
370 &self.node.outputs[self.output as usize]
371 }
372
373 pub(crate) fn error(&self) -> Option<Error> {
375 match self.out().slot.get() {
376 Some(Err(e)) => Some(e.clone()),
377 _ => None,
378 }
379 }
380
381 pub fn shape(&self) -> &[usize] {
385 &self.out().shape
386 }
387
388 pub fn ndim(&self) -> usize {
390 self.out().shape.len()
391 }
392
393 pub fn size(&self) -> usize {
395 self.out().size
396 }
397
398 pub fn dtype(&self) -> DType {
400 self.out().dtype
401 }
402
403 pub fn itemsize(&self) -> usize {
405 self.dtype().size()
406 }
407
408 pub fn nbytes(&self) -> usize {
410 self.size() * self.itemsize()
411 }
412
413 pub fn strides(&self) -> &[isize] {
415 &self.out().strides
416 }
417
418 pub fn flags(&self) -> Flags {
420 self.out().flags
421 }
422
423 pub fn stream(&self) -> Stream {
425 self.node.stream
426 }
427
428 pub fn device(&self) -> Device {
430 self.node.stream.device
431 }
432
433 pub fn is_evaluated(&self) -> bool {
435 self.out().slot.get().is_some()
436 }
437
438 pub fn primitive(&self) -> Option<Primitive> {
441 match &*self.node.lock() {
442 State::Pending { primitive, .. } => Some(primitive.clone()),
443 State::Retained { .. } | State::Done => None,
444 }
445 }
446
447 pub fn eval(&self) -> Result<()> {
451 crate::eval(std::slice::from_ref(self))
452 }
453
454 pub fn to_vec<T: Element>(&self) -> Result<Vec<T>> {
456 if T::DTYPE != self.dtype() {
457 return Err(Error::DTypeMismatch {
458 op: "to_vec",
459 lhs: self.dtype(),
460 rhs: T::DTYPE,
461 });
462 }
463 self.eval()?;
464 let data = self.data().expect("eval succeeded, so data is set");
465 let out = self.out();
466 if out.size == 0 {
467 Ok(Vec::new())
468 } else if out.flags.row_contiguous || out.size == 1 {
469 let start = data.offset.max(0) as usize * out.dtype.size();
470 Ok(T::read_bytes(
471 &data.buffer.bytes()[start..start + self.nbytes()],
472 ))
473 } else {
474 let gathered = crate::kernels::contiguous(out, data)?;
475 Ok(T::read_bytes(gathered.bytes()))
476 }
477 }
478
479 pub fn item<T: Element>(&self) -> Result<T> {
481 if self.size() != 1 {
482 return Err(Error::InvalidArgument(format!(
483 "item() needs exactly one element, the array has shape {:?}",
484 self.shape()
485 )));
486 }
487 if T::DTYPE != self.dtype() {
488 return Err(Error::DTypeMismatch {
489 op: "item",
490 lhs: self.dtype(),
491 rhs: T::DTYPE,
492 });
493 }
494 Ok(self.to_vec::<T>()?[0])
495 }
496
497 pub(crate) fn data(&self) -> Option<&Data> {
499 self.out().slot.get().and_then(|r| r.as_ref().ok())
500 }
501}
502
503impl fmt::Debug for Array {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 let status = match self.out().slot.get() {
506 None => "unevaluated",
507 Some(Ok(_)) => "evaluated",
508 Some(Err(_)) => "failed",
509 };
510 f.debug_struct("Array")
511 .field("shape", &self.shape())
512 .field("dtype", &self.dtype())
513 .field("device", &self.device())
514 .field("status", &status)
515 .finish()
516 }
517}
518
519impl Drop for Node {
523 fn drop(&mut self) {
524 let mut stack = take_inputs(self);
525 while let Some(Array { node, .. }) = stack.pop() {
526 if let Some(mut node) = Arc::into_inner(node) {
527 stack.append(&mut take_inputs(&mut node));
528 }
529 }
530 }
531}
532
533fn take_inputs(node: &mut Node) -> Vec<Array> {
534 let state = node.state.get_mut().unwrap_or_else(PoisonError::into_inner);
535 match std::mem::replace(state, State::Done) {
536 State::Pending { inputs, .. } | State::Retained { inputs, .. } => inputs,
537 State::Done => Vec::new(),
538 }
539}
540
541#[cfg(test)]
542mod tests {
543 use super::*;
544
545 #[test]
546 fn graph_accessors_describe_pending_nodes() {
547 let a = Array::from_slice(&[1i32, 2], &[2]).unwrap();
548 let b = Array::from_slice(&[5i32, 7], &[2]).unwrap();
549 let outs = Array::from_primitive(Primitive::AddMultiply, vec![a.clone(), b]).unwrap();
550 let (sum, product) = (&outs[0], &outs[1]);
551 assert_eq!(sum.id(), product.id(), "siblings share their operation");
552 assert_ne!(sum.id(), a.id());
553 assert_eq!((sum.output_index(), product.output_index()), (0, 1));
554 assert_eq!(product.siblings().len(), 2);
555 assert_eq!(sum.inputs()[0].id(), a.id());
556 assert!(a.inputs().is_empty(), "host data has no inputs");
557 assert!(!sum.is_tracer());
558 product.eval().unwrap();
559 assert!(sum.inputs().is_empty(), "evaluated: detached");
560
561 let p = Array::placeholder(DType::F32, &[3]).unwrap();
562 let q = &p + 1.0f32;
563 assert!(p.is_tracer() && q.is_tracer());
564 assert_eq!(q.shape(), [3]);
565 assert!(q.eval().is_err(), "placeholders cannot be evaluated");
566 }
567
568 #[test]
569 fn node_hasher_spreads_aligned_addresses() {
570 use std::hash::BuildHasher;
571 let build = BuildHasherDefault::<NodeHasher>::default();
572 let buckets: HashSet<u64> = (0..1024u64)
574 .map(|i| {
575 build.hash_one(std::ptr::without_provenance::<Node>(
576 0x6000_0000_0000 + i as usize * 336,
577 )) & 1023
578 })
579 .collect();
580 assert!(
582 buckets.len() > 600,
583 "only {} of 1024 buckets used",
584 buckets.len()
585 );
586 }
587
588 #[test]
589 fn metadata_without_eval() {
590 let a = Array::from_slice(&[1i32, 2, 3, 4, 5, 6], &[2, 3]).unwrap();
591 let c = crate::add(&a, &a).unwrap();
592 assert!(!c.is_evaluated());
593 assert_eq!(c.shape(), &[2, 3]);
594 assert_eq!(c.ndim(), 2);
595 assert_eq!(c.size(), 6);
596 assert_eq!(c.dtype(), DType::I32);
597 assert_eq!(c.itemsize(), 4);
598 assert_eq!(c.nbytes(), 24);
599 assert_eq!(c.strides(), &[3, 1]);
600 assert!(c.flags().row_contiguous);
601 assert_eq!(c.device(), crate::default_device());
602 assert_eq!(c.primitive(), Some(Primitive::Binary(crate::BinaryOp::Add)));
603 assert!(!c.is_evaluated(), "metadata queries must not evaluate");
604 }
605
606 #[test]
607 fn item_on_scalar_and_non_scalar() {
608 assert_eq!(Array::scalar(7u16).item::<u16>().unwrap(), 7);
609 let a = Array::from_slice(&[1.0f32, 2.0], &[2]).unwrap();
610 assert!(matches!(a.item::<f32>(), Err(Error::InvalidArgument(_))));
611 assert!(matches!(
612 Array::scalar(1.0f32).item::<f64>(),
613 Err(Error::DTypeMismatch { .. })
614 ));
615 }
616
617 #[test]
618 fn debug_does_not_evaluate() {
619 let a = Array::scalar(1i8);
620 let c = crate::add(&a, &a).unwrap();
621 assert!(format!("{c:?}").contains("unevaluated"));
622 assert!(!c.is_evaluated());
623 }
624}