Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-43563 : Introduce dedicated opcodes for super calls #24936

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ PyAPI_FUNC(int) _PyObject_LookupAttrId(PyObject *, struct _Py_Identifier *, PyOb

PyAPI_FUNC(int) _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method);

PyAPI_FUNC(PyObject *) _PySuper_Lookup(PyTypeObject *type, PyObject *obj,
PyObject *name, PyObject *super_instance,
int *meth_found);

PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *);
PyAPI_FUNC(PyObject *) _PyObject_NextNotImplemented(PyObject *);
PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *);
Expand Down
2 changes: 2 additions & 0 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/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.10a2 3432 (Function annotation for MAKE_FUNCTION is changed from dict to tuple bpo-42202)
# Python 3.10a2 3433 (RERAISE restores f_lasti if oparg != 0)
# Python 3.10a6 3434 (PEP 634: Structural Pattern Matching)
# Python 3.10a6 3435 (LOAD_*_SUPER opcodes)

#
# MAGIC must change whenever the bytecode emitted by the compiler may no
Expand All @@ -323,7 +324,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 = (3434).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3435).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

_PYCACHE = '__pycache__'
Expand Down
4 changes: 4 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,9 @@ def jabs_op(name, op):
def_op('SET_UPDATE', 163)
def_op('DICT_MERGE', 164)
def_op('DICT_UPDATE', 165)
name_op('LOAD_METHOD_SUPER', 166)
hasconst.append(166)
name_op('LOAD_ATTR_SUPER', 167)
hasconst.append(167)

del def_op, name_op, jrel_op, jabs_op
309 changes: 309 additions & 0 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from test.support import captured_stdout
from test.support.bytecode_helper import BytecodeTestCase
from textwrap import dedent
import unittest
import sys
import dis
Expand Down Expand Up @@ -672,6 +673,314 @@ def check(expected, **kwargs):
check(dis_nested_2)


def test_super_zero_args(self):
src = """
class C:
def f(self): super().f1()
"""
expected = """\
3 0 LOAD_GLOBAL 0 (super)
2 LOAD_DEREF 0 (__class__)
4 LOAD_FAST 0 (self)
6 LOAD_METHOD_SUPER 1 ((1, True))
8 CALL_METHOD 0
10 POP_TOP
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
"""

g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)

def test_super_zero_args_load_attr(self):
src = """
class C:
def f(self): super().f1(a=1)
"""
expected = """\
3 0 LOAD_GLOBAL 0 (super)
2 LOAD_DEREF 0 (__class__)
4 LOAD_FAST 0 (self)
6 LOAD_ATTR_SUPER 1 ((1, True))
8 LOAD_CONST 2 (1)
10 LOAD_CONST 3 (('a',))
12 CALL_FUNCTION_KW 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)

def test_super_two_args(self):
src = """
class C:
def f(self): super(C, self).f1()
"""
expected = """\
3 0 LOAD_GLOBAL 0 (super)
2 LOAD_GLOBAL 1 (C)
4 LOAD_FAST 0 (self)
6 LOAD_METHOD_SUPER 1 ((2, False))
8 CALL_METHOD 0
10 POP_TOP
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)


def test_super_zero_method_args(self):
src = """
class C:
def f(): super().f1()
"""
expected = """\
3 0 LOAD_GLOBAL 0 (super)
2 CALL_FUNCTION 0
4 LOAD_METHOD 1 (f1)
6 CALL_METHOD 0
8 POP_TOP
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)

def test_super_two_args_attr(self):
src = """
class C:
def f(self): super(C, self).f1(a=1)
"""
expected = """\
3 0 LOAD_GLOBAL 0 (super)
2 LOAD_GLOBAL 1 (C)
4 LOAD_FAST 0 (self)
6 LOAD_ATTR_SUPER 1 ((2, False))
8 LOAD_CONST 2 (1)
10 LOAD_CONST 3 (('a',))
12 CALL_FUNCTION_KW 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)

def test_super_attr_load(self):
src = """
class C:
def f(self): return super().x
"""
expected = """\
3 0 LOAD_GLOBAL 0 (super)
2 LOAD_DEREF 0 (__class__)
4 LOAD_FAST 0 (self)
6 LOAD_ATTR_SUPER 1 ((1, True))
8 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)
expected_dis_info = """\
Name: f
Filename: <string>
Argument count: 1
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals: 1
Stack size: 3
Flags: OPTIMIZED, NEWLOCALS
Constants:
0: None
1: (1, True)
Names:
0: super
1: x
Variable names:
0: self
Free variables:
0: __class__"""
self.assertEqual(dedent(dis.code_info(g["C"].f)), expected_dis_info)

def test_super_attr_load_self_cell(self):
src = """
class C:
def f(self):
lambda: self
return super().x
"""
expected = """\
4 0 LOAD_CLOSURE 0 (self)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object <lambda> at 0x..., file "<string>", line 4>)
6 LOAD_CONST 2 ('C.f.<locals>.<lambda>')
8 MAKE_FUNCTION 8 (closure)
10 POP_TOP

