Skip to content

Commit 231b300

Browse files
Support for BinarySlice to avoid allocation
1 parent b230928 commit 231b300

File tree

6 files changed

+221
-0
lines changed

6 files changed

+221
-0
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [`Vec`](./types/vec.md)
1313
- [`HashMap`](./types/hashmap.md)
1414
- [`Binary`](./types/binary.md)
15+
- [`BinarySlice`](./types/binary_slice.md)
1516
- [`Option`](./types/option.md)
1617
- [Object](./types/object.md)
1718
- [Class Object](./types/class_object.md)

guide/src/types/binary_slice.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# `Binary Slices`
2+
3+
Binary data is represented as a string in PHP. The most common source of this
4+
data is from the [`pack`] and [`unpack`] functions. It allows you to use a PHP
5+
string as a read-only slice in Rust.
6+
7+
| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation |
8+
| ------------- | -------------- | --------------- | ---------------- | ------------------ |
9+
| Yes | No | No | No | `zend_string` |
10+
11+
The binary type is represented as a string in PHP. Although not encoded, the
12+
data is converted into a slice and then the pointer to the data is set as the
13+
string pointer, with the length of the array being the length of the string.
14+
15+
`BinarySlice<T>` is valid when `T` implements `PackSlice`. This is currently
16+
implemented on most primitive numbers (i8, i16, i32, i64, u8, u16, u32, u64,
17+
isize, usize, f32, f64).
18+
19+
[`pack`]: https://www.php.net/manual/en/function.pack.php
20+
[`unpack`]: https://www.php.net/manual/en/function.unpack.php
21+
22+
## Rust Usage
23+
24+
```rust,no_run
25+
# #![cfg_attr(windows, feature(abi_vectorcall))]
26+
# extern crate ext_php_rs;
27+
use ext_php_rs::prelude::*;
28+
use ext_php_rs::binary_slice::BinarySlice;
29+
30+
#[php_function]
31+
pub fn test_binary_slice(input: BinarySlice<u8>) -> u8 {
32+
let mut sum = 0;
33+
for i in input.iter() {
34+
sum += i;
35+
}
36+
37+
sum
38+
}
39+
# fn main() {}
40+
```
41+
42+
## PHP Usage
43+
44+
```php
45+
<?php
46+
47+
$data = pack('C*', 1, 2, 3, 4, 5);
48+
$output = test_binary_slice($data);
49+
var_dump($output); // 15
50+
```

guide/src/types/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ have been implemented on most regular Rust types:
1515
- `HashMap<String, T>` where T implements `IntoZval` and/or `FromZval`.
1616
- `Binary<T>` where T implements `Pack`, used for transferring binary string
1717
data.
18+
- `BinarySlice<T>` where T implements `Pack`, used for exposing PHP binary
19+
strings as read-only slices.
1820
- A PHP callable closure or function wrapped with `Callable`.
1921
- `Option<T>` where T implements `IntoZval` and/or `FromZval`, and where `None`
2022
is converted to a PHP `null`.

