mirror of https://github.com/python/cpython
gh-87729: add LOAD_SUPER_ATTR instruction for faster super() (#103497)
This speeds up `super()` (by around 85%, for a simple one-level `super().meth()` microbenchmark) by avoiding allocation of a new single-use `super()` object on each use.
This commit is contained in:
parent
22bed58e53
commit
0dc8b50d33
|
@ -1036,6 +1036,24 @@ iterations of the loop.
|
|||
pushed to the stack before the attribute or unbound method respectively.
|
||||
|
||||
|
||||
.. opcode:: LOAD_SUPER_ATTR (namei)
|
||||
|
||||
This opcode implements :func:`super` (e.g. ``super().method()`` and
|
||||
``super().attr``). It works the same as :opcode:`LOAD_ATTR`, except that
|
||||
``namei`` is shifted left by 2 bits instead of 1, and instead of expecting a
|
||||
single receiver on the stack, it expects three objects (from top of stack
|
||||
down): ``self`` (the first argument to the current method), ``cls`` (the
|
||||
class within which the current method was defined), and the global ``super``.
|
||||
|
||||
The low bit of ``namei`` signals to attempt a method load, as with
|
||||
:opcode:`LOAD_ATTR`.
|
||||
|
||||
The second-low bit of ``namei``, if set, means that this was a two-argument
|
||||
call to :func:`super` (unset means zero-argument).
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
|
||||
.. opcode:: COMPARE_OP (opname)
|
||||
|
||||
Performs a Boolean operation. The operation name can be found in
|
||||
|
|
|
@ -179,6 +179,7 @@ const uint8_t _PyOpcode_Deopt[256] = {
|
|||
[LOAD_GLOBAL_BUILTIN] = LOAD_GLOBAL,
|
||||
[LOAD_GLOBAL_MODULE] = LOAD_GLOBAL,
|
||||
[LOAD_NAME] = LOAD_NAME,
|
||||
[LOAD_SUPER_ATTR] = LOAD_SUPER_ATTR,
|
||||
[MAKE_CELL] = MAKE_CELL,
|
||||
[MAKE_FUNCTION] = MAKE_FUNCTION,
|
||||
[MAP_ADD] = MAP_ADD,
|
||||
|
@ -236,7 +237,7 @@ const uint8_t _PyOpcode_Deopt[256] = {
|
|||
#endif // NEED_OPCODE_TABLES
|
||||
|
||||
#ifdef Py_DEBUG
|
||||
static const char *const _PyOpcode_OpName[263] = {
|
||||
static const char *const _PyOpcode_OpName[266] = {
|
||||
[CACHE] = "CACHE",
|
||||
[POP_TOP] = "POP_TOP",
|
||||
[PUSH_NULL] = "PUSH_NULL",
|
||||
|
@ -378,9 +379,9 @@ static const char *const _PyOpcode_OpName[263] = {
|
|||
[STORE_DEREF] = "STORE_DEREF",
|
||||
[DELETE_DEREF] = "DELETE_DEREF",
|
||||
[JUMP_BACKWARD] = "JUMP_BACKWARD",
|
||||
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
|
||||
[LOAD_SUPER_ATTR] = "LOAD_SUPER_ATTR",
|
||||
[CALL_FUNCTION_EX] = "CALL_FUNCTION_EX",
|
||||
[STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT",
|
||||
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
|
||||
[EXTENDED_ARG] = "EXTENDED_ARG",
|
||||
[LIST_APPEND] = "LIST_APPEND",
|
||||
[SET_ADD] = "SET_ADD",
|
||||
|
@ -390,15 +391,15 @@ static const char *const _PyOpcode_OpName[263] = {
|
|||
[YIELD_VALUE] = "YIELD_VALUE",
|
||||
[RESUME] = "RESUME",
|
||||
[MATCH_CLASS] = "MATCH_CLASS",
|
||||
[STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT",
|
||||
[STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT",
|
||||
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
|
||||
[FORMAT_VALUE] = "FORMAT_VALUE",
|
||||
[BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP",
|
||||
[BUILD_STRING] = "BUILD_STRING",
|
||||
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
|
||||
[UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE",
|
||||
[UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE",
|
||||
[SEND_GEN] = "SEND_GEN",
|
||||
[161] = "<161>",
|
||||
[LIST_EXTEND] = "LIST_EXTEND",
|
||||
[SET_UPDATE] = "SET_UPDATE",
|
||||
[DICT_MERGE] = "DICT_MERGE",
|
||||
|
@ -500,11 +501,13 @@ static const char *const _PyOpcode_OpName[263] = {
|
|||
[JUMP] = "JUMP",
|
||||
[JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT",
|
||||
[LOAD_METHOD] = "LOAD_METHOD",
|
||||
[LOAD_SUPER_METHOD] = "LOAD_SUPER_METHOD",
|
||||
[LOAD_ZERO_SUPER_METHOD] = "LOAD_ZERO_SUPER_METHOD",
|
||||
[LOAD_ZERO_SUPER_ATTR] = "LOAD_ZERO_SUPER_ATTR",
|
||||
};
|
||||
#endif
|
||||
|
||||
#define EXTRA_CASES \
|
||||
case 161: \
|
||||
case 166: \
|
||||
case 167: \
|
||||
case 168: \
|
||||
|
|
|
@ -98,6 +98,9 @@ _Py_type_getattro(PyTypeObject *type, PyObject *name);
|
|||
PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
|
||||
PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);
|
||||
|
||||
PyObject *
|
||||
_PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *meth_found);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -95,6 +95,7 @@ extern "C" {
|
|||
#define STORE_DEREF 138
|
||||
#define DELETE_DEREF 139
|
||||
#define JUMP_BACKWARD 140
|
||||
#define LOAD_SUPER_ATTR 141
|
||||
#define CALL_FUNCTION_EX 142
|
||||
#define EXTENDED_ARG 144
|
||||
#define LIST_APPEND 145
|
||||
|
@ -142,7 +143,10 @@ extern "C" {
|
|||
#define JUMP 260
|
||||
#define JUMP_NO_INTERRUPT 261
|
||||
#define LOAD_METHOD 262
|
||||
#define MAX_PSEUDO_OPCODE 262
|
||||
#define LOAD_SUPER_METHOD 263
|
||||
#define LOAD_ZERO_SUPER_METHOD 264
|
||||
#define LOAD_ZERO_SUPER_ATTR 265
|
||||
#define MAX_PSEUDO_OPCODE 265
|
||||
#define BINARY_OP_ADD_FLOAT 6
|
||||
#define BINARY_OP_ADD_INT 7
|
||||
#define BINARY_OP_ADD_UNICODE 8
|
||||
|
@ -198,18 +202,21 @@ extern "C" {
|
|||
#define STORE_ATTR_SLOT 111
|
||||
#define STORE_ATTR_WITH_HINT 112
|
||||
#define STORE_FAST__LOAD_FAST 113
|
||||
#define STORE_FAST__STORE_FAST 141
|
||||
#define STORE_SUBSCR_DICT 143
|
||||
#define STORE_SUBSCR_LIST_INT 153
|
||||
#define UNPACK_SEQUENCE_LIST 154
|
||||
#define UNPACK_SEQUENCE_TUPLE 158
|
||||
#define UNPACK_SEQUENCE_TWO_TUPLE 159
|
||||
#define SEND_GEN 160
|
||||
#define STORE_FAST__STORE_FAST 143
|
||||
#define STORE_SUBSCR_DICT 153
|
||||
#define STORE_SUBSCR_LIST_INT 154
|
||||
#define UNPACK_SEQUENCE_LIST 158
|
||||
#define UNPACK_SEQUENCE_TUPLE 159
|
||||
#define UNPACK_SEQUENCE_TWO_TUPLE 160
|
||||
#define SEND_GEN 161
|
||||
|
||||
#define HAS_ARG(op) ((((op) >= HAVE_ARGUMENT) && (!IS_PSEUDO_OPCODE(op)))\
|
||||
|| ((op) == JUMP) \
|
||||
|| ((op) == JUMP_NO_INTERRUPT) \
|
||||
|| ((op) == LOAD_METHOD) \
|
||||
|| ((op) == LOAD_SUPER_METHOD) \
|
||||
|| ((op) == LOAD_ZERO_SUPER_METHOD) \
|
||||
|| ((op) == LOAD_ZERO_SUPER_ATTR) \
|
||||
)
|
||||
|
||||
#define HAS_CONST(op) (false\
|
||||
|
|
|
@ -41,6 +41,7 @@ JUMP_BACKWARD = opmap['JUMP_BACKWARD']
|
|||
FOR_ITER = opmap['FOR_ITER']
|
||||
SEND = opmap['SEND']
|
||||
LOAD_ATTR = opmap['LOAD_ATTR']
|
||||
LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR']
|
||||
|
||||
CACHE = opmap["CACHE"]
|
||||
|
||||
|
@ -475,6 +476,10 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
|
|||
argval, argrepr = _get_name_info(arg//2, get_name)
|
||||
if (arg & 1) and argrepr:
|
||||
argrepr = "NULL|self + " + argrepr
|
||||
elif deop == LOAD_SUPER_ATTR:
|
||||
argval, argrepr = _get_name_info(arg//4, get_name)
|
||||
if (arg & 1) and argrepr:
|
||||
argrepr = "NULL|self + " + argrepr
|
||||
else:
|
||||
argval, argrepr = _get_name_info(arg, get_name)
|
||||
elif deop in hasjabs:
|
||||
|
|
|
@ -439,7 +439,8 @@ _code_type = type(_write_atomic.__code__)
|
|||
# Python 3.12a7 3523 (Convert COMPARE_AND_BRANCH back to COMPARE_OP)
|
||||
# Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches)
|
||||
# Python 3.12b1 3525 (Shrink the CALL caches)
|
||||
# Python 3.12a7 3526 (Add instrumentation support)
|
||||
# Python 3.12b1 3526 (Add instrumentation support)
|
||||
# Python 3.12b1 3527 (Optimize super() calls)
|
||||
|
||||
# Python 3.13 will start with 3550
|
||||
|
||||
|
@ -456,7 +457,7 @@ _code_type = type(_write_atomic.__code__)
|
|||
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
|
||||
# in PC/launcher.c must also be updated.
|
||||
|
||||
MAGIC_NUMBER = (3526).to_bytes(2, 'little') + b'\r\n'
|
||||
MAGIC_NUMBER = (3527).to_bytes(2, 'little') + b'\r\n'
|
||||
|
||||
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ hasfree.append(138)
|
|||
def_op('DELETE_DEREF', 139)
|
||||
hasfree.append(139)
|
||||
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)
|
||||
|
||||
name_op('LOAD_SUPER_ATTR', 141)
|
||||
def_op('CALL_FUNCTION_EX', 142) # Flags
|
||||
|
||||
def_op('EXTENDED_ARG', 144)
|
||||
|
@ -264,6 +264,9 @@ pseudo_op('JUMP', 260, ['JUMP_FORWARD', 'JUMP_BACKWARD'])
|
|||
pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])
|
||||
|
||||
pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR'])
|
||||
pseudo_op('LOAD_SUPER_METHOD', 263, ['LOAD_SUPER_ATTR'])
|
||||
pseudo_op('LOAD_ZERO_SUPER_METHOD', 264, ['LOAD_SUPER_ATTR'])
|
||||
pseudo_op('LOAD_ZERO_SUPER_ATTR', 265, ['LOAD_SUPER_ATTR'])
|
||||
|
||||
MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class super:
|
||||
msg = "truly super"
|
||||
|
||||
|
||||
class C:
|
||||
def method(self):
|
||||
return super().msg
|
|
@ -962,7 +962,7 @@ id(42)
|
|||
cmd = textwrap.dedent('''
|
||||
class MyList(list):
|
||||
def __init__(self):
|
||||
super().__init__() # wrapper_call()
|
||||
super(*[]).__init__() # wrapper_call()
|
||||
|
||||
id("first break point")
|
||||
l = MyList()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Unit tests for zero-argument super() & related machinery."""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from test import shadowed_super
|
||||
|
||||
|
||||
class A:
|
||||
|
@ -283,17 +285,28 @@ class TestSuper(unittest.TestCase):
|
|||
def test_obscure_super_errors(self):
|
||||
def f():
|
||||
super()
|
||||
self.assertRaises(RuntimeError, f)
|
||||
with self.assertRaisesRegex(RuntimeError, r"no arguments"):
|
||||
f()
|
||||
|
||||
class C:
|
||||
def f():
|
||||
super()
|
||||
with self.assertRaisesRegex(RuntimeError, r"no arguments"):
|
||||
C.f()
|
||||
|
||||
def f(x):
|
||||
del x
|
||||
super()
|
||||
self.assertRaises(RuntimeError, f, None)
|
||||
with self.assertRaisesRegex(RuntimeError, r"arg\[0\] deleted"):
|
||||
f(None)
|
||||
|
||||
class X:
|
||||
def f(x):
|
||||
nonlocal __class__
|
||||
del __class__
|
||||
super()
|
||||
self.assertRaises(RuntimeError, X().f)
|
||||
with self.assertRaisesRegex(RuntimeError, r"empty __class__ cell"):
|
||||
X().f()
|
||||
|
||||
def test_cell_as_self(self):
|
||||
class X:
|
||||
|
@ -325,6 +338,78 @@ class TestSuper(unittest.TestCase):
|
|||
with self.assertRaisesRegex(TypeError, "argument 1 must be a type"):
|
||||
super(1, int)
|
||||
|
||||
def test_shadowed_global(self):
|
||||
self.assertEqual(shadowed_super.C().method(), "truly super")
|
||||
|
||||
def test_shadowed_local(self):
|
||||
class super:
|
||||
msg = "quite super"
|
||||
|
||||
class C:
|
||||
def method(self):
|
||||
return super().msg
|
||||
|
||||
self.assertEqual(C().method(), "quite super")
|
||||
|
||||
def test_shadowed_dynamic(self):
|
||||
class MySuper:
|
||||
msg = "super super"
|
||||
|
||||
class C:
|
||||
def method(self):
|
||||
return super().msg
|
||||
|
||||
with patch("test.test_super.super", MySuper) as m:
|
||||
self.assertEqual(C().method(), "super super")
|
||||
|
||||
def test_shadowed_dynamic_two_arg(self):
|
||||
call_args = []
|
||||
class MySuper:
|
||||
def __init__(self, *args):
|
||||
call_args.append(args)
|
||||
msg = "super super"
|
||||
|
||||
class C:
|
||||
def method(self):
|
||||
return super(1, 2).msg
|
||||
|
||||
with patch("test.test_super.super", MySuper) as m:
|
||||
self.assertEqual(C().method(), "super super")
|
||||
self.assertEqual(call_args, [(1, 2)])
|
||||
|
||||
def test_attribute_error(self):
|
||||
class C:
|
||||
def method(self):
|
||||
return super().msg
|
||||
|
||||
with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"):
|
||||
C().method()
|
||||
|
||||
def test_bad_first_arg(self):
|
||||
class C:
|
||||
def method(self):
|
||||
return super(1, self).method()
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "argument 1 must be a type"):
|
||||
C().method()
|
||||
|
||||
def test_super___class__(self):
|
||||
class C:
|
||||
def method(self):
|
||||
return super().__class__
|
||||
|
||||
self.assertEqual(C().method(), super)
|
||||
|
||||
def test_super_subclass___class__(self):
|
||||
class mysuper(super):
|
||||
pass
|
||||
|
||||
class C:
|
||||
def method(self):
|
||||
return mysuper(C, self).__class__
|
||||
|
||||
self.assertEqual(C().method(), mysuper)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add :opcode:`LOAD_SUPER_ATTR` to speed up ``super().meth()`` and ``super().attr`` calls.
|
|
@ -9346,26 +9346,20 @@ super_repr(PyObject *self)
|
|||
su->type ? su->type->tp_name : "NULL");
|
||||
}
|
||||
|
||||
// if `method` is non-NULL, we are looking for a method descriptor,
|
||||
// and setting `*method` to 1 means we found one.
|
||||
static PyObject *
|
||||
super_getattro(PyObject *self, PyObject *name)
|
||||
do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
|
||||
PyTypeObject *su_obj_type, PyObject *name, int *method)
|
||||
{
|
||||
superobject *su = (superobject *)self;
|
||||
PyTypeObject *starttype;
|
||||
PyObject *mro;
|
||||
PyObject *mro, *res;
|
||||
Py_ssize_t i, n;
|
||||
int temp_su = 0;
|
||||
|
||||
starttype = su->obj_type;
|
||||
if (starttype == NULL)
|
||||
if (su_obj_type == NULL)
|
||||
goto skip;
|
||||
|
||||
/* We want __class__ to return the class of the super object
|
||||
(i.e. super, or a subclass), not the class of su->obj. */
|
||||
if (PyUnicode_Check(name) &&
|
||||
PyUnicode_GET_LENGTH(name) == 9 &&
|
||||
_PyUnicode_Equal(name, &_Py_ID(__class__)))
|
||||
goto skip;
|
||||
|
||||
mro = starttype->tp_mro;
|
||||
mro = su_obj_type->tp_mro;
|
||||
if (mro == NULL)
|
||||
goto skip;
|
||||
|
||||
|
@ -9374,14 +9368,14 @@ super_getattro(PyObject *self, PyObject *name)
|
|||
|
||||
/* No need to check the last one: it's gonna be skipped anyway. */
|
||||
for (i = 0; i+1 < n; i++) {
|
||||
if ((PyObject *)(su->type) == PyTuple_GET_ITEM(mro, i))
|
||||
if ((PyObject *)(su_type) == PyTuple_GET_ITEM(mro, i))
|
||||
break;
|
||||
}
|
||||
i++; /* skip su->type (if any) */
|
||||
if (i >= n)
|
||||
goto skip;
|
||||
|
||||
/* keep a strong reference to mro because starttype->tp_mro can be
|
||||
/* keep a strong reference to mro because su_obj_type->tp_mro can be
|
||||
replaced during PyDict_GetItemWithError(dict, name) */
|
||||
Py_INCREF(mro);
|
||||
do {
|
||||
|
@ -9389,20 +9383,24 @@ super_getattro(PyObject *self, PyObject *name)
|
|||
PyObject *dict = _PyType_CAST(obj)->tp_dict;
|
||||
assert(dict != NULL && PyDict_Check(dict));
|
||||
|
||||
PyObject *res = PyDict_GetItemWithError(dict, name);
|
||||
res = PyDict_GetItemWithError(dict, name);
|
||||
if (res != NULL) {
|
||||
Py_INCREF(res);
|
||||
|
||||
if (method && _PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
|
||||
*method = 1;
|
||||
}
|
||||
else {
|
||||
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
|
||||
if (f != NULL) {
|
||||
PyObject *res2;
|
||||
res2 = f(res,
|
||||
/* Only pass 'obj' param if this is instance-mode super
|
||||
(See SF ID #743627) */
|
||||
(su->obj == (PyObject *)starttype) ? NULL : su->obj,
|
||||
(PyObject *)starttype);
|
||||
(su_obj == (PyObject *)su_obj_type) ? NULL : su_obj,
|
||||
(PyObject *)su_obj_type);
|
||||
Py_SETREF(res, res2);
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(mro);
|
||||
return res;
|
||||
|
@ -9417,7 +9415,34 @@ super_getattro(PyObject *self, PyObject *name)
|
|||
Py_DECREF(mro);
|
||||
|
||||
skip:
|
||||
if (su == NULL) {
|
||||
PyObject *args[] = {(PyObject *)su_type, su_obj};
|
||||
su = (superobject *)PyObject_Vectorcall((PyObject *)&PySuper_Type, args, 2, NULL);
|
||||
if (su == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
temp_su = 1;
|
||||
}
|
||||
res = PyObject_GenericGetAttr((PyObject *)su, name);
|
||||
if (temp_su) {
|
||||
Py_DECREF(su);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
super_getattro(PyObject *self, PyObject *name)
|
||||
{
|
||||
superobject *su = (superobject *)self;
|
||||
|
||||
/* We want __class__ to return the class of the super object
|
||||
(i.e. super, or a subclass), not the class of su->obj. */
|
||||
if (PyUnicode_Check(name) &&
|
||||
PyUnicode_GET_LENGTH(name) == 9 &&
|
||||
_PyUnicode_Equal(name, &_Py_ID(__class__)))
|
||||
return PyObject_GenericGetAttr(self, name);
|
||||
|
||||
return do_super_lookup(su, su->type, su->obj, su->obj_type, name, NULL);
|
||||
}
|
||||
|
||||
static PyTypeObject *
|
||||
|
@ -9473,6 +9498,18 @@ supercheck(PyTypeObject *type, PyObject *obj)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
_PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *method)
|
||||
{
|
||||
PyTypeObject *su_obj_type = supercheck(su_type, su_obj);
|
||||
if (su_obj_type == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
PyObject *res = do_super_lookup(NULL, su_type, su_obj, su_obj_type, name, method);
|
||||
Py_DECREF(su_obj_type);
|
||||
return res;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
super_descr_get(PyObject *self, PyObject *obj, PyObject *type)
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs
|
||||
#include "pycore_sysmodule.h" // _PySys_Audit()
|
||||
#include "pycore_tuple.h" // _PyTuple_ITEMS()
|
||||
#include "pycore_typeobject.h" // _PySuper_Lookup()
|
||||
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
|
||||
|
||||
#include "pycore_dict.h"
|
||||
|
@ -1553,6 +1554,36 @@ dummy_func(
|
|||
PREDICT(JUMP_BACKWARD);
|
||||
}
|
||||
|
||||
inst(LOAD_SUPER_ATTR, (global_super, class, self -- res2 if (oparg & 1), res)) {
|
||||
PyObject *name = GETITEM(frame->f_code->co_names, oparg >> 2);
|
||||
if (global_super == (PyObject *)&PySuper_Type && PyType_Check(class)) {
|
||||
int method = 0;
|
||||
Py_DECREF(global_super);
|
||||
res = _PySuper_Lookup((PyTypeObject *)class, self, name, oparg & 1 ? &method : NULL);
|
||||
Py_DECREF(class);
|
||||
if (res == NULL) {
|
||||
Py_DECREF(self);
|
||||
ERROR_IF(true, error);
|
||||
}
|
||||
// Works with CALL, pushes two values: either `meth | self` or `NULL | meth`.
|
||||
if (method) {
|
||||
res2 = res;
|
||||
res = self; // transfer ownership
|
||||
} else {
|
||||
res2 = NULL;
|
||||
Py_DECREF(self);
|
||||
}
|
||||
} else {
|
||||
PyObject *stack[] = {class, self};
|
||||
PyObject *super = PyObject_Vectorcall(global_super, stack, oparg & 2, NULL);
|
||||
DECREF_INPUTS();
|
||||
ERROR_IF(super == NULL, error);
|
||||
res = PyObject_GetAttr(super, name);
|
||||
Py_DECREF(super);
|
||||
ERROR_IF(res == NULL, error);
|
||||
}
|
||||
}
|
||||
|
||||
family(load_attr, INLINE_CACHE_ENTRIES_LOAD_ATTR) = {
|
||||
LOAD_ATTR,
|
||||
LOAD_ATTR_INSTANCE_VALUE,
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs
|
||||
#include "pycore_sysmodule.h" // _PySys_Audit()
|
||||
#include "pycore_tuple.h" // _PyTuple_ITEMS()
|
||||
#include "pycore_typeobject.h" // _PySuper_Lookup()
|
||||
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
|
||||
|
||||
#include "pycore_dict.h"
|
||||
|
|
124
Python/compile.c
124
Python/compile.c
|
@ -829,6 +829,10 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
|
||||
case LOAD_METHOD:
|
||||
return 1;
|
||||
case LOAD_SUPER_METHOD:
|
||||
case LOAD_ZERO_SUPER_METHOD:
|
||||
case LOAD_ZERO_SUPER_ATTR:
|
||||
return -1;
|
||||
default:
|
||||
return PY_INVALID_STACK_EFFECT;
|
||||
}
|
||||
|
@ -1047,6 +1051,24 @@ compiler_addop_name(struct compiler_unit *u, location loc,
|
|||
arg <<= 1;
|
||||
arg |= 1;
|
||||
}
|
||||
if (opcode == LOAD_SUPER_ATTR) {
|
||||
arg <<= 2;
|
||||
arg |= 2;
|
||||
}
|
||||
if (opcode == LOAD_SUPER_METHOD) {
|
||||
opcode = LOAD_SUPER_ATTR;
|
||||
arg <<= 2;
|
||||
arg |= 3;
|
||||
}
|
||||
if (opcode == LOAD_ZERO_SUPER_ATTR) {
|
||||
opcode = LOAD_SUPER_ATTR;
|
||||
arg <<= 2;
|
||||
}
|
||||
if (opcode == LOAD_ZERO_SUPER_METHOD) {
|
||||
opcode = LOAD_SUPER_ATTR;
|
||||
arg <<= 2;
|
||||
arg |= 1;
|
||||
}
|
||||
return codegen_addop_i(&u->u_instr_sequence, opcode, arg, loc);
|
||||
}
|
||||
|
||||
|
@ -4230,6 +4252,89 @@ is_import_originated(struct compiler *c, expr_ty e)
|
|||
return flags & DEF_IMPORT;
|
||||
}
|
||||
|
||||
static int
|
||||
can_optimize_super_call(struct compiler *c, expr_ty attr)
|
||||
{
|
||||
expr_ty e = attr->v.Attribute.value;
|
||||
if (e->kind != Call_kind ||
|
||||
e->v.Call.func->kind != Name_kind ||
|
||||
!_PyUnicode_EqualToASCIIString(e->v.Call.func->v.Name.id, "super") ||
|
||||
_PyUnicode_EqualToASCIIString(attr->v.Attribute.attr, "__class__") ||
|
||||
asdl_seq_LEN(e->v.Call.keywords) != 0) {
|
||||
return 0;
|
||||
}
|
||||
Py_ssize_t num_args = asdl_seq_LEN(e->v.Call.args);
|
||||
|
||||
PyObject *super_name = e->v.Call.func->v.Name.id;
|
||||
// detect statically-visible shadowing of 'super' name
|
||||
int scope = _PyST_GetScope(c->u->u_ste, super_name);
|
||||
if (scope != GLOBAL_IMPLICIT) {
|
||||
return 0;
|
||||
}
|
||||
scope = _PyST_GetScope(c->c_st->st_top, super_name);
|
||||
if (scope != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (num_args == 2) {
|
||||
for (Py_ssize_t i = 0; i < num_args; i++) {
|
||||
expr_ty elt = asdl_seq_GET(e->v.Call.args, i);
|
||||
if (elt->kind == Starred_kind) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// exactly two non-starred args; we can just load
|
||||
// the provided args
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (num_args != 0) {
|
||||
return 0;
|
||||
}
|
||||
// we need the following for zero-arg super():
|
||||
|
||||
// enclosing function should have at least one argument
|
||||
if (c->u->u_metadata.u_argcount == 0 &&
|
||||
c->u->u_metadata.u_posonlyargcount == 0) {
|
||||
return 0;
|
||||
}
|
||||
// __class__ cell should be available
|
||||
if (get_ref_type(c, &_Py_ID(__class__)) == FREE) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
load_args_for_super(struct compiler *c, expr_ty e) {
|
||||
location loc = LOC(e);
|
||||
|
||||
// load super() global
|
||||
PyObject *super_name = e->v.Call.func->v.Name.id;
|
||||
RETURN_IF_ERROR(compiler_nameop(c, loc, super_name, Load));
|
||||
|
||||
if (asdl_seq_LEN(e->v.Call.args) == 2) {
|
||||
VISIT(c, expr, asdl_seq_GET(e->v.Call.args, 0));
|
||||
VISIT(c, expr, asdl_seq_GET(e->v.Call.args, 1));
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// load __class__ cell
|
||||
PyObject *name = &_Py_ID(__class__);
|
||||
assert(get_ref_type(c, name) == FREE);
|
||||
RETURN_IF_ERROR(compiler_nameop(c, loc, name, Load));
|
||||
|
||||
// load self (first argument)
|
||||
Py_ssize_t i = 0;
|
||||
PyObject *key, *value;
|
||||
if (!PyDict_Next(c->u->u_metadata.u_varnames, &i, &key, &value)) {
|
||||
return ERROR;
|
||||
}
|
||||
RETURN_IF_ERROR(compiler_nameop(c, loc, key, Load));
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
// If an attribute access spans multiple lines, update the current start
|
||||
// location to point to the attribute name.
|
||||
static location
|
||||
|
@ -4297,11 +4402,21 @@ maybe_optimize_method_call(struct compiler *c, expr_ty e)
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Alright, we can optimize the code. */
|
||||
VISIT(c, expr, meth->v.Attribute.value);
|
||||
location loc = LOC(meth);
|
||||
|
||||
if (can_optimize_super_call(c, meth)) {
|
||||
RETURN_IF_ERROR(load_args_for_super(c, meth->v.Attribute.value));
|
||||
int opcode = asdl_seq_LEN(meth->v.Attribute.value->v.Call.args) ?
|
||||
LOAD_SUPER_METHOD : LOAD_ZERO_SUPER_METHOD;
|
||||
ADDOP_NAME(c, loc, opcode, meth->v.Attribute.attr, names);
|
||||
} else {
|
||||
VISIT(c, expr, meth->v.Attribute.value);
|
||||
loc = update_start_location_to_match_attr(c, loc, meth);
|
||||
ADDOP_NAME(c, loc, LOAD_METHOD, meth->v.Attribute.attr, names);
|
||||
}
|
||||
|
||||
VISIT_SEQ(c, expr, e->v.Call.args);
|
||||
|
||||
if (kwdsl) {
|
||||
|
@ -5309,6 +5424,13 @@ compiler_visit_expr1(struct compiler *c, expr_ty e)
|
|||
return compiler_formatted_value(c, e);
|
||||
/* The following exprs can be assignment targets. */
|
||||
case Attribute_kind:
|
||||
if (e->v.Attribute.ctx == Load && can_optimize_super_call(c, e)) {
|
||||
RETURN_IF_ERROR(load_args_for_super(c, e->v.Attribute.value));
|
||||
int opcode = asdl_seq_LEN(e->v.Attribute.value->v.Call.args) ?
|
||||
LOAD_SUPER_ATTR : LOAD_ZERO_SUPER_ATTR;
|
||||
ADDOP_NAME(c, loc, opcode, e->v.Attribute.attr, names);
|
||||
return SUCCESS;
|
||||
}
|
||||
VISIT(c, expr, e->v.Attribute.value);
|
||||
loc = LOC(e);
|
||||
loc = update_start_location_to_match_attr(c, loc, e);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -205,6 +205,8 @@ _PyOpcode_num_popped(int opcode, int oparg, bool jump) {
|
|||
return 1;
|
||||
case MAP_ADD:
|
||||
return 2;
|
||||
case LOAD_SUPER_ATTR:
|
||||
return 3;
|
||||
case LOAD_ATTR:
|
||||
return 1;
|
||||
case LOAD_ATTR_INSTANCE_VALUE:
|
||||
|
@ -589,6 +591,8 @@ _PyOpcode_num_pushed(int opcode, int oparg, bool jump) {
|
|||
return 0;
|
||||
case MAP_ADD:
|
||||
return 0;
|
||||
case LOAD_SUPER_ATTR:
|
||||
return ((oparg & 1) ? 1 : 0) + 1;
|
||||
case LOAD_ATTR:
|
||||
return ((oparg & 1) ? 1 : 0) + 1;
|
||||
case LOAD_ATTR_INSTANCE_VALUE:
|
||||
|
@ -879,6 +883,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[256] = {
|
|||
[DICT_UPDATE] = { true, INSTR_FMT_IB },
|
||||
[DICT_MERGE] = { true, INSTR_FMT_IB },
|
||||
[MAP_ADD] = { true, INSTR_FMT_IB },
|
||||
[LOAD_SUPER_ATTR] = { true, INSTR_FMT_IB },
|
||||
[LOAD_ATTR] = { true, INSTR_FMT_IBC00000000 },
|
||||
[LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000 },
|
||||
[LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000 },
|
||||
|
|
|
@ -140,9 +140,9 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_STORE_DEREF,
|
||||
&&TARGET_DELETE_DEREF,
|
||||
&&TARGET_JUMP_BACKWARD,
|
||||
&&TARGET_STORE_FAST__STORE_FAST,
|
||||
&&TARGET_LOAD_SUPER_ATTR,
|
||||
&&TARGET_CALL_FUNCTION_EX,
|
||||
&&TARGET_STORE_SUBSCR_DICT,
|
||||
&&TARGET_STORE_FAST__STORE_FAST,
|
||||
&&TARGET_EXTENDED_ARG,
|
||||
&&TARGET_LIST_APPEND,
|
||||
&&TARGET_SET_ADD,
|
||||
|
@ -152,15 +152,15 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_YIELD_VALUE,
|
||||
&&TARGET_RESUME,
|
||||
&&TARGET_MATCH_CLASS,
|
||||
&&TARGET_STORE_SUBSCR_DICT,
|
||||
&&TARGET_STORE_SUBSCR_LIST_INT,
|
||||
&&TARGET_UNPACK_SEQUENCE_LIST,
|
||||
&&TARGET_FORMAT_VALUE,
|
||||
&&TARGET_BUILD_CONST_KEY_MAP,
|
||||
&&TARGET_BUILD_STRING,
|
||||
&&TARGET_UNPACK_SEQUENCE_LIST,
|
||||
&&TARGET_UNPACK_SEQUENCE_TUPLE,
|
||||
&&TARGET_UNPACK_SEQUENCE_TWO_TUPLE,
|
||||
&&TARGET_SEND_GEN,
|
||||
&&_unknown_opcode,
|
||||
&&TARGET_LIST_EXTEND,
|
||||
&&TARGET_SET_UPDATE,
|
||||
&&TARGET_DICT_MERGE,
|
||||
|
|
Loading…
Reference in New Issue