2013-03-02 97 views
6

我在python中编写了一个语言词法分析器/解析器/编译器,它应该稍后在LLVM JIT-VM(使用llvm-py)中运行。前两个步骤现在非常简单,但是(即使我还没有开始编译任务),我看到一个问题,当我的代码想要调用Python代码(或者一般情况下)或者与Python词法分析器交互时/解析器/编译器(特殊)分别。我主要关心的是,代码应该能够在运行时动态地将其他代码加载到VM中,因此它必须从VM内触发Python中的整个词法分析器/分析器/编译器链。从LLVM调用Python代码JIT

首先:这是否可能,或者一旦它启动,VM是“不可变的”?

如果是我目前看到3级可能的解决方案(我打开其他建议)

  • “打出去”的虚拟机,并有可能直接调用主流程的Python函数(也许注册为LLVM函数,以某种方式重定向到主进程)。我没有发现任何有关这方面的信息,无论如何我不确定,如果这是一个好主意(安全等)。
  • 将运行时(静态或动态运行时)编译到LLVM-Assembly/-IR中。这要求IR代码能够修改它运行的虚拟机
  • 将运行时(静态)编译到库中并直接将其加载到VM中。再次,它必须能够将函数(等)添加到它运行的VM中。

回答

2

您可以从LLVM JIT编写的代码调用外部C函数。你还需要什么?

这些外部函数将在执行过程中找到,这意味着如果您将Python链接到您的VM中,则可以调用Python的C API函数。

“VM”可能不像你想象的那么神奇:-)最后,它只是机器代码,它在运行时被发送到缓冲区并从那里执行。如果此代码可以访问运行过程中的其他符号,则它可以执行该进程中的任何其他代码都可以执行的操作。

6

就像Eli说的那样,并没有阻止你调用Python C-API。当您从LLVM JIT内部调用外部函数时,它实际上只在流程空间上使用dlopen(),所以如果您从llvmpy内部运行,您已经可以访问所有Python解释器符号,甚至可以与活动解释器调用ExecutionEngine,或者如果需要,可以旋转一个新的Python解释器。

为了让您开始,请使用我们的评估程序创建一个新的C文件。

#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); 
} 

这里有一个小的Makefile来编译:

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 

然后,我们开始与通常的样板为LLVM,但使用的ctypes我们cbits.so共享库的共享对象加载到全球进程空间,因此我们有python_eval符号。然后,创建一个带有函数的简单LLVM模块,使用ctypes分配一个带有Python源代码的字符串,并将指针传递给ExecutionEngine,该模块运行来自模块的JIT'd函数,然后将Python源代码传递给C函数调用Python C-API,然后退回到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__': 
    # If you want to evaluate the source of an existing function 
    # source_str = inspect.getsource(mypyfn) 

    # If you want to pass a source string 
    source_str = "print 'Hello from Python C-API inside of LLVM!'" 

    buf = ctypes.create_string_buffer(source_str) 
    fn, mod = build() 
    run(fn, mod, buf) 

您应该将以下输出:

=======================================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!