Skip to main content

mnemosyne_local/
selector.rs

1//! The thread-local cache selector macro and its per-backend
2//! instantiations.
3
4/// Helper macro to generate zero-cost backend-specific thread-local cache pools.
5#[macro_export]
6macro_rules! impl_local_allocator_selector {
7    ($backend:ty) => {
8        const _: () = {
9            // Under nightly `nightly_tls`, declare ALLOCATOR_SLOT with #[thread_local].
10            #[cfg(nightly_tls_active)]
11            #[thread_local]
12            static ALLOCATOR_SLOT: $crate::LocalAllocatorSlot<$backend> =
13                $crate::LocalAllocatorSlot::new();
14
15            // Under stable or non-nightly_tls, declare ALLOCATOR_SLOT via std::thread_local!.
16            #[cfg(not(nightly_tls_active))]
17            std::thread_local! {
18                static ALLOCATOR_SLOT: $crate::LocalAllocatorSlot<$backend> = const {
19                    $crate::LocalAllocatorSlot::new()
20                };
21            }
22
23            // Expose the slot access cells/guards needed by our TLS strategies.
24            std::thread_local! {
25                static CACHED_SLOT_PTR: core::cell::Cell<*mut core::ffi::c_void> = const {
26                    core::cell::Cell::new(core::ptr::null_mut())
27                };
28
29                #[cfg(nightly_tls_active)]
30                static ALLOCATOR_EXIT_GUARD: $crate::ThreadExitReclaim<$backend> = const {
31                    $crate::ThreadExitReclaim::new()
32                };
33            }
34
35            #[cfg(nightly_tls_active)]
36            #[thread_local]
37            static mut QUICK_ALLOCATOR_PTR: *mut core::ffi::c_void = core::ptr::null_mut();
38
39            static OS_TLS_KEY: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(u32::MAX);
40
41            struct SlotAccess;
42            impl $crate::tls::TlsSlotAccess<$backend> for SlotAccess {
43                #[inline(always)]
44                fn get_slot_standard<R>(f: impl FnOnce(&$crate::LocalAllocatorSlot<$backend>) -> R) -> R {
45                    #[cfg(nightly_tls_active)]
46                    {
47                        // In nightly `nightly_tls`, get_slot_standard falls back to the static reference.
48                        f(&ALLOCATOR_SLOT)
49                    }
50                    #[cfg(not(nightly_tls_active))]
51                    {
52                        ALLOCATOR_SLOT.with(f)
53                    }
54                }
55
56                #[inline(always)]
57                fn get_cached_cell<R>(f: impl FnOnce(&core::cell::Cell<*mut core::ffi::c_void>) -> R) -> R {
58                    CACHED_SLOT_PTR.with(f)
59                }
60
61                #[inline(always)]
62                fn arm_thread_exit(slot: &$crate::LocalAllocatorSlot<$backend>) {
63                    #[cfg(nightly_tls_active)]
64                    {
65                        $crate::arm_thread_exit(slot, &ALLOCATOR_EXIT_GUARD);
66                    }
67                    #[cfg(not(nightly_tls_active))]
68                    {
69                        // No-op for stable path: LocalAllocatorSlot is registered automatically by standard thread_local!.
70                        let _ = slot;
71                    }
72                }
73
74                #[inline(always)]
75                fn get_os_tls_key() -> &'static core::sync::atomic::AtomicU32 {
76                    &OS_TLS_KEY
77                }
78
79                #[cfg(nightly_tls_active)]
80                #[inline(always)]
81                fn get_slot_nightly<R>(f: impl FnOnce(&$crate::LocalAllocatorSlot<$backend>) -> R) -> R {
82                    f(&ALLOCATOR_SLOT)
83                }
84
85                #[cfg(nightly_tls_active)]
86                #[inline(always)]
87                fn get_quick_allocator_ptr() -> *mut core::ffi::c_void {
88                    // SAFETY: `QUICK_ALLOCATOR_PTR` is `#[thread_local]`, so this
89                    // reads the calling thread's own instance and no other thread
90                    // can observe or race it. The read copies a pointer value and
91                    // creates no reference, so it cannot alias a live borrow.
92                    unsafe { QUICK_ALLOCATOR_PTR }
93                }
94
95                #[cfg(nightly_tls_active)]
96                #[inline(always)]
97                fn set_quick_allocator_ptr(ptr: *mut core::ffi::c_void) {
98                    // SAFETY: as above -- a `#[thread_local]` static written by
99                    // its owning thread. The write stores a pointer value and
100                    // takes no reference, so no borrow of the static is live
101                    // across it.
102                    unsafe { QUICK_ALLOCATOR_PTR = ptr; }
103                }
104            }
105
106            // Statically select the best TLS provider based on compile target and features.
107            #[cfg(all(nightly_tls_active, not(miri)))]
108            type SelectedTls = $crate::tls::NightlyTls<$backend, SlotAccess>;
109
110            #[cfg(any(
111                miri,
112                all(not(nightly_tls_active), feature = "std_tls")
113            ))]
114            type SelectedTls = $crate::tls::CachedCellTls<$backend, SlotAccess>;
115
116            #[cfg(all(not(nightly_tls_active), not(feature = "std_tls"), all(windows, target_arch = "x86_64"), not(miri)))]
117            type SelectedTls = $crate::tls::AsmTls<$backend, SlotAccess>;
118
119            #[cfg(all(not(nightly_tls_active), not(feature = "std_tls"), any(not(all(windows, target_arch = "x86_64")), miri)))]
120            type SelectedTls = $crate::tls::NativeOsTls<$backend, SlotAccess>;
121
122            // The hardened policy gets a distinct cache so pages owned by the
123            // standard and encrypted policies cannot share one allocator's
124            // active-page lists. The two slots intentionally use the same TLS
125            // provider shape; only the slot identity changes.
126            #[cfg(nightly_tls_active)]
127            #[thread_local]
128            static ENCRYPTED_ALLOCATOR_SLOT: $crate::LocalAllocatorSlot<$backend> =
129                $crate::LocalAllocatorSlot::new();
130
131            #[cfg(not(nightly_tls_active))]
132            std::thread_local! {
133                static ENCRYPTED_ALLOCATOR_SLOT: $crate::LocalAllocatorSlot<$backend> = const {
134                    $crate::LocalAllocatorSlot::new()
135                };
136            }
137
138            std::thread_local! {
139                static ENCRYPTED_CACHED_SLOT_PTR: core::cell::Cell<*mut core::ffi::c_void> = const {
140                    core::cell::Cell::new(core::ptr::null_mut())
141                };
142
143                #[cfg(nightly_tls_active)]
144                static ENCRYPTED_ALLOCATOR_EXIT_GUARD: $crate::ThreadExitReclaim<$backend> = const {
145                    $crate::ThreadExitReclaim::new()
146                };
147            }
148
149            #[cfg(nightly_tls_active)]
150            #[thread_local]
151            static mut ENCRYPTED_QUICK_ALLOCATOR_PTR: *mut core::ffi::c_void = core::ptr::null_mut();
152
153            static ENCRYPTED_OS_TLS_KEY: core::sync::atomic::AtomicU32 =
154                core::sync::atomic::AtomicU32::new(u32::MAX);
155
156            struct EncryptedSlotAccess;
157            impl $crate::tls::TlsSlotAccess<$backend> for EncryptedSlotAccess {
158                #[inline(always)]
159                fn get_slot_standard<R>(
160                    f: impl FnOnce(&$crate::LocalAllocatorSlot<$backend>) -> R,
161                ) -> R {
162                    #[cfg(nightly_tls_active)]
163                    {
164                        f(&ENCRYPTED_ALLOCATOR_SLOT)
165                    }
166                    #[cfg(not(nightly_tls_active))]
167                    {
168                        ENCRYPTED_ALLOCATOR_SLOT.with(f)
169                    }
170                }
171
172                #[inline(always)]
173                fn get_cached_cell<R>(
174                    f: impl FnOnce(&core::cell::Cell<*mut core::ffi::c_void>) -> R,
175                ) -> R {
176                    ENCRYPTED_CACHED_SLOT_PTR.with(f)
177                }
178
179                #[inline(always)]
180                fn arm_thread_exit(slot: &$crate::LocalAllocatorSlot<$backend>) {
181                    #[cfg(nightly_tls_active)]
182                    {
183                        $crate::arm_thread_exit(slot, &ENCRYPTED_ALLOCATOR_EXIT_GUARD);
184                    }
185                    #[cfg(not(nightly_tls_active))]
186                    {
187                        let _ = slot;
188                    }
189                }
190
191                #[inline(always)]
192                fn get_os_tls_key() -> &'static core::sync::atomic::AtomicU32 {
193                    &ENCRYPTED_OS_TLS_KEY
194                }
195
196                #[cfg(nightly_tls_active)]
197                #[inline(always)]
198                fn get_slot_nightly<R>(
199                    f: impl FnOnce(&$crate::LocalAllocatorSlot<$backend>) -> R,
200                ) -> R {
201                    f(&ENCRYPTED_ALLOCATOR_SLOT)
202                }
203
204                #[cfg(nightly_tls_active)]
205                #[inline(always)]
206                fn get_quick_allocator_ptr() -> *mut core::ffi::c_void {
207                    // SAFETY: `ENCRYPTED_QUICK_ALLOCATOR_PTR` is `#[thread_local]`,
208                    // so this reads the calling thread's own instance. The read
209                    // copies a pointer value and creates no reference.
210                    unsafe { ENCRYPTED_QUICK_ALLOCATOR_PTR }
211                }
212
213                #[cfg(nightly_tls_active)]
214                #[inline(always)]
215                fn set_quick_allocator_ptr(ptr: *mut core::ffi::c_void) {
216                    // SAFETY: as above -- a `#[thread_local]` static written by
217                    // its owning thread, storing a pointer value with no
218                    // reference taken.
219                    unsafe { ENCRYPTED_QUICK_ALLOCATOR_PTR = ptr; }
220                }
221            }
222
223            #[cfg(all(nightly_tls_active, not(miri)))]
224            type EncryptedSelectedTls = $crate::tls::NightlyTls<$backend, EncryptedSlotAccess>;
225
226            #[cfg(any(
227                miri,
228                all(not(nightly_tls_active), feature = "std_tls")
229            ))]
230            type EncryptedSelectedTls =
231                $crate::tls::CachedCellTls<$backend, EncryptedSlotAccess>;
232
233            #[cfg(all(not(nightly_tls_active), not(feature = "std_tls"), all(windows, target_arch = "x86_64"), not(miri)))]
234            type EncryptedSelectedTls = $crate::tls::AsmTls<$backend, EncryptedSlotAccess>;
235
236            #[cfg(all(not(nightly_tls_active), not(feature = "std_tls"), any(not(all(windows, target_arch = "x86_64")), miri)))]
237            type EncryptedSelectedTls =
238                $crate::tls::NativeOsTls<$backend, EncryptedSlotAccess>;
239
240            impl $crate::tls_slot::PolicySlotSelection<$backend>
241                for mnemosyne_core::policy::StandardPolicy
242            {
243                #[inline(always)]
244                fn with_allocator<R>(
245                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
246                ) -> Option<R> {
247                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator(f)
248                }
249
250                #[inline(always)]
251                unsafe fn with_allocator_unguarded<R>(
252                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
253                ) -> Option<R> {
254                    // SAFETY: forwarded unchanged from this method's own
255                    // `unsafe` contract -- the caller guarantees `f` does not
256                    // re-enter this policy's slot, which is the obligation the
257                    // unarmed re-entrancy gate leaves to it.
258                    unsafe { <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator_unguarded(f) }
259                }
260
261                #[inline(always)]
262                fn get_allocator_ptr() -> *mut core::ffi::c_void {
263                    $crate::internal::ensure_options_initialized();
264                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr()
265                }
266
267                #[inline(always)]
268                fn get_allocator_ptr_raw() -> *mut core::ffi::c_void {
269                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
270                }
271            }
272
273            impl $crate::tls_slot::PolicySlotSelection<$backend>
274                for mnemosyne_core::policy::SecurePolicy
275            {
276                #[inline(always)]
277                fn with_allocator<R>(
278                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
279                ) -> Option<R> {
280                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator(f)
281                }
282
283                #[inline(always)]
284                unsafe fn with_allocator_unguarded<R>(
285                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
286                ) -> Option<R> {
287                    // SAFETY: forwarded unchanged from this method's own
288                    // `unsafe` contract -- the caller guarantees `f` does not
289                    // re-enter this policy's slot, which is the obligation the
290                    // unarmed re-entrancy gate leaves to it.
291                    unsafe { <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator_unguarded(f) }
292                }
293
294                #[inline(always)]
295                fn get_allocator_ptr() -> *mut core::ffi::c_void {
296                    $crate::internal::ensure_options_initialized();
297                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr()
298                }
299
300                #[inline(always)]
301                fn get_allocator_ptr_raw() -> *mut core::ffi::c_void {
302                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
303                }
304            }
305
306            impl $crate::tls_slot::PolicySlotSelection<$backend>
307                for mnemosyne_core::policy::HardenedPolicy
308            {
309                #[inline(always)]
310                fn with_allocator<R>(
311                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
312                ) -> Option<R> {
313                    <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator(f)
314                }
315
316                #[inline(always)]
317                unsafe fn with_allocator_unguarded<R>(
318                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
319                ) -> Option<R> {
320                    // SAFETY: forwarded unchanged from this method's own
321                    // `unsafe` contract -- the caller guarantees `f` does not
322                    // re-enter this policy's slot, which is the obligation the
323                    // unarmed re-entrancy gate leaves to it.
324                    unsafe { <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator_unguarded(f) }
325                }
326
327                #[inline(always)]
328                fn get_allocator_ptr() -> *mut core::ffi::c_void {
329                    $crate::internal::ensure_options_initialized();
330                    <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr()
331                }
332
333                #[inline(always)]
334                fn get_allocator_ptr_raw() -> *mut core::ffi::c_void {
335                    <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
336                }
337            }
338
339            impl $crate::LocalAllocatorSelector<$backend> for $backend {
340                #[inline(always)]
341                fn with_allocator<R>(
342                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
343                ) -> Option<R> {
344                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator(f)
345                }
346
347                #[inline(always)]
348                unsafe fn with_allocator_unguarded<R>(
349                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
350                ) -> Option<R> {
351                    // SAFETY: this function is itself `unsafe`, and its contract
352                    // is the provider's: `f` must not re-enter the allocator.
353                    // The obligation is forwarded to the caller unchanged.
354                    unsafe { <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator_unguarded(f) }
355                }
356
357                #[inline(always)]
358                fn get_allocator_ptr() -> *mut core::ffi::c_void {
359                    $crate::internal::ensure_options_initialized();
360                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr()
361                }
362
363                #[inline(always)]
364                fn get_allocator_ptr_raw() -> *mut core::ffi::c_void {
365                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
366                }
367
368                #[inline(always)]
369                fn register_current_allocator_ptr(ptr: *mut core::ffi::c_void) {
370                    <SelectedTls as $crate::tls::TlsProvider<$backend>>::register_current_allocator_ptr(ptr);
371                    <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::register_current_allocator_ptr(ptr);
372                }
373
374                #[inline(always)]
375                fn with_allocator_for_policy<P: mnemosyne_core::AllocPolicy, R>(
376                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
377                ) -> Option<R> {
378                    if P::ENABLE_FREE_LIST_ENCRYPTION {
379                        <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator(f)
380                    } else {
381                        <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator(f)
382                    }
383                }
384
385                #[inline(always)]
386                unsafe fn with_allocator_unguarded_for_policy<P: mnemosyne_core::AllocPolicy, R>(
387                    f: impl FnOnce(&mut $crate::ThreadAllocator<$backend>) -> R,
388                ) -> Option<R> {
389                    if P::ENABLE_FREE_LIST_ENCRYPTION {
390                        // SAFETY: this function is itself `unsafe` and carries the
391                        // provider's contract -- `f` must not re-enter the
392                        // allocator -- which is forwarded unchanged. The branch
393                        // only selects which provider owns the thread's cache.
394                        unsafe { <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator_unguarded(f) }
395                    } else {
396                        // SAFETY: as in the encrypted branch above.
397                        unsafe { <SelectedTls as $crate::tls::TlsProvider<$backend>>::with_allocator_unguarded(f) }
398                    }
399                }
400
401                #[inline(always)]
402                fn get_allocator_ptr_for_policy<P: mnemosyne_core::AllocPolicy>() -> *mut core::ffi::c_void {
403                    $crate::internal::ensure_options_initialized();
404                    if P::ENABLE_FREE_LIST_ENCRYPTION {
405                        <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr()
406                    } else {
407                        <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr()
408                    }
409                }
410
411                #[inline(always)]
412                fn get_allocator_ptr_raw_for_policy<P: mnemosyne_core::AllocPolicy>() -> *mut core::ffi::c_void {
413                    if P::ENABLE_FREE_LIST_ENCRYPTION {
414                        <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
415                    } else {
416                        <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
417                    }
418                }
419
420                #[inline(always)]
421                fn get_allocator_ptr_raw_for_encryption<const ENCRYPTED: bool>() -> *mut core::ffi::c_void {
422                    if ENCRYPTED {
423                        <EncryptedSelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
424                    } else {
425                        <SelectedTls as $crate::tls::TlsProvider<$backend>>::get_allocator_ptr_raw()
426                    }
427                }
428            }
429        };
430    };
431}
432
433impl_local_allocator_selector!(mnemosyne_backend::MemoryBackendWrapper);
434impl_local_allocator_selector!(mnemosyne_backend::CudaUnifiedBackend);
435impl_local_allocator_selector!(mnemosyne_backend::CudaDeviceBackend);
436impl_local_allocator_selector!(mnemosyne_backend::CudaHbmBackend);
437impl_local_allocator_selector!(mnemosyne_backend::CudaGddrBackend);
438impl_local_allocator_selector!(mnemosyne_backend::CudaHostPinnedBackend);