Skip to content

Commit

Permalink
Merge pull request #139 from TobiasBengtsson/master
Browse files Browse the repository at this point in the history
Support for BinarySlice to avoid allocation
  • Loading branch information
TobiasBengtsson authored Aug 13, 2022
2 parents b230928 + 976bc7d commit a404834
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 13 deletions.
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

0 comments on commit a404834

Please sign in to comment.