JIT: Assertion `exc && PyExceptionInstance_Check(exc)` failed in `*_PyEval_EvalFrameDefault`
Crash report
What happened?
It's possible to cause an abort in a patched debug JIT build by running the following MRE.
The necessary patch:
diff --git a/Include/internal/pycore_backoff.h b/Include/internal/pycore_backoff.h
index 23ca7299e0d..0289b5fee7c 100644
--- a/Include/internal/pycore_backoff.h
+++ b/Include/internal/pycore_backoff.h
@@ -124,7 +124,7 @@ trigger_backoff_counter(void)
// For example, 4095 does not work for the nqueens benchmark on pyperformance
// as we always end up tracing the loop iteration's
// exhaustion iteration. Which aborts our current tracer.
-#define JUMP_BACKWARD_INITIAL_VALUE 4000
+#define JUMP_BACKWARD_INITIAL_VALUE 63
#define JUMP_BACKWARD_INITIAL_BACKOFF 6
static inline _Py_BackoffCounter
initial_jump_backoff_counter(void)
@@ -137,7 +137,7 @@ initial_jump_backoff_counter(void)
* Must be larger than ADAPTIVE_COOLDOWN_VALUE,
* otherwise when a side exit warms up we may construct
* a new trace before the Tier 1 code has properly re-specialized. */
-#define SIDE_EXIT_INITIAL_VALUE 4000
+#define SIDE_EXIT_INITIAL_VALUE 63
#define SIDE_EXIT_INITIAL_BACKOFF 6
static inline _Py_BackoffCounter
MRE:
def f1():
class EvilIterator:
def __init__(self):
self._items = [1, 2]
self._index = 1
def __iter__(self):
return self
def __next__(self):
if not len(self._items) % 13:
self._items.clear()
for i_loop_9279 in range(10):
self._items.extend([1, "", None])
if not len(self._items) % 11:
return 'unexpected_type_from_iterator'
if self._index >= len(self._items):
raise StopIteration
item = self._items[self._index]
self._index += 1
return item
evil_iter = EvilIterator()
large_num = 2**31
for _ in range(400):
try:
_ = [x + y for x in evil_iter for y in evil_iter if evil_iter._items.append(x) or large_num]
except TypeError:
pass
f1()
Backtrace:
python: Python/generated_cases.c.h:9901: PyObject *_PyEval_EvalFrameDefault(PyThreadState *, _PyInterpreterFrame *, int): Assertion `exc && PyExceptionInstance_Check(exc)' failed.
Program received signal SIGABRT, Aborted.
#0 __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2 __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3 0x00007ffff7c45e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4 0x00007ffff7c28888 in __GI_abort () at ./stdlib/abort.c:77
#5 0x00007ffff7c287f0 in __assert_fail_base (fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:118
#6 0x00007ffff7c3c19f in __assert_fail (assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:127
#7 0x0000555555ebab3f in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:9901
#8 0x0000555555e6dc98 in _PyEval_EvalFrame (tstate=0x555556e808c0 <_PyRuntime+359392>, frame=0x7e8ff6fe5220, throwflag=0) at ./Include/internal/pycore_ceval.h:118
#9 _PyEval_Vector (tstate=<optimized out>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=0x0) at Python/ceval.c:2542
#10 0x0000555555e6d6b5 in PyEval_EvalCode (co=<optimized out>, globals=<optimized out>, locals=0x7c7ff70865c0) at Python/ceval.c:1008
#11 0x00005555564626df in run_eval_code_obj (tstate=tstate@entry=0x555556e808c0 <_PyRuntime+359392>, co=co@entry=0x7d1ff701afd0, globals=globals@entry=0x7c7ff70865c0,
locals=locals@entry=0x7c7ff70865c0) at Python/pythonrun.c:1366
#12 0x00005555564618ac in run_mod (mod=<optimized out>, filename=<optimized out>, globals=<optimized out>, locals=<optimized out>, flags=<optimized out>, arena=<optimized out>,
interactive_src=<optimized out>, generate_new_source=<optimized out>) at Python/pythonrun.c:1469
#13 0x000055555645bead in pyrun_file (fp=fp@entry=0x7d4ff6fefa80, filename=filename@entry=0x7ccff6ffafc0, start=start@entry=257, globals=globals@entry=0x7c7ff70865c0,
locals=locals@entry=0x7c7ff70865c0, closeit=closeit@entry=1, flags=0x7bfff5e1e910) at Python/pythonrun.c:1294
#14 0x0000555556459a0d in _PyRun_SimpleFileObject (fp=<optimized out>, filename=<optimized out>, closeit=<optimized out>, flags=<optimized out>) at Python/pythonrun.c:518
#15 0x0000555556458d7e in _PyRun_AnyFileObject (fp=fp@entry=0x7d4ff6fefa80, filename=filename@entry=0x7ccff6ffafc0, closeit=closeit@entry=1, flags=flags@entry=0x7bfff5e1e910)
at Python/pythonrun.c:81
#16 0x00005555564d4cfb in pymain_run_file_obj (program_name=0x7caff7026d50, filename=0x7ccff6ffafc0, skip_source_first_line=0) at Modules/main.c:410
#17 pymain_run_file (config=0x555556e4b998 <_PyRuntime+142520>) at Modules/main.c:429
#18 0x00005555564d2dc4 in pymain_run_python (exitcode=0x7bfff5e1e500) at Modules/main.c:691
#19 Py_RunMain () at Modules/main.c:772
#20 0x00005555564d3cc7 in pymain_main (args=<optimized out>) at Modules/main.c:802
#21 0x00005555564d3e38 in Py_BytesMain (argc=<optimized out>, argv=0x7fffffffdbe8) at Modules/main.c:826
#22 0x00007ffff7c2a575 in __libc_start_call_main (main=main@entry=0x555555919c40 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffdbe8) at ../sysdeps/nptl/libc_start_call_main.h:58
#23 0x00007ffff7c2a628 in __libc_start_main_impl (main=0x555555919c40 <main>, argc=2, argv=0x7fffffffdbe8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7fffffffdbd8) at ../csu/libc-start.c:360
#24 0x00005555558304f5 in _start ()
Output from running with PYTHON_LLTRACE=4 PYTHON_OPT_DEBUG=4:
2394_segfault_lltrace.txt
Found using lafleur.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a3+ (heads/main-dirty:61f2ad9a3a7, Jan 2 2026, 11:43:51) [Clang 21.1.2 (2ubuntu6)]
I can reproduce with the patch. The fact we need patched threshold though is a sign it's time to do this https://github.com/python/cpython/issues/141504. We can't test for this at the moment because our current thresholds don't trigger it. So we should first get to refactoring the JIT so that we can pass JIT thresholds in env vars for testing.
Huh, I can't figure out what's wrong here. @markshannon maybe you might want to take a look.
On main, you can try
PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE=64 ./python ../reproducer.py
Refer analysis my local trace log, I think x + y in the list comprehension will raise a TypeError in _BINARY_OP, then _ERROR_POP_N won't pop the dirty x and y from the stack, so will cause the assertion in RERAISE failed.
Refer analysis my local trace log, I think
x + yin the list comprehension will raise aTypeErrorin_BINARY_OP, then_ERROR_POP_Nwon't pop the dirtyxandyfrom the stack, so will cause the assertion inRERAISEfailed.
_BINARY_OP is not supposed to pop though, if you look at bytecodes.c, it says ERROR_NO_POP()