| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7c64b96 commit 702b388
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -99,8 +99,6 @@ def woohoo(): | |||
| 99 | 99 | raise ZeroDivisionError() | |
| 100 | 100 | self.assertEqual(state, [1, 42, 999]) | |
| 101 | 101 | ||
| 102 | - # TODO: RUSTPYTHON | ||
| 103 | - @unittest.expectedFailure | ||
| 104 | 102 | def test_contextmanager_traceback(self): | |
| 105 | 103 | @contextmanager | |
| 106 | 104 | def f(): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -252,7 +252,6 @@ async def woohoo(): | |||
| 252 | 252 | raise ZeroDivisionError(999) | |
| 253 | 253 | self.assertEqual(state, [1, 42, 999]) | |
| 254 | 254 | ||
| 255 | - @unittest.expectedFailure # TODO: RUSTPYTHON | ||
| 256 | 255 | async def test_contextmanager_except_stopiter(self): | |
| 257 | 256 | @asynccontextmanager | |
| 258 | 257 | async def woohoo(): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -132,7 +132,6 @@ def f(): | |||
| 132 | 132 | self.assertInBytecode(f, 'LOAD_CONST', None) | |
| 133 | 133 | self.check_lnotab(f) | |
| 134 | 134 | ||
| 135 | - @unittest.expectedFailure # TODO: RUSTPYTHON; RETURN_VALUE | ||
| 136 | 135 | def test_while_one(self): | |
| 137 | 136 | # Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx | |
| 138 | 137 | def f(): | |
@@ -545,7 +544,6 @@ def f(cond, true_value, false_value): | |||
| 545 | 544 | self.assertEqual(len(returns), 2) | |
| 546 | 545 | self.check_lnotab(f) | |
| 547 | 546 | ||
| 548 | - @unittest.expectedFailure # TODO: RUSTPYTHON; absolute jump encoding | ||
| 549 | 547 | def test_elim_jump_to_uncond_jump(self): | |
| 550 | 548 | # POP_JUMP_IF_FALSE to JUMP_FORWARD --> POP_JUMP_IF_FALSE to non-jump | |
| 551 | 549 | def f(): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -209,22 +209,21 @@ impl CodeInfo { | |||
| 209 | 209 | // Peephole optimizer creates superinstructions matching CPython | |
| 210 | 210 | self.peephole_optimize(); | |
| 211 | 211 | ||
| 212 | - // insert_superinstructions (flowgraph.c): must run BEFORE optimize_load_fast | ||
| 213 | - self.combine_store_fast_load_fast(); | ||
| 214 | - | ||
| 215 | - // optimize_load_fast (flowgraph.c): LOAD_FAST → LOAD_FAST_BORROW | ||
| 216 | - self.optimize_load_fast_borrow(); | ||
| 217 | - | ||
| 218 | - // Post-codegen CFG analysis passes (flowgraph.c pipeline) | ||
| 212 | + // Phase 1: _PyCfg_OptimizeCodeUnit (flowgraph.c) | ||
| 219 | 213 | mark_except_handlers(&mut self.blocks); | |
| 220 | 214 | label_exception_targets(&mut self.blocks); | |
| 215 | + // TODO: insert_superinstructions disabled pending StoreFastLoadFast VM fix | ||
| 221 | 216 | push_cold_blocks_to_end(&mut self.blocks); | |
| 217 | + | ||
| 218 | + // Phase 2: _PyCfg_OptimizedCfgToInstructionSequence (flowgraph.c) | ||
| 222 | 219 | normalize_jumps(&mut self.blocks); | |
| 223 | 220 | self.dce(); // re-run within-block DCE after normalize_jumps creates new instructions | |
| 224 | 221 | self.eliminate_unreachable_blocks(); | |
| 225 | 222 | duplicate_end_returns(&mut self.blocks); | |
| 226 | 223 | self.dce(); // truncate after terminal in blocks that got return duplicated | |
| 227 | 224 | self.eliminate_unreachable_blocks(); // remove now-unreachable last block | |
| 225 | + // optimize_load_fast: after normalize_jumps | ||
| 226 | + self.optimize_load_fast_borrow(); | ||
| 228 | 227 | self.optimize_load_global_push_null(); | |
| 229 | 228 | ||
| 230 | 229 | let max_stackdepth = self.max_stackdepth()?; | |
@@ -850,17 +849,12 @@ impl CodeInfo { | |||
| 850 | 849 | l / r | |
| 851 | 850 | } | |
| 852 | 851 | BinOp::FloorDivide => { | |
| 853 | - if *r == 0.0 { | ||
| 854 | - return None; | ||
| 855 | - } | ||
| 856 | - (l / r).floor() | ||
| 852 | + // Float floor division uses runtime semantics; skip folding | ||
| 853 | + return None; | ||
| 857 | 854 | } | |
| 858 | 855 | BinOp::Remainder => { | |
| 859 | - if *r == 0.0 { | ||
| 860 | - return None; | ||
| 861 | - } | ||
| 862 | - // Python float modulo: a - b * floor(a/b) | ||
| 863 | - l - r * (l / r).floor() | ||
| 856 | + // Float modulo uses fmod() at runtime; Rust arithmetic differs | ||
| 857 | + return None; | ||
| 864 | 858 | } | |
| 865 | 859 | BinOp::Power => l.powf(*r), | |
| 866 | 860 | _ => return None, | |
@@ -1491,8 +1485,8 @@ impl CodeInfo { | |||
| 1491 | 1485 | /// Optimize LOAD_FAST to LOAD_FAST_BORROW where safe. | |
| 1492 | 1486 | /// | |
| 1493 | 1487 | /// insert_superinstructions (flowgraph.c): Combine STORE_FAST + LOAD_FAST → | |
| 1494 | - /// STORE_FAST_LOAD_FAST. Must run BEFORE optimize_load_fast_borrow so that | ||
| 1495 | - /// the borrow pass sees the combined instruction (matching flowgraph.c order). | ||
| 1488 | + /// STORE_FAST_LOAD_FAST. Currently disabled pending VM stack null investigation. | ||
| 1489 | + #[allow(dead_code)] | ||
| 1496 | 1490 | fn combine_store_fast_load_fast(&mut self) { | |
| 1497 | 1491 | for block in &mut self.blocks { | |
| 1498 | 1492 | let mut i = 0; | |
@@ -1505,6 +1499,13 @@ impl CodeInfo { | |||
| 1505 | 1499 | i += 1; | |
| 1506 | 1500 | continue; | |
| 1507 | 1501 | }; | |
| 1502 | + // Skip if instructions are on different lines (matching make_super_instruction) | ||
| 1503 | + let line1 = curr.location.line; | ||
| 1504 | + let line2 = next.location.line; | ||
| 1505 | + if line1 != line2 { | ||
| 1506 | + i += 1; | ||
| 1507 | + continue; | ||
| 1508 | + } | ||
| 1508 | 1509 | let idx1 = u32::from(curr.arg); | |
| 1509 | 1510 | let idx2 = u32::from(next.arg); | |
| 1510 | 1511 | if idx1 < 16 && idx2 < 16 { | |
@@ -1514,8 +1515,10 @@ impl CodeInfo { | |||
| 1514 | 1515 | } | |
| 1515 | 1516 | .into(); | |
| 1516 | 1517 | block.instructions[i].arg = OpArg::new(packed); | |
| 1517 | - block.instructions.remove(i + 1); | ||
| 1518 | - // Don't advance — check if next pair can also be combined | ||
| 1518 | + // Replace second instruction with NOP (CPython: INSTR_SET_OP0(inst2, NOP)) | ||
| 1519 | + block.instructions[i + 1].instr = Instruction::Nop.into(); | ||
| 1520 | + block.instructions[i + 1].arg = OpArg::new(0); | ||
| 1521 | + i += 2; // skip the NOP | ||
| 1519 | 1522 | } else { | |
| 1520 | 1523 | i += 1; | |
| 1521 | 1524 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -135,6 +135,72 @@ pub fn decode_exception_table(table: &[u8]) -> Vec<ExceptionTableEntry> { | |||
| 135 | 135 | entries | |
| 136 | 136 | } | |
| 137 | 137 | ||
| 138 | + /// Parse linetable to build a boolean mask indicating which code units | ||
| 139 | + /// have NO_LOCATION (line == -1). Returns a Vec<bool> of length `num_units`. | ||
| 140 | + pub fn build_no_location_mask(linetable: &[u8], num_units: usize) -> Vec<bool> { | ||
| 141 | + let mut mask = Vec::new(); | ||
| 142 | + mask.resize(num_units, false); | ||
| 143 | + let mut pos = 0; | ||
| 144 | + let mut unit_idx = 0; | ||
| 145 | + | ||
| 146 | + while pos < linetable.len() && unit_idx < num_units { | ||
| 147 | + let header = linetable[pos]; | ||
| 148 | + pos += 1; | ||
| 149 | + let code = (header >> 3) & 0xf; | ||
| 150 | + let length = ((header & 7) + 1) as usize; | ||
| 151 | + | ||
| 152 | + let is_no_location = code == PyCodeLocationInfoKind::None as u8; | ||
| 153 | + | ||
| 154 | + // Skip payload bytes based on location kind | ||
| 155 | + match code { | ||
| 156 | + 0..=9 => pos += 1, // Short forms: 1 byte payload | ||
| 157 | + 10..=12 => pos += 2, // OneLine forms: 2 bytes payload | ||
| 158 | + 13 => { | ||
| 159 | + // NoColumns: signed varint (line delta) | ||
| 160 | + while pos < linetable.len() { | ||
| 161 | + let b = linetable[pos]; | ||
| 162 | + pos += 1; | ||
| 163 | + if b & 0x40 == 0 { | ||
| 164 | + break; | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + } | ||
| 168 | + 14 => { | ||
| 169 | + // Long form: signed varint (line delta) + 3 unsigned varints | ||
| 170 | + // line_delta | ||
| 171 | + while pos < linetable.len() { | ||
| 172 | + let b = linetable[pos]; | ||
| 173 | + pos += 1; | ||
| 174 | + if b & 0x40 == 0 { | ||
| 175 | + break; | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | + // end_line_delta, col+1, end_col+1 | ||
| 179 | + for _ in 0..3 { | ||
| 180 | + while pos < linetable.len() { | ||
| 181 | + let b = linetable[pos]; | ||
| 182 | + pos += 1; | ||
| 183 | + if b & 0x40 == 0 { | ||
| 184 | + break; | ||
| 185 | + } | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + 15 => {} // None: no payload | ||
| 190 | + _ => {} | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + for _ in 0..length { | ||
| 194 | + if unit_idx < num_units { | ||
| 195 | + mask[unit_idx] = is_no_location; | ||
| 196 | + unit_idx += 1; | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + mask | ||
| 202 | + } | ||
| 203 | + | ||
| 138 | 204 | /// CPython 3.11+ linetable location info codes | |
| 139 | 205 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | |
| 140 | 206 | #[repr(u8)] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -736,6 +736,20 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { | |||
| 736 | 736 | let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; | |
| 737 | 737 | self.store_variable(var_num.get(arg), val) | |
| 738 | 738 | } | |
| 739 | + Instruction::StoreFastLoadFast { var_nums } => { | ||
| 740 | + let oparg = var_nums.get(arg); | ||
| 741 | + let (store_idx, load_idx) = oparg.indexes(); | ||
| 742 | + let val = self.stack.pop().ok_or(JitCompileError::BadBytecode)?; | ||
| 743 | + self.store_variable(store_idx, val)?; | ||
| 744 | + let local = self.variables[load_idx] | ||
| 745 | + .as_ref() | ||
| 746 | + .ok_or(JitCompileError::BadBytecode)?; | ||
| 747 | + self.stack.push(JitValue::from_type_and_value( | ||
| 748 | + local.ty.clone(), | ||
| 749 | + self.builder.use_var(local.var), | ||
| 750 | + )); | ||
| 751 | + Ok(()) | ||
| 752 | + } | ||
| 739 | 753 | Instruction::StoreFastStoreFast { var_nums } => { | |
| 740 | 754 | let oparg = var_nums.get(arg); | |
| 741 | 755 | let (idx1, idx2) = oparg.indexes(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1755,12 +1755,6 @@ impl ExecutingFrame<'_> { | |||
| 1755 | 1755 | exc_tb: PyObjectRef, | |
| 1756 | 1756 | ) -> PyResult<ExecutionResult> { | |
| 1757 | 1757 | self.monitoring_mask = vm.state.monitoring_events.load(); | |
| 1758 | - // Reset prev_line so that LINE monitoring events fire even if | ||
| 1759 | - // the exception handler is on the same line as the yield point. | ||
| 1760 | - // In CPython, _Py_call_instrumentation_line has a special case | ||
| 1761 | - // for RESUME: it fires LINE even when prev_line == current_line. | ||
| 1762 | - // Since gen_throw bypasses RESUME, we reset prev_line instead. | ||
| 1763 | - *self.prev_line = 0; | ||
| 1764 | 1758 | if let Some(jen) = self.yield_from_target() { | |
| 1765 | 1759 | // Check if the exception is GeneratorExit (type or instance). | |
| 1766 | 1760 | // For GeneratorExit, close the sub-iterator instead of throwing. | |
@@ -1796,7 +1790,10 @@ impl ExecutingFrame<'_> { | |||
| 1796 | 1790 | self.push_value(vm.ctx.none()); | |
| 1797 | 1791 | vm.contextualize_exception(&err); | |
| 1798 | 1792 | return match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { | |
| 1799 | - Ok(None) => self.run(vm), | ||
| 1793 | + Ok(None) => { | ||
| 1794 | + *self.prev_line = 0; | ||
| 1795 | + self.run(vm) | ||
| 1796 | + } | ||
| 1800 | 1797 | Ok(Some(result)) => Ok(result), | |
| 1801 | 1798 | Err(exception) => Err(exception), | |
| 1802 | 1799 | }; | |
@@ -1838,7 +1835,10 @@ impl ExecutingFrame<'_> { | |||
| 1838 | 1835 | self.push_value(vm.ctx.none()); | |
| 1839 | 1836 | vm.contextualize_exception(&err); | |
| 1840 | 1837 | match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { | |
| 1841 | - Ok(None) => self.run(vm), | ||
| 1838 | + Ok(None) => { | ||
| 1839 | + *self.prev_line = 0; | ||
| 1840 | + self.run(vm) | ||
| 1841 | + } | ||
| 1842 | 1842 | Ok(Some(result)) => Ok(result), | |
| 1843 | 1843 | Err(exception) => Err(exception), | |
| 1844 | 1844 | } | |
@@ -1906,7 +1906,13 @@ impl ExecutingFrame<'_> { | |||
| 1906 | 1906 | self.push_value(vm.ctx.none()); | |
| 1907 | 1907 | ||
| 1908 | 1908 | match self.unwind_blocks(vm, UnwindReason::Raising { exception }) { | |
| 1909 | - Ok(None) => self.run(vm), | ||
| 1909 | + Ok(None) => { | ||
| 1910 | + // Reset prev_line so that the first instruction in the handler | ||
| 1911 | + // fires a LINE event. In CPython, gen_send_ex re-enters the | ||
| 1912 | + // eval loop which reinitializes its local prev_instr tracker. | ||
| 1913 | + *self.prev_line = 0; | ||
| 1914 | + self.run(vm) | ||
| 1915 | + } | ||
| 1910 | 1916 | Ok(Some(result)) => Ok(result), | |
| 1911 | 1917 | Err(exception) => { | |
| 1912 | 1918 | // Fire PY_UNWIND: exception escapes the generator frame. | |
@@ -9440,20 +9446,25 @@ impl ExecutingFrame<'_> { | |||
| 9440 | 9446 | Ok(vm.ctx.new_tuple(list.borrow_vec().to_vec()).into()) | |
| 9441 | 9447 | } | |
| 9442 | 9448 | bytecode::IntrinsicFunction1::StopIterationError => { | |
| 9443 | - // Convert StopIteration to RuntimeError | ||
| 9444 | - // Used to ensure async generators don't raise StopIteration directly | ||
| 9445 | - // _PyGen_FetchStopIterationValue | ||
| 9446 | - // Use fast_isinstance to handle subclasses of StopIteration | ||
| 9449 | + // Convert StopIteration to RuntimeError (PEP 479) | ||
| 9450 | + // Returns the exception object; RERAISE will re-raise it | ||
| 9447 | 9451 | if arg.fast_isinstance(vm.ctx.exceptions.stop_iteration) { | |
| 9448 | - Err(vm.new_runtime_error("coroutine raised StopIteration")) | ||
| 9452 | + let flags = &self.code.flags; | ||
| 9453 | + let msg = if flags | ||
| 9454 | + .contains(bytecode::CodeFlags::COROUTINE | bytecode::CodeFlags::GENERATOR) | ||
| 9455 | + { | ||
| 9456 | + "async generator raised StopIteration" | ||
| 9457 | + } else if flags.contains(bytecode::CodeFlags::COROUTINE) { | ||
| 9458 | + "coroutine raised StopIteration" | ||
| 9459 | + } else { | ||
| 9460 | + "generator raised StopIteration" | ||
| 9461 | + }; | ||
| 9462 | + let err = vm.new_runtime_error(msg); | ||
| 9463 | + err.set___cause__(arg.downcast().ok()); | ||
| 9464 | + Ok(err.into()) | ||
| 9449 | 9465 | } else { | |
| 9450 | - // If not StopIteration, just re-raise the original exception | ||
| 9451 | - Err(arg.downcast().unwrap_or_else(|obj| { | ||
| 9452 | - vm.new_runtime_error(format!( | ||
| 9453 | - "unexpected exception type: {:?}", | ||
| 9454 | - obj.class() | ||
| 9455 | - )) | ||
| 9456 | - })) | ||
| 9466 | + // Not StopIteration, pass through for RERAISE | ||
| 9467 | + Ok(arg) | ||
| 9457 | 9468 | } | |
| 9458 | 9469 | } | |
| 9459 | 9470 | bytecode::IntrinsicFunction1::AsyncGenWrap => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -174,6 +174,7 @@ mod _symtable { | |||
| 174 | 174 | .symtable | |
| 175 | 175 | .sub_tables | |
| 176 | 176 | .iter() | |
| 177 | + .filter(|t| !t.comp_inlined) | ||
| 177 | 178 | .map(|t| to_py_symbol_table(t.clone()).into_pyobject(vm)) | |
| 178 | 179 | .collect(); | |
| 179 | 180 | Ok(children) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -368,6 +368,9 @@ pub fn instrument_code(code: &PyCode, events: u32) { | |||
| 368 | 368 | // is_line_start[i] = true if position i should have INSTRUMENTED_LINE | |
| 369 | 369 | let mut is_line_start = vec![false; len]; | |
| 370 | 370 | ||
| 371 | + // Build NO_LOCATION mask from linetable | ||
| 372 | + let no_loc_mask = bytecode::build_no_location_mask(&code.code.linetable, len); | ||
| 373 | + | ||
| 371 | 374 | // First pass: mark positions where the source line changes | |
| 372 | 375 | let mut prev_line: Option<u32> = None; | |
| 373 | 376 | for (i, unit) in code | |
@@ -395,6 +398,10 @@ pub fn instrument_code(code: &PyCode, events: u32) { | |||
| 395 | 398 | ) { | |
| 396 | 399 | continue; | |
| 397 | 400 | } | |
| 401 | + // Skip NO_LOCATION instructions | ||
| 402 | + if no_loc_mask.get(i).copied().unwrap_or(false) { | ||
| 403 | + continue; | ||
| 404 | + } | ||
| 398 | 405 | if let Some((loc, _)) = code.code.locations.get(i) { | |
| 399 | 406 | let line = loc.line.get() as u32; | |
| 400 | 407 | let is_new = prev_line != Some(line); | |
@@ -445,6 +452,7 @@ pub fn instrument_code(code: &PyCode, events: u32) { | |||
| 445 | 452 | if let Some(target_idx) = target | |
| 446 | 453 | && target_idx < len | |
| 447 | 454 | && !is_line_start[target_idx] | |
| 455 | + && !no_loc_mask.get(target_idx).copied().unwrap_or(false) | ||
| 448 | 456 | { | |
| 449 | 457 | let target_op = code.code.instructions[target_idx].op; | |
| 450 | 458 | let target_base = target_op.to_base().map_or(target_op, |b| b); | |
@@ -465,7 +473,10 @@ pub fn instrument_code(code: &PyCode, events: u32) { | |||
| 465 | 473 | // Third pass: mark exception handler targets as line starts. | |
| 466 | 474 | for entry in bytecode::decode_exception_table(&code.code.exceptiontable) { | |
| 467 | 475 | let target_idx = entry.target as usize; | |
| 468 | - if target_idx < len && !is_line_start[target_idx] { | ||
| 476 | + if target_idx < len | ||
| 477 | + && !is_line_start[target_idx] | ||
| 478 | + && !no_loc_mask.get(target_idx).copied().unwrap_or(false) | ||
| 479 | + { | ||
| 469 | 480 | let target_op = code.code.instructions[target_idx].op; | |
| 470 | 481 | let target_base = target_op.to_base().map_or(target_op, |b| b); | |
| 471 | 482 | if !matches!(target_base, Instruction::PopIter) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments