diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 73e77f4707cf92a..07cd1a505c51852 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1755,6 +1755,17 @@ iterations of the loop. .. versionadded:: 3.13 +.. opcode:: MATCH_CLASS_GET_OPT_ATTR (namei) + + Replaces ``STACK[-1]`` with ``getattr(STACK[-1], co_names[namei])`` and gets + optional attribute from match subject ``STACK[-2]``. + + Pop ``Stack[-1]``. If attribute is found, pushes it and ``True`` onto the stack. + Otherwise pushes ``None`` and ``False``. + + .. versionadded:: 3.16 + + .. opcode:: MATCH_CLASS (count) ``STACK[-1]`` is a tuple of keyword attribute names, ``STACK[-2]`` is the class diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index 15d3ad6f3a336b7..54bc722850ea265 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -304,6 +304,7 @@ Known values: Python 3.16a1 3704 (Replace DELETE_ATTR with PUSH_NULL; STORE_ATTR) Python 3.16a1 3705 (Add INTRINSIC_ADD_CONDITIONAL_ANNOTATION) Python 3.16a1 3706 (Add INTRINSIC_MATCH_CLASS_ISINSTANCE) + Python 3.16a1 3707 (Add MATCH_CLASS_GET_OPT_ATTR opcode) Python 3.17 will start with 3750 @@ -313,7 +314,7 @@ Known values: */ -#define PYC_MAGIC_NUMBER 3706 +#define PYC_MAGIC_NUMBER 3707 /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes (little-endian) and then appending b'\r\n'. */ #define PYC_MAGIC_NUMBER_TOKEN \ diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 38bccdd48c0f862..65410d48a98b1aa 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -384,6 +384,8 @@ int _PyOpcode_num_popped(int opcode, int oparg) { return 3 + (oparg - 1); case MATCH_CLASS: return 3; + case MATCH_CLASS_GET_OPT_ATTR: + return 1; case MATCH_KEYS: return 2; case MATCH_MAPPING: @@ -879,6 +881,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { return 1 + (oparg - 1); case MATCH_CLASS: return 1; + case MATCH_CLASS_GET_OPT_ATTR: + return 3; case MATCH_KEYS: return 3; case MATCH_MAPPING: @@ -1268,6 +1272,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [MAKE_FUNCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [MAP_ADD] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_CLASS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, + [MATCH_CLASS_GET_OPT_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, @@ -1489,6 +1494,7 @@ _PyOpcode_macro_expansion[256] = { [MAKE_FUNCTION] = { .nuops = 2, .uops = { { _MAKE_FUNCTION, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } }, [MAP_ADD] = { .nuops = 1, .uops = { { _MAP_ADD, OPARG_SIMPLE, 0 } } }, [MATCH_CLASS] = { .nuops = 4, .uops = { { _MATCH_CLASS, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 }, { _POP_TOP, OPARG_SIMPLE, 0 } } }, + [MATCH_CLASS_GET_OPT_ATTR] = { .nuops = 1, .uops = { { _MATCH_CLASS_GET_OPT_ATTR, OPARG_SIMPLE, 0 } } }, [MATCH_KEYS] = { .nuops = 1, .uops = { { _MATCH_KEYS, OPARG_SIMPLE, 0 } } }, [MATCH_MAPPING] = { .nuops = 1, .uops = { { _MATCH_MAPPING, OPARG_SIMPLE, 0 } } }, [MATCH_SEQUENCE] = { .nuops = 1, .uops = { { _MATCH_SEQUENCE, OPARG_SIMPLE, 0 } } }, @@ -1725,6 +1731,7 @@ const char *_PyOpcode_OpName[267] = { [MAKE_FUNCTION] = "MAKE_FUNCTION", [MAP_ADD] = "MAP_ADD", [MATCH_CLASS] = "MATCH_CLASS", + [MATCH_CLASS_GET_OPT_ATTR] = "MATCH_CLASS_GET_OPT_ATTR", [MATCH_KEYS] = "MATCH_KEYS", [MATCH_MAPPING] = "MATCH_MAPPING", [MATCH_SEQUENCE] = "MATCH_SEQUENCE", @@ -1827,7 +1834,6 @@ const uint8_t _PyOpcode_Caches[256] = { PyAPI_DATA(const uint8_t) _PyOpcode_Deopt[256]; #ifdef NEED_OPCODE_METADATA const uint8_t _PyOpcode_Deopt[256] = { - [117] = 117, [118] = 118, [119] = 119, [120] = 120, @@ -2020,6 +2026,7 @@ const uint8_t _PyOpcode_Deopt[256] = { [MAKE_FUNCTION] = MAKE_FUNCTION, [MAP_ADD] = MAP_ADD, [MATCH_CLASS] = MATCH_CLASS, + [MATCH_CLASS_GET_OPT_ATTR] = MATCH_CLASS_GET_OPT_ATTR, [MATCH_KEYS] = MATCH_KEYS, [MATCH_MAPPING] = MATCH_MAPPING, [MATCH_SEQUENCE] = MATCH_SEQUENCE, @@ -2088,7 +2095,6 @@ const uint8_t _PyOpcode_Deopt[256] = { #endif // NEED_OPCODE_METADATA #define EXTRA_CASES \ - case 117: \ case 118: \ case 119: \ case 120: \ diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 625c33a6640b853..a2ba515e753b35b 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -417,6 +417,7 @@ enum { #define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR #define _MAKE_CELL MAKE_CELL #define _MAP_ADD MAP_ADD +#define _MATCH_CLASS_GET_OPT_ATTR MATCH_CLASS_GET_OPT_ATTR #define _MATCH_KEYS MATCH_KEYS #define _MATCH_MAPPING MATCH_MAPPING #define _MATCH_SEQUENCE MATCH_SEQUENCE @@ -1223,6 +1224,7 @@ enum { _MAKE_WARM_r33, _MAP_ADD_r20, _MATCH_CLASS_r33, + _MATCH_CLASS_GET_OPT_ATTR_r13, _MATCH_KEYS_r23, _MATCH_MAPPING_r02, _MATCH_MAPPING_r12, @@ -1439,7 +1441,7 @@ enum { _WITH_EXCEPT_START_r33, _YIELD_VALUE_r11, }; -#define MAX_UOP_REGS_ID 1650 +#define MAX_UOP_REGS_ID 1651 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index f464effe23bc0b3..3b37cae37a4bb31 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -255,6 +255,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_IMPORT_FROM] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_IS_NONE] = HAS_ESCAPES_FLAG, [_GET_LEN] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_MATCH_CLASS_GET_OPT_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_MATCH_CLASS] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, [_MATCH_MAPPING] = 0, [_MATCH_SEQUENCE] = 0, @@ -2425,6 +2426,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { -1, -1, -1 }, }, }, + [_MATCH_CLASS_GET_OPT_ATTR] = { + .best = { 1, 1, 1, 1 }, + .entries = { + { -1, -1, -1 }, + { 3, 1, _MATCH_CLASS_GET_OPT_ATTR_r13 }, + { -1, -1, -1 }, + { -1, -1, -1 }, + }, + }, [_MATCH_CLASS] = { .best = { 3, 3, 3, 3 }, .entries = { @@ -4424,6 +4434,7 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_IMPORT_FROM_r12] = _IMPORT_FROM, [_IS_NONE_r11] = _IS_NONE, [_GET_LEN_r12] = _GET_LEN, + [_MATCH_CLASS_GET_OPT_ATTR_r13] = _MATCH_CLASS_GET_OPT_ATTR, [_MATCH_CLASS_r33] = _MATCH_CLASS, [_MATCH_MAPPING_r02] = _MATCH_MAPPING, [_MATCH_MAPPING_r12] = _MATCH_MAPPING, @@ -5873,6 +5884,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_MAP_ADD_r20] = "_MAP_ADD_r20", [_MATCH_CLASS] = "_MATCH_CLASS", [_MATCH_CLASS_r33] = "_MATCH_CLASS_r33", + [_MATCH_CLASS_GET_OPT_ATTR] = "_MATCH_CLASS_GET_OPT_ATTR", + [_MATCH_CLASS_GET_OPT_ATTR_r13] = "_MATCH_CLASS_GET_OPT_ATTR_r13", [_MATCH_KEYS] = "_MATCH_KEYS", [_MATCH_KEYS_r23] = "_MATCH_KEYS_r23", [_MATCH_MAPPING] = "_MATCH_MAPPING", @@ -6606,6 +6619,8 @@ int _PyUop_num_popped(int opcode, int oparg) return 1; case _GET_LEN: return 0; + case _MATCH_CLASS_GET_OPT_ATTR: + return 0; case _MATCH_CLASS: return 3; case _MATCH_MAPPING: diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index 11342ae451b9f6c..766733245ecec6d 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -106,27 +106,28 @@ extern "C" { #define MAKE_CELL 93 #define MAP_ADD 94 #define MATCH_CLASS 95 -#define POP_JUMP_IF_FALSE 96 -#define POP_JUMP_IF_NONE 97 -#define POP_JUMP_IF_NOT_NONE 98 -#define POP_JUMP_IF_TRUE 99 -#define RAISE_VARARGS 100 -#define RERAISE 101 -#define SEND 102 -#define SET_ADD 103 -#define SET_FUNCTION_ATTRIBUTE 104 -#define SET_UPDATE 105 -#define STORE_ATTR 106 -#define STORE_DEREF 107 -#define STORE_FAST 108 -#define STORE_FAST_LOAD_FAST 109 -#define STORE_FAST_STORE_FAST 110 -#define STORE_GLOBAL 111 -#define STORE_NAME 112 -#define SWAP 113 -#define UNPACK_EX 114 -#define UNPACK_SEQUENCE 115 -#define YIELD_VALUE 116 +#define MATCH_CLASS_GET_OPT_ATTR 96 +#define POP_JUMP_IF_FALSE 97 +#define POP_JUMP_IF_NONE 98 +#define POP_JUMP_IF_NOT_NONE 99 +#define POP_JUMP_IF_TRUE 100 +#define RAISE_VARARGS 101 +#define RERAISE 102 +#define SEND 103 +#define SET_ADD 104 +#define SET_FUNCTION_ATTRIBUTE 105 +#define SET_UPDATE 106 +#define STORE_ATTR 107 +#define STORE_DEREF 108 +#define STORE_FAST 109 +#define STORE_FAST_LOAD_FAST 110 +#define STORE_FAST_STORE_FAST 111 +#define STORE_GLOBAL 112 +#define STORE_NAME 113 +#define SWAP 114 +#define UNPACK_EX 115 +#define UNPACK_SEQUENCE 116 +#define YIELD_VALUE 117 #define RESUME 128 #define BINARY_OP_ADD_FLOAT 129 #define BINARY_OP_ADD_INT 130 diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index df92eae151d2488..e6b4f081a210134 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -326,27 +326,28 @@ MAKE_CELL=93, MAP_ADD=94, MATCH_CLASS=95, - POP_JUMP_IF_FALSE=96, - POP_JUMP_IF_NONE=97, - POP_JUMP_IF_NOT_NONE=98, - POP_JUMP_IF_TRUE=99, - RAISE_VARARGS=100, - RERAISE=101, - SEND=102, - SET_ADD=103, - SET_FUNCTION_ATTRIBUTE=104, - SET_UPDATE=105, - STORE_ATTR=106, - STORE_DEREF=107, - STORE_FAST=108, - STORE_FAST_LOAD_FAST=109, - STORE_FAST_STORE_FAST=110, - STORE_GLOBAL=111, - STORE_NAME=112, - SWAP=113, - UNPACK_EX=114, - UNPACK_SEQUENCE=115, - YIELD_VALUE=116, + MATCH_CLASS_GET_OPT_ATTR=96, + POP_JUMP_IF_FALSE=97, + POP_JUMP_IF_NONE=98, + POP_JUMP_IF_NOT_NONE=99, + POP_JUMP_IF_TRUE=100, + RAISE_VARARGS=101, + RERAISE=102, + SEND=103, + SET_ADD=104, + SET_FUNCTION_ATTRIBUTE=105, + SET_UPDATE=106, + STORE_ATTR=107, + STORE_DEREF=108, + STORE_FAST=109, + STORE_FAST_LOAD_FAST=110, + STORE_FAST_STORE_FAST=111, + STORE_GLOBAL=112, + STORE_NAME=113, + SWAP=114, + UNPACK_EX=115, + UNPACK_SEQUENCE=116, + YIELD_VALUE=117, INSTRUMENTED_END_FOR=233, INSTRUMENTED_POP_ITER=234, INSTRUMENTED_END_SEND=235, diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 8727352a31a7a05..01a24bb71d0e5e9 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -788,6 +788,14 @@ def test_static_swaps_match_mapping(self): self.assertNotInBytecode(code, "SWAP") def test_static_swaps_match_class(self): + swaps = { + "C(a=a, b=_, c=_)", + "C(a=a, b=_, c=c)", + "C(a=a, b=b, c=_)", + "C(a=a, b=b, c=c)", + "C(a=_, b=b, c=_)", + "C(a=_, b=b, c=c)", + } forms = [ "C({}, {}, {})", "C({}, {}, c={})", @@ -799,7 +807,12 @@ def test_static_swaps_match_class(self): pattern = form.format(a, b, c) with self.subTest(pattern): code = compile_pattern_with_fast_locals(pattern) - self.assertNotInBytecode(code, "SWAP") + if pattern in swaps: + # Swaps are expected here. Class patterns with only + # keyword sub-patterns are evaluated depth first. + self.assertInBytecode(code, "SWAP") + else: + self.assertNotInBytecode(code, "SWAP") def test_static_swaps_match_sequence(self): swaps = {"*_, b, c", "a, *_, c", "a, b, *_"} diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-00-08-18.gh-issue-138912.pisRAJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-00-08-18.gh-issue-138912.pisRAJ.rst new file mode 100644 index 000000000000000..be5e6dfd16acd2d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-00-08-18.gh-issue-138912.pisRAJ.rst @@ -0,0 +1,3 @@ +Add :opcode:`MATCH_CLASS_GET_OPT_ATTR` to improve the performance of +:keyword:`match` class patterns with only keyword sub-patterns by 10% to +30%. Patch by Marc Mueller. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index a04770bd83b176e..ce7c3f7f3518f50 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -10885,6 +10885,44 @@ DISPATCH(); } + TARGET(MATCH_CLASS_GET_OPT_ATTR) { + #if _Py_TAIL_CALL_INTERP + int opcode = MATCH_CLASS_GET_OPT_ATTR; + (void)(opcode); + #endif + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(MATCH_CLASS_GET_OPT_ATTR); + _PyStackRef subject; + _PyStackRef attr; + _PyStackRef res; + subject = stack_pointer[-1]; + PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); + assert(PyUnicode_CheckExact(name)); + PyObject *subject_o = PyStackRef_AsPyObjectBorrow(subject); + PyObject *attr_o; + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + (void)PyObject_GetOptionalAttr(subject_o, name, &attr_o); + _PyFrame_StackPointerInvalidate(frame); + if (attr_o) { + assert(!_PyErr_Occurred(tstate)); + attr = PyStackRef_FromPyObjectSteal(attr_o); + res = PyStackRef_True; + } else { + if (_PyErr_Occurred(tstate)) { + JUMP_TO_LABEL(error); + } + attr = PyStackRef_FromPyObjectSteal(Py_None); + res = PyStackRef_False; + } + stack_pointer[0] = attr; + stack_pointer[1] = res; + stack_pointer += 2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + DISPATCH(); + } + TARGET(MATCH_KEYS) { #if _Py_TAIL_CALL_INTERP int opcode = MATCH_KEYS; diff --git a/Modules/_testinternalcapi/test_targets.h b/Modules/_testinternalcapi/test_targets.h index 91b424773224e4b..1f5d2d1ce2ff3bd 100644 --- a/Modules/_testinternalcapi/test_targets.h +++ b/Modules/_testinternalcapi/test_targets.h @@ -96,6 +96,7 @@ static void *opcode_targets_table[256] = { &&TARGET_MAKE_CELL, &&TARGET_MAP_ADD, &&TARGET_MATCH_CLASS, + &&TARGET_MATCH_CLASS_GET_OPT_ATTR, &&TARGET_POP_JUMP_IF_FALSE, &&TARGET_POP_JUMP_IF_NONE, &&TARGET_POP_JUMP_IF_NOT_NONE, @@ -127,7 +128,6 @@ static void *opcode_targets_table[256] = { &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, - &&_unknown_opcode, &&TARGET_RESUME, &&TARGET_BINARY_OP_ADD_FLOAT, &&TARGET_BINARY_OP_ADD_INT, @@ -376,7 +376,7 @@ static void *opcode_tracing_targets_table[256] = { &&TARGET_TRACE_RECORD, &&TARGET_TRACE_RECORD, &&TARGET_TRACE_RECORD, - &&_unknown_opcode, + &&TARGET_TRACE_RECORD, &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, @@ -699,6 +699,7 @@ static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MAKE_CELL(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MAKE_FUNCTION(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MAP_ADD(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_CLASS(TAIL_CALL_PARAMS); +static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_CLASS_GET_OPT_ATTR(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_KEYS(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_MAPPING(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_SEQUENCE(TAIL_CALL_PARAMS); @@ -942,6 +943,7 @@ static py_tail_call_funcptr instruction_funcptr_handler_table[256] = { [MAKE_FUNCTION] = _TAIL_CALL_MAKE_FUNCTION, [MAP_ADD] = _TAIL_CALL_MAP_ADD, [MATCH_CLASS] = _TAIL_CALL_MATCH_CLASS, + [MATCH_CLASS_GET_OPT_ATTR] = _TAIL_CALL_MATCH_CLASS_GET_OPT_ATTR, [MATCH_KEYS] = _TAIL_CALL_MATCH_KEYS, [MATCH_MAPPING] = _TAIL_CALL_MATCH_MAPPING, [MATCH_SEQUENCE] = _TAIL_CALL_MATCH_SEQUENCE, @@ -1005,7 +1007,6 @@ static py_tail_call_funcptr instruction_funcptr_handler_table[256] = { [UNPACK_SEQUENCE_TWO_TUPLE] = _TAIL_CALL_UNPACK_SEQUENCE_TWO_TUPLE, [WITH_EXCEPT_START] = _TAIL_CALL_WITH_EXCEPT_START, [YIELD_VALUE] = _TAIL_CALL_YIELD_VALUE, - [117] = _TAIL_CALL_UNKNOWN_OPCODE, [118] = _TAIL_CALL_UNKNOWN_OPCODE, [119] = _TAIL_CALL_UNKNOWN_OPCODE, [120] = _TAIL_CALL_UNKNOWN_OPCODE, @@ -1200,6 +1201,7 @@ static py_tail_call_funcptr instruction_funcptr_tracing_table[256] = { [MAKE_FUNCTION] = _TAIL_CALL_TRACE_RECORD, [MAP_ADD] = _TAIL_CALL_TRACE_RECORD, [MATCH_CLASS] = _TAIL_CALL_TRACE_RECORD, + [MATCH_CLASS_GET_OPT_ATTR] = _TAIL_CALL_TRACE_RECORD, [MATCH_KEYS] = _TAIL_CALL_TRACE_RECORD, [MATCH_MAPPING] = _TAIL_CALL_TRACE_RECORD, [MATCH_SEQUENCE] = _TAIL_CALL_TRACE_RECORD, @@ -1263,7 +1265,6 @@ static py_tail_call_funcptr instruction_funcptr_tracing_table[256] = { [UNPACK_SEQUENCE_TWO_TUPLE] = _TAIL_CALL_TRACE_RECORD, [WITH_EXCEPT_START] = _TAIL_CALL_TRACE_RECORD, [YIELD_VALUE] = _TAIL_CALL_TRACE_RECORD, - [117] = _TAIL_CALL_UNKNOWN_OPCODE, [118] = _TAIL_CALL_UNKNOWN_OPCODE, [119] = _TAIL_CALL_UNKNOWN_OPCODE, [120] = _TAIL_CALL_UNKNOWN_OPCODE, diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index c82b791439a3fa1..de17c214aae3512 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -2,14 +2,14 @@ unsigned char M_test_frozenmain[] = { 227,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0, 0,0,0,0,0,243,188,0,0,0,128,0,0,0,90,0, - 77,7,69,0,112,0,90,0,77,7,69,4,112,1,89,2, + 77,7,69,0,113,0,90,0,77,7,69,4,113,1,89,2, 31,0,78,1,50,1,0,0,0,0,0,0,29,0,89,2, 31,0,78,2,89,0,76,6,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,50,2,0,0,0,0, 0,0,29,0,89,1,76,8,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,31,0,50,0,0,0, 0,0,0,0,78,3,42,26,0,0,0,0,0,0,0,0, - 0,0,112,5,78,6,67,0,0,0,65,24,0,0,112,6, + 0,0,113,5,78,6,67,0,0,0,65,24,0,0,113,6, 89,2,31,0,78,4,89,6,12,0,78,5,89,5,89,6, 42,26,0,0,0,0,0,0,0,0,0,0,12,0,48,4, 50,1,0,0,0,0,0,0,29,0,71,26,0,0,9,0, diff --git a/Python/bytecodes.c b/Python/bytecodes.c index fdfd5a0e6090464..4c823ca9635da83 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3683,6 +3683,23 @@ dummy_func( len = PyStackRef_FromPyObjectSteal(len_o); } + inst(MATCH_CLASS_GET_OPT_ATTR, (subject -- subject, attr, res)) { + PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); + assert(PyUnicode_CheckExact(name)); + PyObject *subject_o = PyStackRef_AsPyObjectBorrow(subject); + PyObject *attr_o; + (void)PyObject_GetOptionalAttr(subject_o, name, &attr_o); + if (attr_o) { + assert(!_PyErr_Occurred(tstate)); // Success! + attr = PyStackRef_FromPyObjectSteal(attr_o); + res = PyStackRef_True; + } else { + ERROR_IF(_PyErr_Occurred(tstate)); // Error! + attr = PyStackRef_FromPyObjectSteal(Py_None); // No attribute found! + res = PyStackRef_False; + } + } + op(_MATCH_CLASS, (subject, type, names -- attrs, s, tp, n)) { // Pop TOS and TOS1. Set TOS to a tuple of attributes on success, or // None on failure. diff --git a/Python/ceval.c b/Python/ceval.c index c1fbcb0611384c5..e4e51f6376eb4b4 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -534,6 +534,7 @@ PyObject* _PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs) { + assert(nargs > 0); if (!PyType_Check(type)) { const char *e = "class pattern must refer to a class"; _PyErr_Format(tstate, PyExc_TypeError, e); diff --git a/Python/codegen.c b/Python/codegen.c index 7143d9abc4255e8..ac68ece7447bf47 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -6222,6 +6222,61 @@ validate_kwd_attrs(compiler *c, asdl_identifier_seq *attrs, asdl_pattern_seq* pa return SUCCESS; } +static int +codegen_addop_name_match_class_attr(compiler *c, location loc, int opcode, + PyObject *name) +{ + // No name mangling for match attributes + Py_ssize_t arg = _PyCompile_DictAddObj(METADATA(c)->u_names, name); + if (arg < 0) { + return ERROR; + } + ADDOP_I(c, loc, opcode, arg); + return SUCCESS; +} + +static int +codegen_pattern_class_fast(compiler *c, pattern_ty p, pattern_context *pc) +{ + assert(p->kind == MatchClass_kind); + assert(!asdl_seq_LEN(p->v.MatchClass.patterns)); + asdl_identifier_seq *kwd_attrs = p->v.MatchClass.kwd_attrs; + asdl_pattern_seq *kwd_patterns = p->v.MatchClass.kwd_patterns; + Py_ssize_t nattrs = asdl_seq_LEN(kwd_attrs); + assert(nattrs > 0); + ADDOP_I(c, LOC(p), COPY, 1); + VISIT(c, expr, p->v.MatchClass.cls); + ADDOP_I(c, LOC(p), CALL_INTRINSIC_2, INTRINSIC_MATCH_CLASS_ISINSTANCE); + // TOS is now subject: + pc->on_top++; + RETURN_IF_ERROR(jump_to_fail_pop(c, LOC(p), pc, POP_JUMP_IF_FALSE)); + + Py_ssize_t i; + identifier name; + pattern_ty pattern; + for (i = 0; i < nattrs; i++) { + name = asdl_seq_GET(kwd_attrs, i); + RETURN_IF_ERROR(codegen_addop_name_match_class_attr(c, LOC(p), + MATCH_CLASS_GET_OPT_ATTR, name)); + // TOS is now attribute: + pc->on_top++; + RETURN_IF_ERROR(jump_to_fail_pop(c, LOC(p), pc, POP_JUMP_IF_FALSE)); + pc->on_top--; + + pattern = asdl_seq_GET(kwd_patterns, i); + if (WILDCARD_CHECK(pattern)) { + ADDOP(c, LOC(p), POP_TOP); + continue; + } + RETURN_IF_ERROR(codegen_pattern_subpattern(c, pattern, pc)); + } + + pc->on_top--; + // Success! POP subject: + ADDOP(c, LOC(p), POP_TOP); + return SUCCESS; +} + static int codegen_pattern_class(compiler *c, pattern_ty p, pattern_context *pc) { @@ -6251,6 +6306,9 @@ codegen_pattern_class(compiler *c, pattern_ty p, pattern_context *pc) ADDOP_I(c, LOC(p), CALL_INTRINSIC_2, INTRINSIC_MATCH_CLASS_ISINSTANCE); RETURN_IF_ERROR(jump_to_fail_pop(c, LOC(p), pc, POP_JUMP_IF_FALSE)); return SUCCESS; + } else if (nargs == 0) { + // Only keyword patterns + return codegen_pattern_class_fast(c, p, pc); } VISIT(c, expr, p->v.MatchClass.cls); PyObject *attr_names = PyTuple_New(nattrs); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index e3dc2e781a3bebd..e6625c262f8ac3a 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -14293,6 +14293,48 @@ break; } + case _MATCH_CLASS_GET_OPT_ATTR_r13: { + CHECK_CURRENT_CACHED_VALUES(1); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + _PyStackRef subject; + _PyStackRef attr; + _PyStackRef res; + _PyStackRef _stack_item_0 = _tos_cache0; + oparg = CURRENT_OPARG(); + subject = _stack_item_0; + PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); + assert(PyUnicode_CheckExact(name)); + PyObject *subject_o = PyStackRef_AsPyObjectBorrow(subject); + PyObject *attr_o; + stack_pointer[0] = subject; + stack_pointer += 1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + (void)PyObject_GetOptionalAttr(subject_o, name, &attr_o); + _PyFrame_StackPointerInvalidate(frame); + if (attr_o) { + assert(!_PyErr_Occurred(tstate)); + attr = PyStackRef_FromPyObjectSteal(attr_o); + res = PyStackRef_True; + } else { + if (_PyErr_Occurred(tstate)) { + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_ERROR(); + } + attr = PyStackRef_FromPyObjectSteal(Py_None); + res = PyStackRef_False; + } + _tos_cache2 = res; + _tos_cache1 = attr; + _tos_cache0 = subject; + SET_CURRENT_CACHED_VALUES(3); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + break; + } + case _MATCH_CLASS_r33: { CHECK_CURRENT_CACHED_VALUES(3); ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 48f76ccf59f8bc8..96d5e4b9c237629 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -10883,6 +10883,44 @@ DISPATCH(); } + TARGET(MATCH_CLASS_GET_OPT_ATTR) { + #if _Py_TAIL_CALL_INTERP + int opcode = MATCH_CLASS_GET_OPT_ATTR; + (void)(opcode); + #endif + frame->instr_ptr = next_instr; + next_instr += 1; + INSTRUCTION_STATS(MATCH_CLASS_GET_OPT_ATTR); + _PyStackRef subject; + _PyStackRef attr; + _PyStackRef res; + subject = stack_pointer[-1]; + PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); + assert(PyUnicode_CheckExact(name)); + PyObject *subject_o = PyStackRef_AsPyObjectBorrow(subject); + PyObject *attr_o; + _PyFrame_SetStackPointer(frame, stack_pointer); + _PyFrame_StackPointerValidate(frame); + (void)PyObject_GetOptionalAttr(subject_o, name, &attr_o); + _PyFrame_StackPointerInvalidate(frame); + if (attr_o) { + assert(!_PyErr_Occurred(tstate)); + attr = PyStackRef_FromPyObjectSteal(attr_o); + res = PyStackRef_True; + } else { + if (_PyErr_Occurred(tstate)) { + JUMP_TO_LABEL(error); + } + attr = PyStackRef_FromPyObjectSteal(Py_None); + res = PyStackRef_False; + } + stack_pointer[0] = attr; + stack_pointer[1] = res; + stack_pointer += 2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + DISPATCH(); + } + TARGET(MATCH_KEYS) { #if _Py_TAIL_CALL_INTERP int opcode = MATCH_KEYS; diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index 91b424773224e4b..1f5d2d1ce2ff3bd 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -96,6 +96,7 @@ static void *opcode_targets_table[256] = { &&TARGET_MAKE_CELL, &&TARGET_MAP_ADD, &&TARGET_MATCH_CLASS, + &&TARGET_MATCH_CLASS_GET_OPT_ATTR, &&TARGET_POP_JUMP_IF_FALSE, &&TARGET_POP_JUMP_IF_NONE, &&TARGET_POP_JUMP_IF_NOT_NONE, @@ -127,7 +128,6 @@ static void *opcode_targets_table[256] = { &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, - &&_unknown_opcode, &&TARGET_RESUME, &&TARGET_BINARY_OP_ADD_FLOAT, &&TARGET_BINARY_OP_ADD_INT, @@ -376,7 +376,7 @@ static void *opcode_tracing_targets_table[256] = { &&TARGET_TRACE_RECORD, &&TARGET_TRACE_RECORD, &&TARGET_TRACE_RECORD, - &&_unknown_opcode, + &&TARGET_TRACE_RECORD, &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, @@ -699,6 +699,7 @@ static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MAKE_CELL(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MAKE_FUNCTION(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MAP_ADD(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_CLASS(TAIL_CALL_PARAMS); +static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_CLASS_GET_OPT_ATTR(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_KEYS(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_MAPPING(TAIL_CALL_PARAMS); static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_MATCH_SEQUENCE(TAIL_CALL_PARAMS); @@ -942,6 +943,7 @@ static py_tail_call_funcptr instruction_funcptr_handler_table[256] = { [MAKE_FUNCTION] = _TAIL_CALL_MAKE_FUNCTION, [MAP_ADD] = _TAIL_CALL_MAP_ADD, [MATCH_CLASS] = _TAIL_CALL_MATCH_CLASS, + [MATCH_CLASS_GET_OPT_ATTR] = _TAIL_CALL_MATCH_CLASS_GET_OPT_ATTR, [MATCH_KEYS] = _TAIL_CALL_MATCH_KEYS, [MATCH_MAPPING] = _TAIL_CALL_MATCH_MAPPING, [MATCH_SEQUENCE] = _TAIL_CALL_MATCH_SEQUENCE, @@ -1005,7 +1007,6 @@ static py_tail_call_funcptr instruction_funcptr_handler_table[256] = { [UNPACK_SEQUENCE_TWO_TUPLE] = _TAIL_CALL_UNPACK_SEQUENCE_TWO_TUPLE, [WITH_EXCEPT_START] = _TAIL_CALL_WITH_EXCEPT_START, [YIELD_VALUE] = _TAIL_CALL_YIELD_VALUE, - [117] = _TAIL_CALL_UNKNOWN_OPCODE, [118] = _TAIL_CALL_UNKNOWN_OPCODE, [119] = _TAIL_CALL_UNKNOWN_OPCODE, [120] = _TAIL_CALL_UNKNOWN_OPCODE, @@ -1200,6 +1201,7 @@ static py_tail_call_funcptr instruction_funcptr_tracing_table[256] = { [MAKE_FUNCTION] = _TAIL_CALL_TRACE_RECORD, [MAP_ADD] = _TAIL_CALL_TRACE_RECORD, [MATCH_CLASS] = _TAIL_CALL_TRACE_RECORD, + [MATCH_CLASS_GET_OPT_ATTR] = _TAIL_CALL_TRACE_RECORD, [MATCH_KEYS] = _TAIL_CALL_TRACE_RECORD, [MATCH_MAPPING] = _TAIL_CALL_TRACE_RECORD, [MATCH_SEQUENCE] = _TAIL_CALL_TRACE_RECORD, @@ -1263,7 +1265,6 @@ static py_tail_call_funcptr instruction_funcptr_tracing_table[256] = { [UNPACK_SEQUENCE_TWO_TUPLE] = _TAIL_CALL_TRACE_RECORD, [WITH_EXCEPT_START] = _TAIL_CALL_TRACE_RECORD, [YIELD_VALUE] = _TAIL_CALL_TRACE_RECORD, - [117] = _TAIL_CALL_UNKNOWN_OPCODE, [118] = _TAIL_CALL_UNKNOWN_OPCODE, [119] = _TAIL_CALL_UNKNOWN_OPCODE, [120] = _TAIL_CALL_UNKNOWN_OPCODE, diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 21f275f27cafe04..646139bb51528de 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3507,6 +3507,19 @@ break; } + case _MATCH_CLASS_GET_OPT_ATTR: { + JitOptRef attr; + JitOptRef res; + attr = sym_new_not_null(ctx); + res = sym_new_not_null(ctx); + CHECK_STACK_BOUNDS(2); + stack_pointer[0] = attr; + stack_pointer[1] = res; + stack_pointer += 2; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + break; + } + case _MATCH_CLASS: { JitOptRef names; JitOptRef type;