Skip to content

Commit 86134e1

Browse files
Add freelists for PyComplex, PyInt, PyRange (RustPython#7345)
* Add freelists for PyComplex, PyInt, PyBoundMethod, PyRange - PyComplex(100), PyInt(100), PyBoundMethod(20), PyRange(6) - Use try_with instead of with in all freelist push/pop to prevent panic on thread-local access during thread teardown * Use alloc::dealloc in FreeList::Drop to avoid thread-local access panic During thread teardown, Box::from_raw triggers cascading destructors that may access already-destroyed thread-local storage (GC state, other freelists). Use raw dealloc instead to free memory without running destructors. * Auto-format: cargo fmt --all * Fix clippy: use core::alloc::Layout instead of alloc::alloc::Layout * Address review: PyBoundMethod clear=false, update FreeList doc comment - Set clear=false on PyBoundMethod (tp_clear=NULL in classobject.c) - Update FreeList doc comment to match actual Drop behavior (raw dealloc) * Remove PyBoundMethod freelist to fix refcount/weakref test failures Non-Option PyObjectRef fields retain references in freelist, causing weakref and refcount assertions to fail in test_unittest, test_multiprocessing, and test_socket. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent d1c73ac commit 86134e1

File tree

8 files changed

+221
-73
lines changed

8 files changed

+221
-73
lines changed

crates/vm/src/builtins/complex.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use crate::{
1010
stdlib::warnings,
1111
types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
1212
};
13+
use core::cell::Cell;
1314
use core::num::Wrapping;
15+
use core::ptr::NonNull;
1416
use num_complex::Complex64;
1517
use num_traits::Zero;
1618
use rustpython_common::hash;
@@ -24,11 +26,49 @@ pub struct PyComplex {
2426
value: Complex64,
2527
}
2628

29+
// spell-checker:ignore MAXFREELIST
30+
thread_local! {
31+
static COMPLEX_FREELIST: Cell<crate::object::FreeList<PyComplex>> = const { Cell::new(crate::object::FreeList::new()) };
32+
}
33+
2734
impl PyPayload for PyComplex {
35+
const MAX_FREELIST: usize = 100;
36+
const HAS_FREELIST: bool = true;
37+
2838
#[inline]
2939
fn class(ctx: &Context) -> &'static Py<PyType> {
3040
ctx.types.complex_type
3141
}
42+
43+
#[inline]
44+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
45+
COMPLEX_FREELIST
46+
.try_with(|fl| {
47+
let mut list = fl.take();
48+
let stored = if list.len() < Self::MAX_FREELIST {
49+
list.push(obj);
50+
true
51+
} else {
52+
false
53+
};
54+
fl.set(list);
55+
stored
56+
})
57+
.unwrap_or(false)
58+
}
59+
60+
#[inline]
61+
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
62+
COMPLEX_FREELIST
63+
.try_with(|fl| {
64+
let mut list = fl.take();
65+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
66+
fl.set(list);
67+
result
68+
})
69+
.ok()
70+
.flatten()
71+
}
3272
}
3373

3474
impl ToPyObject for Complex64 {

crates/vm/src/builtins/dict.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,27 +77,32 @@ impl PyPayload for PyDict {
7777

7878
#[inline]
7979
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
80-
DICT_FREELIST.with(|fl| {
81-
let mut list = fl.take();
82-
let stored = if list.len() < Self::MAX_FREELIST {
83-
list.push(obj);
84-
true
85-
} else {
86-
false
87-
};
88-
fl.set(list);
89-
stored
90-
})
80+
DICT_FREELIST
81+
.try_with(|fl| {
82+
let mut list = fl.take();
83+
let stored = if list.len() < Self::MAX_FREELIST {
84+
list.push(obj);
85+
true
86+
} else {
87+
false
88+
};
89+
fl.set(list);
90+
stored
91+
})
92+
.unwrap_or(false)
9193
}
9294

9395
#[inline]
9496
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
95-
DICT_FREELIST.with(|fl| {
96-
let mut list = fl.take();
97-
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
98-
fl.set(list);
99-
result
100-
})
97+
DICT_FREELIST
98+
.try_with(|fl| {
99+
let mut list = fl.take();
100+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
101+
fl.set(list);
102+
result
103+
})
104+
.ok()
105+
.flatten()
101106
}
102107
}
103108

