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:
Carl Meyer 2023-04-24 16:22:14 -06:00 committed by GitHub
parent 22bed58e53
commit 0dc8b50d33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 783 additions and 408 deletions

View File

@ -1036,6 +1036,24 @@ iterations of the loop.
pushed to the stack before the attribute or unbound method respectively. 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) .. opcode:: COMPARE_OP (opname)
Performs a Boolean operation. The operation name can be found in Performs a Boolean operation. The operation name can be found in

View File

@ -179,6 +179,7 @@ const uint8_t _PyOpcode_Deopt[256] = {
[LOAD_GLOBAL_BUILTIN] = LOAD_GLOBAL, [LOAD_GLOBAL_BUILTIN] = LOAD_GLOBAL,
[LOAD_GLOBAL_MODULE] = LOAD_GLOBAL, [LOAD_GLOBAL_MODULE] = LOAD_GLOBAL,
[LOAD_NAME] = LOAD_NAME, [LOAD_NAME] = LOAD_NAME,
[LOAD_SUPER_ATTR] = LOAD_SUPER_ATTR,
[MAKE_CELL] = MAKE_CELL, [MAKE_CELL] = MAKE_CELL,
[MAKE_FUNCTION] = MAKE_FUNCTION, [MAKE_FUNCTION] = MAKE_FUNCTION,
[MAP_ADD] = MAP_ADD, [MAP_ADD] = MAP_ADD,
@ -236,7 +237,7 @@ const uint8_t _PyOpcode_Deopt[256] = {
#endif // NEED_OPCODE_TABLES #endif // NEED_OPCODE_TABLES
#ifdef Py_DEBUG #ifdef Py_DEBUG
static const char *const _PyOpcode_OpName[263] = { static const char *const _PyOpcode_OpName[266] = {
[CACHE] = "CACHE", [CACHE] = "CACHE",
[POP_TOP] = "POP_TOP", [POP_TOP] = "POP_TOP",
[PUSH_NULL] = "PUSH_NULL", [PUSH_NULL] = "PUSH_NULL",
@ -378,9 +379,9 @@ static const char *const _PyOpcode_OpName[263] = {
[STORE_DEREF] = "STORE_DEREF", [STORE_DEREF] = "STORE_DEREF",
[DELETE_DEREF] = "DELETE_DEREF", [DELETE_DEREF] = "DELETE_DEREF",
[JUMP_BACKWARD] = "JUMP_BACKWARD", [JUMP_BACKWARD] = "JUMP_BACKWARD",
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST", [LOAD_SUPER_ATTR] = "LOAD_SUPER_ATTR",
[CALL_FUNCTION_EX] = "CALL_FUNCTION_EX", [CALL_FUNCTION_EX] = "CALL_FUNCTION_EX",
[STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT", [STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
[EXTENDED_ARG] = "EXTENDED_ARG", [EXTENDED_ARG] = "EXTENDED_ARG",
[LIST_APPEND] = "LIST_APPEND", [LIST_APPEND] = "LIST_APPEND",
[SET_ADD] = "SET_ADD", [SET_ADD] = "SET_ADD",
@ -390,15 +391,15 @@ static const char *const _PyOpcode_OpName[263] = {
[YIELD_VALUE] = "YIELD_VALUE", [YIELD_VALUE] = "YIELD_VALUE",
[RESUME] = "RESUME", [RESUME] = "RESUME",
[MATCH_CLASS] = "MATCH_CLASS", [MATCH_CLASS] = "MATCH_CLASS",
[STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT",
[STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT", [STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT",
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
[FORMAT_VALUE] = "FORMAT_VALUE", [FORMAT_VALUE] = "FORMAT_VALUE",
[BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP", [BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP",
[BUILD_STRING] = "BUILD_STRING", [BUILD_STRING] = "BUILD_STRING",
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
[UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE", [UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE",
[UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE", [UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE",
[SEND_GEN] = "SEND_GEN", [SEND_GEN] = "SEND_GEN",
[161] = "<161>",
[LIST_EXTEND] = "LIST_EXTEND", [LIST_EXTEND] = "LIST_EXTEND",
[SET_UPDATE] = "SET_UPDATE", [SET_UPDATE] = "SET_UPDATE",
[DICT_MERGE] = "DICT_MERGE", [DICT_MERGE] = "DICT_MERGE",
@ -500,11 +501,13 @@ static const char *const _PyOpcode_OpName[263] = {
[JUMP] = "JUMP", [JUMP] = "JUMP",
[JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT", [JUMP_NO_INTERRUPT] = "JUMP_NO_INTERRUPT",
[LOAD_METHOD] = "LOAD_METHOD", [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 #endif
#define EXTRA_CASES \ #define EXTRA_CASES \
case 161: \
case 166: \ case 166: \
case 167: \ case 167: \
case 168: \ case 168: \

View File

@ -98,6 +98,9 @@ _Py_type_getattro(PyTypeObject *type, PyObject *name);
PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name); PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
PyObject *_Py_slot_tp_getattr_hook(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 #ifdef __cplusplus
} }
#endif #endif

23
Include/opcode.h generated
View File

@ -95,6 +95,7 @@ extern "C" {
#define STORE_DEREF 138 #define STORE_DEREF 138
#define DELETE_DEREF 139 #define DELETE_DEREF 139
#define JUMP_BACKWARD 140 #define JUMP_BACKWARD 140
#define LOAD_SUPER_ATTR 141
#define CALL_FUNCTION_EX 142 #define CALL_FUNCTION_EX 142
#define EXTENDED_ARG 144 #define EXTENDED_ARG 144
#define LIST_APPEND 145 #define LIST_APPEND 145
@ -142,7 +143,10 @@ extern "C" {
#define JUMP 260 #define JUMP 260
#define JUMP_NO_INTERRUPT 261 #define JUMP_NO_INTERRUPT 261
#define LOAD_METHOD 262 #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_FLOAT 6
#define BINARY_OP_ADD_INT 7 #define BINARY_OP_ADD_INT 7
#define BINARY_OP_ADD_UNICODE 8 #define BINARY_OP_ADD_UNICODE 8
@ -198,18 +202,21 @@ extern "C" {
#define STORE_ATTR_SLOT 111 #define STORE_ATTR_SLOT 111
#define STORE_ATTR_WITH_HINT 112 #define STORE_ATTR_WITH_HINT 112
#define STORE_FAST__LOAD_FAST 113 #define STORE_FAST__LOAD_FAST 113
#define STORE_FAST__STORE_FAST 141 #define STORE_FAST__STORE_FAST 143
#define STORE_SUBSCR_DICT 143 #define STORE_SUBSCR_DICT 153
#define STORE_SUBSCR_LIST_INT 153 #define STORE_SUBSCR_LIST_INT 154
#define UNPACK_SEQUENCE_LIST 154 #define UNPACK_SEQUENCE_LIST 158
#define UNPACK_SEQUENCE_TUPLE 158 #define UNPACK_SEQUENCE_TUPLE 159
#define UNPACK_SEQUENCE_TWO_TUPLE 159 #define UNPACK_SEQUENCE_TWO_TUPLE 160
#define SEND_GEN 160 #define SEND_GEN 161
#define HAS_ARG(op) ((((op) >= HAVE_ARGUMENT) && (!IS_PSEUDO_OPCODE(op)))\ #define HAS_ARG(op) ((((op) >= HAVE_ARGUMENT) && (!IS_PSEUDO_OPCODE(op)))\
|| ((op) == JUMP) \ || ((op) == JUMP) \
|| ((op) == JUMP_NO_INTERRUPT) \ || ((op) == JUMP_NO_INTERRUPT) \
|| ((op) == LOAD_METHOD) \ || ((op) == LOAD_METHOD) \
|| ((op) == LOAD_SUPER_METHOD) \
|| ((op) == LOAD_ZERO_SUPER_METHOD) \
|| ((op) == LOAD_ZERO_SUPER_ATTR) \
) )
#define HAS_CONST(op) (false\ #define HAS_CONST(op) (false\

View File

@ -41,6 +41,7 @@ JUMP_BACKWARD = opmap['JUMP_BACKWARD']
FOR_ITER = opmap['FOR_ITER'] FOR_ITER = opmap['FOR_ITER']
SEND = opmap['SEND'] SEND = opmap['SEND']
LOAD_ATTR = opmap['LOAD_ATTR'] LOAD_ATTR = opmap['LOAD_ATTR']
LOAD_SUPER_ATTR = opmap['LOAD_SUPER_ATTR']
CACHE = opmap["CACHE"] 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) argval, argrepr = _get_name_info(arg//2, get_name)
if (arg & 1) and argrepr: if (arg & 1) and argrepr:
argrepr = "NULL|self + " + 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: else:
argval, argrepr = _get_name_info(arg, get_name) argval, argrepr = _get_name_info(arg, get_name)
elif deop in hasjabs: elif deop in hasjabs:

View File

@ -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 3523 (Convert COMPARE_AND_BRANCH back to COMPARE_OP)
# Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches) # Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches)
# Python 3.12b1 3525 (Shrink the CALL 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 # 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 # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated. # 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 _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

View File

@ -196,7 +196,7 @@ hasfree.append(138)
def_op('DELETE_DEREF', 139) def_op('DELETE_DEREF', 139)
hasfree.append(139) hasfree.append(139)
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards) 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('CALL_FUNCTION_EX', 142) # Flags
def_op('EXTENDED_ARG', 144) 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('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])
pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR']) 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 MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1

View File

@ -0,0 +1,7 @@
class super:
msg = "truly super"
class C:
def method(self):
return super().msg

View File

@ -962,7 +962,7 @@ id(42)
cmd = textwrap.dedent(''' cmd = textwrap.dedent('''
class MyList(list): class MyList(list):
def __init__(self): def __init__(self):
super().__init__() # wrapper_call() super(*[]).__init__() # wrapper_call()
id("first break point") id("first break point")
l = MyList() l = MyList()

View File

@ -1,6 +1,8 @@
"""Unit tests for zero-argument super() & related machinery.""" """Unit tests for zero-argument super() & related machinery."""
import unittest import unittest
from unittest.mock import patch
from test import shadowed_super
class A: class A:
@ -283,17 +285,28 @@ class TestSuper(unittest.TestCase):
def test_obscure_super_errors(self): def test_obscure_super_errors(self):
def f(): def f():
super() 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): def f(x):
del x del x
super() super()
self.assertRaises(RuntimeError, f, None) with self.assertRaisesRegex(RuntimeError, r"arg\[0\] deleted"):
f(None)
class X: class X:
def f(x): def f(x):
nonlocal __class__ nonlocal __class__
del __class__ del __class__
super() super()
self.assertRaises(RuntimeError, X().f) with self.assertRaisesRegex(RuntimeError, r"empty __class__ cell"):
X().f()
def test_cell_as_self(self): def test_cell_as_self(self):
class X: class X:
@ -325,6 +338,78 @@ class TestSuper(unittest.TestCase):
with self.assertRaisesRegex(TypeError, "argument 1 must be a type"): with self.assertRaisesRegex(TypeError, "argument 1 must be a type"):
super(1, int) 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__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -0,0 +1 @@
Add :opcode:`LOAD_SUPER_ATTR` to speed up ``super().meth()`` and ``super().attr`` calls.

View File

@ -9346,26 +9346,20 @@ super_repr(PyObject *self)
su->type ? su->type->tp_name : "NULL"); 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 * 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; PyObject *mro, *res;
PyTypeObject *starttype;
PyObject *mro;
Py_ssize_t i, n; Py_ssize_t i, n;
int temp_su = 0;
starttype = su->obj_type; if (su_obj_type == NULL)
if (starttype == NULL)
goto skip; goto skip;
/* We want __class__ to return the class of the super object mro = su_obj_type->tp_mro;
(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;
if (mro == NULL) if (mro == NULL)
goto skip; 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. */ /* No need to check the last one: it's gonna be skipped anyway. */
for (i = 0; i+1 < n; i++) { 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; break;
} }
i++; /* skip su->type (if any) */ i++; /* skip su->type (if any) */
if (i >= n) if (i >= n)
goto skip; 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) */ replaced during PyDict_GetItemWithError(dict, name) */
Py_INCREF(mro); Py_INCREF(mro);
do { do {
@ -9389,20 +9383,24 @@ super_getattro(PyObject *self, PyObject *name)
PyObject *dict = _PyType_CAST(obj)->tp_dict; PyObject *dict = _PyType_CAST(obj)->tp_dict;
assert(dict != NULL && PyDict_Check(dict)); assert(dict != NULL && PyDict_Check(dict));
PyObject *res = PyDict_GetItemWithError(dict, name); res = PyDict_GetItemWithError(dict, name);
if (res != NULL) { if (res != NULL) {
Py_INCREF(res); 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; descrgetfunc f = Py_TYPE(res)->tp_descr_get;
if (f != NULL) { if (f != NULL) {
PyObject *res2; PyObject *res2;
res2 = f(res, res2 = f(res,
/* Only pass 'obj' param if this is instance-mode super /* Only pass 'obj' param if this is instance-mode super
(See SF ID #743627) */ (See SF ID #743627) */
(su->obj == (PyObject *)starttype) ? NULL : su->obj, (su_obj == (PyObject *)su_obj_type) ? NULL : su_obj,
(PyObject *)starttype); (PyObject *)su_obj_type);
Py_SETREF(res, res2); Py_SETREF(res, res2);
} }
}
Py_DECREF(mro); Py_DECREF(mro);
return res; return res;
@ -9417,7 +9415,34 @@ super_getattro(PyObject *self, PyObject *name)
Py_DECREF(mro); Py_DECREF(mro);
skip: 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 PyObject_GenericGetAttr(self, name);
return do_super_lookup(su, su->type, su->obj, su->obj_type, name, NULL);
} }
static PyTypeObject * static PyTypeObject *
@ -9473,6 +9498,18 @@ supercheck(PyTypeObject *type, PyObject *obj)
return NULL; 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 * static PyObject *
super_descr_get(PyObject *self, PyObject *obj, PyObject *type) super_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{ {

View File

@ -25,6 +25,7 @@
#include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs #include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs
#include "pycore_sysmodule.h" // _PySys_Audit() #include "pycore_sysmodule.h" // _PySys_Audit()
#include "pycore_tuple.h" // _PyTuple_ITEMS() #include "pycore_tuple.h" // _PyTuple_ITEMS()
#include "pycore_typeobject.h" // _PySuper_Lookup()
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS #include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
#include "pycore_dict.h" #include "pycore_dict.h"
@ -1553,6 +1554,36 @@ dummy_func(
PREDICT(JUMP_BACKWARD); 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) = { family(load_attr, INLINE_CACHE_ENTRIES_LOAD_ATTR) = {
LOAD_ATTR, LOAD_ATTR,
LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_INSTANCE_VALUE,

View File

@ -21,6 +21,7 @@
#include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs #include "pycore_sliceobject.h" // _PyBuildSlice_ConsumeRefs
#include "pycore_sysmodule.h" // _PySys_Audit() #include "pycore_sysmodule.h" // _PySys_Audit()
#include "pycore_tuple.h" // _PyTuple_ITEMS() #include "pycore_tuple.h" // _PyTuple_ITEMS()
#include "pycore_typeobject.h" // _PySuper_Lookup()
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS #include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
#include "pycore_dict.h" #include "pycore_dict.h"

View File

@ -829,6 +829,10 @@ stack_effect(int opcode, int oparg, int jump)
case LOAD_METHOD: case LOAD_METHOD:
return 1; return 1;
case LOAD_SUPER_METHOD:
case LOAD_ZERO_SUPER_METHOD:
case LOAD_ZERO_SUPER_ATTR:
return -1;
default: default:
return PY_INVALID_STACK_EFFECT; return PY_INVALID_STACK_EFFECT;
} }
@ -1047,6 +1051,24 @@ compiler_addop_name(struct compiler_unit *u, location loc,
arg <<= 1; arg <<= 1;
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); 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; 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 // If an attribute access spans multiple lines, update the current start
// location to point to the attribute name. // location to point to the attribute name.
static location static location
@ -4297,11 +4402,21 @@ maybe_optimize_method_call(struct compiler *c, expr_ty e)
return 0; return 0;
} }
} }
/* Alright, we can optimize the code. */ /* Alright, we can optimize the code. */
VISIT(c, expr, meth->v.Attribute.value);
location loc = LOC(meth); 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); loc = update_start_location_to_match_attr(c, loc, meth);
ADDOP_NAME(c, loc, LOAD_METHOD, meth->v.Attribute.attr, names); ADDOP_NAME(c, loc, LOAD_METHOD, meth->v.Attribute.attr, names);
}
VISIT_SEQ(c, expr, e->v.Call.args); VISIT_SEQ(c, expr, e->v.Call.args);
if (kwdsl) { if (kwdsl) {
@ -5309,6 +5424,13 @@ compiler_visit_expr1(struct compiler *c, expr_ty e)
return compiler_formatted_value(c, e); return compiler_formatted_value(c, e);
/* The following exprs can be assignment targets. */ /* The following exprs can be assignment targets. */
case Attribute_kind: 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); VISIT(c, expr, e->v.Attribute.value);
loc = LOC(e); loc = LOC(e);
loc = update_start_location_to_match_attr(c, loc, e); loc = update_start_location_to_match_attr(c, loc, e);

File diff suppressed because it is too large Load Diff

View File

@ -205,6 +205,8 @@ _PyOpcode_num_popped(int opcode, int oparg, bool jump) {
return 1; return 1;
case MAP_ADD: case MAP_ADD:
return 2; return 2;
case LOAD_SUPER_ATTR:
return 3;
case LOAD_ATTR: case LOAD_ATTR:
return 1; return 1;
case LOAD_ATTR_INSTANCE_VALUE: case LOAD_ATTR_INSTANCE_VALUE:
@ -589,6 +591,8 @@ _PyOpcode_num_pushed(int opcode, int oparg, bool jump) {
return 0; return 0;
case MAP_ADD: case MAP_ADD:
return 0; return 0;
case LOAD_SUPER_ATTR:
return ((oparg & 1) ? 1 : 0) + 1;
case LOAD_ATTR: case LOAD_ATTR:
return ((oparg & 1) ? 1 : 0) + 1; return ((oparg & 1) ? 1 : 0) + 1;
case LOAD_ATTR_INSTANCE_VALUE: case LOAD_ATTR_INSTANCE_VALUE:
@ -879,6 +883,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[256] = {
[DICT_UPDATE] = { true, INSTR_FMT_IB }, [DICT_UPDATE] = { true, INSTR_FMT_IB },
[DICT_MERGE] = { true, INSTR_FMT_IB }, [DICT_MERGE] = { true, INSTR_FMT_IB },
[MAP_ADD] = { 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] = { true, INSTR_FMT_IBC00000000 },
[LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000 }, [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000 },
[LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000 }, [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000 },

View File

@ -140,9 +140,9 @@ static void *opcode_targets[256] = {
&&TARGET_STORE_DEREF, &&TARGET_STORE_DEREF,
&&TARGET_DELETE_DEREF, &&TARGET_DELETE_DEREF,
&&TARGET_JUMP_BACKWARD, &&TARGET_JUMP_BACKWARD,
&&TARGET_STORE_FAST__STORE_FAST, &&TARGET_LOAD_SUPER_ATTR,
&&TARGET_CALL_FUNCTION_EX, &&TARGET_CALL_FUNCTION_EX,
&&TARGET_STORE_SUBSCR_DICT, &&TARGET_STORE_FAST__STORE_FAST,
&&TARGET_EXTENDED_ARG, &&TARGET_EXTENDED_ARG,
&&TARGET_LIST_APPEND, &&TARGET_LIST_APPEND,
&&TARGET_SET_ADD, &&TARGET_SET_ADD,
@ -152,15 +152,15 @@ static void *opcode_targets[256] = {
&&TARGET_YIELD_VALUE, &&TARGET_YIELD_VALUE,
&&TARGET_RESUME, &&TARGET_RESUME,
&&TARGET_MATCH_CLASS, &&TARGET_MATCH_CLASS,
&&TARGET_STORE_SUBSCR_DICT,
&&TARGET_STORE_SUBSCR_LIST_INT, &&TARGET_STORE_SUBSCR_LIST_INT,
&&TARGET_UNPACK_SEQUENCE_LIST,
&&TARGET_FORMAT_VALUE, &&TARGET_FORMAT_VALUE,
&&TARGET_BUILD_CONST_KEY_MAP, &&TARGET_BUILD_CONST_KEY_MAP,
&&TARGET_BUILD_STRING, &&TARGET_BUILD_STRING,
&&TARGET_UNPACK_SEQUENCE_LIST,
&&TARGET_UNPACK_SEQUENCE_TUPLE, &&TARGET_UNPACK_SEQUENCE_TUPLE,
&&TARGET_UNPACK_SEQUENCE_TWO_TUPLE, &&TARGET_UNPACK_SEQUENCE_TWO_TUPLE,
&&TARGET_SEND_GEN, &&TARGET_SEND_GEN,
&&_unknown_opcode,
&&TARGET_LIST_EXTEND, &&TARGET_LIST_EXTEND,
&&TARGET_SET_UPDATE, &&TARGET_SET_UPDATE,
&&TARGET_DICT_MERGE, &&TARGET_DICT_MERGE,