Skip to content

Commit b017cd5

Browse files
committed
more ops
1 parent 3eb4676 commit b017cd5

File tree

4 files changed

+95
-37
lines changed

4 files changed

+95
-37
lines changed

crates/codegen/src/ir.rs

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {

crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/compiler-core/src/bytecode/instruction.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ use crate::{
2323
#[repr(u8)]
2424
pub enum Instruction {
2525
// No-argument instructions (opcode < HAVE_ARGUMENT=44)
26-
Cache = 0, // Placeholder
26+
Cache = 0,
2727
BinarySlice = 1,
2828
BuildTemplate = 2,
29-
BinaryOpInplaceAddUnicode = 3, // Placeholder
29+
BinaryOpInplaceAddUnicode = 3,
3030
CallFunctionEx = 4,
3131
CheckEgMatch = 5,
3232
CheckExcMatch = 6,
@@ -51,7 +51,7 @@ pub enum Instruction {
5151
MatchMapping = 25,
5252
MatchSequence = 26,
5353
Nop = 27,
54-
NotTaken = 28, // Placeholder
54+
NotTaken = 28,
5555
PopExcept = 29,
5656
PopIter = 30,
5757
PopTop = 31,
@@ -348,13 +348,13 @@ pub enum Instruction {
348348
UnpackSequenceTuple = 210, // Placeholder
349349
UnpackSequenceTwoTuple = 211, // Placeholder
350350
// CPython 3.14 instrumented opcodes (234-254)
351-
InstrumentedEndFor = 234, // Placeholder
352-
InstrumentedPopIter = 235, // Placeholder
353-
InstrumentedEndSend = 236, // Placeholder
354-
InstrumentedForIter = 237, // Placeholder
355-
InstrumentedInstruction = 238, // Placeholder
356-
InstrumentedJumpForward = 239, // Placeholder
357-
InstrumentedNotTaken = 240, // Placeholder
351+
InstrumentedEndFor = 234, // Placeholder
352+
InstrumentedPopIter = 235, // Placeholder
353+
InstrumentedEndSend = 236, // Placeholder
354+
InstrumentedForIter = 237, // Placeholder
355+
InstrumentedInstruction = 238, // Placeholder
356+
InstrumentedJumpForward = 239, // Placeholder
357+
InstrumentedNotTaken = 240,
358358
InstrumentedPopJumpIfTrue = 241, // Placeholder
359359
InstrumentedPopJumpIfFalse = 242, // Placeholder
360360
InstrumentedPopJumpIfNone = 243, // Placeholder

crates/vm/src/frame.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,11 @@ impl ExecutingFrame<'_> {
618618

619619
match instruction {
620620
Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)),
621+
// TODO: In CPython, this does in-place unicode concatenation when
622+
// refcount is 1. Falls back to regular iadd for now.
623+
Instruction::BinaryOpInplaceAddUnicode => {
624+
self.execute_bin_op(vm, bytecode::BinaryOperator::InplaceAdd)
625+
}
621626
Instruction::BinarySlice => {
622627
// Stack: [container, start, stop] -> [result]
623628
let stop = self.pop_value();
@@ -1184,8 +1189,16 @@ impl ExecutingFrame<'_> {
11841189
} else {
11851190
self.code.freevars[i - self.code.cellvars.len()]
11861191
};
1187-
// First try to find in the dict
1188-
let value = class_dict.get_item(name, vm).ok();
1192+
// Only treat KeyError as "not found", propagate other exceptions
1193+
let value = if let Some(dict_obj) = class_dict.downcast_ref::<PyDict>() {
1194+
dict_obj.get_item_opt(name, vm)?
1195+
} else {
1196+
match class_dict.get_item(name, vm) {
1197+
Ok(v) => Some(v),
1198+
Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => None,
1199+
Err(e) => return Err(e),
1200+
}
1201+
};
11891202
self.push_value(match value {
11901203
Some(v) => v,
11911204
None => self.cells_frees[i]
@@ -1654,6 +1667,12 @@ impl ExecutingFrame<'_> {
16541667
Ok(None)
16551668
}
16561669
Instruction::Nop => Ok(None),
1670+
// NOT_TAKEN is a branch prediction hint - functionally a NOP
1671+
Instruction::NotTaken => Ok(None),
1672+
// Instrumented version of NOT_TAKEN - NOP without monitoring
1673+
Instruction::InstrumentedNotTaken => Ok(None),
1674+
// CACHE is used by adaptive interpreter for inline caching - NOP for us
1675+
Instruction::Cache => Ok(None),
16571676
Instruction::ReturnGenerator => {
16581677
// In RustPython, generators/coroutines are created in function.rs
16591678
// before the frame starts executing. The RETURN_GENERATOR instruction

0 commit comments

Comments
 (0)