This commit is contained in:
2026-04-10 15:06:59 +02:00
parent 3031b7153b
commit e5a4711004
7806 changed files with 1918528 additions and 335 deletions

View File

@@ -0,0 +1,11 @@
from ._version import get_versions
__version__ = get_versions()['version']
del get_versions
# We default to IR layer typed pointers being enabled, since they're needed in
# the most common usage scenarios with later LLVMs.
def _ir_layer_typed_pointers_enabled():
import os
return os.environ.get('LLVMLITE_ENABLE_IR_LAYER_TYPED_POINTERS', '1') == '1'
ir_layer_typed_pointers_enabled = _ir_layer_typed_pointers_enabled()
del _ir_layer_typed_pointers_enabled

View File

@@ -0,0 +1,11 @@
# This file was generated by 'versioneer.py' (0.14) from
# revision-control system data, or from the parent directory name of an
# unpacked source archive. Distribution tarballs contain a pre-generated copy
# of this file.
version_version = '0.47.0'
version_full = 'a1b33d2c2dac939a6263c544afd46eb66189b740'
def get_versions(default={}, verbose=False):
return {'version': version_version, 'full': version_full}

View File

@@ -0,0 +1,18 @@
"""
Things that rely on the LLVM library
"""
from .dylib import *
from .executionengine import *
from .initfini import *
from .linker import *
from .module import *
from .options import *
from .newpassmanagers import *
from .targets import *
from .value import *
from .typeref import *
from .analysis import *
from .object_file import *
from .context import *
from .orcjit import *
from .config import *

View File

@@ -0,0 +1,69 @@
"""
A collection of analysis utilities
"""
from ctypes import POINTER, c_char_p, c_int
from llvmlite.binding import ffi
from llvmlite.binding.module import parse_assembly
def get_function_cfg(func, show_inst=True):
"""Return a string of the control-flow graph of the function in DOT
format. If the input `func` is not a materialized function, the module
containing the function is parsed to create an actual LLVM module.
The `show_inst` flag controls whether the instructions of each block
are printed.
"""
assert func is not None
from llvmlite import ir
if isinstance(func, ir.Function):
mod = parse_assembly(str(func.module))
func = mod.get_function(func.name)
# Assume func is a materialized function
with ffi.OutputString() as dotstr:
ffi.lib.LLVMPY_WriteCFG(func, dotstr, show_inst)
return str(dotstr)
def view_dot_graph(graph, filename=None, view=False):
"""
View the given DOT source. If view is True, the image is rendered
and viewed by the default application in the system. The file path of
the output is returned. If view is False, a graphviz.Source object is
returned. If view is False and the environment is in a IPython session,
an IPython image object is returned and can be displayed inline in the
notebook.
This function requires the graphviz package.
Args
----
- graph [str]: a DOT source code
- filename [str]: optional. if given and view is True, this specifies
the file path for the rendered output to write to.
- view [bool]: if True, opens the rendered output file.
"""
# Optionally depends on graphviz package
import graphviz as gv
src = gv.Source(graph)
if view:
# Returns the output file path
return src.render(filename, view=view)
else:
# Attempts to show the graph in IPython notebook
try:
__IPYTHON__
except NameError:
return src
else:
import IPython.display as display
format = 'svg'
return display.SVG(data=src.pipe(format))
# Ctypes binding
ffi.lib.LLVMPY_WriteCFG.argtypes = [ffi.LLVMValueRef, POINTER(c_char_p), c_int]

View File

@@ -0,0 +1,34 @@
import atexit
def _encode_string(s):
encoded = s.encode('utf-8')
return encoded
def _decode_string(b):
return b.decode('utf-8')
_encode_string.__doc__ = """Encode a string for use by LLVM."""
_decode_string.__doc__ = """Decode a LLVM character (byte)string."""
_shutting_down = [False]
def _at_shutdown():
_shutting_down[0] = True
atexit.register(_at_shutdown)
def _is_shutting_down(_shutting_down=_shutting_down):
"""
Whether the interpreter is currently shutting down.
For use in finalizers, __del__ methods, and similar; it is advised
to early bind this function rather than look it up when calling it,
since at shutdown module globals may be cleared.
"""
return _shutting_down[0]

View File

@@ -0,0 +1,143 @@
import os
import warnings
from functools import cache
from ctypes import c_int, c_char_p
from llvmlite.binding import ffi
# these are here as they cannot be lazy bound as the module globals make calls
# to the LLVMPY API functions
ffi.lib.LLVMPY_HasSVMLSupport.argtypes = ()
ffi.lib.LLVMPY_HasSVMLSupport.restype = c_int
ffi.lib.LLVMPY_IsStaticLibstdcxxLinkageBuild.argtypes = ()
ffi.lib.LLVMPY_IsStaticLibstdcxxLinkageBuild.restype = c_int
ffi.lib.LLVMPY_IsDynamicLLVMLinkageBuild.argtypes = ()
ffi.lib.LLVMPY_IsDynamicLLVMLinkageBuild.restype = c_int
ffi.lib.LLVMPY_PackageFormat.argtypes = ()
ffi.lib.LLVMPY_PackageFormat.restype = c_char_p
ffi.lib.LLVMPY_LlvmAssertionsState.argtypes = ()
ffi.lib.LLVMPY_LlvmAssertionsState.restype = c_char_p
def _has_svml():
"""
Returns True if SVML was enabled at FFI support compile time.
"""
if ffi.lib.LLVMPY_HasSVMLSupport() == 0:
return False
else:
return True
has_svml = _has_svml()
def _build_llvm_linkage_type():
"""
Returns "static" if the FFI support is statically linked against LLVM,
returns "dynamic" otherwise.
"""
if ffi.lib.LLVMPY_IsDynamicLLVMLinkageBuild() == 0:
return "static"
else:
return "dynamic"
build_llvm_linkage_type = _build_llvm_linkage_type()
def _build_libstdcxx_linkage_type():
"""
Returns "static" if the FFI support is statically linked against libstdc++,
returns "dynamic" otherwise.
"""
if ffi.lib.LLVMPY_IsStaticLibstdcxxLinkageBuild() == 1:
return "static"
else:
return "dynamic"
build_libstdcxx_linkage_type = _build_libstdcxx_linkage_type()
def _package_format():
"""
Returns "wheel", "conda" or "unspecified"
"""
return ffi.lib.LLVMPY_PackageFormat().decode()
package_format = _package_format()
def _llvm_assertions_state():
"""
Returns one of "on", "off" or "unknown". Depending on whether it is
determined that LLVM was build with assertions on, off, or is not known.
"Is not known" is typically from a dynamic linkage against LLVM in which
case it's not easily identified whether LLVM was built with assertions.
"""
return ffi.lib.LLVMPY_LlvmAssertionsState().decode()
llvm_assertions_state = _llvm_assertions_state()
@cache
def get_sysinfo():
d = dict()
d["ffi_lib_location"] = ffi.lib._name
d["package_format"] = package_format
d["llvm_linkage_type"] = build_llvm_linkage_type
d["libstdcxx_linkage_type"] = build_libstdcxx_linkage_type
d["llvm_assertions_state"] = llvm_assertions_state
# import lief
HAVE_LIEF = False
try:
import lief
HAVE_LIEF = True
except ImportError:
msg = "py-lief package not found, sysinfo is limited as a result"
warnings.warn(msg)
d["lief_probe_status"] = HAVE_LIEF
d["linked_libraries"] = None
d["canonicalised_linked_libraries"] = None
def canonicalise_library_type(dso):
"""Canonicalises the representation of the binary::libraries as a
sequence of strings"""
# Note lief v16:
# Mach-O .libraries are DylibCommand instances.
# Windows PE and Linux ELF .libraries are strings.
return [getattr(x, "name", x) for x in dso.libraries]
def canonicalise_library_spelling(libs):
# This adjusts the library "spelling" so that it just contains the
# name given to the linker. e.g. `@rpath/somewhere/libfoo.so.1.3`
# would be canonicalised to "foo".
fixes = []
for lib in libs:
# some libraries, e.g. Mach-O have an @rpath or system path
# prefix in their name, remove it.
path_stripped = os.path.split(lib)[-1]
# Assume all library names contain at least one dot, even if they
# don't it's fine, the first part is the piece of interest.
prefix_libname = path_stripped.split(".")[0]
linker_name = prefix_libname.replace("lib", "").replace("LIB", "")
# further canonicalize by referring to all libraries in lower case.
fixes.append(linker_name.lower())
return fixes
if HAVE_LIEF:
dso = lief.parse(d["ffi_lib_location"])
link_libs = tuple(canonicalise_library_type(dso))
d["linked_libraries"] = link_libs
canonicalised_libs = canonicalise_library_spelling(link_libs)
d["canonicalised_linked_libraries"] = canonicalised_libs
return d

View File

@@ -0,0 +1,31 @@
from llvmlite.binding import ffi
def create_context():
return ContextRef(
ffi.lib.LLVMPY_ContextCreate())
def get_global_context():
return GlobalContextRef(
ffi.lib.LLVMPY_GetGlobalContext())
class ContextRef(ffi.ObjectRef):
def __init__(self, context_ptr):
super(ContextRef, self).__init__(context_ptr)
def _dispose(self):
ffi.lib.LLVMPY_ContextDispose(self)
class GlobalContextRef(ContextRef):
def _dispose(self):
pass
ffi.lib.LLVMPY_GetGlobalContext.restype = ffi.LLVMContextRef
ffi.lib.LLVMPY_ContextCreate.restype = ffi.LLVMContextRef
ffi.lib.LLVMPY_ContextDispose.argtypes = [ffi.LLVMContextRef]

View File

@@ -0,0 +1,45 @@
from ctypes import c_void_p, c_char_p, c_bool, POINTER
from llvmlite.binding import ffi
from llvmlite.binding.common import _encode_string
def address_of_symbol(name):
"""
Get the in-process address of symbol named *name*.
An integer is returned, or None if the symbol isn't found.
"""
return ffi.lib.LLVMPY_SearchAddressOfSymbol(_encode_string(name))
def add_symbol(name, address):
"""
Register the *address* of global symbol *name*. This will make
it usable (e.g. callable) from LLVM-compiled functions.
"""
ffi.lib.LLVMPY_AddSymbol(_encode_string(name), c_void_p(address))
def load_library_permanently(filename):
"""
Load an external library
"""
with ffi.OutputString() as outerr:
if ffi.lib.LLVMPY_LoadLibraryPermanently(
_encode_string(filename), outerr):
raise RuntimeError(str(outerr))
# ============================================================================
# FFI
ffi.lib.LLVMPY_AddSymbol.argtypes = [
c_char_p,
c_void_p,
]
ffi.lib.LLVMPY_SearchAddressOfSymbol.argtypes = [c_char_p]
ffi.lib.LLVMPY_SearchAddressOfSymbol.restype = c_void_p
ffi.lib.LLVMPY_LoadLibraryPermanently.argtypes = [c_char_p, POINTER(c_char_p)]
ffi.lib.LLVMPY_LoadLibraryPermanently.restype = c_bool

View File

@@ -0,0 +1,330 @@
import platform
from ctypes import (POINTER, c_char_p, c_bool, c_void_p,
c_int, c_uint64, c_size_t, CFUNCTYPE, string_at, cast,
py_object, Structure)
from llvmlite.binding import ffi, targets, object_file
# Just check these weren't optimized out of the DLL.
ffi.lib.LLVMPY_LinkInMCJIT
def create_mcjit_compiler(module, target_machine, use_lmm=None):
"""
Create a MCJIT ExecutionEngine from the given *module* and
*target_machine*.
*lmm* controls whether the llvmlite memory manager is used. If not supplied,
the default choice for the platform will be used (``True`` on 64-bit ARM
systems, ``False`` otherwise).
"""
if use_lmm is None:
use_lmm = platform.machine() in ('arm64', 'aarch64')
with ffi.OutputString() as outerr:
engine = ffi.lib.LLVMPY_CreateMCJITCompiler(
module, target_machine, use_lmm, outerr)
if not engine:
raise RuntimeError(str(outerr))
target_machine._owned = True
return ExecutionEngine(engine, module=module)
def check_jit_execution():
"""
Check the system allows execution of in-memory JITted functions.
An exception is raised otherwise.
"""
errno = ffi.lib.LLVMPY_TryAllocateExecutableMemory()
if errno != 0:
raise OSError(errno,
"cannot allocate executable memory. "
"This may be due to security restrictions on your "
"system, such as SELinux or similar mechanisms."
)
class ExecutionEngine(ffi.ObjectRef):
"""An ExecutionEngine owns all Modules associated with it.
Deleting the engine will remove all associated modules.
It is an error to delete the associated modules.
"""
_object_cache = None
def __init__(self, ptr, module):
"""
Module ownership is transferred to the EE
"""
self._modules = set([module])
self._td = None
module._owned = True
ffi.ObjectRef.__init__(self, ptr)
def get_function_address(self, name):
"""
Return the address of the function named *name* as an integer.
It's a fatal error in LLVM if the symbol of *name* doesn't exist.
"""
return ffi.lib.LLVMPY_GetFunctionAddress(self, name.encode("ascii"))
def get_global_value_address(self, name):
"""
Return the address of the global value named *name* as an integer.
It's a fatal error in LLVM if the symbol of *name* doesn't exist.
"""
return ffi.lib.LLVMPY_GetGlobalValueAddress(self, name.encode("ascii"))
def add_global_mapping(self, gv, addr):
ffi.lib.LLVMPY_AddGlobalMapping(self, gv, addr)
def add_module(self, module):
"""
Ownership of module is transferred to the execution engine
"""
if module in self._modules:
raise KeyError("module already added to this engine")
ffi.lib.LLVMPY_AddModule(self, module)
module._owned = True
self._modules.add(module)
def finalize_object(self):
"""
Make sure all modules owned by the execution engine are fully processed
and "usable" for execution.
"""
ffi.lib.LLVMPY_FinalizeObject(self)
def run_static_constructors(self):
"""
Run static constructors which initialize module-level static objects.
"""
ffi.lib.LLVMPY_RunStaticConstructors(self)
def run_static_destructors(self):
"""
Run static destructors which perform module-level cleanup of static
resources.
"""
ffi.lib.LLVMPY_RunStaticDestructors(self)
def remove_module(self, module):
"""
Ownership of module is returned
"""
with ffi.OutputString() as outerr:
if ffi.lib.LLVMPY_RemoveModule(self, module, outerr):
raise RuntimeError(str(outerr))
self._modules.remove(module)
module._owned = False
@property
def target_data(self):
"""
The TargetData for this execution engine.
"""
if self._td is not None:
return self._td
ptr = ffi.lib.LLVMPY_GetExecutionEngineTargetData(self)
self._td = targets.TargetData(ptr)
self._td._owned = True
return self._td
def enable_jit_events(self):
"""
Enable JIT events for profiling of generated code.
Return value indicates whether connection to profiling tool
was successful.
"""
ret = ffi.lib.LLVMPY_EnableJITEvents(self)
return ret
def _find_module_ptr(self, module_ptr):
"""
Find the ModuleRef corresponding to the given pointer.
"""
ptr = cast(module_ptr, c_void_p).value
for module in self._modules:
if cast(module._ptr, c_void_p).value == ptr:
return module
return None
def add_object_file(self, obj_file):
"""
Add object file to the jit. object_file can be instance of
:class:ObjectFile or a string representing file system path
"""
if isinstance(obj_file, str):
obj_file = object_file.ObjectFileRef.from_path(obj_file)
ffi.lib.LLVMPY_MCJITAddObjectFile(self, obj_file)
def set_object_cache(self, notify_func=None, getbuffer_func=None):
"""
Set the object cache "notifyObjectCompiled" and "getBuffer"
callbacks to the given Python functions.
"""
self._object_cache_notify = notify_func
self._object_cache_getbuffer = getbuffer_func
# Lifetime of the object cache is managed by us.
self._object_cache = _ObjectCacheRef(self)
# Note this doesn't keep a reference to self, to avoid reference
# cycles.
ffi.lib.LLVMPY_SetObjectCache(self, self._object_cache)
def _raw_object_cache_notify(self, data):
"""
Low-level notify hook.
"""
if self._object_cache_notify is None:
return
module_ptr = data.contents.module_ptr
buf_ptr = data.contents.buf_ptr
buf_len = data.contents.buf_len
buf = string_at(buf_ptr, buf_len)
module = self._find_module_ptr(module_ptr)
if module is None:
# The LLVM EE should only give notifications for modules
# known by us.
raise RuntimeError("object compilation notification "
"for unknown module %s" % (module_ptr,))
self._object_cache_notify(module, buf)
def _raw_object_cache_getbuffer(self, data):
"""
Low-level getbuffer hook.
"""
if self._object_cache_getbuffer is None:
return
module_ptr = data.contents.module_ptr
module = self._find_module_ptr(module_ptr)
if module is None:
# The LLVM EE should only give notifications for modules
# known by us.
raise RuntimeError("object compilation notification "
"for unknown module %s" % (module_ptr,))
buf = self._object_cache_getbuffer(module)
if buf is not None:
# Create a copy, which will be freed by the caller
data[0].buf_ptr = ffi.lib.LLVMPY_CreateByteString(buf, len(buf))
data[0].buf_len = len(buf)
def _dispose(self):
# The modules will be cleaned up by the EE
for mod in self._modules:
mod.detach()
if self._td is not None:
self._td.detach()
self._modules.clear()
self._object_cache = None
self._capi.LLVMPY_DisposeExecutionEngine(self)
class _ObjectCacheRef(ffi.ObjectRef):
"""
Internal: an ObjectCache instance for use within an ExecutionEngine.
"""
def __init__(self, obj):
ptr = ffi.lib.LLVMPY_CreateObjectCache(_notify_c_hook,
_getbuffer_c_hook,
obj)
ffi.ObjectRef.__init__(self, ptr)
def _dispose(self):
self._capi.LLVMPY_DisposeObjectCache(self)
# ============================================================================
# FFI
ffi.lib.LLVMPY_CreateMCJITCompiler.argtypes = [
ffi.LLVMModuleRef,
ffi.LLVMTargetMachineRef,
c_bool,
POINTER(c_char_p),
]
ffi.lib.LLVMPY_CreateMCJITCompiler.restype = ffi.LLVMExecutionEngineRef
ffi.lib.LLVMPY_RemoveModule.argtypes = [
ffi.LLVMExecutionEngineRef,
ffi.LLVMModuleRef,
POINTER(c_char_p),
]
ffi.lib.LLVMPY_RemoveModule.restype = c_bool
ffi.lib.LLVMPY_AddModule.argtypes = [
ffi.LLVMExecutionEngineRef,
ffi.LLVMModuleRef
]
ffi.lib.LLVMPY_AddGlobalMapping.argtypes = [ffi.LLVMExecutionEngineRef,
ffi.LLVMValueRef,
c_void_p]
ffi.lib.LLVMPY_FinalizeObject.argtypes = [ffi.LLVMExecutionEngineRef]
ffi.lib.LLVMPY_GetExecutionEngineTargetData.argtypes = [
ffi.LLVMExecutionEngineRef
]
ffi.lib.LLVMPY_GetExecutionEngineTargetData.restype = ffi.LLVMTargetDataRef
ffi.lib.LLVMPY_TryAllocateExecutableMemory.argtypes = []
ffi.lib.LLVMPY_TryAllocateExecutableMemory.restype = c_int
ffi.lib.LLVMPY_GetFunctionAddress.argtypes = [
ffi.LLVMExecutionEngineRef,
c_char_p
]
ffi.lib.LLVMPY_GetFunctionAddress.restype = c_uint64
ffi.lib.LLVMPY_GetGlobalValueAddress.argtypes = [
ffi.LLVMExecutionEngineRef,
c_char_p
]
ffi.lib.LLVMPY_GetGlobalValueAddress.restype = c_uint64
ffi.lib.LLVMPY_MCJITAddObjectFile.argtypes = [
ffi.LLVMExecutionEngineRef,
ffi.LLVMObjectFileRef
]
class _ObjectCacheData(Structure):
_fields_ = [
('module_ptr', ffi.LLVMModuleRef),
('buf_ptr', c_void_p),
('buf_len', c_size_t),
]
_ObjectCacheNotifyFunc = CFUNCTYPE(None, py_object,
POINTER(_ObjectCacheData))
_ObjectCacheGetBufferFunc = CFUNCTYPE(None, py_object,
POINTER(_ObjectCacheData))
# XXX The ctypes function wrappers are created at the top-level, otherwise
# there are issues when creating CFUNCTYPEs in child processes on CentOS 5
# 32 bits.
_notify_c_hook = _ObjectCacheNotifyFunc(
ExecutionEngine._raw_object_cache_notify)
_getbuffer_c_hook = _ObjectCacheGetBufferFunc(
ExecutionEngine._raw_object_cache_getbuffer)
ffi.lib.LLVMPY_CreateObjectCache.argtypes = [_ObjectCacheNotifyFunc,
_ObjectCacheGetBufferFunc,
py_object]
ffi.lib.LLVMPY_CreateObjectCache.restype = ffi.LLVMObjectCacheRef
ffi.lib.LLVMPY_DisposeObjectCache.argtypes = [ffi.LLVMObjectCacheRef]
ffi.lib.LLVMPY_SetObjectCache.argtypes = [ffi.LLVMExecutionEngineRef,
ffi.LLVMObjectCacheRef]
ffi.lib.LLVMPY_CreateByteString.restype = c_void_p
ffi.lib.LLVMPY_CreateByteString.argtypes = [c_void_p, c_size_t]

