-
Notifications
You must be signed in to change notification settings - Fork 182
Expand file tree
/
Copy pathprofiling_stats.rs
More file actions
184 lines (170 loc) · 7.34 KB
/
Copy pathprofiling_stats.rs
File metadata and controls
184 lines (170 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! The thread-local allocation profiling stats are held in this module.
//! The stats are used on the hot-path of allocation, so this code is
//! performance sensitive. It is encapsulated so that some unsafe techniques
//! can be used but expose a relatively safe API.
use super::{AllocationProfilingStats, ALLOCATION_PROFILING_INTERVAL};
use crate::profiling::live_heap::LiveHeapTracker;
use libc::size_t;
use std::mem::MaybeUninit;
#[cfg(php_zend_mm_set_custom_handlers_ex)]
use super::allocation_ge84;
#[cfg(not(php_zend_mm_set_custom_handlers_ex))]
use super::allocation_le83;
#[cfg(php_zts)]
use std::cell::UnsafeCell;
use std::num::NonZeroU64;
use std::sync::atomic::Ordering;
#[cfg(php_zts)]
thread_local! {
/// This is initialized in ginit, before any memory allocator hooks are
/// installed. During a request, all accesses will be initialized.
///
/// This is not pub so that unsafe code can be contained to this module.
static ALLOCATION_PROFILING_STATS: UnsafeCell<MaybeUninit<AllocationProfilingStats>> =
const { UnsafeCell::new(MaybeUninit::uninit()) };
}
#[cfg(not(php_zts))]
static mut ALLOCATION_PROFILING_STATS: MaybeUninit<AllocationProfilingStats> =
const { MaybeUninit::uninit() };
/// Accesses the thread-local [`AllocationProfilingStats`], passing a mutable
/// reference to the contained `MaybeUninit` to `F`.
///
/// # Safety
///
/// 1. There should not be any active borrows to the thread-local variable
/// [`AllocationProfilingStats`] when this function is called.
/// 2. Function `F` should not do anything which causes a new borrow on
/// [`AllocationProfilingStats`].
/// 3. Do not call this function in ALLOCATION_PROFILING_STATS's destructor,
/// as it assumes that [`std::thread::LocalKey::try_with`] cannot fail.
///
/// This is not pub to limit caller's ability to violate these conditions.
unsafe fn allocation_profiling_stats_mut<F, R>(f: F) -> R
where
F: FnOnce(&mut MaybeUninit<AllocationProfilingStats>) -> R,
{
#[cfg(php_zts)]
{
let result = ALLOCATION_PROFILING_STATS.try_with(|cell| {
let ptr: *mut MaybeUninit<AllocationProfilingStats> = cell.get();
// SAFETY: the cell is statically initialized to [`MaybeUninit::uninit`] so the
// _cell_ is valid and initialized memory. As required by this own
// function's safety requirements, there should not be any active borrows
// to [`ALLOCATION_PROFILING_STATS`], so this mutable dereference is sound.
let uninit = unsafe { &mut *ptr };
f(uninit)
});
// SAFETY: this function is not called in a destructor, therefore it
// cannot return an AccessError:
// > If the key has been destroyed (which may happen if this is called
// > in a destructor), this function will return an AccessError.
unsafe { result.unwrap_unchecked() }
}
#[cfg(not(php_zts))]
{
// SAFETY: For non-ZTS builds, ALLOCATION_PROFILING_STATS is a static variable.
// As required by this function's safety requirements, there should not be any
// active borrows to ALLOCATION_PROFILING_STATS, so this mutable reference is sound.
let uninit = unsafe {
let ptr: *mut MaybeUninit<AllocationProfilingStats> =
std::ptr::addr_of_mut!(ALLOCATION_PROFILING_STATS);
&mut *ptr
};
f(uninit)
}
}
/// Given the provided allocation length `len`, return whether the allocation
/// should be collected. This is a mutable operation, as the thread-local
/// variable will be modified to reduce the distance until the next sample.
pub fn allocation_profiling_stats_should_collect(len: size_t) -> bool {
let f = |maybe_uninit: &mut MaybeUninit<AllocationProfilingStats>| {
// SAFETY: ALLOCATION_PROFILING_STATS was initialized in GINIT.
let stats = unsafe { maybe_uninit.assume_init_mut() };
stats.should_collect_allocation(len)
};
// SAFETY:
// 1. This function doesn't expose any way for the caller to keep a
// borrow alive, nor do the other public functions, so there cannot be
// any existing borrows alive.
// 2. This closure will not cause any new borrows.
// 3. This function isn't called during ALLOCATION_PROFILING_STATS's dtor,
// as MaybeUninit's destructor does nothing, you have to specifically drop
// it. Even if the destructor were called, AllocationProfilingStats's dtor
// doesn't access the TLS variable (it can't, it doesn't have access).
unsafe { allocation_profiling_stats_mut(f) }
}
pub(crate) fn live_heap_track<T>(tracker: &LiveHeapTracker<T>, ptr: usize, sample: T) -> bool {
// SAFETY: allocation stats are initialized before allocation hooks run,
// and the closure does not retain or recursively borrow them.
unsafe {
allocation_profiling_stats_mut(|stats| {
stats
.assume_init_mut()
.live_heap
.track(tracker, ptr, sample)
})
}
}
pub(crate) fn live_heap_untrack<T>(tracker: &LiveHeapTracker<T>, ptr: usize) -> Option<T> {
// SAFETY: same lifecycle and borrowing guarantees as `live_heap_track`.
unsafe {
allocation_profiling_stats_mut(|stats| {
stats.assume_init_mut().live_heap.untrack(tracker, ptr)
})
}
}
/// Initializes the allocation profiler's globals.
///
/// # Safety
///
/// Must be called once per PHP thread ginit.
pub unsafe fn ginit() {
// SAFETY:
// 1. During ginit, there will not be any other borrows to stats.
// 2. This closure will not make new borrows to stats.
// 3. This is not during the thread-local destructor.
unsafe {
allocation_profiling_stats_mut(|uninit| {
let interval = ALLOCATION_PROFILING_INTERVAL.load(Ordering::Relaxed);
// SAFETY: ALLOCATION_PROFILING_INTERVAL must always be > 0.
let nonzero = NonZeroU64::new_unchecked(interval);
uninit.write(AllocationProfilingStats::new(nonzero));
})
};
#[cfg(not(php_zend_mm_set_custom_handlers_ex))]
allocation_le83::alloc_prof_ginit();
#[cfg(php_zend_mm_set_custom_handlers_ex)]
allocation_ge84::alloc_prof_ginit();
}
/// Initializes the allocation profiler's globals with the provided sampling
/// distance.
///
/// # Safety
///
/// Must be called once per PHP thread minit, unless the allocation profiling
/// is disabled, in which case it can be skipped.
pub unsafe fn minit(sampling_distance: NonZeroU64) {
// SAFETY:
// 1. During minit, there will not be any other borrows.
// 2. This closure will not make new borrows.
// 3. This is not during the thread-local destructor.
unsafe {
allocation_profiling_stats_mut(|uninit| {
// SAFETY: previously initialized in ginit, we're just
// re-initializing it because we now have config
*uninit.assume_init_mut() = AllocationProfilingStats::new(sampling_distance);
})
};
}
/// Shuts down the allocation profiler's globals.
///
/// # Safety
///
/// Must be called once per PHP thread gshutdown.
pub unsafe fn gshutdown() {
// SAFETY:
// 1. During gshutdown, there will not be any other borrows.
// 2. This closure will not make new borrows.
// 3. This is not during the thread-local destructor.
unsafe { allocation_profiling_stats_mut(|maybe_uninit| maybe_uninit.assume_init_drop()) }
}