Skip to content

Commit 0efa2cd

Browse files
authored
Merge pull request #231 from adamreichold/simpler-slice-box
RFC: Extend SliceBox and unify Array creation from owned data
2 parents cdcec14 + 3ebf4eb commit 0efa2cd

File tree

6 files changed

+181
-122
lines changed

6 files changed

+181
-122
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
- Unreleased
44
- Support object arrays ([#216](https://github.com/PyO3/rust-numpy/pull/216))
5+
- Support borrowing arrays that are part of other Python objects via `PyArray::borrow_from_array` ([#230](https://github.com/PyO3/rust-numpy/pull/216))
56
- `PyArray::new` is now `unsafe`, as it produces uninitialized arrays ([#220](https://github.com/PyO3/rust-numpy/pull/220))
67
- Fix thread-safety in internal API globals ([#222](https://github.com/PyO3/rust-numpy/pull/222))
78
- Make arrays produced via `IntoPyArray`, i.e. those owning Rust data, writeable ([#235](https://github.com/PyO3/rust-numpy/pull/235))

src/array.rs

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::{iter::ExactSizeIterator, marker::PhantomData};
1717
use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
1818
use crate::dtype::{DataType, Element};
1919
use crate::error::{FromVecError, NotContiguousError, ShapeError};
20-
use crate::slice_box::SliceBox;
20+
use crate::slice_container::PySliceContainer;
2121

2222
/// A safe, static-typed interface for
2323
/// [NumPy ndarray](https://numpy.org/doc/stable/reference/arrays.ndarray.html).
@@ -432,53 +432,71 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
432432
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
433433
dims.ndim_cint(),
434434
dims.as_dims_ptr(),
435-
T::npy_type() as i32,
436-
strides as *mut _, // strides
437-
ptr::null_mut(), // data
438-
0, // itemsize
439-
flag, // flag
440-
ptr::null_mut(), // obj
435+
T::npy_type() as c_int,
436+
strides as *mut npy_intp, // strides
437+
ptr::null_mut(), // data
438+
0, // itemsize
439+
flag, // flag
440+
ptr::null_mut(), // obj
441441
);
442442
Self::from_owned_ptr(py, ptr)
443443
}
444444

445-
pub(crate) unsafe fn from_boxed_slice<'py, ID>(
445+
unsafe fn new_with_data<'py, ID>(
446446
py: Python<'py>,
447447
dims: ID,
448448
strides: *const npy_intp,
449-
boxed_slice: Box<[T]>,
450-
data_ptr: Option<*const T>,
449+
data_ptr: *const T,
450+
container: *mut PyAny,
451451
) -> &'py Self
452452
where
453453
ID: IntoDimension<Dim = D>,
454454
{
455455
let dims = dims.into_dimension();
456-
let data_ptr = data_ptr.unwrap_or_else(|| boxed_slice.as_ptr());
457-
let container = SliceBox::new(boxed_slice);
458-
let cell = pyo3::PyClassInitializer::from(container)
459-
.create_cell(py)
460-
.expect("Object creation failed.");
461456
let ptr = PY_ARRAY_API.PyArray_New(
462457
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
463458
dims.ndim_cint(),
464459
dims.as_dims_ptr(),
465-
T::npy_type() as i32,
466-
strides as *mut _, // strides
467-
data_ptr as _, // data
468-
mem::size_of::<T>() as i32, // itemsize
469-
npyffi::NPY_ARRAY_WRITEABLE, // flag
470-
ptr::null_mut(), // obj
460+
T::npy_type() as c_int,
461+
strides as *mut npy_intp, // strides
462+
data_ptr as *mut c_void, // data
463+
mem::size_of::<T>() as c_int, // itemsize
464+
npyffi::NPY_ARRAY_WRITEABLE, // flag
465+
ptr::null_mut(), // obj
466+
);
467+
468+
PY_ARRAY_API.PyArray_SetBaseObject(
469+
ptr as *mut npyffi::PyArrayObject,
470+
container as *mut ffi::PyObject,
471471
);
472-
PY_ARRAY_API.PyArray_SetBaseObject(ptr as *mut npyffi::PyArrayObject, cell as _);
472+
473473
Self::from_owned_ptr(py, ptr)
474474
}
475475

476-
/// Creates a NumPy array backed by `array` and ties its ownership to the Python object `owner`.
476+
pub(crate) unsafe fn from_raw_parts<'py, ID, C>(
477+
py: Python<'py>,
478+
dims: ID,
479+
strides: *const npy_intp,
480+
data_ptr: *const T,
481+
container: C,
482+
) -> &'py Self
483+
where
484+
ID: IntoDimension<Dim = D>,
485+
PySliceContainer: From<C>,
486+
{
487+
let container = pyo3::PyClassInitializer::from(PySliceContainer::from(container))
488+
.create_cell(py)
489+
.expect("Object creation failed.");
490+
491+
Self::new_with_data(py, dims, strides, data_ptr, container as *mut PyAny)
492+
}
493+
494+
/// Creates a NumPy array backed by `array` and ties its ownership to the Python object `container`.
477495
///
478496
/// # Safety
479497
///
480-
/// `owner` is set as a base object of the returned array which must not be dropped until `owner` is dropped.
481-
/// Furthermore, `array` must not be reallocated from the time this method is called and until `owner` is dropped.
498+
/// `container` is set as a base object of the returned array which must not be dropped until `container` is dropped.
499+
/// Furthermore, `array` must not be reallocated from the time this method is called and until `container` is dropped.
482500
///
483501
/// # Example
484502
///
@@ -503,33 +521,27 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
503521
/// }
504522
/// }
505523
/// ```
506-
pub unsafe fn borrow_from_array<'py, S>(array: &ArrayBase<S, D>, owner: &'py PyAny) -> &'py Self
524+
pub unsafe fn borrow_from_array<'py, S>(
525+
array: &ArrayBase<S, D>,
526+
container: &'py PyAny,
527+
) -> &'py Self
507528
where
508529
S: Data<Elem = T>,
509530
{
510531
let (strides, dims) = (array.npy_strides(), array.raw_dim());
511532
let data_ptr = array.as_ptr();
512533

513-
let ptr = PY_ARRAY_API.PyArray_New(
514-
PY_ARRAY_API.get_type_object(npyffi::NpyTypes::PyArray_Type),
515-
dims.ndim_cint(),
516-
dims.as_dims_ptr(),
517-
T::npy_type() as c_int,
518-
strides.as_ptr() as *mut npy_intp, // strides
519-
data_ptr as *mut c_void, // data
520-
mem::size_of::<T>() as c_int, // itemsize
521-
0, // flag
522-
ptr::null_mut(), // obj
523-
);
524-
525-
mem::forget(owner.to_object(owner.py()));
534+
let py = container.py();
526535

527-
PY_ARRAY_API.PyArray_SetBaseObject(
528-
ptr as *mut npyffi::PyArrayObject,
529-
owner as *const PyAny as *mut PyAny as *mut ffi::PyObject,
530-
);
536+
mem::forget(container.to_object(py));
531537

532-
Self::from_owned_ptr(owner.py(), ptr)
538+
Self::new_with_data(
539+
py,
540+
dims,
541+
strides.as_ptr(),
542+
data_ptr,
543+
container as *const PyAny as *mut PyAny,
544+
)
533545
}
534546

535547
/// Construct a new nd-dimensional array filled with 0.

src/convert.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,21 @@ impl<T: Element> IntoPyArray for Box<[T]> {
3737
type Item = T;
3838
type Dim = Ix1;
3939
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
40-
let len = self.len();
40+
let dims = [self.len()];
4141
let strides = [mem::size_of::<T>() as npy_intp];
42-
unsafe { PyArray::from_boxed_slice(py, [len], strides.as_ptr(), self, None) }
42+
let data_ptr = self.as_ptr();
43+
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) }
4344
}
4445
}
4546

4647
impl<T: Element> IntoPyArray for Vec<T> {
4748
type Item = T;
4849
type Dim = Ix1;
4950
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
50-
self.into_boxed_slice().into_pyarray(py)
51+
let dims = [self.len()];
52+
let strides = [mem::size_of::<T>() as npy_intp];
53+
let data_ptr = self.as_ptr();
54+
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) }
5155
}
5256
}
5357

