From 31a51572852e6ad8716ccb7abb8646427b17817b Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 27 Aug 2025 17:51:41 +0900 Subject: [PATCH 1/8] gh-137838: Fix JIT trace buffer overrun by increasing possible exit stubs --- Lib/test/test_sys_settrace.py | 2 ++ Lib/test/test_trace.py | 6 ++++++ .../2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst | 2 ++ Python/optimizer.c | 4 ++-- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index b3685a91c57ee7..199a9087dfe3bc 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -360,6 +360,8 @@ class TraceTestCase(unittest.TestCase): # Disable gc collection when tracing, otherwise the # deallocators may be traced as well. def setUp(self): + if os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0': + self.skipTest("Line tracing behavior differs when JIT optimizer is disabled") self.using_gc = gc.isenabled() gc.disable() self.addCleanup(sys.settrace, sys.gettrace()) diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py index bf54c9995376d6..19eee19bdea6d5 100644 --- a/Lib/test/test_trace.py +++ b/Lib/test/test_trace.py @@ -142,6 +142,8 @@ def test_traced_func_linear(self): self.assertEqual(self.tracer.results().counts, expected) + @unittest.skipIf(os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0', + "Line counts differ when JIT optimizer is disabled") def test_traced_func_loop(self): self.tracer.runfunc(traced_func_loop, 2, 3) @@ -166,6 +168,8 @@ def test_traced_func_importing(self): self.assertEqual(self.tracer.results().counts, expected) + @unittest.skipIf(os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0', + "Line counts differ when JIT optimizer is disabled") def test_trace_func_generator(self): self.tracer.runfunc(traced_func_calling_generator) @@ -236,6 +240,8 @@ def setUp(self): self.my_py_filename = fix_ext_py(__file__) self.addCleanup(sys.settrace, sys.gettrace()) + @unittest.skipIf(os.environ.get('PYTHON_UOPS_OPTIMIZE') == '0', + "Line counts differ when JIT optimizer is disabled") def test_exec_counts(self): self.tracer = Trace(count=1, trace=0, countfuncs=0, countcallers=0) code = r'''traced_func_loop(2, 5)''' diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst new file mode 100644 index 00000000000000..3850e7f51583ef --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst @@ -0,0 +1,2 @@ +Fix JIT trace buffer overrun by pre-reserving exit stub space. Patch By +Donghee Na. diff --git a/Python/optimizer.c b/Python/optimizer.c index bae5cfa50ead58..21d6c251801ab4 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -591,8 +591,8 @@ translate_bytecode_to_trace( for (;;) { target = INSTR_IP(instr, code); - // Need space for _DEOPT - max_length--; + // Every instruction might need _DEOPT, and _DEOPT might have _ERROR_POP_N before it + max_length -= 2; uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg; From 650e55d3a35a09853c27bf4baf74912c6fa63300 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 27 Aug 2025 17:52:49 +0900 Subject: [PATCH 2/8] fix --- .../2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst index 3850e7f51583ef..3b8a2a88d4a5ec 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-27-17-51-38.gh-issue-137838.lK6T0j.rst @@ -1,2 +1,2 @@ -Fix JIT trace buffer overrun by pre-reserving exit stub space. Patch By -Donghee Na. +Fix JIT trace buffer overrun by increasing possible exit stubs. +Patch by Donghee Na. From 5d816a255010259f5fee75bf8125eae12cf0f1fd Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 27 Aug 2025 17:56:29 +0900 Subject: [PATCH 3/8] fix --- Python/optimizer.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 21d6c251801ab4..379af3abfc5011 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -652,11 +652,6 @@ translate_bytecode_to_trace( RESERVE_RAW(2, "_EXIT_TRACE"); max_length--; } - if (OPCODE_HAS_ERROR(opcode)) { - // Make space for error stub and final _EXIT_TRACE: - RESERVE_RAW(2, "_ERROR_POP_N"); - max_length--; - } switch (opcode) { case POP_JUMP_IF_NONE: case POP_JUMP_IF_NOT_NONE: From b4aad3c0f0eabade46c7e5c7f0ca24e0bc5bbb01 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 28 Aug 2025 02:27:38 +0900 Subject: [PATCH 4/8] Address code review --- Python/optimizer.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 379af3abfc5011..b0cf9c7f4177de 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -591,8 +591,6 @@ translate_bytecode_to_trace( for (;;) { target = INSTR_IP(instr, code); - // Every instruction might need _DEOPT, and _DEOPT might have _ERROR_POP_N before it - max_length -= 2; uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg; @@ -631,6 +629,8 @@ translate_bytecode_to_trace( assert(opcode != ENTER_EXECUTOR && opcode != EXTENDED_ARG); RESERVE_RAW(2, "_CHECK_VALIDITY"); ADD_TO_TRACE(_CHECK_VALIDITY, 0, 0, target); + // Need to reserve 1 stub in case the _CHECK_VALIDITY results in a _DEOPT_IF + max_length--; if (!OPCODE_HAS_NO_SAVE_IP(opcode)) { RESERVE_RAW(2, "_SET_IP"); ADD_TO_TRACE(_SET_IP, 0, (uintptr_t)instr, target); @@ -652,6 +652,15 @@ translate_bytecode_to_trace( RESERVE_RAW(2, "_EXIT_TRACE"); max_length--; } + if (OPCODE_HAS_ERROR(opcode)) { + // Make space for error stub and final _EXIT_TRACE: + RESERVE_RAW(2, "_ERROR_POP_N"); + max_length--; + } + if (OPCODE_HAS_DEOPT(opcode)) { + RESERVE_RAW(2, "_DEOPT"); + max_length--; + } switch (opcode) { case POP_JUMP_IF_NONE: case POP_JUMP_IF_NOT_NONE: From a6fd711ac9fff15b3d6bb0319338a8849670592d Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 28 Aug 2025 02:28:46 +0900 Subject: [PATCH 5/8] nit --- Python/optimizer.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index b0cf9c7f4177de..f3708ebf354838 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -653,13 +653,13 @@ translate_bytecode_to_trace( max_length--; } if (OPCODE_HAS_ERROR(opcode)) { - // Make space for error stub and final _EXIT_TRACE: - RESERVE_RAW(2, "_ERROR_POP_N"); - max_length--; + // Make space for error stub and final _EXIT_TRACE: + RESERVE_RAW(2, "_ERROR_POP_N"); + max_length--; } if (OPCODE_HAS_DEOPT(opcode)) { - RESERVE_RAW(2, "_DEOPT"); - max_length--; + RESERVE_RAW(2, "_DEOPT"); + max_length--; } switch (opcode) { case POP_JUMP_IF_NONE: From 917c6f0dc6b65a5c420a1d008db4bd8be5a34118 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 28 Aug 2025 22:17:26 +0900 Subject: [PATCH 6/8] Update --- Python/optimizer.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index f3708ebf354838..6303621a63daa8 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -550,7 +550,8 @@ translate_bytecode_to_trace( _Py_CODEUNIT *instr, _PyUOpInstruction *trace, int buffer_size, - _PyBloomFilter *dependencies, bool progress_needed) + _PyBloomFilter *dependencies, bool progress_needed, + bool is_noopt) { bool first = true; PyCodeObject *code = _PyFrame_GetCode(frame); @@ -591,7 +592,12 @@ translate_bytecode_to_trace( for (;;) { target = INSTR_IP(instr, code); - + if (is_noopt) { + max_length-=2; + } + else { + max_length--; + } uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg; @@ -629,8 +635,6 @@ translate_bytecode_to_trace( assert(opcode != ENTER_EXECUTOR && opcode != EXTENDED_ARG); RESERVE_RAW(2, "_CHECK_VALIDITY"); ADD_TO_TRACE(_CHECK_VALIDITY, 0, 0, target); - // Need to reserve 1 stub in case the _CHECK_VALIDITY results in a _DEOPT_IF - max_length--; if (!OPCODE_HAS_NO_SAVE_IP(opcode)) { RESERVE_RAW(2, "_SET_IP"); ADD_TO_TRACE(_SET_IP, 0, (uintptr_t)instr, target); @@ -657,10 +661,6 @@ translate_bytecode_to_trace( RESERVE_RAW(2, "_ERROR_POP_N"); max_length--; } - if (OPCODE_HAS_DEOPT(opcode)) { - RESERVE_RAW(2, "_DEOPT"); - max_length--; - } switch (opcode) { case POP_JUMP_IF_NONE: case POP_JUMP_IF_NOT_NONE: @@ -1287,15 +1287,19 @@ uop_optimize( _Py_BloomFilter_Init(&dependencies); _PyUOpInstruction buffer[UOP_MAX_TRACE_LENGTH]; OPT_STAT_INC(attempts); - int length = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies, progress_needed); + char *env_var = Py_GETENV("PYTHON_UOPS_OPTIMIZE"); + bool is_noopt = true; + if (env_var == NULL || *env_var == '\0' || *env_var > '0') { + is_noopt = false; + } + int length = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies, progress_needed, is_noopt); if (length <= 0) { // Error or nothing translated return length; } assert(length < UOP_MAX_TRACE_LENGTH); OPT_STAT_INC(traces_created); - char *env_var = Py_GETENV("PYTHON_UOPS_OPTIMIZE"); - if (env_var == NULL || *env_var == '\0' || *env_var > '0') { + if (!is_noopt) { length = _Py_uop_analyze_and_optimize(frame, buffer, length, curr_stackentries, &dependencies); From 57f6c3972ed38e4df94e9e1fcca98d93af86f26f Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Fri, 29 Aug 2025 00:48:33 +0900 Subject: [PATCH 7/8] Address code review --- Include/internal/pycore_optimizer.h | 2 +- Python/optimizer.c | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 9f930f2107ed5e..1d54cb763621c1 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -119,7 +119,7 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp); #define JIT_CLEANUP_THRESHOLD 100000 // This is the length of the trace we project initially. -#define UOP_MAX_TRACE_LENGTH 800 +#define UOP_MAX_TRACE_LENGTH 1200 #define TRACE_STACK_SIZE 5 diff --git a/Python/optimizer.c b/Python/optimizer.c index 6303621a63daa8..cb21c3dd8be6df 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -550,8 +550,7 @@ translate_bytecode_to_trace( _Py_CODEUNIT *instr, _PyUOpInstruction *trace, int buffer_size, - _PyBloomFilter *dependencies, bool progress_needed, - bool is_noopt) + _PyBloomFilter *dependencies, bool progress_needed) { bool first = true; PyCodeObject *code = _PyFrame_GetCode(frame); @@ -592,12 +591,7 @@ translate_bytecode_to_trace( for (;;) { target = INSTR_IP(instr, code); - if (is_noopt) { - max_length-=2; - } - else { - max_length--; - } + max_length-=2; uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg; @@ -1292,7 +1286,7 @@ uop_optimize( if (env_var == NULL || *env_var == '\0' || *env_var > '0') { is_noopt = false; } - int length = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies, progress_needed, is_noopt); + int length = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies, progress_needed); if (length <= 0) { // Error or nothing translated return length; From ef3c108588c3f94caec02e9a5ffcbdc8826c7c8c Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Fri, 29 Aug 2025 01:10:28 +0900 Subject: [PATCH 8/8] Add comment --- Python/optimizer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/optimizer.c b/Python/optimizer.c index cb21c3dd8be6df..b82c790ffa9e69 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -591,6 +591,7 @@ translate_bytecode_to_trace( for (;;) { target = INSTR_IP(instr, code); + // One for possible _DEOPT, one because _CHECK_VALIDITY itself might _DEOPT max_length-=2; uint32_t opcode = instr->op.code; uint32_t oparg = instr->op.arg;