Videre
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
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]
|
||||
|
||||
@property
|
||||
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})
|
||||
|
||||
@property
|
||||
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
|
||||
Reference in New Issue
Block a user