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()