As Eli said, it doesn't stop you from accessing the Python C-API. When you call an external function from LLVM JIT, it effectively uses dlopen() in the process space, so if you are working from inside llvmpy, you already have all the Python interpreter characters, you can even interact with the active interpreter that called ExecutionEngine, or you You can use the new Python interpreter if necessary.
To get started, create a new C file with our evaluator.
#include <Python.h> void python_eval(const char* s) { PyCodeObject* code = (PyCodeObject*) Py_CompileString(s, "example", Py_file_input); PyObject* main_module = PyImport_AddModule("__main__"); PyObject* global_dict = PyModule_GetDict(main_module); PyObject* local_dict = PyDict_New(); PyObject* obj = PyEval_EvalCode(code, global_dict, local_dict); PyObject* result = PyObject_Str(obj); // Print the result if you want. // PyObject_Print(result, stdout, 0); }
Here is a little Makefile to compile:
CC = gcc LPYTHON = $(shell python-config --includes) CFLAGS = -shared -fPIC -lpthread $(LPYTHON) .PHONY: all clean all: $(CC) $(CFLAGS) cbits.c -o cbits.so clean: -rm cbits.c
Then we start with the usual LLVM template, but use ctypes to load the shared object of our cbits.so shared library into the global process space to have the python_eval character. Then just create a simple LLVM module with a function, select a line with some Python source using ctypes and pass a pointer to ExecutionEngine, which runs the JIT'd function from our module, which in turn passes the Python source to a C-function that calls the C-API Python and then returns back to LLVM JIT.
import llvm.core as lc import llvm.ee as le import ctypes import inspect ctypes._dlopen('./cbits.so', ctypes.RTLD_GLOBAL) pointer = lc.Type.pointer i32 = lc.Type.int(32) i64 = lc.Type.int(64) char_type = lc.Type.int(8) string_type = pointer(char_type) zero = lc.Constant.int(i64, 0) def build(): mod = lc.Module.new('call python') evalfn = lc.Function.new(mod, lc.Type.function(lc.Type.void(), [string_type], False), "python_eval") funty = lc.Type.function(lc.Type.void(), [string_type]) fn = lc.Function.new(mod, funty, "call") fn_arg0 = fn.args[0] fn_arg0.name = "input" block = fn.append_basic_block("entry") builder = lc.Builder.new(block) builder.call(evalfn, [fn_arg0]) builder.ret_void() return fn, mod def run(fn, mod, buf): tm = le.TargetMachine.new(features='', cm=le.CM_JITDEFAULT) eb = le.EngineBuilder.new(mod) engine = eb.create(tm) ptr = ctypes.cast(buf, ctypes.c_voidp) ax = le.GenericValue.pointer(ptr.value) print 'IR'.center(80, '=') print mod mod.verify() print 'Assembly'.center(80, '=') print mod.to_native_assembly() print 'Result'.center(80, '=') engine.run_function(fn, [ax]) if __name__ == '__main__':
You should draw the following conclusion:
=======================================IR======================================= ; ModuleID = 'call python' declare void @python_eval(i8*) define void @call(i8* %input) { entry: call void @python_eval(i8* %input) ret void } =====================================Result===================================== Hello from Python C-API inside of LLVM!