Skip to content

Commit 0fee38e

Browse files
eendebakptclaude
andcommitted
Optimize __init__ in JIT: resolve init function and eliminate redundant type guards
- _CHECK_AND_ALLOCATE_OBJECT: resolve __init__ from type's _spec_cache so the optimizer can follow into __init__ bodies - _GUARD_TYPE_VERSION_LOCKED: add optimizer handler to track type version and NOP redundant guards on the same object - Add test_guard_type_version_locked_removed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 80d0a85 commit 0fee38e

File tree

3 files changed

+88
-4
lines changed

3 files changed

+88
-4
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,29 @@ class Foo:
15181518
Foo.attr = 0
15191519
self.assertFalse(ex.is_valid())
15201520

1521+
def test_guard_type_version_locked_removed(self):
1522+
"""
1523+
Verify that redundant _GUARD_TYPE_VERSION_LOCKED guards are
1524+
eliminated for sequential STORE_ATTR_INSTANCE_VALUE in __init__.
1525+
"""
1526+
1527+
class Foo:
1528+
def __init__(self):
1529+
self.a = 1
1530+
self.b = 2
1531+
self.c = 3
1532+
1533+
def thing(n):
1534+
for _ in range(n):
1535+
Foo()
1536+
1537+
res, ex = self._run_with_optimizer(thing, TIER2_THRESHOLD)
1538+
self.assertIsNotNone(ex)
1539+
opnames = list(iter_opnames(ex))
1540+
guard_locked_count = opnames.count("_GUARD_TYPE_VERSION_LOCKED")
1541+
# Only the first store needs the guard; the rest should be NOPed.
1542+
self.assertEqual(guard_locked_count, 1)
1543+
15211544
def test_type_version_doesnt_segfault(self):
15221545
"""
15231546
Tests that setting a type version doesn't cause a segfault when later looking at the stack.

Python/optimizer_bytecodes.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@ dummy_func(void) {
134134
assert(!PyJitRef_IsUnique(value));
135135
}
136136

137+
op(_GUARD_TYPE_VERSION_LOCKED, (type_version/2, owner -- owner)) {
138+
assert(type_version);
139+
if (sym_matches_type_version(owner, type_version)) {
140+
ADD_OP(_NOP, 0, 0);
141+
} else {
142+
PyTypeObject *type = _PyType_LookupByVersion(type_version);
143+
if (type) {
144+
if (sym_set_type_version(owner, type_version)) {
145+
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type);
146+
_Py_BloomFilter_Add(dependencies, type);
147+
}
148+
}
149+
}
150+
}
151+
137152
op(_STORE_ATTR_INSTANCE_VALUE, (offset/1, value, owner -- o)) {
138153
(void)offset;
139154
(void)value;
@@ -1027,9 +1042,25 @@ dummy_func(void) {
10271042
}
10281043

10291044
op(_CHECK_AND_ALLOCATE_OBJECT, (type_version/2, callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) {
1030-
(void)type_version;
10311045
(void)args;
1032-
callable = sym_new_not_null(ctx);
1046+
PyTypeObject *type = _PyType_LookupByVersion(type_version);
1047+
if (type) {
1048+
PyHeapTypeObject *cls = (PyHeapTypeObject *)type;
1049+
PyObject *init = FT_ATOMIC_LOAD_PTR_ACQUIRE(cls->_spec_cache.init);
1050+
if (init != NULL && PyFunction_Check(init)) {
1051+
// Record the __init__ function so _CREATE_INIT_FRAME can
1052+
// resolve the code object and continue optimizing.
1053+
callable = sym_new_const(ctx, init);
1054+
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type);
1055+
_Py_BloomFilter_Add(dependencies, type);
1056+
}
1057+
else {
1058+
callable = sym_new_not_null(ctx);
1059+
}
1060+
}
1061+
else {
1062+
callable = sym_new_not_null(ctx);
1063+
}
10331064
self_or_null = sym_new_not_null(ctx);
10341065
}
10351066

Python/optimizer_cases.c.h

Lines changed: 32 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)