Allow optimizer to depend on preloaded symbols (#15021)

* Allow optimizer to depend on preloaded symbols

It is safe for the optimizer to rely on preloaded symbols. This can occur when
compiling non-preloaded files, referencing preloaded ones.

* Disable inline pass for observer test

* Move duplicated code into functions

* Add comment to specific optimization value

* Optimizer should only rely on preloaded symbols in the symbol table

* Fix skipif for windows
This commit is contained in:
Ilija Tovilo 2024-08-02 17:35:27 +02:00 committed by GitHub
parent 521178709e
commit 2e9cc9bc30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 154 additions and 29 deletions

View File

@ -792,6 +792,42 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_
}
}
static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename)
{
zend_class_entry *ce = Z_PTR_P(ce_zv);
if (ce->ce_flags & ZEND_ACC_PRELOADED) {
Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val));
size_t offset = ce_bucket - EG(class_table)->arData;
if (offset < EG(persistent_classes_count)) {
return false;
}
}
return ce->type == ZEND_USER_CLASS
&& (!ce->info.user.filename || ce->info.user.filename != filename);
}
static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename)
{
zend_function *fbc = Z_PTR_P(fbc_zv);
if (fbc->type == ZEND_INTERNAL_FUNCTION) {
return false;
} else if (fbc->type == ZEND_USER_FUNCTION) {
if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) {
Bucket *fbc_bucket = (Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val));
size_t offset = fbc_bucket - EG(function_table)->arData;
if (offset < EG(persistent_functions_count)) {
return false;
}
}
return !fbc->op_array.filename || fbc->op_array.filename != filename;
} else {
ZEND_ASSERT(fbc->type == ZEND_EVAL_CODE);
return true;
}
}
zend_class_entry *zend_optimizer_get_class_entry(
const zend_script *script, const zend_op_array *op_array, zend_string *lcname) {
zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
@ -799,11 +835,9 @@ zend_class_entry *zend_optimizer_get_class_entry(
return ce;
}
ce = zend_hash_find_ptr(CG(class_table), lcname);
if (ce
&& (ce->type == ZEND_INTERNAL_CLASS
|| (op_array && ce->info.user.filename == op_array->filename))) {
return ce;
zval *ce_zv = zend_hash_find(CG(class_table), lcname);
if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array ? op_array->filename : NULL)) {
return Z_PTR_P(ce_zv);
}
if (op_array && op_array->scope && zend_string_equals_ci(op_array->scope->name, lcname)) {
@ -844,15 +878,9 @@ const zend_class_constant *zend_fetch_class_const_info(
if (script) {
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(op1 + 1));
} else {
zend_class_entry *tmp = zend_hash_find_ptr(EG(class_table), Z_STR_P(op1 + 1));
if (tmp != NULL) {
if (tmp->type == ZEND_INTERNAL_CLASS) {
ce = tmp;
} else if (tmp->type == ZEND_USER_CLASS
&& tmp->info.user.filename
&& tmp->info.user.filename == op_array->filename) {
ce = tmp;
}
zval *ce_zv = zend_hash_find(EG(class_table), Z_STR_P(op1 + 1));
if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array->filename)) {
ce = Z_PTR_P(ce_zv);
}
}
}
@ -897,15 +925,12 @@ zend_function *zend_optimizer_get_called_func(
{
zend_string *function_name = Z_STR_P(CRT_CONSTANT(opline->op2));
zend_function *func;
zval *func_zv;
if (script && (func = zend_hash_find_ptr(&script->function_table, function_name)) != NULL) {
return func;
} else if ((func = zend_hash_find_ptr(EG(function_table), function_name)) != NULL) {
if (func->type == ZEND_INTERNAL_FUNCTION) {
return func;
} else if (func->type == ZEND_USER_FUNCTION &&
func->op_array.filename &&
func->op_array.filename == op_array->filename) {
return func;
} else if ((func_zv = zend_hash_find(EG(function_table), function_name)) != NULL) {
if (!zend_optimizer_ignore_function(func_zv, op_array->filename)) {
return Z_PTR_P(func_zv);
}
}
break;
@ -915,15 +940,12 @@ zend_function *zend_optimizer_get_called_func(
if (opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING) {
zval *function_name = CRT_CONSTANT(opline->op2) + 1;
zend_function *func;
zval *func_zv;
if (script && (func = zend_hash_find_ptr(&script->function_table, Z_STR_P(function_name)))) {
return func;
} else if ((func = zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name))) != NULL) {
if (func->type == ZEND_INTERNAL_FUNCTION) {
return func;
} else if (func->type == ZEND_USER_FUNCTION &&
func->op_array.filename &&
func->op_array.filename == op_array->filename) {
return func;
} else if ((func_zv = zend_hash_find(EG(function_table), Z_STR_P(function_name))) != NULL) {
if (!zend_optimizer_ignore_function(func_zv, op_array->filename)) {
return Z_PTR_P(func_zv);
}
}
}

View File

@ -0,0 +1,31 @@
--TEST--
GH-15021: Optimizer only relies on preloaded top-level symbols
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.preload={PWD}/gh15021_preload.inc
--EXTENSIONS--
opcache
--SKIPIF--
<?php
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
?>
--FILE--
<?php
putenv('RUNTIME=1');
$firstRun = !isset(opcache_get_status()['scripts'][__DIR__ . DIRECTORY_SEPARATOR . 'gh15021_required.inc']);
if ($firstRun) {
require __DIR__ . '/gh15021_a.inc';
$expected = 1;
} else {
require __DIR__ . '/gh15021_b.inc';
$expected = 2;
}
require __DIR__ . '/gh15021_required.inc';
var_dump(f() === $expected);
?>
--EXPECT--
bool(true)

View File

@ -0,0 +1,7 @@
<?php
if (getenv('RUNTIME')) {
function g(): int {
return 1;
}
}

View File

@ -0,0 +1,7 @@
<?php
if (getenv('RUNTIME')) {
function g(): int {
return 2;
}
}

View File

@ -0,0 +1,4 @@
<?php
opcache_compile_file(__DIR__ . '/gh15021_a.inc');
opcache_compile_file(__DIR__ . '/gh15021_b.inc');

View File

@ -0,0 +1,5 @@
<?php
function f(): int {
return g();
}

View File

@ -0,0 +1,5 @@
<?php
function foo() {
return 42;
}

View File

@ -0,0 +1,43 @@
--TEST--
Optimizer may rely on preloaded symbols
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.preload={PWD}/preload_optimizer.inc
opcache.opt_debug_level=0x20000
--EXTENSIONS--
opcache
--SKIPIF--
<?php
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
?>
--FILE--
<?php
echo foo();
?>
--EXPECTF--
$_main:
; (lines=1, args=0, vars=0, tmps=%d)
; (after optimizer)
; $PRELOAD$:0-0
0000 RETURN null
foo:
; (lines=1, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spreload_optimizer.inc:3-5
0000 RETURN int(42)
$_main:
; (lines=1, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spreload_optimizer.inc:1-6
0000 RETURN int(1)
$_main:
; (lines=2, args=0, vars=0, tmps=%d)
; (after optimizer)
; %spreload_optimizer.php:1-4
0000 ECHO string("42")
0001 RETURN int(1)
42

View File

@ -10,7 +10,8 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
; Disable inlining pass
opcache.optimization_level=0x7ffe3fff
opcache.preload={PWD}/observer_preload.inc
opcache.file_cache=
opcache.file_cache_only=0