mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
More Load instructions including LoadLocals, LoadFastBorrow (#6886)
* fix stdlib_io * more scpell dict * LoadLocals * LoadFastBorrow * more ops
This commit is contained in:
1 parent
e363b14e81
commit
95abec4c21
12 files changed
+430
-30
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -28,6 +28,7 @@ hexf
|
||||
hexversion
|
||||
idents
|
||||
illumos
|
||||
ilog
|
||||
indexmap
|
||||
insta
|
||||
keccak
|
||||
@@ -86,4 +87,7 @@ wasmer
|
||||
wasmtime
|
||||
widestring
|
||||
winapi
|
||||
winresource
|
||||
winsock
|
||||
bitvec
|
||||
Bitvec
|
||||
@@ -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
|
||||
|
||||
Vendored
-3
@@ -1002,7 +1002,6 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
|
||||
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 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
|
||||
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 @@ class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<usize> = 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<u32> {
|
||||
let mut maxdepth = 0u32;
|
||||
let mut stack = Vec::with_capacity(self.blocks.len());
|
||||
|
||||
Generated
+6
-5
@@ -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 (<code object test at ??? file "source_path", line 1>): 1 0 RUSTPYTHON_PLACEHOLDER
|
||||
3 1 LOAD_CONST (<code object test at ??? file "source_path", line 1>): 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)
|
||||
|
||||
@@ -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<NameIdx>) = 83,
|
||||
LoadFast(Arg<NameIdx>) = 84,
|
||||
LoadFastAndClear(Arg<NameIdx>) = 85,
|
||||
LoadFastBorrow(Arg<NameIdx>) = 86, // Placeholder
|
||||
LoadFastBorrow(Arg<NameIdx>) = 86,
|
||||
LoadFastBorrowLoadFastBorrow {
|
||||
arg: Arg<u32>,
|
||||
} = 87, // Placeholder
|
||||
} = 87,
|
||||
LoadFastCheck(Arg<NameIdx>) = 88,
|
||||
LoadFastLoadFast {
|
||||
arg: Arg<u32>,
|
||||
} = 89,
|
||||
LoadFromDictOrDeref(Arg<NameIdx>) = 90,
|
||||
LoadFromDictOrGlobals(Arg<NameIdx>) = 91, // Placeholder
|
||||
LoadFromDictOrGlobals(Arg<NameIdx>) = 91,
|
||||
LoadGlobal(Arg<NameIdx>) = 92,
|
||||
LoadName(Arg<NameIdx>) = 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),
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
+74
-1
@@ -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::<PyDict>() {
|
||||
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
|
||||
|
||||
@@ -10,15 +10,15 @@ assert bb.seekable()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
Reference in new issue
Block a user