use crate::err::{self, PyResult};
use crate::{exceptions, ffi, AsPyPointer, PyAny, Python};
use std::ffi::CStr;
use std::os::raw;
use std::pin::Pin;
use std::{cell, mem, ptr, slice};
#[repr(transparent)]
pub struct PyBuffer(Pin<Box<ffi::Py_buffer>>);
unsafe impl Send for PyBuffer {}
unsafe impl Sync for PyBuffer {}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ElementType {
SignedInteger { bytes: usize },
UnsignedInteger { bytes: usize },
Bool,
Float { bytes: usize },
Unknown,
}
impl ElementType {
pub fn from_format(format: &CStr) -> ElementType {
let slice = format.to_bytes();
if slice.len() == 1 {
native_element_type_from_type_char(slice[0])
} else if slice.len() == 2 {
match slice[0] {
b'@' => native_element_type_from_type_char(slice[1]),
b'=' | b'<' | b'>' | b'!' => standard_element_type_from_type_char(slice[1]),
_ => ElementType::Unknown,
}
} else {
ElementType::Unknown
}
}
}
fn native_element_type_from_type_char(type_char: u8) -> ElementType {
use self::ElementType::*;
match type_char {
b'c' => UnsignedInteger {
bytes: mem::size_of::<raw::c_char>(),
},
b'b' => SignedInteger {
bytes: mem::size_of::<raw::c_schar>(),
},
b'B' => UnsignedInteger {
bytes: mem::size_of::<raw::c_uchar>(),
},
b'?' => Bool,
b'h' => SignedInteger {
bytes: mem::size_of::<raw::c_short>(),
},
b'H' => UnsignedInteger {
bytes: mem::size_of::<raw::c_ushort>(),
},
b'i' => SignedInteger {
bytes: mem::size_of::<raw::c_int>(),
},
b'I' => UnsignedInteger {
bytes: mem::size_of::<raw::c_uint>(),
},
b'l' => SignedInteger {
bytes: mem::size_of::<raw::c_long>(),
},
b'L' => UnsignedInteger {
bytes: mem::size_of::<raw::c_ulong>(),
},
b'q' => SignedInteger {
bytes: mem::size_of::<raw::c_longlong>(),
},
b'Q' => UnsignedInteger {
bytes: mem::size_of::<raw::c_ulonglong>(),
},
b'n' => SignedInteger {
bytes: mem::size_of::<libc::ssize_t>(),
},
b'N' => UnsignedInteger {
bytes: mem::size_of::<libc::size_t>(),
},
b'e' => Float { bytes: 2 },
b'f' => Float { bytes: 4 },
b'd' => Float { bytes: 8 },
_ => Unknown,
}
}
fn standard_element_type_from_type_char(type_char: u8) -> ElementType {
use self::ElementType::*;
match type_char {
b'c' | b'B' => UnsignedInteger { bytes: 1 },
b'b' => SignedInteger { bytes: 1 },
b'?' => Bool,
b'h' => SignedInteger { bytes: 2 },
b'H' => UnsignedInteger { bytes: 2 },
b'i' | b'l' => SignedInteger { bytes: 4 },
b'I' | b'L' => UnsignedInteger { bytes: 4 },
b'q' => SignedInteger { bytes: 8 },
b'Q' => UnsignedInteger { bytes: 8 },
b'e' => Float { bytes: 2 },
b'f' => Float { bytes: 4 },
b'd' => Float { bytes: 8 },
_ => Unknown,
}
}
#[cfg(target_endian = "little")]
fn is_matching_endian(c: u8) -> bool {
match c {
b'@' | b'=' | b'<' => true,
_ => false,
}
}
#[cfg(target_endian = "big")]
fn is_matching_endian(c: u8) -> bool {
match c {
b'@' | b'=' | b'>' | b'!' => true,
_ => false,
}
}
pub unsafe trait Element {
fn is_compatible_format(format: &CStr) -> bool;
}
fn validate(b: &ffi::Py_buffer) {
assert!(!b.shape.is_null());
assert!(!b.strides.is_null());
}
impl PyBuffer {
pub fn get(py: Python, obj: &PyAny) -> PyResult<PyBuffer> {
unsafe {
let mut buf = Box::pin(mem::zeroed::<ffi::Py_buffer>());
err::error_on_minusone(
py,
ffi::PyObject_GetBuffer(obj.as_ptr(), &mut *buf, ffi::PyBUF_FULL_RO),
)?;
validate(&buf);
Ok(PyBuffer(buf))
}
}
#[inline]
pub fn buf_ptr(&self) -> *mut raw::c_void {
self.0.buf
}
pub fn get_ptr(&self, indices: &[usize]) -> *mut raw::c_void {
let shape = &self.shape()[..indices.len()];
for i in 0..indices.len() {
assert!(indices[i] < shape[i]);
}
unsafe {
ffi::PyBuffer_GetPointer(
&*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer,
indices.as_ptr() as *mut usize as *mut ffi::Py_ssize_t,
)
}
}
#[inline]
pub fn readonly(&self) -> bool {
self.0.readonly != 0
}
#[inline]
pub fn item_size(&self) -> usize {
self.0.itemsize as usize
}
#[inline]
pub fn item_count(&self) -> usize {
(self.0.len as usize) / (self.0.itemsize as usize)
}
#[inline]
pub fn len_bytes(&self) -> usize {
self.0.len as usize
}
#[inline]
pub fn dimensions(&self) -> usize {
self.0.ndim as usize
}
#[inline]
pub fn shape(&self) -> &[usize] {
unsafe { slice::from_raw_parts(self.0.shape as *const usize, self.0.ndim as usize) }
}
#[inline]
pub fn strides(&self) -> &[isize] {
unsafe { slice::from_raw_parts(self.0.strides, self.0.ndim as usize) }
}
#[inline]
pub fn suboffsets(&self) -> Option<&[isize]> {
unsafe {
if self.0.suboffsets.is_null() {
None
} else {
Some(slice::from_raw_parts(
self.0.suboffsets,
self.0.ndim as usize,
))
}
}
}
#[inline]
pub fn format(&self) -> &CStr {
if self.0.format.is_null() {
CStr::from_bytes_with_nul(b"B\0").unwrap()
} else {
unsafe { CStr::from_ptr(self.0.format) }
}
}
#[inline]
pub fn is_c_contiguous(&self) -> bool {
unsafe {
ffi::PyBuffer_IsContiguous(&*self.0 as *const ffi::Py_buffer, b'C' as libc::c_char) != 0
}
}
#[inline]
pub fn is_fortran_contiguous(&self) -> bool {
unsafe {
ffi::PyBuffer_IsContiguous(&*self.0 as *const ffi::Py_buffer, b'F' as libc::c_char) != 0
}
}
pub fn as_slice<'a, T: Element>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell<T>]> {
if mem::size_of::<T>() == self.item_size()
&& (self.0.buf as usize) % mem::align_of::<T>() == 0
&& self.is_c_contiguous()
&& T::is_compatible_format(self.format())
{
unsafe {
Some(slice::from_raw_parts(
self.0.buf as *mut ReadOnlyCell<T>,
self.item_count(),
))
}
} else {
None
}
}
pub fn as_mut_slice<'a, T: Element>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell<T>]> {
if !self.readonly()
&& mem::size_of::<T>() == self.item_size()
&& (self.0.buf as usize) % mem::align_of::<T>() == 0
&& self.is_c_contiguous()
&& T::is_compatible_format(self.format())
{
unsafe {
Some(slice::from_raw_parts(
self.0.buf as *mut cell::Cell<T>,
self.item_count(),
))
}
} else {
None
}
}
pub fn as_fortran_slice<'a, T: Element>(
&'a self,
_py: Python<'a>,
) -> Option<&'a [ReadOnlyCell<T>]> {
if mem::size_of::<T>() == self.item_size()
&& (self.0.buf as usize) % mem::align_of::<T>() == 0
&& self.is_fortran_contiguous()
&& T::is_compatible_format(self.format())
{
unsafe {
Some(slice::from_raw_parts(
self.0.buf as *mut ReadOnlyCell<T>,
self.item_count(),
))
}
} else {
None
}
}
pub fn as_fortran_mut_slice<'a, T: Element>(
&'a self,
_py: Python<'a>,
) -> Option<&'a [cell::Cell<T>]> {
if !self.readonly()
&& mem::size_of::<T>() == self.item_size()
&& (self.0.buf as usize) % mem::align_of::<T>() == 0
&& self.is_fortran_contiguous()
&& T::is_compatible_format(self.format())
{
unsafe {
Some(slice::from_raw_parts(
self.0.buf as *mut cell::Cell<T>,
self.item_count(),
))
}
} else {
None
}
}
pub fn copy_to_slice<T: Element + Copy>(&self, py: Python, target: &mut [T]) -> PyResult<()> {
self.copy_to_slice_impl(py, target, b'C')
}
pub fn copy_to_fortran_slice<T: Element + Copy>(
&self,
py: Python,
target: &mut [T],
) -> PyResult<()> {
self.copy_to_slice_impl(py, target, b'F')
}
fn copy_to_slice_impl<T: Element + Copy>(
&self,
py: Python,
target: &mut [T],
fort: u8,
) -> PyResult<()> {
if mem::size_of_val(target) != self.len_bytes() {
return Err(exceptions::BufferError::py_err(
"Slice length does not match buffer length.",
));
}
if !T::is_compatible_format(self.format()) || mem::size_of::<T>() != self.item_size() {
return incompatible_format_error();
}
unsafe {
err::error_on_minusone(
py,
ffi::PyBuffer_ToContiguous(
target.as_ptr() as *mut raw::c_void,
&*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer,
self.0.len,
fort as libc::c_char,
),
)
}
}
pub fn to_vec<T: Element + Copy>(&self, py: Python) -> PyResult<Vec<T>> {
self.to_vec_impl(py, b'C')
}
pub fn to_fortran_vec<T: Element + Copy>(&self, py: Python) -> PyResult<Vec<T>> {
self.to_vec_impl(py, b'F')
}
fn to_vec_impl<T: Element + Copy>(&self, py: Python, fort: u8) -> PyResult<Vec<T>> {
if !T::is_compatible_format(self.format()) || mem::size_of::<T>() != self.item_size() {
incompatible_format_error()?;
unreachable!();
}
let item_count = self.item_count();
let mut vec: Vec<T> = Vec::with_capacity(item_count);
unsafe {
err::error_on_minusone(
py,
ffi::PyBuffer_ToContiguous(
vec.as_mut_ptr() as *mut raw::c_void,
&*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer,
self.0.len,
fort as libc::c_char,
),
)?;
vec.set_len(item_count);
}
Ok(vec)
}
pub fn copy_from_slice<T: Element + Copy>(&self, py: Python, source: &[T]) -> PyResult<()> {
self.copy_from_slice_impl(py, source, b'C')
}
pub fn copy_from_fortran_slice<T: Element + Copy>(
&self,
py: Python,
source: &[T],
) -> PyResult<()> {
self.copy_from_slice_impl(py, source, b'F')
}
fn copy_from_slice_impl<T: Element + Copy>(
&self,
py: Python,
source: &[T],
fort: u8,
) -> PyResult<()> {
if self.readonly() {
return buffer_readonly_error();
}
if mem::size_of_val(source) != self.len_bytes() {
return Err(exceptions::BufferError::py_err(
"Slice length does not match buffer length.",
));
}
if !T::is_compatible_format(self.format()) || mem::size_of::<T>() != self.item_size() {
return incompatible_format_error();
}
unsafe {
err::error_on_minusone(
py,
ffi::PyBuffer_FromContiguous(
&*self.0 as *const ffi::Py_buffer as *mut ffi::Py_buffer,
source.as_ptr() as *mut raw::c_void,
self.0.len,
fort as libc::c_char,
),
)
}
}
pub fn release(self, _py: Python) {
let mut mdself = mem::ManuallyDrop::new(self);
unsafe {
ffi::PyBuffer_Release(&mut *mdself.0);
let inner: *mut Pin<Box<ffi::Py_buffer>> = &mut mdself.0;
ptr::drop_in_place(inner);
}
}
}
fn incompatible_format_error() -> PyResult<()> {
Err(exceptions::BufferError::py_err(
"Slice type is incompatible with buffer format.",
))
}
fn buffer_readonly_error() -> PyResult<()> {
Err(exceptions::BufferError::py_err(
"Cannot write to read-only buffer.",
))
}
impl Drop for PyBuffer {
fn drop(&mut self) {
let _gil_guard = Python::acquire_gil();
unsafe { ffi::PyBuffer_Release(&mut *self.0) }
}
}
#[repr(transparent)]
pub struct ReadOnlyCell<T>(cell::UnsafeCell<T>);
impl<T: Copy> ReadOnlyCell<T> {
#[inline]
pub fn get(&self) -> T {
unsafe { *self.0.get() }
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.0.get()
}
}
macro_rules! impl_element(
($t:ty, $f:ident) => {
unsafe impl Element for $t {
fn is_compatible_format(format: &CStr) -> bool {
let slice = format.to_bytes();
if slice.len() > 1 && !is_matching_endian(slice[0]) {
return false;
}
ElementType::from_format(format) == ElementType::$f { bytes: mem::size_of::<$t>() }
}
}
}
);
impl_element!(u8, UnsignedInteger);
impl_element!(u16, UnsignedInteger);
impl_element!(u32, UnsignedInteger);
impl_element!(u64, UnsignedInteger);
impl_element!(usize, UnsignedInteger);
impl_element!(i8, SignedInteger);
impl_element!(i16, SignedInteger);
impl_element!(i32, SignedInteger);
impl_element!(i64, SignedInteger);
impl_element!(isize, SignedInteger);
impl_element!(f32, Float);
impl_element!(f64, Float);
#[cfg(test)]
mod test {
use super::PyBuffer;
use crate::ffi;
use crate::Python;
#[allow(unused_imports)]
use crate::objectprotocol::ObjectProtocol;
#[test]
fn test_compatible_size() {
assert_eq!(
std::mem::size_of::<ffi::Py_ssize_t>(),
std::mem::size_of::<usize>()
);
}
#[test]
fn test_bytes_buffer() {
let gil = Python::acquire_gil();
let py = gil.python();
let bytes = py.eval("b'abcde'", None, None).unwrap();
let buffer = PyBuffer::get(py, &bytes).unwrap();
assert_eq!(buffer.dimensions(), 1);
assert_eq!(buffer.item_count(), 5);
assert_eq!(buffer.format().to_str().unwrap(), "B");
assert_eq!(buffer.shape(), [5]);
assert!(buffer.is_c_contiguous());
assert!(buffer.is_fortran_contiguous());
assert!(buffer.as_slice::<f64>(py).is_none());
assert!(buffer.as_slice::<i8>(py).is_none());
let slice = buffer.as_slice::<u8>(py).unwrap();
assert_eq!(slice.len(), 5);
assert_eq!(slice[0].get(), b'a');
assert_eq!(slice[2].get(), b'c');
assert!(buffer.as_mut_slice::<u8>(py).is_none());
assert!(buffer.copy_to_slice(py, &mut [0u8]).is_err());
let mut arr = [0; 5];
buffer.copy_to_slice(py, &mut arr).unwrap();
assert_eq!(arr, b"abcde" as &[u8]);
assert!(buffer.copy_from_slice(py, &[0u8; 5]).is_err());
assert!(buffer.to_vec::<i8>(py).is_err());
assert!(buffer.to_vec::<u16>(py).is_err());
assert_eq!(buffer.to_vec::<u8>(py).unwrap(), b"abcde");
}
#[allow(clippy::float_cmp)]
#[test]
fn test_array_buffer() {
let gil = Python::acquire_gil();
let py = gil.python();
let array = py
.import("array")
.unwrap()
.call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
.unwrap();
let buffer = PyBuffer::get(py, array).unwrap();
assert_eq!(buffer.dimensions(), 1);
assert_eq!(buffer.item_count(), 4);
assert_eq!(buffer.format().to_str().unwrap(), "f");
assert_eq!(buffer.shape(), [4]);
assert!(buffer.as_slice::<f64>(py).is_none());
assert!(buffer.as_slice::<i32>(py).is_none());
let slice = buffer.as_slice::<f32>(py).unwrap();
assert_eq!(slice.len(), 4);
assert_eq!(slice[0].get(), 1.0);
assert_eq!(slice[3].get(), 2.5);
let mut_slice = buffer.as_mut_slice::<f32>(py).unwrap();
assert_eq!(mut_slice.len(), 4);
assert_eq!(mut_slice[0].get(), 1.0);
mut_slice[3].set(2.75);
assert_eq!(slice[3].get(), 2.75);
buffer
.copy_from_slice(py, &[10.0f32, 11.0, 12.0, 13.0])
.unwrap();
assert_eq!(slice[2].get(), 12.0);
assert_eq!(buffer.to_vec::<f32>(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
}
}