FFI C declaration composer

This example illustrates how to generate foreign function interface (FFI) bindings for a C library using the Lua plugin for GCC and the ffi-cdecl module. The C API of the library is extracted using a C source file that contains capture macros, and matching FFI C bindings are generated using the C declaration composer for GCC.

After following the installation instructions, you may run the example:

make -C ffi-cdecl

If the GCC plugin is not installed in the GCC plugin directory:

make -C ffi-cdecl GCCLUA=$HOME/projects/gcc-lua/gcc/gcclua.so

The FFI C bindings are written to the file C.lua:

local ffi = require("ffi")

ffi.cdef[[
int getopt(int, char *const *, const char *);
extern char *optarg;
typedef int clockid_t;
struct timespec {
  long int tv_sec;
  long int tv_nsec;
};
static const int RLIM_INFINITY = -1;
]]

1 Usage

To generate FFI C bindings for a library, we compose a Lua script for the GCC C compiler:

local gcc = require("gcc")
local cdecl = require("gcc.cdecl")
local fficdecl = require("ffi-cdecl")

-- Output generated assembly to /dev/null
gcc.set_asm_file_name(gcc.HOST_BIT_BUCKET)

First, we define a function that captures C declarations in a table:

-- Captured C declarations.
local decls = {}
-- Type declaration identifiers.
local types = {}

-- Parse C declaration from capture macro.
gcc.register_callback(gcc.PLUGIN_PRE_GENERICIZE, function(node)
  local decl, id = fficdecl.parse(node)
  if decl then
    if decl:class() == "type" or decl:code() == "type_decl" then
      types[decl] = id
    end
    table.insert(decls, {decl = decl, id = id})
  end
end)

Second, we output FFI C bindings for the captured declarations to a Lua file:

-- Formats the given declaration as a string of C code.
local function format(decl, id)
  if decl:class() == "constant" then
    return "static const int " .. id .. " = " .. decl:value()
  end
  return cdecl.declare(decl, function(node)
    if node == decl then return id end
    return types[node]
  end)
end

-- Output captured C declarations to Lua file.
gcc.register_callback(gcc.PLUGIN_FINISH_UNIT, function()
  local result = {}
  for i, decl in ipairs(decls) do
    result[i] = format(decl.decl, decl.id) .. ";\n"
  end
  local f = assert(io.open(arg.output, "w"))
  f:write([=[
local ffi = require("ffi")

ffi.cdef[[
]=], table.concat(result), [=[
]]

-- Load POSIX real time extensions into global namespace.
local C = ffi.C
if not pcall(function() return ffi.C.clock_gettime end) then
  C = ffi.load("rt", true)
end

return C
]=])
  f:close()
end)

The script is executed by loading the Lua plugin for GCC as follows:

gcc -S C.c -fplugin=gcclua -fplugin-arg-gcclua-script=C.lua.in -fplugin-arg-gcclua-output=C.lua

If the GCC plugin is not installed in the GCC plugin directory:

gcc -S C.c -fplugin=$HOME/projects/gcc-lua/gcc/gcclua.so -fplugin-arg-gcclua-script=C.lua.in -fplugin-arg-gcclua-output=C.lua

2 C capture files

For the purpose of capturing the C API of a library, the C header ffi-cdecl.h defines a range of capture macros. These macros are used in a C source file that includes the header(s) of the library. Each capture macro receives an identifier that refers to a C declaration or preprocessor constant.

Consider the following capture file C.c for the POSIX C API:

#define _XOPEN_SOURCE 700
#include <libgen.h>
#include <sys/resource.h>
#include <time.h>
#include <unistd.h>

#include "ffi-cdecl.h"

/* capture a function */
cdecl_func(getopt)
/* capture a global variable */
cdecl_var(optarg)
/* capture a type declaration */
cdecl_type(clockid_t)
/* capture a struct */
cdecl_struct(timespec)
/* capture a constant */
cdecl_const(RLIM_INFINITY)

The identifier in the generated FFI C binding may be overridden using the preprocessor:

#define mygetopt getopt
cdecl_func(mygetopt)

3 C capture macros

The C header ffi-cdecl.h defines the following capture macros:

cdecl_type(id)

Captures the type declaration with the given identifier.

cdecl_memb(id)

Captures the type definition of a struct, union or enum type with the given identifier.

cdecl_struct(tag)

Captures the type definition of a struct type with the given tag.

cdecl_union(tag)

Captures the type definition of a union type with the given tag.

cdecl_enum(tag)

Captures the type definition of an enumeration type with the given tag.

cdecl_func(id)

Captures the function declaration with the given identifier.

Any subexpression containing an address-of, cast, or comma operator is replaced by its right-most operand.

cdecl_var(id)

Captures the variable declaration with the given identifier.

Any subexpression containing an address-of, cast, or comma operator is replaced by its right-most operand.

cdecl_const(id)

Captures the integer constant with the given identifier.

Any subexpression containing an address-of, cast, or comma operator is replaced by its right-most operand.