Skip to content

fix(prof) follow PHP globals model #3175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
236 changes: 121 additions & 115 deletions profiling/src/allocation/allocation_ge84.rs
Original file line number Diff line number Diff line change
@@ -1,100 +1,17 @@
use crate::allocation::ALLOCATION_PROFILING_COUNT;
use crate::allocation::ALLOCATION_PROFILING_SIZE;
use crate::allocation::ALLOCATION_PROFILING_STATS;
use crate::allocation::{
tls_zend_mm_state, ZendMMState, ALLOCATION_PROFILING_COUNT, ALLOCATION_PROFILING_SIZE,
ALLOCATION_PROFILING_STATS, ZEND_MM_STATE,
};
use crate::bindings::{self as zend};
use crate::PROFILER_NAME;
use lazy_static::lazy_static;
use libc::{c_char, c_void, size_t};
use log::{debug, error, trace, warn};
use std::cell::UnsafeCell;
use std::ptr;
use std::sync::atomic::Ordering::SeqCst;

struct ZendMMState {
/// The heap we create and set as the current heap in ZendMM
heap: *mut zend::zend_mm_heap,
/// The heap installed in ZendMM at the time we install our custom handlers
prev_heap: *mut zend::zend_mm_heap,
/// The engine's previous custom allocation function, if there is one.
prev_custom_mm_alloc: Option<zend::VmMmCustomAllocFn>,
/// The engine's previous custom reallocation function, if there is one.
prev_custom_mm_realloc: Option<zend::VmMmCustomReallocFn>,
/// The engine's previous custom free function, if there is one.
prev_custom_mm_free: Option<zend::VmMmCustomFreeFn>,
/// The engine's previous custom gc function, if there is one.
prev_custom_mm_gc: Option<zend::VmMmCustomGcFn>,
/// The engine's previous custom shutdown function, if there is one.
prev_custom_mm_shutdown: Option<zend::VmMmCustomShutdownFn>,
/// Safety: this function pointer is only allowed to point to
/// `alloc_prof_prev_alloc()` when at the same time the
/// `ZEND_MM_STATE.prev_custom_mm_alloc` is initialised to a valid function
/// pointer, otherwise there will be dragons.
alloc: unsafe fn(size_t) -> *mut c_void,
/// Safety: this function pointer is only allowed to point to
/// `alloc_prof_prev_realloc()` when at the same time the
/// `ZEND_MM_STATE.prev_custom_mm_realloc` is initialised to a valid
/// function pointer, otherwise there will be dragons.
realloc: unsafe fn(*mut c_void, size_t) -> *mut c_void,
/// Safety: this function pointer is only allowed to point to
/// `alloc_prof_prev_free()` when at the same time the
/// `ZEND_MM_STATE.prev_custom_mm_free` is initialised to a valid function
/// pointer, otherwise there will be dragons.
free: unsafe fn(*mut c_void),
/// Safety: this function pointer is only allowed to point to
/// `alloc_prof_prev_gc()` when at the same time the
/// `ZEND_MM_STATE.prev_custom_mm_gc` is initialised to a valid function
/// pointer, otherwise there will be dragons.
gc: unsafe fn() -> size_t,
/// Safety: this function pointer is only allowed to point to
/// `alloc_prof_prev_shutdown()` when at the same time the
/// `ZEND_MM_STATE.prev_custom_mm_shutdown` is initialised to a valid function
/// pointer, otherwise there will be dragons.
shutdown: unsafe fn(bool, bool),
}

impl ZendMMState {
const fn new() -> ZendMMState {
ZendMMState {
// Safety: Using `ptr::null_mut()` might seem dangerous but actually it is okay in this
// case. The `heap` and `prev_heap` fields will be initialized in the first call to
// RINIT and only used after that. By using this "trick" we can get rid of all
// `unwrap()` calls when using the `heap` or `prev_heap` field. Alternatively we could
// use `unwrap_unchecked()` for the same performance characteristics.
heap: ptr::null_mut(),
prev_heap: ptr::null_mut(),
prev_custom_mm_alloc: None,
prev_custom_mm_realloc: None,
prev_custom_mm_free: None,
prev_custom_mm_gc: None,
prev_custom_mm_shutdown: None,
alloc: alloc_prof_orig_alloc,
realloc: alloc_prof_orig_realloc,
free: alloc_prof_orig_free,
gc: alloc_prof_orig_gc,
shutdown: alloc_prof_orig_shutdown,
}
}
}

impl ZendMMState {}

thread_local! {
/// Using an `UnsafeCell` here should be okay. There might not be any
/// synchronisation issues, as it is used in as thread local and only
/// mutated in RINIT and RSHUTDOWN.
static ZEND_MM_STATE: UnsafeCell<ZendMMState> = const {
UnsafeCell::new(ZendMMState::new())
};
}