View File

@@ -0,0 +1,410 @@
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")
LLVMTimePassesHandlerRef = _make_opaque_ref("LLVMTimePassesHandler")
LLVMPipelineTuningOptionsRef = _make_opaque_ref("LLVMPipeLineTuningOptions")
LLVMModulePassManagerRef = _make_opaque_ref("LLVMModulePassManager")
LLVMFunctionPassManagerRef = _make_opaque_ref("LLVMFunctionPassManager")
LLVMPassBuilderRef = _make_opaque_ref("LLVMPassBuilder")
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):
test_sym = "LLVMPY_GetVersionInfo"
mod_name = __name__.rpartition(".")[0]
lib_name = get_library_name()
with _suppress_cleanup_errors(_importlib_resources_path(
mod_name, lib_name)) as lib_path:
try:
self._lib_handle = ctypes.CDLL(str(lib_path))
# Check that we can look up expected symbols.
getattr(self._lib_handle, test_sym)()
except OSError:
# OSError may be raised if the file cannot be opened, or is not
# a shared library.
msg = (f"Could not find/load shared object file '{lib_name}' "
f"from resource location: '{mod_name}'. This could mean "
"that the library literally cannot be found, but may "
"also mean that the permissions are incorrect or that a "
"dependency of/a symbol in the library could not be "
"resolved.")
raise OSError(msg)
except AttributeError:
# AttributeError is raised if the test_sym symbol does not
# exist.
msg = ("During testing of symbol lookup, the symbol "
f"'{test_sym}' could not be found in the library "
f"'{lib_path}'")
raise OSError(msg)
@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:
pass
# 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, 10)
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)

View File

@@ -0,0 +1,85 @@
from ctypes import c_uint
from llvmlite.binding import ffi
def initialize():
"""
Initialize the LLVM core (deprecated).
This function is deprecated and will raise an error when called.
LLVM initialization is now handled automatically and no longer
requires explicit initialization calls.
Raises:
RuntimeError: Always raised as this function is no longer needed.
"""
raise RuntimeError(
"llvmlite.binding.initialize() is deprecated and will be removed. "
"LLVM initialization is now handled automatically. "
"Please remove calls to this function from your code and check for "
"other behavioral changes that may have occurred due to LLVM updates."
)
def initialize_all_targets():
"""
Initialize all targets. Necessary before targets can be looked up
via the :class:`Target` class.
"""
ffi.lib.LLVMPY_InitializeAllTargetInfos()
ffi.lib.LLVMPY_InitializeAllTargets()
ffi.lib.LLVMPY_InitializeAllTargetMCs()
def initialize_all_asmprinters():
"""
Initialize all code generators. Necessary before generating
any assembly or machine code via the :meth:`TargetMachine.emit_object`
and :meth:`TargetMachine.emit_assembly` methods.
"""
ffi.lib.LLVMPY_InitializeAllAsmPrinters()
def initialize_native_target():
"""
Initialize the native (host) target. Necessary before doing any
code generation.
"""
ffi.lib.LLVMPY_InitializeNativeTarget()
def initialize_native_asmprinter():
"""
Initialize the native ASM printer.
"""
ffi.lib.LLVMPY_InitializeNativeAsmPrinter()
def initialize_native_asmparser():
"""
Initialize the native ASM parser.
"""
ffi.lib.LLVMPY_InitializeNativeAsmParser()
def shutdown():
ffi.lib.LLVMPY_Shutdown()
# =============================================================================
# Set function FFI
ffi.lib.LLVMPY_GetVersionInfo.restype = c_uint
def _version_info():
v = []
x = ffi.lib.LLVMPY_GetVersionInfo()
while x:
v.append(x & 0xff)
x >>= 8
return tuple(reversed(v))
llvm_version_info = _version_info()

View File

@@ -0,0 +1,20 @@
from ctypes import c_int, c_char_p, POINTER
from llvmlite.binding import ffi
def link_modules(dst, src):
with ffi.OutputString() as outerr:
err = ffi.lib.LLVMPY_LinkModules(dst, src, outerr)
# The underlying module was destroyed
src.detach()
if err:
raise RuntimeError(str(outerr))
ffi.lib.LLVMPY_LinkModules.argtypes = [
ffi.LLVMModuleRef,
ffi.LLVMModuleRef,
POINTER(c_char_p),
]
ffi.lib.LLVMPY_LinkModules.restype = c_int

View File

@@ -0,0 +1,349 @@
from ctypes import (c_char_p, byref, POINTER, c_bool, create_string_buffer,
c_size_t, string_at)
from llvmlite.binding import ffi
from llvmlite.binding.linker import link_modules
from llvmlite.binding.common import _decode_string, _encode_string
from llvmlite.binding.value import ValueRef, TypeRef
from llvmlite.binding.context import get_global_context
def parse_assembly(llvmir, context=None):
"""
Create Module from a LLVM IR string
"""
if context is None:
context = get_global_context()
llvmir = _encode_string(llvmir)
strbuf = c_char_p(llvmir)
with ffi.OutputString() as errmsg:
mod = ModuleRef(
ffi.lib.LLVMPY_ParseAssembly(context, strbuf, errmsg),
context)
if errmsg:
mod.close()
raise RuntimeError("LLVM IR parsing error\n{0}".format(errmsg))
return mod
def parse_bitcode(bitcode, context=None):
"""
Create Module from a LLVM *bitcode* (a bytes object).
"""
if context is None:
context = get_global_context()
buf = c_char_p(bitcode)
bufsize = len(bitcode)
with ffi.OutputString() as errmsg:
mod = ModuleRef(ffi.lib.LLVMPY_ParseBitcode(
context, buf, bufsize, errmsg), context)
if errmsg:
mod.close()
raise RuntimeError(
"LLVM bitcode parsing error\n{0}".format(errmsg))
return mod
class ModuleRef(ffi.ObjectRef):
"""
A reference to a LLVM module.
"""
def __init__(self, module_ptr, context):
super(ModuleRef, self).__init__(module_ptr)
self._context = context
def __str__(self):
with ffi.OutputString() as outstr:
ffi.lib.LLVMPY_PrintModuleToString(self, outstr)
return str(outstr)
def as_bitcode(self):
"""
Return the module's LLVM bitcode, as a bytes object.
"""
ptr = c_char_p(None)
size = c_size_t(-1)
ffi.lib.LLVMPY_WriteBitcodeToString(self, byref(ptr), byref(size))
if not ptr:
raise MemoryError
try:
assert size.value >= 0
return string_at(ptr, size.value)
finally:
ffi.lib.LLVMPY_DisposeString(ptr)
def _dispose(self):
self._capi.LLVMPY_DisposeModule(self)
def get_function(self, name):
"""
Get a ValueRef pointing to the function named *name*.
NameError is raised if the symbol isn't found.
"""
p = ffi.lib.LLVMPY_GetNamedFunction(self, _encode_string(name))
if not p:
raise NameError(name)
return ValueRef(p, 'function', dict(module=self))
def get_global_variable(self, name):
"""
Get a ValueRef pointing to the global variable named *name*.
NameError is raised if the symbol isn't found.
"""
p = ffi.lib.LLVMPY_GetNamedGlobalVariable(self, _encode_string(name))
if not p:
raise NameError(name)
return ValueRef(p, 'global', dict(module=self))
def get_struct_type(self, name):
"""
Get a TypeRef pointing to a structure type named *name*.
NameError is raised if the struct type isn't found.
"""
p = ffi.lib.LLVMPY_GetNamedStructType(self, _encode_string(name))
if not p:
raise NameError(name)
return TypeRef(p)
def verify(self):
"""
Verify the module IR's correctness. RuntimeError is raised on error.
"""
with ffi.OutputString() as outmsg:
if ffi.lib.LLVMPY_VerifyModule(self, outmsg):
raise RuntimeError(str(outmsg))
@property
def name(self):
"""
The module's identifier.
"""
return _decode_string(ffi.lib.LLVMPY_GetModuleName(self))
@name.setter
def name(self, value):
ffi.lib.LLVMPY_SetModuleName(self, _encode_string(value))
@property
def source_file(self):
"""
The module's original source file name
"""
return _decode_string(ffi.lib.LLVMPY_GetModuleSourceFileName(self))
@property
def data_layout(self):
"""
This module's data layout specification, as a string.
"""
# LLVMGetDataLayout() points inside a std::string managed by LLVM.
with ffi.OutputString(owned=False) as outmsg:
ffi.lib.LLVMPY_GetDataLayout(self, outmsg)
return str(outmsg)
@data_layout.setter
def data_layout(self, strrep):
ffi.lib.LLVMPY_SetDataLayout(self,
create_string_buffer(
strrep.encode('utf8')))
@property
def triple(self):
"""
This module's target "triple" specification, as a string.
"""
# LLVMGetTarget() points inside a std::string managed by LLVM.
with ffi.OutputString(owned=False) as outmsg:
ffi.lib.LLVMPY_GetTarget(self, outmsg)
return str(outmsg)
@triple.setter
def triple(self, strrep):
ffi.lib.LLVMPY_SetTarget(self,
create_string_buffer(
strrep.encode('utf8')))
def link_in(self, other, preserve=False):
"""
Link the *other* module into this one. The *other* module will
be destroyed unless *preserve* is true.
"""
if preserve:
other = other.clone()
link_modules(self, other)
@property
def global_variables(self):
"""
Return an iterator over this module's global variables.
The iterator will yield a ValueRef for each global variable.
Note that global variables don't include functions
(a function is a "global value" but not a "global variable" in
LLVM parlance)
"""
it = ffi.lib.LLVMPY_ModuleGlobalsIter(self)
return _GlobalsIterator(it, dict(module=self))
@property
def functions(self):
"""
Return an iterator over this module's functions.
The iterator will yield a ValueRef for each function.
"""
it = ffi.lib.LLVMPY_ModuleFunctionsIter(self)
return _FunctionsIterator(it, dict(module=self))
@property
def struct_types(self):
"""
Return an iterator over the struct types defined in
the module. The iterator will yield a TypeRef.
"""
it = ffi.lib.LLVMPY_ModuleTypesIter(self)
return _TypesIterator(it, dict(module=self))
def clone(self):
return ModuleRef(ffi.lib.LLVMPY_CloneModule(self), self._context)
class _Iterator(ffi.ObjectRef):
kind = None
def __init__(self, ptr, parents):
ffi.ObjectRef.__init__(self, ptr)
self._parents = parents
assert self.kind is not None
def __next__(self):
vp = self._next()
if vp:
return ValueRef(vp, self.kind, self._parents)
else:
raise StopIteration
next = __next__
def __iter__(self):
return self
class _GlobalsIterator(_Iterator):
kind = 'global'
def _dispose(self):
self._capi.LLVMPY_DisposeGlobalsIter(self)
def _next(self):
return ffi.lib.LLVMPY_GlobalsIterNext(self)
class _FunctionsIterator(_Iterator):
kind = 'function'
def _dispose(self):
self._capi.LLVMPY_DisposeFunctionsIter(self)
def _next(self):
return ffi.lib.LLVMPY_FunctionsIterNext(self)
class _TypesIterator(_Iterator):
kind = 'type'
def _dispose(self):
self._capi.LLVMPY_DisposeTypesIter(self)
def __next__(self):
vp = self._next()
if vp:
return TypeRef(vp)
else:
raise StopIteration
def _next(self):
return ffi.lib.LLVMPY_TypesIterNext(self)
next = __next__
# =============================================================================
# Set function FFI
ffi.lib.LLVMPY_ParseAssembly.argtypes = [ffi.LLVMContextRef,
c_char_p,
POINTER(c_char_p)]
ffi.lib.LLVMPY_ParseAssembly.restype = ffi.LLVMModuleRef
ffi.lib.LLVMPY_ParseBitcode.argtypes = [ffi.LLVMContextRef,
c_char_p, c_size_t,
POINTER(c_char_p)]
ffi.lib.LLVMPY_ParseBitcode.restype = ffi.LLVMModuleRef
ffi.lib.LLVMPY_DisposeModule.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_PrintModuleToString.argtypes = [ffi.LLVMModuleRef,
POINTER(c_char_p)]
ffi.lib.LLVMPY_WriteBitcodeToString.argtypes = [ffi.LLVMModuleRef,
POINTER(c_char_p),
POINTER(c_size_t)]
ffi.lib.LLVMPY_GetNamedFunction.argtypes = [ffi.LLVMModuleRef,
c_char_p]
ffi.lib.LLVMPY_GetNamedFunction.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_VerifyModule.argtypes = [ffi.LLVMModuleRef,
POINTER(c_char_p)]
ffi.lib.LLVMPY_VerifyModule.restype = c_bool
ffi.lib.LLVMPY_GetDataLayout.argtypes = [ffi.LLVMModuleRef, POINTER(c_char_p)]
ffi.lib.LLVMPY_SetDataLayout.argtypes = [ffi.LLVMModuleRef, c_char_p]
ffi.lib.LLVMPY_GetTarget.argtypes = [ffi.LLVMModuleRef, POINTER(c_char_p)]
ffi.lib.LLVMPY_SetTarget.argtypes = [ffi.LLVMModuleRef, c_char_p]
ffi.lib.LLVMPY_GetNamedGlobalVariable.argtypes = [ffi.LLVMModuleRef, c_char_p]
ffi.lib.LLVMPY_GetNamedGlobalVariable.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_GetNamedStructType.argtypes = [ffi.LLVMModuleRef, c_char_p]
ffi.lib.LLVMPY_GetNamedStructType.restype = ffi.LLVMTypeRef
ffi.lib.LLVMPY_ModuleGlobalsIter.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_ModuleGlobalsIter.restype = ffi.LLVMGlobalsIterator
ffi.lib.LLVMPY_DisposeGlobalsIter.argtypes = [ffi.LLVMGlobalsIterator]
ffi.lib.LLVMPY_GlobalsIterNext.argtypes = [ffi.LLVMGlobalsIterator]
ffi.lib.LLVMPY_GlobalsIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_ModuleFunctionsIter.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_ModuleFunctionsIter.restype = ffi.LLVMFunctionsIterator
ffi.lib.LLVMPY_ModuleTypesIter.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_ModuleTypesIter.restype = ffi.LLVMTypesIterator
ffi.lib.LLVMPY_DisposeFunctionsIter.argtypes = [ffi.LLVMFunctionsIterator]
ffi.lib.LLVMPY_DisposeTypesIter.argtypes = [ffi.LLVMTypesIterator]
ffi.lib.LLVMPY_FunctionsIterNext.argtypes = [ffi.LLVMFunctionsIterator]
ffi.lib.LLVMPY_FunctionsIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_TypesIterNext.argtypes = [ffi.LLVMTypesIterator]
ffi.lib.LLVMPY_TypesIterNext.restype = ffi.LLVMTypeRef
ffi.lib.LLVMPY_CloneModule.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_CloneModule.restype = ffi.LLVMModuleRef
ffi.lib.LLVMPY_GetModuleName.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_GetModuleName.restype = c_char_p
ffi.lib.LLVMPY_SetModuleName.argtypes = [ffi.LLVMModuleRef, c_char_p]
ffi.lib.LLVMPY_GetModuleSourceFileName.argtypes = [ffi.LLVMModuleRef]
ffi.lib.LLVMPY_GetModuleSourceFileName.restype = c_char_p

View File

