diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index 59b5f7d4d09..28b3468c258 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -697,8 +697,6 @@ impl CodeInfo { /// This is a reference counting optimization in CPython; in RustPython /// we implement it for bytecode compatibility. fn optimize_load_fast_borrow(&mut self) { - use rustpython_compiler_core::bytecode::InstructionMetadata; - // NOT_LOCAL marker: instruction didn't come from a LOAD_FAST const NOT_LOCAL: usize = usize::MAX; @@ -733,7 +731,7 @@ impl CodeInfo { // is pop 2 push 1, not pop 1 push 0). We list those explicitly; // the fallback under-pops and under-pushes, which is conservative // (may miss optimisation opportunities but never miscompiles). - let effect = instr.stack_effect(info.arg); + let effect = instr.stack_effect(info.arg.into()); let (pops, pushes) = match instr { // --- pop 2, push 1 --- Instruction::BinaryOp { .. } @@ -866,7 +864,7 @@ impl CodeInfo { let block = &self.blocks[block_idx]; for ins in &block.instructions { let instr = &ins.instr; - let effect = instr.stack_effect(ins.arg); + let effect = instr.stack_effect(ins.arg.into()); if DEBUG { let display_arg = if ins.target == BlockIdx::NULL { ins.arg diff --git a/crates/compiler-core/src/bytecode.rs b/crates/compiler-core/src/bytecode.rs index d177b728efa..13884dc8a73 100644 --- a/crates/compiler-core/src/bytecode.rs +++ b/crates/compiler-core/src/bytecode.rs @@ -16,7 +16,7 @@ use rustpython_wtf8::{Wtf8, Wtf8Buf}; pub use crate::bytecode::{ instruction::{ - AnyInstruction, Arg, Instruction, InstructionMetadata, PseudoInstruction, + AnyInstruction, Arg, Instruction, InstructionMetadata, PseudoInstruction, StackEffect, decode_load_attr_arg, decode_load_super_attr_arg, encode_load_attr_arg, encode_load_super_attr_arg, }, diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index 0458f5e4baf..449357fd246 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -453,293 +453,268 @@ impl InstructionMetadata for Instruction { ) } - fn stack_effect(&self, arg: OpArg) -> i32 { - match self { - Self::Nop => 0, - Self::NotTaken => 0, - Self::ImportName { .. } => -1, - Self::ImportFrom { .. } => 1, - Self::LoadFast(_) => 1, - Self::LoadFastBorrow(_) => 1, - Self::LoadFastAndClear(_) => 1, - Self::LoadName(_) => 1, - Self::LoadGlobal(_) => 1, - Self::LoadDeref(_) => 1, - Self::StoreFast(_) => -1, - Self::StoreName(_) => -1, - Self::StoreGlobal(_) => -1, - Self::StoreDeref(_) => -1, - Self::StoreFastLoadFast { .. } => 0, // pop 1, push 1 - Self::DeleteFast(_) => 0, - Self::DeleteName(_) => 0, - Self::DeleteGlobal(_) => 0, - Self::DeleteDeref(_) => 0, - Self::LoadFromDictOrDeref(_) => 0, // (dict -- value) - Self::StoreSubscr => -3, - Self::DeleteSubscr => -2, - Self::LoadAttr { idx } => { - // Stack effect depends on method flag in encoded oparg - // method=false: pop obj, push attr → effect = 0 - // method=true: pop obj, push (method, self_or_null) → effect = +1 - let (_, is_method) = decode_load_attr_arg(idx.get(arg)); - if is_method { 1 } else { 0 } - } - Self::StoreAttr { .. } => -2, - Self::DeleteAttr { .. } => -1, - Self::LoadCommonConstant { .. } => 1, - Self::LoadConst { .. } => 1, - Self::LoadSmallInt { .. } => 1, - Self::LoadSpecial { .. } => 0, - Self::Reserved => 0, - Self::BinaryOp { .. } => -1, - Self::CompareOp { .. } => -1, - Self::Copy { .. } => 1, - Self::PopTop => -1, - Self::Swap { .. } => 0, - Self::ToBool => 0, - Self::GetIter => 0, - Self::GetLen => 1, - Self::CallIntrinsic1 { .. } => 0, // Takes 1, pushes 1 - Self::CallIntrinsic2 { .. } => -1, // Takes 2, pushes 1 - Self::PopJumpIfTrue { .. } => -1, - Self::PopJumpIfFalse { .. } => -1, - Self::MakeFunction => { - // CPython 3.14 style: MakeFunction only pops code object - -1 + 1 // pop code, push function - } - Self::SetFunctionAttribute { .. } => { - // pops attribute value and function, pushes function back - -2 + 1 - } - // Call: pops nargs + self_or_null + callable, pushes result - Self::Call { nargs } => -(nargs.get(arg) as i32) - 2 + 1, - // CallKw: pops kw_names_tuple + nargs + self_or_null + callable, pushes result - Self::CallKw { nargs } => -1 - (nargs.get(arg) as i32) - 2 + 1, - // CallFunctionEx: always pops kwargs_or_null + args_tuple + self_or_null + callable, pushes result - Self::CallFunctionEx => -4 + 1, - Self::CheckEgMatch => 0, // pops 2 (exc, type), pushes 2 (rest, match) - Self::ConvertValue { .. } => 0, - Self::FormatSimple => 0, - Self::FormatWithSpec => -1, - Self::ForIter { .. } => 1, // push next value - Self::IsOp(_) => -1, - Self::ContainsOp(_) => -1, - Self::ReturnValue => -1, - Self::Resume { .. } => 0, - Self::YieldValue { .. } => 0, - // SEND: (receiver, val) -> (receiver, retval) - no change, both paths keep same depth - Self::Send { .. } => 0, - // END_SEND: (receiver, value) -> (value) - Self::EndSend => -1, - // CLEANUP_THROW: (sub_iter, last_sent_val, exc) -> (None, value) = 3 pop, 2 push = -1 - Self::CleanupThrow => -1, - Self::PushExcInfo => 1, // [exc] -> [prev_exc, exc] - Self::CheckExcMatch => 0, // [exc, type] -> [exc, bool] (pops type, pushes bool) - Self::Reraise { .. } => 0, // Exception raised, stack effect doesn't matter - Self::SetupAnnotations => 0, - Self::WithExceptStart => 1, // push __exit__ result - Self::RaiseVarargs { kind } => { - // Stack effects for different raise kinds: - // - Reraise (0): gets from VM state, no stack pop - // - Raise (1): pops 1 exception - // - RaiseCause (2): pops 2 (exception + cause) - // - ReraiseFromStack (3): pops 1 exception from stack - match kind.get(arg) { + fn stack_effect_info(&self, oparg: u32) -> StackEffect { + // Reason for converting oparg to i32 is because of expressions like `1 + (oparg -1)` + // that causes underflow errors. + let oparg = i32::try_from(oparg).expect("oparg does not fit in an `i32`"); + + // NOTE: Please don't "simplify" expressions here (i.e. `1 + (oparg - 1)`) + // as it will be harder to see diff with what CPython auto-generates + let (pushed, popped) = match self { + Self::BinaryOp { .. } => (1, 2), + Self::BinaryOpAddFloat => (1, 2), + Self::BinaryOpAddInt => (1, 2), + Self::BinaryOpAddUnicode => (1, 2), + Self::BinaryOpExtend => (1, 2), + Self::BinaryOpInplaceAddUnicode => (0, 2), + Self::BinaryOpMultiplyFloat => (1, 2), + Self::BinaryOpMultiplyInt => (1, 2), + Self::BinaryOpSubscrDict => (1, 2), + Self::BinaryOpSubscrGetitem => (0, 2), + Self::BinaryOpSubscrListInt => (1, 2), + Self::BinaryOpSubscrListSlice => (1, 2), + Self::BinaryOpSubscrStrInt => (1, 2), + Self::BinaryOpSubscrTupleInt => (1, 2), + Self::BinaryOpSubtractFloat => (1, 2), + Self::BinaryOpSubtractInt => (1, 2), + Self::BinarySlice { .. } => (1, 3), + Self::BuildInterpolation { .. } => (1, 2 + (oparg & 1)), + Self::BuildList { .. } => (1, oparg), + Self::BuildMap { .. } => (1, oparg * 2), + Self::BuildSet { .. } => (1, oparg), + Self::BuildSlice { .. } => (1, oparg), + Self::BuildString { .. } => (1, oparg), + Self::BuildTemplate { .. } => (1, 2), + Self::BuildTuple { .. } => (1, oparg), + Self::Cache => (0, 0), + Self::Call { .. } => (1, 2 + oparg), + Self::CallAllocAndEnterInit => (0, 2 + oparg), + Self::CallBoundMethodExactArgs => (0, 2 + oparg), + Self::CallBoundMethodGeneral => (0, 2 + oparg), + Self::CallBuiltinClass => (1, 2 + oparg), + Self::CallBuiltinFast => (1, 2 + oparg), + Self::CallBuiltinFastWithKeywords => (1, 2 + oparg), + Self::CallBuiltinO => (1, 2 + oparg), + Self::CallFunctionEx => (1, 4), + Self::CallIntrinsic1 { .. } => (1, 1), + Self::CallIntrinsic2 { .. } => (1, 2), + Self::CallIsinstance => (1, 2 + oparg), + Self::CallKw { .. } => (1, 3 + oparg), + Self::CallKwBoundMethod => (0, 3 + oparg), + Self::CallKwNonPy => (1, 3 + oparg), + Self::CallKwPy => (0, 3 + oparg), + Self::CallLen => (1, 3), + Self::CallListAppend => (0, 3), + Self::CallMethodDescriptorFast => (1, 2 + oparg), + Self::CallMethodDescriptorFastWithKeywords => (1, 2 + oparg), + Self::CallMethodDescriptorNoargs => (1, 2 + oparg), + Self::CallMethodDescriptorO => (1, 2 + oparg), + Self::CallNonPyGeneral => (1, 2 + oparg), + Self::CallPyExactArgs => (0, 2 + oparg), + Self::CallPyGeneral => (0, 2 + oparg), + Self::CallStr1 => (1, 3), + Self::CallTuple1 => (1, 3), + Self::CallType1 => (1, 3), + Self::CheckEgMatch => (2, 2), + Self::CheckExcMatch => (2, 2), + Self::CleanupThrow => (2, 3), + Self::CompareOp { .. } => (1, 2), + Self::CompareOpFloat => (1, 2), + Self::CompareOpInt => (1, 2), + Self::CompareOpStr => (1, 2), + Self::ContainsOp(_) => (1, 2), + Self::ContainsOpDict => (1, 2), + Self::ContainsOpSet => (1, 2), + Self::ConvertValue { .. } => (1, 1), + Self::Copy { .. } => (2 + (oparg - 1), 1 + (oparg - 1)), + Self::CopyFreeVars { .. } => (0, 0), + Self::DeleteAttr { .. } => (0, 1), + Self::DeleteDeref(_) => (0, 0), + Self::DeleteFast(_) => (0, 0), + Self::DeleteGlobal(_) => (0, 0), + Self::DeleteName(_) => (0, 0), + Self::DeleteSubscr => (0, 2), + Self::DictMerge { .. } => (4 + (oparg - 1), 5 + (oparg - 1)), + Self::DictUpdate { .. } => (1 + (oparg - 1), 2 + (oparg - 1)), + Self::EndAsyncFor => (0, 2), + Self::EndFor => (0, 1), + Self::EndSend => (1, 2), + Self::EnterExecutor => (0, 0), + Self::ExitInitCheck => (0, 1), + Self::ExtendedArg => (0, 0), + Self::ForIter { .. } => (2, 1), + Self::ForIterGen => (1, 1), + Self::ForIterList => (2, 1), + Self::ForIterRange => (2, 1), + Self::ForIterTuple => (2, 1), + Self::FormatSimple => (1, 1), + Self::FormatWithSpec => (1, 2), + Self::GetAIter => (1, 1), + Self::GetANext => (2, 1), + Self::GetAwaitable { .. } => (1, 1), + Self::GetIter => (1, 1), + Self::GetLen => (2, 1), + Self::GetYieldFromIter => (1, 1), + Self::ImportFrom { .. } => (2, 1), + Self::ImportName { .. } => (1, 2), + Self::InstrumentedCall => (1, 2 + oparg), + Self::InstrumentedCallFunctionEx => (1, 4), + Self::InstrumentedCallKw => (1, 3 + oparg), + Self::InstrumentedEndAsyncFor => (0, 2), + Self::InstrumentedEndFor => (1, 2), + Self::InstrumentedEndSend => (1, 2), + Self::InstrumentedForIter => (2, 1), + Self::InstrumentedInstruction => (0, 0), + Self::InstrumentedJumpBackward => (0, 0), + Self::InstrumentedJumpForward => (0, 0), + Self::InstrumentedLine => (0, 0), + Self::InstrumentedLoadSuperAttr => (1 + (oparg & 1), 3), + Self::InstrumentedNotTaken => (0, 0), + Self::InstrumentedPopIter => (0, 1), + Self::InstrumentedPopJumpIfFalse => (0, 1), + Self::InstrumentedPopJumpIfNone => (0, 1), + Self::InstrumentedPopJumpIfNotNone => (0, 1), + Self::InstrumentedPopJumpIfTrue => (0, 1), + Self::InstrumentedResume => (0, 0), + Self::InstrumentedReturnValue => (1, 1), + Self::InstrumentedYieldValue => (1, 1), + Self::InterpreterExit => (0, 1), + Self::IsOp(_) => (1, 2), + Self::JumpBackward { .. } => (0, 0), + Self::JumpBackwardJit => (0, 0), + Self::JumpBackwardNoInterrupt { .. } => (0, 0), + Self::JumpBackwardNoJit => (0, 0), + Self::JumpForward { .. } => (0, 0), + Self::ListAppend { .. } => (1 + (oparg - 1), 2 + (oparg - 1)), + Self::ListExtend { .. } => (1 + (oparg - 1), 2 + (oparg - 1)), + Self::LoadAttr { .. } => (1 + (oparg & 1), 1), + Self::LoadAttrClass => (1 + (oparg & 1), 1), + Self::LoadAttrClassWithMetaclassCheck => (1 + (oparg & 1), 1), + Self::LoadAttrGetattributeOverridden => (1, 1), + Self::LoadAttrInstanceValue => (1 + (oparg & 1), 1), + Self::LoadAttrMethodLazyDict => (2, 1), + Self::LoadAttrMethodNoDict => (2, 1), + Self::LoadAttrMethodWithValues => (2, 1), + Self::LoadAttrModule => (1 + (oparg & 1), 1), + Self::LoadAttrNondescriptorNoDict => (1, 1), + Self::LoadAttrNondescriptorWithValues => (1, 1), + Self::LoadAttrProperty => (0, 1), + Self::LoadAttrSlot => (1 + (oparg & 1), 1), + Self::LoadAttrWithHint => (1 + (oparg & 1), 1), + Self::LoadBuildClass => (1, 0), + Self::LoadCommonConstant { .. } => (1, 0), + Self::LoadConst { .. } => (1, 0), + Self::LoadConstImmortal => (1, 0), + Self::LoadConstMortal => (1, 0), + Self::LoadDeref(_) => (1, 0), + Self::LoadFast(_) => (1, 0), + Self::LoadFastAndClear(_) => (1, 0), + Self::LoadFastBorrow(_) => (1, 0), + Self::LoadFastBorrowLoadFastBorrow { .. } => (2, 0), + Self::LoadFastCheck(_) => (1, 0), + Self::LoadFastLoadFast { .. } => (2, 0), + Self::LoadFromDictOrDeref(_) => (1, 1), + Self::LoadFromDictOrGlobals(_) => (1, 1), + Self::LoadGlobal(_) => ( + 1, // TODO: Differs from CPython `1 + (oparg & 1)` + 0, + ), + Self::LoadGlobalBuiltin => (1 + (oparg & 1), 0), + Self::LoadGlobalModule => (1 + (oparg & 1), 0), + Self::LoadLocals => (1, 0), + Self::LoadName(_) => (1, 0), + Self::LoadSmallInt { .. } => (1, 0), + Self::LoadSpecial { .. } => ( + 2, // TODO: Differs from CPython: `1` + 2, + ), + Self::LoadSuperAttr { .. } => (1 + (oparg & 1), 3), + Self::LoadSuperAttrAttr => (1, 3), + Self::LoadSuperAttrMethod => (2, 3), + Self::MakeCell(_) => (0, 0), + Self::MakeFunction { .. } => (1, 1), + Self::MapAdd { .. } => (1 + (oparg - 1), 3 + (oparg - 1)), + Self::MatchClass { .. } => (1, 3), + Self::MatchKeys { .. } => (3, 2), + Self::MatchMapping => (2, 1), + Self::MatchSequence => (2, 1), + Self::Nop => (0, 0), + Self::NotTaken => (0, 0), + Self::PopExcept => (0, 1), + Self::PopIter => (0, 1), + Self::PopJumpIfFalse { .. } => (0, 1), + Self::PopJumpIfNone { .. } => (0, 1), + Self::PopJumpIfNotNone { .. } => (0, 1), + Self::PopJumpIfTrue { .. } => (0, 1), + Self::PopTop => (0, 1), + Self::PushExcInfo => (2, 1), + Self::PushNull => (1, 0), + Self::RaiseVarargs { kind } => ( + 0, + // TODO: Differs from CPython: `oparg` + match kind.get((oparg as u32).into()) { RaiseKind::BareRaise => 0, - RaiseKind::Raise => -1, - RaiseKind::RaiseCause => -2, - RaiseKind::ReraiseFromStack => -1, - } - } - Self::BuildString { size } => -(size.get(arg) as i32) + 1, - Self::BuildTuple { size, .. } => -(size.get(arg) as i32) + 1, - Self::BuildList { size, .. } => -(size.get(arg) as i32) + 1, - Self::BuildSet { size, .. } => -(size.get(arg) as i32) + 1, - Self::BuildMap { size } => { - let nargs = size.get(arg) * 2; - -(nargs as i32) + 1 - } - Self::DictUpdate { .. } => -1, - Self::DictMerge { .. } => -1, - Self::BuildSlice { argc } => { - // push 1 - // pops either 2/3 - // Default to Two (2 args) if arg is invalid - 1 - (argc - .try_get(arg) - .unwrap_or(BuildSliceArgCount::Two) - .argc() - .get() as i32) - } - Self::ListAppend { .. } => -1, - Self::ListExtend { .. } => -1, - Self::SetAdd { .. } => -1, - Self::SetUpdate { .. } => -1, - Self::MapAdd { .. } => -2, - Self::LoadBuildClass => 1, - Self::UnpackSequence { size } => -1 + size.get(arg) as i32, - Self::UnpackEx { args } => { - let UnpackExArgs { before, after } = args.get(arg); - -1 + before as i32 + 1 + after as i32 - } - Self::PopExcept => -1, - Self::PopIter => -1, - Self::GetAwaitable { .. } => 0, - Self::GetAIter => 0, - Self::GetANext => 1, - Self::EndAsyncFor => -2, // pops (awaitable, exc) from stack - Self::MatchMapping => 1, // Push bool result - Self::MatchSequence => 1, // Push bool result - Self::MatchKeys => 1, // Pop 2 (subject, keys), push 3 (subject, keys_or_none, values_or_none) - Self::MatchClass(_) => -2, - Self::ExtendedArg => 0, - Self::UnaryInvert => 0, - Self::UnaryNegative => 0, - Self::UnaryNot => 0, - Self::GetYieldFromIter => 0, - Self::PushNull => 1, // Push NULL for call protocol - // LoadSuperAttr: pop [super, class, self], push [attr] or [method, self_or_null] - // stack_effect depends on load_method flag (bit 0 of oparg) - Self::LoadSuperAttr { arg: idx } => { - let (_, load_method, _) = decode_load_super_attr_arg(idx.get(arg)); - if load_method { -3 + 2 } else { -3 + 1 } - } - // Pseudo instructions (calculated before conversion) - Self::Cache => 0, - Self::BinarySlice => -2, // (container, start, stop -- res) - Self::BinaryOpInplaceAddUnicode => 0, - Self::EndFor => -1, // pop next value at end of loop iteration - Self::ExitInitCheck => -1, // (should_be_none -- ) - Self::InterpreterExit => 0, - Self::LoadLocals => 1, // ( -- locals) - Self::ReturnGenerator => 1, // pushes None for POP_TOP to consume - Self::StoreSlice => -4, // (v, container, start, stop -- ) - Self::CopyFreeVars { .. } => 0, - Self::EnterExecutor => 0, - Self::JumpBackwardNoInterrupt { .. } => 0, - Self::JumpBackward { .. } => 0, - Self::JumpForward { .. } => 0, - Self::LoadFastCheck(_) => 1, - Self::LoadFastLoadFast { .. } => 2, - Self::LoadFastBorrowLoadFastBorrow { .. } => 2, - Self::LoadFromDictOrGlobals(_) => 0, - Self::MakeCell(_) => 0, - Self::StoreFastStoreFast { .. } => -2, // pops 2 values - Self::PopJumpIfNone { .. } => -1, // (value -- ) - Self::PopJumpIfNotNone { .. } => -1, // (value -- ) - Self::BinaryOpAddFloat => 0, - Self::BinaryOpAddInt => 0, - Self::BinaryOpAddUnicode => 0, - Self::BinaryOpExtend => 0, - Self::BinaryOpMultiplyFloat => 0, - Self::BinaryOpMultiplyInt => 0, - Self::BinaryOpSubtractFloat => 0, - Self::BinaryOpSubtractInt => 0, - Self::BinaryOpSubscrDict => 0, - Self::BinaryOpSubscrGetitem => 0, - Self::BinaryOpSubscrListInt => 0, - Self::BinaryOpSubscrListSlice => 0, - Self::BinaryOpSubscrStrInt => 0, - Self::BinaryOpSubscrTupleInt => 0, - Self::CallAllocAndEnterInit => 0, - Self::CallBoundMethodExactArgs => 0, - Self::CallBoundMethodGeneral => 0, - Self::CallBuiltinClass => 0, - Self::CallBuiltinFast => 0, - Self::CallBuiltinFastWithKeywords => 0, - Self::CallBuiltinO => 0, - Self::CallIsinstance => 0, - Self::CallKwBoundMethod => 0, - Self::CallKwNonPy => 0, - Self::CallKwPy => 0, - Self::CallLen => 0, - Self::CallListAppend => 0, - Self::CallMethodDescriptorFast => 0, - Self::CallMethodDescriptorFastWithKeywords => 0, - Self::CallMethodDescriptorNoargs => 0, - Self::CallMethodDescriptorO => 0, - Self::CallNonPyGeneral => 0, - Self::CallPyExactArgs => 0, - Self::CallPyGeneral => 0, - Self::CallStr1 => 0, - Self::CallTuple1 => 0, - Self::CallType1 => 0, - Self::CompareOpFloat => 0, - Self::CompareOpInt => 0, - Self::CompareOpStr => 0, - Self::ContainsOpDict => 0, - Self::ContainsOpSet => 0, - Self::ForIterGen => 0, - Self::ForIterList => 0, - Self::ForIterRange => 0, - Self::ForIterTuple => 0, - Self::JumpBackwardJit => 0, - Self::JumpBackwardNoJit => 0, - Self::LoadAttrClass => 0, - Self::LoadAttrClassWithMetaclassCheck => 0, - Self::LoadAttrGetattributeOverridden => 0, - Self::LoadAttrInstanceValue => 0, - Self::LoadAttrMethodLazyDict => 0, - Self::LoadAttrMethodNoDict => 0, - Self::LoadAttrMethodWithValues => 0, - Self::LoadAttrModule => 0, - Self::LoadAttrNondescriptorNoDict => 0, - Self::LoadAttrNondescriptorWithValues => 0, - Self::LoadAttrProperty => 0, - Self::LoadAttrSlot => 0, - Self::LoadAttrWithHint => 0, - Self::LoadConstImmortal => 0, - Self::LoadConstMortal => 0, - Self::LoadGlobalBuiltin => 0, - Self::LoadGlobalModule => 0, - Self::LoadSuperAttrAttr => 0, - Self::LoadSuperAttrMethod => 0, - Self::ResumeCheck => 0, - Self::SendGen => 0, - Self::StoreAttrInstanceValue => 0, - Self::StoreAttrSlot => 0, - Self::StoreAttrWithHint => 0, - Self::StoreSubscrDict => 0, - Self::StoreSubscrListInt => 0, - Self::ToBoolAlwaysTrue => 0, - Self::ToBoolBool => 0, - Self::ToBoolInt => 0, - Self::ToBoolList => 0, - Self::ToBoolNone => 0, - Self::ToBoolStr => 0, - Self::UnpackSequenceList => 0, - Self::UnpackSequenceTuple => 0, - Self::UnpackSequenceTwoTuple => 0, - Self::InstrumentedEndFor => 0, - Self::InstrumentedPopIter => -1, - Self::InstrumentedEndSend => 0, - Self::InstrumentedForIter => 0, - Self::InstrumentedInstruction => 0, - Self::InstrumentedJumpForward => 0, - Self::InstrumentedNotTaken => 0, - Self::InstrumentedJumpBackward => 0, - Self::InstrumentedPopJumpIfTrue => 0, - Self::InstrumentedPopJumpIfFalse => 0, - Self::InstrumentedPopJumpIfNone => 0, - Self::InstrumentedPopJumpIfNotNone => 0, - Self::InstrumentedResume => 0, - Self::InstrumentedReturnValue => 0, - Self::InstrumentedYieldValue => 0, - Self::InstrumentedEndAsyncFor => -2, - Self::InstrumentedLoadSuperAttr => 0, - Self::InstrumentedCall => 0, - Self::InstrumentedCallKw => 0, - Self::InstrumentedCallFunctionEx => 0, - Self::InstrumentedLine => 0, - // BuildTemplate: pops [strings_tuple, interpolations_tuple], pushes [template] - Self::BuildTemplate => -1, - // BuildInterpolation: pops [value, expr_str, format_spec?], pushes [interpolation] - // has_format_spec is bit 0 of oparg - Self::BuildInterpolation { oparg } => { - let has_format_spec = oparg.get(arg) & 1 != 0; - if has_format_spec { -2 } else { -1 } - } - } + RaiseKind::Raise => 1, + RaiseKind::RaiseCause => 2, + RaiseKind::ReraiseFromStack => 1, + }, + ), + Self::Reraise { .. } => ( + 1 + oparg, // TODO: Differs from CPython: `oparg` + 1 + oparg, + ), + Self::Reserved => (0, 0), + Self::Resume { .. } => (0, 0), + Self::ResumeCheck => (0, 0), + Self::ReturnGenerator => (1, 0), + Self::ReturnValue => ( + 0, // TODO: Differs from CPython: `1` + 1, + ), + Self::Send { .. } => (2, 2), + Self::SendGen => (1, 2), + Self::SetAdd { .. } => (1 + (oparg - 1), 2 + (oparg - 1)), + Self::SetFunctionAttribute { .. } => (1, 2), + Self::SetUpdate { .. } => (1 + (oparg - 1), 2 + (oparg - 1)), + Self::SetupAnnotations => (0, 0), + Self::StoreAttr { .. } => (0, 2), + Self::StoreAttrInstanceValue => (0, 2), + Self::StoreAttrSlot => (0, 2), + Self::StoreAttrWithHint => (0, 2), + Self::StoreDeref(_) => (0, 1), + Self::StoreFast(_) => (0, 1), + Self::StoreFastLoadFast { .. } => (1, 1), + Self::StoreFastStoreFast { .. } => (0, 2), + Self::StoreGlobal(_) => (0, 1), + Self::StoreName(_) => (0, 1), + Self::StoreSlice => (0, 4), + Self::StoreSubscr => (0, 3), + Self::StoreSubscrDict => (0, 3), + Self::StoreSubscrListInt => (0, 3), + Self::Swap { .. } => (2 + (oparg - 2), 2 + (oparg - 2)), + Self::ToBool => (1, 1), + Self::ToBoolAlwaysTrue => (1, 1), + Self::ToBoolBool => (1, 1), + Self::ToBoolInt => (1, 1), + Self::ToBoolList => (1, 1), + Self::ToBoolNone => (1, 1), + Self::ToBoolStr => (1, 1), + Self::UnaryInvert => (1, 1), + Self::UnaryNegative => (1, 1), + Self::UnaryNot => (1, 1), + Self::UnpackEx { .. } => (1 + (oparg & 0xFF) + (oparg >> 8), 1), + Self::UnpackSequence { .. } => (oparg, 1), + Self::UnpackSequenceList => (oparg, 1), + Self::UnpackSequenceTuple => (oparg, 1), + Self::UnpackSequenceTwoTuple => (2, 1), + Self::WithExceptStart => (6, 5), + Self::YieldValue { .. } => (1, 1), + }; + + debug_assert!((0..=i32::MAX).contains(&pushed)); + debug_assert!((0..=i32::MAX).contains(&popped)); + + StackEffect::new(pushed as u32, popped as u32) } #[allow(clippy::too_many_arguments)] @@ -1015,24 +990,35 @@ impl InstructionMetadata for PseudoInstruction { false } - fn is_unconditional_jump(&self) -> bool { - matches!(self, Self::Jump { .. } | Self::JumpNoInterrupt { .. }) + fn stack_effect_info(&self, _oparg: u32) -> StackEffect { + // Reason for converting oparg to i32 is because of expressions like `1 + (oparg -1)` + // that causes underflow errors. + let _oparg = i32::try_from(_oparg).expect("oparg does not fit in an `i32`"); + + // NOTE: Please don't "simplify" expressions here (i.e. `1 + (oparg - 1)`) + // as it will be harder to see diff with what CPython auto-generates + let (pushed, popped) = match self { + Self::AnnotationsPlaceholder => (0, 0), + Self::Jump { .. } => (0, 0), + Self::JumpIfFalse { .. } => (1, 1), + Self::JumpIfTrue { .. } => (1, 1), + Self::JumpNoInterrupt { .. } => (0, 0), + Self::LoadClosure(_) => (1, 0), + Self::PopBlock => (0, 0), + Self::SetupCleanup => (2, 0), + Self::SetupFinally => (1, 0), + Self::SetupWith => (1, 0), + Self::StoreFastMaybeNull(_) => (0, 1), + }; + + debug_assert!((0..=i32::MAX).contains(&pushed)); + debug_assert!((0..=i32::MAX).contains(&popped)); + + StackEffect::new(pushed as u32, popped as u32) } - fn stack_effect(&self, _arg: OpArg) -> i32 { - match self { - Self::AnnotationsPlaceholder => 0, - Self::Jump { .. } => 0, - Self::JumpIfFalse { .. } => 0, // peek, don't pop: COPY + TO_BOOL + POP_JUMP_IF_FALSE - Self::JumpIfTrue { .. } => 0, // peek, don't pop: COPY + TO_BOOL + POP_JUMP_IF_TRUE - Self::JumpNoInterrupt { .. } => 0, - Self::LoadClosure(_) => 1, - Self::PopBlock => 0, - Self::SetupCleanup => 0, - Self::SetupFinally => 0, - Self::SetupWith => 0, - Self::StoreFastMaybeNull(_) => -1, - } + fn is_unconditional_jump(&self) -> bool { + matches!(self, Self::Jump { .. } | Self::JumpNoInterrupt { .. }) } fn fmt_dis( @@ -1103,7 +1089,9 @@ impl InstructionMetadata for AnyInstruction { inst_either!(fn is_scope_exit(&self) -> bool); - inst_either!(fn stack_effect(&self, arg: OpArg) -> i32); + inst_either!(fn stack_effect(&self, oparg: u32) -> i32); + + inst_either!(fn stack_effect_info(&self, oparg: u32) -> StackEffect); inst_either!(fn fmt_dis( &self, @@ -1154,6 +1142,43 @@ impl AnyInstruction { } } +/// What effect the instruction has on the stack. +#[derive(Clone, Copy)] +pub struct StackEffect { + /// How many items the instruction is pushing on the stack. + pushed: u32, + /// How many items the instruction is popping from the stack. + popped: u32, +} + +impl StackEffect { + /// Creates a new [`Self`]. + pub const fn new(pushed: u32, popped: u32) -> Self { + Self { pushed, popped } + } + + /// Get the calculated stack effect as [`i32`]. + pub fn effect(self) -> i32 { + self.into() + } + + /// Get the pushed count. + pub const fn pushed(self) -> u32 { + self.pushed + } + + /// Get the popped count. + pub const fn popped(self) -> u32 { + self.popped + } +} + +impl From for i32 { + fn from(effect: StackEffect) -> Self { + (effect.pushed() as i32) - (effect.popped() as i32) + } +} + pub trait InstructionMetadata { /// Gets the label stored inside this instruction, if it exists. fn label_arg(&self) -> Option>; @@ -1162,17 +1187,14 @@ pub trait InstructionMetadata { fn is_unconditional_jump(&self) -> bool; - /// What effect this instruction has on the stack - /// - /// # Examples - /// - /// ``` - /// use rustpython_compiler_core::bytecode::{Arg, Instruction, Label, InstructionMetadata}; - /// let (target, jump_arg) = Arg::new(Label(0xF)); - /// let jump_instruction = Instruction::JumpForward { target }; - /// assert_eq!(jump_instruction.stack_effect(jump_arg), 0); - /// ``` - fn stack_effect(&self, arg: OpArg) -> i32; + /// Stack effect info for how many items are pushed/popped from the stack, + /// for this instruction. + fn stack_effect_info(&self, oparg: u32) -> StackEffect; + + /// Stack effect of [`Self::stack_effect_info`]. + fn stack_effect(&self, oparg: u32) -> i32 { + self.stack_effect_info(oparg).effect() + } #[allow(clippy::too_many_arguments)] fn fmt_dis( diff --git a/crates/compiler-core/src/bytecode/oparg.rs b/crates/compiler-core/src/bytecode/oparg.rs index e48e226b12a..c5cf342d074 100644 --- a/crates/compiler-core/src/bytecode/oparg.rs +++ b/crates/compiler-core/src/bytecode/oparg.rs @@ -70,6 +70,12 @@ impl From for OpArg { } } +impl From for u32 { + fn from(value: OpArg) -> Self { + value.0 + } +} + #[derive(Default, Copy, Clone)] #[repr(transparent)] pub struct OpArgState { diff --git a/crates/stdlib/src/_opcode.rs b/crates/stdlib/src/_opcode.rs index 4f56be03092..f2b447e78b6 100644 --- a/crates/stdlib/src/_opcode.rs +++ b/crates/stdlib/src/_opcode.rs @@ -198,7 +198,7 @@ mod _opcode { let opcode = Opcode::try_from_pyint(args.opcode, vm)?; let _ = jump; // Python API accepts jump but it's not used - Ok(opcode.stack_effect(oparg.into())) + Ok(opcode.stack_effect(oparg)) } #[pyfunction]