bpo-46823: Implement LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE superinstruction (GH-31484)

This commit is contained in:
Dennis Sweeney 2022-02-24 09:55:59 -05:00 committed by GitHub
parent 4fccf91073
commit a52d2528a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 4 deletions

1
Include/opcode.h generated
View file

@ -181,6 +181,7 @@ extern "C" {
#define LOAD_FAST__LOAD_CONST 169
#define LOAD_CONST__LOAD_FAST 170
#define STORE_FAST__STORE_FAST 173
#define LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE 174
#define DO_TRACING 255
#ifdef NEED_OPCODE_JUMP_TABLES
static uint32_t _PyOpcode_RelativeJump[8] = {

View file

@ -295,6 +295,7 @@ def jabs_op(name, op):
"LOAD_FAST__LOAD_CONST",
"LOAD_CONST__LOAD_FAST",
"STORE_FAST__STORE_FAST",
"LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE",
]
_specialization_stats = [
"success",

View file

@ -0,0 +1 @@
Implement a specialized combined opcode ``LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE``. Patch by Dennis Sweeney.

View file

@ -3444,6 +3444,34 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
}
}
TARGET(LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE) {
assert(cframe.use_tracing == 0);
PyObject *owner = GETLOCAL(oparg); // borrowed
if (owner == NULL) {
goto unbound_local_error;
}
// GET_CACHE(), but for the following opcode
assert(_Py_OPCODE(*next_instr) == LOAD_ATTR_INSTANCE_VALUE);
SpecializedCacheEntry *caches = _GetSpecializedCacheEntryForInstruction(
first_instr, INSTR_OFFSET() + 1, _Py_OPARG(*next_instr));
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
assert(cache0->version != 0);
PyTypeObject *tp = Py_TYPE(owner);
// These DEOPT_IF miss branches do PUSH(Py_NewRef(owner)).
DEOPT_IF(tp->tp_version_tag != cache0->version,
LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE);
assert(tp->tp_dictoffset < 0);
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
PyDictValues *values = *_PyObject_ValuesPointer(owner);
DEOPT_IF(values == NULL, LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE);
PyObject *res = values->values[cache0->index];
DEOPT_IF(res == NULL, LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE);
STAT_INC(LOAD_ATTR, hit);
PUSH(Py_NewRef(res));
next_instr++;
NOTRACE_DISPATCH();
}
TARGET(LOAD_ATTR_INSTANCE_VALUE) {
assert(cframe.use_tracing == 0);
PyObject *owner = TOP();
@ -3452,13 +3480,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
assert(cache0->version != 0);
DEOPT_IF(tp->tp_version_tag != cache0->version, LOAD_ATTR);
DEOPT_IF(tp->tp_version_tag != cache0->version, LOAD_ATTR_INSTANCE_VALUE);
assert(tp->tp_dictoffset < 0);
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
PyDictValues *values = *_PyObject_ValuesPointer(owner);
DEOPT_IF(values == NULL, LOAD_ATTR);
DEOPT_IF(values == NULL, LOAD_ATTR_INSTANCE_VALUE);
res = values->values[cache0->index];
DEOPT_IF(res == NULL, LOAD_ATTR);
DEOPT_IF(res == NULL, LOAD_ATTR_INSTANCE_VALUE);
STAT_INC(LOAD_ATTR, hit);
Py_INCREF(res);
SET_TOP(res);
@ -5515,6 +5543,52 @@ MISS_WITH_CACHE(BINARY_SUBSCR)
MISS_WITH_CACHE(UNPACK_SEQUENCE)
MISS_WITH_OPARG_COUNTER(STORE_SUBSCR)
LOAD_ATTR_INSTANCE_VALUE_miss:
{
// Special-cased so that if LOAD_ATTR_INSTANCE_VALUE
// gets replaced, then any preceeding
// LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE gets replaced as well
STAT_INC(LOAD_ATTR_INSTANCE_VALUE, miss);
STAT_INC(LOAD_ATTR, miss);
_PyAdaptiveEntry *cache = &GET_CACHE()->adaptive;
cache->counter--;
if (cache->counter == 0) {
next_instr[-1] = _Py_MAKECODEUNIT(LOAD_ATTR_ADAPTIVE, _Py_OPARG(next_instr[-1]));
if (_Py_OPCODE(next_instr[-2]) == LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE) {
next_instr[-2] = _Py_MAKECODEUNIT(LOAD_FAST, _Py_OPARG(next_instr[-2]));
if (_Py_OPCODE(next_instr[-3]) == LOAD_FAST) {
next_instr[-3] = _Py_MAKECODEUNIT(LOAD_FAST__LOAD_FAST, _Py_OPARG(next_instr[-3]));
}
}
STAT_INC(LOAD_ATTR, deopt);
cache_backoff(cache);
}
oparg = cache->original_oparg;
JUMP_TO_INSTRUCTION(LOAD_ATTR);
}
LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE_miss:
{
// This is special-cased because we have a superinstruction
// that includes a specialized instruction.
// If the specialized portion misses, carry out
// the first instruction, then perform a miss
// for the second instruction as usual.
// Do LOAD_FAST
{
PyObject *value = GETLOCAL(oparg);
assert(value != NULL); // Already checked if unbound
Py_INCREF(value);
PUSH(value);
NEXTOPARG();
next_instr++;
}
// Now we are in the correct state for LOAD_ATTR
goto LOAD_ATTR_INSTANCE_VALUE_miss;
}
binary_subscr_dict_error:
{
PyObject *sub = POP();

View file

@ -173,7 +173,7 @@ static void *opcode_targets[256] = {
&&TARGET_CALL,
&&TARGET_KW_NAMES,
&&TARGET_STORE_FAST__STORE_FAST,
&&_unknown_opcode,
&&TARGET_LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,

View file

@ -889,6 +889,16 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
return -1;
}
if (err) {
if (_Py_OPCODE(instr[0]) == LOAD_ATTR_INSTANCE_VALUE) {
// Note: instr[-1] exists because there's something on the stack,
// and instr[-2] exists because there's at least a RESUME as well.
if (_Py_OPCODE(instr[-1]) == LOAD_FAST) {
instr[-1] = _Py_MAKECODEUNIT(LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE, _Py_OPARG(instr[-1]));
if (_Py_OPCODE(instr[-2]) == LOAD_FAST__LOAD_FAST) {
instr[-2] = _Py_MAKECODEUNIT(LOAD_FAST, _Py_OPARG(instr[-2]));
}
}
}
goto success;
}
fail: