Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fef345f
Base conf
ShaharNaveh May 5, 2026
09eb0b0
Basic layout
ShaharNaveh May 5, 2026
1ddf444
to from numeric
ShaharNaveh May 5, 2026
1e0c01b
generate has attr
ShaharNaveh May 5, 2026
c66eaf6
Basic instrumented
ShaharNaveh May 5, 2026
d1f0982
sort instrumeneted
ShaharNaveh May 5, 2026
5f07a17
deopt
ShaharNaveh May 5, 2026
bea434e
Cache entries
ShaharNaveh May 5, 2026
8768c9f
simplify
ShaharNaveh May 5, 2026
b9f1941
stack effect
ShaharNaveh May 5, 2026
566c0cf
Simplify conf structure
ShaharNaveh May 6, 2026
0b93378
Switch to numeric_repr
ShaharNaveh May 6, 2026
9ff0a7a
Add oparg conf
ShaharNaveh May 6, 2026
9c74715
`as_opcode()`
ShaharNaveh May 6, 2026
9e6382a
as_{opcode,instruction}
ShaharNaveh May 7, 2026
9f88f88
save progress
ShaharNaveh May 7, 2026
7669f0c
label_arg
ShaharNaveh May 7, 2026
e6d07b1
Compiler core compiles
ShaharNaveh May 7, 2026
99ba1db
More things to compile
ShaharNaveh May 7, 2026
9ce43ac
codegen compiles
ShaharNaveh May 7, 2026
d662a99
add must_use
ShaharNaveh May 7, 2026
0e35818
Overridable stack_effect
ShaharNaveh May 7, 2026
b58d736
fix stack_effect
ShaharNaveh May 7, 2026
74addfd
fix more in frame.rs
ShaharNaveh May 7, 2026
606a5aa
vm compiles
ShaharNaveh May 8, 2026
4ed7659
stdlib compile
ShaharNaveh May 8, 2026
0bc0d9b
_opcode `has_X` ignore deopt
ShaharNaveh May 8, 2026
a42b005
Align methods
ShaharNaveh May 8, 2026
09925bb
save
ShaharNaveh May 8, 2026
428ad29
fix `has_exc`
ShaharNaveh May 8, 2026
92e43e3
clippy
ShaharNaveh May 8, 2026
34268ce
Align script
ShaharNaveh May 8, 2026
9d909a3
clippy
ShaharNaveh May 8, 2026
f9358bf
Simplify code
ShaharNaveh May 9, 2026
d90b993
Fix compiles
ShaharNaveh May 9, 2026
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
More things to compile
  • Loading branch information
ShaharNaveh committed May 7, 2026
commit 99ba1db4510a1fa15dc6b95c6922c39ced608580
150 changes: 143 additions & 7 deletions crates/compiler-core/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
from generators_common import DEFAULT_INPUT
from stack import get_stack_effect

STATE = collections.defaultdict(dict)


