Bytecode parity (#7514)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Jeong, YunWonanddependabot[bot] authored and GitHub committed 2026-03-28 09:19:12 +09:00
1 parent e564d4646f
commit d12f623ade
5 files changed
+71 -34

No files matched your search

+46 -6
View File
@@ -4581,11 +4581,21 @@ impl Compiler {
_ => continue,
};
// Skip @staticmethod and @classmethod decorated functions
let dominated_by_special = f.decorator_list.iter().any(|d| {
let has_special_decorator = f.decorator_list.iter().any(|d| {
matches!(&d.expression, ast::Expr::Name(n)
if n.id.as_str() == "staticmethod" || n.id.as_str() == "classmethod")
});
if dominated_by_special {
if has_special_decorator {
continue;
}
// Skip implicit classmethods (__init_subclass__, __class_getitem__)
let fname = f.name.as_str();
if fname == "__init_subclass__" || fname == "__class_getitem__" {
continue;
}
// For __new__, scan for "self" (not the first param "cls")
if fname == "__new__" {
Self::scan_store_attrs(&f.body, "self", attrs);
continue;
}
let first_param = f
@@ -5458,8 +5468,7 @@ impl Compiler {
// The thing iterated:
// Optimize: `for x in [a, b, c]` → use tuple instead of list
// (list creation is wasteful for iteration)
// Skip optimization if any element is starred (e.g., `[a, *b, c]`)
// Skip for async-for (GET_AITER expects the original type)
if !is_async
&& let ast::Expr::List(ast::ExprList { elts, .. }) = iter
&& !elts.iter().any(|e| matches!(e, ast::Expr::Starred(_)))
@@ -7369,6 +7378,14 @@ impl Compiler {
Some(expression) => self.compile_expression(expression)?,
Option::None => self.emit_load_const(ConstantData::None),
};
if self.ctx.func == FunctionContext::AsyncFunction {
emit!(
self,
Instruction::CallIntrinsic1 {
func: bytecode::IntrinsicFunction1::AsyncGenWrap
}
);
}
// arg=0: direct yield (wrapped for async generators)
emit!(self, Instruction::YieldValue { arg: 0 });
emit!(
@@ -7591,6 +7608,14 @@ impl Compiler {
compiler.compile_comprehension_element(elt)?;
compiler.mark_generator();
if compiler.ctx.func == FunctionContext::AsyncFunction {
emit!(
compiler,
Instruction::CallIntrinsic1 {
func: bytecode::IntrinsicFunction1::AsyncGenWrap
}
);
}
// arg=0: direct yield (wrapped for async generators)
emit!(compiler, Instruction::YieldValue { arg: 0 });
emit!(
@@ -7891,6 +7916,22 @@ impl Compiler {
if let ast::Expr::Starred(ast::ExprStarred { value, .. }) = &arguments.args[0] {
self.compile_expression(value)?;
}
} else if !has_starred {
for arg in &arguments.args {
self.compile_expression(arg)?;
}
self.set_source_range(call_range);
let positional_count = additional_positional + nelts.to_u32();
if positional_count == 0 {
self.emit_load_const(ConstantData::Tuple { elements: vec![] });
} else {
emit!(
self,
Instruction::BuildTuple {
count: positional_count
}
);
}
} else {
// Use starunpack_helper to build a list, then convert to tuple
self.starunpack_helper(
@@ -8236,7 +8277,6 @@ impl Compiler {
// Create comprehension function with closure
self.make_closure(code, bytecode::MakeFunctionFlags::new())?;
emit!(self, Instruction::PushNull);
// Evaluate iterated item:
self.compile_expression(&generators[0].iter)?;
@@ -8250,7 +8290,7 @@ impl Compiler {
};
// Call just created <listcomp> function:
emit!(self, Instruction::Call { argc: 1 });
emit!(self, Instruction::Call { argc: 0 });
if is_async_list_set_dict_comprehension {
emit!(self, Instruction::GetAwaitable { r#where: 0 });
self.emit_load_const(ConstantData::None);
@@ -1,6 +1,6 @@
---
source: crates/codegen/src/compile.rs
assertion_line: 9591
assertion_line: 9780
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)
+19 -7
View File
@@ -1290,22 +1290,34 @@ impl PyCode {
let idx = usize::try_from(opcode).map_err(|_| idx_err(vm))?;
let varnames_len = self.code.varnames.len();
let cellvars_len = self.code.cellvars.len();
// Non-parameter cells: cellvars that are NOT also in varnames
let nonparam_cellvars: Vec<_> = self
.code
.cellvars
.iter()
.filter(|s| {
let s_str: &str = s.as_ref();
!self.code.varnames.iter().any(|v| {
let v_str: &str = v.as_ref();
v_str == s_str
})
})
.collect();
let nonparam_len = nonparam_cellvars.len();
let name = if idx < varnames_len {
// Index in varnames
// Index in varnames (includes parameter cells)
self.code.varnames.get(idx).ok_or_else(|| idx_err(vm))?
} else if idx < varnames_len + cellvars_len {
// Index in cellvars
self.code
.cellvars
} else if idx < varnames_len + nonparam_len {
// Index in non-parameter cellvars
*nonparam_cellvars
.get(idx - varnames_len)
.ok_or_else(|| idx_err(vm))?
} else {
// Index in freevars
self.code
.freevars
.get(idx - varnames_len - cellvars_len)
.get(idx - varnames_len - nonparam_len)
.ok_or_else(|| idx_err(vm))?
};
Ok(name.to_object())
+2 -19
View File
@@ -10,7 +10,6 @@ use crate::{
PyBaseException, PyBaseExceptionRef, PyBaseObject, PyCode, PyCoroutine, PyDict, PyDictRef,
PyFloat, PyFrozenSet, PyGenerator, PyInt, PyInterpolation, PyList, PyModule, PyProperty,
PySet, PySlice, PyStr, PyStrInterned, PyTemplate, PyTraceback, PyType, PyUtf8Str,
asyncgenerator::PyAsyncGenWrappedValue,
builtin_func::PyNativeFunction,
descriptor::{MemberGetter, PyMemberDescriptor, PyMethodDescriptor},
frame::stack_analysis,
@@ -3548,7 +3547,7 @@ impl ExecutingFrame<'_> {
Ok(None)
}
Instruction::YieldValue { arg: oparg } => {
Instruction::YieldValue { .. } => {
debug_assert!(
self.localsplus
.stack_as_slice()
@@ -3557,16 +3556,7 @@ impl ExecutingFrame<'_> {
.all(|sr| !sr.is_borrowed()),
"borrowed refs on stack at yield point"
);
let value = self.pop_value();
// arg=0: direct yield (wrapped for async generators)
// arg=1: yield from await/yield-from (NOT wrapped)
let wrap = oparg.get(arg) == 0;
let value = if wrap && self.code.flags.contains(bytecode::CodeFlags::COROUTINE) {
PyAsyncGenWrappedValue(value).into_pyobject(vm)
} else {
value
};
Ok(Some(ExecutionResult::Yield(value)))
Ok(Some(ExecutionResult::Yield(self.pop_value())))
}
Instruction::Send { .. } => {
// (receiver, v -- receiver, retval)
@@ -5800,13 +5790,6 @@ impl ExecutingFrame<'_> {
let offset = (self.lasti() - 1) * 2;
monitoring::fire_py_yield(vm, self.code, offset, &value)?;
}
let oparg = u32::from(arg);
let wrap = oparg == 0;
let value = if wrap && self.code.flags.contains(bytecode::CodeFlags::COROUTINE) {
PyAsyncGenWrappedValue(value).into_pyobject(vm)
} else {
value
};
Ok(Some(ExecutionResult::Yield(value)))
}
Instruction::InstrumentedCall => {
+3 -1
View File
@@ -298,7 +298,9 @@ impl PyObject {
) -> PyResult<Either<PyObjectRef, bool>> {
let swapped = op.swapped();
let call_cmp = |obj: &Self, other: &Self, op| {
let cmp = obj.class().slots.richcompare.load().unwrap();
let Some(cmp) = obj.class().slots.richcompare.load() else {
return Ok(PyArithmeticValue::NotImplemented);
};
let r = match cmp(obj, other, op, vm)? {
Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A),
Either::B(arithmetic) => arithmetic.map(Either::B),