@@ -59,21 +63,9 @@ where
5963
type Item = A;
6064
type Dim = D;
6165
fn into_pyarray<'py>(self, py: Python<'py>) -> &'py PyArray<Self::Item, Self::Dim> {
62-
let (strides, dim) = (self.npy_strides(), self.raw_dim());
63-
let orig_ptr = self.as_ptr();
64-
// Element of which size is 0 is not supported, but check it for future changes
65-
let is_empty_or_size0 = self.is_empty() || std::mem::size_of::<A>() == 0;
66-
let vec = self.into_raw_vec();
67-
let offset = if is_empty_or_size0 {
68-
0
69-
} else {
70-
(orig_ptr as usize - vec.as_ptr() as usize) / std::mem::size_of::<A>()
71-
};
72-
let mut boxed_slice = vec.into_boxed_slice();
73-
// data_ptr is not always the pointer to the 1st element.
74-
// See https://github.com/PyO3/rust-numpy/issues/182 for the detail.
75-
let data_ptr = unsafe { boxed_slice.as_mut_ptr().add(offset) };
76-
unsafe { PyArray::from_boxed_slice(py, dim, strides.as_ptr(), boxed_slice, Some(data_ptr)) }
66+
let (strides, dims) = (self.npy_strides(), self.raw_dim());
67+
let data_ptr = self.as_ptr();
68+
unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, self) }
7769
}
7870
}
7971

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ mod error;
4242
pub mod npyffi;
4343
pub mod npyiter;
4444
mod readonly;
45-
mod slice_box;
45+
mod slice_container;
4646
mod sum_products;
4747

