diff --git a/doc/ext_ffi_semantics.html b/doc/ext_ffi_semantics.html index f48c6406..b2b3af30 100644 --- a/doc/ext_ffi_semantics.html +++ b/doc/ext_ffi_semantics.html @@ -8,6 +8,12 @@ +
Given that the FFI library is designed to interface with C code and that declarations can be written in plain C syntax, it -closely follows the C language semantics wherever possible. Some -concessions are needed for smoother interoperation with Lua language -semantics. But it should be straightforward to write applications -using the LuaJIT FFI for developers with a C or C++ background. +closely follows the C language semantics, wherever possible. Some +minor concessions are needed for smoother interoperation with Lua +language semantics. +
++Please don't be overwhelmed by the contents of this page — this +is a reference and you may need to consult it, if in doubt. It doesn't +hurt to skim this page, but most of the semantics "just work" as you'd +expect them to work. It should be straightforward to write +applications using the LuaJIT FFI for developers with a C or C++ +background. +
++Please note: this is the first public release of the FFI library. This +does not comprise the final specification for the FFI semantics, yet. +Some of the semantics may need to be changed, based on feedback from +developers. Please report any problems +you've encountered or any improvements you'd like to see — thank +you!
-TODO +These conversion rules apply for read accesses to +C types: indexing pointers, arrays or +struct/union types; reading external variables or +constant values; retrieving return values from C calls: +
+Input | +Conversion | +Output | +
int8_t, int16_t | →sign-ext int32_t → double | number |
uint8_t, uint16_t | →zero-ext int32_t → double | number |
int32_t, uint32_t | → double | number |
int64_t, uint64_t | boxed value | 64 bit int cdata |
double, float | → double | number |
bool | 0 → true, otherwise false | boolean |
Complex number | boxed value | complex cdata |
Vector | boxed value | vector cdata |
Pointer | boxed value | pointer cdata |
Array | boxed reference | reference cdata |
struct/union | boxed reference | reference cdata |
+Bitfields or enum types are treated like their underlying +type. +
++Reference types are dereferenced before a conversion can take +place — the conversion is applied to the C type pointed to +by the reference. +
+ ++These conversion rules apply for write accesses to +C types: indexing pointers, arrays or +struct/union types; initializing cdata objects; +casts to C types; writing to external variables; passing +arguments to C calls: +
+Input | +Conversion | +Output | +
number | → | double |
boolean | false → 0, true → 1 | bool |
nil | NULL → | (void *) |
userdata | userdata payload → | (void *) |
lightuserdata | lightuserdata address → | (void *) |
string | match against enum constant | enum |
string | copy string data + zero-byte | int8_t[], uint8_t[] |
string | string data → | const char[] |
table | table initializer | Array |
table | table initializer | struct/union |
cdata | cdata payload → | C type |
+If the result type of this conversion doesn't match the +C type of the destination, the +conversion rules between C types +are applied. +
++Reference types are immutable after initialization ("no reseating of +references"). For initialization purposes or when passing values to +reference parameters, they are treated like pointers. Note that unlike +in C++, there's no way to implement automatic reference generation of +variables under the Lua language semantics. If you want to call a +function with a reference parameter, you need to explicitly pass a +one-element array. +
+ ++These conversion rules are more or less the same as the standard +C conversion rules. Some rules only apply to casts, or require +pointer or type compatibility: +
+Input | +Conversion | +Output | +
Signed integer | →narrow or sign-extend | Integer |
Unsigned integer | →narrow or zero-extend | Integer |
Integer | →round | double, float |
double, float | →trunc int32_t →narrow | (u)int8_t, (u)int16_t |
double, float | →trunc | (u)int32_t, (u)int64_t |
double, float | →round | float, double |
Number | n == 0 → 0, otherwise 1 | bool |
bool | false → 0, true → 1 | Number |
Complex number | convert real part | Number |
Number | convert real part, imag = 0 | Complex number |
Complex number | convert real and imag part | Complex number |
Number | convert scalar and replicate | Vector |
Vector | copy (same size) | Vector |
struct/union | take base address (compat) | Pointer |
Array | take base address (compat) | Pointer |
Function | take function address | Function pointer |
Number | convert via uintptr_t (cast) | Pointer |
Pointer | convert address (compat/cast) | Pointer |
Pointer | convert address (cast) | Integer |
Array | convert base address (cast) | Integer |
Array | copy (compat) | Array |
struct/union | copy (identical type) | struct/union |
+Bitfields or enum types are treated like their underlying +type. +
++Conversions not listed above will raise an error. E.g. it's not +possible to convert a pointer to a complex number or vice versa. +
+ ++The following default conversion rules apply when passing Lua objects +to the variable argument part of vararg C functions: +
+Input | +Conversion | +Output | +
number | → | double |
boolean | false → 0, true → 1 | bool |
nil | NULL → | (void *) |
userdata | userdata payload → | (void *) |
lightuserdata | lightuserdata address → | (void *) |
string | string data → | const char * |
float cdata | → | double |
Array cdata | take base address | Element pointer |
struct/union cdata | take base address | struct/union pointer |
Function cdata | take function address | Function pointer |
Any other cdata | no conversion | C type |
+To pass a Lua object, other than a cdata object, as a specific type, +you need to override the conversion rules: create a temporary cdata +object with a constructor or a cast and initialize it with the value +to pass: +
++Assuming x is a Lua number, here's how to pass it as an +integer to a vararg function: +
++ffi.cdef[[ +int printf(const char *fmt, ...); +]] +ffi.C.printf("integer value: %d\n", ffi.new("int", x)) ++
+If you don't do this, the default Lua number → double +conversion rule applies. A vararg C function expecting an integer +will see a garbled or uninitialized value.
--Creating a cdata object with ffi.new() -or the equivalent constructor syntax always initializes its contents, -too. Different rules apply, depending on the number of optional +Creating a cdata object with +ffi.new() or the +equivalent constructor syntax always initializes its contents, too. +Different rules apply, depending on the number of optional initializers and the C types involved:
-TODO +The following rules apply if a Lua table is used to initialize an +Array or a struct/union:
++Example: +
++local ffi = require("ffi") + +ffi.cdef[[ +struct foo { int a, b; }; +union bar { int i; double d; }; +struct nested { int x; struct foo y; }; +]] + +ffi.new("int[3]", {}) --> 0, 0, 0 +ffi.new("int[3]", {1}) --> 1, 1, 1 +ffi.new("int[3]", {1,2}) --> 1, 2, 0 +ffi.new("int[3]", {1,2,3}) --> 1, 2, 3 +ffi.new("int[3]", {[0]=1}) --> 1, 1, 1 +ffi.new("int[3]", {[0]=1,2}) --> 1, 2, 0 +ffi.new("int[3]", {[0]=1,2,3}) --> 1, 2, 3 +ffi.new("int[3]", {[0]=1,2,3,4}) --> error: too many initializers + +ffi.new("struct foo", {}) --> a = 0, b = 0 +ffi.new("struct foo", {1}) --> a = 1, b = 0 +ffi.new("struct foo", {1,2}) --> a = 1, b = 2 +ffi.new("struct foo", {[0]=1,2}) --> a = 1, b = 2 +ffi.new("struct foo", {b=2}) --> a = 0, b = 2 +ffi.new("struct foo", {a=1,b=2,c=3}) --> a = 1, b = 2 'c' is ignored + +ffi.new("union bar", {}) --> i = 0, d = 0.0 +ffi.new("union bar", {1}) --> i = 1, d = ? +ffi.new("union bar", {[0]=1,2}) --> i = 1, d = ? '2' is ignored +ffi.new("union bar", {d=2}) --> i = ?, d = 2.0 + +ffi.new("struct nested", {1,{2,3}}) --> x = 1, y.a = 2, y.b = 3 +ffi.new("struct nested", {x=1,y={2,3}}) --> x = 1, y.a = 2, y.b = 3 ++ +
+All of the standard Lua operators can be applied to cdata objects or a +mix of a cdata object and another Lua object. The following list shows +the valid combinations. All other combinations currently raise an +error. +
++Reference types are dereferenced before performing each of +the operations below — the operation is applied to the +C type pointed to by the reference. +
+ ++Note: since there's (deliberately) no address-of operator, a cdata +object holding a value type is effectively immutable after +initialization. The JIT compiler benefits from this fact when applying +certain optimizations. +
++As a consequence of this, the elements of complex numbers and +vectors are immutable. But the elements of an aggregate holding these +types may be modified of course. I.e. you cannot assign to +foo.c.im, but you can assign a (newly created) complex number +to foo.c. +
+ ++Lua tables may be indexed by cdata objects, but this doesn't provide +any useful semantics — cdata objects are unsuitable as table +keys! +
++A cdata object is treated like any other garbage-collected object and +is hashed and compared by its address for table indexing. Since +there's no interning for cdata value types, the same value may be +boxed in different cdata objects with different addresses. Thus +t[1LL+1LL] and t[2LL] usually do not point to +the same hash slot and they certainly do not point to the same +hash slot as t[2]. +
++It would seriously drive up implementation complexity and slow down +the common case, if one were to add extra handling for by-value +hashing and comparisons to Lua tables. Given the ubiquity of their use +inside the VM, this is not acceptable. +
++There are three viable alternatives, if you really need to use cdata +objects as keys: +
+@@ -297,14 +789,9 @@ is not garbage collected. Objects which are passed as an argument to an external C function are kept alive until the call returns. So it's generally safe to create temporary cdata objects in argument lists. This is a common -idiom for passing specific C types to vararg functions: +idiom for passing specific C types to +vararg functions.
-
-ffi.cdef[[
-int printf(const char *fmt, ...);
-]]
-ffi.C.printf("integer value: %d\n", ffi.new("int", x)) -- OK
-
Memory areas returned by C functions (e.g. from malloc()) must be manually managed, of course. Pointers to cdata objects are @@ -468,12 +955,12 @@ suboptimal performance, especially when used in inner loops: