Skip to content

JIT: Failed assertion (trace_length < max_length) in translate_bytecode_to_trace #139192

@devdanzin

Description

@devdanzin

Crash report

What happened?

The interpreter will abort when a JIT build patched with the diff below runs the code in the MRE. Please let me know whether this abort repros for you.

The necessary diff:

diff --git a/Include/internal/pycore_backoff.h b/Include/internal/pycore_backoff.h
index 454c8dde031..9e21c41421a 100644
--- a/Include/internal/pycore_backoff.h
+++ b/Include/internal/pycore_backoff.h
@@ -99,8 +99,8 @@ backoff_counter_triggers(_Py_BackoffCounter counter)
 // Must be larger than ADAPTIVE_COOLDOWN_VALUE, otherwise when JIT code is
 // invalidated we may construct a new trace before the bytecode has properly
 // re-specialized:
-#define JUMP_BACKWARD_INITIAL_VALUE 4095
-#define JUMP_BACKWARD_INITIAL_BACKOFF 12
+#define JUMP_BACKWARD_INITIAL_VALUE 63
+#define JUMP_BACKWARD_INITIAL_BACKOFF 6
 static inline _Py_BackoffCounter
 initial_jump_backoff_counter(void)
 {
@@ -112,8 +112,8 @@ 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 4095
-#define SIDE_EXIT_INITIAL_BACKOFF 12
+#define SIDE_EXIT_INITIAL_VALUE 63
+#define SIDE_EXIT_INITIAL_BACKOFF 6

 static inline _Py_BackoffCounter
 initial_temperature_backoff_counter(void)
diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h
index 685c39dcd65..58a0ff31929 100644
--- a/Include/internal/pycore_optimizer.h
+++ b/Include/internal/pycore_optimizer.h
@@ -91,9 +91,9 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp);

 // Used as the threshold to trigger executor invalidation when
 // trace_run_counter is greater than this value.
-#define JIT_CLEANUP_THRESHOLD 100000
+#define JIT_CLEANUP_THRESHOLD 10000

-#define TRACE_STACK_SIZE 5
+#define TRACE_STACK_SIZE 10

 int _Py_uop_analyze_and_optimize(_PyInterpreterFrame *frame,
     _PyUOpInstruction *trace, int trace_len, int curr_stackentries,
@@ -124,7 +124,7 @@ static inline uint16_t uop_get_error_target(const _PyUOpInstruction *inst)
 }

 // Holds locals, stack, locals, stack ... co_consts (in that order)
-#define MAX_ABSTRACT_INTERP_SIZE 4096
+#define MAX_ABSTRACT_INTERP_SIZE 8192

 #define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * 5)

@@ -135,7 +135,7 @@ static inline uint16_t uop_get_error_target(const _PyUOpInstruction *inst)
 // progress (and inserting a new ENTER_EXECUTOR instruction). In practice, this
 // is the "maximum amount of polymorphism" that an isolated trace tree can
 // handle before rejoining the rest of the program.
-#define MAX_CHAIN_DEPTH 4
+#define MAX_CHAIN_DEPTH 16

 /* Symbols */
 /* See explanation in optimizer_symbols.c */

The (rather long, sorry!) MRE:

from random import Random
fuzzer_rng = Random(238542)

