@@ -27,6 +27,8 @@ use crate::{
2727 vm:: VirtualMachine ,
2828} ;
2929use alloc:: fmt;
30+ use core:: cell:: Cell ;
31+ use core:: ptr:: NonNull ;
3032
3133#[ pyclass( module = false , name = "tuple" , traverse = "manual" ) ]
3234pub struct PyTuple < R = PyObjectRef > {
@@ -53,14 +55,98 @@ unsafe impl Traverse for PyTuple {
5355 }
5456}
5557
56- // No freelist for PyTuple: structseq types (stat_result, struct_time, etc.)
57- // are static subtypes sharing the same Rust payload, making type-safe reuse
58- // impractical without a type-pointer comparison at push time.
58+ // spell-checker:ignore MAXFREELIST MAXSAVESIZE
59+ /// Per-size freelist storage for tuples, matching tuples[PyTuple_MAXSAVESIZE].
60+ /// Each bucket caches tuples of a specific element count (index = len - 1).
61+ struct TupleFreeList {
62+ buckets : [ Vec < * mut PyObject > ; Self :: MAXSAVESIZE ] ,
63+ }
64+
65+ impl TupleFreeList {
66+ /// Largest tuple size to cache on the freelist (sizes 1..=20).
67+ const MAXSAVESIZE : usize = 20 ;
68+ const fn new ( ) -> Self {
69+ Self {
70+ buckets : [ const { Vec :: new ( ) } ; Self :: MAXSAVESIZE ] ,
71+ }
72+ }
73+ }
74+
75+ impl Default for TupleFreeList {
76+ fn default ( ) -> Self {
77+ Self :: new ( )
78+ }
79+ }
80+
81+ impl Drop for TupleFreeList {
82+ fn drop ( & mut self ) {
83+ // Same safety pattern as FreeList<T>::drop — free raw allocation
84+ // without running payload destructors to avoid TLS-after-destruction panics.
85+ let layout = crate :: object:: pyinner_layout :: < PyTuple > ( ) ;
86+ for bucket in & mut self . buckets {
87+ for ptr in bucket. drain ( ..) {
88+ unsafe {
89+ alloc:: alloc:: dealloc ( ptr as * mut u8 , layout) ;
90+ }
91+ }
92+ }
93+ }
94+ }
95+
96+ thread_local ! {
97+ static TUPLE_FREELIST : Cell <TupleFreeList > = const { Cell :: new( TupleFreeList :: new( ) ) } ;
98+ }
99+
59100impl PyPayload for PyTuple {
101+ const MAX_FREELIST : usize = 2000 ;
102+ const HAS_FREELIST : bool = true ;
103+
60104 #[ inline]
61105 fn class ( ctx : & Context ) -> & ' static Py < PyType > {
62106 ctx. types . tuple_type
63107 }
108+
109+ #[ inline]
110+ unsafe fn freelist_push ( obj : * mut PyObject ) -> bool {
111+ let py_tuple = unsafe { & * ( obj as * const crate :: Py < PyTuple > ) } ;
112+ let len = py_tuple. elements . len ( ) ;
113+ if len == 0 || len > TupleFreeList :: MAXSAVESIZE {
114+ return false ;
115+ }
116+ TUPLE_FREELIST
117+ . try_with ( |fl| {
118+ let mut list = fl. take ( ) ;
119+ let bucket = & mut list. buckets [ len - 1 ] ;
120+ let stored = if bucket. len ( ) < Self :: MAX_FREELIST {
121+ bucket. push ( obj) ;
122+ true
123+ } else {
124+ false
125+ } ;
126+ fl. set ( list) ;
127+ stored
128+ } )
129+ . unwrap_or ( false )
130+ }
131+
132+ #[ inline]
133+ unsafe fn freelist_pop ( payload : & Self ) -> Option < NonNull < PyObject > > {
134+ let len = payload. elements . len ( ) ;
135+ if len == 0 || len > TupleFreeList :: MAXSAVESIZE {
136+ return None ;
137+ }
138+ TUPLE_FREELIST
139+ . try_with ( |fl| {
140+ let mut list = fl. take ( ) ;
141+ let result = list. buckets [ len - 1 ]
142+ . pop ( )
143+ . map ( |p| unsafe { NonNull :: new_unchecked ( p) } ) ;
144+ fl. set ( list) ;
145+ result
146+ } )
147+ . ok ( )
148+ . flatten ( )
149+ }
64150}
65151
66152pub trait IntoPyTuple {
0 commit comments