From e0541fb18f38b530e0e87ca7ea6f45227a7032f5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Dec 2025 10:37:22 +0900 Subject: [PATCH 1/3] shape_differs --- crates/vm/src/builtins/type.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index f74095a8c8..055c51f8af 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1349,6 +1349,7 @@ impl Constructor for PyType { let slots = PyTypeSlots { flags, member_count, + itemsize: base.slots.itemsize, ..PyTypeSlots::heap_default() }; let heaptype_ext = HeapTypeExt { @@ -2009,6 +2010,11 @@ fn calculate_meta_class( Ok(winner) } +/// Returns true if the two types have different instance layouts. +fn shape_differs(t1: &Py, t2: &Py) -> bool { + t1.__basicsize__() != t2.__basicsize__() || t1.slots.itemsize != t2.slots.itemsize +} + fn solid_base<'a>(typ: &'a Py, vm: &VirtualMachine) -> &'a Py { let base = if let Some(base) = &typ.base { solid_base(base, vm) @@ -2016,16 +2022,7 @@ fn solid_base<'a>(typ: &'a Py, vm: &VirtualMachine) -> &'a Py { vm.ctx.types.object_type }; - // Check for extra instance variables (CPython's extra_ivars) - let t_size = typ.__basicsize__(); - let b_size = base.__basicsize__(); - let t_itemsize = typ.slots.itemsize; - let b_itemsize = base.slots.itemsize; - - // Has extra ivars if: sizes differ AND (has items OR t_size > b_size) - let has_extra_ivars = t_size != b_size && (t_itemsize > 0 || b_itemsize > 0 || t_size > b_size); - - if has_extra_ivars { typ } else { base } + if shape_differs(typ, base) { typ } else { base } } fn best_base<'a>(bases: &'a [PyTypeRef], vm: &VirtualMachine) -> PyResult<&'a Py> { From a3c3ea0c9ce1cb712a21112af4a46b368412dac7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 21 Jul 2025 16:14:31 +0900 Subject: [PATCH 2/3] may_add_dict --- crates/vm/src/builtins/type.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 055c51f8af..c9a8cfdfda 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1338,10 +1338,16 @@ impl Constructor for PyType { let member_count: usize = base_member_count + heaptype_member_count; let mut flags = PyTypeFlags::heap_type_flags(); + + // Check if we may add dict + // We can only add a dict if the primary base class doesn't already have one + // In CPython, this checks tp_dictoffset == 0 + let may_add_dict = !base.slots.flags.has_feature(PyTypeFlags::HAS_DICT); + // Add HAS_DICT and MANAGED_DICT if: - // 1. __slots__ is not defined, OR + // 1. __slots__ is not defined AND base doesn't have dict, OR // 2. __dict__ is in __slots__ - if heaptype_slots.is_none() || add_dict { + if (heaptype_slots.is_none() && may_add_dict) || add_dict { flags |= PyTypeFlags::HAS_DICT | PyTypeFlags::MANAGED_DICT; } From 63c3ba3ee3e117887f246fc3ded376771a3e16be Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Dec 2025 12:57:53 +0900 Subject: [PATCH 3/3] basicsize --- Lib/test/test_types.py | 2 -- crates/derive-impl/src/pyclass.rs | 1 + crates/vm/src/builtins/type.rs | 4 ++-- crates/vm/src/object/core.rs | 1 + crates/vm/src/object/mod.rs | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 4200cb8ee5..b57fdf35fb 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -591,8 +591,6 @@ def test_format_spec_errors(self): for code in 'xXobns': self.assertRaises(ValueError, format, 0, ',' + code) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_internal_sizes(self): self.assertGreater(object.__basicsize__, 0) self.assertGreater(tuple.__itemsize__, 0) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index 82057d40f9..55f9c76994 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -455,6 +455,7 @@ fn generate_class_def( }); // If repr(transparent) with a base, the type has the same memory layout as base, // so basicsize should be 0 (no additional space beyond the base type) + // Otherwise, basicsize = sizeof(payload). The header size is added in __basicsize__ getter. let basicsize = if is_repr_transparent && base.is_some() { quote!(0) } else { diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index c9a8cfdfda..e807d3f4f8 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -782,8 +782,8 @@ impl PyType { } #[pygetset] - const fn __basicsize__(&self) -> usize { - self.slots.basicsize + fn __basicsize__(&self) -> usize { + crate::object::SIZEOF_PYOBJECT_HEAD + self.slots.basicsize } #[pygetset] diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index a092d7097b..d52a33884c 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -114,6 +114,7 @@ pub(super) struct PyInner { pub(super) payload: T, } +pub(crate) const SIZEOF_PYOBJECT_HEAD: usize = std::mem::size_of::>(); impl fmt::Debug for PyInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/vm/src/object/mod.rs b/crates/vm/src/object/mod.rs index 034523afe5..a279201a0b 100644 --- a/crates/vm/src/object/mod.rs +++ b/crates/vm/src/object/mod.rs @@ -7,4 +7,5 @@ mod traverse_object; pub use self::core::*; pub use self::ext::*; pub use self::payload::*; +pub(crate) use core::SIZEOF_PYOBJECT_HEAD; pub use traverse::{MaybeTraverse, Traverse, TraverseFn};