JIT FFI Experiment

I experimented with idea of compiling code for FFI calls just before a foreign function gets called. For this purpose I wrote a small demo.

demo.c memory maps 256 kilobytes of memory, writes some ia64 instructions which calls a c function. Then it calls those instructions it just wrote. When it works, it prints "Arcane magic 24." to the screen. The number is passed to it in the function call.

This kind of just-in-time compiled "gate" into a C-world is quite large. The call alone requires 13 bytes. Each argument passed in, and returned will likely require another 16-32 bytes of code to parse. You may get it smaller with some clever design I'm not aware of yet. A compiled call handle for read call might take 144 bytes. Though for majority of the code you could hold the handles uncompiled most of the time. All programs do not even use every definition that are provided by your usual FFI bindings. I think this is a feasible approach in the context I've thought about.

Useful links

To get the demo working, I found few resources useful.

Tommi Pisto has written an assembler which works in javascript. It has a convenient real-time compiling widget which explains the structure of an instruction when you click into the bytecode output. It's a quite nice way to fetch an instruction encoding.

System V Application Binary Interface documents the function calling convention in x86-64 linux. The page 21 has a table which points out which registers are used to carry arguments. And which registers are expected to be preserved for the callee.

Finally, when I was unsure about the encoding of some instruction, I also have Intel Architecture Manuals around.

What's the point

Many language implementations have a JIT compiler bundled into them. For efficient code for some hardware-level situations it's likely that foreign function interfacing needs to be acknowledged by JIT anyway.

Some afterthoughts

When I've been using ctypes in python, the ffi code seems to end up being like this:

lib = CDLL('some.so')

sillyFunction = lib.someSillyFunction
sillyFunction.argtypes = [c_int, c_int]
sillyFunction.restype = c_int

It continues with several other definitions, maybe structure definitions in a middle, then it's finished and called by an another library. Basically this is C header definitions cleaned and translated into verbose form. Why not have it other way around?

I don't understand the attraction around C headers anyway. Why have anything like those in the first place? Why the C compilers cannot generate header declarations automatically from the .c source? It would save bunch of time in accessing C code from other coding environments.

That's it for now. I've been working on a bytecode compiler for the language I prototyped while ago. It's likely I will write more posts related to the same subject. If I'm not getting dragged into another topic by any coincidence.

Similar posts