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
18 changes: 18 additions & 0 deletions crates/vm/src/builtins/bool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
24 changes: 23 additions & 1 deletion crates/vm/src/builtins/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions crates/vm/src/builtins/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
18 changes: 18 additions & 0 deletions crates/vm/src/builtins/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
15 changes: 15 additions & 0 deletions crates/vm/src/builtins/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
40 changes: 40 additions & 0 deletions crates/vm/src/builtins/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
17 changes: 17 additions & 0 deletions crates/vm/src/builtins/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
20 changes: 20 additions & 0 deletions crates/vm/src/builtins/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
24 changes: 24 additions & 0 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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
Expand Down
39 changes: 17 additions & 22 deletions crates/vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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)
}
}

Expand Down
8 changes: 7 additions & 1 deletion crates/vm/src/types/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
23 changes: 21 additions & 2 deletions crates/vm/src/types/slot_defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
Loading