I am surprised many don't know how libffi works. Yes, it does generate native machine code to handle your call. Look it up.
Yes it's probably worse than doing the jit in Ruby interpreter, since there you can also inline the type conversion calls, but there principles are the same.
It certainly uses native machine code, but I don't think it generates any at runtime outside of the reverse-FFI closures (at least on linux)? PROT_EXEC at least isn't used outside of them, which'd be a minimum requirement for linux JITting.
Running in a debugger an ffi_call to a "int add(int a, int b)" leads me to https://github.com/libffi/libffi/blob/1716f81e9a115d34042950... as the assembly directly before the function is invoked, and, besides clearly not being JITted from me being able to link to it, it is clearly inefficient and unnecessarily general for the given call, loading 7 arguments instead of just the two necessary ones.
Oops, you are right. I think because the other direction of libffi - ffi_closure - has a jitted trampoline, I mistakenly thought both directions are jitted. Thanks for the correction.
And the JITting in closures amounts to a total of three instructions; certainly not for speed, rather just as the bare minimum to generate distinct function pointers at runtime.
Yes it's probably worse than doing the jit in Ruby interpreter, since there you can also inline the type conversion calls, but there principles are the same.
Edit: This is wrong, see comments below.