|
| 1 | +//! The thread-local allocation profiling stats are held in this module. |
| 2 | +//! The stats are used on the hot-path of allocation, so this code is |
| 3 | +//! performance sensitive. It is encapsulated so that some unsafe techniques |
| 4 | +//! can be used but expose a relatively safe API. |
| 5 | +
|
| 6 | +use super::AllocationProfilingStats; |
| 7 | +use libc::size_t; |
| 8 | +use std::cell::UnsafeCell; |
| 9 | +use std::mem::MaybeUninit; |
| 10 | + |
| 11 | +#[cfg(php_zend_mm_set_custom_handlers_ex)] |
| 12 | +use super::allocation_ge84; |
| 13 | +#[cfg(not(php_zend_mm_set_custom_handlers_ex))] |
| 14 | +use super::allocation_le83; |
| 15 | + |
| 16 | +thread_local! { |
| 17 | + /// This is initialized in ginit, before any memory allocator hooks are |
| 18 | + /// installed. During a request, all accesses will be initialized. |
| 19 | + /// |
| 20 | + /// This is not pub so that unsafe code can be contained to this module. |
| 21 | + static ALLOCATION_PROFILING_STATS: UnsafeCell<MaybeUninit<AllocationProfilingStats>> = |
| 22 | + const { UnsafeCell::new(MaybeUninit::uninit()) }; |
| 23 | +} |
| 24 | + |
| 25 | +/// Accesses the thread-local [`AllocationProfilingStats`], passing a mutable |
| 26 | +/// reference to the contained `MaybeUninit` to `F`. |
| 27 | +/// |
| 28 | +/// # Safety |
| 29 | +/// |
| 30 | +/// 1. There should not be any active borrows to the thread-local variable |
| 31 | +/// [`AllocationProfilingStats`] when this function is called. |
| 32 | +/// 2. Function `F` should not do anything which causes a new borrow on |
| 33 | +/// [`AllocationProfilingStats`]. |
| 34 | +/// 3. Do not call this function in ALLOCATION_PROFILING_STATS's destructor, |
| 35 | +/// as it assumes that [`std::thread::LocalKey::try_with`] cannot fail. |
| 36 | +/// |
| 37 | +/// This is not pub to limit caller's ability to violate these conditions. |
| 38 | +unsafe fn allocation_profiling_stats_mut<F, R>(f: F) -> R |
| 39 | +where |
| 40 | + F: FnOnce(&mut MaybeUninit<AllocationProfilingStats>) -> R, |
| 41 | +{ |
| 42 | + let result = ALLOCATION_PROFILING_STATS.try_with(|cell| { |
| 43 | + let ptr: *mut MaybeUninit<AllocationProfilingStats> = cell.get(); |
| 44 | + // SAFETY: the cell is statically initialized to [`MaybeUninit::uninit`] so the |
| 45 | + // _cell_ is valid and initialized memory. As required by this own |
| 46 | + // function's safety requirements, there should not be any active borrows |
| 47 | + // to [`ALLOCATION_PROFILING_STATS`], so this mutable dereference is sound. |
| 48 | + let uninit = unsafe { &mut *ptr }; |
| 49 | + f(uninit) |
| 50 | + }); |
| 51 | + unsafe { |
| 52 | + // SAFETY: this function is not called in a destructor, therefore it |
| 53 | + // cannot return an AccessError: |
| 54 | + // > If the key has been destroyed (which may happen if this is called |
| 55 | + // > in a destructor), this function will return an AccessError. |
| 56 | + result.unwrap_unchecked() |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +/// Given the provided allocation length `len`, return whether the allocation |
| 61 | +/// should be collected. This is a mutable operation, as the thread-local |
| 62 | +/// variable will be modified to reduce the distance until the next sample. |
| 63 | +pub fn allocation_profiling_stats_should_collect(len: size_t) -> bool { |
| 64 | + let f = |maybe_uninit: &mut MaybeUninit<AllocationProfilingStats>| { |
| 65 | + // SAFETY: ALLOCATION_PROFILING_STATS was initialized in GINIT. |
| 66 | + let stats = unsafe { maybe_uninit.assume_init_mut() }; |
| 67 | + stats.should_collect_allocation(len) |
| 68 | + }; |
| 69 | + |
| 70 | + // SAFETY: |
| 71 | + // 1. This function doesn't expose any way for the caller to keep a |
| 72 | + // borrow alive, nor do the other public functions, so there cannot be |
| 73 | + // any existing borrows alive. |
| 74 | + // 2. This closure will not cause any new borrows. |
| 75 | + // 3. This function isn't called during ALLOCATION_PROFILING_STATS's dtor, |
| 76 | + // as MaybeUninit's destructor does nothing, you have to specifically drop |
| 77 | + // it. Even if the destructor were called, AllocationProfilingStats's dtor |
| 78 | + // doesn't access the TLS variable (it can't, it doesn't have access). |
| 79 | + unsafe { allocation_profiling_stats_mut(f) } |
| 80 | +} |
| 81 | + |
| 82 | +/// Initializes the allocation profiler's globals. |
| 83 | +/// |
| 84 | +/// # Safety |
| 85 | +/// |
| 86 | +/// Must be called once per PHP thread ginit. |
| 87 | +pub unsafe fn ginit() { |
| 88 | + // SAFETY: |
| 89 | + // 1. During ginit, there will not be any other borrows. |
| 90 | + // 2. This closure will not make new borrows. |
| 91 | + // 3. This is not during the thread-local destructor. |
| 92 | + unsafe { |
| 93 | + allocation_profiling_stats_mut(|uninit| { |
| 94 | + uninit.write(AllocationProfilingStats::new()); |
| 95 | + }) |
| 96 | + }; |
| 97 | + |
| 98 | + #[cfg(not(php_zend_mm_set_custom_handlers_ex))] |
| 99 | + allocation_le83::alloc_prof_ginit(); |
| 100 | + #[cfg(php_zend_mm_set_custom_handlers_ex)] |
| 101 | + allocation_ge84::alloc_prof_ginit(); |
| 102 | +} |
| 103 | + |
| 104 | +/// Shuts down the allocation profiler's globals. |
| 105 | +/// |
| 106 | +/// # Safety |
| 107 | +/// |
| 108 | +/// Must be called once per PHP thread gshutdown. |
| 109 | +pub unsafe fn gshutdown() { |
| 110 | + #[cfg(php_zend_mm_set_custom_handlers_ex)] |
| 111 | + allocation_ge84::alloc_prof_gshutdown(); |
| 112 | + |
| 113 | + // SAFETY: |
| 114 | + // 1. During gshutdown, there will not be any other borrows. |
| 115 | + // 2. This closure will not make new borrows. |
| 116 | + // 3. This is not during the thread-local destructor. |
| 117 | + unsafe { allocation_profiling_stats_mut(|maybe_uninit| maybe_uninit.assume_init_drop()) } |
| 118 | +} |
0 commit comments