391 lines
12 KiB
Python
391 lines
12 KiB
Python
|
import sys
|
||
|
import ctypes
|
||
|
import threading
|
||
|
import importlib.resources as _impres
|
||
|
|
||
|
from llvmlite.binding.common import _decode_string, _is_shutting_down
|
||
|
from llvmlite.utils import get_library_name
|
||
|
|
||
|
|
||
|
def _make_opaque_ref(name):
|
||
|
newcls = type(name, (ctypes.Structure,), {})
|
||
|
return ctypes.POINTER(newcls)
|
||
|
|
||
|
|
||
|
LLVMContextRef = _make_opaque_ref("LLVMContext")
|
||
|
LLVMModuleRef = _make_opaque_ref("LLVMModule")
|
||
|
LLVMValueRef = _make_opaque_ref("LLVMValue")
|
||
|
LLVMTypeRef = _make_opaque_ref("LLVMType")
|
||
|
LLVMExecutionEngineRef = _make_opaque_ref("LLVMExecutionEngine")
|
||
|
LLVMPassManagerBuilderRef = _make_opaque_ref("LLVMPassManagerBuilder")
|
||
|
LLVMPassManagerRef = _make_opaque_ref("LLVMPassManager")
|
||
|
LLVMTargetDataRef = _make_opaque_ref("LLVMTargetData")
|
||
|
LLVMTargetLibraryInfoRef = _make_opaque_ref("LLVMTargetLibraryInfo")
|
||
|
LLVMTargetRef = _make_opaque_ref("LLVMTarget")
|
||
|
LLVMTargetMachineRef = _make_opaque_ref("LLVMTargetMachine")
|
||
|
LLVMMemoryBufferRef = _make_opaque_ref("LLVMMemoryBuffer")
|
||
|
LLVMAttributeListIterator = _make_opaque_ref("LLVMAttributeListIterator")
|
||
|
LLVMElementIterator = _make_opaque_ref("LLVMElementIterator")
|
||
|
LLVMAttributeSetIterator = _make_opaque_ref("LLVMAttributeSetIterator")
|
||
|
LLVMGlobalsIterator = _make_opaque_ref("LLVMGlobalsIterator")
|
||
|
LLVMFunctionsIterator = _make_opaque_ref("LLVMFunctionsIterator")
|
||
|
LLVMBlocksIterator = _make_opaque_ref("LLVMBlocksIterator")
|
||
|
LLVMArgumentsIterator = _make_opaque_ref("LLVMArgumentsIterator")
|
||
|
LLVMInstructionsIterator = _make_opaque_ref("LLVMInstructionsIterator")
|
||
|
LLVMOperandsIterator = _make_opaque_ref("LLVMOperandsIterator")
|
||
|
LLVMIncomingBlocksIterator = _make_opaque_ref("LLVMIncomingBlocksIterator")
|
||
|
LLVMTypesIterator = _make_opaque_ref("LLVMTypesIterator")
|
||
|
LLVMObjectCacheRef = _make_opaque_ref("LLVMObjectCache")
|
||
|
LLVMObjectFileRef = _make_opaque_ref("LLVMObjectFile")
|
||
|
LLVMSectionIteratorRef = _make_opaque_ref("LLVMSectionIterator")
|
||
|
LLVMOrcLLJITRef = _make_opaque_ref("LLVMOrcLLJITRef")
|
||
|
LLVMOrcDylibTrackerRef = _make_opaque_ref("LLVMOrcDylibTrackerRef")
|
||
|
|
||
|
|
||
|
class _LLVMLock:
|
||
|
"""A Lock to guarantee thread-safety for the LLVM C-API.
|
||
|
|
||
|
This class implements __enter__ and __exit__ for acquiring and releasing
|
||
|
the lock as a context manager.
|
||
|
|
||
|
Also, callbacks can be attached so that every time the lock is acquired
|
||
|
and released the corresponding callbacks will be invoked.
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
# The reentrant lock is needed for callbacks that re-enter
|
||
|
# the Python interpreter.
|
||
|
self._lock = threading.RLock()
|
||
|
self._cblist = []
|
||
|
|
||
|
def register(self, acq_fn, rel_fn):
|
||
|
"""Register callbacks that are invoked immediately after the lock is
|
||
|
acquired (``acq_fn()``) and immediately before the lock is released
|
||
|
(``rel_fn()``).
|
||
|
"""
|
||
|
self._cblist.append((acq_fn, rel_fn))
|
||
|
|
||
|
def unregister(self, acq_fn, rel_fn):
|
||
|
"""Remove the registered callbacks.
|
||
|
"""
|
||
|
self._cblist.remove((acq_fn, rel_fn))
|
||
|
|
||
|
def __enter__(self):
|
||
|
self._lock.acquire()
|
||
|
# Invoke all callbacks
|
||
|
for acq_fn, rel_fn in self._cblist:
|
||
|
acq_fn()
|
||
|
|
||
|
def __exit__(self, *exc_details):
|
||
|
# Invoke all callbacks
|
||
|
for acq_fn, rel_fn in self._cblist:
|
||
|
rel_fn()
|
||
|
self._lock.release()
|
||
|
|
||
|
|
||
|
class _suppress_cleanup_errors:
|
||
|
def __init__(self, context):
|
||
|
self._context = context
|
||
|
|
||
|
def __enter__(self):
|
||
|
return self._context.__enter__()
|
||
|
|
||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||
|
try:
|
||
|
return self._context.__exit__(exc_type, exc_value, traceback)
|
||
|
except PermissionError:
|
||
|
pass # Resource dylibs can't be deleted on Windows.
|
||
|
|
||
|
|
||
|
class _lib_wrapper(object):
|
||
|
"""Wrap libllvmlite with a lock such that only one thread may access it at
|
||
|
a time.
|
||
|
|
||
|
This class duck-types a CDLL.
|
||
|
"""
|
||
|
__slots__ = ['_lib_handle', '_fntab', '_lock']
|
||
|
|
||
|
def __init__(self):
|
||
|
self._lib_handle = None
|
||
|
self._fntab = {}
|
||
|
self._lock = _LLVMLock()
|
||
|
|
||
|
def _load_lib(self):
|
||
|
try:
|
||
|
with _suppress_cleanup_errors(_importlib_resources_path(
|
||
|
__name__.rpartition(".")[0],
|
||
|
get_library_name())) as lib_path:
|
||
|
self._lib_handle = ctypes.CDLL(str(lib_path))
|
||
|
# Check that we can look up expected symbols.
|
||
|
_ = self._lib_handle.LLVMPY_GetVersionInfo()
|
||
|
except (OSError, AttributeError) as e:
|
||
|
# OSError may be raised if the file cannot be opened, or is not
|
||
|
# a shared library.
|
||
|
# AttributeError is raised if LLVMPY_GetVersionInfo does not
|
||
|
# exist.
|
||
|
raise OSError("Could not find/load shared object file") from e
|
||
|
|
||
|
@property
|
||
|
def _lib(self):
|
||
|
# Not threadsafe.
|
||
|
if not self._lib_handle:
|
||
|
self._load_lib()
|
||
|
return self._lib_handle
|
||
|
|
||
|
def __getattr__(self, name):
|
||
|
try:
|
||
|
return self._fntab[name]
|
||
|
except KeyError:
|
||
|
# Lazily wraps new functions as they are requested
|
||
|
cfn = getattr(self._lib, name)
|
||
|
wrapped = _lib_fn_wrapper(self._lock, cfn)
|
||
|
self._fntab[name] = wrapped
|
||
|
return wrapped
|
||
|
|
||
|
@property
|
||
|
def _name(self):
|
||
|
"""The name of the library passed in the CDLL constructor.
|
||
|
|
||
|
For duck-typing a ctypes.CDLL
|
||
|
"""
|
||
|
return self._lib._name
|
||
|
|
||
|
@property
|
||
|
def _handle(self):
|
||
|
"""The system handle used to access the library.
|
||
|
|
||
|
For duck-typing a ctypes.CDLL
|
||
|
"""
|
||
|
return self._lib._handle
|
||
|
|
||
|
|
||
|
class _lib_fn_wrapper(object):
|
||
|
"""Wraps and duck-types a ctypes.CFUNCTYPE to provide
|
||
|
automatic locking when the wrapped function is called.
|
||
|
|
||
|
TODO: we can add methods to mark the function as threadsafe
|
||
|
and remove the locking-step on call when marked.
|
||
|
"""
|
||
|
__slots__ = ['_lock', '_cfn']
|
||
|
|
||
|
def __init__(self, lock, cfn):
|
||
|
self._lock = lock
|
||
|
self._cfn = cfn
|
||
|
|
||
|
@property
|
||
|
def argtypes(self):
|
||
|
return self._cfn.argtypes
|
||
|
|
||
|
@argtypes.setter
|
||
|
def argtypes(self, argtypes):
|
||
|
self._cfn.argtypes = argtypes
|
||
|
|
||
|
@property
|
||
|
def restype(self):
|
||
|
return self._cfn.restype
|
||
|
|
||
|
@restype.setter
|
||
|
def restype(self, restype):
|
||
|
self._cfn.restype = restype
|
||
|
|
||
|
def __call__(self, *args, **kwargs):
|
||
|
with self._lock:
|
||
|
return self._cfn(*args, **kwargs)
|
||
|
|
||
|
|
||
|
def _importlib_resources_path_repl(package, resource):
|
||
|
"""Replacement implementation of `import.resources.path` to avoid
|
||
|
deprecation warning following code at importlib_resources/_legacy.py
|
||
|
as suggested by https://importlib-resources.readthedocs.io/en/latest/using.html#migrating-from-legacy
|
||
|
|
||
|
Notes on differences from importlib.resources implementation:
|
||
|
|
||
|
The `_common.normalize_path(resource)` call is skipped because it is an
|
||
|
internal API and it is unnecessary for the use here. What it does is
|
||
|
ensuring `resource` is a str and that it does not contain path separators.
|
||
|
""" # noqa E501
|
||
|
return _impres.as_file(_impres.files(package) / resource)
|
||
|
|
||
|
|
||
|
_importlib_resources_path = (_importlib_resources_path_repl
|
||
|
if sys.version_info[:2] >= (3, 9)
|
||
|
else _impres.path)
|
||
|
|
||
|
|
||
|
lib = _lib_wrapper()
|
||
|
|
||
|
|
||
|
def register_lock_callback(acq_fn, rel_fn):
|
||
|
"""Register callback functions for lock acquire and release.
|
||
|
*acq_fn* and *rel_fn* are callables that take no arguments.
|
||
|
"""
|
||
|
lib._lock.register(acq_fn, rel_fn)
|
||
|
|
||
|
|
||
|
def unregister_lock_callback(acq_fn, rel_fn):
|
||
|
"""Remove the registered callback functions for lock acquire and release.
|
||
|
The arguments are the same as used in `register_lock_callback()`.
|
||
|
"""
|
||
|
lib._lock.unregister(acq_fn, rel_fn)
|
||
|
|
||
|
|
||
|
class _DeadPointer(object):
|
||
|
"""
|
||
|
Dummy class to make error messages more helpful.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class OutputString(object):
|
||
|
"""
|
||
|
Object for managing the char* output of LLVM APIs.
|
||
|
"""
|
||
|
_as_parameter_ = _DeadPointer()
|
||
|
|
||
|
@classmethod
|
||
|
def from_return(cls, ptr):
|
||
|
"""Constructing from a pointer returned from the C-API.
|
||
|
The pointer must be allocated with LLVMPY_CreateString.
|
||
|
|
||
|
Note
|
||
|
----
|
||
|
Because ctypes auto-converts *restype* of *c_char_p* into a python
|
||
|
string, we must use *c_void_p* to obtain the raw pointer.
|
||
|
"""
|
||
|
return cls(init=ctypes.cast(ptr, ctypes.c_char_p))
|
||
|
|
||
|
def __init__(self, owned=True, init=None):
|
||
|
self._ptr = init if init is not None else ctypes.c_char_p(None)
|
||
|
self._as_parameter_ = ctypes.byref(self._ptr)
|
||
|
self._owned = owned
|
||
|
|
||
|
def close(self):
|
||
|
if self._ptr is not None:
|
||
|
if self._owned:
|
||
|
lib.LLVMPY_DisposeString(self._ptr)
|
||
|
self._ptr = None
|
||
|
del self._as_parameter_
|
||
|
|
||
|
def __enter__(self):
|
||
|
return self
|
||
|
|
||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||
|
self.close()
|
||
|
|
||
|
def __del__(self, _is_shutting_down=_is_shutting_down):
|
||
|
# Avoid errors trying to rely on globals and modules at interpreter
|
||
|
# shutdown.
|
||
|
if not _is_shutting_down():
|
||
|
if self.close is not None:
|
||
|
self.close()
|
||
|
|
||
|
def __str__(self):
|
||
|
if self._ptr is None:
|
||
|
return "<dead OutputString>"
|
||
|
s = self._ptr.value
|
||
|
assert s is not None
|
||
|
return _decode_string(s)
|
||
|
|
||
|
def __bool__(self):
|
||
|
return bool(self._ptr)
|
||
|
|
||
|
__nonzero__ = __bool__
|
||
|
|
||
|
@property
|
||
|
def bytes(self):
|
||
|
"""Get the raw bytes of content of the char pointer.
|
||
|
"""
|
||
|
return self._ptr.value
|
||
|
|
||
|
|
||
|
def ret_string(ptr):
|
||
|
"""To wrap string return-value from C-API.
|
||
|
"""
|
||
|
if ptr is not None:
|
||
|
return str(OutputString.from_return(ptr))
|
||
|
|
||
|
|
||
|
def ret_bytes(ptr):
|
||
|
"""To wrap bytes return-value from C-API.
|
||
|
"""
|
||
|
if ptr is not None:
|
||
|
return OutputString.from_return(ptr).bytes
|
||
|
|
||
|
|
||
|
class ObjectRef(object):
|
||
|
"""
|
||
|
A wrapper around a ctypes pointer to a LLVM object ("resource").
|
||
|
"""
|
||
|
_closed = False
|
||
|
_as_parameter_ = _DeadPointer()
|
||
|
# Whether this object pointer is owned by another one.
|
||
|
_owned = False
|
||
|
|
||
|
def __init__(self, ptr):
|
||
|
if ptr is None:
|
||
|
raise ValueError("NULL pointer")
|
||
|
self._ptr = ptr
|
||
|
self._as_parameter_ = ptr
|
||
|
self._capi = lib
|
||
|
|
||
|
def close(self):
|
||
|
"""
|
||
|
Close this object and do any required clean-up actions.
|
||
|
"""
|
||
|
try:
|
||
|
if not self._closed and not self._owned:
|
||
|
self._dispose()
|
||
|
finally:
|
||
|
self.detach()
|
||
|
|
||
|
def detach(self):
|
||
|
"""
|
||
|
Detach the underlying LLVM resource without disposing of it.
|
||
|
"""
|
||
|
if not self._closed:
|
||
|
del self._as_parameter_
|
||
|
self._closed = True
|
||
|
self._ptr = None
|
||
|
|
||
|
def _dispose(self):
|
||
|
"""
|
||
|
Dispose of the underlying LLVM resource. Should be overriden
|
||
|
by subclasses. Automatically called by close(), __del__() and
|
||
|
__exit__() (unless the resource has been detached).
|
||
|
"""
|
||
|
|
||
|
@property
|
||
|
def closed(self):
|
||
|
"""
|
||
|
Whether this object has been closed. A closed object can't
|
||
|
be used anymore.
|
||
|
"""
|
||
|
return self._closed
|
||
|
|
||
|
def __enter__(self):
|
||
|
assert hasattr(self, "close")
|
||
|
if self._closed:
|
||
|
raise RuntimeError("%s instance already closed" % (self.__class__,))
|
||
|
return self
|
||
|
|
||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||
|
self.close()
|
||
|
|
||
|
def __del__(self, _is_shutting_down=_is_shutting_down):
|
||
|
if not _is_shutting_down():
|
||
|
if self.close is not None:
|
||
|
self.close()
|
||
|
|
||
|
def __bool__(self):
|
||
|
return bool(self._ptr)
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
if not hasattr(other, "_ptr"):
|
||
|
return False
|
||
|
return ctypes.addressof(self._ptr[0]) == \
|
||
|
ctypes.addressof(other._ptr[0])
|
||
|
|
||
|
__nonzero__ = __bool__
|
||
|
|
||
|
# XXX useful?
|
||
|
def __hash__(self):
|
||
|
return hash(ctypes.cast(self._ptr, ctypes.c_void_p).value)
|