Skip to content

Commit 836c347

Browse files
naschemeebonnal
authored andcommitted
pythongh-115999: Specialize LOAD_SUPER_ATTR in free-threaded builds (pythongh-127128)
Use existing helpers to atomically modify the bytecode. Add unit tests to ensure specializing is happening as expected. Add test_specialize.py that can be used with ThreadSanitizer to detect data races. Fix thread safety issue with cell_set_contents().
1 parent 80d1af3 commit 836c347

File tree

6 files changed

+51
-21
lines changed

6 files changed

+51
-21
lines changed

Lib/test/test_opcache.py

+39
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,45 @@ def binary_op_add_unicode():
12491249
self.assert_specialized(binary_op_add_unicode, "BINARY_OP_ADD_UNICODE")
12501250
self.assert_no_opcode(binary_op_add_unicode, "BINARY_OP")
12511251

1252+
@cpython_only
1253+
@requires_specialization_ft
1254+
def test_load_super_attr(self):
1255+
"""Ensure that LOAD_SUPER_ATTR is specialized as expected."""
1256+
1257+
class A:
1258+
def __init__(self):
1259+
meth = super().__init__
1260+
super().__init__()
1261+
1262+
for _ in range(100):
1263+
A()
1264+
1265+
self.assert_specialized(A.__init__, "LOAD_SUPER_ATTR_ATTR")
1266+
self.assert_specialized(A.__init__, "LOAD_SUPER_ATTR_METHOD")
1267+
self.assert_no_opcode(A.__init__, "LOAD_SUPER_ATTR")
1268+
1269+
# Temporarily replace super() with something else.
1270+
real_super = super
1271+
1272+
def fake_super():
1273+
def init(self):
1274+
pass
1275+
1276+
return init
1277+
1278+
# Force unspecialize
1279+
globals()['super'] = fake_super
1280+
try:
1281+
# Should be unspecialized after enough calls.
1282+
for _ in range(100):
1283+
A()
1284+
finally:
1285+
globals()['super'] = real_super
1286+
1287+
# Ensure the specialized instructions are not present
1288+
self.assert_no_opcode(A.__init__, "LOAD_SUPER_ATTR_ATTR")
1289+
self.assert_no_opcode(A.__init__, "LOAD_SUPER_ATTR_METHOD")
1290+
12521291
@cpython_only
12531292
@requires_specialization_ft
12541293
def test_contain_op(self):

Objects/cellobject.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,9 @@ cell_get_contents(PyObject *self, void *closure)
145145
static int
146146
cell_set_contents(PyObject *self, PyObject *obj, void *Py_UNUSED(ignored))
147147
{
148-
PyCellObject *op = _PyCell_CAST(self);
149-
Py_XSETREF(op->ob_ref, Py_XNewRef(obj));
148+
PyCellObject *cell = _PyCell_CAST(self);
149+
Py_XINCREF(obj);
150+
PyCell_SetTakeRef((PyCellObject *)cell, obj);
150151
return 0;
151152
}
152153

Python/bytecodes.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -1946,7 +1946,7 @@ dummy_func(
19461946
};
19471947

19481948
specializing op(_SPECIALIZE_LOAD_SUPER_ATTR, (counter/1, global_super_st, class_st, unused -- global_super_st, class_st, unused)) {
1949-
#if ENABLE_SPECIALIZATION
1949+
#if ENABLE_SPECIALIZATION_FT
19501950
int load_method = oparg & 1;
19511951
if (ADAPTIVE_COUNTER_TRIGGERS(counter)) {
19521952
next_instr = this_instr;
@@ -1955,7 +1955,7 @@ dummy_func(
19551955
}
19561956
OPCODE_DEFERRED_INC(LOAD_SUPER_ATTR);
19571957
ADVANCE_ADAPTIVE_COUNTER(this_instr[1].counter);
1958-
#endif /* ENABLE_SPECIALIZATION */
1958+
#endif /* ENABLE_SPECIALIZATION_FT */
19591959
}
19601960

19611961
tier1 op(_LOAD_SUPER_ATTR, (global_super_st, class_st, self_st -- attr, null if (oparg & 1))) {

Python/ceval.c

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
#include "pycore_setobject.h" // _PySet_Update()
2929
#include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs
3030
#include "pycore_tuple.h" // _PyTuple_ITEMS()
31-
#include "pycore_typeobject.h" // _PySuper_Lookup()
3231
#include "pycore_uop_ids.h" // Uops
3332
#include "pycore_pyerrors.h"
3433

Python/generated_cases.c.h

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

+5-14
Original file line numberDiff line numberDiff line change
@@ -794,9 +794,8 @@ _Py_Specialize_LoadSuperAttr(_PyStackRef global_super_st, _PyStackRef cls_st, _P
794794
PyObject *global_super = PyStackRef_AsPyObjectBorrow(global_super_st);
795795
PyObject *cls = PyStackRef_AsPyObjectBorrow(cls_st);
796796

797-
assert(ENABLE_SPECIALIZATION);
797+
assert(ENABLE_SPECIALIZATION_FT);
798798
assert(_PyOpcode_Caches[LOAD_SUPER_ATTR] == INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR);
799-
_PySuperAttrCache *cache = (_PySuperAttrCache *)(instr + 1);
800799
if (global_super != (PyObject *)&PySuper_Type) {
801800
SPECIALIZATION_FAIL(LOAD_SUPER_ATTR, SPEC_FAIL_SUPER_SHADOWED);
802801
goto fail;
@@ -805,19 +804,11 @@ _Py_Specialize_LoadSuperAttr(_PyStackRef global_super_st, _PyStackRef cls_st, _P
805804
SPECIALIZATION_FAIL(LOAD_SUPER_ATTR, SPEC_FAIL_SUPER_BAD_CLASS);
806805
goto fail;
807806
}
808-
instr->op.code = load_method ? LOAD_SUPER_ATTR_METHOD : LOAD_SUPER_ATTR_ATTR;
809-
goto success;
810-
811-
fail:
812-
STAT_INC(LOAD_SUPER_ATTR, failure);
813-
assert(!PyErr_Occurred());
814-
instr->op.code = LOAD_SUPER_ATTR;
815-
cache->counter = adaptive_counter_backoff(cache->counter);
807+
uint8_t load_code = load_method ? LOAD_SUPER_ATTR_METHOD : LOAD_SUPER_ATTR_ATTR;
808+
specialize(instr, load_code);
816809
return;
817-
success:
818-
STAT_INC(LOAD_SUPER_ATTR, success);
819-
assert(!PyErr_Occurred());
820-
cache->counter = adaptive_counter_cooldown();
810+
fail:
811+
unspecialize(instr);
821812
}
822813

823814
typedef enum {

0 commit comments

Comments
 (0)