@@ -0,0 +1,82 @@
from llvmlite.binding import ffi
from ctypes import (c_bool, c_char_p, c_char, c_size_t, string_at, c_uint64,
POINTER)
class SectionIteratorRef(ffi.ObjectRef):
def name(self):
return ffi.lib.LLVMPY_GetSectionName(self)
def is_text(self):
return ffi.lib.LLVMPY_IsSectionText(self)
def size(self):
return ffi.lib.LLVMPY_GetSectionSize(self)
def address(self):
return ffi.lib.LLVMPY_GetSectionAddress(self)
def data(self):
return string_at(ffi.lib.LLVMPY_GetSectionContents(self), self.size())
def is_end(self, object_file):
return ffi.lib.LLVMPY_IsSectionIteratorAtEnd(object_file, self)
def next(self):
ffi.lib.LLVMPY_MoveToNextSection(self)
def _dispose(self):
ffi.lib.LLVMPY_DisposeSectionIterator(self)
class ObjectFileRef(ffi.ObjectRef):
@classmethod
def from_data(cls, data):
return cls(ffi.lib.LLVMPY_CreateObjectFile(data, len(data)))
@classmethod
def from_path(cls, path):
with open(path, 'rb') as f:
data = f.read()
return cls(ffi.lib.LLVMPY_CreateObjectFile(data, len(data)))
def sections(self):
it = SectionIteratorRef(ffi.lib.LLVMPY_GetSections(self))
while not it.is_end(self):
yield it
it.next()
def _dispose(self):
ffi.lib.LLVMPY_DisposeObjectFile(self)
ffi.lib.LLVMPY_CreateObjectFile.argtypes = [c_char_p, c_size_t]
ffi.lib.LLVMPY_CreateObjectFile.restype = ffi.LLVMObjectFileRef
ffi.lib.LLVMPY_DisposeObjectFile.argtypes = [ffi.LLVMObjectFileRef]
ffi.lib.LLVMPY_GetSections.argtypes = [ffi.LLVMObjectFileRef]
ffi.lib.LLVMPY_GetSections.restype = ffi.LLVMSectionIteratorRef
ffi.lib.LLVMPY_DisposeSectionIterator.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_MoveToNextSection.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_IsSectionIteratorAtEnd.argtypes = [
ffi.LLVMObjectFileRef, ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_IsSectionIteratorAtEnd.restype = c_bool
ffi.lib.LLVMPY_GetSectionName.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_GetSectionName.restype = c_char_p
ffi.lib.LLVMPY_GetSectionSize.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_GetSectionSize.restype = c_uint64
ffi.lib.LLVMPY_GetSectionAddress.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_GetSectionAddress.restype = c_uint64
ffi.lib.LLVMPY_GetSectionContents.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_GetSectionContents.restype = POINTER(c_char)
ffi.lib.LLVMPY_IsSectionText.argtypes = [ffi.LLVMSectionIteratorRef]
ffi.lib.LLVMPY_IsSectionText.restype = c_bool

View File

@@ -0,0 +1,17 @@
from llvmlite.binding import ffi
from llvmlite.binding.common import _encode_string
from ctypes import c_char_p
def set_option(name, option):
"""
Set the given LLVM "command-line" option.
For example set_option("test", "-debug-pass=Structure") would display
all optimization passes when generating code.
"""
ffi.lib.LLVMPY_SetCommandLine(_encode_string(name),
_encode_string(option))
ffi.lib.LLVMPY_SetCommandLine.argtypes = [c_char_p, c_char_p]

View File

@@ -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

View File

@@ -0,0 +1,462 @@
import os
from ctypes import (POINTER, c_char_p, c_longlong, c_int, c_size_t,
c_void_p, string_at)
from llvmlite.binding import ffi
from llvmlite.binding.initfini import llvm_version_info
from llvmlite.binding.common import _decode_string, _encode_string
from collections import namedtuple
# import for backward compatible API, `has_svml` is now in config module.
from llvmlite.binding.config import _has_svml as has_svml # noqa: F401
Triple = namedtuple('Triple', ['Arch', 'SubArch', 'Vendor',
'OS', 'Env', 'ObjectFormat'])
def get_process_triple():
"""
Return a target triple suitable for generating code for the current process.
An example when the default triple from ``get_default_triple()`` is not be
suitable is when LLVM is compiled for 32-bit but the process is executing
in 64-bit mode.
"""
with ffi.OutputString() as out:
ffi.lib.LLVMPY_GetProcessTriple(out)
return str(out)
def get_triple_parts(triple: str):
"""
Return a tuple of the parts of the given triple.
"""
with ffi.OutputString() as arch, \
ffi.OutputString() as vendor, \
ffi.OutputString() as os, ffi.OutputString() as env:
ffi.lib.LLVMPY_GetTripleParts(triple.encode('utf8'),
arch, vendor, os, env)
arch = str(arch)
subarch = ''
for _str in triple.split('-'):
if _str.startswith(arch):
subarch = _str[len(arch):]
break
return Triple(arch, subarch, str(vendor), str(os),
str(env), get_object_format(triple))
class FeatureMap(dict):
"""
Maps feature name to a boolean indicating the availability of the feature.
Extends ``dict`` to add `.flatten()` method.
"""
def flatten(self, sort=True):
"""
Args
----
sort: bool
Optional. If True, the features are sorted by name; otherwise,
the ordering is unstable between python session due to hash
randomization. Defaults to True.
Returns a string suitable for use as the ``features`` argument to
``Target.create_target_machine()``.
"""
iterator = sorted(self.items()) if sort else iter(self.items())
flag_map = {True: '+', False: '-'}
return ','.join('{0}{1}'.format(flag_map[v], k)
for k, v in iterator)
def get_host_cpu_features():
"""
Returns a dictionary-like object indicating the CPU features for current
architecture and whether they are enabled for this CPU. The key-value pairs
are the feature name as string and a boolean indicating whether the feature
is available. The returned value is an instance of ``FeatureMap`` class,
which adds a new method ``.flatten()`` for returning a string suitable for
use as the "features" argument to ``Target.create_target_machine()``.
If LLVM has not implemented this feature or it fails to get the information,
this function will raise a RuntimeError exception.
"""
with ffi.OutputString() as out:
outdict = FeatureMap()
if not ffi.lib.LLVMPY_GetHostCPUFeatures(out):
return outdict
flag_map = {'+': True, '-': False}
content = str(out)
if content: # protect against empty string
for feat in content.split(','):
if feat: # protect against empty feature
outdict[feat[1:]] = flag_map[feat[0]]
return outdict
def get_default_triple():
"""
Return the default target triple LLVM is configured to produce code for.
"""
with ffi.OutputString() as out:
ffi.lib.LLVMPY_GetDefaultTargetTriple(out)
return str(out)
def get_host_cpu_name():
"""
Get the name of the host's CPU, suitable for using with
:meth:`Target.create_target_machine()`.
"""
with ffi.OutputString() as out:
ffi.lib.LLVMPY_GetHostCPUName(out)
return str(out)
# Adapted from https://github.com/llvm/llvm-project/blob/release/15.x/llvm/include/llvm/ADT/Triple.h#L269 # noqa
llvm_version_major = llvm_version_info[0]
_object_formats = {
0: "Unknown",
1: "COFF",
2: "DXContainer",
3: "ELF",
4: "GOFF",
5: "MachO",
6: "SPIRV",
7: "Wasm",
8: "XCOFF",
}
def get_object_format(triple=None):
"""
Get the object format for the given *triple* string (or the default
triple if omitted).
A string is returned
"""
if triple is None:
triple = get_default_triple()
res = ffi.lib.LLVMPY_GetTripleObjectFormat(_encode_string(triple))
return _object_formats[res]
def create_target_data(layout):
"""
Create a TargetData instance for the given *layout* string.
"""
return TargetData(ffi.lib.LLVMPY_CreateTargetData(_encode_string(layout)))
class TargetData(ffi.ObjectRef):
"""
A TargetData provides structured access to a data layout.
Use :func:`create_target_data` to create instances.
"""
def __str__(self):
if self._closed:
return "<dead TargetData>"
with ffi.OutputString() as out:
ffi.lib.LLVMPY_CopyStringRepOfTargetData(self, out)
return str(out)
def _dispose(self):
self._capi.LLVMPY_DisposeTargetData(self)
def get_abi_size(self, ty):
"""
Get ABI size of LLVM type *ty*.
"""
return ffi.lib.LLVMPY_ABISizeOfType(self, ty)
def get_element_offset(self, ty, position):
"""
Get byte offset of type's ty element at the given position
"""
offset = ffi.lib.LLVMPY_OffsetOfElement(self, ty, position)
if offset == -1:
raise ValueError("Could not determined offset of {}th "
"element of the type '{}'. Is it a struct"
"type?".format(position, str(ty)))
return offset
def get_abi_alignment(self, ty):
"""
Get minimum ABI alignment of LLVM type *ty*.
"""
return ffi.lib.LLVMPY_ABIAlignmentOfType(self, ty)
RELOC = frozenset(['default', 'static', 'pic', 'dynamicnopic'])
CODEMODEL = frozenset(['default', 'jitdefault', 'small', 'kernel', 'medium',
'large'])
class Target(ffi.ObjectRef):
_triple = ''
# No _dispose() method since LLVMGetTargetFromTriple() returns a
# persistent object.
@classmethod
def from_default_triple(cls):
"""
Create a Target instance for the default triple.
"""
triple = get_default_triple()
return cls.from_triple(triple)
@classmethod
def from_triple(cls, triple):
"""
Create a Target instance for the given triple (a string).
"""
with ffi.OutputString() as outerr:
target = ffi.lib.LLVMPY_GetTargetFromTriple(triple.encode('utf8'),
outerr)
if not target:
raise RuntimeError(str(outerr))
target = cls(target)
target._triple = triple
return target
@property
def name(self):
s = ffi.lib.LLVMPY_GetTargetName(self)
return _decode_string(s)
@property
def description(self):
s = ffi.lib.LLVMPY_GetTargetDescription(self)
return _decode_string(s)
@property
def triple(self):
return self._triple
def __str__(self):
return "<Target {0} ({1})>".format(self.name, self.description)
def create_target_machine(self, cpu='', features='',
opt=2, reloc='default', codemodel='jitdefault',
printmc=False, jit=False, abiname=''):
"""
Create a new TargetMachine for this target and the given options.
Specifying codemodel='default' will result in the use of the "small"
code model. Specifying codemodel='jitdefault' will result in the code
model being picked based on platform bitness (32="small", 64="large").
The `printmc` option corresponds to llvm's `-print-machineinstrs`.
The `jit` option should be set when the target-machine is to be used
in a JIT engine.
The `abiname` option specifies the ABI. RISC-V targets with hard-float
needs to pass the ABI name to LLVM.
"""
assert 0 <= opt <= 3
assert reloc in RELOC
assert codemodel in CODEMODEL
triple = self._triple
# MCJIT under Windows only supports ELF objects, see
# http://lists.llvm.org/pipermail/llvm-dev/2013-December/068341.html
# Note we still want to produce regular COFF files in AOT mode.
if os.name == 'nt' and codemodel == 'jitdefault':
triple += '-elf'
tm = ffi.lib.LLVMPY_CreateTargetMachine(self,
_encode_string(triple),
_encode_string(cpu),
_encode_string(features),
opt,
_encode_string(reloc),
_encode_string(codemodel),
int(printmc),
int(jit),
_encode_string(abiname),
)
if tm:
return TargetMachine(tm)
else:
raise RuntimeError("Cannot create target machine")
class TargetMachine(ffi.ObjectRef):
def _dispose(self):
self._capi.LLVMPY_DisposeTargetMachine(self)
def add_analysis_passes(self, pm):
"""
Register analysis passes for this target machine with a pass manager.
"""
ffi.lib.LLVMPY_AddAnalysisPasses(self, pm)
def set_asm_verbosity(self, verbose):
"""
Set whether this target machine will emit assembly with human-readable
comments describing control flow, debug information, and so on.
"""
ffi.lib.LLVMPY_SetTargetMachineAsmVerbosity(self, verbose)
def emit_object(self, module):
"""
Represent the module as a code object, suitable for use with
the platform's linker. Returns a byte string.
"""
return self._emit_to_memory(module, use_object=True)
def emit_assembly(self, module):
"""
Return the raw assembler of the module, as a string.
llvm.initialize_native_asmprinter() must have been called first.
"""
return _decode_string(self._emit_to_memory(module, use_object=False))
def _emit_to_memory(self, module, use_object=False):
"""Returns bytes of object code of the module.
Args
----
use_object : bool
Emit object code or (if False) emit assembly code.
"""
with ffi.OutputString() as outerr:
mb = ffi.lib.LLVMPY_TargetMachineEmitToMemory(self, module,
int(use_object),
outerr)
if not mb:
raise RuntimeError(str(outerr))
bufptr = ffi.lib.LLVMPY_GetBufferStart(mb)
bufsz = ffi.lib.LLVMPY_GetBufferSize(mb)
try:
return string_at(bufptr, bufsz)
finally:
ffi.lib.LLVMPY_DisposeMemoryBuffer(mb)
@property
def target_data(self):
return TargetData(ffi.lib.LLVMPY_CreateTargetMachineData(self))
@property
def triple(self):
with ffi.OutputString() as out:
ffi.lib.LLVMPY_GetTargetMachineTriple(self, out)
return str(out)
# ============================================================================
# FFI
ffi.lib.LLVMPY_GetProcessTriple.argtypes = [POINTER(c_char_p)]
ffi.lib.LLVMPY_GetTripleParts.argtypes = [c_char_p, POINTER(c_char_p),
POINTER(c_char_p), POINTER(c_char_p),
POINTER(c_char_p)]
ffi.lib.LLVMPY_GetHostCPUFeatures.argtypes = [POINTER(c_char_p)]
ffi.lib.LLVMPY_GetHostCPUFeatures.restype = c_int
ffi.lib.LLVMPY_GetDefaultTargetTriple.argtypes = [POINTER(c_char_p)]
ffi.lib.LLVMPY_GetHostCPUName.argtypes = [POINTER(c_char_p)]
ffi.lib.LLVMPY_GetTripleObjectFormat.argtypes = [c_char_p]
ffi.lib.LLVMPY_GetTripleObjectFormat.restype = c_int
ffi.lib.LLVMPY_CreateTargetData.argtypes = [c_char_p]
ffi.lib.LLVMPY_CreateTargetData.restype = ffi.LLVMTargetDataRef
ffi.lib.LLVMPY_CopyStringRepOfTargetData.argtypes = [
ffi.LLVMTargetDataRef,
POINTER(c_char_p),
]
ffi.lib.LLVMPY_DisposeTargetData.argtypes = [
ffi.LLVMTargetDataRef,
]
ffi.lib.LLVMPY_ABISizeOfType.argtypes = [ffi.LLVMTargetDataRef,
ffi.LLVMTypeRef]
ffi.lib.LLVMPY_ABISizeOfType.restype = c_longlong
ffi.lib.LLVMPY_OffsetOfElement.argtypes = [ffi.LLVMTargetDataRef,
ffi.LLVMTypeRef,
c_int]
ffi.lib.LLVMPY_OffsetOfElement.restype = c_longlong
ffi.lib.LLVMPY_ABIAlignmentOfType.argtypes = [ffi.LLVMTargetDataRef,
ffi.LLVMTypeRef]
ffi.lib.LLVMPY_ABIAlignmentOfType.restype = c_longlong
ffi.lib.LLVMPY_GetTargetFromTriple.argtypes = [c_char_p, POINTER(c_char_p)]
ffi.lib.LLVMPY_GetTargetFromTriple.restype = ffi.LLVMTargetRef
ffi.lib.LLVMPY_GetTargetName.argtypes = [ffi.LLVMTargetRef]
ffi.lib.LLVMPY_GetTargetName.restype = c_char_p
ffi.lib.LLVMPY_GetTargetDescription.argtypes = [ffi.LLVMTargetRef]
ffi.lib.LLVMPY_GetTargetDescription.restype = c_char_p
ffi.lib.LLVMPY_CreateTargetMachine.argtypes = [
ffi.LLVMTargetRef,
# Triple
c_char_p,
# CPU
c_char_p,
# Features
c_char_p,
# OptLevel
c_int,
# Reloc
c_char_p,
# CodeModel
c_char_p,
# PrintMC
c_int,
# JIT
c_int,
# ABIName
c_char_p,
]
ffi.lib.LLVMPY_CreateTargetMachine.restype = ffi.LLVMTargetMachineRef
ffi.lib.LLVMPY_DisposeTargetMachine.argtypes = [ffi.LLVMTargetMachineRef]
ffi.lib.LLVMPY_GetTargetMachineTriple.argtypes = [ffi.LLVMTargetMachineRef,
POINTER(c_char_p)]
ffi.lib.LLVMPY_SetTargetMachineAsmVerbosity.argtypes = [
ffi.LLVMTargetMachineRef, c_int]
ffi.lib.LLVMPY_AddAnalysisPasses.argtypes = [
ffi.LLVMTargetMachineRef,
ffi.LLVMPassManagerRef,
]
ffi.lib.LLVMPY_TargetMachineEmitToMemory.argtypes = [
ffi.LLVMTargetMachineRef,
ffi.LLVMModuleRef,
c_int,
POINTER(c_char_p),
]
ffi.lib.LLVMPY_TargetMachineEmitToMemory.restype = ffi.LLVMMemoryBufferRef
ffi.lib.LLVMPY_GetBufferStart.argtypes = [ffi.LLVMMemoryBufferRef]
ffi.lib.LLVMPY_GetBufferStart.restype = c_void_p
ffi.lib.LLVMPY_GetBufferSize.argtypes = [ffi.LLVMMemoryBufferRef]
ffi.lib.LLVMPY_GetBufferSize.restype = c_size_t
ffi.lib.LLVMPY_DisposeMemoryBuffer.argtypes = [ffi.LLVMMemoryBufferRef]
ffi.lib.LLVMPY_CreateTargetMachineData.argtypes = [
ffi.LLVMTargetMachineRef,
]
ffi.lib.LLVMPY_CreateTargetMachineData.restype = ffi.LLVMTargetDataRef

View File

@@ -0,0 +1,267 @@
from ctypes import c_int, c_bool, c_void_p, c_uint64, c_uint, POINTER
import enum
from llvmlite import ir
from llvmlite.binding import ffi
class TypeKind(enum.IntEnum):
# The LLVMTypeKind enum from llvm-c/Core.h
void = 0
half = 1
float = 2
double = 3
x86_fp80 = 4
fp128 = 5
ppc_fp128 = 6
label = 7
integer = 8
function = 9
struct = 10
array = 11
pointer = 12
vector = 13
metadata = 14
x86_mmx = 15
token = 16
scalable_vector = 17
bfloat = 18
x86_amx = 19
_TypeKindToIRType = {
# All TypeKind here must have a TypeRef.as_ir() implementation
TypeKind.void: ir.VoidType,
TypeKind.half: ir.HalfType,
TypeKind.float: ir.FloatType,
TypeKind.double: ir.DoubleType,
TypeKind.integer: ir.IntType,
TypeKind.function: ir.FunctionType,
TypeKind.pointer: ir.PointerType,
TypeKind.array: ir.ArrayType,
TypeKind.vector: ir.VectorType,
TypeKind.struct: ir.LiteralStructType,
}
class TypeRef(ffi.ObjectRef):
"""A weak reference to a LLVM type
"""
@property
def name(self):
"""
Get type name
"""
return ffi.ret_string(ffi.lib.LLVMPY_GetTypeName(self))
@property
def is_struct(self):
"""
Returns true if the type is a struct type.
"""
return ffi.lib.LLVMPY_TypeIsStruct(self)
@property
def is_pointer(self):
"""
Returns true if the type is a pointer type.
"""
return ffi.lib.LLVMPY_TypeIsPointer(self)
@property
def is_array(self):
"""
Returns true if the type is an array type.
"""
return ffi.lib.LLVMPY_TypeIsArray(self)
@property
def is_vector(self):
"""
Returns true if the type is a vector type.
"""
return ffi.lib.LLVMPY_TypeIsVector(self)
@property
def is_function(self):
"""
Returns true if the type is a function type.
"""
return ffi.lib.LLVMPY_TypeIsFunction(self)
@property
def is_function_vararg(self):
"""
Returns true if a function type accepts a variable number of arguments.
When the type is not a function, raises exception.
"""
if self.type_kind != TypeKind.function:
raise ValueError("Type {} is not a function".format(self))
return ffi.lib.LLVMPY_IsFunctionVararg(self)
@property
def elements(self):
"""
Returns iterator over enclosing types
"""
if self.is_pointer:
raise ValueError("Type {} doesn't contain elements.".format(self))
return _TypeListIterator(ffi.lib.LLVMPY_ElementIter(self))
@property
def element_count(self):
"""
Returns the number of elements in an array or a vector. For scalable
vectors, returns minimum number of elements. When the type is neither
an array nor a vector, raises exception.
"""
if not self.is_array and not self.is_vector:
raise ValueError("Type {} is not an array nor vector".format(self))
return ffi.lib.LLVMPY_GetTypeElementCount(self)
@property
def type_width(self):
"""
Return the basic size of this type if it is a primitive type. These are
fixed by LLVM and are not target-dependent.
This will return zero if the type does not have a size or is not a
primitive type.
If this is a scalable vector type, the scalable property will be set and
the runtime size will be a positive integer multiple of the base size.
Note that this may not reflect the size of memory allocated for an
instance of the type or the number of bytes that are written when an
instance of the type is stored to memory.
"""
return ffi.lib.LLVMPY_GetTypeBitWidth(self)
@property
def type_kind(self):
"""
Returns the LLVMTypeKind enumeration of this type.
"""
return TypeKind(ffi.lib.LLVMPY_GetTypeKind(self))
@property
def is_packed_struct(self):
return ffi.lib.LLVMPY_IsPackedStruct(self)
@property
def is_literal_struct(self):
return ffi.lib.LLVMPY_IsLiteralStruct(self)
@property
def is_opaque_struct(self):
return ffi.lib.LLVMPY_IsOpaqueStruct(self)
def get_function_parameters(self) -> tuple["TypeRef"]:
nparams = ffi.lib.LLVMPY_CountParamTypes(self)
if nparams > 0:
out_buffer = (ffi.LLVMTypeRef * nparams)(None)
ffi.lib.LLVMPY_GetParamTypes(self, out_buffer)
return tuple(map(TypeRef, out_buffer))
else:
return ()
def get_function_return(self) -> "TypeRef":
return TypeRef(ffi.lib.LLVMPY_GetReturnType(self))
def as_ir(self, ir_ctx: ir.Context) -> ir.Type:
"""Convert into a ``llvmlite.ir.Type``.
"""
try:
cls = _TypeKindToIRType[self.type_kind]
except KeyError:
msg = f"as_ir() unsupported for TypeRef of {self.type_kind}"
raise TypeError(msg)
else:
return cls.from_llvm(self, ir_ctx)
def __str__(self):
return ffi.ret_string(ffi.lib.LLVMPY_PrintType(self))
class _TypeIterator(ffi.ObjectRef):
def __next__(self):
vp = self._next()
if vp:
return TypeRef(vp)
else:
raise StopIteration
next = __next__
def __iter__(self):
return self
class _TypeListIterator(_TypeIterator):
def _dispose(self):
self._capi.LLVMPY_DisposeElementIter(self)
def _next(self):
return ffi.lib.LLVMPY_ElementIterNext(self)
# FFI
ffi.lib.LLVMPY_PrintType.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_PrintType.restype = c_void_p
ffi.lib.LLVMPY_TypeIsPointer.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_TypeIsPointer.restype = c_bool
ffi.lib.LLVMPY_TypeIsArray.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_TypeIsArray.restype = c_bool
ffi.lib.LLVMPY_TypeIsVector.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_TypeIsVector.restype = c_bool
ffi.lib.LLVMPY_TypeIsStruct.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_TypeIsStruct.restype = c_bool
ffi.lib.LLVMPY_TypeIsFunction.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_TypeIsFunction.restype = c_bool
ffi.lib.LLVMPY_IsPackedStruct.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_IsPackedStruct.restype = c_bool
ffi.lib.LLVMPY_IsOpaqueStruct.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_IsOpaqueStruct.restype = c_bool
ffi.lib.LLVMPY_IsLiteralStruct.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_IsLiteralStruct.restype = c_bool
ffi.lib.LLVMPY_GetReturnType.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_GetReturnType.restype = ffi.LLVMTypeRef
ffi.lib.LLVMPY_CountParamTypes.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_CountParamTypes.restype = c_uint
ffi.lib.LLVMPY_GetParamTypes.argtypes = [ffi.LLVMTypeRef,
POINTER(ffi.LLVMTypeRef)]
ffi.lib.LLVMPY_GetParamTypes.restype = None
ffi.lib.LLVMPY_IsFunctionVararg.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_IsFunctionVararg.restype = c_bool
ffi.lib.LLVMPY_GetTypeKind.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_GetTypeKind.restype = c_int
ffi.lib.LLVMPY_GetTypeElementCount.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_GetTypeElementCount.restype = c_int
ffi.lib.LLVMPY_GetTypeBitWidth.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_GetTypeBitWidth.restype = c_uint64
ffi.lib.LLVMPY_ElementIter.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_ElementIter.restype = ffi.LLVMElementIterator
ffi.lib.LLVMPY_ElementIterNext.argtypes = [ffi.LLVMElementIterator]
ffi.lib.LLVMPY_ElementIterNext.restype = ffi.LLVMTypeRef
ffi.lib.LLVMPY_DisposeElementIter.argtypes = [ffi.LLVMElementIterator]

View File

@@ -0,0 +1,632 @@
from ctypes import (POINTER, byref, cast, c_char_p, c_double, c_int, c_size_t,
c_uint, c_uint64, c_bool, c_void_p)
import enum
from llvmlite.binding import ffi
from llvmlite.binding.common import _decode_string, _encode_string
from llvmlite.binding.typeref import TypeRef
class Linkage(enum.IntEnum):
# The LLVMLinkage enum from llvm-c/Core.h
external = 0
available_externally = 1
linkonce_any = 2
linkonce_odr = 3
linkonce_odr_autohide = 4
weak_any = 5
weak_odr = 6
appending = 7
internal = 8
private = 9
dllimport = 10
dllexport = 11
external_weak = 12
ghost = 13
common = 14
linker_private = 15
linker_private_weak = 16
class Visibility(enum.IntEnum):
# The LLVMVisibility enum from llvm-c/Core.h
default = 0
hidden = 1
protected = 2
class StorageClass(enum.IntEnum):
# The LLVMDLLStorageClass enum from llvm-c/Core.h
default = 0
dllimport = 1
dllexport = 2
class ValueKind(enum.IntEnum):
# The LLVMValueKind enum from llvm-c/Core.h
argument = 0
basic_block = 1
memory_use = 2
memory_def = 3
memory_phi = 4
function = 5
global_alias = 6
global_ifunc = 7
global_variable = 8
block_address = 9
constant_expr = 10
constant_array = 11
constant_struct = 12
constant_vector = 13
undef_value = 14
constant_aggregate_zero = 15
constant_data_array = 16
constant_data_vector = 17
constant_int = 18
constant_fp = 19
constant_pointer_null = 20
constant_token_none = 21
metadata_as_value = 22
inline_asm = 23
instruction = 24
poison_value = 25
class ValueRef(ffi.ObjectRef):
"""A weak reference to a LLVM value.
"""
def __init__(self, ptr, kind, parents):
self._kind = kind
self._parents = parents
ffi.ObjectRef.__init__(self, ptr)
def __str__(self):
with ffi.OutputString() as outstr:
ffi.lib.LLVMPY_PrintValueToString(self, outstr)
return str(outstr)
@property
def module(self):
"""
The module this function or global variable value was obtained from.
"""
return self._parents.get('module')
@property
def function(self):
"""
The function this argument or basic block value was obtained from.
"""
return self._parents.get('function')
@property
def block(self):
"""
The block this instruction value was obtained from.
"""
return self._parents.get('block')
@property
def instruction(self):
"""
The instruction this operand value was obtained from.
"""
return self._parents.get('instruction')
@property
def is_global(self):
return self._kind == 'global'
@property
def is_function(self):
return self._kind == 'function'
@property
def is_block(self):
return self._kind == 'block'
@property
def is_argument(self):
return self._kind == 'argument'
@property
def is_instruction(self):
return self._kind == 'instruction'
@property
def is_operand(self):
return self._kind == 'operand'
@property
def is_constant(self):
return bool(ffi.lib.LLVMPY_IsConstant(self))
@property
def value_kind(self):
return ValueKind(ffi.lib.LLVMPY_GetValueKind(self))
@property
def name(self):
return _decode_string(ffi.lib.LLVMPY_GetValueName(self))
@name.setter
def name(self, val):
ffi.lib.LLVMPY_SetValueName(self, _encode_string(val))
@property
def linkage(self):
return Linkage(ffi.lib.LLVMPY_GetLinkage(self))
@linkage.setter
def linkage(self, value):
if not isinstance(value, Linkage):
value = Linkage[value]
ffi.lib.LLVMPY_SetLinkage(self, value)
@property
def visibility(self):
return Visibility(ffi.lib.LLVMPY_GetVisibility(self))
@visibility.setter
def visibility(self, value):
if not isinstance(value, Visibility):
value = Visibility[value]
ffi.lib.LLVMPY_SetVisibility(self, value)
@property
def storage_class(self):
return StorageClass(ffi.lib.LLVMPY_GetDLLStorageClass(self))
@storage_class.setter
def storage_class(self, value):
if not isinstance(value, StorageClass):
value = StorageClass[value]
ffi.lib.LLVMPY_SetDLLStorageClass(self, value)
def add_function_attribute(self, attr):
"""Only works on function value
Parameters
-----------
attr : str
attribute name
"""
if not self.is_function:
raise ValueError('expected function value, got %s' % (self._kind,))
attrname = str(attr)
attrval = ffi.lib.LLVMPY_GetEnumAttributeKindForName(
_encode_string(attrname), len(attrname))
if attrval == 0:
raise ValueError('no such attribute {!r}'.format(attrname))
ffi.lib.LLVMPY_AddFunctionAttr(self, attrval)
@property
def type(self):
"""
This value's LLVM type.
"""
# XXX what does this return?
return TypeRef(ffi.lib.LLVMPY_TypeOf(self))
@property
def global_value_type(self):
"""
Uses ``LLVMGlobalGetValueType()``.
Needed for opaque pointers in globals.
> For globals, use getValueType().
See https://llvm.org/docs/OpaquePointers.html#migration-instructions
"""
assert self.is_global or self.is_function
return TypeRef(ffi.lib.LLVMPY_GlobalGetValueType(self))
@property
def is_declaration(self):
"""
Whether this value (presumably global) is defined in the current
module.
"""
if not (self.is_global or self.is_function):
raise ValueError('expected global or function value, got %s'
% (self._kind,))
return ffi.lib.LLVMPY_IsDeclaration(self)
@property
def attributes(self):
"""
Return an iterator over this value's attributes.
The iterator will yield a string for each attribute.
"""
itr = iter(())
if self.is_function:
it = ffi.lib.LLVMPY_FunctionAttributesIter(self)
itr = _AttributeListIterator(it)
elif self.is_instruction:
if self.opcode == 'call':
it = ffi.lib.LLVMPY_CallInstAttributesIter(self)
itr = _AttributeListIterator(it)
elif self.opcode == 'invoke':
it = ffi.lib.LLVMPY_InvokeInstAttributesIter(self)
itr = _AttributeListIterator(it)
elif self.is_global:
it = ffi.lib.LLVMPY_GlobalAttributesIter(self)
itr = _AttributeSetIterator(it)
elif self.is_argument:
it = ffi.lib.LLVMPY_ArgumentAttributesIter(self)
itr = _AttributeSetIterator(it)
return itr
@property
def blocks(self):
"""
Return an iterator over this function's blocks.
The iterator will yield a ValueRef for each block.
"""
if not self.is_function:
raise ValueError('expected function value, got %s' % (self._kind,))
it = ffi.lib.LLVMPY_FunctionBlocksIter(self)
parents = self._parents.copy()
parents.update(function=self)
return _BlocksIterator(it, parents)
@property
def arguments(self):
"""
Return an iterator over this function's arguments.
The iterator will yield a ValueRef for each argument.
"""
if not self.is_function:
raise ValueError('expected function value, got %s' % (self._kind,))
it = ffi.lib.LLVMPY_FunctionArgumentsIter(self)
parents = self._parents.copy()
parents.update(function=self)
return _ArgumentsIterator(it, parents)
@property
def instructions(self):
"""
Return an iterator over this block's instructions.
The iterator will yield a ValueRef for each instruction.
"""
if not self.is_block:
raise ValueError('expected block value, got %s' % (self._kind,))
it = ffi.lib.LLVMPY_BlockInstructionsIter(self)
parents = self._parents.copy()
parents.update(block=self)
return _InstructionsIterator(it, parents)
@property
def operands(self):
"""
Return an iterator over this instruction's operands.
The iterator will yield a ValueRef for each operand.
"""
if not self.is_instruction:
raise ValueError('expected instruction value, got %s'
% (self._kind,))
it = ffi.lib.LLVMPY_InstructionOperandsIter(self)
parents = self._parents.copy()
parents.update(instruction=self)
return _OperandsIterator(it, parents)
@property
def opcode(self):
if not self.is_instruction:
raise ValueError('expected instruction value, got %s'
% (self._kind,))
return ffi.ret_string(ffi.lib.LLVMPY_GetOpcodeName(self))
@property
def incoming_blocks(self):
"""
Return an iterator over this phi instruction's incoming blocks.
The iterator will yield a ValueRef for each block.
"""
if not self.is_instruction or self.opcode != 'phi':
raise ValueError('expected phi instruction value, got %s'
% (self._kind,))
it = ffi.lib.LLVMPY_PhiIncomingBlocksIter(self)
parents = self._parents.copy()
parents.update(instruction=self)
return _IncomingBlocksIterator(it, parents)
def get_constant_value(self, signed_int=False, round_fp=False):
"""
Return the constant value, either as a literal (when supported)
or as a string.
Parameters
-----------
signed_int : bool
if True and the constant is an integer, returns a signed version
round_fp : bool
if True and the constant is a floating point value, rounds the
result upon accuracy loss (e.g., when querying an fp128 value).
By default, raises an exception on accuracy loss
"""
if not self.is_constant:
raise ValueError('expected constant value, got %s'
% (self._kind,))
if self.value_kind == ValueKind.constant_int:
# Python integers are also arbitrary-precision
little_endian = c_bool(False)
words = ffi.lib.LLVMPY_GetConstantIntNumWords(self)
ptr = ffi.lib.LLVMPY_GetConstantIntRawValue(
self, byref(little_endian))
asbytes = bytes(cast(ptr, POINTER(c_uint64 * words)).contents)
return int.from_bytes(
asbytes,
('little' if little_endian.value else 'big'),
signed=signed_int,
)
elif self.value_kind == ValueKind.constant_fp:
# Convert floating-point values to double-precision (Python float)
accuracy_loss = c_bool(False)
value = ffi.lib.LLVMPY_GetConstantFPValue(self,
byref(accuracy_loss))
if accuracy_loss.value and not round_fp:
raise ValueError(
'Accuracy loss encountered in conversion of constant '
f'value {str(self)}')
return value
# Otherwise, return the IR string
return str(self)
class _ValueIterator(ffi.ObjectRef):
kind = None # derived classes must specify the Value kind value
# as class attribute
def __init__(self, ptr, parents):
ffi.ObjectRef.__init__(self, ptr)
# Keep parent objects (module, function, etc) alive
self._parents = parents
if self.kind is None:
raise NotImplementedError('%s must specify kind attribute'
% (type(self).__name__,))
def __next__(self):
vp = self._next()
if vp:
return ValueRef(vp, self.kind, self._parents)
else:
raise StopIteration
next = __next__
def __iter__(self):
return self
class _AttributeIterator(ffi.ObjectRef):
def __next__(self):
vp = self._next()
if vp:
return vp
else:
raise StopIteration
next = __next__
def __iter__(self):
return self
class _AttributeListIterator(_AttributeIterator):
def _dispose(self):
self._capi.LLVMPY_DisposeAttributeListIter(self)
def _next(self):
return ffi.ret_bytes(ffi.lib.LLVMPY_AttributeListIterNext(self))
class _AttributeSetIterator(_AttributeIterator):
def _dispose(self):
self._capi.LLVMPY_DisposeAttributeSetIter(self)
def _next(self):
return ffi.ret_bytes(ffi.lib.LLVMPY_AttributeSetIterNext(self))
class _BlocksIterator(_ValueIterator):
kind = 'block'
def _dispose(self):
self._capi.LLVMPY_DisposeBlocksIter(self)
def _next(self):
return ffi.lib.LLVMPY_BlocksIterNext(self)
class _ArgumentsIterator(_ValueIterator):
kind = 'argument'
def _dispose(self):
self._capi.LLVMPY_DisposeArgumentsIter(self)
def _next(self):
return ffi.lib.LLVMPY_ArgumentsIterNext(self)
class _InstructionsIterator(_ValueIterator):
kind = 'instruction'
def _dispose(self):
self._capi.LLVMPY_DisposeInstructionsIter(self)
def _next(self):
return ffi.lib.LLVMPY_InstructionsIterNext(self)
class _OperandsIterator(_ValueIterator):
kind = 'operand'
def _dispose(self):
self._capi.LLVMPY_DisposeOperandsIter(self)
def _next(self):
return ffi.lib.LLVMPY_OperandsIterNext(self)
class _IncomingBlocksIterator(_ValueIterator):
kind = 'block'
def _dispose(self):
self._capi.LLVMPY_DisposeIncomingBlocksIter(self)
def _next(self):
return ffi.lib.LLVMPY_IncomingBlocksIterNext(self)
# FFI
ffi.lib.LLVMPY_PrintValueToString.argtypes = [
ffi.LLVMValueRef,
POINTER(c_char_p)
]
ffi.lib.LLVMPY_GetGlobalParent.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetGlobalParent.restype = ffi.LLVMModuleRef
ffi.lib.LLVMPY_GetValueName.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetValueName.restype = c_char_p
ffi.lib.LLVMPY_SetValueName.argtypes = [ffi.LLVMValueRef, c_char_p]
ffi.lib.LLVMPY_TypeOf.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_TypeOf.restype = ffi.LLVMTypeRef
ffi.lib.LLVMPY_GlobalGetValueType.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GlobalGetValueType.restype = ffi.LLVMTypeRef
ffi.lib.LLVMPY_GetTypeName.argtypes = [ffi.LLVMTypeRef]
ffi.lib.LLVMPY_GetTypeName.restype = c_void_p
ffi.lib.LLVMPY_GetLinkage.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetLinkage.restype = c_int
ffi.lib.LLVMPY_SetLinkage.argtypes = [ffi.LLVMValueRef, c_int]
ffi.lib.LLVMPY_GetVisibility.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetVisibility.restype = c_int
ffi.lib.LLVMPY_SetVisibility.argtypes = [ffi.LLVMValueRef, c_int]
ffi.lib.LLVMPY_GetDLLStorageClass.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetDLLStorageClass.restype = c_int
ffi.lib.LLVMPY_SetDLLStorageClass.argtypes = [ffi.LLVMValueRef, c_int]
ffi.lib.LLVMPY_GetEnumAttributeKindForName.argtypes = [c_char_p, c_size_t]
ffi.lib.LLVMPY_GetEnumAttributeKindForName.restype = c_uint
ffi.lib.LLVMPY_AddFunctionAttr.argtypes = [ffi.LLVMValueRef, c_uint]
ffi.lib.LLVMPY_IsDeclaration.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_IsDeclaration.restype = c_int
ffi.lib.LLVMPY_FunctionAttributesIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_FunctionAttributesIter.restype = ffi.LLVMAttributeListIterator
ffi.lib.LLVMPY_CallInstAttributesIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_CallInstAttributesIter.restype = ffi.LLVMAttributeListIterator
ffi.lib.LLVMPY_InvokeInstAttributesIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_InvokeInstAttributesIter.restype = ffi.LLVMAttributeListIterator
ffi.lib.LLVMPY_GlobalAttributesIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GlobalAttributesIter.restype = ffi.LLVMAttributeSetIterator
ffi.lib.LLVMPY_ArgumentAttributesIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_ArgumentAttributesIter.restype = ffi.LLVMAttributeSetIterator
ffi.lib.LLVMPY_FunctionBlocksIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_FunctionBlocksIter.restype = ffi.LLVMBlocksIterator
ffi.lib.LLVMPY_FunctionArgumentsIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_FunctionArgumentsIter.restype = ffi.LLVMArgumentsIterator
ffi.lib.LLVMPY_BlockInstructionsIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_BlockInstructionsIter.restype = ffi.LLVMInstructionsIterator
ffi.lib.LLVMPY_InstructionOperandsIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_InstructionOperandsIter.restype = ffi.LLVMOperandsIterator
ffi.lib.LLVMPY_PhiIncomingBlocksIter.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_PhiIncomingBlocksIter.restype = ffi.LLVMIncomingBlocksIterator
ffi.lib.LLVMPY_DisposeAttributeListIter.argtypes = [
ffi.LLVMAttributeListIterator]
ffi.lib.LLVMPY_DisposeAttributeSetIter.argtypes = [ffi.LLVMAttributeSetIterator]
ffi.lib.LLVMPY_DisposeBlocksIter.argtypes = [ffi.LLVMBlocksIterator]
ffi.lib.LLVMPY_DisposeInstructionsIter.argtypes = [ffi.LLVMInstructionsIterator]
ffi.lib.LLVMPY_DisposeOperandsIter.argtypes = [ffi.LLVMOperandsIterator]
ffi.lib.LLVMPY_DisposeIncomingBlocksIter.argtypes = [
ffi.LLVMIncomingBlocksIterator]
ffi.lib.LLVMPY_AttributeListIterNext.argtypes = [ffi.LLVMAttributeListIterator]
ffi.lib.LLVMPY_AttributeListIterNext.restype = c_void_p
ffi.lib.LLVMPY_AttributeSetIterNext.argtypes = [ffi.LLVMAttributeSetIterator]
ffi.lib.LLVMPY_AttributeSetIterNext.restype = c_void_p
ffi.lib.LLVMPY_BlocksIterNext.argtypes = [ffi.LLVMBlocksIterator]
ffi.lib.LLVMPY_BlocksIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_ArgumentsIterNext.argtypes = [ffi.LLVMArgumentsIterator]
ffi.lib.LLVMPY_ArgumentsIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_InstructionsIterNext.argtypes = [ffi.LLVMInstructionsIterator]
ffi.lib.LLVMPY_InstructionsIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_OperandsIterNext.argtypes = [ffi.LLVMOperandsIterator]
ffi.lib.LLVMPY_OperandsIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_IncomingBlocksIterNext.argtypes = [
ffi.LLVMIncomingBlocksIterator]
ffi.lib.LLVMPY_IncomingBlocksIterNext.restype = ffi.LLVMValueRef
ffi.lib.LLVMPY_GetOpcodeName.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetOpcodeName.restype = c_void_p
ffi.lib.LLVMPY_IsConstant.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_IsConstant.restype = c_bool
ffi.lib.LLVMPY_GetValueKind.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetValueKind.restype = c_int
ffi.lib.LLVMPY_GetConstantIntRawValue.argtypes = [ffi.LLVMValueRef,
POINTER(c_bool)]
ffi.lib.LLVMPY_GetConstantIntRawValue.restype = POINTER(c_uint64)
ffi.lib.LLVMPY_GetConstantIntNumWords.argtypes = [ffi.LLVMValueRef]
ffi.lib.LLVMPY_GetConstantIntNumWords.restype = c_uint
ffi.lib.LLVMPY_GetConstantFPValue.argtypes = [ffi.LLVMValueRef,
POINTER(c_bool)]
ffi.lib.LLVMPY_GetConstantFPValue.restype = c_double

View File

@@ -0,0 +1,11 @@
"""
This subpackage implements the LLVM IR classes in pure python
"""
from .types import *
from .values import *
from .module import *
from .builder import *
from .instructions import *
from .transforms import *
from .context import Context, global_context

View File

@@ -0,0 +1,80 @@
from collections import defaultdict
class DuplicatedNameError(NameError):
pass
class NameScope(object):
def __init__(self):
self._useset = set([''])
self._basenamemap = defaultdict(int)
def is_used(self, name):
return name in self._useset
def register(self, name, deduplicate=False):
if deduplicate:
name = self.deduplicate(name)
elif self.is_used(name):
raise DuplicatedNameError(name)
self._useset.add(name)
return name
def deduplicate(self, name):
basename = name
while self.is_used(name):
ident = self._basenamemap[basename] + 1
self._basenamemap[basename] = ident
name = "{0}.{1}".format(basename, ident)
return name
def get_child(self):
return type(self)(parent=self)
class _StrCaching(object):
def _clear_string_cache(self):
try:
del self.__cached_str
except AttributeError:
pass
def __str__(self):
try:
return self.__cached_str
except AttributeError:
s = self.__cached_str = self._to_string()
return s
class _StringReferenceCaching(object):
def get_reference(self):
try:
return self.__cached_refstr
except AttributeError:
s = self.__cached_refstr = self._get_reference()
return s
class _HasMetadata(object):
def set_metadata(self, name, node):
"""
Attach unnamed metadata *node* to the metadata slot *name* of this
value.
"""
self.metadata[name] = node
def _stringify_metadata(self, leading_comma=False):
if self.metadata:
buf = []
if leading_comma:
buf.append("")
buf += ["!{0} {1}".format(k, v.get_reference())
for k, v in self.metadata.items()]
return ', '.join(buf)
else:
return ''

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
from llvmlite.ir import _utils
from llvmlite.ir import types
class Context(object):
def __init__(self):
self.scope = _utils.NameScope()
self.identified_types = {}
def get_identified_type(self, name, packed=False):
if name not in self.identified_types:
self.scope.register(name)
ty = types.IdentifiedStructType(self, name, packed)
self.identified_types[name] = ty
else:
ty = self.identified_types[name]
return ty
global_context = Context()

View File

@@ -0,0 +1,920 @@
"""
Implementation of LLVM IR instructions.
"""
from llvmlite.ir import types
from llvmlite.ir.values import (Block, Function, Value, NamedValue, Constant,
MetaDataArgument, MetaDataString, AttributeSet,
Undefined, ArgumentAttributes)
from llvmlite.ir._utils import _HasMetadata
class Instruction(NamedValue, _HasMetadata):
def __init__(self, parent, typ, opname, operands, name='', flags=()):
super(Instruction, self).__init__(parent, typ, name=name)
assert isinstance(parent, Block)
assert isinstance(flags, (tuple, list))
self.opname = opname
self.operands = operands
self.flags = list(flags)
self.metadata = {}
@property
def function(self):
return self.parent.function
@property
def module(self):
return self.parent.function.module
def descr(self, buf):
opname = self.opname
if self.flags:
opname = ' '.join([opname] + self.flags)
operands = ', '.join([op.get_reference() for op in self.operands])
typ = self.type
metadata = self._stringify_metadata(leading_comma=True)
buf.append("{0} {1} {2}{3}\n"
.format(opname, typ, operands, metadata))
def replace_usage(self, old, new):
if old in self.operands:
ops = []
for op in self.operands:
ops.append(new if op is old else op)
self.operands = tuple(ops)
self._clear_string_cache()
def __repr__(self):
return "<ir.%s %r of type '%s', opname %r, operands %r>" % (
self.__class__.__name__, self.name, self.type,
self.opname, self.operands)
class CallInstrAttributes(AttributeSet):
_known = frozenset(['convergent', 'noreturn', 'nounwind', 'readonly',
'readnone', 'noinline', 'alwaysinline'])
TailMarkerOptions = frozenset(['tail', 'musttail', 'notail'])
class FastMathFlags(AttributeSet):
_known = frozenset(['fast', 'nnan', 'ninf', 'nsz', 'arcp', 'contract',
'afn', 'reassoc'])
class CallInstr(Instruction):
def __init__(self, parent, func, args, name='', cconv=None, tail=None,
fastmath=(), attrs=(), arg_attrs=None):
self.cconv = (func.calling_convention
if cconv is None and isinstance(func, Function)
else cconv)
# For backwards compatibility with previous API of accepting a "truthy"
# value for a hint to the optimizer to potentially tail optimize.
if isinstance(tail, str) and tail in TailMarkerOptions:
pass
elif tail:
tail = "tail"
else:
tail = ""
self.tail = tail
self.fastmath = FastMathFlags(fastmath)
self.attributes = CallInstrAttributes(attrs)
self.arg_attributes = {}
if arg_attrs:
for idx, attrs in arg_attrs.items():
if not (0 <= idx < len(args)):
raise ValueError("Invalid argument index {}"
.format(idx))
self.arg_attributes[idx] = ArgumentAttributes(attrs)
# Fix and validate arguments
args = list(args)
for i in range(len(func.function_type.args)):
arg = args[i]
expected_type = func.function_type.args[i]
if (isinstance(expected_type, types.MetaDataType) and
arg.type != expected_type):
arg = MetaDataArgument(arg)
if arg.type != expected_type:
msg = ("Type of #{0} arg mismatch: {1} != {2}"
.format(1 + i, expected_type, arg.type))
raise TypeError(msg)
args[i] = arg
super(CallInstr, self).__init__(parent, func.function_type.return_type,
"call", [func] + list(args), name=name)
@property
def callee(self):
return self.operands[0]
@callee.setter
def callee(self, newcallee):
self.operands[0] = newcallee
@property
def args(self):
return self.operands[1:]
def replace_callee(self, newfunc):
if newfunc.function_type != self.callee.function_type:
raise TypeError("New function has incompatible type")
self.callee = newfunc
@property
def called_function(self):
"""The callee function"""
return self.callee
def _descr(self, buf, add_metadata):
def descr_arg(i, a):
if i in self.arg_attributes:
attrs = ' '.join(self.arg_attributes[i]._to_list(a.type)) + ' '
else:
attrs = ''
return '{0} {1}{2}'.format(a.type, attrs, a.get_reference())
args = ', '.join([descr_arg(i, a) for i, a in enumerate(self.args)])
fnty = self.callee.function_type
# Only print function type if variable-argument
if fnty.var_arg:
ty = fnty
# Otherwise, just print the return type.
else:
# Fastmath flag work only in this case
ty = fnty.return_type
callee_ref = "{0} {1}".format(ty, self.callee.get_reference())
if self.cconv:
callee_ref = "{0} {1}".format(self.cconv, callee_ref)
tail_marker = ""
if self.tail:
tail_marker = "{0} ".format(self.tail)
fn_attrs = ' ' + ' '.join(self.attributes._to_list(fnty.return_type))\
if self.attributes else ''
fm_attrs = ' ' + ' '.join(self.fastmath._to_list(fnty.return_type))\
if self.fastmath else ''
buf.append("{tail}{op}{fastmath} {callee}({args}){attr}{meta}\n".format(
tail=tail_marker,
op=self.opname,
callee=callee_ref,
fastmath=fm_attrs,
args=args,
attr=fn_attrs,
meta=(self._stringify_metadata(leading_comma=True)
if add_metadata else ""),
))
def descr(self, buf):
self._descr(buf, add_metadata=True)
class InvokeInstr(CallInstr):
def __init__(self, parent, func, args, normal_to, unwind_to, name='',
cconv=None, fastmath=(), attrs=(), arg_attrs=None):
assert isinstance(normal_to, Block)
assert isinstance(unwind_to, Block)
super(InvokeInstr, self).__init__(parent, func, args, name, cconv,
tail=False, fastmath=fastmath,
attrs=attrs, arg_attrs=arg_attrs)
self.opname = "invoke"
self.normal_to = normal_to
self.unwind_to = unwind_to
def descr(self, buf):
super(InvokeInstr, self)._descr(buf, add_metadata=False)
buf.append(" to label {0} unwind label {1}{metadata}\n".format(
self.normal_to.get_reference(),
self.unwind_to.get_reference(),
metadata=self._stringify_metadata(leading_comma=True),
))
class Terminator(Instruction):
def __init__(self, parent, opname, operands):
super(Terminator, self).__init__(parent, types.VoidType(), opname,
operands)
def descr(self, buf):
opname = self.opname
operands = ', '.join(["{0} {1}".format(op.type, op.get_reference())
for op in self.operands])
metadata = self._stringify_metadata(leading_comma=True)
buf.append("{0} {1}{2}".format(opname, operands, metadata))
class PredictableInstr(Instruction):
def set_weights(self, weights):
operands = [MetaDataString(self.module, "branch_weights")]
for w in weights:
if w < 0:
raise ValueError("branch weight must be a positive integer")
operands.append(Constant(types.IntType(32), w))
md = self.module.add_metadata(operands)
self.set_metadata("prof", md)
class Ret(Terminator):
def __init__(self, parent, opname, return_value=None):
operands = [return_value] if return_value is not None else []
super(Ret, self).__init__(parent, opname, operands)
@property
def return_value(self):
if self.operands:
return self.operands[0]
else:
return None
def descr(self, buf):
return_value = self.return_value
metadata = self._stringify_metadata(leading_comma=True)
if return_value is not None:
buf.append("{0} {1} {2}{3}\n"
.format(self.opname, return_value.type,
return_value.get_reference(),
metadata))
else:
buf.append("{0}{1}\n".format(self.opname, metadata))
class Branch(Terminator):
pass
class ConditionalBranch(PredictableInstr, Terminator):
pass
class IndirectBranch(PredictableInstr, Terminator):
def __init__(self, parent, opname, addr):
super(IndirectBranch, self).__init__(parent, opname, [addr])
self.destinations = []
@property
def address(self):
return self.operands[0]
def add_destination(self, block):
assert isinstance(block, Block)
self.destinations.append(block)
def descr(self, buf):
destinations = ["label {0}".format(blk.get_reference())
for blk in self.destinations]
buf.append("indirectbr {0} {1}, [{2}] {3}\n".format(
self.address.type,
self.address.get_reference(),
', '.join(destinations),
self._stringify_metadata(leading_comma=True),
))
class SwitchInstr(PredictableInstr, Terminator):
def __init__(self, parent, opname, val, default):
super(SwitchInstr, self).__init__(parent, opname, [val])
self.default = default
self.cases = []
@property
def value(self):
return self.operands[0]
def add_case(self, val, block):
assert isinstance(block, Block)
if not isinstance(val, Value):
val = Constant(self.value.type, val)
self.cases.append((val, block))
def descr(self, buf):
cases = ["{0} {1}, label {2}".format(val.type, val.get_reference(),
blk.get_reference())
for val, blk in self.cases]
buf.append("switch {0} {1}, label {2} [{3}] {4}\n".format(
self.value.type,
self.value.get_reference(),
self.default.get_reference(),
' '.join(cases),
self._stringify_metadata(leading_comma=True),
))
class Resume(Terminator):
pass
class SelectInstr(Instruction):
def __init__(self, parent, cond, lhs, rhs, name='', flags=()):
assert lhs.type == rhs.type
super(SelectInstr, self).__init__(parent, lhs.type, "select",
[cond, lhs, rhs], name=name,
flags=flags)
@property
def cond(self):
return self.operands[0]
@property
def lhs(self):
return self.operands[1]
@property
def rhs(self):
return self.operands[2]
def descr(self, buf):
buf.append("select {0} {1} {2}, {3} {4}, {5} {6} {7}\n".format(
' '.join(self.flags),
self.cond.type, self.cond.get_reference(),
self.lhs.type, self.lhs.get_reference(),
self.rhs.type, self.rhs.get_reference(),
self._stringify_metadata(leading_comma=True),
))
class CompareInstr(Instruction):
# Define the following in subclasses
OPNAME = 'invalid-compare'
VALID_OP = {}
def __init__(self, parent, op, lhs, rhs, name='', flags=[]):
if op not in self.VALID_OP:
raise ValueError("invalid comparison %r for %s" % (op, self.OPNAME))
for flag in flags:
if flag not in self.VALID_FLAG:
raise ValueError("invalid flag %r for %s" % (flag, self.OPNAME))
opname = self.OPNAME
if isinstance(lhs.type, types.VectorType):
typ = types.VectorType(types.IntType(1), lhs.type.count)
else:
typ = types.IntType(1)
super(CompareInstr, self).__init__(parent, typ,
opname, [lhs, rhs], flags=flags,
name=name)
self.op = op
def descr(self, buf):
buf.append("{opname}{flags} {op} {ty} {lhs}, {rhs} {meta}\n".format(
opname=self.opname,
flags=''.join(' ' + it for it in self.flags),
op=self.op,
ty=self.operands[0].type,
lhs=self.operands[0].get_reference(),
rhs=self.operands[1].get_reference(),
meta=self._stringify_metadata(leading_comma=True),
))
class ICMPInstr(CompareInstr):
OPNAME = 'icmp'
VALID_OP = {
'eq': 'equal',
'ne': 'not equal',
'ugt': 'unsigned greater than',
'uge': 'unsigned greater or equal',
'ult': 'unsigned less than',
'ule': 'unsigned less or equal',
'sgt': 'signed greater than',
'sge': 'signed greater or equal',
'slt': 'signed less than',
'sle': 'signed less or equal',
}
VALID_FLAG = set()
class FCMPInstr(CompareInstr):
OPNAME = 'fcmp'
VALID_OP = {
'false': 'no comparison, always returns false',
'oeq': 'ordered and equal',
'ogt': 'ordered and greater than',
'oge': 'ordered and greater than or equal',
'olt': 'ordered and less than',
'ole': 'ordered and less than or equal',
'one': 'ordered and not equal',
'ord': 'ordered (no nans)',
'ueq': 'unordered or equal',
'ugt': 'unordered or greater than',
'uge': 'unordered or greater than or equal',
'ult': 'unordered or less than',
'ule': 'unordered or less than or equal',
'une': 'unordered or not equal',
'uno': 'unordered (either nans)',
'true': 'no comparison, always returns true',
}
VALID_FLAG = {'nnan', 'ninf', 'nsz', 'arcp', 'contract', 'afn', 'reassoc',
'fast'}
class CastInstr(Instruction):
def __init__(self, parent, op, val, typ, name=''):
super(CastInstr, self).__init__(parent, typ, op, [val], name=name)
def descr(self, buf):
buf.append("{0} {1} {2} to {3} {4}\n".format(
self.opname,
self.operands[0].type,
self.operands[0].get_reference(),
self.type,
self._stringify_metadata(leading_comma=True),
))
class LoadInstr(Instruction):
def __init__(self, parent, ptr, name='', typ=None):
if typ is None:
if isinstance(ptr, AllocaInstr):
typ = ptr.allocated_type
# For compatibility with typed pointers. Eventually this should
# probably be removed (when typed pointers are fully removed).
elif not ptr.type.is_opaque:
typ = ptr.type.pointee
else:
raise ValueError("Load lacks type.")
super(LoadInstr, self).__init__(parent, typ, "load", [ptr], name=name)
self.align = None
def descr(self, buf):
[val] = self.operands
if self.align is not None:
align = ', align %d' % (self.align)
else:
align = ''
buf.append("load {0}, {1} {2}{3}{4}\n".format(
self.type,
val.type,
val.get_reference(),
align,
self._stringify_metadata(leading_comma=True),
))
class StoreInstr(Instruction):
def __init__(self, parent, val, ptr):
super(StoreInstr, self).__init__(parent, types.VoidType(), "store",
[val, ptr])
def descr(self, buf):
val, ptr = self.operands
if self.align is not None:
align = ', align %d' % (self.align)
else:
align = ''
buf.append("store {0} {1}, {2} {3}{4}{5}\n".format(
val.type,
val.get_reference(),
ptr.type,
ptr.get_reference(),
align,
self._stringify_metadata(leading_comma=True),
))
class LoadAtomicInstr(Instruction):
def __init__(self, parent, ptr, ordering, align, name='', typ=None):
if typ is None:
if isinstance(ptr, AllocaInstr):
typ = ptr.allocated_type
# For compatibility with typed pointers. Eventually this should
# probably be removed (when typed pointers are fully removed).
elif not ptr.type.is_opaque:
typ = ptr.type.pointee
else:
raise ValueError("Load atomic lacks type.")
super(LoadAtomicInstr, self).__init__(parent, typ, "load atomic",
[ptr], name=name)
self.ordering = ordering
self.align = align
def descr(self, buf):
[val] = self.operands
buf.append("load atomic {0}, {1} {2} {3}, align {4}{5}\n".format(
self.type,
val.type,
val.get_reference(),
self.ordering,
self.align,
self._stringify_metadata(leading_comma=True),
))
class StoreAtomicInstr(Instruction):
def __init__(self, parent, val, ptr, ordering, align):
super(StoreAtomicInstr, self).__init__(parent, types.VoidType(),
"store atomic", [val, ptr])
self.ordering = ordering
self.align = align
def descr(self, buf):
val, ptr = self.operands
buf.append("store atomic {0} {1}, {2} {3} {4}, align {5}{6}\n".format(
val.type,
val.get_reference(),
ptr.type,
ptr.get_reference(),
self.ordering,
self.align,
self._stringify_metadata(leading_comma=True),
))
class AllocaInstr(Instruction):
def __init__(self, parent, typ, count, name):
operands = [count] if count else ()
super(AllocaInstr, self).__init__(parent, typ.as_pointer(), "alloca",
operands, name)
self.allocated_type = typ
self.align = None
def descr(self, buf):
buf.append("{0} {1}".format(self.opname, self.allocated_type))
if self.operands:
op, = self.operands
buf.append(", {0} {1}".format(op.type, op.get_reference()))
if self.align is not None:
buf.append(", align {0}".format(self.align))
if self.metadata:
buf.append(self._stringify_metadata(leading_comma=True))
class GEPInstr(Instruction):
def __init__(self, parent, ptr, indices, inbounds, name,
source_etype=None):
if source_etype is not None:
typ = ptr.type
self.source_etype = source_etype
# For compatibility with typed pointers. Eventually this should
# probably be removed (when typed pointers are fully removed).
elif not ptr.type.is_opaque:
typ = ptr.type
lasttyp = None
lastaddrspace = 0
for i in indices:
lasttyp, typ = typ, typ.gep(i)
# inherit the addrspace from the last seen pointer
if isinstance(lasttyp, types.PointerType):
lastaddrspace = lasttyp.addrspace
if (not isinstance(typ, types.PointerType) and
isinstance(lasttyp, types.PointerType)):
typ = lasttyp
else:
typ = typ.as_pointer(lastaddrspace)
self.source_etype = ptr.type.pointee
else:
raise ValueError("GEP lacks type.")
super(GEPInstr, self).__init__(parent, typ, "getelementptr",
[ptr] + list(indices), name=name)
self.pointer = ptr
self.indices = indices
self.inbounds = inbounds
def descr(self, buf):
indices = ['{0} {1}'.format(i.type, i.get_reference())
for i in self.indices]
op = "getelementptr inbounds" if self.inbounds else "getelementptr"
buf.append("{0} {1}, {2} {3}, {4} {5}\n".format(
op,
self.source_etype,
self.pointer.type,
self.pointer.get_reference(),
', '.join(indices),
self._stringify_metadata(leading_comma=True),
))
class PhiInstr(Instruction):
def __init__(self, parent, typ, name, flags=()):
super(PhiInstr, self).__init__(parent, typ, "phi", (), name=name,
flags=flags)
self.incomings = []
def descr(self, buf):
incs = ', '.join('[{0}, {1}]'.format(v.get_reference(),
b.get_reference())
for v, b in self.incomings)
buf.append("phi {0} {1} {2} {3}\n".format(
' '.join(self.flags),
self.type,
incs,
self._stringify_metadata(leading_comma=True),
))
def add_incoming(self, value, block):
assert isinstance(block, Block)
self.incomings.append((value, block))
def replace_usage(self, old, new):
self.incomings = [((new if val is old else val), blk)
for (val, blk) in self.incomings]
class ExtractElement(Instruction):
def __init__(self, parent, vector, index, name=''):
if not isinstance(vector.type, types.VectorType):
raise TypeError("vector needs to be of VectorType.")
if not isinstance(index.type, types.IntType):
raise TypeError("index needs to be of IntType.")
typ = vector.type.element
super(ExtractElement, self).__init__(parent, typ, "extractelement",
[vector, index], name=name)
def descr(self, buf):
operands = ", ".join("{0} {1}".format(
op.type, op.get_reference()) for op in self.operands)
buf.append("{opname} {operands}\n".format(
opname=self.opname, operands=operands))
class InsertElement(Instruction):
def __init__(self, parent, vector, value, index, name=''):
if not isinstance(vector.type, types.VectorType):
raise TypeError("vector needs to be of VectorType.")
if not value.type == vector.type.element:
raise TypeError(
"value needs to be of type {} not {}.".format(
vector.type.element, value.type))
if not isinstance(index.type, types.IntType):
raise TypeError("index needs to be of IntType.")
typ = vector.type
super(InsertElement, self).__init__(parent, typ, "insertelement",
[vector, value, index], name=name)
def descr(self, buf):
operands = ", ".join("{0} {1}".format(
op.type, op.get_reference()) for op in self.operands)
buf.append("{opname} {operands}\n".format(
opname=self.opname, operands=operands))
class ShuffleVector(Instruction):
def __init__(self, parent, vector1, vector2, mask, name=''):
if not isinstance(vector1.type, types.VectorType):
raise TypeError("vector1 needs to be of VectorType.")
if vector2 != Undefined:
if vector2.type != vector1.type:
raise TypeError("vector2 needs to be " +
"Undefined or of the same type as vector1.")
if (not isinstance(mask, Constant) or
not isinstance(mask.type, types.VectorType) or
not (isinstance(mask.type.element, types.IntType) and
mask.type.element.width == 32)):
raise TypeError("mask needs to be a constant i32 vector.")
typ = types.VectorType(vector1.type.element, mask.type.count)
index_range = range(vector1.type.count
if vector2 == Undefined
else 2 * vector1.type.count)
if not all(ii.constant in index_range for ii in mask.constant):
raise IndexError(
"mask values need to be in {0}".format(index_range),
)
super(ShuffleVector, self).__init__(parent, typ, "shufflevector",
[vector1, vector2, mask], name=name)
def descr(self, buf):
buf.append("shufflevector {0} {1}\n".format(
", ".join("{0} {1}".format(op.type, op.get_reference())
for op in self.operands),
self._stringify_metadata(leading_comma=True),
))
class ExtractValue(Instruction):
def __init__(self, parent, agg, indices, name=''):
typ = agg.type
try:
for i in indices:
typ = typ.elements[i]
except (AttributeError, IndexError):
raise TypeError("Can't index at %r in %s"
% (list(indices), agg.type))
super(ExtractValue, self).__init__(parent, typ, "extractvalue",
[agg], name=name)
self.aggregate = agg
self.indices = indices
def descr(self, buf):
indices = [str(i) for i in self.indices]
buf.append("extractvalue {0} {1}, {2} {3}\n".format(
self.aggregate.type,
self.aggregate.get_reference(),
', '.join(indices),
self._stringify_metadata(leading_comma=True),
))
class InsertValue(Instruction):
def __init__(self, parent, agg, elem, indices, name=''):
typ = agg.type
try:
for i in indices:
typ = typ.elements[i]
except (AttributeError, IndexError):
raise TypeError("Can't index at %r in %s"
% (list(indices), agg.type))
if elem.type != typ:
raise TypeError("Can only insert %s at %r in %s: got %s"
% (typ, list(indices), agg.type, elem.type))
super(InsertValue, self).__init__(parent, agg.type, "insertvalue",
[agg, elem], name=name)
self.aggregate = agg
self.value = elem
self.indices = indices
def descr(self, buf):
indices = [str(i) for i in self.indices]
buf.append("insertvalue {0} {1}, {2} {3}, {4} {5}\n".format(
self.aggregate.type, self.aggregate.get_reference(),
self.value.type, self.value.get_reference(),
', '.join(indices),
self._stringify_metadata(leading_comma=True),
))
class Unreachable(Instruction):
def __init__(self, parent):
super(Unreachable, self).__init__(parent, types.VoidType(),
"unreachable", (), name='')
def descr(self, buf):
buf += (self.opname, "\n")
class InlineAsm(object):
def __init__(self, ftype, asm, constraint, side_effect=False):
self.type = ftype.return_type
self.function_type = ftype
self.asm = asm
self.constraint = constraint
self.side_effect = side_effect
def descr(self, buf):
sideeffect = 'sideeffect' if self.side_effect else ''
fmt = 'asm {sideeffect} "{asm}", "{constraint}"'
buf.append(fmt.format(sideeffect=sideeffect, asm=self.asm,
constraint=self.constraint))
def get_reference(self):
buf = []
self.descr(buf)
return "".join(buf)
def __str__(self):
return "{0} {1}".format(self.type, self.get_reference())
class AtomicRMW(Instruction):
def __init__(self, parent, op, ptr, val, ordering, name):
super(AtomicRMW, self).__init__(parent, val.type, "atomicrmw",
(ptr, val), name=name)
self.operation = op
self.ordering = ordering
def descr(self, buf):
ptr, val = self.operands
fmt = ("atomicrmw {op} {ptrty} {ptr}, {valty} {val} {ordering} "
"{metadata}\n")
buf.append(fmt.format(op=self.operation,
ptrty=ptr.type,
ptr=ptr.get_reference(),
valty=val.type,
val=val.get_reference(),
ordering=self.ordering,
metadata=self._stringify_metadata(
leading_comma=True),
))
class CmpXchg(Instruction):
"""This instruction has changed since llvm3.5. It is not compatible with
older llvm versions.
"""
def __init__(self, parent, ptr, cmp, val, ordering, failordering, name):
outtype = types.LiteralStructType([val.type, types.IntType(1)])
super(CmpXchg, self).__init__(parent, outtype, "cmpxchg",
(ptr, cmp, val), name=name)
self.ordering = ordering
self.failordering = failordering
def descr(self, buf):
ptr, cmpval, val = self.operands
fmt = "cmpxchg {ptrty} {ptr}, {ty} {cmp}, {ty} {val} {ordering} " \
"{failordering} {metadata}\n"
buf.append(fmt.format(ptrty=ptr.type,
ptr=ptr.get_reference(),
ty=cmpval.type,
cmp=cmpval.get_reference(),
val=val.get_reference(),
ordering=self.ordering,
failordering=self.failordering,
metadata=self._stringify_metadata(
leading_comma=True),
))
class _LandingPadClause(object):
def __init__(self, value):
self.value = value
def __str__(self):
return "{kind} {type} {value}".format(
kind=self.kind,
type=self.value.type,
value=self.value.get_reference())
class CatchClause(_LandingPadClause):
kind = 'catch'
class FilterClause(_LandingPadClause):
kind = 'filter'
def __init__(self, value):
assert isinstance(value, Constant)
assert isinstance(value.type, types.ArrayType)
super(FilterClause, self).__init__(value)
class LandingPadInstr(Instruction):
def __init__(self, parent, typ, name='', cleanup=False):
super(LandingPadInstr, self).__init__(parent, typ, "landingpad", [],
name=name)
self.cleanup = cleanup
self.clauses = []
def add_clause(self, clause):
assert isinstance(clause, _LandingPadClause)
self.clauses.append(clause)
def descr(self, buf):
fmt = "landingpad {type}{cleanup}{clauses}\n"
buf.append(fmt.format(type=self.type,
cleanup=' cleanup' if self.cleanup else '',
clauses=''.join(["\n {0}".format(clause)
for clause in self.clauses]),
))
class Fence(Instruction):
"""
The `fence` instruction.
As of LLVM 5.0.1:
fence [syncscope("<target-scope>")] <ordering> ; yields void
"""
VALID_FENCE_ORDERINGS = {"acquire", "release", "acq_rel", "seq_cst"}
def __init__(self, parent, ordering, targetscope=None, name=''):
super(Fence, self).__init__(parent, types.VoidType(), "fence", (),
name=name)
if ordering not in self.VALID_FENCE_ORDERINGS:
msg = "Invalid fence ordering \"{0}\"! Should be one of {1}."
raise ValueError(msg .format(ordering,
", ".join(self.VALID_FENCE_ORDERINGS)))
self.ordering = ordering
self.targetscope = targetscope
def descr(self, buf):
if self.targetscope is None:
syncscope = ""
else:
syncscope = 'syncscope("{0}") '.format(self.targetscope)
fmt = "fence {syncscope}{ordering}\n"
buf.append(fmt.format(syncscope=syncscope,
ordering=self.ordering,
))
class Comment(Instruction):
"""
A line comment.
"""
def __init__(self, parent, text):
super(Comment, self).__init__(parent, types.VoidType(), ";", (),
name='')
assert "\n" not in text, "Comment cannot contain new line"
self.text = text
def descr(self, buf):
buf.append(f"; {self.text}")

View File

@@ -0,0 +1,256 @@
import collections
from llvmlite.ir import context, values, types, _utils
class Module(object):
def __init__(self, name='', context=context.global_context):
self.context = context
self.name = name # name is for debugging/informational
self.data_layout = ""
self.scope = _utils.NameScope()
self.triple = 'unknown-unknown-unknown'
self.globals = collections.OrderedDict()
# Innamed metadata nodes.
self.metadata = []
# Named metadata nodes
self.namedmetadata = {}
# Cache for metadata node deduplication
self._metadatacache = {}
def _fix_metadata_operands(self, operands):
fixed_ops = []
for op in operands:
if op is None:
# A literal None creates a null metadata value
op = types.MetaDataType()(None)
elif isinstance(op, str):
# A literal string creates a metadata string value
op = values.MetaDataString(self, op)
elif isinstance(op, (list, tuple)):
# A sequence creates a metadata node reference
op = self.add_metadata(op)
fixed_ops.append(op)
return fixed_ops
def _fix_di_operands(self, operands):
fixed_ops = []
for name, op in operands:
if isinstance(op, (list, tuple)):
# A sequence creates a metadata node reference
op = self.add_metadata(op)
fixed_ops.append((name, op))
return fixed_ops
def str_ditok_operands(self, operands):
str_ops = []
for name, op in operands:
if name == 'encoding' and isinstance(op, values.DIToken):
# use string value instead of address of object
op = op.value
str_ops.append((name, op))
return str_ops
def add_metadata(self, operands):
"""
Add an unnamed metadata to the module with the given *operands*
(a sequence of values) or return a previous equivalent metadata.
A MDValue instance is returned, it can then be associated to
e.g. an instruction.
"""
if not isinstance(operands, (list, tuple)):
raise TypeError("expected a list or tuple of metadata values, "
"got %r" % (operands,))
operands = self._fix_metadata_operands(operands)
key = tuple(operands)
if key not in self._metadatacache:
n = len(self.metadata)
md = values.MDValue(self, operands, name=str(n))
self._metadatacache[key] = md
else:
md = self._metadatacache[key]
return md
def add_debug_info(self, kind, operands, is_distinct=False):
"""
Add debug information metadata to the module with the given
*operands* (a dict of values with string keys) or return
a previous equivalent metadata. *kind* is a string of the
debug information kind (e.g. "DICompileUnit").
A DIValue instance is returned, it can then be associated to e.g.
an instruction.
"""
operands = tuple(sorted(self._fix_di_operands(operands.items())))
str_op_key = tuple(sorted(self.str_ditok_operands(operands)))
key = (kind, str_op_key, is_distinct)
if key not in self._metadatacache:
n = len(self.metadata)
di = values.DIValue(self, is_distinct, kind, operands, name=str(n))
self._metadatacache[key] = di
else:
di = self._metadatacache[key]
return di
def add_named_metadata(self, name, element=None):
"""
Add a named metadata node to the module, if it doesn't exist,
or return the existing node.
If *element* is given, it will append a new element to
the named metadata node. If *element* is a sequence of values
(rather than a metadata value), a new unnamed node will first be
created.
Example::
module.add_named_metadata("llvm.ident", ["llvmlite/1.0"])
"""
if name in self.namedmetadata:
nmd = self.namedmetadata[name]
else:
nmd = self.namedmetadata[name] = values.NamedMetaData(self)
if element is not None:
if not isinstance(element, values.Value):
element = self.add_metadata(element)
if not isinstance(element.type, types.MetaDataType):
raise TypeError("wrong type for metadata element: got %r"
% (element,))
nmd.add(element)
return nmd
def get_named_metadata(self, name):
"""
Return the metadata node with the given *name*. KeyError is raised
if no such node exists (contrast with add_named_metadata()).
"""
return self.namedmetadata[name]
@property
def functions(self):
"""
A list of functions declared or defined in this module.
"""
return [v for v in self.globals.values()
if isinstance(v, values.Function)]
@property
def global_values(self):
"""
An iterable of global values in this module.
"""
return self.globals.values()
def get_global(self, name):
"""
Get a global value by name.
"""
return self.globals[name]
def add_global(self, globalvalue):
"""
Add a new global value.
"""
assert globalvalue.name not in self.globals
self.globals[globalvalue.name] = globalvalue
def get_unique_name(self, name=''):
"""
Get a unique global name with the following *name* hint.
"""
return self.scope.deduplicate(name)
def declare_intrinsic(self, intrinsic, tys=(), fnty=None):
def _error():
raise NotImplementedError("unknown intrinsic %r with %d types"
% (intrinsic, len(tys)))
if intrinsic in {'llvm.cttz', 'llvm.ctlz', 'llvm.fma'}:
suffixes = [tys[0].intrinsic_name]
else:
suffixes = [t.intrinsic_name for t in tys]
name = '.'.join([intrinsic] + suffixes)
if name in self.globals:
return self.globals[name]
if fnty is not None:
# General case: function type is given
pass
# Compute function type if omitted for common cases
elif len(tys) == 0 and intrinsic == 'llvm.assume':
fnty = types.FunctionType(types.VoidType(), [types.IntType(1)])
elif len(tys) == 1:
if intrinsic == 'llvm.powi':
fnty = types.FunctionType(tys[0], [tys[0], types.IntType(32)])
elif intrinsic == 'llvm.pow':
fnty = types.FunctionType(tys[0], tys * 2)
elif intrinsic == 'llvm.convert.from.fp16':
fnty = types.FunctionType(tys[0], [types.IntType(16)])
elif intrinsic == 'llvm.convert.to.fp16':
fnty = types.FunctionType(types.IntType(16), tys)
else:
fnty = types.FunctionType(tys[0], tys)
elif len(tys) == 2:
if intrinsic == 'llvm.memset':
tys = [tys[0], types.IntType(8), tys[1],
types.IntType(1)]
fnty = types.FunctionType(types.VoidType(), tys)
elif intrinsic in {'llvm.cttz', 'llvm.ctlz'}:
tys = [tys[0], types.IntType(1)]
fnty = types.FunctionType(tys[0], tys)
else:
_error()
elif len(tys) == 3:
if intrinsic in ('llvm.memcpy', 'llvm.memmove'):
tys = tys + [types.IntType(1)]
fnty = types.FunctionType(types.VoidType(), tys)
elif intrinsic == 'llvm.fma':
tys = [tys[0]] * 3
fnty = types.FunctionType(tys[0], tys)
else:
_error()
else:
_error()
return values.Function(self, fnty, name=name)
def get_identified_types(self):
return self.context.identified_types
def _get_body_lines(self):
# Type declarations
lines = [it.get_declaration()
for it in self.get_identified_types().values()]
# Global values (including function definitions)
lines += [str(v) for v in self.globals.values()]
return lines
def _get_metadata_lines(self):
mdbuf = []
for k, v in self.namedmetadata.items():
mdbuf.append("!{name} = !{{ {operands} }}".format(
name=k, operands=', '.join(i.get_reference()
for i in v.operands)))
for md in self.metadata:
mdbuf.append(str(md))
return mdbuf
def _stringify_body(self):
# For testing
return "\n".join(self._get_body_lines())
def _stringify_metadata(self):
# For testing
return "\n".join(self._get_metadata_lines())
def __repr__(self):
lines = []
# Header
lines += [
'; ModuleID = "%s"' % (self.name,),
'target triple = "%s"' % (self.triple,),
'target datalayout = "%s"' % (self.data_layout,),
'']
# Body
lines += self._get_body_lines()
# Metadata
lines += self._get_metadata_lines()
return "\n".join(lines)

View File

@@ -0,0 +1,64 @@
from llvmlite.ir import CallInstr
class Visitor(object):
def visit(self, module):
self._module = module
for func in module.functions:
self.visit_Function(func)
def visit_Function(self, func):
self._function = func
for bb in func.blocks:
self.visit_BasicBlock(bb)
def visit_BasicBlock(self, bb):
self._basic_block = bb
for instr in bb.instructions:
self.visit_Instruction(instr)
def visit_Instruction(self, instr):
raise NotImplementedError
@property
def module(self):
return self._module
@property
def function(self):
return self._function
@property
def basic_block(self):
return self._basic_block
class CallVisitor(Visitor):
def visit_Instruction(self, instr):
if isinstance(instr, CallInstr):
self.visit_Call(instr)
def visit_Call(self, instr):
raise NotImplementedError
class ReplaceCalls(CallVisitor):
def __init__(self, orig, repl):
super(ReplaceCalls, self).__init__()
self.orig = orig
self.repl = repl
self.calls = []
def visit_Call(self, instr):
if instr.callee == self.orig:
instr.replace_callee(self.repl)
self.calls.append(instr)
def replace_all_calls(mod, orig, repl):
"""Replace all calls to `orig` to `repl` in module `mod`.
Returns the references to the returned calls
"""
rc = ReplaceCalls(orig, repl)
rc.visit(mod)
return rc.calls

View File

@@ -0,0 +1,732 @@
"""
Classes that are LLVM types
"""
import struct
from llvmlite import ir_layer_typed_pointers_enabled
from llvmlite.ir._utils import _StrCaching
def _wrapname(x):
return '"{0}"'.format(x.replace('\\', '\\5c').replace('"', '\\22'))
class Type(_StrCaching):
"""
The base class for all LLVM types.
"""
is_pointer = False
null = 'zeroinitializer'
def __repr__(self):
return "<%s %s>" % (type(self), str(self))
def _to_string(self):
raise NotImplementedError
def as_pointer(self, addrspace=0):
return PointerType(self, addrspace)
def __ne__(self, other):
return not (self == other)
def _get_ll_global_value_type(self, target_data, context=None):
"""
Convert this type object to an LLVM type.
"""
from llvmlite.ir import Module, GlobalVariable
from llvmlite.binding import parse_assembly
if context is None:
m = Module()
else:
m = Module(context=context)
foo = GlobalVariable(m, self, name="foo")
with parse_assembly(str(m)) as llmod:
return llmod.get_global_variable(foo.name).global_value_type
def get_abi_size(self, target_data, context=None):
"""
Get the ABI size of this type according to data layout *target_data*.
"""
llty = self._get_ll_global_value_type(target_data, context)
return target_data.get_abi_size(llty)
def get_element_offset(self, target_data, ndx, context=None):
llty = self._get_ll_global_value_type(target_data, context)
return target_data.get_element_offset(llty, ndx)
def get_abi_alignment(self, target_data, context=None):
"""
Get the minimum ABI alignment of this type according to data layout
*target_data*.
"""
llty = self._get_ll_global_value_type(target_data, context)
return target_data.get_abi_alignment(llty)
def format_constant(self, value):
"""
Format constant *value* of this type. This method may be overriden
by subclasses.
"""
return str(value)
def wrap_constant_value(self, value):
"""
Wrap constant *value* if necessary. This method may be overriden
by subclasses (especially aggregate types).
"""
return value
def __call__(self, value):
"""
Create a LLVM constant of this type with the given Python value.
"""
from llvmlite.ir import Constant
return Constant(self, value)
class MetaDataType(Type):
def _to_string(self):
return "metadata"
def as_pointer(self):
raise TypeError
def __eq__(self, other):
return isinstance(other, MetaDataType)
def __hash__(self):
return hash(MetaDataType)
class LabelType(Type):
"""
The label type is the type of e.g. basic blocks.
"""
def _to_string(self):
return "label"
class PointerType(Type):
"""
The type of all pointer values.
By default (without specialisation) represents an opaque pointer.
"""
is_opaque = True
is_pointer = True
null = 'null'
# Factory to create typed or opaque pointers based on `pointee'.
def __new__(cls, pointee=None, addrspace=0):
if cls is PointerType and pointee is not None and \
type(pointee) is not PointerType:
return super().__new__(_TypedPointerType)
return super(PointerType, cls).__new__(cls)
def __init__(self, pointee=None, addrspace=0):
assert pointee is None or type(pointee) is PointerType
self.addrspace = addrspace
def _to_string(self):
if self.addrspace != 0:
return "ptr addrspace({0})".format(self.addrspace)
else:
return "ptr"
def __eq__(self, other):
return (isinstance(other, PointerType) and
self.addrspace == other.addrspace)
def __hash__(self):
return hash(PointerType)
@property
def intrinsic_name(self):
return 'p%d' % self.addrspace
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
return cls()
class _TypedPointerType(PointerType):
"""
The type of typed pointer values. To be removed eventually.
"""
def __init__(self, pointee, addrspace=0):
super(_TypedPointerType, self).__init__(None, addrspace)
assert pointee is not None and type(pointee) is not PointerType
assert not isinstance(pointee, VoidType)
self.pointee = pointee
self.is_opaque = False
def _to_string(self):
if ir_layer_typed_pointers_enabled:
return "{0}*".format(self.pointee) if self.addrspace == 0 else \
"{0} addrspace({1})*".format(self.pointee, self.addrspace)
return super(_TypedPointerType, self)._to_string()
# This implements ``isOpaqueOrPointeeTypeEquals''.
def __eq__(self, other):
if isinstance(other, _TypedPointerType):
return (self.pointee, self.addrspace) == (other.pointee,
other.addrspace)
return (isinstance(other, PointerType) and
self.addrspace == other.addrspace)
def __hash__(self):
return hash(_TypedPointerType)
def gep(self, i):
"""
Resolve the type of the i-th element (for getelementptr lookups).
"""
if not isinstance(i.type, IntType):
raise TypeError(i.type)
return self.pointee
@property
def intrinsic_name(self):
if ir_layer_typed_pointers_enabled:
return 'p%d%s' % (self.addrspace, self.pointee.intrinsic_name)
return super(_TypedPointerType, self).intrinsic_name
class VoidType(Type):
"""
The type for empty values (e.g. a function returning no value).
"""
def _to_string(self):
return 'void'
def __eq__(self, other):
return isinstance(other, VoidType)
def __hash__(self):
return hash(VoidType)
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
return cls()
class FunctionType(Type):
"""
The type for functions.
"""
def __init__(self, return_type, args, var_arg=False):
self.return_type = return_type
self.args = tuple(args)
self.var_arg = var_arg
def _to_string(self):
if self.args:
strargs = ', '.join([str(a) for a in self.args])
if self.var_arg:
return '{0} ({1}, ...)'.format(self.return_type, strargs)
else:
return '{0} ({1})'.format(self.return_type, strargs)
elif self.var_arg:
return '{0} (...)'.format(self.return_type)
else:
return '{0} ()'.format(self.return_type)
def __eq__(self, other):
if isinstance(other, FunctionType):
return (self.return_type == other.return_type and
self.args == other.args and self.var_arg == other.var_arg)
else:
return False
def __hash__(self):
return hash(FunctionType)
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
params = tuple(x.as_ir(ir_ctx=ir_ctx)
for x in typeref.get_function_parameters())
ret = typeref.get_function_return().as_ir(ir_ctx=ir_ctx)
is_vararg = typeref.is_function_vararg
return cls(ret, params, is_vararg)
class IntType(Type):
"""
The type for integers.
"""
null = '0'
_instance_cache = {}
width: int
def __new__(cls, bits):
# Cache all common integer types
if 0 <= bits <= 128:
try:
return cls._instance_cache[bits]
except KeyError:
inst = cls._instance_cache[bits] = cls.__new(bits)
return inst
return cls.__new(bits)
@classmethod
def __new(cls, bits):
assert isinstance(bits, int) and bits >= 0
self = super(IntType, cls).__new__(cls)
self.width = bits
return self
def __getnewargs__(self):
return self.width,
def __copy__(self):
return self
def _to_string(self):
return 'i%u' % (self.width,)
def __eq__(self, other):
if isinstance(other, IntType):
return self.width == other.width
else:
return False
def __hash__(self):
return hash(IntType)
def format_constant(self, val):
if isinstance(val, bool):
return str(val).lower()
else:
return str(val)
def wrap_constant_value(self, val):
if val is None:
return 0
return val
@property
def intrinsic_name(self):
return str(self)
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
return IntType(typeref.type_width)
def _as_float(value):
"""
Truncate to single-precision float.
"""
return struct.unpack('f', struct.pack('f', value))[0]
def _as_half(value):
"""
Truncate to half-precision float.
"""
try:
return struct.unpack('e', struct.pack('e', value))[0]
except struct.error:
# 'e' only added in Python 3.6+
return _as_float(value)
def _format_float_as_hex(value, packfmt, unpackfmt, numdigits):
raw = struct.pack(packfmt, float(value))
intrep = struct.unpack(unpackfmt, raw)[0]
out = '{{0:#{0}x}}'.format(numdigits).format(intrep)
return out
def _format_double(value):
"""
Format *value* as a hexadecimal string of its IEEE double precision
representation.
"""
return _format_float_as_hex(value, 'd', 'Q', 16)
class _BaseFloatType(Type):
def __new__(cls):
return cls._instance_cache
def __eq__(self, other):
return isinstance(other, type(self))
def __hash__(self):
return hash(type(self))
@classmethod
def _create_instance(cls):
cls._instance_cache = super(_BaseFloatType, cls).__new__(cls)
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
return cls()
class HalfType(_BaseFloatType):
"""
The type for single-precision floats.
"""
null = '0.0'
intrinsic_name = 'f16'
def __str__(self):
return 'half'
def format_constant(self, value):
return _format_double(_as_half(value))
class FloatType(_BaseFloatType):
"""
The type for single-precision floats.
"""
null = '0.0'
intrinsic_name = 'f32'
def __str__(self):
return 'float'
def format_constant(self, value):
return _format_double(_as_float(value))
class DoubleType(_BaseFloatType):
"""
The type for double-precision floats.
"""
null = '0.0'
intrinsic_name = 'f64'
def __str__(self):
return 'double'
def format_constant(self, value):
return _format_double(value)
for _cls in (HalfType, FloatType, DoubleType):
_cls._create_instance()
class _Repeat(object):
def __init__(self, value, size):
self.value = value
self.size = size
def __len__(self):
return self.size
def __getitem__(self, item):
if 0 <= item < self.size:
return self.value
else:
raise IndexError(item)
class VectorType(Type):
"""
The type for vectors of primitive data items (e.g. "<f32 x 4>").
"""
def __init__(self, element, count):
self.element = element
self.count = count
@property
def elements(self):
return _Repeat(self.element, self.count)
def __len__(self):
return self.count
def _to_string(self):
return "<%d x %s>" % (self.count, self.element)
def __eq__(self, other):
if isinstance(other, VectorType):
return self.element == other.element and self.count == other.count
def __hash__(self):
# TODO: why does this not take self.element/self.count into account?
return hash(VectorType)
def __copy__(self):
return self
def format_constant(self, value):
itemstring = ", " .join(["{0} {1}".format(x.type, x.get_reference())
for x in value])
return "<{0}>".format(itemstring)
def wrap_constant_value(self, values):
from . import Value, Constant
if not isinstance(values, (list, tuple)):
if isinstance(values, Constant):
if values.type != self.element:
raise TypeError("expected {} for {}".format(
self.element, values.type))
return (values, ) * self.count
return (Constant(self.element, values), ) * self.count
if len(values) != len(self):
raise ValueError("wrong constant size for %s: got %d elements"
% (self, len(values)))
return [Constant(ty, val) if not isinstance(val, Value) else val
for ty, val in zip(self.elements, values)]
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
[elemtyperef] = typeref.elements
elemty = elemtyperef.as_ir(ir_ctx=ir_ctx)
count = typeref.element_count
return cls(elemty, count)
class Aggregate(Type):
"""
Base class for aggregate types.
See http://llvm.org/docs/LangRef.html#t-aggregate
"""
def wrap_constant_value(self, values):
from . import Value, Constant
if not isinstance(values, (list, tuple)):
return values
if len(values) != len(self):
raise ValueError("wrong constant size for %s: got %d elements"
% (self, len(values)))
return [Constant(ty, val) if not isinstance(val, Value) else val
for ty, val in zip(self.elements, values)]
class ArrayType(Aggregate):
"""
The type for fixed-size homogenous arrays (e.g. "[f32 x 3]").
"""
def __init__(self, element, count):
self.element = element
self.count = count
@property
def elements(self):
return _Repeat(self.element, self.count)
def __len__(self):
return self.count
def _to_string(self):
return "[%d x %s]" % (self.count, self.element)
def __eq__(self, other):
if isinstance(other, ArrayType):
return self.element == other.element and self.count == other.count
def __hash__(self):
return hash(ArrayType)
def gep(self, i):
"""
Resolve the type of the i-th element (for getelementptr lookups).
"""
if not isinstance(i.type, IntType):
raise TypeError(i.type)
return self.element
def format_constant(self, value):
itemstring = ", " .join(["{0} {1}".format(x.type, x.get_reference())
for x in value])
return "[{0}]".format(itemstring)
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
[elemtyperef] = typeref.elements
elemty = elemtyperef.as_ir(ir_ctx=ir_ctx)
count = typeref.element_count
return cls(elemty, count)
class BaseStructType(Aggregate):
"""
The base type for heterogenous struct types.
"""
_packed = False
@property
def packed(self):
"""
A boolean attribute that indicates whether the structure uses
packed layout.
"""
return self._packed
@packed.setter
def packed(self, val):
self._packed = bool(val)
def __len__(self):
assert self.elements is not None
return len(self.elements)
def __iter__(self):
assert self.elements is not None
return iter(self.elements)
@property
def is_opaque(self):
return self.elements is None
def structure_repr(self):
"""
Return the LLVM IR for the structure representation
"""
ret = '{%s}' % ', '.join([str(x) for x in self.elements])
return self._wrap_packed(ret)
def format_constant(self, value):
itemstring = ", " .join(["{0} {1}".format(x.type, x.get_reference())
for x in value])
ret = "{{{0}}}".format(itemstring)
return self._wrap_packed(ret)
def gep(self, i):
"""
Resolve the type of the i-th element (for getelementptr lookups).
*i* needs to be a LLVM constant, so that the type can be determined
at compile-time.
"""
if not isinstance(i.type, IntType):
raise TypeError(i.type)
return self.elements[i.constant]
def _wrap_packed(self, textrepr):
"""
Internal helper to wrap textual repr of struct type into packed struct
"""
if self.packed:
return '<{}>'.format(textrepr)
else:
return textrepr
@classmethod
def from_llvm(cls, typeref, ir_ctx):
"""
Create from a llvmlite.binding.TypeRef
"""
if typeref.is_literal_struct:
elems = [el.as_ir(ir_ctx=ir_ctx) for el in typeref.elements]
return cls(elems, typeref.is_packed_struct)
else:
return ir_ctx.get_identified_type(typeref.name)
class LiteralStructType(BaseStructType):
"""
The type of "literal" structs, i.e. structs with a literally-defined
type (by contrast with IdentifiedStructType).
"""
null = 'zeroinitializer'
def __init__(self, elems, packed=False):
"""
*elems* is a sequence of types to be used as members.
*packed* controls the use of packed layout.
"""
self.elements = tuple(elems)
self.packed = packed
def _to_string(self):
return self.structure_repr()
def __eq__(self, other):
if isinstance(other, LiteralStructType):
return (self.elements == other.elements
and self.packed == other.packed)
def __hash__(self):
return hash(LiteralStructType)
class IdentifiedStructType(BaseStructType):
"""
A type which is a named alias for another struct type, akin to a typedef.
While literal struct types can be structurally equal (see
LiteralStructType), identified struct types are compared by name.
Do not use this directly.
"""
null = 'zeroinitializer'
def __init__(self, context, name, packed=False):
"""
*context* is a llvmlite.ir.Context.
*name* is the identifier for the new struct type.
*packed* controls the use of packed layout.
"""
assert name
self.context = context
self.name = name
self.elements = None
self.packed = packed
def _to_string(self):
return "%{name}".format(name=_wrapname(self.name))
def get_declaration(self):
"""
Returns the string for the declaration of the type
"""
if self.is_opaque:
out = "{strrep} = type opaque".format(strrep=str(self))
else:
out = "{strrep} = type {struct}".format(
strrep=str(self), struct=self.structure_repr())
return out
def __eq__(self, other):
if isinstance(other, IdentifiedStructType):
return (self.name == other.name
and self.packed == other.packed)
def __hash__(self):
return hash(IdentifiedStructType)
def set_body(self, *elems):
if not self.is_opaque:
raise RuntimeError("{name} is already defined".format(
name=self.name))
self.elements = tuple(elems)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
import sys
import unittest
from unittest import TestCase
import faulthandler
try:
# May fail in IPython Notebook with UnsupportedOperation
faulthandler.enable()
except BaseException as e:
msg = "Failed to enable faulthandler due to:\n{err}"
warnings.warn(msg.format(err=e))
# Try to inject Numba's unittest customizations.
from llvmlite.tests import customize
def discover_tests(startdir):
"""Discover test under a directory
"""
# Avoid importing unittest
loader = unittest.TestLoader()
suite = loader.discover(startdir)
return suite
def run_tests(suite=None, xmloutput=None, verbosity=1):
"""
args
----
- suite [TestSuite]
A suite of all tests to run
- xmloutput [str or None]
Path of XML output directory (optional)
- verbosity [int]
Verbosity level of tests output
Returns the TestResult object after running the test *suite*.
"""
if suite is None:
suite = discover_tests("llvmlite.tests")
if xmloutput is not None:
import xmlrunner
runner = xmlrunner.XMLTestRunner(output=xmloutput)
else:
runner = None
prog = unittest.main(suite=suite, testRunner=runner, exit=False,
verbosity=verbosity)
return prog.result
def main():
res = run_tests()
sys.exit(0 if res.wasSuccessful() else 1)

View File

@@ -0,0 +1,3 @@
from llvmlite.tests import main
main()

View File

@@ -0,0 +1,407 @@
# XXX Ripped off from numba.tests; we should factor it out somewhere?
import collections
import contextlib
import cProfile
from io import StringIO
import gc
import os
import multiprocessing
import sys
import time
import unittest
import warnings
from unittest import result, runner, signals
# "unittest.main" is really the TestProgram class!
# (defined in a module named itself "unittest.main"...)
class NumbaTestProgram(unittest.main):
"""
A TestProgram subclass adding the following options:
* a -R option to enable reference leak detection
* a --profile option to enable profiling of the test run
Currently the options are only added in 3.4+.
"""
refleak = False
profile = False
multiprocess = False
def __init__(self, *args, **kwargs):
self.discovered_suite = kwargs.pop('suite', None)
# HACK to force unittest not to change warning display options
# (so that NumbaWarnings don't appear all over the place)
sys.warnoptions.append(':x')
super(NumbaTestProgram, self).__init__(*args, **kwargs)
def createTests(self):
if self.discovered_suite is not None:
self.test = self.discovered_suite
else:
super(NumbaTestProgram, self).createTests()
def _getParentArgParser(self):
# NOTE: this hook only exists on Python 3.4+. The options won't be
# added in earlier versions (which use optparse - 3.3 - or getopt()
# - 2.x).
parser = super(NumbaTestProgram, self)._getParentArgParser()
if self.testRunner is None:
parser.add_argument('-R', '--refleak', dest='refleak',
action='store_true',
help='Detect reference / memory leaks')
parser.add_argument('-m', '--multiprocess', dest='multiprocess',
action='store_true',
help='Parallelize tests')
parser.add_argument('--profile', dest='profile',
action='store_true',
help='Profile the test run')
return parser
def parseArgs(self, argv):
if sys.version_info < (3, 4):
# We want these options to work on all versions, emulate them.
if '-R' in argv:
argv.remove('-R')
self.refleak = True
if '-m' in argv:
argv.remove('-m')
self.multiprocess = True
super(NumbaTestProgram, self).parseArgs(argv)
if self.verbosity <= 0:
# We aren't interested in informational messages / warnings when
# running with '-q'.
self.buffer = True
def runTests(self):
if self.refleak:
self.testRunner = RefleakTestRunner
if not hasattr(sys, "gettotalrefcount"):
warnings.warn("detecting reference leaks requires a debug "
"build of Python, only memory leaks will be "
"detected")
elif self.testRunner is None:
self.testRunner = unittest.TextTestRunner
if self.multiprocess:
self.testRunner = ParallelTestRunner(self.testRunner,
verbosity=self.verbosity,
failfast=self.failfast,
buffer=self.buffer)
def run_tests_real():
super(NumbaTestProgram, self).runTests()
if self.profile:
filename = os.path.splitext(
os.path.basename(sys.modules['__main__'].__file__)
)[0] + '.prof'
p = cProfile.Profile(timer=time.perf_counter) # 3.3+
p.enable()
try:
p.runcall(run_tests_real)
finally:
p.disable()
print("Writing test profile data into %r" % (filename,))
p.dump_stats(filename)
else:
run_tests_real()
# Monkey-patch unittest so that individual test modules get our custom
# options for free.
unittest.main = NumbaTestProgram
# The reference leak detection code is liberally taken and adapted from
# Python's own Lib/test/regrtest.py.
def _refleak_cleanup():
# Collect cyclic trash and read memory statistics immediately after.
try:
func1 = sys.getallocatedblocks
except AttributeError:
def func1():
return 42
try:
func2 = sys.gettotalrefcount
except AttributeError:
def func2():
return 42
# Flush standard output, so that buffered data is sent to the OS and
# associated Python objects are reclaimed.
for stream in (sys.stdout, sys.stderr, sys.__stdout__, sys.__stderr__):
if stream is not None:
stream.flush()
sys._clear_type_cache()
# This also clears the various internal CPython freelists.
gc.collect()
return func1(), func2()
class ReferenceLeakError(RuntimeError):
pass
class IntPool(collections.defaultdict):
def __missing__(self, key):
return key
class RefleakTestResult(runner.TextTestResult):
warmup = 3
repetitions = 6
def _huntLeaks(self, test):
self.stream.flush()
repcount = self.repetitions
nwarmup = self.warmup
rc_deltas = [0] * (repcount - nwarmup)
alloc_deltas = [0] * (repcount - nwarmup)
# Preallocate ints likely to be stored in rc_deltas and alloc_deltas,
# to make sys.getallocatedblocks() less flaky.
_int_pool = IntPool()
for i in range(-200, 200):
_int_pool[i]
alloc_before = rc_before = 0
for i in range(repcount):
# Use a pristine, silent result object to avoid recursion
res = result.TestResult()
test.run(res)
# Poorly-written tests may fail when run several times.
# In this case, abort the refleak run and report the failure.
if not res.wasSuccessful():
self.failures.extend(res.failures)
self.errors.extend(res.errors)
raise AssertionError
del res
alloc_after, rc_after = _refleak_cleanup()
if i >= nwarmup:
rc_deltas[i - nwarmup] = _int_pool[rc_after - rc_before]
alloc_deltas[i -
nwarmup] = _int_pool[alloc_after -
alloc_before]
alloc_before, rc_before = alloc_after, rc_after
return rc_deltas, alloc_deltas
def addSuccess(self, test):
try:
rc_deltas, alloc_deltas = self._huntLeaks(test)
except AssertionError:
# Test failed when repeated
assert not self.wasSuccessful()
return
# These checkers return False on success, True on failure
def check_rc_deltas(deltas):
return any(deltas)
def check_alloc_deltas(deltas):
# At least 1/3rd of 0s
if 3 * deltas.count(0) < len(deltas):
return True
# Nothing else than 1s, 0s and -1s
if not set(deltas) <= set((1, 0, -1)):
return True
return False
failed = False
for deltas, item_name, checker in [
(rc_deltas, 'references', check_rc_deltas),
(alloc_deltas, 'memory blocks', check_alloc_deltas)]:
if checker(deltas):
msg = '%s leaked %s %s, sum=%s' % (
test, deltas, item_name, sum(deltas))
failed = True
try:
raise ReferenceLeakError(msg)
except Exception:
exc_info = sys.exc_info()
if self.showAll:
self.stream.write("%s = %r " % (item_name, deltas))
self.addFailure(test, exc_info)
if not failed:
super(RefleakTestResult, self).addSuccess(test)
class RefleakTestRunner(runner.TextTestRunner):
resultclass = RefleakTestResult
def _flatten_suite(test):
"""Expand suite into list of tests
"""
if isinstance(test, unittest.TestSuite):
tests = []
for x in test:
tests.extend(_flatten_suite(x))
return tests
else:
return [test]
class ParallelTestResult(runner.TextTestResult):
"""
A TestResult able to inject results from other results.
"""
def add_results(self, result):
"""
Add the results from the other *result* to this result.
"""
self.stream.write(result.stream.getvalue())
self.stream.flush()
self.testsRun += result.testsRun
self.failures.extend(result.failures)
self.errors.extend(result.errors)
self.skipped.extend(result.skipped)
self.expectedFailures.extend(result.expectedFailures)
self.unexpectedSuccesses.extend(result.unexpectedSuccesses)
class _MinimalResult(object):
"""
A minimal, picklable TestResult-alike object.
"""
__slots__ = (
'failures', 'errors', 'skipped', 'expectedFailures',
'unexpectedSuccesses', 'stream', 'shouldStop', 'testsRun')
def fixup_case(self, case):
"""
Remove any unpicklable attributes from TestCase instance *case*.
"""
# Python 3.3 doesn't reset this one.
case._outcomeForDoCleanups = None
def __init__(self, original_result):
for attr in self.__slots__:
setattr(self, attr, getattr(original_result, attr))
for case, _ in self.expectedFailures:
self.fixup_case(case)
for case, _ in self.errors:
self.fixup_case(case)
for case, _ in self.failures:
self.fixup_case(case)
class _FakeStringIO(object):
"""
A trivial picklable StringIO-alike for Python 2.
"""
def __init__(self, value):
self._value = value
def getvalue(self):
return self._value
class _MinimalRunner(object):
"""
A minimal picklable object able to instantiate a runner in a
child process and run a test case with it.
"""
def __init__(self, runner_cls, runner_args):
self.runner_cls = runner_cls
self.runner_args = runner_args
# Python 2 doesn't know how to pickle instance methods, so we use __call__
# instead.
def __call__(self, test):
# Executed in child process
kwargs = self.runner_args
# Force recording of output in a buffer (it will be printed out
# by the parent).
kwargs['stream'] = StringIO()
runner = self.runner_cls(**kwargs)
result = runner._makeResult()
# Avoid child tracebacks when Ctrl-C is pressed.
signals.installHandler()
signals.registerResult(result)
result.failfast = runner.failfast
result.buffer = runner.buffer
with self.cleanup_object(test):
test(result)
# HACK as cStringIO.StringIO isn't picklable in 2.x
result.stream = _FakeStringIO(result.stream.getvalue())
return _MinimalResult(result)
@contextlib.contextmanager
def cleanup_object(self, test):
"""
A context manager which cleans up unwanted attributes on a test case
(or any other object).
"""
vanilla_attrs = set(test.__dict__)
try:
yield test
finally:
spurious_attrs = set(test.__dict__) - vanilla_attrs
for name in spurious_attrs:
del test.__dict__[name]
class ParallelTestRunner(runner.TextTestRunner):
"""
A test runner which delegates the actual running to a pool of child
processes.
"""
resultclass = ParallelTestResult
def __init__(self, runner_cls, **kwargs):
runner.TextTestRunner.__init__(self, **kwargs)
self.runner_cls = runner_cls
self.runner_args = kwargs
def _run_inner(self, result):
# We hijack TextTestRunner.run()'s inner logic by passing this
# method as if it were a test case.
child_runner = _MinimalRunner(self.runner_cls, self.runner_args)
pool = multiprocessing.Pool()
imap = pool.imap_unordered
try:
for child_result in imap(child_runner, self._test_list):
result.add_results(child_result)
if child_result.shouldStop:
break
return result
finally:
# Kill the still active workers
pool.terminate()
pool.join()
def run(self, test):
self._test_list = _flatten_suite(test)
# This will call self._run_inner() on the created result object,
# and print out the detailed test results at the end.
return super(ParallelTestRunner, self).run(self._run_inner)
try:
import faulthandler
except ImportError:
pass
else:
try:
# May fail in IPython Notebook with UnsupportedOperation
faulthandler.enable()
except BaseException as e:
msg = "Failed to enable faulthandler due to:\n{err}"
warnings.warn(msg.format(err=e))

View File

@@ -0,0 +1,330 @@
"""
Contains tests and a prototype implementation for the fanout algorithm in
the LLVM refprune pass.
"""
try:
from graphviz import Digraph
except ImportError:
pass
from collections import defaultdict
# The entry block. It's always the same.
ENTRY = "A"
# The following caseNN() functions returns a 3-tuple of
# (nodes, edges, expected).
# `nodes` maps BB nodes to incref/decref inside the block.
# `edges` maps BB nodes to their successor BB.
# `expected` maps BB-node with incref to a set of BB-nodes with the decrefs, or
# the value can be None, indicating invalid prune.
def case1():
edges = {
"A": ["B"],
"B": ["C", "D"],
"C": [],
"D": ["E", "F"],
"E": ["G"],
"F": [],
"G": ["H", "I"],
"I": ["G", "F"],
"H": ["J", "K"],
"J": ["L", "M"],
"K": [],
"L": ["Z"],
"M": ["Z", "O", "P"],
"O": ["Z"],
"P": ["Z"],
"Z": [],
}
nodes = defaultdict(list)
nodes["D"] = ["incref"]
nodes["H"] = ["decref"]
nodes["F"] = ["decref", "decref"]
expected = {"D": {"H", "F"}}
return nodes, edges, expected
def case2():
edges = {
"A": ["B", "C"],
"B": ["C"],
"C": [],
}
nodes = defaultdict(list)
nodes["A"] = ["incref"]
nodes["B"] = ["decref"]
nodes["C"] = ["decref"]
expected = {"A": None}
return nodes, edges, expected
def case3():
nodes, edges, _ = case1()
# adds an invalid edge
edges["H"].append("F")
expected = {"D": None}
return nodes, edges, expected
def case4():
nodes, edges, _ = case1()
# adds an invalid edge
edges["H"].append("E")
expected = {"D": None}
return nodes, edges, expected
def case5():
nodes, edges, _ = case1()
# adds backedge to go before incref
edges["B"].append("I")
expected = {"D": None}
return nodes, edges, expected
def case6():
nodes, edges, _ = case1()
# adds backedge to go before incref
edges["I"].append("B")
expected = {"D": None}
return nodes, edges, expected
def case7():
nodes, edges, _ = case1()
# adds forward jump outside
edges["I"].append("M")
expected = {"D": None}
return nodes, edges, expected
def case8():
edges = {
"entry:": ["A"],
"A": ["B", "C"],
"B": ["C"],
"C": [],
}
nodes = defaultdict(list)
nodes["A"] = ["incref"]
nodes["C"] = ["decref"]
expected = {"A": {"C"}}
return nodes, edges, expected
def case9():
nodes, edges, _ = case8()
# adds back edge
edges["C"].append("B")
expected = {"A": None}
return nodes, edges, expected
def case10():
nodes, edges, _ = case8()
# adds back edge to A
edges["C"].append("A")
expected = {"A": {"C"}}
return nodes, edges, expected
def case11():
nodes, edges, _ = case8()
edges["C"].append("D")
edges["D"] = []
expected = {"A": {"C"}}
return nodes, edges, expected
def case12():
nodes, edges, _ = case8()
edges["C"].append("D")
edges["D"] = ["A"]
expected = {"A": {"C"}}
return nodes, edges, expected
def case13():
nodes, edges, _ = case8()
edges["C"].append("D")
edges["D"] = ["B"]
expected = {"A": None}
return nodes, edges, expected
def make_predecessor_map(edges):
d = defaultdict(set)
for src, outgoings in edges.items():
for dst in outgoings:
d[dst].add(src)
return d
class FanoutAlgorithm:
def __init__(self, nodes, edges, verbose=False):
self.nodes = nodes
self.edges = edges
self.rev_edges = make_predecessor_map(edges)
self.print = print if verbose else self._null_print
def run(self):
return self.find_fanout_in_function()
def _null_print(self, *args, **kwargs):
pass
def find_fanout_in_function(self):
got = {}
for cur_node in self.edges:
for incref in (x for x in self.nodes[cur_node] if x == "incref"):
decref_blocks = self.find_fanout(cur_node)
self.print(">>", cur_node, "===", decref_blocks)
got[cur_node] = decref_blocks
return got
def find_fanout(self, head_node):
decref_blocks = self.find_decref_candidates(head_node)
self.print("candidates", decref_blocks)
if not decref_blocks:
return None
if not self.verify_non_overlapping(
head_node, decref_blocks, entry=ENTRY
):
return None
return set(decref_blocks)
def verify_non_overlapping(self, head_node, decref_blocks, entry):
self.print("verify_non_overlapping".center(80, "-"))
# reverse walk for each decref_blocks
# they should end at head_node
todo = list(decref_blocks)
while todo:
cur_node = todo.pop()
visited = set()
workstack = [cur_node]
del cur_node
while workstack:
cur_node = workstack.pop()
self.print("cur_node", cur_node, "|", workstack)
if cur_node in visited:
continue # skip
if cur_node == entry:
# Entry node
self.print(
"!! failed because we arrived at entry", cur_node
)
return False
visited.add(cur_node)
# check all predecessors
self.print(
f" {cur_node} preds {self.get_predecessors(cur_node)}"
)
for pred in self.get_predecessors(cur_node):
if pred in decref_blocks:
# reject because there's a predecessor in decref_blocks
self.print(
"!! reject because predecessor in decref_blocks"
)
return False
if pred != head_node:
workstack.append(pred)
return True
def get_successors(self, node):
return tuple(self.edges[node])
def get_predecessors(self, node):
return tuple(self.rev_edges[node])
def has_decref(self, node):
return "decref" in self.nodes[node]
def walk_child_for_decref(
self, cur_node, path_stack, decref_blocks, depth=10
):
indent = " " * len(path_stack)
self.print(indent, "walk", path_stack, cur_node)
if depth <= 0:
return False # missing
if cur_node in path_stack:
if cur_node == path_stack[0]:
return False # reject interior node backedge
return True # skip
if self.has_decref(cur_node):
decref_blocks.add(cur_node)
self.print(indent, "found decref")
return True
depth -= 1
path_stack += (cur_node,)
found = False
for child in self.get_successors(cur_node):
if not self.walk_child_for_decref(
child, path_stack, decref_blocks
):
found = False
break
else:
found = True
self.print(indent, f"ret {found}")
return found
def find_decref_candidates(self, cur_node):
# Forward pass
self.print("find_decref_candidates".center(80, "-"))
path_stack = (cur_node,)
found = False
decref_blocks = set()
for child in self.get_successors(cur_node):
if not self.walk_child_for_decref(
child, path_stack, decref_blocks
):
found = False
break
else:
found = True
if not found:
return set()
else:
return decref_blocks
def check_once():
nodes, edges, expected = case13()
# Render graph
G = Digraph()
for node in edges:
G.node(node, shape="rect", label=f"{node}\n" + r"\l".join(nodes[node]))
for node, children in edges.items():
for child in children:
G.edge(node, child)
G.view()
algo = FanoutAlgorithm(nodes, edges, verbose=True)
got = algo.run()
assert expected == got
def check_all():
for k, fn in list(globals().items()):
if k.startswith("case"):
print(f"{fn}".center(80, "-"))
nodes, edges, expected = fn()
algo = FanoutAlgorithm(nodes, edges)
got = algo.run()
assert expected == got
print("ALL PASSED")
if __name__ == "__main__":
# check_once()
check_all()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,574 @@
import unittest
from collections import defaultdict
from llvmlite import ir
from llvmlite import binding as llvm
from llvmlite.tests import TestCase
import llvmlite.tests.refprune_proto as proto
def _iterate_cases(generate_test):
def wrap(fn):
def wrapped(self):
return generate_test(self, fn)
wrapped.__doc__ = f"generated test for {fn.__module__}.{fn.__name__}"
return wrapped
for k, case_fn in proto.__dict__.items():
if k.startswith('case'):
yield f'test_{k}', wrap(case_fn)
class PassManagerMixin():
def pb(self):
llvm.initialize_native_target()
tm = llvm.Target.from_default_triple().create_target_machine()
pto = llvm.create_pipeline_tuning_options(speed_level=0, size_level=0)
return llvm.create_pass_builder(tm, pto)
class TestRefPrunePrototype(TestCase):
"""
Test that the prototype is working.
"""
def generate_test(self, case_gen):
nodes, edges, expected = case_gen()
got = proto.FanoutAlgorithm(nodes, edges).run()
self.assertEqual(expected, got)
# Generate tests
for name, case in _iterate_cases(generate_test):
locals()[name] = case
ptr_ty = ir.IntType(8).as_pointer()
class TestRefPrunePass(TestCase, PassManagerMixin):
"""
Test that the C++ implementation matches the expected behavior as for
the prototype.
This generates a LLVM module for each test case, runs the pruner and checks
that the expected results are achieved.
"""
def make_incref(self, m):
fnty = ir.FunctionType(ir.VoidType(), [ptr_ty])
return ir.Function(m, fnty, name='NRT_incref')
def make_decref(self, m):
fnty = ir.FunctionType(ir.VoidType(), [ptr_ty])
return ir.Function(m, fnty, name='NRT_decref')
def make_switcher(self, m):
fnty = ir.FunctionType(ir.IntType(32), ())
return ir.Function(m, fnty, name='switcher')
def make_brancher(self, m):
fnty = ir.FunctionType(ir.IntType(1), ())
return ir.Function(m, fnty, name='brancher')
def generate_ir(self, nodes, edges):
# Build LLVM module for the CFG
m = ir.Module()
incref_fn = self.make_incref(m)
decref_fn = self.make_decref(m)
switcher_fn = self.make_switcher(m)
brancher_fn = self.make_brancher(m)
fnty = ir.FunctionType(ir.VoidType(), [ptr_ty])
fn = ir.Function(m, fnty, name='main')
[ptr] = fn.args
ptr.name = 'mem'
# populate the BB nodes
bbmap = {}
for bb in edges:
bbmap[bb] = fn.append_basic_block(bb)
# populate the BB
builder = ir.IRBuilder()
for bb, jump_targets in edges.items():
builder.position_at_end(bbmap[bb])
# Insert increfs and decrefs
for action in nodes[bb]:
if action == 'incref':
builder.call(incref_fn, [ptr])
elif action == 'decref':
builder.call(decref_fn, [ptr])
else:
raise AssertionError('unreachable')
# Insert the terminator.
# Switch base on the number of jump targets.
n_targets = len(jump_targets)
if n_targets == 0:
builder.ret_void()
elif n_targets == 1:
[dst] = jump_targets
builder.branch(bbmap[dst])
elif n_targets == 2:
[left, right] = jump_targets
sel = builder.call(brancher_fn, ())
builder.cbranch(sel, bbmap[left], bbmap[right])
elif n_targets > 2:
sel = builder.call(switcher_fn, ())
[head, *tail] = jump_targets
sw = builder.switch(sel, default=bbmap[head])
for i, dst in enumerate(tail):
sw.add_case(sel.type(i), bbmap[dst])
else:
raise AssertionError('unreachable')
return m
def apply_refprune(self, irmod):
mod = llvm.parse_assembly(str(irmod))
pb = self.pb()
pm = pb.getModulePassManager()
pm.add_refprune_pass()
pm.run(mod, pb)
return mod
def check(self, mod, expected, nodes):
# preprocess incref/decref locations
# LLVM >= 18 adds an extra empty block "LoopExit" which causes
# regular dict to throw KeyError
d = defaultdict(lambda: defaultdict(int))
for k, vs in nodes.items():
n_incref = vs.count('incref')
n_decref = vs.count('decref')
d[k] = {'incref': n_incref, 'decref': n_decref}
for k, stats in d.items():
if expected.get(k):
stats['incref'] -= 1
for dec_bb in expected[k]:
d[dec_bb]['decref'] -= 1
# find the main function
for f in mod.functions:
if f.name == 'main':
break
# check each BB
for bb in f.blocks:
stats = d[bb.name]
text = str(bb)
n_incref = text.count('NRT_incref')
n_decref = text.count('NRT_decref')
self.assertEqual(stats['incref'], n_incref, msg=f'BB {bb}')
self.assertEqual(stats['decref'], n_decref, msg=f'BB {bb}')
def generate_test(self, case_gen):
nodes, edges, expected = case_gen()
irmod = self.generate_ir(nodes, edges)
outmod = self.apply_refprune(irmod)
self.check(outmod, expected, nodes)
# Generate tests
for name, case in _iterate_cases(generate_test):
locals()[name] = case
class BaseTestByIR(TestCase, PassManagerMixin):
refprune_bitmask = 0
prologue = r"""
declare void @NRT_incref(i8* %ptr)
declare void @NRT_decref(i8* %ptr)
"""
def check(self, irmod, subgraph_limit=None):
mod = llvm.parse_assembly(f"{self.prologue}\n{irmod}")
pb = self.pb()
pm = pb.getModulePassManager()
if subgraph_limit is None:
pm.add_refprune_pass(self.refprune_bitmask)
else:
pm.add_refprune_pass(self.refprune_bitmask,
subgraph_limit=subgraph_limit)
before = llvm.dump_refprune_stats()
pm.run(mod, pb)
after = llvm.dump_refprune_stats()
return mod, after - before
class TestPerBB(BaseTestByIR):
refprune_bitmask = llvm.RefPruneSubpasses.PER_BB
per_bb_ir_1 = r"""
define void @main(i8* %ptr) {
call void @NRT_incref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_bb_1(self):
mod, stats = self.check(self.per_bb_ir_1)
self.assertEqual(stats.basicblock, 2)
per_bb_ir_2 = r"""
define void @main(i8* %ptr) {
call void @NRT_incref(i8* %ptr)
call void @NRT_incref(i8* %ptr)
call void @NRT_incref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_bb_2(self):
mod, stats = self.check(self.per_bb_ir_2)
self.assertEqual(stats.basicblock, 4)
# not pruned
self.assertIn("call void @NRT_incref(ptr %ptr)", str(mod))
per_bb_ir_3 = r"""
define void @main(ptr %ptr, ptr %other) {
call void @NRT_incref(ptr %ptr)
call void @NRT_incref(ptr %ptr)
call void @NRT_decref(ptr %ptr)
call void @NRT_decref(ptr %other)
ret void
}
"""
def test_per_bb_3(self):
mod, stats = self.check(self.per_bb_ir_3)
self.assertEqual(stats.basicblock, 2)
# not pruned
self.assertIn("call void @NRT_decref(ptr %other)", str(mod))
per_bb_ir_4 = r"""
; reordered
define void @main(ptr %ptr, ptr %other) {
call void @NRT_incref(ptr %ptr)
call void @NRT_decref(ptr %ptr)
call void @NRT_decref(ptr %ptr)
call void @NRT_decref(ptr %other)
call void @NRT_incref(ptr %ptr)
ret void
}
"""
def test_per_bb_4(self):
mod, stats = self.check(self.per_bb_ir_4)
self.assertEqual(stats.basicblock, 4)
# not pruned
self.assertIn("call void @NRT_decref(ptr %other)", str(mod))
class TestDiamond(BaseTestByIR):
refprune_bitmask = llvm.RefPruneSubpasses.DIAMOND
per_diamond_1 = r"""
define void @main(i8* %ptr) {
bb_A:
call void @NRT_incref(i8* %ptr)
br label %bb_B
bb_B:
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_diamond_1(self):
mod, stats = self.check(self.per_diamond_1)
self.assertEqual(stats.diamond, 2)
per_diamond_2 = r"""
define void @main(i8* %ptr, i1 %cond) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
br label %bb_D
bb_C:
br label %bb_D
bb_D:
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_diamond_2(self):
mod, stats = self.check(self.per_diamond_2)
self.assertEqual(stats.diamond, 2)
per_diamond_3 = r"""
define void @main(i8* %ptr, i1 %cond) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
br label %bb_D
bb_C:
call void @NRT_decref(i8* %ptr) ; reject because of decref in diamond
br label %bb_D
bb_D:
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_diamond_3(self):
mod, stats = self.check(self.per_diamond_3)
self.assertEqual(stats.diamond, 0)
per_diamond_4 = r"""
define void @main(i8* %ptr, i1 %cond) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_incref(i8* %ptr) ; extra incref will not affect prune
br label %bb_D
bb_C:
br label %bb_D
bb_D:
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_diamond_4(self):
mod, stats = self.check(self.per_diamond_4)
self.assertEqual(stats.diamond, 2)
per_diamond_5 = r"""
define void @main(i8* %ptr, i1 %cond) {
bb_A:
call void @NRT_incref(i8* %ptr)
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
br label %bb_D
bb_C:
br label %bb_D
bb_D:
call void @NRT_decref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_per_diamond_5(self):
mod, stats = self.check(self.per_diamond_5)
self.assertEqual(stats.diamond, 4)
class TestFanout(BaseTestByIR):
"""More complex cases are tested in TestRefPrunePass
"""
refprune_bitmask = llvm.RefPruneSubpasses.FANOUT
fanout_1 = r"""
define void @main(i8* %ptr, i1 %cond) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
ret void
bb_C:
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_fanout_1(self):
mod, stats = self.check(self.fanout_1)
self.assertEqual(stats.fanout, 3)
fanout_2 = r"""
define void @main(i8* %ptr, i1 %cond, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
ret void
bb_C:
call void @NRT_decref(i8* %ptr)
br label %bb_B ; illegal jump to other decref
}
"""
def test_fanout_2(self):
mod, stats = self.check(self.fanout_2)
self.assertEqual(stats.fanout, 0)
fanout_3 = r"""
define void @main(i8* %ptr, i1 %cond) {
bb_A:
call void @NRT_incref(i8* %ptr)
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
ret void
bb_C:
call void @NRT_decref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
ret void
}
"""
def test_fanout_3(self):
mod, stats = self.check(self.fanout_3)
self.assertEqual(stats.fanout, 6)
def test_fanout_3_limited(self):
# With subgraph limit at 1, it is essentially turning off the fanout
# pruner.
mod, stats = self.check(self.fanout_3, subgraph_limit=1)
self.assertEqual(stats.fanout, 0)
class TestFanoutRaise(BaseTestByIR):
refprune_bitmask = llvm.RefPruneSubpasses.FANOUT_RAISE
fanout_raise_1 = r"""
define i32 @main(i8* %ptr, i1 %cond, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
ret i32 0
bb_C:
store i8* null, i8** %excinfo, !numba_exception_output !0
ret i32 1
}
!0 = !{i1 true}
"""
def test_fanout_raise_1(self):
mod, stats = self.check(self.fanout_raise_1)
self.assertEqual(stats.fanout_raise, 2)
fanout_raise_2 = r"""
define i32 @main(i8* %ptr, i1 %cond, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
ret i32 0
bb_C:
store i8* null, i8** %excinfo, !numba_exception_typo !0 ; bad metadata
ret i32 1
}
!0 = !{i1 true}
"""
def test_fanout_raise_2(self):
# This is ensuring that fanout_raise is not pruning when the metadata
# is incorrectly named.
mod, stats = self.check(self.fanout_raise_2)
self.assertEqual(stats.fanout_raise, 0)
fanout_raise_3 = r"""
define i32 @main(i8* %ptr, i1 %cond, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
ret i32 0
bb_C:
store i8* null, i8** %excinfo, !numba_exception_output !0
ret i32 1
}
!0 = !{i32 1} ; ok; use i32
"""
def test_fanout_raise_3(self):
mod, stats = self.check(self.fanout_raise_3)
self.assertEqual(stats.fanout_raise, 2)
fanout_raise_4 = r"""
define i32 @main(i8* %ptr, i1 %cond, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
ret i32 1 ; BAD; all tails are raising without decref
bb_C:
ret i32 1 ; BAD; all tails are raising without decref
}
!0 = !{i1 1}
"""
def test_fanout_raise_4(self):
mod, stats = self.check(self.fanout_raise_4)
self.assertEqual(stats.fanout_raise, 0)
fanout_raise_5 = r"""
define i32 @main(i8* %ptr, i1 %cond, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
br i1 %cond, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
br label %common.ret
bb_C:
store i8* null, i8** %excinfo, !numba_exception_output !0
br label %common.ret
common.ret:
%common.ret.op = phi i32 [ 0, %bb_B ], [ 1, %bb_C ]
ret i32 %common.ret.op
}
!0 = !{i1 1}
"""
def test_fanout_raise_5(self):
mod, stats = self.check(self.fanout_raise_5)
self.assertEqual(stats.fanout_raise, 2)
# test case 6 is from https://github.com/numba/llvmlite/issues/1023
fanout_raise_6 = r"""
define i32 @main(i8* %ptr, i1 %cond1, i1 %cond2, i1 %cond3, i8** %excinfo) {
bb_A:
call void @NRT_incref(i8* %ptr)
call void @NRT_incref(i8* %ptr)
br i1 %cond1, label %bb_B, label %bb_C
bb_B:
call void @NRT_decref(i8* %ptr)
br i1 %cond2, label %bb_D, label %bb_E
bb_C:
store i8* null, i8** %excinfo, !numba_exception_output !0
ret i32 1
bb_D:
call void @NRT_decref(i8* %ptr)
ret i32 0
bb_E:
call void @NRT_incref(i8* %ptr)
br i1 %cond3, label %bb_F, label %bb_C
bb_F:
call void @NRT_decref(i8* %ptr)
call void @NRT_decref(i8* %ptr)
ret i32 0
}
!0 = !{i1 1}
"""
def test_fanout_raise_6(self):
mod, stats = self.check(self.fanout_raise_6)
self.assertEqual(stats.fanout_raise, 7)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,60 @@
import math
import sys
import unittest
from llvmlite.ir import (
Constant, FloatType, DoubleType, LiteralStructType, IntType,
ArrayType, HalfType)
from llvmlite.tests import TestCase
int8 = IntType(8)
int16 = IntType(16)
PY36_OR_LATER = sys.version_info[:2] >= (3, 6)
class TestValueRepr(TestCase):
def test_double_repr(self):
def check_repr(val, expected):
c = Constant(DoubleType(), val)
self.assertEqual(str(c), expected)
check_repr(math.pi, "double 0x400921fb54442d18")
check_repr(float('inf'), "double 0x7ff0000000000000")
check_repr(float('-inf'), "double 0xfff0000000000000")
def test_float_repr(self):
def check_repr(val, expected):
c = Constant(FloatType(), val)
self.assertEqual(str(c), expected)
check_repr(math.pi, "float 0x400921fb60000000")
check_repr(float('inf'), "float 0x7ff0000000000000")
check_repr(float('-inf'), "float 0xfff0000000000000")
@unittest.skipUnless(PY36_OR_LATER, 'py36+ only')
def test_half_repr(self):
def check_repr(val, expected):
c = Constant(HalfType(), val)
self.assertEqual(str(c), expected)
check_repr(math.pi, "half 0x4009200000000000")
check_repr(float('inf'), "half 0x7ff0000000000000")
check_repr(float('-inf'), "half 0xfff0000000000000")
def test_struct_repr(self):
tp = LiteralStructType([int8, int16])
c = Constant(tp, (Constant(int8, 100), Constant(int16, 1000)))
self.assertEqual(str(c), "{i8, i16} {i8 100, i16 1000}")
def test_array_repr(self):
tp = ArrayType(int8, 3)
values = [Constant(int8, x) for x in (5, 10, -15)]
c = Constant(tp, values)
self.assertEqual(str(c), "[3 x i8] [i8 5, i8 10, i8 -15]")
c = Constant(tp, bytearray(b"\x01\x02\x03"))
self.assertEqual(str(c), '[3 x i8] c"\\01\\02\\03"')
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,29 @@
import os
import sys
# This module must be importable without loading the binding, to avoid
# bootstrapping issues in setup.py.
def get_library_name():
"""
Return the name of the llvmlite shared library file.
"""
if os.name == 'posix':
if sys.platform == 'darwin':
return 'libllvmlite.dylib'
else:
return 'libllvmlite.so'
else:
assert os.name == 'nt'
return 'llvmlite.dll'
def get_library_files():
"""
Return the names of shared library files needed for this platform.
"""
files = [get_library_name()]
if os.name == 'nt':
files.extend(['msvcr120.dll', 'msvcp120.dll'])
return files