From ed73dce31a920311b6070c825e64deb41192a8bf Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 5 Dec 2025 14:29:56 +0000 Subject: [PATCH 1/4] Watch attribute loads when promoting JIT constants --- Lib/test/test_capi/test_opt.py | 22 ++++++++++++++++++++++ Python/optimizer_analysis.c | 4 +++- Python/optimizer_bytecodes.c | 12 ++++++------ Python/optimizer_cases.c.h | 12 ++++++------ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 51234a2e40f54f..68310ab352f71c 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2695,6 +2695,28 @@ def recursive_wrapper_4569(): pass """)) + def test_attribute_changes_are_watched(self): + # Just running to make sure it doesn't crash. + script_helper.assert_python_ok("-c", textwrap.dedent(""" + from concurrent.futures import ThreadPoolExecutor + from unittest import TestCase + NTHREADS = 6 + BOTTOM = 0 + TOP = 1250000 + class A: + attr = 10**1000 + class TestType(TestCase): + def read(id0): + for _ in range(BOTTOM, TOP): + A.attr + def write(id0): + x = A.attr + x += 1 + A.attr = x + with ThreadPoolExecutor(NTHREADS) as pool: + pool.submit(read, (1,)) + pool.submit(write, (1,)) + """)) def global_identity(x): return x diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 51722556554609..c4afc6bd29086f 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -242,7 +242,7 @@ eliminate_pop_guard(_PyUOpInstruction *this_instr, bool exit) } static JitOptRef -lookup_attr(JitOptContext *ctx, _PyUOpInstruction *this_instr, +lookup_attr(JitOptContext *ctx, _PyBloomFilter *dependencies, _PyUOpInstruction *this_instr, PyTypeObject *type, PyObject *name, uint16_t immortal, uint16_t mortal) { @@ -252,6 +252,8 @@ lookup_attr(JitOptContext *ctx, _PyUOpInstruction *this_instr, if (lookup) { int opcode = _Py_IsImmortal(lookup) ? immortal : mortal; REPLACE_OP(this_instr, opcode, 0, (uintptr_t)lookup); + PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type); + _Py_BloomFilter_Add(dependencies, type); return sym_new_const(ctx, lookup); } } diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 06fa8a4522a499..9eee3c69da7c86 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -607,7 +607,7 @@ dummy_func(void) { (void)descr; PyTypeObject *type = (PyTypeObject *)sym_get_const(ctx, owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _POP_TOP_LOAD_CONST_INLINE_BORROW, _POP_TOP_LOAD_CONST_INLINE); } @@ -616,7 +616,7 @@ dummy_func(void) { (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _POP_TOP_LOAD_CONST_INLINE_BORROW, _POP_TOP_LOAD_CONST_INLINE); } @@ -625,7 +625,7 @@ dummy_func(void) { (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _POP_TOP_LOAD_CONST_INLINE_BORROW, _POP_TOP_LOAD_CONST_INLINE); } @@ -634,7 +634,7 @@ dummy_func(void) { (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _LOAD_CONST_UNDER_INLINE_BORROW, _LOAD_CONST_UNDER_INLINE); self = owner; @@ -644,7 +644,7 @@ dummy_func(void) { (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _LOAD_CONST_UNDER_INLINE_BORROW, _LOAD_CONST_UNDER_INLINE); self = owner; @@ -654,7 +654,7 @@ dummy_func(void) { (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _LOAD_CONST_UNDER_INLINE_BORROW, _LOAD_CONST_UNDER_INLINE); self = owner; diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 85bebed58677ed..36130cdb893ab1 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1848,7 +1848,7 @@ (void)descr; PyTypeObject *type = (PyTypeObject *)sym_get_const(ctx, owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _POP_TOP_LOAD_CONST_INLINE_BORROW, _POP_TOP_LOAD_CONST_INLINE); stack_pointer[-1] = attr; @@ -2495,7 +2495,7 @@ (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _LOAD_CONST_UNDER_INLINE_BORROW, _LOAD_CONST_UNDER_INLINE); self = owner; @@ -2516,7 +2516,7 @@ (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _LOAD_CONST_UNDER_INLINE_BORROW, _LOAD_CONST_UNDER_INLINE); self = owner; @@ -2536,7 +2536,7 @@ (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _POP_TOP_LOAD_CONST_INLINE_BORROW, _POP_TOP_LOAD_CONST_INLINE); stack_pointer[-1] = attr; @@ -2551,7 +2551,7 @@ (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _POP_TOP_LOAD_CONST_INLINE_BORROW, _POP_TOP_LOAD_CONST_INLINE); stack_pointer[-1] = attr; @@ -2571,7 +2571,7 @@ (void)descr; PyTypeObject *type = sym_get_type(owner); PyObject *name = get_co_name(ctx, oparg >> 1); - attr = lookup_attr(ctx, this_instr, type, name, + attr = lookup_attr(ctx, dependencies, this_instr, type, name, _LOAD_CONST_UNDER_INLINE_BORROW, _LOAD_CONST_UNDER_INLINE); self = owner; From c7e283d431f47808b310ef2358bbf372205a6d90 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:34:03 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst new file mode 100644 index 00000000000000..4ea457a01dd2de --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst @@ -0,0 +1 @@ +Fix a bug with attribute loads treated as constants in the JIT. Patch by Ken Jin. Reproducer by Yuancheng Jiang. From e87d2f99d51a74f6d0f689a2be0fbc4c2268203c Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 5 Dec 2025 21:57:44 +0000 Subject: [PATCH 3/4] Update Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst Co-authored-by: Savannah Ostrowski --- .../2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst index 4ea457a01dd2de..aa8e3da33580c8 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-05-14-33-54.gh-issue-142276.H4j8hP.rst @@ -1 +1 @@ -Fix a bug with attribute loads treated as constants in the JIT. Patch by Ken Jin. Reproducer by Yuancheng Jiang. +Fix missing type watcher when promoting attribute loads to constants in the JIT. Patch by Ken Jin. Reproducer by Yuancheng Jiang. From e4a7c828d7fbedbb4e9abb52f62563542e19de68 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Fri, 5 Dec 2025 22:13:25 +0000 Subject: [PATCH 4/4] format --- Lib/test/test_capi/test_opt.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 68310ab352f71c..2e7485e89fdac5 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2706,13 +2706,13 @@ def test_attribute_changes_are_watched(self): class A: attr = 10**1000 class TestType(TestCase): - def read(id0): - for _ in range(BOTTOM, TOP): - A.attr - def write(id0): - x = A.attr - x += 1 - A.attr = x + def read(id0): + for _ in range(BOTTOM, TOP): + A.attr + def write(id0): + x = A.attr + x += 1 + A.attr = x with ThreadPoolExecutor(NTHREADS) as pool: pool.submit(read, (1,)) pool.submit(write, (1,))