Skip to content

Commit 9ccd300

Browse files
committedMar 27, 2025
Keep optimized opcodes if the result is known-used
1 parent 65b856b commit 9ccd300

File tree

4 files changed

+104
-12
lines changed

4 files changed

+104
-12
lines changed
 

‎Zend/Optimizer/optimize_func_calls.c

+11-6
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,12 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
204204
fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
205205
literal_dtor(&ZEND_OP2_LITERAL(fcall));
206206
fcall->op2.constant = fcall->op2.constant + 1;
207-
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
208-
opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
209-
}
210207
} else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
211208
fcall->opcode = ZEND_INIT_FCALL;
212209
fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
213210
literal_dtor(&op_array->literals[fcall->op2.constant]);
214211
literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
215212
fcall->op2.constant = fcall->op2.constant + 1;
216-
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
217-
opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
218-
}
219213
} else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
220214
|| fcall->opcode == ZEND_INIT_METHOD_CALL
221215
|| fcall->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL
@@ -225,6 +219,17 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
225219
ZEND_UNREACHABLE();
226220
}
227221

222+
/* If the INIT opcode changed the DO opcode also needs to
223+
* change.
224+
*
225+
* At this point we also know whether or not the result of
226+
* the DO opcode is used, allowing to optimize calls to
227+
* ZEND_ACC_NODISCARD functions.
228+
*/
229+
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
230+
opline->opcode = zend_get_call_op(fcall, call_stack[call].func, !RESULT_UNUSED(opline));
231+
}
232+
228233
if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
229234
&& call_stack[call].try_inline
230235
&& opline->opcode != ZEND_CALLABLE_CONVERT) {
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
--TEST--
2+
#[\NoDiscard]: Functions with known-used result use DO_[IU]CALL opcodes
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.opt_debug_level=0x20000
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
13+
$f = tmpfile();
14+
flock($f, LOCK_SH | LOCK_NB);
15+
(void)flock($f, LOCK_SH | LOCK_NB);
16+
$success = flock($f, LOCK_SH | LOCK_NB);
17+
fclose($f);
18+
19+
#[\NoDiscard]
20+
function test() {
21+
return new stdClass();
22+
}
23+
24+
test();
25+
(void)test();
26+
$obj = test();
27+
28+
?>
29+
--EXPECTF--
30+
$_main:
31+
; (lines=29, args=0, vars=3, tmps=1)
32+
; (after optimizer)
33+
; %s
34+
0000 INIT_FCALL 0 %d string("tmpfile")
35+
0001 V3 = DO_ICALL
36+
0002 ASSIGN CV0($f) V3
37+
0003 INIT_FCALL 2 112 string("flock")
38+
0004 SEND_VAR CV0($f) 1
39+
0005 SEND_VAL int(5) 2
40+
0006 DO_FCALL_BY_NAME
41+
0007 INIT_FCALL 2 112 string("flock")
42+
0008 SEND_VAR CV0($f) 1
43+
0009 SEND_VAL int(5) 2
44+
0010 V3 = DO_ICALL
45+
0011 FREE V3
46+
0012 INIT_FCALL 2 112 string("flock")
47+
0013 SEND_VAR CV0($f) 1
48+
0014 SEND_VAL int(5) 2
49+
0015 V3 = DO_ICALL
50+
0016 ASSIGN CV1($success) V3
51+
0017 INIT_FCALL 1 %d string("fclose")
52+
0018 SEND_VAR CV0($f) 1
53+
0019 DO_ICALL
54+
0020 INIT_FCALL 0 %d string("test")
55+
0021 DO_FCALL_BY_NAME
56+
0022 INIT_FCALL 0 %d string("test")
57+
0023 V3 = DO_UCALL
58+
0024 FREE V3
59+
0025 INIT_FCALL 0 %d string("test")
60+
0026 V3 = DO_UCALL
61+
0027 ASSIGN CV2($obj) V3
62+
0028 RETURN int(1)
63+
64+
test:
65+
; (lines=3, args=0, vars=0, tmps=1)
66+
; (after optimizer)
67+
; %s
68+
0000 V0 = NEW 0 string("stdClass")
69+
0001 DO_FCALL
70+
0002 RETURN V0
71+
LIVE RANGES:
72+
0: 0001 - 0002 (new)
73+
74+
Warning: The return value of function flock() should either be used or intentionally ignored by casting it as (void), as locking the stream might have failed in %s on line %d
75+
76+
Warning: The return value of function test() should either be used or intentionally ignored by casting it as (void) in %s on line %d

‎Zend/zend_compile.c

+16-5
Original file line numberDiff line numberDiff line change
@@ -3927,21 +3927,23 @@ static uint32_t zend_compile_args(
39273927
}
39283928
/* }}} */
39293929

3930-
ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc) /* {{{ */
3930+
ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc, bool result_used) /* {{{ */
39313931
{
3932-
if (fbc) {
3932+
uint32_t no_discard = result_used ? 0 : ZEND_ACC_NODISCARD;
3933+
3934+
if (fbc && init_op->opcode != ZEND_NEW) {
39333935
ZEND_ASSERT(!(fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE));
39343936
if (fbc->type == ZEND_INTERNAL_FUNCTION && !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_FUNCTIONS)) {
39353937
if (init_op->opcode == ZEND_INIT_FCALL && !zend_execute_internal) {
3936-
if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD))) {
3938+
if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard))) {
39373939
return ZEND_DO_ICALL;
39383940
} else {
39393941
return ZEND_DO_FCALL_BY_NAME;
39403942
}
39413943
}
39423944
} else if (!(CG(compiler_options) & ZEND_COMPILE_IGNORE_USER_FUNCTIONS)){
39433945
if (zend_execute_ex == execute_ex) {
3944-
if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD))) {
3946+
if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard))) {
39453947
return ZEND_DO_UCALL;
39463948
} else {
39473949
return ZEND_DO_FCALL_BY_NAME;
@@ -3991,7 +3993,16 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, zend_fun
39913993
opline->op1.num = zend_vm_calc_used_stack(arg_count, fbc);
39923994
}
39933995

3994-
opline = zend_emit_op(result, zend_get_call_op(opline, fbc), NULL, NULL);
3996+
uint8_t call_op = zend_get_call_op(
3997+
opline,
3998+
fbc,
3999+
/* result_used: At this point we do not yet reliably
4000+
* know if the result is used. Deoptimize #[\NoDiscard]
4001+
* calls to be sure. The optimizer will fix this up.
4002+
*/
4003+
false
4004+
);
4005+
opline = zend_emit_op(result, call_op, NULL, NULL);
39954006
if (may_have_extra_named_args) {
39964007
opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS;
39974008
}

‎Zend/zend_compile.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ ZEND_API bool zend_is_compiling(void);
984984
ZEND_API char *zend_make_compiled_string_description(const char *name);
985985
ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_handlers);
986986
uint32_t zend_get_class_fetch_type(const zend_string *name);
987-
ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc);
987+
ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc, bool result_used);
988988
ZEND_API bool zend_is_smart_branch(const zend_op *opline);
989989

990990
typedef bool (*zend_auto_global_callback)(zend_string *name);

0 commit comments

Comments
 (0)