1
0
mirror of https://github.com/php/php-src.git synced 2026-03-27 01:32:22 +01:00

Remove ffi README in favour of docs

- https://www.php.net/manual/en/book.ffi.php
- The FFI C parser is mentioned in the CONTRIBUTING.md file
This commit is contained in:
Peter Kokot
2019-05-09 00:29:43 +02:00
parent 330d773b3c
commit 4bfb86cdca

View File

@@ -1,382 +0,0 @@
# FFI PHP extension (Foreign Function Interface)
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.
```php
<?php
$libc = FFI::cdef("
int printf(const char *format, ...);
const char * getenv(const char *);
unsigned int time(unsigned int *);
typedef unsigned int time_t;
typedef unsigned int suseconds_t;
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
struct timezone {
int tz_minuteswest;
int tz_dsttime;
};
int gettimeofday(struct timeval *tv, struct timezone *tz);
", "libc.so.6");
$libc->printf("Hello World from %s!\n", "PHP");
var_dump($libc->getenv("PATH"));
var_dump($libc->time(null));
$tv = $libc->new("struct timeval");
$tz = $libc->new("struct timezone");
$libc->gettimeofday(FFI::addr($tv), FFI::addr($tz));
var_dump($tv->tv_sec, $tv->tv_usec, $tz);
?>
```
```txt
Hello World from PHP!
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"
int(1523617815)
int(1523617815)
int(977765)
object(FFI\CData:<struct>)#3 (2) {
["tz_minuteswest"]=>
int(-180)
["tz_dsttime"]=>
int(0)
}
```
`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.
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()`.
It's also possible to use `FFI::new()` as a static method to create arbitrary C
data structures.
```php
<?php
$p = FFI::new("struct {int x,y;} [2]");
$p[0]->x = 5;
$p[1]->y = 10;
var_dump($p);
```
```txt
object(FFI\CData:<struct>[2])#1 (2) {
[0]=>
object(FFI\CData:<struct>)#2 (2) {
["x"]=>
int(5)
["y"]=>
int(0)
}
[1]=>
object(FFI\CData:<struct>)#3 (2) {
["x"]=>
int(0)
["y"]=>
int(10)
}
}
```
## API reference
### function FFI::cdef([string $cdef = "" [, string $lib = null]]): FFI
#### Call native functions
All functions defined in `FFI::cdef()` may be called as methods of the created
FFI object.
```php
$libc = FFI::cdef("const char * getenv(const char *);", "libc.so.6");
var_dump($libc->getenv("PATH"));
```
#### Read/write values of native variables
All functions defined in `FFI::cdef()` may be accessed as properties of the
created FFI object.
```php
$libc = FFI::cdef("extern int errno;", "libc.so.6");
var_dump($libc->errno);
```
### function FFI::type(string $type): FFI\CType
This function creates and returns a `FFI\CType` object, representng type of
the given C type declaration string.
`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()`.
### function FFI::typeof(FFI\CData $type): FFI\CType
This function returns a `FFI\CType` object, representing the type of the given
`FFI\CData` object.
### static function FFI::arrayType(FFI\CType $type, array $dims): FFI\CType
Constructs a new C array type with elements of $type and dimensions specified by
$dims.
### function FFI::new(mixed $type [, bool $own = true [, bool $persistent = false]]): FFI\CData
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.
```php
$p = FFI::new("int[2][2]");
var_dump($p, FFI::sizeof($p));
```
`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()`.
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()`.
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()`.
### static function FFI::free(FFI\CData $cdata): void
Manually removes previously created "not-owned" data structure.
#### Read/write elements of native arrays
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.
```php
$p = FFI::new("int[2]");
$p[0] = 1;
$p[1] = 2;
foreach ($p as $key => $val) {
echo "$key => $val\n";
}
```
#### Read/write fields of native "struct" or "union"
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.
```php
$pp = FFI::new("struct {int x,y;}[2]");
foreach($pp as $n => &$p) {
$p->x = $p->y = $n;
}
var_dump($pp);
```
#### Pointer arithmetic
CData pointer values may be incremented/decremented by a number. The result is a
pointer of the same type moved on given offset.
Two pointers to the same type may be subtracted and return difference (similar
to C).
### static function FFI::sizeof(mixed $cdata_or_ctype): int
Returns size of C data type of the given `FFI\CData` or `FFI\CType`.
### static function FFI::alignof(mixed $cdata_or_ctype): int
Returns size of C data type of the given `FFI\CData` or `FFI\CType`.
### static function FFI::memcpy(FFI\CData $dst, mixed $src, int $size): void
Copies $size bytes from memory area $src to memory area $dst. $src may be any
native data structure (`FFI\CData`) or PHP `string`.
### static function FFI::memcmp(mixed $src1, mixed $src2, int $size): int
Compares $size bytes from memory area `$src1` and `$dst2`. `$src1` and `$src2`
may be any native data structures (`FFI\CData`) or PHP `string`s.
### static function FFI::memset(FFI\CData $dst, int $c, int $size): void
Fills the $size bytes of the memory area pointed to by `$dst` with the constant
byte `$c`.
### static function FFI::string(FFI\CData $src [, int $size]): string
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.
### function FFI::cast(mixed $type, FFI\CData $cdata): FFI\CData
Casts given $cdata to another C type, specified by C declaration `string` or
`FFI\CType` object.
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()`.
### static function addr(FFI\CData $cdata): FFI\CData
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).
### static function load(string $filename): FFI
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.
```c
#define FFI_LIB "libc.so.6"
int printf(const char *format, ...);
```
Here, FFI_LIB specifies, that the given library should be loaded.
```php
$ffi = FFI::load(__DIR__ . "/printf.h");
$ffi->printf("Hello world!\n");
```
### static function scope(string $name): FFI
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.
```c
#define FFI_LIB "libc.so.6"
#define FFI_SCOPE "libc"
int printf(const char *format, ...);
```
These files are loaded through the same `FFI::load()` load function, executed
from file loaded by `opcache.preload` php.ini directive.
```ini
ffi.preload=/etc/php/ffi/printf.h
```
Finally, `FFI::scope()` instantiates an `FFI` object, that implements all C
definitions from the given scope.
```php
$ffi = FFI::scope("libc");
$ffi->printf("Hello world!\n");
```
## Owned and not-owned CData
FFI extension uses two kind of native C data structures. "Owned" pointers are
created using `FFI::new([, true])`, `clone`d. Owned data is deallocated
together with last PHP variable, that reference it. This mechanism reuses PHP
reference-counting and garbage-collector.
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.
The following example demonstrates the problem.
```php
$p1 = FFI::new("int[2][2]"); // $p1 is owned pointer
$p2 = $p1[0]; // $p2 is not-owned part of $p1
unset($p1); // $p1 is deallocated ($p2 became dangling pointer)
var_dump($p2); // crash because dereferencing of dangling pointer
```
It's possible to change ownership, to avoid this crash, but this would require
manual memory management and may lead to memory leaks.
```php
$p1 = FFI::new("int[2][2]", false); // $p1 is not-owned pointer
$p2 = $p1[0];
unset($p1); // $p1 CData is keep alive (memory leak)
var_dump($p2); // works fine, except of memory leak
```
## PHP callbacks
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.
## FFI API restriction
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.
## Status
In current state, access to FFI data structures is significantly (about 2 times)
slower, than access to PHP arrays and objects. It makes no sense to use them for
speed, but may make sense to reduce memory consumption.
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).
## Requirement
* [libffi-3.*](https://sourceware.org/libffi/)
## Installation
```bash
./configure ... --with-ffi
make
sudo make install
```
## Real usage
FFI extension was used to implement
[PHP TensorFlow binding](https://github.com/dstogov/php-tensorflow).
## FFI parser
FFI C parser is generated using [LLK](https://github.com/dstogov/llk).