mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Add per-type vectorcall for builtin constructors (#7407)
Add vectorcall fast paths for dict, list, set, int, float, str, bool, tuple, frozenset. Clear vectorcall when __init__/__new__ is overridden in Python. Prevent constructor vectorcall inheritance to heap subclasses. Fix stat_result to use struct_sequence_new with reference-copy for hidden time fields.
This commit is contained in:
1 parent
0768cf80d3
commit
62edc4722a
12 files changed
+233
-26
No files matched your search
@@ -182,8 +182,26 @@ impl Representable for PyBool {
|
||||
}
|
||||
}
|
||||
|
||||
fn vectorcall_bool(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
(zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
|
||||
}
|
||||
|
||||
pub(crate) fn init(context: &'static Context) {
|
||||
PyBool::extend_class(context, context.types.bool_type);
|
||||
context
|
||||
.types
|
||||
.bool_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_bool));
|
||||
}
|
||||
|
||||
// pub fn not(vm: &VirtualMachine, obj: &PyObject) -> PyResult<bool> {
|
||||
|
||||
@@ -15,7 +15,9 @@ use crate::{
|
||||
class::{PyClassDef, PyClassImpl},
|
||||
common::ascii,
|
||||
dict_inner::{self, DictKey},
|
||||
function::{ArgIterable, KwArgs, OptionalArg, PyArithmeticValue::*, PyComparisonValue},
|
||||
function::{
|
||||
ArgIterable, FuncArgs, KwArgs, OptionalArg, PyArithmeticValue::*, PyComparisonValue,
|
||||
},
|
||||
iter::PyExactSizeIterator,
|
||||
protocol::{PyIterIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
|
||||
recursion::ReprGuard,
|
||||
@@ -1433,8 +1435,28 @@ fn set_inner_number_or(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyRes
|
||||
set_inner_number_op(a, b, |a, b| a.union(b, vm), vm)
|
||||
}
|
||||
|
||||
fn vectorcall_dict(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let obj = PyDict::default().into_ref_with_type(vm, zelf.to_owned())?;
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
PyDict::slot_init(obj.clone().into(), func_args, vm)?;
|
||||
Ok(obj.into())
|
||||
}
|
||||
|
||||
pub(crate) fn init(context: &'static Context) {
|
||||
PyDict::extend_class(context, context.types.dict_type);
|
||||
context
|
||||
.types
|
||||
.dict_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_dict));
|
||||
PyDictKeys::extend_class(context, context.types.dict_keys_type);
|
||||
PyDictKeyIterator::extend_class(context, context.types.dict_keyiterator_type);
|
||||
PyDictReverseKeyIterator::extend_class(context, context.types.dict_reversekeyiterator_type);
|
||||
|
||||
@@ -525,7 +525,20 @@ pub(crate) fn get_value(obj: &PyObject) -> f64 {
|
||||
obj.downcast_ref::<PyFloat>().unwrap().value
|
||||
}
|
||||
|
||||
fn vectorcall_float(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
(zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
|
||||
}
|
||||
|
||||
#[rustfmt::skip] // to avoid line splitting
|
||||
pub fn init(context: &'static Context) {
|
||||
PyFloat::extend_class(context, context.types.float_type);
|
||||
context.types.float_type.slots.vectorcall.store(Some(vectorcall_float));
|
||||
}
|
||||
@@ -791,6 +791,24 @@ pub fn try_to_float(int: &BigInt, vm: &VirtualMachine) -> PyResult<f64> {
|
||||
.ok_or_else(|| vm.new_overflow_error("int too large to convert to float"))
|
||||
}
|
||||
|
||||
fn vectorcall_int(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
(zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
|
||||
}
|
||||
|
||||
pub(crate) fn init(context: &'static Context) {
|
||||
PyInt::extend_class(context, context.types.int_type);
|
||||
context
|
||||
.types
|
||||
.int_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_int));
|
||||
}
|
||||
@@ -768,9 +768,24 @@ impl IterNext for PyListReverseIterator {
|
||||
}
|
||||
}
|
||||
|
||||
fn vectorcall_list(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let obj = PyList::default().into_ref_with_type(vm, zelf.to_owned())?;
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
PyList::slot_init(obj.clone().into(), func_args, vm)?;
|
||||
Ok(obj.into())
|
||||
}
|
||||
|
||||
pub fn init(context: &'static Context) {
|
||||
let list_type = &context.types.list_type;
|
||||
PyList::extend_class(context, list_type);
|
||||
list_type.slots.vectorcall.store(Some(vectorcall_list));
|
||||
|
||||
PyListIterator::extend_class(context, context.types.list_iterator_type);
|
||||
PyListReverseIterator::extend_class(context, context.types.list_reverseiterator_type);
|
||||
|
||||
@@ -1421,8 +1421,48 @@ impl IterNext for PySetIterator {
|
||||
}
|
||||
}
|
||||
|
||||
fn vectorcall_set(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let obj = PySet::default().into_ref_with_type(vm, zelf.to_owned())?;
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
PySet::slot_init(obj.clone().into(), func_args, vm)?;
|
||||
Ok(obj.into())
|
||||
}
|
||||
|
||||
fn vectorcall_frozenset(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
(zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
|
||||
}
|
||||
|
||||
pub fn init(context: &'static Context) {
|
||||
PySet::extend_class(context, context.types.set_type);
|
||||
context
|
||||
.types
|
||||
.set_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_set));
|
||||
|
||||
PyFrozenSet::extend_class(context, context.types.frozenset_type);
|
||||
context
|
||||
.types
|
||||
.frozenset_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_frozenset));
|
||||
|
||||
PySetIterator::extend_class(context, context.types.set_iterator_type);
|
||||
}
|
||||
@@ -1781,8 +1781,25 @@ struct ReplaceArgs {
|
||||
count: isize,
|
||||
}
|
||||
|
||||
fn vectorcall_str(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
(zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
|
||||
}
|
||||
|
||||
pub fn init(ctx: &'static Context) {
|
||||
PyStr::extend_class(ctx, ctx.types.str_type);
|
||||
ctx.types
|
||||
.str_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_str));
|
||||
|
||||
PyStrIterator::extend_class(ctx, ctx.types.str_iterator_type);
|
||||
}
|
||||
|
||||
@@ -695,9 +695,29 @@ impl IterNext for PyTupleIterator {
|
||||
}
|
||||
}
|
||||
|
||||
fn vectorcall_tuple(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
nargs: usize,
|
||||
kwnames: Option<&[PyObjectRef]>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
|
||||
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
|
||||
// Use the type's own slot_new rather than calling PyTuple::slot_new directly,
|
||||
// so Rust-level subclasses (e.g. struct sequences) get their custom slot_new called.
|
||||
(zelf.slots.new.load().unwrap())(zelf.to_owned(), func_args, vm)
|
||||
}
|
||||
|
||||
pub(crate) fn init(context: &'static Context) {
|
||||
PyTuple::extend_class(context, context.types.tuple_type);
|
||||
PyTupleIterator::extend_class(context, context.types.tuple_iterator_type);
|
||||
context
|
||||
.types
|
||||
.tuple_type
|
||||
.slots
|
||||
.vectorcall
|
||||
.store(Some(vectorcall_tuple));
|
||||
}
|
||||
|
||||
pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<PyHash> {
|
||||
|
||||
@@ -2650,6 +2650,24 @@ fn subtype_set_weakref(obj: PyObjectRef, _value: PyObjectRef, vm: &VirtualMachin
|
||||
|
||||
/// Vectorcall for PyType (PEP 590).
|
||||
/// Fast path: type(x) returns x.__class__ without constructing FuncArgs.
|
||||
///
|
||||
/// # Implementation note: `slots.vectorcall` dual use
|
||||
///
|
||||
/// CPython has three distinct fields on PyTypeObject:
|
||||
/// - `tp_vectorcall`: constructor fast path (e.g. `list_vectorcall`)
|
||||
/// - `tp_vectorcall_offset`: per-instance vectorcall for callables (e.g. functions)
|
||||
/// - `tp_call`: standard call slot
|
||||
///
|
||||
/// RustPython collapses the first two into a single `slots.vectorcall`. The
|
||||
/// `call.is_none()` guard below distinguishes the two uses: callable types have
|
||||
/// `slots.call` set, so their `slots.vectorcall` is for calling instances,
|
||||
/// not for construction.
|
||||
///
|
||||
/// This heuristic is correct for all current builtins but is not a general
|
||||
/// solution — `type` itself is both callable and has a constructor vectorcall,
|
||||
/// handled by the explicit `zelf.is(type_type)` check above the guard.
|
||||
/// If more such types arise, consider splitting into a dedicated
|
||||
/// `constructor_vectorcall` slot.
|
||||
fn vectorcall_type(
|
||||
zelf_obj: &PyObject,
|
||||
args: Vec<PyObjectRef>,
|
||||
@@ -2665,6 +2683,12 @@ fn vectorcall_type(
|
||||
if nargs == 1 && no_kwargs {
|
||||
return Ok(args[0].obj_type());
|
||||
}
|
||||
} else if zelf.slots.call.load().is_none() && zelf.slots.new.load().is_some() {
|
||||
// Per-type constructor vectorcall for non-callable types (dict, list, int, etc.)
|
||||
// Also guard on slots.new to avoid dispatching for DISALLOW_INSTANTIATION types.
|
||||
if let Some(type_vc) = zelf.slots.vectorcall.load() {
|
||||
return type_vc(zelf_obj, args, nargs, kwnames, vm);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: construct FuncArgs and use standard call
|
||||
|
||||
+17
-22
@@ -178,7 +178,6 @@ pub(super) mod _os {
|
||||
};
|
||||
use core::time::Duration;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use itertools::Itertools;
|
||||
use rustpython_common::wtf8::Wtf8Buf;
|
||||
use std::{env, fs, fs::OpenOptions, io, path::PathBuf, time::SystemTime};
|
||||
|
||||
@@ -1363,29 +1362,25 @@ pub(super) mod _os {
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PyStatResult {
|
||||
#[pyslot]
|
||||
fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let flatten_args = |r: &[PyObjectRef]| {
|
||||
let mut vec_args = Vec::from(r);
|
||||
loop {
|
||||
if let Ok(obj) = vec_args.iter().exactly_one() {
|
||||
match obj.downcast_ref::<PyTuple>() {
|
||||
Some(t) => {
|
||||
vec_args = Vec::from(t.as_slice());
|
||||
}
|
||||
None => {
|
||||
return vec_args;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return vec_args;
|
||||
}
|
||||
fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let seq: PyObjectRef = args.bind(vm)?;
|
||||
let result = crate::types::struct_sequence_new(cls.clone(), seq, vm)?;
|
||||
let tuple = result.downcast_ref::<PyTuple>().unwrap();
|
||||
let mut items: Vec<PyObjectRef> = tuple.to_vec();
|
||||
|
||||
// Copy integer time fields to hidden float timestamp slots when not provided.
|
||||
// indices 7-9: st_atime_int, st_mtime_int, st_ctime_int
|
||||
// i+3: st_atime/st_mtime/st_ctime (float timestamps, copied from int if missing)
|
||||
// i+6: st_atime_ns/st_mtime_ns/st_ctime_ns (left as None if not provided)
|
||||
for i in 7..=9 {
|
||||
if vm.is_none(&items[i + 3]) {
|
||||
items[i + 3] = items[i].clone();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let args: FuncArgs = flatten_args(&args.args).into();
|
||||
|
||||
let stat: StatResultData = args.bind(vm)?;
|
||||
Ok(stat.to_pyobject(vm))
|
||||
PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
.into_ref_with_type(vm, cls)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -825,11 +825,17 @@ impl PyType {
|
||||
}
|
||||
SlotAccessor::TpIter => update_main_slot!(iter, iter_wrapper, Iter),
|
||||
SlotAccessor::TpIternext => update_main_slot!(iternext, iternext_wrapper, IterNext),
|
||||
SlotAccessor::TpInit => update_main_slot!(init, init_wrapper, Init),
|
||||
SlotAccessor::TpInit => {
|
||||
update_main_slot!(init, init_wrapper, Init);
|
||||
if ADD {
|
||||
self.slots.vectorcall.store(None);
|
||||
}
|
||||
}
|
||||
SlotAccessor::TpNew => {
|
||||
// __new__ is not wrapped via PyWrapper
|
||||
if ADD {
|
||||
self.slots.new.store(Some(new_wrapper));
|
||||
self.slots.vectorcall.store(None);
|
||||
} else {
|
||||
accessor.inherit_from_mro(self);
|
||||
}
|
||||
|
||||
@@ -453,7 +453,20 @@ impl SlotAccessor {
|
||||
Self::TpStr => inherit_main!(str),
|
||||
Self::TpCall => {
|
||||
inherit_main!(call);
|
||||
inherit_main!(vectorcall);
|
||||
// CPython does not inherit tp_vectorcall to heap types at all; it only
|
||||
// inherits tp_vectorcall_offset (the per-instance callable fast path).
|
||||
// RustPython approximates this by inheriting vectorcall only from types
|
||||
// with a call slot (instance-call vectorcall), not from types that use
|
||||
// vectorcall as a constructor fast path (call=None).
|
||||
// See vectorcall_type() in type.rs for the dual-use design rationale.
|
||||
let inherited_vc = mro.iter().find_map(|cls| {
|
||||
if cls.slots.call.load().is_some() {
|
||||
cls.slots.vectorcall.load()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
typ.slots.vectorcall.store(inherited_vc);
|
||||
}
|
||||
Self::TpIter => inherit_main!(iter),
|
||||
Self::TpIternext => inherit_main!(iternext),
|
||||
@@ -573,7 +586,13 @@ impl SlotAccessor {
|
||||
Self::TpStr => copy_main!(str),
|
||||
Self::TpCall => {
|
||||
copy_main!(call);
|
||||
copy_main!(vectorcall);
|
||||
// See inherit_from_mro TpCall for rationale.
|
||||
if typ.slots.vectorcall.load().is_none()
|
||||
&& base.slots.call.load().is_some()
|
||||
&& let Some(base_val) = base.slots.vectorcall.load()
|
||||
{
|
||||
typ.slots.vectorcall.store(Some(base_val));
|
||||
}
|
||||
}
|
||||
Self::TpIter => copy_main!(iter),
|
||||
Self::TpIternext => copy_main!(iternext),
|
||||
|
||||
Reference in new issue
Block a user