ai-content-maker/.venv/Lib/site-packages/numba/core/pythonapi.py

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