diff --git a/Zend/Optimizer/pass1.c b/Zend/Optimizer/pass1.c index 962bdb6e4be3..adb981a3e7fa 100644 --- a/Zend/Optimizer/pass1.c +++ b/Zend/Optimizer/pass1.c @@ -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; @@ -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) && @@ -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 && diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 245c67c45446..1f65030c99c7 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -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)) { diff --git a/Zend/Optimizer/zend_optimizer.h b/Zend/Optimizer/zend_optimizer.h index d2847c92869c..f5d102518802 100644 --- a/Zend/Optimizer/zend_optimizer.h +++ b/Zend/Optimizer/zend_optimizer.h @@ -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 diff --git a/Zend/Optimizer/zend_optimizer_internal.h b/Zend/Optimizer/zend_optimizer_internal.h index d01df56260bc..88f0e1589fa0 100644 --- a/Zend/Optimizer/zend_optimizer_internal.h +++ b/Zend/Optimizer/zend_optimizer_internal.h @@ -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); diff --git a/ext/opcache/tests/pass1_const_inline_001.phpt b/ext/opcache/tests/pass1_const_inline_001.phpt new file mode 100644 index 000000000000..894c1a0660ed --- /dev/null +++ b/ext/opcache/tests/pass1_const_inline_001.phpt @@ -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-- + +--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 diff --git a/ext/opcache/tests/pass1_const_inline_002.phpt b/ext/opcache/tests/pass1_const_inline_002.phpt new file mode 100644 index 000000000000..ac7d3d35c38b --- /dev/null +++ b/ext/opcache/tests/pass1_const_inline_002.phpt @@ -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-- + +--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) diff --git a/ext/opcache/tests/pass1_const_inline_003.phpt b/ext/opcache/tests/pass1_const_inline_003.phpt new file mode 100644 index 000000000000..39b5a529bcad --- /dev/null +++ b/ext/opcache/tests/pass1_const_inline_003.phpt @@ -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-- + +--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) diff --git a/ext/opcache/tests/pass1_const_inline_004.phpt b/ext/opcache/tests/pass1_const_inline_004.phpt new file mode 100644 index 000000000000..c6b564972bfc --- /dev/null +++ b/ext/opcache/tests/pass1_const_inline_004.phpt @@ -0,0 +1,22 @@ +--TEST-- +define() followed by const redeclaration is not inlined +--EXTENSIONS-- +opcache +--INI-- +opcache.enable_cli=1 +--FILE-- + +--EXPECTF-- +Warning: Constant FOO already defined, this will be an error in PHP 9 in %s on line %d +string(5) "first" diff --git a/ext/opcache/tests/pass1_const_inline_005.inc b/ext/opcache/tests/pass1_const_inline_005.inc new file mode 100644 index 000000000000..540a5a257e72 --- /dev/null +++ b/ext/opcache/tests/pass1_const_inline_005.inc @@ -0,0 +1,2 @@ + +--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) diff --git a/ext/opcache/tests/pass1_const_inline_006.inc b/ext/opcache/tests/pass1_const_inline_006.inc new file mode 100644 index 000000000000..715615dd5607 --- /dev/null +++ b/ext/opcache/tests/pass1_const_inline_006.inc @@ -0,0 +1,2 @@ + +--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)