Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
21d9632
POC: automated instruction gen
ShaharNaveh Sep 23, 2025
f6ab8d2
Add generated file
ShaharNaveh Sep 23, 2025
b8ebe9b
Add note about pseudos opcode
ShaharNaveh Sep 23, 2025
8510815
Use `properties.oparg` instead
ShaharNaveh Sep 23, 2025
bcea0bf
Sort by id. impl TryFrom
ShaharNaveh Sep 25, 2025
c7ffa2a
Have `is_valid` as a seperate method
ShaharNaveh Sep 25, 2025
f1948c6
Include in tree
ShaharNaveh Sep 25, 2025
84c7f45
Impl `has_X` methods
ShaharNaveh Sep 25, 2025
9adf908
is_pseudo
ShaharNaveh Sep 25, 2025
99ed360
Set `Self::`
ShaharNaveh Sep 25, 2025
56c0dc8
refactor
ShaharNaveh Sep 26, 2025
3fdc479
impl num_{pushed,popped}
ShaharNaveh Sep 26, 2025
5cf1888
Resolve some errors
ShaharNaveh Sep 26, 2025
eaee21e
Sort like cpython does it
ShaharNaveh Sep 26, 2025
acd74c7
Make more things compile
ShaharNaveh Sep 26, 2025
f71ad1b
Don't use the new Instruction yet
ShaharNaveh Oct 5, 2025
b860f65
pivot to only generating opcodeid atm
ShaharNaveh Oct 5, 2025
ff4fb09
Remove `enum` related code
ShaharNaveh Oct 5, 2025
493da32
Use `OpcodeId` in `_opcode`
ShaharNaveh Oct 5, 2025
657660f
add auto generated file to .gitattributes
ShaharNaveh Oct 5, 2025
eacab03
clippy
ShaharNaveh Oct 5, 2025
7b36086
Apply coderabbit suggestion
ShaharNaveh Oct 5, 2025
dd761d0
Switch back to enum
ShaharNaveh Oct 5, 2025
aeedbdc
Re-add `num_{popped,pushed}`
ShaharNaveh Oct 5, 2025
33a90f3
Improve `_opcode.stack_effect`
ShaharNaveh Oct 5, 2025
4a11f38
Add missing dis attrs for tests
ShaharNaveh Oct 5, 2025
029e534
Unmark passing tests
ShaharNaveh Oct 5, 2025
11369f8
More changes to `dis.py` from 3.13.7
ShaharNaveh Oct 5, 2025
a5813ea
Add reason for failing tests
ShaharNaveh Oct 5, 2025
6e6accd
Base `Opcode` impl
ShaharNaveh Oct 6, 2025
f8a887a
clippy
ShaharNaveh Oct 6, 2025
a2014b5
Fix another test
ShaharNaveh Oct 6, 2025
6c48b49
clippy
ShaharNaveh Oct 6, 2025
46f3e29
clippy
ShaharNaveh Oct 6, 2025
742b709
cspell
ShaharNaveh Oct 6, 2025
2a8fb1e
Add `new_unchecked`
ShaharNaveh Oct 6, 2025
465528e
More docs
ShaharNaveh Oct 6, 2025
e437232
unused import
ShaharNaveh Oct 6, 2025
1ae0c7e
nits
ShaharNaveh Oct 6, 2025
28bab70
Trigger CI
ShaharNaveh Oct 6, 2025
fe3f94a
Merge remote-tracking branch 'upstream/main' into instruction-autogen
ShaharNaveh Oct 13, 2025
aca4508
Use `num_enum` for opcode enums
ShaharNaveh Oct 13, 2025
3daec5a
Remove unused code
ShaharNaveh Oct 13, 2025
9436354
typo
ShaharNaveh Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use OpcodeId in _opcode
  • Loading branch information
ShaharNaveh committed Oct 5, 2025
commit 493da321d204495143ab0b42260c08928d9738e2
2 changes: 1 addition & 1 deletion compiler/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

pub mod bytecode;
pub mod frozen;
pub mod instruction;
pub mod marshal;
mod mode;
pub mod opcode;

pub use mode::Mode;

Expand Down
18 changes: 9 additions & 9 deletions compiler/core/src/instruction.rs → compiler/core/src/opcode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
///! Python opcode implementation. Currently aligned with cpython 3.13.7

// This file is generated by scripts/gen_instructions.py
// This file is generated by scripts/gen_opcodes.py
// Do not edit!

