The problem is that the C programming language doesn't offer any facilities for manually constructing a call frame. In pure C, you cannot push an arbitrary value onto the stack, perform a non-local jump to a subroutine label, specify which register a value is stored in, etc. You can accomplish some of this with inlined assembly code, but if you're playing with the stack you run the risk of corrupting your program and crashing your computer. Keep in mind that local variables to a function are typically accessed as an offset from the esp register (on x86 anyway), so pushing things onto the stack will change the value of that register, which in turn will make constant offsets from that location point to different values.
When we are talking about calling arbitrary library functions from Parrot, there are three approaches:
- Generate a list of function calling "thunks" from a predefined list of function signatures. You end up with hundreds of such thunks but still can't interact with some functions without adding a signature and recompiling Parrot. Also, all those hundreds of functions need to be loaded into memory when Parrot starts, creating a larger memory footprint and slower startup times.
- Create a function in pure assembly that takes a signature string and manually constructs a call frame. In assembly we can interact with the system stack and processor registers directly. There are two problems with this: Portability, because we would need to write a new assembly routine for each combination of platform and assembler. Also, we couldn't cache the results of the generated call frame, every call to the library function would have to manually build the frame again.
- Use a JIT solution. The current frame builder uses Parrot's old JIT framework to build the frame code in a block of memory, but only works on x86. A "real" solution here would use a JIT package like LLVM or libJIT to generate these frames. This has the benefit that we can cache the generated thunks. We generate them lazily when we need them and cache them for when we need them again.
This in mind, I have two challenges for readers who have some spare time:
- Create a proof-of-concept NCI frame builder using a "real" JIT engine. You can pick anything you want (libJIT, LLVM, GNU Lighting, dynASM, nanoJIT, etc). It must be able to take a function pointer, a string representing the call signature, and proper arguments and no other unnecessary state information or metadata. The frame builder should return the compiled thunk from the JIT engine that can be used to call the target function.
- Create a proof-of-concept NCI function caller using pure assembly or C with inline assembly. Your assembly routine should take a signature string, a function pointer, and a list of arguments, and a pointer to a memory location to hold a return value (if any).
There is no real prize for "winning", but I'll post good submissions here to my blog, publically talk about how awesome you are, and maybe try to get your solution added to Parrot in some way and in some form.