def uop_harness_f1():
    # Simple function call
    def func_1(): pass
    for _ in range(1500):
        res = func_1()

    # Recursive function calls
    def deep_0(p): return p + 1
    def deep_1(p): return deep_0(p) + 1
    def deep_2(p): return deep_1(p) + 1
    for i in range(3500):
        deep_2(i)

    # To decorate a function below, moving this block breaks repro
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

    # Variables and random() calls
    mv_8407_0 = 0
    mv_8407_1 = 1
    var_1, *var_2, var_3, var_4, var_5, var_6, var_7, var_8, var_9, var_10, var_11, var_12, var_13, var_14, var_15, var_16, var_17, var_18, var_19, var_20, var_21 = [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]
    mv_8407_3 = mv_8407_4 = mv_8407_5 = mv_8407_6 = mv_8407_7 = mv_8407_8 = mv_8407_9 = 9
    if fuzzer_rng.random() >= 0.1:
        mv_8407_10 = 9
    if fuzzer_rng.random() >= 0.1:
        mv_8407_11 = 10
    if fuzzer_rng.random() < 0.1:
        mv_8407_12 = 12
    if fuzzer_rng.random() < 0.1:
        mv_8407_13 = 14
    if fuzzer_rng.random() >= 0.1:
        mv_8407_14 = 15
    if fuzzer_rng.random() < 0.1:
        mv_8407_15 = 16
    if fuzzer_rng.random() < 0.1:
        mv_8407_32 = 18
    if fuzzer_rng.random() >= 0.1:
        mv_8407_17 = 16
    if fuzzer_rng.random() < 0.1:
        mv_8407_18 = 19
    if fuzzer_rng.random() >= 0.1:
        for i_loop_4481 in range(730):
            if fuzzer_rng.random() < 0.1:
                mv_8407_19 = 21
    if fuzzer_rng.random() >= 0.1:
        for i_loop_7482 in range(96):
            mv_8407_20 = 20
    for i_loop_6618 in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] * 28:
        for i_loop_8222 in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]:
            mv_8407_46 = 46

    # Decorated function call
    @decorator
    def func_2(): pass
    for _ in range(500):
        res = func_2()

for x in range(4500): # Should abort around x == 122
    print(x)
    uop_harness_f1()

Backtrace for --enable-experimental-jit=interpreter:

