@@ -1048,13 +1048,19 @@ zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op
10481048 return NULL ;
10491049}
10501050
1051+ static zend_always_inline bool zend_op_array_is_scope_fn (const zend_op_array * op_array ) {
1052+ return (op_array -> fn_flags2 & ZEND_ACC2_SCOPE_FUNC ) != 0 ;
1053+ }
1054+
10511055static void zend_optimize (zend_op_array * op_array ,
10521056 zend_optimizer_ctx * ctx )
10531057{
10541058 if (op_array -> type == ZEND_EVAL_CODE ) {
10551059 return ;
10561060 }
10571061
1062+ bool is_scope_fn = zend_op_array_is_scope_fn (op_array );
1063+
10581064 if (ctx -> debug_level & ZEND_DUMP_BEFORE_OPTIMIZER ) {
10591065 zend_dump_op_array (op_array , ZEND_DUMP_LIVE_RANGES , "before optimizer" , NULL );
10601066 }
@@ -1106,9 +1112,13 @@ static void zend_optimize(zend_op_array *op_array,
11061112
11071113 /* pass 6:
11081114 * - DFA optimization
1109- */
1115+ *
1116+ * Skipped for scope-fn op_arrays: DCE on a body's CV write would
1117+ * eliminate a store the parent later reads, but single-op_array SSA
1118+ * cannot see that. Re-enabling needs cross-op-array DCE protection
1119+ * (treat every body CV write as live-out). */
11101120 if ((ZEND_OPTIMIZER_PASS_6 & ctx -> optimization_level ) &&
1111- !(ZEND_OPTIMIZER_PASS_7 & ctx -> optimization_level )) {
1121+ !(ZEND_OPTIMIZER_PASS_7 & ctx -> optimization_level ) && ! is_scope_fn ) {
11121122 zend_optimize_dfa (op_array , ctx );
11131123 if (ctx -> debug_level & ZEND_DUMP_AFTER_PASS_6 ) {
11141124 zend_dump_op_array (op_array , 0 , "after pass 6" , NULL );
@@ -1141,7 +1151,8 @@ static void zend_optimize(zend_op_array *op_array,
11411151 */
11421152 if ((ZEND_OPTIMIZER_PASS_11 & ctx -> optimization_level ) &&
11431153 (!(ZEND_OPTIMIZER_PASS_6 & ctx -> optimization_level ) ||
1144- !(ZEND_OPTIMIZER_PASS_7 & ctx -> optimization_level ))) {
1154+ !(ZEND_OPTIMIZER_PASS_7 & ctx -> optimization_level ) ||
1155+ is_scope_fn /* normally passes 6/7 would handle it, but scope fns are exempt */ )) {
11451156 zend_optimizer_compact_literals (op_array , ctx );
11461157 if (ctx -> debug_level & ZEND_DUMP_AFTER_PASS_11 ) {
11471158 zend_dump_op_array (op_array , 0 , "after pass 11" , NULL );
@@ -1172,6 +1183,8 @@ static void zend_revert_pass_two(zend_op_array *op_array)
11721183
11731184 ZEND_ASSERT ((op_array -> fn_flags & ZEND_ACC_DONE_PASS_TWO ) != 0 );
11741185
1186+ zend_pass_two_revert_scope_fn_reservations (op_array );
1187+
11751188 opline = op_array -> opcodes ;
11761189 const zend_op * end = opline + op_array -> last ;
11771190 while (opline < end ) {
@@ -1304,6 +1317,7 @@ static void zend_redo_pass_two(zend_op_array *op_array)
13041317 }
13051318
13061319 op_array -> fn_flags |= ZEND_ACC_DONE_PASS_TWO ;
1320+ zend_pass_two_install_scope_fn_reservations (op_array );
13071321}
13081322
13091323static void zend_redo_pass_two_ex (zend_op_array * op_array , const zend_ssa * ssa )
@@ -1446,23 +1460,35 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, const zend_ssa *ssa)
14461460 }
14471461
14481462 op_array -> fn_flags |= ZEND_ACC_DONE_PASS_TWO ;
1463+ zend_pass_two_install_scope_fn_reservations (op_array );
14491464}
14501465
14511466static void zend_optimize_op_array (zend_op_array * op_array ,
14521467 zend_optimizer_ctx * ctx )
14531468{
1469+ bool is_scope_fn = zend_op_array_is_scope_fn (op_array );
1470+
1471+ /* Make scope fns individually optimizeable first, before running optimizer. */
1472+ uint32_t saved_scope_T = is_scope_fn ? op_array -> T : 0 ;
1473+ uint32_t saved_scope_ex_offset = is_scope_fn ? zend_unfixup_scope_func_self (op_array , saved_scope_T ) : 0 ;
1474+
14541475 /* Revert pass_two() */
14551476 zend_revert_pass_two (op_array );
14561477
14571478 /* Do actual optimizations */
14581479 zend_optimize (op_array , ctx );
14591480
1460- /* Redo pass_two() */
1461- zend_redo_pass_two (op_array );
1462-
1481+ /* Live ranges must be recalculated before scope fn fixup. */
14631482 if (op_array -> live_range ) {
14641483 zend_recalc_live_ranges (op_array , NULL );
14651484 }
1485+
1486+ if (is_scope_fn ) {
1487+ zend_refixup_scope_func_self (op_array , saved_scope_ex_offset , saved_scope_T );
1488+ }
1489+
1490+ /* Redo pass_two() */
1491+ zend_redo_pass_two (op_array );
14661492}
14671493
14681494static void zend_adjust_fcall_stack_size (const zend_op_array * op_array , const zend_optimizer_ctx * ctx )
@@ -1576,6 +1602,12 @@ static void step_optimize_op_array(zend_op_array *op_array, void *context) {
15761602 zend_optimize_op_array (op_array , (zend_optimizer_ctx * ) context );
15771603}
15781604
1605+ static void step_optimize_scope_fn_op_array (zend_op_array * op_array , void * context ) {
1606+ if (zend_op_array_is_scope_fn (op_array )) {
1607+ zend_optimize_op_array (op_array , (zend_optimizer_ctx * ) context );
1608+ }
1609+ }
1610+
15791611static void step_adjust_fcall_stack_size (zend_op_array * op_array , void * context ) {
15801612 zend_adjust_fcall_stack_size (op_array , (zend_optimizer_ctx * ) context );
15811613}
@@ -1634,6 +1666,12 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
16341666 }
16351667
16361668 for (i = 0 ; i < call_graph .op_arrays_count ; i ++ ) {
1669+ /* Single-op_array SSA can't see scope-fn children's reads of
1670+ * parent CVs. Subsequent DFA-based passes (DCE, SCCP) would
1671+ * treat parent CVs only used by a child as dead. */
1672+ if (call_graph .op_arrays [i ]-> fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES ) {
1673+ continue ;
1674+ }
16371675 func_info = ZEND_FUNC_INFO (call_graph .op_arrays [i ]);
16381676 if (func_info ) {
16391677 if (zend_dfa_analyze_op_array (call_graph .op_arrays [i ], & ctx , & func_info -> ssa ) == SUCCESS ) {
@@ -1646,6 +1684,9 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
16461684
16471685 //TODO: perform inner-script inference???
16481686 for (i = 0 ; i < call_graph .op_arrays_count ; i ++ ) {
1687+ if (call_graph .op_arrays [i ]-> fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES ) {
1688+ continue ;
1689+ }
16491690 func_info = ZEND_FUNC_INFO (call_graph .op_arrays [i ]);
16501691 if (func_info ) {
16511692 zend_dfa_optimize_op_array (call_graph .op_arrays [i ], & ctx , & func_info -> ssa , func_info -> call_map );
@@ -1685,14 +1726,9 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
16851726 }
16861727 }
16871728
1688- if (ZEND_OPTIMIZER_PASS_12 & optimization_level ) {
1689- for (i = 0 ; i < call_graph .op_arrays_count ; i ++ ) {
1690- zend_adjust_fcall_stack_size_graph (call_graph .op_arrays [i ]);
1691- }
1692- }
1693-
16941729 for (i = 0 ; i < call_graph .op_arrays_count ; i ++ ) {
16951730 op_array = call_graph .op_arrays [i ];
1731+ zend_op * old_opcodes = op_array -> opcodes ;
16961732 func_info = ZEND_FUNC_INFO (op_array );
16971733 if (func_info && func_info -> ssa .var_info ) {
16981734 zend_redo_pass_two_ex (op_array , & func_info -> ssa );
@@ -1705,6 +1741,31 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
17051741 zend_recalc_live_ranges (op_array , NULL );
17061742 }
17071743 }
1744+ /* Update offsets for pass 12, as zend_redo_pass_two may reallocate */
1745+ if (func_info && op_array -> opcodes != old_opcodes ) {
1746+ ptrdiff_t delta = op_array -> opcodes - old_opcodes ;
1747+ zend_call_info * call = func_info -> callee_info ;
1748+ while (call ) {
1749+ if (call -> caller_init_opline ) {
1750+ call -> caller_init_opline += delta ;
1751+ }
1752+ if (call -> caller_call_opline ) {
1753+ call -> caller_call_opline += delta ;
1754+ }
1755+ call = call -> next_callee ;
1756+ }
1757+ }
1758+ }
1759+
1760+ /* zend_build_call_graph ignores scope fns, so explicitely optimize them here. */
1761+ zend_foreach_op_array (script , step_optimize_scope_fn_op_array , & ctx );
1762+
1763+ /* PASS_12 requires knowledge of the final callee->T to compute INIT_FCALL stack sizes;
1764+ * for scope fns these change during zend_redo_pass_two, hence we do this late. */
1765+ if (ZEND_OPTIMIZER_PASS_12 & optimization_level ) {
1766+ for (i = 0 ; i < call_graph .op_arrays_count ; i ++ ) {
1767+ zend_adjust_fcall_stack_size_graph (call_graph .op_arrays [i ]);
1768+ }
17081769 }
17091770
17101771 for (i = 0 ; i < call_graph .op_arrays_count ; i ++ ) {
0 commit comments