diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 5cc0d68a24acee9..678a4e288c68227 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 get + optional attribute from match subject ``STACK[-2]``. + + Pop ``Stack[-1]``. If attribute is found, push it and ``True`` onto the stack. + Otherwise push ``None`` and ``False``. + + .. versionadded:: 3.16 + + .. opcode:: MATCH_CLASS (count) ``STACK[-1]`` is a tuple of keyword attribute names, ``STACK[-2]`` is the class @@ -1899,28 +1910,34 @@ iterations of the loop. The operand determines which intrinsic function is called: - +----------------------------------------+-----------------------------------+ - | Operand | Description | - +========================================+===================================+ - | ``INTRINSIC_2_INVALID`` | Not valid | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_PREP_RERAISE_STAR`` | Calculates the | - | | :exc:`ExceptionGroup` to raise | - | | from a ``try-except*``. | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_TYPEVAR_WITH_BOUND`` | Creates a :class:`typing.TypeVar` | - | | with a bound. | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS`` | Creates a | - | | :class:`typing.TypeVar` with | - | | constraints. | - +----------------------------------------+-----------------------------------+ - | ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS`` | Sets the ``__type_params__`` | - | | attribute of a function. | - +----------------------------------------+-----------------------------------+ + +----------------------------------------+-----------------------------------------+ + | Operand | Description | + +========================================+=========================================+ + | ``INTRINSIC_2_INVALID`` | Not valid | + +----------------------------------------+-----------------------------------------+ + | ``INTRINSIC_PREP_RERAISE_STAR`` | Calculates the | + | | :exc:`ExceptionGroup` to raise | + | | from a ``try-except*``. | + +----------------------------------------+-----------------------------------------+ + | ``INTRINSIC_TYPEVAR_WITH_BOUND`` | Creates a :class:`typing.TypeVar` | + | | with a bound. | + +----------------------------------------+-----------------------------------------+ + | ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS`` | Creates a | + | | :class:`typing.TypeVar` with | + | | constraints. | + +----------------------------------------+-----------------------------------------+ + | ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS`` | Sets the ``__type_params__`` | + | | attribute of a function. | + +----------------------------------------+-----------------------------------------+ + | ``INTRINSIC_MATCH_CLASS_ISINSTANCE`` | Do :func:`isinstance` checks for | + | | :ref:`Class patterns `. | + +----------------------------------------+-----------------------------------------+ .. versionadded:: 3.12 + .. versionchanged:: 3.16 + Added ``INTRINSIC_MATCH_CLASS_ISINSTANCE``. + .. opcode:: LOAD_SPECIAL diff --git a/Include/internal/pycore_intrinsics.h b/Include/internal/pycore_intrinsics.h index 59a7b16073f886c..cea7f3c6363cae5 100644 --- a/Include/internal/pycore_intrinsics.h +++ b/Include/internal/pycore_intrinsics.h @@ -30,8 +30,9 @@ #define INTRINSIC_TYPEVAR_WITH_CONSTRAINTS 3 #define INTRINSIC_SET_FUNCTION_TYPE_PARAMS 4 #define INTRINSIC_SET_TYPEPARAM_DEFAULT 5 +#define INTRINSIC_MATCH_CLASS_ISINSTANCE 6 -#define MAX_INTRINSIC_2 5 +#define MAX_INTRINSIC_2 6 typedef PyObject *(*intrinsic_func1)(PyThreadState* tstate, PyObject *value); typedef PyObject *(*intrinsic_func2)(PyThreadState* tstate, PyObject *value1, PyObject *value2); diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h index 53ed570f99f118a..9917bb286e68dfc 100644 --- a/Include/internal/pycore_magic_number.h +++ b/Include/internal/pycore_magic_number.h @@ -302,6 +302,7 @@ Known values: Python 3.16a1 3702 (Replace DELETE_NAME with PUSH_NULL; STORE_NAME) Python 3.16a1 3703 (Replace DELETE_GLOBAL with PUSH_NULL; STORE_GLOBAL) Python 3.16a1 3704 (Replace DELETE_ATTR with PUSH_NULL; STORE_ATTR) + Python 3.16a1 3705 (Add MATCH_CLASS_GET_OPT_ATTR opcode) Python 3.17 will start with 3750 @@ -315,7 +316,7 @@ PC/launcher.c must also be updated. */ -#define PYC_MAGIC_NUMBER 3704 +#define PYC_MAGIC_NUMBER 3705 /* 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 647f6b6cbb989ad..b48726a67636499 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 cd34aaa9d34afde..fa9059c1b4e0b8d 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -333,6 +333,7 @@ extern "C" { #define _MAKE_WARM 572 #define _MAP_ADD MAP_ADD #define _MATCH_CLASS 573 +#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 @@ -1218,222 +1219,223 @@ extern "C" { #define _MAKE_WARM_r33 1433 #define _MAP_ADD_r20 1434 #define _MATCH_CLASS_r33 1435 -#define _MATCH_KEYS_r23 1436 -#define _MATCH_MAPPING_r02 1437 -#define _MATCH_MAPPING_r12 1438 -#define _MATCH_MAPPING_r23 1439 -#define _MATCH_SEQUENCE_r02 1440 -#define _MATCH_SEQUENCE_r12 1441 -#define _MATCH_SEQUENCE_r23 1442 -#define _MAYBE_EXPAND_METHOD_r00 1443 -#define _MAYBE_EXPAND_METHOD_KW_r11 1444 -#define _MONITOR_CALL_r00 1445 -#define _MONITOR_CALL_KW_r11 1446 -#define _MONITOR_JUMP_BACKWARD_r00 1447 -#define _MONITOR_JUMP_BACKWARD_r11 1448 -#define _MONITOR_JUMP_BACKWARD_r22 1449 -#define _MONITOR_JUMP_BACKWARD_r33 1450 -#define _MONITOR_RESUME_r00 1451 -#define _NOP_r00 1452 -#define _NOP_r11 1453 -#define _NOP_r22 1454 -#define _NOP_r33 1455 -#define _POP_EXCEPT_r10 1456 -#define _POP_ITER_r20 1457 -#define _POP_JUMP_IF_FALSE_r00 1458 -#define _POP_JUMP_IF_FALSE_r10 1459 -#define _POP_JUMP_IF_FALSE_r21 1460 -#define _POP_JUMP_IF_FALSE_r32 1461 -#define _POP_JUMP_IF_TRUE_r00 1462 -#define _POP_JUMP_IF_TRUE_r10 1463 -#define _POP_JUMP_IF_TRUE_r21 1464 -#define _POP_JUMP_IF_TRUE_r32 1465 -#define _POP_TOP_r10 1466 -#define _POP_TOP_FLOAT_r00 1467 -#define _POP_TOP_FLOAT_r10 1468 -#define _POP_TOP_FLOAT_r21 1469 -#define _POP_TOP_FLOAT_r32 1470 -#define _POP_TOP_INT_r00 1471 -#define _POP_TOP_INT_r10 1472 -#define _POP_TOP_INT_r21 1473 -#define _POP_TOP_INT_r32 1474 -#define _POP_TOP_NOP_r00 1475 -#define _POP_TOP_NOP_r10 1476 -#define _POP_TOP_NOP_r21 1477 -#define _POP_TOP_NOP_r32 1478 -#define _POP_TOP_OPARG_r00 1479 -#define _POP_TOP_UNICODE_r00 1480 -#define _POP_TOP_UNICODE_r10 1481 -#define _POP_TOP_UNICODE_r21 1482 -#define _POP_TOP_UNICODE_r32 1483 -#define _PUSH_EXC_INFO_r02 1484 -#define _PUSH_EXC_INFO_r12 1485 -#define _PUSH_EXC_INFO_r23 1486 -#define _PUSH_FRAME_r10 1487 -#define _PUSH_NULL_r01 1488 -#define _PUSH_NULL_r12 1489 -#define _PUSH_NULL_r23 1490 -#define _PUSH_NULL_CONDITIONAL_r00 1491 -#define _PUSH_TAGGED_ZERO_r01 1492 -#define _PUSH_TAGGED_ZERO_r12 1493 -#define _PUSH_TAGGED_ZERO_r23 1494 -#define _PY_FRAME_EX_r31 1495 -#define _PY_FRAME_GENERAL_r01 1496 -#define _PY_FRAME_KW_r11 1497 -#define _REPLACE_WITH_TRUE_r02 1498 -#define _REPLACE_WITH_TRUE_r12 1499 -#define _REPLACE_WITH_TRUE_r23 1500 -#define _RESUME_CHECK_r00 1501 -#define _RESUME_CHECK_r11 1502 -#define _RESUME_CHECK_r22 1503 -#define _RESUME_CHECK_r33 1504 -#define _RETURN_GENERATOR_r01 1505 -#define _RETURN_VALUE_r11 1506 -#define _RROT_3_r03 1507 -#define _RROT_3_r13 1508 -#define _RROT_3_r23 1509 -#define _RROT_3_r33 1510 -#define _SAVE_RETURN_OFFSET_r00 1511 -#define _SAVE_RETURN_OFFSET_r11 1512 -#define _SAVE_RETURN_OFFSET_r22 1513 -#define _SAVE_RETURN_OFFSET_r33 1514 -#define _SEND_ASYNC_GEN_r33 1515 -#define _SEND_ASYNC_GEN_TIER_TWO_r33 1516 -#define _SEND_GEN_FRAME_r33 1517 -#define _SEND_VIRTUAL_r33 1518 -#define _SEND_VIRTUAL_TIER_TWO_r03 1519 -#define _SEND_VIRTUAL_TIER_TWO_r13 1520 -#define _SEND_VIRTUAL_TIER_TWO_r23 1521 -#define _SEND_VIRTUAL_TIER_TWO_r33 1522 -#define _SETUP_ANNOTATIONS_r00 1523 -#define _SET_ADD_r10 1524 -#define _SET_FUNCTION_ATTRIBUTE_r01 1525 -#define _SET_FUNCTION_ATTRIBUTE_r11 1526 -#define _SET_FUNCTION_ATTRIBUTE_r21 1527 -#define _SET_FUNCTION_ATTRIBUTE_r32 1528 -#define _SET_IP_r00 1529 -#define _SET_IP_r11 1530 -#define _SET_IP_r22 1531 -#define _SET_IP_r33 1532 -#define _SET_UPDATE_r11 1533 -#define _SPILL_OR_RELOAD_r01 1534 -#define _SPILL_OR_RELOAD_r02 1535 -#define _SPILL_OR_RELOAD_r03 1536 -#define _SPILL_OR_RELOAD_r10 1537 -#define _SPILL_OR_RELOAD_r12 1538 -#define _SPILL_OR_RELOAD_r13 1539 -#define _SPILL_OR_RELOAD_r20 1540 -#define _SPILL_OR_RELOAD_r21 1541 -#define _SPILL_OR_RELOAD_r23 1542 -#define _SPILL_OR_RELOAD_r30 1543 -#define _SPILL_OR_RELOAD_r31 1544 -#define _SPILL_OR_RELOAD_r32 1545 -#define _START_EXECUTOR_r00 1546 -#define _STORE_ATTR_r20 1547 -#define _STORE_ATTR_INSTANCE_VALUE_r21 1548 -#define _STORE_ATTR_SLOT_r21 1549 -#define _STORE_ATTR_WITH_HINT_r21 1550 -#define _STORE_DEREF_r10 1551 -#define _STORE_FAST_LOAD_FAST_r11 1552 -#define _STORE_FAST_STORE_FAST_r20 1553 -#define _STORE_GLOBAL_r10 1554 -#define _STORE_NAME_r10 1555 -#define _STORE_SLICE_r30 1556 -#define _STORE_SUBSCR_r30 1557 -#define _STORE_SUBSCR_DICT_r31 1558 -#define _STORE_SUBSCR_DICT_KNOWN_HASH_r31 1559 -#define _STORE_SUBSCR_LIST_INT_r32 1560 -#define _SWAP_r11 1561 -#define _SWAP_2_r02 1562 -#define _SWAP_2_r12 1563 -#define _SWAP_2_r22 1564 -#define _SWAP_2_r33 1565 -#define _SWAP_3_r03 1566 -#define _SWAP_3_r13 1567 -#define _SWAP_3_r23 1568 -#define _SWAP_3_r33 1569 -#define _SWAP_FAST_r01 1570 -#define _SWAP_FAST_r11 1571 -#define _SWAP_FAST_r22 1572 -#define _SWAP_FAST_r33 1573 -#define _SWAP_FAST_0_r01 1574 -#define _SWAP_FAST_0_r11 1575 -#define _SWAP_FAST_0_r22 1576 -#define _SWAP_FAST_0_r33 1577 -#define _SWAP_FAST_1_r01 1578 -#define _SWAP_FAST_1_r11 1579 -#define _SWAP_FAST_1_r22 1580 -#define _SWAP_FAST_1_r33 1581 -#define _SWAP_FAST_2_r01 1582 -#define _SWAP_FAST_2_r11 1583 -#define _SWAP_FAST_2_r22 1584 -#define _SWAP_FAST_2_r33 1585 -#define _SWAP_FAST_3_r01 1586 -#define _SWAP_FAST_3_r11 1587 -#define _SWAP_FAST_3_r22 1588 -#define _SWAP_FAST_3_r33 1589 -#define _SWAP_FAST_4_r01 1590 -#define _SWAP_FAST_4_r11 1591 -#define _SWAP_FAST_4_r22 1592 -#define _SWAP_FAST_4_r33 1593 -#define _SWAP_FAST_5_r01 1594 -#define _SWAP_FAST_5_r11 1595 -#define _SWAP_FAST_5_r22 1596 -#define _SWAP_FAST_5_r33 1597 -#define _SWAP_FAST_6_r01 1598 -#define _SWAP_FAST_6_r11 1599 -#define _SWAP_FAST_6_r22 1600 -#define _SWAP_FAST_6_r33 1601 -#define _SWAP_FAST_7_r01 1602 -#define _SWAP_FAST_7_r11 1603 -#define _SWAP_FAST_7_r22 1604 -#define _SWAP_FAST_7_r33 1605 -#define _TIER2_RESUME_CHECK_r00 1606 -#define _TIER2_RESUME_CHECK_r11 1607 -#define _TIER2_RESUME_CHECK_r22 1608 -#define _TIER2_RESUME_CHECK_r33 1609 -#define _TO_BOOL_r11 1610 -#define _TO_BOOL_BOOL_r01 1611 -#define _TO_BOOL_BOOL_r11 1612 -#define _TO_BOOL_BOOL_r22 1613 -#define _TO_BOOL_BOOL_r33 1614 -#define _TO_BOOL_INT_r02 1615 -#define _TO_BOOL_INT_r12 1616 -#define _TO_BOOL_INT_r23 1617 -#define _TO_BOOL_LIST_r02 1618 -#define _TO_BOOL_LIST_r12 1619 -#define _TO_BOOL_LIST_r23 1620 -#define _TO_BOOL_NONE_r01 1621 -#define _TO_BOOL_NONE_r11 1622 -#define _TO_BOOL_NONE_r22 1623 -#define _TO_BOOL_NONE_r33 1624 -#define _TO_BOOL_STR_r02 1625 -#define _TO_BOOL_STR_r12 1626 -#define _TO_BOOL_STR_r23 1627 -#define _TRACE_RECORD_r00 1628 -#define _UNARY_INVERT_r12 1629 -#define _UNARY_NEGATIVE_r12 1630 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r02 1631 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r12 1632 -#define _UNARY_NEGATIVE_FLOAT_INPLACE_r23 1633 -#define _UNARY_NOT_r01 1634 -#define _UNARY_NOT_r11 1635 -#define _UNARY_NOT_r22 1636 -#define _UNARY_NOT_r33 1637 -#define _UNPACK_EX_r10 1638 -#define _UNPACK_SEQUENCE_r10 1639 -#define _UNPACK_SEQUENCE_LIST_r10 1640 -#define _UNPACK_SEQUENCE_TUPLE_r10 1641 -#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1642 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r03 1643 -#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r13 1644 -#define _UNPACK_SEQUENCE_UNIQUE_TUPLE_r10 1645 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r02 1646 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r12 1647 -#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r23 1648 -#define _WITH_EXCEPT_START_r33 1649 -#define _YIELD_VALUE_r11 1650 -#define MAX_UOP_REGS_ID 1650 +#define _MATCH_CLASS_GET_OPT_ATTR_r13 1436 +#define _MATCH_KEYS_r23 1437 +#define _MATCH_MAPPING_r02 1438 +#define _MATCH_MAPPING_r12 1439 +#define _MATCH_MAPPING_r23 1440 +#define _MATCH_SEQUENCE_r02 1441 +#define _MATCH_SEQUENCE_r12 1442 +#define _MATCH_SEQUENCE_r23 1443 +#define _MAYBE_EXPAND_METHOD_r00 1444 +#define _MAYBE_EXPAND_METHOD_KW_r11 1445 +#define _MONITOR_CALL_r00 1446 +#define _MONITOR_CALL_KW_r11 1447 +#define _MONITOR_JUMP_BACKWARD_r00 1448 +#define _MONITOR_JUMP_BACKWARD_r11 1449 +#define _MONITOR_JUMP_BACKWARD_r22 1450 +#define _MONITOR_JUMP_BACKWARD_r33 1451 +#define _MONITOR_RESUME_r00 1452 +#define _NOP_r00 1453 +#define _NOP_r11 1454 +#define _NOP_r22 1455 +#define _NOP_r33 1456 +#define _POP_EXCEPT_r10 1457 +#define _POP_ITER_r20 1458 +#define _POP_JUMP_IF_FALSE_r00 1459 +#define _POP_JUMP_IF_FALSE_r10 1460 +#define _POP_JUMP_IF_FALSE_r21 1461 +#define _POP_JUMP_IF_FALSE_r32 1462 +#define _POP_JUMP_IF_TRUE_r00 1463 +#define _POP_JUMP_IF_TRUE_r10 1464 +#define _POP_JUMP_IF_TRUE_r21 1465 +#define _POP_JUMP_IF_TRUE_r32 1466 +#define _POP_TOP_r10 1467 +#define _POP_TOP_FLOAT_r00 1468 +#define _POP_TOP_FLOAT_r10 1469 +#define _POP_TOP_FLOAT_r21 1470 +#define _POP_TOP_FLOAT_r32 1471 +#define _POP_TOP_INT_r00 1472 +#define _POP_TOP_INT_r10 1473 +#define _POP_TOP_INT_r21 1474 +#define _POP_TOP_INT_r32 1475 +#define _POP_TOP_NOP_r00 1476 +#define _POP_TOP_NOP_r10 1477 +#define _POP_TOP_NOP_r21 1478 +#define _POP_TOP_NOP_r32 1479 +#define _POP_TOP_OPARG_r00 1480 +#define _POP_TOP_UNICODE_r00 1481 +#define _POP_TOP_UNICODE_r10 1482 +#define _POP_TOP_UNICODE_r21 1483 +#define _POP_TOP_UNICODE_r32 1484 +#define _PUSH_EXC_INFO_r02 1485 +#define _PUSH_EXC_INFO_r12 1486 +#define _PUSH_EXC_INFO_r23 1487 +#define _PUSH_FRAME_r10 1488 +#define _PUSH_NULL_r01 1489 +#define _PUSH_NULL_r12 1490 +#define _PUSH_NULL_r23 1491 +#define _PUSH_NULL_CONDITIONAL_r00 1492 +#define _PUSH_TAGGED_ZERO_r01 1493 +#define _PUSH_TAGGED_ZERO_r12 1494 +#define _PUSH_TAGGED_ZERO_r23 1495 +#define _PY_FRAME_EX_r31 1496 +#define _PY_FRAME_GENERAL_r01 1497 +#define _PY_FRAME_KW_r11 1498 +#define _REPLACE_WITH_TRUE_r02 1499 +#define _REPLACE_WITH_TRUE_r12 1500 +#define _REPLACE_WITH_TRUE_r23 1501 +#define _RESUME_CHECK_r00 1502 +#define _RESUME_CHECK_r11 1503 +#define _RESUME_CHECK_r22 1504 +#define _RESUME_CHECK_r33 1505 +#define _RETURN_GENERATOR_r01 1506 +#define _RETURN_VALUE_r11 1507 +#define _RROT_3_r03 1508 +#define _RROT_3_r13 1509 +#define _RROT_3_r23 1510 +#define _RROT_3_r33 1511 +#define _SAVE_RETURN_OFFSET_r00 1512 +#define _SAVE_RETURN_OFFSET_r11 1513 +#define _SAVE_RETURN_OFFSET_r22 1514 +#define _SAVE_RETURN_OFFSET_r33 1515 +#define _SEND_ASYNC_GEN_r33 1516 +#define _SEND_ASYNC_GEN_TIER_TWO_r33 1517 +#define _SEND_GEN_FRAME_r33 1518 +#define _SEND_VIRTUAL_r33 1519 +#define _SEND_VIRTUAL_TIER_TWO_r03 1520 +#define _SEND_VIRTUAL_TIER_TWO_r13 1521 +#define _SEND_VIRTUAL_TIER_TWO_r23 1522 +#define _SEND_VIRTUAL_TIER_TWO_r33 1523 +#define _SETUP_ANNOTATIONS_r00 1524 +#define _SET_ADD_r10 1525 +#define _SET_FUNCTION_ATTRIBUTE_r01 1526 +#define _SET_FUNCTION_ATTRIBUTE_r11 1527 +#define _SET_FUNCTION_ATTRIBUTE_r21 1528 +#define _SET_FUNCTION_ATTRIBUTE_r32 1529 +#define _SET_IP_r00 1530 +#define _SET_IP_r11 1531 +#define _SET_IP_r22 1532 +#define _SET_IP_r33 1533 +#define _SET_UPDATE_r11 1534 +#define _SPILL_OR_RELOAD_r01 1535 +#define _SPILL_OR_RELOAD_r02 1536 +#define _SPILL_OR_RELOAD_r03 1537 +#define _SPILL_OR_RELOAD_r10 1538 +#define _SPILL_OR_RELOAD_r12 1539 +#define _SPILL_OR_RELOAD_r13 1540 +#define _SPILL_OR_RELOAD_r20 1541 +#define _SPILL_OR_RELOAD_r21 1542 +#define _SPILL_OR_RELOAD_r23 1543 +#define _SPILL_OR_RELOAD_r30 1544 +#define _SPILL_OR_RELOAD_r31 1545 +#define _SPILL_OR_RELOAD_r32 1546 +#define _START_EXECUTOR_r00 1547 +#define _STORE_ATTR_r20 1548 +#define _STORE_ATTR_INSTANCE_VALUE_r21 1549 +#define _STORE_ATTR_SLOT_r21 1550 +#define _STORE_ATTR_WITH_HINT_r21 1551 +#define _STORE_DEREF_r10 1552 +#define _STORE_FAST_LOAD_FAST_r11 1553 +#define _STORE_FAST_STORE_FAST_r20 1554 +#define _STORE_GLOBAL_r10 1555 +#define _STORE_NAME_r10 1556 +#define _STORE_SLICE_r30 1557 +#define _STORE_SUBSCR_r30 1558 +#define _STORE_SUBSCR_DICT_r31 1559 +#define _STORE_SUBSCR_DICT_KNOWN_HASH_r31 1560 +#define _STORE_SUBSCR_LIST_INT_r32 1561 +#define _SWAP_r11 1562 +#define _SWAP_2_r02 1563 +#define _SWAP_2_r12 1564 +#define _SWAP_2_r22 1565 +#define _SWAP_2_r33 1566 +#define _SWAP_3_r03 1567 +#define _SWAP_3_r13 1568 +#define _SWAP_3_r23 1569 +#define _SWAP_3_r33 1570 +#define _SWAP_FAST_r01 1571 +#define _SWAP_FAST_r11 1572 +#define _SWAP_FAST_r22 1573 +#define _SWAP_FAST_r33 1574 +#define _SWAP_FAST_0_r01 1575 +#define _SWAP_FAST_0_r11 1576 +#define _SWAP_FAST_0_r22 1577 +#define _SWAP_FAST_0_r33 1578 +#define _SWAP_FAST_1_r01 1579 +#define _SWAP_FAST_1_r11 1580 +#define _SWAP_FAST_1_r22 1581 +#define _SWAP_FAST_1_r33 1582 +#define _SWAP_FAST_2_r01 1583 +#define _SWAP_FAST_2_r11 1584 +#define _SWAP_FAST_2_r22 1585 +#define _SWAP_FAST_2_r33 1586 +#define _SWAP_FAST_3_r01 1587 +#define _SWAP_FAST_3_r11 1588 +#define _SWAP_FAST_3_r22 1589 +#define _SWAP_FAST_3_r33 1590 +#define _SWAP_FAST_4_r01 1591 +#define _SWAP_FAST_4_r11 1592 +#define _SWAP_FAST_4_r22 1593 +#define _SWAP_FAST_4_r33 1594 +#define _SWAP_FAST_5_r01 1595 +#define _SWAP_FAST_5_r11 1596 +#define _SWAP_FAST_5_r22 1597 +#define _SWAP_FAST_5_r33 1598 +#define _SWAP_FAST_6_r01 1599 +#define _SWAP_FAST_6_r11 1600 +#define _SWAP_FAST_6_r22 1601 +#define _SWAP_FAST_6_r33 1602 +#define _SWAP_FAST_7_r01 1603 +#define _SWAP_FAST_7_r11 1604 +#define _SWAP_FAST_7_r22 1605 +#define _SWAP_FAST_7_r33 1606 +#define _TIER2_RESUME_CHECK_r00 1607 +#define _TIER2_RESUME_CHECK_r11 1608 +#define _TIER2_RESUME_CHECK_r22 1609 +#define _TIER2_RESUME_CHECK_r33 1610 +#define _TO_BOOL_r11 1611 +#define _TO_BOOL_BOOL_r01 1612 +#define _TO_BOOL_BOOL_r11 1613 +#define _TO_BOOL_BOOL_r22 1614 +#define _TO_BOOL_BOOL_r33 1615 +#define _TO_BOOL_INT_r02 1616 +#define _TO_BOOL_INT_r12 1617 +#define _TO_BOOL_INT_r23 1618 +#define _TO_BOOL_LIST_r02 1619 +#define _TO_BOOL_LIST_r12 1620 +#define _TO_BOOL_LIST_r23 1621 +#define _TO_BOOL_NONE_r01 1622 +#define _TO_BOOL_NONE_r11 1623 +#define _TO_BOOL_NONE_r22 1624 +#define _TO_BOOL_NONE_r33 1625 +#define _TO_BOOL_STR_r02 1626 +#define _TO_BOOL_STR_r12 1627 +#define _TO_BOOL_STR_r23 1628 +#define _TRACE_RECORD_r00 1629 +#define _UNARY_INVERT_r12 1630 +#define _UNARY_NEGATIVE_r12 1631 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r02 1632 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r12 1633 +#define _UNARY_NEGATIVE_FLOAT_INPLACE_r23 1634 +#define _UNARY_NOT_r01 1635 +#define _UNARY_NOT_r11 1636 +#define _UNARY_NOT_r22 1637 +#define _UNARY_NOT_r33 1638 +#define _UNPACK_EX_r10 1639 +#define _UNPACK_SEQUENCE_r10 1640 +#define _UNPACK_SEQUENCE_LIST_r10 1641 +#define _UNPACK_SEQUENCE_TUPLE_r10 1642 +#define _UNPACK_SEQUENCE_TWO_TUPLE_r12 1643 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r03 1644 +#define _UNPACK_SEQUENCE_UNIQUE_THREE_TUPLE_r13 1645 +#define _UNPACK_SEQUENCE_UNIQUE_TUPLE_r10 1646 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r02 1647 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r12 1648 +#define _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE_r23 1649 +#define _WITH_EXCEPT_START_r33 1650 +#define _YIELD_VALUE_r11 1651 +#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 d4cceb5a500cab5..5cd046c48f3ab54 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -254,6 +254,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, @@ -2416,6 +2417,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 = { @@ -4420,6 +4430,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", @@ -6604,6 +6617,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_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 25b2c393e6773de..a291218934f1b6c 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -5721,12 +5721,13 @@ def testfunc(n): def test_match_class(self): def testfunc(n): class A: + __match_args__ = ("val",) val = 1 x = A() ret = 0 for _ in range(n): match x: - case A(): + case A(1): ret += x.val return ret @@ -5735,7 +5736,7 @@ class A: uops = get_opnames(ex) self.assertIn("_MATCH_CLASS", uops) - self.assertEqual(count_ops(ex, "_POP_TOP_NOP"), 4) + self.assertEqual(count_ops(ex, "_POP_TOP_NOP"), 5) def test_dict_update(self): def testfunc(n): diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 28748009f731bc1..61a97f0140f2925 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 without + # positional 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/2025-09-17-22-12-38.gh-issue-138912.PijFSS.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-17-22-12-38.gh-issue-138912.PijFSS.rst new file mode 100644 index 000000000000000..30931f29d8cc841 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-17-22-12-38.gh-issue-138912.PijFSS.rst @@ -0,0 +1,3 @@ +Add :opcode:`MATCH_CLASS_GET_OPT_ATTR` to improve the performance of +:keyword:`match` class patterns without any positional sub-patterns +by ~40%. Patch by Marc Mueller. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst new file mode 100644 index 000000000000000..b440a01443ddc0f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst @@ -0,0 +1,3 @@ +Add fast path for :keyword:`match` class patterns without any sub-patterns +to improve performance by ~15%. +Patch by Marc Mueller. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index ab80c422687bf2a..213588ec8a6de76 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -10906,6 +10906,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/Python/bytecodes.c b/Python/bytecodes.c index 689feeb0a1e0700..5ecc40a73b0f05b 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3678,6 +3678,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 f3f03b28112137a..b9b678cf54c87d5 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -532,6 +532,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); @@ -545,15 +546,12 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, // Short circuit if there aren't any arguments: Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwargs); Py_ssize_t nattrs = nargs + nkwargs; - if (!nattrs) { - return PyTuple_New(0); - } // So far so good: PyObject *seen = NULL; // Only check for duplicates if there is at least one positional attribute // and two or more attributes in total. Duplicate keyword attributes are // detected during the compile stage and raise a SyntaxError. - if (nargs > 0 && nattrs > 1) { + if (nattrs > 1) { seen = PySet_New(NULL); if (seen == NULL) { return NULL; diff --git a/Python/codegen.c b/Python/codegen.c index 81fecd09a2060d1..d327d25433754bb 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -6156,6 +6156,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) { @@ -6179,6 +6234,16 @@ codegen_pattern_class(compiler *c, pattern_ty p, pattern_context *pc) if (nattrs) { RETURN_IF_ERROR(validate_kwd_attrs(c, kwd_attrs, kwd_patterns)); } + if (nargs + nattrs == 0) { + // Fast path if there are no sub-patterns + VISIT(c, expr, p->v.MatchClass.cls); + 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); if (attr_names == NULL) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index a012f14f7fbebe2..1d2254009987f90 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -14208,6 +14208,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 b72116d367634d8..e2fc1942efc9bff 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -10904,6 +10904,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/intrinsics.c b/Python/intrinsics.c index f081f33cc83b88c..63410d8dbe584ad 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -270,6 +270,25 @@ make_typevar_with_constraints(PyThreadState* Py_UNUSED(ignored), PyObject *name, return _Py_make_typevar(name, NULL, evaluate_constraints); } +static PyObject * +match_class_isinstance(PyThreadState* tstate, PyObject *subject, PyObject *type) +{ + /* Fast path for class patterns with no sub-patterns, e.g. `case C():` + Equivalent to the isinstance check performed by _PyEval_MatchClass, + including the same TypeError when the pattern does not refer to a + class. */ + if (!PyType_Check(type)) { + _PyErr_SetString(tstate, PyExc_TypeError, + "class pattern must refer to a class"); + return NULL; + } + int res = PyObject_IsInstance(subject, type); + if (res < 0) { + return NULL; + } + return res ? Py_True : Py_False; +} + const intrinsic_func2_info _PyIntrinsics_BinaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_2_INVALID, no_intrinsic2) @@ -278,6 +297,7 @@ _PyIntrinsics_BinaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEVAR_WITH_CONSTRAINTS, make_typevar_with_constraints) INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_FUNCTION_TYPE_PARAMS, _Py_set_function_type_params) INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_TYPEPARAM_DEFAULT, _Py_set_typeparam_default) + INTRINSIC_FUNC_ENTRY(INTRINSIC_MATCH_CLASS_ISINSTANCE, match_class_isinstance) }; #undef INTRINSIC_FUNC_ENTRY 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 3ad23c3a74f7087..3a22b1c70f20acf 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3497,6 +3497,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;