From 635b4afff1ca1ff4e488995e8a04f24cbbc9e19a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 13 Jul 2025 12:56:59 +0900 Subject: [PATCH 1/4] Fix derive(Traverse) --- derive-impl/src/pytraverse.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/derive-impl/src/pytraverse.rs b/derive-impl/src/pytraverse.rs index 728722b83ac..c5c4bbd2704 100644 --- a/derive-impl/src/pytraverse.rs +++ b/derive-impl/src/pytraverse.rs @@ -105,8 +105,19 @@ pub(crate) fn impl_pytraverse(mut item: DeriveInput) -> Result { let ty = &item.ident; + // Add Traverse bound to all type parameters + for param in &mut item.generics.params { + if let syn::GenericParam::Type(type_param) = param { + type_param + .bounds + .push(syn::parse_quote!(::rustpython_vm::object::Traverse)); + } + } + + let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl(); + let ret = quote! { - unsafe impl ::rustpython_vm::object::Traverse for #ty { + unsafe impl #impl_generics ::rustpython_vm::object::Traverse for #ty #ty_generics #where_clause { fn traverse(&self, tracer_fn: &mut ::rustpython_vm::object::TraverseFn) { #trace_code } From 09489712e6d801722e9c1cb30a99701da15009ae Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 13 Jul 2025 14:34:46 +0900 Subject: [PATCH 2/4] PyPayload::payload_type_of --- vm/src/object/core.rs | 2 +- vm/src/object/payload.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs index bb057f4906f..b4b9557f2ae 100644 --- a/vm/src/object/core.rs +++ b/vm/src/object/core.rs @@ -655,7 +655,7 @@ impl PyObject { #[inline(always)] pub fn payload_is(&self) -> bool { - self.0.typeid == TypeId::of::() + self.0.typeid == T::payload_type_id() } /// Force to return payload as T. diff --git a/vm/src/object/payload.rs b/vm/src/object/payload.rs index 6413d6ae062..f223af6e968 100644 --- a/vm/src/object/payload.rs +++ b/vm/src/object/payload.rs @@ -19,6 +19,10 @@ cfg_if::cfg_if! { pub trait PyPayload: std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + Sized + 'static { + #[inline] + fn payload_type_id() -> std::any::TypeId { + std::any::TypeId::of::() + } fn class(ctx: &Context) -> &'static Py; #[inline] @@ -75,7 +79,7 @@ pub trait PyPayload: } pub trait PyObjectPayload: - std::any::Any + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static + PyPayload + std::any::Any + std::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static { } From 14ce76e6c81b59191f14f811621ada0bba599cbb Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 13 Jul 2025 14:39:42 +0900 Subject: [PATCH 3/4] PyTupleTyped as alias of PyTuple --- vm/src/builtins/function.rs | 15 ++-- vm/src/builtins/tuple.rs | 120 ++++++++++++++++++------------- vm/src/builtins/type.rs | 22 +++--- vm/src/convert/try_from.rs | 8 +-- vm/src/frame.rs | 14 ++-- vm/src/object/core.rs | 44 +++++------- vm/src/object/traverse_object.rs | 5 +- vm/src/vm/context.rs | 7 ++ vm/src/vm/mod.rs | 8 +-- 9 files changed, 134 insertions(+), 109 deletions(-) diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index a29a077e509..45917adcf23 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -8,7 +8,6 @@ use super::{ #[cfg(feature = "jit")] use crate::common::lock::OnceCell; use crate::common::lock::PyMutex; -use crate::convert::{ToPyObject, TryFromObject}; use crate::function::ArgMapping; use crate::object::{Traverse, TraverseFn}; use crate::{ @@ -32,7 +31,7 @@ pub struct PyFunction { code: PyRef, globals: PyDictRef, builtins: PyObjectRef, - closure: Option>, + closure: Option>>, defaults_and_kwdefaults: PyMutex<(Option, Option)>, name: PyMutex, qualname: PyMutex, @@ -47,7 +46,9 @@ pub struct PyFunction { unsafe impl Traverse for PyFunction { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { self.globals.traverse(tracer_fn); - self.closure.traverse(tracer_fn); + if let Some(closure) = self.closure.as_ref() { + closure.as_untyped().traverse(tracer_fn); + } self.defaults_and_kwdefaults.traverse(tracer_fn); } } @@ -58,7 +59,7 @@ impl PyFunction { pub(crate) fn new( code: PyRef, globals: PyDictRef, - closure: Option>, + closure: Option>>, defaults: Option, kw_only_defaults: Option, qualname: PyStrRef, @@ -326,6 +327,7 @@ impl Py { ) -> PyResult { #[cfg(feature = "jit")] if let Some(jitted_code) = self.jitted_code.get() { + use crate::convert::ToPyObject; match jit::get_jit_args(self, &func_args, jitted_code, vm) { Ok(args) => { return Ok(args.invoke().to_pyobject(vm)); @@ -427,7 +429,7 @@ impl PyFunction { #[pymember] fn __closure__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult { let zelf = Self::_as_pyref(&zelf, vm)?; - Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.to_pyobject(vm)))) + Ok(vm.unwrap_or_none(zelf.closure.clone().map(|x| x.into()))) } #[pymember] @@ -612,8 +614,7 @@ impl Constructor for PyFunction { } // Validate that all items are cells and create typed tuple - let typed_closure = - PyTupleTyped::::try_from_object(vm, closure_tuple.into())?; + let typed_closure = closure_tuple.try_into_typed::(vm)?; Some(typed_closure) } else if !args.code.freevars.is_empty() { return Err(vm.new_type_error("arg 5 (closure) must be tuple")); diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 2ee8497dda5..9f589547f0c 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -3,7 +3,7 @@ use crate::common::{ hash::{PyHash, PyUHash}, lock::PyMutex, }; -use crate::object::{Traverse, TraverseFn}; +use crate::object::{MaybeTraverse, Traverse, TraverseFn}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, atomic_func, @@ -449,6 +449,24 @@ impl Representable for PyTuple { } } +impl PyRef { + pub fn try_into_typed( + self, + vm: &VirtualMachine, + ) -> PyResult>>> { + PyRef::>>::try_from_untyped(self, vm) + } + /// # Safety + /// + /// The caller must ensure that all elements in the tuple are valid instances + /// of type `T` before calling this method. This is typically verified by + /// calling `try_into_typed` first. + unsafe fn into_typed_unchecked(self) -> PyRef>> { + let obj: PyObjectRef = self.into(); + unsafe { obj.downcast_unchecked::>>() } + } +} + #[pyclass(module = false, name = "tuple_iterator", traverse)] #[derive(Debug)] pub(crate) struct PyTupleIterator { @@ -500,53 +518,75 @@ pub(crate) fn init(context: &Context) { PyTupleIterator::extend_class(context, context.types.tuple_iterator_type); } -pub struct PyTupleTyped { +#[repr(transparent)] +pub struct PyTupleTyped { // SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the // elements must be logically valid when transmuted to T - tuple: PyTupleRef, - _marker: PhantomData>, + tuple: PyTuple, + _marker: PhantomData, } -unsafe impl Traverse for PyTupleTyped +unsafe impl Traverse for PyTupleTyped where - T: TransmuteFromObject + Traverse, + R: TransmuteFromObject, { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { self.tuple.traverse(tracer_fn); } } -impl TryFromObject for PyTupleTyped { - fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let tuple = PyTupleRef::try_from_object(vm, obj)?; - for elem in &*tuple { - T::check(vm, elem)? - } - // SAFETY: the contract of TransmuteFromObject upholds the variant on `tuple` - Ok(Self { - tuple, - _marker: PhantomData, - }) +impl MaybeTraverse for PyTupleTyped { + const IS_TRACE: bool = true; + fn try_traverse(&self, tracer_fn: &mut TraverseFn<'_>) { + self.traverse(tracer_fn); } } -impl AsRef<[T]> for PyTupleTyped { - fn as_ref(&self) -> &[T] { - self.as_slice() +impl PyTupleTyped> { + pub fn new_ref(elements: Vec>, ctx: &Context) -> PyRef { + // SAFETY: PyRef has the same layout as PyObjectRef + unsafe { + let elements: Vec = + std::mem::transmute::>, Vec>(elements); + let tuple = PyTuple::new_ref(elements, ctx); + tuple.into_typed_unchecked::() + } } } -impl PyTupleTyped { - pub fn empty(vm: &VirtualMachine) -> Self { - Self { - tuple: vm.ctx.empty_tuple.clone(), - _marker: PhantomData, +impl PyRef>> { + pub fn into_untyped(self) -> PyRef { + // SAFETY: PyTupleTyped is transparent over PyTuple + unsafe { std::mem::transmute::>>, PyRef>(self) } + } + + pub fn try_from_untyped(tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult { + // Check that all elements are of the correct type + for elem in tuple.as_slice() { + as TransmuteFromObject>::check(vm, elem)?; } + // SAFETY: We just verified all elements are of type T, and PyTupleTyped has the same layout as PyTuple + Ok(unsafe { std::mem::transmute::, PyRef>>>(tuple) }) } +} +impl Py>> { + pub fn as_untyped(&self) -> &Py { + // SAFETY: PyTupleTyped is transparent over PyTuple + unsafe { std::mem::transmute::<&Py>>, &Py>(self) } + } +} + +impl AsRef<[PyRef]> for PyTupleTyped> { + fn as_ref(&self) -> &[PyRef] { + self.as_slice() + } +} + +impl PyTupleTyped> { #[inline] - pub fn as_slice(&self) -> &[T] { - unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [T]) } + pub fn as_slice(&self) -> &[PyRef] { + unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [PyRef]) } } #[inline] @@ -560,32 +600,16 @@ impl PyTupleTyped { } } -impl Clone for PyTupleTyped { - fn clone(&self) -> Self { - Self { - tuple: self.tuple.clone(), - _marker: PhantomData, - } - } -} - -impl fmt::Debug for PyTupleTyped { +impl fmt::Debug for PyTupleTyped { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_slice().fmt(f) - } -} - -impl From> for PyTupleRef { - #[inline] - fn from(tup: PyTupleTyped) -> Self { - tup.tuple + self.tuple.as_slice().fmt(f) } } -impl ToPyObject for PyTupleTyped { +impl From>>> for PyTupleRef { #[inline] - fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { - self.tuple.into() + fn from(tup: PyRef>>) -> Self { + tup.into_untyped() } } diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 1e18d6fd631..5a8f853bf1f 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -62,7 +62,7 @@ unsafe impl crate::object::Traverse for PyType { pub struct HeapTypeExt { pub name: PyRwLock, pub qualname: PyRwLock, - pub slots: Option>, + pub slots: Option>>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, } @@ -1041,15 +1041,13 @@ impl Constructor for PyType { // TODO: Flags is currently initialized with HAS_DICT. Should be // updated when __slots__ are supported (toggling the flag off if // a class has __slots__ defined). - let heaptype_slots: Option> = + let heaptype_slots: Option>> = if let Some(x) = attributes.get(identifier!(vm, __slots__)) { - Some(if x.to_owned().class().is(vm.ctx.types.str_type) { - PyTupleTyped::::try_from_object( - vm, - vec![x.to_owned()].into_pytuple(vm).into(), - )? + let slots = if x.class().is(vm.ctx.types.str_type) { + let x = unsafe { x.downcast_unchecked_ref::() }; + PyTupleTyped::new_ref(vec![x.to_owned()], &vm.ctx) } else { - let iter = x.to_owned().get_iter(vm)?; + let iter = x.get_iter(vm)?; let elements = { let mut elements = Vec::new(); while let PyIterReturn::Return(element) = iter.next(vm)? { @@ -1057,8 +1055,10 @@ impl Constructor for PyType { } elements }; - PyTupleTyped::::try_from_object(vm, elements.into_pytuple(vm).into())? - }) + let tuple = elements.into_pytuple(vm); + tuple.try_into_typed(vm)? + }; + Some(slots) } else { None }; @@ -1082,7 +1082,7 @@ impl Constructor for PyType { let heaptype_ext = HeapTypeExt { name: PyRwLock::new(name), qualname: PyRwLock::new(qualname), - slots: heaptype_slots.to_owned(), + slots: heaptype_slots.clone(), sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), }; diff --git a/vm/src/convert/try_from.rs b/vm/src/convert/try_from.rs index d2d83b36e78..a875ffa231e 100644 --- a/vm/src/convert/try_from.rs +++ b/vm/src/convert/try_from.rs @@ -78,12 +78,12 @@ where #[inline] fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { let class = T::class(&vm.ctx); - if obj.fast_isinstance(class) { + let result = if obj.fast_isinstance(class) { obj.downcast() - .map_err(|obj| vm.new_downcast_runtime_error(class, &obj)) } else { - Err(vm.new_downcast_type_error(class, &obj)) - } + Err(obj) + }; + result.map_err(|obj| vm.new_downcast_type_error(class, &obj)) } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a3e31c5c2bb..460ba4392ef 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -7,7 +7,7 @@ use crate::{ PySlice, PyStr, PyStrInterned, PyStrRef, PyTraceback, PyType, asyncgenerator::PyAsyncGenWrappedValue, function::{PyCell, PyCellRef, PyFunction}, - tuple::{PyTuple, PyTupleRef, PyTupleTyped}, + tuple::{PyTuple, PyTupleRef}, }, bytecode, convert::{IntoObject, ToPyResult}, @@ -1346,11 +1346,14 @@ impl ExecutingFrame<'_> { #[cfg_attr(feature = "flame-it", flame("Frame"))] fn import(&mut self, vm: &VirtualMachine, module_name: Option<&Py>) -> PyResult<()> { let module_name = module_name.unwrap_or(vm.ctx.empty_str); - let from_list = >>::try_from_object(vm, self.pop_value())? - .unwrap_or_else(|| PyTupleTyped::empty(vm)); + let top = self.pop_value(); + let from_list = match >::try_from_object(vm, top)? { + Some(from_list) => from_list.try_into_typed::(vm)?, + None => vm.ctx.empty_tuple_typed().to_owned(), + }; let level = usize::try_from_object(vm, self.pop_value())?; - let module = vm.import_from(module_name, from_list, level)?; + let module = vm.import_from(module_name, &from_list, level)?; self.push_value(module); Ok(()) @@ -1839,7 +1842,8 @@ impl ExecutingFrame<'_> { .expect("Second to top value on the stack must be a code object"); let closure = if flags.contains(bytecode::MakeFunctionFlags::CLOSURE) { - Some(PyTupleTyped::try_from_object(vm, self.pop_value()).unwrap()) + let tuple = PyTupleRef::try_from_object(vm, self.pop_value()).unwrap(); + Some(tuple.try_into_typed(vm).expect("This is a compiler bug")) } else { None }; diff --git a/vm/src/object/core.rs b/vm/src/object/core.rs index b4b9557f2ae..5012855133a 100644 --- a/vm/src/object/core.rs +++ b/vm/src/object/core.rs @@ -15,7 +15,7 @@ use super::{ ext::{AsObject, PyRefExact, PyResult}, payload::PyObjectPayload, }; -use crate::object::traverse::{Traverse, TraverseFn}; +use crate::object::traverse::{MaybeTraverse, Traverse, TraverseFn}; use crate::object::traverse_object::PyObjVTable; use crate::{ builtins::{PyDictRef, PyType, PyTypeRef}, @@ -121,7 +121,7 @@ impl fmt::Debug for PyInner { } } -unsafe impl Traverse for Py { +unsafe impl Traverse for Py { /// DO notice that call `trace` on `Py` means apply `tracer_fn` on `Py`'s children, /// not like call `trace` on `PyRef` which apply `tracer_fn` on `PyRef` itself fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { @@ -557,7 +557,7 @@ impl PyObjectRef { /// # Safety /// T must be the exact payload type #[inline(always)] - pub unsafe fn downcast_unchecked(self) -> PyRef { + pub unsafe fn downcast_unchecked(self) -> PyRef { // PyRef::from_obj_unchecked(self) // manual impl to avoid assertion let obj = ManuallyDrop::new(self); @@ -893,7 +893,7 @@ impl fmt::Debug for PyObjectRef { } #[repr(transparent)] -pub struct Py(PyInner); +pub struct Py(PyInner); impl Py { pub fn downgrade( @@ -908,7 +908,7 @@ impl Py { } } -impl ToOwned for Py { +impl ToOwned for Py { type Owned = PyRef; #[inline(always)] @@ -920,7 +920,7 @@ impl ToOwned for Py { } } -impl Deref for Py { +impl Deref for Py { type Target = T; #[inline(always)] @@ -984,24 +984,24 @@ impl fmt::Debug for Py { /// situations (such as when implementing in-place methods such as `__iadd__`) /// where a reference to the same object must be returned. #[repr(transparent)] -pub struct PyRef { +pub struct PyRef { ptr: NonNull>, } cfg_if::cfg_if! { if #[cfg(feature = "threading")] { - unsafe impl Send for PyRef {} - unsafe impl Sync for PyRef {} + unsafe impl Send for PyRef {} + unsafe impl Sync for PyRef {} } } -impl fmt::Debug for PyRef { +impl fmt::Debug for PyRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(f) } } -impl Drop for PyRef { +impl Drop for PyRef { #[inline] fn drop(&mut self) { if self.0.ref_count.dec() { @@ -1010,7 +1010,7 @@ impl Drop for PyRef { } } -impl Clone for PyRef { +impl Clone for PyRef { #[inline(always)] fn clone(&self) -> Self { (**self).to_owned() @@ -1070,10 +1070,7 @@ where } } -impl From> for PyObjectRef -where - T: PyObjectPayload, -{ +impl From> for PyObjectRef { #[inline] fn from(value: PyRef) -> Self { let me = ManuallyDrop::new(value); @@ -1081,30 +1078,21 @@ where } } -impl Borrow> for PyRef -where - T: PyObjectPayload, -{ +impl Borrow> for PyRef { #[inline(always)] fn borrow(&self) -> &Py { self } } -impl AsRef> for PyRef -where - T: PyObjectPayload, -{ +impl AsRef> for PyRef { #[inline(always)] fn as_ref(&self) -> &Py { self } } -impl Deref for PyRef -where - T: PyObjectPayload, -{ +impl Deref for PyRef { type Target = Py; #[inline(always)] diff --git a/vm/src/object/traverse_object.rs b/vm/src/object/traverse_object.rs index ee327859506..281b0e56eb5 100644 --- a/vm/src/object/traverse_object.rs +++ b/vm/src/object/traverse_object.rs @@ -3,7 +3,8 @@ use std::fmt; use crate::{ PyObject, object::{ - Erased, InstanceDict, PyInner, PyObjectPayload, debug_obj, drop_dealloc_obj, try_trace_obj, + Erased, InstanceDict, MaybeTraverse, PyInner, PyObjectPayload, debug_obj, drop_dealloc_obj, + try_trace_obj, }, }; @@ -56,7 +57,7 @@ unsafe impl Traverse for PyInner { } } -unsafe impl Traverse for PyInner { +unsafe impl Traverse for PyInner { /// Type is known, so we can call `try_trace` directly instead of using erased type vtable fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { // 1. trace `dict` and `slots` field(`typ` can't trace for it's a AtomicRef while is leaked by design) diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index d35b5b7f7e4..4c673831e06 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -11,6 +11,7 @@ use crate::{ }, getset::PyGetSet, object, pystr, + tuple::PyTupleTyped, type_::PyAttributes, }, class::{PyClassImpl, StaticType}, @@ -373,6 +374,12 @@ impl Context { self.not_implemented.clone().into() } + #[inline] + pub fn empty_tuple_typed(&self) -> &Py> { + let py: &Py = &self.empty_tuple; + unsafe { std::mem::transmute(py) } + } + // universal pyref constructor pub fn new_pyref(&self, value: T) -> PyRef

where diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index 4a319c96352..dbfa2147b39 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -599,7 +599,7 @@ impl VirtualMachine { #[inline] pub fn import<'a>(&self, module_name: impl AsPyStr<'a>, level: usize) -> PyResult { let module_name = module_name.as_pystr(&self.ctx); - let from_list = PyTupleTyped::empty(self); + let from_list = self.ctx.empty_tuple_typed(); self.import_inner(module_name, from_list, level) } @@ -609,7 +609,7 @@ impl VirtualMachine { pub fn import_from<'a>( &self, module_name: impl AsPyStr<'a>, - from_list: PyTupleTyped, + from_list: &Py>, level: usize, ) -> PyResult { let module_name = module_name.as_pystr(&self.ctx); @@ -619,7 +619,7 @@ impl VirtualMachine { fn import_inner( &self, module: &Py, - from_list: PyTupleTyped, + from_list: &Py>, level: usize, ) -> PyResult { // if the import inputs seem weird, e.g a package import or something, rather than just @@ -657,7 +657,7 @@ impl VirtualMachine { } else { (None, None) }; - let from_list = from_list.to_pyobject(self); + let from_list: PyObjectRef = from_list.to_owned().into(); import_func .call((module.to_owned(), globals, locals, from_list, level), self) .inspect_err(|exc| import::remove_importlib_frames(self, exc)) From 6342ad4fa7ec073aa85967e7f2a4c6bcfd2df103 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 14 Jul 2025 14:27:31 +0900 Subject: [PATCH 4/4] Fully integrate PyTupleTyped into PyTuple --- vm/src/builtins/function.rs | 8 +- vm/src/builtins/tuple.rs | 213 +++++++++++++----------------------- vm/src/builtins/type.rs | 10 +- vm/src/vm/context.rs | 3 +- vm/src/vm/mod.rs | 9 +- 5 files changed, 92 insertions(+), 151 deletions(-) diff --git a/vm/src/builtins/function.rs b/vm/src/builtins/function.rs index 45917adcf23..16cb3e420f3 100644 --- a/vm/src/builtins/function.rs +++ b/vm/src/builtins/function.rs @@ -2,8 +2,8 @@ mod jit; use super::{ - PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTupleRef, PyType, - PyTypeRef, tuple::PyTupleTyped, + PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyStr, PyStrRef, PyTuple, PyTupleRef, + PyType, PyTypeRef, }; #[cfg(feature = "jit")] use crate::common::lock::OnceCell; @@ -31,7 +31,7 @@ pub struct PyFunction { code: PyRef, globals: PyDictRef, builtins: PyObjectRef, - closure: Option>>, + closure: Option>>, defaults_and_kwdefaults: PyMutex<(Option, Option)>, name: PyMutex, qualname: PyMutex, @@ -59,7 +59,7 @@ impl PyFunction { pub(crate) fn new( code: PyRef, globals: PyDictRef, - closure: Option>>, + closure: Option>>, defaults: Option, kw_only_defaults: Option, qualname: PyStrRef, diff --git a/vm/src/builtins/tuple.rs b/vm/src/builtins/tuple.rs index 9f589547f0c..2c3255b2490 100644 --- a/vm/src/builtins/tuple.rs +++ b/vm/src/builtins/tuple.rs @@ -3,7 +3,6 @@ use crate::common::{ hash::{PyHash, PyUHash}, lock::PyMutex, }; -use crate::object::{MaybeTraverse, Traverse, TraverseFn}; use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, atomic_func, @@ -22,14 +21,14 @@ use crate::{ utils::collection_repr, vm::VirtualMachine, }; -use std::{fmt, marker::PhantomData, sync::LazyLock}; +use std::{fmt, sync::LazyLock}; #[pyclass(module = false, name = "tuple", traverse)] -pub struct PyTuple { - elements: Box<[PyObjectRef]>, +pub struct PyTuple { + elements: Box<[R]>, } -impl fmt::Debug for PyTuple { +impl fmt::Debug for PyTuple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // TODO: implement more informational, non-recursive Debug formatter f.write_str("tuple") @@ -140,39 +139,60 @@ impl Constructor for PyTuple { } } -impl AsRef<[PyObjectRef]> for PyTuple { - fn as_ref(&self) -> &[PyObjectRef] { - self.as_slice() +impl AsRef<[R]> for PyTuple { + fn as_ref(&self) -> &[R] { + &self.elements } } -impl std::ops::Deref for PyTuple { - type Target = [PyObjectRef]; +impl std::ops::Deref for PyTuple { + type Target = [R]; - fn deref(&self) -> &[PyObjectRef] { - self.as_slice() + fn deref(&self) -> &[R] { + &self.elements } } -impl<'a> std::iter::IntoIterator for &'a PyTuple { - type Item = &'a PyObjectRef; - type IntoIter = std::slice::Iter<'a, PyObjectRef>; +impl<'a, R> std::iter::IntoIterator for &'a PyTuple { + type Item = &'a R; + type IntoIter = std::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl<'a> std::iter::IntoIterator for &'a Py { - type Item = &'a PyObjectRef; - type IntoIter = std::slice::Iter<'a, PyObjectRef>; +impl<'a, R> std::iter::IntoIterator for &'a Py> { + type Item = &'a R; + type IntoIter = std::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl PyTuple { +impl PyTuple { + pub const fn as_slice(&self) -> &[R] { + &self.elements + } + + #[inline] + pub fn len(&self) -> usize { + self.elements.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + #[inline] + pub fn iter(&self) -> std::slice::Iter<'_, R> { + self.elements.iter() + } +} + +impl PyTuple { pub fn new_ref(elements: Vec, ctx: &Context) -> PyRef { if elements.is_empty() { ctx.empty_tuple.clone() @@ -189,10 +209,6 @@ impl PyTuple { Self { elements } } - pub const fn as_slice(&self) -> &[PyObjectRef] { - &self.elements - } - fn repeat(zelf: PyRef, value: isize, vm: &VirtualMachine) -> PyResult> { Ok(if zelf.elements.is_empty() || value == 0 { vm.ctx.empty_tuple.clone() @@ -214,6 +230,18 @@ impl PyTuple { } } +impl PyTuple> { + pub fn new_ref_typed(elements: Vec>, ctx: &Context) -> PyRef>> { + // SAFETY: PyRef has the same layout as PyObjectRef + unsafe { + let elements: Vec = + std::mem::transmute::>, Vec>(elements); + let tuple = PyTuple::::new_ref(elements, ctx); + std::mem::transmute::, PyRef>>>(tuple) + } + } +} + #[pyclass( flags(BASETYPE), with( @@ -272,11 +300,6 @@ impl PyTuple { self.elements.len() } - #[inline] - pub const fn is_empty(&self) -> bool { - self.elements.is_empty() - } - #[pymethod(name = "__rmul__")] #[pymethod] fn __mul__(zelf: PyRef, value: ArgSize, vm: &VirtualMachine) -> PyResult> { @@ -449,21 +472,38 @@ impl Representable for PyTuple { } } -impl PyRef { +impl PyRef> { pub fn try_into_typed( self, vm: &VirtualMachine, - ) -> PyResult>>> { - PyRef::>>::try_from_untyped(self, vm) + ) -> PyResult>>> { + // Check that all elements are of the correct type + for elem in self.as_slice() { + as TransmuteFromObject>::check(vm, elem)?; + } + // SAFETY: We just verified all elements are of type T + Ok(unsafe { std::mem::transmute::, PyRef>>>(self) }) + } +} + +impl PyRef>> { + pub fn into_untyped(self) -> PyRef { + // SAFETY: PyTuple> has the same layout as PyTuple + unsafe { std::mem::transmute::>>, PyRef>(self) } } - /// # Safety - /// - /// The caller must ensure that all elements in the tuple are valid instances - /// of type `T` before calling this method. This is typically verified by - /// calling `try_into_typed` first. - unsafe fn into_typed_unchecked(self) -> PyRef>> { - let obj: PyObjectRef = self.into(); - unsafe { obj.downcast_unchecked::>>() } +} + +impl Py>> { + pub fn as_untyped(&self) -> &Py { + // SAFETY: PyTuple> has the same layout as PyTuple + unsafe { std::mem::transmute::<&Py>>, &Py>(self) } + } +} + +impl From>>> for PyTupleRef { + #[inline] + fn from(tup: PyRef>>) -> Self { + tup.into_untyped() } } @@ -518,101 +558,6 @@ pub(crate) fn init(context: &Context) { PyTupleIterator::extend_class(context, context.types.tuple_iterator_type); } -#[repr(transparent)] -pub struct PyTupleTyped { - // SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the - // elements must be logically valid when transmuted to T - tuple: PyTuple, - _marker: PhantomData, -} - -unsafe impl Traverse for PyTupleTyped -where - R: TransmuteFromObject, -{ - fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.tuple.traverse(tracer_fn); - } -} - -impl MaybeTraverse for PyTupleTyped { - const IS_TRACE: bool = true; - fn try_traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.traverse(tracer_fn); - } -} - -impl PyTupleTyped> { - pub fn new_ref(elements: Vec>, ctx: &Context) -> PyRef { - // SAFETY: PyRef has the same layout as PyObjectRef - unsafe { - let elements: Vec = - std::mem::transmute::>, Vec>(elements); - let tuple = PyTuple::new_ref(elements, ctx); - tuple.into_typed_unchecked::() - } - } -} - -impl PyRef>> { - pub fn into_untyped(self) -> PyRef { - // SAFETY: PyTupleTyped is transparent over PyTuple - unsafe { std::mem::transmute::>>, PyRef>(self) } - } - - pub fn try_from_untyped(tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult { - // Check that all elements are of the correct type - for elem in tuple.as_slice() { - as TransmuteFromObject>::check(vm, elem)?; - } - // SAFETY: We just verified all elements are of type T, and PyTupleTyped has the same layout as PyTuple - Ok(unsafe { std::mem::transmute::, PyRef>>>(tuple) }) - } -} - -impl Py>> { - pub fn as_untyped(&self) -> &Py { - // SAFETY: PyTupleTyped is transparent over PyTuple - unsafe { std::mem::transmute::<&Py>>, &Py>(self) } - } -} - -impl AsRef<[PyRef]> for PyTupleTyped> { - fn as_ref(&self) -> &[PyRef] { - self.as_slice() - } -} - -impl PyTupleTyped> { - #[inline] - pub fn as_slice(&self) -> &[PyRef] { - unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [PyRef]) } - } - - #[inline] - pub fn len(&self) -> usize { - self.tuple.len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.tuple.is_empty() - } -} - -impl fmt::Debug for PyTupleTyped { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.tuple.as_slice().fmt(f) - } -} - -impl From>>> for PyTupleRef { - #[inline] - fn from(tup: PyRef>>) -> Self { - tup.into_untyped() - } -} - pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyResult { #[cfg(target_pointer_width = "64")] const PRIME1: PyUHash = 11400714785074694791; diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 5a8f853bf1f..f2a4fde3b9b 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -1,5 +1,5 @@ use super::{ - PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTuple, PyTupleRef, PyWeak, + PyClassMethod, PyDictRef, PyList, PyStr, PyStrInterned, PyStrRef, PyTupleRef, PyWeak, mappingproxy::PyMappingProxy, object, union_, }; use crate::{ @@ -12,7 +12,7 @@ use crate::{ PyMemberDescriptor, }, function::PyCellRef, - tuple::{IntoPyTuple, PyTupleTyped}, + tuple::{IntoPyTuple, PyTuple}, }, class::{PyClassImpl, StaticType}, common::{ @@ -62,7 +62,7 @@ unsafe impl crate::object::Traverse for PyType { pub struct HeapTypeExt { pub name: PyRwLock, pub qualname: PyRwLock, - pub slots: Option>>, + pub slots: Option>>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, } @@ -1041,11 +1041,11 @@ impl Constructor for PyType { // TODO: Flags is currently initialized with HAS_DICT. Should be // updated when __slots__ are supported (toggling the flag off if // a class has __slots__ defined). - let heaptype_slots: Option>> = + let heaptype_slots: Option>> = if let Some(x) = attributes.get(identifier!(vm, __slots__)) { let slots = if x.class().is(vm.ctx.types.str_type) { let x = unsafe { x.downcast_unchecked_ref::() }; - PyTupleTyped::new_ref(vec![x.to_owned()], &vm.ctx) + PyTuple::new_ref_typed(vec![x.to_owned()], &vm.ctx) } else { let iter = x.get_iter(vm)?; let elements = { diff --git a/vm/src/vm/context.rs b/vm/src/vm/context.rs index 4c673831e06..67072881519 100644 --- a/vm/src/vm/context.rs +++ b/vm/src/vm/context.rs @@ -11,7 +11,6 @@ use crate::{ }, getset::PyGetSet, object, pystr, - tuple::PyTupleTyped, type_::PyAttributes, }, class::{PyClassImpl, StaticType}, @@ -375,7 +374,7 @@ impl Context { } #[inline] - pub fn empty_tuple_typed(&self) -> &Py> { + pub fn empty_tuple_typed(&self) -> &Py> { let py: &Py = &self.empty_tuple; unsafe { std::mem::transmute(py) } } diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index dbfa2147b39..498c7e39d11 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -20,10 +20,7 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, builtins::{ PyBaseExceptionRef, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned, PyStrRef, - PyTypeRef, - code::PyCode, - pystr::AsPyStr, - tuple::{PyTuple, PyTupleTyped}, + PyTypeRef, code::PyCode, pystr::AsPyStr, tuple::PyTuple, }, codecs::CodecsRegistry, common::{hash::HashSecret, lock::PyMutex, rc::PyRc}, @@ -609,7 +606,7 @@ impl VirtualMachine { pub fn import_from<'a>( &self, module_name: impl AsPyStr<'a>, - from_list: &Py>, + from_list: &Py>, level: usize, ) -> PyResult { let module_name = module_name.as_pystr(&self.ctx); @@ -619,7 +616,7 @@ impl VirtualMachine { fn import_inner( &self, module: &Py, - from_list: &Py>, + from_list: &Py>, level: usize, ) -> PyResult { // if the import inputs seem weird, e.g a package import or something, rather than just