rfc:ffi

This is an old revision of the document!


PHP RFC: FFI - Foreign Function Interface

  • Version: 0.9
  • Date: 2013-12-04
  • Author: Dmitry Stogov, dmitry@zend.com
  • Status: Under Discussion
  • First Published at: https://wiki.php.net/rfc/ffi

Introduction

FFI is one of the features that made Python and LuaJIT very useful for fast prototyping. It allows calling C functions and use C data types from pure scripting language and therefore develop “system code” more productively. For PHP, FFI opens a way to write PHP extensions and bindings to C libraries in pure PHP.

Proposal

It is proposed to extend PHP with a simple FFI API designed after LuaJTI/FFI and Python/CFFI (actually, the second was based on the first). This API allows loading shared libraries (.DLL or .so), calling C functions and accessing C data structures, in pure PHP, without learning 3rd “intermediate” language.

The public API is implemented as a single class FFI with few static methods (some of them may be called non-statically), and overloaded object methods, that perform actual interaction with C data. Before diving into the details of FFI API, take a look into few examples, to see, how it's simple to use this API for regular tasks.

Calling a function from shared library

<?php
// create FFI object, loading libc and exporting function printf()
$ffi = new FFI(
    "int printf(const char *format, ...);", // this is regular C declaration
    "libc.so.6");
// call C printf()
$ffi->printf("Hello %s!\n", "world");

Calling a function, returning structure through argument

<?php
// create gettimeofday() binding
$ffi = new FFI("
    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");
// create C data structures
$tv = $ffi->new("struct timeval");
$tz = $ffi->new("struct timezone");
// calls C gettimeofday()
var_dump($ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz)));
// access field of C data structure
var_dump($tv->tv_sec);
// print the whole C data structure
var_dump($tz);

Accessing C variables

<?php
// create FFI object, loading libc and exporting errno variable
$ffi = new FFI("int errno;", // this is regular C declaration
    "libc.so.6");
// print C errno
var_dump($ffi->errno);

Working with C arrays

<?php
// create C data structure
$a = FFI::new("unsigned char[1024*1024]"); // "FFI::new" is different from "new FFI"
// work with it like with regular PHP array
for ($i = 0; $i < 1024 * 1024; $i++) {
  $a[$i] = $i;
}
var_dump($a[25]);
var_dump(count($a));
$sum = 0;
foreach ($a as $n) {
  $sum += $n;
}
var_dump($sum);
var_dump(FFI::sizeof($a));

PHP FFI API

FFI::__construct([string $cdef = "" [, string $lib = null]])

Creates a new FFI object. The first optional argument is a string, containing a sequence of declarations in regular C languages (types, structures, functions, variables, etc). Actually, this string may be copy-pasted from C header files. All the declared entities are going to be available to PHP through overloaded functions or other FFI API functions. Declared C variables may be accessed as FFI object properties, C functions - called as FFI object methods. C type names may be used to create new C data structures... The second optional argument is a shared library file name, to be loaded and linked with definitions.

We don't support C preprocessor, yet. #include, #define and CPP macros don't work.

FFI::new(mixed $type [, bool $own = true [, bool $persistent = false]]): FFI\CData

Creates native data structure of given C type. $type may be any valid C string declaration or an instance of FFI\CType created before. Using the second argument, it's possible to create owned data (default), or unmanaged. In first case, data structure is going to leave together with returned FFI\CData object, and die when last reference is released by regular PHP reference counting or GC. However, in some cases, programmer may decide to keep C data even after, releasing of FFI\CData object and manually free it through FFI::free() similar to regular C. By default, the memory for the data is allocated on PHP request heap (using emalloc()), but it's also possible to use system heap, specifying true in the third argument.

This function may be called statically and use only predefined C type names (e.g int, char, etc), or as a method or previusly created FFI object. In last case, it may reuse any types declared in constructor.

...

FFI::free(FFI\CData $cdata): void

Manually release previously created “not-owned” data structure.

FFI::cast(mixed $type, FFI\CData $cdata): FFI\CData

Performs C type cast. It creates a new FFI\CData object, that references the same C data structure but using different type. The resulting object doesn't own the C data, and the source $cdata must relive the result. C type mat be specified as any valid C type declaration string or FFI\CType object created before.

This function may be called statically and use only predefined C type names (e.g int, char, etc), or as a method or previusly created FFI object. In last case, it may reuse any types declared in constructor.

FFI::type(string $type): FFI\CType

Creates FF\CType object

FFI::array

FFI::addr

FFI::type(FFI\CData $data): FFI\CType

Returns type of the given FFI::CData

FFI::sizeof

FFI::alignof

FFI::memcpy

FFI::memcmp

FFI::memset

FFI::string

FFI::load

FFI::scope

Backward Incompatible Changes

None, except of introduced FFI class and namespace.

Proposed PHP Version(s)

PHP 7.4

RFC Impact

To Opcache

FFI is designed in conjunction with preloading (curently implemented as part of opcache). FFI C headers may be loaded during preloading by FFI::load() and become available to all the following HTTP requests without reloading overhead.

php.ini Defaults

ffi.enable=false|preload|true 

allows enabling or disabling FFI API usage, or restricting it only to preloaded files. The default value is preload

Open Issues

Make sure there are no open issues when the vote starts!

Future Scope

Currently, the performance of C data structures access is worst, then access of native PHP data structures (arrays and objects). This is a common problem of LuaJIT (in interpretator mode) and Python as well. However, LuaJIT may also compile these access code in very efficient way (almost as C compiler), and produce highly efficient machine code. It's planned to try similar things, implementing JIT for PHP.

Proposed Voting Choices

Include FFI extension into PHP-7.4 This project requires 50%+1 majority

Patches and Tests

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

Rejected Features

Keep this updated with features that were discussed on the mail lists.

rfc/ffi.1543928351.txt.gz · Last modified: 2018/12/04 12:59 by dmitry