Spaces:
Running
Running
| import ctypes | |
| from ctypes import POINTER, c_bool, c_char_p, c_uint8, c_uint64, c_size_t | |
| from llvmlite.binding import ffi, targets | |
| class _LinkElement(ctypes.Structure): | |
| _fields_ = [("element_kind", c_uint8), | |
| ("value", c_char_p), | |
| ("value_len", c_size_t)] | |
| class _SymbolAddress(ctypes.Structure): | |
| _fields_ = [("name", c_char_p), ("address", c_uint64)] | |
| class JITLibraryBuilder: | |
| """ | |
| Create a library for linking by OrcJIT | |
| OrcJIT operates like a linker: a number of compilation units and | |
| dependencies are collected together and linked into a single dynamic library | |
| that can export functions to other libraries or to be consumed directly as | |
| entry points into JITted code. The native OrcJIT has a lot of memory | |
| management complications so this API is designed to work well with Python's | |
| garbage collection. | |
| The creation of a new library is a bit like a linker command line where | |
| compilation units, mostly as LLVM IR, and previously constructed libraries | |
| are linked together, then loaded into memory, and the addresses of exported | |
| symbols are extracted. Any static initializers are run and the exported | |
| addresses and a resource tracker is produced. As long as the resource | |
| tracker is referenced somewhere in Python, the exported addresses will be | |
| valid. Once the resource tracker is garbage collected, the static | |
| destructors will run and library will be unloaded from memory. | |
| """ | |
| def __init__(self): | |
| self.__entries = [] | |
| self.__exports = set() | |
| self.__imports = {} | |
| def add_ir(self, llvmir): | |
| """ | |
| Adds a compilation unit to the library using LLVM IR as the input | |
| format. | |
| This takes a string or an object that can be converted to a string, | |
| including IRBuilder, that contains LLVM IR. | |
| """ | |
| self.__entries.append((0, str(llvmir).encode('utf-8'))) | |
| return self | |
| def add_native_assembly(self, asm): | |
| """ | |
| Adds a compilation unit to the library using native assembly as the | |
| input format. | |
| This takes a string or an object that can be converted to a string that | |
| contains native assembly, which will be | |
| parsed by LLVM. | |
| """ | |
| self.__entries.append((1, str(asm).encode('utf-8'))) | |
| return self | |
| def add_object_img(self, data): | |
| """ | |
| Adds a compilation unit to the library using pre-compiled object code. | |
| This takes the bytes of the contents of an object artifact which will be | |
| loaded by LLVM. | |
| """ | |
| self.__entries.append((2, bytes(data))) | |
| return self | |
| def add_object_file(self, file_path): | |
| """ | |
| Adds a compilation unit to the library using pre-compiled object file. | |
| This takes a string or path-like object that references an object file | |
| which will be loaded by LLVM. | |
| """ | |
| with open(file_path, "rb") as f: | |
| self.__entries.append((2, f.read())) | |
| return self | |
| def add_jit_library(self, name): | |
| """ | |
| Adds an existing JIT library as prerequisite. | |
| The name of the library must match the one provided in a previous link | |
| command. | |
| """ | |
| self.__entries.append((3, str(name).encode('utf-8'))) | |
| return self | |
| def add_current_process(self): | |
| """ | |
| Allows the JITted library to access symbols in the current binary. | |
| That is, it allows exporting the current binary's symbols, including | |
| loaded libraries, as imports to the JITted | |
| library. | |
| """ | |
| self.__entries.append((3, b'')) | |
| return self | |
| def import_symbol(self, name, address): | |
| """ | |
| Register the *address* of global symbol *name*. This will make | |
| it usable (e.g. callable) from LLVM-compiled functions. | |
| """ | |
| self.__imports[str(name)] = c_uint64(address) | |
| return self | |
| def export_symbol(self, name): | |
| """ | |
| During linking, extract the address of a symbol that was defined in one | |
| of the compilation units. | |
| This allows getting symbols, functions or global variables, out of the | |
| JIT linked library. The addresses will be | |
| available when the link method is called. | |
| """ | |
| self.__exports.add(str(name)) | |
| return self | |
| def link(self, lljit, library_name): | |
| """ | |
| Link all the current compilation units into a JITted library and extract | |
| the address of exported symbols. | |
| An instance of the OrcJIT instance must be provided and this will be the | |
| scope that is used to find other JITted libraries that are dependencies | |
| and also be the place where this library will be defined. | |
| After linking, the method will return a resource tracker that keeps the | |
| library alive. This tracker also knows the addresses of any exported | |
| symbols that were requested. | |
| The addresses will be valid as long as the resource tracker is | |
| referenced. | |
| When the resource tracker is destroyed, the library will be cleaned up, | |
| however, the name of the library cannot be reused. | |
| """ | |
| assert not lljit.closed, "Cannot add to closed JIT" | |
| encoded_library_name = str(library_name).encode('utf-8') | |
| assert len(encoded_library_name) > 0, "Library cannot be empty" | |
| elements = (_LinkElement * len(self.__entries))() | |
| for idx, (kind, value) in enumerate(self.__entries): | |
| elements[idx].element_kind = c_uint8(kind) | |
| elements[idx].value = c_char_p(value) | |
| elements[idx].value_len = c_size_t(len(value)) | |
| exports = (_SymbolAddress * len(self.__exports))() | |
| for idx, name in enumerate(self.__exports): | |
| exports[idx].name = name.encode('utf-8') | |
| imports = (_SymbolAddress * len(self.__imports))() | |
| for idx, (name, addr) in enumerate(self.__imports.items()): | |
| imports[idx].name = name.encode('utf-8') | |
| imports[idx].address = addr | |
| with ffi.OutputString() as outerr: | |
| tracker = lljit._capi.LLVMPY_LLJIT_Link( | |
| lljit._ptr, | |
| encoded_library_name, | |
| elements, | |
| len(self.__entries), | |
| imports, | |
| len(self.__imports), | |
| exports, | |
| len(self.__exports), | |
| outerr) | |
| if not tracker: | |
| raise RuntimeError(str(outerr)) | |
| return ResourceTracker(tracker, | |
| library_name, | |
| {name: exports[idx].address | |
| for idx, name in enumerate(self.__exports)}) | |
| class ResourceTracker(ffi.ObjectRef): | |
| """ | |
| A resource tracker is created for each loaded JIT library and keeps the | |
| module alive. | |
| OrcJIT supports unloading libraries that are no longer used. This resource | |
| tracker should be stored in any object that reference functions or constants | |
| for a JITted library. When all references to the resource tracker are | |
| dropped, this will trigger LLVM to unload the library and destroy any | |
| functions. | |
| Failure to keep resource trackers while calling a function or accessing a | |
| symbol can result in crashes or memory corruption. | |
| LLVM internally tracks references between different libraries, so only | |
| "leaf" libraries need to be tracked. | |
| """ | |
| def __init__(self, ptr, name, addresses): | |
| self.__addresses = addresses | |
| self.__name = name | |
| ffi.ObjectRef.__init__(self, ptr) | |
| def __getitem__(self, item): | |
| """ | |
| Get the address of an exported symbol as an integer | |
| """ | |
| return self.__addresses[item] | |
| def name(self): | |
| return self.__name | |
| def _dispose(self): | |
| with ffi.OutputString() as outerr: | |
| if self._capi.LLVMPY_LLJIT_Dylib_Tracker_Dispose(self, outerr): | |
| raise RuntimeError(str(outerr)) | |
| class LLJIT(ffi.ObjectRef): | |
| """ | |
| A OrcJIT-based LLVM JIT engine that can compile and run LLVM IR as a | |
| collection of JITted dynamic libraries | |
| The C++ OrcJIT API has a lot of memory ownership patterns that do not work | |
| with Python. This API attempts to provide ones that are safe at the expense | |
| of some features. Each LLJIT instance is a collection of JIT-compiled | |
| libraries. In the C++ API, there is a "main" library; this API does not | |
| provide access to the main library. Use the JITLibraryBuilder to create a | |
| new named library instead. | |
| """ | |
| def __init__(self, ptr): | |
| self._td = None | |
| ffi.ObjectRef.__init__(self, ptr) | |
| def lookup(self, dylib, fn): | |
| """ | |
| Find a function in this dynamic library and construct a new tracking | |
| object for it | |
| If the library or function do not exist, an exception will occur. | |
| Parameters | |
| ---------- | |
| dylib : str or None | |
| the name of the library containing the symbol | |
| fn : str | |
| the name of the function to get | |
| """ | |
| assert not self.closed, "Cannot lookup in closed JIT" | |
| address = ctypes.c_uint64() | |
| with ffi.OutputString() as outerr: | |
| tracker = ffi.lib.LLVMPY_LLJITLookup(self, | |
| dylib.encode("utf-8"), | |
| fn.encode("utf-8"), | |
| ctypes.byref(address), | |
| outerr) | |
| if not tracker: | |
| raise RuntimeError(str(outerr)) | |
| return ResourceTracker(tracker, dylib, {fn: address.value}) | |
| def target_data(self): | |
| """ | |
| The TargetData for this LLJIT instance. | |
| """ | |
| if self._td is not None: | |
| return self._td | |
| ptr = ffi.lib.LLVMPY_LLJITGetDataLayout(self) | |
| self._td = targets.TargetData(ptr) | |
| self._td._owned = True | |
| return self._td | |
| def _dispose(self): | |
| if self._td is not None: | |
| self._td.detach() | |
| self._capi.LLVMPY_LLJITDispose(self) | |
| def create_lljit_compiler(target_machine=None, *, | |
| use_jit_link=False, | |
| suppress_errors=False): | |
| """ | |
| Create an LLJIT instance | |
| """ | |
| with ffi.OutputString() as outerr: | |
| lljit = ffi.lib.LLVMPY_CreateLLJITCompiler(target_machine, | |
| suppress_errors, | |
| use_jit_link, | |
| outerr) | |
| if not lljit: | |
| raise RuntimeError(str(outerr)) | |
| return LLJIT(lljit) | |
| ffi.lib.LLVMPY_LLJITLookup.argtypes = [ | |
| ffi.LLVMOrcLLJITRef, | |
| c_char_p, | |
| c_char_p, | |
| POINTER(c_uint64), | |
| POINTER(c_char_p), | |
| ] | |
| ffi.lib.LLVMPY_LLJITLookup.restype = ffi.LLVMOrcDylibTrackerRef | |
| ffi.lib.LLVMPY_LLJITGetDataLayout.argtypes = [ | |
| ffi.LLVMOrcLLJITRef, | |
| ] | |
| ffi.lib.LLVMPY_LLJITGetDataLayout.restype = ffi.LLVMTargetDataRef | |
| ffi.lib.LLVMPY_CreateLLJITCompiler.argtypes = [ | |
| ffi.LLVMTargetMachineRef, | |
| c_bool, | |
| c_bool, | |
| POINTER(c_char_p), | |
| ] | |
| ffi.lib.LLVMPY_CreateLLJITCompiler.restype = ffi.LLVMOrcLLJITRef | |
| ffi.lib.LLVMPY_LLJITDispose.argtypes = [ | |
| ffi.LLVMOrcLLJITRef, | |
| ] | |
| ffi.lib.LLVMPY_LLJIT_Link.argtypes = [ | |
| ffi.LLVMOrcLLJITRef, | |
| c_char_p, | |
| POINTER(_LinkElement), | |
| c_size_t, | |
| POINTER(_SymbolAddress), | |
| c_size_t, | |
| POINTER(_SymbolAddress), | |
| c_size_t, | |
| POINTER(c_char_p) | |
| ] | |
| ffi.lib.LLVMPY_LLJIT_Link.restype = ffi.LLVMOrcDylibTrackerRef | |
| ffi.lib.LLVMPY_LLJIT_Dylib_Tracker_Dispose.argtypes = [ | |
| ffi.LLVMOrcDylibTrackerRef, | |
| POINTER(c_char_p) | |
| ] | |
| ffi.lib.LLVMPY_LLJIT_Dylib_Tracker_Dispose.restype = c_bool | |