diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index e080e854d6d..ea17d8d36a1 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -1364,7 +1364,6 @@ async def foo(): self.fail('invalid asynchronous context manager did not fail') - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "'async with' received an object from __aexit__ that does not implement __await__: int" does not match "'range_iterator' object is not callable" def test_with_8(self): CNT = 0 @@ -2170,7 +2169,6 @@ async def func(): pass f"coroutine {coro_repr}") self.assertIn("was never awaited", str(cm.unraisable.exc_value)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: StopAsyncIteration not raised def test_for_assign_raising_stop_async_iteration(self): class BadTarget: def __setitem__(self, key, value): @@ -2204,7 +2202,6 @@ async def run_gen(): return 'end' self.assertEqual(run_async(run_gen()), ([], 'end')) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: StopAsyncIteration not raised def test_for_assign_raising_stop_async_iteration_2(self): class BadIterable: def __iter__(self): diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 12ba24253cf..a306ed8d62a 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -128,10 +128,6 @@ pub struct FBlockInfo { pub fb_type: FBlockType, pub fb_block: BlockIdx, pub fb_exit: BlockIdx, - // For Python 3.11+ exception table generation - pub fb_handler: Option, // Exception handler block - pub fb_stack_depth: u32, // Stack depth at block entry - pub fb_preserve_lasti: bool, // Whether to preserve lasti (for SETUP_CLEANUP) // additional data for fblock unwinding pub fb_datum: FBlockDatum, } @@ -1172,7 +1168,7 @@ impl Compiler { character_offset: OneIndexed::MIN, // col = 0 }; let end_location = location; // end_lineno = lineno, end_col = 0 - let except_handler = self.current_except_handler(); + let except_handler = None; self.current_block().instructions.push(ir::InstructionInfo { instr: Instruction::Resume { @@ -1324,48 +1320,15 @@ impl Compiler { fb_block: BlockIdx, fb_exit: BlockIdx, ) -> CompileResult<()> { - self.push_fblock_full( - fb_type, - fb_block, - fb_exit, - None, - 0, - false, - FBlockDatum::None, - ) - } - - /// Push an fblock with exception handler info - fn push_fblock_with_handler( - &mut self, - fb_type: FBlockType, - fb_block: BlockIdx, - fb_exit: BlockIdx, - fb_handler: Option, - fb_stack_depth: u32, - fb_preserve_lasti: bool, - ) -> CompileResult<()> { - self.push_fblock_full( - fb_type, - fb_block, - fb_exit, - fb_handler, - fb_stack_depth, - fb_preserve_lasti, - FBlockDatum::None, - ) + self.push_fblock_full(fb_type, fb_block, fb_exit, FBlockDatum::None) } /// Push an fblock with all parameters including fb_datum - #[allow(clippy::too_many_arguments)] fn push_fblock_full( &mut self, fb_type: FBlockType, fb_block: BlockIdx, fb_exit: BlockIdx, - fb_handler: Option, - fb_stack_depth: u32, - fb_preserve_lasti: bool, fb_datum: FBlockDatum, ) -> CompileResult<()> { let code = self.current_code_info(); @@ -1378,9 +1341,6 @@ impl Compiler { fb_type, fb_block, fb_exit, - fb_handler, - fb_stack_depth, - fb_preserve_lasti, fb_datum, }); Ok(()) @@ -1416,7 +1376,7 @@ impl Compiler { } FBlockType::TryExcept => { - // No POP_BLOCK with exception table, just pop fblock + emit!(self, PseudoInstruction::PopBlock); } FBlockType::FinallyTry => { @@ -1427,18 +1387,16 @@ impl Compiler { } FBlockType::FinallyEnd => { - // Stack when in FinallyEnd: [..., prev_exc, exc] or - // [..., prev_exc, exc, return_value] if preserve_tos - // Note: No lasti here - it's only pushed for cleanup handler exceptions - // We need to pop: exc, prev_exc (via PopExcept) + // codegen_unwind_fblock(FINALLY_END) if preserve_tos { emit!(self, Instruction::Swap { index: 2 }); } - emit!(self, Instruction::PopTop); // exc + emit!(self, Instruction::PopTop); // exc_value if preserve_tos { emit!(self, Instruction::Swap { index: 2 }); } - emit!(self, Instruction::PopExcept); // prev_exc is restored + emit!(self, PseudoInstruction::PopBlock); + emit!(self, Instruction::PopExcept); } FBlockType::With | FBlockType::AsyncWith => { @@ -1475,9 +1433,16 @@ impl Compiler { } FBlockType::HandlerCleanup => { + // codegen_unwind_fblock(HANDLER_CLEANUP) + if let FBlockDatum::ExceptionName(_) = info.fb_datum { + // Named handler: PopBlock for inner SETUP_CLEANUP + emit!(self, PseudoInstruction::PopBlock); + } if preserve_tos { emit!(self, Instruction::Swap { index: 2 }); } + // PopBlock for outer SETUP_CLEANUP (ExceptionHandler) + emit!(self, PseudoInstruction::PopBlock); emit!(self, Instruction::PopExcept); // If there's an exception name, clean it up @@ -1551,49 +1516,20 @@ impl Compiler { self.unwind_fblock(&fblock_info, preserve_tos)?; } UnwindInfo::FinallyTry { body, fblock_idx } => { + // codegen_unwind_fblock(FINALLY_TRY) + emit!(self, PseudoInstruction::PopBlock); + // Temporarily remove the FinallyTry fblock so nested return/break/continue // in the finally body won't see it again let code = self.current_code_info(); let saved_fblock = code.fblock.remove(fblock_idx); // Push PopValue fblock if preserving tos - // IMPORTANT: When preserving TOS (return value), we need to update the - // exception handler's stack_depth to account for the return value on stack. - // Otherwise, if an exception occurs during the finally body, the stack - // will be unwound to the wrong depth and the return value will be lost. if preserve_tos { - // Find the outer handler for exceptions during finally body execution. - // CRITICAL: Only search fblocks with index < fblock_idx (= outer fblocks). - // Inner FinallyTry blocks may have been restored after their unwind - // processing, and we must NOT use their handlers - that would cause - // the inner finally body to execute again on exception. - let (handler, stack_depth, preserve_lasti) = { - let code = self.code_stack.last().unwrap(); - let mut found = None; - // Only search fblocks at indices 0..fblock_idx (outer fblocks) - // After removal, fblock_idx now points to where saved_fblock was, - // so indices 0..fblock_idx are the outer fblocks - for i in (0..fblock_idx).rev() { - let fblock = &code.fblock[i]; - if let Some(handler) = fblock.fb_handler { - found = Some(( - Some(handler), - fblock.fb_stack_depth + 1, // +1 for return value - fblock.fb_preserve_lasti, - )); - break; - } - } - found.unwrap_or((None, 1, false)) - }; - - self.push_fblock_with_handler( + self.push_fblock( FBlockType::PopValue, saved_fblock.fb_block, saved_fblock.fb_block, - handler, - stack_depth, - preserve_lasti, )?; } @@ -1613,22 +1549,6 @@ impl Compiler { Ok(()) } - /// Get the current exception handler from fblock stack - fn current_except_handler(&self) -> Option { - let code = self.code_stack.last()?; - // Walk fblock stack from top to find the nearest exception handler - for fblock in code.fblock.iter().rev() { - if let Some(handler) = fblock.fb_handler { - return Some(ir::ExceptHandlerInfo { - handler_block: handler, - stack_depth: fblock.fb_stack_depth, - preserve_lasti: fblock.fb_preserve_lasti, - }); - } - } - None - } - // could take impl Into>, but everything is borrowed from ast structs; we never // actually have a `String` to pass fn name(&mut self, name: &str) -> bytecode::NameIdx { @@ -2207,6 +2127,7 @@ impl Compiler { func: bytecode::IntrinsicFunction1::ImportStar } ); + emit!(self, Instruction::PopTop); } else { // from mod import a, b as c @@ -2331,6 +2252,10 @@ impl Compiler { }; self.set_source_range(*range); emit!(self, Instruction::RaiseVarargs { kind }); + // Start a new block so dead code after raise doesn't + // corrupt the except stack in label_exception_targets + let dead = self.new_block(); + self.switch_to_block(dead); } ast::Stmt::Try(ast::StmtTry { body, @@ -2419,10 +2344,14 @@ impl Compiler { ast::Stmt::Break(_) => { // Unwind fblock stack until we find a loop, emitting cleanup for each fblock self.compile_break_continue(statement.range(), true)?; + let dead = self.new_block(); + self.switch_to_block(dead); } ast::Stmt::Continue(_) => { // Unwind fblock stack until we find a loop, emitting cleanup for each fblock self.compile_break_continue(statement.range(), false)?; + let dead = self.new_block(); + self.switch_to_block(dead); } ast::Stmt::Return(ast::StmtReturn { value, .. }) => { if !self.ctx.in_func() { @@ -2455,6 +2384,8 @@ impl Compiler { self.emit_return_const(ConstantData::None); } } + let dead = self.new_block(); + self.switch_to_block(dead); } ast::Stmt::Assign(ast::StmtAssign { targets, value, .. }) => { self.compile_expression(value)?; @@ -2824,25 +2755,24 @@ impl Compiler { // Normal path jumps here to skip exception path blocks let end_block = self.new_block(); - // Calculate the stack depth at this point (for exception table) - // SETUP_FINALLY captures current stack depth - let current_depth = self.handler_stack_depth(); - // Setup a finally block if we have a finally statement. // Push fblock with handler info for exception table generation // IMPORTANT: handler goes to finally_except_block (exception path), not finally_block if !finalbody.is_empty() { - // No SetupFinally emit - exception table handles this - // Store finally body in fb_datum for unwind_fblock to compile inline // SETUP_FINALLY doesn't push lasti for try body handler // Exception table: L1 to L2 -> L4 [1] (no lasti) + let setup_target = finally_except_block.unwrap_or(finally_block); + emit!( + self, + PseudoInstruction::SetupFinally { + target: setup_target + } + ); + // Store finally body in fb_datum for unwind_fblock to compile inline self.push_fblock_full( FBlockType::FinallyTry, finally_block, finally_block, - finally_except_block, // Exception path goes to finally_except_block - current_depth, - false, // No lasti for first finally handler FBlockDatum::FinallyBody(finalbody.to_vec()), // Clone finally body for unwind )?; } @@ -2858,6 +2788,7 @@ impl Compiler { // Pop FinallyTry fblock BEFORE compiling orelse/finally (normal path) // This prevents exception table from covering the normal path if !finalbody.is_empty() { + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::FinallyTry); } @@ -2889,22 +2820,13 @@ impl Compiler { } self.switch_to_block(finally_except); - // PUSH_EXC_INFO first, THEN push FinallyEnd fblock - // Stack after unwind (no lasti): [exc] (depth = current_depth + 1) - // Stack after PUSH_EXC_INFO: [prev_exc, exc] (depth = current_depth + 2) + // SETUP_CLEANUP before PUSH_EXC_INFO + if let Some(cleanup) = finally_cleanup_block { + emit!(self, PseudoInstruction::SetupCleanup { target: cleanup }); + } emit!(self, Instruction::PushExcInfo); if let Some(cleanup) = finally_cleanup_block { - // FinallyEnd fblock must be pushed AFTER PUSH_EXC_INFO - // Depth = current_depth + 1 (only prev_exc remains after RERAISE pops exc) - // Exception table: L4 to L5 -> L6 [2] lasti (cleanup handler DOES push lasti) - self.push_fblock_with_handler( - FBlockType::FinallyEnd, - cleanup, - cleanup, - Some(cleanup), - current_depth + 1, - true, // Cleanup handler pushes lasti - )?; + self.push_fblock(FBlockType::FinallyEnd, cleanup, cleanup)?; } self.compile_statements(finalbody)?; @@ -2912,6 +2834,7 @@ impl Compiler { // This ensures RERAISE routes to outer exception handler, not cleanup block // Cleanup block is only for new exceptions raised during finally body execution if finally_cleanup_block.is_some() { + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::FinallyEnd); } @@ -2948,19 +2871,16 @@ impl Compiler { } // try: - // Push fblock with handler info for exception table generation - // No SetupExcept emit - exception table handles this - self.push_fblock_with_handler( - FBlockType::TryExcept, - handler_block, - handler_block, - Some(handler_block), - current_depth, // stack depth for exception handler - false, // no lasti for except - )?; + emit!( + self, + PseudoInstruction::SetupFinally { + target: handler_block + } + ); + self.push_fblock(FBlockType::TryExcept, handler_block, handler_block)?; self.compile_statements(body)?; + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::TryExcept); - // No PopBlock emit - exception table handles this emit!(self, PseudoInstruction::Jump { target: else_block }); // except handlers: @@ -2972,14 +2892,13 @@ impl Compiler { // After PUSH_EXC_INFO, stack is [prev_exc, exc] // depth=1 means keep prev_exc on stack when routing to cleanup let cleanup_block = self.new_block(); - self.push_fblock_with_handler( - FBlockType::ExceptionHandler, - cleanup_block, - cleanup_block, - Some(cleanup_block), - current_depth + 1, // After PUSH_EXC_INFO: [prev_exc] stays on stack - true, // preserve_lasti for cleanup - )?; + emit!( + self, + PseudoInstruction::SetupCleanup { + target: cleanup_block + } + ); + self.push_fblock(FBlockType::ExceptionHandler, cleanup_block, cleanup_block)?; // Exception is on top of stack now, pushed by unwind_blocks // PUSH_EXC_INFO transforms [exc] -> [prev_exc, exc] for PopExcept @@ -3027,17 +2946,13 @@ impl Compiler { let handler_cleanup_block = if name.is_some() { // SETUP_CLEANUP(cleanup_end) for named handler let cleanup_end = self.new_block(); - // Stack at handler entry: [prev_exc, exc] - // depth = 1 (prev_exc on stack after exception is popped) - let handler_depth = current_depth + 1; - self.push_fblock_with_handler( - FBlockType::HandlerCleanup, - cleanup_end, - cleanup_end, - Some(cleanup_end), - handler_depth, - true, // preserve_lasti for RERAISE - )?; + emit!( + self, + PseudoInstruction::SetupCleanup { + target: cleanup_end + } + ); + self.push_fblock(FBlockType::HandlerCleanup, cleanup_end, cleanup_end)?; Some(cleanup_end) } else { // no SETUP_CLEANUP for unnamed handler @@ -3049,6 +2964,10 @@ impl Compiler { self.compile_statements(body)?; self.pop_fblock(FBlockType::HandlerCleanup); + // PopBlock for inner SETUP_CLEANUP (named handler only) + if handler_cleanup_block.is_some() { + emit!(self, PseudoInstruction::PopBlock); + } // Create a block for normal path continuation (after handler body succeeds) let handler_normal_exit = self.new_block(); @@ -3086,9 +3005,9 @@ impl Compiler { // Switch to normal exit block - this is where handler body success continues self.switch_to_block(handler_normal_exit); + // PopBlock for outer SETUP_CLEANUP (ExceptionHandler) + emit!(self, PseudoInstruction::PopBlock); // Now pop ExceptionHandler - the normal path continues from here - // POP_BLOCK (HandlerCleanup) then POP_BLOCK (SETUP_CLEANUP) - // followed by POP_EXCEPT self.pop_fblock(FBlockType::ExceptionHandler); emit!(self, Instruction::PopExcept); @@ -3100,6 +3019,13 @@ impl Compiler { self.compile_name(alias.as_str(), NameUsage::Delete)?; } + // Pop FinallyTry block before jumping to finally body. + // The else_block path also pops this; both paths must agree + // on the except stack when entering finally_block. + if !finalbody.is_empty() { + emit!(self, PseudoInstruction::PopBlock); + } + // Jump to finally block emit!( self, @@ -3110,14 +3036,7 @@ impl Compiler { // Re-push ExceptionHandler for next handler in the loop // This will be popped at the end of handlers loop or when matched - self.push_fblock_with_handler( - FBlockType::ExceptionHandler, - cleanup_block, - cleanup_block, - Some(cleanup_block), - current_depth + 1, // After PUSH_EXC_INFO: [prev_exc] stays on stack - true, // preserve_lasti for cleanup - )?; + self.push_fblock(FBlockType::ExceptionHandler, cleanup_block, cleanup_block)?; // Emit a new label for the next handler self.switch_to_block(next_handler); @@ -3161,7 +3080,7 @@ impl Compiler { // Pop the FinallyTry fblock before jumping to finally if !finalbody.is_empty() { - // No PopBlock/EnterFinally emit - exception table handles this + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::FinallyTry); } @@ -3196,23 +3115,13 @@ impl Compiler { // SETUP_CLEANUP for finally body // Exceptions during finally body need to go to cleanup block - // Stack at entry: [lasti, exc] (lasti from exception table, exc pushed) - // After PUSH_EXC_INFO: [lasti, prev_exc, exc] - // So depth should account for lasti being on stack if let Some(cleanup) = finally_cleanup_block { - self.push_fblock_with_handler( - FBlockType::FinallyEnd, - cleanup, - cleanup, - Some(cleanup), - current_depth + 1, // [lasti] on stack before PUSH_EXC_INFO - true, - )?; + emit!(self, PseudoInstruction::SetupCleanup { target: cleanup }); } - - // PUSH_EXC_INFO: [lasti, exc] -> [lasti, prev_exc, exc] - // Sets exc as current VM exception, saves prev_exc for restoration emit!(self, Instruction::PushExcInfo); + if let Some(cleanup) = finally_cleanup_block { + self.push_fblock(FBlockType::FinallyEnd, cleanup, cleanup)?; + } // Run finally body self.compile_statements(finalbody)?; @@ -3221,6 +3130,7 @@ impl Compiler { // This ensures RERAISE routes to outer exception handler, not cleanup block // Cleanup block is only for new exceptions raised during finally body execution if finally_cleanup_block.is_some() { + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::FinallyEnd); } @@ -3281,35 +3191,39 @@ impl Compiler { let end_block = self.new_block(); let reraise_star_block = self.new_block(); let reraise_block = self.new_block(); - let _cleanup_block = self.new_block(); - - // Calculate the stack depth at this point (for exception table) - let current_depth = self.handler_stack_depth(); + let finally_cleanup_block = if !finalbody.is_empty() { + Some(self.new_block()) + } else { + None + }; + let exit_block = self.new_block(); // Push fblock with handler info for exception table generation if !finalbody.is_empty() { - // No SetupFinally emit - exception table handles this - self.push_fblock_with_handler( + emit!( + self, + PseudoInstruction::SetupFinally { + target: finally_block + } + ); + self.push_fblock_full( FBlockType::FinallyTry, finally_block, finally_block, - Some(finally_block), - current_depth, // stack depth for exception handler - true, // preserve lasti for finally + FBlockDatum::FinallyBody(finalbody.to_vec()), )?; } // SETUP_FINALLY for try body - // Push fblock with handler info for exception table generation - self.push_fblock_with_handler( - FBlockType::TryExcept, - handler_block, - handler_block, - Some(handler_block), - current_depth, // stack depth for exception handler - false, // no lasti for except - )?; + emit!( + self, + PseudoInstruction::SetupFinally { + target: handler_block + } + ); + self.push_fblock(FBlockType::TryExcept, handler_block, handler_block)?; self.compile_statements(body)?; + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::TryExcept); emit!(self, PseudoInstruction::Jump { target: else_block }); @@ -3326,7 +3240,26 @@ impl Compiler { let eg_dummy2 = self.new_block(); self.push_fblock(FBlockType::ExceptionGroupHandler, eg_dummy1, eg_dummy2)?; + // Initialize handler stack before the loop + // BUILD_LIST 0 + COPY 2 to set up [prev_exc, orig, list, rest] + emit!(self, Instruction::BuildList { size: 0 }); + // Stack: [prev_exc, exc, []] + emit!(self, Instruction::Copy { index: 2 }); + // Stack: [prev_exc, orig, list, rest] + let n = handlers.len(); + if n == 0 { + // Empty handlers (invalid AST) - append rest to list and proceed + // Stack: [prev_exc, orig, list, rest] + emit!(self, Instruction::ListAppend { i: 0 }); + // Stack: [prev_exc, orig, list] + emit!( + self, + PseudoInstruction::Jump { + target: reraise_star_block + } + ); + } for (i, handler) in handlers.iter().enumerate() { let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, @@ -3338,17 +3271,6 @@ impl Compiler { let no_match_block = self.new_block(); let next_block = self.new_block(); - // first handler creates list and copies exc - if i == 0 { - // ADDOP_I(c, loc, BUILD_LIST, 0); - emit!(self, Instruction::BuildList { size: 0 }); - // Stack: [prev_exc, exc, []] - // ADDOP_I(c, loc, COPY, 2); - emit!(self, Instruction::Copy { index: 2 }); - // Stack: [prev_exc, exc, [], exc_copy] - // Now stack is: [prev_exc, orig, list, rest] - } - // Compile exception type if let Some(exc_type) = type_ { // Check for unparenthesized tuple @@ -3396,21 +3318,19 @@ impl Compiler { // Stack: [prev_exc, orig, list, new_rest] // HANDLER_CLEANUP fblock for handler body - // Stack depth: prev_exc(1) + orig(1) + list(1) + new_rest(1) = 4 - let eg_handler_depth = self.handler_stack_depth() + 4; - self.push_fblock_with_handler( - FBlockType::HandlerCleanup, - next_block, - end_block, - Some(handler_except_block), - eg_handler_depth, - true, // preserve lasti - )?; + emit!( + self, + PseudoInstruction::SetupCleanup { + target: handler_except_block + } + ); + self.push_fblock(FBlockType::HandlerCleanup, next_block, end_block)?; // Execute handler body self.compile_statements(body)?; // Handler body completed normally + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::HandlerCleanup); // Cleanup name binding @@ -3521,6 +3441,7 @@ impl Compiler { // Stack: [] if !finalbody.is_empty() { + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::FinallyTry); } @@ -3548,13 +3469,17 @@ impl Compiler { // that branches from try body success (where FinallyTry is still active). // We need to re-push FinallyTry to reflect the correct fblock state for else path. if !finalbody.is_empty() { - self.push_fblock_with_handler( + emit!( + self, + PseudoInstruction::SetupFinally { + target: finally_block + } + ); + self.push_fblock_full( FBlockType::FinallyTry, finally_block, finally_block, - Some(finally_block), - current_depth, - true, + FBlockDatum::FinallyBody(finalbody.to_vec()), )?; } self.switch_to_block(else_block); @@ -3562,6 +3487,7 @@ impl Compiler { if !finalbody.is_empty() { // Pop the FinallyTry fblock we just pushed for the else path + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::FinallyTry); } @@ -3569,11 +3495,60 @@ impl Compiler { self.switch_to_block(end_block); if !finalbody.is_empty() { + // Snapshot sub_tables before first finally compilation + let sub_table_cursor = self.symbol_table_stack.last().map(|t| t.next_sub_table); + + // Compile finally body inline for normal path + self.compile_statements(finalbody)?; + emit!(self, PseudoInstruction::Jump { target: exit_block }); + + // Restore sub_tables for exception path compilation + if let Some(cursor) = sub_table_cursor + && let Some(current_table) = self.symbol_table_stack.last_mut() + { + current_table.next_sub_table = cursor; + } + + // Exception handler path self.switch_to_block(finally_block); + emit!(self, Instruction::PushExcInfo); + + if let Some(cleanup) = finally_cleanup_block { + emit!(self, PseudoInstruction::SetupCleanup { target: cleanup }); + self.push_fblock(FBlockType::FinallyEnd, cleanup, cleanup)?; + } + self.compile_statements(finalbody)?; - // No EndFinally emit - exception table handles this + + if finally_cleanup_block.is_some() { + emit!(self, PseudoInstruction::PopBlock); + self.pop_fblock(FBlockType::FinallyEnd); + } + + emit!(self, Instruction::Copy { index: 2_u32 }); + emit!(self, Instruction::PopExcept); + emit!( + self, + Instruction::RaiseVarargs { + kind: bytecode::RaiseKind::ReraiseFromStack + } + ); + + if let Some(cleanup) = finally_cleanup_block { + self.switch_to_block(cleanup); + emit!(self, Instruction::Copy { index: 3_u32 }); + emit!(self, Instruction::PopExcept); + emit!( + self, + Instruction::RaiseVarargs { + kind: bytecode::RaiseKind::ReraiseFromStack + } + ); + } } + self.switch_to_block(exit_block); + Ok(()) } @@ -4799,9 +4774,13 @@ impl Compiler { // Stack: [..., __exit__, enter_result] // Push fblock for exception table - handler goes to exc_handler_block // preserve_lasti=true for with statements - // Use handler_stack_depth() to include all items on stack (for loops, etc.) - let with_depth = self.handler_stack_depth() + 1; // +1 for current __exit__ - self.push_fblock_with_handler( + emit!( + self, + PseudoInstruction::SetupWith { + target: exc_handler_block + } + ); + self.push_fblock( if is_async { FBlockType::AsyncWith } else { @@ -4809,9 +4788,6 @@ impl Compiler { }, exc_handler_block, // block start (will become exit target after store) after_block, - Some(exc_handler_block), - with_depth, - true, // preserve_lasti=true )?; // Store or pop the enter result @@ -4838,6 +4814,7 @@ impl Compiler { } // Pop fblock before normal exit + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(if is_async { FBlockType::AsyncWith } else { @@ -4875,20 +4852,13 @@ impl Compiler { let cleanup_block = self.new_block(); let suppress_block = self.new_block(); - // Push nested fblock for cleanup handler - // Stack at exc_handler_block entry: [..., __exit__, lasti, exc] - // After PUSH_EXC_INFO: [..., __exit__, lasti, prev_exc, exc] - // If exception in __exit__, cleanup handler entry: [..., __exit__, lasti, prev_exc, lasti2, exc2] - // cleanup_depth should be: with_depth + 2 (lasti + prev_exc) - let cleanup_depth = with_depth + 2; - self.push_fblock_with_handler( - FBlockType::ExceptionHandler, - exc_handler_block, - after_block, - Some(cleanup_block), - cleanup_depth, - true, // preserve_lasti=true - )?; + emit!( + self, + PseudoInstruction::SetupCleanup { + target: cleanup_block + } + ); + self.push_fblock(FBlockType::ExceptionHandler, exc_handler_block, after_block)?; // PUSH_EXC_INFO: [exc] -> [prev_exc, exc] emit!(self, Instruction::PushExcInfo); @@ -4917,6 +4887,7 @@ impl Compiler { // handler points to the outer handler (try-except), not cleanup_block. // This is critical: when RERAISE propagates the exception, the exception // table should route it to the outer try-except, not back to cleanup. + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::ExceptionHandler); // Not suppressed: RERAISE 2 @@ -4986,25 +4957,19 @@ impl Compiler { self.switch_to_block(for_block); - // Push fblock for async for loop with exception handler info - // Note: SetupExcept is no longer emitted (exception table handles StopAsyncIteration) - // Stack at this point: [..., async_iterator] - // We need handler_stack_depth() + 1 to keep parent items + async_iterator on stack when exception occurs - let async_for_depth = self.handler_stack_depth() + 1; - self.push_fblock_with_handler( - FBlockType::ForLoop, - for_block, - after_block, - Some(else_block), // Handler for StopAsyncIteration - async_for_depth, // stack depth: keep async_iterator and parent items - false, // no lasti needed - )?; + // codegen_async_for: push fblock BEFORE SETUP_FINALLY + self.push_fblock(FBlockType::ForLoop, for_block, after_block)?; + // SETUP_FINALLY to guard the __anext__ call + emit!(self, PseudoInstruction::SetupFinally { target: else_block }); emit!(self, Instruction::GetANext); self.emit_load_const(ConstantData::None); self.compile_yield_from_sequence(true)?; + // POP_BLOCK for SETUP_FINALLY - only GetANext/yield_from are protected + emit!(self, PseudoInstruction::PopBlock); + + // Success block for __anext__ self.compile_store(target)?; - // Note: PopBlock is no longer emitted (exception table handles this) } else { // Retrieve Iterator emit!(self, Instruction::GetIter); @@ -5027,7 +4992,8 @@ impl Compiler { self.switch_to_block(else_block); - // Pop fblock + // Except block for __anext__ / end of sync for + // No PopBlock here - for async, POP_BLOCK is already in for_block self.pop_fblock(FBlockType::ForLoop); if is_async { @@ -6554,22 +6520,18 @@ impl Compiler { emit!(self, Instruction::Send { target: exit_block }); // SETUP_FINALLY fail - set up exception handler for YIELD_VALUE - // Stack at this point: [receiver, yielded_value] - // handler_depth = base + 2 (receiver + yielded_value) - let handler_depth = self.handler_stack_depth() + 2; - self.push_fblock_with_handler( + emit!(self, PseudoInstruction::SetupFinally { target: fail_block }); + self.push_fblock( FBlockType::TryExcept, // Use TryExcept for exception handler send_block, exit_block, - Some(fail_block), - handler_depth, - false, // no lasti needed )?; // YIELD_VALUE with arg=1 (yield-from/await mode - not wrapped for async gen) emit!(self, Instruction::YieldValue { arg: 1 }); - // POP_BLOCK (implicit - pop fblock before RESUME) + // POP_BLOCK before RESUME + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::TryExcept); // RESUME @@ -7439,23 +7401,25 @@ impl Compiler { loop_labels.push((loop_block, after_block, generator.is_async)); self.switch_to_block(loop_block); if generator.is_async { + emit!( + self, + PseudoInstruction::SetupFinally { + target: after_block + } + ); emit!(self, Instruction::GetANext); - - let current_depth = (init_collection.is_some() as u32) - + u32::try_from(loop_labels.len()).unwrap() - + 1; - self.push_fblock_with_handler( + self.push_fblock( FBlockType::AsyncComprehensionGenerator, loop_block, after_block, - Some(after_block), - current_depth, - false, )?; self.emit_load_const(ConstantData::None); self.compile_yield_from_sequence(true)?; - self.compile_store(&generator.target)?; + // POP_BLOCK before store: only __anext__/yield_from are + // protected by SetupFinally targeting END_ASYNC_FOR. + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::AsyncComprehensionGenerator); + self.compile_store(&generator.target)?; } else { emit!( self, @@ -7479,8 +7443,9 @@ impl Compiler { self.switch_to_block(after_block); if is_async { + // EndAsyncFor pops both the exception and the aiter + // (handler depth is before GetANext, so aiter is at handler depth) emit!(self, Instruction::EndAsyncFor); - emit!(self, Instruction::PopTop); } else { // END_FOR + POP_ITER pattern (CPython 3.14) emit!(self, Instruction::EndFor); @@ -7616,20 +7581,13 @@ impl Compiler { let end_block = self.new_block(); if !pushed_locals.is_empty() { - // Calculate stack depth for exception handler - // Stack: [saved_locals..., collection?, iterator] - let depth = self.handler_stack_depth() - + u32::try_from(pushed_locals.len()).unwrap() - + init_collection.is_some() as u32 - + 1; - self.push_fblock_with_handler( - FBlockType::TryExcept, - cleanup_block, - end_block, - Some(cleanup_block), - depth, - false, - )?; + emit!( + self, + PseudoInstruction::SetupFinally { + target: cleanup_block + } + ); + self.push_fblock(FBlockType::TryExcept, cleanup_block, end_block)?; } // Step 5: Compile the comprehension loop(s) @@ -7692,6 +7650,7 @@ impl Compiler { // Step 8: Clean up - restore saved locals if !pushed_locals.is_empty() { + emit!(self, PseudoInstruction::PopBlock); self.pop_fblock(FBlockType::TryExcept); // Normal path: jump past cleanup @@ -7773,7 +7732,7 @@ impl Compiler { let source = self.source_file.to_source_code(); let location = source.source_location(range.start(), PositionEncoding::Utf8); let end_location = source.source_location(range.end(), PositionEncoding::Utf8); - let except_handler = self.current_except_handler(); + let except_handler = None; self.current_block().instructions.push(ir::InstructionInfo { instr: instr.into(), arg, @@ -7934,7 +7893,10 @@ impl Compiler { With { is_async: bool, }, - HandlerCleanup, + HandlerCleanup { + name: Option, + }, + TryExcept, FinallyTry { body: Vec, fblock_idx: usize, @@ -7955,7 +7917,14 @@ impl Compiler { unwind_actions.push(UnwindAction::With { is_async: true }); } FBlockType::HandlerCleanup => { - unwind_actions.push(UnwindAction::HandlerCleanup); + let name = match &code.fblock[i].fb_datum { + FBlockDatum::ExceptionName(name) => Some(name.clone()), + _ => None, + }; + unwind_actions.push(UnwindAction::HandlerCleanup { name }); + } + FBlockType::TryExcept => { + unwind_actions.push(UnwindAction::TryExcept); } FBlockType::FinallyTry => { // Need to execute finally body before break/continue @@ -7983,6 +7952,8 @@ impl Compiler { for action in unwind_actions { match action { UnwindAction::With { is_async } => { + // codegen_unwind_fblock(WITH/ASYNC_WITH) + emit!(self, PseudoInstruction::PopBlock); // compiler_call_exit_with_nones emit!(self, Instruction::PushNull); self.emit_load_const(ConstantData::None); @@ -7998,10 +7969,29 @@ impl Compiler { emit!(self, Instruction::PopTop); } - UnwindAction::HandlerCleanup => { + UnwindAction::HandlerCleanup { ref name } => { + // codegen_unwind_fblock(HANDLER_CLEANUP) + if name.is_some() { + // Named handler: PopBlock for inner SETUP_CLEANUP + emit!(self, PseudoInstruction::PopBlock); + } + // PopBlock for outer SETUP_CLEANUP (ExceptionHandler) + emit!(self, PseudoInstruction::PopBlock); emit!(self, Instruction::PopExcept); + if let Some(name) = name { + self.emit_load_const(ConstantData::None); + self.store_name(name)?; + self.compile_name(name, NameUsage::Delete)?; + } + } + UnwindAction::TryExcept => { + // codegen_unwind_fblock(TRY_EXCEPT) + emit!(self, PseudoInstruction::PopBlock); } UnwindAction::FinallyTry { body, fblock_idx } => { + // codegen_unwind_fblock(FINALLY_TRY) + emit!(self, PseudoInstruction::PopBlock); + // compile finally body inline // Temporarily pop the FinallyTry fblock so nested break/continue // in the finally body won't see it again. @@ -8016,11 +8006,10 @@ impl Compiler { code.fblock.insert(fblock_idx, saved_fblock); } UnwindAction::FinallyEnd => { - // Stack when in FinallyEnd: [..., prev_exc, exc] - // Note: No lasti here - it's only pushed for cleanup handler exceptions - // We need to pop: exc, prev_exc (via PopExcept) - emit!(self, Instruction::PopTop); // exc - emit!(self, Instruction::PopExcept); // prev_exc is restored + // codegen_unwind_fblock(FINALLY_END) + emit!(self, Instruction::PopTop); // exc_value + emit!(self, PseudoInstruction::PopBlock); + emit!(self, Instruction::PopExcept); } UnwindAction::PopValue => { // Pop the return value - continue/break cancels the pending return @@ -8041,39 +8030,6 @@ impl Compiler { Ok(()) } - /// Calculate the current exception handler stack depth. - /// CPython calculates this based on the SETUP_FINALLY/SETUP_CLEANUP stack depth. - fn handler_stack_depth(&self) -> u32 { - let code = match self.code_stack.last() { - Some(c) => c, - None => return 0, - }; - let mut depth = 0u32; - for fblock in &code.fblock { - match fblock.fb_type { - FBlockType::ForLoop => depth += 1, - FBlockType::With | FBlockType::AsyncWith => depth += 1, - // HandlerCleanup does NOT add to stack depth - it only tracks - // cleanup code for named exception handlers. The stack item - // (prev_exc) is already counted by ExceptionHandler. - // FBlockType::HandlerCleanup => depth += 1, - // inside exception handler, prev_exc is on stack - FBlockType::ExceptionHandler => depth += 1, - // ExceptionGroupHandler: inside except* handler path - // Stack has [prev_exc, orig, list, rest] - add 4 for these - FBlockType::ExceptionGroupHandler => depth += 4, - // FinallyEnd: inside finally exception path - // Stack has [prev_exc, exc] - add 2 for these (no lasti at this level) - FBlockType::FinallyEnd => depth += 2, - // PopValue: preserving a return value on stack during inline finally - // The return value adds 1 to the stack depth - FBlockType::PopValue => depth += 1, - _ => {} - } - } - depth - } - fn current_block(&mut self) -> &mut ir::Block { let info = self.current_code_info(); &mut info.blocks[info.current_block] diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index a791bf63f58..745726bf39b 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -108,7 +108,7 @@ pub struct InstructionInfo { } /// Exception handler information for an instruction. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ExceptHandlerInfo { /// Block to jump to when exception occurs pub handler_block: BlockIdx, @@ -125,6 +125,13 @@ pub struct ExceptHandlerInfo { pub struct Block { pub instructions: Vec, pub next: BlockIdx, + // Post-codegen analysis fields (set by label_exception_targets) + /// Whether this block is an exception handler target (b_except_handler) + pub except_handler: bool, + /// Whether to preserve lasti for this handler block (b_preserve_lasti) + pub preserve_lasti: bool, + /// Stack depth at block entry, set by stack depth analysis + pub start_depth: Option, } impl Default for Block { @@ -132,6 +139,9 @@ impl Default for Block { Self { instructions: Vec::new(), next: BlockIdx::NULL, + except_handler: false, + preserve_lasti: false, + start_depth: None, } } } @@ -192,6 +202,10 @@ impl CodeInfo { // Always apply LOAD_FAST_BORROW optimization self.optimize_load_fast_borrow(); + // Post-codegen CFG analysis passes (flowgraph.c pipeline) + mark_except_handlers(&mut self.blocks); + label_exception_targets(&mut self.blocks); + let max_stackdepth = self.max_stackdepth()?; let cell2arg = self.cell2arg(); @@ -230,43 +244,15 @@ impl CodeInfo { let mut locations = Vec::new(); let mut linetable_locations: Vec = Vec::new(); - // convert_pseudo_ops: instructions before the main loop + // Convert pseudo ops and remove resulting NOPs + convert_pseudo_ops(&mut blocks, varname_cache.len() as u32); for block in blocks .iter_mut() .filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty()) { - for info in &mut block.instructions { - // Real instructions are already encoded by compile.rs - let Some(instr) = info.instr.pseudo() else { - continue; - }; - - match instr { - // POP_BLOCK pseudo → NOP - PseudoInstruction::PopBlock => { - info.instr = Instruction::Nop.into(); - } - // LOAD_CLOSURE pseudo → LOAD_FAST (with varnames offset) - PseudoInstruction::LoadClosure(idx) => { - let varnames_len = varname_cache.len() as u32; - let new_idx = varnames_len + idx.get(info.arg); - info.arg = OpArg(new_idx); - info.instr = Instruction::LoadFast(Arg::marker()).into(); - } - PseudoInstruction::Jump { .. } | PseudoInstruction::JumpNoInterrupt { .. } => { - // Jump pseudo instructions are handled later - } - PseudoInstruction::AnnotationsPlaceholder - | PseudoInstruction::JumpIfFalse { .. } - | PseudoInstruction::JumpIfTrue { .. } - | PseudoInstruction::SetupCleanup - | PseudoInstruction::SetupFinally - | PseudoInstruction::SetupWith - | PseudoInstruction::StoreFastMaybeNull(_) => { - unimplemented!("Got a placeholder pseudo instruction ({instr:?})") - } - } - } + block + .instructions + .retain(|ins| !matches!(ins.instr.real(), Some(Instruction::Nop))); } let mut block_to_offset = vec![Label(0); blocks.len()]; @@ -783,7 +769,7 @@ impl CodeInfo { } } - fn max_stackdepth(&self) -> crate::InternalResult { + fn max_stackdepth(&mut self) -> crate::InternalResult { let mut maxdepth = 0u32; let mut stack = Vec::with_capacity(self.blocks.len()); let mut start_depths = vec![u32::MAX; self.blocks.len()]; @@ -835,47 +821,40 @@ impl CodeInfo { } // Process target blocks for branching instructions if ins.target != BlockIdx::NULL { - // Both jump and non-jump paths have the same stack effect - let target_depth = depth.checked_add_signed(effect).ok_or({ - if effect < 0 { - InternalError::StackUnderflow - } else { - InternalError::StackOverflow + if instr.is_block_push() { + // SETUP_* pseudo ops: target is a handler block. + // Handler entry depth uses the jump-path stack effect: + // SETUP_FINALLY: +1 (pushes exc) + // SETUP_CLEANUP: +2 (pushes lasti + exc) + // SETUP_WITH: +1 (pops __enter__ result, pushes lasti + exc) + let handler_effect: u32 = match instr.pseudo() { + Some(PseudoInstruction::SetupCleanup { .. }) => 2, + _ => 1, // SetupFinally and SetupWith + }; + let handler_depth = depth + handler_effect; + if handler_depth > maxdepth { + maxdepth = handler_depth; } - })?; - if target_depth > maxdepth { - maxdepth = target_depth - } - stackdepth_push(&mut stack, &mut start_depths, ins.target, target_depth); - } - // Process exception handler blocks - // When exception occurs, stack is unwound to handler.stack_depth, then: - // - If preserve_lasti: push lasti (+1) - // - Push exception (+1) - // - Handler block starts with PUSH_EXC_INFO as its first instruction - // So the starting depth for the handler block (BEFORE PUSH_EXC_INFO) is: - // handler.stack_depth + preserve_lasti + 1 (exc) - // PUSH_EXC_INFO will then add +1 when the block is processed - if let Some(ref handler) = ins.except_handler { - let handler_depth = handler.stack_depth + 1 + (handler.preserve_lasti as u32); // +1 for exception, +1 for lasti if preserve_lasti - if DEBUG { - eprintln!( - " HANDLER: block={} depth={} (base={} lasti={})", - handler.handler_block.0, - handler_depth, - handler.stack_depth, - handler.preserve_lasti - ); - } - if handler_depth > maxdepth { - maxdepth = handler_depth; + stackdepth_push(&mut stack, &mut start_depths, ins.target, handler_depth); + } else { + // SEND jumps to END_SEND with receiver still on stack. + // END_SEND performs the receiver pop. + let jump_effect = match instr.real() { + Some(Instruction::Send { .. }) => 0i32, + _ => effect, + }; + let target_depth = depth.checked_add_signed(jump_effect).ok_or({ + if jump_effect < 0 { + InternalError::StackUnderflow + } else { + InternalError::StackOverflow + } + })?; + if target_depth > maxdepth { + maxdepth = target_depth + } + stackdepth_push(&mut stack, &mut start_depths, ins.target, target_depth); } - stackdepth_push( - &mut stack, - &mut start_depths, - handler.handler_block, - handler_depth, - ); } depth = new_depth; if instr.is_scope_exit() || instr.is_unconditional_jump() { @@ -890,6 +869,25 @@ impl CodeInfo { if DEBUG { eprintln!("DONE: {maxdepth}"); } + + // Fix up handler stack_depth in ExceptHandlerInfo using start_depths + // computed above: depth = start_depth - 1 - preserve_lasti + for block in self.blocks.iter_mut() { + for ins in &mut block.instructions { + if let Some(ref mut handler) = ins.except_handler { + let h_start = start_depths[handler.handler_block.idx()]; + if h_start != u32::MAX { + let adjustment = 1 + handler.preserve_lasti as u32; + debug_assert!( + h_start >= adjustment, + "handler start depth {h_start} too shallow for adjustment {adjustment}" + ); + handler.stack_depth = h_start.saturating_sub(adjustment); + } + } + } + } + Ok(maxdepth) } } @@ -1135,3 +1133,194 @@ fn generate_exception_table(blocks: &[Block], block_to_index: &[u32]) -> Box<[u8 encode_exception_table(&entries) } + +/// Mark exception handler target blocks. +/// flowgraph.c mark_except_handlers +pub(crate) fn mark_except_handlers(blocks: &mut [Block]) { + // Reset handler flags + for block in blocks.iter_mut() { + block.except_handler = false; + block.preserve_lasti = false; + } + // Mark target blocks of SETUP_* as except handlers + let targets: Vec = blocks + .iter() + .flat_map(|b| b.instructions.iter()) + .filter(|i| i.instr.is_block_push() && i.target != BlockIdx::NULL) + .map(|i| i.target.idx()) + .collect(); + for idx in targets { + blocks[idx].except_handler = true; + } +} + +/// Label exception targets: walk CFG with except stack, set per-instruction +/// handler info and block preserve_lasti flag. Converts POP_BLOCK to NOP. +/// flowgraph.c label_exception_targets + push_except_block +pub(crate) fn label_exception_targets(blocks: &mut [Block]) { + #[derive(Clone)] + struct ExceptEntry { + handler_block: BlockIdx, + preserve_lasti: bool, + } + + let num_blocks = blocks.len(); + if num_blocks == 0 { + return; + } + + let mut visited = vec![false; num_blocks]; + let mut block_stacks: Vec>> = vec![None; num_blocks]; + + // Entry block + visited[0] = true; + block_stacks[0] = Some(Vec::new()); + + let mut todo = vec![BlockIdx(0)]; + + while let Some(block_idx) = todo.pop() { + let bi = block_idx.idx(); + let mut stack = block_stacks[bi].take().unwrap_or_default(); + let mut last_yield_except_depth: i32 = -1; + + let instr_count = blocks[bi].instructions.len(); + for i in 0..instr_count { + // Read all needed fields (each temporary borrow ends immediately) + let target = blocks[bi].instructions[i].target; + let arg = blocks[bi].instructions[i].arg; + let is_push = blocks[bi].instructions[i].instr.is_block_push(); + let is_pop = blocks[bi].instructions[i].instr.is_pop_block(); + + if is_push { + // Determine preserve_lasti from instruction type (push_except_block) + let preserve_lasti = matches!( + blocks[bi].instructions[i].instr.pseudo(), + Some( + PseudoInstruction::SetupWith { .. } + | PseudoInstruction::SetupCleanup { .. } + ) + ); + + // Set preserve_lasti on handler block + if preserve_lasti && target != BlockIdx::NULL { + blocks[target.idx()].preserve_lasti = true; + } + + // Propagate except stack to handler block if not visited + if target != BlockIdx::NULL && !visited[target.idx()] { + visited[target.idx()] = true; + block_stacks[target.idx()] = Some(stack.clone()); + todo.push(target); + } + + // Push handler onto except stack + stack.push(ExceptEntry { + handler_block: target, + preserve_lasti, + }); + } else if is_pop { + debug_assert!(!stack.is_empty(), "POP_BLOCK with empty except stack at block {bi} instruction {i}"); + stack.pop(); + // POP_BLOCK → NOP + blocks[bi].instructions[i].instr = Instruction::Nop.into(); + } else { + // Set except_handler for this instruction from except stack top + // stack_depth placeholder: filled by fixup_handler_depths + let handler_info = stack.last().map(|e| ExceptHandlerInfo { + handler_block: e.handler_block, + stack_depth: 0, + preserve_lasti: e.preserve_lasti, + }); + blocks[bi].instructions[i].except_handler = handler_info; + + // Track YIELD_VALUE except stack depth + if matches!( + blocks[bi].instructions[i].instr.real(), + Some(Instruction::YieldValue { .. }) + ) { + last_yield_except_depth = stack.len() as i32; + } + + // Set RESUME DEPTH1 flag based on last yield's except depth + if matches!( + blocks[bi].instructions[i].instr.real(), + Some(Instruction::Resume { .. }) + ) { + const RESUME_AT_FUNC_START: u32 = 0; + const RESUME_OPARG_LOCATION_MASK: u32 = 0x3; + const RESUME_OPARG_DEPTH1_MASK: u32 = 0x4; + + if (arg.0 & RESUME_OPARG_LOCATION_MASK) != RESUME_AT_FUNC_START { + if last_yield_except_depth == 1 { + blocks[bi].instructions[i].arg = + OpArg(arg.0 | RESUME_OPARG_DEPTH1_MASK); + } + last_yield_except_depth = -1; + } + } + + // For jump instructions, propagate except stack to target + if target != BlockIdx::NULL && !visited[target.idx()] { + visited[target.idx()] = true; + block_stacks[target.idx()] = Some(stack.clone()); + todo.push(target); + } + } + } + + // Propagate to fallthrough block (block.next) + let next = blocks[bi].next; + if next != BlockIdx::NULL && !visited[next.idx()] { + let has_fallthrough = blocks[bi] + .instructions + .last() + .map(|ins| !ins.instr.is_scope_exit() && !ins.instr.is_unconditional_jump()) + .unwrap_or(true); // Empty block falls through + if has_fallthrough { + visited[next.idx()] = true; + block_stacks[next.idx()] = Some(stack); + todo.push(next); + } + } + } +} + +/// Convert remaining pseudo ops to real instructions or NOP. +/// flowgraph.c convert_pseudo_ops +pub(crate) fn convert_pseudo_ops(blocks: &mut [Block], varnames_len: u32) { + for block in blocks.iter_mut() { + for info in &mut block.instructions { + let Some(pseudo) = info.instr.pseudo() else { + continue; + }; + match pseudo { + // Block push pseudo ops → NOP + PseudoInstruction::SetupCleanup { .. } + | PseudoInstruction::SetupFinally { .. } + | PseudoInstruction::SetupWith { .. } => { + info.instr = Instruction::Nop.into(); + } + // PopBlock in reachable blocks is converted to NOP by + // label_exception_targets. Dead blocks may still have them. + PseudoInstruction::PopBlock => { + info.instr = Instruction::Nop.into(); + } + // LOAD_CLOSURE → LOAD_FAST (with varnames offset) + PseudoInstruction::LoadClosure(idx) => { + let new_idx = varnames_len + idx.get(info.arg); + info.arg = OpArg(new_idx); + info.instr = Instruction::LoadFast(Arg::marker()).into(); + } + // Jump pseudo ops are resolved during block linearization + PseudoInstruction::Jump { .. } | PseudoInstruction::JumpNoInterrupt { .. } => {} + // These should have been resolved earlier + PseudoInstruction::AnnotationsPlaceholder + | PseudoInstruction::JumpIfFalse { .. } + | PseudoInstruction::JumpIfTrue { .. } + | PseudoInstruction::StoreFastMaybeNull(_) => { + unreachable!("Unexpected pseudo instruction in convert_pseudo_ops: {pseudo:?}") + } + } + } + } +} diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index 525b87bfe55..ee36220b538 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -624,10 +624,7 @@ impl InstructionMetadata for Instruction { Self::LoadLocals => (1, 0), Self::LoadName(_) => (1, 0), Self::LoadSmallInt { .. } => (1, 0), - Self::LoadSpecial { .. } => ( - 2, // TODO: Differs from CPython: `1` - 2, - ), + Self::LoadSpecial { .. } => (1, 1), Self::LoadSuperAttr { .. } => (1 + (oparg & 1), 3), Self::LoadSuperAttrAttr => (1, 3), Self::LoadSuperAttrMethod => (2, 3), @@ -943,9 +940,9 @@ pub enum PseudoInstruction { JumpNoInterrupt { target: Arg