2013-03-02 31 views
6

Scrivo un lexer/parser/compilatore in python, che dovrebbe essere eseguito in LITVM JIT-VM (utilizzando llvm-py) in seguito. I primi due passaggi sono abbastanza semplici per ora, ma vedo (anche se non ho ancora avviato il compito di compilazione) un problema, quando il mio codice vuole chiamare Python-Code (in generale), o interagire con il lexer di Python/parser/compiler (in special) rispettivamente. La mia preoccupazione principale è che il codice dovrebbe essere in grado di caricare dinamicamente codice aggiuntivo nella VM in fase di runtime e quindi deve attivare l'intero lexer/parser/compilatore-catena in Python all'interno della VM.Chiama il codice Python da LLVM JIT

Prima di tutto: è addirittura possibile, oppure la VM è "immutabile" una volta avviata?

Se è Attualmente vedi 3 possibili soluzioni (io sono aperto per altri suggerimenti)

  • "uscire" del VM e permettono di chiamare funzioni Python del processo principale direttamente (forse da registrandolo come una funzione LLVM, che reindirizza in qualche modo al processo principale). Non ho trovato nulla su questo e comunque non sono sicuro, se questa è una buona idea (sicurezza e così).
  • Compilare il runtime (staticamente o dinamicamente in fase di esecuzione) in LLVM-Assembly/-IR. Ciò richiede che il codice IR sia in grado di modificare la VM in esecuzione in
  • Compilare il runtime (staticamente) in una libreria e caricarlo direttamente nella VM. Anche in questo caso deve essere possibile aggiungere funzioni (ecc.) Alla VM in cui viene eseguito.

risposta

2

È possibile chiamare le funzioni C esterne dal codice JIT-LLVM. Cos'altro ti serve?

Queste funzioni esterne verranno trovate nel processo di esecuzione, nel senso che se si collega Python alla VM è possibile chiamare le funzioni API C di Python.

La "VM" è probabilmente meno magica di quanto si pensi :-) Alla fine, è solo il codice macchina che viene emesso in fase di esecuzione in un buffer ed eseguito da lì. Nella misura in cui questo codice ha accesso ad altri simboli nel processo in cui è in esecuzione, può fare tutto ciò che può fare qualsiasi altro codice in quel processo.

6

Come ha detto Eli, non ti impedisce di chiamare la C-API Python. Quando chiami una funzione esterna dall'interno del JIT di LLVM utilizza in modo efficace lo dlopen() sullo spazio del processo, quindi se stai eseguendo da dentro a llvmpy hai già tutti i simboli dell'interprete Python accessibili, puoi anche interagire con l'interprete attivo che invocato ExecutionEngine oppure puoi girare un nuovo interprete Python se necessario.

Per iniziare, creare un nuovo file C con il nostro valutatore.

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

Ecco un po 'Makefile per compilare che:

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 

Poi si parte con la solita boilerplate per LLVM ma l'uso ctypes per caricare l'oggetto condiviso della nostra cbits.so libreria condivisa nello spazio processo globale così che abbiamo il simbolo python_eval. Quindi basta creare un semplice modulo LLVM con una funzione, allocare una stringa con qualche sorgente Python con ctypes e passare il puntatore all'ExecutionEngine che esegue la funzione JIT dal nostro modulo, che a sua volta passa il sorgente Python alla funzione C che richiama la Python C-API e quindi restituisce al 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) 

Si dovrebbe il seguente output:

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