From 4d8c59878e9d9e4d1ec0a08955613d7a4555f5d8 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Tue, 18 Jun 2024 15:32:46 -0600 Subject: [PATCH 1/2] feat(profiling): remove execute_internal hook This does some... uh... let's say "clever" things to avoid using the zend_execute_internal hook. In PHP 8.4, that would prevent frameless function call optimizations from being used. Using the hook also has performance costs even on older versions. --- Cargo.toml | 2 +- profiling/build.rs | 15 +++++ profiling/src/bindings/mod.rs | 9 +-- profiling/src/capi.rs | 15 +++-- profiling/src/lib.rs | 83 +++++++++++++++++--------- profiling/src/php_ffi.c | 17 +++++- profiling/src/php_ffi.h | 5 -- profiling/src/profiling/interrupts.rs | 86 ++++++++++++++++++--------- profiling/src/profiling/mod.rs | 25 ++++---- profiling/src/wall_time.rs | 29 ++++++++- 10 files changed, 203 insertions(+), 83 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a0454a4e5..7dc35d3bef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ debug = 2 # full debug info [profile.release] codegen-units = 1 -debug = "line-tables-only" +debug = 2 #todo: revert back incremental = false lto = "fat" diff --git a/profiling/build.rs b/profiling/build.rs index 44a903ac07..735df146e7 100644 --- a/profiling/build.rs +++ b/profiling/build.rs @@ -27,6 +27,7 @@ fn main() { let vernum = php_config_vernum(); let post_startup_cb = cfg_post_startup_cb(vernum); let preload = cfg_preload(vernum); + let good_closure_invoke = cfg_good_closure_invoke(vernum); let fibers = cfg_fibers(vernum); let run_time_cache = cfg_run_time_cache(vernum); let trigger_time_sample = cfg_trigger_time_sample(); @@ -37,6 +38,7 @@ fn main() { post_startup_cb, preload, run_time_cache, + good_closure_invoke, fibers, trigger_time_sample, vernum, @@ -83,11 +85,13 @@ const ZAI_H_FILES: &[&str] = &[ "../zend_abstract_interface/json/json.h", ]; +#[allow(clippy::too_many_arguments)] fn build_zend_php_ffis( php_config_includes: &str, post_startup_cb: bool, preload: bool, run_time_cache: bool, + good_closure_invoke: bool, fibers: bool, trigger_time_sample: bool, vernum: u64, @@ -132,6 +136,7 @@ fn build_zend_php_ffis( let files = ["src/php_ffi.c", "../ext/handlers_api.c"]; let post_startup_cb = if post_startup_cb { "1" } else { "0" }; let preload = if preload { "1" } else { "0" }; + let good_closure_invoke = if good_closure_invoke { "1" } else { "0" }; let fibers = if fibers { "1" } else { "0" }; let run_time_cache = if run_time_cache { "1" } else { "0" }; let trigger_time_sample = if trigger_time_sample { "1" } else { "0" }; @@ -146,6 +151,7 @@ fn build_zend_php_ffis( .files(files.into_iter().chain(zai_c_files)) .define("CFG_POST_STARTUP_CB", post_startup_cb) .define("CFG_PRELOAD", preload) + .define("CFG_GOOD_CLOSURE_INVOKE", good_closure_invoke) .define("CFG_FIBERS", fibers) .define("CFG_RUN_TIME_CACHE", run_time_cache) .define("CFG_STACK_WALKING_TESTS", stack_walking_tests) @@ -310,6 +316,15 @@ fn cfg_php_major_version(vernum: u64) { println!("cargo:rustc-cfg=php{major_version}"); } +fn cfg_good_closure_invoke(vernum: u64) -> bool { + if vernum >= 80300 { + println!("cargo:rustc-cfg=php_good_closure_invoke"); + true + } else { + false + } +} + fn cfg_fibers(vernum: u64) -> bool { if vernum >= 80100 { println!("cargo:rustc-cfg=php_has_fibers"); diff --git a/profiling/src/bindings/mod.rs b/profiling/src/bindings/mod.rs index b4b9fe0814..d4f4acc10f 100644 --- a/profiling/src/bindings/mod.rs +++ b/profiling/src/bindings/mod.rs @@ -6,7 +6,6 @@ use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, c_void, size_t}; use std::borrow::Cow; use std::ffi::CStr; use std::marker::PhantomData; -use std::sync::atomic::AtomicBool; use std::{ptr, str}; extern "C" { @@ -127,7 +126,7 @@ impl _zend_function { /// Returns the module name, if there is one. May return Some(b"\0"). pub fn module_name(&self) -> Option<&[u8]> { if self.is_internal() { - // Safety: union access is guarded by is_internal(), and assume + // SAFETY: union access is guarded by is_internal(), and assume // its module is valid. unsafe { self.internal_function.module.as_ref() } .filter(|module| !module.name.is_null()) @@ -288,10 +287,11 @@ impl Default for ZendExtension { } extern "C" { - /// Retrieves the VM interrupt address of the calling PHP thread. + /// Retrieves the addresses of various parts of executor_globals. + /// /// # Safety /// Must be called from a PHP thread during a request. - pub fn datadog_php_profiling_vm_interrupt_addr() -> *const AtomicBool; + pub fn ddog_php_prof_executor_global_addrs() -> ExecutorGlobalAddrs; /// Registers the extension. Note that it's kept in a zend_llist and gets /// pemalloc'd + memcpy'd into place. The engine says this is a mutable @@ -347,6 +347,7 @@ extern "C" { } use crate::config::ConfigId; +use crate::ExecutorGlobalAddrs; pub use zend_module_dep as ModuleDep; impl ModuleDep { diff --git a/profiling/src/capi.rs b/profiling/src/capi.rs index d906dda5c1..a32a513f56 100644 --- a/profiling/src/capi.rs +++ b/profiling/src/capi.rs @@ -42,11 +42,16 @@ extern "C" fn ddog_php_prof_trigger_time_sample() { super::REQUEST_LOCALS.with(|cell| { if let Ok(locals) = cell.try_borrow() { if locals.system_settings().profiling_enabled { - // Safety: only vm interrupts are stored there, or possibly null (edges only). - if let Some(vm_interrupt) = unsafe { locals.vm_interrupt_addr.as_ref() } { - locals.interrupt_count.fetch_add(1, Ordering::SeqCst); - vm_interrupt.store(true, Ordering::SeqCst); - } + // Safety: only vm interrupts are stored there. + let vm_interrupt = unsafe { locals.executor_global_addrs.vm_interrupt.as_ref() }; + let current_execute_data_cache = + unsafe { *locals.executor_global_addrs.current_execute_data.as_ptr() }; + + locals.interrupt_count.fetch_add(1, Ordering::SeqCst); + locals + .current_execute_data_cache + .store(current_execute_data_cache, Ordering::SeqCst); + vm_interrupt.store(true, Ordering::SeqCst); } } }) diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs index 32aaf3647d..58f61c2523 100644 --- a/profiling/src/lib.rs +++ b/profiling/src/lib.rs @@ -21,10 +21,11 @@ mod exception; #[cfg(feature = "timeline")] mod timeline; +use crate::bindings::ddog_php_prof_executor_global_addrs; use crate::config::{SystemSettings, INITIAL_SYSTEM_SETTINGS}; use bindings::{ - self as zend, ddog_php_prof_php_version, ddog_php_prof_php_version_id, ZendExtension, - ZendResult, + self as zend, ddog_php_prof_php_version, ddog_php_prof_php_version_id, zend_execute_data, zval, + ZendExtension, ZendResult, }; use clocks::*; use core::ptr; @@ -33,14 +34,14 @@ use lazy_static::lazy_static; use libc::c_char; use log::{debug, error, info, trace, warn}; use once_cell::sync::{Lazy, OnceCell}; -use profiling::{LocalRootSpanResourceMessage, Profiler, VmInterrupt}; +use profiling::{LocalRootSpanResourceMessage, Profiler}; use sapi::Sapi; use std::borrow::Cow; use std::cell::RefCell; use std::ffi::CStr; use std::ops::Deref; use std::os::raw::c_int; -use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering}; use std::sync::{Arc, Mutex, Once}; use std::time::{Duration, Instant}; use uuid::Uuid; @@ -333,6 +334,23 @@ extern "C" fn prshutdown() -> ZendResult { ZendResult::Success } +// Keep in-sync with php_ffi.c. +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub struct ExecutorGlobalAddrs { + pub vm_stack_top: ptr::NonNull<*mut zval>, + pub current_execute_data: ptr::NonNull<*mut zend_execute_data>, + pub vm_interrupt: ptr::NonNull, +} + +impl ExecutorGlobalAddrs { + /// # Safety + /// Must be called on a PHP thread during a request. + unsafe fn new() -> ExecutorGlobalAddrs { + ddog_php_prof_executor_global_addrs() + } +} + pub struct RequestLocals { pub env: Option, pub service: Option, @@ -347,29 +365,33 @@ pub struct RequestLocals { pub system_settings: ptr::NonNull, pub interrupt_count: AtomicU32, - pub vm_interrupt_addr: *const AtomicBool, + pub current_execute_data_cache: AtomicPtr, + pub executor_global_addrs: ExecutorGlobalAddrs, } impl RequestLocals { - #[track_caller] - pub fn system_settings(&self) -> &SystemSettings { - // SAFETY: it should always be valid, either set to the - // INITIAL_SYSTEM_SETTINGS or to the SYSTEM_SETTINGS. - unsafe { self.system_settings.as_ref() } - } -} - -impl Default for RequestLocals { - fn default() -> RequestLocals { + /// # Safety + /// Must be called from a PHP Thread. + unsafe fn new() -> RequestLocals { RequestLocals { env: None, service: None, version: None, system_settings: ptr::NonNull::from(INITIAL_SYSTEM_SETTINGS.deref()), interrupt_count: AtomicU32::new(0), - vm_interrupt_addr: ptr::null_mut(), + current_execute_data_cache: AtomicPtr::new(ptr::null_mut()), + + // SAFETY: required by this function's safety conditions. + executor_global_addrs: ExecutorGlobalAddrs::new(), } } + + #[track_caller] + pub fn system_settings(&self) -> &SystemSettings { + // SAFETY: it should always be valid, either set to the + // INITIAL_SYSTEM_SETTINGS or to the SYSTEM_SETTINGS. + unsafe { self.system_settings.as_ref() } + } } thread_local! { @@ -378,7 +400,8 @@ thread_local! { wall_time: Instant::now(), }); - static REQUEST_LOCALS: RefCell = RefCell::new(RequestLocals::default()); + // These are only meant to be used on a PHP thread. + static REQUEST_LOCALS: RefCell = RefCell::new(unsafe { RequestLocals::new() }); /// The tags for this thread/request. These get sent to other threads, /// which is why they are Arc. However, they are wrapped in a RefCell @@ -426,9 +449,9 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { // initialize the thread local storage and cache some items REQUEST_LOCALS.with(|cell| { let mut locals = cell.borrow_mut(); - // Safety: we are in rinit on a PHP thread. - locals.vm_interrupt_addr = unsafe { zend::datadog_php_profiling_vm_interrupt_addr() }; locals.interrupt_count.store(0, Ordering::SeqCst); + // Safety: we are in rinit on a PHP thread. + locals.executor_global_addrs = unsafe { ddog_php_prof_executor_global_addrs() }; // Safety: We are after first rinit and before mshutdown. unsafe { @@ -555,11 +578,17 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { } if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { - let interrupt = VmInterrupt { - interrupt_count_ptr: &locals.interrupt_count as *const AtomicU32, - engine_ptr: locals.vm_interrupt_addr, + let id = profiling::interrupts::Id { + vm_interrupt_addr: locals.executor_global_addrs.vm_interrupt, + current_execute_data_addr: locals.executor_global_addrs.current_execute_data, + }; + let state = profiling::interrupts::State { + interrupt_count_addr: ptr::NonNull::from(&locals.interrupt_count), + current_execute_data_addr: ptr::NonNull::from( + &locals.current_execute_data_cache, + ), }; - profiler.add_interrupt(interrupt); + profiler.add_interrupt(id, state); } }); } else { @@ -611,11 +640,11 @@ extern "C" fn rshutdown(_type: c_int, _module_number: c_int) -> ZendResult { // and we don't need to optimize for that. if system_settings.profiling_enabled { if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { - let interrupt = VmInterrupt { - interrupt_count_ptr: &locals.interrupt_count, - engine_ptr: locals.vm_interrupt_addr, + let id = profiling::interrupts::Id { + current_execute_data_addr: locals.executor_global_addrs.current_execute_data, + vm_interrupt_addr: locals.executor_global_addrs.vm_interrupt, }; - profiler.remove_interrupt(interrupt); + profiler.remove_interrupt(id); } } }); diff --git a/profiling/src/php_ffi.c b/profiling/src/php_ffi.c index 96038fa2a3..a227f3b684 100644 --- a/profiling/src/php_ffi.c +++ b/profiling/src/php_ffi.c @@ -239,7 +239,22 @@ void datadog_php_profiling_startup(zend_extension *extension) { #endif } -void *datadog_php_profiling_vm_interrupt_addr(void) { return &EG(vm_interrupt); } +// Keep in-sync with Rust ExecutorGlobalAddrs. +struct executor_global_addrs { + zval **vm_stack_top; + zend_execute_data **current_execute_data; + void *vm_interrupt; +}; + +// Keep in-sync with Rust version. +struct executor_global_addrs ddog_php_prof_executor_global_addrs(void) { + struct executor_global_addrs addrs = { + &EG(vm_stack_top), + &EG(current_execute_data), + (void*)&EG(vm_interrupt), + }; + return addrs; +} zend_module_entry *datadog_get_module_entry(const char *str, uintptr_t len) { return zend_hash_str_find_ptr(&module_registry, str, len); diff --git a/profiling/src/php_ffi.h b/profiling/src/php_ffi.h index d2c1123e9b..1e87e79294 100644 --- a/profiling/src/php_ffi.h +++ b/profiling/src/php_ffi.h @@ -57,11 +57,6 @@ sapi_request_info datadog_sapi_globals_request_info(); */ zend_module_entry *datadog_get_module_entry(const char *str, uintptr_t len); -/** - * Fetches the VM interrupt address of the calling PHP thread. - */ -void *datadog_php_profiling_vm_interrupt_addr(void); - /** * For Code Hotspots, we need the tracer's local root span id and the current * span id. This is a cross-product struct, so keep it in sync with tracer's diff --git a/profiling/src/profiling/interrupts.rs b/profiling/src/profiling/interrupts.rs index ab69b11034..a804c3f33f 100644 --- a/profiling/src/profiling/interrupts.rs +++ b/profiling/src/profiling/interrupts.rs @@ -1,51 +1,57 @@ -use std::collections::HashSet; -use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use crate::bindings::zend_execute_data; +use ahash::HashMapExt; +use rustc_hash::FxHashMap; +use std::ptr; +use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; #[derive(Debug, Eq, PartialEq, Hash)] pub struct VmInterrupt { - pub interrupt_count_ptr: *const AtomicU32, - pub engine_ptr: *const AtomicBool, + pub current_execute_data: *mut zend_execute_data, + pub interrupt_count_addr: *const AtomicU32, + pub current_execute_data_addr: ptr::NonNull<*mut zend_execute_data>, + pub vm_interrupt_addr: ptr::NonNull, } -impl std::fmt::Display for VmInterrupt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "VmInterrupt{{{:?}, {:?}}}", - self.interrupt_count_ptr, self.engine_ptr - ) - } +#[derive(Debug, Eq, PartialEq, Hash)] +pub struct Id { + pub current_execute_data_addr: ptr::NonNull<*mut zend_execute_data>, + pub vm_interrupt_addr: ptr::NonNull, } -// This is a lie, technically, but we're trying to build it safely on top of -// the PHP VM. -unsafe impl Send for VmInterrupt {} +#[derive(Debug)] +pub struct State { + pub current_execute_data_addr: ptr::NonNull>, + pub interrupt_count_addr: ptr::NonNull, +} -pub(super) struct InterruptManager { - vm_interrupts: Arc>>, +unsafe impl Send for Id {} // lies +unsafe impl Send for State {} // more lies + +pub(super) struct Manager { + vm_interrupts: Arc>>, } -impl InterruptManager { +impl Manager { pub(super) fn new() -> Self { // Capacity 1 because we expect there to be 1 thread in NTS mode, and // if it happens to be ZTS this is just an initial capacity anyway. - let vm_interrupts = Arc::new(Mutex::new(HashSet::with_capacity(1))); + let vm_interrupts = Arc::new(Mutex::new(FxHashMap::with_capacity(1))); Self { vm_interrupts: vm_interrupts.clone(), } } /// Add the interrupt to the manager's set. - pub(super) fn add_interrupt(&self, interrupt: VmInterrupt) { + pub(super) fn add_interrupt(&self, id: Id, state: State) { let mut vm_interrupts = self.vm_interrupts.lock().unwrap(); - vm_interrupts.insert(interrupt); + vm_interrupts.insert(id, state); } /// Remove the interrupt from the manager's set. - pub(super) fn remove_interrupt(&self, interrupt: VmInterrupt) { + pub(super) fn remove_interrupt(&self, id: Id) { let mut vm_interrupts = self.vm_interrupts.lock().unwrap(); - vm_interrupts.remove(&interrupt); + vm_interrupts.remove(&id); } #[inline] @@ -54,10 +60,34 @@ impl InterruptManager { } pub(super) fn trigger_interrupts(&self) { - let vm_interrupts = self.vm_interrupts.lock().unwrap(); - vm_interrupts.iter().for_each(|obj| unsafe { - (*obj.interrupt_count_ptr).fetch_add(1, Ordering::SeqCst); - (*obj.engine_ptr).store(true, Ordering::SeqCst); - }); + let mut vm_interrupts = self.vm_interrupts.lock().unwrap(); + for (id, state) in vm_interrupts.iter_mut() { + // SAFETY: the interrupt_count_addr is stable and alive, and is + // atomic on modern PHP versions (data race possible on older + // versions, but nothing we can do). + unsafe { + (*state.interrupt_count_addr.as_ptr()).fetch_add(1, Ordering::SeqCst); + } + + #[cfg(php_good_closure_invoke)] + { + // SAFETY: THIS IS A DATA RACE CONDITION. DO NOT DEREFERENCE + // THIS POINTER! Compare it to EG(vm_stack_top) in the + // interrupt handler and if they are equal, then dereference + // the EG(vm_stack_top) pointer (after type casting). + let current_execute_data = unsafe { *id.current_execute_data_addr.as_ptr() }; + + // SAFETY: the current_execute_data_addr is stable and writeable. + unsafe { + (*state.current_execute_data_addr.as_ptr()) + .store(current_execute_data, Ordering::SeqCst) + }; + } + + // SAFETY: the vm_interrupt_addr is stable and writeable. + unsafe { + (*id.vm_interrupt_addr.as_ptr()).store(true, Ordering::SeqCst); + } + } } } diff --git a/profiling/src/profiling/mod.rs b/profiling/src/profiling/mod.rs index 242e9a7a00..8a3b209f81 100644 --- a/profiling/src/profiling/mod.rs +++ b/profiling/src/profiling/mod.rs @@ -1,10 +1,10 @@ -mod interrupts; mod sample_type_filter; -pub mod stack_walking; mod thread_utils; mod uploader; -pub use interrupts::*; +pub mod interrupts; +pub mod stack_walking; + pub use sample_type_filter::*; pub use stack_walking::*; use uploader::*; @@ -200,7 +200,7 @@ impl Default for Globals { pub struct Profiler { fork_barrier: Arc, - interrupt_manager: Arc, + interrupt_manager: Arc, message_sender: Sender, upload_sender: Sender, time_collector_handle: JoinHandle<()>, @@ -220,7 +220,7 @@ unsafe impl Send for Profiler {} struct TimeCollector { fork_barrier: Arc, - interrupt_manager: Arc, + interrupt_manager: Arc, message_receiver: Receiver, upload_sender: Sender, upload_period: Duration, @@ -521,7 +521,7 @@ const COW_EVAL: Cow = Cow::Borrowed("[eval]"); impl Profiler { pub fn new(system_settings: &SystemSettings) -> Self { let fork_barrier = Arc::new(Barrier::new(3)); - let interrupt_manager = Arc::new(InterruptManager::new()); + let interrupt_manager = Arc::new(interrupts::Manager::new()); let (message_sender, message_receiver) = crossbeam_channel::bounded(100); let (upload_sender, upload_receiver) = crossbeam_channel::bounded(UPLOAD_CHANNEL_CAPACITY); let time_collector = TimeCollector { @@ -562,18 +562,18 @@ impl Profiler { } } - pub fn add_interrupt(&self, interrupt: VmInterrupt) { + pub fn add_interrupt(&self, id: interrupts::Id, state: interrupts::State) { // First, add the interrupt to the set. - self.interrupt_manager.add_interrupt(interrupt); + self.interrupt_manager.add_interrupt(id, state); // Second, make a best-effort attempt to wake the helper thread so // that it is aware another PHP request is in flight. _ = self.message_sender.try_send(ProfilerMessage::Wake); } - pub fn remove_interrupt(&self, interrupt: VmInterrupt) { + pub fn remove_interrupt(&self, id: interrupts::Id) { // First, remove the interrupt to the set. - self.interrupt_manager.remove_interrupt(interrupt) + self.interrupt_manager.remove_interrupt(id) // Second, do not try to wake the helper thread. In NTS mode, the next // request may come before the timer expires anyway, and if not, at @@ -680,6 +680,11 @@ impl Profiler { } } + #[inline(never)] + pub fn risky_collect_time(&self, execute_data: *mut zend_execute_data, interrupt_count: u32) { + self.collect_time(execute_data, interrupt_count) + } + /// Collect a stack sample with elapsed wall time. Collects CPU time if /// it's enabled and available. pub fn collect_time(&self, execute_data: *mut zend_execute_data, interrupt_count: u32) { diff --git a/profiling/src/wall_time.rs b/profiling/src/wall_time.rs index 2635092b92..2cb945c8f4 100644 --- a/profiling/src/wall_time.rs +++ b/profiling/src/wall_time.rs @@ -56,6 +56,29 @@ pub extern "C" fn ddog_php_prof_interrupt_function(execute_data: *mut zend_execu } if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + // todo: document this crime. + #[cfg(php_good_closure_invoke)] + { + let current_execute_data_cache = locals + .current_execute_data_cache + .swap(core::ptr::null_mut(), Ordering::SeqCst); + let vm_stack_top_addr = locals.executor_global_addrs.vm_stack_top.as_ptr(); + + // The engine stores a zend_execute_data* in vm_stack_top even + // though it's a zval*. We're just following the engine here. + let vm_stack_top = unsafe { *vm_stack_top_addr }.cast::(); + + if current_execute_data_cache == vm_stack_top { + let prev = unsafe { (*vm_stack_top).prev_execute_data }; + if prev == execute_data { + let func = unsafe { (*vm_stack_top).func }; + if !func.is_null() && unsafe { (*func).is_internal() } { + profiler.risky_collect_time(vm_stack_top, interrupt_count); + } + return; + } + } + } // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it. profiler.collect_time(execute_data, interrupt_count); } @@ -133,6 +156,8 @@ pub unsafe fn minit() { }; zend_interrupt_function = Some(function); - PREV_EXECUTE_INTERNAL.write(zend_execute_internal.unwrap_or(zend::execute_internal)); - zend_execute_internal = Some(execute_internal); + if !cfg!(php_good_closure_invoke) { + PREV_EXECUTE_INTERNAL.write(zend_execute_internal.unwrap_or(zend::execute_internal)); + zend_execute_internal = Some(execute_internal); + } } From 6d1ae69cf96f4d31fbcdb0ecf5e04988af578504 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 21 Jun 2024 15:49:19 -0600 Subject: [PATCH 2/2] temporarily hammer PHP 8.3 in randomized tests --- tests/randomized/config/platforms.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/randomized/config/platforms.php b/tests/randomized/config/platforms.php index 32d441b487..b6cff618bc 100644 --- a/tests/randomized/config/platforms.php +++ b/tests/randomized/config/platforms.php @@ -7,6 +7,7 @@ 'centos7' => [ 'php' => [ '8.3', +/* '8.2', '8.1', '8.0', @@ -15,15 +16,19 @@ '7.2', '7.1', '7.0', + */ ], ], 'buster' => [ 'php' => [ '8.3', +/* '8.2', '8.1', '8.0', ] + (php_uname("m") == "arm64" ? [-1 => '7.4'] : []), // opcache broken on 7.4 amd64 + */ + ] ], ]);