Videre
This commit is contained in:
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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}
|
||||
|
||||
@@ -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 *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
BIN
linedance-app/venv/lib/python3.12/site-packages/llvmlite/binding/libllvmlite.so
Executable file
BIN
linedance-app/venv/lib/python3.12/site-packages/llvmlite/binding/libllvmlite.so
Executable file
Binary file not shown.
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
@@ -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()
|
||||
@@ -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}")
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
from llvmlite.tests import main
|
||||
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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))
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user