mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Align codegen passes and opcode metadata with CPython (#7987)
* Share marshal ref table between code object and its internals
read_marshal_bytes, _str, _str_vec, _name_tuple, and _const_tuple now
take a shared ref table and resolve TYPE_REF / register FLAG_REF
entries. deserialize_code is split into a public wrapper and an inner
function that receives the ref table; deserialize_value_depth opens a
fresh inner ref space when it hits Type::Code, mirroring CPython's
behaviour of putting the code object itself at ref slot 0. Nested code
objects inside const tuples reuse the surrounding code's ref space via
the new read_const_value helper.
* Align PYC magic number, FORMAT_VERSION, and header check with CPython 3.14
PYC_MAGIC_NUMBER changes from 2994 to 3627, matching CPython 3.14's
pyc_magic_number_token (0x0a0d0e2b). marshal FORMAT_VERSION drops from
5 to 4 (the encoder/marshal.version value; the decoder already accepts
both). check_pyc_magic_number_bytes now compares all four magic bytes
instead of the first two.
* Accept CPython-tagged .pyc as read-only bytecode source
SourceFileLoader.get_code now also looks for .pyc files using
_RP_FALLBACK_CACHE_TAGS (currently ('cpython-314',)) in addition to
sys.implementation.cache_tag. The matched .pyc is only used for
reading; recompilation still writes to the RustPython-tagged path, so
CPython's .pyc is never overwritten. Source-stat / hash / timestamp
validation logic is unchanged.
* Apply rustfmt to marshal helpers
* Marshal PySlice from format version 4 instead of 5
CPython's marshal supports TYPE_SLICE from format version 4 onwards
and that is the default version. Rejecting slice dumps below version
5 made marshal.dumps(slice(...)) fail with the default version and
broke test.test_marshal.SliceTestCase.test_slice.
* Revert "Accept CPython-tagged .pyc as read-only bytecode source"
Lib/importlib/_bootstrap_external.py is CPython's own code copied
verbatim; local patches here defeat compatibility tracking. The
cpython-XX cache_tag fallback needs to live on the RustPython side
(Rust code or sys.implementation.cache_tag policy), not as edits to
the imported standard library.
This reverts commit 1fc426d0fb5fcdb50d35cad13bbb43e8f6ce1c7f.
* Set marshal FORMAT_VERSION to 5 to match CPython 3.14.5
Py_MARSHAL_VERSION is 5 in CPython 3.14.5 (Include/marshal.h:16) and
TYPE_SLICE serialization rejects version < 5 (Python/marshal.c:720).
Restore the same threshold and constant so marshal.version and the
slice-marshal gate match CPython.
* Thread marshal recursion depth through nested code objects
Code objects embedded in const-tuples reset the depth budget on each
recursion, so a hostile or pathological marshal stream of code-in-tuple-
in-code can blow the stack despite MAX_MARSHAL_STACK_DEPTH. Pass the
current depth through deserialize_code_inner and read_marshal_const_tuple
and decrement at each code-object/tuple boundary.
Also route dict keys through deserialize_value_after_header so TYPE_CODE
keys decode instead of failing with BadType.
* align compiler to CPython
* Align codegen with CPython compile.c
Rename CFG helpers and accessors to the names used in CPython's
compile.c (basicblock_next_instr, basicblock_last_instr,
basicblock_append_instructions, bb_has_fallthrough, is_jump,
make_cfg_traversal_stack, mark_warm/mark_cold, etc.). Drop the unused
boolop-folding gate, mark_cpython_cfg_label_block helper, and
ComprehensionLoopControl::iter_range field.
Track an is_coroutine flag on SymbolTable, set in async def, await, and
async comprehensions, and propagate it through non-generator
comprehensions per symtable_handle_comprehension().
Mark SetupCleanup/SetupFinally/SetupWith as has_arg pseudo-ops, mark
ForIter as a terminator, and add has_arg/has_const on AnyInstruction.
Fix Instruction::stack_effect_jump to delegate to the opcode's
stack_effect_jump rather than stack_effect.
* Align codegen IR with CPython CFG structures
* Match CPython CFG annotation offset arithmetic
* Propagate CPython CFG label translation errors
* Align CPython exception target labeling flow
* Propagate CPython CFG traversal stack allocation errors
* Match CPython optimize_load_fast allocation flow
* Propagate CPython basicblock allocation errors
* Propagate CPython redundant NOP cleanup errors
* Propagate CPython unused const cleanup errors
* Propagate CPython const folding errors
* Propagate CPython swaptimize allocation errors
* Match CPython list-to-tuple fold allocation
* Skip const folding on CPython allocation failures
* Skip subscript folding on CPython allocation failures
* Propagate CPython assembler allocation errors
* Propagate CPython localsplus allocation errors
* Propagate CPython localsplus setup allocation errors
* Propagate CPython jump label map allocation errors
* Propagate CPython instruction sequence allocation errors
* Propagate CPython instruction label allocation errors
* Guard CPython codegen block allocation
* Guard CPython label shadow allocation
* Propagate CPython label shadow allocation errors
* Align CPython c-array allocation updates
* Align CPython ref stack growth
* Match CPython CFG builder debug check
* Avoid Rust-only CFG append clone
* Reuse CPython cleared block slots
* Use CPython block append in copy_basicblock
* Match CPython cfg builder creation order
* Clear label map after CPython apply pass
* Match CPython cfg builder allocation check
* Propagate CPython c-array size errors
* Drop Rust-only label uniqueness check
* Drop Rust-only label shadow debug checks
* Propagate CFG block index overflow
* Propagate CFG label oparg overflow
* Model CPython basicblock instruction storage
* Drop Rust-only recorded CFG precheck
* Model CPython instruction sequence storage
* Propagate instruction sequence offset overflow
* Model CPython instruction sequence labels
* Match CPython jump offset arithmetic
* Match CPython exception table arithmetic
* Match CPython label index arithmetic
* Match CPython instruction offset casts
* Match CPython jump offset indexing
* Match CPython oparg locals casts
* Match CPython localsplus offset arithmetic
* Match CPython cell prefix indexing
* Match CPython C array growth arithmetic
* Match CPython label map allocation arithmetic
* Match CPython label map size tracking
* Use CPython label map size in sequence passes
* Assert CPython label map clearing invariants
* Match CPython label oparg assignment
* Match CPython compiler direct arithmetic
* Match CPython load fast local casts
* Match CPython load fast depth assert
* Match CPython resume depth flagging
* Match CPython stack depth arithmetic
* Drop Rust-only stack overflow error
* Return CPython stack depth directly
* Match CPython C array growth errors
* Match CPython instruction insert asserts
* Match CPython unreachable pseudo jump
* Match CPython CFG size guard
* Match CPython superinstruction assert
* Match CPython redundant jump assert
* Match CPython stackdepth errors
* Match CPython jump offset flow
* Match CPython assembler buffer defaults
* Match CPython bytecode emit growth
* Match CPython assembler entry growth
* Match CPython assembler growth overflow check
* Match CPython remove_unreachable structure
* Match CPython static swap flow
* Inline CPython code unit preprocessing
* Match CPython C array growth checks
* Match CPython label map size guard
* Match CPython load-fast flow
* Simplify CPython CFG condition flow
* Align exception fallthrough propagation
* Match CPython pseudo target table
* Match CPython annotations CFG assert
* Match CPython inverted op assert
* Match CPython many-locals guard
* Reject deopt opcodes in CFG stack effects
* Match CPython invalid stack effect error
* Test CPython deopt stack effect guard
* Match CPython load-fast extended-arg assert
* Match CPython instruction allocation asserts
* Match CPython basicblock last-instr asserts
* Match CPython opcode range asserts
* Assert CPython fallthrough line propagation invariant
* Assert CPython CFG target offset sign
* Assert CPython exception fallthrough invariant
* Assert CPython exception stack bounds
* Assert CPython traversal stack allocation
* Match CPython label-map allocation in shadow
* Mirror CPython label-map sentinel fill
* Match CPython CFG builder allocation asserts
* Match CPython exception stack structure
* Match CPython ref stack structure
* Match CPython CFG traversal stack structure
* Mirror CPython CFG traversal stack pointer
* Use CPython fixed exception handler stack
* Mirror CPython ref stack capacity field
* Match CPython swap optimizer scratch stack
* Align static swap helpers with CPython blocks
* Align swaptimize signature with CPython
* Match CPython redundant pair pass result
* Match CPython inline pass result
* Match CPython redundant NOP pass results
* Fix bytecode metadata after upstream rebase
* Match CPython opcode stack metadata
* Add CPython identifiers to cspell dictionary
Add CNOTAB, LNOTAB, ialloc, ioffset, iused, nblocks, ncellsused,
ncellvars, nextop, noffsets, nvars, swaptimize, untargeted to
.cspell.dict/cpython.txt for the new CFG/assembler code in
crates/codegen/src/ir.rs.
* Fix CI failures from bytecode-parity work
- clippy: drop redundant `test_` prefix on three test functions and
remove an unnecessary `u32` cast in basicblock_clear_reuses_cpython_spare_slots_in_offset_order
- insta: regenerate nested_double_async_with snapshot to match the new
CFG output that drops unreferenced labels after the redundant-NOP pass
- regrtest: drop `@expectedFailure` markers from test_func_args,
test_meth_args (test_compile), test_disassemble_with,
test_disassemble_try_finally (test_dis), and test_except_star
(test_monitoring) which now pass
* Resync generated opcode metadata
Empty conf.toml since WithExceptStart and Setup{Cleanup,Finally,With}
stack effects already match CPython, so the TODO override entries are
stale and only cause CI hook diffs.
Regenerate opcode_metadata.rs and drop the matching SetupCleanup/
SetupFinally/SetupWith assertions on PseudoOpcode::has_arg(); their
`HAS_ARG` flag comes from pseudo definitions in bytecodes.c that the
upstream analyzer does not propagate through PseudoInstruction.properties,
so the generated has_arg() excludes them. has_target() still covers
these block-push pseudos via is_block_push().
* Drop is_block_push has_arg invariant
The CPython invariant `assert(OPCODE_HAS_ARG(op) || !IS_BLOCK_PUSH(op))`
relies on SETUP_{FINALLY,CLEANUP,WITH} carrying `HAS_ARG_FLAG` in
CPython's metadata. The autogen tool reads pseudo-opcode properties from
target instructions and does not propagate the pseudo's own
HAS_ARG flag, so PseudoOpcode::has_arg() omits these three opcodes.
Drop the debug_assert that fired inside py_freeze proc-macro expansion.
* Auto-generate has_eval_break and route AnyInstruction has_arg/has_const via macro
Add fn_has_eval_break to generate_rs_opcode_metadata.py using CPython's
Properties.eval_breaker, removing the hand-written matches! body for
Opcode::has_eval_break and PseudoOpcode::has_eval_break.
Forward has_arg/has_const from Instruction and PseudoInstruction to
their opcode, so AnyInstruction can use either_real_pseudo! like the
other has_* accessors instead of an open-coded match.
This commit is contained in:
1 parent
ca412fce5d
commit
1a959cf7f3
16 files changed
+8488
-15768
No files matched your search
@@ -37,6 +37,7 @@ CFWS
|
||||
CLASSDEREF
|
||||
classdict
|
||||
cmpop
|
||||
CNOTAB
|
||||
codedepth
|
||||
CODEUNIT
|
||||
CONIN
|
||||
@@ -98,12 +99,14 @@ HASUNION
|
||||
heaptype
|
||||
hexdigit
|
||||
HIGHRES
|
||||
ialloc
|
||||
IFUNC
|
||||
IMMUTABLETYPE
|
||||
INCREF
|
||||
inlinedepth
|
||||
inplace
|
||||
inpos
|
||||
ioffset
|
||||
isbytecode
|
||||
ishidden
|
||||
ismine
|
||||
@@ -111,6 +114,7 @@ ISPOINTER
|
||||
isoctal
|
||||
iteminfo
|
||||
Itertool
|
||||
iused
|
||||
keeped
|
||||
kwnames
|
||||
kwonlyarg
|
||||
@@ -122,6 +126,7 @@ linearise
|
||||
lineful
|
||||
lineiterator
|
||||
linetable
|
||||
LNOTAB
|
||||
loadfast
|
||||
localsplus
|
||||
localspluskinds
|
||||
@@ -137,17 +142,22 @@ multibytecodec
|
||||
nameobj
|
||||
nameop
|
||||
nargsf
|
||||
nblocks
|
||||
ncells
|
||||
ncellsused
|
||||
ncellvars
|
||||
nconsts
|
||||
newargs
|
||||
newfree
|
||||
NEWLOCALS
|
||||
newsemlockobject
|
||||
nextop
|
||||
nfrees
|
||||
nkwargs
|
||||
nkwelts
|
||||
nlocalsplus
|
||||
nointerrupt
|
||||
noffsets
|
||||
Nondescriptor
|
||||
noninteger
|
||||
nops
|
||||
@@ -155,6 +165,7 @@ noraise
|
||||
nseen
|
||||
NSIGNALS
|
||||
numer
|
||||
nvars
|
||||
opname
|
||||
opnames
|
||||
orelse
|
||||
@@ -220,6 +231,7 @@ subparams
|
||||
subscr
|
||||
sval
|
||||
swappedbytes
|
||||
swaptimize
|
||||
sysdict
|
||||
tbstderr
|
||||
templatelib
|
||||
@@ -240,6 +252,7 @@ uncollectable
|
||||
Unhandle
|
||||
unparse
|
||||
unparser
|
||||
untargeted
|
||||
untracking
|
||||
VARKEYWORDS
|
||||
varkwarg
|
||||
|
||||
Vendored
-2
@@ -2580,7 +2580,6 @@ class TestExpressionStackSize(unittest.TestCase):
|
||||
def test_dict(self):
|
||||
self.check_stack_size("{" + "x:x, " * self.N + "x:x}")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 102 not less than or equal to 6
|
||||
def test_func_args(self):
|
||||
self.check_stack_size("f(" + "x, " * self.N + ")")
|
||||
|
||||
@@ -2588,7 +2587,6 @@ class TestExpressionStackSize(unittest.TestCase):
|
||||
kwargs = (f'a{i}=x' for i in range(self.N))
|
||||
self.check_stack_size("f(" + ", ".join(kwargs) + ")")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 102 not less than or equal to 6
|
||||
def test_meth_args(self):
|
||||
self.check_stack_size("o.m(" + "x, " * self.N + ")")
|
||||
|
||||
|
||||
Vendored
-2
@@ -1211,14 +1211,12 @@ class DisTests(DisTestBase):
|
||||
def test_disassemble_fstring(self):
|
||||
self.do_disassembly_test(_fstring, dis_fstring)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_disassemble_with(self):
|
||||
self.do_disassembly_test(_with, dis_with)
|
||||
|
||||
def test_disassemble_asyncwith(self):
|
||||
self.do_disassembly_test(_asyncwith, dis_asyncwith)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_disassemble_try_finally(self):
|
||||
self.do_disassembly_test(_tryfinally, dis_tryfinally)
|
||||
self.do_disassembly_test(_tryfinallyconst, dis_tryfinallyconst)
|
||||
|
||||
Vendored
-1
@@ -1624,7 +1624,6 @@ class TestBranchAndJumpEvents(CheckEvents):
|
||||
('branch left', 'func', 44, 50),
|
||||
('branch right', 'func', 28, 70)])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; - bytecode layout differs from CPython
|
||||
def test_except_star(self):
|
||||
|
||||
class Foo:
|
||||
|
||||
+1277
-4001
File diff suppressed because it is too large.
Load diff
@@ -38,16 +38,20 @@ impl fmt::Display for CodegenError {
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum InternalError {
|
||||
StackOverflow,
|
||||
StackUnderflow,
|
||||
InconsistentStackDepth,
|
||||
InvalidStackEffect,
|
||||
MalformedControlFlowGraph,
|
||||
MissingSymbol(String),
|
||||
}
|
||||
|
||||
impl Display for InternalError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::StackOverflow => write!(f, "stack overflow"),
|
||||
Self::StackUnderflow => write!(f, "stack underflow"),
|
||||
Self::StackUnderflow => write!(f, "Invalid CFG, stack underflow"),
|
||||
Self::InconsistentStackDepth => write!(f, "Invalid CFG, inconsistent stackdepth"),
|
||||
Self::InvalidStackEffect => write!(f, "Invalid stack effect"),
|
||||
Self::MalformedControlFlowGraph => write!(f, "malformed control flow graph."),
|
||||
Self::MissingSymbol(s) => write!(
|
||||
f,
|
||||
"The symbol '{s}' must be present in the symbol table, even when it is undefined in python."
|
||||
|
||||
+6768
-11704
File diff suppressed because it is too large.
Load diff
@@ -60,6 +60,9 @@ pub struct SymbolTable {
|
||||
/// Whether this scope contains yield/yield from (is a generator function)
|
||||
pub is_generator: bool,
|
||||
|
||||
/// Whether this scope contains await or async comprehension machinery.
|
||||
pub is_coroutine: bool,
|
||||
|
||||
/// Whether this comprehension scope should be inlined (PEP 709)
|
||||
/// True for list/set/dict comprehensions in non-generator expressions
|
||||
pub comp_inlined: bool,
|
||||
@@ -102,6 +105,7 @@ impl SymbolTable {
|
||||
needs_classdict: false,
|
||||
can_see_class_scope: false,
|
||||
is_generator: false,
|
||||
is_coroutine: false,
|
||||
comp_inlined: false,
|
||||
annotation_block: None,
|
||||
skip_enclosing_function_scope: false,
|
||||
@@ -1532,6 +1536,9 @@ impl SymbolTableBuilder {
|
||||
},
|
||||
has_type_params, // skip_defaults: already scanned above
|
||||
)?;
|
||||
if *is_async {
|
||||
self.tables.last_mut().unwrap().is_coroutine = true;
|
||||
}
|
||||
self.scan_statements(body)?;
|
||||
self.leave_scope();
|
||||
if type_params.is_some() {
|
||||
@@ -2026,6 +2033,7 @@ impl SymbolTableBuilder {
|
||||
range: _,
|
||||
}) => {
|
||||
self.scan_expression(value, context)?;
|
||||
self.tables.last_mut().unwrap().is_coroutine = true;
|
||||
}
|
||||
Expr::Yield(ExprYield {
|
||||
value,
|
||||
@@ -2353,6 +2361,9 @@ impl SymbolTableBuilder {
|
||||
);
|
||||
// Generator expressions need the is_generator flag
|
||||
self.tables.last_mut().unwrap().is_generator = is_generator;
|
||||
if generators.iter().any(|generator| generator.is_async) {
|
||||
self.tables.last_mut().unwrap().is_coroutine = true;
|
||||
}
|
||||
|
||||
// PEP 709: Mark non-generator comprehensions for inlining.
|
||||
// CPython's symtable marks all non-generator comprehensions for
|
||||
@@ -2389,7 +2400,14 @@ impl SymbolTableBuilder {
|
||||
}
|
||||
self.scan_expression(elt1, ExpressionContext::Load)?;
|
||||
|
||||
// CPython symtable_handle_comprehension(): non-generator async
|
||||
// comprehensions propagate ste_coroutine to the enclosing scope after
|
||||
// the comprehension block is exited.
|
||||
let propagate_coroutine = self.tables.last().unwrap().is_coroutine && !is_generator;
|
||||
self.leave_scope();
|
||||
if propagate_coroutine {
|
||||
self.tables.last_mut().unwrap().is_coroutine = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -415,6 +415,10 @@ impl<T> IndexMut<oparg::VarNum> for [T] {
|
||||
}
|
||||
|
||||
/// Per-slot kind flags for localsplus (co_localspluskinds).
|
||||
pub const CO_FAST_ARG_POS: u8 = 0x02;
|
||||
pub const CO_FAST_ARG_KW: u8 = 0x04;
|
||||
pub const CO_FAST_ARG_VAR: u8 = 0x08;
|
||||
pub const CO_FAST_ARG: u8 = CO_FAST_ARG_POS | CO_FAST_ARG_KW | CO_FAST_ARG_VAR;
|
||||
pub const CO_FAST_HIDDEN: u8 = 0x10;
|
||||
pub const CO_FAST_LOCAL: u8 = 0x20;
|
||||
pub const CO_FAST_CELL: u8 = 0x40;
|
||||
@@ -443,7 +447,8 @@ pub struct CodeObject<C: Constant = ConstantData> {
|
||||
pub varnames: Box<[C::Name]>,
|
||||
pub cellvars: Box<[C::Name]>,
|
||||
pub freevars: Box<[C::Name]>,
|
||||
/// Per-slot kind flags: CO_FAST_LOCAL, CO_FAST_CELL, CO_FAST_FREE, CO_FAST_HIDDEN.
|
||||
/// Per-slot kind flags: CO_FAST_ARG_*, CO_FAST_LOCAL, CO_FAST_CELL,
|
||||
/// CO_FAST_FREE, CO_FAST_HIDDEN.
|
||||
/// Length = nlocalsplus (nlocals + ncells + nfrees).
|
||||
pub localspluskinds: Box<[u8]>,
|
||||
/// Line number table (CPython 3.11+ format)
|
||||
|
||||
@@ -63,11 +63,6 @@ macro_rules! define_opcodes {
|
||||
$(Self::$op_name => $op_id,)*
|
||||
}
|
||||
}
|
||||
/// Stack effect of [`Self::stack_effect_info`].
|
||||
#[must_use]
|
||||
$opcode_vis fn stack_effect(&self, oparg: u32) -> i32 {
|
||||
self.stack_effect_info(oparg).effect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$opcode_name> for $instr_name {
|
||||
@@ -156,6 +151,46 @@ macro_rules! define_opcodes {
|
||||
self.as_opcode().is_scope_exit()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn is_terminator(&self) -> bool {
|
||||
self.as_opcode().is_terminator()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn is_no_fallthrough(&self) -> bool {
|
||||
self.as_opcode().is_no_fallthrough()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn has_target(&self) -> bool {
|
||||
self.as_opcode().has_target()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn has_jump(&self) -> bool {
|
||||
self.as_opcode().has_jump()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn has_arg(&self) -> bool {
|
||||
self.as_opcode().has_arg()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn has_const(&self) -> bool {
|
||||
self.as_opcode().has_const()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn has_eval_break(&self) -> bool {
|
||||
self.as_opcode().has_eval_break()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn is_assembler(&self) -> bool {
|
||||
self.as_opcode().is_assembler()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
$instr_vis const fn cache_entries(&self) -> usize{
|
||||
self.as_opcode().cache_entries()
|
||||
@@ -673,16 +708,49 @@ impl Opcode {
|
||||
)
|
||||
}
|
||||
|
||||
/// CPython's `IS_ASSEMBLER_OPCODE`.
|
||||
#[must_use]
|
||||
pub const fn is_assembler(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::JumpForward | Self::JumpBackward | Self::JumpBackwardNoInterrupt
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_scope_exit(&self) -> bool {
|
||||
matches!(self, Self::ReturnValue | Self::RaiseVarargs | Self::Reraise)
|
||||
}
|
||||
|
||||
/// CPython's `IS_TERMINATOR_OPCODE`.
|
||||
#[must_use]
|
||||
pub const fn is_terminator(&self) -> bool {
|
||||
self.has_jump() || self.is_scope_exit()
|
||||
}
|
||||
|
||||
/// CPython's `IS_SCOPE_EXIT_OPCODE || IS_UNCONDITIONAL_JUMP_OPCODE`.
|
||||
#[must_use]
|
||||
pub const fn is_no_fallthrough(&self) -> bool {
|
||||
self.is_scope_exit() || self.is_unconditional_jump()
|
||||
}
|
||||
|
||||
/// CPython's `HAS_TARGET`.
|
||||
#[must_use]
|
||||
pub const fn has_target(&self) -> bool {
|
||||
self.has_jump() || self.is_block_push()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_block_push(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Stack effect of [`Self::stack_effect_info`].
|
||||
#[must_use]
|
||||
pub fn stack_effect(&self, oparg: u32) -> i32 {
|
||||
self.stack_effect_info(oparg).effect()
|
||||
}
|
||||
|
||||
/// Stack effect when the instruction takes its branch (jump=true).
|
||||
///
|
||||
/// CPython equivalent: `stack_effect(opcode, oparg, jump=True)`.
|
||||
@@ -714,6 +782,39 @@ impl PseudoOpcode {
|
||||
matches!(self, Self::Jump | Self::JumpNoInterrupt)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_assembler(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// CPython's `IS_TERMINATOR_OPCODE`.
|
||||
#[must_use]
|
||||
pub const fn is_terminator(&self) -> bool {
|
||||
self.has_jump()
|
||||
}
|
||||
|
||||
/// CPython's `IS_SCOPE_EXIT_OPCODE || IS_UNCONDITIONAL_JUMP_OPCODE`.
|
||||
#[must_use]
|
||||
pub const fn is_no_fallthrough(&self) -> bool {
|
||||
self.is_unconditional_jump()
|
||||
}
|
||||
|
||||
/// CPython's `HAS_TARGET`.
|
||||
#[must_use]
|
||||
pub const fn has_target(&self) -> bool {
|
||||
self.has_jump() || self.is_block_push()
|
||||
}
|
||||
|
||||
/// flowgraph.c get_stack_effects block-push non-jump case.
|
||||
#[must_use]
|
||||
pub fn stack_effect(&self, oparg: u32) -> i32 {
|
||||
if self.is_block_push() {
|
||||
0
|
||||
} else {
|
||||
self.stack_effect_info(oparg).effect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler entry effect for SETUP_* pseudo ops.
|
||||
///
|
||||
/// Fallthrough effect is 0 (NOPs), but when the branch is taken the
|
||||
@@ -778,6 +879,46 @@ impl AnyInstruction {
|
||||
pub const fn is_scope_exit(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn is_terminator(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn is_no_fallthrough(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn has_target(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn has_jump(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn has_arg(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn has_const(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn has_eval_break(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub const fn is_assembler(&self) -> bool
|
||||
);
|
||||
|
||||
either_real_pseudo!(
|
||||
#[must_use]
|
||||
pub fn stack_effect(&self, oparg: u32) -> i32
|
||||
@@ -1179,3 +1320,115 @@ impl<T: OpArgType> fmt::Debug for Arg<T> {
|
||||
// breaks the VM:/
|
||||
const _: () = assert!(core::mem::size_of::<Instruction>() == 1);
|
||||
const _: () = assert!(core::mem::size_of::<PseudoInstruction>() == 2);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn eval_break_flags_match_cpython_jump_metadata() {
|
||||
assert!(Opcode::JumpBackward.has_eval_break());
|
||||
assert!(!Opcode::JumpBackwardNoInterrupt.has_eval_break());
|
||||
assert!(!Opcode::JumpForward.has_eval_break());
|
||||
|
||||
assert!(PseudoOpcode::Jump.has_eval_break());
|
||||
assert!(!PseudoOpcode::JumpIfFalse.has_eval_break());
|
||||
assert!(!PseudoOpcode::JumpIfTrue.has_eval_break());
|
||||
assert!(!PseudoOpcode::JumpNoInterrupt.has_eval_break());
|
||||
|
||||
assert!(AnyInstruction::from(PseudoOpcode::Jump).has_eval_break());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminator_flags_match_cpython_opcode_utils() {
|
||||
assert!(Opcode::JumpForward.is_terminator());
|
||||
assert!(Opcode::PopJumpIfFalse.is_terminator());
|
||||
assert!(Opcode::ForIter.is_terminator());
|
||||
assert!(Opcode::ReturnValue.is_terminator());
|
||||
assert!(!Opcode::Nop.is_terminator());
|
||||
|
||||
assert!(PseudoOpcode::JumpIfTrue.is_terminator());
|
||||
assert!(PseudoOpcode::JumpNoInterrupt.is_terminator());
|
||||
assert!(!PseudoOpcode::SetupFinally.is_terminator());
|
||||
assert!(!PseudoOpcode::SetupWith.is_terminator());
|
||||
assert!(!PseudoOpcode::SetupCleanup.is_terminator());
|
||||
assert!(!PseudoOpcode::PopBlock.is_terminator());
|
||||
|
||||
assert!(AnyInstruction::from(PseudoOpcode::JumpIfFalse).is_terminator());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assembler_flags_match_cpython_opcode_utils() {
|
||||
assert!(Opcode::JumpForward.is_assembler());
|
||||
assert!(Opcode::JumpBackward.is_assembler());
|
||||
assert!(Opcode::JumpBackwardNoInterrupt.is_assembler());
|
||||
assert!(!Opcode::PopJumpIfFalse.is_assembler());
|
||||
assert!(!Opcode::Nop.is_assembler());
|
||||
|
||||
assert!(!PseudoOpcode::Jump.is_assembler());
|
||||
assert!(!PseudoOpcode::JumpNoInterrupt.is_assembler());
|
||||
assert!(!AnyInstruction::from(PseudoOpcode::Jump).is_assembler());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_flags_match_cpython_opcode_utils() {
|
||||
assert!(Opcode::JumpForward.has_target());
|
||||
assert!(Opcode::ForIter.has_target());
|
||||
assert!(!Opcode::ReturnValue.has_target());
|
||||
assert!(!Opcode::Nop.has_target());
|
||||
|
||||
assert!(PseudoOpcode::Jump.has_target());
|
||||
assert!(PseudoOpcode::SetupFinally.has_target());
|
||||
assert!(PseudoOpcode::SetupWith.has_target());
|
||||
assert!(PseudoOpcode::SetupCleanup.has_target());
|
||||
assert!(!PseudoOpcode::PopBlock.has_target());
|
||||
|
||||
assert!(AnyInstruction::from(PseudoOpcode::SetupFinally).has_target());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_flags_match_cpython_opcode_metadata() {
|
||||
assert!(Opcode::LoadConst.has_arg());
|
||||
assert!(Opcode::YieldValue.has_arg());
|
||||
assert!(!Opcode::Nop.has_arg());
|
||||
assert!(!Opcode::ReturnValue.has_arg());
|
||||
|
||||
assert!(PseudoOpcode::Jump.has_arg());
|
||||
assert!(PseudoOpcode::JumpIfFalse.has_arg());
|
||||
assert!(PseudoOpcode::JumpIfTrue.has_arg());
|
||||
assert!(PseudoOpcode::JumpNoInterrupt.has_arg());
|
||||
assert!(PseudoOpcode::LoadClosure.has_arg());
|
||||
assert!(PseudoOpcode::StoreFastMaybeNull.has_arg());
|
||||
assert!(!PseudoOpcode::AnnotationsPlaceholder.has_arg());
|
||||
assert!(!PseudoOpcode::PopBlock.has_arg());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_flags_match_cpython_opcode_metadata() {
|
||||
assert!(Opcode::LoadConst.has_const());
|
||||
assert!(Opcode::LoadConstImmortal.has_const());
|
||||
assert!(Opcode::LoadConstMortal.has_const());
|
||||
assert!(!Opcode::LoadSmallInt.has_const());
|
||||
assert!(!Opcode::Nop.has_const());
|
||||
|
||||
assert!(!PseudoOpcode::LoadClosure.has_const());
|
||||
assert!(!AnyInstruction::from(PseudoOpcode::Jump).has_const());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_fallthrough_flags_match_cpython_basicblock_nofallthrough() {
|
||||
assert!(Opcode::JumpForward.is_no_fallthrough());
|
||||
assert!(Opcode::ReturnValue.is_no_fallthrough());
|
||||
assert!(!Opcode::PopJumpIfFalse.is_no_fallthrough());
|
||||
assert!(!Opcode::ForIter.is_no_fallthrough());
|
||||
assert!(!Opcode::Nop.is_no_fallthrough());
|
||||
|
||||
assert!(PseudoOpcode::Jump.is_no_fallthrough());
|
||||
assert!(PseudoOpcode::JumpNoInterrupt.is_no_fallthrough());
|
||||
assert!(!PseudoOpcode::JumpIfFalse.is_no_fallthrough());
|
||||
assert!(!PseudoOpcode::SetupFinally.is_no_fallthrough());
|
||||
assert!(!PseudoOpcode::SetupWith.is_no_fallthrough());
|
||||
|
||||
assert!(AnyInstruction::from(PseudoOpcode::Jump).is_no_fallthrough());
|
||||
}
|
||||
}
|
||||
+40
-16
@@ -282,6 +282,36 @@ impl super::Opcode {
|
||||
)
|
||||
}
|
||||
|
||||
/// Does this opcode have 'HAS_EVAL_BREAK_FLAG' set.
|
||||
#[must_use]
|
||||
pub const fn has_eval_break(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::CallFunctionEx
|
||||
| Self::Call
|
||||
| Self::JumpBackward
|
||||
| Self::Resume
|
||||
| Self::CallBuiltinClass
|
||||
| Self::CallBuiltinFast
|
||||
| Self::CallBuiltinFastWithKeywords
|
||||
| Self::CallBuiltinO
|
||||
| Self::CallKwNonPy
|
||||
| Self::CallMethodDescriptorFast
|
||||
| Self::CallMethodDescriptorFastWithKeywords
|
||||
| Self::CallMethodDescriptorNoargs
|
||||
| Self::CallMethodDescriptorO
|
||||
| Self::CallNonPyGeneral
|
||||
| Self::CallStr1
|
||||
| Self::CallTuple1
|
||||
| Self::JumpBackwardJit
|
||||
| Self::JumpBackwardNoJit
|
||||
| Self::InstrumentedResume
|
||||
| Self::InstrumentedCall
|
||||
| Self::InstrumentedCallFunctionEx
|
||||
| Self::InstrumentedJumpBackward
|
||||
)
|
||||
}
|
||||
|
||||
/// Does this opcode have 'HAS_FREE_FLAG' set.
|
||||
#[must_use]
|
||||
pub const fn has_free(self) -> bool {
|
||||
@@ -441,10 +471,7 @@ impl super::Opcode {
|
||||
Self::UnaryInvert => (1, 1),
|
||||
Self::UnaryNegative => (1, 1),
|
||||
Self::UnaryNot => (1, 1),
|
||||
Self::WithExceptStart => (
|
||||
7, // TODO: Differs from CPython `6`
|
||||
6, // TODO: Differs from CPython `5`
|
||||
),
|
||||
Self::WithExceptStart => (6, 5),
|
||||
Self::BinaryOp => (1, 2),
|
||||
Self::BuildInterpolation => (1, 2 + (oparg & 1)),
|
||||
Self::BuildList => (1, oparg),
|
||||
@@ -730,6 +757,12 @@ impl super::PseudoOpcode {
|
||||
false
|
||||
}
|
||||
|
||||
/// Does this opcode have 'HAS_EVAL_BREAK_FLAG' set.
|
||||
#[must_use]
|
||||
pub const fn has_eval_break(self) -> bool {
|
||||
matches!(self, Self::Jump)
|
||||
}
|
||||
|
||||
/// Does this opcode have 'HAS_FREE_FLAG' set.
|
||||
#[must_use]
|
||||
pub const fn has_free(self) -> bool {
|
||||
@@ -772,18 +805,9 @@ impl super::PseudoOpcode {
|
||||
Self::JumpNoInterrupt => (0, 0),
|
||||
Self::LoadClosure => (1, 0),
|
||||
Self::PopBlock => (0, 0),
|
||||
Self::SetupCleanup => (
|
||||
0, // TODO: Differs from CPython `2`
|
||||
0,
|
||||
),
|
||||
Self::SetupFinally => (
|
||||
0, // TODO: Differs from CPython `1`
|
||||
0,
|
||||
),
|
||||
Self::SetupWith => (
|
||||
0, // TODO: Differs from CPython `1`
|
||||
0,
|
||||
),
|
||||
Self::SetupCleanup => (2, 0),
|
||||
Self::SetupFinally => (1, 0),
|
||||
Self::SetupWith => (1, 0),
|
||||
Self::StoreFastMaybeNull => (0, 1),
|
||||
};
|
||||
|
||||
|
||||
@@ -575,6 +575,13 @@ pub trait MarshalBag: Copy {
|
||||
}
|
||||
|
||||
fn constant_bag(self) -> Self::ConstantBag;
|
||||
|
||||
fn constant_ref_from_value(
|
||||
&self,
|
||||
_value: &Self::Value,
|
||||
) -> Option<<Self::ConstantBag as ConstantBag>::Constant> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<Bag: ConstantBag> MarshalBag for Bag {
|
||||
@@ -669,6 +676,13 @@ impl<Bag: ConstantBag> MarshalBag for Bag {
|
||||
fn constant_bag(self) -> Self::ConstantBag {
|
||||
self
|
||||
}
|
||||
|
||||
fn constant_ref_from_value(
|
||||
&self,
|
||||
value: &Self::Value,
|
||||
) -> Option<<Self::ConstantBag as ConstantBag>::Constant> {
|
||||
Some(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub const MAX_MARSHAL_STACK_DEPTH: usize = 2000;
|
||||
@@ -727,16 +741,18 @@ fn deserialize_value_after_header<R: Read, Bag: MarshalBag>(
|
||||
};
|
||||
|
||||
let typ = Type::try_from(type_code)?;
|
||||
// Code-objects keep their own inner ref table because Bag::Value (the
|
||||
// outer marshal value) and the constant-bag's Constant type are not
|
||||
// in general the same. When the outer header carried FLAG_REF, the
|
||||
// code object occupies slot 0 of the single global ref space, so we
|
||||
// mirror that by reserving slot 0 of the inner table.
|
||||
// CPython's r_object() uses one global ref table: TYPE_CODE reserves its
|
||||
// slot before reading code fields, and those fields may use later TYPE_REF
|
||||
// indexes. Keep the same indexes even when Bag::Value and Constant differ.
|
||||
let value = if matches!(typ, Type::Code) {
|
||||
let mut inner_refs: Vec<Option<<Bag::ConstantBag as ConstantBag>::Constant>> = Vec::new();
|
||||
if flag {
|
||||
inner_refs.push(None);
|
||||
}
|
||||
let mut inner_refs: Vec<Option<<Bag::ConstantBag as ConstantBag>::Constant>> = refs
|
||||
.iter()
|
||||
.map(|value| {
|
||||
value
|
||||
.as_ref()
|
||||
.and_then(|value| bag.constant_ref_from_value(value))
|
||||
})
|
||||
.collect();
|
||||
let code = deserialize_code_inner(rdr, bag.constant_bag(), depth - 1, &mut inner_refs)?;
|
||||
bag.make_code(code)
|
||||
} else {
|
||||
@@ -1501,6 +1517,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_tuple(hex: &str) -> Vec<ConstantData> {
|
||||
let bytes = hex_to_bytes(hex);
|
||||
let value = deserialize_value(&mut &bytes[..], BasicBag).expect("decode failed");
|
||||
match value {
|
||||
ConstantData::Tuple { elements } => elements,
|
||||
other => panic!("expected Tuple, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// CPython 3.14 marshal output for: `compile("x = 1", "<t>", "exec")`.
|
||||
/// Exercises FLAG_REF on the code object and TYPE_REF for qualname
|
||||
/// pointing back at the obj_name slot.
|
||||
@@ -1562,4 +1587,27 @@ mod tests {
|
||||
));
|
||||
assert!(matches!(consts[2], ConstantData::None));
|
||||
}
|
||||
|
||||
/// CPython 3.14 marshal output for:
|
||||
/// `(compile("x = 1", "<t>", "exec"),)`.
|
||||
/// The outer tuple occupies ref slot 0 and the code object occupies
|
||||
/// slot 1, so code-object fields must preserve that global ref offset.
|
||||
#[test]
|
||||
fn cpython_314_code_inside_tuple_preserves_ref_indexes() {
|
||||
let hex = "a901630000000000000000000000000100000000000000f30a00000080005e017400\
|
||||
520123002902e9010000004e2901da0178a900f300000000da033c743eda083c6d6f\
|
||||
64756c653e720700000001000000730a000000f003010101d8040582017205000000";
|
||||
let tuple = decode_tuple(hex);
|
||||
assert_eq!(tuple.len(), 1);
|
||||
let code = match &tuple[0] {
|
||||
ConstantData::Code { code } => code,
|
||||
other => panic!("expected nested Code, got {other:?}"),
|
||||
};
|
||||
assert_eq!(code.obj_name.as_str(), "<module>");
|
||||
assert_eq!(code.qualname.as_str(), "<module>");
|
||||
assert_eq!(code.source_path.as_str(), "<t>");
|
||||
assert_eq!(code.names.len(), 1);
|
||||
assert_eq!(code.names[0].as_str(), "x");
|
||||
assert_eq!(code.constants.len(), 2);
|
||||
}
|
||||
}
|
||||
Generated
+13
-13
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/stdlib/src/_opcode.rs
|
||||
assertion_line: 300
|
||||
expression: "dis(r#\"\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\"#)"
|
||||
---
|
||||
0 RESUME 0
|
||||
@@ -146,29 +147,29 @@ Disassembly of <code object test at 0xdeadbeef file "<?>", line 1>:
|
||||
L34: PUSH_EXC_INFO
|
||||
WITH_EXCEPT_START
|
||||
TO_BOOL
|
||||
POP_JUMP_IF_TRUE 2 (to L37)
|
||||
L35: NOT_TAKEN
|
||||
L36: RERAISE 2
|
||||
L37: POP_TOP
|
||||
L38: POP_EXCEPT
|
||||
POP_JUMP_IF_TRUE 2 (to L35)
|
||||
NOT_TAKEN
|
||||
RERAISE 2
|
||||
L35: POP_TOP
|
||||
L36: POP_EXCEPT
|
||||
POP_TOP
|
||||
POP_TOP
|
||||
POP_TOP
|
||||
JUMP_BACKWARD 205 (to L2)
|
||||
|
||||
-- L39: COPY 3
|
||||
-- L37: COPY 3
|
||||
POP_EXCEPT
|
||||
RERAISE 1
|
||||
L40: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
|
||||
L38: CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
|
||||
RERAISE 1
|
||||
ExceptionTable:
|
||||
L1 to L3 -> L40 [0] lasti
|
||||
L1 to L3 -> L38 [0] lasti
|
||||
L3 to L4 -> L34 [3] lasti
|
||||
L5 to L7 -> L27 [3]
|
||||
L7 to L8 -> L12 [7]
|
||||
L8 to L10 -> L27 [3]
|
||||
L10 to L11 -> L14 [5] lasti
|
||||
L11 to L12 -> L40 [0] lasti
|
||||
L11 to L12 -> L38 [0] lasti
|
||||
L12 to L13 -> L27 [3]
|
||||
L14 to L16 -> L24 [7] lasti
|
||||
L16 to L17 -> L18 [10]
|
||||
@@ -181,7 +182,6 @@ ExceptionTable:
|
||||
L29 to L30 -> L34 [3] lasti
|
||||
L30 to L32 -> L32 [4] lasti
|
||||
L32 to L33 -> L34 [3] lasti
|
||||
L33 to L34 -> L40 [0] lasti
|
||||
L34 to L35 -> L39 [5] lasti
|
||||
L36 to L38 -> L39 [5] lasti
|
||||
L38 to L40 -> L40 [0] lasti
|
||||
L33 to L34 -> L38 [0] lasti
|
||||
L34 to L36 -> L37 [5] lasti
|
||||
L36 to L38 -> L38 [0] lasti
|
||||
@@ -177,6 +177,7 @@ mod file_run {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::object::AsObject;
|
||||
use rustpython_vm::Interpreter;
|
||||
|
||||
fn interpreter() -> Interpreter {
|
||||
@@ -205,4 +206,28 @@ mod tests {
|
||||
assert_eq!(value, 5);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_expr_return_function_def() {
|
||||
interpreter().enter(|vm| {
|
||||
let scope = vm.new_scope_with_builtins();
|
||||
let value =
|
||||
vm.unwrap_pyresult(vm.run_block_expr(scope.clone(), "def f():\n return 7"));
|
||||
vm.unwrap_pyresult(scope.globals.set_item("returned", value, vm));
|
||||
let value = vm.unwrap_pyresult(vm.run_block_expr(scope, "returned is f"));
|
||||
assert!(value.is(&vm.ctx.true_value));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_expr_return_class_def() {
|
||||
interpreter().enter(|vm| {
|
||||
let scope = vm.new_scope_with_builtins();
|
||||
let value =
|
||||
vm.unwrap_pyresult(vm.run_block_expr(scope.clone(), "class C:\n value = 11"));
|
||||
vm.unwrap_pyresult(scope.globals.set_item("returned", value, vm));
|
||||
let value = vm.unwrap_pyresult(vm.run_block_expr(scope, "returned is C"));
|
||||
assert!(value.is(&vm.ctx.true_value));
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[WithExceptStart]
|
||||
stack_effect = { pushed = "7", popped = "6" }
|
||||
|
||||
[SetupCleanup]
|
||||
stack_effect = { pushed = "0" }
|
||||
|
||||
[SetupFinally]
|
||||
stack_effect = { pushed = "0" }
|
||||
|
||||
[SetupWith]
|
||||
stack_effect = { pushed = "0" }
|
||||
@@ -66,6 +66,12 @@ class OpcodeGen:
|
||||
def fn_has_local(self) -> str:
|
||||
return self.gen_fn_has_attr("has_local", "uses_locals", "HAS_LOCAL_FLAG")
|
||||
|
||||
@property
|
||||
def fn_has_eval_break(self) -> str:
|
||||
return self.gen_fn_has_attr(
|
||||
"has_eval_break", "eval_breaker", "HAS_EVAL_BREAK_FLAG"
|
||||
)
|
||||
|
||||
@property
|
||||
def fn_is_instrumented(self) -> str:
|
||||
arms = "|".join(
|
||||
|
||||
Reference in new issue
Block a user