python: Python/optimizer.c:979: translate_bytecode_to_trace: Assertion `trace_length < max_length' failed.

Program received signal SIGABRT, Aborted.

#0  __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737350580032) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=140737350580032) at ./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=140737350580032, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3  0x00007ffff7ce0476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7cc67f3 in __GI_abort () at ./stdlib/abort.c:79
#5  0x00007ffff7cc671b in __assert_fail_base (fmt=0x7ffff7e7b130 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n",
    assertion=0x555555aea5cd "trace_length < max_length", file=0x555555aea482 "Python/optimizer.c", line=979, function=<optimized out>)
    at ./assert/assert.c:94
#6  0x00007ffff7cd7e96 in __GI___assert_fail (assertion=assertion@entry=0x555555aea5cd "trace_length < max_length",
    file=file@entry=0x555555aea482 "Python/optimizer.c", line=line@entry=979,
    function=function@entry=0x555555aed590 <__PRETTY_FUNCTION__.27> "translate_bytecode_to_trace") at ./assert/assert.c:103
#7  0x0000555555921cf6 in translate_bytecode_to_trace (frame=frame@entry=0x7ffff7fb0098, instr=<optimized out>, instr@entry=0x555555ef93ae,
    trace=trace@entry=0x7ffff78b4000, buffer_size=buffer_size@entry=1200, dependencies=dependencies@entry=0x7fffffffd340,
    progress_needed=progress_needed@entry=false) at Python/optimizer.c:979
#8  0x00005555559222ea in uop_optimize (frame=frame@entry=0x7ffff7fb0098, instr=instr@entry=0x555555ef93ae,
    exec_ptr=exec_ptr@entry=0x7fffffffd498, curr_stackentries=2, progress_needed=progress_needed@entry=false) at Python/optimizer.c:1304
#9  0x000055555592290d in _PyOptimizer_Optimize (frame=frame@entry=0x7ffff7fb0098, start=start@entry=0x555555ef93ae,
    executor_ptr=executor_ptr@entry=0x7fffffffd498, chain_depth=1) at Python/optimizer.c:135
#10 0x00005555558a2099 in _PyTier2Interpreter (current_executor=0x7ffff7aca690, frame=0x7ffff7fb0098, stack_pointer=<optimized out>,
    tstate=0x555555d19fd8 <_PyRuntime+333720>) at Python/executor_cases.c.h:7485
#11 0x0000555555860d01 in _PyEval_EvalFrameDefault (tstate=tstate@entry=0x555555d19fd8 <_PyRuntime+333720>, frame=0x7ffff7fb0098,
    frame@entry=0x7ffff7fb0020, throwflag=throwflag@entry=0) at Python/generated_cases.c.h:5496
#12 0x0000555555875638 in _PyEval_EvalFrame (throwflag=0, frame=0x7ffff7fb0020, tstate=0x555555d19fd8 <_PyRuntime+333720>)
    at ./Include/internal/pycore_ceval.h:121
#13 _PyEval_Vector (tstate=tstate@entry=0x555555d19fd8 <_PyRuntime+333720>, func=func@entry=0x7ffff7a4aa50, locals=locals@entry=0x7ffff7a476b0,
    args=args@entry=0x0, argcount=argcount@entry=0, kwnames=kwnames@entry=0x0) at Python/ceval.c:1990
#14 0x0000555555875737 in PyEval_EvalCode (co=co@entry=0x7ffff7c19a80, globals=globals@entry=0x7ffff7a476b0, locals=locals@entry=0x7ffff7a476b0)
    at Python/ceval.c:873
#15 0x000055555594923b in run_eval_code_obj (tstate=tstate@entry=0x555555d19fd8 <_PyRuntime+333720>, co=co@entry=0x7ffff7c19a80,
    globals=globals@entry=0x7ffff7a476b0, locals=locals@entry=0x7ffff7a476b0) at Python/pythonrun.c:1365
#16 0x000055555594a453 in run_mod (mod=mod@entry=0x555555ef3098, filename=filename@entry=0x7ffff7ab1a20, globals=globals@entry=0x7ffff7a476b0,
    locals=locals@entry=0x7ffff7a476b0, flags=flags@entry=0x7fffffffdb18, arena=arena@entry=0x7ffff7ab7d00, interactive_src=0x0,
    generate_new_source=0) at Python/pythonrun.c:1459

Here's the output of running with PYTHON_LLTRACE=4:
lltrace_trace_length.txt

The output above is cut short, here are the last lines shown in console, including No room for _CHECK_VALIDITY (need 2, got -2):

493: NOT_TAKEN(0)
 700 ADD_TO_TRACE: _CHECK_VALIDITY (0, target=493, operand0=0, operand1=0)
 701 ADD_TO_TRACE: _SET_IP (0, target=493, operand0=0x564abd083a3a, operand1=0)
 702 ADD_TO_TRACE: _NOP (0, target=493, operand0=0, operand1=0)
494: JUMP_BACKWARD_JIT(30)
 703 ADD_TO_TRACE: _CHECK_VALIDITY (0, target=494, operand0=0, operand1=0)
 704 ADD_TO_TRACE: _SET_IP (0, target=494, operand0=0x564abd083a3c, operand1=0)
 705 ADD_TO_TRACE: _CHECK_PERIODIC (0, target=494, operand0=0, operand1=0, error_target=0)
466: FOR_ITER_RANGE(32)
No room for _CHECK_VALIDITY (need 2, got -2)
python: Python/optimizer.c:979: translate_bytecode_to_trace: Assertion `trace_length < max_length' failed.
Aborted (core dumped)

Here's the output of running with PYTHON_OPT_DEBUG=4:
opt_debug_trace_length.txt

Interestingly, the --enable-experimental-jit=interpreter build segfaults when running the same code under PYTHON_LLTRACE=4, in what seems to be a completely different crash. I'll soon report this other crash so we can discuss whether it's a real bug or expected.

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.0a0 (heads/main-dirty:69c6b438e84, Sep 20 2025, 17:15:31) [GCC 11.4.0]

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)topic-JITtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions