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

957 lines
36 KiB
Python

"""
Calling conventions for Numba-compiled functions.
"""
from collections import namedtuple
from collections.abc import Iterable
import itertools
import hashlib
from llvmlite import ir
from numba.core import types, cgutils, errors
from numba.core.base import PYOBJECT, GENERIC_POINTER
TryStatus = namedtuple('TryStatus', ['in_try', 'excinfo'])
Status = namedtuple("Status",
("code",
# If the function returned ok (a value or None)
"is_ok",
# If the function returned None
"is_none",
# If the function errored out (== not is_ok)
"is_error",
# If the generator exited with StopIteration
"is_stop_iteration",
# If the function errored with an already set exception
"is_python_exc",
# If the function errored with a user exception
"is_user_exc",
# The pointer to the exception info structure (for user
# exceptions)
"excinfoptr",
))
int32_t = ir.IntType(32)
int64_t = ir.IntType(64)
errcode_t = int32_t
def _const_int(code):
return ir.Constant(errcode_t, code)
RETCODE_OK = _const_int(0)
RETCODE_EXC = _const_int(-1)
RETCODE_NONE = _const_int(-2)
# StopIteration
RETCODE_STOPIT = _const_int(-3)
FIRST_USEREXC = 1
RETCODE_USEREXC = _const_int(FIRST_USEREXC)
class BaseCallConv(object):
def __init__(self, context):
self.context = context
def return_optional_value(self, builder, retty, valty, value):
if valty == types.none:
# Value is none
self.return_native_none(builder)
elif retty == valty:
# Value is an optional, need a runtime switch
optval = self.context.make_helper(builder, retty, value=value)
validbit = cgutils.as_bool_bit(builder, optval.valid)
with builder.if_then(validbit):
retval = self.context.get_return_value(builder, retty.type,
optval.data)
self.return_value(builder, retval)
self.return_native_none(builder)
elif not isinstance(valty, types.Optional):
# Value is not an optional, need a cast
if valty != retty.type:
value = self.context.cast(builder, value, fromty=valty,
toty=retty.type)
retval = self.context.get_return_value(builder, retty.type, value)
self.return_value(builder, retval)
else:
raise NotImplementedError("returning {0} for {1}".format(valty,
retty))
def return_native_none(self, builder):
self._return_errcode_raw(builder, RETCODE_NONE)
def return_exc(self, builder):
self._return_errcode_raw(builder, RETCODE_EXC)
def return_stop_iteration(self, builder):
self._return_errcode_raw(builder, RETCODE_STOPIT)
def get_return_type(self, ty):
"""
Get the actual type of the return argument for Numba type *ty*.
"""
restype = self.context.data_model_manager[ty].get_return_type()
return restype.as_pointer()
def init_call_helper(self, builder):
"""
Initialize and return a call helper object for the given builder.
"""
ch = self._make_call_helper(builder)
builder.__call_helper = ch
return ch
def _get_call_helper(self, builder):
return builder.__call_helper
def unpack_exception(self, builder, pyapi, status):
return pyapi.unserialize(status.excinfoptr)
def raise_error(self, builder, pyapi, status):
"""
Given a non-ok *status*, raise the corresponding Python exception.
"""
bbend = builder.function.append_basic_block()
with builder.if_then(status.is_user_exc):
# Unserialize user exception.
# Make sure another error may not interfere.
pyapi.err_clear()
exc = self.unpack_exception(builder, pyapi, status)
with cgutils.if_likely(builder,
cgutils.is_not_null(builder, exc)):
pyapi.raise_object(exc) # steals ref
builder.branch(bbend)
with builder.if_then(status.is_stop_iteration):
pyapi.err_set_none("PyExc_StopIteration")
builder.branch(bbend)
with builder.if_then(status.is_python_exc):
# Error already raised => nothing to do
builder.branch(bbend)
pyapi.err_set_string("PyExc_SystemError",
"unknown error when calling native function")
builder.branch(bbend)
builder.position_at_end(bbend)
def decode_arguments(self, builder, argtypes, func):
"""
Get the decoded (unpacked) Python arguments with *argtypes*
from LLVM function *func*. A tuple of LLVM values is returned.
"""
raw_args = self.get_arguments(func)
arginfo = self._get_arg_packer(argtypes)
return arginfo.from_arguments(builder, raw_args)
def _get_arg_packer(self, argtypes):
"""
Get an argument packer for the given argument types.
"""
return self.context.get_arg_packer(argtypes)
class MinimalCallConv(BaseCallConv):
"""
A minimal calling convention, suitable for e.g. GPU targets.
The implemented function signature is:
retcode_t (<Python return type>*, ... <Python arguments>)
The return code will be one of the RETCODE_* constants or a
function-specific user exception id (>= RETCODE_USEREXC).
Caller is responsible for allocating a slot for the return value
(passed as a pointer in the first argument).
"""
def _make_call_helper(self, builder):
return _MinimalCallHelper()
def return_value(self, builder, retval):
retptr = builder.function.args[0]
assert retval.type == retptr.type.pointee, \
(str(retval.type), str(retptr.type.pointee))
builder.store(retval, retptr)
self._return_errcode_raw(builder, RETCODE_OK)
def return_user_exc(self, builder, exc, exc_args=None, loc=None,
func_name=None):
if exc is not None and not issubclass(exc, BaseException):
raise TypeError("exc should be None or exception class, got %r"
% (exc,))
if exc_args is not None and not isinstance(exc_args, tuple):
raise TypeError("exc_args should be None or tuple, got %r"
% (exc_args,))
# Build excinfo struct
if loc is not None:
fname = loc._raw_function_name()
if fname is None:
# could be exec(<string>) or REPL, try func_name
fname = func_name
locinfo = (fname, loc.filename, loc.line)
if None in locinfo:
locinfo = None
else:
locinfo = None
call_helper = self._get_call_helper(builder)
exc_id = call_helper._add_exception(exc, exc_args, locinfo)
self._return_errcode_raw(builder, _const_int(exc_id))
def return_status_propagate(self, builder, status):
self._return_errcode_raw(builder, status.code)
def _return_errcode_raw(self, builder, code):
if isinstance(code, int):
code = _const_int(code)
builder.ret(code)
def _get_return_status(self, builder, code):
"""
Given a return *code*, get a Status instance.
"""
norm = builder.icmp_signed('==', code, RETCODE_OK)
none = builder.icmp_signed('==', code, RETCODE_NONE)
ok = builder.or_(norm, none)
err = builder.not_(ok)
exc = builder.icmp_signed('==', code, RETCODE_EXC)
is_stop_iteration = builder.icmp_signed('==', code, RETCODE_STOPIT)
is_user_exc = builder.icmp_signed('>=', code, RETCODE_USEREXC)
status = Status(code=code,
is_ok=ok,
is_error=err,
is_python_exc=exc,
is_none=none,
is_user_exc=is_user_exc,
is_stop_iteration=is_stop_iteration,
excinfoptr=None)
return status
def get_function_type(self, restype, argtypes):
"""
Get the implemented Function type for *restype* and *argtypes*.
"""
arginfo = self._get_arg_packer(argtypes)
argtypes = list(arginfo.argument_types)
resptr = self.get_return_type(restype)
fnty = ir.FunctionType(errcode_t, [resptr] + argtypes)
return fnty
def decorate_function(self, fn, args, fe_argtypes, noalias=False):
"""
Set names and attributes of function arguments.
"""
assert not noalias
arginfo = self._get_arg_packer(fe_argtypes)
arginfo.assign_names(self.get_arguments(fn),
['arg.' + a for a in args])
fn.args[0].name = ".ret"
def get_arguments(self, func):
"""
Get the Python-level arguments of LLVM *func*.
"""
return func.args[1:]
def call_function(self, builder, callee, resty, argtys, args):
"""
Call the Numba-compiled *callee*.
"""
retty = callee.args[0].type.pointee
retvaltmp = cgutils.alloca_once(builder, retty)
# initialize return value
builder.store(cgutils.get_null_value(retty), retvaltmp)
arginfo = self._get_arg_packer(argtys)
args = arginfo.as_arguments(builder, args)
realargs = [retvaltmp] + list(args)
code = builder.call(callee, realargs)
status = self._get_return_status(builder, code)
retval = builder.load(retvaltmp)
out = self.context.get_returned_value(builder, resty, retval)
return status, out
class _MinimalCallHelper(object):
"""
A call helper object for the "minimal" calling convention.
User exceptions are represented as integer codes and stored in
a mapping for retrieval from the caller.
"""
def __init__(self):
self.exceptions = {}
def _add_exception(self, exc, exc_args, locinfo):
"""
Add a new user exception to this helper. Returns an integer that can be
used to refer to the added exception in future.
Parameters
----------
exc :
exception type
exc_args : None or tuple
exception args
locinfo : tuple
location information
"""
exc_id = len(self.exceptions) + FIRST_USEREXC
self.exceptions[exc_id] = exc, exc_args, locinfo
return exc_id
def get_exception(self, exc_id):
"""
Get information about a user exception. Returns a tuple of
(exception type, exception args, location information).
Parameters
----------
id : integer
The ID of the exception to look up
"""
try:
return self.exceptions[exc_id]
except KeyError:
msg = "unknown error %d in native function" % exc_id
exc = SystemError
exc_args = (msg,)
locinfo = None
return exc, exc_args, locinfo
# The structure type constructed by PythonAPI.serialize_uncached()
# i.e a {i8* pickle_buf, i32 pickle_bufsz, i8* hash_buf, i8* fn, i32 alloc_flag}
PICKLE_BUF_IDX = 0
PICKLE_BUFSZ_IDX = 1
HASH_BUF_IDX = 2
UNWRAP_FUNC_IDX = 3
ALLOC_FLAG_IDX = 4
excinfo_t = ir.LiteralStructType(
[GENERIC_POINTER, int32_t, GENERIC_POINTER, GENERIC_POINTER, int32_t])
excinfo_ptr_t = ir.PointerType(excinfo_t)
class CPUCallConv(BaseCallConv):
"""
The calling convention for CPU targets.
The implemented function signature is:
retcode_t (<Python return type>*, excinfo **, ... <Python arguments>)
The return code will be one of the RETCODE_* constants.
If RETCODE_USEREXC, the exception info pointer will be filled with
a pointer to a constant struct describing the raised exception.
Caller is responsible for allocating slots for the return value
and the exception info pointer (passed as first and second arguments,
respectively).
"""
_status_ids = itertools.count(1)
def _make_call_helper(self, builder):
return None
def return_value(self, builder, retval):
retptr = self._get_return_argument(builder.function)
assert retval.type == retptr.type.pointee, \
(str(retval.type), str(retptr.type.pointee))
builder.store(retval, retptr)
self._return_errcode_raw(builder, RETCODE_OK)
def build_excinfo_struct(self, exc, exc_args, loc, func_name):
# Build excinfo struct
if loc is not None:
fname = loc._raw_function_name()
if fname is None:
# could be exec(<string>) or REPL, try func_name
fname = func_name
locinfo = (fname, loc.filename, loc.line)
if None in locinfo:
locinfo = None
else:
locinfo = None
exc = (exc, exc_args, locinfo)
return exc
def set_static_user_exc(self, builder, exc, exc_args=None, loc=None,
func_name=None):
if exc is not None and not issubclass(exc, BaseException):
raise TypeError("exc should be None or exception class, got %r"
% (exc,))
if exc_args is not None and not isinstance(exc_args, tuple):
raise TypeError("exc_args should be None or tuple, got %r"
% (exc_args,))
# None is indicative of no args, set the exc_args to an empty tuple
# as PyObject_CallObject(exc, exc_args) requires the second argument to
# be a tuple (or nullptr, but doing this makes it consistent)
if exc_args is None:
exc_args = tuple()
# An exception in Numba is defined as the excinfo_t struct defined
# above. Some arguments in this struct are not used, depending on
# which kind of exception is being raised. A static exception uses
# only the first three members whilst a dynamic exception uses all
# members:
#
# static exc - last 2 args are NULL and 0
# vvv vvv vvv
# excinfo_t: {i8*, i32, i8*, i8*, i32}
# ^^^ ^^^ ^^^
# dynamic exc only - first 2 args are used for
# static info
#
# Comment below details how the struct is used in the case of a dynamic
# exception. For dynamic exceptions, see
# CPUCallConv::set_dynamic_user_exc
#
# {i8*, ___, ___, ___, ___}
# ^ serialized info about the exception (loc, kind, compile time
# args)
#
# {___, i32, ___, ___, ___}
# ^ len(serialized_exception)
#
# {___, ___, i8*, ___, ___}
# ^ Store a list of native values in a dynamic exception.
# Or a hash(serialized_exception) in a static exc.
#
# {___, ___, ___, i8*, ___}
# ^ "NULL" as this member is not used in a static exc
#
# {___, ___, ___, ___, i32}
# ^ Number of dynamic args in the exception. For
# static exceptions, this value is "0"
pyapi = self.context.get_python_api(builder)
exc = self.build_excinfo_struct(exc, exc_args, loc, func_name)
struct_gv = pyapi.serialize_object(exc)
excptr = self._get_excinfo_argument(builder.function)
store = builder.store(struct_gv, excptr)
md = builder.module.add_metadata([ir.IntType(1)(1)])
store.set_metadata("numba_exception_output", md)
def return_user_exc(self, builder, exc, exc_args=None, loc=None,
func_name=None):
try_info = getattr(builder, '_in_try_block', False)
self.set_static_user_exc(builder, exc, exc_args=exc_args,
loc=loc, func_name=func_name)
self.check_try_status(builder)
if try_info:
# This is a hack for old-style impl.
# We will branch directly to the exception handler.
builder.branch(try_info['target'])
else:
# Return from the current function
self._return_errcode_raw(builder, RETCODE_USEREXC)
def unpack_dynamic_exception(self, builder, pyapi, status):
excinfo_ptr = status.excinfoptr
# load the serialized exception buffer from the module and create
# a python bytes object
picklebuf = builder.extract_value(
builder.load(excinfo_ptr), PICKLE_BUF_IDX)
picklebuf_sz = builder.extract_value(
builder.load(excinfo_ptr), PICKLE_BUFSZ_IDX)
static_exc_bytes = pyapi.bytes_from_string_and_size(
picklebuf, builder.sext(picklebuf_sz, pyapi.py_ssize_t))
# Load dynamic args (i8*) and the unwrap function
dyn_args = builder.extract_value(
builder.load(excinfo_ptr), HASH_BUF_IDX)
func_ptr = builder.extract_value(
builder.load(excinfo_ptr), UNWRAP_FUNC_IDX)
# Convert the unwrap function to a function pointer and call it.
# Function returns a python tuple with dynamic arguments converted to
# CPython objects
fnty = ir.FunctionType(PYOBJECT, [GENERIC_POINTER])
fn = builder.bitcast(func_ptr, fnty.as_pointer())
py_tuple = builder.call(fn, [dyn_args])
# We check at this stage if creating the Python tuple was successful
# or not. Note the exception is raised by calling PyErr_SetString
# directly as the current function is the CPython wrapper.
failed = cgutils.is_null(builder, py_tuple)
with cgutils.if_unlikely(builder, failed):
msg = ('Error creating Python tuple from runtime exception '
'arguments')
pyapi.err_set_string("PyExc_RuntimeError", msg)
# Return NULL to indicate an error was raised
fnty = builder.function.function_type
if not isinstance(fnty.return_type, ir.VoidType):
# in some ufuncs, the return type is void
builder.ret(cgutils.get_null_value(fnty.return_type))
else:
builder.ret_void()
# merge static and dynamic variables
excinfo = pyapi.build_dynamic_excinfo_struct(static_exc_bytes, py_tuple)
# At this point, one can free the entire excinfo_ptr struct
if self.context.enable_nrt:
# One can safely emit a free instruction as it is only executed
# if its in a dynamic exception branch
self.context.nrt.free(
builder, builder.bitcast(excinfo_ptr, pyapi.voidptr))
return excinfo
def unpack_exception(self, builder, pyapi, status):
# Emit code that checks the alloc flag (last excinfo member)
# if alloc_flag > 0:
# (dynamic) unpack the exception to retrieve runtime information
# and merge with static info
# else:
# (static) unserialize the exception using pythonapi.unserialize
excinfo_ptr = status.excinfoptr
alloc_flag = builder.extract_value(builder.load(excinfo_ptr),
ALLOC_FLAG_IDX)
gt = builder.icmp_signed('>', alloc_flag, int32_t(0))
with builder.if_else(gt) as (then, otherwise):
with then:
dyn_exc = self.unpack_dynamic_exception(builder, pyapi, status)
bb_then = builder.block
with otherwise:
static_exc = pyapi.unserialize(excinfo_ptr)
bb_else = builder.block
phi = builder.phi(static_exc.type)
phi.add_incoming(dyn_exc, bb_then)
phi.add_incoming(static_exc, bb_else)
return phi
def emit_unwrap_dynamic_exception_fn(self, module, st_type, nb_types):
# Create a function that converts a list of runtime arguments to a tuple
# of PyObjects. i.e.:
#
# @njit('void(float, int64)')
# def func(a, b):
# raise ValueError(a, 123, b)
#
# The last three arguments of the exception info structure will hold:
# {___, ___, i8*, i8*, i32}
# ^ A ptr to a {f32, i64} struct
# ^ function ptr that converts i8* -> {f32, i64}* ->
# python tuple
# ^ Number of dynamic arguments = 2
#
_hash = hashlib.sha1(str(st_type).encode()).hexdigest()
name = f'__excinfo_unwrap_args{_hash}'
if name in module.globals:
return module.globals.get(name)
fnty = ir.FunctionType(GENERIC_POINTER, [GENERIC_POINTER])
fn = ir.Function(module, fnty, name)
# prevent the function from being inlined
fn.attributes.add('nounwind')
fn.attributes.add('noinline')
bb_entry = fn.append_basic_block('')
builder = ir.IRBuilder(bb_entry)
pyapi = self.context.get_python_api(builder)
# i8* -> {native arg1 type, native arg2 type, ...}
st_type_ptr = st_type.as_pointer()
st_ptr = builder.bitcast(fn.args[0], st_type_ptr)
# compile time values are stored as None
nb_types = [typ for typ in nb_types if typ is not None]
# convert native values into CPython objects
objs = []
env_manager = self.context.get_env_manager(builder,
return_pyobject=True)
for i, typ in enumerate(nb_types):
val = builder.extract_value(builder.load(st_ptr), i)
obj = pyapi.from_native_value(typ, val, env_manager=env_manager)
# If object cannot be boxed, raise an exception
if obj == cgutils.get_null_value(obj.type):
# When not supported, abort compilation
msg = f'Cannot convert native {typ} to a Python object.'
raise errors.TypingError(msg)
objs.append(obj)
# at this point, a pointer to the list of runtime values can be freed
self.context.nrt.free(builder,
self._get_return_argument(builder.function))
# Create a tuple of CPython objects
tup = pyapi.tuple_pack(objs)
builder.ret(tup)
return fn
def emit_wrap_args_insts(self, builder, pyapi, struct_type, exc_args):
"""
Create an anonymous struct containing the given LLVM *values*.
"""
st_size = pyapi.py_ssize_t(self.context.get_abi_sizeof(struct_type))
st_ptr = builder.bitcast(
self.context.nrt.allocate(builder, st_size),
struct_type.as_pointer())
# skip compile-time values
exc_args = [arg for arg in exc_args if isinstance(arg, ir.Value)]
zero = int32_t(0)
for idx, arg in enumerate(exc_args):
builder.store(arg, builder.gep(st_ptr, [zero, int32_t(idx)]))
return st_ptr
def set_dynamic_user_exc(self, builder, exc, exc_args, nb_types, loc=None,
func_name=None):
"""
Compute the required bits to emit an exception with dynamic (runtime)
values
"""
if not issubclass(exc, BaseException):
raise TypeError("exc should be an exception class, got %r"
% (exc,))
if exc_args is not None and not isinstance(exc_args, tuple):
raise TypeError("exc_args should be None or tuple, got %r"
% (exc_args,))
# An exception in Numba is defined as the excinfo_t struct defined
# above. Some arguments in this struct are not used, depending on
# which kind of exception is being raised. A static exception uses
# only the first three members whilst a dynamic exception uses all
# members:
#
# static exc - last 2 args are NULL and 0
# vvv vvv vvv
# excinfo_t: {i8*, i32, i8*, i8*, i32}
# ^^^ ^^^ ^^^
# dynamic exc only - first 2 args are used for
# static info
#
# Comment below details how the struct is used in the case of a dynamic
# exception. For static exception, see CPUCallConv::set_static_user_exc
#
# {i8*, ___, ___, ___, ___}
# ^ serialized info about the exception (loc, kind, compile time
# args)
#
# {___, i32, ___, ___, ___}
# ^ len(serialized_exception)
#
# {___, ___, i8*, ___, ___}
# ^ Store a list of native values in a dynamic exception.
# Or a hash(serialized_exception) in a static exc.
#
# {___, ___, ___, i8*, ___}
# ^ Pointer to function that convert native values
# into PyObject*. NULL in the case of a static
# exception
#
# {___, ___, ___, ___, i32}
# ^ Number of dynamic args in the exception.
# Default is "0"
#
# The following code will:
# 1) Serialize compile time information and store them in the first
# two args {i8*, i32, ___, ___, ___} of excinfo_t
# 2) Emit the required code for converting native values to CPython
# objects. Those objects are stored in the last three args
# {___, ___, i8*, i8*, i32} of excinfo_t
# 3) Allocate a new excinfo_t struct
# 4) Fill excinfo_t struct and copy the pointer to the excinfo** arg
# serialize comp. time args
pyapi = self.context.get_python_api(builder)
exc = self.build_excinfo_struct(exc, exc_args, loc, func_name)
excinfo_pp = self._get_excinfo_argument(builder.function)
struct_gv = builder.load(pyapi.serialize_object(exc))
# Create the struct for runtime args and emit a function to convert it
# into a Python tuple
struct_type = ir.LiteralStructType([arg.type for arg in exc_args if
isinstance(arg, ir.Value)])
st_ptr = self.emit_wrap_args_insts(builder, pyapi, struct_type,
exc_args)
unwrap_fn = self.emit_unwrap_dynamic_exception_fn(
builder.module, struct_type, nb_types)
# allocate the excinfo struct
exc_size = pyapi.py_ssize_t(self.context.get_abi_sizeof(excinfo_t))
excinfo_p = builder.bitcast(
self.context.nrt.allocate(builder, exc_size),
excinfo_ptr_t)
# fill the args
zero = int32_t(0)
exc_fields = (builder.extract_value(struct_gv, PICKLE_BUF_IDX),
builder.extract_value(struct_gv, PICKLE_BUFSZ_IDX),
builder.bitcast(st_ptr, GENERIC_POINTER),
builder.bitcast(unwrap_fn, GENERIC_POINTER),
int32_t(len(struct_type)))
for idx, arg in enumerate(exc_fields):
builder.store(arg, builder.gep(excinfo_p, [zero, int32_t(idx)]))
builder.store(excinfo_p, excinfo_pp)
def return_dynamic_user_exc(self, builder, exc, exc_args, nb_types,
loc=None, func_name=None):
"""
Same as ::return_user_exc but for dynamic exceptions
"""
self.set_dynamic_user_exc(builder, exc, exc_args, nb_types,
loc=loc, func_name=func_name)
self._return_errcode_raw(builder, RETCODE_USEREXC)
def _get_try_state(self, builder):
try:
return builder.__eh_try_state
except AttributeError:
ptr = cgutils.alloca_once(
builder, cgutils.intp_t, name='try_state', zfill=True,
)
builder.__eh_try_state = ptr
return ptr
def check_try_status(self, builder):
try_state_ptr = self._get_try_state(builder)
try_depth = builder.load(try_state_ptr)
# try_depth > 0
in_try = builder.icmp_unsigned('>', try_depth, try_depth.type(0))
excinfoptr = self._get_excinfo_argument(builder.function)
excinfo = builder.load(excinfoptr)
return TryStatus(in_try=in_try, excinfo=excinfo)
def set_try_status(self, builder):
try_state_ptr = self._get_try_state(builder)
# Increment try depth
old = builder.load(try_state_ptr)
new = builder.add(old, old.type(1))
builder.store(new, try_state_ptr)
def unset_try_status(self, builder):
try_state_ptr = self._get_try_state(builder)
# Decrement try depth
old = builder.load(try_state_ptr)
new = builder.sub(old, old.type(1))
builder.store(new, try_state_ptr)
# Needs to reset the exception state so that the exception handler
# will run normally.
excinfoptr = self._get_excinfo_argument(builder.function)
null = cgutils.get_null_value(excinfoptr.type.pointee)
builder.store(null, excinfoptr)
def return_status_propagate(self, builder, status):
trystatus = self.check_try_status(builder)
excptr = self._get_excinfo_argument(builder.function)
builder.store(status.excinfoptr, excptr)
with builder.if_then(builder.not_(trystatus.in_try)):
self._return_errcode_raw(builder, status.code)
def _return_errcode_raw(self, builder, code):
builder.ret(code)
def _get_return_status(self, builder, code, excinfoptr):
"""
Given a return *code* and *excinfoptr*, get a Status instance.
"""
norm = builder.icmp_signed('==', code, RETCODE_OK)
none = builder.icmp_signed('==', code, RETCODE_NONE)
exc = builder.icmp_signed('==', code, RETCODE_EXC)
is_stop_iteration = builder.icmp_signed('==', code, RETCODE_STOPIT)
ok = builder.or_(norm, none)
err = builder.not_(ok)
is_user_exc = builder.icmp_signed('>=', code, RETCODE_USEREXC)
excinfoptr = builder.select(is_user_exc, excinfoptr,
ir.Constant(excinfo_ptr_t, ir.Undefined))
status = Status(code=code,
is_ok=ok,
is_error=err,
is_python_exc=exc,
is_none=none,
is_user_exc=is_user_exc,
is_stop_iteration=is_stop_iteration,
excinfoptr=excinfoptr)
return status
def get_function_type(self, restype, argtypes):
"""
Get the implemented Function type for *restype* and *argtypes*.
"""
arginfo = self._get_arg_packer(argtypes)
argtypes = list(arginfo.argument_types)
resptr = self.get_return_type(restype)
fnty = ir.FunctionType(errcode_t,
[resptr, ir.PointerType(excinfo_ptr_t)]
+ argtypes)
return fnty
def decorate_function(self, fn, args, fe_argtypes, noalias=False):
"""
Set names of function arguments, and add useful attributes to them.
"""
arginfo = self._get_arg_packer(fe_argtypes)
arginfo.assign_names(self.get_arguments(fn),
['arg.' + a for a in args])
retarg = self._get_return_argument(fn)
retarg.name = "retptr"
retarg.add_attribute("nocapture")
retarg.add_attribute("noalias")
excarg = self._get_excinfo_argument(fn)
excarg.name = "excinfo"
excarg.add_attribute("nocapture")
excarg.add_attribute("noalias")
if noalias:
args = self.get_arguments(fn)
for a in args:
if isinstance(a.type, ir.PointerType):
a.add_attribute("nocapture")
a.add_attribute("noalias")
# Add metadata to mark functions that may need NRT
# thus disabling aggressive refct pruning in removerefctpass.py
def type_may_always_need_nrt(ty):
# Returns True if it's a non-Array type that is contains MemInfo
if not isinstance(ty, types.Array):
dmm = self.context.data_model_manager
if dmm[ty].contains_nrt_meminfo():
return True
return False
args_may_always_need_nrt = any(
map(type_may_always_need_nrt, fe_argtypes)
)
if args_may_always_need_nrt:
nmd = fn.module.add_named_metadata(
'numba_args_may_always_need_nrt',
)
nmd.add(fn.module.add_metadata([fn]))
def get_arguments(self, func):
"""
Get the Python-level arguments of LLVM *func*.
"""
return func.args[2:]
def _get_return_argument(self, func):
return func.args[0]
def _get_excinfo_argument(self, func):
return func.args[1]
def call_function(self, builder, callee, resty, argtys, args,
attrs=None):
"""
Call the Numba-compiled *callee*.
Parameters:
-----------
attrs: LLVM style string or iterable of individual attributes, default
is None which specifies no attributes. Examples:
LLVM style string: "noinline fast"
Equivalent iterable: ("noinline", "fast")
"""
# XXX better fix for callees that are not function values
# (pointers to function; thus have no `.args` attribute)
retty = self._get_return_argument(callee.function_type).pointee
retvaltmp = cgutils.alloca_once(builder, retty)
# initialize return value to zeros
builder.store(cgutils.get_null_value(retty), retvaltmp)
excinfoptr = cgutils.alloca_once(builder, ir.PointerType(excinfo_t),
name="excinfo")
arginfo = self._get_arg_packer(argtys)
args = list(arginfo.as_arguments(builder, args))
realargs = [retvaltmp, excinfoptr] + args
# deal with attrs, it's fine to specify a load in a string like
# "noinline fast" as per LLVM or equally as an iterable of individual
# attributes.
if attrs is None:
_attrs = ()
elif isinstance(attrs, Iterable) and not isinstance(attrs, str):
_attrs = tuple(attrs)
else:
raise TypeError("attrs must be an iterable of strings or None")
code = builder.call(callee, realargs, attrs=_attrs)
status = self._get_return_status(builder, code,
builder.load(excinfoptr))
retval = builder.load(retvaltmp)
out = self.context.get_returned_value(builder, resty, retval)
return status, out
class ErrorModel(object):
def __init__(self, call_conv):
self.call_conv = call_conv
def fp_zero_division(self, builder, exc_args=None, loc=None):
if self.raise_on_fp_zero_division:
self.call_conv.return_user_exc(builder, ZeroDivisionError, exc_args,
loc)
return True
else:
return False
class PythonErrorModel(ErrorModel):
"""
The Python error model. Any invalid FP input raises an exception.
"""
raise_on_fp_zero_division = True
class NumpyErrorModel(ErrorModel):
"""
In the Numpy error model, floating-point errors don't raise an
exception. The FPU exception state is inspected by Numpy at the
end of a ufunc's execution and a warning is raised if appropriate.
Note there's no easy way to set the FPU exception state from LLVM.
Instructions known to set an FP exception can be optimized away:
https://llvm.org/bugs/show_bug.cgi?id=6050
http://lists.llvm.org/pipermail/llvm-dev/2014-September/076918.html
http://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20140929/237997.html
"""
raise_on_fp_zero_division = False
error_models = {
'python': PythonErrorModel,
'numpy': NumpyErrorModel,
}
def create_error_model(model_name, context):
"""
Create an error model instance for the given target context.
"""
return error_models[model_name](context.call_conv)