@@ -712,48 +712,82 @@ impl CodeInfo {
712712 let mut unconsumed = vec ! [ false ; block. instructions. len( ) ] ;
713713
714714 // Simulate stack: each entry is the instruction index that pushed it
715- // (or NOT_LOCAL if not from LOAD_FAST/LOAD_FAST_LOAD_FAST)
715+ // (or NOT_LOCAL if not from LOAD_FAST/LOAD_FAST_LOAD_FAST).
716+ //
717+ // CPython (flowgraph.c optimize_load_fast) pre-fills the stack with
718+ // dummy refs for values inherited from predecessor blocks. We take
719+ // the simpler approach of aborting the optimisation for the whole
720+ // block on stack underflow.
716721 let mut stack: Vec < usize > = Vec :: new ( ) ;
722+ let mut underflow = false ;
717723
718724 for ( i, info) in block. instructions . iter ( ) . enumerate ( ) {
719725 let Some ( instr) = info. instr . real ( ) else {
720726 continue ;
721727 } ;
722728
723- // Get stack effect
729+ // Decompose into (pops, pushes).
730+ //
731+ // stack_effect() returns pushes − pops, which is ambiguous for
732+ // instructions that both pop and push (e.g. BinaryOp: effect=-1
733+ // is pop 2 push 1, not pop 1 push 0). We list those explicitly;
734+ // the fallback under-pops and under-pushes, which is conservative
735+ // (may miss optimisation opportunities but never miscompiles).
724736 let effect = instr. stack_effect ( info. arg ) ;
725- let num_popped = if effect < 0 { ( -effect) as usize } else { 0 } ;
726- let num_pushed = if effect > 0 { effect as usize } else { 0 } ;
727-
728- // More precise: calculate actual pops and pushes
729- // For most instructions: pops = max(0, -effect), pushes = max(0, effect)
730- // But some instructions have both pops and pushes
731737 let ( pops, pushes) = match instr {
732- // Instructions that both pop and push
733- Instruction :: BinaryOp { .. } => ( 2 , 1 ) ,
734- Instruction :: CompareOp { .. } => ( 2 , 1 ) ,
735- Instruction :: ContainsOp ( _) => ( 2 , 1 ) ,
736- Instruction :: IsOp ( _) => ( 2 , 1 ) ,
738+ // --- pop 2, push 1 ---
739+ Instruction :: BinaryOp { .. }
740+ | Instruction :: BinaryOpInplaceAddUnicode
741+ | Instruction :: CompareOp { .. }
742+ | Instruction :: ContainsOp ( _)
743+ | Instruction :: IsOp ( _)
744+ | Instruction :: ImportName { .. }
745+ | Instruction :: FormatWithSpec => ( 2 , 1 ) ,
746+
747+ // --- pop 1, push 1 ---
737748 Instruction :: UnaryInvert
738749 | Instruction :: UnaryNegative
739750 | Instruction :: UnaryNot
740- | Instruction :: ToBool => ( 1 , 1 ) ,
741- Instruction :: GetIter | Instruction :: GetAIter => ( 1 , 1 ) ,
742- Instruction :: LoadAttr { .. } => ( 1 , 1 ) , // simplified
751+ | Instruction :: ToBool
752+ | Instruction :: GetIter
753+ | Instruction :: GetAIter
754+ | Instruction :: FormatSimple
755+ | Instruction :: LoadFromDictOrDeref ( _)
756+ | Instruction :: LoadFromDictOrGlobals ( _) => ( 1 , 1 ) ,
757+
758+ // LoadAttr: pop receiver, push attr (+ push self if method)
759+ Instruction :: LoadAttr { .. } => ( 1 , 1 ) ,
760+
761+ // --- pop 3, push 1 ---
762+ Instruction :: BinarySlice => ( 3 , 1 ) ,
763+
764+ // --- variable pops, push 1 ---
743765 Instruction :: Call { nargs } => ( nargs. get ( info. arg ) as usize + 2 , 1 ) ,
744766 Instruction :: CallKw { nargs } => ( nargs. get ( info. arg ) as usize + 3 , 1 ) ,
745- // Use stack effect for others
746- _ => ( num_popped, num_pushed) ,
767+
768+ // --- conservative fallback ---
769+ // under-pops (≤ actual pops) and under-pushes (≤ actual pushes),
770+ // which keeps extra refs on the stack → marks them unconsumed →
771+ // prevents optimisation. Safe but may miss opportunities.
772+ _ => {
773+ let p = if effect < 0 { ( -effect) as usize } else { 0 } ;
774+ let q = if effect > 0 { effect as usize } else { 0 } ;
775+ ( p, q)
776+ }
747777 } ;
748778
749779 // Pop values from stack
750780 for _ in 0 ..pops {
751781 if stack. pop ( ) . is_none ( ) {
752- // Stack underflow in simulation - block receives values from elsewhere
753- // Conservative: don't optimize this block
782+ // Stack underflow — block receives values from a predecessor.
783+ // Abort optimisation for the entire block.
784+ underflow = true ;
754785 break ;
755786 }
756787 }
788+ if underflow {
789+ break ;
790+ }
757791
758792 // Push values to stack with source instruction index
759793 let source = match instr {
@@ -765,6 +799,10 @@ impl CodeInfo {
765799 }
766800 }
767801
802+ if underflow {
803+ continue ;
804+ }
805+
768806 // Mark instructions whose values remain on stack at block end
769807 for & src in & stack {
770808 if src != NOT_LOCAL {
0 commit comments