From 294750dc0fc4ce69ed45695f7ca5220c9c771752 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 06:40:56 -0400 Subject: [PATCH 01/36] gh-106706 Streamline family syntax --- Python/bytecodes.c | 26 ++++++++++++------------- Tools/cases_generator/test_generator.py | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 18862f87b65fa0..857d269a0c00ae 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -282,7 +282,7 @@ dummy_func( res = Py_IsFalse(value) ? Py_True : Py_False; } - family(to_bool, INLINE_CACHE_ENTRIES_TO_BOOL) = { + family(TO_BOOL, INLINE_CACHE_ENTRIES_TO_BOOL) = { TO_BOOL, TO_BOOL_ALWAYS_TRUE, TO_BOOL_BOOL, @@ -370,7 +370,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - family(binary_op, INLINE_CACHE_ENTRIES_BINARY_OP) = { + family(BINARY_OP, INLINE_CACHE_ENTRIES_BINARY_OP) = { BINARY_OP, BINARY_OP_MULTIPLY_INT, BINARY_OP_ADD_INT, @@ -505,7 +505,7 @@ dummy_func( macro(BINARY_OP_INPLACE_ADD_UNICODE) = _GUARD_BOTH_UNICODE + _BINARY_OP_INPLACE_ADD_UNICODE; - family(binary_subscr, INLINE_CACHE_ENTRIES_BINARY_SUBSCR) = { + family(BINARY_SUBSCR, INLINE_CACHE_ENTRIES_BINARY_SUBSCR) = { BINARY_SUBSCR, BINARY_SUBSCR_DICT, BINARY_SUBSCR_GETITEM, @@ -641,7 +641,7 @@ dummy_func( ERROR_IF(err, error); } - family(store_subscr, INLINE_CACHE_ENTRIES_STORE_SUBSCR) = { + family(STORE_SUBSCR, INLINE_CACHE_ENTRIES_STORE_SUBSCR) = { STORE_SUBSCR, STORE_SUBSCR_DICT, STORE_SUBSCR_LIST_INT, @@ -919,7 +919,7 @@ dummy_func( ERROR_IF(iter == NULL, error); } - family(send, INLINE_CACHE_ENTRIES_SEND) = { + family(SEND, INLINE_CACHE_ENTRIES_SEND) = { SEND, SEND_GEN, }; @@ -1132,7 +1132,7 @@ dummy_func( } } - family(unpack_sequence, INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE) = { + family(UNPACK_SEQUENCE, INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE) = { UNPACK_SEQUENCE, UNPACK_SEQUENCE_TWO_TUPLE, UNPACK_SEQUENCE_TUPLE, @@ -1196,7 +1196,7 @@ dummy_func( ERROR_IF(res == 0, error); } - family(store_attr, INLINE_CACHE_ENTRIES_STORE_ATTR) = { + family(STORE_ATTR, INLINE_CACHE_ENTRIES_STORE_ATTR) = { STORE_ATTR, STORE_ATTR_INSTANCE_VALUE, STORE_ATTR_SLOT, @@ -1296,7 +1296,7 @@ dummy_func( macro(LOAD_FROM_DICT_OR_GLOBALS) = _LOAD_FROM_DICT_OR_GLOBALS; - family(load_global, INLINE_CACHE_ENTRIES_LOAD_GLOBAL) = { + family(LOAD_GLOBAL, INLINE_CACHE_ENTRIES_LOAD_GLOBAL) = { LOAD_GLOBAL, LOAD_GLOBAL_MODULE, LOAD_GLOBAL_BUILTIN, @@ -1645,7 +1645,7 @@ dummy_func( GO_TO_INSTRUCTION(LOAD_SUPER_ATTR); } - family(load_super_attr, INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR) = { + family(LOAD_SUPER_ATTR, INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR) = { LOAD_SUPER_ATTR, LOAD_SUPER_ATTR_ATTR, LOAD_SUPER_ATTR_METHOD, @@ -1748,7 +1748,7 @@ dummy_func( } } - family(load_attr, INLINE_CACHE_ENTRIES_LOAD_ATTR) = { + family(LOAD_ATTR, INLINE_CACHE_ENTRIES_LOAD_ATTR) = { LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_MODULE, @@ -2033,7 +2033,7 @@ dummy_func( Py_DECREF(owner); } - family(compare_op, INLINE_CACHE_ENTRIES_COMPARE_OP) = { + family(COMPARE_OP, INLINE_CACHE_ENTRIES_COMPARE_OP) = { COMPARE_OP, COMPARE_OP_FLOAT, COMPARE_OP_INT, @@ -2335,7 +2335,7 @@ dummy_func( // This is optimized by skipping that instruction and combining // its effect (popping 'iter' instead of pushing 'next'.) - family(for_iter, INLINE_CACHE_ENTRIES_FOR_ITER) = { + family(FOR_ITER, INLINE_CACHE_ENTRIES_FOR_ITER) = { FOR_ITER, FOR_ITER_LIST, FOR_ITER_TUPLE, @@ -2726,7 +2726,7 @@ dummy_func( // Cache layout: counter/1, func_version/2 // Neither CALL_INTRINSIC_1/2 nor CALL_FUNCTION_EX are members! - family(call, INLINE_CACHE_ENTRIES_CALL) = { + family(CALL, INLINE_CACHE_ENTRIES_CALL) = { CALL, CALL_BOUND_METHOD_EXACT_ARGS, CALL_PY_EXACT_ARGS, diff --git a/Tools/cases_generator/test_generator.py b/Tools/cases_generator/test_generator.py index e374ac41e6a94d..da6558e4bb7f66 100644 --- a/Tools/cases_generator/test_generator.py +++ b/Tools/cases_generator/test_generator.py @@ -287,7 +287,7 @@ def test_macro_instruction(): inst(OP3, (unused/5, arg2, left, right -- res)) { res = op3(arg2, left, right); } - family(op, INLINE_CACHE_ENTRIES_OP) = { OP, OP3 }; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP, OP3 }; """ output = """ TARGET(OP1) { From 7dc364c894a3483aef4d063db1cace28b58da0f1 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 06:49:28 -0400 Subject: [PATCH 02/36] Update syntax in interpreter_definition.md --- Tools/cases_generator/interpreter_definition.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index c03870ef59eb49..137ac3ab6ddba3 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -6,7 +6,7 @@ The CPython interpreter is defined in C, meaning that the semantics of the bytecode instructions, the dispatching mechanism, error handling, and tracing and instrumentation are all intermixed. -This document proposes defining a custom C-like DSL for defining the +This document proposes defining a custom C-like DSL for defining the instruction semantics and tools for generating the code deriving from the instruction definitions. @@ -45,7 +45,7 @@ passes from the semantic definition, reducing errors. As we improve the performance of CPython, we need to optimize larger regions of code, use more complex optimizations and, ultimately, translate to machine -code. +code. All of these steps introduce the possibility of more bugs, and require more code to be written. One way to mitigate this is through the use of code generators. @@ -61,7 +61,7 @@ blocks as the instructions for the tier 1 (PEP 659) interpreter. Rewriting all the instructions is tedious and error-prone, and changing the instructions is a maintenance headache as both versions need to be kept in sync. -By using a code generator and using a common source for the instructions, or +By using a code generator and using a common source for the instructions, or parts of instructions, we can reduce the potential for errors considerably. @@ -74,7 +74,7 @@ We update it as the need arises. Each op definition has a kind, a name, a stack and instruction stream effect, and a piece of C code describing its semantics:: - + ``` file: (definition | family | pseudo)+ @@ -85,7 +85,7 @@ and a piece of C code describing its semantics:: "op" "(" NAME "," stack_effect ")" "{" C-code "}" | "macro" "(" NAME ")" "=" uop ("+" uop)* ";" - + stack_effect: "(" [inputs] "--" [outputs] ")" @@ -351,7 +351,7 @@ A _family_ represents a specializable instruction and its specializations. Example: These opcodes all share the same instruction format): ```C - family(load_attr) = { LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; + family(LOAD_ATTR) = { LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; ``` ### Defining a pseudo instruction @@ -412,7 +412,7 @@ rather than popping and pushing, such that `LOAD_ATTR_SLOT` would look something stack_pointer += 1; } s1 = res; - } + } next_instr += (1 + 1 + 2 + 1 + 4); stack_pointer[-1] = s1; DISPATCH(); From dac8eb5a5de62df4ab4c3c8214bd41f57dc5b3db Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 06:55:06 -0400 Subject: [PATCH 03/36] Further refactor bytecodes.c --- Python/bytecodes.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 857d269a0c00ae..7c9d5d5ef5b828 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -283,7 +283,6 @@ dummy_func( } family(TO_BOOL, INLINE_CACHE_ENTRIES_TO_BOOL) = { - TO_BOOL, TO_BOOL_ALWAYS_TRUE, TO_BOOL_BOOL, TO_BOOL_INT, @@ -371,7 +370,6 @@ dummy_func( } family(BINARY_OP, INLINE_CACHE_ENTRIES_BINARY_OP) = { - BINARY_OP, BINARY_OP_MULTIPLY_INT, BINARY_OP_ADD_INT, BINARY_OP_SUBTRACT_INT, @@ -506,7 +504,6 @@ dummy_func( _GUARD_BOTH_UNICODE + _BINARY_OP_INPLACE_ADD_UNICODE; family(BINARY_SUBSCR, INLINE_CACHE_ENTRIES_BINARY_SUBSCR) = { - BINARY_SUBSCR, BINARY_SUBSCR_DICT, BINARY_SUBSCR_GETITEM, BINARY_SUBSCR_LIST_INT, @@ -642,7 +639,6 @@ dummy_func( } family(STORE_SUBSCR, INLINE_CACHE_ENTRIES_STORE_SUBSCR) = { - STORE_SUBSCR, STORE_SUBSCR_DICT, STORE_SUBSCR_LIST_INT, }; @@ -920,7 +916,6 @@ dummy_func( } family(SEND, INLINE_CACHE_ENTRIES_SEND) = { - SEND, SEND_GEN, }; @@ -1133,7 +1128,6 @@ dummy_func( } family(UNPACK_SEQUENCE, INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE) = { - UNPACK_SEQUENCE, UNPACK_SEQUENCE_TWO_TUPLE, UNPACK_SEQUENCE_TUPLE, UNPACK_SEQUENCE_LIST, @@ -1197,7 +1191,6 @@ dummy_func( } family(STORE_ATTR, INLINE_CACHE_ENTRIES_STORE_ATTR) = { - STORE_ATTR, STORE_ATTR_INSTANCE_VALUE, STORE_ATTR_SLOT, STORE_ATTR_WITH_HINT, @@ -1297,7 +1290,6 @@ dummy_func( macro(LOAD_FROM_DICT_OR_GLOBALS) = _LOAD_FROM_DICT_OR_GLOBALS; family(LOAD_GLOBAL, INLINE_CACHE_ENTRIES_LOAD_GLOBAL) = { - LOAD_GLOBAL, LOAD_GLOBAL_MODULE, LOAD_GLOBAL_BUILTIN, }; @@ -1646,7 +1638,6 @@ dummy_func( } family(LOAD_SUPER_ATTR, INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR) = { - LOAD_SUPER_ATTR, LOAD_SUPER_ATTR_ATTR, LOAD_SUPER_ATTR_METHOD, }; @@ -1749,7 +1740,6 @@ dummy_func( } family(LOAD_ATTR, INLINE_CACHE_ENTRIES_LOAD_ATTR) = { - LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_MODULE, LOAD_ATTR_WITH_HINT, @@ -2034,7 +2024,6 @@ dummy_func( } family(COMPARE_OP, INLINE_CACHE_ENTRIES_COMPARE_OP) = { - COMPARE_OP, COMPARE_OP_FLOAT, COMPARE_OP_INT, COMPARE_OP_STR, @@ -2336,7 +2325,6 @@ dummy_func( // its effect (popping 'iter' instead of pushing 'next'.) family(FOR_ITER, INLINE_CACHE_ENTRIES_FOR_ITER) = { - FOR_ITER, FOR_ITER_LIST, FOR_ITER_TUPLE, FOR_ITER_RANGE, @@ -2727,7 +2715,6 @@ dummy_func( // Cache layout: counter/1, func_version/2 // Neither CALL_INTRINSIC_1/2 nor CALL_FUNCTION_EX are members! family(CALL, INLINE_CACHE_ENTRIES_CALL) = { - CALL, CALL_BOUND_METHOD_EXACT_ARGS, CALL_PY_EXACT_ARGS, CALL_PY_WITH_DEFAULTS, From b05a7f8dc0669b8c8169982720c6025eda410682 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 06:55:55 -0400 Subject: [PATCH 04/36] Update test --- Tools/cases_generator/test_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/test_generator.py b/Tools/cases_generator/test_generator.py index da6558e4bb7f66..e44273429b7405 100644 --- a/Tools/cases_generator/test_generator.py +++ b/Tools/cases_generator/test_generator.py @@ -287,7 +287,7 @@ def test_macro_instruction(): inst(OP3, (unused/5, arg2, left, right -- res)) { res = op3(arg2, left, right); } - family(OP, INLINE_CACHE_ENTRIES_OP) = { OP, OP3 }; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP3 }; """ output = """ TARGET(OP1) { From 47f24918b8287aac9859e48416051cf7007aef73 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:09:38 -0400 Subject: [PATCH 05/36] Update syntax in interpreter_definition.md --- Tools/cases_generator/generate_cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index a20abcde85b7c7..be4645a43281da 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -865,7 +865,7 @@ def check_families(self) -> None: - All members must have the same cache, input and output effects """ for family in self.families.values(): - if len(family.members) < 2: + if len(family.members) < 1: self.error(f"Family {family.name!r} has insufficient members", family) members = [ member From d4999264a2f778d2c11dc6f9e61d63ec926332c2 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:10:18 -0400 Subject: [PATCH 06/36] Update interpreter_definition to new syntax --- Tools/cases_generator/interpreter_definition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index 137ac3ab6ddba3..528cbbd923861b 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -351,7 +351,7 @@ A _family_ represents a specializable instruction and its specializations. Example: These opcodes all share the same instruction format): ```C - family(LOAD_ATTR) = { LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; + family(LOAD_ATTR) = { LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; ``` ### Defining a pseudo instruction From 6fee03d502221803783de21684e10702d2cc39c3 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:27:41 -0400 Subject: [PATCH 07/36] Use family name as first instruction --- Tools/cases_generator/generate_cases.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index be4645a43281da..ef4b9e692225d8 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -867,6 +867,11 @@ def check_families(self) -> None: for family in self.families.values(): if len(family.members) < 1: self.error(f"Family {family.name!r} has insufficient members", family) + if family.name not in self.macro_instrs: + self.error( + f"Family {family.name!r} has unknown instruction {family.name!r}", + family, + ) members = [ member for member in family.members @@ -877,10 +882,10 @@ def check_families(self) -> None: self.error( f"Family {family.name!r} has unknown members: {unknown}", family ) - if len(members) < 2: + if len(members) < 1: continue - expected_effects = self.effect_counts(members[0]) - for member in members[1:]: + expected_effects = self.effect_counts(family.name) + for member in members[0:]: member_effects = self.effect_counts(member) if member_effects != expected_effects: self.error( @@ -1305,9 +1310,9 @@ def write_metadata(self) -> None: for name, family in self.families.items(): assert len(family.members) > 1 with self.out.indent(): - self.out.emit(f"\"{family.members[0]}\": [") + self.out.emit(f"\"{family.name}\": [") with self.out.indent(): - for m in family.members[1:]: + for m in family.members[0:]: self.out.emit(f"\"{m}\",") self.out.emit(f"],") self.out.emit("}") From d13dbf21e6df803a9d93b3cf54d0568f07fdb07b Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:30:09 -0400 Subject: [PATCH 08/36] stop using slice in iters --- Tools/cases_generator/generate_cases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index ef4b9e692225d8..538159ba45ee9a 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -885,7 +885,7 @@ def check_families(self) -> None: if len(members) < 1: continue expected_effects = self.effect_counts(family.name) - for member in members[0:]: + for member in members: member_effects = self.effect_counts(member) if member_effects != expected_effects: self.error( @@ -1312,7 +1312,7 @@ def write_metadata(self) -> None: with self.out.indent(): self.out.emit(f"\"{family.name}\": [") with self.out.indent(): - for m in family.members[0:]: + for m in family.members: self.out.emit(f"\"{m}\",") self.out.emit(f"],") self.out.emit("}") From 105871c02e47a78f5cb443f326233685fb3d1386 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:37:33 -0400 Subject: [PATCH 09/36] Remove formatting in interpreter_definition.md --- Tools/cases_generator/interpreter_definition.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index 528cbbd923861b..ad0986d64c54a1 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -6,7 +6,7 @@ The CPython interpreter is defined in C, meaning that the semantics of the bytecode instructions, the dispatching mechanism, error handling, and tracing and instrumentation are all intermixed. -This document proposes defining a custom C-like DSL for defining the +This document proposes defining a custom C-like DSL for defining the instruction semantics and tools for generating the code deriving from the instruction definitions. @@ -45,7 +45,7 @@ passes from the semantic definition, reducing errors. As we improve the performance of CPython, we need to optimize larger regions of code, use more complex optimizations and, ultimately, translate to machine -code. +code. All of these steps introduce the possibility of more bugs, and require more code to be written. One way to mitigate this is through the use of code generators. @@ -61,7 +61,7 @@ blocks as the instructions for the tier 1 (PEP 659) interpreter. Rewriting all the instructions is tedious and error-prone, and changing the instructions is a maintenance headache as both versions need to be kept in sync. -By using a code generator and using a common source for the instructions, or +By using a code generator and using a common source for the instructions, or parts of instructions, we can reduce the potential for errors considerably. @@ -74,7 +74,7 @@ We update it as the need arises. Each op definition has a kind, a name, a stack and instruction stream effect, and a piece of C code describing its semantics:: - + ``` file: (definition | family | pseudo)+ @@ -85,7 +85,7 @@ and a piece of C code describing its semantics:: "op" "(" NAME "," stack_effect ")" "{" C-code "}" | "macro" "(" NAME ")" "=" uop ("+" uop)* ";" - + stack_effect: "(" [inputs] "--" [outputs] ")" @@ -412,7 +412,7 @@ rather than popping and pushing, such that `LOAD_ATTR_SLOT` would look something stack_pointer += 1; } s1 = res; - } + } next_instr += (1 + 1 + 2 + 1 + 4); stack_pointer[-1] = s1; DISPATCH(); From 945f09dca00098c8c55658be5880f4dff2ae38cb Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:43:44 -0400 Subject: [PATCH 10/36] Change test --- Tools/cases_generator/test_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/test_generator.py b/Tools/cases_generator/test_generator.py index e44273429b7405..fe39f91cb9c1f1 100644 --- a/Tools/cases_generator/test_generator.py +++ b/Tools/cases_generator/test_generator.py @@ -321,13 +321,13 @@ def test_macro_instruction(): _tmp_3 = res; } next_instr += 5; - static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); STACK_SHRINK(2); stack_pointer[-1] = _tmp_3; DISPATCH(); } TARGET(OP3) { + static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *arg2 = stack_pointer[-3]; From b588c72c0b5a2a9fb729c835157117dbe5d48315 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:58:55 -0400 Subject: [PATCH 11/36] Fix check --- Lib/_opcode_metadata.py | 72 ------------------------- Python/generated_cases.c.h | 26 ++++----- Tools/cases_generator/generate_cases.py | 2 +- 3 files changed, 14 insertions(+), 86 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index fd8ecdb5c980f3..8c52e7a01167f5 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -31,75 +31,3 @@ "STORE_SUBSCR_DICT", "STORE_SUBSCR_LIST_INT", ], - "SEND": [ - "SEND_GEN", - ], - "UNPACK_SEQUENCE": [ - "UNPACK_SEQUENCE_TWO_TUPLE", - "UNPACK_SEQUENCE_TUPLE", - "UNPACK_SEQUENCE_LIST", - ], - "STORE_ATTR": [ - "STORE_ATTR_INSTANCE_VALUE", - "STORE_ATTR_SLOT", - "STORE_ATTR_WITH_HINT", - ], - "LOAD_GLOBAL": [ - "LOAD_GLOBAL_MODULE", - "LOAD_GLOBAL_BUILTIN", - ], - "LOAD_SUPER_ATTR": [ - "LOAD_SUPER_ATTR_ATTR", - "LOAD_SUPER_ATTR_METHOD", - ], - "LOAD_ATTR": [ - "LOAD_ATTR_INSTANCE_VALUE", - "LOAD_ATTR_MODULE", - "LOAD_ATTR_WITH_HINT", - "LOAD_ATTR_SLOT", - "LOAD_ATTR_CLASS", - "LOAD_ATTR_PROPERTY", - "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - "LOAD_ATTR_METHOD_WITH_VALUES", - "LOAD_ATTR_METHOD_NO_DICT", - "LOAD_ATTR_METHOD_LAZY_DICT", - "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", - "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - ], - "COMPARE_OP": [ - "COMPARE_OP_FLOAT", - "COMPARE_OP_INT", - "COMPARE_OP_STR", - ], - "FOR_ITER": [ - "FOR_ITER_LIST", - "FOR_ITER_TUPLE", - "FOR_ITER_RANGE", - "FOR_ITER_GEN", - ], - "CALL": [ - "CALL_BOUND_METHOD_EXACT_ARGS", - "CALL_PY_EXACT_ARGS", - "CALL_PY_WITH_DEFAULTS", - "CALL_NO_KW_TYPE_1", - "CALL_NO_KW_STR_1", - "CALL_NO_KW_TUPLE_1", - "CALL_BUILTIN_CLASS", - "CALL_NO_KW_BUILTIN_O", - "CALL_NO_KW_BUILTIN_FAST", - "CALL_BUILTIN_FAST_WITH_KEYWORDS", - "CALL_NO_KW_LEN", - "CALL_NO_KW_ISINSTANCE", - "CALL_NO_KW_LIST_APPEND", - "CALL_NO_KW_METHOD_DESCRIPTOR_O", - "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", - "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", - "CALL_NO_KW_ALLOC_AND_ENTER_INIT", - ], -} - -# An irregular case: -_specializations["BINARY_OP"].append("BINARY_OP_INPLACE_ADD_UNICODE") - -_specialized_instructions = [opcode for family in _specializations.values() for opcode in family] diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 383432f51a89ac..257d922aae9452 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -229,7 +229,6 @@ TARGET(TO_BOOL) { PREDICTED(TO_BOOL); - static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; #if ENABLE_SPECIALIZATION @@ -321,6 +320,7 @@ } TARGET(TO_BOOL_ALWAYS_TRUE) { + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; uint32_t version = read_u32(&next_instr[1].cache); @@ -368,6 +368,7 @@ _tmp_2 = res; } next_instr += 1; + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); STACK_SHRINK(1); stack_pointer[-1] = _tmp_2; DISPATCH(); @@ -585,7 +586,6 @@ TARGET(BINARY_SUBSCR) { PREDICTED(BINARY_SUBSCR); - static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *res; @@ -699,6 +699,7 @@ } TARGET(BINARY_SUBSCR_DICT) { + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *res; @@ -769,7 +770,6 @@ TARGET(STORE_SUBSCR) { PREDICTED(STORE_SUBSCR); - static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *v = stack_pointer[-3]; @@ -823,6 +823,7 @@ } TARGET(STORE_SUBSCR_DICT) { + static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *value = stack_pointer[-3]; @@ -1097,7 +1098,6 @@ TARGET(SEND) { PREDICTED(SEND); - static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v = stack_pointer[-1]; PyObject *receiver = stack_pointer[-2]; PyObject *retval; @@ -1153,6 +1153,7 @@ } TARGET(SEND_GEN) { + static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v = stack_pointer[-1]; PyObject *receiver = stack_pointer[-2]; DEOPT_IF(tstate->interp->eval_frame, SEND); @@ -1349,7 +1350,6 @@ TARGET(UNPACK_SEQUENCE) { PREDICTED(UNPACK_SEQUENCE); - static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; #if ENABLE_SPECIALIZATION _PyUnpackSequenceCache *cache = (_PyUnpackSequenceCache *)next_instr; @@ -1372,6 +1372,7 @@ } TARGET(UNPACK_SEQUENCE_TWO_TUPLE) { + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; PyObject **values = stack_pointer - (1); DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); @@ -1434,7 +1435,6 @@ TARGET(STORE_ATTR) { PREDICTED(STORE_ATTR); - static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *v = stack_pointer[-2]; uint16_t counter = read_u16(&next_instr[0].cache); @@ -1602,7 +1602,6 @@ TARGET(LOAD_GLOBAL) { PREDICTED(LOAD_GLOBAL); - static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *null = NULL; PyObject *v; #if ENABLE_SPECIALIZATION @@ -1688,6 +1687,7 @@ _tmp_1 = res; } next_instr += 4; + static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); STACK_GROW(1); STACK_GROW(((oparg & 1) ? 1 : 0)); stack_pointer[-1] = _tmp_1; @@ -2060,7 +2060,6 @@ TARGET(LOAD_SUPER_ATTR) { PREDICTED(LOAD_SUPER_ATTR); - static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self = stack_pointer[-1]; PyObject *class = stack_pointer[-2]; PyObject *global_super = stack_pointer[-3]; @@ -2123,6 +2122,7 @@ } TARGET(LOAD_SUPER_ATTR_ATTR) { + static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self = stack_pointer[-1]; PyObject *class = stack_pointer[-2]; PyObject *global_super = stack_pointer[-3]; @@ -2183,7 +2183,6 @@ TARGET(LOAD_ATTR) { PREDICTED(LOAD_ATTR); - static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *res2 = NULL; PyObject *res; @@ -2240,6 +2239,7 @@ } TARGET(LOAD_ATTR_INSTANCE_VALUE) { + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *res2 = NULL; PyObject *res; @@ -2442,6 +2442,7 @@ } TARGET(STORE_ATTR_INSTANCE_VALUE) { + static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *value = stack_pointer[-2]; uint32_t type_version = read_u32(&next_instr[1].cache); @@ -2537,7 +2538,6 @@ TARGET(COMPARE_OP) { PREDICTED(COMPARE_OP); - static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -2569,6 +2569,7 @@ } TARGET(COMPARE_OP_FLOAT) { + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -2964,7 +2965,6 @@ TARGET(FOR_ITER) { PREDICTED(FOR_ITER); - static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter = stack_pointer[-1]; PyObject *next; #if ENABLE_SPECIALIZATION @@ -3034,6 +3034,7 @@ } TARGET(FOR_ITER_LIST) { + static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter = stack_pointer[-1]; PyObject *next; DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); @@ -3436,7 +3437,6 @@ TARGET(CALL) { PREDICTED(CALL); - static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args = (stack_pointer - oparg); PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; @@ -3531,6 +3531,7 @@ } TARGET(CALL_BOUND_METHOD_EXACT_ARGS) { + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; DEOPT_IF(method != NULL, CALL); @@ -4402,7 +4403,6 @@ TARGET(BINARY_OP) { PREDICTED(BINARY_OP); - static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *rhs = stack_pointer[-1]; PyObject *lhs = stack_pointer[-2]; PyObject *res; diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 538159ba45ee9a..946b4d86a95230 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -867,7 +867,7 @@ def check_families(self) -> None: for family in self.families.values(): if len(family.members) < 1: self.error(f"Family {family.name!r} has insufficient members", family) - if family.name not in self.macro_instrs: + if family.name not in self.macro_instrs and family.name not in self.instrs: self.error( f"Family {family.name!r} has unknown instruction {family.name!r}", family, From e091ca3159ca32aebf8783943bb5e0db6901be99 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:59:48 -0400 Subject: [PATCH 12/36] Fix assertion --- Lib/_opcode_metadata.py | 72 +++++++++++++++++++++++++ Python/executor_cases.c.h | 14 +++-- Tools/cases_generator/generate_cases.py | 2 +- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 8c52e7a01167f5..fd8ecdb5c980f3 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -31,3 +31,75 @@ "STORE_SUBSCR_DICT", "STORE_SUBSCR_LIST_INT", ], + "SEND": [ + "SEND_GEN", + ], + "UNPACK_SEQUENCE": [ + "UNPACK_SEQUENCE_TWO_TUPLE", + "UNPACK_SEQUENCE_TUPLE", + "UNPACK_SEQUENCE_LIST", + ], + "STORE_ATTR": [ + "STORE_ATTR_INSTANCE_VALUE", + "STORE_ATTR_SLOT", + "STORE_ATTR_WITH_HINT", + ], + "LOAD_GLOBAL": [ + "LOAD_GLOBAL_MODULE", + "LOAD_GLOBAL_BUILTIN", + ], + "LOAD_SUPER_ATTR": [ + "LOAD_SUPER_ATTR_ATTR", + "LOAD_SUPER_ATTR_METHOD", + ], + "LOAD_ATTR": [ + "LOAD_ATTR_INSTANCE_VALUE", + "LOAD_ATTR_MODULE", + "LOAD_ATTR_WITH_HINT", + "LOAD_ATTR_SLOT", + "LOAD_ATTR_CLASS", + "LOAD_ATTR_PROPERTY", + "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + "LOAD_ATTR_METHOD_WITH_VALUES", + "LOAD_ATTR_METHOD_NO_DICT", + "LOAD_ATTR_METHOD_LAZY_DICT", + "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + ], + "COMPARE_OP": [ + "COMPARE_OP_FLOAT", + "COMPARE_OP_INT", + "COMPARE_OP_STR", + ], + "FOR_ITER": [ + "FOR_ITER_LIST", + "FOR_ITER_TUPLE", + "FOR_ITER_RANGE", + "FOR_ITER_GEN", + ], + "CALL": [ + "CALL_BOUND_METHOD_EXACT_ARGS", + "CALL_PY_EXACT_ARGS", + "CALL_PY_WITH_DEFAULTS", + "CALL_NO_KW_TYPE_1", + "CALL_NO_KW_STR_1", + "CALL_NO_KW_TUPLE_1", + "CALL_BUILTIN_CLASS", + "CALL_NO_KW_BUILTIN_O", + "CALL_NO_KW_BUILTIN_FAST", + "CALL_BUILTIN_FAST_WITH_KEYWORDS", + "CALL_NO_KW_LEN", + "CALL_NO_KW_ISINSTANCE", + "CALL_NO_KW_LIST_APPEND", + "CALL_NO_KW_METHOD_DESCRIPTOR_O", + "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", + "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", + "CALL_NO_KW_ALLOC_AND_ENTER_INIT", + ], +} + +# An irregular case: +_specializations["BINARY_OP"].append("BINARY_OP_INPLACE_ADD_UNICODE") + +_specialized_instructions = [opcode for family in _specializations.values() for opcode in family] diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2c2dbf429cec11..727fd1681c7874 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -97,7 +97,6 @@ } case TO_BOOL: { - static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; #if ENABLE_SPECIALIZATION @@ -183,6 +182,7 @@ } case TO_BOOL_ALWAYS_TRUE: { + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; uint32_t version = (uint32_t)operand; @@ -329,7 +329,6 @@ } case BINARY_SUBSCR: { - static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *res; @@ -440,6 +439,7 @@ } case BINARY_SUBSCR_DICT: { + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *res; @@ -481,7 +481,6 @@ } case STORE_SUBSCR: { - static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *v = stack_pointer[-3]; @@ -533,6 +532,7 @@ } case STORE_SUBSCR_DICT: { + static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *value = stack_pointer[-3]; @@ -770,7 +770,6 @@ } case UNPACK_SEQUENCE: { - static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; #if ENABLE_SPECIALIZATION _PyUnpackSequenceCache *cache = (_PyUnpackSequenceCache *)next_instr; @@ -792,6 +791,7 @@ } case UNPACK_SEQUENCE_TWO_TUPLE: { + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; PyObject **values = stack_pointer - (1); DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); @@ -932,7 +932,6 @@ } case LOAD_GLOBAL: { - static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *null = NULL; PyObject *v; #if ENABLE_SPECIALIZATION @@ -1313,6 +1312,7 @@ } case LOAD_SUPER_ATTR_ATTR: { + static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self = stack_pointer[-1]; PyObject *class = stack_pointer[-2]; PyObject *global_super = stack_pointer[-3]; @@ -1370,7 +1370,6 @@ } case LOAD_ATTR: { - static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *res2 = NULL; PyObject *res; @@ -1426,7 +1425,6 @@ } case COMPARE_OP: { - static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -1457,6 +1455,7 @@ } case COMPARE_OP_FLOAT: { + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -1935,7 +1934,6 @@ } case BINARY_OP: { - static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *rhs = stack_pointer[-1]; PyObject *lhs = stack_pointer[-2]; PyObject *res; diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 946b4d86a95230..54532888511633 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -1308,7 +1308,7 @@ def write_metadata(self) -> None: self.out.emit("") self.out.emit("_specializations = {") for name, family in self.families.items(): - assert len(family.members) > 1 + assert len(family.members) >= 1 with self.out.indent(): self.out.emit(f"\"{family.name}\": [") with self.out.indent(): From 59dedfe6d050b414e26caefc05be717ae14e5455 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 12:08:37 +0000 Subject: [PATCH 13/36] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst b/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst new file mode 100644 index 00000000000000..d1ce5426f6b0fd --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst @@ -0,0 +1,3 @@ +Change bytecode syntax for families +to remove redundant name matching +pseudo snytax From 868ae1b8c025ac64615bb75a13ad4fcde74b4cf8 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 08:37:02 -0400 Subject: [PATCH 14/36] lint blurb --- .../2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst b/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst index d1ce5426f6b0fd..9dbcc5b1568d05 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst @@ -1,3 +1,3 @@ -Change bytecode syntax for families -to remove redundant name matching +Change bytecode syntax for families +to remove redundant name matching pseudo snytax From 843880d63de4d0637bde935e8e7e39a2149fefaf Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Fri, 14 Jul 2023 06:55:10 -0400 Subject: [PATCH 15/36] Update Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst Co-authored-by: Guido van Rossum --- .../Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst b/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst index 9dbcc5b1568d05..bbd8e8eddda607 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2023-07-13-12-08-35.gh-issue-106706.29zp8E.rst @@ -1,3 +1,3 @@ Change bytecode syntax for families to remove redundant name matching -pseudo snytax +pseudo syntax. From 906c8b28e4e8c4bc99e39c846f3ab1537f388637 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Fri, 14 Jul 2023 06:57:28 -0400 Subject: [PATCH 16/36] Remove redundant checks for family member length --- Tools/cases_generator/generate_cases.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 54532888511633..d454c16bdc562d 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -865,8 +865,6 @@ def check_families(self) -> None: - All members must have the same cache, input and output effects """ for family in self.families.values(): - if len(family.members) < 1: - self.error(f"Family {family.name!r} has insufficient members", family) if family.name not in self.macro_instrs and family.name not in self.instrs: self.error( f"Family {family.name!r} has unknown instruction {family.name!r}", @@ -882,8 +880,6 @@ def check_families(self) -> None: self.error( f"Family {family.name!r} has unknown members: {unknown}", family ) - if len(members) < 1: - continue expected_effects = self.effect_counts(family.name) for member in members: member_effects = self.effect_counts(member) @@ -1308,7 +1304,6 @@ def write_metadata(self) -> None: self.out.emit("") self.out.emit("_specializations = {") for name, family in self.families.items(): - assert len(family.members) >= 1 with self.out.indent(): self.out.emit(f"\"{family.name}\": [") with self.out.indent(): From c367600c777c5d5eff62f5248afca4b3f7550b02 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Fri, 14 Jul 2023 06:59:22 -0400 Subject: [PATCH 17/36] Reword documentation --- .../cases_generator/interpreter_definition.md | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index ad0986d64c54a1..6e6588c78bd542 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -6,15 +6,16 @@ The CPython interpreter is defined in C, meaning that the semantics of the bytecode instructions, the dispatching mechanism, error handling, and tracing and instrumentation are all intermixed. -This document proposes defining a custom C-like DSL for defining the +This document proposes defining a custom C-like DSL for defining the instruction semantics and tools for generating the code deriving from the instruction definitions. These tools would be used to: -* Generate the main interpreter (done) -* Generate the tier 2 interpreter -* Generate documentation for instructions -* Generate metadata about instructions, such as stack use (done). + +- Generate the main interpreter (done) +- Generate the tier 2 interpreter +- Generate documentation for instructions +- Generate metadata about instructions, such as stack use (done). Having a single definition file ensures that there is a single source of truth for bytecode semantics. @@ -45,7 +46,7 @@ passes from the semantic definition, reducing errors. As we improve the performance of CPython, we need to optimize larger regions of code, use more complex optimizations and, ultimately, translate to machine -code. +code. All of these steps introduce the possibility of more bugs, and require more code to be written. One way to mitigate this is through the use of code generators. @@ -61,10 +62,9 @@ blocks as the instructions for the tier 1 (PEP 659) interpreter. Rewriting all the instructions is tedious and error-prone, and changing the instructions is a maintenance headache as both versions need to be kept in sync. -By using a code generator and using a common source for the instructions, or +By using a code generator and using a common source for the instructions, or parts of instructions, we can reduce the potential for errors considerably. - ## Specification This specification is a work in progress. @@ -74,7 +74,7 @@ We update it as the need arises. Each op definition has a kind, a name, a stack and instruction stream effect, and a piece of C code describing its semantics:: - + ``` file: (definition | family | pseudo)+ @@ -85,7 +85,7 @@ and a piece of C code describing its semantics:: "op" "(" NAME "," stack_effect ")" "{" C-code "}" | "macro" "(" NAME ")" "=" uop ("+" uop)* ";" - + stack_effect: "(" [inputs] "--" [outputs] ")" @@ -128,9 +128,9 @@ and a piece of C code describing its semantics:: The following definitions may occur: -* `inst`: A normal instruction, as previously defined by `TARGET(NAME)` in `ceval.c`. -* `op`: A part instruction from which macros can be constructed. -* `macro`: A bytecode instruction constructed from ops and cache effects. +- `inst`: A normal instruction, as previously defined by `TARGET(NAME)` in `ceval.c`. +- `op`: A part instruction from which macros can be constructed. +- `macro`: A bytecode instruction constructed from ops and cache effects. `NAME` can be any ASCII identifier that is a C identifier and not a C or Python keyword. `foo_1` is legal. `$` is not legal, nor is `struct` or `class`. @@ -165,9 +165,9 @@ part of the DSL. Those functions include: -* `DEOPT_IF(cond, instruction)`. Deoptimize if `cond` is met. -* `ERROR_IF(cond, label)`. Jump to error handler at `label` if `cond` is true. -* `DECREF_INPUTS()`. Generate `Py_DECREF()` calls for the input stack effects. +- `DEOPT_IF(cond, instruction)`. Deoptimize if `cond` is met. +- `ERROR_IF(cond, label)`. Jump to error handler at `label` if `cond` is true. +- `DECREF_INPUTS()`. Generate `Py_DECREF()` calls for the input stack effects. Note that the use of `DECREF_INPUTS()` is optional -- manual calls to `Py_DECREF()` or other approaches are also acceptable @@ -203,6 +203,7 @@ two idioms are valid: `ERROR_IF(true, error)`. An example of the latter would be: + ```cc res = PyObject_Add(left, right); if (res == NULL) { @@ -231,13 +232,16 @@ The same is true for all members of a pseudo instruction Some examples: ### Output stack effect + ```C inst ( LOAD_FAST, (-- value) ) { value = frame->f_localsplus[oparg]; Py_INCREF(value); } ``` + This would generate: + ```C TARGET(LOAD_FAST) { PyObject *value; @@ -249,12 +253,15 @@ This would generate: ``` ### Input stack effect + ```C inst ( STORE_FAST, (value --) ) { SETLOCAL(oparg, value); } ``` + This would generate: + ```C TARGET(STORE_FAST) { PyObject *value = PEEK(1); @@ -265,6 +272,7 @@ This would generate: ``` ### Input stack effect and cache effect + ```C op ( CHECK_OBJECT_TYPE, (owner, type_version/2 -- owner) ) { PyTypeObject *tp = Py_TYPE(owner); @@ -272,7 +280,9 @@ This would generate: DEOPT_IF(tp->tp_version_tag != type_version); } ``` + This might become (if it was an instruction): + ```C TARGET(CHECK_OBJECT_TYPE) { PyObject *owner = PEEK(1); @@ -288,12 +298,14 @@ This might become (if it was an instruction): ### More examples For explanations see "Generating the interpreter" below.) + ```C op ( CHECK_HAS_INSTANCE_VALUES, (owner -- owner) ) { PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); DEOPT_IF(!_PyDictOrValues_IsValues(dorv)); } ``` + ```C op ( LOAD_INSTANCE_VALUE, (owner, index/1 -- null if (oparg & 1), res) ) { res = _PyDictOrValues_GetValues(dorv)->values[index]; @@ -303,11 +315,13 @@ For explanations see "Generating the interpreter" below.) Py_DECREF(owner); } ``` + ```C macro ( LOAD_ATTR_INSTANCE_VALUE ) = counter/1 + CHECK_OBJECT_TYPE + CHECK_HAS_INSTANCE_VALUES + LOAD_INSTANCE_VALUE + unused/4 ; ``` + ```C op ( LOAD_SLOT, (owner, index/1 -- null if (oparg & 1), res) ) { char *addr = (char *)owner + index; @@ -318,15 +332,18 @@ For explanations see "Generating the interpreter" below.) Py_DECREF(owner); } ``` + ```C macro ( LOAD_ATTR_SLOT ) = counter/1 + CHECK_OBJECT_TYPE + LOAD_SLOT + unused/4; ``` + ```C inst ( BUILD_TUPLE, (items[oparg] -- tuple) ) { tuple = _PyTuple_FromArraySteal(items, oparg); ERROR_IF(tuple == NULL, error); } ``` + ```C inst ( PRINT_EXPR ) { PyObject *value = POP(); @@ -347,9 +364,10 @@ For explanations see "Generating the interpreter" below.) ### Defining an instruction family -A _family_ represents a specializable instruction and its specializations. +A _family_ maps a specializable instruction to its specializations. Example: These opcodes all share the same instruction format): + ```C family(LOAD_ATTR) = { LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; ``` @@ -359,11 +377,11 @@ Example: These opcodes all share the same instruction format): A _pseudo instruction_ is used by the bytecode compiler to represent a set of possible concrete instructions. Example: `JUMP` may expand to `JUMP_FORWARD` or `JUMP_BACKWARD`: + ```C pseudo(JUMP) = { JUMP_FORWARD, JUMP_BACKWARD }; ``` - ## Generating the interpreter The generated C code for a single instruction includes a preamble and dispatch at the end @@ -412,7 +430,7 @@ rather than popping and pushing, such that `LOAD_ATTR_SLOT` would look something stack_pointer += 1; } s1 = res; - } + } next_instr += (1 + 1 + 2 + 1 + 4); stack_pointer[-1] = s1; DISPATCH(); From d31258c38204816dbb2d8344434370aaaa75ae1d Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Fri, 14 Jul 2023 07:05:40 -0400 Subject: [PATCH 18/36] Update test, regenerate headers --- Tools/cases_generator/test_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/cases_generator/test_generator.py b/Tools/cases_generator/test_generator.py index fe39f91cb9c1f1..da6558e4bb7f66 100644 --- a/Tools/cases_generator/test_generator.py +++ b/Tools/cases_generator/test_generator.py @@ -287,7 +287,7 @@ def test_macro_instruction(): inst(OP3, (unused/5, arg2, left, right -- res)) { res = op3(arg2, left, right); } - family(OP, INLINE_CACHE_ENTRIES_OP) = { OP3 }; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP, OP3 }; """ output = """ TARGET(OP1) { @@ -321,13 +321,13 @@ def test_macro_instruction(): _tmp_3 = res; } next_instr += 5; + static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); STACK_SHRINK(2); stack_pointer[-1] = _tmp_3; DISPATCH(); } TARGET(OP3) { - static_assert(INLINE_CACHE_ENTRIES_OP == 5, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *arg2 = stack_pointer[-3]; From 9986c2e1633f5f16a0d9881ce43d5d7b97a4a7f4 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Fri, 14 Jul 2023 07:08:50 -0400 Subject: [PATCH 19/36] keep formatting in interpreter_definiton --- .../cases_generator/interpreter_definition.md | 56 +++++++------------ 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index 6e6588c78bd542..f141848631d04a 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -6,16 +6,15 @@ The CPython interpreter is defined in C, meaning that the semantics of the bytecode instructions, the dispatching mechanism, error handling, and tracing and instrumentation are all intermixed. -This document proposes defining a custom C-like DSL for defining the +This document proposes defining a custom C-like DSL for defining the instruction semantics and tools for generating the code deriving from the instruction definitions. These tools would be used to: - -- Generate the main interpreter (done) -- Generate the tier 2 interpreter -- Generate documentation for instructions -- Generate metadata about instructions, such as stack use (done). +* Generate the main interpreter (done) +* Generate the tier 2 interpreter +* Generate documentation for instructions +* Generate metadata about instructions, such as stack use (done). Having a single definition file ensures that there is a single source of truth for bytecode semantics. @@ -46,7 +45,7 @@ passes from the semantic definition, reducing errors. As we improve the performance of CPython, we need to optimize larger regions of code, use more complex optimizations and, ultimately, translate to machine -code. +code. All of these steps introduce the possibility of more bugs, and require more code to be written. One way to mitigate this is through the use of code generators. @@ -62,9 +61,10 @@ blocks as the instructions for the tier 1 (PEP 659) interpreter. Rewriting all the instructions is tedious and error-prone, and changing the instructions is a maintenance headache as both versions need to be kept in sync. -By using a code generator and using a common source for the instructions, or +By using a code generator and using a common source for the instructions, or parts of instructions, we can reduce the potential for errors considerably. + ## Specification This specification is a work in progress. @@ -74,7 +74,7 @@ We update it as the need arises. Each op definition has a kind, a name, a stack and instruction stream effect, and a piece of C code describing its semantics:: - + ``` file: (definition | family | pseudo)+ @@ -85,7 +85,7 @@ and a piece of C code describing its semantics:: "op" "(" NAME "," stack_effect ")" "{" C-code "}" | "macro" "(" NAME ")" "=" uop ("+" uop)* ";" - + stack_effect: "(" [inputs] "--" [outputs] ")" @@ -128,9 +128,9 @@ and a piece of C code describing its semantics:: The following definitions may occur: -- `inst`: A normal instruction, as previously defined by `TARGET(NAME)` in `ceval.c`. -- `op`: A part instruction from which macros can be constructed. -- `macro`: A bytecode instruction constructed from ops and cache effects. +* `inst`: A normal instruction, as previously defined by `TARGET(NAME)` in `ceval.c`. +* `op`: A part instruction from which macros can be constructed. +* `macro`: A bytecode instruction constructed from ops and cache effects. `NAME` can be any ASCII identifier that is a C identifier and not a C or Python keyword. `foo_1` is legal. `$` is not legal, nor is `struct` or `class`. @@ -165,9 +165,9 @@ part of the DSL. Those functions include: -- `DEOPT_IF(cond, instruction)`. Deoptimize if `cond` is met. -- `ERROR_IF(cond, label)`. Jump to error handler at `label` if `cond` is true. -- `DECREF_INPUTS()`. Generate `Py_DECREF()` calls for the input stack effects. +* `DEOPT_IF(cond, instruction)`. Deoptimize if `cond` is met. +* `ERROR_IF(cond, label)`. Jump to error handler at `label` if `cond` is true. +* `DECREF_INPUTS()`. Generate `Py_DECREF()` calls for the input stack effects. Note that the use of `DECREF_INPUTS()` is optional -- manual calls to `Py_DECREF()` or other approaches are also acceptable @@ -203,7 +203,6 @@ two idioms are valid: `ERROR_IF(true, error)`. An example of the latter would be: - ```cc res = PyObject_Add(left, right); if (res == NULL) { @@ -232,16 +231,13 @@ The same is true for all members of a pseudo instruction Some examples: ### Output stack effect - ```C inst ( LOAD_FAST, (-- value) ) { value = frame->f_localsplus[oparg]; Py_INCREF(value); } ``` - This would generate: - ```C TARGET(LOAD_FAST) { PyObject *value; @@ -253,15 +249,12 @@ This would generate: ``` ### Input stack effect - ```C inst ( STORE_FAST, (value --) ) { SETLOCAL(oparg, value); } ``` - This would generate: - ```C TARGET(STORE_FAST) { PyObject *value = PEEK(1); @@ -272,7 +265,6 @@ This would generate: ``` ### Input stack effect and cache effect - ```C op ( CHECK_OBJECT_TYPE, (owner, type_version/2 -- owner) ) { PyTypeObject *tp = Py_TYPE(owner); @@ -280,9 +272,7 @@ This would generate: DEOPT_IF(tp->tp_version_tag != type_version); } ``` - This might become (if it was an instruction): - ```C TARGET(CHECK_OBJECT_TYPE) { PyObject *owner = PEEK(1); @@ -298,14 +288,12 @@ This might become (if it was an instruction): ### More examples For explanations see "Generating the interpreter" below.) - ```C op ( CHECK_HAS_INSTANCE_VALUES, (owner -- owner) ) { PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); DEOPT_IF(!_PyDictOrValues_IsValues(dorv)); } ``` - ```C op ( LOAD_INSTANCE_VALUE, (owner, index/1 -- null if (oparg & 1), res) ) { res = _PyDictOrValues_GetValues(dorv)->values[index]; @@ -315,13 +303,11 @@ For explanations see "Generating the interpreter" below.) Py_DECREF(owner); } ``` - ```C macro ( LOAD_ATTR_INSTANCE_VALUE ) = counter/1 + CHECK_OBJECT_TYPE + CHECK_HAS_INSTANCE_VALUES + LOAD_INSTANCE_VALUE + unused/4 ; ``` - ```C op ( LOAD_SLOT, (owner, index/1 -- null if (oparg & 1), res) ) { char *addr = (char *)owner + index; @@ -332,18 +318,15 @@ For explanations see "Generating the interpreter" below.) Py_DECREF(owner); } ``` - ```C macro ( LOAD_ATTR_SLOT ) = counter/1 + CHECK_OBJECT_TYPE + LOAD_SLOT + unused/4; ``` - ```C inst ( BUILD_TUPLE, (items[oparg] -- tuple) ) { tuple = _PyTuple_FromArraySteal(items, oparg); ERROR_IF(tuple == NULL, error); } ``` - ```C inst ( PRINT_EXPR ) { PyObject *value = POP(); @@ -367,9 +350,8 @@ For explanations see "Generating the interpreter" below.) A _family_ maps a specializable instruction to its specializations. Example: These opcodes all share the same instruction format): - ```C - family(LOAD_ATTR) = { LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; + family(load_attr) = { LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_SLOT }; ``` ### Defining a pseudo instruction @@ -377,11 +359,11 @@ Example: These opcodes all share the same instruction format): A _pseudo instruction_ is used by the bytecode compiler to represent a set of possible concrete instructions. Example: `JUMP` may expand to `JUMP_FORWARD` or `JUMP_BACKWARD`: - ```C pseudo(JUMP) = { JUMP_FORWARD, JUMP_BACKWARD }; ``` + ## Generating the interpreter The generated C code for a single instruction includes a preamble and dispatch at the end @@ -430,7 +412,7 @@ rather than popping and pushing, such that `LOAD_ATTR_SLOT` would look something stack_pointer += 1; } s1 = res; - } + } next_instr += (1 + 1 + 2 + 1 + 4); stack_pointer[-1] = s1; DISPATCH(); From 160114108efc1c5a21b822189eb7cd7fa2c5dfc5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Thu, 13 Jul 2023 16:36:19 +0100 Subject: [PATCH 20/36] GH-104909: Split `LOAD_ATTR_INSTANCE_VALUE` into micro-ops (GH-106678) --- Include/internal/pycore_opcode_metadata.h | 22 ++++--- ...-07-12-11-18-55.gh-issue-104909.DRUsuh.rst | 1 + Python/bytecodes.c | 19 +++++- Python/executor_cases.c.h | 18 ++++++ Python/generated_cases.c.h | 62 ++++++++++++------- Tools/cases_generator/generate_cases.py | 14 +---- 6 files changed, 87 insertions(+), 49 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-07-12-11-18-55.gh-issue-104909.DRUsuh.rst diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index d2c1f9ad6e5fb3..79bbe9a916f596 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -39,10 +39,12 @@ #define _SKIP_CACHE 317 #define _GUARD_GLOBALS_VERSION 318 #define _GUARD_BUILTINS_VERSION 319 -#define IS_NONE 320 -#define _ITER_CHECK_RANGE 321 -#define _ITER_EXHAUSTED_RANGE 322 -#define _ITER_NEXT_RANGE 323 +#define _GUARD_TYPE_VERSION 320 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES 321 +#define IS_NONE 322 +#define _ITER_CHECK_RANGE 323 +#define _ITER_EXHAUSTED_RANGE 324 +#define _ITER_NEXT_RANGE 325 #ifndef NEED_OPCODE_METADATA extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); @@ -932,7 +934,7 @@ _PyOpcode_num_pushed(int opcode, int oparg, bool jump) { } #endif -enum InstructionFormat { INSTR_FMT_IB, INSTR_FMT_IBC, INSTR_FMT_IBC00, INSTR_FMT_IBC000, INSTR_FMT_IBC00000000, INSTR_FMT_IX, INSTR_FMT_IXC, INSTR_FMT_IXC00, INSTR_FMT_IXC000 }; +enum InstructionFormat { INSTR_FMT_IB, INSTR_FMT_IBC, INSTR_FMT_IBC00, INSTR_FMT_IBC000, INSTR_FMT_IBC00000, INSTR_FMT_IBC00000000, INSTR_FMT_IX, INSTR_FMT_IXC, INSTR_FMT_IXC0, INSTR_FMT_IXC00, INSTR_FMT_IXC000 }; #define HAS_ARG_FLAG (1) #define HAS_CONST_FLAG (2) #define HAS_NAME_FLAG (4) @@ -1321,9 +1323,11 @@ const char * const _PyOpcode_uop_name[512] = { [317] = "_SKIP_CACHE", [318] = "_GUARD_GLOBALS_VERSION", [319] = "_GUARD_BUILTINS_VERSION", - [320] = "IS_NONE", - [321] = "_ITER_CHECK_RANGE", - [322] = "_ITER_EXHAUSTED_RANGE", - [323] = "_ITER_NEXT_RANGE", + [320] = "_GUARD_TYPE_VERSION", + [321] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", + [322] = "IS_NONE", + [323] = "_ITER_CHECK_RANGE", + [324] = "_ITER_EXHAUSTED_RANGE", + [325] = "_ITER_NEXT_RANGE", }; #endif // NEED_OPCODE_METADATA diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-12-11-18-55.gh-issue-104909.DRUsuh.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-12-11-18-55.gh-issue-104909.DRUsuh.rst new file mode 100644 index 00000000000000..e0c1e67515a62c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-07-12-11-18-55.gh-issue-104909.DRUsuh.rst @@ -0,0 +1 @@ +Split :opcode:`LOAD_ATTR_INSTANCE_VALUE` into micro-ops. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 7c9d5d5ef5b828..2c9326a173c01b 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1806,14 +1806,21 @@ dummy_func( LOAD_ATTR, }; - inst(LOAD_ATTR_INSTANCE_VALUE, (unused/1, type_version/2, index/1, unused/5, owner -- res2 if (oparg & 1), res)) { + op(_GUARD_TYPE_VERSION, (type_version/2, owner -- owner)) { PyTypeObject *tp = Py_TYPE(owner); assert(type_version != 0); DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); - assert(tp->tp_dictoffset < 0); - assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT); + } + + op(_CHECK_MANAGED_OBJECT_HAS_VALUES, (owner -- owner)) { + assert(Py_TYPE(owner)->tp_dictoffset < 0); + assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR); + } + + op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, unused/5, owner -- res2 if (oparg & 1), res)) { + PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); res = _PyDictOrValues_GetValues(dorv)->values[index]; DEOPT_IF(res == NULL, LOAD_ATTR); STAT_INC(LOAD_ATTR, hit); @@ -1822,6 +1829,12 @@ dummy_func( DECREF_INPUTS(); } + macro(LOAD_ATTR_INSTANCE_VALUE) = + _SKIP_CACHE + // Skip over the counter + _GUARD_TYPE_VERSION + + _CHECK_MANAGED_OBJECT_HAS_VALUES + + _LOAD_ATTR_INSTANCE_VALUE; + inst(LOAD_ATTR_MODULE, (unused/1, type_version/2, index/1, unused/5, owner -- res2 if (oparg & 1), res)) { DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR); PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 727fd1681c7874..5d850e4b7f6023 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1424,6 +1424,24 @@ break; } + case _GUARD_TYPE_VERSION: { + PyObject *owner = stack_pointer[-1]; + uint32_t type_version = (uint32_t)operand; + PyTypeObject *tp = Py_TYPE(owner); + assert(type_version != 0); + DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); + break; + } + + case _CHECK_MANAGED_OBJECT_HAS_VALUES: { + PyObject *owner = stack_pointer[-1]; + assert(Py_TYPE(owner)->tp_dictoffset < 0); + assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); + PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); + DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR); + break; + } + case COMPARE_OP: { PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 257d922aae9452..4e55086c0f78e2 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -368,7 +368,6 @@ _tmp_2 = res; } next_instr += 1; - static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); STACK_SHRINK(1); stack_pointer[-1] = _tmp_2; DISPATCH(); @@ -1687,7 +1686,6 @@ _tmp_1 = res; } next_instr += 4; - static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); STACK_GROW(1); STACK_GROW(((oparg & 1) ? 1 : 0)); stack_pointer[-1] = _tmp_1; @@ -2239,29 +2237,45 @@ } TARGET(LOAD_ATTR_INSTANCE_VALUE) { - static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); - PyObject *owner = stack_pointer[-1]; - PyObject *res2 = NULL; - PyObject *res; - uint32_t type_version = read_u32(&next_instr[1].cache); - uint16_t index = read_u16(&next_instr[3].cache); - PyTypeObject *tp = Py_TYPE(owner); - assert(type_version != 0); - DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); - assert(tp->tp_dictoffset < 0); - assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT); - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); - DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR); - res = _PyDictOrValues_GetValues(dorv)->values[index]; - DEOPT_IF(res == NULL, LOAD_ATTR); - STAT_INC(LOAD_ATTR, hit); - Py_INCREF(res); - res2 = NULL; - Py_DECREF(owner); - STACK_GROW(((oparg & 1) ? 1 : 0)); - stack_pointer[-1] = res; - if (oparg & 1) { stack_pointer[-(1 + ((oparg & 1) ? 1 : 0))] = res2; } + PyObject *_tmp_1; + PyObject *_tmp_2 = stack_pointer[-1]; + { + } + { + PyObject *owner = _tmp_2; + uint32_t type_version = read_u32(&next_instr[1].cache); + PyTypeObject *tp = Py_TYPE(owner); + assert(type_version != 0); + DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); + _tmp_2 = owner; + } + { + PyObject *owner = _tmp_2; + assert(Py_TYPE(owner)->tp_dictoffset < 0); + assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT); + PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); + DEOPT_IF(!_PyDictOrValues_IsValues(dorv), LOAD_ATTR); + _tmp_2 = owner; + } + { + PyObject *owner = _tmp_2; + PyObject *res2 = NULL; + PyObject *res; + uint16_t index = read_u16(&next_instr[3].cache); + PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); + res = _PyDictOrValues_GetValues(dorv)->values[index]; + DEOPT_IF(res == NULL, LOAD_ATTR); + STAT_INC(LOAD_ATTR, hit); + Py_INCREF(res); + res2 = NULL; + Py_DECREF(owner); + if (oparg & 1) { _tmp_2 = res2; } + _tmp_1 = res; + } next_instr += 9; + STACK_GROW(((oparg & 1) ? 1 : 0)); + stack_pointer[-1] = _tmp_1; + if (oparg & 1) { stack_pointer[-2] = _tmp_2; } DISPATCH(); } diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index d454c16bdc562d..b3469a1c862849 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -839,19 +839,7 @@ def map_families(self) -> None: ) else: member_instr.family = family - elif member_macro := self.macro_instrs.get(member): - for part in member_macro.parts: - if isinstance(part, Component): - if part.instr.family not in (family, None): - self.error( - f"Component {part.instr.name} of macro {member} " - f"is a member of multiple families " - f"({part.instr.family.name}, {family.name}).", - family, - ) - else: - part.instr.family = family - else: + elif not self.macro_instrs.get(member): self.error( f"Unknown instruction {member!r} referenced in family {family.name!r}", family, From 8ce84bcf85c661335c0331f24cf98221b7904d6e Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Thu, 13 Jul 2023 11:45:21 -0400 Subject: [PATCH 21/36] gh-106690: Add a .coveragerc file to the CPython repository (#8150) The added file is the coverage default at some point in time + checking branches both ways + IDLE additions, labelled as such and somewhat designed to be unlikely to affect other files. Located in the CPython repository directory, it can be used where it is or copied elsewhere, depending on how one runs coverage. --------- Co-authored-by: Terry Jan Reedy Co-authored-by: Alex Waygood Co-authored-by: Erlend E. Aasland --- .coveragerc | 19 +++++++++++++++++++ ...-07-12-14-07-07.gh-issue-106690.NDz-oG.rst | 1 + 2 files changed, 20 insertions(+) create mode 100644 .coveragerc create mode 100644 Misc/NEWS.d/next/Tests/2023-07-12-14-07-07.gh-issue-106690.NDz-oG.rst diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000000000..18bf2f40fe523f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,19 @@ +[run] +branch = True + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + + .*# pragma: no cover + .*# pragma: no branch + + # Additions for IDLE: + .*# htest # + if not (_htest or _utest): + if not .*_utest: + if .*_htest: + diff --git a/Misc/NEWS.d/next/Tests/2023-07-12-14-07-07.gh-issue-106690.NDz-oG.rst b/Misc/NEWS.d/next/Tests/2023-07-12-14-07-07.gh-issue-106690.NDz-oG.rst new file mode 100644 index 00000000000000..e7dc0ac2220502 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-07-12-14-07-07.gh-issue-106690.NDz-oG.rst @@ -0,0 +1 @@ +Add .coveragerc to cpython repository for use with coverage package. From ca8147a78efea8d319f847255fb4c2d03075a8ab Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 13 Jul 2023 12:14:51 -0700 Subject: [PATCH 22/36] gh-106701: Move the hand-written Tier 2 uops to bytecodes.c (#106702) This moves EXIT_TRACE, SAVE_IP, JUMP_TO_TOP, and _POP_JUMP_IF_{FALSE,TRUE} from ceval.c to bytecodes.c. They are no less special than before, but this way they are discoverable o the copy-and-patch tooling. --- Include/internal/pycore_opcode_metadata.h | 100 +++++++++++----------- Python/bytecodes.c | 30 +++++++ Python/ceval.c | 40 --------- Python/executor_cases.c.h | 36 ++++++++ Tools/cases_generator/generate_cases.py | 12 ++- 5 files changed, 124 insertions(+), 94 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 79bbe9a916f596..c88640777e3fb0 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -21,30 +21,30 @@ #define EXIT_TRACE 300 #define SAVE_IP 301 -#define _POP_JUMP_IF_FALSE 302 -#define _POP_JUMP_IF_TRUE 303 -#define JUMP_TO_TOP 304 -#define _GUARD_BOTH_INT 305 -#define _BINARY_OP_MULTIPLY_INT 306 -#define _BINARY_OP_ADD_INT 307 -#define _BINARY_OP_SUBTRACT_INT 308 -#define _GUARD_BOTH_FLOAT 309 -#define _BINARY_OP_MULTIPLY_FLOAT 310 -#define _BINARY_OP_ADD_FLOAT 311 -#define _BINARY_OP_SUBTRACT_FLOAT 312 -#define _GUARD_BOTH_UNICODE 313 -#define _BINARY_OP_ADD_UNICODE 314 -#define _LOAD_LOCALS 315 -#define _LOAD_FROM_DICT_OR_GLOBALS 316 -#define _SKIP_CACHE 317 -#define _GUARD_GLOBALS_VERSION 318 -#define _GUARD_BUILTINS_VERSION 319 -#define _GUARD_TYPE_VERSION 320 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES 321 -#define IS_NONE 322 -#define _ITER_CHECK_RANGE 323 -#define _ITER_EXHAUSTED_RANGE 324 -#define _ITER_NEXT_RANGE 325 +#define _GUARD_BOTH_INT 302 +#define _BINARY_OP_MULTIPLY_INT 303 +#define _BINARY_OP_ADD_INT 304 +#define _BINARY_OP_SUBTRACT_INT 305 +#define _GUARD_BOTH_FLOAT 306 +#define _BINARY_OP_MULTIPLY_FLOAT 307 +#define _BINARY_OP_ADD_FLOAT 308 +#define _BINARY_OP_SUBTRACT_FLOAT 309 +#define _GUARD_BOTH_UNICODE 310 +#define _BINARY_OP_ADD_UNICODE 311 +#define _LOAD_LOCALS 312 +#define _LOAD_FROM_DICT_OR_GLOBALS 313 +#define _SKIP_CACHE 314 +#define _GUARD_GLOBALS_VERSION 315 +#define _GUARD_BUILTINS_VERSION 316 +#define _GUARD_TYPE_VERSION 317 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES 318 +#define IS_NONE 319 +#define _ITER_CHECK_RANGE 320 +#define _ITER_EXHAUSTED_RANGE 321 +#define _ITER_NEXT_RANGE 322 +#define _POP_JUMP_IF_FALSE 323 +#define _POP_JUMP_IF_TRUE 324 +#define JUMP_TO_TOP 325 #ifndef NEED_OPCODE_METADATA extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); @@ -1303,31 +1303,31 @@ const struct opcode_macro_expansion _PyOpcode_macro_expansion[256] = { [SWAP] = { .nuops = 1, .uops = { { SWAP, 0, 0 } } }, }; const char * const _PyOpcode_uop_name[512] = { - [300] = "EXIT_TRACE", - [301] = "SAVE_IP", - [302] = "_POP_JUMP_IF_FALSE", - [303] = "_POP_JUMP_IF_TRUE", - [304] = "JUMP_TO_TOP", - [305] = "_GUARD_BOTH_INT", - [306] = "_BINARY_OP_MULTIPLY_INT", - [307] = "_BINARY_OP_ADD_INT", - [308] = "_BINARY_OP_SUBTRACT_INT", - [309] = "_GUARD_BOTH_FLOAT", - [310] = "_BINARY_OP_MULTIPLY_FLOAT", - [311] = "_BINARY_OP_ADD_FLOAT", - [312] = "_BINARY_OP_SUBTRACT_FLOAT", - [313] = "_GUARD_BOTH_UNICODE", - [314] = "_BINARY_OP_ADD_UNICODE", - [315] = "_LOAD_LOCALS", - [316] = "_LOAD_FROM_DICT_OR_GLOBALS", - [317] = "_SKIP_CACHE", - [318] = "_GUARD_GLOBALS_VERSION", - [319] = "_GUARD_BUILTINS_VERSION", - [320] = "_GUARD_TYPE_VERSION", - [321] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", - [322] = "IS_NONE", - [323] = "_ITER_CHECK_RANGE", - [324] = "_ITER_EXHAUSTED_RANGE", - [325] = "_ITER_NEXT_RANGE", + [EXIT_TRACE] = "EXIT_TRACE", + [SAVE_IP] = "SAVE_IP", + [_GUARD_BOTH_INT] = "_GUARD_BOTH_INT", + [_BINARY_OP_MULTIPLY_INT] = "_BINARY_OP_MULTIPLY_INT", + [_BINARY_OP_ADD_INT] = "_BINARY_OP_ADD_INT", + [_BINARY_OP_SUBTRACT_INT] = "_BINARY_OP_SUBTRACT_INT", + [_GUARD_BOTH_FLOAT] = "_GUARD_BOTH_FLOAT", + [_BINARY_OP_MULTIPLY_FLOAT] = "_BINARY_OP_MULTIPLY_FLOAT", + [_BINARY_OP_ADD_FLOAT] = "_BINARY_OP_ADD_FLOAT", + [_BINARY_OP_SUBTRACT_FLOAT] = "_BINARY_OP_SUBTRACT_FLOAT", + [_GUARD_BOTH_UNICODE] = "_GUARD_BOTH_UNICODE", + [_BINARY_OP_ADD_UNICODE] = "_BINARY_OP_ADD_UNICODE", + [_LOAD_LOCALS] = "_LOAD_LOCALS", + [_LOAD_FROM_DICT_OR_GLOBALS] = "_LOAD_FROM_DICT_OR_GLOBALS", + [_SKIP_CACHE] = "_SKIP_CACHE", + [_GUARD_GLOBALS_VERSION] = "_GUARD_GLOBALS_VERSION", + [_GUARD_BUILTINS_VERSION] = "_GUARD_BUILTINS_VERSION", + [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", + [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", + [IS_NONE] = "IS_NONE", + [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", + [_ITER_EXHAUSTED_RANGE] = "_ITER_EXHAUSTED_RANGE", + [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", + [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", + [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", + [JUMP_TO_TOP] = "JUMP_TO_TOP", }; #endif // NEED_OPCODE_METADATA diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2c9326a173c01b..9aeaf483258473 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3641,6 +3641,36 @@ dummy_func( Py_UNREACHABLE(); } + ///////// Tier-2 only opcodes ///////// + + op(_POP_JUMP_IF_FALSE, (flag -- )) { + if (Py_IsFalse(flag)) { + pc = oparg; + } + } + + op(_POP_JUMP_IF_TRUE, (flag -- )) { + if (Py_IsTrue(flag)) { + pc = oparg; + } + } + + op(JUMP_TO_TOP, (--)) { + pc = 0; + CHECK_EVAL_BREAKER(); + } + + op(SAVE_IP, (--)) { + frame->prev_instr = ip_offset + oparg; + } + + op(EXIT_TRACE, (--)) { + frame->prev_instr--; // Back up to just before destination + _PyFrame_SetStackPointer(frame, stack_pointer); + Py_DECREF(self); + return frame; + } + // END BYTECODES // diff --git a/Python/ceval.c b/Python/ceval.c index de44085d732cfa..d6c72fa3ff386c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2764,46 +2764,6 @@ _PyUopExecute(_PyExecutorObject *executor, _PyInterpreterFrame *frame, PyObject #define ENABLE_SPECIALIZATION 0 #include "executor_cases.c.h" - // NOTE: These pop-jumps move the uop pc, not the bytecode ip - case _POP_JUMP_IF_FALSE: - { - if (Py_IsFalse(stack_pointer[-1])) { - pc = oparg; - } - stack_pointer--; - break; - } - - case _POP_JUMP_IF_TRUE: - { - if (Py_IsTrue(stack_pointer[-1])) { - pc = oparg; - } - stack_pointer--; - break; - } - - case JUMP_TO_TOP: - { - pc = 0; - CHECK_EVAL_BREAKER(); - break; - } - - case SAVE_IP: - { - frame->prev_instr = ip_offset + oparg; - break; - } - - case EXIT_TRACE: - { - frame->prev_instr--; // Back up to just before destination - _PyFrame_SetStackPointer(frame, stack_pointer); - Py_DECREF(self); - return frame; - } - default: { fprintf(stderr, "Unknown uop %d, operand %" PRIu64 "\n", opcode, operand); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 5d850e4b7f6023..d07121c0b6fac3 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1985,3 +1985,39 @@ stack_pointer[-(2 + (oparg-2))] = top; break; } + + case _POP_JUMP_IF_FALSE: { + PyObject *flag = stack_pointer[-1]; + if (Py_IsFalse(flag)) { + pc = oparg; + } + STACK_SHRINK(1); + break; + } + + case _POP_JUMP_IF_TRUE: { + PyObject *flag = stack_pointer[-1]; + if (Py_IsTrue(flag)) { + pc = oparg; + } + STACK_SHRINK(1); + break; + } + + case JUMP_TO_TOP: { + pc = 0; + break; + } + + case SAVE_IP: { + frame->prev_instr = ip_offset + oparg; + break; + } + + case EXIT_TRACE: { + frame->prev_instr--; // Back up to just before destination + _PyFrame_SetStackPointer(frame, stack_pointer); + Py_DECREF(self); + return frame; + break; + } diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index b3469a1c862849..48c082a429e276 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -410,6 +410,8 @@ def __init__(self, inst: parser.InstDef): def is_viable_uop(self) -> bool: """Whether this instruction is viable as a uop.""" + if self.name == "EXIT_TRACE": + return True # This has 'return frame' but it's okay if self.always_exits: # print(f"Skipping {self.name} because it always exits") return False @@ -1279,7 +1281,7 @@ def write_metadata(self) -> None: typing.assert_never(thing) with self.out.block("const char * const _PyOpcode_uop_name[512] =", ";"): - self.write_uop_items(lambda name, counter: f"[{counter}] = \"{name}\",") + self.write_uop_items(lambda name, counter: f"[{name}] = \"{name}\",") self.out.emit("#endif // NEED_OPCODE_METADATA") @@ -1324,17 +1326,19 @@ def write_pseudo_instrs(self) -> None: def write_uop_items(self, make_text: typing.Callable[[str, int], str]) -> None: """Write '#define XXX NNN' for each uop""" counter = 300 # TODO: Avoid collision with pseudo instructions + seen = set() def add(name: str) -> None: + if name in seen: + return nonlocal counter self.out.emit(make_text(name, counter)) counter += 1 + seen.add(name) + # These two are first by convention add("EXIT_TRACE") add("SAVE_IP") - add("_POP_JUMP_IF_FALSE") - add("_POP_JUMP_IF_TRUE") - add("JUMP_TO_TOP") for instr in self.instrs.values(): if instr.kind == "op" and instr.is_viable_uop(): From 58025d6048ae0a33cba4c44705f8f957d8b48a2a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 13 Jul 2023 09:18:53 -1000 Subject: [PATCH 23/36] gh-106664: selectors: add get() method to _SelectorMapping (#106665) It can be used to avoid raising and catching KeyError twice via __getitem__. Co-authored-by: Inada Naoki --- Lib/selectors.py | 14 +++++++++----- Lib/test/test_selectors.py | 6 ++++++ .../2023-07-12-03-04-45.gh-issue-106664.ZeUG78.rst | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-07-12-03-04-45.gh-issue-106664.ZeUG78.rst diff --git a/Lib/selectors.py b/Lib/selectors.py index af6a4f94b5008a..dfcc125dcd94ef 100644 --- a/Lib/selectors.py +++ b/Lib/selectors.py @@ -66,12 +66,16 @@ def __init__(self, selector): def __len__(self): return len(self._selector._fd_to_key) + def get(self, fileobj, default=None): + fd = self._selector._fileobj_lookup(fileobj) + return self._selector._fd_to_key.get(fd, default) + def __getitem__(self, fileobj): - try: - fd = self._selector._fileobj_lookup(fileobj) - return self._selector._fd_to_key[fd] - except KeyError: - raise KeyError("{!r} is not registered".format(fileobj)) from None + fd = self._selector._fileobj_lookup(fileobj) + key = self._selector._fd_to_key.get(fd) + if key is None: + raise KeyError("{!r} is not registered".format(fileobj)) + return key def __iter__(self): return iter(self._selector._fd_to_key) diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index c2db88c203920a..4545cbadb796fd 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -223,6 +223,8 @@ def test_close(self): self.assertRaises(RuntimeError, s.get_key, wr) self.assertRaises(KeyError, mapping.__getitem__, rd) self.assertRaises(KeyError, mapping.__getitem__, wr) + self.assertEqual(mapping.get(rd), None) + self.assertEqual(mapping.get(wr), None) def test_get_key(self): s = self.SELECTOR() @@ -241,13 +243,17 @@ def test_get_map(self): self.addCleanup(s.close) rd, wr = self.make_socketpair() + sentinel = object() keys = s.get_map() self.assertFalse(keys) self.assertEqual(len(keys), 0) self.assertEqual(list(keys), []) + self.assertEqual(keys.get(rd), None) + self.assertEqual(keys.get(rd, sentinel), sentinel) key = s.register(rd, selectors.EVENT_READ, "data") self.assertIn(rd, keys) + self.assertEqual(key, keys.get(rd)) self.assertEqual(key, keys[rd]) self.assertEqual(len(keys), 1) self.assertEqual(list(keys), [rd.fileno()]) diff --git a/Misc/NEWS.d/next/Library/2023-07-12-03-04-45.gh-issue-106664.ZeUG78.rst b/Misc/NEWS.d/next/Library/2023-07-12-03-04-45.gh-issue-106664.ZeUG78.rst new file mode 100644 index 00000000000000..c278cad74bd049 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-07-12-03-04-45.gh-issue-106664.ZeUG78.rst @@ -0,0 +1 @@ +:mod:`selectors`: Add ``_SelectorMapping.get()`` method and optimize ``_SelectorMapping.__getitem__()``. From fd9f8d66bf244ff9fb2354e9899c2a182e8bc142 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 13 Jul 2023 12:24:54 -0700 Subject: [PATCH 24/36] docs: clarify Path.suffix (GH-106650) --- Doc/library/pathlib.rst | 5 +++-- Doc/library/zipfile.rst | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 338575404ff0ad..af81df217eea92 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -432,7 +432,7 @@ Pure paths provide the following methods and properties: .. attribute:: PurePath.suffix - The file extension of the final component, if any:: + The last dot-separated portion of the final component, if any:: >>> PurePosixPath('my/library/setup.py').suffix '.py' @@ -441,10 +441,11 @@ Pure paths provide the following methods and properties: >>> PurePosixPath('my/library').suffix '' + This is commonly called the file extension. .. attribute:: PurePath.suffixes - A list of the path's file extensions:: + A list of the path's suffixes, often called file extensions:: >>> PurePosixPath('my/library.tar.gar').suffixes ['.tar', '.gar'] diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index 45f3d340bd82d3..bd951e4872f113 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -577,7 +577,8 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. .. data:: Path.suffix - The file extension of the final component. + The last dot-separated portion of the final component, if any. + This is commonly called the file extension. .. versionadded:: 3.11 Added :data:`Path.suffix` property. @@ -591,7 +592,7 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. .. data:: Path.suffixes - A list of the path’s file extensions. + A list of the path’s suffixes, commonly called file extensions. .. versionadded:: 3.11 Added :data:`Path.suffixes` property. From 9acddfa48aee0b97da1d83d8645d4022235e1f7a Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Fri, 14 Jul 2023 00:18:32 +0200 Subject: [PATCH 25/36] gh-106368: Increase Argument Clinic test coverage (#106728) - improve output_parameter() coverage - improve coverage for Function.kind --- Lib/test/clinic.test.c | 553 ++++++++++++++++++++++++++++++++++++++++ Lib/test/test_clinic.py | 37 +++ 2 files changed, 590 insertions(+) diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index da97c4bdd7e8e5..2fd8760415dc72 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -3,6 +3,10 @@ output preset block [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=3c81ac2402d06a8b]*/ +/*[clinic input] +class Test "TestObj *" "TestType" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=fc7e50384d12b83f]*/ /*[clinic input] test_object_converter @@ -61,6 +65,58 @@ test_object_converter_impl(PyObject *module, PyObject *a, PyObject *b, /*[clinic end generated code: output=886f4f9b598726b6 input=005e6a8a711a869b]*/ +/*[clinic input] +cloned = test_object_converter +Check the clone feature. +[clinic start generated code]*/ + +PyDoc_STRVAR(cloned__doc__, +"cloned($module, a, b, c, d, /)\n" +"--\n" +"\n" +"Check the clone feature."); + +#define CLONED_METHODDEF \ + {"cloned", _PyCFunction_CAST(cloned), METH_FASTCALL, cloned__doc__}, + +static PyObject * +cloned_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, + PyUnicode_Object *d); + +static PyObject * +cloned(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *a; + PyObject *b; + PyObject *c; + PyUnicode_Object *d; + + if (!_PyArg_CheckPositional("cloned", nargs, 4, 4)) { + goto exit; + } + a = args[0]; + if (!PyUnicode_FSConverter(args[1], &b)) { + goto exit; + } + if (!PyUnicode_Check(args[2])) { + _PyArg_BadArgument("cloned", "argument 3", "str", args[2]); + goto exit; + } + c = args[2]; + d = (PyUnicode_Object *)args[3]; + return_value = cloned_impl(module, a, b, c, d); + +exit: + return return_value; +} + +static PyObject * +cloned_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c, + PyUnicode_Object *d) +/*[clinic end generated code: output=026b483e27c38065 input=0543614019d6fcc7]*/ + + /*[clinic input] test_object_converter_one_arg @@ -4265,3 +4321,500 @@ static PyObject * mangle2_impl(PyObject *module, PyObject *args, PyObject *kwargs, PyObject *return_value) /*[clinic end generated code: output=2ebb62aaefe7590a input=391766fee51bad7a]*/ + + +/*[clinic input] +Test.cls_with_param + cls: defining_class + / + a: int +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_cls_with_param__doc__, +"cls_with_param($self, /, a)\n" +"--\n" +"\n"); + +#define TEST_CLS_WITH_PARAM_METHODDEF \ + {"cls_with_param", _PyCFunction_CAST(Test_cls_with_param), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, Test_cls_with_param__doc__}, + +static PyObject * +Test_cls_with_param_impl(TestObj *self, PyTypeObject *cls, int a); + +static PyObject * +Test_cls_with_param(TestObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "cls_with_param", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + int a; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + a = _PyLong_AsInt(args[0]); + if (a == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = Test_cls_with_param_impl(self, cls, a); + +exit: + return return_value; +} + +static PyObject * +Test_cls_with_param_impl(TestObj *self, PyTypeObject *cls, int a) +/*[clinic end generated code: output=00218e7f583e6c81 input=af158077bd237ef9]*/ + + +/*[clinic input] +Test.__init__ +Empty init method. +[clinic start generated code]*/ + +PyDoc_STRVAR(Test___init____doc__, +"Test()\n" +"--\n" +"\n" +"Empty init method."); + +static int +Test___init___impl(TestObj *self); + +static int +Test___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + PyTypeObject *base_tp = TestType; + + if ((Py_IS_TYPE(self, base_tp) || + Py_TYPE(self)->tp_new == base_tp->tp_new) && + !_PyArg_NoPositional("Test", args)) { + goto exit; + } + if ((Py_IS_TYPE(self, base_tp) || + Py_TYPE(self)->tp_new == base_tp->tp_new) && + !_PyArg_NoKeywords("Test", kwargs)) { + goto exit; + } + return_value = Test___init___impl((TestObj *)self); + +exit: + return return_value; +} + +static int +Test___init___impl(TestObj *self) +/*[clinic end generated code: output=f6a35c85bc5b408f input=4ea79fee54d0c3ff]*/ + + +/*[clinic input] +@classmethod +Test.__new__ +Empty new method. +[clinic start generated code]*/ + +PyDoc_STRVAR(Test__doc__, +"Test()\n" +"--\n" +"\n" +"Empty new method."); + +static PyObject * +Test_impl(PyTypeObject *type); + +static PyObject * +Test(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = TestType; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoPositional("Test", args)) { + goto exit; + } + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("Test", kwargs)) { + goto exit; + } + return_value = Test_impl(type); + +exit: + return return_value; +} + +static PyObject * +Test_impl(PyTypeObject *type) +/*[clinic end generated code: output=68a117adc057940f input=6fe98a19f097907f]*/ + + +/*[clinic input] +Test.cls_no_params + cls: defining_class + / +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_cls_no_params__doc__, +"cls_no_params($self, /)\n" +"--\n" +"\n"); + +#define TEST_CLS_NO_PARAMS_METHODDEF \ + {"cls_no_params", _PyCFunction_CAST(Test_cls_no_params), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, Test_cls_no_params__doc__}, + +static PyObject * +Test_cls_no_params_impl(TestObj *self, PyTypeObject *cls); + +static PyObject * +Test_cls_no_params(TestObj *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs) { + PyErr_SetString(PyExc_TypeError, "cls_no_params() takes no arguments"); + return NULL; + } + return Test_cls_no_params_impl(self, cls); +} + +static PyObject * +Test_cls_no_params_impl(TestObj *self, PyTypeObject *cls) +/*[clinic end generated code: output=cc8845f22cff3dcb input=e7e2e4e344e96a11]*/ + + +/*[clinic input] +Test.metho_not_default_return_converter -> int + a: object + / +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_metho_not_default_return_converter__doc__, +"metho_not_default_return_converter($self, a, /)\n" +"--\n" +"\n"); + +#define TEST_METHO_NOT_DEFAULT_RETURN_CONVERTER_METHODDEF \ + {"metho_not_default_return_converter", (PyCFunction)Test_metho_not_default_return_converter, METH_O, Test_metho_not_default_return_converter__doc__}, + +static int +Test_metho_not_default_return_converter_impl(TestObj *self, PyObject *a); + +static PyObject * +Test_metho_not_default_return_converter(TestObj *self, PyObject *a) +{ + PyObject *return_value = NULL; + int _return_value; + + _return_value = Test_metho_not_default_return_converter_impl(self, a); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong((long)_return_value); + +exit: + return return_value; +} + +static int +Test_metho_not_default_return_converter_impl(TestObj *self, PyObject *a) +/*[clinic end generated code: output=3350de11bd538007 input=428657129b521177]*/ + + +/*[clinic input] +Test.an_metho_arg_named_arg + arg: int + Name should be mangled to 'arg_' in generated output. + / +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_an_metho_arg_named_arg__doc__, +"an_metho_arg_named_arg($self, arg, /)\n" +"--\n" +"\n" +"\n" +"\n" +" arg\n" +" Name should be mangled to \'arg_\' in generated output."); + +#define TEST_AN_METHO_ARG_NAMED_ARG_METHODDEF \ + {"an_metho_arg_named_arg", (PyCFunction)Test_an_metho_arg_named_arg, METH_O, Test_an_metho_arg_named_arg__doc__}, + +static PyObject * +Test_an_metho_arg_named_arg_impl(TestObj *self, int arg); + +static PyObject * +Test_an_metho_arg_named_arg(TestObj *self, PyObject *arg_) +{ + PyObject *return_value = NULL; + int arg; + + arg = _PyLong_AsInt(arg_); + if (arg == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = Test_an_metho_arg_named_arg_impl(self, arg); + +exit: + return return_value; +} + +static PyObject * +Test_an_metho_arg_named_arg_impl(TestObj *self, int arg) +/*[clinic end generated code: output=7d590626642194ae input=2a53a57cf5624f95]*/ + + +/*[clinic input] +Test.__init__ + *args: object + / +Varargs init method. For example, nargs is translated to PyTuple_GET_SIZE. +[clinic start generated code]*/ + +PyDoc_STRVAR(Test___init____doc__, +"Test(*args)\n" +"--\n" +"\n" +"Varargs init method. For example, nargs is translated to PyTuple_GET_SIZE."); + +static int +Test___init___impl(TestObj *self, PyObject *args); + +static int +Test___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + PyTypeObject *base_tp = TestType; + PyObject *__clinic_args = NULL; + + if ((Py_IS_TYPE(self, base_tp) || + Py_TYPE(self)->tp_new == base_tp->tp_new) && + !_PyArg_NoKeywords("Test", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("Test", PyTuple_GET_SIZE(args), 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_GetSlice(0, -1); + return_value = Test___init___impl((TestObj *)self, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +static int +Test___init___impl(TestObj *self, PyObject *args) +/*[clinic end generated code: output=0ed1009fe0dcf98d input=96c3ddc0cd38fc0c]*/ + + +/*[clinic input] +@classmethod +Test.__new__ + *args: object + / +Varargs new method. For example, nargs is translated to PyTuple_GET_SIZE. +[clinic start generated code]*/ + +PyDoc_STRVAR(Test__doc__, +"Test(*args)\n" +"--\n" +"\n" +"Varargs new method. For example, nargs is translated to PyTuple_GET_SIZE."); + +static PyObject * +Test_impl(PyTypeObject *type, PyObject *args); + +static PyObject * +Test(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + PyTypeObject *base_tp = TestType; + PyObject *__clinic_args = NULL; + + if ((type == base_tp || type->tp_init == base_tp->tp_init) && + !_PyArg_NoKeywords("Test", kwargs)) { + goto exit; + } + if (!_PyArg_CheckPositional("Test", PyTuple_GET_SIZE(args), 0, PY_SSIZE_T_MAX)) { + goto exit; + } + __clinic_args = PyTuple_GetSlice(0, -1); + return_value = Test_impl(type, __clinic_args); + +exit: + Py_XDECREF(__clinic_args); + return return_value; +} + +static PyObject * +Test_impl(PyTypeObject *type, PyObject *args) +/*[clinic end generated code: output=8b219f6633e2a2e9 input=26a672e2e9750120]*/ + + +/*[clinic input] +Test.__init__ + a: object +Init method with positional or keyword arguments. +[clinic start generated code]*/ + +PyDoc_STRVAR(Test___init____doc__, +"Test(a)\n" +"--\n" +"\n" +"Init method with positional or keyword arguments."); + +static int +Test___init___impl(TestObj *self, PyObject *a); + +static int +Test___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(a), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "Test", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject * const *fastargs; + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + PyObject *a; + + fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf); + if (!fastargs) { + goto exit; + } + a = fastargs[0]; + return_value = Test___init___impl((TestObj *)self, a); + +exit: + return return_value; +} + +static int +Test___init___impl(TestObj *self, PyObject *a) +/*[clinic end generated code: output=0b9ca79638ab3ecb input=a8f9222a6ab35c59]*/ + + +/*[clinic input] +@classmethod +Test.class_method +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_class_method__doc__, +"class_method($type, /)\n" +"--\n" +"\n"); + +#define TEST_CLASS_METHOD_METHODDEF \ + {"class_method", (PyCFunction)Test_class_method, METH_NOARGS|METH_CLASS, Test_class_method__doc__}, + +static PyObject * +Test_class_method_impl(PyTypeObject *type); + +static PyObject * +Test_class_method(PyTypeObject *type, PyObject *Py_UNUSED(ignored)) +{ + return Test_class_method_impl(type); +} + +static PyObject * +Test_class_method_impl(PyTypeObject *type) +/*[clinic end generated code: output=47fb7ecca1abcaaa input=43bc4a0494547b80]*/ + + +/*[clinic input] +@staticmethod +Test.static_method +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_static_method__doc__, +"static_method()\n" +"--\n" +"\n"); + +#define TEST_STATIC_METHOD_METHODDEF \ + {"static_method", (PyCFunction)Test_static_method, METH_NOARGS|METH_STATIC, Test_static_method__doc__}, + +static PyObject * +Test_static_method_impl(); + +static PyObject * +Test_static_method(void *null, PyObject *Py_UNUSED(ignored)) +{ + return Test_static_method_impl(); +} + +static PyObject * +Test_static_method_impl() +/*[clinic end generated code: output=82524a63025cf7ab input=dae892fac55ae72b]*/ + + +/*[clinic input] +@coexist +Test.meth_coexist +[clinic start generated code]*/ + +PyDoc_STRVAR(Test_meth_coexist__doc__, +"meth_coexist($self, /)\n" +"--\n" +"\n"); + +#define TEST_METH_COEXIST_METHODDEF \ + {"meth_coexist", (PyCFunction)Test_meth_coexist, METH_NOARGS|METH_COEXIST, Test_meth_coexist__doc__}, + +static PyObject * +Test_meth_coexist_impl(TestObj *self); + +static PyObject * +Test_meth_coexist(TestObj *self, PyObject *Py_UNUSED(ignored)) +{ + return Test_meth_coexist_impl(self); +} + +static PyObject * +Test_meth_coexist_impl(TestObj *self) +/*[clinic end generated code: output=808a293d0cd27439 input=2a1d75b5e6fec6dd]*/ diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 685ba58642a5ae..975840333e5901 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -1013,6 +1013,43 @@ def test_defining_class_param_cannot_be_optional(self): out = self.parse_function_should_fail(block) self.assertEqual(out, expected_error_msg) + def test_slot_methods_cannot_access_defining_class(self): + block = """ + module foo + class Foo "" "" + Foo.__init__ + cls: defining_class + a: object + """ + msg = "Slot methods cannot access their defining class." + with self.assertRaisesRegex(ValueError, msg): + self.parse_function(block) + + def test_new_must_be_a_class_method(self): + expected_error_msg = ( + "Error on line 0:\n" + "__new__ must be a class method!\n" + ) + out = self.parse_function_should_fail(""" + module foo + class Foo "" "" + Foo.__new__ + """) + self.assertEqual(out, expected_error_msg) + + def test_init_must_be_a_normal_method(self): + expected_error_msg = ( + "Error on line 0:\n" + "__init__ must be a normal method, not a class or static method!\n" + ) + out = self.parse_function_should_fail(""" + module foo + class Foo "" "" + @classmethod + Foo.__init__ + """) + self.assertEqual(out, expected_error_msg) + def test_unused_param(self): block = self.parse(""" module foo From 0941a94187803b99a93fef352d191fdb9db047fc Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 13 Jul 2023 23:54:05 +0100 Subject: [PATCH 26/36] gh-104683: Argument clinic: use an enum to describe the different kinds of functions (#106721) Argument clinic: use an enum to describe the different kinds of functions --- Tools/clinic/clinic.py | 69 +++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 8a75aba1e4de93..2dce33690993dd 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -807,7 +807,7 @@ def output_templates(self, f): default_return_converter = (not f.return_converter or f.return_converter.type == 'PyObject *') - new_or_init = f.kind in (METHOD_NEW, METHOD_INIT) + new_or_init = f.kind.new_or_init vararg = NO_VARARG pos_only = min_pos = max_pos = min_kw_only = pseudo_args = 0 @@ -1250,7 +1250,7 @@ def parser_body( if new_or_init: methoddef_define = '' - if f.kind == METHOD_NEW: + if f.kind is METHOD_NEW: parser_prototype = parser_prototype_keyword else: return_value_declaration = "int return_value = -1;" @@ -1475,7 +1475,7 @@ def render_function( last_group = 0 first_optional = len(selfless) positional = selfless and selfless[-1].is_positional_only() - new_or_init = f.kind in (METHOD_NEW, METHOD_INIT) + new_or_init = f.kind.new_or_init has_option_groups = False # offset i by -1 because first_optional needs to ignore self @@ -2441,9 +2441,28 @@ def __repr__(self) -> str: """.strip().split()) -INVALID, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW = """ -INVALID, CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW -""".replace(",", "").strip().split() +class FunctionKind(enum.Enum): + INVALID = enum.auto() + CALLABLE = enum.auto() + STATIC_METHOD = enum.auto() + CLASS_METHOD = enum.auto() + METHOD_INIT = enum.auto() + METHOD_NEW = enum.auto() + + @functools.cached_property + def new_or_init(self) -> bool: + return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW} + + def __repr__(self) -> str: + return f"" + + +INVALID: Final = FunctionKind.INVALID +CALLABLE: Final = FunctionKind.CALLABLE +STATIC_METHOD: Final = FunctionKind.STATIC_METHOD +CLASS_METHOD: Final = FunctionKind.CLASS_METHOD +METHOD_INIT: Final = FunctionKind.METHOD_INIT +METHOD_NEW: Final = FunctionKind.METHOD_NEW ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] @@ -2471,7 +2490,7 @@ class Function: return_converter: CReturnConverter return_annotation: object = inspect.Signature.empty docstring: str = '' - kind: str = CALLABLE + kind: FunctionKind = CALLABLE coexist: bool = False # docstring_only means "don't generate a machine-readable # signature, just a normal docstring". it's True for @@ -2497,15 +2516,16 @@ def render_parameters(self) -> list[Parameter]: @property def methoddef_flags(self) -> str | None: - if self.kind in (METHOD_INIT, METHOD_NEW): + if self.kind.new_or_init: return None flags = [] - if self.kind == CLASS_METHOD: - flags.append('METH_CLASS') - elif self.kind == STATIC_METHOD: - flags.append('METH_STATIC') - else: - assert self.kind == CALLABLE, "unknown kind: " + repr(self.kind) + match self.kind: + case FunctionKind.CLASS_METHOD: + flags.append('METH_CLASS') + case FunctionKind.STATIC_METHOD: + flags.append('METH_STATIC') + case _ as kind: + assert kind is FunctionKind.CALLABLE, f"unknown kind: {kind!r}" if self.coexist: flags.append('METH_COEXIST') return '|'.join(flags) @@ -3888,7 +3908,7 @@ def correct_name_for_self( if f.cls: return "PyObject *", "self" return "PyObject *", "module" - if f.kind == STATIC_METHOD: + if f.kind is STATIC_METHOD: return "void *", "null" if f.kind in (CLASS_METHOD, METHOD_NEW): return "PyTypeObject *", "type" @@ -3921,9 +3941,8 @@ def pre_render(self): self.type = self.specified_type or self.type or default_type kind = self.function.kind - new_or_init = kind in (METHOD_NEW, METHOD_INIT) - if (kind == STATIC_METHOD) or new_or_init: + if kind is STATIC_METHOD or kind.new_or_init: self.show_in_signature = False # tp_new (METHOD_NEW) functions are of type newfunc: @@ -3973,7 +3992,7 @@ def render(self, parameter, data): parameter is a clinic.Parameter instance. data is a CRenderData instance. """ - if self.function.kind == STATIC_METHOD: + if self.function.kind is STATIC_METHOD: return self._render_self(parameter, data) @@ -3992,8 +4011,8 @@ def set_template_dict(self, template_dict): kind = self.function.kind cls = self.function.cls - if ((kind in (METHOD_NEW, METHOD_INIT)) and cls and cls.typedef): - if kind == METHOD_NEW: + if kind.new_or_init and cls and cls.typedef: + if kind is METHOD_NEW: type_check = ( '({0} == base_tp || {0}->tp_init == base_tp->tp_init)' ).format(self.name) @@ -4337,7 +4356,7 @@ class DSLParser: parameter_state: int seen_positional_with_default: bool indent: IndentStack - kind: str + kind: FunctionKind coexist: bool parameter_continuation: str preserve_output: bool @@ -4626,7 +4645,7 @@ def state_modulename_name(self, line: str | None) -> None: function_name = fields.pop() module, cls = self.clinic._module_and_class(fields) - if not (existing_function.kind == self.kind and existing_function.coexist == self.coexist): + if not (existing_function.kind is self.kind and existing_function.coexist == self.coexist): fail("'kind' of function and cloned function don't match! (@classmethod/@staticmethod/@coexist)") function = existing_function.copy( name=function_name, full_name=full_name, module=module, @@ -4679,11 +4698,11 @@ def state_modulename_name(self, line: str | None) -> None: fail(f"{fields[-1]} is a special method and cannot be converted to Argument Clinic! (Yet.)") if fields[-1] == '__new__': - if (self.kind != CLASS_METHOD) or (not cls): + if (self.kind is not CLASS_METHOD) or (not cls): fail("__new__ must be a class method!") self.kind = METHOD_NEW elif fields[-1] == '__init__': - if (self.kind != CALLABLE) or (not cls): + if (self.kind is not CALLABLE) or (not cls): fail("__init__ must be a normal method, not a class or static method!") self.kind = METHOD_INIT if not return_converter: @@ -5203,7 +5222,7 @@ def state_function_docstring(self, line): def format_docstring(self): f = self.function - new_or_init = f.kind in (METHOD_NEW, METHOD_INIT) + new_or_init = f.kind.new_or_init if new_or_init and not f.docstring: # don't render a docstring at all, no signature, nothing. return f.docstring From c15f57230f9c432c41f31672bb663853f283209b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 13 Jul 2023 17:27:35 -0700 Subject: [PATCH 27/36] gh-106529: Split FOR_ITER_{LIST,TUPLE} into uops (#106696) Also rename `_ITER_EXHAUSTED_XXX` to `_IS_ITER_EXHAUSTED_XXX` to make it clear this is a test. --- Include/internal/pycore_opcode_metadata.h | 26 +++-- Lib/test/test_capi/test_misc.py | 47 +++++++- Python/bytecodes.c | 129 ++++++++++++++++------ Python/executor_cases.c.h | 76 ++++++++++++- Python/generated_cases.c.h | 125 +++++++++++++-------- Python/optimizer.c | 87 +++++++++++---- 6 files changed, 378 insertions(+), 112 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index c88640777e3fb0..e94732b64384b5 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -39,12 +39,18 @@ #define _GUARD_TYPE_VERSION 317 #define _CHECK_MANAGED_OBJECT_HAS_VALUES 318 #define IS_NONE 319 -#define _ITER_CHECK_RANGE 320 -#define _ITER_EXHAUSTED_RANGE 321 -#define _ITER_NEXT_RANGE 322 -#define _POP_JUMP_IF_FALSE 323 -#define _POP_JUMP_IF_TRUE 324 -#define JUMP_TO_TOP 325 +#define _ITER_CHECK_LIST 320 +#define _IS_ITER_EXHAUSTED_LIST 321 +#define _ITER_NEXT_LIST 322 +#define _ITER_CHECK_TUPLE 323 +#define _IS_ITER_EXHAUSTED_TUPLE 324 +#define _ITER_NEXT_TUPLE 325 +#define _ITER_CHECK_RANGE 326 +#define _IS_ITER_EXHAUSTED_RANGE 327 +#define _ITER_NEXT_RANGE 328 +#define _POP_JUMP_IF_FALSE 329 +#define _POP_JUMP_IF_TRUE 330 +#define JUMP_TO_TOP 331 #ifndef NEED_OPCODE_METADATA extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); @@ -1323,8 +1329,14 @@ const char * const _PyOpcode_uop_name[512] = { [_GUARD_TYPE_VERSION] = "_GUARD_TYPE_VERSION", [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", [IS_NONE] = "IS_NONE", + [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", + [_IS_ITER_EXHAUSTED_LIST] = "_IS_ITER_EXHAUSTED_LIST", + [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", + [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", + [_IS_ITER_EXHAUSTED_TUPLE] = "_IS_ITER_EXHAUSTED_TUPLE", + [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", - [_ITER_EXHAUSTED_RANGE] = "_ITER_EXHAUSTED_RANGE", + [_IS_ITER_EXHAUSTED_RANGE] = "_IS_ITER_EXHAUSTED_RANGE", [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index abdf7ed8976350..43c04463236a2a 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2590,7 +2590,6 @@ def testfunc(n): for i in range(n): total += i return total - # import dis; dis.dis(testfunc) opt = _testinternalcapi.get_uop_optimizer() with temporary_optimizer(opt): @@ -2602,7 +2601,51 @@ def testfunc(n): # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") uops = {opname for opname, _ in ex} - self.assertIn("_ITER_EXHAUSTED_RANGE", uops) + self.assertIn("_IS_ITER_EXHAUSTED_RANGE", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_for_iter_list(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = list(range(10)) + total = testfunc(a) + self.assertEqual(total, 45) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _ in ex} + self.assertIn("_IS_ITER_EXHAUSTED_LIST", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + + def test_for_iter_tuple(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = tuple(range(10)) + total = testfunc(a) + self.assertEqual(total, 45) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _ in ex} + self.assertIn("_IS_ITER_EXHAUSTED_TUPLE", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 9aeaf483258473..ec99213987840b 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -17,6 +17,7 @@ #include "pycore_object.h" // _PyObject_GC_TRACK() #include "pycore_moduleobject.h" // PyModuleObject #include "pycore_opcode.h" // EXTRA_CASES +#include "pycore_opcode_metadata.h" // uop names #include "pycore_opcode_utils.h" // MAKE_FUNCTION_* #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() #include "pycore_pystate.h" // _PyInterpreterState_GET() @@ -55,13 +56,14 @@ static PyObject *value, *value1, *value2, *left, *right, *res, *sum, *prod, *sub; static PyObject *container, *start, *stop, *v, *lhs, *rhs, *res2; static PyObject *list, *tuple, *dict, *owner, *set, *str, *tup, *map, *keys; -static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter; +static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter, *exhausted; static PyObject *aiter, *awaitable, *iterable, *w, *exc_value, *bc, *locals; static PyObject *orig, *excs, *update, *b, *fromlist, *level, *from; static PyObject **pieces, **values; static size_t jump; // Dummy variables for cache effects static uint16_t invert, counter, index, hint; +#define unused 0 // Used in a macro def, can't be static static uint32_t type_version; static PyObject * @@ -2406,52 +2408,108 @@ dummy_func( INSTRUMENTED_JUMP(here, target, PY_MONITORING_EVENT_BRANCH); } - inst(FOR_ITER_LIST, (unused/1, iter -- iter, next)) { + op(_ITER_CHECK_LIST, (iter -- iter)) { DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); + } + + op(_ITER_JUMP_LIST, (iter -- iter)) { _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); STAT_INC(FOR_ITER, hit); PyListObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyList_GET_SIZE(seq)) { - next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_list; // End of this instruction + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); } - it->it_seq = NULL; - Py_DECREF(seq); + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_list: - // Common case: no jump, leave it to the code generator } - inst(FOR_ITER_TUPLE, (unused/1, iter -- iter, next)) { + // Only used by Tier 2 + op(_IS_ITER_EXHAUSTED_LIST, (iter -- iter, exhausted)) { + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + } + + op(_ITER_NEXT_LIST, (iter -- iter, next)) { + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyList_GET_SIZE(seq)); + next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); + } + + macro(FOR_ITER_LIST) = + unused/1 + // Skip over the counter + _ITER_CHECK_LIST + + _ITER_JUMP_LIST + + _ITER_NEXT_LIST; + + op(_ITER_CHECK_TUPLE, (iter -- iter)) { + DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); + } + + op(_ITER_JUMP_TUPLE, (iter -- iter)) { _PyTupleIterObject *it = (_PyTupleIterObject *)iter; - DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER); + assert(Py_TYPE(iter) == &PyTupleIter_Type); STAT_INC(FOR_ITER, hit); PyTupleObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyTuple_GET_SIZE(seq)) { - next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_tuple; // End of this instruction + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); } - it->it_seq = NULL; - Py_DECREF(seq); + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_tuple: - // Common case: no jump, leave it to the code generator } + // Only used by Tier 2 + op(_IS_ITER_EXHAUSTED_TUPLE, (iter -- iter, exhausted)) { + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + } + + op(_ITER_NEXT_TUPLE, (iter -- iter, next)) { + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyTuple_GET_SIZE(seq)); + next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); + } + + macro(FOR_ITER_TUPLE) = + unused/1 + // Skip over the counter + _ITER_CHECK_TUPLE + + _ITER_JUMP_TUPLE + + _ITER_NEXT_TUPLE; + op(_ITER_CHECK_RANGE, (iter -- iter)) { _PyRangeIterObject *r = (_PyRangeIterObject *)iter; DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, FOR_ITER); @@ -2472,7 +2530,7 @@ dummy_func( } // Only used by Tier 2 - op(_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) { + op(_IS_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) { _PyRangeIterObject *r = (_PyRangeIterObject *)iter; assert(Py_TYPE(r) == &PyRangeIter_Type); exhausted = r->len <= 0 ? Py_True : Py_False; @@ -2490,7 +2548,10 @@ dummy_func( } macro(FOR_ITER_RANGE) = - unused/1 + _ITER_CHECK_RANGE + _ITER_JUMP_RANGE + _ITER_NEXT_RANGE; + unused/1 + // Skip over the counter + _ITER_CHECK_RANGE + + _ITER_JUMP_RANGE + + _ITER_NEXT_RANGE; inst(FOR_ITER_GEN, (unused/1, iter -- iter, unused)) { DEOPT_IF(tstate->interp->eval_frame, FOR_ITER); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index d07121c0b6fac3..5ae7bc158b569a 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1737,6 +1737,80 @@ break; } + case _ITER_CHECK_LIST: { + PyObject *iter = stack_pointer[-1]; + DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); + break; + } + + case _IS_ITER_EXHAUSTED_LIST: { + PyObject *iter = stack_pointer[-1]; + PyObject *exhausted; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + STACK_GROW(1); + stack_pointer[-1] = exhausted; + break; + } + + case _ITER_NEXT_LIST: { + PyObject *iter = stack_pointer[-1]; + PyObject *next; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyList_GET_SIZE(seq)); + next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); + STACK_GROW(1); + stack_pointer[-1] = next; + break; + } + + case _ITER_CHECK_TUPLE: { + PyObject *iter = stack_pointer[-1]; + DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); + break; + } + + case _IS_ITER_EXHAUSTED_TUPLE: { + PyObject *iter = stack_pointer[-1]; + PyObject *exhausted; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + STACK_GROW(1); + stack_pointer[-1] = exhausted; + break; + } + + case _ITER_NEXT_TUPLE: { + PyObject *iter = stack_pointer[-1]; + PyObject *next; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyTuple_GET_SIZE(seq)); + next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); + STACK_GROW(1); + stack_pointer[-1] = next; + break; + } + case _ITER_CHECK_RANGE: { PyObject *iter = stack_pointer[-1]; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; @@ -1744,7 +1818,7 @@ break; } - case _ITER_EXHAUSTED_RANGE: { + case _IS_ITER_EXHAUSTED_RANGE: { PyObject *iter = stack_pointer[-1]; PyObject *exhausted; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 4e55086c0f78e2..a1cdce8b1c1eab 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3048,61 +3048,96 @@ } TARGET(FOR_ITER_LIST) { - static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); - PyObject *iter = stack_pointer[-1]; - PyObject *next; - DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); - _PyListIterObject *it = (_PyListIterObject *)iter; - STAT_INC(FOR_ITER, hit); - PyListObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyList_GET_SIZE(seq)) { - next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_list; // End of this instruction + PyObject *_tmp_1; + PyObject *_tmp_2 = stack_pointer[-1]; + { + PyObject *iter = _tmp_2; + DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + STAT_INC(FOR_ITER, hit); + PyListObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); + } + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - it->it_seq = NULL; - Py_DECREF(seq); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + PyObject *next; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyList_GET_SIZE(seq)); + next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); + _tmp_2 = iter; + _tmp_1 = next; } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_list: - // Common case: no jump, leave it to the code generator - STACK_GROW(1); - stack_pointer[-1] = next; next_instr += 1; + STACK_GROW(1); + stack_pointer[-1] = _tmp_1; + stack_pointer[-2] = _tmp_2; DISPATCH(); } TARGET(FOR_ITER_TUPLE) { - PyObject *iter = stack_pointer[-1]; - PyObject *next; - _PyTupleIterObject *it = (_PyTupleIterObject *)iter; - DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER); - STAT_INC(FOR_ITER, hit); - PyTupleObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyTuple_GET_SIZE(seq)) { - next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_tuple; // End of this instruction + PyObject *_tmp_1; + PyObject *_tmp_2 = stack_pointer[-1]; + { + PyObject *iter = _tmp_2; + DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + STAT_INC(FOR_ITER, hit); + PyTupleObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); + } + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - it->it_seq = NULL; - Py_DECREF(seq); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + PyObject *next; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyTuple_GET_SIZE(seq)); + next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); + _tmp_2 = iter; + _tmp_1 = next; } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_tuple: - // Common case: no jump, leave it to the code generator - STACK_GROW(1); - stack_pointer[-1] = next; next_instr += 1; + STACK_GROW(1); + stack_pointer[-1] = _tmp_1; + stack_pointer[-2] = _tmp_2; DISPATCH(); } diff --git a/Python/optimizer.c b/Python/optimizer.c index abd2351f6b78bd..289b202f806ae1 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -378,6 +378,7 @@ translate_bytecode_to_trace( _Py_CODEUNIT *initial_instr = instr; int trace_length = 0; int max_length = buffer_size; + int reserved = 0; #ifdef Py_DEBUG char *uop_debug = Py_GETENV("PYTHONUOPSDEBUG"); @@ -385,6 +386,9 @@ translate_bytecode_to_trace( if (uop_debug != NULL && *uop_debug >= '0') { lltrace = *uop_debug - '0'; // TODO: Parse an int and all that } +#endif + +#ifdef Py_DEBUG #define DPRINTF(level, ...) \ if (lltrace >= (level)) { fprintf(stderr, __VA_ARGS__); } #else @@ -397,6 +401,8 @@ translate_bytecode_to_trace( uop_name(OPCODE), \ (uint64_t)(OPERAND)); \ assert(trace_length < max_length); \ + assert(reserved > 0); \ + reserved--; \ trace[trace_length].opcode = (OPCODE); \ trace[trace_length].operand = (OPERAND); \ trace_length++; @@ -409,9 +415,23 @@ translate_bytecode_to_trace( (INDEX), \ uop_name(OPCODE), \ (uint64_t)(OPERAND)); \ + assert(reserved > 0); \ + reserved--; \ trace[(INDEX)].opcode = (OPCODE); \ trace[(INDEX)].operand = (OPERAND); +// Reserve space for n uops +#define RESERVE_RAW(n, opname) \ + if (trace_length + (n) > max_length) { \ + DPRINTF(2, "No room for %s (need %d, got %d)\n", \ + (opname), (n), max_length - trace_length); \ + goto done; \ + } \ + reserved = (n); // Keep ADD_TO_TRACE / ADD_TO_STUB honest + +// Reserve space for main+stub uops, plus 2 for SAVE_IP and EXIT_TRACE +#define RESERVE(main, stub) RESERVE_RAW((main) + (stub) + 2, uop_name(opcode)) + DPRINTF(4, "Optimizing %s (%s:%d) at byte offset %ld\n", PyUnicode_AsUTF8(code->co_qualname), @@ -420,16 +440,20 @@ translate_bytecode_to_trace( 2 * INSTR_IP(initial_instr, code)); for (;;) { + RESERVE_RAW(2, "epilogue"); // Always need space for SAVE_IP and EXIT_TRACE ADD_TO_TRACE(SAVE_IP, INSTR_IP(instr, code)); + int opcode = instr->op.code; int oparg = instr->op.arg; int extras = 0; + while (opcode == EXTENDED_ARG) { instr++; extras += 1; opcode = instr->op.code; oparg = (oparg << 8) | instr->op.arg; } + if (opcode == ENTER_EXECUTOR) { _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; @@ -437,17 +461,14 @@ translate_bytecode_to_trace( DPRINTF(2, " * ENTER_EXECUTOR -> %s\n", _PyOpcode_OpName[opcode]); oparg = (oparg & 0xffffff00) | executor->vm_data.oparg; } + switch (opcode) { case POP_JUMP_IF_FALSE: case POP_JUMP_IF_TRUE: { // Assume jump unlikely (TODO: handle jump likely case) - // Reserve 5 entries (1 here, 2 stub, plus SAVE_IP + EXIT_TRACE) - if (trace_length + 5 > max_length) { - DPRINTF(1, "Ran out of space for POP_JUMP_IF_FALSE\n"); - goto done; - } + RESERVE(1, 2); _Py_CODEUNIT *target_instr = instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]] + oparg; max_length -= 2; // Really the start of the stubs @@ -461,9 +482,8 @@ translate_bytecode_to_trace( case JUMP_BACKWARD: { - if (instr + 2 - oparg == initial_instr - && trace_length + 3 <= max_length) - { + if (instr + 2 - oparg == initial_instr) { + RESERVE(1, 0); ADD_TO_TRACE(JUMP_TO_TOP, 0); } else { @@ -474,26 +494,45 @@ translate_bytecode_to_trace( case JUMP_FORWARD: { + RESERVE(0, 0); // This will emit two SAVE_IP instructions; leave it to the optimizer instr += oparg; break; } + case FOR_ITER_LIST: + case FOR_ITER_TUPLE: case FOR_ITER_RANGE: { - // Assume jump unlikely (can a for-loop exit be likely?) - // Reserve 9 entries (4 here, 3 stub, plus SAVE_IP + EXIT_TRACE) - if (trace_length + 9 > max_length) { - DPRINTF(1, "Ran out of space for FOR_ITER_RANGE\n"); - goto done; + RESERVE(4, 3); + int check_op, exhausted_op, next_op; + switch (opcode) { + case FOR_ITER_LIST: + check_op = _ITER_CHECK_LIST; + exhausted_op = _IS_ITER_EXHAUSTED_LIST; + next_op = _ITER_NEXT_LIST; + break; + case FOR_ITER_TUPLE: + check_op = _ITER_CHECK_TUPLE; + exhausted_op = _IS_ITER_EXHAUSTED_TUPLE; + next_op = _ITER_NEXT_TUPLE; + break; + case FOR_ITER_RANGE: + check_op = _ITER_CHECK_RANGE; + exhausted_op = _IS_ITER_EXHAUSTED_RANGE; + next_op = _ITER_NEXT_RANGE; + break; + default: + Py_UNREACHABLE(); } + // Assume jump unlikely (can a for-loop exit be likely?) _Py_CODEUNIT *target_instr = // +1 at the end skips over END_FOR instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]] + oparg + 1; max_length -= 3; // Really the start of the stubs - ADD_TO_TRACE(_ITER_CHECK_RANGE, 0); - ADD_TO_TRACE(_ITER_EXHAUSTED_RANGE, 0); + ADD_TO_TRACE(check_op, 0); + ADD_TO_TRACE(exhausted_op, 0); ADD_TO_TRACE(_POP_JUMP_IF_TRUE, max_length); - ADD_TO_TRACE(_ITER_NEXT_RANGE, 0); + ADD_TO_TRACE(next_op, 0); ADD_TO_STUB(max_length + 0, POP_TOP, 0); ADD_TO_STUB(max_length + 1, SAVE_IP, INSTR_IP(target_instr, code)); @@ -507,10 +546,7 @@ translate_bytecode_to_trace( if (expansion->nuops > 0) { // Reserve space for nuops (+ SAVE_IP + EXIT_TRACE) int nuops = expansion->nuops; - if (trace_length + nuops + 2 > max_length) { - DPRINTF(1, "Ran out of space for %s\n", uop_name(opcode)); - goto done; - } + RESERVE(nuops, 0); for (int i = 0; i < nuops; i++) { uint64_t operand; int offset = expansion->uops[i].offset; @@ -556,12 +592,14 @@ translate_bytecode_to_trace( } DPRINTF(2, "Unsupported opcode %s\n", uop_name(opcode)); goto done; // Break out of loop - } - } + } // End default + + } // End switch (opcode) + instr++; // Add cache size for opcode instr += _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; - } + } // End for (;;) done: // Skip short traces like SAVE_IP, LOAD_FAST, SAVE_IP, EXIT_TRACE @@ -610,6 +648,9 @@ translate_bytecode_to_trace( } return 0; +#undef RESERVE +#undef RESERVE_RAW +#undef INSTR_IP #undef ADD_TO_TRACE #undef DPRINTF } From 656c5bc685afb1d244c111a642b4ce420e673415 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Fri, 14 Jul 2023 09:55:49 +0300 Subject: [PATCH 28/36] gh-105626: Change the default return value of `HTTPConnection.get_proxy_response_headers` (#105628) --- Doc/library/http.client.rst | 2 +- Lib/http/client.py | 5 ++--- Lib/test/test_httplib.py | 13 +++++++++++++ .../2023-06-10-12-20-17.gh-issue-105626.XyZein.rst | 3 +++ 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-10-12-20-17.gh-issue-105626.XyZein.rst diff --git a/Doc/library/http.client.rst b/Doc/library/http.client.rst index 45291933d635b9..b9ceab699cef63 100644 --- a/Doc/library/http.client.rst +++ b/Doc/library/http.client.rst @@ -390,7 +390,7 @@ HTTPConnection Objects Returns a dictionary with the headers of the response received from the proxy server to the CONNECT request. - If the CONNECT request was not sent, the method returns an empty dictionary. + If the CONNECT request was not sent, the method returns ``None``. .. versionadded:: 3.12 diff --git a/Lib/http/client.py b/Lib/http/client.py index 3d98e4eb54bb45..b35b1d6368aae7 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -970,13 +970,12 @@ def get_proxy_response_headers(self): received from the proxy server to the CONNECT request sent to set the tunnel. - If the CONNECT request was not sent, the method returns - an empty dictionary. + If the CONNECT request was not sent, the method returns None. """ return ( _parse_header_lines(self._raw_proxy_headers) if self._raw_proxy_headers is not None - else {} + else None ) def connect(self): diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 8955d45fa93dd4..fe8105ee2bb3fa 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -2404,6 +2404,19 @@ def test_proxy_response_headers(self): headers = self.conn.get_proxy_response_headers() self.assertIn(expected_header, headers.items()) + def test_no_proxy_response_headers(self): + expected_header = ('X-Dummy', '1') + response_text = ( + 'HTTP/1.0 200 OK\r\n' + '{0}\r\n\r\n'.format(':'.join(expected_header)) + ) + + self.conn._create_connection = self._create_connection(response_text) + + self.conn.request('PUT', '/', '') + headers = self.conn.get_proxy_response_headers() + self.assertIsNone(headers) + def test_tunnel_leak(self): sock = None diff --git a/Misc/NEWS.d/next/Library/2023-06-10-12-20-17.gh-issue-105626.XyZein.rst b/Misc/NEWS.d/next/Library/2023-06-10-12-20-17.gh-issue-105626.XyZein.rst new file mode 100644 index 00000000000000..2a48361fa596c9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-10-12-20-17.gh-issue-105626.XyZein.rst @@ -0,0 +1,3 @@ +Change the default return value of +:meth:`http.client.HTTPConnection.get_proxy_response_headers` to be ``None`` +and not ``{}``. From 642cc0e59cd80850d3dc63c2ed459a894ae84fb2 Mon Sep 17 00:00:00 2001 From: Grant Ramsay Date: Fri, 14 Jul 2023 19:10:54 +1200 Subject: [PATCH 29/36] gh-105293: Do not call SSL_CTX_set_session_id_context on client side SSL context (#105295) * gh-105293: Do not call SSL_CTX_set_session_id_context on client side SSL context Openssl states this is a "server side only" operation. Calling this on a client side socket can result in unexpected behavior * Add news entry on SSL "set session id context" changes --- .../2023-07-14-14-53-58.gh-issue-105293.kimf_i.rst | 2 ++ Modules/_ssl.c | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-07-14-14-53-58.gh-issue-105293.kimf_i.rst diff --git a/Misc/NEWS.d/next/Library/2023-07-14-14-53-58.gh-issue-105293.kimf_i.rst b/Misc/NEWS.d/next/Library/2023-07-14-14-53-58.gh-issue-105293.kimf_i.rst new file mode 100644 index 00000000000000..c263c8524aa962 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-07-14-14-53-58.gh-issue-105293.kimf_i.rst @@ -0,0 +1,2 @@ +Remove call to ``SSL_CTX_set_session_id_context`` during client side context +creation in the :mod:`ssl` module. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index df1496925f678e..571de331e92cd9 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -847,6 +847,15 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, _setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__); return NULL; } + + if (socket_type == PY_SSL_SERVER) { +#define SID_CTX "Python" + /* Set the session id context (server-side only) */ + SSL_set_session_id_context(self->ssl, (const unsigned char *) SID_CTX, + sizeof(SID_CTX)); +#undef SID_CTX + } + /* bpo43522 and OpenSSL < 1.1.1l: copy hostflags manually */ #if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION < 0x101010cf X509_VERIFY_PARAM *ssl_params = SSL_get0_param(self->ssl); @@ -3186,11 +3195,6 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version) usage for no cost at all. */ SSL_CTX_set_mode(self->ctx, SSL_MODE_RELEASE_BUFFERS); -#define SID_CTX "Python" - SSL_CTX_set_session_id_context(self->ctx, (const unsigned char *) SID_CTX, - sizeof(SID_CTX)); -#undef SID_CTX - params = SSL_CTX_get0_param(self->ctx); /* Improve trust chain building when cross-signed intermediate certificates are present. See https://bugs.python.org/issue23476. */ From 2dd504975e432ad645dcfaeb0e49c5311fb9aba3 Mon Sep 17 00:00:00 2001 From: Charlie Zhao Date: Fri, 14 Jul 2023 15:38:03 +0800 Subject: [PATCH 30/36] gh-106446: Fix failed doctest in stdtypes (#106447) --------- Co-authored-by: Terry Jan Reedy --- Doc/library/stdtypes.rst | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 8e049d9645c0b6..fd51b1187576b1 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -3953,7 +3953,7 @@ copying. >>> m = memoryview(bytearray(b'abc')) >>> mm = m.toreadonly() >>> mm.tolist() - [89, 98, 99] + [97, 98, 99] >>> mm[0] = 42 Traceback (most recent call last): File "", line 1, in @@ -4009,6 +4009,7 @@ copying. :mod:`struct` syntax. One of the formats must be a byte format ('B', 'b' or 'c'). The byte length of the result must be the same as the original length. + Note that all byte lengths may depend on the operating system. Cast 1D/long to 1D/unsigned bytes:: @@ -4039,8 +4040,8 @@ copying. >>> x = memoryview(b) >>> x[0] = b'a' Traceback (most recent call last): - File "", line 1, in - ValueError: memoryview: invalid value for format "B" + ... + TypeError: memoryview: invalid type for format 'B' >>> y = x.cast('c') >>> y[0] = b'a' >>> b @@ -4789,10 +4790,10 @@ An example of dictionary view usage:: >>> # set operations >>> keys & {'eggs', 'bacon', 'salad'} {'bacon'} - >>> keys ^ {'sausage', 'juice'} - {'juice', 'sausage', 'bacon', 'spam'} - >>> keys | ['juice', 'juice', 'juice'] - {'juice', 'sausage', 'bacon', 'spam', 'eggs'} + >>> keys ^ {'sausage', 'juice'} == {'juice', 'sausage', 'bacon', 'spam'} + True + >>> keys | ['juice', 'juice', 'juice'] == {'bacon', 'spam', 'juice'} + True >>> # get back a read-only proxy for the original dictionary >>> values.mapping @@ -4999,8 +5000,8 @@ exception to disallow mistakes like ``dict[str][str]``:: >>> dict[str][str] Traceback (most recent call last): - File "", line 1, in - TypeError: There are no type variables left in dict[str] + ... + TypeError: dict[str] is not a generic class However, such expressions are valid when :ref:`type variables ` are used. The index must have as many elements as there are type variable items @@ -5206,13 +5207,15 @@ enables cleaner type hinting syntax compared to :data:`typing.Union`. >>> isinstance("", int | str) True - However, union objects containing :ref:`parameterized generics - ` cannot be used:: + However, :ref:`parameterized generics ` in + union objects cannot be checked:: - >>> isinstance(1, int | list[int]) + >>> isinstance(1, int | list[int]) # short-circuit evaluation + True + >>> isinstance([1], int | list[int]) Traceback (most recent call last): - File "", line 1, in - TypeError: isinstance() argument 2 cannot contain a parameterized generic + ... + TypeError: isinstance() argument 2 cannot be a parameterized generic The user-exposed type for the union object can be accessed from :data:`types.UnionType` and used for :func:`isinstance` checks. An object cannot be @@ -5515,7 +5518,7 @@ types, where they are relevant. Some of these are not reported by the definition order. Example:: >>> int.__subclasses__() - [] + [, , , ] .. _int_max_str_digits: @@ -5551,7 +5554,7 @@ When an operation would exceed the limit, a :exc:`ValueError` is raised: >>> _ = int('2' * 5432) Traceback (most recent call last): ... - ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 5432 digits; use sys.set_int_max_str_digits() to increase the limit. + ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 5432 digits; use sys.set_int_max_str_digits() to increase the limit >>> i = int('2' * 4300) >>> len(str(i)) 4300 @@ -5559,7 +5562,7 @@ When an operation would exceed the limit, a :exc:`ValueError` is raised: >>> len(str(i_squared)) Traceback (most recent call last): ... - ValueError: Exceeds the limit (4300 digits) for integer string conversion: value has 8599 digits; use sys.set_int_max_str_digits() to increase the limit. + ValueError: Exceeds the limit (4300 digits) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit >>> len(hex(i_squared)) 7144 >>> assert int(hex(i_squared), base=16) == i*i # Hexadecimal is unlimited. From ea05f4d506f4b99a14139a601f1e6a3add9dc28f Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Thu, 13 Jul 2023 07:58:55 -0400 Subject: [PATCH 31/36] Fix check --- Lib/_opcode_metadata.py | 72 ----------------------------------------- 1 file changed, 72 deletions(-) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index fd8ecdb5c980f3..8c52e7a01167f5 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -31,75 +31,3 @@ "STORE_SUBSCR_DICT", "STORE_SUBSCR_LIST_INT", ], - "SEND": [ - "SEND_GEN", - ], - "UNPACK_SEQUENCE": [ - "UNPACK_SEQUENCE_TWO_TUPLE", - "UNPACK_SEQUENCE_TUPLE", - "UNPACK_SEQUENCE_LIST", - ], - "STORE_ATTR": [ - "STORE_ATTR_INSTANCE_VALUE", - "STORE_ATTR_SLOT", - "STORE_ATTR_WITH_HINT", - ], - "LOAD_GLOBAL": [ - "LOAD_GLOBAL_MODULE", - "LOAD_GLOBAL_BUILTIN", - ], - "LOAD_SUPER_ATTR": [ - "LOAD_SUPER_ATTR_ATTR", - "LOAD_SUPER_ATTR_METHOD", - ], - "LOAD_ATTR": [ - "LOAD_ATTR_INSTANCE_VALUE", - "LOAD_ATTR_MODULE", - "LOAD_ATTR_WITH_HINT", - "LOAD_ATTR_SLOT", - "LOAD_ATTR_CLASS", - "LOAD_ATTR_PROPERTY", - "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", - "LOAD_ATTR_METHOD_WITH_VALUES", - "LOAD_ATTR_METHOD_NO_DICT", - "LOAD_ATTR_METHOD_LAZY_DICT", - "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", - "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", - ], - "COMPARE_OP": [ - "COMPARE_OP_FLOAT", - "COMPARE_OP_INT", - "COMPARE_OP_STR", - ], - "FOR_ITER": [ - "FOR_ITER_LIST", - "FOR_ITER_TUPLE", - "FOR_ITER_RANGE", - "FOR_ITER_GEN", - ], - "CALL": [ - "CALL_BOUND_METHOD_EXACT_ARGS", - "CALL_PY_EXACT_ARGS", - "CALL_PY_WITH_DEFAULTS", - "CALL_NO_KW_TYPE_1", - "CALL_NO_KW_STR_1", - "CALL_NO_KW_TUPLE_1", - "CALL_BUILTIN_CLASS", - "CALL_NO_KW_BUILTIN_O", - "CALL_NO_KW_BUILTIN_FAST", - "CALL_BUILTIN_FAST_WITH_KEYWORDS", - "CALL_NO_KW_LEN", - "CALL_NO_KW_ISINSTANCE", - "CALL_NO_KW_LIST_APPEND", - "CALL_NO_KW_METHOD_DESCRIPTOR_O", - "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", - "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", - "CALL_NO_KW_ALLOC_AND_ENTER_INIT", - ], -} - -# An irregular case: -_specializations["BINARY_OP"].append("BINARY_OP_INPLACE_ADD_UNICODE") - -_specialized_instructions = [opcode for family in _specializations.values() for opcode in family] From eab197459d4ced8f564de27abaa874fab980e4f4 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Sat, 15 Jul 2023 12:27:36 -0400 Subject: [PATCH 32/36] Regen cases --- Lib/_opcode_metadata.py | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 8c52e7a01167f5..fd8ecdb5c980f3 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -31,3 +31,75 @@ "STORE_SUBSCR_DICT", "STORE_SUBSCR_LIST_INT", ], + "SEND": [ + "SEND_GEN", + ], + "UNPACK_SEQUENCE": [ + "UNPACK_SEQUENCE_TWO_TUPLE", + "UNPACK_SEQUENCE_TUPLE", + "UNPACK_SEQUENCE_LIST", + ], + "STORE_ATTR": [ + "STORE_ATTR_INSTANCE_VALUE", + "STORE_ATTR_SLOT", + "STORE_ATTR_WITH_HINT", + ], + "LOAD_GLOBAL": [ + "LOAD_GLOBAL_MODULE", + "LOAD_GLOBAL_BUILTIN", + ], + "LOAD_SUPER_ATTR": [ + "LOAD_SUPER_ATTR_ATTR", + "LOAD_SUPER_ATTR_METHOD", + ], + "LOAD_ATTR": [ + "LOAD_ATTR_INSTANCE_VALUE", + "LOAD_ATTR_MODULE", + "LOAD_ATTR_WITH_HINT", + "LOAD_ATTR_SLOT", + "LOAD_ATTR_CLASS", + "LOAD_ATTR_PROPERTY", + "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", + "LOAD_ATTR_METHOD_WITH_VALUES", + "LOAD_ATTR_METHOD_NO_DICT", + "LOAD_ATTR_METHOD_LAZY_DICT", + "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES", + "LOAD_ATTR_NONDESCRIPTOR_NO_DICT", + ], + "COMPARE_OP": [ + "COMPARE_OP_FLOAT", + "COMPARE_OP_INT", + "COMPARE_OP_STR", + ], + "FOR_ITER": [ + "FOR_ITER_LIST", + "FOR_ITER_TUPLE", + "FOR_ITER_RANGE", + "FOR_ITER_GEN", + ], + "CALL": [ + "CALL_BOUND_METHOD_EXACT_ARGS", + "CALL_PY_EXACT_ARGS", + "CALL_PY_WITH_DEFAULTS", + "CALL_NO_KW_TYPE_1", + "CALL_NO_KW_STR_1", + "CALL_NO_KW_TUPLE_1", + "CALL_BUILTIN_CLASS", + "CALL_NO_KW_BUILTIN_O", + "CALL_NO_KW_BUILTIN_FAST", + "CALL_BUILTIN_FAST_WITH_KEYWORDS", + "CALL_NO_KW_LEN", + "CALL_NO_KW_ISINSTANCE", + "CALL_NO_KW_LIST_APPEND", + "CALL_NO_KW_METHOD_DESCRIPTOR_O", + "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + "CALL_NO_KW_METHOD_DESCRIPTOR_NOARGS", + "CALL_NO_KW_METHOD_DESCRIPTOR_FAST", + "CALL_NO_KW_ALLOC_AND_ENTER_INIT", + ], +} + +# An irregular case: +_specializations["BINARY_OP"].append("BINARY_OP_INPLACE_ADD_UNICODE") + +_specialized_instructions = [opcode for family in _specializations.values() for opcode in family] From 1e6856498c3d1e69e035771f0a32cd6159314b94 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Sun, 16 Jul 2023 06:27:57 -0400 Subject: [PATCH 33/36] Check against family name when emitting cache size check, add family.name to map --- Tools/cases_generator/generate_cases.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 48c082a429e276..9f15c7cf01e8b1 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -438,7 +438,7 @@ def write(self, out: Formatter, tier: Tiers = TIER_ONE) -> None: """Write one instruction, sans prologue and epilogue.""" # Write a static assertion that a family's cache size is correct if family := self.family: - if self.name == family.members[0]: + if self.name == family.name: if cache_size := family.size: out.emit( f"static_assert({cache_size} == " @@ -831,7 +831,7 @@ def find_predictions(self) -> None: def map_families(self) -> None: """Link instruction names back to their family, if they have one.""" for family in self.families.values(): - for member in family.members: + for member in [family.name] + family.members: if member_instr := self.instrs.get(member): if member_instr.family not in (family, None): self.error( @@ -1535,7 +1535,7 @@ def write_macro(self, mac: MacroInstruction) -> None: if ( last_instr and (family := last_instr.family) - and mac.name == family.members[0] + and mac.name == family.name and (cache_size := family.size) ): self.out.emit( From 85cbbed86e5c8d821bffad026ac282e9203b2174 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Sun, 16 Jul 2023 06:38:36 -0400 Subject: [PATCH 34/36] update family syntax in test --- Tools/cases_generator/generate_cases.py | 2 ++ Tools/cases_generator/test_generator.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 9f15c7cf01e8b1..7cc82e33a1d348 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -770,6 +770,7 @@ def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None: match thing: case parser.InstDef(name=name): + breakpoint() if name in self.instrs: if not thing.override: raise psr.make_syntax_error( @@ -785,6 +786,7 @@ def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None: thing_first_token, ) self.instrs[name] = Instruction(thing) + breakpoint() instrs_idx[name] = len(self.everything) self.everything.append(thing) case parser.Macro(name): diff --git a/Tools/cases_generator/test_generator.py b/Tools/cases_generator/test_generator.py index da6558e4bb7f66..e44273429b7405 100644 --- a/Tools/cases_generator/test_generator.py +++ b/Tools/cases_generator/test_generator.py @@ -287,7 +287,7 @@ def test_macro_instruction(): inst(OP3, (unused/5, arg2, left, right -- res)) { res = op3(arg2, left, right); } - family(OP, INLINE_CACHE_ENTRIES_OP) = { OP, OP3 }; + family(OP, INLINE_CACHE_ENTRIES_OP) = { OP3 }; """ output = """ TARGET(OP1) { From 82a992724da385f4dcfbb209f429207e115465fc Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Sun, 16 Jul 2023 06:43:56 -0400 Subject: [PATCH 35/36] Regenerate cases --- Python/executor_cases.c.h | 14 ++++++++------ Python/generated_cases.c.h | 22 +++++++++++++--------- Tools/cases_generator/generate_cases.py | 2 -- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 5ae7bc158b569a..626baece814607 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -97,6 +97,7 @@ } case TO_BOOL: { + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; #if ENABLE_SPECIALIZATION @@ -182,7 +183,6 @@ } case TO_BOOL_ALWAYS_TRUE: { - static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; uint32_t version = (uint32_t)operand; @@ -329,6 +329,7 @@ } case BINARY_SUBSCR: { + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *res; @@ -439,7 +440,6 @@ } case BINARY_SUBSCR_DICT: { - static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *res; @@ -481,6 +481,7 @@ } case STORE_SUBSCR: { + static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *v = stack_pointer[-3]; @@ -532,7 +533,6 @@ } case STORE_SUBSCR_DICT: { - static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *value = stack_pointer[-3]; @@ -770,6 +770,7 @@ } case UNPACK_SEQUENCE: { + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; #if ENABLE_SPECIALIZATION _PyUnpackSequenceCache *cache = (_PyUnpackSequenceCache *)next_instr; @@ -791,7 +792,6 @@ } case UNPACK_SEQUENCE_TWO_TUPLE: { - static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; PyObject **values = stack_pointer - (1); DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); @@ -932,6 +932,7 @@ } case LOAD_GLOBAL: { + static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *null = NULL; PyObject *v; #if ENABLE_SPECIALIZATION @@ -1312,7 +1313,6 @@ } case LOAD_SUPER_ATTR_ATTR: { - static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self = stack_pointer[-1]; PyObject *class = stack_pointer[-2]; PyObject *global_super = stack_pointer[-3]; @@ -1370,6 +1370,7 @@ } case LOAD_ATTR: { + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *res2 = NULL; PyObject *res; @@ -1443,6 +1444,7 @@ } case COMPARE_OP: { + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -1473,7 +1475,6 @@ } case COMPARE_OP_FLOAT: { - static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -2026,6 +2027,7 @@ } case BINARY_OP: { + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *rhs = stack_pointer[-1]; PyObject *lhs = stack_pointer[-2]; PyObject *res; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index a1cdce8b1c1eab..68531dc074769e 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -229,6 +229,7 @@ TARGET(TO_BOOL) { PREDICTED(TO_BOOL); + static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; #if ENABLE_SPECIALIZATION @@ -320,7 +321,6 @@ } TARGET(TO_BOOL_ALWAYS_TRUE) { - static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); PyObject *value = stack_pointer[-1]; PyObject *res; uint32_t version = read_u32(&next_instr[1].cache); @@ -585,6 +585,7 @@ TARGET(BINARY_SUBSCR) { PREDICTED(BINARY_SUBSCR); + static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *res; @@ -698,7 +699,6 @@ } TARGET(BINARY_SUBSCR_DICT) { - static_assert(INLINE_CACHE_ENTRIES_BINARY_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *res; @@ -769,6 +769,7 @@ TARGET(STORE_SUBSCR) { PREDICTED(STORE_SUBSCR); + static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *container = stack_pointer[-2]; PyObject *v = stack_pointer[-3]; @@ -822,7 +823,6 @@ } TARGET(STORE_SUBSCR_DICT) { - static_assert(INLINE_CACHE_ENTRIES_STORE_SUBSCR == 1, "incorrect cache size"); PyObject *sub = stack_pointer[-1]; PyObject *dict = stack_pointer[-2]; PyObject *value = stack_pointer[-3]; @@ -1097,6 +1097,7 @@ TARGET(SEND) { PREDICTED(SEND); + static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v = stack_pointer[-1]; PyObject *receiver = stack_pointer[-2]; PyObject *retval; @@ -1152,7 +1153,6 @@ } TARGET(SEND_GEN) { - static_assert(INLINE_CACHE_ENTRIES_SEND == 1, "incorrect cache size"); PyObject *v = stack_pointer[-1]; PyObject *receiver = stack_pointer[-2]; DEOPT_IF(tstate->interp->eval_frame, SEND); @@ -1349,6 +1349,7 @@ TARGET(UNPACK_SEQUENCE) { PREDICTED(UNPACK_SEQUENCE); + static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; #if ENABLE_SPECIALIZATION _PyUnpackSequenceCache *cache = (_PyUnpackSequenceCache *)next_instr; @@ -1371,7 +1372,6 @@ } TARGET(UNPACK_SEQUENCE_TWO_TUPLE) { - static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq = stack_pointer[-1]; PyObject **values = stack_pointer - (1); DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); @@ -1434,6 +1434,7 @@ TARGET(STORE_ATTR) { PREDICTED(STORE_ATTR); + static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *v = stack_pointer[-2]; uint16_t counter = read_u16(&next_instr[0].cache); @@ -1601,6 +1602,7 @@ TARGET(LOAD_GLOBAL) { PREDICTED(LOAD_GLOBAL); + static_assert(INLINE_CACHE_ENTRIES_LOAD_GLOBAL == 4, "incorrect cache size"); PyObject *null = NULL; PyObject *v; #if ENABLE_SPECIALIZATION @@ -2058,6 +2060,7 @@ TARGET(LOAD_SUPER_ATTR) { PREDICTED(LOAD_SUPER_ATTR); + static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self = stack_pointer[-1]; PyObject *class = stack_pointer[-2]; PyObject *global_super = stack_pointer[-3]; @@ -2120,7 +2123,6 @@ } TARGET(LOAD_SUPER_ATTR_ATTR) { - static_assert(INLINE_CACHE_ENTRIES_LOAD_SUPER_ATTR == 1, "incorrect cache size"); PyObject *self = stack_pointer[-1]; PyObject *class = stack_pointer[-2]; PyObject *global_super = stack_pointer[-3]; @@ -2181,6 +2183,7 @@ TARGET(LOAD_ATTR) { PREDICTED(LOAD_ATTR); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *res2 = NULL; PyObject *res; @@ -2456,7 +2459,6 @@ } TARGET(STORE_ATTR_INSTANCE_VALUE) { - static_assert(INLINE_CACHE_ENTRIES_STORE_ATTR == 4, "incorrect cache size"); PyObject *owner = stack_pointer[-1]; PyObject *value = stack_pointer[-2]; uint32_t type_version = read_u32(&next_instr[1].cache); @@ -2552,6 +2554,7 @@ TARGET(COMPARE_OP) { PREDICTED(COMPARE_OP); + static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -2583,7 +2586,6 @@ } TARGET(COMPARE_OP_FLOAT) { - static_assert(INLINE_CACHE_ENTRIES_COMPARE_OP == 1, "incorrect cache size"); PyObject *right = stack_pointer[-1]; PyObject *left = stack_pointer[-2]; PyObject *res; @@ -2979,6 +2981,7 @@ TARGET(FOR_ITER) { PREDICTED(FOR_ITER); + static_assert(INLINE_CACHE_ENTRIES_FOR_ITER == 1, "incorrect cache size"); PyObject *iter = stack_pointer[-1]; PyObject *next; #if ENABLE_SPECIALIZATION @@ -3486,6 +3489,7 @@ TARGET(CALL) { PREDICTED(CALL); + static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject **args = (stack_pointer - oparg); PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; @@ -3580,7 +3584,6 @@ } TARGET(CALL_BOUND_METHOD_EXACT_ARGS) { - static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); PyObject *callable = stack_pointer[-(1 + oparg)]; PyObject *method = stack_pointer[-(2 + oparg)]; DEOPT_IF(method != NULL, CALL); @@ -4452,6 +4455,7 @@ TARGET(BINARY_OP) { PREDICTED(BINARY_OP); + static_assert(INLINE_CACHE_ENTRIES_BINARY_OP == 1, "incorrect cache size"); PyObject *rhs = stack_pointer[-1]; PyObject *lhs = stack_pointer[-2]; PyObject *res; diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 7cc82e33a1d348..9f15c7cf01e8b1 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -770,7 +770,6 @@ def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None: match thing: case parser.InstDef(name=name): - breakpoint() if name in self.instrs: if not thing.override: raise psr.make_syntax_error( @@ -786,7 +785,6 @@ def parse_file(self, filename: str, instrs_idx: dict[str, int]) -> None: thing_first_token, ) self.instrs[name] = Instruction(thing) - breakpoint() instrs_idx[name] = len(self.everything) self.everything.append(thing) case parser.Macro(name): From b05e24db423f4172ce1983ca90a08246f221b326 Mon Sep 17 00:00:00 2001 From: Kevin Diem Date: Sun, 16 Jul 2023 07:14:30 -0400 Subject: [PATCH 36/36] Check families for mac.name instead of last_instr.family --- Tools/cases_generator/generate_cases.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 9f15c7cf01e8b1..af8beb383e7178 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -1533,8 +1533,7 @@ def write_macro(self, mac: MacroInstruction) -> None: self.out.emit(f"next_instr += {cache_adjust};") if ( - last_instr - and (family := last_instr.family) + (family := self.families.get(mac.name)) and mac.name == family.name and (cache_size := family.size) ):