5 12 LOAD_GLOBAL 0 (super)
14 LOAD_DEREF 1 (__class__)
16 LOAD_DEREF 0 (self)
18 LOAD_ATTR_SUPER 3 ((1, True))
20 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)

def test_super_attr_store(self):
src = """
class C:
def f(self): super().x = 1
"""
expected = """\
3 0 LOAD_CONST 1 (1)
2 LOAD_GLOBAL 0 (super)
4 CALL_FUNCTION 0
6 STORE_ATTR 1 (x)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)
expected_dis_info = """\
Name: f
Filename: <string>
Argument count: 1
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals: 1
Stack size: 2
Flags: OPTIMIZED, NEWLOCALS
Constants:
0: None
1: 1
Names:
0: super
1: x
Variable names:
0: self
Free variables:
0: __class__"""
self.assertEqual(dedent(dis.code_info(g["C"].f)), expected_dis_info)

def test_super_as_global_1(self):
src = """
super = 1
class C:
def f(self): super().f1(a=1)
"""
expected = """\
4 0 LOAD_GLOBAL 0 (super)
2 CALL_FUNCTION 0
4 LOAD_ATTR 1 (f1)
6 LOAD_CONST 1 (1)
8 LOAD_CONST 2 (('a',))
10 CALL_FUNCTION_KW 1
12 POP_TOP
14 LOAD_CONST 0 (None)
16 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)
expected_dis_info = """\
Name: f
Filename: <string>
Argument count: 1
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals: 1
Stack size: 3
Flags: OPTIMIZED, NEWLOCALS
Constants:
0: None
1: 1
2: ('a',)
Names:
0: super
1: f1
Variable names:
0: self
Free variables:
0: __class__"""
self.assertEqual(dedent(dis.code_info(g["C"].f)), expected_dis_info)


def test_super_as_local_1(self):
src = """
class C:
def f(self, super):
super().f1(a=1)
"""
expected = """\
4 0 LOAD_FAST 1 (super)
2 CALL_FUNCTION 0
4 LOAD_ATTR 0 (f1)
6 LOAD_CONST 1 (1)
8 LOAD_CONST 2 (('a',))
10 CALL_FUNCTION_KW 1
12 POP_TOP
14 LOAD_CONST 0 (None)
16 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)
expected_dis_info = """\
Name: f
Filename: <string>
Argument count: 2
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals: 2
Stack size: 3
Flags: OPTIMIZED, NEWLOCALS
Constants:
0: None
1: 1
2: ('a',)
Names:
0: f1
Variable names:
0: self
1: super
Free variables:
0: __class__"""
self.assertEqual(dedent(dis.code_info(g["C"].f)), expected_dis_info)

def test_super_as_local_2(self):
src = """
def f():
super = lambda: 1
class C:
def f(self):
super().f1(a=1)
return C
C = f()
"""
expected = """\
6 0 LOAD_DEREF 1 (super)
2 CALL_FUNCTION 0
4 LOAD_ATTR 0 (f1)
6 LOAD_CONST 1 (1)
8 LOAD_CONST 2 (('a',))
10 CALL_FUNCTION_KW 1
12 POP_TOP
14 LOAD_CONST 0 (None)
16 RETURN_VALUE
"""
g = {}
exec(dedent(src), g)
self.do_disassembly_test(g["C"].f, expected)

class DisWithFileTests(DisTests):

# Run the tests again, using the file arg instead of print
Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,12 +928,12 @@ def test_wrapper_call(self):
cmd = textwrap.dedent('''
class MyList(list):
def __init__(self):
super().__init__() # wrapper_call()
super().__init__() # wrapperdescr_call()

id("first break point")
l = MyList()
''')
cmds_after_breakpoint = ['break wrapper_call', 'continue']
cmds_after_breakpoint = ['break wrapperdescr_call', 'continue']
if CET_PROTECTION:
# bpo-32962: same case as in get_stack_trace():
# we need an additional 'next' command in order to read
Expand All @@ -945,7 +945,7 @@ def __init__(self):
gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=cmds_after_breakpoint)
self.assertRegex(gdb_output,
r"<method-wrapper u?'__init__' of MyList object at ")
r"methoddescr-wrapper '__init__'>, args=\(<MyList at ")


class PyPrintTests(DebuggerTests):
Expand Down
Loading