From 231b3005c4bf2c81e319ee8790ea1154f24ff204 Mon Sep 17 00:00:00 2001 From: Tobias Bengtsson Date: Thu, 26 May 2022 09:15:21 +0200 Subject: [PATCH 1/2] Support for BinarySlice to avoid allocation --- guide/src/SUMMARY.md | 1 + guide/src/types/binary_slice.md | 50 ++++++++++ guide/src/types/index.md | 2 + src/binary_slice.rs | 157 ++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/types/zval.rs | 10 ++ 6 files changed, 221 insertions(+) create mode 100644 guide/src/types/binary_slice.md create mode 100644 src/binary_slice.rs diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 88f237fd8a..c050b601dc 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -12,6 +12,7 @@ - [`Vec`](./types/vec.md) - [`HashMap`](./types/hashmap.md) - [`Binary`](./types/binary.md) + - [`BinarySlice`](./types/binary_slice.md) - [`Option`](./types/option.md) - [Object](./types/object.md) - [Class Object](./types/class_object.md) diff --git a/guide/src/types/binary_slice.md b/guide/src/types/binary_slice.md new file mode 100644 index 0000000000..1ea193f7da --- /dev/null +++ b/guide/src/types/binary_slice.md @@ -0,0 +1,50 @@ +# `Binary Slices` + +Binary data is represented as a string in PHP. The most common source of this +data is from the [`pack`] and [`unpack`] functions. It allows you to use a PHP +string as a read-only slice in Rust. + +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ------------------ | +| Yes | No | No | No | `zend_string` | + +The binary type is represented as a string in PHP. Although not encoded, the +data is converted into a slice and then the pointer to the data is set as the +string pointer, with the length of the array being the length of the string. + +`BinarySlice` is valid when `T` implements `PackSlice`. This is currently +implemented on most primitive numbers (i8, i16, i32, i64, u8, u16, u32, u64, +isize, usize, f32, f64). + +[`pack`]: https://www.php.net/manual/en/function.pack.php +[`unpack`]: https://www.php.net/manual/en/function.unpack.php + +## Rust Usage + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +use ext_php_rs::prelude::*; +use ext_php_rs::binary_slice::BinarySlice; + +#[php_function] +pub fn test_binary_slice(input: BinarySlice) -> u8 { + let mut sum = 0; + for i in input.iter() { + sum += i; + } + + sum +} +# fn main() {} +``` + +## PHP Usage + +```php +` where T implements `IntoZval` and/or `FromZval`. - `Binary` where T implements `Pack`, used for transferring binary string data. +- `BinarySlice` where T implements `Pack`, used for exposing PHP binary + strings as read-only slices. - A PHP callable closure or function wrapped with `Callable`. - `Option` where T implements `IntoZval` and/or `FromZval`, and where `None` is converted to a PHP `null`. diff --git a/src/binary_slice.rs b/src/binary_slice.rs new file mode 100644 index 0000000000..058d3a7a89 --- /dev/null +++ b/src/binary_slice.rs @@ -0,0 +1,157 @@ +//! Provides implementations for converting from Zend binary strings as slices, +//! commonly returned from functions such as [`pack`] and [`unpack`]. +//! +//! [`pack`]: https://www.php.net/manual/en/function.pack.php +//! [`unpack`]: https://www.php.net/manual/en/function.unpack.php + +use crate::ffi::zend_string; + +use std::{convert::TryFrom, ops::Deref, slice::from_raw_parts}; + +use crate::{ + convert::FromZval, + error::{Error, Result}, + flags::DataType, + types::Zval, +}; + +/// Acts as a wrapper around [`&[T]`] where `T` implements [`PackSlice`]. +/// Primarily used for passing read-only binary data into Rust functions. +#[derive(Debug)] +pub struct BinarySlice<'a, T>(&'a [T]) +where + T: PackSlice; + +impl<'a, T> BinarySlice<'a, T> +where + T: PackSlice, +{ + /// Creates a new binary slice wrapper from a slice of data. + /// + /// # Parameters + /// + /// * `data` - Slice to store inside the binary wrapper. + pub fn new(data: &'a [T]) -> Self { + Self(data) + } +} + +impl<'a, T> Deref for BinarySlice<'a, T> +where + T: PackSlice, +{ + type Target = &'a [T]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromZval<'_> for BinarySlice<'_, T> +where + T: PackSlice, +{ + const TYPE: DataType = DataType::String; + + fn from_zval(zval: &Zval) -> Option { + zval.binary_slice().map(BinarySlice) + } +} + +impl TryFrom for BinarySlice<'_, T> +where + T: PackSlice, +{ + type Error = Error; + + fn try_from(value: Zval) -> Result { + Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type())) + } +} + +impl<'a, T> From> for &'a [T] +where + T: PackSlice, +{ + fn from(value: BinarySlice<'a, T>) -> Self { + value.0 + } +} + +impl<'a, T> From<&'a [T]> for BinarySlice<'a, T> +where + T: PackSlice, +{ + fn from(value: &'a [T]) -> Self { + Self::new(value) + } +} + +/// Used to expose a Zend binary string as a slice. Useful in conjunction with +/// the [`pack`] and [`unpack`] functions built-in to PHP. +/// +/// # Safety +/// +/// The types cannot be ensured between PHP and Rust, as the data is represented +/// as a string when crossing the language boundary. Exercise caution when using +/// these functions. +/// +/// [`pack`]: https://www.php.net/manual/en/function.pack.php +/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php +pub unsafe trait PackSlice: Clone { + /// Creates a Rust slice from a given Zend binary string. Can be used to + /// pass data from `pack` in PHP to Rust without encoding into another + /// format. Note that the data *must* be all one type, as this + /// implementation only unpacks one type. + /// + /// # Safety + /// + /// There is no way to tell if the data stored in the string is actually of + /// the given type. The results of this function can also differ from + /// platform-to-platform due to the different representation of some + /// types on different platforms. Consult the [`pack`] function + /// documentation for more details. + /// + /// # Parameters + /// + /// * `s` - The Zend string containing the binary data. + /// + /// [`pack`]: https://www.php.net/manual/en/function.pack.php + fn unpack_into<'a>(s: &zend_string) -> &'a [Self]; +} + +/// Implements the [`PackSlice`] trait for a given type. +macro_rules! pack_slice_impl { + ($t: ty) => { + pack_slice_impl!($t, <$t>::BITS); + }; + + ($t: ty, $d: expr) => { + unsafe impl PackSlice for $t { + fn unpack_into<'a>(s: &zend_string) -> &'a [Self] { + let bytes = ($d / 8) as usize; + let len = (s.len as usize) / bytes; + let ptr = s.val.as_ptr() as *const $t; + unsafe { from_raw_parts(ptr, len) } + } + } + }; +} + +pack_slice_impl!(u8); +pack_slice_impl!(i8); + +pack_slice_impl!(u16); +pack_slice_impl!(i16); + +pack_slice_impl!(u32); +pack_slice_impl!(i32); + +pack_slice_impl!(u64); +pack_slice_impl!(i64); + +pack_slice_impl!(isize); +pack_slice_impl!(usize); + +pack_slice_impl!(f32, 32); +pack_slice_impl!(f64, 64); diff --git a/src/lib.rs b/src/lib.rs index 79d2333032..61fef130a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod alloc; pub mod args; pub mod binary; +pub mod binary_slice; pub mod builders; pub mod convert; pub mod error; diff --git a/src/types/zval.rs b/src/types/zval.rs index 83343f41ee..f6c13c544a 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -6,6 +6,7 @@ use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr}; use crate::{ binary::Pack, + binary_slice::PackSlice, boxed::ZBox, convert::{FromZval, FromZvalMut, IntoZval, IntoZvalDyn}, error::{Error, Result}, @@ -137,6 +138,15 @@ impl Zval { } } + pub fn binary_slice<'a, T: PackSlice>(&self) -> Option<&'a [T]> { + if self.is_string() { + // SAFETY: Type is string therefore we are able to take a reference. + Some(T::unpack_into(unsafe { self.value.str_.as_ref() }?)) + } else { + None + } + } + /// Returns the value of the zval if it is a resource. pub fn resource(&self) -> Option<*mut zend_resource> { // TODO: Can we improve this function? I haven't done much research into From 976bc7d686812e2e0af7a164e4d4e232b6d571b4 Mon Sep 17 00:00:00 2001 From: Tobias Bengtsson Date: Sat, 13 Aug 2022 18:29:15 +0200 Subject: [PATCH 2/2] Fix clippy errors --- build.rs | 2 +- src/boxed.rs | 4 ++-- src/props.rs | 7 +++++-- src/types/callable.rs | 2 +- src/types/class_object.rs | 2 +- src/zend/ex.rs | 10 +++++----- src/zend/handlers.rs | 2 +- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/build.rs b/build.rs index ec4153f6c7..518b49486c 100644 --- a/build.rs +++ b/build.rs @@ -131,7 +131,7 @@ impl PHPInfo { fn build_wrapper(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> { let mut build = cc::Build::new(); for (var, val) in defines { - build.define(*var, *val); + build.define(var, *val); } build .file("src/wrapper.c") diff --git a/src/boxed.rs b/src/boxed.rs index ae076160c2..b4dcd33182 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -97,14 +97,14 @@ impl DerefMut for ZBox { impl Debug for ZBox { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - (&**self).fmt(f) + (**self).fmt(f) } } impl Borrow for ZBox { #[inline] fn borrow(&self) -> &T { - &**self + self } } diff --git a/src/props.rs b/src/props.rs index 43a03a9b8a..6865b009ac 100644 --- a/src/props.rs +++ b/src/props.rs @@ -59,6 +59,9 @@ impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T { } } +pub type PropertyGetter<'a, T> = Option PhpResult + Send + Sync + 'a>>; +pub type PropertySetter<'a, T> = Option PhpResult + Send + Sync + 'a>>; + /// Represents a property added to a PHP class. /// /// There are two types of properties: @@ -69,8 +72,8 @@ impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T { pub enum Property<'a, T> { Field(Box &mut dyn Prop) + Send + Sync>), Method { - get: Option PhpResult + Send + Sync + 'a>>, - set: Option PhpResult + Send + Sync + 'a>>, + get: PropertyGetter<'a, T>, + set: PropertySetter<'a, T>, }, } diff --git a/src/types/callable.rs b/src/types/callable.rs index 9c918b6646..17bd3b3c85 100644 --- a/src/types/callable.rs +++ b/src/types/callable.rs @@ -160,7 +160,7 @@ enum OwnedZval<'a> { impl<'a> OwnedZval<'a> { fn as_ref(&self) -> &Zval { match self { - OwnedZval::Reference(zv) => *zv, + OwnedZval::Reference(zv) => zv, OwnedZval::Owned(zv) => zv, } } diff --git a/src/types/class_object.rs b/src/types/class_object.rs index 65a832fea9..09f37aa918 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -260,7 +260,7 @@ impl Clone for ZBox> { // `ZendClassObject` pointer will contain a valid, initialized `obj`, // therefore we can dereference both safely. unsafe { - let mut new = ZendClassObject::new((&***self).clone()); + let mut new = ZendClassObject::new((***self).clone()); zend_objects_clone_members(&mut new.std, &self.std as *const _ as *mut _); new } diff --git a/src/zend/ex.rs b/src/zend/ex.rs index 2c433a3877..37949d47eb 100644 --- a/src/zend/ex.rs +++ b/src/zend/ex.rs @@ -42,7 +42,7 @@ impl ExecuteData { /// dbg!(a); /// } /// ``` - pub fn parser<'a>(&'a mut self) -> ArgParser<'a, '_> { + pub fn parser(&mut self) -> ArgParser<'_, '_> { self.parser_object().0 } @@ -73,7 +73,7 @@ impl ExecuteData { /// dbg!(a, this); /// } /// ``` - pub fn parser_object<'a>(&'a mut self) -> (ArgParser<'a, '_>, Option<&'a mut ZendObject>) { + pub fn parser_object(&mut self) -> (ArgParser<'_, '_>, Option<&mut ZendObject>) { // SAFETY: All fields of the `u2` union are the same type. let n_args = unsafe { self.This.u2.num_args }; let mut args = vec![]; @@ -134,9 +134,9 @@ impl ExecuteData { /// ``` /// /// [`parse_object`]: #method.parse_object - pub fn parser_method<'a, T: RegisteredClass>( - &'a mut self, - ) -> (ArgParser<'a, '_>, Option<&'a mut ZendClassObject>) { + pub fn parser_method( + &mut self, + ) -> (ArgParser<'_, '_>, Option<&mut ZendClassObject>) { let (parser, obj) = self.parser_object(); ( parser, diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index ed116ba015..3d1371a9d5 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -110,7 +110,7 @@ impl ZendObjectHandlers { Ok(rv) => rv, Err(e) => { let _ = e.throw(); - (&mut *rv).set_null(); + (*rv).set_null(); rv } }