crates/vm/src/builtins/float.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,32 @@ impl PyPayload for PyFloat {
4949

5050
#[inline]
5151
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
52-
FLOAT_FREELIST.with(|fl| {
53-
let mut list = fl.take();
54-
let stored = if list.len() < Self::MAX_FREELIST {
55-
list.push(obj);
56-
true
57-
} else {
58-
false
59-
};
60-
fl.set(list);
61-
stored
62-
})
52+
FLOAT_FREELIST
53+
.try_with(|fl| {
54+
let mut list = fl.take();
55+
let stored = if list.len() < Self::MAX_FREELIST {
56+
list.push(obj);
57+
true
58+
} else {
59+
false
60+
};
61+
fl.set(list);
62+
stored
63+
})
64+
.unwrap_or(false)
6365
}
6466

6567
#[inline]
6668
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
67-
FLOAT_FREELIST.with(|fl| {
68-
let mut list = fl.take();
69-
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
70-
fl.set(list);
71-
result
72-
})
69+
FLOAT_FREELIST
70+
.try_with(|fl| {
71+
let mut list = fl.take();
72+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
73+
fl.set(list);
74+
result
75+
})
76+
.ok()
77+
.flatten()
7378
}
7479
}
7580

crates/vm/src/builtins/int.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use crate::{
2020
types::{AsNumber, Comparable, Constructor, Hashable, PyComparisonOp, Representable},
2121
};
2222
use alloc::fmt;
23+
use core::cell::Cell;
2324
use core::ops::{Neg, Not};
25+
use core::ptr::NonNull;
2426
use malachite_bigint::{BigInt, Sign};
2527
use num_integer::Integer;
2628
use num_traits::{One, Pow, PrimInt, Signed, ToPrimitive, Zero};
@@ -48,7 +50,15 @@ where
4850
}
4951
}
5052

53+
// spell-checker:ignore MAXFREELIST
54+
thread_local! {
55+
static INT_FREELIST: Cell<crate::object::FreeList<PyInt>> = const { Cell::new(crate::object::FreeList::new()) };
56+
}
57+
5158
impl PyPayload for PyInt {
59+
const MAX_FREELIST: usize = 100;
60+
const HAS_FREELIST: bool = true;
61+
5262
#[inline]
5363
fn class(ctx: &Context) -> &'static Py<PyType> {
5464
ctx.types.int_type
@@ -57,6 +67,36 @@ impl PyPayload for PyInt {
5767
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
5868
vm.ctx.new_int(self.value).into()
5969
}
70+
71+
#[inline]
72+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
73+
INT_FREELIST
74+
.try_with(|fl| {
75+
let mut list = fl.take();
76+
let stored = if list.len() < Self::MAX_FREELIST {
77+
list.push(obj);
78+
true
79+
} else {
80+
false
81+
};
82+
fl.set(list);
83+
stored
84+
})
85+
.unwrap_or(false)
86+
}
87+
88+
#[inline]
89+
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
90+
INT_FREELIST
91+
.try_with(|fl| {
92+
let mut list = fl.take();
93+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
94+
fl.set(list);
95+
result
96+
})
97+
.ok()
98+
.flatten()
99+
}
60100
}
61101