4848
pub use ndarray;

src/slice_box.rs

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/slice_container.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use std::{mem, slice};
2+
3+
use ndarray::{ArrayBase, Dimension, OwnedRepr};
4+
use pyo3::class::impl_::{PyClassImpl, ThreadCheckerStub};
5+
use pyo3::pyclass::PyClass;
6+
use pyo3::pyclass_slots::PyClassDummySlot;
7+
use pyo3::type_object::{LazyStaticType, PyTypeInfo};
8+
use pyo3::{ffi, types::PyAny, PyCell};
9+
10+
use crate::dtype::Element;
11+
12+
/// Utility type to safely store Box<[_]> or Vec<_> on the Python heap
13+
pub(crate) struct PySliceContainer {
14+
ptr: *mut u8,
15+
len: usize,
16+
cap: usize,
17+
drop: unsafe fn(*mut u8, usize, usize),
18+
}
19+
20+
unsafe impl Send for PySliceContainer {}
21+
22+
impl<T: Send> From<Box<[T]>> for PySliceContainer {
23+
fn from(data: Box<[T]>) -> Self {
24+
unsafe fn drop_boxed_slice<T>(ptr: *mut u8, len: usize, _cap: usize) {
25+
let _ = Box::from_raw(slice::from_raw_parts_mut(ptr as *mut T, len) as *mut [T]);
26+
}
27+
28+
// FIXME(adamreichold): Use `Box::into_raw` when
29+
// `*mut [T]::{as_mut_ptr, len}` become stable and compatible with out MSRV.
30+
let ptr = data.as_ptr() as *mut u8;
31+
let len = data.len();
32+
let cap = 0;
33+
let drop = drop_boxed_slice::<T>;
34+
35+
mem::forget(data);
36+
37+
Self {
38+
ptr,
39+
len,
40+
cap,
41+
drop,
42+
}
43+
}
44+
}
45+
46+
impl<T: Send> From<Vec<T>> for PySliceContainer {
47+
fn from(data: Vec<T>) -> Self {
48+
unsafe fn drop_vec<T>(ptr: *mut u8, len: usize, cap: usize) {
49+
let _ = Vec::from_raw_parts(ptr as *mut T, len, cap);
50+
}
51+
52+
// FIXME(adamreichold): Use `Vec::into_raw_parts`
53+
// when it becomes stable and compatible with our MSRV.
54+
let ptr = data.as_ptr() as *mut u8;
55+
let len = data.len();
56+
let cap = data.capacity();
57+
let drop = drop_vec::<T>;
58+
59+
mem::forget(data);
60+
61+
Self {
62+
ptr,
63+
len,
64+
cap,
65+
drop,
66+
}
67+
}
68+
}
69+
70+
impl<A, D> From<ArrayBase<OwnedRepr<A>, D>> for PySliceContainer
71+
where
72+
A: Element,
73+
D: Dimension,
74+
{
75+
fn from(data: ArrayBase<OwnedRepr<A>, D>) -> Self {
76+
Self::from(data.into_raw_vec())
77+
}
78+
}
79+
80+
impl Drop for PySliceContainer {
81+
fn drop(&mut self) {
82+
unsafe {
83+
(self.drop)(self.ptr, self.len, self.cap);
84+
}
85+
}
86+
}
87+
88+
impl PyClass for PySliceContainer {
89+
type Dict = PyClassDummySlot;
90+
type WeakRef = PyClassDummySlot;
91+
type BaseNativeType = PyAny;
92+
}
93+
94+
impl PyClassImpl for PySliceContainer {
95+
const DOC: &'static str = "Memory store for a PyArray backed by a Box<[_]> or a Vec<_> \0";
96+
97+
type BaseType = PyAny;
98+
type Layout = PyCell<Self>;
99+
type ThreadChecker = ThreadCheckerStub<Self>;
100+
}
101+
102+
unsafe impl PyTypeInfo for PySliceContainer {
103+
type AsRefTarget = PyCell<Self>;
104+
105+
const NAME: &'static str = "PySliceContainer";
106+
const MODULE: Option<&'static str> = Some("_rust_numpy");
107+
108+
#[inline]
109+
fn type_object_raw(py: pyo3::Python) -> *mut ffi::PyTypeObject {
110+
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
111+
TYPE_OBJECT.get_or_init::<Self>(py)
112+
}
113+
}

0 commit comments

Comments
 (0)