Repository navigation
Support non-constant exception values in JIT - #8134
Conversation
7354c1c to
6cc1ffe
Compare
6cc1ffe to
fcb75e1
Compare
Co-authored-by: Gagandeep Singh <gdp.1807@gmail.com>
fb091ed to
fcecc05
Compare
njriasan
left a comment
There was a problem hiding this comment.
Thanks @guilhermeleobas this looks really good.
| """ | ||
| Raise an exception class and some argument *values* unknown at compile-time. | ||
| Note that if *exc_class* is None, a bare "raise" statement is implied | ||
| (i.e. re-raise the current exception). |
There was a problem hiding this comment.
What are the situations in which someone can reraise an exception? I see that code like this below won't work because we can't do as e
import numba
@numba.njit
def f(lst):
try:
idx = lst[10]
except KeyError as e:
raise e
f([1])There was a problem hiding this comment.
This sort of thing is permissible:
from numba import njit
@njit
def reraise():
raise
def foo():
try:
raise ZeroDivisionError
except ZeroDivisionError:
reraise()
foo()| for i, runtime_arg in enumerate(runtime_args)] | ||
| obj = (exc, tuple(real_args), locinfo) | ||
| data = dumps(obj) | ||
| # Does it worth computing the hash? |
There was a problem hiding this comment.
This is a good question. Adding this comment for visibility in future reviews.
stuartarchibald
left a comment
There was a problem hiding this comment.
Thanks for the patch @guilhermeleobas, it's good to see this feature added. I've given this patch an initial review, there's a lot of use cases that currently fail that I think should be addressed ahead of a more thorough review of the code/implementation. Thanks again!
| malloc_fn = cgutils.get_or_insert_function(builder.module, alloc_fnty, name="malloc") | ||
| struct_size = int32_t(self.context.get_abi_sizeof(excinfo_t)) | ||
| ret = builder.call(malloc_fn, [struct_size]) |
There was a problem hiding this comment.
What free's this allocation?
There was a problem hiding this comment.
I've changed the code to use the NRT allocator, which I believe should manage the memory. Correct?
There was a problem hiding this comment.
The NRT allocator is this:
numba/numba/core/runtime/context.py
Lines 52 to 72 in 05e5e34
which calls
NRT_Allocate and ends up in here:numba/numba/core/runtime/nrt.cpp
Lines 518 to 536 in 05e5e34
which is essentially just a call to
malloc. @sklam do you have a view on what to do with this? Perhaps a meminfo could be used.
There was a problem hiding this comment.
So, the memory is still leaking?
There was a problem hiding this comment.
Hi @stuartarchibald, once you have the chance, can you review this PR? I believe I have addressed the memory leak issue
|
|
||
| v = builder.extract_value(builder.load(struct_gv_ptr), 0) | ||
| _len = builder.extract_value(builder.load(struct_gv_ptr), 1) | ||
| pybytes = pyapi.bytes_from_string_and_size( |
There was a problem hiding this comment.
The following code makes a lot of use of the pyapi without checking if the function calls were successful?
| no_pyobj_flags.nrt = True | ||
| self.check_raise_instance_with_runtime_args(flags=no_pyobj_flags) | ||
| no_pyobj_flags.nrt = False | ||
|
|
There was a problem hiding this comment.
Examples of things that are currently not working correctly:
Unhashable:
from numba import njit
import numpy as np
@njit
def foo(z):
raise ValueError('a', [1,])
foo(1)Unhashable:
from numba import njit
import numpy as np
@njit
def foo(z):
raise ValueError('a', ValueError('a'))
foo(1)Incorrect arg reported: ValueError: i8* null
from numba import njit
import numpy as np
@njit
def foo(pred):
raise ValueError({'a': 1, 'b': np.ones(4)})
foo(1)Incorrect arg reported: ValueError: i8* null
from numba import njit
import numpy as np
@njit
def foo():
raise ValueError(range(3))
foo()Segfaults
from numba import njit
import numpy as np
@njit
def foo():
raise ValueError({'a': 1, 'b': 2})
foo()Segfaults:
from numba import njit
import numpy as np
@njit
def foo(rng):
raise ValueError(rng.bit_generator)
rng = np.random.default_rng()
foo(rng)Unhashable:
from numba import njit
import numpy as np
thing = np.ones(10)
@njit
def foo():
raise ValueError(thing)
foo()Unhashable:
from numba import njit, objmode
import numpy as np
@njit
def foo():
raise ValueError(slice(1, 2))
foo()There was a problem hiding this comment.
The cases you marked as unhashable are not related to this PR as Numba type them as static raise. As for the other four cases, the errors are correctly treated.
stuartarchibald
left a comment
There was a problem hiding this comment.
Thanks for the updates @guilhermeleobas, unfortunately I've found an issue with boxing and environments (noted inline) that needs addressing. Otherwise this looks good once the few other comments/suggestions are addressed. Thanks again.
| Similar to ``StaticRaise`` but does not terminate. | ||
| """ | ||
| Kind = 'static' | ||
|
|
||
| def __init__(self, exc_class, exc_args, loc): |
There was a problem hiding this comment.
Why does DynamicTryRaise inherit from StaticTryRaise? Should DynamicTryRaise and StaticTryRaise both inherit from a _TryRaise ABC instead?
There was a problem hiding this comment.
DynamicTryRaise has the same methods and values as StaticTryRaise. If needed, I can just copy instead of inheriting.
There was a problem hiding this comment.
I think copy or create a common base, either is fine. Was just trying to avoid what I think would seem to be an unexpected inheritance relationship.
There was a problem hiding this comment.
I'll just copy. It's better than creating a forth *Raise class in ir.py.
|
|
||
| def test_try_raise(self): | ||
|
|
||
| @njit |
There was a problem hiding this comment.
This fails if the argument to try_raise is a np.array (I tried a = np.ones(3)). There is an AttributeError in numba.core.boxing.box_array, line 429:
AttributeError: 'NoneType' object has no attribute 'read_cost'
(suspect this is because the env_manager is None?)
There was a problem hiding this comment.
The same thing also happens with the more simple:
@njit
def raise_(x):
raise ValueError(x)
@njit
def foo(x):
raise_(x)
foo(np.ones(3))There was a problem hiding this comment.
I've got to what I think is the cause of this. Essentially when the code for unwrapping the dynamic exception is generated it has to emit code to box native representations into python objects, but the "environment manager" is missing as kwarg "env_manager=" here:
Line 582 in 68b0d75
You can get an environment manager by calling self.context.get_env_manager(builder) but this isn't enough though because the emitted environment sentry needs to be aware that this is a Python context code region. I made this change locally to be able to wire this through:
@@ -123,13 +123,14 @@ class CPUContext(BaseContext):
builder, envptr, _dynfunc._impl_info['offsetof_env_body'])
return EnvBody(self, builder, ref=body_ptr, cast_ref=True)
- def get_env_manager(self, builder):
+ def get_env_manager(self, builder, return_pyobject=False):
envgv = self.declare_env_global(builder.module,
self.get_env_name(self.fndesc))
envarg = builder.load(envgv)
pyapi = self.get_python_api(builder)
pyapi.emit_environment_sentry(
envarg, debug_msg=self.fndesc.env_name,
+ return_pyobject=return_pyobject
)
env_body = self.get_env_body(builder, envarg)
return pyapi.get_env_manager(self.environment, env_body, envarg)
and then did this:
@@ -576,10 +576,12 @@ class CPUCallConv(BaseCallConv):
nb_types = [typ for typ in nb_types if typ is not None]
# convert native values into CPython objects
+ env_manager = self.context.get_env_manager(builder,
+ return_pyobject=True)
objs = []
for i, typ in enumerate(nb_types):
val = builder.extract_value(builder.load(st_ptr), i)
- obj = pyapi.from_native_value(typ, val)
+ obj = pyapi.from_native_value(typ, val, env_manager=env_manager)
thoughts?
| with cgutils.if_unlikely(builder, failed): | ||
| msg = ('Error creating Python tuple from runtime exception ' | ||
| 'arguments') | ||
| pyapi.err_set_string("PyExc_RuntimeError", msg) |
There was a problem hiding this comment.
| pyapi.err_set_string("PyExc_RuntimeError", msg) | |
| pyapi.err_set_string("PyExc_RuntimeError", msg) | |
| builder.ret(pyapi.get_null_object()) |
I'm not sure that this has any effect unless the error state is communicated through a return value of NULL.
stuartarchibald
left a comment
There was a problem hiding this comment.
Two minor things, else looks good for a buildfarm test, thanks for all your efforts on this @guilhermeleobas.
|
I've checked/reviewed up to b1eb24b, looks good. Given this enables "dynamic" exceptions, last task is to remove the restriction note from the docs: numba/docs/source/reference/pysupported.rst Lines 142 to 143 in 2829f1f (suggest just making it read raise SomeException(<arguments>), i.e. no restriction clause)
|
stuartarchibald
left a comment
There was a problem hiding this comment.
@guilhermeleobas It's great to see this feature make it into Numba, thanks for all your work on it and for your persistence in getting it completed!
Patch approved pending buildfarm testing!
|
Thanks for all the help. It was a good journey to work on this feature. |
stuartarchibald
left a comment
There was a problem hiding this comment.
Thanks for the fixes @guilhermeleobas.
|
Buildfarm ID: |
|
gpuci run tests |
gmarkall
left a comment
There was a problem hiding this comment.
I haven't reviewed the code, but this has my approval based on the gpuCI pass (i.e. it's not breaking CUDA exceptions).
|
|
|
Hi, just leave a comment: this PR definitely helps us for debugging. Thanks! However, in some cases, it does hurt the execution time when with this PR. In principle, you think in which way this PR can affect the performance, compared with the old constant way? The code changes in this PR seem fundamentally huge, but can we create a Numba envvar to turn it on/off? |
|
Hi @dlee992, glad this work is helping you.
Cannot think of an example of why this can cause a performance bottleneck. It may increase compilation time, but it shouldn't affect the execution. Do you have a reproducer that I can take a look at? |
|
Ah, I retried. When applying this PR locally, I also brought in the changes in #8535 in So accidentally, maybe I found the root cause of my another issue #9186. Maybe it's not llvm14 or this PR's fault, changes in |
Fixes #7834