62102
macro_rules! impl_into_pyobject_int {

crates/vm/src/builtins/list.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,32 @@ impl PyPayload for PyList {
8989

9090
#[inline]
9191
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
92-
LIST_FREELIST.with(|fl| {
93-
let mut list = fl.take();
94-
let stored = if list.len() < Self::MAX_FREELIST {
95-
list.push(obj);
96-
true
97-
} else {
98-
false
99-
};
100-
fl.set(list);
101-
stored
102-
})
92+
LIST_FREELIST
93+
.try_with(|fl| {
94+
let mut list = fl.take();
95+
let stored = if list.len() < Self::MAX_FREELIST {
96+
list.push(obj);
97+
true
98+
} else {
99+
false
100+
};
101+
fl.set(list);
102+
stored
103+
})
104+
.unwrap_or(false)
103105
}
104106

105107
#[inline]
106108
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
107-
LIST_FREELIST.with(|fl| {
108-
let mut list = fl.take();
109-
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
110-
fl.set(list);
111-
result
112-
})
109+
LIST_FREELIST
110+
.try_with(|fl| {
111+
let mut list = fl.take();
112+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
113+
fl.set(list);
114+
result
115+
})
116+
.ok()
117+
.flatten()
113118
}
114119
}
115120

crates/vm/src/builtins/range.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ use crate::{
1515
Representable, SelfIter,
1616
},
1717
};
18+
use core::cell::Cell;
1819
use core::cmp::max;
20+
use core::ptr::NonNull;
1921
use crossbeam_utils::atomic::AtomicCell;
2022
use malachite_bigint::{BigInt, Sign};
2123
use num_integer::Integer;
@@ -67,11 +69,49 @@ pub struct PyRange {
6769
pub step: PyIntRef,
6870
}
6971

72+
// spell-checker:ignore MAXFREELIST
73+
thread_local! {
74+
static RANGE_FREELIST: Cell<crate::object::FreeList<PyRange>> = const { Cell::new(crate::object::FreeList::new()) };
75+
}
76+
7077
impl PyPayload for PyRange {
78+
const MAX_FREELIST: usize = 6;
79+
const HAS_FREELIST: bool = true;
80+
7181
#[inline]
7282
fn class(ctx: &Context) -> &'static Py<PyType> {
7383
ctx.types.range_type
7484
}
85+
86+
#[inline]
87+
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
88+
RANGE_FREELIST
89+
.try_with(|fl| {
90+
let mut list = fl.take();
91+
let stored = if list.len() < Self::MAX_FREELIST {
92+
list.push(obj);
93+
true
94+
} else {
95+
false
96+
};
97+
fl.set(list);
98+
stored
99+
})
100+
.unwrap_or(false)
101+
}
102+
103+
#[inline]
104+
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
105+
RANGE_FREELIST
106+
.try_with(|fl| {
107+
let mut list = fl.take();
108+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
109+
fl.set(list);
110+
result
111+
})
112+
.ok()
113+
.flatten()
114+
}
75115
}
76116

77117
impl PyRange {

crates/vm/src/builtins/slice.rs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,32 @@ impl PyPayload for PySlice {
6060

6161
#[inline]
6262
unsafe fn freelist_push(obj: *mut PyObject) -> bool {
63-
SLICE_FREELIST.with(|fl| {
64-
let mut list = fl.take();
65-
let stored = if list.len() < Self::MAX_FREELIST {
66-
list.push(obj);
67-
true
68-
} else {
69-
false
70-
};
71-
fl.set(list);
72-
stored
73-
})
63+
SLICE_FREELIST
64+
.try_with(|fl| {
65+
let mut list = fl.take();
66+
let stored = if list.len() < Self::MAX_FREELIST {
67+
list.push(obj);
68+
true
69+
} else {
70+
false
71+
};
72+
fl.set(list);
73+
stored
74+
})
75+
.unwrap_or(false)
7476
}
7577

7678
#[inline]
7779
unsafe fn freelist_pop() -> Option<NonNull<PyObject>> {
78-
SLICE_FREELIST.with(|fl| {
79-
let mut list = fl.take();
80-
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
81-
fl.set(list);
82-
result
83-
})
80+
SLICE_FREELIST
81+
.try_with(|fl| {
82+
let mut list = fl.take();
83+
let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
84+
fl.set(list);
85+
result
86+
})
87+
.ok()
88+
.flatten()
8489
}
8590
}
8691

0 commit comments

Comments
 (0)