Skip to content

Commit c32f31b

Browse files
committed
Remove freelist_hint; call freelist_push before tp_clear
By calling freelist_push before tp_clear, the payload is still intact and can be read directly (e.g. tuple element count for bucket selection). This eliminates freelist_hint and the hint parameter entirely.
1 parent aad6d6d commit c32f31b

File tree

10 files changed

+24
-48
lines changed

10 files changed

+24
-48
lines changed

crates/vm/src/builtins/complex.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl PyPayload for PyComplex {
4141
}
4242

4343
#[inline]
44-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
44+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
4545
COMPLEX_FREELIST
4646
.try_with(|fl| {
4747
let mut list = fl.take();

crates/vm/src/builtins/dict.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl PyPayload for PyDict {
7676
}
7777

7878
#[inline]
79-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
79+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
8080
DICT_FREELIST
8181
.try_with(|fl| {
8282
let mut list = fl.take();

crates/vm/src/builtins/float.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl PyPayload for PyFloat {
4848
}
4949

5050
#[inline]
51-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
51+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
5252
FLOAT_FREELIST
5353
.try_with(|fl| {
5454
let mut list = fl.take();

crates/vm/src/builtins/int.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl PyPayload for PyInt {
6969
}
7070

7171
#[inline]
72-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
72+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
7373
INT_FREELIST
7474
.try_with(|fl| {
7575
let mut list = fl.take();

crates/vm/src/builtins/list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl PyPayload for PyList {
8888
}
8989

9090
#[inline]
91-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
91+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
9292
LIST_FREELIST
9393
.try_with(|fl| {
9494
let mut list = fl.take();

crates/vm/src/builtins/range.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl PyPayload for PyRange {
8484
}
8585

8686
#[inline]
87-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
87+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
8888
RANGE_FREELIST
8989
.try_with(|fl| {
9090
let mut list = fl.take();

crates/vm/src/builtins/slice.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl PyPayload for PySlice {
5959
}
6060

6161
#[inline]
62-
unsafe fn freelist_push(obj: *mut PyObject, _hint: usize) -> bool {
62+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
6363
SLICE_FREELIST
6464
.try_with(|fl| {
6565
let mut list = fl.take();

crates/vm/src/builtins/tuple.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,8 @@ impl PyPayload for PyTuple {
107107
}
108108

109109
#[inline]
110-
unsafe fn freelist_hint(obj: *mut PyObject) -> usize {
111-
let py_tuple = unsafe { &*(obj as *const crate::Py<PyTuple>) };
112-
py_tuple.elements.len()
113-
}
114-
115-
#[inline]
116-
unsafe fn freelist_push(obj: *mut PyObject, hint: usize) -> bool {
117-
let len = hint;
110+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
111+
let len = unsafe { &*(obj as *const crate::Py<PyTuple>) }.elements.len();
118112
if len == 0 || len > TupleFreeList::MAX_SAVE_SIZE {
119113
return false;
120114
}

crates/vm/src/object/core.rs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -188,39 +188,32 @@ pub(super) unsafe fn default_dealloc<T: PyPayload>(obj: *mut PyObject) {
188188
);
189189
}
190190

191-
// Capture freelist bucket hint before tp_clear empties the payload.
192-
// Size-based freelists (e.g. PyTuple) need the element count for bucket selection,
193-
// but clear() replaces elements with an empty slice.
194-
let freelist_hint = if T::HAS_FREELIST {
195-
unsafe { T::freelist_hint(obj) }
196-
} else {
197-
0
198-
};
199-
200-
// Extract child references before deallocation to break circular refs (tp_clear)
201-
let mut edges = Vec::new();
202-
if let Some(clear_fn) = vtable.clear {
203-
unsafe { clear_fn(obj, &mut edges) };
204-
}
205-
206-
// Try to store in freelist for reuse; otherwise deallocate.
191+
// Try to store in freelist for reuse BEFORE tp_clear, so that
192+
// size-based freelists (e.g. PyTuple) can read the payload directly.
207193
// Only exact base types (not heaptype or structseq subtypes) go into the freelist.
208194
let typ = obj_ref.class();
209195
let pushed = if T::HAS_FREELIST
210196
&& typ.heaptype_ext.is_none()
211197
&& core::ptr::eq(typ, T::class(crate::vm::Context::genesis()))
212198
{
213-
unsafe { T::freelist_push(obj, freelist_hint) }
199+
unsafe { T::freelist_push(obj) }
214200
} else {
215201
false
216202
};
203+
204+
// Extract child references to break circular refs (tp_clear).
205+
// This runs regardless of freelist push — the object's children must be released.
206+
let mut edges = Vec::new();
207+
if let Some(clear_fn) = vtable.clear {
208+
unsafe { clear_fn(obj, &mut edges) };
209+
}
210+
217211
if !pushed {
218212
// Deallocate the object memory (handles ObjExt prefix if present)
219213
unsafe { PyInner::dealloc(obj as *mut PyInner<T>) };
220214
}
221215

222216
// Drop child references - may trigger recursive destruction.
223-
// The object is already deallocated, so circular refs are broken.
224217
drop(edges);
225218

226219
// Trashcan: decrement depth and process deferred objects at outermost level

crates/vm/src/object/payload.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,15 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static {
5555
/// Maximum number of objects to keep in the freelist.
5656
const MAX_FREELIST: usize = 0;
5757

58-
/// Capture a hint value from the payload before tp_clear runs.
59-
/// Used by size-based freelists (e.g. PyTuple) to remember the element
60-
/// count before clear empties the payload.
61-
///
62-
/// # Safety
63-
/// `obj` must be a valid pointer to a `PyInner<Self>` with the payload still intact.
64-
#[inline]
65-
unsafe fn freelist_hint(_obj: *mut PyObject) -> usize {
66-
0
67-
}
68-
6958
/// Try to push a dead object onto this type's freelist for reuse.
7059
/// Returns true if the object was stored (caller must NOT free the memory).
71-
/// `hint` is the value returned by `freelist_hint` before tp_clear.
60+
/// Called before tp_clear, so the payload is still intact.
7261
///
7362
/// # Safety
74-
/// `obj` must be a valid pointer to a `PyInner<Self>` with refcount 0,
75-
/// after `drop_slow_inner` and `tp_clear` have already run.
63+
/// `obj` must be a valid pointer to a `PyInner<Self>` with refcount 0.
64+
/// The payload is still initialized and can be read for bucket selection.
7665
#[inline]
77-
unsafe fn freelist_push(_obj: *mut PyObject, _hint: usize) -> bool {
66+
unsafe fn freelist_push(_obj: *mut PyObject) -> bool {
7867
false
7968
}
8069

0 commit comments

Comments
 (0)