Visitar URL original
freelist: exact type check at pop call-site · RustPython/RustPython@bbc1d7f · GitHub
Skip to content

Commit bbc1d7f

Browse files
committed
freelist: exact type check at pop call-site
Move exact-type filtering from freelist_pop implementations to the single call-site in new_ref. This prevents structseq and other subtypes from popping tuple freelist entries entirely, rather than popping and then deallocating on type mismatch. Add Context::try_genesis() that returns None during bootstrap to avoid deadlock when genesis() is called during Context initialization.
1 parent 78c3acd commit bbc1d7f

2 files changed

Lines changed: 21 additions & 24 deletions

File tree

‎crates/vm/src/builtins/tuple.rs‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,19 @@ unsafe impl Traverse for PyTuple {
5555
}
5656
}
5757

58-
// spell-checker:ignore MAXFREELIST MAXSAVESIZE
58+
// spell-checker:ignore MAXSAVESIZE
5959
/// Per-size freelist storage for tuples, matching tuples[PyTuple_MAXSAVESIZE].
6060
/// Each bucket caches tuples of a specific element count (index = len - 1).
6161
struct TupleFreeList {
62-
buckets: [Vec<NonNull<PyObject>>; Self::MAXSAVESIZE],
62+
buckets: [Vec<NonNull<PyObject>>; Self::MAX_SAVE_SIZE],
6363
}
6464

6565
impl TupleFreeList {
6666
/// Largest tuple size to cache on the freelist (sizes 1..=20).
67-
const MAXSAVESIZE: usize = 20;
67+
const MAX_SAVE_SIZE: usize = 20;
6868
const fn new() -> Self {
6969
Self {
70-
buckets: [const { Vec::new() }; Self::MAXSAVESIZE],
70+
buckets: [const { Vec::new() }; Self::MAX_SAVE_SIZE],
7171
}
7272
}
7373
}
@@ -119,7 +119,7 @@ impl PyPayload for PyTuple {
119119
return false;
120120
}
121121
let len = hint;
122-
if len == 0 || len > TupleFreeList::MAXSAVESIZE {
122+
if len == 0 || len > TupleFreeList::MAX_SAVE_SIZE {
123123
return false;
124124
}
125125
TUPLE_FREELIST
@@ -141,7 +141,7 @@ impl PyPayload for PyTuple {
141141
#[inline]
142142
unsafe fn freelist_pop(payload: &Self) -> Option<NonNull<PyObject>> {
143143
let len = payload.elements.len();
144-
if len == 0 || len > TupleFreeList::MAXSAVESIZE {
144+
if len == 0 || len > TupleFreeList::MAX_SAVE_SIZE {
145145
return None;
146146
}
147147
TUPLE_FREELIST

‎crates/vm/src/object/core.rs‎

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,7 +2103,7 @@ impl<T: PyPayload + crate::object::MaybeTraverse + core::fmt::Debug> PyRef<T> {
21032103
let has_dict = dict.is_some();
21042104
let is_heaptype = typ.heaptype_ext.is_some();
21052105

2106-
// Try to reuse from freelist (exact type only, no dict, no heaptype)
2106+
// Try to reuse from freelist (no dict, no heaptype)
21072107
let cached = if !has_dict && !is_heaptype {
21082108
unsafe { T::freelist_pop(&payload) }
21092109
} else {
@@ -2112,25 +2112,22 @@ impl<T: PyPayload + crate::object::MaybeTraverse + core::fmt::Debug> PyRef<T> {
21122112

21132113
let ptr = if let Some(cached) = cached {
21142114
let inner = cached.as_ptr() as *mut PyInner<T>;
2115-
// Push-side exact type check filters subtypes, but subtypes
2116-
// sharing the same Rust payload (e.g. structseq) may still
2117-
// call freelist_pop. Verify typ matches; if not, deallocate.
2118-
let cached_typ = unsafe { (*inner).typ.load_raw() };
2119-
if !core::ptr::eq(cached_typ, &*typ as *const Py<PyType>) {
2120-
unsafe { PyInner::dealloc(inner) };
2121-
let inner = PyInner::new(payload, typ, dict);
2122-
unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }
2123-
} else {
2124-
unsafe {
2125-
core::ptr::write(&mut (*inner).ref_count, RefCount::new());
2126-
(*inner).gc_bits.store(0, Ordering::Relaxed);
2127-
core::ptr::drop_in_place(&mut (*inner).payload);
2128-
core::ptr::write(&mut (*inner).payload, payload);
2115+
unsafe {
2116+
core::ptr::write(&mut (*inner).ref_count, RefCount::new());
2117+
(*inner).gc_bits.store(0, Ordering::Relaxed);
2118+
core::ptr::drop_in_place(&mut (*inner).payload);
2119+
core::ptr::write(&mut (*inner).payload, payload);
2120+
// Freelist only stores exact base types (push-side filter),
2121+
// but subtypes sharing the same Rust payload (e.g. structseq)
2122+
// may pop entries. Update typ if it differs.
2123+
let cached_typ: *const Py<PyType> = &*(*inner).typ;
2124+
if core::ptr::eq(cached_typ, &*typ) {
2125+
drop(typ);
2126+
} else {
2127+
let _old = (*inner).typ.swap(typ);
21292128
}
2130-
// Drop the caller's typ since the cached object already holds one
2131-
drop(typ);
2132-
unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }
21332129
}
2130+
unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }
21342131
} else {
21352132
let inner = PyInner::new(payload, typ, dict);
21362133
unsafe { NonNull::new_unchecked(inner.cast::<Py<T>>()) }

0 commit comments

Comments
 (0)