Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Zend/Optimizer/pass1.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
if (!ctx->constants || !zend_optimizer_get_collected_constant(ctx->constants, &ZEND_OP2_LITERAL(opline), &result)) {
break;
}
if (Z_TYPE(result) == IS_UNDEF) {
/* blocked by define() or attributed const */
break;
}
}
if (Z_TYPE(result) == IS_CONSTANT_AST) {
break;
Expand Down Expand Up @@ -224,8 +228,9 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)

if (Z_TYPE(ZEND_OP1_LITERAL(send1_opline)) == IS_STRING && send2_opline) {

/* define() can be overridden at runtime; block inlining */
if (collect_constants) {
zend_optimizer_collect_constant(ctx, &ZEND_OP1_LITERAL(send1_opline), &ZEND_OP1_LITERAL(send2_opline));
zend_optimizer_block_constant(ctx, &ZEND_OP1_LITERAL(send1_opline));
}

if (RESULT_UNUSED(opline) &&
Expand Down Expand Up @@ -286,6 +291,13 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
literal_dtor(&ZEND_OP1_LITERAL(opline));
replace_by_const_or_qm_assign(op_array, opline, &result);
break;
case ZEND_DECLARE_ATTRIBUTED_CONST:
/* attributes must fire at access time; block inlining */
if (collect_constants &&
Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING) {
zend_optimizer_block_constant(ctx, &ZEND_OP1_LITERAL(opline));
}
break;
case ZEND_DECLARE_CONST:
if (collect_constants &&
Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING &&
Expand Down
13 changes: 13 additions & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, const zval *name,
}
}

/* prevent inlining of a constant; UNDEF sentinel blocks later DECLARE_CONST collection */
void zend_optimizer_block_constant(zend_optimizer_ctx *ctx, const zval *name)
{
if (!ctx->constants) {
ctx->constants = zend_arena_alloc(&ctx->arena, sizeof(HashTable));
zend_hash_init(ctx->constants, 16, NULL, zval_ptr_dtor_nogc, 0);
}

zval undef;
ZVAL_UNDEF(&undef);
zend_hash_add(ctx->constants, Z_STR_P(name), &undef);
}

zend_result zend_optimizer_eval_binary_op(zval *result, uint8_t opcode, zval *op1, zval *op2) /* {{{ */
{
if (zend_binary_op_produces_error(opcode, op1, op2)) {
Expand Down
2 changes: 1 addition & 1 deletion Zend/Optimizer/zend_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@

#define ZEND_OPTIMIZER_ALL_PASSES 0x7FFFFFFF

#define DEFAULT_OPTIMIZATION_LEVEL "0x7FFEBFFF"
#define DEFAULT_OPTIMIZATION_LEVEL "0x7FFEFFFF"


#define ZEND_DUMP_AFTER_PASS_1 ZEND_OPTIMIZER_PASS_1
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_optimizer_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ void zend_optimizer_convert_to_free_op1(const zend_op_array *op_array, zend_op *
uint32_t zend_optimizer_add_literal(zend_op_array *op_array, const zval *zv);
bool zend_optimizer_get_persistent_constant(zend_string *name, zval *result, bool copy);
void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, const zval *name, zval* value);
void zend_optimizer_block_constant(zend_optimizer_ctx *ctx, const zval *name);
bool zend_optimizer_get_collected_constant(const HashTable *constants, const zval *name, zval* value);
zend_result zend_optimizer_eval_binary_op(zval *result, uint8_t opcode, zval *op1, zval *op2);
zend_result zend_optimizer_eval_unary_op(zval *result, uint8_t opcode, zval *op1);
Expand Down
43 changes: 43 additions & 0 deletions ext/opcache/tests/pass1_const_inline_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
const at file scope is inlined by the optimizer, define() is not
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.opt_debug_level=0x20000
--FILE--
<?php

const CONST_VAL = 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please also add tests for what happens if the constant is already declared in a separate file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the constant is only declared in the separate file the optimizer can't inline it since the value is unknown at compile time, so it emits FETCH_CONSTANT.

If you also redeclare it locally the optimizer inlines the local value, but at runtime the required declaration wins and the local one warns. You get different results with and without opcache, so there's a real divergence there!

define('DEFINE_VAL', 2);

function use_const() {
return CONST_VAL;
}

function use_define() {
return DEFINE_VAL;
}

?>
--EXPECTF--
$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 DECLARE_CONST string("CONST_VAL") int(1)
0001 DECLARE_CONST string("DEFINE_VAL") int(2)
0002 RETURN int(1)

use_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(1)

use_define:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 T%d = FETCH_CONSTANT string("DEFINE_VAL")
0001 RETURN %s
86 changes: 86 additions & 0 deletions ext/opcache/tests/pass1_const_inline_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
--TEST--
Global and namespace const declarations are inlined by the optimizer
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.opt_debug_level=0x20000
--FILE--
<?php

const GLOBAL_CONST = 42;

function use_global_const() {
return GLOBAL_CONST;
}

function use_global_const_fqn() {
return \GLOBAL_CONST;
}

class MyClass {
const CLASS_CONST = 99;
final const FINAL_CLASS_CONST = 77;

public function use_class_const() {
Comment thread
wilaak marked this conversation as resolved.
return self::CLASS_CONST;
}

public function use_static_class_const() {
return static::CLASS_CONST;
}

public function use_static_final_class_const() {
return static::FINAL_CLASS_CONST;
}
}

function use_class_const_static() {
return MyClass::CLASS_CONST;
}

?>
--EXPECTF--
$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 DECLARE_CONST string("GLOBAL_CONST") int(42)
0001 RETURN int(1)

use_global_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(42)

use_global_const_fqn:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(42)

use_class_const_static:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(99)

MyClass::use_class_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(99)

MyClass::use_static_class_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 T%d = FETCH_CLASS_CONSTANT (static) (exception) string("CLASS_CONST")
0001 RETURN %s

MyClass::use_static_final_class_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(77)
41 changes: 41 additions & 0 deletions ext/opcache/tests/pass1_const_inline_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
Namespace const declarations are inlined by the optimizer
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.opt_debug_level=0x20000
--FILE--
<?php
namespace MyNS;

const NS_CONST = 100;

function use_ns_const() {
return NS_CONST;
}

function use_ns_const_fqn() {
return \MyNS\NS_CONST;
}

?>
--EXPECTF--
$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 DECLARE_CONST string("MyNS\\NS_CONST") int(100)
0001 RETURN int(1)

MyNS\use_ns_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(100)

MyNS\use_ns_const_fqn:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(100)
22 changes: 22 additions & 0 deletions ext/opcache/tests/pass1_const_inline_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
define() followed by const redeclaration is not inlined
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
--FILE--
<?php

define('FOO', 'first');
const FOO = 'second';

function get_foo() {
return FOO;
}

var_dump(get_foo());

?>
--EXPECTF--
Warning: Constant FOO already defined, this will be an error in PHP 9 in %s on line %d
string(5) "first"
2 changes: 2 additions & 0 deletions ext/opcache/tests/pass1_const_inline_005.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
const EXTERNAL_CONST = 42;
38 changes: 38 additions & 0 deletions ext/opcache/tests/pass1_const_inline_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
const declared only in a separate required file is not inlined by the optimizer
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.opt_debug_level=0x20000
--FILE--
<?php

require __DIR__ . '/pass1_const_inline_005.inc';

function use_external_const() {
return EXTERNAL_CONST;
}

?>
--EXPECTF--
$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 INCLUDE_OR_EVAL (require) string("%s")
0001 RETURN int(1)

use_external_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 T%d = FETCH_CONSTANT string("EXTERNAL_CONST")
0001 RETURN %s

$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 DECLARE_CONST string("EXTERNAL_CONST") int(42)
0001 RETURN int(1)
2 changes: 2 additions & 0 deletions ext/opcache/tests/pass1_const_inline_006.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
const SHARED_CONST = 42;
50 changes: 50 additions & 0 deletions ext/opcache/tests/pass1_const_inline_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
const redeclared after being declared in a separate required file: optimizer inlines local value, runtime warning
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.opt_debug_level=0x20000
--FILE--
<?php

require __DIR__ . '/pass1_const_inline_006.inc';

const SHARED_CONST = 99;

function use_shared_const() {
return SHARED_CONST;
}

var_dump(use_shared_const());

?>
--EXPECTF--
$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 INCLUDE_OR_EVAL (require) string("%s")
0001 DECLARE_CONST string("SHARED_CONST") int(99)
0002 INIT_FCALL 1 %d string("var_dump")
0003 INIT_FCALL 0 %d string("use_shared_const")
0004 T%d = DO_UCALL
0005 SEND_VAL T%d 1
0006 DO_ICALL
0007 RETURN int(1)

use_shared_const:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 RETURN int(99)

$_main:
; (lines=%d, args=%d, vars=%d, tmps=%d)
; (after optimizer)
; %s
0000 DECLARE_CONST string("SHARED_CONST") int(42)
0001 RETURN int(1)

Warning: Constant SHARED_CONST already defined, this will be an error in PHP 9 in %s on line %d
int(99)
Loading