import numpy as np from numba import cuda from numba.cuda.testing import unittest, xfail_unless_cudasim, CUDATestCase from numba.core import config class TestException(CUDATestCase): def test_exception(self): def foo(ary): x = cuda.threadIdx.x if x == 2: # NOTE: indexing with a out-of-bounds constant can fail at # compile-time instead (because the getitem is rewritten as a # static_getitem) ary.shape[-x] unsafe_foo = cuda.jit(foo) safe_foo = cuda.jit(debug=True, opt=False)(foo) if not config.ENABLE_CUDASIM: # Simulator throws exceptions regardless of debug # setting unsafe_foo[1, 3](np.array([0, 1])) with self.assertRaises(IndexError) as cm: safe_foo[1, 3](np.array([0, 1])) self.assertIn("tuple index out of range", str(cm.exception)) def test_user_raise(self): @cuda.jit(debug=True, opt=False) def foo(do_raise): if do_raise: raise ValueError foo[1, 1](False) with self.assertRaises(ValueError): foo[1, 1](True) def case_raise_causing_warp_diverge(self, with_debug_mode): """Testing issue #2655. Exception raising code can cause the compiler to miss location of unifying branch target and resulting in unexpected warp divergence. """ with_opt_mode = not with_debug_mode @cuda.jit(debug=with_debug_mode, opt=with_opt_mode) def problematic(x, y): tid = cuda.threadIdx.x ntid = cuda.blockDim.x if tid > 12: for i in range(ntid): y[i] += x[i] // y[i] cuda.syncthreads() if tid < 17: for i in range(ntid): x[i] += x[i] // y[i] @cuda.jit def oracle(x, y): tid = cuda.threadIdx.x ntid = cuda.blockDim.x if tid > 12: for i in range(ntid): if y[i] != 0: y[i] += x[i] // y[i] cuda.syncthreads() if tid < 17: for i in range(ntid): if y[i] != 0: x[i] += x[i] // y[i] n = 32 got_x = 1. / (np.arange(n) + 0.01) got_y = 1. / (np.arange(n) + 0.01) problematic[1, n](got_x, got_y) expect_x = 1. / (np.arange(n) + 0.01) expect_y = 1. / (np.arange(n) + 0.01) oracle[1, n](expect_x, expect_y) np.testing.assert_almost_equal(expect_x, got_x) np.testing.assert_almost_equal(expect_y, got_y) def test_raise_causing_warp_diverge(self): """Test case for issue #2655. """ self.case_raise_causing_warp_diverge(with_debug_mode=False) # The following two cases relate to Issue #7806: Division by zero stops the # kernel. https://github.com/numba/numba/issues/7806. def test_no_zero_division_error(self): # When debug is False: # - Division by zero raises no exception # - Execution proceeds after a divide by zero @cuda.jit def f(r, x, y): r[0] = y[0] / x[0] r[1] = y[0] r = np.zeros(2) x = np.zeros(1) y = np.ones(1) f[1, 1](r, x, y) self.assertTrue(np.isinf(r[0]), 'Expected inf from div by zero') self.assertEqual(r[1], y[0], 'Expected execution to continue') def test_zero_division_error_in_debug(self): # When debug is True: # - Zero by division raises an exception # - Execution halts at the point of division by zero @cuda.jit(debug=True, opt=False) def f(r, x, y): r[0] = y[0] / x[0] r[1] = y[0] r = np.zeros(2) x = np.zeros(1) y = np.ones(1) # Simulator and device behaviour differs slightly in the exception # raised - in debug mode, the CUDA target uses the Python error model, # which gives a ZeroDivision error. The simulator uses NumPy with the # error mode for division by zero set to raise, which results in a # FloatingPointError instead. if config.ENABLE_CUDASIM: exc = FloatingPointError else: exc = ZeroDivisionError with self.assertRaises(exc): f[1, 1](r, x, y) self.assertEqual(r[0], 0, 'Expected result to be left unset') self.assertEqual(r[1], 0, 'Expected execution to stop') @xfail_unless_cudasim def test_raise_in_device_function(self): # This is an expected failure because reporting of exceptions raised in # device functions does not work correctly - see Issue #8036: # https://github.com/numba/numba/issues/8036 msg = 'Device Function Error' @cuda.jit(device=True) def f(): raise ValueError(msg) @cuda.jit(debug=True) def kernel(): f() with self.assertRaises(ValueError) as raises: kernel[1, 1]() self.assertIn(msg, str(raises.exception)) if __name__ == '__main__': unittest.main()