/// Represents a valid opcode ID.
Expand Down Expand Up @@ -248,7 +248,7 @@ impl OpcodeId {
#[must_use]
pub const fn is_pseudo(&self) -> bool {
matches!(
self,
*self,
Self::JUMP
| Self::JUMP_NO_INTERRUPT
| Self::LOAD_CLOSURE
Expand All @@ -268,7 +268,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_arg(&self) -> bool {
matches!(
self,
*self,
Self::BINARY_OP
| Self::BUILD_CONST_KEY_MAP
| Self::BUILD_LIST
Expand Down Expand Up @@ -425,7 +425,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_const(&self) -> bool {
matches!(
self,
*self,
Self::INSTRUMENTED_RETURN_CONST | Self::LOAD_CONST | Self::RETURN_CONST
)
}
Expand All @@ -434,7 +434,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_name(&self) -> bool {
matches!(
self,
*self,
Self::DELETE_ATTR
| Self::DELETE_GLOBAL
| Self::DELETE_NAME
Expand Down Expand Up @@ -464,7 +464,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_jump(&self) -> bool {
matches!(
self,
*self,
Self::FOR_ITER
| Self::FOR_ITER_LIST
| Self::FOR_ITER_RANGE
Expand All @@ -486,7 +486,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_free(&self) -> bool {
matches!(
self,
*self,
Self::DELETE_DEREF
| Self::LOAD_DEREF
| Self::LOAD_FROM_DICT_OR_DEREF
Expand All @@ -499,7 +499,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_local(&self) -> bool {
matches!(
self,
*self,
Self::BINARY_OP_INPLACE_ADD_UNICODE
| Self::DELETE_FAST
| Self::LOAD_FAST
Expand All @@ -518,7 +518,7 @@ impl OpcodeId {
#[must_use]
pub const fn has_exc(&self) -> bool {
matches!(
self,
*self,
Self::COPY
| Self::END_FOR
| Self::END_SEND
Expand Down
6 changes: 3 additions & 3 deletions scripts/gen_instructions.py → scripts/gen_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from opcode_metadata_generator import cflags

Comment thread
ShaharNaveh marked this conversation as resolved.
ROOT = pathlib.Path(__file__).parents[1]
OUT_PATH = ROOT / "compiler" / "core" / "src" / "instruction.rs"
OUT_PATH = ROOT / "compiler" / "core" / "src" / "opcode.rs"


def group_ranges(it: "Iterable[int]") -> "Iterator[range]":
Expand Down Expand Up @@ -153,7 +153,7 @@ def generate_is_pseudo(self) -> str:
/// Whether opcode ID is pseudo.
#[must_use]
pub const fn is_pseudo(&self) -> bool {{
matches!(self, {matches})
matches!(*self, {matches})
}}
""".strip()

Expand All @@ -166,7 +166,7 @@ def _generate_has_attr(self, attr: str, *, flag_override: str | None = None) ->
/// Whether opcode ID have '{flag}' set.
#[must_use]
pub const fn has_{attr}(&self) -> bool {{
matches!(self, {matches})
matches!(*self, {matches})
}}
""".strip()

Expand Down
118 changes: 48 additions & 70 deletions stdlib/src/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,10 @@ mod opcode {
use crate::vm::{
AsObject, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyBool, PyInt, PyIntRef, PyNone},
instruction::Instruction,
bytecode::Instruction,
match_class,
opcode::OpcodeId,
};
use std::ops::Deref;

struct Opcode(Instruction);

impl Deref for Opcode {
type Target = Instruction;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Opcode {
#[must_use]
fn try_from_pyint(raw: PyIntRef, vm: &VirtualMachine) -> PyResult<Self> {
let instruction = raw
.try_to_primitive::<u16>(vm)
.and_then(|v| {
Instruction::try_from(v).map_err(|_| {
vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned())
})
})
.map_err(|_| vm.new_value_error("invalid opcode or oparg"))?;

Ok(Self(instruction))
}
}

#[pyattr]
const ENABLE_SPECIALIZATION: u8 = 1;
Expand All @@ -49,82 +23,87 @@ mod opcode {
jump: Option<PyObjectRef>,
}

/*
#[pyfunction]
fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult<i32> {
let oparg = args
.oparg
.map(|v| {
if !v.fast_isinstance(vm.ctx.types.int_type) {
return Err(vm.new_type_error(format!(
"'{}' object cannot be interpreted as an integer",
v.class().name()
)));
// TODO: Make use of the auto generated `Instruction` enum (when we will have it)
#[pyfunction]
fn stack_effect(args: StackEffectArgs, vm: &VirtualMachine) -> PyResult<i32> {
let oparg = args
.oparg
.map(|v| {
if !v.fast_isinstance(vm.ctx.types.int_type) {
return Err(vm.new_type_error(format!(
"'{}' object cannot be interpreted as an integer",
v.class().name()
)));
}
v.downcast_ref::<PyInt>()
.ok_or_else(|| vm.new_type_error(""))?
.try_to_primitive::<u32>(vm)
})
.unwrap_or(Ok(0))?;

let jump = args
.jump
.map(|v| {
match_class!(match v {
b @ PyBool => Ok(b.is(&vm.ctx.true_value)),
_n @ PyNone => Ok(false),
_ => {
Err(vm.new_value_error("stack_effect: jump must be False, True or None"))
}
v.downcast_ref::<PyInt>()
.ok_or_else(|| vm.new_type_error(""))?
.try_to_primitive::<u32>(vm)
})
.unwrap_or(Ok(0))?;

let jump = args
.jump
.map(|v| {
match_class!(match v {
b @ PyBool => Ok(b.is(&vm.ctx.true_value)),
_n @ PyNone => Ok(false),
_ => {
Err(vm.new_value_error("stack_effect: jump must be False, True or None"))
}
})
})
.unwrap_or(Ok(false))?;
})
.unwrap_or(Ok(false))?;

let opcode = Opcode::try_from_pyint(args.opcode, vm)?;
let instruction = args
.opcode
.try_to_primitive::<u8>(vm)
.and_then(|v| {
Instruction::try_from(v)
.map_err(|_| vm.new_exception_empty(vm.ctx.exceptions.value_error.to_owned()))
})
.map_err(|_| vm.new_value_error("invalid opcode or oparg"))?;

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Ok(opcode.stack_effect(oparg.into(), jump))
}
*/
Ok(instruction.stack_effect(oparg.into(), jump))
}

/*
#[pyfunction]
fn is_valid(opcode: i32) -> bool {
Opcode::is_valid(opcode)
OpcodeId::try_from(opcode).is_ok()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider filtering pseudo-opcodes in is_valid for consistency.

All the has_* predicates (lines 71-101) explicitly filter out pseudo-opcodes with !oid.is_pseudo(), but is_valid does not. Pseudo-opcodes are typically not valid for runtime bytecode execution. For consistency with the other predicates and to match typical CPython semantics, is_valid should likely also exclude pseudo-opcodes.

Apply this diff to filter pseudo-opcodes:

 fn is_valid(opcode: i32) -> bool {
-    OpcodeId::try_from(opcode).is_ok()
+    OpcodeId::try_from(opcode).is_ok_and(|oid| !oid.is_pseudo())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
OpcodeId::try_from(opcode).is_ok()
fn is_valid(opcode: i32) -> bool {
OpcodeId::try_from(opcode).is_ok_and(|oid| !oid.is_pseudo())
}
🤖 Prompt for AI Agents
In stdlib/src/opcode.rs around line 66, update the is_valid implementation to
exclude pseudo-opcodes like the other has_* predicates: after converting opcode
to an OpcodeId via try_from, return true only if the conversion succeeds and the
resulting OpcodeId is not a pseudo-opcode (i.e., check !oid.is_pseudo()); ensure
you handle the Err case from try_from by returning false.

}

#[pyfunction]
fn has_arg(opcode: i32) -> bool {
Opcode::has_arg(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_arg())
}

#[pyfunction]
fn has_const(opcode: i32) -> bool {
Opcode::has_const(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_const())
}

#[pyfunction]
fn has_name(opcode: i32) -> bool {
Opcode::has_name(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_name())
}

#[pyfunction]
fn has_jump(opcode: i32) -> bool {
Opcode::has_jump(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_jump())
}

#[pyfunction]
fn has_free(opcode: i32) -> bool {
Opcode::has_free(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_free())
}

#[pyfunction]
fn has_local(opcode: i32) -> bool {
Opcode::has_local(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_local())
}

#[pyfunction]
fn has_exc(opcode: i32) -> bool {
Opcode::has_exc(opcode)
OpcodeId::try_from(opcode).map_or(false, |oid| !oid.is_pseudo() && oid.has_exc())
}
Comment thread
ShaharNaveh marked this conversation as resolved.

#[pyfunction]
Expand Down Expand Up @@ -212,5 +191,4 @@ mod opcode {
fn get_specialization_stats(vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.none()
}
*/
}
2 changes: 1 addition & 1 deletion vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub use self::object::{
pub use self::vm::{Context, Interpreter, Settings, VirtualMachine};

pub use rustpython_common as common;
pub use rustpython_compiler_core::{bytecode, frozen, instruction};
pub use rustpython_compiler_core::{bytecode, frozen, opcode};
pub use rustpython_literal as literal;

#[doc(hidden)]
Expand Down