1use super::{
2 PositionIterInternal, PyBytes, PyBytesRef, PyGenericAlias, PyInt, PyListRef, PySlice, PyStr,
3 PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, PyUtf8Str, PyUtf8StrRef, iter::builtins_iter,
4 locked_next,
5};
6use crate::common::lock::LazyLock;
7use crate::{
8 AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
9 TryFromBorrowedObject, TryFromObject, VirtualMachine, atomic_func,
10 buffer::{FormatSpec, PackErrorKind},
11 bytes_inner::{ByteInnerHexOptions, bytes_to_hex},
12 class::{PyClassImpl, StaticType},
13 common::{
14 borrow::{BorrowedValue, BorrowedValueMut},
15 hash::PyHash,
16 lock::OnceCell,
17 },
18 convert::ToPyObject,
19 function::Either,
20 function::{ArgIndex, NameExcInfo, OptionalArg, PosArgs, PyComparisonValue},
21 protocol::{
22 BufferDescriptor, BufferFlags, BufferMethods, PyBuffer, PyIterReturn, PyMappingMethods,
23 PySequenceMethods, VecBuffer,
24 },
25 sliceable::SequenceIndexOp,
26 types::{
27 AsBuffer, AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable,
28 PyComparisonOp, Representable, SelfIter,
29 },
30};
31use core::{cmp::Ordering, fmt::Debug, ops::Range};
32use crossbeam_utils::atomic::AtomicCell;
33use itertools::Itertools;
34use rustpython_common::lock::PyMutex;
35
36const MAX_NDIM: usize = 64;
38
39#[derive(FromArgs)]
40pub struct PyMemoryViewNewArgs {
41 object: PyObjectRef,
42}
43
44#[derive(FromArgs)]
45struct PyMemoryViewFromFlagsArgs {
46 object: PyObjectRef,
47 flags: ArgIndex,
48}
49
50#[pyclass(module = false, name = "memoryview", traverse)]
51#[derive(Debug)]
52pub struct PyMemoryView {
53 buffer: PyBuffer,
56 #[pytraverse(skip)]
59 released: AtomicCell<bool>,
60 #[pytraverse(skip)]
63 restricted: AtomicCell<bool>,
64 #[pytraverse(skip)]
65 format_spec: FormatSpec,
66 #[pytraverse(skip)]
68 desc: BufferDescriptor,
69 #[pytraverse(skip)]
70 hash: OnceCell<PyHash>,
71 #[pytraverse(skip)]
75 exports: AtomicCell<usize>,
76}
77
78impl Constructor for PyMemoryView {
79 type Args = PyMemoryViewNewArgs;
80
81 fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
82 Self::from_object(&args.object, vm)
83 }
84}
85
86impl PyMemoryView {
87 fn parse_format(format: &str, vm: &VirtualMachine) -> PyResult<FormatSpec> {
88 FormatSpec::parse(format.as_bytes(), vm)
89 }
90
91 fn native_fmtchar(format: &str) -> Option<u8> {
94 let format = format.strip_prefix('@').unwrap_or(format);
95 let [c] = *format.as_bytes() else {
96 return None;
97 };
98 matches!(
99 c,
100 b'c' | b'b'
101 | b'B'
102 | b'h'
103 | b'H'
104 | b'i'
105 | b'I'
106 | b'l'
107 | b'L'
108 | b'q'
109 | b'Q'
110 | b'n'
111 | b'N'
112 | b'f'
113 | b'd'
114 | b'e'
115 | b'?'
116 | b'P'
117 )
118 .then_some(c)
119 }
120
121 pub fn from_object(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Self> {
124 Self::from_object_with_flags(obj, BufferFlags::FULL_RO, vm)
125 }
126
127 #[must_use]
130 pub fn clone_buffer(&self) -> PyBuffer {
131 let mut buffer = self.buffer.clone();
132 buffer.desc = self.desc.clone();
133 buffer
134 }
135
136 pub fn from_object_with_flags(
138 obj: &PyObject,
139 flags: BufferFlags,
140 vm: &VirtualMachine,
141 ) -> PyResult<Self> {
142 if let Some(other) = obj.downcast_ref::<Self>() {
143 other.try_not_released(vm)?;
144 other.try_not_restricted(vm)?;
145 Ok(other.new_view())
146 } else if obj.check_buffer() {
147 let buffer = PyBuffer::from_object(vm, obj, flags)?;
148 Self::from_buffer(buffer, vm)
149 } else {
150 Err(vm.new_type_error(format!(
151 "memoryview: a bytes-like object is required, not '{}'",
152 obj.class().name()
153 )))
154 }
155 }
156
157 pub fn from_buffer(buffer: PyBuffer, vm: &VirtualMachine) -> PyResult<Self> {
161 let format_spec = Self::parse_format(&buffer.desc.format, vm)?;
165 let desc = buffer.desc.clone();
166
167 Ok(Self {
168 buffer,
169 released: AtomicCell::new(false),
170 restricted: AtomicCell::new(false),
171 format_spec,
172 desc,
173 hash: OnceCell::new(),
174 exports: AtomicCell::new(0),
175 })
176 }
177
178 pub fn from_buffer_range(
182 buffer: PyBuffer,
183 range: Range<usize>,
184 vm: &VirtualMachine,
185 ) -> PyResult<Self> {
186 let mut zelf = Self::from_buffer(buffer, vm)?;
187
188 zelf.init_range(range, 0);
189 zelf.init_len();
190 Ok(zelf)
191 }
192
193 #[must_use]
195 pub fn new_view(&self) -> Self {
196 Self {
197 buffer: self.buffer.clone(),
198 released: AtomicCell::new(false),
199 restricted: AtomicCell::new(false),
200 format_spec: self.format_spec.clone(),
201 desc: self.desc.clone(),
202 hash: OnceCell::new(),
203 exports: AtomicCell::new(0),
204 }
205 }
206
207 #[must_use]
211 fn borrowed_view(&self) -> Self {
212 Self {
213 buffer: self.buffer.detached(),
214 released: AtomicCell::new(false),
215 restricted: AtomicCell::new(false),
216 format_spec: self.format_spec.clone(),
217 desc: self.desc.clone(),
218 hash: OnceCell::new(),
219 exports: AtomicCell::new(0),
220 }
221 }
222
223 pub fn viewed_object(&self) -> &PyObject {
225 &self.buffer.obj
226 }
227
228 fn try_not_released(&self, vm: &VirtualMachine) -> PyResult<()> {
229 if self.released.load() {
230 Err(vm.new_value_error("operation forbidden on released memoryview object"))
231 } else {
232 Ok(())
233 }
234 }
235
236 fn try_not_restricted(&self, vm: &VirtualMachine) -> PyResult<()> {
237 if self.restricted.load() {
238 Err(vm.new_value_error("cannot create new view on restricted memoryview"))
239 } else {
240 Ok(())
241 }
242 }
243
244 fn try_usable(&self, vm: &VirtualMachine) -> PyResult<()> {
245 self.try_not_released(vm)?;
246 self.try_not_restricted(vm)
247 }
248
249 fn check_buffer_request(&self, flags: BufferFlags, vm: &VirtualMachine) -> PyResult<()> {
251 let c_contiguous = self.desc.is_contiguous();
252 flags.check_writable(
253 self.desc.readonly,
254 "memoryview: underlying buffer is not writable",
255 vm,
256 )?;
257 if flags.contains(BufferFlags::C_CONTIGUOUS) && !c_contiguous {
258 return Err(vm.new_buffer_error("memoryview: underlying buffer is not C-contiguous"));
259 }
260 if flags.contains(BufferFlags::F_CONTIGUOUS) && !self.desc.is_fortran_contiguous() {
261 return Err(
262 vm.new_buffer_error("memoryview: underlying buffer is not Fortran contiguous")
263 );
264 }
265 if flags.contains(BufferFlags::ANY_CONTIGUOUS)
266 && !c_contiguous
267 && !self.desc.is_fortran_contiguous()
268 {
269 return Err(vm.new_buffer_error("memoryview: underlying buffer is not contiguous"));
270 }
271 if !flags.contains(BufferFlags::INDIRECT) && self.desc.has_suboffsets() {
274 return Err(vm.new_buffer_error("memoryview: underlying buffer requires suboffsets"));
275 }
276 if !flags.contains(BufferFlags::STRIDES) && !c_contiguous {
277 return Err(vm.new_buffer_error("memoryview: underlying buffer is not C-contiguous"));
278 }
279 if !flags.contains(BufferFlags::ND) && flags.intersects(BufferFlags::FORMAT) {
280 return Err(vm.new_buffer_error(
281 "memoryview: cannot cast to unsigned bytes if the format flag is present",
282 ));
283 }
284 Ok(())
285 }
286
287 fn requested_desc(
290 &self,
291 flags: BufferFlags,
292 vm: &VirtualMachine,
293 ) -> PyResult<BufferDescriptor> {
294 self.check_buffer_request(flags, vm)?;
295 Ok(self.desc.projected(flags))
296 }
297
298 fn getitem_by_idx(&self, i: isize, vm: &VirtualMachine) -> PyResult {
299 if self.desc.ndim() != 1 {
300 return Err(
301 vm.new_not_implemented_error("multi-dimensional sub-views are not implemented")
302 );
303 }
304 let (shape, _, _) = self.desc.dim_desc[0];
305 let index = i
307 .wrapped_at(shape)
308 .ok_or_else(|| vm.new_index_error("index out of bounds on dimension 1"))?;
309 self.unpack_single(self.desc.fast_position(&[index]) as usize, vm)
310 }
311
312 fn getitem_by_slice(&self, slice: &Py<PySlice>, vm: &VirtualMachine) -> PyResult {
313 self.try_not_restricted(vm)?;
314 let mut other = self.new_view();
315 other.init_slice(slice, 0, vm)?;
316 other.init_len();
317
318 Ok(other.into_ref(&vm.ctx).into())
319 }
320
321 fn getitem_by_multi_idx(&self, indexes: &[isize], vm: &VirtualMachine) -> PyResult {
322 let pos = self.pos_from_multi_index(indexes, vm)?;
323 self.unpack_single(pos, vm)
324 }
325
326 fn setitem_by_idx(&self, i: isize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
327 if self.desc.ndim() != 1 {
328 return Err(vm.new_not_implemented_error("sub-views are not implemented"));
329 }
330 let (shape, _, _) = self.desc.dim_desc[0];
331 let index = i
333 .wrapped_at(shape)
334 .ok_or_else(|| vm.new_index_error("index out of bounds on dimension 1"))?;
335 self.pack_single(self.desc.fast_position(&[index]) as usize, value, vm)
336 }
337
338 fn setitem_by_multi_idx(
339 &self,
340 indexes: &[isize],
341 value: PyObjectRef,
342 vm: &VirtualMachine,
343 ) -> PyResult<()> {
344 let pos = self.pos_from_multi_index(indexes, vm)?;
345 self.pack_single(pos, value, vm)
346 }
347
348 fn pack_single(&self, pos: usize, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
349 let data = self.format_spec.try_pack(vec![value], vm).map_err(|err| {
356 let what = match err.kind {
357 PackErrorKind::Type => "type",
358 PackErrorKind::Value => "value",
359 PackErrorKind::Raised => return err.exception,
360 };
361 let msg = format!(
362 "memoryview: invalid {what} for format '{}'",
363 self.desc.format
364 );
365 match err.kind {
366 PackErrorKind::Type => vm.new_type_error(msg),
367 _ => vm.new_value_error(msg),
368 }
369 })?;
370 self.try_not_released(vm)?;
374 let mut bytes = self.buffer.obj_bytes_mut();
375 bytes[pos..pos + self.format_spec.size()].copy_from_slice(&data);
376 Ok(())
377 }
378
379 fn unpack_single(&self, pos: usize, vm: &VirtualMachine) -> PyResult {
380 self.try_not_released(vm)?;
383 let bytes = self.buffer.obj_bytes();
384 self.format_spec
386 .unpack(&bytes[pos..pos + self.format_spec.size()], vm)
387 .map(|x| {
388 if x.as_slice().len() == 1 {
389 x.as_slice()[0].to_owned()
390 } else {
391 x.into()
392 }
393 })
394 }
395
396 fn pos_from_multi_index(&self, indexes: &[isize], vm: &VirtualMachine) -> PyResult<usize> {
397 match indexes.len().cmp(&self.desc.ndim()) {
398 Ordering::Less => {
399 return Err(vm.new_not_implemented_error("sub-views are not implemented"));
400 }
401 Ordering::Greater => {
402 return Err(vm.new_type_error(format!(
403 "cannot index {}-dimension view with {}-element tuple",
404 self.desc.ndim(),
405 indexes.len()
406 )));
407 }
408 Ordering::Equal => (),
409 }
410
411 Ok(self.desc.position(indexes, vm)? as usize)
412 }
413
414 fn init_len(&mut self) {
415 let product: usize = self.desc.dim_desc.iter().map(|x| x.0).product();
416 self.desc.len = product * self.desc.itemsize;
417 }
418
419 fn adjust_position(&mut self, dim: usize, delta: isize) {
423 match self.desc.dim_desc[..dim]
424 .iter()
425 .rposition(|&(_, _, suboffset)| suboffset != 0)
426 {
427 Some(n) => self.desc.dim_desc[n].2 += delta,
428 None => self.desc.offset += delta,
429 }
430 }
431
432 fn init_range(&mut self, range: Range<usize>, dim: usize) {
433 let (shape, stride, _) = self.desc.dim_desc[dim];
434 debug_assert!(shape >= range.len());
435
436 self.adjust_position(dim, stride * range.start as isize);
437 self.desc.dim_desc[dim].0 = range.len();
438 }
439
440 fn init_slice(&mut self, slice: &Py<PySlice>, dim: usize, vm: &VirtualMachine) -> PyResult<()> {
442 let (shape, stride, _) = self.desc.dim_desc[dim];
443 let slice = slice.to_saturated(vm)?;
444 let (start, slice_len) = slice.adjust_indices_start(shape);
445
446 self.adjust_position(dim, stride.wrapping_mul(start));
449 self.desc.dim_desc[dim].0 = slice_len;
450 self.desc.dim_desc[dim].1 = stride.wrapping_mul(slice.step());
451
452 Ok(())
453 }
454
455 fn _to_list(
456 &self,
457 bytes: &[u8],
458 mut index: isize,
459 dim: usize,
460 vm: &VirtualMachine,
461 ) -> PyResult<PyListRef> {
462 let (shape, stride, suboffset) = self.desc.dim_desc[dim];
463 if dim + 1 == self.desc.ndim() {
464 let mut v = Vec::with_capacity(shape);
465 for _ in 0..shape {
466 let pos = (index + suboffset) as usize;
467 let obj = format_unpack(
468 &self.format_spec,
469 &bytes[pos..pos + self.format_spec.size()],
470 vm,
471 )?;
472 v.push(obj);
473 index += stride;
474 }
475 return Ok(vm.ctx.new_list(v));
476 }
477
478 let mut v = Vec::with_capacity(shape);
479 for _ in 0..shape {
480 let obj = self._to_list(bytes, index + suboffset, dim + 1, vm)?.into();
481 v.push(obj);
482 index += stride;
483 }
484 Ok(vm.ctx.new_list(v))
485 }
486
487 fn eq(zelf: &Py<Self>, other: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
488 if zelf.is(other) {
489 return Ok(true);
490 }
491 if zelf.released.load() {
492 return Ok(false);
493 }
494
495 let other = if let Some(mv) = other.downcast_ref::<Self>() {
496 if mv.released.load() {
497 return Ok(false);
498 }
499 let mut view = mv.buffer.detached();
502 view.desc = mv.desc.clone();
503 view
504 } else {
505 match PyBuffer::try_from_borrowed_object(vm, other) {
506 Ok(buf) => buf,
507 Err(_) => return Ok(false),
508 }
509 };
510
511 if !is_equiv_shape(&zelf.desc, &other.desc) {
512 return Ok(false);
513 }
514
515 let a_format_spec = &zelf.format_spec;
516 let b_format_spec = &Self::parse_format(&other.desc.format, vm)?;
517 let a_itemsize = a_format_spec.size();
520 let b_itemsize = b_format_spec.size();
521
522 if zelf.desc.ndim() == 0 {
523 let a_pos = zelf.desc.offset as usize;
524 let b_pos = other.desc.offset as usize;
525 let a_bytes = zelf.buffer.obj_bytes();
526 let a_val = format_unpack(a_format_spec, &a_bytes[a_pos..a_pos + a_itemsize], vm)?;
527 drop(a_bytes);
528 let b_bytes = other.obj_bytes();
529 let b_val = format_unpack(b_format_spec, &b_bytes[b_pos..b_pos + b_itemsize], vm)?;
530 drop(b_bytes);
531 return vm.bool_eq(&a_val, &b_val);
532 }
533
534 let mut ret = Ok(true);
536 let a_bytes = zelf.buffer.obj_bytes();
537 let b_bytes = other.obj_bytes();
538 zelf.desc.zip_eq(&other.desc, false, |a_range, b_range| {
539 let a_range = a_range.start as usize..a_range.start as usize + a_itemsize;
540 let b_range = b_range.start as usize..b_range.start as usize + b_itemsize;
541 let a_val = match format_unpack(a_format_spec, &a_bytes[a_range], vm) {
542 Ok(val) => val,
543 Err(e) => {
544 ret = Err(e);
545 return true;
546 }
547 };
548 let b_val = match format_unpack(b_format_spec, &b_bytes[b_range], vm) {
549 Ok(val) => val,
550 Err(e) => {
551 ret = Err(e);
552 return true;
553 }
554 };
555 ret = vm.bool_eq(&a_val, &b_val);
556 if let Ok(b) = ret { !b } else { true }
557 });
558 ret
559 }
560
561 fn as_contiguous(&self) -> Option<BorrowedValue<'_, [u8]>> {
562 self.desc.is_contiguous().then(|| {
563 let range = self.desc.contiguous_range();
564 BorrowedValue::map(self.buffer.obj_bytes(), |x| &x[range])
565 })
566 }
567
568 fn _as_contiguous_mut(&self) -> Option<BorrowedValueMut<'_, [u8]>> {
569 self.desc.is_contiguous().then(|| {
570 let range = self.desc.contiguous_range();
571 BorrowedValueMut::map(self.buffer.obj_bytes_mut(), |x| &mut x[range])
572 })
573 }
574
575 fn append_to(&self, buf: &mut Vec<u8>) {
576 if let Some(bytes) = self.as_contiguous() {
577 buf.extend_from_slice(&bytes);
578 } else {
579 buf.reserve(self.desc.len);
580 let bytes = &*self.buffer.obj_bytes();
581 self.desc.for_each_segment(true, |range| {
582 buf.extend_from_slice(&bytes[range.start as usize..range.end as usize]);
583 })
584 }
585 }
586
587 fn contiguous_or_collect<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
588 let borrowed;
589 let mut collected;
590 let v = if let Some(bytes) = self.as_contiguous() {
591 borrowed = bytes;
592 &*borrowed
593 } else {
594 collected = vec![];
595 self.append_to(&mut collected);
596 &collected
597 };
598 f(v)
599 }
600
601 pub fn to_contiguous(&self, vm: &VirtualMachine) -> PyBuffer {
604 let mut data = vec![];
605 self.append_to(&mut data);
606
607 let desc = self.desc.contiguous();
608
609 VecBuffer::from(data)
610 .into_ref(&vm.ctx)
611 .into_pybuffer_with_descriptor(desc)
612 }
613}
614
615impl Py<PyMemoryView> {
616 fn setitem_by_slice(
617 &self,
618 slice: &Py<PySlice>,
619 src: PyObjectRef,
620 vm: &VirtualMachine,
621 ) -> PyResult<()> {
622 if self.desc.ndim() != 1 {
623 return Err(vm.new_not_implemented_error("sub-view are not implemented"));
624 }
625
626 let mut dest = self.borrowed_view();
627 dest.init_slice(slice, 0, vm)?;
628 dest.init_len();
629
630 if self.is(&src) {
631 return if !is_equiv_structure(&self.desc, &dest.desc) {
632 Err(vm.new_value_error(
633 "memoryview assignment: lvalue and rvalue have different structures",
634 ))
635 } else {
636 Ok(())
638 };
639 };
640
641 let src = PyBuffer::try_from_object(vm, src)?;
643 self.try_not_released(vm)?;
646
647 if !is_equiv_structure(&src.desc, &dest.desc) {
648 return Err(vm.new_value_error(
649 "memoryview assignment: lvalue and rvalue have different structures",
650 ));
651 }
652
653 let src = if root_exporter(&src).is(&root_exporter(&dest.buffer)) {
657 let owned = src.to_contiguous(vm);
658 drop(src);
659 owned
660 } else {
661 src
662 };
663
664 let mut bytes_mut = dest.buffer.obj_bytes_mut();
665 let src_bytes = src.obj_bytes();
666 dest.desc.zip_eq(&src.desc, true, |a_range, b_range| {
667 let a_range = a_range.start as usize..a_range.end as usize;
668 let b_range = b_range.start as usize..b_range.end as usize;
669 bytes_mut[a_range].copy_from_slice(&src_bytes[b_range]);
670 false
671 });
672
673 Ok(())
674 }
675}
676
677impl PyMemoryView {
678 pub fn release(&self) {
681 if self.released.compare_exchange(false, true).is_ok() {
682 self.buffer.release();
683 }
684 }
685
686 fn while_exported<R>(&self, f: impl FnOnce() -> R) -> R {
689 self.exports.fetch_add(1);
690 let result = f();
691 self.exports.fetch_sub(1);
692 result
693 }
694
695 fn __getitem__(zelf: PyRef<Self>, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
696 zelf.try_not_released(vm)?;
697 if zelf.desc.ndim() == 0 {
698 if needle.is(&vm.ctx.ellipsis) {
700 return Ok(zelf.into());
701 }
702 if let Some(tuple) = needle.downcast_ref::<PyTuple>()
703 && tuple.as_slice().is_empty()
704 {
705 return zelf.unpack_single(zelf.desc.offset as usize, vm);
706 }
707 return Err(vm.new_type_error("invalid indexing of 0-dim memory"));
708 }
709
710 match SubscriptNeedle::try_from_object(vm, needle)? {
711 SubscriptNeedle::Index(i) => zelf.getitem_by_idx(i, vm),
712 SubscriptNeedle::Slice(slice) => zelf.getitem_by_slice(&slice, vm),
713 SubscriptNeedle::MultiIndex(indices) => zelf.getitem_by_multi_idx(&indices, vm),
714 }
715 }
716
717 fn __delitem__(&self, _needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
718 self.try_not_released(vm)?;
719 if self.desc.readonly {
722 return Err(vm.new_type_error("cannot modify read-only memory"));
723 }
724 Err(vm.new_type_error("cannot delete memory"))
725 }
726
727 fn __len__(&self, vm: &VirtualMachine) -> PyResult<usize> {
728 self.try_not_released(vm)?;
729 if self.desc.ndim() == 0 {
730 Err(vm.new_type_error("0-dim memory has no length"))
732 } else {
733 Ok(self.desc.dim_desc[0].0)
735 }
736 }
737
738 fn cast_to_1d(&self, format: &Py<PyUtf8Str>, vm: &VirtualMachine) -> PyResult<Self> {
739 let format_str = format.as_str();
740 let Some(dest_char) = Self::native_fmtchar(format_str) else {
741 return Err(vm.new_value_error(
742 "memoryview: destination format must be a native single character format prefixed with an optional '@'",
743 ));
744 };
745 let source_is_bytes = Self::native_fmtchar(&self.desc.format).is_some_and(is_byte_fmtchar);
749 if !source_is_bytes && !is_byte_fmtchar(dest_char) {
750 return Err(vm.new_type_error("memoryview: cannot cast between two non-byte formats"));
751 }
752 let format_spec = Self::parse_format(format_str, vm)?;
753 let itemsize = format_spec.size();
754 if !self.desc.len.is_multiple_of(itemsize) {
755 return Err(vm.new_type_error("memoryview: length is not a multiple of itemsize"));
756 }
757
758 let zelf = Self {
759 buffer: self.buffer.clone(),
760 released: AtomicCell::new(false),
761 restricted: AtomicCell::new(false),
762 format_spec,
763 desc: BufferDescriptor {
764 len: self.desc.len,
765 offset: self.desc.offset,
766 readonly: self.desc.readonly,
767 itemsize,
768 format: format_str.to_owned().into(),
769 dim_desc: vec![(self.desc.len / itemsize, itemsize as isize, 0)],
770 },
771 hash: OnceCell::new(),
772 exports: AtomicCell::new(0),
773 };
774 Ok(zelf)
775 }
776}
777
778#[pyclass(
779 itemsize = core::mem::size_of::<isize>(),
780 with(
781 Py,
782 Hashable,
783 Comparable,
784 AsBuffer,
785 AsMapping,
786 AsSequence,
787 Constructor,
788 Iterable,
789 Representable
790 ),
791 flags(SEQUENCE, HAS_WEAKREF)
792)]
793impl PyMemoryView {}
794
795#[pyclass]
796impl Py<PyMemoryView> {
797 fn __setitem__(
798 &self,
799 needle: PyObjectRef,
800 value: PyObjectRef,
801 vm: &VirtualMachine,
802 ) -> PyResult<()> {
803 self.try_not_released(vm)?;
804 if self.desc.readonly {
805 return Err(vm.new_type_error("cannot modify read-only memory"));
806 }
807 if self.desc.ndim() == 0 {
808 if needle.is(&vm.ctx.ellipsis) {
810 return self.pack_single(self.desc.offset as usize, value, vm);
811 } else if let Some(tuple) = needle.downcast_ref::<PyTuple>()
812 && tuple.as_slice().is_empty()
813 {
814 return self.pack_single(self.desc.offset as usize, value, vm);
815 }
816 return Err(vm.new_type_error("invalid indexing of 0-dim memory"));
817 }
818 match SubscriptNeedle::try_from_object(vm, needle)? {
819 SubscriptNeedle::Index(i) => self.setitem_by_idx(i, value, vm),
820 SubscriptNeedle::Slice(slice) => self.setitem_by_slice(&slice, value, vm),
821 SubscriptNeedle::MultiIndex(indices) => self.setitem_by_multi_idx(&indices, value, vm),
822 }
823 }
824
825 #[pymethod]
826 fn __reduce_ex__(&self, _proto: usize, vm: &VirtualMachine) -> PyResult {
827 self.__reduce__(vm)
828 }
829
830 #[pymethod]
831 fn __reduce__(&self, vm: &VirtualMachine) -> PyResult {
832 Err(vm.new_type_error("cannot pickle 'memoryview' object"))
833 }
834
835 #[pyclassmethod]
836 fn __class_getitem__(
837 cls: PyTypeRef,
838 object: PyObjectRef,
839 vm: &VirtualMachine,
840 ) -> PyResult<PyGenericAlias> {
841 PyGenericAlias::from_args(cls, object, vm)
842 }
843
844 #[pyclassmethod]
845 fn _from_flags(
846 _cls: PyTypeRef,
847 args: PyMemoryViewFromFlagsArgs,
848 vm: &VirtualMachine,
849 ) -> PyResult<PyRef<PyMemoryView>> {
850 let flags =
851 BufferFlags::from_bits_retain(args.flags.as_ref().try_to_primitive::<i32>(vm)? as u32);
852 PyMemoryView::from_object_with_flags(&args.object, flags, vm).map(|mv| mv.into_ref(&vm.ctx))
853 }
854
855 #[pymethod(name = "release")]
856 fn py_release(&self, vm: &VirtualMachine) -> PyResult<()> {
857 let exports = self.exports.load();
859 if !self.released.load() && exports > 0 {
860 let plural = if exports == 1 { "" } else { "s" };
861 return Err(
862 vm.new_buffer_error(format!("memoryview has {exports} exported buffer{plural}"))
863 );
864 }
865 self.release();
866 Ok(())
867 }
868
869 #[pygetset]
870 fn obj(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
871 self.try_not_released(vm)?;
872 Ok(if self.buffer.obj.downcastable::<PyBufferWindow>() {
875 vm.ctx.none()
876 } else {
877 self.buffer.obj.clone()
878 })
879 }
880
881 #[pygetset]
882 fn nbytes(&self, vm: &VirtualMachine) -> PyResult<usize> {
883 self.try_not_released(vm).map(|_| self.desc.len)
884 }
885
886 #[pygetset]
887 fn readonly(&self, vm: &VirtualMachine) -> PyResult<bool> {
888 self.try_not_released(vm).map(|_| self.desc.readonly)
889 }
890
891 #[pygetset]
892 fn itemsize(&self, vm: &VirtualMachine) -> PyResult<usize> {
893 self.try_not_released(vm).map(|_| self.desc.itemsize)
894 }
895
896 #[pygetset]
897 fn ndim(&self, vm: &VirtualMachine) -> PyResult<usize> {
898 self.try_not_released(vm).map(|_| self.desc.ndim())
899 }
900
901 #[pygetset]
902 fn shape(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
903 self.try_not_released(vm)?;
904 Ok(vm.ctx.new_tuple(
905 self.desc
906 .dim_desc
907 .iter()
908 .map(|(shape, _, _)| shape.to_pyobject(vm))
909 .collect(),
910 ))
911 }
912
913 #[pygetset]
914 fn strides(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
915 self.try_not_released(vm)?;
916 Ok(vm.ctx.new_tuple(
917 self.desc
918 .dim_desc
919 .iter()
920 .map(|(_, stride, _)| stride.to_pyobject(vm))
921 .collect(),
922 ))
923 }
924
925 #[pygetset]
926 fn suboffsets(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
927 self.try_not_released(vm)?;
928 let has_suboffsets = self
929 .desc
930 .dim_desc
931 .iter()
932 .any(|(_, _, suboffset)| *suboffset != 0);
933 if has_suboffsets {
934 Ok(vm.ctx.new_tuple(
935 self.desc
936 .dim_desc
937 .iter()
938 .map(|(_, _, suboffset)| suboffset.to_pyobject(vm))
939 .collect(),
940 ))
941 } else {
942 Ok(vm.ctx.empty_tuple.clone())
943 }
944 }
945
946 #[pygetset]
947 fn format(&self, vm: &VirtualMachine) -> PyResult<PyStr> {
948 self.try_not_released(vm)
949 .map(|_| PyStr::from(self.desc.format.clone()))
950 }
951
952 #[pygetset]
953 fn contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
954 self.try_not_released(vm)
955 .map(|_| self.desc.is_contiguous() || self.desc.is_fortran_contiguous())
956 }
957
958 #[pygetset]
959 fn c_contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
960 self.try_not_released(vm).map(|_| self.desc.is_contiguous())
961 }
962
963 #[pygetset]
964 fn f_contiguous(&self, vm: &VirtualMachine) -> PyResult<bool> {
965 self.try_not_released(vm)
966 .map(|_| self.desc.is_fortran_contiguous())
967 }
968
969 #[pymethod]
970 fn __enter__(zelf: PyRef<PyMemoryView>, vm: &VirtualMachine) -> PyResult<PyRef<PyMemoryView>> {
971 zelf.try_not_released(vm).map(|_| zelf)
972 }
973
974 #[pymethod]
976 fn __exit__(
977 &self,
978 _exc_info: PosArgs<PyObjectRef, NameExcInfo>,
979 vm: &VirtualMachine,
980 ) -> PyResult<()> {
981 self.py_release(vm)
982 }
983
984 #[pymethod]
985 fn tobytes(&self, args: ToBytesArgs, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
986 self.try_not_released(vm)?;
987 let order = match &args.order {
988 None => Order::C,
989 Some(order) => match order.to_str() {
990 Some("C") => Order::C,
991 Some("F") => Order::Fortran,
992 Some("A") => Order::Any,
993 _ => return Err(vm.new_value_error("order must be 'C', 'F' or 'A'")),
994 },
995 };
996
997 let mut v = vec![];
998 if order == Order::Fortran && self.desc.ndim() > 1 {
1003 v.reserve(self.desc.len);
1004 let bytes = &*self.buffer.obj_bytes();
1005 self.desc.for_each_segment_fortran(|range| {
1006 v.extend_from_slice(&bytes[range.start as usize..range.end as usize]);
1007 });
1008 } else {
1009 self.append_to(&mut v);
1010 }
1011 Ok(PyBytes::from(v).into_ref(&vm.ctx))
1012 }
1013
1014 #[pymethod]
1015 fn tolist(&self, vm: &VirtualMachine) -> PyResult {
1017 self.try_not_released(vm)?;
1018 let bytes = self.buffer.obj_bytes();
1019 if self.desc.ndim() == 0 {
1020 let pos = self.desc.offset as usize;
1022 return format_unpack(
1023 &self.format_spec,
1024 &bytes[pos..pos + self.format_spec.size()],
1025 vm,
1026 );
1027 }
1028 self._to_list(&bytes, self.desc.offset, 0, vm)
1029 .map(Into::into)
1030 }
1031
1032 #[pymethod]
1033 fn toreadonly(&self, vm: &VirtualMachine) -> PyResult<PyRef<PyMemoryView>> {
1034 self.try_usable(vm)?;
1035 let mut other = self.new_view();
1036 other.desc.readonly = true;
1037 Ok(other.into_ref(&vm.ctx))
1038 }
1039
1040 #[pymethod]
1041 fn hex(&self, options: ByteInnerHexOptions, vm: &VirtualMachine) -> PyResult<String> {
1042 self.try_not_released(vm)?;
1043 let (sep, bytes_per_sep) = self.while_exported(|| options.resolve(vm))?;
1046 self.try_not_released(vm)?;
1047 Ok(self.contiguous_or_collect(|x| bytes_to_hex(x, sep, bytes_per_sep)))
1048 }
1049
1050 #[pymethod]
1051 fn count(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
1052 self.try_not_released(vm)?;
1053 if self.desc.ndim() != 1 {
1054 return Err(
1055 vm.new_not_implemented_error("multi-dimensional sub-views are not implemented")
1056 );
1057 }
1058 let len = self.desc.dim_desc[0].0;
1059 let mut count = 0;
1060 for i in 0..len {
1061 let item = self.getitem_by_idx(i as isize, vm)?;
1062 if vm.bool_eq(&item, &value)? {
1063 count += 1;
1064 }
1065 }
1066 Ok(count)
1067 }
1068
1069 #[pymethod]
1070 fn index(&self, args: MemoryIndexArgs, vm: &VirtualMachine) -> PyResult<usize> {
1071 self.try_not_released(vm)?;
1072 if self.desc.ndim() != 1 {
1073 return Err(
1074 vm.new_not_implemented_error("multi-dimensional sub-views are not implemented")
1075 );
1076 }
1077 let len = self.desc.dim_desc[0].0;
1078 let MemoryIndexArgs { value, start, stop } = args;
1079
1080 let start = if start < 0 {
1081 (start + len as isize).max(0) as usize
1082 } else {
1083 (start as usize).min(len)
1084 };
1085 let stop = if stop < 0 {
1086 (stop + len as isize).max(0) as usize
1087 } else {
1088 (stop as usize).min(len)
1089 };
1090
1091 for i in start..stop {
1092 let item = self.getitem_by_idx(i as isize, vm)?;
1093 if vm.bool_eq(&item, &value)? {
1094 return Ok(i);
1095 }
1096 }
1097 Err(vm.new_value_error("memoryview.index(x): x not in memoryview"))
1098 }
1099
1100 #[pymethod]
1101 fn cast(&self, args: CastArgs, vm: &VirtualMachine) -> PyResult<PyRef<PyMemoryView>> {
1102 self.try_usable(vm)?;
1103 if !self.desc.is_contiguous() {
1104 return Err(vm.new_type_error("memoryview: casts are restricted to C-contiguous views"));
1105 }
1106
1107 let CastArgs { format, shape } = args;
1108
1109 if let OptionalArg::Present(shape) = shape {
1110 if self.desc.is_zero_in_shape() {
1111 return Err(vm.new_type_error(
1112 "memoryview: cannot cast view with zeros in shape or strides",
1113 ));
1114 }
1115
1116 let tup;
1117 let list;
1118 let list_borrow;
1119 let shape = match shape {
1120 Either::A(shape) => {
1121 tup = shape;
1122 tup.as_slice()
1123 }
1124 Either::B(shape) => {
1125 list = shape;
1126 list_borrow = list.borrow_vec();
1127 &list_borrow
1128 }
1129 };
1130
1131 let shape_ndim = shape.len();
1132 if shape_ndim > MAX_NDIM {
1133 return Err(vm.new_value_error(format!(
1134 "memoryview: number of dimensions must not exceed {MAX_NDIM}"
1135 )));
1136 }
1137 if self.desc.ndim() != 1 && shape_ndim != 1 {
1138 return Err(vm.new_type_error("memoryview: cast must be 1D -> ND or ND -> 1D"));
1139 }
1140
1141 let mut other = self.cast_to_1d(&format, vm)?;
1142 let itemsize = other.desc.itemsize;
1143
1144 if shape_ndim == 0 {
1146 if itemsize != other.desc.len {
1147 return Err(
1148 vm.new_type_error("memoryview: product(shape) * itemsize != buffer size")
1149 );
1150 }
1151 other.desc.dim_desc = vec![];
1152 return Ok(other.into_ref(&vm.ctx));
1153 }
1154
1155 let mut product_shape = itemsize;
1156 let mut dim_descriptor = Vec::with_capacity(shape_ndim);
1157
1158 for x in shape {
1159 let x = x
1160 .downcast_ref::<PyInt>()
1161 .ok_or_else(|| {
1162 vm.new_type_error("memoryview.cast(): elements of shape must be integers")
1163 })?
1164 .try_to_primitive::<usize>(vm)
1165 .ok()
1166 .filter(|x| *x > 0)
1167 .ok_or_else(|| {
1168 vm.new_value_error(
1169 "memoryview.cast(): elements of shape must be integers > 0",
1170 )
1171 })?;
1172
1173 if x > isize::MAX as usize / product_shape {
1174 return Err(vm.new_value_error("memoryview.cast(): product(shape) > SSIZE_MAX"));
1175 }
1176 product_shape *= x;
1177 dim_descriptor.push((x, 0, 0));
1178 }
1179
1180 dim_descriptor.last_mut().unwrap().1 = itemsize as isize;
1181 for i in (0..dim_descriptor.len() - 1).rev() {
1182 dim_descriptor[i].1 = dim_descriptor[i + 1].1 * dim_descriptor[i + 1].0 as isize;
1183 }
1184
1185 if product_shape != other.desc.len {
1186 return Err(
1187 vm.new_type_error("memoryview: product(shape) * itemsize != buffer size")
1188 );
1189 }
1190
1191 other.desc.dim_desc = dim_descriptor;
1192
1193 Ok(other.into_ref(&vm.ctx))
1194 } else {
1195 Ok(self.cast_to_1d(&format, vm)?.into_ref(&vm.ctx))
1196 }
1197 }
1198}
1199
1200#[derive(FromArgs)]
1201struct MemoryIndexArgs {
1202 #[pyarg(positional)]
1203 value: PyObjectRef,
1204 #[pyarg(positional, default)]
1205 start: isize,
1206 #[pyarg(positional, default = isize::MAX)]
1208 stop: isize,
1209}
1210
1211#[derive(FromArgs)]
1212struct ToBytesArgs {
1213 #[pyarg(any, default, py_default = "'C'")]
1215 order: Option<PyStrRef>,
1216}
1217
1218#[derive(PartialEq, Eq)]
1220enum Order {
1221 C,
1222 Fortran,
1223 Any,
1224}
1225
1226#[derive(FromArgs)]
1227struct CastArgs {
1228 #[pyarg(any)]
1229 format: PyUtf8StrRef,
1230 #[pyarg(any, optional)]
1231 shape: OptionalArg<Either<PyTupleRef, PyListRef>>,
1232}
1233
1234enum SubscriptNeedle {
1235 Index(isize),
1236 Slice(PyRef<PySlice>),
1237 MultiIndex(Vec<isize>),
1238 }
1240
1241impl TryFromObject for SubscriptNeedle {
1247 fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
1248 if obj.number().is_index() {
1249 return Ok(Self::Index(obj.try_index(vm)?.try_to_primitive(vm)?));
1250 }
1251 if obj.downcastable::<PySlice>() {
1252 return Ok(Self::Slice(unsafe { obj.downcast_unchecked::<PySlice>() }));
1253 }
1254 if let Some(tuple) = obj.downcast_ref::<PyTuple>() {
1255 if tuple.as_slice().iter().all(|x| x.number().is_index()) {
1256 let indices = tuple
1259 .as_slice()
1260 .iter()
1261 .map(|x| x.try_index(vm)?.try_to_primitive::<isize>(vm))
1262 .try_collect()?;
1263 return Ok(Self::MultiIndex(indices));
1264 }
1265 if tuple.as_slice().iter().all(|x| x.downcastable::<PySlice>()) {
1266 return Err(
1267 vm.new_not_implemented_error("multi-dimensional slicing is not implemented")
1268 );
1269 }
1270 }
1271 Err(vm.new_type_error("memoryview: invalid slice key"))
1272 }
1273}
1274
1275static BUFFER_METHODS: BufferMethods = BufferMethods {
1276 obj_bytes: |buffer| buffer.obj_as::<PyMemoryView>().buffer.obj_bytes(),
1277 obj_bytes_mut: |buffer| buffer.obj_as::<PyMemoryView>().buffer.obj_bytes_mut(),
1278 release: |buffer| {
1282 let mv = buffer.obj_as::<PyMemoryView>();
1283 mv.exports.fetch_sub(1);
1284 mv.buffer.release_share();
1285 },
1286 retain: |buffer| {
1287 let mv = buffer.obj_as::<PyMemoryView>();
1288 mv.exports.fetch_add(1);
1289 mv.buffer.retain_share();
1290 },
1291};
1292
1293impl AsBuffer for PyMemoryView {
1294 const RELEASE_BUFFER: bool = true;
1295
1296 fn slot_as_buffer(
1298 zelf: &PyObject,
1299 flags: BufferFlags,
1300 vm: &VirtualMachine,
1301 ) -> PyResult<PyBuffer> {
1302 let zelf = zelf
1303 .downcast_ref::<Self>()
1304 .ok_or_else(|| vm.new_type_error("unexpected payload for as_buffer"))?;
1305 zelf.try_usable(vm)?;
1306 Ok(PyBuffer::new(
1307 zelf.to_owned().into(),
1308 zelf.requested_desc(flags, vm)?,
1309 &BUFFER_METHODS,
1310 ))
1311 }
1312
1313 fn as_buffer(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyBuffer> {
1314 zelf.try_usable(vm)?;
1315 Ok(PyBuffer::new(
1318 zelf.to_owned().into(),
1319 zelf.desc.clone(),
1320 &BUFFER_METHODS,
1321 ))
1322 }
1323}
1324
1325impl AsMapping for PyMemoryView {
1326 fn as_mapping() -> &'static PyMappingMethods {
1327 static AS_MAPPING: PyMappingMethods = PyMappingMethods {
1328 length: atomic_func!(|mapping, vm| PyMemoryView::mapping_downcast(mapping).__len__(vm)),
1329 subscript: atomic_func!(|mapping, needle, vm| {
1330 let zelf = PyMemoryView::mapping_downcast(mapping);
1331 PyMemoryView::__getitem__(zelf.to_owned(), needle.to_owned(), vm)
1332 }),
1333 ass_subscript: atomic_func!(|mapping, needle, value, vm| {
1334 let zelf = PyMemoryView::mapping_downcast(mapping);
1335 if let Some(value) = value {
1336 zelf.__setitem__(needle.to_owned(), value, vm)
1337 } else {
1338 zelf.__delitem__(needle.to_owned(), vm)
1339 }
1340 }),
1341 };
1342 &AS_MAPPING
1343 }
1344}
1345
1346impl AsSequence for PyMemoryView {
1347 fn as_sequence() -> &'static PySequenceMethods {
1348 static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1349 length: atomic_func!(|seq, vm| {
1350 let zelf = PyMemoryView::sequence_downcast(seq);
1351 zelf.try_not_released(vm)?;
1352 zelf.__len__(vm)
1353 }),
1354 item: atomic_func!(|seq, i, vm| {
1355 let zelf = PyMemoryView::sequence_downcast(seq);
1356 zelf.try_not_released(vm)?;
1357 zelf.getitem_by_idx(i, vm)
1358 }),
1359 ..PySequenceMethods::NOT_IMPLEMENTED
1360 });
1361 &AS_SEQUENCE
1362 }
1363}
1364
1365impl Comparable for PyMemoryView {
1366 fn cmp(
1367 zelf: &Py<Self>,
1368 other: &PyObject,
1369 op: PyComparisonOp,
1370 vm: &VirtualMachine,
1371 ) -> PyResult<PyComparisonValue> {
1372 match op {
1373 PyComparisonOp::Ne => {
1374 Self::eq(zelf, other, vm).map(|x| PyComparisonValue::Implemented(!x))
1375 }
1376 PyComparisonOp::Eq => Self::eq(zelf, other, vm).map(PyComparisonValue::Implemented),
1377 _ => Err(vm.new_type_error(format!(
1378 "'{}' not supported between instances of '{}' and '{}'",
1379 op.operator_token(),
1380 zelf.class().slot_name(),
1381 other.class().slot_name()
1382 ))),
1383 }
1384 }
1385}
1386
1387impl Hashable for PyMemoryView {
1388 fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
1389 if let Some(val) = zelf.hash.get() {
1390 return Ok(*val);
1391 }
1392 zelf.try_not_released(vm)?;
1393 if !zelf.desc.readonly {
1394 return Err(vm.new_value_error("cannot hash writable memoryview object"));
1395 }
1396 if !Self::native_fmtchar(&zelf.desc.format).is_some_and(is_byte_fmtchar) {
1399 return Err(
1400 vm.new_value_error("memoryview: hashing is restricted to formats 'B', 'b' or 'c'")
1401 );
1402 }
1403 if !zelf.buffer.obj.downcastable::<PyBufferWindow>() {
1407 zelf.while_exported(|| zelf.buffer.obj.hash(vm))?;
1408 }
1409 let val = zelf.contiguous_or_collect(|bytes| crate::vm::hash_secret().hash_bytes(bytes));
1410 let _ = zelf.hash.set(val);
1411 Ok(*zelf.hash.get().unwrap())
1412 }
1413}
1414
1415impl PyPayload for PyMemoryView {
1416 #[inline]
1417 fn class(ctx: &Context) -> &'static Py<PyType> {
1418 ctx.types.memoryview_type
1419 }
1420}
1421
1422impl Representable for PyMemoryView {
1423 #[inline]
1424 fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1425 let repr = if zelf.released.load() {
1426 format!("<released memory at {:#x}>", zelf.get_id())
1427 } else {
1428 format!("<memory at {:#x}>", zelf.get_id())
1429 };
1430 Ok(repr)
1431 }
1432}
1433
1434pub(crate) fn init(ctx: &'static Context) {
1435 PyMemoryView::extend_class(ctx, ctx.types.memoryview_type);
1436 PyMemoryViewIterator::extend_class(ctx, ctx.types.memoryviewiterator_type);
1437 let wrapper_type = PyBufferWrapper::init_builtin_type();
1438 wrapper_type.slots.has_release_buffer.store(true);
1441 PyBufferWrapper::extend_class(ctx, wrapper_type);
1442 PyBufferWindow::extend_class(ctx, PyBufferWindow::init_builtin_type());
1443}
1444
1445#[pyclass(module = false, name = "_buffer_wrapper", traverse)]
1446#[derive(Debug)]
1447struct PyBufferWrapper {
1448 exporter: PyObjectRef,
1450 returned_mv: PyMutex<Option<PyRef<PyMemoryView>>>,
1452 view: PyBuffer,
1455 #[pytraverse(skip)]
1457 exports: AtomicCell<usize>,
1458}
1459
1460impl PyPayload for PyBufferWrapper {
1461 fn class(_ctx: &Context) -> &'static Py<PyType> {
1462 Self::static_type()
1463 }
1464}
1465
1466#[pyclass(flags(DISALLOW_INSTANTIATION))]
1467impl PyBufferWrapper {}
1468
1469static BUFFER_WRAPPER_METHODS: BufferMethods = BufferMethods {
1470 obj_bytes: |buffer| buffer.obj_as::<PyBufferWrapper>().view.obj_bytes(),
1471 obj_bytes_mut: |buffer| buffer.obj_as::<PyBufferWrapper>().view.obj_bytes_mut(),
1472 retain: |buffer| {
1473 let wrapper = buffer.obj_as::<PyBufferWrapper>();
1474 wrapper.exports.fetch_add(1);
1475 wrapper.view.retain_share();
1476 },
1477 release: |buffer| {
1479 let wrapper = buffer.obj_as::<PyBufferWrapper>();
1480 wrapper.view.release_share();
1481 if wrapper.exports.fetch_sub(1) != 1 {
1482 return;
1483 }
1484 let Some(mv) = wrapper.returned_mv.lock().take() else {
1485 return;
1486 };
1487 if !mv.buffer.obj.is(&wrapper.exporter)
1490 && wrapper
1491 .exporter
1492 .class()
1493 .slots()
1494 .python_release_buffer
1495 .load()
1496 {
1497 call_python_release_buffer(&wrapper.exporter, mv.clone());
1498 }
1499 drop(mv);
1501 },
1502};
1503
1504#[pyclass(module = false, name = "_buffer_window", traverse)]
1508#[derive(Debug)]
1509struct PyBufferWindow {
1510 source: PyBuffer,
1511}
1512
1513impl PyPayload for PyBufferWindow {
1514 fn class(_ctx: &Context) -> &'static Py<PyType> {
1515 Self::static_type()
1516 }
1517}
1518
1519#[pyclass(flags(DISALLOW_INSTANTIATION))]
1520impl PyBufferWindow {}
1521
1522static BUFFER_WINDOW_METHODS: BufferMethods = BufferMethods {
1523 obj_bytes: |buffer| buffer.obj_as::<PyBufferWindow>().source.obj_bytes(),
1524 obj_bytes_mut: |buffer| buffer.obj_as::<PyBufferWindow>().source.obj_bytes_mut(),
1525 retain: |_buffer| {},
1526 release: |_buffer| {},
1527};
1528
1529fn root_exporter(buffer: &PyBuffer) -> PyObjectRef {
1537 let mut obj = buffer.obj.clone();
1538 loop {
1539 let next = if let Some(view) = obj.downcast_ref::<PyMemoryView>() {
1540 view.buffer.obj.clone()
1541 } else if let Some(wrapper) = obj.downcast_ref::<PyBufferWrapper>() {
1542 wrapper.view.obj.clone()
1543 } else if let Some(window) = obj.downcast_ref::<PyBufferWindow>() {
1544 window.source.obj.clone()
1545 } else {
1546 return obj;
1547 };
1548 obj = next;
1549 }
1550}
1551
1552pub(crate) fn buffer_from_python_getbuffer(
1554 obj: &PyObject,
1555 flags: BufferFlags,
1556 vm: &VirtualMachine,
1557) -> PyResult<PyBuffer> {
1558 let flags_obj = vm.ctx.new_int(flags.bits() as i32);
1559 let ret = vm.call_special_method(obj, identifier!(vm, __buffer__), (flags_obj,))?;
1560 let mv = ret
1561 .downcast::<PyMemoryView>()
1562 .map_err(|_| vm.new_type_error("__buffer__ returned non-memoryview object"))?;
1563
1564 mv.try_usable(vm)?;
1567 let desc = mv.requested_desc(flags, vm)?;
1568 let wrapper = PyBufferWrapper {
1569 exporter: obj.to_owned(),
1570 view: mv.buffer.detached(),
1571 returned_mv: PyMutex::new(Some(mv)),
1572 exports: AtomicCell::new(0),
1573 }
1574 .into_pyobject(vm);
1575
1576 Ok(PyBuffer::new(wrapper, desc, &BUFFER_WRAPPER_METHODS))
1578}
1579
1580pub(crate) fn release_buffer_from_python(
1582 obj: &PyObject,
1583 mv: &Py<PyMemoryView>,
1584 vm: &VirtualMachine,
1585) -> PyResult<()> {
1586 let view_obj = &mv.buffer.obj;
1587 if view_obj.downcastable::<PyBufferWindow>() {
1588 return Ok(());
1591 }
1592 let exports_obj = view_obj.is(obj)
1593 || view_obj
1594 .downcast_ref::<PyBufferWrapper>()
1595 .is_some_and(|wrapper| wrapper.exporter.is(obj));
1596 if !exports_obj {
1597 return Err(vm.new_value_error("memoryview's buffer is not this object"));
1598 }
1599 if mv.released.load() {
1600 return Err(vm.new_value_error("memoryview's buffer has already been released"));
1601 }
1602 mv.release();
1603 Ok(())
1604}
1605
1606pub(crate) fn release_buffer_call_python(buffer: &PyBuffer) {
1608 crate::vm::thread::try_with_current_vm(|vm| {
1609 let exporter = buffer.obj.clone();
1610 let window = PyBufferWindow {
1611 source: buffer.detached(),
1612 }
1613 .into_pyobject(vm);
1614 let window = PyBuffer::new(window, buffer.desc.clone(), &BUFFER_WINDOW_METHODS);
1615 let mv = match PyMemoryView::from_buffer(window, vm) {
1616 Ok(mv) => mv,
1617 Err(exc) => {
1618 let msg = format!(
1619 "Exception ignored in bf_releasebuffer of {}",
1620 exporter.class().name()
1621 );
1622 return vm.run_unraisable(exc, Some(msg), vm.ctx.none());
1623 }
1624 };
1625 mv.restricted.store(true);
1628 let mv = mv.into_ref(&vm.ctx);
1629 call_python_release_buffer(&exporter, mv.clone());
1630 mv.release();
1632 });
1633}
1634
1635fn call_python_release_buffer(exporter: &PyObject, mv: PyRef<PyMemoryView>) {
1636 crate::vm::thread::try_with_current_vm(|vm| {
1637 let method = vm.get_special_method(exporter, identifier!(vm, __release_buffer__));
1638 if let Ok(Some(method)) = method
1639 && let Err(exc) = method.invoke((mv,), vm)
1640 {
1641 let msg = format!(
1642 "Exception ignored in __release_buffer__ of {}",
1643 exporter.class().name()
1644 );
1645 vm.run_unraisable(exc, Some(msg), vm.ctx.none());
1646 }
1647 });
1648}
1649
1650fn format_unpack(
1651 format_spec: &FormatSpec,
1652 bytes: &[u8],
1653 vm: &VirtualMachine,
1654) -> PyResult<PyObjectRef> {
1655 format_spec.unpack(bytes, vm).map(|x| {
1656 if x.as_slice().len() == 1 {
1657 x.as_slice()[0].to_owned()
1658 } else {
1659 x.into()
1660 }
1661 })
1662}
1663
1664const fn is_byte_fmtchar(ch: u8) -> bool {
1666 matches!(ch, b'c' | b'b' | b'B')
1667}
1668fn is_equiv_shape(a: &BufferDescriptor, b: &BufferDescriptor) -> bool {
1669 if a.ndim() != b.ndim() {
1670 return false;
1671 }
1672
1673 let a_iter = a.dim_desc.iter().map(|x| x.0);
1674 let b_iter = b.dim_desc.iter().map(|x| x.0);
1675 for (a_shape, b_shape) in a_iter.zip(b_iter) {
1676 if a_shape != b_shape {
1677 return false;
1678 }
1679 if a_shape == 0 {
1681 break;
1682 }
1683 }
1684 true
1685}
1686
1687fn is_equiv_format(a: &BufferDescriptor, b: &BufferDescriptor) -> bool {
1688 a.itemsize == b.itemsize && a.format == b.format
1690}
1691
1692fn is_equiv_structure(a: &BufferDescriptor, b: &BufferDescriptor) -> bool {
1693 is_equiv_format(a, b) && is_equiv_shape(a, b)
1694}
1695
1696impl Iterable for PyMemoryView {
1697 fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
1698 Ok(PyMemoryViewIterator {
1699 internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
1700 }
1701 .into_pyobject(vm))
1702 }
1703}
1704
1705#[pyclass(module = false, name = "memory_iterator")]
1706#[derive(Debug, Traverse)]
1707pub(crate) struct PyMemoryViewIterator {
1708 internal: PyMutex<PositionIterInternal<PyRef<PyMemoryView>>>,
1709}
1710
1711impl PyPayload for PyMemoryViewIterator {
1712 fn class(ctx: &Context) -> &'static Py<PyType> {
1713 ctx.types.memoryviewiterator_type
1714 }
1715}
1716
1717#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
1718impl Py<PyMemoryViewIterator> {
1719 #[pymethod]
1720 fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
1721 let func = builtins_iter(vm)?;
1722 Ok(self.internal.lock().reduce(
1723 func,
1724 |x| x.clone().into(),
1725 |vm| vm.ctx.empty_tuple.clone().into(),
1726 vm,
1727 ))
1728 }
1729}
1730
1731impl SelfIter for PyMemoryViewIterator {}
1732impl IterNext for PyMemoryViewIterator {
1733 fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
1734 locked_next(&zelf.internal, |mv, pos| {
1735 let len = mv.__len__(vm)?;
1736 Ok(if pos >= len {
1737 PyIterReturn::StopIteration(None)
1738 } else {
1739 PyIterReturn::Return(mv.getitem_by_idx(pos.try_into().unwrap(), vm)?)
1740 })
1741 })
1742 }
1743}