mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Tighten specialization guards and add send_none fastpath (#7359)
* vm: align specialization guards with CPython patterns * vm: tighten call specialization runtime guards * vm: add send_none fastpath for generator specialization * vm: restrict method-descriptor specialization to methods * vm: deopt call specializations on guard misses * vm: match CPython send/for-iter closed-frame guards * vm: restrict len/isinstance specialization to builtins * vm: use exact-type guards for call specializations * vm: align class-call specialization flow with CPython * vm: prefer FAST call opcodes for positional builtin calls * vm: add callable identity guard to CALL_LIST_APPEND * vm: make CALL_LIST_APPEND runtime guard pointer-based * vm: align call guard cache and fallback behavior with CPython * vm: use base vectorcall fallback for EXIT-style call misses * vm: simplify CALL_LEN/CALL_ISINSTANCE runtime guards * vm: infer call-convention flags for CPython-style CALL specialization * vm: check use_tracing in eval_frame_active, add SendGen send_none - Implement specialization_eval_frame_active to check vm.use_tracing so specializations are skipped when tracing/profiling is active - Add send_none fastpath in SendGen handler for the common None case
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use super::Diagnostic;
|
||||
use crate::util::{
|
||||
ALL_ALLOWED_NAMES, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta,
|
||||
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, format_doc, pyclass_ident_and_attrs,
|
||||
pyexception_ident_and_attrs, text_signature,
|
||||
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, format_doc, infer_native_call_flags,
|
||||
pyclass_ident_and_attrs, pyexception_ident_and_attrs, text_signature,
|
||||
};
|
||||
use core::str::FromStr;
|
||||
use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
|
||||
@@ -1015,6 +1015,16 @@ where
|
||||
|
||||
let raw = item_meta.raw()?;
|
||||
let sig_doc = text_signature(func.sig(), &py_name);
|
||||
let has_receiver = func
|
||||
.sig()
|
||||
.inputs
|
||||
.iter()
|
||||
.any(|arg| matches!(arg, syn::FnArg::Receiver(_)));
|
||||
let drop_first_typed = match self.inner.attr_name {
|
||||
AttrName::Method | AttrName::ClassMethod if !has_receiver => 1,
|
||||
_ => 0,
|
||||
};
|
||||
let call_flags = infer_native_call_flags(func.sig(), drop_first_typed);
|
||||
|
||||
// Add #[allow(non_snake_case)] for setter methods like set___name__
|
||||
let method_name = ident.to_string();
|
||||
@@ -1031,6 +1041,7 @@ where
|
||||
doc,
|
||||
raw,
|
||||
attr_name: self.inner.attr_name,
|
||||
call_flags,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -1248,6 +1259,7 @@ struct MethodNurseryItem {
|
||||
raw: bool,
|
||||
doc: Option<String>,
|
||||
attr_name: AttrName,
|
||||
call_flags: TokenStream,
|
||||
}
|
||||
|
||||
impl MethodNursery {
|
||||
@@ -1278,7 +1290,7 @@ impl ToTokens for MethodNursery {
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
let flags = match &item.attr_name {
|
||||
let binding_flags = match &item.attr_name {
|
||||
AttrName::Method => {
|
||||
quote! { rustpython_vm::function::PyMethodFlags::METHOD }
|
||||
}
|
||||
@@ -1290,6 +1302,12 @@ impl ToTokens for MethodNursery {
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let call_flags = &item.call_flags;
|
||||
let flags = quote! {
|
||||
rustpython_vm::function::PyMethodFlags::from_bits_retain(
|
||||
(#binding_flags).bits() | (#call_flags).bits()
|
||||
)
|
||||
};
|
||||
// TODO: intern
|
||||
// let py_name = if py_name.starts_with("__") && py_name.ends_with("__") {
|
||||
// let name_ident = Ident::new(&py_name, ident.span());
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::error::Diagnostic;
|
||||
use crate::pystructseq::PyStructSequenceMeta;
|
||||
use crate::util::{
|
||||
ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner,
|
||||
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc, iter_use_idents,
|
||||
pyclass_ident_and_attrs, text_signature,
|
||||
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, format_doc,
|
||||
infer_native_call_flags, iter_use_idents, pyclass_ident_and_attrs, text_signature,
|
||||
};
|
||||
use core::str::FromStr;
|
||||
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
|
||||
@@ -525,6 +525,7 @@ struct FunctionNurseryItem {
|
||||
cfgs: Vec<Attribute>,
|
||||
ident: Ident,
|
||||
doc: String,
|
||||
call_flags: TokenStream,
|
||||
}
|
||||
|
||||
impl FunctionNursery {
|
||||
@@ -550,7 +551,6 @@ struct ValidatedFunctionNursery(FunctionNursery);
|
||||
impl ToTokens for ValidatedFunctionNursery {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mut inner_tokens = TokenStream::new();
|
||||
let flags = quote! { rustpython_vm::function::PyMethodFlags::empty() };
|
||||
for item in &self.0.items {
|
||||
let ident = &item.ident;
|
||||
let cfgs = &item.cfgs;
|
||||
@@ -558,6 +558,7 @@ impl ToTokens for ValidatedFunctionNursery {
|
||||
let py_names = &item.py_names;
|
||||
let doc = &item.doc;
|
||||
let doc = quote!(Some(#doc));
|
||||
let flags = &item.call_flags;
|
||||
|
||||
inner_tokens.extend(quote![
|
||||
#(
|
||||
@@ -706,12 +707,14 @@ impl ModuleItem for FunctionItem {
|
||||
py_names
|
||||
}
|
||||
};
|
||||
let call_flags = infer_native_call_flags(func.sig(), 0);
|
||||
|
||||
args.context.function_items.add_item(FunctionNurseryItem {
|
||||
ident: ident.to_owned(),
|
||||
py_names,
|
||||
cfgs: args.cfgs.to_vec(),
|
||||
doc,
|
||||
call_flags,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -732,6 +732,77 @@ pub(crate) fn text_signature(sig: &Signature, name: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn infer_native_call_flags(sig: &Signature, drop_first_typed: usize) -> TokenStream {
|
||||
// Best-effort mapping of Rust function signatures to CPython-style
|
||||
// METH_* calling convention flags used by CALL specialization.
|
||||
let mut typed_args = Vec::new();
|
||||
for arg in &sig.inputs {
|
||||
let syn::FnArg::Typed(typed) = arg else {
|
||||
continue;
|
||||
};
|
||||
let ty_tokens = &typed.ty;
|
||||
let ty = quote!(#ty_tokens).to_string().replace(' ', "");
|
||||
// `vm: &VirtualMachine` is not a Python-level argument.
|
||||
if ty.starts_with('&') && ty.ends_with("VirtualMachine") {
|
||||
continue;
|
||||
}
|
||||
typed_args.push(ty);
|
||||
}
|
||||
|
||||
let mut user_args = typed_args.into_iter();
|
||||
for _ in 0..drop_first_typed {
|
||||
if user_args.next().is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut has_keywords = false;
|
||||
let mut variable_arity = false;
|
||||
let mut fixed_positional = 0usize;
|
||||
|
||||
for ty in user_args {
|
||||
let is_named = |name: &str| {
|
||||
ty == name
|
||||
|| ty.starts_with(&format!("{name}<"))
|
||||
|| ty.contains(&format!("::{name}<"))
|
||||
|| ty.ends_with(&format!("::{name}"))
|
||||
};
|
||||
|
||||
if is_named("FuncArgs") {
|
||||
has_keywords = true;
|
||||
variable_arity = true;
|
||||
continue;
|
||||
}
|
||||
if is_named("KwArgs") {
|
||||
has_keywords = true;
|
||||
variable_arity = true;
|
||||
continue;
|
||||
}
|
||||
if is_named("PosArgs") || is_named("OptionalArg") || is_named("OptionalOption") {
|
||||
variable_arity = true;
|
||||
continue;
|
||||
}
|
||||
fixed_positional += 1;
|
||||
}
|
||||
|
||||
if has_keywords {
|
||||
quote! {
|
||||
rustpython_vm::function::PyMethodFlags::from_bits_retain(
|
||||
rustpython_vm::function::PyMethodFlags::FASTCALL.bits()
|
||||
| rustpython_vm::function::PyMethodFlags::KEYWORDS.bits()
|
||||
)
|
||||
}
|
||||
} else if variable_arity {
|
||||
quote! { rustpython_vm::function::PyMethodFlags::FASTCALL }
|
||||
} else {
|
||||
match fixed_positional {
|
||||
0 => quote! { rustpython_vm::function::PyMethodFlags::NOARGS },
|
||||
1 => quote! { rustpython_vm::function::PyMethodFlags::O },
|
||||
_ => quote! { rustpython_vm::function::PyMethodFlags::FASTCALL },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn func_sig(sig: &Signature) -> String {
|
||||
sig.inputs
|
||||
.iter()
|
||||
|
||||
@@ -115,6 +115,48 @@ impl Coro {
|
||||
result
|
||||
}
|
||||
|
||||
fn finalize_send_result(
|
||||
&self,
|
||||
jen: &PyObject,
|
||||
result: PyResult<ExecutionResult>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyIterReturn> {
|
||||
self.maybe_close(&result);
|
||||
match result {
|
||||
Ok(exec_res) => Ok(exec_res.into_iter_return(vm)),
|
||||
Err(e) => {
|
||||
if e.fast_isinstance(vm.ctx.exceptions.stop_iteration) {
|
||||
let err =
|
||||
vm.new_runtime_error(format!("{} raised StopIteration", gen_name(jen, vm)));
|
||||
err.set___cause__(Some(e));
|
||||
Err(err)
|
||||
} else if jen.class().is(vm.ctx.types.async_generator)
|
||||
&& e.fast_isinstance(vm.ctx.exceptions.stop_async_iteration)
|
||||
{
|
||||
let err = vm.new_runtime_error("async generator raised StopAsyncIteration");
|
||||
err.set___cause__(Some(e));
|
||||
Err(err)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send_none(&self, jen: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
|
||||
if self.closed.load() {
|
||||
return Ok(PyIterReturn::StopIteration(None));
|
||||
}
|
||||
self.frame.locals_to_fast(vm)?;
|
||||
let value = if self.frame.lasti() > 0 {
|
||||
Some(vm.ctx.none())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let result = self.run_with_context(jen, vm, |f| f.resume(value, vm));
|
||||
self.finalize_send_result(jen, result, vm)
|
||||
}
|
||||
|
||||
pub fn send(
|
||||
&self,
|
||||
jen: &PyObject,
|
||||
@@ -136,26 +178,7 @@ impl Coro {
|
||||
None
|
||||
};
|
||||
let result = self.run_with_context(jen, vm, |f| f.resume(value, vm));
|
||||
self.maybe_close(&result);
|
||||
match result {
|
||||
Ok(exec_res) => Ok(exec_res.into_iter_return(vm)),
|
||||
Err(e) => {
|
||||
if e.fast_isinstance(vm.ctx.exceptions.stop_iteration) {
|
||||
let err =
|
||||
vm.new_runtime_error(format!("{} raised StopIteration", gen_name(jen, vm)));
|
||||
err.set___cause__(Some(e));
|
||||
Err(err)
|
||||
} else if jen.class().is(vm.ctx.types.async_generator)
|
||||
&& e.fast_isinstance(vm.ctx.exceptions.stop_async_iteration)
|
||||
{
|
||||
let err = vm.new_runtime_error("async generator raised StopAsyncIteration");
|
||||
err.set___cause__(Some(e));
|
||||
Err(err)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.finalize_send_result(jen, result, vm)
|
||||
}
|
||||
|
||||
pub fn throw(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,11 +12,11 @@ bitflags::bitflags! {
|
||||
// METH_XXX flags in CPython
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct PyMethodFlags: u32 {
|
||||
// const VARARGS = 0x0001;
|
||||
// const KEYWORDS = 0x0002;
|
||||
const VARARGS = 0x0001;
|
||||
const KEYWORDS = 0x0002;
|
||||
// METH_NOARGS and METH_O must not be combined with the flags above.
|
||||
// const NOARGS = 0x0004;
|
||||
// const O = 0x0008;
|
||||
const NOARGS = 0x0004;
|
||||
const O = 0x0008;
|
||||
|
||||
// METH_CLASS and METH_STATIC are a little different; these control
|
||||
// the construction of methods for a class. These cannot be used for
|
||||
@@ -31,7 +31,7 @@ bitflags::bitflags! {
|
||||
// const COEXIST = 0x0040;
|
||||
|
||||
// if not Py_LIMITED_API
|
||||
// const FASTCALL = 0x0080;
|
||||
const FASTCALL = 0x0080;
|
||||
|
||||
// This bit is preserved for Stackless Python
|
||||
// const STACKLESS = 0x0100;
|
||||
|
||||
Reference in New Issue
Block a user