I want to compile functions ahead of time to avoid compilation times, but I do not want to install a compiler. The bundled llvm should be sufficient since it also works for JIT.
There is a StackOverflow answer by @jpivarski llvm - Marshaling object code for a Numba function - Stack Overflow which does exactly that, but I can only get half of it to work.
Compiling a function with scalar parameters to a byte array, then loading it again with llvm and calling it as a C-function works perfectly. However, I would also like to use NumPy Arrays and I do not know the API for that. On Stackoverflow, it was suggested to call the cpython wrapper, which segfaults for me. Maybe the API changed since then?
This is the code so far which is mostly due to @jpivarski and @stuartarchibald so thanks to both 
from numba import njit
import numpy as np
import ctypes
import llvmlite.binding as llvm
def compile_function_to_bytes():
# Function to compile
@njit
def foo(x):
return x * 2
# Trigger JIT
foo(12)
# Find function signature, look it up in JITed library and return bytes
sig = foo.signatures[0]
lib = foo.overloads[sig].library
bytes = lib._get_compiled_object()
cfunc_name = foo.overloads[sig].fndesc.llvm_cfunc_wrapper_name
cpython_name = foo.overloads[sig].fndesc.llvm_cpython_wrapper_name
return bytes, cfunc_name, cpython_name
def main():
bytes, cfunc_name, cpython_name = compile_function_to_bytes()
# Initialize llvm
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
def create_execution_engine():
target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
backing_mod = llvm.parse_assembly("")
engine = llvm.create_mcjit_compiler(backing_mod, target_machine)
return engine
# Load bytes into llvm and retrieve cfunc from it
obj = llvm.ObjectFileRef.from_data(bytes)
engine = create_execution_engine()
engine.add_object_file(obj)
cfunc_ptr = engine.get_function_address(cfunc_name)
cpython_ptr = engine.get_function_address(cpython_name)
# Convert pointer to cfunc
foo_cfunc = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)(cfunc_ptr)
result = foo_cfunc(100)
print("Calling as cfunc. This should work!")
print("result:", result)
class PyTypeObject(ctypes.Structure):
_fields_ = ("ob_refcnt", ctypes.c_int), ("ob_type", ctypes.c_void_p), ("ob_size", ctypes.c_int), ("tp_name", ctypes.c_char_p)
class PyObject(ctypes.Structure):
_fields_ = ("ob_refcnt", ctypes.c_int), ("ob_type", ctypes.POINTER(PyTypeObject))
PyObjectPtr = ctypes.POINTER(PyObject)
foo_cpython = ctypes.CFUNCTYPE(PyObjectPtr, PyObjectPtr, PyObjectPtr, PyObjectPtr)(cpython_ptr)
# Assuming this API:
# https://docs.python.org/3/c-api/structures.html#c.PyCFunctionWithKeywords
def foo_wrapped(*args, **kwargs):
closure = ()
return foo_cpython(
ctypes.cast(id(closure), PyObjectPtr),
ctypes.cast(id(args), PyObjectPtr),
ctypes.cast(id(kwargs), PyObjectPtr))
print("Calling as cpython wrapper. This does not work yet.")
result = foo_wrapped(100) # <------- Segfault here
print("result:", result)
main()
This crashes when calling foo_wrapped. Here is a stacktrace (on WSL2, crashes when calling the wrapper on other OSes):
Calling as cfunc. This should work!
result: 200
Calling as cpython wrapper. This does not work yet.
Thread 1 "python3" received signal SIGSEGV, Segmentation fault.
0x000000000055d33c in PyErr_SetString ()
(gdb) bt
#0 0x000000000055d33c in PyErr_SetString ()
#1 0x00007ffff75b510b in cpython::__main__::compile_function_to_bytes::$3clocals$3e::foo$241(long long) ()
#2 0x00007ffff7474ff5 in ?? () from /lib/x86_64-linux-gnu/libffi.so.7
#3 0x00007ffff747440a in ?? () from /lib/x86_64-linux-gnu/libffi.so.7
#4 0x00007ffff7792316 in _ctypes_callproc ()
from /usr/lib/python3.8/lib-dynload/_ctypes.cpython-38-x86_64-linux-gnu.so
#5 0x00007ffff7792af7 in ?? () from /usr/lib/python3.8/lib-dynload/_ctypes.cpython-38-x86_64-linux-gnu.so
#6 0x00000000005f3010 in _PyObject_MakeTpCall ()
#7 0x000000000056fd36 in _PyEval_EvalFrameDefault ()
#8 0x0000000000568d9a in _PyEval_EvalCodeWithName ()
#9 0x00000000005f5b33 in _PyFunction_Vectorcall ()
#10 0x000000000056aadf in _PyEval_EvalFrameDefault ()
#11 0x0000000000568d9a in _PyEval_EvalCodeWithName ()
#12 0x00000000005f5b33 in _PyFunction_Vectorcall ()
#13 0x000000000056aadf in _PyEval_EvalFrameDefault ()
#14 0x0000000000568d9a in _PyEval_EvalCodeWithName ()
#15 0x000000000068cdc7 in PyEval_EvalCode ()
#16 0x000000000067e161 in ?? ()
#17 0x000000000067e1df in ?? ()
#18 0x000000000067e281 in ?? ()
#19 0x000000000067e627 in PyRun_SimpleFileExFlags ()
#20 0x00000000006b6e62 in Py_RunMain ()
#21 0x00000000006b71ed in Py_BytesMain ()
#22 0x00007ffff7ded0b3 in __libc_start_main (main=0x4ef190 <main>, argc=2, argv=0x7fffffffe248,
init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe238)
at ../csu/libc-start.c:308
#23 0x00000000005f96de in _start ()