src/binary_slice.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//! Provides implementations for converting from Zend binary strings as slices,
2+
//! commonly returned from functions such as [`pack`] and [`unpack`].
3+
//!
4+
//! [`pack`]: https://www.php.net/manual/en/function.pack.php
5+
//! [`unpack`]: https://www.php.net/manual/en/function.unpack.php
6+
7+
use crate::ffi::zend_string;
8+
9+
use std::{convert::TryFrom, ops::Deref, slice::from_raw_parts};
10+
11+
use crate::{
12+
convert::FromZval,
13+
error::{Error, Result},
14+
flags::DataType,
15+
types::Zval,
16+
};
17+
18+
/// Acts as a wrapper around [`&[T]`] where `T` implements [`PackSlice`].
19+
/// Primarily used for passing read-only binary data into Rust functions.
20+
#[derive(Debug)]
21+
pub struct BinarySlice<'a, T>(&'a [T])
22+
where
23+
T: PackSlice;
24+
25+
impl<'a, T> BinarySlice<'a, T>
26+
where
27+
T: PackSlice,
28+
{
29+
/// Creates a new binary slice wrapper from a slice of data.
30+
///
31+
/// # Parameters
32+
///
33+
/// * `data` - Slice to store inside the binary wrapper.
34+
pub fn new(data: &'a [T]) -> Self {
35+
Self(data)
36+
}
37+
}
38+
39+
impl<'a, T> Deref for BinarySlice<'a, T>
40+
where
41+
T: PackSlice,
42+
{
43+
type Target = &'a [T];
44+
45+
fn deref(&self) -> &Self::Target {
46+
&self.0
47+
}
48+
}
49+
50+
impl<T> FromZval<'_> for BinarySlice<'_, T>
51+
where
52+
T: PackSlice,
53+
{
54+
const TYPE: DataType = DataType::String;
55+
56+
fn from_zval(zval: &Zval) -> Option<Self> {
57+
zval.binary_slice().map(BinarySlice)
58+
}
59+
}
60+
61+
impl<T> TryFrom<Zval> for BinarySlice<'_, T>
62+
where
63+
T: PackSlice,
64+
{
65+
type Error = Error;
66+
67+
fn try_from(value: Zval) -> Result<Self> {
68+
Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type()))
69+
}
70+
}
71+
72+
impl<'a, T> From<BinarySlice<'a, T>> for &'a [T]
73+
where
74+
T: PackSlice,
75+
{
76+
fn from(value: BinarySlice<'a, T>) -> Self {
77+
value.0
78+
}
79+
}
80+
81+
impl<'a, T> From<&'a [T]> for BinarySlice<'a, T>
82+
where
83+
T: PackSlice,
84+
{
85+
fn from(value: &'a [T]) -> Self {
86+
Self::new(value)
87+
}
88+
}
89+
90+
/// Used to expose a Zend binary string as a slice. Useful in conjunction with
91+
/// the [`pack`] and [`unpack`] functions built-in to PHP.
92+
///
93+
/// # Safety
94+
///
95+
/// The types cannot be ensured between PHP and Rust, as the data is represented
96+
/// as a string when crossing the language boundary. Exercise caution when using
97+
/// these functions.
98+
///
99+
/// [`pack`]: https://www.php.net/manual/en/function.pack.php
100+
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
101+
pub unsafe trait PackSlice: Clone {
102+
/// Creates a Rust slice from a given Zend binary string. Can be used to
103+
/// pass data from `pack` in PHP to Rust without encoding into another
104+
/// format. Note that the data *must* be all one type, as this
105+
/// implementation only unpacks one type.
106+
///
107+
/// # Safety
108+
///
109+
/// There is no way to tell if the data stored in the string is actually of
110+
/// the given type. The results of this function can also differ from
111+
/// platform-to-platform due to the different representation of some
112+
/// types on different platforms. Consult the [`pack`] function
113+
/// documentation for more details.
114+
///
115+
/// # Parameters
116+
///
117+
/// * `s` - The Zend string containing the binary data.
118+
///
119+
/// [`pack`]: https://www.php.net/manual/en/function.pack.php
120+
fn unpack_into<'a>(s: &zend_string) -> &'a [Self];
121+
}
122+
123+
/// Implements the [`PackSlice`] trait for a given type.
124+
macro_rules! pack_slice_impl {
125+
($t: ty) => {
126+
pack_slice_impl!($t, <$t>::BITS);
127+
};
128+
129+
($t: ty, $d: expr) => {
130+
unsafe impl PackSlice for $t {
131+
fn unpack_into<'a>(s: &zend_string) -> &'a [Self] {
132+
let bytes = ($d / 8) as usize;
133+
let len = (s.len as usize) / bytes;
134+
let ptr = s.val.as_ptr() as *const $t;
135+
unsafe { from_raw_parts(ptr, len) }
136+
}
137+
}
138+
};
139+
}
140+
141+
pack_slice_impl!(u8);
142+
pack_slice_impl!(i8);
143+
144+
pack_slice_impl!(u16);
145+
pack_slice_impl!(i16);
146+
147+
pack_slice_impl!(u32);
148+
pack_slice_impl!(i32);
149+
150+
pack_slice_impl!(u64);
151+
pack_slice_impl!(i64);
152+
153+
pack_slice_impl!(isize);
154+
pack_slice_impl!(usize);
155+
156+
pack_slice_impl!(f32, 32);
157+
pack_slice_impl!(f64, 64);

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
pub mod alloc;
1010
pub mod args;
1111
pub mod binary;
12+
pub mod binary_slice;
1213
pub mod builders;
1314
pub mod convert;
1415
pub mod error;

src/types/zval.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr};
66

77
use crate::{
88
binary::Pack,
9+
binary_slice::PackSlice,
910
boxed::ZBox,
1011
convert::{FromZval, FromZvalMut, IntoZval, IntoZvalDyn},
1112
error::{Error, Result},
@@ -137,6 +138,15 @@ impl Zval {
137138
}
138139
}
139140

141+
pub fn binary_slice<'a, T: PackSlice>(&self) -> Option<&'a [T]> {
142+
if self.is_string() {
143+
// SAFETY: Type is string therefore we are able to take a reference.
144+
Some(T::unpack_into(unsafe { self.value.str_.as_ref() }?))
145+
} else {
146+
None
147+
}
148+
}
149+
140150
/// Returns the value of the zval if it is a resource.
141151
pub fn resource(&self) -> Option<*mut zend_resource> {
142152
// TODO: Can we improve this function? I haven't done much research into

0 commit comments

Comments
 (0)