macro_rules! tls_zend_mm_state {
($x:ident) => {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
(*zend_mm_state).$x
})
};
}
#[cfg(not(php_zts))]
use std::ptr::addr_of_mut;

lazy_static! {
static ref JIT_ENABLED: bool = unsafe { zend::ddog_php_jit_enabled() };
Expand All @@ -116,9 +33,7 @@ pub fn first_rinit_should_disable_due_to_jit() -> bool {
/// This function may panic if called out of order!
pub fn alloc_prof_ginit() {
unsafe { zend::ddog_php_opcache_init_handle() };
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();

let zend_mm_state_init = |zend_mm_state: *mut ZendMMState| {
// Only need to create an observed heap once per thread. When we have it, we can just
// install the observed heap via `zend::zend_mm_set_heap()`
if unsafe { !(*zend_mm_state).heap.is_null() } {
Expand Down Expand Up @@ -158,6 +73,14 @@ pub fn alloc_prof_ginit() {
ptr::addr_of_mut!((*zend_mm_state).shutdown).write(alloc_prof_prev_shutdown);
}
}
} else {
unsafe {
ptr::addr_of_mut!((*zend_mm_state).alloc).write(alloc_prof_orig_alloc);
ptr::addr_of_mut!((*zend_mm_state).free).write(alloc_prof_orig_free);
ptr::addr_of_mut!((*zend_mm_state).realloc).write(alloc_prof_orig_realloc);
ptr::addr_of_mut!((*zend_mm_state).gc).write(alloc_prof_orig_gc);
ptr::addr_of_mut!((*zend_mm_state).shutdown).write(alloc_prof_orig_shutdown);
}
}

// Create a new (to be observed) heap and prepare custom handlers
Expand All @@ -176,16 +99,26 @@ pub fn alloc_prof_ginit() {
);
}
debug!("New observed heap created");
};

#[cfg(php_zts)]
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
zend_mm_state_init(zend_mm_state);
});

#[cfg(not(php_zts))]
unsafe {
zend_mm_state_init(addr_of_mut!(ZEND_MM_STATE));
}
}

/// This resets the thread locale variable `ZEND_MM_STATE` and frees allocated memory. It
/// guarantees compliance with the safety guarantees described in the `ZendMMState` structure,
/// specifically for `ZendMMState::alloc`, `ZendMMState::realloc`, `ZendMMState::free`,
/// `ZendMMState::gc` and `ZendMMState::shutdown`.
pub fn alloc_prof_gshutdown() {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
let zend_mm_state_shutdown = |zend_mm_state: *mut ZendMMState| {
unsafe {
// Remove custom handlers to allow for ZendMM internal shutdown
zend::zend_mm_set_custom_handlers_ex(
Expand Down Expand Up @@ -218,10 +151,22 @@ pub fn alloc_prof_gshutdown() {
ptr::addr_of_mut!((*zend_mm_state).prev_heap).write(ptr::null_mut());
}
trace!("Observed heap was freed and `zend_mm_state` reset");
};

#[cfg(php_zts)]
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
zend_mm_state_shutdown(zend_mm_state);
});

#[cfg(not(php_zts))]
unsafe {
zend_mm_state_shutdown(addr_of_mut!(ZEND_MM_STATE));
}
}

pub fn alloc_prof_rinit() {
#[cfg(php_zts)]
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
// Safety: `zend_mm_state.heap` got initialized in `MINIT` and is guaranteed to
Expand All @@ -232,6 +177,12 @@ pub fn alloc_prof_rinit() {
}
});

#[cfg(not(php_zts))]
// Safety: see above
unsafe {
zend::zend_mm_set_heap(ZEND_MM_STATE.heap);
}

// `is_zend_mm()` should be false now, as we installed our custom handlers
if is_zend_mm() {
// Can't proceed with it being disabled, because that's a system-wide
Expand All @@ -250,9 +201,7 @@ pub fn alloc_prof_rshutdown() {
return;
}

ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();

let zend_mm_state_shutdown = |zend_mm_state: *mut ZendMMState| {
// Do a sanity check and see if something played with our heap
let mut custom_mm_malloc: Option<zend::VmMmCustomAllocFn> = None;
let mut custom_mm_free: Option<zend::VmMmCustomFreeFn> = None;
Expand Down Expand Up @@ -299,13 +248,22 @@ pub fn alloc_prof_rshutdown() {
} else {
// This is the happy path. Restore previous heap.
unsafe {
zend::zend_mm_set_heap(
(*zend_mm_state).prev_heap
);
zend::zend_mm_set_heap((*zend_mm_state).prev_heap);
}
trace!("Memory allocation profiling shutdown gracefully.");
}
};

#[cfg(php_zts)]
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
zend_mm_state_shutdown(zend_mm_state);
});

#[cfg(not(php_zts))]
unsafe {
zend_mm_state_shutdown(addr_of_mut!(ZEND_MM_STATE));
}
}

