Skip to content

issues-2711 Add PyMemoryView#2792

Closed
ikrivosheev wants to merge 1 commit intoPyO3:mainfrom
ikrivosheev:feature/issues-2711_py_memory_view
Closed

issues-2711 Add PyMemoryView#2792
ikrivosheev wants to merge 1 commit intoPyO3:mainfrom
ikrivosheev:feature/issues-2711_py_memory_view

Conversation

@ikrivosheev
Copy link
Copy Markdown
Contributor

@ikrivosheev ikrivosheev commented Nov 30, 2022

Closed: #2711.

Comment thread src/types/memoryview.rs Outdated
pyobject_native_type_core!(PyMemoryView<T>, ffi::PyMemoryView_Type, #checkfunction=ffi::PyMemoryView_Check; T);

impl PyMemoryView<u8> {
pub fn new<'p>(py: Python<'p>, memory: &mut [u8], is_writable: bool) -> &'p PyMemoryView<u8> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this API isn't safe - the memory would have to be borrowed for as long as the returned memoryview lives, but that can't be encoded since Python objects can't have associated lifetimes (except for the 'py pseudo-lifetime).

It might be possible to offer it as unsafe fn with appropriate documentation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this isn't safe as-is. We technically already have the unsafe fn in from_ptr. It would also be very hard to use correctly, as if you pass this object to other Python code it's extremely likely this memoryview may live longer than you expect.

A safe alternative may be to accept &'static [u8], although that impairs usability a bit.

Maybe the PyMemoryView::from already started below is the thing we should encourage users to use (they can then wrap their buffers in #[pyclass] types which expose buffer access).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Copy link
Copy Markdown
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! Sorry for the long delay in reviewing this, I was a bit overloaded before the Christmas holidays.

Thank you for working on this. This is a great start, however we need to adjust some of the APIs a bit before this can be ready for merge. See other comments 🙂

Comment thread src/types/memoryview.rs Outdated

/// Represents a Python `memoryview`.
#[repr(transparent)]
pub struct PyMemoryView<T>(PyAny, PhantomData<T>);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The choice to make this generic is interesting, and I've been thinking about this further. At the moment none of PyO3's types (other than buffer) are generic. Also PyMemoryView_Check won't check the generic type is correct, which makes it easy for this type to hold memory views with the wrong T.

For these reasons I think it's better if we remove the generic type from this and just have PyMemoryView.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/types/memoryview.rs Outdated
pyobject_native_type_core!(PyMemoryView<T>, ffi::PyMemoryView_Type, #checkfunction=ffi::PyMemoryView_Check; T);

impl PyMemoryView<u8> {
pub fn new<'p>(py: Python<'p>, memory: &mut [u8], is_writable: bool) -> &'p PyMemoryView<u8> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this isn't safe as-is. We technically already have the unsafe fn in from_ptr. It would also be very hard to use correctly, as if you pass this object to other Python code it's extremely likely this memoryview may live longer than you expect.

A safe alternative may be to accept &'static [u8], although that impairs usability a bit.

Maybe the PyMemoryView::from already started below is the thing we should encourage users to use (they can then wrap their buffers in #[pyclass] types which expose buffer access).

Comment thread src/types/memoryview.rs
Comment on lines +35 to +31
/// This function dereferences the raw pointer `ptr` as the
/// leading pointer of a slice of length `len`. [As with
/// `std::slice::from_raw_parts`, this is
/// unsafe](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also needs to have a safety note requiring that the created memory view does not outlive the data in ptr. This is extremely hard to use safely!

Comment thread src/types/memoryview.rs Outdated

/// Creates a new Python `memoryview` object from another Python object that
/// implements the buffer protocol.
pub fn from<'p, I>(py: Python<'p>, src: &'p I) -> PyResult<&'p PyMemoryView<T>>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be tempted to just have pub fn from(&PyAny) -> PyResult<&PyMemoryView> to simplify the signature a bit.

Comment thread src/types/memoryview.rs
Comment on lines +67 to +56
pub fn len(&self) -> usize {
unsafe {
let size = ffi::Py_SIZE(self.as_ptr());
// non-negative Py_ssize_t should always fit into Rust usize
size as usize
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this always returns length in u8, not units of T? So again this looks ok, but only with untyped PyMemoryView.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/types/memoryview.rs Outdated

/// Gets the Python string as a byte slice.
#[inline]
pub fn as_buffer(&self) -> PyResult<PyBuffer<T>> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and with untyped PyMemoryView, this can still be generic:

Suggested change
pub fn as_buffer(&self) -> PyResult<PyBuffer<T>> {
pub fn as_buffer<T>(&self) -> PyResult<PyBuffer<T>> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/types/memoryview.rs
{
unsafe { py.from_owned_ptr_or_err(ffi::PyMemoryView_FromObject(src.as_ptr())) }
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is possibly also scope to have PyMemoryView::from_buffer which takes PyBuffer as input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidhewitt hmm, I have a question: how I can as_ptr() for PyBuffer?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://docs.python.org/3/c-api/memoryview.html#c.PyMemoryView_FromBuffer - no need to .as_ptr() the buffer structure

@ikrivosheev ikrivosheev force-pushed the feature/issues-2711_py_memory_view branch from c2d8c96 to 2ed62af Compare December 25, 2023 16:35
@ikrivosheev
Copy link
Copy Markdown
Contributor Author

@birkenfeld @davidhewitt hello! Sorry for the delay... I extended PyMemoryView and added some new methods.

Comment thread src/types/memoryview.rs
unsafe { self.downcast_unchecked() }
}

/// Gets the Python string as a byte slice.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment here looks out of date.

Comment thread src/types/memoryview.rs

/// Gets the Python string as a byte slice.
#[inline]
pub fn as_buffer<T: Element>(&self) -> PyResult<PyBuffer<T>> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think, is there a memory safety concern casting from bytes to arbitrary elements T here? Potentially as long as the buffer format is checked, it's fine? We require Element: Copy which implies these types are all POD and don't own heap allocations to trigger use-after-free etc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is, it was already be present in PyBuffer::get, wasn't it? And that one does indeed check the buffer format using Element::is_compatible_format.

What I would like to see is a unit test to check whether this actually works for anything but PyBuffer<u8>.

Comment thread src/types/memoryview.rs
{
unsafe { py.from_owned_ptr_or_err(ffi::PyMemoryView_FromObject(src.as_ptr())) }
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://docs.python.org/3/c-api/memoryview.html#c.PyMemoryView_FromBuffer - no need to .as_ptr() the buffer structure

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 23, 2024

CodSpeed Performance Report

Merging #2792 will not alter performance

Comparing ikrivosheev:feature/issues-2711_py_memory_view (2ed62af) with main (f37c682)

Summary

✅ 63 untouched benchmarks

@davidhewitt
Copy link
Copy Markdown
Member

This has been stale for so long and we have the basic type merged now, will close this and defer possible improvements for a future time.

@davidhewitt davidhewitt closed this Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add memory view wrapper

4 participants