Repository: numba/numba

Issue:
0.57.0rc1 UnsupportedError when number of keyword arguments in CALL_FUNCTION_EX is 16 or more
Reported on discourse for the RC: https://numba.discourse.group/t/ann-numba-0-57-0rc1-and-llvmlite-0-40-0rc1/1879/2

minimal reproducer:

```python
import numba

@numba.njit
def foo(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q):
    return (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q)


@numba.njit
def bar(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q):
    return foo(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, i=i, j=j, k=k, l=l, m=m, n=n, o=o, p=p, q=q)

bar(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
```

traceback:
```
% NUMBA_DEVELOPER_MODE=1 python call_fn_ex.py
Traceback (most recent call last):
  File "/path/to/numba/call_fn_ex.py", line 12, in <module>
    bar(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
  File "/path/to/numba/numba/core/dispatcher.py", line 471, in _compile_for_args
    error_rewrite(e, 'unsupported_error')
  File "/path/to/numba/numba/core/dispatcher.py", line 407, in error_rewrite
    raise e
  File "/path/to/numba/numba/core/dispatcher.py", line 420, in _compile_for_args
    return_val = self.compile(tuple(argtypes))
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/dispatcher.py", line 965, in compile
    cres = self._compiler.compile(args, return_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/dispatcher.py", line 125, in compile
    status, retval = self._compile_cached(args, return_type)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/dispatcher.py", line 139, in _compile_cached
    retval = self._compile_core(args, return_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/dispatcher.py", line 152, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/compiler.py", line 742, in compile_extra
    return pipeline.compile_extra(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/compiler.py", line 460, in compile_extra
    return self._compile_bytecode()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/compiler.py", line 528, in _compile_bytecode
    return self._compile_core()
           ^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/compiler.py", line 507, in _compile_core
    raise e
  File "/path/to/numba/numba/core/compiler.py", line 494, in _compile_core
    pm.run(self.state)
  File "/path/to/numba/numba/core/compiler_machinery.py", line 368, in run
    raise patched_exception
  File "/path/to/numba/numba/core/compiler_machinery.py", line 356, in run
    self._runPass(idx, pass_inst, state)
  File "/path/to/numba/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/compiler_machinery.py", line 311, in _runPass
    mutated |= check(pss.run_pass, internal_state)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/compiler_machinery.py", line 273, in check
    mangled = func(compiler_state)
              ^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/untyped_passes.py", line 86, in run_pass
    func_ir = interp.interpret(bc)
              ^^^^^^^^^^^^^^^^^^^^
  File "/path/to/numba/numba/core/interpreter.py", line 1363, in interpret
    flow.run()
  File "/path/to/numba/numba/core/byteflow.py", line 110, in run
    runner.dispatch(state)
  File "/path/to/numba/numba/core/byteflow.py", line 337, in dispatch
    fn(state, inst)
  File "/path/to/numba/numba/core/byteflow.py", line 1063, in op_CALL_FUNCTION_EX
    raise UnsupportedError(errmsg)
numba.core.errors.UnsupportedError: Failed in nopython mode pipeline (step: analyzing bytecode)
```


Discussion:
At the surface, it seems a small change base upon https://github.com/numba/numba/pull/7866 would fix this.

Resolving patch:
diff --git a/numba/core/byteflow.py b/numba/core/byteflow.py
--- a/numba/core/byteflow.py
+++ b/numba/core/byteflow.py
@@ -1058,7 +1058,7 @@ def op_CALL_FUNCTION_KW(self, state, inst):
         state.push(res)
 
     def op_CALL_FUNCTION_EX(self, state, inst):
-        if inst.arg & 1 and PYVERSION != (3, 10):
+        if inst.arg & 1 and PYVERSION < (3, 10):
             errmsg = "CALL_FUNCTION_EX with **kwargs not supported"
             raise UnsupportedError(errmsg)
         if inst.arg & 1:


Test patch:
diff --git a/numba/tests/test_interpreter.py b/numba/tests/test_interpreter.py
--- a/numba/tests/test_interpreter.py
+++ b/numba/tests/test_interpreter.py
@@ -6,7 +6,11 @@
 from numba.extending import overload
 from numba.core import types
 from numba.core.errors import UnsupportedError
-from numba.tests.support import TestCase, MemoryLeakMixin, skip_unless_py10
+from numba.tests.support import (
+    TestCase,
+    MemoryLeakMixin,
+    skip_unless_py10_or_later,
+)
 
 
 @njit
