Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for BinarySlice to avoid allocation #139

Merged
merged 2 commits into from
Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 50 additions & 0 deletions guide/src/types/binary_slice.md
Original file line number Diff line number Diff line change
@@ -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<T>` 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>) -> u8 {
let mut sum = 0;
for i in input.iter() {
sum += i;
}

sum
}
# fn main() {}
```

## PHP Usage

```php
<?php

$data = pack('C*', 1, 2, 3, 4, 5);
$output = test_binary_slice($data);
var_dump($output); // 15
```
2 changes: 2 additions & 0 deletions guide/src/types/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ have been implemented on most regular Rust types:
- `HashMap<String, T>` where T implements `IntoZval` and/or `FromZval`.
- `Binary<T>` where T implements `Pack`, used for transferring binary string
data.
- `BinarySlice<T>` where T implements `Pack`, used for exposing PHP binary
strings as read-only slices.
- A PHP callable closure or function wrapped with `Callable`.
- `Option<T>` where T implements `IntoZval` and/or `FromZval`, and where `None`
is converted to a PHP `null`.
Expand Down
157 changes: 157 additions & 0 deletions src/binary_slice.rs
Original file line number Diff line number Diff line change
@@ -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<T> FromZval<'_> for BinarySlice<'_, T>
where
T: PackSlice,
{
const TYPE: DataType = DataType::String;

fn from_zval(zval: &Zval) -> Option<Self> {
zval.binary_slice().map(BinarySlice)
}
}

impl<T> TryFrom<Zval> for BinarySlice<'_, T>
where
T: PackSlice,
{
type Error = Error;

fn try_from(value: Zval) -> Result<Self> {
Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type()))
}
}

impl<'a, T> From<BinarySlice<'a, T>> 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);
4 changes: 2 additions & 2 deletions src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ impl<T: ZBoxable> DerefMut for ZBox<T> {
impl<T: ZBoxable + Debug> Debug for ZBox<T> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(&**self).fmt(f)
(**self).fmt(f)
}
}

impl<T: ZBoxable> Borrow<T> for ZBox<T> {
#[inline]
fn borrow(&self) -> &T {
&**self
self
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T {
}
}

pub type PropertyGetter<'a, T> = Option<Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>>;
pub type PropertySetter<'a, T> = Option<Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>>;

/// Represents a property added to a PHP class.
///
/// There are two types of properties:
Expand All @@ -69,8 +72,8 @@ impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T {
pub enum Property<'a, T> {
Field(Box<dyn (Fn(&mut T) -> &mut dyn Prop) + Send + Sync>),
Method {
get: Option<Box<dyn Fn(&T, &mut Zval) -> PhpResult + Send + Sync + 'a>>,
set: Option<Box<dyn Fn(&mut T, &Zval) -> PhpResult + Send + Sync + 'a>>,
get: PropertyGetter<'a, T>,
set: PropertySetter<'a, T>,
},
}

Expand Down
2 changes: 1 addition & 1 deletion src/types/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/types/class_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ impl<T: RegisteredClass + Clone> Clone for ZBox<ZendClassObject<T>> {
// `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
}
Expand Down
10 changes: 10 additions & 0 deletions src/types/zval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/zend/ex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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![];
Expand Down Expand Up @@ -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<T>>) {
pub fn parser_method<T: RegisteredClass>(
&mut self,
) -> (ArgParser<'_, '_>, Option<&mut ZendClassObject<T>>) {
let (parser, obj) = self.parser_object();
(
parser,
Expand Down
2 changes: 1 addition & 1 deletion src/zend/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl ZendObjectHandlers {
Ok(rv) => rv,
Err(e) => {
let _ = e.throw();
(&mut *rv).set_null();
(*rv).set_null();
rv
}
}
Expand Down