PHANTOM
🇮🇳 IN
Skip to content

Commit e089d50

Browse files
committed
Added FFI extension
1 parent d0fd2be commit e089d50

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+15482
-0
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ PHP NEWS
1313
- Date:
1414
. Fixed bug #75232 (print_r of DateTime creating side-effect). (Nikita)
1515

16+
- FFI:
17+
. Added FFI extension. (Dmitry)
18+
1619
- FPM:
1720
. Implemented FR #72510 (systemd service should be hardened). (Craig Andrews)
1821

ext/ffi/CREDITS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FFI
2+
Dmitry Stogov

ext/ffi/README.md

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# FFI PHP extension (Foreign Function Interface)
2+
3+
FFI PHP extension provides a simple way to call native functions, access native variables and create/access data structures defined in C language. The API of the extension is very simple and demonstrated by the following example and its output.
4+
5+
```php
6+
<?php
7+
$libc = FFI::cdef("
8+
int printf(const char *format, ...);
9+
const char * getenv(const char *);
10+
unsigned int time(unsigned int *);
11+
12+
typedef unsigned int time_t;
13+
typedef unsigned int suseconds_t;
14+
15+
struct timeval {
16+
time_t tv_sec;
17+
suseconds_t tv_usec;
18+
};
19+
20+
struct timezone {
21+
int tz_minuteswest;
22+
int tz_dsttime;
23+
};
24+
25+
int gettimeofday(struct timeval *tv, struct timezone *tz);
26+
", "libc.so.6");
27+
28+
$libc->printf("Hello World from %s!\n", "PHP");
29+
var_dump($libc->getenv("PATH"));
30+
var_dump($libc->time(null));
31+
32+
$tv = $libc->new("struct timeval");
33+
$tz = $libc->new("struct timezone");
34+
$libc->gettimeofday(FFI::addr($tv), FFI::addr($tz));
35+
var_dump($tv->tv_sec, $tv->tv_usec, $tz);
36+
?>
37+
```
38+
39+
```
40+
Hello World from PHP!
41+
string(135) "/usr/lib64/qt-3.3/bin:/usr/lib64/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/home/dmitry/.local/bin:/home/dmitry/bin"
42+
int(1523617815)
43+
int(1523617815)
44+
int(977765)
45+
object(FFI\CData:<struct>)#3 (2) {
46+
["tz_minuteswest"]=>
47+
int(-180)
48+
["tz_dsttime"]=>
49+
int(0)
50+
}
51+
```
52+
53+
FFI::cdef() takes two arguments (both are optional). The first one is a collection of C declarations and the second is DSO library. All variables and functions defined by first arguments are bound to corresponding native symbols in DSO library and then may be accessed as FFI object methods and properties. C types of argument, return value and variables are automatically converted to/from PHP types (if possible). Otherwise, they are wrapped in a special CData proxy object and may be accessed by elements.
54+
55+
In some cases (e.g. passing C structure by pointer) we may need to create a real C data structures. This is possible using FFF::new() method. It takes a C type definition and may reuse C types and tags defined by FFI::cdef().
56+
57+
It's also possible to use FFI::new() as a static method to create arbitrary C data structures.
58+
59+
``` php
60+
<?php
61+
$p = FFI::new("struct {int x,y;} [2]");
62+
$p[0]->x = 5;
63+
$p[1]->y = 10;
64+
var_dump($p);
65+
```
66+
67+
```
68+
object(FFI\CData:<struct>[2])#1 (2) {
69+
[0]=>
70+
object(FFI\CData:<struct>)#2 (2) {
71+
["x"]=>
72+
int(5)
73+
["y"]=>
74+
int(0)
75+
}
76+
[1]=>
77+
object(FFI\CData:<struct>)#3 (2) {
78+
["x"]=>
79+
int(0)
80+
["y"]=>
81+
int(10)
82+
}
83+
}
84+
```
85+
86+
### API Reference
87+
88+
##### function FFI::cdef([string $cdef = "" [, string $lib = null]]): FFI
89+
90+
##### Call Native Functions
91+
92+
All functions defined in FFI::cdef() may be called as methods of the created FFI object.
93+
94+
```php
95+
$libc = FFI::cdef("const char * getenv(const char *);", "libc.so.6");
96+
var_dump($libc->getenv("PATH"));
97+
```
98+
99+
##### Read/Write Values of Native Variables
100+
101+
All functions defined in FFI::cdef() may be accessed as properties of the created FFI object.
102+
103+
```php
104+
$libc = FFI::cdef("extern int errno;", "libc.so.6");
105+
var_dump($libc->errno);
106+
```
107+
108+
##### function FFI::type(string $type): FFI\CType
109+
110+
This function creates and returns a **FFI\CType** object, representng type of the given C type declaration string.
111+
112+
FFI::type() may be called statically and use only predefined types, or as a method of previously created FFI object. In last case the first argument may reuse all type and tag names defined in FFI::cdef().
113+
114+
115+
##### function FFI::typeof(FFI\CData $type): FFI\CType
116+
117+
This function returns a **FFI\CType** object, representing the type of the given **FFI\CData** object.
118+
119+
##### static function FFI::arrayType(FFI\CType $type, array $dims): FFI\CType
120+
121+
Constructs a new C array type with elements of $type and dimensions specified by $dims.
122+
123+
##### function FFI::new(mixed $type [, bool $own = true [, bool $persistent = false]]): FFI\CData
124+
125+
This function may be used to create a native data structure. The first argument is a C type definition. It may be a **string** or **FFI\CType** object. The following example creates two dimensional array of integers.
126+
127+
```php
128+
$p = FFI::new("int[2][2]");
129+
var_dump($p, FFI::sizeof($p));
130+
```
131+
132+
FFI::new() may be called statically and use only predefined types, or as a method of previously created FFI object. In last case the first argument may reuse all type and tag names defined in FFI::cdef().
133+
134+
By default **FFI::new()** creates "owned" native data structures, that live together with corresponding PHP object, reusing PHP reference-counting and GC. However, in some cases it may be necessary to manually control the life time of the data structure. In this case, the PHP ownership on the corresponding data, may be manually changed, using **false** as the second optianal argument. Later, not-owned CData should be manually deallocated using **FFI::free()**.
135+
136+
Using the optional $persistent argument it's possible to allocate C objects in persistent memory, through malloc(), otherwise memory is allocated in PHP request heap, through emalloc().
137+
138+
##### static function FFI::free(FFI\CData $cdata): void
139+
140+
manually removes previously created "not-owned" data structure.
141+
142+
##### Read/Write Elements of Native Arrays
143+
144+
Elements of native array may be accessed in the same way as elements of PHP arrays. Of course, native arrays support only integer indexes. It's not possible to check element existence using isset() or empty() and remove element using unset(). Native arrays work fine with "foreach" statement.
145+
146+
```php
147+
$p = FFI::new("int[2]");
148+
$p[0] = 1;
149+
$p[1] = 2;
150+
foreach ($p as $key => $val) {
151+
echo "$key => $val\n";
152+
}
153+
```
154+
155+
##### Read/Write Fields of Native "struct" or "union"
156+
157+
Fields of native struct/union may be accessed in the same way as properties of PHP objects. It's not possible to check filed existence using isset() or empty(), remove them using unset(), and iterate using "foreach" statement.
158+
159+
```php
160+
$pp = FFI::new("struct {int x,y;}[2]");
161+
foreach($pp as $n => &$p) {
162+
$p->x = $p->y = $n;
163+
}
164+
var_dump($pp);
165+
```
166+
167+
##### Pointer arithmetic
168+
169+
CData pointer values may be incremented/decremented by a number. The result is a pointer of the same type moved on given offset.
170+
171+
Two pointers to the same type may be subtracted and return difference (similar to C).
172+
173+
##### static function FFI::sizeof(mixed $cdata_or_ctype): int
174+
175+
returns size of C data type of the given **FFI\CData** or **FFI\CType**.
176+
177+
##### static function FFI::alignof(mixed $cdata_or_ctype): int
178+
179+
returns size of C data type of the given **FFI\CData** or **FFI\CType**.
180+
181+
##### static function FFI::memcpy(FFI\CData $dst, mixed $src, int $size): void
182+
183+
copies $size bytes from memory area $src to memory area $dst. $src may be any native data structure (**FFI\CData**) or PHP **string**.
184+
185+
##### static function FFI::memcmp(mixed $src1, mixed $src2, int $size): int
186+
187+
compares $size bytes from memory area $src1 and $dst2. $src1 and $src2 may be any native data structures (**FFI\CData**) or PHP **string**s.
188+
189+
##### static function FFI::memset(FFI\CData $dst, int $c, int $size): void
190+
191+
fills the $size bytes of the memory area pointed to by $dst with the constant byte $c
192+
193+
##### static function FFI::string(FFI\CData $src [, int $size]): string
194+
195+
creates a PHP string from $size bytes of memory area pointed by $src. If size is omitted, $src must be zero terminated array of C chars.
196+
197+
##### function FFI::cast(mixed $type, FFI\CData $cdata): FFI\CData
198+
199+
Casts given $cdata to another C type, specified by C declaration **string** or **FFI\CType** object.
200+
201+
This function may be called statically and use only predefined types, or as a method of previously created FFI object. In last case the first argument may reuse all type and tag names defined in FFI::cdef().
202+
203+
##### static function addr(FFI\CData $cdata): FFI\CData;
204+
205+
Returns C pointer to the given C data structure. The pointer is not "owned" and won't be free. Anyway, this is a potentially unsafe operation, because the life-time of the returned pointer may be longer than life-time of the source object, and this may cause dangling pointer dereference (like in regular C).
206+
207+
##### static function load(string $filename): FFI;
208+
209+
Instead of embedding of a long C definition into PHP string, and creating FFI through FFI::cdef(), it's possible to separate it into a C header file. Note, that C preprocessor directives (e.g. #define or #ifdef) are not supported. And only a couple of special macros may be used especially for FFI.
210+
211+
``` C
212+
#define FFI_LIB "libc.so.6"
213+
214+
int printf(const char *format, ...);
215+
```
216+
217+
Here, FFI_LIB specifies, that the given library should be loaded.
218+
219+
``` php
220+
$ffi = FFI::load(__DIR__ . "/printf.h");
221+
$ffi->printf("Hello world!\n");
222+
223+
```
224+
225+
##### static function scope(string $name): FFI;
226+
227+
FFI definition parsing and shared library loading may take significant time. It's not useful to do it on each HTTP request in WEB environment. However, it's possible to pre-load FFI definitions and libraries at php startup, and instantiate FFI objects when necessary. Header files may be extended with **FFI_SCOPE** define (default pre-loading scope is "C"). This name is going to be used as **FFI::scope()** argument. It's possible to pre-load few files into a single scope.
228+
229+
``` C
230+
#define FFI_LIB "libc.so.6"
231+
#define FFI_SCOPE "libc"
232+
233+
int printf(const char *format, ...);
234+
```
235+
236+
These files are loaded through the same **FFI::load()** load function, executed from file loaded by **opcache.preload** php.ini directive.
237+
238+
``` ini
239+
ffi.preload=/etc/php/ffi/printf.h
240+
```
241+
242+
Finally, **FFI::scope()** instantiate an **FFI** object, that implements all C definition from the given scope.
243+
244+
``` php
245+
$ffi = FFI::scope("libc");
246+
$ffi->printf("Hello world!\n");
247+
```
248+
249+
##### Owned and Not-Owned CData
250+
251+
FFI extension uses two kind of native C data structures. "Owned" pointers are created using **FFI::new([, true])**, **clone**ed. Owned data is deallocated together with last PHP variable, that reference it. This mechanism reuses PHP reference-counting and garbage-collector.
252+
253+
Elements of C arrays and structures, as well as most data structures returned by C functions are "not-owned". They work just as regular C pointers. They may leak memory, if not freed manually using **FFI::free()**, or may become dangling pointers and lead to PHP crashes.
254+
255+
The following example demonstrates the problem.
256+
257+
```php
258+
$p1 = FFI::new("int[2][2]"); // $p1 is owned pointer
259+
$p2 = $p1[0]; // $p2 is not-owned part of $p1
260+
unset($p1); // $p1 is deallocated ($p2 became dangling pointer)
261+
var_dump($p2); // crash because dereferencing of dangling pointer
262+
```
263+
264+
It's possible to change ownership, to avoid this crash, but this would require manual memory management and may lead to memory leaks
265+
266+
```php
267+
$p1 = FFI::new("int[2][2]", false); // $p1 is not-owned pointer
268+
$p2 = $p1[0];
269+
unset($p1); // $p1 CData is keep alive (memory leak)
270+
var_dump($p2); // works fine, except of memory leak
271+
```
272+
273+
##### PHP Callbacks
274+
275+
It's possible to assign PHP function to native function variable (or pass it as a function argument). This seems to work, but this functionality is not supported on all libffi platforms, it is not efficient and leaks resources by the end of request.
276+
277+
##### FFI API restriction
278+
279+
With FFI users may do almost anything, like in C, and therefor may crash PHP in thousand ways. It's possible to completely disable or enable all FFI functions using ffi.enable=0/1 configuration directives, or limit FFI usage to preloaded scripts using ffi.enable=preload (this is the default setting). In case FFI is not completely disabled, it's also enabled for CLI scripts. Finally, the restriction affects only FFI functions their selves, but not the overloaded method of created FFI or CData objects.
280+
281+
### Status
282+
283+
In current state, access to FFI data structures is significantly (about 2 times) slower, than access to PHP arrays and objects. It make no sense to use them for speed, but may make sense to reduce memory consumption.
284+
285+
FFI functionality may be included into PHP-8 core, to provide better interpretation performance and integrate with JIT, providing almost C performance (similar to LuaJIT)
286+
287+
### Requirement
288+
289+
- [libffi-3.*](http://sourceware.org/libffi/)
290+
291+
### Install
292+
293+
``` bash
294+
./configure ... --with-ffi
295+
make
296+
sudo make install
297+
```
298+
299+
### Real Usage
300+
301+
FFI extension was used to implement [PHP TensorFlow binding](https://github.com/dstogov/php-tensorflow)

ext/ffi/config.m4

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
dnl config.m4 for extension FFI
2+
3+
PHP_ARG_WITH(ffi, for FFI support,
4+
[ --with-ffi Include FFI support])
5+
6+
if test "$PHP_FFI" != "no"; then
7+
if test -r $PHP_FFI/include/ffi.h; then
8+
FFI_INCDIR=$PHP_FFI/include
9+
FFI_LIBDIR=$PHP_FFI
10+
else
11+
dnl First try to find pkg-config
12+
if test -z "$PKG_CONFIG"; then
13+
AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
14+
fi
15+
16+
dnl If pkg-config is installed, try using it
17+
if test -x "$PKG_CONFIG" && "$PKG_CONFIG" --exists libffi; then
18+
FFI_VER=`"$PKG_CONFIG" --modversion libffi`
19+
FFI_INCDIR=`"$PKG_CONFIG" --variable=includedir libffi`
20+
FFI_LIBDIR=`"$PKG_CONFIG" --variable=libdir libffi`
21+
AC_MSG_CHECKING(for libffi)
22+
AC_MSG_RESULT(found version $FFI_VER)
23+
else
24+
AC_MSG_CHECKING(for libffi in default path)
25+
for i in /usr/local /usr; do
26+
if test -r $i/include/ffi.h; then
27+
FFI_DIR=$i
28+
AC_MSG_RESULT(found in $i)
29+
break
30+
fi
31+
done
32+
33+
if test -z "$FFI_DIR"; then
34+
AC_MSG_RESULT(not found)
35+
AC_MSG_ERROR(Please reinstall the libffi distribution)
36+
fi
37+
38+
FFI_INCDIR="$FFI_DIR/include"
39+
FFI_LIBDIR="$FFI_DIR/$PHP_LIBDIR"
40+
fi
41+
fi
42+
43+
AC_CHECK_TYPES(long double)
44+
45+
PHP_CHECK_LIBRARY(ffi, ffi_call,
46+
[
47+
PHP_ADD_INCLUDE($FFI_INCDIR)
48+
PHP_ADD_LIBRARY_WITH_PATH(ffi, $FFI_LIBDIR, FFI_SHARED_LIBADD)
49+
AC_DEFINE(HAVE_FFI,1,[ Have ffi support ])
50+
], [
51+
AC_MSG_ERROR(FFI module requires libffi)
52+
], [
53+
-L$FFI_LIBDIR
54+
])
55+
56+
PHP_NEW_EXTENSION(ffi, ffi.c ffi_parser.c, $ext_shared)
57+
PHP_SUBST(FFI_SHARED_LIBADD)
58+
fi

ext/ffi/config.w32

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ARG_WITH('ffi', 'ffi support', 'no');
2+
3+
if (PHP_FFI != 'no') {
4+
if (CHECK_HEADER_ADD_INCLUDE("ffi.h", "CFLAGS_FFI", PHP_FFI+ ";" + PHP_PHP_BUILD + "\\include") &&
5+
CHECK_LIB("libffi.lib", "ffi", PHP_FFI)) {
6+
AC_DEFINE('HAVE_FFI', 1, 'ffi support enabled');
7+
8+
EXTENSION('ffi', 'ffi.c ffi_parser.c', null, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
9+
} else {
10+
WARNING('ffi not enabled, headers or libraries not found');
11+
}
12+
}

0 commit comments

Comments
 (0)