From e7dc3aa8ee4f98f58d958ae27acdb5aab88d907d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 3 Jun 2026 17:21:49 +0200 Subject: [PATCH] Relax optimizer/JIT cross-file boundary restriction for CLI In CLI (but not cli-server) it's safe to assume script structures are fully immutable, given the structures will not be freed until the process ends. Hence, we can make stronger assumptions during optimization and jitting. This may be beneficial for web servers implemented in PHP, or worker mode. --- Zend/Optimizer/zend_optimizer.c | 11 +++++++++++ main/SAPI.c | 8 ++++++++ main/SAPI.h | 6 +++++- sapi/cli/php_cli.c | 9 +++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index d10b4d83fc3e..4ce4a2ba2731 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -29,6 +29,7 @@ #include "zend_inference.h" #include "zend_dump.h" #include "php.h" +#include "SAPI.h" #ifndef ZEND_OPTIMIZER_MAX_REGISTERED_PASSES # define ZEND_OPTIMIZER_MAX_REGISTERED_PASSES 32 @@ -786,6 +787,9 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, const zend_string *filename return false; } } + if (sapi_is_single_request()) { + return false; + } return ce->type == ZEND_USER_CLASS && (!ce->info.user.filename || ce->info.user.filename != filename); } @@ -807,6 +811,9 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, const zend_string *file return false; } } + if (sapi_is_single_request()) { + return false; + } return !fbc->op_array.filename || fbc->op_array.filename != filename; } else { ZEND_ASSERT(fbc->type == ZEND_EVAL_CODE); @@ -899,6 +906,10 @@ const zend_class_constant *zend_fetch_class_const_info( *is_prototype = is_static_reference && !(const_info->ce->ce_flags & ZEND_ACC_FINAL) && !(ZEND_CLASS_CONST_FLAGS(const_info) & ZEND_ACC_FINAL); + if (Z_TYPE(const_info->value) > IS_ARRAY) { + return NULL; + } + return const_info; } diff --git a/main/SAPI.c b/main/SAPI.c index 62e1c89e4bb9..c7e2f269d7bf 100644 --- a/main/SAPI.c +++ b/main/SAPI.c @@ -1173,3 +1173,11 @@ SAPI_API void sapi_add_request_header(const char *var, unsigned int var_len, cha } } /* }}} */ + +SAPI_API bool sapi_is_single_request(void) +{ + if (!sapi_module.is_single_request) { + return false; + } + return sapi_module.is_single_request(); +} diff --git a/main/SAPI.h b/main/SAPI.h index e62f686603c4..980802291715 100644 --- a/main/SAPI.h +++ b/main/SAPI.h @@ -167,6 +167,7 @@ SAPI_API void sapi_deactivate_destroy(void); SAPI_API void sapi_deactivate(void); SAPI_API void sapi_initialize_empty_request(void); SAPI_API void sapi_add_request_header(const char *var, unsigned int var_len, char *val, unsigned int val_len, void *arg); +SAPI_API bool sapi_is_single_request(void); END_EXTERN_C() /* @@ -290,6 +291,8 @@ struct _sapi_module_struct { unsigned int (*input_filter_init)(void); int (*pre_request_init)(void); /* called before activate and before the post data read - used for .user.ini */ + + bool (*is_single_request)(void); /* Whether the SAPI will only handle a single request. This implies all script structures are immutable. */ }; struct _sapi_post_entry { @@ -341,6 +344,7 @@ END_EXTERN_C() NULL, /* ini_entries; */ \ NULL, /* additional_functions */ \ NULL, /* input_filter_init */ \ - NULL /* pre_request_init */ + NULL, /* pre_request_init */ \ + NULL /* is_single_request */ #endif /* SAPI_H */ diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 9a05f8e68547..71b88e740533 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -592,6 +592,11 @@ static int zend_ini_entry_cmp(Bucket *a, Bucket *b) return zend_binary_strcasecmp(ZSTR_VAL(A->name), ZSTR_LEN(A->name), ZSTR_VAL(B->name), ZSTR_LEN(B->name)); } +static bool is_single_request(void) +{ + return true; +} + static int do_cli(int argc, char **argv) /* {{{ */ { int c; @@ -836,6 +841,10 @@ static int do_cli(int argc, char **argv) /* {{{ */ } } + if (num_repeats == 1) { + sapi_module.is_single_request = is_single_request; + } + if (param_error) { PUTS(param_error); EG(exit_status) = 1;