Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 0 additions & 4 deletions Lib/_opcode_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

_specializations = {}





_specialized_opmap = {}

opmap = {
Expand Down
30 changes: 20 additions & 10 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6212,23 +6212,33 @@ impl Compiler {
}
emit!(self, Instruction::CallFunctionEx { has_kwargs });
} else if !arguments.keywords.is_empty() {
let mut kwarg_names = vec![];
// No **kwargs in this branch (has_double_star is false),
// so all keywords have arg.is_some()
let mut kwarg_names = Vec::with_capacity(arguments.keywords.len());
for keyword in &arguments.keywords {
if let Some(name) = &keyword.arg {
kwarg_names.push(ConstantData::Str {
value: name.as_str().into(),
});
} else {
// This means **kwargs!
panic!("name must be set");
}
let name = keyword
.arg
.as_ref()
.expect("has_double_star is false, so arg must be Some");
kwarg_names.push(ConstantData::Str {
value: name.as_str().into(),
});
self.compile_expression(&keyword.value)?;
}

self.emit_load_const(ConstantData::Tuple {
elements: kwarg_names,
});
emit!(self, Instruction::CallKw { nargs: count });
// nargs = positional args + keyword args
let positional = additional_positional
.checked_add(u32::try_from(arguments.args.len()).expect("too many positional args"))
.expect("too many positional args");
let keyword_count =
u32::try_from(arguments.keywords.len()).expect("too many keyword args");
let nargs = positional
.checked_add(keyword_count)
.expect("too many arguments");
emit!(self, Instruction::CallKw { nargs });
} else {
emit!(self, Instruction::Call { nargs: count });
}
Expand Down
63 changes: 56 additions & 7 deletions crates/codegen/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use crate::{IndexMap, IndexSet, error::InternalError};
use rustpython_compiler_core::{
OneIndexed, SourceLocation,
bytecode::{
CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
Arg, CodeFlags, CodeObject, CodeUnit, CodeUnits, ConstantData, ExceptionTableEntry,
InstrDisplayContext, Instruction, Label, OpArg, PyCodeLocationInfoKind,
encode_exception_table,
encode_exception_table, encode_load_attr_arg,
},
varint::{write_signed_varint, write_varint},
};
Expand Down Expand Up @@ -189,6 +189,34 @@ impl CodeInfo {
let mut instructions = Vec::new();
let mut locations = Vec::new();

// convert_pseudo_ops: instructions before the main loop
for block in blocks
.iter_mut()
.filter(|b| b.next != BlockIdx::NULL || !b.instructions.is_empty())
{
for info in &mut block.instructions {
match info.instr {
// LOAD_ATTR_METHOD pseudo → LOAD_ATTR (with method flag=1)
Instruction::LoadAttrMethod { idx } => {
let encoded = encode_load_attr_arg(idx.get(info.arg), true);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadAttr { idx: Arg::marker() };
}
// LOAD_ATTR → encode with method flag=0
Instruction::LoadAttr { idx } => {
let encoded = encode_load_attr_arg(idx.get(info.arg), false);
info.arg = OpArg(encoded);
info.instr = Instruction::LoadAttr { idx: Arg::marker() };
}
// POP_BLOCK pseudo → NOP
Instruction::PopBlock => {
info.instr = Instruction::Nop;
}
_ => {}
}
}
}

let mut block_to_offset = vec![Label(0); blocks.len()];
// block_to_index: maps block idx to instruction index (for exception table)
// This is the index into the final instructions array, including EXTENDED_ARG
Expand All @@ -213,23 +241,44 @@ impl CodeInfo {
let mut next_block = BlockIdx(0);
while next_block != BlockIdx::NULL {
let block = &mut blocks[next_block];
// Track current instruction offset for jump direction resolution
let mut current_offset = block_to_offset[next_block.idx()].0;
for info in &mut block.instructions {
let (op, arg, target) = (info.instr, &mut info.arg, info.target);
let target = info.target;
if target != BlockIdx::NULL {
let new_arg = OpArg(block_to_offset[target.idx()].0);
recompile_extended_arg |= new_arg.instr_size() != arg.instr_size();
*arg = new_arg;
recompile_extended_arg |= new_arg.instr_size() != info.arg.instr_size();
info.arg = new_arg;
}
let (extras, lo_arg) = arg.split();

// Convert JUMP pseudo to real instructions (direction depends on offset)
let op = match info.instr {
Instruction::Jump { .. } if target != BlockIdx::NULL => {
let target_offset = block_to_offset[target.idx()].0;
if target_offset > current_offset {
Instruction::JumpForward {
target: Arg::marker(),
}
} else {
Instruction::JumpBackward {
target: Arg::marker(),
}
}
}
other => other,
};

let (extras, lo_arg) = info.arg.split();
locations.extend(core::iter::repeat_n(
(info.location, info.end_location),
arg.instr_size(),
info.arg.instr_size(),
));
instructions.extend(
extras
.map(|byte| CodeUnit::new(Instruction::ExtendedArg, byte))
.chain([CodeUnit { op, arg: lo_arg }]),
);
current_offset += info.arg.instr_size() as u32;
}
next_block = block.next;
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 37 additions & 1 deletion crates/compiler-core/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ pub fn find_exception_handler(table: &[u8], offset: u32) -> Option<ExceptionTabl
None
}

/// Encode LOAD_ATTR oparg: bit 0 = method flag, bits 1+ = name index.
#[inline]
pub const fn encode_load_attr_arg(name_idx: u32, is_method: bool) -> u32 {
(name_idx << 1) | (is_method as u32)
}

/// Decode LOAD_ATTR oparg: returns (name_idx, is_method).
#[inline]
pub const fn decode_load_attr_arg(oparg: u32) -> (u32, bool) {
let is_method = (oparg & 1) == 1;
let name_idx = oparg >> 1;
(name_idx, is_method)
}
Comment on lines +87 to +99
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

LOAD_ATTR oparg encoding needs an explicit size invariant (avoid silent truncation).
(name_idx << 1) will discard the top bit if name_idx >= 2^31. If that’s impossible by construction, consider asserting/documenting it.

Possible defensive tweak
 pub const fn encode_load_attr_arg(name_idx: u32, is_method: bool) -> u32 {
-    (name_idx << 1) | (is_method as u32)
+    // name_idx uses bits 1..; bit0 is reserved for is_method.
+    debug_assert!(name_idx < (1 << 31));
+    (name_idx << 1) | (is_method as u32)
 }
🤖 Prompt for AI Agents
In @crates/compiler-core/src/bytecode.rs around lines 87 - 99, The
encode_load_attr_arg function can silently truncate when name_idx has its top
bit set; add an explicit size invariant check (e.g., assert or debug_assert)
that name_idx fits in 31 bits before shifting, and document this invariant in
the function comment; keep decode_load_attr_arg unchanged but ensure the pair of
functions' contract/comments state the 31-bit limit so callers cannot pass
larger values.


/// Oparg values for [`Instruction::ConvertValue`].
///
/// ## See also
Expand Down Expand Up @@ -1740,6 +1754,9 @@ impl Instruction {
pub const fn label_arg(&self) -> Option<Arg<Label>> {
match self {
Jump { target: l }
| JumpBackward { target: l }
| JumpBackwardNoInterrupt { target: l }
| JumpForward { target: l }
| JumpIfNotExcMatch(l)
| PopJumpIfTrue { target: l }
| PopJumpIfFalse { target: l }
Expand All @@ -1766,6 +1783,9 @@ impl Instruction {
matches!(
self,
Jump { .. }
| JumpForward { .. }
| JumpBackward { .. }
| JumpBackwardNoInterrupt { .. }
| Continue { .. }
| Break { .. }
| ReturnValue
Expand Down Expand Up @@ -2060,11 +2080,27 @@ impl Instruction {
ImportName { idx } => w!(IMPORT_NAME, name = idx),
IsOp(inv) => w!(IS_OP, ?inv),
Jump { target } => w!(JUMP, target),
JumpBackward { target } => w!(JUMP_BACKWARD, target),
JumpBackwardNoInterrupt { target } => w!(JUMP_BACKWARD_NO_INTERRUPT, target),
JumpForward { target } => w!(JUMP_FORWARD, target),
JumpIfFalseOrPop { target } => w!(JUMP_IF_FALSE_OR_POP, target),
JumpIfNotExcMatch(target) => w!(JUMP_IF_NOT_EXC_MATCH, target),
JumpIfTrueOrPop { target } => w!(JUMP_IF_TRUE_OR_POP, target),
ListAppend { i } => w!(LIST_APPEND, i),
LoadAttr { idx } => w!(LOAD_ATTR, name = idx),
LoadAttr { idx } => {
let encoded = idx.get(arg);
let (name_idx, is_method) = decode_load_attr_arg(encoded);
let attr_name = name(name_idx);
if is_method {
write!(
f,
"{:pad$}({}, {}, method=true)",
"LOAD_ATTR", encoded, attr_name
)
} else {
write!(f, "{:pad$}({}, {})", "LOAD_ATTR", encoded, attr_name)
}
}
LoadAttrMethod { idx } => w!(LOAD_ATTR_METHOD, name = idx),
LoadBuildClass => w!(LOAD_BUILD_CLASS),
LoadClassDeref(idx) => w!(LOAD_CLASSDEREF, cell_name = idx),
Expand Down
10 changes: 8 additions & 2 deletions crates/jit/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
match instruction {
Instruction::ReturnValue
| Instruction::ReturnConst { .. }
| Instruction::Jump { .. } => {
| Instruction::Jump { .. }
| Instruction::JumpBackward { .. }
| Instruction::JumpBackwardNoInterrupt { .. }
| Instruction::JumpForward { .. } => {
in_unreachable_code = true;
}
_ => {}
Expand Down Expand Up @@ -558,7 +561,10 @@ impl<'a, 'b> FunctionCompiler<'a, 'b> {
}
Instruction::ExtendedArg => Ok(()),

Instruction::Jump { target } => {
Instruction::Jump { target }
| Instruction::JumpBackward { target }
| Instruction::JumpBackwardNoInterrupt { target }
| Instruction::JumpForward { target } => {
let target_block = self.get_or_create_block(target.get(arg));
self.builder.ins().jump(target_block, &[]);
Ok(())
Expand Down
Loading
Loading