From 6ea4cd2bb1a4c95c2ce98e867c2f3d95cf6e19e5 Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:07 +0900 Subject: [PATCH 01/12] Align compiler and VM with final CPython 3.15 bytecode Reconstruct the compiler-only baseline from validated487ae47 on the current main and frozen mapping prerequisitec78d811. Preserve the full source delta while dropping obsolete prerequisite ancestry; detailed old-to-new provenance is retained in the recovery artifact. Assisted-by: OpenAI Codex: model identifier unavailable --- .cspell.dict/rustpython.txt | 1 + .github/workflows/ci.yaml | 5 +- .pre-commit-config.yaml | 21 +- Lib/_opcode_metadata.py | 750 ++-- Lib/dis.py | 27 +- Lib/opcode.py | 161 +- crates/codegen/src/compile.rs | 1273 ++++-- crates/codegen/src/ir.rs | 454 ++- crates/compiler-core/src/bytecode.rs | 84 +- .../compiler-core/src/bytecode/instruction.rs | 467 ++- crates/compiler-core/src/bytecode/oparg.rs | 26 +- .../src/bytecode/opcode_metadata.rs | 95 +- .../src/bytecode/public_metadata.rs | 496 +++ .../src/bytecode/target_stack_cases.rs | 3433 +++++++++++++++++ crates/compiler-core/src/marshal.rs | 379 +- crates/jit/src/instructions.rs | 58 + crates/jit/tests/common.rs | 11 +- crates/stdlib/src/_opcode.rs | 136 +- ...k_attribute_and_subscript_expressions.snap | 2 +- ...n_stdlib___opcode__tests__const_no_op.snap | 4 +- ...nt_true_if_pass_keeps_line_anchor_nop.snap | 2 +- ...ython_stdlib___opcode__tests__if_ands.snap | 2 +- ...thon_stdlib___opcode__tests__if_mixed.snap | 2 +- ...python_stdlib___opcode__tests__if_ors.snap | 2 +- ...tdlib___opcode__tests__nested_bool_op.snap | 6 +- ...code__tests__nested_double_async_with.snap | 118 +- crates/vm/src/builtins/code.rs | 432 ++- crates/vm/src/builtins/code/compile_cache.rs | 538 +++ crates/vm/src/builtins/coroutine.rs | 2 +- crates/vm/src/builtins/frame.rs | 35 +- crates/vm/src/builtins/function.rs | 12 +- crates/vm/src/coroutine.rs | 37 +- crates/vm/src/frame.rs | 360 +- crates/vm/src/protocol/callable.rs | 2 +- crates/vm/src/stdlib/marshal.rs | 118 +- crates/vm/src/stdlib/sys.rs | 2 +- crates/vm/src/stdlib/sys/monitoring.rs | 6 +- crates/vm/src/version.rs | 7 +- crates/vm/src/vm/mod.rs | 49 +- crates/vm/src/vm/vm_new.rs | 4 +- .../vm/tests/fixtures/private_cache_bytes.rs | 9 + crates/vm/tests/private_cache_reader.rs | 38 + .../snippets/adaptive_public_export_315.py | 101 + .../snippets/canonical_code_storage_315.py | 216 ++ .../snippets/canonical_code_writer_315.py | 118 + .../snippets/compiled_constant_sharing_315.py | 160 + .../snippets/compiler_315_warmed_export.py | 70 + extra_tests/snippets/iterator_contract_315.py | 501 +++ .../snippets/marshal_actual_owners_315.py | 136 + extra_tests/snippets/opcode_calling_315.py | 179 + .../snippets/private_315_cache_contract.py | 109 + .../snippets/public_opcode_metadata_315.py | 567 +++ .../snippets/stable_code_fields_315.py | 154 + scripts/byte_exact_capture.py | 101 + scripts/byte_exact_compile.py | 174 + scripts/byte_exact_fixtures.json | 242 ++ scripts/test_byte_exact_compile.py | 112 + .../opcode_metadata/CANONICAL_CODE_STORAGE.md | 37 + .../COMPILER_315_DEVELOPMENT.md | 110 + tools/opcode_metadata/STABLE_CODE_FIELDS.md | 132 + tools/opcode_metadata/conf.toml | 4 + tools/opcode_metadata/cpython.py | 13 +- .../generate_public_metadata.py | 188 + .../generate_py_opcode_metadata.py | 105 +- tools/opcode_metadata/opcodes.py | 8 +- tools/opcode_metadata/stack_contract.c | 18 + tools/opcode_metadata/target_source.json | 48 + tools/opcode_metadata/target_source.py | 36 + tools/opcode_metadata/verify_opcode_ids.py | 44 + .../verify_py_opcode_metadata.py | 24 + .../opcode_metadata/verify_stack_contract.py | 51 + 71 files changed, 11661 insertions(+), 1763 deletions(-) create mode 100644 crates/compiler-core/src/bytecode/public_metadata.rs create mode 100644 crates/compiler-core/src/bytecode/target_stack_cases.rs create mode 100644 crates/vm/src/builtins/code/compile_cache.rs create mode 100644 crates/vm/tests/fixtures/private_cache_bytes.rs create mode 100644 crates/vm/tests/private_cache_reader.rs create mode 100644 extra_tests/snippets/adaptive_public_export_315.py create mode 100644 extra_tests/snippets/canonical_code_storage_315.py create mode 100644 extra_tests/snippets/canonical_code_writer_315.py create mode 100644 extra_tests/snippets/compiled_constant_sharing_315.py create mode 100644 extra_tests/snippets/compiler_315_warmed_export.py create mode 100644 extra_tests/snippets/iterator_contract_315.py create mode 100644 extra_tests/snippets/marshal_actual_owners_315.py create mode 100644 extra_tests/snippets/opcode_calling_315.py create mode 100644 extra_tests/snippets/private_315_cache_contract.py create mode 100644 extra_tests/snippets/public_opcode_metadata_315.py create mode 100644 extra_tests/snippets/stable_code_fields_315.py create mode 100644 scripts/byte_exact_capture.py create mode 100644 scripts/byte_exact_compile.py create mode 100644 scripts/byte_exact_fixtures.json create mode 100644 scripts/test_byte_exact_compile.py create mode 100644 tools/opcode_metadata/CANONICAL_CODE_STORAGE.md create mode 100644 tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md create mode 100644 tools/opcode_metadata/STABLE_CODE_FIELDS.md create mode 100644 tools/opcode_metadata/generate_public_metadata.py create mode 100644 tools/opcode_metadata/stack_contract.c create mode 100644 tools/opcode_metadata/target_source.json create mode 100644 tools/opcode_metadata/target_source.py create mode 100644 tools/opcode_metadata/verify_opcode_ids.py create mode 100644 tools/opcode_metadata/verify_py_opcode_metadata.py create mode 100644 tools/opcode_metadata/verify_stack_contract.py diff --git a/.cspell.dict/rustpython.txt b/.cspell.dict/rustpython.txt index b4170d20da4..563b58c248a 100644 --- a/.cspell.dict/rustpython.txt +++ b/.cspell.dict/rustpython.txt @@ -12,6 +12,7 @@ pyast pyattr pyclass pyclassmethod +pycode pyexception pyfunction pygetset diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c5cccd14703..d33eb8cdf51 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -790,9 +790,8 @@ jobs: with: repository: python/cpython path: cpython - # RustPython still uses the 3.14 bytecode format; this source is only - # for opcode metadata, independent of the 3.15 runtime target. - ref: "v3.14.7" + # Exact final source shared by all opcode metadata checks. + ref: "d220409a13b77de7a6c30abe16ea90db01528edc" # v3.15.0 persist-credentials: false - name: prek run (manual stage) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a78da8d2e6a..633ebc67eef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: - id: generate-rs-opcode-metadata name: generate rust opcode metadata entry: python3 tools/opcode_metadata/generate_rs_opcode_metadata.py - files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$' + files: '^(crates/compiler-core/src/bytecode/(instruction|opcode_metadata)\.rs|tools/opcode_metadata/.*)$' pass_filenames: false language: system require_serial: true @@ -51,10 +51,10 @@ repos: stages: - manual - - id: generate-py-opcode-metadata - name: generate python opcode metadata - entry: python3 tools/opcode_metadata/generate_py_opcode_metadata.py - files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$' + - id: verify-py-opcode-metadata + name: verify canonical python opcode metadata + entry: python3 tools/opcode_metadata/verify_py_opcode_metadata.py + files: '^(Lib/(opcode|_opcode_metadata|dis)\.py|tools/opcode_metadata/.*)$' pass_filenames: false language: system require_serial: true @@ -62,6 +62,17 @@ repos: stages: - manual + - id: check-public-opcode-metadata + name: check public opcode metadata + entry: python3 tools/opcode_metadata/generate_public_metadata.py --check + files: '^(crates/compiler-core/src/bytecode/public_metadata\.rs|tools/opcode_metadata/.*)$' + pass_filenames: false + language: system + require_serial: true + priority: 1 + stages: + - manual + - repo: https://github.com/streetsidesoftware/cspell-cli rev: v10.3.0 hooks: diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 001a016621c..183d0af30ac 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -1,371 +1,387 @@ -# This file is generated by tools/opcode_metadata/generate_py_opcode_metadata.py -# for RustPython bytecode format (CPython 3.14 compatible opcode numbers). +# This file is generated by Tools/cases_generator/py_metadata_generator.py +# from: +# Python/bytecodes.c # Do not edit! +_specializations = frozendict( + RESUME=( + "RESUME_CHECK", + "RESUME_CHECK_JIT", + ), + TO_BOOL=( + "TO_BOOL_ALWAYS_TRUE", + "TO_BOOL_BOOL", + "TO_BOOL_INT", + "TO_BOOL_LIST", + "TO_BOOL_NONE", + "TO_BOOL_STR", + ), + BINARY_OP=( + "BINARY_OP_MULTIPLY_INT", + "BINARY_OP_ADD_INT", + "BINARY_OP_SUBTRACT_INT", + "BINARY_OP_MULTIPLY_FLOAT", + "BINARY_OP_ADD_FLOAT", + "BINARY_OP_SUBTRACT_FLOAT", + "BINARY_OP_ADD_UNICODE", + "BINARY_OP_SUBSCR_LIST_INT", + "BINARY_OP_SUBSCR_LIST_SLICE", + "BINARY_OP_SUBSCR_TUPLE_INT", + "BINARY_OP_SUBSCR_STR_INT", + "BINARY_OP_SUBSCR_USTR_INT", + "BINARY_OP_SUBSCR_DICT", + "BINARY_OP_SUBSCR_GETITEM", + "BINARY_OP_INPLACE_ADD_UNICODE", + "BINARY_OP_EXTEND", + ), + STORE_SUBSCR=( + "STORE_SUBSCR_DICT", + "STORE_SUBSCR_LIST_INT", + ), + SEND=( + "SEND_GEN", + "SEND_VIRTUAL", + "SEND_ASYNC_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_CLASS_WITH_METACLASS_CHECK", + "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", + ), + CONTAINS_OP=( + "CONTAINS_OP_SET", + "CONTAINS_OP_DICT", + ), + JUMP_BACKWARD=( + "JUMP_BACKWARD_NO_JIT", + "JUMP_BACKWARD_JIT", + ), + GET_ITER=( + "GET_ITER_SELF", + "GET_ITER_VIRTUAL", + ), + FOR_ITER=( + "FOR_ITER_LIST", + "FOR_ITER_TUPLE", + "FOR_ITER_RANGE", + "FOR_ITER_GEN", + "FOR_ITER_VIRTUAL", + ), + CALL=( + "CALL_BOUND_METHOD_EXACT_ARGS", + "CALL_PY_EXACT_ARGS", + "CALL_TYPE_1", + "CALL_STR_1", + "CALL_TUPLE_1", + "CALL_BUILTIN_CLASS", + "CALL_BUILTIN_O", + "CALL_BUILTIN_FAST", + "CALL_BUILTIN_FAST_WITH_KEYWORDS", + "CALL_LEN", + "CALL_ISINSTANCE", + "CALL_LIST_APPEND", + "CALL_METHOD_DESCRIPTOR_O", + "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", + "CALL_METHOD_DESCRIPTOR_NOARGS", + "CALL_METHOD_DESCRIPTOR_FAST", + "CALL_ALLOC_AND_ENTER_INIT", + "CALL_PY_GENERAL", + "CALL_BOUND_METHOD_GENERAL", + "CALL_NON_PY_GENERAL", + ), + CALL_KW=( + "CALL_KW_BOUND_METHOD", + "CALL_KW_PY", + "CALL_KW_NON_PY", + ), + CALL_FUNCTION_EX=( + "CALL_EX_PY", + "CALL_EX_NON_PY_GENERAL", + ), +) -_specializations = { - "RESUME": [ - "RESUME_CHECK", - ], - "LOAD_CONST": [ - "LOAD_CONST_MORTAL", - "LOAD_CONST_IMMORTAL", - ], - "TO_BOOL": [ - "TO_BOOL_ALWAYS_TRUE", - "TO_BOOL_BOOL", - "TO_BOOL_INT", - "TO_BOOL_LIST", - "TO_BOOL_NONE", - "TO_BOOL_STR", - ], - "BINARY_OP": [ - "BINARY_OP_MULTIPLY_INT", - "BINARY_OP_ADD_INT", - "BINARY_OP_SUBTRACT_INT", - "BINARY_OP_MULTIPLY_FLOAT", - "BINARY_OP_ADD_FLOAT", - "BINARY_OP_SUBTRACT_FLOAT", - "BINARY_OP_ADD_UNICODE", - "BINARY_OP_SUBSCR_LIST_INT", - "BINARY_OP_SUBSCR_LIST_SLICE", - "BINARY_OP_SUBSCR_TUPLE_INT", - "BINARY_OP_SUBSCR_STR_INT", - "BINARY_OP_SUBSCR_DICT", - "BINARY_OP_SUBSCR_GETITEM", - "BINARY_OP_EXTEND", - "BINARY_OP_INPLACE_ADD_UNICODE", - ], - "STORE_SUBSCR": [ - "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_CLASS_WITH_METACLASS_CHECK", - "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", - ], - "CONTAINS_OP": [ - "CONTAINS_OP_SET", - "CONTAINS_OP_DICT", - ], - "JUMP_BACKWARD": [ - "JUMP_BACKWARD_NO_JIT", - "JUMP_BACKWARD_JIT", - ], - "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_TYPE_1", - "CALL_STR_1", - "CALL_TUPLE_1", - "CALL_BUILTIN_CLASS", - "CALL_BUILTIN_O", - "CALL_BUILTIN_FAST", - "CALL_BUILTIN_FAST_WITH_KEYWORDS", - "CALL_LEN", - "CALL_ISINSTANCE", - "CALL_LIST_APPEND", - "CALL_METHOD_DESCRIPTOR_O", - "CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS", - "CALL_METHOD_DESCRIPTOR_NOARGS", - "CALL_METHOD_DESCRIPTOR_FAST", - "CALL_ALLOC_AND_ENTER_INIT", - "CALL_PY_GENERAL", - "CALL_BOUND_METHOD_GENERAL", - "CALL_NON_PY_GENERAL", - ], - "CALL_KW": [ - "CALL_KW_BOUND_METHOD", - "CALL_KW_PY", - "CALL_KW_NON_PY", - ], -} +_specialized_opmap = frozendict( + BINARY_OP_ADD_FLOAT=129, + BINARY_OP_ADD_INT=130, + BINARY_OP_ADD_UNICODE=131, + BINARY_OP_EXTEND=132, + BINARY_OP_INPLACE_ADD_UNICODE=3, + BINARY_OP_MULTIPLY_FLOAT=133, + BINARY_OP_MULTIPLY_INT=134, + BINARY_OP_SUBSCR_DICT=135, + BINARY_OP_SUBSCR_GETITEM=136, + BINARY_OP_SUBSCR_LIST_INT=137, + BINARY_OP_SUBSCR_LIST_SLICE=138, + BINARY_OP_SUBSCR_STR_INT=139, + BINARY_OP_SUBSCR_TUPLE_INT=140, + BINARY_OP_SUBSCR_USTR_INT=141, + BINARY_OP_SUBTRACT_FLOAT=142, + BINARY_OP_SUBTRACT_INT=143, + CALL_ALLOC_AND_ENTER_INIT=144, + CALL_BOUND_METHOD_EXACT_ARGS=145, + CALL_BOUND_METHOD_GENERAL=146, + CALL_BUILTIN_CLASS=147, + CALL_BUILTIN_FAST=148, + CALL_BUILTIN_FAST_WITH_KEYWORDS=149, + CALL_BUILTIN_O=150, + CALL_EX_NON_PY_GENERAL=151, + CALL_EX_PY=152, + CALL_ISINSTANCE=153, + CALL_KW_BOUND_METHOD=154, + CALL_KW_NON_PY=155, + CALL_KW_PY=156, + CALL_LEN=157, + CALL_LIST_APPEND=158, + CALL_METHOD_DESCRIPTOR_FAST=159, + CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS=160, + CALL_METHOD_DESCRIPTOR_NOARGS=161, + CALL_METHOD_DESCRIPTOR_O=162, + CALL_NON_PY_GENERAL=163, + CALL_PY_EXACT_ARGS=164, + CALL_PY_GENERAL=165, + CALL_STR_1=166, + CALL_TUPLE_1=167, + CALL_TYPE_1=168, + COMPARE_OP_FLOAT=169, + COMPARE_OP_INT=170, + COMPARE_OP_STR=171, + CONTAINS_OP_DICT=172, + CONTAINS_OP_SET=173, + FOR_ITER_GEN=174, + FOR_ITER_LIST=175, + FOR_ITER_RANGE=176, + FOR_ITER_TUPLE=177, + FOR_ITER_VIRTUAL=178, + GET_ITER_SELF=179, + GET_ITER_VIRTUAL=180, + JUMP_BACKWARD_JIT=181, + JUMP_BACKWARD_NO_JIT=182, + LOAD_ATTR_CLASS=183, + LOAD_ATTR_CLASS_WITH_METACLASS_CHECK=184, + LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN=185, + LOAD_ATTR_INSTANCE_VALUE=186, + LOAD_ATTR_METHOD_LAZY_DICT=187, + LOAD_ATTR_METHOD_NO_DICT=188, + LOAD_ATTR_METHOD_WITH_VALUES=189, + LOAD_ATTR_MODULE=190, + LOAD_ATTR_NONDESCRIPTOR_NO_DICT=191, + LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES=192, + LOAD_ATTR_PROPERTY=193, + LOAD_ATTR_SLOT=194, + LOAD_ATTR_WITH_HINT=195, + LOAD_GLOBAL_BUILTIN=196, + LOAD_GLOBAL_MODULE=197, + LOAD_SUPER_ATTR_ATTR=198, + LOAD_SUPER_ATTR_METHOD=199, + RESUME_CHECK=200, + RESUME_CHECK_JIT=201, + SEND_ASYNC_GEN=202, + SEND_GEN=203, + SEND_VIRTUAL=204, + STORE_ATTR_INSTANCE_VALUE=205, + STORE_ATTR_SLOT=206, + STORE_ATTR_WITH_HINT=207, + STORE_SUBSCR_DICT=208, + STORE_SUBSCR_LIST_INT=209, + TO_BOOL_ALWAYS_TRUE=210, + TO_BOOL_BOOL=211, + TO_BOOL_INT=212, + TO_BOOL_LIST=213, + TO_BOOL_NONE=214, + TO_BOOL_STR=215, + UNPACK_SEQUENCE_LIST=216, + UNPACK_SEQUENCE_TUPLE=217, + UNPACK_SEQUENCE_TWO_TUPLE=218, +) -_specialized_opmap = { - 'BINARY_OP_ADD_FLOAT': 129, - 'BINARY_OP_ADD_INT': 130, - 'BINARY_OP_ADD_UNICODE': 131, - 'BINARY_OP_EXTEND': 132, - 'BINARY_OP_INPLACE_ADD_UNICODE': 3, - 'BINARY_OP_MULTIPLY_FLOAT': 133, - 'BINARY_OP_MULTIPLY_INT': 134, - 'BINARY_OP_SUBSCR_DICT': 135, - 'BINARY_OP_SUBSCR_GETITEM': 136, - 'BINARY_OP_SUBSCR_LIST_INT': 137, - 'BINARY_OP_SUBSCR_LIST_SLICE': 138, - 'BINARY_OP_SUBSCR_STR_INT': 139, - 'BINARY_OP_SUBSCR_TUPLE_INT': 140, - 'BINARY_OP_SUBTRACT_FLOAT': 141, - 'BINARY_OP_SUBTRACT_INT': 142, - 'CALL_ALLOC_AND_ENTER_INIT': 143, - 'CALL_BOUND_METHOD_EXACT_ARGS': 144, - 'CALL_BOUND_METHOD_GENERAL': 145, - 'CALL_BUILTIN_CLASS': 146, - 'CALL_BUILTIN_FAST': 147, - 'CALL_BUILTIN_FAST_WITH_KEYWORDS': 148, - 'CALL_BUILTIN_O': 149, - 'CALL_ISINSTANCE': 150, - 'CALL_KW_BOUND_METHOD': 151, - 'CALL_KW_NON_PY': 152, - 'CALL_KW_PY': 153, - 'CALL_LEN': 154, - 'CALL_LIST_APPEND': 155, - 'CALL_METHOD_DESCRIPTOR_FAST': 156, - 'CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS': 157, - 'CALL_METHOD_DESCRIPTOR_NOARGS': 158, - 'CALL_METHOD_DESCRIPTOR_O': 159, - 'CALL_NON_PY_GENERAL': 160, - 'CALL_PY_EXACT_ARGS': 161, - 'CALL_PY_GENERAL': 162, - 'CALL_STR_1': 163, - 'CALL_TUPLE_1': 164, - 'CALL_TYPE_1': 165, - 'COMPARE_OP_FLOAT': 166, - 'COMPARE_OP_INT': 167, - 'COMPARE_OP_STR': 168, - 'CONTAINS_OP_DICT': 169, - 'CONTAINS_OP_SET': 170, - 'FOR_ITER_GEN': 171, - 'FOR_ITER_LIST': 172, - 'FOR_ITER_RANGE': 173, - 'FOR_ITER_TUPLE': 174, - 'JUMP_BACKWARD_JIT': 175, - 'JUMP_BACKWARD_NO_JIT': 176, - 'LOAD_ATTR_CLASS': 177, - 'LOAD_ATTR_CLASS_WITH_METACLASS_CHECK': 178, - 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 179, - 'LOAD_ATTR_INSTANCE_VALUE': 180, - 'LOAD_ATTR_METHOD_LAZY_DICT': 181, - 'LOAD_ATTR_METHOD_NO_DICT': 182, - 'LOAD_ATTR_METHOD_WITH_VALUES': 183, - 'LOAD_ATTR_MODULE': 184, - 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 185, - 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 186, - 'LOAD_ATTR_PROPERTY': 187, - 'LOAD_ATTR_SLOT': 188, - 'LOAD_ATTR_WITH_HINT': 189, - 'LOAD_CONST_IMMORTAL': 190, - 'LOAD_CONST_MORTAL': 191, - 'LOAD_GLOBAL_BUILTIN': 192, - 'LOAD_GLOBAL_MODULE': 193, - 'LOAD_SUPER_ATTR_ATTR': 194, - 'LOAD_SUPER_ATTR_METHOD': 195, - 'RESUME_CHECK': 196, - 'SEND_GEN': 197, - 'STORE_ATTR_INSTANCE_VALUE': 198, - 'STORE_ATTR_SLOT': 199, - 'STORE_ATTR_WITH_HINT': 200, - 'STORE_SUBSCR_DICT': 201, - 'STORE_SUBSCR_LIST_INT': 202, - 'TO_BOOL_ALWAYS_TRUE': 203, - 'TO_BOOL_BOOL': 204, - 'TO_BOOL_INT': 205, - 'TO_BOOL_LIST': 206, - 'TO_BOOL_NONE': 207, - 'TO_BOOL_STR': 208, - 'UNPACK_SEQUENCE_LIST': 209, - 'UNPACK_SEQUENCE_TUPLE': 210, - 'UNPACK_SEQUENCE_TWO_TUPLE': 211, -} +opmap = frozendict( + CACHE=0, + RESERVED=17, + RESUME=128, + INSTRUMENTED_LINE=253, + ENTER_EXECUTOR=254, + TRACE_RECORD=255, + BINARY_SLICE=1, + BUILD_TEMPLATE=2, + CALL_FUNCTION_EX=4, + CHECK_EG_MATCH=5, + CHECK_EXC_MATCH=6, + CLEANUP_THROW=7, + DELETE_SUBSCR=8, + END_FOR=9, + END_SEND=10, + EXIT_INIT_CHECK=11, + FORMAT_SIMPLE=12, + FORMAT_WITH_SPEC=13, + GET_AITER=14, + GET_ANEXT=15, + GET_LEN=16, + INTERPRETER_EXIT=18, + LOAD_BUILD_CLASS=19, + LOAD_LOCALS=20, + MAKE_FUNCTION=21, + MATCH_KEYS=22, + MATCH_MAPPING=23, + MATCH_SEQUENCE=24, + NOP=25, + NOT_TAKEN=26, + POP_EXCEPT=27, + POP_ITER=28, + POP_TOP=29, + PUSH_EXC_INFO=30, + PUSH_NULL=31, + RETURN_GENERATOR=32, + RETURN_VALUE=33, + SETUP_ANNOTATIONS=34, + STORE_SLICE=35, + STORE_SUBSCR=36, + TO_BOOL=37, + UNARY_INVERT=38, + UNARY_NEGATIVE=39, + UNARY_NOT=40, + WITH_EXCEPT_START=41, + BINARY_OP=42, + BUILD_INTERPOLATION=43, + BUILD_LIST=44, + BUILD_MAP=45, + BUILD_SET=46, + BUILD_SLICE=47, + BUILD_STRING=48, + BUILD_TUPLE=49, + CALL=50, + CALL_INTRINSIC_1=51, + CALL_INTRINSIC_2=52, + CALL_KW=53, + COMPARE_OP=54, + CONTAINS_OP=55, + CONVERT_VALUE=56, + COPY=57, + COPY_FREE_VARS=58, + DELETE_ATTR=59, + DELETE_DEREF=60, + DELETE_FAST=61, + DELETE_GLOBAL=62, + DELETE_NAME=63, + DICT_MERGE=64, + DICT_UPDATE=65, + END_ASYNC_FOR=66, + EXTENDED_ARG=67, + FOR_ITER=68, + GET_AWAITABLE=69, + GET_ITER=70, + IMPORT_FROM=71, + IMPORT_NAME=72, + IS_OP=73, + JUMP_BACKWARD=74, + JUMP_BACKWARD_NO_INTERRUPT=75, + JUMP_FORWARD=76, + LIST_APPEND=77, + LIST_EXTEND=78, + LOAD_ATTR=79, + LOAD_COMMON_CONSTANT=80, + LOAD_CONST=81, + LOAD_DEREF=82, + LOAD_FAST=83, + LOAD_FAST_AND_CLEAR=84, + LOAD_FAST_BORROW=85, + LOAD_FAST_BORROW_LOAD_FAST_BORROW=86, + LOAD_FAST_CHECK=87, + LOAD_FAST_LOAD_FAST=88, + LOAD_FROM_DICT_OR_DEREF=89, + LOAD_FROM_DICT_OR_GLOBALS=90, + LOAD_GLOBAL=91, + LOAD_NAME=92, + LOAD_SMALL_INT=93, + LOAD_SPECIAL=94, + LOAD_SUPER_ATTR=95, + MAKE_CELL=96, + MAP_ADD=97, + MATCH_CLASS=98, + POP_JUMP_IF_FALSE=99, + POP_JUMP_IF_NONE=100, + POP_JUMP_IF_NOT_NONE=101, + POP_JUMP_IF_TRUE=102, + RAISE_VARARGS=103, + RERAISE=104, + SEND=105, + SET_ADD=106, + SET_FUNCTION_ATTRIBUTE=107, + SET_UPDATE=108, + STORE_ATTR=109, + STORE_DEREF=110, + STORE_FAST=111, + STORE_FAST_LOAD_FAST=112, + STORE_FAST_STORE_FAST=113, + STORE_GLOBAL=114, + STORE_NAME=115, + SWAP=116, + UNPACK_EX=117, + UNPACK_SEQUENCE=118, + YIELD_VALUE=119, + INSTRUMENTED_END_FOR=233, + INSTRUMENTED_POP_ITER=234, + INSTRUMENTED_END_SEND=235, + INSTRUMENTED_FOR_ITER=236, + INSTRUMENTED_INSTRUCTION=237, + INSTRUMENTED_JUMP_FORWARD=238, + INSTRUMENTED_NOT_TAKEN=239, + INSTRUMENTED_POP_JUMP_IF_TRUE=240, + INSTRUMENTED_POP_JUMP_IF_FALSE=241, + INSTRUMENTED_POP_JUMP_IF_NONE=242, + INSTRUMENTED_POP_JUMP_IF_NOT_NONE=243, + INSTRUMENTED_RESUME=244, + INSTRUMENTED_RETURN_VALUE=245, + INSTRUMENTED_YIELD_VALUE=246, + INSTRUMENTED_END_ASYNC_FOR=247, + INSTRUMENTED_LOAD_SUPER_ATTR=248, + INSTRUMENTED_CALL=249, + INSTRUMENTED_CALL_KW=250, + INSTRUMENTED_CALL_FUNCTION_EX=251, + INSTRUMENTED_JUMP_BACKWARD=252, + ANNOTATIONS_PLACEHOLDER=256, + JUMP=257, + JUMP_IF_FALSE=258, + JUMP_IF_TRUE=259, + JUMP_NO_INTERRUPT=260, + LOAD_CLOSURE=261, + POP_BLOCK=262, + SETUP_CLEANUP=263, + SETUP_FINALLY=264, + SETUP_WITH=265, + STORE_FAST_MAYBE_NULL=266, +) -opmap = { - 'CACHE': 0, - 'RESERVED': 17, - 'RESUME': 128, - 'INSTRUMENTED_LINE': 254, - 'ENTER_EXECUTOR': 255, - 'BINARY_SLICE': 1, - 'BUILD_TEMPLATE': 2, - 'CALL_FUNCTION_EX': 4, - 'CHECK_EG_MATCH': 5, - 'CHECK_EXC_MATCH': 6, - 'CLEANUP_THROW': 7, - 'DELETE_SUBSCR': 8, - 'END_FOR': 9, - 'END_SEND': 10, - 'EXIT_INIT_CHECK': 11, - 'FORMAT_SIMPLE': 12, - 'FORMAT_WITH_SPEC': 13, - 'GET_AITER': 14, - 'GET_ANEXT': 15, - 'GET_ITER': 16, - 'GET_LEN': 18, - 'GET_YIELD_FROM_ITER': 19, - 'INTERPRETER_EXIT': 20, - 'LOAD_BUILD_CLASS': 21, - 'LOAD_LOCALS': 22, - 'MAKE_FUNCTION': 23, - 'MATCH_KEYS': 24, - 'MATCH_MAPPING': 25, - 'MATCH_SEQUENCE': 26, - 'NOP': 27, - 'NOT_TAKEN': 28, - 'POP_EXCEPT': 29, - 'POP_ITER': 30, - 'POP_TOP': 31, - 'PUSH_EXC_INFO': 32, - 'PUSH_NULL': 33, - 'RETURN_GENERATOR': 34, - 'RETURN_VALUE': 35, - 'SETUP_ANNOTATIONS': 36, - 'STORE_SLICE': 37, - 'STORE_SUBSCR': 38, - 'TO_BOOL': 39, - 'UNARY_INVERT': 40, - 'UNARY_NEGATIVE': 41, - 'UNARY_NOT': 42, - 'WITH_EXCEPT_START': 43, - 'BINARY_OP': 44, - 'BUILD_INTERPOLATION': 45, - 'BUILD_LIST': 46, - 'BUILD_MAP': 47, - 'BUILD_SET': 48, - 'BUILD_SLICE': 49, - 'BUILD_STRING': 50, - 'BUILD_TUPLE': 51, - 'CALL': 52, - 'CALL_INTRINSIC_1': 53, - 'CALL_INTRINSIC_2': 54, - 'CALL_KW': 55, - 'COMPARE_OP': 56, - 'CONTAINS_OP': 57, - 'CONVERT_VALUE': 58, - 'COPY': 59, - 'COPY_FREE_VARS': 60, - 'DELETE_ATTR': 61, - 'DELETE_DEREF': 62, - 'DELETE_FAST': 63, - 'DELETE_GLOBAL': 64, - 'DELETE_NAME': 65, - 'DICT_MERGE': 66, - 'DICT_UPDATE': 67, - 'END_ASYNC_FOR': 68, - 'EXTENDED_ARG': 69, - 'FOR_ITER': 70, - 'GET_AWAITABLE': 71, - 'IMPORT_FROM': 72, - 'IMPORT_NAME': 73, - 'IS_OP': 74, - 'JUMP_BACKWARD': 75, - 'JUMP_BACKWARD_NO_INTERRUPT': 76, - 'JUMP_FORWARD': 77, - 'LIST_APPEND': 78, - 'LIST_EXTEND': 79, - 'LOAD_ATTR': 80, - 'LOAD_COMMON_CONSTANT': 81, - 'LOAD_CONST': 82, - 'LOAD_DEREF': 83, - 'LOAD_FAST': 84, - 'LOAD_FAST_AND_CLEAR': 85, - 'LOAD_FAST_BORROW': 86, - 'LOAD_FAST_BORROW_LOAD_FAST_BORROW': 87, - 'LOAD_FAST_CHECK': 88, - 'LOAD_FAST_LOAD_FAST': 89, - 'LOAD_FROM_DICT_OR_DEREF': 90, - 'LOAD_FROM_DICT_OR_GLOBALS': 91, - 'LOAD_GLOBAL': 92, - 'LOAD_NAME': 93, - 'LOAD_SMALL_INT': 94, - 'LOAD_SPECIAL': 95, - 'LOAD_SUPER_ATTR': 96, - 'MAKE_CELL': 97, - 'MAP_ADD': 98, - 'MATCH_CLASS': 99, - 'POP_JUMP_IF_FALSE': 100, - 'POP_JUMP_IF_NONE': 101, - 'POP_JUMP_IF_NOT_NONE': 102, - 'POP_JUMP_IF_TRUE': 103, - 'RAISE_VARARGS': 104, - 'RERAISE': 105, - 'SEND': 106, - 'SET_ADD': 107, - 'SET_FUNCTION_ATTRIBUTE': 108, - 'SET_UPDATE': 109, - 'STORE_ATTR': 110, - 'STORE_DEREF': 111, - 'STORE_FAST': 112, - 'STORE_FAST_LOAD_FAST': 113, - 'STORE_FAST_STORE_FAST': 114, - 'STORE_GLOBAL': 115, - 'STORE_NAME': 116, - 'SWAP': 117, - 'UNPACK_EX': 118, - 'UNPACK_SEQUENCE': 119, - 'YIELD_VALUE': 120, - 'INSTRUMENTED_END_FOR': 234, - 'INSTRUMENTED_POP_ITER': 235, - 'INSTRUMENTED_END_SEND': 236, - 'INSTRUMENTED_FOR_ITER': 237, - 'INSTRUMENTED_INSTRUCTION': 238, - 'INSTRUMENTED_JUMP_FORWARD': 239, - 'INSTRUMENTED_NOT_TAKEN': 240, - 'INSTRUMENTED_POP_JUMP_IF_TRUE': 241, - 'INSTRUMENTED_POP_JUMP_IF_FALSE': 242, - 'INSTRUMENTED_POP_JUMP_IF_NONE': 243, - 'INSTRUMENTED_POP_JUMP_IF_NOT_NONE': 244, - 'INSTRUMENTED_RESUME': 245, - 'INSTRUMENTED_RETURN_VALUE': 246, - 'INSTRUMENTED_YIELD_VALUE': 247, - 'INSTRUMENTED_END_ASYNC_FOR': 248, - 'INSTRUMENTED_LOAD_SUPER_ATTR': 249, - 'INSTRUMENTED_CALL': 250, - 'INSTRUMENTED_CALL_KW': 251, - 'INSTRUMENTED_CALL_FUNCTION_EX': 252, - 'INSTRUMENTED_JUMP_BACKWARD': 253, - 'ANNOTATIONS_PLACEHOLDER': 256, - 'JUMP': 257, - 'JUMP_IF_FALSE': 258, - 'JUMP_IF_TRUE': 259, - 'JUMP_NO_INTERRUPT': 260, - 'LOAD_CLOSURE': 261, - 'POP_BLOCK': 262, - 'SETUP_CLEANUP': 263, - 'SETUP_FINALLY': 264, - 'SETUP_WITH': 265, - 'STORE_FAST_MAYBE_NULL': 266, -} - -HAVE_ARGUMENT = 43 -MIN_INSTRUMENTED_OPCODE = 234 +HAVE_ARGUMENT = 41 +MIN_INSTRUMENTED_OPCODE = 233 diff --git a/Lib/dis.py b/Lib/dis.py index d6d2c1386dd..d60507ae473 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -35,6 +35,7 @@ FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure', 'annotate') ENTER_EXECUTOR = opmap['ENTER_EXECUTOR'] +IMPORT_NAME = opmap['IMPORT_NAME'] LOAD_GLOBAL = opmap['LOAD_GLOBAL'] LOAD_SMALL_INT = opmap['LOAD_SMALL_INT'] BINARY_OP = opmap['BINARY_OP'] @@ -530,7 +531,6 @@ def print_instruction_line(self, instr, mark_as_current): fields.append(instr.opname.ljust(_OPNAME_WIDTH)) # Column: Opcode argument if instr.arg is not None: - arg = repr(instr.arg) # If opname is longer than _OPNAME_WIDTH, we allow it to overflow into # the space reserved for oparg. This results in fewer misaligned opargs # in the disassembly output. @@ -601,6 +601,12 @@ def get_argval_argrepr(self, op, arg, offset): argval, argrepr = _get_name_info(arg//4, get_name) if (arg & 1) and argrepr: argrepr = f"{argrepr} + NULL|self" + elif deop == IMPORT_NAME: + argval, argrepr = _get_name_info(arg//4, get_name) + if (arg & 1) and argrepr: + argrepr = f"{argrepr} + lazy" + elif (arg & 2) and argrepr: + argrepr = f"{argrepr} + eager" else: argval, argrepr = _get_name_info(arg, get_name) elif deop in hasjump or deop in hasexc: @@ -637,6 +643,7 @@ def get_argval_argrepr(self, op, arg, offset): argrepr = _intrinsic_2_descs[arg] elif deop == LOAD_COMMON_CONSTANT: obj = _common_constants[arg] + argval = obj if isinstance(obj, type): argrepr = obj.__name__ else: @@ -686,10 +693,15 @@ def _get_const_value(op, arg, co_consts): Otherwise (if it is a LOAD_CONST and co_consts is not provided) returns the dis.UNKNOWN sentinel. """ - assert op in hasconst or op == LOAD_SMALL_INT + assert op in hasconst or op == LOAD_SMALL_INT or op == LOAD_COMMON_CONSTANT if op == LOAD_SMALL_INT: return arg + if op == LOAD_COMMON_CONSTANT: + # Opargs 0-6 are callables; 7-11 are literal values. + if 7 <= arg <= 11: + return _common_constants[arg] + return UNKNOWN argval = UNKNOWN if co_consts is not None: argval = co_consts[arg] @@ -1009,11 +1021,14 @@ def _find_imports(co): if op == IMPORT_NAME and i >= 2: from_op = opargs[i-1] level_op = opargs[i-2] - if (from_op[0] in hasconst and - (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)): + if ((from_op[0] in hasconst or from_op[0] == LOAD_COMMON_CONSTANT) and + (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT or + level_op[0] == LOAD_COMMON_CONSTANT)): level = _get_const_value(level_op[0], level_op[1], consts) fromlist = _get_const_value(from_op[0], from_op[1], consts) - yield (names[oparg], level, fromlist) + # IMPORT_NAME encodes lazy/eager flags in bits 0-1, + # name index in bits 2+. + yield (names[oparg >> 2], level, fromlist) def _find_store_names(co): """Find names of variables which are written in the code @@ -1131,7 +1146,7 @@ def dis(self): def main(args=None): import argparse - parser = argparse.ArgumentParser(color=True) + parser = argparse.ArgumentParser() parser.add_argument('-C', '--show-caches', action='store_true', help='show inline caches') parser.add_argument('-O', '--show-offsets', action='store_true', diff --git a/Lib/opcode.py b/Lib/opcode.py index 0e9520b6832..4e60fb5af34 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -40,83 +40,96 @@ _intrinsic_2_descs = _opcode.get_intrinsic2_descs() _special_method_names = _opcode.get_special_method_names() _common_constants = [builtins.AssertionError, builtins.NotImplementedError, - builtins.tuple, builtins.all, builtins.any] + builtins.tuple, builtins.all, builtins.any, builtins.list, + builtins.set, + # Append-only — must match CONSTANT_* in + # Include/internal/pycore_opcode_utils.h. + None, "", True, False, -1] _nb_ops = _opcode.get_nb_ops() hascompare = [opmap["COMPARE_OP"]] -_cache_format = { - "LOAD_GLOBAL": { - "counter": 1, - "index": 1, - "module_keys_version": 1, - "builtin_keys_version": 1, - }, - "BINARY_OP": { - "counter": 1, - "descr": 4, - }, - "UNPACK_SEQUENCE": { - "counter": 1, - }, - "COMPARE_OP": { - "counter": 1, - }, - "CONTAINS_OP": { - "counter": 1, - }, - "FOR_ITER": { - "counter": 1, - }, - "LOAD_SUPER_ATTR": { - "counter": 1, - }, - "LOAD_ATTR": { - "counter": 1, - "version": 2, - "keys_version": 2, - "descr": 4, - }, - "STORE_ATTR": { - "counter": 1, - "version": 2, - "index": 1, - }, - "CALL": { - "counter": 1, - "func_version": 2, - }, - "CALL_KW": { - "counter": 1, - "func_version": 2, - }, - "STORE_SUBSCR": { - "counter": 1, - }, - "SEND": { - "counter": 1, - }, - "JUMP_BACKWARD": { - "counter": 1, - }, - "TO_BOOL": { - "counter": 1, - "version": 2, - }, - "POP_JUMP_IF_TRUE": { - "counter": 1, - }, - "POP_JUMP_IF_FALSE": { - "counter": 1, - }, - "POP_JUMP_IF_NONE": { - "counter": 1, - }, - "POP_JUMP_IF_NOT_NONE": { - "counter": 1, - }, -} +_cache_format = frozendict( + LOAD_GLOBAL=frozendict( + counter=1, + index=1, + module_keys_version=1, + builtin_keys_version=1, + ), + BINARY_OP=frozendict( + counter=1, + descr=4, + ), + UNPACK_SEQUENCE=frozendict( + counter=1, + ), + COMPARE_OP=frozendict( + counter=1, + ), + CONTAINS_OP=frozendict( + counter=1, + ), + FOR_ITER=frozendict( + counter=1, + ), + GET_ITER=frozendict( + counter=1, + ), + LOAD_SUPER_ATTR=frozendict( + counter=1, + ), + LOAD_ATTR=frozendict( + counter=1, + version=2, + keys_version=2, + descr=4, + ), + STORE_ATTR=frozendict( + counter=1, + version=2, + index=1, + ), + CALL=frozendict( + counter=1, + func_version=2, + ), + CALL_KW=frozendict( + counter=1, + func_version=2, + ), + CALL_FUNCTION_EX=frozendict( + counter=1, + ), + STORE_SUBSCR=frozendict( + counter=1, + ), + SEND=frozendict( + counter=1, + ), + JUMP_BACKWARD=frozendict( + counter=1, + ), + TO_BOOL=frozendict( + counter=1, + version=2, + ), + POP_JUMP_IF_TRUE=frozendict( + counter=1, + ), + POP_JUMP_IF_FALSE=frozendict( + counter=1, + ), + POP_JUMP_IF_NONE=frozendict( + counter=1, + ), + POP_JUMP_IF_NOT_NONE=frozendict( + counter=1, + ), + RESUME=frozendict( + counter=1, + ), +) -_inline_cache_entries = { +_inline_cache_entries = frozendict({ name : sum(value.values()) for (name, value) in _cache_format.items() -} +}) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 95c1ceb8098..06de2e9dfd1 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -68,6 +68,7 @@ const CO_MAXBLOCKS: usize = 21; pub enum FBlockType { WhileLoop, ForLoop, + AsyncForLoop, TryExcept, FinallyTry, FinallyEnd, @@ -107,6 +108,8 @@ enum SuperCallType<'a> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum BuiltinGeneratorCallKind { Tuple, + List, + Set, All, Any, } @@ -2483,17 +2486,27 @@ impl<'warnings> Compiler<'warnings> { // No cleanup needed } - FBlockType::ForLoop => { - // When returning from a for-loop, CPython swaps the preserved - // value with the iterator and uses POP_TOP for loop cleanup. + FBlockType::ForLoop | FBlockType::AsyncForLoop => { + let iterator_slots = if info.fb_type == FBlockType::ForLoop { + 2 + } else { + 1 + }; if preserve_tos { self.set_unwind_source_range(*loc); - emit!(self, Instruction::Swap { i: 2 }); + emit!( + self, + Instruction::Swap { + i: iterator_slots + 1 + } + ); + self.mark_unwind_no_location(*loc); + } + for _ in 0..iterator_slots { + self.set_unwind_source_range(*loc); + emit!(self, Instruction::PopTop); self.mark_unwind_no_location(*loc); } - self.set_unwind_source_range(*loc); - emit!(self, Instruction::PopTop); - self.mark_unwind_no_location(*loc); } FBlockType::TryExcept => { @@ -2598,6 +2611,8 @@ impl<'warnings> Compiler<'warnings> { self.set_unwind_source_range(*loc); emit!(self, Instruction::GetAwaitable { r#where: 2 }); self.set_unwind_source_range(*loc); + emit!(self, Instruction::PushNull); + self.set_unwind_source_range(*loc); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -2699,7 +2714,12 @@ impl<'warnings> Compiler<'warnings> { *unwind_loc, )); } - if stop_at_loop && matches!(top.fb_type, FBlockType::WhileLoop | FBlockType::ForLoop) { + if stop_at_loop + && matches!( + top.fb_type, + FBlockType::WhileLoop | FBlockType::ForLoop | FBlockType::AsyncForLoop + ) + { return Ok(Some(top)); } @@ -3507,6 +3527,28 @@ impl<'warnings> Compiler<'warnings> { Ok(()) } + /// Ordinary imports reserve two low bits for the import policy. + fn ordinary_import_flags(&mut self, star: bool) -> u32 { + if star || self.current_symbol_table().typ != CompilerScope::Module { + return 2; + } + if self.current_code_info().fblock.iter().any(|block| { + matches!( + block.fb_type, + FBlockType::TryExcept + | FBlockType::FinallyTry + | FBlockType::FinallyEnd + | FBlockType::ExceptionHandler + | FBlockType::ExceptionGroupHandler + | FBlockType::HandlerCleanup + ) + }) { + 2 + } else { + 0 + } + } + fn compile_statement(&mut self, statement: &ast::Stmt) -> CompileResult<()> { trace!("Compiling {statement:?}"); let prev_source_range = self.current_source_range; @@ -3535,7 +3577,7 @@ impl<'warnings> Compiler<'warnings> { value: num_traits::Zero::zero(), }); self.emit_load_const(ConstantData::None); - let namei = self.name(&name.name); + let namei = (self.name(&name.name) << 2) | self.ordinary_import_flags(false); emit!(self, Instruction::ImportName { namei }); if let Some(alias) = &name.asname { let parts: Vec<&str> = name.name.split('.').skip(1).collect(); @@ -3580,7 +3622,8 @@ impl<'warnings> Compiler<'warnings> { }); let module_name = module.as_ref().map_or("", |s| s.as_str()); - let module_idx = self.name(module_name); + let module_idx = + (self.name(module_name) << 2) | self.ordinary_import_flags(import_star); emit!(self, Instruction::ImportName { namei: module_idx }); if import_star { @@ -6415,6 +6458,7 @@ impl<'warnings> Compiler<'warnings> { ); // [aexit_func, self_ae, aenter_func, self_an] emit!(self, Instruction::Call { argc: 0 }); // [aexit_func, self_ae, awaitable] emit!(self, Instruction::GetAwaitable { r#where: 1 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } else { @@ -6499,6 +6543,7 @@ impl<'warnings> Compiler<'warnings> { self.compile_call_exit_with_nones(); if is_async { emit!(self, Instruction::GetAwaitable { r#where: 2 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -6525,6 +6570,7 @@ impl<'warnings> Compiler<'warnings> { if is_async { emit!(self, Instruction::GetAwaitable { r#where: 2 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -6589,7 +6635,7 @@ impl<'warnings> Compiler<'warnings> { // codegen_async_for: push fblock BEFORE SETUP_FINALLY self.push_fblock_labels( - FBlockType::ForLoop, + FBlockType::AsyncForLoop, for_label, after_label, FBlockDatum::None, @@ -6598,6 +6644,7 @@ impl<'warnings> Compiler<'warnings> { // SETUP_FINALLY to guard the __anext__ call emit!(self, PseudoInstruction::SetupFinally { delta: else_block }); emit!(self, Instruction::GetAnext); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); self.use_cpython_label_block(send_block); let _ = self.compile_yield_from_sequence(true); @@ -6611,7 +6658,7 @@ impl<'warnings> Compiler<'warnings> { } else { // Retrieve Iterator self.set_source_range(iter.range()); - emit!(self, Instruction::GetIter); + emit!(self, Instruction::GetIter { mode: 0 }); self.use_cpython_label_block(for_block); @@ -6636,7 +6683,7 @@ impl<'warnings> Compiler<'warnings> { // codegen_async_for() pops the loop fblock before the // END_ASYNC_FOR exception block. Sync codegen_for() keeps the // fblock through END_FOR/POP_ITER and pops below. - self.pop_fblock_label(FBlockType::ForLoop, for_label); + self.pop_fblock_label(FBlockType::AsyncForLoop, for_label); } self.use_cpython_label_block(else_block); @@ -6676,7 +6723,7 @@ impl<'warnings> Compiler<'warnings> { if generator.is_async { emit!(self, Instruction::GetAiter); } else { - emit!(self, Instruction::GetIter); + emit!(self, Instruction::GetIter { mode: 0 }); } self.set_source_range(saved_range); Ok(()) @@ -8818,8 +8865,8 @@ impl<'warnings> Compiler<'warnings> { ); // fail: CLEANUP_THROW - // Stack when exception: [receiver, yielded_value, exc] - // CLEANUP_THROW: [sub_iter, last_sent_val, exc] -> [None, value] + // Stack when exception: [receiver, NULL, yielded_value, exc] + // CLEANUP_THROW: [sub_iter, NULL, last_sent_val, exc] -> [None, NULL, value] // CPython lets this block fall through to END_SEND during codegen; // push_cold_blocks_to_end later inserts the no-interrupt jump after // moving the cold fail block behind the warm exit path. @@ -8827,8 +8874,8 @@ impl<'warnings> Compiler<'warnings> { emit!(self, Instruction::CleanupThrow); // exit: END_SEND - // Stack: [receiver, value] (from SEND) or [None, value] (from CLEANUP_THROW) - // END_SEND: [receiver/None, value] -> [value] + // Stack: [receiver, NULL, value] or [None, NULL, value] after CLEANUP_THROW. + // END_SEND: [receiver/None, NULL, value] -> [value] self.use_cpython_label_block(exit_block); emit!(self, Instruction::EndSend); @@ -9036,6 +9083,7 @@ impl<'warnings> Compiler<'warnings> { self.compile_expression(value)?; self.set_source_range(range); emit!(self, Instruction::GetAwaitable { r#where: 0 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -9052,7 +9100,7 @@ impl<'warnings> Compiler<'warnings> { self.mark_generator(); self.compile_expression(value)?; self.set_source_range(range); - emit!(self, Instruction::GetYieldFromIter); + emit!(self, Instruction::GetIter { mode: 1 }); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(false); } @@ -9428,6 +9476,8 @@ impl<'warnings> Compiler<'warnings> { fn detect_builtin_generator_call(&self, name: &str) -> Option { match name { "tuple" => Some(BuiltinGeneratorCallKind::Tuple), + "list" => Some(BuiltinGeneratorCallKind::List), + "set" => Some(BuiltinGeneratorCallKind::Set), "all" => Some(BuiltinGeneratorCallKind::All), "any" => Some(BuiltinGeneratorCallKind::Any), _ => None, @@ -9448,6 +9498,8 @@ impl<'warnings> Compiler<'warnings> { ) -> CompileResult<()> { let common_constant = match kind { BuiltinGeneratorCallKind::Tuple => bytecode::CommonConstant::BuiltinTuple, + BuiltinGeneratorCallKind::List => bytecode::CommonConstant::BuiltinList, + BuiltinGeneratorCallKind::Set => bytecode::CommonConstant::BuiltinSet, BuiltinGeneratorCallKind::All => bytecode::CommonConstant::BuiltinAll, BuiltinGeneratorCallKind::Any => bytecode::CommonConstant::BuiltinAny, }; @@ -9472,14 +9524,23 @@ impl<'warnings> Compiler<'warnings> { ); emit!(self, Instruction::PopTop); - if matches!(kind, BuiltinGeneratorCallKind::Tuple) { - self.set_source_range(loc); - emit!(self, Instruction::BuildList { count: 0 }); + match kind { + BuiltinGeneratorCallKind::Tuple | BuiltinGeneratorCallKind::List => { + self.set_source_range(loc); + emit!(self, Instruction::BuildList { count: 0 }); + } + BuiltinGeneratorCallKind::Set => { + self.set_source_range(loc); + emit!(self, Instruction::BuildSet { count: 0 }); + } + BuiltinGeneratorCallKind::All | BuiltinGeneratorCallKind::Any => {} } let symbol_table_cursors = self.current_symbol_table_cursors(); self.compile_expression(generator_expr)?; self.set_symbol_table_cursors(symbol_table_cursors); + self.set_source_range(loc); + emit!(self, Instruction::PushNull); let loop_block = self.new_block(); let cleanup = self.new_block(); @@ -9488,9 +9549,15 @@ impl<'warnings> Compiler<'warnings> { emit!(self, Instruction::ForIter { delta: cleanup }); match kind { - BuiltinGeneratorCallKind::Tuple => { + BuiltinGeneratorCallKind::Tuple | BuiltinGeneratorCallKind::List => { + self.set_source_range(loc); + emit!(self, Instruction::ListAppend { i: 3 }); + self.set_source_range(loc); + emit!(self, PseudoInstruction::Jump { delta: loop_block }); + } + BuiltinGeneratorCallKind::Set => { self.set_source_range(loc); - emit!(self, Instruction::ListAppend { i: 2 }); + emit!(self, Instruction::SetAdd { i: 3 }); self.set_source_range(loc); emit!(self, PseudoInstruction::Jump { delta: loop_block }); } @@ -9533,6 +9600,7 @@ impl<'warnings> Compiler<'warnings> { } ); } + BuiltinGeneratorCallKind::List | BuiltinGeneratorCallKind::Set => {} BuiltinGeneratorCallKind::All => { self.set_source_range(loc); self.emit_load_const(ConstantData::Boolean { value: true }); @@ -9716,7 +9784,7 @@ impl<'warnings> Compiler<'warnings> { if genexpr_call_name.is_some() { // CPython `maybe_optimize_function_call()` creates and uses // `skip_optimization` for every name(genexpr) shape after - // loading the function, even when the name is not all/any/tuple. + // loading the function, even when the name is not all/any/tuple/list/set. let skip_optimization = self.current_code_info().new_instr_sequence_label(); let result = self .current_code_info() @@ -10716,6 +10784,22 @@ impl<'warnings> Compiler<'warnings> { let stop_iteration_block = if comprehension_type == ComprehensionType::Generator { let handler_block = self.new_block(); self.insert_cpython_stopiteration_setup_cleanup(handler_block); + let source = self.source_file.to_source_code(); + let iterator = ir::InstructionInfo { + instr: if outermost.is_async { + bytecode::Opcode::GetAiter.into() + } else { + bytecode::Opcode::GetIter.into() + }, + arg: OpArg::NULL, + target: BlockIdx::NULL, + location: source.source_location(outermost.iter.start(), PositionEncoding::Utf8), + end_location: source.source_location(outermost.iter.end(), PositionEncoding::Utf8), + except_handler: None, + lineno_override: None, + }; + let result = self.current_code_info().insert_genexpr_prefix(iterator); + unwrap_internal(self, result); Some(handler_block) } else { None @@ -10764,8 +10848,15 @@ impl<'warnings> Compiler<'warnings> { }; if gen_index == 0 { - // Load iterator onto stack (passed as first argument): - emit!(self, Instruction::LoadFast { var_num: arg0 }); + // Genexpr creation has already left the acquired iterator on the stack. + if comprehension_type != ComprehensionType::Generator { + emit!(self, Instruction::LoadFast { var_num: arg0 }); + if generator.is_async { + emit!(self, Instruction::GetAiter); + } else { + emit!(self, Instruction::GetIter { mode: 0 }); + } + } } else { // Evaluate iterated item: self.compile_comprehension_iter(generator)?; @@ -10783,6 +10874,7 @@ impl<'warnings> Compiler<'warnings> { )?; emit!(self, PseudoInstruction::SetupFinally { delta: after_block }); emit!(self, Instruction::GetAnext); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); self.use_cpython_label_block(send_block); let _ = self.compile_yield_from_sequence(true); @@ -10798,7 +10890,7 @@ impl<'warnings> Compiler<'warnings> { self.set_source_range(saved_range); self.compile_store(&generator.target)?; } - real_loop_depth += 1; + real_loop_depth += if generator.is_async { 1 } else { 2 }; let backedge_range = if gen_index + 1 == generators.len() { element_range } else { @@ -10887,8 +10979,8 @@ impl<'warnings> Compiler<'warnings> { self.set_source_range(comprehension_range); self.make_closure(code, bytecode::MakeFunctionFlags::new())?; - // Evaluate iterated item and get its iterator. - self.compile_comprehension_iter(outermost)?; + // The callee acquires the iterator; a genexpr does this eagerly before creation. + self.compile_expression(&outermost.iter)?; self.symbol_table_stack .last_mut() .expect("no current symbol table") @@ -10899,6 +10991,7 @@ impl<'warnings> Compiler<'warnings> { emit!(self, Instruction::Call { argc: 0 }); if is_async_list_set_dict_comprehension { emit!(self, Instruction::GetAwaitable { r#where: 0 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -10922,7 +11015,7 @@ impl<'warnings> Compiler<'warnings> { // nested scopes (e.g. lambdas) whose sub_tables sit at the current // position in the parent's list. Those must be consumed before we // splice in the comprehension's own children. - self.compile_comprehension_iter(&generators[0])?; + self.compile_expression(&generators[0].iter)?; match comp_source { ComprehensionSymbolSource::Child => { self.symbol_table_stack @@ -11131,6 +11224,15 @@ impl<'warnings> Compiler<'warnings> { if i > 0 { self.compile_comprehension_iter(generator)?; + } else { + let saved_range = self.current_source_range; + self.set_source_range(generator.iter.range()); + if generator.is_async { + emit!(self, Instruction::GetAiter); + } else { + emit!(self, Instruction::GetIter { mode: 0 }); + } + self.set_source_range(saved_range); } self.use_cpython_label_block(loop_block); @@ -11146,6 +11248,7 @@ impl<'warnings> Compiler<'warnings> { )?; emit!(self, PseudoInstruction::SetupFinally { delta: after_block }); emit!(self, Instruction::GetAnext); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); self.use_cpython_label_block(send_block); let _ = self.compile_yield_from_sequence(true); @@ -11160,7 +11263,7 @@ impl<'warnings> Compiler<'warnings> { self.compile_store(&generator.target)?; } - real_loop_depth += 1; + real_loop_depth += if generator.is_async { 1 } else { 2 }; let backedge_range = if i + 1 == generators.len() { element_range } else { @@ -11478,10 +11581,9 @@ impl<'warnings> Compiler<'warnings> { self.set_no_location(); } - /// CPython `codegen_wrap_in_stopiteration_handler()` inserts - /// `SETUP_CLEANUP` into the instruction sequence at index 0 with - /// `_PyInstructionSequence_InsertInstruction()`. Generator and cell/free - /// prefixes are inserted later by `flowgraph.c::insert_prefix_instructions()`. + /// Insert StopIteration cleanup before the ordinary function-start RESUME. + /// Generator creation prefixes are added before this protected body, either + /// by insert_genexpr_prefix() or during locals-plus preparation. fn insert_cpython_stopiteration_setup_cleanup(&mut self, handler_block: BlockIdx) { let code = self.current_code_info(); let entry = code @@ -12221,7 +12323,7 @@ impl<'warnings> Compiler<'warnings> { let mut found_loop = false; for i in (0..code.fblock.len()).rev() { match code.fblock[i].fb_type { - FBlockType::WhileLoop | FBlockType::ForLoop => { + FBlockType::WhileLoop | FBlockType::ForLoop | FBlockType::AsyncForLoop => { found_loop = true; break; } @@ -14628,7 +14730,7 @@ match x: // codegen emits the implicit LOAD_CONST/RETURN_VALUE with // NO_LOCATION, then flowgraph.c::propagate_line_numbers() propagates // the module RESUME location, whose line is 0. - assert_eq!(code.linetable.as_ref(), &[0xf2, 0x03, 0x01, 0x01, 0x01]); + assert_eq!(code.linetable.as_ref(), &[0xf3, 0x03, 0x01, 0x01, 0x01]); } #[test] @@ -14646,7 +14748,7 @@ x = 1 assert_eq!( code.linetable.as_ref(), &[ - 0xf0, 0x03, 0x01, 0x01, 0x01, 0xd9, 0x00, 0x05, 0xd8, 0x04, 0x05, 0x82, 0x01, + 0xf1, 0x03, 0x01, 0x01, 0x01, 0xd9, 0x00, 0x05, 0xd8, 0x04, 0x05, 0x82, 0x01 ], ); } @@ -14673,10 +14775,10 @@ def f(x, y, z): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdf, 0x0a, 0x0b, 0xdf, 0x0b, 0x0c, 0xd9, 0x0c, 0x10, 0xdf, 0x0d, 0x0e, + 0x81, 0x00, 0xdf, 0x0a, 0x0b, 0xdf, 0x0b, 0x0c, 0xd9, 0x0c, 0x10, 0xdf, 0x0d, 0x0e, 0xd8, 0x0f, 0x10, 0x90, 0x31, 0x8c, 0x75, 0xd8, 0x17, 0x18, 0x90, 0x08, 0xdf, 0x0f, 0x10, 0xd8, 0x14, 0x15, 0x98, 0x01, 0x95, 0x45, 0x92, 0x01, 0xf1, 0x03, 0x00, 0x10, - 0x11, 0xe7, 0x0d, 0x0e, 0x89, 0x51, 0xd9, 0x13, 0x14, 0xd8, 0x0b, 0x0d, 0x80, 0x49, + 0x11, 0xe7, 0x0d, 0x0e, 0x89, 0x51, 0xd9, 0x13, 0x14, 0xd8, 0x0b, 0x0d, 0x80, 0x49 ], "CPython basicblock_remove_redundant_nops() copies the full NOP location into a following no-location jump" ); @@ -14903,11 +15005,12 @@ def f(support, func, value): .expect("missing CPython-style false jump for if/break"); assert!( matches!( - ops.get(cond..cond + 5), + ops.get(cond..cond + 6), Some([ Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, Instruction::PopTop, + Instruction::PopTop, Instruction::JumpForward { .. }, Instruction::JumpBackward { .. }, ]) @@ -14951,7 +15054,7 @@ def f(input): Instruction::CompareOp { .. }, Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, Instruction::JumpBackward { .. }, ] @@ -15992,7 +16095,7 @@ def f(self): == "close" ) && matches!(window[2].op, Instruction::Call { .. }) && matches!(window[3].op, Instruction::PopTop) - && matches!(window[4].op, Instruction::LoadConst { .. }) + && matches!(window[4].op, Instruction::LoadCommonConstant { .. }) && matches!(window[5].op, Instruction::ReturnValue) }) .unwrap_or_else(|| { @@ -16521,7 +16624,7 @@ def f(buffer, pos, last_char): Instruction::NotTaken, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, ] ) @@ -16536,7 +16639,7 @@ def f(buffer, pos, last_char): Instruction::CompareOp { .. }, Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -16603,7 +16706,7 @@ def f( // CPython 3.14 codegen_function() computes firstlineno from the // FunctionDef before compiling annotations, then passes it to // codegen_function_body(). - assert_eq!(f.linetable.as_ref(), &[0x80, 0x00, 0xe1, 0x03, 0x06]); + assert_eq!(f.linetable.as_ref(), &[0x81, 0x00, 0xe1, 0x03, 0x06]); } #[test] @@ -16619,12 +16722,12 @@ def g(): // CPython 3.14 codegen_function_annotations() receives LOC(function) // and uses it for the annotation closure's BUILD_MAP/RETURN_VALUE and // for the parent MAKE_FUNCTION annotate sequence. - assert_eq!(g.linetable.as_ref(), &[0x80, 0x00, 0xdf, 0x04, 0x26]); + assert_eq!(g.linetable.as_ref(), &[0x81, 0x00, 0xdf, 0x04, 0x26]); assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0xd7, 0x04, 0x26, 0xd1, 0x04, 0x26, 0x94, 0x23, 0x9c, 0x13, 0xd0, 0x0d, - 0x1d, 0xd1, 0x04, 0x26, + 0x81, 0x00, 0xd7, 0x04, 0x26, 0xd1, 0x04, 0x26, 0x94, 0x23, 0x9c, 0x13, 0xd0, 0x0d, + 0x1d, 0xd1, 0x04, 0x26 ], ); } @@ -16639,7 +16742,7 @@ def g(): assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0xd7, 0x00, 0x17, 0xd1, 0x00, 0x17, 0x8c, 0x62, 0xd3, 0x00, 0x17 + 0x81, 0x00, 0xd7, 0x00, 0x17, 0xd1, 0x00, 0x17, 0x8c, 0x62, 0xd3, 0x00, 0x17 ], ); } @@ -16662,9 +16765,9 @@ Y: str assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0x87, 0x09, 0x81, 0x09, 0xdf, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, + 0x81, 0x00, 0x87, 0x09, 0x81, 0x09, 0xdf, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, 0x06, 0xf1, 0x03, 0x00, 0x01, 0x0a, 0xe7, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, - 0x06, 0xf2, 0x05, 0x00, 0x01, 0x0a, + 0x06, 0xf2, 0x05, 0x00, 0x01, 0x0a ] ); } @@ -16935,7 +17038,7 @@ def f(self, other): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0f, 0x13, 0xd2, 0x0b, 0x1c, 0xd0, 0x04, 0x1c, + 0x81, 0x00, 0xd8, 0x0f, 0x13, 0xd2, 0x0b, 0x1c, 0xd0, 0x04, 0x1c ] ); } @@ -16957,9 +17060,9 @@ def f(c): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x10, 0x14, 0x98, 0x01, 0xd7, 0x10, 0x21, 0xd4, 0x10, 0x21, 0x98, + 0x81, 0x00, 0xd8, 0x10, 0x14, 0x98, 0x01, 0xd7, 0x10, 0x21, 0xd4, 0x10, 0x21, 0x98, 0x54, 0xd1, 0x10, 0x21, 0xd4, 0x0b, 0x22, 0xd0, 0x04, 0x22, 0xd1, 0x10, 0x21, 0xd4, - 0x0b, 0x22, 0xd0, 0x04, 0x22, + 0x0b, 0x22, 0xd0, 0x04, 0x22 ] ); } @@ -16979,19 +17082,19 @@ def f(c): assert_eq!( type_params.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0xd0, 0x00, 0x27, 0x90, 0x76, 0x9b, 0x23, 0x93, 0x76, 0xd7, 0x00, - 0x27, 0xd1, 0x00, 0x27, + 0xf8, 0x81, 0x00, 0xd0, 0x00, 0x27, 0x90, 0x76, 0x9b, 0x23, 0x93, 0x76, 0xd7, 0x00, + 0x27, 0xd1, 0x00, 0x27 ], ); assert_eq!( bound.linetable.as_ref(), - &[0x80, 0x00, 0x9f, 0x23, 0x9e, 0x23] + &[0x81, 0x00, 0x9f, 0x23, 0x9e, 0x23] ); assert_eq!( alias.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0xd7, 0x00, 0x27, 0xd0, 0x00, 0x27, 0xa4, 0x13, 0xa0, 0x51, 0xa5, - 0x16, 0xd0, 0x00, 0x27, + 0xf8, 0x81, 0x00, 0xd7, 0x00, 0x27, 0xd0, 0x00, 0x27, 0xa4, 0x13, 0xa0, 0x51, 0xa5, + 0x16, 0xd0, 0x00, 0x27 ], ); } @@ -17024,7 +17127,7 @@ def f(c): assert_eq!( type_params.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0x80, 0x0d, 0x84, 0x71, 0x87, 0x0d, 0x81, 0x0d + 0xf8, 0x81, 0x00, 0x80, 0x0d, 0x84, 0x71, 0x87, 0x0d, 0x81, 0x0d ], ); } @@ -17077,7 +17180,7 @@ def f(c): assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0xd7, 0x00, 0x15, 0xd1, 0x00, 0x15, 0x8c, 0x43, 0xd1, 0x00, 0x15, + 0x81, 0x00, 0xd7, 0x00, 0x15, 0xd1, 0x00, 0x15, 0x8c, 0x43, 0xd1, 0x00, 0x15 ], ); } @@ -17115,7 +17218,7 @@ def outer(): assert_eq!( type_params.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f, + 0xf8, 0x81, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f ] ); } @@ -17133,9 +17236,9 @@ def f(): let wrapper_positions: Vec<_> = f .instructions .iter() - .filter(|unit| !matches!(unit.op, Instruction::Resume { .. })) + .zip(&f.locations) + .filter(|(unit, _)| !matches!(unit.op, Instruction::Resume { .. } | Instruction::Cache)) .take(4) - .zip(f.locations.iter().filter(|_| true).skip(1)) .map(|(unit, (location, end_location))| { ( unit.op, @@ -17362,8 +17465,8 @@ class C: assert_eq!( class_code.linetable.as_ref(), &[ - 0xf8, 0x87, 0x00, 0x80, 0x00, 0xd9, 0x04, 0x09, 0xf7, 0x03, 0x00, 0x01, 0x01, 0x83, - 0x00, + 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd9, 0x04, 0x09, 0xf7, 0x03, 0x00, 0x01, 0x01, 0x83, + 0x00 ], ); } @@ -17378,7 +17481,7 @@ class C: // the __annotations__ store sequence at LOC(AnnAssign). assert_eq!( class_code.linetable.as_ref(), - &[0x87, 0x00, 0xd8, 0x09, 0x0c, 0x87, 0x48] + &[0x87, 0x00, 0x80, 0x00, 0xd8, 0x09, 0x0c, 0x87, 0x48] ); } @@ -17456,11 +17559,11 @@ g = lambda i: {**i} // inherits the full dict literal location after compiling its body. assert_eq!( f.linetable.as_ref(), - &[0x80, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b] + &[0x81, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b] ); assert_eq!( g.linetable.as_ref(), - &[0x80, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65] + &[0x81, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65] ); } @@ -17703,11 +17806,10 @@ def f(self, payload): function.constants.as_ref(), [ ConstantData::Str { value: doc }, - ConstantData::None, ConstantData::Str { value: message }, ] if doc.as_ref() == "doc" && message.as_ref() == "bad" ), - "CPython registers None from the pre-folded `is None` comparison before the else-body string" + "CPython 3.15 keeps the docstring and else-body string in co_consts; None uses the common-constant table" ); } @@ -17811,7 +17913,7 @@ def f(self, maintype): .. }, CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, CodeUnit { @@ -18176,7 +18278,7 @@ def f(command): [ Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] )), "CPython NEXT_LOCATION keeps the pass NOP after match subject POP_TOP, got {ops:?}", @@ -18230,9 +18332,9 @@ def f(): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x08, 0x0d, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x0e, 0x97, + 0x81, 0x00, 0xd8, 0x08, 0x0d, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x0e, 0x97, 0x11, 0x97, 0x51, 0x9f, 0x11, 0x88, 0x5d, 0xe0, 0x0b, 0x0c, 0x80, 0x48, 0xf0, 0x05, - 0x00, 0x0e, 0x1b, 0xd8, 0x10, 0x14, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x80, 0x48, + 0x00, 0x0e, 0x1b, 0xd8, 0x10, 0x14, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x80, 0x48 ], "CPython codegen_pattern_or() emits each alternative COPY with LOC(alt)" ); @@ -18297,9 +18399,9 @@ def f(self): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0a, 0x0b, 0xde, 0x0d, 0x0e, 0xd9, 0x10, 0x14, 0x89, 0x41, 0xdd, + 0x81, 0x00, 0xd8, 0x0a, 0x0b, 0xde, 0x0d, 0x0e, 0xd9, 0x10, 0x14, 0x89, 0x41, 0xdd, 0x0d, 0x0e, 0xd8, 0x10, 0x15, 0x88, 0x41, 0xd8, 0x04, 0x08, 0x87, 0x4d, 0x81, 0x4d, - 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a, + 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a ], "CPython codegen_match_inner() emits the success jump with NO_LOCATION" ); @@ -18371,10 +18473,10 @@ def f(self): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x08, 0x0a, 0x80, 0x41, 0xd8, 0x08, 0x0c, 0x80, 0x41, 0xd8, 0x0a, + 0x81, 0x00, 0xd8, 0x08, 0x0a, 0x80, 0x41, 0xd8, 0x08, 0x0c, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x13, 0x8f, 0x56, 0x8a, 0x56, 0x95, 0x11, 0x89, 0x56, 0xd8, 0x10, 0x11, 0x89, 0x41, 0xf2, 0x03, 0x00, 0x0e, 0x14, 0xe0, 0x04, 0x08, 0x87, 0x4d, 0x81, - 0x4d, 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a, + 0x4d, 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a ], "CPython codegen_pattern_mapping() returns to LOC(p) for BUILD_TUPLE/MATCH_KEYS scaffolding" ); @@ -18446,9 +18548,9 @@ def f(x): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0a, 0x0b, 0xdc, 0x0d, 0x11, 0x8f, 0x57, 0x88, 0x57, 0xd8, 0x10, + 0x81, 0x00, 0xd8, 0x0a, 0x0b, 0xdc, 0x0d, 0x11, 0x8f, 0x57, 0x88, 0x57, 0xd8, 0x10, 0x11, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x88, 0x34, 0x80, 0x4b, 0xf0, 0x05, 0x00, 0x0e, - 0x15, 0xe0, 0x0b, 0x0c, 0x88, 0x61, 0x88, 0x34, 0x80, 0x4b, + 0x15, 0xe0, 0x0b, 0x0c, 0x88, 0x61, 0x88, 0x34, 0x80, 0x4b ], "CPython codegen_pattern_class() returns to LOC(p) after VISIT(cls)" ); @@ -18551,7 +18653,7 @@ def f(waiters): [ Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, Instruction::LoadAttr { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] )), @@ -18896,11 +18998,11 @@ def or_true(x): // keeps the literal range rather than the whole BoolOp range. assert_eq!( and_false.linetable.as_ref(), - &[0x80, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16] + &[0x81, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16] ); assert_eq!( or_true.linetable.as_ref(), - &[0x80, 0x00, 0xd8, 0x0b, 0x0f, 0xd0, 0x04, 0x14] + &[0x81, 0x00, 0xd8, 0x0b, 0x0f, 0xd0, 0x04, 0x14] ); } @@ -18919,7 +19021,7 @@ def f(): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x04, 0x15, 0x90, 0x23, 0xd3, 0x04, 0x15, 0x88, 0x35, + 0x81, 0x00, 0xd8, 0x04, 0x15, 0x90, 0x23, 0xd3, 0x04, 0x15, 0x88, 0x35 ] ); } @@ -18943,7 +19045,7 @@ def f(a, b): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0b, 0x0c, 0x80, 0x71, 0xd8, 0x0b, 0x0c, 0x82, 0x71, + 0x81, 0x00, 0xd8, 0x0b, 0x0c, 0x80, 0x71, 0xd8, 0x0b, 0x0c, 0x82, 0x71 ] ); } @@ -19076,14 +19178,14 @@ def f(parameters): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd9, 0x0b, 0x2d, 0xa1, 0x2a, 0xd3, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, + 0x81, 0x00, 0xd9, 0x0b, 0x2d, 0xa0, 0x2a, 0xd3, 0x0b, 0x2d, 0xd0, 0x04, 0x2d ] ); assert_eq!( genexpr.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x2d, 0xa1, 0x2a, 0x98, 0x51, 0x94, 0x04, 0x90, - 0x51, 0x93, 0x07, 0x8d, 0x4c, 0xa3, 0x2a, 0xf9, + 0xf9, 0xa2, 0x2a, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x2a, 0x98, 0x51, 0x94, 0x04, 0x90, + 0x51, 0x93, 0x07, 0x8e, 0x4c, 0xa3, 0x2a, 0xf9 ] ); } @@ -19112,22 +19214,24 @@ def explicit(): assert_eq!( implicit.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9c, 0x39, - 0xd3, 0x0f, 0x25, 0xd3, 0x0b, 0x25, 0xd0, 0x04, 0x25, + 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, + 0x9b, 0x39, 0xd3, 0x0f, 0x25, 0x8f, 0x34, 0xd0, 0x04, 0x25, 0x88, 0x34, 0xd1, 0x0f, + 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9b, 0x39, 0xd3, 0x0f, 0x25, 0xd3, 0x0b, 0x25, 0xd0, + 0x04, 0x25 ] ); assert_eq!( implicit_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x25, 0x99, 0x39, 0x90, 0x61, 0x94, 0x01, 0x9b, - 0x39, 0xf9, + 0xf9, 0x9a, 0x39, 0xe9, 0x00, 0x81, 0x00, 0x99, 0x39, 0x90, 0x61, 0x95, 0x01, 0x9b, + 0x39, 0xf9 ] ); assert_eq!( explicit_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x26, 0x99, 0x49, 0x90, 0x71, 0x94, 0x11, 0x9b, - 0x49, 0xf9, + 0xf9, 0x9a, 0x49, 0xe9, 0x00, 0x81, 0x00, 0x99, 0x49, 0x90, 0x71, 0x95, 0x11, 0x9b, + 0x49, 0xf9 ] ); } @@ -19167,57 +19271,59 @@ def explicit_gen(xs): assert_eq!( bytes_binop.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa4, 0x5a, - 0xd3, 0x10, 0x30, 0xd3, 0x0b, 0x30, 0xd0, 0x04, 0x30, + 0x81, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa3, 0x5a, + 0xd3, 0x10, 0x30, 0xd3, 0x0b, 0x30, 0xd0, 0x04, 0x30 ] ); assert_eq!( bytes_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x30, 0xa1, 0x5a, 0xa0, 0x01, 0x90, 0x64, 0x97, - 0x28, 0x92, 0x28, 0xa3, 0x5a, 0xf9, + 0xf9, 0xa2, 0x5a, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x5a, 0xa0, 0x01, 0x90, 0x64, 0x97, + 0x28, 0x93, 0x28, 0xa3, 0x5a, 0xf9 ] ); assert_eq!( dict_tuple.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x2f, 0xa0, 0x51, 0xa7, 0x57, 0xa1, 0x57, - 0xa4, 0x59, 0xd3, 0x0f, 0x2f, 0xd3, 0x0b, 0x2f, 0xd0, 0x04, 0x2f, + 0x81, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x2f, 0xa0, 0x51, 0xa7, 0x57, 0xa1, 0x57, + 0xa3, 0x59, 0xd3, 0x0f, 0x2f, 0xd3, 0x0b, 0x2f, 0xd0, 0x04, 0x2f ] ); assert_eq!( dict_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x2f, 0xa1, 0x59, 0x99, 0x36, 0x98, 0x41, 0x90, - 0x11, 0x95, 0x06, 0xa3, 0x59, 0xf9, + 0xf9, 0xa2, 0x59, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x59, 0x99, 0x36, 0x98, 0x41, 0x90, + 0x11, 0x96, 0x06, 0xa3, 0x59, 0xf9 ] ); assert_eq!( plain_tuple_elt.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x26, 0xa1, 0x32, 0xd3, 0x0f, 0x26, 0xd3, - 0x0b, 0x26, 0xd0, 0x04, 0x26, + 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x0f, 0x26, 0xa0, 0x32, 0xd3, 0x0f, + 0x26, 0x8f, 0x34, 0xd0, 0x04, 0x26, 0x88, 0x34, 0xd1, 0x0f, 0x26, 0xa0, 0x32, 0xd3, + 0x0f, 0x26, 0xd3, 0x0b, 0x26, 0xd0, 0x04, 0x26 ] ); assert_eq!( plain_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x26, 0xa1, 0x32, 0x99, 0x34, 0x98, 0x31, 0x90, - 0x11, 0x95, 0x06, 0xa3, 0x32, 0xf9, + 0xf9, 0xa2, 0x32, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x32, 0x99, 0x34, 0x98, 0x31, 0x90, + 0x11, 0x96, 0x06, 0xa3, 0x32, 0xf9 ] ); assert_eq!( explicit_gen.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x10, 0x27, 0xa1, 0x42, 0xd3, 0x10, 0x27, 0xd3, - 0x0b, 0x28, 0xd0, 0x04, 0x28, + 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x10, 0x27, 0xa0, 0x42, 0xd3, 0x10, + 0x27, 0x8f, 0x34, 0xd0, 0x04, 0x28, 0x88, 0x34, 0xd1, 0x10, 0x27, 0xa0, 0x42, 0xd3, + 0x10, 0x27, 0xd3, 0x0b, 0x28, 0xd0, 0x04, 0x28 ] ); assert_eq!( explicit_inner.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x27, 0xa1, 0x42, 0x99, 0x44, 0x98, 0x41, 0x90, - 0x21, 0x95, 0x16, 0xa3, 0x42, 0xf9, + 0xf9, 0xa2, 0x42, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x42, 0x99, 0x44, 0x98, 0x41, 0x90, + 0x21, 0x96, 0x16, 0xa3, 0x42, 0xf9 ] ); } @@ -19236,14 +19342,14 @@ def f(p, q): let f = find_code(&code, "f").expect("missing f code"); let genexpr = find_code(f, "").expect("missing genexpr code"); - // Columns are one-based here, so these are `dis`'s 14..56. + // MAKE_FUNCTION keeps the genexpr range; eager .0 acquisition uses the iterable range. assert_eq!( instruction_range(f, |op| matches!(op, Instruction::MakeFunction)), Some((2, 15, 2, 57)) ); assert_eq!( instruction_range(genexpr, |op| matches!(op, Instruction::LoadFast { .. })), - Some((2, 15, 2, 57)) + Some((2, 47, 2, 56)) ); } @@ -19265,7 +19371,12 @@ w = f'' 'a' f'' .instructions .iter() .zip(&code.locations) - .filter(|(unit, _)| matches!(unit.op, Instruction::LoadConst { .. })) + .filter(|(unit, _)| { + matches!(unit.op, Instruction::LoadConst { .. }) + || matches!(unit.op, Instruction::LoadCommonConstant { idx } + if idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) + == bytecode::CommonConstant::EmptyStr) + }) .map(|(_, locations)| location_range(locations)) .take(4) .collect(); @@ -19300,16 +19411,16 @@ def boolop(fields): assert_eq!( simple_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x31, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8, - 0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8a, 0x41, 0x93, 0x75, 0xf9, + 0xf9, 0x92, 0x75, 0xe9, 0x00, 0x81, 0x00, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8, + 0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8b, 0x41, 0x93, 0x75, 0xf9 ] ); assert_eq!( boolop_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x3a, 0x91, 0x76, 0x90, 0x21, 0xa7, 0x16, 0xa5, - 0x16, 0x8c, 0x41, 0xb0, 0x01, 0xb7, 0x09, 0xb5, 0x09, 0x8f, 0x41, 0x8a, 0x41, 0x93, - 0x76, 0xf9, + 0xf9, 0x92, 0x76, 0xe9, 0x00, 0x81, 0x00, 0x91, 0x76, 0x90, 0x21, 0xa7, 0x16, 0xa5, + 0x16, 0x8c, 0x41, 0xb0, 0x01, 0xb7, 0x09, 0xb5, 0x09, 0x8f, 0x41, 0x8b, 0x41, 0x93, + 0x76, 0xf9 ] ); } @@ -19335,9 +19446,9 @@ def f(self, node): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x10, 0x14, 0x80, 0x44, 0x84, 0x49, 0xf0, 0x02, 0x03, 0x05, 0x1a, + 0x81, 0x00, 0xd8, 0x10, 0x14, 0x80, 0x44, 0x84, 0x49, 0xf0, 0x02, 0x03, 0x05, 0x1a, 0xd8, 0x08, 0x0c, 0x8f, 0x09, 0x89, 0x09, 0x90, 0x24, 0x8c, 0x0f, 0xe0, 0x14, 0x19, - 0x88, 0x04, 0x8e, 0x09, 0xf8, 0x90, 0x45, 0x88, 0x04, 0x8d, 0x09, 0xfa, + 0x88, 0x04, 0x8e, 0x09, 0xf8, 0x90, 0x45, 0x88, 0x04, 0x8d, 0x09, 0xfa ] ); } @@ -19363,11 +19474,10 @@ def f(close): .instructions .iter() .position(|unit| { - let Instruction::LoadConst { consti } = unit.op else { + let Instruction::LoadCommonConstant { idx } = unit.op else { return false; }; - let constant = &f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))]; - matches!(constant, ConstantData::Boolean { value: true }) + idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) == bytecode::CommonConstant::True }) .expect("missing __debug__ constant load"); @@ -19494,7 +19604,7 @@ def f(file): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x07, 0x0a, 0x87, 0x7c, 0x81, 0x7c, 0x90, 0x77, 0xd4, 0x07, 0x1e, + 0x81, 0x00, 0xdc, 0x07, 0x0a, 0x87, 0x7c, 0x81, 0x7c, 0x90, 0x77, 0xd4, 0x07, 0x1e, 0xf0, 0x02, 0x05, 0x09, 0x19, 0xdb, 0x0c, 0x15, 0xd8, 0x13, 0x15, 0xd7, 0x13, 0x30, 0xd1, 0x13, 0x30, 0xd7, 0x13, 0x32, 0xd2, 0x13, 0x32, 0xd9, 0x17, 0x1c, 0xf0, 0x03, 0x00, 0x14, 0x33, 0xf0, 0x08, 0x03, 0x05, 0x39, 0xdc, 0x0f, 0x11, 0x8f, 0x79, 0x89, @@ -19503,7 +19613,7 @@ def f(file): 0x01, 0x09, 0x19, 0xda, 0x13, 0x18, 0xf0, 0x03, 0x01, 0x09, 0x19, 0xfb, 0xf4, 0x08, 0x00, 0x0c, 0x13, 0xf4, 0x00, 0x01, 0x05, 0x39, 0xdc, 0x0f, 0x16, 0x90, 0x74, 0x98, 0x58, 0xd3, 0x0f, 0x26, 0xd7, 0x0f, 0x38, 0xd0, 0x0f, 0x38, 0xa8, 0x34, 0xaf, 0x3b, - 0xa9, 0x3b, 0xab, 0x3d, 0xd2, 0x08, 0x38, 0xf0, 0x03, 0x01, 0x05, 0x39, 0xfa, + 0xa9, 0x3b, 0xab, 0x3d, 0xd2, 0x08, 0x38, 0xf0, 0x03, 0x01, 0x05, 0x39, 0xfa ] ); } @@ -19527,13 +19637,13 @@ def spec(x): assert_eq!( simple.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0e, 0x12, 0x8f, 0x6a, 0x89, 0x6a, 0x88, 0x5c, 0xd0, 0x04, 0x1a, + 0x81, 0x00, 0xd8, 0x0e, 0x12, 0x8f, 0x6a, 0x89, 0x6a, 0x88, 0x5c, 0xd0, 0x04, 0x1a ] ); assert_eq!( spec.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0e, 0x0f, 0x88, 0x58, 0x90, 0x22, 0x88, 0x58, 0xd0, 0x04, 0x16, + 0x81, 0x00, 0xd8, 0x0e, 0x0f, 0x88, 0x58, 0x90, 0x22, 0x88, 0x58, 0xd0, 0x04, 0x16 ] ); } @@ -19651,8 +19761,8 @@ def f(a): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xf0, 0x04, 0x01, 0x09, 0x0c, 0xd8, 0x0c, 0x0d, 0x88, 0x33, 0xf0, 0x00, - 0x01, 0x0f, 0x0c, 0xf0, 0x03, 0x02, 0x09, 0x0c, 0xf0, 0x03, 0x04, 0x05, 0x06, + 0x81, 0x00, 0xf0, 0x04, 0x01, 0x09, 0x0c, 0xd8, 0x0c, 0x0d, 0x88, 0x33, 0xf0, 0x00, + 0x01, 0x0f, 0x0c, 0xf0, 0x03, 0x02, 0x09, 0x0c, 0xf0, 0x03, 0x04, 0x05, 0x06 ], "CPython parser/codegen represents adjacent f-string literal fragments as Constant ranges spanning the merged fragments" ); @@ -19763,23 +19873,23 @@ class E: assert_eq!( c.linetable.as_ref(), &[ - 0xf8, 0x87, 0x00, 0x80, 0x00, 0xd8, 0x05, 0x08, 0xf1, 0x02, 0x01, 0x05, 0x0e, 0xf3, - 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x0e, + 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd8, 0x05, 0x08, 0xf1, 0x02, 0x01, 0x05, 0x0e, 0xf3, + 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x0e ] ); - assert_eq!(d.linetable.as_ref(), &[0x86, 0x00, 0xe3, 0x04, 0x08]); + assert_eq!(d.linetable.as_ref(), &[0x87, 0x00, 0xe3, 0x04, 0x08]); assert_eq!( e.linetable.as_ref(), &[ - 0xf8, 0x87, 0x00, 0x80, 0x00, 0xd8, 0x05, 0x08, 0xf7, 0x02, 0x01, 0x05, 0x22, 0xf3, - 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x22, + 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd8, 0x05, 0x08, 0xf7, 0x02, 0x01, 0x05, 0x22, 0xf3, + 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x22 ] ); assert_eq!( annotate.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0xf7, 0x00, 0x01, 0x05, 0x22, 0xf1, 0x00, 0x01, 0x05, 0x22, 0x91, - 0x73, 0xf0, 0x00, 0x01, 0x05, 0x22, 0xa1, 0x2a, 0xf1, 0x00, 0x01, 0x05, 0x22, + 0xf8, 0x81, 0x00, 0xf7, 0x00, 0x01, 0x05, 0x22, 0xf1, 0x00, 0x01, 0x05, 0x22, 0x91, + 0x73, 0xf0, 0x00, 0x01, 0x05, 0x22, 0xa1, 0x2a, 0xf1, 0x00, 0x01, 0x05, 0x22 ] ); } @@ -20138,7 +20248,7 @@ x = +0.0j Instruction::Resume { .. }, Instruction::LoadConst { .. }, Instruction::StoreName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue ] ), @@ -20585,8 +20695,8 @@ def f(xs): .iter() .filter(|unit| matches!(unit.op, Instruction::PushNull)) .count(), - 1, - "fallback call path should remain for shadowed any()" + 2, + "the iterator NULL slot and fallback call NULL slot must both remain" ); let genexpr_const_count = f .constants @@ -20602,9 +20712,9 @@ def f(xs): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdf, 0x0b, 0x0e, 0x8b, 0x33, 0x89, 0x6f, 0x99, 0x22, 0x8b, 0x6f, 0x8f, - 0x33, 0x8c, 0x33, 0xd0, 0x04, 0x1d, 0x8a, 0x33, 0xd0, 0x04, 0x1d, 0x88, 0x33, 0x89, - 0x6f, 0x99, 0x22, 0x8b, 0x6f, 0xd3, 0x0b, 0x1d, 0xd0, 0x04, 0x1d, + 0x81, 0x00, 0xdf, 0x0b, 0x0e, 0x8b, 0x33, 0x89, 0x6f, 0x98, 0x22, 0x8b, 0x6f, 0x8f, + 0x33, 0x8d, 0x33, 0xd0, 0x04, 0x1d, 0x8a, 0x33, 0xd0, 0x04, 0x1d, 0x88, 0x33, 0x89, + 0x6f, 0x98, 0x22, 0x8b, 0x6f, 0xd3, 0x0b, 0x1d, 0xd0, 0x04, 0x1d ] ); } @@ -20656,7 +20766,7 @@ async def f(get_xs): } #[test] - fn builtin_tuple_genexpr_call_is_optimized_but_list_set_are_not() { + fn builtin_tuple_list_set_genexpr_calls_are_optimized() { let code = compile_exec( "\ def tuple_f(xs): @@ -20684,13 +20794,13 @@ def set_f(xs): _ => None, }) .expect("tuple(genexpr) fast path should emit LIST_APPEND"); - assert_eq!(tuple_list_append, 2); + assert_eq!(tuple_list_append, 3); assert_eq!( tuple_f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdf, 0x0b, 0x10, 0x8c, 0x35, 0x91, 0x0f, 0x99, 0x42, 0x93, 0x0f, 0x8f, - 0x35, 0xd0, 0x04, 0x1f, 0x88, 0x35, 0x91, 0x0f, 0x99, 0x42, 0x93, 0x0f, 0xd3, 0x0b, - 0x1f, 0xd0, 0x04, 0x1f, + 0x81, 0x00, 0xdf, 0x0b, 0x10, 0x8c, 0x35, 0x91, 0x0f, 0x98, 0x42, 0x93, 0x0f, 0x8f, + 0x35, 0x88, 0x35, 0xd0, 0x04, 0x1f, 0x88, 0x35, 0x91, 0x0f, 0x98, 0x42, 0x93, 0x0f, + 0xd3, 0x0b, 0x1f, 0xd0, 0x04, 0x1f ] ); @@ -20700,11 +20810,11 @@ def set_f(xs): .instructions .iter() .any(|unit| matches!(unit.op, Instruction::Call { .. })), - "list(genexpr) should stay on the normal call path" + "list(genexpr) keeps a fallback call for a shadowed builtin" ); assert!( - !has_common_constant(list_f, bytecode::CommonConstant::BuiltinList), - "CPython 3.14.5 does not optimize list(genexpr)" + has_common_constant(list_f, bytecode::CommonConstant::BuiltinList), + "CPython 3.15 guards the optimized list(genexpr) path by builtin identity" ); let set_f = find_code(&code, "set_f").expect("missing set_f code"); @@ -20713,12 +20823,23 @@ def set_f(xs): .instructions .iter() .any(|unit| matches!(unit.op, Instruction::Call { .. })), - "set(genexpr) should stay on the normal call path" + "set(genexpr) keeps a fallback call for a shadowed builtin" ); assert!( - !has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet), - "CPython 3.14.5 does not optimize set(genexpr)" + has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet), + "CPython 3.15 guards the optimized set(genexpr) path by builtin identity" ); + for (code, append) in [ + (list_f, bytecode::Opcode::ListAppend), + (set_f, bytecode::Opcode::SetAdd), + ] { + assert!( + cpython315_decoded_instructions(code) + .iter() + .any(|(_, op, arg)| { op.as_opcode() == append && *arg == 3 }) + ); + assert!(!has_intrinsic_1(code, IntrinsicFunction1::ListToTuple)); + } } #[test] @@ -20969,7 +21090,7 @@ def f(cache, lock, format): Instruction::LoadFastLoadFast { .. }, Instruction::LoadFast { .. }, Instruction::StoreSubscr, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -21126,8 +21247,8 @@ def aug_const(x, y): assert_eq!( aug_const.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x04, 0x05, 0x80, 0x63, 0x87, 0x46, 0x88, 0x61, 0x85, 0x4b, 0x85, - 0x46, + 0x81, 0x00, 0xd8, 0x04, 0x05, 0x80, 0x63, 0x87, 0x46, 0x88, 0x61, 0x85, 0x4b, 0x85, + 0x46 ] ); } @@ -21186,7 +21307,7 @@ def f(obj): .filter(|op| !matches!(op, Instruction::Cache)) .collect(); - let has_cpython_shape = ops.windows(7).any(|window| { + let has_cpython_shape = ops.windows(8).any(|window| { matches!( window, [ @@ -21196,6 +21317,7 @@ def f(obj): Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, Instruction::Swap { .. }, Instruction::PopTop, + Instruction::PopTop, Instruction::ReturnValue, ] ) @@ -21570,7 +21692,7 @@ def f(self, exc_type, KeyboardInterrupt, TimeoutExpired): Instruction::LoadSmallInt { .. }, Instruction::LoadFastBorrow { .. }, Instruction::StoreAttr { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -21710,7 +21832,7 @@ def f(locale, category, locales): Instruction::RaiseVarargs { .. }, Instruction::Nop, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::YieldValue { .. }, ] ) @@ -21857,18 +21979,19 @@ def f(parts): ); let break_cleanup_idx = ops - .windows(3) + .windows(4) .position(|window| { matches!( window, [ Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::PopTop, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue ] ) }) - .expect("missing POP_TOP/LOAD_CONST/RETURN_VALUE break cleanup"); + .expect("missing POP_TOP/POP_TOP/LOAD_COMMON_CONSTANT/RETURN_VALUE break cleanup"); let end_for_idx = ops .iter() .position(|op| matches!(op, Instruction::EndFor)) @@ -21908,7 +22031,7 @@ elif maxsize == 9223372036854775807: [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -21923,7 +22046,7 @@ elif maxsize == 9223372036854775807: Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -22574,7 +22697,7 @@ def f(self, file, backupfilename): ], [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::LoadFast { .. }, Instruction::StoreAttr { .. }, Instruction::Reraise { .. }, @@ -23068,15 +23191,9 @@ def f(x): ) }) .expect("missing folded -0.0 match value"); - let none_index = f - .constants - .iter() - .position(|constant| matches!(constant, ConstantData::None)) - .expect("missing implicit None"); - assert!( - negative_zero_index < none_index, - "CPython ast_preprocess.c folds MatchValue constants before codegen registers the implicit None" - ); + assert_eq!(negative_zero_index, 0); + assert_eq!(f.constants.len(), 1); + assert!(has_common_constant(f, bytecode::CommonConstant::None)); } #[test] @@ -23163,7 +23280,7 @@ def f(x): } #[test] - fn match_mapping_attribute_key_keeps_plain_load_fast_without_block_disable() { + fn match_mapping_attribute_key_keeps_borrowed_load_fast_without_block_disable() { let code = compile_exec( "\ def f(self): @@ -23211,8 +23328,8 @@ def f(self): .expect("missing Keys.KEY attribute load"); let prev = f.instructions[key_load_idx - 1].op; assert!( - matches!(prev, Instruction::LoadFast { .. }), - "CPython optimize_load_fast() records MATCH_KEYS' no-input pseudo-ref with the produced-value loop index, so this consumed Keys load stays strong; got ops={:?}", + matches!(prev, Instruction::LoadFastBorrow { .. }), + "CPython optimize_load_fast() records MATCH_KEYS' no-input pseudo-ref with the current instruction index, so this consumed Keys load is borrowed; got ops={:?}", f.instructions .iter() .map(|unit| unit.op) @@ -23396,9 +23513,9 @@ def f(i): matches!( window, [ - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -23595,9 +23712,9 @@ def f(cm): [ (Instruction::DeleteFast { .. }, 6), (Instruction::Nop, 6), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), (Instruction::Call { .. }, 2), ] ) @@ -23635,9 +23752,9 @@ def f(cm, names, modname): Instruction::Call { .. }, Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23674,9 +23791,9 @@ def f(cm): [ Instruction::StoreFast { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23716,9 +23833,9 @@ def f(cm, func, check): Instruction::Call { .. }, Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23763,9 +23880,9 @@ def f(meta_path, cm): (Instruction::Call { .. }, 9), (Instruction::StoreFast { .. }, 9), (Instruction::Nop, 9), - (Instruction::LoadConst { .. }, 3), - (Instruction::LoadConst { .. }, 3), - (Instruction::LoadConst { .. }, 3), + (Instruction::LoadCommonConstant { .. }, 3), + (Instruction::LoadCommonConstant { .. }, 3), + (Instruction::LoadCommonConstant { .. }, 3), ] ) }), @@ -23850,9 +23967,9 @@ def f(cm): [ Instruction::StoreFast { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23865,9 +23982,9 @@ def f(cm): window, [ Instruction::StoreFast { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23904,9 +24021,9 @@ def f(cm, ValueError): window, [ (Instruction::Nop, 6), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), (Instruction::Call { .. }, 2), ] ) @@ -23944,9 +24061,9 @@ def f(open, src, dst, copyfileobj): [ Instruction::Call { .. }, Instruction::PopTop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -23962,9 +24079,9 @@ def f(open, src, dst, copyfileobj): Instruction::Call { .. }, Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -24727,9 +24844,9 @@ def f(selector, self): [ (Instruction::JumpBackward { .. }, 4), (Instruction::Nop, 3), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), (Instruction::Call { .. }, 2), ] ) @@ -25873,7 +25990,7 @@ def f(part, lines, maxlen, encoding): window, [ Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreSubscr, ] ) @@ -25998,7 +26115,7 @@ def f(part, lines, maxlen, encoding): window, [ Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreSubscr, ] ) @@ -26930,11 +27047,23 @@ def f(fields): Instruction::LoadFastBorrow { .. }, Instruction::LoadAttr { .. }, Instruction::Call { .. }, - Instruction::GetIter, + Instruction::Call { .. }, ] ) }), - "expected plain attr-iter chain to keep borrowed receiver, got ops={ops:?}" + "expected plain attr-genexpr-call chain to keep borrowed receiver, got ops={ops:?}" + ); + assert!( + !f.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::GetIter { .. })) + ); + let genexpr = find_code(f, "").expect("missing genexpr"); + assert!( + genexpr + .instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::GetIter { .. })) ); } @@ -28083,15 +28212,20 @@ def bug(): ) .unwrap(); + assert!( + entries + .iter() + .all(|entry| { not_taken_idx < entry.start || not_taken_idx >= entry.end }) + ); assert!( entries.iter().any(|entry| { - entry.start <= not_taken_idx + entry.start <= alias_store_idx && alias_store_idx < entry.end && entry.target == copy_idx && entry.depth == 1 && entry.push_lasti }), - "CPython codegen_try_except() stores the exception alias before the inner SETUP_CLEANUP, so NOT_TAKEN and the alias store stay covered by the outer cleanup entry; entries={entries:?}, instructions={:?}", + "CPython codegen_try_except() stores the exception alias before the inner SETUP_CLEANUP, so the alias store stays covered by the outer cleanup entry; entries={entries:?}, instructions={:?}", bug.instructions ); } @@ -28452,7 +28586,7 @@ def f(self): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreName { .. } | Instruction::StoreFast { .. }, Instruction::DeleteName { .. } | Instruction::DeleteFast { .. }, Instruction::JumpForward { .. }, @@ -28903,7 +29037,13 @@ def f(cond, obj, xs, E): _ => None, }; - for name in ["xs", "obj", "x"] { + assert!(normal_tail.iter().any(|unit| { + matches!(unit.op, Instruction::LoadFast { .. }) && load_name(unit) == Some("xs") + })); + assert!(!normal_tail.iter().any(|unit| { + matches!(unit.op, Instruction::LoadFastBorrow { .. }) && load_name(unit) == Some("xs") + })); + for name in ["obj", "x"] { assert!( normal_tail .iter() @@ -29410,9 +29550,9 @@ def f(obj): [ Instruction::Resume { .. }, Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BuildSlice { .. }, Instruction::BinaryOp { .. }, Instruction::ReturnValue, @@ -29437,7 +29577,7 @@ def f(obj, step): .zip(&f.locations) .filter_map(|(unit, (location, end_location))| { let op = match unit.op { - Instruction::LoadConst { .. } => "LOAD_CONST", + Instruction::LoadCommonConstant { .. } => "LOAD_CONST", Instruction::BuildSlice { .. } => "BUILD_SLICE", _ => return None, }; @@ -29566,7 +29706,7 @@ def f(obj): Instruction::Resume { .. }, Instruction::LoadFastBorrow { .. }, Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BinarySlice, Instruction::ReturnValue, ] @@ -29732,9 +29872,9 @@ def f(self, xs, ys, cm1, cm2): [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -29750,9 +29890,9 @@ def f(self, xs, ys, cm1, cm2): Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -29780,15 +29920,9 @@ def f(p, s): .filter(|unit| { matches!( unit.op, - Instruction::LoadConst { consti } - if matches!( - f.constants.get( - consti - .get(OpArg::new(u32::from(u8::from(unit.arg)))) - .as_usize() - ), - Some(ConstantData::Integer { value }) if value == &BigInt::from(-1) - ) + Instruction::LoadCommonConstant { idx } + if idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) + == bytecode::CommonConstant::MinusOne ) }) .count(); @@ -29821,18 +29955,21 @@ def f(xs): .collect(); assert!( - units.windows(4).any(|window| { + units.windows(5).any(|window| { matches!( window[0].op, - Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. } + Instruction::LoadCommonConstant { idx } + if idx.get(OpArg::new(u32::from(u8::from(window[0].arg)))) + == bytecode::CommonConstant::MinusOne ) && matches!( window[1].op, Instruction::Swap { i } - if i.get(OpArg::new(u32::from(u8::from(window[1].arg)))) == 2 + if i.get(OpArg::new(u32::from(u8::from(window[1].arg)))) == 3 ) && matches!(window[2].op, Instruction::PopTop) - && matches!(window[3].op, Instruction::ReturnValue) + && matches!(window[3].op, Instruction::PopTop) + && matches!(window[4].op, Instruction::ReturnValue) }), - "expected CPython-style LOAD_CONST/SWAP/POP_TOP/RETURN_VALUE cleanup, got units={units:?}" + "expected CPython-style LOAD_COMMON_CONSTANT/SWAP 3/POP_TOP/POP_TOP/RETURN_VALUE cleanup, got units={units:?}" ); } @@ -29999,10 +30136,10 @@ def f(self): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. } | Instruction::StoreName { .. }, Instruction::DeleteFast { .. } | Instruction::DeleteName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30043,10 +30180,10 @@ def f(onerror, err, OSError): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. } | Instruction::StoreName { .. }, Instruction::DeleteFast { .. } | Instruction::DeleteName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30087,10 +30224,10 @@ def f(self, module_name, ModuleNotFoundError): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. } | Instruction::StoreName { .. }, Instruction::DeleteFast { .. } | Instruction::DeleteName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30377,12 +30514,12 @@ def f(cm, registry, altkey): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30424,9 +30561,9 @@ def f(a, b, path): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -30630,7 +30767,7 @@ def f(self, logger): [ Instruction::PopTop, Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, Instruction::DeleteFast { .. }, Instruction::JumpBackwardNoInterrupt { .. }, @@ -32205,7 +32342,7 @@ def f(): } #[test] - fn constant_list_iterable_uses_tuple() { + fn constant_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def f(): @@ -32214,26 +32351,17 @@ def f(): ); let f = find_code(&code, "f").expect("missing function code"); - assert!( - !f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::BuildList { .. })), - "constant list iterable should avoid BUILD_LIST before GET_ITER" - ); + assert!(f.instructions.iter().any(|unit| { + matches!(unit.op, Instruction::BuildList { count } + if count.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 1) + })); assert!(f.constants.iter().any(|constant| matches!( constant, ConstantData::Tuple { elements } - if matches!( - elements.as_slice(), - [ConstantData::Tuple { elements: inner }] - if matches!( - inner.as_slice(), - [ - ConstantData::Integer { .. }, - ConstantData::Integer { .. } - ] - ) - ) + if matches!(elements.as_slice(), [ + ConstantData::Integer { value: first }, + ConstantData::Integer { value: second }, + ] if first == &BigInt::from(1) && second == &BigInt::from(2)) ))); } @@ -32253,9 +32381,9 @@ def f(): matches!(constants[0], ConstantData::Str { value } if value.to_string() == "a"), "CPython emits list elements as LOAD_CONST before flowgraph folds GET_ITER lists" ); - assert!(matches!(constants[1], ConstantData::None)); + assert_eq!(constants.len(), 2); assert!(matches!( - constants[2], + constants[1], ConstantData::Tuple { elements } if matches!( elements.as_slice(), @@ -32475,12 +32603,11 @@ def f(g, kwargs, ns): ); let f = find_code(&code, "f").expect("missing function code"); let constants = f.constants.iter().collect::>(); - assert_eq!(constants.len(), 3); + assert_eq!(constants.len(), 2); assert!( matches!(constants[0], ConstantData::Str { value } if value.to_string() == "T") - && matches!(constants[1], ConstantData::None) - && matches!(constants[2], ConstantData::Tuple { elements } if elements.is_empty()), + && matches!(constants[1], ConstantData::Tuple { elements } if elements.is_empty()), "CPython emits BUILD_TUPLE 0 for CALL_FUNCTION_EX args and folds it after earlier constants" ); } @@ -32524,10 +32651,10 @@ def f(): let f = find_code(&code, "f").expect("missing function code"); assert!( - !f.instructions + f.instructions .iter() .any(|unit| matches!(unit.op, Instruction::BuildSet { .. })), - "constant set iterable should avoid BUILD_SET before GET_ITER" + "constant set iterable should build the set before saved locals and GET_ITER" ); assert!(f.constants.iter().any(|constant| matches!( constant, @@ -32707,13 +32834,13 @@ def f(a, b, c): assert!( f.instructions .iter() - .any(|unit| matches!(unit.op, Instruction::GetIter)), + .any(|unit| matches!(unit.op, Instruction::GetIter { .. })), "expected GET_ITER in for loop" ); } #[test] - fn comprehension_single_list_iterable_uses_tuple() { + fn comprehension_single_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def g(): @@ -32729,13 +32856,20 @@ def g(): .collect(); assert!( - ops.windows(2).any(|window| { + ops.windows(6).any(|window| { matches!( window, - [Instruction::BuildTuple { .. }, Instruction::GetIter] + [ + Instruction::BuildList { .. }, + Instruction::LoadFastAndClear { .. }, + Instruction::Swap { .. }, + Instruction::BuildList { .. }, + Instruction::Swap { .. }, + Instruction::GetIter { .. }, + ] ) }), - "expected BUILD_TUPLE before GET_ITER for single-item list iterable in comprehension, got ops={ops:?}" + "expected BUILD_LIST before saved locals, output collection, and GET_ITER for single-item list iterable in comprehension, got ops={ops:?}" ); } @@ -32754,10 +32888,10 @@ async def run_list(): assert_eq!( run_list.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xdc, 0x1e, 0x1f, 0xa0, 0x01, 0x9b, 0x64, 0xa4, 0x41, 0xa0, - 0x62, 0xa3, 0x45, 0x99, 0x5d, 0xd3, 0x0b, 0x2b, 0x99, 0x5d, 0x98, 0x01, 0x8f, 0x47, - 0x8a, 0x47, 0x99, 0x5d, 0xd1, 0x0b, 0x2b, 0xd0, 0x04, 0x2b, 0x89, 0x47, 0xf9, 0xd2, - 0x0b, 0x2b, 0xf9, + 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x1e, 0x1f, 0xa0, 0x01, 0x9b, 0x64, 0xa4, 0x41, 0xa0, + 0x62, 0xa3, 0x45, 0x98, 0x5d, 0xd3, 0x0b, 0x2b, 0x9b, 0x5d, 0x98, 0x01, 0x8f, 0x47, + 0x8c, 0x47, 0x99, 0x5d, 0xd1, 0x0b, 0x2b, 0xd0, 0x04, 0x2b, 0x89, 0x47, 0xf9, 0xd2, + 0x0b, 0x2b, 0xf9 ], "CPython codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter)" ); @@ -32772,12 +32906,13 @@ def f(self): ", ); let f = find_code(&code, "f").expect("missing f code"); - let get_iter_positions: Vec<_> = f + let genexpr = find_code(f, "").expect("missing genexpr code"); + let get_iter_positions: Vec<_> = genexpr .instructions .iter() - .zip(&f.locations) + .zip(&genexpr.locations) .filter_map(|(unit, (location, end_location))| { - matches!(unit.op, Instruction::GetIter).then_some(( + matches!(unit.op, Instruction::GetIter { .. }).then_some(( location.line.get(), location.character_offset.get(), end_location.line.get(), @@ -32807,10 +32942,10 @@ async def run_list(): assert_eq!( run_list.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xdc, 0x18, 0x19, 0x98, 0x22, 0x9b, 0x05, 0x9c, 0x71, 0xa0, - 0x15, 0x9b, 0x78, 0xd1, 0x17, 0x28, 0xd4, 0x0b, 0x3a, 0xd1, 0x17, 0x28, 0x90, 0x21, - 0xb7, 0x27, 0xb2, 0x27, 0xa8, 0x51, 0x8a, 0x41, 0xb1, 0x27, 0x89, 0x41, 0xd1, 0x17, - 0x28, 0xd2, 0x0b, 0x3a, 0xd0, 0x04, 0x3a, 0xb1, 0x27, 0xf9, 0xd3, 0x0b, 0x3a, 0xf9, + 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x18, 0x19, 0x98, 0x22, 0x9b, 0x05, 0x9c, 0x71, 0xa0, + 0x15, 0x9b, 0x78, 0xd0, 0x17, 0x28, 0xd4, 0x0b, 0x3a, 0xd3, 0x17, 0x28, 0x90, 0x21, + 0xb7, 0x27, 0xb5, 0x27, 0xa8, 0x51, 0x8a, 0x41, 0xb1, 0x27, 0x89, 0x41, 0xd1, 0x17, + 0x28, 0xd2, 0x0b, 0x3a, 0xd0, 0x04, 0x3a, 0xb1, 0x27, 0xf9, 0xd3, 0x0b, 0x3a, 0xf9 ], "CPython codegen_sync_comprehension_generator() emits comprehension backedges at elt_loc" ); @@ -32923,9 +33058,10 @@ async def run_list(): assert_eq!( run_list.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xdc, 0x21, 0x22, 0xa0, 0x42, 0xa8, 0x02, 0xa0, 0x38, 0xa4, - 0x1b, 0xd7, 0x0b, 0x2d, 0xd3, 0x0b, 0x2d, 0x98, 0x41, 0x90, 0x01, 0x8f, 0x45, 0x88, - 0x45, 0xd4, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, 0xf9, 0xd2, 0x0b, 0x2d, 0xf9, + 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x21, 0x22, 0xa0, 0x42, 0xa8, 0x02, 0xa0, 0x38, 0xa3, + 0x1b, 0xd3, 0x0b, 0x2d, 0xa0, 0x1b, 0xd7, 0x0b, 0x2d, 0xd1, 0x0b, 0x2d, 0x98, 0x41, + 0x90, 0x01, 0x8f, 0x45, 0x88, 0x45, 0xd4, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, 0xf9, 0xd2, + 0x0b, 0x2d, 0xf9 ], "CPython codegen_async_comprehension_generator() emits END_ASYNC_FOR at comprehension loc" ); @@ -32944,17 +33080,17 @@ async def f(source, buffer): assert_eq!( f.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd9, 0x18, 0x1e, 0x9c, 0x08, 0xf7, 0x00, 0x01, 0x05, 0x1f, - 0xf0, 0x00, 0x01, 0x05, 0x1f, 0x89, 0x66, 0x88, 0x62, 0xd8, 0x08, 0x0e, 0x8f, 0x0d, + 0xe9, 0x00, 0x81, 0x00, 0xd9, 0x18, 0x1e, 0x9c, 0x08, 0xf7, 0x00, 0x01, 0x05, 0x1f, + 0xf2, 0x00, 0x01, 0x05, 0x1f, 0x89, 0x66, 0x88, 0x62, 0xd8, 0x08, 0x0e, 0x8f, 0x0d, 0x89, 0x0d, 0x90, 0x62, 0x95, 0x67, 0xd6, 0x08, 0x1e, 0xf1, 0x03, 0x01, 0x05, 0x1f, - 0x9a, 0x08, 0xf9, + 0x9a, 0x08, 0xf9 ], "CPython codegen_async_for() emits GET_ANEXT/yield-from scaffolding at LOC(s)" ); } #[test] - fn nested_comprehension_list_iterable_uses_tuple() { + fn nested_comprehension_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def f(): @@ -32970,13 +33106,20 @@ def f(): .collect(); assert!( - ops.windows(2).any(|window| { + ops.windows(6).any(|window| { matches!( window, - [Instruction::BuildTuple { .. }, Instruction::GetIter] + [ + Instruction::BuildList { .. }, + Instruction::LoadFastAndClear { .. }, + Instruction::Swap { .. }, + Instruction::BuildList { .. }, + Instruction::Swap { .. }, + Instruction::GetIter { .. }, + ] ) }), - "expected BUILD_TUPLE before GET_ITER for nested list iterable in comprehension, got ops={ops:?}" + "expected BUILD_LIST before saved locals, output collection, and GET_ITER for nested list iterable in comprehension, got ops={ops:?}" ); } @@ -32994,11 +33137,11 @@ def f(): .iter() .filter(|unit| matches!(unit.op, Instruction::ForIter { .. })) .count(); - let has_map_add_depth_2 = f.instructions.iter().any(|unit| { + let has_map_add_depth_3 = f.instructions.iter().any(|unit| { matches!( unit.op, Instruction::MapAdd { i } - if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 2 + if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 3 ) }); @@ -33008,8 +33151,8 @@ def f(): f.instructions ); assert!( - has_map_add_depth_2, - "assignment-idiom dictcomp should use MAP_ADD depth 2, got instructions={:?}", + has_map_add_depth_3, + "assignment-idiom dictcomp should use MAP_ADD depth 3, got instructions={:?}", f.instructions ); assert!( @@ -33327,9 +33470,9 @@ async def f(cm, source, tgt): [ Instruction::EndAsyncFor, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -33492,19 +33635,29 @@ _pathseps_with_colon = {f':{s}' for s in path_separators} .filter(|op| !matches!(op, Instruction::Cache)) .collect(); - let load_name_path = ops - .windows(2) - .any(|window| matches!(window, [Instruction::LoadName { .. }, Instruction::GetIter])); + let load_name_path = ops.windows(6).any(|window| { + matches!( + window, + [ + Instruction::LoadName { .. }, + Instruction::LoadFastAndClear { .. }, + Instruction::Swap { .. }, + Instruction::BuildSet { .. }, + Instruction::Swap { .. }, + Instruction::GetIter { .. } + ] + ) + }); assert!( load_name_path, - "expected outer iterable to stay a NAME lookup before GET_ITER, got ops={ops:?}" + "expected NAME lookup before locals are saved and iterator acquisition after collection construction, got ops={ops:?}" ); assert!( !ops.windows(2).any(|window| matches!( window, [ Instruction::LoadFast { .. } | Instruction::LoadFastCheck { .. }, - Instruction::GetIter + Instruction::GetIter { .. } ] )), "module local outer iterable should not become a fast local, got ops={ops:?}" @@ -33633,7 +33786,7 @@ def outer(): // between visiting the value and visiting the target. assert_eq!( spam.linetable.as_ref(), - &[0xf8, 0x80, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11,] + &[0xf8, 0x81, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11] ); } @@ -33828,13 +33981,15 @@ values = ( "CPython codegen_stmt_expr() prints and pops interactive expressions; it does not preserve the final expression as the code object's return value, got ops={ops:?}" ); let Some(load_none) = ops.iter().rev().nth(1) else { - panic!("missing final LOAD_CONST None before RETURN_VALUE, got ops={ops:?}"); + panic!("missing final LOAD_COMMON_CONSTANT None before RETURN_VALUE, got ops={ops:?}"); }; - let Instruction::LoadConst { consti } = load_none.op else { - panic!("missing final LOAD_CONST None before RETURN_VALUE, got ops={ops:?}"); + let Instruction::LoadCommonConstant { idx } = load_none.op else { + panic!("missing final LOAD_COMMON_CONSTANT None before RETURN_VALUE, got ops={ops:?}"); }; - let constant = &code.constants[consti.get(OpArg::new(u32::from(u8::from(load_none.arg))))]; - assert!(matches!(constant, ConstantData::None)); + assert_eq!( + idx.get(OpArg::new(u32::from(u8::from(load_none.arg)))), + bytecode::CommonConstant::None + ); assert!(matches!( ops.last().map(|unit| unit.op), Some(Instruction::ReturnValue) @@ -35160,7 +35315,7 @@ def f(items): let get_iter_idx = ops .iter() - .rposition(|op| matches!(op, Instruction::GetIter)) + .rposition(|op| matches!(op, Instruction::GetIter { .. })) .expect("missing GET_ITER"); assert!( matches!(ops[get_iter_idx - 1], Instruction::LoadFast { .. }), @@ -35183,7 +35338,7 @@ def f(items): } #[test] - fn plain_pass_before_for_tail_keeps_borrows() { + fn plain_pass_before_for_tail_keeps_owned_iterator_input() { let code = compile_exec( "\ def f(xs): @@ -35203,11 +35358,11 @@ def f(xs): let get_iter_idx = ops .iter() - .position(|op| matches!(op, Instruction::GetIter)) + .position(|op| matches!(op, Instruction::GetIter { .. })) .expect("missing GET_ITER"); assert!( - matches!(ops[get_iter_idx - 1], Instruction::LoadFastBorrow { .. }), - "plain pass before for-tail should keep borrowed iterable load, got ops={ops:?}" + matches!(ops[get_iter_idx - 1], Instruction::LoadFast { .. }), + "plain pass before for-tail should keep an owned iterable load for GET_ITER, got ops={ops:?}" ); } @@ -35372,7 +35527,7 @@ def f(s, size, encodeSetO, encodeWhiteSpace): window, [ Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, @@ -35865,7 +36020,7 @@ def f(cls, _FIELDS, _PARAMS): Instruction::NotTaken, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, ] ) @@ -35901,9 +36056,9 @@ def f(it): Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, Instruction::JumpBackward { .. } @@ -35938,9 +36093,9 @@ async def foo(): matches!( window, [ - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::GetAwaitable { .. }, ] @@ -35954,9 +36109,9 @@ async def foo(): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::GetAwaitable { .. }, ] @@ -36003,8 +36158,8 @@ async def foo(self): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -36039,8 +36194,8 @@ def f(): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -38176,7 +38331,7 @@ def f(self, document): } #[test] - fn from_import_after_conditional_store_join_uses_strong_prefix_loads() { + fn from_import_after_conditional_store_join_uses_borrowed_prefix_loads() { let code = compile_exec( "\ def f(x): @@ -38209,8 +38364,11 @@ def f(x): .expect("missing rpartition LOAD_ATTR"); assert!( - matches!(ops[rpartition_idx - 1].op, Instruction::LoadFast { .. }), - "CPython optimize_load_fast() keeps the conditional-store join receiver strong before IMPORT_FROM, got ops={ops:?}" + matches!( + ops[rpartition_idx - 1].op, + Instruction::LoadFastBorrow { .. } + ), + "CPython optimize_load_fast() borrows the conditional-store join receiver before IMPORT_FROM, got ops={ops:?}" ); } @@ -38345,9 +38503,25 @@ def f(output): warm_path .iter() .filter_map(load_fast_name) - .all(|name| name != "row" && name != "result"), - "terminal-except inlined comprehension warm path should keep CPython-style borrowed row/result loads, got warm_path={warm_path:?}" + .all(|name| name != "row"), + "terminal-except inlined comprehension warm path should keep CPython-style borrowed row loads, got warm_path={warm_path:?}" + ); + assert_eq!( + warm_path + .iter() + .filter_map(load_fast_name) + .filter(|name| *name == "result") + .count(), + 1 ); + let result_iterable = warm_path + .iter() + .position(|unit| load_fast_name(unit) == Some("result")) + .unwrap(); + assert!(matches!( + warm_path[result_iterable + 1].op, + Instruction::LoadFastAndClear { .. } + )); for name in ["row", "result"] { assert!( warm_path @@ -38642,7 +38816,7 @@ def f(flags, A, B, stop): Instruction::PopJumpIfTrue { .. }, Instruction::NotTaken, Instruction::JumpBackward { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -39629,7 +39803,7 @@ def f(self, rawdata, j, match): } #[test] - fn join_store_global_before_import_keeps_strong_load_fast() { + fn join_store_global_before_import_keeps_borrowed_load_fast() { let code = compile_exec( "\ def f(module=None): @@ -39653,12 +39827,12 @@ def f(module=None): matches!( window, [ - Instruction::LoadFast { .. }, + Instruction::LoadFastBorrow { .. }, Instruction::StoreGlobal { .. }, ] ) }), - "expected CPython-style strong LOAD_FAST before join STORE_GLOBAL followed by import, got ops={ops:?}" + "expected CPython-style LOAD_FAST_BORROW before join STORE_GLOBAL followed by import, got ops={ops:?}" ); } @@ -40106,7 +40280,7 @@ def f(self, b, BlockingIOError): .. }, CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, ] @@ -40153,9 +40327,9 @@ def f(self): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -40192,9 +40366,9 @@ def f(cm, source): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -40230,9 +40404,9 @@ def f(cm, xs, g): [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40247,9 +40421,9 @@ def f(cm, xs, g): Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40326,9 +40500,9 @@ def f(cm1, cm2, g, E): Instruction::Copy { .. }, Instruction::PopExcept, Instruction::Reraise { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -40343,9 +40517,9 @@ def f(cm1, cm2, g, E): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -40377,9 +40551,9 @@ def f(cm, dst): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40501,7 +40675,7 @@ def f(self, cm, E): .collect(); let get_iter = instructions .iter() - .position(|unit| matches!(unit.op, Instruction::GetIter)) + .position(|unit| matches!(unit.op, Instruction::GetIter { .. })) .expect("missing loop iterator"); let handler_start = instructions .iter() @@ -40551,7 +40725,7 @@ def f(resources, valid_zones, TZPATH, os): .collect(); let get_iter = instructions .iter() - .position(|unit| matches!(unit.op, Instruction::GetIter)) + .position(|unit| matches!(unit.op, Instruction::GetIter { .. })) .expect("missing post-try loop iterator"); let tail = &instructions[get_iter.saturating_sub(1)..]; let load_fast_name = |unit: &&bytecode::CodeUnit| match unit.op { @@ -41676,7 +41850,7 @@ def f(events, callback, args, self, sig, signal): .. }, bytecode::CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, bytecode::CodeUnit { @@ -42771,7 +42945,7 @@ def f(pattern, prefix, get_prefix): .collect(); assert!( - ops.windows(7).any(|window| { + ops.windows(8).any(|window| { matches!( window, [ @@ -42779,8 +42953,9 @@ def f(pattern, prefix, get_prefix): Instruction::PopJumpIfTrue { .. }, Instruction::NotTaken, Instruction::PopTop, + Instruction::PopTop, Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BuildTuple { .. }, ] ) @@ -43577,16 +43752,16 @@ def f(self, obj, expected, buf): .collect(); let get_iter = instructions .iter() - .position(|unit| matches!(unit.op, Instruction::GetIter)) + .position(|unit| matches!(unit.op, Instruction::GetIter { .. })) .expect("missing GET_ITER"); assert!( matches!( instructions .get(get_iter.saturating_sub(1)) .map(|unit| unit.op), - Some(Instruction::LoadFastBorrow { .. }) + Some(Instruction::LoadFast { .. }) ), - "finally-protected loop without except resume should keep borrowed iterable load, got instructions={instructions:?}" + "finally-protected loop without except resume should keep the owned iterable load for GET_ITER, got instructions={instructions:?}" ); for attr_name in ["close", "open"] { @@ -43922,14 +44097,30 @@ async def name_4(): else { panic!("missing GET_AITER in name_4"); }; - let prev = &name_4.instructions[get_aiter_pos - 1]; + assert!(matches!( + name_4.instructions[get_aiter_pos - 4].op, + Instruction::LoadFastAndClear { .. } + )); + assert!(matches!( + name_4.instructions[get_aiter_pos - 3].op, + Instruction::Swap { .. } + )); + assert!(matches!( + name_4.instructions[get_aiter_pos - 2].op, + Instruction::BuildMap { .. } + )); + assert!(matches!( + name_4.instructions[get_aiter_pos - 1].op, + Instruction::Swap { .. } + )); + let prev = &name_4.instructions[get_aiter_pos - 5]; assert!( matches!( prev.op, Instruction::LoadFastBorrow { var_num } if name_4.varnames[usize::from(var_num.get(OpArg::new(u32::from(u8::from(prev.arg)))))] == "name_5" ), - "expected async comprehension iterator capture to borrow name_5 before GET_AITER, got {prev:?}" + "expected async comprehension iterator capture to borrow name_5 before saved locals, result collection, and GET_AITER, got {prev:?}" ); } @@ -44141,4 +44332,300 @@ def f(_fwalk, stack, isbytes, topdown, onerror, follow_symlinks, close): &instructions[cleanup_pop.saturating_sub(12)..cleanup_pop] ); } + + fn cpython315_decoded_instructions(code: &CodeObject) -> Vec<(usize, Instruction, u32)> { + let mut state = bytecode::OpArgState::default(); + code.instructions + .iter() + .copied() + .enumerate() + .filter_map(|(offset, unit)| { + let (op, arg) = state.get(unit); + (!matches!(op, Instruction::Cache | Instruction::ExtendedArg)).then_some(( + offset, + op, + u32::from(arg), + )) + }) + .collect() + } + + #[test] + fn cpython315_genexpr_acquires_iterator_before_return_generator() { + for (source, is_async) in [ + ("def f(xs):\n return (x for x in xs)\n", false), + ("async def f(xs):\n return (x async for x in xs)\n", true), + ] { + let code = compile_exec(source); + let f = find_code(&code, "f").unwrap(); + let genexpr = find_code(f, "").unwrap(); + let instructions = cpython315_decoded_instructions(genexpr); + assert!(matches!(instructions[0].1, Instruction::Resume { .. })); + assert_eq!(instructions[0].2, 4); + assert!(matches!( + instructions[1].1, + Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. } + )); + assert_eq!(instructions[1].2, 0); + assert!(if is_async { + matches!(instructions[2].1, Instruction::GetAiter) + } else { + matches!(instructions[2].1, Instruction::GetIter { .. }) + }); + assert_eq!(instructions[2].2, 0); + assert!(matches!(instructions[3].1, Instruction::ReturnGenerator)); + assert!(matches!(instructions[4].1, Instruction::PopTop)); + assert!(matches!(instructions[5].1, Instruction::Resume { .. })); + assert_eq!(instructions[5].2, 0); + assert_eq!( + instructions + .iter() + .filter(|(_, op, _)| matches!(op, Instruction::ReturnGenerator)) + .count(), + 1 + ); + assert!(!f.instructions.iter().any(|unit| matches!( + unit.op, + Instruction::GetIter { .. } | Instruction::GetAiter + ))); + let return_offset = u32::try_from(instructions[3].0).unwrap(); + assert!( + bytecode::decode_exception_table(&genexpr.exceptiontable) + .iter() + .all(|entry| return_offset < entry.start || return_offset >= entry.end) + ); + } + } + + #[test] + fn cpython315_await_and_yield_from_have_pairs_and_cached_resumes() { + for (source, is_await) in [ + ("def f(xs):\n yield from xs\n", false), + ("async def f(xs):\n return await xs\n", true), + ] { + let code = compile_exec(source); + let f = find_code(&code, "f").unwrap(); + let instructions = cpython315_decoded_instructions(f); + let acquire = instructions + .iter() + .position(|(_, op, _)| { + matches!( + op, + Instruction::GetIter { .. } | Instruction::GetAwaitable { .. } + ) + }) + .unwrap(); + if is_await { + assert!(matches!(instructions[acquire + 1].1, Instruction::PushNull)); + } else { + assert_eq!(instructions[acquire].2, 1); + } + let send = instructions + .iter() + .position(|(_, op, _)| matches!(op, Instruction::Send { .. })) + .unwrap(); + let (offset, op, arg) = instructions[send]; + assert!(matches!( + f.instructions[offset + 1 + op.cache_entries() + arg as usize].op, + Instruction::EndSend + )); + let resume = instructions[send + 2]; + assert!(matches!(resume.1, Instruction::Resume { .. })); + assert_eq!(resume.2 & 7, if is_await { 3 } else { 2 }); + assert_eq!(resume.1.cache_entries(), 1); + assert!(matches!( + f.instructions[resume.0 + 1].op, + Instruction::Cache + )); + } + } + + #[test] + fn cpython315_comprehension_collection_depth_counts_iterator_slots() { + for (source, depth) in [ + ( + "def f(xs, ys):\n return [x for x in xs for y in ys]\n", + 5, + ), + ( + "async def f(xs, ys):\n return [x async for x in xs for y in ys]\n", + 4, + ), + ] { + let code = compile_exec(source); + let instructions = cpython315_decoded_instructions(find_code(&code, "f").unwrap()); + let append = instructions + .iter() + .find(|(_, op, _)| matches!(op, Instruction::ListAppend { .. })) + .unwrap(); + assert_eq!(append.2, depth); + } + } + + #[test] + fn cpython315_ordinary_imports_pack_names_across_extended_arg_boundary() { + use core::fmt::Write; + let mut source = String::new(); + for i in 0..70 { + writeln!(source, "import module_{i}").unwrap(); + } + let code = compile_exec(&source); + let imports: Vec<_> = cpython315_decoded_instructions(&code) + .into_iter() + .filter(|(_, op, _)| matches!(op, Instruction::ImportName { .. })) + .collect(); + assert_eq!(imports.len(), 70); + for (i, (_, _, arg)) in imports.iter().enumerate() { + assert_eq!(*arg, u32::try_from(i).unwrap() << 2); + assert_eq!(code.names[(arg >> 2) as usize], format!("module_{i}")); + } + assert!( + code.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::ExtendedArg)) + ); + } + + #[test] + fn cpython315_ordinary_import_policy_matches_scope_and_handler_context() { + for (source, name, flags) in [ + ("import sample\n", "", &[0][..]), + ("from sample import member\n", "", &[0][..]), + ("from sample import *\n", "", &[2][..]), + ("def f():\n import sample\n", "f", &[2][..]), + ("class C:\n from sample import member\n", "C", &[2][..]), + ( + "try:\n import sample\nexcept Exception:\n pass\n", + "", + &[2][..], + ), + ( + "try:\n operation()\nexcept Exception:\n import sample\n", + "", + &[2][..], + ), + ( + "try:\n operation()\nfinally:\n import sample\n", + "", + &[0, 2][..], + ), + ("with manager:\n import sample\n", "", &[0][..]), + ] { + let code = compile_exec(source); + let scope = find_code(&code, name).unwrap(); + let instructions = cpython315_decoded_instructions(scope); + let imports: Vec<_> = instructions + .iter() + .filter(|(_, op, _)| matches!(op, Instruction::ImportName { .. })) + .collect(); + assert_eq!( + imports + .iter() + .map(|import| import.2 & 3) + .collect::>(), + flags, + "{source}" + ); + for import in imports { + assert_eq!(scope.names[(import.2 >> 2) as usize], "sample"); + } + } + } + + #[test] + fn cpython315_loop_return_cleanup_distinguishes_sync_and_async_iterators() { + for (source, slots) in [ + ("def f(xs):\n for x in xs:\n return x\n", 2), + ( + "async def f(xs):\n async for x in xs:\n return x\n", + 1, + ), + ] { + let code = compile_exec(source); + let instructions = cpython315_decoded_instructions(find_code(&code, "f").unwrap()); + let swap = instructions + .iter() + .position(|(_, op, arg)| { + matches!(op, Instruction::Swap { .. }) && *arg == slots + 1 + }) + .unwrap(); + for i in 1..=slots as usize { + assert!(matches!(instructions[swap + i].1, Instruction::PopTop)); + } + assert!(matches!( + instructions[swap + slots as usize + 1].1, + Instruction::ReturnValue + )); + } + } + + #[test] + fn cpython315_end_async_for_offsets_account_for_resume_cache() { + let code = compile_exec("async def f(xs):\n async for x in xs:\n pass\n"); + let f = find_code(&code, "f").unwrap(); + let instructions = cpython315_decoded_instructions(f); + let (offset, _, arg) = *instructions + .iter() + .find(|(_, op, _)| matches!(op, Instruction::EndAsyncFor)) + .unwrap(); + assert!(matches!( + f.instructions[offset + 1 - arg as usize].op, + Instruction::EndSend + )); + } + + #[test] + fn cpython315_list_set_async_genexpr_calls_keep_normal_call_path() { + for (name, constant) in [ + ("list", bytecode::CommonConstant::BuiltinList), + ("set", bytecode::CommonConstant::BuiltinSet), + ] { + let source = format!("async def f(xs):\n return {name}(x async for x in xs)\n"); + let code = compile_exec(&source); + let f = find_code(&code, "f").unwrap(); + assert!(!has_common_constant(f, constant)); + assert!( + !f.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::ForIter { .. })) + ); + assert!( + f.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::Call { .. })) + ); + } + } + + #[test] + fn cpython315_with_cleanup_excludes_synthetic_not_taken() { + let code = compile_exec("def f(c):\n with c:\n x = 1\n y = 2\n"); + let f = find_code(&code, "f").unwrap(); + let not_taken = u32::try_from( + f.instructions + .iter() + .position(|unit| matches!(unit.op, Instruction::NotTaken)) + .unwrap(), + ) + .unwrap(); + let entries = bytecode::decode_exception_table(&f.exceptiontable); + assert_eq!(entries.len(), 3); + assert!( + entries + .iter() + .all(|entry| not_taken < entry.start || not_taken >= entry.end) + ); + assert_eq!(entries[1].end, not_taken); + assert_eq!(entries[2].start, not_taken + 1); + assert_eq!(entries[1].target, entries[2].target); + assert_eq!(entries[1].depth, 4); + assert_eq!(entries[2].depth, 4); + assert!(entries[1].push_lasti && entries[2].push_lasti); + assert_eq!( + f.exceptiontable.as_ref(), + &[ + 0x8c, 0x03, 0x1b, 0x05, 0x9b, 0x08, 0x2e, 0x09, 0xa4, 0x02, 0x2e, 0x09 + ] + ); + } } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 790846a4028..9ee1c5dfe2b 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -455,7 +455,11 @@ impl InstructionInfo { /// flowgraph.c loads_const const fn loads_const(&self) -> bool { - self.instr.has_const() || matches!(self.instr.real_opcode(), Some(Opcode::LoadSmallInt)) + self.instr.has_const() + || matches!( + self.instr.real_opcode(), + Some(Opcode::LoadSmallInt | Opcode::LoadCommonConstant) + ) } /// flowgraph.c STORES_TO @@ -478,6 +482,26 @@ impl InstructionInfo { false } + /// flowgraph.c maybe_instr_make_load_common_const + fn maybe_instr_make_load_common_const(&mut self, constant: &ConstantData) -> bool { + use oparg::CommonConstant; + let idx = match constant { + ConstantData::None => CommonConstant::None, + ConstantData::Boolean { value: true } => CommonConstant::True, + ConstantData::Boolean { value: false } => CommonConstant::False, + ConstantData::Str { value } if value.is_empty() => CommonConstant::EmptyStr, + ConstantData::Integer { value } if value.to_i32() == Some(-1) => { + CommonConstant::MinusOne + } + _ => return false, + }; + self.instr_set_op1( + Opcode::LoadCommonConstant.into(), + OpArg::new(u32::from(idx)), + ); + true + } + /// flowgraph.c make_super_instruction fn make_super_instruction(inst1: &mut Self, inst2: &mut Self, super_op: AnyInstruction) { let line1 = inst1.instruction_lineno(); @@ -1084,7 +1108,7 @@ fn resolve_unconditional_jumps(instr_sequence: &mut InstructionSequence) { /// assemble.c resolve_jump_offsets fn resolve_jump_offsets(instr_sequence: &mut InstructionSequence) { // The offset (in code units) of END_SEND from SEND in the yield-from sequence. - const END_SEND_OFFSET: i32 = 5; + const END_SEND_OFFSET: i32 = 6; for i in 0..instr_sequence.instr_used { let instr = &mut instr_sequence.instrs[i]; let opcode = instr.info.instr.expect_real(); @@ -2227,7 +2251,7 @@ impl Blocks { AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => { match func.get(inst.arg) { IntrinsicFunction1::ListToTuple => { - if matches!(nextop, Some(Instruction::GetIter)) { + if matches!(nextop, Some(Instruction::GetIter { .. })) { self[block_idx].instructions[i].set_to_nop(); } else { fold_constant_intrinsic_list_to_tuple( @@ -2410,6 +2434,7 @@ impl Blocks { Instruction::FormatSimple | Instruction::GetAnext | Instruction::GetLen + | Instruction::GetIter { .. } | Instruction::GetYieldFromIter | Instruction::ImportFrom { .. } | Instruction::MatchKeys @@ -2420,10 +2445,8 @@ impl Blocks { let effect = instr.stack_effect_info(arg_u32); let net_pushed = effect.pushed() as isize - effect.popped() as isize; debug_assert!(net_pushed >= 0); - // CPython optimize_load_fast() shadows the outer - // instruction index in this produced-value loop. - for produced in 0..net_pushed { - push_ref(&mut refs, produced, NOT_LOCAL)?; + for _ in 0..net_pushed { + push_ref(&mut refs, i as isize, NOT_LOCAL)?; } } AnyInstruction::Real( @@ -2447,10 +2470,19 @@ impl Blocks { Instruction::EndSend | Instruction::SetFunctionAttribute { .. }, ) => { let effect = instr.stack_effect_info(arg_u32); - debug_assert_eq!(effect.popped(), 2); + debug_assert_eq!( + effect.popped(), + if matches!(instr.real(), Some(Instruction::EndSend)) { + 3 + } else { + 2 + } + ); debug_assert_eq!(effect.pushed(), 1); let tos = ref_stack_pop(&mut refs); - let _ = ref_stack_pop(&mut refs); + for _ in 1..effect.popped() { + let _ = ref_stack_pop(&mut refs); + } push_ref(&mut refs, tos.instr, tos.local)?; } AnyInstruction::Real(Instruction::CheckExcMatch) => { @@ -2634,8 +2666,14 @@ impl Blocks { let opcode = instr_info.instr.real_opcode(); let is_redundant_pair = matches!(opcode, Some(Opcode::PopTop)) - && (matches!(prev_opcode, Some(Opcode::LoadConst | Opcode::LoadSmallInt)) - || (prev_oparg == 1 && matches!(prev_opcode, Some(Opcode::Copy)))); + && (matches!( + prev_opcode, + Some( + Opcode::LoadConst + | Opcode::LoadSmallInt + | Opcode::LoadCommonConstant + ) + ) || (prev_oparg == 1 && matches!(prev_opcode, Some(Opcode::Copy)))); if is_redundant_pair { let (prev_block, prev_instr_idx) = @@ -3452,6 +3490,8 @@ impl Blocks { debug_assert!(dest <= instr_count); let num_removed = instr_count - dest; self[block_idx].instruction_used = dest; + // Later instructions reuse these slots and must not inherit a removed handler. + self[block_idx].instructions[dest..instr_count].fill(InstructionInfo::empty()); num_removed } @@ -4009,6 +4049,35 @@ impl CodeInfo { ) } + pub(crate) fn insert_genexpr_prefix( + &mut self, + mut iterator: InstructionInfo, + ) -> crate::InternalResult<()> { + let mut resume = InstructionInfo::empty(); + resume.instr_set_op1( + Opcode::Resume.into(), + OpArg::new(oparg::ResumeLocation::AtGenExprStart.into()), + ); + resume.lineno_override = Some(NO_LOCATION_OVERRIDE); + let mut load = iterator; + load.instr_set_op1(Opcode::LoadFast.into(), OpArg::new(0)); + iterator.arg = OpArg::new(0); + let mut return_generator = InstructionInfo::empty(); + return_generator.instr_set_op0(Instruction::ReturnGenerator.into()); + return_generator.location.line = self.metadata.firstlineno; + return_generator.end_location.line = self.metadata.firstlineno; + return_generator.lineno_override = Some(LINE_ONLY_LOCATION_OVERRIDE); + let mut pop = return_generator; + pop.instr_set_op0(Instruction::PopTop.into()); + for (i, info) in [resume, load, iterator, return_generator, pop] + .into_iter() + .enumerate() + { + instruction_sequence_insert_instruction(&mut self.instr_sequence, i, info)?; + } + Ok(()) + } + pub(crate) fn push_unmapped_instr_sequence_label(&mut self) -> crate::InternalResult<()> { instruction_sequence_label_map_push_unmapped_label( &mut self.instr_sequence_label_map, @@ -4344,7 +4413,12 @@ fn insert_prefix_instructions( let firstlineno = metadata.firstlineno; debug_assert!(firstlineno.get() > 0); - if is_generator(flags) { + if is_generator(flags) + && !entry + .used_instructions() + .iter() + .any(|info| matches!(info.instr.real(), Some(Instruction::ReturnGenerator))) + { let location = SourceLocation { line: firstlineno, character_offset: OneIndexed::MIN, @@ -4517,6 +4591,13 @@ fn load_const_truthiness( Some(constant.truthiness()) } Instruction::LoadSmallInt { i } => Some(i.get(arg) != 0), + Instruction::LoadCommonConstant { idx } => { + use oparg::CommonConstant; + Some(!matches!( + idx.get(arg), + CommonConstant::None | CommonConstant::False | CommonConstant::EmptyStr + )) + } _ => None, } } @@ -4534,7 +4615,9 @@ fn instr_make_load_const( instr: &mut InstructionInfo, constant: ConstantData, ) -> crate::InternalResult<()> { - if instr.maybe_instr_make_load_smallint(&constant) { + if instr.maybe_instr_make_load_smallint(&constant) + || instr.maybe_instr_make_load_common_const(&constant) + { return Ok(()); } @@ -4627,6 +4710,21 @@ fn fold_const_binop( /// flowgraph.c get_const_value fn get_const_value(metadata: &CodeUnitMetadata, info: &InstructionInfo) -> Option { match info.instr.real_opcode() { + Some(Opcode::LoadCommonConstant) => { + use oparg::CommonConstant; + match CommonConstant::try_from(u32::from(info.arg)).ok()? { + CommonConstant::None => Some(ConstantData::None), + CommonConstant::EmptyStr => Some(ConstantData::Str { + value: Wtf8Buf::new(), + }), + CommonConstant::True => Some(ConstantData::Boolean { value: true }), + CommonConstant::False => Some(ConstantData::Boolean { value: false }), + CommonConstant::MinusOne => Some(ConstantData::Integer { + value: BigInt::from(-1), + }), + _ => None, + } + } Some(Opcode::LoadSmallInt) => { let v = u32::from(info.arg) as i32; Some(ConstantData::Integer { @@ -5449,7 +5547,7 @@ fn optimize_lists_and_sets( let contains_or_iter = matches!( nextop, - Some(Instruction::GetIter | Instruction::ContainsOp { .. }) + Some(Instruction::GetIter { .. } | Instruction::ContainsOp { .. }) ); let seq_size = u32::from(block.instructions[i].arg) as usize; if seq_size > STACK_USE_GUIDELINE || (seq_size < MIN_CONST_SEQUENCE_SIZE && !contains_or_iter) { @@ -5539,84 +5637,88 @@ fn basicblock_optimize_load_const( metadata: &mut CodeUnitMetadata, block: &mut Block, ) -> crate::InternalResult<()> { - let mut i = 0; let mut effective_opcode = Instruction::Nop.into(); let mut effective_oparg = OpArg::new(0); - while i < block.instruction_used { - if matches!( - block.instructions[i].instr.real(), - Some(Instruction::LoadConst { .. }) - ) && let Some(constant) = get_const_value(metadata, &block.instructions[i]) - { - block.instructions[i].maybe_instr_make_load_smallint(&constant); - } + for i in 0..block.instruction_used { + 'instruction: { + if matches!( + block.instructions[i].instr.real(), + Some(Instruction::LoadConst { .. }) + ) && let Some(constant) = get_const_value(metadata, &block.instructions[i]) + { + block.instructions[i].maybe_instr_make_load_smallint(&constant); + } - let curr = block.instructions[i]; - let curr_arg = curr.arg; + let curr = block.instructions[i]; + let curr_arg = curr.arg; - let is_copy_of_load_const = matches!( - (effective_opcode, curr.instr.real()), - (AnyInstruction::Real(Instruction::LoadConst { .. }), Some(Instruction::Copy { i })) - if i.get(curr_arg) == 1 - ); - if !is_copy_of_load_const { - effective_opcode = curr.instr; - effective_oparg = curr_arg; - } - debug_assert!(!effective_opcode.is_assembler()); - let Some(const_instr @ (Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. })) = - effective_opcode.real() - else { - i += 1; - continue; - }; - let const_arg = effective_oparg; + let is_copy_of_load_const = matches!( + (effective_opcode, curr.instr.real()), + (AnyInstruction::Real(Instruction::LoadConst { .. }), Some(Instruction::Copy { i })) + if i.get(curr_arg) == 1 + ); + if !is_copy_of_load_const { + effective_opcode = curr.instr; + effective_oparg = curr_arg; + } + debug_assert!(!effective_opcode.is_assembler()); + let Some( + const_instr @ (Instruction::LoadConst { .. } + | Instruction::LoadSmallInt { .. } + | Instruction::LoadCommonConstant { .. }), + ) = effective_opcode.real() + else { + break 'instruction; + }; + let const_arg = effective_oparg; - if i + 1 >= block.instruction_used { - i += 1; - continue; - } + if i + 1 >= block.instruction_used { + break 'instruction; + } - let next = block.instructions[i + 1]; - let next_arg = next.arg; + let next = block.instructions[i + 1]; + let next_arg = next.arg; - if let Some(is_true) = load_const_truthiness(const_instr, const_arg, metadata) { - let const_jump = match (next.instr.real_opcode(), next.instr.pseudo_opcode()) { - (_, Some(PseudoOpcode::JumpIfTrue)) => Some((true, false)), - (_, Some(PseudoOpcode::JumpIfFalse)) => Some((false, false)), - (Some(Opcode::PopJumpIfTrue), _) => Some((true, true)), - (Some(Opcode::PopJumpIfFalse), _) => Some((false, true)), - _ => None, - }; - if let Some((jump_if_true, pops_condition)) = const_jump { - if pops_condition { - block.instructions[i].set_to_nop(); - } - if is_true == jump_if_true { - block.instructions[i + 1].instr = PseudoOpcode::Jump.into(); - } else { - block.instructions[i + 1].set_to_nop(); + if let Some(is_true) = load_const_truthiness(const_instr, const_arg, metadata) { + let const_jump = match (next.instr.real_opcode(), next.instr.pseudo_opcode()) { + (_, Some(PseudoOpcode::JumpIfTrue)) => Some((true, false)), + (_, Some(PseudoOpcode::JumpIfFalse)) => Some((false, false)), + (Some(Opcode::PopJumpIfTrue), _) => Some((true, true)), + (Some(Opcode::PopJumpIfFalse), _) => Some((false, true)), + _ => None, + }; + if let Some((jump_if_true, pops_condition)) = const_jump { + if pops_condition { + block.instructions[i].set_to_nop(); + } + if is_true == jump_if_true { + block.instructions[i + 1].instr = PseudoOpcode::Jump.into(); + } else { + block.instructions[i + 1].set_to_nop(); + } + break 'instruction; } - i += 1; - continue; } - } - // The remaining combinations require both instructions to be real. - let Some(next_instr) = next.instr.real() else { - i += 1; - continue; - }; + // The remaining combinations require both instructions to be real. + let Some(next_instr) = next.instr.real() else { + break 'instruction; + }; - if let Instruction::LoadConst { consti } = const_instr { - let constant = &metadata.consts[consti.get(const_arg).as_usize()]; - if matches!(constant, ConstantData::None) - && let Instruction::IsOp { invert } = next_instr - { + let is_none = match const_instr { + Instruction::LoadConst { consti } => matches!( + &metadata.consts[consti.get(const_arg).as_usize()], + ConstantData::None + ), + Instruction::LoadCommonConstant { idx } => { + idx.get(const_arg) == oparg::CommonConstant::None + } + _ => false, + }; + if is_none && let Instruction::IsOp { invert } = next_instr { let mut jump_idx = i + 2; if jump_idx >= block.instruction_used { - i += 1; - continue; + break 'instruction; } if matches!( @@ -5626,14 +5728,12 @@ fn basicblock_optimize_load_const( block.instructions[jump_idx].set_to_nop(); jump_idx += 1; if jump_idx >= block.instruction_used { - i += 1; - continue; + break 'instruction; } } let Some(jump_instr) = block.instructions[jump_idx].instr.real() else { - i += 1; - continue; + break 'instruction; }; let mut invert = matches!( @@ -5646,8 +5746,7 @@ fn basicblock_optimize_load_const( } Instruction::PopJumpIfTrue { .. } => {} _ => { - i += 1; - continue; + break 'instruction; } }; @@ -5659,27 +5758,32 @@ fn basicblock_optimize_load_const( Opcode::PopJumpIfNone } .into(); - i += 1; - continue; + break 'instruction; } - } + if matches!( + const_instr, + Instruction::LoadConst { .. } + | Instruction::LoadSmallInt { .. } + | Instruction::LoadCommonConstant { .. } + ) && matches!(next_instr, Instruction::ToBool) + && let Some(value) = load_const_truthiness(const_instr, const_arg, metadata) + { + let const_idx = add_const(metadata, ConstantData::Boolean { value })?; + block.instructions[i].set_to_nop(); + + block.instructions[i + 1] + .instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32)); + break 'instruction; + } + } if matches!( - const_instr, - Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. } - ) && matches!(next_instr, Instruction::ToBool) - && let Some(value) = load_const_truthiness(const_instr, const_arg, metadata) + block.instructions[i].instr.real_opcode(), + Some(Opcode::LoadConst) + ) && let Some(constant) = get_const_value(metadata, &block.instructions[i]) { - let const_idx = add_const(metadata, ConstantData::Boolean { value })?; - block.instructions[i].set_to_nop(); - - block.instructions[i + 1] - .instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32)); - i += 1; - continue; + block.instructions[i].maybe_instr_make_load_common_const(&constant); } - - i += 1; } Ok(()) } @@ -6968,7 +7072,9 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes } else if let Some(Instruction::Resume { context: _ }) = instr.real() { blocks[bi].instructions[i].except_handler = handler; let resume_arg = u32::from(arg); - if resume_arg != u32::from(oparg::ResumeLocation::AtFuncStart) { + if resume_arg != u32::from(oparg::ResumeLocation::AtFuncStart) + && resume_arg != u32::from(oparg::ResumeLocation::AtGenExprStart) + { debug_assert!(last_yield_except_depth >= 0); if last_yield_except_depth == 1 { blocks[bi].instructions[i].arg = @@ -6976,6 +7082,8 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes } last_yield_except_depth = -1; } + } else if matches!(instr.real(), Some(Instruction::ReturnGenerator)) { + blocks[bi].instructions[i].except_handler = None; } else { blocks[bi].instructions[i].except_handler = handler; } @@ -7726,7 +7834,7 @@ mod tests { // CPython `basicblock_optimize_load_const()` keeps the previous // LOAD_CONST as the effective opcode for a following `COPY 1`, so the - // COPY is NOPed and TO_BOOL becomes LOAD_CONST True. + // COPY is NOPed and TO_BOOL becomes LOAD_COMMON_CONSTANT True. assert!(matches!( code.blocks[0].instructions[0].instr.real(), Some(Instruction::LoadConst { .. }) @@ -7738,11 +7846,11 @@ mod tests { let load_bool = &code.blocks[0].instructions[2]; assert!(matches!( load_bool.instr.real(), - Some(Instruction::LoadConst { .. }) + Some(Instruction::LoadCommonConstant { .. }) )); assert_eq!( - code.metadata.consts[u32::from(load_bool.arg) as usize], - ConstantData::Boolean { value: true } + u32::from(load_bool.arg), + u32::from(oparg::CommonConstant::True) ); } @@ -7793,7 +7901,7 @@ mod tests { } #[test] - fn optimize_load_fast_records_no_input_opcode_ref_at_cpython_produced_index() { + fn optimize_load_fast_records_no_input_opcode_ref_at_cpython_instruction_index() { let mut block = Block::default(); test_block_push(&mut block, test_instr(Opcode::LoadFast.into(), 10)); test_block_push(&mut block, test_instr(Instruction::GetLen, 10)); @@ -7807,13 +7915,11 @@ mod tests { .optimize_load_fast() .expect("optimize_load_fast succeeds"); - // CPython `optimize_load_fast()` shadows the outer instruction index in - // the produced-value loop for GET_LEN, so the produced ref is recorded - // with index 0 here. The original LOAD_FAST is therefore not considered - // the consumed producer. + // CPython records GET_LEN's result at instruction index 1. The + // SWAP/POP_TOP consumes the original local reference, so it can borrow. assert!(matches!( code.blocks[0].instructions[0].instr.real(), - Some(Instruction::LoadFast { .. }) + Some(Instruction::LoadFastBorrow { .. }) )); } @@ -8079,4 +8185,128 @@ mod tests { assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(2)); } + + #[test] + fn cpython315_common_constants_lower_and_round_trip_without_numeric_aliasing() { + use oparg::CommonConstant; + for (constant, common) in [ + (ConstantData::None, CommonConstant::None), + ( + ConstantData::Str { + value: Wtf8Buf::new(), + }, + CommonConstant::EmptyStr, + ), + (ConstantData::Boolean { value: true }, CommonConstant::True), + ( + ConstantData::Boolean { value: false }, + CommonConstant::False, + ), + (int_const(-1), CommonConstant::MinusOne), + ] { + let mut metadata = test_code_info(Block::default()).metadata; + metadata.consts.insert_full(constant.clone()); + let mut block = Block::default(); + test_block_push(&mut block, test_instr(Opcode::LoadConst.into(), 1)); + basicblock_optimize_load_const(&mut metadata, &mut block).unwrap(); + let info = block.instructions[0]; + assert_eq!(info.instr.real_opcode(), Some(Opcode::LoadCommonConstant)); + assert_eq!(u32::from(info.arg), u32::from(common)); + assert_eq!(get_const_value(&metadata, &info), Some(constant.clone())); + assert_eq!( + load_const_truthiness(info.instr.expect_real(), info.arg, &metadata), + Some(constant.truthiness()) + ); + } + for constant in [ + int_const(0), + int_const(1), + ConstantData::Float { value: -1.0 }, + ] { + assert!( + !test_instr(Opcode::LoadConst.into(), 1) + .maybe_instr_make_load_common_const(&constant) + ); + } + } + + #[test] + fn cpython315_get_iter_tracks_both_stack_slots_for_borrow_analysis() { + let mut block = Block::default(); + for (op, arg) in [ + (Opcode::LoadFast, 0), + (Opcode::GetIter, 0), + (Opcode::Swap, 2), + (Opcode::PopTop, 0), + (Opcode::PopTop, 0), + ] { + let mut info = test_instr(op.into(), 1); + info.arg = OpArg::new(arg); + test_block_push(&mut block, info); + } + let mut code = test_code_info(block); + code.blocks.optimize_load_fast().unwrap(); + assert_eq!( + code.blocks[0].instructions[0].instr.real_opcode(), + Some(Opcode::LoadFastBorrow) + ); + } + + #[test] + fn cpython315_end_send_preserves_result_borrow_and_consumes_iterator_pair() { + let mut block = Block::default(); + for (op, arg) in [ + ( + Opcode::LoadCommonConstant, + u32::from(oparg::CommonConstant::None), + ), + (Opcode::PushNull, 0), + (Opcode::LoadFast, 0), + (Opcode::EndSend, 0), + (Opcode::PopTop, 0), + ] { + let mut info = test_instr(op.into(), 1); + info.arg = OpArg::new(arg); + test_block_push(&mut block, info); + } + let mut code = test_code_info(block); + assert_eq!(code.blocks.calculate_stackdepth().unwrap(), 3); + code.blocks.optimize_load_fast().unwrap(); + assert_eq!( + code.blocks[0].instructions[2].instr.real_opcode(), + Some(Opcode::LoadFastBorrow) + ); + } + + #[test] + fn cpython315_redundant_nop_compaction_clears_vacated_metadata() { + let handler = ExceptHandlerInfo { + handler_block: BlockIdx::new(2), + preserve_lasti: true, + }; + let mut block = Block::default(); + for _ in 0..2 { + let mut nop = test_instr(Instruction::Nop, 10); + nop.lineno_override = Some(NO_LOCATION_OVERRIDE); + nop.target = BlockIdx::new(1); + nop.except_handler = Some(handler); + test_block_push(&mut block, nop); + } + let mut jump = test_instr(Opcode::PopJumpIfTrue.into(), 10); + jump.target = BlockIdx::new(1); + jump.except_handler = Some(handler); + test_block_push(&mut block, jump); + let mut blocks = Blocks::from([block, Block::default(), Block::default()]); + assert_eq!(blocks.basicblock_remove_redundant_nops(BlockIdx::new(0)), 2); + assert_eq!(blocks[0].instructions[0].target, jump.target); + assert_eq!(blocks[0].instructions[0].except_handler, Some(handler)); + for slot in &blocks[0].instructions[1..3] { + assert_eq!(slot.target, BlockIdx::NULL); + assert_eq!(slot.except_handler, None); + } + blocks.normalize_jumps_in_block(BlockIdx::new(0)).unwrap(); + let not_taken = &blocks[0].instructions[1]; + assert_eq!(not_taken.instr.real_opcode(), Some(Opcode::NotTaken)); + assert_eq!(not_taken.except_handler, None); + } } diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index f7d117cb76b..d0c1ef92a8a 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -35,6 +35,7 @@ pub use crate::bytecode::{ mod instruction; mod opcode_metadata; +pub mod public_metadata; pub mod oparg; @@ -286,12 +287,16 @@ impl PyCodeLocationInfoKind { pub trait Constant: Sized + Clone { type Name: AsRef; + /// An immutable owner whose element view remains valid while borrowed. + type TupleOwner: Clone + AsRef<[Self]>; + /// Transforms the given Constant to a BorrowedConstant fn borrow_constant(&self) -> BorrowedConstant<'_, Self>; } impl Constant for ConstantData { type Name = String; + type TupleOwner = Box<[Self]>; fn borrow_constant(&self) -> BorrowedConstant<'_, Self> { match self { @@ -373,19 +378,59 @@ impl ConstantBag for BasicBag { } #[derive(Clone)] -pub struct Constants(Box<[C]>); +pub struct Constants(ConstantStorage); + +#[derive(Clone)] +enum ConstantStorage { + Boxed(Box<[C]>), + Tuple(C::TupleOwner), +} + +impl Constants { + pub fn from_tuple(owner: C::TupleOwner) -> Self { + Self(ConstantStorage::Tuple(owner)) + } + + pub fn tuple_owner(&self) -> Option<&C::TupleOwner> { + match &self.0 { + ConstantStorage::Tuple(owner) => Some(owner), + ConstantStorage::Boxed(_) => None, + } + } + + /// Consume boxed elements without adding another lasting element owner. + /// An immutable tuple must be copied when its elements are taken out. + pub fn into_boxed(self) -> Box<[C]> { + match self.0 { + ConstantStorage::Boxed(elements) => elements, + ConstantStorage::Tuple(owner) => owner.as_ref().into(), + } + } +} impl Deref for Constants { type Target = [C]; fn deref(&self) -> &Self::Target { - &self.0 + match &self.0 { + ConstantStorage::Boxed(elements) => elements, + ConstantStorage::Tuple(owner) => owner.as_ref(), + } } } impl DerefMut for Constants { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + if let ConstantStorage::Tuple(owner) = &self.0 { + // Allocate and clone before releasing the old owner. No published + // tuple is ever mutated, even when it has only one current owner. + let elements = owner.as_ref().into(); + self.0 = ConstantStorage::Boxed(elements); + } + match &mut self.0 { + ConstantStorage::Boxed(elements) => elements, + ConstantStorage::Tuple(_) => unreachable!(), + } } } @@ -393,19 +438,19 @@ impl Index for Constants { type Output = C; fn index(&self, consti: oparg::ConstIdx) -> &Self::Output { - &self.0[consti.as_usize()] + &self.deref()[consti.as_usize()] } } impl IndexMut for Constants { fn index_mut(&mut self, consti: oparg::ConstIdx) -> &mut Self::Output { - &mut self.0[consti.as_usize()] + &mut self.deref_mut()[consti.as_usize()] } } impl FromIterator for Constants { fn from_iter>(iter: T) -> Self { - Self(iter.into_iter().collect()) + Self(ConstantStorage::Boxed(iter.into_iter().collect())) } } @@ -734,6 +779,26 @@ impl Deref for CodeUnits { } impl CodeUnits { + /// Apply final 3.15 GET_ITER mode fixups before code publication. + /// Python/specialize.c:fixup_getiter normalizes every nonzero mode. + pub fn normalize_get_iter(&mut self, flags: CodeFlags) { + let units = self.units.get_mut(); + let mut index = 0; + while index < units.len() { + let unit = &mut units[index]; + if matches!(unit.op, Instruction::GetIter { .. }) && unit.arg != 0.into() { + let mode = if flags.intersects(CodeFlags::COROUTINE | CodeFlags::ITERABLE_COROUTINE) + { + 2 + } else { + 3 + }; + unit.arg = mode.into(); + } + index += 1 + unit.op.cache_entries(); + } + } + /// Disable adaptive specialization by setting all counters to unreachable. /// Used for CPython-compiled bytecode where specialization may not be safe. pub fn disable_specialization(&self) { @@ -742,6 +807,13 @@ impl CodeUnits { } } + /// Number of code units, without borrowing the live mutable instruction slice. + /// Constructors and Clone keep the out-of-line counter array at this length. + #[must_use] + pub fn unit_count(&self) -> usize { + self.adaptive_counters.len() + } + /// Replace the opcode at `index` in-place without changing the arg byte. /// Uses atomic Release store to ensure prior cache writes are visible /// to threads that subsequently read the new opcode with Acquire. diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index f403507f1aa..0f680d7d1f0 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -333,261 +333,261 @@ define_opcodes!( FormatWithSpec = 13, GetAiter = 14, GetAnext = 15, - GetIter = 16, + GetIter { mode: Arg } = 70, Reserved = 17, - GetLen = 18, - GetYieldFromIter = 19, - InterpreterExit = 20, - LoadBuildClass = 21, - LoadLocals = 22, - MakeFunction = 23, - MatchKeys = 24, - MatchMapping = 25, - MatchSequence = 26, - Nop = 27, - NotTaken = 28, - PopExcept = 29, - PopIter = 30, - PopTop = 31, - PushExcInfo = 32, - PushNull = 33, - ReturnGenerator = 34, - ReturnValue = 35, - SetupAnnotations = 36, - StoreSlice = 37, - StoreSubscr = 38, - ToBool = 39, - UnaryInvert = 40, - UnaryNegative = 41, - UnaryNot = 42, - WithExceptStart = 43, + GetLen = 16, + GetYieldFromIter = 219, + InterpreterExit = 18, + LoadBuildClass = 19, + LoadLocals = 20, + MakeFunction = 21, + MatchKeys = 22, + MatchMapping = 23, + MatchSequence = 24, + Nop = 25, + NotTaken = 26, + PopExcept = 27, + PopIter = 28, + PopTop = 29, + PushExcInfo = 30, + PushNull = 31, + ReturnGenerator = 32, + ReturnValue = 33, + SetupAnnotations = 34, + StoreSlice = 35, + StoreSubscr = 36, + ToBool = 37, + UnaryInvert = 38, + UnaryNegative = 39, + UnaryNot = 40, + WithExceptStart = 41, BinaryOp { op: Arg, - } = 44, + } = 42, BuildInterpolation { format: Arg, - } = 45, + } = 43, BuildList { count: Arg, - } = 46, + } = 44, BuildMap { count: Arg, - } = 47, + } = 45, BuildSet { count: Arg, - } = 48, + } = 46, BuildSlice { argc: Arg, - } = 49, + } = 47, BuildString { count: Arg, - } = 50, + } = 48, BuildTuple { count: Arg, - } = 51, + } = 49, Call { argc: Arg, - } = 52, + } = 50, CallIntrinsic1 { func: Arg, - } = 53, + } = 51, CallIntrinsic2 { func: Arg, - } = 54, + } = 52, CallKw { argc: Arg, - } = 55, + } = 53, CompareOp { opname: Arg, - } = 56, + } = 54, ContainsOp { invert: Arg, - } = 57, + } = 55, ConvertValue { oparg: Arg, - } = 58, + } = 56, Copy { i: Arg, - } = 59, + } = 57, CopyFreeVars { n: Arg, - } = 60, + } = 58, DeleteAttr { namei: Arg, - } = 61, + } = 59, DeleteDeref { i: Arg, - } = 62, + } = 60, DeleteFast { var_num: Arg, - } = 63, + } = 61, DeleteGlobal { namei: Arg, - } = 64, + } = 62, DeleteName { namei: Arg, - } = 65, + } = 63, DictMerge { i: Arg, - } = 66, + } = 64, DictUpdate { i: Arg, - } = 67, - EndAsyncFor = 68, - ExtendedArg = 69, + } = 65, + EndAsyncFor = 66, + ExtendedArg = 67, ForIter { delta: Arg, - } = 70, + } = 68, GetAwaitable { r#where: Arg, - } = 71, + } = 69, ImportFrom { namei: Arg, - } = 72, + } = 71, ImportName { namei: Arg, - } = 73, + } = 72, IsOp { invert: Arg, - } = 74, + } = 73, JumpBackward { delta: Arg, - } = 75, + } = 74, JumpBackwardNoInterrupt { delta: Arg, - } = 76, + } = 75, JumpForward { delta: Arg, - } = 77, + } = 76, ListAppend { i: Arg, - } = 78, + } = 77, ListExtend { i: Arg, - } = 79, + } = 78, LoadAttr { namei: Arg, - } = 80, + } = 79, LoadCommonConstant { idx: Arg, - } = 81, + } = 80, LoadConst { consti: Arg, - } = 82, + } = 81, LoadDeref { i: Arg, - } = 83, + } = 82, LoadFast { var_num: Arg, - } = 84, + } = 83, LoadFastAndClear { var_num: Arg, - } = 85, + } = 84, LoadFastBorrow { var_num: Arg, - } = 86, + } = 85, LoadFastBorrowLoadFastBorrow { var_nums: Arg, - } = 87, + } = 86, LoadFastCheck { var_num: Arg, - } = 88, + } = 87, LoadFastLoadFast { var_nums: Arg, - } = 89, + } = 88, LoadFromDictOrDeref { i: Arg, - } = 90, + } = 89, LoadFromDictOrGlobals { i: Arg, - } = 91, + } = 90, LoadGlobal { namei: Arg, - } = 92, + } = 91, LoadName { namei: Arg, - } = 93, + } = 92, LoadSmallInt { i: Arg, - } = 94, + } = 93, LoadSpecial { method: Arg, - } = 95, + } = 94, LoadSuperAttr { namei: Arg, - } = 96, + } = 95, MakeCell { i: Arg, - } = 97, + } = 96, MapAdd { i: Arg, - } = 98, + } = 97, MatchClass { count: Arg, - } = 99, + } = 98, PopJumpIfFalse { delta: Arg, - } = 100, + } = 99, PopJumpIfNone { delta: Arg, - } = 101, + } = 100, PopJumpIfNotNone { delta: Arg, - } = 102, + } = 101, PopJumpIfTrue { delta: Arg, - } = 103, + } = 102, RaiseVarargs { argc: Arg, - } = 104, + } = 103, Reraise { depth: Arg, - } = 105, + } = 104, Send { delta: Arg, - } = 106, + } = 105, SetAdd { i: Arg, - } = 107, + } = 106, SetFunctionAttribute { flag: Arg, - } = 108, + } = 107, SetUpdate { i: Arg, - } = 109, + } = 108, StoreAttr { namei: Arg, - } = 110, + } = 109, StoreDeref { i: Arg, - } = 111, + } = 110, StoreFast { var_num: Arg, - } = 112, + } = 111, StoreFastLoadFast { var_nums: Arg, - } = 113, + } = 112, StoreFastStoreFast { var_nums: Arg, - } = 114, + } = 113, StoreGlobal { namei: Arg, - } = 115, + } = 114, StoreName { namei: Arg, - } = 116, + } = 115, Swap { i: Arg, - } = 117, + } = 116, UnpackEx { counts: Arg, - } = 118, + } = 117, UnpackSequence { count: Arg, - } = 119, + } = 118, YieldValue { arg: Arg, - } = 120, + } = 119, Resume { context: Arg, } = 128, @@ -603,99 +603,99 @@ define_opcodes!( BinaryOpSubscrListSlice = 138, BinaryOpSubscrStrInt = 139, BinaryOpSubscrTupleInt = 140, - BinaryOpSubtractFloat = 141, - BinaryOpSubtractInt = 142, - CallAllocAndEnterInit = 143, - CallBoundMethodExactArgs = 144, - CallBoundMethodGeneral = 145, - CallBuiltinClass = 146, - CallBuiltinFast = 147, - CallBuiltinFastWithKeywords = 148, - CallBuiltinO = 149, - CallIsinstance = 150, - CallKwBoundMethod = 151, - CallKwNonPy = 152, - CallKwPy = 153, - CallLen = 154, - CallListAppend = 155, - CallMethodDescriptorFast = 156, - CallMethodDescriptorFastWithKeywords = 157, - CallMethodDescriptorNoargs = 158, - CallMethodDescriptorO = 159, - CallNonPyGeneral = 160, - CallPyExactArgs = 161, - CallPyGeneral = 162, - CallStr1 = 163, - CallTuple1 = 164, - CallType1 = 165, - CompareOpFloat = 166, - CompareOpInt = 167, - CompareOpStr = 168, - ContainsOpDict = 169, - ContainsOpSet = 170, - ForIterGen = 171, - ForIterList = 172, - ForIterRange = 173, - ForIterTuple = 174, - JumpBackwardJit = 175, - JumpBackwardNoJit = 176, - LoadAttrClass = 177, - LoadAttrClassWithMetaclassCheck = 178, - LoadAttrGetattributeOverridden = 179, - LoadAttrInstanceValue = 180, - LoadAttrMethodLazyDict = 181, - LoadAttrMethodNoDict = 182, - LoadAttrMethodWithValues = 183, - LoadAttrModule = 184, - LoadAttrNondescriptorNoDict = 185, - LoadAttrNondescriptorWithValues = 186, - LoadAttrProperty = 187, - LoadAttrSlot = 188, - LoadAttrWithHint = 189, - LoadConstImmortal = 190, - LoadConstMortal = 191, - LoadGlobalBuiltin = 192, - LoadGlobalModule = 193, - LoadSuperAttrAttr = 194, - LoadSuperAttrMethod = 195, - ResumeCheck = 196, - SendGen = 197, - StoreAttrInstanceValue = 198, - StoreAttrSlot = 199, - StoreAttrWithHint = 200, - StoreSubscrDict = 201, - StoreSubscrListInt = 202, - ToBoolAlwaysTrue = 203, - ToBoolBool = 204, - ToBoolInt = 205, - ToBoolList = 206, - ToBoolNone = 207, - ToBoolStr = 208, - UnpackSequenceList = 209, - UnpackSequenceTuple = 210, - UnpackSequenceTwoTuple = 211, - InstrumentedEndFor = 234, - InstrumentedPopIter = 235, - InstrumentedEndSend = 236, - InstrumentedForIter = 237, - InstrumentedInstruction = 238, - InstrumentedJumpForward = 239, - InstrumentedNotTaken = 240, - InstrumentedPopJumpIfTrue = 241, - InstrumentedPopJumpIfFalse = 242, - InstrumentedPopJumpIfNone = 243, - InstrumentedPopJumpIfNotNone = 244, - InstrumentedResume = 245, - InstrumentedReturnValue = 246, - InstrumentedYieldValue = 247, - InstrumentedEndAsyncFor = 248, - InstrumentedLoadSuperAttr = 249, - InstrumentedCall = 250, - InstrumentedCallKw = 251, - InstrumentedCallFunctionEx = 252, - InstrumentedJumpBackward = 253, - InstrumentedLine = 254, - EnterExecutor = 255, + BinaryOpSubtractFloat = 142, + BinaryOpSubtractInt = 143, + CallAllocAndEnterInit = 144, + CallBoundMethodExactArgs = 145, + CallBoundMethodGeneral = 146, + CallBuiltinClass = 147, + CallBuiltinFast = 148, + CallBuiltinFastWithKeywords = 149, + CallBuiltinO = 150, + CallIsinstance = 153, + CallKwBoundMethod = 154, + CallKwNonPy = 155, + CallKwPy = 156, + CallLen = 157, + CallListAppend = 158, + CallMethodDescriptorFast = 159, + CallMethodDescriptorFastWithKeywords = 160, + CallMethodDescriptorNoargs = 161, + CallMethodDescriptorO = 162, + CallNonPyGeneral = 163, + CallPyExactArgs = 164, + CallPyGeneral = 165, + CallStr1 = 166, + CallTuple1 = 167, + CallType1 = 168, + CompareOpFloat = 169, + CompareOpInt = 170, + CompareOpStr = 171, + ContainsOpDict = 172, + ContainsOpSet = 173, + ForIterGen = 174, + ForIterList = 175, + ForIterRange = 176, + ForIterTuple = 177, + JumpBackwardJit = 181, + JumpBackwardNoJit = 182, + LoadAttrClass = 183, + LoadAttrClassWithMetaclassCheck = 184, + LoadAttrGetattributeOverridden = 185, + LoadAttrInstanceValue = 186, + LoadAttrMethodLazyDict = 187, + LoadAttrMethodNoDict = 188, + LoadAttrMethodWithValues = 189, + LoadAttrModule = 190, + LoadAttrNondescriptorNoDict = 191, + LoadAttrNondescriptorWithValues = 192, + LoadAttrProperty = 193, + LoadAttrSlot = 194, + LoadAttrWithHint = 195, + LoadConstImmortal = 220, + LoadConstMortal = 221, + LoadGlobalBuiltin = 196, + LoadGlobalModule = 197, + LoadSuperAttrAttr = 198, + LoadSuperAttrMethod = 199, + ResumeCheck = 200, + SendGen = 203, + StoreAttrInstanceValue = 205, + StoreAttrSlot = 206, + StoreAttrWithHint = 207, + StoreSubscrDict = 208, + StoreSubscrListInt = 209, + ToBoolAlwaysTrue = 210, + ToBoolBool = 211, + ToBoolInt = 212, + ToBoolList = 213, + ToBoolNone = 214, + ToBoolStr = 215, + UnpackSequenceList = 216, + UnpackSequenceTuple = 217, + UnpackSequenceTwoTuple = 218, + InstrumentedEndFor = 233, + InstrumentedPopIter = 234, + InstrumentedEndSend = 235, + InstrumentedForIter = 236, + InstrumentedInstruction = 237, + InstrumentedJumpForward = 238, + InstrumentedNotTaken = 239, + InstrumentedPopJumpIfTrue = 240, + InstrumentedPopJumpIfFalse = 241, + InstrumentedPopJumpIfNone = 242, + InstrumentedPopJumpIfNotNone = 243, + InstrumentedResume = 244, + InstrumentedReturnValue = 245, + InstrumentedYieldValue = 246, + InstrumentedEndAsyncFor = 247, + InstrumentedLoadSuperAttr = 248, + InstrumentedCall = 249, + InstrumentedCallKw = 250, + InstrumentedCallFunctionEx = 251, + InstrumentedJumpBackward = 252, + InstrumentedLine = 253, + EnterExecutor = 254, } ); @@ -1342,6 +1342,7 @@ const _: () = assert!(core::mem::size_of::() == 2); #[cfg(test)] mod tests { use super::*; + use alloc::vec; #[test] fn eval_break_flags_match_cpython_jump_metadata() { @@ -1436,10 +1437,71 @@ mod tests { assert!(!AnyInstruction::from(PseudoOpcode::Jump).has_const()); } + include!("target_stack_cases.rs"); + + #[test] + fn get_iter_modes_normalize_before_publication() { + use crate::bytecode::{CodeFlags, CodeUnit, CodeUnits}; + for flags in [ + CodeFlags::empty(), + CodeFlags::COROUTINE, + CodeFlags::ITERABLE_COROUTINE, + ] { + for input in 0..=3u8 { + let mut units = CodeUnits::from(vec![ + CodeUnit::new(Opcode::GetIter.as_instruction(), input.into()), + CodeUnit::new(Opcode::Cache.as_instruction(), 0.into()), + CodeUnit::new(Opcode::ReturnValue.as_instruction(), 0.into()), + ]); + units.normalize_get_iter(flags); + let expected = if input == 0 { + 0 + } else if flags.is_empty() { + 3 + } else { + 2 + }; + assert_eq!(u8::from(units[0].arg), expected); + assert_eq!(units[1].op.as_opcode(), Opcode::Cache); + assert_eq!(units[2].op.as_opcode(), Opcode::ReturnValue); + } + } + } + + #[test] + fn private_load_const_exports_as_public_opcode() { + use crate::bytecode::{CodeUnit, CodeUnits}; + for opcode in [Opcode::LoadConstImmortal, Opcode::LoadConstMortal] { + let units = CodeUnits::from(vec![CodeUnit::new(opcode.as_instruction(), 7.into())]); + assert_eq!(units.original_bytes(), vec![Opcode::LoadConst.as_u8(), 7]); + } + } + + #[test] + fn supported_stack_contract_matches_pinned_c_metadata() { + let mut checked = 0; + for &(opcode, arg, popped, pushed) in TARGET_STACK_CASES { + let Ok(op) = AnyOpcode::try_from(opcode) else { + continue; + }; + let actual = match op { + AnyOpcode::Real(op) => op.stack_effect_info(arg), + AnyOpcode::Pseudo(op) => op.stack_effect_info(arg), + }; + assert_eq!( + (actual.popped(), actual.pushed()), + (popped, pushed), + "opcode {opcode}, argument {arg}" + ); + checked += 1; + } + assert_eq!(checked, 3290, "supported C stack contract coverage changed"); + } + #[test] fn stack_effects_match_cpython_opcode_metadata() { - assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 1); - assert_eq!(Opcode::ForIter.stack_effect_info(0).pushed(), 2); + assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 2); + assert_eq!(Opcode::ForIter.stack_effect_info(0).pushed(), 3); assert_eq!(Opcode::ForIter.stack_effect(0), 1); assert_eq!(Opcode::ForIter.stack_effect_jump(0), 1); @@ -1473,10 +1535,8 @@ mod tests { assert!(AnyInstruction::from(PseudoOpcode::Jump).is_no_fallthrough()); } - /// Snapshot of the chained `match` implementations that `Opcode::deopt` - /// and `Opcode::cache_entries` used before they were rewritten as table - /// lookups. Exists only to pin the observable behavior of the table - /// lookups against the logic they replaced. + /// Independent match reference for the pinned final 3.15 cache/deopt contract. + /// Keep exhaustive numeric iteration below to check generated lookup tables. mod reference { use super::Opcode; @@ -1576,6 +1636,7 @@ mod tests { pub(super) const fn cache_entries(op: Opcode) -> usize { match deoptimize(op) { + Opcode::Resume | Opcode::GetIter | Opcode::CallFunctionEx => 1, Opcode::StoreSubscr => 1, Opcode::ToBool => 3, Opcode::BinaryOp => 5, diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index 8f706003091..cccee707251 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -756,6 +756,16 @@ oparg_enum!( BuiltinList = (5, "list"), /// Built-in `set` type BuiltinSet = (6, "set"), + /// The None singleton. + None = (7, "None"), + /// The empty string. + EmptyStr = (8, "empty string"), + /// The true singleton. + True = (9, "True"), + /// The false singleton. + False = (10, "False"), + /// Integer minus one. + MinusOne = (11, "-1"), } ); @@ -906,11 +916,11 @@ newtype_oparg!( ); impl ResumeContext { - /// [CPython `RESUME_OPARG_LOCATION_MASK`](https://github.com/python/cpython/blob/v3.14.3/Include/internal/pycore_opcode_utils.h#L84) - pub const LOCATION_MASK: u32 = 0x3; + /// [CPython `RESUME_OPARG_LOCATION_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h) + pub const LOCATION_MASK: u32 = 0x7; - /// [CPython `RESUME_OPARG_DEPTH1_MASK`](https://github.com/python/cpython/blob/v3.14.3/Include/internal/pycore_opcode_utils.h#L85) - pub const DEPTH1_MASK: u32 = 0x4; + /// [CPython `RESUME_OPARG_DEPTH1_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h) + pub const DEPTH1_MASK: u32 = 0x8; #[must_use] pub const fn new(location: ResumeLocation, is_exception_depth1: bool) -> Self { @@ -926,8 +936,8 @@ impl ResumeContext { /// Resume location is determined by [`Self::LOCATION_MASK`]. #[must_use] pub fn location(&self) -> ResumeLocation { - // SAFETY: The mask should return a value that is in range. - unsafe { ResumeLocation::try_from(self.as_u32() & Self::LOCATION_MASK).unwrap_unchecked() } + ResumeLocation::try_from(self.as_u32() & Self::LOCATION_MASK) + .expect("invalid RESUME location") } /// True if the bit at [`Self::DEPTH1_MASK`] is on. @@ -947,6 +957,8 @@ pub enum ResumeLocation { AfterYieldFrom, /// After an `await` expression. AfterAwait, + /// Eager iterator acquisition before generator-expression creation. + AtGenExprStart, } impl From for ResumeContext { @@ -964,6 +976,7 @@ impl TryFrom for ResumeLocation { 1 => Self::AfterYield, 2 => Self::AfterYieldFrom, 3 => Self::AfterAwait, + 4 => Self::AtGenExprStart, _ => return Err(Self::Error::InvalidBytecode), }) } @@ -977,6 +990,7 @@ impl ResumeLocation { Self::AfterYield => 1, Self::AfterYieldFrom => 2, Self::AfterAwait => 3, + Self::AtGenExprStart => 4, } } diff --git a/crates/compiler-core/src/bytecode/opcode_metadata.rs b/crates/compiler-core/src/bytecode/opcode_metadata.rs index ca23e3c9e02..e62a80dadc4 100644 --- a/crates/compiler-core/src/bytecode/opcode_metadata.rs +++ b/crates/compiler-core/src/bytecode/opcode_metadata.rs @@ -15,15 +15,15 @@ impl super::Opcode { #[inline] pub const fn cache_entries(self) -> usize { const CACHE_ENTRIES: [u8; 256] = [ - 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 4, 4, 1, 1, 0, 1, 4, 4, 4, 1, 1, - 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 3, 3, 0, 1, 0, 0, + 0, 0, 0, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 3, + 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 1, 1, 1, 0, 0, + 1, 0, 4, 4, 4, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 3, 3, 1, 1, 0, 0, 0, ]; CACHE_ENTRIES[self.as_numeric() as usize] as usize @@ -174,6 +174,7 @@ impl super::Opcode { Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), + None, Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::Call), @@ -183,6 +184,8 @@ impl super::Opcode { Some(super::Opcode::Call), Some(super::Opcode::Call), Some(super::Opcode::Call), + None, + None, Some(super::Opcode::Call), Some(super::Opcode::CallKw), Some(super::Opcode::CallKw), @@ -208,6 +211,9 @@ impl super::Opcode { Some(super::Opcode::ForIter), Some(super::Opcode::ForIter), Some(super::Opcode::ForIter), + None, + None, + None, Some(super::Opcode::JumpBackward), Some(super::Opcode::JumpBackward), Some(super::Opcode::LoadAttr), @@ -223,14 +229,15 @@ impl super::Opcode { Some(super::Opcode::LoadAttr), Some(super::Opcode::LoadAttr), Some(super::Opcode::LoadAttr), - Some(super::Opcode::LoadConst), - Some(super::Opcode::LoadConst), Some(super::Opcode::LoadGlobal), Some(super::Opcode::LoadGlobal), Some(super::Opcode::LoadSuperAttr), Some(super::Opcode::LoadSuperAttr), Some(super::Opcode::Resume), + None, + None, Some(super::Opcode::Send), + None, Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), @@ -246,15 +253,8 @@ impl super::Opcode { Some(super::Opcode::UnpackSequence), Some(super::Opcode::UnpackSequence), None, - None, - None, - None, - None, - None, - None, - None, - None, - None, + Some(super::Opcode::LoadConst), + Some(super::Opcode::LoadConst), None, None, None, @@ -327,6 +327,7 @@ impl super::Opcode { | Self::ExtendedArg | Self::ForIter | Self::GetAwaitable + | Self::GetIter | Self::ImportFrom | Self::ImportName | Self::IsOp @@ -384,7 +385,6 @@ impl super::Opcode { | Self::CallBuiltinFast | Self::CallBuiltinFastWithKeywords | Self::CallBuiltinO - | Self::CallIsinstance | Self::CallKwBoundMethod | Self::CallKwNonPy | Self::CallKwPy @@ -423,8 +423,6 @@ impl super::Opcode { | Self::LoadAttrProperty | Self::LoadAttrSlot | Self::LoadAttrWithHint - | Self::LoadConstImmortal - | Self::LoadConstMortal | Self::LoadGlobalBuiltin | Self::LoadGlobalModule | Self::LoadSuperAttrAttr @@ -434,6 +432,8 @@ impl super::Opcode { | Self::UnpackSequenceList | Self::UnpackSequenceTuple | Self::UnpackSequenceTwoTuple + | Self::LoadConstImmortal + | Self::LoadConstMortal | Self::InstrumentedForIter | Self::InstrumentedJumpForward | Self::InstrumentedPopJumpIfTrue @@ -609,23 +609,21 @@ impl super::Opcode { Self::Cache => (0, 0), Self::BinarySlice => (1, 3), Self::BuildTemplate => (1, 2), - Self::BinaryOpInplaceAddUnicode => (0, 2), + Self::BinaryOpInplaceAddUnicode => (1, 2), Self::CallFunctionEx => (1, 4), Self::CheckEgMatch => (2, 2), Self::CheckExcMatch => (2, 2), - Self::CleanupThrow => (2, 3), + Self::CleanupThrow => (3, 4), Self::DeleteSubscr => (0, 2), Self::EndFor => (0, 1), - Self::EndSend => (1, 2), + Self::EndSend => (1, 3), Self::ExitInitCheck => (0, 1), Self::FormatSimple => (1, 1), Self::FormatWithSpec => (1, 2), Self::GetAiter => (1, 1), Self::GetAnext => (2, 1), - Self::GetIter => (1, 1), - Self::Reserved => (0, 0), Self::GetLen => (2, 1), - Self::GetYieldFromIter => (1, 1), + Self::Reserved => (0, 0), Self::InterpreterExit => (0, 1), Self::LoadBuildClass => (1, 0), Self::LoadLocals => (1, 0), @@ -636,7 +634,7 @@ impl super::Opcode { Self::Nop => (0, 0), Self::NotTaken => (0, 0), Self::PopExcept => (0, 1), - Self::PopIter => (0, 1), + Self::PopIter => (0, 2), Self::PopTop => (0, 1), Self::PushExcInfo => (2, 1), Self::PushNull => (1, 0), @@ -676,8 +674,9 @@ impl super::Opcode { Self::DictUpdate => (1 + (oparg - 1), 2 + (oparg - 1)), Self::EndAsyncFor => (0, 2), Self::ExtendedArg => (0, 0), - Self::ForIter => (2, 1), + Self::ForIter => (3, 2), Self::GetAwaitable => (1, 1), + Self::GetIter => (2, 1), Self::ImportFrom => (2, 1), Self::ImportName => (1, 2), Self::IsOp => (1, 2), @@ -712,7 +711,7 @@ impl super::Opcode { Self::PopJumpIfTrue => (0, 1), Self::RaiseVarargs => (0, oparg), Self::Reraise => (oparg, 1 + oparg), - Self::Send => (2, 2), + Self::Send => (3, 3), Self::SetAdd => (1 + (oparg - 1), 2 + (oparg - 1)), Self::SetFunctionAttribute => (1, 2), Self::SetUpdate => (1 + (oparg - 1), 2 + (oparg - 1)), @@ -749,12 +748,12 @@ impl super::Opcode { Self::CallBuiltinFast => (1, 2 + oparg), Self::CallBuiltinFastWithKeywords => (1, 2 + oparg), Self::CallBuiltinO => (1, 2 + oparg), - Self::CallIsinstance => (1, 2 + oparg), + Self::CallIsinstance => (1, 4), Self::CallKwBoundMethod => (0, 3 + oparg), Self::CallKwNonPy => (1, 3 + oparg), Self::CallKwPy => (0, 3 + oparg), Self::CallLen => (1, 3), - Self::CallListAppend => (0, 3), + Self::CallListAppend => (1, 3), Self::CallMethodDescriptorFast => (1, 2 + oparg), Self::CallMethodDescriptorFastWithKeywords => (1, 2 + oparg), Self::CallMethodDescriptorNoargs => (1, 2 + oparg), @@ -770,15 +769,15 @@ impl super::Opcode { Self::CompareOpStr => (1, 2), Self::ContainsOpDict => (1, 2), Self::ContainsOpSet => (1, 2), - Self::ForIterGen => (1, 1), - Self::ForIterList => (2, 1), - Self::ForIterRange => (2, 1), - Self::ForIterTuple => (2, 1), + Self::ForIterGen => (2, 2), + Self::ForIterList => (3, 2), + Self::ForIterRange => (3, 2), + Self::ForIterTuple => (3, 2), Self::JumpBackwardJit => (0, 0), Self::JumpBackwardNoJit => (0, 0), Self::LoadAttrClass => (1 + (oparg & 1), 1), Self::LoadAttrClassWithMetaclassCheck => (1 + (oparg & 1), 1), - Self::LoadAttrGetattributeOverridden => (1, 1), + Self::LoadAttrGetattributeOverridden => (0, 1), Self::LoadAttrInstanceValue => (1 + (oparg & 1), 1), Self::LoadAttrMethodLazyDict => (2, 1), Self::LoadAttrMethodNoDict => (2, 1), @@ -789,14 +788,12 @@ impl super::Opcode { Self::LoadAttrProperty => (0, 1), Self::LoadAttrSlot => (1 + (oparg & 1), 1), Self::LoadAttrWithHint => (1 + (oparg & 1), 1), - Self::LoadConstImmortal => (1, 0), - Self::LoadConstMortal => (1, 0), Self::LoadGlobalBuiltin => (1 + (oparg & 1), 0), Self::LoadGlobalModule => (1 + (oparg & 1), 0), Self::LoadSuperAttrAttr => (1, 3), Self::LoadSuperAttrMethod => (2, 3), Self::ResumeCheck => (0, 0), - Self::SendGen => (1, 2), + Self::SendGen => (2, 3), Self::StoreAttrInstanceValue => (0, 2), Self::StoreAttrSlot => (0, 2), Self::StoreAttrWithHint => (0, 2), @@ -811,10 +808,16 @@ impl super::Opcode { Self::UnpackSequenceList => (oparg, 1), Self::UnpackSequenceTuple => (oparg, 1), Self::UnpackSequenceTwoTuple => (2, 1), - Self::InstrumentedEndFor => (1, 2), - Self::InstrumentedPopIter => (0, 1), - Self::InstrumentedEndSend => (1, 2), - Self::InstrumentedForIter => (2, 1), + Self::GetYieldFromIter => ( + 1, // TODO: Differs from CPython `None` + 1, // TODO: Differs from CPython `None` + ), + Self::LoadConstImmortal => (1, 0), + Self::LoadConstMortal => (1, 0), + Self::InstrumentedEndFor => (2, 3), + Self::InstrumentedPopIter => (0, 2), + Self::InstrumentedEndSend => (1, 3), + Self::InstrumentedForIter => (3, 2), Self::InstrumentedInstruction => (0, 0), Self::InstrumentedJumpForward => (0, 0), Self::InstrumentedNotTaken => (0, 0), diff --git a/crates/compiler-core/src/bytecode/public_metadata.rs b/crates/compiler-core/src/bytecode/public_metadata.rs new file mode 100644 index 00000000000..f2bbb02b98b --- /dev/null +++ b/crates/compiler-core/src/bytecode/public_metadata.rs @@ -0,0 +1,496 @@ +// Generated by tools/opcode_metadata/generate_public_metadata.py from pinned final CPython. +//! Public introspection metadata, independent of the VM's executable opcode set. +//! Describing an opcode does not enable execution of its CPython specialization. +const DESCRIPTORS: [u32; 267] = [ + 2147483648, // 0: CACHE + 2147484416, // 1: BINARY_SLICE + 2147484416, // 2: BUILD_TEMPLATE + 3221227296, // 3: BINARY_OP_INPLACE_ADD_UNICODE + 2147627840, // 4: CALL_FUNCTION_EX + 2147484416, // 5: CHECK_EG_MATCH + 2147492608, // 6: CHECK_EXC_MATCH + 2147492608, // 7: CLEANUP_THROW + 2147484416, // 8: DELETE_SUBSCR + 2147500544, // 9: END_FOR + 2147486208, // 10: END_SEND + 2147492608, // 11: EXIT_INIT_CHECK + 2147484416, // 12: FORMAT_SIMPLE + 2147484416, // 13: FORMAT_WITH_SPEC + 2147484416, // 14: GET_AITER + 2147492608, // 15: GET_ANEXT + 2147484416, // 16: GET_LEN + 2147483648, // 17: RESERVED + 2147484160, // 18: INTERPRETER_EXIT + 2147484416, // 19: LOAD_BUILD_CLASS + 2147483904, // 20: LOAD_LOCALS + 2147492608, // 21: MAKE_FUNCTION + 2147484416, // 22: MATCH_KEYS + 2147483648, // 23: MATCH_MAPPING + 2147483648, // 24: MATCH_SEQUENCE + 2147485696, // 25: NOP + 2147485696, // 26: NOT_TAKEN + 2147484160, // 27: POP_EXCEPT + 2147484160, // 28: POP_ITER + 2147486208, // 29: POP_TOP + 2147483648, // 30: PUSH_EXC_INFO + 2147485696, // 31: PUSH_NULL + 2147615488, // 32: RETURN_GENERATOR + 2147615232, // 33: RETURN_VALUE + 2147484416, // 34: SETUP_ANNOTATIONS + 2147484416, // 35: STORE_SLICE + 2147484416, // 36: STORE_SUBSCR + 2147484416, // 37: TO_BOOL + 2147492608, // 38: UNARY_INVERT + 2147492608, // 39: UNARY_NEGATIVE + 2147483648, // 40: UNARY_NOT + 2147484416, // 41: WITH_EXCEPT_START + 2147754753, // 42: BINARY_OP + 2147484417, // 43: BUILD_INTERPOLATION + 2147492609, // 44: BUILD_LIST + 2147484417, // 45: BUILD_MAP + 2147484417, // 46: BUILD_SET + 2147484417, // 47: BUILD_SLICE + 2147484417, // 48: BUILD_STRING + 2147492097, // 49: BUILD_TUPLE + 2147627841, // 50: CALL + 2147492609, // 51: CALL_INTRINSIC_1 + 2147492609, // 52: CALL_INTRINSIC_2 + 2147627777, // 53: CALL_KW + 2147484417, // 54: COMPARE_OP + 2147492609, // 55: CONTAINS_OP + 2147484417, // 56: CONVERT_VALUE + 2147485697, // 57: COPY + 2147483649, // 58: COPY_FREE_VARS + 2147484421, // 59: DELETE_ATTR + 2147492625, // 60: DELETE_DEREF + 2147484449, // 61: DELETE_FAST + 2147492613, // 62: DELETE_GLOBAL + 2147492613, // 63: DELETE_NAME + 2147492609, // 64: DICT_MERGE + 2147492609, // 65: DICT_UPDATE + 2147689225, // 66: END_ASYNC_FOR + 2147483649, // 67: EXTENDED_ARG + 2147820297, // 68: FOR_ITER + 2147484417, // 69: GET_AWAITABLE + 2147746561, // 70: GET_ITER + 2147484421, // 71: IMPORT_FROM + 2147484421, // 72: IMPORT_NAME + 2147484161, // 73: IS_OP + 2147484489, // 74: JUMP_BACKWARD + 2147483657, // 75: JUMP_BACKWARD_NO_INTERRUPT + 2147483657, // 76: JUMP_FORWARD + 2147483905, // 77: LIST_APPEND + 2147492609, // 78: LIST_EXTEND + 2147484421, // 79: LOAD_ATTR + 2147483649, // 80: LOAD_COMMON_CONSTANT + 2147483651, // 81: LOAD_CONST + 2147484449, // 82: LOAD_DEREF + 2147485729, // 83: LOAD_FAST + 2147483681, // 84: LOAD_FAST_AND_CLEAR + 2147485729, // 85: LOAD_FAST_BORROW + 2147483681, // 86: LOAD_FAST_BORROW_LOAD_FAST_BORROW + 2147483937, // 87: LOAD_FAST_CHECK + 2147483681, // 88: LOAD_FAST_LOAD_FAST + 2147492625, // 89: LOAD_FROM_DICT_OR_DEREF + 2147484421, // 90: LOAD_FROM_DICT_OR_GLOBALS + 2147484421, // 91: LOAD_GLOBAL + 2147484421, // 92: LOAD_NAME + 2147483649, // 93: LOAD_SMALL_INT + 2147754753, // 94: LOAD_SPECIAL + 2147484421, // 95: LOAD_SUPER_ATTR + 2147492625, // 96: MAKE_CELL + 2147484417, // 97: MAP_ADD + 2147492609, // 98: MATCH_CLASS + 2147483657, // 99: POP_JUMP_IF_FALSE + 2147484169, // 100: POP_JUMP_IF_NONE + 2147484169, // 101: POP_JUMP_IF_NOT_NONE + 2147483657, // 102: POP_JUMP_IF_TRUE + 2147492097, // 103: RAISE_VARARGS + 2147492609, // 104: RERAISE + 2147627785, // 105: SEND + 2147484417, // 106: SET_ADD + 2147483649, // 107: SET_FUNCTION_ATTRIBUTE + 2147492609, // 108: SET_UPDATE + 2147484421, // 109: STORE_ATTR + 2147484177, // 110: STORE_DEREF + 2147484193, // 111: STORE_FAST + 2147484193, // 112: STORE_FAST_LOAD_FAST + 2147484193, // 113: STORE_FAST_STORE_FAST + 2147484421, // 114: STORE_GLOBAL + 2147484421, // 115: STORE_NAME + 2147485697, // 116: SWAP + 2147484417, // 117: UNPACK_EX + 2147484417, // 118: UNPACK_SEQUENCE + 2147615233, // 119: YIELD_VALUE + 0, 0, 0, 0, 0, 0, 0, 0, 2147492673, // 128: RESUME + 3221234944, // 129: BINARY_OP_ADD_FLOAT + 3221226496, // 130: BINARY_OP_ADD_INT + 3221234944, // 131: BINARY_OP_ADD_UNICODE + 3221235456, // 132: BINARY_OP_EXTEND + 3221234944, // 133: BINARY_OP_MULTIPLY_FLOAT + 3221226496, // 134: BINARY_OP_MULTIPLY_INT + 3221496704, // 135: BINARY_OP_SUBSCR_DICT + 3221623936, // 136: BINARY_OP_SUBSCR_GETITEM + 3221227008, // 137: BINARY_OP_SUBSCR_LIST_INT + 3221235456, // 138: BINARY_OP_SUBSCR_LIST_SLICE + 3221226496, // 139: BINARY_OP_SUBSCR_STR_INT + 3221227008, // 140: BINARY_OP_SUBSCR_TUPLE_INT + 3221226496, // 141: BINARY_OP_SUBSCR_USTR_INT + 3221234944, // 142: BINARY_OP_SUBTRACT_FLOAT + 3221226496, // 143: BINARY_OP_SUBTRACT_INT + 3221632897, // 144: CALL_ALLOC_AND_ENTER_INIT + 3221624449, // 145: CALL_BOUND_METHOD_EXACT_ARGS + 3221632897, // 146: CALL_BOUND_METHOD_GENERAL + 3221497665, // 147: CALL_BUILTIN_CLASS + 3221497665, // 148: CALL_BUILTIN_FAST + 3221497665, // 149: CALL_BUILTIN_FAST_WITH_KEYWORDS + 3221497665, // 150: CALL_BUILTIN_O + 3221235520, // 151: CALL_EX_NON_PY_GENERAL + 3221632897, // 152: CALL_EX_PY + 3221235456, // 153: CALL_ISINSTANCE + 3221624705, // 154: CALL_KW_BOUND_METHOD + 3221227329, // 155: CALL_KW_NON_PY + 3221624705, // 156: CALL_KW_PY + 3221235456, // 157: CALL_LEN + 3221235585, // 158: CALL_LIST_APPEND + 3221235521, // 159: CALL_METHOD_DESCRIPTOR_FAST + 3221497665, // 160: CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS + 3221497665, // 161: CALL_METHOD_DESCRIPTOR_NOARGS + 3221497665, // 162: CALL_METHOD_DESCRIPTOR_O + 3221489473, // 163: CALL_NON_PY_GENERAL + 3221623937, // 164: CALL_PY_EXACT_ARGS + 3221632897, // 165: CALL_PY_GENERAL + 3221235521, // 166: CALL_STR_1 + 3221235521, // 167: CALL_TUPLE_1 + 3221227009, // 168: CALL_TYPE_1 + 3221226497, // 169: COMPARE_OP_FLOAT + 3221226497, // 170: COMPARE_OP_INT + 3221226497, // 171: COMPARE_OP_STR + 3221235457, // 172: CONTAINS_OP_DICT + 3221235457, // 173: CONTAINS_OP_SET + 3221623937, // 174: FOR_ITER_GEN + 3221292681, // 175: FOR_ITER_LIST + 3221292297, // 176: FOR_ITER_RANGE + 3221292041, // 177: FOR_ITER_TUPLE + 3221301001, // 178: FOR_ITER_VIRTUAL + 3221488640, // 179: GET_ITER_SELF + 3221488640, // 180: GET_ITER_VIRTUAL + 3221226313, // 181: JUMP_BACKWARD_JIT + 3221226313, // 182: JUMP_BACKWARD_NO_JIT + 3221489153, // 183: LOAD_ATTR_CLASS + 3221489153, // 184: LOAD_ATTR_CLASS_WITH_METACLASS_CHECK + 3221623941, // 185: LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN + 3221489281, // 186: LOAD_ATTR_INSTANCE_VALUE + 3221488641, // 187: LOAD_ATTR_METHOD_LAZY_DICT + 3221488641, // 188: LOAD_ATTR_METHOD_NO_DICT + 3221488641, // 189: LOAD_ATTR_METHOD_WITH_VALUES + 3221227009, // 190: LOAD_ATTR_MODULE + 3221489153, // 191: LOAD_ATTR_NONDESCRIPTOR_NO_DICT + 3221489153, // 192: LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES + 3221623937, // 193: LOAD_ATTR_PROPERTY + 3221489153, // 194: LOAD_ATTR_SLOT + 3221489157, // 195: LOAD_ATTR_WITH_HINT + 3221225601, // 196: LOAD_GLOBAL_BUILTIN + 3221225601, // 197: LOAD_GLOBAL_MODULE + 3221227269, // 198: LOAD_SUPER_ATTR_ATTR + 3221497605, // 199: LOAD_SUPER_ATTR_METHOD + 3221225600, // 200: RESUME_CHECK + 3221225601, // 201: RESUME_CHECK_JIT + 3221301001, // 202: SEND_ASYNC_GEN + 3221623937, // 203: SEND_GEN + 3221301001, // 204: SEND_VIRTUAL + 3221489280, // 205: STORE_ATTR_INSTANCE_VALUE + 3221489280, // 206: STORE_ATTR_SLOT + 3221489285, // 207: STORE_ATTR_WITH_HINT + 3221488512, // 208: STORE_SUBSCR_DICT + 3221227136, // 209: STORE_SUBSCR_LIST_INT + 3221489152, // 210: TO_BOOL_ALWAYS_TRUE + 3221226496, // 211: TO_BOOL_BOOL + 3221226496, // 212: TO_BOOL_INT + 3221227008, // 213: TO_BOOL_LIST + 3221226496, // 214: TO_BOOL_NONE + 3221226496, // 215: TO_BOOL_STR + 3221227137, // 216: UNPACK_SEQUENCE_LIST + 3221227009, // 217: UNPACK_SEQUENCE_TUPLE + 3221227009, // 218: UNPACK_SEQUENCE_TWO_TUPLE + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2147508992, // 233: INSTRUMENTED_END_FOR + 2147484160, // 234: INSTRUMENTED_POP_ITER + 2147492608, // 235: INSTRUMENTED_END_SEND + 2147689225, // 236: INSTRUMENTED_FOR_ITER + 2147484416, // 237: INSTRUMENTED_INSTRUCTION + 2147483649, // 238: INSTRUMENTED_JUMP_FORWARD + 2147483648, // 239: INSTRUMENTED_NOT_TAKEN + 2147483649, // 240: INSTRUMENTED_POP_JUMP_IF_TRUE + 2147483649, // 241: INSTRUMENTED_POP_JUMP_IF_FALSE + 2147484161, // 242: INSTRUMENTED_POP_JUMP_IF_NONE + 2147484161, // 243: INSTRUMENTED_POP_JUMP_IF_NOT_NONE + 2147492673, // 244: INSTRUMENTED_RESUME + 2147615488, // 245: INSTRUMENTED_RETURN_VALUE + 2147623681, // 246: INSTRUMENTED_YIELD_VALUE + 2147689225, // 247: INSTRUMENTED_END_ASYNC_FOR + 2147484421, // 248: INSTRUMENTED_LOAD_SUPER_ATTR + 2147627841, // 249: INSTRUMENTED_CALL + 2147627777, // 250: INSTRUMENTED_CALL_KW + 2147627840, // 251: INSTRUMENTED_CALL_FUNCTION_EX + 2147484481, // 252: INSTRUMENTED_JUMP_BACKWARD + 2147488256, // 253: INSTRUMENTED_LINE + 2147483649, // 254: ENTER_EXECUTOR + 2147484417, // 255: TRACE_RECORD + 2147485696, // 256: ANNOTATIONS_PLACEHOLDER + 2147484489, // 257: JUMP + 2147484425, // 258: JUMP_IF_FALSE + 2147484425, // 259: JUMP_IF_TRUE + 2147483657, // 260: JUMP_NO_INTERRUPT + 2147485729, // 261: LOAD_CLOSURE + 2147485696, // 262: POP_BLOCK + 2684356609, // 263: SETUP_CLEANUP + 2684356609, // 264: SETUP_FINALLY + 2684356609, // 265: SETUP_WITH + 2147484193, // 266: STORE_FAST_MAYBE_NULL +]; +#[derive(Clone, Copy)] +pub struct PublicOpcode { + opcode: u16, + flags: u32, +} +impl PublicOpcode { + #[must_use] + pub fn from_i32(opcode: i32) -> Option { + let index = usize::try_from(opcode).ok()?; + let flags = *DESCRIPTORS.get(index)?; + (flags & (1 << 31) != 0).then_some(Self { + opcode: u16::try_from(index).ok()?, + flags, + }) + } + #[must_use] + pub const fn has_arg(self) -> bool { + self.flags & 1 != 0 + } + #[must_use] + pub const fn has_const(self) -> bool { + self.flags & 2 != 0 + } + #[must_use] + pub const fn has_name(self) -> bool { + self.flags & 4 != 0 + } + #[must_use] + pub const fn has_jump(self) -> bool { + self.flags & 8 != 0 + } + #[must_use] + pub const fn has_free(self) -> bool { + self.flags & 16 != 0 + } + #[must_use] + pub const fn has_local(self) -> bool { + self.flags & 32 != 0 + } + #[must_use] + pub const fn is_block_push(self) -> bool { + self.flags & (1 << 29) != 0 + } + /// Existing caller conversion is intentionally separate from this metadata domain. + #[must_use] + pub fn stack_effect(self, oparg: u32, jump: Option) -> Option { + if self.flags & (1 << 30) != 0 { + return None; + } + let oparg = oparg.cast_signed(); + let (popped, pushed) = match self.opcode { + 0 => (0i32, 0i32), // CACHE + 1 => (3i32, 1i32), // BINARY_SLICE + 2 => (2i32, 1i32), // BUILD_TEMPLATE + 4 => (4i32, 1i32), // CALL_FUNCTION_EX + 5 => (2i32, 2i32), // CHECK_EG_MATCH + 6 => (2i32, 2i32), // CHECK_EXC_MATCH + 7 => (4i32, 3i32), // CLEANUP_THROW + 8 => (2i32, 0i32), // DELETE_SUBSCR + 9 => (1i32, 0i32), // END_FOR + 10 => (3i32, 1i32), // END_SEND + 11 => (1i32, 0i32), // EXIT_INIT_CHECK + 12 => (1i32, 1i32), // FORMAT_SIMPLE + 13 => (2i32, 1i32), // FORMAT_WITH_SPEC + 14 => (1i32, 1i32), // GET_AITER + 15 => (1i32, 2i32), // GET_ANEXT + 16 => (1i32, 2i32), // GET_LEN + 17 => (0i32, 0i32), // RESERVED + 18 => (1i32, 0i32), // INTERPRETER_EXIT + 19 => (0i32, 1i32), // LOAD_BUILD_CLASS + 20 => (0i32, 1i32), // LOAD_LOCALS + 21 => (1i32, 1i32), // MAKE_FUNCTION + 22 => (2i32, 3i32), // MATCH_KEYS + 23 => (1i32, 2i32), // MATCH_MAPPING + 24 => (1i32, 2i32), // MATCH_SEQUENCE + 25 => (0i32, 0i32), // NOP + 26 => (0i32, 0i32), // NOT_TAKEN + 27 => (1i32, 0i32), // POP_EXCEPT + 28 => (2i32, 0i32), // POP_ITER + 29 => (1i32, 0i32), // POP_TOP + 30 => (1i32, 2i32), // PUSH_EXC_INFO + 31 => (0i32, 1i32), // PUSH_NULL + 32 => (0i32, 1i32), // RETURN_GENERATOR + 33 => (1i32, 1i32), // RETURN_VALUE + 34 => (0i32, 0i32), // SETUP_ANNOTATIONS + 35 => (4i32, 0i32), // STORE_SLICE + 36 => (3i32, 0i32), // STORE_SUBSCR + 37 => (1i32, 1i32), // TO_BOOL + 38 => (1i32, 1i32), // UNARY_INVERT + 39 => (1i32, 1i32), // UNARY_NEGATIVE + 40 => (1i32, 1i32), // UNARY_NOT + 41 => (5i32, 6i32), // WITH_EXCEPT_START + 42 => (2i32, 1i32), // BINARY_OP + 43 => ((2i32).wrapping_add(oparg & 1i32), 1i32), // BUILD_INTERPOLATION + 44 => (oparg, 1i32), // BUILD_LIST + 45 => ((oparg).wrapping_mul(2i32), 1i32), // BUILD_MAP + 46 => (oparg, 1i32), // BUILD_SET + 47 => (oparg, 1i32), // BUILD_SLICE + 48 => (oparg, 1i32), // BUILD_STRING + 49 => (oparg, 1i32), // BUILD_TUPLE + 50 => ((2i32).wrapping_add(oparg), 1i32), // CALL + 51 => (1i32, 1i32), // CALL_INTRINSIC_1 + 52 => (2i32, 1i32), // CALL_INTRINSIC_2 + 53 => ((3i32).wrapping_add(oparg), 1i32), // CALL_KW + 54 => (2i32, 1i32), // COMPARE_OP + 55 => (2i32, 1i32), // CONTAINS_OP + 56 => (1i32, 1i32), // CONVERT_VALUE + 57 => ( + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + (2i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // COPY + 58 => (0i32, 0i32), // COPY_FREE_VARS + 59 => (1i32, 0i32), // DELETE_ATTR + 60 => (0i32, 0i32), // DELETE_DEREF + 61 => (0i32, 0i32), // DELETE_FAST + 62 => (0i32, 0i32), // DELETE_GLOBAL + 63 => (0i32, 0i32), // DELETE_NAME + 64 => ( + (5i32).wrapping_add((oparg).wrapping_sub(1i32)), + (4i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // DICT_MERGE + 65 => ( + (2i32).wrapping_add((oparg).wrapping_sub(1i32)), + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // DICT_UPDATE + 66 => (2i32, 0i32), // END_ASYNC_FOR + 67 => (0i32, 0i32), // EXTENDED_ARG + 68 => (2i32, 3i32), // FOR_ITER + 69 => (1i32, 1i32), // GET_AWAITABLE + 70 => (1i32, 2i32), // GET_ITER + 71 => (1i32, 2i32), // IMPORT_FROM + 72 => (2i32, 1i32), // IMPORT_NAME + 73 => (2i32, 1i32), // IS_OP + 74 => (0i32, 0i32), // JUMP_BACKWARD + 75 => (0i32, 0i32), // JUMP_BACKWARD_NO_INTERRUPT + 76 => (0i32, 0i32), // JUMP_FORWARD + 77 => ( + (2i32).wrapping_add((oparg).wrapping_sub(1i32)), + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // LIST_APPEND + 78 => ( + (2i32).wrapping_add((oparg).wrapping_sub(1i32)), + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // LIST_EXTEND + 79 => (1i32, (1i32).wrapping_add(oparg & 1i32)), // LOAD_ATTR + 80 => (0i32, 1i32), // LOAD_COMMON_CONSTANT + 81 => (0i32, 1i32), // LOAD_CONST + 82 => (0i32, 1i32), // LOAD_DEREF + 83 => (0i32, 1i32), // LOAD_FAST + 84 => (0i32, 1i32), // LOAD_FAST_AND_CLEAR + 85 => (0i32, 1i32), // LOAD_FAST_BORROW + 86 => (0i32, 2i32), // LOAD_FAST_BORROW_LOAD_FAST_BORROW + 87 => (0i32, 1i32), // LOAD_FAST_CHECK + 88 => (0i32, 2i32), // LOAD_FAST_LOAD_FAST + 89 => (1i32, 1i32), // LOAD_FROM_DICT_OR_DEREF + 90 => (1i32, 1i32), // LOAD_FROM_DICT_OR_GLOBALS + 91 => (0i32, (1i32).wrapping_add(oparg & 1i32)), // LOAD_GLOBAL + 92 => (0i32, 1i32), // LOAD_NAME + 93 => (0i32, 1i32), // LOAD_SMALL_INT + 94 => (1i32, 2i32), // LOAD_SPECIAL + 95 => (3i32, (1i32).wrapping_add(oparg & 1i32)), // LOAD_SUPER_ATTR + 96 => (0i32, 0i32), // MAKE_CELL + 97 => ( + (3i32).wrapping_add((oparg).wrapping_sub(1i32)), + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // MAP_ADD + 98 => (3i32, 1i32), // MATCH_CLASS + 99 => (1i32, 0i32), // POP_JUMP_IF_FALSE + 100 => (1i32, 0i32), // POP_JUMP_IF_NONE + 101 => (1i32, 0i32), // POP_JUMP_IF_NOT_NONE + 102 => (1i32, 0i32), // POP_JUMP_IF_TRUE + 103 => (oparg, 0i32), // RAISE_VARARGS + 104 => ((1i32).wrapping_add(oparg), oparg), // RERAISE + 105 => (3i32, 3i32), // SEND + 106 => ( + (2i32).wrapping_add((oparg).wrapping_sub(1i32)), + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // SET_ADD + 107 => (2i32, 1i32), // SET_FUNCTION_ATTRIBUTE + 108 => ( + (2i32).wrapping_add((oparg).wrapping_sub(1i32)), + (1i32).wrapping_add((oparg).wrapping_sub(1i32)), + ), // SET_UPDATE + 109 => (2i32, 0i32), // STORE_ATTR + 110 => (1i32, 0i32), // STORE_DEREF + 111 => (1i32, 0i32), // STORE_FAST + 112 => (1i32, 1i32), // STORE_FAST_LOAD_FAST + 113 => (2i32, 0i32), // STORE_FAST_STORE_FAST + 114 => (1i32, 0i32), // STORE_GLOBAL + 115 => (1i32, 0i32), // STORE_NAME + 116 => ( + (2i32).wrapping_add((oparg).wrapping_sub(2i32)), + (2i32).wrapping_add((oparg).wrapping_sub(2i32)), + ), // SWAP + 117 => ( + 1i32, + ((1i32).wrapping_add(oparg & 255i32)).wrapping_add(oparg >> 8), + ), // UNPACK_EX + 118 => (1i32, oparg), // UNPACK_SEQUENCE + 119 => (1i32, 1i32), // YIELD_VALUE + 128 => (0i32, 0i32), // RESUME + 233 => (3i32, 2i32), // INSTRUMENTED_END_FOR + 234 => (2i32, 0i32), // INSTRUMENTED_POP_ITER + 235 => (3i32, 1i32), // INSTRUMENTED_END_SEND + 236 => (2i32, 3i32), // INSTRUMENTED_FOR_ITER + 237 => (0i32, 0i32), // INSTRUMENTED_INSTRUCTION + 238 => (0i32, 0i32), // INSTRUMENTED_JUMP_FORWARD + 239 => (0i32, 0i32), // INSTRUMENTED_NOT_TAKEN + 240 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_TRUE + 241 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_FALSE + 242 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_NONE + 243 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_NOT_NONE + 244 => (0i32, 0i32), // INSTRUMENTED_RESUME + 245 => (1i32, 1i32), // INSTRUMENTED_RETURN_VALUE + 246 => (1i32, 1i32), // INSTRUMENTED_YIELD_VALUE + 247 => (2i32, 0i32), // INSTRUMENTED_END_ASYNC_FOR + 248 => (3i32, (1i32).wrapping_add(oparg & 1i32)), // INSTRUMENTED_LOAD_SUPER_ATTR + 249 => ((2i32).wrapping_add(oparg), 1i32), // INSTRUMENTED_CALL + 250 => ((3i32).wrapping_add(oparg), 1i32), // INSTRUMENTED_CALL_KW + 251 => (4i32, 1i32), // INSTRUMENTED_CALL_FUNCTION_EX + 252 => (0i32, 0i32), // INSTRUMENTED_JUMP_BACKWARD + 253 => (0i32, 0i32), // INSTRUMENTED_LINE + 254 => (0i32, 0i32), // ENTER_EXECUTOR + 255 => (0i32, 0i32), // TRACE_RECORD + 256 => (0i32, 0i32), // ANNOTATIONS_PLACEHOLDER + 257 => (0i32, 0i32), // JUMP + 258 => (1i32, 1i32), // JUMP_IF_FALSE + 259 => (1i32, 1i32), // JUMP_IF_TRUE + 260 => (0i32, 0i32), // JUMP_NO_INTERRUPT + 261 => (0i32, 1i32), // LOAD_CLOSURE + 262 => (0i32, 0i32), // POP_BLOCK + 263 => (0i32, 2i32), // SETUP_CLEANUP + 264 => (0i32, 1i32), // SETUP_FINALLY + 265 => (0i32, 1i32), // SETUP_WITH + 266 => (1i32, 0i32), // STORE_FAST_MAYBE_NULL + _ => return None, + }; + if popped < 0 || pushed < 0 { + return None; + } + if self.is_block_push() && jump == Some(false) { + return Some(0); + } + Some(pushed.wrapping_sub(popped)) + } +} diff --git a/crates/compiler-core/src/bytecode/target_stack_cases.rs b/crates/compiler-core/src/bytecode/target_stack_cases.rs new file mode 100644 index 00000000000..64ccb5424a3 --- /dev/null +++ b/crates/compiler-core/src/bytecode/target_stack_cases.rs @@ -0,0 +1,3433 @@ +// Generated by tools/opcode_metadata/verify_stack_contract.py from pinned CPython C metadata. +const TARGET_STACK_CASES: &[(u16, u32, u32, u32)] = &[ + (0, 0, 0, 0), + (0, 1, 0, 0), + (0, 2, 0, 0), + (0, 3, 0, 0), + (0, 4, 0, 0), + (0, 5, 0, 0), + (0, 7, 0, 0), + (0, 8, 0, 0), + (0, 15, 0, 0), + (0, 16, 0, 0), + (0, 31, 0, 0), + (0, 255, 0, 0), + (0, 256, 0, 0), + (0, 65535, 0, 0), + (1, 0, 3, 1), + (1, 1, 3, 1), + (1, 2, 3, 1), + (1, 3, 3, 1), + (1, 4, 3, 1), + (1, 5, 3, 1), + (1, 7, 3, 1), + (1, 8, 3, 1), + (1, 15, 3, 1), + (1, 16, 3, 1), + (1, 31, 3, 1), + (1, 255, 3, 1), + (1, 256, 3, 1), + (1, 65535, 3, 1), + (2, 0, 2, 1), + (2, 1, 2, 1), + (2, 2, 2, 1), + (2, 3, 2, 1), + (2, 4, 2, 1), + (2, 5, 2, 1), + (2, 7, 2, 1), + (2, 8, 2, 1), + (2, 15, 2, 1), + (2, 16, 2, 1), + (2, 31, 2, 1), + (2, 255, 2, 1), + (2, 256, 2, 1), + (2, 65535, 2, 1), + (3, 0, 2, 1), + (3, 1, 2, 1), + (3, 2, 2, 1), + (3, 3, 2, 1), + (3, 4, 2, 1), + (3, 5, 2, 1), + (3, 7, 2, 1), + (3, 8, 2, 1), + (3, 15, 2, 1), + (3, 16, 2, 1), + (3, 31, 2, 1), + (3, 255, 2, 1), + (3, 256, 2, 1), + (3, 65535, 2, 1), + (4, 0, 4, 1), + (4, 1, 4, 1), + (4, 2, 4, 1), + (4, 3, 4, 1), + (4, 4, 4, 1), + (4, 5, 4, 1), + (4, 7, 4, 1), + (4, 8, 4, 1), + (4, 15, 4, 1), + (4, 16, 4, 1), + (4, 31, 4, 1), + (4, 255, 4, 1), + (4, 256, 4, 1), + (4, 65535, 4, 1), + (5, 0, 2, 2), + (5, 1, 2, 2), + (5, 2, 2, 2), + (5, 3, 2, 2), + (5, 4, 2, 2), + (5, 5, 2, 2), + (5, 7, 2, 2), + (5, 8, 2, 2), + (5, 15, 2, 2), + (5, 16, 2, 2), + (5, 31, 2, 2), + (5, 255, 2, 2), + (5, 256, 2, 2), + (5, 65535, 2, 2), + (6, 0, 2, 2), + (6, 1, 2, 2), + (6, 2, 2, 2), + (6, 3, 2, 2), + (6, 4, 2, 2), + (6, 5, 2, 2), + (6, 7, 2, 2), + (6, 8, 2, 2), + (6, 15, 2, 2), + (6, 16, 2, 2), + (6, 31, 2, 2), + (6, 255, 2, 2), + (6, 256, 2, 2), + (6, 65535, 2, 2), + (7, 0, 4, 3), + (7, 1, 4, 3), + (7, 2, 4, 3), + (7, 3, 4, 3), + (7, 4, 4, 3), + (7, 5, 4, 3), + (7, 7, 4, 3), + (7, 8, 4, 3), + (7, 15, 4, 3), + (7, 16, 4, 3), + (7, 31, 4, 3), + (7, 255, 4, 3), + (7, 256, 4, 3), + (7, 65535, 4, 3), + (8, 0, 2, 0), + (8, 1, 2, 0), + (8, 2, 2, 0), + (8, 3, 2, 0), + (8, 4, 2, 0), + (8, 5, 2, 0), + (8, 7, 2, 0), + (8, 8, 2, 0), + (8, 15, 2, 0), + (8, 16, 2, 0), + (8, 31, 2, 0), + (8, 255, 2, 0), + (8, 256, 2, 0), + (8, 65535, 2, 0), + (9, 0, 1, 0), + (9, 1, 1, 0), + (9, 2, 1, 0), + (9, 3, 1, 0), + (9, 4, 1, 0), + (9, 5, 1, 0), + (9, 7, 1, 0), + (9, 8, 1, 0), + (9, 15, 1, 0), + (9, 16, 1, 0), + (9, 31, 1, 0), + (9, 255, 1, 0), + (9, 256, 1, 0), + (9, 65535, 1, 0), + (10, 0, 3, 1), + (10, 1, 3, 1), + (10, 2, 3, 1), + (10, 3, 3, 1), + (10, 4, 3, 1), + (10, 5, 3, 1), + (10, 7, 3, 1), + (10, 8, 3, 1), + (10, 15, 3, 1), + (10, 16, 3, 1), + (10, 31, 3, 1), + (10, 255, 3, 1), + (10, 256, 3, 1), + (10, 65535, 3, 1), + (11, 0, 1, 0), + (11, 1, 1, 0), + (11, 2, 1, 0), + (11, 3, 1, 0), + (11, 4, 1, 0), + (11, 5, 1, 0), + (11, 7, 1, 0), + (11, 8, 1, 0), + (11, 15, 1, 0), + (11, 16, 1, 0), + (11, 31, 1, 0), + (11, 255, 1, 0), + (11, 256, 1, 0), + (11, 65535, 1, 0), + (12, 0, 1, 1), + (12, 1, 1, 1), + (12, 2, 1, 1), + (12, 3, 1, 1), + (12, 4, 1, 1), + (12, 5, 1, 1), + (12, 7, 1, 1), + (12, 8, 1, 1), + (12, 15, 1, 1), + (12, 16, 1, 1), + (12, 31, 1, 1), + (12, 255, 1, 1), + (12, 256, 1, 1), + (12, 65535, 1, 1), + (13, 0, 2, 1), + (13, 1, 2, 1), + (13, 2, 2, 1), + (13, 3, 2, 1), + (13, 4, 2, 1), + (13, 5, 2, 1), + (13, 7, 2, 1), + (13, 8, 2, 1), + (13, 15, 2, 1), + (13, 16, 2, 1), + (13, 31, 2, 1), + (13, 255, 2, 1), + (13, 256, 2, 1), + (13, 65535, 2, 1), + (14, 0, 1, 1), + (14, 1, 1, 1), + (14, 2, 1, 1), + (14, 3, 1, 1), + (14, 4, 1, 1), + (14, 5, 1, 1), + (14, 7, 1, 1), + (14, 8, 1, 1), + (14, 15, 1, 1), + (14, 16, 1, 1), + (14, 31, 1, 1), + (14, 255, 1, 1), + (14, 256, 1, 1), + (14, 65535, 1, 1), + (15, 0, 1, 2), + (15, 1, 1, 2), + (15, 2, 1, 2), + (15, 3, 1, 2), + (15, 4, 1, 2), + (15, 5, 1, 2), + (15, 7, 1, 2), + (15, 8, 1, 2), + (15, 15, 1, 2), + (15, 16, 1, 2), + (15, 31, 1, 2), + (15, 255, 1, 2), + (15, 256, 1, 2), + (15, 65535, 1, 2), + (16, 0, 1, 2), + (16, 1, 1, 2), + (16, 2, 1, 2), + (16, 3, 1, 2), + (16, 4, 1, 2), + (16, 5, 1, 2), + (16, 7, 1, 2), + (16, 8, 1, 2), + (16, 15, 1, 2), + (16, 16, 1, 2), + (16, 31, 1, 2), + (16, 255, 1, 2), + (16, 256, 1, 2), + (16, 65535, 1, 2), + (17, 0, 0, 0), + (17, 1, 0, 0), + (17, 2, 0, 0), + (17, 3, 0, 0), + (17, 4, 0, 0), + (17, 5, 0, 0), + (17, 7, 0, 0), + (17, 8, 0, 0), + (17, 15, 0, 0), + (17, 16, 0, 0), + (17, 31, 0, 0), + (17, 255, 0, 0), + (17, 256, 0, 0), + (17, 65535, 0, 0), + (18, 0, 1, 0), + (18, 1, 1, 0), + (18, 2, 1, 0), + (18, 3, 1, 0), + (18, 4, 1, 0), + (18, 5, 1, 0), + (18, 7, 1, 0), + (18, 8, 1, 0), + (18, 15, 1, 0), + (18, 16, 1, 0), + (18, 31, 1, 0), + (18, 255, 1, 0), + (18, 256, 1, 0), + (18, 65535, 1, 0), + (19, 0, 0, 1), + (19, 1, 0, 1), + (19, 2, 0, 1), + (19, 3, 0, 1), + (19, 4, 0, 1), + (19, 5, 0, 1), + (19, 7, 0, 1), + (19, 8, 0, 1), + (19, 15, 0, 1), + (19, 16, 0, 1), + (19, 31, 0, 1), + (19, 255, 0, 1), + (19, 256, 0, 1), + (19, 65535, 0, 1), + (20, 0, 0, 1), + (20, 1, 0, 1), + (20, 2, 0, 1), + (20, 3, 0, 1), + (20, 4, 0, 1), + (20, 5, 0, 1), + (20, 7, 0, 1), + (20, 8, 0, 1), + (20, 15, 0, 1), + (20, 16, 0, 1), + (20, 31, 0, 1), + (20, 255, 0, 1), + (20, 256, 0, 1), + (20, 65535, 0, 1), + (21, 0, 1, 1), + (21, 1, 1, 1), + (21, 2, 1, 1), + (21, 3, 1, 1), + (21, 4, 1, 1), + (21, 5, 1, 1), + (21, 7, 1, 1), + (21, 8, 1, 1), + (21, 15, 1, 1), + (21, 16, 1, 1), + (21, 31, 1, 1), + (21, 255, 1, 1), + (21, 256, 1, 1), + (21, 65535, 1, 1), + (22, 0, 2, 3), + (22, 1, 2, 3), + (22, 2, 2, 3), + (22, 3, 2, 3), + (22, 4, 2, 3), + (22, 5, 2, 3), + (22, 7, 2, 3), + (22, 8, 2, 3), + (22, 15, 2, 3), + (22, 16, 2, 3), + (22, 31, 2, 3), + (22, 255, 2, 3), + (22, 256, 2, 3), + (22, 65535, 2, 3), + (23, 0, 1, 2), + (23, 1, 1, 2), + (23, 2, 1, 2), + (23, 3, 1, 2), + (23, 4, 1, 2), + (23, 5, 1, 2), + (23, 7, 1, 2), + (23, 8, 1, 2), + (23, 15, 1, 2), + (23, 16, 1, 2), + (23, 31, 1, 2), + (23, 255, 1, 2), + (23, 256, 1, 2), + (23, 65535, 1, 2), + (24, 0, 1, 2), + (24, 1, 1, 2), + (24, 2, 1, 2), + (24, 3, 1, 2), + (24, 4, 1, 2), + (24, 5, 1, 2), + (24, 7, 1, 2), + (24, 8, 1, 2), + (24, 15, 1, 2), + (24, 16, 1, 2), + (24, 31, 1, 2), + (24, 255, 1, 2), + (24, 256, 1, 2), + (24, 65535, 1, 2), + (25, 0, 0, 0), + (25, 1, 0, 0), + (25, 2, 0, 0), + (25, 3, 0, 0), + (25, 4, 0, 0), + (25, 5, 0, 0), + (25, 7, 0, 0), + (25, 8, 0, 0), + (25, 15, 0, 0), + (25, 16, 0, 0), + (25, 31, 0, 0), + (25, 255, 0, 0), + (25, 256, 0, 0), + (25, 65535, 0, 0), + (26, 0, 0, 0), + (26, 1, 0, 0), + (26, 2, 0, 0), + (26, 3, 0, 0), + (26, 4, 0, 0), + (26, 5, 0, 0), + (26, 7, 0, 0), + (26, 8, 0, 0), + (26, 15, 0, 0), + (26, 16, 0, 0), + (26, 31, 0, 0), + (26, 255, 0, 0), + (26, 256, 0, 0), + (26, 65535, 0, 0), + (27, 0, 1, 0), + (27, 1, 1, 0), + (27, 2, 1, 0), + (27, 3, 1, 0), + (27, 4, 1, 0), + (27, 5, 1, 0), + (27, 7, 1, 0), + (27, 8, 1, 0), + (27, 15, 1, 0), + (27, 16, 1, 0), + (27, 31, 1, 0), + (27, 255, 1, 0), + (27, 256, 1, 0), + (27, 65535, 1, 0), + (28, 0, 2, 0), + (28, 1, 2, 0), + (28, 2, 2, 0), + (28, 3, 2, 0), + (28, 4, 2, 0), + (28, 5, 2, 0), + (28, 7, 2, 0), + (28, 8, 2, 0), + (28, 15, 2, 0), + (28, 16, 2, 0), + (28, 31, 2, 0), + (28, 255, 2, 0), + (28, 256, 2, 0), + (28, 65535, 2, 0), + (29, 0, 1, 0), + (29, 1, 1, 0), + (29, 2, 1, 0), + (29, 3, 1, 0), + (29, 4, 1, 0), + (29, 5, 1, 0), + (29, 7, 1, 0), + (29, 8, 1, 0), + (29, 15, 1, 0), + (29, 16, 1, 0), + (29, 31, 1, 0), + (29, 255, 1, 0), + (29, 256, 1, 0), + (29, 65535, 1, 0), + (30, 0, 1, 2), + (30, 1, 1, 2), + (30, 2, 1, 2), + (30, 3, 1, 2), + (30, 4, 1, 2), + (30, 5, 1, 2), + (30, 7, 1, 2), + (30, 8, 1, 2), + (30, 15, 1, 2), + (30, 16, 1, 2), + (30, 31, 1, 2), + (30, 255, 1, 2), + (30, 256, 1, 2), + (30, 65535, 1, 2), + (31, 0, 0, 1), + (31, 1, 0, 1), + (31, 2, 0, 1), + (31, 3, 0, 1), + (31, 4, 0, 1), + (31, 5, 0, 1), + (31, 7, 0, 1), + (31, 8, 0, 1), + (31, 15, 0, 1), + (31, 16, 0, 1), + (31, 31, 0, 1), + (31, 255, 0, 1), + (31, 256, 0, 1), + (31, 65535, 0, 1), + (32, 0, 0, 1), + (32, 1, 0, 1), + (32, 2, 0, 1), + (32, 3, 0, 1), + (32, 4, 0, 1), + (32, 5, 0, 1), + (32, 7, 0, 1), + (32, 8, 0, 1), + (32, 15, 0, 1), + (32, 16, 0, 1), + (32, 31, 0, 1), + (32, 255, 0, 1), + (32, 256, 0, 1), + (32, 65535, 0, 1), + (33, 0, 1, 1), + (33, 1, 1, 1), + (33, 2, 1, 1), + (33, 3, 1, 1), + (33, 4, 1, 1), + (33, 5, 1, 1), + (33, 7, 1, 1), + (33, 8, 1, 1), + (33, 15, 1, 1), + (33, 16, 1, 1), + (33, 31, 1, 1), + (33, 255, 1, 1), + (33, 256, 1, 1), + (33, 65535, 1, 1), + (34, 0, 0, 0), + (34, 1, 0, 0), + (34, 2, 0, 0), + (34, 3, 0, 0), + (34, 4, 0, 0), + (34, 5, 0, 0), + (34, 7, 0, 0), + (34, 8, 0, 0), + (34, 15, 0, 0), + (34, 16, 0, 0), + (34, 31, 0, 0), + (34, 255, 0, 0), + (34, 256, 0, 0), + (34, 65535, 0, 0), + (35, 0, 4, 0), + (35, 1, 4, 0), + (35, 2, 4, 0), + (35, 3, 4, 0), + (35, 4, 4, 0), + (35, 5, 4, 0), + (35, 7, 4, 0), + (35, 8, 4, 0), + (35, 15, 4, 0), + (35, 16, 4, 0), + (35, 31, 4, 0), + (35, 255, 4, 0), + (35, 256, 4, 0), + (35, 65535, 4, 0), + (36, 0, 3, 0), + (36, 1, 3, 0), + (36, 2, 3, 0), + (36, 3, 3, 0), + (36, 4, 3, 0), + (36, 5, 3, 0), + (36, 7, 3, 0), + (36, 8, 3, 0), + (36, 15, 3, 0), + (36, 16, 3, 0), + (36, 31, 3, 0), + (36, 255, 3, 0), + (36, 256, 3, 0), + (36, 65535, 3, 0), + (37, 0, 1, 1), + (37, 1, 1, 1), + (37, 2, 1, 1), + (37, 3, 1, 1), + (37, 4, 1, 1), + (37, 5, 1, 1), + (37, 7, 1, 1), + (37, 8, 1, 1), + (37, 15, 1, 1), + (37, 16, 1, 1), + (37, 31, 1, 1), + (37, 255, 1, 1), + (37, 256, 1, 1), + (37, 65535, 1, 1), + (38, 0, 1, 1), + (38, 1, 1, 1), + (38, 2, 1, 1), + (38, 3, 1, 1), + (38, 4, 1, 1), + (38, 5, 1, 1), + (38, 7, 1, 1), + (38, 8, 1, 1), + (38, 15, 1, 1), + (38, 16, 1, 1), + (38, 31, 1, 1), + (38, 255, 1, 1), + (38, 256, 1, 1), + (38, 65535, 1, 1), + (39, 0, 1, 1), + (39, 1, 1, 1), + (39, 2, 1, 1), + (39, 3, 1, 1), + (39, 4, 1, 1), + (39, 5, 1, 1), + (39, 7, 1, 1), + (39, 8, 1, 1), + (39, 15, 1, 1), + (39, 16, 1, 1), + (39, 31, 1, 1), + (39, 255, 1, 1), + (39, 256, 1, 1), + (39, 65535, 1, 1), + (40, 0, 1, 1), + (40, 1, 1, 1), + (40, 2, 1, 1), + (40, 3, 1, 1), + (40, 4, 1, 1), + (40, 5, 1, 1), + (40, 7, 1, 1), + (40, 8, 1, 1), + (40, 15, 1, 1), + (40, 16, 1, 1), + (40, 31, 1, 1), + (40, 255, 1, 1), + (40, 256, 1, 1), + (40, 65535, 1, 1), + (41, 0, 5, 6), + (41, 1, 5, 6), + (41, 2, 5, 6), + (41, 3, 5, 6), + (41, 4, 5, 6), + (41, 5, 5, 6), + (41, 7, 5, 6), + (41, 8, 5, 6), + (41, 15, 5, 6), + (41, 16, 5, 6), + (41, 31, 5, 6), + (41, 255, 5, 6), + (41, 256, 5, 6), + (41, 65535, 5, 6), + (42, 0, 2, 1), + (42, 1, 2, 1), + (42, 2, 2, 1), + (42, 3, 2, 1), + (42, 4, 2, 1), + (42, 5, 2, 1), + (42, 7, 2, 1), + (42, 8, 2, 1), + (42, 15, 2, 1), + (42, 16, 2, 1), + (42, 31, 2, 1), + (42, 255, 2, 1), + (42, 256, 2, 1), + (42, 65535, 2, 1), + (43, 0, 2, 1), + (43, 1, 3, 1), + (43, 2, 2, 1), + (43, 3, 3, 1), + (43, 4, 2, 1), + (43, 5, 3, 1), + (43, 7, 3, 1), + (43, 8, 2, 1), + (43, 15, 3, 1), + (43, 16, 2, 1), + (43, 31, 3, 1), + (43, 255, 3, 1), + (43, 256, 2, 1), + (43, 65535, 3, 1), + (44, 0, 0, 1), + (44, 1, 1, 1), + (44, 2, 2, 1), + (44, 3, 3, 1), + (44, 4, 4, 1), + (44, 5, 5, 1), + (44, 7, 7, 1), + (44, 8, 8, 1), + (44, 15, 15, 1), + (44, 16, 16, 1), + (44, 31, 31, 1), + (44, 255, 255, 1), + (44, 256, 256, 1), + (44, 65535, 65535, 1), + (45, 0, 0, 1), + (45, 1, 2, 1), + (45, 2, 4, 1), + (45, 3, 6, 1), + (45, 4, 8, 1), + (45, 5, 10, 1), + (45, 7, 14, 1), + (45, 8, 16, 1), + (45, 15, 30, 1), + (45, 16, 32, 1), + (45, 31, 62, 1), + (45, 255, 510, 1), + (45, 256, 512, 1), + (45, 65535, 131070, 1), + (46, 0, 0, 1), + (46, 1, 1, 1), + (46, 2, 2, 1), + (46, 3, 3, 1), + (46, 4, 4, 1), + (46, 5, 5, 1), + (46, 7, 7, 1), + (46, 8, 8, 1), + (46, 15, 15, 1), + (46, 16, 16, 1), + (46, 31, 31, 1), + (46, 255, 255, 1), + (46, 256, 256, 1), + (46, 65535, 65535, 1), + (47, 0, 0, 1), + (47, 1, 1, 1), + (47, 2, 2, 1), + (47, 3, 3, 1), + (47, 4, 4, 1), + (47, 5, 5, 1), + (47, 7, 7, 1), + (47, 8, 8, 1), + (47, 15, 15, 1), + (47, 16, 16, 1), + (47, 31, 31, 1), + (47, 255, 255, 1), + (47, 256, 256, 1), + (47, 65535, 65535, 1), + (48, 0, 0, 1), + (48, 1, 1, 1), + (48, 2, 2, 1), + (48, 3, 3, 1), + (48, 4, 4, 1), + (48, 5, 5, 1), + (48, 7, 7, 1), + (48, 8, 8, 1), + (48, 15, 15, 1), + (48, 16, 16, 1), + (48, 31, 31, 1), + (48, 255, 255, 1), + (48, 256, 256, 1), + (48, 65535, 65535, 1), + (49, 0, 0, 1), + (49, 1, 1, 1), + (49, 2, 2, 1), + (49, 3, 3, 1), + (49, 4, 4, 1), + (49, 5, 5, 1), + (49, 7, 7, 1), + (49, 8, 8, 1), + (49, 15, 15, 1), + (49, 16, 16, 1), + (49, 31, 31, 1), + (49, 255, 255, 1), + (49, 256, 256, 1), + (49, 65535, 65535, 1), + (50, 0, 2, 1), + (50, 1, 3, 1), + (50, 2, 4, 1), + (50, 3, 5, 1), + (50, 4, 6, 1), + (50, 5, 7, 1), + (50, 7, 9, 1), + (50, 8, 10, 1), + (50, 15, 17, 1), + (50, 16, 18, 1), + (50, 31, 33, 1), + (50, 255, 257, 1), + (50, 256, 258, 1), + (50, 65535, 65537, 1), + (51, 0, 1, 1), + (51, 1, 1, 1), + (51, 2, 1, 1), + (51, 3, 1, 1), + (51, 4, 1, 1), + (51, 5, 1, 1), + (51, 7, 1, 1), + (51, 8, 1, 1), + (51, 15, 1, 1), + (51, 16, 1, 1), + (51, 31, 1, 1), + (51, 255, 1, 1), + (51, 256, 1, 1), + (51, 65535, 1, 1), + (52, 0, 2, 1), + (52, 1, 2, 1), + (52, 2, 2, 1), + (52, 3, 2, 1), + (52, 4, 2, 1), + (52, 5, 2, 1), + (52, 7, 2, 1), + (52, 8, 2, 1), + (52, 15, 2, 1), + (52, 16, 2, 1), + (52, 31, 2, 1), + (52, 255, 2, 1), + (52, 256, 2, 1), + (52, 65535, 2, 1), + (53, 0, 3, 1), + (53, 1, 4, 1), + (53, 2, 5, 1), + (53, 3, 6, 1), + (53, 4, 7, 1), + (53, 5, 8, 1), + (53, 7, 10, 1), + (53, 8, 11, 1), + (53, 15, 18, 1), + (53, 16, 19, 1), + (53, 31, 34, 1), + (53, 255, 258, 1), + (53, 256, 259, 1), + (53, 65535, 65538, 1), + (54, 0, 2, 1), + (54, 1, 2, 1), + (54, 2, 2, 1), + (54, 3, 2, 1), + (54, 4, 2, 1), + (54, 5, 2, 1), + (54, 7, 2, 1), + (54, 8, 2, 1), + (54, 15, 2, 1), + (54, 16, 2, 1), + (54, 31, 2, 1), + (54, 255, 2, 1), + (54, 256, 2, 1), + (54, 65535, 2, 1), + (55, 0, 2, 1), + (55, 1, 2, 1), + (55, 2, 2, 1), + (55, 3, 2, 1), + (55, 4, 2, 1), + (55, 5, 2, 1), + (55, 7, 2, 1), + (55, 8, 2, 1), + (55, 15, 2, 1), + (55, 16, 2, 1), + (55, 31, 2, 1), + (55, 255, 2, 1), + (55, 256, 2, 1), + (55, 65535, 2, 1), + (56, 0, 1, 1), + (56, 1, 1, 1), + (56, 2, 1, 1), + (56, 3, 1, 1), + (56, 4, 1, 1), + (56, 5, 1, 1), + (56, 7, 1, 1), + (56, 8, 1, 1), + (56, 15, 1, 1), + (56, 16, 1, 1), + (56, 31, 1, 1), + (56, 255, 1, 1), + (56, 256, 1, 1), + (56, 65535, 1, 1), + (57, 0, 0, 1), + (57, 1, 1, 2), + (57, 2, 2, 3), + (57, 3, 3, 4), + (57, 4, 4, 5), + (57, 5, 5, 6), + (57, 7, 7, 8), + (57, 8, 8, 9), + (57, 15, 15, 16), + (57, 16, 16, 17), + (57, 31, 31, 32), + (57, 255, 255, 256), + (57, 256, 256, 257), + (57, 65535, 65535, 65536), + (58, 0, 0, 0), + (58, 1, 0, 0), + (58, 2, 0, 0), + (58, 3, 0, 0), + (58, 4, 0, 0), + (58, 5, 0, 0), + (58, 7, 0, 0), + (58, 8, 0, 0), + (58, 15, 0, 0), + (58, 16, 0, 0), + (58, 31, 0, 0), + (58, 255, 0, 0), + (58, 256, 0, 0), + (58, 65535, 0, 0), + (59, 0, 1, 0), + (59, 1, 1, 0), + (59, 2, 1, 0), + (59, 3, 1, 0), + (59, 4, 1, 0), + (59, 5, 1, 0), + (59, 7, 1, 0), + (59, 8, 1, 0), + (59, 15, 1, 0), + (59, 16, 1, 0), + (59, 31, 1, 0), + (59, 255, 1, 0), + (59, 256, 1, 0), + (59, 65535, 1, 0), + (60, 0, 0, 0), + (60, 1, 0, 0), + (60, 2, 0, 0), + (60, 3, 0, 0), + (60, 4, 0, 0), + (60, 5, 0, 0), + (60, 7, 0, 0), + (60, 8, 0, 0), + (60, 15, 0, 0), + (60, 16, 0, 0), + (60, 31, 0, 0), + (60, 255, 0, 0), + (60, 256, 0, 0), + (60, 65535, 0, 0), + (61, 0, 0, 0), + (61, 1, 0, 0), + (61, 2, 0, 0), + (61, 3, 0, 0), + (61, 4, 0, 0), + (61, 5, 0, 0), + (61, 7, 0, 0), + (61, 8, 0, 0), + (61, 15, 0, 0), + (61, 16, 0, 0), + (61, 31, 0, 0), + (61, 255, 0, 0), + (61, 256, 0, 0), + (61, 65535, 0, 0), + (62, 0, 0, 0), + (62, 1, 0, 0), + (62, 2, 0, 0), + (62, 3, 0, 0), + (62, 4, 0, 0), + (62, 5, 0, 0), + (62, 7, 0, 0), + (62, 8, 0, 0), + (62, 15, 0, 0), + (62, 16, 0, 0), + (62, 31, 0, 0), + (62, 255, 0, 0), + (62, 256, 0, 0), + (62, 65535, 0, 0), + (63, 0, 0, 0), + (63, 1, 0, 0), + (63, 2, 0, 0), + (63, 3, 0, 0), + (63, 4, 0, 0), + (63, 5, 0, 0), + (63, 7, 0, 0), + (63, 8, 0, 0), + (63, 15, 0, 0), + (63, 16, 0, 0), + (63, 31, 0, 0), + (63, 255, 0, 0), + (63, 256, 0, 0), + (63, 65535, 0, 0), + (64, 0, 4, 3), + (64, 1, 5, 4), + (64, 2, 6, 5), + (64, 3, 7, 6), + (64, 4, 8, 7), + (64, 5, 9, 8), + (64, 7, 11, 10), + (64, 8, 12, 11), + (64, 15, 19, 18), + (64, 16, 20, 19), + (64, 31, 35, 34), + (64, 255, 259, 258), + (64, 256, 260, 259), + (64, 65535, 65539, 65538), + (65, 0, 1, 0), + (65, 1, 2, 1), + (65, 2, 3, 2), + (65, 3, 4, 3), + (65, 4, 5, 4), + (65, 5, 6, 5), + (65, 7, 8, 7), + (65, 8, 9, 8), + (65, 15, 16, 15), + (65, 16, 17, 16), + (65, 31, 32, 31), + (65, 255, 256, 255), + (65, 256, 257, 256), + (65, 65535, 65536, 65535), + (66, 0, 2, 0), + (66, 1, 2, 0), + (66, 2, 2, 0), + (66, 3, 2, 0), + (66, 4, 2, 0), + (66, 5, 2, 0), + (66, 7, 2, 0), + (66, 8, 2, 0), + (66, 15, 2, 0), + (66, 16, 2, 0), + (66, 31, 2, 0), + (66, 255, 2, 0), + (66, 256, 2, 0), + (66, 65535, 2, 0), + (67, 0, 0, 0), + (67, 1, 0, 0), + (67, 2, 0, 0), + (67, 3, 0, 0), + (67, 4, 0, 0), + (67, 5, 0, 0), + (67, 7, 0, 0), + (67, 8, 0, 0), + (67, 15, 0, 0), + (67, 16, 0, 0), + (67, 31, 0, 0), + (67, 255, 0, 0), + (67, 256, 0, 0), + (67, 65535, 0, 0), + (68, 0, 2, 3), + (68, 1, 2, 3), + (68, 2, 2, 3), + (68, 3, 2, 3), + (68, 4, 2, 3), + (68, 5, 2, 3), + (68, 7, 2, 3), + (68, 8, 2, 3), + (68, 15, 2, 3), + (68, 16, 2, 3), + (68, 31, 2, 3), + (68, 255, 2, 3), + (68, 256, 2, 3), + (68, 65535, 2, 3), + (69, 0, 1, 1), + (69, 1, 1, 1), + (69, 2, 1, 1), + (69, 3, 1, 1), + (69, 4, 1, 1), + (69, 5, 1, 1), + (69, 7, 1, 1), + (69, 8, 1, 1), + (69, 15, 1, 1), + (69, 16, 1, 1), + (69, 31, 1, 1), + (69, 255, 1, 1), + (69, 256, 1, 1), + (69, 65535, 1, 1), + (70, 0, 1, 2), + (70, 1, 1, 2), + (70, 2, 1, 2), + (70, 3, 1, 2), + (70, 4, 1, 2), + (70, 5, 1, 2), + (70, 7, 1, 2), + (70, 8, 1, 2), + (70, 15, 1, 2), + (70, 16, 1, 2), + (70, 31, 1, 2), + (70, 255, 1, 2), + (70, 256, 1, 2), + (70, 65535, 1, 2), + (71, 0, 1, 2), + (71, 1, 1, 2), + (71, 2, 1, 2), + (71, 3, 1, 2), + (71, 4, 1, 2), + (71, 5, 1, 2), + (71, 7, 1, 2), + (71, 8, 1, 2), + (71, 15, 1, 2), + (71, 16, 1, 2), + (71, 31, 1, 2), + (71, 255, 1, 2), + (71, 256, 1, 2), + (71, 65535, 1, 2), + (72, 0, 2, 1), + (72, 1, 2, 1), + (72, 2, 2, 1), + (72, 3, 2, 1), + (72, 4, 2, 1), + (72, 5, 2, 1), + (72, 7, 2, 1), + (72, 8, 2, 1), + (72, 15, 2, 1), + (72, 16, 2, 1), + (72, 31, 2, 1), + (72, 255, 2, 1), + (72, 256, 2, 1), + (72, 65535, 2, 1), + (73, 0, 2, 1), + (73, 1, 2, 1), + (73, 2, 2, 1), + (73, 3, 2, 1), + (73, 4, 2, 1), + (73, 5, 2, 1), + (73, 7, 2, 1), + (73, 8, 2, 1), + (73, 15, 2, 1), + (73, 16, 2, 1), + (73, 31, 2, 1), + (73, 255, 2, 1), + (73, 256, 2, 1), + (73, 65535, 2, 1), + (74, 0, 0, 0), + (74, 1, 0, 0), + (74, 2, 0, 0), + (74, 3, 0, 0), + (74, 4, 0, 0), + (74, 5, 0, 0), + (74, 7, 0, 0), + (74, 8, 0, 0), + (74, 15, 0, 0), + (74, 16, 0, 0), + (74, 31, 0, 0), + (74, 255, 0, 0), + (74, 256, 0, 0), + (74, 65535, 0, 0), + (75, 0, 0, 0), + (75, 1, 0, 0), + (75, 2, 0, 0), + (75, 3, 0, 0), + (75, 4, 0, 0), + (75, 5, 0, 0), + (75, 7, 0, 0), + (75, 8, 0, 0), + (75, 15, 0, 0), + (75, 16, 0, 0), + (75, 31, 0, 0), + (75, 255, 0, 0), + (75, 256, 0, 0), + (75, 65535, 0, 0), + (76, 0, 0, 0), + (76, 1, 0, 0), + (76, 2, 0, 0), + (76, 3, 0, 0), + (76, 4, 0, 0), + (76, 5, 0, 0), + (76, 7, 0, 0), + (76, 8, 0, 0), + (76, 15, 0, 0), + (76, 16, 0, 0), + (76, 31, 0, 0), + (76, 255, 0, 0), + (76, 256, 0, 0), + (76, 65535, 0, 0), + (77, 0, 1, 0), + (77, 1, 2, 1), + (77, 2, 3, 2), + (77, 3, 4, 3), + (77, 4, 5, 4), + (77, 5, 6, 5), + (77, 7, 8, 7), + (77, 8, 9, 8), + (77, 15, 16, 15), + (77, 16, 17, 16), + (77, 31, 32, 31), + (77, 255, 256, 255), + (77, 256, 257, 256), + (77, 65535, 65536, 65535), + (78, 0, 1, 0), + (78, 1, 2, 1), + (78, 2, 3, 2), + (78, 3, 4, 3), + (78, 4, 5, 4), + (78, 5, 6, 5), + (78, 7, 8, 7), + (78, 8, 9, 8), + (78, 15, 16, 15), + (78, 16, 17, 16), + (78, 31, 32, 31), + (78, 255, 256, 255), + (78, 256, 257, 256), + (78, 65535, 65536, 65535), + (79, 0, 1, 1), + (79, 1, 1, 2), + (79, 2, 1, 1), + (79, 3, 1, 2), + (79, 4, 1, 1), + (79, 5, 1, 2), + (79, 7, 1, 2), + (79, 8, 1, 1), + (79, 15, 1, 2), + (79, 16, 1, 1), + (79, 31, 1, 2), + (79, 255, 1, 2), + (79, 256, 1, 1), + (79, 65535, 1, 2), + (80, 0, 0, 1), + (80, 1, 0, 1), + (80, 2, 0, 1), + (80, 3, 0, 1), + (80, 4, 0, 1), + (80, 5, 0, 1), + (80, 7, 0, 1), + (80, 8, 0, 1), + (80, 15, 0, 1), + (80, 16, 0, 1), + (80, 31, 0, 1), + (80, 255, 0, 1), + (80, 256, 0, 1), + (80, 65535, 0, 1), + (81, 0, 0, 1), + (81, 1, 0, 1), + (81, 2, 0, 1), + (81, 3, 0, 1), + (81, 4, 0, 1), + (81, 5, 0, 1), + (81, 7, 0, 1), + (81, 8, 0, 1), + (81, 15, 0, 1), + (81, 16, 0, 1), + (81, 31, 0, 1), + (81, 255, 0, 1), + (81, 256, 0, 1), + (81, 65535, 0, 1), + (82, 0, 0, 1), + (82, 1, 0, 1), + (82, 2, 0, 1), + (82, 3, 0, 1), + (82, 4, 0, 1), + (82, 5, 0, 1), + (82, 7, 0, 1), + (82, 8, 0, 1), + (82, 15, 0, 1), + (82, 16, 0, 1), + (82, 31, 0, 1), + (82, 255, 0, 1), + (82, 256, 0, 1), + (82, 65535, 0, 1), + (83, 0, 0, 1), + (83, 1, 0, 1), + (83, 2, 0, 1), + (83, 3, 0, 1), + (83, 4, 0, 1), + (83, 5, 0, 1), + (83, 7, 0, 1), + (83, 8, 0, 1), + (83, 15, 0, 1), + (83, 16, 0, 1), + (83, 31, 0, 1), + (83, 255, 0, 1), + (83, 256, 0, 1), + (83, 65535, 0, 1), + (84, 0, 0, 1), + (84, 1, 0, 1), + (84, 2, 0, 1), + (84, 3, 0, 1), + (84, 4, 0, 1), + (84, 5, 0, 1), + (84, 7, 0, 1), + (84, 8, 0, 1), + (84, 15, 0, 1), + (84, 16, 0, 1), + (84, 31, 0, 1), + (84, 255, 0, 1), + (84, 256, 0, 1), + (84, 65535, 0, 1), + (85, 0, 0, 1), + (85, 1, 0, 1), + (85, 2, 0, 1), + (85, 3, 0, 1), + (85, 4, 0, 1), + (85, 5, 0, 1), + (85, 7, 0, 1), + (85, 8, 0, 1), + (85, 15, 0, 1), + (85, 16, 0, 1), + (85, 31, 0, 1), + (85, 255, 0, 1), + (85, 256, 0, 1), + (85, 65535, 0, 1), + (86, 0, 0, 2), + (86, 1, 0, 2), + (86, 2, 0, 2), + (86, 3, 0, 2), + (86, 4, 0, 2), + (86, 5, 0, 2), + (86, 7, 0, 2), + (86, 8, 0, 2), + (86, 15, 0, 2), + (86, 16, 0, 2), + (86, 31, 0, 2), + (86, 255, 0, 2), + (86, 256, 0, 2), + (86, 65535, 0, 2), + (87, 0, 0, 1), + (87, 1, 0, 1), + (87, 2, 0, 1), + (87, 3, 0, 1), + (87, 4, 0, 1), + (87, 5, 0, 1), + (87, 7, 0, 1), + (87, 8, 0, 1), + (87, 15, 0, 1), + (87, 16, 0, 1), + (87, 31, 0, 1), + (87, 255, 0, 1), + (87, 256, 0, 1), + (87, 65535, 0, 1), + (88, 0, 0, 2), + (88, 1, 0, 2), + (88, 2, 0, 2), + (88, 3, 0, 2), + (88, 4, 0, 2), + (88, 5, 0, 2), + (88, 7, 0, 2), + (88, 8, 0, 2), + (88, 15, 0, 2), + (88, 16, 0, 2), + (88, 31, 0, 2), + (88, 255, 0, 2), + (88, 256, 0, 2), + (88, 65535, 0, 2), + (89, 0, 1, 1), + (89, 1, 1, 1), + (89, 2, 1, 1), + (89, 3, 1, 1), + (89, 4, 1, 1), + (89, 5, 1, 1), + (89, 7, 1, 1), + (89, 8, 1, 1), + (89, 15, 1, 1), + (89, 16, 1, 1), + (89, 31, 1, 1), + (89, 255, 1, 1), + (89, 256, 1, 1), + (89, 65535, 1, 1), + (90, 0, 1, 1), + (90, 1, 1, 1), + (90, 2, 1, 1), + (90, 3, 1, 1), + (90, 4, 1, 1), + (90, 5, 1, 1), + (90, 7, 1, 1), + (90, 8, 1, 1), + (90, 15, 1, 1), + (90, 16, 1, 1), + (90, 31, 1, 1), + (90, 255, 1, 1), + (90, 256, 1, 1), + (90, 65535, 1, 1), + (91, 0, 0, 1), + (91, 1, 0, 2), + (91, 2, 0, 1), + (91, 3, 0, 2), + (91, 4, 0, 1), + (91, 5, 0, 2), + (91, 7, 0, 2), + (91, 8, 0, 1), + (91, 15, 0, 2), + (91, 16, 0, 1), + (91, 31, 0, 2), + (91, 255, 0, 2), + (91, 256, 0, 1), + (91, 65535, 0, 2), + (92, 0, 0, 1), + (92, 1, 0, 1), + (92, 2, 0, 1), + (92, 3, 0, 1), + (92, 4, 0, 1), + (92, 5, 0, 1), + (92, 7, 0, 1), + (92, 8, 0, 1), + (92, 15, 0, 1), + (92, 16, 0, 1), + (92, 31, 0, 1), + (92, 255, 0, 1), + (92, 256, 0, 1), + (92, 65535, 0, 1), + (93, 0, 0, 1), + (93, 1, 0, 1), + (93, 2, 0, 1), + (93, 3, 0, 1), + (93, 4, 0, 1), + (93, 5, 0, 1), + (93, 7, 0, 1), + (93, 8, 0, 1), + (93, 15, 0, 1), + (93, 16, 0, 1), + (93, 31, 0, 1), + (93, 255, 0, 1), + (93, 256, 0, 1), + (93, 65535, 0, 1), + (94, 0, 1, 2), + (94, 1, 1, 2), + (94, 2, 1, 2), + (94, 3, 1, 2), + (94, 4, 1, 2), + (94, 5, 1, 2), + (94, 7, 1, 2), + (94, 8, 1, 2), + (94, 15, 1, 2), + (94, 16, 1, 2), + (94, 31, 1, 2), + (94, 255, 1, 2), + (94, 256, 1, 2), + (94, 65535, 1, 2), + (95, 0, 3, 1), + (95, 1, 3, 2), + (95, 2, 3, 1), + (95, 3, 3, 2), + (95, 4, 3, 1), + (95, 5, 3, 2), + (95, 7, 3, 2), + (95, 8, 3, 1), + (95, 15, 3, 2), + (95, 16, 3, 1), + (95, 31, 3, 2), + (95, 255, 3, 2), + (95, 256, 3, 1), + (95, 65535, 3, 2), + (96, 0, 0, 0), + (96, 1, 0, 0), + (96, 2, 0, 0), + (96, 3, 0, 0), + (96, 4, 0, 0), + (96, 5, 0, 0), + (96, 7, 0, 0), + (96, 8, 0, 0), + (96, 15, 0, 0), + (96, 16, 0, 0), + (96, 31, 0, 0), + (96, 255, 0, 0), + (96, 256, 0, 0), + (96, 65535, 0, 0), + (97, 0, 2, 0), + (97, 1, 3, 1), + (97, 2, 4, 2), + (97, 3, 5, 3), + (97, 4, 6, 4), + (97, 5, 7, 5), + (97, 7, 9, 7), + (97, 8, 10, 8), + (97, 15, 17, 15), + (97, 16, 18, 16), + (97, 31, 33, 31), + (97, 255, 257, 255), + (97, 256, 258, 256), + (97, 65535, 65537, 65535), + (98, 0, 3, 1), + (98, 1, 3, 1), + (98, 2, 3, 1), + (98, 3, 3, 1), + (98, 4, 3, 1), + (98, 5, 3, 1), + (98, 7, 3, 1), + (98, 8, 3, 1), + (98, 15, 3, 1), + (98, 16, 3, 1), + (98, 31, 3, 1), + (98, 255, 3, 1), + (98, 256, 3, 1), + (98, 65535, 3, 1), + (99, 0, 1, 0), + (99, 1, 1, 0), + (99, 2, 1, 0), + (99, 3, 1, 0), + (99, 4, 1, 0), + (99, 5, 1, 0), + (99, 7, 1, 0), + (99, 8, 1, 0), + (99, 15, 1, 0), + (99, 16, 1, 0), + (99, 31, 1, 0), + (99, 255, 1, 0), + (99, 256, 1, 0), + (99, 65535, 1, 0), + (100, 0, 1, 0), + (100, 1, 1, 0), + (100, 2, 1, 0), + (100, 3, 1, 0), + (100, 4, 1, 0), + (100, 5, 1, 0), + (100, 7, 1, 0), + (100, 8, 1, 0), + (100, 15, 1, 0), + (100, 16, 1, 0), + (100, 31, 1, 0), + (100, 255, 1, 0), + (100, 256, 1, 0), + (100, 65535, 1, 0), + (101, 0, 1, 0), + (101, 1, 1, 0), + (101, 2, 1, 0), + (101, 3, 1, 0), + (101, 4, 1, 0), + (101, 5, 1, 0), + (101, 7, 1, 0), + (101, 8, 1, 0), + (101, 15, 1, 0), + (101, 16, 1, 0), + (101, 31, 1, 0), + (101, 255, 1, 0), + (101, 256, 1, 0), + (101, 65535, 1, 0), + (102, 0, 1, 0), + (102, 1, 1, 0), + (102, 2, 1, 0), + (102, 3, 1, 0), + (102, 4, 1, 0), + (102, 5, 1, 0), + (102, 7, 1, 0), + (102, 8, 1, 0), + (102, 15, 1, 0), + (102, 16, 1, 0), + (102, 31, 1, 0), + (102, 255, 1, 0), + (102, 256, 1, 0), + (102, 65535, 1, 0), + (103, 0, 0, 0), + (103, 1, 1, 0), + (103, 2, 2, 0), + (103, 3, 3, 0), + (103, 4, 4, 0), + (103, 5, 5, 0), + (103, 7, 7, 0), + (103, 8, 8, 0), + (103, 15, 15, 0), + (103, 16, 16, 0), + (103, 31, 31, 0), + (103, 255, 255, 0), + (103, 256, 256, 0), + (103, 65535, 65535, 0), + (104, 0, 1, 0), + (104, 1, 2, 1), + (104, 2, 3, 2), + (104, 3, 4, 3), + (104, 4, 5, 4), + (104, 5, 6, 5), + (104, 7, 8, 7), + (104, 8, 9, 8), + (104, 15, 16, 15), + (104, 16, 17, 16), + (104, 31, 32, 31), + (104, 255, 256, 255), + (104, 256, 257, 256), + (104, 65535, 65536, 65535), + (105, 0, 3, 3), + (105, 1, 3, 3), + (105, 2, 3, 3), + (105, 3, 3, 3), + (105, 4, 3, 3), + (105, 5, 3, 3), + (105, 7, 3, 3), + (105, 8, 3, 3), + (105, 15, 3, 3), + (105, 16, 3, 3), + (105, 31, 3, 3), + (105, 255, 3, 3), + (105, 256, 3, 3), + (105, 65535, 3, 3), + (106, 0, 1, 0), + (106, 1, 2, 1), + (106, 2, 3, 2), + (106, 3, 4, 3), + (106, 4, 5, 4), + (106, 5, 6, 5), + (106, 7, 8, 7), + (106, 8, 9, 8), + (106, 15, 16, 15), + (106, 16, 17, 16), + (106, 31, 32, 31), + (106, 255, 256, 255), + (106, 256, 257, 256), + (106, 65535, 65536, 65535), + (107, 0, 2, 1), + (107, 1, 2, 1), + (107, 2, 2, 1), + (107, 3, 2, 1), + (107, 4, 2, 1), + (107, 5, 2, 1), + (107, 7, 2, 1), + (107, 8, 2, 1), + (107, 15, 2, 1), + (107, 16, 2, 1), + (107, 31, 2, 1), + (107, 255, 2, 1), + (107, 256, 2, 1), + (107, 65535, 2, 1), + (108, 0, 1, 0), + (108, 1, 2, 1), + (108, 2, 3, 2), + (108, 3, 4, 3), + (108, 4, 5, 4), + (108, 5, 6, 5), + (108, 7, 8, 7), + (108, 8, 9, 8), + (108, 15, 16, 15), + (108, 16, 17, 16), + (108, 31, 32, 31), + (108, 255, 256, 255), + (108, 256, 257, 256), + (108, 65535, 65536, 65535), + (109, 0, 2, 0), + (109, 1, 2, 0), + (109, 2, 2, 0), + (109, 3, 2, 0), + (109, 4, 2, 0), + (109, 5, 2, 0), + (109, 7, 2, 0), + (109, 8, 2, 0), + (109, 15, 2, 0), + (109, 16, 2, 0), + (109, 31, 2, 0), + (109, 255, 2, 0), + (109, 256, 2, 0), + (109, 65535, 2, 0), + (110, 0, 1, 0), + (110, 1, 1, 0), + (110, 2, 1, 0), + (110, 3, 1, 0), + (110, 4, 1, 0), + (110, 5, 1, 0), + (110, 7, 1, 0), + (110, 8, 1, 0), + (110, 15, 1, 0), + (110, 16, 1, 0), + (110, 31, 1, 0), + (110, 255, 1, 0), + (110, 256, 1, 0), + (110, 65535, 1, 0), + (111, 0, 1, 0), + (111, 1, 1, 0), + (111, 2, 1, 0), + (111, 3, 1, 0), + (111, 4, 1, 0), + (111, 5, 1, 0), + (111, 7, 1, 0), + (111, 8, 1, 0), + (111, 15, 1, 0), + (111, 16, 1, 0), + (111, 31, 1, 0), + (111, 255, 1, 0), + (111, 256, 1, 0), + (111, 65535, 1, 0), + (112, 0, 1, 1), + (112, 1, 1, 1), + (112, 2, 1, 1), + (112, 3, 1, 1), + (112, 4, 1, 1), + (112, 5, 1, 1), + (112, 7, 1, 1), + (112, 8, 1, 1), + (112, 15, 1, 1), + (112, 16, 1, 1), + (112, 31, 1, 1), + (112, 255, 1, 1), + (112, 256, 1, 1), + (112, 65535, 1, 1), + (113, 0, 2, 0), + (113, 1, 2, 0), + (113, 2, 2, 0), + (113, 3, 2, 0), + (113, 4, 2, 0), + (113, 5, 2, 0), + (113, 7, 2, 0), + (113, 8, 2, 0), + (113, 15, 2, 0), + (113, 16, 2, 0), + (113, 31, 2, 0), + (113, 255, 2, 0), + (113, 256, 2, 0), + (113, 65535, 2, 0), + (114, 0, 1, 0), + (114, 1, 1, 0), + (114, 2, 1, 0), + (114, 3, 1, 0), + (114, 4, 1, 0), + (114, 5, 1, 0), + (114, 7, 1, 0), + (114, 8, 1, 0), + (114, 15, 1, 0), + (114, 16, 1, 0), + (114, 31, 1, 0), + (114, 255, 1, 0), + (114, 256, 1, 0), + (114, 65535, 1, 0), + (115, 0, 1, 0), + (115, 1, 1, 0), + (115, 2, 1, 0), + (115, 3, 1, 0), + (115, 4, 1, 0), + (115, 5, 1, 0), + (115, 7, 1, 0), + (115, 8, 1, 0), + (115, 15, 1, 0), + (115, 16, 1, 0), + (115, 31, 1, 0), + (115, 255, 1, 0), + (115, 256, 1, 0), + (115, 65535, 1, 0), + (116, 0, 0, 0), + (116, 1, 1, 1), + (116, 2, 2, 2), + (116, 3, 3, 3), + (116, 4, 4, 4), + (116, 5, 5, 5), + (116, 7, 7, 7), + (116, 8, 8, 8), + (116, 15, 15, 15), + (116, 16, 16, 16), + (116, 31, 31, 31), + (116, 255, 255, 255), + (116, 256, 256, 256), + (116, 65535, 65535, 65535), + (117, 0, 1, 1), + (117, 1, 1, 2), + (117, 2, 1, 3), + (117, 3, 1, 4), + (117, 4, 1, 5), + (117, 5, 1, 6), + (117, 7, 1, 8), + (117, 8, 1, 9), + (117, 15, 1, 16), + (117, 16, 1, 17), + (117, 31, 1, 32), + (117, 255, 1, 256), + (117, 256, 1, 2), + (117, 65535, 1, 511), + (118, 0, 1, 0), + (118, 1, 1, 1), + (118, 2, 1, 2), + (118, 3, 1, 3), + (118, 4, 1, 4), + (118, 5, 1, 5), + (118, 7, 1, 7), + (118, 8, 1, 8), + (118, 15, 1, 15), + (118, 16, 1, 16), + (118, 31, 1, 31), + (118, 255, 1, 255), + (118, 256, 1, 256), + (118, 65535, 1, 65535), + (119, 0, 1, 1), + (119, 1, 1, 1), + (119, 2, 1, 1), + (119, 3, 1, 1), + (119, 4, 1, 1), + (119, 5, 1, 1), + (119, 7, 1, 1), + (119, 8, 1, 1), + (119, 15, 1, 1), + (119, 16, 1, 1), + (119, 31, 1, 1), + (119, 255, 1, 1), + (119, 256, 1, 1), + (119, 65535, 1, 1), + (128, 0, 0, 0), + (128, 1, 0, 0), + (128, 2, 0, 0), + (128, 3, 0, 0), + (128, 4, 0, 0), + (128, 5, 0, 0), + (128, 7, 0, 0), + (128, 8, 0, 0), + (128, 15, 0, 0), + (128, 16, 0, 0), + (128, 31, 0, 0), + (128, 255, 0, 0), + (128, 256, 0, 0), + (128, 65535, 0, 0), + (129, 0, 2, 1), + (129, 1, 2, 1), + (129, 2, 2, 1), + (129, 3, 2, 1), + (129, 4, 2, 1), + (129, 5, 2, 1), + (129, 7, 2, 1), + (129, 8, 2, 1), + (129, 15, 2, 1), + (129, 16, 2, 1), + (129, 31, 2, 1), + (129, 255, 2, 1), + (129, 256, 2, 1), + (129, 65535, 2, 1), + (130, 0, 2, 1), + (130, 1, 2, 1), + (130, 2, 2, 1), + (130, 3, 2, 1), + (130, 4, 2, 1), + (130, 5, 2, 1), + (130, 7, 2, 1), + (130, 8, 2, 1), + (130, 15, 2, 1), + (130, 16, 2, 1), + (130, 31, 2, 1), + (130, 255, 2, 1), + (130, 256, 2, 1), + (130, 65535, 2, 1), + (131, 0, 2, 1), + (131, 1, 2, 1), + (131, 2, 2, 1), + (131, 3, 2, 1), + (131, 4, 2, 1), + (131, 5, 2, 1), + (131, 7, 2, 1), + (131, 8, 2, 1), + (131, 15, 2, 1), + (131, 16, 2, 1), + (131, 31, 2, 1), + (131, 255, 2, 1), + (131, 256, 2, 1), + (131, 65535, 2, 1), + (132, 0, 2, 1), + (132, 1, 2, 1), + (132, 2, 2, 1), + (132, 3, 2, 1), + (132, 4, 2, 1), + (132, 5, 2, 1), + (132, 7, 2, 1), + (132, 8, 2, 1), + (132, 15, 2, 1), + (132, 16, 2, 1), + (132, 31, 2, 1), + (132, 255, 2, 1), + (132, 256, 2, 1), + (132, 65535, 2, 1), + (133, 0, 2, 1), + (133, 1, 2, 1), + (133, 2, 2, 1), + (133, 3, 2, 1), + (133, 4, 2, 1), + (133, 5, 2, 1), + (133, 7, 2, 1), + (133, 8, 2, 1), + (133, 15, 2, 1), + (133, 16, 2, 1), + (133, 31, 2, 1), + (133, 255, 2, 1), + (133, 256, 2, 1), + (133, 65535, 2, 1), + (134, 0, 2, 1), + (134, 1, 2, 1), + (134, 2, 2, 1), + (134, 3, 2, 1), + (134, 4, 2, 1), + (134, 5, 2, 1), + (134, 7, 2, 1), + (134, 8, 2, 1), + (134, 15, 2, 1), + (134, 16, 2, 1), + (134, 31, 2, 1), + (134, 255, 2, 1), + (134, 256, 2, 1), + (134, 65535, 2, 1), + (135, 0, 2, 1), + (135, 1, 2, 1), + (135, 2, 2, 1), + (135, 3, 2, 1), + (135, 4, 2, 1), + (135, 5, 2, 1), + (135, 7, 2, 1), + (135, 8, 2, 1), + (135, 15, 2, 1), + (135, 16, 2, 1), + (135, 31, 2, 1), + (135, 255, 2, 1), + (135, 256, 2, 1), + (135, 65535, 2, 1), + (136, 0, 2, 0), + (136, 1, 2, 0), + (136, 2, 2, 0), + (136, 3, 2, 0), + (136, 4, 2, 0), + (136, 5, 2, 0), + (136, 7, 2, 0), + (136, 8, 2, 0), + (136, 15, 2, 0), + (136, 16, 2, 0), + (136, 31, 2, 0), + (136, 255, 2, 0), + (136, 256, 2, 0), + (136, 65535, 2, 0), + (137, 0, 2, 1), + (137, 1, 2, 1), + (137, 2, 2, 1), + (137, 3, 2, 1), + (137, 4, 2, 1), + (137, 5, 2, 1), + (137, 7, 2, 1), + (137, 8, 2, 1), + (137, 15, 2, 1), + (137, 16, 2, 1), + (137, 31, 2, 1), + (137, 255, 2, 1), + (137, 256, 2, 1), + (137, 65535, 2, 1), + (138, 0, 2, 1), + (138, 1, 2, 1), + (138, 2, 2, 1), + (138, 3, 2, 1), + (138, 4, 2, 1), + (138, 5, 2, 1), + (138, 7, 2, 1), + (138, 8, 2, 1), + (138, 15, 2, 1), + (138, 16, 2, 1), + (138, 31, 2, 1), + (138, 255, 2, 1), + (138, 256, 2, 1), + (138, 65535, 2, 1), + (139, 0, 2, 1), + (139, 1, 2, 1), + (139, 2, 2, 1), + (139, 3, 2, 1), + (139, 4, 2, 1), + (139, 5, 2, 1), + (139, 7, 2, 1), + (139, 8, 2, 1), + (139, 15, 2, 1), + (139, 16, 2, 1), + (139, 31, 2, 1), + (139, 255, 2, 1), + (139, 256, 2, 1), + (139, 65535, 2, 1), + (140, 0, 2, 1), + (140, 1, 2, 1), + (140, 2, 2, 1), + (140, 3, 2, 1), + (140, 4, 2, 1), + (140, 5, 2, 1), + (140, 7, 2, 1), + (140, 8, 2, 1), + (140, 15, 2, 1), + (140, 16, 2, 1), + (140, 31, 2, 1), + (140, 255, 2, 1), + (140, 256, 2, 1), + (140, 65535, 2, 1), + (141, 0, 2, 1), + (141, 1, 2, 1), + (141, 2, 2, 1), + (141, 3, 2, 1), + (141, 4, 2, 1), + (141, 5, 2, 1), + (141, 7, 2, 1), + (141, 8, 2, 1), + (141, 15, 2, 1), + (141, 16, 2, 1), + (141, 31, 2, 1), + (141, 255, 2, 1), + (141, 256, 2, 1), + (141, 65535, 2, 1), + (142, 0, 2, 1), + (142, 1, 2, 1), + (142, 2, 2, 1), + (142, 3, 2, 1), + (142, 4, 2, 1), + (142, 5, 2, 1), + (142, 7, 2, 1), + (142, 8, 2, 1), + (142, 15, 2, 1), + (142, 16, 2, 1), + (142, 31, 2, 1), + (142, 255, 2, 1), + (142, 256, 2, 1), + (142, 65535, 2, 1), + (143, 0, 2, 1), + (143, 1, 2, 1), + (143, 2, 2, 1), + (143, 3, 2, 1), + (143, 4, 2, 1), + (143, 5, 2, 1), + (143, 7, 2, 1), + (143, 8, 2, 1), + (143, 15, 2, 1), + (143, 16, 2, 1), + (143, 31, 2, 1), + (143, 255, 2, 1), + (143, 256, 2, 1), + (143, 65535, 2, 1), + (144, 0, 2, 0), + (144, 1, 3, 0), + (144, 2, 4, 0), + (144, 3, 5, 0), + (144, 4, 6, 0), + (144, 5, 7, 0), + (144, 7, 9, 0), + (144, 8, 10, 0), + (144, 15, 17, 0), + (144, 16, 18, 0), + (144, 31, 33, 0), + (144, 255, 257, 0), + (144, 256, 258, 0), + (144, 65535, 65537, 0), + (145, 0, 2, 0), + (145, 1, 3, 0), + (145, 2, 4, 0), + (145, 3, 5, 0), + (145, 4, 6, 0), + (145, 5, 7, 0), + (145, 7, 9, 0), + (145, 8, 10, 0), + (145, 15, 17, 0), + (145, 16, 18, 0), + (145, 31, 33, 0), + (145, 255, 257, 0), + (145, 256, 258, 0), + (145, 65535, 65537, 0), + (146, 0, 2, 0), + (146, 1, 3, 0), + (146, 2, 4, 0), + (146, 3, 5, 0), + (146, 4, 6, 0), + (146, 5, 7, 0), + (146, 7, 9, 0), + (146, 8, 10, 0), + (146, 15, 17, 0), + (146, 16, 18, 0), + (146, 31, 33, 0), + (146, 255, 257, 0), + (146, 256, 258, 0), + (146, 65535, 65537, 0), + (147, 0, 2, 1), + (147, 1, 3, 1), + (147, 2, 4, 1), + (147, 3, 5, 1), + (147, 4, 6, 1), + (147, 5, 7, 1), + (147, 7, 9, 1), + (147, 8, 10, 1), + (147, 15, 17, 1), + (147, 16, 18, 1), + (147, 31, 33, 1), + (147, 255, 257, 1), + (147, 256, 258, 1), + (147, 65535, 65537, 1), + (148, 0, 2, 1), + (148, 1, 3, 1), + (148, 2, 4, 1), + (148, 3, 5, 1), + (148, 4, 6, 1), + (148, 5, 7, 1), + (148, 7, 9, 1), + (148, 8, 10, 1), + (148, 15, 17, 1), + (148, 16, 18, 1), + (148, 31, 33, 1), + (148, 255, 257, 1), + (148, 256, 258, 1), + (148, 65535, 65537, 1), + (149, 0, 2, 1), + (149, 1, 3, 1), + (149, 2, 4, 1), + (149, 3, 5, 1), + (149, 4, 6, 1), + (149, 5, 7, 1), + (149, 7, 9, 1), + (149, 8, 10, 1), + (149, 15, 17, 1), + (149, 16, 18, 1), + (149, 31, 33, 1), + (149, 255, 257, 1), + (149, 256, 258, 1), + (149, 65535, 65537, 1), + (150, 0, 2, 1), + (150, 1, 3, 1), + (150, 2, 4, 1), + (150, 3, 5, 1), + (150, 4, 6, 1), + (150, 5, 7, 1), + (150, 7, 9, 1), + (150, 8, 10, 1), + (150, 15, 17, 1), + (150, 16, 18, 1), + (150, 31, 33, 1), + (150, 255, 257, 1), + (150, 256, 258, 1), + (150, 65535, 65537, 1), + (151, 0, 4, 1), + (151, 1, 4, 1), + (151, 2, 4, 1), + (151, 3, 4, 1), + (151, 4, 4, 1), + (151, 5, 4, 1), + (151, 7, 4, 1), + (151, 8, 4, 1), + (151, 15, 4, 1), + (151, 16, 4, 1), + (151, 31, 4, 1), + (151, 255, 4, 1), + (151, 256, 4, 1), + (151, 65535, 4, 1), + (152, 0, 4, 0), + (152, 1, 4, 0), + (152, 2, 4, 0), + (152, 3, 4, 0), + (152, 4, 4, 0), + (152, 5, 4, 0), + (152, 7, 4, 0), + (152, 8, 4, 0), + (152, 15, 4, 0), + (152, 16, 4, 0), + (152, 31, 4, 0), + (152, 255, 4, 0), + (152, 256, 4, 0), + (152, 65535, 4, 0), + (153, 0, 4, 1), + (153, 1, 4, 1), + (153, 2, 4, 1), + (153, 3, 4, 1), + (153, 4, 4, 1), + (153, 5, 4, 1), + (153, 7, 4, 1), + (153, 8, 4, 1), + (153, 15, 4, 1), + (153, 16, 4, 1), + (153, 31, 4, 1), + (153, 255, 4, 1), + (153, 256, 4, 1), + (153, 65535, 4, 1), + (154, 0, 3, 0), + (154, 1, 4, 0), + (154, 2, 5, 0), + (154, 3, 6, 0), + (154, 4, 7, 0), + (154, 5, 8, 0), + (154, 7, 10, 0), + (154, 8, 11, 0), + (154, 15, 18, 0), + (154, 16, 19, 0), + (154, 31, 34, 0), + (154, 255, 258, 0), + (154, 256, 259, 0), + (154, 65535, 65538, 0), + (155, 0, 3, 1), + (155, 1, 4, 1), + (155, 2, 5, 1), + (155, 3, 6, 1), + (155, 4, 7, 1), + (155, 5, 8, 1), + (155, 7, 10, 1), + (155, 8, 11, 1), + (155, 15, 18, 1), + (155, 16, 19, 1), + (155, 31, 34, 1), + (155, 255, 258, 1), + (155, 256, 259, 1), + (155, 65535, 65538, 1), + (156, 0, 3, 0), + (156, 1, 4, 0), + (156, 2, 5, 0), + (156, 3, 6, 0), + (156, 4, 7, 0), + (156, 5, 8, 0), + (156, 7, 10, 0), + (156, 8, 11, 0), + (156, 15, 18, 0), + (156, 16, 19, 0), + (156, 31, 34, 0), + (156, 255, 258, 0), + (156, 256, 259, 0), + (156, 65535, 65538, 0), + (157, 0, 3, 1), + (157, 1, 3, 1), + (157, 2, 3, 1), + (157, 3, 3, 1), + (157, 4, 3, 1), + (157, 5, 3, 1), + (157, 7, 3, 1), + (157, 8, 3, 1), + (157, 15, 3, 1), + (157, 16, 3, 1), + (157, 31, 3, 1), + (157, 255, 3, 1), + (157, 256, 3, 1), + (157, 65535, 3, 1), + (158, 0, 3, 1), + (158, 1, 3, 1), + (158, 2, 3, 1), + (158, 3, 3, 1), + (158, 4, 3, 1), + (158, 5, 3, 1), + (158, 7, 3, 1), + (158, 8, 3, 1), + (158, 15, 3, 1), + (158, 16, 3, 1), + (158, 31, 3, 1), + (158, 255, 3, 1), + (158, 256, 3, 1), + (158, 65535, 3, 1), + (159, 0, 2, 1), + (159, 1, 3, 1), + (159, 2, 4, 1), + (159, 3, 5, 1), + (159, 4, 6, 1), + (159, 5, 7, 1), + (159, 7, 9, 1), + (159, 8, 10, 1), + (159, 15, 17, 1), + (159, 16, 18, 1), + (159, 31, 33, 1), + (159, 255, 257, 1), + (159, 256, 258, 1), + (159, 65535, 65537, 1), + (160, 0, 2, 1), + (160, 1, 3, 1), + (160, 2, 4, 1), + (160, 3, 5, 1), + (160, 4, 6, 1), + (160, 5, 7, 1), + (160, 7, 9, 1), + (160, 8, 10, 1), + (160, 15, 17, 1), + (160, 16, 18, 1), + (160, 31, 33, 1), + (160, 255, 257, 1), + (160, 256, 258, 1), + (160, 65535, 65537, 1), + (161, 0, 2, 1), + (161, 1, 3, 1), + (161, 2, 4, 1), + (161, 3, 5, 1), + (161, 4, 6, 1), + (161, 5, 7, 1), + (161, 7, 9, 1), + (161, 8, 10, 1), + (161, 15, 17, 1), + (161, 16, 18, 1), + (161, 31, 33, 1), + (161, 255, 257, 1), + (161, 256, 258, 1), + (161, 65535, 65537, 1), + (162, 0, 2, 1), + (162, 1, 3, 1), + (162, 2, 4, 1), + (162, 3, 5, 1), + (162, 4, 6, 1), + (162, 5, 7, 1), + (162, 7, 9, 1), + (162, 8, 10, 1), + (162, 15, 17, 1), + (162, 16, 18, 1), + (162, 31, 33, 1), + (162, 255, 257, 1), + (162, 256, 258, 1), + (162, 65535, 65537, 1), + (163, 0, 2, 1), + (163, 1, 3, 1), + (163, 2, 4, 1), + (163, 3, 5, 1), + (163, 4, 6, 1), + (163, 5, 7, 1), + (163, 7, 9, 1), + (163, 8, 10, 1), + (163, 15, 17, 1), + (163, 16, 18, 1), + (163, 31, 33, 1), + (163, 255, 257, 1), + (163, 256, 258, 1), + (163, 65535, 65537, 1), + (164, 0, 2, 0), + (164, 1, 3, 0), + (164, 2, 4, 0), + (164, 3, 5, 0), + (164, 4, 6, 0), + (164, 5, 7, 0), + (164, 7, 9, 0), + (164, 8, 10, 0), + (164, 15, 17, 0), + (164, 16, 18, 0), + (164, 31, 33, 0), + (164, 255, 257, 0), + (164, 256, 258, 0), + (164, 65535, 65537, 0), + (165, 0, 2, 0), + (165, 1, 3, 0), + (165, 2, 4, 0), + (165, 3, 5, 0), + (165, 4, 6, 0), + (165, 5, 7, 0), + (165, 7, 9, 0), + (165, 8, 10, 0), + (165, 15, 17, 0), + (165, 16, 18, 0), + (165, 31, 33, 0), + (165, 255, 257, 0), + (165, 256, 258, 0), + (165, 65535, 65537, 0), + (166, 0, 3, 1), + (166, 1, 3, 1), + (166, 2, 3, 1), + (166, 3, 3, 1), + (166, 4, 3, 1), + (166, 5, 3, 1), + (166, 7, 3, 1), + (166, 8, 3, 1), + (166, 15, 3, 1), + (166, 16, 3, 1), + (166, 31, 3, 1), + (166, 255, 3, 1), + (166, 256, 3, 1), + (166, 65535, 3, 1), + (167, 0, 3, 1), + (167, 1, 3, 1), + (167, 2, 3, 1), + (167, 3, 3, 1), + (167, 4, 3, 1), + (167, 5, 3, 1), + (167, 7, 3, 1), + (167, 8, 3, 1), + (167, 15, 3, 1), + (167, 16, 3, 1), + (167, 31, 3, 1), + (167, 255, 3, 1), + (167, 256, 3, 1), + (167, 65535, 3, 1), + (168, 0, 3, 1), + (168, 1, 3, 1), + (168, 2, 3, 1), + (168, 3, 3, 1), + (168, 4, 3, 1), + (168, 5, 3, 1), + (168, 7, 3, 1), + (168, 8, 3, 1), + (168, 15, 3, 1), + (168, 16, 3, 1), + (168, 31, 3, 1), + (168, 255, 3, 1), + (168, 256, 3, 1), + (168, 65535, 3, 1), + (169, 0, 2, 1), + (169, 1, 2, 1), + (169, 2, 2, 1), + (169, 3, 2, 1), + (169, 4, 2, 1), + (169, 5, 2, 1), + (169, 7, 2, 1), + (169, 8, 2, 1), + (169, 15, 2, 1), + (169, 16, 2, 1), + (169, 31, 2, 1), + (169, 255, 2, 1), + (169, 256, 2, 1), + (169, 65535, 2, 1), + (170, 0, 2, 1), + (170, 1, 2, 1), + (170, 2, 2, 1), + (170, 3, 2, 1), + (170, 4, 2, 1), + (170, 5, 2, 1), + (170, 7, 2, 1), + (170, 8, 2, 1), + (170, 15, 2, 1), + (170, 16, 2, 1), + (170, 31, 2, 1), + (170, 255, 2, 1), + (170, 256, 2, 1), + (170, 65535, 2, 1), + (171, 0, 2, 1), + (171, 1, 2, 1), + (171, 2, 2, 1), + (171, 3, 2, 1), + (171, 4, 2, 1), + (171, 5, 2, 1), + (171, 7, 2, 1), + (171, 8, 2, 1), + (171, 15, 2, 1), + (171, 16, 2, 1), + (171, 31, 2, 1), + (171, 255, 2, 1), + (171, 256, 2, 1), + (171, 65535, 2, 1), + (172, 0, 2, 1), + (172, 1, 2, 1), + (172, 2, 2, 1), + (172, 3, 2, 1), + (172, 4, 2, 1), + (172, 5, 2, 1), + (172, 7, 2, 1), + (172, 8, 2, 1), + (172, 15, 2, 1), + (172, 16, 2, 1), + (172, 31, 2, 1), + (172, 255, 2, 1), + (172, 256, 2, 1), + (172, 65535, 2, 1), + (173, 0, 2, 1), + (173, 1, 2, 1), + (173, 2, 2, 1), + (173, 3, 2, 1), + (173, 4, 2, 1), + (173, 5, 2, 1), + (173, 7, 2, 1), + (173, 8, 2, 1), + (173, 15, 2, 1), + (173, 16, 2, 1), + (173, 31, 2, 1), + (173, 255, 2, 1), + (173, 256, 2, 1), + (173, 65535, 2, 1), + (174, 0, 2, 2), + (174, 1, 2, 2), + (174, 2, 2, 2), + (174, 3, 2, 2), + (174, 4, 2, 2), + (174, 5, 2, 2), + (174, 7, 2, 2), + (174, 8, 2, 2), + (174, 15, 2, 2), + (174, 16, 2, 2), + (174, 31, 2, 2), + (174, 255, 2, 2), + (174, 256, 2, 2), + (174, 65535, 2, 2), + (175, 0, 2, 3), + (175, 1, 2, 3), + (175, 2, 2, 3), + (175, 3, 2, 3), + (175, 4, 2, 3), + (175, 5, 2, 3), + (175, 7, 2, 3), + (175, 8, 2, 3), + (175, 15, 2, 3), + (175, 16, 2, 3), + (175, 31, 2, 3), + (175, 255, 2, 3), + (175, 256, 2, 3), + (175, 65535, 2, 3), + (176, 0, 2, 3), + (176, 1, 2, 3), + (176, 2, 2, 3), + (176, 3, 2, 3), + (176, 4, 2, 3), + (176, 5, 2, 3), + (176, 7, 2, 3), + (176, 8, 2, 3), + (176, 15, 2, 3), + (176, 16, 2, 3), + (176, 31, 2, 3), + (176, 255, 2, 3), + (176, 256, 2, 3), + (176, 65535, 2, 3), + (177, 0, 2, 3), + (177, 1, 2, 3), + (177, 2, 2, 3), + (177, 3, 2, 3), + (177, 4, 2, 3), + (177, 5, 2, 3), + (177, 7, 2, 3), + (177, 8, 2, 3), + (177, 15, 2, 3), + (177, 16, 2, 3), + (177, 31, 2, 3), + (177, 255, 2, 3), + (177, 256, 2, 3), + (177, 65535, 2, 3), + (178, 0, 2, 3), + (178, 1, 2, 3), + (178, 2, 2, 3), + (178, 3, 2, 3), + (178, 4, 2, 3), + (178, 5, 2, 3), + (178, 7, 2, 3), + (178, 8, 2, 3), + (178, 15, 2, 3), + (178, 16, 2, 3), + (178, 31, 2, 3), + (178, 255, 2, 3), + (178, 256, 2, 3), + (178, 65535, 2, 3), + (179, 0, 1, 2), + (179, 1, 1, 2), + (179, 2, 1, 2), + (179, 3, 1, 2), + (179, 4, 1, 2), + (179, 5, 1, 2), + (179, 7, 1, 2), + (179, 8, 1, 2), + (179, 15, 1, 2), + (179, 16, 1, 2), + (179, 31, 1, 2), + (179, 255, 1, 2), + (179, 256, 1, 2), + (179, 65535, 1, 2), + (180, 0, 1, 2), + (180, 1, 1, 2), + (180, 2, 1, 2), + (180, 3, 1, 2), + (180, 4, 1, 2), + (180, 5, 1, 2), + (180, 7, 1, 2), + (180, 8, 1, 2), + (180, 15, 1, 2), + (180, 16, 1, 2), + (180, 31, 1, 2), + (180, 255, 1, 2), + (180, 256, 1, 2), + (180, 65535, 1, 2), + (181, 0, 0, 0), + (181, 1, 0, 0), + (181, 2, 0, 0), + (181, 3, 0, 0), + (181, 4, 0, 0), + (181, 5, 0, 0), + (181, 7, 0, 0), + (181, 8, 0, 0), + (181, 15, 0, 0), + (181, 16, 0, 0), + (181, 31, 0, 0), + (181, 255, 0, 0), + (181, 256, 0, 0), + (181, 65535, 0, 0), + (182, 0, 0, 0), + (182, 1, 0, 0), + (182, 2, 0, 0), + (182, 3, 0, 0), + (182, 4, 0, 0), + (182, 5, 0, 0), + (182, 7, 0, 0), + (182, 8, 0, 0), + (182, 15, 0, 0), + (182, 16, 0, 0), + (182, 31, 0, 0), + (182, 255, 0, 0), + (182, 256, 0, 0), + (182, 65535, 0, 0), + (183, 0, 1, 1), + (183, 1, 1, 2), + (183, 2, 1, 1), + (183, 3, 1, 2), + (183, 4, 1, 1), + (183, 5, 1, 2), + (183, 7, 1, 2), + (183, 8, 1, 1), + (183, 15, 1, 2), + (183, 16, 1, 1), + (183, 31, 1, 2), + (183, 255, 1, 2), + (183, 256, 1, 1), + (183, 65535, 1, 2), + (184, 0, 1, 1), + (184, 1, 1, 2), + (184, 2, 1, 1), + (184, 3, 1, 2), + (184, 4, 1, 1), + (184, 5, 1, 2), + (184, 7, 1, 2), + (184, 8, 1, 1), + (184, 15, 1, 2), + (184, 16, 1, 1), + (184, 31, 1, 2), + (184, 255, 1, 2), + (184, 256, 1, 1), + (184, 65535, 1, 2), + (185, 0, 1, 0), + (185, 1, 1, 0), + (185, 2, 1, 0), + (185, 3, 1, 0), + (185, 4, 1, 0), + (185, 5, 1, 0), + (185, 7, 1, 0), + (185, 8, 1, 0), + (185, 15, 1, 0), + (185, 16, 1, 0), + (185, 31, 1, 0), + (185, 255, 1, 0), + (185, 256, 1, 0), + (185, 65535, 1, 0), + (186, 0, 1, 1), + (186, 1, 1, 2), + (186, 2, 1, 1), + (186, 3, 1, 2), + (186, 4, 1, 1), + (186, 5, 1, 2), + (186, 7, 1, 2), + (186, 8, 1, 1), + (186, 15, 1, 2), + (186, 16, 1, 1), + (186, 31, 1, 2), + (186, 255, 1, 2), + (186, 256, 1, 1), + (186, 65535, 1, 2), + (187, 0, 1, 2), + (187, 1, 1, 2), + (187, 2, 1, 2), + (187, 3, 1, 2), + (187, 4, 1, 2), + (187, 5, 1, 2), + (187, 7, 1, 2), + (187, 8, 1, 2), + (187, 15, 1, 2), + (187, 16, 1, 2), + (187, 31, 1, 2), + (187, 255, 1, 2), + (187, 256, 1, 2), + (187, 65535, 1, 2), + (188, 0, 1, 2), + (188, 1, 1, 2), + (188, 2, 1, 2), + (188, 3, 1, 2), + (188, 4, 1, 2), + (188, 5, 1, 2), + (188, 7, 1, 2), + (188, 8, 1, 2), + (188, 15, 1, 2), + (188, 16, 1, 2), + (188, 31, 1, 2), + (188, 255, 1, 2), + (188, 256, 1, 2), + (188, 65535, 1, 2), + (189, 0, 1, 2), + (189, 1, 1, 2), + (189, 2, 1, 2), + (189, 3, 1, 2), + (189, 4, 1, 2), + (189, 5, 1, 2), + (189, 7, 1, 2), + (189, 8, 1, 2), + (189, 15, 1, 2), + (189, 16, 1, 2), + (189, 31, 1, 2), + (189, 255, 1, 2), + (189, 256, 1, 2), + (189, 65535, 1, 2), + (190, 0, 1, 1), + (190, 1, 1, 2), + (190, 2, 1, 1), + (190, 3, 1, 2), + (190, 4, 1, 1), + (190, 5, 1, 2), + (190, 7, 1, 2), + (190, 8, 1, 1), + (190, 15, 1, 2), + (190, 16, 1, 1), + (190, 31, 1, 2), + (190, 255, 1, 2), + (190, 256, 1, 1), + (190, 65535, 1, 2), + (191, 0, 1, 1), + (191, 1, 1, 1), + (191, 2, 1, 1), + (191, 3, 1, 1), + (191, 4, 1, 1), + (191, 5, 1, 1), + (191, 7, 1, 1), + (191, 8, 1, 1), + (191, 15, 1, 1), + (191, 16, 1, 1), + (191, 31, 1, 1), + (191, 255, 1, 1), + (191, 256, 1, 1), + (191, 65535, 1, 1), + (192, 0, 1, 1), + (192, 1, 1, 1), + (192, 2, 1, 1), + (192, 3, 1, 1), + (192, 4, 1, 1), + (192, 5, 1, 1), + (192, 7, 1, 1), + (192, 8, 1, 1), + (192, 15, 1, 1), + (192, 16, 1, 1), + (192, 31, 1, 1), + (192, 255, 1, 1), + (192, 256, 1, 1), + (192, 65535, 1, 1), + (193, 0, 1, 0), + (193, 1, 1, 0), + (193, 2, 1, 0), + (193, 3, 1, 0), + (193, 4, 1, 0), + (193, 5, 1, 0), + (193, 7, 1, 0), + (193, 8, 1, 0), + (193, 15, 1, 0), + (193, 16, 1, 0), + (193, 31, 1, 0), + (193, 255, 1, 0), + (193, 256, 1, 0), + (193, 65535, 1, 0), + (194, 0, 1, 1), + (194, 1, 1, 2), + (194, 2, 1, 1), + (194, 3, 1, 2), + (194, 4, 1, 1), + (194, 5, 1, 2), + (194, 7, 1, 2), + (194, 8, 1, 1), + (194, 15, 1, 2), + (194, 16, 1, 1), + (194, 31, 1, 2), + (194, 255, 1, 2), + (194, 256, 1, 1), + (194, 65535, 1, 2), + (195, 0, 1, 1), + (195, 1, 1, 2), + (195, 2, 1, 1), + (195, 3, 1, 2), + (195, 4, 1, 1), + (195, 5, 1, 2), + (195, 7, 1, 2), + (195, 8, 1, 1), + (195, 15, 1, 2), + (195, 16, 1, 1), + (195, 31, 1, 2), + (195, 255, 1, 2), + (195, 256, 1, 1), + (195, 65535, 1, 2), + (196, 0, 0, 1), + (196, 1, 0, 2), + (196, 2, 0, 1), + (196, 3, 0, 2), + (196, 4, 0, 1), + (196, 5, 0, 2), + (196, 7, 0, 2), + (196, 8, 0, 1), + (196, 15, 0, 2), + (196, 16, 0, 1), + (196, 31, 0, 2), + (196, 255, 0, 2), + (196, 256, 0, 1), + (196, 65535, 0, 2), + (197, 0, 0, 1), + (197, 1, 0, 2), + (197, 2, 0, 1), + (197, 3, 0, 2), + (197, 4, 0, 1), + (197, 5, 0, 2), + (197, 7, 0, 2), + (197, 8, 0, 1), + (197, 15, 0, 2), + (197, 16, 0, 1), + (197, 31, 0, 2), + (197, 255, 0, 2), + (197, 256, 0, 1), + (197, 65535, 0, 2), + (198, 0, 3, 1), + (198, 1, 3, 1), + (198, 2, 3, 1), + (198, 3, 3, 1), + (198, 4, 3, 1), + (198, 5, 3, 1), + (198, 7, 3, 1), + (198, 8, 3, 1), + (198, 15, 3, 1), + (198, 16, 3, 1), + (198, 31, 3, 1), + (198, 255, 3, 1), + (198, 256, 3, 1), + (198, 65535, 3, 1), + (199, 0, 3, 2), + (199, 1, 3, 2), + (199, 2, 3, 2), + (199, 3, 3, 2), + (199, 4, 3, 2), + (199, 5, 3, 2), + (199, 7, 3, 2), + (199, 8, 3, 2), + (199, 15, 3, 2), + (199, 16, 3, 2), + (199, 31, 3, 2), + (199, 255, 3, 2), + (199, 256, 3, 2), + (199, 65535, 3, 2), + (200, 0, 0, 0), + (200, 1, 0, 0), + (200, 2, 0, 0), + (200, 3, 0, 0), + (200, 4, 0, 0), + (200, 5, 0, 0), + (200, 7, 0, 0), + (200, 8, 0, 0), + (200, 15, 0, 0), + (200, 16, 0, 0), + (200, 31, 0, 0), + (200, 255, 0, 0), + (200, 256, 0, 0), + (200, 65535, 0, 0), + (201, 0, 0, 0), + (201, 1, 0, 0), + (201, 2, 0, 0), + (201, 3, 0, 0), + (201, 4, 0, 0), + (201, 5, 0, 0), + (201, 7, 0, 0), + (201, 8, 0, 0), + (201, 15, 0, 0), + (201, 16, 0, 0), + (201, 31, 0, 0), + (201, 255, 0, 0), + (201, 256, 0, 0), + (201, 65535, 0, 0), + (202, 0, 3, 3), + (202, 1, 3, 3), + (202, 2, 3, 3), + (202, 3, 3, 3), + (202, 4, 3, 3), + (202, 5, 3, 3), + (202, 7, 3, 3), + (202, 8, 3, 3), + (202, 15, 3, 3), + (202, 16, 3, 3), + (202, 31, 3, 3), + (202, 255, 3, 3), + (202, 256, 3, 3), + (202, 65535, 3, 3), + (203, 0, 3, 2), + (203, 1, 3, 2), + (203, 2, 3, 2), + (203, 3, 3, 2), + (203, 4, 3, 2), + (203, 5, 3, 2), + (203, 7, 3, 2), + (203, 8, 3, 2), + (203, 15, 3, 2), + (203, 16, 3, 2), + (203, 31, 3, 2), + (203, 255, 3, 2), + (203, 256, 3, 2), + (203, 65535, 3, 2), + (204, 0, 3, 3), + (204, 1, 3, 3), + (204, 2, 3, 3), + (204, 3, 3, 3), + (204, 4, 3, 3), + (204, 5, 3, 3), + (204, 7, 3, 3), + (204, 8, 3, 3), + (204, 15, 3, 3), + (204, 16, 3, 3), + (204, 31, 3, 3), + (204, 255, 3, 3), + (204, 256, 3, 3), + (204, 65535, 3, 3), + (205, 0, 2, 0), + (205, 1, 2, 0), + (205, 2, 2, 0), + (205, 3, 2, 0), + (205, 4, 2, 0), + (205, 5, 2, 0), + (205, 7, 2, 0), + (205, 8, 2, 0), + (205, 15, 2, 0), + (205, 16, 2, 0), + (205, 31, 2, 0), + (205, 255, 2, 0), + (205, 256, 2, 0), + (205, 65535, 2, 0), + (206, 0, 2, 0), + (206, 1, 2, 0), + (206, 2, 2, 0), + (206, 3, 2, 0), + (206, 4, 2, 0), + (206, 5, 2, 0), + (206, 7, 2, 0), + (206, 8, 2, 0), + (206, 15, 2, 0), + (206, 16, 2, 0), + (206, 31, 2, 0), + (206, 255, 2, 0), + (206, 256, 2, 0), + (206, 65535, 2, 0), + (207, 0, 2, 0), + (207, 1, 2, 0), + (207, 2, 2, 0), + (207, 3, 2, 0), + (207, 4, 2, 0), + (207, 5, 2, 0), + (207, 7, 2, 0), + (207, 8, 2, 0), + (207, 15, 2, 0), + (207, 16, 2, 0), + (207, 31, 2, 0), + (207, 255, 2, 0), + (207, 256, 2, 0), + (207, 65535, 2, 0), + (208, 0, 3, 0), + (208, 1, 3, 0), + (208, 2, 3, 0), + (208, 3, 3, 0), + (208, 4, 3, 0), + (208, 5, 3, 0), + (208, 7, 3, 0), + (208, 8, 3, 0), + (208, 15, 3, 0), + (208, 16, 3, 0), + (208, 31, 3, 0), + (208, 255, 3, 0), + (208, 256, 3, 0), + (208, 65535, 3, 0), + (209, 0, 3, 0), + (209, 1, 3, 0), + (209, 2, 3, 0), + (209, 3, 3, 0), + (209, 4, 3, 0), + (209, 5, 3, 0), + (209, 7, 3, 0), + (209, 8, 3, 0), + (209, 15, 3, 0), + (209, 16, 3, 0), + (209, 31, 3, 0), + (209, 255, 3, 0), + (209, 256, 3, 0), + (209, 65535, 3, 0), + (210, 0, 1, 1), + (210, 1, 1, 1), + (210, 2, 1, 1), + (210, 3, 1, 1), + (210, 4, 1, 1), + (210, 5, 1, 1), + (210, 7, 1, 1), + (210, 8, 1, 1), + (210, 15, 1, 1), + (210, 16, 1, 1), + (210, 31, 1, 1), + (210, 255, 1, 1), + (210, 256, 1, 1), + (210, 65535, 1, 1), + (211, 0, 1, 1), + (211, 1, 1, 1), + (211, 2, 1, 1), + (211, 3, 1, 1), + (211, 4, 1, 1), + (211, 5, 1, 1), + (211, 7, 1, 1), + (211, 8, 1, 1), + (211, 15, 1, 1), + (211, 16, 1, 1), + (211, 31, 1, 1), + (211, 255, 1, 1), + (211, 256, 1, 1), + (211, 65535, 1, 1), + (212, 0, 1, 1), + (212, 1, 1, 1), + (212, 2, 1, 1), + (212, 3, 1, 1), + (212, 4, 1, 1), + (212, 5, 1, 1), + (212, 7, 1, 1), + (212, 8, 1, 1), + (212, 15, 1, 1), + (212, 16, 1, 1), + (212, 31, 1, 1), + (212, 255, 1, 1), + (212, 256, 1, 1), + (212, 65535, 1, 1), + (213, 0, 1, 1), + (213, 1, 1, 1), + (213, 2, 1, 1), + (213, 3, 1, 1), + (213, 4, 1, 1), + (213, 5, 1, 1), + (213, 7, 1, 1), + (213, 8, 1, 1), + (213, 15, 1, 1), + (213, 16, 1, 1), + (213, 31, 1, 1), + (213, 255, 1, 1), + (213, 256, 1, 1), + (213, 65535, 1, 1), + (214, 0, 1, 1), + (214, 1, 1, 1), + (214, 2, 1, 1), + (214, 3, 1, 1), + (214, 4, 1, 1), + (214, 5, 1, 1), + (214, 7, 1, 1), + (214, 8, 1, 1), + (214, 15, 1, 1), + (214, 16, 1, 1), + (214, 31, 1, 1), + (214, 255, 1, 1), + (214, 256, 1, 1), + (214, 65535, 1, 1), + (215, 0, 1, 1), + (215, 1, 1, 1), + (215, 2, 1, 1), + (215, 3, 1, 1), + (215, 4, 1, 1), + (215, 5, 1, 1), + (215, 7, 1, 1), + (215, 8, 1, 1), + (215, 15, 1, 1), + (215, 16, 1, 1), + (215, 31, 1, 1), + (215, 255, 1, 1), + (215, 256, 1, 1), + (215, 65535, 1, 1), + (216, 0, 1, 0), + (216, 1, 1, 1), + (216, 2, 1, 2), + (216, 3, 1, 3), + (216, 4, 1, 4), + (216, 5, 1, 5), + (216, 7, 1, 7), + (216, 8, 1, 8), + (216, 15, 1, 15), + (216, 16, 1, 16), + (216, 31, 1, 31), + (216, 255, 1, 255), + (216, 256, 1, 256), + (216, 65535, 1, 65535), + (217, 0, 1, 0), + (217, 1, 1, 1), + (217, 2, 1, 2), + (217, 3, 1, 3), + (217, 4, 1, 4), + (217, 5, 1, 5), + (217, 7, 1, 7), + (217, 8, 1, 8), + (217, 15, 1, 15), + (217, 16, 1, 16), + (217, 31, 1, 31), + (217, 255, 1, 255), + (217, 256, 1, 256), + (217, 65535, 1, 65535), + (218, 0, 1, 2), + (218, 1, 1, 2), + (218, 2, 1, 2), + (218, 3, 1, 2), + (218, 4, 1, 2), + (218, 5, 1, 2), + (218, 7, 1, 2), + (218, 8, 1, 2), + (218, 15, 1, 2), + (218, 16, 1, 2), + (218, 31, 1, 2), + (218, 255, 1, 2), + (218, 256, 1, 2), + (218, 65535, 1, 2), + (233, 0, 3, 2), + (233, 1, 3, 2), + (233, 2, 3, 2), + (233, 3, 3, 2), + (233, 4, 3, 2), + (233, 5, 3, 2), + (233, 7, 3, 2), + (233, 8, 3, 2), + (233, 15, 3, 2), + (233, 16, 3, 2), + (233, 31, 3, 2), + (233, 255, 3, 2), + (233, 256, 3, 2), + (233, 65535, 3, 2), + (234, 0, 2, 0), + (234, 1, 2, 0), + (234, 2, 2, 0), + (234, 3, 2, 0), + (234, 4, 2, 0), + (234, 5, 2, 0), + (234, 7, 2, 0), + (234, 8, 2, 0), + (234, 15, 2, 0), + (234, 16, 2, 0), + (234, 31, 2, 0), + (234, 255, 2, 0), + (234, 256, 2, 0), + (234, 65535, 2, 0), + (235, 0, 3, 1), + (235, 1, 3, 1), + (235, 2, 3, 1), + (235, 3, 3, 1), + (235, 4, 3, 1), + (235, 5, 3, 1), + (235, 7, 3, 1), + (235, 8, 3, 1), + (235, 15, 3, 1), + (235, 16, 3, 1), + (235, 31, 3, 1), + (235, 255, 3, 1), + (235, 256, 3, 1), + (235, 65535, 3, 1), + (236, 0, 2, 3), + (236, 1, 2, 3), + (236, 2, 2, 3), + (236, 3, 2, 3), + (236, 4, 2, 3), + (236, 5, 2, 3), + (236, 7, 2, 3), + (236, 8, 2, 3), + (236, 15, 2, 3), + (236, 16, 2, 3), + (236, 31, 2, 3), + (236, 255, 2, 3), + (236, 256, 2, 3), + (236, 65535, 2, 3), + (237, 0, 0, 0), + (237, 1, 0, 0), + (237, 2, 0, 0), + (237, 3, 0, 0), + (237, 4, 0, 0), + (237, 5, 0, 0), + (237, 7, 0, 0), + (237, 8, 0, 0), + (237, 15, 0, 0), + (237, 16, 0, 0), + (237, 31, 0, 0), + (237, 255, 0, 0), + (237, 256, 0, 0), + (237, 65535, 0, 0), + (238, 0, 0, 0), + (238, 1, 0, 0), + (238, 2, 0, 0), + (238, 3, 0, 0), + (238, 4, 0, 0), + (238, 5, 0, 0), + (238, 7, 0, 0), + (238, 8, 0, 0), + (238, 15, 0, 0), + (238, 16, 0, 0), + (238, 31, 0, 0), + (238, 255, 0, 0), + (238, 256, 0, 0), + (238, 65535, 0, 0), + (239, 0, 0, 0), + (239, 1, 0, 0), + (239, 2, 0, 0), + (239, 3, 0, 0), + (239, 4, 0, 0), + (239, 5, 0, 0), + (239, 7, 0, 0), + (239, 8, 0, 0), + (239, 15, 0, 0), + (239, 16, 0, 0), + (239, 31, 0, 0), + (239, 255, 0, 0), + (239, 256, 0, 0), + (239, 65535, 0, 0), + (240, 0, 1, 0), + (240, 1, 1, 0), + (240, 2, 1, 0), + (240, 3, 1, 0), + (240, 4, 1, 0), + (240, 5, 1, 0), + (240, 7, 1, 0), + (240, 8, 1, 0), + (240, 15, 1, 0), + (240, 16, 1, 0), + (240, 31, 1, 0), + (240, 255, 1, 0), + (240, 256, 1, 0), + (240, 65535, 1, 0), + (241, 0, 1, 0), + (241, 1, 1, 0), + (241, 2, 1, 0), + (241, 3, 1, 0), + (241, 4, 1, 0), + (241, 5, 1, 0), + (241, 7, 1, 0), + (241, 8, 1, 0), + (241, 15, 1, 0), + (241, 16, 1, 0), + (241, 31, 1, 0), + (241, 255, 1, 0), + (241, 256, 1, 0), + (241, 65535, 1, 0), + (242, 0, 1, 0), + (242, 1, 1, 0), + (242, 2, 1, 0), + (242, 3, 1, 0), + (242, 4, 1, 0), + (242, 5, 1, 0), + (242, 7, 1, 0), + (242, 8, 1, 0), + (242, 15, 1, 0), + (242, 16, 1, 0), + (242, 31, 1, 0), + (242, 255, 1, 0), + (242, 256, 1, 0), + (242, 65535, 1, 0), + (243, 0, 1, 0), + (243, 1, 1, 0), + (243, 2, 1, 0), + (243, 3, 1, 0), + (243, 4, 1, 0), + (243, 5, 1, 0), + (243, 7, 1, 0), + (243, 8, 1, 0), + (243, 15, 1, 0), + (243, 16, 1, 0), + (243, 31, 1, 0), + (243, 255, 1, 0), + (243, 256, 1, 0), + (243, 65535, 1, 0), + (244, 0, 0, 0), + (244, 1, 0, 0), + (244, 2, 0, 0), + (244, 3, 0, 0), + (244, 4, 0, 0), + (244, 5, 0, 0), + (244, 7, 0, 0), + (244, 8, 0, 0), + (244, 15, 0, 0), + (244, 16, 0, 0), + (244, 31, 0, 0), + (244, 255, 0, 0), + (244, 256, 0, 0), + (244, 65535, 0, 0), + (245, 0, 1, 1), + (245, 1, 1, 1), + (245, 2, 1, 1), + (245, 3, 1, 1), + (245, 4, 1, 1), + (245, 5, 1, 1), + (245, 7, 1, 1), + (245, 8, 1, 1), + (245, 15, 1, 1), + (245, 16, 1, 1), + (245, 31, 1, 1), + (245, 255, 1, 1), + (245, 256, 1, 1), + (245, 65535, 1, 1), + (246, 0, 1, 1), + (246, 1, 1, 1), + (246, 2, 1, 1), + (246, 3, 1, 1), + (246, 4, 1, 1), + (246, 5, 1, 1), + (246, 7, 1, 1), + (246, 8, 1, 1), + (246, 15, 1, 1), + (246, 16, 1, 1), + (246, 31, 1, 1), + (246, 255, 1, 1), + (246, 256, 1, 1), + (246, 65535, 1, 1), + (247, 0, 2, 0), + (247, 1, 2, 0), + (247, 2, 2, 0), + (247, 3, 2, 0), + (247, 4, 2, 0), + (247, 5, 2, 0), + (247, 7, 2, 0), + (247, 8, 2, 0), + (247, 15, 2, 0), + (247, 16, 2, 0), + (247, 31, 2, 0), + (247, 255, 2, 0), + (247, 256, 2, 0), + (247, 65535, 2, 0), + (248, 0, 3, 1), + (248, 1, 3, 2), + (248, 2, 3, 1), + (248, 3, 3, 2), + (248, 4, 3, 1), + (248, 5, 3, 2), + (248, 7, 3, 2), + (248, 8, 3, 1), + (248, 15, 3, 2), + (248, 16, 3, 1), + (248, 31, 3, 2), + (248, 255, 3, 2), + (248, 256, 3, 1), + (248, 65535, 3, 2), + (249, 0, 2, 1), + (249, 1, 3, 1), + (249, 2, 4, 1), + (249, 3, 5, 1), + (249, 4, 6, 1), + (249, 5, 7, 1), + (249, 7, 9, 1), + (249, 8, 10, 1), + (249, 15, 17, 1), + (249, 16, 18, 1), + (249, 31, 33, 1), + (249, 255, 257, 1), + (249, 256, 258, 1), + (249, 65535, 65537, 1), + (250, 0, 3, 1), + (250, 1, 4, 1), + (250, 2, 5, 1), + (250, 3, 6, 1), + (250, 4, 7, 1), + (250, 5, 8, 1), + (250, 7, 10, 1), + (250, 8, 11, 1), + (250, 15, 18, 1), + (250, 16, 19, 1), + (250, 31, 34, 1), + (250, 255, 258, 1), + (250, 256, 259, 1), + (250, 65535, 65538, 1), + (251, 0, 4, 1), + (251, 1, 4, 1), + (251, 2, 4, 1), + (251, 3, 4, 1), + (251, 4, 4, 1), + (251, 5, 4, 1), + (251, 7, 4, 1), + (251, 8, 4, 1), + (251, 15, 4, 1), + (251, 16, 4, 1), + (251, 31, 4, 1), + (251, 255, 4, 1), + (251, 256, 4, 1), + (251, 65535, 4, 1), + (252, 0, 0, 0), + (252, 1, 0, 0), + (252, 2, 0, 0), + (252, 3, 0, 0), + (252, 4, 0, 0), + (252, 5, 0, 0), + (252, 7, 0, 0), + (252, 8, 0, 0), + (252, 15, 0, 0), + (252, 16, 0, 0), + (252, 31, 0, 0), + (252, 255, 0, 0), + (252, 256, 0, 0), + (252, 65535, 0, 0), + (253, 0, 0, 0), + (253, 1, 0, 0), + (253, 2, 0, 0), + (253, 3, 0, 0), + (253, 4, 0, 0), + (253, 5, 0, 0), + (253, 7, 0, 0), + (253, 8, 0, 0), + (253, 15, 0, 0), + (253, 16, 0, 0), + (253, 31, 0, 0), + (253, 255, 0, 0), + (253, 256, 0, 0), + (253, 65535, 0, 0), + (254, 0, 0, 0), + (254, 1, 0, 0), + (254, 2, 0, 0), + (254, 3, 0, 0), + (254, 4, 0, 0), + (254, 5, 0, 0), + (254, 7, 0, 0), + (254, 8, 0, 0), + (254, 15, 0, 0), + (254, 16, 0, 0), + (254, 31, 0, 0), + (254, 255, 0, 0), + (254, 256, 0, 0), + (254, 65535, 0, 0), + (255, 0, 0, 0), + (255, 1, 0, 0), + (255, 2, 0, 0), + (255, 3, 0, 0), + (255, 4, 0, 0), + (255, 5, 0, 0), + (255, 7, 0, 0), + (255, 8, 0, 0), + (255, 15, 0, 0), + (255, 16, 0, 0), + (255, 31, 0, 0), + (255, 255, 0, 0), + (255, 256, 0, 0), + (255, 65535, 0, 0), + (256, 0, 0, 0), + (256, 1, 0, 0), + (256, 2, 0, 0), + (256, 3, 0, 0), + (256, 4, 0, 0), + (256, 5, 0, 0), + (256, 7, 0, 0), + (256, 8, 0, 0), + (256, 15, 0, 0), + (256, 16, 0, 0), + (256, 31, 0, 0), + (256, 255, 0, 0), + (256, 256, 0, 0), + (256, 65535, 0, 0), + (257, 0, 0, 0), + (257, 1, 0, 0), + (257, 2, 0, 0), + (257, 3, 0, 0), + (257, 4, 0, 0), + (257, 5, 0, 0), + (257, 7, 0, 0), + (257, 8, 0, 0), + (257, 15, 0, 0), + (257, 16, 0, 0), + (257, 31, 0, 0), + (257, 255, 0, 0), + (257, 256, 0, 0), + (257, 65535, 0, 0), + (258, 0, 1, 1), + (258, 1, 1, 1), + (258, 2, 1, 1), + (258, 3, 1, 1), + (258, 4, 1, 1), + (258, 5, 1, 1), + (258, 7, 1, 1), + (258, 8, 1, 1), + (258, 15, 1, 1), + (258, 16, 1, 1), + (258, 31, 1, 1), + (258, 255, 1, 1), + (258, 256, 1, 1), + (258, 65535, 1, 1), + (259, 0, 1, 1), + (259, 1, 1, 1), + (259, 2, 1, 1), + (259, 3, 1, 1), + (259, 4, 1, 1), + (259, 5, 1, 1), + (259, 7, 1, 1), + (259, 8, 1, 1), + (259, 15, 1, 1), + (259, 16, 1, 1), + (259, 31, 1, 1), + (259, 255, 1, 1), + (259, 256, 1, 1), + (259, 65535, 1, 1), + (260, 0, 0, 0), + (260, 1, 0, 0), + (260, 2, 0, 0), + (260, 3, 0, 0), + (260, 4, 0, 0), + (260, 5, 0, 0), + (260, 7, 0, 0), + (260, 8, 0, 0), + (260, 15, 0, 0), + (260, 16, 0, 0), + (260, 31, 0, 0), + (260, 255, 0, 0), + (260, 256, 0, 0), + (260, 65535, 0, 0), + (261, 0, 0, 1), + (261, 1, 0, 1), + (261, 2, 0, 1), + (261, 3, 0, 1), + (261, 4, 0, 1), + (261, 5, 0, 1), + (261, 7, 0, 1), + (261, 8, 0, 1), + (261, 15, 0, 1), + (261, 16, 0, 1), + (261, 31, 0, 1), + (261, 255, 0, 1), + (261, 256, 0, 1), + (261, 65535, 0, 1), + (262, 0, 0, 0), + (262, 1, 0, 0), + (262, 2, 0, 0), + (262, 3, 0, 0), + (262, 4, 0, 0), + (262, 5, 0, 0), + (262, 7, 0, 0), + (262, 8, 0, 0), + (262, 15, 0, 0), + (262, 16, 0, 0), + (262, 31, 0, 0), + (262, 255, 0, 0), + (262, 256, 0, 0), + (262, 65535, 0, 0), + (263, 0, 0, 2), + (263, 1, 0, 2), + (263, 2, 0, 2), + (263, 3, 0, 2), + (263, 4, 0, 2), + (263, 5, 0, 2), + (263, 7, 0, 2), + (263, 8, 0, 2), + (263, 15, 0, 2), + (263, 16, 0, 2), + (263, 31, 0, 2), + (263, 255, 0, 2), + (263, 256, 0, 2), + (263, 65535, 0, 2), + (264, 0, 0, 1), + (264, 1, 0, 1), + (264, 2, 0, 1), + (264, 3, 0, 1), + (264, 4, 0, 1), + (264, 5, 0, 1), + (264, 7, 0, 1), + (264, 8, 0, 1), + (264, 15, 0, 1), + (264, 16, 0, 1), + (264, 31, 0, 1), + (264, 255, 0, 1), + (264, 256, 0, 1), + (264, 65535, 0, 1), + (265, 0, 0, 1), + (265, 1, 0, 1), + (265, 2, 0, 1), + (265, 3, 0, 1), + (265, 4, 0, 1), + (265, 5, 0, 1), + (265, 7, 0, 1), + (265, 8, 0, 1), + (265, 15, 0, 1), + (265, 16, 0, 1), + (265, 31, 0, 1), + (265, 255, 0, 1), + (265, 256, 0, 1), + (265, 65535, 0, 1), + (266, 0, 1, 0), + (266, 1, 1, 0), + (266, 2, 1, 0), + (266, 3, 1, 0), + (266, 4, 1, 0), + (266, 5, 1, 0), + (266, 7, 1, 0), + (266, 8, 1, 0), + (266, 15, 1, 0), + (266, 16, 1, 0), + (266, 31, 1, 0), + (266, 255, 1, 0), + (266, 256, 1, 0), + (266, 65535, 1, 0), +]; diff --git a/crates/compiler-core/src/marshal.rs b/crates/compiler-core/src/marshal.rs index 1615c1d6a78..4e2edc80b56 100644 --- a/crates/compiler-core/src/marshal.rs +++ b/crates/compiler-core/src/marshal.rs @@ -554,6 +554,18 @@ fn read_const_value( Ok(value) } +/// Actual surviving code-field objects from the public marshal stream. +/// Execution bytecode is deliberately excluded: exported co_code is a separate +/// lazy cache, not the input byte object's retained identity. +pub struct CodeFieldObjects { + pub constants: V, + pub names: V, + pub localsplus_names: V, + pub localsplus_kinds: V, + pub linetable: V, + pub exceptiontable: V, +} + pub trait MarshalBag: Copy { type Value: Clone; type ConstantBag: ConstantBag; @@ -624,6 +636,18 @@ pub trait MarshalBag: Copy { self.make_code_with_constants(code, constants) } + /// Preserve actual field owners in runtime bags. Existing overrides keep + /// their original hook chain through the default implementation. + fn make_code_with_fields( + &self, + code: CodeObject<::Constant>, + constants: Vec, + code_bytes: Vec, + _fields: CodeFieldObjects, + ) -> Result { + self.make_code_with_constants_and_bytes(code, constants, code_bytes) + } + fn make_stop_iter(&self) -> Result; fn make_list(&self, it: impl Iterator) -> Result; @@ -958,17 +982,20 @@ fn deserialize_code_value_inner( .into_iter() .collect(); - let read_strings = - |rdr: &mut R, refs: &mut Vec>| -> Result> { - let tuple = deserialize_value_depth(rdr, bag, child_depth, refs)?; - bag.tuple_elements_from_value(&tuple) - .ok_or(MarshalError::BadType)? - .iter() - .map(|value| bag.str_from_value(value).ok_or(MarshalError::BadType)) - .collect() - }; - let names_raw = read_strings(rdr, refs)?; - let localsplusnames = read_strings(rdr, refs)?; + let read_strings = |rdr: &mut R, + refs: &mut Vec>| + -> Result<(Bag::Value, Vec)> { + let tuple = deserialize_value_depth(rdr, bag, child_depth, refs)?; + let names = bag + .tuple_elements_from_value(&tuple) + .ok_or(MarshalError::BadType)? + .iter() + .map(|value| bag.str_from_value(value).ok_or(MarshalError::BadType)) + .collect::>>()?; + Ok((tuple, names)) + }; + let (names_value, names_raw) = read_strings(rdr, refs)?; + let (localsplusnames_value, localsplusnames) = read_strings(rdr, refs)?; let kinds_value = deserialize_value_depth(rdr, bag, child_depth, refs)?; let localspluskinds = bag @@ -1050,7 +1077,19 @@ fn deserialize_code_value_inner( linetable, exceptiontable, }; - bag.make_code_with_constants_and_bytes(code, constant_values, code_bytes) + bag.make_code_with_fields( + code, + constant_values, + code_bytes, + CodeFieldObjects { + constants: consts_value, + names: names_value, + localsplus_names: localsplusnames_value, + localsplus_kinds: kinds_value, + linetable: linetable_value, + exceptiontable: exceptiontable_value, + }, + ) } fn deserialize_value_typed( @@ -1479,6 +1518,35 @@ pub fn serialize_code(buf: &mut W, code: &CodeObject) pub fn serialize_code_with( buf: &mut W, code: &CodeObject, + write_constant: impl FnMut(&mut W, &C) -> core::result::Result<(), E>, +) -> core::result::Result<(), E> { + serialize_code_with_fields(buf, code, |_, _| Ok(false), write_constant) +} + +/// Object-valued code fields that a runtime can route through its common writer. +/// Filename emission intentionally remains in the legacy scalar sequence. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CodeField { + Bytecode, + Constants, + Names, + LocalsPlusNames, + LocalsPlusKinds, + Name, + Qualname, + LineTable, + ExceptionTable, +} + +/// Serialize code with optional actual-object field routing. +/// +/// Returning true means the callback completely wrote that field. A false +/// result selects the legacy compiler/frozen representation. The callback runs +/// before any fallback bytes or temporary name vectors are computed. +pub fn serialize_code_with_fields( + buf: &mut W, + code: &CodeObject, + mut write_field: impl FnMut(&mut W, CodeField) -> core::result::Result, mut write_constant: impl FnMut(&mut W, &C) -> core::result::Result<(), E>, ) -> core::result::Result<(), E> { // 1–5: scalar fields @@ -1489,57 +1557,80 @@ pub fn serialize_code_with( buf.write_u32(code.flags.bits()); // 6: co_code (TYPE_STRING) — bytecode already uses flat localsplus indices - let bytecode = code.instructions.original_bytes(); - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &bytecode); + if !write_field(buf, CodeField::Bytecode)? { + let bytecode = code.instructions.original_bytes(); + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &bytecode); + } // 7: co_consts (TYPE_TUPLE) - buf.write_u8(Type::Tuple as u8); - write_len(buf, code.constants.len()); - for constant in &*code.constants { - write_constant(buf, constant)?; + if !write_field(buf, CodeField::Constants)? { + buf.write_u8(Type::Tuple as u8); + write_len(buf, code.constants.len()); + for constant in &*code.constants { + write_constant(buf, constant)?; + } } // 8: co_names (tuple of strings) - write_marshal_name_tuple(buf, &code.names); + if !write_field(buf, CodeField::Names)? { + write_marshal_name_tuple(buf, &code.names); + } // 9: co_localsplusnames — varnames ++ cell_only ++ freevars - let cell_only_names: Vec<&str> = code - .cellvars - .iter() - .filter(|cv| !code.varnames.iter().any(|v| v.as_ref() == cv.as_ref())) - .map(|cv| cv.as_ref()) - .collect(); - let total_lp_count = code.varnames.len() + cell_only_names.len() + code.freevars.len(); - buf.write_u8(Type::Tuple as u8); - write_len(buf, total_lp_count); - for n in &code.varnames { - write_marshal_str(buf, n.as_ref()); - } - for &n in &cell_only_names { - write_marshal_str(buf, n); - } - for n in &code.freevars { - write_marshal_str(buf, n.as_ref()); + if !write_field(buf, CodeField::LocalsPlusNames)? { + let cell_only_names: Vec<&str> = code + .cellvars + .iter() + .filter(|cv| !code.varnames.iter().any(|v| v.as_ref() == cv.as_ref())) + .map(|cv| cv.as_ref()) + .collect(); + let total_lp_count = code.varnames.len() + cell_only_names.len() + code.freevars.len(); + buf.write_u8(Type::Tuple as u8); + write_len(buf, total_lp_count); + for n in &code.varnames { + write_marshal_str(buf, n.as_ref()); + } + for &n in &cell_only_names { + write_marshal_str(buf, n); + } + for n in &code.freevars { + write_marshal_str(buf, n.as_ref()); + } } + // 10: co_localspluskinds — use the stored kinds directly - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &code.localspluskinds); + if !write_field(buf, CodeField::LocalsPlusKinds)? { + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &code.localspluskinds); + } // 11: co_filename write_marshal_str(buf, code.source_path.as_ref()); // 12: co_name - write_marshal_str(buf, code.obj_name.as_ref()); + if !write_field(buf, CodeField::Name)? { + write_marshal_str(buf, code.obj_name.as_ref()); + } + // 13: co_qualname - write_marshal_str(buf, code.qualname.as_ref()); + if !write_field(buf, CodeField::Qualname)? { + write_marshal_str(buf, code.qualname.as_ref()); + } + // 14: co_firstlineno buf.write_u32(code.first_line_number.map_or(0, |x| x.get() as _)); // 15: co_linetable - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &code.linetable); + if !write_field(buf, CodeField::LineTable)? { + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &code.linetable); + } + // 16: co_exceptiontable - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &code.exceptiontable); + if !write_field(buf, CodeField::ExceptionTable)? { + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &code.exceptiontable); + } + Ok(()) } @@ -1878,7 +1969,7 @@ mod tests { use super::*; use crate::bytecode::{BasicBag, ConstantData}; - fn hex_to_bytes(hex: &str) -> Vec { + pub(super) fn hex_to_bytes(hex: &str) -> Vec { (0..hex.len()) .step_by(2) .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) @@ -1988,3 +2079,199 @@ mod tests { assert_eq!(code.constants.len(), 2); } } + +#[cfg(test)] +mod field_hook_tests { + use super::tests::hex_to_bytes; + use super::*; + use core::cell::Cell; + + #[derive(Clone, Copy)] + struct HookBag<'a>(&'a Cell); + + impl MarshalBag for HookBag<'_> { + type Value = (); + type ConstantBag = BasicBag; + fn make_bool(&self, _: bool) {} + fn make_none(&self) {} + fn make_ellipsis(&self) {} + fn make_float(&self, _: f64) {} + fn make_complex(&self, _: Complex64) {} + fn make_str(&self, _: &Wtf8) {} + fn make_bytes(&self, _: &[u8]) {} + fn make_int(&self, _: BigInt) {} + fn make_tuple(&self, _: impl Iterator) {} + fn make_code(&self, _: CodeObject) -> Result<()> { + assert_eq!(self.0.get(), 3); + self.0.set(7); + Ok(()) + } + fn make_code_with_constants(&self, code: CodeObject, constants: Vec<()>) -> Result<()> { + assert_eq!(self.0.get(), 1); + assert_eq!(constants.len(), 2); + self.0.set(3); + self.make_code(code) + } + fn make_code_with_constants_and_bytes( + &self, + code: CodeObject, + constants: Vec<()>, + bytes: Vec, + ) -> Result<()> { + assert_eq!(self.0.get(), 0); + assert_eq!(bytes, [1, 2]); + self.0.set(1); + self.make_code_with_constants(code, constants) + } + fn make_stop_iter(&self) -> Result<()> { + Err(MarshalError::BadType) + } + fn make_list(&self, _: impl Iterator) -> Result<()> { + Err(MarshalError::BadType) + } + fn make_set(&self, _: impl Iterator) -> Result<()> { + Err(MarshalError::BadType) + } + fn make_frozenset(&self, _: impl Iterator) -> Result<()> { + Err(MarshalError::BadType) + } + fn make_dict(&self, _: impl Iterator) -> Result<()> { + Err(MarshalError::BadType) + } + fn constant_bag(self) -> BasicBag { + BasicBag + } + } + + fn writer_fixture() -> CodeObject { + CodeObject { + instructions: CodeUnits::try_from(&[][..]).unwrap(), + locations: Box::new([]), + flags: CodeFlags::empty(), + posonlyarg_count: 0, + arg_count: 0, + kwonlyarg_count: 0, + source_path: "code_storage.py".into(), + first_line_number: None, + max_stackdepth: 1, + obj_name: "sample".into(), + qualname: "sample".into(), + constants: core::iter::empty().collect(), + names: Box::new([]), + varnames: Box::new([]), + cellvars: Box::new([]), + freevars: Box::new([]), + localspluskinds: Box::new([]), + linetable: Box::new([]), + exceptiontable: Box::new([]), + } + } + + #[test] + fn code_field_default_preserves_complete_legacy_bytes() { + // Complete compiler/frozen payload from the pre-routing field sequence. + let code = writer_fixture(); + let expected = hex_to_bytes( + "0000000000000000000000000100000000000000730000000028000000002800000000280000000073000000007a0f636f64655f73746f726167652e70797a0673616d706c657a0673616d706c650000000073000000007300000000", + ); + let mut bytes = Vec::new(); + serialize_code(&mut bytes, &code); + assert_eq!(bytes, expected); + let mut visited = Vec::new(); + bytes.clear(); + serialize_code_with_fields( + &mut bytes, + &code, + |_, field| { + visited.push(field); + Ok::<_, ()>(false) + }, + |_, _| panic!("empty fixture has no constants"), + ) + .unwrap(); + assert_eq!(bytes, expected); + assert_eq!( + visited, + [ + CodeField::Bytecode, + CodeField::Constants, + CodeField::Names, + CodeField::LocalsPlusNames, + CodeField::LocalsPlusKinds, + CodeField::Name, + CodeField::Qualname, + CodeField::LineTable, + CodeField::ExceptionTable + ] + ); + } + + #[test] + fn code_field_error_stops_before_fallback_and_later_fields() { + let code = writer_fixture(); + let mut bytes = Vec::new(); + let mut visited = Vec::new(); + let result = serialize_code_with_fields( + &mut bytes, + &code, + |buf, field| { + visited.push(field); + if field == CodeField::Constants { + return Err(17); + } + buf.write_u8(0xaa); + Ok(true) + }, + |_, _| panic!("handled field must not reach constant fallback"), + ); + assert_eq!(result, Err(17)); + assert_eq!(visited, [CodeField::Bytecode, CodeField::Constants]); + assert_eq!( + &bytes[..20], + &hex_to_bytes("0000000000000000000000000100000000000000") + ); + assert_eq!(&bytes[20..], &[0xaa]); + } + + #[test] + fn actual_fields_default_preserves_existing_hook_chain() { + let code = CodeObject { + instructions: CodeUnits::try_from(&[][..]).unwrap(), + locations: Box::new([]), + flags: CodeFlags::empty(), + posonlyarg_count: 0, + arg_count: 0, + kwonlyarg_count: 0, + source_path: "code_storage.py".into(), + first_line_number: None, + max_stackdepth: 1, + obj_name: "sample".into(), + qualname: "sample".into(), + constants: core::iter::empty().collect(), + names: Box::new([]), + varnames: Box::new([]), + cellvars: Box::new([]), + freevars: Box::new([]), + localspluskinds: Box::new([]), + linetable: Box::new([]), + exceptiontable: Box::new([]), + }; + let calls = Cell::new(0); + HookBag(&calls) + .make_code_with_fields( + code, + alloc::vec![(), ()], + alloc::vec![1, 2], + CodeFieldObjects { + constants: (), + names: (), + localsplus_names: (), + localsplus_kinds: (), + linetable: (), + exceptiontable: (), + }, + ) + .unwrap(); + assert_eq!(calls.get(), 7); + } +} diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 499d7c8baa4..c0e63f29683 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -663,6 +663,21 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.stack.push(val); Ok(()) } + Instruction::LoadCommonConstant { idx } => { + let val = match idx.get(arg) { + oparg::CommonConstant::None => JitValue::None, + oparg::CommonConstant::True | oparg::CommonConstant::False => { + let value = i64::from(idx.get(arg) == oparg::CommonConstant::True); + JitValue::Bool(self.builder.ins().iconst(types::I8, value)) + } + oparg::CommonConstant::MinusOne => { + JitValue::Int(self.builder.ins().iconst(types::I64, -1)) + } + _ => return Err(JitCompileError::NotSupported), + }; + self.stack.push(val); + Ok(()) + } Instruction::LoadSmallInt { i } => { let small_int = i.get(arg) as i64; let val = self.builder.ins().iconst(types::I64, small_int); @@ -1508,3 +1523,46 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { res } } + +#[cfg(test)] +mod tests { + use super::*; + use bytecode::{BasicBag, ConstantData}; + + #[test] + fn common_constant_return_types_and_unsupported_domain() { + let frozen = rustpython_derive::py_compile!( + crate_name = "rustpython_compiler_core", + source = "def nothing():\n return None\ndef truth():\n return True\ndef falsehood():\n return False\ndef minus_one():\n return -1\ndef empty_string():\n return ''\n" + ); + let module = frozen.decode(BasicBag); + let mut checked = 0; + for constant in module.constants.iter() { + let ConstantData::Code { code } = constant else { + continue; + }; + let (common, return_type) = match code.obj_name.as_str() { + "nothing" => (oparg::CommonConstant::None, Some(JitType::None)), + "truth" => (oparg::CommonConstant::True, Some(JitType::Bool)), + "falsehood" => (oparg::CommonConstant::False, Some(JitType::Bool)), + "minus_one" => (oparg::CommonConstant::MinusOne, Some(JitType::Int)), + "empty_string" => (oparg::CommonConstant::EmptyStr, None), + name => panic!("unexpected fixture code: {name}"), + }; + let mut state = OpArgState::default(); + assert!(code.instructions.iter().copied().any(|unit| { + let (instruction, arg) = state.get(unit); + matches!(instruction, Instruction::LoadCommonConstant { idx } if idx.get(arg) == common) + })); + let result = crate::compile(code, &[], None); + if let Some(expected) = return_type { + let compiled = result.expect("supported common constant must compile"); + assert_eq!(compiled.sig.ret, Some(expected)); + } else { + assert!(matches!(result, Err(JitCompileError::NotSupported))); + } + checked += 1; + } + assert_eq!(checked, 5); + } +} diff --git a/crates/jit/tests/common.rs b/crates/jit/tests/common.rs index fe0e4bd33d4..0cbce428a0e 100644 --- a/crates/jit/tests/common.rs +++ b/crates/jit/tests/common.rs @@ -1,6 +1,6 @@ use core::ops::ControlFlow; use rustpython_compiler_core::bytecode::{ - CodeObject, ConstantData, Constants, Instruction, OpArg, OpArgState, + CodeObject, CommonConstant, ConstantData, Constants, Instruction, OpArg, OpArgState, }; use rustpython_jit::{CompiledCode, JitType}; use rustpython_wtf8::{Wtf8, Wtf8Buf}; @@ -207,6 +207,15 @@ impl StackMachine { Instruction::LoadConst { consti } => { self.stack.push(constants[consti.get(arg)].clone().into()) } + Instruction::LoadCommonConstant { idx } => { + self.stack.push(match idx.get(arg) { + CommonConstant::None => StackValue::None, + CommonConstant::EmptyStr => StackValue::String(String::new()), + constant => unimplemented!( + "common constant {constant:?} isn't yet supported in py_function!" + ), + }); + } Instruction::LoadName { namei } => self .stack .push(StackValue::String(names[namei.get(arg) as usize].clone())), diff --git a/crates/stdlib/src/_opcode.rs b/crates/stdlib/src/_opcode.rs index 6a3c500c537..badf0c166bb 100644 --- a/crates/stdlib/src/_opcode.rs +++ b/crates/stdlib/src/_opcode.rs @@ -4,15 +4,11 @@ pub(crate) use _opcode::module_def; mod _opcode { use crate::vm::{ AsObject, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyInt, PyIntRef}, - bytecode::{AnyOpcode, oparg}, + bytecode::{oparg, public_metadata::PublicOpcode}, }; - fn try_from_i32(raw: i32) -> Result { - u16::try_from(raw) - .map_err(|_| ())? - .try_into() - .map_err(|_| ()) + fn try_from_i32(raw: i32) -> Option { + PublicOpcode::from_i32(raw) } // prepare specialization @@ -25,114 +21,102 @@ mod _opcode { #[derive(FromArgs)] struct StackEffectArgs { #[pyarg(positional)] - opcode: PyIntRef, + opcode: PyObjectRef, #[pyarg(positional, optional)] oparg: Option, #[pyarg(named, optional)] jump: Option, } + // Convert only after FromArgs has checked arity and keywords, so rejected + // calls never invoke __index__. Keep these C API diagnostics local. + fn opcode_int(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + let value = obj.try_index(vm)?; + i32::try_from(value.as_bigint()) + .map_err(|_| vm.new_overflow_error("Python int too large to convert to C int")) + } + + #[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)] + fn operand_int(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + let value = obj.try_index(vm)?; + let value = core::ffi::c_long::try_from(value.as_bigint()) + .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?; + // Final _opcode.c narrows PyLong_AsLong's result to C int. This is the + // explicit low-bit policy for supported two's-complement target ABIs; + // it does not imply portable C signed-arithmetic overflow semantics. + Ok(value as i32) + } + #[pyfunction] fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult { - let oparg = args.oparg.map_or(Ok(0), |v| { - if !v.fast_isinstance(vm.ctx.types.int_type) { - return Err(vm.new_type_error(format!( - "'{}' object cannot be interpreted as an integer", - v.class().name() - ))); - } - v.downcast_ref::() - .ok_or_else(|| { - vm.new_type_error(format!( - "'{}' object cannot be interpreted as an integer", - v.class().name() - )) - })? - .try_to_primitive::(vm) - })?; - - let jump: Option = match args.jump { - Some(v) => { - if vm.is_none(&v) { - None - } else { - Some(v.try_to_bool(vm).map_err(|_| { - vm.new_value_error("stack_effect: jump must be False, True or None") - })?) - } - } - None => None, + // Retain every original argument owner through conversion, validation, + // effect selection and local error construction. Outer native transport + // and result wrapping keep their existing ownership behavior. + let opcode = opcode_int(&args.opcode, vm)?; + let oparg = match args.oparg.as_ref() { + Some(value) if !vm.is_none(value) => operand_int(value, vm)?, + _ => 0, }; - - let instr = args - .opcode - .try_to_primitive::(vm) - .and_then(|v| { - AnyOpcode::try_from(v) - .map_err(|_| vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned())) - }) - .map_err(|_| vm.new_value_error("invalid opcode or oparg"))?; - - // Raise ValueError if specialized. - if instr.real().is_some_and(|op| op.deopt().is_some()) { - return Err(vm.new_value_error("invalid opcode or oparg")); - } - - let effect = match jump { - Some(true) => instr.stack_effect_jump(oparg), - Some(false) => instr.stack_effect(oparg), - // jump=None: max of both paths (CPython convention) - None => instr - .stack_effect(oparg) - .max(instr.stack_effect_jump(oparg)), + let jump = match args.jump.as_ref() { + None => None, + Some(value) if vm.is_none(value) => None, + Some(value) if value.is(&vm.ctx.true_value) => Some(true), + Some(value) if value.is(&vm.ctx.false_value) => Some(false), + Some(_) => { + return Err(vm.new_value_error("stack_effect: jump must be False, True or None")); + } }; - Ok(effect) + PublicOpcode::from_i32(opcode) + // Preserve the signed C-int bit pattern at the generated API's + // existing u32 boundary; it immediately reinterprets it as i32. + .and_then(|opcode| opcode.stack_effect(oparg.cast_unsigned(), jump)) + .ok_or_else(|| vm.new_value_error("invalid opcode or oparg")) } #[derive(FromArgs)] struct OpcodeArg { #[pyarg(any)] - opcode: i32, + opcode: PyObjectRef, } #[pyfunction] - fn is_valid(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok() + fn is_valid(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some()) } #[pyfunction] - fn has_arg(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.has_arg()) + fn has_arg(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_arg())) } #[pyfunction] - fn has_const(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.has_const()) + fn has_const(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_const())) } #[pyfunction] - fn has_name(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.has_name()) + fn has_name(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_name())) } #[pyfunction] - fn has_jump(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.has_jump()) + fn has_jump(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_jump())) } #[pyfunction] - fn has_free(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.has_free()) + fn has_free(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_free())) } #[pyfunction] - fn has_local(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.has_local()) + fn has_local(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_local())) } #[pyfunction] - fn has_exc(OpcodeArg { opcode }: OpcodeArg) -> bool { - try_from_i32(opcode).is_ok_and(|op| op.is_block_push()) + fn has_exc(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { + Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.is_block_push())) } #[pyfunction] diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__bare_function_annotations_check_attribute_and_subscript_expressions.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__bare_function_annotations_check_attribute_and_subscript_expressions.snap index c0cac3bbb98..76a7e0d8871 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__bare_function_annotations_check_attribute_and_subscript_expressions.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__bare_function_annotations_check_attribute_and_subscript_expressions.snap @@ -11,7 +11,7 @@ expression: "dis(r#\"\ndef f(one: int):\n int.new_attr: int\n [list][0].ne MAKE_FUNCTION SET_FUNCTION_ATTRIBUTE 16 (annotate) STORE_NAME 0 (f) - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE Disassembly of ", line 1>: diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__const_no_op.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__const_no_op.snap index dc97f6b79c1..5713fb761d2 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__const_no_op.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__const_no_op.snap @@ -5,7 +5,7 @@ expression: "dis(r#\"\nx = not True\n\"#)" --- 0 RESUME 0 - 1 LOAD_CONST 2 (False) + 1 LOAD_COMMON_CONSTANT 10 (False) STORE_NAME 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__constant_true_if_pass_keeps_line_anchor_nop.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__constant_true_if_pass_keeps_line_anchor_nop.snap index 3de37ce2009..5cbdc2104d3 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__constant_true_if_pass_keeps_line_anchor_nop.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__constant_true_if_pass_keeps_line_anchor_nop.snap @@ -7,5 +7,5 @@ expression: "dis(r#\"\nif 1:\n pass\n\"#)" 1 NOP - 2 LOAD_CONST 1 (None) + 2 LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ands.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ands.snap index 5c58a2b6b85..0f3b62b3cdb 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ands.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ands.snap @@ -5,5 +5,5 @@ expression: "dis(r#\"\nif True and False and False:\n pass\n\"#)" --- 0 RESUME 0 - 1 LOAD_CONST 1 (None) + 1 LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_mixed.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_mixed.snap index 6bef04ee143..7da2fc67637 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_mixed.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_mixed.snap @@ -5,5 +5,5 @@ expression: "dis(r#\"\nif (True and False) or (False and True):\n pass\n\"#)" --- 0 RESUME 0 - 1 LOAD_CONST 1 (None) + 1 LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ors.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ors.snap index 065d893732e..d89e0a8d01d 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ors.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__if_ors.snap @@ -7,5 +7,5 @@ expression: "dis(r#\"\nif True or False or False:\n pass\n\"#)" 1 NOP - 2 LOAD_CONST 1 (None) + 2 LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_bool_op.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_bool_op.snap index 00eeb277455..3278d9a7bad 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_bool_op.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_bool_op.snap @@ -13,13 +13,13 @@ expression: "dis(r#\"\nx = Test() and False or False\n\"#)" POP_JUMP_IF_FALSE 11 (to L1) NOT_TAKEN POP_TOP - LOAD_CONST 0 (False) + LOAD_COMMON_CONSTANT 10 (False) COPY 1 TO_BOOL POP_JUMP_IF_TRUE 3 (to L2) NOT_TAKEN L1: POP_TOP - LOAD_CONST 0 (False) + LOAD_COMMON_CONSTANT 10 (False) L2: STORE_NAME 1 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_double_async_with.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_double_async_with.snap index 1c4b2ca9469..692ad6dc6ca 100644 --- a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_double_async_with.snap +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__nested_double_async_with.snap @@ -8,7 +8,7 @@ expression: "dis(r#\"\nasync def test():\n for stop_exc in (StopIteration('sp 1 LOAD_CONST 0 (", line 1>) MAKE_FUNCTION STORE_NAME 0 (test) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE Disassembly of ", line 1>: @@ -23,8 +23,8 @@ Disassembly of ", line 1>: LOAD_CONST 1 ('ham') CALL 1 BUILD_TUPLE 2 - GET_ITER - L2: FOR_ITER 71 (to L11) + GET_ITER 0 + L2: FOR_ITER 73 (to L11) STORE_FAST 0 (stop_exc) 3 LOAD_GLOBAL 4 (self) @@ -53,11 +53,12 @@ Disassembly of ", line 1>: LOAD_SPECIAL 2 (__aenter__) CALL 0 GET_AWAITABLE 1 - LOAD_CONST 3 (None) - L6: SEND 3 (to L9) + PUSH_NULL + LOAD_COMMON_CONSTANT 7 (None) + L6: SEND 4 (to L9) L7: YIELD_VALUE 1 L8: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L6) + JUMP_BACKWARD_NO_INTERRUPT 6 (to L6) L9: END_SEND L10: POP_TOP @@ -66,7 +67,7 @@ Disassembly of ", line 1>: 2 L11: END_FOR POP_ITER - LOAD_CONST 3 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE 5 L12: CLEANUP_THROW @@ -74,11 +75,12 @@ Disassembly of ", line 1>: L14: PUSH_EXC_INFO WITH_EXCEPT_START GET_AWAITABLE 2 - LOAD_CONST 3 (None) - L15: SEND 4 (to L19) + PUSH_NULL + LOAD_COMMON_CONSTANT 7 (None) + L15: SEND 5 (to L19) L16: YIELD_VALUE 1 L17: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L15) + JUMP_BACKWARD_NO_INTERRUPT 6 (to L15) L18: CLEANUP_THROW L19: END_SEND TO_BOOL @@ -102,86 +104,88 @@ Disassembly of ", line 1>: LOAD_ATTR 13 (fail + NULL|self) LOAD_FAST 0 (stop_exc) FORMAT_SIMPLE - LOAD_CONST 4 (' was suppressed') + LOAD_CONST 3 (' was suppressed') BUILD_STRING 2 CALL 1 POP_TOP - JUMP_FORWARD 45 (to L33) + JUMP_FORWARD 45 (to L35) -- L27: PUSH_EXC_INFO 7 LOAD_GLOBAL 14 (Exception) CHECK_EXC_MATCH - POP_JUMP_IF_FALSE 32 (to L31) - NOT_TAKEN - STORE_FAST 1 (ex) + POP_JUMP_IF_FALSE 32 (to L33) + L28: NOT_TAKEN + L29: STORE_FAST 1 (ex) - 8 L28: LOAD_GLOBAL 4 (self) + 8 L30: LOAD_GLOBAL 4 (self) LOAD_ATTR 17 (assertIs + NULL|self) LOAD_FAST_LOAD_FAST 16 (ex, stop_exc) CALL 2 POP_TOP - L29: POP_EXCEPT - LOAD_CONST 3 (None) + L31: POP_EXCEPT + LOAD_COMMON_CONSTANT 7 (None) STORE_FAST 1 (ex) DELETE_FAST 1 (ex) - JUMP_FORWARD 8 (to L33) + JUMP_FORWARD 8 (to L35) - -- L30: LOAD_CONST 3 (None) + -- L32: LOAD_COMMON_CONSTANT 7 (None) STORE_FAST 1 (ex) DELETE_FAST 1 (ex) RERAISE 1 - 7 L31: RERAISE 0 + 7 L33: RERAISE 0 - -- L32: COPY 3 + -- L34: COPY 3 POP_EXCEPT RERAISE 1 - 3 L33: LOAD_CONST 3 (None) - LOAD_CONST 3 (None) - LOAD_CONST 3 (None) + 3 L35: LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) CALL 3 POP_TOP - JUMP_BACKWARD 188 (to L2) - L34: PUSH_EXC_INFO + JUMP_BACKWARD 192 (to L2) + L36: PUSH_EXC_INFO WITH_EXCEPT_START TO_BOOL - POP_JUMP_IF_TRUE 2 (to L35) - NOT_TAKEN - RERAISE 2 - L35: POP_TOP - L36: POP_EXCEPT + POP_JUMP_IF_TRUE 2 (to L39) + L37: NOT_TAKEN + L38: RERAISE 2 + L39: POP_TOP + L40: POP_EXCEPT POP_TOP POP_TOP POP_TOP - JUMP_BACKWARD 205 (to L2) + JUMP_BACKWARD 209 (to L2) - -- L37: COPY 3 + -- L41: COPY 3 POP_EXCEPT RERAISE 1 - L38: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + L42: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) RERAISE 1 ExceptionTable: - L1 to L3 -> L38 [0] lasti - L3 to L4 -> L34 [3] lasti - L5 to L7 -> L27 [3] - L7 to L8 -> L12 [7] - L8 to L10 -> L27 [3] - L10 to L11 -> L14 [5] lasti - L11 to L12 -> L38 [0] lasti - L12 to L13 -> L27 [3] - L14 to L16 -> L24 [7] lasti - L16 to L17 -> L18 [10] - L17 to L20 -> L24 [7] lasti - L21 to L23 -> L24 [7] lasti - L23 to L25 -> L27 [3] - L26 to L27 -> L34 [3] lasti - L27 to L28 -> L32 [4] lasti - L28 to L29 -> L30 [4] lasti - L29 to L30 -> L34 [3] lasti - L30 to L32 -> L32 [4] lasti - L32 to L33 -> L34 [3] lasti - L33 to L34 -> L38 [0] lasti - L34 to L36 -> L37 [5] lasti - L36 to L38 -> L38 [0] lasti + L1 to L3 -> L42 [0] lasti + L3 to L4 -> L36 [4] lasti + L5 to L7 -> L27 [4] + L7 to L8 -> L12 [9] + L8 to L10 -> L27 [4] + L10 to L11 -> L14 [6] lasti + L11 to L12 -> L42 [0] lasti + L12 to L13 -> L27 [4] + L14 to L16 -> L24 [8] lasti + L16 to L17 -> L18 [12] + L17 to L20 -> L24 [8] lasti + L21 to L23 -> L24 [8] lasti + L23 to L25 -> L27 [4] + L26 to L27 -> L36 [4] lasti + L27 to L28 -> L34 [5] lasti + L29 to L30 -> L34 [5] lasti + L30 to L31 -> L32 [5] lasti + L31 to L32 -> L36 [4] lasti + L32 to L34 -> L34 [5] lasti + L34 to L35 -> L36 [4] lasti + L35 to L36 -> L42 [0] lasti + L36 to L37 -> L41 [6] lasti + L38 to L40 -> L41 [6] lasti + L40 to L42 -> L42 [0] lasti diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index 6cf1036ab11..aa755647370 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -1,8 +1,10 @@ //! Infamous code object. The python class `code` +mod compile_cache; + use super::descriptor::{MemberKind, MemberLayout}; use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, set::PyFrozenSet}; -use crate::common::lock::PyMutex; +use crate::common::lock::{OnceCell, PyMutex}; #[cfg(feature = "host_env")] use crate::convert::ToPyException; use crate::{ @@ -154,11 +156,11 @@ pub struct ReplaceArgs { #[pyarg(named, optional)] co_firstlineno: OptionalArg, #[pyarg(named, optional)] - co_consts: OptionalArg>, + co_consts: OptionalArg, #[pyarg(named, optional)] co_name: OptionalArg, #[pyarg(named, optional)] - co_names: OptionalArg>, + co_names: OptionalArg, #[pyarg(named, optional)] co_flags: OptionalArg, #[pyarg(named, optional)] @@ -256,7 +258,22 @@ fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> { }) } +/// Immutable tuple backing for runtime constants. Construction is private so +/// the only exposed slice always borrows a real tuple owner. +#[derive(Clone)] +pub struct LiteralTupleOwner(PyTupleRef); + +impl AsRef<[Literal]> for LiteralTupleOwner { + fn as_ref(&self) -> &[Literal] { + let elements = self.0.as_slice(); + // SAFETY: Literal is transparent over PyObjectRef and accepts every + // Python object. The immutable slice cannot outlive this tuple owner. + unsafe { &*(elements as *const [PyObjectRef] as *const [Literal]) } + } +} + impl Constant for Literal { + type TupleOwner = LiteralTupleOwner; type Name = &'static PyStrInterned; fn borrow_constant(&self) -> BorrowedConstant<'_, Self> { borrow_obj_constant(&self.0) @@ -357,6 +374,15 @@ pub(crate) type CodeObject = bytecode::CodeObject; pub trait IntoCodeObject { fn into_code_object(self, vm: &VirtualMachine) -> CodeObject; + + /// Finalize runtime fields before publication. Basic compilation overrides + /// this to share one private pool; frozen and Literal routes keep defaults. + fn into_pycode(self, vm: &VirtualMachine) -> PyCode + where + Self: Sized, + { + PyCode::new(self.into_code_object(vm), &vm.ctx) + } } impl IntoCodeObject for CodeObject { @@ -367,7 +393,13 @@ impl IntoCodeObject for CodeObject { impl IntoCodeObject for bytecode::CodeObject { fn into_code_object(self, vm: &VirtualMachine) -> CodeObject { - self.map_bag(PyVmBag(vm)) + // Raw callers receive pooled constants/nested bodies, but no promise + // of root assembly-field identity; use into_pycode for that boundary. + compile_cache::into_code_object(self, vm) + } + + fn into_pycode(self, vm: &VirtualMachine) -> PyCode { + compile_cache::into_pycode(self, vm) } } @@ -398,11 +430,26 @@ pub struct PyCode { #[pymember(name = "co_name", path = "obj_name")] #[pymember(name = "co_qualname", path = "qualname")] #[pymember(name = "co_flags", path = "flags")] + // Logical code fields are finalized by `new`. Execution may specialize or + // instrument instructions, but must not change the canonical public bytes. + // Callers replacing logical bytecode must construct a new PyCode instead. pub code: CodeObject, + /// Canonical bytes captured before execution can instrument the code. + canonical_code: Box<[u8]>, + /// The stable Python object exposed by co_code, allocated on first access. + code_bytes: OnceCell, /// Slot-indexed names, equivalent to CPython's `co_localsplusnames`. /// Derived once so frame-local proxy operations do not repeatedly scan /// merged cell variables. localsplus_names: Box<[&'static PyStrInterned]>, + names_tuple: PyTupleRef, + localsplus_names_tuple: PyTupleRef, + localsplus_kinds_bytes: PyBytesRef, + linetable_bytes: PyBytesRef, + exceptiontable_bytes: PyBytesRef, + varnames_tuple: OnceCell, + cellvars_tuple: OnceCell, + freevars_tuple: OnceCell, #[pymember(name = "co_filename")] source_path: AtomicPtr, #[pymember(name = "co_nlocals")] @@ -511,7 +558,53 @@ impl MemberLayout for CodeFlags { } impl PyCode { - pub fn new(code: CodeObject) -> Self { + /// Borrow the actual finalized owner, without reconstructing field values. + /// The caller keeps its canonical bytecode getter owner through the write. + pub(crate) fn marshal_field<'a>( + &'a self, + field: rustpython_compiler_core::marshal::CodeField, + bytecode: &'a PyBytesRef, + ) -> &'a PyObject { + use rustpython_compiler_core::marshal::CodeField; + match field { + CodeField::Bytecode => bytecode.as_object(), + CodeField::Constants => self + .code + .constants + .tuple_owner() + .expect("finalized constants") + .0 + .as_object(), + CodeField::Names => self.names_tuple.as_object(), + CodeField::LocalsPlusNames => self.localsplus_names_tuple.as_object(), + CodeField::LocalsPlusKinds => self.localsplus_kinds_bytes.as_object(), + CodeField::Name => self.code.obj_name.as_object(), + CodeField::Qualname => self.code.qualname.as_object(), + CodeField::LineTable => self.linetable_bytes.as_object(), + CodeField::ExceptionTable => self.exceptiontable_bytes.as_object(), + } + } + + /// Finalize a pristine code object before publishing it to the runtime. + /// + /// The input must not be cloned from an already instrumented PyCode: + /// generic monitoring wrappers need side tables that CodeObject does not + /// carry. Python `replace` reconstructs from canonical bytes instead. + pub fn new(mut code: CodeObject, ctx: &Context) -> Self { + if code.constants.tuple_owner().is_none() { + let boxed = core::mem::replace(&mut code.constants, core::iter::empty().collect()); + let tuple = ctx.new_tuple( + boxed + .into_boxed() + .into_vec() + .into_iter() + .map(|x| x.0) + .collect(), + ); + code.constants = bytecode::Constants::from_tuple(LiteralTupleOwner(tuple)); + } + code.instructions.normalize_get_iter(code.flags); + let canonical_code = code.instructions.original_bytes().into_boxed_slice(); let sp = code.source_path as *const PyStrInterned as *mut PyStrInterned; let nlocals = i32::try_from(code.varnames.len()).unwrap_or(i32::MAX); let localsplus_names = { @@ -547,8 +640,28 @@ impl PyCode { | Instruction::InstrumentedEndAsyncFor ) }); + let names_tuple = ctx.new_tuple(code.names.iter().map(|name| name.to_object()).collect()); + let localsplus_names_tuple = ctx.new_tuple( + localsplus_names + .iter() + .map(|name| name.to_object()) + .collect(), + ); + let localsplus_kinds_bytes = ctx.new_bytes(code.localspluskinds.to_vec()); + let linetable_bytes = ctx.new_bytes(code.linetable.to_vec()); + let exceptiontable_bytes = ctx.new_bytes(code.exceptiontable.to_vec()); Self { code, + names_tuple, + localsplus_names_tuple, + localsplus_kinds_bytes, + linetable_bytes, + exceptiontable_bytes, + varnames_tuple: OnceCell::new(), + cellvars_tuple: OnceCell::new(), + freevars_tuple: OnceCell::new(), + canonical_code, + code_bytes: OnceCell::new(), localsplus_names, source_path: AtomicPtr::new(sp), nlocals, @@ -559,6 +672,65 @@ impl PyCode { } } + /// Attach validated public-reader fields before publication. Superseded + /// boxed constants are dropped before the returned code can be observed. + pub(crate) fn from_marshaled_fields( + mut code: CodeObject, + fields: rustpython_compiler_core::marshal::CodeFieldObjects, + ctx: &Context, + ) -> Result { + use rustpython_compiler_core::marshal::MarshalError; + let constants = fields + .constants + .downcast::() + .map_err(|_| MarshalError::BadType)?; + let names = fields + .names + .downcast::() + .map_err(|_| MarshalError::BadType)?; + let locals = fields + .localsplus_names + .downcast::() + .map_err(|_| MarshalError::BadType)?; + let kinds = fields + .localsplus_kinds + .downcast::() + .map_err(|_| MarshalError::BadType)?; + let lines = fields + .linetable + .downcast::() + .map_err(|_| MarshalError::BadType)?; + let exceptions = fields + .exceptiontable + .downcast::() + .map_err(|_| MarshalError::BadType)?; + code.constants = bytecode::Constants::from_tuple(LiteralTupleOwner(constants)); + let mut result = Self::new(code, ctx); + result.retain_normalized_names(names, Some(locals)); + result.localsplus_kinds_bytes = kinds; + result.linetable_bytes = lines; + result.exceptiontable_bytes = exceptions; + Ok(result) + } + + fn retain_normalized_names(&mut self, names: PyTupleRef, locals: Option) { + let matches = |tuple: &PyTupleRef, canonical: &[&'static PyStrInterned]| { + tuple.len() == canonical.len() + && tuple + .iter() + .zip(canonical) + .all(|(item, name)| item.is(*name)) + }; + if matches(&names, &self.code.names) { + self.names_tuple = names; + } + if let Some(locals) = locals + && matches(&locals, &self.localsplus_names) + { + self.localsplus_names_tuple = locals; + } + } + #[inline(always)] pub(crate) fn localsplus_name(&self, index: usize) -> &'static PyStrInterned { // Bytecode operands and frame-proxy indices are validated against @@ -580,11 +752,19 @@ impl PyCode { } pub fn new_ref_with_bag(vm: &VirtualMachine, code: CodeObject) -> PyRef { - PyRef::new_ref(Self::new(code), vm.ctx.types.code_type.to_owned(), None) + PyRef::new_ref( + Self::new(code, &vm.ctx), + vm.ctx.types.code_type.to_owned(), + None, + ) } pub fn new_ref_from_bytecode(vm: &VirtualMachine, code: bytecode::CodeObject) -> PyRef { - Self::new_ref_with_bag(vm, code.map_bag(PyVmBag(vm))) + PyRef::new_ref( + code.into_pycode(vm), + vm.ctx.types.code_type.to_owned(), + None, + ) } pub fn new_ref_from_frozen>( @@ -884,16 +1064,7 @@ impl Constructor for PyCode { let instructions = CodeUnits::try_from(bytecode_bytes) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?; - // Convert constants - let constants = args - .consts - .as_slice() - .iter() - .map(|obj| { - // Convert PyObject to Literal constant. For now, just wrap it - Literal(obj.clone()) - }) - .collect(); + let constants = bytecode::Constants::from_tuple(LiteralTupleOwner(args.consts)); // Create locations (start and end pairs) let row = if args.firstlineno > 0 { @@ -955,7 +1126,10 @@ impl Constructor for PyCode { exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(), }; - Ok(Self::new(code)) + let mut result = Self::new(code, &vm.ctx); + result.linetable_bytes = args.linetable; + result.exceptiontable_bytes = args.exceptiontable; + Ok(result) } } @@ -973,12 +1147,12 @@ impl PyCode { impl Py { #[pygetset] pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef { - let cellvars = self - .cellvars - .iter() - .map(|name| name.to_pyobject(vm)) - .collect(); - vm.ctx.new_tuple(cellvars) + self.cellvars_tuple + .get_or_init(|| { + vm.ctx + .new_tuple(self.cellvars.iter().map(|name| name.to_object()).collect()) + }) + .clone() } #[pygetset] @@ -987,68 +1161,109 @@ impl Py { } #[pygetset] - fn co_consts(&self, vm: &VirtualMachine) -> PyTupleRef { - let consts = self.code.constants.iter().map(|x| x.0.clone()).collect(); - vm.ctx.new_tuple(consts) + fn co_consts(&self) -> PyTupleRef { + self.code + .constants + .tuple_owner() + .expect("finalized constants") + .0 + .clone() } #[pygetset] - fn co_names(&self, vm: &VirtualMachine) -> PyTupleRef { - let names = self - .code - .names - .deref() - .iter() - .map(|name| name.to_pyobject(vm)) - .collect(); - vm.ctx.new_tuple(names) + fn co_names(&self) -> PyTupleRef { + self.names_tuple.clone() } #[pygetset] pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef { - let varnames = self.code.varnames.iter().map(|s| s.to_object()).collect(); - vm.ctx.new_tuple(varnames) + self.varnames_tuple + .get_or_init(|| { + vm.ctx.new_tuple( + self.code + .varnames + .iter() + .map(|name| name.to_object()) + .collect(), + ) + }) + .clone() } #[pygetset] pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { - vm.ctx.new_bytes(self.code.instructions.original_bytes()) + self.code_bytes + .get_or_init(|| vm.ctx.new_bytes(self.canonical_code.to_vec())) + .clone() } #[pygetset] - pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { - // Return current (possibly quickened/specialized) bytecode - let bytes = unsafe { - core::slice::from_raw_parts( - self.code.instructions.as_ptr() as *const u8, - self.code.instructions.len() * 2, - ) + pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> PyResult { + let incompatible = || vm.new_value_error("incompatible adaptive bytecode layout"); + let legacy = + || vm.new_value_error("legacy private GET_YIELD_FROM_ITER has no public 3.15 encoding"); + let (canonical, []) = self.canonical_code.as_chunks::<2>() else { + return Err(incompatible()); }; - vm.ctx.new_bytes(bytes.to_vec()) + let instructions = &self.code.instructions; + if canonical.len() != instructions.unit_count() { + return Err(incompatible()); + } + let mut bytes = Vec::with_capacity(self.canonical_code.len()); + let mut index = 0; + while index < canonical.len() { + let base = Instruction::try_from(canonical[index][0]).map_err(|_| incompatible())?; + if matches!(base, Instruction::GetYieldFromIter) { + return Err(legacy()); + } + let next = index + .checked_add(1 + base.cache_entries()) + .filter(|&next| next <= canonical.len()) + .ok_or_else(incompatible)?; + // Boundaries come only from this code's immutable canonical backing. + // Never decode a cache payload or use a live monitoring wrapper's width. + let op = match instructions.read_op(index) { + op @ (Instruction::LoadConstImmortal | Instruction::LoadConstMortal) => { + op.deoptimize() + } + Instruction::GetYieldFromIter => return Err(legacy()), + op => op, + }; + bytes.push(u8::from(op)); + bytes.push(u8::from(instructions.read_arg(index))); + for cache in index + 1..next { + bytes.extend_from_slice(&instructions.read_cache_u16(cache).to_ne_bytes()); + } + index = next; + } + // Per-cell atomic observation, not a coherent cross-thread snapshot. + // This does not repair the interpreter's other live-storage access paths. + Ok(vm.ctx.new_bytes(bytes)) } #[pygetset] pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef { - let names = self - .code - .freevars - .deref() - .iter() - .map(|name| name.to_pyobject(vm)) - .collect(); - vm.ctx.new_tuple(names) + self.freevars_tuple + .get_or_init(|| { + vm.ctx.new_tuple( + self.code + .freevars + .iter() + .map(|name| name.to_object()) + .collect(), + ) + }) + .clone() } #[pygetset] - pub fn co_linetable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { - // Return the actual linetable from the code object - vm.ctx.new_bytes(self.code.linetable.to_vec()) + pub fn co_linetable(&self) -> PyBytesRef { + self.linetable_bytes.clone() } #[pygetset] - pub fn co_exceptiontable(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { - // Return the actual exception table from the code object - vm.ctx.new_bytes(self.code.exceptiontable.to_vec()) + pub fn co_exceptiontable(&self) -> PyBytesRef { + self.exceptiontable_bytes.clone() } // spell-checker: ignore lnotab @@ -1380,7 +1595,13 @@ impl Py { let constants = match co_consts { OptionalArg::Present(constants) => constants, - OptionalArg::Missing => self.code.constants.iter().map(|x| x.0.clone()).collect(), + OptionalArg::Missing => self + .code + .constants + .tuple_owner() + .expect("finalized constants") + .0 + .clone(), }; let obj_name = match co_name { @@ -1390,13 +1611,7 @@ impl Py { let names = match co_names { OptionalArg::Present(names) => names, - OptionalArg::Missing => self - .code - .names - .deref() - .iter() - .map(|name| name.to_pyobject(vm)) - .collect(), + OptionalArg::Missing => self.names_tuple.clone(), }; let flags = CodeFlags::from_bits_truncate(match co_flags { @@ -1427,7 +1642,10 @@ impl Py { CodeUnits::try_from(code_bytes.as_bytes()) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))? } - OptionalArg::Missing => self.code.instructions.clone(), + // Live instructions may contain monitoring wrappers whose original + // opcodes exist only in the source code object's side tables. + OptionalArg::Missing => CodeUnits::try_from(self.canonical_code.as_ref()) + .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?, }; let intern_all = |objs: Vec, field: &str| -> PyResult> { @@ -1482,15 +1700,13 @@ impl Py { // Handle linetable and exceptiontable let linetable = match co_linetable { - OptionalArg::Present(linetable) => linetable.as_bytes().to_vec().into_boxed_slice(), - OptionalArg::Missing => self.code.linetable.clone(), + OptionalArg::Present(linetable) => linetable, + OptionalArg::Missing => self.linetable_bytes.clone(), }; let exceptiontable = match co_exceptiontable { - OptionalArg::Present(exceptiontable) => { - exceptiontable.as_bytes().to_vec().into_boxed_slice() - } - OptionalArg::Missing => self.code.exceptiontable.clone(), + OptionalArg::Present(exceptiontable) => exceptiontable, + OptionalArg::Missing => self.exceptiontable_bytes.clone(), }; let new_code = CodeObject { @@ -1508,17 +1724,21 @@ impl Py { // FIXME: invalid locations. Actually locations is a duplication of linetable. // It can be removed once we move every other code to use linetable only. locations: self.code.locations.clone(), - constants: constants.into_iter().map(Literal).collect(), - names: intern_all(names, "co_names")?, + constants: bytecode::Constants::from_tuple(LiteralTupleOwner(constants)), + names: intern_all(names.as_slice().to_vec(), "co_names")?, varnames, cellvars, freevars, localspluskinds, - linetable, - exceptiontable, + linetable: linetable.as_bytes().into(), + exceptiontable: exceptiontable.as_bytes().into(), }; - Ok(PyCode::new(new_code)) + let mut result = PyCode::new(new_code, &vm.ctx); + result.retain_normalized_names(names, None); + result.linetable_bytes = linetable; + result.exceptiontable_bytes = exceptiontable; + Ok(result) } #[pymethod] @@ -1640,3 +1860,53 @@ impl<'a> LineTableReader<'a> { pub(crate) fn init(ctx: &'static Context) { PyCode::extend_class(ctx, ctx.types.code_type); } + +#[cfg(test)] +mod storage_tests { + use super::*; + + #[test] + fn tuple_constants_share_owner_and_copy_on_write() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let child: PyObjectRef = vm.ctx.new_list(vec![]).into(); + assert_eq!(child.strong_count(), 1); + let owner = vm.ctx.new_tuple(vec![child.clone()]); + let mut constants = bytecode::Constants::from_tuple(LiteralTupleOwner(owner.clone())); + assert_eq!(child.strong_count(), 2); + let shared = constants.clone(); + assert_eq!(child.strong_count(), 2); + constants[bytecode::oparg::ConstIdx::from_u32(0)] = Literal(vm.ctx.none()); + assert!(constants.tuple_owner().is_none()); + assert!(shared[bytecode::oparg::ConstIdx::from_u32(0)].0.is(&child)); + assert!(owner.as_slice()[0].is(&child)); + assert_eq!(child.strong_count(), 2); + drop(shared); + drop(owner); + assert_eq!(child.strong_count(), 1); + }); + } + + #[cfg(feature = "compiler")] + #[test] + fn published_code_has_one_constant_owner_representation() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let compiled = vm + .compile("pass", crate::compiler::Mode::Exec, "code_storage.py") + .unwrap(); + let child: PyObjectRef = vm.ctx.new_list(vec![]).into(); + let mut raw = compiled.code.clone(); + raw.constants = core::iter::once(Literal(child.clone())).collect(); + assert_eq!(child.strong_count(), 2); + let code = PyCode::new(raw, &vm.ctx); + assert_eq!(child.strong_count(), 2); + let owner = code.code.constants.tuple_owner().unwrap().0.clone(); + let cloned = code.code.clone(); + assert_eq!(child.strong_count(), 2); + drop(code); + drop(cloned); + assert_eq!(child.strong_count(), 2); + drop(owner); + assert_eq!(child.strong_count(), 1); + }); + } +} diff --git a/crates/vm/src/builtins/code/compile_cache.rs b/crates/vm/src/builtins/code/compile_cache.rs new file mode 100644 index 00000000000..6ea516eff2d --- /dev/null +++ b/crates/vm/src/builtins/code/compile_cache.rs @@ -0,0 +1,538 @@ +//! Compilation-private sharing of supported constants and surviving assembly fields. +//! +//! This sees the optimized Basic graph, not CPython's full first-winner chronology. +//! Code/NaN keys and whole slice subtrees deliberately remain unmerged. The pool is +//! never used by public readers, frozen decoding, constructors or replace(). + +use super::*; +use alloc::collections::BTreeMap; +use core::cell::RefCell; + +// Keys contain no Python owners. Bit keys preserve each signed-zero combination; +// NaNs are excluded because their identity cannot be represented by their bits. +#[derive(Eq, PartialEq, Ord, PartialOrd)] +enum Key { + None, + Ellipsis, + Bool(bool), + Int(BigInt), + Float(u64), + Complex(u64, u64), + Str(Vec), + Bytes(Vec), + Tuple(Vec), + FrozenSet(Vec), +} + +fn key(obj: &PyObject) -> Option { + use crate::builtins::{PyBool, PyBytes, PyComplex, PyEllipsis, PyFloat, PyInt, PyStr, PyTuple}; + if obj + .class() + .is(super::super::singletons::PyNone::static_type()) + { + Some(Key::None) + } else if obj.class().is(PyEllipsis::static_type()) { + Some(Key::Ellipsis) + } else if obj.class().is(PyBool::static_type()) { + Some(Key::Bool( + !obj.downcast_ref::()?.as_bigint().is_zero(), + )) + } else if obj.class().is(PyInt::static_type()) { + Some(Key::Int(obj.downcast_ref::()?.as_bigint().clone())) + } else if obj.class().is(PyFloat::static_type()) { + let value = obj.downcast_ref::()?.to_f64(); + (!value.is_nan()).then(|| Key::Float(value.to_bits())) + } else if obj.class().is(PyComplex::static_type()) { + let value = obj.downcast_ref::()?.as_complex(); + (!value.re.is_nan() && !value.im.is_nan()) + .then(|| Key::Complex(value.re.to_bits(), value.im.to_bits())) + } else if obj.class().is(PyStr::static_type()) { + Some(Key::Str( + obj.downcast_ref::()?.as_wtf8().as_bytes().to_vec(), + )) + } else if obj.class().is(PyBytes::static_type()) { + Some(Key::Bytes( + obj.downcast_ref::()?.as_bytes().to_vec(), + )) + } else if obj.class().is(PyTuple::static_type()) { + obj.downcast_ref::()? + .iter() + .map(|child| key(child)) + .collect::>>() + .map(Key::Tuple) + } else if obj.class().is(PyFrozenSet::static_type()) { + // Actual survivors matter: Python set equality can collapse unlike types. + // This scoped snapshot owns children only while producing Rust-only keys. + // Never use Literal::borrow_constant: its frozenset/slice adapters leak. + let elements = obj.downcast_ref::()?.elements(); + let mut keys = elements + .iter() + .map(|child| key(child)) + .collect::>>()?; + keys.sort_unstable(); + drop(elements); + Some(Key::FrozenSet(keys)) + } else { + None + } +} + +type Pool = RefCell>; + +#[derive(Clone, Copy)] +struct CompileBag<'a> { + vm: &'a VirtualMachine, + pool: &'a Pool, +} + +impl CompileBag<'_> { + fn share(&self, candidate: PyObjectRef) -> PyObjectRef { + let Some(key) = key(&candidate) else { + return candidate; + }; + let winner = { self.pool.borrow().get(&key).cloned() }; + if let Some(winner) = winner { + // No RefCell guard remains when the candidate is released. + winner + } else { + self.pool.borrow_mut().insert(key, candidate.clone()); + candidate + } + } + + fn finalize(&self, code: CodeObject) -> PyCode { + let mut result = PyCode::new(code, &self.vm.ctx); + // Pinned assemble.c surviving-field order. Input bytecode is not the + // separately cached exported co_code and must not be pooled here. + result.exceptiontable_bytes = self + .share(result.exceptiontable_bytes.into()) + .downcast() + .unwrap(); + result.linetable_bytes = self + .share(result.linetable_bytes.into()) + .downcast() + .unwrap(); + let names = self + .share(result.names_tuple.clone().into()) + .downcast() + .unwrap(); + result.retain_normalized_names(names, None); + let constants = result + .code + .constants + .tuple_owner() + .expect("finalized constants") + .0 + .clone(); + let constants = self.share(constants.into()).downcast().unwrap(); + result.code.constants = bytecode::Constants::from_tuple(LiteralTupleOwner(constants)); + let locals = self + .share(result.localsplus_names_tuple.clone().into()) + .downcast() + .unwrap(); + // An equal-text pool winner is not necessarily normalized. Preserve the + // safe copy fallback rather than changing published tuple elements. + let names = result.names_tuple.clone(); + result.retain_normalized_names(names, Some(locals)); + result + } + + fn wrap(&self, code: CodeObject) -> PyRef { + PyRef::new_ref( + self.finalize(code), + self.vm.ctx.types.code_type.to_owned(), + None, + ) + } +} + +impl ConstantBag for CompileBag<'_> { + type Constant = Literal; + + fn make_constant(&self, constant: BorrowedConstant<'_, C>) -> Literal { + let obj = match constant { + BorrowedConstant::Code { code } => { + return Literal(self.wrap(code.map_clone_bag(self)).into()); + } + // Delegate the entire subtree, including children, without caching. + BorrowedConstant::Slice { .. } => return PyVmBag(self.vm).make_constant(constant), + BorrowedConstant::Tuple { elements } => self + .vm + .ctx + .new_tuple( + elements + .iter() + .map(|child| self.make_constant(child.borrow_constant()).0) + .collect(), + ) + .into(), + BorrowedConstant::Frozenset { elements } => PyFrozenSet::from_iter( + self.vm, + elements + .iter() + .map(|child| self.make_constant(child.borrow_constant()).0), + ) + .unwrap() + .into_ref(&self.vm.ctx) + .into(), + _ => PyVmBag(self.vm).make_constant(constant).0, + }; + Literal(self.share(obj)) + } + + fn make_name(&self, name: &str) -> &'static PyStrInterned { + self.vm.ctx.intern_str(name) + } + fn make_int(&self, value: BigInt) -> Literal { + Literal(self.share(self.vm.ctx.new_int(value).into())) + } + fn make_tuple(&self, elements: impl Iterator) -> Literal { + Literal( + self.share( + self.vm + .ctx + .new_tuple(elements.map(|value| value.0).collect()) + .into(), + ), + ) + } + fn make_code(&self, code: CodeObject) -> Literal { + Literal(self.wrap(code).into()) + } +} + +pub(super) fn into_pycode(code: bytecode::CodeObject, vm: &VirtualMachine) -> PyCode { + let pool = Pool::default(); + let bag = CompileBag { vm, pool: &pool }; + let result = bag.finalize(code.map_bag(bag)); + // Nested PyRefs are still reachable only through this private result graph. + // RAII also drops the pool on unwind; no cache owners escape publication. + drop(pool); + result +} + +pub(super) fn into_code_object(code: bytecode::CodeObject, vm: &VirtualMachine) -> CodeObject { + let pool = Pool::default(); + let result = code.map_bag(CompileBag { vm, pool: &pool }); + drop(pool); + result +} + +#[cfg(test)] +mod tests { + use super::*; + use bytecode::ConstantData as Data; + + #[test] + fn compilation_keys_preserve_types_zeros_and_exclusions() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let pool = Pool::default(); + let bag = CompileBag { vm, pool: &pool }; + let data = [ + Data::Boolean { value: false }, + Data::Integer { value: 0.into() }, + Data::Float { value: 0.0 }, + Data::Float { value: -0.0 }, + Data::Complex { + value: num_complex::Complex64::new(0.0, 0.0), + }, + Data::Complex { + value: num_complex::Complex64::new(-0.0, 0.0), + }, + Data::Complex { + value: num_complex::Complex64::new(0.0, -0.0), + }, + Data::Complex { + value: num_complex::Complex64::new(-0.0, -0.0), + }, + ]; + let values: Vec<_> = data + .iter() + .map(|value| bag.make_constant(value.borrow_constant())) + .collect(); + for (index, value) in values.iter().enumerate() { + let repeated = bag.make_constant(data[index].borrow_constant()); + assert!(value.0.is(&repeated.0)); + for other in &values[..index] { + assert!(!value.0.is(&other.0)); + } + } + let nan = Data::Tuple { + elements: vec![Data::Float { value: f64::NAN }], + }; + let first = bag.make_constant(nan.borrow_constant()); + let second = bag.make_constant(nan.borrow_constant()); + assert!(key(&first.0).is_none()); + assert!(!first.0.is(&second.0)); + for scalar in [ + Data::Complex { + value: num_complex::Complex64::new(f64::NAN, 1.0), + }, + Data::Complex { + value: num_complex::Complex64::new(1.0, f64::NAN), + }, + ] { + let aggregate = Data::Tuple { + elements: vec![ + scalar, + Data::Bytes { + value: b"safe sibling next to NaN".to_vec(), + }, + ], + }; + let first = bag.make_constant(aggregate.borrow_constant()); + let second = bag.make_constant(aggregate.borrow_constant()); + assert!(key(&first.0).is_none()); + assert!(!first.0.is(&second.0)); + let first = first.0.downcast_ref::().unwrap(); + let second = second.0.downcast_ref::().unwrap(); + assert!(!first.as_slice()[0].is(&second.as_slice()[0])); + assert!(first.as_slice()[1].is(&second.as_slice()[1])); + } + let infinities = [ + Data::Float { + value: f64::INFINITY, + }, + Data::Float { + value: f64::NEG_INFINITY, + }, + Data::Complex { + value: num_complex::Complex64::new(f64::INFINITY, f64::NEG_INFINITY), + }, + Data::Complex { + value: num_complex::Complex64::new(f64::NEG_INFINITY, f64::INFINITY), + }, + ]; + let mut infinite_values: Vec = Vec::new(); + for value in infinities { + let first = bag.make_constant(value.borrow_constant()); + let second = bag.make_constant(value.borrow_constant()); + assert!(key(&first.0).is_some()); + assert!(first.0.is(&second.0)); + assert!(infinite_values.iter().all(|other| !first.0.is(&other.0))); + infinite_values.push(first); + } + let bytes = || Data::Bytes { + value: b"uncached slice child".to_vec(), + }; + let slice = Data::Slice { + elements: Box::new([bytes(), Data::None, Data::None]), + }; + let first = bag.make_constant(slice.borrow_constant()); + let second = bag.make_constant(slice.borrow_constant()); + assert!(key(&first.0).is_none()); + let first = first.0.downcast_ref::().unwrap(); + let second = second.0.downcast_ref::().unwrap(); + assert!( + !first + .start + .as_ref() + .unwrap() + .is(second.start.as_ref().unwrap()) + ); + let cached = bag.make_constant(bytes().borrow_constant()); + assert!(!first.start.as_ref().unwrap().is(&cached.0)); + }); + } + + #[test] + fn compilation_frozenset_keys_use_survivors_and_release_pool_owners() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let pool = Pool::default(); + let bag = CompileBag { vm, pool: &pool }; + let set = |elements| Data::Frozenset { elements }; + let boolean_first = set(vec![ + Data::Boolean { value: true }, + Data::Integer { value: 1.into() }, + ]); + let integer_first = set(vec![ + Data::Integer { value: 1.into() }, + Data::Boolean { value: true }, + ]); + let first = bag.make_constant(boolean_first.borrow_constant()); + let second = bag.make_constant(integer_first.borrow_constant()); + assert!(!first.0.is(&second.0)); + assert!( + first.0.downcast_ref::().unwrap().elements()[0] + .class() + .is(crate::builtins::PyBool::static_type()) + ); + assert!( + second.0.downcast_ref::().unwrap().elements()[0] + .class() + .is(crate::builtins::PyInt::static_type()) + ); + let forward = set(vec![ + Data::Bytes { + value: b"first ordinary element".to_vec(), + }, + Data::Bytes { + value: b"second ordinary element".to_vec(), + }, + ]); + let reverse = set(vec![ + Data::Bytes { + value: b"second ordinary element".to_vec(), + }, + Data::Bytes { + value: b"first ordinary element".to_vec(), + }, + ]); + let forward = bag.make_constant(forward.borrow_constant()); + let reverse = bag.make_constant(reverse.borrow_constant()); + assert!(forward.0.is(&reverse.0)); + let snapshot = forward.0.downcast_ref::().unwrap().elements(); + let counts: Vec<_> = snapshot.iter().map(|value| value.strong_count()).collect(); + for _ in 0..3 { + assert!(key(&forward.0).is_some()); + assert_eq!( + counts, + snapshot + .iter() + .map(|value| value.strong_count()) + .collect::>() + ); + } + drop(snapshot); + let data = Data::Tuple { + elements: vec![Data::Bytes { + value: b"private pool lifetime".to_vec(), + }], + }; + let value = bag.make_constant(data.borrow_constant()); + let again = bag.make_constant(data.borrow_constant()); + assert!(value.0.is(&again.0)); + drop(again); + assert_eq!(value.0.strong_count(), 2); + drop(pool); + assert_eq!(value.0.strong_count(), 1); + let child = &value + .0 + .downcast_ref::() + .unwrap() + .as_slice()[0]; + assert_eq!(child.strong_count(), 1); + }); + } + + #[cfg(feature = "compiler")] + #[test] + fn basic_route_finalizes_fields_before_publication() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let basic = crate::compiler::compile( + "def left(x):\n return x + 12345678901234567890\ndef right(x):\n return x + 12345678901234567890\n", + crate::compiler::Mode::Exec, "compile_cache.py", vm.compile_opts(), + ).unwrap(); + let root = vm.new_code(basic); + let nested: Vec<_> = root.code.constants.iter() + .filter_map(|value| value.0.downcast_ref::()).collect(); + assert_eq!(nested.len(), 2); + let [left, right] = [nested[0], nested[1]]; + assert!(!left.is(right)); + assert!(left.code.constants.tuple_owner().unwrap().0.is(&right.code.constants.tuple_owner().unwrap().0)); + assert!(left.linetable_bytes.is(&right.linetable_bytes)); + assert!(left.exceptiontable_bytes.is(&right.exceptiontable_bytes)); + assert!(left.names_tuple.is(&right.names_tuple)); + assert!(left.localsplus_names_tuple.is(&right.localsplus_names_tuple)); + assert_eq!(left.code.constants.tuple_owner().unwrap().0.as_object().strong_count(), 2); + assert!(left.code_bytes.get().is_none()); + assert!(right.code_bytes.get().is_none()); + assert!(!left.co_code(vm).is(&right.co_code(vm))); + }); + } + #[cfg(feature = "compiler")] + #[test] + fn assembly_fields_share_literals_but_default_routes_do_not_pool() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let mut basic = crate::compiler::compile( + "try:\n value = target\nexcept TypeError:\n value = None\n", + crate::compiler::Mode::Exec, + "compile_cache.py", + vm.compile_opts(), + ) + .unwrap(); + assert!(!basic.linetable.is_empty()); + assert!(!basic.exceptiontable.is_empty()); + basic.constants = vec![ + Data::Bytes { + value: basic.linetable.to_vec(), + }, + Data::Bytes { + value: basic.exceptiontable.to_vec(), + }, + ] + .into_iter() + .collect(); + let frozen = frozen::FrozenCodeObject::encode(&basic); + let raw = basic.clone().map_bag(PyVmBag(vm)); + let literal_default = vm.new_code(raw); + let frozen_default = vm.new_code(frozen); + let pooled = vm.new_code(basic); + for (index, field) in [&pooled.linetable_bytes, &pooled.exceptiontable_bytes] + .into_iter() + .enumerate() + { + assert!(pooled.code.constants.get(index).unwrap().0.is(field)); + assert_eq!(field.as_object().strong_count(), 2); // One tuple edge and one field edge. + } + assert!( + !literal_default + .code + .constants + .first() + .unwrap() + .0 + .is(&literal_default.linetable_bytes) + ); + assert!( + !frozen_default + .code + .constants + .first() + .unwrap() + .0 + .is(&frozen_default.linetable_bytes) + ); + let tuple = vm.ctx.new_tuple(vec![pooled.to_owned().into()]); + assert!(key(pooled.as_object()).is_none()); + assert!(key(tuple.as_object()).is_none()); + }); + } + + #[cfg(feature = "compiler")] + #[test] + fn noncanonical_name_winner_is_not_published() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let basic = crate::compiler::compile( + "result = target", + crate::compiler::Mode::Exec, + "compile_cache.py", + vm.compile_opts(), + ) + .unwrap(); + let raw = basic.map_bag(PyVmBag(vm)); + let noncanonical = vm.ctx.new_tuple( + raw.names + .iter() + .map(|name| vm.ctx.new_str(name.as_str()).into()) + .collect(), + ); + assert!(!noncanonical.is_empty()); + for (value, canonical) in noncanonical.iter().zip(raw.names.iter()) { + assert!(!value.is(*canonical)); + } + let pool = Pool::default(); + let bag = CompileBag { vm, pool: &pool }; + let winner = bag.share(noncanonical.clone().into()); + assert!(winner.is(&noncanonical)); + drop(winner); + let result = bag.finalize(raw); + assert!(!result.names_tuple.is(&noncanonical)); + for (value, canonical) in result.names_tuple.iter().zip(result.code.names.iter()) { + assert!(value.is(*canonical)); + } + drop(pool); + assert_eq!(noncanonical.as_object().strong_count(), 1); + }); + } +} diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 649481c8cc3..a0d4a581aa9 100644 --- a/crates/vm/src/builtins/coroutine.rs +++ b/crates/vm/src/builtins/coroutine.rs @@ -174,7 +174,7 @@ impl Destructor for PyCoroutine { if zelf.inner.closed() || zelf.inner.running() { return Ok(()); } - if zelf.inner.frame_opt().is_none_or(|f| f.lasti() == 0) { + if !zelf.inner.started() { crate::warn::warn_unawaited_coroutine(zelf.as_object(), &zelf.inner.qualname(), vm); zelf.inner.closed.store(true); return Ok(()); diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index be8ddd9f391..078b4ba7af7 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -242,7 +242,14 @@ pub(crate) mod stack_analysis { } } } - Instruction::GetIter | Instruction::GetAiter => { + Instruction::GetIter { .. } => { + next_stack = push_value(pop_value(next_stack), Kind::Iterator as i64); + next_stack = push_value(next_stack, Kind::Iterator as i64); + if next_i < stacks.len() { + stacks[next_i] = next_stack; + } + } + Instruction::GetAiter => { next_stack = push_value(pop_value(next_stack), Kind::Iterator as i64); if next_i < stacks.len() { stacks[next_i] = next_stack; @@ -254,19 +261,13 @@ pub(crate) mod stack_analysis { if next_i < stacks.len() { stacks[next_i] = body_stack; } - // Exhaustion path: relative forward from after_caches - let mut j = next_i + oparg as usize; - if j < instructions.len() { - let target_op = - instructions[j].op.to_base().unwrap_or(instructions[j].op); - if matches!(target_op, Instruction::EndFor) { - j += 1; - } - } + // CPython models the target END_FOR with the yielded-value slot. + let j = next_i + oparg as usize; if j < stacks.len() && stacks[j] == UNINITIALIZED { - stacks[j] = next_stack; + stacks[j] = body_stack; } } + Instruction::EndAsyncFor => { next_stack = pop_value(pop_value(next_stack)); if next_i < stacks.len() { @@ -630,6 +631,11 @@ impl Py { let mut cur = crate::vm::thread::get_current_frame(); while !cur.is_null() { if core::ptr::eq(cur, prev) { + // SAFETY: cur was found on this thread's live active chain. + let cur = unsafe { crate::frame::first_complete_iframe(cur) }; + if cur.is_null() { + return None; + } let iframe_ref = unsafe { &*cur }; let fo = iframe_ref.materialize(vm); return Some(fo.to_owned()); @@ -652,6 +658,11 @@ impl Py { // return and free the stack-allocated iframe at any time. vm.state.stop_the_world.stop_the_world(&vm.state); scopeguard::defer! { vm.state.stop_the_world.start_the_world(&vm.state); } + // SAFETY: the world is stopped, preserving the live chain. + let prev = unsafe { crate::frame::first_complete_iframe(prev) }; + if prev.is_null() { + return None; + } let prev_ref = unsafe { &*prev }; // Fast path: already materialized. if let Some(fo) = prev_ref.frame_obj() { @@ -659,7 +670,7 @@ impl Py { } // Slow path: copy the whole chain, linked through retained_back. // SAFETY: the world is stopped, so the owning thread is parked. - let fo = unsafe { prev_ref.materialize_detached_chain(vm) }; + let fo = unsafe { prev_ref.materialize_complete_detached_chain(vm) }; return Some(fo); } diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 40e51c6b31b..80127a76fe7 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -637,6 +637,7 @@ impl Py { ); self.fill_locals_from_args(&frame, func_args, vm)?; if is_gen || is_coro || is_async_gen { + vm.with_generator_frame(frame.clone(), |f| f.prepare_generator(vm))?; return Ok(self.make_generator_or_coro(frame, vm)); } // Tracing active: use heap frame with full trace support. @@ -844,7 +845,7 @@ impl Py { &self, args: impl ExactSizeIterator, vm: &VirtualMachine, - ) -> PyObjectRef { + ) -> PyResult { let code: PyRef = (*self.code).to_owned(); debug_assert_eq!(args.len(), code.arg_count as usize); @@ -886,7 +887,8 @@ impl Py { } } - self.make_generator_or_coro(frame, vm) + vm.with_generator_frame(frame.clone(), |f| f.prepare_generator(vm))?; + Ok(self.make_generator_or_coro(frame, vm)) } pub(crate) fn invoke_prepared_exact_args( @@ -946,7 +948,7 @@ impl Py { // specialization classification, but calling one produces a // generator/coroutine object instead of running the frame. if self.is_generator_like() { - return Ok(self.make_generator_exact_args(args.into_iter(), vm)); + return self.make_generator_exact_args(args.into_iter(), vm); } self.invoke_prepared_exact_args(args.into_iter(), vm) } @@ -968,7 +970,7 @@ impl Py { // specialization classification, but calling one produces a // generator/coroutine object instead of running the frame. if self.is_generator_like() { - return Ok(self.make_generator_exact_args(taken, vm)); + return self.make_generator_exact_args(taken, vm); } self.invoke_prepared_exact_args(taken, vm) } @@ -1779,7 +1781,7 @@ pub(crate) fn vectorcall_function( // is called in, and a fresh `MAKE_FUNCTION` each time keeps those out // of the call-site specialization that would otherwise catch it. args.truncate(nargs); - return Ok(zelf.make_generator_exact_args(args.into_iter(), vm)); + return zelf.make_generator_exact_args(args.into_iter(), vm); } if !has_kwargs && base_simple && nargs == code.arg_count as usize { diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index 5da8c8272a1..bf287c02823 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -40,6 +40,8 @@ pub struct Coro { code: PyRef, pub closed: AtomicCell, // TODO: https://github.com/RustPython/RustPython/pull/3183#discussion_r720560652 running: AtomicCell, + /// Whether execution has resumed beyond the eager creation preamble. + started: AtomicCell, // _weakreflist name: PyMutex, qualname: PyMutex, @@ -159,16 +161,16 @@ pub(crate) fn flat_resume_enter( if coro.closed.load() { return Ok(FlatEnter::Exhausted); } - let value = if coro.frame_opt().is_some_and(|f| f.lasti() > 0) { - Some(value) - } else if !vm.is_none(&value) { + if !coro.started() && !vm.is_none(&value) { return Err(vm.new_type_error(format!( "can't send non-None value to a just-started {}", gen_name(&jen, vm), ))); - } else { - None - }; + } + let value = coro + .frame_opt() + .is_some_and(|f| f.lasti() > 0) + .then_some(value); // SAFETY: exclusive access guaranteed by the claim let gen_exc = unsafe { coro.exception.swap(None) }; let frame_ref = coro.frame(); @@ -184,6 +186,7 @@ pub(crate) fn flat_resume_enter( return Err(exc); } }; + coro.started.store(true); // The claim outlives this scope; `flat_resume_exit` drops it. core::mem::forget(_claim); Ok(FlatEnter::Entered { @@ -234,6 +237,7 @@ impl Coro { code, closed: AtomicCell::new(false), running: AtomicCell::new(false), + started: AtomicCell::new(false), exception: PyAtomicRef::from(None), name: PyMutex::new(name), qualname: PyMutex::new(qualname), @@ -307,6 +311,7 @@ impl Coro { let frame = self.frame(); vm.resume_gen_frame(&frame, gen_exc, |f| { + self.started.store(true); let result = func(f); // SAFETY: exclusive access guaranteed by the claim let _old = unsafe { (*exception_ptr).swap(vm.current_exception()) }; @@ -401,16 +406,16 @@ impl Coro { if self.closed.load() { return Self::send_when_closed(jen, vm); } - let value = if self.frame_opt().is_some_and(|f| f.lasti() > 0) { - Some(value) - } else if !vm.is_none(&value) { + if !self.started() && !vm.is_none(&value) { return Err(vm.new_type_error(format!( "can't send non-None value to a just-started {}", gen_name(jen, vm), ))); - } else { - None - }; + } + let value = self + .frame_opt() + .is_some_and(|f| f.lasti() > 0) + .then_some(value); let result = self.run_claimed(&claim, vm, |f| f.resume(value, vm)); self.maybe_close(&result, &claim); drop(claim); @@ -462,7 +467,7 @@ impl Coro { return Ok(vm.ctx.none()); } // FRAME_CREATED: mark finished and clear the iframe. - if self.frame_opt().is_none_or(|f| f.lasti() == 0) { + if !self.started() { self.closed.store(true); self.clear_except_code(); return Ok(vm.ctx.none()); @@ -493,13 +498,11 @@ impl Coro { /// Whether the frame has run at all, i.e. is stopped at a `yield` rather /// than at its start. pub(crate) fn started(&self) -> bool { - self.frame_opt().is_some_and(|f| f.lasti() > 0) + self.started.load() } pub fn suspended(&self) -> bool { - !self.closed.load() - && !self.running.load() - && self.frame_opt().is_some_and(|f| f.lasti() > 0) + !self.closed.load() && !self.running.load() && self.started() } pub fn running(&self) -> bool { diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index a043838d49b..699235aa9b9 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -20,6 +20,7 @@ use crate::{ }, bytecode::{ self, ADAPTIVE_COOLDOWN_VALUE, Instruction, LoadAttr, LoadSuperAttr, Opcode, SpecialMethod, + oparg::{ResumeContext, ResumeLocation}, }, convert::{ToPyObject, ToPyResult}, coroutine::Coro, @@ -105,6 +106,39 @@ pub fn current_thread_frame_materialize(vm: &VirtualMachine) -> Option *const InterpreterFrame { + while !current.is_null() { + // SAFETY: the caller guarantees the chain remains alive. + let frame = unsafe { &*current }; + if !frame.is_incomplete_for_trace() { + break; + } + current = frame.previous(); + } + current +} + +/// Resolve the active frame for a legacy trace/profile callback, skipping +/// incomplete generator-creation frames before materializing a frame object. +/// Raw execution-chain users deliberately keep seeing the actual active frame. +pub(crate) fn current_complete_frame_for_trace(vm: &VirtualMachine) -> Option { + // SAFETY: synchronous inspection of the current thread's active chain. + let current = unsafe { first_complete_iframe(crate::vm::thread::get_current_frame()) }; + if current.is_null() { + return None; + } + // SAFETY: the selected frame is alive on this thread's execution chain. + Some(unsafe { (*current).materialize(vm).to_owned() }) +} + /// Read the globals dict from the topmost frame on this thread's chain. /// Returns `None` if the chain is empty. #[must_use] @@ -137,12 +171,13 @@ pub fn current_builtins() -> Option { Some(unsafe { (*ptr).builtins().to_owned() }) } -/// The frame `offset` positions below the current thread's top frame (offset 0 -/// is the top), or `None` if the stack is not that deep. +/// The complete frame `offset` positions below the current thread's first +/// complete frame, or `None` if the stack is not that deep. /// Materializes the FrameObject on demand for stack-allocated frames. #[must_use] pub fn frame_at_offset(offset: usize, vm: &VirtualMachine) -> Option { - let mut cur = crate::vm::thread::get_current_frame(); + // SAFETY: synchronous inspection of this thread's active chain. + let mut cur = unsafe { first_complete_iframe(crate::vm::thread::get_current_frame()) }; let mut remaining = offset; while !cur.is_null() { if remaining == 0 { @@ -150,7 +185,8 @@ pub fn frame_at_offset(offset: usize, vm: &VirtualMachine) -> Option bool { + if FrameOwner::from_i8(self.owner.load(atomic::Ordering::Acquire)) == FrameOwner::Generator + || self.generator.load().is_some() + { + return false; + } + let instructions = &self.code().instructions; + let lasti = self.lasti.load(Relaxed) as usize; + let mut index = 0; + while index < instructions.len() { + let unit = instructions.read_unit(index); + if matches!( + unit.op, + Instruction::Resume { .. } + | Instruction::ResumeCheck + | Instruction::InstrumentedResume + ) && u32::from(u8::from(unit.arg)) & ResumeContext::LOCATION_MASK + != ResumeLocation::AtGenExprStart.as_u32() + { + // lasti is one past the in-flight instruction. At RESUME + // itself, lasti == index + 1 and the frame is complete. + return lasti <= index; + } + // Prefixes before the first traceable RESUME are uninstrumented. + // Skip inline cache payloads rather than decoding them as opcodes. + index += 1 + unit.op.cache_entries(); + } + true + } + /// Get the last instruction index. #[inline(always)] pub fn get_lasti(&self) -> u32 { @@ -1228,10 +1298,43 @@ impl InterpreterFrame { #[cold] #[inline(never)] pub(crate) unsafe fn materialize_detached_chain(&self, vm: &VirtualMachine) -> FrameObjectRef { + // SAFETY: forwarded unchanged from this method's contract. + unsafe { self.materialize_detached_chain_impl::(vm) } + } + + /// Copy the complete ancestors used by Python-visible `f_back` traversal. + /// The raw snapshot path above deliberately preserves every active node. + /// + /// # Safety + /// The caller must hold the world stopped and select a complete top frame. + #[cfg(feature = "threading")] + #[cold] + #[inline(never)] + pub(crate) unsafe fn materialize_complete_detached_chain( + &self, + vm: &VirtualMachine, + ) -> FrameObjectRef { + // SAFETY: forwarded unchanged from this method's contract. + unsafe { self.materialize_detached_chain_impl::(vm) } + } + + #[cfg(feature = "threading")] + #[cold] + unsafe fn materialize_detached_chain_impl( + &self, + vm: &VirtualMachine, + ) -> FrameObjectRef { let top = unsafe { self.materialize_detached(vm) }; let mut child = top.clone(); let mut cur = self.previous(); - while !cur.is_null() { + loop { + if SKIP_INCOMPLETE { + // SAFETY: the caller holds the owning thread stopped. + cur = unsafe { first_complete_iframe(cur) }; + } + if cur.is_null() { + break; + } let caller = unsafe { &*cur }; let caller_fo = unsafe { caller.materialize_detached(vm) }; { @@ -1756,6 +1859,7 @@ pub enum ExecutionResult { /// CACHE <- the parked frame's lasti is here + 1 /// YIELD_VALUE 1 /// resumed_at: RESUME +/// CACHE /// JUMP_BACKWARD_NO_INTERRUPT -> send_idx /// exit: END_SEND /// ``` @@ -2529,6 +2633,18 @@ impl Py { self.with_exec(vm, |mut exec| exec.run(vm)) } + /// Execute the eager generator preamble and suspend immediately after + /// RETURN_GENERATOR. The caller links this frame with `vm.with_generator_frame`. + /// The first resume supplies the value consumed by the following POP_TOP. + pub(crate) fn prepare_generator(&self, vm: &VirtualMachine) -> PyResult<()> { + self.with_exec(vm, |mut exec| match exec.run_until::(vm)? { + None => Ok(()), + Some(_) => { + Err(vm.new_runtime_error("generator preamble did not reach RETURN_GENERATOR")) + } + }) + } + /// Resume a suspended generator or coroutine body, pushing `value` as the /// result of the `yield` it stopped at. /// @@ -2788,10 +2904,10 @@ pub(crate) fn gen_collapse_allowed(vm: &VirtualMachine) -> bool { /// delegating to, if the trampoline can resume it in this frame's place. /// /// Recognizes the shape documented on [`GenCont`] — `lasti` at the `RESUME` -/// that follows the delegating `YIELD_VALUE`, with the delegate on top of the -/// stack — and requires the `SEND` to have already specialized to `SendGen`, -/// so that a `Send` still collecting specialization feedback keeps running -/// normally. The frame is left untouched; `park_at_send` commits to it. +/// that follows the delegating `YIELD_VALUE`, with the delegate below its +/// index-or-NULL stack slot — and requires the `SEND` to have already +/// specialized to `SendGen`, so that a `Send` still collecting feedback keeps +/// running normally. The frame is left untouched; `park_at_send` commits to it. pub(crate) fn yield_from_delegate( iframe: &InterpreterFrame, vm: &VirtualMachine, @@ -2824,10 +2940,11 @@ pub(crate) fn yield_from_delegate( { return None; } - let delegate = match iframe.localsplus.stack_last() { - Some(Some(top)) => top.as_object(), - _ => return None, - }; + let stack = iframe.localsplus.stack_as_slice(); + let delegate = stack + .get(stack.len().checked_sub(2)?)? + .as_ref()? + .as_object(); // The same guards `SendGen` applies before resuming a generator itself. if delegate.downcast_ref_if_exact::(vm).is_none() && delegate.downcast_ref_if_exact::(vm).is_none() @@ -3072,7 +3189,8 @@ pub(crate) fn release_datastack_frame(frame: &Py, vm: &VirtualMachi { let mut guard = frame.iframe().cold().retained_back.lock(); if guard.is_none() { - let prev = frame.previous_iframe(); + // SAFETY: this frame is returning into its still-active caller. + let prev = unsafe { first_complete_iframe(frame.previous_iframe()) }; *guard = unsafe { owned_chain_frame(prev) }; } } @@ -3440,7 +3558,11 @@ impl ExecutingFrame<'_> { /// (COPY_FREE_VARS / RETURN_GENERATOR that precede it are not a 'call', /// and later RESUMEs in a SEND loop are not a new call either). fn trace_call_from_resume(&mut self, vm: &VirtualMachine, resume_type: u32) -> PyResult<()> { - if self.call_traced { + // RESUME_AT_GEN_EXPR_START runs before the frame is complete. The + // first traceable instruction is the ordinary RESUME after creation. + if resume_type & ResumeContext::LOCATION_MASK == ResumeLocation::AtGenExprStart.as_u32() + || self.call_traced + { return Ok(()); } self.call_traced = true; @@ -3626,7 +3748,22 @@ impl ExecutingFrame<'_> { /// Matches `_PyEval_MonitorRaise` → `PY_MONITORING_EVENT_RAISE` → /// `sys_trace_exception_func` in legacy_tracing.c. fn fire_exception_trace(&self, exc: &Py, vm: &VirtualMachine) -> PyResult<()> { - if vm.use_tracing.get() && self.trace_is_set(vm) { + if !vm.use_tracing.get() { + return Ok(()); + } + let trace_is_set = if self.iframe().is_incomplete_for_trace() { + // Legacy tracing resolves PyEval_GetFrame to the first complete + // caller while sys.monitoring continues to use this code object. + current_complete_frame_for_trace(vm).is_some_and(|frame| { + frame + .iframe() + .cold_opt() + .is_some_and(|cold| cold.trace.lock().is_some()) + }) + } else { + self.trace_is_set(vm) + }; + if trace_is_set { let exc_type: PyObjectRef = exc.class().to_owned().into(); let exc_value: PyObjectRef = exc.to_owned().into(); let exc_tb: PyObjectRef = exc @@ -3639,6 +3776,14 @@ impl ExecutingFrame<'_> { } fn run(&mut self, vm: &VirtualMachine) -> PyResult { + self.run_until::(vm) + .map(|result| result.expect("ordinary execution cannot stop at a generator preamble")) + } + + fn run_until( + &mut self, + vm: &VirtualMachine, + ) -> PyResult> { flame_guard!(format!( "FrameObject::run({obj_name})", obj_name = self.code.obj_name @@ -3681,7 +3826,7 @@ impl ExecutingFrame<'_> { // principle toggle it, because we refresh `tracing` below right // after that call returns (that cold path is only taken when // tracing was already on, so it costs nothing on the hot path). - let mut tracing = vm.use_tracing.get(); + let mut tracing = !PREPARE_GENERATOR && vm.use_tracing.get(); // Fire 'line' trace event when line number changes. // Only fire if this frame has a per-frame trace function set @@ -3726,7 +3871,7 @@ impl ExecutingFrame<'_> { idx = lasti_cell.load(Relaxed) as usize; continue; } - Ok(Some(value)) => break Ok(value), + Ok(Some(value)) => break Ok(Some(value)), Err(e) => break Err(e), } } @@ -3734,7 +3879,7 @@ impl ExecutingFrame<'_> { // The trace callback may have toggled tracing (e.g. via // sys.settrace(None)); refresh before the opcode-trace check // below reuses this flag. - tracing = vm.use_tracing.get(); + tracing = !PREPARE_GENERATOR && vm.use_tracing.get(); // Trace callback may have changed lasti via set_f_lineno. // Re-read and restart the loop from the new position. if lasti_cell.load(Relaxed) != (idx as u32 + 1) { @@ -3811,9 +3956,10 @@ impl ExecutingFrame<'_> { frame: &mut ExecutingFrame<'_>, exception: PyBaseExceptionRef, idx: usize, + complete_frame: bool, vm: &VirtualMachine, ) -> FrameResult { - if let Some((loc, _end_loc)) = frame.code.locations.get(idx) { + if complete_frame && let Some((loc, _end_loc)) = frame.code.locations.get(idx) { let next = exception.traceback(); let new_traceback = PyTraceback::new( next, @@ -3832,10 +3978,10 @@ impl ExecutingFrame<'_> { }, ) } - match handle_signal_exception(self, exception, idx, vm) { + match handle_signal_exception(self, exception, idx, !PREPARE_GENERATOR, vm) { Ok(None) => {} Ok(Some(value)) => { - break Ok(value); + break Ok(Some(value)); } Err(exception) => { break Err(exception); @@ -3859,6 +4005,13 @@ impl ExecutingFrame<'_> { crate::opcode_histogram::record(prev_op, op_byte); prev_op = op_byte; } + if PREPARE_GENERATOR && matches!(op, Instruction::ReturnGenerator) { + // CPython copies the eager frame at RETURN_GENERATOR with its + // instruction pointer advanced to POP_TOP. Iterator pairs from + // generator expressions must survive the execution borrow too. + self.localsplus.promote_stack(); + break Ok(None); + } let result = self.execute_instruction(op, arg, &mut do_extend_arg, vm); // Skip inline cache entries if instruction fell through (no jump). // `cache_entries()` is a table lookup, so it is done here rather @@ -3874,7 +4027,7 @@ impl ExecutingFrame<'_> { match result { Ok(None) => {} Ok(Some(value)) => { - break Ok(value); + break Ok(Some(value)); } // Instruction raised an exception Err(exception) => { @@ -3885,6 +4038,7 @@ impl ExecutingFrame<'_> { idx: usize, is_reraise: bool, is_new_raise: bool, + complete_frame: bool, vm: &VirtualMachine, ) -> FrameResult { // 1. Extract traceback from exception's '__traceback__' attr. @@ -3903,7 +4057,9 @@ impl ExecutingFrame<'_> { // checking for duplicates. Each time an exception passes through // a frame (e.g., in a loop with repeated raise statements), // a new traceback entry is added. - if let Some((loc, _end_loc)) = frame.code.locations.get(idx) { + if complete_frame + && let Some((loc, _end_loc)) = frame.code.locations.get(idx) + { let next = exception.traceback(); let new_traceback = PyTraceback::new( @@ -4001,11 +4157,19 @@ impl ExecutingFrame<'_> { self.fire_exception_trace(&exception, vm)?; } - match handle_exception(self, exception, idx, is_reraise, is_new_raise, vm) { + match handle_exception( + self, + exception, + idx, + is_reraise, + is_new_raise, + !PREPARE_GENERATOR, + vm, + ) { // The handler unwound to a new position, so the next // index is whatever it left in the frame. Ok(None) => next_idx = lasti_cell.load(Relaxed), - Ok(Some(result)) => break Ok(result), + Ok(Some(result)) => break Ok(Some(result)), Err(exception) => { // Fire PY_UNWIND: exception escapes this frame let exception = if vm.state.monitoring_events.load() @@ -4040,16 +4204,18 @@ impl ExecutingFrame<'_> { // In RustPython, we check: // 1. lasti points to RESUME (after YIELD_VALUE) // 2. The previous instruction was YIELD_VALUE with arg >= 1 - // 3. Stack top is the delegate (receiver) + // 3. The delegate (receiver) is below its index-or-NULL slot // // First check if stack is empty - if so, we can't be in yield-from - if self.localsplus.stack_is_empty() { + if self.localsplus.stack_len() < 2 { return None; } let lasti = self.lasti() as usize; if let Some(unit) = self.code.instructions.get(lasti) { match &unit.op { - Instruction::Send { .. } => return Some(self.top_value()), + Instruction::Send { .. } | Instruction::SendGen => { + return Some(self.nth_value(1)); + } Instruction::Resume { .. } | Instruction::InstrumentedResume => { // Check if previous instruction was YIELD_VALUE with arg >= 1 // This indicates yield-from/await context @@ -4063,8 +4229,8 @@ impl ExecutingFrame<'_> { // YIELD_VALUE arg: 0 = direct yield, >= 1 = yield-from/await // OpArgByte.0 is the raw byte value if u8::from(prev_unit.arg) >= 1 { - // In yield-from/await context, delegate is on top of stack - return Some(self.top_value()); + // The iterator pair remains after YIELD_VALUE. + return Some(self.nth_value(1)); } } } @@ -4915,36 +5081,40 @@ impl ExecutingFrame<'_> { self.push_value(iter); Ok(None) } - Instruction::GetIter => { - let iterated_obj = self.pop_value(); - let iter_obj = PyIter::try_from_object(vm, iterated_obj)?; - self.push_value(iter_obj.into()); - Ok(None) - } - Instruction::GetYieldFromIter => { - // GET_YIELD_FROM_ITER: prepare iterator for yield from - // If iterable is a coroutine, ensure we're in a coroutine context - // If iterable is a generator, use it directly - // Otherwise, call iter() on it + Instruction::GetIter { mode } => { + // Include/internal/pycore_opcode_utils.h GET_ITER_YIELD_FROM_*. + const GET_ITER_ORDINARY: u32 = 0; + const GET_ITER_YIELD_FROM: u32 = 1; + const GET_ITER_YIELD_FROM_NO_CHECK: u32 = 2; + const GET_ITER_YIELD_FROM_CORO_CHECK: u32 = 3; + let iterable = self.pop_value(); - let iter = if iterable.class().is(vm.ctx.types.coroutine_type) { - // Coroutine requires CO_COROUTINE or CO_ITERABLE_COROUTINE flag - if !self.code.flags.intersects( - bytecode::CodeFlags::COROUTINE | bytecode::CodeFlags::ITERABLE_COROUTINE, - ) { + let mode = mode.get(arg); + let iter = if mode != GET_ITER_ORDINARY + && iterable.class().is(vm.ctx.types.coroutine_type) + { + // Code construction resolves the compiler-only mode 1 + // using CO_COROUTINE / CO_ITERABLE_COROUTINE. + let reject_coroutine = match mode { + GET_ITER_YIELD_FROM_CORO_CHECK => true, + GET_ITER_YIELD_FROM_NO_CHECK => false, + GET_ITER_YIELD_FROM => !self.code.flags.intersects( + bytecode::CodeFlags::COROUTINE + | bytecode::CodeFlags::ITERABLE_COROUTINE, + ), + _ => false, + }; + if reject_coroutine { return Err(vm.new_type_error( "cannot 'yield from' a coroutine object in a non-coroutine generator", )); } iterable - } else if iterable.class().is(vm.ctx.types.generator_type) { - // Generator can be used directly - iterable } else { - // Otherwise, get iterator PyIter::try_from_object(vm, iterable)?.into() }; self.push_value(iter); + self.push_null(); Ok(None) } Instruction::GetLen => { @@ -4960,7 +5130,7 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::ImportName { namei: idx } => { - self.import(vm, Some(self.code.names[idx.get(arg) as usize]))?; + self.import(vm, Some(self.code.names[(idx.get(arg) >> 2) as usize]))?; Ok(None) } Instruction::IsOp { invert } => { @@ -5139,6 +5309,11 @@ impl ExecutingFrame<'_> { .expect("builtin_any not initialized"), CommonConstant::BuiltinList => vm.ctx.types.list_type.to_owned().into(), CommonConstant::BuiltinSet => vm.ctx.types.set_type.to_owned().into(), + CommonConstant::None => vm.ctx.none(), + CommonConstant::EmptyStr => vm.ctx.empty_str.to_owned().into(), + CommonConstant::True => vm.ctx.new_bool(true).into(), + CommonConstant::False => vm.ctx.new_bool(false).into(), + CommonConstant::MinusOne => vm.ctx.new_int(-1).into(), }; self.push_value(value); Ok(None) @@ -5615,8 +5790,8 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::PopTop => { - // Pop value from stack and ignore. - self.pop_stackref(); + // Loop unwinding also uses POP_TOP for index-or-NULL slots. + self.pop_stackref_opt(); Ok(None) } Instruction::EndFor => { @@ -5625,7 +5800,8 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::PopIter => { - // Pop the iterator from stack (end of for loop) + // Discard the iterator's index-or-NULL slot and the iterator. + self.pop_stackref_opt(); self.pop_stackref(); Ok(None) } @@ -5930,10 +6106,10 @@ impl ExecutingFrame<'_> { Ok(Some(ExecutionResult::Yield(self.pop_value()))) } Instruction::Send { .. } => { - // (receiver, v -- receiver, retval) + // (receiver, null_or_index, v -- receiver, null_or_index, retval) self.adaptive(|s, ii, cb| s.specialize_send(vm, ii, cb)); let exit_label = bytecode::Label::from_u32(self.lasti() + 1 + u32::from(arg)); - let receiver = self.nth_value(1); + let receiver = self.nth_value(2); let can_fast_send = !self.specialization_eval_frame_active(vm) && (receiver.downcast_ref_if_exact::(vm).is_some() || receiver.downcast_ref_if_exact::(vm).is_some()) @@ -5941,7 +6117,7 @@ impl ExecutingFrame<'_> { .builtin_coro(receiver) .is_some_and(|coro| !coro.running() && !coro.closed()); let val = self.pop_value(); - let receiver = self.top_value(); + let receiver = self.nth_value(1); let ret = if can_fast_send { let coro = self.builtin_coro(receiver).unwrap(); if vm.is_none(&val) { @@ -5971,8 +6147,8 @@ impl ExecutingFrame<'_> { } Instruction::SendGen => { let exit_label = bytecode::Label::from_u32(self.lasti() + 1 + u32::from(arg)); - // Stack: [receiver, val] — peek receiver before popping - let receiver = self.nth_value(1); + // Stack: [receiver, null_or_index, val] — peek before popping + let receiver = self.nth_value(2); let mut started = false; let can_fast_send = !self.specialization_eval_frame_active(vm) && (receiver.downcast_ref_if_exact::(vm).is_some() @@ -5984,7 +6160,7 @@ impl ExecutingFrame<'_> { let val = self.pop_value(); if can_fast_send { - let receiver = self.top_value(); + let receiver = self.nth_value(1); // Hand an already-suspended generator to the trampoline, // which runs its frame in this same eval loop and re-yields // for this frame — no Rust frame and no instruction per @@ -6019,7 +6195,7 @@ impl ExecutingFrame<'_> { } } } - let receiver = self.top_value(); + let receiver = self.nth_value(1); match self._send(receiver, val, vm)? { PyIterReturn::Return(value) => { self.push_value(value); @@ -6038,9 +6214,9 @@ impl ExecutingFrame<'_> { } } Instruction::EndSend => { - // Stack: (receiver, value) -> (value) - // Pops receiver, leaves value + // (receiver, null_or_index, value) -> (value) let value = self.pop_value(); + self.pop_stackref_opt(); // discard index-or-NULL self.pop_stackref(); // discard receiver self.push_value(value); Ok(None) @@ -6057,39 +6233,24 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::CleanupThrow => { - // CLEANUP_THROW: (sub_iter, last_sent_val, exc) -> (None, value) OR re-raise - // If StopIteration: pop all 3, extract value, push (None, value) - // Otherwise: pop all 3, return Err(exc) for unwind_blocks to handle - // - // Unlike CPython where exception_unwind pops the triple as part of - // stack cleanup to handler depth, RustPython pops here explicitly - // and lets unwind_blocks find outer handlers. - // Compiler sets handler_depth = base + 2 (before exc is pushed). - - // First peek at exc_value (top of stack) without popping + // (sub_iter, null_in, last_sent_val, exc) -> (None, null_out, value) + // Leave nonmatching exceptions and their stack for unwinding. let exc = self.top_value(); - - // Check if it's a StopIteration if let Some(exc_ref) = exc.downcast_ref::() && exc_ref.fast_isinstance(vm.ctx.exceptions.stop_iteration) { - // Extract value from StopIteration let value = exc_ref.get_arg(0).unwrap_or_else(|| vm.ctx.none()); - // Now pop all three self.pop_stackref(); // exc self.pop_stackref(); // last_sent_val + let index_or_null = self.pop_stackref_opt(); self.pop_stackref(); // sub_iter self.push_value(vm.ctx.none()); + self.push_stackref_opt(index_or_null); self.push_value(value); return Ok(None); } - - // Re-raise other exceptions: pop all three and return Err(exc) - let exc = self.pop_value(); // exc - self.pop_stackref(); // last_sent_val - self.pop_stackref(); // sub_iter - let exc = exc + .to_owned() .downcast::() .map_err(|_| vm.new_type_error("exception expected"))?; Err(exc) @@ -7947,7 +8108,7 @@ impl ExecutingFrame<'_> { } Instruction::ForIterRange => { let target = bytecode::Label::from_u32(self.lasti() + 1 + u32::from(arg)); - let iter = self.top_value(); + let iter = self.nth_value(1); if let Some(range_iter) = iter.downcast_ref_if_exact::(vm) { if let Some(value) = range_iter.fast_next() { self.push_value(vm.ctx.new_int(value).into()); @@ -7962,7 +8123,7 @@ impl ExecutingFrame<'_> { } Instruction::ForIterList => { let target = bytecode::Label::from_u32(self.lasti() + 1 + u32::from(arg)); - let iter = self.top_value(); + let iter = self.nth_value(1); if let Some(list_iter) = iter.downcast_ref_if_exact::(vm) { if let Some(value) = list_iter.fast_next() { self.push_value(value); @@ -7977,7 +8138,7 @@ impl ExecutingFrame<'_> { } Instruction::ForIterTuple => { let target = bytecode::Label::from_u32(self.lasti() + 1 + u32::from(arg)); - let iter = self.top_value(); + let iter = self.nth_value(1); if let Some(tuple_iter) = iter.downcast_ref_if_exact::(vm) { if let Some(value) = tuple_iter.fast_next() { self.push_value(value); @@ -7992,7 +8153,7 @@ impl ExecutingFrame<'_> { } Instruction::ForIterGen => { let target = bytecode::Label::from_u32(self.lasti() + 1 + u32::from(arg)); - let iter = self.top_value(); + let iter = self.nth_value(1); if self.specialization_eval_frame_active(vm) { self.execute_for_iter(vm, target)?; return Ok(None); @@ -8285,10 +8446,10 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::InstrumentedEndFor => { - // Stack: [value, receiver(iter), ...] + // Stack: [..., receiver(iter), null_or_index, value] // PyGen_Check: only fire STOP_ITERATION for generators let is_gen = self - .nth_value(1) + .nth_value(2) .downcast_ref::() .is_some(); let value = self.pop_value(); @@ -8300,6 +8461,7 @@ impl ExecutingFrame<'_> { } Instruction::InstrumentedEndSend => { let value = self.pop_value(); + self.pop_stackref_opt(); let receiver = self.pop_value(); // PyGen_Check || PyCoro_CheckExact let is_gen_or_coro = receiver @@ -8388,6 +8550,7 @@ impl ExecutingFrame<'_> { } Instruction::InstrumentedPopIter => { // BRANCH_RIGHT is fired by InstrumentedForIter, not here. + self.pop_stackref_opt(); self.pop_stackref(); Ok(None) } @@ -9473,7 +9636,7 @@ impl ExecutingFrame<'_> { value: Option, vm: &VirtualMachine, ) -> PyResult<()> { - let iter = self.top_value(); + let iter = self.nth_value(1); if iter.downcast_ref_if_exact::(vm).is_some() || iter.downcast_ref_if_exact::(vm).is_some() || iter.downcast_ref_if_exact::(vm).is_some() @@ -9497,14 +9660,14 @@ impl ExecutingFrame<'_> { Ok(()) } - /// Advance the iterator on top of stack. + /// Advance the iterator below its index-or-NULL stack slot. /// Returns `true` if iteration continued (item pushed), `false` if exhausted (jumped). fn execute_for_iter( &mut self, vm: &VirtualMachine, target: bytecode::Label, ) -> Result { - let top = self.top_value(); + let top = self.nth_value(1); // FOR_ITER_RANGE: bypass generic iterator protocol for range iterators if let Some(range_iter) = top.downcast_ref_if_exact::(vm) { @@ -9535,10 +9698,7 @@ impl ExecutingFrame<'_> { self.jump(self.for_iter_jump_target(target)); Ok(false) } - Err(next_error) => { - self.pop_stackref(); - Err(next_error) - } + Err(next_error) => Err(next_error), } } @@ -11440,8 +11600,8 @@ impl ExecutingFrame<'_> { ) { return; } - // Stack: [receiver, val] — receiver is at position 1 - let receiver = self.nth_value(1); + // Stack: [receiver, null_or_index, val] — receiver is at position 2 + let receiver = self.nth_value(2); let is_exact_gen_or_coro = receiver.downcast_ref_if_exact::(vm).is_some() || receiver.downcast_ref_if_exact::(vm).is_some(); if is_exact_gen_or_coro && !self.specialization_eval_frame_active(vm) { @@ -11707,7 +11867,7 @@ impl ExecutingFrame<'_> { ) { return; } - let iter = self.top_value(); + let iter = self.nth_value(1); let new_op = if iter.downcast_ref_if_exact::(vm).is_some() { Some(Instruction::ForIterRange) diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 67a5354b131..7852f761572 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -267,7 +267,7 @@ impl VirtualMachine { let is_profile_event = event.is_profile_event(); let is_opcode_event = event.is_opcode_event(); - let Some(frame_ref) = crate::frame::current_thread_frame_materialize(self) else { + let Some(frame_ref) = crate::frame::current_complete_frame_for_trace(self) else { return Ok(None); }; diff --git a/crates/vm/src/stdlib/marshal.rs b/crates/vm/src/stdlib/marshal.rs index 2624e9c0edd..1c70c964fd5 100644 --- a/crates/vm/src/stdlib/marshal.rs +++ b/crates/vm/src/stdlib/marshal.rs @@ -124,6 +124,8 @@ mod decl { struct WriterRefEntry { idx: u32, + // Keep the address valid until this whole per-write table is dropped. + _owner: PyObjectRef, /// Set between `reserve` and `complete` for the object kinds whose /// immutable representation cannot be rebuilt from a back-reference. incomplete: bool, @@ -158,8 +160,14 @@ mod decl { } fn reserve(&mut self, obj: &PyObject, incomplete: bool) -> u32 { let idx = self.next_idx; - self.map - .insert(obj.get_id(), WriterRefEntry { idx, incomplete }); + self.map.insert( + obj.get_id(), + WriterRefEntry { + idx, + incomplete, + _owner: obj.to_owned(), + }, + ); self.next_idx += 1; idx } @@ -233,8 +241,15 @@ mod decl { || obj.is(PyStopIteration::static_type()) || obj.downcast_ref::().is_some(); - // FLAG_REF: check if already written, otherwise reserve slot - if !is_singleton && let Some(rt) = refs.as_mut() { + // Observe actual ownership before lookup/reservation, as w_ref does. + // This only selects a wire representation: it does not grant exclusive + // access, and concurrent ownership changes can change the observation. + // Interned exact strings stay reference-tracked even when unique. + let use_ref = refs.is_some() + && !is_singleton + && (obj.strong_count() != 1 + || (obj.class().is(PyStr::static_type()) && obj.is_interned())); + if use_ref && let Some(rt) = refs.as_mut() { match rt.try_ref(buf, obj) { Ok(true) => return Ok(()), Ok(false) => {} @@ -247,7 +262,6 @@ mod decl { } } let type_pos = buf.len(); - let use_ref = refs.is_some() && !is_singleton; // A code or slice entry stays incomplete until its contents are // written: the reader rebuilds both from their fields, so a // back-reference issued while those fields are still being emitted @@ -390,14 +404,25 @@ mod decl { return Err(vm.new_value_error("marshalling code objects is disallowed")); } buf.write_u8(b'c'); - // `Literal` holds the exact object a constant was built from, so - // route `co_consts` back through the object writer: it reaches the - // values `BorrowedConstant` cannot describe and shares the one - // reference table the reader indexes against. - marshal::serialize_code_with(buf, &co.code, |buf, constant| { - let constant = PyObjectRef::from(constant.clone()); - write_object_depth(buf, &constant, refs, version, allow_code, vm, depth - 1) - })?; + // Keep this real getter owner through every field and error path. + let bytecode = co.co_code(vm); + marshal::serialize_code_with_fields( + buf, + &co.code, + |buf, field| { + write_object_depth( + buf, + co.marshal_field(field, &bytecode), + refs, + version, + allow_code, + vm, + depth - 1, + )?; + Ok(true) + }, + |_, _| Err(vm.new_runtime_error("unhandled runtime code field")), + )?; } else if let Some(sl) = obj.downcast_ref::() { if version < 5 { return Err(vm.new_value_error("unmarshallable object")); @@ -602,6 +627,25 @@ mod decl { } Ok(crate::builtins::PyCode::new_ref_with_bag(self.vm, code).into()) } + fn make_code_with_fields( + &self, + code: CodeObject, + constants: Vec, + _code_bytes: Vec, + fields: marshal::CodeFieldObjects, + ) -> Result { + if !self.allow_code { + return Err(self.remember_python_error( + self.vm + .new_value_error("unmarshalling code objects is disallowed"), + )); + } + // The actual tuple in fields is the surviving owner. Discard the + // compatibility channel's temporary child references first. + drop(constants); + let code = PyCode::from_marshaled_fields(code, fields, &self.vm.ctx)?; + Ok(code.into_ref(&self.vm.ctx).into()) + } fn make_stop_iter(&self) -> Result { Ok(self.vm.ctx.exceptions.stop_iteration.to_owned().into()) } @@ -893,4 +937,52 @@ mod decl { } Ok(()) } + #[cfg(test)] + mod writer_tests { + use super::*; + + #[test] + fn reservation_owns_once_until_table_drop() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let object: PyObjectRef = vm.ctx.new_list(vec![]).into(); + let mut table = WriterRefTable::new(); + let mut bytes = Vec::new(); + assert_eq!(object.strong_count(), 1); + assert_eq!(table.try_ref(&mut bytes, &object), Ok(false)); + table.reserve(&object, false); + assert_eq!(object.strong_count(), 2); + assert_eq!(table.try_ref(&mut bytes, &object), Ok(true)); + assert_eq!(object.strong_count(), 2); + drop(table); + assert_eq!(object.strong_count(), 1); + }); + } + + #[cfg(feature = "compiler")] + #[test] + fn constants_tuple_consumes_its_own_depth() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let code = vm + .compile("1234567", crate::compiler::Mode::Eval, "writer_depth.py") + .unwrap(); + assert_eq!(code.constants.len(), 1); + assert!(code.names.is_empty()); + assert!(code.varnames.is_empty()); + for (depth, succeeds) in [(2, false), (3, true)] { + let mut bytes = Vec::new(); + let mut refs = Some(WriterRefTable::new()); + let result = write_object_depth( + &mut bytes, + code.as_object(), + &mut refs, + 6, + true, + vm, + depth, + ); + assert_eq!(result.is_ok(), succeeds); + } + }); + } + } } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 28f1078625a..bb35df00016 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -668,7 +668,7 @@ pub mod sys { const NAME: &str = "rustpython"; // Keep implementation-specific bytecode caches separate. - let cache_tag = format!("{NAME}-{}{}", version::MAJOR, version::MINOR); + let cache_tag = version::PYC_CACHE_TAG; let ctx = &vm.ctx; py_namespace!(vm, { "name" => ctx.new_str(NAME), diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index aa180daeb96..930bef9cd29 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -404,8 +404,10 @@ pub(crate) fn instrument_code(code: &Py, events: u32) { // Find _co_firsttraceable: index of first RESUME instruction let mut first_traceable = None; - for_each_instruction(code, |i, op, _| { + for_each_instruction(code, |i, op, oparg| { if first_traceable.is_none() + && u32::from(oparg) & crate::bytecode::oparg::ResumeContext::LOCATION_MASK + != crate::bytecode::oparg::ResumeLocation::AtGenExprStart.as_u32() && matches!( op, Instruction::Resume { .. } @@ -421,7 +423,7 @@ pub(crate) fn instrument_code(code: &Py, events: u32) { // Phase 4: Place regular INSTRUMENTED_* opcodes whose event is active. // Walk by cache_entries so specialized CACHE payloads are not decoded as opcodes. { - let mut i = 0; + let mut i = first_traceable; while i < len { let op = code.code.instructions.read_op(i); let oparg = code.code.instructions.read_arg(i).as_u8(); diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 05eb12a2942..a2c6f42de62 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -68,9 +68,10 @@ pub const RUSTPYTHON_VERSION: &str = const { } }; -// Must be aligned to Lib/importlib/_bootstrap_external.py -// Matches CPython 3.14 (Include/internal/pycore_magic_number.h). -pub const PYC_MAGIC_NUMBER: u16 = 3627; +// Private, non-distributable reconstruction ABI. This is not CPython's magic. +// Change token and tag together for every incompatible development format. +pub const PYC_MAGIC_NUMBER: u16 = 0xDA16; +pub const PYC_CACHE_TAG: &str = "rustpython-315-devabi2"; // CPython format: magic_number | ('\r' << 16) | ('\n' << 24) // This protects against text-mode file reads diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index c3625828cc4..abe37625fdc 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2686,6 +2686,24 @@ impl VirtualMachine { &self, frame: FrameObjectRef, f: F, + ) -> PyResult { + self.with_frame_impl::(frame, f) + } + + /// Generator construction runs an incomplete frame whose caller link must + /// not escape. RETURN_GENERATOR copies the frame with previous=NULL in CPython. + pub(crate) fn with_generator_frame PyResult>( + &self, + frame: FrameObjectRef, + f: F, + ) -> PyResult { + self.with_frame_impl::(frame, f) + } + + fn with_frame_impl PyResult>( + &self, + frame: FrameObjectRef, + f: F, ) -> PyResult { self.check_recursive_call("")?; @@ -2732,7 +2750,7 @@ impl VirtualMachine { // Capture f_back before clearing previous so code holding a // reference to this FrameObject can walk the chain after return. - if !old_chain.is_null() { + if RETAIN_BACK && !old_chain.is_null() { let strong = frame.as_object().strong_count(); // Only set retained_back if someone else holds a reference (escaped) // AND the caller already has a FrameObject. Materializing the caller @@ -2743,8 +2761,11 @@ impl VirtualMachine { if strong > 1 { let mut guard = frame.iframe().cold().retained_back.lock(); if guard.is_none() { - let prev_iframe = unsafe { &*old_chain }; - if let Some(fo) = prev_iframe.frame_obj() { + // SAFETY: old_chain remains live until this synchronous entry exits. + let visible = unsafe { crate::frame::first_complete_iframe(old_chain) }; + if let Some(prev_iframe) = unsafe { visible.as_ref() } + && let Some(fo) = prev_iframe.frame_obj() + { *guard = Some(fo.to_owned()); } } @@ -2870,8 +2891,9 @@ impl VirtualMachine { // The slots above are the last write this thread makes into // the frame object, so it is now readable from anywhere. fo.iframe().detach(); - if !old_chain.is_null() { - let prev_iframe = unsafe { &*old_chain }; + // SAFETY: old_chain belongs to this still-active thread chain. + let visible = unsafe { crate::frame::first_complete_iframe(old_chain) }; + if let Some(prev_iframe) = unsafe { visible.as_ref() } { let back_fo = prev_iframe.materialize_chain(self); *fo.iframe().cold().retained_back.lock() = Some(back_fo); } @@ -3057,10 +3079,19 @@ impl VirtualMachine { if result.is_err() && self.use_tracing.get() && (!self.is_none(&self.profile_func.borrow()) - || frame - .iframe() - .cold_opt() - .is_some_and(|c| c.trace.lock().is_some())) + || if frame.iframe().is_incomplete_for_trace() { + crate::frame::current_complete_frame_for_trace(self).is_some_and(|visible| { + visible + .iframe() + .cold_opt() + .is_some_and(|c| c.trace.lock().is_some()) + }) + } else { + frame + .iframe() + .cold_opt() + .is_some_and(|c| c.trace.lock().is_some()) + }) { let ret_result = self.trace_event_what( TraceEvent::Return, diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 7c5d43d1643..be1cbce2603 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -55,8 +55,8 @@ impl VirtualMachine { } pub fn new_code(&self, code: impl IntoCodeObject) -> PyRef { - let code = code.into_code_object(self); - PyRef::new_ref(PyCode::new(code), self.ctx.types.code_type.to_owned(), None) + let code = code.into_pycode(self); + PyRef::new_ref(code, self.ctx.types.code_type.to_owned(), None) } pub fn new_tuple(&self, value: impl IntoPyTuple) -> PyTupleRef { diff --git a/crates/vm/tests/fixtures/private_cache_bytes.rs b/crates/vm/tests/fixtures/private_cache_bytes.rs new file mode 100644 index 00000000000..9865ebc85cf --- /dev/null +++ b/crates/vm/tests/fixtures/private_cache_bytes.rs @@ -0,0 +1,9 @@ +// Fresh private devabi2 fixture: compile("pass\n", "byte_exact/embedded_cache.py", "exec"). +const PRIVATE_PYC: &[u8] = &[ + 22, 218, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 115, 8, 0, 0, 0, 128, 0, 0, 0, 80, 7, 33, 0, 40, 1, 0, 0, 0, 78, 40, 0, + 0, 0, 0, 40, 0, 0, 0, 0, 115, 0, 0, 0, 0, 122, 28, 98, 121, 116, 101, 95, 101, 120, 97, 99, + 116, 47, 101, 109, 98, 101, 100, 100, 101, 100, 95, 99, 97, 99, 104, 101, 46, 112, 121, 122, 8, + 60, 109, 111, 100, 117, 108, 101, 62, 122, 8, 60, 109, 111, 100, 117, 108, 101, 62, 1, 0, 0, 0, + 115, 8, 0, 0, 0, 241, 3, 1, 1, 1, 217, 0, 4, 115, 0, 0, 0, 0, +]; diff --git a/crates/vm/tests/private_cache_reader.rs b/crates/vm/tests/private_cache_reader.rs new file mode 100644 index 00000000000..19a680a53ec --- /dev/null +++ b/crates/vm/tests/private_cache_reader.rs @@ -0,0 +1,38 @@ +//! Direct embedding API format boundary. Python behavior is tested in snippets. + +#![allow(clippy::tests_outside_test_module)] + +use rustpython_vm::{AsObject, Interpreter, builtins::PyCode}; + +include!("fixtures/private_cache_bytes.rs"); + +#[test] +fn foreign_formats_are_rejected_before_bootstrap_lookup() { + Interpreter::without_stdlib(Default::default()).enter(|vm| { + for magic in [3627u16, 3666, 0xDA15] { + let mut bytes = PRIVATE_PYC.to_vec(); + bytes[..2].copy_from_slice(&magic.to_le_bytes()); + let error = PyCode::from_pyc(&bytes, None, None, None, vm).unwrap_err(); + assert!(error.fast_isinstance(vm.ctx.exceptions.value_error)); + } + }); +} + +#[cfg(feature = "importlib")] +#[test] +fn private_format_loads_and_runs_with_external_bootstrap() { + Interpreter::without_stdlib(Default::default()).enter(|vm| { + assert_eq!( + &PRIVATE_PYC[..4], + &rustpython_vm::version::PYC_MAGIC_NUMBER_BYTES + ); + let code = PyCode::from_pyc(PRIVATE_PYC, None, None, None, vm).unwrap(); + assert_eq!(code.arg_count, 0); + let scope = vm.new_scope_with_main().unwrap(); + vm.run_pyc_bytes(PRIVATE_PYC, scope) + .unwrap_or_else(|error| { + vm.print_exception(&error); + panic!("private embedded pyc execution failed"); + }); + }); +} diff --git a/extra_tests/snippets/adaptive_public_export_315.py b/extra_tests/snippets/adaptive_public_export_315.py new file mode 100644 index 00000000000..2c1856121b5 --- /dev/null +++ b/extra_tests/snippets/adaptive_public_export_315.py @@ -0,0 +1,101 @@ +"""Public adaptive bytes preserve layout without exposing private constant opcodes.""" + +import dis +import marshal +import opcode +import sys + +FILENAME = "adaptive_public_export.py" +CONSTANT_SOURCE = ( + 'def warm_const():\n return ("ordinary constant payload", 123456789)\n' +) +LOOP_SOURCE = "def total(n):\n result = 0\n for value in range(n):\n result += value\n return result\n" +PUBLIC = set(opcode.opmap.values()) | set(opcode._specialized_opmap.values()) + + +def make(source, name): + namespace = {} + exec(compile(source, FILENAME, "exec"), namespace) + return namespace[name] + + +def check_layout(code, canonical, offsets): + adaptive = code._co_code_adaptive + assert len(adaptive) == len(canonical) + for offset in offsets: + assert adaptive[offset] in PUBLIC, (offset, adaptive[offset]) + assert adaptive[offset] not in (219, 220, 221) + assert adaptive[offset + 1] == canonical[offset + 1] + assert code.co_code == canonical + return adaptive + + +function = make(CONSTANT_SOURCE, "warm_const") +code = function.__code__ +canonical = code.co_code +offsets = tuple(i.offset for i in dis.get_instructions(code)) +cold = check_layout(code, canonical, offsets) +constant_offset = next( + i.offset for i in dis.get_instructions(code) if i.opname == "LOAD_CONST" +) +assert cold[constant_offset] == opcode.opmap["LOAD_CONST"] +before = marshal.dumps(code, 6) +for _ in range(1000): + assert function() == ("ordinary constant payload", 123456789) +assert marshal.dumps(code, 6) == before +adaptive = check_layout(code, canonical, offsets) +constants = [ + i for i in dis.get_instructions(code, adaptive=True) if i.opname == "LOAD_CONST" +] +assert len(constants) == 1 +assert constants[0].argval == ("ordinary constant payload", 123456789) +assert adaptive[constants[0].offset] == opcode.opmap["LOAD_CONST"] + +loop = make(LOOP_SOURCE, "total") +code = loop.__code__ +canonical = code.co_code +offsets = tuple(i.offset for i in dis.get_instructions(code)) +for _ in range(1000): + assert loop(10) == 45 +first = check_layout(code, canonical, offsets) +second = check_layout(code, canonical, offsets) +assert first == second +assert any(i.cache_info for i in dis.get_instructions(code)) +rows = tuple(dis.get_instructions(code, adaptive=True)) +assert tuple(i.offset for i in rows) == offsets +assert any(i.opcode in opcode._specialized_opmap.values() for i in rows) +assert all(first[i.offset] == i.opcode for i in rows) + +monitoring = sys.monitoring +available = [tool for tool in range(6) if monitoring.get_tool(tool) is None] +assert available +TOOL = available[0] +monitoring.use_tool_id(TOOL, "adaptive-public-export") +try: + for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION): + function = make(LOOP_SOURCE, "total") + code = function.__code__ + canonical = code.co_code + offsets = tuple(i.offset for i in dis.get_instructions(code)) + observed = [] + + def inspect(actual_code, location): + assert actual_code is code + if not observed: + # First adaptive export for this code happens under active wrappers. + observed.append(check_layout(actual_code, canonical, offsets)) + assert marshal.loads(marshal.dumps(actual_code, 6)).co_code == canonical + + monitoring.register_callback(TOOL, event, inspect) + monitoring.set_local_events(TOOL, code, event) + try: + assert function(10) == 45 + assert len(observed) == 1 + finally: + monitoring.set_local_events(TOOL, code, 0) + monitoring.register_callback(TOOL, event, None) + check_layout(code, canonical, offsets) + assert function(5) == 10 +finally: + monitoring.set_events(TOOL, 0) + monitoring.free_tool_id(TOOL) diff --git a/extra_tests/snippets/canonical_code_storage_315.py b/extra_tests/snippets/canonical_code_storage_315.py new file mode 100644 index 00000000000..71a9bd43a6a --- /dev/null +++ b/extra_tests/snippets/canonical_code_storage_315.py @@ -0,0 +1,216 @@ +"""Stable canonical code exports, including the first read during monitoring.""" + +import _frozen_importlib +import marshal +import sys +import types + +SOURCE = """def total(values): + result = 0 + for value in values: + result += value + return result +""" +FILENAME = "canonical_code_storage.py" + + +def make_function(): + namespace = {} + exec(compile(SOURCE, FILENAME, "exec"), namespace) + return namespace["total"] + + +reference = make_function() +expected = reference.__code__.co_code +assert reference.__code__.co_code is expected +assert reference.__code__.co_code == expected +for _ in range(100): + assert reference((1, 2, 3)) == 6 +assert reference.__code__.co_code is expected + +original = reference.__code__ +replaced = original.replace() +decoded = marshal.loads(marshal.dumps(original, 6)) +constructed = types.CodeType( + original.co_argcount, + original.co_posonlyargcount, + original.co_kwonlyargcount, + original.co_nlocals, + original.co_stacksize, + original.co_flags, + expected, + original.co_consts, + original.co_names, + original.co_varnames, + FILENAME, + original.co_name, + original.co_qualname, + original.co_firstlineno, + original.co_linetable, + original.co_exceptiontable, + original.co_freevars, + original.co_cellvars, +) +for code in (replaced, decoded, constructed): + first = code.co_code + assert first == expected + assert code.co_code is first + assert types.FunctionType(code, {})([2, 3, 4]) == 9 + assert code.co_code is first + +# Complete byte spellings from the pinned final 3.15 oracle for this ordinary +# yield-from fixture. Changing flags must normalize nonzero GET_ITER again. +delegate = compile( + "def delegate(values):\n yield from values\n", FILENAME, "exec" +).co_consts[0] +ordinary_delegate = bytes( + [ + 32, + 0, + 29, + 0, + 128, + 0, + 0, + 0, + 83, + 0, + 70, + 3, + 0, + 0, + 80, + 7, + 105, + 4, + 0, + 0, + 119, + 1, + 128, + 2, + 0, + 0, + 75, + 6, + 10, + 0, + 29, + 0, + 80, + 7, + 33, + 0, + 7, + 0, + 75, + 6, + 51, + 3, + 104, + 1, + ] +) +coroutine_delegate = bytes( + [ + 32, + 0, + 29, + 0, + 128, + 0, + 0, + 0, + 83, + 0, + 70, + 2, + 0, + 0, + 80, + 7, + 105, + 4, + 0, + 0, + 119, + 1, + 128, + 2, + 0, + 0, + 75, + 6, + 10, + 0, + 29, + 0, + 80, + 7, + 33, + 0, + 7, + 0, + 75, + 6, + 51, + 3, + 104, + 1, + ] +) +assert delegate.co_code == ordinary_delegate +changed = delegate.replace(co_flags=delegate.co_flags | 0x100) +assert changed.co_code == coroutine_delegate +assert changed.co_code is changed.co_code +restored = changed.replace(co_flags=delegate.co_flags) +assert restored.co_code == ordinary_delegate +for code in (delegate, changed, restored): + first = code.co_code + assert list(types.FunctionType(code, {})([1, 2])) == [1, 2] + assert code.co_code is first + +frozen_code = _frozen_importlib._find_spec.__code__ +assert _frozen_importlib.__spec__.origin == "frozen" +frozen_bytes = frozen_code.co_code +assert frozen_code.co_code is frozen_bytes + +monitoring = sys.monitoring +available = [tool for tool in range(6) if monitoring.get_tool(tool) is None] +assert available +TOOL = available[0] +monitoring.use_tool_id(TOOL, "canonical-code-storage") +try: + for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION): + function = make_function() + code = function.__code__ + observed = [] + + def inspect(actual_code, location): + assert actual_code is code + if not observed: + # This is this code object's first co_code read. A separate + # compilation supplied expected bytes before monitoring began. + first = actual_code.co_code + assert first == expected + assert actual_code.co_code is first + replacement = actual_code.replace() + assert replacement.co_code == expected + assert replacement.co_code is replacement.co_code + assert types.FunctionType(replacement, {})([3, 4]) == 7 + observed.append(first) + + monitoring.register_callback(TOOL, event, inspect) + monitoring.set_local_events(TOOL, code, event) + try: + assert function([1, 2, 3]) == 6 + assert observed + assert code.co_code is observed[0] + finally: + monitoring.set_local_events(TOOL, code, 0) + monitoring.register_callback(TOOL, event, None) + assert code.co_code is observed[0] + assert function([4, 5]) == 9 +finally: + monitoring.set_events(TOOL, 0) + monitoring.free_tool_id(TOOL) diff --git a/extra_tests/snippets/canonical_code_writer_315.py b/extra_tests/snippets/canonical_code_writer_315.py new file mode 100644 index 00000000000..cb3bc3133b3 --- /dev/null +++ b/extra_tests/snippets/canonical_code_writer_315.py @@ -0,0 +1,118 @@ +"""Actual code fields, writer ownership, and canonical monitored serialization.""" + +import gc +import marshal +import sys +import types +import weakref + +SOURCE = "def sample(a):\n try:\n return a + marker\n except TypeError:\n return 0\n" +FILENAME = "canonical_code_writer.py" +code = compile(SOURCE, FILENAME, "exec").co_consts[0] +assert code.co_linetable and code.co_exceptiontable and code.co_names + +for version in range(7): + for field_name in ("co_consts", "co_names", "co_linetable", "co_exceptiontable"): + field = getattr(code, field_name) + for field_first in (False, True): + graph = (field, code) if field_first else (code, field) + decoded = marshal.loads(marshal.dumps(graph, version)) + other, restored = decoded if field_first else reversed(decoded) + assert getattr(restored, field_name) == field + assert restored.co_code == code.co_code + if version >= 3: + assert getattr(restored, field_name) is other + value = {"tuple": (1, "item"), "set": {2, 3}, "frozen": frozenset((4, 5))} + assert marshal.loads(marshal.dumps(value, version)) == value + +shared = [1, 2] +restored = marshal.loads(marshal.dumps((shared, shared), 6)) +assert restored[0] is restored[1] +cycle = [] +cycle.append(cycle) +restored = marshal.loads(marshal.dumps(cycle, 6)) +assert restored[0] is restored +cycle.clear() +restored.clear() + +# Code and slice back-references cannot be completed by the public reader. +for use_code in (False, True): + child = [] + owner = code.replace(co_consts=(child,)) if use_code else slice(child, None, None) + child.append(owner) + try: + marshal.dumps(owner, 6) + except ValueError: + pass + else: + raise AssertionError("incomplete immutable cycle accepted") + child.clear() + del owner, child + + +class Unsupported: + pass + + +def failed_write(in_set): + value = Unsupported() + reference = weakref.ref(value) + graph = {value} if in_set else [value] + try: + marshal.dumps(graph, 6) + except ValueError: + pass + else: + raise AssertionError("unsupported value accepted") + return reference + + +for in_set in (False, True): + reference = failed_write(in_set) + gc.collect() + assert reference() is None + +# Successful reservations must also release their owners when dumps returns. +clone = code.replace() +reference = weakref.ref(clone) +marshal.dumps((clone, clone), 6) +del clone +gc.collect() +assert reference() is None + +# Serialization, rather than a preceding getter, must obtain canonical bytes +# during the first monitored execution, including opcodes with inline caches. +LOOP = "def total(values):\n result = 0\n for value in values:\n result += value\n return result\n" +expected = compile(LOOP, FILENAME, "exec").co_consts[0].co_code +monitoring = sys.monitoring +available = [tool for tool in range(6) if monitoring.get_tool(tool) is None] +assert available +tool = available[0] +monitoring.use_tool_id(tool, "canonical-code-writer") +try: + for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION): + namespace = {} + exec(compile(LOOP, FILENAME, "exec"), namespace) + function = namespace["total"] + target = function.__code__ + observed = [] + + def inspect(actual, location): + assert actual is target + if not observed: + payload = marshal.dumps(actual, 6) + restored = marshal.loads(payload) + assert restored.co_code == expected + assert types.FunctionType(restored, {})([2, 3]) == 5 + observed.append(payload) + + monitoring.register_callback(tool, event, inspect) + monitoring.set_local_events(tool, target, event) + try: + assert function([1, 2, 3]) == 6 + assert len(observed) == 1 + finally: + monitoring.set_local_events(tool, target, 0) + monitoring.register_callback(tool, event, None) +finally: + monitoring.free_tool_id(tool) diff --git a/extra_tests/snippets/compiled_constant_sharing_315.py b/extra_tests/snippets/compiled_constant_sharing_315.py new file mode 100644 index 00000000000..ab0b7c8cf92 --- /dev/null +++ b/extra_tests/snippets/compiled_constant_sharing_315.py @@ -0,0 +1,160 @@ +"""Compilation-local field identities, with actual complete-stream witnesses. + +This does not claim full compiler first-winner chronology or native root parity. +NaN/code/slice key exclusions are tested structurally on the Rust Basic route. +""" + +import ast +import gc +import marshal +import types +import weakref + +SOURCE = """def left(x): + try: + return x + 12345678901234567890 + except TypeError: + return ('shared payload !', b'shared bytes !', (98765432109876543210,)) +def right(x): + try: + return x + 12345678901234567890 + except TypeError: + return ('shared payload !', b'shared bytes !', (98765432109876543210,)) +def outer(): + def inner(x): + return x + 12345678901234567890 + return inner +def member_left(x): + return x in {12345678901234567890, 98765432109876543210} +def member_right(x): + return x in {12345678901234567890, 98765432109876543210} +def import_scope(): + import ordinary_name +""" + + +def children(code): + return { + value.co_name: value + for value in code.co_consts + if isinstance(value, types.CodeType) + } + + +def constant(code, kind): + values = [value for value in code.co_consts if type(value) is kind] + assert len(values) == 1, (kind, values) + return values[0] + + +def check_compile(source): + root = compile(source, "compile_sharing.py", "exec") + codes = children(root) + left, right = codes["left"], codes["right"] + assert left is not right + for name in ("co_consts", "co_names", "co_linetable", "co_exceptiontable"): + first, second = getattr(left, name), getattr(right, name) + assert first, name # Every tested owner field is nonempty. + assert first is second, name + assert left.co_varnames == right.co_varnames == ("x",) + assert left.co_varnames is not right.co_varnames + assert left.co_code == right.co_code + assert left.co_code is not right.co_code + inner = children(codes["outer"])["inner"] + assert constant(left, int) is constant(inner, int) + first_set = constant(codes["member_left"], frozenset) + second_set = constant(codes["member_right"], frozenset) + assert first_set is second_set + assert any(value is constant(left, int) for value in first_set) + # Derived varnames are separately cached, even when the internal names and + # localsplusnames tuple owners can be the same compiler cache winner. + imported = codes["import_scope"] + assert imported.co_names == imported.co_varnames == ("ordinary_name",) + assert imported.co_names is not imported.co_varnames + for version in range(3, 7): + stream = marshal.dumps( + [left, right, left.co_consts, left.co_linetable], version + ) + decoded = marshal.loads(stream) + assert decoded[0].co_consts is decoded[1].co_consts is decoded[2] + assert decoded[0].co_linetable is decoded[1].co_linetable is decoded[3] + return root + + +source_root = check_compile(SOURCE) +ast_root = check_compile(ast.parse(SOURCE)) +source_left = children(source_root)["left"] +ast_left = children(ast_root)["left"] +assert source_left.co_consts == ast_left.co_consts +assert source_left.co_consts is not ast_left.co_consts +assert constant(source_left, int) is not constant(ast_left, int) +assert source_left.co_linetable == ast_left.co_linetable +assert source_left.co_linetable is not ast_left.co_linetable + +# Safe owner release: an externally retained constants tuple outlives its code, +# and does not itself retain the code. No cyclic-GC policy is asserted. +retained = source_left.co_consts +reference = weakref.ref(source_left) +del source_left, source_root +gc.collect() +assert reference() is None +assert 12345678901234567890 in retained + +# Scalar typed and signed-zero distinctions remain intact in source compilation. +scalar = compile("value = (False, 0, 0.0, -0.0, 0j, -0j)", "compile_sharing.py", "exec") +values = constant(scalar, tuple) +assert [type(value) for value in values] == [bool, int, float, float, complex, complex] +assert values[2] is not values[3] +assert values[4] is not values[5] +assert marshal.dumps(values[2]) != marshal.dumps(values[3]) +assert marshal.dumps(values[4]) != marshal.dumps(values[5]) + +# Public construction/replace are not a new compilation session. Retain their +# actual supplied tuple owners rather than pooling equal values globally. +base = children(ast_root)["left"] +first = tuple([None, bytes(bytearray(b"public constructor bytes !"))]) +second = tuple([None, bytes(bytearray(b"public constructor bytes !"))]) +a = base.replace(co_consts=first) +b = base.replace(co_consts=second) +assert a.co_consts is first and b.co_consts is second +assert a.co_consts == b.co_consts and a.co_consts is not b.co_consts +assert base.co_consts is not first + +# AST constants retain nonfinite values. Distinct NaN values keep their own +# aggregates while ordinary equal byte siblings can still share within the pool. +for make_nan in ( + lambda: float("nan"), + lambda: complex(float("nan"), 1), + lambda: complex(1, float("nan")), +): + left_value = (make_nan(), bytes(bytearray(b"safe sibling next to NaN"))) + right_value = (make_nan(), bytes(bytearray(b"safe sibling next to NaN"))) + assert left_value[1] is not right_value[1] + tree = ast.Expression(body=ast.Constant(value=(left_value, right_value))) + nan_code = compile(ast.fix_missing_locations(tree), "compile_sharing.py", "eval") + left_value, right_value = constant(nan_code, tuple) + assert left_value is not right_value + assert left_value[0] is not right_value[0] + assert left_value[1] is right_value[1] + for version in range(7): + restored = marshal.loads(marshal.dumps((left_value, right_value), version)) + assert type(restored[0][0]) is type(left_value[0]) + assert restored[0][1] == restored[1][1] == b"safe sibling next to NaN" + if version >= 3: + assert restored[0][1] is restored[1][1] + +infinities = ( + float("inf"), + float("-inf"), + complex(float("inf"), float("-inf")), + complex(float("-inf"), float("inf")), +) +# Build equal but independently allocated input objects to exercise compilation. +repeated = tuple(type(value)(str(value)) for value in infinities) +assert all(a is not b for a, b in zip(infinities, repeated)) +tree = ast.Expression(body=ast.Constant(value=infinities + repeated)) +inf_code = compile(ast.fix_missing_locations(tree), "compile_sharing.py", "eval") +values = constant(inf_code, tuple) +assert values[:4] == values[4:] +assert all(a is b for a, b in zip(values[:4], values[4:])) +assert len({id(value) for value in values}) == 4 diff --git a/extra_tests/snippets/compiler_315_warmed_export.py b/extra_tests/snippets/compiler_315_warmed_export.py new file mode 100644 index 00000000000..4034f515327 --- /dev/null +++ b/extra_tests/snippets/compiler_315_warmed_export.py @@ -0,0 +1,70 @@ +"""Ordinary warmed exports retain public opcodes and unchanged raw fields.""" + +import marshal +import sys +import types + +if sys.version_info < (3, 15): + sys.exit(0) + + +source = """ +def direct(x): + return x + 1000 +def looping(xs): + total = 0 + for x in xs: + total += direct(x) + return total +def delegate(xs): + yield from xs +""" +root = compile(source, "byte_exact/warmed_export.py", "exec") +namespace = {} +exec(root, namespace) +codes = [value for value in root.co_consts if isinstance(value, types.CodeType)] +before = [(code.co_code, code.co_linetable, code.co_exceptiontable) for code in codes] +for _ in range(100): + assert namespace["looping"]([1, 2, 3]) == 3006 + assert list(namespace["delegate"]([1, 2])) == [1, 2] +after = [(code.co_code, code.co_linetable, code.co_exceptiontable) for code in codes] +assert before == after + +# Generated cache widths in final 3.15 pycore_opcode_metadata.h. Operand bytes +# are not opcode positions and must not be rejected by a byte-membership test. +cache_widths = { + 128: 1, + 37: 3, + 36: 1, + 105: 1, + 68: 1, + 118: 1, + 109: 4, + 91: 4, + 95: 1, + 79: 9, + 54: 1, + 55: 1, + 74: 1, + 102: 1, + 99: 1, + 100: 1, + 101: 1, + 70: 1, + 50: 3, + 53: 3, + 4: 1, + 42: 5, +} +for code in codes: + exported = code.co_code + index = 0 + while index < len(exported): + opcode = exported[index] + assert opcode not in (219, 220, 221) + index += 2 * (1 + cache_widths.get(opcode, 0)) + assert index == len(exported) + loaded = marshal.loads(marshal.dumps(code, 6)) + assert loaded.co_code == exported + assert loaded.co_linetable == code.co_linetable + assert loaded.co_exceptiontable == code.co_exceptiontable diff --git a/extra_tests/snippets/iterator_contract_315.py b/extra_tests/snippets/iterator_contract_315.py new file mode 100644 index 00000000000..22596f21489 --- /dev/null +++ b/extra_tests/snippets/iterator_contract_315.py @@ -0,0 +1,501 @@ +"""CPython 3.15 iterator-pair, generator-creation, and SEND regression checks.""" + + +def test_iterator_pairs_survive_specialized_loops_and_unwinding(): + class Iterator: + def __init__(self): + self.index = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.index == 4: + raise StopIteration + value = self.index + self.index += 1 + return value + + def values(): + yield from range(4) + + def consume(items): + total = 0 + for item in items: + total += item + return total + + def leave(items): + for item in items: + for inner in range(3): + if inner == 1: + return item + inner + return -1 + + for warmup in range(200): + assert consume(range(4)) == 6 + assert consume([0, 1, 2, 3]) == 6 + assert consume((0, 1, 2, 3)) == 6 + assert consume(values()) == 6 + assert consume(Iterator()) == 6 + assert leave(range(4)) == 1 + assert [x * y for x in range(3) for y in range(2)] == [0, 0, 0, 1, 0, 2] + for x in range(5): + if x == 2: + break + assert x == 2 + + +def test_eager_generator_prefix_preserves_laziness_and_created_state(): + events = [] + + class Iterable: + def __iter__(self): + events.append("iter") + return iter((4, 5)) + + def body(value): + events.append(value) + return value + + g = (body(value) for value in Iterable()) + assert events == ["iter"] + assert not g.gi_suspended + try: + g.send(1) + except TypeError: + pass + else: + raise AssertionError("created generator accepted non-None") + assert events == ["iter"] + assert next(g) == 4 + assert events == ["iter", 4] + assert g.gi_suspended + assert list(g) == [5] + + class BadIterable: + def __iter__(self): + raise ValueError("eager") + + try: + (value for value in BadIterable()) + except ValueError as error: + assert error.args == ("eager",) + else: + raise AssertionError("iterator acquisition was deferred") + + def outer(value): + def generator(): + events.append("body") + yield value + + return generator() + + g = outer(17) + assert "body" not in events + g.close() + assert "body" not in events + assert list(outer(19)) == [19] + + +def test_send_pairs_support_delegation_and_throw_cleanup(): + def leaf(): + sent = yield 1 + return sent + + def chain(depth): + if depth: + return (yield from chain(depth - 1)) + return (yield from leaf()) + + for warmup in range(200): + g = chain(12) + assert next(g) == 1 + assert g.gi_yieldfrom is not None + try: + g.send(17) + except StopIteration as error: + assert error.value == 17 + else: + raise AssertionError("delegation did not finish") + + class Delegate: + def __iter__(self): + return self + + def __next__(self): + return 3 + + def send(self, value): + return value + + def throw(self, error): + raise StopIteration(23) + + def custom(): + result = yield from Delegate() + yield result + + g = custom() + assert next(g) == 3 + assert g.send(9) == 9 + assert g.throw(ValueError) == 23 + assert list(g) == [] + + class RaisingDelegate(Delegate): + def throw(self, error): + raise RuntimeError("delegate") + + def raises(): + try: + yield from RaisingDelegate() + except RuntimeError: + yield "caught" + + g = raises() + assert next(g) == 3 + assert g.throw(RuntimeError) == "caught" + assert list(g) == [] + + events = [] + + def close_leaf(): + try: + yield 1 + finally: + events.append("closed") + + def close_outer(): + yield from close_leaf() + + g = close_outer() + assert next(g) == 1 + g.close() + assert events == ["closed"] + + +def test_await_and_async_generator_expression_keep_iterator_shapes(): + events = [] + + class AsyncIterator: + def __init__(self): + self.index = 0 + + def __aiter__(self): + events.append("aiter") + return self + + async def __anext__(self): + if self.index == 3: + raise StopAsyncIteration + value = self.index + self.index += 1 + return value + + g = (value * 2 async for value in AsyncIterator()) + assert events == ["aiter"] + assert not g.ag_running + + async def consume(): + result = [] + async for value in g: + result.append(value) + return result + + coro = consume() + try: + coro.send(None) + except StopIteration as error: + assert error.value == [0, 2, 4] + else: + raise AssertionError("unexpected coroutine suspension") + assert events == ["aiter"] + + class Awaitable: + def __await__(self): + sent = yield "pause" + return sent + + async def awaits(): + return await Awaitable() + + for warmup in range(200): + coro = awaits() + assert coro.send(None) == "pause" + assert coro.cr_await is not None + try: + coro.send(31) + except StopIteration as error: + assert error.value == 31 + else: + raise AssertionError("await did not finish") + + +test_iterator_pairs_survive_specialized_loops_and_unwinding() +test_eager_generator_prefix_preserves_laziness_and_created_state() +test_send_pairs_support_delegation_and_throw_cleanup() +test_await_and_async_generator_expression_keep_iterator_shapes() + + +def test_eager_prefix_is_untraceable_but_reports_monitoring_errors(): + import sys + + trace = [] + + class Good: + def __iter__(self): + return iter([1]) + + class Bad: + def __iter__(self): + raise ValueError("eager iterator") + + def create(iterable): + return (value for value in iterable) + + def tracer(frame, event, arg): + if frame.f_code.co_name in ("create", "", "__iter__"): + trace.append((frame.f_code.co_name, event)) + return tracer + + sys.settrace(tracer) + try: + generator = create(Good()) + generator.close() + try: + create(Bad()) + except ValueError as error: + names = [] + traceback = error.__traceback__ + while traceback is not None: + names.append(traceback.tb_frame.f_code.co_name) + traceback = traceback.tb_next + else: + raise AssertionError("expected eager iterator error") + finally: + sys.settrace(None) + assert not any(name == "" for name, event in trace) + assert trace == [ + ("create", "call"), + ("create", "line"), + ("__iter__", "call"), + ("__iter__", "line"), + ("__iter__", "return"), + ("create", "return"), + ("create", "call"), + ("create", "line"), + ("__iter__", "call"), + ("__iter__", "line"), + ("__iter__", "exception"), + ("__iter__", "return"), + ("create", "exception"), + ("create", "return"), + ("create", "exception"), + ("create", "return"), + ] + assert ("__iter__", "call") in trace + assert ("__iter__", "exception") in trace + assert names == [ + "test_eager_prefix_is_untraceable_but_reports_monitoring_errors", + "create", + "__iter__", + ] + + monitoring = sys.monitoring + records = [] + event_names = ( + "PY_START", + "PY_RESUME", + "PY_UNWIND", + "RAISE", + "LINE", + "INSTRUCTION", + ) + monitoring.use_tool_id(2, "iterator-contract-315") + try: + for name in event_names: + event = getattr(monitoring.events, name) + + def callback(code, *args, name=name): + if code.co_name == "": + records.append(name) + + monitoring.register_callback(2, event, callback) + monitoring.set_events( + 2, sum(getattr(monitoring.events, name) for name in event_names) + ) + generator = create(Good()) + generator.close() + assert records == [] + try: + create(Bad()) + except ValueError: + pass + else: + raise AssertionError("expected monitored eager iterator error") + finally: + monitoring.set_events(2, 0) + monitoring.free_tool_id(2) + assert records == ["RAISE", "PY_UNWIND"] + + +test_eager_prefix_is_untraceable_but_reports_monitoring_errors() + + +def test_created_generator_frames_do_not_retain_materialized_caller(): + import sys + + def generator(): + yield 1 + + async def coroutine(): + return 1 + + async def async_generator(): + yield 1 + + def create(): + caller = sys._getframe() + return ( + (value for value in [1]), + generator(), + coroutine(), + async_generator(), + caller, + ) + + genexpr, gen, coro, agen, caller = create() + assert caller.f_code.co_name == "create" + assert genexpr.gi_frame.f_back is None + assert gen.gi_frame.f_back is None + assert coro.cr_frame.f_back is None + assert agen.ag_frame.f_back is None + genexpr.close() + gen.close() + coro.close() + close = agen.aclose() + try: + close.send(None) + except StopIteration: + pass + else: + raise AssertionError("unstarted async generator close suspended") + + +test_created_generator_frames_do_not_retain_materialized_caller() + + +def test_instrumented_iteration_and_send_keep_both_iterator_slots(): + import sys + + def iterate(values): + result = [] + for value in values: + result.append(value) + return result + + def leaf(): + yield 1 + return 9 + + def delegate(): + return (yield from leaf()) + + monitoring = sys.monitoring + records = [] + event_names = ("BRANCH_LEFT", "BRANCH_RIGHT", "STOP_ITERATION", "PY_YIELD") + monitoring.use_tool_id(2, "instrumented-iterator-contract-315") + try: + for name in event_names: + event = getattr(monitoring.events, name) + + def callback(code, *args, name=name): + if code.co_name in ("iterate", "delegate"): + records.append((code.co_name, name)) + + monitoring.register_callback(2, event, callback) + monitoring.set_events( + 2, sum(getattr(monitoring.events, name) for name in event_names) + ) + assert iterate(range(3)) == [0, 1, 2] + assert iterate([0, 1, 2]) == [0, 1, 2] + assert iterate((0, 1, 2)) == [0, 1, 2] + assert iterate(value for value in range(3)) == [0, 1, 2] + generator = delegate() + assert next(generator) == 1 + try: + next(generator) + except StopIteration as error: + assert error.value == 9 + else: + raise AssertionError("instrumented delegate did not finish") + finally: + monitoring.set_events(2, 0) + monitoring.free_tool_id(2) + assert ("iterate", "BRANCH_LEFT") in records + assert ("iterate", "BRANCH_RIGHT") in records + assert ("delegate", "PY_YIELD") in records + assert ("delegate", "STOP_ITERATION") in records + + +test_instrumented_iteration_and_send_keep_both_iterator_slots() + + +def test_eager_iterator_frame_inspection_skips_incomplete_generator(): + import sys + + iterator_frames = [] + caller_frames = [] + seen = [] + + class Iterable: + def __iter__(self): + frame = sys._getframe() + caller = sys._getframe(1) + iterator_frames.append(frame) + caller_frames.append(caller) + seen.append((caller.f_code.co_name, frame.f_back.f_code.co_name)) + return iter([7]) + + def create(): + return (value for value in Iterable()) + + generator = create() + assert seen == [("create", "create")] + # Both real frames have returned; retained f_back must still refer to + # the complete creator rather than its hidden generator preamble. + assert iterator_frames[0].f_back is caller_frames[0] + assert iterator_frames[0].f_back.f_code.co_name == "create" + assert generator.gi_frame.f_back is None + assert list(generator) == [7] + + +test_eager_iterator_frame_inspection_skips_incomplete_generator() + + +def test_unstarted_coroutine_warning_survives_creation_preamble(): + import gc + import warnings + + async def pending(): + return 7 + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", RuntimeWarning) + coroutine = pending() + del coroutine + gc.collect() + assert len(caught) == 1 + assert caught[0].category is RuntimeWarning + assert str(caught[0].message).endswith("pending' was never awaited") + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", RuntimeWarning) + coroutine = pending() + coroutine.close() + del coroutine + gc.collect() + assert caught == [] + + +test_unstarted_coroutine_warning_survives_creation_preamble() diff --git a/extra_tests/snippets/marshal_actual_owners_315.py b/extra_tests/snippets/marshal_actual_owners_315.py new file mode 100644 index 00000000000..d4dd4c36252 --- /dev/null +++ b/extra_tests/snippets/marshal_actual_owners_315.py @@ -0,0 +1,136 @@ +"""Actual-owner reference selection: complete streams and bounded lifetimes.""" + +import gc +import marshal +import sys +import weakref + +# These full certified streams describe an explicitly aliased root and a +# repeated child. Direct/named ownership streams are retained separately; this +# golden does not claim root-transport parity for those other Python paths. +GOLDENS = ( + "28020000005b030000006604312e32357801320133730200000041425b030000006604312e3235780132013373020000004142", + "28020000005b030000006604312e32357801320133730200000041425b030000006604312e3235780132013373020000004142", + "28020000005b0300000067000000000000f43f7900000000000000400000000000000840730200000041425b0300000067000000000000f43f790000000000000040000000000000084073020000004142", + "a802000000db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", + "a902db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", + "a902db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", + "a902db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", +) + + +def make_graph(): + child = [float("1.25"), complex("2+3j"), bytes(bytearray([65, 66]))] + return (child, child) + + +for version, expected in enumerate(GOLDENS): + graph = make_graph() + aliases = [graph, graph] + payload = marshal.dumps(graph, version) + assert aliases[0] is aliases[1] is graph + assert payload == bytes.fromhex(expected) + restored = marshal.loads(payload) + assert restored == graph + if version >= 3: + assert restored[0] is restored[1] + +for version in range(7): + text = sys.intern("".join(("ordinary", "_interned", "_owner"))) + objects = { + "dict": {"value": [1, 2], "other": (3, 4)}, + "set": {1.25, 2.75}, + "frozen": frozenset((1.25, 2.75)), + "interned": (text, text), + } + restored = marshal.loads(marshal.dumps(objects, version)) + assert restored == objects + if version >= 3: + assert restored["interned"][0] is restored["interned"][1] + +for version in range(3, 7): + child = [] + graph = {"first": child, "second": child} + child.append(graph) + restored = marshal.loads(marshal.dumps(graph, version)) + assert restored["first"] is restored["second"] + assert restored["first"][0] is restored + child.clear() + restored["first"].clear() + + +class Unsupported: + pass + + +def failed_writer(in_set): + value = Unsupported() + reference = weakref.ref(value) + graph = {value} if in_set else {"value": value} + try: + marshal.dumps(graph, 6) + except ValueError: + pass + else: + raise AssertionError("unsupported value accepted") + return reference + + +for in_set in (False, True): + for _ in range(8): + reference = failed_writer(in_set) + gc.collect() + assert reference() is None + +# Actual weak-reference acquisition and release must not leak a writer owner. +# This checks bounded lifetime/acceptance, not an exclusive-count guarantee. +code = compile("pass", "ordinary_owner.py", "exec") +reference = weakref.ref(code) +for _ in range(32): + acquired = reference() + assert acquired is code + marshal.loads(marshal.dumps((code, acquired), 6)) + del acquired +try: + marshal.dumps(code, 6, allow_code=False) +except ValueError: + pass +else: + raise AssertionError("disallowed code accepted") +del code +gc.collect() +assert reference() is None + +# Observational stress only: scheduling need not overlap, and this does not +# establish snapshot semantics or CPython free-threaded count equivalence. +import _thread + +code = compile("value = 7", "ordinary_owner.py", "exec") +reference = weakref.ref(code) +done = _thread.allocate_lock() +done.acquire() +errors = [] + + +def acquire_owners(): + try: + for _ in range(512): + value = reference() + assert value is not None + del value + except BaseException as error: + errors.append(error) + finally: + done.release() + + +_thread.start_new_thread(acquire_owners, ()) +for _ in range(64): + restored = marshal.loads(marshal.dumps(code, 6)) + assert restored.co_code == code.co_code +assert done.acquire(timeout=10) +done.release() +assert not errors +del code, restored +gc.collect() +assert reference() is None diff --git a/extra_tests/snippets/opcode_calling_315.py b/extra_tests/snippets/opcode_calling_315.py new file mode 100644 index 00000000000..49a6f9fdc88 --- /dev/null +++ b/extra_tests/snippets/opcode_calling_315.py @@ -0,0 +1,179 @@ +"""Local _opcode conversion/order contract; shared binder wording is separate.""" + +import _opcode +import opcode +import struct + +NOP = opcode.opmap["NOP"] +events = [] + + +class Indexed: + def __init__(self, value): + self.value = value + + def __index__(self): + events.append("index") + return self.value + + +class IndexErrorValue: + def __index__(self): + events.append("index-error") + raise RuntimeError("ordinary index failure") + + +class IntOnly: + def __int__(self): + events.append("int") + return NOP + + +class IntSubclass(int): + def __index__(self): + raise AssertionError("int subclass index override must not be called") + + +class BoolError: + def __bool__(self): + events.append("bool") + raise AssertionError("jump must not be truth-tested") + + +def error(kind, call, message=None): + try: + call() + except kind as exc: + if message is not None: + assert str(exc) == message, str(exc) + else: + raise AssertionError("expected " + kind.__name__) + + +assert _opcode.stack_effect(NOP) == 0 +assert _opcode.stack_effect(NOP, None) == 0 +assert _opcode.stack_effect(opcode.opmap["BUILD_LIST"], 3) == -2 +error( + ValueError, + lambda: _opcode.stack_effect(opcode.opmap["BUILD_LIST"], -1), + "invalid opcode or oparg", +) +assert _opcode.stack_effect(Indexed(NOP), Indexed(-1)) == 0 +assert events == ["index", "index"] +assert _opcode.stack_effect(IntSubclass(NOP), IntSubclass(-1)) == 0 +for jump in (None, False, True): + assert _opcode.stack_effect(NOP, jump=jump) == 0 +for jump in (0, 1, "", [], Indexed(1), BoolError()): + events.clear() + error( + ValueError, + lambda: _opcode.stack_effect(NOP, jump=jump), + "stack_effect: jump must be False, True or None", + ) + assert events == [] + +for function in ( + _opcode.is_valid, + _opcode.has_arg, + _opcode.has_const, + _opcode.has_name, + _opcode.has_jump, + _opcode.has_free, + _opcode.has_local, + _opcode.has_exc, +): + expected = function(NOP) + events.clear() + assert function(Indexed(NOP)) == expected + assert events == ["index"] + assert function(opcode=IntSubclass(NOP)) == expected + for value in (-(1 << 31) - 1, 1 << 31, 1 << 100): + error( + OverflowError, + lambda: function(value), + "Python int too large to convert to C int", + ) + events.clear() + error(TypeError, lambda: function(Indexed(NOP), 0)) + assert events == [] + error(TypeError, lambda: function(IntOnly())) + assert events == [] + +# Constant-effect NOP keeps extreme conversion values out of arithmetic formulas. +long_bits = struct.calcsize("@l") * 8 +for value in ( + -(1 << 31), + (1 << 31) - 1, + -(1 << (long_bits - 1)), + (1 << (long_bits - 1)) - 1, +): + assert _opcode.stack_effect(NOP, value) == 0 +for value in (-(1 << (long_bits - 1)) - 1, 1 << (long_bits - 1)): + error( + OverflowError, + lambda: _opcode.stack_effect(NOP, value), + "Python int too large to convert to C long", + ) +for value in (-(1 << 31) - 1, 1 << 31): + error( + OverflowError, + lambda: _opcode.stack_effect(value), + "Python int too large to convert to C int", + ) + +events.clear() +error( + RuntimeError, + lambda: _opcode.stack_effect(IndexErrorValue(), IndexErrorValue()), + "ordinary index failure", +) +assert events == ["index-error"] +events.clear() +error( + OverflowError, + lambda: _opcode.stack_effect(1 << 100, IndexErrorValue()), + "Python int too large to convert to C int", +) +assert events == [] +error( + RuntimeError, + lambda: _opcode.stack_effect(-1, IndexErrorValue(), jump=BoolError()), + "ordinary index failure", +) +assert events == ["index-error"] +events.clear() +error( + ValueError, + lambda: _opcode.stack_effect(-1, 0, jump=BoolError()), + "stack_effect: jump must be False, True or None", +) +assert events == [] +for call in ( + lambda: _opcode.stack_effect(Indexed(NOP), 0, False), + lambda: _opcode.stack_effect(Indexed(NOP), other=False), + lambda: _opcode.stack_effect(Indexed(NOP), oparg=Indexed(0)), +): + error(TypeError, call) + assert events == [] + + +class LifetimeOperand: + def __index__(self): + events.append("temporary-index") + return 0 + + def __del__(self): + events.append("temporary-release") + + +# Acyclic temporaries are released on both success and validation error. +events.clear() +assert _opcode.stack_effect(NOP, LifetimeOperand()) == 0 +assert events == ["temporary-index", "temporary-release"] +events.clear() +error( + ValueError, + lambda: _opcode.stack_effect(NOP, LifetimeOperand(), jump=0), + "stack_effect: jump must be False, True or None", +) +assert events == ["temporary-index", "temporary-release"] diff --git a/extra_tests/snippets/private_315_cache_contract.py b/extra_tests/snippets/private_315_cache_contract.py new file mode 100644 index 00000000000..334fd9426df --- /dev/null +++ b/extra_tests/snippets/private_315_cache_contract.py @@ -0,0 +1,109 @@ +"""Private development ABI reader/writer boundary; no public compatibility claim.""" + +import _frozen_importlib_external as external +import importlib +import importlib.util +import py_compile +import sys +import tempfile +import zipfile +from pathlib import Path + +if sys.implementation.name == "rustpython": + assert external.MAGIC_NUMBER == bytes((0x16, 0xDA, 13, 10)) + assert sys.implementation.cache_tag == "rustpython-315-devabi2" + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "cache_subject.py" + source.write_text("value = 41\n", encoding="ascii") + code = compile("value = 42\n", "byte_exact/cache_subject.py", "exec") + stat = source.stat() + headers = [ + external._code_to_timestamp_pyc(code, int(stat.st_mtime), stat.st_size), + external._code_to_hash_pyc( + code, importlib.util.source_hash(source.read_bytes()), True + ), + external._code_to_hash_pyc( + code, importlib.util.source_hash(source.read_bytes()), False + ), + ] + rejected = [3627, 3666, 0xDA15] + current = 0xDA16 + + def load_from(path): + sys.modules.pop("cache_subject", None) + sys.path.insert(0, str(path)) + importlib.invalidate_caches() + try: + return importlib.import_module("cache_subject").value + finally: + sys.path.pop(0) + sys.modules.pop("cache_subject", None) + + # Source caches must fall back to source on every foreign magic. + cache = Path(importlib.util.cache_from_source(str(source))) + cache.parent.mkdir() + for payload in headers: + for magic in rejected + [current]: + candidate = magic.to_bytes(2, "little") + b"\r\n" + payload[4:] + cache.write_bytes(candidate) + assert load_from(root) == (42 if magic == current else 41) + + # Sourceless files cannot silently fall back or accept foreign formats. + source.unlink() + sourceless = root / "cache_subject.pyc" + for payload in headers: + for magic in rejected + [current]: + sourceless.write_bytes( + magic.to_bytes(2, "little") + b"\r\n" + payload[4:] + ) + if magic == current: + assert load_from(root) == 42 + else: + try: + load_from(root) + except ImportError: + pass + else: + raise AssertionError("foreign sourceless cache accepted") + + # ZIP source fallback and pyc-only rejection use the same token. + for header_index, payload in enumerate(headers): + for with_source in (False, True): + for magic in rejected + [current]: + archive = root / f"case_{header_index}_{with_source}_{magic}.zip" + with zipfile.ZipFile(archive, "w") as output: + output.writestr( + "cache_subject.pyc", + magic.to_bytes(2, "little") + b"\r\n" + payload[4:], + ) + if with_source: + output.writestr("cache_subject.py", "value = 41\n") + if magic == current or with_source: + value = load_from(archive) + if magic != current: + assert value == 41 + elif with_source and header_index == 0: + # ZIP timestamp validation may select the source. + assert value in (41, 42) + else: + assert value == 42 + else: + try: + load_from(archive) + except ImportError: + pass + else: + raise AssertionError("foreign ZIP cache accepted") + + # Explicit py_compile writes under -B; all writers must use the private token. + source.write_text("value = 41\n", encoding="ascii") + explicit = root / "explicit.pyc" + previous = sys.dont_write_bytecode + sys.dont_write_bytecode = True + try: + py_compile.compile(str(source), cfile=str(explicit), doraise=True) + assert explicit.read_bytes()[:4] == external.MAGIC_NUMBER + finally: + sys.dont_write_bytecode = previous diff --git a/extra_tests/snippets/public_opcode_metadata_315.py b/extra_tests/snippets/public_opcode_metadata_315.py new file mode 100644 index 00000000000..231b4693268 --- /dev/null +++ b/extra_tests/snippets/public_opcode_metadata_315.py @@ -0,0 +1,567 @@ +"""Complete bounded final3.15 public metadata contract, including private holes. + +The lossless flag rows and complete stack matrix come from the certified final +oracle. Calling conversion outside ordinary nonnegative arguments is separate. +""" + +import _opcode +import hashlib +import json + +FLAGS = ( + "is_valid", + "has_arg", + "has_const", + "has_name", + "has_jump", + "has_free", + "has_local", + "has_exc", +) +EXPECTED_FLAGS = ( + 0, + 1, + 1, + 1, + 65, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 11, + 35, + 67, + 11, + 11, + 3, + 3, + 19, + 3, + 19, + 3, + 3, + 11, + 11, + 3, + 19, + 19, + 19, + 3, + 3, + 11, + 3, + 7, + 67, + 67, + 67, + 67, + 67, + 67, + 67, + 35, + 11, + 11, + 11, + 3, + 3, + 11, + 35, + 3, + 3, + 19, + 19, + 19, + 19, + 3, + 3, + 19, + 3, + 3, + 3, + 11, + 35, + 67, + 67, + 67, + 11, + 11, + 3, + 3, + 3, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 1, + 3, + 1, + 3, + 3, + 3, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 19, + 19, + 19, + 19, + 1, + 1, + 19, + 19, + 3, + 3, + 11, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 11, + 3, + 3, + 11, + 11, + 1, + 3, + 19, + 3, + 19, + 1, + 1, + 11, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 19, + 1, + 3, + 1, + 3, + 3, + 3, + 3, + 3, + 1, + 3, + 19, + 11, + 3, + 3, + 1, + 3, + 1, + 3, + 3, + 1, + 19, + 19, + 19, + 19, + 67, + 1, + 131, + 131, + 131, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +) +EXPECTED_STACK_SHA256 = ( + "37dc8fdfed5c2a15665eece22718422b02721a1d9ce6e4963b89c04727074f37" +) + +actual_flags = tuple( + sum(int(getattr(_opcode, name)(op)) << bit for bit, name in enumerate(FLAGS)) + for op in range(-1, 513) +) +assert len(actual_flags) == 514 +assert actual_flags == EXPECTED_FLAGS +assert _opcode.is_valid(255) and _opcode.has_arg(255) +assert all(not _opcode.is_valid(op) for op in (219, 220, 221)) +rows = [] +for op in range(-1, 513): + for arg in (0, 1, 2, 3, 4, 8, 255): + for jump in (None, False, True): + try: + value = _opcode.stack_effect(op, arg, jump=jump) + except Exception as exc: + value = [type(exc).__name__, str(exc)] + rows.append([op, arg, jump, value]) +assert len(rows) == 10794 +assert ( + hashlib.sha256(json.dumps(rows, separators=(",", ":")).encode()).hexdigest() + == EXPECTED_STACK_SHA256 +) +assert all( + _opcode.stack_effect(255, arg, jump=jump) == 0 + for arg in (0, 255) + for jump in (None, False, True) +) diff --git a/extra_tests/snippets/stable_code_fields_315.py b/extra_tests/snippets/stable_code_fields_315.py new file mode 100644 index 00000000000..637c79fad98 --- /dev/null +++ b/extra_tests/snippets/stable_code_fields_315.py @@ -0,0 +1,154 @@ +"""Stable surviving code fields and sole constants tuple ownership.""" + +import gc +import marshal +import sys +import types +import weakref + +SOURCE = "def sample(a):\n try:\n return a + marker\n except TypeError:\n return 0\n" +FILENAME = "stable_code_fields.py" +base = compile(SOURCE, FILENAME, "exec").co_consts[0] +for name in ( + "co_consts", + "co_names", + "co_varnames", + "co_freevars", + "co_cellvars", + "co_linetable", + "co_exceptiontable", + "co_code", +): + value = getattr(base, name) + assert getattr(base, name) is value + +replacement = base.replace() +for name in ("co_consts", "co_names", "co_linetable", "co_exceptiontable"): + assert getattr(replacement, name) is getattr(base, name) +assert replacement.co_code == base.co_code +assert replacement.co_code is not base.co_code +assert replacement.co_varnames == base.co_varnames +assert replacement.co_varnames is not base.co_varnames + + +class Child: + pass + + +child = Child() +weak = weakref.ref(child) +values = (child, [1, 2], slice(1, 5, 2), frozenset((3, 4))) +owned = base.replace(co_consts=values) +assert owned.co_consts is values +assert owned.co_consts[0] is child +assert owned.co_consts[2] is values[2] +assert owned.co_consts[3] is values[3] +clone = owned.replace() +assert clone.co_consts is values +del child, owned, values +gc.collect() +assert weak() is not None +del clone +gc.collect() +assert weak() is None + +# The public constructor copies validated names, retains its constants and +# surviving byte tables, and independently creates its exported code cache. +input_code = bytes(bytearray(base.co_code)) +input_names = tuple(list(base.co_names)) +constructed = types.CodeType( + base.co_argcount, + base.co_posonlyargcount, + base.co_kwonlyargcount, + base.co_nlocals, + base.co_stacksize, + base.co_flags, + input_code, + base.co_consts, + input_names, + base.co_varnames, + FILENAME, + base.co_name, + base.co_qualname, + base.co_firstlineno, + base.co_linetable, + base.co_exceptiontable, + base.co_freevars, + base.co_cellvars, +) +assert constructed.co_consts is base.co_consts +assert constructed.co_names == input_names +assert constructed.co_names is not input_names +assert constructed.co_linetable is base.co_linetable +assert constructed.co_exceptiontable is base.co_exceptiontable +assert constructed.co_code == input_code +assert constructed.co_code is not input_code + +# Retain the known normalization gap. Rust never mutates a published caller +# tuple; when a member needs canonical interning it creates a normalized copy. +raw_name = "".join(("mar", "ker")) +assert raw_name is not base.co_names[0] +names = (raw_name,) +normalized = base.replace(co_names=names) +assert normalized.co_names[0] is base.co_names[0] +if sys.implementation.name == "rustpython": + assert normalized.co_names is not names + assert names[0] is raw_name +else: + assert normalized.co_names is names + assert names[0] is base.co_names[0] + +outer = compile( + "def outer(a):\n def inner():\n return a\n return inner\n", + FILENAME, + "exec", +).co_consts[0] +inner = next(value for value in outer.co_consts if isinstance(value, types.CodeType)) +assert outer.co_cellvars == ("a",) +assert outer.co_cellvars is outer.co_cellvars +assert inner.co_freevars == ("a",) +assert inner.co_freevars is inner.co_freevars + +# Whole unchanged streams emitted by the pinned final oracle. Both field-first +# and code-first ordering exercise reader reference identity; the current Rust +# writer is deliberately not used to create the expected aliases. +FIELD_FIRST = bytes.fromhex( + "2905a901e900000000a902da066d61726b6572da09547970654572726f72f3230000008100f002030511d80f" + "1094368d7ad00819f8dc0b14f400010511da0f10f003010511faf310000000830c100090091f039a011f039e" + "011f03e30100000000000000000000000400000003000000f34400000080000000190055005b000000000000" + "0000002a000000000000000000000021001e005b0200000000000000000600630500001a001d001b005d0021" + "00680039031b006801720000000072020000002901da0161730100000026da15737461626c655f636f64655f" + "6669656c64732e7079da0673616d706c65720b0000000100000072050000007206000000" +) +CODE_FIRST = bytes.fromhex( + "2905e30100000000000000000000000400000003000000f34400000080000000190055005b00000000000000" + "00002a000000000000000000000021001e005b0200000000000000000600630500001a001d001b005d002100" + "680039031b006801a901e900000000a902da066d61726b6572da09547970654572726f722901da0161730100" + "000026da15737461626c655f636f64655f6669656c64732e7079da0673616d706c65720900000001000000f3" + "230000008100f002030511d80f1094368d7ad00819f8dc0b14f400010511da0f10f003010511faf310000000" + "830c100090091f039a011f039e011f0372020000007204000000720a000000720b000000" +) +for order, payload in (("field_first", FIELD_FIRST), ("code_first", CODE_FIRST)): + decoded = marshal.loads(payload) + if order == "field_first": + consts, names, lines, exceptions, code = decoded + else: + code, consts, names, lines, exceptions = decoded + assert code.co_consts is consts + assert code.co_names is names + assert code.co_linetable is lines + assert code.co_exceptiontable is exceptions + assert code.co_code == base.co_code + assert code.co_code is code.co_code + function = types.FunctionType(code, {"marker": 10}) + assert function(2) == 12 + assert function("x") == 0 + +# Final CodeType.replace accepts tuple fields, not arbitrary iterables. +for field in ("co_consts", "co_names"): + try: + base.replace(**{field: []}) + except TypeError: + pass + else: + raise AssertionError(field + " accepted a list") diff --git a/scripts/byte_exact_capture.py b/scripts/byte_exact_capture.py new file mode 100644 index 00000000000..90b75026030 --- /dev/null +++ b/scripts/byte_exact_capture.py @@ -0,0 +1,101 @@ +"""Compile one ordinary request; emit unmodified marshal before field inspection.""" + +import json +import marshal +import struct +import sys +import types + +FIELDS = ( + "co_argcount", + "co_posonlyargcount", + "co_kwonlyargcount", + "co_nlocals", + "co_stacksize", + "co_flags", + "co_code", + "co_consts", + "co_names", + "co_varnames", + "co_freevars", + "co_cellvars", + "co_filename", + "co_name", + "co_qualname", + "co_firstlineno", + "co_linetable", + "co_exceptiontable", +) + + +def describe(value): + kind = type(value) + if kind is types.CodeType: + return { + "type": "code", + "fields": {name: describe(getattr(value, name)) for name in FIELDS}, + } + if value is None or value is Ellipsis: + return {"type": kind.__name__} + if kind in (bool, int, str): + return {"type": kind.__name__, "value": value} + if kind is bytes: + return {"type": "bytes", "value": list(value)} + if kind is float: + return {"type": "float", "bits": list(struct.pack(" U:\n return x\n", + "mode": "exec", + "flags": 16777216 + }, + { + "name": "formatting", + "source": "result = f\"{value!r:>10}\"\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "template", + "source": "result = t\"value={value}\"\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "matching", + "source": "match value:\n case [a, b]:\n result = a + b\n case _:\n result = None\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "yield_plain", + "source": "def f():\n yield 1\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "import_module", + "source": "import os\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "import_function", + "source": "def f():\n import os\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "import_handler", + "source": "try:\n import os\nexcept ImportError:\n import sys\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "genexpr_list", + "source": "def f(xs):\n return list(x + 1 for x in xs if x)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "genexpr_set", + "source": "def f(xs):\n return set(x + 1 for x in xs if x)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "genexpr_tuple", + "source": "def f(xs):\n return tuple(x + 1 for x in xs if x)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "async_genexpr", + "source": "async def f(xs):\n return list(x async for x in xs)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "borrow_locals", + "source": "def f(a, b):\n return a, b, a\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "exception_alias", + "source": "try:\n work()\nexcept Exception as error:\n result = error\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "assembly_siblings", + "source": "def left():\n return 1\ndef right():\n return 1\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_if_ors", + "source": "if True or False or False:\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_if_ands", + "source": "if True and False and False:\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_if_mixed", + "source": "if (True and False) or (False and True):\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_nested_bool_op", + "source": "x = Test() and False or False", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_const_no_op", + "source": "x = not True", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_constant_true_if_pass_keeps_line_anchor_nop", + "source": "if 1:\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_nested_double_async_with", + "source": "async def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_bare_function_annotations_check_attribute_and_subscript_expressions", + "source": "def f(one: int):\n int.new_attr: int\n [list][0].new_attr: [int, str]\n my_lst = [1]\n my_lst[one]: int\n return my_lst", + "mode": "exec", + "flags": 0 + }, + { + "name": "cache_nested_constants", + "source": "def outer():\n def left(x):\n return x + 12345678901234567890\n def right(x):\n return x + 12345678901234567890\n return left, right\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "cache_sibling_exception_fields", + "source": "def left(x):\n try:\n return x + 12345678901234567890\n except TypeError:\n return ('payload !', b'bytes !')\ndef right(x):\n try:\n return x + 12345678901234567890\n except TypeError:\n return ('payload !', b'bytes !')\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "cache_frozenset_siblings", + "source": "def left(x):\n return x in {12345678901234567890, 98765432109876543210}\ndef right(x):\n return x in {12345678901234567890, 98765432109876543210}\n", + "mode": "exec", + "flags": 0 + } +] diff --git a/scripts/test_byte_exact_compile.py b/scripts/test_byte_exact_compile.py new file mode 100644 index 00000000000..f07a20ca3cd --- /dev/null +++ b/scripts/test_byte_exact_compile.py @@ -0,0 +1,112 @@ +"""Controller integrity checks; these do not test interpreter behavior.""" + +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + +import byte_exact_capture +from byte_exact_compile import ( + capture_environment, + compare_captures, + differences, + requests, + validate_cache_prefix, +) + + +class CorpusTests(unittest.TestCase): + def setUp(self): + self.fixture = { + "name": "ordinary", + "source": "x = 1\n", + "mode": "exec", + "flags": 0, + } + + def test_empty_corpus_is_an_error(self): + with self.assertRaisesRegex(ValueError, "empty"): + requests([], []) + + def test_unknown_selection_is_an_error(self): + with self.assertRaisesRegex(ValueError, "unknown"): + requests([self.fixture], ["missing"]) + + def test_duplicate_names_are_an_error(self): + with self.assertRaisesRegex(ValueError, "duplicate"): + requests([self.fixture, self.fixture], []) + + def test_explicit_selection_retains_all_optimizations(self): + corpus = requests([self.fixture], ["ordinary"]) + self.assertEqual([r["optimize"] for r in corpus], [0, 1, 2]) + self.assertTrue(all(r["filename"] == "byte_exact/ordinary.py" for r in corpus)) + + def test_ascii_boundary(self): + with self.assertRaisesRegex(ValueError, "ASCII"): + requests([{**self.fixture, "source": "x = 'é'"}], []) + + def test_equal_fields(self): + self.assertEqual(differences({"a": [1, 2]}, {"a": [1, 2]}), []) + + def test_byte_difference_is_retained(self): + self.assertEqual(differences([1, 128], [1, 0], "marshal"), ["marshal/1"]) + + def test_nested_difference_has_single_leaf_path(self): + self.assertEqual( + differences({"code": {"flags": 1}}, {"code": {"flags": 2}}), ["/code/flags"] + ) + + def test_length_difference_is_not_normalized(self): + self.assertEqual(differences([1, 0], [1], "code"), ["code"]) + + def test_types_are_not_coerced(self): + self.assertEqual(differences(True, 1, "constant"), ["constant"]) + + def test_cache_prefix_is_explicit_in_sanitized_environment(self): + with TemporaryDirectory() as directory: + prefix = validate_cache_prefix(directory) + env = capture_environment(None, prefix) + self.assertEqual(env["PYTHONPYCACHEPREFIX"], str(prefix)) + self.assertEqual(env["PYTHONDONTWRITEBYTECODE"], "1") + self.assertNotIn("RUSTPYTHONPATH", env) + + def test_populated_cache_prefix_is_rejected(self): + with TemporaryDirectory() as directory: + (Path(directory) / "existing.pyc").write_bytes(b"cache") + with self.assertRaisesRegex(ValueError, "empty"): + validate_cache_prefix(directory) + + def test_runtime_provenance_is_not_a_comparison_field(self): + reference = {"marshal": [1], "magic": [2], "code": {}, "runtime": "reference"} + candidate = {**reference, "runtime": "candidate"} + self.assertEqual( + compare_captures(reference, candidate), + {"marshal": [], "magic": [], "code": []}, + ) + + def test_runtime_provenance_follows_serialization_and_fields(self): + events = [] + request = {**self.fixture, "filename": "ordinary.py", "optimize": 0} + with ( + patch.object( + byte_exact_capture.marshal, + "dumps", + side_effect=lambda *a: events.append("marshal") or b"raw", + ), + patch.object( + byte_exact_capture, + "describe", + side_effect=lambda value: events.append("fields") or {}, + ), + patch.object( + byte_exact_capture, + "runtime_provenance", + side_effect=lambda: events.append("runtime") or {}, + ), + ): + byte_exact_capture.capture(request) + self.assertEqual(events, ["marshal", "fields", "runtime"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/opcode_metadata/CANONICAL_CODE_STORAGE.md b/tools/opcode_metadata/CANONICAL_CODE_STORAGE.md new file mode 100644 index 00000000000..2e68f3b2242 --- /dev/null +++ b/tools/opcode_metadata/CANONICAL_CODE_STORAGE.md @@ -0,0 +1,37 @@ +# Canonical code storage follow-on + +This storage-only follow-on starts from reviewed checkpoint a974ec938d700459d5badd578df59b587f4a4396. +It does not change the common marshal writer, reference policy, filename handling, +private magic, native argument transport, or tuple/metadata ownership. + +Pinned Objects/codeobject.c:_PyCode_GetCode caches a stable bytes object and +uses monitoring-aware deoptimization; code_replace_impl obtains that public +representation when co_code is omitted. Rust's previous live original_bytes() +export cannot recover generic INSTRUMENTED_LINE/INSTRUCTION side-table opcodes. + +PyCode::new now captures immutable canonical bytes immediately after GET_ITER +normalization, before code publication. co_code lazily creates one PyBytes object +from that backing through the threading-aware OnceCell. Python replace without +co_code parses the immutable backing instead of cloning live execution units. +Source/AST, Basic bytecode materialization, Python constructor/replace, frozen +code and the public marshal reader all converge on PyCode::new. No context API +change is needed for this narrow lazy bytes cache. + +The public embedding constructor requires pristine input. Copying a monitored +PyCode.code into a new PyCode cannot carry its dynamic monitoring side tables. +Logical post-finalization code mutation is unsupported; execution specialization +and instrumentation may mutate live instructions but cannot replace canonical +public bytes. Embedders replacing logical code must construct a new PyCode from +canonical bytes. This is a documented API contract, not an unsafe lifetime claim. + +The additive Python regression checks cold/warmed stable identity, constructor, +replace, public decoding and a core frozen code object. Independent compilations +provide expected bytes so each monitored target's first getter call occurs inside +an active LINE or INSTRUCTION callback. Replacement construction and execution +there must never copy live monitoring wrappers. All filenames are fixed ASCII. + +Crucially, marshal's current serialize_code_with still reads live instructions. +This getter/storage change alone does not fix marshal-under-monitoring or make +full streams exact. A later separately reviewed common-writer connection must +use the actual cached object and preserve its temporary owner through all fields. +No public serialization compatibility or full-byte parity is claimed here. diff --git a/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md b/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md new file mode 100644 index 00000000000..bd5a61c4d2d --- /dev/null +++ b/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md @@ -0,0 +1,110 @@ +# Final 3.15 compiler ABI reconstruction + +This is new work based on public RustPython commit +`06007c81fa99e813cbf4583dab326764ba0ef017`. Earlier local checkpoints were lost +with their execution environment. Their historical results are not validation +of this reconstruction. + +The source target is CPython v3.15.0, +`d220409a13b77de7a6c30abe16ea90db01528edc`. `target_source.json` pins the +generator dependencies and directly consulted ABI sources. The active metadata +generator verifies those hashes before importing the CPython generator modules. +The target generator directory must also be free of bytecode caches; the gate +rejects existing pycs and disables writes before importing from the verified path. +`verify_stack_contract.py` compiles the pinned C metadata independently and +checks 3,430 argument-specific stack rows; Rust tests compare every supported +opcode against those rows. Unsupported specialized instructions are not claimed +implemented merely because their reference rows are recorded. + +## Private cache contract + +The intermediate ABI uses magic `0xDA16` (bytes `16 da 0d 0a`) and cache tag +`rustpython-315-devabi2`. Available repository and pinned-source definitions were +checked for collisions; this is not a globally reserved token. The lost +`0xDA15` development format is deliberately rejected, as are public CPython +formats. Change both token and tag whenever the intermediate format changes +incompatibly. + +The shared Rust magic feeds importlib, source and sourceless caches, ZIP imports, +explicit `py_compile`/`compileall` writers, and direct Rust pyc readers. `-B` +does not prohibit cache reads or explicit writes. Validation uses a fresh owned +Lib copy, records cache inventories, and checks foreign timestamp/hash caches +and ZIP fallback/rejection. The CLI prepends `BUILDTIME_RUSTPYTHONPATH` ahead of +`RUSTPYTHONPATH`; a new environment path alone does not isolate cache reads. +Strict captures must verify actual module paths and keep their embedded Lib copy +separate from cache-writing Rust embedding tests. Headerless marshal and explicit frozen-data input +remain caller-version-coupled. Frozen modules must be rebuilt using the same +compiler and VM target; private pyc magic does not validate their separate +compressed format. + +## Output and integration gates + +The strict harness captures untouched marshal version 6 bytes before inspecting +code fields. It compares magic, marshal and recursively captured fields without +normalizing byte streams, exception ranges, reference flags, or nested code. +An empty corpus is an error. Its finite fixtures are witnesses, not a general +compiler-equivalence proof. Behavioral regressions belong in Python snippets; +Rust tests additionally check structural compiler and ownership invariants. +The reference invocation requires an empty `PYTHONPYCACHEPREFIX` so existing +certified-build caches are not read. The harness verifies that directory remains +empty and records runtime paths, cache settings and already-imported module +origins after serialization and field capture. These diagnostics are provenance; +they do not alter the three strict comparison domains. + +Canonical `co_code` uses immutable prepublication bytes and a stable bytes owner; +the common marshal writer uses that actual field rather than live monitoring +instructions. The adaptive getter projects private Rust `LOAD_CONST` IDs220/221 +to public `LOAD_CONST` at immutable canonical instruction boundaries, preserving +arguments, public specialization IDs and cache payload bytes. Legacy private +ID219 has no equivalent one-unit public encoding and raises `ValueError` on +adaptive export. Length and truncated canonical cache spans are checked; arbitrary +same-length postpublication embedding mutation remains outside the finalized-code +contract. Per-cell atomic reads do not establish a coherent concurrent snapshot +or repair inherited shared-storage accessor and mixed-width dispatch concerns. +The projection neither mutates executable instructions nor changes canonical or +marshal bytes. Runtime specialization choices need not match CPython. +Retained private `GET_YIELD_FROM_ITER` must never be emitted by the compiler. +The current local tree includes the separately reviewed builtin `frozendict` +prerequisite and exact final `Lib/opcode.py`, `Lib/_opcode_metadata.py`, and +`Lib/dis.py` imports made only through `scripts/update_lib`. The eight original +dis tests pass with certified final expectations. This local validation does not +waive Rust-first merge sequencing or establish a distributable implementation. +Future Lib updates must also use `scripts/update_lib` with the exact source pin; +manual copying or editing these files is not permitted. +The public magic transition requires full serialization compatibility, +reader/writer/frozen agreement, and the separately planned exact Python update. + +Marshal reference ownership, compiler constant-origin chronology and mortal +interning remain separate work. No count offsets, flag masking, equal-text +over-interning or weakening of static-reference invariants is permitted. +The filename/surrogate family remains outside this stage. + +## Public native opcode metadata + +`generate_public_metadata.py --source ` generates the native +`_opcode` introspection domain from final C descriptors, deoptimization mapping +and stack expressions. Its 267-slot descriptor extent and 256-slot deopt extent +are deliberately separate from the executable Rust opcode enum. This includes +CPython-only specialized/tier2 descriptors and excludes private Rust IDs219–221; +it does not implement their execution or change cache magic. Specialized stack +effects remain invalid. Formula parsing fails closed and uses wrapping signed +32-bit arithmetic rather than panic-prone Rust arithmetic. This is an explicit +implementation policy, not a portable claim about C signed overflow; equivalence +outside the tested argument window remains unclaimed. + +The bounded Python regression retains all514 numeric flag rows and a complete +10,794-row stack-result digest from the certified interpreter, including errors. +It uses ordinary nonnegative arguments and exact None/False/True jump values. +The numeric metadata matrix does not cover calling conversions; the separate +calling protocol regression below does not claim all diagnostic wording parity. +Canonical metadata/dis files are exact final imports through update_lib. Their +local integration does not waive Rust-first merge order or publication gates. + +The local `_opcode` calling-conversion follow-on accepts index objects after +argument binding, converts the opcode before its operand, treats an explicit +`None` operand as zero, and requires exact `None`/`True`/`False` jump identities. +C-int and C-long overflow messages are local to this module. C-long narrowing +uses a panic-free low-bit policy for supported two's-complement target ABIs; +Linux oracle evidence does not certify a Windows run or C signed-overflow +formulas. Shared argument-binder and invalid-index-result diagnostic wording +remain separately visible in the complete calling matrix. diff --git a/tools/opcode_metadata/STABLE_CODE_FIELDS.md b/tools/opcode_metadata/STABLE_CODE_FIELDS.md new file mode 100644 index 00000000000..8d126ef8bce --- /dev/null +++ b/tools/opcode_metadata/STABLE_CODE_FIELDS.md @@ -0,0 +1,132 @@ +# Stable code fields and common writer routing + +The storage stage follows canonical code storage commit6746df059. The subsequent +common-writer stage is described below. The subsequent actual-owner selector is described below. Native call ownership, +permanent string interning and filename handling remain unchanged. +Full serialization parity remains unresolved. + +Runtime `Constants` owns one immutable Python tuple. It does not keep a +second boxed list of owning child references beside a getter cache. The compiler +representation remains boxed. Cloning a runtime constant pool shares the tuple; +mutable access copies the elements before replacing the owner. Published tuples +are never mutated. The owner-bound Literal slice uses the existing transparent +layout over PyObjectRef and never calls the slice/frozenset constant adapter. + +Surviving names, locals-plus names/kinds and line/exception tables have stable +Python owners. Derived variable/cell/free-name tuples are separate lazy caches. +Constructor names are copied after validation; replace retains unchanged names. +Public marshal decode retains actual field objects when normalized name member +identities permit it, otherwise uses a safe normalized copy. That copy has a +known tuple-identity difference from CPython's in-place name normalization. +Existing constant interning behavior is preserved, not expanded. + +Decoder input co_code bytes are not retained as the exported co_code object. +That export remains A0's independent lazy cache of canonical normalized bytes. +The new reader hook defaults to the existing bytes/constants/code hook chain. +Runtime decoding drops temporary child vectors and replaced boxes before code +publication. Reader allow_code, type, depth and incomplete-reference checks stay +in force. + +## Rust source API migration + +- `PyCode::new(code, &Context)` now requires the creating runtime's context so + final tuple/metadata objects exist before publication. Callers must still pass + pristine instruction streams, as required by A0. +- Implementations of compiler-core `Constant` supply `TupleOwner: Clone + + AsRef<[Self]>`. BasicBag uses boxed constants; the runtime uses a private + validated tuple owner. Downstream bags must provide an immutable owner-bound + element view. No Python object type is introduced into compiler-core. +- Public getters for co_linetable/co_exceptiontable no longer require a VM + argument; they return retained Python bytes owners. +- A new `MarshalBag::make_code_with_fields` is additive. Its default delegates + `make_code_with_constants_and_bytes`, preserving earlier customization hooks. + +The checked-in Python test retains full certified streams in both alias orders, +checks identity and execution, and explicitly preserves the normalization gap. +Rust tests verify actual child-owner counts, COW isolation, publication and +reader-hook ordering. No test count is used by production serialization. + +## Integration limits + +The existing untracked PyCode policy and permanent-intern ownership contracts +are unchanged. Pinned CPython only tracks code objects in Py_GIL_DISABLED +builds; the certified GIL-enabled oracle is untracked too. This stage does not claim a general cycle-collection repair or mortal +interning parity. Python child ownership moves to the tuple edge and ordinary +Rust drops release it; a future traversal change must account for that real edge. +Compilation against current main after #9080 is a separate integration gate even +though the source audit found no direct overlap and transparent ownership remains. +Full workspace/CAPI/release and the canonical metadata dependencies remain +required before a general integration/CI-green claim. + +## Common writer routing follow-on + +The runtime marshal writer routes nine actual surviving code-field objects +through its ordinary object writer: canonical code bytes, constants, names, +localsplus names and kinds, name, qualname, line table and exception table. +The field callback runs before the compiler/frozen fallback computes any bytes +or temporary names. The filename operation stays unchanged in its original +position. Default compiler/frozen callers retain their previous full encoding. + +Runtime constants are serialized as the real tuple, including its normal child +depth, rather than a synthetic tuple header followed by flattened children. +The canonical code getter's real owner lives through the complete code write; +other fields are borrowed from their existing owners. Each reference-table +reservation additionally owns its referent until that per-call table drops, +including set-sort temporary tables and error exits. No dictionary lock spans +recursive serialization. + +This changes ordinary code-field bytes and fixes the live-monitoring export +source used by this writer. At the routing checkpoint, reference selection was +still blanket; the selector follow-on below replaces that rule. Direct, named +and aliased roots retain their real native-call owners; +no counts or flags are adjusted to imitate CPython. Filename identity, interning +lifetime and native argument provenance remain separate compatibility gaps. +Storage and writer changes alone do not establish complete marshal parity or +public cache-format compatibility. + + +## Observational actual-owner reference selection + +Before reference lookup or reservation, the common writer observes the actual +strong count. A unique object is emitted without registration unless it is an +exact interned string. One decision controls lookup, reservation, flag emission +and completion. Table owners, canonical getter owners, dictionary snapshots and +set-sort temporary owners remain real and unchanged; no counts are subtracted. + +The relaxed count observation changes only wire registration. It does not prove +exclusive access or authorize borrowing or mutation. Concurrent weak acquisition +or graph changes can affect bytes and decoded alias observations; serialization +is not a snapshot or a claim of CPython free-threaded uniqueness parity. Existing +immortal and permanent-interner contracts remain intact. Native direct/named +root differences and compiler-origin sharing remain separate parity gaps. + +## Compilation-local materialization (A4) + +Source/AST and Basic CodeObject publication share one private pool of typed, +owner-free Rust keys. The five surviving fields are finalized in this order: +exception table, line table, names, constants, localsplusnames. Nested code values +are finalized before their PyRefs enter the private root graph; the pool drops +before root publication, including on unwinding. Exported co_code, localspluskinds +and separately lazy var/cell/free tuples are not pooled. + +The additive IntoCodeObject::into_pycode method defaults to the former runtime +construction path. Its Basic override joins the source path; downstream +implementations inherit the default without a new required method. Raw Basic +into_code_object shares constants and nested bodies but cannot promise finalized +root field identities. Frozen decoding, runtime Literal input, public readers, +constructors and replace retain their prior routes. + +Keys distinguish types and all signed zeros. Frozensets are keyed by their actual +surviving element types, with temporary snapshot owners released before cache +selection. No leaking Literal borrow adapter is used to inspect runtime keys. +NaNs and aggregates containing them, code objects and code-containing aggregates, +and entire slice subtrees remain excluded. Noncanonical equal-text name tuple +winners are rejected without mutating them; the normalized-copy identity gap +remains. No global interning policy or native transport changes are made. + +This bottom-up optimized-graph materialization does not reproduce CPython's +parent-first recursive registration or earlier compiler/assembly first winners, +including discarded constants and input bytecode. Exact chronological sharing +and full marshal parity remain unresolved; finite passing witnesses cannot prove +those properties. Real pool owners are removed by ordinary drop, not count +adjustments. The runtime's existing GC policy remains unchanged. diff --git a/tools/opcode_metadata/conf.toml b/tools/opcode_metadata/conf.toml index e69de29bb2d..a0cb9692755 100644 --- a/tools/opcode_metadata/conf.toml +++ b/tools/opcode_metadata/conf.toml @@ -0,0 +1,4 @@ +# Private legacy instruction. The compiler must never emit this opcode. +[GetYieldFromIter.stack_effect] +popped = "1" +pushed = "1" diff --git a/tools/opcode_metadata/cpython.py b/tools/opcode_metadata/cpython.py index 56743a88842..5868d7d7483 100644 --- a/tools/opcode_metadata/cpython.py +++ b/tools/opcode_metadata/cpython.py @@ -1,26 +1,37 @@ import os import pathlib import sys +from functools import cache + +from target_source import verify_import_origins, verify_source try: CPYTHON_ROOT = pathlib.Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve() except KeyError: raise ValueError("Missing environment variable 'CPYTHON_ROOT'") +sys.dont_write_bytecode = True +verify_source(CPYTHON_ROOT) +verify_import_origins(CPYTHON_ROOT) + CPYTHON_TOOLS_LIB = CPYTHON_ROOT / "Tools" / "cases_generator" if (path := CPYTHON_TOOLS_LIB.as_posix()) not in sys.path: - sys.path.append(path) + sys.path.insert(0, path) from analyzer import SKIP_PROPERTIES, Analysis, Family, Properties, analyze_files from stack import get_stack_effect +verify_import_origins(CPYTHON_ROOT) + +@cache def get_analysis() -> Analysis: from generators_common import DEFAULT_INPUT + verify_import_origins(CPYTHON_ROOT) analysis = analyze_files([DEFAULT_INPUT]) # Our speration is done at the enum definition analysis.instructions |= analysis.pseudos diff --git a/tools/opcode_metadata/generate_public_metadata.py b/tools/opcode_metadata/generate_public_metadata.py new file mode 100644 index 00000000000..2cbb55e9983 --- /dev/null +++ b/tools/opcode_metadata/generate_public_metadata.py @@ -0,0 +1,188 @@ +"""Generate public _opcode descriptors without expanding executable opcodes.""" + +import argparse +import ast +import os +import re +import subprocess +from pathlib import Path + +from target_source import verify_source + +ROOT = Path(__file__).resolve().parents[2] + + +def expression(text): + """Translate the pinned grammar with an explicit panic-free i32 wrapping policy.""" + + def emit(node): + if isinstance(node, ast.Constant) and type(node.value) is int: + return f"{node.value}i32" + if isinstance(node, ast.Name) and node.id == "oparg": + return "oparg" + if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): + return f"({emit(node.operand)}).wrapping_neg()" + if isinstance(node, ast.BinOp): + left, right = emit(node.left), emit(node.right) + methods = { + ast.Add: "wrapping_add", + ast.Sub: "wrapping_sub", + ast.Mult: "wrapping_mul", + } + if method := methods.get(type(node.op)): + # The argument list already delimits this entire bit expression. + if isinstance(node.right, ast.BinOp) and isinstance( + node.right.op, (ast.BitAnd, ast.RShift) + ): + right = right[1:-1] + return f"({left}).{method}({right})" + if isinstance(node.op, ast.BitAnd): + return f"({left} & {right})" + if ( + isinstance(node.op, ast.RShift) + and isinstance(node.right, ast.Constant) + and 0 <= node.right.value < 32 + ): + return f"({left} >> {node.right.value})" + raise ValueError(f"unsupported pinned stack expression: {text}") + + return emit(ast.parse(text, mode="eval").body) + + +def generate(source): + verify_source(source) + ids = { + name: int(value) + for name, value in re.findall( + r"^#define\s+(\w+)\s+(\d+)\s*$", + (source / "Include/opcode_ids.h").read_text(), + re.M, + ) + } + header = (source / "Include/internal/pycore_opcode_metadata.h").read_text() + extent, body = re.search( + r"const struct opcode_metadata _PyOpcode_opcode_metadata\[(\d+)\] = \{(.*?)\n\};", + header, + re.S, + ).groups() + assert int(extent) == 267 + flag_values = { + name: int(value) + for name, value in re.findall( + r"^#define (HAS_\w+_FLAG) \((\d+)\)$", header, re.M + ) + } + descriptors = {} + for name, flags in re.findall(r"\[(\w+)\] = \{ true, (?:\w+|-1), (.*?) \},", body): + value = 0 + for flag in flags.split(" | "): + value |= 0 if flag == "0" else flag_values[flag] + descriptors[ids[name]] = (name, value) + assert len(descriptors) == 245 + deopt_body = re.search( + r"const uint8_t _PyOpcode_Deopt\[256\] = \{(.*?)\n\};", header, re.S + )[1] + number = lambda value: int(value) if value.isdigit() else ids[value] + deopt = { + number(a): number(b) for a, b in re.findall(r"\[(\w+)\] = (\w+),", deopt_body) + } + assert set(deopt) == set(range(256)) + utils = (source / "Include/internal/pycore_opcode_utils.h").read_text() + block = re.search( + r"#define IS_BLOCK_PUSH_OPCODE\(opcode\)(.*?)(?=\n\n)", utils, re.S + )[1] + block_ids = {ids[name] for name in re.findall(r"opcode\) == (\w+)", block)} + assert block_ids == { + ids[x] for x in ("SETUP_FINALLY", "SETUP_WITH", "SETUP_CLEANUP") + } + effects = [] + for kind in ("popped", "pushed"): + body = re.search( + rf"int _PyOpcode_num_{kind}\(int opcode, int oparg\) \{{(.*?)\n\}}", + header, + re.S, + )[1] + rows = { + ids[name]: expression(expr) + for name, expr in re.findall(r"case (\w+):\s*return (.*?);", body) + } + assert set(rows) == set(descriptors) + effects.append(rows) + rows = [] + for opcode in range(267): + if opcode not in descriptors: + rows.append("0,") + continue + name, flags = descriptors[opcode] + value = flags | (1 << 31) + if opcode < 256 and deopt[opcode] != opcode: + value |= 1 << 30 + if opcode in block_ids: + value |= 1 << 29 + rows.append(f"{value}, // {opcode}: {name}") + methods = "\n".join( + f"#[must_use] pub const fn {name}(self) -> bool {{ self.flags & {flag_values[flag]} != 0 }}" + for name, flag in ( + ("has_arg", "HAS_ARG_FLAG"), + ("has_const", "HAS_CONST_FLAG"), + ("has_name", "HAS_NAME_FLAG"), + ("has_jump", "HAS_JUMP_FLAG"), + ("has_free", "HAS_FREE_FLAG"), + ("has_local", "HAS_LOCAL_FLAG"), + ) + ) + arms = "\n".join( + f"{op} => ({effects[0][op]}, {effects[1][op]}), // {name}" + for op, (name, _) in sorted(descriptors.items()) + if not (op < 256 and deopt[op] != op) + ) + return f"""// Generated by tools/opcode_metadata/generate_public_metadata.py from pinned final CPython. +//! Public introspection metadata, independent of the VM's executable opcode set. +//! Describing an opcode does not enable execution of its CPython specialization. +const DESCRIPTORS: [u32; 267] = [{chr(10).join(rows)}\n]; +#[derive(Clone, Copy)] +pub struct PublicOpcode {{ opcode: u16, flags: u32 }} +impl PublicOpcode {{ + #[must_use] + pub fn from_i32(opcode: i32) -> Option {{ + let index = usize::try_from(opcode).ok()?; + let flags = *DESCRIPTORS.get(index)?; + (flags & (1 << 31) != 0).then_some(Self {{ opcode: u16::try_from(index).ok()?, flags }}) + }} + {methods} + #[must_use] pub const fn is_block_push(self) -> bool {{ self.flags & (1 << 29) != 0 }} + /// Existing caller conversion is intentionally separate from this metadata domain. + #[must_use] + pub fn stack_effect(self, oparg: u32, jump: Option) -> Option {{ + if self.flags & (1 << 30) != 0 {{ return None; }} + let oparg = oparg.cast_signed(); + let (popped, pushed) = match self.opcode {{ {arms}\n _ => return None }}; + if popped < 0 || pushed < 0 {{ return None; }} + if self.is_block_push() && jump == Some(false) {{ return Some(0); }} + Some(pushed.wrapping_sub(popped)) + }} +}} +""" + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, default=os.environ.get("CPYTHON_ROOT")) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + if args.source is None: + parser.error("--source or CPYTHON_ROOT is required") + output = subprocess.check_output( + ["rustfmt", "--emit=stdout", "--edition=2024"], + input=generate(args.source), + text=True, + ) + target = ROOT / "crates/compiler-core/src/bytecode/public_metadata.rs" + if args.check: + assert target.read_text() == output + else: + target.write_text(output) + + +if __name__ == "__main__": + main() diff --git a/tools/opcode_metadata/generate_py_opcode_metadata.py b/tools/opcode_metadata/generate_py_opcode_metadata.py index 08f350e4b39..c1d84252041 100644 --- a/tools/opcode_metadata/generate_py_opcode_metadata.py +++ b/tools/opcode_metadata/generate_py_opcode_metadata.py @@ -1,107 +1,6 @@ -""" -Generate Lib/_opcode_metadata.py for RustPython bytecode. - -This file generates opcode metadata that is compatible with CPython 3.14. -""" - -import functools -import io -import itertools -import operator -import pathlib -import typing - -from opcodes import OpcodeInfo -from utils import DEFAULT_INPUT, ROOT, get_conf, to_pascal_case, to_upper_snake_case - -OUT_FILE = ROOT / "Lib/_opcode_metadata.py" - - -# Opcodes that needs to be first, regardless of their opcode ID. -PRIORITY_OPMAP = { - "CACHE", - "RESERVED", - "RESUME", - "INSTRUMENTED_LINE", - "ENTER_EXECUTOR", -} - -INDENT = " " * 4 -INDENT2 = INDENT * 2 - - -def main(): - override_conf = get_conf() - inp = DEFAULT_INPUT.read_text() - - infos = tuple(OpcodeInfo.iter_infos(inp, override_conf)) - opcodes = tuple(itertools.chain.from_iterable(info.opcodes for info in infos)) - - script_path = pathlib.Path(__file__).resolve().relative_to(ROOT).as_posix() - out = io.StringIO() - - out.write( - f""" -# This file is generated by {script_path} -# for RustPython bytecode format (CPython 3.14 compatible opcode numbers). -# Do not edit! -""".lstrip() - ) - - # _specializations - out.write("\n") - out.write("_specializations = {\n") - - deopts = functools.reduce(operator.ior, map(operator.attrgetter("deopts"), infos)) - - for key, lst in deopts.items(): - key = to_upper_snake_case(key) - out.write(f'{INDENT}"{key}": [\n') - - for item in map(to_upper_snake_case, lst): - out.write(f'{INDENT2}"{item}",\n') - - out.write(f"{INDENT}],\n") - - out.write("}\n") - - # _specialized_opmap - out.write("\n") - out.write("_specialized_opmap = {\n") - - specialized = set(itertools.chain.from_iterable(deopts.values())) - for opcode in sorted(opcodes, key=lambda op: op.cpython_name): - if opcode.rust_name not in specialized: - continue - out.write(f"{INDENT}'{opcode.cpython_name}': {opcode.id},\n") - - out.write("}\n") - - # opmap - out.write("\n") - out.write("opmap = {\n") - - key = lambda op: (op.cpython_name not in PRIORITY_OPMAP, op.id) - for opcode in sorted(opcodes, key=key): - if opcode.rust_name in specialized: - continue - - out.write(f"{INDENT}'{opcode.cpython_name}': {opcode.id},\n") - - out.write("}\n") - - # min - out.write("\n") - have_argument = min(opcode.id for opcode in opcodes if opcode.have_argument) - 1 - out.write(f"HAVE_ARGUMENT = {have_argument}\n") - - min_instrumented = min(opcode.id for opcode in opcodes if opcode.is_instrumented) - out.write(f"MIN_INSTRUMENTED_OPCODE = {min_instrumented}\n") - - # write output - generated = out.getvalue() - OUT_FILE.write_text(generated) +"""Compatibility entry point: verify canonical metadata without rewriting Lib.""" +from verify_py_opcode_metadata import main if __name__ == "__main__": main() diff --git a/tools/opcode_metadata/opcodes.py b/tools/opcode_metadata/opcodes.py index 9178921b2c0..99c49328bc5 100644 --- a/tools/opcode_metadata/opcodes.py +++ b/tools/opcode_metadata/opcodes.py @@ -38,6 +38,8 @@ def deopts(self) -> dict[str, list[str]]: res[family_name].append(member_name) + if "LoadConstImmortal" in names: + res["LoadConst"].extend(["LoadConstImmortal", "LoadConstMortal"]) return dict(res) def __iter__(self): @@ -81,7 +83,11 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]: cpython_name = opcode.cpython_name kwargs = {} - if instr := analysis.instructions.get(cpython_name): + if instr := analysis.instructions.get( + "LOAD_CONST" + if rust_name in ("LoadConstImmortal", "LoadConstMortal") + else cpython_name + ): kwargs["properties"] = properties_with_pseudo_flags(instr) kwargs["family"] = getattr(instr, "family", None) kwargs["cache_entry"] = getattr(instr, "size", -1) diff --git a/tools/opcode_metadata/stack_contract.c b/tools/opcode_metadata/stack_contract.c new file mode 100644 index 00000000000..2b33f27c8c1 --- /dev/null +++ b/tools/opcode_metadata/stack_contract.c @@ -0,0 +1,18 @@ +#define Py_BUILD_CORE +#define NEED_OPCODE_METADATA +#include "Python.h" +#include "pycore_opcode_metadata.h" +#include +int main(void) { + const int args[] = {0,1,2,3,4,5,7,8,15,16,31,255,256,65535}; + for (int op=0; op<512; op++) { + for (unsigned int i=0; i= 0 && pushed >= 0) { + printf("%d %d %d %d\n",op,args[i],popped,pushed); + } + } + } + return 0; +} diff --git a/tools/opcode_metadata/target_source.json b/tools/opcode_metadata/target_source.json new file mode 100644 index 00000000000..60ec701a0d9 --- /dev/null +++ b/tools/opcode_metadata/target_source.json @@ -0,0 +1,48 @@ +{ + "commit": "d220409a13b77de7a6c30abe16ea90db01528edc", + "files": { + "Include/internal/pycore_interpframe.h": "87f52ece3a11f8dfde0274c95c7064efc90b859dd73386cca8cdb83609f21821", + "Include/internal/pycore_opcode_metadata.h": "ce5fb59eadbe061266d7582d5cdd0bf655bb93d6d08fb660a8edf0a19c198fec", + "Include/internal/pycore_opcode_utils.h": "17bb85a7bcabbc29a505949512527f97070daf68f8692f015ac8b6a0f0f0c4e3", + "Include/internal/pycore_uop_ids.h": "dc3b00ba46b803ae4c46cb6b5063ee6814a9330f586692ff4bb500a0b888c421", + "Include/opcode_ids.h": "ffd77843d3b0b58ba476fdc2eca3afc0436b6c5a97adc0cae7e39c1bd0221778", + "Lib/_opcode_metadata.py": "3a451ba73726b21e47424721df8fb42dc43b7e9cc29579cd60e36e9bc2157d7e", + "Lib/dis.py": "bfcd5b49946afdc19b8c2c18ed093af2076e6f3f6c1fb65b5d999830d6ff1af2", + "Lib/opcode.py": "2afb1b49a8f0e8f41e70b5d9192181ea31cb2844c9b298275722f4e698cd0b39", + "Modules/_opcode.c": "f702c64dd389ab42121fff2374a9d0a8ffdd3418b8f717f158a13095f17a7de2", + "Modules/clinic/_opcode.c.h": "b561a19c951b03c3a3a4ef2b665c6e873a128c8b2adfabcf6e0b770345df1f9d", + "Objects/codeobject.c": "b95a59389e3da52283d96b9bfd1a6479ca5d7a6e45c4eeadf5433d88fa84e6d8", + "Objects/frameobject.c": "84d0268c5248e5049158b63ab82556e04be6122f8ebc400bf7723a13b9b98c62", + "Objects/longobject.c": "1e3bb095f475dd25e020bfd33365ad5bbab7f21b7c6439c9bd7dc79839ede8dc", + "Objects/unicodeobject.c": "dcb23f28d01be5b0aa344e7f4d6f79b58cb958139f67f3747cbfd0867ab68f5a", + "Python/assemble.c": "831156f971b07c1ceae22c97a90e9e56363b4a2856896f5383690a30509b94c8", + "Python/bytecodes.c": "aa81f2f18d44cf4e04aa75b5e00e5e50b022c560028030bd062d781bd734c9ea", + "Python/codegen.c": "536a4f1c79b1e0aa292f9954d9e1ca8b02e6c3eddbd08dd111a0998c41848a5d", + "Python/compile.c": "4bff663c758db06b355305406771bc8289af275ca5391daf798aa68d5250f098", + "Python/flowgraph.c": "331f430d7e028b7bd2ab58ec44f7ffaf4a7ac4d53985cdf723477d9406b12aac", + "Python/instrumentation.c": "1650fd63b57447c4ac6d8cd7a8dbd81e5bf0124fd4ce7615b2c348880757fadf", + "Python/marshal.c": "2d2fe69cf2e2f2fcc2693084b91c615ba8c0edba83b003b8bc6408bf1edf77e1", + "Python/specialize.c": "372091addcc5d351b6960ecd9e181ef6ea4af7f367f6d0b5aa655c2a2f7334ce", + "Python/sysmodule.c": "a7c715a7d5db0ffa601b151235dfe7e928b57bd8150cf02eafcd1d5cafcde25f", + "Tools/cases_generator/_typing_backports.py": "4fe25f5705e4f9f047ba330c70264032291c64d24cbd5f2d6da0615978521021", + "Tools/cases_generator/analyzer.py": "10c410377629b4897cf1289bc84743e61c67c0a8c09ab5b4aba0b7fc74809763", + "Tools/cases_generator/cwriter.py": "fb65713b1ad11c5dc190f50f6bb0ed026d5b582c9e5bac623037c000e25c9403", + "Tools/cases_generator/generators_common.py": "06912332943c5962c45314d92bb7a40d95802c165b3dc791d9680e1c5a450118", + "Tools/cases_generator/lexer.py": "80f2c5b97e012e0acda4b0a3de38506dbdd926e64f6328344172246d34ee0c4a", + "Tools/cases_generator/opcode_id_generator.py": "69535cc326af8b3b649000c96d7f609f79a3d7b0a737a56469a9b5c442914966", + "Tools/cases_generator/opcode_metadata_generator.py": "48bc3b643528a6d9eee6a48361882b5a2e30024fe74ae9f1a062f1d9524dd2d3", + "Tools/cases_generator/optimizer_generator.py": "3cf3e9e3881b33700a851732c18ab32db4dddd4488ecc7b83e6e404caffd7e2f", + "Tools/cases_generator/parser.py": "9f88319c6f461740cbfda18feb338a04276cd6672b5475956b155ba62f8912f6", + "Tools/cases_generator/parsing.py": "02f90f5a1dc0c9ded0fcadf74b2f703301b0e5038ec0f2fdc0f185942ed77928", + "Tools/cases_generator/plexer.py": "c13ce4a0b558791b1843e0969ee6abfc3e094e2116cc817fc1101dee9ccbb876", + "Tools/cases_generator/py_metadata_generator.py": "23ba6de30dfc06bc7189e7636c98604fdf1e654af504237699a5b68a19fbca20", + "Tools/cases_generator/record_function_generator.py": "13b66491a9f50df69ede5af3cf67dfa3b1ad688baf85c17f418a1ac29a1b107a", + "Tools/cases_generator/stack.py": "2e192c11d7264d322cf691a3f66ce687459869c9b132710868a69d225103a4a1", + "Tools/cases_generator/target_generator.py": "c7087ffceda3ac08c58aeb756f6a1326fd3a2dac4514bc2124b4660327c3d8b5", + "Tools/cases_generator/tier1_generator.py": "a0c2ae9eb48d6e84f9b5fe3857ffb2aabb32e6231995a5e2c2a32d0e44788b57", + "Tools/cases_generator/tier2_generator.py": "cb44cf5c1493a79b01b779bbf9b8efcf78bee1d339c03d3afe4c050e576fb09d", + "Tools/cases_generator/uop_id_generator.py": "3aec741aed85baa041a9f40e1bb6019dd4cc35ae4d4de0b0c5dd092011fa439f", + "Tools/cases_generator/uop_metadata_generator.py": "d95e5001035eda47642ecd70d2884b39f7dd69547bcd9c77dd9a341e2e088f3b" + }, + "tag": "v3.15.0" +} diff --git a/tools/opcode_metadata/target_source.py b/tools/opcode_metadata/target_source.py new file mode 100644 index 00000000000..68d993aaf41 --- /dev/null +++ b/tools/opcode_metadata/target_source.py @@ -0,0 +1,36 @@ +"""Fail closed before importing any target CPython generator code.""" + +import hashlib +import json +import sys +from pathlib import Path + + +def verify_source(root): + caches = list((root / "Tools/cases_generator").rglob("*.pyc")) + if caches: + raise ValueError( + "target generator source must be cache-free; found " + str(caches[0]) + ) + manifest = json.loads(Path(__file__).with_suffix(".json").read_text()) + for relative, expected in manifest["files"].items(): + path = root / relative + actual = hashlib.sha256(path.read_bytes()).hexdigest() + if actual != expected: + raise ValueError(f"wrong {manifest['tag']} source: {relative}: {actual}") + return manifest + + +def verify_import_origins(root): + manifest = json.loads(Path(__file__).with_suffix(".json").read_text()) + for relative in manifest["files"]: + path = Path(relative) + if path.parent != Path("Tools/cases_generator") or path.suffix != ".py": + continue + module = sys.modules.get(path.stem) + if module is not None: + origin = getattr(module, "__file__", None) + if origin is None or Path(origin).resolve() != (root / path).resolve(): + raise ValueError( + "generator dependency loaded outside pinned source: " + path.stem + ) diff --git a/tools/opcode_metadata/verify_opcode_ids.py b/tools/opcode_metadata/verify_opcode_ids.py new file mode 100644 index 00000000000..c2a8d4da252 --- /dev/null +++ b/tools/opcode_metadata/verify_opcode_ids.py @@ -0,0 +1,44 @@ +"""Verify every active public discriminant against pinned final opcode_ids.h.""" + +import argparse +import re +from pathlib import Path + +from target_source import verify_source + +from utils import DEFAULT_INPUT, to_upper_snake_case + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", required=True, type=Path) + args = parser.parse_args() + verify_source(args.source) + ids = { + name: int(value) + for name, value in re.findall( + r"^#define\s+(\w+)\s+(\d+)\s*$", + (args.source / "Include/opcode_ids.h").read_text(), + re.M, + ) + } + private = { + "GetYieldFromIter": 219, + "LoadConstImmortal": 220, + "LoadConstMortal": 221, + } + entries = re.findall( + r"^ (\w+)(?:\s*\{[^}]*\})?\s*=\s*(\d+),", + DEFAULT_INPUT.read_text(), + re.M, + ) + assert len(entries) == 238 + assert len({int(value) for _, value in entries}) == len(entries) + for name, value in entries: + expected = private[name] if name in private else ids[to_upper_snake_case(name)] + assert int(value) == expected, (name, value, expected) + print("Verified 235 public and 3 explicitly private opcode discriminants") + + +if __name__ == "__main__": + main() diff --git a/tools/opcode_metadata/verify_py_opcode_metadata.py b/tools/opcode_metadata/verify_py_opcode_metadata.py new file mode 100644 index 00000000000..e0acbf94e90 --- /dev/null +++ b/tools/opcode_metadata/verify_py_opcode_metadata.py @@ -0,0 +1,24 @@ +"""Verify exact final Python metadata; refresh it only with scripts/update_lib.""" + +import os +from pathlib import Path + +from target_source import verify_source + +from utils import ROOT + + +def main(): + source = Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve() + verify_source(source) + for name in ("opcode.py", "_opcode_metadata.py", "dis.py"): + relative = Path("Lib") / name + if (ROOT / relative).read_bytes() != (source / relative).read_bytes(): + raise ValueError( + f"{relative} differs from pinned final source; use scripts/update_lib" + ) + print(f"Verified {relative}") + + +if __name__ == "__main__": + main() diff --git a/tools/opcode_metadata/verify_stack_contract.py b/tools/opcode_metadata/verify_stack_contract.py new file mode 100644 index 00000000000..f0ac88780f5 --- /dev/null +++ b/tools/opcode_metadata/verify_stack_contract.py @@ -0,0 +1,51 @@ +"""Regenerate independent C stack goldens, or fail if checked-in rows differ.""" + +import argparse +import subprocess +import tempfile +from pathlib import Path + +from target_source import verify_source + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, required=True) + parser.add_argument("--build", type=Path, required=True) + parser.add_argument("--write", action="store_true") + args = parser.parse_args() + verify_source(args.source) + here = Path(__file__).resolve().parent + with tempfile.TemporaryDirectory() as temporary: + binary = Path(temporary) / "stack-contract" + subprocess.run( + [ + "cc", + "-I" + str(args.build), + "-I" + str(args.source / "Include"), + "-I" + str(args.source / "Include/internal"), + str(here / "stack_contract.c"), + "-o", + str(binary), + ], + check=True, + ) + output = subprocess.check_output([str(binary)], text=True) + rows = [tuple(map(int, line.split())) for line in output.splitlines()] + assert len(rows) == 3430 and len(set(rows)) == 3430 + contents = ( + "// Generated by tools/opcode_metadata/verify_stack_contract.py from pinned CPython C metadata.\n" + "const TARGET_STACK_CASES: &[(u16, u32, u32, u32)] = &[\n" + + "".join(f" ({op}, {arg}, {pop}, {push}),\n" for op, arg, pop, push in rows) + + "];\n" + ) + target = here.parents[1] / "crates/compiler-core/src/bytecode/target_stack_cases.rs" + if args.write: + target.write_text(contents) + elif target.read_text() != contents: + raise SystemExit("pinned C stack rows differ") + print(f"Verified {len(rows)} exact C stack rows") + + +if __name__ == "__main__": + main() From 3ad58d2a4a9700749ad97b1ed11fa1bd706d9367 Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:40 +0900 Subject: [PATCH 02/12] Fix final script validation, exception cleanup and private cache contracts Replay reviewed compiler CI repairs 2191a9d, 1f1fe04, a2dacaf on the frozen current prerequisite. Intermediate compile/diagnostic corrections are combined where they address the same failure. Assisted-by: OpenAI Codex: model identifier unavailable --- crates/vm/src/version.rs | 5 +- crates/vm/src/vm/crossinterp.rs | 56 ++++++------------- crates/vm/src/vm/thread.rs | 13 +++-- .../snippets/private_315_cache_contract.py | 8 ++- 4 files changed, 32 insertions(+), 50 deletions(-) diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index a2c6f42de62..7c24b23d8da 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -69,9 +69,10 @@ pub const RUSTPYTHON_VERSION: &str = const { }; // Private, non-distributable reconstruction ABI. This is not CPython's magic. -// Change token and tag together for every incompatible development format. +// Change the token for every incompatible development format. The public +// cache tag identifies the implementation/version; magic rejects stale ABIs. pub const PYC_MAGIC_NUMBER: u16 = 0xDA16; -pub const PYC_CACHE_TAG: &str = "rustpython-315-devabi2"; +pub const PYC_CACHE_TAG: &str = concat!("rustpython-", env!("MAJOR_CPY"), env!("MINOR_CPY")); // CPython format: magic_number | ('\r' << 16) | ('\n' << 24) // This protects against text-mode file reads diff --git a/crates/vm/src/vm/crossinterp.rs b/crates/vm/src/vm/crossinterp.rs index a72d34ea92a..3cc93d4783b 100644 --- a/crates/vm/src/vm/crossinterp.rs +++ b/crates/vm/src/vm/crossinterp.rs @@ -15,7 +15,7 @@ use crate::{ }, bytecode::{ BorrowedConstant, CodeFlags, Constant, Instruction, - oparg::{OpArg, OpArgState}, + oparg::{CommonConstant, OpArg, OpArgState}, }, protocol::PyBuffer, }; @@ -828,47 +828,25 @@ pub(crate) fn code_returns_only_none(code: &Py) -> bool { if !is_pure_function(code) { return false; } - let units = &code.instructions; - let Some(last) = units.len().checked_sub(1) else { - return true; - }; - // The last instruction either returns or raises. We can take advantage - // of that for a quick exit. - let final_op = units.read_op(last).deoptimize(); - - // Look up None in co_consts. - let Some(none_index) = code - .constants - .iter() - .position(|c| matches!(c.borrow_constant(), BorrowedConstant::None)) - else { - // None wasn't there, which means there was no implicit return, - // "return", or "return None". - - // That means there must be an explicit return (non-None), or it only - // raises. - if matches!(final_op, Instruction::ReturnValue) { - // It was an explicit return (non-None). - return false; - } - // It must end with a raise then. We still have to walk the bytecode - // to see if there's any explicit return (non-None). - return !walk_instructions(code).any(|(_, op, _)| matches!(op, Instruction::ReturnValue)); - }; - // Walk the bytecode, looking for RETURN_VALUE. - for (at, op, _) in walk_instructions(code) { - if !matches!(op, Instruction::ReturnValue) { + // Final 3.15 bytecode loads None through LOAD_COMMON_CONSTANT, so it + // need not appear in co_consts. Walk decoded operands rather than looking + // one raw code unit back, which can lose EXTENDED_ARG bits. + let mut previous_loads_none = false; + for (_, op, arg) in walk_instructions(code) { + if matches!(op, Instruction::ExtendedArg) { continue; } - // Ignore it if it returns None. - if let Some(prev) = at.checked_sub(1) - && matches!(units.read_op(prev).deoptimize(), Instruction::LoadConst { .. }) - // We don't worry about EXTENDED_ARG for now. - && usize::from(u8::from(units.read_arg(prev))) == none_index - { - continue; + if matches!(op, Instruction::ReturnValue) && !previous_loads_none { + return false; } - return false; + previous_loads_none = match op { + Instruction::LoadCommonConstant { idx } => idx.get(arg) == CommonConstant::None, + Instruction::LoadConst { consti } => code + .constants + .get(usize::from(consti.get(arg))) + .is_some_and(|value| matches!(value.borrow_constant(), BorrowedConstant::None)), + _ => false, + }; } true } diff --git a/crates/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs index e1bc40c44b0..3c21fe4f487 100644 --- a/crates/vm/src/vm/thread.rs +++ b/crates/vm/src/vm/thread.rs @@ -1094,13 +1094,14 @@ pub fn get_current_frame() -> *const InterpreterFrame { /// Called from push_exception/pop_exception/set_exception. #[cfg(feature = "threading")] pub fn update_thread_exception(exc: Option) { - CURRENT_THREAD_SLOT.with(|slot| { - if let Some(s) = slot.borrow().as_ref() { - // SAFETY: Called only from the owning thread. The old ref is dropped - // here on the owning thread, which is safe. - let _old = unsafe { s.exception.swap(exc) }; - } + // Release the TLS borrow before dropping either exception: traceback + // locals can finalize another interpreter and re-enter this thread slot. + let slot = current_thread_slot(); + let old = slot.and_then(|s| { + // SAFETY: Called only from the owning thread. + unsafe { s.exception.swap(exc) } }); + drop(old); } /// Collect all threads' current exceptions for sys._current_exceptions(). diff --git a/extra_tests/snippets/private_315_cache_contract.py b/extra_tests/snippets/private_315_cache_contract.py index 334fd9426df..0f0bdfe88a4 100644 --- a/extra_tests/snippets/private_315_cache_contract.py +++ b/extra_tests/snippets/private_315_cache_contract.py @@ -11,12 +11,14 @@ if sys.implementation.name == "rustpython": assert external.MAGIC_NUMBER == bytes((0x16, 0xDA, 13, 10)) - assert sys.implementation.cache_tag == "rustpython-315-devabi2" + assert sys.implementation.cache_tag == ( + f"{sys.implementation.name}-{sys.version_info.major}{sys.version_info.minor}" + ) with tempfile.TemporaryDirectory() as directory: root = Path(directory) source = root / "cache_subject.py" - source.write_text("value = 41\n", encoding="ascii") + source.write_text("value = 41\n", encoding="ascii", newline="\n") code = compile("value = 42\n", "byte_exact/cache_subject.py", "exec") stat = source.stat() headers = [ @@ -98,7 +100,7 @@ def load_from(path): raise AssertionError("foreign ZIP cache accepted") # Explicit py_compile writes under -B; all writers must use the private token. - source.write_text("value = 41\n", encoding="ascii") + source.write_text("value = 41\n", encoding="ascii", newline="\n") explicit = root / "explicit.pyc" previous = sys.dont_write_bytecode sys.dont_write_bytecode = True From 67efaa18c47a31ea923c2c43e51d0f01063bc9cb Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:43 +0900 Subject: [PATCH 03/12] Align canonical compiler fixtures and generator frame state Replay reviewed compiler CI repairs 476bb74 on the frozen current prerequisite. Intermediate compile/diagnostic corrections are combined where they address the same failure. Assisted-by: OpenAI Codex: model identifier unavailable --- Lib/test/test_code.py | 35 +- Lib/test/test_compile.py | 61 ++- Lib/test/test_dis.py | 660 +++++++++++++++------------- Lib/test/test_generators.py | 46 +- Lib/test/test_monitoring.py | 556 ++++++++++++++++++----- Lib/test/test_peepholer.py | 374 +++++++++++++--- crates/vm/src/builtins/frame.rs | 22 +- crates/vm/src/builtins/generator.rs | 13 + crates/vm/src/coroutine.rs | 26 ++ 9 files changed, 1249 insertions(+), 544 deletions(-) diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 6c26259cf15..77193be1263 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -87,7 +87,7 @@ freevars: () nlocals: 0 flags: 67108867 -consts: ("'doc string'", 'None') +consts: ("'doc string'",) >>> def keywordonly_args(a,b,*,k1): ... return a,b,k1 @@ -161,7 +161,7 @@ freevars: () nlocals: 3 flags: 67108995 -consts: ("'This is a docstring from async function'", 'None') +consts: ("'This is a docstring from async function'",) >>> def no_docstring(x, y, z): ... return x + "hello" + y + z + "world" @@ -423,14 +423,6 @@ def func(): new_code = code = func.__code__.replace(co_linetable=b'') self.assertEqual(list(new_code.co_lines()), []) - @unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12) - def test_co_lnotab_is_deprecated(self): # TODO: remove in 3.14 - def func(): - pass - - with self.assertWarns(DeprecationWarning): - func.__code__.co_lnotab - @unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing') def test_returns_only_none(self): value = True @@ -540,7 +532,7 @@ def test_co_positions_artificial_instructions(self): ], [ ("PUSH_EXC_INFO", None), - ("LOAD_CONST", None), # artificial 'None' + ("LOAD_COMMON_CONSTANT", None), # artificial 'None' ("STORE_NAME", "e"), # XX: we know the location for this ("DELETE_NAME", "e"), ("RERAISE", 1), @@ -1165,6 +1157,22 @@ def test_stateless(self): with self.assertRaises(Exception): _testinternalcapi.verify_stateless_code(func) + def test_code_richcompare_raise_exception(self): + class BadStr(str): + def __eq__(self, _): + raise RuntimeError("Poison!") + + __hash__ = str.__hash__ + + c1 = compile("pass", "test", "exec") + c2 = c1.replace(co_name=BadStr("poison")) + c3 = compile("pass", "poison", "exec") + with self.assertRaises(RuntimeError): + c2 == c3 + + @unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12) + def test_co_lnotab_is_deprecated(self): + return super().test_co_lnotab_is_deprecated() def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) @@ -1438,6 +1446,7 @@ def f(): co_code=bytes( [ dis.opmap["RESUME"], 0, + dis.opmap["CACHE"], 0, dis.opmap["LOAD_COMMON_CONSTANT"], 0, dis.opmap["RAISE_VARARGS"], 1, ] @@ -1446,7 +1455,7 @@ def f(): [ (1 << 7) | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3) - | (3 - 1), + | (4 - 1), 0, ] ), @@ -1454,7 +1463,7 @@ def f(): self.assertRaises(AssertionError, f) self.assertEqual( list(f.__code__.co_positions()), - 3 * [(42, 42, None, None)], + 4 * [(42, 42, None, None)], ) @cpython_only diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 021c3622566..d279851e7b7 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -724,8 +724,7 @@ def test_yet_more_evil_still_undecodable(self): @support.cpython_only @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") - @support.run_with_limited_c_stack( - 100_000 if sys.platform == "android" else 500_000) + @support.skip_if_huge_c_stack(100_000 if sys.platform == "android" else 500_000) @support.skip_emscripten_stack_overflow() def test_compiler_recursion_limit(self): # Compiler frames are small @@ -841,8 +840,10 @@ def f(): else: return "unused" - self.assertEqual(f.__code__.co_consts, - (f.__doc__, "used")) + if f.__doc__ is None: + self.assertEqual(f.__code__.co_consts, (True, "used")) + else: + self.assertEqual(f.__code__.co_consts, (f.__doc__, "used")) @support.cpython_only def test_remove_unused_consts_no_docstring(self): @@ -887,7 +888,11 @@ def test_strip_unused_None(self): def f1(): "docstring" return 42 - self.assertEqual(f1.__code__.co_consts, (f1.__doc__,)) + + if f1.__doc__ is None: + self.assertEqual(f1.__code__.co_consts, (42,)) + else: + self.assertEqual(f1.__code__.co_consts, (f1.__doc__,)) # This is a regression test for a CPython specific peephole optimizer # implementation bug present in a few releases. It's assertion verifies @@ -1294,7 +1299,7 @@ def return_genexp(): x in y) - genexp_lines = [0, 4, 2, 0, 4] + genexp_lines = [4, 0, 4, 2, 0, 4] genexp_code = return_genexp.__code__.co_consts[0] code_lines = self.get_code_lines(genexp_code) @@ -1629,6 +1634,17 @@ def test_remove_redundant_nop_edge_case(self): def f(): a if (1 if b else c) else d + def test_lineno_propagation_empty_blocks(self): + # Smoke test. See gh-138714. + def f(): + while name: + try: + break + except: + pass + else: + 1 if 1 else 1 + def test_global_declaration_in_except_used_in_else(self): # See gh-111123 code = textwrap.dedent("""\ @@ -1733,6 +1749,30 @@ def test_compile_warning_in_finally(self): self.assertEqual(wm.category, SyntaxWarning) self.assertIn("\"is\" with 'int' literal", str(wm.message)) + def test_filter_syntax_warnings_by_module(self): + filename = support.findfile('test_import/data/syntax_warnings.py') + with open(filename, 'rb') as f: + source = f.read() + module_re = r'test\.test_import\.data\.syntax_warnings\z' + with warnings.catch_warnings(record=True) as wlog: + warnings.simplefilter('error') + warnings.filterwarnings('always', module=module_re) + compile(source, filename, 'exec') + self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21]) + for wm in wlog: + self.assertEqual(wm.filename, filename) + self.assertIs(wm.category, SyntaxWarning) + + with warnings.catch_warnings(record=True) as wlog: + warnings.simplefilter('error') + warnings.filterwarnings('always', module=r'package\.module\z') + warnings.filterwarnings('error', module=module_re) + compile(source, filename, 'exec', module='package.module') + self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21]) + for wm in wlog: + self.assertEqual(wm.filename, filename) + self.assertIs(wm.category, SyntaxWarning) + @support.subTests('src', [ textwrap.dedent(""" def f(): @@ -2419,8 +2459,8 @@ def test_lambda_return_position(self): for i, pos in enumerate(positions): with self.subTest(i=i, pos=pos): start_line, end_line, start_col, end_col = pos - if i == 0 and start_col == end_col == 0: - # ignore the RESUME in the beginning + if i <= 1: + # ignore the RESUME and CACHE in the beginning continue self.assertEqual(start_line, 1) self.assertEqual(end_line, 1) @@ -2446,12 +2486,13 @@ def f(): start_line, end_line, _, _ = instr.positions self.assertEqual(start_line, end_line) - # Expect four `LOAD_CONST None` instructions: + # Expect four `None`-loading instructions: # three for the no-exception __exit__ call, and one for the return. # They should all have the locations of the context manager ('xyz'). load_none = [instr for instr in dis.get_instructions(f) if - instr.opname == 'LOAD_CONST' and instr.argval is None] + instr.opname in ('LOAD_CONST', 'LOAD_COMMON_CONSTANT') + and instr.argval is None] return_value = [instr for instr in dis.get_instructions(f) if instr.opname == 'RETURN_VALUE'] diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 190374fba8c..a103601a23a 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -56,7 +56,7 @@ def cm(cls, x): COMPARE_OP 72 (==) LOAD_FAST_BORROW 0 (self) STORE_ATTR 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (_C.__init__.__code__.co_firstlineno, _C.__init__.__code__.co_firstlineno + 1,) @@ -67,7 +67,7 @@ def cm(cls, x): COMPARE_OP 72 (==) LOAD_FAST_BORROW 0 STORE_ATTR 0 - LOAD_CONST 1 + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -79,7 +79,7 @@ def cm(cls, x): COMPARE_OP 72 (==) LOAD_FAST_BORROW 0 (cls) STORE_ATTR 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (_C.cm.__code__.co_firstlineno, _C.cm.__code__.co_firstlineno + 2,) @@ -90,7 +90,7 @@ def cm(cls, x): LOAD_SMALL_INT 1 COMPARE_OP 72 (==) STORE_FAST 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (_C.sm.__code__.co_firstlineno, _C.sm.__code__.co_firstlineno + 2,) @@ -127,13 +127,13 @@ def _f(a): dis_f_with_offsets = """\ %3d 0 RESUME 0 -%3d 2 LOAD_GLOBAL 1 (print + NULL) - 12 LOAD_FAST_BORROW 0 (a) - 14 CALL 1 - 22 POP_TOP +%3d 4 LOAD_GLOBAL 1 (print + NULL) + 14 LOAD_FAST_BORROW 0 (a) + 16 CALL 1 + 24 POP_TOP -%3d 24 LOAD_SMALL_INT 1 - 26 RETURN_VALUE +%3d 26 LOAD_SMALL_INT 1 + 28 RETURN_VALUE """ % (_f.__code__.co_firstlineno, _f.__code__.co_firstlineno + 1, _f.__code__.co_firstlineno + 2) @@ -174,7 +174,7 @@ def bug708901(): %3d LOAD_SMALL_INT 10 %3d CALL 2 - GET_ITER + GET_ITER 0 L1: FOR_ITER 3 (to L2) STORE_FAST 0 (res) @@ -182,7 +182,7 @@ def bug708901(): %3d L2: END_FOR POP_ITER - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (bug708901.__code__.co_firstlineno, bug708901.__code__.co_firstlineno + 1, @@ -204,7 +204,6 @@ def bug1333982(x=[]): LOAD_CONST 1 ( at 0x..., file "%s", line %d>) MAKE_FUNCTION LOAD_FAST_BORROW 0 (x) - GET_ITER CALL 0 %3d LOAD_SMALL_INT 1 @@ -230,7 +229,7 @@ def bug42562(): dis_bug42562 = """\ RESUME 0 - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -283,7 +282,7 @@ def wrap_func_w_kwargs(): LOAD_CONST 1 (('c',)) CALL_KW 3 POP_TOP - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (wrap_func_w_kwargs.__code__.co_firstlineno, wrap_func_w_kwargs.__code__.co_firstlineno + 1) @@ -293,10 +292,10 @@ def wrap_func_w_kwargs(): 1 LOAD_SMALL_INT 0 LOAD_CONST 1 (('*',)) - IMPORT_NAME 0 (math) + IMPORT_NAME 2 (math + eager) CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) POP_TOP - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -323,7 +322,7 @@ def wrap_func_w_kwargs(): %3d LOAD_GLOBAL 0 (spam) POP_TOP - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -332,19 +331,19 @@ def wrap_func_w_kwargs(): %4d LOAD_GLOBAL 0 (spam) POP_TOP - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ dis_module_expected_results = """\ Disassembly of f: 4 RESUME 0 - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE Disassembly of g: 5 RESUME 0 - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -369,7 +368,7 @@ def wrap_func_w_kwargs(): LOAD_SMALL_INT 1 BINARY_OP 0 (+) STORE_NAME 0 (x) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -410,7 +409,7 @@ def wrap_func_w_kwargs(): LOAD_SMALL_INT 0 CALL 1 STORE_SUBSCR - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -428,7 +427,7 @@ def foo(a: int, b: str) -> str: MAKE_FUNCTION SET_FUNCTION_ATTRIBUTE 16 (annotate) STORE_NAME 0 (foo) - LOAD_CONST 2 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ @@ -454,52 +453,53 @@ def foo(a: int, b: str) -> str: """ dis_traceback = """\ -%4d RESUME 0 +%4d RESUME 0 -%4d NOP +%4d NOP -%4d L1: LOAD_SMALL_INT 1 - LOAD_SMALL_INT 0 - --> BINARY_OP 11 (/) - POP_TOP +%4d L1: LOAD_SMALL_INT 1 + LOAD_SMALL_INT 0 + --> BINARY_OP 11 (/) + POP_TOP -%4d L2: LOAD_FAST_CHECK 1 (tb) - RETURN_VALUE +%4d L2: LOAD_FAST_CHECK 1 (tb) + RETURN_VALUE - -- L3: PUSH_EXC_INFO + -- L3: PUSH_EXC_INFO -%4d LOAD_GLOBAL 0 (Exception) - CHECK_EXC_MATCH - POP_JUMP_IF_FALSE 24 (to L7) - NOT_TAKEN - STORE_FAST 0 (e) +%4d LOAD_GLOBAL 0 (Exception) + CHECK_EXC_MATCH + POP_JUMP_IF_FALSE 24 (to L9) + L4: NOT_TAKEN + L5: STORE_FAST 0 (e) -%4d L4: LOAD_FAST 0 (e) - LOAD_ATTR 2 (__traceback__) - STORE_FAST 1 (tb) - L5: POP_EXCEPT - LOAD_CONST 1 (None) - STORE_FAST 0 (e) - DELETE_FAST 0 (e) +%4d L6: LOAD_FAST 0 (e) + LOAD_ATTR 2 (__traceback__) + STORE_FAST 1 (tb) + L7: POP_EXCEPT + LOAD_COMMON_CONSTANT 7 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) -%4d LOAD_FAST 1 (tb) - RETURN_VALUE +%4d LOAD_FAST 1 (tb) + RETURN_VALUE - -- L6: LOAD_CONST 1 (None) - STORE_FAST 0 (e) - DELETE_FAST 0 (e) - RERAISE 1 + -- L8: LOAD_COMMON_CONSTANT 7 (None) + STORE_FAST 0 (e) + DELETE_FAST 0 (e) + RERAISE 1 -%4d L7: RERAISE 0 +%4d L9: RERAISE 0 - -- L8: COPY 3 - POP_EXCEPT - RERAISE 1 + -- L10: COPY 3 + POP_EXCEPT + RERAISE 1 ExceptionTable: L1 to L2 -> L3 [0] - L3 to L4 -> L8 [1] lasti - L4 to L5 -> L6 [1] lasti - L6 to L8 -> L8 [1] lasti + L3 to L4 -> L10 [1] lasti + L5 to L6 -> L10 [1] lasti + L6 to L7 -> L8 [1] lasti + L8 to L10 -> L10 [1] lasti """ % (TRACEBACK_CODE.co_firstlineno, TRACEBACK_CODE.co_firstlineno + 1, TRACEBACK_CODE.co_firstlineno + 2, @@ -554,40 +554,41 @@ def _with(c): %4d LOAD_SMALL_INT 1 STORE_FAST 1 (x) -%4d L2: LOAD_CONST 1 (None) - LOAD_CONST 1 (None) - LOAD_CONST 1 (None) +%4d L2: LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) CALL 3 POP_TOP %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE %4d L3: PUSH_EXC_INFO WITH_EXCEPT_START TO_BOOL - POP_JUMP_IF_TRUE 2 (to L4) - NOT_TAKEN - RERAISE 2 - L4: POP_TOP - L5: POP_EXCEPT + POP_JUMP_IF_TRUE 2 (to L6) + L4: NOT_TAKEN + L5: RERAISE 2 + L6: POP_TOP + L7: POP_EXCEPT POP_TOP POP_TOP POP_TOP %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE - -- L6: COPY 3 + -- L8: COPY 3 POP_EXCEPT RERAISE 1 ExceptionTable: L1 to L2 -> L3 [2] lasti - L3 to L5 -> L6 [4] lasti + L3 to L4 -> L8 [4] lasti + L5 to L7 -> L8 [4] lasti """ % (_with.__code__.co_firstlineno, _with.__code__.co_firstlineno + 1, _with.__code__.co_firstlineno + 2, @@ -615,47 +616,50 @@ async def _asyncwith(c): LOAD_SPECIAL 2 (__aenter__) CALL 0 GET_AWAITABLE 1 - LOAD_CONST 0 (None) - L2: SEND 3 (to L5) + PUSH_NULL + LOAD_COMMON_CONSTANT 7 (None) + L2: SEND 4 (to L5) L3: YIELD_VALUE 1 L4: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L2) + JUMP_BACKWARD_NO_INTERRUPT 6 (to L2) L5: END_SEND L6: POP_TOP %4d LOAD_SMALL_INT 1 STORE_FAST 1 (x) -%4d L7: LOAD_CONST 0 (None) - LOAD_CONST 0 (None) - LOAD_CONST 0 (None) +%4d L7: LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) + LOAD_COMMON_CONSTANT 7 (None) CALL 3 GET_AWAITABLE 2 - LOAD_CONST 0 (None) - L8: SEND 3 (to L11) + PUSH_NULL + LOAD_COMMON_CONSTANT 7 (None) + L8: SEND 4 (to L11) L9: YIELD_VALUE 1 L10: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L8) + JUMP_BACKWARD_NO_INTERRUPT 6 (to L8) L11: END_SEND POP_TOP %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE %4d L12: CLEANUP_THROW - L13: JUMP_BACKWARD_NO_INTERRUPT 26 (to L5) + L13: JUMP_BACKWARD_NO_INTERRUPT 28 (to L5) L14: CLEANUP_THROW L15: JUMP_BACKWARD_NO_INTERRUPT 10 (to L11) L16: PUSH_EXC_INFO WITH_EXCEPT_START GET_AWAITABLE 2 - LOAD_CONST 0 (None) - L17: SEND 4 (to L21) + PUSH_NULL + LOAD_COMMON_CONSTANT 7 (None) + L17: SEND 5 (to L21) L18: YIELD_VALUE 1 L19: RESUME 3 - JUMP_BACKWARD_NO_INTERRUPT 5 (to L17) + JUMP_BACKWARD_NO_INTERRUPT 6 (to L17) L20: CLEANUP_THROW L21: END_SEND TO_BOOL @@ -670,7 +674,7 @@ async def _asyncwith(c): %4d LOAD_SMALL_INT 2 STORE_FAST 2 (y) - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE -- L26: COPY 3 @@ -680,15 +684,15 @@ async def _asyncwith(c): RERAISE 1 ExceptionTable: L1 to L3 -> L27 [0] lasti - L3 to L4 -> L12 [4] + L3 to L4 -> L12 [5] L4 to L6 -> L27 [0] lasti L6 to L7 -> L16 [2] lasti L7 to L9 -> L27 [0] lasti - L9 to L10 -> L14 [2] + L9 to L10 -> L14 [3] L10 to L13 -> L27 [0] lasti L14 to L15 -> L27 [0] lasti L16 to L18 -> L26 [4] lasti - L18 to L19 -> L20 [7] + L18 to L19 -> L20 [8] L19 to L22 -> L26 [4] lasti L23 to L25 -> L26 [4] lasti L25 to L27 -> L27 [0] lasti @@ -826,14 +830,35 @@ def foo(x): %4d RESUME 0 -%4d LOAD_GLOBAL 1 (list + NULL) +%4d LOAD_GLOBAL 0 (list) + COPY 1 + LOAD_COMMON_CONSTANT 5 (list) + IS_OP 0 (is) + POP_JUMP_IF_FALSE 22 (to L3) + NOT_TAKEN + POP_TOP + BUILD_LIST 0 + LOAD_FAST_BORROW 0 (x) + BUILD_TUPLE 1 + LOAD_CONST %d ( at 0x..., file "%s", line %d>) + MAKE_FUNCTION + SET_FUNCTION_ATTRIBUTE 8 (closure) + LOAD_DEREF 1 (y) + CALL 0 + PUSH_NULL + L1: FOR_ITER 3 (to L2) + LIST_APPEND 3 + JUMP_BACKWARD 5 (to L1) + L2: END_FOR + POP_ITER + RETURN_VALUE + L3: PUSH_NULL LOAD_FAST_BORROW 0 (x) BUILD_TUPLE 1 LOAD_CONST 1 ( at 0x..., file "%s", line %d>) MAKE_FUNCTION SET_FUNCTION_ATTRIBUTE 8 (closure) LOAD_DEREF 1 (y) - GET_ITER CALL 0 CALL 1 RETURN_VALUE @@ -842,6 +867,9 @@ def foo(x): _h.__code__.co_firstlineno + 1, _h.__code__.co_firstlineno + 1, _h.__code__.co_firstlineno + 3, + 1 if __debug__ else 0, + __file__, + _h.__code__.co_firstlineno + 3, __file__, _h.__code__.co_firstlineno + 3, ) @@ -849,29 +877,31 @@ def foo(x): dis_nested_2 = """%s Disassembly of at 0x..., file "%s", line %d>: -- COPY_FREE_VARS 1 + RESUME 4 -%4d RETURN_GENERATOR +%4d LOAD_FAST 0 (.0) + GET_ITER 0 + RETURN_GENERATOR POP_TOP L1: RESUME 0 - LOAD_FAST 0 (.0) - L2: FOR_ITER 14 (to L3) + L2: FOR_ITER 15 (to L3) STORE_FAST 1 (z) LOAD_DEREF 2 (x) LOAD_FAST_BORROW 1 (z) BINARY_OP 0 (+) YIELD_VALUE 0 - RESUME 5 + RESUME 9 POP_TOP - JUMP_BACKWARD 16 (to L2) + JUMP_BACKWARD 17 (to L2) L3: END_FOR POP_ITER - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) RERAISE 1 ExceptionTable: - L1 to L4 -> L4 [0] lasti + L1 to L4 -> L4 [2] lasti """ % (dis_nested_1, __file__, _h.__code__.co_firstlineno + 3, @@ -883,7 +913,7 @@ def load_test(x, y=0): return a, b dis_load_test_quickened_code = """\ -%3d RESUME_CHECK 0 +%3d RESUME_CHECK{: <6} 0 %3d LOAD_FAST_LOAD_FAST 1 (x, y) STORE_FAST_STORE_FAST 50 (b, a) @@ -900,14 +930,14 @@ def loop_test(): load_test(i) dis_loop_test_quickened_code = """\ -%3d RESUME_CHECK 0 +%3d RESUME_CHECK{: <6} 0 %3d BUILD_LIST 0 - LOAD_CONST_MORTAL 2 ((1, 2, 3)) + LOAD_CONST 1 ((1, 2, 3)) LIST_EXTEND 1 LOAD_SMALL_INT 3 BINARY_OP 5 (*) - GET_ITER + GET_ITER_VIRTUAL 0 L1: FOR_ITER_LIST 14 (to L2) STORE_FAST 0 (i) @@ -919,7 +949,7 @@ def loop_test(): %3d L2: END_FOR POP_ITER - LOAD_CONST_IMMORTAL 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """ % (loop_test.__code__.co_firstlineno, loop_test.__code__.co_firstlineno + 1, @@ -937,7 +967,7 @@ def extended_arg_quick(): UNPACK_EX 256 POP_TOP STORE_FAST 0 (_) - LOAD_CONST 1 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE """% (extended_arg_quick.__code__.co_firstlineno, extended_arg_quick.__code__.co_firstlineno + 1,) @@ -1009,6 +1039,7 @@ def test_widths(self): long_opcodes = set(['JUMP_BACKWARD_NO_INTERRUPT', 'LOAD_FAST_BORROW_LOAD_FAST_BORROW', 'INSTRUMENTED_CALL_FUNCTION_EX', + 'YIELD_FROM_CORO_CHECK', 'ANNOTATIONS_PLACEHOLDER']) for op, opname in enumerate(dis.opname): if opname in long_opcodes or opname.startswith("INSTRUMENTED"): @@ -1053,7 +1084,7 @@ def test_dis_with_some_positions(self): '', '2:3-3:15 NOP', '', - '3:11-3:15 LOAD_CONST 0 (None)', + '3:11-3:15 LOAD_COMMON_CONSTANT 7 (None)', '3:11-3:15 RETURN_VALUE', '', ' -- L1: PUSH_EXC_INFO', @@ -1084,7 +1115,7 @@ def test_dis_with_linenos_but_no_columns(self): '', '2:5-2:6 LOAD_SMALL_INT 1', '2:?-2:? STORE_FAST 0 (x)', - '2:?-2:? LOAD_CONST 1 (None)', + '2:?-2:? LOAD_COMMON_CONSTANT 7 (None)', '2:?-2:? RETURN_VALUE', '', ]) @@ -1097,7 +1128,7 @@ def f(): f.__code__ = f.__code__.replace(co_linetable=b'') expect = '\n'.join([ ' RESUME 0', - ' LOAD_CONST 0 (None)', + ' LOAD_COMMON_CONSTANT 7 (None)', ' RETURN_VALUE', '', ]) @@ -1297,28 +1328,32 @@ def code_quicken(f): def test_super_instructions(self): self.code_quicken(lambda: load_test(0, 0)) got = self.get_disassembly(load_test, adaptive=True) - self.do_disassembly_compare(got, dis_load_test_quickened_code) + jit = sys._jit.is_enabled() + expected = dis_load_test_quickened_code.format("_JIT" if jit else "") + self.do_disassembly_compare(got, expected) @cpython_only @requires_specialization def test_load_attr_specialize(self): load_attr_quicken = """\ - 0 RESUME_CHECK 0 + 0 RESUME_CHECK{: <6} 0 - 1 LOAD_CONST_IMMORTAL 0 ('a') + 1 LOAD_CONST 0 ('a') LOAD_ATTR_SLOT 0 (__class__) RETURN_VALUE """ co = compile("'a'.__class__", "", "eval") self.code_quicken(lambda: exec(co, {}, {})) got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, load_attr_quicken) + jit = sys._jit.is_enabled() + expected = load_attr_quicken.format("_JIT" if jit else "") + self.do_disassembly_compare(got, expected) @cpython_only @requires_specialization def test_call_specialize(self): call_quicken = """\ - 0 RESUME_CHECK 0 + 0 RESUME_CHECK{: <6} 0 1 LOAD_NAME 0 (str) PUSH_NULL @@ -1329,7 +1364,9 @@ def test_call_specialize(self): co = compile("str(1)", "", "eval") self.code_quicken(lambda: exec(co, {}, {})) got = self.get_disassembly(co, adaptive=True) - self.do_disassembly_compare(got, call_quicken) + jit = sys._jit.is_enabled() + expected = call_quicken.format("_JIT" if jit else "") + self.do_disassembly_compare(got, expected) @cpython_only @requires_specialization @@ -1338,7 +1375,9 @@ def test_loop_quicken(self): self.code_quicken(loop_test) got = self.get_disassembly(loop_test, adaptive=True) jit = sys._jit.is_enabled() - expected = dis_loop_test_quickened_code.format("JIT" if jit else "NO_JIT") + resume_str = "_JIT" if jit else "" + jit_str = "JIT " if jit else "NO_JIT" + expected = dis_loop_test_quickened_code.format(resume_str, jit_str) self.do_disassembly_compare(got, expected) @cpython_only @@ -1405,7 +1444,7 @@ def test_show_caches(self): caches = list(self.get_cached_values(quickened, adaptive)) for cache in caches: self.assertRegex(cache, pattern) - total_caches = 21 + total_caches = 23 empty_caches = 7 self.assertEqual(caches.count(""), empty_caches) self.assertEqual(len(caches), total_caches) @@ -1467,7 +1506,7 @@ def get_disassembly(self, func, lasti=-1, wrapper=True, **kwargs): Kw-only arguments: 0 Number of locals: 1 Stack size: \\d+ -Flags: OPTIMIZED, NEWLOCALS, HAS_DOCSTRING +Flags: OPTIMIZED, NEWLOCALS(, HAS_DOCSTRING)? Constants: {code_info_consts} Names: @@ -1494,7 +1533,6 @@ def f(c=c): Flags: OPTIMIZED, NEWLOCALS, VARARGS, VARKEYWORDS, GENERATOR Constants: 0: - 1: None Variable names: 0: a 1: b @@ -1566,7 +1604,6 @@ def f(c=c): Flags: 0x0 Constants: 0: 1 - 1: None Names: 0: x""" @@ -1601,7 +1638,6 @@ async def async_def(): Flags: OPTIMIZED, NEWLOCALS, COROUTINE Constants: 0: 1 - 1: None Names: 0: b 1: c @@ -1750,209 +1786,210 @@ def _prepare_test_cases(): expected_opinfo_outer = [ make_inst(opname='MAKE_CELL', arg=0, argval='a', argrepr='a', offset=0, start_offset=0, starts_line=True, line_number=None), make_inst(opname='MAKE_CELL', arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=False, line_number=None), - make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1), - make_inst(opname='LOAD_CONST', arg=4, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=True, line_number=2), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=False, line_number=2), - make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=False, line_number=2), - make_inst(opname='BUILD_TUPLE', arg=2, argval=2, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=2), - make_inst(opname='LOAD_CONST', arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, line_number=2), - make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=2), - make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, argrepr='closure', offset=18, start_offset=18, starts_line=False, line_number=2), - make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, argrepr='defaults', offset=20, start_offset=20, starts_line=False, line_number=2), - make_inst(opname='STORE_FAST', arg=2, argval='f', argrepr='f', offset=22, start_offset=22, starts_line=False, line_number=2), - make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=24, start_offset=24, starts_line=True, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_DEREF', arg=0, argval='a', argrepr='a', offset=34, start_offset=34, starts_line=False, line_number=7), - make_inst(opname='LOAD_DEREF', arg=1, argval='b', argrepr='b', offset=36, start_offset=36, starts_line=False, line_number=7), - make_inst(opname='LOAD_CONST', arg=2, argval='', argrepr="''", offset=38, start_offset=38, starts_line=False, line_number=7), - make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=40, start_offset=40, starts_line=False, line_number=7), - make_inst(opname='BUILD_LIST', arg=0, argval=0, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7), - make_inst(opname='BUILD_MAP', arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7), - make_inst(opname='LOAD_CONST', arg=3, argval='Hello world!', argrepr="'Hello world!'", offset=46, start_offset=46, starts_line=False, line_number=7), - make_inst(opname='CALL', arg=7, argval=7, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=7), - make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='f', argrepr='f', offset=58, start_offset=58, starts_line=True, line_number=8), - make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=8), + make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=True, line_number=1, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=3, argval=(3, 4), argrepr='(3, 4)', offset=8, start_offset=8, starts_line=True, line_number=2), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=2), + make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=2), + make_inst(opname='BUILD_TUPLE', arg=2, argval=2, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=2), + make_inst(opname='LOAD_CONST', arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=16, start_offset=16, starts_line=False, line_number=2), + make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=2), + make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, argrepr='closure', offset=20, start_offset=20, starts_line=False, line_number=2), + make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, argrepr='defaults', offset=22, start_offset=22, starts_line=False, line_number=2), + make_inst(opname='STORE_FAST', arg=2, argval='f', argrepr='f', offset=24, start_offset=24, starts_line=False, line_number=2), + make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=26, start_offset=26, starts_line=True, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_DEREF', arg=0, argval='a', argrepr='a', offset=36, start_offset=36, starts_line=False, line_number=7), + make_inst(opname='LOAD_DEREF', arg=1, argval='b', argrepr='b', offset=38, start_offset=38, starts_line=False, line_number=7), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=8, argval='', argrepr="''", offset=40, start_offset=40, starts_line=False, line_number=7), + make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=7), + make_inst(opname='BUILD_LIST', arg=0, argval=0, argrepr='', offset=44, start_offset=44, starts_line=False, line_number=7), + make_inst(opname='BUILD_MAP', arg=0, argval=0, argrepr='', offset=46, start_offset=46, starts_line=False, line_number=7), + make_inst(opname='LOAD_CONST', arg=2, argval='Hello world!', argrepr="'Hello world!'", offset=48, start_offset=48, starts_line=False, line_number=7), + make_inst(opname='CALL', arg=7, argval=7, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=58, start_offset=58, starts_line=False, line_number=7), + make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='f', argrepr='f', offset=60, start_offset=60, starts_line=True, line_number=8), + make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=62, start_offset=62, starts_line=False, line_number=8), ] expected_opinfo_f = [ make_inst(opname='COPY_FREE_VARS', arg=2, argval=2, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None), make_inst(opname='MAKE_CELL', arg=0, argval='c', argrepr='c', offset=2, start_offset=2, starts_line=False, line_number=None), make_inst(opname='MAKE_CELL', arg=1, argval='d', argrepr='d', offset=4, start_offset=4, starts_line=False, line_number=None), - make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=True, line_number=2), - make_inst(opname='LOAD_CONST', arg=2, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=True, line_number=3), - make_inst(opname='LOAD_FAST_BORROW', arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=False, line_number=3), - make_inst(opname='LOAD_FAST_BORROW', arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=False, line_number=3), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=False, line_number=3), - make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=False, line_number=3), - make_inst(opname='BUILD_TUPLE', arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=False, line_number=3), - make_inst(opname='LOAD_CONST', arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=False, line_number=3), - make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3), - make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, argrepr='closure', offset=24, start_offset=24, starts_line=False, line_number=3), - make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, argrepr='defaults', offset=26, start_offset=26, starts_line=False, line_number=3), - make_inst(opname='STORE_FAST', arg=2, argval='inner', argrepr='inner', offset=28, start_offset=28, starts_line=False, line_number=3), - make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=5, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_DEREF', arg=3, argval='a', argrepr='a', offset=40, start_offset=40, starts_line=False, line_number=5), - make_inst(opname='LOAD_DEREF', arg=4, argval='b', argrepr='b', offset=42, start_offset=42, starts_line=False, line_number=5), - make_inst(opname='LOAD_DEREF', arg=0, argval='c', argrepr='c', offset=44, start_offset=44, starts_line=False, line_number=5), - make_inst(opname='LOAD_DEREF', arg=1, argval='d', argrepr='d', offset=46, start_offset=46, starts_line=False, line_number=5), - make_inst(opname='CALL', arg=4, argval=4, argrepr='', offset=48, start_offset=48, starts_line=False, line_number=5, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=56, start_offset=56, starts_line=False, line_number=5), - make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='inner', argrepr='inner', offset=58, start_offset=58, starts_line=True, line_number=6), - make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=60, start_offset=60, starts_line=False, line_number=6), + make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=True, line_number=2, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=2, argval=(5, 6), argrepr='(5, 6)', offset=10, start_offset=10, starts_line=True, line_number=3), + make_inst(opname='LOAD_FAST_BORROW', arg=3, argval='a', argrepr='a', offset=12, start_offset=12, starts_line=False, line_number=3), + make_inst(opname='LOAD_FAST_BORROW', arg=4, argval='b', argrepr='b', offset=14, start_offset=14, starts_line=False, line_number=3), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='c', argrepr='c', offset=16, start_offset=16, starts_line=False, line_number=3), + make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='d', argrepr='d', offset=18, start_offset=18, starts_line=False, line_number=3), + make_inst(opname='BUILD_TUPLE', arg=4, argval=4, argrepr='', offset=20, start_offset=20, starts_line=False, line_number=3), + make_inst(opname='LOAD_CONST', arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=22, start_offset=22, starts_line=False, line_number=3), + make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=3), + make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, argrepr='closure', offset=26, start_offset=26, starts_line=False, line_number=3), + make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, argrepr='defaults', offset=28, start_offset=28, starts_line=False, line_number=3), + make_inst(opname='STORE_FAST', arg=2, argval='inner', argrepr='inner', offset=30, start_offset=30, starts_line=False, line_number=3), + make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=32, start_offset=32, starts_line=True, line_number=5, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_DEREF', arg=3, argval='a', argrepr='a', offset=42, start_offset=42, starts_line=False, line_number=5), + make_inst(opname='LOAD_DEREF', arg=4, argval='b', argrepr='b', offset=44, start_offset=44, starts_line=False, line_number=5), + make_inst(opname='LOAD_DEREF', arg=0, argval='c', argrepr='c', offset=46, start_offset=46, starts_line=False, line_number=5), + make_inst(opname='LOAD_DEREF', arg=1, argval='d', argrepr='d', offset=48, start_offset=48, starts_line=False, line_number=5), + make_inst(opname='CALL', arg=4, argval=4, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=5, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=58, start_offset=58, starts_line=False, line_number=5), + make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='inner', argrepr='inner', offset=60, start_offset=60, starts_line=True, line_number=6), + make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=62, start_offset=62, starts_line=False, line_number=6), ] expected_opinfo_inner = [ make_inst(opname='COPY_FREE_VARS', arg=4, argval=4, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=None), - make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=2, start_offset=2, starts_line=True, line_number=3), - make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=4, start_offset=4, starts_line=True, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_DEREF', arg=2, argval='a', argrepr='a', offset=14, start_offset=14, starts_line=False, line_number=4), - make_inst(opname='LOAD_DEREF', arg=3, argval='b', argrepr='b', offset=16, start_offset=16, starts_line=False, line_number=4), - make_inst(opname='LOAD_DEREF', arg=4, argval='c', argrepr='c', offset=18, start_offset=18, starts_line=False, line_number=4), - make_inst(opname='LOAD_DEREF', arg=5, argval='d', argrepr='d', offset=20, start_offset=20, starts_line=False, line_number=4), - make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=1, argval=('e', 'f'), argrepr='e, f', offset=22, start_offset=22, starts_line=False, line_number=4), - make_inst(opname='CALL', arg=6, argval=6, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=32, start_offset=32, starts_line=False, line_number=4), - make_inst(opname='LOAD_CONST', arg=0, argval=None, argrepr='None', offset=34, start_offset=34, starts_line=False, line_number=4), - make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=36, start_offset=36, starts_line=False, line_number=4), + make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=2, start_offset=2, starts_line=True, line_number=3, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + NULL', offset=6, start_offset=6, starts_line=True, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_DEREF', arg=2, argval='a', argrepr='a', offset=16, start_offset=16, starts_line=False, line_number=4), + make_inst(opname='LOAD_DEREF', arg=3, argval='b', argrepr='b', offset=18, start_offset=18, starts_line=False, line_number=4), + make_inst(opname='LOAD_DEREF', arg=4, argval='c', argrepr='c', offset=20, start_offset=20, starts_line=False, line_number=4), + make_inst(opname='LOAD_DEREF', arg=5, argval='d', argrepr='d', offset=22, start_offset=22, starts_line=False, line_number=4), + make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=1, argval=('e', 'f'), argrepr='e, f', offset=24, start_offset=24, starts_line=False, line_number=4), + make_inst(opname='CALL', arg=6, argval=6, argrepr='', offset=26, start_offset=26, starts_line=False, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=34, start_offset=34, starts_line=False, line_number=4), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=36, start_offset=36, starts_line=False, line_number=4), + make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=38, start_offset=38, starts_line=False, line_number=4), ] expected_opinfo_jumpy = [ - make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1), - make_inst(opname='LOAD_GLOBAL', arg=1, argval='range', argrepr='range + NULL', offset=2, start_offset=2, starts_line=True, line_number=3, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_SMALL_INT', arg=10, argval=10, argrepr='', offset=12, start_offset=12, starts_line=False, line_number=3), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='GET_ITER', arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=False, line_number=3), - make_inst(opname='FOR_ITER', arg=32, argval=92, argrepr='to L4', offset=24, start_offset=24, starts_line=False, line_number=3, label=1, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=28, start_offset=28, starts_line=False, line_number=3), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=30, start_offset=30, starts_line=True, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=40, start_offset=40, starts_line=False, line_number=4), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=42, start_offset=42, starts_line=False, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=50, start_offset=50, starts_line=False, line_number=4), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=52, start_offset=52, starts_line=True, line_number=5), - make_inst(opname='LOAD_SMALL_INT', arg=4, argval=4, argrepr='', offset=54, start_offset=54, starts_line=False, line_number=5), - make_inst(opname='COMPARE_OP', arg=18, argval='<', argrepr='bool(<)', offset=56, start_offset=56, starts_line=False, line_number=5, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='POP_JUMP_IF_FALSE', arg=3, argval=70, argrepr='to L2', offset=60, start_offset=60, starts_line=False, line_number=5, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=64, start_offset=64, starts_line=False, line_number=5), - make_inst(opname='JUMP_BACKWARD', arg=23, argval=24, argrepr='to L1', offset=66, start_offset=66, starts_line=True, line_number=6, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=70, start_offset=70, starts_line=True, line_number=7, label=2), - make_inst(opname='LOAD_SMALL_INT', arg=6, argval=6, argrepr='', offset=72, start_offset=72, starts_line=False, line_number=7), - make_inst(opname='COMPARE_OP', arg=148, argval='>', argrepr='bool(>)', offset=74, start_offset=74, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='POP_JUMP_IF_TRUE', arg=3, argval=88, argrepr='to L3', offset=78, start_offset=78, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=82, start_offset=82, starts_line=False, line_number=7), - make_inst(opname='JUMP_BACKWARD', arg=32, argval=24, argrepr='to L1', offset=84, start_offset=84, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=88, start_offset=88, starts_line=True, line_number=8, label=3), - make_inst(opname='JUMP_FORWARD', arg=13, argval=118, argrepr='to L5', offset=90, start_offset=90, starts_line=False, line_number=8), - make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=92, start_offset=92, starts_line=True, line_number=3, label=4), - make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=94, start_offset=94, starts_line=False, line_number=3), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=96, start_offset=96, starts_line=True, line_number=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=1, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=106, start_offset=106, starts_line=False, line_number=10), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=108, start_offset=108, starts_line=False, line_number=10, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=116, start_offset=116, starts_line=False, line_number=10), - make_inst(opname='LOAD_FAST_CHECK', arg=0, argval='i', argrepr='i', offset=118, start_offset=118, starts_line=True, line_number=11, label=5), - make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=120, start_offset=120, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_JUMP_IF_FALSE', arg=40, argval=212, argrepr='to L8', offset=128, start_offset=128, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=132, start_offset=132, starts_line=False, line_number=11), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=134, start_offset=134, starts_line=True, line_number=12, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=144, start_offset=144, starts_line=False, line_number=12), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=146, start_offset=146, starts_line=False, line_number=12, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=154, start_offset=154, starts_line=False, line_number=12), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=156, start_offset=156, starts_line=True, line_number=13), - make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=158, start_offset=158, starts_line=False, line_number=13), - make_inst(opname='BINARY_OP', arg=23, argval=23, argrepr='-=', offset=160, start_offset=160, starts_line=False, line_number=13, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]), - make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=172, start_offset=172, starts_line=False, line_number=13), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=174, start_offset=174, starts_line=True, line_number=14), - make_inst(opname='LOAD_SMALL_INT', arg=6, argval=6, argrepr='', offset=176, start_offset=176, starts_line=False, line_number=14), - make_inst(opname='COMPARE_OP', arg=148, argval='>', argrepr='bool(>)', offset=178, start_offset=178, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='POP_JUMP_IF_FALSE', arg=3, argval=192, argrepr='to L6', offset=182, start_offset=182, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=186, start_offset=186, starts_line=False, line_number=14), - make_inst(opname='JUMP_BACKWARD', arg=37, argval=118, argrepr='to L5', offset=188, start_offset=188, starts_line=True, line_number=15, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=192, start_offset=192, starts_line=True, line_number=16, label=6), - make_inst(opname='LOAD_SMALL_INT', arg=4, argval=4, argrepr='', offset=194, start_offset=194, starts_line=False, line_number=16), - make_inst(opname='COMPARE_OP', arg=18, argval='<', argrepr='bool(<)', offset=196, start_offset=196, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='POP_JUMP_IF_TRUE', arg=3, argval=210, argrepr='to L7', offset=200, start_offset=200, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=204, start_offset=204, starts_line=False, line_number=16), - make_inst(opname='JUMP_BACKWARD', arg=46, argval=118, argrepr='to L5', offset=206, start_offset=206, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='JUMP_FORWARD', arg=11, argval=234, argrepr='to L9', offset=210, start_offset=210, starts_line=True, line_number=17, label=7), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=212, start_offset=212, starts_line=True, line_number=19, label=8, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=2, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=222, start_offset=222, starts_line=False, line_number=19), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=224, start_offset=224, starts_line=False, line_number=19, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=232, start_offset=232, starts_line=False, line_number=19), - make_inst(opname='NOP', arg=None, argval=None, argrepr='', offset=234, start_offset=234, starts_line=True, line_number=20, label=9), - make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=236, start_offset=236, starts_line=True, line_number=21), - make_inst(opname='LOAD_SMALL_INT', arg=0, argval=0, argrepr='', offset=238, start_offset=238, starts_line=False, line_number=21), - make_inst(opname='BINARY_OP', arg=11, argval=11, argrepr='/', offset=240, start_offset=240, starts_line=False, line_number=21, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=252, start_offset=252, starts_line=False, line_number=21), - make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=254, start_offset=254, starts_line=True, line_number=25), - make_inst(opname='COPY', arg=1, argval=1, argrepr='', offset=256, start_offset=256, starts_line=False, line_number=25), - make_inst(opname='LOAD_SPECIAL', arg=1, argval=1, argrepr='__exit__', offset=258, start_offset=258, starts_line=False, line_number=25), - make_inst(opname='SWAP', arg=2, argval=2, argrepr='', offset=260, start_offset=260, starts_line=False, line_number=25), - make_inst(opname='SWAP', arg=3, argval=3, argrepr='', offset=262, start_offset=262, starts_line=False, line_number=25), - make_inst(opname='LOAD_SPECIAL', arg=0, argval=0, argrepr='__enter__', offset=264, start_offset=264, starts_line=False, line_number=25), - make_inst(opname='CALL', arg=0, argval=0, argrepr='', offset=266, start_offset=266, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='STORE_FAST', arg=1, argval='dodgy', argrepr='dodgy', offset=274, start_offset=274, starts_line=False, line_number=25), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=276, start_offset=276, starts_line=True, line_number=26, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=3, argval='Never reach this', argrepr="'Never reach this'", offset=286, start_offset=286, starts_line=False, line_number=26), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=288, start_offset=288, starts_line=False, line_number=26, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=296, start_offset=296, starts_line=False, line_number=26), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=298, start_offset=298, starts_line=True, line_number=25), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=300, start_offset=300, starts_line=False, line_number=25), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=302, start_offset=302, starts_line=False, line_number=25), - make_inst(opname='CALL', arg=3, argval=3, argrepr='', offset=304, start_offset=304, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=312, start_offset=312, starts_line=False, line_number=25), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=314, start_offset=314, starts_line=True, line_number=28, label=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=6, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=324, start_offset=324, starts_line=False, line_number=28), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=326, start_offset=326, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=334, start_offset=334, starts_line=False, line_number=28), - make_inst(opname='LOAD_CONST', arg=4, argval=None, argrepr='None', offset=336, start_offset=336, starts_line=False, line_number=28), - make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=338, start_offset=338, starts_line=False, line_number=28), - make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=True, line_number=25), - make_inst(opname='WITH_EXCEPT_START', arg=None, argval=None, argrepr='', offset=342, start_offset=342, starts_line=False, line_number=25), - make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_JUMP_IF_TRUE', arg=2, argval=360, argrepr='to L11', offset=352, start_offset=352, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=356, start_offset=356, starts_line=False, line_number=25), - make_inst(opname='RERAISE', arg=2, argval=2, argrepr='', offset=358, start_offset=358, starts_line=False, line_number=25), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=360, start_offset=360, starts_line=False, line_number=25, label=11), - make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=25), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=364, start_offset=364, starts_line=False, line_number=25), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=366, start_offset=366, starts_line=False, line_number=25), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=368, start_offset=368, starts_line=False, line_number=25), - make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=29, argval=314, argrepr='to L10', offset=370, start_offset=370, starts_line=False, line_number=25), - make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=372, start_offset=372, starts_line=True, line_number=None), - make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=374, start_offset=374, starts_line=False, line_number=None), - make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=376, start_offset=376, starts_line=False, line_number=None), - make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=378, start_offset=378, starts_line=False, line_number=None), - make_inst(opname='LOAD_GLOBAL', arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=380, start_offset=380, starts_line=True, line_number=22, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='CHECK_EXC_MATCH', arg=None, argval=None, argrepr='', offset=390, start_offset=390, starts_line=False, line_number=22), - make_inst(opname='POP_JUMP_IF_FALSE', arg=15, argval=426, argrepr='to L12', offset=392, start_offset=392, starts_line=False, line_number=22, cache_info=[('counter', 1, b'\x00\x00')]), - make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=22), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=398, start_offset=398, starts_line=False, line_number=22), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=400, start_offset=400, starts_line=True, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=5, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=410, start_offset=410, starts_line=False, line_number=23), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=412, start_offset=412, starts_line=False, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=420, start_offset=420, starts_line=False, line_number=23), - make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=422, start_offset=422, starts_line=False, line_number=23), - make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=56, argval=314, argrepr='to L10', offset=424, start_offset=424, starts_line=False, line_number=23), - make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=426, start_offset=426, starts_line=True, line_number=22, label=12), - make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=428, start_offset=428, starts_line=True, line_number=None), - make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=430, start_offset=430, starts_line=False, line_number=None), - make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=432, start_offset=432, starts_line=False, line_number=None), - make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=434, start_offset=434, starts_line=False, line_number=None), - make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=436, start_offset=436, starts_line=True, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), - make_inst(opname='LOAD_CONST', arg=6, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=446, start_offset=446, starts_line=False, line_number=28), - make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=448, start_offset=448, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), - make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=456, start_offset=456, starts_line=False, line_number=28), - make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=458, start_offset=458, starts_line=False, line_number=28), - make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=460, start_offset=460, starts_line=True, line_number=None), - make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=462, start_offset=462, starts_line=False, line_number=None), - make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=464, start_offset=464, starts_line=False, line_number=None), + make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=1, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='LOAD_GLOBAL', arg=1, argval='range', argrepr='range + NULL', offset=4, start_offset=4, starts_line=True, line_number=3, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_SMALL_INT', arg=10, argval=10, argrepr='', offset=14, start_offset=14, starts_line=False, line_number=3), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=16, start_offset=16, starts_line=False, line_number=3, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='GET_ITER', arg=0, argval=0, argrepr='', offset=24, start_offset=24, starts_line=False, line_number=3, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='FOR_ITER', arg=33, argval=98, argrepr='to L4', offset=28, start_offset=28, starts_line=False, line_number=3, label=1, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=32, start_offset=32, starts_line=False, line_number=3), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=34, start_offset=34, starts_line=True, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=44, start_offset=44, starts_line=False, line_number=4), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=46, start_offset=46, starts_line=False, line_number=4, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=54, start_offset=54, starts_line=False, line_number=4), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=56, start_offset=56, starts_line=True, line_number=5), + make_inst(opname='LOAD_SMALL_INT', arg=4, argval=4, argrepr='', offset=58, start_offset=58, starts_line=False, line_number=5), + make_inst(opname='COMPARE_OP', arg=18, argval='<', argrepr='bool(<)', offset=60, start_offset=60, starts_line=False, line_number=5, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='POP_JUMP_IF_FALSE', arg=3, argval=74, argrepr='to L2', offset=64, start_offset=64, starts_line=False, line_number=5, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=68, start_offset=68, starts_line=False, line_number=5), + make_inst(opname='JUMP_BACKWARD', arg=23, argval=28, argrepr='to L1', offset=70, start_offset=70, starts_line=True, line_number=6, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=74, start_offset=74, starts_line=True, line_number=7, label=2), + make_inst(opname='LOAD_SMALL_INT', arg=6, argval=6, argrepr='', offset=76, start_offset=76, starts_line=False, line_number=7), + make_inst(opname='COMPARE_OP', arg=148, argval='>', argrepr='bool(>)', offset=78, start_offset=78, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='POP_JUMP_IF_TRUE', arg=3, argval=92, argrepr='to L3', offset=82, start_offset=82, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=86, start_offset=86, starts_line=False, line_number=7), + make_inst(opname='JUMP_BACKWARD', arg=32, argval=28, argrepr='to L1', offset=88, start_offset=88, starts_line=False, line_number=7, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=92, start_offset=92, starts_line=True, line_number=8, label=3), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=94, start_offset=94, starts_line=False, line_number=8), + make_inst(opname='JUMP_FORWARD', arg=13, argval=124, argrepr='to L5', offset=96, start_offset=96, starts_line=False, line_number=8), + make_inst(opname='END_FOR', arg=None, argval=None, argrepr='', offset=98, start_offset=98, starts_line=True, line_number=3, label=4), + make_inst(opname='POP_ITER', arg=None, argval=None, argrepr='', offset=100, start_offset=100, starts_line=False, line_number=3), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=102, start_offset=102, starts_line=True, line_number=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=1, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=112, start_offset=112, starts_line=False, line_number=10), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=114, start_offset=114, starts_line=False, line_number=10, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=122, start_offset=122, starts_line=False, line_number=10), + make_inst(opname='LOAD_FAST_CHECK', arg=0, argval='i', argrepr='i', offset=124, start_offset=124, starts_line=True, line_number=11, label=5), + make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=126, start_offset=126, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_JUMP_IF_FALSE', arg=40, argval=218, argrepr='to L8', offset=134, start_offset=134, starts_line=False, line_number=11, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=138, start_offset=138, starts_line=False, line_number=11), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=140, start_offset=140, starts_line=True, line_number=12, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=150, start_offset=150, starts_line=False, line_number=12), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=152, start_offset=152, starts_line=False, line_number=12, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=160, start_offset=160, starts_line=False, line_number=12), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=162, start_offset=162, starts_line=True, line_number=13), + make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=164, start_offset=164, starts_line=False, line_number=13), + make_inst(opname='BINARY_OP', arg=23, argval=23, argrepr='-=', offset=166, start_offset=166, starts_line=False, line_number=13, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]), + make_inst(opname='STORE_FAST', arg=0, argval='i', argrepr='i', offset=178, start_offset=178, starts_line=False, line_number=13), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=180, start_offset=180, starts_line=True, line_number=14), + make_inst(opname='LOAD_SMALL_INT', arg=6, argval=6, argrepr='', offset=182, start_offset=182, starts_line=False, line_number=14), + make_inst(opname='COMPARE_OP', arg=148, argval='>', argrepr='bool(>)', offset=184, start_offset=184, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='POP_JUMP_IF_FALSE', arg=3, argval=198, argrepr='to L6', offset=188, start_offset=188, starts_line=False, line_number=14, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=192, start_offset=192, starts_line=False, line_number=14), + make_inst(opname='JUMP_BACKWARD', arg=37, argval=124, argrepr='to L5', offset=194, start_offset=194, starts_line=True, line_number=15, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=198, start_offset=198, starts_line=True, line_number=16, label=6), + make_inst(opname='LOAD_SMALL_INT', arg=4, argval=4, argrepr='', offset=200, start_offset=200, starts_line=False, line_number=16), + make_inst(opname='COMPARE_OP', arg=18, argval='<', argrepr='bool(<)', offset=202, start_offset=202, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='POP_JUMP_IF_TRUE', arg=3, argval=216, argrepr='to L7', offset=206, start_offset=206, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=210, start_offset=210, starts_line=False, line_number=16), + make_inst(opname='JUMP_BACKWARD', arg=46, argval=124, argrepr='to L5', offset=212, start_offset=212, starts_line=False, line_number=16, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='JUMP_FORWARD', arg=11, argval=240, argrepr='to L9', offset=216, start_offset=216, starts_line=True, line_number=17, label=7), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=218, start_offset=218, starts_line=True, line_number=19, label=8, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=2, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=228, start_offset=228, starts_line=False, line_number=19), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=230, start_offset=230, starts_line=False, line_number=19, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=238, start_offset=238, starts_line=False, line_number=19), + make_inst(opname='NOP', arg=None, argval=None, argrepr='', offset=240, start_offset=240, starts_line=True, line_number=20, label=9), + make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=242, start_offset=242, starts_line=True, line_number=21), + make_inst(opname='LOAD_SMALL_INT', arg=0, argval=0, argrepr='', offset=244, start_offset=244, starts_line=False, line_number=21), + make_inst(opname='BINARY_OP', arg=11, argval=11, argrepr='/', offset=246, start_offset=246, starts_line=False, line_number=21, cache_info=[('counter', 1, b'\x00\x00'), ('descr', 4, b'\x00\x00\x00\x00\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=258, start_offset=258, starts_line=False, line_number=21), + make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='i', argrepr='i', offset=260, start_offset=260, starts_line=True, line_number=25), + make_inst(opname='COPY', arg=1, argval=1, argrepr='', offset=262, start_offset=262, starts_line=False, line_number=25), + make_inst(opname='LOAD_SPECIAL', arg=1, argval=1, argrepr='__exit__', offset=264, start_offset=264, starts_line=False, line_number=25), + make_inst(opname='SWAP', arg=2, argval=2, argrepr='', offset=266, start_offset=266, starts_line=False, line_number=25), + make_inst(opname='SWAP', arg=3, argval=3, argrepr='', offset=268, start_offset=268, starts_line=False, line_number=25), + make_inst(opname='LOAD_SPECIAL', arg=0, argval=0, argrepr='__enter__', offset=270, start_offset=270, starts_line=False, line_number=25), + make_inst(opname='CALL', arg=0, argval=0, argrepr='', offset=272, start_offset=272, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='STORE_FAST', arg=1, argval='dodgy', argrepr='dodgy', offset=280, start_offset=280, starts_line=False, line_number=25), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=282, start_offset=282, starts_line=True, line_number=26, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=3, argval='Never reach this', argrepr="'Never reach this'", offset=292, start_offset=292, starts_line=False, line_number=26), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=294, start_offset=294, starts_line=False, line_number=26, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=302, start_offset=302, starts_line=False, line_number=26), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=304, start_offset=304, starts_line=True, line_number=25), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=306, start_offset=306, starts_line=False, line_number=25), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=308, start_offset=308, starts_line=False, line_number=25), + make_inst(opname='CALL', arg=3, argval=3, argrepr='', offset=310, start_offset=310, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=318, start_offset=318, starts_line=False, line_number=25), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=320, start_offset=320, starts_line=True, line_number=28, label=10, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=330, start_offset=330, starts_line=False, line_number=28), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=332, start_offset=332, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=340, start_offset=340, starts_line=False, line_number=28), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=342, start_offset=342, starts_line=False, line_number=28), + make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=344, start_offset=344, starts_line=False, line_number=28), + make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=346, start_offset=346, starts_line=True, line_number=25), + make_inst(opname='WITH_EXCEPT_START', arg=None, argval=None, argrepr='', offset=348, start_offset=348, starts_line=False, line_number=25), + make_inst(opname='TO_BOOL', arg=None, argval=None, argrepr='', offset=350, start_offset=350, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00'), ('version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_JUMP_IF_TRUE', arg=2, argval=366, argrepr='to L11', offset=358, start_offset=358, starts_line=False, line_number=25, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=362, start_offset=362, starts_line=False, line_number=25), + make_inst(opname='RERAISE', arg=2, argval=2, argrepr='', offset=364, start_offset=364, starts_line=False, line_number=25), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=366, start_offset=366, starts_line=False, line_number=25, label=11), + make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=368, start_offset=368, starts_line=False, line_number=25), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=370, start_offset=370, starts_line=False, line_number=25), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=372, start_offset=372, starts_line=False, line_number=25), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=374, start_offset=374, starts_line=False, line_number=25), + make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=29, argval=320, argrepr='to L10', offset=376, start_offset=376, starts_line=False, line_number=25), + make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=378, start_offset=378, starts_line=True, line_number=None), + make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=380, start_offset=380, starts_line=False, line_number=None), + make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=382, start_offset=382, starts_line=False, line_number=None), + make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=384, start_offset=384, starts_line=False, line_number=None), + make_inst(opname='LOAD_GLOBAL', arg=4, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=386, start_offset=386, starts_line=True, line_number=22, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='CHECK_EXC_MATCH', arg=None, argval=None, argrepr='', offset=396, start_offset=396, starts_line=False, line_number=22), + make_inst(opname='POP_JUMP_IF_FALSE', arg=15, argval=432, argrepr='to L12', offset=398, start_offset=398, starts_line=False, line_number=22, cache_info=[('counter', 1, b'\x00\x00')]), + make_inst(opname='NOT_TAKEN', arg=None, argval=None, argrepr='', offset=402, start_offset=402, starts_line=False, line_number=22), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=404, start_offset=404, starts_line=False, line_number=22), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=406, start_offset=406, starts_line=True, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=4, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=416, start_offset=416, starts_line=False, line_number=23), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=418, start_offset=418, starts_line=False, line_number=23, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=426, start_offset=426, starts_line=False, line_number=23), + make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=428, start_offset=428, starts_line=False, line_number=23), + make_inst(opname='JUMP_BACKWARD_NO_INTERRUPT', arg=56, argval=320, argrepr='to L10', offset=430, start_offset=430, starts_line=False, line_number=23), + make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=432, start_offset=432, starts_line=True, line_number=22, label=12), + make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=434, start_offset=434, starts_line=True, line_number=None), + make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=436, start_offset=436, starts_line=False, line_number=None), + make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=438, start_offset=438, starts_line=False, line_number=None), + make_inst(opname='PUSH_EXC_INFO', arg=None, argval=None, argrepr='', offset=440, start_offset=440, starts_line=False, line_number=None), + make_inst(opname='LOAD_GLOBAL', arg=3, argval='print', argrepr='print + NULL', offset=442, start_offset=442, starts_line=True, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), ('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, b'\x00\x00')]), + make_inst(opname='LOAD_CONST', arg=5, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=452, start_offset=452, starts_line=False, line_number=28), + make_inst(opname='CALL', arg=1, argval=1, argrepr='', offset=454, start_offset=454, starts_line=False, line_number=28, cache_info=[('counter', 1, b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]), + make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=462, start_offset=462, starts_line=False, line_number=28), + make_inst(opname='RERAISE', arg=0, argval=0, argrepr='', offset=464, start_offset=464, starts_line=False, line_number=28), + make_inst(opname='COPY', arg=3, argval=3, argrepr='', offset=466, start_offset=466, starts_line=True, line_number=None), + make_inst(opname='POP_EXCEPT', arg=None, argval=None, argrepr='', offset=468, start_offset=468, starts_line=False, line_number=None), + make_inst(opname='RERAISE', arg=1, argval=1, argrepr='', offset=470, start_offset=470, starts_line=False, line_number=None), ] # One last piece of inspect fodder to check the default line number handling def simple(): pass expected_opinfo_simple = [ make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=0, start_offset=0, starts_line=True, line_number=simple.__code__.co_firstlineno), - make_inst(opname='LOAD_CONST', arg=0, argval=None, argrepr='None', offset=2, start_offset=2, starts_line=False, line_number=simple.__code__.co_firstlineno), - make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=4, start_offset=4, starts_line=False, line_number=simple.__code__.co_firstlineno), + make_inst(opname='LOAD_COMMON_CONSTANT', arg=7, argval=None, argrepr='None', offset=4, start_offset=4, starts_line=False, line_number=simple.__code__.co_firstlineno), + make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', offset=6, start_offset=6, starts_line=False, line_number=simple.__code__.co_firstlineno), ] @@ -2343,8 +2380,10 @@ def test_from_traceback_dis(self): @requires_debug_ranges() def test_bytecode_co_positions(self): bytecode = dis.Bytecode("a=1") - for instr, positions in zip(bytecode, bytecode.codeobj.co_positions()): - assert instr.positions == positions + it = bytecode.codeobj.co_positions() + next(it) # Ignore the CACHE for the RESUME + for instr, positions in zip(bytecode, it): + self.assertEqual(instr.positions, positions) class TestBytecodeTestCase(BytecodeTestCase): def test_assert_not_in_with_op_not_in_bytecode(self): @@ -2412,7 +2451,7 @@ def func(): code = func.__code__ offsets = [linestart[0] for linestart in dis.findlinestarts(code)] - self.assertEqual(offsets, [0, 2]) + self.assertEqual(offsets, [0, 4]) class TestDisTraceback(DisTestBase): @@ -2550,6 +2589,7 @@ def test_show_cache(self): source = 'print()' expect = ''' 0 RESUME 0 + CACHE 0 (counter: 0) 1 LOAD_NAME 0 (print) PUSH_NULL @@ -2558,7 +2598,7 @@ def test_show_cache(self): CACHE 0 (func_version: 0) CACHE 0 POP_TOP - LOAD_CONST 0 (None) + LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE ''' for flag in ['-C', '--show-caches']: @@ -2570,8 +2610,8 @@ def test_show_offsets(self): expect = ''' 0 0 RESUME 0 - 1 2 LOAD_CONST 0 (None) - 4 RETURN_VALUE + 1 4 LOAD_COMMON_CONSTANT 7 (None) + 6 RETURN_VALUE ''' for flag in ['-O', '--show-offsets']: self.check_output(source, expect, flag) @@ -2582,7 +2622,7 @@ def test_show_positions(self): expect = ''' 0:0-1:0 RESUME 0 - 1:0-1:4 LOAD_CONST 0 (None) + 1:0-1:4 LOAD_COMMON_CONSTANT 7 (None) 1:0-1:4 RETURN_VALUE ''' for flag in ['-P', '--show-positions']: @@ -2594,7 +2634,7 @@ def test_specialized_code(self): expect = ''' 0 RESUME 0 - 1 LOAD_CONST 0 (None) + 1 LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUE ''' for flag in ['-S', '--specialized']: diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 037c1bed5fe..6eb25960101 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -300,7 +300,7 @@ class C: def __iter__(self): return Iterator() - self.assertEqual([1, 2], list(i for i in C())) + self.assertEqual([1,2], list(i for i in C())) def test_close_clears_frame(self): # gh-142766: Test that closing a generator clears its frame @@ -357,26 +357,25 @@ def gen(it): yield x return gen(range(10)) - def process_tests(self, get_generator, is_expr): - err_iterator = "'.*' object is not an iterator" - err_iterable = "'.*' object is not iterable" - for obj in self.iterables: - g_obj = get_generator(obj) - with self.subTest(g_obj=g_obj, obj=obj): - if is_expr: - self.assertRaisesRegex(TypeError, err_iterator, list, g_obj) - else: + def process_tests(self, get_generator, changes_iterable): + if changes_iterable: + for obj in self.iterables: + g_obj = get_generator(obj) + with self.subTest(g_obj=g_obj, obj=obj): self.assertListEqual(list(g_obj), list(obj)) - g_iter = get_generator(iter(obj)) - with self.subTest(g_iter=g_iter, obj=obj): - self.assertListEqual(list(g_iter), list(obj)) + g_iter = get_generator(iter(obj)) + with self.subTest(g_iter=g_iter, obj=obj): + self.assertListEqual(list(g_iter), list(obj)) + err_regex = "'.*' object is not iterable" for obj in self.non_iterables: g_obj = get_generator(obj) with self.subTest(g_obj=g_obj): - err = err_iterator if is_expr else err_iterable - self.assertRaisesRegex(TypeError, err, list, g_obj) + if changes_iterable: + self.assertRaisesRegex(TypeError, err_regex, list, g_obj) + else: + next(g_obj) def test_modify_f_locals(self): def modify_f_locals(g, local, obj): @@ -389,22 +388,22 @@ def get_generator_genexpr(obj): def get_generator_genfunc(obj): return modify_f_locals(self.genfunc(), 'it', obj) - self.process_tests(get_generator_genexpr, True) - self.process_tests(get_generator_genfunc, False) + self.process_tests(get_generator_genexpr, False) + self.process_tests(get_generator_genfunc, True) def test_new_gen_from_gi_code(self): def new_gen_from_gi_code(g, obj): generator_func = types.FunctionType(g.gi_code, {}) return generator_func(obj) - def get_generator_genexpr(obj): - return new_gen_from_gi_code(self.genexpr(), obj) + for obj in self.non_iterables: + with self.assertRaises(TypeError): + new_gen_from_gi_code(self.genexpr(), obj) def get_generator_genfunc(obj): return new_gen_from_gi_code(self.genfunc(), obj) - self.process_tests(get_generator_genexpr, True) - self.process_tests(get_generator_genfunc, False) + self.process_tests(get_generator_genfunc, True) class ExceptionTest(unittest.TestCase): @@ -1367,7 +1366,7 @@ def b(): >>> type(i) >>> [s for s in dir(i) if not s.startswith('_')] -['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw'] +['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_state', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw'] >>> from test.support import HAVE_DOCSTRINGS >>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') Implement next(self). @@ -2878,8 +2877,7 @@ def printsolution(self, x): } def load_tests(loader, tests, pattern): - from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON - tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # TODO: RUSTPYTHON + tests.addTest(doctest.DocTestSuite()) return tests diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index bb375606a0d..db4059b860c 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -12,16 +12,7 @@ import unittest import test.support -from test.support import requires_specialization_ft, script_helper - -try: - import _testcapi -except ImportError: - _testcapi = None -try: - import _testinternalcapi -except ImportError: - _testinternalcapi = None +from test.support import import_helper, requires_specialization, script_helper PAIR = (0,1) @@ -202,13 +193,10 @@ def test_c_return_count(self): (E.BRANCH, "branch"), ] -EXCEPT_EVENTS = [ +SIMPLE_EVENTS = INSTRUMENTED_EVENTS + [ (E.RAISE, "raise"), - (E.PY_UNWIND, "unwind"), (E.EXCEPTION_HANDLED, "exception_handled"), -] - -SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [ + (E.PY_UNWIND, "unwind"), (E.C_RAISE, "c_raise"), (E.C_RETURN, "c_return"), ] @@ -744,18 +732,6 @@ def test_disable_legal_events(self): sys.monitoring.register_callback(TEST_TOOL, event, None) - def test_disable_illegal_events(self): - for event, name in EXCEPT_EVENTS: - try: - counter = CounterWithDisable() - counter.disable = True - sys.monitoring.register_callback(TEST_TOOL, event, counter) - sys.monitoring.set_events(TEST_TOOL, event) - with self.assertRaises(ValueError): - self.raise_handle_reraise() - finally: - sys.monitoring.set_events(TEST_TOOL, 0) - sys.monitoring.register_callback(TEST_TOOL, event, None) class ExceptionRecorder: @@ -879,7 +855,6 @@ def func1(): self.check_events(func1, [("raise", KeyError)]) - @unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi") def test_implicit_stop_iteration(self): """Generators are documented as raising a StopIteration when they terminate. @@ -889,6 +864,8 @@ def test_implicit_stop_iteration(self): This test checks that both paths record an equivalent event. """ + _testinternalcapi = import_helper.import_module("_testinternalcapi") + def gen(): yield 1 return 2 @@ -1054,9 +1031,10 @@ def func(): ) self.assertEqual(events[0], ("throw", IndexError)) - @unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi") - @requires_specialization_ft + @requires_specialization def test_no_unwind_for_shim_frame(self): + _testinternalcapi = import_helper.import_module("_testinternalcapi") + class ValueErrorRaiser: def __init__(self): raise ValueError() @@ -1222,19 +1200,20 @@ def func1(): line3 = 3 self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'get_events', 10), - ('line', 'func1', 1), - ('instruction', 'func1', 2), - ('instruction', 'func1', 4), - ('line', 'func1', 2), - ('instruction', 'func1', 6), - ('instruction', 'func1', 8), - ('line', 'func1', 3), - ('instruction', 'func1', 10), - ('instruction', 'func1', 12), - ('instruction', 'func1', 14), - ('instruction', 'func1', 16), - ('line', 'get_events', 11)]) + ("line", "get_events", 10), + ("line", "func1", 1), + ("instruction", "func1", 4), + ("instruction", "func1", 6), + ("line", "func1", 2), + ("instruction", "func1", 8), + ("instruction", "func1", 10), + ("line", "func1", 3), + ("instruction", "func1", 12), + ("instruction", "func1", 14), + ("instruction", "func1", 16), + ("instruction", "func1", 18), + ("line", "get_events", 11), + ]) def test_c_call(self): @@ -1244,22 +1223,23 @@ def func2(): line3 = 3 self.check_events(func2, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'get_events', 10), - ('line', 'func2', 1), - ('instruction', 'func2', 2), - ('instruction', 'func2', 4), - ('line', 'func2', 2), - ('instruction', 'func2', 6), - ('instruction', 'func2', 8), - ('instruction', 'func2', 28), - ('instruction', 'func2', 30), - ('instruction', 'func2', 38), - ('line', 'func2', 3), - ('instruction', 'func2', 40), - ('instruction', 'func2', 42), - ('instruction', 'func2', 44), - ('instruction', 'func2', 46), - ('line', 'get_events', 11)]) + ("line", "get_events", 10), + ("line", "func2", 1), + ("instruction", "func2", 4), + ("instruction", "func2", 6), + ("line", "func2", 2), + ("instruction", "func2", 8), + ("instruction", "func2", 10), + ("instruction", "func2", 30), + ("instruction", "func2", 32), + ("instruction", "func2", 40), + ("line", "func2", 3), + ("instruction", "func2", 42), + ("instruction", "func2", 44), + ("instruction", "func2", 46), + ("instruction", "func2", 48), + ("line", "get_events", 11), + ]) def test_try_except(self): @@ -1272,28 +1252,29 @@ def func3(): line = 6 self.check_events(func3, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ - ('line', 'get_events', 10), - ('line', 'func3', 1), - ('instruction', 'func3', 2), - ('line', 'func3', 2), - ('instruction', 'func3', 4), - ('instruction', 'func3', 6), - ('line', 'func3', 3), - ('instruction', 'func3', 8), - ('instruction', 'func3', 18), - ('instruction', 'func3', 20), - ('line', 'func3', 4), - ('instruction', 'func3', 22), - ('line', 'func3', 5), - ('instruction', 'func3', 24), - ('instruction', 'func3', 26), - ('instruction', 'func3', 28), - ('line', 'func3', 6), - ('instruction', 'func3', 30), - ('instruction', 'func3', 32), - ('instruction', 'func3', 34), - ('instruction', 'func3', 36), - ('line', 'get_events', 11)]) + ("line", "get_events", 10), + ("line", "func3", 1), + ("instruction", "func3", 4), + ("line", "func3", 2), + ("instruction", "func3", 6), + ("instruction", "func3", 8), + ("line", "func3", 3), + ("instruction", "func3", 10), + ("instruction", "func3", 20), + ("instruction", "func3", 22), + ("line", "func3", 4), + ("instruction", "func3", 24), + ("line", "func3", 5), + ("instruction", "func3", 26), + ("instruction", "func3", 28), + ("instruction", "func3", 30), + ("line", "func3", 6), + ("instruction", "func3", 32), + ("instruction", "func3", 34), + ("instruction", "func3", 36), + ("instruction", "func3", 38), + ("line", "get_events", 11), + ]) def test_with_restart(self): def func1(): @@ -1304,16 +1285,16 @@ def func1(): self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ ('line', 'get_events', 10), ('line', 'func1', 1), - ('instruction', 'func1', 2), ('instruction', 'func1', 4), - ('line', 'func1', 2), ('instruction', 'func1', 6), + ('line', 'func1', 2), ('instruction', 'func1', 8), - ('line', 'func1', 3), ('instruction', 'func1', 10), + ('line', 'func1', 3), ('instruction', 'func1', 12), ('instruction', 'func1', 14), ('instruction', 'func1', 16), + ('instruction', 'func1', 18), ('line', 'get_events', 11)]) sys.monitoring.restart_events() @@ -1321,16 +1302,16 @@ def func1(): self.check_events(func1, recorders = LINE_AND_INSTRUCTION_RECORDERS, expected = [ ('line', 'get_events', 10), ('line', 'func1', 1), - ('instruction', 'func1', 2), ('instruction', 'func1', 4), - ('line', 'func1', 2), ('instruction', 'func1', 6), + ('line', 'func1', 2), ('instruction', 'func1', 8), - ('line', 'func1', 3), ('instruction', 'func1', 10), + ('line', 'func1', 3), ('instruction', 'func1', 12), ('instruction', 'func1', 14), ('instruction', 'func1', 16), + ('instruction', 'func1', 18), ('line', 'get_events', 11)]) def test_turn_off_only_instruction(self): @@ -1378,10 +1359,10 @@ def func1(): line1 = 1 MUST_INCLUDE_LI = [ - ('instruction', 'func1', 2), - ('line', 'func1', 2), ('instruction', 'func1', 4), - ('instruction', 'func1', 6)] + ('line', 'func1', 2), + ('instruction', 'func1', 6), + ('instruction', 'func1', 8)] def test_line_then_instruction(self): recorders = [ LineRecorder, InstructionRecorder ] @@ -1398,11 +1379,11 @@ def func2(): len(()) MUST_INCLUDE_CI = [ - ('instruction', 'func2', 2), + ('instruction', 'func2', 4), ('call', 'func2', sys.monitoring.MISSING), ('call', 'len', ()), - ('instruction', 'func2', 12), - ('instruction', 'func2', 14)] + ('instruction', 'func2', 14), + ('instruction', 'func2', 16)] @@ -1486,8 +1467,334 @@ def func3(): ('line', 'func3', 6)]) def test_set_non_local_event(self): + # C_RETURN/C_RAISE are ancillary (derived) events — not settable as local with self.assertRaises(ValueError): - sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.RAISE) + sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.C_RETURN) + + def test_local_reraise(self): + """RERAISE fires as a local event only for the instrumented code object.""" + + def foo(): + try: + raise RuntimeError("test") + except RuntimeError: + raise + + def bar(): + try: + raise RuntimeError("test") + except RuntimeError: + raise + + events = set() + + def callback(code, offset, exc): + events.add(code.co_name) + + try: + sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE) + try: + foo() + except RuntimeError: + pass + try: + bar() # should NOT trigger the callback + except RuntimeError: + pass + self.assertEqual(events, {'foo'}) + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None) + + def test_local_reraise_disable(self): + """Returning DISABLE from a RERAISE callback disables it for that code object.""" + + call_count = 0 + + def foo(): + try: + raise RuntimeError("test") + except RuntimeError: + raise + + def callback(code, offset, exc): + nonlocal call_count + call_count += 1 + return sys.monitoring.DISABLE + + try: + sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE) + try: + foo() + except RuntimeError: + pass + self.assertEqual(call_count, 1) + try: + foo() + except RuntimeError: + pass + self.assertEqual(call_count, 1) # not fired again — disabled + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None) + + def test_local_py_throw(self): + """PY_THROW fires as a local event only for the instrumented code object.""" + + def gen_foo(): + yield 1 + yield 2 + + def gen_bar(): + yield 1 + yield 2 + + events = [] + + def callback(code, offset, exc): + events.append(code.co_name) + + try: + sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback) + sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW) + + g = gen_foo() + next(g) + try: + g.throw(RuntimeError("test")) + except RuntimeError: + pass + + h = gen_bar() + next(h) + try: + h.throw(RuntimeError("test")) # should NOT trigger the callback + except RuntimeError: + pass + + self.assertEqual(events, ['gen_foo']) + finally: + sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None) + + def test_local_py_throw_disable(self): + """Returning DISABLE from a PY_THROW callback disables it for that code object.""" + + call_count = 0 + + def gen_foo(): + yield 1 + yield 2 + + def callback(code, offset, exc): + nonlocal call_count + call_count += 1 + return sys.monitoring.DISABLE + + try: + sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback) + sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, E.PY_THROW) + + g = gen_foo() + next(g) + try: + g.throw(RuntimeError("test")) + except RuntimeError: + pass + self.assertEqual(call_count, 1) + + g2 = gen_foo() + next(g2) + try: + g2.throw(RuntimeError("test")) + except RuntimeError: + pass + self.assertEqual(call_count, 1) # not fired again — disabled + finally: + sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None) + + def test_local_raise(self): + """RAISE fires as a local event only for the instrumented code object.""" + + def foo(): + try: + raise RuntimeError("test") + except RuntimeError: + pass + + def bar(): + try: + raise RuntimeError("test") + except RuntimeError: + pass + + events = [] + + def callback(code, offset, exc): + events.append(code.co_name) + + try: + sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE) + foo() + bar() # should NOT trigger the callback + self.assertEqual(events, ['foo']) + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None) + + def test_local_raise_disable(self): + """Returning DISABLE from a RAISE callback disables it for that code object.""" + + call_count = 0 + + def foo(): + try: + raise RuntimeError("test") + except RuntimeError: + pass + + def callback(code, offset, exc): + nonlocal call_count + call_count += 1 + return sys.monitoring.DISABLE + + try: + sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE) + foo() + self.assertEqual(call_count, 1) + foo() + self.assertEqual(call_count, 1) # not fired again — disabled + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None) + + def test_local_exception_handled(self): + """EXCEPTION_HANDLED fires as a local event only for the instrumented code object.""" + + def foo(): + try: + raise RuntimeError("test") + except RuntimeError: + pass + + def bar(): + try: + raise RuntimeError("test") + except RuntimeError: + pass + + events = [] + + def callback(code, offset, exc): + events.append(code.co_name) + + try: + sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED) + foo() + bar() # should NOT trigger the callback + self.assertEqual(events, ['foo']) + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None) + + def test_local_exception_handled_disable(self): + """Returning DISABLE from an EXCEPTION_HANDLED callback disables it for that code object.""" + + call_count = 0 + + def foo(): + try: + raise RuntimeError("test") + except RuntimeError: + pass + + def callback(code, offset, exc): + nonlocal call_count + call_count += 1 + return sys.monitoring.DISABLE + + try: + sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.EXCEPTION_HANDLED) + foo() + self.assertEqual(call_count, 1) + foo() + self.assertEqual(call_count, 1) # not fired again — disabled + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED, None) + + def test_local_py_unwind(self): + """PY_UNWIND fires as a local event only for the instrumented code object.""" + + def foo(): + raise RuntimeError("test") + + def bar(): + raise RuntimeError("test") + + events = [] + + def callback(code, offset, exc): + events.append(code.co_name) + + try: + sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND) + + try: + foo() + except RuntimeError: + pass + + try: + bar() # should NOT trigger the callback + except RuntimeError: + pass + + self.assertEqual(events, ['foo']) + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None) + + def test_local_py_unwind_disable(self): + """Returning DISABLE from a PY_UNWIND callback disables it for that code object.""" + + call_count = 0 + + def foo(): + raise RuntimeError("test") + + def callback(code, offset, exc): + nonlocal call_count + call_count += 1 + return sys.monitoring.DISABLE + + try: + sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback) + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.PY_UNWIND) + + try: + foo() + except RuntimeError: + pass + self.assertEqual(call_count, 1) # fired once + + try: + foo() + except RuntimeError: + pass + self.assertEqual(call_count, 1) # not fired again — disabled by DISABLE return + + finally: + sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0) + sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None) def line_from_offset(code, offset): for start, end, line in code.co_lines(): @@ -1617,11 +1924,11 @@ def whilefunc(n=0): ('branch right', 'whilefunc', 1, 3)]) self.check_events(func, recorders = BRANCH_OFFSET_RECORDERS, expected = [ - ('branch left', 'func', 28, 32), - ('branch right', 'func', 44, 58), - ('branch left', 'func', 28, 32), - ('branch left', 'func', 44, 50), - ('branch right', 'func', 28, 70)]) + ('branch left', 'func', 32, 36), + ('branch right', 'func', 48, 62), + ('branch left', 'func', 32, 36), + ('branch left', 'func', 48, 54), + ('branch right', 'func', 32, 74)]) def test_except_star(self): @@ -1648,8 +1955,8 @@ def func(): ('branch', 'func', 4, 4), ('line', 'func', 5), ('line', 'meth', 1), - ('jump', 'func', 5, '[offset=120]'), - ('branch', 'func', '[offset=124]', '[offset=130]'), + ('jump', 'func', 5, '[offset=122]'), + ('branch', 'func', '[offset=126]', '[offset=132]'), ('line', 'get_events', 11)]) self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [ @@ -1663,8 +1970,8 @@ def func(): ('line', 'func', 5), ('line', 'meth', 1), ('return', 'meth', None), - ('jump', 'func', 5, '[offset=120]'), - ('branch', 'func', '[offset=124]', '[offset=130]'), + ('jump', 'func', 5, '[offset=122]'), + ('branch', 'func', '[offset=126]', '[offset=132]'), ('return', 'func', None), ('line', 'get_events', 11)]) @@ -1676,8 +1983,8 @@ def foo(n=0): n += 1 return None - in_loop = ('branch left', 'foo', 10, 16) - exit_loop = ('branch right', 'foo', 10, 40) + in_loop = ('branch left', 'foo', 12, 18) + exit_loop = ('branch right', 'foo', 12, 42) self.check_events(foo, recorders = BRANCH_OFFSET_RECORDERS, expected = [ in_loop, in_loop, @@ -2150,8 +2457,8 @@ def run(): finally: sys.monitoring.set_events(TEST_TOOL, 0) - @unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi") def test_108390(self): + _testinternalcapi = import_helper.import_module("_testinternalcapi") class Foo: def __init__(self, set_event): @@ -2209,6 +2516,21 @@ def callback(code, instruction_offset): sys.monitoring.restart_events() sys.monitoring.set_events(0, 0) + def test_134879(self): + # gh-134789 + # Specialized FOR_ITER not incrementing index + def foo(): + t = 0 + for i in [1,2,3,4]: + t += i + self.assertEqual(t, 10) + + sys.monitoring.use_tool_id(0, "test") + self.addCleanup(sys.monitoring.free_tool_id, 0) + sys.monitoring.set_local_events(0, foo.__code__, E.BRANCH_LEFT | E.BRANCH_RIGHT) + foo() + sys.monitoring.set_local_events(0, foo.__code__, 0) + class TestOptimizer(MonitoringTestBase, unittest.TestCase): @@ -2227,8 +2549,9 @@ def test_func(x): class TestTier2Optimizer(CheckEvents): - @unittest.skipUnless(_testinternalcapi, "requires _testinternalcapi") def test_monitoring_already_opimized_loop(self): + _testinternalcapi = import_helper.import_module("_testinternalcapi") + def test_func(recorder): set_events = sys.monitoring.set_events line = E.LINE @@ -2262,23 +2585,24 @@ def test_monitoring_live_at_shutdown(self): script_helper.run_test_script(script) -@unittest.skipUnless(_testcapi, "requires _testcapi") class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase): class Scope: def __init__(self, *args): + self._testcapi = import_helper.import_module("_testcapi") self.args = args def __enter__(self): - _testcapi.monitoring_enter_scope(*self.args) + self._testcapi.monitoring_enter_scope(*self.args) def __exit__(self, *args): - _testcapi.monitoring_exit_scope() + self._testcapi.monitoring_exit_scope() def setUp(self): super(TestCApiEventGeneration, self).setUp() - capi = _testcapi + self._testcapi = import_helper.import_module("_testcapi") + capi = self._testcapi self.codelike = capi.CodeLike(2) @@ -2347,7 +2671,7 @@ def __call__(self, *args): def test_fire_event(self): for expected, event, function, *args in self.cases: offset = 0 - self.codelike = _testcapi.CodeLike(1) + self.codelike = self._testcapi.CodeLike(1) with self.subTest(function.__name__): args_ = (self.codelike, offset) + tuple(args) self.check_event_count(event, function, args_, expected) @@ -2358,7 +2682,7 @@ def test_missing_exception(self): continue assert args and isinstance(args[-1], BaseException) offset = 0 - self.codelike = _testcapi.CodeLike(1) + self.codelike = self._testcapi.CodeLike(1) with self.subTest(function.__name__): args_ = (self.codelike, offset) + tuple(args[:-1]) + (None,) evt = int(math.log2(event)) @@ -2368,7 +2692,7 @@ def test_missing_exception(self): def test_fire_event_failing_callback(self): for expected, event, function, *args in self.cases: offset = 0 - self.codelike = _testcapi.CodeLike(1) + self.codelike = self._testcapi.CodeLike(1) with self.subTest(function.__name__): args_ = (self.codelike, offset) + tuple(args) exc = OSError(42) @@ -2418,12 +2742,14 @@ def check_disable(self, event, func, args, expected): def test_disable_event(self): for expected, event, function, *args in self.cases: offset = 0 - self.codelike = _testcapi.CodeLike(2) + self.codelike = self._testcapi.CodeLike(2) with self.subTest(function.__name__): args_ = (self.codelike, 0) + tuple(args) self.check_disable(event, function, args_, expected) def test_enter_scope_two_events(self): + _testcapi = self._testcapi + try: yield_counter = CounterWithDisable() unwind_counter = CounterWithDisable() diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 66af5ae7cf9..1c683077edd 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -13,7 +13,7 @@ from test import support from test.support.bytecode_helper import ( - BytecodeTestCase, CfgOptimizationTestCase, CompilationStepTestCase) + BytecodeTestCase, CfgOptimizationTestCase) def compile_pattern_with_fast_locals(pattern): @@ -106,7 +106,7 @@ def test_elim_inversion_of_is_or_in(self): self.check_lnotab(code) def test_global_as_constant(self): - # LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False + # LOAD_GLOBAL None/True/False --> LOAD_COMMON_CONSTANT None/True/False def f(): x = None x = None @@ -121,7 +121,7 @@ def h(): for func, elem in ((f, None), (g, True), (h, False)): with self.subTest(func=func): self.assertNotInBytecode(func, 'LOAD_GLOBAL') - self.assertInBytecode(func, 'LOAD_CONST', elem) + self.assertInBytecode(func, 'LOAD_COMMON_CONSTANT', elem) self.check_lnotab(func) def f(): @@ -129,7 +129,7 @@ def f(): return None self.assertNotInBytecode(f, 'LOAD_GLOBAL') - self.assertInBytecode(f, 'LOAD_CONST', None) + self.assertInBytecode(f, 'LOAD_COMMON_CONSTANT', None) self.check_lnotab(f) def test_while_one(self): @@ -146,13 +146,14 @@ def f(): def test_pack_unpack(self): for line, elem in ( - ('a, = a,', 'LOAD_CONST',), + ('a, = a,', None), ('a, b = a, b', 'SWAP',), ('a, b, c = a, b, c', 'SWAP',), ): with self.subTest(line=line): code = compile(line,'','single') - self.assertInBytecode(code, elem) + if elem is not None: + self.assertInBytecode(code, elem) self.assertNotInBytecode(code, 'BUILD_TUPLE') self.assertNotInBytecode(code, 'UNPACK_SEQUENCE') self.check_lnotab(code) @@ -174,10 +175,10 @@ def test_constant_folding_tuples_of_constants(self): # Long tuples should be folded too. code = compile(repr(tuple(range(10000))),'','single') self.assertNotInBytecode(code, 'BUILD_TUPLE') - # One LOAD_CONST for the tuple, one for the None return value + # One LOAD_CONST for the tuple; None return value uses LOAD_COMMON_CONSTANT load_consts = [instr for instr in dis.get_instructions(code) if instr.opname == 'LOAD_CONST'] - self.assertEqual(len(load_consts), 2) + self.assertEqual(len(load_consts), 1) self.check_lnotab(code) # Bug 1053819: Tuple of constants misidentified when presented with: @@ -283,11 +284,11 @@ def test_constant_folding_unaryop(self): ('-0.0', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0), ('-(1.0-1.0)', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.0), ('-0.5', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -0.5), - ('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_CONST', -1), + ('---1', 'UNARY_NEGATIVE', None, True, 'LOAD_COMMON_CONSTANT', -1), ('---""', 'UNARY_NEGATIVE', None, False, None, None), ('~~~1', 'UNARY_INVERT', None, True, 'LOAD_CONST', -2), ('~~~""', 'UNARY_INVERT', None, False, None, None), - ('not not True', 'UNARY_NOT', None, True, 'LOAD_CONST', True), + ('not not True', 'UNARY_NOT', None, True, 'LOAD_COMMON_CONSTANT', True), ('not not x', 'UNARY_NOT', None, True, 'LOAD_NAME', 'x'), # this should be optimized regardless of constant or not ('+++1', 'CALL_INTRINSIC_1', intrinsic_positive, True, 'LOAD_SMALL_INT', 1), ('---x', 'UNARY_NEGATIVE', None, False, None, None), @@ -326,7 +327,7 @@ def test_constant_folding_binop(self): ('1 + 2', 'NB_ADD', True, 'LOAD_SMALL_INT', 3), ('1 + 2 + 3', 'NB_ADD', True, 'LOAD_SMALL_INT', 6), ('1 + ""', 'NB_ADD', False, None, None), - ('1 - 2', 'NB_SUBTRACT', True, 'LOAD_CONST', -1), + ('1 - 2', 'NB_SUBTRACT', True, 'LOAD_COMMON_CONSTANT', -1), ('1 - 2 - 3', 'NB_SUBTRACT', True, 'LOAD_CONST', -4), ('1 - ""', 'NB_SUBTRACT', False, None, None), ('2 * 2', 'NB_MULTIPLY', True, 'LOAD_SMALL_INT', 4), @@ -734,23 +735,27 @@ def test_format_errors(self): with self.assertRaisesRegex(TypeError, 'not all arguments converted during string formatting'): eval("'%s' % (x, y)", {'x': 1, 'y': 2}) - with self.assertRaisesRegex(ValueError, 'incomplete format'): + with self.assertRaisesRegex(ValueError, 'stray % at position 2'): eval("'%s%' % (x,)", {'x': 1234}) - with self.assertRaisesRegex(ValueError, 'incomplete format'): + with self.assertRaisesRegex(ValueError, 'stray % at position 4'): eval("'%s%%%' % (x,)", {'x': 1234}) with self.assertRaisesRegex(TypeError, 'not enough arguments for format string'): eval("'%s%z' % (x,)", {'x': 1234}) - with self.assertRaisesRegex(ValueError, 'unsupported format character'): + with self.assertRaisesRegex(ValueError, + 'unsupported format %z at position 2'): eval("'%s%z' % (x, 5)", {'x': 1234}) with self.assertRaisesRegex(TypeError, 'format argument 1: %d requires a real number, not str'): eval("'%d' % (x,)", {'x': '1234'}) - with self.assertRaisesRegex(TypeError, 'an integer is required, not float'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: %x requires an integer, not float'): eval("'%x' % (x,)", {'x': 1234.56}) - with self.assertRaisesRegex(TypeError, 'an integer is required, not str'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: %x requires an integer, not str'): eval("'%x' % (x,)", {'x': '1234'}) - with self.assertRaisesRegex(TypeError, 'must be real number, not str'): + with self.assertRaisesRegex(TypeError, + 'format argument 1: %f requires a real number, not str'): eval("'%f' % (x,)", {'x': '1234'}) with self.assertRaisesRegex(TypeError, 'not enough arguments for format string'): @@ -1118,6 +1123,13 @@ def trace(frame, event, arg): self.assertInBytecode(f, "LOAD_FAST_BORROW") self.assertNotInBytecode(f, "LOAD_FAST_CHECK") + def test_import_from_doesnt_clobber_load_fast_borrow(self): + def f(self): + if x: pass + self.x + from shutil import ExecError + print(ExecError) + self.assertInBytecode(f, "LOAD_FAST_BORROW", "self") class DirectCfgOptimizerTests(CfgOptimizationTestCase): @@ -1507,7 +1519,7 @@ def test_optimize_literal_list_for_iter(self): ('LOAD_SMALL_INT', 1, 0), ('LOAD_SMALL_INT', 2, 0), ('BUILD_LIST', 2, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1520,7 +1532,7 @@ def test_optimize_literal_list_for_iter(self): ] after = [ ('LOAD_CONST', 1, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1528,7 +1540,7 @@ def test_optimize_literal_list_for_iter(self): end, ('END_FOR', None, 0), ('POP_ITER', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)]) @@ -1538,7 +1550,7 @@ def test_optimize_literal_list_for_iter(self): ('LOAD_SMALL_INT', 1, 0), ('LOAD_NAME', 0, 0), ('BUILD_LIST', 2, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1553,7 +1565,7 @@ def test_optimize_literal_list_for_iter(self): ('LOAD_SMALL_INT', 1, 0), ('LOAD_NAME', 0, 0), ('BUILD_TUPLE', 2, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1561,7 +1573,7 @@ def test_optimize_literal_list_for_iter(self): end, ('END_FOR', None, 0), ('POP_ITER', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None]) @@ -1572,7 +1584,7 @@ def test_optimize_literal_set_for_iter(self): ('LOAD_SMALL_INT', 1, 0), ('LOAD_SMALL_INT', 2, 0), ('BUILD_SET', 2, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1585,7 +1597,7 @@ def test_optimize_literal_set_for_iter(self): ] after = [ ('LOAD_CONST', 1, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1593,7 +1605,7 @@ def test_optimize_literal_set_for_iter(self): end, ('END_FOR', None, 0), ('POP_ITER', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})]) @@ -1604,7 +1616,7 @@ def test_optimize_literal_set_for_iter(self): ('LOAD_SMALL_INT', 1, 0), ('LOAD_NAME', 0, 0), ('BUILD_SET', 2, 0), - ('GET_ITER', None, 0), + ('GET_ITER', 0, 0), start := self.Label(), ('FOR_ITER', end := self.Label(), 0), ('STORE_FAST', 0, 0), @@ -1615,7 +1627,22 @@ def test_optimize_literal_set_for_iter(self): ('LOAD_CONST', 0, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None]) + expected = [ + ('LOAD_SMALL_INT', 1, 0), + ('LOAD_NAME', 0, 0), + ('BUILD_SET', 2, 0), + ('GET_ITER', 0, 0), + start := self.Label(), + ('FOR_ITER', end := self.Label(), 0), + ('STORE_FAST', 0, 0), + ('JUMP', start, 0), + end, + ('END_FOR', None, 0), + ('POP_ITER', None, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), + ('RETURN_VALUE', None, 0), + ] + self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None]) def test_optimize_literal_list_contains(self): # x in [1, 2] ==> x in (1, 2) @@ -1634,7 +1661,7 @@ def test_optimize_literal_list_contains(self): ('LOAD_CONST', 1, 0), ('CONTAINS_OP', 0, 0), ('POP_TOP', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, (1, 2)]) @@ -1657,7 +1684,7 @@ def test_optimize_literal_list_contains(self): ('BUILD_TUPLE', 2, 0), ('CONTAINS_OP', 0, 0), ('POP_TOP', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None]) @@ -1679,7 +1706,7 @@ def test_optimize_literal_set_contains(self): ('LOAD_CONST', 1, 0), ('CONTAINS_OP', 0, 0), ('POP_TOP', None, 0), - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[None], expected_consts=[None, frozenset({1, 2})]) @@ -1696,7 +1723,17 @@ def test_optimize_literal_set_contains(self): ('LOAD_CONST', 0, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(same, same, consts=[None], expected_consts=[None]) + expected = [ + ('LOAD_NAME', 0, 0), + ('LOAD_SMALL_INT', 1, 0), + ('LOAD_NAME', 1, 0), + ('BUILD_SET', 2, 0), + ('CONTAINS_OP', 0, 0), + ('POP_TOP', None, 0), + ('LOAD_COMMON_CONSTANT', 7, 0), + ('RETURN_VALUE', None, 0), + ] + self.cfg_optimization_test(same, expected, consts=[None], expected_consts=[None]) def test_optimize_unary_not(self): # test folding @@ -1707,10 +1744,10 @@ def test_optimize_unary_not(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 1, 0), + ('LOAD_COMMON_CONSTANT', 10, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False]) + self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) # test cancel out before = [ @@ -1758,7 +1795,7 @@ def test_optimize_unary_not(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 0, 0), + ('LOAD_COMMON_CONSTANT', 9, 0), ('RETURN_VALUE', None, 0), ] self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) @@ -1774,10 +1811,10 @@ def test_optimize_unary_not(self): ('RETURN_VALUE', None, 0), ] after = [ - ('LOAD_CONST', 1, 0), + ('LOAD_COMMON_CONSTANT', 10, 0), ('RETURN_VALUE', None, 0), ] - self.cfg_optimization_test(before, after, consts=[], expected_consts=[True, False]) + self.cfg_optimization_test(before, after, consts=[], expected_consts=[True]) # test cancel out & eliminate to bool (to bool stays as we are not iterating to a fixed point) before = [ @@ -2393,7 +2430,7 @@ def test_list_to_tuple_get_iter(self): ("LOAD_FAST", 1, 4), ("LIST_EXTEND", 1, 5), ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6), - ("GET_ITER", None, 7), + ("GET_ITER", 0, 7), top := self.Label(), ("FOR_ITER", end := self.Label(), 8), ("STORE_FAST", 2, 9), @@ -2411,7 +2448,7 @@ def test_list_to_tuple_get_iter(self): ("LOAD_FAST_BORROW", 1, 4), ("LIST_EXTEND", 1, 5), ("NOP", None, 6), # ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 6), - ("GET_ITER", None, 7), + ("GET_ITER", 0, 7), top := self.Label(), ("FOR_ITER", end := self.Label(), 8), ("STORE_FAST", 2, 9), @@ -2419,7 +2456,7 @@ def test_list_to_tuple_get_iter(self): end, ("END_FOR", None, 11), ("POP_TOP", None, 12), - ("LOAD_CONST", 0, 13), + ("LOAD_COMMON_CONSTANT", 7, 13), ("RETURN_VALUE", None, 14), ] self.cfg_optimization_test(insts, expected_insts, consts=[None]) @@ -2433,6 +2470,168 @@ def test_list_to_tuple_get_iter_is_safe(self): self.assertEqual(b, [3, 2, 1, 0]) self.assertEqual(items, []) + def test_fold_constant_big_list_for_iter(self): + # for x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple + consts = 35 + before = ( + [("BUILD_LIST", 0, 1)] + + [("LOAD_CONST", 0, 2), ("LIST_APPEND", 1, 3)] * consts + + [("GET_ITER", 0, 4), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 5), + ("STORE_FAST", 0, 6), + ("JUMP", top, 7), + end, + ("END_FOR", None, 8), + ("POP_ITER", None, 9), + ("LOAD_CONST", 0, 10), + ("RETURN_VALUE", None, 11)] + ) + after = [ + ("LOAD_CONST", 1, 3), + ("GET_ITER", 0, 4), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 5), + ("STORE_FAST", 0, 6), + ("JUMP", top, 7), + end, + ("END_FOR", None, 8), + ("POP_ITER", None, 9), + ("LOAD_CONST", 0, 10), + ("RETURN_VALUE", None, 11), + ] + result_const = tuple(["test"] * consts) + self.cfg_optimization_test(before, after, consts=["test"], + expected_consts=["test", result_const]) + + def test_fold_constant_big_set_for_iter(self): + # for x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset + before = [ + ("BUILD_SET", 0, 1), + ("LOAD_SMALL_INT", 1, 2), ("SET_ADD", 1, 3), + ("LOAD_SMALL_INT", 2, 4), ("SET_ADD", 1, 5), + ("LOAD_SMALL_INT", 3, 6), ("SET_ADD", 1, 7), + ("GET_ITER", 0, 8), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 9), + ("STORE_FAST", 0, 10), + ("JUMP", top, 11), + end, + ("END_FOR", None, 12), + ("POP_ITER", None, 13), + ("LOAD_CONST", 0, 14), + ("RETURN_VALUE", None, 15), + ] + after = [ + ("LOAD_CONST", 1, 7), + ("GET_ITER", 0, 8), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 9), + ("STORE_FAST", 0, 10), + ("JUMP", top, 11), + end, + ("END_FOR", None, 12), + ("POP_ITER", None, 13), + ("LOAD_CONST", 0, 14), + ("RETURN_VALUE", None, 15), + ] + self.cfg_optimization_test(before, after, consts=["test"], + expected_consts=["test", frozenset({1, 2, 3})]) + + def test_fold_constant_list_to_tuple_for_iter(self): + INTRINSIC_LIST_TO_TUPLE = 6 + before = [ + ("BUILD_LIST", 0, 1), + ("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3), + ("LOAD_SMALL_INT", 2, 4), ("LIST_APPEND", 1, 5), + ("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7), + ("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 8), + ("GET_ITER", 0, 9), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 10), + ("STORE_FAST", 0, 11), + ("JUMP", top, 12), + end, + ("END_FOR", None, 13), + ("POP_ITER", None, 14), + ("LOAD_CONST", 0, 15), + ("RETURN_VALUE", None, 16), + ] + after = [ + ("LOAD_CONST", 1, 8), + ("GET_ITER", 0, 9), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 10), + ("STORE_FAST", 0, 11), + ("JUMP", top, 12), + end, + ("END_FOR", None, 13), + ("POP_ITER", None, 14), + ("LOAD_CONST", 0, 15), + ("RETURN_VALUE", None, 16), + ] + self.cfg_optimization_test(before, after, consts=["test"], + expected_consts=["test", (1, 2, 3)]) + + def test_fold_constant_big_list_contains_op(self): + # x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple + before = [ + ("LOAD_FAST", 0, 1), + ("BUILD_LIST", 0, 2), + ("LOAD_SMALL_INT", 1, 3), ("LIST_APPEND", 1, 4), + ("LOAD_SMALL_INT", 2, 5), ("LIST_APPEND", 1, 6), + ("LOAD_SMALL_INT", 3, 7), ("LIST_APPEND", 1, 8), + ("CONTAINS_OP", 0, 9), + ("RETURN_VALUE", None, 10), + ] + after = [ + ("LOAD_FAST_BORROW", 0, 1), + ("LOAD_CONST", 1, 8), + ("CONTAINS_OP", 0, 9), + ("RETURN_VALUE", None, 10), + ] + self.cfg_optimization_test(before, after, consts=[None], + expected_consts=[None, (1, 2, 3)]) + + def test_fold_constant_big_set_contains_op(self): + # x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset + before = [ + ("LOAD_FAST", 0, 1), + ("BUILD_SET", 0, 2), + ("LOAD_SMALL_INT", 1, 3), ("SET_ADD", 1, 4), + ("LOAD_SMALL_INT", 2, 5), ("SET_ADD", 1, 6), + ("LOAD_SMALL_INT", 3, 7), ("SET_ADD", 1, 8), + ("CONTAINS_OP", 0, 9), + ("RETURN_VALUE", None, 10), + ] + after = [ + ("LOAD_FAST_BORROW", 0, 1), + ("LOAD_CONST", 1, 8), + ("CONTAINS_OP", 0, 9), + ("RETURN_VALUE", None, 10), + ] + self.cfg_optimization_test(before, after, consts=[None], + expected_consts=[None, frozenset({1, 2, 3})]) + + def test_no_fold_big_list_for_iter_with_non_const(self): + same = [ + ("BUILD_LIST", 0, 1), + ("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3), + ("LOAD_FAST_BORROW", 0, 4), ("LIST_APPEND", 1, 5), + ("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7), + ("GET_ITER", 0, 8), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 9), + ("STORE_FAST", 1, 10), + ("JUMP", top, 11), + end, + ("END_FOR", None, 12), + ("POP_ITER", None, 13), + ("LOAD_CONST", 0, 14), + ("RETURN_VALUE", None, 15), + ] + self.cfg_optimization_test(same, same, consts=["test"]) + class OptimizeLoadFastTestCase(DirectCfgOptimizerTests): def make_bb(self, insts): @@ -2443,7 +2642,7 @@ def make_bb(self, insts): maxconst = max(maxconst, arg) consts = [None for _ in range(maxconst + 1)] return insts + [ - ("LOAD_CONST", 0, last_loc + 1), + ("LOAD_COMMON_CONSTANT", 7, last_loc + 1), ("RETURN_VALUE", None, last_loc + 2), ], consts @@ -2474,7 +2673,7 @@ def test_optimized(self): ] expected = [ ("LOAD_FAST_BORROW", 0, 1), - ("LOAD_CONST", 1, 2), + ("LOAD_COMMON_CONSTANT", 7, 2), ("SWAP", 2, 3), ("POP_TOP", None, 4), ] @@ -2512,7 +2711,13 @@ def test_unoptimized_if_support_killed(self): ("STORE_FAST", 0, 3), ("POP_TOP", None, 4), ] - self.check(insts, insts) + expected = [ + ("LOAD_FAST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 2), + ("STORE_FAST", 0, 3), + ("POP_TOP", None, 4), + ] + self.check(insts, expected) insts = [ ("LOAD_FAST", 0, 1), @@ -2521,7 +2726,14 @@ def test_unoptimized_if_support_killed(self): ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), ("POP_TOP", None, 5), ] - self.check(insts, insts) + expected = [ + ("LOAD_FAST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 2), + ("LOAD_COMMON_CONSTANT", 7, 3), + ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), + ("POP_TOP", None, 5), + ] + self.check(insts, expected) insts = [ ("LOAD_FAST", 0, 1), @@ -2542,7 +2754,12 @@ def test_unoptimized_if_aliased(self): ("LOAD_CONST", 0, 3), ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), ] - self.check(insts, insts) + expected = [ + ("LOAD_FAST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 3), + ("STORE_FAST_STORE_FAST", ((0 << 4) | 1), 4), + ] + self.check(insts, expected) def test_consume_no_inputs(self): insts = [ @@ -2587,7 +2804,19 @@ def test_for_iter(self): ("LOAD_CONST", 0, 7), ("RETURN_VALUE", None, 8), ] - self.cfg_optimization_test(insts, insts, consts=[None]) + expected = [ + ("LOAD_FAST", 0, 1), + top := self.Label(), + ("FOR_ITER", end := self.Label(), 2), + ("STORE_FAST", 2, 3), + ("JUMP", top, 4), + end, + ("END_FOR", None, 5), + ("POP_TOP", None, 6), + ("LOAD_COMMON_CONSTANT", 7, 7), + ("RETURN_VALUE", None, 8), + ] + self.cfg_optimization_test(insts, expected, consts=[None]) def test_load_attr(self): insts = [ @@ -2656,10 +2885,10 @@ def test_send(self): ("LOAD_FAST", 0, 1), ("LOAD_FAST_BORROW", 1, 2), ("SEND", end := self.Label(), 3), - ("LOAD_CONST", 0, 4), + ("LOAD_COMMON_CONSTANT", 7, 4), ("RETURN_VALUE", None, 5), end, - ("LOAD_CONST", 0, 6), + ("LOAD_COMMON_CONSTANT", 7, 6), ("RETURN_VALUE", None, 7) ] self.cfg_optimization_test(insts, expected, consts=[None]) @@ -2697,7 +2926,15 @@ def test_set_function_attribute(self): ("LOAD_CONST", 0, 5), ("RETURN_VALUE", None, 6) ] - self.cfg_optimization_test(insts, insts, consts=[None]) + expected = [ + ("LOAD_COMMON_CONSTANT", 7, 1), + ("LOAD_FAST", 0, 2), + ("SET_FUNCTION_ATTRIBUTE", 2, 3), + ("STORE_FAST", 1, 4), + ("LOAD_COMMON_CONSTANT", 7, 5), + ("RETURN_VALUE", None, 6) + ] + self.cfg_optimization_test(insts, expected, consts=[None]) insts = [ ("LOAD_CONST", 0, 1), @@ -2706,7 +2943,7 @@ def test_set_function_attribute(self): ("RETURN_VALUE", None, 4) ] expected = [ - ("LOAD_CONST", 0, 1), + ("LOAD_COMMON_CONSTANT", 7, 1), ("LOAD_FAST_BORROW", 0, 2), ("SET_FUNCTION_ATTRIBUTE", 2, 3), ("RETURN_VALUE", None, 4) @@ -2714,22 +2951,37 @@ def test_set_function_attribute(self): self.cfg_optimization_test(insts, expected, consts=[None]) def test_get_yield_from_iter(self): - # GET_YIELD_FROM_ITER may leave its operand on the stack insts = [ ("LOAD_FAST", 0, 1), - ("GET_YIELD_FROM_ITER", None, 2), - ("LOAD_CONST", 0, 3), + ("GET_ITER", 1, 2), + ("PUSH_NULL", None, 3), + ("LOAD_CONST", 0, 4), send := self.Label(), - ("SEND", end := self.Label(), 5), - ("YIELD_VALUE", 1, 6), - ("RESUME", 2, 7), - ("JUMP", send, 8), + ("SEND", end := self.Label(), 6), + ("YIELD_VALUE", 1, 7), + ("RESUME", 2, 8), + ("JUMP", send, 9), end, - ("END_SEND", None, 9), - ("LOAD_CONST", 0, 10), - ("RETURN_VALUE", None, 11), + ("END_SEND", None, 10), + ("LOAD_CONST", 0, 11), + ("RETURN_VALUE", None, 12), + ] + expected = [ + ("LOAD_FAST", 0, 1), + ("GET_ITER", 1, 2), + ("PUSH_NULL", None, 3), + ("LOAD_COMMON_CONSTANT", 7, 4), + send := self.Label(), + ("SEND", end := self.Label(), 6), + ("YIELD_VALUE", 1, 7), + ("RESUME", 2, 8), + ("JUMP", send, 9), + end, + ("END_SEND", None, 10), + ("LOAD_COMMON_CONSTANT", 7, 11), + ("RETURN_VALUE", None, 12), ] - self.cfg_optimization_test(insts, insts, consts=[None]) + self.cfg_optimization_test(insts, expected, consts=[None]) def test_push_exc_info(self): insts = [ diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 078b4ba7af7..682611892e1 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -8,7 +8,7 @@ use crate::{ class::PyClassImpl, frame::{FrameObject, FrameOwner}, function::PySetterValue, - types::{Destructor, Representable}, + types::Representable, }; use core::sync::atomic::Ordering::Relaxed; use num_traits::Zero; @@ -515,19 +515,19 @@ impl Py { ); match owner { FrameOwner::Generator => { - // FRAME_SUSPENDED (lasti > 0) cannot be cleared. FRAME_CREATED - // and finished frames go through the owner finalizer. - if self.lasti() != 0 { - return Err(vm.new_runtime_error("cannot clear a suspended frame")); - } + // RETURN_GENERATOR executes the creation preamble, so lasti + // no longer distinguishes a created frame from a suspended one. if let Some(owner) = self.iframe().generator.to_owned() { - if let Some(coro) = owner.downcast_ref::() { - let _ = PyCoroutine::del(coro, vm); + let coro = if let Some(coro) = owner.downcast_ref::() { + coro.as_coro() } else if let Some(async_gen) = owner.downcast_ref::() { - let _ = PyAsyncGen::del(async_gen, vm); + async_gen.as_coro() } else if let Some(generator) = owner.downcast_ref::() { - let _ = PyGenerator::del(generator, vm); - } + generator.as_coro() + } else { + unreachable!("generator-owned frame has no coroutine owner") + }; + coro.clear_frame(&owner, vm)?; } return Ok(()); } diff --git a/crates/vm/src/builtins/generator.rs b/crates/vm/src/builtins/generator.rs index 8eb6013e703..5e1b5dc484c 100644 --- a/crates/vm/src/builtins/generator.rs +++ b/crates/vm/src/builtins/generator.rs @@ -122,6 +122,19 @@ impl Py { self.inner.running() } + #[pygetset] + fn gi_state(&self) -> &'static str { + if self.inner.closed() { + "GEN_CLOSED" + } else if self.inner.running() { + "GEN_RUNNING" + } else if self.inner.started() { + "GEN_SUSPENDED" + } else { + "GEN_CREATED" + } + } + #[pygetset] fn gi_code(&self, _vm: &VirtualMachine) -> PyRef { self.inner.code() diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index bf287c02823..feb95d49ca1 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -495,6 +495,32 @@ impl Coro { } } + /// Clear a created generator frame without resuming its body. + pub(crate) fn clear_frame(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult<()> { + if self.closed.load() { + return Ok(()); + } + if self.running.compare_exchange(false, true).is_err() { + return Err(vm.new_runtime_error("cannot clear an executing frame")); + } + let claim = RunningGuard(self); + if self.closed.load() { + return Ok(()); + } + if self.started() { + return Err(vm.new_runtime_error("cannot clear a suspended frame")); + } + self.closed.store(true); + // Closed owners cannot resume. Publish that state and release the + // running claim before a warning hook can observe or clear us again. + drop(claim); + if jen.class().is(vm.ctx.types.coroutine_type) { + crate::warn::warn_unawaited_coroutine(jen, &self.qualname(), vm); + } + self.clear_except_code(); + Ok(()) + } + /// Whether the frame has run at all, i.e. is stopped at a `yield` rather /// than at its start. pub(crate) fn started(&self) -> bool { From e5040fd4b35cf2f3643d9fca017c212b312cf495 Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:46 +0900 Subject: [PATCH 04/12] Preserve code-name owners and final warning and constant-fold contracts Replay reviewed compiler CI repairs 6482adf on the frozen current prerequisite. Intermediate compile/diagnostic corrections are combined where they address the same failure. Assisted-by: OpenAI Codex: model identifier unavailable --- Lib/_py_warnings.py | 107 +++++++++++++++++++++++++-------- crates/codegen/src/ir.rs | 72 +++++++++++++++------- crates/common/src/cformat.rs | 34 +++++++++-- crates/vm/src/builtins/code.rs | 33 ++++++++-- crates/vm/src/cformat.rs | 32 ++++++++-- crates/vm/src/warn.rs | 57 +++++++++++++----- 6 files changed, 257 insertions(+), 78 deletions(-) diff --git a/Lib/_py_warnings.py b/Lib/_py_warnings.py index 55f8c069591..ab09913de68 100644 --- a/Lib/_py_warnings.py +++ b/Lib/_py_warnings.py @@ -369,9 +369,15 @@ def _setoption(arg): if message or module: import re if message: - message = re.escape(message) + if len(message) >= 2 and message[0] == message[-1] == '/': + message = message[1:-1] + else: + message = re.escape(message) if module: - module = re.escape(module) + r'\z' + if len(module) >= 2 and module[0] == module[-1] == '/': + module = module[1:-1] + else: + module = re.escape(module) + r'\z' if lineno: try: lineno = int(lineno) @@ -381,7 +387,23 @@ def _setoption(arg): raise _wm._OptionError("invalid lineno %r" % (lineno,)) from None else: lineno = 0 - _wm.filterwarnings(action, message, category, module, lineno) + try: + _wm.filterwarnings(action, message, category, module, lineno) + except re.PatternError if message or module else (): + if message: + try: + re.compile(message) + except re.PatternError: + raise _wm._OptionError(f"invalid regular expression for " + f"message: {message!r}") from None + if module: + try: + re.compile(module) + except re.PatternError: + raise _wm._OptionError(f"invalid regular expression for " + f"module: {module!r}") from None + # Should never happen. + raise # Helper for _setoption() @@ -449,9 +471,12 @@ def warn(message, category=None, stacklevel=1, source=None, # Check category argument if category is None: category = UserWarning - if not (isinstance(category, type) and issubclass(category, Warning)): - raise TypeError("category must be a Warning subclass, " - "not '{:s}'".format(type(category).__name__)) + elif not isinstance(category, type): + raise TypeError(f"category must be a Warning subclass, not " + f"'{type(category).__name__}'") + elif not issubclass(category, Warning): + raise TypeError(f"category must be a Warning subclass, not " + f"class '{category.__name__}'") if not isinstance(skip_file_prefixes, tuple): # The C version demands a tuple for implementation performance. raise TypeError('skip_file_prefixes must be a tuple of strs.') @@ -495,14 +520,43 @@ def warn(message, category=None, stacklevel=1, source=None, ) +def _match_filename(pattern, filename, *, MS_WINDOWS=(sys.platform == 'win32')): + if not filename: + return pattern.match('') is not None + if filename[0] == '<' and filename[-1] == '>': + return pattern.match(filename) is not None + + is_py = (filename[-3:].lower() == '.py' + if MS_WINDOWS else + filename.endswith('.py')) + if is_py: + filename = filename[:-3] + if pattern.match(filename): # for backward compatibility + return True + if MS_WINDOWS: + if not is_py and filename[-4:].lower() == '.pyw': + filename = filename[:-4] + is_py = True + if is_py and filename[-9:].lower() in (r'\__init__', '/__init__'): + filename = filename[:-9] + filename = filename.replace('\\', '/') + else: + if is_py and filename.endswith('/__init__'): + filename = filename[:-9] + filename = filename.replace('/', '.') + i = 0 + while True: + if pattern.match(filename, i): + return True + i = filename.find('.', i) + 1 + if not i: + return False + + def warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None): lineno = int(lineno) - if module is None: - module = filename or "" - if module[-3:].lower() == ".py": - module = module[:-3] # XXX What about leading pathname? if isinstance(message, Warning): text = str(message) category = message.__class__ @@ -524,9 +578,11 @@ def warn_explicit(message, category, filename, lineno, action, msg, cat, mod, ln = item if ((msg is None or msg.match(text)) and issubclass(category, cat) and - (mod is None or mod.match(module)) and - (ln == 0 or lineno == ln)): - break + (ln == 0 or lineno == ln) and + (mod is None or (_match_filename(mod, filename) + if module is None else + mod.match(module)))): + break else: action = _wm.defaultaction # Early exit actions @@ -564,17 +620,18 @@ def warn_explicit(message, category, filename, lineno, linecache.getlines(filename, module_globals) # Print message and context - msg = _wm.WarningMessage(message, category, filename, lineno, source=source) + msg = _wm.WarningMessage(message, category, filename, lineno, + module=module, source=source) _wm._showwarnmsg(msg) class WarningMessage(object): _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", - "line", "source") + "line", "source", "module") def __init__(self, message, category, filename, lineno, file=None, - line=None, source=None): + line=None, source=None, module=None): self.message = message self.category = category self.filename = filename @@ -582,12 +639,14 @@ def __init__(self, message, category, filename, lineno, file=None, self.file = file self.line = line self.source = source + self.module = module self._category_name = category.__name__ if category else None def __str__(self): - return ("{message : %r, category : %r, filename : %r, lineno : %s, " - "line : %r}" % (self.message, self._category_name, - self.filename, self.lineno, self.line)) + return ("{message : %r, category : %r, module : %r, " + "filename : %r, lineno : %s, line : %r}" % ( + self.message, self._category_name, self.module, + self.filename, self.lineno, self.line)) def __repr__(self): return f'<{type(self).__qualname__} {self}>' @@ -647,8 +706,8 @@ def __enter__(self): context = None self._filters = self._module.filters self._module.filters = self._filters[:] - self._showwarning = self._module.showwarning self._showwarnmsg_impl = self._module._showwarnmsg_impl + self._showwarning = self._module.showwarning self._module._filters_mutated_lock_held() if self._record: if _use_context: @@ -656,9 +715,9 @@ def __enter__(self): else: log = [] self._module._showwarnmsg_impl = log.append - # Reset showwarning() to the default implementation to make sure - # that _showwarnmsg() calls _showwarnmsg_impl() - self._module.showwarning = self._module._showwarning_orig + # Reset showwarning() to the default implementation to make sure + # that _showwarnmsg() calls _showwarnmsg_impl() + self._module.showwarning = self._module._showwarning_orig else: log = None if self._filter is not None: @@ -673,8 +732,8 @@ def __exit__(self, *exc_info): self._module._warnings_context.set(self._saved_context) else: self._module.filters = self._filters - self._module.showwarning = self._showwarning self._module._showwarnmsg_impl = self._showwarnmsg_impl + self._module.showwarning = self._showwarning self._module._filters_mutated_lock_held() diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 9ee1c5dfe2b..5f8c2cf008f 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -2251,14 +2251,17 @@ impl Blocks { AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => { match func.get(inst.arg) { IntrinsicFunction1::ListToTuple => { - if matches!(nextop, Some(Instruction::GetIter { .. })) { + fold_constant_sequence_into_load_const( + metadata, + &mut self[block_idx], + i, + )?; + if matches!( + self[block_idx].instructions[i].instr.real(), + Some(Instruction::CallIntrinsic1 { .. }) + ) && matches!(nextop, Some(Instruction::GetIter { .. })) + { self[block_idx].instructions[i].set_to_nop(); - } else { - fold_constant_intrinsic_list_to_tuple( - metadata, - &mut self[block_idx], - i, - )?; } } IntrinsicFunction1::UnaryPositive => { @@ -2267,6 +2270,16 @@ impl Blocks { _ => {} } } + AnyInstruction::Real( + Instruction::ListAppend { .. } | Instruction::SetAdd { .. }, + ) if u32::from(inst.arg) == 1 + && matches!( + nextop, + Some(Instruction::GetIter { .. } | Instruction::ContainsOp { .. }) + ) => + { + fold_constant_sequence_into_load_const(metadata, &mut self[block_idx], i)?; + } AnyInstruction::Real(Instruction::BinaryOp { .. }) => { fold_const_binop(metadata, &mut self[block_idx], i)?; } @@ -5456,20 +5469,24 @@ fn fold_tuple_of_constants( Ok(true) } -fn fold_constant_intrinsic_list_to_tuple( +fn fold_constant_sequence_into_load_const( metadata: &mut CodeUnitMetadata, block: &mut Block, i: usize, ) -> crate::InternalResult { - let Some(Instruction::CallIntrinsic1 { func }) = block.instructions[i].instr.real() else { - return Ok(false); + let (intrinsic, is_set) = match block.instructions[i].instr.real() { + Some(Instruction::CallIntrinsic1 { func }) + if func.get(block.instructions[i].arg) == IntrinsicFunction1::ListToTuple => + { + (true, false) + } + Some(Instruction::ListAppend { .. }) => (false, false), + Some(Instruction::SetAdd { .. }) => (false, true), + _ => return Ok(false), }; - if func.get(block.instructions[i].arg) != IntrinsicFunction1::ListToTuple { - return Ok(false); - } let mut consts_found = 0usize; - let mut expect_append = true; + let mut expect_append = intrinsic; let mut pos = i; while let Some(prev) = pos.checked_sub(1) { pos = prev; @@ -5478,9 +5495,12 @@ fn fold_constant_intrinsic_list_to_tuple( continue; } - if matches!(instr.instr.real(), Some(Instruction::BuildList { .. })) - && u32::from(instr.arg) == 0 - { + let is_start = if is_set { + matches!(instr.instr.real(), Some(Instruction::BuildSet { .. })) + } else { + matches!(instr.instr.real(), Some(Instruction::BuildList { .. })) + }; + if is_start && u32::from(instr.arg) == 0 { if !expect_append { return Ok(false); } @@ -5489,7 +5509,8 @@ fn fold_constant_intrinsic_list_to_tuple( elements .try_reserve_exact(consts_found) .map_err(|_| InternalError::MalformedControlFlowGraph)?; - for idx in (pos..i).rev() { + let end = if intrinsic { i } else { i + 1 }; + for idx in (pos..end).rev() { if matches!(block.instructions[idx].instr.real(), Some(Instruction::Nop)) { continue; } @@ -5506,15 +5527,22 @@ fn fold_constant_intrinsic_list_to_tuple( instr_make_load_const( metadata, &mut block.instructions[i], - ConstantData::Tuple { elements }, + if is_set { + ConstantData::Frozenset { elements } + } else { + ConstantData::Tuple { elements } + }, )?; return Ok(true); } if expect_append { - if !matches!(instr.instr.real(), Some(Instruction::ListAppend { .. })) - || u32::from(instr.arg) != 1 - { + let is_append = if is_set { + matches!(instr.instr.real(), Some(Instruction::SetAdd { .. })) + } else { + matches!(instr.instr.real(), Some(Instruction::ListAppend { .. })) + }; + if !is_append || u32::from(instr.arg) != 1 { return Ok(false); } } else { diff --git a/crates/common/src/cformat.rs b/crates/common/src/cformat.rs index de437f2ce0d..8117cf7b700 100644 --- a/crates/common/src/cformat.rs +++ b/crates/common/src/cformat.rs @@ -20,6 +20,7 @@ pub enum CFormatErrorType { MissingModuloSign, UnsupportedFormatChar(CodePoint), IncompleteFormat, + StrayModulo, WidthTooBig, PrecisionTooBig, // Unimplemented, @@ -39,6 +40,7 @@ impl fmt::Display for CFormatError { match self.typ { CFormatErrorType::UnmatchedKeyParentheses => write!(f, "incomplete format key"), CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"), + CFormatErrorType::StrayModulo => write!(f, "stray % at position {}", self.index), CFormatErrorType::UnsupportedFormatChar(c) => write!( f, "unsupported format character '{}' ({:#x}) at index {}", @@ -832,8 +834,20 @@ impl CFormatStrOrBytes { let spec = CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError { - typ: err.0, - index: err.1, + typ: if context == CFormatContext::Str + && err.0 == CFormatErrorType::IncompleteFormat + { + CFormatErrorType::StrayModulo + } else { + err.0 + }, + index: if context == CFormatContext::Str + && err.0 == CFormatErrorType::IncompleteFormat + { + index + } else { + err.1 + }, })?; parts.push((index, CFormatPart::Spec(spec))); @@ -842,8 +856,16 @@ impl CFormatStrOrBytes { } } else { return Err(CFormatError { - typ: CFormatErrorType::IncompleteFormat, - index: index + 1, + typ: if context == CFormatContext::Str { + CFormatErrorType::StrayModulo + } else { + CFormatErrorType::IncompleteFormat + }, + index: if context == CFormatContext::Str { + index + } else { + index + 1 + }, }); } } else { @@ -991,8 +1013,8 @@ mod tests { assert_eq!( "Hello %".parse::(), Err(CFormatError { - typ: CFormatErrorType::IncompleteFormat, - index: 7 + typ: CFormatErrorType::StrayModulo, + index: 6 }) ); } diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index aa755647370..a2e8e0d630a 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -427,7 +427,6 @@ pub struct PyCode { #[pymember(name = "co_posonlyargcount", path = "posonlyarg_count")] #[pymember(name = "co_kwonlyargcount", path = "kwonlyarg_count")] #[pymember(name = "co_stacksize", path = "max_stackdepth")] - #[pymember(name = "co_name", path = "obj_name")] #[pymember(name = "co_qualname", path = "qualname")] #[pymember(name = "co_flags", path = "flags")] // Logical code fields are finalized by `new`. Execution may specialize or @@ -436,6 +435,8 @@ pub struct PyCode { pub code: CodeObject, /// Canonical bytes captured before execution can instrument the code. canonical_code: Box<[u8]>, + /// Preserve a public co_name subclass; exact strings use the interned owner. + name_owner: Option, /// The stable Python object exposed by co_code, allocated on first access. code_bytes: OnceCell, /// Slot-indexed names, equivalent to CPython's `co_localsplusnames`. @@ -578,7 +579,7 @@ impl PyCode { CodeField::Names => self.names_tuple.as_object(), CodeField::LocalsPlusNames => self.localsplus_names_tuple.as_object(), CodeField::LocalsPlusKinds => self.localsplus_kinds_bytes.as_object(), - CodeField::Name => self.code.obj_name.as_object(), + CodeField::Name => self.name_object(), CodeField::Qualname => self.code.qualname.as_object(), CodeField::LineTable => self.linetable_bytes.as_object(), CodeField::ExceptionTable => self.exceptiontable_bytes.as_object(), @@ -661,6 +662,7 @@ impl PyCode { cellvars_tuple: OnceCell::new(), freevars_tuple: OnceCell::new(), canonical_code, + name_owner: None, code_bytes: OnceCell::new(), localsplus_names, source_path: AtomicPtr::new(sp), @@ -739,6 +741,18 @@ impl PyCode { self.localsplus_names[index] } + fn name_object(&self) -> &PyObject { + self.name_owner + .as_ref() + .map_or_else(|| self.code.obj_name.as_object(), |name| name.as_object()) + } + + fn retain_name_subclass(&mut self, name: PyStrRef, ctx: &Context) { + if !name.class().is(ctx.types.str_type) { + self.name_owner = Some(name); + } + } + pub fn source_path(&self) -> &'static PyStrInterned { // SAFETY: always points to a valid &'static PyStrInterned (interned strings are never deallocated) unsafe { &*self.source_path.load(Ordering::Relaxed) } @@ -912,7 +926,7 @@ impl Comparable for PyCode { let other = class_or_notimplemented!(Self, other); let a = &zelf.code; let b = &other.code; - let eq = a.obj_name == b.obj_name + let eq = vm.bool_eq(zelf.name_object(), other.name_object())? && a.arg_count == b.arg_count && a.posonlyarg_count == b.posonlyarg_count && a.kwonlyarg_count == b.kwonlyarg_count @@ -951,7 +965,7 @@ impl Hashable for PyCode { let code = &zelf.code; // Hash a tuple of key attributes, matching CPython's code_hash let tuple = vm.ctx.new_tuple(vec![ - vm.ctx.new_str(code.obj_name.as_str()).into(), + zelf.name_object().to_owned(), vm.ctx.new_int(code.arg_count).into(), vm.ctx.new_int(code.posonlyarg_count).into(), vm.ctx.new_int(code.kwonlyarg_count).into(), @@ -1127,6 +1141,7 @@ impl Constructor for PyCode { }; let mut result = Self::new(code, &vm.ctx); + result.retain_name_subclass(args.name, &vm.ctx); result.linetable_bytes = args.linetable; result.exceptiontable_bytes = args.exceptiontable; Ok(result) @@ -1160,6 +1175,13 @@ impl Py { self.code.first_line_number.map_or(0, |n| n.get() as _) } + #[pygetset] + fn co_name(&self) -> PyStrRef { + self.name_owner + .clone() + .unwrap_or_else(|| self.code.obj_name.to_owned()) + } + #[pygetset] fn co_consts(&self) -> PyTupleRef { self.code @@ -1606,7 +1628,7 @@ impl Py { let obj_name = match co_name { OptionalArg::Present(obj_name) => obj_name, - OptionalArg::Missing => self.code.obj_name.to_owned(), + OptionalArg::Missing => self.co_name(), }; let names = match co_names { @@ -1735,6 +1757,7 @@ impl Py { }; let mut result = PyCode::new(new_code, &vm.ctx); + result.retain_name_subclass(obj_name, &vm.ctx); result.retain_normalized_names(names, None); result.linetable_bytes = linetable; result.exceptiontable_bytes = exceptiontable; diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index abb845f9d2c..9a68e778897 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -203,8 +203,14 @@ fn spec_format_string( spec: &CFormatSpec, obj: PyObjectRef, argument: FormatArgument<'_>, + format_start: usize, ) -> PyResult { match &spec.format_type { + CFormatType::Unsupported { ch, .. } if ch.to_char_lossy().is_ascii_alphabetic() => Err(vm + .new_value_error(format!( + "unsupported format %{} at position {format_start}", + ch.to_char_lossy(), + ))), CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!( "unsupported format character '{}' ({:#x}) at index {index}", ch.to_char_lossy(), @@ -245,15 +251,27 @@ fn spec_format_string( Ok(spec.format_number(i.as_bigint()).into()) } else { Err(vm.new_type_error(format!( - "%{} format: an integer is required, not {}", + "{}: %{} requires an integer, not {}", + argument.description(vm)?, spec.format_type.to_char(), - obj.class().name() + obj.class().slot_name() ))) } } }, CFormatType::Float(_) => { - let value = ArgIntoFloat::try_from_object(vm, obj)?; + let value = match ArgIntoFloat::try_from_object(vm, obj.clone()) { + Ok(value) => value, + Err(error) if error.fast_isinstance(vm.ctx.exceptions.type_error) => { + return Err(vm.new_type_error(format!( + "{}: %{} requires a real number, not {}", + argument.description(vm)?, + spec.format_type.to_char(), + obj.class().slot_name() + ))); + } + Err(error) => return Err(error), + }; Ok(spec.format_float(value.into()).into()) } CFormatType::Character(CCharacterType::Character) => { @@ -504,7 +522,7 @@ pub(crate) fn cformat_string( } // dict - for (_, part) in format { + for (format_start, part) in format { match part { CFormatPart::Literal(literal) => result.push_wtf8(&literal), CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => { @@ -516,6 +534,7 @@ pub(crate) fn cformat_string( &spec, value, FormatArgument::Mapping(key.as_object()), + format_start, )?; result.push_wtf8(&part_result); } @@ -540,7 +559,7 @@ pub(crate) fn cformat_string( let mut value_iter = value_iter.inspect(|_| argument_index.set(argument_index.get() + 1)); let is_tuple = values_obj.downcast_ref::().is_some(); - for (_, part) in format { + for (format_start, part) in format { match part { CFormatPart::Literal(literal) => result.push_wtf8(&literal), CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => { @@ -561,7 +580,8 @@ pub(crate) fn cformat_string( } else { FormatArgument::Scalar }; - let part_result = spec_format_string(vm, &spec, value.to_owned(), argument)?; + let part_result = + spec_format_string(vm, &spec, value.to_owned(), argument, format_start)?; result.push_wtf8(&part_result); } } diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index e369606f581..e99cb857472 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -336,22 +336,38 @@ fn update_registry( already_warned(registry, &altkey, true, vm) } -fn normalize_module(filename: &Py, vm: &VirtualMachine) -> PyObjectRef { - match filename.byte_len() { - 0 => vm.new_pyobj(""), - len if len >= 3 && filename.as_bytes().ends_with(b".py") => { - vm.new_pyobj(&filename.as_wtf8()[..len - 3]) - } - _ => filename.as_object().to_owned(), +fn check_module_matched( + pattern: &PyObject, + module: Option<&PyObject>, + filename: &Py, + vm: &VirtualMachine, +) -> PyResult { + if let Some(module) = module { + return check_matched(pattern, module, vm); + } + if vm.is_none(pattern) { + return Ok(true); + } + if pattern.class().is(vm.ctx.types.str_type) { + return Ok(false); } + let warnings = vm.import("_py_warnings", 0)?; + vm.call_method( + &warnings, + "_match_filename", + (pattern.to_owned(), filename.to_owned()), + )? + .is_true(vm) } /// Search a filters list for a matching action. +#[allow(clippy::too_many_arguments)] fn filter_search( category: &PyObject, text: &PyObject, lineno: usize, - module: &PyObject, + module: Option<&PyObject>, + filename: &Py, filters: &PyListRef, list_name: &str, vm: &VirtualMachine, @@ -380,7 +396,7 @@ fn filter_search( } let good_msg = check_matched(&tmp_item.as_slice()[1], text, vm)?; let is_subclass = category.is_subclass(&tmp_item.as_slice()[2], vm)?; - let good_mod = check_matched(&tmp_item.as_slice()[3], module, vm)?; + let good_mod = check_module_matched(&tmp_item.as_slice()[3], module, filename, vm)?; let ln: usize = tmp_item.as_slice()[4] .try_int(vm) .map_or(0, |v| v.as_u32_mask() as _); @@ -397,7 +413,8 @@ fn get_filter( category: &PyObject, text: &PyObject, lineno: usize, - module: &PyObject, + module: Option<&PyObject>, + filename: &Py, vm: &VirtualMachine, ) -> PyResult { if let Some(context_filters) = get_warnings_context_filters(vm)? { @@ -406,6 +423,7 @@ fn get_filter( text, lineno, module, + filename, &context_filters, "_warnings_context _filters", vm, @@ -416,7 +434,9 @@ fn get_filter( } let filters = get_warnings_filters(vm)?; - if let Some(action) = filter_search(category, text, lineno, module, &filters, "filters", vm)? { + if let Some(action) = filter_search( + category, text, lineno, module, filename, &filters, "filters", vm, + )? { return Ok(action); } get_default_action(vm) @@ -504,9 +524,9 @@ pub fn warn_explicit( source: Option, vm: &VirtualMachine, ) -> PyResult<()> { - // Normalize module. None → silent return (late-shutdown safety). - let module = module.unwrap_or_else(|| normalize_module(&filename, vm)); - if vm.is_none(&module) { + // Explicit None remains a late-shutdown silent return. An omitted module + // is matched against the filename by the canonical warnings helper. + if module.as_ref().is_some_and(|module| vm.is_none(module)) { return Ok(()); } @@ -543,7 +563,14 @@ pub fn warn_explicit( } // Get filter action - let action = get_filter(category.as_object(), text.as_object(), lineno, &module, vm)?; + let action = get_filter( + category.as_object(), + text.as_object(), + lineno, + module.as_deref(), + &filename, + vm, + )?; let action_str = PyStrRef::try_from_object(vm, action) .map_err(|_| vm.new_type_error("action must be a string"))?; From 2ab2473d02db179c6a433a5c90e7125e9226eb80 Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:48 +0900 Subject: [PATCH 05/12] Skip unavailable monitoring C API tests before tool registration Replay reviewed compiler CI repairs 246c309 on the frozen current prerequisite. Intermediate compile/diagnostic corrections are combined where they address the same failure. Assisted-by: OpenAI Codex: model identifier unavailable --- Lib/test/test_monitoring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index db4059b860c..97bb17f6778 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -2599,9 +2599,9 @@ def __exit__(self, *args): self._testcapi.monitoring_exit_scope() def setUp(self): + self._testcapi = import_helper.import_module("_testcapi") super(TestCApiEventGeneration, self).setUp() - self._testcapi = import_helper.import_module("_testcapi") capi = self._testcapi self.codelike = capi.CodeLike(2) From c659c27ca912a51301dc9fcb9d7301759152ef2c Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:49 +0900 Subject: [PATCH 06/12] Assert exact final constant-list iterable folding Replay reviewed compiler CI repairs 7ecedcf on the frozen current prerequisite. Intermediate compile/diagnostic corrections are combined where they address the same failure. Assisted-by: OpenAI Codex: model identifier unavailable --- crates/codegen/src/compile.rs | 46 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 06de2e9dfd1..fe2ed8b2db6 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -32613,7 +32613,7 @@ def f(g, kwargs, ns): } #[test] - fn large_constant_list_iterable_keeps_streaming_list_build() { + fn large_constant_list_iterable_folds_to_tuple_like_cpython() { let source = format!( "def f():\n for x in [{}]:\n pass\n", (0..=STACK_USE_GUIDELINE) @@ -32624,19 +32624,47 @@ def f(g, kwargs, ns): let code = compile_exec(&source); let f = find_code(&code, "f").expect("missing function code"); + // Final CPython folds the stack-limited BUILD_LIST/LIST_APPEND sequence + // before GET_ITER, preserving every constant in its original order. + let tuple_loads = f + .instructions + .iter() + .filter_map(|unit| match unit.op { + Instruction::LoadConst { consti } => { + match &f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))] { + ConstantData::Tuple { elements } => Some(elements), + _ => None, + } + } + _ => None, + }) + .collect::>(); + assert_eq!(tuple_loads.len(), 1); + let elements = tuple_loads[0]; + assert_eq!( + elements.len(), + usize::try_from(STACK_USE_GUIDELINE + 1).unwrap() + ); + for (i, element) in elements.iter().enumerate() { + assert!(matches!( + element, + ConstantData::Str { value } if value.to_string() == format!("v{i}") + )); + } assert!( - f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::BuildList { .. })), - "large list iterable should keep CPython streaming BUILD_LIST form, got instructions={:?}", + !f.instructions.iter().any(|unit| matches!( + unit.op, + Instruction::BuildList { .. } | Instruction::ListAppend { .. } + )), + "constant iterable should use the folded tuple, got instructions={:?}", f.instructions ); - assert!( + assert_eq!( f.instructions .iter() - .any(|unit| matches!(unit.op, Instruction::ListAppend { .. })), - "large list iterable should use LIST_APPEND streaming form, got instructions={:?}", - f.instructions + .filter(|unit| matches!(unit.op, Instruction::GetIter { .. })) + .count(), + 1 ); } From 2ae198d063ef28f530e6fc16f952481953e170ae Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 15:09:52 +0900 Subject: [PATCH 07/12] Align local monitoring events and exception lifecycle state with final 3.15 Replay reviewed compiler CI repairs ec206b1, 191fe58 on the frozen current prerequisite. Intermediate compile/diagnostic corrections are combined where they address the same failure. Assisted-by: OpenAI Codex: model identifier unavailable --- Lib/test/test_format.py | 2 +- Lib/test/test_warnings/__init__.py | 31 +++++++++++++++-------- crates/vm/src/cformat.rs | 14 ++++++++--- crates/vm/src/frame.rs | 3 +++ crates/vm/src/stdlib/sys/monitoring.rs | 34 +++++++++++++++++++------- 5 files changed, 61 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 1f626d87fa6..a4bf797aab1 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -626,7 +626,7 @@ def test_specifier_z_error(self): with self.assertRaisesRegex(ValueError, error_msg): f"{'x':zs}" # can't apply to string - error_msg = re.escape("unsupported format character 'z'") + error_msg = re.escape("unsupported format %z at position 0") with self.assertRaisesRegex(ValueError, error_msg): "%z.1f" % 0 # not allowed in old style string interpolation with self.assertRaisesRegex(ValueError, error_msg): diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index bbb64faa4a7..e7ca3e14158 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -250,10 +250,23 @@ def test_filter_module(self): self.module.warn_explicit('msg', UserWarning, 'filename', 42, module='package.module') self.assertEqual(len(w), 1) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module', 42) + self.assertEqual(len(w), 2) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module.py', 42) + self.assertEqual(len(w), 3) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module/__init__.py', 42) + self.assertEqual(len(w), 4) with self.assertRaises(UserWarning): - self.module.warn_explicit('msg', UserWarning, '/path/to/package/module', 42) - with self.assertRaises(UserWarning): - self.module.warn_explicit('msg', UserWarning, '/path/to/package/module.py', 42) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module/__init__', 42) + if MS_WINDOWS: + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42) + self.assertEqual(len(w), 5) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module\__INIT__.PY', 42) + self.assertEqual(len(w), 6) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PYW', 42) + self.assertEqual(len(w), 7) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module\__INIT__.PYW', 42) + self.assertEqual(len(w), 8) with self.module.catch_warnings(record=True) as w: self.module.simplefilter('error') @@ -277,9 +290,8 @@ def test_filter_module(self): with self.assertRaises(UserWarning): self.module.warn_explicit('msg', UserWarning, '/PATH/TO/PACKAGE/MODULE', 42) if MS_WINDOWS: - if self.module is py_warnings: - self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module.PY', 42) - self.assertEqual(len(w), 3) + self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module.PY', 42) + self.assertEqual(len(w), 3) with self.assertRaises(UserWarning): self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module/__init__.py', 42) with self.assertRaises(UserWarning): @@ -303,9 +315,8 @@ def test_filter_module(self): self.assertEqual(len(w), 1) self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.py', 42) self.assertEqual(len(w), 2) - if self.module is py_warnings: - self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42) - self.assertEqual(len(w), 3) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42) + self.assertEqual(len(w), 3) with self.assertRaises(UserWarning): self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.pyw', 42) with self.assertRaises(UserWarning): @@ -400,7 +411,7 @@ def test_message_matching(self): def test_mutate_filter_list(self): class X: - def match(self, a): + def match(self, a, start=0): L[:] = [] L = [("default",X(),UserWarning,X(),0) for i in range(2)] diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index 9a68e778897..ae359250b6c 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -73,8 +73,14 @@ fn spec_format_bytes( spec: &CFormatSpec, obj: PyObjectRef, argument: FormatArgument<'_>, + format_start: usize, ) -> PyResult> { match &spec.format_type { + CFormatType::Unsupported { ch, .. } if ch.to_char_lossy().is_ascii_alphabetic() => Err(vm + .new_value_error(format!( + "unsupported format %{} at position {format_start}", + ch.to_char_lossy(), + ))), CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!( "unsupported format character '{}' ({:#x}) at index {index}", ch.to_char_lossy(), @@ -407,7 +413,7 @@ pub(crate) fn cformat_bytes( } // dict - for (_, part) in format { + for (format_start, part) in format { match part { CFormatPart::Literal(literal) => result.extend(literal), CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => { @@ -419,6 +425,7 @@ pub(crate) fn cformat_bytes( &spec, value, FormatArgument::Mapping(key.as_object()), + format_start, )?; result.extend(part_result); } @@ -443,7 +450,7 @@ pub(crate) fn cformat_bytes( let mut value_iter = value_iter.inspect(|_| argument_index.set(argument_index.get() + 1)); let is_tuple = values_obj.downcast_ref::().is_some(); - for (_, part) in format { + for (format_start, part) in format { match part { CFormatPart::Literal(literal) => result.extend(literal), CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => { @@ -464,7 +471,8 @@ pub(crate) fn cformat_bytes( } else { FormatArgument::Scalar }; - let part_result = spec_format_bytes(vm, &spec, value.to_owned(), argument)?; + let part_result = + spec_format_bytes(vm, &spec, value.to_owned(), argument, format_start)?; result.extend(part_result); } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 699235aa9b9..2e5c07c15a5 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -4171,6 +4171,7 @@ impl ExecutingFrame<'_> { Ok(None) => next_idx = lasti_cell.load(Relaxed), Ok(Some(result)) => break Ok(Some(result)), Err(exception) => { + monitoring::reset_reraise_pending(); // Fire PY_UNWIND: exception escapes this frame let exception = if vm.state.monitoring_events.load() & MonitoringEvent::PyUnwind.mask() @@ -4454,6 +4455,7 @@ impl ExecutingFrame<'_> { } Ok(Some(result)) => Ok(result), Err(exception) => { + monitoring::reset_reraise_pending(); // Fire PY_UNWIND: exception escapes the generator frame. let exception = if vm.state.monitoring_events.load() & MonitoringEvent::PyUnwind.mask() != 0 { @@ -8922,6 +8924,7 @@ impl ExecutingFrame<'_> { if let Some(entry) = bytecode::find_exception_handler(&self.code.exceptiontable, offset) { + monitoring::reset_reraise_pending(); // Fire EXCEPTION_HANDLED before setting up handler. // If the callback raises, the handler is NOT set up and the // new exception propagates instead. diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index 930bef9cd29..7d1aa61d5f5 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -9,7 +9,8 @@ use std::collections::{HashMap, HashSet}; pub(crate) const TOOL_LIMIT: usize = 6; const EVENTS_COUNT: usize = 19; -const LOCAL_EVENTS_COUNT: usize = 11; +const INSTRUMENTED_EVENTS_COUNT: usize = 11; +const LOCAL_EVENTS_COUNT: usize = 16; const UNGROUPED_EVENTS_COUNT: usize = 18; /// Event identifier (`PY_MONITORING_EVENT_*`), stored in `tstate->what_event`. @@ -905,10 +906,21 @@ fn fire( for (tool, cb) in callbacks { let result = cb.call(args.clone(), vm)?; if disable_sentinel.as_ref().is_some_and(|d| result.is(d)) { - // Only local events (event_id < LOCAL_EVENTS_COUNT) can be disabled. - // Non-local events (RAISE, EXCEPTION_HANDLED, PY_UNWIND, etc.) - // cannot be disabled per code object. - if event_id >= LOCAL_EVENTS_COUNT { + if event_id < INSTRUMENTED_EVENTS_COUNT { + let mut state = vm.state.monitoring.lock(); + state.disabled.insert((code_id, offset as usize, tool)); + } else if event_id < LOCAL_EVENTS_COUNT { + // Non-instrumented events disable their local registration + // for this code object, leaving global events active. + let mut state = vm.state.monitoring.lock(); + if let Some(events) = state.local_events.get_mut(&(tool, code_id)) { + *events &= !event_mask; + if *events == 0 { + state.local_events.remove(&(tool, code_id)); + } + } + update_events_mask(vm, &state); + } else { // Remove the callback. let mut state = vm.state.monitoring.lock(); state.callbacks.remove(&(tool, event_id)); @@ -917,8 +929,6 @@ fn fire( EVENT_NAMES[event_id] ))); } - let mut state = vm.state.monitoring.lock(); - state.disabled.insert((code_id, offset as usize, tool)); } } Ok(()) @@ -1073,6 +1083,12 @@ pub(crate) fn fire_raise( ) } +/// End suppression when the exception reaches a handler or escapes a frame, +/// independently of whether either lifecycle event has a registered callback. +pub(crate) fn reset_reraise_pending() { + RERAISE_PENDING.with(|f| f.set(false)); +} + /// Only fires if no RERAISE has been fired since the last EXCEPTION_HANDLED, /// preventing duplicate events from chained cleanup handlers. pub(crate) fn fire_reraise( @@ -1104,7 +1120,7 @@ pub(crate) fn fire_exception_handled( offset: u32, exception: &PyObject, ) -> PyResult<()> { - RERAISE_PENDING.with(|f| f.set(false)); + reset_reraise_pending(); fire( vm, MonitoringEvent::ExceptionHandled, @@ -1120,7 +1136,7 @@ pub(crate) fn fire_py_unwind( offset: u32, exception: &PyObject, ) -> PyResult<()> { - RERAISE_PENDING.with(|f| f.set(false)); + reset_reraise_pending(); fire( vm, MonitoringEvent::PyUnwind, From a3d7a54148bcf1c77259f2733382410600883272 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 14 Oct 2026 00:08:27 +0900 Subject: [PATCH 08/12] Align 3.15 code objects and codegen with CPython - Store co_consts, co_names, co_localsplusnames, co_localspluskinds, co_linetable, co_exceptiontable and co_name as objects on PyCode; build co_code and the varnames/cellvars/freevars tuples lazily in _co_cached, with co_code deoptimized through deopt_code. - Move the compiler constant cache (_PyCode_ConstantKey, const_cache_insert) to builtins/code/const_cache.rs. - Write every code field with w_object in marshal and pass the read field objects to MarshalBag::make_code through CodeFields. - Emit RETURN_GENERATOR/POP_TOP in enter_scope and place SETUP_CLEANUP after them; drop the generator part of insert_prefix_instructions. - Add PyCode::first_traceable (_co_firsttraceable) for _PyFrame_IsIncomplete and sys.monitoring. - Follow the CPython DISABLE branches in sys.monitoring, the check_matched signature in warnings, the 3.15 % format error messages for str and bytes, code_returns_only_none, and the FOR_ITER stack analysis. - Compute IMPORT_NAME flags with in_exception_handler. - Replace the linetable/exceptiontable byte tests in codegen with dis(show_positions=True) insta snapshots in _opcode. - Remove the opcode metadata verification tooling, byte-exact scripts, private cache tests and redundant snippets. - Restore test_code.py and test_monitoring.py to v3.15.0 plus RUSTPYTHON markers. Assisted-by: Claude Code:claude-opus-5-5 --- .github/workflows/ci.yaml | 3 +- .pre-commit-config.yaml | 21 +- Lib/_opcode_metadata.py | 5 +- Lib/test/test_code.py | 4 - Lib/test/test_monitoring.py | 3 +- crates/codegen/src/compile.rs | 1560 +------- crates/codegen/src/ir.rs | 263 +- crates/common/src/cformat.rs | 35 +- crates/compiler-core/src/bytecode.rs | 116 +- .../compiler-core/src/bytecode/instruction.rs | 98 +- crates/compiler-core/src/bytecode/oparg.rs | 12 +- .../src/bytecode/opcode_metadata.rs | 63 +- .../src/bytecode/public_metadata.rs | 496 --- .../src/bytecode/target_stack_cases.rs | 3433 ----------------- crates/compiler-core/src/marshal.rs | 384 +- crates/jit/src/instructions.rs | 43 - crates/stdlib/src/_opcode.rs | 818 +++- ...__tests__adjacent_no_location_entries.snap | 109 + ...otated_multiline_function_firstlineno.snap | 39 + ...s__annotation_scope_function_location.snap | 44 + ..._tests__assert_false_message_location.snap | 19 + ...code__tests__async_for_anext_location.snap | 57 + ...pcode__tests__async_for_end_async_for.snap | 48 + ...ts__augassign_constant_slice_location.snap | 27 + ...code__tests__builtin_any_genexpr_call.snap | 70 + ..._builtin_tuple_list_set_genexpr_calls.snap | 182 + ...__class_deferred_annotations_location.snap | 65 + ...tests__comprehension_collection_depth.snap | 110 + ..._comprehension_list_iterable_location.snap | 83 + ...tests__decorated_definition_locations.snap | 164 + ...empty_module_implicit_return_location.snap | 7 + ...pcode__tests__fstring_format_location.snap | 32 + ...ts__future_annotation_string_location.snap | 41 + ...eric_class_type_params_store_location.snap | 64 + ...ic_function_annotation_scope_location.snap | 48 + ...ric_typealias_wrapper_return_location.snap | 48 + ...ts__genexpr_closure_and_call_location.snap | 48 + ...ests__genexpr_filter_cleanup_location.snap | 104 + ...tdlib___opcode__tests__genexpr_prefix.snap | 103 + ...l_genexpr_parenthesized_element_range.snap | 216 ++ ...e__tests__implicit_call_genexpr_range.snap | 131 + ..._stdlib___opcode__tests__import_flags.snap | 193 + ..._comprehension_end_async_for_location.snap | 97 + ...lined_comprehension_backedge_location.snap | 92 + ...ests__joined_string_literals_location.snap | 24 + ...__tests__lambda_dict_literal_location.snap | 29 + ...__tests__list_set_async_genexpr_calls.snap | 129 + ...___opcode__tests__loop_return_cleanup.snap | 74 + ...sts__match_class_scaffolding_location.snap | 39 + ...e__tests__match_mapping_keys_location.snap | 60 + ...tests__match_or_alternative_locations.snap | 56 + ...e__tests__match_success_jump_location.snap | 44 + ..._module_deferred_annotations_location.snap | 63 + ...ode__tests__module_docstring_location.snap | 13 + ...pcode__tests__namedexpr_copy_location.snap | 40 + ...__tests__not_chained_compare_location.snap | 35 + ...__opcode__tests__not_compare_location.snap | 19 + ...opcode__tests__redundant_nop_location.snap | 65 + ...tarred_arg_annotation_unpack_location.snap | 34 + ..._static_swap_implicit_return_location.snap | 22 + ...tests__taken_constant_boolop_location.snap | 27 + ...inally_exception_scaffolding_location.snap | 46 + ...de__tests__type_param_scope_locations.snap | 66 + ...ests__with_cleanup_excludes_not_taken.snap | 62 + ...__opcode__tests__yield_from_and_await.snap | 66 + crates/vm/src/builtins/code.rs | 561 ++- crates/vm/src/builtins/code/compile_cache.rs | 538 --- crates/vm/src/builtins/code/const_cache.rs | 147 + crates/vm/src/builtins/frame.rs | 14 +- crates/vm/src/cformat.rs | 125 +- crates/vm/src/frame.rs | 87 +- crates/vm/src/stdlib/marshal.rs | 125 +- crates/vm/src/stdlib/sys.rs | 2 +- crates/vm/src/stdlib/sys/monitoring.rs | 53 +- crates/vm/src/version.rs | 8 +- crates/vm/src/vm/crossinterp.rs | 38 +- crates/vm/src/vm/mod.rs | 7 +- crates/vm/src/warn.rs | 59 +- .../vm/tests/fixtures/private_cache_bytes.rs | 9 - crates/vm/tests/private_cache_reader.rs | 38 - .../snippets/adaptive_public_export_315.py | 101 - .../snippets/canonical_code_storage_315.py | 216 -- .../snippets/canonical_code_writer_315.py | 118 - .../snippets/compiled_constant_sharing_315.py | 160 - .../snippets/compiler_315_warmed_export.py | 70 - ...ct_315.py => generator_iterator_prefix.py} | 0 .../snippets/marshal_actual_owners_315.py | 136 - extra_tests/snippets/opcode_calling_315.py | 179 - .../snippets/private_315_cache_contract.py | 111 - .../snippets/public_opcode_metadata_315.py | 567 --- .../snippets/stable_code_fields_315.py | 154 - scripts/byte_exact_capture.py | 101 - scripts/byte_exact_compile.py | 174 - scripts/byte_exact_fixtures.json | 242 -- scripts/test_byte_exact_compile.py | 112 - .../opcode_metadata/CANONICAL_CODE_STORAGE.md | 37 - .../COMPILER_315_DEVELOPMENT.md | 110 - tools/opcode_metadata/STABLE_CODE_FIELDS.md | 132 - tools/opcode_metadata/conf.toml | 4 - tools/opcode_metadata/cpython.py | 13 +- .../generate_public_metadata.py | 188 - .../generate_py_opcode_metadata.py | 102 +- tools/opcode_metadata/opcodes.py | 8 +- tools/opcode_metadata/stack_contract.c | 18 - tools/opcode_metadata/target_source.json | 48 - tools/opcode_metadata/target_source.py | 36 - tools/opcode_metadata/verify_opcode_ids.py | 44 - .../verify_py_opcode_metadata.py | 24 - .../opcode_metadata/verify_stack_contract.py | 51 - 109 files changed, 4949 insertions(+), 10729 deletions(-) delete mode 100644 crates/compiler-core/src/bytecode/public_metadata.rs delete mode 100644 crates/compiler-core/src/bytecode/target_stack_cases.rs create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__adjacent_no_location_entries.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotated_multiline_function_firstlineno.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotation_scope_function_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__assert_false_message_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_anext_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_end_async_for.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__augassign_constant_slice_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_any_genexpr_call.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_tuple_list_set_genexpr_calls.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__class_deferred_annotations_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_collection_depth.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_list_iterable_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__decorated_definition_locations.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__empty_module_implicit_return_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__fstring_format_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__future_annotation_string_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_class_type_params_store_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_function_annotation_scope_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_typealias_wrapper_return_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_closure_and_call_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_filter_cleanup_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_prefix.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_parenthesized_element_range.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_range.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__import_flags.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_async_comprehension_end_async_for_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_comprehension_backedge_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__joined_string_literals_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__lambda_dict_literal_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__list_set_async_genexpr_calls.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__loop_return_cleanup.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_class_scaffolding_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_mapping_keys_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_or_alternative_locations.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_success_jump_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_deferred_annotations_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_docstring_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__namedexpr_copy_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_chained_compare_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_compare_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__redundant_nop_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__starred_arg_annotation_unpack_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__static_swap_implicit_return_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__taken_constant_boolop_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__try_finally_exception_scaffolding_location.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__type_param_scope_locations.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__with_cleanup_excludes_not_taken.snap create mode 100644 crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__yield_from_and_await.snap delete mode 100644 crates/vm/src/builtins/code/compile_cache.rs create mode 100644 crates/vm/src/builtins/code/const_cache.rs delete mode 100644 crates/vm/tests/fixtures/private_cache_bytes.rs delete mode 100644 crates/vm/tests/private_cache_reader.rs delete mode 100644 extra_tests/snippets/adaptive_public_export_315.py delete mode 100644 extra_tests/snippets/canonical_code_storage_315.py delete mode 100644 extra_tests/snippets/canonical_code_writer_315.py delete mode 100644 extra_tests/snippets/compiled_constant_sharing_315.py delete mode 100644 extra_tests/snippets/compiler_315_warmed_export.py rename extra_tests/snippets/{iterator_contract_315.py => generator_iterator_prefix.py} (100%) delete mode 100644 extra_tests/snippets/marshal_actual_owners_315.py delete mode 100644 extra_tests/snippets/opcode_calling_315.py delete mode 100644 extra_tests/snippets/private_315_cache_contract.py delete mode 100644 extra_tests/snippets/public_opcode_metadata_315.py delete mode 100644 extra_tests/snippets/stable_code_fields_315.py delete mode 100644 scripts/byte_exact_capture.py delete mode 100644 scripts/byte_exact_compile.py delete mode 100644 scripts/byte_exact_fixtures.json delete mode 100644 scripts/test_byte_exact_compile.py delete mode 100644 tools/opcode_metadata/CANONICAL_CODE_STORAGE.md delete mode 100644 tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md delete mode 100644 tools/opcode_metadata/STABLE_CODE_FIELDS.md delete mode 100644 tools/opcode_metadata/generate_public_metadata.py delete mode 100644 tools/opcode_metadata/stack_contract.c delete mode 100644 tools/opcode_metadata/target_source.json delete mode 100644 tools/opcode_metadata/target_source.py delete mode 100644 tools/opcode_metadata/verify_opcode_ids.py delete mode 100644 tools/opcode_metadata/verify_py_opcode_metadata.py delete mode 100644 tools/opcode_metadata/verify_stack_contract.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d33eb8cdf51..0dfd1f0e32f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -790,8 +790,7 @@ jobs: with: repository: python/cpython path: cpython - # Exact final source shared by all opcode metadata checks. - ref: "d220409a13b77de7a6c30abe16ea90db01528edc" # v3.15.0 + ref: "v3.15.0" persist-credentials: false - name: prek run (manual stage) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 633ebc67eef..a78da8d2e6a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: - id: generate-rs-opcode-metadata name: generate rust opcode metadata entry: python3 tools/opcode_metadata/generate_rs_opcode_metadata.py - files: '^(crates/compiler-core/src/bytecode/(instruction|opcode_metadata)\.rs|tools/opcode_metadata/.*)$' + files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$' pass_filenames: false language: system require_serial: true @@ -51,10 +51,10 @@ repos: stages: - manual - - id: verify-py-opcode-metadata - name: verify canonical python opcode metadata - entry: python3 tools/opcode_metadata/verify_py_opcode_metadata.py - files: '^(Lib/(opcode|_opcode_metadata|dis)\.py|tools/opcode_metadata/.*)$' + - id: generate-py-opcode-metadata + name: generate python opcode metadata + entry: python3 tools/opcode_metadata/generate_py_opcode_metadata.py + files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$' pass_filenames: false language: system require_serial: true @@ -62,17 +62,6 @@ repos: stages: - manual - - id: check-public-opcode-metadata - name: check public opcode metadata - entry: python3 tools/opcode_metadata/generate_public_metadata.py --check - files: '^(crates/compiler-core/src/bytecode/public_metadata\.rs|tools/opcode_metadata/.*)$' - pass_filenames: false - language: system - require_serial: true - priority: 1 - stages: - - manual - - repo: https://github.com/streetsidesoftware/cspell-cli rev: v10.3.0 hooks: diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 183d0af30ac..b81115a0c16 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -1,6 +1,5 @@ -# This file is generated by Tools/cases_generator/py_metadata_generator.py -# from: -# Python/bytecodes.c +# This file is generated by tools/opcode_metadata/generate_py_opcode_metadata.py +# for RustPython bytecode format. # Do not edit! _specializations = frozendict( RESUME=( diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 77193be1263..0af191cb5df 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -1170,10 +1170,6 @@ def __eq__(self, _): with self.assertRaises(RuntimeError): c2 == c3 - @unittest.expectedFailure # TODO: RUSTPYTHON; co_lnotab intentionally not implemented (deprecated since 3.12) - def test_co_lnotab_is_deprecated(self): - return super().test_co_lnotab_is_deprecated() - def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 97bb17f6778..006480abe03 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -2585,6 +2585,7 @@ def test_monitoring_live_at_shutdown(self): script_helper.run_test_script(script) +@unittest.skip("TODO: RUSTPYTHON; no _testcapi, and setUp leaves the tool ids in use when its import fails") class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase): class Scope: @@ -2599,9 +2600,9 @@ def __exit__(self, *args): self._testcapi.monitoring_exit_scope() def setUp(self): - self._testcapi = import_helper.import_module("_testcapi") super(TestCApiEventGeneration, self).setUp() + self._testcapi = import_helper.import_module("_testcapi") capi = self._testcapi self.codelike = capi.CodeLike(2) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index fe2ed8b2db6..1de20568f08 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -1952,6 +1952,8 @@ impl<'warnings> Compiler<'warnings> { } }; + let is_generator = ste.is_coroutine || ste.is_generator; + // Use varnames from symbol table (already collected in definition order) let varname_cache: IndexSet = ste.varnames.iter().cloned().collect(); let nparams = ste.varnames.len(); @@ -2153,6 +2155,27 @@ impl<'warnings> Compiler<'warnings> { self.set_qualname(); } + // Add the generator prefix instructions. + if is_generator { + // RETURN_GENERATOR + POP_TOP have a net stack effect of 0, because + // RETURN_GENERATOR pushes the generator before returning. + let location = SourceLocation { + line: OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN), + character_offset: OneIndexed::MIN, + }; + for instr in [Instruction::ReturnGenerator, Instruction::PopTop] { + self.cpython_cfg_builder_addop(ir::InstructionInfo { + instr: instr.into(), + arg: OpArg::NULL, + target: BlockIdx::NULL, + location, + end_location: location, + except_handler: None, + lineno_override: Some(ir::LINE_ONLY_LOCATION_OVERRIDE), + }); + } + } + // Emit RESUME (handles async preamble and module lineno 0) // CPython: LOCATION(lineno, lineno, 0, 0), then loc.lineno = 0 for module self.emit_resume_for_scope(scope_type, lineno); @@ -3527,12 +3550,9 @@ impl<'warnings> Compiler<'warnings> { Ok(()) } - /// Ordinary imports reserve two low bits for the import policy. - fn ordinary_import_flags(&mut self, star: bool) -> u32 { - if star || self.current_symbol_table().typ != CompilerScope::Module { - return 2; - } - if self.current_code_info().fblock.iter().any(|block| { + /// `_PyCompile_InExceptionHandler` + fn in_exception_handler(&mut self) -> bool { + self.current_code_info().fblock.iter().any(|block| { matches!( block.fb_type, FBlockType::TryExcept @@ -3542,11 +3562,7 @@ impl<'warnings> Compiler<'warnings> { | FBlockType::ExceptionGroupHandler | FBlockType::HandlerCleanup ) - }) { - 2 - } else { - 0 - } + }) } fn compile_statement(&mut self, statement: &ast::Stmt) -> CompileResult<()> { @@ -3577,7 +3593,15 @@ impl<'warnings> Compiler<'warnings> { value: num_traits::Zero::zero(), }); self.emit_load_const(ConstantData::None); - let namei = (self.name(&name.name) << 2) | self.ordinary_import_flags(false); + // force eager import in try/except block + let low = if self.in_exception_handler() + || self.current_symbol_table().typ != CompilerScope::Module + { + 2 + } else { + 0 + }; + let namei = (self.name(&name.name) << 2) | low; emit!(self, Instruction::ImportName { namei }); if let Some(alias) = &name.asname { let parts: Vec<&str> = name.name.split('.').skip(1).collect(); @@ -3622,8 +3646,16 @@ impl<'warnings> Compiler<'warnings> { }); let module_name = module.as_ref().map_or("", |s| s.as_str()); - let module_idx = - (self.name(module_name) << 2) | self.ordinary_import_flags(import_star); + // forced non-lazy import due to try/except or import * + let low = if self.in_exception_handler() + || self.current_symbol_table().typ != CompilerScope::Module + || import_star + { + 2 + } else { + 0 + }; + let module_idx = (self.name(module_name) << 2) | low; emit!(self, Instruction::ImportName { namei: module_idx }); if import_star { @@ -9100,7 +9132,12 @@ impl<'warnings> Compiler<'warnings> { self.mark_generator(); self.compile_expression(value)?; self.set_source_range(range); - emit!(self, Instruction::GetIter { mode: 1 }); + emit!( + self, + Instruction::GetIter { + mode: oparg::GET_ITER_YIELD_FROM + } + ); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(false); } @@ -11581,41 +11618,11 @@ impl<'warnings> Compiler<'warnings> { self.set_no_location(); } - /// Insert StopIteration cleanup before the ordinary function-start RESUME. - /// Generator creation prefixes are added before this protected body, either - /// by insert_genexpr_prefix() or during locals-plus preparation. + /// The SETUP_CLEANUP part of `codegen_wrap_in_stopiteration_handler`. fn insert_cpython_stopiteration_setup_cleanup(&mut self, handler_block: BlockIdx) { - let code = self.current_code_info(); - let entry = code - .blocks - .first_mut() - .expect("code unit must have an entry block"); - debug_assert!( - entry - .used_instructions() - .first() - .is_some_and(|info| match info.instr.real() { - Some(Instruction::Resume { context }) => matches!( - context.get(info.arg).location(), - oparg::ResumeLocation::AtFuncStart - ), - _ => false, - }), - "scope entry must start with a function-start RESUME" - ); - debug_assert!( - !entry.used_instructions().iter().any(|info| matches!( - info.instr.real(), - Some( - Instruction::ReturnGenerator - | Instruction::MakeCell { .. } - | Instruction::CopyFreeVars { .. } - ) - )), - "CPython inserts StopIteration cleanup before CFG prefix instructions" - ); - - let result = code.insert_start_setup_cleanup(handler_block); + let result = self + .current_code_info() + .insert_start_setup_cleanup(handler_block); unwrap_internal(self, result); } @@ -14724,66 +14731,6 @@ match x: ); } - #[test] - fn empty_module_implicit_return_inherits_resume_location_like_cpython() { - let code = compile_exec(""); - // codegen emits the implicit LOAD_CONST/RETURN_VALUE with - // NO_LOCATION, then flowgraph.c::propagate_line_numbers() propagates - // the module RESUME location, whose line is 0. - assert_eq!(code.linetable.as_ref(), &[0xf3, 0x03, 0x01, 0x01, 0x01]); - } - - #[test] - fn module_docstring_load_uses_doc_location_like_cpython() { - let code = compile_exec( - "\ -\"doc\" -x = 1 -", - ); - - // codegen_body() emits the docstring LOAD_CONST at the - // string expression location, then emits STORE_NAME __doc__ with - // NO_LOCATION. - assert_eq!( - code.linetable.as_ref(), - &[ - 0xf1, 0x03, 0x01, 0x01, 0x01, 0xd9, 0x00, 0x05, 0xd8, 0x04, 0x05, 0x82, 0x01 - ], - ); - } - - #[test] - fn redundant_nop_location_copies_full_location_like_cpython() { - let code = compile_exec( - "\ -def f(x, y, z): - while x: - if y: - pass - elif z: - if y < 0: - return y - if z: - y = y + 1 - elif y: - return 1 - return -1 -", - ); - let f = find_code(&code, "f").expect("missing function code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdf, 0x0a, 0x0b, 0xdf, 0x0b, 0x0c, 0xd9, 0x0c, 0x10, 0xdf, 0x0d, 0x0e, - 0xd8, 0x0f, 0x10, 0x90, 0x31, 0x8c, 0x75, 0xd8, 0x17, 0x18, 0x90, 0x08, 0xdf, 0x0f, - 0x10, 0xd8, 0x14, 0x15, 0x98, 0x01, 0x95, 0x45, 0x92, 0x01, 0xf1, 0x03, 0x00, 0x10, - 0x11, 0xe7, 0x0d, 0x0e, 0x89, 0x51, 0xd9, 0x13, 0x14, 0xd8, 0x0b, 0x0d, 0x80, 0x49 - ], - "CPython basicblock_remove_redundant_nops() copies the full NOP location into a following no-location jump" - ); - } - fn scan_program_symbol_table(source: &str) -> SymbolTable { let source_file = SourceFileBuilder::new("source_path", source).finish(); let parsed = ruff_python_parser::parse( @@ -16692,86 +16639,6 @@ def f(buffer, pos, last_char): }) } - #[test] - fn annotated_multiline_function_body_keeps_def_firstlineno_like_cpython() { - let code = compile_exec( - r#" -a = 1 -def f( - x: a, -): ... -"#, - ); - let f = find_code(&code, "f").expect("missing f code"); - // CPython 3.14 codegen_function() computes firstlineno from the - // FunctionDef before compiling annotations, then passes it to - // codegen_function_body(). - assert_eq!(f.linetable.as_ref(), &[0x81, 0x00, 0xe1, 0x03, 0x06]); - } - - #[test] - fn annotation_scope_return_uses_function_location_like_cpython() { - let code = compile_exec( - r#" -def g(): - def f(x: not (int is int), /): ... -"#, - ); - let g = find_code(&code, "g").expect("missing g code"); - let annotate = find_code(g, "__annotate__").expect("missing annotation code"); - // CPython 3.14 codegen_function_annotations() receives LOC(function) - // and uses it for the annotation closure's BUILD_MAP/RETURN_VALUE and - // for the parent MAKE_FUNCTION annotate sequence. - assert_eq!(g.linetable.as_ref(), &[0x81, 0x00, 0xdf, 0x04, 0x26]); - assert_eq!( - annotate.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd7, 0x04, 0x26, 0xd1, 0x04, 0x26, 0x94, 0x23, 0x9c, 0x13, 0xd0, 0x0d, - 0x1d, 0xd1, 0x04, 0x26 - ], - ); - } - - #[test] - fn starred_arg_annotation_unpack_uses_function_location_like_cpython() { - let code = compile_exec("def f(*args: *Ts): pass\n"); - let annotate = find_code(&code, "__annotate__").expect("missing annotation code"); - - // codegen_argannotation() visits `Ts` at the annotation - // expression location, then emits UNPACK_SEQUENCE at LOC(function). - assert_eq!( - annotate.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd7, 0x00, 0x17, 0xd1, 0x00, 0x17, 0x8c, 0x62, 0xd3, 0x00, 0x17 - ], - ); - } - - #[test] - fn module_deferred_annotations_use_start_location_like_cpython() { - let code = compile_exec( - "\ -import os -X: int -Y: str -", - ); - let annotate = find_code(&code, "__annotate__").expect("missing __annotate__ code"); - - // compile.c::start_location() passes the first module - // statement location into _PyCodegen_Module(), and - // codegen_process_deferred_annotations() uses that loc for annotation - // scope setup, BUILD_MAP, STORE_SUBSCR, and RETURN_VALUE. - assert_eq!( - annotate.linetable.as_ref(), - &[ - 0x81, 0x00, 0x87, 0x09, 0x81, 0x09, 0xdf, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, - 0x06, 0xf1, 0x03, 0x00, 0x01, 0x0a, 0xe7, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, - 0x06, 0xf2, 0x05, 0x00, 0x01, 0x0a - ] - ); - } - #[test] fn super_method_call_kw_names_use_attribute_location_like_cpython() { let code = compile_exec( @@ -17022,83 +16889,6 @@ def continues(xs): } } - #[test] - fn not_compare_uses_unary_location_like_cpython() { - let code = compile_exec( - "\ -def f(self, other): - return not self == other -", - ); - let f = find_code(&code, "f").expect("missing f code"); - - // CPython 3.14 parses the Compare inside UnaryOp(Not) with the - // UnaryOp start location, so codegen_compare() emits COMPARE_OP at - // the full "not self == other" range before flowgraph folds TO_BOOL. - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x0f, 0x13, 0xd2, 0x0b, 0x1c, 0xd0, 0x04, 0x1c - ] - ); - } - - #[test] - fn not_chained_compare_keeps_compare_location_like_cpython() { - let code = compile_exec( - "\ -def f(c): - return not (b\" \" <= c <= b\"~\") -", - ); - let f = find_code(&code, "f").expect("missing f code"); - - // CPython's single Compare under UnaryOp(Not) includes "not" in the - // Compare range, but chained comparisons keep their inner range for - // compare scaffolding and only use the UnaryOp range for TO_BOOL and - // UNARY_NOT. - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x10, 0x14, 0x98, 0x01, 0xd7, 0x10, 0x21, 0xd4, 0x10, 0x21, 0x98, - 0x54, 0xd1, 0x10, 0x21, 0xd4, 0x0b, 0x22, 0xd0, 0x04, 0x22, 0xd1, 0x10, 0x21, 0xd4, - 0x0b, 0x22, 0xd0, 0x04, 0x22 - ] - ); - } - - #[test] - fn type_param_scopes_use_cpython_locations() { - let code = compile_exec("type BoundGenericAlias[X: int] = set[X]\n"); - let type_params = find_code(&code, "") - .expect("missing generic parameters code"); - let bound = find_direct_child_code(type_params, "X").expect("missing X bound code"); - let alias = - find_direct_child_code(type_params, "BoundGenericAlias").expect("missing alias code"); - - // CPython 3.14 codegen_type_params() emits type-parameter ops at - // LOC(typeparam), bound/default evaluator ops at LOC(e), and type alias - // body plumbing at LOC(s). - assert_eq!( - type_params.linetable.as_ref(), - &[ - 0xf8, 0x81, 0x00, 0xd0, 0x00, 0x27, 0x90, 0x76, 0x9b, 0x23, 0x93, 0x76, 0xd7, 0x00, - 0x27, 0xd1, 0x00, 0x27 - ], - ); - assert_eq!( - bound.linetable.as_ref(), - &[0x81, 0x00, 0x9f, 0x23, 0x9e, 0x23] - ); - assert_eq!( - alias.linetable.as_ref(), - &[ - 0xf8, 0x81, 0x00, 0xd7, 0x00, 0x27, 0xd0, 0x00, 0x27, 0xa4, 0x13, 0xa0, 0x51, 0xa5, - 0x16, 0xd0, 0x00, 0x27 - ], - ); - } - #[test] fn typealias_value_scope_has_single_return_like_cpython() { let code = compile_exec("type Alias = int\n"); @@ -17115,23 +16905,6 @@ def f(c): ); } - #[test] - fn generic_typealias_wrapper_return_uses_alias_location_like_cpython() { - let code = compile_exec("type A[T] = T\n"); - let type_params = - find_code(&code, "").expect("missing type params code"); - - // codegen_typealias() assembles the generic-parameters - // wrapper with addNone=0 after codegen_typealias_body() leaves the type - // alias object on the stack. The final RETURN_VALUE keeps LOC(type alias). - assert_eq!( - type_params.linetable.as_ref(), - &[ - 0xf8, 0x81, 0x00, 0x80, 0x0d, 0x84, 0x71, 0x87, 0x0d, 0x81, 0x0d - ], - ); - } - #[test] fn type_param_bound_scope_has_single_return_like_cpython() { let code = compile_exec("type Alias[T: int] = T\n"); @@ -17166,25 +16939,6 @@ def f(c): ); } - #[test] - fn generic_function_annotation_scope_uses_function_location_like_cpython() { - let code = compile_exec("def f[T](x: int): ...\n"); - let type_params = - find_code(&code, "").expect("missing type params code"); - let annotate = - find_direct_child_code(type_params, "__annotate__").expect("missing annotation code"); - - // CPython 3.14 passes LOC(function) into codegen_function_annotations(), - // even when the annotation closure is emitted inside the generic - // parameters scope after codegen_type_params(). - assert_eq!( - annotate.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd7, 0x00, 0x15, 0xd1, 0x00, 0x15, 0x8c, 0x43, 0xd1, 0x00, 0x15 - ], - ); - } - #[test] fn decorated_generic_function_type_params_use_decorator_firstlineno_like_cpython() { let code = compile_exec( @@ -17202,27 +16956,6 @@ def f[T](): pass assert_eq!(type_params.first_line_number.unwrap().get(), 2); } - #[test] - fn generic_class_type_params_store_uses_class_location_like_cpython() { - let code = compile_exec( - "\ -def outer(): - class X[T]: ... -", - ); - let type_params = - find_code(&code, "").expect("missing type params code"); - - // CPython 3.14 codegen_class() calls codegen_type_params(), then stores - // the resulting .type_params cell with codegen_nameop(c, LOC(class), ...). - assert_eq!( - type_params.linetable.as_ref(), - &[ - 0xf8, 0x81, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f - ] - ); - } - #[test] fn generic_class_wrapper_ops_use_class_location_like_cpython() { let code = compile_exec( @@ -17447,44 +17180,6 @@ class C[T]: pass assert_eq!(type_params.first_line_number.unwrap().get(), 2); } - #[test] - fn class_deferred_annotations_use_class_body_location_like_cpython() { - let code = compile_exec( - r#" -class C: - "doc" - x: int -"#, - ); - let class_code = find_code(&code, "C").expect("missing class code"); - - // CPython 3.14 calls codegen_body(c, loc, ...) from codegen_class_body() - // with LOCATION(firstlineno, firstlineno, 0, 0). Deferred annotation - // closure setup and following artificial class tail inherit that class - // body location, not the annotation expression location. - assert_eq!( - class_code.linetable.as_ref(), - &[ - 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd9, 0x04, 0x09, 0xf7, 0x03, 0x00, 0x01, 0x01, 0x83, - 0x00 - ], - ); - } - - #[test] - fn future_annotation_string_uses_annotation_location_like_cpython() { - let code = compile_exec("from __future__ import annotations\nclass Bar:\n foo: Foo\n"); - let class_code = find_code(&code, "Bar").expect("missing class code"); - - // CPython 3.14 codegen_annassign() calls codegen_visit_annexpr(), - // which emits the stringized annotation at LOC(annotation), then emits - // the __annotations__ store sequence at LOC(AnnAssign). - assert_eq!( - class_code.linetable.as_ref(), - &[0x87, 0x00, 0x80, 0x00, 0xd8, 0x09, 0x0c, 0x87, 0x48] - ); - } - #[test] #[expect( clippy::literal_string_with_formatting_args, @@ -17532,41 +17227,6 @@ class C: ); } - #[test] - fn lambda_dict_literal_ops_use_dict_location_like_cpython() { - let code = compile_exec( - "\ -f = lambda data: {'x': data} -g = lambda i: {**i} -", - ); - let f = find_code(&code, "").expect("missing f lambda code"); - let g = code - .constants - .iter() - .filter_map(|constant| { - if let ConstantData::Code { code } = constant { - (code.obj_name == "").then_some(code.as_ref()) - } else { - None - } - }) - .nth(1) - .expect("missing g lambda code"); - - // CPython 3.14 codegen_dict()/codegen_subdict() uses LOC(dict) for - // BUILD_MAP, MAP_ADD, and DICT_UPDATE, so the lambda RETURN_VALUE - // inherits the full dict literal location after compiling its body. - assert_eq!( - f.linetable.as_ref(), - &[0x81, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b] - ); - assert_eq!( - g.linetable.as_ref(), - &[0x81, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65] - ); - } - #[test] fn dict_unpacking_large_regular_run_uses_subdict_chunks_like_cpython() { let pairs = (0..17) @@ -18316,30 +17976,6 @@ def f(x): ); } - #[test] - fn match_or_alternative_copies_use_alternative_locations_like_cpython() { - let code = compile_exec( - "\ -def f(): - x = False - match 0: - case 0 | 1 | 2 | 3: - x = True - return x -", - ); - let f = find_code(&code, "f").expect("missing f code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x08, 0x0d, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x0e, 0x97, - 0x11, 0x97, 0x51, 0x9f, 0x11, 0x88, 0x5d, 0xe0, 0x0b, 0x0c, 0x80, 0x48, 0xf0, 0x05, - 0x00, 0x0e, 0x1b, 0xd8, 0x10, 0x14, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x80, 0x48 - ], - "CPython codegen_pattern_or() emits each alternative COPY with LOC(alt)" - ); - } - #[test] fn match_or_conflicting_bind_error_uses_or_pattern_location_like_cpython() { let error = compile_exec_error( @@ -18382,31 +18018,6 @@ def f(value): ); } - #[test] - fn match_success_jump_uses_no_location_like_cpython() { - let code = compile_exec( - "\ -def f(self): - match 0: - case 0: - x = True - case 0: - x = False - self.assertIs(x, True) -", - ); - let f = find_code(&code, "f").expect("missing f code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x0a, 0x0b, 0xde, 0x0d, 0x0e, 0xd9, 0x10, 0x14, 0x89, 0x41, 0xdd, - 0x0d, 0x0e, 0xd8, 0x10, 0x15, 0x88, 0x41, 0xd8, 0x04, 0x08, 0x87, 0x4d, 0x81, 0x4d, - 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a - ], - "CPython codegen_match_inner() emits the success jump with NO_LOCATION" - ); - } - #[test] fn match_default_simple_guard_jump_uses_guard_location_like_cpython() { let code = compile_exec( @@ -18456,32 +18067,6 @@ def f(x, y): ); } - #[test] - fn match_mapping_keys_scaffolding_uses_mapping_location_like_cpython() { - let code = compile_exec( - "\ -def f(self): - x = {} - y = None - match x: - case {0: 0}: - y = 0 - self.assertIs(y, None) -", - ); - let f = find_code(&code, "f").expect("missing f code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x08, 0x0a, 0x80, 0x41, 0xd8, 0x08, 0x0c, 0x80, 0x41, 0xd8, 0x0a, - 0x0b, 0xdf, 0x0d, 0x13, 0x8f, 0x56, 0x8a, 0x56, 0x95, 0x11, 0x89, 0x56, 0xd8, 0x10, - 0x11, 0x89, 0x41, 0xf2, 0x03, 0x00, 0x0e, 0x14, 0xe0, 0x04, 0x08, 0x87, 0x4d, 0x81, - 0x4d, 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a - ], - "CPython codegen_pattern_mapping() returns to LOC(p) for BUILD_TUPLE/MATCH_KEYS scaffolding" - ); - } - #[test] fn match_mapping_rest_cleanup_uses_mapping_location_like_cpython() { let code = compile_exec( @@ -18533,29 +18118,6 @@ def f(x): } } - #[test] - fn match_class_scaffolding_uses_class_pattern_location_like_cpython() { - let code = compile_exec( - "\ -def f(x): - match x: - case bool(z): - y = 0 - return y, z -", - ); - let f = find_code(&code, "f").expect("missing f code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x0a, 0x0b, 0xdc, 0x0d, 0x11, 0x8f, 0x57, 0x88, 0x57, 0xd8, 0x10, - 0x11, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x88, 0x34, 0x80, 0x4b, 0xf0, 0x05, 0x00, 0x0e, - 0x15, 0xe0, 0x0b, 0x0c, 0x88, 0x61, 0x88, 0x34, 0x80, 0x4b - ], - "CPython codegen_pattern_class() returns to LOC(p) after VISIT(cls)" - ); - } - #[test] fn match_class_wildcard_pop_uses_class_pattern_location_like_cpython() { let code = compile_exec( @@ -18979,77 +18541,6 @@ x = 1 or 2 or 3 ); } - #[test] - fn taken_constant_boolop_load_const_uses_literal_location_like_cpython() { - let code = compile_exec( - "\ -def and_false(x): - return False and x - -def or_true(x): - return True or x -", - ); - let and_false = find_code(&code, "and_false").expect("missing and_false code"); - let or_true = find_code(&code, "or_true").expect("missing or_true code"); - - // CPython 3.14 codegen_boolop() VISITs the selected literal before the - // short-circuit jump is optimized away, so the surviving LOAD_CONST - // keeps the literal range rather than the whole BoolOp range. - assert_eq!( - and_false.linetable.as_ref(), - &[0x81, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16] - ); - assert_eq!( - or_true.linetable.as_ref(), - &[0x81, 0x00, 0xd8, 0x0b, 0x0f, 0xd0, 0x04, 0x14] - ); - } - - #[test] - fn assert_false_message_call_uses_assert_location_like_cpython() { - let code = compile_exec( - "\ -def f(): - assert False, \"x\" -", - ); - let f = find_code(&code, "f").expect("missing f code"); - - // CPython 3.14 codegen_assert() emits LOAD_COMMON_CONSTANT and CALL - // at LOC(assert statement), then RAISE_VARARGS at LOC(test). - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x04, 0x15, 0x90, 0x23, 0xd3, 0x04, 0x15, 0x88, 0x35 - ] - ); - } - - #[test] - fn static_swap_implicit_return_keeps_preswap_store_location_like_cpython() { - let code = compile_exec( - "\ -def f(a, b): - a, b = a, b - b, a = a, b -", - ); - let f = find_code(&code, "f").expect("missing f code"); - - // CPython 3.14 flowgraph.c resolves line numbers before - // optimize_basic_block() turns BUILD_TUPLE/UNPACK_SEQUENCE into SWAP - // and apply_static_swaps() reorders the STORE_FAST pair. The - // synthetic return epilogue therefore keeps the pre-swap final store - // location. - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x0b, 0x0c, 0x80, 0x71, 0xd8, 0x0b, 0x0c, 0x82, 0x71 - ] - ); - } - #[test] fn unpack_store_pair_jump_uses_second_target_location_like_cpython() { let code = compile_exec( @@ -19161,173 +18652,6 @@ def f(flag, n, exp): assert_eq!(jump_position, (3, 12, 3, 13)); } - #[test] - fn genexpr_make_closure_and_call_use_genexpr_location_like_cpython() { - let code = compile_exec( - "\ -def f(parameters): - return ((p, type(p)) for p in parameters) -", - ); - let f = find_code(&code, "f").expect("missing f code"); - let genexpr = find_code(f, "").expect("missing genexpr code"); - - // CPython 3.14 codegen_comprehension() uses LOC(e) for - // codegen_make_closure(), the outer CALL, and the implicit .0 load - // in codegen_sync_comprehension_generator(). - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd9, 0x0b, 0x2d, 0xa0, 0x2a, 0xd3, 0x0b, 0x2d, 0xd0, 0x04, 0x2d - ] - ); - assert_eq!( - genexpr.linetable.as_ref(), - &[ - 0xf9, 0xa2, 0x2a, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x2a, 0x98, 0x51, 0x94, 0x04, 0x90, - 0x51, 0x93, 0x07, 0x8e, 0x4c, 0xa3, 0x2a, 0xf9 - ] - ); - } - - #[test] - fn implicit_call_genexpr_range_includes_call_parens_like_cpython() { - let code = compile_exec( - "\ -def implicit(): - return list(x for x in range(10)) - -def explicit(): - return list((x for x in range(10))) -", - ); - let implicit = find_code(&code, "implicit").expect("missing implicit code"); - let implicit_gen = find_code(implicit, "").expect("missing implicit genexpr code"); - let explicit = find_code(&code, "explicit").expect("missing explicit code"); - let explicit_gen = find_code(explicit, "").expect("missing explicit genexpr code"); - - // CPython's parser gives an unparenthesized sole GeneratorExp call - // argument the call-parenthesized range, and codegen_comprehension() - // uses LOC(e) for MAKE_FUNCTION, the outer CALL, and the implicit .0 - // LOAD_FAST. Explicitly parenthesized genexprs already carry their own - // parentheses and must not be widened again. - assert_eq!( - implicit.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, - 0x9b, 0x39, 0xd3, 0x0f, 0x25, 0x8f, 0x34, 0xd0, 0x04, 0x25, 0x88, 0x34, 0xd1, 0x0f, - 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9b, 0x39, 0xd3, 0x0f, 0x25, 0xd3, 0x0b, 0x25, 0xd0, - 0x04, 0x25 - ] - ); - assert_eq!( - implicit_gen.linetable.as_ref(), - &[ - 0xf9, 0x9a, 0x39, 0xe9, 0x00, 0x81, 0x00, 0x99, 0x39, 0x90, 0x61, 0x95, 0x01, 0x9b, - 0x39, 0xf9 - ] - ); - assert_eq!( - explicit_gen.linetable.as_ref(), - &[ - 0xf9, 0x9a, 0x49, 0xe9, 0x00, 0x81, 0x00, 0x99, 0x49, 0x90, 0x71, 0x95, 0x11, 0x9b, - 0x49, 0xf9 - ] - ); - } - - #[test] - fn implicit_call_genexpr_parenthesized_element_range_like_cpython() { - let code = compile_exec( - "\ -def bytes_binop(): - return bytes((x ^ 0x5C) for x in range(256)) - -def dict_tuple(d): - return dict((v, k) for (k, v) in d.items()) - -def plain_tuple_elt(xs): - return list((x, y) for x, y in xs) - -def explicit_gen(xs): - return list(((x, y) for x, y in xs)) -", - ); - let bytes_binop = find_code(&code, "bytes_binop").expect("missing bytes_binop code"); - let bytes_gen = find_code(bytes_binop, "").expect("missing bytes genexpr code"); - let dict_tuple = find_code(&code, "dict_tuple").expect("missing dict_tuple code"); - let dict_gen = find_code(dict_tuple, "").expect("missing dict genexpr code"); - let plain_tuple_elt = - find_code(&code, "plain_tuple_elt").expect("missing plain_tuple_elt code"); - let plain_gen = - find_code(plain_tuple_elt, "").expect("missing plain genexpr code"); - let explicit_gen = find_code(&code, "explicit_gen").expect("missing explicit_gen code"); - let explicit_inner = - find_code(explicit_gen, "").expect("missing explicit genexpr code"); - - // CPython 3.14's parser includes the call argument parentheses in - // LOC(GeneratorExp) for implicit sole-argument generator expressions, - // even when the element expression itself starts with parentheses. - assert_eq!( - bytes_binop.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa3, 0x5a, - 0xd3, 0x10, 0x30, 0xd3, 0x0b, 0x30, 0xd0, 0x04, 0x30 - ] - ); - assert_eq!( - bytes_gen.linetable.as_ref(), - &[ - 0xf9, 0xa2, 0x5a, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x5a, 0xa0, 0x01, 0x90, 0x64, 0x97, - 0x28, 0x93, 0x28, 0xa3, 0x5a, 0xf9 - ] - ); - assert_eq!( - dict_tuple.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x2f, 0xa0, 0x51, 0xa7, 0x57, 0xa1, 0x57, - 0xa3, 0x59, 0xd3, 0x0f, 0x2f, 0xd3, 0x0b, 0x2f, 0xd0, 0x04, 0x2f - ] - ); - assert_eq!( - dict_gen.linetable.as_ref(), - &[ - 0xf9, 0xa2, 0x59, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x59, 0x99, 0x36, 0x98, 0x41, 0x90, - 0x11, 0x96, 0x06, 0xa3, 0x59, 0xf9 - ] - ); - assert_eq!( - plain_tuple_elt.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x0f, 0x26, 0xa0, 0x32, 0xd3, 0x0f, - 0x26, 0x8f, 0x34, 0xd0, 0x04, 0x26, 0x88, 0x34, 0xd1, 0x0f, 0x26, 0xa0, 0x32, 0xd3, - 0x0f, 0x26, 0xd3, 0x0b, 0x26, 0xd0, 0x04, 0x26 - ] - ); - assert_eq!( - plain_gen.linetable.as_ref(), - &[ - 0xf9, 0xa2, 0x32, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x32, 0x99, 0x34, 0x98, 0x31, 0x90, - 0x11, 0x96, 0x06, 0xa3, 0x32, 0xf9 - ] - ); - assert_eq!( - explicit_gen.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x10, 0x27, 0xa0, 0x42, 0xd3, 0x10, - 0x27, 0x8f, 0x34, 0xd0, 0x04, 0x28, 0x88, 0x34, 0xd1, 0x10, 0x27, 0xa0, 0x42, 0xd3, - 0x10, 0x27, 0xd3, 0x0b, 0x28, 0xd0, 0x04, 0x28 - ] - ); - assert_eq!( - explicit_inner.linetable.as_ref(), - &[ - 0xf9, 0xa2, 0x42, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x42, 0x99, 0x44, 0x98, 0x41, 0x90, - 0x21, 0x96, 0x16, 0xa3, 0x42, 0xf9 - ] - ); - } - #[test] fn implicit_call_genexpr_operator_element_range_like_cpython() { // The element opens with a parenthesized group that the rest of the @@ -19388,71 +18712,6 @@ w = f'' 'a' f'' ); } - #[test] - fn genexpr_filter_cleanup_jumps_use_element_location_like_cpython() { - let code = compile_exec( - "\ -def simple(names): - return (x for x in names if not _ishidden(x)) - -def boolop(fields): - return (f for f in fields if f.init and not f.kw_only) -", - ); - let simple = find_code(&code, "simple").expect("missing simple code"); - let simple_gen = find_code(simple, "").expect("missing simple genexpr code"); - let boolop = find_code(&code, "boolop").expect("missing boolop code"); - let boolop_gen = find_code(boolop, "").expect("missing boolop genexpr code"); - - // codegen_sync_comprehension_generator() emits the - // comprehension guard jump to if_cleanup, then emits the if_cleanup - // backedge with elt_loc. flowgraph.c::jump_thread() copies that target - // jump location to the threaded POP_JUMP/NOT_TAKEN cleanup path. - assert_eq!( - simple_gen.linetable.as_ref(), - &[ - 0xf9, 0x92, 0x75, 0xe9, 0x00, 0x81, 0x00, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8, - 0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8b, 0x41, 0x93, 0x75, 0xf9 - ] - ); - assert_eq!( - boolop_gen.linetable.as_ref(), - &[ - 0xf9, 0x92, 0x76, 0xe9, 0x00, 0x81, 0x00, 0x91, 0x76, 0x90, 0x21, 0xa7, 0x16, 0xa5, - 0x16, 0x8c, 0x41, 0xb0, 0x01, 0xb7, 0x09, 0xb5, 0x09, 0x8f, 0x41, 0x8b, 0x41, 0x93, - 0x76, 0xf9 - ] - ); - } - - #[test] - fn try_finally_exception_scaffolding_uses_no_location_like_cpython() { - let code = compile_exec( - "\ -def f(self, node): - self.flag = True - try: - self.body(node) - finally: - self.flag = False -", - ); - let f = find_code(&code, "f").expect("missing f code"); - - // codegen_try_finally() emits the exception path - // SETUP_CLEANUP/PUSH_EXC_INFO and POP_EXCEPT_AND_RERAISE with - // NO_LOCATION; flowgraph line propagation then gives only the - // finalbody's direct RERAISE the finalbody location. - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x10, 0x14, 0x80, 0x44, 0x84, 0x49, 0xf0, 0x02, 0x03, 0x05, 0x1a, - 0xd8, 0x08, 0x0c, 0x8f, 0x09, 0x89, 0x09, 0x90, 0x24, 0x8c, 0x0f, 0xe0, 0x14, 0x19, - 0x88, 0x04, 0x8e, 0x09, 0xf8, 0x90, 0x45, 0x88, 0x04, 0x8d, 0x09, 0xfa - ] - ); - } - #[test] fn return_debug_in_finally_uses_cpython_preprocessed_constant_order() { let code = compile_exec( @@ -19578,76 +18837,6 @@ def f(close): ); } - #[test] - fn adjacent_no_location_entries_merge_like_cpython() { - let code = compile_exec( - "\ -def f(file): - if sys.platform == \"win32\": - try: - import nt - if not nt._supports_virtual_terminal(): - return False - except (ImportError, AttributeError): - return False - try: - return os.isatty(file.fileno()) - except OSError: - return hasattr(file, \"isatty\") and file.isatty() -", - ); - let f = find_code(&code, "f").expect("missing f code"); - - // CPython's NO_LOCATION is {-1, -1, -1, -1}, and - // assemble.c::assemble_location_info() merges adjacent instructions - // with the same NO_LOCATION into one linetable entry. - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdc, 0x07, 0x0a, 0x87, 0x7c, 0x81, 0x7c, 0x90, 0x77, 0xd4, 0x07, 0x1e, - 0xf0, 0x02, 0x05, 0x09, 0x19, 0xdb, 0x0c, 0x15, 0xd8, 0x13, 0x15, 0xd7, 0x13, 0x30, - 0xd1, 0x13, 0x30, 0xd7, 0x13, 0x32, 0xd2, 0x13, 0x32, 0xd9, 0x17, 0x1c, 0xf0, 0x03, - 0x00, 0x14, 0x33, 0xf0, 0x08, 0x03, 0x05, 0x39, 0xdc, 0x0f, 0x11, 0x8f, 0x79, 0x89, - 0x79, 0x98, 0x14, 0x9f, 0x1b, 0x99, 0x1b, 0x9b, 0x1d, 0xd3, 0x0f, 0x27, 0xd0, 0x08, - 0x27, 0xf8, 0xf4, 0x07, 0x00, 0x11, 0x1c, 0x9c, 0x5e, 0xd0, 0x0f, 0x2c, 0xf4, 0x00, - 0x01, 0x09, 0x19, 0xda, 0x13, 0x18, 0xf0, 0x03, 0x01, 0x09, 0x19, 0xfb, 0xf4, 0x08, - 0x00, 0x0c, 0x13, 0xf4, 0x00, 0x01, 0x05, 0x39, 0xdc, 0x0f, 0x16, 0x90, 0x74, 0x98, - 0x58, 0xd3, 0x0f, 0x26, 0xd7, 0x0f, 0x38, 0xd0, 0x0f, 0x38, 0xa8, 0x34, 0xaf, 0x3b, - 0xa9, 0x3b, 0xab, 0x3d, 0xd2, 0x08, 0x38, 0xf0, 0x03, 0x01, 0x05, 0x39, 0xfa - ] - ); - } - - #[test] - fn fstring_format_ops_use_formatted_value_location_like_cpython() { - let code = compile_exec( - "\ -def simple(self): - return f'{self.value}' - -def spec(x): - return f'{x!r:>3}' -", - ); - let simple = find_code(&code, "simple").expect("missing simple code"); - let spec = find_code(&code, "spec").expect("missing spec code"); - - // CPython 3.14 codegen_formatted_value() VISITs the inner expression - // first, then emits CONVERT_VALUE / FORMAT_* at LOC(FormattedValue). - assert_eq!( - simple.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x0e, 0x12, 0x8f, 0x6a, 0x89, 0x6a, 0x88, 0x5c, 0xd0, 0x04, 0x1a - ] - ); - assert_eq!( - spec.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x0e, 0x0f, 0x88, 0x58, 0x90, 0x22, 0x88, 0x58, 0xd0, 0x04, 0x16 - ] - ); - } - #[test] fn debug_fstring_literal_merging_and_location_like_cpython() { fn string_load_position(code: &CodeObject, expected: &str) -> (usize, usize, usize, usize) { @@ -19745,29 +18934,6 @@ def padded(digits, int_len): ); } - #[test] - fn joined_string_literals_extend_pending_literal_location_like_cpython() { - let code = compile_exec( - "\ -def f(a): - return ( - 'x' - f'y{a}z' - 'w' - ) -", - ); - let f = find_code(&code, "f").expect("missing f code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xf0, 0x04, 0x01, 0x09, 0x0c, 0xd8, 0x0c, 0x0d, 0x88, 0x33, 0xf0, 0x00, - 0x01, 0x0f, 0x0c, 0xf0, 0x03, 0x02, 0x09, 0x0c, 0xf0, 0x03, 0x04, 0x05, 0x06 - ], - "CPython parser/codegen represents adjacent f-string literal fragments as Constant ranges spanning the merged fragments" - ); - } - #[test] fn starred_call_preserves_bool_op_short_circuit_shape() { let code = compile_exec( @@ -19840,60 +19006,6 @@ def outer(null): ); } - #[test] - fn decorated_definitions_use_cpython_locations() { - let code = compile_exec( - "\ -def dec(f): return f - -class C: - @dec - def f(self): - yield - -@dec -class D: - pass - -class E: - @dec - def g(self, flags: int, /) -> memoryview: - raise NotImplementedError -", - ); - let c = find_code(&code, "C").expect("missing C code"); - let d = find_code(&code, "D").expect("missing D code"); - let e = find_code(&code, "E").expect("missing E code"); - let annotate = find_code(e, "__annotate__").expect("missing annotation code"); - - // CPython 3.14 codegen_function()/codegen_class() evaluate - // decorators first, then use LOC(s) for codegen_make_closure() and - // codegen_nameop(); codegen_apply_decorators() emits CALL at each - // decorator expression's location. - assert_eq!( - c.linetable.as_ref(), - &[ - 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd8, 0x05, 0x08, 0xf1, 0x02, 0x01, 0x05, 0x0e, 0xf3, - 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x0e - ] - ); - assert_eq!(d.linetable.as_ref(), &[0x87, 0x00, 0xe3, 0x04, 0x08]); - assert_eq!( - e.linetable.as_ref(), - &[ - 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd8, 0x05, 0x08, 0xf7, 0x02, 0x01, 0x05, 0x22, 0xf3, - 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x22 - ] - ); - assert_eq!( - annotate.linetable.as_ref(), - &[ - 0xf8, 0x81, 0x00, 0xf7, 0x00, 0x01, 0x05, 0x22, 0xf1, 0x00, 0x01, 0x05, 0x22, 0x91, - 0x73, 0xf0, 0x00, 0x01, 0x05, 0x22, 0xa1, 0x2a, 0xf1, 0x00, 0x01, 0x05, 0x22 - ] - ); - } - #[test] fn taken_constant_boolop_jump_disables_following_borrows() { for source in [ @@ -20669,56 +19781,6 @@ def outer(log): ); } - #[test] - fn builtin_any_genexpr_call_is_optimized() { - let code = compile_exec( - "\ -def f(xs): - return any(x for x in xs) -", - ); - let f = find_code(&code, "f").expect("missing function code"); - - assert!(has_common_constant(f, bytecode::CommonConstant::BuiltinAny)); - assert!( - f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::PopJumpIfTrue { .. })) - ); - assert!( - f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::NotTaken)) - ); - assert_eq!( - f.instructions - .iter() - .filter(|unit| matches!(unit.op, Instruction::PushNull)) - .count(), - 2, - "the iterator NULL slot and fallback call NULL slot must both remain" - ); - let genexpr_const_count = f - .constants - .iter() - .filter(|constant| { - matches!(constant, ConstantData::Code { code } if code.obj_name == "") - }) - .count(); - assert_eq!( - genexpr_const_count, 1, - "optimized and fallback any(genexpr) paths should share the same CPython-range code const" - ); - assert_eq!( - f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdf, 0x0b, 0x0e, 0x8b, 0x33, 0x89, 0x6f, 0x98, 0x22, 0x8b, 0x6f, 0x8f, - 0x33, 0x8d, 0x33, 0xd0, 0x04, 0x1d, 0x8a, 0x33, 0xd0, 0x04, 0x1d, 0x88, 0x33, 0x89, - 0x6f, 0x98, 0x22, 0x8b, 0x6f, 0xd3, 0x0b, 0x1d, 0xd0, 0x04, 0x1d - ] - ); - } - #[test] fn builtin_any_async_genexpr_call_is_not_optimized_like_cpython() { for source in [ @@ -20765,83 +19827,6 @@ async def f(get_xs): ); } - #[test] - fn builtin_tuple_list_set_genexpr_calls_are_optimized() { - let code = compile_exec( - "\ -def tuple_f(xs): - return tuple(x for x in xs) - -def list_f(xs): - return list(x for x in xs) - -def set_f(xs): - return set(x for x in xs) -", - ); - - let tuple_f = find_code(&code, "tuple_f").expect("missing tuple_f code"); - assert!(has_common_constant( - tuple_f, - bytecode::CommonConstant::BuiltinTuple - )); - assert!(has_intrinsic_1(tuple_f, IntrinsicFunction1::ListToTuple)); - let tuple_list_append = tuple_f - .instructions - .iter() - .find_map(|unit| match unit.op { - Instruction::ListAppend { .. } => Some(u32::from(u8::from(unit.arg))), - _ => None, - }) - .expect("tuple(genexpr) fast path should emit LIST_APPEND"); - assert_eq!(tuple_list_append, 3); - assert_eq!( - tuple_f.linetable.as_ref(), - &[ - 0x81, 0x00, 0xdf, 0x0b, 0x10, 0x8c, 0x35, 0x91, 0x0f, 0x98, 0x42, 0x93, 0x0f, 0x8f, - 0x35, 0x88, 0x35, 0xd0, 0x04, 0x1f, 0x88, 0x35, 0x91, 0x0f, 0x98, 0x42, 0x93, 0x0f, - 0xd3, 0x0b, 0x1f, 0xd0, 0x04, 0x1f - ] - ); - - let list_f = find_code(&code, "list_f").expect("missing list_f code"); - assert!( - list_f - .instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::Call { .. })), - "list(genexpr) keeps a fallback call for a shadowed builtin" - ); - assert!( - has_common_constant(list_f, bytecode::CommonConstant::BuiltinList), - "CPython 3.15 guards the optimized list(genexpr) path by builtin identity" - ); - - let set_f = find_code(&code, "set_f").expect("missing set_f code"); - assert!( - set_f - .instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::Call { .. })), - "set(genexpr) keeps a fallback call for a shadowed builtin" - ); - assert!( - has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet), - "CPython 3.15 guards the optimized set(genexpr) path by builtin identity" - ); - for (code, append) in [ - (list_f, bytecode::Opcode::ListAppend), - (set_f, bytecode::Opcode::SetAdd), - ] { - assert!( - cpython315_decoded_instructions(code) - .iter() - .any(|(_, op, arg)| { op.as_opcode() == append && *arg == 3 }) - ); - assert!(!has_intrinsic_1(code, IntrinsicFunction1::ListToTuple)); - } - } - #[test] fn builtin_tuple_genexpr_try_assignment_uses_shared_tail() { let code = compile_exec( @@ -21232,27 +20217,6 @@ def aug(x, a, b, y): ); } - #[test] - fn augassign_constant_slice_copy_uses_subscript_location_like_cpython() { - let code = compile_exec( - "\ -def aug_const(x, y): - x[1:2] += y -", - ); - let aug_const = find_code(&code, "aug_const").expect("missing aug_const code"); - - // CPython 3.14 codegen_augassign() visits a constant slice, then emits - // COPY/COPY/BINARY_OP NB_SUBSCR at LOC(target), not at LOC(slice). - assert_eq!( - aug_const.linetable.as_ref(), - &[ - 0x81, 0x00, 0xd8, 0x04, 0x05, 0x80, 0x63, 0x87, 0x46, 0x88, 0x61, 0x85, 0x4b, 0x85, - 0x46 - ] - ); - } - #[test] fn augassign_attribute_copy_uses_target_location_like_cpython() { let code = compile_exec( @@ -32901,30 +31865,6 @@ def g(): ); } - #[test] - fn comprehension_list_iterable_build_uses_iter_location_like_cpython() { - let code = compile_exec( - "\ -async def f(i): - return i - -async def run_list(): - return [await c for c in [f(1), f(41)]] -", - ); - let run_list = find_code(&code, "run_list").expect("missing run_list code"); - assert_eq!( - run_list.linetable.as_ref(), - &[ - 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x1e, 0x1f, 0xa0, 0x01, 0x9b, 0x64, 0xa4, 0x41, 0xa0, - 0x62, 0xa3, 0x45, 0x98, 0x5d, 0xd3, 0x0b, 0x2b, 0x9b, 0x5d, 0x98, 0x01, 0x8f, 0x47, - 0x8c, 0x47, 0x99, 0x5d, 0xd1, 0x0b, 0x2b, 0xd0, 0x04, 0x2b, 0x89, 0x47, 0xf9, 0xd2, - 0x0b, 0x2b, 0xf9 - ], - "CPython codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter)" - ); - } - #[test] fn comprehension_boolop_iter_get_iter_uses_iter_location_like_cpython() { let code = compile_exec( @@ -32955,30 +31895,6 @@ def f(self): ); } - #[test] - fn inlined_comprehension_backedges_use_element_location_like_cpython() { - let code = compile_exec( - "\ -async def f(i): - return i - -async def run_list(): - return [s for c in [f(''), f('abc')] for s in await c] -", - ); - let run_list = find_code(&code, "run_list").expect("missing run_list code"); - assert_eq!( - run_list.linetable.as_ref(), - &[ - 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x18, 0x19, 0x98, 0x22, 0x9b, 0x05, 0x9c, 0x71, 0xa0, - 0x15, 0x9b, 0x78, 0xd0, 0x17, 0x28, 0xd4, 0x0b, 0x3a, 0xd3, 0x17, 0x28, 0x90, 0x21, - 0xb7, 0x27, 0xb5, 0x27, 0xa8, 0x51, 0x8a, 0x41, 0xb1, 0x27, 0x89, 0x41, 0xd1, 0x17, - 0x28, 0xd2, 0x0b, 0x3a, 0xd0, 0x04, 0x3a, 0xb1, 0x27, 0xf9, 0xd3, 0x0b, 0x3a, 0xf9 - ], - "CPython codegen_sync_comprehension_generator() emits comprehension backedges at elt_loc" - ); - } - #[test] fn nested_dict_comprehension_outer_backedge_uses_key_location_like_cpython() { let code = compile_exec( @@ -33070,53 +31986,6 @@ def f(fields): ); } - #[test] - fn inlined_async_comprehension_end_async_for_uses_comprehension_location_like_cpython() { - let code = compile_exec( - "\ -async def f(it): - for i in it: - yield i - -async def run_list(): - return [i + 1 async for i in f([10, 20])] -", - ); - let run_list = find_code(&code, "run_list").expect("missing run_list code"); - assert_eq!( - run_list.linetable.as_ref(), - &[ - 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x21, 0x22, 0xa0, 0x42, 0xa8, 0x02, 0xa0, 0x38, 0xa3, - 0x1b, 0xd3, 0x0b, 0x2d, 0xa0, 0x1b, 0xd7, 0x0b, 0x2d, 0xd1, 0x0b, 0x2d, 0x98, 0x41, - 0x90, 0x01, 0x8f, 0x45, 0x88, 0x45, 0xd4, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, 0xf9, 0xd2, - 0x0b, 0x2d, 0xf9 - ], - "CPython codegen_async_comprehension_generator() emits END_ASYNC_FOR at comprehension loc" - ); - } - - #[test] - fn async_for_anext_sequence_uses_statement_location_like_cpython() { - let code = compile_exec( - "\ -async def f(source, buffer): - async for i1, i2 in source(): - buffer.append(i1 + i2) -", - ); - let f = find_code(&code, "f").expect("missing f code"); - assert_eq!( - f.linetable.as_ref(), - &[ - 0xe9, 0x00, 0x81, 0x00, 0xd9, 0x18, 0x1e, 0x9c, 0x08, 0xf7, 0x00, 0x01, 0x05, 0x1f, - 0xf2, 0x00, 0x01, 0x05, 0x1f, 0x89, 0x66, 0x88, 0x62, 0xd8, 0x08, 0x0e, 0x8f, 0x0d, - 0x89, 0x0d, 0x90, 0x62, 0x95, 0x67, 0xd6, 0x08, 0x1e, 0xf1, 0x03, 0x01, 0x05, 0x1f, - 0x9a, 0x08, 0xf9 - ], - "CPython codegen_async_for() emits GET_ANEXT/yield-from scaffolding at LOC(s)" - ); - } - #[test] fn nested_comprehension_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( @@ -33797,27 +32666,6 @@ def f(seq, value): })); } - #[test] - fn namedexpr_copy_uses_namedexpr_location_like_cpython() { - let code = compile_exec( - "\ -def outer(): - a = 10 - def spam(): - nonlocal a - (a := 20) -", - ); - let spam = find_code(&code, "spam").expect("missing spam code"); - - // CPython 3.14 NamedExpr_kind emits COPY at LOC(named expression), - // between visiting the value and visiting the target. - assert_eq!( - spam.linetable.as_ref(), - &[0xf8, 0x81, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11] - ); - } - #[test] fn genexpr_namedexpr_target_is_cell_not_fast_local() { let code = compile_exec( @@ -44360,300 +43208,4 @@ def f(_fwalk, stack, isbytes, topdown, onerror, follow_symlinks, close): &instructions[cleanup_pop.saturating_sub(12)..cleanup_pop] ); } - - fn cpython315_decoded_instructions(code: &CodeObject) -> Vec<(usize, Instruction, u32)> { - let mut state = bytecode::OpArgState::default(); - code.instructions - .iter() - .copied() - .enumerate() - .filter_map(|(offset, unit)| { - let (op, arg) = state.get(unit); - (!matches!(op, Instruction::Cache | Instruction::ExtendedArg)).then_some(( - offset, - op, - u32::from(arg), - )) - }) - .collect() - } - - #[test] - fn cpython315_genexpr_acquires_iterator_before_return_generator() { - for (source, is_async) in [ - ("def f(xs):\n return (x for x in xs)\n", false), - ("async def f(xs):\n return (x async for x in xs)\n", true), - ] { - let code = compile_exec(source); - let f = find_code(&code, "f").unwrap(); - let genexpr = find_code(f, "").unwrap(); - let instructions = cpython315_decoded_instructions(genexpr); - assert!(matches!(instructions[0].1, Instruction::Resume { .. })); - assert_eq!(instructions[0].2, 4); - assert!(matches!( - instructions[1].1, - Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. } - )); - assert_eq!(instructions[1].2, 0); - assert!(if is_async { - matches!(instructions[2].1, Instruction::GetAiter) - } else { - matches!(instructions[2].1, Instruction::GetIter { .. }) - }); - assert_eq!(instructions[2].2, 0); - assert!(matches!(instructions[3].1, Instruction::ReturnGenerator)); - assert!(matches!(instructions[4].1, Instruction::PopTop)); - assert!(matches!(instructions[5].1, Instruction::Resume { .. })); - assert_eq!(instructions[5].2, 0); - assert_eq!( - instructions - .iter() - .filter(|(_, op, _)| matches!(op, Instruction::ReturnGenerator)) - .count(), - 1 - ); - assert!(!f.instructions.iter().any(|unit| matches!( - unit.op, - Instruction::GetIter { .. } | Instruction::GetAiter - ))); - let return_offset = u32::try_from(instructions[3].0).unwrap(); - assert!( - bytecode::decode_exception_table(&genexpr.exceptiontable) - .iter() - .all(|entry| return_offset < entry.start || return_offset >= entry.end) - ); - } - } - - #[test] - fn cpython315_await_and_yield_from_have_pairs_and_cached_resumes() { - for (source, is_await) in [ - ("def f(xs):\n yield from xs\n", false), - ("async def f(xs):\n return await xs\n", true), - ] { - let code = compile_exec(source); - let f = find_code(&code, "f").unwrap(); - let instructions = cpython315_decoded_instructions(f); - let acquire = instructions - .iter() - .position(|(_, op, _)| { - matches!( - op, - Instruction::GetIter { .. } | Instruction::GetAwaitable { .. } - ) - }) - .unwrap(); - if is_await { - assert!(matches!(instructions[acquire + 1].1, Instruction::PushNull)); - } else { - assert_eq!(instructions[acquire].2, 1); - } - let send = instructions - .iter() - .position(|(_, op, _)| matches!(op, Instruction::Send { .. })) - .unwrap(); - let (offset, op, arg) = instructions[send]; - assert!(matches!( - f.instructions[offset + 1 + op.cache_entries() + arg as usize].op, - Instruction::EndSend - )); - let resume = instructions[send + 2]; - assert!(matches!(resume.1, Instruction::Resume { .. })); - assert_eq!(resume.2 & 7, if is_await { 3 } else { 2 }); - assert_eq!(resume.1.cache_entries(), 1); - assert!(matches!( - f.instructions[resume.0 + 1].op, - Instruction::Cache - )); - } - } - - #[test] - fn cpython315_comprehension_collection_depth_counts_iterator_slots() { - for (source, depth) in [ - ( - "def f(xs, ys):\n return [x for x in xs for y in ys]\n", - 5, - ), - ( - "async def f(xs, ys):\n return [x async for x in xs for y in ys]\n", - 4, - ), - ] { - let code = compile_exec(source); - let instructions = cpython315_decoded_instructions(find_code(&code, "f").unwrap()); - let append = instructions - .iter() - .find(|(_, op, _)| matches!(op, Instruction::ListAppend { .. })) - .unwrap(); - assert_eq!(append.2, depth); - } - } - - #[test] - fn cpython315_ordinary_imports_pack_names_across_extended_arg_boundary() { - use core::fmt::Write; - let mut source = String::new(); - for i in 0..70 { - writeln!(source, "import module_{i}").unwrap(); - } - let code = compile_exec(&source); - let imports: Vec<_> = cpython315_decoded_instructions(&code) - .into_iter() - .filter(|(_, op, _)| matches!(op, Instruction::ImportName { .. })) - .collect(); - assert_eq!(imports.len(), 70); - for (i, (_, _, arg)) in imports.iter().enumerate() { - assert_eq!(*arg, u32::try_from(i).unwrap() << 2); - assert_eq!(code.names[(arg >> 2) as usize], format!("module_{i}")); - } - assert!( - code.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::ExtendedArg)) - ); - } - - #[test] - fn cpython315_ordinary_import_policy_matches_scope_and_handler_context() { - for (source, name, flags) in [ - ("import sample\n", "", &[0][..]), - ("from sample import member\n", "", &[0][..]), - ("from sample import *\n", "", &[2][..]), - ("def f():\n import sample\n", "f", &[2][..]), - ("class C:\n from sample import member\n", "C", &[2][..]), - ( - "try:\n import sample\nexcept Exception:\n pass\n", - "", - &[2][..], - ), - ( - "try:\n operation()\nexcept Exception:\n import sample\n", - "", - &[2][..], - ), - ( - "try:\n operation()\nfinally:\n import sample\n", - "", - &[0, 2][..], - ), - ("with manager:\n import sample\n", "", &[0][..]), - ] { - let code = compile_exec(source); - let scope = find_code(&code, name).unwrap(); - let instructions = cpython315_decoded_instructions(scope); - let imports: Vec<_> = instructions - .iter() - .filter(|(_, op, _)| matches!(op, Instruction::ImportName { .. })) - .collect(); - assert_eq!( - imports - .iter() - .map(|import| import.2 & 3) - .collect::>(), - flags, - "{source}" - ); - for import in imports { - assert_eq!(scope.names[(import.2 >> 2) as usize], "sample"); - } - } - } - - #[test] - fn cpython315_loop_return_cleanup_distinguishes_sync_and_async_iterators() { - for (source, slots) in [ - ("def f(xs):\n for x in xs:\n return x\n", 2), - ( - "async def f(xs):\n async for x in xs:\n return x\n", - 1, - ), - ] { - let code = compile_exec(source); - let instructions = cpython315_decoded_instructions(find_code(&code, "f").unwrap()); - let swap = instructions - .iter() - .position(|(_, op, arg)| { - matches!(op, Instruction::Swap { .. }) && *arg == slots + 1 - }) - .unwrap(); - for i in 1..=slots as usize { - assert!(matches!(instructions[swap + i].1, Instruction::PopTop)); - } - assert!(matches!( - instructions[swap + slots as usize + 1].1, - Instruction::ReturnValue - )); - } - } - - #[test] - fn cpython315_end_async_for_offsets_account_for_resume_cache() { - let code = compile_exec("async def f(xs):\n async for x in xs:\n pass\n"); - let f = find_code(&code, "f").unwrap(); - let instructions = cpython315_decoded_instructions(f); - let (offset, _, arg) = *instructions - .iter() - .find(|(_, op, _)| matches!(op, Instruction::EndAsyncFor)) - .unwrap(); - assert!(matches!( - f.instructions[offset + 1 - arg as usize].op, - Instruction::EndSend - )); - } - - #[test] - fn cpython315_list_set_async_genexpr_calls_keep_normal_call_path() { - for (name, constant) in [ - ("list", bytecode::CommonConstant::BuiltinList), - ("set", bytecode::CommonConstant::BuiltinSet), - ] { - let source = format!("async def f(xs):\n return {name}(x async for x in xs)\n"); - let code = compile_exec(&source); - let f = find_code(&code, "f").unwrap(); - assert!(!has_common_constant(f, constant)); - assert!( - !f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::ForIter { .. })) - ); - assert!( - f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::Call { .. })) - ); - } - } - - #[test] - fn cpython315_with_cleanup_excludes_synthetic_not_taken() { - let code = compile_exec("def f(c):\n with c:\n x = 1\n y = 2\n"); - let f = find_code(&code, "f").unwrap(); - let not_taken = u32::try_from( - f.instructions - .iter() - .position(|unit| matches!(unit.op, Instruction::NotTaken)) - .unwrap(), - ) - .unwrap(); - let entries = bytecode::decode_exception_table(&f.exceptiontable); - assert_eq!(entries.len(), 3); - assert!( - entries - .iter() - .all(|entry| not_taken < entry.start || not_taken >= entry.end) - ); - assert_eq!(entries[1].end, not_taken); - assert_eq!(entries[2].start, not_taken + 1); - assert_eq!(entries[1].target, entries[2].target); - assert_eq!(entries[1].depth, 4); - assert_eq!(entries[2].depth, 4); - assert!(entries[1].push_lasti && entries[2].push_lasti); - assert_eq!( - f.exceptiontable.as_ref(), - &[ - 0x8c, 0x03, 0x1b, 0x05, 0x9b, 0x08, 0x2e, 0x09, 0xa4, 0x02, 0x2e, 0x09 - ] - ); - } } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 5f8c2cf008f..9c4ad879d94 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -2448,7 +2448,6 @@ impl Blocks { | Instruction::GetAnext | Instruction::GetLen | Instruction::GetIter { .. } - | Instruction::GetYieldFromIter | Instruction::ImportFrom { .. } | Instruction::MatchKeys | Instruction::MatchMapping @@ -4038,6 +4037,7 @@ impl CodeInfo { } } + /// Insert SETUP_CLEANUP just after the initial RETURN_GENERATOR; POP_TOP. pub(crate) fn insert_start_setup_cleanup( &mut self, handler_block: BlockIdx, @@ -4047,9 +4047,20 @@ impl CodeInfo { &mut self.instr_sequence, handler_block, )?; + let seq = &self.instr_sequence; + let opcode_at = |i: usize| seq.instrs[i].info.instr.real(); + let mut resume = 0; + while !matches!(opcode_at(resume), Some(Instruction::ReturnGenerator)) { + resume += 1; + debug_assert!(resume < seq.instr_used); + } + resume += 1; + debug_assert!(matches!(opcode_at(resume), Some(Instruction::PopTop))); + resume += 1; + debug_assert!(resume < seq.instr_used); instruction_sequence_insert_instruction( &mut self.instr_sequence, - 0, + resume, InstructionInfo { instr: PseudoOpcode::SetupCleanup.into(), arg: instruction_sequence_label_oparg(handler_label), @@ -4062,6 +4073,8 @@ impl CodeInfo { ) } + /// Insert `RESUME`, `LOAD_FAST 0` and the outermost `GET_ITER` (or + /// `GET_AITER`) before RETURN_GENERATOR. pub(crate) fn insert_genexpr_prefix( &mut self, mut iterator: InstructionInfo, @@ -4075,17 +4088,7 @@ impl CodeInfo { let mut load = iterator; load.instr_set_op1(Opcode::LoadFast.into(), OpArg::new(0)); iterator.arg = OpArg::new(0); - let mut return_generator = InstructionInfo::empty(); - return_generator.instr_set_op0(Instruction::ReturnGenerator.into()); - return_generator.location.line = self.metadata.firstlineno; - return_generator.end_location.line = self.metadata.firstlineno; - return_generator.lineno_override = Some(LINE_ONLY_LOCATION_OVERRIDE); - let mut pop = return_generator; - pop.instr_set_op0(Instruction::PopTop.into()); - for (i, info) in [resume, load, iterator, return_generator, pop] - .into_iter() - .enumerate() - { + for (i, info) in [resume, load, iterator].into_iter().enumerate() { instruction_sequence_insert_instruction(&mut self.instr_sequence, i, info)?; } Ok(()) @@ -4196,7 +4199,7 @@ fn optimized_cfg_to_instruction_sequence( blocks.convert_pseudo_conditional_jumps()?; let max_stackdepth = blocks.calculate_stackdepth()?; debug_assert!(!is_generator(flags) || max_stackdepth != 0); - let nlocalsplus = prepare_localsplus(metadata, blocks, flags)?; + let nlocalsplus = prepare_localsplus(metadata, blocks)?; // Pseudo ops are lowered after stackdepth and // localsplus preparation, before normalize_jumps. convert_pseudo_ops(blocks)?; @@ -4418,7 +4421,6 @@ fn insert_prefix_instructions( blocks: &mut Blocks, cellfixedoffsets: &[i32], nfreevars: usize, - flags: CodeFlags, ) -> crate::InternalResult<()> { debug_assert!(!blocks.is_empty()); let entry = &mut blocks[0]; @@ -4426,42 +4428,6 @@ fn insert_prefix_instructions( let firstlineno = metadata.firstlineno; debug_assert!(firstlineno.get() > 0); - if is_generator(flags) - && !entry - .used_instructions() - .iter() - .any(|info| matches!(info.instr.real(), Some(Instruction::ReturnGenerator))) - { - let location = SourceLocation { - line: firstlineno, - character_offset: OneIndexed::MIN, - }; - entry.basicblock_insert_instruction( - 0, - InstructionInfo { - instr: Instruction::ReturnGenerator.into(), - arg: OpArg::new(0), - target: BlockIdx::NULL, - location, - end_location: location, - except_handler: None, - lineno_override: Some(LINE_ONLY_LOCATION_OVERRIDE), - }, - )?; - entry.basicblock_insert_instruction( - 1, - InstructionInfo { - instr: Instruction::PopTop.into(), - arg: OpArg::new(0), - target: BlockIdx::NULL, - location, - end_location: location, - except_handler: None, - lineno_override: Some(LINE_ONLY_LOCATION_OVERRIDE), - }, - )?; - } - if ncellvars > 0 { let nvars = metadata.varnames.len() + ncellvars; let mut sorted = Vec::new(); @@ -4515,7 +4481,6 @@ fn insert_prefix_instructions( fn prepare_localsplus( metadata: &CodeUnitMetadata, blocks: &mut Blocks, - flags: CodeFlags, ) -> crate::InternalResult { let nlocals = metadata.varnames.len(); let ncellvars = metadata.cellvars.len(); @@ -4530,7 +4495,7 @@ fn prepare_localsplus( let mut cellfixedoffsets = build_cellfixedoffsets(metadata)?; // This must be called before fix_cell_offsets(). - insert_prefix_instructions(metadata, blocks, &cellfixedoffsets, nfreevars, flags)?; + insert_prefix_instructions(metadata, blocks, &cellfixedoffsets, nfreevars)?; let numdropped = fix_cell_offsets(metadata, blocks, &mut cellfixedoffsets); nlocalsplus -= numdropped; @@ -5969,7 +5934,7 @@ impl CodeInfo { )); let _max_stackdepth = self.blocks.calculate_stackdepth()?; - let _nlocalsplus = prepare_localsplus(&self.metadata, &mut self.blocks, self.flags)?; + let _nlocalsplus = prepare_localsplus(&self.metadata, &mut self.blocks)?; convert_pseudo_ops(&mut self.blocks)?; trace.push(( "after_convert_pseudo_ops".to_owned(), @@ -7110,8 +7075,6 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes } last_yield_except_depth = -1; } - } else if matches!(instr.real(), Some(Instruction::ReturnGenerator)) { - blocks[bi].instructions[i].except_handler = None; } else { blocks[bi].instructions[i].except_handler = handler; } @@ -7951,70 +7914,6 @@ mod tests { )); } - #[test] - fn constant_sequence_loads_use_cpython_opcode_has_const_metadata() { - let mut metadata = CodeUnitMetadata { - name: "".to_owned(), - qualname: Some("".to_owned()), - consts: Default::default(), - names: IndexSet::default(), - varnames: IndexSet::default(), - cellvars: IndexSet::default(), - freevars: IndexSet::default(), - fast_hidden: IndexMap::default(), - fast_hidden_final: IndexSet::default(), - argcount: 0, - posonlyargcount: 0, - kwonlyargcount: 0, - firstlineno: OneIndexed::MIN, - }; - let (left, _) = metadata - .consts - .insert_full(ConstantData::Str { value: "a".into() }); - let (right, _) = metadata - .consts - .insert_full(ConstantData::Str { value: "b".into() }); - - let mut immortal = test_instr(Instruction::Nop, 90); - immortal.instr = Opcode::LoadConstImmortal.into(); - immortal.arg = OpArg::new(left as u32); - let mut mortal = test_instr(Instruction::Nop, 90); - mortal.instr = Opcode::LoadConstMortal.into(); - mortal.arg = OpArg::new(right as u32); - let mut build = test_instr(Opcode::BuildTuple.into(), 90); - build.arg = OpArg::new(2); - let mut block = Block::default(); - for info in [immortal, mortal, build] { - test_block_push(&mut block, info); - } - - assert!( - fold_tuple_of_constants(&mut metadata, &mut block, 2) - .expect("fold_tuple_of_constants succeeds") - ); - - // CPython `loads_const()` accepts every `OPCODE_HAS_CONST` opcode, not - // just canonical LOAD_CONST, so LOAD_CONST_IMMORTAL/MORTAL participate - // in constant-sequence folding. - assert!(matches!( - block.instructions[0].instr.real(), - Some(Instruction::Nop) - )); - assert!(matches!( - block.instructions[1].instr.real(), - Some(Instruction::Nop) - )); - let folded = &block.instructions[2]; - assert!(matches!( - folded.instr.real(), - Some(Instruction::LoadConst { .. }) - )); - assert!(matches!( - &metadata.consts[u32::from(folded.arg) as usize], - ConstantData::Tuple { elements } if elements.len() == 2 - )); - } - #[test] fn empty_tuple_repeat_folds_negative_count_like_cpython() { let folded = const_folding_safe_multiply( @@ -8213,128 +8112,4 @@ mod tests { assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(2)); } - - #[test] - fn cpython315_common_constants_lower_and_round_trip_without_numeric_aliasing() { - use oparg::CommonConstant; - for (constant, common) in [ - (ConstantData::None, CommonConstant::None), - ( - ConstantData::Str { - value: Wtf8Buf::new(), - }, - CommonConstant::EmptyStr, - ), - (ConstantData::Boolean { value: true }, CommonConstant::True), - ( - ConstantData::Boolean { value: false }, - CommonConstant::False, - ), - (int_const(-1), CommonConstant::MinusOne), - ] { - let mut metadata = test_code_info(Block::default()).metadata; - metadata.consts.insert_full(constant.clone()); - let mut block = Block::default(); - test_block_push(&mut block, test_instr(Opcode::LoadConst.into(), 1)); - basicblock_optimize_load_const(&mut metadata, &mut block).unwrap(); - let info = block.instructions[0]; - assert_eq!(info.instr.real_opcode(), Some(Opcode::LoadCommonConstant)); - assert_eq!(u32::from(info.arg), u32::from(common)); - assert_eq!(get_const_value(&metadata, &info), Some(constant.clone())); - assert_eq!( - load_const_truthiness(info.instr.expect_real(), info.arg, &metadata), - Some(constant.truthiness()) - ); - } - for constant in [ - int_const(0), - int_const(1), - ConstantData::Float { value: -1.0 }, - ] { - assert!( - !test_instr(Opcode::LoadConst.into(), 1) - .maybe_instr_make_load_common_const(&constant) - ); - } - } - - #[test] - fn cpython315_get_iter_tracks_both_stack_slots_for_borrow_analysis() { - let mut block = Block::default(); - for (op, arg) in [ - (Opcode::LoadFast, 0), - (Opcode::GetIter, 0), - (Opcode::Swap, 2), - (Opcode::PopTop, 0), - (Opcode::PopTop, 0), - ] { - let mut info = test_instr(op.into(), 1); - info.arg = OpArg::new(arg); - test_block_push(&mut block, info); - } - let mut code = test_code_info(block); - code.blocks.optimize_load_fast().unwrap(); - assert_eq!( - code.blocks[0].instructions[0].instr.real_opcode(), - Some(Opcode::LoadFastBorrow) - ); - } - - #[test] - fn cpython315_end_send_preserves_result_borrow_and_consumes_iterator_pair() { - let mut block = Block::default(); - for (op, arg) in [ - ( - Opcode::LoadCommonConstant, - u32::from(oparg::CommonConstant::None), - ), - (Opcode::PushNull, 0), - (Opcode::LoadFast, 0), - (Opcode::EndSend, 0), - (Opcode::PopTop, 0), - ] { - let mut info = test_instr(op.into(), 1); - info.arg = OpArg::new(arg); - test_block_push(&mut block, info); - } - let mut code = test_code_info(block); - assert_eq!(code.blocks.calculate_stackdepth().unwrap(), 3); - code.blocks.optimize_load_fast().unwrap(); - assert_eq!( - code.blocks[0].instructions[2].instr.real_opcode(), - Some(Opcode::LoadFastBorrow) - ); - } - - #[test] - fn cpython315_redundant_nop_compaction_clears_vacated_metadata() { - let handler = ExceptHandlerInfo { - handler_block: BlockIdx::new(2), - preserve_lasti: true, - }; - let mut block = Block::default(); - for _ in 0..2 { - let mut nop = test_instr(Instruction::Nop, 10); - nop.lineno_override = Some(NO_LOCATION_OVERRIDE); - nop.target = BlockIdx::new(1); - nop.except_handler = Some(handler); - test_block_push(&mut block, nop); - } - let mut jump = test_instr(Opcode::PopJumpIfTrue.into(), 10); - jump.target = BlockIdx::new(1); - jump.except_handler = Some(handler); - test_block_push(&mut block, jump); - let mut blocks = Blocks::from([block, Block::default(), Block::default()]); - assert_eq!(blocks.basicblock_remove_redundant_nops(BlockIdx::new(0)), 2); - assert_eq!(blocks[0].instructions[0].target, jump.target); - assert_eq!(blocks[0].instructions[0].except_handler, Some(handler)); - for slot in &blocks[0].instructions[1..3] { - assert_eq!(slot.target, BlockIdx::NULL); - assert_eq!(slot.except_handler, None); - } - blocks.normalize_jumps_in_block(BlockIdx::new(0)).unwrap(); - let not_taken = &blocks[0].instructions[1]; - assert_eq!(not_taken.instr.real_opcode(), Some(Opcode::NotTaken)); - assert_eq!(not_taken.except_handler, None); - } } diff --git a/crates/common/src/cformat.rs b/crates/common/src/cformat.rs index 8117cf7b700..e2f9d50cb6a 100644 --- a/crates/common/src/cformat.rs +++ b/crates/common/src/cformat.rs @@ -19,7 +19,6 @@ pub enum CFormatErrorType { UnmatchedKeyParentheses, MissingModuloSign, UnsupportedFormatChar(CodePoint), - IncompleteFormat, StrayModulo, WidthTooBig, PrecisionTooBig, @@ -39,7 +38,6 @@ impl fmt::Display for CFormatError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.typ { CFormatErrorType::UnmatchedKeyParentheses => write!(f, "incomplete format key"), - CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"), CFormatErrorType::StrayModulo => write!(f, "stray % at position {}", self.index), CFormatErrorType::UnsupportedFormatChar(c) => write!( f, @@ -646,7 +644,7 @@ where { let (index, c) = iter.next().ok_or_else(|| { ( - CFormatErrorType::IncompleteFormat, + CFormatErrorType::StrayModulo, iter.peek().map_or(0, |x| x.0), ) })?; @@ -833,21 +831,14 @@ impl CFormatStrOrBytes { } let spec = - CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError { - typ: if context == CFormatContext::Str - && err.0 == CFormatErrorType::IncompleteFormat - { - CFormatErrorType::StrayModulo - } else { - err.0 - }, - index: if context == CFormatContext::Str - && err.0 == CFormatErrorType::IncompleteFormat - { + CFormatSpecKeyed::parse(iter, context).map_err(|(typ, err_index)| { + // A stray % is reported at the position of the %. + let index = if typ == CFormatErrorType::StrayModulo { index } else { - err.1 - }, + err_index + }; + CFormatError { typ, index } })?; parts.push((index, CFormatPart::Spec(spec))); @@ -856,16 +847,8 @@ impl CFormatStrOrBytes { } } else { return Err(CFormatError { - typ: if context == CFormatContext::Str { - CFormatErrorType::StrayModulo - } else { - CFormatErrorType::IncompleteFormat - }, - index: if context == CFormatContext::Str { - index - } else { - index + 1 - }, + typ: CFormatErrorType::StrayModulo, + index, }); } } else { diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index d0c1ef92a8a..8132c561318 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -11,7 +11,7 @@ use bitflags::bitflags; use core::{ cell::UnsafeCell, hash, mem, - ops::{Deref, DerefMut, Index, IndexMut}, + ops::{Deref, Index, IndexMut}, sync::atomic::{AtomicU8, AtomicU16, AtomicUsize, Ordering}, }; use itertools::Itertools; @@ -35,7 +35,6 @@ pub use crate::bytecode::{ mod instruction; mod opcode_metadata; -pub mod public_metadata; pub mod oparg; @@ -287,8 +286,8 @@ impl PyCodeLocationInfoKind { pub trait Constant: Sized + Clone { type Name: AsRef; - /// An immutable owner whose element view remains valid while borrowed. - type TupleOwner: Clone + AsRef<[Self]>; + /// Storage of a code object's constants (`co_consts`). + type Tuple: Clone + Deref + FromIterator; /// Transforms the given Constant to a BorrowedConstant fn borrow_constant(&self) -> BorrowedConstant<'_, Self>; @@ -296,7 +295,7 @@ pub trait Constant: Sized + Clone { impl Constant for ConstantData { type Name = String; - type TupleOwner = Box<[Self]>; + type Tuple = Box<[Self]>; fn borrow_constant(&self) -> BorrowedConstant<'_, Self> { match self { @@ -378,33 +377,15 @@ impl ConstantBag for BasicBag { } #[derive(Clone)] -pub struct Constants(ConstantStorage); - -#[derive(Clone)] -enum ConstantStorage { - Boxed(Box<[C]>), - Tuple(C::TupleOwner), -} +pub struct Constants(C::Tuple); impl Constants { - pub fn from_tuple(owner: C::TupleOwner) -> Self { - Self(ConstantStorage::Tuple(owner)) + pub const fn new(tuple: C::Tuple) -> Self { + Self(tuple) } - pub fn tuple_owner(&self) -> Option<&C::TupleOwner> { - match &self.0 { - ConstantStorage::Tuple(owner) => Some(owner), - ConstantStorage::Boxed(_) => None, - } - } - - /// Consume boxed elements without adding another lasting element owner. - /// An immutable tuple must be copied when its elements are taken out. - pub fn into_boxed(self) -> Box<[C]> { - match self.0 { - ConstantStorage::Boxed(elements) => elements, - ConstantStorage::Tuple(owner) => owner.as_ref().into(), - } + pub const fn as_tuple(&self) -> &C::Tuple { + &self.0 } } @@ -412,25 +393,7 @@ impl Deref for Constants { type Target = [C]; fn deref(&self) -> &Self::Target { - match &self.0 { - ConstantStorage::Boxed(elements) => elements, - ConstantStorage::Tuple(owner) => owner.as_ref(), - } - } -} - -impl DerefMut for Constants { - fn deref_mut(&mut self) -> &mut Self::Target { - if let ConstantStorage::Tuple(owner) = &self.0 { - // Allocate and clone before releasing the old owner. No published - // tuple is ever mutated, even when it has only one current owner. - let elements = owner.as_ref().into(); - self.0 = ConstantStorage::Boxed(elements); - } - match &mut self.0 { - ConstantStorage::Boxed(elements) => elements, - ConstantStorage::Tuple(_) => unreachable!(), - } + &self.0 } } @@ -438,19 +401,13 @@ impl Index for Constants { type Output = C; fn index(&self, consti: oparg::ConstIdx) -> &Self::Output { - &self.deref()[consti.as_usize()] - } -} - -impl IndexMut for Constants { - fn index_mut(&mut self, consti: oparg::ConstIdx) -> &mut Self::Output { - &mut self.deref_mut()[consti.as_usize()] + &self.0[consti.as_usize()] } } impl FromIterator for Constants { fn from_iter>(iter: T) -> Self { - Self(ConstantStorage::Boxed(iter.into_iter().collect())) + Self(iter.into_iter().collect()) } } @@ -779,23 +736,24 @@ impl Deref for CodeUnits { } impl CodeUnits { - /// Apply final 3.15 GET_ITER mode fixups before code publication. - /// Python/specialize.c:fixup_getiter normalizes every nonzero mode. - pub fn normalize_get_iter(&mut self, flags: CodeFlags) { + /// `fixup_getiter` applied to every `GET_ITER` by `_PyCode_Quicken`. + /// + /// The compiler can't know if `types.coroutine()` will be called, so a + /// yield-from `GET_ITER` is resolved when the code object is created. + pub fn fixup_get_iter(&mut self, flags: CodeFlags) { + let mode = if flags.intersects(CodeFlags::COROUTINE | CodeFlags::ITERABLE_COROUTINE) { + oparg::GET_ITER_YIELD_FROM_NO_CHECK + } else { + oparg::GET_ITER_YIELD_FROM_CORO_CHECK + }; + let mut i = 0; let units = self.units.get_mut(); - let mut index = 0; - while index < units.len() { - let unit = &mut units[index]; - if matches!(unit.op, Instruction::GetIter { .. }) && unit.arg != 0.into() { - let mode = if flags.intersects(CodeFlags::COROUTINE | CodeFlags::ITERABLE_COROUTINE) - { - 2 - } else { - 3 - }; - unit.arg = mode.into(); + while i < units.len() { + let unit = &mut units[i]; + if matches!(unit.op, Instruction::GetIter { .. }) && u8::from(unit.arg) != 0 { + unit.arg = (mode as u8).into(); } - index += 1 + unit.op.cache_entries(); + i += 1 + unit.op.cache_entries(); } } @@ -807,13 +765,6 @@ impl CodeUnits { } } - /// Number of code units, without borrowing the live mutable instruction slice. - /// Constructors and Clone keep the out-of-line counter array at this length. - #[must_use] - pub fn unit_count(&self) -> usize { - self.adaptive_counters.len() - } - /// Replace the opcode at `index` in-place without changing the arg byte. /// Uses atomic Release store to ensure prior cache writes are visible /// to threads that subsequently read the new opcode with Acquire. @@ -1009,11 +960,20 @@ impl CodeUnits { /// (marshal) and `co_code`. Specialized opcodes are mapped back to their /// base variants via `deoptimize()` and all CACHE entries are zeroed. pub fn original_bytes(&self) -> Vec { + self.original_bytes_with(|_, op| op) + } + + /// [`Self::original_bytes`], with `resolve` first replacing the opcode at + /// an index, e.g. an instrumentation placeholder by its original opcode. + pub fn original_bytes_with( + &self, + resolve: impl Fn(usize, Instruction) -> Instruction, + ) -> Vec { let len = self.len(); let mut out = Vec::with_capacity(len * 2); let mut i = 0; while i < len { - let op = self.read_op(i).deoptimize(); + let op = resolve(i, self.read_op(i)).deoptimize(); let arg = self.read_arg(i); let caches = op.cache_entries(); out.push(u8::from(op)); diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index 0f680d7d1f0..a34759a92b7 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -333,10 +333,8 @@ define_opcodes!( FormatWithSpec = 13, GetAiter = 14, GetAnext = 15, - GetIter { mode: Arg } = 70, - Reserved = 17, GetLen = 16, - GetYieldFromIter = 219, + Reserved = 17, InterpreterExit = 18, LoadBuildClass = 19, LoadLocals = 20, @@ -441,6 +439,7 @@ define_opcodes!( GetAwaitable { r#where: Arg, } = 69, + GetIter { mode: Arg } = 70, ImportFrom { namei: Arg, } = 71, @@ -603,6 +602,7 @@ define_opcodes!( BinaryOpSubscrListSlice = 138, BinaryOpSubscrStrInt = 139, BinaryOpSubscrTupleInt = 140, + BinaryOpSubscrUstrInt = 141, BinaryOpSubtractFloat = 142, BinaryOpSubtractInt = 143, CallAllocAndEnterInit = 144, @@ -612,6 +612,8 @@ define_opcodes!( CallBuiltinFast = 148, CallBuiltinFastWithKeywords = 149, CallBuiltinO = 150, + CallExNonPyGeneral = 151, + CallExPy = 152, CallIsinstance = 153, CallKwBoundMethod = 154, CallKwNonPy = 155, @@ -637,6 +639,9 @@ define_opcodes!( ForIterList = 175, ForIterRange = 176, ForIterTuple = 177, + ForIterVirtual = 178, + GetIterSelf = 179, + GetIterVirtual = 180, JumpBackwardJit = 181, JumpBackwardNoJit = 182, LoadAttrClass = 183, @@ -652,14 +657,15 @@ define_opcodes!( LoadAttrProperty = 193, LoadAttrSlot = 194, LoadAttrWithHint = 195, - LoadConstImmortal = 220, - LoadConstMortal = 221, LoadGlobalBuiltin = 196, LoadGlobalModule = 197, LoadSuperAttrAttr = 198, LoadSuperAttrMethod = 199, ResumeCheck = 200, + ResumeCheckJit = 201, + SendAsyncGen = 202, SendGen = 203, + SendVirtual = 204, StoreAttrInstanceValue = 205, StoreAttrSlot = 206, StoreAttrWithHint = 207, @@ -696,6 +702,7 @@ define_opcodes!( InstrumentedJumpBackward = 252, InstrumentedLine = 253, EnterExecutor = 254, + TraceRecord = 255, } ); @@ -1342,7 +1349,6 @@ const _: () = assert!(core::mem::size_of::() == 2); #[cfg(test)] mod tests { use super::*; - use alloc::vec; #[test] fn eval_break_flags_match_cpython_jump_metadata() { @@ -1428,8 +1434,6 @@ mod tests { #[test] fn const_flags_match_cpython_opcode_metadata() { assert!(Opcode::LoadConst.has_const()); - assert!(Opcode::LoadConstImmortal.has_const()); - assert!(Opcode::LoadConstMortal.has_const()); assert!(!Opcode::LoadSmallInt.has_const()); assert!(!Opcode::Nop.has_const()); @@ -1437,67 +1441,6 @@ mod tests { assert!(!AnyInstruction::from(PseudoOpcode::Jump).has_const()); } - include!("target_stack_cases.rs"); - - #[test] - fn get_iter_modes_normalize_before_publication() { - use crate::bytecode::{CodeFlags, CodeUnit, CodeUnits}; - for flags in [ - CodeFlags::empty(), - CodeFlags::COROUTINE, - CodeFlags::ITERABLE_COROUTINE, - ] { - for input in 0..=3u8 { - let mut units = CodeUnits::from(vec![ - CodeUnit::new(Opcode::GetIter.as_instruction(), input.into()), - CodeUnit::new(Opcode::Cache.as_instruction(), 0.into()), - CodeUnit::new(Opcode::ReturnValue.as_instruction(), 0.into()), - ]); - units.normalize_get_iter(flags); - let expected = if input == 0 { - 0 - } else if flags.is_empty() { - 3 - } else { - 2 - }; - assert_eq!(u8::from(units[0].arg), expected); - assert_eq!(units[1].op.as_opcode(), Opcode::Cache); - assert_eq!(units[2].op.as_opcode(), Opcode::ReturnValue); - } - } - } - - #[test] - fn private_load_const_exports_as_public_opcode() { - use crate::bytecode::{CodeUnit, CodeUnits}; - for opcode in [Opcode::LoadConstImmortal, Opcode::LoadConstMortal] { - let units = CodeUnits::from(vec![CodeUnit::new(opcode.as_instruction(), 7.into())]); - assert_eq!(units.original_bytes(), vec![Opcode::LoadConst.as_u8(), 7]); - } - } - - #[test] - fn supported_stack_contract_matches_pinned_c_metadata() { - let mut checked = 0; - for &(opcode, arg, popped, pushed) in TARGET_STACK_CASES { - let Ok(op) = AnyOpcode::try_from(opcode) else { - continue; - }; - let actual = match op { - AnyOpcode::Real(op) => op.stack_effect_info(arg), - AnyOpcode::Pseudo(op) => op.stack_effect_info(arg), - }; - assert_eq!( - (actual.popped(), actual.pushed()), - (popped, pushed), - "opcode {opcode}, argument {arg}" - ); - checked += 1; - } - assert_eq!(checked, 3290, "supported C stack contract coverage changed"); - } - #[test] fn stack_effects_match_cpython_opcode_metadata() { assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 2); @@ -1535,15 +1478,16 @@ mod tests { assert!(AnyInstruction::from(PseudoOpcode::Jump).is_no_fallthrough()); } - /// Independent match reference for the pinned final 3.15 cache/deopt contract. - /// Keep exhaustive numeric iteration below to check generated lookup tables. + /// Snapshot of the chained `match` implementations that `Opcode::deopt` + /// and `Opcode::cache_entries` used before they were rewritten as table + /// lookups. Exists only to pin the observable behavior of the table + /// lookups against the logic they replaced. mod reference { use super::Opcode; pub(super) const fn deopt(op: Opcode) -> Option { Some(match op { - Opcode::ResumeCheck => Opcode::Resume, - Opcode::LoadConstMortal | Opcode::LoadConstImmortal => Opcode::LoadConst, + Opcode::ResumeCheck | Opcode::ResumeCheckJit => Opcode::Resume, Opcode::ToBoolAlwaysTrue | Opcode::ToBoolBool | Opcode::ToBoolInt @@ -1561,12 +1505,13 @@ mod tests { | Opcode::BinaryOpSubscrListSlice | Opcode::BinaryOpSubscrTupleInt | Opcode::BinaryOpSubscrStrInt + | Opcode::BinaryOpSubscrUstrInt | Opcode::BinaryOpSubscrDict | Opcode::BinaryOpSubscrGetitem | Opcode::BinaryOpExtend | Opcode::BinaryOpInplaceAddUnicode => Opcode::BinaryOp, Opcode::StoreSubscrDict | Opcode::StoreSubscrListInt => Opcode::StoreSubscr, - Opcode::SendGen => Opcode::Send, + Opcode::SendGen | Opcode::SendAsyncGen | Opcode::SendVirtual => Opcode::Send, Opcode::UnpackSequenceTwoTuple | Opcode::UnpackSequenceTuple | Opcode::UnpackSequenceList => Opcode::UnpackSequence, @@ -1596,7 +1541,9 @@ mod tests { Opcode::ForIterList | Opcode::ForIterTuple | Opcode::ForIterRange - | Opcode::ForIterGen => Opcode::ForIter, + | Opcode::ForIterGen + | Opcode::ForIterVirtual => Opcode::ForIter, + Opcode::GetIterSelf | Opcode::GetIterVirtual => Opcode::GetIter, Opcode::CallBoundMethodExactArgs | Opcode::CallPyExactArgs | Opcode::CallType1 @@ -1620,6 +1567,7 @@ mod tests { Opcode::CallKwBoundMethod | Opcode::CallKwPy | Opcode::CallKwNonPy => { Opcode::CallKw } + Opcode::CallExPy | Opcode::CallExNonPyGeneral => Opcode::CallFunctionEx, _ => return None, }) } diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index cccee707251..78d3bc6bf78 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -342,6 +342,11 @@ oparg_enum!( } ); +/// Values used in the oparg for `GET_ITER`. +pub const GET_ITER_YIELD_FROM: u32 = 1; +pub const GET_ITER_YIELD_FROM_NO_CHECK: u32 = 2; +pub const GET_ITER_YIELD_FROM_CORO_CHECK: u32 = 3; + pub type NameIdx = u32; impl OpArgType for u32 {} @@ -916,10 +921,10 @@ newtype_oparg!( ); impl ResumeContext { - /// [CPython `RESUME_OPARG_LOCATION_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h) + /// [CPython `RESUME_OPARG_LOCATION_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h#L92) pub const LOCATION_MASK: u32 = 0x7; - /// [CPython `RESUME_OPARG_DEPTH1_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h) + /// [CPython `RESUME_OPARG_DEPTH1_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h#L93) pub const DEPTH1_MASK: u32 = 0x8; #[must_use] @@ -957,7 +962,8 @@ pub enum ResumeLocation { AfterYieldFrom, /// After an `await` expression. AfterAwait, - /// Eager iterator acquisition before generator-expression creation. + /// Start of a generator expression, before its outermost iterator is + /// acquired and the generator is created. AtGenExprStart, } diff --git a/crates/compiler-core/src/bytecode/opcode_metadata.rs b/crates/compiler-core/src/bytecode/opcode_metadata.rs index e62a80dadc4..82c3ec09191 100644 --- a/crates/compiler-core/src/bytecode/opcode_metadata.rs +++ b/crates/compiler-core/src/bytecode/opcode_metadata.rs @@ -19,10 +19,10 @@ impl super::Opcode { 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 3, - 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 0, 0, 0, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 1, 1, 1, 0, 0, - 1, 0, 4, 4, 4, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, + 3, 3, 3, 3, 3, 3, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 1, 1, 1, 1, 1, + 1, 1, 4, 4, 4, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 3, 3, 1, 1, 0, 0, 0, ]; @@ -174,7 +174,7 @@ impl super::Opcode { Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), - None, + Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::Call), @@ -184,8 +184,8 @@ impl super::Opcode { Some(super::Opcode::Call), Some(super::Opcode::Call), Some(super::Opcode::Call), - None, - None, + Some(super::Opcode::CallFunctionEx), + Some(super::Opcode::CallFunctionEx), Some(super::Opcode::Call), Some(super::Opcode::CallKw), Some(super::Opcode::CallKw), @@ -211,9 +211,9 @@ impl super::Opcode { Some(super::Opcode::ForIter), Some(super::Opcode::ForIter), Some(super::Opcode::ForIter), - None, - None, - None, + Some(super::Opcode::ForIter), + Some(super::Opcode::GetIter), + Some(super::Opcode::GetIter), Some(super::Opcode::JumpBackward), Some(super::Opcode::JumpBackward), Some(super::Opcode::LoadAttr), @@ -234,10 +234,10 @@ impl super::Opcode { Some(super::Opcode::LoadSuperAttr), Some(super::Opcode::LoadSuperAttr), Some(super::Opcode::Resume), - None, - None, + Some(super::Opcode::Resume), + Some(super::Opcode::Send), + Some(super::Opcode::Send), Some(super::Opcode::Send), - None, Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), @@ -253,8 +253,8 @@ impl super::Opcode { Some(super::Opcode::UnpackSequence), Some(super::Opcode::UnpackSequence), None, - Some(super::Opcode::LoadConst), - Some(super::Opcode::LoadConst), + None, + None, None, None, None, @@ -385,6 +385,7 @@ impl super::Opcode { | Self::CallBuiltinFast | Self::CallBuiltinFastWithKeywords | Self::CallBuiltinO + | Self::CallExPy | Self::CallKwBoundMethod | Self::CallKwNonPy | Self::CallKwPy @@ -408,6 +409,7 @@ impl super::Opcode { | Self::ForIterList | Self::ForIterRange | Self::ForIterTuple + | Self::ForIterVirtual | Self::JumpBackwardJit | Self::JumpBackwardNoJit | Self::LoadAttrClass @@ -427,13 +429,14 @@ impl super::Opcode { | Self::LoadGlobalModule | Self::LoadSuperAttrAttr | Self::LoadSuperAttrMethod + | Self::ResumeCheckJit + | Self::SendAsyncGen | Self::SendGen + | Self::SendVirtual | Self::StoreAttrWithHint | Self::UnpackSequenceList | Self::UnpackSequenceTuple | Self::UnpackSequenceTwoTuple - | Self::LoadConstImmortal - | Self::LoadConstMortal | Self::InstrumentedForIter | Self::InstrumentedJumpForward | Self::InstrumentedPopJumpIfTrue @@ -448,16 +451,14 @@ impl super::Opcode { | Self::InstrumentedCallKw | Self::InstrumentedJumpBackward | Self::EnterExecutor + | Self::TraceRecord ) } /// Does this opcode have 'HAS_CONST_FLAG' set. #[must_use] pub const fn has_const(self) -> bool { - matches!( - self, - Self::LoadConst | Self::LoadConstImmortal | Self::LoadConstMortal - ) + matches!(self, Self::LoadConst) } /// Does this opcode have 'HAS_EVAL_BREAK_FLAG' set. @@ -473,6 +474,7 @@ impl super::Opcode { | Self::CallBuiltinFast | Self::CallBuiltinFastWithKeywords | Self::CallBuiltinO + | Self::CallExNonPyGeneral | Self::CallKwNonPy | Self::CallMethodDescriptorFast | Self::CallMethodDescriptorFastWithKeywords @@ -517,8 +519,11 @@ impl super::Opcode { | Self::ForIterList | Self::ForIterRange | Self::ForIterTuple + | Self::ForIterVirtual | Self::JumpBackwardJit | Self::JumpBackwardNoJit + | Self::SendAsyncGen + | Self::SendVirtual | Self::InstrumentedForIter | Self::InstrumentedEndAsyncFor ) @@ -739,6 +744,7 @@ impl super::Opcode { Self::BinaryOpSubscrListSlice => (1, 2), Self::BinaryOpSubscrStrInt => (1, 2), Self::BinaryOpSubscrTupleInt => (1, 2), + Self::BinaryOpSubscrUstrInt => (1, 2), Self::BinaryOpSubtractFloat => (1, 2), Self::BinaryOpSubtractInt => (1, 2), Self::CallAllocAndEnterInit => (0, 2 + oparg), @@ -748,6 +754,8 @@ impl super::Opcode { Self::CallBuiltinFast => (1, 2 + oparg), Self::CallBuiltinFastWithKeywords => (1, 2 + oparg), Self::CallBuiltinO => (1, 2 + oparg), + Self::CallExNonPyGeneral => (1, 4), + Self::CallExPy => (0, 4), Self::CallIsinstance => (1, 4), Self::CallKwBoundMethod => (0, 3 + oparg), Self::CallKwNonPy => (1, 3 + oparg), @@ -773,6 +781,9 @@ impl super::Opcode { Self::ForIterList => (3, 2), Self::ForIterRange => (3, 2), Self::ForIterTuple => (3, 2), + Self::ForIterVirtual => (3, 2), + Self::GetIterSelf => (2, 1), + Self::GetIterVirtual => (2, 1), Self::JumpBackwardJit => (0, 0), Self::JumpBackwardNoJit => (0, 0), Self::LoadAttrClass => (1 + (oparg & 1), 1), @@ -793,7 +804,10 @@ impl super::Opcode { Self::LoadSuperAttrAttr => (1, 3), Self::LoadSuperAttrMethod => (2, 3), Self::ResumeCheck => (0, 0), + Self::ResumeCheckJit => (0, 0), + Self::SendAsyncGen => (3, 3), Self::SendGen => (2, 3), + Self::SendVirtual => (3, 3), Self::StoreAttrInstanceValue => (0, 2), Self::StoreAttrSlot => (0, 2), Self::StoreAttrWithHint => (0, 2), @@ -808,12 +822,6 @@ impl super::Opcode { Self::UnpackSequenceList => (oparg, 1), Self::UnpackSequenceTuple => (oparg, 1), Self::UnpackSequenceTwoTuple => (2, 1), - Self::GetYieldFromIter => ( - 1, // TODO: Differs from CPython `None` - 1, // TODO: Differs from CPython `None` - ), - Self::LoadConstImmortal => (1, 0), - Self::LoadConstMortal => (1, 0), Self::InstrumentedEndFor => (2, 3), Self::InstrumentedPopIter => (0, 2), Self::InstrumentedEndSend => (1, 3), @@ -836,6 +844,7 @@ impl super::Opcode { Self::InstrumentedJumpBackward => (0, 0), Self::InstrumentedLine => (0, 0), Self::EnterExecutor => (0, 0), + Self::TraceRecord => (0, 0), }; debug_assert!(u32::try_from(pushed).is_ok()); diff --git a/crates/compiler-core/src/bytecode/public_metadata.rs b/crates/compiler-core/src/bytecode/public_metadata.rs deleted file mode 100644 index f2bbb02b98b..00000000000 --- a/crates/compiler-core/src/bytecode/public_metadata.rs +++ /dev/null @@ -1,496 +0,0 @@ -// Generated by tools/opcode_metadata/generate_public_metadata.py from pinned final CPython. -//! Public introspection metadata, independent of the VM's executable opcode set. -//! Describing an opcode does not enable execution of its CPython specialization. -const DESCRIPTORS: [u32; 267] = [ - 2147483648, // 0: CACHE - 2147484416, // 1: BINARY_SLICE - 2147484416, // 2: BUILD_TEMPLATE - 3221227296, // 3: BINARY_OP_INPLACE_ADD_UNICODE - 2147627840, // 4: CALL_FUNCTION_EX - 2147484416, // 5: CHECK_EG_MATCH - 2147492608, // 6: CHECK_EXC_MATCH - 2147492608, // 7: CLEANUP_THROW - 2147484416, // 8: DELETE_SUBSCR - 2147500544, // 9: END_FOR - 2147486208, // 10: END_SEND - 2147492608, // 11: EXIT_INIT_CHECK - 2147484416, // 12: FORMAT_SIMPLE - 2147484416, // 13: FORMAT_WITH_SPEC - 2147484416, // 14: GET_AITER - 2147492608, // 15: GET_ANEXT - 2147484416, // 16: GET_LEN - 2147483648, // 17: RESERVED - 2147484160, // 18: INTERPRETER_EXIT - 2147484416, // 19: LOAD_BUILD_CLASS - 2147483904, // 20: LOAD_LOCALS - 2147492608, // 21: MAKE_FUNCTION - 2147484416, // 22: MATCH_KEYS - 2147483648, // 23: MATCH_MAPPING - 2147483648, // 24: MATCH_SEQUENCE - 2147485696, // 25: NOP - 2147485696, // 26: NOT_TAKEN - 2147484160, // 27: POP_EXCEPT - 2147484160, // 28: POP_ITER - 2147486208, // 29: POP_TOP - 2147483648, // 30: PUSH_EXC_INFO - 2147485696, // 31: PUSH_NULL - 2147615488, // 32: RETURN_GENERATOR - 2147615232, // 33: RETURN_VALUE - 2147484416, // 34: SETUP_ANNOTATIONS - 2147484416, // 35: STORE_SLICE - 2147484416, // 36: STORE_SUBSCR - 2147484416, // 37: TO_BOOL - 2147492608, // 38: UNARY_INVERT - 2147492608, // 39: UNARY_NEGATIVE - 2147483648, // 40: UNARY_NOT - 2147484416, // 41: WITH_EXCEPT_START - 2147754753, // 42: BINARY_OP - 2147484417, // 43: BUILD_INTERPOLATION - 2147492609, // 44: BUILD_LIST - 2147484417, // 45: BUILD_MAP - 2147484417, // 46: BUILD_SET - 2147484417, // 47: BUILD_SLICE - 2147484417, // 48: BUILD_STRING - 2147492097, // 49: BUILD_TUPLE - 2147627841, // 50: CALL - 2147492609, // 51: CALL_INTRINSIC_1 - 2147492609, // 52: CALL_INTRINSIC_2 - 2147627777, // 53: CALL_KW - 2147484417, // 54: COMPARE_OP - 2147492609, // 55: CONTAINS_OP - 2147484417, // 56: CONVERT_VALUE - 2147485697, // 57: COPY - 2147483649, // 58: COPY_FREE_VARS - 2147484421, // 59: DELETE_ATTR - 2147492625, // 60: DELETE_DEREF - 2147484449, // 61: DELETE_FAST - 2147492613, // 62: DELETE_GLOBAL - 2147492613, // 63: DELETE_NAME - 2147492609, // 64: DICT_MERGE - 2147492609, // 65: DICT_UPDATE - 2147689225, // 66: END_ASYNC_FOR - 2147483649, // 67: EXTENDED_ARG - 2147820297, // 68: FOR_ITER - 2147484417, // 69: GET_AWAITABLE - 2147746561, // 70: GET_ITER - 2147484421, // 71: IMPORT_FROM - 2147484421, // 72: IMPORT_NAME - 2147484161, // 73: IS_OP - 2147484489, // 74: JUMP_BACKWARD - 2147483657, // 75: JUMP_BACKWARD_NO_INTERRUPT - 2147483657, // 76: JUMP_FORWARD - 2147483905, // 77: LIST_APPEND - 2147492609, // 78: LIST_EXTEND - 2147484421, // 79: LOAD_ATTR - 2147483649, // 80: LOAD_COMMON_CONSTANT - 2147483651, // 81: LOAD_CONST - 2147484449, // 82: LOAD_DEREF - 2147485729, // 83: LOAD_FAST - 2147483681, // 84: LOAD_FAST_AND_CLEAR - 2147485729, // 85: LOAD_FAST_BORROW - 2147483681, // 86: LOAD_FAST_BORROW_LOAD_FAST_BORROW - 2147483937, // 87: LOAD_FAST_CHECK - 2147483681, // 88: LOAD_FAST_LOAD_FAST - 2147492625, // 89: LOAD_FROM_DICT_OR_DEREF - 2147484421, // 90: LOAD_FROM_DICT_OR_GLOBALS - 2147484421, // 91: LOAD_GLOBAL - 2147484421, // 92: LOAD_NAME - 2147483649, // 93: LOAD_SMALL_INT - 2147754753, // 94: LOAD_SPECIAL - 2147484421, // 95: LOAD_SUPER_ATTR - 2147492625, // 96: MAKE_CELL - 2147484417, // 97: MAP_ADD - 2147492609, // 98: MATCH_CLASS - 2147483657, // 99: POP_JUMP_IF_FALSE - 2147484169, // 100: POP_JUMP_IF_NONE - 2147484169, // 101: POP_JUMP_IF_NOT_NONE - 2147483657, // 102: POP_JUMP_IF_TRUE - 2147492097, // 103: RAISE_VARARGS - 2147492609, // 104: RERAISE - 2147627785, // 105: SEND - 2147484417, // 106: SET_ADD - 2147483649, // 107: SET_FUNCTION_ATTRIBUTE - 2147492609, // 108: SET_UPDATE - 2147484421, // 109: STORE_ATTR - 2147484177, // 110: STORE_DEREF - 2147484193, // 111: STORE_FAST - 2147484193, // 112: STORE_FAST_LOAD_FAST - 2147484193, // 113: STORE_FAST_STORE_FAST - 2147484421, // 114: STORE_GLOBAL - 2147484421, // 115: STORE_NAME - 2147485697, // 116: SWAP - 2147484417, // 117: UNPACK_EX - 2147484417, // 118: UNPACK_SEQUENCE - 2147615233, // 119: YIELD_VALUE - 0, 0, 0, 0, 0, 0, 0, 0, 2147492673, // 128: RESUME - 3221234944, // 129: BINARY_OP_ADD_FLOAT - 3221226496, // 130: BINARY_OP_ADD_INT - 3221234944, // 131: BINARY_OP_ADD_UNICODE - 3221235456, // 132: BINARY_OP_EXTEND - 3221234944, // 133: BINARY_OP_MULTIPLY_FLOAT - 3221226496, // 134: BINARY_OP_MULTIPLY_INT - 3221496704, // 135: BINARY_OP_SUBSCR_DICT - 3221623936, // 136: BINARY_OP_SUBSCR_GETITEM - 3221227008, // 137: BINARY_OP_SUBSCR_LIST_INT - 3221235456, // 138: BINARY_OP_SUBSCR_LIST_SLICE - 3221226496, // 139: BINARY_OP_SUBSCR_STR_INT - 3221227008, // 140: BINARY_OP_SUBSCR_TUPLE_INT - 3221226496, // 141: BINARY_OP_SUBSCR_USTR_INT - 3221234944, // 142: BINARY_OP_SUBTRACT_FLOAT - 3221226496, // 143: BINARY_OP_SUBTRACT_INT - 3221632897, // 144: CALL_ALLOC_AND_ENTER_INIT - 3221624449, // 145: CALL_BOUND_METHOD_EXACT_ARGS - 3221632897, // 146: CALL_BOUND_METHOD_GENERAL - 3221497665, // 147: CALL_BUILTIN_CLASS - 3221497665, // 148: CALL_BUILTIN_FAST - 3221497665, // 149: CALL_BUILTIN_FAST_WITH_KEYWORDS - 3221497665, // 150: CALL_BUILTIN_O - 3221235520, // 151: CALL_EX_NON_PY_GENERAL - 3221632897, // 152: CALL_EX_PY - 3221235456, // 153: CALL_ISINSTANCE - 3221624705, // 154: CALL_KW_BOUND_METHOD - 3221227329, // 155: CALL_KW_NON_PY - 3221624705, // 156: CALL_KW_PY - 3221235456, // 157: CALL_LEN - 3221235585, // 158: CALL_LIST_APPEND - 3221235521, // 159: CALL_METHOD_DESCRIPTOR_FAST - 3221497665, // 160: CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS - 3221497665, // 161: CALL_METHOD_DESCRIPTOR_NOARGS - 3221497665, // 162: CALL_METHOD_DESCRIPTOR_O - 3221489473, // 163: CALL_NON_PY_GENERAL - 3221623937, // 164: CALL_PY_EXACT_ARGS - 3221632897, // 165: CALL_PY_GENERAL - 3221235521, // 166: CALL_STR_1 - 3221235521, // 167: CALL_TUPLE_1 - 3221227009, // 168: CALL_TYPE_1 - 3221226497, // 169: COMPARE_OP_FLOAT - 3221226497, // 170: COMPARE_OP_INT - 3221226497, // 171: COMPARE_OP_STR - 3221235457, // 172: CONTAINS_OP_DICT - 3221235457, // 173: CONTAINS_OP_SET - 3221623937, // 174: FOR_ITER_GEN - 3221292681, // 175: FOR_ITER_LIST - 3221292297, // 176: FOR_ITER_RANGE - 3221292041, // 177: FOR_ITER_TUPLE - 3221301001, // 178: FOR_ITER_VIRTUAL - 3221488640, // 179: GET_ITER_SELF - 3221488640, // 180: GET_ITER_VIRTUAL - 3221226313, // 181: JUMP_BACKWARD_JIT - 3221226313, // 182: JUMP_BACKWARD_NO_JIT - 3221489153, // 183: LOAD_ATTR_CLASS - 3221489153, // 184: LOAD_ATTR_CLASS_WITH_METACLASS_CHECK - 3221623941, // 185: LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN - 3221489281, // 186: LOAD_ATTR_INSTANCE_VALUE - 3221488641, // 187: LOAD_ATTR_METHOD_LAZY_DICT - 3221488641, // 188: LOAD_ATTR_METHOD_NO_DICT - 3221488641, // 189: LOAD_ATTR_METHOD_WITH_VALUES - 3221227009, // 190: LOAD_ATTR_MODULE - 3221489153, // 191: LOAD_ATTR_NONDESCRIPTOR_NO_DICT - 3221489153, // 192: LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES - 3221623937, // 193: LOAD_ATTR_PROPERTY - 3221489153, // 194: LOAD_ATTR_SLOT - 3221489157, // 195: LOAD_ATTR_WITH_HINT - 3221225601, // 196: LOAD_GLOBAL_BUILTIN - 3221225601, // 197: LOAD_GLOBAL_MODULE - 3221227269, // 198: LOAD_SUPER_ATTR_ATTR - 3221497605, // 199: LOAD_SUPER_ATTR_METHOD - 3221225600, // 200: RESUME_CHECK - 3221225601, // 201: RESUME_CHECK_JIT - 3221301001, // 202: SEND_ASYNC_GEN - 3221623937, // 203: SEND_GEN - 3221301001, // 204: SEND_VIRTUAL - 3221489280, // 205: STORE_ATTR_INSTANCE_VALUE - 3221489280, // 206: STORE_ATTR_SLOT - 3221489285, // 207: STORE_ATTR_WITH_HINT - 3221488512, // 208: STORE_SUBSCR_DICT - 3221227136, // 209: STORE_SUBSCR_LIST_INT - 3221489152, // 210: TO_BOOL_ALWAYS_TRUE - 3221226496, // 211: TO_BOOL_BOOL - 3221226496, // 212: TO_BOOL_INT - 3221227008, // 213: TO_BOOL_LIST - 3221226496, // 214: TO_BOOL_NONE - 3221226496, // 215: TO_BOOL_STR - 3221227137, // 216: UNPACK_SEQUENCE_LIST - 3221227009, // 217: UNPACK_SEQUENCE_TUPLE - 3221227009, // 218: UNPACK_SEQUENCE_TWO_TUPLE - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2147508992, // 233: INSTRUMENTED_END_FOR - 2147484160, // 234: INSTRUMENTED_POP_ITER - 2147492608, // 235: INSTRUMENTED_END_SEND - 2147689225, // 236: INSTRUMENTED_FOR_ITER - 2147484416, // 237: INSTRUMENTED_INSTRUCTION - 2147483649, // 238: INSTRUMENTED_JUMP_FORWARD - 2147483648, // 239: INSTRUMENTED_NOT_TAKEN - 2147483649, // 240: INSTRUMENTED_POP_JUMP_IF_TRUE - 2147483649, // 241: INSTRUMENTED_POP_JUMP_IF_FALSE - 2147484161, // 242: INSTRUMENTED_POP_JUMP_IF_NONE - 2147484161, // 243: INSTRUMENTED_POP_JUMP_IF_NOT_NONE - 2147492673, // 244: INSTRUMENTED_RESUME - 2147615488, // 245: INSTRUMENTED_RETURN_VALUE - 2147623681, // 246: INSTRUMENTED_YIELD_VALUE - 2147689225, // 247: INSTRUMENTED_END_ASYNC_FOR - 2147484421, // 248: INSTRUMENTED_LOAD_SUPER_ATTR - 2147627841, // 249: INSTRUMENTED_CALL - 2147627777, // 250: INSTRUMENTED_CALL_KW - 2147627840, // 251: INSTRUMENTED_CALL_FUNCTION_EX - 2147484481, // 252: INSTRUMENTED_JUMP_BACKWARD - 2147488256, // 253: INSTRUMENTED_LINE - 2147483649, // 254: ENTER_EXECUTOR - 2147484417, // 255: TRACE_RECORD - 2147485696, // 256: ANNOTATIONS_PLACEHOLDER - 2147484489, // 257: JUMP - 2147484425, // 258: JUMP_IF_FALSE - 2147484425, // 259: JUMP_IF_TRUE - 2147483657, // 260: JUMP_NO_INTERRUPT - 2147485729, // 261: LOAD_CLOSURE - 2147485696, // 262: POP_BLOCK - 2684356609, // 263: SETUP_CLEANUP - 2684356609, // 264: SETUP_FINALLY - 2684356609, // 265: SETUP_WITH - 2147484193, // 266: STORE_FAST_MAYBE_NULL -]; -#[derive(Clone, Copy)] -pub struct PublicOpcode { - opcode: u16, - flags: u32, -} -impl PublicOpcode { - #[must_use] - pub fn from_i32(opcode: i32) -> Option { - let index = usize::try_from(opcode).ok()?; - let flags = *DESCRIPTORS.get(index)?; - (flags & (1 << 31) != 0).then_some(Self { - opcode: u16::try_from(index).ok()?, - flags, - }) - } - #[must_use] - pub const fn has_arg(self) -> bool { - self.flags & 1 != 0 - } - #[must_use] - pub const fn has_const(self) -> bool { - self.flags & 2 != 0 - } - #[must_use] - pub const fn has_name(self) -> bool { - self.flags & 4 != 0 - } - #[must_use] - pub const fn has_jump(self) -> bool { - self.flags & 8 != 0 - } - #[must_use] - pub const fn has_free(self) -> bool { - self.flags & 16 != 0 - } - #[must_use] - pub const fn has_local(self) -> bool { - self.flags & 32 != 0 - } - #[must_use] - pub const fn is_block_push(self) -> bool { - self.flags & (1 << 29) != 0 - } - /// Existing caller conversion is intentionally separate from this metadata domain. - #[must_use] - pub fn stack_effect(self, oparg: u32, jump: Option) -> Option { - if self.flags & (1 << 30) != 0 { - return None; - } - let oparg = oparg.cast_signed(); - let (popped, pushed) = match self.opcode { - 0 => (0i32, 0i32), // CACHE - 1 => (3i32, 1i32), // BINARY_SLICE - 2 => (2i32, 1i32), // BUILD_TEMPLATE - 4 => (4i32, 1i32), // CALL_FUNCTION_EX - 5 => (2i32, 2i32), // CHECK_EG_MATCH - 6 => (2i32, 2i32), // CHECK_EXC_MATCH - 7 => (4i32, 3i32), // CLEANUP_THROW - 8 => (2i32, 0i32), // DELETE_SUBSCR - 9 => (1i32, 0i32), // END_FOR - 10 => (3i32, 1i32), // END_SEND - 11 => (1i32, 0i32), // EXIT_INIT_CHECK - 12 => (1i32, 1i32), // FORMAT_SIMPLE - 13 => (2i32, 1i32), // FORMAT_WITH_SPEC - 14 => (1i32, 1i32), // GET_AITER - 15 => (1i32, 2i32), // GET_ANEXT - 16 => (1i32, 2i32), // GET_LEN - 17 => (0i32, 0i32), // RESERVED - 18 => (1i32, 0i32), // INTERPRETER_EXIT - 19 => (0i32, 1i32), // LOAD_BUILD_CLASS - 20 => (0i32, 1i32), // LOAD_LOCALS - 21 => (1i32, 1i32), // MAKE_FUNCTION - 22 => (2i32, 3i32), // MATCH_KEYS - 23 => (1i32, 2i32), // MATCH_MAPPING - 24 => (1i32, 2i32), // MATCH_SEQUENCE - 25 => (0i32, 0i32), // NOP - 26 => (0i32, 0i32), // NOT_TAKEN - 27 => (1i32, 0i32), // POP_EXCEPT - 28 => (2i32, 0i32), // POP_ITER - 29 => (1i32, 0i32), // POP_TOP - 30 => (1i32, 2i32), // PUSH_EXC_INFO - 31 => (0i32, 1i32), // PUSH_NULL - 32 => (0i32, 1i32), // RETURN_GENERATOR - 33 => (1i32, 1i32), // RETURN_VALUE - 34 => (0i32, 0i32), // SETUP_ANNOTATIONS - 35 => (4i32, 0i32), // STORE_SLICE - 36 => (3i32, 0i32), // STORE_SUBSCR - 37 => (1i32, 1i32), // TO_BOOL - 38 => (1i32, 1i32), // UNARY_INVERT - 39 => (1i32, 1i32), // UNARY_NEGATIVE - 40 => (1i32, 1i32), // UNARY_NOT - 41 => (5i32, 6i32), // WITH_EXCEPT_START - 42 => (2i32, 1i32), // BINARY_OP - 43 => ((2i32).wrapping_add(oparg & 1i32), 1i32), // BUILD_INTERPOLATION - 44 => (oparg, 1i32), // BUILD_LIST - 45 => ((oparg).wrapping_mul(2i32), 1i32), // BUILD_MAP - 46 => (oparg, 1i32), // BUILD_SET - 47 => (oparg, 1i32), // BUILD_SLICE - 48 => (oparg, 1i32), // BUILD_STRING - 49 => (oparg, 1i32), // BUILD_TUPLE - 50 => ((2i32).wrapping_add(oparg), 1i32), // CALL - 51 => (1i32, 1i32), // CALL_INTRINSIC_1 - 52 => (2i32, 1i32), // CALL_INTRINSIC_2 - 53 => ((3i32).wrapping_add(oparg), 1i32), // CALL_KW - 54 => (2i32, 1i32), // COMPARE_OP - 55 => (2i32, 1i32), // CONTAINS_OP - 56 => (1i32, 1i32), // CONVERT_VALUE - 57 => ( - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - (2i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // COPY - 58 => (0i32, 0i32), // COPY_FREE_VARS - 59 => (1i32, 0i32), // DELETE_ATTR - 60 => (0i32, 0i32), // DELETE_DEREF - 61 => (0i32, 0i32), // DELETE_FAST - 62 => (0i32, 0i32), // DELETE_GLOBAL - 63 => (0i32, 0i32), // DELETE_NAME - 64 => ( - (5i32).wrapping_add((oparg).wrapping_sub(1i32)), - (4i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // DICT_MERGE - 65 => ( - (2i32).wrapping_add((oparg).wrapping_sub(1i32)), - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // DICT_UPDATE - 66 => (2i32, 0i32), // END_ASYNC_FOR - 67 => (0i32, 0i32), // EXTENDED_ARG - 68 => (2i32, 3i32), // FOR_ITER - 69 => (1i32, 1i32), // GET_AWAITABLE - 70 => (1i32, 2i32), // GET_ITER - 71 => (1i32, 2i32), // IMPORT_FROM - 72 => (2i32, 1i32), // IMPORT_NAME - 73 => (2i32, 1i32), // IS_OP - 74 => (0i32, 0i32), // JUMP_BACKWARD - 75 => (0i32, 0i32), // JUMP_BACKWARD_NO_INTERRUPT - 76 => (0i32, 0i32), // JUMP_FORWARD - 77 => ( - (2i32).wrapping_add((oparg).wrapping_sub(1i32)), - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // LIST_APPEND - 78 => ( - (2i32).wrapping_add((oparg).wrapping_sub(1i32)), - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // LIST_EXTEND - 79 => (1i32, (1i32).wrapping_add(oparg & 1i32)), // LOAD_ATTR - 80 => (0i32, 1i32), // LOAD_COMMON_CONSTANT - 81 => (0i32, 1i32), // LOAD_CONST - 82 => (0i32, 1i32), // LOAD_DEREF - 83 => (0i32, 1i32), // LOAD_FAST - 84 => (0i32, 1i32), // LOAD_FAST_AND_CLEAR - 85 => (0i32, 1i32), // LOAD_FAST_BORROW - 86 => (0i32, 2i32), // LOAD_FAST_BORROW_LOAD_FAST_BORROW - 87 => (0i32, 1i32), // LOAD_FAST_CHECK - 88 => (0i32, 2i32), // LOAD_FAST_LOAD_FAST - 89 => (1i32, 1i32), // LOAD_FROM_DICT_OR_DEREF - 90 => (1i32, 1i32), // LOAD_FROM_DICT_OR_GLOBALS - 91 => (0i32, (1i32).wrapping_add(oparg & 1i32)), // LOAD_GLOBAL - 92 => (0i32, 1i32), // LOAD_NAME - 93 => (0i32, 1i32), // LOAD_SMALL_INT - 94 => (1i32, 2i32), // LOAD_SPECIAL - 95 => (3i32, (1i32).wrapping_add(oparg & 1i32)), // LOAD_SUPER_ATTR - 96 => (0i32, 0i32), // MAKE_CELL - 97 => ( - (3i32).wrapping_add((oparg).wrapping_sub(1i32)), - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // MAP_ADD - 98 => (3i32, 1i32), // MATCH_CLASS - 99 => (1i32, 0i32), // POP_JUMP_IF_FALSE - 100 => (1i32, 0i32), // POP_JUMP_IF_NONE - 101 => (1i32, 0i32), // POP_JUMP_IF_NOT_NONE - 102 => (1i32, 0i32), // POP_JUMP_IF_TRUE - 103 => (oparg, 0i32), // RAISE_VARARGS - 104 => ((1i32).wrapping_add(oparg), oparg), // RERAISE - 105 => (3i32, 3i32), // SEND - 106 => ( - (2i32).wrapping_add((oparg).wrapping_sub(1i32)), - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // SET_ADD - 107 => (2i32, 1i32), // SET_FUNCTION_ATTRIBUTE - 108 => ( - (2i32).wrapping_add((oparg).wrapping_sub(1i32)), - (1i32).wrapping_add((oparg).wrapping_sub(1i32)), - ), // SET_UPDATE - 109 => (2i32, 0i32), // STORE_ATTR - 110 => (1i32, 0i32), // STORE_DEREF - 111 => (1i32, 0i32), // STORE_FAST - 112 => (1i32, 1i32), // STORE_FAST_LOAD_FAST - 113 => (2i32, 0i32), // STORE_FAST_STORE_FAST - 114 => (1i32, 0i32), // STORE_GLOBAL - 115 => (1i32, 0i32), // STORE_NAME - 116 => ( - (2i32).wrapping_add((oparg).wrapping_sub(2i32)), - (2i32).wrapping_add((oparg).wrapping_sub(2i32)), - ), // SWAP - 117 => ( - 1i32, - ((1i32).wrapping_add(oparg & 255i32)).wrapping_add(oparg >> 8), - ), // UNPACK_EX - 118 => (1i32, oparg), // UNPACK_SEQUENCE - 119 => (1i32, 1i32), // YIELD_VALUE - 128 => (0i32, 0i32), // RESUME - 233 => (3i32, 2i32), // INSTRUMENTED_END_FOR - 234 => (2i32, 0i32), // INSTRUMENTED_POP_ITER - 235 => (3i32, 1i32), // INSTRUMENTED_END_SEND - 236 => (2i32, 3i32), // INSTRUMENTED_FOR_ITER - 237 => (0i32, 0i32), // INSTRUMENTED_INSTRUCTION - 238 => (0i32, 0i32), // INSTRUMENTED_JUMP_FORWARD - 239 => (0i32, 0i32), // INSTRUMENTED_NOT_TAKEN - 240 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_TRUE - 241 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_FALSE - 242 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_NONE - 243 => (1i32, 0i32), // INSTRUMENTED_POP_JUMP_IF_NOT_NONE - 244 => (0i32, 0i32), // INSTRUMENTED_RESUME - 245 => (1i32, 1i32), // INSTRUMENTED_RETURN_VALUE - 246 => (1i32, 1i32), // INSTRUMENTED_YIELD_VALUE - 247 => (2i32, 0i32), // INSTRUMENTED_END_ASYNC_FOR - 248 => (3i32, (1i32).wrapping_add(oparg & 1i32)), // INSTRUMENTED_LOAD_SUPER_ATTR - 249 => ((2i32).wrapping_add(oparg), 1i32), // INSTRUMENTED_CALL - 250 => ((3i32).wrapping_add(oparg), 1i32), // INSTRUMENTED_CALL_KW - 251 => (4i32, 1i32), // INSTRUMENTED_CALL_FUNCTION_EX - 252 => (0i32, 0i32), // INSTRUMENTED_JUMP_BACKWARD - 253 => (0i32, 0i32), // INSTRUMENTED_LINE - 254 => (0i32, 0i32), // ENTER_EXECUTOR - 255 => (0i32, 0i32), // TRACE_RECORD - 256 => (0i32, 0i32), // ANNOTATIONS_PLACEHOLDER - 257 => (0i32, 0i32), // JUMP - 258 => (1i32, 1i32), // JUMP_IF_FALSE - 259 => (1i32, 1i32), // JUMP_IF_TRUE - 260 => (0i32, 0i32), // JUMP_NO_INTERRUPT - 261 => (0i32, 1i32), // LOAD_CLOSURE - 262 => (0i32, 0i32), // POP_BLOCK - 263 => (0i32, 2i32), // SETUP_CLEANUP - 264 => (0i32, 1i32), // SETUP_FINALLY - 265 => (0i32, 1i32), // SETUP_WITH - 266 => (1i32, 0i32), // STORE_FAST_MAYBE_NULL - _ => return None, - }; - if popped < 0 || pushed < 0 { - return None; - } - if self.is_block_push() && jump == Some(false) { - return Some(0); - } - Some(pushed.wrapping_sub(popped)) - } -} diff --git a/crates/compiler-core/src/bytecode/target_stack_cases.rs b/crates/compiler-core/src/bytecode/target_stack_cases.rs deleted file mode 100644 index 64ccb5424a3..00000000000 --- a/crates/compiler-core/src/bytecode/target_stack_cases.rs +++ /dev/null @@ -1,3433 +0,0 @@ -// Generated by tools/opcode_metadata/verify_stack_contract.py from pinned CPython C metadata. -const TARGET_STACK_CASES: &[(u16, u32, u32, u32)] = &[ - (0, 0, 0, 0), - (0, 1, 0, 0), - (0, 2, 0, 0), - (0, 3, 0, 0), - (0, 4, 0, 0), - (0, 5, 0, 0), - (0, 7, 0, 0), - (0, 8, 0, 0), - (0, 15, 0, 0), - (0, 16, 0, 0), - (0, 31, 0, 0), - (0, 255, 0, 0), - (0, 256, 0, 0), - (0, 65535, 0, 0), - (1, 0, 3, 1), - (1, 1, 3, 1), - (1, 2, 3, 1), - (1, 3, 3, 1), - (1, 4, 3, 1), - (1, 5, 3, 1), - (1, 7, 3, 1), - (1, 8, 3, 1), - (1, 15, 3, 1), - (1, 16, 3, 1), - (1, 31, 3, 1), - (1, 255, 3, 1), - (1, 256, 3, 1), - (1, 65535, 3, 1), - (2, 0, 2, 1), - (2, 1, 2, 1), - (2, 2, 2, 1), - (2, 3, 2, 1), - (2, 4, 2, 1), - (2, 5, 2, 1), - (2, 7, 2, 1), - (2, 8, 2, 1), - (2, 15, 2, 1), - (2, 16, 2, 1), - (2, 31, 2, 1), - (2, 255, 2, 1), - (2, 256, 2, 1), - (2, 65535, 2, 1), - (3, 0, 2, 1), - (3, 1, 2, 1), - (3, 2, 2, 1), - (3, 3, 2, 1), - (3, 4, 2, 1), - (3, 5, 2, 1), - (3, 7, 2, 1), - (3, 8, 2, 1), - (3, 15, 2, 1), - (3, 16, 2, 1), - (3, 31, 2, 1), - (3, 255, 2, 1), - (3, 256, 2, 1), - (3, 65535, 2, 1), - (4, 0, 4, 1), - (4, 1, 4, 1), - (4, 2, 4, 1), - (4, 3, 4, 1), - (4, 4, 4, 1), - (4, 5, 4, 1), - (4, 7, 4, 1), - (4, 8, 4, 1), - (4, 15, 4, 1), - (4, 16, 4, 1), - (4, 31, 4, 1), - (4, 255, 4, 1), - (4, 256, 4, 1), - (4, 65535, 4, 1), - (5, 0, 2, 2), - (5, 1, 2, 2), - (5, 2, 2, 2), - (5, 3, 2, 2), - (5, 4, 2, 2), - (5, 5, 2, 2), - (5, 7, 2, 2), - (5, 8, 2, 2), - (5, 15, 2, 2), - (5, 16, 2, 2), - (5, 31, 2, 2), - (5, 255, 2, 2), - (5, 256, 2, 2), - (5, 65535, 2, 2), - (6, 0, 2, 2), - (6, 1, 2, 2), - (6, 2, 2, 2), - (6, 3, 2, 2), - (6, 4, 2, 2), - (6, 5, 2, 2), - (6, 7, 2, 2), - (6, 8, 2, 2), - (6, 15, 2, 2), - (6, 16, 2, 2), - (6, 31, 2, 2), - (6, 255, 2, 2), - (6, 256, 2, 2), - (6, 65535, 2, 2), - (7, 0, 4, 3), - (7, 1, 4, 3), - (7, 2, 4, 3), - (7, 3, 4, 3), - (7, 4, 4, 3), - (7, 5, 4, 3), - (7, 7, 4, 3), - (7, 8, 4, 3), - (7, 15, 4, 3), - (7, 16, 4, 3), - (7, 31, 4, 3), - (7, 255, 4, 3), - (7, 256, 4, 3), - (7, 65535, 4, 3), - (8, 0, 2, 0), - (8, 1, 2, 0), - (8, 2, 2, 0), - (8, 3, 2, 0), - (8, 4, 2, 0), - (8, 5, 2, 0), - (8, 7, 2, 0), - (8, 8, 2, 0), - (8, 15, 2, 0), - (8, 16, 2, 0), - (8, 31, 2, 0), - (8, 255, 2, 0), - (8, 256, 2, 0), - (8, 65535, 2, 0), - (9, 0, 1, 0), - (9, 1, 1, 0), - (9, 2, 1, 0), - (9, 3, 1, 0), - (9, 4, 1, 0), - (9, 5, 1, 0), - (9, 7, 1, 0), - (9, 8, 1, 0), - (9, 15, 1, 0), - (9, 16, 1, 0), - (9, 31, 1, 0), - (9, 255, 1, 0), - (9, 256, 1, 0), - (9, 65535, 1, 0), - (10, 0, 3, 1), - (10, 1, 3, 1), - (10, 2, 3, 1), - (10, 3, 3, 1), - (10, 4, 3, 1), - (10, 5, 3, 1), - (10, 7, 3, 1), - (10, 8, 3, 1), - (10, 15, 3, 1), - (10, 16, 3, 1), - (10, 31, 3, 1), - (10, 255, 3, 1), - (10, 256, 3, 1), - (10, 65535, 3, 1), - (11, 0, 1, 0), - (11, 1, 1, 0), - (11, 2, 1, 0), - (11, 3, 1, 0), - (11, 4, 1, 0), - (11, 5, 1, 0), - (11, 7, 1, 0), - (11, 8, 1, 0), - (11, 15, 1, 0), - (11, 16, 1, 0), - (11, 31, 1, 0), - (11, 255, 1, 0), - (11, 256, 1, 0), - (11, 65535, 1, 0), - (12, 0, 1, 1), - (12, 1, 1, 1), - (12, 2, 1, 1), - (12, 3, 1, 1), - (12, 4, 1, 1), - (12, 5, 1, 1), - (12, 7, 1, 1), - (12, 8, 1, 1), - (12, 15, 1, 1), - (12, 16, 1, 1), - (12, 31, 1, 1), - (12, 255, 1, 1), - (12, 256, 1, 1), - (12, 65535, 1, 1), - (13, 0, 2, 1), - (13, 1, 2, 1), - (13, 2, 2, 1), - (13, 3, 2, 1), - (13, 4, 2, 1), - (13, 5, 2, 1), - (13, 7, 2, 1), - (13, 8, 2, 1), - (13, 15, 2, 1), - (13, 16, 2, 1), - (13, 31, 2, 1), - (13, 255, 2, 1), - (13, 256, 2, 1), - (13, 65535, 2, 1), - (14, 0, 1, 1), - (14, 1, 1, 1), - (14, 2, 1, 1), - (14, 3, 1, 1), - (14, 4, 1, 1), - (14, 5, 1, 1), - (14, 7, 1, 1), - (14, 8, 1, 1), - (14, 15, 1, 1), - (14, 16, 1, 1), - (14, 31, 1, 1), - (14, 255, 1, 1), - (14, 256, 1, 1), - (14, 65535, 1, 1), - (15, 0, 1, 2), - (15, 1, 1, 2), - (15, 2, 1, 2), - (15, 3, 1, 2), - (15, 4, 1, 2), - (15, 5, 1, 2), - (15, 7, 1, 2), - (15, 8, 1, 2), - (15, 15, 1, 2), - (15, 16, 1, 2), - (15, 31, 1, 2), - (15, 255, 1, 2), - (15, 256, 1, 2), - (15, 65535, 1, 2), - (16, 0, 1, 2), - (16, 1, 1, 2), - (16, 2, 1, 2), - (16, 3, 1, 2), - (16, 4, 1, 2), - (16, 5, 1, 2), - (16, 7, 1, 2), - (16, 8, 1, 2), - (16, 15, 1, 2), - (16, 16, 1, 2), - (16, 31, 1, 2), - (16, 255, 1, 2), - (16, 256, 1, 2), - (16, 65535, 1, 2), - (17, 0, 0, 0), - (17, 1, 0, 0), - (17, 2, 0, 0), - (17, 3, 0, 0), - (17, 4, 0, 0), - (17, 5, 0, 0), - (17, 7, 0, 0), - (17, 8, 0, 0), - (17, 15, 0, 0), - (17, 16, 0, 0), - (17, 31, 0, 0), - (17, 255, 0, 0), - (17, 256, 0, 0), - (17, 65535, 0, 0), - (18, 0, 1, 0), - (18, 1, 1, 0), - (18, 2, 1, 0), - (18, 3, 1, 0), - (18, 4, 1, 0), - (18, 5, 1, 0), - (18, 7, 1, 0), - (18, 8, 1, 0), - (18, 15, 1, 0), - (18, 16, 1, 0), - (18, 31, 1, 0), - (18, 255, 1, 0), - (18, 256, 1, 0), - (18, 65535, 1, 0), - (19, 0, 0, 1), - (19, 1, 0, 1), - (19, 2, 0, 1), - (19, 3, 0, 1), - (19, 4, 0, 1), - (19, 5, 0, 1), - (19, 7, 0, 1), - (19, 8, 0, 1), - (19, 15, 0, 1), - (19, 16, 0, 1), - (19, 31, 0, 1), - (19, 255, 0, 1), - (19, 256, 0, 1), - (19, 65535, 0, 1), - (20, 0, 0, 1), - (20, 1, 0, 1), - (20, 2, 0, 1), - (20, 3, 0, 1), - (20, 4, 0, 1), - (20, 5, 0, 1), - (20, 7, 0, 1), - (20, 8, 0, 1), - (20, 15, 0, 1), - (20, 16, 0, 1), - (20, 31, 0, 1), - (20, 255, 0, 1), - (20, 256, 0, 1), - (20, 65535, 0, 1), - (21, 0, 1, 1), - (21, 1, 1, 1), - (21, 2, 1, 1), - (21, 3, 1, 1), - (21, 4, 1, 1), - (21, 5, 1, 1), - (21, 7, 1, 1), - (21, 8, 1, 1), - (21, 15, 1, 1), - (21, 16, 1, 1), - (21, 31, 1, 1), - (21, 255, 1, 1), - (21, 256, 1, 1), - (21, 65535, 1, 1), - (22, 0, 2, 3), - (22, 1, 2, 3), - (22, 2, 2, 3), - (22, 3, 2, 3), - (22, 4, 2, 3), - (22, 5, 2, 3), - (22, 7, 2, 3), - (22, 8, 2, 3), - (22, 15, 2, 3), - (22, 16, 2, 3), - (22, 31, 2, 3), - (22, 255, 2, 3), - (22, 256, 2, 3), - (22, 65535, 2, 3), - (23, 0, 1, 2), - (23, 1, 1, 2), - (23, 2, 1, 2), - (23, 3, 1, 2), - (23, 4, 1, 2), - (23, 5, 1, 2), - (23, 7, 1, 2), - (23, 8, 1, 2), - (23, 15, 1, 2), - (23, 16, 1, 2), - (23, 31, 1, 2), - (23, 255, 1, 2), - (23, 256, 1, 2), - (23, 65535, 1, 2), - (24, 0, 1, 2), - (24, 1, 1, 2), - (24, 2, 1, 2), - (24, 3, 1, 2), - (24, 4, 1, 2), - (24, 5, 1, 2), - (24, 7, 1, 2), - (24, 8, 1, 2), - (24, 15, 1, 2), - (24, 16, 1, 2), - (24, 31, 1, 2), - (24, 255, 1, 2), - (24, 256, 1, 2), - (24, 65535, 1, 2), - (25, 0, 0, 0), - (25, 1, 0, 0), - (25, 2, 0, 0), - (25, 3, 0, 0), - (25, 4, 0, 0), - (25, 5, 0, 0), - (25, 7, 0, 0), - (25, 8, 0, 0), - (25, 15, 0, 0), - (25, 16, 0, 0), - (25, 31, 0, 0), - (25, 255, 0, 0), - (25, 256, 0, 0), - (25, 65535, 0, 0), - (26, 0, 0, 0), - (26, 1, 0, 0), - (26, 2, 0, 0), - (26, 3, 0, 0), - (26, 4, 0, 0), - (26, 5, 0, 0), - (26, 7, 0, 0), - (26, 8, 0, 0), - (26, 15, 0, 0), - (26, 16, 0, 0), - (26, 31, 0, 0), - (26, 255, 0, 0), - (26, 256, 0, 0), - (26, 65535, 0, 0), - (27, 0, 1, 0), - (27, 1, 1, 0), - (27, 2, 1, 0), - (27, 3, 1, 0), - (27, 4, 1, 0), - (27, 5, 1, 0), - (27, 7, 1, 0), - (27, 8, 1, 0), - (27, 15, 1, 0), - (27, 16, 1, 0), - (27, 31, 1, 0), - (27, 255, 1, 0), - (27, 256, 1, 0), - (27, 65535, 1, 0), - (28, 0, 2, 0), - (28, 1, 2, 0), - (28, 2, 2, 0), - (28, 3, 2, 0), - (28, 4, 2, 0), - (28, 5, 2, 0), - (28, 7, 2, 0), - (28, 8, 2, 0), - (28, 15, 2, 0), - (28, 16, 2, 0), - (28, 31, 2, 0), - (28, 255, 2, 0), - (28, 256, 2, 0), - (28, 65535, 2, 0), - (29, 0, 1, 0), - (29, 1, 1, 0), - (29, 2, 1, 0), - (29, 3, 1, 0), - (29, 4, 1, 0), - (29, 5, 1, 0), - (29, 7, 1, 0), - (29, 8, 1, 0), - (29, 15, 1, 0), - (29, 16, 1, 0), - (29, 31, 1, 0), - (29, 255, 1, 0), - (29, 256, 1, 0), - (29, 65535, 1, 0), - (30, 0, 1, 2), - (30, 1, 1, 2), - (30, 2, 1, 2), - (30, 3, 1, 2), - (30, 4, 1, 2), - (30, 5, 1, 2), - (30, 7, 1, 2), - (30, 8, 1, 2), - (30, 15, 1, 2), - (30, 16, 1, 2), - (30, 31, 1, 2), - (30, 255, 1, 2), - (30, 256, 1, 2), - (30, 65535, 1, 2), - (31, 0, 0, 1), - (31, 1, 0, 1), - (31, 2, 0, 1), - (31, 3, 0, 1), - (31, 4, 0, 1), - (31, 5, 0, 1), - (31, 7, 0, 1), - (31, 8, 0, 1), - (31, 15, 0, 1), - (31, 16, 0, 1), - (31, 31, 0, 1), - (31, 255, 0, 1), - (31, 256, 0, 1), - (31, 65535, 0, 1), - (32, 0, 0, 1), - (32, 1, 0, 1), - (32, 2, 0, 1), - (32, 3, 0, 1), - (32, 4, 0, 1), - (32, 5, 0, 1), - (32, 7, 0, 1), - (32, 8, 0, 1), - (32, 15, 0, 1), - (32, 16, 0, 1), - (32, 31, 0, 1), - (32, 255, 0, 1), - (32, 256, 0, 1), - (32, 65535, 0, 1), - (33, 0, 1, 1), - (33, 1, 1, 1), - (33, 2, 1, 1), - (33, 3, 1, 1), - (33, 4, 1, 1), - (33, 5, 1, 1), - (33, 7, 1, 1), - (33, 8, 1, 1), - (33, 15, 1, 1), - (33, 16, 1, 1), - (33, 31, 1, 1), - (33, 255, 1, 1), - (33, 256, 1, 1), - (33, 65535, 1, 1), - (34, 0, 0, 0), - (34, 1, 0, 0), - (34, 2, 0, 0), - (34, 3, 0, 0), - (34, 4, 0, 0), - (34, 5, 0, 0), - (34, 7, 0, 0), - (34, 8, 0, 0), - (34, 15, 0, 0), - (34, 16, 0, 0), - (34, 31, 0, 0), - (34, 255, 0, 0), - (34, 256, 0, 0), - (34, 65535, 0, 0), - (35, 0, 4, 0), - (35, 1, 4, 0), - (35, 2, 4, 0), - (35, 3, 4, 0), - (35, 4, 4, 0), - (35, 5, 4, 0), - (35, 7, 4, 0), - (35, 8, 4, 0), - (35, 15, 4, 0), - (35, 16, 4, 0), - (35, 31, 4, 0), - (35, 255, 4, 0), - (35, 256, 4, 0), - (35, 65535, 4, 0), - (36, 0, 3, 0), - (36, 1, 3, 0), - (36, 2, 3, 0), - (36, 3, 3, 0), - (36, 4, 3, 0), - (36, 5, 3, 0), - (36, 7, 3, 0), - (36, 8, 3, 0), - (36, 15, 3, 0), - (36, 16, 3, 0), - (36, 31, 3, 0), - (36, 255, 3, 0), - (36, 256, 3, 0), - (36, 65535, 3, 0), - (37, 0, 1, 1), - (37, 1, 1, 1), - (37, 2, 1, 1), - (37, 3, 1, 1), - (37, 4, 1, 1), - (37, 5, 1, 1), - (37, 7, 1, 1), - (37, 8, 1, 1), - (37, 15, 1, 1), - (37, 16, 1, 1), - (37, 31, 1, 1), - (37, 255, 1, 1), - (37, 256, 1, 1), - (37, 65535, 1, 1), - (38, 0, 1, 1), - (38, 1, 1, 1), - (38, 2, 1, 1), - (38, 3, 1, 1), - (38, 4, 1, 1), - (38, 5, 1, 1), - (38, 7, 1, 1), - (38, 8, 1, 1), - (38, 15, 1, 1), - (38, 16, 1, 1), - (38, 31, 1, 1), - (38, 255, 1, 1), - (38, 256, 1, 1), - (38, 65535, 1, 1), - (39, 0, 1, 1), - (39, 1, 1, 1), - (39, 2, 1, 1), - (39, 3, 1, 1), - (39, 4, 1, 1), - (39, 5, 1, 1), - (39, 7, 1, 1), - (39, 8, 1, 1), - (39, 15, 1, 1), - (39, 16, 1, 1), - (39, 31, 1, 1), - (39, 255, 1, 1), - (39, 256, 1, 1), - (39, 65535, 1, 1), - (40, 0, 1, 1), - (40, 1, 1, 1), - (40, 2, 1, 1), - (40, 3, 1, 1), - (40, 4, 1, 1), - (40, 5, 1, 1), - (40, 7, 1, 1), - (40, 8, 1, 1), - (40, 15, 1, 1), - (40, 16, 1, 1), - (40, 31, 1, 1), - (40, 255, 1, 1), - (40, 256, 1, 1), - (40, 65535, 1, 1), - (41, 0, 5, 6), - (41, 1, 5, 6), - (41, 2, 5, 6), - (41, 3, 5, 6), - (41, 4, 5, 6), - (41, 5, 5, 6), - (41, 7, 5, 6), - (41, 8, 5, 6), - (41, 15, 5, 6), - (41, 16, 5, 6), - (41, 31, 5, 6), - (41, 255, 5, 6), - (41, 256, 5, 6), - (41, 65535, 5, 6), - (42, 0, 2, 1), - (42, 1, 2, 1), - (42, 2, 2, 1), - (42, 3, 2, 1), - (42, 4, 2, 1), - (42, 5, 2, 1), - (42, 7, 2, 1), - (42, 8, 2, 1), - (42, 15, 2, 1), - (42, 16, 2, 1), - (42, 31, 2, 1), - (42, 255, 2, 1), - (42, 256, 2, 1), - (42, 65535, 2, 1), - (43, 0, 2, 1), - (43, 1, 3, 1), - (43, 2, 2, 1), - (43, 3, 3, 1), - (43, 4, 2, 1), - (43, 5, 3, 1), - (43, 7, 3, 1), - (43, 8, 2, 1), - (43, 15, 3, 1), - (43, 16, 2, 1), - (43, 31, 3, 1), - (43, 255, 3, 1), - (43, 256, 2, 1), - (43, 65535, 3, 1), - (44, 0, 0, 1), - (44, 1, 1, 1), - (44, 2, 2, 1), - (44, 3, 3, 1), - (44, 4, 4, 1), - (44, 5, 5, 1), - (44, 7, 7, 1), - (44, 8, 8, 1), - (44, 15, 15, 1), - (44, 16, 16, 1), - (44, 31, 31, 1), - (44, 255, 255, 1), - (44, 256, 256, 1), - (44, 65535, 65535, 1), - (45, 0, 0, 1), - (45, 1, 2, 1), - (45, 2, 4, 1), - (45, 3, 6, 1), - (45, 4, 8, 1), - (45, 5, 10, 1), - (45, 7, 14, 1), - (45, 8, 16, 1), - (45, 15, 30, 1), - (45, 16, 32, 1), - (45, 31, 62, 1), - (45, 255, 510, 1), - (45, 256, 512, 1), - (45, 65535, 131070, 1), - (46, 0, 0, 1), - (46, 1, 1, 1), - (46, 2, 2, 1), - (46, 3, 3, 1), - (46, 4, 4, 1), - (46, 5, 5, 1), - (46, 7, 7, 1), - (46, 8, 8, 1), - (46, 15, 15, 1), - (46, 16, 16, 1), - (46, 31, 31, 1), - (46, 255, 255, 1), - (46, 256, 256, 1), - (46, 65535, 65535, 1), - (47, 0, 0, 1), - (47, 1, 1, 1), - (47, 2, 2, 1), - (47, 3, 3, 1), - (47, 4, 4, 1), - (47, 5, 5, 1), - (47, 7, 7, 1), - (47, 8, 8, 1), - (47, 15, 15, 1), - (47, 16, 16, 1), - (47, 31, 31, 1), - (47, 255, 255, 1), - (47, 256, 256, 1), - (47, 65535, 65535, 1), - (48, 0, 0, 1), - (48, 1, 1, 1), - (48, 2, 2, 1), - (48, 3, 3, 1), - (48, 4, 4, 1), - (48, 5, 5, 1), - (48, 7, 7, 1), - (48, 8, 8, 1), - (48, 15, 15, 1), - (48, 16, 16, 1), - (48, 31, 31, 1), - (48, 255, 255, 1), - (48, 256, 256, 1), - (48, 65535, 65535, 1), - (49, 0, 0, 1), - (49, 1, 1, 1), - (49, 2, 2, 1), - (49, 3, 3, 1), - (49, 4, 4, 1), - (49, 5, 5, 1), - (49, 7, 7, 1), - (49, 8, 8, 1), - (49, 15, 15, 1), - (49, 16, 16, 1), - (49, 31, 31, 1), - (49, 255, 255, 1), - (49, 256, 256, 1), - (49, 65535, 65535, 1), - (50, 0, 2, 1), - (50, 1, 3, 1), - (50, 2, 4, 1), - (50, 3, 5, 1), - (50, 4, 6, 1), - (50, 5, 7, 1), - (50, 7, 9, 1), - (50, 8, 10, 1), - (50, 15, 17, 1), - (50, 16, 18, 1), - (50, 31, 33, 1), - (50, 255, 257, 1), - (50, 256, 258, 1), - (50, 65535, 65537, 1), - (51, 0, 1, 1), - (51, 1, 1, 1), - (51, 2, 1, 1), - (51, 3, 1, 1), - (51, 4, 1, 1), - (51, 5, 1, 1), - (51, 7, 1, 1), - (51, 8, 1, 1), - (51, 15, 1, 1), - (51, 16, 1, 1), - (51, 31, 1, 1), - (51, 255, 1, 1), - (51, 256, 1, 1), - (51, 65535, 1, 1), - (52, 0, 2, 1), - (52, 1, 2, 1), - (52, 2, 2, 1), - (52, 3, 2, 1), - (52, 4, 2, 1), - (52, 5, 2, 1), - (52, 7, 2, 1), - (52, 8, 2, 1), - (52, 15, 2, 1), - (52, 16, 2, 1), - (52, 31, 2, 1), - (52, 255, 2, 1), - (52, 256, 2, 1), - (52, 65535, 2, 1), - (53, 0, 3, 1), - (53, 1, 4, 1), - (53, 2, 5, 1), - (53, 3, 6, 1), - (53, 4, 7, 1), - (53, 5, 8, 1), - (53, 7, 10, 1), - (53, 8, 11, 1), - (53, 15, 18, 1), - (53, 16, 19, 1), - (53, 31, 34, 1), - (53, 255, 258, 1), - (53, 256, 259, 1), - (53, 65535, 65538, 1), - (54, 0, 2, 1), - (54, 1, 2, 1), - (54, 2, 2, 1), - (54, 3, 2, 1), - (54, 4, 2, 1), - (54, 5, 2, 1), - (54, 7, 2, 1), - (54, 8, 2, 1), - (54, 15, 2, 1), - (54, 16, 2, 1), - (54, 31, 2, 1), - (54, 255, 2, 1), - (54, 256, 2, 1), - (54, 65535, 2, 1), - (55, 0, 2, 1), - (55, 1, 2, 1), - (55, 2, 2, 1), - (55, 3, 2, 1), - (55, 4, 2, 1), - (55, 5, 2, 1), - (55, 7, 2, 1), - (55, 8, 2, 1), - (55, 15, 2, 1), - (55, 16, 2, 1), - (55, 31, 2, 1), - (55, 255, 2, 1), - (55, 256, 2, 1), - (55, 65535, 2, 1), - (56, 0, 1, 1), - (56, 1, 1, 1), - (56, 2, 1, 1), - (56, 3, 1, 1), - (56, 4, 1, 1), - (56, 5, 1, 1), - (56, 7, 1, 1), - (56, 8, 1, 1), - (56, 15, 1, 1), - (56, 16, 1, 1), - (56, 31, 1, 1), - (56, 255, 1, 1), - (56, 256, 1, 1), - (56, 65535, 1, 1), - (57, 0, 0, 1), - (57, 1, 1, 2), - (57, 2, 2, 3), - (57, 3, 3, 4), - (57, 4, 4, 5), - (57, 5, 5, 6), - (57, 7, 7, 8), - (57, 8, 8, 9), - (57, 15, 15, 16), - (57, 16, 16, 17), - (57, 31, 31, 32), - (57, 255, 255, 256), - (57, 256, 256, 257), - (57, 65535, 65535, 65536), - (58, 0, 0, 0), - (58, 1, 0, 0), - (58, 2, 0, 0), - (58, 3, 0, 0), - (58, 4, 0, 0), - (58, 5, 0, 0), - (58, 7, 0, 0), - (58, 8, 0, 0), - (58, 15, 0, 0), - (58, 16, 0, 0), - (58, 31, 0, 0), - (58, 255, 0, 0), - (58, 256, 0, 0), - (58, 65535, 0, 0), - (59, 0, 1, 0), - (59, 1, 1, 0), - (59, 2, 1, 0), - (59, 3, 1, 0), - (59, 4, 1, 0), - (59, 5, 1, 0), - (59, 7, 1, 0), - (59, 8, 1, 0), - (59, 15, 1, 0), - (59, 16, 1, 0), - (59, 31, 1, 0), - (59, 255, 1, 0), - (59, 256, 1, 0), - (59, 65535, 1, 0), - (60, 0, 0, 0), - (60, 1, 0, 0), - (60, 2, 0, 0), - (60, 3, 0, 0), - (60, 4, 0, 0), - (60, 5, 0, 0), - (60, 7, 0, 0), - (60, 8, 0, 0), - (60, 15, 0, 0), - (60, 16, 0, 0), - (60, 31, 0, 0), - (60, 255, 0, 0), - (60, 256, 0, 0), - (60, 65535, 0, 0), - (61, 0, 0, 0), - (61, 1, 0, 0), - (61, 2, 0, 0), - (61, 3, 0, 0), - (61, 4, 0, 0), - (61, 5, 0, 0), - (61, 7, 0, 0), - (61, 8, 0, 0), - (61, 15, 0, 0), - (61, 16, 0, 0), - (61, 31, 0, 0), - (61, 255, 0, 0), - (61, 256, 0, 0), - (61, 65535, 0, 0), - (62, 0, 0, 0), - (62, 1, 0, 0), - (62, 2, 0, 0), - (62, 3, 0, 0), - (62, 4, 0, 0), - (62, 5, 0, 0), - (62, 7, 0, 0), - (62, 8, 0, 0), - (62, 15, 0, 0), - (62, 16, 0, 0), - (62, 31, 0, 0), - (62, 255, 0, 0), - (62, 256, 0, 0), - (62, 65535, 0, 0), - (63, 0, 0, 0), - (63, 1, 0, 0), - (63, 2, 0, 0), - (63, 3, 0, 0), - (63, 4, 0, 0), - (63, 5, 0, 0), - (63, 7, 0, 0), - (63, 8, 0, 0), - (63, 15, 0, 0), - (63, 16, 0, 0), - (63, 31, 0, 0), - (63, 255, 0, 0), - (63, 256, 0, 0), - (63, 65535, 0, 0), - (64, 0, 4, 3), - (64, 1, 5, 4), - (64, 2, 6, 5), - (64, 3, 7, 6), - (64, 4, 8, 7), - (64, 5, 9, 8), - (64, 7, 11, 10), - (64, 8, 12, 11), - (64, 15, 19, 18), - (64, 16, 20, 19), - (64, 31, 35, 34), - (64, 255, 259, 258), - (64, 256, 260, 259), - (64, 65535, 65539, 65538), - (65, 0, 1, 0), - (65, 1, 2, 1), - (65, 2, 3, 2), - (65, 3, 4, 3), - (65, 4, 5, 4), - (65, 5, 6, 5), - (65, 7, 8, 7), - (65, 8, 9, 8), - (65, 15, 16, 15), - (65, 16, 17, 16), - (65, 31, 32, 31), - (65, 255, 256, 255), - (65, 256, 257, 256), - (65, 65535, 65536, 65535), - (66, 0, 2, 0), - (66, 1, 2, 0), - (66, 2, 2, 0), - (66, 3, 2, 0), - (66, 4, 2, 0), - (66, 5, 2, 0), - (66, 7, 2, 0), - (66, 8, 2, 0), - (66, 15, 2, 0), - (66, 16, 2, 0), - (66, 31, 2, 0), - (66, 255, 2, 0), - (66, 256, 2, 0), - (66, 65535, 2, 0), - (67, 0, 0, 0), - (67, 1, 0, 0), - (67, 2, 0, 0), - (67, 3, 0, 0), - (67, 4, 0, 0), - (67, 5, 0, 0), - (67, 7, 0, 0), - (67, 8, 0, 0), - (67, 15, 0, 0), - (67, 16, 0, 0), - (67, 31, 0, 0), - (67, 255, 0, 0), - (67, 256, 0, 0), - (67, 65535, 0, 0), - (68, 0, 2, 3), - (68, 1, 2, 3), - (68, 2, 2, 3), - (68, 3, 2, 3), - (68, 4, 2, 3), - (68, 5, 2, 3), - (68, 7, 2, 3), - (68, 8, 2, 3), - (68, 15, 2, 3), - (68, 16, 2, 3), - (68, 31, 2, 3), - (68, 255, 2, 3), - (68, 256, 2, 3), - (68, 65535, 2, 3), - (69, 0, 1, 1), - (69, 1, 1, 1), - (69, 2, 1, 1), - (69, 3, 1, 1), - (69, 4, 1, 1), - (69, 5, 1, 1), - (69, 7, 1, 1), - (69, 8, 1, 1), - (69, 15, 1, 1), - (69, 16, 1, 1), - (69, 31, 1, 1), - (69, 255, 1, 1), - (69, 256, 1, 1), - (69, 65535, 1, 1), - (70, 0, 1, 2), - (70, 1, 1, 2), - (70, 2, 1, 2), - (70, 3, 1, 2), - (70, 4, 1, 2), - (70, 5, 1, 2), - (70, 7, 1, 2), - (70, 8, 1, 2), - (70, 15, 1, 2), - (70, 16, 1, 2), - (70, 31, 1, 2), - (70, 255, 1, 2), - (70, 256, 1, 2), - (70, 65535, 1, 2), - (71, 0, 1, 2), - (71, 1, 1, 2), - (71, 2, 1, 2), - (71, 3, 1, 2), - (71, 4, 1, 2), - (71, 5, 1, 2), - (71, 7, 1, 2), - (71, 8, 1, 2), - (71, 15, 1, 2), - (71, 16, 1, 2), - (71, 31, 1, 2), - (71, 255, 1, 2), - (71, 256, 1, 2), - (71, 65535, 1, 2), - (72, 0, 2, 1), - (72, 1, 2, 1), - (72, 2, 2, 1), - (72, 3, 2, 1), - (72, 4, 2, 1), - (72, 5, 2, 1), - (72, 7, 2, 1), - (72, 8, 2, 1), - (72, 15, 2, 1), - (72, 16, 2, 1), - (72, 31, 2, 1), - (72, 255, 2, 1), - (72, 256, 2, 1), - (72, 65535, 2, 1), - (73, 0, 2, 1), - (73, 1, 2, 1), - (73, 2, 2, 1), - (73, 3, 2, 1), - (73, 4, 2, 1), - (73, 5, 2, 1), - (73, 7, 2, 1), - (73, 8, 2, 1), - (73, 15, 2, 1), - (73, 16, 2, 1), - (73, 31, 2, 1), - (73, 255, 2, 1), - (73, 256, 2, 1), - (73, 65535, 2, 1), - (74, 0, 0, 0), - (74, 1, 0, 0), - (74, 2, 0, 0), - (74, 3, 0, 0), - (74, 4, 0, 0), - (74, 5, 0, 0), - (74, 7, 0, 0), - (74, 8, 0, 0), - (74, 15, 0, 0), - (74, 16, 0, 0), - (74, 31, 0, 0), - (74, 255, 0, 0), - (74, 256, 0, 0), - (74, 65535, 0, 0), - (75, 0, 0, 0), - (75, 1, 0, 0), - (75, 2, 0, 0), - (75, 3, 0, 0), - (75, 4, 0, 0), - (75, 5, 0, 0), - (75, 7, 0, 0), - (75, 8, 0, 0), - (75, 15, 0, 0), - (75, 16, 0, 0), - (75, 31, 0, 0), - (75, 255, 0, 0), - (75, 256, 0, 0), - (75, 65535, 0, 0), - (76, 0, 0, 0), - (76, 1, 0, 0), - (76, 2, 0, 0), - (76, 3, 0, 0), - (76, 4, 0, 0), - (76, 5, 0, 0), - (76, 7, 0, 0), - (76, 8, 0, 0), - (76, 15, 0, 0), - (76, 16, 0, 0), - (76, 31, 0, 0), - (76, 255, 0, 0), - (76, 256, 0, 0), - (76, 65535, 0, 0), - (77, 0, 1, 0), - (77, 1, 2, 1), - (77, 2, 3, 2), - (77, 3, 4, 3), - (77, 4, 5, 4), - (77, 5, 6, 5), - (77, 7, 8, 7), - (77, 8, 9, 8), - (77, 15, 16, 15), - (77, 16, 17, 16), - (77, 31, 32, 31), - (77, 255, 256, 255), - (77, 256, 257, 256), - (77, 65535, 65536, 65535), - (78, 0, 1, 0), - (78, 1, 2, 1), - (78, 2, 3, 2), - (78, 3, 4, 3), - (78, 4, 5, 4), - (78, 5, 6, 5), - (78, 7, 8, 7), - (78, 8, 9, 8), - (78, 15, 16, 15), - (78, 16, 17, 16), - (78, 31, 32, 31), - (78, 255, 256, 255), - (78, 256, 257, 256), - (78, 65535, 65536, 65535), - (79, 0, 1, 1), - (79, 1, 1, 2), - (79, 2, 1, 1), - (79, 3, 1, 2), - (79, 4, 1, 1), - (79, 5, 1, 2), - (79, 7, 1, 2), - (79, 8, 1, 1), - (79, 15, 1, 2), - (79, 16, 1, 1), - (79, 31, 1, 2), - (79, 255, 1, 2), - (79, 256, 1, 1), - (79, 65535, 1, 2), - (80, 0, 0, 1), - (80, 1, 0, 1), - (80, 2, 0, 1), - (80, 3, 0, 1), - (80, 4, 0, 1), - (80, 5, 0, 1), - (80, 7, 0, 1), - (80, 8, 0, 1), - (80, 15, 0, 1), - (80, 16, 0, 1), - (80, 31, 0, 1), - (80, 255, 0, 1), - (80, 256, 0, 1), - (80, 65535, 0, 1), - (81, 0, 0, 1), - (81, 1, 0, 1), - (81, 2, 0, 1), - (81, 3, 0, 1), - (81, 4, 0, 1), - (81, 5, 0, 1), - (81, 7, 0, 1), - (81, 8, 0, 1), - (81, 15, 0, 1), - (81, 16, 0, 1), - (81, 31, 0, 1), - (81, 255, 0, 1), - (81, 256, 0, 1), - (81, 65535, 0, 1), - (82, 0, 0, 1), - (82, 1, 0, 1), - (82, 2, 0, 1), - (82, 3, 0, 1), - (82, 4, 0, 1), - (82, 5, 0, 1), - (82, 7, 0, 1), - (82, 8, 0, 1), - (82, 15, 0, 1), - (82, 16, 0, 1), - (82, 31, 0, 1), - (82, 255, 0, 1), - (82, 256, 0, 1), - (82, 65535, 0, 1), - (83, 0, 0, 1), - (83, 1, 0, 1), - (83, 2, 0, 1), - (83, 3, 0, 1), - (83, 4, 0, 1), - (83, 5, 0, 1), - (83, 7, 0, 1), - (83, 8, 0, 1), - (83, 15, 0, 1), - (83, 16, 0, 1), - (83, 31, 0, 1), - (83, 255, 0, 1), - (83, 256, 0, 1), - (83, 65535, 0, 1), - (84, 0, 0, 1), - (84, 1, 0, 1), - (84, 2, 0, 1), - (84, 3, 0, 1), - (84, 4, 0, 1), - (84, 5, 0, 1), - (84, 7, 0, 1), - (84, 8, 0, 1), - (84, 15, 0, 1), - (84, 16, 0, 1), - (84, 31, 0, 1), - (84, 255, 0, 1), - (84, 256, 0, 1), - (84, 65535, 0, 1), - (85, 0, 0, 1), - (85, 1, 0, 1), - (85, 2, 0, 1), - (85, 3, 0, 1), - (85, 4, 0, 1), - (85, 5, 0, 1), - (85, 7, 0, 1), - (85, 8, 0, 1), - (85, 15, 0, 1), - (85, 16, 0, 1), - (85, 31, 0, 1), - (85, 255, 0, 1), - (85, 256, 0, 1), - (85, 65535, 0, 1), - (86, 0, 0, 2), - (86, 1, 0, 2), - (86, 2, 0, 2), - (86, 3, 0, 2), - (86, 4, 0, 2), - (86, 5, 0, 2), - (86, 7, 0, 2), - (86, 8, 0, 2), - (86, 15, 0, 2), - (86, 16, 0, 2), - (86, 31, 0, 2), - (86, 255, 0, 2), - (86, 256, 0, 2), - (86, 65535, 0, 2), - (87, 0, 0, 1), - (87, 1, 0, 1), - (87, 2, 0, 1), - (87, 3, 0, 1), - (87, 4, 0, 1), - (87, 5, 0, 1), - (87, 7, 0, 1), - (87, 8, 0, 1), - (87, 15, 0, 1), - (87, 16, 0, 1), - (87, 31, 0, 1), - (87, 255, 0, 1), - (87, 256, 0, 1), - (87, 65535, 0, 1), - (88, 0, 0, 2), - (88, 1, 0, 2), - (88, 2, 0, 2), - (88, 3, 0, 2), - (88, 4, 0, 2), - (88, 5, 0, 2), - (88, 7, 0, 2), - (88, 8, 0, 2), - (88, 15, 0, 2), - (88, 16, 0, 2), - (88, 31, 0, 2), - (88, 255, 0, 2), - (88, 256, 0, 2), - (88, 65535, 0, 2), - (89, 0, 1, 1), - (89, 1, 1, 1), - (89, 2, 1, 1), - (89, 3, 1, 1), - (89, 4, 1, 1), - (89, 5, 1, 1), - (89, 7, 1, 1), - (89, 8, 1, 1), - (89, 15, 1, 1), - (89, 16, 1, 1), - (89, 31, 1, 1), - (89, 255, 1, 1), - (89, 256, 1, 1), - (89, 65535, 1, 1), - (90, 0, 1, 1), - (90, 1, 1, 1), - (90, 2, 1, 1), - (90, 3, 1, 1), - (90, 4, 1, 1), - (90, 5, 1, 1), - (90, 7, 1, 1), - (90, 8, 1, 1), - (90, 15, 1, 1), - (90, 16, 1, 1), - (90, 31, 1, 1), - (90, 255, 1, 1), - (90, 256, 1, 1), - (90, 65535, 1, 1), - (91, 0, 0, 1), - (91, 1, 0, 2), - (91, 2, 0, 1), - (91, 3, 0, 2), - (91, 4, 0, 1), - (91, 5, 0, 2), - (91, 7, 0, 2), - (91, 8, 0, 1), - (91, 15, 0, 2), - (91, 16, 0, 1), - (91, 31, 0, 2), - (91, 255, 0, 2), - (91, 256, 0, 1), - (91, 65535, 0, 2), - (92, 0, 0, 1), - (92, 1, 0, 1), - (92, 2, 0, 1), - (92, 3, 0, 1), - (92, 4, 0, 1), - (92, 5, 0, 1), - (92, 7, 0, 1), - (92, 8, 0, 1), - (92, 15, 0, 1), - (92, 16, 0, 1), - (92, 31, 0, 1), - (92, 255, 0, 1), - (92, 256, 0, 1), - (92, 65535, 0, 1), - (93, 0, 0, 1), - (93, 1, 0, 1), - (93, 2, 0, 1), - (93, 3, 0, 1), - (93, 4, 0, 1), - (93, 5, 0, 1), - (93, 7, 0, 1), - (93, 8, 0, 1), - (93, 15, 0, 1), - (93, 16, 0, 1), - (93, 31, 0, 1), - (93, 255, 0, 1), - (93, 256, 0, 1), - (93, 65535, 0, 1), - (94, 0, 1, 2), - (94, 1, 1, 2), - (94, 2, 1, 2), - (94, 3, 1, 2), - (94, 4, 1, 2), - (94, 5, 1, 2), - (94, 7, 1, 2), - (94, 8, 1, 2), - (94, 15, 1, 2), - (94, 16, 1, 2), - (94, 31, 1, 2), - (94, 255, 1, 2), - (94, 256, 1, 2), - (94, 65535, 1, 2), - (95, 0, 3, 1), - (95, 1, 3, 2), - (95, 2, 3, 1), - (95, 3, 3, 2), - (95, 4, 3, 1), - (95, 5, 3, 2), - (95, 7, 3, 2), - (95, 8, 3, 1), - (95, 15, 3, 2), - (95, 16, 3, 1), - (95, 31, 3, 2), - (95, 255, 3, 2), - (95, 256, 3, 1), - (95, 65535, 3, 2), - (96, 0, 0, 0), - (96, 1, 0, 0), - (96, 2, 0, 0), - (96, 3, 0, 0), - (96, 4, 0, 0), - (96, 5, 0, 0), - (96, 7, 0, 0), - (96, 8, 0, 0), - (96, 15, 0, 0), - (96, 16, 0, 0), - (96, 31, 0, 0), - (96, 255, 0, 0), - (96, 256, 0, 0), - (96, 65535, 0, 0), - (97, 0, 2, 0), - (97, 1, 3, 1), - (97, 2, 4, 2), - (97, 3, 5, 3), - (97, 4, 6, 4), - (97, 5, 7, 5), - (97, 7, 9, 7), - (97, 8, 10, 8), - (97, 15, 17, 15), - (97, 16, 18, 16), - (97, 31, 33, 31), - (97, 255, 257, 255), - (97, 256, 258, 256), - (97, 65535, 65537, 65535), - (98, 0, 3, 1), - (98, 1, 3, 1), - (98, 2, 3, 1), - (98, 3, 3, 1), - (98, 4, 3, 1), - (98, 5, 3, 1), - (98, 7, 3, 1), - (98, 8, 3, 1), - (98, 15, 3, 1), - (98, 16, 3, 1), - (98, 31, 3, 1), - (98, 255, 3, 1), - (98, 256, 3, 1), - (98, 65535, 3, 1), - (99, 0, 1, 0), - (99, 1, 1, 0), - (99, 2, 1, 0), - (99, 3, 1, 0), - (99, 4, 1, 0), - (99, 5, 1, 0), - (99, 7, 1, 0), - (99, 8, 1, 0), - (99, 15, 1, 0), - (99, 16, 1, 0), - (99, 31, 1, 0), - (99, 255, 1, 0), - (99, 256, 1, 0), - (99, 65535, 1, 0), - (100, 0, 1, 0), - (100, 1, 1, 0), - (100, 2, 1, 0), - (100, 3, 1, 0), - (100, 4, 1, 0), - (100, 5, 1, 0), - (100, 7, 1, 0), - (100, 8, 1, 0), - (100, 15, 1, 0), - (100, 16, 1, 0), - (100, 31, 1, 0), - (100, 255, 1, 0), - (100, 256, 1, 0), - (100, 65535, 1, 0), - (101, 0, 1, 0), - (101, 1, 1, 0), - (101, 2, 1, 0), - (101, 3, 1, 0), - (101, 4, 1, 0), - (101, 5, 1, 0), - (101, 7, 1, 0), - (101, 8, 1, 0), - (101, 15, 1, 0), - (101, 16, 1, 0), - (101, 31, 1, 0), - (101, 255, 1, 0), - (101, 256, 1, 0), - (101, 65535, 1, 0), - (102, 0, 1, 0), - (102, 1, 1, 0), - (102, 2, 1, 0), - (102, 3, 1, 0), - (102, 4, 1, 0), - (102, 5, 1, 0), - (102, 7, 1, 0), - (102, 8, 1, 0), - (102, 15, 1, 0), - (102, 16, 1, 0), - (102, 31, 1, 0), - (102, 255, 1, 0), - (102, 256, 1, 0), - (102, 65535, 1, 0), - (103, 0, 0, 0), - (103, 1, 1, 0), - (103, 2, 2, 0), - (103, 3, 3, 0), - (103, 4, 4, 0), - (103, 5, 5, 0), - (103, 7, 7, 0), - (103, 8, 8, 0), - (103, 15, 15, 0), - (103, 16, 16, 0), - (103, 31, 31, 0), - (103, 255, 255, 0), - (103, 256, 256, 0), - (103, 65535, 65535, 0), - (104, 0, 1, 0), - (104, 1, 2, 1), - (104, 2, 3, 2), - (104, 3, 4, 3), - (104, 4, 5, 4), - (104, 5, 6, 5), - (104, 7, 8, 7), - (104, 8, 9, 8), - (104, 15, 16, 15), - (104, 16, 17, 16), - (104, 31, 32, 31), - (104, 255, 256, 255), - (104, 256, 257, 256), - (104, 65535, 65536, 65535), - (105, 0, 3, 3), - (105, 1, 3, 3), - (105, 2, 3, 3), - (105, 3, 3, 3), - (105, 4, 3, 3), - (105, 5, 3, 3), - (105, 7, 3, 3), - (105, 8, 3, 3), - (105, 15, 3, 3), - (105, 16, 3, 3), - (105, 31, 3, 3), - (105, 255, 3, 3), - (105, 256, 3, 3), - (105, 65535, 3, 3), - (106, 0, 1, 0), - (106, 1, 2, 1), - (106, 2, 3, 2), - (106, 3, 4, 3), - (106, 4, 5, 4), - (106, 5, 6, 5), - (106, 7, 8, 7), - (106, 8, 9, 8), - (106, 15, 16, 15), - (106, 16, 17, 16), - (106, 31, 32, 31), - (106, 255, 256, 255), - (106, 256, 257, 256), - (106, 65535, 65536, 65535), - (107, 0, 2, 1), - (107, 1, 2, 1), - (107, 2, 2, 1), - (107, 3, 2, 1), - (107, 4, 2, 1), - (107, 5, 2, 1), - (107, 7, 2, 1), - (107, 8, 2, 1), - (107, 15, 2, 1), - (107, 16, 2, 1), - (107, 31, 2, 1), - (107, 255, 2, 1), - (107, 256, 2, 1), - (107, 65535, 2, 1), - (108, 0, 1, 0), - (108, 1, 2, 1), - (108, 2, 3, 2), - (108, 3, 4, 3), - (108, 4, 5, 4), - (108, 5, 6, 5), - (108, 7, 8, 7), - (108, 8, 9, 8), - (108, 15, 16, 15), - (108, 16, 17, 16), - (108, 31, 32, 31), - (108, 255, 256, 255), - (108, 256, 257, 256), - (108, 65535, 65536, 65535), - (109, 0, 2, 0), - (109, 1, 2, 0), - (109, 2, 2, 0), - (109, 3, 2, 0), - (109, 4, 2, 0), - (109, 5, 2, 0), - (109, 7, 2, 0), - (109, 8, 2, 0), - (109, 15, 2, 0), - (109, 16, 2, 0), - (109, 31, 2, 0), - (109, 255, 2, 0), - (109, 256, 2, 0), - (109, 65535, 2, 0), - (110, 0, 1, 0), - (110, 1, 1, 0), - (110, 2, 1, 0), - (110, 3, 1, 0), - (110, 4, 1, 0), - (110, 5, 1, 0), - (110, 7, 1, 0), - (110, 8, 1, 0), - (110, 15, 1, 0), - (110, 16, 1, 0), - (110, 31, 1, 0), - (110, 255, 1, 0), - (110, 256, 1, 0), - (110, 65535, 1, 0), - (111, 0, 1, 0), - (111, 1, 1, 0), - (111, 2, 1, 0), - (111, 3, 1, 0), - (111, 4, 1, 0), - (111, 5, 1, 0), - (111, 7, 1, 0), - (111, 8, 1, 0), - (111, 15, 1, 0), - (111, 16, 1, 0), - (111, 31, 1, 0), - (111, 255, 1, 0), - (111, 256, 1, 0), - (111, 65535, 1, 0), - (112, 0, 1, 1), - (112, 1, 1, 1), - (112, 2, 1, 1), - (112, 3, 1, 1), - (112, 4, 1, 1), - (112, 5, 1, 1), - (112, 7, 1, 1), - (112, 8, 1, 1), - (112, 15, 1, 1), - (112, 16, 1, 1), - (112, 31, 1, 1), - (112, 255, 1, 1), - (112, 256, 1, 1), - (112, 65535, 1, 1), - (113, 0, 2, 0), - (113, 1, 2, 0), - (113, 2, 2, 0), - (113, 3, 2, 0), - (113, 4, 2, 0), - (113, 5, 2, 0), - (113, 7, 2, 0), - (113, 8, 2, 0), - (113, 15, 2, 0), - (113, 16, 2, 0), - (113, 31, 2, 0), - (113, 255, 2, 0), - (113, 256, 2, 0), - (113, 65535, 2, 0), - (114, 0, 1, 0), - (114, 1, 1, 0), - (114, 2, 1, 0), - (114, 3, 1, 0), - (114, 4, 1, 0), - (114, 5, 1, 0), - (114, 7, 1, 0), - (114, 8, 1, 0), - (114, 15, 1, 0), - (114, 16, 1, 0), - (114, 31, 1, 0), - (114, 255, 1, 0), - (114, 256, 1, 0), - (114, 65535, 1, 0), - (115, 0, 1, 0), - (115, 1, 1, 0), - (115, 2, 1, 0), - (115, 3, 1, 0), - (115, 4, 1, 0), - (115, 5, 1, 0), - (115, 7, 1, 0), - (115, 8, 1, 0), - (115, 15, 1, 0), - (115, 16, 1, 0), - (115, 31, 1, 0), - (115, 255, 1, 0), - (115, 256, 1, 0), - (115, 65535, 1, 0), - (116, 0, 0, 0), - (116, 1, 1, 1), - (116, 2, 2, 2), - (116, 3, 3, 3), - (116, 4, 4, 4), - (116, 5, 5, 5), - (116, 7, 7, 7), - (116, 8, 8, 8), - (116, 15, 15, 15), - (116, 16, 16, 16), - (116, 31, 31, 31), - (116, 255, 255, 255), - (116, 256, 256, 256), - (116, 65535, 65535, 65535), - (117, 0, 1, 1), - (117, 1, 1, 2), - (117, 2, 1, 3), - (117, 3, 1, 4), - (117, 4, 1, 5), - (117, 5, 1, 6), - (117, 7, 1, 8), - (117, 8, 1, 9), - (117, 15, 1, 16), - (117, 16, 1, 17), - (117, 31, 1, 32), - (117, 255, 1, 256), - (117, 256, 1, 2), - (117, 65535, 1, 511), - (118, 0, 1, 0), - (118, 1, 1, 1), - (118, 2, 1, 2), - (118, 3, 1, 3), - (118, 4, 1, 4), - (118, 5, 1, 5), - (118, 7, 1, 7), - (118, 8, 1, 8), - (118, 15, 1, 15), - (118, 16, 1, 16), - (118, 31, 1, 31), - (118, 255, 1, 255), - (118, 256, 1, 256), - (118, 65535, 1, 65535), - (119, 0, 1, 1), - (119, 1, 1, 1), - (119, 2, 1, 1), - (119, 3, 1, 1), - (119, 4, 1, 1), - (119, 5, 1, 1), - (119, 7, 1, 1), - (119, 8, 1, 1), - (119, 15, 1, 1), - (119, 16, 1, 1), - (119, 31, 1, 1), - (119, 255, 1, 1), - (119, 256, 1, 1), - (119, 65535, 1, 1), - (128, 0, 0, 0), - (128, 1, 0, 0), - (128, 2, 0, 0), - (128, 3, 0, 0), - (128, 4, 0, 0), - (128, 5, 0, 0), - (128, 7, 0, 0), - (128, 8, 0, 0), - (128, 15, 0, 0), - (128, 16, 0, 0), - (128, 31, 0, 0), - (128, 255, 0, 0), - (128, 256, 0, 0), - (128, 65535, 0, 0), - (129, 0, 2, 1), - (129, 1, 2, 1), - (129, 2, 2, 1), - (129, 3, 2, 1), - (129, 4, 2, 1), - (129, 5, 2, 1), - (129, 7, 2, 1), - (129, 8, 2, 1), - (129, 15, 2, 1), - (129, 16, 2, 1), - (129, 31, 2, 1), - (129, 255, 2, 1), - (129, 256, 2, 1), - (129, 65535, 2, 1), - (130, 0, 2, 1), - (130, 1, 2, 1), - (130, 2, 2, 1), - (130, 3, 2, 1), - (130, 4, 2, 1), - (130, 5, 2, 1), - (130, 7, 2, 1), - (130, 8, 2, 1), - (130, 15, 2, 1), - (130, 16, 2, 1), - (130, 31, 2, 1), - (130, 255, 2, 1), - (130, 256, 2, 1), - (130, 65535, 2, 1), - (131, 0, 2, 1), - (131, 1, 2, 1), - (131, 2, 2, 1), - (131, 3, 2, 1), - (131, 4, 2, 1), - (131, 5, 2, 1), - (131, 7, 2, 1), - (131, 8, 2, 1), - (131, 15, 2, 1), - (131, 16, 2, 1), - (131, 31, 2, 1), - (131, 255, 2, 1), - (131, 256, 2, 1), - (131, 65535, 2, 1), - (132, 0, 2, 1), - (132, 1, 2, 1), - (132, 2, 2, 1), - (132, 3, 2, 1), - (132, 4, 2, 1), - (132, 5, 2, 1), - (132, 7, 2, 1), - (132, 8, 2, 1), - (132, 15, 2, 1), - (132, 16, 2, 1), - (132, 31, 2, 1), - (132, 255, 2, 1), - (132, 256, 2, 1), - (132, 65535, 2, 1), - (133, 0, 2, 1), - (133, 1, 2, 1), - (133, 2, 2, 1), - (133, 3, 2, 1), - (133, 4, 2, 1), - (133, 5, 2, 1), - (133, 7, 2, 1), - (133, 8, 2, 1), - (133, 15, 2, 1), - (133, 16, 2, 1), - (133, 31, 2, 1), - (133, 255, 2, 1), - (133, 256, 2, 1), - (133, 65535, 2, 1), - (134, 0, 2, 1), - (134, 1, 2, 1), - (134, 2, 2, 1), - (134, 3, 2, 1), - (134, 4, 2, 1), - (134, 5, 2, 1), - (134, 7, 2, 1), - (134, 8, 2, 1), - (134, 15, 2, 1), - (134, 16, 2, 1), - (134, 31, 2, 1), - (134, 255, 2, 1), - (134, 256, 2, 1), - (134, 65535, 2, 1), - (135, 0, 2, 1), - (135, 1, 2, 1), - (135, 2, 2, 1), - (135, 3, 2, 1), - (135, 4, 2, 1), - (135, 5, 2, 1), - (135, 7, 2, 1), - (135, 8, 2, 1), - (135, 15, 2, 1), - (135, 16, 2, 1), - (135, 31, 2, 1), - (135, 255, 2, 1), - (135, 256, 2, 1), - (135, 65535, 2, 1), - (136, 0, 2, 0), - (136, 1, 2, 0), - (136, 2, 2, 0), - (136, 3, 2, 0), - (136, 4, 2, 0), - (136, 5, 2, 0), - (136, 7, 2, 0), - (136, 8, 2, 0), - (136, 15, 2, 0), - (136, 16, 2, 0), - (136, 31, 2, 0), - (136, 255, 2, 0), - (136, 256, 2, 0), - (136, 65535, 2, 0), - (137, 0, 2, 1), - (137, 1, 2, 1), - (137, 2, 2, 1), - (137, 3, 2, 1), - (137, 4, 2, 1), - (137, 5, 2, 1), - (137, 7, 2, 1), - (137, 8, 2, 1), - (137, 15, 2, 1), - (137, 16, 2, 1), - (137, 31, 2, 1), - (137, 255, 2, 1), - (137, 256, 2, 1), - (137, 65535, 2, 1), - (138, 0, 2, 1), - (138, 1, 2, 1), - (138, 2, 2, 1), - (138, 3, 2, 1), - (138, 4, 2, 1), - (138, 5, 2, 1), - (138, 7, 2, 1), - (138, 8, 2, 1), - (138, 15, 2, 1), - (138, 16, 2, 1), - (138, 31, 2, 1), - (138, 255, 2, 1), - (138, 256, 2, 1), - (138, 65535, 2, 1), - (139, 0, 2, 1), - (139, 1, 2, 1), - (139, 2, 2, 1), - (139, 3, 2, 1), - (139, 4, 2, 1), - (139, 5, 2, 1), - (139, 7, 2, 1), - (139, 8, 2, 1), - (139, 15, 2, 1), - (139, 16, 2, 1), - (139, 31, 2, 1), - (139, 255, 2, 1), - (139, 256, 2, 1), - (139, 65535, 2, 1), - (140, 0, 2, 1), - (140, 1, 2, 1), - (140, 2, 2, 1), - (140, 3, 2, 1), - (140, 4, 2, 1), - (140, 5, 2, 1), - (140, 7, 2, 1), - (140, 8, 2, 1), - (140, 15, 2, 1), - (140, 16, 2, 1), - (140, 31, 2, 1), - (140, 255, 2, 1), - (140, 256, 2, 1), - (140, 65535, 2, 1), - (141, 0, 2, 1), - (141, 1, 2, 1), - (141, 2, 2, 1), - (141, 3, 2, 1), - (141, 4, 2, 1), - (141, 5, 2, 1), - (141, 7, 2, 1), - (141, 8, 2, 1), - (141, 15, 2, 1), - (141, 16, 2, 1), - (141, 31, 2, 1), - (141, 255, 2, 1), - (141, 256, 2, 1), - (141, 65535, 2, 1), - (142, 0, 2, 1), - (142, 1, 2, 1), - (142, 2, 2, 1), - (142, 3, 2, 1), - (142, 4, 2, 1), - (142, 5, 2, 1), - (142, 7, 2, 1), - (142, 8, 2, 1), - (142, 15, 2, 1), - (142, 16, 2, 1), - (142, 31, 2, 1), - (142, 255, 2, 1), - (142, 256, 2, 1), - (142, 65535, 2, 1), - (143, 0, 2, 1), - (143, 1, 2, 1), - (143, 2, 2, 1), - (143, 3, 2, 1), - (143, 4, 2, 1), - (143, 5, 2, 1), - (143, 7, 2, 1), - (143, 8, 2, 1), - (143, 15, 2, 1), - (143, 16, 2, 1), - (143, 31, 2, 1), - (143, 255, 2, 1), - (143, 256, 2, 1), - (143, 65535, 2, 1), - (144, 0, 2, 0), - (144, 1, 3, 0), - (144, 2, 4, 0), - (144, 3, 5, 0), - (144, 4, 6, 0), - (144, 5, 7, 0), - (144, 7, 9, 0), - (144, 8, 10, 0), - (144, 15, 17, 0), - (144, 16, 18, 0), - (144, 31, 33, 0), - (144, 255, 257, 0), - (144, 256, 258, 0), - (144, 65535, 65537, 0), - (145, 0, 2, 0), - (145, 1, 3, 0), - (145, 2, 4, 0), - (145, 3, 5, 0), - (145, 4, 6, 0), - (145, 5, 7, 0), - (145, 7, 9, 0), - (145, 8, 10, 0), - (145, 15, 17, 0), - (145, 16, 18, 0), - (145, 31, 33, 0), - (145, 255, 257, 0), - (145, 256, 258, 0), - (145, 65535, 65537, 0), - (146, 0, 2, 0), - (146, 1, 3, 0), - (146, 2, 4, 0), - (146, 3, 5, 0), - (146, 4, 6, 0), - (146, 5, 7, 0), - (146, 7, 9, 0), - (146, 8, 10, 0), - (146, 15, 17, 0), - (146, 16, 18, 0), - (146, 31, 33, 0), - (146, 255, 257, 0), - (146, 256, 258, 0), - (146, 65535, 65537, 0), - (147, 0, 2, 1), - (147, 1, 3, 1), - (147, 2, 4, 1), - (147, 3, 5, 1), - (147, 4, 6, 1), - (147, 5, 7, 1), - (147, 7, 9, 1), - (147, 8, 10, 1), - (147, 15, 17, 1), - (147, 16, 18, 1), - (147, 31, 33, 1), - (147, 255, 257, 1), - (147, 256, 258, 1), - (147, 65535, 65537, 1), - (148, 0, 2, 1), - (148, 1, 3, 1), - (148, 2, 4, 1), - (148, 3, 5, 1), - (148, 4, 6, 1), - (148, 5, 7, 1), - (148, 7, 9, 1), - (148, 8, 10, 1), - (148, 15, 17, 1), - (148, 16, 18, 1), - (148, 31, 33, 1), - (148, 255, 257, 1), - (148, 256, 258, 1), - (148, 65535, 65537, 1), - (149, 0, 2, 1), - (149, 1, 3, 1), - (149, 2, 4, 1), - (149, 3, 5, 1), - (149, 4, 6, 1), - (149, 5, 7, 1), - (149, 7, 9, 1), - (149, 8, 10, 1), - (149, 15, 17, 1), - (149, 16, 18, 1), - (149, 31, 33, 1), - (149, 255, 257, 1), - (149, 256, 258, 1), - (149, 65535, 65537, 1), - (150, 0, 2, 1), - (150, 1, 3, 1), - (150, 2, 4, 1), - (150, 3, 5, 1), - (150, 4, 6, 1), - (150, 5, 7, 1), - (150, 7, 9, 1), - (150, 8, 10, 1), - (150, 15, 17, 1), - (150, 16, 18, 1), - (150, 31, 33, 1), - (150, 255, 257, 1), - (150, 256, 258, 1), - (150, 65535, 65537, 1), - (151, 0, 4, 1), - (151, 1, 4, 1), - (151, 2, 4, 1), - (151, 3, 4, 1), - (151, 4, 4, 1), - (151, 5, 4, 1), - (151, 7, 4, 1), - (151, 8, 4, 1), - (151, 15, 4, 1), - (151, 16, 4, 1), - (151, 31, 4, 1), - (151, 255, 4, 1), - (151, 256, 4, 1), - (151, 65535, 4, 1), - (152, 0, 4, 0), - (152, 1, 4, 0), - (152, 2, 4, 0), - (152, 3, 4, 0), - (152, 4, 4, 0), - (152, 5, 4, 0), - (152, 7, 4, 0), - (152, 8, 4, 0), - (152, 15, 4, 0), - (152, 16, 4, 0), - (152, 31, 4, 0), - (152, 255, 4, 0), - (152, 256, 4, 0), - (152, 65535, 4, 0), - (153, 0, 4, 1), - (153, 1, 4, 1), - (153, 2, 4, 1), - (153, 3, 4, 1), - (153, 4, 4, 1), - (153, 5, 4, 1), - (153, 7, 4, 1), - (153, 8, 4, 1), - (153, 15, 4, 1), - (153, 16, 4, 1), - (153, 31, 4, 1), - (153, 255, 4, 1), - (153, 256, 4, 1), - (153, 65535, 4, 1), - (154, 0, 3, 0), - (154, 1, 4, 0), - (154, 2, 5, 0), - (154, 3, 6, 0), - (154, 4, 7, 0), - (154, 5, 8, 0), - (154, 7, 10, 0), - (154, 8, 11, 0), - (154, 15, 18, 0), - (154, 16, 19, 0), - (154, 31, 34, 0), - (154, 255, 258, 0), - (154, 256, 259, 0), - (154, 65535, 65538, 0), - (155, 0, 3, 1), - (155, 1, 4, 1), - (155, 2, 5, 1), - (155, 3, 6, 1), - (155, 4, 7, 1), - (155, 5, 8, 1), - (155, 7, 10, 1), - (155, 8, 11, 1), - (155, 15, 18, 1), - (155, 16, 19, 1), - (155, 31, 34, 1), - (155, 255, 258, 1), - (155, 256, 259, 1), - (155, 65535, 65538, 1), - (156, 0, 3, 0), - (156, 1, 4, 0), - (156, 2, 5, 0), - (156, 3, 6, 0), - (156, 4, 7, 0), - (156, 5, 8, 0), - (156, 7, 10, 0), - (156, 8, 11, 0), - (156, 15, 18, 0), - (156, 16, 19, 0), - (156, 31, 34, 0), - (156, 255, 258, 0), - (156, 256, 259, 0), - (156, 65535, 65538, 0), - (157, 0, 3, 1), - (157, 1, 3, 1), - (157, 2, 3, 1), - (157, 3, 3, 1), - (157, 4, 3, 1), - (157, 5, 3, 1), - (157, 7, 3, 1), - (157, 8, 3, 1), - (157, 15, 3, 1), - (157, 16, 3, 1), - (157, 31, 3, 1), - (157, 255, 3, 1), - (157, 256, 3, 1), - (157, 65535, 3, 1), - (158, 0, 3, 1), - (158, 1, 3, 1), - (158, 2, 3, 1), - (158, 3, 3, 1), - (158, 4, 3, 1), - (158, 5, 3, 1), - (158, 7, 3, 1), - (158, 8, 3, 1), - (158, 15, 3, 1), - (158, 16, 3, 1), - (158, 31, 3, 1), - (158, 255, 3, 1), - (158, 256, 3, 1), - (158, 65535, 3, 1), - (159, 0, 2, 1), - (159, 1, 3, 1), - (159, 2, 4, 1), - (159, 3, 5, 1), - (159, 4, 6, 1), - (159, 5, 7, 1), - (159, 7, 9, 1), - (159, 8, 10, 1), - (159, 15, 17, 1), - (159, 16, 18, 1), - (159, 31, 33, 1), - (159, 255, 257, 1), - (159, 256, 258, 1), - (159, 65535, 65537, 1), - (160, 0, 2, 1), - (160, 1, 3, 1), - (160, 2, 4, 1), - (160, 3, 5, 1), - (160, 4, 6, 1), - (160, 5, 7, 1), - (160, 7, 9, 1), - (160, 8, 10, 1), - (160, 15, 17, 1), - (160, 16, 18, 1), - (160, 31, 33, 1), - (160, 255, 257, 1), - (160, 256, 258, 1), - (160, 65535, 65537, 1), - (161, 0, 2, 1), - (161, 1, 3, 1), - (161, 2, 4, 1), - (161, 3, 5, 1), - (161, 4, 6, 1), - (161, 5, 7, 1), - (161, 7, 9, 1), - (161, 8, 10, 1), - (161, 15, 17, 1), - (161, 16, 18, 1), - (161, 31, 33, 1), - (161, 255, 257, 1), - (161, 256, 258, 1), - (161, 65535, 65537, 1), - (162, 0, 2, 1), - (162, 1, 3, 1), - (162, 2, 4, 1), - (162, 3, 5, 1), - (162, 4, 6, 1), - (162, 5, 7, 1), - (162, 7, 9, 1), - (162, 8, 10, 1), - (162, 15, 17, 1), - (162, 16, 18, 1), - (162, 31, 33, 1), - (162, 255, 257, 1), - (162, 256, 258, 1), - (162, 65535, 65537, 1), - (163, 0, 2, 1), - (163, 1, 3, 1), - (163, 2, 4, 1), - (163, 3, 5, 1), - (163, 4, 6, 1), - (163, 5, 7, 1), - (163, 7, 9, 1), - (163, 8, 10, 1), - (163, 15, 17, 1), - (163, 16, 18, 1), - (163, 31, 33, 1), - (163, 255, 257, 1), - (163, 256, 258, 1), - (163, 65535, 65537, 1), - (164, 0, 2, 0), - (164, 1, 3, 0), - (164, 2, 4, 0), - (164, 3, 5, 0), - (164, 4, 6, 0), - (164, 5, 7, 0), - (164, 7, 9, 0), - (164, 8, 10, 0), - (164, 15, 17, 0), - (164, 16, 18, 0), - (164, 31, 33, 0), - (164, 255, 257, 0), - (164, 256, 258, 0), - (164, 65535, 65537, 0), - (165, 0, 2, 0), - (165, 1, 3, 0), - (165, 2, 4, 0), - (165, 3, 5, 0), - (165, 4, 6, 0), - (165, 5, 7, 0), - (165, 7, 9, 0), - (165, 8, 10, 0), - (165, 15, 17, 0), - (165, 16, 18, 0), - (165, 31, 33, 0), - (165, 255, 257, 0), - (165, 256, 258, 0), - (165, 65535, 65537, 0), - (166, 0, 3, 1), - (166, 1, 3, 1), - (166, 2, 3, 1), - (166, 3, 3, 1), - (166, 4, 3, 1), - (166, 5, 3, 1), - (166, 7, 3, 1), - (166, 8, 3, 1), - (166, 15, 3, 1), - (166, 16, 3, 1), - (166, 31, 3, 1), - (166, 255, 3, 1), - (166, 256, 3, 1), - (166, 65535, 3, 1), - (167, 0, 3, 1), - (167, 1, 3, 1), - (167, 2, 3, 1), - (167, 3, 3, 1), - (167, 4, 3, 1), - (167, 5, 3, 1), - (167, 7, 3, 1), - (167, 8, 3, 1), - (167, 15, 3, 1), - (167, 16, 3, 1), - (167, 31, 3, 1), - (167, 255, 3, 1), - (167, 256, 3, 1), - (167, 65535, 3, 1), - (168, 0, 3, 1), - (168, 1, 3, 1), - (168, 2, 3, 1), - (168, 3, 3, 1), - (168, 4, 3, 1), - (168, 5, 3, 1), - (168, 7, 3, 1), - (168, 8, 3, 1), - (168, 15, 3, 1), - (168, 16, 3, 1), - (168, 31, 3, 1), - (168, 255, 3, 1), - (168, 256, 3, 1), - (168, 65535, 3, 1), - (169, 0, 2, 1), - (169, 1, 2, 1), - (169, 2, 2, 1), - (169, 3, 2, 1), - (169, 4, 2, 1), - (169, 5, 2, 1), - (169, 7, 2, 1), - (169, 8, 2, 1), - (169, 15, 2, 1), - (169, 16, 2, 1), - (169, 31, 2, 1), - (169, 255, 2, 1), - (169, 256, 2, 1), - (169, 65535, 2, 1), - (170, 0, 2, 1), - (170, 1, 2, 1), - (170, 2, 2, 1), - (170, 3, 2, 1), - (170, 4, 2, 1), - (170, 5, 2, 1), - (170, 7, 2, 1), - (170, 8, 2, 1), - (170, 15, 2, 1), - (170, 16, 2, 1), - (170, 31, 2, 1), - (170, 255, 2, 1), - (170, 256, 2, 1), - (170, 65535, 2, 1), - (171, 0, 2, 1), - (171, 1, 2, 1), - (171, 2, 2, 1), - (171, 3, 2, 1), - (171, 4, 2, 1), - (171, 5, 2, 1), - (171, 7, 2, 1), - (171, 8, 2, 1), - (171, 15, 2, 1), - (171, 16, 2, 1), - (171, 31, 2, 1), - (171, 255, 2, 1), - (171, 256, 2, 1), - (171, 65535, 2, 1), - (172, 0, 2, 1), - (172, 1, 2, 1), - (172, 2, 2, 1), - (172, 3, 2, 1), - (172, 4, 2, 1), - (172, 5, 2, 1), - (172, 7, 2, 1), - (172, 8, 2, 1), - (172, 15, 2, 1), - (172, 16, 2, 1), - (172, 31, 2, 1), - (172, 255, 2, 1), - (172, 256, 2, 1), - (172, 65535, 2, 1), - (173, 0, 2, 1), - (173, 1, 2, 1), - (173, 2, 2, 1), - (173, 3, 2, 1), - (173, 4, 2, 1), - (173, 5, 2, 1), - (173, 7, 2, 1), - (173, 8, 2, 1), - (173, 15, 2, 1), - (173, 16, 2, 1), - (173, 31, 2, 1), - (173, 255, 2, 1), - (173, 256, 2, 1), - (173, 65535, 2, 1), - (174, 0, 2, 2), - (174, 1, 2, 2), - (174, 2, 2, 2), - (174, 3, 2, 2), - (174, 4, 2, 2), - (174, 5, 2, 2), - (174, 7, 2, 2), - (174, 8, 2, 2), - (174, 15, 2, 2), - (174, 16, 2, 2), - (174, 31, 2, 2), - (174, 255, 2, 2), - (174, 256, 2, 2), - (174, 65535, 2, 2), - (175, 0, 2, 3), - (175, 1, 2, 3), - (175, 2, 2, 3), - (175, 3, 2, 3), - (175, 4, 2, 3), - (175, 5, 2, 3), - (175, 7, 2, 3), - (175, 8, 2, 3), - (175, 15, 2, 3), - (175, 16, 2, 3), - (175, 31, 2, 3), - (175, 255, 2, 3), - (175, 256, 2, 3), - (175, 65535, 2, 3), - (176, 0, 2, 3), - (176, 1, 2, 3), - (176, 2, 2, 3), - (176, 3, 2, 3), - (176, 4, 2, 3), - (176, 5, 2, 3), - (176, 7, 2, 3), - (176, 8, 2, 3), - (176, 15, 2, 3), - (176, 16, 2, 3), - (176, 31, 2, 3), - (176, 255, 2, 3), - (176, 256, 2, 3), - (176, 65535, 2, 3), - (177, 0, 2, 3), - (177, 1, 2, 3), - (177, 2, 2, 3), - (177, 3, 2, 3), - (177, 4, 2, 3), - (177, 5, 2, 3), - (177, 7, 2, 3), - (177, 8, 2, 3), - (177, 15, 2, 3), - (177, 16, 2, 3), - (177, 31, 2, 3), - (177, 255, 2, 3), - (177, 256, 2, 3), - (177, 65535, 2, 3), - (178, 0, 2, 3), - (178, 1, 2, 3), - (178, 2, 2, 3), - (178, 3, 2, 3), - (178, 4, 2, 3), - (178, 5, 2, 3), - (178, 7, 2, 3), - (178, 8, 2, 3), - (178, 15, 2, 3), - (178, 16, 2, 3), - (178, 31, 2, 3), - (178, 255, 2, 3), - (178, 256, 2, 3), - (178, 65535, 2, 3), - (179, 0, 1, 2), - (179, 1, 1, 2), - (179, 2, 1, 2), - (179, 3, 1, 2), - (179, 4, 1, 2), - (179, 5, 1, 2), - (179, 7, 1, 2), - (179, 8, 1, 2), - (179, 15, 1, 2), - (179, 16, 1, 2), - (179, 31, 1, 2), - (179, 255, 1, 2), - (179, 256, 1, 2), - (179, 65535, 1, 2), - (180, 0, 1, 2), - (180, 1, 1, 2), - (180, 2, 1, 2), - (180, 3, 1, 2), - (180, 4, 1, 2), - (180, 5, 1, 2), - (180, 7, 1, 2), - (180, 8, 1, 2), - (180, 15, 1, 2), - (180, 16, 1, 2), - (180, 31, 1, 2), - (180, 255, 1, 2), - (180, 256, 1, 2), - (180, 65535, 1, 2), - (181, 0, 0, 0), - (181, 1, 0, 0), - (181, 2, 0, 0), - (181, 3, 0, 0), - (181, 4, 0, 0), - (181, 5, 0, 0), - (181, 7, 0, 0), - (181, 8, 0, 0), - (181, 15, 0, 0), - (181, 16, 0, 0), - (181, 31, 0, 0), - (181, 255, 0, 0), - (181, 256, 0, 0), - (181, 65535, 0, 0), - (182, 0, 0, 0), - (182, 1, 0, 0), - (182, 2, 0, 0), - (182, 3, 0, 0), - (182, 4, 0, 0), - (182, 5, 0, 0), - (182, 7, 0, 0), - (182, 8, 0, 0), - (182, 15, 0, 0), - (182, 16, 0, 0), - (182, 31, 0, 0), - (182, 255, 0, 0), - (182, 256, 0, 0), - (182, 65535, 0, 0), - (183, 0, 1, 1), - (183, 1, 1, 2), - (183, 2, 1, 1), - (183, 3, 1, 2), - (183, 4, 1, 1), - (183, 5, 1, 2), - (183, 7, 1, 2), - (183, 8, 1, 1), - (183, 15, 1, 2), - (183, 16, 1, 1), - (183, 31, 1, 2), - (183, 255, 1, 2), - (183, 256, 1, 1), - (183, 65535, 1, 2), - (184, 0, 1, 1), - (184, 1, 1, 2), - (184, 2, 1, 1), - (184, 3, 1, 2), - (184, 4, 1, 1), - (184, 5, 1, 2), - (184, 7, 1, 2), - (184, 8, 1, 1), - (184, 15, 1, 2), - (184, 16, 1, 1), - (184, 31, 1, 2), - (184, 255, 1, 2), - (184, 256, 1, 1), - (184, 65535, 1, 2), - (185, 0, 1, 0), - (185, 1, 1, 0), - (185, 2, 1, 0), - (185, 3, 1, 0), - (185, 4, 1, 0), - (185, 5, 1, 0), - (185, 7, 1, 0), - (185, 8, 1, 0), - (185, 15, 1, 0), - (185, 16, 1, 0), - (185, 31, 1, 0), - (185, 255, 1, 0), - (185, 256, 1, 0), - (185, 65535, 1, 0), - (186, 0, 1, 1), - (186, 1, 1, 2), - (186, 2, 1, 1), - (186, 3, 1, 2), - (186, 4, 1, 1), - (186, 5, 1, 2), - (186, 7, 1, 2), - (186, 8, 1, 1), - (186, 15, 1, 2), - (186, 16, 1, 1), - (186, 31, 1, 2), - (186, 255, 1, 2), - (186, 256, 1, 1), - (186, 65535, 1, 2), - (187, 0, 1, 2), - (187, 1, 1, 2), - (187, 2, 1, 2), - (187, 3, 1, 2), - (187, 4, 1, 2), - (187, 5, 1, 2), - (187, 7, 1, 2), - (187, 8, 1, 2), - (187, 15, 1, 2), - (187, 16, 1, 2), - (187, 31, 1, 2), - (187, 255, 1, 2), - (187, 256, 1, 2), - (187, 65535, 1, 2), - (188, 0, 1, 2), - (188, 1, 1, 2), - (188, 2, 1, 2), - (188, 3, 1, 2), - (188, 4, 1, 2), - (188, 5, 1, 2), - (188, 7, 1, 2), - (188, 8, 1, 2), - (188, 15, 1, 2), - (188, 16, 1, 2), - (188, 31, 1, 2), - (188, 255, 1, 2), - (188, 256, 1, 2), - (188, 65535, 1, 2), - (189, 0, 1, 2), - (189, 1, 1, 2), - (189, 2, 1, 2), - (189, 3, 1, 2), - (189, 4, 1, 2), - (189, 5, 1, 2), - (189, 7, 1, 2), - (189, 8, 1, 2), - (189, 15, 1, 2), - (189, 16, 1, 2), - (189, 31, 1, 2), - (189, 255, 1, 2), - (189, 256, 1, 2), - (189, 65535, 1, 2), - (190, 0, 1, 1), - (190, 1, 1, 2), - (190, 2, 1, 1), - (190, 3, 1, 2), - (190, 4, 1, 1), - (190, 5, 1, 2), - (190, 7, 1, 2), - (190, 8, 1, 1), - (190, 15, 1, 2), - (190, 16, 1, 1), - (190, 31, 1, 2), - (190, 255, 1, 2), - (190, 256, 1, 1), - (190, 65535, 1, 2), - (191, 0, 1, 1), - (191, 1, 1, 1), - (191, 2, 1, 1), - (191, 3, 1, 1), - (191, 4, 1, 1), - (191, 5, 1, 1), - (191, 7, 1, 1), - (191, 8, 1, 1), - (191, 15, 1, 1), - (191, 16, 1, 1), - (191, 31, 1, 1), - (191, 255, 1, 1), - (191, 256, 1, 1), - (191, 65535, 1, 1), - (192, 0, 1, 1), - (192, 1, 1, 1), - (192, 2, 1, 1), - (192, 3, 1, 1), - (192, 4, 1, 1), - (192, 5, 1, 1), - (192, 7, 1, 1), - (192, 8, 1, 1), - (192, 15, 1, 1), - (192, 16, 1, 1), - (192, 31, 1, 1), - (192, 255, 1, 1), - (192, 256, 1, 1), - (192, 65535, 1, 1), - (193, 0, 1, 0), - (193, 1, 1, 0), - (193, 2, 1, 0), - (193, 3, 1, 0), - (193, 4, 1, 0), - (193, 5, 1, 0), - (193, 7, 1, 0), - (193, 8, 1, 0), - (193, 15, 1, 0), - (193, 16, 1, 0), - (193, 31, 1, 0), - (193, 255, 1, 0), - (193, 256, 1, 0), - (193, 65535, 1, 0), - (194, 0, 1, 1), - (194, 1, 1, 2), - (194, 2, 1, 1), - (194, 3, 1, 2), - (194, 4, 1, 1), - (194, 5, 1, 2), - (194, 7, 1, 2), - (194, 8, 1, 1), - (194, 15, 1, 2), - (194, 16, 1, 1), - (194, 31, 1, 2), - (194, 255, 1, 2), - (194, 256, 1, 1), - (194, 65535, 1, 2), - (195, 0, 1, 1), - (195, 1, 1, 2), - (195, 2, 1, 1), - (195, 3, 1, 2), - (195, 4, 1, 1), - (195, 5, 1, 2), - (195, 7, 1, 2), - (195, 8, 1, 1), - (195, 15, 1, 2), - (195, 16, 1, 1), - (195, 31, 1, 2), - (195, 255, 1, 2), - (195, 256, 1, 1), - (195, 65535, 1, 2), - (196, 0, 0, 1), - (196, 1, 0, 2), - (196, 2, 0, 1), - (196, 3, 0, 2), - (196, 4, 0, 1), - (196, 5, 0, 2), - (196, 7, 0, 2), - (196, 8, 0, 1), - (196, 15, 0, 2), - (196, 16, 0, 1), - (196, 31, 0, 2), - (196, 255, 0, 2), - (196, 256, 0, 1), - (196, 65535, 0, 2), - (197, 0, 0, 1), - (197, 1, 0, 2), - (197, 2, 0, 1), - (197, 3, 0, 2), - (197, 4, 0, 1), - (197, 5, 0, 2), - (197, 7, 0, 2), - (197, 8, 0, 1), - (197, 15, 0, 2), - (197, 16, 0, 1), - (197, 31, 0, 2), - (197, 255, 0, 2), - (197, 256, 0, 1), - (197, 65535, 0, 2), - (198, 0, 3, 1), - (198, 1, 3, 1), - (198, 2, 3, 1), - (198, 3, 3, 1), - (198, 4, 3, 1), - (198, 5, 3, 1), - (198, 7, 3, 1), - (198, 8, 3, 1), - (198, 15, 3, 1), - (198, 16, 3, 1), - (198, 31, 3, 1), - (198, 255, 3, 1), - (198, 256, 3, 1), - (198, 65535, 3, 1), - (199, 0, 3, 2), - (199, 1, 3, 2), - (199, 2, 3, 2), - (199, 3, 3, 2), - (199, 4, 3, 2), - (199, 5, 3, 2), - (199, 7, 3, 2), - (199, 8, 3, 2), - (199, 15, 3, 2), - (199, 16, 3, 2), - (199, 31, 3, 2), - (199, 255, 3, 2), - (199, 256, 3, 2), - (199, 65535, 3, 2), - (200, 0, 0, 0), - (200, 1, 0, 0), - (200, 2, 0, 0), - (200, 3, 0, 0), - (200, 4, 0, 0), - (200, 5, 0, 0), - (200, 7, 0, 0), - (200, 8, 0, 0), - (200, 15, 0, 0), - (200, 16, 0, 0), - (200, 31, 0, 0), - (200, 255, 0, 0), - (200, 256, 0, 0), - (200, 65535, 0, 0), - (201, 0, 0, 0), - (201, 1, 0, 0), - (201, 2, 0, 0), - (201, 3, 0, 0), - (201, 4, 0, 0), - (201, 5, 0, 0), - (201, 7, 0, 0), - (201, 8, 0, 0), - (201, 15, 0, 0), - (201, 16, 0, 0), - (201, 31, 0, 0), - (201, 255, 0, 0), - (201, 256, 0, 0), - (201, 65535, 0, 0), - (202, 0, 3, 3), - (202, 1, 3, 3), - (202, 2, 3, 3), - (202, 3, 3, 3), - (202, 4, 3, 3), - (202, 5, 3, 3), - (202, 7, 3, 3), - (202, 8, 3, 3), - (202, 15, 3, 3), - (202, 16, 3, 3), - (202, 31, 3, 3), - (202, 255, 3, 3), - (202, 256, 3, 3), - (202, 65535, 3, 3), - (203, 0, 3, 2), - (203, 1, 3, 2), - (203, 2, 3, 2), - (203, 3, 3, 2), - (203, 4, 3, 2), - (203, 5, 3, 2), - (203, 7, 3, 2), - (203, 8, 3, 2), - (203, 15, 3, 2), - (203, 16, 3, 2), - (203, 31, 3, 2), - (203, 255, 3, 2), - (203, 256, 3, 2), - (203, 65535, 3, 2), - (204, 0, 3, 3), - (204, 1, 3, 3), - (204, 2, 3, 3), - (204, 3, 3, 3), - (204, 4, 3, 3), - (204, 5, 3, 3), - (204, 7, 3, 3), - (204, 8, 3, 3), - (204, 15, 3, 3), - (204, 16, 3, 3), - (204, 31, 3, 3), - (204, 255, 3, 3), - (204, 256, 3, 3), - (204, 65535, 3, 3), - (205, 0, 2, 0), - (205, 1, 2, 0), - (205, 2, 2, 0), - (205, 3, 2, 0), - (205, 4, 2, 0), - (205, 5, 2, 0), - (205, 7, 2, 0), - (205, 8, 2, 0), - (205, 15, 2, 0), - (205, 16, 2, 0), - (205, 31, 2, 0), - (205, 255, 2, 0), - (205, 256, 2, 0), - (205, 65535, 2, 0), - (206, 0, 2, 0), - (206, 1, 2, 0), - (206, 2, 2, 0), - (206, 3, 2, 0), - (206, 4, 2, 0), - (206, 5, 2, 0), - (206, 7, 2, 0), - (206, 8, 2, 0), - (206, 15, 2, 0), - (206, 16, 2, 0), - (206, 31, 2, 0), - (206, 255, 2, 0), - (206, 256, 2, 0), - (206, 65535, 2, 0), - (207, 0, 2, 0), - (207, 1, 2, 0), - (207, 2, 2, 0), - (207, 3, 2, 0), - (207, 4, 2, 0), - (207, 5, 2, 0), - (207, 7, 2, 0), - (207, 8, 2, 0), - (207, 15, 2, 0), - (207, 16, 2, 0), - (207, 31, 2, 0), - (207, 255, 2, 0), - (207, 256, 2, 0), - (207, 65535, 2, 0), - (208, 0, 3, 0), - (208, 1, 3, 0), - (208, 2, 3, 0), - (208, 3, 3, 0), - (208, 4, 3, 0), - (208, 5, 3, 0), - (208, 7, 3, 0), - (208, 8, 3, 0), - (208, 15, 3, 0), - (208, 16, 3, 0), - (208, 31, 3, 0), - (208, 255, 3, 0), - (208, 256, 3, 0), - (208, 65535, 3, 0), - (209, 0, 3, 0), - (209, 1, 3, 0), - (209, 2, 3, 0), - (209, 3, 3, 0), - (209, 4, 3, 0), - (209, 5, 3, 0), - (209, 7, 3, 0), - (209, 8, 3, 0), - (209, 15, 3, 0), - (209, 16, 3, 0), - (209, 31, 3, 0), - (209, 255, 3, 0), - (209, 256, 3, 0), - (209, 65535, 3, 0), - (210, 0, 1, 1), - (210, 1, 1, 1), - (210, 2, 1, 1), - (210, 3, 1, 1), - (210, 4, 1, 1), - (210, 5, 1, 1), - (210, 7, 1, 1), - (210, 8, 1, 1), - (210, 15, 1, 1), - (210, 16, 1, 1), - (210, 31, 1, 1), - (210, 255, 1, 1), - (210, 256, 1, 1), - (210, 65535, 1, 1), - (211, 0, 1, 1), - (211, 1, 1, 1), - (211, 2, 1, 1), - (211, 3, 1, 1), - (211, 4, 1, 1), - (211, 5, 1, 1), - (211, 7, 1, 1), - (211, 8, 1, 1), - (211, 15, 1, 1), - (211, 16, 1, 1), - (211, 31, 1, 1), - (211, 255, 1, 1), - (211, 256, 1, 1), - (211, 65535, 1, 1), - (212, 0, 1, 1), - (212, 1, 1, 1), - (212, 2, 1, 1), - (212, 3, 1, 1), - (212, 4, 1, 1), - (212, 5, 1, 1), - (212, 7, 1, 1), - (212, 8, 1, 1), - (212, 15, 1, 1), - (212, 16, 1, 1), - (212, 31, 1, 1), - (212, 255, 1, 1), - (212, 256, 1, 1), - (212, 65535, 1, 1), - (213, 0, 1, 1), - (213, 1, 1, 1), - (213, 2, 1, 1), - (213, 3, 1, 1), - (213, 4, 1, 1), - (213, 5, 1, 1), - (213, 7, 1, 1), - (213, 8, 1, 1), - (213, 15, 1, 1), - (213, 16, 1, 1), - (213, 31, 1, 1), - (213, 255, 1, 1), - (213, 256, 1, 1), - (213, 65535, 1, 1), - (214, 0, 1, 1), - (214, 1, 1, 1), - (214, 2, 1, 1), - (214, 3, 1, 1), - (214, 4, 1, 1), - (214, 5, 1, 1), - (214, 7, 1, 1), - (214, 8, 1, 1), - (214, 15, 1, 1), - (214, 16, 1, 1), - (214, 31, 1, 1), - (214, 255, 1, 1), - (214, 256, 1, 1), - (214, 65535, 1, 1), - (215, 0, 1, 1), - (215, 1, 1, 1), - (215, 2, 1, 1), - (215, 3, 1, 1), - (215, 4, 1, 1), - (215, 5, 1, 1), - (215, 7, 1, 1), - (215, 8, 1, 1), - (215, 15, 1, 1), - (215, 16, 1, 1), - (215, 31, 1, 1), - (215, 255, 1, 1), - (215, 256, 1, 1), - (215, 65535, 1, 1), - (216, 0, 1, 0), - (216, 1, 1, 1), - (216, 2, 1, 2), - (216, 3, 1, 3), - (216, 4, 1, 4), - (216, 5, 1, 5), - (216, 7, 1, 7), - (216, 8, 1, 8), - (216, 15, 1, 15), - (216, 16, 1, 16), - (216, 31, 1, 31), - (216, 255, 1, 255), - (216, 256, 1, 256), - (216, 65535, 1, 65535), - (217, 0, 1, 0), - (217, 1, 1, 1), - (217, 2, 1, 2), - (217, 3, 1, 3), - (217, 4, 1, 4), - (217, 5, 1, 5), - (217, 7, 1, 7), - (217, 8, 1, 8), - (217, 15, 1, 15), - (217, 16, 1, 16), - (217, 31, 1, 31), - (217, 255, 1, 255), - (217, 256, 1, 256), - (217, 65535, 1, 65535), - (218, 0, 1, 2), - (218, 1, 1, 2), - (218, 2, 1, 2), - (218, 3, 1, 2), - (218, 4, 1, 2), - (218, 5, 1, 2), - (218, 7, 1, 2), - (218, 8, 1, 2), - (218, 15, 1, 2), - (218, 16, 1, 2), - (218, 31, 1, 2), - (218, 255, 1, 2), - (218, 256, 1, 2), - (218, 65535, 1, 2), - (233, 0, 3, 2), - (233, 1, 3, 2), - (233, 2, 3, 2), - (233, 3, 3, 2), - (233, 4, 3, 2), - (233, 5, 3, 2), - (233, 7, 3, 2), - (233, 8, 3, 2), - (233, 15, 3, 2), - (233, 16, 3, 2), - (233, 31, 3, 2), - (233, 255, 3, 2), - (233, 256, 3, 2), - (233, 65535, 3, 2), - (234, 0, 2, 0), - (234, 1, 2, 0), - (234, 2, 2, 0), - (234, 3, 2, 0), - (234, 4, 2, 0), - (234, 5, 2, 0), - (234, 7, 2, 0), - (234, 8, 2, 0), - (234, 15, 2, 0), - (234, 16, 2, 0), - (234, 31, 2, 0), - (234, 255, 2, 0), - (234, 256, 2, 0), - (234, 65535, 2, 0), - (235, 0, 3, 1), - (235, 1, 3, 1), - (235, 2, 3, 1), - (235, 3, 3, 1), - (235, 4, 3, 1), - (235, 5, 3, 1), - (235, 7, 3, 1), - (235, 8, 3, 1), - (235, 15, 3, 1), - (235, 16, 3, 1), - (235, 31, 3, 1), - (235, 255, 3, 1), - (235, 256, 3, 1), - (235, 65535, 3, 1), - (236, 0, 2, 3), - (236, 1, 2, 3), - (236, 2, 2, 3), - (236, 3, 2, 3), - (236, 4, 2, 3), - (236, 5, 2, 3), - (236, 7, 2, 3), - (236, 8, 2, 3), - (236, 15, 2, 3), - (236, 16, 2, 3), - (236, 31, 2, 3), - (236, 255, 2, 3), - (236, 256, 2, 3), - (236, 65535, 2, 3), - (237, 0, 0, 0), - (237, 1, 0, 0), - (237, 2, 0, 0), - (237, 3, 0, 0), - (237, 4, 0, 0), - (237, 5, 0, 0), - (237, 7, 0, 0), - (237, 8, 0, 0), - (237, 15, 0, 0), - (237, 16, 0, 0), - (237, 31, 0, 0), - (237, 255, 0, 0), - (237, 256, 0, 0), - (237, 65535, 0, 0), - (238, 0, 0, 0), - (238, 1, 0, 0), - (238, 2, 0, 0), - (238, 3, 0, 0), - (238, 4, 0, 0), - (238, 5, 0, 0), - (238, 7, 0, 0), - (238, 8, 0, 0), - (238, 15, 0, 0), - (238, 16, 0, 0), - (238, 31, 0, 0), - (238, 255, 0, 0), - (238, 256, 0, 0), - (238, 65535, 0, 0), - (239, 0, 0, 0), - (239, 1, 0, 0), - (239, 2, 0, 0), - (239, 3, 0, 0), - (239, 4, 0, 0), - (239, 5, 0, 0), - (239, 7, 0, 0), - (239, 8, 0, 0), - (239, 15, 0, 0), - (239, 16, 0, 0), - (239, 31, 0, 0), - (239, 255, 0, 0), - (239, 256, 0, 0), - (239, 65535, 0, 0), - (240, 0, 1, 0), - (240, 1, 1, 0), - (240, 2, 1, 0), - (240, 3, 1, 0), - (240, 4, 1, 0), - (240, 5, 1, 0), - (240, 7, 1, 0), - (240, 8, 1, 0), - (240, 15, 1, 0), - (240, 16, 1, 0), - (240, 31, 1, 0), - (240, 255, 1, 0), - (240, 256, 1, 0), - (240, 65535, 1, 0), - (241, 0, 1, 0), - (241, 1, 1, 0), - (241, 2, 1, 0), - (241, 3, 1, 0), - (241, 4, 1, 0), - (241, 5, 1, 0), - (241, 7, 1, 0), - (241, 8, 1, 0), - (241, 15, 1, 0), - (241, 16, 1, 0), - (241, 31, 1, 0), - (241, 255, 1, 0), - (241, 256, 1, 0), - (241, 65535, 1, 0), - (242, 0, 1, 0), - (242, 1, 1, 0), - (242, 2, 1, 0), - (242, 3, 1, 0), - (242, 4, 1, 0), - (242, 5, 1, 0), - (242, 7, 1, 0), - (242, 8, 1, 0), - (242, 15, 1, 0), - (242, 16, 1, 0), - (242, 31, 1, 0), - (242, 255, 1, 0), - (242, 256, 1, 0), - (242, 65535, 1, 0), - (243, 0, 1, 0), - (243, 1, 1, 0), - (243, 2, 1, 0), - (243, 3, 1, 0), - (243, 4, 1, 0), - (243, 5, 1, 0), - (243, 7, 1, 0), - (243, 8, 1, 0), - (243, 15, 1, 0), - (243, 16, 1, 0), - (243, 31, 1, 0), - (243, 255, 1, 0), - (243, 256, 1, 0), - (243, 65535, 1, 0), - (244, 0, 0, 0), - (244, 1, 0, 0), - (244, 2, 0, 0), - (244, 3, 0, 0), - (244, 4, 0, 0), - (244, 5, 0, 0), - (244, 7, 0, 0), - (244, 8, 0, 0), - (244, 15, 0, 0), - (244, 16, 0, 0), - (244, 31, 0, 0), - (244, 255, 0, 0), - (244, 256, 0, 0), - (244, 65535, 0, 0), - (245, 0, 1, 1), - (245, 1, 1, 1), - (245, 2, 1, 1), - (245, 3, 1, 1), - (245, 4, 1, 1), - (245, 5, 1, 1), - (245, 7, 1, 1), - (245, 8, 1, 1), - (245, 15, 1, 1), - (245, 16, 1, 1), - (245, 31, 1, 1), - (245, 255, 1, 1), - (245, 256, 1, 1), - (245, 65535, 1, 1), - (246, 0, 1, 1), - (246, 1, 1, 1), - (246, 2, 1, 1), - (246, 3, 1, 1), - (246, 4, 1, 1), - (246, 5, 1, 1), - (246, 7, 1, 1), - (246, 8, 1, 1), - (246, 15, 1, 1), - (246, 16, 1, 1), - (246, 31, 1, 1), - (246, 255, 1, 1), - (246, 256, 1, 1), - (246, 65535, 1, 1), - (247, 0, 2, 0), - (247, 1, 2, 0), - (247, 2, 2, 0), - (247, 3, 2, 0), - (247, 4, 2, 0), - (247, 5, 2, 0), - (247, 7, 2, 0), - (247, 8, 2, 0), - (247, 15, 2, 0), - (247, 16, 2, 0), - (247, 31, 2, 0), - (247, 255, 2, 0), - (247, 256, 2, 0), - (247, 65535, 2, 0), - (248, 0, 3, 1), - (248, 1, 3, 2), - (248, 2, 3, 1), - (248, 3, 3, 2), - (248, 4, 3, 1), - (248, 5, 3, 2), - (248, 7, 3, 2), - (248, 8, 3, 1), - (248, 15, 3, 2), - (248, 16, 3, 1), - (248, 31, 3, 2), - (248, 255, 3, 2), - (248, 256, 3, 1), - (248, 65535, 3, 2), - (249, 0, 2, 1), - (249, 1, 3, 1), - (249, 2, 4, 1), - (249, 3, 5, 1), - (249, 4, 6, 1), - (249, 5, 7, 1), - (249, 7, 9, 1), - (249, 8, 10, 1), - (249, 15, 17, 1), - (249, 16, 18, 1), - (249, 31, 33, 1), - (249, 255, 257, 1), - (249, 256, 258, 1), - (249, 65535, 65537, 1), - (250, 0, 3, 1), - (250, 1, 4, 1), - (250, 2, 5, 1), - (250, 3, 6, 1), - (250, 4, 7, 1), - (250, 5, 8, 1), - (250, 7, 10, 1), - (250, 8, 11, 1), - (250, 15, 18, 1), - (250, 16, 19, 1), - (250, 31, 34, 1), - (250, 255, 258, 1), - (250, 256, 259, 1), - (250, 65535, 65538, 1), - (251, 0, 4, 1), - (251, 1, 4, 1), - (251, 2, 4, 1), - (251, 3, 4, 1), - (251, 4, 4, 1), - (251, 5, 4, 1), - (251, 7, 4, 1), - (251, 8, 4, 1), - (251, 15, 4, 1), - (251, 16, 4, 1), - (251, 31, 4, 1), - (251, 255, 4, 1), - (251, 256, 4, 1), - (251, 65535, 4, 1), - (252, 0, 0, 0), - (252, 1, 0, 0), - (252, 2, 0, 0), - (252, 3, 0, 0), - (252, 4, 0, 0), - (252, 5, 0, 0), - (252, 7, 0, 0), - (252, 8, 0, 0), - (252, 15, 0, 0), - (252, 16, 0, 0), - (252, 31, 0, 0), - (252, 255, 0, 0), - (252, 256, 0, 0), - (252, 65535, 0, 0), - (253, 0, 0, 0), - (253, 1, 0, 0), - (253, 2, 0, 0), - (253, 3, 0, 0), - (253, 4, 0, 0), - (253, 5, 0, 0), - (253, 7, 0, 0), - (253, 8, 0, 0), - (253, 15, 0, 0), - (253, 16, 0, 0), - (253, 31, 0, 0), - (253, 255, 0, 0), - (253, 256, 0, 0), - (253, 65535, 0, 0), - (254, 0, 0, 0), - (254, 1, 0, 0), - (254, 2, 0, 0), - (254, 3, 0, 0), - (254, 4, 0, 0), - (254, 5, 0, 0), - (254, 7, 0, 0), - (254, 8, 0, 0), - (254, 15, 0, 0), - (254, 16, 0, 0), - (254, 31, 0, 0), - (254, 255, 0, 0), - (254, 256, 0, 0), - (254, 65535, 0, 0), - (255, 0, 0, 0), - (255, 1, 0, 0), - (255, 2, 0, 0), - (255, 3, 0, 0), - (255, 4, 0, 0), - (255, 5, 0, 0), - (255, 7, 0, 0), - (255, 8, 0, 0), - (255, 15, 0, 0), - (255, 16, 0, 0), - (255, 31, 0, 0), - (255, 255, 0, 0), - (255, 256, 0, 0), - (255, 65535, 0, 0), - (256, 0, 0, 0), - (256, 1, 0, 0), - (256, 2, 0, 0), - (256, 3, 0, 0), - (256, 4, 0, 0), - (256, 5, 0, 0), - (256, 7, 0, 0), - (256, 8, 0, 0), - (256, 15, 0, 0), - (256, 16, 0, 0), - (256, 31, 0, 0), - (256, 255, 0, 0), - (256, 256, 0, 0), - (256, 65535, 0, 0), - (257, 0, 0, 0), - (257, 1, 0, 0), - (257, 2, 0, 0), - (257, 3, 0, 0), - (257, 4, 0, 0), - (257, 5, 0, 0), - (257, 7, 0, 0), - (257, 8, 0, 0), - (257, 15, 0, 0), - (257, 16, 0, 0), - (257, 31, 0, 0), - (257, 255, 0, 0), - (257, 256, 0, 0), - (257, 65535, 0, 0), - (258, 0, 1, 1), - (258, 1, 1, 1), - (258, 2, 1, 1), - (258, 3, 1, 1), - (258, 4, 1, 1), - (258, 5, 1, 1), - (258, 7, 1, 1), - (258, 8, 1, 1), - (258, 15, 1, 1), - (258, 16, 1, 1), - (258, 31, 1, 1), - (258, 255, 1, 1), - (258, 256, 1, 1), - (258, 65535, 1, 1), - (259, 0, 1, 1), - (259, 1, 1, 1), - (259, 2, 1, 1), - (259, 3, 1, 1), - (259, 4, 1, 1), - (259, 5, 1, 1), - (259, 7, 1, 1), - (259, 8, 1, 1), - (259, 15, 1, 1), - (259, 16, 1, 1), - (259, 31, 1, 1), - (259, 255, 1, 1), - (259, 256, 1, 1), - (259, 65535, 1, 1), - (260, 0, 0, 0), - (260, 1, 0, 0), - (260, 2, 0, 0), - (260, 3, 0, 0), - (260, 4, 0, 0), - (260, 5, 0, 0), - (260, 7, 0, 0), - (260, 8, 0, 0), - (260, 15, 0, 0), - (260, 16, 0, 0), - (260, 31, 0, 0), - (260, 255, 0, 0), - (260, 256, 0, 0), - (260, 65535, 0, 0), - (261, 0, 0, 1), - (261, 1, 0, 1), - (261, 2, 0, 1), - (261, 3, 0, 1), - (261, 4, 0, 1), - (261, 5, 0, 1), - (261, 7, 0, 1), - (261, 8, 0, 1), - (261, 15, 0, 1), - (261, 16, 0, 1), - (261, 31, 0, 1), - (261, 255, 0, 1), - (261, 256, 0, 1), - (261, 65535, 0, 1), - (262, 0, 0, 0), - (262, 1, 0, 0), - (262, 2, 0, 0), - (262, 3, 0, 0), - (262, 4, 0, 0), - (262, 5, 0, 0), - (262, 7, 0, 0), - (262, 8, 0, 0), - (262, 15, 0, 0), - (262, 16, 0, 0), - (262, 31, 0, 0), - (262, 255, 0, 0), - (262, 256, 0, 0), - (262, 65535, 0, 0), - (263, 0, 0, 2), - (263, 1, 0, 2), - (263, 2, 0, 2), - (263, 3, 0, 2), - (263, 4, 0, 2), - (263, 5, 0, 2), - (263, 7, 0, 2), - (263, 8, 0, 2), - (263, 15, 0, 2), - (263, 16, 0, 2), - (263, 31, 0, 2), - (263, 255, 0, 2), - (263, 256, 0, 2), - (263, 65535, 0, 2), - (264, 0, 0, 1), - (264, 1, 0, 1), - (264, 2, 0, 1), - (264, 3, 0, 1), - (264, 4, 0, 1), - (264, 5, 0, 1), - (264, 7, 0, 1), - (264, 8, 0, 1), - (264, 15, 0, 1), - (264, 16, 0, 1), - (264, 31, 0, 1), - (264, 255, 0, 1), - (264, 256, 0, 1), - (264, 65535, 0, 1), - (265, 0, 0, 1), - (265, 1, 0, 1), - (265, 2, 0, 1), - (265, 3, 0, 1), - (265, 4, 0, 1), - (265, 5, 0, 1), - (265, 7, 0, 1), - (265, 8, 0, 1), - (265, 15, 0, 1), - (265, 16, 0, 1), - (265, 31, 0, 1), - (265, 255, 0, 1), - (265, 256, 0, 1), - (265, 65535, 0, 1), - (266, 0, 1, 0), - (266, 1, 1, 0), - (266, 2, 1, 0), - (266, 3, 1, 0), - (266, 4, 1, 0), - (266, 5, 1, 0), - (266, 7, 1, 0), - (266, 8, 1, 0), - (266, 15, 1, 0), - (266, 16, 1, 0), - (266, 31, 1, 0), - (266, 255, 1, 0), - (266, 256, 1, 0), - (266, 65535, 1, 0), -]; diff --git a/crates/compiler-core/src/marshal.rs b/crates/compiler-core/src/marshal.rs index 4e2edc80b56..fee6a111c98 100644 --- a/crates/compiler-core/src/marshal.rs +++ b/crates/compiler-core/src/marshal.rs @@ -554,14 +554,12 @@ fn read_const_value( Ok(value) } -/// Actual surviving code-field objects from the public marshal stream. -/// Execution bytecode is deliberately excluded: exported co_code is a separate -/// lazy cache, not the input byte object's retained identity. -pub struct CodeFieldObjects { - pub constants: V, +/// Object-valued code fields as read from the stream (`r_object` TYPE_CODE). +pub struct CodeFields { + pub consts: V, pub names: V, - pub localsplus_names: V, - pub localsplus_kinds: V, + pub localsplusnames: V, + pub localspluskinds: V, pub linetable: V, pub exceptiontable: V, } @@ -592,23 +590,14 @@ pub trait MarshalBag: Copy { fn make_tuple(&self, elements: impl Iterator) -> Self::Value; + /// Construct a code object. `fields` holds the objects read for the + /// object-valued fields that `code` was decoded from. fn make_code( &self, code: CodeObject<::Constant>, + fields: CodeFields, ) -> Result; - /// Construct a runtime code object while retaining the exact values read - /// from ``co_consts``. Compiler bags ignore this second channel; runtime - /// bags use it for marshalable values (lists, dicts, sets, recursive - /// containers) that their compiler constant representation cannot hold. - fn make_code_with_constants( - &self, - code: CodeObject<::Constant>, - _constants: Vec, - ) -> Result { - self.make_code(code) - } - /// Decode the public `co_code` byte string into the execution-oriented /// instruction storage. /// @@ -620,34 +609,6 @@ pub trait MarshalBag: Copy { CodeUnits::try_from(code_bytes) } - /// Construct a runtime code object while retaining both its exact public - /// `co_code` bytes and the exact values read from `co_consts`. - /// - /// The default ignores the redundant byte spelling because ordinary - /// compiler code is represented losslessly by `CodeUnits`. Runtime bags - /// that accepted otherwise unrepresentable opcode bytes in - /// [`MarshalBag::code_units_from_bytes`] can preserve them here. - fn make_code_with_constants_and_bytes( - &self, - code: CodeObject<::Constant>, - constants: Vec, - _code_bytes: Vec, - ) -> Result { - self.make_code_with_constants(code, constants) - } - - /// Preserve actual field owners in runtime bags. Existing overrides keep - /// their original hook chain through the default implementation. - fn make_code_with_fields( - &self, - code: CodeObject<::Constant>, - constants: Vec, - code_bytes: Vec, - _fields: CodeFieldObjects, - ) -> Result { - self.make_code_with_constants_and_bytes(code, constants, code_bytes) - } - fn make_stop_iter(&self) -> Result; fn make_list(&self, it: impl Iterator) -> Result; @@ -810,6 +771,7 @@ impl MarshalBag for Bag { fn make_code( &self, code: CodeObject<::Constant>, + _fields: CodeFields, ) -> Result { Ok(self.make_code(code)) } @@ -1077,15 +1039,13 @@ fn deserialize_code_value_inner( linetable, exceptiontable, }; - bag.make_code_with_fields( + bag.make_code( code, - constant_values, - code_bytes, - CodeFieldObjects { - constants: consts_value, + CodeFields { + consts: consts_value, names: names_value, - localsplus_names: localsplusnames_value, - localsplus_kinds: kinds_value, + localsplusnames: localsplusnames_value, + localspluskinds: kinds_value, linetable: linetable_value, exceptiontable: exceptiontable_value, }, @@ -1518,35 +1478,6 @@ pub fn serialize_code(buf: &mut W, code: &CodeObject) pub fn serialize_code_with( buf: &mut W, code: &CodeObject, - write_constant: impl FnMut(&mut W, &C) -> core::result::Result<(), E>, -) -> core::result::Result<(), E> { - serialize_code_with_fields(buf, code, |_, _| Ok(false), write_constant) -} - -/// Object-valued code fields that a runtime can route through its common writer. -/// Filename emission intentionally remains in the legacy scalar sequence. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CodeField { - Bytecode, - Constants, - Names, - LocalsPlusNames, - LocalsPlusKinds, - Name, - Qualname, - LineTable, - ExceptionTable, -} - -/// Serialize code with optional actual-object field routing. -/// -/// Returning true means the callback completely wrote that field. A false -/// result selects the legacy compiler/frozen representation. The callback runs -/// before any fallback bytes or temporary name vectors are computed. -pub fn serialize_code_with_fields( - buf: &mut W, - code: &CodeObject, - mut write_field: impl FnMut(&mut W, CodeField) -> core::result::Result, mut write_constant: impl FnMut(&mut W, &C) -> core::result::Result<(), E>, ) -> core::result::Result<(), E> { // 1–5: scalar fields @@ -1557,80 +1488,57 @@ pub fn serialize_code_with_fields( buf.write_u32(code.flags.bits()); // 6: co_code (TYPE_STRING) — bytecode already uses flat localsplus indices - if !write_field(buf, CodeField::Bytecode)? { - let bytecode = code.instructions.original_bytes(); - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &bytecode); - } + let bytecode = code.instructions.original_bytes(); + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &bytecode); // 7: co_consts (TYPE_TUPLE) - if !write_field(buf, CodeField::Constants)? { - buf.write_u8(Type::Tuple as u8); - write_len(buf, code.constants.len()); - for constant in &*code.constants { - write_constant(buf, constant)?; - } + buf.write_u8(Type::Tuple as u8); + write_len(buf, code.constants.len()); + for constant in &*code.constants { + write_constant(buf, constant)?; } // 8: co_names (tuple of strings) - if !write_field(buf, CodeField::Names)? { - write_marshal_name_tuple(buf, &code.names); - } + write_marshal_name_tuple(buf, &code.names); // 9: co_localsplusnames — varnames ++ cell_only ++ freevars - if !write_field(buf, CodeField::LocalsPlusNames)? { - let cell_only_names: Vec<&str> = code - .cellvars - .iter() - .filter(|cv| !code.varnames.iter().any(|v| v.as_ref() == cv.as_ref())) - .map(|cv| cv.as_ref()) - .collect(); - let total_lp_count = code.varnames.len() + cell_only_names.len() + code.freevars.len(); - buf.write_u8(Type::Tuple as u8); - write_len(buf, total_lp_count); - for n in &code.varnames { - write_marshal_str(buf, n.as_ref()); - } - for &n in &cell_only_names { - write_marshal_str(buf, n); - } - for n in &code.freevars { - write_marshal_str(buf, n.as_ref()); - } + let cell_only_names: Vec<&str> = code + .cellvars + .iter() + .filter(|cv| !code.varnames.iter().any(|v| v.as_ref() == cv.as_ref())) + .map(|cv| cv.as_ref()) + .collect(); + let total_lp_count = code.varnames.len() + cell_only_names.len() + code.freevars.len(); + buf.write_u8(Type::Tuple as u8); + write_len(buf, total_lp_count); + for n in &code.varnames { + write_marshal_str(buf, n.as_ref()); } - - // 10: co_localspluskinds — use the stored kinds directly - if !write_field(buf, CodeField::LocalsPlusKinds)? { - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &code.localspluskinds); + for &n in &cell_only_names { + write_marshal_str(buf, n); } + for n in &code.freevars { + write_marshal_str(buf, n.as_ref()); + } + // 10: co_localspluskinds — use the stored kinds directly + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &code.localspluskinds); // 11: co_filename write_marshal_str(buf, code.source_path.as_ref()); // 12: co_name - if !write_field(buf, CodeField::Name)? { - write_marshal_str(buf, code.obj_name.as_ref()); - } - + write_marshal_str(buf, code.obj_name.as_ref()); // 13: co_qualname - if !write_field(buf, CodeField::Qualname)? { - write_marshal_str(buf, code.qualname.as_ref()); - } - + write_marshal_str(buf, code.qualname.as_ref()); // 14: co_firstlineno buf.write_u32(code.first_line_number.map_or(0, |x| x.get() as _)); // 15: co_linetable - if !write_field(buf, CodeField::LineTable)? { - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &code.linetable); - } - + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &code.linetable); // 16: co_exceptiontable - if !write_field(buf, CodeField::ExceptionTable)? { - buf.write_u8(Type::Bytes as u8); - write_vec(buf, &code.exceptiontable); - } - + buf.write_u8(Type::Bytes as u8); + write_vec(buf, &code.exceptiontable); Ok(()) } @@ -1969,7 +1877,7 @@ mod tests { use super::*; use crate::bytecode::{BasicBag, ConstantData}; - pub(super) fn hex_to_bytes(hex: &str) -> Vec { + fn hex_to_bytes(hex: &str) -> Vec { (0..hex.len()) .step_by(2) .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) @@ -2079,199 +1987,3 @@ mod tests { assert_eq!(code.constants.len(), 2); } } - -#[cfg(test)] -mod field_hook_tests { - use super::tests::hex_to_bytes; - use super::*; - use core::cell::Cell; - - #[derive(Clone, Copy)] - struct HookBag<'a>(&'a Cell); - - impl MarshalBag for HookBag<'_> { - type Value = (); - type ConstantBag = BasicBag; - fn make_bool(&self, _: bool) {} - fn make_none(&self) {} - fn make_ellipsis(&self) {} - fn make_float(&self, _: f64) {} - fn make_complex(&self, _: Complex64) {} - fn make_str(&self, _: &Wtf8) {} - fn make_bytes(&self, _: &[u8]) {} - fn make_int(&self, _: BigInt) {} - fn make_tuple(&self, _: impl Iterator) {} - fn make_code(&self, _: CodeObject) -> Result<()> { - assert_eq!(self.0.get(), 3); - self.0.set(7); - Ok(()) - } - fn make_code_with_constants(&self, code: CodeObject, constants: Vec<()>) -> Result<()> { - assert_eq!(self.0.get(), 1); - assert_eq!(constants.len(), 2); - self.0.set(3); - self.make_code(code) - } - fn make_code_with_constants_and_bytes( - &self, - code: CodeObject, - constants: Vec<()>, - bytes: Vec, - ) -> Result<()> { - assert_eq!(self.0.get(), 0); - assert_eq!(bytes, [1, 2]); - self.0.set(1); - self.make_code_with_constants(code, constants) - } - fn make_stop_iter(&self) -> Result<()> { - Err(MarshalError::BadType) - } - fn make_list(&self, _: impl Iterator) -> Result<()> { - Err(MarshalError::BadType) - } - fn make_set(&self, _: impl Iterator) -> Result<()> { - Err(MarshalError::BadType) - } - fn make_frozenset(&self, _: impl Iterator) -> Result<()> { - Err(MarshalError::BadType) - } - fn make_dict(&self, _: impl Iterator) -> Result<()> { - Err(MarshalError::BadType) - } - fn constant_bag(self) -> BasicBag { - BasicBag - } - } - - fn writer_fixture() -> CodeObject { - CodeObject { - instructions: CodeUnits::try_from(&[][..]).unwrap(), - locations: Box::new([]), - flags: CodeFlags::empty(), - posonlyarg_count: 0, - arg_count: 0, - kwonlyarg_count: 0, - source_path: "code_storage.py".into(), - first_line_number: None, - max_stackdepth: 1, - obj_name: "sample".into(), - qualname: "sample".into(), - constants: core::iter::empty().collect(), - names: Box::new([]), - varnames: Box::new([]), - cellvars: Box::new([]), - freevars: Box::new([]), - localspluskinds: Box::new([]), - linetable: Box::new([]), - exceptiontable: Box::new([]), - } - } - - #[test] - fn code_field_default_preserves_complete_legacy_bytes() { - // Complete compiler/frozen payload from the pre-routing field sequence. - let code = writer_fixture(); - let expected = hex_to_bytes( - "0000000000000000000000000100000000000000730000000028000000002800000000280000000073000000007a0f636f64655f73746f726167652e70797a0673616d706c657a0673616d706c650000000073000000007300000000", - ); - let mut bytes = Vec::new(); - serialize_code(&mut bytes, &code); - assert_eq!(bytes, expected); - let mut visited = Vec::new(); - bytes.clear(); - serialize_code_with_fields( - &mut bytes, - &code, - |_, field| { - visited.push(field); - Ok::<_, ()>(false) - }, - |_, _| panic!("empty fixture has no constants"), - ) - .unwrap(); - assert_eq!(bytes, expected); - assert_eq!( - visited, - [ - CodeField::Bytecode, - CodeField::Constants, - CodeField::Names, - CodeField::LocalsPlusNames, - CodeField::LocalsPlusKinds, - CodeField::Name, - CodeField::Qualname, - CodeField::LineTable, - CodeField::ExceptionTable - ] - ); - } - - #[test] - fn code_field_error_stops_before_fallback_and_later_fields() { - let code = writer_fixture(); - let mut bytes = Vec::new(); - let mut visited = Vec::new(); - let result = serialize_code_with_fields( - &mut bytes, - &code, - |buf, field| { - visited.push(field); - if field == CodeField::Constants { - return Err(17); - } - buf.write_u8(0xaa); - Ok(true) - }, - |_, _| panic!("handled field must not reach constant fallback"), - ); - assert_eq!(result, Err(17)); - assert_eq!(visited, [CodeField::Bytecode, CodeField::Constants]); - assert_eq!( - &bytes[..20], - &hex_to_bytes("0000000000000000000000000100000000000000") - ); - assert_eq!(&bytes[20..], &[0xaa]); - } - - #[test] - fn actual_fields_default_preserves_existing_hook_chain() { - let code = CodeObject { - instructions: CodeUnits::try_from(&[][..]).unwrap(), - locations: Box::new([]), - flags: CodeFlags::empty(), - posonlyarg_count: 0, - arg_count: 0, - kwonlyarg_count: 0, - source_path: "code_storage.py".into(), - first_line_number: None, - max_stackdepth: 1, - obj_name: "sample".into(), - qualname: "sample".into(), - constants: core::iter::empty().collect(), - names: Box::new([]), - varnames: Box::new([]), - cellvars: Box::new([]), - freevars: Box::new([]), - localspluskinds: Box::new([]), - linetable: Box::new([]), - exceptiontable: Box::new([]), - }; - let calls = Cell::new(0); - HookBag(&calls) - .make_code_with_fields( - code, - alloc::vec![(), ()], - alloc::vec![1, 2], - CodeFieldObjects { - constants: (), - names: (), - localsplus_names: (), - localsplus_kinds: (), - linetable: (), - exceptiontable: (), - }, - ) - .unwrap(); - assert_eq!(calls.get(), 7); - } -} diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index c0e63f29683..4f739651635 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -1523,46 +1523,3 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { res } } - -#[cfg(test)] -mod tests { - use super::*; - use bytecode::{BasicBag, ConstantData}; - - #[test] - fn common_constant_return_types_and_unsupported_domain() { - let frozen = rustpython_derive::py_compile!( - crate_name = "rustpython_compiler_core", - source = "def nothing():\n return None\ndef truth():\n return True\ndef falsehood():\n return False\ndef minus_one():\n return -1\ndef empty_string():\n return ''\n" - ); - let module = frozen.decode(BasicBag); - let mut checked = 0; - for constant in module.constants.iter() { - let ConstantData::Code { code } = constant else { - continue; - }; - let (common, return_type) = match code.obj_name.as_str() { - "nothing" => (oparg::CommonConstant::None, Some(JitType::None)), - "truth" => (oparg::CommonConstant::True, Some(JitType::Bool)), - "falsehood" => (oparg::CommonConstant::False, Some(JitType::Bool)), - "minus_one" => (oparg::CommonConstant::MinusOne, Some(JitType::Int)), - "empty_string" => (oparg::CommonConstant::EmptyStr, None), - name => panic!("unexpected fixture code: {name}"), - }; - let mut state = OpArgState::default(); - assert!(code.instructions.iter().copied().any(|unit| { - let (instruction, arg) = state.get(unit); - matches!(instruction, Instruction::LoadCommonConstant { idx } if idx.get(arg) == common) - })); - let result = crate::compile(code, &[], None); - if let Some(expected) = return_type { - let compiled = result.expect("supported common constant must compile"); - assert_eq!(compiled.sig.ret, Some(expected)); - } else { - assert!(matches!(result, Err(JitCompileError::NotSupported))); - } - checked += 1; - } - assert_eq!(checked, 5); - } -} diff --git a/crates/stdlib/src/_opcode.rs b/crates/stdlib/src/_opcode.rs index badf0c166bb..ab942f7b106 100644 --- a/crates/stdlib/src/_opcode.rs +++ b/crates/stdlib/src/_opcode.rs @@ -4,11 +4,14 @@ pub(crate) use _opcode::module_def; mod _opcode { use crate::vm::{ AsObject, PyObjectRef, PyResult, VirtualMachine, - bytecode::{oparg, public_metadata::PublicOpcode}, + bytecode::{AnyOpcode, oparg}, }; - fn try_from_i32(raw: i32) -> Option { - PublicOpcode::from_i32(raw) + fn try_from_i32(raw: i32) -> Result { + u16::try_from(raw) + .map_err(|_| ())? + .try_into() + .map_err(|_| ()) } // prepare specialization @@ -21,102 +24,88 @@ mod _opcode { #[derive(FromArgs)] struct StackEffectArgs { #[pyarg(positional)] - opcode: PyObjectRef, + opcode: i32, #[pyarg(positional, optional)] oparg: Option, #[pyarg(named, optional)] jump: Option, } - // Convert only after FromArgs has checked arity and keywords, so rejected - // calls never invoke __index__. Keep these C API diagnostics local. - fn opcode_int(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - let value = obj.try_index(vm)?; - i32::try_from(value.as_bigint()) - .map_err(|_| vm.new_overflow_error("Python int too large to convert to C int")) - } - - #[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)] - fn operand_int(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - let value = obj.try_index(vm)?; - let value = core::ffi::c_long::try_from(value.as_bigint()) - .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?; - // Final _opcode.c narrows PyLong_AsLong's result to C int. This is the - // explicit low-bit policy for supported two's-complement target ABIs; - // it does not imply portable C signed-arithmetic overflow semantics. - Ok(value as i32) - } - #[pyfunction] fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult { - // Retain every original argument owner through conversion, validation, - // effect selection and local error construction. Outer native transport - // and result wrapping keep their existing ownership behavior. - let opcode = opcode_int(&args.opcode, vm)?; - let oparg = match args.oparg.as_ref() { - Some(value) if !vm.is_none(value) => operand_int(value, vm)?, + let oparg = match args.oparg { + // (int)PyLong_AsLong(oparg) + Some(v) if !vm.is_none(&v) => v.try_into_value::(vm)? as i32, _ => 0, }; - let jump = match args.jump.as_ref() { + let jump = match args.jump { None => None, - Some(value) if vm.is_none(value) => None, - Some(value) if value.is(&vm.ctx.true_value) => Some(true), - Some(value) if value.is(&vm.ctx.false_value) => Some(false), + Some(v) if vm.is_none(&v) => None, + Some(v) if v.is(&vm.ctx.true_value) => Some(true), + Some(v) if v.is(&vm.ctx.false_value) => Some(false), Some(_) => { return Err(vm.new_value_error("stack_effect: jump must be False, True or None")); } }; - PublicOpcode::from_i32(opcode) - // Preserve the signed C-int bit pattern at the generated API's - // existing u32 boundary; it immediately reinterprets it as i32. - .and_then(|opcode| opcode.stack_effect(oparg.cast_unsigned(), jump)) - .ok_or_else(|| vm.new_value_error("invalid opcode or oparg")) + let invalid = || vm.new_value_error("invalid opcode or oparg"); + let instr = try_from_i32(args.opcode).map_err(|_| invalid())?; + // Specialized instructions are not supported. + if instr.real().is_some_and(|op| op.deopt().is_some()) { + return Err(invalid()); + } + let oparg = u32::try_from(oparg).map_err(|_| invalid())?; + let effect = if jump == Some(false) { + instr.stack_effect(oparg) + } else { + instr.stack_effect_jump(oparg) + }; + Ok(effect) } #[derive(FromArgs)] struct OpcodeArg { #[pyarg(any)] - opcode: PyObjectRef, + opcode: i32, } #[pyfunction] - fn is_valid(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some()) + fn is_valid(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok() } #[pyfunction] - fn has_arg(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_arg())) + fn has_arg(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.has_arg()) } #[pyfunction] - fn has_const(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_const())) + fn has_const(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.has_const()) } #[pyfunction] - fn has_name(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_name())) + fn has_name(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.has_name()) } #[pyfunction] - fn has_jump(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_jump())) + fn has_jump(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.has_jump()) } #[pyfunction] - fn has_free(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_free())) + fn has_free(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.has_free()) } #[pyfunction] - fn has_local(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.has_local())) + fn has_local(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.has_local()) } #[pyfunction] - fn has_exc(OpcodeArg { opcode }: OpcodeArg, vm: &VirtualMachine) -> PyResult { - Ok(try_from_i32(opcode_int(&opcode, vm)?).is_some_and(|op| op.is_block_push())) + fn has_exc(OpcodeArg { opcode }: OpcodeArg) -> bool { + try_from_i32(opcode).is_ok_and(|op| op.is_block_push()) } #[pyfunction] @@ -181,16 +170,20 @@ mod tests { macro_rules! assert_dis_snapshot { ($value:expr) => { - insta::assert_snapshot!(dis($value)) + insta::assert_snapshot!(dis($value, false)) + }; + (positions: $value:expr) => { + insta::assert_snapshot!(dis($value, true)) }; } - /// Returns the [`dis.dis`](https://docs.python.org/3/library/dis.html#dis.dis) output. + /// Returns the [`dis.dis`](https://docs.python.org/3/library/dis.html#dis.dis) output, + /// with the source positions of the instructions if `show_positions` is true. /// /// # Notes /// /// Memory addresses in the output are replaced with `0xdeadbeef` for consistency. - fn dis(source: &str) -> String { + fn dis(source: &str, show_positions: bool) -> String { const FNAME: &str = ""; let builder = vm::Interpreter::builder(Default::default()); @@ -207,6 +200,10 @@ mod tests { .map_err(|err| err.into_pyexception(vm, Some(source))) .unwrap(); scope.globals.set_item("code", code_obj.into(), vm).unwrap(); + scope + .globals + .set_item("show_positions", vm.new_pyobj(show_positions), vm) + .unwrap(); let py_source = r#" import dis @@ -216,13 +213,13 @@ import sys old_stdout = sys.stdout sys.stdout = buf = io.StringIO() -dis.dis(code) +dis.dis(code, show_positions=show_positions) sys.stdout = old_stdout tmp_output = buf.getvalue() # constant mem address -output = re.sub(r'(0xdeadbeef', tmp_output) +output = re.sub(r'(0xdeadbeef', tmp_output) "#; let py_code_obj = vm @@ -322,6 +319,707 @@ def f(one: int): my_lst = [1] my_lst[one]: int return my_lst +"# + ) + } + + #[test] + fn empty_module_implicit_return_location() { + // The implicit return is emitted with NO_LOCATION; propagate_line_numbers() + // gives it the module RESUME location, whose line is 0. + assert_dis_snapshot!(positions: "") + } + + #[test] + fn module_docstring_location() { + // codegen_body() loads the docstring at its expression location and stores + // __doc__ with NO_LOCATION. + assert_dis_snapshot!( + positions: r#" +"doc" +x = 1 +"# + ) + } + + #[test] + fn redundant_nop_location() { + // basicblock_remove_redundant_nops() copies the full NOP location into a + // following no-location jump. + assert_dis_snapshot!( + positions: r#" +def f(x, y, z): + while x: + if y: + pass + elif z: + if y < 0: + return y + if z: + y = y + 1 + elif y: + return 1 + return -1 +"# + ) + } + + #[test] + fn annotated_multiline_function_firstlineno() { + // codegen_function() computes firstlineno from the FunctionDef before + // compiling annotations. + assert_dis_snapshot!( + positions: r#" + +a = 1 +def f( + x: a, +): ... +"# + ) + } + + #[test] + fn annotation_scope_function_location() { + // codegen_function_annotations() receives LOC(function) and uses it for the + // annotation closure's BUILD_MAP/RETURN_VALUE and the MAKE_FUNCTION annotate + // sequence. + assert_dis_snapshot!( + positions: r#" + +def g(): + def f(x: not (int is int), /): ... +"# + ) + } + + #[test] + fn starred_arg_annotation_unpack_location() { + // codegen_argannotation() visits `Ts` at the annotation location, then emits + // UNPACK_SEQUENCE at LOC(function). + assert_dis_snapshot!( + positions: r#" +def f(*args: *Ts): pass +"# + ) + } + + #[test] + fn module_deferred_annotations_location() { + // codegen_process_deferred_annotations() uses the first module statement + // location from start_location(). + assert_dis_snapshot!( + positions: r#" +import os +X: int +Y: str +"# + ) + } + + #[test] + fn not_compare_location() { + // A Compare inside UnaryOp(Not) is parsed with the UnaryOp start location. + assert_dis_snapshot!( + positions: r#" +def f(self, other): + return not self == other +"# + ) + } + + #[test] + fn not_chained_compare_location() { + // A chained comparison keeps its inner range for the comparison scaffolding + // and uses the UnaryOp range only for TO_BOOL and UNARY_NOT. + assert_dis_snapshot!( + positions: r#" +def f(c): + return not (b" " <= c <= b"~") +"# + ) + } + + #[test] + fn type_param_scope_locations() { + // codegen_type_params() emits type parameter ops at LOC(typeparam), + // bound/default evaluators at LOC(e), and type alias plumbing at LOC(s). + assert_dis_snapshot!( + positions: r#" +type BoundGenericAlias[X: int] = set[X] +"# + ) + } + + #[test] + fn generic_typealias_wrapper_return_location() { + // The generic parameters wrapper of a type alias returns at LOC(type alias). + assert_dis_snapshot!( + positions: r#" +type A[T] = T +"# + ) + } + + #[test] + fn generic_function_annotation_scope_location() { + // codegen_function_annotations() gets LOC(function) inside the generic + // parameters scope too. + assert_dis_snapshot!( + positions: r#" +def f[T](x: int): ... +"# + ) + } + + #[test] + fn generic_class_type_params_store_location() { + // codegen_class() stores the .type_params cell at LOC(class). + assert_dis_snapshot!( + positions: r#" +def outer(): + class X[T]: ... +"# + ) + } + + #[test] + fn class_deferred_annotations_location() { + // Deferred annotation setup and the class tail inherit the class body + // location LOCATION(firstlineno, firstlineno, 0, 0). + assert_dis_snapshot!( + positions: r#" + +class C: + "doc" + x: int +"# + ) + } + + #[test] + fn future_annotation_string_location() { + // The stringized annotation is loaded at LOC(annotation) and stored at + // LOC(AnnAssign). + assert_dis_snapshot!( + positions: r#" +from __future__ import annotations +class Bar: + foo: Foo +"# + ) + } + + #[test] + fn lambda_dict_literal_location() { + // codegen_dict() and codegen_subdict() use LOC(dict) for BUILD_MAP, MAP_ADD + // and DICT_UPDATE. + assert_dis_snapshot!( + positions: r#" +f = lambda data: {'x': data} +g = lambda i: {**i} +"# + ) + } + + #[test] + fn match_or_alternative_locations() { + // codegen_pattern_or() emits the COPY of each alternative at LOC(alt). + assert_dis_snapshot!( + positions: r#" +def f(): + x = False + match 0: + case 0 | 1 | 2 | 3: + x = True + return x +"# + ) + } + + #[test] + fn match_success_jump_location() { + // codegen_match_inner() emits the success jump with NO_LOCATION. + assert_dis_snapshot!( + positions: r#" +def f(self): + match 0: + case 0: + x = True + case 0: + x = False + self.assertIs(x, True) +"# + ) + } + + #[test] + fn match_mapping_keys_location() { + // codegen_pattern_mapping() returns to LOC(p) for the BUILD_TUPLE/MATCH_KEYS + // scaffolding. + assert_dis_snapshot!( + positions: r#" +def f(self): + x = {} + y = None + match x: + case {0: 0}: + y = 0 + self.assertIs(y, None) +"# + ) + } + + #[test] + fn match_class_scaffolding_location() { + // codegen_pattern_class() returns to LOC(p) after visiting the class. + assert_dis_snapshot!( + positions: r#" +def f(x): + match x: + case bool(z): + y = 0 + return y, z +"# + ) + } + + #[test] + fn taken_constant_boolop_location() { + // The surviving load of a short-circuited BoolOp keeps the literal range. + assert_dis_snapshot!( + positions: r#" +def and_false(x): + return False and x + +def or_true(x): + return True or x +"# + ) + } + + #[test] + fn assert_false_message_location() { + // codegen_assert() emits LOAD_COMMON_CONSTANT and CALL at LOC(assert) and + // RAISE_VARARGS at LOC(test). + assert_dis_snapshot!( + positions: r#" +def f(): + assert False, "x" +"# + ) + } + + #[test] + fn static_swap_implicit_return_location() { + // Line numbers are resolved before apply_static_swaps() reorders the stores, + // so the return epilogue keeps the location of the last store before the swap. + assert_dis_snapshot!( + positions: r#" +def f(a, b): + a, b = a, b + b, a = a, b +"# + ) + } + + #[test] + fn genexpr_closure_and_call_location() { + // codegen_comprehension() uses LOC(e) for the closure, the outer CALL and the + // implicit .0 load. + assert_dis_snapshot!( + positions: r#" +def f(parameters): + return ((p, type(p)) for p in parameters) +"# + ) + } + + #[test] + fn implicit_call_genexpr_range() { + // A sole unparenthesized generator expression argument spans the call + // parentheses. + assert_dis_snapshot!( + positions: r#" +def implicit(): + return list(x for x in range(10)) + +def explicit(): + return list((x for x in range(10))) +"# + ) + } + + #[test] + fn implicit_call_genexpr_parenthesized_element_range() { + // The call parentheses are part of the generator expression range even when + // its element starts with a parenthesis. + assert_dis_snapshot!( + positions: r#" +def bytes_binop(): + return bytes((x ^ 0x5C) for x in range(256)) + +def dict_tuple(d): + return dict((v, k) for (k, v) in d.items()) + +def plain_tuple_elt(xs): + return list((x, y) for x, y in xs) + +def explicit_gen(xs): + return list(((x, y) for x, y in xs)) +"# + ) + } + + #[test] + fn genexpr_filter_cleanup_location() { + // jump_thread() copies the if_cleanup backedge location, elt_loc, to the + // threaded cleanup path. + assert_dis_snapshot!( + positions: r#" +def simple(names): + return (x for x in names if not _ishidden(x)) + +def boolop(fields): + return (f for f in fields if f.init and not f.kw_only) +"# + ) + } + + #[test] + fn try_finally_exception_scaffolding_location() { + // codegen_try_finally() emits the exception path scaffolding with NO_LOCATION. + assert_dis_snapshot!( + positions: r#" +def f(self, node): + self.flag = True + try: + self.body(node) + finally: + self.flag = False +"# + ) + } + + #[test] + fn adjacent_no_location_entries() { + // assemble_location_info() merges adjacent NO_LOCATION instructions into one + // linetable entry. + assert_dis_snapshot!( + positions: r#" +def f(file): + if sys.platform == "win32": + try: + import nt + if not nt._supports_virtual_terminal(): + return False + except (ImportError, AttributeError): + return False + try: + return os.isatty(file.fileno()) + except OSError: + return hasattr(file, "isatty") and file.isatty() +"# + ) + } + + #[test] + fn fstring_format_location() { + // codegen_formatted_value() emits CONVERT_VALUE and FORMAT_* at + // LOC(FormattedValue). + assert_dis_snapshot!( + positions: r#" +def simple(self): + return f'{self.value}' + +def spec(x): + return f'{x!r:>3}' +"# + ) + } + + #[test] + fn joined_string_literals_location() { + // Adjacent f-string literal fragments are one Constant spanning all of them. + assert_dis_snapshot!( + positions: r#" +def f(a): + return ( + 'x' + f'y{a}z' + 'w' + ) +"# + ) + } + + #[test] + fn decorated_definition_locations() { + // Decorators are evaluated first, the closure and the store use LOC(s), and + // each decorator CALL uses the decorator location. + assert_dis_snapshot!( + positions: r#" +def dec(f): return f + +class C: + @dec + def f(self): + yield + +@dec +class D: + pass + +class E: + @dec + def g(self, flags: int, /) -> memoryview: + raise NotImplementedError +"# + ) + } + + #[test] + fn builtin_any_genexpr_call() { + // any(genexpr) is inlined behind a builtin identity check that falls back to + // the ordinary call. + assert_dis_snapshot!( + positions: r#" +def f(xs): + return any(x for x in xs) +"# + ) + } + + #[test] + fn builtin_tuple_list_set_genexpr_calls() { + // tuple(), list() and set() of a generator expression are inlined behind a + // builtin identity check that falls back to the ordinary call. + assert_dis_snapshot!( + positions: r#" +def tuple_f(xs): + return tuple(x for x in xs) + +def list_f(xs): + return list(x for x in xs) + +def set_f(xs): + return set(x for x in xs) +"# + ) + } + + #[test] + fn augassign_constant_slice_location() { + // codegen_augassign() emits the COPY/COPY/BINARY_OP NB_SUBSCR at LOC(target). + assert_dis_snapshot!( + positions: r#" +def aug_const(x, y): + x[1:2] += y +"# + ) + } + + #[test] + fn comprehension_list_iterable_location() { + // codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter). + assert_dis_snapshot!( + positions: r#" +async def f(i): + return i + +async def run_list(): + return [await c for c in [f(1), f(41)]] +"# + ) + } + + #[test] + fn inlined_comprehension_backedge_location() { + // codegen_sync_comprehension_generator() emits the backedges at elt_loc. + assert_dis_snapshot!( + positions: r#" +async def f(i): + return i + +async def run_list(): + return [s for c in [f(''), f('abc')] for s in await c] +"# + ) + } + + #[test] + fn inlined_async_comprehension_end_async_for_location() { + // codegen_async_comprehension_generator() emits END_ASYNC_FOR at the + // comprehension location. + assert_dis_snapshot!( + positions: r#" +async def f(it): + for i in it: + yield i + +async def run_list(): + return [i + 1 async for i in f([10, 20])] +"# + ) + } + + #[test] + fn async_for_anext_location() { + // codegen_async_for() emits the GET_ANEXT/yield-from scaffolding at LOC(s). + assert_dis_snapshot!( + positions: r#" +async def f(source, buffer): + async for i1, i2 in source(): + buffer.append(i1 + i2) +"# + ) + } + + #[test] + fn namedexpr_copy_location() { + // The COPY of a named expression is at its location, between the value and + // the target. + assert_dis_snapshot!( + positions: r#" +def outer(): + a = 10 + def spam(): + nonlocal a + (a := 20) +"# + ) + } + + #[test] + fn with_cleanup_excludes_not_taken() { + // The NOT_TAKEN after the with body is outside the exception table entries. + assert_dis_snapshot!( + positions: r#" +def f(c): + with c: + x = 1 + y = 2 +"# + ) + } + + #[test] + fn async_for_end_async_for() { + // END_ASYNC_FOR jumps back to the END_SEND of its awaitable loop. + assert_dis_snapshot!( + positions: r#" +async def f(xs): + async for x in xs: + pass +"# + ) + } + + #[test] + fn genexpr_prefix() { + // The outermost iterator is acquired before RETURN_GENERATOR, outside the + // StopIteration handler. + assert_dis_snapshot!( + positions: r#" +def f(xs): + return (x for x in xs) + +async def g(xs): + return (x async for x in xs) +"# + ) + } + + #[test] + fn yield_from_and_await() { + // GET_ITER and GET_AWAITABLE leave the iterator pair that SEND uses. + assert_dis_snapshot!( + positions: r#" +def f(xs): + yield from xs + +async def g(xs): + return await xs +"# + ) + } + + #[test] + fn comprehension_collection_depth() { + // The collection depth counts the slots of every nested iterator. + assert_dis_snapshot!( + positions: r#" +def f(xs, ys): + return [x for x in xs for y in ys] + +async def g(xs, ys): + return [x async for x in xs for y in ys] +"# + ) + } + + #[test] + fn import_flags() { + // The IMPORT_NAME oparg flags depend on the scope and the enclosing handlers. + assert_dis_snapshot!( + positions: r#" +import sample +from sample import member +from sample import * + +def f(): + import sample + +class C: + from sample import member + +try: + import sample +except Exception: + pass + +try: + operation() +except Exception: + import sample + +try: + operation() +finally: + import sample + +with manager: + import sample +"# + ) + } + + #[test] + fn loop_return_cleanup() { + // A return pops two slots for a for loop and one for an async for loop. + assert_dis_snapshot!( + positions: r#" +def f(xs): + for x in xs: + return x + +async def g(xs): + async for x in xs: + return x +"# + ) + } + + #[test] + fn list_set_async_genexpr_calls() { + // list() and set() of an async generator expression stay ordinary calls. + assert_dis_snapshot!( + positions: r#" +async def f(xs): + return list(x async for x in xs) + +async def g(xs): + return set(x async for x in xs) "# ) } diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__adjacent_no_location_entries.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__adjacent_no_location_entries.snap new file mode 100644 index 00000000000..1a9458c566c --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__adjacent_no_location_entries.snap @@ -0,0 +1,109 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(file):\n if sys.platform == \"win32\":\n try:\n import nt\n if not nt._supports_virtual_terminal():\n return False\n except (ImportError, AttributeError):\n return False\n try:\n return os.isatty(file.fileno())\n except OSError:\n return hasattr(file, \"isatty\") and file.isatty()\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-12:56 LOAD_CONST 0 (", line 1>) +1:0-12:56 MAKE_FUNCTION +1:0-12:56 STORE_NAME 0 (f) +1:0-12:56 LOAD_COMMON_CONSTANT 7 (None) +1:0-12:56 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:7-2:10 LOAD_GLOBAL 0 (sys) +2:7-2:19 LOAD_ATTR 2 (platform) +2:23-2:30 LOAD_CONST 0 ('win32') +2:7-2:30 COMPARE_OP 88 (bool(==)) +2:7-2:30 POP_JUMP_IF_FALSE 31 (to L4) +2:7-2:30 NOT_TAKEN + +3:8-8:24 NOP + +4:12-4:21 L1: LOAD_SMALL_INT 0 +4:12-4:21 LOAD_COMMON_CONSTANT 7 (None) +4:12-4:21 IMPORT_NAME 10 (nt + eager) +4:12-4:21 STORE_FAST 1 (nt) + +5:19-5:21 LOAD_FAST_BORROW 1 (nt) +5:19-5:48 LOAD_ATTR 7 (_supports_virtual_terminal + NULL|self) +5:19-5:50 CALL 0 +5:19-5:50 TO_BOOL +5:19-5:50 POP_JUMP_IF_TRUE 3 (to L3) +5:19-5:50 L2: NOT_TAKEN + +6:23-6:28 LOAD_COMMON_CONSTANT 10 (False) +6:23-6:28 RETURN_VALUE + +5:19-5:50 L3: NOP + +9:4-12:56 L4: NOP + +10:15-10:17 L5: LOAD_GLOBAL 12 (os) +10:15-10:24 LOAD_ATTR 15 (isatty + NULL|self) +10:25-10:29 LOAD_FAST_BORROW 0 (file) +10:25-10:36 LOAD_ATTR 17 (fileno + NULL|self) +10:25-10:38 CALL 0 +10:15-10:39 CALL 1 +10:8-10:39 L6: RETURN_VALUE + + -- L7: PUSH_EXC_INFO + +7:16-7:27 LOAD_GLOBAL 8 (ImportError) +7:29-7:43 LOAD_GLOBAL 10 (AttributeError) +7:15-7:44 BUILD_TUPLE 2 +7:8-8:24 CHECK_EXC_MATCH +7:8-8:24 POP_JUMP_IF_FALSE 5 (to L11) +7:8-8:24 L8: NOT_TAKEN +7:8-8:24 L9: POP_TOP + +8:19-8:24 L10: POP_EXCEPT +8:19-8:24 LOAD_COMMON_CONSTANT 10 (False) +8:19-8:24 RETURN_VALUE + +7:8-8:24 L11: RERAISE 0 + + -- L12: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 + -- L13: PUSH_EXC_INFO + +11:11-11:18 LOAD_GLOBAL 18 (OSError) +11:4-12:56 CHECK_EXC_MATCH +11:4-12:56 POP_JUMP_IF_FALSE 40 (to L20) +11:4-12:56 L14: NOT_TAKEN +11:4-12:56 L15: POP_TOP + +12:15-12:22 LOAD_GLOBAL 21 (hasattr + NULL) +12:23-12:27 LOAD_FAST 0 (file) +12:29-12:37 LOAD_CONST 1 ('isatty') +12:15-12:38 CALL 2 +12:15-12:56 COPY 1 +12:15-12:56 TO_BOOL +12:15-12:56 POP_JUMP_IF_FALSE 17 (to L18) +12:15-12:56 L16: NOT_TAKEN +12:15-12:56 L17: POP_TOP +12:43-12:47 LOAD_FAST 0 (file) +12:43-12:54 LOAD_ATTR 15 (isatty + NULL|self) +12:43-12:56 CALL 0 +12:8-12:56 L18: SWAP 2 +12:8-12:56 L19: POP_EXCEPT +12:8-12:56 RETURN_VALUE + +11:4-12:56 L20: RERAISE 0 + + -- L21: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L7 [0] + L5 to L6 -> L13 [0] + L7 to L8 -> L12 [1] lasti + L9 to L10 -> L12 [1] lasti + L11 to L12 -> L12 [1] lasti + L13 to L14 -> L21 [1] lasti + L15 to L16 -> L21 [1] lasti + L17 to L19 -> L21 [1] lasti + L20 to L21 -> L21 [1] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotated_multiline_function_firstlineno.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotated_multiline_function_firstlineno.snap new file mode 100644 index 00000000000..98bf8123a15 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotated_multiline_function_firstlineno.snap @@ -0,0 +1,39 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\n\na = 1\ndef f(\n x: a,\n): ...\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:4-1:5 LOAD_SMALL_INT 1 +1:0-1:1 STORE_NAME 0 (a) + +2:0-4:6 LOAD_CONST 1 (", line 2>) +2:0-4:6 MAKE_FUNCTION +2:0-4:6 LOAD_CONST 2 (", line 2>) +2:0-4:6 MAKE_FUNCTION +2:0-4:6 SET_FUNCTION_ATTRIBUTE 16 (annotate) +2:0-4:6 STORE_NAME 1 (f) +2:0-4:6 LOAD_COMMON_CONSTANT 7 (None) +2:0-4:6 RETURN_VALUE + +Disassembly of ", line 2>: +2:0-2:0 RESUME 0 +2:0-4:6 LOAD_FAST_BORROW 0 (format) +2:0-4:6 LOAD_SMALL_INT 2 +2:0-4:6 COMPARE_OP 132 (>) +2:0-4:6 POP_JUMP_IF_FALSE 3 (to L1) +2:0-4:6 NOT_TAKEN +2:0-4:6 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +2:0-4:6 RAISE_VARARGS 1 +2:0-4:6 L1: LOAD_CONST 1 ('x') + +3:7-3:8 LOAD_GLOBAL 0 (a) + +2:0-4:6 BUILD_MAP 1 +2:0-4:6 RETURN_VALUE + +Disassembly of ", line 2>: +2:0-2:0 RESUME 0 + +4:3-4:6 LOAD_COMMON_CONSTANT 7 (None) +4:3-4:6 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotation_scope_function_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotation_scope_function_location.snap new file mode 100644 index 00000000000..e8dec1373fc --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__annotation_scope_function_location.snap @@ -0,0 +1,44 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\n\ndef g():\n def f(x: not (int is int), /): ...\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:38 LOAD_CONST 0 (", line 1>) +1:0-2:38 MAKE_FUNCTION +1:0-2:38 STORE_NAME 0 (g) +1:0-2:38 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:38 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:4-2:38 LOAD_CONST 0 (", line 2>) +2:4-2:38 MAKE_FUNCTION +2:4-2:38 LOAD_CONST 1 (", line 2>) +2:4-2:38 MAKE_FUNCTION +2:4-2:38 SET_FUNCTION_ATTRIBUTE 16 (annotate) +2:4-2:38 STORE_FAST 0 (f) +2:4-2:38 LOAD_COMMON_CONSTANT 7 (None) +2:4-2:38 RETURN_VALUE + +Disassembly of ", line 2>: +2:0-2:0 RESUME 0 +2:4-2:38 LOAD_FAST_BORROW 0 (format) +2:4-2:38 LOAD_SMALL_INT 2 +2:4-2:38 COMPARE_OP 132 (>) +2:4-2:38 POP_JUMP_IF_FALSE 3 (to L1) +2:4-2:38 NOT_TAKEN +2:4-2:38 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +2:4-2:38 RAISE_VARARGS 1 +2:4-2:38 L1: LOAD_CONST 1 ('x') +2:18-2:21 LOAD_GLOBAL 0 (int) +2:25-2:28 LOAD_GLOBAL 0 (int) +2:13-2:29 IS_OP 1 (is not) +2:4-2:38 BUILD_MAP 1 +2:4-2:38 RETURN_VALUE + +Disassembly of ", line 2>: +2:0-2:0 RESUME 0 +2:35-2:38 LOAD_COMMON_CONSTANT 7 (None) +2:35-2:38 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__assert_false_message_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__assert_false_message_location.snap new file mode 100644 index 00000000000..e0c9da9f252 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__assert_false_message_location.snap @@ -0,0 +1,19 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f():\n assert False, \"x\"\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:21 LOAD_CONST 0 (", line 1>) +1:0-2:21 MAKE_FUNCTION +1:0-2:21 STORE_NAME 0 (f) +1:0-2:21 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:21 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:4-2:21 LOAD_COMMON_CONSTANT 0 (AssertionError) +2:18-2:21 LOAD_CONST 1 ('x') +2:4-2:21 CALL 0 +2:11-2:16 RAISE_VARARGS 1 diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_anext_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_anext_location.snap new file mode 100644 index 00000000000..57a303b673e --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_anext_location.snap @@ -0,0 +1,57 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nasync def f(source, buffer):\n async for i1, i2 in source():\n buffer.append(i1 + i2)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-3:30 LOAD_CONST 0 (", line 1>) +1:0-3:30 MAKE_FUNCTION +1:0-3:30 STORE_NAME 0 (f) +1:0-3:30 LOAD_COMMON_CONSTANT 7 (None) +1:0-3:30 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:24-2:30 LOAD_FAST_BORROW 0 (source) +2:24-2:30 PUSH_NULL +2:24-2:32 CALL 0 +2:24-2:32 GET_AITER +2:4-3:30 L2: GET_ANEXT +2:4-3:30 PUSH_NULL +2:4-3:30 LOAD_COMMON_CONSTANT 7 (None) +2:4-3:30 L3: SEND 4 (to L6) +2:4-3:30 L4: YIELD_VALUE 1 +2:4-3:30 L5: RESUME 3 +2:4-3:30 JUMP_BACKWARD_NO_INTERRUPT 6 (to L3) +2:4-3:30 L6: END_SEND +2:4-3:30 L7: NOT_TAKEN +2:14-2:20 UNPACK_SEQUENCE 2 +2:14-2:16 STORE_FAST_STORE_FAST 35 (i1, i2) + +3:8-3:14 LOAD_FAST_BORROW 1 (buffer) +3:8-3:21 LOAD_ATTR 1 (append + NULL|self) +3:22-3:24 LOAD_FAST_BORROW_LOAD_FAST_BORROW 35 (i1, i2) +3:22-3:29 BINARY_OP 0 (+) +3:8-3:30 CALL 1 +3:8-3:30 POP_TOP +3:8-3:30 JUMP_BACKWARD 39 (to L2) + +2:4-3:30 L8: CLEANUP_THROW +2:4-3:30 L9: JUMP_BACKWARD_NO_INTERRUPT 32 (to L6) +2:24-2:32 L10: END_ASYNC_FOR 33 (from L6) +2:24-2:32 LOAD_COMMON_CONSTANT 7 (None) +2:24-2:32 RETURN_VALUE + + -- L11: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L11 [0] lasti + L2 to L4 -> L10 [1] + L4 to L5 -> L8 [4] + L5 to L7 -> L10 [1] + L7 to L8 -> L11 [0] lasti + L8 to L9 -> L10 [1] + L10 to L11 -> L11 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_end_async_for.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_end_async_for.snap new file mode 100644 index 00000000000..5583383e472 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__async_for_end_async_for.snap @@ -0,0 +1,48 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nasync def f(xs):\n async for x in xs:\n pass\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-3:12 LOAD_CONST 0 (", line 1>) +1:0-3:12 MAKE_FUNCTION +1:0-3:12 STORE_NAME 0 (f) +1:0-3:12 LOAD_COMMON_CONSTANT 7 (None) +1:0-3:12 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:19-2:21 LOAD_FAST_BORROW 0 (xs) +2:19-2:21 GET_AITER +2:4-3:12 L2: GET_ANEXT +2:4-3:12 PUSH_NULL +2:4-3:12 LOAD_COMMON_CONSTANT 7 (None) +2:4-3:12 L3: SEND 4 (to L6) +2:4-3:12 L4: YIELD_VALUE 1 +2:4-3:12 L5: RESUME 3 +2:4-3:12 JUMP_BACKWARD_NO_INTERRUPT 6 (to L3) +2:4-3:12 L6: END_SEND +2:4-3:12 L7: NOT_TAKEN +2:14-2:15 STORE_FAST 1 (x) + +3:8-3:12 JUMP_BACKWARD 14 (to L2) + +2:4-3:12 L8: CLEANUP_THROW +2:4-3:12 L9: JUMP_BACKWARD_NO_INTERRUPT 7 (to L6) +2:19-2:21 L10: END_ASYNC_FOR 8 (from L6) +2:19-2:21 LOAD_COMMON_CONSTANT 7 (None) +2:19-2:21 RETURN_VALUE + + -- L11: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L11 [0] lasti + L2 to L4 -> L10 [1] + L4 to L5 -> L8 [4] + L5 to L7 -> L10 [1] + L7 to L8 -> L11 [0] lasti + L8 to L9 -> L10 [1] + L10 to L11 -> L11 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__augassign_constant_slice_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__augassign_constant_slice_location.snap new file mode 100644 index 00000000000..8c44eba81d9 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__augassign_constant_slice_location.snap @@ -0,0 +1,27 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef aug_const(x, y):\n x[1:2] += y\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:15 LOAD_CONST 0 (", line 1>) +1:0-2:15 MAKE_FUNCTION +1:0-2:15 STORE_NAME 0 (aug_const) +1:0-2:15 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:15 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:4-2:5 LOAD_FAST_BORROW 0 (x) +2:6-2:9 LOAD_CONST 0 (slice(1, 2, None)) +2:4-2:10 COPY 2 +2:4-2:10 COPY 2 +2:4-2:10 BINARY_OP 26 ([]) +2:14-2:15 LOAD_FAST_BORROW 1 (y) +2:4-2:15 BINARY_OP 13 (+=) +2:4-2:10 SWAP 3 +2:4-2:10 SWAP 2 +2:4-2:10 STORE_SUBSCR +2:4-2:10 LOAD_COMMON_CONSTANT 7 (None) +2:4-2:10 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_any_genexpr_call.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_any_genexpr_call.snap new file mode 100644 index 00000000000..674ca418cfe --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_any_genexpr_call.snap @@ -0,0 +1,70 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(xs):\n return any(x for x in xs)\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:29 LOAD_CONST 0 (", line 1>) +1:0-2:29 MAKE_FUNCTION +1:0-2:29 STORE_NAME 0 (f) +1:0-2:29 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:29 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:14 LOAD_GLOBAL 0 (any) +2:11-2:14 COPY 1 +2:11-2:14 LOAD_COMMON_CONSTANT 4 () +2:11-2:14 IS_OP 0 (is) +2:11-2:14 POP_JUMP_IF_FALSE 28 (to L4) +2:11-2:14 NOT_TAKEN +2:11-2:14 POP_TOP +2:14-2:29 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:14-2:29 MAKE_FUNCTION +2:26-2:28 LOAD_FAST_BORROW 0 (xs) +2:14-2:29 CALL 0 +2:11-2:14 PUSH_NULL +2:11-2:14 L1: FOR_ITER 12 (to L3) +2:11-2:14 TO_BOOL +2:11-2:14 POP_JUMP_IF_TRUE 3 (to L2) +2:11-2:14 NOT_TAKEN +2:11-2:14 JUMP_BACKWARD 11 (to L1) +2:11-2:14 L2: POP_ITER +2:11-2:14 LOAD_COMMON_CONSTANT 9 (True) +2:4-2:29 RETURN_VALUE +2:11-2:14 L3: END_FOR +2:11-2:14 POP_ITER +2:11-2:14 LOAD_COMMON_CONSTANT 10 (False) +2:4-2:29 RETURN_VALUE +2:11-2:14 L4: PUSH_NULL +2:14-2:29 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:14-2:29 MAKE_FUNCTION +2:26-2:28 LOAD_FAST_BORROW 0 (xs) +2:14-2:29 CALL 0 +2:11-2:29 CALL 1 +2:4-2:29 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:26-2:28 LOAD_FAST 0 (.0) +2:26-2:28 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:26-2:28 L2: FOR_ITER 7 (to L3) +2:21-2:22 STORE_FAST_LOAD_FAST 17 (x, x) +2:15-2:16 YIELD_VALUE 0 +2:15-2:16 RESUME 9 +2:15-2:16 POP_TOP +2:15-2:16 JUMP_BACKWARD 9 (to L2) +2:26-2:28 L3: END_FOR +2:26-2:28 POP_ITER +2:26-2:28 LOAD_COMMON_CONSTANT 7 (None) +2:26-2:28 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_tuple_list_set_genexpr_calls.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_tuple_list_set_genexpr_calls.snap new file mode 100644 index 00000000000..a5e81ffaf1d --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__builtin_tuple_list_set_genexpr_calls.snap @@ -0,0 +1,182 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef tuple_f(xs):\n return tuple(x for x in xs)\n\ndef list_f(xs):\n return list(x for x in xs)\n\ndef set_f(xs):\n return set(x for x in xs)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:31 LOAD_CONST 0 (", line 1>) +1:0-2:31 MAKE_FUNCTION +1:0-2:31 STORE_NAME 0 (tuple_f) + +4:0-5:30 LOAD_CONST 1 (", line 4>) +4:0-5:30 MAKE_FUNCTION +4:0-5:30 STORE_NAME 1 (list_f) + +7:0-8:29 LOAD_CONST 2 (", line 7>) +7:0-8:29 MAKE_FUNCTION +7:0-8:29 STORE_NAME 2 (set_f) +7:0-8:29 LOAD_COMMON_CONSTANT 7 (None) +7:0-8:29 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:16 LOAD_GLOBAL 0 (tuple) +2:11-2:16 COPY 1 +2:11-2:16 LOAD_COMMON_CONSTANT 2 (tuple) +2:11-2:16 IS_OP 0 (is) +2:11-2:16 POP_JUMP_IF_FALSE 20 (to L3) +2:11-2:16 NOT_TAKEN +2:11-2:16 POP_TOP +2:11-2:16 BUILD_LIST 0 +2:16-2:31 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:16-2:31 MAKE_FUNCTION +2:28-2:30 LOAD_FAST_BORROW 0 (xs) +2:16-2:31 CALL 0 +2:11-2:16 PUSH_NULL +2:11-2:16 L1: FOR_ITER 3 (to L2) +2:11-2:16 LIST_APPEND 3 +2:11-2:16 JUMP_BACKWARD 5 (to L1) +2:11-2:16 L2: END_FOR +2:11-2:16 POP_ITER +2:11-2:16 CALL_INTRINSIC_1 6 (INTRINSIC_LIST_TO_TUPLE) +2:4-2:31 RETURN_VALUE +2:11-2:16 L3: PUSH_NULL +2:16-2:31 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:16-2:31 MAKE_FUNCTION +2:28-2:30 LOAD_FAST_BORROW 0 (xs) +2:16-2:31 CALL 0 +2:11-2:31 CALL 1 +2:4-2:31 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:28-2:30 LOAD_FAST 0 (.0) +2:28-2:30 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:28-2:30 L2: FOR_ITER 7 (to L3) +2:23-2:24 STORE_FAST_LOAD_FAST 17 (x, x) +2:17-2:18 YIELD_VALUE 0 +2:17-2:18 RESUME 9 +2:17-2:18 POP_TOP +2:17-2:18 JUMP_BACKWARD 9 (to L2) +2:28-2:30 L3: END_FOR +2:28-2:30 POP_ITER +2:28-2:30 LOAD_COMMON_CONSTANT 7 (None) +2:28-2:30 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 4>: +4:0-4:0 RESUME 0 + +5:11-5:15 LOAD_GLOBAL 0 (list) +5:11-5:15 COPY 1 +5:11-5:15 LOAD_COMMON_CONSTANT 5 (list) +5:11-5:15 IS_OP 0 (is) +5:11-5:15 POP_JUMP_IF_FALSE 19 (to L3) +5:11-5:15 NOT_TAKEN +5:11-5:15 POP_TOP +5:11-5:15 BUILD_LIST 0 +5:15-5:30 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:15-5:30 MAKE_FUNCTION +5:27-5:29 LOAD_FAST_BORROW 0 (xs) +5:15-5:30 CALL 0 +5:11-5:15 PUSH_NULL +5:11-5:15 L1: FOR_ITER 3 (to L2) +5:11-5:15 LIST_APPEND 3 +5:11-5:15 JUMP_BACKWARD 5 (to L1) +5:11-5:15 L2: END_FOR +5:11-5:15 POP_ITER +5:4-5:30 RETURN_VALUE +5:11-5:15 L3: PUSH_NULL +5:15-5:30 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:15-5:30 MAKE_FUNCTION +5:27-5:29 LOAD_FAST_BORROW 0 (xs) +5:15-5:30 CALL 0 +5:11-5:30 CALL 1 +5:4-5:30 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 5>: + -- RESUME 4 + +5:27-5:29 LOAD_FAST 0 (.0) +5:27-5:29 GET_ITER 0 +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 +5:27-5:29 L2: FOR_ITER 7 (to L3) +5:22-5:23 STORE_FAST_LOAD_FAST 17 (x, x) +5:16-5:17 YIELD_VALUE 0 +5:16-5:17 RESUME 9 +5:16-5:17 POP_TOP +5:16-5:17 JUMP_BACKWARD 9 (to L2) +5:27-5:29 L3: END_FOR +5:27-5:29 POP_ITER +5:27-5:29 LOAD_COMMON_CONSTANT 7 (None) +5:27-5:29 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 7>: +7:0-7:0 RESUME 0 + +8:11-8:14 LOAD_GLOBAL 0 (set) +8:11-8:14 COPY 1 +8:11-8:14 LOAD_COMMON_CONSTANT 6 (set) +8:11-8:14 IS_OP 0 (is) +8:11-8:14 POP_JUMP_IF_FALSE 19 (to L3) +8:11-8:14 NOT_TAKEN +8:11-8:14 POP_TOP +8:11-8:14 BUILD_SET 0 +8:14-8:29 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 8>) +8:14-8:29 MAKE_FUNCTION +8:26-8:28 LOAD_FAST_BORROW 0 (xs) +8:14-8:29 CALL 0 +8:11-8:14 PUSH_NULL +8:11-8:14 L1: FOR_ITER 3 (to L2) +8:11-8:14 SET_ADD 3 +8:11-8:14 JUMP_BACKWARD 5 (to L1) +8:11-8:14 L2: END_FOR +8:11-8:14 POP_ITER +8:4-8:29 RETURN_VALUE +8:11-8:14 L3: PUSH_NULL +8:14-8:29 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 8>) +8:14-8:29 MAKE_FUNCTION +8:26-8:28 LOAD_FAST_BORROW 0 (xs) +8:14-8:29 CALL 0 +8:11-8:29 CALL 1 +8:4-8:29 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 8>: + -- RESUME 4 + +8:26-8:28 LOAD_FAST 0 (.0) +8:26-8:28 GET_ITER 0 +8:?-8:? RETURN_GENERATOR +8:?-8:? POP_TOP +8:0-8:0 L1: RESUME 0 +8:26-8:28 L2: FOR_ITER 7 (to L3) +8:21-8:22 STORE_FAST_LOAD_FAST 17 (x, x) +8:15-8:16 YIELD_VALUE 0 +8:15-8:16 RESUME 9 +8:15-8:16 POP_TOP +8:15-8:16 JUMP_BACKWARD 9 (to L2) +8:26-8:28 L3: END_FOR +8:26-8:28 POP_ITER +8:26-8:28 LOAD_COMMON_CONSTANT 7 (None) +8:26-8:28 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__class_deferred_annotations_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__class_deferred_annotations_location.snap new file mode 100644 index 00000000000..c9d12f9fe74 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__class_deferred_annotations_location.snap @@ -0,0 +1,65 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\n\nclass C:\n \"doc\"\n x: int\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-3:10 LOAD_BUILD_CLASS +1:0-3:10 PUSH_NULL +1:0-3:10 LOAD_CONST 0 (", line 1>) +1:0-3:10 MAKE_FUNCTION +1:0-3:10 LOAD_CONST 1 ('C') +1:0-3:10 CALL 2 +1:0-3:10 STORE_NAME 0 (C) +1:0-3:10 LOAD_COMMON_CONSTANT 7 (None) +1:0-3:10 RETURN_VALUE + +Disassembly of ", line 1>: + -- MAKE_CELL 0 (__classdict__) + +1:0-1:0 RESUME 0 +1:0-1:0 LOAD_NAME 0 (__name__) +1:0-1:0 STORE_NAME 1 (__module__) +1:0-1:0 LOAD_CONST 0 ('C') +1:0-1:0 STORE_NAME 2 (__qualname__) +1:0-1:0 LOAD_SMALL_INT 1 +1:0-1:0 STORE_NAME 3 (__firstlineno__) +1:0-1:0 LOAD_LOCALS +1:0-1:0 STORE_DEREF 0 (__classdict__) + +2:4-2:9 LOAD_CONST 1 ('doc') +2:4-2:9 STORE_NAME 4 (__doc__) + +1:0-1:0 LOAD_FAST_BORROW 0 (__classdict__) +1:0-1:0 BUILD_TUPLE 1 +1:0-1:0 LOAD_CONST 2 (", line 1>) +1:0-1:0 MAKE_FUNCTION +1:0-1:0 SET_FUNCTION_ATTRIBUTE 8 (closure) +1:0-1:0 STORE_NAME 5 (__annotate_func__) +1:0-1:0 LOAD_CONST 3 (()) +1:0-1:0 STORE_NAME 6 (__static_attributes__) +1:0-1:0 LOAD_FAST_BORROW 0 (__classdict__) +1:0-1:0 STORE_NAME 7 (__classdictcell__) +1:0-1:0 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:0 RETURN_VALUE + +Disassembly of ", line 1>: + -- COPY_FREE_VARS 1 + +1:0-1:0 RESUME 0 +1:0-1:0 LOAD_FAST_BORROW 0 (format) +1:0-1:0 LOAD_SMALL_INT 2 +1:0-1:0 COMPARE_OP 132 (>) +1:0-1:0 POP_JUMP_IF_FALSE 3 (to L1) +1:0-1:0 NOT_TAKEN +1:0-1:0 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:0-1:0 RAISE_VARARGS 1 +1:0-1:0 L1: BUILD_MAP 0 + +3:7-3:10 LOAD_DEREF 1 (__classdict__) +3:7-3:10 LOAD_FROM_DICT_OR_GLOBALS 0 (int) +3:4-3:10 COPY 2 +3:4-3:10 LOAD_CONST 1 ('x') + +1:0-1:0 STORE_SUBSCR +1:0-1:0 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_collection_depth.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_collection_depth.snap new file mode 100644 index 00000000000..5db02a260b7 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_collection_depth.snap @@ -0,0 +1,110 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(xs, ys):\n return [x for x in xs for y in ys]\n\nasync def g(xs, ys):\n return [x async for x in xs for y in ys]\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:38 LOAD_CONST 0 (", line 1>) +1:0-2:38 MAKE_FUNCTION +1:0-2:38 STORE_NAME 0 (f) + +4:0-5:44 LOAD_CONST 1 (", line 4>) +4:0-5:44 MAKE_FUNCTION +4:0-5:44 STORE_NAME 1 (g) +4:0-5:44 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:44 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:23-2:25 LOAD_FAST 0 (xs) +2:11-2:38 LOAD_FAST_AND_CLEAR 2 (x) +2:11-2:38 LOAD_FAST_AND_CLEAR 3 (y) +2:11-2:38 SWAP 3 +2:11-2:38 L1: BUILD_LIST 0 +2:11-2:38 SWAP 2 +2:23-2:25 GET_ITER 0 +2:23-2:25 L2: FOR_ITER 13 (to L5) +2:18-2:19 STORE_FAST_LOAD_FAST 33 (x, ys) +2:35-2:37 GET_ITER 0 +2:35-2:37 L3: FOR_ITER 4 (to L4) +2:30-2:31 STORE_FAST_LOAD_FAST 50 (y, x) +2:12-2:13 LIST_APPEND 5 +2:12-2:13 JUMP_BACKWARD 6 (to L3) +2:35-2:37 L4: END_FOR +2:35-2:37 POP_ITER +2:12-2:13 JUMP_BACKWARD 15 (to L2) +2:23-2:25 L5: END_FOR +2:23-2:25 POP_ITER +2:11-2:38 L6: SWAP 3 +2:11-2:38 STORE_FAST 3 (y) +2:11-2:38 STORE_FAST 2 (x) +2:4-2:38 RETURN_VALUE + + -- L7: SWAP 2 + -- POP_TOP + +2:11-2:38 SWAP 3 +2:11-2:38 STORE_FAST 3 (y) +2:11-2:38 STORE_FAST 2 (x) +2:11-2:38 RERAISE 0 +ExceptionTable: + L1 to L6 -> L7 [3] + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +5:29-5:31 LOAD_FAST_BORROW 0 (xs) +5:11-5:44 LOAD_FAST_AND_CLEAR 2 (x) +5:11-5:44 LOAD_FAST_AND_CLEAR 3 (y) +5:11-5:44 SWAP 3 +5:11-5:44 L2: BUILD_LIST 0 +5:11-5:44 SWAP 2 +5:29-5:31 GET_AITER +5:11-5:44 L3: GET_ANEXT +5:11-5:44 PUSH_NULL +5:11-5:44 LOAD_COMMON_CONSTANT 7 (None) +5:11-5:44 L4: SEND 4 (to L7) +5:11-5:44 L5: YIELD_VALUE 1 +5:11-5:44 L6: RESUME 3 +5:11-5:44 JUMP_BACKWARD_NO_INTERRUPT 6 (to L4) +5:11-5:44 L7: END_SEND +5:24-5:25 L8: STORE_FAST_LOAD_FAST 33 (x, ys) +5:41-5:43 GET_ITER 0 +5:41-5:43 L9: FOR_ITER 4 (to L10) +5:36-5:37 STORE_FAST_LOAD_FAST 50 (y, x) +5:12-5:13 LIST_APPEND 4 +5:12-5:13 JUMP_BACKWARD 6 (to L9) +5:41-5:43 L10: END_FOR +5:41-5:43 POP_ITER +5:12-5:13 JUMP_BACKWARD 23 (to L3) +5:11-5:44 L11: CLEANUP_THROW +5:11-5:44 L12: JUMP_BACKWARD_NO_INTERRUPT 16 (to L7) +5:11-5:44 L13: END_ASYNC_FOR 17 (from L7) +5:11-5:44 L14: SWAP 3 +5:11-5:44 STORE_FAST 3 (y) +5:11-5:44 STORE_FAST 2 (x) +5:4-5:44 RETURN_VALUE + + -- L15: SWAP 2 + -- POP_TOP + +5:11-5:44 SWAP 3 +5:11-5:44 STORE_FAST 3 (y) +5:11-5:44 STORE_FAST 2 (x) +5:11-5:44 RERAISE 0 + + -- L16: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L16 [0] lasti + L2 to L3 -> L15 [3] + L3 to L5 -> L13 [4] + L5 to L6 -> L11 [7] + L6 to L8 -> L13 [4] + L8 to L11 -> L15 [3] + L11 to L12 -> L13 [4] + L13 to L14 -> L15 [3] + L14 to L16 -> L16 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_list_iterable_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_list_iterable_location.snap new file mode 100644 index 00000000000..c7bca48e842 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__comprehension_list_iterable_location.snap @@ -0,0 +1,83 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nasync def f(i):\n return i\n\nasync def run_list():\n return [await c for c in [f(1), f(41)]]\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:12 LOAD_CONST 0 (", line 1>) +1:0-2:12 MAKE_FUNCTION +1:0-2:12 STORE_NAME 0 (f) + +4:0-5:43 LOAD_CONST 1 (", line 4>) +4:0-5:43 MAKE_FUNCTION +4:0-5:43 STORE_NAME 1 (run_list) +4:0-5:43 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:43 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:11-2:12 LOAD_FAST_BORROW 0 (i) +2:4-2:12 RETURN_VALUE + + -- L2: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [0] lasti + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +5:30-5:31 LOAD_GLOBAL 1 (f + NULL) +5:32-5:33 LOAD_SMALL_INT 1 +5:30-5:34 CALL 1 +5:36-5:37 LOAD_GLOBAL 1 (f + NULL) +5:38-5:40 LOAD_SMALL_INT 41 +5:36-5:41 CALL 1 +5:29-5:42 BUILD_LIST 2 +5:11-5:43 LOAD_FAST_AND_CLEAR 0 (c) +5:11-5:43 SWAP 2 +5:11-5:43 L2: BUILD_LIST 0 +5:11-5:43 SWAP 2 +5:29-5:42 GET_ITER 0 +5:29-5:42 L3: FOR_ITER 14 (to L8) +5:24-5:25 STORE_FAST_LOAD_FAST 0 (c, c) +5:12-5:19 GET_AWAITABLE 0 +5:12-5:19 PUSH_NULL +5:12-5:19 LOAD_COMMON_CONSTANT 7 (None) +5:12-5:19 L4: SEND 4 (to L7) +5:12-5:19 L5: YIELD_VALUE 1 +5:12-5:19 L6: RESUME 3 +5:12-5:19 JUMP_BACKWARD_NO_INTERRUPT 6 (to L4) +5:12-5:19 L7: END_SEND +5:12-5:19 LIST_APPEND 3 +5:12-5:19 JUMP_BACKWARD 16 (to L3) +5:29-5:42 L8: END_FOR +5:29-5:42 POP_ITER +5:11-5:43 L9: SWAP 2 +5:11-5:43 STORE_FAST 0 (c) +5:4-5:43 RETURN_VALUE +5:12-5:19 L10: CLEANUP_THROW +5:12-5:19 L11: JUMP_BACKWARD_NO_INTERRUPT 11 (to L7) + + -- L12: SWAP 2 + -- POP_TOP + +5:11-5:43 SWAP 2 +5:11-5:43 STORE_FAST 0 (c) +5:11-5:43 RERAISE 0 + + -- L13: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L13 [0] lasti + L2 to L5 -> L12 [2] + L5 to L6 -> L10 [7] + L6 to L9 -> L12 [2] + L9 to L10 -> L13 [0] lasti + L10 to L11 -> L12 [2] + L12 to L13 -> L13 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__decorated_definition_locations.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__decorated_definition_locations.snap new file mode 100644 index 00000000000..91c9b7c85df --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__decorated_definition_locations.snap @@ -0,0 +1,164 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef dec(f): return f\n\nclass C:\n @dec\n def f(self):\n yield\n\n@dec\nclass D:\n pass\n\nclass E:\n @dec\n def g(self, flags: int, /) -> memoryview:\n raise NotImplementedError\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-1:20 LOAD_CONST 0 (", line 1>) +1:0-1:20 MAKE_FUNCTION +1:0-1:20 STORE_NAME 0 (dec) + +3:0-6:13 LOAD_BUILD_CLASS +3:0-6:13 PUSH_NULL +3:0-6:13 LOAD_CONST 1 (", line 3>) +3:0-6:13 MAKE_FUNCTION +3:0-6:13 LOAD_CONST 2 ('C') +3:0-6:13 CALL 2 +3:0-6:13 STORE_NAME 1 (C) + +8:1-8:4 LOAD_NAME 0 (dec) + +9:0-10:8 LOAD_BUILD_CLASS +9:0-10:8 PUSH_NULL +9:0-10:8 LOAD_CONST 3 (", line 8>) +9:0-10:8 MAKE_FUNCTION +9:0-10:8 LOAD_CONST 4 ('D') +9:0-10:8 CALL 2 + +8:1-8:4 CALL 0 + +9:0-10:8 STORE_NAME 2 (D) + +12:0-15:33 LOAD_BUILD_CLASS +12:0-15:33 PUSH_NULL +12:0-15:33 LOAD_CONST 5 (", line 12>) +12:0-15:33 MAKE_FUNCTION +12:0-15:33 LOAD_CONST 6 ('E') +12:0-15:33 CALL 2 +12:0-15:33 STORE_NAME 3 (E) +12:0-15:33 LOAD_COMMON_CONSTANT 7 (None) +12:0-15:33 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:19-1:20 LOAD_FAST_BORROW 0 (f) +1:12-1:20 RETURN_VALUE + +Disassembly of ", line 3>: + -- MAKE_CELL 0 (__classdict__) + +3:0-3:0 RESUME 0 +3:0-3:0 LOAD_NAME 0 (__name__) +3:0-3:0 STORE_NAME 1 (__module__) +3:0-3:0 LOAD_CONST 0 ('C') +3:0-3:0 STORE_NAME 2 (__qualname__) +3:0-3:0 LOAD_SMALL_INT 3 +3:0-3:0 STORE_NAME 3 (__firstlineno__) +3:0-3:0 LOAD_LOCALS +3:0-3:0 STORE_DEREF 0 (__classdict__) + +4:5-4:8 LOAD_NAME 4 (dec) + +5:4-6:13 LOAD_CONST 1 (", line 4>) +5:4-6:13 MAKE_FUNCTION + +4:5-4:8 CALL 0 + +5:4-6:13 STORE_NAME 5 (f) +5:4-6:13 LOAD_CONST 2 (()) +5:4-6:13 STORE_NAME 6 (__static_attributes__) +5:4-6:13 LOAD_FAST_BORROW 0 (__classdict__) +5:4-6:13 STORE_NAME 7 (__classdictcell__) +5:4-6:13 LOAD_COMMON_CONSTANT 7 (None) +5:4-6:13 RETURN_VALUE + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +6:8-6:13 LOAD_COMMON_CONSTANT 7 (None) +6:8-6:13 YIELD_VALUE 0 +6:8-6:13 RESUME 9 +6:8-6:13 POP_TOP +6:8-6:13 LOAD_COMMON_CONSTANT 7 (None) +6:8-6:13 RETURN_VALUE + + -- L2: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [0] lasti + +Disassembly of ", line 8>: +8:0-8:0 RESUME 0 +8:0-8:0 LOAD_NAME 0 (__name__) +8:0-8:0 STORE_NAME 1 (__module__) +8:0-8:0 LOAD_CONST 0 ('D') +8:0-8:0 STORE_NAME 2 (__qualname__) +8:0-8:0 LOAD_SMALL_INT 8 +8:0-8:0 STORE_NAME 3 (__firstlineno__) + +10:4-10:8 LOAD_CONST 1 (()) +10:4-10:8 STORE_NAME 4 (__static_attributes__) +10:4-10:8 LOAD_COMMON_CONSTANT 7 (None) +10:4-10:8 RETURN_VALUE + +Disassembly of ", line 12>: + -- MAKE_CELL 0 (__classdict__) + +12:0-12:0 RESUME 0 +12:0-12:0 LOAD_NAME 0 (__name__) +12:0-12:0 STORE_NAME 1 (__module__) +12:0-12:0 LOAD_CONST 0 ('E') +12:0-12:0 STORE_NAME 2 (__qualname__) +12:0-12:0 LOAD_SMALL_INT 12 +12:0-12:0 STORE_NAME 3 (__firstlineno__) +12:0-12:0 LOAD_LOCALS +12:0-12:0 STORE_DEREF 0 (__classdict__) + +13:5-13:8 LOAD_NAME 4 (dec) + +14:4-15:33 LOAD_FAST_BORROW 0 (__classdict__) +14:4-15:33 BUILD_TUPLE 1 +14:4-15:33 LOAD_CONST 1 (", line 14>) +14:4-15:33 MAKE_FUNCTION +14:4-15:33 SET_FUNCTION_ATTRIBUTE 8 (closure) +14:4-15:33 LOAD_CONST 2 (", line 13>) +14:4-15:33 MAKE_FUNCTION +14:4-15:33 SET_FUNCTION_ATTRIBUTE 16 (annotate) + +13:5-13:8 CALL 0 + +14:4-15:33 STORE_NAME 5 (g) +14:4-15:33 LOAD_CONST 3 (()) +14:4-15:33 STORE_NAME 6 (__static_attributes__) +14:4-15:33 LOAD_FAST_BORROW 0 (__classdict__) +14:4-15:33 STORE_NAME 7 (__classdictcell__) +14:4-15:33 LOAD_COMMON_CONSTANT 7 (None) +14:4-15:33 RETURN_VALUE + +Disassembly of ", line 14>: + -- COPY_FREE_VARS 1 + +14:0-14:0 RESUME 0 +14:4-15:33 LOAD_FAST_BORROW 0 (format) +14:4-15:33 LOAD_SMALL_INT 2 +14:4-15:33 COMPARE_OP 132 (>) +14:4-15:33 POP_JUMP_IF_FALSE 3 (to L1) +14:4-15:33 NOT_TAKEN +14:4-15:33 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +14:4-15:33 RAISE_VARARGS 1 +14:4-15:33 L1: LOAD_CONST 1 ('flags') +14:23-14:26 LOAD_DEREF 1 (__classdict__) +14:23-14:26 LOAD_FROM_DICT_OR_GLOBALS 0 (int) +14:4-15:33 LOAD_CONST 2 ('return') +14:34-14:44 LOAD_DEREF 1 (__classdict__) +14:34-14:44 LOAD_FROM_DICT_OR_GLOBALS 1 (memoryview) +14:4-15:33 BUILD_MAP 2 +14:4-15:33 RETURN_VALUE + +Disassembly of ", line 13>: +13:0-13:0 RESUME 0 + +15:14-15:33 LOAD_GLOBAL 0 (NotImplementedError) +15:8-15:33 RAISE_VARARGS 1 diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__empty_module_implicit_return_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__empty_module_implicit_return_location.snap new file mode 100644 index 00000000000..75795d00b10 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__empty_module_implicit_return_location.snap @@ -0,0 +1,7 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(\"\", true)" +--- +0:0-1:0 RESUME 0 +0:0-1:0 LOAD_COMMON_CONSTANT 7 (None) +0:0-1:0 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__fstring_format_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__fstring_format_location.snap new file mode 100644 index 00000000000..904cde2d209 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__fstring_format_location.snap @@ -0,0 +1,32 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef simple(self):\n return f'{self.value}'\n\ndef spec(x):\n return f'{x!r:>3}'\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:26 LOAD_CONST 0 (", line 1>) +1:0-2:26 MAKE_FUNCTION +1:0-2:26 STORE_NAME 0 (simple) + +4:0-5:22 LOAD_CONST 1 (", line 4>) +4:0-5:22 MAKE_FUNCTION +4:0-5:22 STORE_NAME 1 (spec) +4:0-5:22 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:22 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:14-2:18 LOAD_FAST_BORROW 0 (self) +2:14-2:24 LOAD_ATTR 0 (value) +2:13-2:25 FORMAT_SIMPLE +2:4-2:26 RETURN_VALUE + +Disassembly of ", line 4>: +4:0-4:0 RESUME 0 + +5:14-5:15 LOAD_FAST_BORROW 0 (x) +5:13-5:21 CONVERT_VALUE 2 (repr) +5:18-5:20 LOAD_CONST 0 ('>3') +5:13-5:21 FORMAT_WITH_SPEC +5:4-5:22 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__future_annotation_string_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__future_annotation_string_location.snap new file mode 100644 index 00000000000..82a554f86af --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__future_annotation_string_location.snap @@ -0,0 +1,41 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nfrom __future__ import annotations\nclass Bar:\n foo: Foo\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-1:34 LOAD_SMALL_INT 0 +1:0-1:34 LOAD_CONST 1 (('annotations',)) +1:0-1:34 IMPORT_NAME 0 (__future__) +1:0-1:34 IMPORT_FROM 1 (annotations) +1:0-1:34 STORE_NAME 1 (annotations) +1:0-1:34 POP_TOP + +2:0-3:12 LOAD_BUILD_CLASS +2:0-3:12 PUSH_NULL +2:0-3:12 LOAD_CONST 2 (", line 2>) +2:0-3:12 MAKE_FUNCTION +2:0-3:12 LOAD_CONST 3 ('Bar') +2:0-3:12 CALL 2 +2:0-3:12 STORE_NAME 2 (Bar) +2:0-3:12 LOAD_COMMON_CONSTANT 7 (None) +2:0-3:12 RETURN_VALUE + +Disassembly of ", line 2>: +2:0-2:0 RESUME 0 +2:0-2:0 LOAD_NAME 0 (__name__) +2:0-2:0 STORE_NAME 1 (__module__) +2:0-2:0 LOAD_CONST 0 ('Bar') +2:0-2:0 STORE_NAME 2 (__qualname__) +2:0-2:0 LOAD_SMALL_INT 2 +2:0-2:0 STORE_NAME 3 (__firstlineno__) +2:0-2:0 SETUP_ANNOTATIONS + +3:9-3:12 LOAD_CONST 1 ('Foo') +3:4-3:12 LOAD_NAME 4 (__annotations__) +3:4-3:12 LOAD_CONST 2 ('foo') +3:4-3:12 STORE_SUBSCR +3:4-3:12 LOAD_CONST 3 (()) +3:4-3:12 STORE_NAME 5 (__static_attributes__) +3:4-3:12 LOAD_COMMON_CONSTANT 7 (None) +3:4-3:12 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_class_type_params_store_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_class_type_params_store_location.snap new file mode 100644 index 00000000000..f0d8113a6a1 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_class_type_params_store_location.snap @@ -0,0 +1,64 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef outer():\n class X[T]: ...\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:19 LOAD_CONST 0 (", line 1>) +1:0-2:19 MAKE_FUNCTION +1:0-2:19 STORE_NAME 0 (outer) +1:0-2:19 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:19 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:4-2:19 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:4-2:19 MAKE_FUNCTION +2:4-2:19 PUSH_NULL +2:4-2:19 CALL 0 +2:4-2:19 STORE_FAST 0 (X) +2:4-2:19 LOAD_COMMON_CONSTANT 7 (None) +2:4-2:19 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- MAKE_CELL 2 (.type_params) + +2:0-2:0 RESUME 0 +2:12-2:13 LOAD_CONST 0 ('T') +2:12-2:13 CALL_INTRINSIC_1 7 (INTRINSIC_TYPEVAR) +2:12-2:13 COPY 1 +2:12-2:13 STORE_FAST 0 (T) +2:12-2:13 BUILD_TUPLE 1 +2:4-2:19 STORE_DEREF 2 (.type_params) +2:4-2:19 LOAD_BUILD_CLASS +2:4-2:19 PUSH_NULL +2:4-2:19 LOAD_FAST_BORROW 2 (.type_params) +2:4-2:19 BUILD_TUPLE 1 +2:4-2:19 LOAD_CONST 1 (", line 2>) +2:4-2:19 MAKE_FUNCTION +2:4-2:19 SET_FUNCTION_ATTRIBUTE 8 (closure) +2:4-2:19 LOAD_CONST 2 ('X') +2:4-2:19 LOAD_DEREF 2 (.type_params) +2:4-2:19 CALL_INTRINSIC_1 10 (INTRINSIC_SUBSCRIPT_GENERIC) +2:4-2:19 STORE_FAST_LOAD_FAST 17 (.generic_base, .generic_base) +2:4-2:19 CALL 3 +2:4-2:19 RETURN_VALUE + +Disassembly of ", line 2>: + -- COPY_FREE_VARS 1 + +2:0-2:0 RESUME 0 +2:0-2:0 LOAD_NAME 0 (__name__) +2:0-2:0 STORE_NAME 1 (__module__) +2:0-2:0 LOAD_CONST 0 ('outer..X') +2:0-2:0 STORE_NAME 2 (__qualname__) +2:0-2:0 LOAD_SMALL_INT 2 +2:0-2:0 STORE_NAME 3 (__firstlineno__) +2:0-2:0 LOAD_LOCALS +2:0-2:0 LOAD_FROM_DICT_OR_DEREF 0 (.type_params) +2:0-2:0 STORE_NAME 4 (__type_params__) +2:16-2:19 LOAD_CONST 1 (()) +2:16-2:19 STORE_NAME 5 (__static_attributes__) +2:16-2:19 LOAD_COMMON_CONSTANT 7 (None) +2:16-2:19 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_function_annotation_scope_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_function_annotation_scope_location.snap new file mode 100644 index 00000000000..0b9fa6890ee --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_function_annotation_scope_location.snap @@ -0,0 +1,48 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f[T](x: int): ...\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-1:21 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 1>) +1:0-1:21 MAKE_FUNCTION +1:0-1:21 PUSH_NULL +1:0-1:21 CALL 0 +1:0-1:21 STORE_NAME 0 (f) +1:0-1:21 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:21 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 1>: +1:0-1:0 RESUME 0 +1:6-1:7 LOAD_CONST 0 ('T') +1:6-1:7 CALL_INTRINSIC_1 7 (INTRINSIC_TYPEVAR) +1:6-1:7 COPY 1 +1:6-1:7 STORE_FAST 1 (T) +1:6-1:7 BUILD_TUPLE 1 +1:0-1:21 LOAD_CONST 1 (", line 1>) +1:0-1:21 MAKE_FUNCTION +1:0-1:21 LOAD_CONST 2 (", line 1>) +1:0-1:21 MAKE_FUNCTION +1:0-1:21 SET_FUNCTION_ATTRIBUTE 16 (annotate) +1:0-1:21 SWAP 2 +1:0-1:21 CALL_INTRINSIC_2 4 (INTRINSIC_SET_FUNCTION_TYPE_PARAMS) +1:0-1:21 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:0-1:21 LOAD_FAST_BORROW 0 (format) +1:0-1:21 LOAD_SMALL_INT 2 +1:0-1:21 COMPARE_OP 132 (>) +1:0-1:21 POP_JUMP_IF_FALSE 3 (to L1) +1:0-1:21 NOT_TAKEN +1:0-1:21 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:0-1:21 RAISE_VARARGS 1 +1:0-1:21 L1: LOAD_CONST 1 ('x') +1:12-1:15 LOAD_GLOBAL 0 (int) +1:0-1:21 BUILD_MAP 1 +1:0-1:21 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:18-1:21 LOAD_COMMON_CONSTANT 7 (None) +1:18-1:21 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_typealias_wrapper_return_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_typealias_wrapper_return_location.snap new file mode 100644 index 00000000000..dd85d9a57e1 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__generic_typealias_wrapper_return_location.snap @@ -0,0 +1,48 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ntype A[T] = T\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-1:13 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 1>) +1:0-1:13 MAKE_FUNCTION +1:0-1:13 PUSH_NULL +1:0-1:13 CALL 0 +1:0-1:13 STORE_NAME 0 (A) +1:0-1:13 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:13 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 1>: + -- MAKE_CELL 0 (T) + +1:0-1:0 RESUME 0 +1:0-1:13 LOAD_CONST 0 ('A') +1:7-1:8 LOAD_CONST 1 ('T') +1:7-1:8 CALL_INTRINSIC_1 7 (INTRINSIC_TYPEVAR) +1:7-1:8 COPY 1 +1:7-1:8 STORE_DEREF 0 (T) +1:7-1:8 BUILD_TUPLE 1 +1:0-1:13 LOAD_CONST 2 ((1,)) +1:0-1:13 LOAD_FAST_BORROW 0 (T) +1:0-1:13 BUILD_TUPLE 1 +1:0-1:13 LOAD_CONST 3 (", line 1>) +1:0-1:13 MAKE_FUNCTION +1:0-1:13 SET_FUNCTION_ATTRIBUTE 8 (closure) +1:0-1:13 SET_FUNCTION_ATTRIBUTE 1 (defaults) +1:0-1:13 BUILD_TUPLE 3 +1:0-1:13 CALL_INTRINSIC_1 11 (INTRINSIC_TYPEALIAS) +1:0-1:13 RETURN_VALUE + +Disassembly of ", line 1>: + -- COPY_FREE_VARS 1 + +1:0-1:0 RESUME 0 +1:0-1:13 LOAD_FAST_BORROW 0 (.format) +1:0-1:13 LOAD_SMALL_INT 2 +1:0-1:13 COMPARE_OP 132 (>) +1:0-1:13 POP_JUMP_IF_FALSE 3 (to L1) +1:0-1:13 NOT_TAKEN +1:0-1:13 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:0-1:13 RAISE_VARARGS 1 +1:12-1:13 L1: LOAD_DEREF 1 (T) +1:0-1:13 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_closure_and_call_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_closure_and_call_location.snap new file mode 100644 index 00000000000..266fe98e0be --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_closure_and_call_location.snap @@ -0,0 +1,48 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(parameters):\n return ((p, type(p)) for p in parameters)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:45 LOAD_CONST 0 (", line 1>) +1:0-2:45 MAKE_FUNCTION +1:0-2:45 STORE_NAME 0 (f) +1:0-2:45 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:45 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:45 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:11-2:45 MAKE_FUNCTION +2:34-2:44 LOAD_FAST_BORROW 0 (parameters) +2:11-2:45 CALL 0 +2:4-2:45 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:34-2:44 LOAD_FAST 0 (.0) +2:34-2:44 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:34-2:44 L2: FOR_ITER 18 (to L3) +2:29-2:30 STORE_FAST_LOAD_FAST 17 (p, p) +2:16-2:20 LOAD_GLOBAL 1 (type + NULL) +2:21-2:22 LOAD_FAST_BORROW 1 (p) +2:16-2:23 CALL 1 +2:12-2:24 BUILD_TUPLE 2 +2:12-2:24 YIELD_VALUE 0 +2:12-2:24 RESUME 9 +2:12-2:24 POP_TOP +2:12-2:24 JUMP_BACKWARD 20 (to L2) +2:34-2:44 L3: END_FOR +2:34-2:44 POP_ITER +2:34-2:44 LOAD_COMMON_CONSTANT 7 (None) +2:34-2:44 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_filter_cleanup_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_filter_cleanup_location.snap new file mode 100644 index 00000000000..6318f1ac1d3 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_filter_cleanup_location.snap @@ -0,0 +1,104 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef simple(names):\n return (x for x in names if not _ishidden(x))\n\ndef boolop(fields):\n return (f for f in fields if f.init and not f.kw_only)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:49 LOAD_CONST 0 (", line 1>) +1:0-2:49 MAKE_FUNCTION +1:0-2:49 STORE_NAME 0 (simple) + +4:0-5:58 LOAD_CONST 1 (", line 4>) +4:0-5:58 MAKE_FUNCTION +4:0-5:58 STORE_NAME 1 (boolop) +4:0-5:58 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:58 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:49 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:11-2:49 MAKE_FUNCTION +2:23-2:28 LOAD_FAST_BORROW 0 (names) +2:11-2:49 CALL 0 +2:4-2:49 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:23-2:28 LOAD_FAST 0 (.0) +2:23-2:28 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:23-2:28 L2: FOR_ITER 27 (to L5) +2:18-2:19 STORE_FAST 1 (x) +2:36-2:45 LOAD_GLOBAL 1 (_ishidden + NULL) +2:46-2:47 LOAD_FAST_BORROW 1 (x) +2:36-2:48 CALL 1 +2:36-2:48 TO_BOOL +2:12-2:13 L3: POP_JUMP_IF_FALSE 3 (to L4) +2:12-2:13 NOT_TAKEN +2:12-2:13 JUMP_BACKWARD 22 (to L2) +2:12-2:13 L4: LOAD_FAST_BORROW 1 (x) +2:12-2:13 YIELD_VALUE 0 +2:12-2:13 RESUME 9 +2:12-2:13 POP_TOP +2:12-2:13 JUMP_BACKWARD 29 (to L2) +2:23-2:28 L5: END_FOR +2:23-2:28 POP_ITER +2:23-2:28 LOAD_COMMON_CONSTANT 7 (None) +2:23-2:28 RETURN_VALUE + + -- L6: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L3 -> L6 [2] lasti + L4 to L6 -> L6 [2] lasti + +Disassembly of ", line 4>: +4:0-4:0 RESUME 0 + +5:11-5:58 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:11-5:58 MAKE_FUNCTION +5:23-5:29 LOAD_FAST_BORROW 0 (fields) +5:11-5:58 CALL 0 +5:4-5:58 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 5>: + -- RESUME 4 + +5:23-5:29 LOAD_FAST 0 (.0) +5:23-5:29 GET_ITER 0 +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 +5:23-5:29 L2: FOR_ITER 47 (to L7) +5:18-5:19 STORE_FAST_LOAD_FAST 17 (f, f) +5:33-5:39 LOAD_ATTR 0 (init) +5:33-5:39 TO_BOOL +5:12-5:13 L3: POP_JUMP_IF_TRUE 3 (to L4) +5:12-5:13 NOT_TAKEN +5:12-5:13 JUMP_BACKWARD 22 (to L2) +5:48-5:49 L4: LOAD_FAST_BORROW 1 (f) +5:48-5:57 LOAD_ATTR 2 (kw_only) +5:48-5:57 TO_BOOL +5:12-5:13 L5: POP_JUMP_IF_FALSE 3 (to L6) +5:12-5:13 NOT_TAKEN +5:12-5:13 JUMP_BACKWARD 42 (to L2) +5:12-5:13 L6: LOAD_FAST_BORROW 1 (f) +5:12-5:13 YIELD_VALUE 0 +5:12-5:13 RESUME 9 +5:12-5:13 POP_TOP +5:12-5:13 JUMP_BACKWARD 49 (to L2) +5:23-5:29 L7: END_FOR +5:23-5:29 POP_ITER +5:23-5:29 LOAD_COMMON_CONSTANT 7 (None) +5:23-5:29 RETURN_VALUE + + -- L8: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L3 -> L8 [2] lasti + L4 to L5 -> L8 [2] lasti + L6 to L8 -> L8 [2] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_prefix.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_prefix.snap new file mode 100644 index 00000000000..aff0ded464d --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__genexpr_prefix.snap @@ -0,0 +1,103 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(xs):\n return (x for x in xs)\n\nasync def g(xs):\n return (x async for x in xs)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:26 LOAD_CONST 0 (", line 1>) +1:0-2:26 MAKE_FUNCTION +1:0-2:26 STORE_NAME 0 (f) + +4:0-5:32 LOAD_CONST 1 (", line 4>) +4:0-5:32 MAKE_FUNCTION +4:0-5:32 STORE_NAME 1 (g) +4:0-5:32 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:32 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:26 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:11-2:26 MAKE_FUNCTION +2:23-2:25 LOAD_FAST_BORROW 0 (xs) +2:11-2:26 CALL 0 +2:4-2:26 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:23-2:25 LOAD_FAST 0 (.0) +2:23-2:25 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:23-2:25 L2: FOR_ITER 7 (to L3) +2:18-2:19 STORE_FAST_LOAD_FAST 17 (x, x) +2:12-2:13 YIELD_VALUE 0 +2:12-2:13 RESUME 9 +2:12-2:13 POP_TOP +2:12-2:13 JUMP_BACKWARD 9 (to L2) +2:23-2:25 L3: END_FOR +2:23-2:25 POP_ITER +2:23-2:25 LOAD_COMMON_CONSTANT 7 (None) +2:23-2:25 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +5:11-5:32 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:11-5:32 MAKE_FUNCTION +5:29-5:31 LOAD_FAST_BORROW 0 (xs) +5:11-5:32 CALL 0 +5:4-5:32 RETURN_VALUE + + -- L2: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [0] lasti + +Disassembly of at 0xdeadbeef, file "", line 5>: + -- RESUME 4 + +5:29-5:31 LOAD_FAST_BORROW 0 (.0) +5:29-5:31 GET_AITER +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 +5:11-5:32 L2: GET_ANEXT +5:11-5:32 PUSH_NULL +5:11-5:32 LOAD_COMMON_CONSTANT 7 (None) +5:11-5:32 L3: SEND 4 (to L6) +5:11-5:32 L4: YIELD_VALUE 1 +5:11-5:32 L5: RESUME 3 +5:11-5:32 JUMP_BACKWARD_NO_INTERRUPT 6 (to L3) +5:11-5:32 L6: END_SEND +5:24-5:25 L7: STORE_FAST_LOAD_FAST 17 (x, x) +5:12-5:13 CALL_INTRINSIC_1 4 (INTRINSIC_ASYNC_GEN_WRAP) +5:12-5:13 YIELD_VALUE 0 +5:12-5:13 RESUME 9 +5:12-5:13 POP_TOP +5:12-5:13 JUMP_BACKWARD 18 (to L2) +5:11-5:32 L8: CLEANUP_THROW +5:11-5:32 L9: JUMP_BACKWARD_NO_INTERRUPT 11 (to L6) +5:11-5:32 L10: END_ASYNC_FOR 12 (from L6) +5:11-5:32 LOAD_COMMON_CONSTANT 7 (None) +5:11-5:32 RETURN_VALUE + + -- L11: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L11 [1] lasti + L2 to L4 -> L10 [1] + L4 to L5 -> L8 [4] + L5 to L7 -> L10 [1] + L7 to L8 -> L11 [1] lasti + L8 to L9 -> L10 [1] + L10 to L11 -> L11 [1] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_parenthesized_element_range.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_parenthesized_element_range.snap new file mode 100644 index 00000000000..7d1cdd914c2 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_parenthesized_element_range.snap @@ -0,0 +1,216 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef bytes_binop():\n return bytes((x ^ 0x5C) for x in range(256))\n\ndef dict_tuple(d):\n return dict((v, k) for (k, v) in d.items())\n\ndef plain_tuple_elt(xs):\n return list((x, y) for x, y in xs)\n\ndef explicit_gen(xs):\n return list(((x, y) for x, y in xs))\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:48 LOAD_CONST 0 (", line 1>) +1:0-2:48 MAKE_FUNCTION +1:0-2:48 STORE_NAME 0 (bytes_binop) + +4:0-5:47 LOAD_CONST 1 (", line 4>) +4:0-5:47 MAKE_FUNCTION +4:0-5:47 STORE_NAME 1 (dict_tuple) + +7:0-8:38 LOAD_CONST 2 (", line 7>) +7:0-8:38 MAKE_FUNCTION +7:0-8:38 STORE_NAME 2 (plain_tuple_elt) + +10:0-11:40 LOAD_CONST 3 (", line 10>) +10:0-11:40 MAKE_FUNCTION +10:0-11:40 STORE_NAME 3 (explicit_gen) +10:0-11:40 LOAD_COMMON_CONSTANT 7 (None) +10:0-11:40 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:16 LOAD_GLOBAL 1 (bytes + NULL) +2:16-2:48 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:16-2:48 MAKE_FUNCTION +2:37-2:42 LOAD_GLOBAL 3 (range + NULL) +2:43-2:46 LOAD_CONST 1 (256) +2:37-2:47 CALL 1 +2:16-2:48 CALL 0 +2:11-2:48 CALL 1 +2:4-2:48 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:37-2:47 LOAD_FAST 0 (.0) +2:37-2:47 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:37-2:47 L2: FOR_ITER 14 (to L3) +2:32-2:33 STORE_FAST_LOAD_FAST 17 (x, x) +2:22-2:26 LOAD_SMALL_INT 92 +2:18-2:26 BINARY_OP 12 (^) +2:18-2:26 YIELD_VALUE 0 +2:18-2:26 RESUME 9 +2:18-2:26 POP_TOP +2:18-2:26 JUMP_BACKWARD 16 (to L2) +2:37-2:47 L3: END_FOR +2:37-2:47 POP_ITER +2:37-2:47 LOAD_COMMON_CONSTANT 7 (None) +2:37-2:47 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 4>: +4:0-4:0 RESUME 0 + +5:11-5:15 LOAD_GLOBAL 1 (dict + NULL) +5:15-5:47 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:15-5:47 MAKE_FUNCTION +5:37-5:38 LOAD_FAST_BORROW 0 (d) +5:37-5:44 LOAD_ATTR 3 (items + NULL|self) +5:37-5:46 CALL 0 +5:15-5:47 CALL 0 +5:11-5:47 CALL 1 +5:4-5:47 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 5>: + -- RESUME 4 + +5:37-5:46 LOAD_FAST 0 (.0) +5:37-5:46 GET_ITER 0 +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 +5:37-5:46 L2: FOR_ITER 11 (to L3) +5:27-5:33 UNPACK_SEQUENCE 2 +5:28-5:29 STORE_FAST_STORE_FAST 18 (k, v) +5:17-5:18 LOAD_FAST_BORROW_LOAD_FAST_BORROW 33 (v, k) +5:16-5:22 BUILD_TUPLE 2 +5:16-5:22 YIELD_VALUE 0 +5:16-5:22 RESUME 9 +5:16-5:22 POP_TOP +5:16-5:22 JUMP_BACKWARD 13 (to L2) +5:37-5:46 L3: END_FOR +5:37-5:46 POP_ITER +5:37-5:46 LOAD_COMMON_CONSTANT 7 (None) +5:37-5:46 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 7>: +7:0-7:0 RESUME 0 + +8:11-8:15 LOAD_GLOBAL 0 (list) +8:11-8:15 COPY 1 +8:11-8:15 LOAD_COMMON_CONSTANT 5 (list) +8:11-8:15 IS_OP 0 (is) +8:11-8:15 POP_JUMP_IF_FALSE 19 (to L3) +8:11-8:15 NOT_TAKEN +8:11-8:15 POP_TOP +8:11-8:15 BUILD_LIST 0 +8:15-8:38 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 8>) +8:15-8:38 MAKE_FUNCTION +8:35-8:37 LOAD_FAST_BORROW 0 (xs) +8:15-8:38 CALL 0 +8:11-8:15 PUSH_NULL +8:11-8:15 L1: FOR_ITER 3 (to L2) +8:11-8:15 LIST_APPEND 3 +8:11-8:15 JUMP_BACKWARD 5 (to L1) +8:11-8:15 L2: END_FOR +8:11-8:15 POP_ITER +8:4-8:38 RETURN_VALUE +8:11-8:15 L3: PUSH_NULL +8:15-8:38 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 8>) +8:15-8:38 MAKE_FUNCTION +8:35-8:37 LOAD_FAST_BORROW 0 (xs) +8:15-8:38 CALL 0 +8:11-8:38 CALL 1 +8:4-8:38 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 8>: + -- RESUME 4 + +8:35-8:37 LOAD_FAST 0 (.0) +8:35-8:37 GET_ITER 0 +8:?-8:? RETURN_GENERATOR +8:?-8:? POP_TOP +8:0-8:0 L1: RESUME 0 +8:35-8:37 L2: FOR_ITER 11 (to L3) +8:27-8:31 UNPACK_SEQUENCE 2 +8:27-8:28 STORE_FAST_STORE_FAST 18 (x, y) +8:17-8:18 LOAD_FAST_BORROW_LOAD_FAST_BORROW 18 (x, y) +8:16-8:22 BUILD_TUPLE 2 +8:16-8:22 YIELD_VALUE 0 +8:16-8:22 RESUME 9 +8:16-8:22 POP_TOP +8:16-8:22 JUMP_BACKWARD 13 (to L2) +8:35-8:37 L3: END_FOR +8:35-8:37 POP_ITER +8:35-8:37 LOAD_COMMON_CONSTANT 7 (None) +8:35-8:37 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 10>: +10:0-10:0 RESUME 0 + +11:11-11:15 LOAD_GLOBAL 0 (list) +11:11-11:15 COPY 1 +11:11-11:15 LOAD_COMMON_CONSTANT 5 (list) +11:11-11:15 IS_OP 0 (is) +11:11-11:15 POP_JUMP_IF_FALSE 19 (to L3) +11:11-11:15 NOT_TAKEN +11:11-11:15 POP_TOP +11:11-11:15 BUILD_LIST 0 +11:16-11:39 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 11>) +11:16-11:39 MAKE_FUNCTION +11:36-11:38 LOAD_FAST_BORROW 0 (xs) +11:16-11:39 CALL 0 +11:11-11:15 PUSH_NULL +11:11-11:15 L1: FOR_ITER 3 (to L2) +11:11-11:15 LIST_APPEND 3 +11:11-11:15 JUMP_BACKWARD 5 (to L1) +11:11-11:15 L2: END_FOR +11:11-11:15 POP_ITER +11:4-11:40 RETURN_VALUE +11:11-11:15 L3: PUSH_NULL +11:16-11:39 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 11>) +11:16-11:39 MAKE_FUNCTION +11:36-11:38 LOAD_FAST_BORROW 0 (xs) +11:16-11:39 CALL 0 +11:11-11:40 CALL 1 +11:4-11:40 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 11>: + -- RESUME 4 + +11:36-11:38 LOAD_FAST 0 (.0) +11:36-11:38 GET_ITER 0 +11:?-11:? RETURN_GENERATOR +11:?-11:? POP_TOP +11:0-11:0 L1: RESUME 0 +11:36-11:38 L2: FOR_ITER 11 (to L3) +11:28-11:32 UNPACK_SEQUENCE 2 +11:28-11:29 STORE_FAST_STORE_FAST 18 (x, y) +11:18-11:19 LOAD_FAST_BORROW_LOAD_FAST_BORROW 18 (x, y) +11:17-11:23 BUILD_TUPLE 2 +11:17-11:23 YIELD_VALUE 0 +11:17-11:23 RESUME 9 +11:17-11:23 POP_TOP +11:17-11:23 JUMP_BACKWARD 13 (to L2) +11:36-11:38 L3: END_FOR +11:36-11:38 POP_ITER +11:36-11:38 LOAD_COMMON_CONSTANT 7 (None) +11:36-11:38 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_range.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_range.snap new file mode 100644 index 00000000000..bc11da5dc0a --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__implicit_call_genexpr_range.snap @@ -0,0 +1,131 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef implicit():\n return list(x for x in range(10))\n\ndef explicit():\n return list((x for x in range(10)))\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:37 LOAD_CONST 0 (", line 1>) +1:0-2:37 MAKE_FUNCTION +1:0-2:37 STORE_NAME 0 (implicit) + +4:0-5:39 LOAD_CONST 1 (", line 4>) +4:0-5:39 MAKE_FUNCTION +4:0-5:39 STORE_NAME 1 (explicit) +4:0-5:39 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:39 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:15 LOAD_GLOBAL 0 (list) +2:11-2:15 COPY 1 +2:11-2:15 LOAD_COMMON_CONSTANT 5 (list) +2:11-2:15 IS_OP 0 (is) +2:11-2:15 POP_JUMP_IF_FALSE 28 (to L3) +2:11-2:15 NOT_TAKEN +2:11-2:15 POP_TOP +2:11-2:15 BUILD_LIST 0 +2:15-2:37 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:15-2:37 MAKE_FUNCTION +2:27-2:32 LOAD_GLOBAL 3 (range + NULL) +2:33-2:35 LOAD_SMALL_INT 10 +2:27-2:36 CALL 1 +2:15-2:37 CALL 0 +2:11-2:15 PUSH_NULL +2:11-2:15 L1: FOR_ITER 3 (to L2) +2:11-2:15 LIST_APPEND 3 +2:11-2:15 JUMP_BACKWARD 5 (to L1) +2:11-2:15 L2: END_FOR +2:11-2:15 POP_ITER +2:4-2:37 RETURN_VALUE +2:11-2:15 L3: PUSH_NULL +2:15-2:37 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:15-2:37 MAKE_FUNCTION +2:27-2:32 LOAD_GLOBAL 3 (range + NULL) +2:33-2:35 LOAD_SMALL_INT 10 +2:27-2:36 CALL 1 +2:15-2:37 CALL 0 +2:11-2:37 CALL 1 +2:4-2:37 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:27-2:36 LOAD_FAST 0 (.0) +2:27-2:36 GET_ITER 0 +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:27-2:36 L2: FOR_ITER 7 (to L3) +2:22-2:23 STORE_FAST_LOAD_FAST 17 (x, x) +2:16-2:17 YIELD_VALUE 0 +2:16-2:17 RESUME 9 +2:16-2:17 POP_TOP +2:16-2:17 JUMP_BACKWARD 9 (to L2) +2:27-2:36 L3: END_FOR +2:27-2:36 POP_ITER +2:27-2:36 LOAD_COMMON_CONSTANT 7 (None) +2:27-2:36 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti + +Disassembly of ", line 4>: +4:0-4:0 RESUME 0 + +5:11-5:15 LOAD_GLOBAL 0 (list) +5:11-5:15 COPY 1 +5:11-5:15 LOAD_COMMON_CONSTANT 5 (list) +5:11-5:15 IS_OP 0 (is) +5:11-5:15 POP_JUMP_IF_FALSE 28 (to L3) +5:11-5:15 NOT_TAKEN +5:11-5:15 POP_TOP +5:11-5:15 BUILD_LIST 0 +5:16-5:38 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:16-5:38 MAKE_FUNCTION +5:28-5:33 LOAD_GLOBAL 3 (range + NULL) +5:34-5:36 LOAD_SMALL_INT 10 +5:28-5:37 CALL 1 +5:16-5:38 CALL 0 +5:11-5:15 PUSH_NULL +5:11-5:15 L1: FOR_ITER 3 (to L2) +5:11-5:15 LIST_APPEND 3 +5:11-5:15 JUMP_BACKWARD 5 (to L1) +5:11-5:15 L2: END_FOR +5:11-5:15 POP_ITER +5:4-5:39 RETURN_VALUE +5:11-5:15 L3: PUSH_NULL +5:16-5:38 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:16-5:38 MAKE_FUNCTION +5:28-5:33 LOAD_GLOBAL 3 (range + NULL) +5:34-5:36 LOAD_SMALL_INT 10 +5:28-5:37 CALL 1 +5:16-5:38 CALL 0 +5:11-5:39 CALL 1 +5:4-5:39 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 5>: + -- RESUME 4 + +5:28-5:37 LOAD_FAST 0 (.0) +5:28-5:37 GET_ITER 0 +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 +5:28-5:37 L2: FOR_ITER 7 (to L3) +5:23-5:24 STORE_FAST_LOAD_FAST 17 (x, x) +5:17-5:18 YIELD_VALUE 0 +5:17-5:18 RESUME 9 +5:17-5:18 POP_TOP +5:17-5:18 JUMP_BACKWARD 9 (to L2) +5:28-5:37 L3: END_FOR +5:28-5:37 POP_ITER +5:28-5:37 LOAD_COMMON_CONSTANT 7 (None) +5:28-5:37 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [2] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__import_flags.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__import_flags.snap new file mode 100644 index 00000000000..d26a61edca5 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__import_flags.snap @@ -0,0 +1,193 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nimport sample\nfrom sample import member\nfrom sample import *\n\ndef f():\n import sample\n\nclass C:\n from sample import member\n\ntry:\n import sample\nexcept Exception:\n pass\n\ntry:\n operation()\nexcept Exception:\n import sample\n\ntry:\n operation()\nfinally:\n import sample\n\nwith manager:\n import sample\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-1:13 LOAD_SMALL_INT 0 +1:0-1:13 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:13 IMPORT_NAME 0 (sample) +1:0-1:13 STORE_NAME 0 (sample) + +2:0-2:25 LOAD_SMALL_INT 0 +2:0-2:25 LOAD_CONST 1 (('member',)) +2:0-2:25 IMPORT_NAME 0 (sample) +2:0-2:25 IMPORT_FROM 1 (member) +2:0-2:25 STORE_NAME 1 (member) +2:0-2:25 POP_TOP + +3:0-3:20 LOAD_SMALL_INT 0 +3:0-3:20 LOAD_CONST 2 (('*',)) +3:0-3:20 IMPORT_NAME 2 (sample + eager) +3:0-3:20 CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) +3:0-3:20 POP_TOP + +5:0-6:17 LOAD_CONST 3 (", line 5>) +5:0-6:17 MAKE_FUNCTION +5:0-6:17 STORE_NAME 2 (f) + +8:0-9:29 LOAD_BUILD_CLASS +8:0-9:29 PUSH_NULL +8:0-9:29 LOAD_CONST 4 (", line 8>) +8:0-9:29 MAKE_FUNCTION +8:0-9:29 LOAD_CONST 5 ('C') +8:0-9:29 CALL 2 +8:0-9:29 STORE_NAME 3 (C) + +11:0-14:8 NOP + +12:4-12:17 L1: LOAD_SMALL_INT 0 +12:4-12:17 LOAD_COMMON_CONSTANT 7 (None) +12:4-12:17 IMPORT_NAME 2 (sample + eager) +12:4-12:17 STORE_NAME 0 (sample) + +16:0-19:17 L2: NOP + +17:4-17:13 L3: LOAD_NAME 5 (operation) +17:4-17:13 PUSH_NULL +17:4-17:15 CALL 0 +17:4-17:15 POP_TOP + +21:0-24:17 L4: NOP + +22:4-22:13 L5: LOAD_NAME 5 (operation) +22:4-22:13 PUSH_NULL +22:4-22:15 CALL 0 +22:4-22:15 POP_TOP + +24:4-24:17 L6: LOAD_SMALL_INT 0 +24:4-24:17 LOAD_COMMON_CONSTANT 7 (None) +24:4-24:17 IMPORT_NAME 0 (sample) +24:4-24:17 STORE_NAME 0 (sample) + +26:5-26:12 LOAD_NAME 6 (manager) +26:5-26:12 COPY 1 +26:5-26:12 LOAD_SPECIAL 1 (__exit__) +26:5-26:12 SWAP 2 +26:5-26:12 SWAP 3 +26:5-26:12 LOAD_SPECIAL 0 (__enter__) +26:5-26:12 CALL 0 +26:5-26:12 L7: POP_TOP + +27:4-27:17 LOAD_SMALL_INT 0 +27:4-27:17 LOAD_COMMON_CONSTANT 7 (None) +27:4-27:17 IMPORT_NAME 0 (sample) +27:4-27:17 STORE_NAME 0 (sample) + +26:5-26:12 L8: LOAD_COMMON_CONSTANT 7 (None) +26:5-26:12 LOAD_COMMON_CONSTANT 7 (None) +26:5-26:12 LOAD_COMMON_CONSTANT 7 (None) +26:5-26:12 CALL 3 +26:5-26:12 POP_TOP +26:5-26:12 LOAD_COMMON_CONSTANT 7 (None) +26:5-26:12 RETURN_VALUE + + -- L9: PUSH_EXC_INFO + +13:7-13:16 LOAD_NAME 4 (Exception) +13:0-14:8 CHECK_EXC_MATCH +13:0-14:8 POP_JUMP_IF_FALSE 4 (to L13) +13:0-14:8 L10: NOT_TAKEN +13:0-14:8 L11: POP_TOP + +14:4-14:8 L12: POP_EXCEPT +14:4-14:8 JUMP_BACKWARD_NO_INTERRUPT 54 (to L2) + +13:0-14:8 L13: RERAISE 0 + + -- L14: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 + -- L15: PUSH_EXC_INFO + +18:7-18:16 LOAD_NAME 4 (Exception) +18:0-19:17 CHECK_EXC_MATCH +18:0-19:17 POP_JUMP_IF_FALSE 8 (to L19) +18:0-19:17 L16: NOT_TAKEN +18:0-19:17 L17: POP_TOP + +19:4-19:17 LOAD_SMALL_INT 0 +19:4-19:17 LOAD_COMMON_CONSTANT 7 (None) +19:4-19:17 IMPORT_NAME 2 (sample + eager) +19:4-19:17 STORE_NAME 0 (sample) +19:4-19:17 L18: POP_EXCEPT +19:4-19:17 JUMP_BACKWARD_NO_INTERRUPT 63 (to L4) + +18:0-19:17 L19: RERAISE 0 + + -- L20: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 + -- L21: PUSH_EXC_INFO + +24:4-24:17 LOAD_SMALL_INT 0 +24:4-24:17 LOAD_COMMON_CONSTANT 7 (None) +24:4-24:17 IMPORT_NAME 2 (sample + eager) +24:4-24:17 STORE_NAME 0 (sample) +24:4-24:17 RERAISE 0 + + -- L22: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 + +26:5-26:12 L23: PUSH_EXC_INFO +26:5-26:12 WITH_EXCEPT_START +26:5-26:12 TO_BOOL +26:5-26:12 POP_JUMP_IF_TRUE 2 (to L26) +26:5-26:12 L24: NOT_TAKEN +26:5-26:12 L25: RERAISE 2 +26:5-26:12 L26: POP_TOP +26:5-26:12 L27: POP_EXCEPT +26:5-26:12 POP_TOP +26:5-26:12 POP_TOP +26:5-26:12 POP_TOP +26:5-26:12 LOAD_COMMON_CONSTANT 7 (None) +26:5-26:12 RETURN_VALUE + + -- L28: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L9 [0] + L3 to L4 -> L15 [0] + L5 to L6 -> L21 [0] + L7 to L8 -> L23 [2] lasti + L9 to L10 -> L14 [1] lasti + L11 to L12 -> L14 [1] lasti + L13 to L14 -> L14 [1] lasti + L15 to L16 -> L20 [1] lasti + L17 to L18 -> L20 [1] lasti + L19 to L20 -> L20 [1] lasti + L21 to L22 -> L22 [1] lasti + L23 to L24 -> L28 [4] lasti + L25 to L27 -> L28 [4] lasti + +Disassembly of ", line 5>: +5:0-5:0 RESUME 0 + +6:4-6:17 LOAD_SMALL_INT 0 +6:4-6:17 LOAD_COMMON_CONSTANT 7 (None) +6:4-6:17 IMPORT_NAME 2 (sample + eager) +6:4-6:17 STORE_FAST 0 (sample) +6:4-6:17 LOAD_COMMON_CONSTANT 7 (None) +6:4-6:17 RETURN_VALUE + +Disassembly of ", line 8>: +8:0-8:0 RESUME 0 +8:0-8:0 LOAD_NAME 0 (__name__) +8:0-8:0 STORE_NAME 1 (__module__) +8:0-8:0 LOAD_CONST 0 ('C') +8:0-8:0 STORE_NAME 2 (__qualname__) +8:0-8:0 LOAD_SMALL_INT 8 +8:0-8:0 STORE_NAME 3 (__firstlineno__) + +9:4-9:29 LOAD_SMALL_INT 0 +9:4-9:29 LOAD_CONST 1 (('member',)) +9:4-9:29 IMPORT_NAME 18 (sample + eager) +9:4-9:29 IMPORT_FROM 5 (member) +9:4-9:29 STORE_NAME 5 (member) +9:4-9:29 POP_TOP +9:4-9:29 LOAD_CONST 2 (()) +9:4-9:29 STORE_NAME 6 (__static_attributes__) +9:4-9:29 LOAD_COMMON_CONSTANT 7 (None) +9:4-9:29 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_async_comprehension_end_async_for_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_async_comprehension_end_async_for_location.snap new file mode 100644 index 00000000000..0e996da0912 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_async_comprehension_end_async_for_location.snap @@ -0,0 +1,97 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nasync def f(it):\n for i in it:\n yield i\n\nasync def run_list():\n return [i + 1 async for i in f([10, 20])]\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-3:15 LOAD_CONST 0 (", line 1>) +1:0-3:15 MAKE_FUNCTION +1:0-3:15 STORE_NAME 0 (f) + +5:0-6:45 LOAD_CONST 1 (", line 5>) +5:0-6:45 MAKE_FUNCTION +5:0-6:45 STORE_NAME 1 (run_list) +5:0-6:45 LOAD_COMMON_CONSTANT 7 (None) +5:0-6:45 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:13-2:15 LOAD_FAST 0 (it) +2:13-2:15 GET_ITER 0 +2:13-2:15 L2: FOR_ITER 9 (to L3) +2:8-2:9 STORE_FAST 1 (i) + +3:14-3:15 LOAD_FAST_BORROW 1 (i) +3:8-3:15 CALL_INTRINSIC_1 4 (INTRINSIC_ASYNC_GEN_WRAP) +3:8-3:15 YIELD_VALUE 0 +3:8-3:15 RESUME 9 +3:8-3:15 POP_TOP +3:8-3:15 JUMP_BACKWARD 11 (to L2) + +2:13-2:15 L3: END_FOR +2:13-2:15 POP_ITER +2:13-2:15 LOAD_COMMON_CONSTANT 7 (None) +2:13-2:15 RETURN_VALUE + + -- L4: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L4 -> L4 [0] lasti + +Disassembly of ", line 5>: +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 + +6:33-6:34 LOAD_GLOBAL 1 (f + NULL) +6:36-6:38 LOAD_SMALL_INT 10 +6:40-6:42 LOAD_SMALL_INT 20 +6:35-6:43 BUILD_LIST 2 +6:33-6:44 CALL 1 +6:11-6:45 LOAD_FAST_AND_CLEAR 0 (i) +6:11-6:45 SWAP 2 +6:11-6:45 L2: BUILD_LIST 0 +6:11-6:45 SWAP 2 +6:33-6:44 GET_AITER +6:11-6:45 L3: GET_ANEXT +6:11-6:45 PUSH_NULL +6:11-6:45 LOAD_COMMON_CONSTANT 7 (None) +6:11-6:45 L4: SEND 4 (to L7) +6:11-6:45 L5: YIELD_VALUE 1 +6:11-6:45 L6: RESUME 3 +6:11-6:45 JUMP_BACKWARD_NO_INTERRUPT 6 (to L4) +6:11-6:45 L7: END_SEND +6:28-6:29 L8: STORE_FAST_LOAD_FAST 0 (i, i) +6:16-6:17 LOAD_SMALL_INT 1 +6:12-6:17 BINARY_OP 0 (+) +6:12-6:17 LIST_APPEND 2 +6:12-6:17 JUMP_BACKWARD 21 (to L3) +6:11-6:45 L9: CLEANUP_THROW +6:11-6:45 L10: JUMP_BACKWARD_NO_INTERRUPT 14 (to L7) +6:11-6:45 L11: END_ASYNC_FOR 15 (from L7) +6:11-6:45 L12: SWAP 2 +6:11-6:45 STORE_FAST 0 (i) +6:4-6:45 RETURN_VALUE + + -- L13: SWAP 2 + -- POP_TOP + +6:11-6:45 SWAP 2 +6:11-6:45 STORE_FAST 0 (i) +6:11-6:45 RERAISE 0 + + -- L14: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L14 [0] lasti + L2 to L3 -> L13 [2] + L3 to L5 -> L11 [3] + L5 to L6 -> L9 [6] + L6 to L8 -> L11 [3] + L8 to L9 -> L13 [2] + L9 to L10 -> L11 [3] + L11 to L12 -> L13 [2] + L12 to L14 -> L14 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_comprehension_backedge_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_comprehension_backedge_location.snap new file mode 100644 index 00000000000..9ab39805cc9 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__inlined_comprehension_backedge_location.snap @@ -0,0 +1,92 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nasync def f(i):\n return i\n\nasync def run_list():\n return [s for c in [f(''), f('abc')] for s in await c]\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:12 LOAD_CONST 0 (", line 1>) +1:0-2:12 MAKE_FUNCTION +1:0-2:12 STORE_NAME 0 (f) + +4:0-5:58 LOAD_CONST 1 (", line 4>) +4:0-5:58 MAKE_FUNCTION +4:0-5:58 STORE_NAME 1 (run_list) +4:0-5:58 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:58 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:11-2:12 LOAD_FAST_BORROW 0 (i) +2:4-2:12 RETURN_VALUE + + -- L2: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [0] lasti + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +5:24-5:25 LOAD_GLOBAL 1 (f + NULL) +5:26-5:28 LOAD_COMMON_CONSTANT 8 ('') +5:24-5:29 CALL 1 +5:31-5:32 LOAD_GLOBAL 1 (f + NULL) +5:33-5:38 LOAD_CONST 1 ('abc') +5:31-5:39 CALL 1 +5:23-5:40 BUILD_LIST 2 +5:11-5:58 LOAD_FAST_AND_CLEAR 0 (c) +5:11-5:58 LOAD_FAST_AND_CLEAR 1 (s) +5:11-5:58 SWAP 3 +5:11-5:58 L2: BUILD_LIST 0 +5:11-5:58 SWAP 2 +5:23-5:40 GET_ITER 0 +5:23-5:40 L3: FOR_ITER 23 (to L10) +5:18-5:19 STORE_FAST_LOAD_FAST 0 (c, c) +5:50-5:57 GET_AWAITABLE 0 +5:50-5:57 PUSH_NULL +5:50-5:57 LOAD_COMMON_CONSTANT 7 (None) +5:50-5:57 L4: SEND 4 (to L7) +5:50-5:57 L5: YIELD_VALUE 1 +5:50-5:57 L6: RESUME 3 +5:50-5:57 JUMP_BACKWARD_NO_INTERRUPT 6 (to L4) +5:50-5:57 L7: END_SEND +5:50-5:57 GET_ITER 0 +5:50-5:57 L8: FOR_ITER 4 (to L9) +5:45-5:46 STORE_FAST_LOAD_FAST 17 (s, s) +5:12-5:13 LIST_APPEND 5 +5:12-5:13 JUMP_BACKWARD 6 (to L8) +5:50-5:57 L9: END_FOR +5:50-5:57 POP_ITER +5:12-5:13 JUMP_BACKWARD 25 (to L3) +5:23-5:40 L10: END_FOR +5:23-5:40 POP_ITER +5:11-5:58 L11: SWAP 3 +5:11-5:58 STORE_FAST 1 (s) +5:11-5:58 STORE_FAST 0 (c) +5:4-5:58 RETURN_VALUE +5:50-5:57 L12: CLEANUP_THROW +5:50-5:57 L13: JUMP_BACKWARD_NO_INTERRUPT 21 (to L7) + + -- L14: SWAP 2 + -- POP_TOP + +5:11-5:58 SWAP 3 +5:11-5:58 STORE_FAST 1 (s) +5:11-5:58 STORE_FAST 0 (c) +5:11-5:58 RERAISE 0 + + -- L15: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L15 [0] lasti + L2 to L5 -> L14 [3] + L5 to L6 -> L12 [8] + L6 to L11 -> L14 [3] + L11 to L12 -> L15 [0] lasti + L12 to L13 -> L14 [3] + L14 to L15 -> L15 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__joined_string_literals_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__joined_string_literals_location.snap new file mode 100644 index 00000000000..69582f4f61f --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__joined_string_literals_location.snap @@ -0,0 +1,24 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(a):\n return (\n 'x'\n f'y{a}z'\n 'w'\n )\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-6:5 LOAD_CONST 0 (", line 1>) +1:0-6:5 MAKE_FUNCTION +1:0-6:5 STORE_NAME 0 (f) +1:0-6:5 LOAD_COMMON_CONSTANT 7 (None) +1:0-6:5 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +3:8-4:11 LOAD_CONST 0 ('xy') + +4:12-4:13 LOAD_FAST_BORROW 0 (a) +4:11-4:14 FORMAT_SIMPLE +4:14-5:11 LOAD_CONST 1 ('zw') + +3:8-5:11 BUILD_STRING 3 + +2:4-6:5 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__lambda_dict_literal_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__lambda_dict_literal_location.snap new file mode 100644 index 00000000000..11e587c10b0 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__lambda_dict_literal_location.snap @@ -0,0 +1,29 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nf = lambda data: {'x': data}\ng = lambda i: {**i}\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:4-1:28 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 1>) +1:4-1:28 MAKE_FUNCTION +1:0-1:1 STORE_NAME 0 (f) + +2:4-2:19 LOAD_CONST 1 ( at 0xdeadbeef, file "", line 2>) +2:4-2:19 MAKE_FUNCTION +2:0-2:1 STORE_NAME 1 (g) +2:0-2:1 LOAD_COMMON_CONSTANT 7 (None) +2:0-2:1 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 1>: +1:0-1:0 RESUME 0 +1:18-1:21 LOAD_CONST 0 ('x') +1:23-1:27 LOAD_FAST_BORROW 0 (data) +1:17-1:28 BUILD_MAP 1 +1:17-1:28 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 2>: +2:0-2:0 RESUME 0 +2:14-2:19 BUILD_MAP 0 +2:17-2:18 LOAD_FAST_BORROW 0 (i) +2:14-2:19 DICT_UPDATE 1 +2:14-2:19 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__list_set_async_genexpr_calls.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__list_set_async_genexpr_calls.snap new file mode 100644 index 00000000000..68e944983c8 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__list_set_async_genexpr_calls.snap @@ -0,0 +1,129 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nasync def f(xs):\n return list(x async for x in xs)\n\nasync def g(xs):\n return set(x async for x in xs)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:36 LOAD_CONST 0 (", line 1>) +1:0-2:36 MAKE_FUNCTION +1:0-2:36 STORE_NAME 0 (f) + +4:0-5:35 LOAD_CONST 1 (", line 4>) +4:0-5:35 MAKE_FUNCTION +4:0-5:35 STORE_NAME 1 (g) +4:0-5:35 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:35 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:11-2:15 LOAD_GLOBAL 1 (list + NULL) +2:15-2:36 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 2>) +2:15-2:36 MAKE_FUNCTION +2:33-2:35 LOAD_FAST_BORROW 0 (xs) +2:15-2:36 CALL 0 +2:11-2:36 CALL 1 +2:4-2:36 RETURN_VALUE + + -- L2: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [0] lasti + +Disassembly of at 0xdeadbeef, file "", line 2>: + -- RESUME 4 + +2:33-2:35 LOAD_FAST_BORROW 0 (.0) +2:33-2:35 GET_AITER +2:?-2:? RETURN_GENERATOR +2:?-2:? POP_TOP +2:0-2:0 L1: RESUME 0 +2:15-2:36 L2: GET_ANEXT +2:15-2:36 PUSH_NULL +2:15-2:36 LOAD_COMMON_CONSTANT 7 (None) +2:15-2:36 L3: SEND 4 (to L6) +2:15-2:36 L4: YIELD_VALUE 1 +2:15-2:36 L5: RESUME 3 +2:15-2:36 JUMP_BACKWARD_NO_INTERRUPT 6 (to L3) +2:15-2:36 L6: END_SEND +2:28-2:29 L7: STORE_FAST_LOAD_FAST 17 (x, x) +2:16-2:17 CALL_INTRINSIC_1 4 (INTRINSIC_ASYNC_GEN_WRAP) +2:16-2:17 YIELD_VALUE 0 +2:16-2:17 RESUME 9 +2:16-2:17 POP_TOP +2:16-2:17 JUMP_BACKWARD 18 (to L2) +2:15-2:36 L8: CLEANUP_THROW +2:15-2:36 L9: JUMP_BACKWARD_NO_INTERRUPT 11 (to L6) +2:15-2:36 L10: END_ASYNC_FOR 12 (from L6) +2:15-2:36 LOAD_COMMON_CONSTANT 7 (None) +2:15-2:36 RETURN_VALUE + + -- L11: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L11 [1] lasti + L2 to L4 -> L10 [1] + L4 to L5 -> L8 [4] + L5 to L7 -> L10 [1] + L7 to L8 -> L11 [1] lasti + L8 to L9 -> L10 [1] + L10 to L11 -> L11 [1] lasti + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +5:11-5:14 LOAD_GLOBAL 1 (set + NULL) +5:14-5:35 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 5>) +5:14-5:35 MAKE_FUNCTION +5:32-5:34 LOAD_FAST_BORROW 0 (xs) +5:14-5:35 CALL 0 +5:11-5:35 CALL 1 +5:4-5:35 RETURN_VALUE + + -- L2: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L2 [0] lasti + +Disassembly of at 0xdeadbeef, file "", line 5>: + -- RESUME 4 + +5:32-5:34 LOAD_FAST_BORROW 0 (.0) +5:32-5:34 GET_AITER +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 +5:14-5:35 L2: GET_ANEXT +5:14-5:35 PUSH_NULL +5:14-5:35 LOAD_COMMON_CONSTANT 7 (None) +5:14-5:35 L3: SEND 4 (to L6) +5:14-5:35 L4: YIELD_VALUE 1 +5:14-5:35 L5: RESUME 3 +5:14-5:35 JUMP_BACKWARD_NO_INTERRUPT 6 (to L3) +5:14-5:35 L6: END_SEND +5:27-5:28 L7: STORE_FAST_LOAD_FAST 17 (x, x) +5:15-5:16 CALL_INTRINSIC_1 4 (INTRINSIC_ASYNC_GEN_WRAP) +5:15-5:16 YIELD_VALUE 0 +5:15-5:16 RESUME 9 +5:15-5:16 POP_TOP +5:15-5:16 JUMP_BACKWARD 18 (to L2) +5:14-5:35 L8: CLEANUP_THROW +5:14-5:35 L9: JUMP_BACKWARD_NO_INTERRUPT 11 (to L6) +5:14-5:35 L10: END_ASYNC_FOR 12 (from L6) +5:14-5:35 LOAD_COMMON_CONSTANT 7 (None) +5:14-5:35 RETURN_VALUE + + -- L11: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L11 [1] lasti + L2 to L4 -> L10 [1] + L4 to L5 -> L8 [4] + L5 to L7 -> L10 [1] + L7 to L8 -> L11 [1] lasti + L8 to L9 -> L10 [1] + L10 to L11 -> L11 [1] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__loop_return_cleanup.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__loop_return_cleanup.snap new file mode 100644 index 00000000000..098a84a976b --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__loop_return_cleanup.snap @@ -0,0 +1,74 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(xs):\n for x in xs:\n return x\n\nasync def g(xs):\n async for x in xs:\n return x\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-3:16 LOAD_CONST 0 (", line 1>) +1:0-3:16 MAKE_FUNCTION +1:0-3:16 STORE_NAME 0 (f) + +5:0-7:16 LOAD_CONST 1 (", line 5>) +5:0-7:16 MAKE_FUNCTION +5:0-7:16 STORE_NAME 1 (g) +5:0-7:16 LOAD_COMMON_CONSTANT 7 (None) +5:0-7:16 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:13-2:15 LOAD_FAST 0 (xs) +2:13-2:15 GET_ITER 0 +2:13-2:15 FOR_ITER 6 (to L1) +2:8-2:9 STORE_FAST 1 (x) + +3:15-3:16 LOAD_FAST_BORROW 1 (x) +3:8-3:16 SWAP 3 +3:8-3:16 POP_TOP +3:8-3:16 POP_TOP +3:8-3:16 RETURN_VALUE + +2:13-2:15 L1: END_FOR +2:13-2:15 POP_ITER +2:13-2:15 LOAD_COMMON_CONSTANT 7 (None) +2:13-2:15 RETURN_VALUE + +Disassembly of ", line 5>: +5:?-5:? RETURN_GENERATOR +5:?-5:? POP_TOP +5:0-5:0 L1: RESUME 0 + +6:19-6:21 LOAD_FAST_BORROW 0 (xs) +6:19-6:21 GET_AITER +6:4-7:16 L2: GET_ANEXT +6:4-7:16 PUSH_NULL +6:4-7:16 LOAD_COMMON_CONSTANT 7 (None) +6:4-7:16 L3: SEND 4 (to L6) +6:4-7:16 L4: YIELD_VALUE 1 +6:4-7:16 L5: RESUME 3 +6:4-7:16 JUMP_BACKWARD_NO_INTERRUPT 6 (to L3) +6:4-7:16 L6: END_SEND +6:4-7:16 L7: NOT_TAKEN +6:14-6:15 STORE_FAST 1 (x) + +7:15-7:16 LOAD_FAST_BORROW 1 (x) +7:8-7:16 SWAP 2 +7:8-7:16 POP_TOP +7:8-7:16 RETURN_VALUE + +6:4-7:16 L8: CLEANUP_THROW +6:4-7:16 L9: JUMP_BACKWARD_NO_INTERRUPT 9 (to L6) +6:19-6:21 L10: END_ASYNC_FOR 10 (from L6) +6:19-6:21 LOAD_COMMON_CONSTANT 7 (None) +6:19-6:21 RETURN_VALUE + + -- L11: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L11 [0] lasti + L2 to L4 -> L10 [1] + L4 to L5 -> L8 [4] + L5 to L7 -> L10 [1] + L7 to L8 -> L11 [0] lasti + L8 to L9 -> L10 [1] + L10 to L11 -> L11 [0] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_class_scaffolding_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_class_scaffolding_location.snap new file mode 100644 index 00000000000..ba6e6a937c3 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_class_scaffolding_location.snap @@ -0,0 +1,39 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(x):\n match x:\n case bool(z):\n y = 0\n return y, z\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-5:15 LOAD_CONST 0 (", line 1>) +1:0-5:15 MAKE_FUNCTION +1:0-5:15 STORE_NAME 0 (f) +1:0-5:15 LOAD_COMMON_CONSTANT 7 (None) +1:0-5:15 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:10-2:11 LOAD_FAST_BORROW 0 (x) + +3:13-3:17 LOAD_GLOBAL 0 (bool) +3:13-3:20 LOAD_CONST 0 (()) +3:13-3:20 MATCH_CLASS 1 +3:13-3:20 COPY 1 +3:13-3:20 POP_JUMP_IF_NONE 9 (to L1) +3:13-3:20 NOT_TAKEN +3:13-3:20 UNPACK_SEQUENCE 1 +3:13-3:20 STORE_FAST 1 (z) + +4:16-4:17 LOAD_SMALL_INT 0 +4:12-4:13 STORE_FAST 2 (y) + +5:11-5:12 LOAD_FAST_BORROW_LOAD_FAST_BORROW 33 (y, z) +5:11-5:15 BUILD_TUPLE 2 +5:4-5:15 RETURN_VALUE + +3:13-3:20 L1: POP_TOP + +5:11-5:12 LOAD_FAST_CHECK 2 (y) +5:14-5:15 LOAD_FAST_CHECK 1 (z) +5:11-5:15 BUILD_TUPLE 2 +5:4-5:15 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_mapping_keys_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_mapping_keys_location.snap new file mode 100644 index 00000000000..cd45a759947 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_mapping_keys_location.snap @@ -0,0 +1,60 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(self):\n x = {}\n y = None\n match x:\n case {0: 0}:\n y = 0\n self.assertIs(y, None)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-7:26 LOAD_CONST 0 (", line 1>) +1:0-7:26 MAKE_FUNCTION +1:0-7:26 STORE_NAME 0 (f) +1:0-7:26 LOAD_COMMON_CONSTANT 7 (None) +1:0-7:26 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:8-2:10 BUILD_MAP 0 +2:4-2:5 STORE_FAST 1 (x) + +3:8-3:12 LOAD_COMMON_CONSTANT 7 (None) +3:4-3:5 STORE_FAST 2 (y) + +4:10-4:11 LOAD_FAST 1 (x) + +5:13-5:19 MATCH_MAPPING +5:13-5:19 POP_JUMP_IF_FALSE 29 (to L3) +5:13-5:19 NOT_TAKEN +5:13-5:19 GET_LEN +5:13-5:19 LOAD_SMALL_INT 1 +5:13-5:19 COMPARE_OP 172 (>=) +5:13-5:19 POP_JUMP_IF_FALSE 22 (to L3) +5:13-5:19 NOT_TAKEN +5:13-5:19 LOAD_CONST 1 ((0,)) +5:13-5:19 MATCH_KEYS +5:13-5:19 COPY 1 +5:13-5:19 POP_JUMP_IF_NONE 14 (to L1) +5:13-5:19 NOT_TAKEN +5:13-5:19 UNPACK_SEQUENCE 1 +5:17-5:18 LOAD_SMALL_INT 0 +5:17-5:18 COMPARE_OP 88 (bool(==)) +5:17-5:18 POP_JUMP_IF_FALSE 7 (to L2) +5:17-5:18 NOT_TAKEN +5:13-5:19 POP_TOP +5:13-5:19 POP_TOP + +6:16-6:17 LOAD_SMALL_INT 0 +6:12-6:13 STORE_FAST 2 (y) +6:12-6:13 JUMP_FORWARD 3 (to L4) + +5:13-5:19 L1: POP_TOP +5:13-5:19 L2: POP_TOP +5:13-5:19 L3: POP_TOP + +7:4-7:8 L4: LOAD_FAST_BORROW 0 (self) +7:4-7:17 LOAD_ATTR 1 (assertIs + NULL|self) +7:18-7:19 LOAD_FAST_BORROW 2 (y) +7:21-7:25 LOAD_COMMON_CONSTANT 7 (None) +7:4-7:26 CALL 2 +7:4-7:26 POP_TOP +7:4-7:26 LOAD_COMMON_CONSTANT 7 (None) +7:4-7:26 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_or_alternative_locations.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_or_alternative_locations.snap new file mode 100644 index 00000000000..e0b03b2a2dd --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_or_alternative_locations.snap @@ -0,0 +1,56 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f():\n x = False\n match 0:\n case 0 | 1 | 2 | 3:\n x = True\n return x\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-6:12 LOAD_CONST 0 (", line 1>) +1:0-6:12 MAKE_FUNCTION +1:0-6:12 STORE_NAME 0 (f) +1:0-6:12 LOAD_COMMON_CONSTANT 7 (None) +1:0-6:12 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:8-2:13 LOAD_COMMON_CONSTANT 10 (False) +2:4-2:5 STORE_FAST 0 (x) + +3:10-3:11 LOAD_SMALL_INT 0 + +4:13-4:14 COPY 1 +4:13-4:14 LOAD_SMALL_INT 0 +4:13-4:14 COMPARE_OP 88 (bool(==)) +4:13-4:14 POP_JUMP_IF_FALSE 2 (to L1) +4:13-4:14 NOT_TAKEN +4:13-4:14 JUMP_FORWARD 27 (to L5) +4:17-4:18 L1: COPY 1 +4:17-4:18 LOAD_SMALL_INT 1 +4:17-4:18 COMPARE_OP 88 (bool(==)) +4:17-4:18 POP_JUMP_IF_FALSE 2 (to L2) +4:17-4:18 NOT_TAKEN +4:17-4:18 JUMP_FORWARD 19 (to L5) +4:21-4:22 L2: COPY 1 +4:21-4:22 LOAD_SMALL_INT 2 +4:21-4:22 COMPARE_OP 88 (bool(==)) +4:21-4:22 POP_JUMP_IF_FALSE 2 (to L3) +4:21-4:22 NOT_TAKEN +4:21-4:22 JUMP_FORWARD 11 (to L5) +4:25-4:26 L3: COPY 1 +4:25-4:26 LOAD_SMALL_INT 3 +4:25-4:26 COMPARE_OP 88 (bool(==)) +4:25-4:26 POP_JUMP_IF_FALSE 2 (to L4) +4:25-4:26 NOT_TAKEN +4:25-4:26 JUMP_FORWARD 3 (to L5) +4:13-4:26 L4: POP_TOP + +6:11-6:12 LOAD_FAST_BORROW 0 (x) +6:4-6:12 RETURN_VALUE + +4:13-4:26 L5: POP_TOP + +5:16-5:20 LOAD_COMMON_CONSTANT 9 (True) +5:12-5:13 STORE_FAST 0 (x) + +6:11-6:12 LOAD_FAST_BORROW 0 (x) +6:4-6:12 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_success_jump_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_success_jump_location.snap new file mode 100644 index 00000000000..b76ff06acd8 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__match_success_jump_location.snap @@ -0,0 +1,44 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(self):\n match 0:\n case 0:\n x = True\n case 0:\n x = False\n self.assertIs(x, True)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-7:26 LOAD_CONST 0 (", line 1>) +1:0-7:26 MAKE_FUNCTION +1:0-7:26 STORE_NAME 0 (f) +1:0-7:26 LOAD_COMMON_CONSTANT 7 (None) +1:0-7:26 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:10-2:11 LOAD_SMALL_INT 0 + +3:13-3:14 COPY 1 +3:13-3:14 LOAD_SMALL_INT 0 +3:13-3:14 COMPARE_OP 88 (bool(==)) +3:13-3:14 POP_JUMP_IF_FALSE 5 (to L1) +3:13-3:14 NOT_TAKEN + +4:16-4:20 POP_TOP +4:16-4:20 LOAD_COMMON_CONSTANT 9 (True) +4:12-4:13 STORE_FAST 1 (x) +4:12-4:13 JUMP_FORWARD 8 (to L2) + +5:13-5:14 L1: LOAD_SMALL_INT 0 +5:13-5:14 COMPARE_OP 88 (bool(==)) +5:13-5:14 POP_JUMP_IF_FALSE 3 (to L2) +5:13-5:14 NOT_TAKEN + +6:16-6:21 LOAD_COMMON_CONSTANT 10 (False) +6:12-6:13 STORE_FAST 1 (x) + +7:4-7:8 L2: LOAD_FAST_BORROW 0 (self) +7:4-7:17 LOAD_ATTR 1 (assertIs + NULL|self) +7:18-7:19 LOAD_FAST_CHECK 1 (x) +7:21-7:25 LOAD_COMMON_CONSTANT 9 (True) +7:4-7:26 CALL 2 +7:4-7:26 POP_TOP +7:4-7:26 LOAD_COMMON_CONSTANT 7 (None) +7:4-7:26 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_deferred_annotations_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_deferred_annotations_location.snap new file mode 100644 index 00000000000..c266a49f7ad --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_deferred_annotations_location.snap @@ -0,0 +1,63 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\nimport os\nX: int\nY: str\n\"#, true)" +--- + -- MAKE_CELL 0 (__conditional_annotations__) + +0:0-1:0 RESUME 0 + +1:0-1:9 LOAD_CONST 1 (", line 1>) +1:0-1:9 MAKE_FUNCTION +1:0-1:9 STORE_NAME 2 (__annotate__) +1:0-1:9 BUILD_SET 0 +1:0-1:9 STORE_NAME 0 (__conditional_annotations__) +1:0-1:9 LOAD_SMALL_INT 0 +1:0-1:9 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:9 IMPORT_NAME 4 (os) +1:0-1:9 STORE_NAME 1 (os) + +2:0-2:6 LOAD_NAME 0 (__conditional_annotations__) +2:0-2:6 LOAD_SMALL_INT 0 +2:0-2:6 SET_ADD 1 +2:0-2:6 POP_TOP + +3:0-3:6 LOAD_NAME 0 (__conditional_annotations__) +3:0-3:6 LOAD_SMALL_INT 1 +3:0-3:6 SET_ADD 1 +3:0-3:6 POP_TOP +3:0-3:6 LOAD_COMMON_CONSTANT 7 (None) +3:0-3:6 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:0-1:9 LOAD_FAST_BORROW 0 (format) +1:0-1:9 LOAD_SMALL_INT 2 +1:0-1:9 COMPARE_OP 132 (>) +1:0-1:9 POP_JUMP_IF_FALSE 3 (to L1) +1:0-1:9 NOT_TAKEN +1:0-1:9 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:0-1:9 RAISE_VARARGS 1 +1:0-1:9 L1: BUILD_MAP 0 + +2:0-2:6 LOAD_SMALL_INT 0 +2:0-2:6 LOAD_GLOBAL 0 (__conditional_annotations__) +2:0-2:6 CONTAINS_OP 0 (in) +2:0-2:6 POP_JUMP_IF_FALSE 10 (to L2) +2:0-2:6 NOT_TAKEN +2:3-2:6 LOAD_GLOBAL 2 (int) +2:0-2:6 COPY 2 +2:0-2:6 LOAD_CONST 1 ('X') + +1:0-1:9 STORE_SUBSCR + +3:0-3:6 L2: LOAD_SMALL_INT 1 +3:0-3:6 LOAD_GLOBAL 0 (__conditional_annotations__) +3:0-3:6 CONTAINS_OP 0 (in) +3:0-3:6 POP_JUMP_IF_FALSE 10 (to L3) +3:0-3:6 NOT_TAKEN +3:3-3:6 LOAD_GLOBAL 4 (str) +3:0-3:6 COPY 2 +3:0-3:6 LOAD_CONST 2 ('Y') + +1:0-1:9 STORE_SUBSCR +1:0-1:9 L3: RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_docstring_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_docstring_location.snap new file mode 100644 index 00000000000..20daf83cdee --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__module_docstring_location.snap @@ -0,0 +1,13 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\n\"doc\"\nx = 1\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-1:5 LOAD_CONST 0 ('doc') +1:0-1:5 STORE_NAME 0 (__doc__) + +2:4-2:5 LOAD_SMALL_INT 1 +2:0-2:1 STORE_NAME 1 (x) +2:0-2:1 LOAD_COMMON_CONSTANT 7 (None) +2:0-2:1 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__namedexpr_copy_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__namedexpr_copy_location.snap new file mode 100644 index 00000000000..7ea7e2d942a --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__namedexpr_copy_location.snap @@ -0,0 +1,40 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef outer():\n a = 10\n def spam():\n nonlocal a\n (a := 20)\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-5:17 LOAD_CONST 0 (", line 1>) +1:0-5:17 MAKE_FUNCTION +1:0-5:17 STORE_NAME 0 (outer) +1:0-5:17 LOAD_COMMON_CONSTANT 7 (None) +1:0-5:17 RETURN_VALUE + +Disassembly of ", line 1>: + -- MAKE_CELL 1 (a) + +1:0-1:0 RESUME 0 + +2:8-2:10 LOAD_SMALL_INT 10 +2:4-2:5 STORE_DEREF 1 (a) + +3:4-5:17 LOAD_FAST_BORROW 1 (a) +3:4-5:17 BUILD_TUPLE 1 +3:4-5:17 LOAD_CONST 1 (", line 3>) +3:4-5:17 MAKE_FUNCTION +3:4-5:17 SET_FUNCTION_ATTRIBUTE 8 (closure) +3:4-5:17 STORE_FAST 0 (spam) +3:4-5:17 LOAD_COMMON_CONSTANT 7 (None) +3:4-5:17 RETURN_VALUE + +Disassembly of ", line 3>: + -- COPY_FREE_VARS 1 + +3:0-3:0 RESUME 0 + +5:14-5:16 LOAD_SMALL_INT 20 +5:9-5:16 COPY 1 +5:9-5:10 STORE_DEREF 0 (a) +5:9-5:10 POP_TOP +5:9-5:10 LOAD_COMMON_CONSTANT 7 (None) +5:9-5:10 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_chained_compare_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_chained_compare_location.snap new file mode 100644 index 00000000000..32292e60cef --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_chained_compare_location.snap @@ -0,0 +1,35 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(c):\n return not (b\" \" <= c <= b\"~\")\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:34 LOAD_CONST 0 (", line 1>) +1:0-2:34 MAKE_FUNCTION +1:0-2:34 STORE_NAME 0 (f) +1:0-2:34 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:34 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:16-2:20 LOAD_CONST 0 (b' ') +2:24-2:25 LOAD_FAST 0 (c) +2:16-2:33 SWAP 2 +2:16-2:33 COPY 2 +2:16-2:33 COMPARE_OP 42 (<=) +2:16-2:33 COPY 1 +2:16-2:33 TO_BOOL +2:16-2:33 POP_JUMP_IF_FALSE 11 (to L1) +2:16-2:33 NOT_TAKEN +2:16-2:33 POP_TOP +2:29-2:33 LOAD_CONST 1 (b'~') +2:16-2:33 COMPARE_OP 42 (<=) +2:11-2:34 TO_BOOL +2:11-2:34 UNARY_NOT +2:4-2:34 RETURN_VALUE +2:16-2:33 L1: SWAP 2 +2:16-2:33 POP_TOP +2:11-2:34 TO_BOOL +2:11-2:34 UNARY_NOT +2:4-2:34 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_compare_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_compare_location.snap new file mode 100644 index 00000000000..5b24334db26 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__not_compare_location.snap @@ -0,0 +1,19 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(self, other):\n return not self == other\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-2:28 LOAD_CONST 0 (", line 1>) +1:0-2:28 MAKE_FUNCTION +1:0-2:28 STORE_NAME 0 (f) +1:0-2:28 LOAD_COMMON_CONSTANT 7 (None) +1:0-2:28 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:15-2:19 LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (self, other) +2:11-2:28 COMPARE_OP 88 (bool(==)) +2:11-2:28 UNARY_NOT +2:4-2:28 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__redundant_nop_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__redundant_nop_location.snap new file mode 100644 index 00000000000..43fbde70607 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__redundant_nop_location.snap @@ -0,0 +1,65 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(x, y, z):\n while x:\n if y:\n pass\n elif z:\n if y < 0:\n return y\n if z:\n y = y + 1\n elif y:\n return 1\n return -1\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-12:13 LOAD_CONST 0 (", line 1>) +1:0-12:13 MAKE_FUNCTION +1:0-12:13 STORE_NAME 0 (f) +1:0-12:13 LOAD_COMMON_CONSTANT 7 (None) +1:0-12:13 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:10-2:11 L1: LOAD_FAST_BORROW 0 (x) +2:10-2:11 TO_BOOL +2:10-2:11 POP_JUMP_IF_FALSE 61 (to L7) +2:10-2:11 NOT_TAKEN + +3:11-3:12 LOAD_FAST_BORROW 1 (y) +3:11-3:12 TO_BOOL +3:11-3:12 POP_JUMP_IF_FALSE 3 (to L2) +3:11-3:12 NOT_TAKEN + +4:12-4:16 JUMP_BACKWARD 18 (to L1) + +5:13-5:14 L2: LOAD_FAST_BORROW 2 (z) +5:13-5:14 TO_BOOL +5:13-5:14 POP_JUMP_IF_FALSE 31 (to L5) +5:13-5:14 NOT_TAKEN + +6:15-6:16 LOAD_FAST_BORROW 1 (y) +6:19-6:20 LOAD_SMALL_INT 0 +6:15-6:20 COMPARE_OP 18 (bool(<)) +6:15-6:20 POP_JUMP_IF_FALSE 3 (to L3) +6:15-6:20 NOT_TAKEN + +7:23-7:24 LOAD_FAST_BORROW 1 (y) +7:16-7:24 RETURN_VALUE + +8:15-8:16 L3: LOAD_FAST_BORROW 2 (z) +8:15-8:16 TO_BOOL +8:15-8:16 POP_JUMP_IF_FALSE 12 (to L4) +8:15-8:16 NOT_TAKEN + +9:20-9:21 LOAD_FAST_BORROW 1 (y) +9:24-9:25 LOAD_SMALL_INT 1 +9:20-9:25 BINARY_OP 0 (+) +9:16-9:17 STORE_FAST 1 (y) +9:16-9:17 JUMP_BACKWARD 54 (to L1) + +8:15-8:16 L4: JUMP_BACKWARD 56 (to L1) + +10:13-10:14 L5: LOAD_FAST_BORROW 1 (y) +10:13-10:14 TO_BOOL +10:13-10:14 POP_JUMP_IF_TRUE 3 (to L6) +10:13-10:14 NOT_TAKEN +10:13-10:14 JUMP_BACKWARD 66 (to L1) + +11:19-11:20 L6: LOAD_SMALL_INT 1 +11:19-11:20 RETURN_VALUE + +12:11-12:13 L7: LOAD_COMMON_CONSTANT 11 (-1) +12:4-12:13 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__starred_arg_annotation_unpack_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__starred_arg_annotation_unpack_location.snap new file mode 100644 index 00000000000..4f82d8e7a10 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__starred_arg_annotation_unpack_location.snap @@ -0,0 +1,34 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(*args: *Ts): pass\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-1:23 LOAD_CONST 0 (", line 1>) +1:0-1:23 MAKE_FUNCTION +1:0-1:23 LOAD_CONST 1 (", line 1>) +1:0-1:23 MAKE_FUNCTION +1:0-1:23 SET_FUNCTION_ATTRIBUTE 16 (annotate) +1:0-1:23 STORE_NAME 0 (f) +1:0-1:23 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:23 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:0-1:23 LOAD_FAST_BORROW 0 (format) +1:0-1:23 LOAD_SMALL_INT 2 +1:0-1:23 COMPARE_OP 132 (>) +1:0-1:23 POP_JUMP_IF_FALSE 3 (to L1) +1:0-1:23 NOT_TAKEN +1:0-1:23 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:0-1:23 RAISE_VARARGS 1 +1:0-1:23 L1: LOAD_CONST 1 ('args') +1:14-1:16 LOAD_GLOBAL 0 (Ts) +1:0-1:23 UNPACK_SEQUENCE 1 +1:0-1:23 BUILD_MAP 1 +1:0-1:23 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:19-1:23 LOAD_COMMON_CONSTANT 7 (None) +1:19-1:23 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__static_swap_implicit_return_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__static_swap_implicit_return_location.snap new file mode 100644 index 00000000000..33c4cade544 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__static_swap_implicit_return_location.snap @@ -0,0 +1,22 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(a, b):\n a, b = a, b\n b, a = a, b\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-3:15 LOAD_CONST 0 (", line 1>) +1:0-3:15 MAKE_FUNCTION +1:0-3:15 STORE_NAME 0 (f) +1:0-3:15 LOAD_COMMON_CONSTANT 7 (None) +1:0-3:15 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:12 LOAD_FAST_LOAD_FAST 1 (a, b) +2:7-2:8 STORE_FAST_STORE_FAST 16 (b, a) + +3:11-3:12 LOAD_FAST_LOAD_FAST 1 (a, b) +3:7-3:8 STORE_FAST_STORE_FAST 1 (a, b) +3:7-3:8 LOAD_COMMON_CONSTANT 7 (None) +3:7-3:8 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__taken_constant_boolop_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__taken_constant_boolop_location.snap new file mode 100644 index 00000000000..57ce5871085 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__taken_constant_boolop_location.snap @@ -0,0 +1,27 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef and_false(x):\n return False and x\n\ndef or_true(x):\n return True or x\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:22 LOAD_CONST 0 (", line 1>) +1:0-2:22 MAKE_FUNCTION +1:0-2:22 STORE_NAME 0 (and_false) + +4:0-5:20 LOAD_CONST 1 (", line 4>) +4:0-5:20 MAKE_FUNCTION +4:0-5:20 STORE_NAME 1 (or_true) +4:0-5:20 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:20 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:11-2:16 LOAD_COMMON_CONSTANT 10 (False) +2:4-2:22 RETURN_VALUE + +Disassembly of ", line 4>: +4:0-4:0 RESUME 0 + +5:11-5:15 LOAD_COMMON_CONSTANT 9 (True) +5:4-5:20 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__try_finally_exception_scaffolding_location.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__try_finally_exception_scaffolding_location.snap new file mode 100644 index 00000000000..68303704059 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__try_finally_exception_scaffolding_location.snap @@ -0,0 +1,46 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(self, node):\n self.flag = True\n try:\n self.body(node)\n finally:\n self.flag = False\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-6:25 LOAD_CONST 0 (", line 1>) +1:0-6:25 MAKE_FUNCTION +1:0-6:25 STORE_NAME 0 (f) +1:0-6:25 LOAD_COMMON_CONSTANT 7 (None) +1:0-6:25 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:16-2:20 LOAD_COMMON_CONSTANT 9 (True) +2:4-2:8 LOAD_FAST_BORROW 0 (self) +2:4-2:13 STORE_ATTR 0 (flag) + +3:4-6:25 NOP + +4:8-4:12 L1: LOAD_FAST_BORROW 0 (self) +4:8-4:17 LOAD_ATTR 3 (body + NULL|self) +4:18-4:22 LOAD_FAST_BORROW 1 (node) +4:8-4:23 CALL 1 +4:8-4:23 POP_TOP + +6:20-6:25 L2: LOAD_COMMON_CONSTANT 10 (False) +6:8-6:12 LOAD_FAST_BORROW 0 (self) +6:8-6:17 STORE_ATTR 0 (flag) +6:8-6:17 LOAD_COMMON_CONSTANT 7 (None) +6:8-6:17 RETURN_VALUE + + -- L3: PUSH_EXC_INFO + +6:20-6:25 LOAD_COMMON_CONSTANT 10 (False) +6:8-6:12 LOAD_FAST 0 (self) +6:8-6:17 STORE_ATTR 0 (flag) +6:8-6:17 RERAISE 0 + + -- L4: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [0] + L3 to L4 -> L4 [1] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__type_param_scope_locations.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__type_param_scope_locations.snap new file mode 100644 index 00000000000..9f5ae9dee3e --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__type_param_scope_locations.snap @@ -0,0 +1,66 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ntype BoundGenericAlias[X: int] = set[X]\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-1:39 LOAD_CONST 0 ( at 0xdeadbeef, file "", line 1>) +1:0-1:39 MAKE_FUNCTION +1:0-1:39 PUSH_NULL +1:0-1:39 CALL 0 +1:0-1:39 STORE_NAME 0 (BoundGenericAlias) +1:0-1:39 LOAD_COMMON_CONSTANT 7 (None) +1:0-1:39 RETURN_VALUE + +Disassembly of at 0xdeadbeef, file "", line 1>: + -- MAKE_CELL 0 (X) + +1:0-1:0 RESUME 0 +1:0-1:39 LOAD_CONST 0 ('BoundGenericAlias') +1:23-1:29 LOAD_CONST 1 ('X') +1:26-1:29 LOAD_CONST 2 ((1,)) +1:26-1:29 LOAD_CONST 3 (", line 1>) +1:26-1:29 MAKE_FUNCTION +1:26-1:29 SET_FUNCTION_ATTRIBUTE 1 (defaults) +1:23-1:29 CALL_INTRINSIC_2 2 (INTRINSIC_TYPEVAR_WITH_BOUND) +1:23-1:29 COPY 1 +1:23-1:29 STORE_DEREF 0 (X) +1:23-1:29 BUILD_TUPLE 1 +1:0-1:39 LOAD_CONST 2 ((1,)) +1:0-1:39 LOAD_FAST_BORROW 0 (X) +1:0-1:39 BUILD_TUPLE 1 +1:0-1:39 LOAD_CONST 4 (", line 1>) +1:0-1:39 MAKE_FUNCTION +1:0-1:39 SET_FUNCTION_ATTRIBUTE 8 (closure) +1:0-1:39 SET_FUNCTION_ATTRIBUTE 1 (defaults) +1:0-1:39 BUILD_TUPLE 3 +1:0-1:39 CALL_INTRINSIC_1 11 (INTRINSIC_TYPEALIAS) +1:0-1:39 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 +1:26-1:29 LOAD_FAST_BORROW 0 (.format) +1:26-1:29 LOAD_SMALL_INT 2 +1:26-1:29 COMPARE_OP 132 (>) +1:26-1:29 POP_JUMP_IF_FALSE 3 (to L1) +1:26-1:29 NOT_TAKEN +1:26-1:29 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:26-1:29 RAISE_VARARGS 1 +1:26-1:29 L1: LOAD_GLOBAL 0 (int) +1:26-1:29 RETURN_VALUE + +Disassembly of ", line 1>: + -- COPY_FREE_VARS 1 + +1:0-1:0 RESUME 0 +1:0-1:39 LOAD_FAST_BORROW 0 (.format) +1:0-1:39 LOAD_SMALL_INT 2 +1:0-1:39 COMPARE_OP 132 (>) +1:0-1:39 POP_JUMP_IF_FALSE 3 (to L1) +1:0-1:39 NOT_TAKEN +1:0-1:39 LOAD_COMMON_CONSTANT 1 (NotImplementedError) +1:0-1:39 RAISE_VARARGS 1 +1:33-1:36 L1: LOAD_GLOBAL 0 (set) +1:37-1:38 LOAD_DEREF 1 (X) +1:33-1:39 BINARY_OP 26 ([]) +1:0-1:39 RETURN_VALUE diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__with_cleanup_excludes_not_taken.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__with_cleanup_excludes_not_taken.snap new file mode 100644 index 00000000000..b2cec5081f3 --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__with_cleanup_excludes_not_taken.snap @@ -0,0 +1,62 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(c):\n with c:\n x = 1\n y = 2\n\"#, true)" +--- +0:0-1:0 RESUME 0 + +1:0-4:9 LOAD_CONST 0 (", line 1>) +1:0-4:9 MAKE_FUNCTION +1:0-4:9 STORE_NAME 0 (f) +1:0-4:9 LOAD_COMMON_CONSTANT 7 (None) +1:0-4:9 RETURN_VALUE + +Disassembly of ", line 1>: +1:0-1:0 RESUME 0 + +2:9-2:10 LOAD_FAST_BORROW 0 (c) +2:9-2:10 COPY 1 +2:9-2:10 LOAD_SPECIAL 1 (__exit__) +2:9-2:10 SWAP 2 +2:9-2:10 SWAP 3 +2:9-2:10 LOAD_SPECIAL 0 (__enter__) +2:9-2:10 CALL 0 +2:9-2:10 L1: POP_TOP + +3:12-3:13 LOAD_SMALL_INT 1 +3:8-3:9 STORE_FAST 1 (x) + +2:9-2:10 L2: LOAD_COMMON_CONSTANT 7 (None) +2:9-2:10 LOAD_COMMON_CONSTANT 7 (None) +2:9-2:10 LOAD_COMMON_CONSTANT 7 (None) +2:9-2:10 CALL 3 +2:9-2:10 POP_TOP + +4:8-4:9 LOAD_SMALL_INT 2 +4:4-4:5 STORE_FAST 2 (y) +4:4-4:5 LOAD_COMMON_CONSTANT 7 (None) +4:4-4:5 RETURN_VALUE + +2:9-2:10 L3: PUSH_EXC_INFO +2:9-2:10 WITH_EXCEPT_START +2:9-2:10 TO_BOOL +2:9-2:10 POP_JUMP_IF_TRUE 2 (to L6) +2:9-2:10 L4: NOT_TAKEN +2:9-2:10 L5: RERAISE 2 +2:9-2:10 L6: POP_TOP +2:9-2:10 L7: POP_EXCEPT +2:9-2:10 POP_TOP +2:9-2:10 POP_TOP +2:9-2:10 POP_TOP + +4:8-4:9 LOAD_SMALL_INT 2 +4:4-4:5 STORE_FAST 2 (y) +4:4-4:5 LOAD_COMMON_CONSTANT 7 (None) +4:4-4:5 RETURN_VALUE + + -- L8: COPY 3 + -- POP_EXCEPT + -- RERAISE 1 +ExceptionTable: + L1 to L2 -> L3 [2] lasti + L3 to L4 -> L8 [4] lasti + L5 to L7 -> L8 [4] lasti diff --git a/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__yield_from_and_await.snap b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__yield_from_and_await.snap new file mode 100644 index 00000000000..9805d8e97df --- /dev/null +++ b/crates/stdlib/src/snapshots/rustpython_stdlib___opcode__tests__yield_from_and_await.snap @@ -0,0 +1,66 @@ +--- +source: crates/stdlib/src/_opcode.rs +expression: "dis(r#\"\ndef f(xs):\n yield from xs\n\nasync def g(xs):\n return await xs\n\"#,\ntrue)" +--- +0:0-1:0 RESUME 0 + +1:0-2:17 LOAD_CONST 0 (", line 1>) +1:0-2:17 MAKE_FUNCTION +1:0-2:17 STORE_NAME 0 (f) + +4:0-5:19 LOAD_CONST 1 (", line 4>) +4:0-5:19 MAKE_FUNCTION +4:0-5:19 STORE_NAME 1 (g) +4:0-5:19 LOAD_COMMON_CONSTANT 7 (None) +4:0-5:19 RETURN_VALUE + +Disassembly of ", line 1>: +1:?-1:? RETURN_GENERATOR +1:?-1:? POP_TOP +1:0-1:0 L1: RESUME 0 + +2:15-2:17 LOAD_FAST 0 (xs) +2:4-2:17 GET_ITER 3 +2:4-2:17 LOAD_COMMON_CONSTANT 7 (None) +2:4-2:17 L2: SEND 4 (to L5) +2:4-2:17 L3: YIELD_VALUE 1 +2:4-2:17 L4: RESUME 2 +2:4-2:17 JUMP_BACKWARD_NO_INTERRUPT 6 (to L2) +2:4-2:17 L5: END_SEND +2:4-2:17 POP_TOP +2:4-2:17 LOAD_COMMON_CONSTANT 7 (None) +2:4-2:17 RETURN_VALUE +2:4-2:17 L6: CLEANUP_THROW +2:4-2:17 L7: JUMP_BACKWARD_NO_INTERRUPT 6 (to L5) + + -- L8: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L3 -> L8 [0] lasti + L3 to L4 -> L6 [3] + L4 to L7 -> L8 [0] lasti + +Disassembly of ", line 4>: +4:?-4:? RETURN_GENERATOR +4:?-4:? POP_TOP +4:0-4:0 L1: RESUME 0 + +5:17-5:19 LOAD_FAST_BORROW 0 (xs) +5:11-5:19 GET_AWAITABLE 0 +5:11-5:19 PUSH_NULL +5:11-5:19 LOAD_COMMON_CONSTANT 7 (None) +5:11-5:19 L2: SEND 4 (to L5) +5:11-5:19 L3: YIELD_VALUE 1 +5:11-5:19 L4: RESUME 3 +5:11-5:19 JUMP_BACKWARD_NO_INTERRUPT 6 (to L2) +5:11-5:19 L5: END_SEND +5:4-5:19 RETURN_VALUE +5:11-5:19 L6: CLEANUP_THROW +5:11-5:19 L7: JUMP_BACKWARD_NO_INTERRUPT 4 (to L5) + + -- L8: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR) + -- RERAISE 1 +ExceptionTable: + L1 to L3 -> L8 [0] lasti + L3 to L4 -> L6 [3] + L4 to L7 -> L8 [0] lasti diff --git a/crates/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index a2e8e0d630a..89f760ad26f 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -1,6 +1,6 @@ //! Infamous code object. The python class `code` -mod compile_cache; +mod const_cache; use super::descriptor::{MemberKind, MemberLayout}; use super::{PyBytesRef, PyStrRef, PyTupleRef, PyType, set::PyFrozenSet}; @@ -258,22 +258,34 @@ fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> { }) } -/// Immutable tuple backing for runtime constants. Construction is private so -/// the only exposed slice always borrows a real tuple owner. +/// `co_consts` viewed as constants. #[derive(Clone)] -pub struct LiteralTupleOwner(PyTupleRef); +pub struct LiteralTuple(PyTupleRef); -impl AsRef<[Literal]> for LiteralTupleOwner { - fn as_ref(&self) -> &[Literal] { +impl LiteralTuple { + pub const fn as_pytuple(&self) -> &PyTupleRef { + &self.0 + } +} + +impl Deref for LiteralTuple { + type Target = [Literal]; + + fn deref(&self) -> &[Literal] { let elements = self.0.as_slice(); - // SAFETY: Literal is transparent over PyObjectRef and accepts every - // Python object. The immutable slice cannot outlive this tuple owner. + // SAFETY: Literal is repr(transparent) over PyObjectRef. unsafe { &*(elements as *const [PyObjectRef] as *const [Literal]) } } } +impl FromIterator for LiteralTuple { + fn from_iter>(iter: T) -> Self { + Self(Context::genesis().new_tuple(iter.into_iter().map(|c| c.0).collect())) + } +} + impl Constant for Literal { - type TupleOwner = LiteralTupleOwner; + type Tuple = LiteralTuple; type Name = &'static PyStrInterned; fn borrow_constant(&self) -> BorrowedConstant<'_, Self> { borrow_obj_constant(&self.0) @@ -375,8 +387,6 @@ pub(crate) type CodeObject = bytecode::CodeObject; pub trait IntoCodeObject { fn into_code_object(self, vm: &VirtualMachine) -> CodeObject; - /// Finalize runtime fields before publication. Basic compilation overrides - /// this to share one private pool; frozen and Literal routes keep defaults. fn into_pycode(self, vm: &VirtualMachine) -> PyCode where Self: Sized, @@ -393,13 +403,11 @@ impl IntoCodeObject for CodeObject { impl IntoCodeObject for bytecode::CodeObject { fn into_code_object(self, vm: &VirtualMachine) -> CodeObject { - // Raw callers receive pooled constants/nested bodies, but no promise - // of root assembly-field identity; use into_pycode for that boundary. - compile_cache::into_code_object(self, vm) + const_cache::into_code_object(self, vm) } fn into_pycode(self, vm: &VirtualMachine) -> PyCode { - compile_cache::into_pycode(self, vm) + const_cache::into_pycode(self, vm) } } @@ -421,6 +429,15 @@ pub struct CoMonitoringData { pub per_instruction_opcodes: Vec, } +/// `_PyCoCached`: objects created on first use. +#[derive(Default)] +struct CoCached { + code: OnceCell, + varnames: OnceCell, + cellvars: OnceCell, + freevars: OnceCell, +} + #[pyclass(module = false, name = "code")] pub struct PyCode { #[pymember(name = "co_argcount", path = "arg_count")] @@ -429,28 +446,21 @@ pub struct PyCode { #[pymember(name = "co_stacksize", path = "max_stackdepth")] #[pymember(name = "co_qualname", path = "qualname")] #[pymember(name = "co_flags", path = "flags")] - // Logical code fields are finalized by `new`. Execution may specialize or - // instrument instructions, but must not change the canonical public bytes. - // Callers replacing logical bytecode must construct a new PyCode instead. pub code: CodeObject, - /// Canonical bytes captured before execution can instrument the code. - canonical_code: Box<[u8]>, - /// Preserve a public co_name subclass; exact strings use the interned owner. - name_owner: Option, - /// The stable Python object exposed by co_code, allocated on first access. - code_bytes: OnceCell, /// Slot-indexed names, equivalent to CPython's `co_localsplusnames`. /// Derived once so frame-local proxy operations do not repeatedly scan /// merged cell variables. localsplus_names: Box<[&'static PyStrInterned]>, - names_tuple: PyTupleRef, - localsplus_names_tuple: PyTupleRef, - localsplus_kinds_bytes: PyBytesRef, - linetable_bytes: PyBytesRef, - exceptiontable_bytes: PyBytesRef, - varnames_tuple: OnceCell, - cellvars_tuple: OnceCell, - freevars_tuple: OnceCell, + pub(crate) co_name: PyStrRef, + pub(crate) co_names: PyTupleRef, + pub(crate) co_localsplusnames: PyTupleRef, + pub(crate) co_localspluskinds: PyBytesRef, + pub(crate) co_linetable: PyBytesRef, + pub(crate) co_exceptiontable: PyBytesRef, + co_cached: CoCached, + /// `_co_firsttraceable`: the index of the first `RESUME` that does not + /// start a generator expression, or the code length if there is none. + pub(crate) first_traceable: usize, #[pymember(name = "co_filename")] source_path: AtomicPtr, #[pymember(name = "co_nlocals")] @@ -554,58 +564,17 @@ fn build_localspluskinds( Ok(kinds.into_boxed_slice()) } +fn holds_names(tuple: &super::PyTuple, names: &[&'static PyStrInterned]) -> bool { + tuple.len() == names.len() && tuple.iter().zip(names).all(|(item, name)| item.is(*name)) +} + impl MemberLayout for CodeFlags { const KIND: MemberKind = MemberKind::Int; } impl PyCode { - /// Borrow the actual finalized owner, without reconstructing field values. - /// The caller keeps its canonical bytecode getter owner through the write. - pub(crate) fn marshal_field<'a>( - &'a self, - field: rustpython_compiler_core::marshal::CodeField, - bytecode: &'a PyBytesRef, - ) -> &'a PyObject { - use rustpython_compiler_core::marshal::CodeField; - match field { - CodeField::Bytecode => bytecode.as_object(), - CodeField::Constants => self - .code - .constants - .tuple_owner() - .expect("finalized constants") - .0 - .as_object(), - CodeField::Names => self.names_tuple.as_object(), - CodeField::LocalsPlusNames => self.localsplus_names_tuple.as_object(), - CodeField::LocalsPlusKinds => self.localsplus_kinds_bytes.as_object(), - CodeField::Name => self.name_object(), - CodeField::Qualname => self.code.qualname.as_object(), - CodeField::LineTable => self.linetable_bytes.as_object(), - CodeField::ExceptionTable => self.exceptiontable_bytes.as_object(), - } - } - - /// Finalize a pristine code object before publishing it to the runtime. - /// - /// The input must not be cloned from an already instrumented PyCode: - /// generic monitoring wrappers need side tables that CodeObject does not - /// carry. Python `replace` reconstructs from canonical bytes instead. pub fn new(mut code: CodeObject, ctx: &Context) -> Self { - if code.constants.tuple_owner().is_none() { - let boxed = core::mem::replace(&mut code.constants, core::iter::empty().collect()); - let tuple = ctx.new_tuple( - boxed - .into_boxed() - .into_vec() - .into_iter() - .map(|x| x.0) - .collect(), - ); - code.constants = bytecode::Constants::from_tuple(LiteralTupleOwner(tuple)); - } - code.instructions.normalize_get_iter(code.flags); - let canonical_code = code.instructions.original_bytes().into_boxed_slice(); + code.instructions.fixup_get_iter(code.flags); let sp = code.source_path as *const PyStrInterned as *mut PyStrInterned; let nlocals = i32::try_from(code.varnames.len()).unwrap_or(i32::MAX); let localsplus_names = { @@ -641,29 +610,28 @@ impl PyCode { | Instruction::InstrumentedEndAsyncFor ) }); - let names_tuple = ctx.new_tuple(code.names.iter().map(|name| name.to_object()).collect()); - let localsplus_names_tuple = ctx.new_tuple( - localsplus_names - .iter() - .map(|name| name.to_object()) - .collect(), - ); - let localsplus_kinds_bytes = ctx.new_bytes(code.localspluskinds.to_vec()); - let linetable_bytes = ctx.new_bytes(code.linetable.to_vec()); - let exceptiontable_bytes = ctx.new_bytes(code.exceptiontable.to_vec()); + let first_traceable = code + .instructions + .iter() + .position(|unit| { + matches!(unit.op, Instruction::Resume { .. }) + && u32::from(u8::from(unit.arg)) & bytecode::oparg::ResumeContext::LOCATION_MASK + != bytecode::oparg::ResumeLocation::AtGenExprStart.as_u32() + }) + .unwrap_or(code.instructions.len()); + let names_tuple = |names: &[&'static PyStrInterned]| { + ctx.new_tuple(names.iter().map(|name| name.to_object()).collect()) + }; Self { + first_traceable, + co_name: code.obj_name.to_owned(), + co_names: names_tuple(&code.names), + co_localsplusnames: names_tuple(&localsplus_names), + co_localspluskinds: ctx.new_bytes(code.localspluskinds.to_vec()), + co_linetable: ctx.new_bytes(code.linetable.to_vec()), + co_exceptiontable: ctx.new_bytes(code.exceptiontable.to_vec()), + co_cached: CoCached::default(), code, - names_tuple, - localsplus_names_tuple, - localsplus_kinds_bytes, - linetable_bytes, - exceptiontable_bytes, - varnames_tuple: OnceCell::new(), - cellvars_tuple: OnceCell::new(), - freevars_tuple: OnceCell::new(), - canonical_code, - name_owner: None, - code_bytes: OnceCell::new(), localsplus_names, source_path: AtomicPtr::new(sp), nlocals, @@ -674,62 +642,39 @@ impl PyCode { } } - /// Attach validated public-reader fields before publication. Superseded - /// boxed constants are dropped before the returned code can be observed. - pub(crate) fn from_marshaled_fields( + /// The code object `r_object` creates from a TYPE_CODE record. It keeps + /// the objects read for the object-valued fields. + pub(crate) fn from_marshal( mut code: CodeObject, - fields: rustpython_compiler_core::marshal::CodeFieldObjects, + fields: rustpython_compiler_core::marshal::CodeFields, ctx: &Context, ) -> Result { use rustpython_compiler_core::marshal::MarshalError; - let constants = fields - .constants - .downcast::() - .map_err(|_| MarshalError::BadType)?; - let names = fields - .names - .downcast::() - .map_err(|_| MarshalError::BadType)?; - let locals = fields - .localsplus_names - .downcast::() - .map_err(|_| MarshalError::BadType)?; - let kinds = fields - .localsplus_kinds - .downcast::() - .map_err(|_| MarshalError::BadType)?; - let lines = fields - .linetable - .downcast::() - .map_err(|_| MarshalError::BadType)?; - let exceptions = fields - .exceptiontable - .downcast::() - .map_err(|_| MarshalError::BadType)?; - code.constants = bytecode::Constants::from_tuple(LiteralTupleOwner(constants)); - let mut result = Self::new(code, ctx); - result.retain_normalized_names(names, Some(locals)); - result.localsplus_kinds_bytes = kinds; - result.linetable_bytes = lines; - result.exceptiontable_bytes = exceptions; - Ok(result) - } - - fn retain_normalized_names(&mut self, names: PyTupleRef, locals: Option) { - let matches = |tuple: &PyTupleRef, canonical: &[&'static PyStrInterned]| { - tuple.len() == canonical.len() - && tuple - .iter() - .zip(canonical) - .all(|(item, name)| item.is(*name)) - }; - if matches(&names, &self.code.names) { - self.names_tuple = names; + fn downcast(obj: PyObjectRef) -> Result, MarshalError> { + obj.downcast().map_err(|_| MarshalError::BadType) + } + code.constants = bytecode::Constants::new(LiteralTuple(downcast(fields.consts)?)); + let mut code = Self::new(code, ctx); + code.set_names(downcast(fields.names)?); + code.set_localsplusnames(downcast(fields.localsplusnames)?); + code.co_localspluskinds = downcast(fields.localspluskinds)?; + code.co_linetable = downcast(fields.linetable)?; + code.co_exceptiontable = downcast(fields.exceptiontable)?; + Ok(code) + } + + /// Keep `names` as `co_names` when it holds the interned names, the way + /// `intern_strings` interns a names tuple in place. + fn set_names(&mut self, names: PyTupleRef) { + if holds_names(&names, &self.code.names) { + self.co_names = names; } - if let Some(locals) = locals - && matches(&locals, &self.localsplus_names) - { - self.localsplus_names_tuple = locals; + } + + /// Keep `localsplusnames` as `co_localsplusnames`; see [`Self::set_names`]. + fn set_localsplusnames(&mut self, localsplusnames: PyTupleRef) { + if holds_names(&localsplusnames, &self.localsplus_names) { + self.co_localsplusnames = localsplusnames; } } @@ -741,16 +686,39 @@ impl PyCode { self.localsplus_names[index] } - fn name_object(&self) -> &PyObject { - self.name_owner - .as_ref() - .map_or_else(|| self.code.obj_name.as_object(), |name| name.as_object()) + pub(crate) fn co_consts(&self) -> &PyTupleRef { + self.code.constants.as_tuple().as_pytuple() } - fn retain_name_subclass(&mut self, name: PyStrRef, ctx: &Context) { - if !name.class().is(ctx.types.str_type) { - self.name_owner = Some(name); - } + /// `_PyCode_GetCode` + pub(crate) fn get_code(&self, vm: &VirtualMachine) -> PyBytesRef { + self.co_cached + .code + .get_or_init(|| vm.ctx.new_bytes(self.deopt_code())) + .clone() + } + + /// `deopt_code`: the instructions with each opcode replaced by its base + /// opcode (`_Py_GetBaseCodeUnit`) and the inline caches zeroed. + fn deopt_code(&self) -> Vec { + let monitoring_data = self.monitoring_data.lock(); + self.code.instructions.original_bytes_with(|i, op| { + let Some(data) = monitoring_data.as_ref() else { + return op; + }; + let op = match op { + Instruction::InstrumentedLine => Instruction::try_from(data.line_opcodes[i]) + .expect("INSTRUMENTED_LINE without original opcode"), + op => op, + }; + match op { + Instruction::InstrumentedInstruction => { + Instruction::try_from(data.per_instruction_opcodes[i]) + .expect("INSTRUMENTED_INSTRUCTION without original opcode") + } + op => op, + } + }) } pub fn source_path(&self) -> &'static PyStrInterned { @@ -915,6 +883,78 @@ impl Representable for PyCode { } } +/// `_PyCode_ConstantKey` +pub(crate) fn constant_key(op: &PyObject, vm: &VirtualMachine) -> PyResult { + use super::{PyBool, PyBytes, PyComplex, PyFloat, PyInt, PySlice, PyStr, PyTuple}; + + let ctx = &vm.ctx; + let class = op.class(); + let pair = |first: PyObjectRef| ctx.new_tuple(vec![first, op.to_owned()]).into(); + let triple = |tag: PyObjectRef| { + ctx.new_tuple(vec![class.to_owned().into(), op.to_owned(), tag]) + .into() + }; + let is_neg_zero = |d: f64| d == 0.0 && d.is_sign_negative(); + + let key = if vm.is_none(op) + || op.is(&ctx.ellipsis) + || class.is(PyInt::static_type()) + || class.is(PyStr::static_type()) + || op.downcast_ref::().is_some() + { + // Objects of these types are always different from object of other + // type and from tuples. + op.to_owned() + } else if class.is(PyBool::static_type()) || class.is(PyBytes::static_type()) { + // Make booleans different from integers 0 and 1. + pair(class.to_owned().into()) + } else if let Some(float) = op.downcast_ref_if_exact::(vm) { + if is_neg_zero(float.to_f64()) { + triple(ctx.none()) + } else { + pair(class.to_owned().into()) + } + } else if let Some(complex) = op.downcast_ref_if_exact::(vm) { + // All four complex zeros must be distinguished. + let z = complex.to_complex(); + match (is_neg_zero(z.re), is_neg_zero(z.im)) { + (true, true) => triple(ctx.new_bool(true).into()), + (false, true) => triple(ctx.new_bool(false).into()), + (true, false) => triple(ctx.none()), + (false, false) => pair(class.to_owned().into()), + } + } else if let Some(tuple) = op.downcast_ref_if_exact::(vm) { + let keys = tuple + .iter() + .map(|item| constant_key(item, vm)) + .collect::>()?; + pair(ctx.new_tuple(keys).into()) + } else if let Some(set) = op.downcast_ref_if_exact::(vm) { + let keys = set + .elements() + .iter() + .map(|item| constant_key(item, vm)) + .collect::>>()?; + pair(PyFrozenSet::from_iter(vm, keys)?.into_ref(ctx).into()) + } else if let Some(slice) = op.downcast_ref::() { + let key = |item: Option<&PyObjectRef>| match item { + Some(item) => constant_key(item, vm), + None => Ok(ctx.none()), + }; + let slice_key = PySlice { + start: Some(key(slice.start.as_ref())?), + stop: key(Some(&slice.stop))?, + step: Some(key(slice.step.as_ref())?), + }; + pair(slice_key.into_ref(ctx).into()) + } else { + // For other types, use the object identifier as a unique identifier + // to ensure that they are seen as unequal. + pair(ctx.new_int(op.get_id()).into()) + }; + Ok(key) +} + impl Comparable for PyCode { fn cmp( zelf: &Py, @@ -926,35 +966,23 @@ impl Comparable for PyCode { let other = class_or_notimplemented!(Self, other); let a = &zelf.code; let b = &other.code; - let eq = vm.bool_eq(zelf.name_object(), other.name_object())? + let eq = vm.bool_eq(zelf.co_name.as_object(), other.co_name.as_object())? && a.arg_count == b.arg_count && a.posonlyarg_count == b.posonlyarg_count && a.kwonlyarg_count == b.kwonlyarg_count && a.flags == b.flags && a.first_line_number == b.first_line_number - && a.instructions.original_bytes() == b.instructions.original_bytes() + && zelf.get_code(vm).as_bytes() == other.get_code(vm).as_bytes() && a.linetable == b.linetable && a.exceptiontable == b.exceptiontable && a.names == b.names && a.varnames == b.varnames && a.freevars == b.freevars && a.cellvars == b.cellvars - && { - let a_consts: Vec<_> = a.constants.iter().map(|c| c.0.clone()).collect(); - let b_consts: Vec<_> = b.constants.iter().map(|c| c.0.clone()).collect(); - if a_consts.len() != b_consts.len() { - false - } else { - let mut eq = true; - for (ac, bc) in a_consts.iter().zip(b_consts.iter()) { - if !vm.bool_eq(ac, bc)? { - eq = false; - break; - } - } - eq - } - }; + && vm.bool_eq( + &*constant_key(zelf.co_consts().as_object(), vm)?, + &*constant_key(other.co_consts().as_object(), vm)?, + )?; Ok(eq.into()) }) } @@ -965,7 +993,7 @@ impl Hashable for PyCode { let code = &zelf.code; // Hash a tuple of key attributes, matching CPython's code_hash let tuple = vm.ctx.new_tuple(vec![ - zelf.name_object().to_owned(), + zelf.co_name.clone().into(), vm.ctx.new_int(code.arg_count).into(), vm.ctx.new_int(code.posonlyarg_count).into(), vm.ctx.new_int(code.kwonlyarg_count).into(), @@ -974,7 +1002,7 @@ impl Hashable for PyCode { vm.ctx .new_int(code.first_line_number.map_or(0, |n| n.get()) as i64) .into(), - vm.ctx.new_bytes(code.instructions.original_bytes()).into(), + zelf.get_code(vm).into(), { let consts: Vec<_> = code.constants.iter().map(|c| c.0.clone()).collect(); vm.ctx.new_tuple(consts).into() @@ -1078,7 +1106,7 @@ impl Constructor for PyCode { let instructions = CodeUnits::try_from(bytecode_bytes) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?; - let constants = bytecode::Constants::from_tuple(LiteralTupleOwner(args.consts)); + let constants = bytecode::Constants::new(LiteralTuple(args.consts)); // Create locations (start and end pairs) let row = if args.firstlineno > 0 { @@ -1140,11 +1168,12 @@ impl Constructor for PyCode { exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(), }; - let mut result = Self::new(code, &vm.ctx); - result.retain_name_subclass(args.name, &vm.ctx); - result.linetable_bytes = args.linetable; - result.exceptiontable_bytes = args.exceptiontable; - Ok(result) + let mut code = Self::new(code, &vm.ctx); + code.co_name = args.name; + code.set_names(args.names); + code.co_linetable = args.linetable; + code.co_exceptiontable = args.exceptiontable; + Ok(code) } } @@ -1162,7 +1191,8 @@ impl PyCode { impl Py { #[pygetset] pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef { - self.cellvars_tuple + self.co_cached + .cellvars .get_or_init(|| { vm.ctx .new_tuple(self.cellvars.iter().map(|name| name.to_object()).collect()) @@ -1177,29 +1207,23 @@ impl Py { #[pygetset] fn co_name(&self) -> PyStrRef { - self.name_owner - .clone() - .unwrap_or_else(|| self.code.obj_name.to_owned()) + self.co_name.clone() } #[pygetset] fn co_consts(&self) -> PyTupleRef { - self.code - .constants - .tuple_owner() - .expect("finalized constants") - .0 - .clone() + PyCode::co_consts(self).clone() } #[pygetset] fn co_names(&self) -> PyTupleRef { - self.names_tuple.clone() + self.co_names.clone() } #[pygetset] pub fn co_varnames(&self, vm: &VirtualMachine) -> PyTupleRef { - self.varnames_tuple + self.co_cached + .varnames .get_or_init(|| { vm.ctx.new_tuple( self.code @@ -1213,59 +1237,26 @@ impl Py { } #[pygetset] - pub fn co_code(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { - self.code_bytes - .get_or_init(|| vm.ctx.new_bytes(self.canonical_code.to_vec())) - .clone() + pub fn co_code(&self, vm: &VirtualMachine) -> PyBytesRef { + self.get_code(vm) } #[pygetset] - pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> PyResult { - let incompatible = || vm.new_value_error("incompatible adaptive bytecode layout"); - let legacy = - || vm.new_value_error("legacy private GET_YIELD_FROM_ITER has no public 3.15 encoding"); - let (canonical, []) = self.canonical_code.as_chunks::<2>() else { - return Err(incompatible()); + pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> PyBytesRef { + // Return current (possibly quickened/specialized) bytecode + let bytes = unsafe { + core::slice::from_raw_parts( + self.code.instructions.as_ptr() as *const u8, + self.code.instructions.len() * 2, + ) }; - let instructions = &self.code.instructions; - if canonical.len() != instructions.unit_count() { - return Err(incompatible()); - } - let mut bytes = Vec::with_capacity(self.canonical_code.len()); - let mut index = 0; - while index < canonical.len() { - let base = Instruction::try_from(canonical[index][0]).map_err(|_| incompatible())?; - if matches!(base, Instruction::GetYieldFromIter) { - return Err(legacy()); - } - let next = index - .checked_add(1 + base.cache_entries()) - .filter(|&next| next <= canonical.len()) - .ok_or_else(incompatible)?; - // Boundaries come only from this code's immutable canonical backing. - // Never decode a cache payload or use a live monitoring wrapper's width. - let op = match instructions.read_op(index) { - op @ (Instruction::LoadConstImmortal | Instruction::LoadConstMortal) => { - op.deoptimize() - } - Instruction::GetYieldFromIter => return Err(legacy()), - op => op, - }; - bytes.push(u8::from(op)); - bytes.push(u8::from(instructions.read_arg(index))); - for cache in index + 1..next { - bytes.extend_from_slice(&instructions.read_cache_u16(cache).to_ne_bytes()); - } - index = next; - } - // Per-cell atomic observation, not a coherent cross-thread snapshot. - // This does not repair the interpreter's other live-storage access paths. - Ok(vm.ctx.new_bytes(bytes)) + vm.ctx.new_bytes(bytes.to_vec()) } #[pygetset] pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef { - self.freevars_tuple + self.co_cached + .freevars .get_or_init(|| { vm.ctx.new_tuple( self.code @@ -1280,12 +1271,12 @@ impl Py { #[pygetset] pub fn co_linetable(&self) -> PyBytesRef { - self.linetable_bytes.clone() + self.co_linetable.clone() } #[pygetset] pub fn co_exceptiontable(&self) -> PyBytesRef { - self.exceptiontable_bytes.clone() + self.co_exceptiontable.clone() } // spell-checker: ignore lnotab @@ -1617,23 +1608,17 @@ impl Py { let constants = match co_consts { OptionalArg::Present(constants) => constants, - OptionalArg::Missing => self - .code - .constants - .tuple_owner() - .expect("finalized constants") - .0 - .clone(), + OptionalArg::Missing => PyCode::co_consts(self).clone(), }; let obj_name = match co_name { OptionalArg::Present(obj_name) => obj_name, - OptionalArg::Missing => self.co_name(), + OptionalArg::Missing => self.co_name.clone(), }; let names = match co_names { OptionalArg::Present(names) => names, - OptionalArg::Missing => self.names_tuple.clone(), + OptionalArg::Missing => self.co_names.clone(), }; let flags = CodeFlags::from_bits_truncate(match co_flags { @@ -1664,9 +1649,7 @@ impl Py { CodeUnits::try_from(code_bytes.as_bytes()) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))? } - // Live instructions may contain monitoring wrappers whose original - // opcodes exist only in the source code object's side tables. - OptionalArg::Missing => CodeUnits::try_from(self.canonical_code.as_ref()) + OptionalArg::Missing => CodeUnits::try_from(self.get_code(vm).as_bytes()) .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?, }; @@ -1723,12 +1706,12 @@ impl Py { // Handle linetable and exceptiontable let linetable = match co_linetable { OptionalArg::Present(linetable) => linetable, - OptionalArg::Missing => self.linetable_bytes.clone(), + OptionalArg::Missing => self.co_linetable.clone(), }; let exceptiontable = match co_exceptiontable { OptionalArg::Present(exceptiontable) => exceptiontable, - OptionalArg::Missing => self.exceptiontable_bytes.clone(), + OptionalArg::Missing => self.co_exceptiontable.clone(), }; let new_code = CodeObject { @@ -1746,7 +1729,7 @@ impl Py { // FIXME: invalid locations. Actually locations is a duplication of linetable. // It can be removed once we move every other code to use linetable only. locations: self.code.locations.clone(), - constants: bytecode::Constants::from_tuple(LiteralTupleOwner(constants)), + constants: bytecode::Constants::new(LiteralTuple(constants)), names: intern_all(names.as_slice().to_vec(), "co_names")?, varnames, cellvars, @@ -1756,12 +1739,12 @@ impl Py { exceptiontable: exceptiontable.as_bytes().into(), }; - let mut result = PyCode::new(new_code, &vm.ctx); - result.retain_name_subclass(obj_name, &vm.ctx); - result.retain_normalized_names(names, None); - result.linetable_bytes = linetable; - result.exceptiontable_bytes = exceptiontable; - Ok(result) + let mut code = PyCode::new(new_code, &vm.ctx); + code.co_name = obj_name; + code.set_names(names); + code.co_linetable = linetable; + code.co_exceptiontable = exceptiontable; + Ok(code) } #[pymethod] @@ -1883,53 +1866,3 @@ impl<'a> LineTableReader<'a> { pub(crate) fn init(ctx: &'static Context) { PyCode::extend_class(ctx, ctx.types.code_type); } - -#[cfg(test)] -mod storage_tests { - use super::*; - - #[test] - fn tuple_constants_share_owner_and_copy_on_write() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let child: PyObjectRef = vm.ctx.new_list(vec![]).into(); - assert_eq!(child.strong_count(), 1); - let owner = vm.ctx.new_tuple(vec![child.clone()]); - let mut constants = bytecode::Constants::from_tuple(LiteralTupleOwner(owner.clone())); - assert_eq!(child.strong_count(), 2); - let shared = constants.clone(); - assert_eq!(child.strong_count(), 2); - constants[bytecode::oparg::ConstIdx::from_u32(0)] = Literal(vm.ctx.none()); - assert!(constants.tuple_owner().is_none()); - assert!(shared[bytecode::oparg::ConstIdx::from_u32(0)].0.is(&child)); - assert!(owner.as_slice()[0].is(&child)); - assert_eq!(child.strong_count(), 2); - drop(shared); - drop(owner); - assert_eq!(child.strong_count(), 1); - }); - } - - #[cfg(feature = "compiler")] - #[test] - fn published_code_has_one_constant_owner_representation() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let compiled = vm - .compile("pass", crate::compiler::Mode::Exec, "code_storage.py") - .unwrap(); - let child: PyObjectRef = vm.ctx.new_list(vec![]).into(); - let mut raw = compiled.code.clone(); - raw.constants = core::iter::once(Literal(child.clone())).collect(); - assert_eq!(child.strong_count(), 2); - let code = PyCode::new(raw, &vm.ctx); - assert_eq!(child.strong_count(), 2); - let owner = code.code.constants.tuple_owner().unwrap().0.clone(); - let cloned = code.code.clone(); - assert_eq!(child.strong_count(), 2); - drop(code); - drop(cloned); - assert_eq!(child.strong_count(), 2); - drop(owner); - assert_eq!(child.strong_count(), 1); - }); - } -} diff --git a/crates/vm/src/builtins/code/compile_cache.rs b/crates/vm/src/builtins/code/compile_cache.rs deleted file mode 100644 index 6ea516eff2d..00000000000 --- a/crates/vm/src/builtins/code/compile_cache.rs +++ /dev/null @@ -1,538 +0,0 @@ -//! Compilation-private sharing of supported constants and surviving assembly fields. -//! -//! This sees the optimized Basic graph, not CPython's full first-winner chronology. -//! Code/NaN keys and whole slice subtrees deliberately remain unmerged. The pool is -//! never used by public readers, frozen decoding, constructors or replace(). - -use super::*; -use alloc::collections::BTreeMap; -use core::cell::RefCell; - -// Keys contain no Python owners. Bit keys preserve each signed-zero combination; -// NaNs are excluded because their identity cannot be represented by their bits. -#[derive(Eq, PartialEq, Ord, PartialOrd)] -enum Key { - None, - Ellipsis, - Bool(bool), - Int(BigInt), - Float(u64), - Complex(u64, u64), - Str(Vec), - Bytes(Vec), - Tuple(Vec), - FrozenSet(Vec), -} - -fn key(obj: &PyObject) -> Option { - use crate::builtins::{PyBool, PyBytes, PyComplex, PyEllipsis, PyFloat, PyInt, PyStr, PyTuple}; - if obj - .class() - .is(super::super::singletons::PyNone::static_type()) - { - Some(Key::None) - } else if obj.class().is(PyEllipsis::static_type()) { - Some(Key::Ellipsis) - } else if obj.class().is(PyBool::static_type()) { - Some(Key::Bool( - !obj.downcast_ref::()?.as_bigint().is_zero(), - )) - } else if obj.class().is(PyInt::static_type()) { - Some(Key::Int(obj.downcast_ref::()?.as_bigint().clone())) - } else if obj.class().is(PyFloat::static_type()) { - let value = obj.downcast_ref::()?.to_f64(); - (!value.is_nan()).then(|| Key::Float(value.to_bits())) - } else if obj.class().is(PyComplex::static_type()) { - let value = obj.downcast_ref::()?.as_complex(); - (!value.re.is_nan() && !value.im.is_nan()) - .then(|| Key::Complex(value.re.to_bits(), value.im.to_bits())) - } else if obj.class().is(PyStr::static_type()) { - Some(Key::Str( - obj.downcast_ref::()?.as_wtf8().as_bytes().to_vec(), - )) - } else if obj.class().is(PyBytes::static_type()) { - Some(Key::Bytes( - obj.downcast_ref::()?.as_bytes().to_vec(), - )) - } else if obj.class().is(PyTuple::static_type()) { - obj.downcast_ref::()? - .iter() - .map(|child| key(child)) - .collect::>>() - .map(Key::Tuple) - } else if obj.class().is(PyFrozenSet::static_type()) { - // Actual survivors matter: Python set equality can collapse unlike types. - // This scoped snapshot owns children only while producing Rust-only keys. - // Never use Literal::borrow_constant: its frozenset/slice adapters leak. - let elements = obj.downcast_ref::()?.elements(); - let mut keys = elements - .iter() - .map(|child| key(child)) - .collect::>>()?; - keys.sort_unstable(); - drop(elements); - Some(Key::FrozenSet(keys)) - } else { - None - } -} - -type Pool = RefCell>; - -#[derive(Clone, Copy)] -struct CompileBag<'a> { - vm: &'a VirtualMachine, - pool: &'a Pool, -} - -impl CompileBag<'_> { - fn share(&self, candidate: PyObjectRef) -> PyObjectRef { - let Some(key) = key(&candidate) else { - return candidate; - }; - let winner = { self.pool.borrow().get(&key).cloned() }; - if let Some(winner) = winner { - // No RefCell guard remains when the candidate is released. - winner - } else { - self.pool.borrow_mut().insert(key, candidate.clone()); - candidate - } - } - - fn finalize(&self, code: CodeObject) -> PyCode { - let mut result = PyCode::new(code, &self.vm.ctx); - // Pinned assemble.c surviving-field order. Input bytecode is not the - // separately cached exported co_code and must not be pooled here. - result.exceptiontable_bytes = self - .share(result.exceptiontable_bytes.into()) - .downcast() - .unwrap(); - result.linetable_bytes = self - .share(result.linetable_bytes.into()) - .downcast() - .unwrap(); - let names = self - .share(result.names_tuple.clone().into()) - .downcast() - .unwrap(); - result.retain_normalized_names(names, None); - let constants = result - .code - .constants - .tuple_owner() - .expect("finalized constants") - .0 - .clone(); - let constants = self.share(constants.into()).downcast().unwrap(); - result.code.constants = bytecode::Constants::from_tuple(LiteralTupleOwner(constants)); - let locals = self - .share(result.localsplus_names_tuple.clone().into()) - .downcast() - .unwrap(); - // An equal-text pool winner is not necessarily normalized. Preserve the - // safe copy fallback rather than changing published tuple elements. - let names = result.names_tuple.clone(); - result.retain_normalized_names(names, Some(locals)); - result - } - - fn wrap(&self, code: CodeObject) -> PyRef { - PyRef::new_ref( - self.finalize(code), - self.vm.ctx.types.code_type.to_owned(), - None, - ) - } -} - -impl ConstantBag for CompileBag<'_> { - type Constant = Literal; - - fn make_constant(&self, constant: BorrowedConstant<'_, C>) -> Literal { - let obj = match constant { - BorrowedConstant::Code { code } => { - return Literal(self.wrap(code.map_clone_bag(self)).into()); - } - // Delegate the entire subtree, including children, without caching. - BorrowedConstant::Slice { .. } => return PyVmBag(self.vm).make_constant(constant), - BorrowedConstant::Tuple { elements } => self - .vm - .ctx - .new_tuple( - elements - .iter() - .map(|child| self.make_constant(child.borrow_constant()).0) - .collect(), - ) - .into(), - BorrowedConstant::Frozenset { elements } => PyFrozenSet::from_iter( - self.vm, - elements - .iter() - .map(|child| self.make_constant(child.borrow_constant()).0), - ) - .unwrap() - .into_ref(&self.vm.ctx) - .into(), - _ => PyVmBag(self.vm).make_constant(constant).0, - }; - Literal(self.share(obj)) - } - - fn make_name(&self, name: &str) -> &'static PyStrInterned { - self.vm.ctx.intern_str(name) - } - fn make_int(&self, value: BigInt) -> Literal { - Literal(self.share(self.vm.ctx.new_int(value).into())) - } - fn make_tuple(&self, elements: impl Iterator) -> Literal { - Literal( - self.share( - self.vm - .ctx - .new_tuple(elements.map(|value| value.0).collect()) - .into(), - ), - ) - } - fn make_code(&self, code: CodeObject) -> Literal { - Literal(self.wrap(code).into()) - } -} - -pub(super) fn into_pycode(code: bytecode::CodeObject, vm: &VirtualMachine) -> PyCode { - let pool = Pool::default(); - let bag = CompileBag { vm, pool: &pool }; - let result = bag.finalize(code.map_bag(bag)); - // Nested PyRefs are still reachable only through this private result graph. - // RAII also drops the pool on unwind; no cache owners escape publication. - drop(pool); - result -} - -pub(super) fn into_code_object(code: bytecode::CodeObject, vm: &VirtualMachine) -> CodeObject { - let pool = Pool::default(); - let result = code.map_bag(CompileBag { vm, pool: &pool }); - drop(pool); - result -} - -#[cfg(test)] -mod tests { - use super::*; - use bytecode::ConstantData as Data; - - #[test] - fn compilation_keys_preserve_types_zeros_and_exclusions() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let pool = Pool::default(); - let bag = CompileBag { vm, pool: &pool }; - let data = [ - Data::Boolean { value: false }, - Data::Integer { value: 0.into() }, - Data::Float { value: 0.0 }, - Data::Float { value: -0.0 }, - Data::Complex { - value: num_complex::Complex64::new(0.0, 0.0), - }, - Data::Complex { - value: num_complex::Complex64::new(-0.0, 0.0), - }, - Data::Complex { - value: num_complex::Complex64::new(0.0, -0.0), - }, - Data::Complex { - value: num_complex::Complex64::new(-0.0, -0.0), - }, - ]; - let values: Vec<_> = data - .iter() - .map(|value| bag.make_constant(value.borrow_constant())) - .collect(); - for (index, value) in values.iter().enumerate() { - let repeated = bag.make_constant(data[index].borrow_constant()); - assert!(value.0.is(&repeated.0)); - for other in &values[..index] { - assert!(!value.0.is(&other.0)); - } - } - let nan = Data::Tuple { - elements: vec![Data::Float { value: f64::NAN }], - }; - let first = bag.make_constant(nan.borrow_constant()); - let second = bag.make_constant(nan.borrow_constant()); - assert!(key(&first.0).is_none()); - assert!(!first.0.is(&second.0)); - for scalar in [ - Data::Complex { - value: num_complex::Complex64::new(f64::NAN, 1.0), - }, - Data::Complex { - value: num_complex::Complex64::new(1.0, f64::NAN), - }, - ] { - let aggregate = Data::Tuple { - elements: vec![ - scalar, - Data::Bytes { - value: b"safe sibling next to NaN".to_vec(), - }, - ], - }; - let first = bag.make_constant(aggregate.borrow_constant()); - let second = bag.make_constant(aggregate.borrow_constant()); - assert!(key(&first.0).is_none()); - assert!(!first.0.is(&second.0)); - let first = first.0.downcast_ref::().unwrap(); - let second = second.0.downcast_ref::().unwrap(); - assert!(!first.as_slice()[0].is(&second.as_slice()[0])); - assert!(first.as_slice()[1].is(&second.as_slice()[1])); - } - let infinities = [ - Data::Float { - value: f64::INFINITY, - }, - Data::Float { - value: f64::NEG_INFINITY, - }, - Data::Complex { - value: num_complex::Complex64::new(f64::INFINITY, f64::NEG_INFINITY), - }, - Data::Complex { - value: num_complex::Complex64::new(f64::NEG_INFINITY, f64::INFINITY), - }, - ]; - let mut infinite_values: Vec = Vec::new(); - for value in infinities { - let first = bag.make_constant(value.borrow_constant()); - let second = bag.make_constant(value.borrow_constant()); - assert!(key(&first.0).is_some()); - assert!(first.0.is(&second.0)); - assert!(infinite_values.iter().all(|other| !first.0.is(&other.0))); - infinite_values.push(first); - } - let bytes = || Data::Bytes { - value: b"uncached slice child".to_vec(), - }; - let slice = Data::Slice { - elements: Box::new([bytes(), Data::None, Data::None]), - }; - let first = bag.make_constant(slice.borrow_constant()); - let second = bag.make_constant(slice.borrow_constant()); - assert!(key(&first.0).is_none()); - let first = first.0.downcast_ref::().unwrap(); - let second = second.0.downcast_ref::().unwrap(); - assert!( - !first - .start - .as_ref() - .unwrap() - .is(second.start.as_ref().unwrap()) - ); - let cached = bag.make_constant(bytes().borrow_constant()); - assert!(!first.start.as_ref().unwrap().is(&cached.0)); - }); - } - - #[test] - fn compilation_frozenset_keys_use_survivors_and_release_pool_owners() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let pool = Pool::default(); - let bag = CompileBag { vm, pool: &pool }; - let set = |elements| Data::Frozenset { elements }; - let boolean_first = set(vec![ - Data::Boolean { value: true }, - Data::Integer { value: 1.into() }, - ]); - let integer_first = set(vec![ - Data::Integer { value: 1.into() }, - Data::Boolean { value: true }, - ]); - let first = bag.make_constant(boolean_first.borrow_constant()); - let second = bag.make_constant(integer_first.borrow_constant()); - assert!(!first.0.is(&second.0)); - assert!( - first.0.downcast_ref::().unwrap().elements()[0] - .class() - .is(crate::builtins::PyBool::static_type()) - ); - assert!( - second.0.downcast_ref::().unwrap().elements()[0] - .class() - .is(crate::builtins::PyInt::static_type()) - ); - let forward = set(vec![ - Data::Bytes { - value: b"first ordinary element".to_vec(), - }, - Data::Bytes { - value: b"second ordinary element".to_vec(), - }, - ]); - let reverse = set(vec![ - Data::Bytes { - value: b"second ordinary element".to_vec(), - }, - Data::Bytes { - value: b"first ordinary element".to_vec(), - }, - ]); - let forward = bag.make_constant(forward.borrow_constant()); - let reverse = bag.make_constant(reverse.borrow_constant()); - assert!(forward.0.is(&reverse.0)); - let snapshot = forward.0.downcast_ref::().unwrap().elements(); - let counts: Vec<_> = snapshot.iter().map(|value| value.strong_count()).collect(); - for _ in 0..3 { - assert!(key(&forward.0).is_some()); - assert_eq!( - counts, - snapshot - .iter() - .map(|value| value.strong_count()) - .collect::>() - ); - } - drop(snapshot); - let data = Data::Tuple { - elements: vec![Data::Bytes { - value: b"private pool lifetime".to_vec(), - }], - }; - let value = bag.make_constant(data.borrow_constant()); - let again = bag.make_constant(data.borrow_constant()); - assert!(value.0.is(&again.0)); - drop(again); - assert_eq!(value.0.strong_count(), 2); - drop(pool); - assert_eq!(value.0.strong_count(), 1); - let child = &value - .0 - .downcast_ref::() - .unwrap() - .as_slice()[0]; - assert_eq!(child.strong_count(), 1); - }); - } - - #[cfg(feature = "compiler")] - #[test] - fn basic_route_finalizes_fields_before_publication() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let basic = crate::compiler::compile( - "def left(x):\n return x + 12345678901234567890\ndef right(x):\n return x + 12345678901234567890\n", - crate::compiler::Mode::Exec, "compile_cache.py", vm.compile_opts(), - ).unwrap(); - let root = vm.new_code(basic); - let nested: Vec<_> = root.code.constants.iter() - .filter_map(|value| value.0.downcast_ref::()).collect(); - assert_eq!(nested.len(), 2); - let [left, right] = [nested[0], nested[1]]; - assert!(!left.is(right)); - assert!(left.code.constants.tuple_owner().unwrap().0.is(&right.code.constants.tuple_owner().unwrap().0)); - assert!(left.linetable_bytes.is(&right.linetable_bytes)); - assert!(left.exceptiontable_bytes.is(&right.exceptiontable_bytes)); - assert!(left.names_tuple.is(&right.names_tuple)); - assert!(left.localsplus_names_tuple.is(&right.localsplus_names_tuple)); - assert_eq!(left.code.constants.tuple_owner().unwrap().0.as_object().strong_count(), 2); - assert!(left.code_bytes.get().is_none()); - assert!(right.code_bytes.get().is_none()); - assert!(!left.co_code(vm).is(&right.co_code(vm))); - }); - } - #[cfg(feature = "compiler")] - #[test] - fn assembly_fields_share_literals_but_default_routes_do_not_pool() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let mut basic = crate::compiler::compile( - "try:\n value = target\nexcept TypeError:\n value = None\n", - crate::compiler::Mode::Exec, - "compile_cache.py", - vm.compile_opts(), - ) - .unwrap(); - assert!(!basic.linetable.is_empty()); - assert!(!basic.exceptiontable.is_empty()); - basic.constants = vec![ - Data::Bytes { - value: basic.linetable.to_vec(), - }, - Data::Bytes { - value: basic.exceptiontable.to_vec(), - }, - ] - .into_iter() - .collect(); - let frozen = frozen::FrozenCodeObject::encode(&basic); - let raw = basic.clone().map_bag(PyVmBag(vm)); - let literal_default = vm.new_code(raw); - let frozen_default = vm.new_code(frozen); - let pooled = vm.new_code(basic); - for (index, field) in [&pooled.linetable_bytes, &pooled.exceptiontable_bytes] - .into_iter() - .enumerate() - { - assert!(pooled.code.constants.get(index).unwrap().0.is(field)); - assert_eq!(field.as_object().strong_count(), 2); // One tuple edge and one field edge. - } - assert!( - !literal_default - .code - .constants - .first() - .unwrap() - .0 - .is(&literal_default.linetable_bytes) - ); - assert!( - !frozen_default - .code - .constants - .first() - .unwrap() - .0 - .is(&frozen_default.linetable_bytes) - ); - let tuple = vm.ctx.new_tuple(vec![pooled.to_owned().into()]); - assert!(key(pooled.as_object()).is_none()); - assert!(key(tuple.as_object()).is_none()); - }); - } - - #[cfg(feature = "compiler")] - #[test] - fn noncanonical_name_winner_is_not_published() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let basic = crate::compiler::compile( - "result = target", - crate::compiler::Mode::Exec, - "compile_cache.py", - vm.compile_opts(), - ) - .unwrap(); - let raw = basic.map_bag(PyVmBag(vm)); - let noncanonical = vm.ctx.new_tuple( - raw.names - .iter() - .map(|name| vm.ctx.new_str(name.as_str()).into()) - .collect(), - ); - assert!(!noncanonical.is_empty()); - for (value, canonical) in noncanonical.iter().zip(raw.names.iter()) { - assert!(!value.is(*canonical)); - } - let pool = Pool::default(); - let bag = CompileBag { vm, pool: &pool }; - let winner = bag.share(noncanonical.clone().into()); - assert!(winner.is(&noncanonical)); - drop(winner); - let result = bag.finalize(raw); - assert!(!result.names_tuple.is(&noncanonical)); - for (value, canonical) in result.names_tuple.iter().zip(result.code.names.iter()) { - assert!(value.is(*canonical)); - } - drop(pool); - assert_eq!(noncanonical.as_object().strong_count(), 1); - }); - } -} diff --git a/crates/vm/src/builtins/code/const_cache.rs b/crates/vm/src/builtins/code/const_cache.rs new file mode 100644 index 00000000000..026dfda13bd --- /dev/null +++ b/crates/vm/src/builtins/code/const_cache.rs @@ -0,0 +1,147 @@ +//! The compiler's constant cache (`c_const_cache`). +//! +//! Equal constants and code fields produced by one compilation share a single +//! object, merged under their `_PyCode_ConstantKey`. + +use super::*; +use crate::builtins::{PyDict, PyDictRef, PyTuple}; + +/// `const_cache_insert`. Items of a tuple or frozenset constant are created +/// through the cache before the container, so a single non-recursive insert +/// also merges them. +fn const_cache_insert( + const_cache: &Py, + o: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + // None and Ellipsis are singletons, and key is the singleton. + if vm.is_none(&o) || o.is(&vm.ctx.ellipsis) { + return Ok(o); + } + let key = constant_key(&o, vm)?; + let key = match const_cache.get_item_opt(&*key, vm)? { + Some(existing) => existing, + None => { + const_cache.set_item(&*key, key.clone(), vm)?; + key + } + }; + // See constant_key() + Ok(match key.downcast_ref_if_exact::(vm) { + Some(key) => key.as_slice()[1].clone(), + None => key, + }) +} + +/// A [`ConstantBag`] creating the constants of one compilation through its +/// constant cache. +#[derive(Clone, Copy)] +struct CompileBag<'a> { + vm: &'a VirtualMachine, + const_cache: &'a Py, +} + +impl CompileBag<'_> { + /// `_PyCompile_ConstCacheMergeOne` + fn merge_one(&self, obj: PyRef) -> PyRef { + self.merge(obj.into()) + .downcast() + .expect("an equal constant key implies the same type") + } + + fn merge(&self, obj: PyObjectRef) -> PyObjectRef { + const_cache_insert(self.const_cache, obj, self.vm) + .expect("constant keys are hashable and comparable") + } + + /// Create a code object whose fields are merged the way `assemble.c` + /// merges them, in the same order. + fn new_code(&self, code: CodeObject) -> PyCode { + let mut code = PyCode::new(code, &self.vm.ctx); + code.co_exceptiontable = self.merge_one(code.co_exceptiontable); + code.co_linetable = self.merge_one(code.co_linetable); + code.co_names = self.merge_one(code.co_names); + let consts = self.merge_one(code.code.constants.as_tuple().as_pytuple().clone()); + code.code.constants = bytecode::Constants::new(LiteralTuple(consts)); + code.co_localsplusnames = self.merge_one(code.co_localsplusnames); + code + } +} + +impl ConstantBag for CompileBag<'_> { + type Constant = Literal; + + fn make_constant(&self, constant: BorrowedConstant<'_, C>) -> Literal { + let vm = self.vm; + let obj = match constant { + BorrowedConstant::Code { code } => return self.make_code(code.map_clone_bag(self)), + BorrowedConstant::Tuple { elements } => vm + .ctx + .new_tuple( + elements + .iter() + .map(|item| self.make_constant(item.borrow_constant()).0) + .collect(), + ) + .into(), + BorrowedConstant::Frozenset { elements } => PyFrozenSet::from_iter( + vm, + elements + .iter() + .map(|item| self.make_constant(item.borrow_constant()).0), + ) + .unwrap() + .into_ref(&vm.ctx) + .into(), + BorrowedConstant::Slice { elements } => { + let [start, stop, step] = elements + .each_ref() + .map(|item| self.make_constant(item.borrow_constant()).0); + crate::builtins::PySlice { + start: Some(start), + stop, + step: Some(step), + } + .into_ref(&vm.ctx) + .into() + } + _ => PyVmBag(vm).make_constant(constant).0, + }; + Literal(self.merge(obj)) + } + + fn make_name(&self, name: &str) -> &'static PyStrInterned { + self.vm.ctx.intern_str(name) + } + + fn make_int(&self, value: BigInt) -> Literal { + Literal(self.merge(self.vm.ctx.new_int(value).into())) + } + + fn make_tuple(&self, elements: impl Iterator) -> Literal { + let tuple = self.vm.ctx.new_tuple(elements.map(|item| item.0).collect()); + Literal(self.merge(tuple.into())) + } + + fn make_code(&self, code: CodeObject) -> Literal { + let code = self.new_code(code).into_ref(&self.vm.ctx); + Literal(self.merge(code.into())) + } +} + +pub(super) fn into_code_object(code: bytecode::CodeObject, vm: &VirtualMachine) -> CodeObject { + let const_cache: PyDictRef = vm.ctx.new_dict(); + code.map_bag(CompileBag { + vm, + const_cache: &const_cache, + }) +} + +pub(super) fn into_pycode(code: bytecode::CodeObject, vm: &VirtualMachine) -> PyCode { + let const_cache: PyDictRef = vm.ctx.new_dict(); + let bag = CompileBag { + vm, + const_cache: &const_cache, + }; + bag.new_code(code.map_bag(bag)) +} diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 682611892e1..61d173c8b3a 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -256,18 +256,18 @@ pub(crate) mod stack_analysis { } } Instruction::ForIter { .. } => { - // Fall-through (iteration continues): pushes the next value - let body_stack = push_value(next_stack, Kind::Object as i64); + // Both the body and the END_FOR target start with the + // next value pushed. + let target_stack = push_value(next_stack, Kind::Object as i64); if next_i < stacks.len() { - stacks[next_i] = body_stack; + stacks[next_i] = target_stack; } - // CPython models the target END_FOR with the yielded-value slot. let j = next_i + oparg as usize; - if j < stacks.len() && stacks[j] == UNINITIALIZED { - stacks[j] = body_stack; + if j < stacks.len() { + debug_assert!(stacks[j] == UNINITIALIZED || stacks[j] == target_stack); + stacks[j] = target_stack; } } - Instruction::EndAsyncFor => { next_stack = pop_value(pop_value(next_stack)); if next_i < stacks.len() { diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index ae359250b6c..b2cd5f443cb 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -42,6 +42,37 @@ impl FormatArgument<'_> { } } +/// `FORMAT_ERROR` for an argument of the wrong type. +fn wrong_type_error( + vm: &VirtualMachine, + spec: &CFormatSpec, + obj: &PyObject, + argument: FormatArgument<'_>, + required: &str, +) -> PyResult { + Ok(vm.new_type_error(format!( + "{}: %{} requires {required}, not {}", + argument.description(vm)?, + spec.format_type.to_char(), + obj.class().slot_name() + ))) +} + +fn format_float_arg( + vm: &VirtualMachine, + spec: &CFormatSpec, + obj: PyObjectRef, + argument: FormatArgument<'_>, +) -> PyResult { + match ArgIntoFloat::try_from_object(vm, obj.clone()) { + Ok(value) => Ok(value.into()), + Err(error) if error.fast_isinstance(vm.ctx.exceptions.type_error) => { + Err(wrong_type_error(vm, spec, &obj, argument, "a real number")?) + } + Err(error) => Err(error), + } +} + fn format_decimal_object( vm: &VirtualMachine, spec: &CFormatSpec, @@ -55,19 +86,30 @@ fn format_decimal_object( Some(Err(error)) if !error.fast_isinstance(vm.ctx.exceptions.type_error) => { return Err(error); } - _ => { - return Err(vm.new_type_error(format!( - "{}: %{} requires a real number, not {}", - argument.description(vm)?, - spec.format_type.to_char(), - obj.class().slot_name() - ))); - } + _ => return Err(wrong_type_error(vm, spec, obj, argument, "a real number")?), }; check_int_to_str_digits(i.as_bigint(), vm)?; Ok(spec.format_number(i.as_bigint())) } +fn unsupported_format_error( + ch: CodePoint, + index: usize, + format_start: usize, + vm: &VirtualMachine, +) -> PyBaseExceptionRef { + let c = ch.to_char_lossy(); + let msg = if c.is_ascii_alphabetic() { + format!("unsupported format %{c} at position {format_start}") + } else { + format!( + "unsupported format character '{c}' ({:#x}) at index {index}", + ch.to_u32() + ) + }; + vm.new_value_error(msg) +} + fn spec_format_bytes( vm: &VirtualMachine, spec: &CFormatSpec, @@ -76,16 +118,9 @@ fn spec_format_bytes( format_start: usize, ) -> PyResult> { match &spec.format_type { - CFormatType::Unsupported { ch, .. } if ch.to_char_lossy().is_ascii_alphabetic() => Err(vm - .new_value_error(format!( - "unsupported format %{} at position {format_start}", - ch.to_char_lossy(), - ))), - CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!( - "unsupported format character '{}' ({:#x}) at index {index}", - ch.to_char_lossy(), - ch.to_u32(), - ))), + CFormatType::Unsupported { ch, index } => { + Err(unsupported_format_error(*ch, *index, format_start, vm)) + } // Unlike strings, %r and %a are identical for bytes: the behaviour corresponds to // %a for strings (not %r) CFormatType::String(CFormatConversion::Repr | CFormatConversion::Ascii) => { @@ -138,26 +173,13 @@ fn spec_format_bytes( let i = int_result?; Ok(spec.format_number(i.as_bigint()).into_bytes()) } else { - Err(vm.new_type_error(format!( - "%{} format: an integer is required, not {}", - spec.format_type.to_char(), - obj.class().name() - ))) + Err(wrong_type_error(vm, spec, &obj, argument, "an integer")?) } } }, CFormatType::Float(_) => { - let class = obj.class().to_owned(); - let value = ArgIntoFloat::try_from_object(vm, obj).map_err(|e| { - if e.fast_isinstance(vm.ctx.exceptions.type_error) { - // formatfloat in bytesobject.c generates its own specific exception - // text in this case, mirror it here. - vm.new_type_error(format!("float argument required, not {}", class.name())) - } else { - e - } - })?; - Ok(spec.format_float(value.into()).into_bytes()) + let value = format_float_arg(vm, spec, obj, argument)?; + Ok(spec.format_float(value).into_bytes()) } CFormatType::Character(CCharacterType::Character) => { // CPython parity: bytes `%c` accepts a single byte or any object @@ -212,16 +234,9 @@ fn spec_format_string( format_start: usize, ) -> PyResult { match &spec.format_type { - CFormatType::Unsupported { ch, .. } if ch.to_char_lossy().is_ascii_alphabetic() => Err(vm - .new_value_error(format!( - "unsupported format %{} at position {format_start}", - ch.to_char_lossy(), - ))), - CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!( - "unsupported format character '{}' ({:#x}) at index {index}", - ch.to_char_lossy(), - ch.to_u32(), - ))), + CFormatType::Unsupported { ch, index } => { + Err(unsupported_format_error(*ch, *index, format_start, vm)) + } CFormatType::String(conversion) => { let result = match conversion { CFormatConversion::Ascii => builtins::ascii(obj, vm)?.as_wtf8().to_owned(), @@ -256,29 +271,13 @@ fn spec_format_string( let i = int_result?; Ok(spec.format_number(i.as_bigint()).into()) } else { - Err(vm.new_type_error(format!( - "{}: %{} requires an integer, not {}", - argument.description(vm)?, - spec.format_type.to_char(), - obj.class().slot_name() - ))) + Err(wrong_type_error(vm, spec, &obj, argument, "an integer")?) } } }, CFormatType::Float(_) => { - let value = match ArgIntoFloat::try_from_object(vm, obj.clone()) { - Ok(value) => value, - Err(error) if error.fast_isinstance(vm.ctx.exceptions.type_error) => { - return Err(vm.new_type_error(format!( - "{}: %{} requires a real number, not {}", - argument.description(vm)?, - spec.format_type.to_char(), - obj.class().slot_name() - ))); - } - Err(error) => return Err(error), - }; - Ok(spec.format_float(value.into()).into()) + let value = format_float_arg(vm, spec, obj, argument)?; + Ok(spec.format_float(value).into()) } CFormatType::Character(CCharacterType::Character) => { // CPython parity: `%c` accepts a single-char str or any object with diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 2e5c07c15a5..67530e28d1f 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -106,7 +106,8 @@ pub fn current_thread_frame_materialize(vm: &VirtualMachine) -> Option bool { + /// `_PyFrame_IsIncomplete`. A generator frame is complete even before its + /// first resume. Its owner is temporarily Thread while it executes, so the + /// owning generator's back-reference is checked too. + pub(crate) fn is_incomplete(&self) -> bool { if FrameOwner::from_i8(self.owner.load(atomic::Ordering::Acquire)) == FrameOwner::Generator || self.generator.load().is_some() { return false; } - let instructions = &self.code().instructions; - let lasti = self.lasti.load(Relaxed) as usize; - let mut index = 0; - while index < instructions.len() { - let unit = instructions.read_unit(index); - if matches!( - unit.op, - Instruction::Resume { .. } - | Instruction::ResumeCheck - | Instruction::InstrumentedResume - ) && u32::from(u8::from(unit.arg)) & ResumeContext::LOCATION_MASK - != ResumeLocation::AtGenExprStart.as_u32() - { - // lasti is one past the in-flight instruction. At RESUME - // itself, lasti == index + 1 and the frame is complete. - return lasti <= index; - } - // Prefixes before the first traceable RESUME are uninstrumented. - // Skip inline cache payloads rather than decoding them as opcodes. - index += 1 + unit.op.cache_entries(); - } - true + // lasti is one past the in-flight instruction. + (self.lasti.load(Relaxed) as usize) <= self.code().first_traceable } /// Get the last instruction index. @@ -3751,7 +3731,7 @@ impl ExecutingFrame<'_> { if !vm.use_tracing.get() { return Ok(()); } - let trace_is_set = if self.iframe().is_incomplete_for_trace() { + let trace_is_set = if self.iframe().is_incomplete() { // Legacy tracing resolves PyEval_GetFrame to the first complete // caller while sys.monitoring continues to use this code object. current_complete_frame_for_trace(vm).is_some_and(|frame| { @@ -4006,9 +3986,9 @@ impl ExecutingFrame<'_> { prev_op = op_byte; } if PREPARE_GENERATOR && matches!(op, Instruction::ReturnGenerator) { - // CPython copies the eager frame at RETURN_GENERATOR with its - // instruction pointer advanced to POP_TOP. Iterator pairs from - // generator expressions must survive the execution borrow too. + // The generator takes over the frame with its instruction + // pointer advanced to POP_TOP. The iterator pair a generator + // expression pushed must outlive this execution borrow. self.localsplus.promote_stack(); break Ok(None); } @@ -5084,36 +5064,19 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::GetIter { mode } => { - // Include/internal/pycore_opcode_utils.h GET_ITER_YIELD_FROM_*. - const GET_ITER_ORDINARY: u32 = 0; - const GET_ITER_YIELD_FROM: u32 = 1; - const GET_ITER_YIELD_FROM_NO_CHECK: u32 = 2; - const GET_ITER_YIELD_FROM_CORO_CHECK: u32 = 3; - + // _PyEval_GetIter let iterable = self.pop_value(); - let mode = mode.get(arg); - let iter = if mode != GET_ITER_ORDINARY - && iterable.class().is(vm.ctx.types.coroutine_type) - { - // Code construction resolves the compiler-only mode 1 - // using CO_COROUTINE / CO_ITERABLE_COROUTINE. - let reject_coroutine = match mode { - GET_ITER_YIELD_FROM_CORO_CHECK => true, - GET_ITER_YIELD_FROM_NO_CHECK => false, - GET_ITER_YIELD_FROM => !self.code.flags.intersects( - bytecode::CodeFlags::COROUTINE - | bytecode::CodeFlags::ITERABLE_COROUTINE, - ), - _ => false, - }; - if reject_coroutine { + let yield_from = mode.get(arg); + debug_assert_ne!(yield_from, bytecode::oparg::GET_ITER_YIELD_FROM); + let iter = if yield_from != 0 && iterable.class().is(vm.ctx.types.coroutine_type) { + if yield_from == bytecode::oparg::GET_ITER_YIELD_FROM_CORO_CHECK { return Err(vm.new_type_error( "cannot 'yield from' a coroutine object in a non-coroutine generator", )); } iterable } else { - PyIter::try_from_object(vm, iterable)?.into() + iterable.get_iter(vm)?.into() }; self.push_value(iter); self.push_null(); @@ -5275,18 +5238,6 @@ impl ExecutingFrame<'_> { } Instruction::LoadConst { consti } => { self.push_value(self.code.constants[consti.get(arg)].clone().into()); - // Mirror CPython's LOAD_CONST family transition. RustPython does - // not currently distinguish immortal constants at runtime. - let instr_idx = self.lasti() as usize - 1; - unsafe { - self.code - .instructions - .replace_op(instr_idx, Instruction::LoadConstMortal); - } - Ok(None) - } - Instruction::LoadConstMortal | Instruction::LoadConstImmortal => { - self.push_value(self.code.constants[u32::from(arg).into()].clone().into()); Ok(None) } Instruction::LoadCommonConstant { idx } => { diff --git a/crates/vm/src/stdlib/marshal.rs b/crates/vm/src/stdlib/marshal.rs index 1c70c964fd5..9d7d6458eec 100644 --- a/crates/vm/src/stdlib/marshal.rs +++ b/crates/vm/src/stdlib/marshal.rs @@ -124,7 +124,7 @@ mod decl { struct WriterRefEntry { idx: u32, - // Keep the address valid until this whole per-write table is dropped. + // The table holds a reference, as w_ref does, so the id stays unique. _owner: PyObjectRef, /// Set between `reserve` and `complete` for the object kinds whose /// immutable representation cannot be rebuilt from a back-reference. @@ -241,10 +241,9 @@ mod decl { || obj.is(PyStopIteration::static_type()) || obj.downcast_ref::().is_some(); - // Observe actual ownership before lookup/reservation, as w_ref does. - // This only selects a wire representation: it does not grant exclusive - // access, and concurrent ownership changes can change the observation. - // Interned exact strings stay reference-tracked even when unique. + // If it has only one reference, it definitely isn't shared. But + // TYPE_REF is always used for interned strings, to keep PYC files as + // stable as possible. let use_ref = refs.is_some() && !is_singleton && (obj.strong_count() != 1 @@ -404,25 +403,35 @@ mod decl { return Err(vm.new_value_error("marshalling code objects is disallowed")); } buf.write_u8(b'c'); - // Keep this real getter owner through every field and error path. - let bytecode = co.co_code(vm); - marshal::serialize_code_with_fields( - buf, - &co.code, - |buf, field| { - write_object_depth( - buf, - co.marshal_field(field, &bytecode), - refs, - version, - allow_code, - vm, - depth - 1, - )?; - Ok(true) - }, - |_, _| Err(vm.new_runtime_error("unhandled runtime code field")), - )?; + let code = &co.code; + for value in [ + code.arg_count, + code.posonlyarg_count, + code.kwonlyarg_count, + code.max_stackdepth, + code.flags.bits(), + ] { + buf.write_u32(value); + } + let co_code = co.get_code(vm); + let mut write_field = |buf: &mut Vec, field: &PyObject| { + write_object_depth(buf, field, refs, version, allow_code, vm, depth - 1) + }; + for field in [ + co_code.as_object(), + co.co_consts().as_object(), + co.co_names.as_object(), + co.co_localsplusnames.as_object(), + co.co_localspluskinds.as_object(), + co.source_path().as_object(), + co.co_name.as_object(), + code.qualname.as_object(), + ] { + write_field(buf, field)?; + } + buf.write_u32(code.first_line_number.map_or(0, |n| n.get() as u32)); + write_field(buf, co.co_linetable.as_object())?; + write_field(buf, co.co_exceptiontable.as_object())?; } else if let Some(sl) = obj.downcast_ref::() { if version < 5 { return Err(vm.new_value_error("unmarshallable object")); @@ -618,21 +627,10 @@ mod decl { unsafe { tuple.payload.set_marshal_item(index, value) }; Ok(()) } - fn make_code(&self, code: CodeObject) -> Result { - if !self.allow_code { - return Err(self.remember_python_error( - self.vm - .new_value_error("unmarshalling code objects is disallowed"), - )); - } - Ok(crate::builtins::PyCode::new_ref_with_bag(self.vm, code).into()) - } - fn make_code_with_fields( + fn make_code( &self, code: CodeObject, - constants: Vec, - _code_bytes: Vec, - fields: marshal::CodeFieldObjects, + fields: marshal::CodeFields, ) -> Result { if !self.allow_code { return Err(self.remember_python_error( @@ -640,10 +638,7 @@ mod decl { .new_value_error("unmarshalling code objects is disallowed"), )); } - // The actual tuple in fields is the surviving owner. Discard the - // compatibility channel's temporary child references first. - drop(constants); - let code = PyCode::from_marshaled_fields(code, fields, &self.vm.ctx)?; + let code = PyCode::from_marshal(code, fields, &self.vm.ctx)?; Ok(code.into_ref(&self.vm.ctx).into()) } fn make_stop_iter(&self) -> Result { @@ -937,52 +932,4 @@ mod decl { } Ok(()) } - #[cfg(test)] - mod writer_tests { - use super::*; - - #[test] - fn reservation_owns_once_until_table_drop() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let object: PyObjectRef = vm.ctx.new_list(vec![]).into(); - let mut table = WriterRefTable::new(); - let mut bytes = Vec::new(); - assert_eq!(object.strong_count(), 1); - assert_eq!(table.try_ref(&mut bytes, &object), Ok(false)); - table.reserve(&object, false); - assert_eq!(object.strong_count(), 2); - assert_eq!(table.try_ref(&mut bytes, &object), Ok(true)); - assert_eq!(object.strong_count(), 2); - drop(table); - assert_eq!(object.strong_count(), 1); - }); - } - - #[cfg(feature = "compiler")] - #[test] - fn constants_tuple_consumes_its_own_depth() { - crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { - let code = vm - .compile("1234567", crate::compiler::Mode::Eval, "writer_depth.py") - .unwrap(); - assert_eq!(code.constants.len(), 1); - assert!(code.names.is_empty()); - assert!(code.varnames.is_empty()); - for (depth, succeeds) in [(2, false), (3, true)] { - let mut bytes = Vec::new(); - let mut refs = Some(WriterRefTable::new()); - let result = write_object_depth( - &mut bytes, - code.as_object(), - &mut refs, - 6, - true, - vm, - depth, - ); - assert_eq!(result.is_ok(), succeeds); - } - }); - } - } } diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index bb35df00016..28f1078625a 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -668,7 +668,7 @@ pub mod sys { const NAME: &str = "rustpython"; // Keep implementation-specific bytecode caches separate. - let cache_tag = version::PYC_CACHE_TAG; + let cache_tag = format!("{NAME}-{}{}", version::MAJOR, version::MINOR); let ctx = &vm.ctx; py_namespace!(vm, { "name" => ctx.new_str(NAME), diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index 7d1aa61d5f5..b6d265f9477 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -9,9 +9,7 @@ use std::collections::{HashMap, HashSet}; pub(crate) const TOOL_LIMIT: usize = 6; const EVENTS_COUNT: usize = 19; -const INSTRUMENTED_EVENTS_COUNT: usize = 11; -const LOCAL_EVENTS_COUNT: usize = 16; -const UNGROUPED_EVENTS_COUNT: usize = 18; +const UNGROUPED_EVENTS_COUNT: usize = 16; /// Event identifier (`PY_MONITORING_EVENT_*`), stored in `tstate->what_event`. /// `None` on the VM field is the `< 0` sentinel (not in a monitoring callback). @@ -205,6 +203,11 @@ fn parse_single_event(event: i32, vm: &VirtualMachine) -> PyResult { Ok(event_id) } +/// `PY_MONITORING_IS_INSTRUMENTED_EVENT` +const fn is_instrumented_event(event_id: usize) -> bool { + event_id <= MonitoringEvent::StopIteration as usize +} + fn normalize_event_set(event_set: i32, local: bool, vm: &VirtualMachine) -> PyResult { let kind = if local { "local event set" @@ -233,7 +236,7 @@ fn normalize_event_set(event_set: i32, local: bool, vm: &VirtualMachine) -> PyRe event_set |= MonitoringEvent::BranchLeft.mask() | MonitoringEvent::BranchRight.mask(); } - if local && event_set >= (1 << LOCAL_EVENTS_COUNT) { + if local && event_set >= (1 << UNGROUPED_EVENTS_COUNT) { return Err(vm.new_value_error(format!("invalid local event set 0x{event_set:x}"))); } @@ -282,23 +285,6 @@ fn opcode_event_is_active( event_for_opcode(op, oparg).is_some_and(|ev| events & ev.mask() != 0) } -/// Walk real instructions, skipping specialization CACHE payloads. -/// Cache slots may contain pointer bits that are not valid opcodes. -fn for_each_instruction( - code: &Py, - mut f: impl FnMut(usize, rustpython_compiler_core::bytecode::Instruction, u8), -) { - let len = code.code.instructions.len(); - let mut i = 0; - while i < len { - let op = code.code.instructions.read_op(i); - let oparg = code.code.instructions.read_arg(i).as_u8(); - let caches = op.deoptimize().cache_entries(); - f(i, op, oparg); - i += 1 + caches; - } -} - /// Rewrite a code object's bytecode in-place with layered instrumentation. /// /// Three layers (outermost first): @@ -403,23 +389,7 @@ pub(crate) fn instrument_code(code: &Py, events: u32) { data.line_opcodes.resize(len, 0); data.per_instruction_opcodes.resize(len, 0); - // Find _co_firsttraceable: index of first RESUME instruction - let mut first_traceable = None; - for_each_instruction(code, |i, op, oparg| { - if first_traceable.is_none() - && u32::from(oparg) & crate::bytecode::oparg::ResumeContext::LOCATION_MASK - != crate::bytecode::oparg::ResumeLocation::AtGenExprStart.as_u32() - && matches!( - op, - Instruction::Resume { .. } - | Instruction::ResumeCheck - | Instruction::InstrumentedResume - ) - { - first_traceable = Some(i); - } - }); - let first_traceable = first_traceable.unwrap_or(0); + let first_traceable = code.first_traceable; // Phase 4: Place regular INSTRUMENTED_* opcodes whose event is active. // Walk by cache_entries so specialized CACHE payloads are not decoded as opcodes. @@ -906,12 +876,11 @@ fn fire( for (tool, cb) in callbacks { let result = cb.call(args.clone(), vm)?; if disable_sentinel.as_ref().is_some_and(|d| result.is(d)) { - if event_id < INSTRUMENTED_EVENTS_COUNT { + if is_instrumented_event(event_id) { let mut state = vm.state.monitoring.lock(); state.disabled.insert((code_id, offset as usize, tool)); - } else if event_id < LOCAL_EVENTS_COUNT { - // Non-instrumented events disable their local registration - // for this code object, leaving global events active. + } else if event_id < UNGROUPED_EVENTS_COUNT { + // Other (non-instrumented) event: disable for this code object. let mut state = vm.state.monitoring.lock(); if let Some(events) = state.local_events.get_mut(&(tool, code_id)) { *events &= !event_mask; diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 7c24b23d8da..bd421f7ca6e 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -68,11 +68,9 @@ pub const RUSTPYTHON_VERSION: &str = const { } }; -// Private, non-distributable reconstruction ABI. This is not CPython's magic. -// Change the token for every incompatible development format. The public -// cache tag identifies the implementation/version; magic rejects stale ABIs. -pub const PYC_MAGIC_NUMBER: u16 = 0xDA16; -pub const PYC_CACHE_TAG: &str = concat!("rustpython-", env!("MAJOR_CPY"), env!("MINOR_CPY")); +// Must be aligned to Lib/importlib/_bootstrap_external.py +// Matches CPython 3.15 (Include/internal/pycore_magic_number.h). +pub const PYC_MAGIC_NUMBER: u16 = 3666; // CPython format: magic_number | ('\r' << 16) | ('\n' << 24) // This protects against text-mode file reads diff --git a/crates/vm/src/vm/crossinterp.rs b/crates/vm/src/vm/crossinterp.rs index 3cc93d4783b..70ea5104827 100644 --- a/crates/vm/src/vm/crossinterp.rs +++ b/crates/vm/src/vm/crossinterp.rs @@ -828,25 +828,31 @@ pub(crate) fn code_returns_only_none(code: &Py) -> bool { if !is_pure_function(code) { return false; } - // Final 3.15 bytecode loads None through LOAD_COMMON_CONSTANT, so it - // need not appear in co_consts. Walk decoded operands rather than looking - // one raw code unit back, which can lose EXTENDED_ARG bits. - let mut previous_loads_none = false; - for (_, op, arg) in walk_instructions(code) { - if matches!(op, Instruction::ExtendedArg) { + let units = &code.instructions; + // Look up None in co_consts. + let none_index = code + .constants + .iter() + .position(|c| matches!(c.borrow_constant(), BorrowedConstant::None)); + // We don't worry about EXTENDED_ARG for now. + for (at, op, _) in walk_instructions(code) { + if !matches!(op, Instruction::ReturnValue) { continue; } - if matches!(op, Instruction::ReturnValue) && !previous_loads_none { - return false; + debug_assert_ne!(at, 0); + let prev_op = units.read_op(at - 1).deoptimize(); + let prev_arg = u32::from(u8::from(units.read_arg(at - 1))); + if matches!(prev_op, Instruction::LoadCommonConstant { .. }) + && prev_arg == u32::from(CommonConstant::None) + { + continue; } - previous_loads_none = match op { - Instruction::LoadCommonConstant { idx } => idx.get(arg) == CommonConstant::None, - Instruction::LoadConst { consti } => code - .constants - .get(usize::from(consti.get(arg))) - .is_some_and(|value| matches!(value.borrow_constant(), BorrowedConstant::None)), - _ => false, - }; + if matches!(prev_op, Instruction::LoadConst { .. }) + && none_index.is_some_and(|none_index| prev_arg as usize == none_index) + { + continue; + } + return false; } true } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index abe37625fdc..4d003b8a268 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2690,8 +2690,9 @@ impl VirtualMachine { self.with_frame_impl::(frame, f) } - /// Generator construction runs an incomplete frame whose caller link must - /// not escape. RETURN_GENERATOR copies the frame with previous=NULL in CPython. + /// Run the incomplete frame of a generator being created. Its caller link + /// must not escape, as RETURN_GENERATOR copies the frame with + /// `previous = NULL`. pub(crate) fn with_generator_frame PyResult>( &self, frame: FrameObjectRef, @@ -3079,7 +3080,7 @@ impl VirtualMachine { if result.is_err() && self.use_tracing.get() && (!self.is_none(&self.profile_func.borrow()) - || if frame.iframe().is_incomplete_for_trace() { + || if frame.iframe().is_incomplete() { crate::frame::current_complete_frame_for_trace(self).is_some_and(|visible| { visible .iframe() diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index e99cb857472..d69d45780d3 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -100,14 +100,33 @@ impl WarningsState { /// None matches everything; plain strings do exact comparison; /// regex objects use .match(). -fn check_matched(obj: &PyObject, arg: &PyObject, vm: &VirtualMachine) -> PyResult { +fn check_matched( + obj: &PyObject, + arg: Option<&PyObject>, + arg2: Option<&Py>, + vm: &VirtualMachine, +) -> PyResult { + // A 'None' filter always matches if vm.is_none(obj) { return Ok(true); } + // An internal plain text default filter must match exactly if obj.class().is(vm.ctx.types.str_type) { - return obj.rich_compare_bool(arg, crate::types::PyComparisonOp::Eq, vm); + return match arg { + Some(arg) => obj.rich_compare_bool(arg, crate::types::PyComparisonOp::Eq, vm), + None => Ok(false), + }; } - let result = vm.call_method(obj, "match", (arg.to_owned(),))?; + // Otherwise assume a regex filter and call its match() method + let result = match arg { + Some(arg) => vm.call_method(obj, "match", (arg.to_owned(),))?, + None => { + let match_filename = vm + .import("_py_warnings", 0)? + .get_attr("_match_filename", vm)?; + match_filename.call((obj.to_owned(), arg2.map(|f| f.to_owned())), vm)? + } + }; result.is_true(vm) } @@ -336,30 +355,6 @@ fn update_registry( already_warned(registry, &altkey, true, vm) } -fn check_module_matched( - pattern: &PyObject, - module: Option<&PyObject>, - filename: &Py, - vm: &VirtualMachine, -) -> PyResult { - if let Some(module) = module { - return check_matched(pattern, module, vm); - } - if vm.is_none(pattern) { - return Ok(true); - } - if pattern.class().is(vm.ctx.types.str_type) { - return Ok(false); - } - let warnings = vm.import("_py_warnings", 0)?; - vm.call_method( - &warnings, - "_match_filename", - (pattern.to_owned(), filename.to_owned()), - )? - .is_true(vm) -} - /// Search a filters list for a matching action. #[allow(clippy::too_many_arguments)] fn filter_search( @@ -394,9 +389,9 @@ fn filter_search( action.class().name() ))); } - let good_msg = check_matched(&tmp_item.as_slice()[1], text, vm)?; + let good_msg = check_matched(&tmp_item.as_slice()[1], Some(text), None, vm)?; let is_subclass = category.is_subclass(&tmp_item.as_slice()[2], vm)?; - let good_mod = check_module_matched(&tmp_item.as_slice()[3], module, filename, vm)?; + let good_mod = check_matched(&tmp_item.as_slice()[3], module, Some(filename), vm)?; let ln: usize = tmp_item.as_slice()[4] .try_int(vm) .map_or(0, |v| v.as_u32_mask() as _); @@ -524,8 +519,10 @@ pub fn warn_explicit( source: Option, vm: &VirtualMachine, ) -> PyResult<()> { - // Explicit None remains a late-shutdown silent return. An omitted module - // is matched against the filename by the canonical warnings helper. + // module can be None if a warning is emitted late during Python shutdown. + // In this case, the Python warnings module was probably unloaded, filters + // are no more available to choose as action. It is safer to ignore the + // warning and do nothing. if module.as_ref().is_some_and(|module| vm.is_none(module)) { return Ok(()); } diff --git a/crates/vm/tests/fixtures/private_cache_bytes.rs b/crates/vm/tests/fixtures/private_cache_bytes.rs deleted file mode 100644 index 9865ebc85cf..00000000000 --- a/crates/vm/tests/fixtures/private_cache_bytes.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Fresh private devabi2 fixture: compile("pass\n", "byte_exact/embedded_cache.py", "exec"). -const PRIVATE_PYC: &[u8] = &[ - 22, 218, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 115, 8, 0, 0, 0, 128, 0, 0, 0, 80, 7, 33, 0, 40, 1, 0, 0, 0, 78, 40, 0, - 0, 0, 0, 40, 0, 0, 0, 0, 115, 0, 0, 0, 0, 122, 28, 98, 121, 116, 101, 95, 101, 120, 97, 99, - 116, 47, 101, 109, 98, 101, 100, 100, 101, 100, 95, 99, 97, 99, 104, 101, 46, 112, 121, 122, 8, - 60, 109, 111, 100, 117, 108, 101, 62, 122, 8, 60, 109, 111, 100, 117, 108, 101, 62, 1, 0, 0, 0, - 115, 8, 0, 0, 0, 241, 3, 1, 1, 1, 217, 0, 4, 115, 0, 0, 0, 0, -]; diff --git a/crates/vm/tests/private_cache_reader.rs b/crates/vm/tests/private_cache_reader.rs deleted file mode 100644 index 19a680a53ec..00000000000 --- a/crates/vm/tests/private_cache_reader.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Direct embedding API format boundary. Python behavior is tested in snippets. - -#![allow(clippy::tests_outside_test_module)] - -use rustpython_vm::{AsObject, Interpreter, builtins::PyCode}; - -include!("fixtures/private_cache_bytes.rs"); - -#[test] -fn foreign_formats_are_rejected_before_bootstrap_lookup() { - Interpreter::without_stdlib(Default::default()).enter(|vm| { - for magic in [3627u16, 3666, 0xDA15] { - let mut bytes = PRIVATE_PYC.to_vec(); - bytes[..2].copy_from_slice(&magic.to_le_bytes()); - let error = PyCode::from_pyc(&bytes, None, None, None, vm).unwrap_err(); - assert!(error.fast_isinstance(vm.ctx.exceptions.value_error)); - } - }); -} - -#[cfg(feature = "importlib")] -#[test] -fn private_format_loads_and_runs_with_external_bootstrap() { - Interpreter::without_stdlib(Default::default()).enter(|vm| { - assert_eq!( - &PRIVATE_PYC[..4], - &rustpython_vm::version::PYC_MAGIC_NUMBER_BYTES - ); - let code = PyCode::from_pyc(PRIVATE_PYC, None, None, None, vm).unwrap(); - assert_eq!(code.arg_count, 0); - let scope = vm.new_scope_with_main().unwrap(); - vm.run_pyc_bytes(PRIVATE_PYC, scope) - .unwrap_or_else(|error| { - vm.print_exception(&error); - panic!("private embedded pyc execution failed"); - }); - }); -} diff --git a/extra_tests/snippets/adaptive_public_export_315.py b/extra_tests/snippets/adaptive_public_export_315.py deleted file mode 100644 index 2c1856121b5..00000000000 --- a/extra_tests/snippets/adaptive_public_export_315.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Public adaptive bytes preserve layout without exposing private constant opcodes.""" - -import dis -import marshal -import opcode -import sys - -FILENAME = "adaptive_public_export.py" -CONSTANT_SOURCE = ( - 'def warm_const():\n return ("ordinary constant payload", 123456789)\n' -) -LOOP_SOURCE = "def total(n):\n result = 0\n for value in range(n):\n result += value\n return result\n" -PUBLIC = set(opcode.opmap.values()) | set(opcode._specialized_opmap.values()) - - -def make(source, name): - namespace = {} - exec(compile(source, FILENAME, "exec"), namespace) - return namespace[name] - - -def check_layout(code, canonical, offsets): - adaptive = code._co_code_adaptive - assert len(adaptive) == len(canonical) - for offset in offsets: - assert adaptive[offset] in PUBLIC, (offset, adaptive[offset]) - assert adaptive[offset] not in (219, 220, 221) - assert adaptive[offset + 1] == canonical[offset + 1] - assert code.co_code == canonical - return adaptive - - -function = make(CONSTANT_SOURCE, "warm_const") -code = function.__code__ -canonical = code.co_code -offsets = tuple(i.offset for i in dis.get_instructions(code)) -cold = check_layout(code, canonical, offsets) -constant_offset = next( - i.offset for i in dis.get_instructions(code) if i.opname == "LOAD_CONST" -) -assert cold[constant_offset] == opcode.opmap["LOAD_CONST"] -before = marshal.dumps(code, 6) -for _ in range(1000): - assert function() == ("ordinary constant payload", 123456789) -assert marshal.dumps(code, 6) == before -adaptive = check_layout(code, canonical, offsets) -constants = [ - i for i in dis.get_instructions(code, adaptive=True) if i.opname == "LOAD_CONST" -] -assert len(constants) == 1 -assert constants[0].argval == ("ordinary constant payload", 123456789) -assert adaptive[constants[0].offset] == opcode.opmap["LOAD_CONST"] - -loop = make(LOOP_SOURCE, "total") -code = loop.__code__ -canonical = code.co_code -offsets = tuple(i.offset for i in dis.get_instructions(code)) -for _ in range(1000): - assert loop(10) == 45 -first = check_layout(code, canonical, offsets) -second = check_layout(code, canonical, offsets) -assert first == second -assert any(i.cache_info for i in dis.get_instructions(code)) -rows = tuple(dis.get_instructions(code, adaptive=True)) -assert tuple(i.offset for i in rows) == offsets -assert any(i.opcode in opcode._specialized_opmap.values() for i in rows) -assert all(first[i.offset] == i.opcode for i in rows) - -monitoring = sys.monitoring -available = [tool for tool in range(6) if monitoring.get_tool(tool) is None] -assert available -TOOL = available[0] -monitoring.use_tool_id(TOOL, "adaptive-public-export") -try: - for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION): - function = make(LOOP_SOURCE, "total") - code = function.__code__ - canonical = code.co_code - offsets = tuple(i.offset for i in dis.get_instructions(code)) - observed = [] - - def inspect(actual_code, location): - assert actual_code is code - if not observed: - # First adaptive export for this code happens under active wrappers. - observed.append(check_layout(actual_code, canonical, offsets)) - assert marshal.loads(marshal.dumps(actual_code, 6)).co_code == canonical - - monitoring.register_callback(TOOL, event, inspect) - monitoring.set_local_events(TOOL, code, event) - try: - assert function(10) == 45 - assert len(observed) == 1 - finally: - monitoring.set_local_events(TOOL, code, 0) - monitoring.register_callback(TOOL, event, None) - check_layout(code, canonical, offsets) - assert function(5) == 10 -finally: - monitoring.set_events(TOOL, 0) - monitoring.free_tool_id(TOOL) diff --git a/extra_tests/snippets/canonical_code_storage_315.py b/extra_tests/snippets/canonical_code_storage_315.py deleted file mode 100644 index 71a9bd43a6a..00000000000 --- a/extra_tests/snippets/canonical_code_storage_315.py +++ /dev/null @@ -1,216 +0,0 @@ -"""Stable canonical code exports, including the first read during monitoring.""" - -import _frozen_importlib -import marshal -import sys -import types - -SOURCE = """def total(values): - result = 0 - for value in values: - result += value - return result -""" -FILENAME = "canonical_code_storage.py" - - -def make_function(): - namespace = {} - exec(compile(SOURCE, FILENAME, "exec"), namespace) - return namespace["total"] - - -reference = make_function() -expected = reference.__code__.co_code -assert reference.__code__.co_code is expected -assert reference.__code__.co_code == expected -for _ in range(100): - assert reference((1, 2, 3)) == 6 -assert reference.__code__.co_code is expected - -original = reference.__code__ -replaced = original.replace() -decoded = marshal.loads(marshal.dumps(original, 6)) -constructed = types.CodeType( - original.co_argcount, - original.co_posonlyargcount, - original.co_kwonlyargcount, - original.co_nlocals, - original.co_stacksize, - original.co_flags, - expected, - original.co_consts, - original.co_names, - original.co_varnames, - FILENAME, - original.co_name, - original.co_qualname, - original.co_firstlineno, - original.co_linetable, - original.co_exceptiontable, - original.co_freevars, - original.co_cellvars, -) -for code in (replaced, decoded, constructed): - first = code.co_code - assert first == expected - assert code.co_code is first - assert types.FunctionType(code, {})([2, 3, 4]) == 9 - assert code.co_code is first - -# Complete byte spellings from the pinned final 3.15 oracle for this ordinary -# yield-from fixture. Changing flags must normalize nonzero GET_ITER again. -delegate = compile( - "def delegate(values):\n yield from values\n", FILENAME, "exec" -).co_consts[0] -ordinary_delegate = bytes( - [ - 32, - 0, - 29, - 0, - 128, - 0, - 0, - 0, - 83, - 0, - 70, - 3, - 0, - 0, - 80, - 7, - 105, - 4, - 0, - 0, - 119, - 1, - 128, - 2, - 0, - 0, - 75, - 6, - 10, - 0, - 29, - 0, - 80, - 7, - 33, - 0, - 7, - 0, - 75, - 6, - 51, - 3, - 104, - 1, - ] -) -coroutine_delegate = bytes( - [ - 32, - 0, - 29, - 0, - 128, - 0, - 0, - 0, - 83, - 0, - 70, - 2, - 0, - 0, - 80, - 7, - 105, - 4, - 0, - 0, - 119, - 1, - 128, - 2, - 0, - 0, - 75, - 6, - 10, - 0, - 29, - 0, - 80, - 7, - 33, - 0, - 7, - 0, - 75, - 6, - 51, - 3, - 104, - 1, - ] -) -assert delegate.co_code == ordinary_delegate -changed = delegate.replace(co_flags=delegate.co_flags | 0x100) -assert changed.co_code == coroutine_delegate -assert changed.co_code is changed.co_code -restored = changed.replace(co_flags=delegate.co_flags) -assert restored.co_code == ordinary_delegate -for code in (delegate, changed, restored): - first = code.co_code - assert list(types.FunctionType(code, {})([1, 2])) == [1, 2] - assert code.co_code is first - -frozen_code = _frozen_importlib._find_spec.__code__ -assert _frozen_importlib.__spec__.origin == "frozen" -frozen_bytes = frozen_code.co_code -assert frozen_code.co_code is frozen_bytes - -monitoring = sys.monitoring -available = [tool for tool in range(6) if monitoring.get_tool(tool) is None] -assert available -TOOL = available[0] -monitoring.use_tool_id(TOOL, "canonical-code-storage") -try: - for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION): - function = make_function() - code = function.__code__ - observed = [] - - def inspect(actual_code, location): - assert actual_code is code - if not observed: - # This is this code object's first co_code read. A separate - # compilation supplied expected bytes before monitoring began. - first = actual_code.co_code - assert first == expected - assert actual_code.co_code is first - replacement = actual_code.replace() - assert replacement.co_code == expected - assert replacement.co_code is replacement.co_code - assert types.FunctionType(replacement, {})([3, 4]) == 7 - observed.append(first) - - monitoring.register_callback(TOOL, event, inspect) - monitoring.set_local_events(TOOL, code, event) - try: - assert function([1, 2, 3]) == 6 - assert observed - assert code.co_code is observed[0] - finally: - monitoring.set_local_events(TOOL, code, 0) - monitoring.register_callback(TOOL, event, None) - assert code.co_code is observed[0] - assert function([4, 5]) == 9 -finally: - monitoring.set_events(TOOL, 0) - monitoring.free_tool_id(TOOL) diff --git a/extra_tests/snippets/canonical_code_writer_315.py b/extra_tests/snippets/canonical_code_writer_315.py deleted file mode 100644 index cb3bc3133b3..00000000000 --- a/extra_tests/snippets/canonical_code_writer_315.py +++ /dev/null @@ -1,118 +0,0 @@ -"""Actual code fields, writer ownership, and canonical monitored serialization.""" - -import gc -import marshal -import sys -import types -import weakref - -SOURCE = "def sample(a):\n try:\n return a + marker\n except TypeError:\n return 0\n" -FILENAME = "canonical_code_writer.py" -code = compile(SOURCE, FILENAME, "exec").co_consts[0] -assert code.co_linetable and code.co_exceptiontable and code.co_names - -for version in range(7): - for field_name in ("co_consts", "co_names", "co_linetable", "co_exceptiontable"): - field = getattr(code, field_name) - for field_first in (False, True): - graph = (field, code) if field_first else (code, field) - decoded = marshal.loads(marshal.dumps(graph, version)) - other, restored = decoded if field_first else reversed(decoded) - assert getattr(restored, field_name) == field - assert restored.co_code == code.co_code - if version >= 3: - assert getattr(restored, field_name) is other - value = {"tuple": (1, "item"), "set": {2, 3}, "frozen": frozenset((4, 5))} - assert marshal.loads(marshal.dumps(value, version)) == value - -shared = [1, 2] -restored = marshal.loads(marshal.dumps((shared, shared), 6)) -assert restored[0] is restored[1] -cycle = [] -cycle.append(cycle) -restored = marshal.loads(marshal.dumps(cycle, 6)) -assert restored[0] is restored -cycle.clear() -restored.clear() - -# Code and slice back-references cannot be completed by the public reader. -for use_code in (False, True): - child = [] - owner = code.replace(co_consts=(child,)) if use_code else slice(child, None, None) - child.append(owner) - try: - marshal.dumps(owner, 6) - except ValueError: - pass - else: - raise AssertionError("incomplete immutable cycle accepted") - child.clear() - del owner, child - - -class Unsupported: - pass - - -def failed_write(in_set): - value = Unsupported() - reference = weakref.ref(value) - graph = {value} if in_set else [value] - try: - marshal.dumps(graph, 6) - except ValueError: - pass - else: - raise AssertionError("unsupported value accepted") - return reference - - -for in_set in (False, True): - reference = failed_write(in_set) - gc.collect() - assert reference() is None - -# Successful reservations must also release their owners when dumps returns. -clone = code.replace() -reference = weakref.ref(clone) -marshal.dumps((clone, clone), 6) -del clone -gc.collect() -assert reference() is None - -# Serialization, rather than a preceding getter, must obtain canonical bytes -# during the first monitored execution, including opcodes with inline caches. -LOOP = "def total(values):\n result = 0\n for value in values:\n result += value\n return result\n" -expected = compile(LOOP, FILENAME, "exec").co_consts[0].co_code -monitoring = sys.monitoring -available = [tool for tool in range(6) if monitoring.get_tool(tool) is None] -assert available -tool = available[0] -monitoring.use_tool_id(tool, "canonical-code-writer") -try: - for event in (monitoring.events.LINE, monitoring.events.INSTRUCTION): - namespace = {} - exec(compile(LOOP, FILENAME, "exec"), namespace) - function = namespace["total"] - target = function.__code__ - observed = [] - - def inspect(actual, location): - assert actual is target - if not observed: - payload = marshal.dumps(actual, 6) - restored = marshal.loads(payload) - assert restored.co_code == expected - assert types.FunctionType(restored, {})([2, 3]) == 5 - observed.append(payload) - - monitoring.register_callback(tool, event, inspect) - monitoring.set_local_events(tool, target, event) - try: - assert function([1, 2, 3]) == 6 - assert len(observed) == 1 - finally: - monitoring.set_local_events(tool, target, 0) - monitoring.register_callback(tool, event, None) -finally: - monitoring.free_tool_id(tool) diff --git a/extra_tests/snippets/compiled_constant_sharing_315.py b/extra_tests/snippets/compiled_constant_sharing_315.py deleted file mode 100644 index ab0b7c8cf92..00000000000 --- a/extra_tests/snippets/compiled_constant_sharing_315.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Compilation-local field identities, with actual complete-stream witnesses. - -This does not claim full compiler first-winner chronology or native root parity. -NaN/code/slice key exclusions are tested structurally on the Rust Basic route. -""" - -import ast -import gc -import marshal -import types -import weakref - -SOURCE = """def left(x): - try: - return x + 12345678901234567890 - except TypeError: - return ('shared payload !', b'shared bytes !', (98765432109876543210,)) -def right(x): - try: - return x + 12345678901234567890 - except TypeError: - return ('shared payload !', b'shared bytes !', (98765432109876543210,)) -def outer(): - def inner(x): - return x + 12345678901234567890 - return inner -def member_left(x): - return x in {12345678901234567890, 98765432109876543210} -def member_right(x): - return x in {12345678901234567890, 98765432109876543210} -def import_scope(): - import ordinary_name -""" - - -def children(code): - return { - value.co_name: value - for value in code.co_consts - if isinstance(value, types.CodeType) - } - - -def constant(code, kind): - values = [value for value in code.co_consts if type(value) is kind] - assert len(values) == 1, (kind, values) - return values[0] - - -def check_compile(source): - root = compile(source, "compile_sharing.py", "exec") - codes = children(root) - left, right = codes["left"], codes["right"] - assert left is not right - for name in ("co_consts", "co_names", "co_linetable", "co_exceptiontable"): - first, second = getattr(left, name), getattr(right, name) - assert first, name # Every tested owner field is nonempty. - assert first is second, name - assert left.co_varnames == right.co_varnames == ("x",) - assert left.co_varnames is not right.co_varnames - assert left.co_code == right.co_code - assert left.co_code is not right.co_code - inner = children(codes["outer"])["inner"] - assert constant(left, int) is constant(inner, int) - first_set = constant(codes["member_left"], frozenset) - second_set = constant(codes["member_right"], frozenset) - assert first_set is second_set - assert any(value is constant(left, int) for value in first_set) - # Derived varnames are separately cached, even when the internal names and - # localsplusnames tuple owners can be the same compiler cache winner. - imported = codes["import_scope"] - assert imported.co_names == imported.co_varnames == ("ordinary_name",) - assert imported.co_names is not imported.co_varnames - for version in range(3, 7): - stream = marshal.dumps( - [left, right, left.co_consts, left.co_linetable], version - ) - decoded = marshal.loads(stream) - assert decoded[0].co_consts is decoded[1].co_consts is decoded[2] - assert decoded[0].co_linetable is decoded[1].co_linetable is decoded[3] - return root - - -source_root = check_compile(SOURCE) -ast_root = check_compile(ast.parse(SOURCE)) -source_left = children(source_root)["left"] -ast_left = children(ast_root)["left"] -assert source_left.co_consts == ast_left.co_consts -assert source_left.co_consts is not ast_left.co_consts -assert constant(source_left, int) is not constant(ast_left, int) -assert source_left.co_linetable == ast_left.co_linetable -assert source_left.co_linetable is not ast_left.co_linetable - -# Safe owner release: an externally retained constants tuple outlives its code, -# and does not itself retain the code. No cyclic-GC policy is asserted. -retained = source_left.co_consts -reference = weakref.ref(source_left) -del source_left, source_root -gc.collect() -assert reference() is None -assert 12345678901234567890 in retained - -# Scalar typed and signed-zero distinctions remain intact in source compilation. -scalar = compile("value = (False, 0, 0.0, -0.0, 0j, -0j)", "compile_sharing.py", "exec") -values = constant(scalar, tuple) -assert [type(value) for value in values] == [bool, int, float, float, complex, complex] -assert values[2] is not values[3] -assert values[4] is not values[5] -assert marshal.dumps(values[2]) != marshal.dumps(values[3]) -assert marshal.dumps(values[4]) != marshal.dumps(values[5]) - -# Public construction/replace are not a new compilation session. Retain their -# actual supplied tuple owners rather than pooling equal values globally. -base = children(ast_root)["left"] -first = tuple([None, bytes(bytearray(b"public constructor bytes !"))]) -second = tuple([None, bytes(bytearray(b"public constructor bytes !"))]) -a = base.replace(co_consts=first) -b = base.replace(co_consts=second) -assert a.co_consts is first and b.co_consts is second -assert a.co_consts == b.co_consts and a.co_consts is not b.co_consts -assert base.co_consts is not first - -# AST constants retain nonfinite values. Distinct NaN values keep their own -# aggregates while ordinary equal byte siblings can still share within the pool. -for make_nan in ( - lambda: float("nan"), - lambda: complex(float("nan"), 1), - lambda: complex(1, float("nan")), -): - left_value = (make_nan(), bytes(bytearray(b"safe sibling next to NaN"))) - right_value = (make_nan(), bytes(bytearray(b"safe sibling next to NaN"))) - assert left_value[1] is not right_value[1] - tree = ast.Expression(body=ast.Constant(value=(left_value, right_value))) - nan_code = compile(ast.fix_missing_locations(tree), "compile_sharing.py", "eval") - left_value, right_value = constant(nan_code, tuple) - assert left_value is not right_value - assert left_value[0] is not right_value[0] - assert left_value[1] is right_value[1] - for version in range(7): - restored = marshal.loads(marshal.dumps((left_value, right_value), version)) - assert type(restored[0][0]) is type(left_value[0]) - assert restored[0][1] == restored[1][1] == b"safe sibling next to NaN" - if version >= 3: - assert restored[0][1] is restored[1][1] - -infinities = ( - float("inf"), - float("-inf"), - complex(float("inf"), float("-inf")), - complex(float("-inf"), float("inf")), -) -# Build equal but independently allocated input objects to exercise compilation. -repeated = tuple(type(value)(str(value)) for value in infinities) -assert all(a is not b for a, b in zip(infinities, repeated)) -tree = ast.Expression(body=ast.Constant(value=infinities + repeated)) -inf_code = compile(ast.fix_missing_locations(tree), "compile_sharing.py", "eval") -values = constant(inf_code, tuple) -assert values[:4] == values[4:] -assert all(a is b for a, b in zip(values[:4], values[4:])) -assert len({id(value) for value in values}) == 4 diff --git a/extra_tests/snippets/compiler_315_warmed_export.py b/extra_tests/snippets/compiler_315_warmed_export.py deleted file mode 100644 index 4034f515327..00000000000 --- a/extra_tests/snippets/compiler_315_warmed_export.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Ordinary warmed exports retain public opcodes and unchanged raw fields.""" - -import marshal -import sys -import types - -if sys.version_info < (3, 15): - sys.exit(0) - - -source = """ -def direct(x): - return x + 1000 -def looping(xs): - total = 0 - for x in xs: - total += direct(x) - return total -def delegate(xs): - yield from xs -""" -root = compile(source, "byte_exact/warmed_export.py", "exec") -namespace = {} -exec(root, namespace) -codes = [value for value in root.co_consts if isinstance(value, types.CodeType)] -before = [(code.co_code, code.co_linetable, code.co_exceptiontable) for code in codes] -for _ in range(100): - assert namespace["looping"]([1, 2, 3]) == 3006 - assert list(namespace["delegate"]([1, 2])) == [1, 2] -after = [(code.co_code, code.co_linetable, code.co_exceptiontable) for code in codes] -assert before == after - -# Generated cache widths in final 3.15 pycore_opcode_metadata.h. Operand bytes -# are not opcode positions and must not be rejected by a byte-membership test. -cache_widths = { - 128: 1, - 37: 3, - 36: 1, - 105: 1, - 68: 1, - 118: 1, - 109: 4, - 91: 4, - 95: 1, - 79: 9, - 54: 1, - 55: 1, - 74: 1, - 102: 1, - 99: 1, - 100: 1, - 101: 1, - 70: 1, - 50: 3, - 53: 3, - 4: 1, - 42: 5, -} -for code in codes: - exported = code.co_code - index = 0 - while index < len(exported): - opcode = exported[index] - assert opcode not in (219, 220, 221) - index += 2 * (1 + cache_widths.get(opcode, 0)) - assert index == len(exported) - loaded = marshal.loads(marshal.dumps(code, 6)) - assert loaded.co_code == exported - assert loaded.co_linetable == code.co_linetable - assert loaded.co_exceptiontable == code.co_exceptiontable diff --git a/extra_tests/snippets/iterator_contract_315.py b/extra_tests/snippets/generator_iterator_prefix.py similarity index 100% rename from extra_tests/snippets/iterator_contract_315.py rename to extra_tests/snippets/generator_iterator_prefix.py diff --git a/extra_tests/snippets/marshal_actual_owners_315.py b/extra_tests/snippets/marshal_actual_owners_315.py deleted file mode 100644 index d4dd4c36252..00000000000 --- a/extra_tests/snippets/marshal_actual_owners_315.py +++ /dev/null @@ -1,136 +0,0 @@ -"""Actual-owner reference selection: complete streams and bounded lifetimes.""" - -import gc -import marshal -import sys -import weakref - -# These full certified streams describe an explicitly aliased root and a -# repeated child. Direct/named ownership streams are retained separately; this -# golden does not claim root-transport parity for those other Python paths. -GOLDENS = ( - "28020000005b030000006604312e32357801320133730200000041425b030000006604312e3235780132013373020000004142", - "28020000005b030000006604312e32357801320133730200000041425b030000006604312e3235780132013373020000004142", - "28020000005b0300000067000000000000f43f7900000000000000400000000000000840730200000041425b0300000067000000000000f43f790000000000000040000000000000084073020000004142", - "a802000000db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", - "a902db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", - "a902db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", - "a902db0300000067000000000000f43f7900000000000000400000000000000840730200000041427201000000", -) - - -def make_graph(): - child = [float("1.25"), complex("2+3j"), bytes(bytearray([65, 66]))] - return (child, child) - - -for version, expected in enumerate(GOLDENS): - graph = make_graph() - aliases = [graph, graph] - payload = marshal.dumps(graph, version) - assert aliases[0] is aliases[1] is graph - assert payload == bytes.fromhex(expected) - restored = marshal.loads(payload) - assert restored == graph - if version >= 3: - assert restored[0] is restored[1] - -for version in range(7): - text = sys.intern("".join(("ordinary", "_interned", "_owner"))) - objects = { - "dict": {"value": [1, 2], "other": (3, 4)}, - "set": {1.25, 2.75}, - "frozen": frozenset((1.25, 2.75)), - "interned": (text, text), - } - restored = marshal.loads(marshal.dumps(objects, version)) - assert restored == objects - if version >= 3: - assert restored["interned"][0] is restored["interned"][1] - -for version in range(3, 7): - child = [] - graph = {"first": child, "second": child} - child.append(graph) - restored = marshal.loads(marshal.dumps(graph, version)) - assert restored["first"] is restored["second"] - assert restored["first"][0] is restored - child.clear() - restored["first"].clear() - - -class Unsupported: - pass - - -def failed_writer(in_set): - value = Unsupported() - reference = weakref.ref(value) - graph = {value} if in_set else {"value": value} - try: - marshal.dumps(graph, 6) - except ValueError: - pass - else: - raise AssertionError("unsupported value accepted") - return reference - - -for in_set in (False, True): - for _ in range(8): - reference = failed_writer(in_set) - gc.collect() - assert reference() is None - -# Actual weak-reference acquisition and release must not leak a writer owner. -# This checks bounded lifetime/acceptance, not an exclusive-count guarantee. -code = compile("pass", "ordinary_owner.py", "exec") -reference = weakref.ref(code) -for _ in range(32): - acquired = reference() - assert acquired is code - marshal.loads(marshal.dumps((code, acquired), 6)) - del acquired -try: - marshal.dumps(code, 6, allow_code=False) -except ValueError: - pass -else: - raise AssertionError("disallowed code accepted") -del code -gc.collect() -assert reference() is None - -# Observational stress only: scheduling need not overlap, and this does not -# establish snapshot semantics or CPython free-threaded count equivalence. -import _thread - -code = compile("value = 7", "ordinary_owner.py", "exec") -reference = weakref.ref(code) -done = _thread.allocate_lock() -done.acquire() -errors = [] - - -def acquire_owners(): - try: - for _ in range(512): - value = reference() - assert value is not None - del value - except BaseException as error: - errors.append(error) - finally: - done.release() - - -_thread.start_new_thread(acquire_owners, ()) -for _ in range(64): - restored = marshal.loads(marshal.dumps(code, 6)) - assert restored.co_code == code.co_code -assert done.acquire(timeout=10) -done.release() -assert not errors -del code, restored -gc.collect() -assert reference() is None diff --git a/extra_tests/snippets/opcode_calling_315.py b/extra_tests/snippets/opcode_calling_315.py deleted file mode 100644 index 49a6f9fdc88..00000000000 --- a/extra_tests/snippets/opcode_calling_315.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Local _opcode conversion/order contract; shared binder wording is separate.""" - -import _opcode -import opcode -import struct - -NOP = opcode.opmap["NOP"] -events = [] - - -class Indexed: - def __init__(self, value): - self.value = value - - def __index__(self): - events.append("index") - return self.value - - -class IndexErrorValue: - def __index__(self): - events.append("index-error") - raise RuntimeError("ordinary index failure") - - -class IntOnly: - def __int__(self): - events.append("int") - return NOP - - -class IntSubclass(int): - def __index__(self): - raise AssertionError("int subclass index override must not be called") - - -class BoolError: - def __bool__(self): - events.append("bool") - raise AssertionError("jump must not be truth-tested") - - -def error(kind, call, message=None): - try: - call() - except kind as exc: - if message is not None: - assert str(exc) == message, str(exc) - else: - raise AssertionError("expected " + kind.__name__) - - -assert _opcode.stack_effect(NOP) == 0 -assert _opcode.stack_effect(NOP, None) == 0 -assert _opcode.stack_effect(opcode.opmap["BUILD_LIST"], 3) == -2 -error( - ValueError, - lambda: _opcode.stack_effect(opcode.opmap["BUILD_LIST"], -1), - "invalid opcode or oparg", -) -assert _opcode.stack_effect(Indexed(NOP), Indexed(-1)) == 0 -assert events == ["index", "index"] -assert _opcode.stack_effect(IntSubclass(NOP), IntSubclass(-1)) == 0 -for jump in (None, False, True): - assert _opcode.stack_effect(NOP, jump=jump) == 0 -for jump in (0, 1, "", [], Indexed(1), BoolError()): - events.clear() - error( - ValueError, - lambda: _opcode.stack_effect(NOP, jump=jump), - "stack_effect: jump must be False, True or None", - ) - assert events == [] - -for function in ( - _opcode.is_valid, - _opcode.has_arg, - _opcode.has_const, - _opcode.has_name, - _opcode.has_jump, - _opcode.has_free, - _opcode.has_local, - _opcode.has_exc, -): - expected = function(NOP) - events.clear() - assert function(Indexed(NOP)) == expected - assert events == ["index"] - assert function(opcode=IntSubclass(NOP)) == expected - for value in (-(1 << 31) - 1, 1 << 31, 1 << 100): - error( - OverflowError, - lambda: function(value), - "Python int too large to convert to C int", - ) - events.clear() - error(TypeError, lambda: function(Indexed(NOP), 0)) - assert events == [] - error(TypeError, lambda: function(IntOnly())) - assert events == [] - -# Constant-effect NOP keeps extreme conversion values out of arithmetic formulas. -long_bits = struct.calcsize("@l") * 8 -for value in ( - -(1 << 31), - (1 << 31) - 1, - -(1 << (long_bits - 1)), - (1 << (long_bits - 1)) - 1, -): - assert _opcode.stack_effect(NOP, value) == 0 -for value in (-(1 << (long_bits - 1)) - 1, 1 << (long_bits - 1)): - error( - OverflowError, - lambda: _opcode.stack_effect(NOP, value), - "Python int too large to convert to C long", - ) -for value in (-(1 << 31) - 1, 1 << 31): - error( - OverflowError, - lambda: _opcode.stack_effect(value), - "Python int too large to convert to C int", - ) - -events.clear() -error( - RuntimeError, - lambda: _opcode.stack_effect(IndexErrorValue(), IndexErrorValue()), - "ordinary index failure", -) -assert events == ["index-error"] -events.clear() -error( - OverflowError, - lambda: _opcode.stack_effect(1 << 100, IndexErrorValue()), - "Python int too large to convert to C int", -) -assert events == [] -error( - RuntimeError, - lambda: _opcode.stack_effect(-1, IndexErrorValue(), jump=BoolError()), - "ordinary index failure", -) -assert events == ["index-error"] -events.clear() -error( - ValueError, - lambda: _opcode.stack_effect(-1, 0, jump=BoolError()), - "stack_effect: jump must be False, True or None", -) -assert events == [] -for call in ( - lambda: _opcode.stack_effect(Indexed(NOP), 0, False), - lambda: _opcode.stack_effect(Indexed(NOP), other=False), - lambda: _opcode.stack_effect(Indexed(NOP), oparg=Indexed(0)), -): - error(TypeError, call) - assert events == [] - - -class LifetimeOperand: - def __index__(self): - events.append("temporary-index") - return 0 - - def __del__(self): - events.append("temporary-release") - - -# Acyclic temporaries are released on both success and validation error. -events.clear() -assert _opcode.stack_effect(NOP, LifetimeOperand()) == 0 -assert events == ["temporary-index", "temporary-release"] -events.clear() -error( - ValueError, - lambda: _opcode.stack_effect(NOP, LifetimeOperand(), jump=0), - "stack_effect: jump must be False, True or None", -) -assert events == ["temporary-index", "temporary-release"] diff --git a/extra_tests/snippets/private_315_cache_contract.py b/extra_tests/snippets/private_315_cache_contract.py deleted file mode 100644 index 0f0bdfe88a4..00000000000 --- a/extra_tests/snippets/private_315_cache_contract.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Private development ABI reader/writer boundary; no public compatibility claim.""" - -import _frozen_importlib_external as external -import importlib -import importlib.util -import py_compile -import sys -import tempfile -import zipfile -from pathlib import Path - -if sys.implementation.name == "rustpython": - assert external.MAGIC_NUMBER == bytes((0x16, 0xDA, 13, 10)) - assert sys.implementation.cache_tag == ( - f"{sys.implementation.name}-{sys.version_info.major}{sys.version_info.minor}" - ) - - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - source = root / "cache_subject.py" - source.write_text("value = 41\n", encoding="ascii", newline="\n") - code = compile("value = 42\n", "byte_exact/cache_subject.py", "exec") - stat = source.stat() - headers = [ - external._code_to_timestamp_pyc(code, int(stat.st_mtime), stat.st_size), - external._code_to_hash_pyc( - code, importlib.util.source_hash(source.read_bytes()), True - ), - external._code_to_hash_pyc( - code, importlib.util.source_hash(source.read_bytes()), False - ), - ] - rejected = [3627, 3666, 0xDA15] - current = 0xDA16 - - def load_from(path): - sys.modules.pop("cache_subject", None) - sys.path.insert(0, str(path)) - importlib.invalidate_caches() - try: - return importlib.import_module("cache_subject").value - finally: - sys.path.pop(0) - sys.modules.pop("cache_subject", None) - - # Source caches must fall back to source on every foreign magic. - cache = Path(importlib.util.cache_from_source(str(source))) - cache.parent.mkdir() - for payload in headers: - for magic in rejected + [current]: - candidate = magic.to_bytes(2, "little") + b"\r\n" + payload[4:] - cache.write_bytes(candidate) - assert load_from(root) == (42 if magic == current else 41) - - # Sourceless files cannot silently fall back or accept foreign formats. - source.unlink() - sourceless = root / "cache_subject.pyc" - for payload in headers: - for magic in rejected + [current]: - sourceless.write_bytes( - magic.to_bytes(2, "little") + b"\r\n" + payload[4:] - ) - if magic == current: - assert load_from(root) == 42 - else: - try: - load_from(root) - except ImportError: - pass - else: - raise AssertionError("foreign sourceless cache accepted") - - # ZIP source fallback and pyc-only rejection use the same token. - for header_index, payload in enumerate(headers): - for with_source in (False, True): - for magic in rejected + [current]: - archive = root / f"case_{header_index}_{with_source}_{magic}.zip" - with zipfile.ZipFile(archive, "w") as output: - output.writestr( - "cache_subject.pyc", - magic.to_bytes(2, "little") + b"\r\n" + payload[4:], - ) - if with_source: - output.writestr("cache_subject.py", "value = 41\n") - if magic == current or with_source: - value = load_from(archive) - if magic != current: - assert value == 41 - elif with_source and header_index == 0: - # ZIP timestamp validation may select the source. - assert value in (41, 42) - else: - assert value == 42 - else: - try: - load_from(archive) - except ImportError: - pass - else: - raise AssertionError("foreign ZIP cache accepted") - - # Explicit py_compile writes under -B; all writers must use the private token. - source.write_text("value = 41\n", encoding="ascii", newline="\n") - explicit = root / "explicit.pyc" - previous = sys.dont_write_bytecode - sys.dont_write_bytecode = True - try: - py_compile.compile(str(source), cfile=str(explicit), doraise=True) - assert explicit.read_bytes()[:4] == external.MAGIC_NUMBER - finally: - sys.dont_write_bytecode = previous diff --git a/extra_tests/snippets/public_opcode_metadata_315.py b/extra_tests/snippets/public_opcode_metadata_315.py deleted file mode 100644 index 231b4693268..00000000000 --- a/extra_tests/snippets/public_opcode_metadata_315.py +++ /dev/null @@ -1,567 +0,0 @@ -"""Complete bounded final3.15 public metadata contract, including private holes. - -The lossless flag rows and complete stack matrix come from the certified final -oracle. Calling conversion outside ordinary nonnegative arguments is separate. -""" - -import _opcode -import hashlib -import json - -FLAGS = ( - "is_valid", - "has_arg", - "has_const", - "has_name", - "has_jump", - "has_free", - "has_local", - "has_exc", -) -EXPECTED_FLAGS = ( - 0, - 1, - 1, - 1, - 65, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 11, - 35, - 67, - 11, - 11, - 3, - 3, - 19, - 3, - 19, - 3, - 3, - 11, - 11, - 3, - 19, - 19, - 19, - 3, - 3, - 11, - 3, - 7, - 67, - 67, - 67, - 67, - 67, - 67, - 67, - 35, - 11, - 11, - 11, - 3, - 3, - 11, - 35, - 3, - 3, - 19, - 19, - 19, - 19, - 3, - 3, - 19, - 3, - 3, - 3, - 11, - 35, - 67, - 67, - 67, - 11, - 11, - 3, - 3, - 3, - 3, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 1, - 3, - 1, - 3, - 3, - 3, - 1, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 19, - 19, - 19, - 19, - 1, - 1, - 19, - 19, - 3, - 3, - 11, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 3, - 11, - 3, - 3, - 11, - 11, - 1, - 3, - 19, - 3, - 19, - 1, - 1, - 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 3, - 3, - 3, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 19, - 1, - 3, - 1, - 3, - 3, - 3, - 3, - 3, - 1, - 3, - 19, - 11, - 3, - 3, - 1, - 3, - 1, - 3, - 3, - 1, - 19, - 19, - 19, - 19, - 67, - 1, - 131, - 131, - 131, - 67, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, -) -EXPECTED_STACK_SHA256 = ( - "37dc8fdfed5c2a15665eece22718422b02721a1d9ce6e4963b89c04727074f37" -) - -actual_flags = tuple( - sum(int(getattr(_opcode, name)(op)) << bit for bit, name in enumerate(FLAGS)) - for op in range(-1, 513) -) -assert len(actual_flags) == 514 -assert actual_flags == EXPECTED_FLAGS -assert _opcode.is_valid(255) and _opcode.has_arg(255) -assert all(not _opcode.is_valid(op) for op in (219, 220, 221)) -rows = [] -for op in range(-1, 513): - for arg in (0, 1, 2, 3, 4, 8, 255): - for jump in (None, False, True): - try: - value = _opcode.stack_effect(op, arg, jump=jump) - except Exception as exc: - value = [type(exc).__name__, str(exc)] - rows.append([op, arg, jump, value]) -assert len(rows) == 10794 -assert ( - hashlib.sha256(json.dumps(rows, separators=(",", ":")).encode()).hexdigest() - == EXPECTED_STACK_SHA256 -) -assert all( - _opcode.stack_effect(255, arg, jump=jump) == 0 - for arg in (0, 255) - for jump in (None, False, True) -) diff --git a/extra_tests/snippets/stable_code_fields_315.py b/extra_tests/snippets/stable_code_fields_315.py deleted file mode 100644 index 637c79fad98..00000000000 --- a/extra_tests/snippets/stable_code_fields_315.py +++ /dev/null @@ -1,154 +0,0 @@ -"""Stable surviving code fields and sole constants tuple ownership.""" - -import gc -import marshal -import sys -import types -import weakref - -SOURCE = "def sample(a):\n try:\n return a + marker\n except TypeError:\n return 0\n" -FILENAME = "stable_code_fields.py" -base = compile(SOURCE, FILENAME, "exec").co_consts[0] -for name in ( - "co_consts", - "co_names", - "co_varnames", - "co_freevars", - "co_cellvars", - "co_linetable", - "co_exceptiontable", - "co_code", -): - value = getattr(base, name) - assert getattr(base, name) is value - -replacement = base.replace() -for name in ("co_consts", "co_names", "co_linetable", "co_exceptiontable"): - assert getattr(replacement, name) is getattr(base, name) -assert replacement.co_code == base.co_code -assert replacement.co_code is not base.co_code -assert replacement.co_varnames == base.co_varnames -assert replacement.co_varnames is not base.co_varnames - - -class Child: - pass - - -child = Child() -weak = weakref.ref(child) -values = (child, [1, 2], slice(1, 5, 2), frozenset((3, 4))) -owned = base.replace(co_consts=values) -assert owned.co_consts is values -assert owned.co_consts[0] is child -assert owned.co_consts[2] is values[2] -assert owned.co_consts[3] is values[3] -clone = owned.replace() -assert clone.co_consts is values -del child, owned, values -gc.collect() -assert weak() is not None -del clone -gc.collect() -assert weak() is None - -# The public constructor copies validated names, retains its constants and -# surviving byte tables, and independently creates its exported code cache. -input_code = bytes(bytearray(base.co_code)) -input_names = tuple(list(base.co_names)) -constructed = types.CodeType( - base.co_argcount, - base.co_posonlyargcount, - base.co_kwonlyargcount, - base.co_nlocals, - base.co_stacksize, - base.co_flags, - input_code, - base.co_consts, - input_names, - base.co_varnames, - FILENAME, - base.co_name, - base.co_qualname, - base.co_firstlineno, - base.co_linetable, - base.co_exceptiontable, - base.co_freevars, - base.co_cellvars, -) -assert constructed.co_consts is base.co_consts -assert constructed.co_names == input_names -assert constructed.co_names is not input_names -assert constructed.co_linetable is base.co_linetable -assert constructed.co_exceptiontable is base.co_exceptiontable -assert constructed.co_code == input_code -assert constructed.co_code is not input_code - -# Retain the known normalization gap. Rust never mutates a published caller -# tuple; when a member needs canonical interning it creates a normalized copy. -raw_name = "".join(("mar", "ker")) -assert raw_name is not base.co_names[0] -names = (raw_name,) -normalized = base.replace(co_names=names) -assert normalized.co_names[0] is base.co_names[0] -if sys.implementation.name == "rustpython": - assert normalized.co_names is not names - assert names[0] is raw_name -else: - assert normalized.co_names is names - assert names[0] is base.co_names[0] - -outer = compile( - "def outer(a):\n def inner():\n return a\n return inner\n", - FILENAME, - "exec", -).co_consts[0] -inner = next(value for value in outer.co_consts if isinstance(value, types.CodeType)) -assert outer.co_cellvars == ("a",) -assert outer.co_cellvars is outer.co_cellvars -assert inner.co_freevars == ("a",) -assert inner.co_freevars is inner.co_freevars - -# Whole unchanged streams emitted by the pinned final oracle. Both field-first -# and code-first ordering exercise reader reference identity; the current Rust -# writer is deliberately not used to create the expected aliases. -FIELD_FIRST = bytes.fromhex( - "2905a901e900000000a902da066d61726b6572da09547970654572726f72f3230000008100f002030511d80f" - "1094368d7ad00819f8dc0b14f400010511da0f10f003010511faf310000000830c100090091f039a011f039e" - "011f03e30100000000000000000000000400000003000000f34400000080000000190055005b000000000000" - "0000002a000000000000000000000021001e005b0200000000000000000600630500001a001d001b005d0021" - "00680039031b006801720000000072020000002901da0161730100000026da15737461626c655f636f64655f" - "6669656c64732e7079da0673616d706c65720b0000000100000072050000007206000000" -) -CODE_FIRST = bytes.fromhex( - "2905e30100000000000000000000000400000003000000f34400000080000000190055005b00000000000000" - "00002a000000000000000000000021001e005b0200000000000000000600630500001a001d001b005d002100" - "680039031b006801a901e900000000a902da066d61726b6572da09547970654572726f722901da0161730100" - "000026da15737461626c655f636f64655f6669656c64732e7079da0673616d706c65720900000001000000f3" - "230000008100f002030511d80f1094368d7ad00819f8dc0b14f400010511da0f10f003010511faf310000000" - "830c100090091f039a011f039e011f0372020000007204000000720a000000720b000000" -) -for order, payload in (("field_first", FIELD_FIRST), ("code_first", CODE_FIRST)): - decoded = marshal.loads(payload) - if order == "field_first": - consts, names, lines, exceptions, code = decoded - else: - code, consts, names, lines, exceptions = decoded - assert code.co_consts is consts - assert code.co_names is names - assert code.co_linetable is lines - assert code.co_exceptiontable is exceptions - assert code.co_code == base.co_code - assert code.co_code is code.co_code - function = types.FunctionType(code, {"marker": 10}) - assert function(2) == 12 - assert function("x") == 0 - -# Final CodeType.replace accepts tuple fields, not arbitrary iterables. -for field in ("co_consts", "co_names"): - try: - base.replace(**{field: []}) - except TypeError: - pass - else: - raise AssertionError(field + " accepted a list") diff --git a/scripts/byte_exact_capture.py b/scripts/byte_exact_capture.py deleted file mode 100644 index 90b75026030..00000000000 --- a/scripts/byte_exact_capture.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Compile one ordinary request; emit unmodified marshal before field inspection.""" - -import json -import marshal -import struct -import sys -import types - -FIELDS = ( - "co_argcount", - "co_posonlyargcount", - "co_kwonlyargcount", - "co_nlocals", - "co_stacksize", - "co_flags", - "co_code", - "co_consts", - "co_names", - "co_varnames", - "co_freevars", - "co_cellvars", - "co_filename", - "co_name", - "co_qualname", - "co_firstlineno", - "co_linetable", - "co_exceptiontable", -) - - -def describe(value): - kind = type(value) - if kind is types.CodeType: - return { - "type": "code", - "fields": {name: describe(getattr(value, name)) for name in FIELDS}, - } - if value is None or value is Ellipsis: - return {"type": kind.__name__} - if kind in (bool, int, str): - return {"type": kind.__name__, "value": value} - if kind is bytes: - return {"type": "bytes", "value": list(value)} - if kind is float: - return {"type": "float", "bits": list(struct.pack(" U:\n return x\n", - "mode": "exec", - "flags": 16777216 - }, - { - "name": "formatting", - "source": "result = f\"{value!r:>10}\"\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "template", - "source": "result = t\"value={value}\"\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "matching", - "source": "match value:\n case [a, b]:\n result = a + b\n case _:\n result = None\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "yield_plain", - "source": "def f():\n yield 1\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "import_module", - "source": "import os\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "import_function", - "source": "def f():\n import os\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "import_handler", - "source": "try:\n import os\nexcept ImportError:\n import sys\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "genexpr_list", - "source": "def f(xs):\n return list(x + 1 for x in xs if x)\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "genexpr_set", - "source": "def f(xs):\n return set(x + 1 for x in xs if x)\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "genexpr_tuple", - "source": "def f(xs):\n return tuple(x + 1 for x in xs if x)\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "async_genexpr", - "source": "async def f(xs):\n return list(x async for x in xs)\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "borrow_locals", - "source": "def f(a, b):\n return a, b, a\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "exception_alias", - "source": "try:\n work()\nexcept Exception as error:\n result = error\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "assembly_siblings", - "source": "def left():\n return 1\ndef right():\n return 1\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_if_ors", - "source": "if True or False or False:\n pass", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_if_ands", - "source": "if True and False and False:\n pass", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_if_mixed", - "source": "if (True and False) or (False and True):\n pass", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_nested_bool_op", - "source": "x = Test() and False or False", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_const_no_op", - "source": "x = not True", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_constant_true_if_pass_keeps_line_anchor_nop", - "source": "if 1:\n pass", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_nested_double_async_with", - "source": "async def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')", - "mode": "exec", - "flags": 0 - }, - { - "name": "dis_bare_function_annotations_check_attribute_and_subscript_expressions", - "source": "def f(one: int):\n int.new_attr: int\n [list][0].new_attr: [int, str]\n my_lst = [1]\n my_lst[one]: int\n return my_lst", - "mode": "exec", - "flags": 0 - }, - { - "name": "cache_nested_constants", - "source": "def outer():\n def left(x):\n return x + 12345678901234567890\n def right(x):\n return x + 12345678901234567890\n return left, right\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "cache_sibling_exception_fields", - "source": "def left(x):\n try:\n return x + 12345678901234567890\n except TypeError:\n return ('payload !', b'bytes !')\ndef right(x):\n try:\n return x + 12345678901234567890\n except TypeError:\n return ('payload !', b'bytes !')\n", - "mode": "exec", - "flags": 0 - }, - { - "name": "cache_frozenset_siblings", - "source": "def left(x):\n return x in {12345678901234567890, 98765432109876543210}\ndef right(x):\n return x in {12345678901234567890, 98765432109876543210}\n", - "mode": "exec", - "flags": 0 - } -] diff --git a/scripts/test_byte_exact_compile.py b/scripts/test_byte_exact_compile.py deleted file mode 100644 index f07a20ca3cd..00000000000 --- a/scripts/test_byte_exact_compile.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Controller integrity checks; these do not test interpreter behavior.""" - -import unittest -from pathlib import Path -from tempfile import TemporaryDirectory -from unittest.mock import patch - -import byte_exact_capture -from byte_exact_compile import ( - capture_environment, - compare_captures, - differences, - requests, - validate_cache_prefix, -) - - -class CorpusTests(unittest.TestCase): - def setUp(self): - self.fixture = { - "name": "ordinary", - "source": "x = 1\n", - "mode": "exec", - "flags": 0, - } - - def test_empty_corpus_is_an_error(self): - with self.assertRaisesRegex(ValueError, "empty"): - requests([], []) - - def test_unknown_selection_is_an_error(self): - with self.assertRaisesRegex(ValueError, "unknown"): - requests([self.fixture], ["missing"]) - - def test_duplicate_names_are_an_error(self): - with self.assertRaisesRegex(ValueError, "duplicate"): - requests([self.fixture, self.fixture], []) - - def test_explicit_selection_retains_all_optimizations(self): - corpus = requests([self.fixture], ["ordinary"]) - self.assertEqual([r["optimize"] for r in corpus], [0, 1, 2]) - self.assertTrue(all(r["filename"] == "byte_exact/ordinary.py" for r in corpus)) - - def test_ascii_boundary(self): - with self.assertRaisesRegex(ValueError, "ASCII"): - requests([{**self.fixture, "source": "x = 'é'"}], []) - - def test_equal_fields(self): - self.assertEqual(differences({"a": [1, 2]}, {"a": [1, 2]}), []) - - def test_byte_difference_is_retained(self): - self.assertEqual(differences([1, 128], [1, 0], "marshal"), ["marshal/1"]) - - def test_nested_difference_has_single_leaf_path(self): - self.assertEqual( - differences({"code": {"flags": 1}}, {"code": {"flags": 2}}), ["/code/flags"] - ) - - def test_length_difference_is_not_normalized(self): - self.assertEqual(differences([1, 0], [1], "code"), ["code"]) - - def test_types_are_not_coerced(self): - self.assertEqual(differences(True, 1, "constant"), ["constant"]) - - def test_cache_prefix_is_explicit_in_sanitized_environment(self): - with TemporaryDirectory() as directory: - prefix = validate_cache_prefix(directory) - env = capture_environment(None, prefix) - self.assertEqual(env["PYTHONPYCACHEPREFIX"], str(prefix)) - self.assertEqual(env["PYTHONDONTWRITEBYTECODE"], "1") - self.assertNotIn("RUSTPYTHONPATH", env) - - def test_populated_cache_prefix_is_rejected(self): - with TemporaryDirectory() as directory: - (Path(directory) / "existing.pyc").write_bytes(b"cache") - with self.assertRaisesRegex(ValueError, "empty"): - validate_cache_prefix(directory) - - def test_runtime_provenance_is_not_a_comparison_field(self): - reference = {"marshal": [1], "magic": [2], "code": {}, "runtime": "reference"} - candidate = {**reference, "runtime": "candidate"} - self.assertEqual( - compare_captures(reference, candidate), - {"marshal": [], "magic": [], "code": []}, - ) - - def test_runtime_provenance_follows_serialization_and_fields(self): - events = [] - request = {**self.fixture, "filename": "ordinary.py", "optimize": 0} - with ( - patch.object( - byte_exact_capture.marshal, - "dumps", - side_effect=lambda *a: events.append("marshal") or b"raw", - ), - patch.object( - byte_exact_capture, - "describe", - side_effect=lambda value: events.append("fields") or {}, - ), - patch.object( - byte_exact_capture, - "runtime_provenance", - side_effect=lambda: events.append("runtime") or {}, - ), - ): - byte_exact_capture.capture(request) - self.assertEqual(events, ["marshal", "fields", "runtime"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/opcode_metadata/CANONICAL_CODE_STORAGE.md b/tools/opcode_metadata/CANONICAL_CODE_STORAGE.md deleted file mode 100644 index 2e68f3b2242..00000000000 --- a/tools/opcode_metadata/CANONICAL_CODE_STORAGE.md +++ /dev/null @@ -1,37 +0,0 @@ -# Canonical code storage follow-on - -This storage-only follow-on starts from reviewed checkpoint a974ec938d700459d5badd578df59b587f4a4396. -It does not change the common marshal writer, reference policy, filename handling, -private magic, native argument transport, or tuple/metadata ownership. - -Pinned Objects/codeobject.c:_PyCode_GetCode caches a stable bytes object and -uses monitoring-aware deoptimization; code_replace_impl obtains that public -representation when co_code is omitted. Rust's previous live original_bytes() -export cannot recover generic INSTRUMENTED_LINE/INSTRUCTION side-table opcodes. - -PyCode::new now captures immutable canonical bytes immediately after GET_ITER -normalization, before code publication. co_code lazily creates one PyBytes object -from that backing through the threading-aware OnceCell. Python replace without -co_code parses the immutable backing instead of cloning live execution units. -Source/AST, Basic bytecode materialization, Python constructor/replace, frozen -code and the public marshal reader all converge on PyCode::new. No context API -change is needed for this narrow lazy bytes cache. - -The public embedding constructor requires pristine input. Copying a monitored -PyCode.code into a new PyCode cannot carry its dynamic monitoring side tables. -Logical post-finalization code mutation is unsupported; execution specialization -and instrumentation may mutate live instructions but cannot replace canonical -public bytes. Embedders replacing logical code must construct a new PyCode from -canonical bytes. This is a documented API contract, not an unsafe lifetime claim. - -The additive Python regression checks cold/warmed stable identity, constructor, -replace, public decoding and a core frozen code object. Independent compilations -provide expected bytes so each monitored target's first getter call occurs inside -an active LINE or INSTRUCTION callback. Replacement construction and execution -there must never copy live monitoring wrappers. All filenames are fixed ASCII. - -Crucially, marshal's current serialize_code_with still reads live instructions. -This getter/storage change alone does not fix marshal-under-monitoring or make -full streams exact. A later separately reviewed common-writer connection must -use the actual cached object and preserve its temporary owner through all fields. -No public serialization compatibility or full-byte parity is claimed here. diff --git a/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md b/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md deleted file mode 100644 index bd5a61c4d2d..00000000000 --- a/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md +++ /dev/null @@ -1,110 +0,0 @@ -# Final 3.15 compiler ABI reconstruction - -This is new work based on public RustPython commit -`06007c81fa99e813cbf4583dab326764ba0ef017`. Earlier local checkpoints were lost -with their execution environment. Their historical results are not validation -of this reconstruction. - -The source target is CPython v3.15.0, -`d220409a13b77de7a6c30abe16ea90db01528edc`. `target_source.json` pins the -generator dependencies and directly consulted ABI sources. The active metadata -generator verifies those hashes before importing the CPython generator modules. -The target generator directory must also be free of bytecode caches; the gate -rejects existing pycs and disables writes before importing from the verified path. -`verify_stack_contract.py` compiles the pinned C metadata independently and -checks 3,430 argument-specific stack rows; Rust tests compare every supported -opcode against those rows. Unsupported specialized instructions are not claimed -implemented merely because their reference rows are recorded. - -## Private cache contract - -The intermediate ABI uses magic `0xDA16` (bytes `16 da 0d 0a`) and cache tag -`rustpython-315-devabi2`. Available repository and pinned-source definitions were -checked for collisions; this is not a globally reserved token. The lost -`0xDA15` development format is deliberately rejected, as are public CPython -formats. Change both token and tag whenever the intermediate format changes -incompatibly. - -The shared Rust magic feeds importlib, source and sourceless caches, ZIP imports, -explicit `py_compile`/`compileall` writers, and direct Rust pyc readers. `-B` -does not prohibit cache reads or explicit writes. Validation uses a fresh owned -Lib copy, records cache inventories, and checks foreign timestamp/hash caches -and ZIP fallback/rejection. The CLI prepends `BUILDTIME_RUSTPYTHONPATH` ahead of -`RUSTPYTHONPATH`; a new environment path alone does not isolate cache reads. -Strict captures must verify actual module paths and keep their embedded Lib copy -separate from cache-writing Rust embedding tests. Headerless marshal and explicit frozen-data input -remain caller-version-coupled. Frozen modules must be rebuilt using the same -compiler and VM target; private pyc magic does not validate their separate -compressed format. - -## Output and integration gates - -The strict harness captures untouched marshal version 6 bytes before inspecting -code fields. It compares magic, marshal and recursively captured fields without -normalizing byte streams, exception ranges, reference flags, or nested code. -An empty corpus is an error. Its finite fixtures are witnesses, not a general -compiler-equivalence proof. Behavioral regressions belong in Python snippets; -Rust tests additionally check structural compiler and ownership invariants. -The reference invocation requires an empty `PYTHONPYCACHEPREFIX` so existing -certified-build caches are not read. The harness verifies that directory remains -empty and records runtime paths, cache settings and already-imported module -origins after serialization and field capture. These diagnostics are provenance; -they do not alter the three strict comparison domains. - -Canonical `co_code` uses immutable prepublication bytes and a stable bytes owner; -the common marshal writer uses that actual field rather than live monitoring -instructions. The adaptive getter projects private Rust `LOAD_CONST` IDs220/221 -to public `LOAD_CONST` at immutable canonical instruction boundaries, preserving -arguments, public specialization IDs and cache payload bytes. Legacy private -ID219 has no equivalent one-unit public encoding and raises `ValueError` on -adaptive export. Length and truncated canonical cache spans are checked; arbitrary -same-length postpublication embedding mutation remains outside the finalized-code -contract. Per-cell atomic reads do not establish a coherent concurrent snapshot -or repair inherited shared-storage accessor and mixed-width dispatch concerns. -The projection neither mutates executable instructions nor changes canonical or -marshal bytes. Runtime specialization choices need not match CPython. -Retained private `GET_YIELD_FROM_ITER` must never be emitted by the compiler. -The current local tree includes the separately reviewed builtin `frozendict` -prerequisite and exact final `Lib/opcode.py`, `Lib/_opcode_metadata.py`, and -`Lib/dis.py` imports made only through `scripts/update_lib`. The eight original -dis tests pass with certified final expectations. This local validation does not -waive Rust-first merge sequencing or establish a distributable implementation. -Future Lib updates must also use `scripts/update_lib` with the exact source pin; -manual copying or editing these files is not permitted. -The public magic transition requires full serialization compatibility, -reader/writer/frozen agreement, and the separately planned exact Python update. - -Marshal reference ownership, compiler constant-origin chronology and mortal -interning remain separate work. No count offsets, flag masking, equal-text -over-interning or weakening of static-reference invariants is permitted. -The filename/surrogate family remains outside this stage. - -## Public native opcode metadata - -`generate_public_metadata.py --source ` generates the native -`_opcode` introspection domain from final C descriptors, deoptimization mapping -and stack expressions. Its 267-slot descriptor extent and 256-slot deopt extent -are deliberately separate from the executable Rust opcode enum. This includes -CPython-only specialized/tier2 descriptors and excludes private Rust IDs219–221; -it does not implement their execution or change cache magic. Specialized stack -effects remain invalid. Formula parsing fails closed and uses wrapping signed -32-bit arithmetic rather than panic-prone Rust arithmetic. This is an explicit -implementation policy, not a portable claim about C signed overflow; equivalence -outside the tested argument window remains unclaimed. - -The bounded Python regression retains all514 numeric flag rows and a complete -10,794-row stack-result digest from the certified interpreter, including errors. -It uses ordinary nonnegative arguments and exact None/False/True jump values. -The numeric metadata matrix does not cover calling conversions; the separate -calling protocol regression below does not claim all diagnostic wording parity. -Canonical metadata/dis files are exact final imports through update_lib. Their -local integration does not waive Rust-first merge order or publication gates. - -The local `_opcode` calling-conversion follow-on accepts index objects after -argument binding, converts the opcode before its operand, treats an explicit -`None` operand as zero, and requires exact `None`/`True`/`False` jump identities. -C-int and C-long overflow messages are local to this module. C-long narrowing -uses a panic-free low-bit policy for supported two's-complement target ABIs; -Linux oracle evidence does not certify a Windows run or C signed-overflow -formulas. Shared argument-binder and invalid-index-result diagnostic wording -remain separately visible in the complete calling matrix. diff --git a/tools/opcode_metadata/STABLE_CODE_FIELDS.md b/tools/opcode_metadata/STABLE_CODE_FIELDS.md deleted file mode 100644 index 8d126ef8bce..00000000000 --- a/tools/opcode_metadata/STABLE_CODE_FIELDS.md +++ /dev/null @@ -1,132 +0,0 @@ -# Stable code fields and common writer routing - -The storage stage follows canonical code storage commit6746df059. The subsequent -common-writer stage is described below. The subsequent actual-owner selector is described below. Native call ownership, -permanent string interning and filename handling remain unchanged. -Full serialization parity remains unresolved. - -Runtime `Constants` owns one immutable Python tuple. It does not keep a -second boxed list of owning child references beside a getter cache. The compiler -representation remains boxed. Cloning a runtime constant pool shares the tuple; -mutable access copies the elements before replacing the owner. Published tuples -are never mutated. The owner-bound Literal slice uses the existing transparent -layout over PyObjectRef and never calls the slice/frozenset constant adapter. - -Surviving names, locals-plus names/kinds and line/exception tables have stable -Python owners. Derived variable/cell/free-name tuples are separate lazy caches. -Constructor names are copied after validation; replace retains unchanged names. -Public marshal decode retains actual field objects when normalized name member -identities permit it, otherwise uses a safe normalized copy. That copy has a -known tuple-identity difference from CPython's in-place name normalization. -Existing constant interning behavior is preserved, not expanded. - -Decoder input co_code bytes are not retained as the exported co_code object. -That export remains A0's independent lazy cache of canonical normalized bytes. -The new reader hook defaults to the existing bytes/constants/code hook chain. -Runtime decoding drops temporary child vectors and replaced boxes before code -publication. Reader allow_code, type, depth and incomplete-reference checks stay -in force. - -## Rust source API migration - -- `PyCode::new(code, &Context)` now requires the creating runtime's context so - final tuple/metadata objects exist before publication. Callers must still pass - pristine instruction streams, as required by A0. -- Implementations of compiler-core `Constant` supply `TupleOwner: Clone + - AsRef<[Self]>`. BasicBag uses boxed constants; the runtime uses a private - validated tuple owner. Downstream bags must provide an immutable owner-bound - element view. No Python object type is introduced into compiler-core. -- Public getters for co_linetable/co_exceptiontable no longer require a VM - argument; they return retained Python bytes owners. -- A new `MarshalBag::make_code_with_fields` is additive. Its default delegates - `make_code_with_constants_and_bytes`, preserving earlier customization hooks. - -The checked-in Python test retains full certified streams in both alias orders, -checks identity and execution, and explicitly preserves the normalization gap. -Rust tests verify actual child-owner counts, COW isolation, publication and -reader-hook ordering. No test count is used by production serialization. - -## Integration limits - -The existing untracked PyCode policy and permanent-intern ownership contracts -are unchanged. Pinned CPython only tracks code objects in Py_GIL_DISABLED -builds; the certified GIL-enabled oracle is untracked too. This stage does not claim a general cycle-collection repair or mortal -interning parity. Python child ownership moves to the tuple edge and ordinary -Rust drops release it; a future traversal change must account for that real edge. -Compilation against current main after #9080 is a separate integration gate even -though the source audit found no direct overlap and transparent ownership remains. -Full workspace/CAPI/release and the canonical metadata dependencies remain -required before a general integration/CI-green claim. - -## Common writer routing follow-on - -The runtime marshal writer routes nine actual surviving code-field objects -through its ordinary object writer: canonical code bytes, constants, names, -localsplus names and kinds, name, qualname, line table and exception table. -The field callback runs before the compiler/frozen fallback computes any bytes -or temporary names. The filename operation stays unchanged in its original -position. Default compiler/frozen callers retain their previous full encoding. - -Runtime constants are serialized as the real tuple, including its normal child -depth, rather than a synthetic tuple header followed by flattened children. -The canonical code getter's real owner lives through the complete code write; -other fields are borrowed from their existing owners. Each reference-table -reservation additionally owns its referent until that per-call table drops, -including set-sort temporary tables and error exits. No dictionary lock spans -recursive serialization. - -This changes ordinary code-field bytes and fixes the live-monitoring export -source used by this writer. At the routing checkpoint, reference selection was -still blanket; the selector follow-on below replaces that rule. Direct, named -and aliased roots retain their real native-call owners; -no counts or flags are adjusted to imitate CPython. Filename identity, interning -lifetime and native argument provenance remain separate compatibility gaps. -Storage and writer changes alone do not establish complete marshal parity or -public cache-format compatibility. - - -## Observational actual-owner reference selection - -Before reference lookup or reservation, the common writer observes the actual -strong count. A unique object is emitted without registration unless it is an -exact interned string. One decision controls lookup, reservation, flag emission -and completion. Table owners, canonical getter owners, dictionary snapshots and -set-sort temporary owners remain real and unchanged; no counts are subtracted. - -The relaxed count observation changes only wire registration. It does not prove -exclusive access or authorize borrowing or mutation. Concurrent weak acquisition -or graph changes can affect bytes and decoded alias observations; serialization -is not a snapshot or a claim of CPython free-threaded uniqueness parity. Existing -immortal and permanent-interner contracts remain intact. Native direct/named -root differences and compiler-origin sharing remain separate parity gaps. - -## Compilation-local materialization (A4) - -Source/AST and Basic CodeObject publication share one private pool of typed, -owner-free Rust keys. The five surviving fields are finalized in this order: -exception table, line table, names, constants, localsplusnames. Nested code values -are finalized before their PyRefs enter the private root graph; the pool drops -before root publication, including on unwinding. Exported co_code, localspluskinds -and separately lazy var/cell/free tuples are not pooled. - -The additive IntoCodeObject::into_pycode method defaults to the former runtime -construction path. Its Basic override joins the source path; downstream -implementations inherit the default without a new required method. Raw Basic -into_code_object shares constants and nested bodies but cannot promise finalized -root field identities. Frozen decoding, runtime Literal input, public readers, -constructors and replace retain their prior routes. - -Keys distinguish types and all signed zeros. Frozensets are keyed by their actual -surviving element types, with temporary snapshot owners released before cache -selection. No leaking Literal borrow adapter is used to inspect runtime keys. -NaNs and aggregates containing them, code objects and code-containing aggregates, -and entire slice subtrees remain excluded. Noncanonical equal-text name tuple -winners are rejected without mutating them; the normalized-copy identity gap -remains. No global interning policy or native transport changes are made. - -This bottom-up optimized-graph materialization does not reproduce CPython's -parent-first recursive registration or earlier compiler/assembly first winners, -including discarded constants and input bytecode. Exact chronological sharing -and full marshal parity remain unresolved; finite passing witnesses cannot prove -those properties. Real pool owners are removed by ordinary drop, not count -adjustments. The runtime's existing GC policy remains unchanged. diff --git a/tools/opcode_metadata/conf.toml b/tools/opcode_metadata/conf.toml index a0cb9692755..e69de29bb2d 100644 --- a/tools/opcode_metadata/conf.toml +++ b/tools/opcode_metadata/conf.toml @@ -1,4 +0,0 @@ -# Private legacy instruction. The compiler must never emit this opcode. -[GetYieldFromIter.stack_effect] -popped = "1" -pushed = "1" diff --git a/tools/opcode_metadata/cpython.py b/tools/opcode_metadata/cpython.py index 5868d7d7483..56743a88842 100644 --- a/tools/opcode_metadata/cpython.py +++ b/tools/opcode_metadata/cpython.py @@ -1,37 +1,26 @@ import os import pathlib import sys -from functools import cache - -from target_source import verify_import_origins, verify_source try: CPYTHON_ROOT = pathlib.Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve() except KeyError: raise ValueError("Missing environment variable 'CPYTHON_ROOT'") -sys.dont_write_bytecode = True -verify_source(CPYTHON_ROOT) -verify_import_origins(CPYTHON_ROOT) - CPYTHON_TOOLS_LIB = CPYTHON_ROOT / "Tools" / "cases_generator" if (path := CPYTHON_TOOLS_LIB.as_posix()) not in sys.path: - sys.path.insert(0, path) + sys.path.append(path) from analyzer import SKIP_PROPERTIES, Analysis, Family, Properties, analyze_files from stack import get_stack_effect -verify_import_origins(CPYTHON_ROOT) - -@cache def get_analysis() -> Analysis: from generators_common import DEFAULT_INPUT - verify_import_origins(CPYTHON_ROOT) analysis = analyze_files([DEFAULT_INPUT]) # Our speration is done at the enum definition analysis.instructions |= analysis.pseudos diff --git a/tools/opcode_metadata/generate_public_metadata.py b/tools/opcode_metadata/generate_public_metadata.py deleted file mode 100644 index 2cbb55e9983..00000000000 --- a/tools/opcode_metadata/generate_public_metadata.py +++ /dev/null @@ -1,188 +0,0 @@ -"""Generate public _opcode descriptors without expanding executable opcodes.""" - -import argparse -import ast -import os -import re -import subprocess -from pathlib import Path - -from target_source import verify_source - -ROOT = Path(__file__).resolve().parents[2] - - -def expression(text): - """Translate the pinned grammar with an explicit panic-free i32 wrapping policy.""" - - def emit(node): - if isinstance(node, ast.Constant) and type(node.value) is int: - return f"{node.value}i32" - if isinstance(node, ast.Name) and node.id == "oparg": - return "oparg" - if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): - return f"({emit(node.operand)}).wrapping_neg()" - if isinstance(node, ast.BinOp): - left, right = emit(node.left), emit(node.right) - methods = { - ast.Add: "wrapping_add", - ast.Sub: "wrapping_sub", - ast.Mult: "wrapping_mul", - } - if method := methods.get(type(node.op)): - # The argument list already delimits this entire bit expression. - if isinstance(node.right, ast.BinOp) and isinstance( - node.right.op, (ast.BitAnd, ast.RShift) - ): - right = right[1:-1] - return f"({left}).{method}({right})" - if isinstance(node.op, ast.BitAnd): - return f"({left} & {right})" - if ( - isinstance(node.op, ast.RShift) - and isinstance(node.right, ast.Constant) - and 0 <= node.right.value < 32 - ): - return f"({left} >> {node.right.value})" - raise ValueError(f"unsupported pinned stack expression: {text}") - - return emit(ast.parse(text, mode="eval").body) - - -def generate(source): - verify_source(source) - ids = { - name: int(value) - for name, value in re.findall( - r"^#define\s+(\w+)\s+(\d+)\s*$", - (source / "Include/opcode_ids.h").read_text(), - re.M, - ) - } - header = (source / "Include/internal/pycore_opcode_metadata.h").read_text() - extent, body = re.search( - r"const struct opcode_metadata _PyOpcode_opcode_metadata\[(\d+)\] = \{(.*?)\n\};", - header, - re.S, - ).groups() - assert int(extent) == 267 - flag_values = { - name: int(value) - for name, value in re.findall( - r"^#define (HAS_\w+_FLAG) \((\d+)\)$", header, re.M - ) - } - descriptors = {} - for name, flags in re.findall(r"\[(\w+)\] = \{ true, (?:\w+|-1), (.*?) \},", body): - value = 0 - for flag in flags.split(" | "): - value |= 0 if flag == "0" else flag_values[flag] - descriptors[ids[name]] = (name, value) - assert len(descriptors) == 245 - deopt_body = re.search( - r"const uint8_t _PyOpcode_Deopt\[256\] = \{(.*?)\n\};", header, re.S - )[1] - number = lambda value: int(value) if value.isdigit() else ids[value] - deopt = { - number(a): number(b) for a, b in re.findall(r"\[(\w+)\] = (\w+),", deopt_body) - } - assert set(deopt) == set(range(256)) - utils = (source / "Include/internal/pycore_opcode_utils.h").read_text() - block = re.search( - r"#define IS_BLOCK_PUSH_OPCODE\(opcode\)(.*?)(?=\n\n)", utils, re.S - )[1] - block_ids = {ids[name] for name in re.findall(r"opcode\) == (\w+)", block)} - assert block_ids == { - ids[x] for x in ("SETUP_FINALLY", "SETUP_WITH", "SETUP_CLEANUP") - } - effects = [] - for kind in ("popped", "pushed"): - body = re.search( - rf"int _PyOpcode_num_{kind}\(int opcode, int oparg\) \{{(.*?)\n\}}", - header, - re.S, - )[1] - rows = { - ids[name]: expression(expr) - for name, expr in re.findall(r"case (\w+):\s*return (.*?);", body) - } - assert set(rows) == set(descriptors) - effects.append(rows) - rows = [] - for opcode in range(267): - if opcode not in descriptors: - rows.append("0,") - continue - name, flags = descriptors[opcode] - value = flags | (1 << 31) - if opcode < 256 and deopt[opcode] != opcode: - value |= 1 << 30 - if opcode in block_ids: - value |= 1 << 29 - rows.append(f"{value}, // {opcode}: {name}") - methods = "\n".join( - f"#[must_use] pub const fn {name}(self) -> bool {{ self.flags & {flag_values[flag]} != 0 }}" - for name, flag in ( - ("has_arg", "HAS_ARG_FLAG"), - ("has_const", "HAS_CONST_FLAG"), - ("has_name", "HAS_NAME_FLAG"), - ("has_jump", "HAS_JUMP_FLAG"), - ("has_free", "HAS_FREE_FLAG"), - ("has_local", "HAS_LOCAL_FLAG"), - ) - ) - arms = "\n".join( - f"{op} => ({effects[0][op]}, {effects[1][op]}), // {name}" - for op, (name, _) in sorted(descriptors.items()) - if not (op < 256 and deopt[op] != op) - ) - return f"""// Generated by tools/opcode_metadata/generate_public_metadata.py from pinned final CPython. -//! Public introspection metadata, independent of the VM's executable opcode set. -//! Describing an opcode does not enable execution of its CPython specialization. -const DESCRIPTORS: [u32; 267] = [{chr(10).join(rows)}\n]; -#[derive(Clone, Copy)] -pub struct PublicOpcode {{ opcode: u16, flags: u32 }} -impl PublicOpcode {{ - #[must_use] - pub fn from_i32(opcode: i32) -> Option {{ - let index = usize::try_from(opcode).ok()?; - let flags = *DESCRIPTORS.get(index)?; - (flags & (1 << 31) != 0).then_some(Self {{ opcode: u16::try_from(index).ok()?, flags }}) - }} - {methods} - #[must_use] pub const fn is_block_push(self) -> bool {{ self.flags & (1 << 29) != 0 }} - /// Existing caller conversion is intentionally separate from this metadata domain. - #[must_use] - pub fn stack_effect(self, oparg: u32, jump: Option) -> Option {{ - if self.flags & (1 << 30) != 0 {{ return None; }} - let oparg = oparg.cast_signed(); - let (popped, pushed) = match self.opcode {{ {arms}\n _ => return None }}; - if popped < 0 || pushed < 0 {{ return None; }} - if self.is_block_push() && jump == Some(false) {{ return Some(0); }} - Some(pushed.wrapping_sub(popped)) - }} -}} -""" - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--source", type=Path, default=os.environ.get("CPYTHON_ROOT")) - parser.add_argument("--check", action="store_true") - args = parser.parse_args() - if args.source is None: - parser.error("--source or CPYTHON_ROOT is required") - output = subprocess.check_output( - ["rustfmt", "--emit=stdout", "--edition=2024"], - input=generate(args.source), - text=True, - ) - target = ROOT / "crates/compiler-core/src/bytecode/public_metadata.rs" - if args.check: - assert target.read_text() == output - else: - target.write_text(output) - - -if __name__ == "__main__": - main() diff --git a/tools/opcode_metadata/generate_py_opcode_metadata.py b/tools/opcode_metadata/generate_py_opcode_metadata.py index c1d84252041..2341588cd5c 100644 --- a/tools/opcode_metadata/generate_py_opcode_metadata.py +++ b/tools/opcode_metadata/generate_py_opcode_metadata.py @@ -1,6 +1,104 @@ -"""Compatibility entry point: verify canonical metadata without rewriting Lib.""" +""" +Generate Lib/_opcode_metadata.py for RustPython bytecode. +""" + +import functools +import io +import itertools +import operator +import pathlib +import typing + +from opcodes import OpcodeInfo +from utils import DEFAULT_INPUT, ROOT, get_conf, to_pascal_case, to_upper_snake_case + +OUT_FILE = ROOT / "Lib/_opcode_metadata.py" + + +# Opcodes that needs to be first, regardless of their opcode ID. +PRIORITY_OPMAP = { + "CACHE", + "RESERVED", + "RESUME", + "INSTRUMENTED_LINE", + "ENTER_EXECUTOR", + "TRACE_RECORD", +} + +INDENT = " " * 4 + + +def main(): + override_conf = get_conf() + inp = DEFAULT_INPUT.read_text() + + infos = tuple(OpcodeInfo.iter_infos(inp, override_conf)) + opcodes = tuple(itertools.chain.from_iterable(info.opcodes for info in infos)) + + script_path = pathlib.Path(__file__).resolve().relative_to(ROOT).as_posix() + out = io.StringIO() + + out.write( + f""" +# This file is generated by {script_path} +# for RustPython bytecode format. +# Do not edit! +""".lstrip() + ) + + # _specializations + out.write("_specializations = frozendict(\n") + + deopts = functools.reduce(operator.ior, map(operator.attrgetter("deopts"), infos)) + + for key, lst in deopts.items(): + key = to_upper_snake_case(key) + out.write(f"{INDENT}{key}=(\n") + + for item in map(to_upper_snake_case, dict.fromkeys(lst)): + out.write(f'{INDENT * 3}"{item}",\n') + + out.write(f"{INDENT}),\n") + + out.write(")\n") + + # _specialized_opmap + out.write("\n") + out.write("_specialized_opmap = frozendict(\n") + + specialized = set(itertools.chain.from_iterable(deopts.values())) + for opcode in sorted(opcodes, key=lambda op: op.cpython_name): + if opcode.rust_name not in specialized: + continue + out.write(f"{INDENT}{opcode.cpython_name}={opcode.id},\n") + + out.write(")\n") + + # opmap + out.write("\n") + out.write("opmap = frozendict(\n") + + key = lambda op: (op.cpython_name not in PRIORITY_OPMAP, op.id) + for opcode in sorted(opcodes, key=key): + if opcode.rust_name in specialized: + continue + + out.write(f"{INDENT}{opcode.cpython_name}={opcode.id},\n") + + out.write(")\n") + + # min + out.write("\n") + have_argument = min(opcode.id for opcode in opcodes if opcode.have_argument) - 1 + out.write(f"HAVE_ARGUMENT = {have_argument}\n") + + min_instrumented = min(opcode.id for opcode in opcodes if opcode.is_instrumented) + out.write(f"MIN_INSTRUMENTED_OPCODE = {min_instrumented}\n") + + # write output + generated = out.getvalue() + OUT_FILE.write_text(generated) -from verify_py_opcode_metadata import main if __name__ == "__main__": main() diff --git a/tools/opcode_metadata/opcodes.py b/tools/opcode_metadata/opcodes.py index 99c49328bc5..9178921b2c0 100644 --- a/tools/opcode_metadata/opcodes.py +++ b/tools/opcode_metadata/opcodes.py @@ -38,8 +38,6 @@ def deopts(self) -> dict[str, list[str]]: res[family_name].append(member_name) - if "LoadConstImmortal" in names: - res["LoadConst"].extend(["LoadConstImmortal", "LoadConstMortal"]) return dict(res) def __iter__(self): @@ -83,11 +81,7 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]: cpython_name = opcode.cpython_name kwargs = {} - if instr := analysis.instructions.get( - "LOAD_CONST" - if rust_name in ("LoadConstImmortal", "LoadConstMortal") - else cpython_name - ): + if instr := analysis.instructions.get(cpython_name): kwargs["properties"] = properties_with_pseudo_flags(instr) kwargs["family"] = getattr(instr, "family", None) kwargs["cache_entry"] = getattr(instr, "size", -1) diff --git a/tools/opcode_metadata/stack_contract.c b/tools/opcode_metadata/stack_contract.c deleted file mode 100644 index 2b33f27c8c1..00000000000 --- a/tools/opcode_metadata/stack_contract.c +++ /dev/null @@ -1,18 +0,0 @@ -#define Py_BUILD_CORE -#define NEED_OPCODE_METADATA -#include "Python.h" -#include "pycore_opcode_metadata.h" -#include -int main(void) { - const int args[] = {0,1,2,3,4,5,7,8,15,16,31,255,256,65535}; - for (int op=0; op<512; op++) { - for (unsigned int i=0; i= 0 && pushed >= 0) { - printf("%d %d %d %d\n",op,args[i],popped,pushed); - } - } - } - return 0; -} diff --git a/tools/opcode_metadata/target_source.json b/tools/opcode_metadata/target_source.json deleted file mode 100644 index 60ec701a0d9..00000000000 --- a/tools/opcode_metadata/target_source.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "commit": "d220409a13b77de7a6c30abe16ea90db01528edc", - "files": { - "Include/internal/pycore_interpframe.h": "87f52ece3a11f8dfde0274c95c7064efc90b859dd73386cca8cdb83609f21821", - "Include/internal/pycore_opcode_metadata.h": "ce5fb59eadbe061266d7582d5cdd0bf655bb93d6d08fb660a8edf0a19c198fec", - "Include/internal/pycore_opcode_utils.h": "17bb85a7bcabbc29a505949512527f97070daf68f8692f015ac8b6a0f0f0c4e3", - "Include/internal/pycore_uop_ids.h": "dc3b00ba46b803ae4c46cb6b5063ee6814a9330f586692ff4bb500a0b888c421", - "Include/opcode_ids.h": "ffd77843d3b0b58ba476fdc2eca3afc0436b6c5a97adc0cae7e39c1bd0221778", - "Lib/_opcode_metadata.py": "3a451ba73726b21e47424721df8fb42dc43b7e9cc29579cd60e36e9bc2157d7e", - "Lib/dis.py": "bfcd5b49946afdc19b8c2c18ed093af2076e6f3f6c1fb65b5d999830d6ff1af2", - "Lib/opcode.py": "2afb1b49a8f0e8f41e70b5d9192181ea31cb2844c9b298275722f4e698cd0b39", - "Modules/_opcode.c": "f702c64dd389ab42121fff2374a9d0a8ffdd3418b8f717f158a13095f17a7de2", - "Modules/clinic/_opcode.c.h": "b561a19c951b03c3a3a4ef2b665c6e873a128c8b2adfabcf6e0b770345df1f9d", - "Objects/codeobject.c": "b95a59389e3da52283d96b9bfd1a6479ca5d7a6e45c4eeadf5433d88fa84e6d8", - "Objects/frameobject.c": "84d0268c5248e5049158b63ab82556e04be6122f8ebc400bf7723a13b9b98c62", - "Objects/longobject.c": "1e3bb095f475dd25e020bfd33365ad5bbab7f21b7c6439c9bd7dc79839ede8dc", - "Objects/unicodeobject.c": "dcb23f28d01be5b0aa344e7f4d6f79b58cb958139f67f3747cbfd0867ab68f5a", - "Python/assemble.c": "831156f971b07c1ceae22c97a90e9e56363b4a2856896f5383690a30509b94c8", - "Python/bytecodes.c": "aa81f2f18d44cf4e04aa75b5e00e5e50b022c560028030bd062d781bd734c9ea", - "Python/codegen.c": "536a4f1c79b1e0aa292f9954d9e1ca8b02e6c3eddbd08dd111a0998c41848a5d", - "Python/compile.c": "4bff663c758db06b355305406771bc8289af275ca5391daf798aa68d5250f098", - "Python/flowgraph.c": "331f430d7e028b7bd2ab58ec44f7ffaf4a7ac4d53985cdf723477d9406b12aac", - "Python/instrumentation.c": "1650fd63b57447c4ac6d8cd7a8dbd81e5bf0124fd4ce7615b2c348880757fadf", - "Python/marshal.c": "2d2fe69cf2e2f2fcc2693084b91c615ba8c0edba83b003b8bc6408bf1edf77e1", - "Python/specialize.c": "372091addcc5d351b6960ecd9e181ef6ea4af7f367f6d0b5aa655c2a2f7334ce", - "Python/sysmodule.c": "a7c715a7d5db0ffa601b151235dfe7e928b57bd8150cf02eafcd1d5cafcde25f", - "Tools/cases_generator/_typing_backports.py": "4fe25f5705e4f9f047ba330c70264032291c64d24cbd5f2d6da0615978521021", - "Tools/cases_generator/analyzer.py": "10c410377629b4897cf1289bc84743e61c67c0a8c09ab5b4aba0b7fc74809763", - "Tools/cases_generator/cwriter.py": "fb65713b1ad11c5dc190f50f6bb0ed026d5b582c9e5bac623037c000e25c9403", - "Tools/cases_generator/generators_common.py": "06912332943c5962c45314d92bb7a40d95802c165b3dc791d9680e1c5a450118", - "Tools/cases_generator/lexer.py": "80f2c5b97e012e0acda4b0a3de38506dbdd926e64f6328344172246d34ee0c4a", - "Tools/cases_generator/opcode_id_generator.py": "69535cc326af8b3b649000c96d7f609f79a3d7b0a737a56469a9b5c442914966", - "Tools/cases_generator/opcode_metadata_generator.py": "48bc3b643528a6d9eee6a48361882b5a2e30024fe74ae9f1a062f1d9524dd2d3", - "Tools/cases_generator/optimizer_generator.py": "3cf3e9e3881b33700a851732c18ab32db4dddd4488ecc7b83e6e404caffd7e2f", - "Tools/cases_generator/parser.py": "9f88319c6f461740cbfda18feb338a04276cd6672b5475956b155ba62f8912f6", - "Tools/cases_generator/parsing.py": "02f90f5a1dc0c9ded0fcadf74b2f703301b0e5038ec0f2fdc0f185942ed77928", - "Tools/cases_generator/plexer.py": "c13ce4a0b558791b1843e0969ee6abfc3e094e2116cc817fc1101dee9ccbb876", - "Tools/cases_generator/py_metadata_generator.py": "23ba6de30dfc06bc7189e7636c98604fdf1e654af504237699a5b68a19fbca20", - "Tools/cases_generator/record_function_generator.py": "13b66491a9f50df69ede5af3cf67dfa3b1ad688baf85c17f418a1ac29a1b107a", - "Tools/cases_generator/stack.py": "2e192c11d7264d322cf691a3f66ce687459869c9b132710868a69d225103a4a1", - "Tools/cases_generator/target_generator.py": "c7087ffceda3ac08c58aeb756f6a1326fd3a2dac4514bc2124b4660327c3d8b5", - "Tools/cases_generator/tier1_generator.py": "a0c2ae9eb48d6e84f9b5fe3857ffb2aabb32e6231995a5e2c2a32d0e44788b57", - "Tools/cases_generator/tier2_generator.py": "cb44cf5c1493a79b01b779bbf9b8efcf78bee1d339c03d3afe4c050e576fb09d", - "Tools/cases_generator/uop_id_generator.py": "3aec741aed85baa041a9f40e1bb6019dd4cc35ae4d4de0b0c5dd092011fa439f", - "Tools/cases_generator/uop_metadata_generator.py": "d95e5001035eda47642ecd70d2884b39f7dd69547bcd9c77dd9a341e2e088f3b" - }, - "tag": "v3.15.0" -} diff --git a/tools/opcode_metadata/target_source.py b/tools/opcode_metadata/target_source.py deleted file mode 100644 index 68d993aaf41..00000000000 --- a/tools/opcode_metadata/target_source.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Fail closed before importing any target CPython generator code.""" - -import hashlib -import json -import sys -from pathlib import Path - - -def verify_source(root): - caches = list((root / "Tools/cases_generator").rglob("*.pyc")) - if caches: - raise ValueError( - "target generator source must be cache-free; found " + str(caches[0]) - ) - manifest = json.loads(Path(__file__).with_suffix(".json").read_text()) - for relative, expected in manifest["files"].items(): - path = root / relative - actual = hashlib.sha256(path.read_bytes()).hexdigest() - if actual != expected: - raise ValueError(f"wrong {manifest['tag']} source: {relative}: {actual}") - return manifest - - -def verify_import_origins(root): - manifest = json.loads(Path(__file__).with_suffix(".json").read_text()) - for relative in manifest["files"]: - path = Path(relative) - if path.parent != Path("Tools/cases_generator") or path.suffix != ".py": - continue - module = sys.modules.get(path.stem) - if module is not None: - origin = getattr(module, "__file__", None) - if origin is None or Path(origin).resolve() != (root / path).resolve(): - raise ValueError( - "generator dependency loaded outside pinned source: " + path.stem - ) diff --git a/tools/opcode_metadata/verify_opcode_ids.py b/tools/opcode_metadata/verify_opcode_ids.py deleted file mode 100644 index c2a8d4da252..00000000000 --- a/tools/opcode_metadata/verify_opcode_ids.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Verify every active public discriminant against pinned final opcode_ids.h.""" - -import argparse -import re -from pathlib import Path - -from target_source import verify_source - -from utils import DEFAULT_INPUT, to_upper_snake_case - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--source", required=True, type=Path) - args = parser.parse_args() - verify_source(args.source) - ids = { - name: int(value) - for name, value in re.findall( - r"^#define\s+(\w+)\s+(\d+)\s*$", - (args.source / "Include/opcode_ids.h").read_text(), - re.M, - ) - } - private = { - "GetYieldFromIter": 219, - "LoadConstImmortal": 220, - "LoadConstMortal": 221, - } - entries = re.findall( - r"^ (\w+)(?:\s*\{[^}]*\})?\s*=\s*(\d+),", - DEFAULT_INPUT.read_text(), - re.M, - ) - assert len(entries) == 238 - assert len({int(value) for _, value in entries}) == len(entries) - for name, value in entries: - expected = private[name] if name in private else ids[to_upper_snake_case(name)] - assert int(value) == expected, (name, value, expected) - print("Verified 235 public and 3 explicitly private opcode discriminants") - - -if __name__ == "__main__": - main() diff --git a/tools/opcode_metadata/verify_py_opcode_metadata.py b/tools/opcode_metadata/verify_py_opcode_metadata.py deleted file mode 100644 index e0acbf94e90..00000000000 --- a/tools/opcode_metadata/verify_py_opcode_metadata.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Verify exact final Python metadata; refresh it only with scripts/update_lib.""" - -import os -from pathlib import Path - -from target_source import verify_source - -from utils import ROOT - - -def main(): - source = Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve() - verify_source(source) - for name in ("opcode.py", "_opcode_metadata.py", "dis.py"): - relative = Path("Lib") / name - if (ROOT / relative).read_bytes() != (source / relative).read_bytes(): - raise ValueError( - f"{relative} differs from pinned final source; use scripts/update_lib" - ) - print(f"Verified {relative}") - - -if __name__ == "__main__": - main() diff --git a/tools/opcode_metadata/verify_stack_contract.py b/tools/opcode_metadata/verify_stack_contract.py deleted file mode 100644 index f0ac88780f5..00000000000 --- a/tools/opcode_metadata/verify_stack_contract.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Regenerate independent C stack goldens, or fail if checked-in rows differ.""" - -import argparse -import subprocess -import tempfile -from pathlib import Path - -from target_source import verify_source - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--source", type=Path, required=True) - parser.add_argument("--build", type=Path, required=True) - parser.add_argument("--write", action="store_true") - args = parser.parse_args() - verify_source(args.source) - here = Path(__file__).resolve().parent - with tempfile.TemporaryDirectory() as temporary: - binary = Path(temporary) / "stack-contract" - subprocess.run( - [ - "cc", - "-I" + str(args.build), - "-I" + str(args.source / "Include"), - "-I" + str(args.source / "Include/internal"), - str(here / "stack_contract.c"), - "-o", - str(binary), - ], - check=True, - ) - output = subprocess.check_output([str(binary)], text=True) - rows = [tuple(map(int, line.split())) for line in output.splitlines()] - assert len(rows) == 3430 and len(set(rows)) == 3430 - contents = ( - "// Generated by tools/opcode_metadata/verify_stack_contract.py from pinned CPython C metadata.\n" - "const TARGET_STACK_CASES: &[(u16, u32, u32, u32)] = &[\n" - + "".join(f" ({op}, {arg}, {pop}, {push}),\n" for op, arg, pop, push in rows) - + "];\n" - ) - target = here.parents[1] / "crates/compiler-core/src/bytecode/target_stack_cases.rs" - if args.write: - target.write_text(contents) - elif target.read_text() != contents: - raise SystemExit("pinned C stack rows differ") - print(f"Verified {len(rows)} exact C stack rows") - - -if __name__ == "__main__": - main() From 94efced7d943f5df69a811aa5dfd0292dc3fea32 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 14 Oct 2026 17:01:48 +0900 Subject: [PATCH 09/12] Remove generator snippet checks covered by Lib/test Drop extra_tests/snippets/generator_iterator_prefix.py. Its loop, delegation, async, f_back and never-awaited checks are covered by test_generators, test_coroutines, test_asyncgen, test_yield_from and test_monitoring. Move the remaining checks into existing files: genexpr prefix tracing and monitoring into stdlib_sys.py, and the caller frame seen from the eager iterator into stdlib_sys_getframe.py. Assisted-by: Claude Code:claude-opus-5-5 --- .../snippets/generator_iterator_prefix.py | 501 ------------------ extra_tests/snippets/stdlib_sys.py | 86 +++ extra_tests/snippets/stdlib_sys_getframe.py | 23 + 3 files changed, 109 insertions(+), 501 deletions(-) delete mode 100644 extra_tests/snippets/generator_iterator_prefix.py diff --git a/extra_tests/snippets/generator_iterator_prefix.py b/extra_tests/snippets/generator_iterator_prefix.py deleted file mode 100644 index 22596f21489..00000000000 --- a/extra_tests/snippets/generator_iterator_prefix.py +++ /dev/null @@ -1,501 +0,0 @@ -"""CPython 3.15 iterator-pair, generator-creation, and SEND regression checks.""" - - -def test_iterator_pairs_survive_specialized_loops_and_unwinding(): - class Iterator: - def __init__(self): - self.index = 0 - - def __iter__(self): - return self - - def __next__(self): - if self.index == 4: - raise StopIteration - value = self.index - self.index += 1 - return value - - def values(): - yield from range(4) - - def consume(items): - total = 0 - for item in items: - total += item - return total - - def leave(items): - for item in items: - for inner in range(3): - if inner == 1: - return item + inner - return -1 - - for warmup in range(200): - assert consume(range(4)) == 6 - assert consume([0, 1, 2, 3]) == 6 - assert consume((0, 1, 2, 3)) == 6 - assert consume(values()) == 6 - assert consume(Iterator()) == 6 - assert leave(range(4)) == 1 - assert [x * y for x in range(3) for y in range(2)] == [0, 0, 0, 1, 0, 2] - for x in range(5): - if x == 2: - break - assert x == 2 - - -def test_eager_generator_prefix_preserves_laziness_and_created_state(): - events = [] - - class Iterable: - def __iter__(self): - events.append("iter") - return iter((4, 5)) - - def body(value): - events.append(value) - return value - - g = (body(value) for value in Iterable()) - assert events == ["iter"] - assert not g.gi_suspended - try: - g.send(1) - except TypeError: - pass - else: - raise AssertionError("created generator accepted non-None") - assert events == ["iter"] - assert next(g) == 4 - assert events == ["iter", 4] - assert g.gi_suspended - assert list(g) == [5] - - class BadIterable: - def __iter__(self): - raise ValueError("eager") - - try: - (value for value in BadIterable()) - except ValueError as error: - assert error.args == ("eager",) - else: - raise AssertionError("iterator acquisition was deferred") - - def outer(value): - def generator(): - events.append("body") - yield value - - return generator() - - g = outer(17) - assert "body" not in events - g.close() - assert "body" not in events - assert list(outer(19)) == [19] - - -def test_send_pairs_support_delegation_and_throw_cleanup(): - def leaf(): - sent = yield 1 - return sent - - def chain(depth): - if depth: - return (yield from chain(depth - 1)) - return (yield from leaf()) - - for warmup in range(200): - g = chain(12) - assert next(g) == 1 - assert g.gi_yieldfrom is not None - try: - g.send(17) - except StopIteration as error: - assert error.value == 17 - else: - raise AssertionError("delegation did not finish") - - class Delegate: - def __iter__(self): - return self - - def __next__(self): - return 3 - - def send(self, value): - return value - - def throw(self, error): - raise StopIteration(23) - - def custom(): - result = yield from Delegate() - yield result - - g = custom() - assert next(g) == 3 - assert g.send(9) == 9 - assert g.throw(ValueError) == 23 - assert list(g) == [] - - class RaisingDelegate(Delegate): - def throw(self, error): - raise RuntimeError("delegate") - - def raises(): - try: - yield from RaisingDelegate() - except RuntimeError: - yield "caught" - - g = raises() - assert next(g) == 3 - assert g.throw(RuntimeError) == "caught" - assert list(g) == [] - - events = [] - - def close_leaf(): - try: - yield 1 - finally: - events.append("closed") - - def close_outer(): - yield from close_leaf() - - g = close_outer() - assert next(g) == 1 - g.close() - assert events == ["closed"] - - -def test_await_and_async_generator_expression_keep_iterator_shapes(): - events = [] - - class AsyncIterator: - def __init__(self): - self.index = 0 - - def __aiter__(self): - events.append("aiter") - return self - - async def __anext__(self): - if self.index == 3: - raise StopAsyncIteration - value = self.index - self.index += 1 - return value - - g = (value * 2 async for value in AsyncIterator()) - assert events == ["aiter"] - assert not g.ag_running - - async def consume(): - result = [] - async for value in g: - result.append(value) - return result - - coro = consume() - try: - coro.send(None) - except StopIteration as error: - assert error.value == [0, 2, 4] - else: - raise AssertionError("unexpected coroutine suspension") - assert events == ["aiter"] - - class Awaitable: - def __await__(self): - sent = yield "pause" - return sent - - async def awaits(): - return await Awaitable() - - for warmup in range(200): - coro = awaits() - assert coro.send(None) == "pause" - assert coro.cr_await is not None - try: - coro.send(31) - except StopIteration as error: - assert error.value == 31 - else: - raise AssertionError("await did not finish") - - -test_iterator_pairs_survive_specialized_loops_and_unwinding() -test_eager_generator_prefix_preserves_laziness_and_created_state() -test_send_pairs_support_delegation_and_throw_cleanup() -test_await_and_async_generator_expression_keep_iterator_shapes() - - -def test_eager_prefix_is_untraceable_but_reports_monitoring_errors(): - import sys - - trace = [] - - class Good: - def __iter__(self): - return iter([1]) - - class Bad: - def __iter__(self): - raise ValueError("eager iterator") - - def create(iterable): - return (value for value in iterable) - - def tracer(frame, event, arg): - if frame.f_code.co_name in ("create", "", "__iter__"): - trace.append((frame.f_code.co_name, event)) - return tracer - - sys.settrace(tracer) - try: - generator = create(Good()) - generator.close() - try: - create(Bad()) - except ValueError as error: - names = [] - traceback = error.__traceback__ - while traceback is not None: - names.append(traceback.tb_frame.f_code.co_name) - traceback = traceback.tb_next - else: - raise AssertionError("expected eager iterator error") - finally: - sys.settrace(None) - assert not any(name == "" for name, event in trace) - assert trace == [ - ("create", "call"), - ("create", "line"), - ("__iter__", "call"), - ("__iter__", "line"), - ("__iter__", "return"), - ("create", "return"), - ("create", "call"), - ("create", "line"), - ("__iter__", "call"), - ("__iter__", "line"), - ("__iter__", "exception"), - ("__iter__", "return"), - ("create", "exception"), - ("create", "return"), - ("create", "exception"), - ("create", "return"), - ] - assert ("__iter__", "call") in trace - assert ("__iter__", "exception") in trace - assert names == [ - "test_eager_prefix_is_untraceable_but_reports_monitoring_errors", - "create", - "__iter__", - ] - - monitoring = sys.monitoring - records = [] - event_names = ( - "PY_START", - "PY_RESUME", - "PY_UNWIND", - "RAISE", - "LINE", - "INSTRUCTION", - ) - monitoring.use_tool_id(2, "iterator-contract-315") - try: - for name in event_names: - event = getattr(monitoring.events, name) - - def callback(code, *args, name=name): - if code.co_name == "": - records.append(name) - - monitoring.register_callback(2, event, callback) - monitoring.set_events( - 2, sum(getattr(monitoring.events, name) for name in event_names) - ) - generator = create(Good()) - generator.close() - assert records == [] - try: - create(Bad()) - except ValueError: - pass - else: - raise AssertionError("expected monitored eager iterator error") - finally: - monitoring.set_events(2, 0) - monitoring.free_tool_id(2) - assert records == ["RAISE", "PY_UNWIND"] - - -test_eager_prefix_is_untraceable_but_reports_monitoring_errors() - - -def test_created_generator_frames_do_not_retain_materialized_caller(): - import sys - - def generator(): - yield 1 - - async def coroutine(): - return 1 - - async def async_generator(): - yield 1 - - def create(): - caller = sys._getframe() - return ( - (value for value in [1]), - generator(), - coroutine(), - async_generator(), - caller, - ) - - genexpr, gen, coro, agen, caller = create() - assert caller.f_code.co_name == "create" - assert genexpr.gi_frame.f_back is None - assert gen.gi_frame.f_back is None - assert coro.cr_frame.f_back is None - assert agen.ag_frame.f_back is None - genexpr.close() - gen.close() - coro.close() - close = agen.aclose() - try: - close.send(None) - except StopIteration: - pass - else: - raise AssertionError("unstarted async generator close suspended") - - -test_created_generator_frames_do_not_retain_materialized_caller() - - -def test_instrumented_iteration_and_send_keep_both_iterator_slots(): - import sys - - def iterate(values): - result = [] - for value in values: - result.append(value) - return result - - def leaf(): - yield 1 - return 9 - - def delegate(): - return (yield from leaf()) - - monitoring = sys.monitoring - records = [] - event_names = ("BRANCH_LEFT", "BRANCH_RIGHT", "STOP_ITERATION", "PY_YIELD") - monitoring.use_tool_id(2, "instrumented-iterator-contract-315") - try: - for name in event_names: - event = getattr(monitoring.events, name) - - def callback(code, *args, name=name): - if code.co_name in ("iterate", "delegate"): - records.append((code.co_name, name)) - - monitoring.register_callback(2, event, callback) - monitoring.set_events( - 2, sum(getattr(monitoring.events, name) for name in event_names) - ) - assert iterate(range(3)) == [0, 1, 2] - assert iterate([0, 1, 2]) == [0, 1, 2] - assert iterate((0, 1, 2)) == [0, 1, 2] - assert iterate(value for value in range(3)) == [0, 1, 2] - generator = delegate() - assert next(generator) == 1 - try: - next(generator) - except StopIteration as error: - assert error.value == 9 - else: - raise AssertionError("instrumented delegate did not finish") - finally: - monitoring.set_events(2, 0) - monitoring.free_tool_id(2) - assert ("iterate", "BRANCH_LEFT") in records - assert ("iterate", "BRANCH_RIGHT") in records - assert ("delegate", "PY_YIELD") in records - assert ("delegate", "STOP_ITERATION") in records - - -test_instrumented_iteration_and_send_keep_both_iterator_slots() - - -def test_eager_iterator_frame_inspection_skips_incomplete_generator(): - import sys - - iterator_frames = [] - caller_frames = [] - seen = [] - - class Iterable: - def __iter__(self): - frame = sys._getframe() - caller = sys._getframe(1) - iterator_frames.append(frame) - caller_frames.append(caller) - seen.append((caller.f_code.co_name, frame.f_back.f_code.co_name)) - return iter([7]) - - def create(): - return (value for value in Iterable()) - - generator = create() - assert seen == [("create", "create")] - # Both real frames have returned; retained f_back must still refer to - # the complete creator rather than its hidden generator preamble. - assert iterator_frames[0].f_back is caller_frames[0] - assert iterator_frames[0].f_back.f_code.co_name == "create" - assert generator.gi_frame.f_back is None - assert list(generator) == [7] - - -test_eager_iterator_frame_inspection_skips_incomplete_generator() - - -def test_unstarted_coroutine_warning_survives_creation_preamble(): - import gc - import warnings - - async def pending(): - return 7 - - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always", RuntimeWarning) - coroutine = pending() - del coroutine - gc.collect() - assert len(caught) == 1 - assert caught[0].category is RuntimeWarning - assert str(caught[0].message).endswith("pending' was never awaited") - - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always", RuntimeWarning) - coroutine = pending() - coroutine.close() - del coroutine - gc.collect() - assert caught == [] - - -test_unstarted_coroutine_warning_survives_creation_preamble() diff --git a/extra_tests/snippets/stdlib_sys.py b/extra_tests/snippets/stdlib_sys.py index c785a092efc..40daf9530f9 100644 --- a/extra_tests/snippets/stdlib_sys.py +++ b/extra_tests/snippets/stdlib_sys.py @@ -69,6 +69,92 @@ def demo(x): assert ("demo", "call", None) in events +# The eager iterator prefix of a generator expression is not traced, but +# monitoring still reports the error it raises. + + +class GoodIterable: + def __iter__(self): + return iter([1]) + + +class BadIterable: + def __iter__(self): + raise ValueError("eager iterator") + + +def create_genexpr(iterable): + return (value for value in iterable) + + +events = [] + + +def trc(frame, event, arg): + if frame.f_code.co_name in ("create_genexpr", "", "__iter__"): + events.append((frame.f_code.co_name, event)) + return trc + + +sys.settrace(trc) +try: + create_genexpr(GoodIterable()).close() + try: + create_genexpr(BadIterable()) + except ValueError as error: + tb_names = [] + tb = error.__traceback__ + while tb is not None: + tb_names.append(tb.tb_frame.f_code.co_name) + tb = tb.tb_next + else: + raise AssertionError("expected eager iterator error") +finally: + sys.settrace(None) +assert events == [ + ("create_genexpr", "call"), + ("create_genexpr", "line"), + ("__iter__", "call"), + ("__iter__", "line"), + ("__iter__", "return"), + ("create_genexpr", "return"), + ("create_genexpr", "call"), + ("create_genexpr", "line"), + ("__iter__", "call"), + ("__iter__", "line"), + ("__iter__", "exception"), + ("__iter__", "return"), + ("create_genexpr", "exception"), + ("create_genexpr", "return"), + ("create_genexpr", "exception"), + ("create_genexpr", "return"), +], events +assert tb_names == ["", "create_genexpr", "__iter__"], tb_names + +monitoring = sys.monitoring +event_names = ("PY_START", "PY_RESUME", "PY_UNWIND", "RAISE", "LINE", "INSTRUCTION") +records = [] +monitoring.use_tool_id(2, "genexpr-prefix") +try: + for name in event_names: + + def callback(code, *args, name=name): + if code.co_name == "": + records.append(name) + + monitoring.register_callback(2, getattr(monitoring.events, name), callback) + monitoring.set_events( + 2, sum(getattr(monitoring.events, name) for name in event_names) + ) + create_genexpr(GoodIterable()).close() + assert records == [], records + with assert_raises(ValueError): + create_genexpr(BadIterable()) +finally: + monitoring.set_events(2, 0) + monitoring.free_tool_id(2) +assert records == ["RAISE", "PY_UNWIND"], records + assert sys.exc_info() == (None, None, None) try: diff --git a/extra_tests/snippets/stdlib_sys_getframe.py b/extra_tests/snippets/stdlib_sys_getframe.py index 50447ce8822..80b63187d67 100644 --- a/extra_tests/snippets/stdlib_sys_getframe.py +++ b/extra_tests/snippets/stdlib_sys_getframe.py @@ -23,3 +23,26 @@ def __init__(self): TestClass() + + +# A frame entered from a generator expression's eager iterator prefix sees +# the creating function as its caller. +iterator_frames = [] + + +class Iterable: + def __iter__(self): + iterator_frames.append((sys._getframe(), sys._getframe(1))) + return iter([7]) + + +def create(): + return (value for value in Iterable()) + + +generator = create() +frame, caller = iterator_frames[0] +assert caller.f_code.co_name == "create" +assert frame.f_back is caller +assert generator.gi_frame.f_back is None +assert list(generator) == [7] From 9ad52b3cfb9d5b9cc2da8bd0ba102d200d67eceb Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 14 Oct 2026 17:30:01 +0900 Subject: [PATCH 10/12] Emit the generator prefix for top-level await modules Module code compiled with PyCF_ALLOW_TOP_LEVEL_AWAIT and marked ste_coroutine now starts with RETURN_GENERATOR; POP_TOP. The prefix and RESUME emission move into emit_scope_prefix, shared by enter_scope and the module entry points, as in codegen_enter_scope. Fixes test_builtin.test_compile_top_level_await. Assisted-by: Claude Code:claude-opus-5-5 --- crates/codegen/src/compile.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 1de20568f08..257169e34bd 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -2155,6 +2155,15 @@ impl<'warnings> Compiler<'warnings> { self.set_qualname(); } + self.emit_scope_prefix(scope_type, lineno, is_generator); + + Ok(()) + } + + /// Emit the generator prefix and RESUME, as the tail of + /// codegen_enter_scope does. `is_generator` is + /// `ste_coroutine || ste_generator`. + fn emit_scope_prefix(&mut self, scope_type: CompilerScope, lineno: u32, is_generator: bool) { // Add the generator prefix instructions. if is_generator { // RETURN_GENERATOR + POP_TOP have a net stack effect of 0, because @@ -2176,16 +2185,6 @@ impl<'warnings> Compiler<'warnings> { } } - // Emit RESUME (handles async preamble and module lineno 0) - // CPython: LOCATION(lineno, lineno, 0, 0), then loc.lineno = 0 for module - self.emit_resume_for_scope(scope_type, lineno); - - Ok(()) - } - - /// Emit RESUME instruction with proper handling for module lineno. - /// codegen_enter_scope equivalent for RESUME emission. - fn emit_resume_for_scope(&mut self, scope_type: CompilerScope, lineno: u32) { // CPython: LOCATION(lineno, lineno, 0, 0) // Module scope: loc.lineno = 0 (before the first line) let lineno_override = if scope_type == CompilerScope::Module { @@ -2966,7 +2965,8 @@ impl<'warnings> Compiler<'warnings> { self.symbol_table_stack.push(symbol_table); - self.emit_resume_for_scope(CompilerScope::Module, 1); + let is_coroutine = self.current_symbol_table().is_coroutine; + self.emit_scope_prefix(CompilerScope::Module, 1, is_coroutine); emit!(self, PseudoInstruction::AnnotationsPlaceholder); let (doc, statements) = split_doc_with_range(&body.body, &self.opts); @@ -3047,7 +3047,8 @@ impl<'warnings> Compiler<'warnings> { self.symbol_table_stack.push(symbol_table); let module_start_loc = self.module_start_location(body); - self.emit_resume_for_scope(CompilerScope::Module, 1); + let is_coroutine = self.current_symbol_table().is_coroutine; + self.emit_scope_prefix(CompilerScope::Module, 1, is_coroutine); emit!(self, PseudoInstruction::AnnotationsPlaceholder); let annotations_used = self.current_symbol_table().annotations_used; @@ -3087,7 +3088,8 @@ impl<'warnings> Compiler<'warnings> { .insert(bytecode::CodeFlags::COROUTINE); } self.symbol_table_stack.push(symbol_table); - self.emit_resume_for_scope(CompilerScope::Module, 1); + let is_coroutine = self.current_symbol_table().is_coroutine; + self.emit_scope_prefix(CompilerScope::Module, 1, is_coroutine); if let Some((last_statement, statements)) = body.split_last() { self.compile_statements(statements)?; @@ -3161,7 +3163,8 @@ impl<'warnings> Compiler<'warnings> { .insert(bytecode::CodeFlags::COROUTINE); } self.symbol_table_stack.push(symbol_table); - self.emit_resume_for_scope(CompilerScope::Module, 1); + let is_coroutine = self.current_symbol_table().is_coroutine; + self.emit_scope_prefix(CompilerScope::Module, 1, is_coroutine); self.compile_expression(&expression.body)?; self.emit_return_value(); From 32a7e664cb8e5faf1c46a8d16c08f8c41a2e64f8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 14 Oct 2026 19:28:40 +0900 Subject: [PATCH 11/12] Allow the c_long cast in _opcode.stack_effect c_long is i32 on Windows, where clippy reports the cast to i32 as unnecessary. Assisted-by: Claude Code:claude-opus-5-5 --- crates/stdlib/src/_opcode.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/stdlib/src/_opcode.rs b/crates/stdlib/src/_opcode.rs index ab942f7b106..d7fd9a8f61d 100644 --- a/crates/stdlib/src/_opcode.rs +++ b/crates/stdlib/src/_opcode.rs @@ -32,6 +32,7 @@ mod _opcode { } #[pyfunction] + #[allow(clippy::unnecessary_cast)] // `c_long` is `i32` or `i64` fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult { let oparg = match args.oparg { // (int)PyLong_AsLong(oparg) From 79866967b14484723d74253f3e5ac699b80bd9dd Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 14 Oct 2026 19:28:42 +0900 Subject: [PATCH 12/12] Update malachite crates to 0.14.0 pymath requires malachite-bigint ^0, so the example projects, which have no lock file, resolve it to 0.14.0 while the workspace pinned 0.13.0, and fail to build. Assisted-by: Claude Code:claude-opus-5-5 --- Cargo.lock | 25 +++++++++++++------------ Cargo.toml | 6 +++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f541d0c3eeb..e7c3c7f01be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2378,9 +2378,9 @@ dependencies = [ [[package]] name = "malachite-base" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f9f06976ea22081565c6f4ddc40e699982c1c6b9bd1bd4871b92eb986f2307" +checksum = "d5399d024a5e8fd066c158d208a02fb2dabb737c401ab03954f27d170fd49ffc" dependencies = [ "hashbrown 0.16.1", "itertools 0.14.0", @@ -2390,9 +2390,9 @@ dependencies = [ [[package]] name = "malachite-bigint" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0ea1859fa74e642919ea67bb7afe2e663f88f2e59afdeafda4a0b5ec6855fb" +checksum = "2ba82fa23fb2f63fcebd9ca012d885d41d4e95c9f14904df6cdcad9232646b2a" dependencies = [ "malachite-base", "malachite-nz", @@ -2403,10 +2403,11 @@ dependencies = [ [[package]] name = "malachite-nz" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5eef5870b4c0072ab10e28ed6e8228ae224f188bdbb65c739fcc189248381f5" +checksum = "7ac55a9e8426447c374668b93c35b7b6ab43a21dc7ba97ad0e852be2305a5900" dependencies = [ + "bytemuck", "itertools 0.14.0", "libm", "malachite-base", @@ -2415,9 +2416,9 @@ dependencies = [ [[package]] name = "malachite-q" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ff775589824d11de83716ee96d70b7a4fbd70794415ba363b6aeed272010f7" +checksum = "070267491ad6c23e504a71368542a2376d62f6622286be47870d7dcd56f24fce" dependencies = [ "itertools 0.14.0", "libm", @@ -4221,9 +4222,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safe_arch" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed" +checksum = "42c6efa15875e6ecb39ca61fb0b0c1a40b84fac5a5ffe71eef7d1000c8eb3f5f" dependencies = [ "bytemuck", ] @@ -5138,9 +5139,9 @@ dependencies = [ [[package]] name = "wide" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdfe6a32973f2d1b268b8895845a8a96cac2f0191e72c27cc929036060dbf89" +checksum = "d920ac99c3c8edce110cb8d07dbb324d6d026011dce85b1e9355b70f0adacc4f" dependencies = [ "bytemuck", "safe_arch", diff --git a/Cargo.toml b/Cargo.toml index 0b55f2e8277..4b84f30930a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -245,9 +245,9 @@ log = "0.4.30" lz4_flex = "0.14" nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } mac_address = "1.1.3" -malachite-bigint = "0.13.0" -malachite-q = "0.13.0" -malachite-base = "0.13.0" +malachite-bigint = "0.14.0" +malachite-q = "0.14.0" +malachite-base = "0.14.0" md-5 = "0.11" memchr = { version = "2.8.1", default-features = false, features = ["alloc"] } memmap2 = "0.9.10"