Skip to main content

rustpython_vm/builtins/
memory.rs

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
36/// The most dimensions a view can describe. PyBUF_MAX_NDIM
37const 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    /// One share of the acquisition this view is looking at, given up when the
54    /// view is released or dropped.
55    buffer: PyBuffer,
56    // the released memoryview does not mean the buffer is destroyed
57    // because the possible another memoryview is viewing from it
58    #[pytraverse(skip)]
59    released: AtomicCell<bool>,
60    /// Forbids handing out anything that outlives this view, for the window
61    /// passed to `__release_buffer__`.
62    #[pytraverse(skip)]
63    restricted: AtomicCell<bool>,
64    #[pytraverse(skip)]
65    format_spec: FormatSpec,
66    // memoryview's options could be different from buffer's options
67    #[pytraverse(skip)]
68    desc: BufferDescriptor,
69    #[pytraverse(skip)]
70    hash: OnceCell<PyHash>,
71    /// Buffers handed out of this view that have not been given back yet. The
72    /// view cannot be released while any of them is outstanding, so that what
73    /// reads them keeps reading memory that is still there. self->exports
74    #[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    /// The single native format character a cast is allowed to name, with an
92    /// optional `@` in front of it. get_native_fmtchar
93    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    /// this should be the main entrance to create the memoryview
122    /// to avoid the chained memoryview
123    pub fn from_object(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Self> {
124        Self::from_object_with_flags(obj, BufferFlags::FULL_RO, vm)
125    }
126
127    /// One share of the underlying buffer export. Used for cross-interpreter
128    /// `send_buffer` so the destination memoryview sees the same memory.
129    #[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    // PyMemoryView_FromObjectAndFlags
137    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    /// don't use this function to create the memoryview if the buffer is exporting
158    /// via another memoryview, use PyMemoryView::new_view() or PyMemoryView::from_object
159    /// to reduce the chain
160    pub fn from_buffer(buffer: PyBuffer, vm: &VirtualMachine) -> PyResult<Self> {
161        // when we get a buffer means the buffered object is size locked
162        // so we can assume the buffer's options will never change as long
163        // as memoryview is still alive
164        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    /// don't use this function to create the memoryview if the buffer is exporting
179    /// via another memoryview, use PyMemoryView::new_view() or PyMemoryView::from_object
180    /// to reduce the chain
181    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    /// this should be the only way to create a memoryview from another memoryview.
194    #[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    /// A view for a temporary that never reaches Python. It counts as no export,
208    /// so the exporter stays exactly as resizable as it already was, the way a
209    /// `Py_buffer dest = *view` copy does.
210    #[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    /// The object this view looks at, whose storage it borrows.
224    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    /// Reject a request this view cannot serve. memory_getbuf
250    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        // No exporter here produces a suboffset, so this is a guard rather than a
272        // reachable rejection.
273        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    /// The descriptor this view exports for `flags`, or an error if it cannot
288    /// serve the request. memory_getbuf
289    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        // ptr_from_index
306        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        // ptr_from_index
332        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        // The value is converted before the destination is borrowed, because the
350        // conversion runs `__index__` or `__float__`, which can read or write the
351        // same buffer.
352        // TODO: Optimize
353        // A value of the wrong kind and a value the format has no room for are
354        // different errors here, though packing reports both the same way.
355        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        // The conversion, and the index that produced `pos`, could have released
371        // the view; `pos` addresses a buffer that is no longer there.
372        // CHECK_RELEASED_INT_AGAIN
373        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        // The index that produced `pos` could have released the view.
381        // CHECK_RELEASED_AGAIN
382        self.try_not_released(vm)?;
383        let bytes = self.buffer.obj_bytes();
384        // TODO: Optimize
385        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    /// Move this view by `delta` bytes. The offset moves, unless a dimension
420    /// outside `dim` is reached through a pointer, in which case its suboffset
421    /// does.
422    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    // init_slice
441    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        // Repeated slicing multiplies the stride by the step every time, which
447        // overflows after about twenty rounds; C wraps there and so does this.
448        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            // Another view's buffer is read where it lies rather than acquired,
500            // so that a restricted view still compares. memory_richcompare
501            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        // An element is as wide as its format, which a projected descriptor can
518        // make narrower than the item size it steps by.
519        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        // TODO: optimize cmp by format
535        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    /// clone data from memoryview
602    /// keep the shape, convert to contiguous
603    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                // assign self[:] to self
637                Ok(())
638            };
639        };
640
641        // PyObject_GetBuffer(value, &src, PyBUF_FULL_RO)
642        let src = PyBuffer::try_from_object(vm, src)?;
643        // Acquiring the source ran `__buffer__`, which can release this view.
644        // copy_single: CHECK_RELEASED_INT_AGAIN
645        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        // copy_buffer reads the source as it stood before the copy began, which an
654        // overlapping assignment depends on and which also keeps the two borrows
655        // below off the same storage.
656        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    /// Give up the view's share without asking whether anything is reading it.
679    /// The teardown paths have nowhere to report a refusal.
680    pub fn release(&self) {
681        if self.released.compare_exchange(false, true).is_ok() {
682            self.buffer.release();
683        }
684    }
685
686    /// Count this view as exported while `f` runs, so Python reached from inside
687    /// it cannot release the memory being read out from under it.
688    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            // 0-d memoryview can be referenced using mv[...] or mv[()] only
699            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        // What cannot be written cannot be deleted from either, and that is
720        // the first thing answered.
721        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            // 0-dimensional memoryview has no length
731            Err(vm.new_type_error("0-dim memory has no length"))
732        } else {
733            // shape for dim[0]
734            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        // One side has to be bytes. Casting between two item types would
746        // reinterpret the items rather than re-divide the memory, and the
747        // source items were written by something that chose their type.
748        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            // TODO: merge branches when we got conditional if let
809            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        // _memory_release: what still reads this view holds it open.
858        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        // A window over a buffer being released exposes no exporter, like a
873        // Py_buffer whose obj is NULL.
874        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    // memory_exit
975    #[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        // 'A' asks for the memory as it is laid out, which is what appending a
999        // contiguous view does. Only a Fortran walk of a view that is not
1000        // already Fortran-contiguous reorders anything, and a view of fewer
1001        // than two dimensions has one layout under either name.
1002        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    // memory_tolist
1016    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            // A 0-dim view holds one element, which is what it unpacks to.
1021            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        // Measuring the separator runs Python, which must not release the bytes
1044        // being written out. memoryview_hex_impl
1045        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            // 0 ndim is single item, so the buffer has to be that one item
1145            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    // Omission is clamped to the view length.
1207    #[pyarg(positional, default = isize::MAX)]
1208    stop: isize,
1209}
1210
1211#[derive(FromArgs)]
1212struct ToBytesArgs {
1213    // Missing means C order.
1214    #[pyarg(any, default, py_default = "'C'")]
1215    order: Option<PyStrRef>,
1216}
1217
1218/// The layout a copy of a view is written in.
1219#[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    // MultiSlice(Vec<PySliceRef>),
1239}
1240
1241/// memory_subscript
1242///
1243/// Which kind of key this is follows from the types in it alone, so an item that
1244/// answers `__index__` but raises on the way reports that rather than making the
1245/// whole key invalid. is_multiindex / is_multislice
1246impl 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                // ptr_from_tuple: each item is converted where it sits, and the
1257                // conversion can run Python that releases the view.
1258                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    // memory_releasebuf / memory_getbuf: a consumer's export of this view is a
1279    // share of the acquisition the view is looking at, and one more reason the
1280    // view itself cannot be released.
1281    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    // memory_getbuf
1297    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        // memory_getbuf: *view = *base — the descriptor already says where the
1316        // view starts.
1317        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        // The hash is over the bytes, so it agrees with the hash of the same
1397        // bytes only where an item is a byte.
1398        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        // A view is no more hashable than what it looks at, and asking that runs
1404        // Python, which must not release the memory the hash is taken over.
1405        // memory_hash
1406        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    // bufferwrapper_as_buffer: bf_releasebuffer and no bf_getbuffer, so the type
1439    // has `__release_buffer__` but no `__buffer__`.
1440    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    // bw->obj: the object whose `__buffer__` produced the view
1449    exporter: PyObjectRef,
1450    // bw->mv: the memoryview `__buffer__` returned, dropped with the last export
1451    returned_mv: PyMutex<Option<PyRef<PyMemoryView>>>,
1452    /// Memory of `returned_mv`, held on behalf of every live export. The wrapper
1453    /// forwards shares of it rather than owning one.
1454    view: PyBuffer,
1455    /// Exports handed out for this wrapper; the wrapper is spent at zero.
1456    #[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    // bufferwrapper_releasebuf
1478    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        // A native release runs when the memoryview itself is torn down; only a
1488        // Python-level hook on a foreign exporter has to be called here.
1489        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        // Py_CLEAR(bw->mv): the view outlives this only if user code kept it.
1500        drop(mv);
1501    },
1502};
1503
1504// Read-only window over an exporter, handed to `__release_buffer__`. It owns no
1505// export, like a `Py_buffer` whose `obj` is NULL, so releasing it is inert and
1506// cannot recurse back into the hook.
1507#[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
1529/// The object that ultimately owns the bytes a buffer reads, seen through the
1530/// payloads that only forward to another export: a view, the wrapper holding what
1531/// a `__buffer__` returned, and the window handed to `__release_buffer__`.
1532///
1533/// Two buffers that resolve to the same object address the same storage, so
1534/// borrowing one for writing while the other is borrowed for reading would
1535/// deadlock on it.
1536fn 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
1552// slot_bf_getbuffer
1553pub(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    // PyObject_GetBuffer(ret, buffer, flags): the returned view has to satisfy
1565    // the request in its own right.
1566    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    // PyBuffer::new retains once through BUFFER_WRAPPER_METHODS.
1577    Ok(PyBuffer::new(wrapper, desc, &BUFFER_WRAPPER_METHODS))
1578}
1579
1580// wrap_releasebuffer
1581pub(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        // A window exports nothing, so there is nothing left to release, as for
1589        // a `Py_buffer` whose `obj` is NULL.
1590        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
1606// releasebuffer_call_python, for a buffer acquired from a native exporter
1607pub(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        // Restricted, so user code cannot keep anything addressing the memory
1626        // that is about to go away.
1627        mv.restricted.store(true);
1628        let mv = mv.into_ref(&vm.ctx);
1629        call_python_release_buffer(&exporter, mv.clone());
1630        // The window does not outlive the release it was made for.
1631        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
1664/// Whether `ch` names a format whose items are single bytes.
1665const 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 both shape is 0, ignore the rest
1680        if a_shape == 0 {
1681            break;
1682        }
1683    }
1684    true
1685}
1686
1687fn is_equiv_format(a: &BufferDescriptor, b: &BufferDescriptor) -> bool {
1688    // TODO: skip @
1689    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}