2013-03-02 14 views
6

Piszę lexer/parser/kompilator języka w python, który powinien działać w LVVM JIT-VM (przy użyciu llvm-py) później. Pierwsze dwa kroki są teraz dość proste, ale (nawet jeśli nie uruchomiłem jeszcze zadania kompilacji), widzę problem, gdy mój kod chce wywoływać Python-Code (ogólnie) lub interakcję z lexerem Python/odpowiednio parser/kompilator (w specjalnym). Moim głównym zmartwieniem jest to, że kod powinien móc dynamicznie ładować dodatkowy kod do maszyny wirtualnej w czasie wykonywania, a zatem musi wywoływać cały łańcuch leksyk/parser/kompilator w Pythonie z poziomu maszyny wirtualnej.Wywołanie kodu Python z LLVM JIT

Po pierwsze: czy to jest nawet możliwe, czy też VM "unmutable" po uruchomieniu?

Jeśli to ja obecnie zobaczyć 3 możliwe rozwiązania (jestem otwarty na inne propozycje)

  • „wyrwania się” z maszyny wirtualnej i pozwalają zadzwonić Python funkcje głównego procesu bezpośrednio (być może przez rejestrowanie go jako funkcji LLVM, która w jakiś sposób przekierowuje do głównego procesu). Nie znalazłem nic o tym i tak czy inaczej nie jestem pewien, czy to jest dobry pomysł (bezpieczeństwo i takie).
  • Skompiluj środowisko wykonawcze (statycznie lub dynamicznie w czasie wykonywania) do LLVM-Assembly/-IR. Wymaga to, aby kod IR był w stanie zmodyfikować maszynę wirtualną, którą uruchamia w
  • Skompilować środowisko wykonawcze (statycznie) do biblioteki i załadować bezpośrednio do maszyny wirtualnej. Ponownie musi mieć możliwość dodawania funkcji (itp.) Do maszyny wirtualnej, w której działa.

Odpowiedz

2

Możesz wywołać zewnętrzne funkcje C z kodu JIT LLVM. Co jeszcze potrzebujesz?

Te zewnętrzne funkcje zostaną znalezione w procesie wykonywania, co oznacza, że ​​jeśli połączysz Python ze swoją maszyną wirtualną, możesz wywołać funkcje API C Pythona.

"VM" jest prawdopodobnie mniej magiczny, niż myślisz :-) W końcu jest to kod maszynowy, który jest wysyłany w środowisku wykonawczym do bufora i wykonywany stamtąd. Do tego stopnia, że ​​ten kod ma dostęp do innych symboli w procesie, w którym działa, może zrobić wszystko, co może zrobić każdy inny kod w tym procesie.

6

Tak jak powiedział Eli, nie powstrzymuje cię przed wołaniem do C-API Pythona. Kiedy wywołujesz zewnętrzną funkcję z wnętrza JL LLVM, efektywnie używa tylko dlopen() w przestrzeni procesowej, więc jeśli korzystasz z wewnątrz llvmpy, masz już dostępne wszystkie symbole interpretera Pythona, możesz nawet wchodzić w interakcję z aktywnym interpreterem, który wywołał ExecutionEngine lub w razie potrzeby możesz obrócić nowego interpretera języka Python.

Aby zacząć, utwórz nowy plik C za pomocą naszego oceniającego.

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

Oto mały Makefile skompilować że:

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 

Potem zaczniemy ze zwykłymi boilerplate dla LLVM ale używać ctypes załadować obiekt udostępniony naszego cbits.so udostępnionej biblioteki w globalnej przestrzeni procesowej tak że mamy symbol python_eval. Następnie po prostu stwórz prosty moduł LLVM z funkcją, przydziel łańcuch z jakimś źródłem Pythona za pomocą ctypów i przekaż wskaźnik do ExecutionEngine, który uruchamia funkcję JIT z naszego modułu, który z kolei przekazuje źródło Pythona do funkcji C, wywołuje C-API Pythona, a następnie powraca do JIT LLVM.

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) 

Powinieneś następujące wyjścia:

=======================================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! 
Powiązane problemy