@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
class OpcodeGen:
Expand Down Expand Up @@ -79,11 +81,11 @@ def fn_as_numeric(self) -> str:
"""

@property
def fn_tryfrom_numeric(self) -> str:
def fn_try_from_numeric(self) -> str:
arms = ",\n".join(f"{instr.opcode} => Self::{instr.name}" for instr in self)
return f"""
#[must_use]
pub const fn from_{self.numeric_repr}(
pub const fn try_from_{self.numeric_repr}(
value: {self.numeric_repr}
) -> Result<Self, MarshalError> {{
Ok(match value {{
Expand All @@ -94,13 +96,13 @@ def fn_tryfrom_numeric(self) -> str:
"""

@property
def impl_tryfrom_numeric(self) -> str:
def impl_try_from_numeric(self) -> str:
return f"""
impl TryFrom<{self.numeric_repr}> for {self.name} {{
type Error = MarshalError;

fn try_from(value: {self.numeric_repr}) -> Result<Self, Self::Error> {{
Self::from_{self.numeric_repr}(value)
Self::try_from_{self.numeric_repr}(value)
}}
}}
"""
Expand Down Expand Up @@ -169,6 +171,8 @@ def instrumented(self) -> list:

@property
def fn_to_base(self) -> str:
STATE[self.name]["to_base"] = True

inames = {instr.name for instr in self.instrumented}
names = {instr.name for instr in self} - inames

Expand All @@ -181,6 +185,7 @@ def fn_to_base(self) -> str:

arms = arms.strip()
if not arms:
STATE[self.name]["to_base"] = False
return ""

return f"""
Expand Down Expand Up @@ -223,6 +228,8 @@ def fn_to_instrumented(self) -> str:

@property
def fn_deopt(self) -> str:
STATE[self.name]["deopt"] = True

names = {instr.name for instr in self}

deopts = collections.defaultdict(list)
Expand All @@ -244,6 +251,7 @@ def fn_deopt(self) -> str:

arms = arms.strip()
if not arms:
STATE[self.name]["deopt"] = False
return ""

return f"""
Expand All @@ -258,6 +266,8 @@ def fn_deopt(self) -> str:

@property
def fn_cache_entries(self) -> str:
STATE[self.name]["cache_entries"] = True

arms = ""
for instr in self:
name = instr.name
Expand All @@ -277,6 +287,7 @@ def fn_cache_entries(self) -> str:

arms = arms.strip()
if not arms:
STATE[self.name]["cache_entries"] = False
return ""

return f"""
Expand All @@ -290,7 +301,7 @@ def fn_cache_entries(self) -> str:
"""

@property
def fn_stack_effect(self) -> str:
def fn_stack_effect_info(self) -> str:
arms = ""
for instr in self:
stack = get_stack_effect(instr)
Expand All @@ -303,17 +314,21 @@ def fn_stack_effect(self) -> str:
arms = arms.strip()

return f"""
fn stack_effect_info(&self, oparg: u32) -> StackEffect {{
pub fn stack_effect_info(&self, oparg: u32) -> StackEffect {{
// Reason for converting oparg to i32 is because of expressions like `1 + (oparg -1)`
// that causes underflow errors.
#[allow(unused, reason = "This is auto generated code")]
let oparg = i32::try_from(oparg).expect("oparg does not fit in an `i32`");

let (pushed, popped) = match self {{
{arms}
}};

debug_assert!(u32::try_from(pushed).is_ok());
debug_assert!(i32::try_from(pushed).is_ok());

debug_assert!(u32::try_from(popped).is_ok());
debug_assert!(i32::try_from(popped).is_ok());

StackEffect::new(pushed as u32, popped as u32)
}}
Expand Down Expand Up @@ -351,6 +366,29 @@ def impl_as_instruction(self) -> str:
}}
"""

@property
def fn_stack_effect_jump(self) -> str:
return """
/// Stack effect when the instruction takes its branch (jump=true).
///
/// CPython equivalent: `stack_effect(opcode, oparg, jump=True)`.
/// For most instructions this equals the fallthrough effect.
/// Override for instructions where branch and fallthrough differ
/// (e.g. `FOR_ITER`: fallthrough = +1, branch = −1).
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.stack_effect(oparg)
}
"""

@property
def fn_stack_effect(self) -> str:
return """
/// Stack effect of [`Self::stack_effect_info`].
pub fn stack_effect(&self, oparg: u32) -> i32 {
self.stack_effect_info(oparg).effect()
}
"""

def __iter__(self):
yield from self.instructions

Expand Down Expand Up @@ -434,6 +472,25 @@ def impl_as_opcode(self) -> str:
}}
"""

@property
def fn_as_numeric_repr(self) -> str:
return f"""
#[must_use]
pub const fn as_{self.numeric_repr}(self) -> {self.numeric_repr} {{
self.as_opcode().as_{self.numeric_repr}()
}}
"""

@property
def impl_as_numeric_repr(self) -> str:
return f"""
impl From<{self.name}> for {self.numeric_repr} {{
fn from(instruction: {self.name}) -> Self {{
instruction.as_{self.numeric_repr}()
}}
}}
"""

@property
def fn_label_arg(self) -> str:
>
Expand All @@ -452,14 +509,93 @@ def fn_label_arg(self) -> str:

return f"""
#[must_use]
pub const fn label_arg(&self) -> Option<{TARGET}> {{
pub const fn label_arg(&self) -> Option<Arg<{TARGET}>> {{
Some(match self {{
{arms}
_ => return None,
}})
}}
"""

@property
def fn_to_base(self) -> str:
if not STATE[self.opcode_enum]["to_base"]:
return ""

return f"""
#[must_use]
pub const fn to_base(self) -> Option<Self> {{
if let Some(opcode) = self.as_opcode().to_base() {{
Some(opcode.as_instruction())
}} else {{
None
}}
}}
"""

@property
def fn_try_from_numeric(self) -> str:
return f"""
#[must_use]
pub const fn try_from_{self.numeric_repr}(
value: {self.numeric_repr}
) -> Result<Self, MarshalError> {{
match {self.opcode_enum}::try_from_{self.numeric_repr}(value) {{
Ok(opcode) => Ok(opcode.as_instruction()),
Err(e) => Err(e),
}}
}}
"""

@property
def impl_try_from_numeric(self) -> str:
return f"""
impl TryFrom<{self.numeric_repr}> for {self.name} {{
type Error = MarshalError;

fn try_from(value: {self.numeric_repr}) -> Result<Self, Self::Error> {{
Self::try_from_{self.numeric_repr}(value)
}}
}}
"""

@property
def fn_stack_effect(self) -> str:
return """
/// Stack effect of [`Self::stack_effect_info`].
pub fn stack_effect(&self, oparg: u32) -> i32 {
self.as_opcode().stack_effect(oparg)
}
"""

@property
def fn_cache_entries(self) -> str:
if not STATE[self.opcode_enum]["cache_entries"]:
return ""

return f"""
#[must_use]
pub const fn cache_entries(self) -> usize {{
self.as_opcode().cache_entries()
}}
"""

@property
def fn_deopt(self) -> str:
if not STATE[self.opcode_enum]["deopt"]:
return ""

return f"""
#[must_use]
pub const fn deopt(self) -> Option<Self> {{
if let Some(opcode) = self.as_opcode().deopt() {{
Some(opcode.as_instruction())
}} else {{
None
}}
}}
"""

def __iter__(self):
yield from self.instructions

Expand Down
19 changes: 17 additions & 2 deletions crates/compiler-core/src/bytecode/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ impl Instruction {
Opcode::ReturnValue | Opcode::RaiseVarargs | Opcode::Reraise
)
}

/// Map a specialized or instrumented opcode back to its adaptive (base) variant.
#[must_use]
pub const fn deoptimize(self) -> Self {
match self.deopt() {
Some(v) => v,
None => {
// Instrumented opcodes map back to their base
match self.to_base() {
Some(v) => v,
None => self,
}
}
}
}
Comment on lines +121 to 124

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 | 🔴 Critical | ⚡ Quick win

Instruction::stack_effect_jump calls the wrong delegate.

This forwards to Opcode::stack_effect, not Opcode::stack_effect_jump. PseudoInstruction::stack_effect_jump (line 151-153) correctly forwards to stack_effect_jump. Today the two Opcode methods return identical values, but the moment Opcode::stack_effect_jump is overridden for branch-vs-fallthrough cases (e.g. ForIter, as called out in the doc-comment at lines 49-55), the Instruction path will silently produce wrong stack effects on the branch edge — exactly the contract this method is meant to model.

🐛 Proposed fix
     #[must_use]
     pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
-        self.as_opcode().stack_effect(oparg)
+        self.as_opcode().stack_effect_jump(oparg)
     }
📝 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
#[must_use]
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.as_opcode().stack_effect(oparg)
}
#[must_use]
pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
self.as_opcode().stack_effect_jump(oparg)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/compiler-core/src/bytecode/instruction.rs` around lines 121 - 124,
Instruction::stack_effect_jump incorrectly delegates to Opcode::stack_effect
instead of Opcode::stack_effect_jump; update the method to call
self.as_opcode().stack_effect_jump(oparg) so it mirrors
PseudoInstruction::stack_effect_jump and correctly uses the branch-specific
stack effect behavior for opcodes like ForIter (use as_opcode(),
stack_effect_jump, and Instruction::stack_effect_jump to locate the change).

}

impl PseudoInstruction {
Expand Down Expand Up @@ -164,7 +179,7 @@ impl AnyInstruction {
#[must_use]
pub const fn real_opcode(self) -> Option<Opcode> {
match self.real() {
Some(ins) => Some(ins.opcode()),
Some(ins) => Some(ins.as_opcode()),
_ => None,
}
}
Expand All @@ -173,7 +188,7 @@ impl AnyInstruction {
#[must_use]
pub const fn pseudo_opcode(self) -> Option<PseudoOpcode> {
match self.pseudo() {
Some(ins) => Some(ins.opcode()),
Some(ins) => Some(ins.as_opcode()),
_ => None,
}
}
Expand Down
Loading