Skip to content

Commit

Permalink
pythongh-87729: add instruction for faster zero-arg super()
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm committed Apr 13, 2023
1 parent fb38c1b commit 0a0ebe2
Show file tree
Hide file tree
Showing 15 changed files with 652 additions and 398 deletions.
13 changes: 7 additions & 6 deletions Include/internal/pycore_opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 11 additions & 8 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
FOR_ITER = opmap['FOR_ITER']
SEND = opmap['SEND']
LOAD_ATTR = opmap['LOAD_ATTR']
LOAD_ZERO_SUPER_ATTR = opmap['LOAD_ZERO_SUPER_ATTR']

CACHE = opmap["CACHE"]

Expand Down Expand Up @@ -471,7 +472,7 @@ 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 + " + argrepr
elif deop == LOAD_ATTR:
elif deop == LOAD_ATTR or deop == LOAD_ZERO_SUPER_ATTR:
argval, argrepr = _get_name_info(arg//2, get_name)
if (arg & 1) and argrepr:
argrepr = "NULL|self + " + argrepr
Expand Down
5 changes: 3 additions & 2 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ def _write_atomic(path, data, mode=0o666):
# 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

Expand All @@ -456,7 +457,7 @@ def _write_atomic(path, data, mode=0o666):
# 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

Expand Down
3 changes: 2 additions & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def pseudo_op(name, op, real_ops):
def_op('DELETE_DEREF', 139)
hasfree.append(139)
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)

name_op('LOAD_ZERO_SUPER_ATTR', 141)
def_op('CALL_FUNCTION_EX', 142) # Flags

def_op('EXTENDED_ARG', 144)
Expand Down Expand Up @@ -264,6 +264,7 @@ def pseudo_op(name, op, real_ops):
pseudo_op('JUMP_NO_INTERRUPT', 261, ['JUMP_FORWARD', 'JUMP_BACKWARD_NO_INTERRUPT'])

pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR'])
pseudo_op('LOAD_ZERO_SUPER_METHOD', 263, ['LOAD_ZERO_SUPER_ATTR'])

MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1

Expand Down
7 changes: 7 additions & 0 deletions Lib/test/shadowed_super.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class super:
msg = "truly super"


class C:
def method(self):
return super().msg
51 changes: 48 additions & 3 deletions Lib/test/test_super.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -283,17 +285,28 @@ def f(self):
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:
Expand Down Expand Up @@ -325,6 +338,38 @@ def test_super_argtype(self):
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_attribute_error(self):
class C:
def method(self):
return super().msg

with self.assertRaisesRegex(AttributeError, "'super' object has no attribute 'msg'"):
C().method()


if __name__ == "__main__":
unittest.main()
68 changes: 51 additions & 17 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -9358,14 +9358,15 @@ super_repr(PyObject *self)
}

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 *meth_found)
{
superobject *su = (superobject *)self;
PyTypeObject *starttype;
PyObject *mro;
PyObject *mro, *res;
Py_ssize_t i, n;
int temp_su = 0;

starttype = su->obj_type;
starttype = su_obj_type;
if (starttype == NULL)
goto skip;

Expand All @@ -9385,7 +9386,7 @@ 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) */
Expand All @@ -9400,19 +9401,22 @@ 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);

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);
Py_SETREF(res, res2);
if (meth_found && _PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
*meth_found = 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);
Py_SETREF(res, res2);
}
}

Py_DECREF(mro);
Expand All @@ -9428,7 +9432,25 @@ super_getattro(PyObject *self, PyObject *name)
Py_DECREF(mro);

skip:
return PyObject_GenericGetAttr(self, name);
if (su == NULL) {
su = PyObject_Vectorcall((PyObject *)&PySuper_Type, NULL, 0, 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;
return do_super_lookup(su, su->type, su->obj, su->obj_type, name, NULL);
}

static PyTypeObject *
Expand Down Expand Up @@ -9484,6 +9506,18 @@ supercheck(PyTypeObject *type, PyObject *obj)
return NULL;
}

PyObject *
_PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *meth_found)
{
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, meth_found);
Py_DECREF(su_obj_type);
return res;
}

static PyObject *
super_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
Expand Down
Loading

0 comments on commit 0a0ebe2

Please sign in to comment.