From b008111980ddd7c1768f6940e17b503fc84af5f3 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Mon, 20 Jan 2020 11:41:06 -0500 Subject: [PATCH 1/3] Proposal: Run the Control Flow Graph pass twice Run the Control Flow Graph optimizations again, after all of the type inference steps and optimizations have run. Passes run after CFG eliminate various redundant conditions, infer types and constants for values, and can also change or remove opcodes, ensuring that the Control Flow Graph optimizations can optimize code further, and lets those passes do transformations relying on the subsequent optimizations implemented in the Control Flow Graph code. (without duplicating the implementation) This is meant as a proof of concept, - I'm not sure of the best way to make the debug output (e.g. pass numbers) follow conventions. - I'm not familiar with the data structures used. I'm assuming it's safe to run pass 5 at that point. - There are at most 3 passes on the first run of CFG. (see `#define PASSES 3` in block_pass.c) Maybe the second run of CFG should have a lower limit. Tests were updated: - CFG can eliminate unnecessary jumps - CFG optimized the calls to ECHO into a single call with a string. --- ext/opcache/Optimizer/zend_optimizer.c | 17 +++++++++++++++++ ext/opcache/tests/opt/sccp_002.phpt | 7 +++---- ext/opcache/tests/opt/sccp_003.phpt | 7 +++---- ext/opcache/tests/opt/sccp_004.phpt | 7 +++---- ext/opcache/tests/opt/sccp_010.phpt | 7 +++---- ext/opcache/tests/opt/sccp_012.phpt | 7 +++---- ext/opcache/tests/opt/sccp_026.phpt | 17 ++++++++--------- 7 files changed, 40 insertions(+), 29 deletions(-) diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 84d4c61831bb7..09fc2e779eb58 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -1300,6 +1300,16 @@ static void zend_optimize_op_array(zend_op_array *op_array, /* Redo pass_two() */ zend_redo_pass_two(op_array); + /* pass 5: + * - CFG optimization (again) after converting code to no-ops. + */ + if (ZEND_OPTIMIZER_PASS_5 & ctx->optimization_level) { + zend_optimize_cfg(op_array, ctx); + if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_5) { + zend_dump_op_array(op_array, 0, "after repeating pass 5", NULL); + } + } + if (op_array->live_range) { zend_recalc_live_ranges(op_array, NULL); } @@ -1417,6 +1427,13 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend } } + for (i = 0; i < call_graph.op_arrays_count; i++) { + zend_optimize_cfg(call_graph.op_arrays[i], &ctx); + if (debug_level & ZEND_DUMP_AFTER_PASS_5) { + zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 5 (again)", NULL); + } + } + if (ZEND_OPTIMIZER_PASS_11 & optimization_level) { for (i = 0; i < call_graph.op_arrays_count; i++) { zend_optimizer_compact_literals(call_graph.op_arrays[i], &ctx); diff --git a/ext/opcache/tests/opt/sccp_002.phpt b/ext/opcache/tests/opt/sccp_002.phpt index 0fd10f2ac57ad..373c24bfef63d 100644 --- a/ext/opcache/tests/opt/sccp_002.phpt +++ b/ext/opcache/tests/opt/sccp_002.phpt @@ -28,10 +28,9 @@ $_main: ; (lines=1, args=0, vars=0, tmps=0) ; %ssccp_002.php:1-14 L0 (14): RETURN int(1) -foo: ; (lines=4, args=1, vars=1, tmps=0) +foo: ; (lines=3, args=1, vars=1, tmps=0) ; (after optimizer) ; %ssccp_002.php:2-12 L0 (2): CV0($x) = RECV 1 -L1 (9): ECHO int(1) -L2 (11): ECHO int(1) -L3 (12): RETURN null +L1 (11): ECHO string("11") +L2 (12): RETURN null diff --git a/ext/opcache/tests/opt/sccp_003.phpt b/ext/opcache/tests/opt/sccp_003.phpt index 282a5788e628b..1ae7dde91fc82 100644 --- a/ext/opcache/tests/opt/sccp_003.phpt +++ b/ext/opcache/tests/opt/sccp_003.phpt @@ -28,9 +28,8 @@ $_main: ; (lines=1, args=0, vars=0, tmps=0) ; %ssccp_003.php:1-14 L0 (14): RETURN int(1) -foo: ; (lines=3, args=0, vars=0, tmps=0) +foo: ; (lines=2, args=0, vars=0, tmps=0) ; (after optimizer) ; %ssccp_003.php:2-12 -L0 (9): ECHO int(1) -L1 (11): ECHO int(1) -L2 (12): RETURN null +L0 (11): ECHO string("11") +L1 (12): RETURN null diff --git a/ext/opcache/tests/opt/sccp_004.phpt b/ext/opcache/tests/opt/sccp_004.phpt index d82212e0ef004..5d09fe9bbc506 100644 --- a/ext/opcache/tests/opt/sccp_004.phpt +++ b/ext/opcache/tests/opt/sccp_004.phpt @@ -31,10 +31,9 @@ $_main: ; (lines=1, args=0, vars=0, tmps=0) ; %ssccp_004.php:1-17 L0 (17): RETURN int(1) -foo: ; (lines=4, args=1, vars=1, tmps=0) +foo: ; (lines=3, args=1, vars=1, tmps=0) ; (after optimizer) ; %ssccp_004.php:2-15 L0 (2): CV0($x) = RECV 1 -L1 (11): ECHO bool(true) -L2 (14): ECHO int(1) -L3 (15): RETURN null +L1 (14): ECHO string("11") +L2 (15): RETURN null diff --git a/ext/opcache/tests/opt/sccp_010.phpt b/ext/opcache/tests/opt/sccp_010.phpt index e88bf579f7178..82e3a6546b0fb 100644 --- a/ext/opcache/tests/opt/sccp_010.phpt +++ b/ext/opcache/tests/opt/sccp_010.phpt @@ -29,9 +29,8 @@ $_main: ; (lines=1, args=0, vars=0, tmps=0) ; %ssccp_010.php:1-15 L0 (15): RETURN int(1) -foo: ; (lines=3, args=0, vars=0, tmps=0) +foo: ; (lines=2, args=0, vars=0, tmps=0) ; (after optimizer) ; %ssccp_010.php:2-13 -L0 (10): ECHO int(1) -L1 (12): ECHO int(1) -L2 (13): RETURN null +L0 (12): ECHO string("11") +L1 (13): RETURN null diff --git a/ext/opcache/tests/opt/sccp_012.phpt b/ext/opcache/tests/opt/sccp_012.phpt index 5d2f3e9a01c9b..3948063762650 100644 --- a/ext/opcache/tests/opt/sccp_012.phpt +++ b/ext/opcache/tests/opt/sccp_012.phpt @@ -31,9 +31,8 @@ $_main: ; (lines=1, args=0, vars=0, tmps=0) ; %ssccp_012.php:1-17 L0 (17): RETURN int(1) -foo: ; (lines=3, args=0, vars=0, tmps=0) +foo: ; (lines=2, args=0, vars=0, tmps=0) ; (after optimizer) ; %ssccp_012.php:2-15 -L0 (10): ECHO int(1) -L1 (14): ECHO int(4) -L2 (15): RETURN null +L0 (14): ECHO string("14") +L1 (15): RETURN null diff --git a/ext/opcache/tests/opt/sccp_026.phpt b/ext/opcache/tests/opt/sccp_026.phpt index 132c9ddef3bd3..7948b379a0b2c 100644 --- a/ext/opcache/tests/opt/sccp_026.phpt +++ b/ext/opcache/tests/opt/sccp_026.phpt @@ -15,7 +15,7 @@ function test($var) { return; } - var_dump($username); + var_dump($username); } ?> --EXPECTF-- @@ -24,15 +24,14 @@ $_main: ; (lines=1, args=0, vars=0, tmps=0) ; %s:1-10 L0 (10): RETURN int(1) -test: ; (lines=9, args=1, vars=2, tmps=1) +test: ; (lines=8, args=1, vars=2, tmps=1) ; (after optimizer) ; %s:2-8 L0 (2): CV0($var) = RECV 1 L1 (3): T2 = TYPE_CHECK (string) CV0($var) -L2 (3): JMPZ T2 L4 -L3 (3): JMP L5 -L4 (4): RETURN null -L5 (7): INIT_FCALL 1 %d string("var_dump") -L6 (7): SEND_VAR CV1($username) 1 -L7 (7): DO_ICALL -L8 (8): RETURN null +L2 (3): JMPNZ T2 L4 +L3 (4): RETURN null +L4 (7): INIT_FCALL 1 96 string("var_dump") +L5 (7): SEND_VAR CV1($username) 1 +L6 (7): DO_ICALL +L7 (8): RETURN null From a51bf0ade648c4aea650bb799097b3adda62f482 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Mon, 20 Jan 2020 16:02:03 -0500 Subject: [PATCH 2/3] Fix the pointers in func_info->callee_info --- ext/opcache/Optimizer/block_pass.c | 15 +++++++++++++++ ext/opcache/Optimizer/zend_optimizer.c | 10 ++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index bd1946e960b08..c1712ee2f7beb 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -22,6 +22,7 @@ #include "php.h" #include "Optimizer/zend_optimizer.h" #include "Optimizer/zend_optimizer_internal.h" +#include "Optimizer/zend_call_graph.h" #include "zend_API.h" #include "zend_constants.h" #include "zend_execute.h" @@ -924,12 +925,14 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array /* Rebuild plain (optimized) op_array from CFG */ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_optimizer_ctx *ctx) { + zend_func_info *func_info; zend_basic_block *blocks = cfg->blocks; zend_basic_block *end = blocks + cfg->blocks_count; zend_basic_block *b; zend_op *new_opcodes; zend_op *opline; uint32_t len = 0; + size_t opline_delta; int n; for (b = blocks; b < end; b++) { @@ -970,6 +973,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op } new_opcodes = emalloc(len * sizeof(zend_op)); + opline_delta = (void*)new_opcodes - (void*)op_array->opcodes; opline = new_opcodes; /* Copy code of reachable blocks into a single buffer */ @@ -1095,6 +1099,17 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op zend_build_delayed_early_binding_list(op_array); } + /* update call graph (needed in second run of pass 5) */ + func_info = ZEND_FUNC_INFO(op_array); + if (func_info) { + zend_call_info *call_info = func_info->callee_info; + while (call_info) { + call_info->caller_init_opline = ((void*)call_info->caller_init_opline) + opline_delta; + call_info->caller_call_opline = ((void*)call_info->caller_call_opline) + opline_delta; + call_info = call_info->next_callee; + } + } + /* rebuild map (just for printing) */ memset(cfg->map, -1, sizeof(int) * op_array->last); for (n = 0; n < cfg->blocks_count; n++) { diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 09fc2e779eb58..be2a185a3d329 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -1427,10 +1427,12 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend } } - for (i = 0; i < call_graph.op_arrays_count; i++) { - zend_optimize_cfg(call_graph.op_arrays[i], &ctx); - if (debug_level & ZEND_DUMP_AFTER_PASS_5) { - zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 5 (again)", NULL); + if (ZEND_OPTIMIZER_PASS_5 & optimization_level) { + for (i = 0; i < call_graph.op_arrays_count; i++) { + zend_optimize_cfg(call_graph.op_arrays[i], &ctx); + if (debug_level & ZEND_DUMP_AFTER_PASS_5) { + zend_dump_op_array(call_graph.op_arrays[i], 0, "after repeating pass 5", NULL); + } } } From c29f52ff3ee28df8defc97ebc68c576e242858c3 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Mon, 20 Jan 2020 16:19:46 -0500 Subject: [PATCH 3/3] Fix windows build, use uint8_t for pointer arithmetic --- ext/opcache/Optimizer/block_pass.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index c1712ee2f7beb..56a93558bd5e6 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -973,7 +973,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op } new_opcodes = emalloc(len * sizeof(zend_op)); - opline_delta = (void*)new_opcodes - (void*)op_array->opcodes; + opline_delta = ((uint8_t *)new_opcodes) - ((uint8_t *)op_array->opcodes); opline = new_opcodes; /* Copy code of reachable blocks into a single buffer */ @@ -1104,8 +1104,8 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op if (func_info) { zend_call_info *call_info = func_info->callee_info; while (call_info) { - call_info->caller_init_opline = ((void*)call_info->caller_init_opline) + opline_delta; - call_info->caller_call_opline = ((void*)call_info->caller_call_opline) + opline_delta; + call_info->caller_init_opline = (zend_op *)(((uint8_t *)call_info->caller_init_opline) + opline_delta); + call_info->caller_call_opline = (zend_op *)(((uint8_t *)call_info->caller_call_opline) + opline_delta); call_info = call_info->next_callee; } }