Skip to content

Commit 2e9cc9b

Browse files
authored
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
1 parent 5211787 commit 2e9cc9b

9 files changed

+154
-29
lines changed

Zend/Optimizer/zend_optimizer.c

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -792,18 +792,52 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_
792792
}
793793
}
794794

795+
static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename)
796+
{
797+
zend_class_entry *ce = Z_PTR_P(ce_zv);
798+
799+
if (ce->ce_flags & ZEND_ACC_PRELOADED) {
800+
Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val));
801+
size_t offset = ce_bucket - EG(class_table)->arData;
802+
if (offset < EG(persistent_classes_count)) {
803+
return false;
804+
}
805+
}
806+
return ce->type == ZEND_USER_CLASS
807+
&& (!ce->info.user.filename || ce->info.user.filename != filename);
808+
}
809+
810+
static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename)
811+
{
812+
zend_function *fbc = Z_PTR_P(fbc_zv);
813+
814+
if (fbc->type == ZEND_INTERNAL_FUNCTION) {
815+
return false;
816+
} else if (fbc->type == ZEND_USER_FUNCTION) {
817+
if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) {
818+
Bucket *fbc_bucket = (Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val));
819+
size_t offset = fbc_bucket - EG(function_table)->arData;
820+
if (offset < EG(persistent_functions_count)) {
821+
return false;
822+
}
823+
}
824+
return !fbc->op_array.filename || fbc->op_array.filename != filename;
825+
} else {
826+
ZEND_ASSERT(fbc->type == ZEND_EVAL_CODE);
827+
return true;
828+
}
829+
}
830+
795831
zend_class_entry *zend_optimizer_get_class_entry(
796832
const zend_script *script, const zend_op_array *op_array, zend_string *lcname) {
797833
zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
798834
if (ce) {
799835
return ce;
800836
}
801837

802-
ce = zend_hash_find_ptr(CG(class_table), lcname);
803-
if (ce
804-
&& (ce->type == ZEND_INTERNAL_CLASS
805-
|| (op_array && ce->info.user.filename == op_array->filename))) {
806-
return ce;
838+
zval *ce_zv = zend_hash_find(CG(class_table), lcname);
839+
if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array ? op_array->filename : NULL)) {
840+
return Z_PTR_P(ce_zv);
807841
}
808842

