【问题标题】:Call Python code from LLVM JIT从 LLVM JIT 调用 Python 代码
【发布时间】:2013-03-02 00:08:20
【问题描述】:

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

首先:这是否可能,或者虚拟机一旦启动就“不可变”?

如果是,我目前看到 3 种可能的解决方案(我愿意接受其他建议)

  • “脱离”VM 并可以直接调用主进程的 Python 函数(可能通过将其注册为 LLVM 函数,以某种方式重定向到主进程)。我对此一无所知,无论如何我不确定这是否是个好主意(安全等)。
  • 将运行时(在运行时静态或动态)编译成 LLVM-Assembly/-IR。这要求 IR 代码能够修改它在其中运行的 VM
  • 将运行时(静态)编译到库中并将其直接加载到 VM 中。同样,它必须能够向运行它的 VM 添加功能(等)。

【问题讨论】:

    标签: python llvm llvm-py


    【解决方案1】:

    就像 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 函数,然后将 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!
    

    【讨论】:

      【解决方案2】:

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

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

      “VM”可能没有您想象的那么神奇 :-) 最后,它只是在运行时发出到缓冲区并从那里执行的机器代码。如果这段代码可以访问它正在运行的进程中的其他符号,它就可以执行该进程中任何其他代码可以执行的所有操作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-02-17
        • 1970-01-01
        • 2015-10-06
        • 2019-06-21
        • 2015-06-15
        • 2021-06-28
        • 1970-01-01
        相关资源
        最近更新 更多