unsafe extern "C" fn alloc_prof_malloc(len: size_t) -> *mut c_void {
Expand All @@ -329,8 +287,7 @@ unsafe extern "C" fn alloc_prof_malloc(len: size_t) -> *mut c_void {
}

unsafe fn alloc_prof_prev_alloc(len: size_t) -> *mut c_void {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
let alloc = |zend_mm_state: *mut ZendMMState| {
// Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).prev_heap);
// Safety: `ZEND_MM_STATE.prev_custom_mm_alloc` will be initialised in
Expand All @@ -340,7 +297,18 @@ unsafe fn alloc_prof_prev_alloc(len: size_t) -> *mut c_void {
// Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).heap);
ptr
})
};

#[cfg(php_zts)]
return ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
alloc(zend_mm_state)
});

#[cfg(not(php_zts))]
unsafe {
alloc(addr_of_mut!(ZEND_MM_STATE))
}
}

unsafe fn alloc_prof_orig_alloc(len: size_t) -> *mut c_void {
Expand All @@ -357,8 +325,7 @@ unsafe extern "C" fn alloc_prof_free(ptr: *mut c_void) {
}

unsafe fn alloc_prof_prev_free(ptr: *mut c_void) {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
let free = |zend_mm_state: *mut ZendMMState| {
// Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).prev_heap);
// Safety: `ZEND_MM_STATE.prev_custom_mm_free` will be initialised in
Expand All @@ -367,7 +334,18 @@ unsafe fn alloc_prof_prev_free(ptr: *mut c_void) {
((*zend_mm_state).prev_custom_mm_free.unwrap_unchecked())(ptr);
// Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).heap);
})
};

#[cfg(php_zts)]
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
free(zend_mm_state);
});

#[cfg(not(php_zts))]
unsafe {
free(addr_of_mut!(ZEND_MM_STATE));
}
}

unsafe fn alloc_prof_orig_free(ptr: *mut c_void) {
Expand Down Expand Up @@ -395,8 +373,7 @@ unsafe extern "C" fn alloc_prof_realloc(prev_ptr: *mut c_void, len: size_t) -> *
}

unsafe fn alloc_prof_prev_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
let realloc = |zend_mm_state: *mut ZendMMState| {
// Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).prev_heap);
// Safety: `ZEND_MM_STATE.prev_custom_mm_realloc` will be initialised in
Expand All @@ -406,7 +383,18 @@ unsafe fn alloc_prof_prev_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_
// Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).heap);
ptr
})
};

#[cfg(php_zts)]
return ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
realloc(zend_mm_state)
});

#[cfg(not(php_zts))]
unsafe {
realloc(addr_of_mut!(ZEND_MM_STATE))
}
}

unsafe fn alloc_prof_orig_realloc(prev_ptr: *mut c_void, len: size_t) -> *mut c_void {
Expand All @@ -418,8 +406,7 @@ unsafe extern "C" fn alloc_prof_gc() -> size_t {
}

unsafe fn alloc_prof_prev_gc() -> size_t {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
let gc = |zend_mm_state: *mut ZendMMState| {
// Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).prev_heap);
// Safety: `ZEND_MM_STATE.prev_custom_mm_gc` will be initialised in
Expand All @@ -429,7 +416,18 @@ unsafe fn alloc_prof_prev_gc() -> size_t {
// Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).heap);
freed
})
};

#[cfg(php_zts)]
return ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
gc(zend_mm_state)
});

#[cfg(not(php_zts))]
unsafe {
gc(addr_of_mut!(ZEND_MM_STATE))
}
}

unsafe fn alloc_prof_orig_gc() -> size_t {
Expand All @@ -441,8 +439,7 @@ unsafe extern "C" fn alloc_prof_shutdown(full: bool, silent: bool) {
}

unsafe fn alloc_prof_prev_shutdown(full: bool, silent: bool) {
ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
let shutdown = |zend_mm_state: *mut ZendMMState| {
// Safety: `ZEND_MM_STATE.prev_heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).prev_heap);
// Safety: `ZEND_MM_STATE.prev_custom_mm_shutdown` will be initialised in
Expand All @@ -451,7 +448,16 @@ unsafe fn alloc_prof_prev_shutdown(full: bool, silent: bool) {
((*zend_mm_state).prev_custom_mm_shutdown.unwrap_unchecked())(full, silent);
// Safety: `ZEND_MM_STATE.heap` got initialised in `alloc_prof_rinit()`
zend::zend_mm_set_heap((*zend_mm_state).heap);
})
};

#[cfg(php_zts)]
return ZEND_MM_STATE.with(|cell| {
let zend_mm_state = cell.get();
shutdown(zend_mm_state)
});

#[cfg(not(php_zts))]
shutdown(addr_of_mut!(ZEND_MM_STATE))
}

unsafe fn alloc_prof_orig_shutdown(full: bool, silent: bool) {
Expand Down
Loading
Loading