809843
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(
844878
if (script) {
845879
ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(op1 + 1));
846880
} else {
847-
zend_class_entry *tmp = zend_hash_find_ptr(EG(class_table), Z_STR_P(op1 + 1));
848-
if (tmp != NULL) {
849-
if (tmp->type == ZEND_INTERNAL_CLASS) {
850-
ce = tmp;
851-
} else if (tmp->type == ZEND_USER_CLASS
852-
&& tmp->info.user.filename
853-
&& tmp->info.user.filename == op_array->filename) {
854-
ce = tmp;
855-
}
881+
zval *ce_zv = zend_hash_find(EG(class_table), Z_STR_P(op1 + 1));
882+
if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array->filename)) {
883+
ce = Z_PTR_P(ce_zv);
856884
}
857885
}
858886
}
@@ -897,15 +925,12 @@ zend_function *zend_optimizer_get_called_func(
897925
{
898926
zend_string *function_name = Z_STR_P(CRT_CONSTANT(opline->op2));
899927
zend_function *func;
928+
zval *func_zv;
900929
if (script && (func = zend_hash_find_ptr(&script->function_table, function_name)) != NULL) {
901930
return func;
902-
} else if ((func = zend_hash_find_ptr(EG(function_table), function_name)) != NULL) {
903-
if (func->type == ZEND_INTERNAL_FUNCTION) {
904-
return func;
905-
} else if (func->type == ZEND_USER_FUNCTION &&
906-
func->op_array.filename &&
907-
func->op_array.filename == op_array->filename) {
908-
return func;
931+
} else if ((func_zv = zend_hash_find(EG(function_table), function_name)) != NULL) {
932+
if (!zend_optimizer_ignore_function(func_zv, op_array->filename)) {
933+
return Z_PTR_P(func_zv);
909934
}
910935
}
911936
break;
@@ -915,15 +940,12 @@ zend_function *zend_optimizer_get_called_func(
915940
if (opline->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_STRING) {
916941
zval *function_name = CRT_CONSTANT(opline->op2) + 1;
917942
zend_function *func;
943+
zval *func_zv;
918944
if (script && (func = zend_hash_find_ptr(&script->function_table, Z_STR_P(function_name)))) {
919945
return func;
920-
} else if ((func = zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name))) != NULL) {
921-
if (func->type == ZEND_INTERNAL_FUNCTION) {
922-
return func;
923-
} else if (func->type == ZEND_USER_FUNCTION &&
924-
func->op_array.filename &&
925-
func->op_array.filename == op_array->filename) {
926-
return func;
946+
} else if ((func_zv = zend_hash_find(EG(function_table), Z_STR_P(function_name))) != NULL) {
947+
if (!zend_optimizer_ignore_function(func_zv, op_array->filename)) {
948+
return Z_PTR_P(func_zv);
927949
}
928950
}
929951
}

ext/opcache/tests/gh15021.phpt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
GH-15021: Optimizer only relies on preloaded top-level symbols
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.preload={PWD}/gh15021_preload.inc
7+
--EXTENSIONS--
8+
opcache
9+
--SKIPIF--
10+
<?php
11+
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
12+
?>
13+
--FILE--
14+
<?php
15+
putenv('RUNTIME=1');
16+
$firstRun = !isset(opcache_get_status()['scripts'][__DIR__ . DIRECTORY_SEPARATOR . 'gh15021_required.inc']);
17+
18+
if ($firstRun) {
19+
require __DIR__ . '/gh15021_a.inc';
20+
$expected = 1;
21+
} else {
22+
require __DIR__ . '/gh15021_b.inc';
23+
$expected = 2;
24+
}
25+
26+
require __DIR__ . '/gh15021_required.inc';
27+
28+
var_dump(f() === $expected);
29+
?>
30+
--EXPECT--
31+
bool(true)

ext/opcache/tests/gh15021_a.inc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
if (getenv('RUNTIME')) {
4+
function g(): int {
5+
return 1;
6+
}
7+
}

ext/opcache/tests/gh15021_b.inc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
if (getenv('RUNTIME')) {
4+
function g(): int {
5+
return 2;
6+
}
7+
}

ext/opcache/tests/gh15021_preload.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
opcache_compile_file(__DIR__ . '/gh15021_a.inc');
4+
opcache_compile_file(__DIR__ . '/gh15021_b.inc');
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
function f(): int {
4+
return g();
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
function foo() {
4+
return 42;
5+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
Optimizer may rely on preloaded symbols
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.preload={PWD}/preload_optimizer.inc
7+
opcache.opt_debug_level=0x20000
8+
--EXTENSIONS--
9+
opcache
10+
--SKIPIF--
11+
<?php
12+
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
13+
?>
14+
--FILE--
15+
<?php
16+
echo foo();
17+
?>
18+
--EXPECTF--
19+
$_main:
20+
; (lines=1, args=0, vars=0, tmps=%d)
21+
; (after optimizer)
22+
; $PRELOAD$:0-0
23+
0000 RETURN null
24+
25+
foo:
26+
; (lines=1, args=0, vars=0, tmps=%d)
27+
; (after optimizer)
28+
; %spreload_optimizer.inc:3-5
29+
0000 RETURN int(42)
30+
31+
$_main:
32+
; (lines=1, args=0, vars=0, tmps=%d)
33+
; (after optimizer)
34+
; %spreload_optimizer.inc:1-6
35+
0000 RETURN int(1)
36+
37+
$_main:
38+
; (lines=2, args=0, vars=0, tmps=%d)
39+
; (after optimizer)
40+
; %spreload_optimizer.php:1-4
41+
0000 ECHO string("42")
42+
0001 RETURN int(1)
43+
42

ext/zend_test/tests/observer_preload.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows
1010
--INI--
1111
opcache.enable=1
1212
opcache.enable_cli=1
13-
opcache.optimization_level=-1
13+
; Disable inlining pass
14+
opcache.optimization_level=0x7ffe3fff
1415
opcache.preload={PWD}/observer_preload.inc
1516
opcache.file_cache=
1617
opcache.file_cache_only=0

0 commit comments

Comments
 (0)