From b50dc3f6d06c19e09cde6a3ca7f96bb4e54f148b Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 9 Dec 2021 12:25:13 +0000 Subject: [PATCH 1/9] Add 3 new opcodes for calls: PRECALL_METHOD, CALL_NO_KW, CALL_KW. --- Include/opcode.h | 3 ++ Lib/importlib/_bootstrap_external.py | 3 +- Lib/opcode.py | 4 ++ Lib/test/test_compile.py | 6 +-- Lib/test/test_dis.py | 54 +++++++++---------- Python/ceval.c | 80 +++++++++++++++++++++++++--- Python/compile.c | 29 ++++++---- Python/opcode_targets.h | 6 +-- 8 files changed, 135 insertions(+), 50 deletions(-) diff --git a/Include/opcode.h b/Include/opcode.h index f22f7e94f6190c..53bfc068956098 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -112,6 +112,9 @@ extern "C" { #define DICT_MERGE 164 #define DICT_UPDATE 165 #define CALL_METHOD_KW 166 +#define PRECALL_METHOD 168 +#define CALL_NO_KW 169 +#define CALL_KW 170 #define BINARY_OP_ADAPTIVE 7 #define BINARY_OP_ADD_INT 8 #define BINARY_OP_ADD_FLOAT 13 diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 6970e9f0a94d49..29e5599d8f57fe 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -371,6 +371,7 @@ def _write_atomic(path, data, mode=0o666): # Python 3.11a3 3464 (bpo-45636: Merge numeric BINARY_*/INPLACE_* into # BINARY_OP) # Python 3.11a3 3465 (Add COPY_FREE_VARS opcode) +# Python 3.11a4 3466 (Change CALL_xxx opcodes) # # MAGIC must change whenever the bytecode emitted by the compiler may no @@ -380,7 +381,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 = (3465).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (3466).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c _PYCACHE = '__pycache__' diff --git a/Lib/opcode.py b/Lib/opcode.py index e5889bca4c161c..62652812ee9355 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -193,6 +193,10 @@ def jabs_op(name, op): def_op('DICT_UPDATE', 165) def_op('CALL_METHOD_KW', 166) +def_op('PRECALL_METHOD', 168) +def_op('CALL_NO_KW', 169) +def_op('CALL_KW', 170) + del def_op, name_op, jrel_op, jabs_op _nb_ops = [ diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index f72c7ca68f363a..a3ab3eb00c0f03 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -837,7 +837,7 @@ def foo(x): self.assertNotIn('LOAD_METHOD', instructions) self.assertNotIn('CALL_METHOD', instructions) self.assertIn('LOAD_ATTR', instructions) - self.assertIn('CALL_FUNCTION', instructions) + self.assertIn('CALL_NO_KW', instructions) def test_lineno_procedure_call(self): def call(): @@ -1093,7 +1093,7 @@ def test_multiline_expression(self): ) """ compiled_code, _ = self.check_positions_against_ast(snippet) - self.assertOpcodeSourcePositionIs(compiled_code, 'CALL_FUNCTION', + self.assertOpcodeSourcePositionIs(compiled_code, 'CALL_NO_KW', line=1, end_line=3, column=0, end_column=1) def test_very_long_line_end_offset(self): @@ -1103,7 +1103,7 @@ def test_very_long_line_end_offset(self): snippet = f"g('{long_string}')" compiled_code, _ = self.check_positions_against_ast(snippet) - self.assertOpcodeSourcePositionIs(compiled_code, 'CALL_FUNCTION', + self.assertOpcodeSourcePositionIs(compiled_code, 'CALL_NO_KW', line=1, end_line=1, column=None, end_column=None) def test_complex_single_line_expression(self): diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index dd328f072e45dc..862da42b71a631 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -95,7 +95,7 @@ def _f(a): dis_f = """\ %3d 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (a) - 4 CALL_FUNCTION 1 + 4 CALL_NO_KW 1 6 POP_TOP %3d 8 LOAD_CONST 1 (1) @@ -107,7 +107,7 @@ def _f(a): dis_f_co_code = """\ 0 LOAD_GLOBAL 0 2 LOAD_FAST 0 - 4 CALL_FUNCTION 1 + 4 CALL_NO_KW 1 6 POP_TOP 8 LOAD_CONST 1 10 RETURN_VALUE @@ -125,7 +125,7 @@ def bug708901(): %3d 4 LOAD_CONST 2 (10) -%3d 6 CALL_FUNCTION 2 +%3d 6 CALL_NO_KW 2 8 GET_ITER >> 10 FOR_ITER 2 (to 16) 12 STORE_FAST 0 (res) @@ -152,12 +152,12 @@ def bug1333982(x=[]): 4 MAKE_FUNCTION 0 6 LOAD_FAST 0 (x) 8 GET_ITER - 10 CALL_FUNCTION 1 + 10 CALL_NO_KW 1 %3d 12 LOAD_CONST 3 (1) %3d 14 BINARY_OP 0 (+) - 16 CALL_FUNCTION 1 + 16 CALL_NO_KW 1 18 RAISE_VARARGS 1 """ % (bug1333982.__code__.co_firstlineno + 1, __file__, @@ -260,7 +260,7 @@ def bug42562(): 3 14 LOAD_NAME 3 (fun) 16 LOAD_CONST 0 (1) - 18 CALL_FUNCTION 1 + 18 CALL_NO_KW 1 20 LOAD_NAME 2 (__annotations__) 22 LOAD_CONST 2 ('y') 24 STORE_SUBSCR @@ -269,7 +269,7 @@ def bug42562(): 28 LOAD_NAME 4 (lst) 30 LOAD_NAME 3 (fun) 32 LOAD_CONST 3 (0) - 34 CALL_FUNCTION 1 + 34 CALL_NO_KW 1 36 STORE_SUBSCR 38 LOAD_NAME 1 (int) 40 POP_TOP @@ -384,12 +384,12 @@ def _tryfinallyconst(b): %3d 2 LOAD_FAST 0 (a) %3d 4 LOAD_FAST 1 (b) - 6 CALL_FUNCTION 0 + 6 CALL_NO_KW 0 8 POP_TOP 10 RETURN_VALUE >> 12 PUSH_EXC_INFO 14 LOAD_FAST 1 (b) - 16 CALL_FUNCTION 0 + 16 CALL_NO_KW 0 18 POP_TOP 20 RERAISE 0 >> 22 POP_EXCEPT_AND_RERAISE @@ -407,13 +407,13 @@ def _tryfinallyconst(b): %3d 2 NOP %3d 4 LOAD_FAST 0 (b) - 6 CALL_FUNCTION 0 + 6 CALL_NO_KW 0 8 POP_TOP 10 LOAD_CONST 1 (1) 12 RETURN_VALUE 14 PUSH_EXC_INFO 16 LOAD_FAST 0 (b) - 18 CALL_FUNCTION 0 + 18 CALL_NO_KW 0 20 POP_TOP 22 RERAISE 0 >> 24 POP_EXCEPT_AND_RERAISE @@ -468,7 +468,7 @@ def foo(x): 10 MAKE_FUNCTION 8 (closure) 12 LOAD_DEREF 1 (y) 14 GET_ITER - 16 CALL_FUNCTION 1 + 16 CALL_NO_KW 1 18 RETURN_VALUE """ % (dis_nested_0, __file__, @@ -1004,7 +1004,7 @@ def _prepare_test_cases(): Instruction(opname='BUILD_LIST', opcode=103, arg=0, argval=0, argrepr='', offset=28, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='BUILD_MAP', opcode=105, arg=0, argval=0, argrepr='', offset=30, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Hello world!', argrepr="'Hello world!'", offset=32, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=7, argval=7, argrepr='', offset=34, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=7, argval=7, argrepr='', offset=34, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=36, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='f', argrepr='f', offset=38, starts_line=8, is_jump_target=False, positions=None), Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=40, starts_line=None, is_jump_target=False, positions=None), @@ -1028,7 +1028,7 @@ def _prepare_test_cases(): Instruction(opname='LOAD_DEREF', opcode=137, arg=4, argval='b', argrepr='b', offset=28, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_DEREF', opcode=137, arg=0, argval='c', argrepr='c', offset=30, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_DEREF', opcode=137, arg=1, argval='d', argrepr='d', offset=32, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=4, argval=4, argrepr='', offset=34, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=4, argval=4, argrepr='', offset=34, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=36, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='inner', argrepr='inner', offset=38, starts_line=6, is_jump_target=False, positions=None), Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=40, starts_line=None, is_jump_target=False, positions=None), @@ -1043,7 +1043,7 @@ def _prepare_test_cases(): Instruction(opname='LOAD_DEREF', opcode=137, arg=5, argval='d', argrepr='d', offset=10, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='e', argrepr='e', offset=12, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='f', argrepr='f', offset=14, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=6, argval=6, argrepr='', offset=16, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=6, argval=6, argrepr='', offset=16, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=18, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=20, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=22, starts_line=None, is_jump_target=False, positions=None), @@ -1052,13 +1052,13 @@ def _prepare_test_cases(): expected_opinfo_jumpy = [ Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='range', argrepr='range', offset=0, starts_line=3, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=10, argrepr='10', offset=2, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=4, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=4, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='GET_ITER', opcode=68, arg=None, argval=None, argrepr='', offset=6, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='FOR_ITER', opcode=93, arg=17, argval=44, argrepr='to 44', offset=8, starts_line=None, is_jump_target=True, positions=None), Instruction(opname='STORE_FAST', opcode=125, arg=0, argval='i', argrepr='i', offset=10, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=12, starts_line=4, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=14, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=16, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=16, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=18, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=20, starts_line=5, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=22, starts_line=None, is_jump_target=False, positions=None), @@ -1074,13 +1074,13 @@ def _prepare_test_cases(): Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=4, argval=8, argrepr='to 8', offset=42, starts_line=7, is_jump_target=True, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=44, starts_line=10, is_jump_target=True, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=46, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=48, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=48, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=50, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=52, starts_line=11, is_jump_target=True, positions=None), Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=48, argval=96, argrepr='to 96', offset=54, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=56, starts_line=12, is_jump_target=True, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=58, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=60, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=60, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=62, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=64, starts_line=13, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=66, starts_line=None, is_jump_target=False, positions=None), @@ -1100,7 +1100,7 @@ def _prepare_test_cases(): Instruction(opname='POP_JUMP_IF_TRUE', opcode=115, arg=28, argval=56, argrepr='to 56', offset=94, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=96, starts_line=19, is_jump_target=True, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=98, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=100, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=100, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=102, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=104, starts_line=20, is_jump_target=True, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=106, starts_line=21, is_jump_target=False, positions=None), @@ -1116,7 +1116,7 @@ def _prepare_test_cases(): Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=126, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=128, starts_line=23, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=130, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=132, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=132, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=134, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=136, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='JUMP_FORWARD', opcode=110, arg=33, argval=206, argrepr='to 206', offset=138, starts_line=None, is_jump_target=False, positions=None), @@ -1127,12 +1127,12 @@ def _prepare_test_cases(): Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=148, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=150, starts_line=26, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=9, argval='Never reach this', argrepr="'Never reach this'", offset=152, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=154, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=154, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=156, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=158, starts_line=25, is_jump_target=False, positions=None), Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=160, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=162, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=3, argval=3, argrepr='', offset=164, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=3, argval=3, argrepr='', offset=164, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=166, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='JUMP_FORWARD', opcode=110, arg=25, argval=220, argrepr='to 220', offset=168, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=170, starts_line=None, is_jump_target=False, positions=None), @@ -1149,28 +1149,28 @@ def _prepare_test_cases(): Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=192, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=194, starts_line=28, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=196, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=198, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=198, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=200, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=202, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=204, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=206, starts_line=23, is_jump_target=True, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=208, starts_line=28, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=210, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=212, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=212, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=214, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=216, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=218, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='NOP', opcode=9, arg=None, argval=None, argrepr='', offset=220, starts_line=25, is_jump_target=True, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=222, starts_line=28, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=224, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=226, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=226, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=228, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=230, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=232, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='PUSH_EXC_INFO', opcode=35, arg=None, argval=None, argrepr='', offset=234, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=236, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=238, starts_line=None, is_jump_target=False, positions=None), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=240, starts_line=None, is_jump_target=False, positions=None), + Instruction(opname='CALL_NO_KW', opcode=169, arg=1, argval=1, argrepr='', offset=240, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=242, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=244, starts_line=None, is_jump_target=False, positions=None), Instruction(opname='POP_EXCEPT_AND_RERAISE', opcode=37, arg=None, argval=None, argrepr='', offset=246, starts_line=None, is_jump_target=False, positions=None), diff --git a/Python/ceval.c b/Python/ceval.c index 4f5ccf51e9cfe7..ffe0e5f23da899 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1666,6 +1666,12 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr CFrame cframe; + /* Variables used for making calls */ + PyObject *kwnames; + int nargs; + int postcall_shrink = 1; + int extra_args = 0; + /* WARNING: Because the CFrame lives on the C stack, * but can be accessed from a heap allocated object (tstate) * strict stack discipline must be maintained. @@ -1808,11 +1814,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr switch (opcode) { #endif - /* Variables used for making calls */ - PyObject *kwnames; - int nargs; - int postcall_shrink; - /* BEWARE! It is essential that any operation that fails must goto error and that all operation that succeed call DISPATCH() ! */ @@ -4586,6 +4587,71 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr goto call_function; } + TARGET(PRECALL_METHOD) { + /* Designed to work in tamdem with LOAD_METHOD. */ + /* `meth` is NULL when LOAD_METHOD thinks that it's not + a method call. + + Stack layout: + + ... | NULL | callable | arg1 | ... | argN + ^- TOP() + ^- (-oparg) + ^- (-oparg-1) + ^- (-oparg-2) + + `callable` will be POPed by call_function. + NULL will will be POPed manually later. + If `meth` isn't NULL, it's a method call. Stack layout: + + ... | method | self | arg1 | ... | argN + ^- TOP() + ^- (-oparg) + ^- (-oparg-1) + ^- (-oparg-2) + + `self` and `method` will be POPed by call_function. + We'll be passing `oparg + 1` to call_function, to + make it accept the `self` as a first argument. + */ + int is_method = (PEEK(oparg + 2) != NULL); + extra_args = is_method; + postcall_shrink = 2-is_method; + DISPATCH(); + } + +#define check_call() \ + assert(PEEK(oparg + 1) != NULL); \ + if (extra_args == 0) { \ + if (postcall_shrink == 1) { \ + } else { \ + assert(postcall_shrink == 2); \ + assert(PEEK(oparg + 2) == NULL); \ + } \ + } else { \ + assert(extra_args == 1); \ + assert(postcall_shrink == 1); \ + assert(PEEK(oparg + 2) != NULL); \ + } + + TARGET(CALL_NO_KW) { + kwnames = NULL; + check_call(); + oparg += extra_args; + extra_args = 0; + nargs = oparg; + goto call_function; + } + + TARGET(CALL_KW) { + kwnames = POP(); + check_call(); + oparg += extra_args; + extra_args = 0; + nargs = oparg - (int)PyTuple_GET_SIZE(kwnames); + goto call_function; + } + TARGET(CALL_METHOD_KW) { /* Designed to work in tandem with LOAD_METHOD. Same as CALL_METHOD but pops TOS to get a tuple of keyword names. */ @@ -4638,6 +4704,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr stack_pointer, nargs, kwnames ); STACK_SHRINK(postcall_shrink); + postcall_shrink = 1; // The frame has stolen all the arguments from the stack, // so there is no need to clean them up. Py_XDECREF(kwnames); @@ -4670,6 +4737,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr Py_DECREF(stack_pointer[i]); } STACK_SHRINK(postcall_shrink); + postcall_shrink = 1; PUSH(res); if (res == NULL) { goto error; @@ -7170,7 +7238,7 @@ format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int prevprevop "that does not implement __await__: %.100s", type->tp_name); } - else if (prevopcode == WITH_EXCEPT_START || (prevopcode == CALL_FUNCTION && prevprevopcode == DUP_TOP)) { + else if (prevopcode == WITH_EXCEPT_START || (prevopcode == CALL_NO_KW && prevprevopcode == DUP_TOP)) { _PyErr_Format(tstate, PyExc_TypeError, "'async with' received an object from __aexit__ " "that does not implement __await__: %.100s", diff --git a/Python/compile.c b/Python/compile.c index 6138031833ac93..3f5522577d16c2 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1150,6 +1150,12 @@ stack_effect(int opcode, int oparg, int jump) return -oparg; /* Functions and calls */ + case PRECALL_METHOD: + return -1; + case CALL_NO_KW: + return -oparg; + case CALL_KW: + return -oparg-1; case CALL_FUNCTION: return -oparg; case CALL_METHOD: @@ -1800,7 +1806,7 @@ compiler_call_exit_with_nones(struct compiler *c) { ADDOP_LOAD_CONST(c, Py_None); ADDOP(c, DUP_TOP); ADDOP(c, DUP_TOP); - ADDOP_I(c, CALL_FUNCTION, 3); + ADDOP_I(c, CALL_NO_KW, 3); return 1; } @@ -2464,7 +2470,7 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async) /* decorators */ for (i = 0; i < asdl_seq_LEN(decos); i++) { - ADDOP_I(c, CALL_FUNCTION, 1); + ADDOP_I(c, CALL_NO_KW, 1); } return compiler_nameop(c, name, Store); @@ -2598,7 +2604,7 @@ compiler_class(struct compiler *c, stmt_ty s) /* 6. apply decorators */ for (i = 0; i < asdl_seq_LEN(decos); i++) { - ADDOP_I(c, CALL_FUNCTION, 1); + ADDOP_I(c, CALL_NO_KW, 1); } /* 7. store into */ @@ -3533,7 +3539,7 @@ compiler_assert(struct compiler *c, stmt_ty s) ADDOP(c, LOAD_ASSERTION_ERROR); if (s->v.Assert.msg) { VISIT(c, expr, s->v.Assert.msg); - ADDOP_I(c, CALL_FUNCTION, 1); + ADDOP_I(c, CALL_NO_KW, 1); } ADDOP_I(c, RAISE_VARARGS, 1); compiler_use_next_block(c, end); @@ -4339,10 +4345,12 @@ maybe_optimize_method_call(struct compiler *c, expr_ty e) if (!compiler_call_simple_kw_helper(c, kwds, kwdsl)) { return 0; }; - ADDOP_I(c, CALL_METHOD_KW, argsl + kwdsl); + ADDOP_I(c, PRECALL_METHOD, argsl + kwdsl+1); + ADDOP_I(c, CALL_KW, argsl + kwdsl); } else { - ADDOP_I(c, CALL_METHOD, argsl); + ADDOP_I(c, PRECALL_METHOD, argsl); + ADDOP_I(c, CALL_NO_KW, argsl); } c->u->u_lineno = old_lineno; return 1; @@ -4409,7 +4417,8 @@ compiler_joined_str(struct compiler *c, expr_ty e) VISIT(c, expr, asdl_seq_GET(e->v.JoinedStr.values, i)); ADDOP_I(c, LIST_APPEND, 1); } - ADDOP_I(c, CALL_METHOD, 1); + ADDOP_I(c, PRECALL_METHOD, 1); + ADDOP_I(c, CALL_NO_KW, 1); } else { VISIT_SEQ(c, expr, e->v.JoinedStr.values); @@ -4581,11 +4590,11 @@ compiler_call_helper(struct compiler *c, if (!compiler_call_simple_kw_helper(c, keywords, nkwelts)) { return 0; }; - ADDOP_I(c, CALL_FUNCTION_KW, n + nelts + nkwelts); + ADDOP_I(c, CALL_KW, n + nelts + nkwelts); return 1; } else { - ADDOP_I(c, CALL_FUNCTION, n + nelts); + ADDOP_I(c, CALL_NO_KW, n + nelts); return 1; } @@ -4982,7 +4991,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, ADDOP(c, GET_ITER); } - ADDOP_I(c, CALL_FUNCTION, 1); + ADDOP_I(c, CALL_NO_KW, 1); if (is_async_generator && type != COMP_GENEXP) { ADDOP(c, GET_AWAITABLE); diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index 872a6883119926..c825b82dc8092b 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -167,9 +167,9 @@ static void *opcode_targets[256] = { &&TARGET_DICT_UPDATE, &&TARGET_CALL_METHOD_KW, &&_unknown_opcode, - &&_unknown_opcode, - &&_unknown_opcode, - &&_unknown_opcode, + &&TARGET_PRECALL_METHOD, + &&TARGET_CALL_NO_KW, + &&TARGET_CALL_KW, &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, From ca3ab171daa49b41bde48a45e3594624f971ac35 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 9 Dec 2021 13:35:00 +0000 Subject: [PATCH 2/9] Update specialization to handle new CALL opcodes. --- Include/internal/pycore_code.h | 2 +- Include/opcode.h | 12 +++--- Lib/opcode.py | 12 +++--- Programs/test_frozenmain.h | 8 ++-- Python/ceval.c | 78 +++++++++++++++------------------- Python/opcode_targets.h | 12 +++--- Python/specialize.c | 47 +++++++++++--------- 7 files changed, 84 insertions(+), 87 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 496d52f580f1f3..c723f70284a650 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -271,7 +271,7 @@ int _Py_Specialize_LoadGlobal(PyObject *globals, PyObject *builtins, _Py_CODEUNI int _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache); int _Py_Specialize_BinarySubscr(PyObject *sub, PyObject *container, _Py_CODEUNIT *instr, SpecializedCacheEntry *cache); int _Py_Specialize_StoreSubscr(PyObject *container, PyObject *sub, _Py_CODEUNIT *instr); -int _Py_Specialize_CallFunction(PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins); +int _Py_Specialize_CallNoKw(PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins); void _Py_Specialize_BinaryOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, SpecializedCacheEntry *cache); void _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, SpecializedCacheEntry *cache); diff --git a/Include/opcode.h b/Include/opcode.h index 53bfc068956098..f59cffeba10e0d 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -136,12 +136,12 @@ extern "C" { #define STORE_SUBSCR_ADAPTIVE 36 #define STORE_SUBSCR_LIST_INT 38 #define STORE_SUBSCR_DICT 39 -#define CALL_FUNCTION_ADAPTIVE 40 -#define CALL_FUNCTION_BUILTIN_O 41 -#define CALL_FUNCTION_BUILTIN_FAST 42 -#define CALL_FUNCTION_LEN 43 -#define CALL_FUNCTION_ISINSTANCE 44 -#define CALL_FUNCTION_PY_SIMPLE 45 +#define CALL_NO_KW_ADAPTIVE 40 +#define CALL_NO_KW_BUILTIN_O 41 +#define CALL_NO_KW_BUILTIN_FAST 42 +#define CALL_NO_KW_LEN 43 +#define CALL_NO_KW_ISINSTANCE 44 +#define CALL_NO_KW_PY_SIMPLE 45 #define JUMP_ABSOLUTE_QUICK 46 #define LOAD_ATTR_ADAPTIVE 47 #define LOAD_ATTR_INSTANCE_VALUE 48 diff --git a/Lib/opcode.py b/Lib/opcode.py index 62652812ee9355..e14eeef38b88c4 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -250,12 +250,12 @@ def jabs_op(name, op): "STORE_SUBSCR_ADAPTIVE", "STORE_SUBSCR_LIST_INT", "STORE_SUBSCR_DICT", - "CALL_FUNCTION_ADAPTIVE", - "CALL_FUNCTION_BUILTIN_O", - "CALL_FUNCTION_BUILTIN_FAST", - "CALL_FUNCTION_LEN", - "CALL_FUNCTION_ISINSTANCE", - "CALL_FUNCTION_PY_SIMPLE", + "CALL_NO_KW_ADAPTIVE", + "CALL_NO_KW_BUILTIN_O", + "CALL_NO_KW_BUILTIN_FAST", + "CALL_NO_KW_LEN", + "CALL_NO_KW_ISINSTANCE", + "CALL_NO_KW_PY_SIMPLE", "JUMP_ABSOLUTE_QUICK", "LOAD_ATTR_ADAPTIVE", "LOAD_ATTR_INSTANCE_VALUE", diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index 2c789915bf5035..45ca31e1b5e95e 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -2,11 +2,11 @@ unsigned char M_test_frozenmain[] = { 227,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0, 0,0,0,0,0,115,86,0,0,0,100,0,100,1,108,0, - 90,0,100,0,100,1,108,1,90,1,101,2,100,2,131,1, - 1,0,101,2,100,3,101,0,106,3,131,2,1,0,101,1, - 106,4,131,0,100,4,25,0,90,5,100,5,68,0,93,14, + 90,0,100,0,100,1,108,1,90,1,101,2,100,2,169,1, + 1,0,101,2,100,3,101,0,106,3,169,2,1,0,101,1, + 106,4,169,0,100,4,25,0,90,5,100,5,68,0,93,14, 90,6,101,2,100,6,101,6,155,0,100,7,101,5,101,6, - 25,0,155,0,157,4,131,1,1,0,113,26,100,1,83,0, + 25,0,155,0,157,4,169,1,1,0,113,26,100,1,83,0, 41,8,233,0,0,0,0,78,122,18,70,114,111,122,101,110, 32,72,101,108,108,111,32,87,111,114,108,100,122,8,115,121, 115,46,97,114,103,118,218,6,99,111,110,102,105,103,41,5, diff --git a/Python/ceval.c b/Python/ceval.c index ffe0e5f23da899..85a1bef0d72acb 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4620,23 +4620,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } -#define check_call() \ - assert(PEEK(oparg + 1) != NULL); \ - if (extra_args == 0) { \ - if (postcall_shrink == 1) { \ - } else { \ - assert(postcall_shrink == 2); \ - assert(PEEK(oparg + 2) == NULL); \ - } \ - } else { \ - assert(extra_args == 1); \ - assert(postcall_shrink == 1); \ - assert(PEEK(oparg + 2) != NULL); \ - } - TARGET(CALL_NO_KW) { + PREDICTED(CALL_NO_KW); kwnames = NULL; - check_call(); oparg += extra_args; extra_args = 0; nargs = oparg; @@ -4645,7 +4631,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr TARGET(CALL_KW) { kwnames = POP(); - check_call(); oparg += extra_args; extra_args = 0; nargs = oparg - (int)PyTuple_GET_SIZE(kwnames); @@ -4746,43 +4731,47 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } - TARGET(CALL_FUNCTION_ADAPTIVE) { + TARGET(CALL_NO_KW_ADAPTIVE) { SpecializedCacheEntry *cache = GET_CACHE(); - nargs = cache->adaptive.original_oparg; + oparg = cache->adaptive.original_oparg; if (cache->adaptive.counter == 0) { next_instr--; - if (_Py_Specialize_CallFunction( + int nargs = oparg+extra_args; + if (_Py_Specialize_CallNoKw( PEEK(nargs + 1), next_instr, nargs, cache, BUILTINS()) < 0) { goto error; } DISPATCH(); } else { - STAT_INC(CALL_FUNCTION, deferred); + STAT_INC(CALL_NO_KW, deferred); cache->adaptive.counter--; - oparg = nargs; kwnames = NULL; - postcall_shrink = 1; + oparg += extra_args; + extra_args = 0; + nargs = oparg; goto call_function; } } - TARGET(CALL_FUNCTION_PY_SIMPLE) { + TARGET(CALL_NO_KW_PY_SIMPLE) { SpecializedCacheEntry *caches = GET_CACHE(); _PyAdaptiveEntry *cache0 = &caches[0].adaptive; - int argcount = cache0->original_oparg; + int argcount = cache0->original_oparg + extra_args; _PyCallCache *cache1 = &caches[-1].call; PyObject *callable = PEEK(argcount+1); - DEOPT_IF(!PyFunction_Check(callable), CALL_FUNCTION); + DEOPT_IF(!PyFunction_Check(callable), CALL_NO_KW); PyFunctionObject *func = (PyFunctionObject *)callable; - DEOPT_IF(func->func_version != cache1->func_version, CALL_FUNCTION); + DEOPT_IF(func->func_version != cache1->func_version, CALL_NO_KW); /* PEP 523 */ - DEOPT_IF(tstate->interp->eval_frame != NULL, CALL_FUNCTION); - STAT_INC(CALL_FUNCTION, hit); + DEOPT_IF(tstate->interp->eval_frame != NULL, CALL_NO_KW); + STAT_INC(CALL_NO_KW, hit); + extra_args = 0; PyCodeObject *code = (PyCodeObject *)func->func_code; size_t size = code->co_nlocalsplus + code->co_stacksize + FRAME_SPECIALS_SIZE; InterpreterFrame *new_frame = _PyThreadState_BumpFramePointer(tstate, size); if (new_frame == NULL) { + postcall_shrink = 1; goto error; } _PyFrame_InitializeSpecials(new_frame, func, @@ -4800,7 +4789,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr for (int i = argcount+deflen; i < code->co_nlocalsplus; i++) { new_frame->localsplus[i] = NULL; } - STACK_SHRINK(1); + STACK_SHRINK(postcall_shrink); + postcall_shrink = 1; Py_DECREF(func); _PyFrame_SetStackPointer(frame, stack_pointer); new_frame->previous = frame; @@ -4809,14 +4799,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr goto start_frame; } - TARGET(CALL_FUNCTION_BUILTIN_O) { + TARGET(CALL_NO_KW_BUILTIN_O) { assert(cframe.use_tracing == 0); /* Builtin METH_O functions */ PyObject *callable = SECOND(); - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_FUNCTION); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL_FUNCTION); - STAT_INC(CALL_FUNCTION, hit); + DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_NO_KW); + DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL_NO_KW); + STAT_INC(CALL_NO_KW, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); // This is slower but CPython promises to check all non-vectorcall @@ -4839,7 +4829,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } - TARGET(CALL_FUNCTION_BUILTIN_FAST) { + TARGET(CALL_NO_KW_BUILTIN_FAST) { assert(cframe.use_tracing == 0); /* Builtin METH_FASTCALL functions, without keywords */ SpecializedCacheEntry *caches = GET_CACHE(); @@ -4847,10 +4837,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr int nargs = cache0->original_oparg; PyObject **pfunc = &PEEK(nargs + 1); PyObject *callable = *pfunc; - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_FUNCTION); + DEOPT_IF(!PyCFunction_CheckExact(callable), CALL_NO_KW); DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_FASTCALL, - CALL_FUNCTION); - STAT_INC(CALL_FUNCTION, hit); + CALL_NO_KW); + STAT_INC(CALL_NO_KW, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); /* res = func(self, args, nargs) */ @@ -4877,7 +4867,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } - TARGET(CALL_FUNCTION_LEN) { + TARGET(CALL_NO_KW_LEN) { assert(cframe.use_tracing == 0); /* len(o) */ SpecializedCacheEntry *caches = GET_CACHE(); @@ -4885,8 +4875,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr _PyObjectCache *cache1 = &caches[-1].obj; PyObject *callable = SECOND(); - DEOPT_IF(callable != cache1->obj, CALL_FUNCTION); - STAT_INC(CALL_FUNCTION, hit); + DEOPT_IF(callable != cache1->obj, CALL_NO_KW); + STAT_INC(CALL_NO_KW, hit); Py_ssize_t len_i = PyObject_Length(TOP()); if (len_i < 0) { @@ -4905,7 +4895,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } - TARGET(CALL_FUNCTION_ISINSTANCE) { + TARGET(CALL_NO_KW_ISINSTANCE) { assert(cframe.use_tracing == 0); /* isinstance(o, o2) */ SpecializedCacheEntry *caches = GET_CACHE(); @@ -4913,8 +4903,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr _PyObjectCache *cache1 = &caches[-1].obj; PyObject *callable = THIRD(); - DEOPT_IF(callable != cache1->obj, CALL_FUNCTION); - STAT_INC(CALL_FUNCTION, hit); + DEOPT_IF(callable != cache1->obj, CALL_NO_KW); + STAT_INC(CALL_NO_KW, hit); int retval = PyObject_IsInstance(SECOND(), TOP()); if (retval < 0) { @@ -5247,7 +5237,7 @@ MISS_WITH_CACHE(LOAD_ATTR) MISS_WITH_CACHE(STORE_ATTR) MISS_WITH_CACHE(LOAD_GLOBAL) MISS_WITH_CACHE(LOAD_METHOD) -MISS_WITH_CACHE(CALL_FUNCTION) +MISS_WITH_CACHE(CALL_NO_KW) MISS_WITH_CACHE(BINARY_OP) MISS_WITH_CACHE(COMPARE_OP) MISS_WITH_CACHE(BINARY_SUBSCR) diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index c825b82dc8092b..ad661f99692c6c 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -39,12 +39,12 @@ static void *opcode_targets[256] = { &&TARGET_POP_EXCEPT_AND_RERAISE, &&TARGET_STORE_SUBSCR_LIST_INT, &&TARGET_STORE_SUBSCR_DICT, - &&TARGET_CALL_FUNCTION_ADAPTIVE, - &&TARGET_CALL_FUNCTION_BUILTIN_O, - &&TARGET_CALL_FUNCTION_BUILTIN_FAST, - &&TARGET_CALL_FUNCTION_LEN, - &&TARGET_CALL_FUNCTION_ISINSTANCE, - &&TARGET_CALL_FUNCTION_PY_SIMPLE, + &&TARGET_CALL_NO_KW_ADAPTIVE, + &&TARGET_CALL_NO_KW_BUILTIN_O, + &&TARGET_CALL_NO_KW_BUILTIN_FAST, + &&TARGET_CALL_NO_KW_LEN, + &&TARGET_CALL_NO_KW_ISINSTANCE, + &&TARGET_CALL_NO_KW_PY_SIMPLE, &&TARGET_JUMP_ABSOLUTE_QUICK, &&TARGET_LOAD_ATTR_ADAPTIVE, &&TARGET_LOAD_ATTR_INSTANCE_VALUE, diff --git a/Python/specialize.c b/Python/specialize.c index cdc535396fa762..59bd96eee3596f 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -127,7 +127,7 @@ _Py_GetSpecializationStats(void) { err += add_stat_dict(stats, BINARY_SUBSCR, "binary_subscr"); err += add_stat_dict(stats, STORE_SUBSCR, "store_subscr"); err += add_stat_dict(stats, STORE_ATTR, "store_attr"); - err += add_stat_dict(stats, CALL_FUNCTION, "call_function"); + err += add_stat_dict(stats, CALL_NO_KW, "call_no_kw"); err += add_stat_dict(stats, BINARY_OP, "binary_op"); err += add_stat_dict(stats, COMPARE_OP, "compare_op"); if (err < 0) { @@ -186,7 +186,7 @@ _Py_PrintSpecializationStats(void) print_stats(out, &_specialization_stats[BINARY_SUBSCR], "binary_subscr"); print_stats(out, &_specialization_stats[STORE_SUBSCR], "store_subscr"); print_stats(out, &_specialization_stats[STORE_ATTR], "store_attr"); - print_stats(out, &_specialization_stats[CALL_FUNCTION], "call_function"); + print_stats(out, &_specialization_stats[CALL_NO_KW], "call_no_kw"); print_stats(out, &_specialization_stats[BINARY_OP], "binary_op"); print_stats(out, &_specialization_stats[COMPARE_OP], "compare_op"); if (out != stderr) { @@ -238,7 +238,7 @@ static uint8_t adaptive_opcodes[256] = { [LOAD_METHOD] = LOAD_METHOD_ADAPTIVE, [BINARY_SUBSCR] = BINARY_SUBSCR_ADAPTIVE, [STORE_SUBSCR] = STORE_SUBSCR_ADAPTIVE, - [CALL_FUNCTION] = CALL_FUNCTION_ADAPTIVE, + [CALL_NO_KW] = CALL_NO_KW_ADAPTIVE, [STORE_ATTR] = STORE_ATTR_ADAPTIVE, [BINARY_OP] = BINARY_OP_ADAPTIVE, [COMPARE_OP] = COMPARE_OP_ADAPTIVE, @@ -251,7 +251,7 @@ static uint8_t cache_requirements[256] = { [LOAD_METHOD] = 3, /* _PyAdaptiveEntry, _PyAttrCache and _PyObjectCache */ [BINARY_SUBSCR] = 2, /* _PyAdaptiveEntry, _PyObjectCache */ [STORE_SUBSCR] = 0, - [CALL_FUNCTION] = 2, /* _PyAdaptiveEntry and _PyObjectCache/_PyCallCache */ + [CALL_NO_KW] = 2, /* _PyAdaptiveEntry and _PyObjectCache/_PyCallCache */ [STORE_ATTR] = 2, /* _PyAdaptiveEntry and _PyAttrCache */ [BINARY_OP] = 1, // _PyAdaptiveEntry [COMPARE_OP] = 1, /* _PyAdaptiveEntry */ @@ -491,6 +491,7 @@ initial_counter_value(void) { #define SPEC_FAIL_PYCFUNCTION_NOARGS 16 #define SPEC_FAIL_BAD_CALL_FLAGS 17 #define SPEC_FAIL_CLASS 18 +#define SPEC_FAIL_C_METHOD_CALL 19 /* COMPARE_OP */ #define SPEC_FAIL_STRING_COMPARE 13 @@ -1261,7 +1262,7 @@ specialize_class_call( PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache) { - SPECIALIZATION_FAIL(CALL_FUNCTION, SPEC_FAIL_CLASS); + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_CLASS); return -1; } @@ -1274,7 +1275,7 @@ specialize_py_call( PyCodeObject *code = (PyCodeObject *)func->func_code; int kind = function_kind(code); if (kind != SIMPLE_FUNCTION) { - SPECIALIZATION_FAIL(CALL_FUNCTION, kind); + SPECIALIZATION_FAIL(CALL_NO_KW, kind); return -1; } int argcount = code->co_argcount; @@ -1282,7 +1283,7 @@ specialize_py_call( assert(defcount <= argcount); int min_args = argcount-defcount; if (nargs > argcount || nargs < min_args) { - SPECIALIZATION_FAIL(CALL_FUNCTION, SPEC_FAIL_WRONG_NUMBER_ARGUMENTS); + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_WRONG_NUMBER_ARGUMENTS); return -1; } assert(nargs <= argcount && nargs >= min_args); @@ -1291,18 +1292,18 @@ specialize_py_call( assert(defstart >= 0 && deflen >= 0); assert(deflen == 0 || func->func_defaults != NULL); if (defstart > 0xffff || deflen > 0xffff) { - SPECIALIZATION_FAIL(CALL_FUNCTION, SPEC_FAIL_OUT_OF_RANGE); + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OUT_OF_RANGE); return -1; } int version = _PyFunction_GetVersionForCurrentState(func); if (version == 0) { - SPECIALIZATION_FAIL(CALL_FUNCTION, SPEC_FAIL_OUT_OF_VERSIONS); + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OUT_OF_VERSIONS); return -1; } cache1->func_version = version; cache1->defaults_start = defstart; cache1->defaults_len = deflen; - *instr = _Py_MAKECODEUNIT(CALL_FUNCTION_PY_SIMPLE, _Py_OPARG(*instr)); + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_PY_SIMPLE, _Py_OPARG(*instr)); return 0; } @@ -1335,6 +1336,10 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins) { _PyObjectCache *cache1 = &cache[-1].obj; + if (_Py_OPCODE(instr[-1]) == PRECALL_METHOD) { + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_C_METHOD_CALL); + return -1; + } if (PyCFunction_GET_FUNCTION(callable) == NULL) { return 1; } @@ -1343,18 +1348,18 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs, METH_KEYWORDS | METH_METHOD)) { case METH_O: { if (nargs != 1) { - SPECIALIZATION_FAIL(CALL_FUNCTION, SPEC_FAIL_OUT_OF_RANGE); + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OUT_OF_RANGE); return 1; } /* len(o) */ PyObject *builtin_len = PyDict_GetItemString(builtins, "len"); if (callable == builtin_len) { cache1->obj = builtin_len; // borrowed - *instr = _Py_MAKECODEUNIT(CALL_FUNCTION_LEN, + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_LEN, _Py_OPARG(*instr)); return 0; } - *instr = _Py_MAKECODEUNIT(CALL_FUNCTION_BUILTIN_O, + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_BUILTIN_O, _Py_OPARG(*instr)); return 0; } @@ -1365,17 +1370,17 @@ specialize_c_call(PyObject *callable, _Py_CODEUNIT *instr, int nargs, builtins, "isinstance"); if (callable == builtin_isinstance) { cache1->obj = builtin_isinstance; // borrowed - *instr = _Py_MAKECODEUNIT(CALL_FUNCTION_ISINSTANCE, + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_ISINSTANCE, _Py_OPARG(*instr)); return 0; } } - *instr = _Py_MAKECODEUNIT(CALL_FUNCTION_BUILTIN_FAST, + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_BUILTIN_FAST, _Py_OPARG(*instr)); return 0; } default: - SPECIALIZATION_FAIL(CALL_FUNCTION, + SPECIALIZATION_FAIL(CALL_NO_KW, builtin_call_fail_kind(PyCFunction_GET_FLAGS(callable))); return 1; } @@ -1406,12 +1411,14 @@ call_fail_kind(PyObject *callable) - Specialize calling classes. */ int -_Py_Specialize_CallFunction( +_Py_Specialize_CallNoKw( PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins) { int fail; + + if (PyCFunction_CheckExact(callable)) { fail = specialize_c_call(callable, instr, nargs, cache, builtins); } @@ -1422,17 +1429,17 @@ _Py_Specialize_CallFunction( fail = specialize_class_call(callable, instr, nargs, cache); } else { - SPECIALIZATION_FAIL(CALL_FUNCTION, call_fail_kind(callable)); + SPECIALIZATION_FAIL(CALL_NO_KW, call_fail_kind(callable)); fail = -1; } _PyAdaptiveEntry *cache0 = &cache->adaptive; if (fail) { - STAT_INC(CALL_FUNCTION, specialization_failure); + STAT_INC(CALL_NO_KW, specialization_failure); assert(!PyErr_Occurred()); cache_backoff(cache0); } else { - STAT_INC(CALL_FUNCTION, specialization_success); + STAT_INC(CALL_NO_KW, specialization_success); assert(!PyErr_Occurred()); cache0->counter = initial_counter_value(); } From 140a2142839ff7f5858a909e20ca652867189163 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 9 Dec 2021 16:21:56 +0000 Subject: [PATCH 3/9] Specialize call to method descriptors. --- Include/internal/pycore_code.h | 2 +- Include/opcode.h | 49 +++++++++--------- Lib/opcode.py | 3 ++ Python/ceval.c | 92 ++++++++++++++++++++++++++++++++++ Python/opcode_targets.h | 32 ++++++------ Python/specialize.c | 49 ++++++++++++++++++ 6 files changed, 187 insertions(+), 40 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index c723f70284a650..b0463e361718a5 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -288,7 +288,7 @@ void _Py_Specialize_CompareOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr, #define COLLECT_SPECIALIZATION_STATS_DETAILED PRINT_SPECIALIZATION_STATS_DETAILED #endif -#define SPECIALIZATION_FAILURE_KINDS 20 +#define SPECIALIZATION_FAILURE_KINDS 30 #if COLLECT_SPECIALIZATION_STATS diff --git a/Include/opcode.h b/Include/opcode.h index f59cffeba10e0d..45f57ec3b1cb41 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -142,29 +142,32 @@ extern "C" { #define CALL_NO_KW_LEN 43 #define CALL_NO_KW_ISINSTANCE 44 #define CALL_NO_KW_PY_SIMPLE 45 -#define JUMP_ABSOLUTE_QUICK 46 -#define LOAD_ATTR_ADAPTIVE 47 -#define LOAD_ATTR_INSTANCE_VALUE 48 -#define LOAD_ATTR_WITH_HINT 55 -#define LOAD_ATTR_SLOT 56 -#define LOAD_ATTR_MODULE 57 -#define LOAD_GLOBAL_ADAPTIVE 58 -#define LOAD_GLOBAL_MODULE 59 -#define LOAD_GLOBAL_BUILTIN 62 -#define LOAD_METHOD_ADAPTIVE 63 -#define LOAD_METHOD_CACHED 64 -#define LOAD_METHOD_CLASS 65 -#define LOAD_METHOD_MODULE 66 -#define LOAD_METHOD_NO_DICT 67 -#define STORE_ATTR_ADAPTIVE 75 -#define STORE_ATTR_INSTANCE_VALUE 76 -#define STORE_ATTR_SLOT 77 -#define STORE_ATTR_WITH_HINT 78 -#define LOAD_FAST__LOAD_FAST 79 -#define STORE_FAST__LOAD_FAST 80 -#define LOAD_FAST__LOAD_CONST 81 -#define LOAD_CONST__LOAD_FAST 87 -#define STORE_FAST__STORE_FAST 88 +#define CALL_NO_KW_LIST_APPEND 46 +#define CALL_NO_KW_METHOD_DESCRIPTOR_O 47 +#define CALL_NO_KW_METHOD_DESCRIPTOR_FAST 48 +#define JUMP_ABSOLUTE_QUICK 55 +#define LOAD_ATTR_ADAPTIVE 56 +#define LOAD_ATTR_INSTANCE_VALUE 57 +#define LOAD_ATTR_WITH_HINT 58 +#define LOAD_ATTR_SLOT 59 +#define LOAD_ATTR_MODULE 62 +#define LOAD_GLOBAL_ADAPTIVE 63 +#define LOAD_GLOBAL_MODULE 64 +#define LOAD_GLOBAL_BUILTIN 65 +#define LOAD_METHOD_ADAPTIVE 66 +#define LOAD_METHOD_CACHED 67 +#define LOAD_METHOD_CLASS 75 +#define LOAD_METHOD_MODULE 76 +#define LOAD_METHOD_NO_DICT 77 +#define STORE_ATTR_ADAPTIVE 78 +#define STORE_ATTR_INSTANCE_VALUE 79 +#define STORE_ATTR_SLOT 80 +#define STORE_ATTR_WITH_HINT 81 +#define LOAD_FAST__LOAD_FAST 87 +#define STORE_FAST__LOAD_FAST 88 +#define LOAD_FAST__LOAD_CONST 123 +#define LOAD_CONST__LOAD_FAST 127 +#define STORE_FAST__STORE_FAST 128 #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 e14eeef38b88c4..b41dd1d33338d0 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -256,6 +256,9 @@ def jabs_op(name, op): "CALL_NO_KW_LEN", "CALL_NO_KW_ISINSTANCE", "CALL_NO_KW_PY_SIMPLE", + "CALL_NO_KW_LIST_APPEND", + "CALL_NO_KW_METHOD_DESCRIPTOR_O", + "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", "JUMP_ABSOLUTE_QUICK", "LOAD_ATTR_ADAPTIVE", "LOAD_ATTR_INSTANCE_VALUE", diff --git a/Python/ceval.c b/Python/ceval.c index 85a1bef0d72acb..f9abe48e11ad38 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4924,6 +4924,98 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } + TARGET(CALL_NO_KW_LIST_APPEND) { + assert(_Py_OPCODE(next_instr[-2]) == PRECALL_METHOD); + assert(GET_CACHE()->adaptive.original_oparg == 1); + DEOPT_IF(extra_args == 0, CALL_NO_KW); + PyObject *list = SECOND(); + DEOPT_IF(!PyList_CheckExact(list), CALL_NO_KW); + STAT_INC(CALL_NO_KW, hit); + assert(extra_args == 1); + extra_args = 0; + assert(postcall_shrink == 1); + PyObject *arg = TOP(); + int err = PyList_Append(list, arg); + if (err) { + goto error; + } + PyObject *callable = THIRD(); + Py_DECREF(arg); + Py_DECREF(list); + Py_INCREF(Py_None); + STACK_SHRINK(2); + SET_TOP(Py_None); + Py_DECREF(callable); + DISPATCH(); + } + + TARGET(CALL_NO_KW_METHOD_DESCRIPTOR_O) { + assert(_Py_OPCODE(next_instr[-2]) == PRECALL_METHOD); + SpecializedCacheEntry *caches = GET_CACHE(); + assert(caches[0].adaptive.original_oparg == 1); + DEOPT_IF(extra_args == 0, CALL_NO_KW); + assert(extra_args == 1); + PyObject *callable = THIRD(); + DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), CALL_NO_KW); + DEOPT_IF(((PyMethodDescrObject *)callable)->d_method->ml_flags != METH_O, CALL_NO_KW); + assert(postcall_shrink == 1); + STAT_INC(CALL_NO_KW, hit); + assert(extra_args == 1); + extra_args = 0; + PyCFunction cfunc = ((PyMethodDescrObject *)callable)->d_method->ml_meth; + // This is slower but CPython promises to check all non-vectorcall + // function calls. + if (_Py_EnterRecursiveCall(tstate, " while calling a Python object")) { + goto error; + } + PyObject *arg = POP(); + PyObject *self = POP(); + PyObject *res = cfunc(self, arg); + _Py_LeaveRecursiveCall(tstate); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + Py_DECREF(self); + Py_DECREF(arg); + SET_TOP(res); + Py_DECREF(callable); + if (res == NULL) { + goto error; + } + DISPATCH(); + } + + TARGET(CALL_NO_KW_METHOD_DESCRIPTOR_FAST) { + assert(_Py_OPCODE(next_instr[-2]) == PRECALL_METHOD); + /* Builtin METH_FASTCALL methods, without keywords */ + SpecializedCacheEntry *caches = GET_CACHE(); + _PyAdaptiveEntry *cache0 = &caches[0].adaptive; + DEOPT_IF(extra_args == 0, CALL_NO_KW); + assert(extra_args == 1); + int nargs = cache0->original_oparg; + PyObject *callable = PEEK(nargs + 2); + DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), CALL_NO_KW); + PyMethodDef *meth = ((PyMethodDescrObject *)callable)->d_method; + DEOPT_IF(meth->ml_flags != METH_FASTCALL, CALL_NO_KW); + assert(postcall_shrink == 1); + STAT_INC(CALL_NO_KW, hit); + assert(extra_args == 1); + extra_args = 0; + _PyCFunctionFast cfunc = (_PyCFunctionFast)(void(*)(void))meth->ml_meth; + PyObject *self = PEEK(nargs+1); + PyObject *res = cfunc(self, &PEEK(nargs), nargs); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + /* Clear the stack of the arguments. */ + STACK_SHRINK(nargs+1); + for (int i = 0; i <= nargs; i++) { + Py_DECREF(stack_pointer[i]); + } + SET_TOP(res); + Py_DECREF(callable); + if (res == NULL) { + goto error; + } + DISPATCH(); + } + TARGET(CALL_FUNCTION_EX) { PREDICTED(CALL_FUNCTION_EX); PyObject *func, *callargs, *kwargs = NULL, *result; diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index ad661f99692c6c..286053d4411c56 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -45,28 +45,28 @@ static void *opcode_targets[256] = { &&TARGET_CALL_NO_KW_LEN, &&TARGET_CALL_NO_KW_ISINSTANCE, &&TARGET_CALL_NO_KW_PY_SIMPLE, - &&TARGET_JUMP_ABSOLUTE_QUICK, - &&TARGET_LOAD_ATTR_ADAPTIVE, - &&TARGET_LOAD_ATTR_INSTANCE_VALUE, + &&TARGET_CALL_NO_KW_LIST_APPEND, + &&TARGET_CALL_NO_KW_METHOD_DESCRIPTOR_O, + &&TARGET_CALL_NO_KW_METHOD_DESCRIPTOR_FAST, &&TARGET_WITH_EXCEPT_START, &&TARGET_GET_AITER, &&TARGET_GET_ANEXT, &&TARGET_BEFORE_ASYNC_WITH, &&TARGET_BEFORE_WITH, &&TARGET_END_ASYNC_FOR, + &&TARGET_JUMP_ABSOLUTE_QUICK, + &&TARGET_LOAD_ATTR_ADAPTIVE, + &&TARGET_LOAD_ATTR_INSTANCE_VALUE, &&TARGET_LOAD_ATTR_WITH_HINT, &&TARGET_LOAD_ATTR_SLOT, + &&TARGET_STORE_SUBSCR, + &&TARGET_DELETE_SUBSCR, &&TARGET_LOAD_ATTR_MODULE, &&TARGET_LOAD_GLOBAL_ADAPTIVE, &&TARGET_LOAD_GLOBAL_MODULE, - &&TARGET_STORE_SUBSCR, - &&TARGET_DELETE_SUBSCR, &&TARGET_LOAD_GLOBAL_BUILTIN, &&TARGET_LOAD_METHOD_ADAPTIVE, &&TARGET_LOAD_METHOD_CACHED, - &&TARGET_LOAD_METHOD_CLASS, - &&TARGET_LOAD_METHOD_MODULE, - &&TARGET_LOAD_METHOD_NO_DICT, &&TARGET_GET_ITER, &&TARGET_GET_YIELD_FROM_ITER, &&TARGET_PRINT_EXPR, @@ -74,20 +74,20 @@ static void *opcode_targets[256] = { &&TARGET_YIELD_FROM, &&TARGET_GET_AWAITABLE, &&TARGET_LOAD_ASSERTION_ERROR, + &&TARGET_LOAD_METHOD_CLASS, + &&TARGET_LOAD_METHOD_MODULE, + &&TARGET_LOAD_METHOD_NO_DICT, &&TARGET_STORE_ATTR_ADAPTIVE, &&TARGET_STORE_ATTR_INSTANCE_VALUE, &&TARGET_STORE_ATTR_SLOT, &&TARGET_STORE_ATTR_WITH_HINT, - &&TARGET_LOAD_FAST__LOAD_FAST, - &&TARGET_STORE_FAST__LOAD_FAST, - &&TARGET_LOAD_FAST__LOAD_CONST, &&TARGET_LIST_TO_TUPLE, &&TARGET_RETURN_VALUE, &&TARGET_IMPORT_STAR, &&TARGET_SETUP_ANNOTATIONS, &&TARGET_YIELD_VALUE, - &&TARGET_LOAD_CONST__LOAD_FAST, - &&TARGET_STORE_FAST__STORE_FAST, + &&TARGET_LOAD_FAST__LOAD_FAST, + &&TARGET_STORE_FAST__LOAD_FAST, &&TARGET_POP_EXCEPT, &&TARGET_STORE_NAME, &&TARGET_DELETE_NAME, @@ -122,12 +122,12 @@ static void *opcode_targets[256] = { &&TARGET_COPY, &&TARGET_JUMP_IF_NOT_EXC_MATCH, &&TARGET_BINARY_OP, - &&_unknown_opcode, + &&TARGET_LOAD_FAST__LOAD_CONST, &&TARGET_LOAD_FAST, &&TARGET_STORE_FAST, &&TARGET_DELETE_FAST, - &&_unknown_opcode, - &&_unknown_opcode, + &&TARGET_LOAD_CONST__LOAD_FAST, + &&TARGET_STORE_FAST__STORE_FAST, &&TARGET_GEN_START, &&TARGET_RAISE_VARARGS, &&TARGET_CALL_FUNCTION, diff --git a/Python/specialize.c b/Python/specialize.c index 59bd96eee3596f..4d95de569d5686 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -492,6 +492,7 @@ initial_counter_value(void) { #define SPEC_FAIL_BAD_CALL_FLAGS 17 #define SPEC_FAIL_CLASS 18 #define SPEC_FAIL_C_METHOD_CALL 19 +#define SPEC_FAIL_METHDESCR_NON_METHOD 20 /* COMPARE_OP */ #define SPEC_FAIL_STRING_COMPARE 13 @@ -1266,6 +1267,50 @@ specialize_class_call( return -1; } +static PyMethodDescrObject *_list_append = NULL; +_Py_IDENTIFIER(append); + +static int +specialize_method_descriptor( + PyMethodDescrObject *descr, _Py_CODEUNIT *instr, + int nargs, SpecializedCacheEntry *cache) +{ + int oparg = cache->adaptive.original_oparg; + if (nargs - oparg != 1) { + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_METHDESCR_NON_METHOD); + return -1; + } + if (_list_append == NULL) { + _list_append = (PyMethodDescrObject *)_PyType_LookupId(&PyList_Type, &PyId_append); + } + if (oparg == 1 && descr == _list_append) { + assert(_Py_OPCODE(instr[-1]) == PRECALL_METHOD); + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_LIST_APPEND, _Py_OPARG(*instr)); + return 0; + } + + switch (descr->d_method->ml_flags & + (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | + METH_KEYWORDS | METH_METHOD)) { + case METH_O: { + if (oparg != 1) { + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OUT_OF_RANGE); + return 1; + } + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_METHOD_DESCRIPTOR_O, + _Py_OPARG(*instr)); + return 0; + } + case METH_FASTCALL: { + *instr = _Py_MAKECODEUNIT(CALL_NO_KW_METHOD_DESCRIPTOR_FAST, + _Py_OPARG(*instr)); + return 0; + } + } + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OTHER); + return -1; +} + static int specialize_py_call( PyFunctionObject *func, _Py_CODEUNIT *instr, @@ -1428,6 +1473,10 @@ _Py_Specialize_CallNoKw( else if (PyType_Check(callable)) { fail = specialize_class_call(callable, instr, nargs, cache); } + else if (Py_IS_TYPE(callable, &PyMethodDescr_Type)) { + fail = specialize_method_descriptor( + (PyMethodDescrObject *)callable, instr, nargs, cache); + } else { SPECIALIZATION_FAIL(CALL_NO_KW, call_fail_kind(callable)); fail = -1; From badecf29f9718c1761f9e2372fdabec122da32fc Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 9 Dec 2021 16:31:18 +0000 Subject: [PATCH 4/9] Remove old CALL opcodes: CALL_FUNCTION, CALL_METHOD, CALL_METHOD_KW, CALL_FUNCTION_KW. --- Include/opcode.h | 4 -- Lib/opcode.py | 6 +-- Objects/exception_handling_notes.txt | 6 +-- Objects/lnotab_notes.txt | 4 +- Python/ceval.c | 75 +++------------------------- Python/compile.c | 8 --- Python/opcode_targets.h | 8 +-- 7 files changed, 18 insertions(+), 93 deletions(-) diff --git a/Include/opcode.h b/Include/opcode.h index 45f57ec3b1cb41..48d24ea1822dfe 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -85,7 +85,6 @@ extern "C" { #define DELETE_FAST 126 #define GEN_START 129 #define RAISE_VARARGS 130 -#define CALL_FUNCTION 131 #define MAKE_FUNCTION 132 #define BUILD_SLICE 133 #define MAKE_CELL 135 @@ -93,7 +92,6 @@ extern "C" { #define LOAD_DEREF 137 #define STORE_DEREF 138 #define DELETE_DEREF 139 -#define CALL_FUNCTION_KW 141 #define CALL_FUNCTION_EX 142 #define EXTENDED_ARG 144 #define LIST_APPEND 145 @@ -106,12 +104,10 @@ extern "C" { #define BUILD_CONST_KEY_MAP 156 #define BUILD_STRING 157 #define LOAD_METHOD 160 -#define CALL_METHOD 161 #define LIST_EXTEND 162 #define SET_UPDATE 163 #define DICT_MERGE 164 #define DICT_UPDATE 165 -#define CALL_METHOD_KW 166 #define PRECALL_METHOD 168 #define CALL_NO_KW 169 #define CALL_KW 170 diff --git a/Lib/opcode.py b/Lib/opcode.py index b41dd1d33338d0..873b578d3f7b53 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -152,7 +152,7 @@ def jabs_op(name, op): def_op('GEN_START', 129) # Kind of generator/coroutine def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3) -def_op('CALL_FUNCTION', 131) # #args + def_op('MAKE_FUNCTION', 132) # Flags def_op('BUILD_SLICE', 133) # Number of items @@ -167,7 +167,6 @@ def jabs_op(name, op): def_op('DELETE_DEREF', 139) hasfree.append(139) -def_op('CALL_FUNCTION_KW', 141) # #args + #kwargs def_op('CALL_FUNCTION_EX', 142) # Flags def_op('EXTENDED_ARG', 144) @@ -186,12 +185,11 @@ def jabs_op(name, op): def_op('BUILD_STRING', 157) name_op('LOAD_METHOD', 160) -def_op('CALL_METHOD', 161) + def_op('LIST_EXTEND', 162) def_op('SET_UPDATE', 163) def_op('DICT_MERGE', 164) def_op('DICT_UPDATE', 165) -def_op('CALL_METHOD_KW', 166) def_op('PRECALL_METHOD', 168) def_op('CALL_NO_KW', 169) diff --git a/Objects/exception_handling_notes.txt b/Objects/exception_handling_notes.txt index e738c2781ad23f..a136358f90c888 100644 --- a/Objects/exception_handling_notes.txt +++ b/Objects/exception_handling_notes.txt @@ -23,7 +23,7 @@ compiles as follows in 3.10: 3 2 LOAD_GLOBAL 0 (g) 4 LOAD_CONST 1 (0) - 6 CALL_FUNCTION 1 + 6 CALL_NO_KW 1 8 POP_TOP 10 POP_BLOCK 12 LOAD_CONST 0 (None) @@ -47,7 +47,7 @@ a table to determine where to jump to when an exception is raised. 3 2 LOAD_GLOBAL 0 (g) 4 LOAD_CONST 1 (0) - 6 CALL_FUNCTION 1 + 6 CALL_NO_KW 1 8 POP_TOP 10 LOAD_CONST 0 (None) 12 RETURN_VALUE @@ -68,7 +68,7 @@ ExceptionTable: (Note this code is from an early 3.11 alpha, the NOP may well have be removed before release). If an instruction raises an exception then its offset is used to find the target to jump to. -For example, the CALL_FUNCTION at offset 6, falls into the range 2 to 8. +For example, the CALL_NO_KW at offset 6, falls into the range 2 to 8. So, if g() raises an exception, then control jumps to offset 14. diff --git a/Objects/lnotab_notes.txt b/Objects/lnotab_notes.txt index e52e437c65025e..362b87a86a481f 100644 --- a/Objects/lnotab_notes.txt +++ b/Objects/lnotab_notes.txt @@ -188,7 +188,7 @@ which compiles to this: 3 6 LOAD_GLOBAL 0 (print) 8 LOAD_CONST 1 (1) - 10 CALL_FUNCTION 1 + 10 CALL_NO_KW 1 12 POP_TOP 4 14 BREAK_LOOP @@ -197,7 +197,7 @@ which compiles to this: 6 20 LOAD_GLOBAL 0 (print) 22 LOAD_CONST 2 (2) - 24 CALL_FUNCTION 1 + 24 CALL_NO_KW 1 26 POP_TOP >> 28 LOAD_CONST 0 (None) 30 RETURN_VALUE diff --git a/Python/ceval.c b/Python/ceval.c index f9abe48e11ad38..0b4fbaa12a29c6 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4210,7 +4210,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr if (iter == NULL) goto error; PREDICT(FOR_ITER); - PREDICT(CALL_FUNCTION); + PREDICT(CALL_NO_KW); DISPATCH(); } @@ -4552,41 +4552,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } - TARGET(CALL_METHOD) { - /* Designed to work in tamdem with LOAD_METHOD. */ - /* `meth` is NULL when LOAD_METHOD thinks that it's not - a method call. - - Stack layout: - - ... | NULL | callable | arg1 | ... | argN - ^- TOP() - ^- (-oparg) - ^- (-oparg-1) - ^- (-oparg-2) - - `callable` will be POPed by call_function. - NULL will will be POPed manually later. - If `meth` isn't NULL, it's a method call. Stack layout: - - ... | method | self | arg1 | ... | argN - ^- TOP() - ^- (-oparg) - ^- (-oparg-1) - ^- (-oparg-2) - - `self` and `method` will be POPed by call_function. - We'll be passing `oparg + 1` to call_function, to - make it accept the `self` as a first argument. - */ - int is_method = (PEEK(oparg + 2) != NULL); - oparg += is_method; - nargs = oparg; - kwnames = NULL; - postcall_shrink = 2-is_method; - goto call_function; - } - TARGET(PRECALL_METHOD) { /* Designed to work in tamdem with LOAD_METHOD. */ /* `meth` is NULL when LOAD_METHOD thinks that it's not @@ -4620,15 +4585,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DISPATCH(); } - TARGET(CALL_NO_KW) { - PREDICTED(CALL_NO_KW); - kwnames = NULL; - oparg += extra_args; - extra_args = 0; - nargs = oparg; - goto call_function; - } - TARGET(CALL_KW) { kwnames = POP(); oparg += extra_args; @@ -4637,31 +4593,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr goto call_function; } - TARGET(CALL_METHOD_KW) { - /* Designed to work in tandem with LOAD_METHOD. Same as CALL_METHOD - but pops TOS to get a tuple of keyword names. */ - kwnames = POP(); - int is_method = (PEEK(oparg + 2) != NULL); - oparg += is_method; - nargs = oparg - (int)PyTuple_GET_SIZE(kwnames); - postcall_shrink = 2-is_method; - goto call_function; - } - - TARGET(CALL_FUNCTION_KW) { - kwnames = POP(); - nargs = oparg - (int)PyTuple_GET_SIZE(kwnames); - postcall_shrink = 1; - goto call_function; - } - - TARGET(CALL_FUNCTION) { - PREDICTED(CALL_FUNCTION); - STAT_INC(CALL_FUNCTION, unquickened); + TARGET(CALL_NO_KW) { PyObject *function; - nargs = oparg; + PREDICTED(CALL_NO_KW); + STAT_INC(CALL_NO_KW, unquickened); kwnames = NULL; - postcall_shrink = 1; + oparg += extra_args; + extra_args = 0; + nargs = oparg; call_function: function = PEEK(oparg + 1); if (Py_TYPE(function) == &PyMethod_Type) { diff --git a/Python/compile.c b/Python/compile.c index 3f5522577d16c2..43900110a0d77d 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1156,14 +1156,6 @@ stack_effect(int opcode, int oparg, int jump) return -oparg; case CALL_KW: return -oparg-1; - case CALL_FUNCTION: - return -oparg; - case CALL_METHOD: - return -oparg-1; - case CALL_METHOD_KW: - return -oparg-2; - case CALL_FUNCTION_KW: - return -oparg-1; case CALL_FUNCTION_EX: return -1 - ((oparg & 0x01) != 0); case MAKE_FUNCTION: diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index 286053d4411c56..ea5ceb0225d157 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -130,7 +130,7 @@ static void *opcode_targets[256] = { &&TARGET_STORE_FAST__STORE_FAST, &&TARGET_GEN_START, &&TARGET_RAISE_VARARGS, - &&TARGET_CALL_FUNCTION, + &&_unknown_opcode, &&TARGET_MAKE_FUNCTION, &&TARGET_BUILD_SLICE, &&_unknown_opcode, @@ -140,7 +140,7 @@ static void *opcode_targets[256] = { &&TARGET_STORE_DEREF, &&TARGET_DELETE_DEREF, &&_unknown_opcode, - &&TARGET_CALL_FUNCTION_KW, + &&_unknown_opcode, &&TARGET_CALL_FUNCTION_EX, &&_unknown_opcode, &&TARGET_EXTENDED_ARG, @@ -160,12 +160,12 @@ static void *opcode_targets[256] = { &&_unknown_opcode, &&_unknown_opcode, &&TARGET_LOAD_METHOD, - &&TARGET_CALL_METHOD, + &&_unknown_opcode, &&TARGET_LIST_EXTEND, &&TARGET_SET_UPDATE, &&TARGET_DICT_MERGE, &&TARGET_DICT_UPDATE, - &&TARGET_CALL_METHOD_KW, + &&_unknown_opcode, &&_unknown_opcode, &&TARGET_PRECALL_METHOD, &&TARGET_CALL_NO_KW, From 859f0334837a9d50aca61cbd7c525314137dc1f0 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 9 Dec 2021 17:50:16 +0000 Subject: [PATCH 5/9] Make sure that we have the correct number of arguments in CALL_NO_KW_PY_SIMPLE. --- Python/ceval.c | 1 + Python/specialize.c | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index 0b4fbaa12a29c6..64eb5047c97d05 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4697,6 +4697,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr SpecializedCacheEntry *caches = GET_CACHE(); _PyAdaptiveEntry *cache0 = &caches[0].adaptive; int argcount = cache0->original_oparg + extra_args; + DEOPT_IF(argcount != cache0->index, CALL_NO_KW); _PyCallCache *cache1 = &caches[-1].call; PyObject *callable = PEEK(argcount+1); DEOPT_IF(!PyFunction_Check(callable), CALL_NO_KW); diff --git a/Python/specialize.c b/Python/specialize.c index 4d95de569d5686..2320b728809df8 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -1324,6 +1324,10 @@ specialize_py_call( return -1; } int argcount = code->co_argcount; + if (argcount > 0xffff) { + SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OUT_OF_RANGE); + return -1; + } int defcount = func->func_defaults == NULL ? 0 : (int)PyTuple_GET_SIZE(func->func_defaults); assert(defcount <= argcount); int min_args = argcount-defcount; @@ -1345,6 +1349,7 @@ specialize_py_call( SPECIALIZATION_FAIL(CALL_NO_KW, SPEC_FAIL_OUT_OF_VERSIONS); return -1; } + cache[0].adaptive.index = nargs; cache1->func_version = version; cache1->defaults_start = defstart; cache1->defaults_len = deflen; @@ -1462,8 +1467,6 @@ _Py_Specialize_CallNoKw( PyObject *builtins) { int fail; - - if (PyCFunction_CheckExact(callable)) { fail = specialize_c_call(callable, instr, nargs, cache, builtins); } From 7e01f266020b553b241f65d0f0cd9fd44732d556 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 13 Dec 2021 15:28:13 +0000 Subject: [PATCH 6/9] Add a couple of macros and a comment, to make handling of stack adjustment around calls a bit more maintainable. --- Python/ceval.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index 64eb5047c97d05..337bad8b814dba 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1669,8 +1669,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr /* Variables used for making calls */ PyObject *kwnames; int nargs; + /* + * It is only between a PRECALL_METHOD instruction and the following instruction, + * that these two values can be anything other than their defaults. */ int postcall_shrink = 1; int extra_args = 0; +#define RESET_STACK_ADJUST_FOR_CALLS \ + do { \ + postcall_shrink = 1; \ + extra_args = 0; \ + } while (0) +#define STACK_ADJUST_IS_RESET \ + (postcall_shrink == 1 && extra_args == 0) /* WARNING: Because the CFrame lives on the C stack, * but can be accessed from a heap allocated object (tstate) @@ -4599,7 +4609,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr STAT_INC(CALL_NO_KW, unquickened); kwnames = NULL; oparg += extra_args; - extra_args = 0; nargs = oparg; call_function: function = PEEK(oparg + 1); @@ -4628,7 +4637,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr stack_pointer, nargs, kwnames ); STACK_SHRINK(postcall_shrink); - postcall_shrink = 1; + RESET_STACK_ADJUST_FOR_CALLS; // The frame has stolen all the arguments from the stack, // so there is no need to clean them up. Py_XDECREF(kwnames); @@ -4661,7 +4670,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr Py_DECREF(stack_pointer[i]); } STACK_SHRINK(postcall_shrink); - postcall_shrink = 1; + RESET_STACK_ADJUST_FOR_CALLS; PUSH(res); if (res == NULL) { goto error; @@ -4687,7 +4696,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr cache->adaptive.counter--; kwnames = NULL; oparg += extra_args; - extra_args = 0; nargs = oparg; goto call_function; } @@ -4706,12 +4714,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr /* PEP 523 */ DEOPT_IF(tstate->interp->eval_frame != NULL, CALL_NO_KW); STAT_INC(CALL_NO_KW, hit); - extra_args = 0; PyCodeObject *code = (PyCodeObject *)func->func_code; size_t size = code->co_nlocalsplus + code->co_stacksize + FRAME_SPECIALS_SIZE; InterpreterFrame *new_frame = _PyThreadState_BumpFramePointer(tstate, size); if (new_frame == NULL) { - postcall_shrink = 1; + RESET_STACK_ADJUST_FOR_CALLS; goto error; } _PyFrame_InitializeSpecials(new_frame, func, @@ -4730,7 +4737,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr new_frame->localsplus[i] = NULL; } STACK_SHRINK(postcall_shrink); - postcall_shrink = 1; + RESET_STACK_ADJUST_FOR_CALLS; Py_DECREF(func); _PyFrame_SetStackPointer(frame, stack_pointer); new_frame->previous = frame; @@ -4741,6 +4748,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr TARGET(CALL_NO_KW_BUILTIN_O) { assert(cframe.use_tracing == 0); + assert(STACK_ADJUST_IS_RESET); /* Builtin METH_O functions */ PyObject *callable = SECOND(); @@ -4771,6 +4779,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr TARGET(CALL_NO_KW_BUILTIN_FAST) { assert(cframe.use_tracing == 0); + assert(STACK_ADJUST_IS_RESET); /* Builtin METH_FASTCALL functions, without keywords */ SpecializedCacheEntry *caches = GET_CACHE(); _PyAdaptiveEntry *cache0 = &caches[0].adaptive; @@ -4809,6 +4818,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr TARGET(CALL_NO_KW_LEN) { assert(cframe.use_tracing == 0); + assert(STACK_ADJUST_IS_RESET); /* len(o) */ SpecializedCacheEntry *caches = GET_CACHE(); assert(caches[0].adaptive.original_oparg == 1); @@ -4837,6 +4847,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr TARGET(CALL_NO_KW_ISINSTANCE) { assert(cframe.use_tracing == 0); + assert(STACK_ADJUST_IS_RESET); /* isinstance(o, o2) */ SpecializedCacheEntry *caches = GET_CACHE(); assert(caches[0].adaptive.original_oparg == 2); @@ -4873,7 +4884,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr STAT_INC(CALL_NO_KW, hit); assert(extra_args == 1); extra_args = 0; - assert(postcall_shrink == 1); + assert(STACK_ADJUST_IS_RESET); PyObject *arg = TOP(); int err = PyList_Append(list, arg); if (err) { @@ -4891,17 +4902,16 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr TARGET(CALL_NO_KW_METHOD_DESCRIPTOR_O) { assert(_Py_OPCODE(next_instr[-2]) == PRECALL_METHOD); - SpecializedCacheEntry *caches = GET_CACHE(); - assert(caches[0].adaptive.original_oparg == 1); + assert(GET_CACHE()->adaptive.original_oparg == 1); DEOPT_IF(extra_args == 0, CALL_NO_KW); assert(extra_args == 1); PyObject *callable = THIRD(); DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), CALL_NO_KW); DEOPT_IF(((PyMethodDescrObject *)callable)->d_method->ml_flags != METH_O, CALL_NO_KW); - assert(postcall_shrink == 1); STAT_INC(CALL_NO_KW, hit); assert(extra_args == 1); extra_args = 0; + assert(STACK_ADJUST_IS_RESET); PyCFunction cfunc = ((PyMethodDescrObject *)callable)->d_method->ml_meth; // This is slower but CPython promises to check all non-vectorcall // function calls. @@ -4935,10 +4945,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), CALL_NO_KW); PyMethodDef *meth = ((PyMethodDescrObject *)callable)->d_method; DEOPT_IF(meth->ml_flags != METH_FASTCALL, CALL_NO_KW); - assert(postcall_shrink == 1); STAT_INC(CALL_NO_KW, hit); assert(extra_args == 1); extra_args = 0; + assert(STACK_ADJUST_IS_RESET); _PyCFunctionFast cfunc = (_PyCFunctionFast)(void(*)(void))meth->ml_meth; PyObject *self = PEEK(nargs+1); PyObject *res = cfunc(self, &PEEK(nargs), nargs); From 6541e3592fd359e3477536de47cfb053d610bb76 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 13 Dec 2021 15:51:47 +0000 Subject: [PATCH 7/9] Document new opcodes. --- Doc/library/dis.rst | 47 +++++++++++++++------------------------------ 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 9665e8dbf6cfc6..9f60cb43b47643 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -36,7 +36,7 @@ the following command can be used to display the disassembly of >>> dis.dis(myfunc) 2 0 LOAD_GLOBAL 0 (len) 2 LOAD_FAST 0 (alist) - 4 CALL_FUNCTION 1 + 4 CALL_NO_KW 1 6 RETURN_VALUE (The "2" is a line number). @@ -104,7 +104,7 @@ Example:: ... LOAD_GLOBAL LOAD_FAST - CALL_FUNCTION + CALL_NO_KW RETURN_VALUE @@ -616,7 +616,7 @@ iterations of the loop. .. opcode:: LOAD_BUILD_CLASS Pushes :func:`builtins.__build_class__` onto the stack. It is later called - by :opcode:`CALL_FUNCTION` to construct a class. + by :opcode:`CALL_NO_KW` to construct a class. .. opcode:: BEFORE_WITH (delta) @@ -1013,21 +1013,20 @@ All of the following opcodes use their arguments. with ``__cause__`` set to ``TOS``) -.. opcode:: CALL_FUNCTION (argc) +.. opcode:: CALL_NO_KW (argc) Calls a callable object with positional arguments. *argc* indicates the number of positional arguments. The top of the stack contains positional arguments, with the right-most argument on top. Below the arguments is a callable object to call. - ``CALL_FUNCTION`` pops all arguments and the callable object off the stack, + ``CALL_NO_KW`` pops all arguments and the callable object off the stack, calls the callable object with those arguments, and pushes the return value returned by the callable object. - .. versionchanged:: 3.6 - This opcode is used only for calls with positional arguments. + .. versionadded:: 3.11 -.. opcode:: CALL_FUNCTION_KW (argc) +.. opcode:: CALL_KW (argc) Calls a callable object with positional (if any) and keyword arguments. *argc* indicates the total number of positional and keyword arguments. @@ -1037,13 +1036,11 @@ All of the following opcodes use their arguments. in the order corresponding to the tuple. Below that are positional arguments, with the right-most parameter on top. Below the arguments is a callable object to call. - ``CALL_FUNCTION_KW`` pops all arguments and the callable object off the stack, + ``CALL_KW`` pops all arguments and the callable object off the stack, calls the callable object with those arguments, and pushes the return value returned by the callable object. - .. versionchanged:: 3.6 - Keyword arguments are packed in a tuple instead of a dictionary, - *argc* indicates the total number of arguments. + .. versionadded:: 3.1 .. opcode:: CALL_FUNCTION_EX (flags) @@ -1073,30 +1070,16 @@ All of the following opcodes use their arguments. .. versionadded:: 3.7 -.. opcode:: CALL_METHOD (argc) - - Calls a method. *argc* is the number of positional arguments. - Keyword arguments are not supported. This opcode is designed to be used - with :opcode:`LOAD_METHOD`. Positional arguments are on top of the stack. - Below them, the two items described in :opcode:`LOAD_METHOD` are on the - stack (either ``self`` and an unbound method object or ``NULL`` and an - arbitrary callable). All of them are popped and the return value is pushed. - - .. versionadded:: 3.7 - - -.. opcode:: CALL_METHOD_KW (argc) +.. opcode:: PRECALL_METHOD (argc) - Calls a method in a similar fashion as :opcode:`CALL_METHOD`, but also supports keyword arguments. - *argc* is the number of positional and keyword arguments. - This opcode is designed to be used with :opcode:`LOAD_METHOD`. TOS is a - tuple of keyword argument names. Argument values are below that. - Below them, the two items described in :opcode:`LOAD_METHOD` are on the - stack (either ``self`` and an unbound method object or ``NULL`` and an - arbitrary callable). All of them are popped from the stack and the return value is pushed. + Prefixes either :opcode:`CALL_NO_KW` or :opcode:`CALL_KW`. + This opcode is designed to be used with :opcode:`LOAD_METHOD`. + Sets internal variables, so that :opcode:`CALL_NO_KW` or :opcode:`CALL_KW` + clean up after :opcode:`LOAD_METHOD` correctly. .. versionadded:: 3.11 + .. opcode:: MAKE_FUNCTION (flags) Pushes a new function object on the stack. From bottom to top, the consumed From b0e00a8201b0433d64270acb37e73b978056b1b1 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 13 Dec 2021 17:12:48 +0000 Subject: [PATCH 8/9] Add news. --- .../2021-12-13-17-12-16.bpo-44525.4-FiSf.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-12-13-17-12-16.bpo-44525.4-FiSf.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-12-13-17-12-16.bpo-44525.4-FiSf.rst b/Misc/NEWS.d/next/Core and Builtins/2021-12-13-17-12-16.bpo-44525.4-FiSf.rst new file mode 100644 index 00000000000000..d929666c9107d8 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-12-13-17-12-16.bpo-44525.4-FiSf.rst @@ -0,0 +1,8 @@ +Replace the four call bytecode instructions which one pre-call instruction +and two call instructions. + +Removes ``CALL_FUNCTION``, ``CALL_FUNCTION_KW``, ``CALL_METHOD`` and +``CALL_METHOD_KW``. + +Adds ``CALL_NO_KW`` and ``CALL_KW`` call instructions, and +``PRECALL_METHOD`` prefix for pairing with ``LOAD_METHOD``. From 4c855e24371398e3b9b1db00fa6c07d3d9fefc23 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 14 Dec 2021 11:36:43 +0000 Subject: [PATCH 9/9] Fix typo --- Doc/library/dis.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 9f60cb43b47643..88ce0c8854f668 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1040,7 +1040,7 @@ All of the following opcodes use their arguments. calls the callable object with those arguments, and pushes the return value returned by the callable object. - .. versionadded:: 3.1 + .. versionadded:: 3.11 .. opcode:: CALL_FUNCTION_EX (flags)