diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index b0115cecc60..7574d45c5f1 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -1,3 +1,6 @@ +ADDOP +aftersign +argdefs argtypes asdl asname @@ -5,83 +8,177 @@ attro augassign badcert badsyntax +baseinfo basetype boolop +BUILDSTDLIB bxor +byteswap cached_tsver cadata cafile +calldepth +callinfo +callproc +capath +carg cellarg cellvar cellvars +cfield CLASSDEREF +classdict cmpop +codedepth +CODEUNIT +CONVFUNC +convparam +copyslot +cpucount +defaultdict denom +dictbytype DICTFLAG dictoffset distpoint +dynload elts +eofs +evalloop excepthandler +exceptiontable +fblock +fblocks +fdescr +ffi_argtypes +fielddesc +fieldlist fileutils finalbody +flowgraph formatfloat freevar freevars fromlist +getdict +getfunc +getiter +getsets +getslice +globalgetvar +HASARRAY +HASBITFIELD +HASPOINTER +HASSTRUCT +HASUNION heaptype HIGHRES +IFUNC IMMUTABLETYPE INCREF +inlinedepth inplace ismine +ISPOINTER +iteminfo Itertool keeped kwnames kwonlyarg kwonlyargs lasti +libffi linearise +lineiterator +linetable +loadfast +localsplus Lshift lsprof +MAXBLOCKS maxdepth +metavars +miscompiles mult multibytecodec +nameop +nconsts +newargs +newfree +NEWLOCALS newsemlockobject +nfrees nkwargs nkwelts +Nondescriptor +noninteger +nops noraise +nseen +NSIGNALS numer +opname +opnames orelse +outparam +outparm +paramfunc +parg pathconfig patma +peepholer phcount platstdlib posonlyarg posonlyargs prec preinitialized +pybuilddir +pycore pydecimal +Pyfunc pymain pyrepl +PYTHONTRACEMALLOC pythonw PYTHREAD_NAME releasebuffer repr +resinfo Rshift SA_ONSTACK +scls +setdict +setfunc +SETREF +setresult +setslice +SLOTDEFINED SOABI +SSLEOF stackdepth +staticbase stginfo +storefast stringlib structseq subkwargs subparams subscr +sval swappedbytes +templatelib ticketer +tmptype tok_oldval tvars +typeobject +typeparam +Typeparam +typeparams +typeslots unaryop +Unhandle unparse unparser VARKEYWORDS diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 73b2d620ca6..2dd31f8f579 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -272,3 +272,29 @@ xmlcharrefreplace xoptions xopts yieldfrom +addcompare +altzone +classmethods +ctype +ctypes +genexpressions +getargs +getopt +getweakref +getweakrefs +inittab +Inittab +interpchannels +interpqueues +markupbase +mymodule +pydatetime +pyio +pymain +setprofileallthreads +settraceallthreads +sitebuiltins +subclassing +subpatterns +sysdict +weakrefset diff --git a/.cspell.dict/rust-more.txt b/.cspell.dict/rust-more.txt index ff2013e81a7..c3ebd61833a 100644 --- a/.cspell.dict/rust-more.txt +++ b/.cspell.dict/rust-more.txt @@ -28,6 +28,7 @@ hexf hexversion idents illumos +ilog indexmap insta keccak @@ -86,4 +87,7 @@ wasmer wasmtime widestring winapi +winresource winsock +bitvec +Bitvec diff --git a/.cspell.json b/.cspell.json index 89cde1ce775..253a67929ce 100644 --- a/.cspell.json +++ b/.cspell.json @@ -69,10 +69,13 @@ "GetSet", "groupref", "internable", + "interps", "jitted", "jitting", "lossily", "makeunicodedata", + "microbenchmark", + "microbenchmarks", "miri", "notrace", "oparg", @@ -121,6 +124,7 @@ "uninit", "unraisable", "unresizable", + "varint", "wasi", "zelf", // unix diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index d4e833a795a..c5c10cb5318 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1002,7 +1002,6 @@ def f(): self.assertInBytecode(f, 'LOAD_FAST_CHECK', "a73") self.assertInBytecode(f, 'LOAD_FAST_BORROW', "a73") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_setting_lineno_no_undefined(self): code = textwrap.dedent("""\ def f(): @@ -1123,7 +1122,6 @@ def f(): self.assertNotInBytecode(f, "LOAD_FAST_CHECK") return f - @unittest.expectedFailure # TODO: RUSTPYTHON def test_modifying_local_does_not_add_check(self): f = self.make_function_with_no_checks() def trace(frame, event, arg): @@ -1137,7 +1135,6 @@ def trace(frame, event, arg): self.assertInBytecode(f, "LOAD_FAST_BORROW") self.assertNotInBytecode(f, "LOAD_FAST_CHECK") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_initializing_local_does_not_add_check(self): f = self.make_function_with_no_checks() def trace(frame, event, arg): diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 8ab0e6223d1..b961be42517 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -2048,8 +2048,14 @@ impl Compiler { let op = match usage { NameUsage::Load => { - // Special case for class scope + // ClassBlock (not inlined comp): LOAD_LOCALS first, then LOAD_FROM_DICT_OR_DEREF if self.ctx.in_class && !self.ctx.in_func() { + emit!(self, Instruction::LoadLocals); + Instruction::LoadFromDictOrDeref + // can_see_class_scope: LOAD_DEREF(__classdict__) first + } else if can_see_class_scope { + let classdict_idx = self.get_free_var_index("__classdict__")?; + self.emit_arg(classdict_idx, Instruction::LoadDeref); Instruction::LoadFromDictOrDeref } else { Instruction::LoadDeref diff --git a/crates/codegen/src/ir.rs b/crates/codegen/src/ir.rs index fa662e1853f..59b5f7d4d09 100644 --- a/crates/codegen/src/ir.rs +++ b/crates/codegen/src/ir.rs @@ -189,6 +189,9 @@ impl CodeInfo { self.peephole_optimize(); } + // Always apply LOAD_FAST_BORROW optimization + self.optimize_load_fast_borrow(); + let max_stackdepth = self.max_stackdepth()?; let cell2arg = self.cell2arg(); @@ -687,6 +690,155 @@ impl CodeInfo { } } + /// Optimize LOAD_FAST to LOAD_FAST_BORROW where safe. + /// + /// A LOAD_FAST can be converted to LOAD_FAST_BORROW if its value is + /// consumed within the same basic block (not passed to another block). + /// 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; + + for block in &mut self.blocks { + if block.instructions.is_empty() { + continue; + } + + // Track which instructions' outputs are still on stack at block end + // For each instruction, we track if its pushed value(s) are unconsumed + let mut unconsumed = vec![false; block.instructions.len()]; + + // Simulate stack: each entry is the instruction index that pushed it + // (or NOT_LOCAL if not from LOAD_FAST/LOAD_FAST_LOAD_FAST). + // + // CPython (flowgraph.c optimize_load_fast) pre-fills the stack with + // dummy refs for values inherited from predecessor blocks. We take + // the simpler approach of aborting the optimisation for the whole + // block on stack underflow. + let mut stack: Vec = Vec::new(); + let mut underflow = false; + + for (i, info) in block.instructions.iter().enumerate() { + let Some(instr) = info.instr.real() else { + continue; + }; + + // Decompose into (pops, pushes). + // + // stack_effect() returns pushes − pops, which is ambiguous for + // instructions that both pop and push (e.g. BinaryOp: effect=-1 + // 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 (pops, pushes) = match instr { + // --- pop 2, push 1 --- + Instruction::BinaryOp { .. } + | Instruction::BinaryOpInplaceAddUnicode + | Instruction::CompareOp { .. } + | Instruction::ContainsOp(_) + | Instruction::IsOp(_) + | Instruction::ImportName { .. } + | Instruction::FormatWithSpec => (2, 1), + + // --- pop 1, push 1 --- + Instruction::UnaryInvert + | Instruction::UnaryNegative + | Instruction::UnaryNot + | Instruction::ToBool + | Instruction::GetIter + | Instruction::GetAIter + | Instruction::FormatSimple + | Instruction::LoadFromDictOrDeref(_) + | Instruction::LoadFromDictOrGlobals(_) => (1, 1), + + // LoadAttr: pop receiver, push attr. + // method=true: push (method, self_or_null) → (1, 2) + Instruction::LoadAttr { idx } => { + let (_, is_method) = + rustpython_compiler_core::bytecode::decode_load_attr_arg( + idx.get(info.arg), + ); + if is_method { (1, 2) } else { (1, 1) } + } + + // --- pop 3, push 1 --- + Instruction::BinarySlice => (3, 1), + + // --- variable pops, push 1 --- + Instruction::Call { nargs } => (nargs.get(info.arg) as usize + 2, 1), + Instruction::CallKw { nargs } => (nargs.get(info.arg) as usize + 3, 1), + + // --- conservative fallback --- + // under-pops (≤ actual pops) and under-pushes (≤ actual pushes), + // which keeps extra refs on the stack → marks them unconsumed → + // prevents optimisation. Safe but may miss opportunities. + _ => { + let p = if effect < 0 { (-effect) as usize } else { 0 }; + let q = if effect > 0 { effect as usize } else { 0 }; + (p, q) + } + }; + + // Pop values from stack + for _ in 0..pops { + if stack.pop().is_none() { + // Stack underflow — block receives values from a predecessor. + // Abort optimisation for the entire block. + underflow = true; + break; + } + } + if underflow { + break; + } + + // Push values to stack with source instruction index + let source = match instr { + Instruction::LoadFast(_) | Instruction::LoadFastLoadFast { .. } => i, + _ => NOT_LOCAL, + }; + for _ in 0..pushes { + stack.push(source); + } + } + + if underflow { + continue; + } + + // Mark instructions whose values remain on stack at block end + for &src in &stack { + if src != NOT_LOCAL { + unconsumed[src] = true; + } + } + + // Convert LOAD_FAST to LOAD_FAST_BORROW where value is fully consumed + for (i, info) in block.instructions.iter_mut().enumerate() { + if unconsumed[i] { + continue; + } + let Some(instr) = info.instr.real() else { + continue; + }; + match instr { + Instruction::LoadFast(_) => { + info.instr = Instruction::LoadFastBorrow(Arg::marker()).into(); + } + Instruction::LoadFastLoadFast { .. } => { + info.instr = + Instruction::LoadFastBorrowLoadFastBorrow { arg: Arg::marker() }.into(); + } + _ => {} + } + } + } + } + fn max_stackdepth(&self) -> crate::InternalResult { let mut maxdepth = 0u32; let mut stack = Vec::with_capacity(self.blocks.len()); diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap index 534f8bd34c2..4f67bd10805 100644 --- a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap @@ -1,10 +1,11 @@ --- source: crates/codegen/src/compile.rs +assertion_line: 8782 expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):\n with self.subTest(type=type(stop_exc)):\n try:\n async with egg():\n raise stop_exc\n except Exception as ex:\n self.assertIs(ex, stop_exc)\n else:\n self.fail(f'{stop_exc} was suppressed')\n\")" --- 1 0 RESUME (0) - 3 1 LOAD_CONST (): 1 0 RUSTPYTHON_PLACEHOLDER + 3 1 LOAD_CONST (): 1 0 RETURN_GENERATOR 1 POP_TOP 2 RESUME (0) @@ -25,7 +26,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 16 LOAD_ATTR (7, subTest, method=true) 17 LOAD_GLOBAL (4, type) 18 PUSH_NULL - 19 LOAD_FAST (0, stop_exc) + 19 LOAD_FAST_BORROW (0, stop_exc) 20 CALL (1) 21 LOAD_CONST (("type")) 22 CALL_KW (1) @@ -105,8 +106,8 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 8 92 LOAD_GLOBAL (2, self) 93 LOAD_ATTR (15, assertIs, method=true) - 94 LOAD_FAST (1, ex) - 95 LOAD_FAST (0, stop_exc) + 94 LOAD_FAST_BORROW (1, ex) + 95 LOAD_FAST_BORROW (0, stop_exc) 96 CALL (2) 97 POP_TOP 98 JUMP_FORWARD (103) @@ -126,7 +127,7 @@ expression: "compile_exec(\"\\\nasync def test():\n for stop_exc in (StopIter 10 >> 112 LOAD_GLOBAL (2, self) 113 LOAD_ATTR (17, fail, method=true) - 114 LOAD_FAST (0, stop_exc) + 114 LOAD_FAST_BORROW (0, stop_exc) 115 FORMAT_SIMPLE 116 LOAD_CONST (" was suppressed") 117 BUILD_STRING (2) diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index f9c9d17c0ad..4bf15cf5772 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -23,10 +23,10 @@ use crate::{ #[repr(u8)] pub enum Instruction { // No-argument instructions (opcode < HAVE_ARGUMENT=44) - Cache = 0, // Placeholder + Cache = 0, BinarySlice = 1, BuildTemplate = 2, - BinaryOpInplaceAddUnicode = 3, // Placeholder + BinaryOpInplaceAddUnicode = 3, CallFunctionEx = 4, CheckEgMatch = 5, CheckExcMatch = 6, @@ -45,13 +45,13 @@ pub enum Instruction { GetYieldFromIter = 19, InterpreterExit = 20, // Placeholder LoadBuildClass = 21, - LoadLocals = 22, // Placeholder + LoadLocals = 22, MakeFunction = 23, MatchKeys = 24, MatchMapping = 25, MatchSequence = 26, Nop = 27, - NotTaken = 28, // Placeholder + NotTaken = 28, PopExcept = 29, PopIter = 30, PopTop = 31, @@ -177,16 +177,16 @@ pub enum Instruction { LoadDeref(Arg) = 83, LoadFast(Arg) = 84, LoadFastAndClear(Arg) = 85, - LoadFastBorrow(Arg) = 86, // Placeholder + LoadFastBorrow(Arg) = 86, LoadFastBorrowLoadFastBorrow { arg: Arg, - } = 87, // Placeholder + } = 87, LoadFastCheck(Arg) = 88, LoadFastLoadFast { arg: Arg, } = 89, LoadFromDictOrDeref(Arg) = 90, - LoadFromDictOrGlobals(Arg) = 91, // Placeholder + LoadFromDictOrGlobals(Arg) = 91, LoadGlobal(Arg) = 92, LoadName(Arg) = 93, LoadSmallInt { @@ -348,13 +348,13 @@ pub enum Instruction { UnpackSequenceTuple = 210, // Placeholder UnpackSequenceTwoTuple = 211, // Placeholder // CPython 3.14 instrumented opcodes (234-254) - InstrumentedEndFor = 234, // Placeholder - InstrumentedPopIter = 235, // Placeholder - InstrumentedEndSend = 236, // Placeholder - InstrumentedForIter = 237, // Placeholder - InstrumentedInstruction = 238, // Placeholder - InstrumentedJumpForward = 239, // Placeholder - InstrumentedNotTaken = 240, // Placeholder + InstrumentedEndFor = 234, // Placeholder + InstrumentedPopIter = 235, // Placeholder + InstrumentedEndSend = 236, // Placeholder + InstrumentedForIter = 237, // Placeholder + InstrumentedInstruction = 238, // Placeholder + InstrumentedJumpForward = 239, // Placeholder + InstrumentedNotTaken = 240, InstrumentedPopJumpIfTrue = 241, // Placeholder InstrumentedPopJumpIfFalse = 242, // Placeholder InstrumentedPopJumpIfNone = 243, // Placeholder @@ -472,7 +472,7 @@ impl InstructionMetadata for Instruction { Self::DeleteName(_) => 0, Self::DeleteGlobal(_) => 0, Self::DeleteDeref(_) => 0, - Self::LoadFromDictOrDeref(_) => 1, + Self::LoadFromDictOrDeref(_) => 0, // (dict -- value) Self::StoreSubscr => -3, Self::DeleteSubscr => -2, Self::LoadAttr { idx } => { @@ -861,6 +861,29 @@ impl InstructionMetadata for Instruction { Self::LoadDeref(idx) => w!(LOAD_DEREF, cell_name = idx), Self::LoadFast(idx) => w!(LOAD_FAST, varname = idx), Self::LoadFastAndClear(idx) => w!(LOAD_FAST_AND_CLEAR, varname = idx), + Self::LoadFastBorrow(idx) => w!(LOAD_FAST_BORROW, varname = idx), + Self::LoadFastCheck(idx) => w!(LOAD_FAST_CHECK, varname = idx), + Self::LoadFastLoadFast { arg: packed } => { + let oparg = packed.get(arg); + let idx1 = oparg >> 4; + let idx2 = oparg & 15; + let name1 = varname(idx1); + let name2 = varname(idx2); + write!(f, "{:pad$}({}, {})", "LOAD_FAST_LOAD_FAST", name1, name2) + } + Self::LoadFastBorrowLoadFastBorrow { arg: packed } => { + let oparg = packed.get(arg); + let idx1 = oparg >> 4; + let idx2 = oparg & 15; + let name1 = varname(idx1); + let name2 = varname(idx2); + write!( + f, + "{:pad$}({}, {})", + "LOAD_FAST_BORROW_LOAD_FAST_BORROW", name1, name2 + ) + } + Self::LoadFromDictOrGlobals(idx) => w!(LOAD_FROM_DICT_OR_GLOBALS, name = idx), Self::LoadGlobal(idx) => w!(LOAD_GLOBAL, name = idx), Self::LoadName(idx) => w!(LOAD_NAME, name = idx), Self::LoadSpecial { method } => w!(LOAD_SPECIAL, method), @@ -893,6 +916,7 @@ impl InstructionMetadata for Instruction { Self::Reraise { depth } => w!(RERAISE, depth), Self::Resume { arg } => w!(RESUME, arg), Self::ReturnValue => w!(RETURN_VALUE), + Self::ReturnGenerator => w!(RETURN_GENERATOR), Self::Send { target } => w!(SEND, target), Self::SetAdd { i } => w!(SET_ADD, i), Self::SetFunctionAttribute { attr } => w!(SET_FUNCTION_ATTRIBUTE, ?attr), diff --git a/crates/jit/src/instructions.rs b/crates/jit/src/instructions.rs index 0430be1ea06..19931038fe0 100644 --- a/crates/jit/src/instructions.rs +++ b/crates/jit/src/instructions.rs @@ -578,7 +578,7 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { self.stack.push(JitValue::Int(val)); Ok(()) } - Instruction::LoadFast(idx) => { + Instruction::LoadFast(idx) | Instruction::LoadFastBorrow(idx) => { let local = self.variables[idx.get(arg) as usize] .as_ref() .ok_or(JitCompileError::BadBytecode)?; @@ -588,6 +588,22 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> { )); Ok(()) } + Instruction::LoadFastLoadFast { arg: packed } + | Instruction::LoadFastBorrowLoadFastBorrow { arg: packed } => { + let oparg = packed.get(arg); + let idx1 = oparg >> 4; + let idx2 = oparg & 0xF; + for idx in [idx1, idx2] { + let local = self.variables[idx as usize] + .as_ref() + .ok_or(JitCompileError::BadBytecode)?; + self.stack.push(JitValue::from_type_and_value( + local.ty.clone(), + self.builder.use_var(local.var), + )); + } + Ok(()) + } Instruction::LoadGlobal(idx) => { let name = &bytecode.names[idx.get(arg) as usize]; diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 40bf21e51e7..b7df081a642 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -618,6 +618,11 @@ impl ExecutingFrame<'_> { match instruction { Instruction::BinaryOp { op } => self.execute_bin_op(vm, op.get(arg)), + // TODO: In CPython, this does in-place unicode concatenation when + // refcount is 1. Falls back to regular iadd for now. + Instruction::BinaryOpInplaceAddUnicode => { + self.execute_bin_op(vm, bytecode::BinaryOperator::InplaceAdd) + } Instruction::BinarySlice => { // Stack: [container, start, stop] -> [result] let stop = self.pop_value(); @@ -1176,13 +1181,24 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::LoadFromDictOrDeref(i) => { + // Pop dict from stack (locals or classdict depending on context) + let class_dict = self.pop_value(); let i = i.get(arg) as usize; let name = if i < self.code.cellvars.len() { self.code.cellvars[i] } else { self.code.freevars[i - self.code.cellvars.len()] }; - let value = self.locals.mapping().subscript(name, vm).ok(); + // Only treat KeyError as "not found", propagate other exceptions + let value = if let Some(dict_obj) = class_dict.downcast_ref::() { + dict_obj.get_item_opt(name, vm)? + } else { + match class_dict.get_item(name, vm) { + Ok(v) => Some(v), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => None, + Err(e) => return Err(e), + } + }; self.push_value(match value { Some(v) => v, None => self.cells_frees[i] @@ -1322,6 +1338,57 @@ impl ExecutingFrame<'_> { self.push_value(x2); Ok(None) } + // TODO: Implement true borrow optimization (skip Arc::clone). + // CPython's LOAD_FAST_BORROW uses PyStackRef_Borrow to avoid refcount + // increment for values that are consumed within the same basic block. + // Possible approaches: + // - Store raw pointers with careful lifetime management + // - Add a "borrowed" variant to stack slots + // - Use arena allocation for short-lived stack values + // Currently this just clones like LoadFast. + Instruction::LoadFastBorrow(idx) => { + let idx = idx.get(arg) as usize; + let x = self.fastlocals.lock()[idx].clone().ok_or_else(|| { + vm.new_exception_msg( + vm.ctx.exceptions.unbound_local_error.to_owned(), + format!( + "local variable '{}' referenced before assignment", + self.code.varnames[idx] + ), + ) + })?; + self.push_value(x); + Ok(None) + } + // TODO: Same as LoadFastBorrow - implement true borrow optimization. + Instruction::LoadFastBorrowLoadFastBorrow { arg: packed } => { + let oparg = packed.get(arg); + let idx1 = (oparg >> 4) as usize; + let idx2 = (oparg & 15) as usize; + let fastlocals = self.fastlocals.lock(); + let x1 = fastlocals[idx1].clone().ok_or_else(|| { + vm.new_exception_msg( + vm.ctx.exceptions.unbound_local_error.to_owned(), + format!( + "local variable '{}' referenced before assignment", + self.code.varnames[idx1] + ), + ) + })?; + let x2 = fastlocals[idx2].clone().ok_or_else(|| { + vm.new_exception_msg( + vm.ctx.exceptions.unbound_local_error.to_owned(), + format!( + "local variable '{}' referenced before assignment", + self.code.varnames[idx2] + ), + ) + })?; + drop(fastlocals); + self.push_value(x1); + self.push_value(x2); + Ok(None) + } Instruction::LoadGlobal(idx) => { let name = &self.code.names[idx.get(arg) as usize]; let x = self.load_global_or_builtin(name, vm)?; @@ -1600,6 +1667,12 @@ impl ExecutingFrame<'_> { Ok(None) } Instruction::Nop => Ok(None), + // NOT_TAKEN is a branch prediction hint - functionally a NOP + Instruction::NotTaken => Ok(None), + // Instrumented version of NOT_TAKEN - NOP without monitoring + Instruction::InstrumentedNotTaken => Ok(None), + // CACHE is used by adaptive interpreter for inline caching - NOP for us + Instruction::Cache => Ok(None), Instruction::ReturnGenerator => { // In RustPython, generators/coroutines are created in function.rs // before the frame starts executing. The RETURN_GENERATOR instruction diff --git a/extra_tests/snippets/stdlib_io.py b/extra_tests/snippets/stdlib_io.py index 722886d34ee..7c473908295 100644 --- a/extra_tests/snippets/stdlib_io.py +++ b/extra_tests/snippets/stdlib_io.py @@ -10,15 +10,15 @@ result = bb.read() -assert len(result) <= 8 * 1024 +assert len(result) <= 16 * 1024 assert len(result) >= 0 assert isinstance(result, bytes) with FileIO("README.md") as fio: res = fio.read() - assert len(result) <= 8 * 1024 - assert len(result) >= 0 - assert isinstance(result, bytes) + assert len(res) <= 16 * 1024 + assert len(res) >= 0 + assert isinstance(res, bytes) fd = os.open("README.md", os.O_RDONLY)