From a974ec938d700459d5badd578df59b587f4a4396 Mon Sep 17 00:00:00 2001 From: dot Date: Mon, 12 Oct 2026 00:18:46 +0900 Subject: [PATCH] Align compiler and VM with pinned CPython 3.15 ABI Couple opcode/cache/stack metadata with iterator pairs, generator preambles, ordinary import packing, common constants, compiler lowering and VM/JIT consumers. Pin official source/tool hashes and retain strict raw code and marshal comparisons. Use private development magic 0xDA16 and cache tag devabi2 until serialization and Python integration are coherent. Final clean corpus matches recursive code fields in 111/111 requests; full marshal/header exactness remains 0/111. Eight existing disassembly tests remain blocked by unchanged Lib metadata. Monitoring export and frozendict/update_lib integration remain explicit follow-on gates. Validation: compiler-core 23, codegen 807, CAPI 121 (4 existing ignored), focused certified-controller pytest, release/frozen witnesses, workspace and CAPI all-target Clippy. Independent source/artifact review covers manifest ec1013591ae97b4c519f4a2c78a8200664f3479f0c5452991b5fea4d1be669ea. Assisted-by: OpenAI Codex:model identifier unavailable --- crates/codegen/src/compile.rs | 1273 ++++-- crates/codegen/src/ir.rs | 470 ++- crates/compiler-core/src/bytecode.rs | 20 + .../compiler-core/src/bytecode/instruction.rs | 467 ++- crates/compiler-core/src/bytecode/oparg.rs | 26 +- .../src/bytecode/opcode_metadata.rs | 95 +- .../src/bytecode/target_stack_cases.rs | 3433 +++++++++++++++++ crates/jit/src/instructions.rs | 58 + crates/jit/tests/common.rs | 11 +- crates/vm/src/builtins/code.rs | 3 +- crates/vm/src/builtins/coroutine.rs | 2 +- crates/vm/src/builtins/frame.rs | 35 +- crates/vm/src/builtins/function.rs | 12 +- crates/vm/src/coroutine.rs | 37 +- crates/vm/src/frame.rs | 360 +- crates/vm/src/protocol/callable.rs | 2 +- crates/vm/src/stdlib/sys.rs | 2 +- crates/vm/src/stdlib/sys/monitoring.rs | 6 +- crates/vm/src/version.rs | 7 +- crates/vm/src/vm/mod.rs | 49 +- .../vm/tests/fixtures/private_cache_bytes.rs | 9 + crates/vm/tests/private_cache_reader.rs | 38 + .../snippets/compiler_315_warmed_export.py | 70 + extra_tests/snippets/iterator_contract_315.py | 501 +++ .../snippets/private_315_cache_contract.py | 109 + scripts/byte_exact_capture.py | 101 + scripts/byte_exact_compile.py | 174 + scripts/byte_exact_fixtures.json | 224 ++ scripts/test_byte_exact_compile.py | 112 + .../COMPILER_315_DEVELOPMENT.md | 78 + tools/opcode_metadata/conf.toml | 4 + tools/opcode_metadata/cpython.py | 13 +- tools/opcode_metadata/opcodes.py | 8 +- tools/opcode_metadata/stack_contract.c | 18 + tools/opcode_metadata/target_source.json | 42 + tools/opcode_metadata/target_source.py | 36 + tools/opcode_metadata/verify_opcode_ids.py | 44 + .../opcode_metadata/verify_stack_contract.py | 51 + 38 files changed, 7077 insertions(+), 923 deletions(-) create mode 100644 crates/compiler-core/src/bytecode/target_stack_cases.rs create mode 100644 crates/vm/tests/fixtures/private_cache_bytes.rs create mode 100644 crates/vm/tests/private_cache_reader.rs create mode 100644 extra_tests/snippets/compiler_315_warmed_export.py create mode 100644 extra_tests/snippets/iterator_contract_315.py create mode 100644 extra_tests/snippets/private_315_cache_contract.py create mode 100644 scripts/byte_exact_capture.py create mode 100644 scripts/byte_exact_compile.py create mode 100644 scripts/byte_exact_fixtures.json create mode 100644 scripts/test_byte_exact_compile.py create mode 100644 tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md create mode 100644 tools/opcode_metadata/stack_contract.c create mode 100644 tools/opcode_metadata/target_source.json create mode 100644 tools/opcode_metadata/target_source.py create mode 100644 tools/opcode_metadata/verify_opcode_ids.py create mode 100644 tools/opcode_metadata/verify_stack_contract.py diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 95c1ceb80..06de2e9df 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -68,6 +68,7 @@ const CO_MAXBLOCKS: usize = 21; pub enum FBlockType { WhileLoop, ForLoop, + AsyncForLoop, TryExcept, FinallyTry, FinallyEnd, @@ -107,6 +108,8 @@ enum SuperCallType<'a> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum BuiltinGeneratorCallKind { Tuple, + List, + Set, All, Any, } @@ -2483,17 +2486,27 @@ impl<'warnings> Compiler<'warnings> { // No cleanup needed } - FBlockType::ForLoop => { - // When returning from a for-loop, CPython swaps the preserved - // value with the iterator and uses POP_TOP for loop cleanup. + FBlockType::ForLoop | FBlockType::AsyncForLoop => { + let iterator_slots = if info.fb_type == FBlockType::ForLoop { + 2 + } else { + 1 + }; if preserve_tos { self.set_unwind_source_range(*loc); - emit!(self, Instruction::Swap { i: 2 }); + emit!( + self, + Instruction::Swap { + i: iterator_slots + 1 + } + ); + self.mark_unwind_no_location(*loc); + } + for _ in 0..iterator_slots { + self.set_unwind_source_range(*loc); + emit!(self, Instruction::PopTop); self.mark_unwind_no_location(*loc); } - self.set_unwind_source_range(*loc); - emit!(self, Instruction::PopTop); - self.mark_unwind_no_location(*loc); } FBlockType::TryExcept => { @@ -2598,6 +2611,8 @@ impl<'warnings> Compiler<'warnings> { self.set_unwind_source_range(*loc); emit!(self, Instruction::GetAwaitable { r#where: 2 }); self.set_unwind_source_range(*loc); + emit!(self, Instruction::PushNull); + self.set_unwind_source_range(*loc); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -2699,7 +2714,12 @@ impl<'warnings> Compiler<'warnings> { *unwind_loc, )); } - if stop_at_loop && matches!(top.fb_type, FBlockType::WhileLoop | FBlockType::ForLoop) { + if stop_at_loop + && matches!( + top.fb_type, + FBlockType::WhileLoop | FBlockType::ForLoop | FBlockType::AsyncForLoop + ) + { return Ok(Some(top)); } @@ -3507,6 +3527,28 @@ impl<'warnings> Compiler<'warnings> { Ok(()) } + /// Ordinary imports reserve two low bits for the import policy. + fn ordinary_import_flags(&mut self, star: bool) -> u32 { + if star || self.current_symbol_table().typ != CompilerScope::Module { + return 2; + } + if self.current_code_info().fblock.iter().any(|block| { + matches!( + block.fb_type, + FBlockType::TryExcept + | FBlockType::FinallyTry + | FBlockType::FinallyEnd + | FBlockType::ExceptionHandler + | FBlockType::ExceptionGroupHandler + | FBlockType::HandlerCleanup + ) + }) { + 2 + } else { + 0 + } + } + fn compile_statement(&mut self, statement: &ast::Stmt) -> CompileResult<()> { trace!("Compiling {statement:?}"); let prev_source_range = self.current_source_range; @@ -3535,7 +3577,7 @@ impl<'warnings> Compiler<'warnings> { value: num_traits::Zero::zero(), }); self.emit_load_const(ConstantData::None); - let namei = self.name(&name.name); + let namei = (self.name(&name.name) << 2) | self.ordinary_import_flags(false); emit!(self, Instruction::ImportName { namei }); if let Some(alias) = &name.asname { let parts: Vec<&str> = name.name.split('.').skip(1).collect(); @@ -3580,7 +3622,8 @@ impl<'warnings> Compiler<'warnings> { }); let module_name = module.as_ref().map_or("", |s| s.as_str()); - let module_idx = self.name(module_name); + let module_idx = + (self.name(module_name) << 2) | self.ordinary_import_flags(import_star); emit!(self, Instruction::ImportName { namei: module_idx }); if import_star { @@ -6415,6 +6458,7 @@ impl<'warnings> Compiler<'warnings> { ); // [aexit_func, self_ae, aenter_func, self_an] emit!(self, Instruction::Call { argc: 0 }); // [aexit_func, self_ae, awaitable] emit!(self, Instruction::GetAwaitable { r#where: 1 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } else { @@ -6499,6 +6543,7 @@ impl<'warnings> Compiler<'warnings> { self.compile_call_exit_with_nones(); if is_async { emit!(self, Instruction::GetAwaitable { r#where: 2 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -6525,6 +6570,7 @@ impl<'warnings> Compiler<'warnings> { if is_async { emit!(self, Instruction::GetAwaitable { r#where: 2 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -6589,7 +6635,7 @@ impl<'warnings> Compiler<'warnings> { // codegen_async_for: push fblock BEFORE SETUP_FINALLY self.push_fblock_labels( - FBlockType::ForLoop, + FBlockType::AsyncForLoop, for_label, after_label, FBlockDatum::None, @@ -6598,6 +6644,7 @@ impl<'warnings> Compiler<'warnings> { // SETUP_FINALLY to guard the __anext__ call emit!(self, PseudoInstruction::SetupFinally { delta: else_block }); emit!(self, Instruction::GetAnext); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); self.use_cpython_label_block(send_block); let _ = self.compile_yield_from_sequence(true); @@ -6611,7 +6658,7 @@ impl<'warnings> Compiler<'warnings> { } else { // Retrieve Iterator self.set_source_range(iter.range()); - emit!(self, Instruction::GetIter); + emit!(self, Instruction::GetIter { mode: 0 }); self.use_cpython_label_block(for_block); @@ -6636,7 +6683,7 @@ impl<'warnings> Compiler<'warnings> { // codegen_async_for() pops the loop fblock before the // END_ASYNC_FOR exception block. Sync codegen_for() keeps the // fblock through END_FOR/POP_ITER and pops below. - self.pop_fblock_label(FBlockType::ForLoop, for_label); + self.pop_fblock_label(FBlockType::AsyncForLoop, for_label); } self.use_cpython_label_block(else_block); @@ -6676,7 +6723,7 @@ impl<'warnings> Compiler<'warnings> { if generator.is_async { emit!(self, Instruction::GetAiter); } else { - emit!(self, Instruction::GetIter); + emit!(self, Instruction::GetIter { mode: 0 }); } self.set_source_range(saved_range); Ok(()) @@ -8818,8 +8865,8 @@ impl<'warnings> Compiler<'warnings> { ); // fail: CLEANUP_THROW - // Stack when exception: [receiver, yielded_value, exc] - // CLEANUP_THROW: [sub_iter, last_sent_val, exc] -> [None, value] + // Stack when exception: [receiver, NULL, yielded_value, exc] + // CLEANUP_THROW: [sub_iter, NULL, last_sent_val, exc] -> [None, NULL, value] // CPython lets this block fall through to END_SEND during codegen; // push_cold_blocks_to_end later inserts the no-interrupt jump after // moving the cold fail block behind the warm exit path. @@ -8827,8 +8874,8 @@ impl<'warnings> Compiler<'warnings> { emit!(self, Instruction::CleanupThrow); // exit: END_SEND - // Stack: [receiver, value] (from SEND) or [None, value] (from CLEANUP_THROW) - // END_SEND: [receiver/None, value] -> [value] + // Stack: [receiver, NULL, value] or [None, NULL, value] after CLEANUP_THROW. + // END_SEND: [receiver/None, NULL, value] -> [value] self.use_cpython_label_block(exit_block); emit!(self, Instruction::EndSend); @@ -9036,6 +9083,7 @@ impl<'warnings> Compiler<'warnings> { self.compile_expression(value)?; self.set_source_range(range); emit!(self, Instruction::GetAwaitable { r#where: 0 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -9052,7 +9100,7 @@ impl<'warnings> Compiler<'warnings> { self.mark_generator(); self.compile_expression(value)?; self.set_source_range(range); - emit!(self, Instruction::GetYieldFromIter); + emit!(self, Instruction::GetIter { mode: 1 }); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(false); } @@ -9428,6 +9476,8 @@ impl<'warnings> Compiler<'warnings> { fn detect_builtin_generator_call(&self, name: &str) -> Option { match name { "tuple" => Some(BuiltinGeneratorCallKind::Tuple), + "list" => Some(BuiltinGeneratorCallKind::List), + "set" => Some(BuiltinGeneratorCallKind::Set), "all" => Some(BuiltinGeneratorCallKind::All), "any" => Some(BuiltinGeneratorCallKind::Any), _ => None, @@ -9448,6 +9498,8 @@ impl<'warnings> Compiler<'warnings> { ) -> CompileResult<()> { let common_constant = match kind { BuiltinGeneratorCallKind::Tuple => bytecode::CommonConstant::BuiltinTuple, + BuiltinGeneratorCallKind::List => bytecode::CommonConstant::BuiltinList, + BuiltinGeneratorCallKind::Set => bytecode::CommonConstant::BuiltinSet, BuiltinGeneratorCallKind::All => bytecode::CommonConstant::BuiltinAll, BuiltinGeneratorCallKind::Any => bytecode::CommonConstant::BuiltinAny, }; @@ -9472,14 +9524,23 @@ impl<'warnings> Compiler<'warnings> { ); emit!(self, Instruction::PopTop); - if matches!(kind, BuiltinGeneratorCallKind::Tuple) { - self.set_source_range(loc); - emit!(self, Instruction::BuildList { count: 0 }); + match kind { + BuiltinGeneratorCallKind::Tuple | BuiltinGeneratorCallKind::List => { + self.set_source_range(loc); + emit!(self, Instruction::BuildList { count: 0 }); + } + BuiltinGeneratorCallKind::Set => { + self.set_source_range(loc); + emit!(self, Instruction::BuildSet { count: 0 }); + } + BuiltinGeneratorCallKind::All | BuiltinGeneratorCallKind::Any => {} } let symbol_table_cursors = self.current_symbol_table_cursors(); self.compile_expression(generator_expr)?; self.set_symbol_table_cursors(symbol_table_cursors); + self.set_source_range(loc); + emit!(self, Instruction::PushNull); let loop_block = self.new_block(); let cleanup = self.new_block(); @@ -9488,9 +9549,15 @@ impl<'warnings> Compiler<'warnings> { emit!(self, Instruction::ForIter { delta: cleanup }); match kind { - BuiltinGeneratorCallKind::Tuple => { + BuiltinGeneratorCallKind::Tuple | BuiltinGeneratorCallKind::List => { self.set_source_range(loc); - emit!(self, Instruction::ListAppend { i: 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 +9600,7 @@ impl<'warnings> Compiler<'warnings> { } ); } + BuiltinGeneratorCallKind::List | BuiltinGeneratorCallKind::Set => {} BuiltinGeneratorCallKind::All => { self.set_source_range(loc); self.emit_load_const(ConstantData::Boolean { value: true }); @@ -9716,7 +9784,7 @@ impl<'warnings> Compiler<'warnings> { if genexpr_call_name.is_some() { // CPython `maybe_optimize_function_call()` creates and uses // `skip_optimization` for every name(genexpr) shape after - // loading the function, even when the name is not all/any/tuple. + // loading the function, even when the name is not all/any/tuple/list/set. let skip_optimization = self.current_code_info().new_instr_sequence_label(); let result = self .current_code_info() @@ -10716,6 +10784,22 @@ impl<'warnings> Compiler<'warnings> { let stop_iteration_block = if comprehension_type == ComprehensionType::Generator { let handler_block = self.new_block(); self.insert_cpython_stopiteration_setup_cleanup(handler_block); + let source = self.source_file.to_source_code(); + let iterator = ir::InstructionInfo { + instr: if outermost.is_async { + bytecode::Opcode::GetAiter.into() + } else { + bytecode::Opcode::GetIter.into() + }, + arg: OpArg::NULL, + target: BlockIdx::NULL, + location: source.source_location(outermost.iter.start(), PositionEncoding::Utf8), + end_location: source.source_location(outermost.iter.end(), PositionEncoding::Utf8), + except_handler: None, + lineno_override: None, + }; + let result = self.current_code_info().insert_genexpr_prefix(iterator); + unwrap_internal(self, result); Some(handler_block) } else { None @@ -10764,8 +10848,15 @@ impl<'warnings> Compiler<'warnings> { }; if gen_index == 0 { - // Load iterator onto stack (passed as first argument): - emit!(self, Instruction::LoadFast { var_num: arg0 }); + // Genexpr creation has already left the acquired iterator on the stack. + if comprehension_type != ComprehensionType::Generator { + emit!(self, Instruction::LoadFast { var_num: arg0 }); + if generator.is_async { + emit!(self, Instruction::GetAiter); + } else { + emit!(self, Instruction::GetIter { mode: 0 }); + } + } } else { // Evaluate iterated item: self.compile_comprehension_iter(generator)?; @@ -10783,6 +10874,7 @@ impl<'warnings> Compiler<'warnings> { )?; emit!(self, PseudoInstruction::SetupFinally { delta: after_block }); emit!(self, Instruction::GetAnext); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); self.use_cpython_label_block(send_block); let _ = self.compile_yield_from_sequence(true); @@ -10798,7 +10890,7 @@ impl<'warnings> Compiler<'warnings> { self.set_source_range(saved_range); self.compile_store(&generator.target)?; } - real_loop_depth += 1; + real_loop_depth += if generator.is_async { 1 } else { 2 }; let backedge_range = if gen_index + 1 == generators.len() { element_range } else { @@ -10887,8 +10979,8 @@ impl<'warnings> Compiler<'warnings> { self.set_source_range(comprehension_range); self.make_closure(code, bytecode::MakeFunctionFlags::new())?; - // Evaluate iterated item and get its iterator. - self.compile_comprehension_iter(outermost)?; + // The callee acquires the iterator; a genexpr does this eagerly before creation. + self.compile_expression(&outermost.iter)?; self.symbol_table_stack .last_mut() .expect("no current symbol table") @@ -10899,6 +10991,7 @@ impl<'warnings> Compiler<'warnings> { emit!(self, Instruction::Call { argc: 0 }); if is_async_list_set_dict_comprehension { emit!(self, Instruction::GetAwaitable { r#where: 0 }); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); let _ = self.compile_yield_from_sequence(true); } @@ -10922,7 +11015,7 @@ impl<'warnings> Compiler<'warnings> { // nested scopes (e.g. lambdas) whose sub_tables sit at the current // position in the parent's list. Those must be consumed before we // splice in the comprehension's own children. - self.compile_comprehension_iter(&generators[0])?; + self.compile_expression(&generators[0].iter)?; match comp_source { ComprehensionSymbolSource::Child => { self.symbol_table_stack @@ -11131,6 +11224,15 @@ impl<'warnings> Compiler<'warnings> { if i > 0 { self.compile_comprehension_iter(generator)?; + } else { + let saved_range = self.current_source_range; + self.set_source_range(generator.iter.range()); + if generator.is_async { + emit!(self, Instruction::GetAiter); + } else { + emit!(self, Instruction::GetIter { mode: 0 }); + } + self.set_source_range(saved_range); } self.use_cpython_label_block(loop_block); @@ -11146,6 +11248,7 @@ impl<'warnings> Compiler<'warnings> { )?; emit!(self, PseudoInstruction::SetupFinally { delta: after_block }); emit!(self, Instruction::GetAnext); + emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); self.use_cpython_label_block(send_block); let _ = self.compile_yield_from_sequence(true); @@ -11160,7 +11263,7 @@ impl<'warnings> Compiler<'warnings> { self.compile_store(&generator.target)?; } - real_loop_depth += 1; + real_loop_depth += if generator.is_async { 1 } else { 2 }; let backedge_range = if i + 1 == generators.len() { element_range } else { @@ -11478,10 +11581,9 @@ impl<'warnings> Compiler<'warnings> { self.set_no_location(); } - /// CPython `codegen_wrap_in_stopiteration_handler()` inserts - /// `SETUP_CLEANUP` into the instruction sequence at index 0 with - /// `_PyInstructionSequence_InsertInstruction()`. Generator and cell/free - /// prefixes are inserted later by `flowgraph.c::insert_prefix_instructions()`. + /// Insert StopIteration cleanup before the ordinary function-start RESUME. + /// Generator creation prefixes are added before this protected body, either + /// by insert_genexpr_prefix() or during locals-plus preparation. fn insert_cpython_stopiteration_setup_cleanup(&mut self, handler_block: BlockIdx) { let code = self.current_code_info(); let entry = code @@ -12221,7 +12323,7 @@ impl<'warnings> Compiler<'warnings> { let mut found_loop = false; for i in (0..code.fblock.len()).rev() { match code.fblock[i].fb_type { - FBlockType::WhileLoop | FBlockType::ForLoop => { + FBlockType::WhileLoop | FBlockType::ForLoop | FBlockType::AsyncForLoop => { found_loop = true; break; } @@ -14628,7 +14730,7 @@ match x: // codegen emits the implicit LOAD_CONST/RETURN_VALUE with // NO_LOCATION, then flowgraph.c::propagate_line_numbers() propagates // the module RESUME location, whose line is 0. - assert_eq!(code.linetable.as_ref(), &[0xf2, 0x03, 0x01, 0x01, 0x01]); + assert_eq!(code.linetable.as_ref(), &[0xf3, 0x03, 0x01, 0x01, 0x01]); } #[test] @@ -14646,7 +14748,7 @@ x = 1 assert_eq!( code.linetable.as_ref(), &[ - 0xf0, 0x03, 0x01, 0x01, 0x01, 0xd9, 0x00, 0x05, 0xd8, 0x04, 0x05, 0x82, 0x01, + 0xf1, 0x03, 0x01, 0x01, 0x01, 0xd9, 0x00, 0x05, 0xd8, 0x04, 0x05, 0x82, 0x01 ], ); } @@ -14673,10 +14775,10 @@ def f(x, y, z): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdf, 0x0a, 0x0b, 0xdf, 0x0b, 0x0c, 0xd9, 0x0c, 0x10, 0xdf, 0x0d, 0x0e, + 0x81, 0x00, 0xdf, 0x0a, 0x0b, 0xdf, 0x0b, 0x0c, 0xd9, 0x0c, 0x10, 0xdf, 0x0d, 0x0e, 0xd8, 0x0f, 0x10, 0x90, 0x31, 0x8c, 0x75, 0xd8, 0x17, 0x18, 0x90, 0x08, 0xdf, 0x0f, 0x10, 0xd8, 0x14, 0x15, 0x98, 0x01, 0x95, 0x45, 0x92, 0x01, 0xf1, 0x03, 0x00, 0x10, - 0x11, 0xe7, 0x0d, 0x0e, 0x89, 0x51, 0xd9, 0x13, 0x14, 0xd8, 0x0b, 0x0d, 0x80, 0x49, + 0x11, 0xe7, 0x0d, 0x0e, 0x89, 0x51, 0xd9, 0x13, 0x14, 0xd8, 0x0b, 0x0d, 0x80, 0x49 ], "CPython basicblock_remove_redundant_nops() copies the full NOP location into a following no-location jump" ); @@ -14903,11 +15005,12 @@ def f(support, func, value): .expect("missing CPython-style false jump for if/break"); assert!( matches!( - ops.get(cond..cond + 5), + ops.get(cond..cond + 6), Some([ Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, Instruction::PopTop, + Instruction::PopTop, Instruction::JumpForward { .. }, Instruction::JumpBackward { .. }, ]) @@ -14951,7 +15054,7 @@ def f(input): Instruction::CompareOp { .. }, Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, Instruction::JumpBackward { .. }, ] @@ -15992,7 +16095,7 @@ def f(self): == "close" ) && matches!(window[2].op, Instruction::Call { .. }) && matches!(window[3].op, Instruction::PopTop) - && matches!(window[4].op, Instruction::LoadConst { .. }) + && matches!(window[4].op, Instruction::LoadCommonConstant { .. }) && matches!(window[5].op, Instruction::ReturnValue) }) .unwrap_or_else(|| { @@ -16521,7 +16624,7 @@ def f(buffer, pos, last_char): Instruction::NotTaken, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, ] ) @@ -16536,7 +16639,7 @@ def f(buffer, pos, last_char): Instruction::CompareOp { .. }, Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -16603,7 +16706,7 @@ def f( // CPython 3.14 codegen_function() computes firstlineno from the // FunctionDef before compiling annotations, then passes it to // codegen_function_body(). - assert_eq!(f.linetable.as_ref(), &[0x80, 0x00, 0xe1, 0x03, 0x06]); + assert_eq!(f.linetable.as_ref(), &[0x81, 0x00, 0xe1, 0x03, 0x06]); } #[test] @@ -16619,12 +16722,12 @@ def g(): // CPython 3.14 codegen_function_annotations() receives LOC(function) // and uses it for the annotation closure's BUILD_MAP/RETURN_VALUE and // for the parent MAKE_FUNCTION annotate sequence. - assert_eq!(g.linetable.as_ref(), &[0x80, 0x00, 0xdf, 0x04, 0x26]); + assert_eq!(g.linetable.as_ref(), &[0x81, 0x00, 0xdf, 0x04, 0x26]); assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0xd7, 0x04, 0x26, 0xd1, 0x04, 0x26, 0x94, 0x23, 0x9c, 0x13, 0xd0, 0x0d, - 0x1d, 0xd1, 0x04, 0x26, + 0x81, 0x00, 0xd7, 0x04, 0x26, 0xd1, 0x04, 0x26, 0x94, 0x23, 0x9c, 0x13, 0xd0, 0x0d, + 0x1d, 0xd1, 0x04, 0x26 ], ); } @@ -16639,7 +16742,7 @@ def g(): assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0xd7, 0x00, 0x17, 0xd1, 0x00, 0x17, 0x8c, 0x62, 0xd3, 0x00, 0x17 + 0x81, 0x00, 0xd7, 0x00, 0x17, 0xd1, 0x00, 0x17, 0x8c, 0x62, 0xd3, 0x00, 0x17 ], ); } @@ -16662,9 +16765,9 @@ Y: str assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0x87, 0x09, 0x81, 0x09, 0xdf, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, + 0x81, 0x00, 0x87, 0x09, 0x81, 0x09, 0xdf, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, 0x06, 0xf1, 0x03, 0x00, 0x01, 0x0a, 0xe7, 0x00, 0x06, 0x82, 0x06, 0x84, 0x33, 0x81, - 0x06, 0xf2, 0x05, 0x00, 0x01, 0x0a, + 0x06, 0xf2, 0x05, 0x00, 0x01, 0x0a ] ); } @@ -16935,7 +17038,7 @@ def f(self, other): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0f, 0x13, 0xd2, 0x0b, 0x1c, 0xd0, 0x04, 0x1c, + 0x81, 0x00, 0xd8, 0x0f, 0x13, 0xd2, 0x0b, 0x1c, 0xd0, 0x04, 0x1c ] ); } @@ -16957,9 +17060,9 @@ def f(c): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x10, 0x14, 0x98, 0x01, 0xd7, 0x10, 0x21, 0xd4, 0x10, 0x21, 0x98, + 0x81, 0x00, 0xd8, 0x10, 0x14, 0x98, 0x01, 0xd7, 0x10, 0x21, 0xd4, 0x10, 0x21, 0x98, 0x54, 0xd1, 0x10, 0x21, 0xd4, 0x0b, 0x22, 0xd0, 0x04, 0x22, 0xd1, 0x10, 0x21, 0xd4, - 0x0b, 0x22, 0xd0, 0x04, 0x22, + 0x0b, 0x22, 0xd0, 0x04, 0x22 ] ); } @@ -16979,19 +17082,19 @@ def f(c): assert_eq!( type_params.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0xd0, 0x00, 0x27, 0x90, 0x76, 0x9b, 0x23, 0x93, 0x76, 0xd7, 0x00, - 0x27, 0xd1, 0x00, 0x27, + 0xf8, 0x81, 0x00, 0xd0, 0x00, 0x27, 0x90, 0x76, 0x9b, 0x23, 0x93, 0x76, 0xd7, 0x00, + 0x27, 0xd1, 0x00, 0x27 ], ); assert_eq!( bound.linetable.as_ref(), - &[0x80, 0x00, 0x9f, 0x23, 0x9e, 0x23] + &[0x81, 0x00, 0x9f, 0x23, 0x9e, 0x23] ); assert_eq!( alias.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0xd7, 0x00, 0x27, 0xd0, 0x00, 0x27, 0xa4, 0x13, 0xa0, 0x51, 0xa5, - 0x16, 0xd0, 0x00, 0x27, + 0xf8, 0x81, 0x00, 0xd7, 0x00, 0x27, 0xd0, 0x00, 0x27, 0xa4, 0x13, 0xa0, 0x51, 0xa5, + 0x16, 0xd0, 0x00, 0x27 ], ); } @@ -17024,7 +17127,7 @@ def f(c): assert_eq!( type_params.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0x80, 0x0d, 0x84, 0x71, 0x87, 0x0d, 0x81, 0x0d + 0xf8, 0x81, 0x00, 0x80, 0x0d, 0x84, 0x71, 0x87, 0x0d, 0x81, 0x0d ], ); } @@ -17077,7 +17180,7 @@ def f(c): assert_eq!( annotate.linetable.as_ref(), &[ - 0x80, 0x00, 0xd7, 0x00, 0x15, 0xd1, 0x00, 0x15, 0x8c, 0x43, 0xd1, 0x00, 0x15, + 0x81, 0x00, 0xd7, 0x00, 0x15, 0xd1, 0x00, 0x15, 0x8c, 0x43, 0xd1, 0x00, 0x15 ], ); } @@ -17115,7 +17218,7 @@ def outer(): assert_eq!( type_params.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f, + 0xf8, 0x81, 0x00, 0x8c, 0x41, 0x87, 0x4f, 0x87, 0x4f, 0x80, 0x4f ] ); } @@ -17133,9 +17236,9 @@ def f(): let wrapper_positions: Vec<_> = f .instructions .iter() - .filter(|unit| !matches!(unit.op, Instruction::Resume { .. })) + .zip(&f.locations) + .filter(|(unit, _)| !matches!(unit.op, Instruction::Resume { .. } | Instruction::Cache)) .take(4) - .zip(f.locations.iter().filter(|_| true).skip(1)) .map(|(unit, (location, end_location))| { ( unit.op, @@ -17362,8 +17465,8 @@ class C: assert_eq!( class_code.linetable.as_ref(), &[ - 0xf8, 0x87, 0x00, 0x80, 0x00, 0xd9, 0x04, 0x09, 0xf7, 0x03, 0x00, 0x01, 0x01, 0x83, - 0x00, + 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd9, 0x04, 0x09, 0xf7, 0x03, 0x00, 0x01, 0x01, 0x83, + 0x00 ], ); } @@ -17378,7 +17481,7 @@ class C: // the __annotations__ store sequence at LOC(AnnAssign). assert_eq!( class_code.linetable.as_ref(), - &[0x87, 0x00, 0xd8, 0x09, 0x0c, 0x87, 0x48] + &[0x87, 0x00, 0x80, 0x00, 0xd8, 0x09, 0x0c, 0x87, 0x48] ); } @@ -17456,11 +17559,11 @@ g = lambda i: {**i} // inherits the full dict literal location after compiling its body. assert_eq!( f.linetable.as_ref(), - &[0x80, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b] + &[0x81, 0x00, 0x90, 0x23, 0x90, 0x74, 0x91, 0x1b] ); assert_eq!( g.linetable.as_ref(), - &[0x80, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65] + &[0x81, 0x00, 0x88, 0x65, 0x90, 0x11, 0x89, 0x65] ); } @@ -17703,11 +17806,10 @@ def f(self, payload): function.constants.as_ref(), [ ConstantData::Str { value: doc }, - ConstantData::None, ConstantData::Str { value: message }, ] if doc.as_ref() == "doc" && message.as_ref() == "bad" ), - "CPython registers None from the pre-folded `is None` comparison before the else-body string" + "CPython 3.15 keeps the docstring and else-body string in co_consts; None uses the common-constant table" ); } @@ -17811,7 +17913,7 @@ def f(self, maintype): .. }, CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, CodeUnit { @@ -18176,7 +18278,7 @@ def f(command): [ Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] )), "CPython NEXT_LOCATION keeps the pass NOP after match subject POP_TOP, got {ops:?}", @@ -18230,9 +18332,9 @@ def f(): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x08, 0x0d, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x0e, 0x97, + 0x81, 0x00, 0xd8, 0x08, 0x0d, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x0e, 0x97, 0x11, 0x97, 0x51, 0x9f, 0x11, 0x88, 0x5d, 0xe0, 0x0b, 0x0c, 0x80, 0x48, 0xf0, 0x05, - 0x00, 0x0e, 0x1b, 0xd8, 0x10, 0x14, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x80, 0x48, + 0x00, 0x0e, 0x1b, 0xd8, 0x10, 0x14, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x80, 0x48 ], "CPython codegen_pattern_or() emits each alternative COPY with LOC(alt)" ); @@ -18297,9 +18399,9 @@ def f(self): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0a, 0x0b, 0xde, 0x0d, 0x0e, 0xd9, 0x10, 0x14, 0x89, 0x41, 0xdd, + 0x81, 0x00, 0xd8, 0x0a, 0x0b, 0xde, 0x0d, 0x0e, 0xd9, 0x10, 0x14, 0x89, 0x41, 0xdd, 0x0d, 0x0e, 0xd8, 0x10, 0x15, 0x88, 0x41, 0xd8, 0x04, 0x08, 0x87, 0x4d, 0x81, 0x4d, - 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a, + 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a ], "CPython codegen_match_inner() emits the success jump with NO_LOCATION" ); @@ -18371,10 +18473,10 @@ def f(self): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x08, 0x0a, 0x80, 0x41, 0xd8, 0x08, 0x0c, 0x80, 0x41, 0xd8, 0x0a, + 0x81, 0x00, 0xd8, 0x08, 0x0a, 0x80, 0x41, 0xd8, 0x08, 0x0c, 0x80, 0x41, 0xd8, 0x0a, 0x0b, 0xdf, 0x0d, 0x13, 0x8f, 0x56, 0x8a, 0x56, 0x95, 0x11, 0x89, 0x56, 0xd8, 0x10, 0x11, 0x89, 0x41, 0xf2, 0x03, 0x00, 0x0e, 0x14, 0xe0, 0x04, 0x08, 0x87, 0x4d, 0x81, - 0x4d, 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a, + 0x4d, 0x90, 0x21, 0x90, 0x54, 0xd6, 0x04, 0x1a ], "CPython codegen_pattern_mapping() returns to LOC(p) for BUILD_TUPLE/MATCH_KEYS scaffolding" ); @@ -18446,9 +18548,9 @@ def f(x): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0a, 0x0b, 0xdc, 0x0d, 0x11, 0x8f, 0x57, 0x88, 0x57, 0xd8, 0x10, + 0x81, 0x00, 0xd8, 0x0a, 0x0b, 0xdc, 0x0d, 0x11, 0x8f, 0x57, 0x88, 0x57, 0xd8, 0x10, 0x11, 0x88, 0x41, 0xd8, 0x0b, 0x0c, 0x88, 0x34, 0x80, 0x4b, 0xf0, 0x05, 0x00, 0x0e, - 0x15, 0xe0, 0x0b, 0x0c, 0x88, 0x61, 0x88, 0x34, 0x80, 0x4b, + 0x15, 0xe0, 0x0b, 0x0c, 0x88, 0x61, 0x88, 0x34, 0x80, 0x4b ], "CPython codegen_pattern_class() returns to LOC(p) after VISIT(cls)" ); @@ -18551,7 +18653,7 @@ def f(waiters): [ Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, Instruction::LoadAttr { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] )), @@ -18896,11 +18998,11 @@ def or_true(x): // keeps the literal range rather than the whole BoolOp range. assert_eq!( and_false.linetable.as_ref(), - &[0x80, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16] + &[0x81, 0x00, 0xd8, 0x0b, 0x10, 0xd0, 0x04, 0x16] ); assert_eq!( or_true.linetable.as_ref(), - &[0x80, 0x00, 0xd8, 0x0b, 0x0f, 0xd0, 0x04, 0x14] + &[0x81, 0x00, 0xd8, 0x0b, 0x0f, 0xd0, 0x04, 0x14] ); } @@ -18919,7 +19021,7 @@ def f(): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x04, 0x15, 0x90, 0x23, 0xd3, 0x04, 0x15, 0x88, 0x35, + 0x81, 0x00, 0xd8, 0x04, 0x15, 0x90, 0x23, 0xd3, 0x04, 0x15, 0x88, 0x35 ] ); } @@ -18943,7 +19045,7 @@ def f(a, b): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0b, 0x0c, 0x80, 0x71, 0xd8, 0x0b, 0x0c, 0x82, 0x71, + 0x81, 0x00, 0xd8, 0x0b, 0x0c, 0x80, 0x71, 0xd8, 0x0b, 0x0c, 0x82, 0x71 ] ); } @@ -19076,14 +19178,14 @@ def f(parameters): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd9, 0x0b, 0x2d, 0xa1, 0x2a, 0xd3, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, + 0x81, 0x00, 0xd9, 0x0b, 0x2d, 0xa0, 0x2a, 0xd3, 0x0b, 0x2d, 0xd0, 0x04, 0x2d ] ); assert_eq!( genexpr.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x2d, 0xa1, 0x2a, 0x98, 0x51, 0x94, 0x04, 0x90, - 0x51, 0x93, 0x07, 0x8d, 0x4c, 0xa3, 0x2a, 0xf9, + 0xf9, 0xa2, 0x2a, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x2a, 0x98, 0x51, 0x94, 0x04, 0x90, + 0x51, 0x93, 0x07, 0x8e, 0x4c, 0xa3, 0x2a, 0xf9 ] ); } @@ -19112,22 +19214,24 @@ def explicit(): assert_eq!( implicit.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9c, 0x39, - 0xd3, 0x0f, 0x25, 0xd3, 0x0b, 0x25, 0xd0, 0x04, 0x25, + 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x0f, 0x25, 0x9c, 0x35, 0xa0, 0x12, + 0x9b, 0x39, 0xd3, 0x0f, 0x25, 0x8f, 0x34, 0xd0, 0x04, 0x25, 0x88, 0x34, 0xd1, 0x0f, + 0x25, 0x9c, 0x35, 0xa0, 0x12, 0x9b, 0x39, 0xd3, 0x0f, 0x25, 0xd3, 0x0b, 0x25, 0xd0, + 0x04, 0x25 ] ); assert_eq!( implicit_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x25, 0x99, 0x39, 0x90, 0x61, 0x94, 0x01, 0x9b, - 0x39, 0xf9, + 0xf9, 0x9a, 0x39, 0xe9, 0x00, 0x81, 0x00, 0x99, 0x39, 0x90, 0x61, 0x95, 0x01, 0x9b, + 0x39, 0xf9 ] ); assert_eq!( explicit_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x26, 0x99, 0x49, 0x90, 0x71, 0x94, 0x11, 0x9b, - 0x49, 0xf9, + 0xf9, 0x9a, 0x49, 0xe9, 0x00, 0x81, 0x00, 0x99, 0x49, 0x90, 0x71, 0x95, 0x11, 0x9b, + 0x49, 0xf9 ] ); } @@ -19167,57 +19271,59 @@ def explicit_gen(xs): assert_eq!( bytes_binop.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa4, 0x5a, - 0xd3, 0x10, 0x30, 0xd3, 0x0b, 0x30, 0xd0, 0x04, 0x30, + 0x81, 0x00, 0xdc, 0x0b, 0x10, 0xd1, 0x10, 0x30, 0xa4, 0x55, 0xa8, 0x33, 0xa3, 0x5a, + 0xd3, 0x10, 0x30, 0xd3, 0x0b, 0x30, 0xd0, 0x04, 0x30 ] ); assert_eq!( bytes_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x30, 0xa1, 0x5a, 0xa0, 0x01, 0x90, 0x64, 0x97, - 0x28, 0x92, 0x28, 0xa3, 0x5a, 0xf9, + 0xf9, 0xa2, 0x5a, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x5a, 0xa0, 0x01, 0x90, 0x64, 0x97, + 0x28, 0x93, 0x28, 0xa3, 0x5a, 0xf9 ] ); assert_eq!( dict_tuple.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x2f, 0xa0, 0x51, 0xa7, 0x57, 0xa1, 0x57, - 0xa4, 0x59, 0xd3, 0x0f, 0x2f, 0xd3, 0x0b, 0x2f, 0xd0, 0x04, 0x2f, + 0x81, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x2f, 0xa0, 0x51, 0xa7, 0x57, 0xa1, 0x57, + 0xa3, 0x59, 0xd3, 0x0f, 0x2f, 0xd3, 0x0b, 0x2f, 0xd0, 0x04, 0x2f ] ); assert_eq!( dict_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x2f, 0xa1, 0x59, 0x99, 0x36, 0x98, 0x41, 0x90, - 0x11, 0x95, 0x06, 0xa3, 0x59, 0xf9, + 0xf9, 0xa2, 0x59, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x59, 0x99, 0x36, 0x98, 0x41, 0x90, + 0x11, 0x96, 0x06, 0xa3, 0x59, 0xf9 ] ); assert_eq!( plain_tuple_elt.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x0f, 0x26, 0xa1, 0x32, 0xd3, 0x0f, 0x26, 0xd3, - 0x0b, 0x26, 0xd0, 0x04, 0x26, + 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x0f, 0x26, 0xa0, 0x32, 0xd3, 0x0f, + 0x26, 0x8f, 0x34, 0xd0, 0x04, 0x26, 0x88, 0x34, 0xd1, 0x0f, 0x26, 0xa0, 0x32, 0xd3, + 0x0f, 0x26, 0xd3, 0x0b, 0x26, 0xd0, 0x04, 0x26 ] ); assert_eq!( plain_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0f, 0x26, 0xa1, 0x32, 0x99, 0x34, 0x98, 0x31, 0x90, - 0x11, 0x95, 0x06, 0xa3, 0x32, 0xf9, + 0xf9, 0xa2, 0x32, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x32, 0x99, 0x34, 0x98, 0x31, 0x90, + 0x11, 0x96, 0x06, 0xa3, 0x32, 0xf9 ] ); assert_eq!( explicit_gen.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x0b, 0x0f, 0xd1, 0x10, 0x27, 0xa1, 0x42, 0xd3, 0x10, 0x27, 0xd3, - 0x0b, 0x28, 0xd0, 0x04, 0x28, + 0x81, 0x00, 0xdf, 0x0b, 0x0f, 0x8c, 0x34, 0xd1, 0x10, 0x27, 0xa0, 0x42, 0xd3, 0x10, + 0x27, 0x8f, 0x34, 0xd0, 0x04, 0x28, 0x88, 0x34, 0xd1, 0x10, 0x27, 0xa0, 0x42, 0xd3, + 0x10, 0x27, 0xd3, 0x0b, 0x28, 0xd0, 0x04, 0x28 ] ); assert_eq!( explicit_inner.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x10, 0x27, 0xa1, 0x42, 0x99, 0x44, 0x98, 0x41, 0x90, - 0x21, 0x95, 0x16, 0xa3, 0x42, 0xf9, + 0xf9, 0xa2, 0x42, 0xe9, 0x00, 0x81, 0x00, 0xa1, 0x42, 0x99, 0x44, 0x98, 0x41, 0x90, + 0x21, 0x96, 0x16, 0xa3, 0x42, 0xf9 ] ); } @@ -19236,14 +19342,14 @@ def f(p, q): let f = find_code(&code, "f").expect("missing f code"); let genexpr = find_code(f, "").expect("missing genexpr code"); - // Columns are one-based here, so these are `dis`'s 14..56. + // MAKE_FUNCTION keeps the genexpr range; eager .0 acquisition uses the iterable range. assert_eq!( instruction_range(f, |op| matches!(op, Instruction::MakeFunction)), Some((2, 15, 2, 57)) ); assert_eq!( instruction_range(genexpr, |op| matches!(op, Instruction::LoadFast { .. })), - Some((2, 15, 2, 57)) + Some((2, 47, 2, 56)) ); } @@ -19265,7 +19371,12 @@ w = f'' 'a' f'' .instructions .iter() .zip(&code.locations) - .filter(|(unit, _)| matches!(unit.op, Instruction::LoadConst { .. })) + .filter(|(unit, _)| { + matches!(unit.op, Instruction::LoadConst { .. }) + || matches!(unit.op, Instruction::LoadCommonConstant { idx } + if idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) + == bytecode::CommonConstant::EmptyStr) + }) .map(|(_, locations)| location_range(locations)) .take(4) .collect(); @@ -19300,16 +19411,16 @@ def boolop(fields): assert_eq!( simple_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x31, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8, - 0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8a, 0x41, 0x93, 0x75, 0xf9, + 0xf9, 0x92, 0x75, 0xe9, 0x00, 0x81, 0x00, 0x91, 0x75, 0x90, 0x21, 0xa4, 0x49, 0xa8, + 0x61, 0xa7, 0x4c, 0x8f, 0x41, 0x8b, 0x41, 0x93, 0x75, 0xf9 ] ); assert_eq!( boolop_gen.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd0, 0x0b, 0x3a, 0x91, 0x76, 0x90, 0x21, 0xa7, 0x16, 0xa5, - 0x16, 0x8c, 0x41, 0xb0, 0x01, 0xb7, 0x09, 0xb5, 0x09, 0x8f, 0x41, 0x8a, 0x41, 0x93, - 0x76, 0xf9, + 0xf9, 0x92, 0x76, 0xe9, 0x00, 0x81, 0x00, 0x91, 0x76, 0x90, 0x21, 0xa7, 0x16, 0xa5, + 0x16, 0x8c, 0x41, 0xb0, 0x01, 0xb7, 0x09, 0xb5, 0x09, 0x8f, 0x41, 0x8b, 0x41, 0x93, + 0x76, 0xf9 ] ); } @@ -19335,9 +19446,9 @@ def f(self, node): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x10, 0x14, 0x80, 0x44, 0x84, 0x49, 0xf0, 0x02, 0x03, 0x05, 0x1a, + 0x81, 0x00, 0xd8, 0x10, 0x14, 0x80, 0x44, 0x84, 0x49, 0xf0, 0x02, 0x03, 0x05, 0x1a, 0xd8, 0x08, 0x0c, 0x8f, 0x09, 0x89, 0x09, 0x90, 0x24, 0x8c, 0x0f, 0xe0, 0x14, 0x19, - 0x88, 0x04, 0x8e, 0x09, 0xf8, 0x90, 0x45, 0x88, 0x04, 0x8d, 0x09, 0xfa, + 0x88, 0x04, 0x8e, 0x09, 0xf8, 0x90, 0x45, 0x88, 0x04, 0x8d, 0x09, 0xfa ] ); } @@ -19363,11 +19474,10 @@ def f(close): .instructions .iter() .position(|unit| { - let Instruction::LoadConst { consti } = unit.op else { + let Instruction::LoadCommonConstant { idx } = unit.op else { return false; }; - let constant = &f.constants[consti.get(OpArg::new(u32::from(u8::from(unit.arg))))]; - matches!(constant, ConstantData::Boolean { value: true }) + idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) == bytecode::CommonConstant::True }) .expect("missing __debug__ constant load"); @@ -19494,7 +19604,7 @@ def f(file): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdc, 0x07, 0x0a, 0x87, 0x7c, 0x81, 0x7c, 0x90, 0x77, 0xd4, 0x07, 0x1e, + 0x81, 0x00, 0xdc, 0x07, 0x0a, 0x87, 0x7c, 0x81, 0x7c, 0x90, 0x77, 0xd4, 0x07, 0x1e, 0xf0, 0x02, 0x05, 0x09, 0x19, 0xdb, 0x0c, 0x15, 0xd8, 0x13, 0x15, 0xd7, 0x13, 0x30, 0xd1, 0x13, 0x30, 0xd7, 0x13, 0x32, 0xd2, 0x13, 0x32, 0xd9, 0x17, 0x1c, 0xf0, 0x03, 0x00, 0x14, 0x33, 0xf0, 0x08, 0x03, 0x05, 0x39, 0xdc, 0x0f, 0x11, 0x8f, 0x79, 0x89, @@ -19503,7 +19613,7 @@ def f(file): 0x01, 0x09, 0x19, 0xda, 0x13, 0x18, 0xf0, 0x03, 0x01, 0x09, 0x19, 0xfb, 0xf4, 0x08, 0x00, 0x0c, 0x13, 0xf4, 0x00, 0x01, 0x05, 0x39, 0xdc, 0x0f, 0x16, 0x90, 0x74, 0x98, 0x58, 0xd3, 0x0f, 0x26, 0xd7, 0x0f, 0x38, 0xd0, 0x0f, 0x38, 0xa8, 0x34, 0xaf, 0x3b, - 0xa9, 0x3b, 0xab, 0x3d, 0xd2, 0x08, 0x38, 0xf0, 0x03, 0x01, 0x05, 0x39, 0xfa, + 0xa9, 0x3b, 0xab, 0x3d, 0xd2, 0x08, 0x38, 0xf0, 0x03, 0x01, 0x05, 0x39, 0xfa ] ); } @@ -19527,13 +19637,13 @@ def spec(x): assert_eq!( simple.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0e, 0x12, 0x8f, 0x6a, 0x89, 0x6a, 0x88, 0x5c, 0xd0, 0x04, 0x1a, + 0x81, 0x00, 0xd8, 0x0e, 0x12, 0x8f, 0x6a, 0x89, 0x6a, 0x88, 0x5c, 0xd0, 0x04, 0x1a ] ); assert_eq!( spec.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x0e, 0x0f, 0x88, 0x58, 0x90, 0x22, 0x88, 0x58, 0xd0, 0x04, 0x16, + 0x81, 0x00, 0xd8, 0x0e, 0x0f, 0x88, 0x58, 0x90, 0x22, 0x88, 0x58, 0xd0, 0x04, 0x16 ] ); } @@ -19651,8 +19761,8 @@ def f(a): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xf0, 0x04, 0x01, 0x09, 0x0c, 0xd8, 0x0c, 0x0d, 0x88, 0x33, 0xf0, 0x00, - 0x01, 0x0f, 0x0c, 0xf0, 0x03, 0x02, 0x09, 0x0c, 0xf0, 0x03, 0x04, 0x05, 0x06, + 0x81, 0x00, 0xf0, 0x04, 0x01, 0x09, 0x0c, 0xd8, 0x0c, 0x0d, 0x88, 0x33, 0xf0, 0x00, + 0x01, 0x0f, 0x0c, 0xf0, 0x03, 0x02, 0x09, 0x0c, 0xf0, 0x03, 0x04, 0x05, 0x06 ], "CPython parser/codegen represents adjacent f-string literal fragments as Constant ranges spanning the merged fragments" ); @@ -19763,23 +19873,23 @@ class E: assert_eq!( c.linetable.as_ref(), &[ - 0xf8, 0x87, 0x00, 0x80, 0x00, 0xd8, 0x05, 0x08, 0xf1, 0x02, 0x01, 0x05, 0x0e, 0xf3, - 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x0e, + 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd8, 0x05, 0x08, 0xf1, 0x02, 0x01, 0x05, 0x0e, 0xf3, + 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x0e ] ); - assert_eq!(d.linetable.as_ref(), &[0x86, 0x00, 0xe3, 0x04, 0x08]); + assert_eq!(d.linetable.as_ref(), &[0x87, 0x00, 0xe3, 0x04, 0x08]); assert_eq!( e.linetable.as_ref(), &[ - 0xf8, 0x87, 0x00, 0x80, 0x00, 0xd8, 0x05, 0x08, 0xf7, 0x02, 0x01, 0x05, 0x22, 0xf3, - 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x22, + 0xf8, 0x87, 0x00, 0x81, 0x00, 0xd8, 0x05, 0x08, 0xf7, 0x02, 0x01, 0x05, 0x22, 0xf3, + 0x03, 0x00, 0x06, 0x09, 0xf6, 0x02, 0x01, 0x05, 0x22 ] ); assert_eq!( annotate.linetable.as_ref(), &[ - 0xf8, 0x80, 0x00, 0xf7, 0x00, 0x01, 0x05, 0x22, 0xf1, 0x00, 0x01, 0x05, 0x22, 0x91, - 0x73, 0xf0, 0x00, 0x01, 0x05, 0x22, 0xa1, 0x2a, 0xf1, 0x00, 0x01, 0x05, 0x22, + 0xf8, 0x81, 0x00, 0xf7, 0x00, 0x01, 0x05, 0x22, 0xf1, 0x00, 0x01, 0x05, 0x22, 0x91, + 0x73, 0xf0, 0x00, 0x01, 0x05, 0x22, 0xa1, 0x2a, 0xf1, 0x00, 0x01, 0x05, 0x22 ] ); } @@ -20138,7 +20248,7 @@ x = +0.0j Instruction::Resume { .. }, Instruction::LoadConst { .. }, Instruction::StoreName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue ] ), @@ -20585,8 +20695,8 @@ def f(xs): .iter() .filter(|unit| matches!(unit.op, Instruction::PushNull)) .count(), - 1, - "fallback call path should remain for shadowed any()" + 2, + "the iterator NULL slot and fallback call NULL slot must both remain" ); let genexpr_const_count = f .constants @@ -20602,9 +20712,9 @@ def f(xs): assert_eq!( f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdf, 0x0b, 0x0e, 0x8b, 0x33, 0x89, 0x6f, 0x99, 0x22, 0x8b, 0x6f, 0x8f, - 0x33, 0x8c, 0x33, 0xd0, 0x04, 0x1d, 0x8a, 0x33, 0xd0, 0x04, 0x1d, 0x88, 0x33, 0x89, - 0x6f, 0x99, 0x22, 0x8b, 0x6f, 0xd3, 0x0b, 0x1d, 0xd0, 0x04, 0x1d, + 0x81, 0x00, 0xdf, 0x0b, 0x0e, 0x8b, 0x33, 0x89, 0x6f, 0x98, 0x22, 0x8b, 0x6f, 0x8f, + 0x33, 0x8d, 0x33, 0xd0, 0x04, 0x1d, 0x8a, 0x33, 0xd0, 0x04, 0x1d, 0x88, 0x33, 0x89, + 0x6f, 0x98, 0x22, 0x8b, 0x6f, 0xd3, 0x0b, 0x1d, 0xd0, 0x04, 0x1d ] ); } @@ -20656,7 +20766,7 @@ async def f(get_xs): } #[test] - fn builtin_tuple_genexpr_call_is_optimized_but_list_set_are_not() { + fn builtin_tuple_list_set_genexpr_calls_are_optimized() { let code = compile_exec( "\ def tuple_f(xs): @@ -20684,13 +20794,13 @@ def set_f(xs): _ => None, }) .expect("tuple(genexpr) fast path should emit LIST_APPEND"); - assert_eq!(tuple_list_append, 2); + assert_eq!(tuple_list_append, 3); assert_eq!( tuple_f.linetable.as_ref(), &[ - 0x80, 0x00, 0xdf, 0x0b, 0x10, 0x8c, 0x35, 0x91, 0x0f, 0x99, 0x42, 0x93, 0x0f, 0x8f, - 0x35, 0xd0, 0x04, 0x1f, 0x88, 0x35, 0x91, 0x0f, 0x99, 0x42, 0x93, 0x0f, 0xd3, 0x0b, - 0x1f, 0xd0, 0x04, 0x1f, + 0x81, 0x00, 0xdf, 0x0b, 0x10, 0x8c, 0x35, 0x91, 0x0f, 0x98, 0x42, 0x93, 0x0f, 0x8f, + 0x35, 0x88, 0x35, 0xd0, 0x04, 0x1f, 0x88, 0x35, 0x91, 0x0f, 0x98, 0x42, 0x93, 0x0f, + 0xd3, 0x0b, 0x1f, 0xd0, 0x04, 0x1f ] ); @@ -20700,11 +20810,11 @@ def set_f(xs): .instructions .iter() .any(|unit| matches!(unit.op, Instruction::Call { .. })), - "list(genexpr) should stay on the normal call path" + "list(genexpr) keeps a fallback call for a shadowed builtin" ); assert!( - !has_common_constant(list_f, bytecode::CommonConstant::BuiltinList), - "CPython 3.14.5 does not optimize list(genexpr)" + has_common_constant(list_f, bytecode::CommonConstant::BuiltinList), + "CPython 3.15 guards the optimized list(genexpr) path by builtin identity" ); let set_f = find_code(&code, "set_f").expect("missing set_f code"); @@ -20713,12 +20823,23 @@ def set_f(xs): .instructions .iter() .any(|unit| matches!(unit.op, Instruction::Call { .. })), - "set(genexpr) should stay on the normal call path" + "set(genexpr) keeps a fallback call for a shadowed builtin" ); assert!( - !has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet), - "CPython 3.14.5 does not optimize set(genexpr)" + has_common_constant(set_f, bytecode::CommonConstant::BuiltinSet), + "CPython 3.15 guards the optimized set(genexpr) path by builtin identity" ); + for (code, append) in [ + (list_f, bytecode::Opcode::ListAppend), + (set_f, bytecode::Opcode::SetAdd), + ] { + assert!( + cpython315_decoded_instructions(code) + .iter() + .any(|(_, op, arg)| { op.as_opcode() == append && *arg == 3 }) + ); + assert!(!has_intrinsic_1(code, IntrinsicFunction1::ListToTuple)); + } } #[test] @@ -20969,7 +21090,7 @@ def f(cache, lock, format): Instruction::LoadFastLoadFast { .. }, Instruction::LoadFast { .. }, Instruction::StoreSubscr, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -21126,8 +21247,8 @@ def aug_const(x, y): assert_eq!( aug_const.linetable.as_ref(), &[ - 0x80, 0x00, 0xd8, 0x04, 0x05, 0x80, 0x63, 0x87, 0x46, 0x88, 0x61, 0x85, 0x4b, 0x85, - 0x46, + 0x81, 0x00, 0xd8, 0x04, 0x05, 0x80, 0x63, 0x87, 0x46, 0x88, 0x61, 0x85, 0x4b, 0x85, + 0x46 ] ); } @@ -21186,7 +21307,7 @@ def f(obj): .filter(|op| !matches!(op, Instruction::Cache)) .collect(); - let has_cpython_shape = ops.windows(7).any(|window| { + let has_cpython_shape = ops.windows(8).any(|window| { matches!( window, [ @@ -21196,6 +21317,7 @@ def f(obj): Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, Instruction::Swap { .. }, Instruction::PopTop, + Instruction::PopTop, Instruction::ReturnValue, ] ) @@ -21570,7 +21692,7 @@ def f(self, exc_type, KeyboardInterrupt, TimeoutExpired): Instruction::LoadSmallInt { .. }, Instruction::LoadFastBorrow { .. }, Instruction::StoreAttr { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -21710,7 +21832,7 @@ def f(locale, category, locales): Instruction::RaiseVarargs { .. }, Instruction::Nop, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::YieldValue { .. }, ] ) @@ -21857,18 +21979,19 @@ def f(parts): ); let break_cleanup_idx = ops - .windows(3) + .windows(4) .position(|window| { matches!( window, [ Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::PopTop, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue ] ) }) - .expect("missing POP_TOP/LOAD_CONST/RETURN_VALUE break cleanup"); + .expect("missing POP_TOP/POP_TOP/LOAD_COMMON_CONSTANT/RETURN_VALUE break cleanup"); let end_for_idx = ops .iter() .position(|op| matches!(op, Instruction::EndFor)) @@ -21908,7 +22031,7 @@ elif maxsize == 9223372036854775807: [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -21923,7 +22046,7 @@ elif maxsize == 9223372036854775807: Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -22574,7 +22697,7 @@ def f(self, file, backupfilename): ], [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::LoadFast { .. }, Instruction::StoreAttr { .. }, Instruction::Reraise { .. }, @@ -23068,15 +23191,9 @@ def f(x): ) }) .expect("missing folded -0.0 match value"); - let none_index = f - .constants - .iter() - .position(|constant| matches!(constant, ConstantData::None)) - .expect("missing implicit None"); - assert!( - negative_zero_index < none_index, - "CPython ast_preprocess.c folds MatchValue constants before codegen registers the implicit None" - ); + assert_eq!(negative_zero_index, 0); + assert_eq!(f.constants.len(), 1); + assert!(has_common_constant(f, bytecode::CommonConstant::None)); } #[test] @@ -23163,7 +23280,7 @@ def f(x): } #[test] - fn match_mapping_attribute_key_keeps_plain_load_fast_without_block_disable() { + fn match_mapping_attribute_key_keeps_borrowed_load_fast_without_block_disable() { let code = compile_exec( "\ def f(self): @@ -23211,8 +23328,8 @@ def f(self): .expect("missing Keys.KEY attribute load"); let prev = f.instructions[key_load_idx - 1].op; assert!( - matches!(prev, Instruction::LoadFast { .. }), - "CPython optimize_load_fast() records MATCH_KEYS' no-input pseudo-ref with the produced-value loop index, so this consumed Keys load stays strong; got ops={:?}", + matches!(prev, Instruction::LoadFastBorrow { .. }), + "CPython optimize_load_fast() records MATCH_KEYS' no-input pseudo-ref with the current instruction index, so this consumed Keys load is borrowed; got ops={:?}", f.instructions .iter() .map(|unit| unit.op) @@ -23396,9 +23513,9 @@ def f(i): matches!( window, [ - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -23595,9 +23712,9 @@ def f(cm): [ (Instruction::DeleteFast { .. }, 6), (Instruction::Nop, 6), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), (Instruction::Call { .. }, 2), ] ) @@ -23635,9 +23752,9 @@ def f(cm, names, modname): Instruction::Call { .. }, Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23674,9 +23791,9 @@ def f(cm): [ Instruction::StoreFast { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23716,9 +23833,9 @@ def f(cm, func, check): Instruction::Call { .. }, Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23763,9 +23880,9 @@ def f(meta_path, cm): (Instruction::Call { .. }, 9), (Instruction::StoreFast { .. }, 9), (Instruction::Nop, 9), - (Instruction::LoadConst { .. }, 3), - (Instruction::LoadConst { .. }, 3), - (Instruction::LoadConst { .. }, 3), + (Instruction::LoadCommonConstant { .. }, 3), + (Instruction::LoadCommonConstant { .. }, 3), + (Instruction::LoadCommonConstant { .. }, 3), ] ) }), @@ -23850,9 +23967,9 @@ def f(cm): [ Instruction::StoreFast { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23865,9 +23982,9 @@ def f(cm): window, [ Instruction::StoreFast { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -23904,9 +24021,9 @@ def f(cm, ValueError): window, [ (Instruction::Nop, 6), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), (Instruction::Call { .. }, 2), ] ) @@ -23944,9 +24061,9 @@ def f(open, src, dst, copyfileobj): [ Instruction::Call { .. }, Instruction::PopTop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -23962,9 +24079,9 @@ def f(open, src, dst, copyfileobj): Instruction::Call { .. }, Instruction::PopTop, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -24727,9 +24844,9 @@ def f(selector, self): [ (Instruction::JumpBackward { .. }, 4), (Instruction::Nop, 3), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), - (Instruction::LoadConst { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), + (Instruction::LoadCommonConstant { .. }, 2), (Instruction::Call { .. }, 2), ] ) @@ -25873,7 +25990,7 @@ def f(part, lines, maxlen, encoding): window, [ Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreSubscr, ] ) @@ -25998,7 +26115,7 @@ def f(part, lines, maxlen, encoding): window, [ Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreSubscr, ] ) @@ -26930,11 +27047,23 @@ def f(fields): Instruction::LoadFastBorrow { .. }, Instruction::LoadAttr { .. }, Instruction::Call { .. }, - Instruction::GetIter, + Instruction::Call { .. }, ] ) }), - "expected plain attr-iter chain to keep borrowed receiver, got ops={ops:?}" + "expected plain attr-genexpr-call chain to keep borrowed receiver, got ops={ops:?}" + ); + assert!( + !f.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::GetIter { .. })) + ); + let genexpr = find_code(f, "").expect("missing genexpr"); + assert!( + genexpr + .instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::GetIter { .. })) ); } @@ -28083,15 +28212,20 @@ def bug(): ) .unwrap(); + assert!( + entries + .iter() + .all(|entry| { not_taken_idx < entry.start || not_taken_idx >= entry.end }) + ); assert!( entries.iter().any(|entry| { - entry.start <= not_taken_idx + entry.start <= alias_store_idx && alias_store_idx < entry.end && entry.target == copy_idx && entry.depth == 1 && entry.push_lasti }), - "CPython codegen_try_except() stores the exception alias before the inner SETUP_CLEANUP, so NOT_TAKEN and the alias store stay covered by the outer cleanup entry; entries={entries:?}, instructions={:?}", + "CPython codegen_try_except() stores the exception alias before the inner SETUP_CLEANUP, so the alias store stays covered by the outer cleanup entry; entries={entries:?}, instructions={:?}", bug.instructions ); } @@ -28452,7 +28586,7 @@ def f(self): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreName { .. } | Instruction::StoreFast { .. }, Instruction::DeleteName { .. } | Instruction::DeleteFast { .. }, Instruction::JumpForward { .. }, @@ -28903,7 +29037,13 @@ def f(cond, obj, xs, E): _ => None, }; - for name in ["xs", "obj", "x"] { + assert!(normal_tail.iter().any(|unit| { + matches!(unit.op, Instruction::LoadFast { .. }) && load_name(unit) == Some("xs") + })); + assert!(!normal_tail.iter().any(|unit| { + matches!(unit.op, Instruction::LoadFastBorrow { .. }) && load_name(unit) == Some("xs") + })); + for name in ["obj", "x"] { assert!( normal_tail .iter() @@ -29410,9 +29550,9 @@ def f(obj): [ Instruction::Resume { .. }, Instruction::LoadFastBorrow { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BuildSlice { .. }, Instruction::BinaryOp { .. }, Instruction::ReturnValue, @@ -29437,7 +29577,7 @@ def f(obj, step): .zip(&f.locations) .filter_map(|(unit, (location, end_location))| { let op = match unit.op { - Instruction::LoadConst { .. } => "LOAD_CONST", + Instruction::LoadCommonConstant { .. } => "LOAD_CONST", Instruction::BuildSlice { .. } => "BUILD_SLICE", _ => return None, }; @@ -29566,7 +29706,7 @@ def f(obj): Instruction::Resume { .. }, Instruction::LoadFastBorrow { .. }, Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BinarySlice, Instruction::ReturnValue, ] @@ -29732,9 +29872,9 @@ def f(self, xs, ys, cm1, cm2): [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -29750,9 +29890,9 @@ def f(self, xs, ys, cm1, cm2): Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -29780,15 +29920,9 @@ def f(p, s): .filter(|unit| { matches!( unit.op, - Instruction::LoadConst { consti } - if matches!( - f.constants.get( - consti - .get(OpArg::new(u32::from(u8::from(unit.arg)))) - .as_usize() - ), - Some(ConstantData::Integer { value }) if value == &BigInt::from(-1) - ) + Instruction::LoadCommonConstant { idx } + if idx.get(OpArg::new(u32::from(u8::from(unit.arg)))) + == bytecode::CommonConstant::MinusOne ) }) .count(); @@ -29821,18 +29955,21 @@ def f(xs): .collect(); assert!( - units.windows(4).any(|window| { + units.windows(5).any(|window| { matches!( window[0].op, - Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. } + Instruction::LoadCommonConstant { idx } + if idx.get(OpArg::new(u32::from(u8::from(window[0].arg)))) + == bytecode::CommonConstant::MinusOne ) && matches!( window[1].op, Instruction::Swap { i } - if i.get(OpArg::new(u32::from(u8::from(window[1].arg)))) == 2 + if i.get(OpArg::new(u32::from(u8::from(window[1].arg)))) == 3 ) && matches!(window[2].op, Instruction::PopTop) - && matches!(window[3].op, Instruction::ReturnValue) + && matches!(window[3].op, Instruction::PopTop) + && matches!(window[4].op, Instruction::ReturnValue) }), - "expected CPython-style LOAD_CONST/SWAP/POP_TOP/RETURN_VALUE cleanup, got units={units:?}" + "expected CPython-style LOAD_COMMON_CONSTANT/SWAP 3/POP_TOP/POP_TOP/RETURN_VALUE cleanup, got units={units:?}" ); } @@ -29999,10 +30136,10 @@ def f(self): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. } | Instruction::StoreName { .. }, Instruction::DeleteFast { .. } | Instruction::DeleteName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30043,10 +30180,10 @@ def f(onerror, err, OSError): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. } | Instruction::StoreName { .. }, Instruction::DeleteFast { .. } | Instruction::DeleteName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30087,10 +30224,10 @@ def f(self, module_name, ModuleNotFoundError): window, [ Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. } | Instruction::StoreName { .. }, Instruction::DeleteFast { .. } | Instruction::DeleteName { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30377,12 +30514,12 @@ def f(cm, registry, altkey): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::ReturnValue, ] ) @@ -30424,9 +30561,9 @@ def f(a, b, path): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -30630,7 +30767,7 @@ def f(self, logger): [ Instruction::PopTop, Instruction::PopExcept, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, Instruction::DeleteFast { .. }, Instruction::JumpBackwardNoInterrupt { .. }, @@ -32205,7 +32342,7 @@ def f(): } #[test] - fn constant_list_iterable_uses_tuple() { + fn constant_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def f(): @@ -32214,26 +32351,17 @@ def f(): ); let f = find_code(&code, "f").expect("missing function code"); - assert!( - !f.instructions - .iter() - .any(|unit| matches!(unit.op, Instruction::BuildList { .. })), - "constant list iterable should avoid BUILD_LIST before GET_ITER" - ); + assert!(f.instructions.iter().any(|unit| { + matches!(unit.op, Instruction::BuildList { count } + if count.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 1) + })); assert!(f.constants.iter().any(|constant| matches!( constant, ConstantData::Tuple { elements } - if matches!( - elements.as_slice(), - [ConstantData::Tuple { elements: inner }] - if matches!( - inner.as_slice(), - [ - ConstantData::Integer { .. }, - ConstantData::Integer { .. } - ] - ) - ) + if matches!(elements.as_slice(), [ + ConstantData::Integer { value: first }, + ConstantData::Integer { value: second }, + ] if first == &BigInt::from(1) && second == &BigInt::from(2)) ))); } @@ -32253,9 +32381,9 @@ def f(): matches!(constants[0], ConstantData::Str { value } if value.to_string() == "a"), "CPython emits list elements as LOAD_CONST before flowgraph folds GET_ITER lists" ); - assert!(matches!(constants[1], ConstantData::None)); + assert_eq!(constants.len(), 2); assert!(matches!( - constants[2], + constants[1], ConstantData::Tuple { elements } if matches!( elements.as_slice(), @@ -32475,12 +32603,11 @@ def f(g, kwargs, ns): ); let f = find_code(&code, "f").expect("missing function code"); let constants = f.constants.iter().collect::>(); - assert_eq!(constants.len(), 3); + assert_eq!(constants.len(), 2); assert!( matches!(constants[0], ConstantData::Str { value } if value.to_string() == "T") - && matches!(constants[1], ConstantData::None) - && matches!(constants[2], ConstantData::Tuple { elements } if elements.is_empty()), + && matches!(constants[1], ConstantData::Tuple { elements } if elements.is_empty()), "CPython emits BUILD_TUPLE 0 for CALL_FUNCTION_EX args and folds it after earlier constants" ); } @@ -32524,10 +32651,10 @@ def f(): let f = find_code(&code, "f").expect("missing function code"); assert!( - !f.instructions + f.instructions .iter() .any(|unit| matches!(unit.op, Instruction::BuildSet { .. })), - "constant set iterable should avoid BUILD_SET before GET_ITER" + "constant set iterable should build the set before saved locals and GET_ITER" ); assert!(f.constants.iter().any(|constant| matches!( constant, @@ -32707,13 +32834,13 @@ def f(a, b, c): assert!( f.instructions .iter() - .any(|unit| matches!(unit.op, Instruction::GetIter)), + .any(|unit| matches!(unit.op, Instruction::GetIter { .. })), "expected GET_ITER in for loop" ); } #[test] - fn comprehension_single_list_iterable_uses_tuple() { + fn comprehension_single_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def g(): @@ -32729,13 +32856,20 @@ def g(): .collect(); assert!( - ops.windows(2).any(|window| { + ops.windows(6).any(|window| { matches!( window, - [Instruction::BuildTuple { .. }, Instruction::GetIter] + [ + Instruction::BuildList { .. }, + Instruction::LoadFastAndClear { .. }, + Instruction::Swap { .. }, + Instruction::BuildList { .. }, + Instruction::Swap { .. }, + Instruction::GetIter { .. }, + ] ) }), - "expected BUILD_TUPLE before GET_ITER for single-item list iterable in comprehension, got ops={ops:?}" + "expected BUILD_LIST before saved locals, output collection, and GET_ITER for single-item list iterable in comprehension, got ops={ops:?}" ); } @@ -32754,10 +32888,10 @@ async def run_list(): assert_eq!( run_list.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xdc, 0x1e, 0x1f, 0xa0, 0x01, 0x9b, 0x64, 0xa4, 0x41, 0xa0, - 0x62, 0xa3, 0x45, 0x99, 0x5d, 0xd3, 0x0b, 0x2b, 0x99, 0x5d, 0x98, 0x01, 0x8f, 0x47, - 0x8a, 0x47, 0x99, 0x5d, 0xd1, 0x0b, 0x2b, 0xd0, 0x04, 0x2b, 0x89, 0x47, 0xf9, 0xd2, - 0x0b, 0x2b, 0xf9, + 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x1e, 0x1f, 0xa0, 0x01, 0x9b, 0x64, 0xa4, 0x41, 0xa0, + 0x62, 0xa3, 0x45, 0x98, 0x5d, 0xd3, 0x0b, 0x2b, 0x9b, 0x5d, 0x98, 0x01, 0x8f, 0x47, + 0x8c, 0x47, 0x99, 0x5d, 0xd1, 0x0b, 0x2b, 0xd0, 0x04, 0x2b, 0x89, 0x47, 0xf9, 0xd2, + 0x0b, 0x2b, 0xf9 ], "CPython codegen_comprehension_iter() emits GET_ITER at LOC(comp->iter)" ); @@ -32772,12 +32906,13 @@ def f(self): ", ); let f = find_code(&code, "f").expect("missing f code"); - let get_iter_positions: Vec<_> = f + let genexpr = find_code(f, "").expect("missing genexpr code"); + let get_iter_positions: Vec<_> = genexpr .instructions .iter() - .zip(&f.locations) + .zip(&genexpr.locations) .filter_map(|(unit, (location, end_location))| { - matches!(unit.op, Instruction::GetIter).then_some(( + matches!(unit.op, Instruction::GetIter { .. }).then_some(( location.line.get(), location.character_offset.get(), end_location.line.get(), @@ -32807,10 +32942,10 @@ async def run_list(): assert_eq!( run_list.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xdc, 0x18, 0x19, 0x98, 0x22, 0x9b, 0x05, 0x9c, 0x71, 0xa0, - 0x15, 0x9b, 0x78, 0xd1, 0x17, 0x28, 0xd4, 0x0b, 0x3a, 0xd1, 0x17, 0x28, 0x90, 0x21, - 0xb7, 0x27, 0xb2, 0x27, 0xa8, 0x51, 0x8a, 0x41, 0xb1, 0x27, 0x89, 0x41, 0xd1, 0x17, - 0x28, 0xd2, 0x0b, 0x3a, 0xd0, 0x04, 0x3a, 0xb1, 0x27, 0xf9, 0xd3, 0x0b, 0x3a, 0xf9, + 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x18, 0x19, 0x98, 0x22, 0x9b, 0x05, 0x9c, 0x71, 0xa0, + 0x15, 0x9b, 0x78, 0xd0, 0x17, 0x28, 0xd4, 0x0b, 0x3a, 0xd3, 0x17, 0x28, 0x90, 0x21, + 0xb7, 0x27, 0xb5, 0x27, 0xa8, 0x51, 0x8a, 0x41, 0xb1, 0x27, 0x89, 0x41, 0xd1, 0x17, + 0x28, 0xd2, 0x0b, 0x3a, 0xd0, 0x04, 0x3a, 0xb1, 0x27, 0xf9, 0xd3, 0x0b, 0x3a, 0xf9 ], "CPython codegen_sync_comprehension_generator() emits comprehension backedges at elt_loc" ); @@ -32923,9 +33058,10 @@ async def run_list(): assert_eq!( run_list.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xdc, 0x21, 0x22, 0xa0, 0x42, 0xa8, 0x02, 0xa0, 0x38, 0xa4, - 0x1b, 0xd7, 0x0b, 0x2d, 0xd3, 0x0b, 0x2d, 0x98, 0x41, 0x90, 0x01, 0x8f, 0x45, 0x88, - 0x45, 0xd4, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, 0xf9, 0xd2, 0x0b, 0x2d, 0xf9, + 0xe9, 0x00, 0x81, 0x00, 0xdc, 0x21, 0x22, 0xa0, 0x42, 0xa8, 0x02, 0xa0, 0x38, 0xa3, + 0x1b, 0xd3, 0x0b, 0x2d, 0xa0, 0x1b, 0xd7, 0x0b, 0x2d, 0xd1, 0x0b, 0x2d, 0x98, 0x41, + 0x90, 0x01, 0x8f, 0x45, 0x88, 0x45, 0xd4, 0x0b, 0x2d, 0xd0, 0x04, 0x2d, 0xf9, 0xd2, + 0x0b, 0x2d, 0xf9 ], "CPython codegen_async_comprehension_generator() emits END_ASYNC_FOR at comprehension loc" ); @@ -32944,17 +33080,17 @@ async def f(source, buffer): assert_eq!( f.linetable.as_ref(), &[ - 0xe9, 0x00, 0x80, 0x00, 0xd9, 0x18, 0x1e, 0x9c, 0x08, 0xf7, 0x00, 0x01, 0x05, 0x1f, - 0xf0, 0x00, 0x01, 0x05, 0x1f, 0x89, 0x66, 0x88, 0x62, 0xd8, 0x08, 0x0e, 0x8f, 0x0d, + 0xe9, 0x00, 0x81, 0x00, 0xd9, 0x18, 0x1e, 0x9c, 0x08, 0xf7, 0x00, 0x01, 0x05, 0x1f, + 0xf2, 0x00, 0x01, 0x05, 0x1f, 0x89, 0x66, 0x88, 0x62, 0xd8, 0x08, 0x0e, 0x8f, 0x0d, 0x89, 0x0d, 0x90, 0x62, 0x95, 0x67, 0xd6, 0x08, 0x1e, 0xf1, 0x03, 0x01, 0x05, 0x1f, - 0x9a, 0x08, 0xf9, + 0x9a, 0x08, 0xf9 ], "CPython codegen_async_for() emits GET_ANEXT/yield-from scaffolding at LOC(s)" ); } #[test] - fn nested_comprehension_list_iterable_uses_tuple() { + fn nested_comprehension_list_iterable_keeps_list_before_saved_locals() { let code = compile_exec( "\ def f(): @@ -32970,13 +33106,20 @@ def f(): .collect(); assert!( - ops.windows(2).any(|window| { + ops.windows(6).any(|window| { matches!( window, - [Instruction::BuildTuple { .. }, Instruction::GetIter] + [ + Instruction::BuildList { .. }, + Instruction::LoadFastAndClear { .. }, + Instruction::Swap { .. }, + Instruction::BuildList { .. }, + Instruction::Swap { .. }, + Instruction::GetIter { .. }, + ] ) }), - "expected BUILD_TUPLE before GET_ITER for nested list iterable in comprehension, got ops={ops:?}" + "expected BUILD_LIST before saved locals, output collection, and GET_ITER for nested list iterable in comprehension, got ops={ops:?}" ); } @@ -32994,11 +33137,11 @@ def f(): .iter() .filter(|unit| matches!(unit.op, Instruction::ForIter { .. })) .count(); - let has_map_add_depth_2 = f.instructions.iter().any(|unit| { + let has_map_add_depth_3 = f.instructions.iter().any(|unit| { matches!( unit.op, Instruction::MapAdd { i } - if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 2 + if i.get(OpArg::new(u32::from(u8::from(unit.arg)))) == 3 ) }); @@ -33008,8 +33151,8 @@ def f(): f.instructions ); assert!( - has_map_add_depth_2, - "assignment-idiom dictcomp should use MAP_ADD depth 2, got instructions={:?}", + has_map_add_depth_3, + "assignment-idiom dictcomp should use MAP_ADD depth 3, got instructions={:?}", f.instructions ); assert!( @@ -33327,9 +33470,9 @@ async def f(cm, source, tgt): [ Instruction::EndAsyncFor, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -33492,19 +33635,29 @@ _pathseps_with_colon = {f':{s}' for s in path_separators} .filter(|op| !matches!(op, Instruction::Cache)) .collect(); - let load_name_path = ops - .windows(2) - .any(|window| matches!(window, [Instruction::LoadName { .. }, Instruction::GetIter])); + let load_name_path = ops.windows(6).any(|window| { + matches!( + window, + [ + Instruction::LoadName { .. }, + Instruction::LoadFastAndClear { .. }, + Instruction::Swap { .. }, + Instruction::BuildSet { .. }, + Instruction::Swap { .. }, + Instruction::GetIter { .. } + ] + ) + }); assert!( load_name_path, - "expected outer iterable to stay a NAME lookup before GET_ITER, got ops={ops:?}" + "expected NAME lookup before locals are saved and iterator acquisition after collection construction, got ops={ops:?}" ); assert!( !ops.windows(2).any(|window| matches!( window, [ Instruction::LoadFast { .. } | Instruction::LoadFastCheck { .. }, - Instruction::GetIter + Instruction::GetIter { .. } ] )), "module local outer iterable should not become a fast local, got ops={ops:?}" @@ -33633,7 +33786,7 @@ def outer(): // between visiting the value and visiting the target. assert_eq!( spam.linetable.as_ref(), - &[0xf8, 0x80, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11,] + &[0xf8, 0x81, 0x00, 0xe0, 0x0e, 0x10, 0x88, 0x17, 0x8b, 0x11] ); } @@ -33828,13 +33981,15 @@ values = ( "CPython codegen_stmt_expr() prints and pops interactive expressions; it does not preserve the final expression as the code object's return value, got ops={ops:?}" ); let Some(load_none) = ops.iter().rev().nth(1) else { - panic!("missing final LOAD_CONST None before RETURN_VALUE, got ops={ops:?}"); + panic!("missing final LOAD_COMMON_CONSTANT None before RETURN_VALUE, got ops={ops:?}"); }; - let Instruction::LoadConst { consti } = load_none.op else { - panic!("missing final LOAD_CONST None before RETURN_VALUE, got ops={ops:?}"); + let Instruction::LoadCommonConstant { idx } = load_none.op else { + panic!("missing final LOAD_COMMON_CONSTANT None before RETURN_VALUE, got ops={ops:?}"); }; - let constant = &code.constants[consti.get(OpArg::new(u32::from(u8::from(load_none.arg))))]; - assert!(matches!(constant, ConstantData::None)); + assert_eq!( + idx.get(OpArg::new(u32::from(u8::from(load_none.arg)))), + bytecode::CommonConstant::None + ); assert!(matches!( ops.last().map(|unit| unit.op), Some(Instruction::ReturnValue) @@ -35160,7 +35315,7 @@ def f(items): let get_iter_idx = ops .iter() - .rposition(|op| matches!(op, Instruction::GetIter)) + .rposition(|op| matches!(op, Instruction::GetIter { .. })) .expect("missing GET_ITER"); assert!( matches!(ops[get_iter_idx - 1], Instruction::LoadFast { .. }), @@ -35183,7 +35338,7 @@ def f(items): } #[test] - fn plain_pass_before_for_tail_keeps_borrows() { + fn plain_pass_before_for_tail_keeps_owned_iterator_input() { let code = compile_exec( "\ def f(xs): @@ -35203,11 +35358,11 @@ def f(xs): let get_iter_idx = ops .iter() - .position(|op| matches!(op, Instruction::GetIter)) + .position(|op| matches!(op, Instruction::GetIter { .. })) .expect("missing GET_ITER"); assert!( - matches!(ops[get_iter_idx - 1], Instruction::LoadFastBorrow { .. }), - "plain pass before for-tail should keep borrowed iterable load, got ops={ops:?}" + matches!(ops[get_iter_idx - 1], Instruction::LoadFast { .. }), + "plain pass before for-tail should keep an owned iterable load for GET_ITER, got ops={ops:?}" ); } @@ -35372,7 +35527,7 @@ def f(s, size, encodeSetO, encodeWhiteSpace): window, [ Instruction::PopTop, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, @@ -35865,7 +36020,7 @@ def f(cls, _FIELDS, _PARAMS): Instruction::NotTaken, Instruction::JumpBackward { .. } | Instruction::JumpBackwardNoInterrupt { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::StoreFast { .. }, ] ) @@ -35901,9 +36056,9 @@ def f(it): Instruction::PopJumpIfFalse { .. }, Instruction::NotTaken, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, Instruction::JumpBackward { .. } @@ -35938,9 +36093,9 @@ async def foo(): matches!( window, [ - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::GetAwaitable { .. }, ] @@ -35954,9 +36109,9 @@ async def foo(): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::GetAwaitable { .. }, ] @@ -36003,8 +36158,8 @@ async def foo(self): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -36039,8 +36194,8 @@ def f(): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -38176,7 +38331,7 @@ def f(self, document): } #[test] - fn from_import_after_conditional_store_join_uses_strong_prefix_loads() { + fn from_import_after_conditional_store_join_uses_borrowed_prefix_loads() { let code = compile_exec( "\ def f(x): @@ -38209,8 +38364,11 @@ def f(x): .expect("missing rpartition LOAD_ATTR"); assert!( - matches!(ops[rpartition_idx - 1].op, Instruction::LoadFast { .. }), - "CPython optimize_load_fast() keeps the conditional-store join receiver strong before IMPORT_FROM, got ops={ops:?}" + matches!( + ops[rpartition_idx - 1].op, + Instruction::LoadFastBorrow { .. } + ), + "CPython optimize_load_fast() borrows the conditional-store join receiver before IMPORT_FROM, got ops={ops:?}" ); } @@ -38345,9 +38503,25 @@ def f(output): warm_path .iter() .filter_map(load_fast_name) - .all(|name| name != "row" && name != "result"), - "terminal-except inlined comprehension warm path should keep CPython-style borrowed row/result loads, got warm_path={warm_path:?}" + .all(|name| name != "row"), + "terminal-except inlined comprehension warm path should keep CPython-style borrowed row loads, got warm_path={warm_path:?}" ); + assert_eq!( + warm_path + .iter() + .filter_map(load_fast_name) + .filter(|name| *name == "result") + .count(), + 1 + ); + let result_iterable = warm_path + .iter() + .position(|unit| load_fast_name(unit) == Some("result")) + .unwrap(); + assert!(matches!( + warm_path[result_iterable + 1].op, + Instruction::LoadFastAndClear { .. } + )); for name in ["row", "result"] { assert!( warm_path @@ -38642,7 +38816,7 @@ def f(flags, A, B, stop): Instruction::PopJumpIfTrue { .. }, Instruction::NotTaken, Instruction::JumpBackward { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -39629,7 +39803,7 @@ def f(self, rawdata, j, match): } #[test] - fn join_store_global_before_import_keeps_strong_load_fast() { + fn join_store_global_before_import_keeps_borrowed_load_fast() { let code = compile_exec( "\ def f(module=None): @@ -39653,12 +39827,12 @@ def f(module=None): matches!( window, [ - Instruction::LoadFast { .. }, + Instruction::LoadFastBorrow { .. }, Instruction::StoreGlobal { .. }, ] ) }), - "expected CPython-style strong LOAD_FAST before join STORE_GLOBAL followed by import, got ops={ops:?}" + "expected CPython-style LOAD_FAST_BORROW before join STORE_GLOBAL followed by import, got ops={ops:?}" ); } @@ -40106,7 +40280,7 @@ def f(self, b, BlockingIOError): .. }, CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, ] @@ -40153,9 +40327,9 @@ def f(self): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -40192,9 +40366,9 @@ def f(cm, source): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, Instruction::PopTop, ] @@ -40230,9 +40404,9 @@ def f(cm, xs, g): [ Instruction::EndFor, Instruction::PopIter, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40247,9 +40421,9 @@ def f(cm, xs, g): Instruction::EndFor, Instruction::PopIter, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40326,9 +40500,9 @@ def f(cm1, cm2, g, E): Instruction::Copy { .. }, Instruction::PopExcept, Instruction::Reraise { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -40343,9 +40517,9 @@ def f(cm1, cm2, g, E): Instruction::PopExcept, Instruction::Reraise { .. }, Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, ] ) }), @@ -40377,9 +40551,9 @@ def f(cm, dst): window, [ Instruction::Nop, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::Call { .. }, ] ) @@ -40501,7 +40675,7 @@ def f(self, cm, E): .collect(); let get_iter = instructions .iter() - .position(|unit| matches!(unit.op, Instruction::GetIter)) + .position(|unit| matches!(unit.op, Instruction::GetIter { .. })) .expect("missing loop iterator"); let handler_start = instructions .iter() @@ -40551,7 +40725,7 @@ def f(resources, valid_zones, TZPATH, os): .collect(); let get_iter = instructions .iter() - .position(|unit| matches!(unit.op, Instruction::GetIter)) + .position(|unit| matches!(unit.op, Instruction::GetIter { .. })) .expect("missing post-try loop iterator"); let tail = &instructions[get_iter.saturating_sub(1)..]; let load_fast_name = |unit: &&bytecode::CodeUnit| match unit.op { @@ -41676,7 +41850,7 @@ def f(events, callback, args, self, sig, signal): .. }, bytecode::CodeUnit { - op: Instruction::LoadConst { .. }, + op: Instruction::LoadCommonConstant { .. }, .. }, bytecode::CodeUnit { @@ -42771,7 +42945,7 @@ def f(pattern, prefix, get_prefix): .collect(); assert!( - ops.windows(7).any(|window| { + ops.windows(8).any(|window| { matches!( window, [ @@ -42779,8 +42953,9 @@ def f(pattern, prefix, get_prefix): Instruction::PopJumpIfTrue { .. }, Instruction::NotTaken, Instruction::PopTop, + Instruction::PopTop, Instruction::LoadFastBorrow { .. } | Instruction::LoadFast { .. }, - Instruction::LoadConst { .. }, + Instruction::LoadCommonConstant { .. }, Instruction::BuildTuple { .. }, ] ) @@ -43577,16 +43752,16 @@ def f(self, obj, expected, buf): .collect(); let get_iter = instructions .iter() - .position(|unit| matches!(unit.op, Instruction::GetIter)) + .position(|unit| matches!(unit.op, Instruction::GetIter { .. })) .expect("missing GET_ITER"); assert!( matches!( instructions .get(get_iter.saturating_sub(1)) .map(|unit| unit.op), - Some(Instruction::LoadFastBorrow { .. }) + Some(Instruction::LoadFast { .. }) ), - "finally-protected loop without except resume should keep borrowed iterable load, got instructions={instructions:?}" + "finally-protected loop without except resume should keep the owned iterable load for GET_ITER, got instructions={instructions:?}" ); for attr_name in ["close", "open"] { @@ -43922,14 +44097,30 @@ async def name_4(): else { panic!("missing GET_AITER in name_4"); }; - let prev = &name_4.instructions[get_aiter_pos - 1]; + assert!(matches!( + name_4.instructions[get_aiter_pos - 4].op, + Instruction::LoadFastAndClear { .. } + )); + assert!(matches!( + name_4.instructions[get_aiter_pos - 3].op, + Instruction::Swap { .. } + )); + assert!(matches!( + name_4.instructions[get_aiter_pos - 2].op, + Instruction::BuildMap { .. } + )); + assert!(matches!( + name_4.instructions[get_aiter_pos - 1].op, + Instruction::Swap { .. } + )); + let prev = &name_4.instructions[get_aiter_pos - 5]; assert!( matches!( prev.op, Instruction::LoadFastBorrow { var_num } if name_4.varnames[usize::from(var_num.get(OpArg::new(u32::from(u8::from(prev.arg)))))] == "name_5" ), - "expected async comprehension iterator capture to borrow name_5 before GET_AITER, got {prev:?}" + "expected async comprehension iterator capture to borrow name_5 before saved locals, result collection, and GET_AITER, got {prev:?}" ); } @@ -44141,4 +44332,300 @@ def f(_fwalk, stack, isbytes, topdown, onerror, follow_symlinks, close): &instructions[cleanup_pop.saturating_sub(12)..cleanup_pop] ); } + + fn cpython315_decoded_instructions(code: &CodeObject) -> Vec<(usize, Instruction, u32)> { + let mut state = bytecode::OpArgState::default(); + code.instructions + .iter() + .copied() + .enumerate() + .filter_map(|(offset, unit)| { + let (op, arg) = state.get(unit); + (!matches!(op, Instruction::Cache | Instruction::ExtendedArg)).then_some(( + offset, + op, + u32::from(arg), + )) + }) + .collect() + } + + #[test] + fn cpython315_genexpr_acquires_iterator_before_return_generator() { + for (source, is_async) in [ + ("def f(xs):\n return (x for x in xs)\n", false), + ("async def f(xs):\n return (x async for x in xs)\n", true), + ] { + let code = compile_exec(source); + let f = find_code(&code, "f").unwrap(); + let genexpr = find_code(f, "").unwrap(); + let instructions = cpython315_decoded_instructions(genexpr); + assert!(matches!(instructions[0].1, Instruction::Resume { .. })); + assert_eq!(instructions[0].2, 4); + assert!(matches!( + instructions[1].1, + Instruction::LoadFast { .. } | Instruction::LoadFastBorrow { .. } + )); + assert_eq!(instructions[1].2, 0); + assert!(if is_async { + matches!(instructions[2].1, Instruction::GetAiter) + } else { + matches!(instructions[2].1, Instruction::GetIter { .. }) + }); + assert_eq!(instructions[2].2, 0); + assert!(matches!(instructions[3].1, Instruction::ReturnGenerator)); + assert!(matches!(instructions[4].1, Instruction::PopTop)); + assert!(matches!(instructions[5].1, Instruction::Resume { .. })); + assert_eq!(instructions[5].2, 0); + assert_eq!( + instructions + .iter() + .filter(|(_, op, _)| matches!(op, Instruction::ReturnGenerator)) + .count(), + 1 + ); + assert!(!f.instructions.iter().any(|unit| matches!( + unit.op, + Instruction::GetIter { .. } | Instruction::GetAiter + ))); + let return_offset = u32::try_from(instructions[3].0).unwrap(); + assert!( + bytecode::decode_exception_table(&genexpr.exceptiontable) + .iter() + .all(|entry| return_offset < entry.start || return_offset >= entry.end) + ); + } + } + + #[test] + fn cpython315_await_and_yield_from_have_pairs_and_cached_resumes() { + for (source, is_await) in [ + ("def f(xs):\n yield from xs\n", false), + ("async def f(xs):\n return await xs\n", true), + ] { + let code = compile_exec(source); + let f = find_code(&code, "f").unwrap(); + let instructions = cpython315_decoded_instructions(f); + let acquire = instructions + .iter() + .position(|(_, op, _)| { + matches!( + op, + Instruction::GetIter { .. } | Instruction::GetAwaitable { .. } + ) + }) + .unwrap(); + if is_await { + assert!(matches!(instructions[acquire + 1].1, Instruction::PushNull)); + } else { + assert_eq!(instructions[acquire].2, 1); + } + let send = instructions + .iter() + .position(|(_, op, _)| matches!(op, Instruction::Send { .. })) + .unwrap(); + let (offset, op, arg) = instructions[send]; + assert!(matches!( + f.instructions[offset + 1 + op.cache_entries() + arg as usize].op, + Instruction::EndSend + )); + let resume = instructions[send + 2]; + assert!(matches!(resume.1, Instruction::Resume { .. })); + assert_eq!(resume.2 & 7, if is_await { 3 } else { 2 }); + assert_eq!(resume.1.cache_entries(), 1); + assert!(matches!( + f.instructions[resume.0 + 1].op, + Instruction::Cache + )); + } + } + + #[test] + fn cpython315_comprehension_collection_depth_counts_iterator_slots() { + for (source, depth) in [ + ( + "def f(xs, ys):\n return [x for x in xs for y in ys]\n", + 5, + ), + ( + "async def f(xs, ys):\n return [x async for x in xs for y in ys]\n", + 4, + ), + ] { + let code = compile_exec(source); + let instructions = cpython315_decoded_instructions(find_code(&code, "f").unwrap()); + let append = instructions + .iter() + .find(|(_, op, _)| matches!(op, Instruction::ListAppend { .. })) + .unwrap(); + assert_eq!(append.2, depth); + } + } + + #[test] + fn cpython315_ordinary_imports_pack_names_across_extended_arg_boundary() { + use core::fmt::Write; + let mut source = String::new(); + for i in 0..70 { + writeln!(source, "import module_{i}").unwrap(); + } + let code = compile_exec(&source); + let imports: Vec<_> = cpython315_decoded_instructions(&code) + .into_iter() + .filter(|(_, op, _)| matches!(op, Instruction::ImportName { .. })) + .collect(); + assert_eq!(imports.len(), 70); + for (i, (_, _, arg)) in imports.iter().enumerate() { + assert_eq!(*arg, u32::try_from(i).unwrap() << 2); + assert_eq!(code.names[(arg >> 2) as usize], format!("module_{i}")); + } + assert!( + code.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::ExtendedArg)) + ); + } + + #[test] + fn cpython315_ordinary_import_policy_matches_scope_and_handler_context() { + for (source, name, flags) in [ + ("import sample\n", "", &[0][..]), + ("from sample import member\n", "", &[0][..]), + ("from sample import *\n", "", &[2][..]), + ("def f():\n import sample\n", "f", &[2][..]), + ("class C:\n from sample import member\n", "C", &[2][..]), + ( + "try:\n import sample\nexcept Exception:\n pass\n", + "", + &[2][..], + ), + ( + "try:\n operation()\nexcept Exception:\n import sample\n", + "", + &[2][..], + ), + ( + "try:\n operation()\nfinally:\n import sample\n", + "", + &[0, 2][..], + ), + ("with manager:\n import sample\n", "", &[0][..]), + ] { + let code = compile_exec(source); + let scope = find_code(&code, name).unwrap(); + let instructions = cpython315_decoded_instructions(scope); + let imports: Vec<_> = instructions + .iter() + .filter(|(_, op, _)| matches!(op, Instruction::ImportName { .. })) + .collect(); + assert_eq!( + imports + .iter() + .map(|import| import.2 & 3) + .collect::>(), + flags, + "{source}" + ); + for import in imports { + assert_eq!(scope.names[(import.2 >> 2) as usize], "sample"); + } + } + } + + #[test] + fn cpython315_loop_return_cleanup_distinguishes_sync_and_async_iterators() { + for (source, slots) in [ + ("def f(xs):\n for x in xs:\n return x\n", 2), + ( + "async def f(xs):\n async for x in xs:\n return x\n", + 1, + ), + ] { + let code = compile_exec(source); + let instructions = cpython315_decoded_instructions(find_code(&code, "f").unwrap()); + let swap = instructions + .iter() + .position(|(_, op, arg)| { + matches!(op, Instruction::Swap { .. }) && *arg == slots + 1 + }) + .unwrap(); + for i in 1..=slots as usize { + assert!(matches!(instructions[swap + i].1, Instruction::PopTop)); + } + assert!(matches!( + instructions[swap + slots as usize + 1].1, + Instruction::ReturnValue + )); + } + } + + #[test] + fn cpython315_end_async_for_offsets_account_for_resume_cache() { + let code = compile_exec("async def f(xs):\n async for x in xs:\n pass\n"); + let f = find_code(&code, "f").unwrap(); + let instructions = cpython315_decoded_instructions(f); + let (offset, _, arg) = *instructions + .iter() + .find(|(_, op, _)| matches!(op, Instruction::EndAsyncFor)) + .unwrap(); + assert!(matches!( + f.instructions[offset + 1 - arg as usize].op, + Instruction::EndSend + )); + } + + #[test] + fn cpython315_list_set_async_genexpr_calls_keep_normal_call_path() { + for (name, constant) in [ + ("list", bytecode::CommonConstant::BuiltinList), + ("set", bytecode::CommonConstant::BuiltinSet), + ] { + let source = format!("async def f(xs):\n return {name}(x async for x in xs)\n"); + let code = compile_exec(&source); + let f = find_code(&code, "f").unwrap(); + assert!(!has_common_constant(f, constant)); + assert!( + !f.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::ForIter { .. })) + ); + assert!( + f.instructions + .iter() + .any(|unit| matches!(unit.op, Instruction::Call { .. })) + ); + } + } + + #[test] + fn cpython315_with_cleanup_excludes_synthetic_not_taken() { + let code = compile_exec("def f(c):\n with c:\n x = 1\n y = 2\n"); + let f = find_code(&code, "f").unwrap(); + let not_taken = u32::try_from( + f.instructions + .iter() + .position(|unit| matches!(unit.op, Instruction::NotTaken)) + .unwrap(), + ) + .unwrap(); + let entries = bytecode::decode_exception_table(&f.exceptiontable); + assert_eq!(entries.len(), 3); + assert!( + entries + .iter() + .all(|entry| not_taken < entry.start || not_taken >= entry.end) + ); + assert_eq!(entries[1].end, not_taken); + assert_eq!(entries[2].start, not_taken + 1); + assert_eq!(entries[1].target, entries[2].target); + assert_eq!(entries[1].depth, 4); + assert_eq!(entries[2].depth, 4); + assert!(entries[1].push_lasti && entries[2].push_lasti); + assert_eq!( + f.exceptiontable.as_ref(), + &[ + 0x8c, 0x03, 0x1b, 0x05, 0x9b, 0x08, 0x2e, 0x09, 0xa4, 0x02, 0x2e, 0x09 + ] + ); + } } diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 790846a40..9ee1c5dfe 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -455,7 +455,11 @@ impl InstructionInfo { /// flowgraph.c loads_const const fn loads_const(&self) -> bool { - self.instr.has_const() || matches!(self.instr.real_opcode(), Some(Opcode::LoadSmallInt)) + self.instr.has_const() + || matches!( + self.instr.real_opcode(), + Some(Opcode::LoadSmallInt | Opcode::LoadCommonConstant) + ) } /// flowgraph.c STORES_TO @@ -478,6 +482,26 @@ impl InstructionInfo { false } + /// flowgraph.c maybe_instr_make_load_common_const + fn maybe_instr_make_load_common_const(&mut self, constant: &ConstantData) -> bool { + use oparg::CommonConstant; + let idx = match constant { + ConstantData::None => CommonConstant::None, + ConstantData::Boolean { value: true } => CommonConstant::True, + ConstantData::Boolean { value: false } => CommonConstant::False, + ConstantData::Str { value } if value.is_empty() => CommonConstant::EmptyStr, + ConstantData::Integer { value } if value.to_i32() == Some(-1) => { + CommonConstant::MinusOne + } + _ => return false, + }; + self.instr_set_op1( + Opcode::LoadCommonConstant.into(), + OpArg::new(u32::from(idx)), + ); + true + } + /// flowgraph.c make_super_instruction fn make_super_instruction(inst1: &mut Self, inst2: &mut Self, super_op: AnyInstruction) { let line1 = inst1.instruction_lineno(); @@ -1084,7 +1108,7 @@ fn resolve_unconditional_jumps(instr_sequence: &mut InstructionSequence) { /// assemble.c resolve_jump_offsets fn resolve_jump_offsets(instr_sequence: &mut InstructionSequence) { // The offset (in code units) of END_SEND from SEND in the yield-from sequence. - const END_SEND_OFFSET: i32 = 5; + const END_SEND_OFFSET: i32 = 6; for i in 0..instr_sequence.instr_used { let instr = &mut instr_sequence.instrs[i]; let opcode = instr.info.instr.expect_real(); @@ -2227,7 +2251,7 @@ impl Blocks { AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => { match func.get(inst.arg) { IntrinsicFunction1::ListToTuple => { - if matches!(nextop, Some(Instruction::GetIter)) { + if matches!(nextop, Some(Instruction::GetIter { .. })) { self[block_idx].instructions[i].set_to_nop(); } else { fold_constant_intrinsic_list_to_tuple( @@ -2410,6 +2434,7 @@ impl Blocks { Instruction::FormatSimple | Instruction::GetAnext | Instruction::GetLen + | Instruction::GetIter { .. } | Instruction::GetYieldFromIter | Instruction::ImportFrom { .. } | Instruction::MatchKeys @@ -2420,10 +2445,8 @@ impl Blocks { let effect = instr.stack_effect_info(arg_u32); let net_pushed = effect.pushed() as isize - effect.popped() as isize; debug_assert!(net_pushed >= 0); - // CPython optimize_load_fast() shadows the outer - // instruction index in this produced-value loop. - for produced in 0..net_pushed { - push_ref(&mut refs, produced, NOT_LOCAL)?; + for _ in 0..net_pushed { + push_ref(&mut refs, i as isize, NOT_LOCAL)?; } } AnyInstruction::Real( @@ -2447,10 +2470,19 @@ impl Blocks { Instruction::EndSend | Instruction::SetFunctionAttribute { .. }, ) => { let effect = instr.stack_effect_info(arg_u32); - debug_assert_eq!(effect.popped(), 2); + debug_assert_eq!( + effect.popped(), + if matches!(instr.real(), Some(Instruction::EndSend)) { + 3 + } else { + 2 + } + ); debug_assert_eq!(effect.pushed(), 1); let tos = ref_stack_pop(&mut refs); - let _ = ref_stack_pop(&mut refs); + for _ in 1..effect.popped() { + let _ = ref_stack_pop(&mut refs); + } push_ref(&mut refs, tos.instr, tos.local)?; } AnyInstruction::Real(Instruction::CheckExcMatch) => { @@ -2634,8 +2666,14 @@ impl Blocks { let opcode = instr_info.instr.real_opcode(); let is_redundant_pair = matches!(opcode, Some(Opcode::PopTop)) - && (matches!(prev_opcode, Some(Opcode::LoadConst | Opcode::LoadSmallInt)) - || (prev_oparg == 1 && matches!(prev_opcode, Some(Opcode::Copy)))); + && (matches!( + prev_opcode, + Some( + Opcode::LoadConst + | Opcode::LoadSmallInt + | Opcode::LoadCommonConstant + ) + ) || (prev_oparg == 1 && matches!(prev_opcode, Some(Opcode::Copy)))); if is_redundant_pair { let (prev_block, prev_instr_idx) = @@ -3452,6 +3490,8 @@ impl Blocks { debug_assert!(dest <= instr_count); let num_removed = instr_count - dest; self[block_idx].instruction_used = dest; + // Later instructions reuse these slots and must not inherit a removed handler. + self[block_idx].instructions[dest..instr_count].fill(InstructionInfo::empty()); num_removed } @@ -4009,6 +4049,35 @@ impl CodeInfo { ) } + pub(crate) fn insert_genexpr_prefix( + &mut self, + mut iterator: InstructionInfo, + ) -> crate::InternalResult<()> { + let mut resume = InstructionInfo::empty(); + resume.instr_set_op1( + Opcode::Resume.into(), + OpArg::new(oparg::ResumeLocation::AtGenExprStart.into()), + ); + resume.lineno_override = Some(NO_LOCATION_OVERRIDE); + let mut load = iterator; + load.instr_set_op1(Opcode::LoadFast.into(), OpArg::new(0)); + iterator.arg = OpArg::new(0); + let mut return_generator = InstructionInfo::empty(); + return_generator.instr_set_op0(Instruction::ReturnGenerator.into()); + return_generator.location.line = self.metadata.firstlineno; + return_generator.end_location.line = self.metadata.firstlineno; + return_generator.lineno_override = Some(LINE_ONLY_LOCATION_OVERRIDE); + let mut pop = return_generator; + pop.instr_set_op0(Instruction::PopTop.into()); + for (i, info) in [resume, load, iterator, return_generator, pop] + .into_iter() + .enumerate() + { + instruction_sequence_insert_instruction(&mut self.instr_sequence, i, info)?; + } + Ok(()) + } + pub(crate) fn push_unmapped_instr_sequence_label(&mut self) -> crate::InternalResult<()> { instruction_sequence_label_map_push_unmapped_label( &mut self.instr_sequence_label_map, @@ -4344,7 +4413,12 @@ fn insert_prefix_instructions( let firstlineno = metadata.firstlineno; debug_assert!(firstlineno.get() > 0); - if is_generator(flags) { + if is_generator(flags) + && !entry + .used_instructions() + .iter() + .any(|info| matches!(info.instr.real(), Some(Instruction::ReturnGenerator))) + { let location = SourceLocation { line: firstlineno, character_offset: OneIndexed::MIN, @@ -4517,6 +4591,13 @@ fn load_const_truthiness( Some(constant.truthiness()) } Instruction::LoadSmallInt { i } => Some(i.get(arg) != 0), + Instruction::LoadCommonConstant { idx } => { + use oparg::CommonConstant; + Some(!matches!( + idx.get(arg), + CommonConstant::None | CommonConstant::False | CommonConstant::EmptyStr + )) + } _ => None, } } @@ -4534,7 +4615,9 @@ fn instr_make_load_const( instr: &mut InstructionInfo, constant: ConstantData, ) -> crate::InternalResult<()> { - if instr.maybe_instr_make_load_smallint(&constant) { + if instr.maybe_instr_make_load_smallint(&constant) + || instr.maybe_instr_make_load_common_const(&constant) + { return Ok(()); } @@ -4627,6 +4710,21 @@ fn fold_const_binop( /// flowgraph.c get_const_value fn get_const_value(metadata: &CodeUnitMetadata, info: &InstructionInfo) -> Option { match info.instr.real_opcode() { + Some(Opcode::LoadCommonConstant) => { + use oparg::CommonConstant; + match CommonConstant::try_from(u32::from(info.arg)).ok()? { + CommonConstant::None => Some(ConstantData::None), + CommonConstant::EmptyStr => Some(ConstantData::Str { + value: Wtf8Buf::new(), + }), + CommonConstant::True => Some(ConstantData::Boolean { value: true }), + CommonConstant::False => Some(ConstantData::Boolean { value: false }), + CommonConstant::MinusOne => Some(ConstantData::Integer { + value: BigInt::from(-1), + }), + _ => None, + } + } Some(Opcode::LoadSmallInt) => { let v = u32::from(info.arg) as i32; Some(ConstantData::Integer { @@ -5449,7 +5547,7 @@ fn optimize_lists_and_sets( let contains_or_iter = matches!( nextop, - Some(Instruction::GetIter | Instruction::ContainsOp { .. }) + Some(Instruction::GetIter { .. } | Instruction::ContainsOp { .. }) ); let seq_size = u32::from(block.instructions[i].arg) as usize; if seq_size > STACK_USE_GUIDELINE || (seq_size < MIN_CONST_SEQUENCE_SIZE && !contains_or_iter) { @@ -5539,84 +5637,88 @@ fn basicblock_optimize_load_const( metadata: &mut CodeUnitMetadata, block: &mut Block, ) -> crate::InternalResult<()> { - let mut i = 0; let mut effective_opcode = Instruction::Nop.into(); let mut effective_oparg = OpArg::new(0); - while i < block.instruction_used { - if matches!( - block.instructions[i].instr.real(), - Some(Instruction::LoadConst { .. }) - ) && let Some(constant) = get_const_value(metadata, &block.instructions[i]) - { - block.instructions[i].maybe_instr_make_load_smallint(&constant); - } - - 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; - - if i + 1 >= block.instruction_used { - i += 1; - continue; - } - - 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(); - } - i += 1; - continue; - } - } - - // The remaining combinations require both instructions to be real. - let Some(next_instr) = next.instr.real() else { - i += 1; - continue; - }; - - 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 + 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 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 { + break 'instruction; + } + + 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(); + } + break 'instruction; + } + } + + // The remaining combinations require both instructions to be real. + let Some(next_instr) = next.instr.real() else { + break 'instruction; + }; + + let is_none = match const_instr { + Instruction::LoadConst { consti } => matches!( + &metadata.consts[consti.get(const_arg).as_usize()], + ConstantData::None + ), + Instruction::LoadCommonConstant { idx } => { + idx.get(const_arg) == oparg::CommonConstant::None + } + _ => false, + }; + if is_none && let Instruction::IsOp { invert } = next_instr { let mut jump_idx = i + 2; if jump_idx >= block.instruction_used { - i += 1; - continue; + break 'instruction; } if matches!( @@ -5626,14 +5728,12 @@ fn basicblock_optimize_load_const( block.instructions[jump_idx].set_to_nop(); jump_idx += 1; if jump_idx >= block.instruction_used { - i += 1; - continue; + break 'instruction; } } let Some(jump_instr) = block.instructions[jump_idx].instr.real() else { - i += 1; - continue; + break 'instruction; }; let mut invert = matches!( @@ -5646,8 +5746,7 @@ fn basicblock_optimize_load_const( } Instruction::PopJumpIfTrue { .. } => {} _ => { - i += 1; - continue; + break 'instruction; } }; @@ -5659,27 +5758,32 @@ fn basicblock_optimize_load_const( Opcode::PopJumpIfNone } .into(); - i += 1; - continue; + break 'instruction; + } + + if matches!( + const_instr, + Instruction::LoadConst { .. } + | Instruction::LoadSmallInt { .. } + | Instruction::LoadCommonConstant { .. } + ) && matches!(next_instr, Instruction::ToBool) + && let Some(value) = load_const_truthiness(const_instr, const_arg, metadata) + { + let const_idx = add_const(metadata, ConstantData::Boolean { value })?; + block.instructions[i].set_to_nop(); + + block.instructions[i + 1] + .instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32)); + break 'instruction; } } - if matches!( - const_instr, - Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. } - ) && matches!(next_instr, Instruction::ToBool) - && let Some(value) = load_const_truthiness(const_instr, const_arg, metadata) + block.instructions[i].instr.real_opcode(), + Some(Opcode::LoadConst) + ) && let Some(constant) = get_const_value(metadata, &block.instructions[i]) { - let const_idx = add_const(metadata, ConstantData::Boolean { value })?; - block.instructions[i].set_to_nop(); - - block.instructions[i + 1] - .instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32)); - i += 1; - continue; + block.instructions[i].maybe_instr_make_load_common_const(&constant); } - - i += 1; } Ok(()) } @@ -6968,7 +7072,9 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes } else if let Some(Instruction::Resume { context: _ }) = instr.real() { blocks[bi].instructions[i].except_handler = handler; let resume_arg = u32::from(arg); - if resume_arg != u32::from(oparg::ResumeLocation::AtFuncStart) { + if resume_arg != u32::from(oparg::ResumeLocation::AtFuncStart) + && resume_arg != u32::from(oparg::ResumeLocation::AtGenExprStart) + { debug_assert!(last_yield_except_depth >= 0); if last_yield_except_depth == 1 { blocks[bi].instructions[i].arg = @@ -6976,6 +7082,8 @@ pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalRes } last_yield_except_depth = -1; } + } else if matches!(instr.real(), Some(Instruction::ReturnGenerator)) { + blocks[bi].instructions[i].except_handler = None; } else { blocks[bi].instructions[i].except_handler = handler; } @@ -7726,7 +7834,7 @@ mod tests { // CPython `basicblock_optimize_load_const()` keeps the previous // LOAD_CONST as the effective opcode for a following `COPY 1`, so the - // COPY is NOPed and TO_BOOL becomes LOAD_CONST True. + // COPY is NOPed and TO_BOOL becomes LOAD_COMMON_CONSTANT True. assert!(matches!( code.blocks[0].instructions[0].instr.real(), Some(Instruction::LoadConst { .. }) @@ -7738,11 +7846,11 @@ mod tests { let load_bool = &code.blocks[0].instructions[2]; assert!(matches!( load_bool.instr.real(), - Some(Instruction::LoadConst { .. }) + Some(Instruction::LoadCommonConstant { .. }) )); assert_eq!( - code.metadata.consts[u32::from(load_bool.arg) as usize], - ConstantData::Boolean { value: true } + u32::from(load_bool.arg), + u32::from(oparg::CommonConstant::True) ); } @@ -7793,7 +7901,7 @@ mod tests { } #[test] - fn optimize_load_fast_records_no_input_opcode_ref_at_cpython_produced_index() { + fn optimize_load_fast_records_no_input_opcode_ref_at_cpython_instruction_index() { let mut block = Block::default(); test_block_push(&mut block, test_instr(Opcode::LoadFast.into(), 10)); test_block_push(&mut block, test_instr(Instruction::GetLen, 10)); @@ -7807,13 +7915,11 @@ mod tests { .optimize_load_fast() .expect("optimize_load_fast succeeds"); - // CPython `optimize_load_fast()` shadows the outer instruction index in - // the produced-value loop for GET_LEN, so the produced ref is recorded - // with index 0 here. The original LOAD_FAST is therefore not considered - // the consumed producer. + // CPython records GET_LEN's result at instruction index 1. The + // SWAP/POP_TOP consumes the original local reference, so it can borrow. assert!(matches!( code.blocks[0].instructions[0].instr.real(), - Some(Instruction::LoadFast { .. }) + Some(Instruction::LoadFastBorrow { .. }) )); } @@ -8079,4 +8185,128 @@ mod tests { assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(2)); } + + #[test] + fn cpython315_common_constants_lower_and_round_trip_without_numeric_aliasing() { + use oparg::CommonConstant; + for (constant, common) in [ + (ConstantData::None, CommonConstant::None), + ( + ConstantData::Str { + value: Wtf8Buf::new(), + }, + CommonConstant::EmptyStr, + ), + (ConstantData::Boolean { value: true }, CommonConstant::True), + ( + ConstantData::Boolean { value: false }, + CommonConstant::False, + ), + (int_const(-1), CommonConstant::MinusOne), + ] { + let mut metadata = test_code_info(Block::default()).metadata; + metadata.consts.insert_full(constant.clone()); + let mut block = Block::default(); + test_block_push(&mut block, test_instr(Opcode::LoadConst.into(), 1)); + basicblock_optimize_load_const(&mut metadata, &mut block).unwrap(); + let info = block.instructions[0]; + assert_eq!(info.instr.real_opcode(), Some(Opcode::LoadCommonConstant)); + assert_eq!(u32::from(info.arg), u32::from(common)); + assert_eq!(get_const_value(&metadata, &info), Some(constant.clone())); + assert_eq!( + load_const_truthiness(info.instr.expect_real(), info.arg, &metadata), + Some(constant.truthiness()) + ); + } + for constant in [ + int_const(0), + int_const(1), + ConstantData::Float { value: -1.0 }, + ] { + assert!( + !test_instr(Opcode::LoadConst.into(), 1) + .maybe_instr_make_load_common_const(&constant) + ); + } + } + + #[test] + fn cpython315_get_iter_tracks_both_stack_slots_for_borrow_analysis() { + let mut block = Block::default(); + for (op, arg) in [ + (Opcode::LoadFast, 0), + (Opcode::GetIter, 0), + (Opcode::Swap, 2), + (Opcode::PopTop, 0), + (Opcode::PopTop, 0), + ] { + let mut info = test_instr(op.into(), 1); + info.arg = OpArg::new(arg); + test_block_push(&mut block, info); + } + let mut code = test_code_info(block); + code.blocks.optimize_load_fast().unwrap(); + assert_eq!( + code.blocks[0].instructions[0].instr.real_opcode(), + Some(Opcode::LoadFastBorrow) + ); + } + + #[test] + fn cpython315_end_send_preserves_result_borrow_and_consumes_iterator_pair() { + let mut block = Block::default(); + for (op, arg) in [ + ( + Opcode::LoadCommonConstant, + u32::from(oparg::CommonConstant::None), + ), + (Opcode::PushNull, 0), + (Opcode::LoadFast, 0), + (Opcode::EndSend, 0), + (Opcode::PopTop, 0), + ] { + let mut info = test_instr(op.into(), 1); + info.arg = OpArg::new(arg); + test_block_push(&mut block, info); + } + let mut code = test_code_info(block); + assert_eq!(code.blocks.calculate_stackdepth().unwrap(), 3); + code.blocks.optimize_load_fast().unwrap(); + assert_eq!( + code.blocks[0].instructions[2].instr.real_opcode(), + Some(Opcode::LoadFastBorrow) + ); + } + + #[test] + fn cpython315_redundant_nop_compaction_clears_vacated_metadata() { + let handler = ExceptHandlerInfo { + handler_block: BlockIdx::new(2), + preserve_lasti: true, + }; + let mut block = Block::default(); + for _ in 0..2 { + let mut nop = test_instr(Instruction::Nop, 10); + nop.lineno_override = Some(NO_LOCATION_OVERRIDE); + nop.target = BlockIdx::new(1); + nop.except_handler = Some(handler); + test_block_push(&mut block, nop); + } + let mut jump = test_instr(Opcode::PopJumpIfTrue.into(), 10); + jump.target = BlockIdx::new(1); + jump.except_handler = Some(handler); + test_block_push(&mut block, jump); + let mut blocks = Blocks::from([block, Block::default(), Block::default()]); + assert_eq!(blocks.basicblock_remove_redundant_nops(BlockIdx::new(0)), 2); + assert_eq!(blocks[0].instructions[0].target, jump.target); + assert_eq!(blocks[0].instructions[0].except_handler, Some(handler)); + for slot in &blocks[0].instructions[1..3] { + assert_eq!(slot.target, BlockIdx::NULL); + assert_eq!(slot.except_handler, None); + } + blocks.normalize_jumps_in_block(BlockIdx::new(0)).unwrap(); + let not_taken = &blocks[0].instructions[1]; + assert_eq!(not_taken.instr.real_opcode(), Some(Opcode::NotTaken)); + assert_eq!(not_taken.except_handler, None); + } } diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index f7d117cb7..c5f396718 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -734,6 +734,26 @@ impl Deref for CodeUnits { } impl CodeUnits { + /// Apply final 3.15 GET_ITER mode fixups before code publication. + /// Python/specialize.c:fixup_getiter normalizes every nonzero mode. + pub fn normalize_get_iter(&mut self, flags: CodeFlags) { + let units = self.units.get_mut(); + let mut index = 0; + while index < units.len() { + let unit = &mut units[index]; + if matches!(unit.op, Instruction::GetIter { .. }) && unit.arg != 0.into() { + let mode = if flags.intersects(CodeFlags::COROUTINE | CodeFlags::ITERABLE_COROUTINE) + { + 2 + } else { + 3 + }; + unit.arg = mode.into(); + } + index += 1 + unit.op.cache_entries(); + } + } + /// Disable adaptive specialization by setting all counters to unreachable. /// Used for CPython-compiled bytecode where specialization may not be safe. pub fn disable_specialization(&self) { diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index f403507f1..0f680d7d1 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -333,261 +333,261 @@ define_opcodes!( FormatWithSpec = 13, GetAiter = 14, GetAnext = 15, - GetIter = 16, + GetIter { mode: Arg } = 70, Reserved = 17, - GetLen = 18, - GetYieldFromIter = 19, - InterpreterExit = 20, - LoadBuildClass = 21, - LoadLocals = 22, - MakeFunction = 23, - MatchKeys = 24, - MatchMapping = 25, - MatchSequence = 26, - Nop = 27, - NotTaken = 28, - PopExcept = 29, - PopIter = 30, - PopTop = 31, - PushExcInfo = 32, - PushNull = 33, - ReturnGenerator = 34, - ReturnValue = 35, - SetupAnnotations = 36, - StoreSlice = 37, - StoreSubscr = 38, - ToBool = 39, - UnaryInvert = 40, - UnaryNegative = 41, - UnaryNot = 42, - WithExceptStart = 43, + GetLen = 16, + GetYieldFromIter = 219, + InterpreterExit = 18, + LoadBuildClass = 19, + LoadLocals = 20, + MakeFunction = 21, + MatchKeys = 22, + MatchMapping = 23, + MatchSequence = 24, + Nop = 25, + NotTaken = 26, + PopExcept = 27, + PopIter = 28, + PopTop = 29, + PushExcInfo = 30, + PushNull = 31, + ReturnGenerator = 32, + ReturnValue = 33, + SetupAnnotations = 34, + StoreSlice = 35, + StoreSubscr = 36, + ToBool = 37, + UnaryInvert = 38, + UnaryNegative = 39, + UnaryNot = 40, + WithExceptStart = 41, BinaryOp { op: Arg, - } = 44, + } = 42, BuildInterpolation { format: Arg, - } = 45, + } = 43, BuildList { count: Arg, - } = 46, + } = 44, BuildMap { count: Arg, - } = 47, + } = 45, BuildSet { count: Arg, - } = 48, + } = 46, BuildSlice { argc: Arg, - } = 49, + } = 47, BuildString { count: Arg, - } = 50, + } = 48, BuildTuple { count: Arg, - } = 51, + } = 49, Call { argc: Arg, - } = 52, + } = 50, CallIntrinsic1 { func: Arg, - } = 53, + } = 51, CallIntrinsic2 { func: Arg, - } = 54, + } = 52, CallKw { argc: Arg, - } = 55, + } = 53, CompareOp { opname: Arg, - } = 56, + } = 54, ContainsOp { invert: Arg, - } = 57, + } = 55, ConvertValue { oparg: Arg, - } = 58, + } = 56, Copy { i: Arg, - } = 59, + } = 57, CopyFreeVars { n: Arg, - } = 60, + } = 58, DeleteAttr { namei: Arg, - } = 61, + } = 59, DeleteDeref { i: Arg, - } = 62, + } = 60, DeleteFast { var_num: Arg, - } = 63, + } = 61, DeleteGlobal { namei: Arg, - } = 64, + } = 62, DeleteName { namei: Arg, - } = 65, + } = 63, DictMerge { i: Arg, - } = 66, + } = 64, DictUpdate { i: Arg, - } = 67, - EndAsyncFor = 68, - ExtendedArg = 69, + } = 65, + EndAsyncFor = 66, + ExtendedArg = 67, ForIter { delta: Arg, - } = 70, + } = 68, GetAwaitable { r#where: Arg, - } = 71, + } = 69, ImportFrom { namei: Arg, - } = 72, + } = 71, ImportName { namei: Arg, - } = 73, + } = 72, IsOp { invert: Arg, - } = 74, + } = 73, JumpBackward { delta: Arg, - } = 75, + } = 74, JumpBackwardNoInterrupt { delta: Arg, - } = 76, + } = 75, JumpForward { delta: Arg, - } = 77, + } = 76, ListAppend { i: Arg, - } = 78, + } = 77, ListExtend { i: Arg, - } = 79, + } = 78, LoadAttr { namei: Arg, - } = 80, + } = 79, LoadCommonConstant { idx: Arg, - } = 81, + } = 80, LoadConst { consti: Arg, - } = 82, + } = 81, LoadDeref { i: Arg, - } = 83, + } = 82, LoadFast { var_num: Arg, - } = 84, + } = 83, LoadFastAndClear { var_num: Arg, - } = 85, + } = 84, LoadFastBorrow { var_num: Arg, - } = 86, + } = 85, LoadFastBorrowLoadFastBorrow { var_nums: Arg, - } = 87, + } = 86, LoadFastCheck { var_num: Arg, - } = 88, + } = 87, LoadFastLoadFast { var_nums: Arg, - } = 89, + } = 88, LoadFromDictOrDeref { i: Arg, - } = 90, + } = 89, LoadFromDictOrGlobals { i: Arg, - } = 91, + } = 90, LoadGlobal { namei: Arg, - } = 92, + } = 91, LoadName { namei: Arg, - } = 93, + } = 92, LoadSmallInt { i: Arg, - } = 94, + } = 93, LoadSpecial { method: Arg, - } = 95, + } = 94, LoadSuperAttr { namei: Arg, - } = 96, + } = 95, MakeCell { i: Arg, - } = 97, + } = 96, MapAdd { i: Arg, - } = 98, + } = 97, MatchClass { count: Arg, - } = 99, + } = 98, PopJumpIfFalse { delta: Arg, - } = 100, + } = 99, PopJumpIfNone { delta: Arg, - } = 101, + } = 100, PopJumpIfNotNone { delta: Arg, - } = 102, + } = 101, PopJumpIfTrue { delta: Arg, - } = 103, + } = 102, RaiseVarargs { argc: Arg, - } = 104, + } = 103, Reraise { depth: Arg, - } = 105, + } = 104, Send { delta: Arg, - } = 106, + } = 105, SetAdd { i: Arg, - } = 107, + } = 106, SetFunctionAttribute { flag: Arg, - } = 108, + } = 107, SetUpdate { i: Arg, - } = 109, + } = 108, StoreAttr { namei: Arg, - } = 110, + } = 109, StoreDeref { i: Arg, - } = 111, + } = 110, StoreFast { var_num: Arg, - } = 112, + } = 111, StoreFastLoadFast { var_nums: Arg, - } = 113, + } = 112, StoreFastStoreFast { var_nums: Arg, - } = 114, + } = 113, StoreGlobal { namei: Arg, - } = 115, + } = 114, StoreName { namei: Arg, - } = 116, + } = 115, Swap { i: Arg, - } = 117, + } = 116, UnpackEx { counts: Arg, - } = 118, + } = 117, UnpackSequence { count: Arg, - } = 119, + } = 118, YieldValue { arg: Arg, - } = 120, + } = 119, Resume { context: Arg, } = 128, @@ -603,99 +603,99 @@ define_opcodes!( BinaryOpSubscrListSlice = 138, BinaryOpSubscrStrInt = 139, BinaryOpSubscrTupleInt = 140, - BinaryOpSubtractFloat = 141, - BinaryOpSubtractInt = 142, - CallAllocAndEnterInit = 143, - CallBoundMethodExactArgs = 144, - CallBoundMethodGeneral = 145, - CallBuiltinClass = 146, - CallBuiltinFast = 147, - CallBuiltinFastWithKeywords = 148, - CallBuiltinO = 149, - CallIsinstance = 150, - CallKwBoundMethod = 151, - CallKwNonPy = 152, - CallKwPy = 153, - CallLen = 154, - CallListAppend = 155, - CallMethodDescriptorFast = 156, - CallMethodDescriptorFastWithKeywords = 157, - CallMethodDescriptorNoargs = 158, - CallMethodDescriptorO = 159, - CallNonPyGeneral = 160, - CallPyExactArgs = 161, - CallPyGeneral = 162, - CallStr1 = 163, - CallTuple1 = 164, - CallType1 = 165, - CompareOpFloat = 166, - CompareOpInt = 167, - CompareOpStr = 168, - ContainsOpDict = 169, - ContainsOpSet = 170, - ForIterGen = 171, - ForIterList = 172, - ForIterRange = 173, - ForIterTuple = 174, - JumpBackwardJit = 175, - JumpBackwardNoJit = 176, - LoadAttrClass = 177, - LoadAttrClassWithMetaclassCheck = 178, - LoadAttrGetattributeOverridden = 179, - LoadAttrInstanceValue = 180, - LoadAttrMethodLazyDict = 181, - LoadAttrMethodNoDict = 182, - LoadAttrMethodWithValues = 183, - LoadAttrModule = 184, - LoadAttrNondescriptorNoDict = 185, - LoadAttrNondescriptorWithValues = 186, - LoadAttrProperty = 187, - LoadAttrSlot = 188, - LoadAttrWithHint = 189, - LoadConstImmortal = 190, - LoadConstMortal = 191, - LoadGlobalBuiltin = 192, - LoadGlobalModule = 193, - LoadSuperAttrAttr = 194, - LoadSuperAttrMethod = 195, - ResumeCheck = 196, - SendGen = 197, - StoreAttrInstanceValue = 198, - StoreAttrSlot = 199, - StoreAttrWithHint = 200, - StoreSubscrDict = 201, - StoreSubscrListInt = 202, - ToBoolAlwaysTrue = 203, - ToBoolBool = 204, - ToBoolInt = 205, - ToBoolList = 206, - ToBoolNone = 207, - ToBoolStr = 208, - UnpackSequenceList = 209, - UnpackSequenceTuple = 210, - UnpackSequenceTwoTuple = 211, - InstrumentedEndFor = 234, - InstrumentedPopIter = 235, - InstrumentedEndSend = 236, - InstrumentedForIter = 237, - InstrumentedInstruction = 238, - InstrumentedJumpForward = 239, - InstrumentedNotTaken = 240, - InstrumentedPopJumpIfTrue = 241, - InstrumentedPopJumpIfFalse = 242, - InstrumentedPopJumpIfNone = 243, - InstrumentedPopJumpIfNotNone = 244, - InstrumentedResume = 245, - InstrumentedReturnValue = 246, - InstrumentedYieldValue = 247, - InstrumentedEndAsyncFor = 248, - InstrumentedLoadSuperAttr = 249, - InstrumentedCall = 250, - InstrumentedCallKw = 251, - InstrumentedCallFunctionEx = 252, - InstrumentedJumpBackward = 253, - InstrumentedLine = 254, - EnterExecutor = 255, + BinaryOpSubtractFloat = 142, + BinaryOpSubtractInt = 143, + CallAllocAndEnterInit = 144, + CallBoundMethodExactArgs = 145, + CallBoundMethodGeneral = 146, + CallBuiltinClass = 147, + CallBuiltinFast = 148, + CallBuiltinFastWithKeywords = 149, + CallBuiltinO = 150, + CallIsinstance = 153, + CallKwBoundMethod = 154, + CallKwNonPy = 155, + CallKwPy = 156, + CallLen = 157, + CallListAppend = 158, + CallMethodDescriptorFast = 159, + CallMethodDescriptorFastWithKeywords = 160, + CallMethodDescriptorNoargs = 161, + CallMethodDescriptorO = 162, + CallNonPyGeneral = 163, + CallPyExactArgs = 164, + CallPyGeneral = 165, + CallStr1 = 166, + CallTuple1 = 167, + CallType1 = 168, + CompareOpFloat = 169, + CompareOpInt = 170, + CompareOpStr = 171, + ContainsOpDict = 172, + ContainsOpSet = 173, + ForIterGen = 174, + ForIterList = 175, + ForIterRange = 176, + ForIterTuple = 177, + JumpBackwardJit = 181, + JumpBackwardNoJit = 182, + LoadAttrClass = 183, + LoadAttrClassWithMetaclassCheck = 184, + LoadAttrGetattributeOverridden = 185, + LoadAttrInstanceValue = 186, + LoadAttrMethodLazyDict = 187, + LoadAttrMethodNoDict = 188, + LoadAttrMethodWithValues = 189, + LoadAttrModule = 190, + LoadAttrNondescriptorNoDict = 191, + LoadAttrNondescriptorWithValues = 192, + LoadAttrProperty = 193, + LoadAttrSlot = 194, + LoadAttrWithHint = 195, + LoadConstImmortal = 220, + LoadConstMortal = 221, + LoadGlobalBuiltin = 196, + LoadGlobalModule = 197, + LoadSuperAttrAttr = 198, + LoadSuperAttrMethod = 199, + ResumeCheck = 200, + SendGen = 203, + StoreAttrInstanceValue = 205, + StoreAttrSlot = 206, + StoreAttrWithHint = 207, + StoreSubscrDict = 208, + StoreSubscrListInt = 209, + ToBoolAlwaysTrue = 210, + ToBoolBool = 211, + ToBoolInt = 212, + ToBoolList = 213, + ToBoolNone = 214, + ToBoolStr = 215, + UnpackSequenceList = 216, + UnpackSequenceTuple = 217, + UnpackSequenceTwoTuple = 218, + InstrumentedEndFor = 233, + InstrumentedPopIter = 234, + InstrumentedEndSend = 235, + InstrumentedForIter = 236, + InstrumentedInstruction = 237, + InstrumentedJumpForward = 238, + InstrumentedNotTaken = 239, + InstrumentedPopJumpIfTrue = 240, + InstrumentedPopJumpIfFalse = 241, + InstrumentedPopJumpIfNone = 242, + InstrumentedPopJumpIfNotNone = 243, + InstrumentedResume = 244, + InstrumentedReturnValue = 245, + InstrumentedYieldValue = 246, + InstrumentedEndAsyncFor = 247, + InstrumentedLoadSuperAttr = 248, + InstrumentedCall = 249, + InstrumentedCallKw = 250, + InstrumentedCallFunctionEx = 251, + InstrumentedJumpBackward = 252, + InstrumentedLine = 253, + EnterExecutor = 254, } ); @@ -1342,6 +1342,7 @@ const _: () = assert!(core::mem::size_of::() == 2); #[cfg(test)] mod tests { use super::*; + use alloc::vec; #[test] fn eval_break_flags_match_cpython_jump_metadata() { @@ -1436,10 +1437,71 @@ mod tests { assert!(!AnyInstruction::from(PseudoOpcode::Jump).has_const()); } + include!("target_stack_cases.rs"); + + #[test] + fn get_iter_modes_normalize_before_publication() { + use crate::bytecode::{CodeFlags, CodeUnit, CodeUnits}; + for flags in [ + CodeFlags::empty(), + CodeFlags::COROUTINE, + CodeFlags::ITERABLE_COROUTINE, + ] { + for input in 0..=3u8 { + let mut units = CodeUnits::from(vec![ + CodeUnit::new(Opcode::GetIter.as_instruction(), input.into()), + CodeUnit::new(Opcode::Cache.as_instruction(), 0.into()), + CodeUnit::new(Opcode::ReturnValue.as_instruction(), 0.into()), + ]); + units.normalize_get_iter(flags); + let expected = if input == 0 { + 0 + } else if flags.is_empty() { + 3 + } else { + 2 + }; + assert_eq!(u8::from(units[0].arg), expected); + assert_eq!(units[1].op.as_opcode(), Opcode::Cache); + assert_eq!(units[2].op.as_opcode(), Opcode::ReturnValue); + } + } + } + + #[test] + fn private_load_const_exports_as_public_opcode() { + use crate::bytecode::{CodeUnit, CodeUnits}; + for opcode in [Opcode::LoadConstImmortal, Opcode::LoadConstMortal] { + let units = CodeUnits::from(vec![CodeUnit::new(opcode.as_instruction(), 7.into())]); + assert_eq!(units.original_bytes(), vec![Opcode::LoadConst.as_u8(), 7]); + } + } + + #[test] + fn supported_stack_contract_matches_pinned_c_metadata() { + let mut checked = 0; + for &(opcode, arg, popped, pushed) in TARGET_STACK_CASES { + let Ok(op) = AnyOpcode::try_from(opcode) else { + continue; + }; + let actual = match op { + AnyOpcode::Real(op) => op.stack_effect_info(arg), + AnyOpcode::Pseudo(op) => op.stack_effect_info(arg), + }; + assert_eq!( + (actual.popped(), actual.pushed()), + (popped, pushed), + "opcode {opcode}, argument {arg}" + ); + checked += 1; + } + assert_eq!(checked, 3290, "supported C stack contract coverage changed"); + } + #[test] fn stack_effects_match_cpython_opcode_metadata() { - assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 1); - assert_eq!(Opcode::ForIter.stack_effect_info(0).pushed(), 2); + assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 2); + assert_eq!(Opcode::ForIter.stack_effect_info(0).pushed(), 3); assert_eq!(Opcode::ForIter.stack_effect(0), 1); assert_eq!(Opcode::ForIter.stack_effect_jump(0), 1); @@ -1473,10 +1535,8 @@ mod tests { assert!(AnyInstruction::from(PseudoOpcode::Jump).is_no_fallthrough()); } - /// Snapshot of the chained `match` implementations that `Opcode::deopt` - /// and `Opcode::cache_entries` used before they were rewritten as table - /// lookups. Exists only to pin the observable behavior of the table - /// lookups against the logic they replaced. + /// Independent match reference for the pinned final 3.15 cache/deopt contract. + /// Keep exhaustive numeric iteration below to check generated lookup tables. mod reference { use super::Opcode; @@ -1576,6 +1636,7 @@ mod tests { pub(super) const fn cache_entries(op: Opcode) -> usize { match deoptimize(op) { + Opcode::Resume | Opcode::GetIter | Opcode::CallFunctionEx => 1, Opcode::StoreSubscr => 1, Opcode::ToBool => 3, Opcode::BinaryOp => 5, diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index 8f7060030..cccee7072 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -756,6 +756,16 @@ oparg_enum!( BuiltinList = (5, "list"), /// Built-in `set` type BuiltinSet = (6, "set"), + /// The None singleton. + None = (7, "None"), + /// The empty string. + EmptyStr = (8, "empty string"), + /// The true singleton. + True = (9, "True"), + /// The false singleton. + False = (10, "False"), + /// Integer minus one. + MinusOne = (11, "-1"), } ); @@ -906,11 +916,11 @@ newtype_oparg!( ); impl ResumeContext { - /// [CPython `RESUME_OPARG_LOCATION_MASK`](https://github.com/python/cpython/blob/v3.14.3/Include/internal/pycore_opcode_utils.h#L84) - pub const LOCATION_MASK: u32 = 0x3; + /// [CPython `RESUME_OPARG_LOCATION_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h) + pub const LOCATION_MASK: u32 = 0x7; - /// [CPython `RESUME_OPARG_DEPTH1_MASK`](https://github.com/python/cpython/blob/v3.14.3/Include/internal/pycore_opcode_utils.h#L85) - pub const DEPTH1_MASK: u32 = 0x4; + /// [CPython `RESUME_OPARG_DEPTH1_MASK`](https://github.com/python/cpython/blob/v3.15.0/Include/internal/pycore_opcode_utils.h) + pub const DEPTH1_MASK: u32 = 0x8; #[must_use] pub const fn new(location: ResumeLocation, is_exception_depth1: bool) -> Self { @@ -926,8 +936,8 @@ impl ResumeContext { /// Resume location is determined by [`Self::LOCATION_MASK`]. #[must_use] pub fn location(&self) -> ResumeLocation { - // SAFETY: The mask should return a value that is in range. - unsafe { ResumeLocation::try_from(self.as_u32() & Self::LOCATION_MASK).unwrap_unchecked() } + ResumeLocation::try_from(self.as_u32() & Self::LOCATION_MASK) + .expect("invalid RESUME location") } /// True if the bit at [`Self::DEPTH1_MASK`] is on. @@ -947,6 +957,8 @@ pub enum ResumeLocation { AfterYieldFrom, /// After an `await` expression. AfterAwait, + /// Eager iterator acquisition before generator-expression creation. + AtGenExprStart, } impl From for ResumeContext { @@ -964,6 +976,7 @@ impl TryFrom for ResumeLocation { 1 => Self::AfterYield, 2 => Self::AfterYieldFrom, 3 => Self::AfterAwait, + 4 => Self::AtGenExprStart, _ => return Err(Self::Error::InvalidBytecode), }) } @@ -977,6 +990,7 @@ impl ResumeLocation { Self::AfterYield => 1, Self::AfterYieldFrom => 2, Self::AfterAwait => 3, + Self::AtGenExprStart => 4, } } diff --git a/crates/compiler-core/src/bytecode/opcode_metadata.rs b/crates/compiler-core/src/bytecode/opcode_metadata.rs index ca23e3c9e..e62a80dad 100644 --- a/crates/compiler-core/src/bytecode/opcode_metadata.rs +++ b/crates/compiler-core/src/bytecode/opcode_metadata.rs @@ -15,15 +15,15 @@ impl super::Opcode { #[inline] pub const fn cache_entries(self) -> usize { const CACHE_ENTRIES: [u8; 256] = [ - 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 4, 4, 1, 1, 0, 1, 4, 4, 4, 1, 1, - 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 3, 3, 0, 1, 0, 0, + 0, 0, 0, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 3, + 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 1, 1, 1, 0, 0, + 1, 0, 4, 4, 4, 1, 1, 3, 3, 3, 3, 3, 3, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 3, 3, 1, 1, 0, 0, 0, ]; CACHE_ENTRIES[self.as_numeric() as usize] as usize @@ -174,6 +174,7 @@ impl super::Opcode { Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), + None, Some(super::Opcode::BinaryOp), Some(super::Opcode::BinaryOp), Some(super::Opcode::Call), @@ -183,6 +184,8 @@ impl super::Opcode { Some(super::Opcode::Call), Some(super::Opcode::Call), Some(super::Opcode::Call), + None, + None, Some(super::Opcode::Call), Some(super::Opcode::CallKw), Some(super::Opcode::CallKw), @@ -208,6 +211,9 @@ impl super::Opcode { Some(super::Opcode::ForIter), Some(super::Opcode::ForIter), Some(super::Opcode::ForIter), + None, + None, + None, Some(super::Opcode::JumpBackward), Some(super::Opcode::JumpBackward), Some(super::Opcode::LoadAttr), @@ -223,14 +229,15 @@ impl super::Opcode { Some(super::Opcode::LoadAttr), Some(super::Opcode::LoadAttr), Some(super::Opcode::LoadAttr), - Some(super::Opcode::LoadConst), - Some(super::Opcode::LoadConst), Some(super::Opcode::LoadGlobal), Some(super::Opcode::LoadGlobal), Some(super::Opcode::LoadSuperAttr), Some(super::Opcode::LoadSuperAttr), Some(super::Opcode::Resume), + None, + None, Some(super::Opcode::Send), + None, Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), Some(super::Opcode::StoreAttr), @@ -246,15 +253,8 @@ impl super::Opcode { Some(super::Opcode::UnpackSequence), Some(super::Opcode::UnpackSequence), None, - None, - None, - None, - None, - None, - None, - None, - None, - None, + Some(super::Opcode::LoadConst), + Some(super::Opcode::LoadConst), None, None, None, @@ -327,6 +327,7 @@ impl super::Opcode { | Self::ExtendedArg | Self::ForIter | Self::GetAwaitable + | Self::GetIter | Self::ImportFrom | Self::ImportName | Self::IsOp @@ -384,7 +385,6 @@ impl super::Opcode { | Self::CallBuiltinFast | Self::CallBuiltinFastWithKeywords | Self::CallBuiltinO - | Self::CallIsinstance | Self::CallKwBoundMethod | Self::CallKwNonPy | Self::CallKwPy @@ -423,8 +423,6 @@ impl super::Opcode { | Self::LoadAttrProperty | Self::LoadAttrSlot | Self::LoadAttrWithHint - | Self::LoadConstImmortal - | Self::LoadConstMortal | Self::LoadGlobalBuiltin | Self::LoadGlobalModule | Self::LoadSuperAttrAttr @@ -434,6 +432,8 @@ impl super::Opcode { | Self::UnpackSequenceList | Self::UnpackSequenceTuple | Self::UnpackSequenceTwoTuple + | Self::LoadConstImmortal + | Self::LoadConstMortal | Self::InstrumentedForIter | Self::InstrumentedJumpForward | Self::InstrumentedPopJumpIfTrue @@ -609,23 +609,21 @@ impl super::Opcode { Self::Cache => (0, 0), Self::BinarySlice => (1, 3), Self::BuildTemplate => (1, 2), - Self::BinaryOpInplaceAddUnicode => (0, 2), + Self::BinaryOpInplaceAddUnicode => (1, 2), Self::CallFunctionEx => (1, 4), Self::CheckEgMatch => (2, 2), Self::CheckExcMatch => (2, 2), - Self::CleanupThrow => (2, 3), + Self::CleanupThrow => (3, 4), Self::DeleteSubscr => (0, 2), Self::EndFor => (0, 1), - Self::EndSend => (1, 2), + Self::EndSend => (1, 3), Self::ExitInitCheck => (0, 1), Self::FormatSimple => (1, 1), Self::FormatWithSpec => (1, 2), Self::GetAiter => (1, 1), Self::GetAnext => (2, 1), - Self::GetIter => (1, 1), - Self::Reserved => (0, 0), Self::GetLen => (2, 1), - Self::GetYieldFromIter => (1, 1), + Self::Reserved => (0, 0), Self::InterpreterExit => (0, 1), Self::LoadBuildClass => (1, 0), Self::LoadLocals => (1, 0), @@ -636,7 +634,7 @@ impl super::Opcode { Self::Nop => (0, 0), Self::NotTaken => (0, 0), Self::PopExcept => (0, 1), - Self::PopIter => (0, 1), + Self::PopIter => (0, 2), Self::PopTop => (0, 1), Self::PushExcInfo => (2, 1), Self::PushNull => (1, 0), @@ -676,8 +674,9 @@ impl super::Opcode { Self::DictUpdate => (1 + (oparg - 1), 2 + (oparg - 1)), Self::EndAsyncFor => (0, 2), Self::ExtendedArg => (0, 0), - Self::ForIter => (2, 1), + Self::ForIter => (3, 2), Self::GetAwaitable => (1, 1), + Self::GetIter => (2, 1), Self::ImportFrom => (2, 1), Self::ImportName => (1, 2), Self::IsOp => (1, 2), @@ -712,7 +711,7 @@ impl super::Opcode { Self::PopJumpIfTrue => (0, 1), Self::RaiseVarargs => (0, oparg), Self::Reraise => (oparg, 1 + oparg), - Self::Send => (2, 2), + Self::Send => (3, 3), Self::SetAdd => (1 + (oparg - 1), 2 + (oparg - 1)), Self::SetFunctionAttribute => (1, 2), Self::SetUpdate => (1 + (oparg - 1), 2 + (oparg - 1)), @@ -749,12 +748,12 @@ impl super::Opcode { Self::CallBuiltinFast => (1, 2 + oparg), Self::CallBuiltinFastWithKeywords => (1, 2 + oparg), Self::CallBuiltinO => (1, 2 + oparg), - Self::CallIsinstance => (1, 2 + oparg), + Self::CallIsinstance => (1, 4), Self::CallKwBoundMethod => (0, 3 + oparg), Self::CallKwNonPy => (1, 3 + oparg), Self::CallKwPy => (0, 3 + oparg), Self::CallLen => (1, 3), - Self::CallListAppend => (0, 3), + Self::CallListAppend => (1, 3), Self::CallMethodDescriptorFast => (1, 2 + oparg), Self::CallMethodDescriptorFastWithKeywords => (1, 2 + oparg), Self::CallMethodDescriptorNoargs => (1, 2 + oparg), @@ -770,15 +769,15 @@ impl super::Opcode { Self::CompareOpStr => (1, 2), Self::ContainsOpDict => (1, 2), Self::ContainsOpSet => (1, 2), - Self::ForIterGen => (1, 1), - Self::ForIterList => (2, 1), - Self::ForIterRange => (2, 1), - Self::ForIterTuple => (2, 1), + Self::ForIterGen => (2, 2), + Self::ForIterList => (3, 2), + Self::ForIterRange => (3, 2), + Self::ForIterTuple => (3, 2), Self::JumpBackwardJit => (0, 0), Self::JumpBackwardNoJit => (0, 0), Self::LoadAttrClass => (1 + (oparg & 1), 1), Self::LoadAttrClassWithMetaclassCheck => (1 + (oparg & 1), 1), - Self::LoadAttrGetattributeOverridden => (1, 1), + Self::LoadAttrGetattributeOverridden => (0, 1), Self::LoadAttrInstanceValue => (1 + (oparg & 1), 1), Self::LoadAttrMethodLazyDict => (2, 1), Self::LoadAttrMethodNoDict => (2, 1), @@ -789,14 +788,12 @@ impl super::Opcode { Self::LoadAttrProperty => (0, 1), Self::LoadAttrSlot => (1 + (oparg & 1), 1), Self::LoadAttrWithHint => (1 + (oparg & 1), 1), - Self::LoadConstImmortal => (1, 0), - Self::LoadConstMortal => (1, 0), Self::LoadGlobalBuiltin => (1 + (oparg & 1), 0), Self::LoadGlobalModule => (1 + (oparg & 1), 0), Self::LoadSuperAttrAttr => (1, 3), Self::LoadSuperAttrMethod => (2, 3), Self::ResumeCheck => (0, 0), - Self::SendGen => (1, 2), + Self::SendGen => (2, 3), Self::StoreAttrInstanceValue => (0, 2), Self::StoreAttrSlot => (0, 2), Self::StoreAttrWithHint => (0, 2), @@ -811,10 +808,16 @@ impl super::Opcode { Self::UnpackSequenceList => (oparg, 1), Self::UnpackSequenceTuple => (oparg, 1), Self::UnpackSequenceTwoTuple => (2, 1), - Self::InstrumentedEndFor => (1, 2), - Self::InstrumentedPopIter => (0, 1), - Self::InstrumentedEndSend => (1, 2), - Self::InstrumentedForIter => (2, 1), + Self::GetYieldFromIter => ( + 1, // TODO: Differs from CPython `None` + 1, // TODO: Differs from CPython `None` + ), + Self::LoadConstImmortal => (1, 0), + Self::LoadConstMortal => (1, 0), + Self::InstrumentedEndFor => (2, 3), + Self::InstrumentedPopIter => (0, 2), + Self::InstrumentedEndSend => (1, 3), + Self::InstrumentedForIter => (3, 2), Self::InstrumentedInstruction => (0, 0), Self::InstrumentedJumpForward => (0, 0), Self::InstrumentedNotTaken => (0, 0), diff --git a/crates/compiler-core/src/bytecode/target_stack_cases.rs b/crates/compiler-core/src/bytecode/target_stack_cases.rs new file mode 100644 index 000000000..64ccb5424 --- /dev/null +++ b/crates/compiler-core/src/bytecode/target_stack_cases.rs @@ -0,0 +1,3433 @@ +// Generated by tools/opcode_metadata/verify_stack_contract.py from pinned CPython C metadata. +const TARGET_STACK_CASES: &[(u16, u32, u32, u32)] = &[ + (0, 0, 0, 0), + (0, 1, 0, 0), + (0, 2, 0, 0), + (0, 3, 0, 0), + (0, 4, 0, 0), + (0, 5, 0, 0), + (0, 7, 0, 0), + (0, 8, 0, 0), + (0, 15, 0, 0), + (0, 16, 0, 0), + (0, 31, 0, 0), + (0, 255, 0, 0), + (0, 256, 0, 0), + (0, 65535, 0, 0), + (1, 0, 3, 1), + (1, 1, 3, 1), + (1, 2, 3, 1), + (1, 3, 3, 1), + (1, 4, 3, 1), + (1, 5, 3, 1), + (1, 7, 3, 1), + (1, 8, 3, 1), + (1, 15, 3, 1), + (1, 16, 3, 1), + (1, 31, 3, 1), + (1, 255, 3, 1), + (1, 256, 3, 1), + (1, 65535, 3, 1), + (2, 0, 2, 1), + (2, 1, 2, 1), + (2, 2, 2, 1), + (2, 3, 2, 1), + (2, 4, 2, 1), + (2, 5, 2, 1), + (2, 7, 2, 1), + (2, 8, 2, 1), + (2, 15, 2, 1), + (2, 16, 2, 1), + (2, 31, 2, 1), + (2, 255, 2, 1), + (2, 256, 2, 1), + (2, 65535, 2, 1), + (3, 0, 2, 1), + (3, 1, 2, 1), + (3, 2, 2, 1), + (3, 3, 2, 1), + (3, 4, 2, 1), + (3, 5, 2, 1), + (3, 7, 2, 1), + (3, 8, 2, 1), + (3, 15, 2, 1), + (3, 16, 2, 1), + (3, 31, 2, 1), + (3, 255, 2, 1), + (3, 256, 2, 1), + (3, 65535, 2, 1), + (4, 0, 4, 1), + (4, 1, 4, 1), + (4, 2, 4, 1), + (4, 3, 4, 1), + (4, 4, 4, 1), + (4, 5, 4, 1), + (4, 7, 4, 1), + (4, 8, 4, 1), + (4, 15, 4, 1), + (4, 16, 4, 1), + (4, 31, 4, 1), + (4, 255, 4, 1), + (4, 256, 4, 1), + (4, 65535, 4, 1), + (5, 0, 2, 2), + (5, 1, 2, 2), + (5, 2, 2, 2), + (5, 3, 2, 2), + (5, 4, 2, 2), + (5, 5, 2, 2), + (5, 7, 2, 2), + (5, 8, 2, 2), + (5, 15, 2, 2), + (5, 16, 2, 2), + (5, 31, 2, 2), + (5, 255, 2, 2), + (5, 256, 2, 2), + (5, 65535, 2, 2), + (6, 0, 2, 2), + (6, 1, 2, 2), + (6, 2, 2, 2), + (6, 3, 2, 2), + (6, 4, 2, 2), + (6, 5, 2, 2), + (6, 7, 2, 2), + (6, 8, 2, 2), + (6, 15, 2, 2), + (6, 16, 2, 2), + (6, 31, 2, 2), + (6, 255, 2, 2), + (6, 256, 2, 2), + (6, 65535, 2, 2), + (7, 0, 4, 3), + (7, 1, 4, 3), + (7, 2, 4, 3), + (7, 3, 4, 3), + (7, 4, 4, 3), + (7, 5, 4, 3), + (7, 7, 4, 3), + (7, 8, 4, 3), + (7, 15, 4, 3), + (7, 16, 4, 3), + (7, 31, 4, 3), + (7, 255, 4, 3), + (7, 256, 4, 3), + (7, 65535, 4, 3), + (8, 0, 2, 0), + (8, 1, 2, 0), + (8, 2, 2, 0), + (8, 3, 2, 0), + (8, 4, 2, 0), + (8, 5, 2, 0), + (8, 7, 2, 0), + (8, 8, 2, 0), + (8, 15, 2, 0), + (8, 16, 2, 0), + (8, 31, 2, 0), + (8, 255, 2, 0), + (8, 256, 2, 0), + (8, 65535, 2, 0), + (9, 0, 1, 0), + (9, 1, 1, 0), + (9, 2, 1, 0), + (9, 3, 1, 0), + (9, 4, 1, 0), + (9, 5, 1, 0), + (9, 7, 1, 0), + (9, 8, 1, 0), + (9, 15, 1, 0), + (9, 16, 1, 0), + (9, 31, 1, 0), + (9, 255, 1, 0), + (9, 256, 1, 0), + (9, 65535, 1, 0), + (10, 0, 3, 1), + (10, 1, 3, 1), + (10, 2, 3, 1), + (10, 3, 3, 1), + (10, 4, 3, 1), + (10, 5, 3, 1), + (10, 7, 3, 1), + (10, 8, 3, 1), + (10, 15, 3, 1), + (10, 16, 3, 1), + (10, 31, 3, 1), + (10, 255, 3, 1), + (10, 256, 3, 1), + (10, 65535, 3, 1), + (11, 0, 1, 0), + (11, 1, 1, 0), + (11, 2, 1, 0), + (11, 3, 1, 0), + (11, 4, 1, 0), + (11, 5, 1, 0), + (11, 7, 1, 0), + (11, 8, 1, 0), + (11, 15, 1, 0), + (11, 16, 1, 0), + (11, 31, 1, 0), + (11, 255, 1, 0), + (11, 256, 1, 0), + (11, 65535, 1, 0), + (12, 0, 1, 1), + (12, 1, 1, 1), + (12, 2, 1, 1), + (12, 3, 1, 1), + (12, 4, 1, 1), + (12, 5, 1, 1), + (12, 7, 1, 1), + (12, 8, 1, 1), + (12, 15, 1, 1), + (12, 16, 1, 1), + (12, 31, 1, 1), + (12, 255, 1, 1), + (12, 256, 1, 1), + (12, 65535, 1, 1), + (13, 0, 2, 1), + (13, 1, 2, 1), + (13, 2, 2, 1), + (13, 3, 2, 1), + (13, 4, 2, 1), + (13, 5, 2, 1), + (13, 7, 2, 1), + (13, 8, 2, 1), + (13, 15, 2, 1), + (13, 16, 2, 1), + (13, 31, 2, 1), + (13, 255, 2, 1), + (13, 256, 2, 1), + (13, 65535, 2, 1), + (14, 0, 1, 1), + (14, 1, 1, 1), + (14, 2, 1, 1), + (14, 3, 1, 1), + (14, 4, 1, 1), + (14, 5, 1, 1), + (14, 7, 1, 1), + (14, 8, 1, 1), + (14, 15, 1, 1), + (14, 16, 1, 1), + (14, 31, 1, 1), + (14, 255, 1, 1), + (14, 256, 1, 1), + (14, 65535, 1, 1), + (15, 0, 1, 2), + (15, 1, 1, 2), + (15, 2, 1, 2), + (15, 3, 1, 2), + (15, 4, 1, 2), + (15, 5, 1, 2), + (15, 7, 1, 2), + (15, 8, 1, 2), + (15, 15, 1, 2), + (15, 16, 1, 2), + (15, 31, 1, 2), + (15, 255, 1, 2), + (15, 256, 1, 2), + (15, 65535, 1, 2), + (16, 0, 1, 2), + (16, 1, 1, 2), + (16, 2, 1, 2), + (16, 3, 1, 2), + (16, 4, 1, 2), + (16, 5, 1, 2), + (16, 7, 1, 2), + (16, 8, 1, 2), + (16, 15, 1, 2), + (16, 16, 1, 2), + (16, 31, 1, 2), + (16, 255, 1, 2), + (16, 256, 1, 2), + (16, 65535, 1, 2), + (17, 0, 0, 0), + (17, 1, 0, 0), + (17, 2, 0, 0), + (17, 3, 0, 0), + (17, 4, 0, 0), + (17, 5, 0, 0), + (17, 7, 0, 0), + (17, 8, 0, 0), + (17, 15, 0, 0), + (17, 16, 0, 0), + (17, 31, 0, 0), + (17, 255, 0, 0), + (17, 256, 0, 0), + (17, 65535, 0, 0), + (18, 0, 1, 0), + (18, 1, 1, 0), + (18, 2, 1, 0), + (18, 3, 1, 0), + (18, 4, 1, 0), + (18, 5, 1, 0), + (18, 7, 1, 0), + (18, 8, 1, 0), + (18, 15, 1, 0), + (18, 16, 1, 0), + (18, 31, 1, 0), + (18, 255, 1, 0), + (18, 256, 1, 0), + (18, 65535, 1, 0), + (19, 0, 0, 1), + (19, 1, 0, 1), + (19, 2, 0, 1), + (19, 3, 0, 1), + (19, 4, 0, 1), + (19, 5, 0, 1), + (19, 7, 0, 1), + (19, 8, 0, 1), + (19, 15, 0, 1), + (19, 16, 0, 1), + (19, 31, 0, 1), + (19, 255, 0, 1), + (19, 256, 0, 1), + (19, 65535, 0, 1), + (20, 0, 0, 1), + (20, 1, 0, 1), + (20, 2, 0, 1), + (20, 3, 0, 1), + (20, 4, 0, 1), + (20, 5, 0, 1), + (20, 7, 0, 1), + (20, 8, 0, 1), + (20, 15, 0, 1), + (20, 16, 0, 1), + (20, 31, 0, 1), + (20, 255, 0, 1), + (20, 256, 0, 1), + (20, 65535, 0, 1), + (21, 0, 1, 1), + (21, 1, 1, 1), + (21, 2, 1, 1), + (21, 3, 1, 1), + (21, 4, 1, 1), + (21, 5, 1, 1), + (21, 7, 1, 1), + (21, 8, 1, 1), + (21, 15, 1, 1), + (21, 16, 1, 1), + (21, 31, 1, 1), + (21, 255, 1, 1), + (21, 256, 1, 1), + (21, 65535, 1, 1), + (22, 0, 2, 3), + (22, 1, 2, 3), + (22, 2, 2, 3), + (22, 3, 2, 3), + (22, 4, 2, 3), + (22, 5, 2, 3), + (22, 7, 2, 3), + (22, 8, 2, 3), + (22, 15, 2, 3), + (22, 16, 2, 3), + (22, 31, 2, 3), + (22, 255, 2, 3), + (22, 256, 2, 3), + (22, 65535, 2, 3), + (23, 0, 1, 2), + (23, 1, 1, 2), + (23, 2, 1, 2), + (23, 3, 1, 2), + (23, 4, 1, 2), + (23, 5, 1, 2), + (23, 7, 1, 2), + (23, 8, 1, 2), + (23, 15, 1, 2), + (23, 16, 1, 2), + (23, 31, 1, 2), + (23, 255, 1, 2), + (23, 256, 1, 2), + (23, 65535, 1, 2), + (24, 0, 1, 2), + (24, 1, 1, 2), + (24, 2, 1, 2), + (24, 3, 1, 2), + (24, 4, 1, 2), + (24, 5, 1, 2), + (24, 7, 1, 2), + (24, 8, 1, 2), + (24, 15, 1, 2), + (24, 16, 1, 2), + (24, 31, 1, 2), + (24, 255, 1, 2), + (24, 256, 1, 2), + (24, 65535, 1, 2), + (25, 0, 0, 0), + (25, 1, 0, 0), + (25, 2, 0, 0), + (25, 3, 0, 0), + (25, 4, 0, 0), + (25, 5, 0, 0), + (25, 7, 0, 0), + (25, 8, 0, 0), + (25, 15, 0, 0), + (25, 16, 0, 0), + (25, 31, 0, 0), + (25, 255, 0, 0), + (25, 256, 0, 0), + (25, 65535, 0, 0), + (26, 0, 0, 0), + (26, 1, 0, 0), + (26, 2, 0, 0), + (26, 3, 0, 0), + (26, 4, 0, 0), + (26, 5, 0, 0), + (26, 7, 0, 0), + (26, 8, 0, 0), + (26, 15, 0, 0), + (26, 16, 0, 0), + (26, 31, 0, 0), + (26, 255, 0, 0), + (26, 256, 0, 0), + (26, 65535, 0, 0), + (27, 0, 1, 0), + (27, 1, 1, 0), + (27, 2, 1, 0), + (27, 3, 1, 0), + (27, 4, 1, 0), + (27, 5, 1, 0), + (27, 7, 1, 0), + (27, 8, 1, 0), + (27, 15, 1, 0), + (27, 16, 1, 0), + (27, 31, 1, 0), + (27, 255, 1, 0), + (27, 256, 1, 0), + (27, 65535, 1, 0), + (28, 0, 2, 0), + (28, 1, 2, 0), + (28, 2, 2, 0), + (28, 3, 2, 0), + (28, 4, 2, 0), + (28, 5, 2, 0), + (28, 7, 2, 0), + (28, 8, 2, 0), + (28, 15, 2, 0), + (28, 16, 2, 0), + (28, 31, 2, 0), + (28, 255, 2, 0), + (28, 256, 2, 0), + (28, 65535, 2, 0), + (29, 0, 1, 0), + (29, 1, 1, 0), + (29, 2, 1, 0), + (29, 3, 1, 0), + (29, 4, 1, 0), + (29, 5, 1, 0), + (29, 7, 1, 0), + (29, 8, 1, 0), + (29, 15, 1, 0), + (29, 16, 1, 0), + (29, 31, 1, 0), + (29, 255, 1, 0), + (29, 256, 1, 0), + (29, 65535, 1, 0), + (30, 0, 1, 2), + (30, 1, 1, 2), + (30, 2, 1, 2), + (30, 3, 1, 2), + (30, 4, 1, 2), + (30, 5, 1, 2), + (30, 7, 1, 2), + (30, 8, 1, 2), + (30, 15, 1, 2), + (30, 16, 1, 2), + (30, 31, 1, 2), + (30, 255, 1, 2), + (30, 256, 1, 2), + (30, 65535, 1, 2), + (31, 0, 0, 1), + (31, 1, 0, 1), + (31, 2, 0, 1), + (31, 3, 0, 1), + (31, 4, 0, 1), + (31, 5, 0, 1), + (31, 7, 0, 1), + (31, 8, 0, 1), + (31, 15, 0, 1), + (31, 16, 0, 1), + (31, 31, 0, 1), + (31, 255, 0, 1), + (31, 256, 0, 1), + (31, 65535, 0, 1), + (32, 0, 0, 1), + (32, 1, 0, 1), + (32, 2, 0, 1), + (32, 3, 0, 1), + (32, 4, 0, 1), + (32, 5, 0, 1), + (32, 7, 0, 1), + (32, 8, 0, 1), + (32, 15, 0, 1), + (32, 16, 0, 1), + (32, 31, 0, 1), + (32, 255, 0, 1), + (32, 256, 0, 1), + (32, 65535, 0, 1), + (33, 0, 1, 1), + (33, 1, 1, 1), + (33, 2, 1, 1), + (33, 3, 1, 1), + (33, 4, 1, 1), + (33, 5, 1, 1), + (33, 7, 1, 1), + (33, 8, 1, 1), + (33, 15, 1, 1), + (33, 16, 1, 1), + (33, 31, 1, 1), + (33, 255, 1, 1), + (33, 256, 1, 1), + (33, 65535, 1, 1), + (34, 0, 0, 0), + (34, 1, 0, 0), + (34, 2, 0, 0), + (34, 3, 0, 0), + (34, 4, 0, 0), + (34, 5, 0, 0), + (34, 7, 0, 0), + (34, 8, 0, 0), + (34, 15, 0, 0), + (34, 16, 0, 0), + (34, 31, 0, 0), + (34, 255, 0, 0), + (34, 256, 0, 0), + (34, 65535, 0, 0), + (35, 0, 4, 0), + (35, 1, 4, 0), + (35, 2, 4, 0), + (35, 3, 4, 0), + (35, 4, 4, 0), + (35, 5, 4, 0), + (35, 7, 4, 0), + (35, 8, 4, 0), + (35, 15, 4, 0), + (35, 16, 4, 0), + (35, 31, 4, 0), + (35, 255, 4, 0), + (35, 256, 4, 0), + (35, 65535, 4, 0), + (36, 0, 3, 0), + (36, 1, 3, 0), + (36, 2, 3, 0), + (36, 3, 3, 0), + (36, 4, 3, 0), + (36, 5, 3, 0), + (36, 7, 3, 0), + (36, 8, 3, 0), + (36, 15, 3, 0), + (36, 16, 3, 0), + (36, 31, 3, 0), + (36, 255, 3, 0), + (36, 256, 3, 0), + (36, 65535, 3, 0), + (37, 0, 1, 1), + (37, 1, 1, 1), + (37, 2, 1, 1), + (37, 3, 1, 1), + (37, 4, 1, 1), + (37, 5, 1, 1), + (37, 7, 1, 1), + (37, 8, 1, 1), + (37, 15, 1, 1), + (37, 16, 1, 1), + (37, 31, 1, 1), + (37, 255, 1, 1), + (37, 256, 1, 1), + (37, 65535, 1, 1), + (38, 0, 1, 1), + (38, 1, 1, 1), + (38, 2, 1, 1), + (38, 3, 1, 1), + (38, 4, 1, 1), + (38, 5, 1, 1), + (38, 7, 1, 1), + (38, 8, 1, 1), + (38, 15, 1, 1), + (38, 16, 1, 1), + (38, 31, 1, 1), + (38, 255, 1, 1), + (38, 256, 1, 1), + (38, 65535, 1, 1), + (39, 0, 1, 1), + (39, 1, 1, 1), + (39, 2, 1, 1), + (39, 3, 1, 1), + (39, 4, 1, 1), + (39, 5, 1, 1), + (39, 7, 1, 1), + (39, 8, 1, 1), + (39, 15, 1, 1), + (39, 16, 1, 1), + (39, 31, 1, 1), + (39, 255, 1, 1), + (39, 256, 1, 1), + (39, 65535, 1, 1), + (40, 0, 1, 1), + (40, 1, 1, 1), + (40, 2, 1, 1), + (40, 3, 1, 1), + (40, 4, 1, 1), + (40, 5, 1, 1), + (40, 7, 1, 1), + (40, 8, 1, 1), + (40, 15, 1, 1), + (40, 16, 1, 1), + (40, 31, 1, 1), + (40, 255, 1, 1), + (40, 256, 1, 1), + (40, 65535, 1, 1), + (41, 0, 5, 6), + (41, 1, 5, 6), + (41, 2, 5, 6), + (41, 3, 5, 6), + (41, 4, 5, 6), + (41, 5, 5, 6), + (41, 7, 5, 6), + (41, 8, 5, 6), + (41, 15, 5, 6), + (41, 16, 5, 6), + (41, 31, 5, 6), + (41, 255, 5, 6), + (41, 256, 5, 6), + (41, 65535, 5, 6), + (42, 0, 2, 1), + (42, 1, 2, 1), + (42, 2, 2, 1), + (42, 3, 2, 1), + (42, 4, 2, 1), + (42, 5, 2, 1), + (42, 7, 2, 1), + (42, 8, 2, 1), + (42, 15, 2, 1), + (42, 16, 2, 1), + (42, 31, 2, 1), + (42, 255, 2, 1), + (42, 256, 2, 1), + (42, 65535, 2, 1), + (43, 0, 2, 1), + (43, 1, 3, 1), + (43, 2, 2, 1), + (43, 3, 3, 1), + (43, 4, 2, 1), + (43, 5, 3, 1), + (43, 7, 3, 1), + (43, 8, 2, 1), + (43, 15, 3, 1), + (43, 16, 2, 1), + (43, 31, 3, 1), + (43, 255, 3, 1), + (43, 256, 2, 1), + (43, 65535, 3, 1), + (44, 0, 0, 1), + (44, 1, 1, 1), + (44, 2, 2, 1), + (44, 3, 3, 1), + (44, 4, 4, 1), + (44, 5, 5, 1), + (44, 7, 7, 1), + (44, 8, 8, 1), + (44, 15, 15, 1), + (44, 16, 16, 1), + (44, 31, 31, 1), + (44, 255, 255, 1), + (44, 256, 256, 1), + (44, 65535, 65535, 1), + (45, 0, 0, 1), + (45, 1, 2, 1), + (45, 2, 4, 1), + (45, 3, 6, 1), + (45, 4, 8, 1), + (45, 5, 10, 1), + (45, 7, 14, 1), + (45, 8, 16, 1), + (45, 15, 30, 1), + (45, 16, 32, 1), + (45, 31, 62, 1), + (45, 255, 510, 1), + (45, 256, 512, 1), + (45, 65535, 131070, 1), + (46, 0, 0, 1), + (46, 1, 1, 1), + (46, 2, 2, 1), + (46, 3, 3, 1), + (46, 4, 4, 1), + (46, 5, 5, 1), + (46, 7, 7, 1), + (46, 8, 8, 1), + (46, 15, 15, 1), + (46, 16, 16, 1), + (46, 31, 31, 1), + (46, 255, 255, 1), + (46, 256, 256, 1), + (46, 65535, 65535, 1), + (47, 0, 0, 1), + (47, 1, 1, 1), + (47, 2, 2, 1), + (47, 3, 3, 1), + (47, 4, 4, 1), + (47, 5, 5, 1), + (47, 7, 7, 1), + (47, 8, 8, 1), + (47, 15, 15, 1), + (47, 16, 16, 1), + (47, 31, 31, 1), + (47, 255, 255, 1), + (47, 256, 256, 1), + (47, 65535, 65535, 1), + (48, 0, 0, 1), + (48, 1, 1, 1), + (48, 2, 2, 1), + (48, 3, 3, 1), + (48, 4, 4, 1), + (48, 5, 5, 1), + (48, 7, 7, 1), + (48, 8, 8, 1), + (48, 15, 15, 1), + (48, 16, 16, 1), + (48, 31, 31, 1), + (48, 255, 255, 1), + (48, 256, 256, 1), + (48, 65535, 65535, 1), + (49, 0, 0, 1), + (49, 1, 1, 1), + (49, 2, 2, 1), + (49, 3, 3, 1), + (49, 4, 4, 1), + (49, 5, 5, 1), + (49, 7, 7, 1), + (49, 8, 8, 1), + (49, 15, 15, 1), + (49, 16, 16, 1), + (49, 31, 31, 1), + (49, 255, 255, 1), + (49, 256, 256, 1), + (49, 65535, 65535, 1), + (50, 0, 2, 1), + (50, 1, 3, 1), + (50, 2, 4, 1), + (50, 3, 5, 1), + (50, 4, 6, 1), + (50, 5, 7, 1), + (50, 7, 9, 1), + (50, 8, 10, 1), + (50, 15, 17, 1), + (50, 16, 18, 1), + (50, 31, 33, 1), + (50, 255, 257, 1), + (50, 256, 258, 1), + (50, 65535, 65537, 1), + (51, 0, 1, 1), + (51, 1, 1, 1), + (51, 2, 1, 1), + (51, 3, 1, 1), + (51, 4, 1, 1), + (51, 5, 1, 1), + (51, 7, 1, 1), + (51, 8, 1, 1), + (51, 15, 1, 1), + (51, 16, 1, 1), + (51, 31, 1, 1), + (51, 255, 1, 1), + (51, 256, 1, 1), + (51, 65535, 1, 1), + (52, 0, 2, 1), + (52, 1, 2, 1), + (52, 2, 2, 1), + (52, 3, 2, 1), + (52, 4, 2, 1), + (52, 5, 2, 1), + (52, 7, 2, 1), + (52, 8, 2, 1), + (52, 15, 2, 1), + (52, 16, 2, 1), + (52, 31, 2, 1), + (52, 255, 2, 1), + (52, 256, 2, 1), + (52, 65535, 2, 1), + (53, 0, 3, 1), + (53, 1, 4, 1), + (53, 2, 5, 1), + (53, 3, 6, 1), + (53, 4, 7, 1), + (53, 5, 8, 1), + (53, 7, 10, 1), + (53, 8, 11, 1), + (53, 15, 18, 1), + (53, 16, 19, 1), + (53, 31, 34, 1), + (53, 255, 258, 1), + (53, 256, 259, 1), + (53, 65535, 65538, 1), + (54, 0, 2, 1), + (54, 1, 2, 1), + (54, 2, 2, 1), + (54, 3, 2, 1), + (54, 4, 2, 1), + (54, 5, 2, 1), + (54, 7, 2, 1), + (54, 8, 2, 1), + (54, 15, 2, 1), + (54, 16, 2, 1), + (54, 31, 2, 1), + (54, 255, 2, 1), + (54, 256, 2, 1), + (54, 65535, 2, 1), + (55, 0, 2, 1), + (55, 1, 2, 1), + (55, 2, 2, 1), + (55, 3, 2, 1), + (55, 4, 2, 1), + (55, 5, 2, 1), + (55, 7, 2, 1), + (55, 8, 2, 1), + (55, 15, 2, 1), + (55, 16, 2, 1), + (55, 31, 2, 1), + (55, 255, 2, 1), + (55, 256, 2, 1), + (55, 65535, 2, 1), + (56, 0, 1, 1), + (56, 1, 1, 1), + (56, 2, 1, 1), + (56, 3, 1, 1), + (56, 4, 1, 1), + (56, 5, 1, 1), + (56, 7, 1, 1), + (56, 8, 1, 1), + (56, 15, 1, 1), + (56, 16, 1, 1), + (56, 31, 1, 1), + (56, 255, 1, 1), + (56, 256, 1, 1), + (56, 65535, 1, 1), + (57, 0, 0, 1), + (57, 1, 1, 2), + (57, 2, 2, 3), + (57, 3, 3, 4), + (57, 4, 4, 5), + (57, 5, 5, 6), + (57, 7, 7, 8), + (57, 8, 8, 9), + (57, 15, 15, 16), + (57, 16, 16, 17), + (57, 31, 31, 32), + (57, 255, 255, 256), + (57, 256, 256, 257), + (57, 65535, 65535, 65536), + (58, 0, 0, 0), + (58, 1, 0, 0), + (58, 2, 0, 0), + (58, 3, 0, 0), + (58, 4, 0, 0), + (58, 5, 0, 0), + (58, 7, 0, 0), + (58, 8, 0, 0), + (58, 15, 0, 0), + (58, 16, 0, 0), + (58, 31, 0, 0), + (58, 255, 0, 0), + (58, 256, 0, 0), + (58, 65535, 0, 0), + (59, 0, 1, 0), + (59, 1, 1, 0), + (59, 2, 1, 0), + (59, 3, 1, 0), + (59, 4, 1, 0), + (59, 5, 1, 0), + (59, 7, 1, 0), + (59, 8, 1, 0), + (59, 15, 1, 0), + (59, 16, 1, 0), + (59, 31, 1, 0), + (59, 255, 1, 0), + (59, 256, 1, 0), + (59, 65535, 1, 0), + (60, 0, 0, 0), + (60, 1, 0, 0), + (60, 2, 0, 0), + (60, 3, 0, 0), + (60, 4, 0, 0), + (60, 5, 0, 0), + (60, 7, 0, 0), + (60, 8, 0, 0), + (60, 15, 0, 0), + (60, 16, 0, 0), + (60, 31, 0, 0), + (60, 255, 0, 0), + (60, 256, 0, 0), + (60, 65535, 0, 0), + (61, 0, 0, 0), + (61, 1, 0, 0), + (61, 2, 0, 0), + (61, 3, 0, 0), + (61, 4, 0, 0), + (61, 5, 0, 0), + (61, 7, 0, 0), + (61, 8, 0, 0), + (61, 15, 0, 0), + (61, 16, 0, 0), + (61, 31, 0, 0), + (61, 255, 0, 0), + (61, 256, 0, 0), + (61, 65535, 0, 0), + (62, 0, 0, 0), + (62, 1, 0, 0), + (62, 2, 0, 0), + (62, 3, 0, 0), + (62, 4, 0, 0), + (62, 5, 0, 0), + (62, 7, 0, 0), + (62, 8, 0, 0), + (62, 15, 0, 0), + (62, 16, 0, 0), + (62, 31, 0, 0), + (62, 255, 0, 0), + (62, 256, 0, 0), + (62, 65535, 0, 0), + (63, 0, 0, 0), + (63, 1, 0, 0), + (63, 2, 0, 0), + (63, 3, 0, 0), + (63, 4, 0, 0), + (63, 5, 0, 0), + (63, 7, 0, 0), + (63, 8, 0, 0), + (63, 15, 0, 0), + (63, 16, 0, 0), + (63, 31, 0, 0), + (63, 255, 0, 0), + (63, 256, 0, 0), + (63, 65535, 0, 0), + (64, 0, 4, 3), + (64, 1, 5, 4), + (64, 2, 6, 5), + (64, 3, 7, 6), + (64, 4, 8, 7), + (64, 5, 9, 8), + (64, 7, 11, 10), + (64, 8, 12, 11), + (64, 15, 19, 18), + (64, 16, 20, 19), + (64, 31, 35, 34), + (64, 255, 259, 258), + (64, 256, 260, 259), + (64, 65535, 65539, 65538), + (65, 0, 1, 0), + (65, 1, 2, 1), + (65, 2, 3, 2), + (65, 3, 4, 3), + (65, 4, 5, 4), + (65, 5, 6, 5), + (65, 7, 8, 7), + (65, 8, 9, 8), + (65, 15, 16, 15), + (65, 16, 17, 16), + (65, 31, 32, 31), + (65, 255, 256, 255), + (65, 256, 257, 256), + (65, 65535, 65536, 65535), + (66, 0, 2, 0), + (66, 1, 2, 0), + (66, 2, 2, 0), + (66, 3, 2, 0), + (66, 4, 2, 0), + (66, 5, 2, 0), + (66, 7, 2, 0), + (66, 8, 2, 0), + (66, 15, 2, 0), + (66, 16, 2, 0), + (66, 31, 2, 0), + (66, 255, 2, 0), + (66, 256, 2, 0), + (66, 65535, 2, 0), + (67, 0, 0, 0), + (67, 1, 0, 0), + (67, 2, 0, 0), + (67, 3, 0, 0), + (67, 4, 0, 0), + (67, 5, 0, 0), + (67, 7, 0, 0), + (67, 8, 0, 0), + (67, 15, 0, 0), + (67, 16, 0, 0), + (67, 31, 0, 0), + (67, 255, 0, 0), + (67, 256, 0, 0), + (67, 65535, 0, 0), + (68, 0, 2, 3), + (68, 1, 2, 3), + (68, 2, 2, 3), + (68, 3, 2, 3), + (68, 4, 2, 3), + (68, 5, 2, 3), + (68, 7, 2, 3), + (68, 8, 2, 3), + (68, 15, 2, 3), + (68, 16, 2, 3), + (68, 31, 2, 3), + (68, 255, 2, 3), + (68, 256, 2, 3), + (68, 65535, 2, 3), + (69, 0, 1, 1), + (69, 1, 1, 1), + (69, 2, 1, 1), + (69, 3, 1, 1), + (69, 4, 1, 1), + (69, 5, 1, 1), + (69, 7, 1, 1), + (69, 8, 1, 1), + (69, 15, 1, 1), + (69, 16, 1, 1), + (69, 31, 1, 1), + (69, 255, 1, 1), + (69, 256, 1, 1), + (69, 65535, 1, 1), + (70, 0, 1, 2), + (70, 1, 1, 2), + (70, 2, 1, 2), + (70, 3, 1, 2), + (70, 4, 1, 2), + (70, 5, 1, 2), + (70, 7, 1, 2), + (70, 8, 1, 2), + (70, 15, 1, 2), + (70, 16, 1, 2), + (70, 31, 1, 2), + (70, 255, 1, 2), + (70, 256, 1, 2), + (70, 65535, 1, 2), + (71, 0, 1, 2), + (71, 1, 1, 2), + (71, 2, 1, 2), + (71, 3, 1, 2), + (71, 4, 1, 2), + (71, 5, 1, 2), + (71, 7, 1, 2), + (71, 8, 1, 2), + (71, 15, 1, 2), + (71, 16, 1, 2), + (71, 31, 1, 2), + (71, 255, 1, 2), + (71, 256, 1, 2), + (71, 65535, 1, 2), + (72, 0, 2, 1), + (72, 1, 2, 1), + (72, 2, 2, 1), + (72, 3, 2, 1), + (72, 4, 2, 1), + (72, 5, 2, 1), + (72, 7, 2, 1), + (72, 8, 2, 1), + (72, 15, 2, 1), + (72, 16, 2, 1), + (72, 31, 2, 1), + (72, 255, 2, 1), + (72, 256, 2, 1), + (72, 65535, 2, 1), + (73, 0, 2, 1), + (73, 1, 2, 1), + (73, 2, 2, 1), + (73, 3, 2, 1), + (73, 4, 2, 1), + (73, 5, 2, 1), + (73, 7, 2, 1), + (73, 8, 2, 1), + (73, 15, 2, 1), + (73, 16, 2, 1), + (73, 31, 2, 1), + (73, 255, 2, 1), + (73, 256, 2, 1), + (73, 65535, 2, 1), + (74, 0, 0, 0), + (74, 1, 0, 0), + (74, 2, 0, 0), + (74, 3, 0, 0), + (74, 4, 0, 0), + (74, 5, 0, 0), + (74, 7, 0, 0), + (74, 8, 0, 0), + (74, 15, 0, 0), + (74, 16, 0, 0), + (74, 31, 0, 0), + (74, 255, 0, 0), + (74, 256, 0, 0), + (74, 65535, 0, 0), + (75, 0, 0, 0), + (75, 1, 0, 0), + (75, 2, 0, 0), + (75, 3, 0, 0), + (75, 4, 0, 0), + (75, 5, 0, 0), + (75, 7, 0, 0), + (75, 8, 0, 0), + (75, 15, 0, 0), + (75, 16, 0, 0), + (75, 31, 0, 0), + (75, 255, 0, 0), + (75, 256, 0, 0), + (75, 65535, 0, 0), + (76, 0, 0, 0), + (76, 1, 0, 0), + (76, 2, 0, 0), + (76, 3, 0, 0), + (76, 4, 0, 0), + (76, 5, 0, 0), + (76, 7, 0, 0), + (76, 8, 0, 0), + (76, 15, 0, 0), + (76, 16, 0, 0), + (76, 31, 0, 0), + (76, 255, 0, 0), + (76, 256, 0, 0), + (76, 65535, 0, 0), + (77, 0, 1, 0), + (77, 1, 2, 1), + (77, 2, 3, 2), + (77, 3, 4, 3), + (77, 4, 5, 4), + (77, 5, 6, 5), + (77, 7, 8, 7), + (77, 8, 9, 8), + (77, 15, 16, 15), + (77, 16, 17, 16), + (77, 31, 32, 31), + (77, 255, 256, 255), + (77, 256, 257, 256), + (77, 65535, 65536, 65535), + (78, 0, 1, 0), + (78, 1, 2, 1), + (78, 2, 3, 2), + (78, 3, 4, 3), + (78, 4, 5, 4), + (78, 5, 6, 5), + (78, 7, 8, 7), + (78, 8, 9, 8), + (78, 15, 16, 15), + (78, 16, 17, 16), + (78, 31, 32, 31), + (78, 255, 256, 255), + (78, 256, 257, 256), + (78, 65535, 65536, 65535), + (79, 0, 1, 1), + (79, 1, 1, 2), + (79, 2, 1, 1), + (79, 3, 1, 2), + (79, 4, 1, 1), + (79, 5, 1, 2), + (79, 7, 1, 2), + (79, 8, 1, 1), + (79, 15, 1, 2), + (79, 16, 1, 1), + (79, 31, 1, 2), + (79, 255, 1, 2), + (79, 256, 1, 1), + (79, 65535, 1, 2), + (80, 0, 0, 1), + (80, 1, 0, 1), + (80, 2, 0, 1), + (80, 3, 0, 1), + (80, 4, 0, 1), + (80, 5, 0, 1), + (80, 7, 0, 1), + (80, 8, 0, 1), + (80, 15, 0, 1), + (80, 16, 0, 1), + (80, 31, 0, 1), + (80, 255, 0, 1), + (80, 256, 0, 1), + (80, 65535, 0, 1), + (81, 0, 0, 1), + (81, 1, 0, 1), + (81, 2, 0, 1), + (81, 3, 0, 1), + (81, 4, 0, 1), + (81, 5, 0, 1), + (81, 7, 0, 1), + (81, 8, 0, 1), + (81, 15, 0, 1), + (81, 16, 0, 1), + (81, 31, 0, 1), + (81, 255, 0, 1), + (81, 256, 0, 1), + (81, 65535, 0, 1), + (82, 0, 0, 1), + (82, 1, 0, 1), + (82, 2, 0, 1), + (82, 3, 0, 1), + (82, 4, 0, 1), + (82, 5, 0, 1), + (82, 7, 0, 1), + (82, 8, 0, 1), + (82, 15, 0, 1), + (82, 16, 0, 1), + (82, 31, 0, 1), + (82, 255, 0, 1), + (82, 256, 0, 1), + (82, 65535, 0, 1), + (83, 0, 0, 1), + (83, 1, 0, 1), + (83, 2, 0, 1), + (83, 3, 0, 1), + (83, 4, 0, 1), + (83, 5, 0, 1), + (83, 7, 0, 1), + (83, 8, 0, 1), + (83, 15, 0, 1), + (83, 16, 0, 1), + (83, 31, 0, 1), + (83, 255, 0, 1), + (83, 256, 0, 1), + (83, 65535, 0, 1), + (84, 0, 0, 1), + (84, 1, 0, 1), + (84, 2, 0, 1), + (84, 3, 0, 1), + (84, 4, 0, 1), + (84, 5, 0, 1), + (84, 7, 0, 1), + (84, 8, 0, 1), + (84, 15, 0, 1), + (84, 16, 0, 1), + (84, 31, 0, 1), + (84, 255, 0, 1), + (84, 256, 0, 1), + (84, 65535, 0, 1), + (85, 0, 0, 1), + (85, 1, 0, 1), + (85, 2, 0, 1), + (85, 3, 0, 1), + (85, 4, 0, 1), + (85, 5, 0, 1), + (85, 7, 0, 1), + (85, 8, 0, 1), + (85, 15, 0, 1), + (85, 16, 0, 1), + (85, 31, 0, 1), + (85, 255, 0, 1), + (85, 256, 0, 1), + (85, 65535, 0, 1), + (86, 0, 0, 2), + (86, 1, 0, 2), + (86, 2, 0, 2), + (86, 3, 0, 2), + (86, 4, 0, 2), + (86, 5, 0, 2), + (86, 7, 0, 2), + (86, 8, 0, 2), + (86, 15, 0, 2), + (86, 16, 0, 2), + (86, 31, 0, 2), + (86, 255, 0, 2), + (86, 256, 0, 2), + (86, 65535, 0, 2), + (87, 0, 0, 1), + (87, 1, 0, 1), + (87, 2, 0, 1), + (87, 3, 0, 1), + (87, 4, 0, 1), + (87, 5, 0, 1), + (87, 7, 0, 1), + (87, 8, 0, 1), + (87, 15, 0, 1), + (87, 16, 0, 1), + (87, 31, 0, 1), + (87, 255, 0, 1), + (87, 256, 0, 1), + (87, 65535, 0, 1), + (88, 0, 0, 2), + (88, 1, 0, 2), + (88, 2, 0, 2), + (88, 3, 0, 2), + (88, 4, 0, 2), + (88, 5, 0, 2), + (88, 7, 0, 2), + (88, 8, 0, 2), + (88, 15, 0, 2), + (88, 16, 0, 2), + (88, 31, 0, 2), + (88, 255, 0, 2), + (88, 256, 0, 2), + (88, 65535, 0, 2), + (89, 0, 1, 1), + (89, 1, 1, 1), + (89, 2, 1, 1), + (89, 3, 1, 1), + (89, 4, 1, 1), + (89, 5, 1, 1), + (89, 7, 1, 1), + (89, 8, 1, 1), + (89, 15, 1, 1), + (89, 16, 1, 1), + (89, 31, 1, 1), + (89, 255, 1, 1), + (89, 256, 1, 1), + (89, 65535, 1, 1), + (90, 0, 1, 1), + (90, 1, 1, 1), + (90, 2, 1, 1), + (90, 3, 1, 1), + (90, 4, 1, 1), + (90, 5, 1, 1), + (90, 7, 1, 1), + (90, 8, 1, 1), + (90, 15, 1, 1), + (90, 16, 1, 1), + (90, 31, 1, 1), + (90, 255, 1, 1), + (90, 256, 1, 1), + (90, 65535, 1, 1), + (91, 0, 0, 1), + (91, 1, 0, 2), + (91, 2, 0, 1), + (91, 3, 0, 2), + (91, 4, 0, 1), + (91, 5, 0, 2), + (91, 7, 0, 2), + (91, 8, 0, 1), + (91, 15, 0, 2), + (91, 16, 0, 1), + (91, 31, 0, 2), + (91, 255, 0, 2), + (91, 256, 0, 1), + (91, 65535, 0, 2), + (92, 0, 0, 1), + (92, 1, 0, 1), + (92, 2, 0, 1), + (92, 3, 0, 1), + (92, 4, 0, 1), + (92, 5, 0, 1), + (92, 7, 0, 1), + (92, 8, 0, 1), + (92, 15, 0, 1), + (92, 16, 0, 1), + (92, 31, 0, 1), + (92, 255, 0, 1), + (92, 256, 0, 1), + (92, 65535, 0, 1), + (93, 0, 0, 1), + (93, 1, 0, 1), + (93, 2, 0, 1), + (93, 3, 0, 1), + (93, 4, 0, 1), + (93, 5, 0, 1), + (93, 7, 0, 1), + (93, 8, 0, 1), + (93, 15, 0, 1), + (93, 16, 0, 1), + (93, 31, 0, 1), + (93, 255, 0, 1), + (93, 256, 0, 1), + (93, 65535, 0, 1), + (94, 0, 1, 2), + (94, 1, 1, 2), + (94, 2, 1, 2), + (94, 3, 1, 2), + (94, 4, 1, 2), + (94, 5, 1, 2), + (94, 7, 1, 2), + (94, 8, 1, 2), + (94, 15, 1, 2), + (94, 16, 1, 2), + (94, 31, 1, 2), + (94, 255, 1, 2), + (94, 256, 1, 2), + (94, 65535, 1, 2), + (95, 0, 3, 1), + (95, 1, 3, 2), + (95, 2, 3, 1), + (95, 3, 3, 2), + (95, 4, 3, 1), + (95, 5, 3, 2), + (95, 7, 3, 2), + (95, 8, 3, 1), + (95, 15, 3, 2), + (95, 16, 3, 1), + (95, 31, 3, 2), + (95, 255, 3, 2), + (95, 256, 3, 1), + (95, 65535, 3, 2), + (96, 0, 0, 0), + (96, 1, 0, 0), + (96, 2, 0, 0), + (96, 3, 0, 0), + (96, 4, 0, 0), + (96, 5, 0, 0), + (96, 7, 0, 0), + (96, 8, 0, 0), + (96, 15, 0, 0), + (96, 16, 0, 0), + (96, 31, 0, 0), + (96, 255, 0, 0), + (96, 256, 0, 0), + (96, 65535, 0, 0), + (97, 0, 2, 0), + (97, 1, 3, 1), + (97, 2, 4, 2), + (97, 3, 5, 3), + (97, 4, 6, 4), + (97, 5, 7, 5), + (97, 7, 9, 7), + (97, 8, 10, 8), + (97, 15, 17, 15), + (97, 16, 18, 16), + (97, 31, 33, 31), + (97, 255, 257, 255), + (97, 256, 258, 256), + (97, 65535, 65537, 65535), + (98, 0, 3, 1), + (98, 1, 3, 1), + (98, 2, 3, 1), + (98, 3, 3, 1), + (98, 4, 3, 1), + (98, 5, 3, 1), + (98, 7, 3, 1), + (98, 8, 3, 1), + (98, 15, 3, 1), + (98, 16, 3, 1), + (98, 31, 3, 1), + (98, 255, 3, 1), + (98, 256, 3, 1), + (98, 65535, 3, 1), + (99, 0, 1, 0), + (99, 1, 1, 0), + (99, 2, 1, 0), + (99, 3, 1, 0), + (99, 4, 1, 0), + (99, 5, 1, 0), + (99, 7, 1, 0), + (99, 8, 1, 0), + (99, 15, 1, 0), + (99, 16, 1, 0), + (99, 31, 1, 0), + (99, 255, 1, 0), + (99, 256, 1, 0), + (99, 65535, 1, 0), + (100, 0, 1, 0), + (100, 1, 1, 0), + (100, 2, 1, 0), + (100, 3, 1, 0), + (100, 4, 1, 0), + (100, 5, 1, 0), + (100, 7, 1, 0), + (100, 8, 1, 0), + (100, 15, 1, 0), + (100, 16, 1, 0), + (100, 31, 1, 0), + (100, 255, 1, 0), + (100, 256, 1, 0), + (100, 65535, 1, 0), + (101, 0, 1, 0), + (101, 1, 1, 0), + (101, 2, 1, 0), + (101, 3, 1, 0), + (101, 4, 1, 0), + (101, 5, 1, 0), + (101, 7, 1, 0), + (101, 8, 1, 0), + (101, 15, 1, 0), + (101, 16, 1, 0), + (101, 31, 1, 0), + (101, 255, 1, 0), + (101, 256, 1, 0), + (101, 65535, 1, 0), + (102, 0, 1, 0), + (102, 1, 1, 0), + (102, 2, 1, 0), + (102, 3, 1, 0), + (102, 4, 1, 0), + (102, 5, 1, 0), + (102, 7, 1, 0), + (102, 8, 1, 0), + (102, 15, 1, 0), + (102, 16, 1, 0), + (102, 31, 1, 0), + (102, 255, 1, 0), + (102, 256, 1, 0), + (102, 65535, 1, 0), + (103, 0, 0, 0), + (103, 1, 1, 0), + (103, 2, 2, 0), + (103, 3, 3, 0), + (103, 4, 4, 0), + (103, 5, 5, 0), + (103, 7, 7, 0), + (103, 8, 8, 0), + (103, 15, 15, 0), + (103, 16, 16, 0), + (103, 31, 31, 0), + (103, 255, 255, 0), + (103, 256, 256, 0), + (103, 65535, 65535, 0), + (104, 0, 1, 0), + (104, 1, 2, 1), + (104, 2, 3, 2), + (104, 3, 4, 3), + (104, 4, 5, 4), + (104, 5, 6, 5), + (104, 7, 8, 7), + (104, 8, 9, 8), + (104, 15, 16, 15), + (104, 16, 17, 16), + (104, 31, 32, 31), + (104, 255, 256, 255), + (104, 256, 257, 256), + (104, 65535, 65536, 65535), + (105, 0, 3, 3), + (105, 1, 3, 3), + (105, 2, 3, 3), + (105, 3, 3, 3), + (105, 4, 3, 3), + (105, 5, 3, 3), + (105, 7, 3, 3), + (105, 8, 3, 3), + (105, 15, 3, 3), + (105, 16, 3, 3), + (105, 31, 3, 3), + (105, 255, 3, 3), + (105, 256, 3, 3), + (105, 65535, 3, 3), + (106, 0, 1, 0), + (106, 1, 2, 1), + (106, 2, 3, 2), + (106, 3, 4, 3), + (106, 4, 5, 4), + (106, 5, 6, 5), + (106, 7, 8, 7), + (106, 8, 9, 8), + (106, 15, 16, 15), + (106, 16, 17, 16), + (106, 31, 32, 31), + (106, 255, 256, 255), + (106, 256, 257, 256), + (106, 65535, 65536, 65535), + (107, 0, 2, 1), + (107, 1, 2, 1), + (107, 2, 2, 1), + (107, 3, 2, 1), + (107, 4, 2, 1), + (107, 5, 2, 1), + (107, 7, 2, 1), + (107, 8, 2, 1), + (107, 15, 2, 1), + (107, 16, 2, 1), + (107, 31, 2, 1), + (107, 255, 2, 1), + (107, 256, 2, 1), + (107, 65535, 2, 1), + (108, 0, 1, 0), + (108, 1, 2, 1), + (108, 2, 3, 2), + (108, 3, 4, 3), + (108, 4, 5, 4), + (108, 5, 6, 5), + (108, 7, 8, 7), + (108, 8, 9, 8), + (108, 15, 16, 15), + (108, 16, 17, 16), + (108, 31, 32, 31), + (108, 255, 256, 255), + (108, 256, 257, 256), + (108, 65535, 65536, 65535), + (109, 0, 2, 0), + (109, 1, 2, 0), + (109, 2, 2, 0), + (109, 3, 2, 0), + (109, 4, 2, 0), + (109, 5, 2, 0), + (109, 7, 2, 0), + (109, 8, 2, 0), + (109, 15, 2, 0), + (109, 16, 2, 0), + (109, 31, 2, 0), + (109, 255, 2, 0), + (109, 256, 2, 0), + (109, 65535, 2, 0), + (110, 0, 1, 0), + (110, 1, 1, 0), + (110, 2, 1, 0), + (110, 3, 1, 0), + (110, 4, 1, 0), + (110, 5, 1, 0), + (110, 7, 1, 0), + (110, 8, 1, 0), + (110, 15, 1, 0), + (110, 16, 1, 0), + (110, 31, 1, 0), + (110, 255, 1, 0), + (110, 256, 1, 0), + (110, 65535, 1, 0), + (111, 0, 1, 0), + (111, 1, 1, 0), + (111, 2, 1, 0), + (111, 3, 1, 0), + (111, 4, 1, 0), + (111, 5, 1, 0), + (111, 7, 1, 0), + (111, 8, 1, 0), + (111, 15, 1, 0), + (111, 16, 1, 0), + (111, 31, 1, 0), + (111, 255, 1, 0), + (111, 256, 1, 0), + (111, 65535, 1, 0), + (112, 0, 1, 1), + (112, 1, 1, 1), + (112, 2, 1, 1), + (112, 3, 1, 1), + (112, 4, 1, 1), + (112, 5, 1, 1), + (112, 7, 1, 1), + (112, 8, 1, 1), + (112, 15, 1, 1), + (112, 16, 1, 1), + (112, 31, 1, 1), + (112, 255, 1, 1), + (112, 256, 1, 1), + (112, 65535, 1, 1), + (113, 0, 2, 0), + (113, 1, 2, 0), + (113, 2, 2, 0), + (113, 3, 2, 0), + (113, 4, 2, 0), + (113, 5, 2, 0), + (113, 7, 2, 0), + (113, 8, 2, 0), + (113, 15, 2, 0), + (113, 16, 2, 0), + (113, 31, 2, 0), + (113, 255, 2, 0), + (113, 256, 2, 0), + (113, 65535, 2, 0), + (114, 0, 1, 0), + (114, 1, 1, 0), + (114, 2, 1, 0), + (114, 3, 1, 0), + (114, 4, 1, 0), + (114, 5, 1, 0), + (114, 7, 1, 0), + (114, 8, 1, 0), + (114, 15, 1, 0), + (114, 16, 1, 0), + (114, 31, 1, 0), + (114, 255, 1, 0), + (114, 256, 1, 0), + (114, 65535, 1, 0), + (115, 0, 1, 0), + (115, 1, 1, 0), + (115, 2, 1, 0), + (115, 3, 1, 0), + (115, 4, 1, 0), + (115, 5, 1, 0), + (115, 7, 1, 0), + (115, 8, 1, 0), + (115, 15, 1, 0), + (115, 16, 1, 0), + (115, 31, 1, 0), + (115, 255, 1, 0), + (115, 256, 1, 0), + (115, 65535, 1, 0), + (116, 0, 0, 0), + (116, 1, 1, 1), + (116, 2, 2, 2), + (116, 3, 3, 3), + (116, 4, 4, 4), + (116, 5, 5, 5), + (116, 7, 7, 7), + (116, 8, 8, 8), + (116, 15, 15, 15), + (116, 16, 16, 16), + (116, 31, 31, 31), + (116, 255, 255, 255), + (116, 256, 256, 256), + (116, 65535, 65535, 65535), + (117, 0, 1, 1), + (117, 1, 1, 2), + (117, 2, 1, 3), + (117, 3, 1, 4), + (117, 4, 1, 5), + (117, 5, 1, 6), + (117, 7, 1, 8), + (117, 8, 1, 9), + (117, 15, 1, 16), + (117, 16, 1, 17), + (117, 31, 1, 32), + (117, 255, 1, 256), + (117, 256, 1, 2), + (117, 65535, 1, 511), + (118, 0, 1, 0), + (118, 1, 1, 1), + (118, 2, 1, 2), + (118, 3, 1, 3), + (118, 4, 1, 4), + (118, 5, 1, 5), + (118, 7, 1, 7), + (118, 8, 1, 8), + (118, 15, 1, 15), + (118, 16, 1, 16), + (118, 31, 1, 31), + (118, 255, 1, 255), + (118, 256, 1, 256), + (118, 65535, 1, 65535), + (119, 0, 1, 1), + (119, 1, 1, 1), + (119, 2, 1, 1), + (119, 3, 1, 1), + (119, 4, 1, 1), + (119, 5, 1, 1), + (119, 7, 1, 1), + (119, 8, 1, 1), + (119, 15, 1, 1), + (119, 16, 1, 1), + (119, 31, 1, 1), + (119, 255, 1, 1), + (119, 256, 1, 1), + (119, 65535, 1, 1), + (128, 0, 0, 0), + (128, 1, 0, 0), + (128, 2, 0, 0), + (128, 3, 0, 0), + (128, 4, 0, 0), + (128, 5, 0, 0), + (128, 7, 0, 0), + (128, 8, 0, 0), + (128, 15, 0, 0), + (128, 16, 0, 0), + (128, 31, 0, 0), + (128, 255, 0, 0), + (128, 256, 0, 0), + (128, 65535, 0, 0), + (129, 0, 2, 1), + (129, 1, 2, 1), + (129, 2, 2, 1), + (129, 3, 2, 1), + (129, 4, 2, 1), + (129, 5, 2, 1), + (129, 7, 2, 1), + (129, 8, 2, 1), + (129, 15, 2, 1), + (129, 16, 2, 1), + (129, 31, 2, 1), + (129, 255, 2, 1), + (129, 256, 2, 1), + (129, 65535, 2, 1), + (130, 0, 2, 1), + (130, 1, 2, 1), + (130, 2, 2, 1), + (130, 3, 2, 1), + (130, 4, 2, 1), + (130, 5, 2, 1), + (130, 7, 2, 1), + (130, 8, 2, 1), + (130, 15, 2, 1), + (130, 16, 2, 1), + (130, 31, 2, 1), + (130, 255, 2, 1), + (130, 256, 2, 1), + (130, 65535, 2, 1), + (131, 0, 2, 1), + (131, 1, 2, 1), + (131, 2, 2, 1), + (131, 3, 2, 1), + (131, 4, 2, 1), + (131, 5, 2, 1), + (131, 7, 2, 1), + (131, 8, 2, 1), + (131, 15, 2, 1), + (131, 16, 2, 1), + (131, 31, 2, 1), + (131, 255, 2, 1), + (131, 256, 2, 1), + (131, 65535, 2, 1), + (132, 0, 2, 1), + (132, 1, 2, 1), + (132, 2, 2, 1), + (132, 3, 2, 1), + (132, 4, 2, 1), + (132, 5, 2, 1), + (132, 7, 2, 1), + (132, 8, 2, 1), + (132, 15, 2, 1), + (132, 16, 2, 1), + (132, 31, 2, 1), + (132, 255, 2, 1), + (132, 256, 2, 1), + (132, 65535, 2, 1), + (133, 0, 2, 1), + (133, 1, 2, 1), + (133, 2, 2, 1), + (133, 3, 2, 1), + (133, 4, 2, 1), + (133, 5, 2, 1), + (133, 7, 2, 1), + (133, 8, 2, 1), + (133, 15, 2, 1), + (133, 16, 2, 1), + (133, 31, 2, 1), + (133, 255, 2, 1), + (133, 256, 2, 1), + (133, 65535, 2, 1), + (134, 0, 2, 1), + (134, 1, 2, 1), + (134, 2, 2, 1), + (134, 3, 2, 1), + (134, 4, 2, 1), + (134, 5, 2, 1), + (134, 7, 2, 1), + (134, 8, 2, 1), + (134, 15, 2, 1), + (134, 16, 2, 1), + (134, 31, 2, 1), + (134, 255, 2, 1), + (134, 256, 2, 1), + (134, 65535, 2, 1), + (135, 0, 2, 1), + (135, 1, 2, 1), + (135, 2, 2, 1), + (135, 3, 2, 1), + (135, 4, 2, 1), + (135, 5, 2, 1), + (135, 7, 2, 1), + (135, 8, 2, 1), + (135, 15, 2, 1), + (135, 16, 2, 1), + (135, 31, 2, 1), + (135, 255, 2, 1), + (135, 256, 2, 1), + (135, 65535, 2, 1), + (136, 0, 2, 0), + (136, 1, 2, 0), + (136, 2, 2, 0), + (136, 3, 2, 0), + (136, 4, 2, 0), + (136, 5, 2, 0), + (136, 7, 2, 0), + (136, 8, 2, 0), + (136, 15, 2, 0), + (136, 16, 2, 0), + (136, 31, 2, 0), + (136, 255, 2, 0), + (136, 256, 2, 0), + (136, 65535, 2, 0), + (137, 0, 2, 1), + (137, 1, 2, 1), + (137, 2, 2, 1), + (137, 3, 2, 1), + (137, 4, 2, 1), + (137, 5, 2, 1), + (137, 7, 2, 1), + (137, 8, 2, 1), + (137, 15, 2, 1), + (137, 16, 2, 1), + (137, 31, 2, 1), + (137, 255, 2, 1), + (137, 256, 2, 1), + (137, 65535, 2, 1), + (138, 0, 2, 1), + (138, 1, 2, 1), + (138, 2, 2, 1), + (138, 3, 2, 1), + (138, 4, 2, 1), + (138, 5, 2, 1), + (138, 7, 2, 1), + (138, 8, 2, 1), + (138, 15, 2, 1), + (138, 16, 2, 1), + (138, 31, 2, 1), + (138, 255, 2, 1), + (138, 256, 2, 1), + (138, 65535, 2, 1), + (139, 0, 2, 1), + (139, 1, 2, 1), + (139, 2, 2, 1), + (139, 3, 2, 1), + (139, 4, 2, 1), + (139, 5, 2, 1), + (139, 7, 2, 1), + (139, 8, 2, 1), + (139, 15, 2, 1), + (139, 16, 2, 1), + (139, 31, 2, 1), + (139, 255, 2, 1), + (139, 256, 2, 1), + (139, 65535, 2, 1), + (140, 0, 2, 1), + (140, 1, 2, 1), + (140, 2, 2, 1), + (140, 3, 2, 1), + (140, 4, 2, 1), + (140, 5, 2, 1), + (140, 7, 2, 1), + (140, 8, 2, 1), + (140, 15, 2, 1), + (140, 16, 2, 1), + (140, 31, 2, 1), + (140, 255, 2, 1), + (140, 256, 2, 1), + (140, 65535, 2, 1), + (141, 0, 2, 1), + (141, 1, 2, 1), + (141, 2, 2, 1), + (141, 3, 2, 1), + (141, 4, 2, 1), + (141, 5, 2, 1), + (141, 7, 2, 1), + (141, 8, 2, 1), + (141, 15, 2, 1), + (141, 16, 2, 1), + (141, 31, 2, 1), + (141, 255, 2, 1), + (141, 256, 2, 1), + (141, 65535, 2, 1), + (142, 0, 2, 1), + (142, 1, 2, 1), + (142, 2, 2, 1), + (142, 3, 2, 1), + (142, 4, 2, 1), + (142, 5, 2, 1), + (142, 7, 2, 1), + (142, 8, 2, 1), + (142, 15, 2, 1), + (142, 16, 2, 1), + (142, 31, 2, 1), + (142, 255, 2, 1), + (142, 256, 2, 1), + (142, 65535, 2, 1), + (143, 0, 2, 1), + (143, 1, 2, 1), + (143, 2, 2, 1), + (143, 3, 2, 1), + (143, 4, 2, 1), + (143, 5, 2, 1), + (143, 7, 2, 1), + (143, 8, 2, 1), + (143, 15, 2, 1), + (143, 16, 2, 1), + (143, 31, 2, 1), + (143, 255, 2, 1), + (143, 256, 2, 1), + (143, 65535, 2, 1), + (144, 0, 2, 0), + (144, 1, 3, 0), + (144, 2, 4, 0), + (144, 3, 5, 0), + (144, 4, 6, 0), + (144, 5, 7, 0), + (144, 7, 9, 0), + (144, 8, 10, 0), + (144, 15, 17, 0), + (144, 16, 18, 0), + (144, 31, 33, 0), + (144, 255, 257, 0), + (144, 256, 258, 0), + (144, 65535, 65537, 0), + (145, 0, 2, 0), + (145, 1, 3, 0), + (145, 2, 4, 0), + (145, 3, 5, 0), + (145, 4, 6, 0), + (145, 5, 7, 0), + (145, 7, 9, 0), + (145, 8, 10, 0), + (145, 15, 17, 0), + (145, 16, 18, 0), + (145, 31, 33, 0), + (145, 255, 257, 0), + (145, 256, 258, 0), + (145, 65535, 65537, 0), + (146, 0, 2, 0), + (146, 1, 3, 0), + (146, 2, 4, 0), + (146, 3, 5, 0), + (146, 4, 6, 0), + (146, 5, 7, 0), + (146, 7, 9, 0), + (146, 8, 10, 0), + (146, 15, 17, 0), + (146, 16, 18, 0), + (146, 31, 33, 0), + (146, 255, 257, 0), + (146, 256, 258, 0), + (146, 65535, 65537, 0), + (147, 0, 2, 1), + (147, 1, 3, 1), + (147, 2, 4, 1), + (147, 3, 5, 1), + (147, 4, 6, 1), + (147, 5, 7, 1), + (147, 7, 9, 1), + (147, 8, 10, 1), + (147, 15, 17, 1), + (147, 16, 18, 1), + (147, 31, 33, 1), + (147, 255, 257, 1), + (147, 256, 258, 1), + (147, 65535, 65537, 1), + (148, 0, 2, 1), + (148, 1, 3, 1), + (148, 2, 4, 1), + (148, 3, 5, 1), + (148, 4, 6, 1), + (148, 5, 7, 1), + (148, 7, 9, 1), + (148, 8, 10, 1), + (148, 15, 17, 1), + (148, 16, 18, 1), + (148, 31, 33, 1), + (148, 255, 257, 1), + (148, 256, 258, 1), + (148, 65535, 65537, 1), + (149, 0, 2, 1), + (149, 1, 3, 1), + (149, 2, 4, 1), + (149, 3, 5, 1), + (149, 4, 6, 1), + (149, 5, 7, 1), + (149, 7, 9, 1), + (149, 8, 10, 1), + (149, 15, 17, 1), + (149, 16, 18, 1), + (149, 31, 33, 1), + (149, 255, 257, 1), + (149, 256, 258, 1), + (149, 65535, 65537, 1), + (150, 0, 2, 1), + (150, 1, 3, 1), + (150, 2, 4, 1), + (150, 3, 5, 1), + (150, 4, 6, 1), + (150, 5, 7, 1), + (150, 7, 9, 1), + (150, 8, 10, 1), + (150, 15, 17, 1), + (150, 16, 18, 1), + (150, 31, 33, 1), + (150, 255, 257, 1), + (150, 256, 258, 1), + (150, 65535, 65537, 1), + (151, 0, 4, 1), + (151, 1, 4, 1), + (151, 2, 4, 1), + (151, 3, 4, 1), + (151, 4, 4, 1), + (151, 5, 4, 1), + (151, 7, 4, 1), + (151, 8, 4, 1), + (151, 15, 4, 1), + (151, 16, 4, 1), + (151, 31, 4, 1), + (151, 255, 4, 1), + (151, 256, 4, 1), + (151, 65535, 4, 1), + (152, 0, 4, 0), + (152, 1, 4, 0), + (152, 2, 4, 0), + (152, 3, 4, 0), + (152, 4, 4, 0), + (152, 5, 4, 0), + (152, 7, 4, 0), + (152, 8, 4, 0), + (152, 15, 4, 0), + (152, 16, 4, 0), + (152, 31, 4, 0), + (152, 255, 4, 0), + (152, 256, 4, 0), + (152, 65535, 4, 0), + (153, 0, 4, 1), + (153, 1, 4, 1), + (153, 2, 4, 1), + (153, 3, 4, 1), + (153, 4, 4, 1), + (153, 5, 4, 1), + (153, 7, 4, 1), + (153, 8, 4, 1), + (153, 15, 4, 1), + (153, 16, 4, 1), + (153, 31, 4, 1), + (153, 255, 4, 1), + (153, 256, 4, 1), + (153, 65535, 4, 1), + (154, 0, 3, 0), + (154, 1, 4, 0), + (154, 2, 5, 0), + (154, 3, 6, 0), + (154, 4, 7, 0), + (154, 5, 8, 0), + (154, 7, 10, 0), + (154, 8, 11, 0), + (154, 15, 18, 0), + (154, 16, 19, 0), + (154, 31, 34, 0), + (154, 255, 258, 0), + (154, 256, 259, 0), + (154, 65535, 65538, 0), + (155, 0, 3, 1), + (155, 1, 4, 1), + (155, 2, 5, 1), + (155, 3, 6, 1), + (155, 4, 7, 1), + (155, 5, 8, 1), + (155, 7, 10, 1), + (155, 8, 11, 1), + (155, 15, 18, 1), + (155, 16, 19, 1), + (155, 31, 34, 1), + (155, 255, 258, 1), + (155, 256, 259, 1), + (155, 65535, 65538, 1), + (156, 0, 3, 0), + (156, 1, 4, 0), + (156, 2, 5, 0), + (156, 3, 6, 0), + (156, 4, 7, 0), + (156, 5, 8, 0), + (156, 7, 10, 0), + (156, 8, 11, 0), + (156, 15, 18, 0), + (156, 16, 19, 0), + (156, 31, 34, 0), + (156, 255, 258, 0), + (156, 256, 259, 0), + (156, 65535, 65538, 0), + (157, 0, 3, 1), + (157, 1, 3, 1), + (157, 2, 3, 1), + (157, 3, 3, 1), + (157, 4, 3, 1), + (157, 5, 3, 1), + (157, 7, 3, 1), + (157, 8, 3, 1), + (157, 15, 3, 1), + (157, 16, 3, 1), + (157, 31, 3, 1), + (157, 255, 3, 1), + (157, 256, 3, 1), + (157, 65535, 3, 1), + (158, 0, 3, 1), + (158, 1, 3, 1), + (158, 2, 3, 1), + (158, 3, 3, 1), + (158, 4, 3, 1), + (158, 5, 3, 1), + (158, 7, 3, 1), + (158, 8, 3, 1), + (158, 15, 3, 1), + (158, 16, 3, 1), + (158, 31, 3, 1), + (158, 255, 3, 1), + (158, 256, 3, 1), + (158, 65535, 3, 1), + (159, 0, 2, 1), + (159, 1, 3, 1), + (159, 2, 4, 1), + (159, 3, 5, 1), + (159, 4, 6, 1), + (159, 5, 7, 1), + (159, 7, 9, 1), + (159, 8, 10, 1), + (159, 15, 17, 1), + (159, 16, 18, 1), + (159, 31, 33, 1), + (159, 255, 257, 1), + (159, 256, 258, 1), + (159, 65535, 65537, 1), + (160, 0, 2, 1), + (160, 1, 3, 1), + (160, 2, 4, 1), + (160, 3, 5, 1), + (160, 4, 6, 1), + (160, 5, 7, 1), + (160, 7, 9, 1), + (160, 8, 10, 1), + (160, 15, 17, 1), + (160, 16, 18, 1), + (160, 31, 33, 1), + (160, 255, 257, 1), + (160, 256, 258, 1), + (160, 65535, 65537, 1), + (161, 0, 2, 1), + (161, 1, 3, 1), + (161, 2, 4, 1), + (161, 3, 5, 1), + (161, 4, 6, 1), + (161, 5, 7, 1), + (161, 7, 9, 1), + (161, 8, 10, 1), + (161, 15, 17, 1), + (161, 16, 18, 1), + (161, 31, 33, 1), + (161, 255, 257, 1), + (161, 256, 258, 1), + (161, 65535, 65537, 1), + (162, 0, 2, 1), + (162, 1, 3, 1), + (162, 2, 4, 1), + (162, 3, 5, 1), + (162, 4, 6, 1), + (162, 5, 7, 1), + (162, 7, 9, 1), + (162, 8, 10, 1), + (162, 15, 17, 1), + (162, 16, 18, 1), + (162, 31, 33, 1), + (162, 255, 257, 1), + (162, 256, 258, 1), + (162, 65535, 65537, 1), + (163, 0, 2, 1), + (163, 1, 3, 1), + (163, 2, 4, 1), + (163, 3, 5, 1), + (163, 4, 6, 1), + (163, 5, 7, 1), + (163, 7, 9, 1), + (163, 8, 10, 1), + (163, 15, 17, 1), + (163, 16, 18, 1), + (163, 31, 33, 1), + (163, 255, 257, 1), + (163, 256, 258, 1), + (163, 65535, 65537, 1), + (164, 0, 2, 0), + (164, 1, 3, 0), + (164, 2, 4, 0), + (164, 3, 5, 0), + (164, 4, 6, 0), + (164, 5, 7, 0), + (164, 7, 9, 0), + (164, 8, 10, 0), + (164, 15, 17, 0), + (164, 16, 18, 0), + (164, 31, 33, 0), + (164, 255, 257, 0), + (164, 256, 258, 0), + (164, 65535, 65537, 0), + (165, 0, 2, 0), + (165, 1, 3, 0), + (165, 2, 4, 0), + (165, 3, 5, 0), + (165, 4, 6, 0), + (165, 5, 7, 0), + (165, 7, 9, 0), + (165, 8, 10, 0), + (165, 15, 17, 0), + (165, 16, 18, 0), + (165, 31, 33, 0), + (165, 255, 257, 0), + (165, 256, 258, 0), + (165, 65535, 65537, 0), + (166, 0, 3, 1), + (166, 1, 3, 1), + (166, 2, 3, 1), + (166, 3, 3, 1), + (166, 4, 3, 1), + (166, 5, 3, 1), + (166, 7, 3, 1), + (166, 8, 3, 1), + (166, 15, 3, 1), + (166, 16, 3, 1), + (166, 31, 3, 1), + (166, 255, 3, 1), + (166, 256, 3, 1), + (166, 65535, 3, 1), + (167, 0, 3, 1), + (167, 1, 3, 1), + (167, 2, 3, 1), + (167, 3, 3, 1), + (167, 4, 3, 1), + (167, 5, 3, 1), + (167, 7, 3, 1), + (167, 8, 3, 1), + (167, 15, 3, 1), + (167, 16, 3, 1), + (167, 31, 3, 1), + (167, 255, 3, 1), + (167, 256, 3, 1), + (167, 65535, 3, 1), + (168, 0, 3, 1), + (168, 1, 3, 1), + (168, 2, 3, 1), + (168, 3, 3, 1), + (168, 4, 3, 1), + (168, 5, 3, 1), + (168, 7, 3, 1), + (168, 8, 3, 1), + (168, 15, 3, 1), + (168, 16, 3, 1), + (168, 31, 3, 1), + (168, 255, 3, 1), + (168, 256, 3, 1), + (168, 65535, 3, 1), + (169, 0, 2, 1), + (169, 1, 2, 1), + (169, 2, 2, 1), + (169, 3, 2, 1), + (169, 4, 2, 1), + (169, 5, 2, 1), + (169, 7, 2, 1), + (169, 8, 2, 1), + (169, 15, 2, 1), + (169, 16, 2, 1), + (169, 31, 2, 1), + (169, 255, 2, 1), + (169, 256, 2, 1), + (169, 65535, 2, 1), + (170, 0, 2, 1), + (170, 1, 2, 1), + (170, 2, 2, 1), + (170, 3, 2, 1), + (170, 4, 2, 1), + (170, 5, 2, 1), + (170, 7, 2, 1), + (170, 8, 2, 1), + (170, 15, 2, 1), + (170, 16, 2, 1), + (170, 31, 2, 1), + (170, 255, 2, 1), + (170, 256, 2, 1), + (170, 65535, 2, 1), + (171, 0, 2, 1), + (171, 1, 2, 1), + (171, 2, 2, 1), + (171, 3, 2, 1), + (171, 4, 2, 1), + (171, 5, 2, 1), + (171, 7, 2, 1), + (171, 8, 2, 1), + (171, 15, 2, 1), + (171, 16, 2, 1), + (171, 31, 2, 1), + (171, 255, 2, 1), + (171, 256, 2, 1), + (171, 65535, 2, 1), + (172, 0, 2, 1), + (172, 1, 2, 1), + (172, 2, 2, 1), + (172, 3, 2, 1), + (172, 4, 2, 1), + (172, 5, 2, 1), + (172, 7, 2, 1), + (172, 8, 2, 1), + (172, 15, 2, 1), + (172, 16, 2, 1), + (172, 31, 2, 1), + (172, 255, 2, 1), + (172, 256, 2, 1), + (172, 65535, 2, 1), + (173, 0, 2, 1), + (173, 1, 2, 1), + (173, 2, 2, 1), + (173, 3, 2, 1), + (173, 4, 2, 1), + (173, 5, 2, 1), + (173, 7, 2, 1), + (173, 8, 2, 1), + (173, 15, 2, 1), + (173, 16, 2, 1), + (173, 31, 2, 1), + (173, 255, 2, 1), + (173, 256, 2, 1), + (173, 65535, 2, 1), + (174, 0, 2, 2), + (174, 1, 2, 2), + (174, 2, 2, 2), + (174, 3, 2, 2), + (174, 4, 2, 2), + (174, 5, 2, 2), + (174, 7, 2, 2), + (174, 8, 2, 2), + (174, 15, 2, 2), + (174, 16, 2, 2), + (174, 31, 2, 2), + (174, 255, 2, 2), + (174, 256, 2, 2), + (174, 65535, 2, 2), + (175, 0, 2, 3), + (175, 1, 2, 3), + (175, 2, 2, 3), + (175, 3, 2, 3), + (175, 4, 2, 3), + (175, 5, 2, 3), + (175, 7, 2, 3), + (175, 8, 2, 3), + (175, 15, 2, 3), + (175, 16, 2, 3), + (175, 31, 2, 3), + (175, 255, 2, 3), + (175, 256, 2, 3), + (175, 65535, 2, 3), + (176, 0, 2, 3), + (176, 1, 2, 3), + (176, 2, 2, 3), + (176, 3, 2, 3), + (176, 4, 2, 3), + (176, 5, 2, 3), + (176, 7, 2, 3), + (176, 8, 2, 3), + (176, 15, 2, 3), + (176, 16, 2, 3), + (176, 31, 2, 3), + (176, 255, 2, 3), + (176, 256, 2, 3), + (176, 65535, 2, 3), + (177, 0, 2, 3), + (177, 1, 2, 3), + (177, 2, 2, 3), + (177, 3, 2, 3), + (177, 4, 2, 3), + (177, 5, 2, 3), + (177, 7, 2, 3), + (177, 8, 2, 3), + (177, 15, 2, 3), + (177, 16, 2, 3), + (177, 31, 2, 3), + (177, 255, 2, 3), + (177, 256, 2, 3), + (177, 65535, 2, 3), + (178, 0, 2, 3), + (178, 1, 2, 3), + (178, 2, 2, 3), + (178, 3, 2, 3), + (178, 4, 2, 3), + (178, 5, 2, 3), + (178, 7, 2, 3), + (178, 8, 2, 3), + (178, 15, 2, 3), + (178, 16, 2, 3), + (178, 31, 2, 3), + (178, 255, 2, 3), + (178, 256, 2, 3), + (178, 65535, 2, 3), + (179, 0, 1, 2), + (179, 1, 1, 2), + (179, 2, 1, 2), + (179, 3, 1, 2), + (179, 4, 1, 2), + (179, 5, 1, 2), + (179, 7, 1, 2), + (179, 8, 1, 2), + (179, 15, 1, 2), + (179, 16, 1, 2), + (179, 31, 1, 2), + (179, 255, 1, 2), + (179, 256, 1, 2), + (179, 65535, 1, 2), + (180, 0, 1, 2), + (180, 1, 1, 2), + (180, 2, 1, 2), + (180, 3, 1, 2), + (180, 4, 1, 2), + (180, 5, 1, 2), + (180, 7, 1, 2), + (180, 8, 1, 2), + (180, 15, 1, 2), + (180, 16, 1, 2), + (180, 31, 1, 2), + (180, 255, 1, 2), + (180, 256, 1, 2), + (180, 65535, 1, 2), + (181, 0, 0, 0), + (181, 1, 0, 0), + (181, 2, 0, 0), + (181, 3, 0, 0), + (181, 4, 0, 0), + (181, 5, 0, 0), + (181, 7, 0, 0), + (181, 8, 0, 0), + (181, 15, 0, 0), + (181, 16, 0, 0), + (181, 31, 0, 0), + (181, 255, 0, 0), + (181, 256, 0, 0), + (181, 65535, 0, 0), + (182, 0, 0, 0), + (182, 1, 0, 0), + (182, 2, 0, 0), + (182, 3, 0, 0), + (182, 4, 0, 0), + (182, 5, 0, 0), + (182, 7, 0, 0), + (182, 8, 0, 0), + (182, 15, 0, 0), + (182, 16, 0, 0), + (182, 31, 0, 0), + (182, 255, 0, 0), + (182, 256, 0, 0), + (182, 65535, 0, 0), + (183, 0, 1, 1), + (183, 1, 1, 2), + (183, 2, 1, 1), + (183, 3, 1, 2), + (183, 4, 1, 1), + (183, 5, 1, 2), + (183, 7, 1, 2), + (183, 8, 1, 1), + (183, 15, 1, 2), + (183, 16, 1, 1), + (183, 31, 1, 2), + (183, 255, 1, 2), + (183, 256, 1, 1), + (183, 65535, 1, 2), + (184, 0, 1, 1), + (184, 1, 1, 2), + (184, 2, 1, 1), + (184, 3, 1, 2), + (184, 4, 1, 1), + (184, 5, 1, 2), + (184, 7, 1, 2), + (184, 8, 1, 1), + (184, 15, 1, 2), + (184, 16, 1, 1), + (184, 31, 1, 2), + (184, 255, 1, 2), + (184, 256, 1, 1), + (184, 65535, 1, 2), + (185, 0, 1, 0), + (185, 1, 1, 0), + (185, 2, 1, 0), + (185, 3, 1, 0), + (185, 4, 1, 0), + (185, 5, 1, 0), + (185, 7, 1, 0), + (185, 8, 1, 0), + (185, 15, 1, 0), + (185, 16, 1, 0), + (185, 31, 1, 0), + (185, 255, 1, 0), + (185, 256, 1, 0), + (185, 65535, 1, 0), + (186, 0, 1, 1), + (186, 1, 1, 2), + (186, 2, 1, 1), + (186, 3, 1, 2), + (186, 4, 1, 1), + (186, 5, 1, 2), + (186, 7, 1, 2), + (186, 8, 1, 1), + (186, 15, 1, 2), + (186, 16, 1, 1), + (186, 31, 1, 2), + (186, 255, 1, 2), + (186, 256, 1, 1), + (186, 65535, 1, 2), + (187, 0, 1, 2), + (187, 1, 1, 2), + (187, 2, 1, 2), + (187, 3, 1, 2), + (187, 4, 1, 2), + (187, 5, 1, 2), + (187, 7, 1, 2), + (187, 8, 1, 2), + (187, 15, 1, 2), + (187, 16, 1, 2), + (187, 31, 1, 2), + (187, 255, 1, 2), + (187, 256, 1, 2), + (187, 65535, 1, 2), + (188, 0, 1, 2), + (188, 1, 1, 2), + (188, 2, 1, 2), + (188, 3, 1, 2), + (188, 4, 1, 2), + (188, 5, 1, 2), + (188, 7, 1, 2), + (188, 8, 1, 2), + (188, 15, 1, 2), + (188, 16, 1, 2), + (188, 31, 1, 2), + (188, 255, 1, 2), + (188, 256, 1, 2), + (188, 65535, 1, 2), + (189, 0, 1, 2), + (189, 1, 1, 2), + (189, 2, 1, 2), + (189, 3, 1, 2), + (189, 4, 1, 2), + (189, 5, 1, 2), + (189, 7, 1, 2), + (189, 8, 1, 2), + (189, 15, 1, 2), + (189, 16, 1, 2), + (189, 31, 1, 2), + (189, 255, 1, 2), + (189, 256, 1, 2), + (189, 65535, 1, 2), + (190, 0, 1, 1), + (190, 1, 1, 2), + (190, 2, 1, 1), + (190, 3, 1, 2), + (190, 4, 1, 1), + (190, 5, 1, 2), + (190, 7, 1, 2), + (190, 8, 1, 1), + (190, 15, 1, 2), + (190, 16, 1, 1), + (190, 31, 1, 2), + (190, 255, 1, 2), + (190, 256, 1, 1), + (190, 65535, 1, 2), + (191, 0, 1, 1), + (191, 1, 1, 1), + (191, 2, 1, 1), + (191, 3, 1, 1), + (191, 4, 1, 1), + (191, 5, 1, 1), + (191, 7, 1, 1), + (191, 8, 1, 1), + (191, 15, 1, 1), + (191, 16, 1, 1), + (191, 31, 1, 1), + (191, 255, 1, 1), + (191, 256, 1, 1), + (191, 65535, 1, 1), + (192, 0, 1, 1), + (192, 1, 1, 1), + (192, 2, 1, 1), + (192, 3, 1, 1), + (192, 4, 1, 1), + (192, 5, 1, 1), + (192, 7, 1, 1), + (192, 8, 1, 1), + (192, 15, 1, 1), + (192, 16, 1, 1), + (192, 31, 1, 1), + (192, 255, 1, 1), + (192, 256, 1, 1), + (192, 65535, 1, 1), + (193, 0, 1, 0), + (193, 1, 1, 0), + (193, 2, 1, 0), + (193, 3, 1, 0), + (193, 4, 1, 0), + (193, 5, 1, 0), + (193, 7, 1, 0), + (193, 8, 1, 0), + (193, 15, 1, 0), + (193, 16, 1, 0), + (193, 31, 1, 0), + (193, 255, 1, 0), + (193, 256, 1, 0), + (193, 65535, 1, 0), + (194, 0, 1, 1), + (194, 1, 1, 2), + (194, 2, 1, 1), + (194, 3, 1, 2), + (194, 4, 1, 1), + (194, 5, 1, 2), + (194, 7, 1, 2), + (194, 8, 1, 1), + (194, 15, 1, 2), + (194, 16, 1, 1), + (194, 31, 1, 2), + (194, 255, 1, 2), + (194, 256, 1, 1), + (194, 65535, 1, 2), + (195, 0, 1, 1), + (195, 1, 1, 2), + (195, 2, 1, 1), + (195, 3, 1, 2), + (195, 4, 1, 1), + (195, 5, 1, 2), + (195, 7, 1, 2), + (195, 8, 1, 1), + (195, 15, 1, 2), + (195, 16, 1, 1), + (195, 31, 1, 2), + (195, 255, 1, 2), + (195, 256, 1, 1), + (195, 65535, 1, 2), + (196, 0, 0, 1), + (196, 1, 0, 2), + (196, 2, 0, 1), + (196, 3, 0, 2), + (196, 4, 0, 1), + (196, 5, 0, 2), + (196, 7, 0, 2), + (196, 8, 0, 1), + (196, 15, 0, 2), + (196, 16, 0, 1), + (196, 31, 0, 2), + (196, 255, 0, 2), + (196, 256, 0, 1), + (196, 65535, 0, 2), + (197, 0, 0, 1), + (197, 1, 0, 2), + (197, 2, 0, 1), + (197, 3, 0, 2), + (197, 4, 0, 1), + (197, 5, 0, 2), + (197, 7, 0, 2), + (197, 8, 0, 1), + (197, 15, 0, 2), + (197, 16, 0, 1), + (197, 31, 0, 2), + (197, 255, 0, 2), + (197, 256, 0, 1), + (197, 65535, 0, 2), + (198, 0, 3, 1), + (198, 1, 3, 1), + (198, 2, 3, 1), + (198, 3, 3, 1), + (198, 4, 3, 1), + (198, 5, 3, 1), + (198, 7, 3, 1), + (198, 8, 3, 1), + (198, 15, 3, 1), + (198, 16, 3, 1), + (198, 31, 3, 1), + (198, 255, 3, 1), + (198, 256, 3, 1), + (198, 65535, 3, 1), + (199, 0, 3, 2), + (199, 1, 3, 2), + (199, 2, 3, 2), + (199, 3, 3, 2), + (199, 4, 3, 2), + (199, 5, 3, 2), + (199, 7, 3, 2), + (199, 8, 3, 2), + (199, 15, 3, 2), + (199, 16, 3, 2), + (199, 31, 3, 2), + (199, 255, 3, 2), + (199, 256, 3, 2), + (199, 65535, 3, 2), + (200, 0, 0, 0), + (200, 1, 0, 0), + (200, 2, 0, 0), + (200, 3, 0, 0), + (200, 4, 0, 0), + (200, 5, 0, 0), + (200, 7, 0, 0), + (200, 8, 0, 0), + (200, 15, 0, 0), + (200, 16, 0, 0), + (200, 31, 0, 0), + (200, 255, 0, 0), + (200, 256, 0, 0), + (200, 65535, 0, 0), + (201, 0, 0, 0), + (201, 1, 0, 0), + (201, 2, 0, 0), + (201, 3, 0, 0), + (201, 4, 0, 0), + (201, 5, 0, 0), + (201, 7, 0, 0), + (201, 8, 0, 0), + (201, 15, 0, 0), + (201, 16, 0, 0), + (201, 31, 0, 0), + (201, 255, 0, 0), + (201, 256, 0, 0), + (201, 65535, 0, 0), + (202, 0, 3, 3), + (202, 1, 3, 3), + (202, 2, 3, 3), + (202, 3, 3, 3), + (202, 4, 3, 3), + (202, 5, 3, 3), + (202, 7, 3, 3), + (202, 8, 3, 3), + (202, 15, 3, 3), + (202, 16, 3, 3), + (202, 31, 3, 3), + (202, 255, 3, 3), + (202, 256, 3, 3), + (202, 65535, 3, 3), + (203, 0, 3, 2), + (203, 1, 3, 2), + (203, 2, 3, 2), + (203, 3, 3, 2), + (203, 4, 3, 2), + (203, 5, 3, 2), + (203, 7, 3, 2), + (203, 8, 3, 2), + (203, 15, 3, 2), + (203, 16, 3, 2), + (203, 31, 3, 2), + (203, 255, 3, 2), + (203, 256, 3, 2), + (203, 65535, 3, 2), + (204, 0, 3, 3), + (204, 1, 3, 3), + (204, 2, 3, 3), + (204, 3, 3, 3), + (204, 4, 3, 3), + (204, 5, 3, 3), + (204, 7, 3, 3), + (204, 8, 3, 3), + (204, 15, 3, 3), + (204, 16, 3, 3), + (204, 31, 3, 3), + (204, 255, 3, 3), + (204, 256, 3, 3), + (204, 65535, 3, 3), + (205, 0, 2, 0), + (205, 1, 2, 0), + (205, 2, 2, 0), + (205, 3, 2, 0), + (205, 4, 2, 0), + (205, 5, 2, 0), + (205, 7, 2, 0), + (205, 8, 2, 0), + (205, 15, 2, 0), + (205, 16, 2, 0), + (205, 31, 2, 0), + (205, 255, 2, 0), + (205, 256, 2, 0), + (205, 65535, 2, 0), + (206, 0, 2, 0), + (206, 1, 2, 0), + (206, 2, 2, 0), + (206, 3, 2, 0), + (206, 4, 2, 0), + (206, 5, 2, 0), + (206, 7, 2, 0), + (206, 8, 2, 0), + (206, 15, 2, 0), + (206, 16, 2, 0), + (206, 31, 2, 0), + (206, 255, 2, 0), + (206, 256, 2, 0), + (206, 65535, 2, 0), + (207, 0, 2, 0), + (207, 1, 2, 0), + (207, 2, 2, 0), + (207, 3, 2, 0), + (207, 4, 2, 0), + (207, 5, 2, 0), + (207, 7, 2, 0), + (207, 8, 2, 0), + (207, 15, 2, 0), + (207, 16, 2, 0), + (207, 31, 2, 0), + (207, 255, 2, 0), + (207, 256, 2, 0), + (207, 65535, 2, 0), + (208, 0, 3, 0), + (208, 1, 3, 0), + (208, 2, 3, 0), + (208, 3, 3, 0), + (208, 4, 3, 0), + (208, 5, 3, 0), + (208, 7, 3, 0), + (208, 8, 3, 0), + (208, 15, 3, 0), + (208, 16, 3, 0), + (208, 31, 3, 0), + (208, 255, 3, 0), + (208, 256, 3, 0), + (208, 65535, 3, 0), + (209, 0, 3, 0), + (209, 1, 3, 0), + (209, 2, 3, 0), + (209, 3, 3, 0), + (209, 4, 3, 0), + (209, 5, 3, 0), + (209, 7, 3, 0), + (209, 8, 3, 0), + (209, 15, 3, 0), + (209, 16, 3, 0), + (209, 31, 3, 0), + (209, 255, 3, 0), + (209, 256, 3, 0), + (209, 65535, 3, 0), + (210, 0, 1, 1), + (210, 1, 1, 1), + (210, 2, 1, 1), + (210, 3, 1, 1), + (210, 4, 1, 1), + (210, 5, 1, 1), + (210, 7, 1, 1), + (210, 8, 1, 1), + (210, 15, 1, 1), + (210, 16, 1, 1), + (210, 31, 1, 1), + (210, 255, 1, 1), + (210, 256, 1, 1), + (210, 65535, 1, 1), + (211, 0, 1, 1), + (211, 1, 1, 1), + (211, 2, 1, 1), + (211, 3, 1, 1), + (211, 4, 1, 1), + (211, 5, 1, 1), + (211, 7, 1, 1), + (211, 8, 1, 1), + (211, 15, 1, 1), + (211, 16, 1, 1), + (211, 31, 1, 1), + (211, 255, 1, 1), + (211, 256, 1, 1), + (211, 65535, 1, 1), + (212, 0, 1, 1), + (212, 1, 1, 1), + (212, 2, 1, 1), + (212, 3, 1, 1), + (212, 4, 1, 1), + (212, 5, 1, 1), + (212, 7, 1, 1), + (212, 8, 1, 1), + (212, 15, 1, 1), + (212, 16, 1, 1), + (212, 31, 1, 1), + (212, 255, 1, 1), + (212, 256, 1, 1), + (212, 65535, 1, 1), + (213, 0, 1, 1), + (213, 1, 1, 1), + (213, 2, 1, 1), + (213, 3, 1, 1), + (213, 4, 1, 1), + (213, 5, 1, 1), + (213, 7, 1, 1), + (213, 8, 1, 1), + (213, 15, 1, 1), + (213, 16, 1, 1), + (213, 31, 1, 1), + (213, 255, 1, 1), + (213, 256, 1, 1), + (213, 65535, 1, 1), + (214, 0, 1, 1), + (214, 1, 1, 1), + (214, 2, 1, 1), + (214, 3, 1, 1), + (214, 4, 1, 1), + (214, 5, 1, 1), + (214, 7, 1, 1), + (214, 8, 1, 1), + (214, 15, 1, 1), + (214, 16, 1, 1), + (214, 31, 1, 1), + (214, 255, 1, 1), + (214, 256, 1, 1), + (214, 65535, 1, 1), + (215, 0, 1, 1), + (215, 1, 1, 1), + (215, 2, 1, 1), + (215, 3, 1, 1), + (215, 4, 1, 1), + (215, 5, 1, 1), + (215, 7, 1, 1), + (215, 8, 1, 1), + (215, 15, 1, 1), + (215, 16, 1, 1), + (215, 31, 1, 1), + (215, 255, 1, 1), + (215, 256, 1, 1), + (215, 65535, 1, 1), + (216, 0, 1, 0), + (216, 1, 1, 1), + (216, 2, 1, 2), + (216, 3, 1, 3), + (216, 4, 1, 4), + (216, 5, 1, 5), + (216, 7, 1, 7), + (216, 8, 1, 8), + (216, 15, 1, 15), + (216, 16, 1, 16), + (216, 31, 1, 31), + (216, 255, 1, 255), + (216, 256, 1, 256), + (216, 65535, 1, 65535), + (217, 0, 1, 0), + (217, 1, 1, 1), + (217, 2, 1, 2), + (217, 3, 1, 3), + (217, 4, 1, 4), + (217, 5, 1, 5), + (217, 7, 1, 7), + (217, 8, 1, 8), + (217, 15, 1, 15), + (217, 16, 1, 16), + (217, 31, 1, 31), + (217, 255, 1, 255), + (217, 256, 1, 256), + (217, 65535, 1, 65535), + (218, 0, 1, 2), + (218, 1, 1, 2), + (218, 2, 1, 2), + (218, 3, 1, 2), + (218, 4, 1, 2), + (218, 5, 1, 2), + (218, 7, 1, 2), + (218, 8, 1, 2), + (218, 15, 1, 2), + (218, 16, 1, 2), + (218, 31, 1, 2), + (218, 255, 1, 2), + (218, 256, 1, 2), + (218, 65535, 1, 2), + (233, 0, 3, 2), + (233, 1, 3, 2), + (233, 2, 3, 2), + (233, 3, 3, 2), + (233, 4, 3, 2), + (233, 5, 3, 2), + (233, 7, 3, 2), + (233, 8, 3, 2), + (233, 15, 3, 2), + (233, 16, 3, 2), + (233, 31, 3, 2), + (233, 255, 3, 2), + (233, 256, 3, 2), + (233, 65535, 3, 2), + (234, 0, 2, 0), + (234, 1, 2, 0), + (234, 2, 2, 0), + (234, 3, 2, 0), + (234, 4, 2, 0), + (234, 5, 2, 0), + (234, 7, 2, 0), + (234, 8, 2, 0), + (234, 15, 2, 0), + (234, 16, 2, 0), + (234, 31, 2, 0), + (234, 255, 2, 0), + (234, 256, 2, 0), + (234, 65535, 2, 0), + (235, 0, 3, 1), + (235, 1, 3, 1), + (235, 2, 3, 1), + (235, 3, 3, 1), + (235, 4, 3, 1), + (235, 5, 3, 1), + (235, 7, 3, 1), + (235, 8, 3, 1), + (235, 15, 3, 1), + (235, 16, 3, 1), + (235, 31, 3, 1), + (235, 255, 3, 1), + (235, 256, 3, 1), + (235, 65535, 3, 1), + (236, 0, 2, 3), + (236, 1, 2, 3), + (236, 2, 2, 3), + (236, 3, 2, 3), + (236, 4, 2, 3), + (236, 5, 2, 3), + (236, 7, 2, 3), + (236, 8, 2, 3), + (236, 15, 2, 3), + (236, 16, 2, 3), + (236, 31, 2, 3), + (236, 255, 2, 3), + (236, 256, 2, 3), + (236, 65535, 2, 3), + (237, 0, 0, 0), + (237, 1, 0, 0), + (237, 2, 0, 0), + (237, 3, 0, 0), + (237, 4, 0, 0), + (237, 5, 0, 0), + (237, 7, 0, 0), + (237, 8, 0, 0), + (237, 15, 0, 0), + (237, 16, 0, 0), + (237, 31, 0, 0), + (237, 255, 0, 0), + (237, 256, 0, 0), + (237, 65535, 0, 0), + (238, 0, 0, 0), + (238, 1, 0, 0), + (238, 2, 0, 0), + (238, 3, 0, 0), + (238, 4, 0, 0), + (238, 5, 0, 0), + (238, 7, 0, 0), + (238, 8, 0, 0), + (238, 15, 0, 0), + (238, 16, 0, 0), + (238, 31, 0, 0), + (238, 255, 0, 0), + (238, 256, 0, 0), + (238, 65535, 0, 0), + (239, 0, 0, 0), + (239, 1, 0, 0), + (239, 2, 0, 0), + (239, 3, 0, 0), + (239, 4, 0, 0), + (239, 5, 0, 0), + (239, 7, 0, 0), + (239, 8, 0, 0), + (239, 15, 0, 0), + (239, 16, 0, 0), + (239, 31, 0, 0), + (239, 255, 0, 0), + (239, 256, 0, 0), + (239, 65535, 0, 0), + (240, 0, 1, 0), + (240, 1, 1, 0), + (240, 2, 1, 0), + (240, 3, 1, 0), + (240, 4, 1, 0), + (240, 5, 1, 0), + (240, 7, 1, 0), + (240, 8, 1, 0), + (240, 15, 1, 0), + (240, 16, 1, 0), + (240, 31, 1, 0), + (240, 255, 1, 0), + (240, 256, 1, 0), + (240, 65535, 1, 0), + (241, 0, 1, 0), + (241, 1, 1, 0), + (241, 2, 1, 0), + (241, 3, 1, 0), + (241, 4, 1, 0), + (241, 5, 1, 0), + (241, 7, 1, 0), + (241, 8, 1, 0), + (241, 15, 1, 0), + (241, 16, 1, 0), + (241, 31, 1, 0), + (241, 255, 1, 0), + (241, 256, 1, 0), + (241, 65535, 1, 0), + (242, 0, 1, 0), + (242, 1, 1, 0), + (242, 2, 1, 0), + (242, 3, 1, 0), + (242, 4, 1, 0), + (242, 5, 1, 0), + (242, 7, 1, 0), + (242, 8, 1, 0), + (242, 15, 1, 0), + (242, 16, 1, 0), + (242, 31, 1, 0), + (242, 255, 1, 0), + (242, 256, 1, 0), + (242, 65535, 1, 0), + (243, 0, 1, 0), + (243, 1, 1, 0), + (243, 2, 1, 0), + (243, 3, 1, 0), + (243, 4, 1, 0), + (243, 5, 1, 0), + (243, 7, 1, 0), + (243, 8, 1, 0), + (243, 15, 1, 0), + (243, 16, 1, 0), + (243, 31, 1, 0), + (243, 255, 1, 0), + (243, 256, 1, 0), + (243, 65535, 1, 0), + (244, 0, 0, 0), + (244, 1, 0, 0), + (244, 2, 0, 0), + (244, 3, 0, 0), + (244, 4, 0, 0), + (244, 5, 0, 0), + (244, 7, 0, 0), + (244, 8, 0, 0), + (244, 15, 0, 0), + (244, 16, 0, 0), + (244, 31, 0, 0), + (244, 255, 0, 0), + (244, 256, 0, 0), + (244, 65535, 0, 0), + (245, 0, 1, 1), + (245, 1, 1, 1), + (245, 2, 1, 1), + (245, 3, 1, 1), + (245, 4, 1, 1), + (245, 5, 1, 1), + (245, 7, 1, 1), + (245, 8, 1, 1), + (245, 15, 1, 1), + (245, 16, 1, 1), + (245, 31, 1, 1), + (245, 255, 1, 1), + (245, 256, 1, 1), + (245, 65535, 1, 1), + (246, 0, 1, 1), + (246, 1, 1, 1), + (246, 2, 1, 1), + (246, 3, 1, 1), + (246, 4, 1, 1), + (246, 5, 1, 1), + (246, 7, 1, 1), + (246, 8, 1, 1), + (246, 15, 1, 1), + (246, 16, 1, 1), + (246, 31, 1, 1), + (246, 255, 1, 1), + (246, 256, 1, 1), + (246, 65535, 1, 1), + (247, 0, 2, 0), + (247, 1, 2, 0), + (247, 2, 2, 0), + (247, 3, 2, 0), + (247, 4, 2, 0), + (247, 5, 2, 0), + (247, 7, 2, 0), + (247, 8, 2, 0), + (247, 15, 2, 0), + (247, 16, 2, 0), + (247, 31, 2, 0), + (247, 255, 2, 0), + (247, 256, 2, 0), + (247, 65535, 2, 0), + (248, 0, 3, 1), + (248, 1, 3, 2), + (248, 2, 3, 1), + (248, 3, 3, 2), + (248, 4, 3, 1), + (248, 5, 3, 2), + (248, 7, 3, 2), + (248, 8, 3, 1), + (248, 15, 3, 2), + (248, 16, 3, 1), + (248, 31, 3, 2), + (248, 255, 3, 2), + (248, 256, 3, 1), + (248, 65535, 3, 2), + (249, 0, 2, 1), + (249, 1, 3, 1), + (249, 2, 4, 1), + (249, 3, 5, 1), + (249, 4, 6, 1), + (249, 5, 7, 1), + (249, 7, 9, 1), + (249, 8, 10, 1), + (249, 15, 17, 1), + (249, 16, 18, 1), + (249, 31, 33, 1), + (249, 255, 257, 1), + (249, 256, 258, 1), + (249, 65535, 65537, 1), + (250, 0, 3, 1), + (250, 1, 4, 1), + (250, 2, 5, 1), + (250, 3, 6, 1), + (250, 4, 7, 1), + (250, 5, 8, 1), + (250, 7, 10, 1), + (250, 8, 11, 1), + (250, 15, 18, 1), + (250, 16, 19, 1), + (250, 31, 34, 1), + (250, 255, 258, 1), + (250, 256, 259, 1), + (250, 65535, 65538, 1), + (251, 0, 4, 1), + (251, 1, 4, 1), + (251, 2, 4, 1), + (251, 3, 4, 1), + (251, 4, 4, 1), + (251, 5, 4, 1), + (251, 7, 4, 1), + (251, 8, 4, 1), + (251, 15, 4, 1), + (251, 16, 4, 1), + (251, 31, 4, 1), + (251, 255, 4, 1), + (251, 256, 4, 1), + (251, 65535, 4, 1), + (252, 0, 0, 0), + (252, 1, 0, 0), + (252, 2, 0, 0), + (252, 3, 0, 0), + (252, 4, 0, 0), + (252, 5, 0, 0), + (252, 7, 0, 0), + (252, 8, 0, 0), + (252, 15, 0, 0), + (252, 16, 0, 0), + (252, 31, 0, 0), + (252, 255, 0, 0), + (252, 256, 0, 0), + (252, 65535, 0, 0), + (253, 0, 0, 0), + (253, 1, 0, 0), + (253, 2, 0, 0), + (253, 3, 0, 0), + (253, 4, 0, 0), + (253, 5, 0, 0), + (253, 7, 0, 0), + (253, 8, 0, 0), + (253, 15, 0, 0), + (253, 16, 0, 0), + (253, 31, 0, 0), + (253, 255, 0, 0), + (253, 256, 0, 0), + (253, 65535, 0, 0), + (254, 0, 0, 0), + (254, 1, 0, 0), + (254, 2, 0, 0), + (254, 3, 0, 0), + (254, 4, 0, 0), + (254, 5, 0, 0), + (254, 7, 0, 0), + (254, 8, 0, 0), + (254, 15, 0, 0), + (254, 16, 0, 0), + (254, 31, 0, 0), + (254, 255, 0, 0), + (254, 256, 0, 0), + (254, 65535, 0, 0), + (255, 0, 0, 0), + (255, 1, 0, 0), + (255, 2, 0, 0), + (255, 3, 0, 0), + (255, 4, 0, 0), + (255, 5, 0, 0), + (255, 7, 0, 0), + (255, 8, 0, 0), + (255, 15, 0, 0), + (255, 16, 0, 0), + (255, 31, 0, 0), + (255, 255, 0, 0), + (255, 256, 0, 0), + (255, 65535, 0, 0), + (256, 0, 0, 0), + (256, 1, 0, 0), + (256, 2, 0, 0), + (256, 3, 0, 0), + (256, 4, 0, 0), + (256, 5, 0, 0), + (256, 7, 0, 0), + (256, 8, 0, 0), + (256, 15, 0, 0), + (256, 16, 0, 0), + (256, 31, 0, 0), + (256, 255, 0, 0), + (256, 256, 0, 0), + (256, 65535, 0, 0), + (257, 0, 0, 0), + (257, 1, 0, 0), + (257, 2, 0, 0), + (257, 3, 0, 0), + (257, 4, 0, 0), + (257, 5, 0, 0), + (257, 7, 0, 0), + (257, 8, 0, 0), + (257, 15, 0, 0), + (257, 16, 0, 0), + (257, 31, 0, 0), + (257, 255, 0, 0), + (257, 256, 0, 0), + (257, 65535, 0, 0), + (258, 0, 1, 1), + (258, 1, 1, 1), + (258, 2, 1, 1), + (258, 3, 1, 1), + (258, 4, 1, 1), + (258, 5, 1, 1), + (258, 7, 1, 1), + (258, 8, 1, 1), + (258, 15, 1, 1), + (258, 16, 1, 1), + (258, 31, 1, 1), + (258, 255, 1, 1), + (258, 256, 1, 1), + (258, 65535, 1, 1), + (259, 0, 1, 1), + (259, 1, 1, 1), + (259, 2, 1, 1), + (259, 3, 1, 1), + (259, 4, 1, 1), + (259, 5, 1, 1), + (259, 7, 1, 1), + (259, 8, 1, 1), + (259, 15, 1, 1), + (259, 16, 1, 1), + (259, 31, 1, 1), + (259, 255, 1, 1), + (259, 256, 1, 1), + (259, 65535, 1, 1), + (260, 0, 0, 0), + (260, 1, 0, 0), + (260, 2, 0, 0), + (260, 3, 0, 0), + (260, 4, 0, 0), + (260, 5, 0, 0), + (260, 7, 0, 0), + (260, 8, 0, 0), + (260, 15, 0, 0), + (260, 16, 0, 0), + (260, 31, 0, 0), + (260, 255, 0, 0), + (260, 256, 0, 0), + (260, 65535, 0, 0), + (261, 0, 0, 1), + (261, 1, 0, 1), + (261, 2, 0, 1), + (261, 3, 0, 1), + (261, 4, 0, 1), + (261, 5, 0, 1), + (261, 7, 0, 1), + (261, 8, 0, 1), + (261, 15, 0, 1), + (261, 16, 0, 1), + (261, 31, 0, 1), + (261, 255, 0, 1), + (261, 256, 0, 1), + (261, 65535, 0, 1), + (262, 0, 0, 0), + (262, 1, 0, 0), + (262, 2, 0, 0), + (262, 3, 0, 0), + (262, 4, 0, 0), + (262, 5, 0, 0), + (262, 7, 0, 0), + (262, 8, 0, 0), + (262, 15, 0, 0), + (262, 16, 0, 0), + (262, 31, 0, 0), + (262, 255, 0, 0), + (262, 256, 0, 0), + (262, 65535, 0, 0), + (263, 0, 0, 2), + (263, 1, 0, 2), + (263, 2, 0, 2), + (263, 3, 0, 2), + (263, 4, 0, 2), + (263, 5, 0, 2), + (263, 7, 0, 2), + (263, 8, 0, 2), + (263, 15, 0, 2), + (263, 16, 0, 2), + (263, 31, 0, 2), + (263, 255, 0, 2), + (263, 256, 0, 2), + (263, 65535, 0, 2), + (264, 0, 0, 1), + (264, 1, 0, 1), + (264, 2, 0, 1), + (264, 3, 0, 1), + (264, 4, 0, 1), + (264, 5, 0, 1), + (264, 7, 0, 1), + (264, 8, 0, 1), + (264, 15, 0, 1), + (264, 16, 0, 1), + (264, 31, 0, 1), + (264, 255, 0, 1), + (264, 256, 0, 1), + (264, 65535, 0, 1), + (265, 0, 0, 1), + (265, 1, 0, 1), + (265, 2, 0, 1), + (265, 3, 0, 1), + (265, 4, 0, 1), + (265, 5, 0, 1), + (265, 7, 0, 1), + (265, 8, 0, 1), + (265, 15, 0, 1), + (265, 16, 0, 1), + (265, 31, 0, 1), + (265, 255, 0, 1), + (265, 256, 0, 1), + (265, 65535, 0, 1), + (266, 0, 1, 0), + (266, 1, 1, 0), + (266, 2, 1, 0), + (266, 3, 1, 0), + (266, 4, 1, 0), + (266, 5, 1, 0), + (266, 7, 1, 0), + (266, 8, 1, 0), + (266, 15, 1, 0), + (266, 16, 1, 0), + (266, 31, 1, 0), + (266, 255, 1, 0), + (266, 256, 1, 0), + (266, 65535, 1, 0), +]; diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 499d7c8ba..c0e63f296 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -663,6 +663,21 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.stack.push(val); Ok(()) } + Instruction::LoadCommonConstant { idx } => { + let val = match idx.get(arg) { + oparg::CommonConstant::None => JitValue::None, + oparg::CommonConstant::True | oparg::CommonConstant::False => { + let value = i64::from(idx.get(arg) == oparg::CommonConstant::True); + JitValue::Bool(self.builder.ins().iconst(types::I8, value)) + } + oparg::CommonConstant::MinusOne => { + JitValue::Int(self.builder.ins().iconst(types::I64, -1)) + } + _ => return Err(JitCompileError::NotSupported), + }; + self.stack.push(val); + Ok(()) + } Instruction::LoadSmallInt { i } => { let small_int = i.get(arg) as i64; let val = self.builder.ins().iconst(types::I64, small_int); @@ -1508,3 +1523,46 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { res } } + +#[cfg(test)] +mod tests { + use super::*; + use bytecode::{BasicBag, ConstantData}; + + #[test] + fn common_constant_return_types_and_unsupported_domain() { + let frozen = rustpython_derive::py_compile!( + crate_name = "rustpython_compiler_core", + source = "def nothing():\n return None\ndef truth():\n return True\ndef falsehood():\n return False\ndef minus_one():\n return -1\ndef empty_string():\n return ''\n" + ); + let module = frozen.decode(BasicBag); + let mut checked = 0; + for constant in module.constants.iter() { + let ConstantData::Code { code } = constant else { + continue; + }; + let (common, return_type) = match code.obj_name.as_str() { + "nothing" => (oparg::CommonConstant::None, Some(JitType::None)), + "truth" => (oparg::CommonConstant::True, Some(JitType::Bool)), + "falsehood" => (oparg::CommonConstant::False, Some(JitType::Bool)), + "minus_one" => (oparg::CommonConstant::MinusOne, Some(JitType::Int)), + "empty_string" => (oparg::CommonConstant::EmptyStr, None), + name => panic!("unexpected fixture code: {name}"), + }; + let mut state = OpArgState::default(); + assert!(code.instructions.iter().copied().any(|unit| { + let (instruction, arg) = state.get(unit); + matches!(instruction, Instruction::LoadCommonConstant { idx } if idx.get(arg) == common) + })); + let result = crate::compile(code, &[], None); + if let Some(expected) = return_type { + let compiled = result.expect("supported common constant must compile"); + assert_eq!(compiled.sig.ret, Some(expected)); + } else { + assert!(matches!(result, Err(JitCompileError::NotSupported))); + } + checked += 1; + } + assert_eq!(checked, 5); + } +} diff --git a/crates/jit/tests/common.rs b/crates/jit/tests/common.rs index fe0e4bd33..0cbce428a 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/vm/src/builtins/code.rs b/crates/vm/src/builtins/code.rs index 6cf1036ab..15bb33e88 100644 --- a/crates/vm/src/builtins/code.rs +++ b/crates/vm/src/builtins/code.rs @@ -511,7 +511,8 @@ impl MemberLayout for CodeFlags { } impl PyCode { - pub fn new(code: CodeObject) -> Self { + pub fn new(mut code: CodeObject) -> Self { + code.instructions.normalize_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 = { diff --git a/crates/vm/src/builtins/coroutine.rs b/crates/vm/src/builtins/coroutine.rs index 649481c8c..a0d4a581a 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 905cbb582..f6ebac7f6 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -242,7 +242,14 @@ pub(crate) mod stack_analysis { } } } - Instruction::GetIter | Instruction::GetAiter => { + Instruction::GetIter { .. } => { + next_stack = push_value(pop_value(next_stack), Kind::Iterator as i64); + next_stack = push_value(next_stack, Kind::Iterator as i64); + if next_i < stacks.len() { + stacks[next_i] = next_stack; + } + } + Instruction::GetAiter => { next_stack = push_value(pop_value(next_stack), Kind::Iterator as i64); if next_i < stacks.len() { stacks[next_i] = next_stack; @@ -254,19 +261,13 @@ pub(crate) mod stack_analysis { if next_i < stacks.len() { stacks[next_i] = body_stack; } - // Exhaustion path: relative forward from after_caches - let mut j = next_i + oparg as usize; - if j < instructions.len() { - let target_op = - instructions[j].op.to_base().unwrap_or(instructions[j].op); - if matches!(target_op, Instruction::EndFor) { - j += 1; - } - } + // CPython models the target END_FOR with the yielded-value slot. + let j = next_i + oparg as usize; if j < stacks.len() && stacks[j] == UNINITIALIZED { - stacks[j] = next_stack; + stacks[j] = body_stack; } } + Instruction::EndAsyncFor => { next_stack = pop_value(pop_value(next_stack)); if next_i < stacks.len() { @@ -630,6 +631,11 @@ impl Py { let mut cur = crate::vm::thread::get_current_frame(); while !cur.is_null() { if core::ptr::eq(cur, prev) { + // SAFETY: cur was found on this thread's live active chain. + let cur = unsafe { crate::frame::first_complete_iframe(cur) }; + if cur.is_null() { + return None; + } let iframe_ref = unsafe { &*cur }; let fo = iframe_ref.materialize(vm); return Some(fo.to_owned()); @@ -652,6 +658,11 @@ impl Py { // return and free the stack-allocated iframe at any time. vm.state.stop_the_world.stop_the_world(&vm.state); scopeguard::defer! { vm.state.stop_the_world.start_the_world(&vm.state); } + // SAFETY: the world is stopped, preserving the live chain. + let prev = unsafe { crate::frame::first_complete_iframe(prev) }; + if prev.is_null() { + return None; + } let prev_ref = unsafe { &*prev }; // Fast path: already materialized. if let Some(fo) = prev_ref.frame_obj() { @@ -659,7 +670,7 @@ impl Py { } // Slow path: copy the whole chain, linked through retained_back. // SAFETY: the world is stopped, so the owning thread is parked. - let fo = unsafe { prev_ref.materialize_detached_chain(vm) }; + let fo = unsafe { prev_ref.materialize_complete_detached_chain(vm) }; return Some(fo); } diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index b0bd5a010..16c15c8a3 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -635,6 +635,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. @@ -842,7 +843,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); @@ -884,7 +885,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( @@ -944,7 +946,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) } @@ -966,7 +968,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) } @@ -1777,7 +1779,7 @@ pub(crate) fn vectorcall_function( // is called in, and a fresh `MAKE_FUNCTION` each time keeps those out // of the call-site specialization that would otherwise catch it. args.truncate(nargs); - return Ok(zelf.make_generator_exact_args(args.into_iter(), vm)); + return zelf.make_generator_exact_args(args.into_iter(), vm); } if !has_kwargs && base_simple && nargs == code.arg_count as usize { diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index 5da8c8272..bf287c028 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -40,6 +40,8 @@ pub struct Coro { code: PyRef, pub closed: AtomicCell, // TODO: https://github.com/RustPython/RustPython/pull/3183#discussion_r720560652 running: AtomicCell, + /// Whether execution has resumed beyond the eager creation preamble. + started: AtomicCell, // _weakreflist name: PyMutex, qualname: PyMutex, @@ -159,16 +161,16 @@ pub(crate) fn flat_resume_enter( if coro.closed.load() { return Ok(FlatEnter::Exhausted); } - let value = if coro.frame_opt().is_some_and(|f| f.lasti() > 0) { - Some(value) - } else if !vm.is_none(&value) { + if !coro.started() && !vm.is_none(&value) { return Err(vm.new_type_error(format!( "can't send non-None value to a just-started {}", gen_name(&jen, vm), ))); - } else { - None - }; + } + let value = coro + .frame_opt() + .is_some_and(|f| f.lasti() > 0) + .then_some(value); // SAFETY: exclusive access guaranteed by the claim let gen_exc = unsafe { coro.exception.swap(None) }; let frame_ref = coro.frame(); @@ -184,6 +186,7 @@ pub(crate) fn flat_resume_enter( return Err(exc); } }; + coro.started.store(true); // The claim outlives this scope; `flat_resume_exit` drops it. core::mem::forget(_claim); Ok(FlatEnter::Entered { @@ -234,6 +237,7 @@ impl Coro { code, closed: AtomicCell::new(false), running: AtomicCell::new(false), + started: AtomicCell::new(false), exception: PyAtomicRef::from(None), name: PyMutex::new(name), qualname: PyMutex::new(qualname), @@ -307,6 +311,7 @@ impl Coro { let frame = self.frame(); vm.resume_gen_frame(&frame, gen_exc, |f| { + self.started.store(true); let result = func(f); // SAFETY: exclusive access guaranteed by the claim let _old = unsafe { (*exception_ptr).swap(vm.current_exception()) }; @@ -401,16 +406,16 @@ impl Coro { if self.closed.load() { return Self::send_when_closed(jen, vm); } - let value = if self.frame_opt().is_some_and(|f| f.lasti() > 0) { - Some(value) - } else if !vm.is_none(&value) { + if !self.started() && !vm.is_none(&value) { return Err(vm.new_type_error(format!( "can't send non-None value to a just-started {}", gen_name(jen, vm), ))); - } else { - None - }; + } + let value = self + .frame_opt() + .is_some_and(|f| f.lasti() > 0) + .then_some(value); let result = self.run_claimed(&claim, vm, |f| f.resume(value, vm)); self.maybe_close(&result, &claim); drop(claim); @@ -462,7 +467,7 @@ impl Coro { return Ok(vm.ctx.none()); } // FRAME_CREATED: mark finished and clear the iframe. - if self.frame_opt().is_none_or(|f| f.lasti() == 0) { + if !self.started() { self.closed.store(true); self.clear_except_code(); return Ok(vm.ctx.none()); @@ -493,13 +498,11 @@ impl Coro { /// Whether the frame has run at all, i.e. is stopped at a `yield` rather /// than at its start. pub(crate) fn started(&self) -> bool { - self.frame_opt().is_some_and(|f| f.lasti() > 0) + self.started.load() } pub fn suspended(&self) -> bool { - !self.closed.load() - && !self.running.load() - && self.frame_opt().is_some_and(|f| f.lasti() > 0) + !self.closed.load() && !self.running.load() && self.started() } pub fn running(&self) -> bool { diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 2c9d60751..132d20307 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -20,6 +20,7 @@ use crate::{ }, bytecode::{ self, ADAPTIVE_COOLDOWN_VALUE, Instruction, LoadAttr, LoadSuperAttr, Opcode, SpecialMethod, + oparg::{ResumeContext, ResumeLocation}, }, convert::{ToPyObject, ToPyResult}, coroutine::Coro, @@ -105,6 +106,39 @@ pub fn current_thread_frame_materialize(vm: &VirtualMachine) -> Option *const InterpreterFrame { + while !current.is_null() { + // SAFETY: the caller guarantees the chain remains alive. + let frame = unsafe { &*current }; + if !frame.is_incomplete_for_trace() { + break; + } + current = frame.previous(); + } + current +} + +/// Resolve the active frame for a legacy trace/profile callback, skipping +/// incomplete generator-creation frames before materializing a frame object. +/// Raw execution-chain users deliberately keep seeing the actual active frame. +pub(crate) fn current_complete_frame_for_trace(vm: &VirtualMachine) -> Option { + // SAFETY: synchronous inspection of the current thread's active chain. + let current = unsafe { first_complete_iframe(crate::vm::thread::get_current_frame()) }; + if current.is_null() { + return None; + } + // SAFETY: the selected frame is alive on this thread's execution chain. + Some(unsafe { (*current).materialize(vm).to_owned() }) +} + /// Read the globals dict from the topmost frame on this thread's chain. /// Returns `None` if the chain is empty. #[must_use] @@ -137,12 +171,13 @@ pub fn current_builtins() -> Option { Some(unsafe { (*ptr).builtins().to_owned() }) } -/// The frame `offset` positions below the current thread's top frame (offset 0 -/// is the top), or `None` if the stack is not that deep. +/// The complete frame `offset` positions below the current thread's first +/// complete frame, or `None` if the stack is not that deep. /// Materializes the FrameObject on demand for stack-allocated frames. #[must_use] pub fn frame_at_offset(offset: usize, vm: &VirtualMachine) -> Option { - let mut cur = crate::vm::thread::get_current_frame(); + // SAFETY: synchronous inspection of this thread's active chain. + let mut cur = unsafe { first_complete_iframe(crate::vm::thread::get_current_frame()) }; let mut remaining = offset; while !cur.is_null() { if remaining == 0 { @@ -150,7 +185,8 @@ pub fn frame_at_offset(offset: usize, vm: &VirtualMachine) -> Option bool { + if FrameOwner::from_i8(self.owner.load(atomic::Ordering::Acquire)) == FrameOwner::Generator + || self.generator.load().is_some() + { + return false; + } + let instructions = &self.code().instructions; + let lasti = self.lasti.load(Relaxed) as usize; + let mut index = 0; + while index < instructions.len() { + let unit = instructions.read_unit(index); + if matches!( + unit.op, + Instruction::Resume { .. } + | Instruction::ResumeCheck + | Instruction::InstrumentedResume + ) && u32::from(u8::from(unit.arg)) & ResumeContext::LOCATION_MASK + != ResumeLocation::AtGenExprStart.as_u32() + { + // lasti is one past the in-flight instruction. At RESUME + // itself, lasti == index + 1 and the frame is complete. + return lasti <= index; + } + // Prefixes before the first traceable RESUME are uninstrumented. + // Skip inline cache payloads rather than decoding them as opcodes. + index += 1 + unit.op.cache_entries(); + } + true + } + /// Get the last instruction index. #[inline(always)] pub fn get_lasti(&self) -> u32 { @@ -1228,10 +1298,43 @@ impl InterpreterFrame { #[cold] #[inline(never)] pub(crate) unsafe fn materialize_detached_chain(&self, vm: &VirtualMachine) -> FrameObjectRef { + // SAFETY: forwarded unchanged from this method's contract. + unsafe { self.materialize_detached_chain_impl::(vm) } + } + + /// Copy the complete ancestors used by Python-visible `f_back` traversal. + /// The raw snapshot path above deliberately preserves every active node. + /// + /// # Safety + /// The caller must hold the world stopped and select a complete top frame. + #[cfg(feature = "threading")] + #[cold] + #[inline(never)] + pub(crate) unsafe fn materialize_complete_detached_chain( + &self, + vm: &VirtualMachine, + ) -> FrameObjectRef { + // SAFETY: forwarded unchanged from this method's contract. + unsafe { self.materialize_detached_chain_impl::(vm) } + } + + #[cfg(feature = "threading")] + #[cold] + unsafe fn materialize_detached_chain_impl( + &self, + vm: &VirtualMachine, + ) -> FrameObjectRef { let top = unsafe { self.materialize_detached(vm) }; let mut child = top.clone(); let mut cur = self.previous(); - while !cur.is_null() { + loop { + if SKIP_INCOMPLETE { + // SAFETY: the caller holds the owning thread stopped. + cur = unsafe { first_complete_iframe(cur) }; + } + if cur.is_null() { + break; + } let caller = unsafe { &*cur }; let caller_fo = unsafe { caller.materialize_detached(vm) }; { @@ -1756,6 +1859,7 @@ pub enum ExecutionResult { /// CACHE <- the parked frame's lasti is here + 1 /// YIELD_VALUE 1 /// resumed_at: RESUME +/// CACHE /// JUMP_BACKWARD_NO_INTERRUPT -> send_idx /// exit: END_SEND /// ``` @@ -2529,6 +2633,18 @@ impl Py { self.with_exec(vm, |mut exec| exec.run(vm)) } + /// Execute the eager generator preamble and suspend immediately after + /// RETURN_GENERATOR. The caller links this frame with `vm.with_generator_frame`. + /// The first resume supplies the value consumed by the following POP_TOP. + pub(crate) fn prepare_generator(&self, vm: &VirtualMachine) -> PyResult<()> { + self.with_exec(vm, |mut exec| match exec.run_until::(vm)? { + None => Ok(()), + Some(_) => { + Err(vm.new_runtime_error("generator preamble did not reach RETURN_GENERATOR")) + } + }) + } + /// Resume a suspended generator or coroutine body, pushing `value` as the /// result of the `yield` it stopped at. /// @@ -2788,10 +2904,10 @@ pub(crate) fn gen_collapse_allowed(vm: &VirtualMachine) -> bool { /// delegating to, if the trampoline can resume it in this frame's place. /// /// Recognizes the shape documented on [`GenCont`] — `lasti` at the `RESUME` -/// that follows the delegating `YIELD_VALUE`, with the delegate on top of the -/// stack — and requires the `SEND` to have already specialized to `SendGen`, -/// so that a `Send` still collecting specialization feedback keeps running -/// normally. The frame is left untouched; `park_at_send` commits to it. +/// that follows the delegating `YIELD_VALUE`, with the delegate below its +/// index-or-NULL stack slot — and requires the `SEND` to have already +/// specialized to `SendGen`, so that a `Send` still collecting feedback keeps +/// running normally. The frame is left untouched; `park_at_send` commits to it. pub(crate) fn yield_from_delegate( iframe: &InterpreterFrame, vm: &VirtualMachine, @@ -2824,10 +2940,11 @@ pub(crate) fn yield_from_delegate( { return None; } - let delegate = match iframe.localsplus.stack_last() { - Some(Some(top)) => top.as_object(), - _ => return None, - }; + let stack = iframe.localsplus.stack_as_slice(); + let delegate = stack + .get(stack.len().checked_sub(2)?)? + .as_ref()? + .as_object(); // The same guards `SendGen` applies before resuming a generator itself. if delegate.downcast_ref_if_exact::(vm).is_none() && delegate.downcast_ref_if_exact::(vm).is_none() @@ -3072,7 +3189,8 @@ pub(crate) fn release_datastack_frame(frame: &Py, vm: &VirtualMachi { let mut guard = frame.iframe().cold().retained_back.lock(); if guard.is_none() { - let prev = frame.previous_iframe(); + // SAFETY: this frame is returning into its still-active caller. + let prev = unsafe { first_complete_iframe(frame.previous_iframe()) }; *guard = unsafe { owned_chain_frame(prev) }; } } @@ -3440,7 +3558,11 @@ impl ExecutingFrame<'_> { /// (COPY_FREE_VARS / RETURN_GENERATOR that precede it are not a 'call', /// and later RESUMEs in a SEND loop are not a new call either). fn trace_call_from_resume(&mut self, vm: &VirtualMachine, resume_type: u32) -> PyResult<()> { - if self.call_traced { + // RESUME_AT_GEN_EXPR_START runs before the frame is complete. The + // first traceable instruction is the ordinary RESUME after creation. + if resume_type & ResumeContext::LOCATION_MASK == ResumeLocation::AtGenExprStart.as_u32() + || self.call_traced + { return Ok(()); } self.call_traced = true; @@ -3626,7 +3748,22 @@ impl ExecutingFrame<'_> { /// Matches `_PyEval_MonitorRaise` → `PY_MONITORING_EVENT_RAISE` → /// `sys_trace_exception_func` in legacy_tracing.c. fn fire_exception_trace(&self, exc: &Py, vm: &VirtualMachine) -> PyResult<()> { - if vm.use_tracing.get() && self.trace_is_set(vm) { + if !vm.use_tracing.get() { + return Ok(()); + } + let trace_is_set = if self.iframe().is_incomplete_for_trace() { + // Legacy tracing resolves PyEval_GetFrame to the first complete + // caller while sys.monitoring continues to use this code object. + current_complete_frame_for_trace(vm).is_some_and(|frame| { + frame + .iframe() + .cold_opt() + .is_some_and(|cold| cold.trace.lock().is_some()) + }) + } else { + self.trace_is_set(vm) + }; + if trace_is_set { let exc_type: PyObjectRef = exc.class().to_owned().into(); let exc_value: PyObjectRef = exc.to_owned().into(); let exc_tb: PyObjectRef = exc @@ -3639,6 +3776,14 @@ impl ExecutingFrame<'_> { } fn run(&mut self, vm: &VirtualMachine) -> PyResult { + self.run_until::(vm) + .map(|result| result.expect("ordinary execution cannot stop at a generator preamble")) + } + + fn run_until( + &mut self, + vm: &VirtualMachine, + ) -> PyResult> { flame_guard!(format!( "FrameObject::run({obj_name})", obj_name = self.code.obj_name @@ -3681,7 +3826,7 @@ impl ExecutingFrame<'_> { // principle toggle it, because we refresh `tracing` below right // after that call returns (that cold path is only taken when // tracing was already on, so it costs nothing on the hot path). - let mut tracing = vm.use_tracing.get(); + let mut tracing = !PREPARE_GENERATOR && vm.use_tracing.get(); // Fire 'line' trace event when line number changes. // Only fire if this frame has a per-frame trace function set @@ -3726,7 +3871,7 @@ impl ExecutingFrame<'_> { idx = lasti_cell.load(Relaxed) as usize; continue; } - Ok(Some(value)) => break Ok(value), + Ok(Some(value)) => break Ok(Some(value)), Err(e) => break Err(e), } } @@ -3734,7 +3879,7 @@ impl ExecutingFrame<'_> { // The trace callback may have toggled tracing (e.g. via // sys.settrace(None)); refresh before the opcode-trace check // below reuses this flag. - tracing = vm.use_tracing.get(); + tracing = !PREPARE_GENERATOR && vm.use_tracing.get(); // Trace callback may have changed lasti via set_f_lineno. // Re-read and restart the loop from the new position. if lasti_cell.load(Relaxed) != (idx as u32 + 1) { @@ -3811,9 +3956,10 @@ impl ExecutingFrame<'_> { frame: &mut ExecutingFrame<'_>, exception: PyBaseExceptionRef, idx: usize, + complete_frame: bool, vm: &VirtualMachine, ) -> FrameResult { - if let Some((loc, _end_loc)) = frame.code.locations.get(idx) { + if complete_frame && let Some((loc, _end_loc)) = frame.code.locations.get(idx) { let next = exception.traceback(); let new_traceback = PyTraceback::new( next, @@ -3832,10 +3978,10 @@ impl ExecutingFrame<'_> { }, ) } - match handle_signal_exception(self, exception, idx, vm) { + match handle_signal_exception(self, exception, idx, !PREPARE_GENERATOR, vm) { Ok(None) => {} Ok(Some(value)) => { - break Ok(value); + break Ok(Some(value)); } Err(exception) => { break Err(exception); @@ -3859,6 +4005,13 @@ impl ExecutingFrame<'_> { crate::opcode_histogram::record(prev_op, op_byte); prev_op = op_byte; } + if PREPARE_GENERATOR && matches!(op, Instruction::ReturnGenerator) { + // CPython copies the eager frame at RETURN_GENERATOR with its + // instruction pointer advanced to POP_TOP. Iterator pairs from + // generator expressions must survive the execution borrow too. + self.localsplus.promote_stack(); + break Ok(None); + } let result = self.execute_instruction(op, arg, &mut do_extend_arg, vm); // Skip inline cache entries if instruction fell through (no jump). // `cache_entries()` is a table lookup, so it is done here rather @@ -3874,7 +4027,7 @@ impl ExecutingFrame<'_> { match result { Ok(None) => {} Ok(Some(value)) => { - break Ok(value); + break Ok(Some(value)); } // Instruction raised an exception Err(exception) => { @@ -3885,6 +4038,7 @@ impl ExecutingFrame<'_> { idx: usize, is_reraise: bool, is_new_raise: bool, + complete_frame: bool, vm: &VirtualMachine, ) -> FrameResult { // 1. Extract traceback from exception's '__traceback__' attr. @@ -3903,7 +4057,9 @@ impl ExecutingFrame<'_> { // checking for duplicates. Each time an exception passes through // a frame (e.g., in a loop with repeated raise statements), // a new traceback entry is added. - if let Some((loc, _end_loc)) = frame.code.locations.get(idx) { + if complete_frame + && let Some((loc, _end_loc)) = frame.code.locations.get(idx) + { let next = exception.traceback(); let new_traceback = PyTraceback::new( @@ -4001,11 +4157,19 @@ impl ExecutingFrame<'_> { self.fire_exception_trace(&exception, vm)?; } - match handle_exception(self, exception, idx, is_reraise, is_new_raise, vm) { + match handle_exception( + self, + exception, + idx, + is_reraise, + is_new_raise, + !PREPARE_GENERATOR, + vm, + ) { // The handler unwound to a new position, so the next // index is whatever it left in the frame. Ok(None) => next_idx = lasti_cell.load(Relaxed), - Ok(Some(result)) => break Ok(result), + Ok(Some(result)) => break Ok(Some(result)), Err(exception) => { // Fire PY_UNWIND: exception escapes this frame let exception = if vm.state.monitoring_events.load() @@ -4040,16 +4204,18 @@ impl ExecutingFrame<'_> { // In RustPython, we check: // 1. lasti points to RESUME (after YIELD_VALUE) // 2. The previous instruction was YIELD_VALUE with arg >= 1 - // 3. Stack top is the delegate (receiver) + // 3. The delegate (receiver) is below its index-or-NULL slot // // First check if stack is empty - if so, we can't be in yield-from - if self.localsplus.stack_is_empty() { + if self.localsplus.stack_len() < 2 { return None; } let lasti = self.lasti() as usize; if let Some(unit) = self.code.instructions.get(lasti) { match &unit.op { - Instruction::Send { .. } => return Some(self.top_value()), + Instruction::Send { .. } | Instruction::SendGen => { + return Some(self.nth_value(1)); + } Instruction::Resume { .. } | Instruction::InstrumentedResume => { // Check if previous instruction was YIELD_VALUE with arg >= 1 // This indicates yield-from/await context @@ -4063,8 +4229,8 @@ impl ExecutingFrame<'_> { // YIELD_VALUE arg: 0 = direct yield, >= 1 = yield-from/await // OpArgByte.0 is the raw byte value if u8::from(prev_unit.arg) >= 1 { - // In yield-from/await context, delegate is on top of stack - return Some(self.top_value()); + // The iterator pair remains after YIELD_VALUE. + return Some(self.nth_value(1)); } } } @@ -4920,36 +5086,40 @@ impl ExecutingFrame<'_> { self.push_value(iter); Ok(None) } - Instruction::GetIter => { - let iterated_obj = self.pop_value(); - let iter_obj = PyIter::try_from_object(vm, iterated_obj)?; - self.push_value(iter_obj.into()); - Ok(None) - } - Instruction::GetYieldFromIter => { - // GET_YIELD_FROM_ITER: prepare iterator for yield from - // If iterable is a coroutine, ensure we're in a coroutine context - // If iterable is a generator, use it directly - // Otherwise, call iter() on it + Instruction::GetIter { mode } => { + // Include/internal/pycore_opcode_utils.h GET_ITER_YIELD_FROM_*. + const GET_ITER_ORDINARY: u32 = 0; + const GET_ITER_YIELD_FROM: u32 = 1; + const GET_ITER_YIELD_FROM_NO_CHECK: u32 = 2; + const GET_ITER_YIELD_FROM_CORO_CHECK: u32 = 3; + let iterable = self.pop_value(); - let iter = if iterable.class().is(vm.ctx.types.coroutine_type) { - // Coroutine requires CO_COROUTINE or CO_ITERABLE_COROUTINE flag - if !self.code.flags.intersects( - bytecode::CodeFlags::COROUTINE | bytecode::CodeFlags::ITERABLE_COROUTINE, - ) { + let mode = mode.get(arg); + let iter = if mode != GET_ITER_ORDINARY + && iterable.class().is(vm.ctx.types.coroutine_type) + { + // Code construction resolves the compiler-only mode 1 + // using CO_COROUTINE / CO_ITERABLE_COROUTINE. + let reject_coroutine = match mode { + GET_ITER_YIELD_FROM_CORO_CHECK => true, + GET_ITER_YIELD_FROM_NO_CHECK => false, + GET_ITER_YIELD_FROM => !self.code.flags.intersects( + bytecode::CodeFlags::COROUTINE + | bytecode::CodeFlags::ITERABLE_COROUTINE, + ), + _ => false, + }; + if reject_coroutine { return Err(vm.new_type_error( "cannot 'yield from' a coroutine object in a non-coroutine generator", )); } iterable - } else if iterable.class().is(vm.ctx.types.generator_type) { - // Generator can be used directly - iterable } else { - // Otherwise, get iterator PyIter::try_from_object(vm, iterable)?.into() }; self.push_value(iter); + self.push_null(); Ok(None) } Instruction::GetLen => { @@ -4965,7 +5135,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 } => { @@ -5144,6 +5314,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) @@ -5620,8 +5795,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 => { @@ -5630,7 +5805,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) } @@ -5935,10 +6111,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()) @@ -5946,7 +6122,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) { @@ -5976,8 +6152,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() @@ -5989,7 +6165,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 @@ -6024,7 +6200,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); @@ -6043,9 +6219,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) @@ -6062,39 +6238,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) @@ -7952,7 +8113,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()); @@ -7967,7 +8128,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); @@ -7982,7 +8143,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); @@ -7997,7 +8158,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); @@ -8289,10 +8450,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(); @@ -8304,6 +8465,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 @@ -8392,6 +8554,7 @@ impl ExecutingFrame<'_> { } Instruction::InstrumentedPopIter => { // BRANCH_RIGHT is fired by InstrumentedForIter, not here. + self.pop_stackref_opt(); self.pop_stackref(); Ok(None) } @@ -9459,7 +9622,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() @@ -9483,14 +9646,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) { @@ -9521,10 +9684,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), } } @@ -11426,8 +11586,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) { @@ -11693,7 +11853,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 49172a152..37ecd1020 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -262,7 +262,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/sys.rs b/crates/vm/src/stdlib/sys.rs index 28f107862..bb35df000 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -668,7 +668,7 @@ pub mod sys { const NAME: &str = "rustpython"; // Keep implementation-specific bytecode caches separate. - let cache_tag = format!("{NAME}-{}{}", version::MAJOR, version::MINOR); + let cache_tag = version::PYC_CACHE_TAG; let ctx = &vm.ctx; py_namespace!(vm, { "name" => ctx.new_str(NAME), diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index aa180daeb..930bef9cd 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -404,8 +404,10 @@ pub(crate) fn instrument_code(code: &Py, events: u32) { // Find _co_firsttraceable: index of first RESUME instruction let mut first_traceable = None; - for_each_instruction(code, |i, op, _| { + for_each_instruction(code, |i, op, oparg| { if first_traceable.is_none() + && u32::from(oparg) & crate::bytecode::oparg::ResumeContext::LOCATION_MASK + != crate::bytecode::oparg::ResumeLocation::AtGenExprStart.as_u32() && matches!( op, Instruction::Resume { .. } @@ -421,7 +423,7 @@ pub(crate) fn instrument_code(code: &Py, events: u32) { // Phase 4: Place regular INSTRUMENTED_* opcodes whose event is active. // Walk by cache_entries so specialized CACHE payloads are not decoded as opcodes. { - let mut i = 0; + let mut i = first_traceable; while i < len { let op = code.code.instructions.read_op(i); let oparg = code.code.instructions.read_arg(i).as_u8(); diff --git a/crates/vm/src/version.rs b/crates/vm/src/version.rs index 05eb12a29..a2c6f42de 100644 --- a/crates/vm/src/version.rs +++ b/crates/vm/src/version.rs @@ -68,9 +68,10 @@ pub const RUSTPYTHON_VERSION: &str = const { } }; -// Must be aligned to Lib/importlib/_bootstrap_external.py -// Matches CPython 3.14 (Include/internal/pycore_magic_number.h). -pub const PYC_MAGIC_NUMBER: u16 = 3627; +// Private, non-distributable reconstruction ABI. This is not CPython's magic. +// Change token and tag together for every incompatible development format. +pub const PYC_MAGIC_NUMBER: u16 = 0xDA16; +pub const PYC_CACHE_TAG: &str = "rustpython-315-devabi2"; // CPython format: magic_number | ('\r' << 16) | ('\n' << 24) // This protects against text-mode file reads diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 304991f95..2ee22d3a5 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2642,6 +2642,24 @@ impl VirtualMachine { &self, frame: FrameObjectRef, f: F, + ) -> PyResult { + self.with_frame_impl::(frame, f) + } + + /// Generator construction runs an incomplete frame whose caller link must + /// not escape. RETURN_GENERATOR copies the frame with previous=NULL in CPython. + pub(crate) fn with_generator_frame PyResult>( + &self, + frame: FrameObjectRef, + f: F, + ) -> PyResult { + self.with_frame_impl::(frame, f) + } + + fn with_frame_impl PyResult>( + &self, + frame: FrameObjectRef, + f: F, ) -> PyResult { self.check_recursive_call("")?; @@ -2688,7 +2706,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 @@ -2699,8 +2717,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()); } } @@ -2826,8 +2847,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); } @@ -3013,10 +3035,19 @@ impl VirtualMachine { if result.is_err() && self.use_tracing.get() && (!self.is_none(&self.profile_func.borrow()) - || frame - .iframe() - .cold_opt() - .is_some_and(|c| c.trace.lock().is_some())) + || if frame.iframe().is_incomplete_for_trace() { + crate::frame::current_complete_frame_for_trace(self).is_some_and(|visible| { + visible + .iframe() + .cold_opt() + .is_some_and(|c| c.trace.lock().is_some()) + }) + } else { + frame + .iframe() + .cold_opt() + .is_some_and(|c| c.trace.lock().is_some()) + }) { let ret_result = self.trace_event_what( TraceEvent::Return, diff --git a/crates/vm/tests/fixtures/private_cache_bytes.rs b/crates/vm/tests/fixtures/private_cache_bytes.rs new file mode 100644 index 000000000..9865ebc85 --- /dev/null +++ b/crates/vm/tests/fixtures/private_cache_bytes.rs @@ -0,0 +1,9 @@ +// Fresh private devabi2 fixture: compile("pass\n", "byte_exact/embedded_cache.py", "exec"). +const PRIVATE_PYC: &[u8] = &[ + 22, 218, 13, 10, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 115, 8, 0, 0, 0, 128, 0, 0, 0, 80, 7, 33, 0, 40, 1, 0, 0, 0, 78, 40, 0, + 0, 0, 0, 40, 0, 0, 0, 0, 115, 0, 0, 0, 0, 122, 28, 98, 121, 116, 101, 95, 101, 120, 97, 99, + 116, 47, 101, 109, 98, 101, 100, 100, 101, 100, 95, 99, 97, 99, 104, 101, 46, 112, 121, 122, 8, + 60, 109, 111, 100, 117, 108, 101, 62, 122, 8, 60, 109, 111, 100, 117, 108, 101, 62, 1, 0, 0, 0, + 115, 8, 0, 0, 0, 241, 3, 1, 1, 1, 217, 0, 4, 115, 0, 0, 0, 0, +]; diff --git a/crates/vm/tests/private_cache_reader.rs b/crates/vm/tests/private_cache_reader.rs new file mode 100644 index 000000000..19a680a53 --- /dev/null +++ b/crates/vm/tests/private_cache_reader.rs @@ -0,0 +1,38 @@ +//! Direct embedding API format boundary. Python behavior is tested in snippets. + +#![allow(clippy::tests_outside_test_module)] + +use rustpython_vm::{AsObject, Interpreter, builtins::PyCode}; + +include!("fixtures/private_cache_bytes.rs"); + +#[test] +fn foreign_formats_are_rejected_before_bootstrap_lookup() { + Interpreter::without_stdlib(Default::default()).enter(|vm| { + for magic in [3627u16, 3666, 0xDA15] { + let mut bytes = PRIVATE_PYC.to_vec(); + bytes[..2].copy_from_slice(&magic.to_le_bytes()); + let error = PyCode::from_pyc(&bytes, None, None, None, vm).unwrap_err(); + assert!(error.fast_isinstance(vm.ctx.exceptions.value_error)); + } + }); +} + +#[cfg(feature = "importlib")] +#[test] +fn private_format_loads_and_runs_with_external_bootstrap() { + Interpreter::without_stdlib(Default::default()).enter(|vm| { + assert_eq!( + &PRIVATE_PYC[..4], + &rustpython_vm::version::PYC_MAGIC_NUMBER_BYTES + ); + let code = PyCode::from_pyc(PRIVATE_PYC, None, None, None, vm).unwrap(); + assert_eq!(code.arg_count, 0); + let scope = vm.new_scope_with_main().unwrap(); + vm.run_pyc_bytes(PRIVATE_PYC, scope) + .unwrap_or_else(|error| { + vm.print_exception(&error); + panic!("private embedded pyc execution failed"); + }); + }); +} diff --git a/extra_tests/snippets/compiler_315_warmed_export.py b/extra_tests/snippets/compiler_315_warmed_export.py new file mode 100644 index 000000000..4034f5153 --- /dev/null +++ b/extra_tests/snippets/compiler_315_warmed_export.py @@ -0,0 +1,70 @@ +"""Ordinary warmed exports retain public opcodes and unchanged raw fields.""" + +import marshal +import sys +import types + +if sys.version_info < (3, 15): + sys.exit(0) + + +source = """ +def direct(x): + return x + 1000 +def looping(xs): + total = 0 + for x in xs: + total += direct(x) + return total +def delegate(xs): + yield from xs +""" +root = compile(source, "byte_exact/warmed_export.py", "exec") +namespace = {} +exec(root, namespace) +codes = [value for value in root.co_consts if isinstance(value, types.CodeType)] +before = [(code.co_code, code.co_linetable, code.co_exceptiontable) for code in codes] +for _ in range(100): + assert namespace["looping"]([1, 2, 3]) == 3006 + assert list(namespace["delegate"]([1, 2])) == [1, 2] +after = [(code.co_code, code.co_linetable, code.co_exceptiontable) for code in codes] +assert before == after + +# Generated cache widths in final 3.15 pycore_opcode_metadata.h. Operand bytes +# are not opcode positions and must not be rejected by a byte-membership test. +cache_widths = { + 128: 1, + 37: 3, + 36: 1, + 105: 1, + 68: 1, + 118: 1, + 109: 4, + 91: 4, + 95: 1, + 79: 9, + 54: 1, + 55: 1, + 74: 1, + 102: 1, + 99: 1, + 100: 1, + 101: 1, + 70: 1, + 50: 3, + 53: 3, + 4: 1, + 42: 5, +} +for code in codes: + exported = code.co_code + index = 0 + while index < len(exported): + opcode = exported[index] + assert opcode not in (219, 220, 221) + index += 2 * (1 + cache_widths.get(opcode, 0)) + assert index == len(exported) + loaded = marshal.loads(marshal.dumps(code, 6)) + assert loaded.co_code == exported + assert loaded.co_linetable == code.co_linetable + assert loaded.co_exceptiontable == code.co_exceptiontable diff --git a/extra_tests/snippets/iterator_contract_315.py b/extra_tests/snippets/iterator_contract_315.py new file mode 100644 index 000000000..22596f214 --- /dev/null +++ b/extra_tests/snippets/iterator_contract_315.py @@ -0,0 +1,501 @@ +"""CPython 3.15 iterator-pair, generator-creation, and SEND regression checks.""" + + +def test_iterator_pairs_survive_specialized_loops_and_unwinding(): + class Iterator: + def __init__(self): + self.index = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.index == 4: + raise StopIteration + value = self.index + self.index += 1 + return value + + def values(): + yield from range(4) + + def consume(items): + total = 0 + for item in items: + total += item + return total + + def leave(items): + for item in items: + for inner in range(3): + if inner == 1: + return item + inner + return -1 + + for warmup in range(200): + assert consume(range(4)) == 6 + assert consume([0, 1, 2, 3]) == 6 + assert consume((0, 1, 2, 3)) == 6 + assert consume(values()) == 6 + assert consume(Iterator()) == 6 + assert leave(range(4)) == 1 + assert [x * y for x in range(3) for y in range(2)] == [0, 0, 0, 1, 0, 2] + for x in range(5): + if x == 2: + break + assert x == 2 + + +def test_eager_generator_prefix_preserves_laziness_and_created_state(): + events = [] + + class Iterable: + def __iter__(self): + events.append("iter") + return iter((4, 5)) + + def body(value): + events.append(value) + return value + + g = (body(value) for value in Iterable()) + assert events == ["iter"] + assert not g.gi_suspended + try: + g.send(1) + except TypeError: + pass + else: + raise AssertionError("created generator accepted non-None") + assert events == ["iter"] + assert next(g) == 4 + assert events == ["iter", 4] + assert g.gi_suspended + assert list(g) == [5] + + class BadIterable: + def __iter__(self): + raise ValueError("eager") + + try: + (value for value in BadIterable()) + except ValueError as error: + assert error.args == ("eager",) + else: + raise AssertionError("iterator acquisition was deferred") + + def outer(value): + def generator(): + events.append("body") + yield value + + return generator() + + g = outer(17) + assert "body" not in events + g.close() + assert "body" not in events + assert list(outer(19)) == [19] + + +def test_send_pairs_support_delegation_and_throw_cleanup(): + def leaf(): + sent = yield 1 + return sent + + def chain(depth): + if depth: + return (yield from chain(depth - 1)) + return (yield from leaf()) + + for warmup in range(200): + g = chain(12) + assert next(g) == 1 + assert g.gi_yieldfrom is not None + try: + g.send(17) + except StopIteration as error: + assert error.value == 17 + else: + raise AssertionError("delegation did not finish") + + class Delegate: + def __iter__(self): + return self + + def __next__(self): + return 3 + + def send(self, value): + return value + + def throw(self, error): + raise StopIteration(23) + + def custom(): + result = yield from Delegate() + yield result + + g = custom() + assert next(g) == 3 + assert g.send(9) == 9 + assert g.throw(ValueError) == 23 + assert list(g) == [] + + class RaisingDelegate(Delegate): + def throw(self, error): + raise RuntimeError("delegate") + + def raises(): + try: + yield from RaisingDelegate() + except RuntimeError: + yield "caught" + + g = raises() + assert next(g) == 3 + assert g.throw(RuntimeError) == "caught" + assert list(g) == [] + + events = [] + + def close_leaf(): + try: + yield 1 + finally: + events.append("closed") + + def close_outer(): + yield from close_leaf() + + g = close_outer() + assert next(g) == 1 + g.close() + assert events == ["closed"] + + +def test_await_and_async_generator_expression_keep_iterator_shapes(): + events = [] + + class AsyncIterator: + def __init__(self): + self.index = 0 + + def __aiter__(self): + events.append("aiter") + return self + + async def __anext__(self): + if self.index == 3: + raise StopAsyncIteration + value = self.index + self.index += 1 + return value + + g = (value * 2 async for value in AsyncIterator()) + assert events == ["aiter"] + assert not g.ag_running + + async def consume(): + result = [] + async for value in g: + result.append(value) + return result + + coro = consume() + try: + coro.send(None) + except StopIteration as error: + assert error.value == [0, 2, 4] + else: + raise AssertionError("unexpected coroutine suspension") + assert events == ["aiter"] + + class Awaitable: + def __await__(self): + sent = yield "pause" + return sent + + async def awaits(): + return await Awaitable() + + for warmup in range(200): + coro = awaits() + assert coro.send(None) == "pause" + assert coro.cr_await is not None + try: + coro.send(31) + except StopIteration as error: + assert error.value == 31 + else: + raise AssertionError("await did not finish") + + +test_iterator_pairs_survive_specialized_loops_and_unwinding() +test_eager_generator_prefix_preserves_laziness_and_created_state() +test_send_pairs_support_delegation_and_throw_cleanup() +test_await_and_async_generator_expression_keep_iterator_shapes() + + +def test_eager_prefix_is_untraceable_but_reports_monitoring_errors(): + import sys + + trace = [] + + class Good: + def __iter__(self): + return iter([1]) + + class Bad: + def __iter__(self): + raise ValueError("eager iterator") + + def create(iterable): + return (value for value in iterable) + + def tracer(frame, event, arg): + if frame.f_code.co_name in ("create", "", "__iter__"): + trace.append((frame.f_code.co_name, event)) + return tracer + + sys.settrace(tracer) + try: + generator = create(Good()) + generator.close() + try: + create(Bad()) + except ValueError as error: + names = [] + traceback = error.__traceback__ + while traceback is not None: + names.append(traceback.tb_frame.f_code.co_name) + traceback = traceback.tb_next + else: + raise AssertionError("expected eager iterator error") + finally: + sys.settrace(None) + assert not any(name == "" for name, event in trace) + assert trace == [ + ("create", "call"), + ("create", "line"), + ("__iter__", "call"), + ("__iter__", "line"), + ("__iter__", "return"), + ("create", "return"), + ("create", "call"), + ("create", "line"), + ("__iter__", "call"), + ("__iter__", "line"), + ("__iter__", "exception"), + ("__iter__", "return"), + ("create", "exception"), + ("create", "return"), + ("create", "exception"), + ("create", "return"), + ] + assert ("__iter__", "call") in trace + assert ("__iter__", "exception") in trace + assert names == [ + "test_eager_prefix_is_untraceable_but_reports_monitoring_errors", + "create", + "__iter__", + ] + + monitoring = sys.monitoring + records = [] + event_names = ( + "PY_START", + "PY_RESUME", + "PY_UNWIND", + "RAISE", + "LINE", + "INSTRUCTION", + ) + monitoring.use_tool_id(2, "iterator-contract-315") + try: + for name in event_names: + event = getattr(monitoring.events, name) + + def callback(code, *args, name=name): + if code.co_name == "": + records.append(name) + + monitoring.register_callback(2, event, callback) + monitoring.set_events( + 2, sum(getattr(monitoring.events, name) for name in event_names) + ) + generator = create(Good()) + generator.close() + assert records == [] + try: + create(Bad()) + except ValueError: + pass + else: + raise AssertionError("expected monitored eager iterator error") + finally: + monitoring.set_events(2, 0) + monitoring.free_tool_id(2) + assert records == ["RAISE", "PY_UNWIND"] + + +test_eager_prefix_is_untraceable_but_reports_monitoring_errors() + + +def test_created_generator_frames_do_not_retain_materialized_caller(): + import sys + + def generator(): + yield 1 + + async def coroutine(): + return 1 + + async def async_generator(): + yield 1 + + def create(): + caller = sys._getframe() + return ( + (value for value in [1]), + generator(), + coroutine(), + async_generator(), + caller, + ) + + genexpr, gen, coro, agen, caller = create() + assert caller.f_code.co_name == "create" + assert genexpr.gi_frame.f_back is None + assert gen.gi_frame.f_back is None + assert coro.cr_frame.f_back is None + assert agen.ag_frame.f_back is None + genexpr.close() + gen.close() + coro.close() + close = agen.aclose() + try: + close.send(None) + except StopIteration: + pass + else: + raise AssertionError("unstarted async generator close suspended") + + +test_created_generator_frames_do_not_retain_materialized_caller() + + +def test_instrumented_iteration_and_send_keep_both_iterator_slots(): + import sys + + def iterate(values): + result = [] + for value in values: + result.append(value) + return result + + def leaf(): + yield 1 + return 9 + + def delegate(): + return (yield from leaf()) + + monitoring = sys.monitoring + records = [] + event_names = ("BRANCH_LEFT", "BRANCH_RIGHT", "STOP_ITERATION", "PY_YIELD") + monitoring.use_tool_id(2, "instrumented-iterator-contract-315") + try: + for name in event_names: + event = getattr(monitoring.events, name) + + def callback(code, *args, name=name): + if code.co_name in ("iterate", "delegate"): + records.append((code.co_name, name)) + + monitoring.register_callback(2, event, callback) + monitoring.set_events( + 2, sum(getattr(monitoring.events, name) for name in event_names) + ) + assert iterate(range(3)) == [0, 1, 2] + assert iterate([0, 1, 2]) == [0, 1, 2] + assert iterate((0, 1, 2)) == [0, 1, 2] + assert iterate(value for value in range(3)) == [0, 1, 2] + generator = delegate() + assert next(generator) == 1 + try: + next(generator) + except StopIteration as error: + assert error.value == 9 + else: + raise AssertionError("instrumented delegate did not finish") + finally: + monitoring.set_events(2, 0) + monitoring.free_tool_id(2) + assert ("iterate", "BRANCH_LEFT") in records + assert ("iterate", "BRANCH_RIGHT") in records + assert ("delegate", "PY_YIELD") in records + assert ("delegate", "STOP_ITERATION") in records + + +test_instrumented_iteration_and_send_keep_both_iterator_slots() + + +def test_eager_iterator_frame_inspection_skips_incomplete_generator(): + import sys + + iterator_frames = [] + caller_frames = [] + seen = [] + + class Iterable: + def __iter__(self): + frame = sys._getframe() + caller = sys._getframe(1) + iterator_frames.append(frame) + caller_frames.append(caller) + seen.append((caller.f_code.co_name, frame.f_back.f_code.co_name)) + return iter([7]) + + def create(): + return (value for value in Iterable()) + + generator = create() + assert seen == [("create", "create")] + # Both real frames have returned; retained f_back must still refer to + # the complete creator rather than its hidden generator preamble. + assert iterator_frames[0].f_back is caller_frames[0] + assert iterator_frames[0].f_back.f_code.co_name == "create" + assert generator.gi_frame.f_back is None + assert list(generator) == [7] + + +test_eager_iterator_frame_inspection_skips_incomplete_generator() + + +def test_unstarted_coroutine_warning_survives_creation_preamble(): + import gc + import warnings + + async def pending(): + return 7 + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", RuntimeWarning) + coroutine = pending() + del coroutine + gc.collect() + assert len(caught) == 1 + assert caught[0].category is RuntimeWarning + assert str(caught[0].message).endswith("pending' was never awaited") + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", RuntimeWarning) + coroutine = pending() + coroutine.close() + del coroutine + gc.collect() + assert caught == [] + + +test_unstarted_coroutine_warning_survives_creation_preamble() diff --git a/extra_tests/snippets/private_315_cache_contract.py b/extra_tests/snippets/private_315_cache_contract.py new file mode 100644 index 000000000..334fd9426 --- /dev/null +++ b/extra_tests/snippets/private_315_cache_contract.py @@ -0,0 +1,109 @@ +"""Private development ABI reader/writer boundary; no public compatibility claim.""" + +import _frozen_importlib_external as external +import importlib +import importlib.util +import py_compile +import sys +import tempfile +import zipfile +from pathlib import Path + +if sys.implementation.name == "rustpython": + assert external.MAGIC_NUMBER == bytes((0x16, 0xDA, 13, 10)) + assert sys.implementation.cache_tag == "rustpython-315-devabi2" + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "cache_subject.py" + source.write_text("value = 41\n", encoding="ascii") + code = compile("value = 42\n", "byte_exact/cache_subject.py", "exec") + stat = source.stat() + headers = [ + external._code_to_timestamp_pyc(code, int(stat.st_mtime), stat.st_size), + external._code_to_hash_pyc( + code, importlib.util.source_hash(source.read_bytes()), True + ), + external._code_to_hash_pyc( + code, importlib.util.source_hash(source.read_bytes()), False + ), + ] + rejected = [3627, 3666, 0xDA15] + current = 0xDA16 + + def load_from(path): + sys.modules.pop("cache_subject", None) + sys.path.insert(0, str(path)) + importlib.invalidate_caches() + try: + return importlib.import_module("cache_subject").value + finally: + sys.path.pop(0) + sys.modules.pop("cache_subject", None) + + # Source caches must fall back to source on every foreign magic. + cache = Path(importlib.util.cache_from_source(str(source))) + cache.parent.mkdir() + for payload in headers: + for magic in rejected + [current]: + candidate = magic.to_bytes(2, "little") + b"\r\n" + payload[4:] + cache.write_bytes(candidate) + assert load_from(root) == (42 if magic == current else 41) + + # Sourceless files cannot silently fall back or accept foreign formats. + source.unlink() + sourceless = root / "cache_subject.pyc" + for payload in headers: + for magic in rejected + [current]: + sourceless.write_bytes( + magic.to_bytes(2, "little") + b"\r\n" + payload[4:] + ) + if magic == current: + assert load_from(root) == 42 + else: + try: + load_from(root) + except ImportError: + pass + else: + raise AssertionError("foreign sourceless cache accepted") + + # ZIP source fallback and pyc-only rejection use the same token. + for header_index, payload in enumerate(headers): + for with_source in (False, True): + for magic in rejected + [current]: + archive = root / f"case_{header_index}_{with_source}_{magic}.zip" + with zipfile.ZipFile(archive, "w") as output: + output.writestr( + "cache_subject.pyc", + magic.to_bytes(2, "little") + b"\r\n" + payload[4:], + ) + if with_source: + output.writestr("cache_subject.py", "value = 41\n") + if magic == current or with_source: + value = load_from(archive) + if magic != current: + assert value == 41 + elif with_source and header_index == 0: + # ZIP timestamp validation may select the source. + assert value in (41, 42) + else: + assert value == 42 + else: + try: + load_from(archive) + except ImportError: + pass + else: + raise AssertionError("foreign ZIP cache accepted") + + # Explicit py_compile writes under -B; all writers must use the private token. + source.write_text("value = 41\n", encoding="ascii") + explicit = root / "explicit.pyc" + previous = sys.dont_write_bytecode + sys.dont_write_bytecode = True + try: + py_compile.compile(str(source), cfile=str(explicit), doraise=True) + assert explicit.read_bytes()[:4] == external.MAGIC_NUMBER + finally: + sys.dont_write_bytecode = previous diff --git a/scripts/byte_exact_capture.py b/scripts/byte_exact_capture.py new file mode 100644 index 000000000..90b750260 --- /dev/null +++ b/scripts/byte_exact_capture.py @@ -0,0 +1,101 @@ +"""Compile one ordinary request; emit unmodified marshal before field inspection.""" + +import json +import marshal +import struct +import sys +import types + +FIELDS = ( + "co_argcount", + "co_posonlyargcount", + "co_kwonlyargcount", + "co_nlocals", + "co_stacksize", + "co_flags", + "co_code", + "co_consts", + "co_names", + "co_varnames", + "co_freevars", + "co_cellvars", + "co_filename", + "co_name", + "co_qualname", + "co_firstlineno", + "co_linetable", + "co_exceptiontable", +) + + +def describe(value): + kind = type(value) + if kind is types.CodeType: + return { + "type": "code", + "fields": {name: describe(getattr(value, name)) for name in FIELDS}, + } + if value is None or value is Ellipsis: + return {"type": kind.__name__} + if kind in (bool, int, str): + return {"type": kind.__name__, "value": value} + if kind is bytes: + return {"type": "bytes", "value": list(value)} + if kind is float: + return {"type": "float", "bits": list(struct.pack(" U:\n return x\n", + "mode": "exec", + "flags": 16777216 + }, + { + "name": "formatting", + "source": "result = f\"{value!r:>10}\"\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "template", + "source": "result = t\"value={value}\"\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "matching", + "source": "match value:\n case [a, b]:\n result = a + b\n case _:\n result = None\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "yield_plain", + "source": "def f():\n yield 1\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "import_module", + "source": "import os\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "import_function", + "source": "def f():\n import os\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "import_handler", + "source": "try:\n import os\nexcept ImportError:\n import sys\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "genexpr_list", + "source": "def f(xs):\n return list(x + 1 for x in xs if x)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "genexpr_set", + "source": "def f(xs):\n return set(x + 1 for x in xs if x)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "genexpr_tuple", + "source": "def f(xs):\n return tuple(x + 1 for x in xs if x)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "async_genexpr", + "source": "async def f(xs):\n return list(x async for x in xs)\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "borrow_locals", + "source": "def f(a, b):\n return a, b, a\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "exception_alias", + "source": "try:\n work()\nexcept Exception as error:\n result = error\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "assembly_siblings", + "source": "def left():\n return 1\ndef right():\n return 1\n", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_if_ors", + "source": "if True or False or False:\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_if_ands", + "source": "if True and False and False:\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_if_mixed", + "source": "if (True and False) or (False and True):\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_nested_bool_op", + "source": "x = Test() and False or False", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_const_no_op", + "source": "x = not True", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_constant_true_if_pass_keeps_line_anchor_nop", + "source": "if 1:\n pass", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_nested_double_async_with", + "source": "async def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')", + "mode": "exec", + "flags": 0 + }, + { + "name": "dis_bare_function_annotations_check_attribute_and_subscript_expressions", + "source": "def f(one: int):\n int.new_attr: int\n [list][0].new_attr: [int, str]\n my_lst = [1]\n my_lst[one]: int\n return my_lst", + "mode": "exec", + "flags": 0 + } +] diff --git a/scripts/test_byte_exact_compile.py b/scripts/test_byte_exact_compile.py new file mode 100644 index 000000000..f07a20ca3 --- /dev/null +++ b/scripts/test_byte_exact_compile.py @@ -0,0 +1,112 @@ +"""Controller integrity checks; these do not test interpreter behavior.""" + +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + +import byte_exact_capture +from byte_exact_compile import ( + capture_environment, + compare_captures, + differences, + requests, + validate_cache_prefix, +) + + +class CorpusTests(unittest.TestCase): + def setUp(self): + self.fixture = { + "name": "ordinary", + "source": "x = 1\n", + "mode": "exec", + "flags": 0, + } + + def test_empty_corpus_is_an_error(self): + with self.assertRaisesRegex(ValueError, "empty"): + requests([], []) + + def test_unknown_selection_is_an_error(self): + with self.assertRaisesRegex(ValueError, "unknown"): + requests([self.fixture], ["missing"]) + + def test_duplicate_names_are_an_error(self): + with self.assertRaisesRegex(ValueError, "duplicate"): + requests([self.fixture, self.fixture], []) + + def test_explicit_selection_retains_all_optimizations(self): + corpus = requests([self.fixture], ["ordinary"]) + self.assertEqual([r["optimize"] for r in corpus], [0, 1, 2]) + self.assertTrue(all(r["filename"] == "byte_exact/ordinary.py" for r in corpus)) + + def test_ascii_boundary(self): + with self.assertRaisesRegex(ValueError, "ASCII"): + requests([{**self.fixture, "source": "x = 'é'"}], []) + + def test_equal_fields(self): + self.assertEqual(differences({"a": [1, 2]}, {"a": [1, 2]}), []) + + def test_byte_difference_is_retained(self): + self.assertEqual(differences([1, 128], [1, 0], "marshal"), ["marshal/1"]) + + def test_nested_difference_has_single_leaf_path(self): + self.assertEqual( + differences({"code": {"flags": 1}}, {"code": {"flags": 2}}), ["/code/flags"] + ) + + def test_length_difference_is_not_normalized(self): + self.assertEqual(differences([1, 0], [1], "code"), ["code"]) + + def test_types_are_not_coerced(self): + self.assertEqual(differences(True, 1, "constant"), ["constant"]) + + def test_cache_prefix_is_explicit_in_sanitized_environment(self): + with TemporaryDirectory() as directory: + prefix = validate_cache_prefix(directory) + env = capture_environment(None, prefix) + self.assertEqual(env["PYTHONPYCACHEPREFIX"], str(prefix)) + self.assertEqual(env["PYTHONDONTWRITEBYTECODE"], "1") + self.assertNotIn("RUSTPYTHONPATH", env) + + def test_populated_cache_prefix_is_rejected(self): + with TemporaryDirectory() as directory: + (Path(directory) / "existing.pyc").write_bytes(b"cache") + with self.assertRaisesRegex(ValueError, "empty"): + validate_cache_prefix(directory) + + def test_runtime_provenance_is_not_a_comparison_field(self): + reference = {"marshal": [1], "magic": [2], "code": {}, "runtime": "reference"} + candidate = {**reference, "runtime": "candidate"} + self.assertEqual( + compare_captures(reference, candidate), + {"marshal": [], "magic": [], "code": []}, + ) + + def test_runtime_provenance_follows_serialization_and_fields(self): + events = [] + request = {**self.fixture, "filename": "ordinary.py", "optimize": 0} + with ( + patch.object( + byte_exact_capture.marshal, + "dumps", + side_effect=lambda *a: events.append("marshal") or b"raw", + ), + patch.object( + byte_exact_capture, + "describe", + side_effect=lambda value: events.append("fields") or {}, + ), + patch.object( + byte_exact_capture, + "runtime_provenance", + side_effect=lambda: events.append("runtime") or {}, + ), + ): + byte_exact_capture.capture(request) + self.assertEqual(events, ["marshal", "fields", "runtime"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md b/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md new file mode 100644 index 000000000..577bebabd --- /dev/null +++ b/tools/opcode_metadata/COMPILER_315_DEVELOPMENT.md @@ -0,0 +1,78 @@ +# Final 3.15 compiler ABI reconstruction + +This is new work based on public RustPython commit +`06007c81fa99e813cbf4583dab326764ba0ef017`. Earlier local checkpoints were lost +with their execution environment. Their historical results are not validation +of this reconstruction. + +The source target is CPython v3.15.0, +`d220409a13b77de7a6c30abe16ea90db01528edc`. `target_source.json` pins the +generator dependencies and directly consulted ABI sources. The active metadata +generator verifies those hashes before importing the CPython generator modules. +The target generator directory must also be free of bytecode caches; the gate +rejects existing pycs and disables writes before importing from the verified path. +`verify_stack_contract.py` compiles the pinned C metadata independently and +checks 3,430 argument-specific stack rows; Rust tests compare every supported +opcode against those rows. Unsupported specialized instructions are not claimed +implemented merely because their reference rows are recorded. + +## Private cache contract + +The intermediate ABI uses magic `0xDA16` (bytes `16 da 0d 0a`) and cache tag +`rustpython-315-devabi2`. Available repository and pinned-source definitions were +checked for collisions; this is not a globally reserved token. The lost +`0xDA15` development format is deliberately rejected, as are public CPython +formats. Change both token and tag whenever the intermediate format changes +incompatibly. + +The shared Rust magic feeds importlib, source and sourceless caches, ZIP imports, +explicit `py_compile`/`compileall` writers, and direct Rust pyc readers. `-B` +does not prohibit cache reads or explicit writes. Validation uses a fresh owned +Lib copy, records cache inventories, and checks foreign timestamp/hash caches +and ZIP fallback/rejection. The CLI prepends `BUILDTIME_RUSTPYTHONPATH` ahead of +`RUSTPYTHONPATH`; a new environment path alone does not isolate cache reads. +Strict captures must verify actual module paths and keep their embedded Lib copy +separate from cache-writing Rust embedding tests. Headerless marshal and explicit frozen-data input +remain caller-version-coupled. Frozen modules must be rebuilt using the same +compiler and VM target; private pyc magic does not validate their separate +compressed format. + +## Output and integration gates + +The strict harness captures untouched marshal version 6 bytes before inspecting +code fields. It compares magic, marshal and recursively captured fields without +normalizing byte streams, exception ranges, reference flags, or nested code. +An empty corpus is an error. Its finite fixtures are witnesses, not a general +compiler-equivalence proof. Behavioral regressions belong in Python snippets; +Rust tests additionally check structural compiler and ownership invariants. +The reference invocation requires an empty `PYTHONPYCACHEPREFIX` so existing +certified-build caches are not read. The harness verifies that directory remains +empty and records runtime paths, cache settings and already-imported module +origins after serialization and field capture. These diagnostics are provenance; +they do not alter the three strict comparison domains. + +Private load-constant specializations deoptimize to public `LOAD_CONST` during +export. Active LINE/INSTRUCTION monitoring export remains unresolved: the +current getter reads live instructions and cannot reconstruct generic monitoring +wrappers without per-code metadata. Ordinary warmed tests do not cover that +case; canonical immutable storage requires a separately reviewed next stage. +Retained private `GET_YIELD_FROM_ITER` must never be emitted by the +compiler. Warmed export tests cover ordinary specialization and marshal reads. + +No Lib files are modified. Consequently the unchanged Python `opcode` and +`dis` metadata are a known deferred integration gap. Neither private magic nor +passing raw-code fixtures makes this a distributable 3.15 implementation. +The future exact-copy unit is `Lib/opcode.py`, `Lib/_opcode_metadata.py`, and +`Lib/dis.py`; final metadata also requires builtin `frozendict`, a separate +Rust-first dependency absent from this baseline. Incorrect disassembly snapshots +must not be accepted to hide that integration gap. +Any future Lib update must use the repository's `update_lib` script, after +verifying its supported scope and exact source pin. Manual copying or editing +these files is not permitted. +The public magic transition requires full serialization compatibility, +reader/writer/frozen agreement, and the separately planned exact Python update. + +Marshal reference ownership, compiler constant-origin chronology and mortal +interning remain separate work. No count offsets, flag masking, equal-text +over-interning or weakening of static-reference invariants is permitted. +The filename/surrogate family remains outside this stage. diff --git a/tools/opcode_metadata/conf.toml b/tools/opcode_metadata/conf.toml index e69de29bb..a0cb96927 100644 --- a/tools/opcode_metadata/conf.toml +++ b/tools/opcode_metadata/conf.toml @@ -0,0 +1,4 @@ +# Private legacy instruction. The compiler must never emit this opcode. +[GetYieldFromIter.stack_effect] +popped = "1" +pushed = "1" diff --git a/tools/opcode_metadata/cpython.py b/tools/opcode_metadata/cpython.py index 56743a888..5868d7d74 100644 --- a/tools/opcode_metadata/cpython.py +++ b/tools/opcode_metadata/cpython.py @@ -1,26 +1,37 @@ import os import pathlib import sys +from functools import cache + +from target_source import verify_import_origins, verify_source try: CPYTHON_ROOT = pathlib.Path(os.environ["CPYTHON_ROOT"]).expanduser().resolve() except KeyError: raise ValueError("Missing environment variable 'CPYTHON_ROOT'") +sys.dont_write_bytecode = True +verify_source(CPYTHON_ROOT) +verify_import_origins(CPYTHON_ROOT) + CPYTHON_TOOLS_LIB = CPYTHON_ROOT / "Tools" / "cases_generator" if (path := CPYTHON_TOOLS_LIB.as_posix()) not in sys.path: - sys.path.append(path) + sys.path.insert(0, path) from analyzer import SKIP_PROPERTIES, Analysis, Family, Properties, analyze_files from stack import get_stack_effect +verify_import_origins(CPYTHON_ROOT) + +@cache def get_analysis() -> Analysis: from generators_common import DEFAULT_INPUT + verify_import_origins(CPYTHON_ROOT) analysis = analyze_files([DEFAULT_INPUT]) # Our speration is done at the enum definition analysis.instructions |= analysis.pseudos diff --git a/tools/opcode_metadata/opcodes.py b/tools/opcode_metadata/opcodes.py index 9178921b2..99c49328b 100644 --- a/tools/opcode_metadata/opcodes.py +++ b/tools/opcode_metadata/opcodes.py @@ -38,6 +38,8 @@ class OpcodeInfo: res[family_name].append(member_name) + if "LoadConstImmortal" in names: + res["LoadConst"].extend(["LoadConstImmortal", "LoadConstMortal"]) return dict(res) def __iter__(self): @@ -81,7 +83,11 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]: cpython_name = opcode.cpython_name kwargs = {} - if instr := analysis.instructions.get(cpython_name): + if instr := analysis.instructions.get( + "LOAD_CONST" + if rust_name in ("LoadConstImmortal", "LoadConstMortal") + else cpython_name + ): kwargs["properties"] = properties_with_pseudo_flags(instr) kwargs["family"] = getattr(instr, "family", None) kwargs["cache_entry"] = getattr(instr, "size", -1) diff --git a/tools/opcode_metadata/stack_contract.c b/tools/opcode_metadata/stack_contract.c new file mode 100644 index 000000000..2b33f27c8 --- /dev/null +++ b/tools/opcode_metadata/stack_contract.c @@ -0,0 +1,18 @@ +#define Py_BUILD_CORE +#define NEED_OPCODE_METADATA +#include "Python.h" +#include "pycore_opcode_metadata.h" +#include +int main(void) { + const int args[] = {0,1,2,3,4,5,7,8,15,16,31,255,256,65535}; + for (int op=0; op<512; op++) { + for (unsigned int i=0; i= 0 && pushed >= 0) { + printf("%d %d %d %d\n",op,args[i],popped,pushed); + } + } + } + return 0; +} diff --git a/tools/opcode_metadata/target_source.json b/tools/opcode_metadata/target_source.json new file mode 100644 index 000000000..7ee16c697 --- /dev/null +++ b/tools/opcode_metadata/target_source.json @@ -0,0 +1,42 @@ +{ + "commit": "d220409a13b77de7a6c30abe16ea90db01528edc", + "files": { + "Include/internal/pycore_interpframe.h": "87f52ece3a11f8dfde0274c95c7064efc90b859dd73386cca8cdb83609f21821", + "Include/internal/pycore_opcode_metadata.h": "ce5fb59eadbe061266d7582d5cdd0bf655bb93d6d08fb660a8edf0a19c198fec", + "Include/internal/pycore_opcode_utils.h": "17bb85a7bcabbc29a505949512527f97070daf68f8692f015ac8b6a0f0f0c4e3", + "Include/internal/pycore_uop_ids.h": "dc3b00ba46b803ae4c46cb6b5063ee6814a9330f586692ff4bb500a0b888c421", + "Include/opcode_ids.h": "ffd77843d3b0b58ba476fdc2eca3afc0436b6c5a97adc0cae7e39c1bd0221778", + "Objects/codeobject.c": "b95a59389e3da52283d96b9bfd1a6479ca5d7a6e45c4eeadf5433d88fa84e6d8", + "Objects/frameobject.c": "84d0268c5248e5049158b63ab82556e04be6122f8ebc400bf7723a13b9b98c62", + "Objects/unicodeobject.c": "dcb23f28d01be5b0aa344e7f4d6f79b58cb958139f67f3747cbfd0867ab68f5a", + "Python/assemble.c": "831156f971b07c1ceae22c97a90e9e56363b4a2856896f5383690a30509b94c8", + "Python/bytecodes.c": "aa81f2f18d44cf4e04aa75b5e00e5e50b022c560028030bd062d781bd734c9ea", + "Python/codegen.c": "536a4f1c79b1e0aa292f9954d9e1ca8b02e6c3eddbd08dd111a0998c41848a5d", + "Python/compile.c": "4bff663c758db06b355305406771bc8289af275ca5391daf798aa68d5250f098", + "Python/flowgraph.c": "331f430d7e028b7bd2ab58ec44f7ffaf4a7ac4d53985cdf723477d9406b12aac", + "Python/instrumentation.c": "1650fd63b57447c4ac6d8cd7a8dbd81e5bf0124fd4ce7615b2c348880757fadf", + "Python/marshal.c": "2d2fe69cf2e2f2fcc2693084b91c615ba8c0edba83b003b8bc6408bf1edf77e1", + "Python/specialize.c": "372091addcc5d351b6960ecd9e181ef6ea4af7f367f6d0b5aa655c2a2f7334ce", + "Python/sysmodule.c": "a7c715a7d5db0ffa601b151235dfe7e928b57bd8150cf02eafcd1d5cafcde25f", + "Tools/cases_generator/_typing_backports.py": "4fe25f5705e4f9f047ba330c70264032291c64d24cbd5f2d6da0615978521021", + "Tools/cases_generator/analyzer.py": "10c410377629b4897cf1289bc84743e61c67c0a8c09ab5b4aba0b7fc74809763", + "Tools/cases_generator/cwriter.py": "fb65713b1ad11c5dc190f50f6bb0ed026d5b582c9e5bac623037c000e25c9403", + "Tools/cases_generator/generators_common.py": "06912332943c5962c45314d92bb7a40d95802c165b3dc791d9680e1c5a450118", + "Tools/cases_generator/lexer.py": "80f2c5b97e012e0acda4b0a3de38506dbdd926e64f6328344172246d34ee0c4a", + "Tools/cases_generator/opcode_id_generator.py": "69535cc326af8b3b649000c96d7f609f79a3d7b0a737a56469a9b5c442914966", + "Tools/cases_generator/opcode_metadata_generator.py": "48bc3b643528a6d9eee6a48361882b5a2e30024fe74ae9f1a062f1d9524dd2d3", + "Tools/cases_generator/optimizer_generator.py": "3cf3e9e3881b33700a851732c18ab32db4dddd4488ecc7b83e6e404caffd7e2f", + "Tools/cases_generator/parser.py": "9f88319c6f461740cbfda18feb338a04276cd6672b5475956b155ba62f8912f6", + "Tools/cases_generator/parsing.py": "02f90f5a1dc0c9ded0fcadf74b2f703301b0e5038ec0f2fdc0f185942ed77928", + "Tools/cases_generator/plexer.py": "c13ce4a0b558791b1843e0969ee6abfc3e094e2116cc817fc1101dee9ccbb876", + "Tools/cases_generator/py_metadata_generator.py": "23ba6de30dfc06bc7189e7636c98604fdf1e654af504237699a5b68a19fbca20", + "Tools/cases_generator/record_function_generator.py": "13b66491a9f50df69ede5af3cf67dfa3b1ad688baf85c17f418a1ac29a1b107a", + "Tools/cases_generator/stack.py": "2e192c11d7264d322cf691a3f66ce687459869c9b132710868a69d225103a4a1", + "Tools/cases_generator/target_generator.py": "c7087ffceda3ac08c58aeb756f6a1326fd3a2dac4514bc2124b4660327c3d8b5", + "Tools/cases_generator/tier1_generator.py": "a0c2ae9eb48d6e84f9b5fe3857ffb2aabb32e6231995a5e2c2a32d0e44788b57", + "Tools/cases_generator/tier2_generator.py": "cb44cf5c1493a79b01b779bbf9b8efcf78bee1d339c03d3afe4c050e576fb09d", + "Tools/cases_generator/uop_id_generator.py": "3aec741aed85baa041a9f40e1bb6019dd4cc35ae4d4de0b0c5dd092011fa439f", + "Tools/cases_generator/uop_metadata_generator.py": "d95e5001035eda47642ecd70d2884b39f7dd69547bcd9c77dd9a341e2e088f3b" + }, + "tag": "v3.15.0" +} diff --git a/tools/opcode_metadata/target_source.py b/tools/opcode_metadata/target_source.py new file mode 100644 index 000000000..68d993aaf --- /dev/null +++ b/tools/opcode_metadata/target_source.py @@ -0,0 +1,36 @@ +"""Fail closed before importing any target CPython generator code.""" + +import hashlib +import json +import sys +from pathlib import Path + + +def verify_source(root): + caches = list((root / "Tools/cases_generator").rglob("*.pyc")) + if caches: + raise ValueError( + "target generator source must be cache-free; found " + str(caches[0]) + ) + manifest = json.loads(Path(__file__).with_suffix(".json").read_text()) + for relative, expected in manifest["files"].items(): + path = root / relative + actual = hashlib.sha256(path.read_bytes()).hexdigest() + if actual != expected: + raise ValueError(f"wrong {manifest['tag']} source: {relative}: {actual}") + return manifest + + +def verify_import_origins(root): + manifest = json.loads(Path(__file__).with_suffix(".json").read_text()) + for relative in manifest["files"]: + path = Path(relative) + if path.parent != Path("Tools/cases_generator") or path.suffix != ".py": + continue + module = sys.modules.get(path.stem) + if module is not None: + origin = getattr(module, "__file__", None) + if origin is None or Path(origin).resolve() != (root / path).resolve(): + raise ValueError( + "generator dependency loaded outside pinned source: " + path.stem + ) diff --git a/tools/opcode_metadata/verify_opcode_ids.py b/tools/opcode_metadata/verify_opcode_ids.py new file mode 100644 index 000000000..c2a8d4da2 --- /dev/null +++ b/tools/opcode_metadata/verify_opcode_ids.py @@ -0,0 +1,44 @@ +"""Verify every active public discriminant against pinned final opcode_ids.h.""" + +import argparse +import re +from pathlib import Path + +from target_source import verify_source + +from utils import DEFAULT_INPUT, to_upper_snake_case + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", required=True, type=Path) + args = parser.parse_args() + verify_source(args.source) + ids = { + name: int(value) + for name, value in re.findall( + r"^#define\s+(\w+)\s+(\d+)\s*$", + (args.source / "Include/opcode_ids.h").read_text(), + re.M, + ) + } + private = { + "GetYieldFromIter": 219, + "LoadConstImmortal": 220, + "LoadConstMortal": 221, + } + entries = re.findall( + r"^ (\w+)(?:\s*\{[^}]*\})?\s*=\s*(\d+),", + DEFAULT_INPUT.read_text(), + re.M, + ) + assert len(entries) == 238 + assert len({int(value) for _, value in entries}) == len(entries) + for name, value in entries: + expected = private[name] if name in private else ids[to_upper_snake_case(name)] + assert int(value) == expected, (name, value, expected) + print("Verified 235 public and 3 explicitly private opcode discriminants") + + +if __name__ == "__main__": + main() diff --git a/tools/opcode_metadata/verify_stack_contract.py b/tools/opcode_metadata/verify_stack_contract.py new file mode 100644 index 000000000..f0ac88780 --- /dev/null +++ b/tools/opcode_metadata/verify_stack_contract.py @@ -0,0 +1,51 @@ +"""Regenerate independent C stack goldens, or fail if checked-in rows differ.""" + +import argparse +import subprocess +import tempfile +from pathlib import Path + +from target_source import verify_source + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, required=True) + parser.add_argument("--build", type=Path, required=True) + parser.add_argument("--write", action="store_true") + args = parser.parse_args() + verify_source(args.source) + here = Path(__file__).resolve().parent + with tempfile.TemporaryDirectory() as temporary: + binary = Path(temporary) / "stack-contract" + subprocess.run( + [ + "cc", + "-I" + str(args.build), + "-I" + str(args.source / "Include"), + "-I" + str(args.source / "Include/internal"), + str(here / "stack_contract.c"), + "-o", + str(binary), + ], + check=True, + ) + output = subprocess.check_output([str(binary)], text=True) + rows = [tuple(map(int, line.split())) for line in output.splitlines()] + assert len(rows) == 3430 and len(set(rows)) == 3430 + contents = ( + "// Generated by tools/opcode_metadata/verify_stack_contract.py from pinned CPython C metadata.\n" + "const TARGET_STACK_CASES: &[(u16, u32, u32, u32)] = &[\n" + + "".join(f" ({op}, {arg}, {pop}, {push}),\n" for op, arg, pop, push in rows) + + "];\n" + ) + target = here.parents[1] / "crates/compiler-core/src/bytecode/target_stack_cases.rs" + if args.write: + target.write_text(contents) + elif target.read_text() != contents: + raise SystemExit("pinned C stack rows differ") + print(f"Verified {len(rows)} exact C stack rows") + + +if __name__ == "__main__": + main()