More thoughts on foreign function interfaces
I have been thinking some more about Using a C library from Java, especially the Translate to JVM approach.
When I tried this, I mostly got caught up in compiling the target library to MIPS in the first place. I tried building a GCC cross compiler for MIPS, but ran into some build errors and kinda gave up.
Since then, I’ve looked at using clang to target a MIPS binary, and this looks feasible, but I haven’t tried it. There’s also an option to build to LLVM IR and interpret that. This seems like a good idea, but I’ve heard, via other projects that work off LLVM IR, that it’s something of a moving target; also, it seems like a more complex process than it would be to integrate a MIPS core.
I’ll need to compile a C library with enough functionality to support the target library, and the library has C++ components so I’ll need to deal with that too. At least the library exposes a C interface so I don’t need to build C++ FFI glue.
I identified all of these problems earlier on, but despite all this I wasn’t quite sure how I would actually call the target library’s functions from outside the MIPS core. I knew I could use interrupts to switch out of the MIPS context, like syscalls do to switch from user to kernel context, but I didn’t quite know where to fit that machinery in.
So I came up with some options:
- I could write some glue, in C, that I package with the library, to provide a syscall-like interface between the MIPS guest and the host (Java) code. The Java code acts as a supervisor and has access to the MIPS core’s registers, memory (& stack), etc., so would be able to use that for passing arguments & context around. But if possible, I’d like to do it without writing C glue code.
-
I can have some host (Java) code that set up the stack & environment etc. required to call a function, and then start executing at the right location. It’ll have to capture the return somehow, which I can either intercept with something akin to a debugger breakpoint, or place some code in memory to invoke an interrupt (as in option 1).
It occurred to me while I was considering this option, that even though I’m running the code in a virtual host environment that’s local to my own program, it’s still C code and I still have to think about all the usual FFI stuff: Type signatures, struct packing, memory management etc. That means that even if I run the library in an emulated core, I’ll still end up building an interface similar to Java Native Access or the newer Java Foreign Function and Memory (FFM) API. I guess I kind of hoped that part of the process would be simpler.
I still think it’s a viable solution with real benefits, especially if I need to target multiple platforms. I also wonder if it might be feasible to compile or transpile the C library directly to Java, whether to source code or bytecode. That sounds far more difficult to me because it depends on compiler internals, but maybe building a new backend onto an existing compiler could be practical?