@@ -153,7 +157,7 @@ def gen_func(self, n_args, n_kws):
         exec(func_text, {"sum_jit_func": sum_jit_func}, local_vars)
         return local_vars["impl"]
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_all_args(self):
         """
         Tests calling a function when n_args > 30 and
@@ -168,7 +172,7 @@ def test_all_args(self):
         b = cfunc(*total_args)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_all_kws(self):
         """
         Tests calling a function when n_kws > 15 and
@@ -182,7 +186,7 @@ def test_all_kws(self):
         b = cfunc(*total_args)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_small_args_small_kws(self):
         """
         Tests calling a function when (n_args / 2) + n_kws > 15,
@@ -198,7 +202,7 @@ def test_small_args_small_kws(self):
         b = cfunc(*total_args)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_small_args_large_kws(self):
         """
         Tests calling a function when (n_args / 2) + n_kws > 15,
@@ -214,7 +218,7 @@ def test_small_args_large_kws(self):
         b = cfunc(*total_args)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_args_small_kws(self):
         """
         Tests calling a function when (n_args / 2) + n_kws > 15,
@@ -230,7 +234,7 @@ def test_large_args_small_kws(self):
         b = cfunc(*total_args)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_args_large_kws(self):
         """
         Tests calling a function when (n_args / 2) + n_kws > 15,
@@ -246,7 +250,7 @@ def test_large_args_large_kws(self):
         b = cfunc(*total_args)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_kws_objmode(self):
         """
         Tests calling an objectmode function with > 15 return values.
@@ -337,7 +341,7 @@ def objmode_func():
         b = objmode_func()
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_args_inline_controlflow(self):
         """
         Tests generating large args when one of the inputs
@@ -391,7 +395,7 @@ def inline_func(flag):
             str(raises.exception)
         )
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_args_noninlined_controlflow(self):
         """
         Tests generating large args when one of the inputs
@@ -446,7 +450,7 @@ def inline_func(flag):
         b = cfunc(False)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_all_args_inline_controlflow(self):
         """
         Tests generating only large args when one of the inputs
@@ -501,7 +505,7 @@ def inline_func(flag):
             str(raises.exception)
         )
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_all_args_noninlined_controlflow(self):
         """
         Tests generating large args when one of the inputs
@@ -555,7 +559,7 @@ def inline_func(flag):
         b = cfunc(False)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_kws_inline_controlflow(self):
         """
         Tests generating large kws when one of the inputs
@@ -588,7 +592,7 @@ def inline_func(flag):
             str(raises.exception)
         )
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_kws_noninlined_controlflow(self):
         """
         Tests generating large kws when one of the inputs
@@ -639,7 +643,7 @@ class TestLargeConstDict(TestCase, MemoryLeakMixin):
     to succeed.
     """
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_heterogeneous_const_dict(self):
         """
         Tests that a function with a large heterogeneous constant
@@ -678,7 +682,7 @@ def const_func():
         b = cfunc()
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_heterogeneous_LiteralStrKeyDict_literal_values(self):
         """Check the literal values for a LiteralStrKeyDict requiring
         optimizations because it is heterogeneous.
@@ -749,7 +753,7 @@ def foo():
 
         foo()
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_heterogeneous_const_keys_dict(self):
         """
         Tests that a function with a large heterogeneous constant
@@ -790,7 +794,7 @@ def const_keys_func(a):
         b = cfunc(value)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_dict_mutation_not_carried(self):
         """Checks that the optimization for large dictionaries
         do not incorrectly update initial values due to other
@@ -858,7 +862,7 @@ def foo():
 
         foo()
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_usercode_update_use_d2(self):
         """
         Tests an example using a regular update is
@@ -934,7 +938,7 @@ def const_dict_func():
         b = cfunc()
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_const_dict_inline_controlflow(self):
         """
         Tests generating a large dictionary when one of
@@ -976,7 +980,7 @@ def inline_func(a, flag):
             str(raises.exception)
         )
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_large_const_dict_noninline_controlflow(self):
         """
         Tests generating large constant dict when one of the
@@ -1018,7 +1022,7 @@ def non_inline_func(a, flag):
         b = cfunc(value, False)
         self.assertEqual(a, b)
 
-    @skip_unless_py10
+    @skip_unless_py10_or_later
     def test_fuse_twice_literal_values(self):
         """
         Tests that the correct literal values are generated