My question is about JIT'ing foreign function interfaces in general, but I will use LuaJIT as a specific example. It is said that LuaJIT's FFI is faster than binding to C directly because calls to the C functions can get JIT compiled.
Could anyone explain this. Typically, the C function being bound already exists in a library so it has already been compiled, so what exactly gets JIT'ed.
LuaJIT extensions: the FFI Library
[...] The code generated by the JIT-compiler for accesses to C data structures from Lua code is on par with the code a C compiler would generate. Calls to C functions can be inlined in JIT-compiled code, unlike calls to functions bound via the classic Lua/C API.
In response to your question of what gets compiled, it's just that: the calls to C functions. In fact, calls to C functions via the classic C API will never be compiled, even in a future version of LuaJIT. They will raise NYI messages, causing any traces to abort and effectively preventing surrounding Lua code from being compiled. For example, a classic C function call FUNCC
opcode within a loop will prevent that loop from being compiled. This just means that LuaJIT will fall back to its interpreter, which is still quite fast.
To reiterate, LuaJIT does not perform any magic on already-compiled C code. It simply inlines FFI calls to C functions within JIT assembly code.