[Python-checkins] bpo-46823: Implement LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE superinstruction (GH-31484)

markshannon webhook-mailer at python.org
Thu Feb 24 09:56:05 EST 2022


https://github.com/python/cpython/commit/a52d2528a405c1e2bfeb6470cb3313a5338dc45f
commit: a52d2528a405c1e2bfeb6470cb3313a5338dc45f
branch: main
author: Dennis Sweeney <36520290+sweeneyde at users.noreply.github.com>
committer: markshannon <mark at hotpy.org>
date: 2022-02-24T14:55:59Z
summary:

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

files:
A Misc/NEWS.d/next/Core and Builtins/2022-02-22-05-14-25.bpo-46823.z9NZC9.rst
M Include/opcode.h
M Lib/opcode.py
M Python/ceval.c
M Python/opcode_targets.h
M Python/specialize.c

diff --git a/Include/opcode.h b/Include/opcode.h
index 7820af64829b6..50153c19e9b23 100644
--- a/Include/opcode.h
+++ b/Include/opcode.h
@@ -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] = {
diff --git a/Lib/opcode.py b/Lib/opcode.py
index 0c859c16b718c..71a9347debad7 100644
--- a/Lib/opcode.py
+++ b/Lib/opcode.py
@@ -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",
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-02-22-05-14-25.bpo-46823.z9NZC9.rst b/Misc/NEWS.d/next/Core and Builtins/2022-02-22-05-14-25.bpo-46823.z9NZC9.rst
new file mode 100644
index 0000000000000..908f48d33f285
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-02-22-05-14-25.bpo-46823.z9NZC9.rst	
@@ -0,0 +1 @@
+Implement a specialized combined opcode ``LOAD_FAST__LOAD_ATTR_INSTANCE_VALUE``.  Patch by Dennis Sweeney.
diff --git a/Python/ceval.c b/Python/ceval.c
index 807757019020a..f3bdaf1377077 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -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();
diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h
index cf5bdc772e421..fac671eb024f5 100644
--- a/Python/opcode_targets.h
+++ b/Python/opcode_targets.h
@@ -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,
diff --git a/Python/specialize.c b/Python/specialize.c
index 816cca1074e83..1641766712a60 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -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:



More information about the Python-checkins mailing list