1723 lines
68 KiB
Python
1723 lines
68 KiB
Python
from collections import namedtuple
|
|
import contextlib
|
|
import pickle
|
|
import hashlib
|
|
import sys
|
|
|
|
from llvmlite import ir
|
|
from llvmlite.ir import Constant
|
|
|
|
import ctypes
|
|
from numba import _helperlib
|
|
from numba.core import (
|
|
types, utils, config, lowering, cgutils, imputils, serialize,
|
|
)
|
|
from numba.core.utils import PYVERSION
|
|
|
|
PY_UNICODE_1BYTE_KIND = _helperlib.py_unicode_1byte_kind
|
|
PY_UNICODE_2BYTE_KIND = _helperlib.py_unicode_2byte_kind
|
|
PY_UNICODE_4BYTE_KIND = _helperlib.py_unicode_4byte_kind
|
|
if PYVERSION in ((3, 9), (3, 10), (3, 11)):
|
|
PY_UNICODE_WCHAR_KIND = _helperlib.py_unicode_wchar_kind
|
|
|
|
|
|
class _Registry(object):
|
|
|
|
def __init__(self):
|
|
self.functions = {}
|
|
|
|
def register(self, typeclass):
|
|
assert issubclass(typeclass, types.Type)
|
|
def decorator(func):
|
|
if typeclass in self.functions:
|
|
raise KeyError("duplicate registration for %s" % (typeclass,))
|
|
self.functions[typeclass] = func
|
|
return func
|
|
return decorator
|
|
|
|
def lookup(self, typeclass, default=None):
|
|
assert issubclass(typeclass, types.Type)
|
|
for cls in typeclass.__mro__:
|
|
func = self.functions.get(cls)
|
|
if func is not None:
|
|
return func
|
|
return default
|
|
|
|
# Registries of boxing / unboxing implementations
|
|
_boxers = _Registry()
|
|
_unboxers = _Registry()
|
|
_reflectors = _Registry()
|
|
|
|
box = _boxers.register
|
|
unbox = _unboxers.register
|
|
reflect = _reflectors.register
|
|
|
|
class _BoxContext(namedtuple("_BoxContext",
|
|
("context", "builder", "pyapi", "env_manager"))):
|
|
"""
|
|
The facilities required by boxing implementations.
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def box(self, typ, val):
|
|
return self.pyapi.from_native_value(typ, val, self.env_manager)
|
|
|
|
|
|
class _UnboxContext(namedtuple("_UnboxContext",
|
|
("context", "builder", "pyapi"))):
|
|
"""
|
|
The facilities required by unboxing implementations.
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def unbox(self, typ, obj):
|
|
return self.pyapi.to_native_value(typ, obj)
|
|
|
|
|
|
class _ReflectContext(namedtuple("_ReflectContext",
|
|
("context", "builder", "pyapi", "env_manager",
|
|
"is_error"))):
|
|
"""
|
|
The facilities required by reflection implementations.
|
|
"""
|
|
__slots__ = ()
|
|
|
|
# XXX the error bit is currently unused by consumers (e.g. PyCallWrapper)
|
|
def set_error(self):
|
|
self.builder.store(self.is_error, cgutils.true_bit)
|
|
|
|
def box(self, typ, val):
|
|
return self.pyapi.from_native_value(typ, val, self.env_manager)
|
|
|
|
def reflect(self, typ, val):
|
|
return self.pyapi.reflect_native_value(typ, val, self.env_manager)
|
|
|
|
|
|
class NativeValue(object):
|
|
"""
|
|
Encapsulate the result of converting a Python object to a native value,
|
|
recording whether the conversion was successful and how to cleanup.
|
|
"""
|
|
|
|
def __init__(self, value, is_error=None, cleanup=None):
|
|
self.value = value
|
|
self.is_error = is_error if is_error is not None else cgutils.false_bit
|
|
self.cleanup = cleanup
|
|
|
|
|
|
class EnvironmentManager(object):
|
|
|
|
def __init__(self, pyapi, env, env_body, env_ptr):
|
|
assert isinstance(env, lowering.Environment)
|
|
self.pyapi = pyapi
|
|
self.env = env
|
|
self.env_body = env_body
|
|
self.env_ptr = env_ptr
|
|
|
|
def add_const(self, const):
|
|
"""
|
|
Add a constant to the environment, return its index.
|
|
"""
|
|
# All constants are frozen inside the environment
|
|
if isinstance(const, str):
|
|
const = sys.intern(const)
|
|
for index, val in enumerate(self.env.consts):
|
|
if val is const:
|
|
break
|
|
else:
|
|
index = len(self.env.consts)
|
|
self.env.consts.append(const)
|
|
return index
|
|
|
|
def read_const(self, index):
|
|
"""
|
|
Look up constant number *index* inside the environment body.
|
|
A borrowed reference is returned.
|
|
|
|
The returned LLVM value may have NULL value at runtime which indicates
|
|
an error at runtime.
|
|
"""
|
|
assert index < len(self.env.consts)
|
|
|
|
builder = self.pyapi.builder
|
|
consts = self.env_body.consts
|
|
ret = cgutils.alloca_once(builder, self.pyapi.pyobj, zfill=True)
|
|
with builder.if_else(cgutils.is_not_null(builder, consts)) as \
|
|
(br_not_null, br_null):
|
|
with br_not_null:
|
|
getitem = self.pyapi.list_getitem(consts, index)
|
|
builder.store(getitem, ret)
|
|
with br_null:
|
|
# This can happen when the Environment is accidentally released
|
|
# and has subsequently been garbage collected.
|
|
self.pyapi.err_set_string(
|
|
"PyExc_RuntimeError",
|
|
"`env.consts` is NULL in `read_const`",
|
|
)
|
|
return builder.load(ret)
|
|
|
|
|
|
_IteratorLoop = namedtuple('_IteratorLoop', ('value', 'do_break'))
|
|
|
|
|
|
class PythonAPI(object):
|
|
"""
|
|
Code generation facilities to call into the CPython C API (and related
|
|
helpers).
|
|
"""
|
|
|
|
def __init__(self, context, builder):
|
|
"""
|
|
Note: Maybe called multiple times when lowering a function
|
|
"""
|
|
self.context = context
|
|
self.builder = builder
|
|
|
|
self.module = builder.basic_block.function.module
|
|
# A unique mapping of serialized objects in this module
|
|
try:
|
|
self.module.__serialized
|
|
except AttributeError:
|
|
self.module.__serialized = {}
|
|
|
|
# Initialize types
|
|
self.pyobj = self.context.get_argument_type(types.pyobject)
|
|
self.pyobjptr = self.pyobj.as_pointer()
|
|
self.voidptr = ir.PointerType(ir.IntType(8))
|
|
self.long = ir.IntType(ctypes.sizeof(ctypes.c_long) * 8)
|
|
self.ulong = self.long
|
|
self.longlong = ir.IntType(ctypes.sizeof(ctypes.c_ulonglong) * 8)
|
|
self.ulonglong = self.longlong
|
|
self.double = ir.DoubleType()
|
|
self.py_ssize_t = self.context.get_value_type(types.intp)
|
|
self.cstring = ir.PointerType(ir.IntType(8))
|
|
self.gil_state = ir.IntType(_helperlib.py_gil_state_size * 8)
|
|
self.py_buffer_t = ir.ArrayType(ir.IntType(8), _helperlib.py_buffer_size)
|
|
self.py_hash_t = self.py_ssize_t
|
|
self.py_unicode_1byte_kind = _helperlib.py_unicode_1byte_kind
|
|
self.py_unicode_2byte_kind = _helperlib.py_unicode_2byte_kind
|
|
self.py_unicode_4byte_kind = _helperlib.py_unicode_4byte_kind
|
|
|
|
def get_env_manager(self, env, env_body, env_ptr):
|
|
return EnvironmentManager(self, env, env_body, env_ptr)
|
|
|
|
def emit_environment_sentry(self, envptr, return_pyobject=False,
|
|
debug_msg=''):
|
|
"""Emits LLVM code to ensure the `envptr` is not NULL
|
|
"""
|
|
is_null = cgutils.is_null(self.builder, envptr)
|
|
with cgutils.if_unlikely(self.builder, is_null):
|
|
if return_pyobject:
|
|
fnty = self.builder.function.type.pointee
|
|
assert fnty.return_type == self.pyobj
|
|
self.err_set_string(
|
|
"PyExc_RuntimeError", f"missing Environment: {debug_msg}",
|
|
)
|
|
self.builder.ret(self.get_null_object())
|
|
else:
|
|
self.context.call_conv.return_user_exc(
|
|
self.builder, RuntimeError,
|
|
(f"missing Environment: {debug_msg}",),
|
|
)
|
|
|
|
# ------ Python API -----
|
|
|
|
#
|
|
# Basic object API
|
|
#
|
|
|
|
def incref(self, obj):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="Py_IncRef")
|
|
self.builder.call(fn, [obj])
|
|
|
|
def decref(self, obj):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="Py_DecRef")
|
|
self.builder.call(fn, [obj])
|
|
|
|
def get_type(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_py_type")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
#
|
|
# Argument unpacking
|
|
#
|
|
|
|
def parse_tuple_and_keywords(self, args, kws, fmt, keywords, *objs):
|
|
charptr = ir.PointerType(ir.IntType(8))
|
|
charptrary = ir.PointerType(charptr)
|
|
argtypes = [self.pyobj, self.pyobj, charptr, charptrary]
|
|
fnty = ir.FunctionType(ir.IntType(32), argtypes, var_arg=True)
|
|
fn = self._get_function(fnty, name="PyArg_ParseTupleAndKeywords")
|
|
return self.builder.call(fn, [args, kws, fmt, keywords] + list(objs))
|
|
|
|
def parse_tuple(self, args, fmt, *objs):
|
|
charptr = ir.PointerType(ir.IntType(8))
|
|
argtypes = [self.pyobj, charptr]
|
|
fnty = ir.FunctionType(ir.IntType(32), argtypes, var_arg=True)
|
|
fn = self._get_function(fnty, name="PyArg_ParseTuple")
|
|
return self.builder.call(fn, [args, fmt] + list(objs))
|
|
|
|
def unpack_tuple(self, args, name, n_min, n_max, *objs):
|
|
charptr = ir.PointerType(ir.IntType(8))
|
|
argtypes = [self.pyobj, charptr, self.py_ssize_t, self.py_ssize_t]
|
|
fnty = ir.FunctionType(ir.IntType(32), argtypes, var_arg=True)
|
|
fn = self._get_function(fnty, name="PyArg_UnpackTuple")
|
|
n_min = Constant(self.py_ssize_t, int(n_min))
|
|
n_max = Constant(self.py_ssize_t, int(n_max))
|
|
if isinstance(name, str):
|
|
name = self.context.insert_const_string(self.builder.module, name)
|
|
return self.builder.call(fn, [args, name, n_min, n_max] + list(objs))
|
|
|
|
#
|
|
# Exception and errors
|
|
#
|
|
|
|
def err_occurred(self):
|
|
fnty = ir.FunctionType(self.pyobj, ())
|
|
fn = self._get_function(fnty, name="PyErr_Occurred")
|
|
return self.builder.call(fn, ())
|
|
|
|
def err_clear(self):
|
|
fnty = ir.FunctionType(ir.VoidType(), ())
|
|
fn = self._get_function(fnty, name="PyErr_Clear")
|
|
return self.builder.call(fn, ())
|
|
|
|
def err_set_string(self, exctype, msg):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj, self.cstring])
|
|
fn = self._get_function(fnty, name="PyErr_SetString")
|
|
if isinstance(exctype, str):
|
|
exctype = self.get_c_object(exctype)
|
|
if isinstance(msg, str):
|
|
msg = self.context.insert_const_string(self.module, msg)
|
|
return self.builder.call(fn, (exctype, msg))
|
|
|
|
def err_format(self, exctype, msg, *format_args):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj, self.cstring], var_arg=True)
|
|
fn = self._get_function(fnty, name="PyErr_Format")
|
|
if isinstance(exctype, str):
|
|
exctype = self.get_c_object(exctype)
|
|
if isinstance(msg, str):
|
|
msg = self.context.insert_const_string(self.module, msg)
|
|
return self.builder.call(fn, (exctype, msg) + tuple(format_args))
|
|
|
|
def raise_object(self, exc=None):
|
|
"""
|
|
Raise an arbitrary exception (type or value or (type, args)
|
|
or None - if reraising). A reference to the argument is consumed.
|
|
"""
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_do_raise")
|
|
if exc is None:
|
|
exc = self.make_none()
|
|
return self.builder.call(fn, (exc,))
|
|
|
|
def err_set_object(self, exctype, excval):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyErr_SetObject")
|
|
if isinstance(exctype, str):
|
|
exctype = self.get_c_object(exctype)
|
|
return self.builder.call(fn, (exctype, excval))
|
|
|
|
def err_set_none(self, exctype):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyErr_SetNone")
|
|
if isinstance(exctype, str):
|
|
exctype = self.get_c_object(exctype)
|
|
return self.builder.call(fn, (exctype,))
|
|
|
|
def err_write_unraisable(self, obj):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyErr_WriteUnraisable")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
def err_fetch(self, pty, pval, ptb):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobjptr] * 3)
|
|
fn = self._get_function(fnty, name="PyErr_Fetch")
|
|
return self.builder.call(fn, (pty, pval, ptb))
|
|
|
|
def err_restore(self, ty, val, tb):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj] * 3)
|
|
fn = self._get_function(fnty, name="PyErr_Restore")
|
|
return self.builder.call(fn, (ty, val, tb))
|
|
|
|
@contextlib.contextmanager
|
|
def err_push(self, keep_new=False):
|
|
"""
|
|
Temporarily push the current error indicator while the code
|
|
block is executed. If *keep_new* is True and the code block
|
|
raises a new error, the new error is kept, otherwise the old
|
|
error indicator is restored at the end of the block.
|
|
"""
|
|
pty, pval, ptb = [cgutils.alloca_once(self.builder, self.pyobj)
|
|
for i in range(3)]
|
|
self.err_fetch(pty, pval, ptb)
|
|
yield
|
|
ty = self.builder.load(pty)
|
|
val = self.builder.load(pval)
|
|
tb = self.builder.load(ptb)
|
|
if keep_new:
|
|
new_error = cgutils.is_not_null(self.builder, self.err_occurred())
|
|
with self.builder.if_else(new_error, likely=False) as (if_error, if_ok):
|
|
with if_error:
|
|
# Code block raised an error, keep it
|
|
self.decref(ty)
|
|
self.decref(val)
|
|
self.decref(tb)
|
|
with if_ok:
|
|
# Restore previous error
|
|
self.err_restore(ty, val, tb)
|
|
else:
|
|
self.err_restore(ty, val, tb)
|
|
|
|
def get_c_object(self, name):
|
|
"""
|
|
Get a Python object through its C-accessible *name*
|
|
(e.g. "PyExc_ValueError"). The underlying variable must be
|
|
a `PyObject *`, and the value of that pointer is returned.
|
|
"""
|
|
# A LLVM global variable is implicitly a pointer to the declared
|
|
# type, so fix up by using pyobj.pointee.
|
|
return self.context.get_c_value(self.builder, self.pyobj.pointee, name,
|
|
dllimport=True)
|
|
|
|
def raise_missing_global_error(self, name):
|
|
msg = "global name '%s' is not defined" % name
|
|
cstr = self.context.insert_const_string(self.module, msg)
|
|
self.err_set_string("PyExc_NameError", cstr)
|
|
|
|
def raise_missing_name_error(self, name):
|
|
msg = "name '%s' is not defined" % name
|
|
cstr = self.context.insert_const_string(self.module, msg)
|
|
self.err_set_string("PyExc_NameError", cstr)
|
|
|
|
def fatal_error(self, msg):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.cstring])
|
|
fn = self._get_function(fnty, name="Py_FatalError")
|
|
fn.attributes.add("noreturn")
|
|
cstr = self.context.insert_const_string(self.module, msg)
|
|
self.builder.call(fn, (cstr,))
|
|
|
|
#
|
|
# Concrete dict API
|
|
#
|
|
|
|
def dict_getitem_string(self, dic, name):
|
|
"""Lookup name inside dict
|
|
|
|
Returns a borrowed reference
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.cstring])
|
|
fn = self._get_function(fnty, name="PyDict_GetItemString")
|
|
cstr = self.context.insert_const_string(self.module, name)
|
|
return self.builder.call(fn, [dic, cstr])
|
|
|
|
def dict_getitem(self, dic, name):
|
|
"""Lookup name inside dict
|
|
|
|
Returns a borrowed reference
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyDict_GetItem")
|
|
return self.builder.call(fn, [dic, name])
|
|
|
|
def dict_new(self, presize=0):
|
|
if presize == 0:
|
|
fnty = ir.FunctionType(self.pyobj, ())
|
|
fn = self._get_function(fnty, name="PyDict_New")
|
|
return self.builder.call(fn, ())
|
|
else:
|
|
fnty = ir.FunctionType(self.pyobj, [self.py_ssize_t])
|
|
fn = self._get_function(fnty, name="_PyDict_NewPresized")
|
|
return self.builder.call(fn,
|
|
[Constant(self.py_ssize_t, int(presize))])
|
|
|
|
def dict_setitem(self, dictobj, nameobj, valobj):
|
|
fnty = ir.FunctionType(ir.IntType(32), (self.pyobj, self.pyobj,
|
|
self.pyobj))
|
|
fn = self._get_function(fnty, name="PyDict_SetItem")
|
|
return self.builder.call(fn, (dictobj, nameobj, valobj))
|
|
|
|
def dict_setitem_string(self, dictobj, name, valobj):
|
|
fnty = ir.FunctionType(ir.IntType(32), (self.pyobj, self.cstring,
|
|
self.pyobj))
|
|
fn = self._get_function(fnty, name="PyDict_SetItemString")
|
|
cstr = self.context.insert_const_string(self.module, name)
|
|
return self.builder.call(fn, (dictobj, cstr, valobj))
|
|
|
|
def dict_pack(self, keyvalues):
|
|
"""
|
|
Args
|
|
-----
|
|
keyvalues: iterable of (str, llvm.Value of PyObject*)
|
|
"""
|
|
dictobj = self.dict_new()
|
|
with self.if_object_ok(dictobj):
|
|
for k, v in keyvalues:
|
|
self.dict_setitem_string(dictobj, k, v)
|
|
return dictobj
|
|
|
|
#
|
|
# Concrete number APIs
|
|
#
|
|
|
|
def float_from_double(self, fval):
|
|
fnty = ir.FunctionType(self.pyobj, [self.double])
|
|
fn = self._get_function(fnty, name="PyFloat_FromDouble")
|
|
return self.builder.call(fn, [fval])
|
|
|
|
def number_as_ssize_t(self, numobj):
|
|
fnty = ir.FunctionType(self.py_ssize_t, [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_AsSsize_t")
|
|
# We don't want any clipping, so pass OverflowError as the 2nd arg
|
|
exc_class = self.get_c_object("PyExc_OverflowError")
|
|
return self.builder.call(fn, [numobj, exc_class])
|
|
|
|
def number_long(self, numobj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_Long")
|
|
return self.builder.call(fn, [numobj])
|
|
|
|
def long_as_ulonglong(self, numobj):
|
|
fnty = ir.FunctionType(self.ulonglong, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyLong_AsUnsignedLongLong")
|
|
return self.builder.call(fn, [numobj])
|
|
|
|
def long_as_longlong(self, numobj):
|
|
fnty = ir.FunctionType(self.ulonglong, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyLong_AsLongLong")
|
|
return self.builder.call(fn, [numobj])
|
|
|
|
def long_as_voidptr(self, numobj):
|
|
"""
|
|
Convert the given Python integer to a void*. This is recommended
|
|
over number_as_ssize_t as it isn't affected by signedness.
|
|
"""
|
|
fnty = ir.FunctionType(self.voidptr, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyLong_AsVoidPtr")
|
|
return self.builder.call(fn, [numobj])
|
|
|
|
def _long_from_native_int(self, ival, func_name, native_int_type,
|
|
signed):
|
|
fnty = ir.FunctionType(self.pyobj, [native_int_type])
|
|
fn = self._get_function(fnty, name=func_name)
|
|
resptr = cgutils.alloca_once(self.builder, self.pyobj)
|
|
fn = self._get_function(fnty, name=func_name)
|
|
self.builder.store(self.builder.call(fn, [ival]), resptr)
|
|
|
|
return self.builder.load(resptr)
|
|
|
|
def long_from_long(self, ival):
|
|
func_name = "PyLong_FromLong"
|
|
fnty = ir.FunctionType(self.pyobj, [self.long])
|
|
fn = self._get_function(fnty, name=func_name)
|
|
return self.builder.call(fn, [ival])
|
|
|
|
def long_from_ulong(self, ival):
|
|
return self._long_from_native_int(ival, "PyLong_FromUnsignedLong",
|
|
self.long, signed=False)
|
|
|
|
def long_from_ssize_t(self, ival):
|
|
return self._long_from_native_int(ival, "PyLong_FromSsize_t",
|
|
self.py_ssize_t, signed=True)
|
|
|
|
def long_from_longlong(self, ival):
|
|
return self._long_from_native_int(ival, "PyLong_FromLongLong",
|
|
self.longlong, signed=True)
|
|
|
|
def long_from_ulonglong(self, ival):
|
|
return self._long_from_native_int(ival, "PyLong_FromUnsignedLongLong",
|
|
self.ulonglong, signed=False)
|
|
|
|
def long_from_signed_int(self, ival):
|
|
"""
|
|
Return a Python integer from any native integer value.
|
|
"""
|
|
bits = ival.type.width
|
|
if bits <= self.long.width:
|
|
return self.long_from_long(self.builder.sext(ival, self.long))
|
|
elif bits <= self.longlong.width:
|
|
return self.long_from_longlong(self.builder.sext(ival, self.longlong))
|
|
else:
|
|
raise OverflowError("integer too big (%d bits)" % (bits))
|
|
|
|
def long_from_unsigned_int(self, ival):
|
|
"""
|
|
Same as long_from_signed_int, but for unsigned values.
|
|
"""
|
|
bits = ival.type.width
|
|
if bits <= self.ulong.width:
|
|
return self.long_from_ulong(self.builder.zext(ival, self.ulong))
|
|
elif bits <= self.ulonglong.width:
|
|
return self.long_from_ulonglong(self.builder.zext(ival, self.ulonglong))
|
|
else:
|
|
raise OverflowError("integer too big (%d bits)" % (bits))
|
|
|
|
def _get_number_operator(self, name):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_%s" % name)
|
|
return fn
|
|
|
|
def _call_number_operator(self, name, lhs, rhs, inplace=False):
|
|
if inplace:
|
|
name = "InPlace" + name
|
|
fn = self._get_number_operator(name)
|
|
return self.builder.call(fn, [lhs, rhs])
|
|
|
|
def number_add(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Add", lhs, rhs, inplace=inplace)
|
|
|
|
def number_subtract(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Subtract", lhs, rhs, inplace=inplace)
|
|
|
|
def number_multiply(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Multiply", lhs, rhs, inplace=inplace)
|
|
|
|
def number_truedivide(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("TrueDivide", lhs, rhs, inplace=inplace)
|
|
|
|
def number_floordivide(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("FloorDivide", lhs, rhs, inplace=inplace)
|
|
|
|
def number_remainder(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Remainder", lhs, rhs, inplace=inplace)
|
|
|
|
def number_matrix_multiply(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("MatrixMultiply", lhs, rhs, inplace=inplace)
|
|
|
|
def number_lshift(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Lshift", lhs, rhs, inplace=inplace)
|
|
|
|
def number_rshift(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Rshift", lhs, rhs, inplace=inplace)
|
|
|
|
def number_and(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("And", lhs, rhs, inplace=inplace)
|
|
|
|
def number_or(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Or", lhs, rhs, inplace=inplace)
|
|
|
|
def number_xor(self, lhs, rhs, inplace=False):
|
|
return self._call_number_operator("Xor", lhs, rhs, inplace=inplace)
|
|
|
|
def number_power(self, lhs, rhs, inplace=False):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj] * 3)
|
|
fname = "PyNumber_InPlacePower" if inplace else "PyNumber_Power"
|
|
fn = self._get_function(fnty, fname)
|
|
return self.builder.call(fn, [lhs, rhs, self.borrow_none()])
|
|
|
|
def number_negative(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_Negative")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
def number_positive(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_Positive")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
def number_float(self, val):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_Float")
|
|
return self.builder.call(fn, [val])
|
|
|
|
def number_invert(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyNumber_Invert")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
def float_as_double(self, fobj):
|
|
fnty = ir.FunctionType(self.double, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyFloat_AsDouble")
|
|
return self.builder.call(fn, [fobj])
|
|
|
|
def bool_from_bool(self, bval):
|
|
"""
|
|
Get a Python bool from a LLVM boolean.
|
|
"""
|
|
longval = self.builder.zext(bval, self.long)
|
|
return self.bool_from_long(longval)
|
|
|
|
def bool_from_long(self, ival):
|
|
fnty = ir.FunctionType(self.pyobj, [self.long])
|
|
fn = self._get_function(fnty, name="PyBool_FromLong")
|
|
return self.builder.call(fn, [ival])
|
|
|
|
def complex_from_doubles(self, realval, imagval):
|
|
fnty = ir.FunctionType(self.pyobj, [ir.DoubleType(), ir.DoubleType()])
|
|
fn = self._get_function(fnty, name="PyComplex_FromDoubles")
|
|
return self.builder.call(fn, [realval, imagval])
|
|
|
|
def complex_real_as_double(self, cobj):
|
|
fnty = ir.FunctionType(ir.DoubleType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyComplex_RealAsDouble")
|
|
return self.builder.call(fn, [cobj])
|
|
|
|
def complex_imag_as_double(self, cobj):
|
|
fnty = ir.FunctionType(ir.DoubleType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyComplex_ImagAsDouble")
|
|
return self.builder.call(fn, [cobj])
|
|
|
|
#
|
|
# Concrete slice API
|
|
#
|
|
def slice_as_ints(self, obj):
|
|
"""
|
|
Read the members of a slice of integers.
|
|
|
|
Returns a (ok, start, stop, step) tuple where ok is a boolean and
|
|
the following members are pointer-sized ints.
|
|
"""
|
|
pstart = cgutils.alloca_once(self.builder, self.py_ssize_t)
|
|
pstop = cgutils.alloca_once(self.builder, self.py_ssize_t)
|
|
pstep = cgutils.alloca_once(self.builder, self.py_ssize_t)
|
|
fnty = ir.FunctionType(ir.IntType(32),
|
|
[self.pyobj] + [self.py_ssize_t.as_pointer()] * 3)
|
|
fn = self._get_function(fnty, name="numba_unpack_slice")
|
|
res = self.builder.call(fn, (obj, pstart, pstop, pstep))
|
|
start = self.builder.load(pstart)
|
|
stop = self.builder.load(pstop)
|
|
step = self.builder.load(pstep)
|
|
return cgutils.is_null(self.builder, res), start, stop, step
|
|
|
|
#
|
|
# List and sequence APIs
|
|
#
|
|
|
|
def sequence_getslice(self, obj, start, stop):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.py_ssize_t,
|
|
self.py_ssize_t])
|
|
fn = self._get_function(fnty, name="PySequence_GetSlice")
|
|
return self.builder.call(fn, (obj, start, stop))
|
|
|
|
def sequence_tuple(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PySequence_Tuple")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def sequence_concat(self, obj1, obj2):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PySequence_Concat")
|
|
return self.builder.call(fn, [obj1, obj2])
|
|
|
|
def list_new(self, szval):
|
|
fnty = ir.FunctionType(self.pyobj, [self.py_ssize_t])
|
|
fn = self._get_function(fnty, name="PyList_New")
|
|
return self.builder.call(fn, [szval])
|
|
|
|
def list_size(self, lst):
|
|
fnty = ir.FunctionType(self.py_ssize_t, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyList_Size")
|
|
return self.builder.call(fn, [lst])
|
|
|
|
def list_append(self, lst, val):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyList_Append")
|
|
return self.builder.call(fn, [lst, val])
|
|
|
|
def list_setitem(self, lst, idx, val):
|
|
"""
|
|
Warning: Steals reference to ``val``
|
|
"""
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.py_ssize_t,
|
|
self.pyobj])
|
|
fn = self._get_function(fnty, name="PyList_SetItem")
|
|
return self.builder.call(fn, [lst, idx, val])
|
|
|
|
def list_getitem(self, lst, idx):
|
|
"""
|
|
Returns a borrowed reference.
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.py_ssize_t])
|
|
fn = self._get_function(fnty, name="PyList_GetItem")
|
|
if isinstance(idx, int):
|
|
idx = self.context.get_constant(types.intp, idx)
|
|
return self.builder.call(fn, [lst, idx])
|
|
|
|
def list_setslice(self, lst, start, stop, obj):
|
|
if obj is None:
|
|
obj = self.get_null_object()
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.py_ssize_t,
|
|
self.py_ssize_t, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyList_SetSlice")
|
|
return self.builder.call(fn, (lst, start, stop, obj))
|
|
|
|
|
|
#
|
|
# Concrete tuple API
|
|
#
|
|
|
|
def tuple_getitem(self, tup, idx):
|
|
"""
|
|
Borrow reference
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.py_ssize_t])
|
|
fn = self._get_function(fnty, name="PyTuple_GetItem")
|
|
idx = self.context.get_constant(types.intp, idx)
|
|
return self.builder.call(fn, [tup, idx])
|
|
|
|
def tuple_pack(self, items):
|
|
fnty = ir.FunctionType(self.pyobj, [self.py_ssize_t], var_arg=True)
|
|
fn = self._get_function(fnty, name="PyTuple_Pack")
|
|
n = self.context.get_constant(types.intp, len(items))
|
|
args = [n]
|
|
args.extend(items)
|
|
return self.builder.call(fn, args)
|
|
|
|
def tuple_size(self, tup):
|
|
fnty = ir.FunctionType(self.py_ssize_t, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyTuple_Size")
|
|
return self.builder.call(fn, [tup])
|
|
|
|
def tuple_new(self, count):
|
|
fnty = ir.FunctionType(self.pyobj, [ir.IntType(32)])
|
|
fn = self._get_function(fnty, name='PyTuple_New')
|
|
return self.builder.call(fn, [self.context.get_constant(types.int32,
|
|
count)])
|
|
|
|
def tuple_setitem(self, tuple_val, index, item):
|
|
"""
|
|
Steals a reference to `item`.
|
|
"""
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, ir.IntType(32), self.pyobj])
|
|
setitem_fn = self._get_function(fnty, name='PyTuple_SetItem')
|
|
index = self.context.get_constant(types.int32, index)
|
|
self.builder.call(setitem_fn, [tuple_val, index, item])
|
|
|
|
#
|
|
# Concrete set API
|
|
#
|
|
|
|
def set_new(self, iterable=None):
|
|
if iterable is None:
|
|
iterable = self.get_null_object()
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PySet_New")
|
|
return self.builder.call(fn, [iterable])
|
|
|
|
def set_add(self, set, value):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PySet_Add")
|
|
return self.builder.call(fn, [set, value])
|
|
|
|
def set_clear(self, set):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PySet_Clear")
|
|
return self.builder.call(fn, [set])
|
|
|
|
def set_size(self, set):
|
|
fnty = ir.FunctionType(self.py_ssize_t, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PySet_Size")
|
|
return self.builder.call(fn, [set])
|
|
|
|
def set_update(self, set, iterable):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="_PySet_Update")
|
|
return self.builder.call(fn, [set, iterable])
|
|
|
|
def set_next_entry(self, set, posptr, keyptr, hashptr):
|
|
fnty = ir.FunctionType(ir.IntType(32),
|
|
[self.pyobj, self.py_ssize_t.as_pointer(),
|
|
self.pyobj.as_pointer(), self.py_hash_t.as_pointer()])
|
|
fn = self._get_function(fnty, name="_PySet_NextEntry")
|
|
return self.builder.call(fn, (set, posptr, keyptr, hashptr))
|
|
|
|
@contextlib.contextmanager
|
|
def set_iterate(self, set):
|
|
builder = self.builder
|
|
|
|
hashptr = cgutils.alloca_once(builder, self.py_hash_t, name="hashptr")
|
|
keyptr = cgutils.alloca_once(builder, self.pyobj, name="keyptr")
|
|
posptr = cgutils.alloca_once_value(builder,
|
|
Constant(self.py_ssize_t, 0),
|
|
name="posptr")
|
|
|
|
bb_body = builder.append_basic_block("bb_body")
|
|
bb_end = builder.append_basic_block("bb_end")
|
|
|
|
builder.branch(bb_body)
|
|
def do_break():
|
|
builder.branch(bb_end)
|
|
|
|
with builder.goto_block(bb_body):
|
|
r = self.set_next_entry(set, posptr, keyptr, hashptr)
|
|
finished = cgutils.is_null(builder, r)
|
|
with builder.if_then(finished, likely=False):
|
|
builder.branch(bb_end)
|
|
yield _IteratorLoop(builder.load(keyptr), do_break)
|
|
builder.branch(bb_body)
|
|
|
|
builder.position_at_end(bb_end)
|
|
|
|
#
|
|
# GIL APIs
|
|
#
|
|
|
|
def gil_ensure(self):
|
|
"""
|
|
Ensure the GIL is acquired.
|
|
The returned value must be consumed by gil_release().
|
|
"""
|
|
gilptrty = ir.PointerType(self.gil_state)
|
|
fnty = ir.FunctionType(ir.VoidType(), [gilptrty])
|
|
fn = self._get_function(fnty, "numba_gil_ensure")
|
|
gilptr = cgutils.alloca_once(self.builder, self.gil_state)
|
|
self.builder.call(fn, [gilptr])
|
|
return gilptr
|
|
|
|
def gil_release(self, gil):
|
|
"""
|
|
Release the acquired GIL by gil_ensure().
|
|
Must be paired with a gil_ensure().
|
|
"""
|
|
gilptrty = ir.PointerType(self.gil_state)
|
|
fnty = ir.FunctionType(ir.VoidType(), [gilptrty])
|
|
fn = self._get_function(fnty, "numba_gil_release")
|
|
return self.builder.call(fn, [gil])
|
|
|
|
def save_thread(self):
|
|
"""
|
|
Release the GIL and return the former thread state
|
|
(an opaque non-NULL pointer).
|
|
"""
|
|
fnty = ir.FunctionType(self.voidptr, [])
|
|
fn = self._get_function(fnty, name="PyEval_SaveThread")
|
|
return self.builder.call(fn, [])
|
|
|
|
def restore_thread(self, thread_state):
|
|
"""
|
|
Restore the given thread state by reacquiring the GIL.
|
|
"""
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.voidptr])
|
|
fn = self._get_function(fnty, name="PyEval_RestoreThread")
|
|
self.builder.call(fn, [thread_state])
|
|
|
|
#
|
|
# Generic object private data (a way of associating an arbitrary void *
|
|
# pointer to an arbitrary Python object).
|
|
#
|
|
|
|
def object_get_private_data(self, obj):
|
|
fnty = ir.FunctionType(self.voidptr, [self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_get_pyobject_private_data")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
def object_set_private_data(self, obj, ptr):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj, self.voidptr])
|
|
fn = self._get_function(fnty, name="numba_set_pyobject_private_data")
|
|
return self.builder.call(fn, (obj, ptr))
|
|
|
|
def object_reset_private_data(self, obj):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_reset_pyobject_private_data")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
|
|
#
|
|
# Other APIs (organize them better!)
|
|
#
|
|
|
|
def import_module_noblock(self, modname):
|
|
fnty = ir.FunctionType(self.pyobj, [self.cstring])
|
|
fn = self._get_function(fnty, name="PyImport_ImportModuleNoBlock")
|
|
return self.builder.call(fn, [modname])
|
|
|
|
def call_function_objargs(self, callee, objargs):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj], var_arg=True)
|
|
fn = self._get_function(fnty, name="PyObject_CallFunctionObjArgs")
|
|
args = [callee] + list(objargs)
|
|
args.append(self.context.get_constant_null(types.pyobject))
|
|
return self.builder.call(fn, args)
|
|
|
|
def call_method(self, callee, method, objargs=()):
|
|
cname = self.context.insert_const_string(self.module, method)
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.cstring, self.cstring],
|
|
var_arg=True)
|
|
fn = self._get_function(fnty, name="PyObject_CallMethod")
|
|
fmt = 'O' * len(objargs)
|
|
cfmt = self.context.insert_const_string(self.module, fmt)
|
|
args = [callee, cname, cfmt]
|
|
if objargs:
|
|
args.extend(objargs)
|
|
args.append(self.context.get_constant_null(types.pyobject))
|
|
return self.builder.call(fn, args)
|
|
|
|
def call(self, callee, args=None, kws=None):
|
|
if args_was_none := args is None:
|
|
args = self.tuple_new(0)
|
|
if kws is None:
|
|
kws = self.get_null_object()
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj] * 3)
|
|
fn = self._get_function(fnty, name="PyObject_Call")
|
|
result = self.builder.call(fn, (callee, args, kws))
|
|
if args_was_none:
|
|
self.decref(args)
|
|
return result
|
|
|
|
def object_type(self, obj):
|
|
"""Emit a call to ``PyObject_Type(obj)`` to get the type of ``obj``.
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_Type")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
def object_istrue(self, obj):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_IsTrue")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def object_not(self, obj):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_Not")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def object_richcompare(self, lhs, rhs, opstr):
|
|
"""
|
|
Refer to Python source Include/object.h for macros definition
|
|
of the opid.
|
|
"""
|
|
ops = ['<', '<=', '==', '!=', '>', '>=']
|
|
if opstr in ops:
|
|
opid = ops.index(opstr)
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj, ir.IntType(32)])
|
|
fn = self._get_function(fnty, name="PyObject_RichCompare")
|
|
lopid = self.context.get_constant(types.int32, opid)
|
|
return self.builder.call(fn, (lhs, rhs, lopid))
|
|
elif opstr == 'is':
|
|
bitflag = self.builder.icmp_unsigned('==', lhs, rhs)
|
|
return self.bool_from_bool(bitflag)
|
|
elif opstr == 'is not':
|
|
bitflag = self.builder.icmp_unsigned('!=', lhs, rhs)
|
|
return self.bool_from_bool(bitflag)
|
|
elif opstr in ('in', 'not in'):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PySequence_Contains")
|
|
status = self.builder.call(fn, (rhs, lhs))
|
|
negone = self.context.get_constant(types.int32, -1)
|
|
is_good = self.builder.icmp_unsigned('!=', status, negone)
|
|
# Stack allocate output and initialize to Null
|
|
outptr = cgutils.alloca_once_value(self.builder,
|
|
Constant(self.pyobj, None))
|
|
# If PySequence_Contains returns non-error value
|
|
with cgutils.if_likely(self.builder, is_good):
|
|
if opstr == 'not in':
|
|
status = self.builder.not_(status)
|
|
# Store the status as a boolean object
|
|
truncated = self.builder.trunc(status, ir.IntType(1))
|
|
self.builder.store(self.bool_from_bool(truncated),
|
|
outptr)
|
|
|
|
return self.builder.load(outptr)
|
|
else:
|
|
raise NotImplementedError("Unknown operator {op!r}".format(
|
|
op=opstr))
|
|
|
|
def iter_next(self, iterobj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyIter_Next")
|
|
return self.builder.call(fn, [iterobj])
|
|
|
|
def object_getiter(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_GetIter")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def object_getattr_string(self, obj, attr):
|
|
cstr = self.context.insert_const_string(self.module, attr)
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.cstring])
|
|
fn = self._get_function(fnty, name="PyObject_GetAttrString")
|
|
return self.builder.call(fn, [obj, cstr])
|
|
|
|
def object_getattr(self, obj, attr):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_GetAttr")
|
|
return self.builder.call(fn, [obj, attr])
|
|
|
|
def object_setattr_string(self, obj, attr, val):
|
|
cstr = self.context.insert_const_string(self.module, attr)
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.cstring, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_SetAttrString")
|
|
return self.builder.call(fn, [obj, cstr, val])
|
|
|
|
def object_setattr(self, obj, attr, val):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_SetAttr")
|
|
return self.builder.call(fn, [obj, attr, val])
|
|
|
|
def object_delattr_string(self, obj, attr):
|
|
# PyObject_DelAttrString() is actually a C macro calling
|
|
# PyObject_SetAttrString() with value == NULL.
|
|
return self.object_setattr_string(obj, attr, self.get_null_object())
|
|
|
|
def object_delattr(self, obj, attr):
|
|
# PyObject_DelAttr() is actually a C macro calling
|
|
# PyObject_SetAttr() with value == NULL.
|
|
return self.object_setattr(obj, attr, self.get_null_object())
|
|
|
|
def object_getitem(self, obj, key):
|
|
"""
|
|
Return obj[key]
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_GetItem")
|
|
return self.builder.call(fn, (obj, key))
|
|
|
|
def object_setitem(self, obj, key, val):
|
|
"""
|
|
obj[key] = val
|
|
"""
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_SetItem")
|
|
return self.builder.call(fn, (obj, key, val))
|
|
|
|
def object_delitem(self, obj, key):
|
|
"""
|
|
del obj[key]
|
|
"""
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_DelItem")
|
|
return self.builder.call(fn, (obj, key))
|
|
|
|
def string_as_string(self, strobj):
|
|
fnty = ir.FunctionType(self.cstring, [self.pyobj])
|
|
fname = "PyUnicode_AsUTF8"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [strobj])
|
|
|
|
def string_as_string_and_size(self, strobj):
|
|
"""
|
|
Returns a tuple of ``(ok, buffer, length)``.
|
|
The ``ok`` is i1 value that is set if ok.
|
|
The ``buffer`` is a i8* of the output buffer.
|
|
The ``length`` is a i32/i64 (py_ssize_t) of the length of the buffer.
|
|
"""
|
|
|
|
p_length = cgutils.alloca_once(self.builder, self.py_ssize_t)
|
|
fnty = ir.FunctionType(self.cstring, [self.pyobj,
|
|
self.py_ssize_t.as_pointer()])
|
|
fname = "PyUnicode_AsUTF8AndSize"
|
|
fn = self._get_function(fnty, name=fname)
|
|
|
|
buffer = self.builder.call(fn, [strobj, p_length])
|
|
ok = self.builder.icmp_unsigned('!=',
|
|
Constant(buffer.type, None),
|
|
buffer)
|
|
return (ok, buffer, self.builder.load(p_length))
|
|
|
|
def string_as_string_size_and_kind(self, strobj):
|
|
"""
|
|
Returns a tuple of ``(ok, buffer, length, kind)``.
|
|
The ``ok`` is i1 value that is set if ok.
|
|
The ``buffer`` is a i8* of the output buffer.
|
|
The ``length`` is a i32/i64 (py_ssize_t) of the length of the buffer.
|
|
The ``kind`` is a i32 (int32) of the Unicode kind constant
|
|
The ``hash`` is a long/uint64_t (py_hash_t) of the Unicode constant hash
|
|
"""
|
|
p_length = cgutils.alloca_once(self.builder, self.py_ssize_t)
|
|
p_kind = cgutils.alloca_once(self.builder, ir.IntType(32))
|
|
p_ascii = cgutils.alloca_once(self.builder, ir.IntType(32))
|
|
p_hash = cgutils.alloca_once(self.builder, self.py_hash_t)
|
|
fnty = ir.FunctionType(self.cstring, [self.pyobj,
|
|
self.py_ssize_t.as_pointer(),
|
|
ir.IntType(32).as_pointer(),
|
|
ir.IntType(32).as_pointer(),
|
|
self.py_hash_t.as_pointer()])
|
|
fname = "numba_extract_unicode"
|
|
fn = self._get_function(fnty, name=fname)
|
|
|
|
buffer = self.builder.call(
|
|
fn, [strobj, p_length, p_kind, p_ascii, p_hash])
|
|
ok = self.builder.icmp_unsigned('!=',
|
|
Constant(buffer.type, None),
|
|
buffer)
|
|
return (ok, buffer, self.builder.load(p_length),
|
|
self.builder.load(p_kind), self.builder.load(p_ascii),
|
|
self.builder.load(p_hash))
|
|
|
|
def string_from_string_and_size(self, string, size):
|
|
fnty = ir.FunctionType(self.pyobj, [self.cstring, self.py_ssize_t])
|
|
fname = "PyString_FromStringAndSize"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [string, size])
|
|
|
|
def string_from_string(self, string):
|
|
fnty = ir.FunctionType(self.pyobj, [self.cstring])
|
|
fname = "PyUnicode_FromString"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [string])
|
|
|
|
def string_from_kind_and_data(self, kind, string, size):
|
|
fnty = ir.FunctionType(self.pyobj, [ir.IntType(32), self.cstring, self.py_ssize_t])
|
|
fname = "PyUnicode_FromKindAndData"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [kind, string, size])
|
|
|
|
def bytes_as_string(self, obj):
|
|
fnty = ir.FunctionType(self.cstring, [self.pyobj])
|
|
fname = "PyBytes_AsString"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def bytes_as_string_and_size(self, obj, p_buffer, p_length):
|
|
fnty = ir.FunctionType(
|
|
ir.IntType(32),
|
|
[self.pyobj, self.cstring.as_pointer(), self.py_ssize_t.as_pointer()],
|
|
)
|
|
fname = "PyBytes_AsStringAndSize"
|
|
fn = self._get_function(fnty, name=fname)
|
|
result = self.builder.call(fn, [obj, p_buffer, p_length])
|
|
ok = self.builder.icmp_signed("!=", Constant(result.type, -1), result)
|
|
return ok
|
|
|
|
def bytes_from_string_and_size(self, string, size):
|
|
fnty = ir.FunctionType(self.pyobj, [self.cstring, self.py_ssize_t])
|
|
fname = "PyBytes_FromStringAndSize"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [string, size])
|
|
|
|
def object_hash(self, obj):
|
|
fnty = ir.FunctionType(self.py_hash_t, [self.pyobj, ])
|
|
fname = "PyObject_Hash"
|
|
fn = self._get_function(fnty, name=fname)
|
|
return self.builder.call(fn, [obj,])
|
|
|
|
def object_str(self, obj):
|
|
fnty = ir.FunctionType(self.pyobj, [self.pyobj])
|
|
fn = self._get_function(fnty, name="PyObject_Str")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def make_none(self):
|
|
obj = self.borrow_none()
|
|
self.incref(obj)
|
|
return obj
|
|
|
|
def borrow_none(self):
|
|
return self.get_c_object("_Py_NoneStruct")
|
|
|
|
def sys_write_stdout(self, fmt, *args):
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.cstring], var_arg=True)
|
|
fn = self._get_function(fnty, name="PySys_FormatStdout")
|
|
return self.builder.call(fn, (fmt,) + args)
|
|
|
|
def object_dump(self, obj):
|
|
"""
|
|
Dump a Python object on C stderr. For debugging purposes.
|
|
"""
|
|
fnty = ir.FunctionType(ir.VoidType(), [self.pyobj])
|
|
fn = self._get_function(fnty, name="_PyObject_Dump")
|
|
return self.builder.call(fn, (obj,))
|
|
|
|
#
|
|
# NRT (Numba runtime) APIs
|
|
#
|
|
|
|
def nrt_adapt_ndarray_to_python(self, aryty, ary, dtypeptr):
|
|
assert self.context.enable_nrt, "NRT required"
|
|
|
|
intty = ir.IntType(32)
|
|
# Embed the Python type of the array (maybe subclass) in the LLVM IR.
|
|
serial_aryty_pytype = self.unserialize(self.serialize_object(aryty.box_type))
|
|
|
|
fnty = ir.FunctionType(self.pyobj,
|
|
[self.voidptr, self.pyobj, intty, intty, self.pyobj])
|
|
fn = self._get_function(fnty, name="NRT_adapt_ndarray_to_python_acqref")
|
|
fn.args[0].add_attribute('nocapture')
|
|
|
|
ndim = self.context.get_constant(types.int32, aryty.ndim)
|
|
writable = self.context.get_constant(types.int32, int(aryty.mutable))
|
|
|
|
aryptr = cgutils.alloca_once_value(self.builder, ary)
|
|
return self.builder.call(fn, [self.builder.bitcast(aryptr,
|
|
self.voidptr),
|
|
serial_aryty_pytype,
|
|
ndim, writable, dtypeptr])
|
|
|
|
def nrt_meminfo_new_from_pyobject(self, data, pyobj):
|
|
"""
|
|
Allocate a new MemInfo with data payload borrowed from a python
|
|
object.
|
|
"""
|
|
mod = self.builder.module
|
|
fnty = ir.FunctionType(
|
|
cgutils.voidptr_t,
|
|
[cgutils.voidptr_t, cgutils.voidptr_t],
|
|
)
|
|
fn = cgutils.get_or_insert_function(
|
|
mod,
|
|
fnty,
|
|
"NRT_meminfo_new_from_pyobject",
|
|
)
|
|
fn.args[0].add_attribute('nocapture')
|
|
fn.args[1].add_attribute('nocapture')
|
|
fn.return_value.add_attribute("noalias")
|
|
return self.builder.call(fn, [data, pyobj])
|
|
|
|
def nrt_meminfo_as_pyobject(self, miptr):
|
|
mod = self.builder.module
|
|
fnty = ir.FunctionType(
|
|
self.pyobj,
|
|
[cgutils.voidptr_t]
|
|
)
|
|
fn = cgutils.get_or_insert_function(
|
|
mod,
|
|
fnty,
|
|
'NRT_meminfo_as_pyobject',
|
|
)
|
|
fn.return_value.add_attribute("noalias")
|
|
return self.builder.call(fn, [miptr])
|
|
|
|
def nrt_meminfo_from_pyobject(self, miobj):
|
|
mod = self.builder.module
|
|
fnty = ir.FunctionType(
|
|
cgutils.voidptr_t,
|
|
[self.pyobj]
|
|
)
|
|
fn = cgutils.get_or_insert_function(
|
|
mod,
|
|
fnty,
|
|
'NRT_meminfo_from_pyobject',
|
|
)
|
|
fn.return_value.add_attribute("noalias")
|
|
return self.builder.call(fn, [miobj])
|
|
|
|
def nrt_adapt_ndarray_from_python(self, ary, ptr):
|
|
assert self.context.enable_nrt
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.voidptr])
|
|
fn = self._get_function(fnty, name="NRT_adapt_ndarray_from_python")
|
|
fn.args[0].add_attribute('nocapture')
|
|
fn.args[1].add_attribute('nocapture')
|
|
return self.builder.call(fn, (ary, ptr))
|
|
|
|
def nrt_adapt_buffer_from_python(self, buf, ptr):
|
|
assert self.context.enable_nrt
|
|
fnty = ir.FunctionType(ir.VoidType(), [ir.PointerType(self.py_buffer_t),
|
|
self.voidptr])
|
|
fn = self._get_function(fnty, name="NRT_adapt_buffer_from_python")
|
|
fn.args[0].add_attribute('nocapture')
|
|
fn.args[1].add_attribute('nocapture')
|
|
return self.builder.call(fn, (buf, ptr))
|
|
|
|
# ------ utils -----
|
|
|
|
def _get_function(self, fnty, name):
|
|
return cgutils.get_or_insert_function(self.module, fnty, name)
|
|
|
|
def alloca_obj(self):
|
|
return self.builder.alloca(self.pyobj)
|
|
|
|
def alloca_buffer(self):
|
|
"""
|
|
Return a pointer to a stack-allocated, zero-initialized Py_buffer.
|
|
"""
|
|
# Treat the buffer as an opaque array of bytes
|
|
ptr = cgutils.alloca_once_value(self.builder,
|
|
Constant(self.py_buffer_t, None))
|
|
return ptr
|
|
|
|
@contextlib.contextmanager
|
|
def if_object_ok(self, obj):
|
|
with cgutils.if_likely(self.builder,
|
|
cgutils.is_not_null(self.builder, obj)):
|
|
yield
|
|
|
|
def print_object(self, obj):
|
|
strobj = self.object_str(obj)
|
|
cstr = self.string_as_string(strobj)
|
|
fmt = self.context.insert_const_string(self.module, "%s")
|
|
self.sys_write_stdout(fmt, cstr)
|
|
self.decref(strobj)
|
|
|
|
def print_string(self, text):
|
|
fmt = self.context.insert_const_string(self.module, text)
|
|
self.sys_write_stdout(fmt)
|
|
|
|
def get_null_object(self):
|
|
return Constant(self.pyobj, None)
|
|
|
|
def return_none(self):
|
|
none = self.make_none()
|
|
self.builder.ret(none)
|
|
|
|
def list_pack(self, items):
|
|
n = len(items)
|
|
seq = self.list_new(self.context.get_constant(types.intp, n))
|
|
with self.if_object_ok(seq):
|
|
for i in range(n):
|
|
idx = self.context.get_constant(types.intp, i)
|
|
self.incref(items[i])
|
|
self.list_setitem(seq, idx, items[i])
|
|
return seq
|
|
|
|
def unserialize(self, structptr):
|
|
"""
|
|
Unserialize some data. *structptr* should be a pointer to
|
|
a {i8* data, i32 length, i8* hashbuf, i8* func_ptr, i32 alloc_flag}
|
|
structure.
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj,
|
|
(self.voidptr, ir.IntType(32), self.voidptr))
|
|
fn = self._get_function(fnty, name="numba_unpickle")
|
|
ptr = self.builder.extract_value(self.builder.load(structptr), 0)
|
|
n = self.builder.extract_value(self.builder.load(structptr), 1)
|
|
hashed = self.builder.extract_value(self.builder.load(structptr), 2)
|
|
return self.builder.call(fn, (ptr, n, hashed))
|
|
|
|
def build_dynamic_excinfo_struct(self, struct_gv, exc_args):
|
|
"""
|
|
Serialize some data at runtime. Returns a pointer to a python tuple
|
|
(bytes_data, hash) where the first element is the serialized data as
|
|
bytes and the second its hash.
|
|
"""
|
|
fnty = ir.FunctionType(self.pyobj, (self.pyobj, self.pyobj))
|
|
fn = self._get_function(fnty, name="numba_runtime_build_excinfo_struct")
|
|
return self.builder.call(fn, (struct_gv, exc_args))
|
|
|
|
def serialize_uncached(self, obj):
|
|
"""
|
|
Same as serialize_object(), but don't create a global variable,
|
|
simply return a literal for structure:
|
|
{i8* data, i32 length, i8* hashbuf, i8* func_ptr, i32 alloc_flag}
|
|
"""
|
|
# First make the array constant
|
|
data = serialize.dumps(obj)
|
|
assert len(data) < 2**31
|
|
name = ".const.pickledata.%s" % (id(obj) if config.DIFF_IR == 0 else "DIFF_IR")
|
|
bdata = cgutils.make_bytearray(data)
|
|
# Make SHA1 hash on the pickled content
|
|
# NOTE: update buffer size in numba_unpickle() when changing the
|
|
# hash algorithm.
|
|
hashed = cgutils.make_bytearray(hashlib.sha1(data).digest())
|
|
arr = self.context.insert_unique_const(self.module, name, bdata)
|
|
hasharr = self.context.insert_unique_const(
|
|
self.module, f"{name}.sha1", hashed,
|
|
)
|
|
# Then populate the structure constant
|
|
struct = Constant.literal_struct([
|
|
arr.bitcast(self.voidptr),
|
|
Constant(ir.IntType(32), arr.type.pointee.count),
|
|
hasharr.bitcast(self.voidptr),
|
|
cgutils.get_null_value(self.voidptr),
|
|
Constant(ir.IntType(32), 0),
|
|
])
|
|
return struct
|
|
|
|
def serialize_object(self, obj):
|
|
"""
|
|
Serialize the given object in the bitcode, and return it
|
|
as a pointer to a
|
|
{i8* data, i32 length, i8* hashbuf, i8* fn_ptr, i32 alloc_flag},
|
|
structure constant (suitable for passing to unserialize()).
|
|
"""
|
|
try:
|
|
gv = self.module.__serialized[obj]
|
|
except KeyError:
|
|
struct = self.serialize_uncached(obj)
|
|
name = ".const.picklebuf.%s" % (id(obj) if config.DIFF_IR == 0 else "DIFF_IR")
|
|
gv = self.context.insert_unique_const(self.module, name, struct)
|
|
# Make the id() (and hence the name) unique while populating the module.
|
|
self.module.__serialized[obj] = gv
|
|
return gv
|
|
|
|
def c_api_error(self):
|
|
return cgutils.is_not_null(self.builder, self.err_occurred())
|
|
|
|
def to_native_value(self, typ, obj):
|
|
"""
|
|
Unbox the Python object as the given Numba type.
|
|
A NativeValue instance is returned.
|
|
"""
|
|
from numba.core.boxing import unbox_unsupported
|
|
|
|
impl = _unboxers.lookup(typ.__class__, unbox_unsupported)
|
|
c = _UnboxContext(self.context, self.builder, self)
|
|
return impl(typ, obj, c)
|
|
|
|
def from_native_return(self, typ, val, env_manager):
|
|
assert not isinstance(typ, types.Optional), "callconv should have " \
|
|
"prevented the return of " \
|
|
"optional value"
|
|
out = self.from_native_value(typ, val, env_manager)
|
|
return out
|
|
|
|
def from_native_value(self, typ, val, env_manager=None):
|
|
"""
|
|
Box the native value of the given Numba type. A Python object
|
|
pointer is returned (NULL if an error occurred).
|
|
This method steals any native (NRT) reference embedded in *val*.
|
|
"""
|
|
from numba.core.boxing import box_unsupported
|
|
|
|
impl = _boxers.lookup(typ.__class__, box_unsupported)
|
|
|
|
c = _BoxContext(self.context, self.builder, self, env_manager)
|
|
return impl(typ, val, c)
|
|
|
|
def reflect_native_value(self, typ, val, env_manager=None):
|
|
"""
|
|
Reflect the native value onto its Python original, if any.
|
|
An error bit (as an LLVM value) is returned.
|
|
"""
|
|
impl = _reflectors.lookup(typ.__class__)
|
|
if impl is None:
|
|
# Reflection isn't needed for most types
|
|
return cgutils.false_bit
|
|
|
|
is_error = cgutils.alloca_once_value(self.builder, cgutils.false_bit)
|
|
c = _ReflectContext(self.context, self.builder, self, env_manager,
|
|
is_error)
|
|
impl(typ, val, c)
|
|
return self.builder.load(c.is_error)
|
|
|
|
def to_native_generator(self, obj, typ):
|
|
"""
|
|
Extract the generator structure pointer from a generator *obj*
|
|
(a _dynfunc.Generator instance).
|
|
"""
|
|
gen_ptr_ty = ir.PointerType(self.context.get_data_type(typ))
|
|
value = self.context.get_generator_state(self.builder, obj, gen_ptr_ty)
|
|
return NativeValue(value)
|
|
|
|
def from_native_generator(self, val, typ, env=None):
|
|
"""
|
|
Make a Numba generator (a _dynfunc.Generator instance) from a
|
|
generator structure pointer *val*.
|
|
*env* is an optional _dynfunc.Environment instance to be wrapped
|
|
in the generator.
|
|
"""
|
|
llty = self.context.get_data_type(typ)
|
|
assert not llty.is_pointer
|
|
gen_struct_size = self.context.get_abi_sizeof(llty)
|
|
|
|
gendesc = self.context.get_generator_desc(typ)
|
|
|
|
# This is the PyCFunctionWithKeywords generated by PyCallWrapper
|
|
genfnty = ir.FunctionType(self.pyobj, [self.pyobj, self.pyobj, self.pyobj])
|
|
genfn = self._get_function(genfnty, name=gendesc.llvm_cpython_wrapper_name)
|
|
|
|
# This is the raw finalizer generated by _lower_generator_finalize_func()
|
|
finalizerty = ir.FunctionType(ir.VoidType(), [self.voidptr])
|
|
if typ.has_finalizer:
|
|
finalizer = self._get_function(finalizerty, name=gendesc.llvm_finalizer_name)
|
|
else:
|
|
finalizer = Constant(ir.PointerType(finalizerty), None)
|
|
|
|
# PyObject *numba_make_generator(state_size, initial_state, nextfunc, finalizer, env)
|
|
fnty = ir.FunctionType(self.pyobj, [self.py_ssize_t,
|
|
self.voidptr,
|
|
ir.PointerType(genfnty),
|
|
ir.PointerType(finalizerty),
|
|
self.voidptr])
|
|
fn = self._get_function(fnty, name="numba_make_generator")
|
|
|
|
state_size = Constant(self.py_ssize_t, gen_struct_size)
|
|
initial_state = self.builder.bitcast(val, self.voidptr)
|
|
if env is None:
|
|
env = self.get_null_object()
|
|
env = self.builder.bitcast(env, self.voidptr)
|
|
|
|
return self.builder.call(fn,
|
|
(state_size, initial_state, genfn, finalizer, env))
|
|
|
|
def numba_array_adaptor(self, ary, ptr):
|
|
assert not self.context.enable_nrt
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, self.voidptr])
|
|
fn = self._get_function(fnty, name="numba_adapt_ndarray")
|
|
fn.args[0].add_attribute('nocapture')
|
|
fn.args[1].add_attribute('nocapture')
|
|
return self.builder.call(fn, (ary, ptr))
|
|
|
|
def numba_buffer_adaptor(self, buf, ptr):
|
|
fnty = ir.FunctionType(ir.VoidType(),
|
|
[ir.PointerType(self.py_buffer_t), self.voidptr])
|
|
fn = self._get_function(fnty, name="numba_adapt_buffer")
|
|
fn.args[0].add_attribute('nocapture')
|
|
fn.args[1].add_attribute('nocapture')
|
|
return self.builder.call(fn, (buf, ptr))
|
|
|
|
def complex_adaptor(self, cobj, cmplx):
|
|
fnty = ir.FunctionType(ir.IntType(32), [self.pyobj, cmplx.type])
|
|
fn = self._get_function(fnty, name="numba_complex_adaptor")
|
|
return self.builder.call(fn, [cobj, cmplx])
|
|
|
|
def extract_record_data(self, obj, pbuf):
|
|
fnty = ir.FunctionType(self.voidptr,
|
|
[self.pyobj, ir.PointerType(self.py_buffer_t)])
|
|
fn = self._get_function(fnty, name="numba_extract_record_data")
|
|
return self.builder.call(fn, [obj, pbuf])
|
|
|
|
def get_buffer(self, obj, pbuf):
|
|
fnty = ir.FunctionType(ir.IntType(32),
|
|
[self.pyobj, ir.PointerType(self.py_buffer_t)])
|
|
fn = self._get_function(fnty, name="numba_get_buffer")
|
|
return self.builder.call(fn, [obj, pbuf])
|
|
|
|
def release_buffer(self, pbuf):
|
|
fnty = ir.FunctionType(ir.VoidType(), [ir.PointerType(self.py_buffer_t)])
|
|
fn = self._get_function(fnty, name="numba_release_buffer")
|
|
return self.builder.call(fn, [pbuf])
|
|
|
|
def extract_np_datetime(self, obj):
|
|
fnty = ir.FunctionType(ir.IntType(64), [self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_extract_np_datetime")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def extract_np_timedelta(self, obj):
|
|
fnty = ir.FunctionType(ir.IntType(64), [self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_extract_np_timedelta")
|
|
return self.builder.call(fn, [obj])
|
|
|
|
def create_np_datetime(self, val, unit_code):
|
|
unit_code = Constant(ir.IntType(32), int(unit_code))
|
|
fnty = ir.FunctionType(self.pyobj, [ir.IntType(64), ir.IntType(32)])
|
|
fn = self._get_function(fnty, name="numba_create_np_datetime")
|
|
return self.builder.call(fn, [val, unit_code])
|
|
|
|
def create_np_timedelta(self, val, unit_code):
|
|
unit_code = Constant(ir.IntType(32), int(unit_code))
|
|
fnty = ir.FunctionType(self.pyobj, [ir.IntType(64), ir.IntType(32)])
|
|
fn = self._get_function(fnty, name="numba_create_np_timedelta")
|
|
return self.builder.call(fn, [val, unit_code])
|
|
|
|
def recreate_record(self, pdata, size, dtype, env_manager):
|
|
fnty = ir.FunctionType(self.pyobj, [ir.PointerType(ir.IntType(8)),
|
|
ir.IntType(32), self.pyobj])
|
|
fn = self._get_function(fnty, name="numba_recreate_record")
|
|
dtypeaddr = env_manager.read_const(env_manager.add_const(dtype))
|
|
return self.builder.call(fn, [pdata, size, dtypeaddr])
|
|
|
|
def string_from_constant_string(self, string):
|
|
cstr = self.context.insert_const_string(self.module, string)
|
|
sz = self.context.get_constant(types.intp, len(string))
|
|
return self.string_from_string_and_size(cstr, sz)
|
|
|
|
def call_jit_code(self, func, sig, args):
|
|
"""Calls into Numba jitted code and propagate error using the Python
|
|
calling convention.
|
|
|
|
Parameters
|
|
----------
|
|
func : function
|
|
The Python function to be compiled. This function is compiled
|
|
in nopython-mode.
|
|
sig : numba.typing.Signature
|
|
The function signature for *func*.
|
|
args : Sequence[llvmlite.binding.Value]
|
|
LLVM values to use as arguments.
|
|
|
|
Returns
|
|
-------
|
|
(is_error, res) : 2-tuple of llvmlite.binding.Value.
|
|
is_error : true iff *func* raised an exception.
|
|
res : Returned value from *func* iff *is_error* is false.
|
|
|
|
If *is_error* is true, this method will adapt the nopython exception
|
|
into a Python exception. Caller should return NULL to Python to
|
|
indicate an error.
|
|
"""
|
|
# Compile *func*
|
|
builder = self.builder
|
|
cres = self.context.compile_subroutine(builder, func, sig)
|
|
got_retty = cres.signature.return_type
|
|
retty = sig.return_type
|
|
if got_retty != retty:
|
|
# This error indicates an error in *func* or the caller of this
|
|
# method.
|
|
raise errors.LoweringError(
|
|
f'mismatching signature {got_retty} != {retty}.\n'
|
|
)
|
|
# Call into *func*
|
|
status, res = self.context.call_internal_no_propagate(
|
|
builder, cres.fndesc, sig, args,
|
|
)
|
|
# Post-call handling for *func*
|
|
is_error_ptr = cgutils.alloca_once(builder, cgutils.bool_t, zfill=True)
|
|
res_type = self.context.get_value_type(sig.return_type)
|
|
res_ptr = cgutils.alloca_once(builder, res_type, zfill=True)
|
|
|
|
# Handle error and adapt the nopython exception into cpython exception
|
|
with builder.if_else(status.is_error) as (has_err, no_err):
|
|
with has_err:
|
|
builder.store(status.is_error, is_error_ptr)
|
|
# Set error state in the Python interpreter
|
|
self.context.call_conv.raise_error(builder, self, status)
|
|
with no_err:
|
|
# Handle returned value
|
|
res = imputils.fix_returning_optional(
|
|
self.context, builder, sig, status, res,
|
|
)
|
|
builder.store(res, res_ptr)
|
|
|
|
is_error = builder.load(is_error_ptr)
|
|
res = builder.load(res_ptr)
|
|
return is_error, res
|
|
|
|
|
|
class ObjModeUtils:
|
|
"""Internal utils for calling objmode dispatcher from within NPM code.
|
|
"""
|
|
def __init__(self, pyapi):
|
|
self.pyapi = pyapi
|
|
|
|
def load_dispatcher(self, fnty, argtypes):
|
|
builder = self.pyapi.builder
|
|
tyctx = self.pyapi.context
|
|
m = builder.module
|
|
|
|
# Add a global variable to cache the objmode dispatcher
|
|
gv = ir.GlobalVariable(
|
|
m, self.pyapi.pyobj,
|
|
name=m.get_unique_name("cached_objmode_dispatcher"),
|
|
)
|
|
gv.initializer = gv.type.pointee(None)
|
|
gv.linkage = 'internal'
|
|
|
|
# Make a basic-block to common exit
|
|
bb_end = builder.append_basic_block("bb_end")
|
|
|
|
if serialize.is_serialiable(fnty.dispatcher):
|
|
serialized_dispatcher = self.pyapi.serialize_object(
|
|
(fnty.dispatcher, tuple(argtypes)),
|
|
)
|
|
compile_args = self.pyapi.unserialize(serialized_dispatcher)
|
|
# unserialize (unpickling) can fail
|
|
failed_unser = cgutils.is_null(builder, compile_args)
|
|
with builder.if_then(failed_unser):
|
|
# early exit. `gv` is still null.
|
|
builder.branch(bb_end)
|
|
|
|
cached = builder.load(gv)
|
|
with builder.if_then(cgutils.is_null(builder, cached)):
|
|
if serialize.is_serialiable(fnty.dispatcher):
|
|
cls = type(self)
|
|
compiler = self.pyapi.unserialize(
|
|
self.pyapi.serialize_object(cls._call_objmode_dispatcher)
|
|
)
|
|
callee = self.pyapi.call_function_objargs(
|
|
compiler, [compile_args],
|
|
)
|
|
# Clean up
|
|
self.pyapi.decref(compiler)
|
|
self.pyapi.decref(compile_args)
|
|
else:
|
|
entry_pt = fnty.dispatcher.compile(tuple(argtypes))
|
|
callee = tyctx.add_dynamic_addr(
|
|
builder, id(entry_pt), info="with_objectmode",
|
|
)
|
|
# Incref the dispatcher and cache it
|
|
self.pyapi.incref(callee)
|
|
builder.store(callee, gv)
|
|
# Jump to the exit block
|
|
builder.branch(bb_end)
|
|
# Define the exit block
|
|
builder.position_at_end(bb_end)
|
|
callee = builder.load(gv)
|
|
return callee
|
|
|
|
@staticmethod
|
|
def _call_objmode_dispatcher(compile_args):
|
|
dispatcher, argtypes = compile_args
|
|
entrypt = dispatcher.compile(argtypes)
|
|
return entrypt
|