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..0dfd1f0e32f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -790,9 +790,7 @@ 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" + ref: "v3.15.0" persist-credentials: false - name: prek run (manual stage) 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" diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 001a016621c..b81115a0c16 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -1,371 +1,386 @@ # This file is generated by tools/opcode_metadata/generate_py_opcode_metadata.py -# for RustPython bytecode format (CPython 3.14 compatible opcode numbers). +# for RustPython bytecode format. # 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/_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/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/Lib/test/test_code.py b/Lib/test/test_code.py index 6c26259cf15..0af191cb5df 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,18 @@ 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 def isinterned(s): return s is sys.intern(('_' + s + '_')[1:-1]) @@ -1438,6 +1442,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 +1451,7 @@ def f(): [ (1 << 7) | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3) - | (3 - 1), + | (4 - 1), 0, ] ), @@ -1454,7 +1459,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_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_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..006480abe03 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,25 @@ def test_monitoring_live_at_shutdown(self): script_helper.run_test_script(script) -@unittest.skipUnless(_testcapi, "requires _testcapi") +@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: 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 +2672,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 +2683,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 +2693,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 +2743,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/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/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 95c1ceb8098..257169e34bd 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, } @@ -1949,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(); @@ -2150,16 +2155,36 @@ impl<'warnings> Compiler<'warnings> { self.set_qualname(); } - // 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); + self.emit_scope_prefix(scope_type, lineno, is_generator); 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) { + /// 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 + // 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), + }); + } + } + // CPython: LOCATION(lineno, lineno, 0, 0) // Module scope: loc.lineno = 0 (before the first line) let lineno_override = if scope_type == CompilerScope::Module { @@ -2483,17 +2508,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 +2633,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 +2736,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)); } @@ -2923,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); @@ -3004,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; @@ -3044,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)?; @@ -3118,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(); @@ -3507,6 +3553,21 @@ impl<'warnings> Compiler<'warnings> { Ok(()) } + /// `_PyCompile_InExceptionHandler` + fn in_exception_handler(&mut self) -> bool { + self.current_code_info().fblock.iter().any(|block| { + matches!( + block.fb_type, + FBlockType::TryExcept + | FBlockType::FinallyTry + | FBlockType::FinallyEnd + | FBlockType::ExceptionHandler + | FBlockType::ExceptionGroupHandler + | FBlockType::HandlerCleanup + ) + }) + } + fn compile_statement(&mut self, statement: &ast::Stmt) -> CompileResult<()> { trace!("Compiling {statement:?}"); let prev_source_range = self.current_source_range; @@ -3535,7 +3596,15 @@ impl<'warnings> Compiler<'warnings> { value: num_traits::Zero::zero(), }); self.emit_load_const(ConstantData::None); - let namei = self.name(&name.name); + // 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(); @@ -3580,7 +3649,16 @@ impl<'warnings> Compiler<'warnings> { }); let module_name = module.as_ref().map_or("", |s| s.as_str()); - let module_idx = self.name(module_name); + // 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 { @@ -6415,6 +6493,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 +6578,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 +6605,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 +6670,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 +6679,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 +6693,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 +6718,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 +6758,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 +8900,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 +8909,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 +9118,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 +9135,12 @@ 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: oparg::GET_ITER_YIELD_FROM + } + ); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(false); } @@ -9428,6 +9516,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 +9538,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 +9564,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 +9589,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: 2 }); + 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::SetAdd { i: 3 }); self.set_source_range(loc); emit!(self, PseudoInstruction::Jump { delta: loop_block }); } @@ -9533,6 +9640,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 +9824,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 +10824,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 +10888,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 +10914,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 +10930,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 +11019,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 +11031,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 +11055,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 +11264,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 +11288,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 +11303,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,42 +11621,11 @@ 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()`. + /// 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); } @@ -12221,7 +12333,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; } @@ -14622,66 +14734,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(), &[0xf2, 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(), - &[ - 0xf0, 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(), - &[ - 0x80, 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( @@ -14903,11 +14955,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 +15004,7 @@ def f(input): Instruction::CompareOp { .. }, Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, Instruction::JumpBackward { .. }, ] @@ -15992,7 +16045,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 +16574,7 @@ def f(buffer, pos, last_char): Instruction::NotTaken, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, ] ) @@ -16536,7 +16589,7 @@ def f(buffer, pos, last_char): Instruction::CompareOp { .. }, Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -16589,86 +16642,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(), &[0x80, 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(), &[0x80, 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, - ], - ); - } - - #[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(), - &[ - 0x80, 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(), - &[ - 0x80, 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( @@ -16919,83 +16892,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(), - &[ - 0x80, 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(), - &[ - 0x80, 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, 0x80, 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] - ); - assert_eq!( - alias.linetable.as_ref(), - &[ - 0xf8, 0x80, 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"); @@ -17012,23 +16908,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, 0x80, 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"); @@ -17063,25 +16942,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(), - &[ - 0x80, 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( @@ -17099,27 +16959,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, 0x80, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f, - ] - ); - } - #[test] fn generic_class_wrapper_ops_use_class_location_like_cpython() { let code = compile_exec( @@ -17133,9 +16972,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, @@ -17344,44 +17183,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, 0x80, 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, 0xd8, 0x09, 0x0c, 0x87, 0x48] - ); - } - #[test] #[expect( clippy::literal_string_with_formatting_args, @@ -17429,41 +17230,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(), - &[0x80, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b] - ); - assert_eq!( - g.linetable.as_ref(), - &[0x80, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65] - ); - } - #[test] fn dict_unpacking_large_regular_run_uses_subdict_chunks_like_cpython() { let pairs = (0..17) @@ -17703,11 +17469,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 +17576,7 @@ def f(self, maintype): .. }, CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, CodeUnit { @@ -18176,7 +17941,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:?}", @@ -18214,30 +17979,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(), - &[ - 0x80, 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( @@ -18280,31 +18021,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(), - &[ - 0x80, 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( @@ -18354,32 +18070,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(), - &[ - 0x80, 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( @@ -18431,29 +18121,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(), - &[ - 0x80, 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( @@ -18551,7 +18218,7 @@ def f(waiters): [ Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, Instruction::LoadAttr { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] )), @@ -18877,77 +18544,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(), - &[0x80, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16] - ); - assert_eq!( - or_true.linetable.as_ref(), - &[0x80, 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(), - &[ - 0x80, 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(), - &[ - 0x80, 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( @@ -19059,169 +18655,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(), - &[ - 0x80, 0x00, 0xd9, 0x0b, 0x2d, 0xa1, 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, - ] - ); - } - - #[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(), - &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9c, 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, - ] - ); - assert_eq!( - explicit_gen.linetable.as_ref(), - &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x26, 0x99, 0x49, 0x90, 0x71, 0x94, 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(), - &[ - 0x80, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa4, 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, - ] - ); - 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, - ] - ); - 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, - ] - ); - 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, - ] - ); - 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, - ] - ); - 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, - ] - ); - 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, - ] - ); - } - #[test] fn implicit_call_genexpr_operator_element_range_like_cpython() { // The element opens with a parenthesized group that the rest of the @@ -19236,14 +18669,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 +18698,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(); @@ -19277,71 +18715,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(), - &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x31, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8, - 0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8a, 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, - ] - ); - } - - #[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(), - &[ - 0x80, 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( @@ -19363,11 +18736,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"); @@ -19468,76 +18840,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(), - &[ - 0x80, 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(), - &[ - 0x80, 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, - ] - ); - } - #[test] fn debug_fstring_literal_merging_and_location_like_cpython() { fn string_load_position(code: &CodeObject, expected: &str) -> (usize, usize, usize, usize) { @@ -19635,29 +18937,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(), - &[ - 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, - ], - "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( @@ -19730,60 +19009,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, 0x80, 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!( - 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, - ] - ); - 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, - ] - ); - } - #[test] fn taken_constant_boolop_jump_disables_following_borrows() { for source in [ @@ -20138,7 +19363,7 @@ x = +0.0j Instruction::Resume { .. }, Instruction::LoadConst { .. }, Instruction::StoreName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue ] ), @@ -20559,56 +19784,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(), - 1, - "fallback call path should remain for shadowed any()" - ); - 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(), - &[ - 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, - ] - ); - } - #[test] fn builtin_any_async_genexpr_call_is_not_optimized_like_cpython() { for source in [ @@ -20655,72 +19830,6 @@ async def f(get_xs): ); } - #[test] - fn builtin_tuple_genexpr_call_is_optimized_but_list_set_are_not() { - 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, 2); - 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, - ] - ); - - 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) should stay on the normal call path" - ); - assert!( - !has_common_constant(list_f, bytecode::CommonConstant::BuiltinList), - "CPython 3.14.5 does not optimize list(genexpr)" - ); - - 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) should stay on the normal call path" - ); - assert!( - !has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet), - "CPython 3.14.5 does not optimize set(genexpr)" - ); - } - #[test] fn builtin_tuple_genexpr_try_assignment_uses_shared_tail() { let code = compile_exec( @@ -20969,7 +20078,7 @@ def f(cache, lock, format): Instruction::LoadFastLoadFast { .. }, Instruction::LoadFast { .. }, Instruction::StoreSubscr, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -21111,27 +20220,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(), - &[ - 0x80, 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( @@ -21186,7 +20274,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 +20284,7 @@ def f(obj): Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, Instruction::Swap { .. }, Instruction::PopTop, + Instruction::PopTop, Instruction::ReturnValue, ] ) @@ -21570,7 +20659,7 @@ def f(self, exc_type, KeyboardInterrupt, TimeoutExpired): Instruction::LoadSmallInt { .. }, Instruction::LoadFastBorrow { .. }, Instruction::StoreAttr { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -21710,7 +20799,7 @@ def f(locale, category, locales): Instruction::RaiseVarargs { .. }, Instruction::Nop, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::YieldValue { .. }, ] ) @@ -21857,18 +20946,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 +20998,7 @@ elif maxsize == 9223372036854775807: [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -21923,7 +21013,7 @@ elif maxsize == 9223372036854775807: Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -22574,7 +21664,7 @@ def f(self, file, backupfilename): ], [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::LoadFast { .. }, Instruction::StoreAttr { .. }, Instruction::Reraise { .. }, @@ -23068,15 +22158,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 +22247,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 +22295,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 +22480,9 @@ def f(i): matches!( window, [ - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -23595,9 +22679,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 +22719,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 +22758,9 @@ def f(cm): [ Instruction::StoreFast { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23716,9 +22800,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 +22847,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 +22934,9 @@ def f(cm): [ Instruction::StoreFast { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23865,9 +22949,9 @@ def f(cm): window, [ Instruction::StoreFast { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23904,9 +22988,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 +23028,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 +23046,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 +23811,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 +24957,7 @@ def f(part, lines, maxlen, encoding): window, [ Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreSubscr, ] ) @@ -25998,7 +25082,7 @@ def f(part, lines, maxlen, encoding): window, [ Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreSubscr, ] ) @@ -26930,11 +26014,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 +27179,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 +27553,7 @@ def f(self): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreName { .. } | Instruction::StoreFast { .. }, Instruction::DeleteName { .. } | Instruction::DeleteFast { .. }, Instruction::JumpForward { .. }, @@ -28903,7 +28004,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 +28517,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 +28544,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 +28673,7 @@ def f(obj): Instruction::Resume { .. }, Instruction::LoadFastBorrow { .. }, Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BinarySlice, Instruction::ReturnValue, ] @@ -29732,9 +28839,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 +28857,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 +28887,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 +28922,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 +29103,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 +29147,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 +29191,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 +29481,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 +29528,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 +29734,7 @@ def f(self, logger): [ Instruction::PopTop, Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, Instruction::DeleteFast { .. }, Instruction::JumpBackwardNoInterrupt { .. }, @@ -32205,7 +31309,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 +31318,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 +31348,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,18 +31570,17 @@ 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" ); } #[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) @@ -32497,19 +31591,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 ); } @@ -32524,10 +31646,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 +31829,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,37 +31851,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:?}" - ); - } - - #[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, 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, - ], - "CPython codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter)" + "expected BUILD_LIST before saved locals, output collection, and GET_ITER for single-item list iterable in comprehension, got ops={ops:?}" ); } @@ -32772,12 +31877,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(), @@ -32792,30 +31898,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, 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, - ], - "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( @@ -32908,53 +31990,7 @@ 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, 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, - ], - "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, 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, - 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_uses_tuple() { + fn nested_comprehension_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def f(): @@ -32970,13 +32006,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 +32037,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 +32051,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 +32370,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 +32535,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:?}" @@ -33616,27 +32669,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, 0x80, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11,] - ); - } - #[test] fn genexpr_namedexpr_target_is_cell_not_fast_local() { let code = compile_exec( @@ -33828,13 +32860,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 +34194,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 +34217,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 +34237,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 +34406,7 @@ def f(s, size, encodeSetO, encodeWhiteSpace): window, [ Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, @@ -35865,7 +34899,7 @@ def f(cls, _FIELDS, _PARAMS): Instruction::NotTaken, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, ] ) @@ -35901,9 +34935,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 +34972,9 @@ async def foo(): matches!( window, [ - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::GetAwaitable { .. }, ] @@ -35954,9 +34988,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 +35037,8 @@ async def foo(self): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -36039,8 +35073,8 @@ def f(): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -38176,7 +37210,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 +37243,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 +37382,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 +37695,7 @@ def f(flags, A, B, stop): Instruction::PopJumpIfTrue { .. }, Instruction::NotTaken, Instruction::JumpBackward { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -39629,7 +38682,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 +38706,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 +39159,7 @@ def f(self, b, BlockingIOError): .. }, CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, ] @@ -40153,9 +39206,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 +39245,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 +39283,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 +39300,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 +39379,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 +39396,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 +39430,9 @@ def f(cm, dst): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40501,7 +39554,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 +39604,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 +40729,7 @@ def f(events, callback, args, self, sig, signal): .. }, bytecode::CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, bytecode::CodeUnit { @@ -42771,7 +41824,7 @@ def f(pattern, prefix, get_prefix): .collect(); assert!( - ops.windows(7).any(|window| { + ops.windows(8).any(|window| { matches!( window, [ @@ -42779,8 +41832,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 +42631,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 +42976,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:?}" ); } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 790846a4028..9c4ad879d94 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,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 => { @@ -2243,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)?; } @@ -2410,7 +2447,7 @@ impl Blocks { Instruction::FormatSimple | Instruction::GetAnext | Instruction::GetLen - | Instruction::GetYieldFromIter + | Instruction::GetIter { .. } | Instruction::ImportFrom { .. } | Instruction::MatchKeys | Instruction::MatchMapping @@ -2420,10 +2457,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 +2482,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 +2678,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 +3502,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 } @@ -3985,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, @@ -3994,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), @@ -4009,6 +4073,27 @@ 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, + ) -> 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); + for (i, info) in [resume, load, iterator].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, @@ -4114,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)?; @@ -4336,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]; @@ -4344,37 +4428,6 @@ fn insert_prefix_instructions( let firstlineno = metadata.firstlineno; debug_assert!(firstlineno.get() > 0); - if is_generator(flags) { - 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(); @@ -4428,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(); @@ -4443,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; @@ -4517,6 +4569,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 +4593,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 +4688,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 { @@ -5358,20 +5434,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; @@ -5380,9 +5460,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); } @@ -5391,7 +5474,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; } @@ -5408,15 +5492,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 { @@ -5449,7 +5540,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 +5630,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 +5721,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 +5739,7 @@ fn basicblock_optimize_load_const( } Instruction::PopJumpIfTrue { .. } => {} _ => { - i += 1; - continue; + break 'instruction; } }; @@ -5659,27 +5751,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(()) } @@ -5837,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(), @@ -6968,7 +7065,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 = @@ -7726,7 +7825,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 +7837,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 +7892,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,77 +7906,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 { .. }) - )); - } - - #[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 + Some(Instruction::LoadFastBorrow { .. }) )); } diff --git a/crates/common/src/cformat.rs b/crates/common/src/cformat.rs index de437f2ce0d..e2f9d50cb6a 100644 --- a/crates/common/src/cformat.rs +++ b/crates/common/src/cformat.rs @@ -19,7 +19,7 @@ pub enum CFormatErrorType { UnmatchedKeyParentheses, MissingModuloSign, UnsupportedFormatChar(CodePoint), - IncompleteFormat, + StrayModulo, WidthTooBig, PrecisionTooBig, // Unimplemented, @@ -38,7 +38,7 @@ 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, "unsupported format character '{}' ({:#x}) at index {}", @@ -644,7 +644,7 @@ where { let (index, c) = iter.next().ok_or_else(|| { ( - CFormatErrorType::IncompleteFormat, + CFormatErrorType::StrayModulo, iter.peek().map_or(0, |x| x.0), ) })?; @@ -831,9 +831,14 @@ impl CFormatStrOrBytes { } let spec = - CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError { - typ: err.0, - index: err.1, + 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_index + }; + CFormatError { typ, index } })?; parts.push((index, CFormatPart::Spec(spec))); @@ -842,8 +847,8 @@ impl CFormatStrOrBytes { } } else { return Err(CFormatError { - typ: CFormatErrorType::IncompleteFormat, - index: index + 1, + typ: CFormatErrorType::StrayModulo, + index, }); } } else { @@ -991,8 +996,8 @@ mod tests { assert_eq!( "Hello %".parse::(), Err(CFormatError { - typ: CFormatErrorType::IncompleteFormat, - index: 7 + typ: CFormatErrorType::StrayModulo, + index: 6 }) ); } diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index f7d117cb76b..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; @@ -286,12 +286,16 @@ impl PyCodeLocationInfoKind { pub trait Constant: Sized + Clone { type Name: AsRef; + /// 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>; } impl Constant for ConstantData { type Name = String; + type Tuple = Box<[Self]>; fn borrow_constant(&self) -> BorrowedConstant<'_, Self> { match self { @@ -373,19 +377,23 @@ impl ConstantBag for BasicBag { } #[derive(Clone)] -pub struct Constants(Box<[C]>); +pub struct Constants(C::Tuple); -impl Deref for Constants { - type Target = [C]; +impl Constants { + pub const fn new(tuple: C::Tuple) -> Self { + Self(tuple) + } - fn deref(&self) -> &Self::Target { + pub const fn as_tuple(&self) -> &C::Tuple { &self.0 } } -impl DerefMut for Constants { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 +impl Deref for Constants { + type Target = [C]; + + fn deref(&self) -> &Self::Target { + &self.0 } } @@ -397,12 +405,6 @@ impl Index for Constants { } } -impl IndexMut for Constants { - fn index_mut(&mut self, consti: oparg::ConstIdx) -> &mut Self::Output { - &mut self.0[consti.as_usize()] - } -} - impl FromIterator for Constants { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect()) @@ -734,6 +736,27 @@ impl Deref for CodeUnits { } impl CodeUnits { + /// `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(); + 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(); + } + i += 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) { @@ -937,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 f403507f1aa..a34759a92b7 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -333,261 +333,260 @@ define_opcodes!( FormatWithSpec = 13, GetAiter = 14, GetAnext = 15, - GetIter = 16, + GetLen = 16, 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, + 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, + GetIter { mode: Arg } = 70, 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 +602,107 @@ 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, + BinaryOpSubscrUstrInt = 141, + BinaryOpSubtractFloat = 142, + BinaryOpSubtractInt = 143, + CallAllocAndEnterInit = 144, + CallBoundMethodExactArgs = 145, + CallBoundMethodGeneral = 146, + CallBuiltinClass = 147, + CallBuiltinFast = 148, + CallBuiltinFastWithKeywords = 149, + CallBuiltinO = 150, + CallExNonPyGeneral = 151, + CallExPy = 152, + 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, + ForIterVirtual = 178, + GetIterSelf = 179, + GetIterVirtual = 180, + 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, + LoadGlobalBuiltin = 196, + LoadGlobalModule = 197, + LoadSuperAttrAttr = 198, + LoadSuperAttrMethod = 199, + ResumeCheck = 200, + ResumeCheckJit = 201, + SendAsyncGen = 202, + SendGen = 203, + SendVirtual = 204, + 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, + TraceRecord = 255, } ); @@ -1427,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()); @@ -1438,8 +1443,8 @@ mod tests { #[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); @@ -1482,8 +1487,7 @@ mod tests { 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 @@ -1501,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, @@ -1536,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 @@ -1560,6 +1567,7 @@ mod tests { Opcode::CallKwBoundMethod | Opcode::CallKwPy | Opcode::CallKwNonPy => { Opcode::CallKw } + Opcode::CallExPy | Opcode::CallExNonPyGeneral => Opcode::CallFunctionEx, _ => return None, }) } @@ -1576,6 +1584,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..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 {} @@ -756,6 +761,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 +921,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#L92) + 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#L93) + pub const DEPTH1_MASK: u32 = 0x8; #[must_use] pub const fn new(location: ResumeLocation, is_exception_depth1: bool) -> Self { @@ -926,8 +941,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 +962,9 @@ pub enum ResumeLocation { AfterYieldFrom, /// After an `await` expression. AfterAwait, + /// Start of a generator expression, before its outermost iterator is + /// acquired and the generator is created. + AtGenExprStart, } impl From for ResumeContext { @@ -964,6 +982,7 @@ impl TryFrom for ResumeLocation { 1 => Self::AfterYield, 2 => Self::AfterYieldFrom, 3 => Self::AfterAwait, + 4 => Self::AtGenExprStart, _ => return Err(Self::Error::InvalidBytecode), }) } @@ -977,6 +996,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..82c3ec09191 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, 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, ]; CACHE_ENTRIES[self.as_numeric() as usize] as usize @@ -176,6 +176,7 @@ impl super::Opcode { Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), + Some(super::Opcode::BinaryOp), Some(super::Opcode::Call), Some(super::Opcode::Call), Some(super::Opcode::Call), @@ -183,6 +184,8 @@ impl super::Opcode { Some(super::Opcode::Call), Some(super::Opcode::Call), Some(super::Opcode::Call), + Some(super::Opcode::CallFunctionEx), + Some(super::Opcode::CallFunctionEx), 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), + Some(super::Opcode::ForIter), + Some(super::Opcode::GetIter), + Some(super::Opcode::GetIter), Some(super::Opcode::JumpBackward), Some(super::Opcode::JumpBackward), Some(super::Opcode::LoadAttr), @@ -223,13 +229,14 @@ 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), + Some(super::Opcode::Resume), + Some(super::Opcode::Send), + Some(super::Opcode::Send), Some(super::Opcode::Send), Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), @@ -282,13 +289,6 @@ impl super::Opcode { None, None, None, - None, - None, - None, - None, - None, - None, - None, ]; DEOPT[self.as_numeric() as usize] @@ -327,6 +327,7 @@ impl super::Opcode { | Self::ExtendedArg | Self::ForIter | Self::GetAwaitable + | Self::GetIter | Self::ImportFrom | Self::ImportName | Self::IsOp @@ -384,7 +385,7 @@ impl super::Opcode { | Self::CallBuiltinFast | Self::CallBuiltinFastWithKeywords | Self::CallBuiltinO - | Self::CallIsinstance + | 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 @@ -423,13 +425,14 @@ impl super::Opcode { | Self::LoadAttrProperty | Self::LoadAttrSlot | Self::LoadAttrWithHint - | Self::LoadConstImmortal - | Self::LoadConstMortal | Self::LoadGlobalBuiltin | Self::LoadGlobalModule | Self::LoadSuperAttrAttr | Self::LoadSuperAttrMethod + | Self::ResumeCheckJit + | Self::SendAsyncGen | Self::SendGen + | Self::SendVirtual | Self::StoreAttrWithHint | Self::UnpackSequenceList | Self::UnpackSequenceTuple @@ -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 ) @@ -609,23 +614,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 +639,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 +679,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 +716,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)), @@ -740,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), @@ -749,12 +754,14 @@ impl super::Opcode { Self::CallBuiltinFast => (1, 2 + oparg), Self::CallBuiltinFastWithKeywords => (1, 2 + oparg), Self::CallBuiltinO => (1, 2 + oparg), - Self::CallIsinstance => (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), 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 +777,18 @@ 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::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), 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 +799,15 @@ 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::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), @@ -811,10 +822,10 @@ 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::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), @@ -833,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/marshal.rs b/crates/compiler-core/src/marshal.rs index 1615c1d6a78..fee6a111c98 100644 --- a/crates/compiler-core/src/marshal.rs +++ b/crates/compiler-core/src/marshal.rs @@ -554,6 +554,16 @@ fn read_const_value( Ok(value) } +/// Object-valued code fields as read from the stream (`r_object` TYPE_CODE). +pub struct CodeFields { + pub consts: V, + pub names: V, + pub localsplusnames: V, + pub localspluskinds: V, + pub linetable: V, + pub exceptiontable: V, +} + pub trait MarshalBag: Copy { type Value: Clone; type ConstantBag: ConstantBag; @@ -580,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. /// @@ -608,22 +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) - } - fn make_stop_iter(&self) -> Result; fn make_list(&self, it: impl Iterator) -> Result; @@ -786,6 +771,7 @@ impl MarshalBag for Bag { fn make_code( &self, code: CodeObject<::Constant>, + _fields: CodeFields, ) -> Result { Ok(self.make_code(code)) } @@ -958,17 +944,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 +1039,17 @@ fn deserialize_code_value_inner( linetable, exceptiontable, }; - bag.make_code_with_constants_and_bytes(code, constant_values, code_bytes) + bag.make_code( + code, + CodeFields { + consts: consts_value, + names: names_value, + localsplusnames: localsplusnames_value, + localspluskinds: kinds_value, + linetable: linetable_value, + exceptiontable: exceptiontable_value, + }, + ) } fn deserialize_value_typed( diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 499d7c8baa4..4f739651635 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); 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..d7fd9a8f61d 100644 --- a/crates/stdlib/src/_opcode.rs +++ b/crates/stdlib/src/_opcode.rs @@ -4,7 +4,6 @@ pub(crate) use _opcode::module_def; mod _opcode { use crate::vm::{ AsObject, PyObjectRef, PyResult, VirtualMachine, - builtins::{PyInt, PyIntRef}, bytecode::{AnyOpcode, oparg}, }; @@ -25,7 +24,7 @@ mod _opcode { #[derive(FromArgs)] struct StackEffectArgs { #[pyarg(positional)] - opcode: PyIntRef, + opcode: i32, #[pyarg(positional, optional)] oparg: Option, #[pyarg(named, optional)] @@ -33,58 +32,33 @@ mod _opcode { } #[pyfunction] + #[allow(clippy::unnecessary_cast)] // `c_long` is `i32` or `i64` 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") - })?) - } - } + 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 { None => None, + 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")); + } }; - - 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. + 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(vm.new_value_error("invalid opcode or oparg")); + return Err(invalid()); } - - 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 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) } @@ -197,16 +171,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()); @@ -223,6 +201,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 @@ -232,13 +214,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 @@ -338,6 +320,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__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__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__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__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__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__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__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/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 6cf1036ab11..89f760ad26f 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 const_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,34 @@ fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> { }) } +/// `co_consts` viewed as constants. +#[derive(Clone)] +pub struct LiteralTuple(PyTupleRef); + +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 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 Tuple = LiteralTuple; type Name = &'static PyStrInterned; fn borrow_constant(&self) -> BorrowedConstant<'_, Self> { borrow_obj_constant(&self.0) @@ -357,6 +386,13 @@ pub(crate) type CodeObject = bytecode::CodeObject; pub trait IntoCodeObject { fn into_code_object(self, vm: &VirtualMachine) -> CodeObject; + + 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 +403,11 @@ impl IntoCodeObject for CodeObject { impl IntoCodeObject for bytecode::CodeObject { fn into_code_object(self, vm: &VirtualMachine) -> CodeObject { - self.map_bag(PyVmBag(vm)) + const_cache::into_code_object(self, vm) + } + + fn into_pycode(self, vm: &VirtualMachine) -> PyCode { + const_cache::into_pycode(self, vm) } } @@ -389,13 +429,21 @@ 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")] #[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")] pub code: CodeObject, @@ -403,6 +451,16 @@ pub struct PyCode { /// Derived once so frame-local proxy operations do not repeatedly scan /// merged cell variables. localsplus_names: Box<[&'static PyStrInterned]>, + 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")] @@ -506,12 +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 { - pub fn new(code: CodeObject) -> Self { + pub fn new(mut code: CodeObject, ctx: &Context) -> Self { + 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 = { @@ -547,7 +610,27 @@ impl PyCode { | Instruction::InstrumentedEndAsyncFor ) }); + 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, localsplus_names, source_path: AtomicPtr::new(sp), @@ -559,6 +642,42 @@ impl PyCode { } } + /// 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::CodeFields, + ctx: &Context, + ) -> Result { + use rustpython_compiler_core::marshal::MarshalError; + 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; + } + } + + /// 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; + } + } + #[inline(always)] pub(crate) fn localsplus_name(&self, index: usize) -> &'static PyStrInterned { // Bytecode operands and frame-proxy indices are validated against @@ -567,6 +686,41 @@ impl PyCode { self.localsplus_names[index] } + pub(crate) fn co_consts(&self) -> &PyTupleRef { + self.code.constants.as_tuple().as_pytuple() + } + + /// `_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 { // SAFETY: always points to a valid &'static PyStrInterned (interned strings are never deallocated) unsafe { &*self.source_path.load(Ordering::Relaxed) } @@ -580,11 +734,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>( @@ -721,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, @@ -732,35 +966,23 @@ 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.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()) }) } @@ -771,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![ - vm.ctx.new_str(code.obj_name.as_str()).into(), + 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(), @@ -780,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() @@ -884,16 +1106,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::new(LiteralTuple(args.consts)); // Create locations (start and end pairs) let row = if args.firstlineno > 0 { @@ -955,7 +1168,12 @@ impl Constructor for PyCode { exceptiontable: args.exceptiontable.as_bytes().to_vec().into_boxed_slice(), }; - Ok(Self::new(code)) + 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) } } @@ -973,12 +1191,13 @@ impl PyCode { impl Py { #[pygetset] pub fn co_cellvars(&self, vm: &VirtualMachine) -> PyTupleRef { - let cellvars = self + self.co_cached .cellvars - .iter() - .map(|name| name.to_pyobject(vm)) - .collect(); - vm.ctx.new_tuple(cellvars) + .get_or_init(|| { + vm.ctx + .new_tuple(self.cellvars.iter().map(|name| name.to_object()).collect()) + }) + .clone() } #[pygetset] @@ -987,36 +1206,43 @@ 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_name(&self) -> PyStrRef { + self.co_name.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_consts(&self) -> PyTupleRef { + PyCode::co_consts(self).clone() + } + + #[pygetset] + fn co_names(&self) -> PyTupleRef { + self.co_names.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.co_cached + .varnames + .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()) + pub fn co_code(&self, vm: &VirtualMachine) -> PyBytesRef { + self.get_code(vm) } #[pygetset] - pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> crate::builtins::PyBytesRef { + pub fn _co_code_adaptive(&self, vm: &VirtualMachine) -> PyBytesRef { // Return current (possibly quickened/specialized) bytecode let bytes = unsafe { core::slice::from_raw_parts( @@ -1029,26 +1255,28 @@ impl Py { #[pygetset] pub fn co_freevars(&self, vm: &VirtualMachine) -> PyTupleRef { - let names = self - .code + self.co_cached .freevars - .deref() - .iter() - .map(|name| name.to_pyobject(vm)) - .collect(); - vm.ctx.new_tuple(names) + .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.co_linetable.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.co_exceptiontable.clone() } // spell-checker: ignore lnotab @@ -1380,23 +1608,17 @@ 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 => PyCode::co_consts(self).clone(), }; 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.clone(), }; 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.co_names.clone(), }; let flags = CodeFlags::from_bits_truncate(match co_flags { @@ -1427,7 +1649,8 @@ 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(), + OptionalArg::Missing => CodeUnits::try_from(self.get_code(vm).as_bytes()) + .map_err(|e| vm.new_value_error(format!("invalid bytecode: {e}")))?, }; let intern_all = |objs: Vec, field: &str| -> PyResult> { @@ -1482,15 +1705,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.co_linetable.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.co_exceptiontable.clone(), }; let new_code = CodeObject { @@ -1508,17 +1729,22 @@ 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::new(LiteralTuple(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 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] 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/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..61d173c8b3a 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; @@ -242,29 +242,30 @@ 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::ForIter { .. } => { - // Fall-through (iteration continues): pushes the next value - let body_stack = push_value(next_stack, Kind::Object as i64); + Instruction::GetAiter => { + next_stack = push_value(pop_value(next_stack), Kind::Iterator as i64); if next_i < stacks.len() { - stacks[next_i] = body_stack; + stacks[next_i] = next_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; - } + } + Instruction::ForIter { .. } => { + // 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] = target_stack; } - if j < stacks.len() && stacks[j] == UNINITIALIZED { - stacks[j] = next_stack; + let j = next_i + oparg as usize; + if j < stacks.len() { + debug_assert!(stacks[j] == UNINITIALIZED || stacks[j] == target_stack); + stacks[j] = target_stack; } } Instruction::EndAsyncFor => { @@ -514,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(()); } @@ -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/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/cformat.rs b/crates/vm/src/cformat.rs index abb845f9d2c..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,31 +86,41 @@ 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, obj: PyObjectRef, argument: FormatArgument<'_>, + format_start: usize, ) -> PyResult> { match &spec.format_type { - 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) => { @@ -132,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 @@ -203,13 +231,12 @@ fn spec_format_string( spec: &CFormatSpec, obj: PyObjectRef, argument: FormatArgument<'_>, + format_start: usize, ) -> PyResult { match &spec.format_type { - 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(), @@ -244,17 +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!( - "%{} 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 value = ArgIntoFloat::try_from_object(vm, obj)?; - 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 @@ -389,7 +412,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 }) => { @@ -401,6 +424,7 @@ pub(crate) fn cformat_bytes( &spec, value, FormatArgument::Mapping(key.as_object()), + format_start, )?; result.extend(part_result); } @@ -425,7 +449,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, .. }) => { @@ -446,7 +470,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); } } @@ -504,7 +529,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 +541,7 @@ pub(crate) fn cformat_string( &spec, value, FormatArgument::Mapping(key.as_object()), + format_start, )?; result.push_wtf8(&part_result); } @@ -540,7 +566,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 +587,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/coroutine.rs b/crates/vm/src/coroutine.rs index 5da8c8272a1..feb95d49ca1 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()); @@ -490,16 +495,40 @@ 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 { - 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..67530e28d1f 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,40 @@ 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() { + 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 +172,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 +186,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; + } + // lasti is one past the in-flight instruction. + (self.lasti.load(Relaxed) as usize) <= self.code().first_traceable + } + /// Get the last instruction index. #[inline(always)] pub fn get_lasti(&self) -> u32 { @@ -1228,10 +1278,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 +1839,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 +2613,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 +2884,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 +2920,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 +3169,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 +3538,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 +3728,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() { + // 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 +3756,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 +3806,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 +3851,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 +3859,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 +3936,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 +3958,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 +3985,13 @@ impl ExecutingFrame<'_> { crate::opcode_histogram::record(prev_op, op_byte); prev_op = op_byte; } + if PREPARE_GENERATOR && matches!(op, Instruction::ReturnGenerator) { + // 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); + } 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 +4007,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 +4018,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 +4037,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,12 +4137,21 @@ 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) => { + monitoring::reset_reraise_pending(); // Fire PY_UNWIND: exception escapes this frame let exception = if vm.state.monitoring_events.load() & MonitoringEvent::PyUnwind.mask() @@ -4040,16 +4185,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 +4210,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)); } } } @@ -4288,6 +4435,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 { @@ -4915,36 +5063,23 @@ 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 } => { + // _PyEval_GetIter 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 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 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() + iterable.get_iter(vm)?.into() }; self.push_value(iter); + self.push_null(); Ok(None) } Instruction::GetLen => { @@ -4960,7 +5095,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 } => { @@ -5103,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 } => { @@ -5139,6 +5262,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 +5743,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 +5753,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 +6059,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 +6070,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 +6100,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 +6113,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 +6148,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 +6167,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 +6186,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 +8061,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 +8076,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 +8091,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 +8106,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 +8399,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 +8414,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 +8503,7 @@ impl ExecutingFrame<'_> { } Instruction::InstrumentedPopIter => { // BRANCH_RIGHT is fired by InstrumentedForIter, not here. + self.pop_stackref_opt(); self.pop_stackref(); Ok(None) } @@ -8759,6 +8875,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. @@ -9473,7 +9590,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 +9614,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 +9652,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 +11554,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 +11821,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..9d7d6458eec 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, + // 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. 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,14 @@ 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() { + // 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 + || (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 +261,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 +403,35 @@ 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) - })?; + 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")); @@ -593,14 +627,19 @@ mod decl { unsafe { tuple.payload.set_marshal_item(index, value) }; Ok(()) } - fn make_code(&self, code: CodeObject) -> Result { + fn make_code( + &self, + code: CodeObject, + fields: marshal::CodeFields, + ) -> 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()) + let code = PyCode::from_marshal(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()) diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index aa180daeb96..b6d265f9477 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -9,8 +9,7 @@ use std::collections::{HashMap, HashSet}; pub(crate) const TOOL_LIMIT: usize = 6; const EVENTS_COUNT: usize = 19; -const LOCAL_EVENTS_COUNT: usize = 11; -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). @@ -204,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" @@ -232,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}"))); } @@ -281,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): @@ -402,26 +389,12 @@ 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, _| { - if first_traceable.is_none() - && 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. { - 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(); @@ -903,10 +876,20 @@ 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 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 < 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; + 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)); @@ -915,8 +898,6 @@ fn fire( EVENT_NAMES[event_id] ))); } - let mut state = vm.state.monitoring.lock(); - state.disabled.insert((code_id, offset as usize, tool)); } } Ok(()) @@ -1071,6 +1052,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( @@ -1102,7 +1089,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, @@ -1118,7 +1105,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, diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 05eb12a2942..bd421f7ca6e 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -69,8 +69,8 @@ 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; +// 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 a72d34ea92a..70ea5104827 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, }; @@ -829,42 +829,26 @@ pub(crate) fn code_returns_only_none(code: &Py) -> bool { 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 + let 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. + .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; } - // 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 + 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; + } + if matches!(prev_op, Instruction::LoadConst { .. }) + && none_index.is_some_and(|none_index| prev_arg as usize == none_index) { continue; } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index c3625828cc4..4d003b8a268 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2686,6 +2686,25 @@ impl VirtualMachine { &self, frame: FrameObjectRef, f: F, + ) -> PyResult { + self.with_frame_impl::(frame, f) + } + + /// 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, + 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 +2751,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 +2762,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 +2892,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 +3080,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() { + 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/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/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/src/warn.rs b/crates/vm/src/warn.rs index e369606f581..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,22 +355,14 @@ 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(), - } -} - /// 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, @@ -378,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_matched(&tmp_item.as_slice()[3], module, 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 _); @@ -397,7 +408,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 +418,7 @@ fn get_filter( text, lineno, module, + filename, &context_filters, "_warnings_context _filters", vm, @@ -416,7 +429,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 +519,11 @@ 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) { + // 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(()); } @@ -543,7 +560,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"))?; 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] diff --git a/tools/opcode_metadata/generate_py_opcode_metadata.py b/tools/opcode_metadata/generate_py_opcode_metadata.py index 08f350e4b39..2341588cd5c 100644 --- a/tools/opcode_metadata/generate_py_opcode_metadata.py +++ b/tools/opcode_metadata/generate_py_opcode_metadata.py @@ -1,7 +1,5 @@ """ Generate Lib/_opcode_metadata.py for RustPython bytecode. - -This file generates opcode metadata that is compatible with CPython 3.14. """ import functools @@ -24,10 +22,10 @@ "RESUME", "INSTRUMENTED_LINE", "ENTER_EXECUTOR", + "TRACE_RECORD", } INDENT = " " * 4 -INDENT2 = INDENT * 2 def main(): @@ -43,52 +41,51 @@ def main(): out.write( f""" # This file is generated by {script_path} -# for RustPython bytecode format (CPython 3.14 compatible opcode numbers). +# for RustPython bytecode format. # Do not edit! """.lstrip() ) # _specializations - out.write("\n") - out.write("_specializations = {\n") + 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') + out.write(f"{INDENT}{key}=(\n") - for item in map(to_upper_snake_case, lst): - out.write(f'{INDENT2}"{item}",\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(f"{INDENT}),\n") - out.write("}\n") + out.write(")\n") # _specialized_opmap out.write("\n") - out.write("_specialized_opmap = {\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(f"{INDENT}{opcode.cpython_name}={opcode.id},\n") - out.write("}\n") + out.write(")\n") # opmap out.write("\n") - out.write("opmap = {\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(f"{INDENT}{opcode.cpython_name}={opcode.id},\n") - out.write("}\n") + out.write(")\n") # min out.write("\n")