mirror of
https://github.com/php/php-src.git
synced 2025-08-16 14:08:47 +02:00
Added goblal optimisation passes based on data flow analyses using SSA form:
SCCP - Sparse Conditional Constant Propagation, DCE - Dead Code Elimination and removing of unused local variablesi. Squashed commit of the following: commitbf5ac05fc0
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 14:26:40 2017 +0300 Added news entry commit4cfa6984b1
Merge:1cdaaac
1f261d7
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 13:30:50 2017 +0300 Merge branch 'sccp' into dce * sccp: Bump OCI8 version for recent patch WS Fix test title Ensure that the stream position is kept between reads Turn off EXIF_DEBUG so Travis don't complain at me Don't add a new line to undefined tags in EXIF_DEBUG mode Fix compile error with EXIF_DEBUG update NEWS disable --with-pcre-valgrind on travis fix default args for --with-pcre-valgrind Enable valgrind support for PCRE by default in debug builds add oniguruma.patch to ease future upgrades SIZEOF_SIZE_T doesn't exist on AIX and POWER8 (ppc64le), keep using SIZEOF_LONG commit1f261d77cb
Merge:a32a3fb
b280ba8
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 13:30:39 2017 +0300 Merge branch 'master' into sccp * master: Bump OCI8 version for recent patch WS Fix test title Ensure that the stream position is kept between reads Turn off EXIF_DEBUG so Travis don't complain at me Don't add a new line to undefined tags in EXIF_DEBUG mode Fix compile error with EXIF_DEBUG update NEWS disable --with-pcre-valgrind on travis fix default args for --with-pcre-valgrind Enable valgrind support for PCRE by default in debug builds add oniguruma.patch to ease future upgrades SIZEOF_SIZE_T doesn't exist on AIX and POWER8 (ppc64le), keep using SIZEOF_LONG commit1cdaaac601
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 13:27:12 2017 +0300 Use generic evalution mechanism for constant functions commit75bd92a609
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 12:39:05 2017 +0300 Fixed use-def chain unlinking for "$a = 1; $a += $a;" commit7d7746814d
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 12:38:29 2017 +0300 Enable duplicate predecessors verification commit6b1667f206
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:55:20 2017 +0300 Removed duplicate definitions commit1415b53014
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:51:29 2017 +0300 Enable evaluation of constant functions with 3 arguments commitab367deef9
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:45:13 2017 +0300 Removed deprecated check commitc51659ea8c
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:40:42 2017 +0300 Reduce limit commitb1be5a04d7
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:38:23 2017 +0300 Disable constant array_flip() evaluation commit7a5b0596a1
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:33:20 2017 +0300 Fixed comments commit377e48b342
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:28:50 2017 +0300 Cast of string to long/double can not produce exception commit228dd01af3
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:24:50 2017 +0300 Added missed return commit0972a21636
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:22:36 2017 +0300 objects may be nested in array operands commitbd346bfa5c
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:19:20 2017 +0300 ~$resource is unsupported. commitc77e45610c
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:15:39 2017 +0300 ws commit0b64d71109
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:14:40 2017 +0300 Call to zend_ssa_unlink_use_chain() shouldn't be dropped commitcb7059fcf6
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:11:58 2017 +0300 Safer check for function name. The previous check is incorrect in ZTS build. commit7280aba1e1
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 11:02:10 2017 +0300 Missing warning commit54bc7b576c
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 14 10:56:42 2017 +0300 Proper check for successors count commitea8c004a15
Merge:624f76d
a32a3fb
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 13 15:56:26 2017 +0300 Merge branch 'sccp' into dce * sccp: fix fold Fixed bug #74866 extension_dir = "./ext" now use current directory for base add next vc15 toolset to the list Revert "Enable whole program optimization for builds without PGO, too" extend comment cleanup discontinued target commita32a3fb67c
Merge:2722dbf
5fb2abd
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 13 15:56:14 2017 +0300 Merge branch 'master' into sccp * master: fix fold Fixed bug #74866 extension_dir = "./ext" now use current directory for base add next vc15 toolset to the list Revert "Enable whole program optimization for builds without PGO, too" extend comment cleanup discontinued target commit624f76df48
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 13 12:30:27 2017 +0300 Set RETURN_VALUE_UNUSED instead of additional FREE opcode, if possible. Keep alive dead instructions that have to free two temporary variables. commit94c9b26695
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 13 11:51:14 2017 +0300 More accurate "vararg" handling in DCE commit665ed8491c
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 13 09:31:45 2017 +0300 Improved DCE performance, by avoiding redundand checks and repeatable iterations. commit3f42ce18ba
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 23:03:11 2017 +0300 Added few more instructions without side effects and exceptions commitb17178f991
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 20:39:02 2017 +0300 Temprary enable SSA validation in DEBUG build commite238a8dc79
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 20:37:53 2017 +0300 Inegrate SSA validation developed by Nikita commita247cee80b
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 20:31:27 2017 +0300 Perform DCE pass before other DFA optimisations, to properly reconstruct "no value" use-def chains. commita651564f29
Merge:06f6eb0
2722dbf
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 18:55:05 2017 +0300 Merge branch 'sccp' into dce * sccp: Resources should be closed during object destructioin, not during freeing. Guard against AppVeyor losing deps issue increase poll timeout as false positives mitigation Value of EG(user_exception_handler) should't relive request boundary sodium ext: remove function names before exception messages sodium ext: update the crypto_kx_*() API to the libsodium one Revert "fix macro redifinitions" commit2722dbfdf5
Merge:6595ea3
09d3b73
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 18:54:48 2017 +0300 Merge branch 'master' into sccp * master: Resources should be closed during object destructioin, not during freeing. Guard against AppVeyor losing deps issue increase poll timeout as false positives mitigation Value of EG(user_exception_handler) should't relive request boundary sodium ext: remove function names before exception messages sodium ext: update the crypto_kx_*() API to the libsodium one Revert "fix macro redifinitions" commit06f6eb0e68
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 14:52:28 2017 +0300 Use zend_ssa_is_no_val_use() instead of zend_has_improper_op1_use() commit4b64dbb30d
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 13:07:14 2017 +0300 Check if instruction may throw exception only for instructions without known side effects. Always disable removing ASSIGN and UNSET_VAR that may throw. commitc5aa1f47cd
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 11:21:07 2017 +0300 Use existing bit commitc2af153bae
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 11:10:48 2017 +0300 Updated Windows build commitde5e8fc129
Merge:8c0de53
6595ea3
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 11:10:12 2017 +0300 Merge branch 'sccp' into dce * sccp: (29 commits) Use existing bit Updated Windows build Fixed compilation error Remove debug code We need to check for the length here too, or we crash and no one likes that! :( * Implemented #65187 (exif_read_data/thumbnail: add support for stream resource) * ext/exif now uses FAST_ZPP Remove extraneous configure flag Revert "remove excessive checks and fix warnings" parametrize zip names Upgrade bundled PCRE to 8.41 Updated NEWS file with LDAP changes Fixed removing all controls by passing an empty array to ldap_set_option Filled in NEWS file with ext/ldap last modifications change order, allow to build as shared extension restore file deleted by mistake in a merge commit Fix segfault in php_stream_context_get_option call remove excessive checks and fix warnings fix macro redifinitions fix symbol availability and ws Remove this for now, as not found ... commit6595ea3420
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 10:27:02 2017 +0300 Use existing bit commitf0bfd36cb8
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 10:21:22 2017 +0300 Updated Windows build commita9bd7c89f2
Merge:d1eb5ed
2b7d3fb
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 12 09:51:32 2017 +0300 Merge branch 'master' into sccp * master: (27 commits) Fixed compilation error Remove debug code We need to check for the length here too, or we crash and no one likes that! :( * Implemented #65187 (exif_read_data/thumbnail: add support for stream resource) * ext/exif now uses FAST_ZPP Remove extraneous configure flag Revert "remove excessive checks and fix warnings" parametrize zip names Upgrade bundled PCRE to 8.41 Updated NEWS file with LDAP changes Fixed removing all controls by passing an empty array to ldap_set_option Filled in NEWS file with ext/ldap last modifications change order, allow to build as shared extension restore file deleted by mistake in a merge commit Fix segfault in php_stream_context_get_option call remove excessive checks and fix warnings fix macro redifinitions fix symbol availability and ws Remove this for now, as not found fix authors NEWS for Sodium ... commit8c0de53e5f
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 21:54:36 2017 +0300 Initial integration of Dead Code Elimination (DCE) and unused variable removing passes, originally developed in https://github.com/nikic/php-src/tree/opt, into DFA optimization pass. commitd1eb5ede3a
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 12:19:11 2017 +0300 Proper SSA reconstruction for "$a = $a;" commit4872d139b5
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 11:57:33 2017 +0300 Replace conditions, that should be always true, by ZEND_ASSERT() commit9915b1f5cd
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 11:56:51 2017 +0300 Fixed pass name commitd26ff1b88d
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 11:55:47 2017 +0300 Don't create identical predecessors commit0625fbe32b
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 09:36:07 2017 +0300 Update unreachable blocks. commit9d7d409e6a
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 09:28:49 2017 +0300 Keep consistent cfg.map[] commit85a86e58b2
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 02:36:14 2017 +0300 Remove unusded phi commitd5e0f2df4c
Author: Dmitry Stogov <dmitry@zend.com> Date: Tue Jul 11 02:35:00 2017 +0300 Don't clear phi->spources[] too early. commita90ed34295
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 21:29:39 2017 +0300 Make SCCP to remove dead live ranges. commit320237f3d8
Merge:63bbed5
7be2637
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 17:35:21 2017 +0300 Merge branch 'master' into sccp * master: Fixed memory leak introduced by7cb5bdf64a
eliminate casts remove checks for eol dependencies improve test Small fix in ext/ldap, Moved vars definitions to the beginning of the block using them ZipArchive implements countable, added ZipArchive::count() method commit63bbed5e71
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 17:01:15 2017 +0300 Evaluation of few more constant functions commit07f45d8a3d
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 16:22:47 2017 +0300 Properly unlinking dead blocks from predecessors/successors and dominators commit502002aa6e
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 13:33:14 2017 +0300 Replacel constant JMPZ/NZ/ZNZ by JMP or NOP commit3253e61b66
Merge:e7f69f0
161c378
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 12:22:39 2017 +0300 Merge branch 'master' into sccp * master: Revert "Fixed bug #74878" Upgrading note for #74837 Fixed bug #74837 - NEWS Implement Countable for DomNodeList and DOMNamedNodeMap (Request #74837) Fix #49649 - Handle property visibility changes on unserialization commite7f69f07fc
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 12:15:08 2017 +0300 Prevent compile-time evaluation of implode() with arguments causing run-time warnings commit0e882f189a
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 11:54:04 2017 +0300 Constant evaluation of ini_get() for some safe cases commit9e36a748b2
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 11:13:37 2017 +0300 Constant evaluation of implode() commite73046e266
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 10:51:23 2017 +0300 Fixed uninitialized value commitf5e2e8e68c
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 10:05:37 2017 +0300 Remove (compact) unused constants after SCCP pass commitf0b7bb86eb
Merge:e69d4f6
cfacf84
Author: Dmitry Stogov <dmitry@zend.com> Date: Mon Jul 10 09:10:00 2017 +0300 Merge branch 'master' into sccp * master: (37 commits) #73594 tests only check the extra params if dns_get_record is successful Fixed bug #74852 (property_exists returns true on unknown DateInterval property) fix uninitialized var fix comparison warning comply with POSIX signature fix warning remove some casts cleanup casts remove useless cast eliminate casts sync vim mode lines in main [ci skip] update NEWS [ci skip] update NEWS [ci skip] update NEWS Fixed bug #74883 SQLite3::__construct() produces "out of memory" exception with invalid flags Silent compiler warning Fix test Deprecated the read_exif_data() alias Add myself as exif maintainer update libs versions ... commite69d4f6140
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 7 12:51:41 2017 +0300 Avoid in-place modification of referenced data commit58f7c17978
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 7 12:33:24 2017 +0300 Use arena for temporary data. commit93d3e7ddc2
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 7 11:54:47 2017 +0300 Made sccp_ctx to be an "extension" of scdf_ctx and remove duplicate data. commitf810c6f7c4
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 7 11:20:48 2017 +0300 Improved SSCP integration commitd17ed887f3
Merge:d90805a
29653da
Author: Dmitry Stogov <dmitry@zend.com> Date: Fri Jul 7 10:22:37 2017 +0300 Merge branch 'master' into sccp * master: Fixed bug #74873 (Minor BC break: PCRE_JIT changes output of preg_match()). Fixed bug #72324 (imap_mailboxmsginfo() return wrong size) Fix redefine warnings Expand sb's name and capitalize my own Write the URL on a new line, so that it is easier copyable commitd90805a40b
Merge:2e5e03b
fc336c7
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 23:07:04 2017 +0300 Merge branch 'master' into sccp * master: Added missed dump of "main" script code replace the stack var by a macro [ci skip] sync NEWS minor fix for web announce add missing NEWS entry for #74087 and also fix the formatting move NEWS entry to the correct place, also bump the version commit2e5e03b673
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 23:03:41 2017 +0300 Call info should be removed, but at least we should prevent incorrect stack adjustment. commit1ee9110b35
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 19:34:43 2017 +0300 Remove NOP instructions, introduced bvy SCCP. This commit discloses unrelated issue caused ext/soap/tests/bug70211.phpt failure. commit9a2f50070d
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 16:34:02 2017 +0300 Avoid useless iterations for first SSA variablesi, always marked BOT. commitc57dd7c6ef
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 16:33:46 2017 +0300 Use reference-counting commit90f822d68e
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 14:00:22 2017 +0300 Support for few more opcodes commitcffee2f7e5
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 12:35:13 2017 +0300 Combined constants substitutaion and dead instruction removing in single pass. This eleminates substitution in dead instructions. commitf890375c16
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 10:34:48 2017 +0300 Use reference-counting instead of duplication commitdb0cd64dfa
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 03:04:27 2017 +0300 Improved SCDF<->SCCP interface - "get_feasible_successors" callback is changed into "mark_feasible_successors" and should mark necessary edges through scdf_mark_edge_feasible() - SCDF takes care about OP_DATA instruction - SCDF code is re-arranged to avoid repeatable checks commite0ad5dd489
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 00:55:40 2017 +0300 Changed representation of "feasible_edges", using one bit per edge. commitafee3138fe
Author: Dmitry Stogov <dmitry@zend.com> Date: Thu Jul 6 00:49:56 2017 +0300 Revert "Don't propagate unused values" This reverts commit84e5bfd430
. commit84e5bfd430
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 23:39:42 2017 +0300 Don't propagate unused values commitd4f15b9506
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 23:39:10 2017 +0300 Don't visit the same Phi twice commit2558311b4d
Merge:722a59d
7bb4ae5
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 21:51:06 2017 +0300 Merge branch 'master' into sccp * master: Fixed final dump "after optimizer" commit722a59ddb1
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 21:09:29 2017 +0300 SCCP doesn't support VERIFY_RETURN_TYPE (ext/opcache/tests/bug73789.phpt failure) commit7084fade4d
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 20:37:21 2017 +0300 Fixed SSA reconstruction commit37ec4e0845
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 19:34:46 2017 +0300 Disable constant propagation for variables that can be modified indirectly commit4bb9b6526e
Merge:6800460
73d5097
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 19:17:04 2017 +0300 Merge branch 'master' into sccp * master: (43 commits) Keep information about SSA variables, that may be modified indirectly. Added constants for known ldap controls OID and tests for ldap_get/set_option for controls Added support for controls to ldap_get_option [ci skip] sync NEWS NEWS for oniguruma Patch from the upstream git https://github.com/kkos/oniguruma/issues/60 (CVE-2017-9228) Patch from the upstream git https://github.com/kkos/oniguruma/issues/59 (CVE-2017-9229) b690371bbf97794b4a1d3f295d4fb9a8b05d402d Modified for onig 5.9.6 Patch from the upstream git https://github.com/kkos/oniguruma/issues/58 (CVE-2017-9227) Patch from the upstream git https://github.com/kkos/oniguruma/issues/57 (CVE-2017-9224) Patch from the upstream git https://github.com/kkos/oniguruma/issues/55 (CVE-2017-9226) b4bf968ad52afe14e60a2dc8a95d3555c543353a Modified for onig 5.9.6 f015fbdd95f76438cd86366467bb2b39870dd7c6 Modified for onig 5.9.6 valid_symbol_table removed Improve fix for #74145 Fix wddx Fix tests Fixed bug #74111 Fix bug #74603 - use correct buffer size Fix bug #74651 - check EVP_SealInit as it can return -1 Update NEWS Fix bug #74087 Fixed parsing of strange formats with mixed month/day and time strings ... commit680046086c
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 16:14:38 2017 +0300 Support for few more internal functions evaluation commit74a29468ef
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 13:42:55 2017 +0300 Disabled evaluation of strpos() with empty needle. commite8908946e5
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 13:17:30 2017 +0300 Replace calls to in_array() with constant array by IN_ARRAY instruction after SCCP. commit4e8fa2c3dd
Author: Dmitry Stogov <dmitry@zend.com> Date: Wed Jul 5 00:58:12 2017 +0300 Initial integration of Sparse Conditional Constant Propagation (SCCP), originally developed in https://github.com/nikic/php-src/tree/opt, into DFA optimization pass.
This commit is contained in:
parent
b5fd99b636
commit
2a286ad599
21 changed files with 4153 additions and 63 deletions
5
NEWS
5
NEWS
|
@ -44,6 +44,11 @@ PHP NEWS
|
|||
- LDAP:
|
||||
. Fixed passing an empty array to ldap_set_option for client or server controls.
|
||||
|
||||
- Opcache:
|
||||
. Added goblal optimisation passes based on data flow analyses using SSA form:
|
||||
SCCP - Sparse Conditional Constant Propagation, DCE - Dead Code Elimination
|
||||
and removing of unused local variables (Nikita, Dmitry)
|
||||
|
||||
- OpenSSL:
|
||||
. Fixed bug #74651 (negative-size-param (-1) in memcpy in zif_openssl_seal()).
|
||||
(Stas)
|
||||
|
|
|
@ -245,6 +245,14 @@ static inline int zend_bitset_last(zend_bitset set, uint32_t len)
|
|||
} \
|
||||
} while (0)
|
||||
|
||||
static inline int zend_bitset_pop_first(zend_bitset set, uint32_t len) {
|
||||
int i = zend_bitset_first(set, len);
|
||||
if (i >= 0) {
|
||||
zend_bitset_excl(set, i);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
#endif /* _ZEND_BITSET_H_ */
|
||||
|
||||
/*
|
||||
|
|
112
ext/opcache/Optimizer/compact_vars.c
Normal file
112
ext/opcache/Optimizer/compact_vars.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, Removing unused variables |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "ZendAccelerator.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
#include "zend_bitset.h"
|
||||
|
||||
/* This pass removes all CVs that are completely unused. It does *not* merge any CVs.
|
||||
* This pass does not operate on SSA form anymore. */
|
||||
void zend_optimizer_compact_vars(zend_op_array *op_array) {
|
||||
int i;
|
||||
|
||||
ALLOCA_FLAG(use_heap1);
|
||||
ALLOCA_FLAG(use_heap2);
|
||||
uint32_t used_cvs_len = zend_bitset_len(op_array->last_var);
|
||||
zend_bitset used_cvs = ZEND_BITSET_ALLOCA(used_cvs_len, use_heap1);
|
||||
uint32_t *cv_map = do_alloca(op_array->last_var * sizeof(uint32_t), use_heap2);
|
||||
uint32_t num_cvs, tmp_offset;
|
||||
|
||||
/* Determine which CVs are used */
|
||||
zend_bitset_clear(used_cvs, used_cvs_len);
|
||||
for (i = 0; i < op_array->last; i++) {
|
||||
zend_op *opline = &op_array->opcodes[i];
|
||||
if (opline->op1_type == IS_CV) {
|
||||
zend_bitset_incl(used_cvs, VAR_NUM(opline->op1.var));
|
||||
}
|
||||
if (opline->op2_type == IS_CV) {
|
||||
zend_bitset_incl(used_cvs, VAR_NUM(opline->op2.var));
|
||||
}
|
||||
if (opline->result_type == IS_CV) {
|
||||
zend_bitset_incl(used_cvs, VAR_NUM(opline->result.var));
|
||||
}
|
||||
}
|
||||
|
||||
num_cvs = 0;
|
||||
for (i = 0; i < op_array->last_var; i++) {
|
||||
if (zend_bitset_in(used_cvs, i)) {
|
||||
cv_map[i] = num_cvs++;
|
||||
} else {
|
||||
cv_map[i] = (uint32_t) -1;
|
||||
}
|
||||
}
|
||||
|
||||
free_alloca(used_cvs, use_heap1);
|
||||
if (num_cvs == op_array->last_var) {
|
||||
free_alloca(cv_map, use_heap2);
|
||||
return;
|
||||
}
|
||||
|
||||
ZEND_ASSERT(num_cvs < op_array->last_var);
|
||||
tmp_offset = op_array->last_var - num_cvs;
|
||||
|
||||
/* Update CV and TMP references in opcodes */
|
||||
for (i = 0; i < op_array->last; i++) {
|
||||
zend_op *opline = &op_array->opcodes[i];
|
||||
if (opline->op1_type == IS_CV) {
|
||||
opline->op1.var = NUM_VAR(cv_map[VAR_NUM(opline->op1.var)]);
|
||||
} else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
|
||||
opline->op1.var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
if (opline->op2_type == IS_CV) {
|
||||
opline->op2.var = NUM_VAR(cv_map[VAR_NUM(opline->op2.var)]);
|
||||
} else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
|
||||
opline->op2.var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
if (opline->result_type == IS_CV) {
|
||||
opline->result.var = NUM_VAR(cv_map[VAR_NUM(opline->result.var)]);
|
||||
} else if (opline->result_type & (IS_VAR|IS_TMP_VAR)) {
|
||||
opline->result.var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update TMP references in live ranges */
|
||||
if (op_array->live_range) {
|
||||
for (i = 0; i < op_array->last_live_range; i++) {
|
||||
op_array->live_range[i].var -= sizeof(zval) * tmp_offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update CV name table */
|
||||
{
|
||||
zend_string **names = safe_emalloc(sizeof(zend_string *), num_cvs, 0);
|
||||
for (i = 0; i < op_array->last_var; i++) {
|
||||
if (cv_map[i] != (uint32_t) -1) {
|
||||
names[cv_map[i]] = op_array->vars[i];
|
||||
} else {
|
||||
zend_string_release(op_array->vars[i]);
|
||||
}
|
||||
}
|
||||
efree(op_array->vars);
|
||||
op_array->vars = names;
|
||||
}
|
||||
|
||||
op_array->last_var = num_cvs;
|
||||
|
||||
free_alloca(cv_map, use_heap2);
|
||||
}
|
647
ext/opcache/Optimizer/dce.c
Normal file
647
ext/opcache/Optimizer/dce.c
Normal file
|
@ -0,0 +1,647 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, DCE - Dead Code Elimination |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "ZendAccelerator.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
#include "Optimizer/zend_inference.h"
|
||||
#include "Optimizer/zend_ssa.h"
|
||||
#include "Optimizer/zend_func_info.h"
|
||||
#include "Optimizer/zend_call_graph.h"
|
||||
#include "zend_bitset.h"
|
||||
|
||||
/* This pass implements a form of dead code elimination (DCE). The algorithm optimistically assumes
|
||||
* that all instructions and phis are dead. Instructions with immediate side-effects are then marked
|
||||
* as live. We then recursively (using a worklist) propagate liveness to the instructions that def
|
||||
* the used operands.
|
||||
*
|
||||
* Notes:
|
||||
* * This pass does not perform unreachable code elimination. This happens as part of the SCCP
|
||||
* pass.
|
||||
* * The DCE is performed without taking control-dependence into account, i.e. all conditional
|
||||
* branches are assumed to be live. It's possible to take control-dependence into account using
|
||||
* the DCE algorithm described by Cytron et al., however it requires the construction of a
|
||||
* postdominator tree and of postdominance frontiers, which does not seem worthwhile at this
|
||||
* point.
|
||||
* * We separate intrinsic side-effects from potential side-effects in the form of notices thrown
|
||||
* by the instruction (in case we want to make this configurable). See may_have_side_effect() and
|
||||
* zend_may_throw().
|
||||
* * We often cannot DCE assignments and unsets while guaranteeing that dtors run in the same
|
||||
* order. There is an optimization option to allow reordering of dtor effects.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
zend_ssa *ssa;
|
||||
zend_op_array *op_array;
|
||||
zend_bitset instr_dead;
|
||||
zend_bitset phi_dead;
|
||||
zend_bitset instr_worklist;
|
||||
zend_bitset phi_worklist;
|
||||
zend_bitset phi_worklist_no_val;
|
||||
uint32_t instr_worklist_len;
|
||||
uint32_t phi_worklist_len;
|
||||
unsigned reorder_dtor_effects : 1;
|
||||
} context;
|
||||
|
||||
static inline zend_bool is_bad_mod(const zend_ssa *ssa, int use, int def) {
|
||||
if (def < 0) {
|
||||
/* This modification is not tracked by SSA, assume the worst */
|
||||
return 1;
|
||||
}
|
||||
if (ssa->var_info[use].type & MAY_BE_REF) {
|
||||
/* Modification of reference may have side-effect */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool may_have_side_effects(
|
||||
zend_op_array *op_array, zend_ssa *ssa,
|
||||
const zend_op *opline, const zend_ssa_op *ssa_op,
|
||||
zend_bool reorder_dtor_effects) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_NOP:
|
||||
case ZEND_IS_IDENTICAL:
|
||||
case ZEND_IS_NOT_IDENTICAL:
|
||||
case ZEND_QM_ASSIGN:
|
||||
case ZEND_FREE:
|
||||
case ZEND_TYPE_CHECK:
|
||||
case ZEND_DEFINED:
|
||||
case ZEND_ADD:
|
||||
case ZEND_SUB:
|
||||
case ZEND_MUL:
|
||||
case ZEND_POW:
|
||||
case ZEND_BW_OR:
|
||||
case ZEND_BW_AND:
|
||||
case ZEND_BW_XOR:
|
||||
case ZEND_CONCAT:
|
||||
case ZEND_FAST_CONCAT:
|
||||
case ZEND_DIV:
|
||||
case ZEND_MOD:
|
||||
case ZEND_BOOL_XOR:
|
||||
case ZEND_BOOL:
|
||||
case ZEND_BOOL_NOT:
|
||||
case ZEND_BW_NOT:
|
||||
case ZEND_SL:
|
||||
case ZEND_SR:
|
||||
case ZEND_IS_EQUAL:
|
||||
case ZEND_IS_NOT_EQUAL:
|
||||
case ZEND_IS_SMALLER:
|
||||
case ZEND_IS_SMALLER_OR_EQUAL:
|
||||
case ZEND_CASE:
|
||||
case ZEND_CAST:
|
||||
case ZEND_ROPE_INIT:
|
||||
case ZEND_ROPE_ADD:
|
||||
case ZEND_ROPE_END:
|
||||
case ZEND_INIT_ARRAY:
|
||||
case ZEND_ADD_ARRAY_ELEMENT:
|
||||
case ZEND_SPACESHIP:
|
||||
case ZEND_STRLEN:
|
||||
case ZEND_COUNT:
|
||||
case ZEND_GET_TYPE:
|
||||
case ZEND_ISSET_ISEMPTY_THIS:
|
||||
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
|
||||
case ZEND_FETCH_DIM_IS:
|
||||
case ZEND_ISSET_ISEMPTY_VAR:
|
||||
case ZEND_FETCH_IS:
|
||||
/* No side effects */
|
||||
return 0;
|
||||
case ZEND_JMP:
|
||||
case ZEND_JMPZ:
|
||||
case ZEND_JMPNZ:
|
||||
case ZEND_JMPZNZ:
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_JMP_SET:
|
||||
case ZEND_COALESCE:
|
||||
case ZEND_ASSERT_CHECK:
|
||||
/* For our purposes a jumps and branches are side effects. */
|
||||
return 1;
|
||||
case ZEND_BEGIN_SILENCE:
|
||||
case ZEND_END_SILENCE:
|
||||
case ZEND_ECHO:
|
||||
case ZEND_INCLUDE_OR_EVAL:
|
||||
case ZEND_THROW:
|
||||
case ZEND_EXT_STMT:
|
||||
case ZEND_EXT_FCALL_BEGIN:
|
||||
case ZEND_EXT_FCALL_END:
|
||||
case ZEND_EXT_NOP:
|
||||
case ZEND_TICKS:
|
||||
case ZEND_YIELD:
|
||||
case ZEND_YIELD_FROM:
|
||||
/* Intrinsic side effects */
|
||||
return 1;
|
||||
case ZEND_DO_FCALL:
|
||||
case ZEND_DO_FCALL_BY_NAME:
|
||||
case ZEND_DO_ICALL:
|
||||
case ZEND_DO_UCALL:
|
||||
/* For now assume all calls have side effects */
|
||||
return 1;
|
||||
case ZEND_RECV:
|
||||
case ZEND_RECV_INIT:
|
||||
/* Even though RECV_INIT can be side-effect free, these cannot be simply dropped
|
||||
* due to the prologue skipping code. */
|
||||
return 1;
|
||||
case ZEND_ASSIGN_REF:
|
||||
return 1;
|
||||
case ZEND_ASSIGN:
|
||||
{
|
||||
if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)) {
|
||||
return 1;
|
||||
}
|
||||
if (!reorder_dtor_effects) {
|
||||
if (opline->op2_type != IS_CONST && (OP2_INFO() & MAY_HAVE_DTOR)) {
|
||||
/* DCE might shorten lifetime */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case ZEND_UNSET_VAR:
|
||||
{
|
||||
uint32_t t1 = OP1_INFO();
|
||||
if (!(opline->extended_value & ZEND_QUICK_SET)) {
|
||||
return 1;
|
||||
}
|
||||
if (t1 & MAY_BE_REF) {
|
||||
/* We don't consider uses as the LHS of an assignment as real uses during DCE, so
|
||||
* an unset may be considered dead even if there is a later assignment to the
|
||||
* variable. Removing the unset in this case would not be correct if the variable
|
||||
* is a reference, because unset breaks references. */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case ZEND_PRE_INC:
|
||||
case ZEND_POST_INC:
|
||||
case ZEND_PRE_DEC:
|
||||
case ZEND_POST_DEC:
|
||||
return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def);
|
||||
case ZEND_ASSIGN_ADD:
|
||||
case ZEND_ASSIGN_SUB:
|
||||
case ZEND_ASSIGN_MUL:
|
||||
case ZEND_ASSIGN_DIV:
|
||||
case ZEND_ASSIGN_MOD:
|
||||
case ZEND_ASSIGN_SL:
|
||||
case ZEND_ASSIGN_SR:
|
||||
case ZEND_ASSIGN_CONCAT:
|
||||
case ZEND_ASSIGN_BW_OR:
|
||||
case ZEND_ASSIGN_BW_AND:
|
||||
case ZEND_ASSIGN_BW_XOR:
|
||||
case ZEND_ASSIGN_POW:
|
||||
if (opline->extended_value) {
|
||||
/* ASSIGN_DIM has no side-effect, but we can't deal with OP_DATA anyway */
|
||||
return 1;
|
||||
}
|
||||
return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def);
|
||||
default:
|
||||
/* For everything we didn't handle, assume a side-effect */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline void add_to_worklists(context *ctx, int var_num, int check) {
|
||||
zend_ssa_var *var = &ctx->ssa->vars[var_num];
|
||||
if (var->definition >= 0) {
|
||||
if (!check || zend_bitset_in(ctx->instr_dead, var->definition)) {
|
||||
zend_bitset_incl(ctx->instr_worklist, var->definition);
|
||||
}
|
||||
} else if (var->definition_phi) {
|
||||
if (!check || zend_bitset_in(ctx->phi_dead, var_num)) {
|
||||
zend_bitset_incl(ctx->phi_worklist, var_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void add_to_phi_worklist_no_val(context *ctx, int var_num) {
|
||||
zend_ssa_var *var = &ctx->ssa->vars[var_num];
|
||||
if (var->definition_phi && zend_bitset_in(ctx->phi_dead, var_num)) {
|
||||
zend_bitset_incl(ctx->phi_worklist_no_val, var_num);
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op, int check) {
|
||||
if (ssa_op->result_use >= 0) {
|
||||
add_to_worklists(ctx, ssa_op->result_use, check);
|
||||
}
|
||||
if (ssa_op->op1_use >= 0) {
|
||||
if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) {
|
||||
add_to_worklists(ctx, ssa_op->op1_use, check);
|
||||
} else {
|
||||
add_to_phi_worklist_no_val(ctx, ssa_op->op1_use);
|
||||
}
|
||||
}
|
||||
if (ssa_op->op2_use >= 0) {
|
||||
if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) {
|
||||
add_to_worklists(ctx, ssa_op->op2_use, check);
|
||||
} else {
|
||||
add_to_phi_worklist_no_val(ctx, ssa_op->op2_use);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static zend_always_inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi, int check) {
|
||||
zend_ssa *ssa = ctx->ssa;
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
add_to_worklists(ctx, source, check);
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
}
|
||||
|
||||
static inline zend_bool is_var_dead(context *ctx, int var_num) {
|
||||
zend_ssa_var *var = &ctx->ssa->vars[var_num];
|
||||
if (var->definition_phi) {
|
||||
return zend_bitset_in(ctx->phi_dead, var_num);
|
||||
} else if (var->definition >= 0) {
|
||||
return zend_bitset_in(ctx->instr_dead, var->definition);
|
||||
} else {
|
||||
/* Variable has no definition, so either the definition has already been removed (var is
|
||||
* dead) or this is one of the implicit variables at the start of the function (for our
|
||||
* purposes live) */
|
||||
return var_num >= ctx->op_array->last_var;
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes we can mark the var as EXT_UNUSED
|
||||
static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, zend_op *opline) {
|
||||
if (use_chain >= 0) {
|
||||
return 0;
|
||||
}
|
||||
zend_ssa_var *var = &ctx->ssa->vars[free_var];
|
||||
int def = var->definition;
|
||||
|
||||
if (def >= 0) {
|
||||
zend_ssa_op *def_op = &ctx->ssa->ops[def];
|
||||
|
||||
if (def_op->result_def == free_var
|
||||
&& var->phi_use_chain == NULL
|
||||
&& var->use_chain == (opline - ctx->op_array->opcodes)) {
|
||||
zend_op *def_opline = &ctx->op_array->opcodes[def];
|
||||
|
||||
switch (def_opline->opcode) {
|
||||
case ZEND_ASSIGN:
|
||||
case ZEND_ASSIGN_REF:
|
||||
case ZEND_ASSIGN_DIM:
|
||||
case ZEND_ASSIGN_OBJ:
|
||||
case ZEND_ASSIGN_ADD:
|
||||
case ZEND_ASSIGN_SUB:
|
||||
case ZEND_ASSIGN_MUL:
|
||||
case ZEND_ASSIGN_DIV:
|
||||
case ZEND_ASSIGN_MOD:
|
||||
case ZEND_ASSIGN_SL:
|
||||
case ZEND_ASSIGN_SR:
|
||||
case ZEND_ASSIGN_CONCAT:
|
||||
case ZEND_ASSIGN_BW_OR:
|
||||
case ZEND_ASSIGN_BW_AND:
|
||||
case ZEND_ASSIGN_BW_XOR:
|
||||
case ZEND_ASSIGN_POW:
|
||||
case ZEND_PRE_INC:
|
||||
case ZEND_POST_INC:
|
||||
case ZEND_PRE_DEC:
|
||||
case ZEND_POST_DEC:
|
||||
case ZEND_PRE_INC_OBJ:
|
||||
case ZEND_POST_INC_OBJ:
|
||||
case ZEND_PRE_DEC_OBJ:
|
||||
case ZEND_POST_DEC_OBJ:
|
||||
case ZEND_DO_ICALL:
|
||||
case ZEND_DO_UCALL:
|
||||
case ZEND_DO_FCALL_BY_NAME:
|
||||
case ZEND_DO_FCALL:
|
||||
case ZEND_INCLUDE_OR_EVAL:
|
||||
case ZEND_YIELD:
|
||||
case ZEND_YIELD_FROM:
|
||||
case ZEND_ASSERT_CHECK:
|
||||
def_opline->result_type = IS_UNUSED;
|
||||
def_opline->result.var = 0;
|
||||
def_op->result_def = -1;
|
||||
var->definition = -1;
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Returns whether the instruction has been DCEd */
|
||||
static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
|
||||
zend_ssa *ssa = ctx->ssa;
|
||||
int free_var = -1;
|
||||
zend_uchar free_var_type;
|
||||
|
||||
if (opline->opcode == ZEND_NOP) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
|
||||
if (opline->opcode == ZEND_FREE && !is_var_dead(ctx, ssa_op->op1_use)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op1_use)) {
|
||||
if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) {
|
||||
free_var = ssa_op->op1_use;
|
||||
free_var_type = opline->op1_type;
|
||||
}
|
||||
}
|
||||
if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) {
|
||||
if (!try_remove_var_def(ctx, ssa_op->op2_use, ssa_op->op2_use_chain, opline)) {
|
||||
if (free_var >= 0) {
|
||||
// TODO: We can't free two vars. Keep instruction alive.
|
||||
zend_bitset_excl(ctx->instr_dead, opline - ctx->op_array->opcodes);
|
||||
return 0;
|
||||
}
|
||||
free_var = ssa_op->op2_use;
|
||||
free_var_type = opline->op2_type;
|
||||
}
|
||||
}
|
||||
|
||||
zend_ssa_rename_defs_of_instr(ctx->ssa, ssa_op);
|
||||
zend_ssa_remove_instr(ctx->ssa, opline, ssa_op);
|
||||
|
||||
if (free_var >= 0) {
|
||||
opline->opcode = ZEND_FREE;
|
||||
opline->op1.var = (uintptr_t) ZEND_CALL_VAR_NUM(NULL, ssa->vars[free_var].var);
|
||||
opline->op1_type = free_var_type;
|
||||
|
||||
ssa_op->op1_use = free_var;
|
||||
ssa_op->op1_use_chain = ssa->vars[free_var].use_chain;
|
||||
ssa->vars[free_var].use_chain = ssa_op - ssa->ops;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO Move this somewhere else (CFG simplification?)
|
||||
static int simplify_jumps(zend_ssa *ssa, zend_op_array *op_array) {
|
||||
int removed_ops = 0;
|
||||
zend_basic_block *block;
|
||||
FOREACH_BLOCK(block) {
|
||||
int block_num = block - ssa->cfg.blocks;
|
||||
zend_op *opline = &op_array->opcodes[block->start + block->len - 1];
|
||||
zend_ssa_op *ssa_op = &ssa->ops[block->start + block->len - 1];
|
||||
zval *op1;
|
||||
|
||||
if (block->len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert jump-and-set into jump if result is not used */
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ_EX:
|
||||
ZEND_ASSERT(ssa_op->result_def >= 0);
|
||||
if (ssa->vars[ssa_op->result_def].use_chain < 0
|
||||
&& ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
|
||||
opline->opcode = ZEND_JMPZ;
|
||||
opline->result_type = IS_UNUSED;
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
}
|
||||
break;
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_JMP_SET:
|
||||
ZEND_ASSERT(ssa_op->result_def >= 0);
|
||||
if (ssa->vars[ssa_op->result_def].use_chain < 0
|
||||
&& ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
|
||||
opline->opcode = ZEND_JMPNZ;
|
||||
opline->result_type = IS_UNUSED;
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Convert jump-and-set to QM_ASSIGN/BOOL if the "else" branch is not taken. */
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
if (block->successors_count == 1 && block->successors[0] != block_num + 1) {
|
||||
opline->opcode = ZEND_BOOL;
|
||||
}
|
||||
break;
|
||||
case ZEND_JMP_SET:
|
||||
case ZEND_COALESCE:
|
||||
if (block->successors_count == 1 && block->successors[0] != block_num + 1) {
|
||||
opline->opcode = ZEND_QM_ASSIGN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (opline->op1_type != IS_CONST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Convert constant conditional jump to unconditional jump */
|
||||
op1 = &ZEND_OP1_LITERAL(opline);
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ:
|
||||
if (!zend_is_true(op1)) {
|
||||
literal_dtor(op1);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->op1.num = opline->op2.num;
|
||||
opline->opcode = ZEND_JMP;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
removed_ops++;
|
||||
}
|
||||
break;
|
||||
case ZEND_JMPNZ:
|
||||
if (zend_is_true(op1)) {
|
||||
literal_dtor(op1);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->op1.num = opline->op2.num;
|
||||
opline->opcode = ZEND_JMP;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
removed_ops++;
|
||||
}
|
||||
break;
|
||||
case ZEND_COALESCE:
|
||||
ZEND_ASSERT(ssa_op->result_def >= 0);
|
||||
if (ssa->vars[ssa_op->result_def].use_chain >= 0
|
||||
|| ssa->vars[ssa_op->result_def].phi_use_chain != NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
if (Z_TYPE_P(op1) != IS_NULL) {
|
||||
literal_dtor(op1);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->op1.num = opline->op2.num;
|
||||
opline->opcode = ZEND_JMP;
|
||||
opline->result_type = IS_UNUSED;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
removed_ops++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} FOREACH_BLOCK_END();
|
||||
return removed_ops;
|
||||
}
|
||||
|
||||
static inline int get_common_phi_source(zend_ssa *ssa, zend_ssa_phi *phi) {
|
||||
int common_source = -1;
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
if (common_source == -1) {
|
||||
common_source = source;
|
||||
} else if (common_source != source && source != phi->ssa_var) {
|
||||
return -1;
|
||||
}
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
ZEND_ASSERT(common_source != -1);
|
||||
return common_source;
|
||||
}
|
||||
|
||||
static void try_remove_trivial_phi(context *ctx, zend_ssa_phi *phi) {
|
||||
zend_ssa *ssa = ctx->ssa;
|
||||
if (phi->pi < 0) {
|
||||
/* Phi assignment with identical source operands */
|
||||
int common_source = get_common_phi_source(ssa, phi);
|
||||
if (common_source >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, phi->ssa_var, common_source, 1);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}
|
||||
} else {
|
||||
/* Pi assignment that is only used in Phi/Pi assignments */
|
||||
// TODO What if we want to rerun type inference after DCE? Maybe separate this?
|
||||
/*ZEND_ASSERT(phi->sources[0] != -1);
|
||||
if (ssa->vars[phi->ssa_var].use_chain < 0) {
|
||||
zend_ssa_rename_var_uses_keep_types(ssa, phi->ssa_var, phi->sources[0], 1);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
static inline zend_bool may_break_varargs(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op) {
|
||||
if (ssa_op->op1_def >= 0
|
||||
&& ssa->vars[ssa_op->op1_def].var < op_array->num_args) {
|
||||
return 1;
|
||||
}
|
||||
if (ssa_op->op2_def >= 0
|
||||
&& ssa->vars[ssa_op->op2_def].var < op_array->num_args) {
|
||||
return 1;
|
||||
}
|
||||
if (ssa_op->result_def >= 0
|
||||
&& ssa->vars[ssa_op->result_def].var < op_array->num_args) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects) {
|
||||
int i;
|
||||
zend_ssa_phi *phi;
|
||||
int removed_ops = 0;
|
||||
|
||||
/* DCE of CV operations that changes arguments may affect vararg functions. */
|
||||
zend_bool has_varargs = ssa->cfg.vararg;
|
||||
|
||||
context ctx;
|
||||
ctx.ssa = ssa;
|
||||
ctx.op_array = op_array;
|
||||
ctx.reorder_dtor_effects = reorder_dtor_effects;
|
||||
|
||||
/* We have no dedicated phi vector, so we use the whole ssa var vector instead */
|
||||
ctx.instr_worklist_len = zend_bitset_len(op_array->last);
|
||||
ctx.instr_worklist = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
memset(ctx.instr_worklist, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
|
||||
ctx.phi_worklist = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
memset(ctx.phi_worklist, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
ctx.phi_worklist_no_val = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
memset(ctx.phi_worklist_no_val, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
|
||||
/* Optimistically assume all instructions and phis to be dead */
|
||||
ctx.instr_dead = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
memset(ctx.instr_dead, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
|
||||
ctx.phi_dead = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
memset(ctx.phi_dead, 0xff, sizeof(zend_ulong) * ctx.phi_worklist_len);
|
||||
|
||||
/* Mark reacable instruction without side effects as dead */
|
||||
int b = ssa->cfg.blocks_count;
|
||||
while (b > 0) {
|
||||
b--;
|
||||
zend_basic_block *block = &ssa->cfg.blocks[b];
|
||||
if (!(block->flags & ZEND_BB_REACHABLE)) {
|
||||
continue;
|
||||
}
|
||||
i = block->start + block->len;
|
||||
while (i > block->start) {
|
||||
i--;
|
||||
|
||||
if (zend_bitset_in(ctx.instr_worklist, i)) {
|
||||
zend_bitset_excl(ctx.instr_worklist, i);
|
||||
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
|
||||
} else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
|
||||
|| zend_may_throw(&op_array->opcodes[i], op_array, ssa)
|
||||
|| (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) {
|
||||
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
|
||||
} else {
|
||||
zend_bitset_incl(ctx.instr_dead, i);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Propagate liveness backwards to all definitions of used vars */
|
||||
while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len)
|
||||
|| !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) {
|
||||
while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) {
|
||||
zend_bitset_excl(ctx.instr_dead, i);
|
||||
add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 1);
|
||||
}
|
||||
while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
|
||||
zend_bitset_excl(ctx.phi_dead, i);
|
||||
zend_bitset_excl(ctx.phi_worklist_no_val, i);
|
||||
add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Eliminate dead instructions */
|
||||
ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
|
||||
removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
|
||||
} ZEND_BITSET_FOREACH_END();
|
||||
|
||||
/* Improper uses don't count as "uses" for the purpose of instruction elimination,
|
||||
* but we have to retain phis defining them.
|
||||
* Propagate this information backwards, marking any phi with an improperly used
|
||||
* target as non-dead. */
|
||||
while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, ctx.phi_worklist_len)) >= 0) {
|
||||
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
|
||||
int source;
|
||||
zend_bitset_excl(ctx.phi_dead, i);
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
add_to_phi_worklist_no_val(&ctx, source);
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
}
|
||||
|
||||
/* Now collect the actually dead phis */
|
||||
FOREACH_PHI(phi) {
|
||||
if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) {
|
||||
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
} else {
|
||||
/* Remove trivial phis (phis with identical source operands) */
|
||||
try_remove_trivial_phi(&ctx, phi);
|
||||
}
|
||||
} FOREACH_PHI_END();
|
||||
|
||||
removed_ops += simplify_jumps(ssa, op_array);
|
||||
|
||||
return removed_ops;
|
||||
}
|
|
@ -31,6 +31,14 @@
|
|||
#include "zend_inference.h"
|
||||
#include "zend_dump.h"
|
||||
|
||||
#ifndef ZEND_DEBUG_DFA
|
||||
# define ZEND_DEBUG_DFA ZEND_DEBUG
|
||||
#endif
|
||||
|
||||
#if ZEND_DEBUG_DFA
|
||||
# include "ssa_integrity.c"
|
||||
#endif
|
||||
|
||||
int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags)
|
||||
{
|
||||
uint32_t build_flags;
|
||||
|
@ -87,7 +95,7 @@ int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx,
|
|||
}
|
||||
|
||||
if (ctx->debug_level & ZEND_DUMP_DFA_SSA) {
|
||||
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa);
|
||||
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "dfa ssa", ssa);
|
||||
}
|
||||
|
||||
|
||||
|
@ -153,6 +161,7 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa)
|
|||
if (i != target) {
|
||||
op_array->opcodes[target] = op_array->opcodes[i];
|
||||
ssa->ops[target] = ssa->ops[i];
|
||||
ssa->cfg.map[target] = b - blocks;
|
||||
}
|
||||
target++;
|
||||
}
|
||||
|
@ -171,6 +180,9 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa)
|
|||
new_opline = op_array->opcodes + target - 1;
|
||||
zend_optimizer_migrate_jump(op_array, new_opline, opline);
|
||||
}
|
||||
} else {
|
||||
b->start = target;
|
||||
b->len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,7 +331,126 @@ static zend_bool opline_supports_assign_contraction(
|
|||
return 1;
|
||||
}
|
||||
|
||||
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa)
|
||||
int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
|
||||
{
|
||||
zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
|
||||
int removed_ops = 0;
|
||||
|
||||
if (func_info->callee_info) {
|
||||
zend_call_info *call_info = func_info->callee_info;
|
||||
|
||||
do {
|
||||
if (call_info->caller_call_opline->opcode == ZEND_DO_ICALL
|
||||
&& call_info->callee_func
|
||||
&& ZSTR_LEN(call_info->callee_func->common.function_name) == sizeof("in_array")-1
|
||||
&& memcmp(ZSTR_VAL(call_info->callee_func->common.function_name), "in_array", sizeof("in_array")-1) == 0
|
||||
&& (call_info->caller_init_opline->extended_value == 2
|
||||
|| (call_info->caller_init_opline->extended_value == 3
|
||||
&& (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL
|
||||
&& (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) {
|
||||
|
||||
zend_op *send_array;
|
||||
zend_op *send_needly;
|
||||
zend_bool strict = 0;
|
||||
|
||||
if (call_info->caller_init_opline->extended_value == 2) {
|
||||
send_array = call_info->caller_call_opline - 1;
|
||||
send_needly = call_info->caller_call_opline - 2;
|
||||
} else {
|
||||
if (zend_is_true(CT_CONSTANT_EX(op_array, (call_info->caller_call_opline - 1)->op1.constant))) {
|
||||
strict = 1;
|
||||
}
|
||||
send_array = call_info->caller_call_opline - 2;
|
||||
send_needly = call_info->caller_call_opline - 3;
|
||||
}
|
||||
|
||||
if (send_array->opcode == ZEND_SEND_VAL
|
||||
&& send_array->op1_type == IS_CONST
|
||||
&& Z_TYPE_P(CT_CONSTANT_EX(op_array, send_array->op1.constant)) == IS_ARRAY
|
||||
&& (send_needly->opcode == ZEND_SEND_VAL
|
||||
|| send_needly->opcode == ZEND_SEND_VAR)
|
||||
) {
|
||||
int ok = 1;
|
||||
|
||||
HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, send_array->op1.constant));
|
||||
HashTable *dst;
|
||||
zval *val, tmp;
|
||||
zend_ulong idx;
|
||||
|
||||
ZVAL_TRUE(&tmp);
|
||||
dst = emalloc(sizeof(HashTable));
|
||||
zend_hash_init(dst, zend_hash_num_elements(src), NULL, ZVAL_PTR_DTOR, 0);
|
||||
if (strict) {
|
||||
ZEND_HASH_FOREACH_VAL(src, val) {
|
||||
if (Z_TYPE_P(val) == IS_STRING) {
|
||||
zend_hash_add(dst, Z_STR_P(val), &tmp);
|
||||
} else if (Z_TYPE_P(val) == IS_LONG) {
|
||||
zend_hash_index_add(dst, Z_LVAL_P(val), &tmp);
|
||||
} else {
|
||||
zend_array_destroy(dst);
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
} else {
|
||||
ZEND_HASH_FOREACH_VAL(src, val) {
|
||||
if (Z_TYPE_P(val) != IS_STRING || ZEND_HANDLE_NUMERIC(Z_STR_P(val), idx)) {
|
||||
zend_array_destroy(dst);
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
zend_hash_add(dst, Z_STR_P(val), &tmp);
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
uint32_t op_num = send_needly - op_array->opcodes;
|
||||
zend_ssa_op *ssa_op = ssa->ops + op_num;
|
||||
|
||||
if (ssa_op->op1_use >= 0) {
|
||||
/* Reconstruct SSA */
|
||||
int var_num = ssa_op->op1_use;
|
||||
zend_ssa_var *var = ssa->vars + var_num;
|
||||
|
||||
ZEND_ASSERT(ssa_op->op1_def < 0);
|
||||
zend_ssa_unlink_use_chain(ssa, op_num, ssa_op->op1_use);
|
||||
ssa_op->op1_use = -1;
|
||||
ssa_op->op1_use_chain = -1;
|
||||
op_num = call_info->caller_call_opline - op_array->opcodes;
|
||||
ssa_op = ssa->ops + op_num;
|
||||
ssa_op->op1_use = var_num;
|
||||
ssa_op->op1_use_chain = var->use_chain;
|
||||
var->use_chain = op_num;
|
||||
}
|
||||
|
||||
ZVAL_ARR(&tmp, dst);
|
||||
|
||||
/* Update opcode */
|
||||
call_info->caller_call_opline->opcode = ZEND_IN_ARRAY;
|
||||
call_info->caller_call_opline->extended_value = strict;
|
||||
call_info->caller_call_opline->op1_type = send_needly->op1_type;
|
||||
call_info->caller_call_opline->op1.num = send_needly->op1.num;
|
||||
call_info->caller_call_opline->op2_type = IS_CONST;
|
||||
call_info->caller_call_opline->op2.constant = zend_optimizer_add_literal(op_array, &tmp);
|
||||
if (call_info->caller_init_opline->extended_value == 3) {
|
||||
MAKE_NOP(call_info->caller_call_opline - 1);
|
||||
}
|
||||
MAKE_NOP(call_info->caller_init_opline);
|
||||
MAKE_NOP(send_needly);
|
||||
MAKE_NOP(send_array);
|
||||
removed_ops++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
call_info = call_info->next_callee;
|
||||
} while (call_info);
|
||||
}
|
||||
|
||||
return removed_ops;
|
||||
}
|
||||
|
||||
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map)
|
||||
{
|
||||
if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) {
|
||||
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa);
|
||||
|
@ -332,6 +463,38 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
|
|||
zend_op *opline;
|
||||
zval tmp;
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_8 & ctx->optimization_level) {
|
||||
if (sccp_optimize_op_array(ctx, op_array, ssa, call_map)) {
|
||||
remove_nops = 1;
|
||||
}
|
||||
#if ZEND_DEBUG_DFA
|
||||
ssa_verify_integrity(op_array, ssa, "after sccp");
|
||||
#endif
|
||||
if (ZEND_FUNC_INFO(op_array)) {
|
||||
if (zend_dfa_optimize_calls(op_array, ssa)) {
|
||||
remove_nops = 1;
|
||||
}
|
||||
}
|
||||
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_8) {
|
||||
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after sccp pass", ssa);
|
||||
}
|
||||
#if ZEND_DEBUG_DFA
|
||||
ssa_verify_integrity(op_array, ssa, "after calls");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_14 & ctx->optimization_level) {
|
||||
if (dce_optimize_op_array(op_array, ssa, 0)) {
|
||||
remove_nops = 1;
|
||||
}
|
||||
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_14) {
|
||||
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "after dce pass", ssa);
|
||||
}
|
||||
#if ZEND_DEBUG_DFA
|
||||
ssa_verify_integrity(op_array, ssa, "after dce");
|
||||
#endif
|
||||
}
|
||||
|
||||
for (v = op_array->last_var; v < ssa->vars_count; v++) {
|
||||
|
||||
op_1 = ssa->vars[v].definition;
|
||||
|
@ -470,24 +633,28 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
|
|||
|
||||
// op_1: ASSIGN #orig_var.CV [undef,scalar] -> #v.CV, CONST|TMPVAR => QM_ASSIGN v.CV, CONST|TMPVAR
|
||||
|
||||
if (zend_ssa_unlink_use_chain(ssa, op_1, orig_var)) {
|
||||
/* Reconstruct SSA */
|
||||
ssa->ops[op_1].result_def = v;
|
||||
ssa->ops[op_1].op1_def = -1;
|
||||
ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use;
|
||||
ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain;
|
||||
ssa->ops[op_1].op2_use = -1;
|
||||
ssa->ops[op_1].op2_use_chain = -1;
|
||||
|
||||
/* Update opcode */
|
||||
opline->result_type = opline->op1_type;
|
||||
opline->result.var = opline->op1.var;
|
||||
opline->op1_type = opline->op2_type;
|
||||
opline->op1.var = opline->op2.var;
|
||||
opline->op2_type = IS_UNUSED;
|
||||
opline->op2.var = 0;
|
||||
opline->opcode = ZEND_QM_ASSIGN;
|
||||
if (ssa->ops[op_1].op1_use != ssa->ops[op_1].op2_use) {
|
||||
zend_ssa_unlink_use_chain(ssa, op_1, orig_var);
|
||||
} else {
|
||||
ssa->ops[op_1].op2_use_chain = ssa->ops[op_1].op1_use_chain;
|
||||
}
|
||||
|
||||
/* Reconstruct SSA */
|
||||
ssa->ops[op_1].result_def = v;
|
||||
ssa->ops[op_1].op1_def = -1;
|
||||
ssa->ops[op_1].op1_use = ssa->ops[op_1].op2_use;
|
||||
ssa->ops[op_1].op1_use_chain = ssa->ops[op_1].op2_use_chain;
|
||||
ssa->ops[op_1].op2_use = -1;
|
||||
ssa->ops[op_1].op2_use_chain = -1;
|
||||
|
||||
/* Update opcode */
|
||||
opline->result_type = opline->op1_type;
|
||||
opline->result.var = opline->op1.var;
|
||||
opline->op1_type = opline->op2_type;
|
||||
opline->op1.var = opline->op2.var;
|
||||
opline->op2_type = IS_UNUSED;
|
||||
opline->op2.var = 0;
|
||||
opline->opcode = ZEND_QM_ASSIGN;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,8 +744,15 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
|
|||
}
|
||||
}
|
||||
|
||||
#if ZEND_DEBUG_DFA
|
||||
ssa_verify_integrity(op_array, ssa, "after dfa");
|
||||
#endif
|
||||
|
||||
if (remove_nops) {
|
||||
zend_ssa_remove_nops(op_array, ssa);
|
||||
#if ZEND_DEBUG_DFA
|
||||
ssa_verify_integrity(op_array, ssa, "after nop");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -598,7 +772,7 @@ void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx)
|
|||
return;
|
||||
}
|
||||
|
||||
zend_dfa_optimize_op_array(op_array, ctx, &ssa);
|
||||
zend_dfa_optimize_op_array(op_array, ctx, &ssa, NULL);
|
||||
|
||||
/* Destroy SSA */
|
||||
zend_arena_release(&ctx->arena, checkpoint);
|
||||
|
|
1445
ext/opcache/Optimizer/sccp.c
Normal file
1445
ext/opcache/Optimizer/sccp.c
Normal file
File diff suppressed because it is too large
Load diff
228
ext/opcache/Optimizer/scdf.c
Normal file
228
ext/opcache/Optimizer/scdf.c
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, Sparse Conditional Data Flow Propagation Framework |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "ZendAccelerator.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
#include "Optimizer/scdf.h"
|
||||
|
||||
/* This defines a generic framework for sparse conditional dataflow propagation. The algorithm is
|
||||
* based on "Sparse conditional constant propagation" by Wegman and Zadeck. We're using a
|
||||
* generalized implementation as described in chapter 8.3 of the SSA book.
|
||||
*
|
||||
* Every SSA variable is associated with an element on a finite-height lattice, those value can only
|
||||
* ever be lowered during the operation of the algorithm. If a value is lowered all instructions and
|
||||
* phis using that value need to be reconsidered (this is done by adding the variable to a
|
||||
* worklist). For phi functions the result is computed by applying the meet operation to the
|
||||
* operands. This continues until a fixed point is reached.
|
||||
*
|
||||
* The algorithm is control-flow sensitive: All blocks except the start block are initially assumed
|
||||
* to be unreachable. When considering a branch instruction, we determine the feasible successors
|
||||
* based on the current state of the variable lattice. If a new edge becomes feasible we either have
|
||||
* to mark the successor block executable and consider all instructions in it, or, if the target is
|
||||
* already executable, we only have to reconsider the phi functions (as we only consider phi
|
||||
* operands which are associated with a feasible edge).
|
||||
*
|
||||
* The generic framework requires the definition of three functions:
|
||||
* * visit_instr() should recompute the lattice values of all SSA variables defined by an
|
||||
* instruction.
|
||||
* * visit_phi() should recompute the lattice value of the SSA variable defined by the phi. While
|
||||
* doing this it should only consider operands for which scfg_is_edge_feasible() returns true.
|
||||
* * get_feasible_successors() should determine the feasible successors for a branch instruction.
|
||||
* Note that this callback only needs to handle conditional branches (with two successors).
|
||||
*/
|
||||
|
||||
#if 0
|
||||
#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINT(...)
|
||||
#endif
|
||||
|
||||
void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to) {
|
||||
uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
|
||||
|
||||
if (zend_bitset_in(scdf->feasible_edges, edge)) {
|
||||
/* We already handled this edge */
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_PRINT("Marking edge %d->%d feasible\n", from, to);
|
||||
zend_bitset_incl(scdf->feasible_edges, edge);
|
||||
|
||||
if (!zend_bitset_in(scdf->executable_blocks, to)) {
|
||||
if (!zend_bitset_in(scdf->block_worklist, to)) {
|
||||
DEBUG_PRINT("Adding block %d to worklist\n", to);
|
||||
}
|
||||
zend_bitset_incl(scdf->block_worklist, to);
|
||||
} else {
|
||||
/* Block is already executable, only a new edge became feasible.
|
||||
* Reevaluate phi nodes to account for changed source operands. */
|
||||
zend_ssa_block *ssa_block = &scdf->ssa->blocks[to];
|
||||
zend_ssa_phi *phi;
|
||||
for (phi = ssa_block->phis; phi; phi = phi->next) {
|
||||
zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
|
||||
scdf->handlers.visit_phi(scdf, phi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa) {
|
||||
uint32_t edges_count = 0;
|
||||
int b;
|
||||
|
||||
for (b = 0; b < ssa->cfg.blocks_count; b++) {
|
||||
edges_count += ssa->cfg.blocks[b].predecessors_count;
|
||||
}
|
||||
|
||||
scdf->op_array = op_array;
|
||||
scdf->ssa = ssa;
|
||||
|
||||
scdf->instr_worklist_len = zend_bitset_len(op_array->last);
|
||||
scdf->phi_var_worklist_len = zend_bitset_len(ssa->vars_count);
|
||||
scdf->block_worklist_len = zend_bitset_len(ssa->cfg.blocks_count);
|
||||
|
||||
scdf->instr_worklist = zend_arena_calloc(&ctx->arena,
|
||||
scdf->instr_worklist_len + scdf->phi_var_worklist_len + 2 * scdf->block_worklist_len + zend_bitset_len(edges_count),
|
||||
sizeof(zend_ulong));
|
||||
|
||||
scdf->phi_var_worklist = scdf->instr_worklist + scdf->instr_worklist_len;
|
||||
scdf->block_worklist = scdf->phi_var_worklist + scdf->phi_var_worklist_len;
|
||||
scdf->executable_blocks = scdf->block_worklist + scdf->block_worklist_len;
|
||||
scdf->feasible_edges = scdf->executable_blocks + scdf->block_worklist_len;
|
||||
|
||||
zend_bitset_incl(scdf->block_worklist, 0);
|
||||
zend_bitset_incl(scdf->executable_blocks, 0);
|
||||
}
|
||||
|
||||
void scdf_solve(scdf_ctx *scdf, const char *name) {
|
||||
zend_ssa *ssa = scdf->ssa;
|
||||
DEBUG_PRINT("Start SCDF solve (%s)\n", name);
|
||||
while (!zend_bitset_empty(scdf->instr_worklist, scdf->instr_worklist_len)
|
||||
|| !zend_bitset_empty(scdf->phi_var_worklist, scdf->phi_var_worklist_len)
|
||||
|| !zend_bitset_empty(scdf->block_worklist, scdf->block_worklist_len)
|
||||
) {
|
||||
int i;
|
||||
while ((i = zend_bitset_pop_first(scdf->phi_var_worklist, scdf->phi_var_worklist_len)) >= 0) {
|
||||
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
|
||||
ZEND_ASSERT(phi);
|
||||
if (zend_bitset_in(scdf->executable_blocks, phi->block)) {
|
||||
scdf->handlers.visit_phi(scdf, phi);
|
||||
}
|
||||
}
|
||||
|
||||
while ((i = zend_bitset_pop_first(scdf->instr_worklist, scdf->instr_worklist_len)) >= 0) {
|
||||
int block_num = ssa->cfg.map[i];
|
||||
if (zend_bitset_in(scdf->executable_blocks, block_num)) {
|
||||
zend_basic_block *block = &ssa->cfg.blocks[block_num];
|
||||
zend_op *opline = &scdf->op_array->opcodes[i];
|
||||
zend_ssa_op *ssa_op = &ssa->ops[i];
|
||||
if (opline->opcode == ZEND_OP_DATA) {
|
||||
opline--;
|
||||
ssa_op--;
|
||||
}
|
||||
scdf->handlers.visit_instr(scdf, opline, ssa_op);
|
||||
if (i == block->start + block->len - 1) {
|
||||
if (block->successors_count == 1) {
|
||||
scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
|
||||
} else if (block->successors_count > 1) {
|
||||
scdf->handlers.mark_feasible_successors(scdf, block_num, block, opline, ssa_op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((i = zend_bitset_pop_first(scdf->block_worklist, scdf->block_worklist_len)) >= 0) {
|
||||
/* This block is now live. Interpret phis and instructions in it. */
|
||||
zend_basic_block *block = &ssa->cfg.blocks[i];
|
||||
zend_ssa_block *ssa_block = &ssa->blocks[i];
|
||||
|
||||
DEBUG_PRINT("Pop block %d from worklist\n", i);
|
||||
zend_bitset_incl(scdf->executable_blocks, i);
|
||||
|
||||
{
|
||||
zend_ssa_phi *phi;
|
||||
for (phi = ssa_block->phis; phi; phi = phi->next) {
|
||||
zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
|
||||
scdf->handlers.visit_phi(scdf, phi);
|
||||
}
|
||||
}
|
||||
|
||||
if (block->len == 0) {
|
||||
/* Zero length blocks don't have a last instruction that would normally do this */
|
||||
scdf_mark_edge_feasible(scdf, i, block->successors[0]);
|
||||
} else {
|
||||
zend_op *opline;
|
||||
int j, end = block->start + block->len;
|
||||
for (j = block->start; j < end; j++) {
|
||||
opline = &scdf->op_array->opcodes[j];
|
||||
zend_bitset_excl(scdf->instr_worklist, j);
|
||||
if (opline->opcode != ZEND_OP_DATA) {
|
||||
scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]);
|
||||
}
|
||||
}
|
||||
if (block->successors_count == 1) {
|
||||
scdf_mark_edge_feasible(scdf, i, block->successors[0]);
|
||||
} else if (block->successors_count > 1) {
|
||||
if (opline->opcode == ZEND_OP_DATA) {
|
||||
opline--;
|
||||
j--;
|
||||
}
|
||||
scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If a live range starts in a reachable block and ends in an unreachable block, we should
|
||||
* not eliminate the latter. While it cannot be reached, the FREE opcode of the loop var
|
||||
* is necessary for the correctness of temporary compaction. */
|
||||
static zend_bool kept_alive_by_live_range(scdf_ctx *scdf, uint32_t block) {
|
||||
uint32_t i;
|
||||
const zend_op_array *op_array = scdf->op_array;
|
||||
const zend_cfg *cfg = &scdf->ssa->cfg;
|
||||
for (i = 0; i < op_array->last_live_range; i++) {
|
||||
zend_live_range *live_range = &op_array->live_range[i];
|
||||
uint32_t start_block = cfg->map[live_range->start];
|
||||
uint32_t end_block = cfg->map[live_range->end];
|
||||
|
||||
if (end_block == block && start_block != block
|
||||
&& zend_bitset_in(scdf->executable_blocks, start_block)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Removes unreachable blocks. This will remove both the instructions (and phis) in the
|
||||
* blocks, as well as remove them from the successor / predecessor lists and mark them
|
||||
* unreachable. Blocks already marked unreachable are not removed. */
|
||||
int scdf_remove_unreachable_blocks(scdf_ctx *scdf) {
|
||||
zend_ssa *ssa = scdf->ssa;
|
||||
int i;
|
||||
int removed_ops = 0;
|
||||
|
||||
for (i = 0; i < ssa->cfg.blocks_count; i++) {
|
||||
if (!zend_bitset_in(scdf->executable_blocks, i)
|
||||
&& (ssa->cfg.blocks[i].flags & ZEND_BB_REACHABLE)
|
||||
&& !kept_alive_by_live_range(scdf, i)) {
|
||||
removed_ops += ssa->cfg.blocks[i].len;
|
||||
zend_ssa_remove_block(scdf->op_array, ssa, i);
|
||||
}
|
||||
}
|
||||
return removed_ops;
|
||||
}
|
99
ext/opcache/Optimizer/scdf.h
Normal file
99
ext/opcache/Optimizer/scdf.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, Call Graph |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef _SCDF_H
|
||||
#define _SCDF_H
|
||||
|
||||
#include "zend_bitset.h"
|
||||
|
||||
typedef struct _scdf_ctx {
|
||||
zend_op_array *op_array;
|
||||
zend_ssa *ssa;
|
||||
zend_bitset instr_worklist;
|
||||
/* Represent phi-instructions through the defining var */
|
||||
zend_bitset phi_var_worklist;
|
||||
zend_bitset block_worklist;
|
||||
zend_bitset executable_blocks;
|
||||
/* 1 bit per edge, see scdf_edge(cfg, from, to) */
|
||||
zend_bitset feasible_edges;
|
||||
uint32_t instr_worklist_len;
|
||||
uint32_t phi_var_worklist_len;
|
||||
uint32_t block_worklist_len;
|
||||
|
||||
struct {
|
||||
void (*visit_instr)(
|
||||
struct _scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op);
|
||||
void (*visit_phi)(
|
||||
struct _scdf_ctx *scdf, zend_ssa_phi *phi);
|
||||
void (*mark_feasible_successors)(
|
||||
struct _scdf_ctx *scdf, int block_num, zend_basic_block *block,
|
||||
zend_op *opline, zend_ssa_op *ssa_op);
|
||||
} handlers;
|
||||
} scdf_ctx;
|
||||
|
||||
void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa);
|
||||
void scdf_solve(scdf_ctx *scdf, const char *name);
|
||||
|
||||
int scdf_remove_unreachable_blocks(scdf_ctx *scdf);
|
||||
|
||||
/* Add uses to worklist */
|
||||
static inline void scdf_add_to_worklist(scdf_ctx *scdf, int var_num) {
|
||||
zend_ssa *ssa = scdf->ssa;
|
||||
zend_ssa_var *var = &ssa->vars[var_num];
|
||||
int use;
|
||||
zend_ssa_phi *phi;
|
||||
FOREACH_USE(var, use) {
|
||||
zend_bitset_incl(scdf->instr_worklist, use);
|
||||
} FOREACH_USE_END();
|
||||
FOREACH_PHI_USE(var, phi) {
|
||||
zend_bitset_incl(scdf->phi_var_worklist, phi->ssa_var);
|
||||
} FOREACH_PHI_USE_END();
|
||||
}
|
||||
|
||||
/* This should usually not be necessary, however it's used for type narrowing. */
|
||||
static inline void scdf_add_def_to_worklist(scdf_ctx *scdf, int var_num) {
|
||||
zend_ssa_var *var = &scdf->ssa->vars[var_num];
|
||||
if (var->definition >= 0) {
|
||||
zend_bitset_incl(scdf->instr_worklist, var->definition);
|
||||
} else if (var->definition_phi) {
|
||||
zend_bitset_incl(scdf->phi_var_worklist, var_num);
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t scdf_edge(zend_cfg *cfg, int from, int to) {
|
||||
zend_basic_block *to_block = cfg->blocks + to;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < to_block->predecessors_count; i++) {
|
||||
uint32_t edge = to_block->predecessor_offset + i;
|
||||
|
||||
if (cfg->predecessors[edge] == from) {
|
||||
return edge;
|
||||
}
|
||||
}
|
||||
ZEND_ASSERT(0);
|
||||
}
|
||||
|
||||
static inline zend_bool scdf_is_edge_feasible(scdf_ctx *scdf, int from, int to) {
|
||||
uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
|
||||
return zend_bitset_in(scdf->feasible_edges, edge);
|
||||
}
|
||||
|
||||
void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to);
|
||||
|
||||
#endif
|
369
ext/opcache/Optimizer/ssa_integrity.c
Normal file
369
ext/opcache/Optimizer/ssa_integrity.c
Normal file
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| Zend Engine, SSA validation |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 1998-2017 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "ZendAccelerator.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
|
||||
/* The ssa_verify_integrity() function ensures that that certain invariants of the SSA form and
|
||||
* CFG are upheld and prints messages to stderr if this is not the case. */
|
||||
|
||||
static inline zend_bool is_in_use_chain(zend_ssa *ssa, int var, int check) {
|
||||
int use;
|
||||
FOREACH_USE(&ssa->vars[var], use) {
|
||||
if (use == check) {
|
||||
return 1;
|
||||
}
|
||||
} FOREACH_USE_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool is_in_phi_use_chain(zend_ssa *ssa, int var, zend_ssa_phi *check) {
|
||||
zend_ssa_phi *phi;
|
||||
FOREACH_PHI_USE(&ssa->vars[var], phi) {
|
||||
if (phi == check) {
|
||||
return 1;
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool is_used_by_op(zend_ssa *ssa, int op, int check) {
|
||||
zend_ssa_op *ssa_op = &ssa->ops[op];
|
||||
return (ssa_op->op1_use == check)
|
||||
|| (ssa_op->op2_use == check)
|
||||
|| (ssa_op->result_use == check);
|
||||
}
|
||||
|
||||
static inline zend_bool is_defined_by_op(zend_ssa *ssa, int op, int check) {
|
||||
zend_ssa_op *ssa_op = &ssa->ops[op];
|
||||
return (ssa_op->op1_def == check)
|
||||
|| (ssa_op->op2_def == check)
|
||||
|| (ssa_op->result_def == check);
|
||||
}
|
||||
|
||||
static inline zend_bool is_in_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi, int check) {
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
if (source == check) {
|
||||
return 1;
|
||||
}
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool is_in_predecessors(zend_cfg *cfg, zend_basic_block *block, int check) {
|
||||
int i, *predecessors = &cfg->predecessors[block->predecessor_offset];
|
||||
for (i = 0; i < block->predecessors_count; i++) {
|
||||
if (predecessors[i] == check) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool is_in_successors(zend_basic_block *block, int check) {
|
||||
int s;
|
||||
for (s = 0; s < block->successors_count; s++) {
|
||||
if (block->successors[s] == check) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline zend_bool is_var_type(zend_uchar type) {
|
||||
return type == IS_CV || type == IS_VAR || type == IS_TMP_VAR;
|
||||
}
|
||||
|
||||
#define FAIL(...) do { \
|
||||
if (status == SUCCESS) { \
|
||||
fprintf(stderr, "\nIn function %s::%s (%s):\n", \
|
||||
op_array->scope ? ZSTR_VAL(op_array->scope->name) : "", \
|
||||
op_array->function_name ? ZSTR_VAL(op_array->function_name) : "{main}", extra); \
|
||||
} \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
status = FAILURE; \
|
||||
} while (0)
|
||||
|
||||
#define VARFMT "%d (%s%s)"
|
||||
#define VAR(i) \
|
||||
(i), (ssa->vars[i].var < op_array->last_var ? "CV $" : "TMP"), \
|
||||
(ssa->vars[i].var < op_array->last_var ? ZSTR_VAL(op_array->vars[ssa->vars[i].var]) : "")
|
||||
|
||||
#define INSTRFMT "%d (%s)"
|
||||
#define INSTR(i) \
|
||||
(i), (zend_get_opcode_name(op_array->opcodes[i].opcode))
|
||||
|
||||
int ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *extra) {
|
||||
zend_cfg *cfg = &ssa->cfg;
|
||||
zend_ssa_phi *phi;
|
||||
int i, status = SUCCESS;
|
||||
|
||||
/* Vars */
|
||||
for (i = 0; i < ssa->vars_count; i++) {
|
||||
zend_ssa_var *var = &ssa->vars[i];
|
||||
int use, c;
|
||||
uint32_t type = ssa->var_info[i].type;
|
||||
|
||||
if (var->definition < 0 && !var->definition_phi && i > op_array->last_var) {
|
||||
if (var->use_chain >= 0) {
|
||||
FAIL("var " VARFMT " without def has op uses\n", VAR(i));
|
||||
}
|
||||
if (var->phi_use_chain) {
|
||||
FAIL("var " VARFMT " without def has phi uses\n", VAR(i));
|
||||
}
|
||||
}
|
||||
if (var->definition >= 0 && var->definition_phi) {
|
||||
FAIL("var " VARFMT " has both def and def_phi\n", VAR(i));
|
||||
}
|
||||
if (var->definition >= 0) {
|
||||
if (!is_defined_by_op(ssa, var->definition, i)) {
|
||||
FAIL("var " VARFMT " not defined by op " INSTRFMT "\n",
|
||||
VAR(i), INSTR(var->definition));
|
||||
}
|
||||
}
|
||||
if (var->definition_phi) {
|
||||
if (var->definition_phi->ssa_var != i) {
|
||||
FAIL("var " VARFMT " not defined by given phi\n", VAR(i));
|
||||
}
|
||||
}
|
||||
|
||||
c = 0;
|
||||
FOREACH_USE(var, use) {
|
||||
if (++c > 10000) {
|
||||
FAIL("cycle in uses of " VARFMT "\n", VAR(i));
|
||||
return status;
|
||||
}
|
||||
if (!is_used_by_op(ssa, use, i)) {
|
||||
fprintf(stderr, "var " VARFMT " not in uses of op %d\n", VAR(i), use);
|
||||
}
|
||||
} FOREACH_USE_END();
|
||||
|
||||
c = 0;
|
||||
FOREACH_PHI_USE(var, phi) {
|
||||
if (++c > 10000) {
|
||||
FAIL("cycle in phi uses of " VARFMT "\n", VAR(i));
|
||||
return status;
|
||||
}
|
||||
if (!is_in_phi_sources(ssa, phi, i)) {
|
||||
FAIL("var " VARFMT " not in phi sources of %d\n", VAR(i), phi->ssa_var);
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
|
||||
if ((type & MAY_BE_ARRAY_KEY_ANY) && !(type & MAY_BE_ARRAY_OF_ANY)) {
|
||||
FAIL("var " VARFMT " has array key type but not value type\n", VAR(i));
|
||||
}
|
||||
if ((type & MAY_BE_ARRAY_OF_ANY) && !(type & MAY_BE_ARRAY_KEY_ANY)) {
|
||||
FAIL("var " VARFMT " has array value type but not key type\n", VAR(i));
|
||||
}
|
||||
}
|
||||
|
||||
/* Instructions */
|
||||
FOREACH_INSTR_NUM(i) {
|
||||
zend_ssa_op *ssa_op = &ssa->ops[i];
|
||||
zend_op *opline = &op_array->opcodes[i];
|
||||
if (is_var_type(opline->op1_type)) {
|
||||
if (ssa_op->op1_use < 0 && ssa_op->op1_def < 0) {
|
||||
FAIL("var op1 of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
|
||||
}
|
||||
} else {
|
||||
if (ssa_op->op1_use >= 0 || ssa_op->op1_def >= 0) {
|
||||
FAIL("non-var op1 of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
|
||||
}
|
||||
}
|
||||
if (is_var_type(opline->op2_type)) {
|
||||
if (ssa_op->op2_use < 0 && ssa_op->op2_def < 0) {
|
||||
FAIL("var op2 of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
|
||||
}
|
||||
} else {
|
||||
if (ssa_op->op2_use >= 0 || ssa_op->op2_def >= 0) {
|
||||
FAIL("non-var op2 of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
|
||||
}
|
||||
}
|
||||
if (is_var_type(opline->result_type)) {
|
||||
if (ssa_op->result_use < 0 && ssa_op->result_def < 0) {
|
||||
FAIL("var result of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
|
||||
}
|
||||
} else {
|
||||
if (ssa_op->result_use >= 0 || ssa_op->result_def >= 0) {
|
||||
FAIL("non-var result of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (ssa_op->op1_use >= 0) {
|
||||
if (ssa_op->op1_use >= ssa->vars_count) {
|
||||
FAIL("op1 use %d out of range\n", ssa_op->op1_use);
|
||||
}
|
||||
if (!is_in_use_chain(ssa, ssa_op->op1_use, i)) {
|
||||
FAIL("op1 use of " VARFMT " in " INSTRFMT " not in use chain\n",
|
||||
VAR(ssa_op->op1_use), INSTR(i));
|
||||
}
|
||||
if (VAR_NUM(opline->op1.var) != ssa->vars[ssa_op->op1_use].var) {
|
||||
FAIL("op1 use of " VARFMT " does not match op %d of " INSTRFMT "\n",
|
||||
VAR(ssa_op->op1_use), VAR_NUM(opline->op1.var), INSTR(i));
|
||||
}
|
||||
}
|
||||
if (ssa_op->op2_use >= 0) {
|
||||
if (ssa_op->op2_use >= ssa->vars_count) {
|
||||
FAIL("op2 use %d out of range\n", ssa_op->op2_use);
|
||||
}
|
||||
if (!is_in_use_chain(ssa, ssa_op->op2_use, i)) {
|
||||
FAIL("op1 use of " VARFMT " in " INSTRFMT " not in use chain\n",
|
||||
VAR(ssa_op->op2_use), INSTR(i));
|
||||
}
|
||||
if (VAR_NUM(opline->op2.var) != ssa->vars[ssa_op->op2_use].var) {
|
||||
FAIL("op2 use of " VARFMT " does not match op %d of " INSTRFMT "\n",
|
||||
VAR(ssa_op->op2_use), VAR_NUM(opline->op2.var), INSTR(i));
|
||||
}
|
||||
}
|
||||
if (ssa_op->result_use >= 0) {
|
||||
if (ssa_op->result_use >= ssa->vars_count) {
|
||||
FAIL("result use %d out of range\n", ssa_op->result_use);
|
||||
}
|
||||
if (!is_in_use_chain(ssa, ssa_op->result_use, i)) {
|
||||
FAIL("result use of " VARFMT " in " INSTRFMT " not in use chain\n",
|
||||
VAR(ssa_op->result_use), INSTR(i));
|
||||
}
|
||||
if (VAR_NUM(opline->result.var) != ssa->vars[ssa_op->result_use].var) {
|
||||
FAIL("result use of " VARFMT " does not match op %d of " INSTRFMT "\n",
|
||||
VAR(ssa_op->result_use), VAR_NUM(opline->result.var), INSTR(i));
|
||||
}
|
||||
}
|
||||
if (ssa_op->op1_def >= 0) {
|
||||
if (ssa_op->op1_def >= ssa->vars_count) {
|
||||
FAIL("op1 def %d out of range\n", ssa_op->op1_def);
|
||||
}
|
||||
if (ssa->vars[ssa_op->op1_def].definition != i) {
|
||||
FAIL("op1 def of " VARFMT " in " INSTRFMT " invalid\n",
|
||||
VAR(ssa_op->op1_def), INSTR(i));
|
||||
}
|
||||
if (VAR_NUM(opline->op1.var) != ssa->vars[ssa_op->op1_def].var) {
|
||||
FAIL("op1 def of " VARFMT " does not match op %d of " INSTRFMT "\n",
|
||||
VAR(ssa_op->op1_def), VAR_NUM(opline->op1.var), INSTR(i));
|
||||
}
|
||||
}
|
||||
if (ssa_op->op2_def >= 0) {
|
||||
if (ssa_op->op2_def >= ssa->vars_count) {
|
||||
FAIL("op2 def %d out of range\n", ssa_op->op2_def);
|
||||
}
|
||||
if (ssa->vars[ssa_op->op2_def].definition != i) {
|
||||
FAIL("op2 def of " VARFMT " in " INSTRFMT " invalid\n",
|
||||
VAR(ssa_op->op2_def), INSTR(i));
|
||||
}
|
||||
if (VAR_NUM(opline->op2.var) != ssa->vars[ssa_op->op2_def].var) {
|
||||
FAIL("op2 def of " VARFMT " does not match op %d of " INSTRFMT "\n",
|
||||
VAR(ssa_op->op2_def), VAR_NUM(opline->op2.var), INSTR(i));
|
||||
}
|
||||
}
|
||||
if (ssa_op->result_def >= 0) {
|
||||
if (ssa_op->result_def >= ssa->vars_count) {
|
||||
FAIL("result def %d out of range\n", ssa_op->result_def);
|
||||
}
|
||||
if (ssa->vars[ssa_op->result_def].definition != i) {
|
||||
FAIL("result def of " VARFMT " in " INSTRFMT " invalid\n",
|
||||
VAR(ssa_op->result_def), INSTR(i));
|
||||
}
|
||||
if (VAR_NUM(opline->result.var) != ssa->vars[ssa_op->result_def].var) {
|
||||
FAIL("result def of " VARFMT " does not match op %d of " INSTRFMT "\n",
|
||||
VAR(ssa_op->result_def), VAR_NUM(opline->result.var), INSTR(i));
|
||||
}
|
||||
}
|
||||
} FOREACH_INSTR_NUM_END();
|
||||
|
||||
/* Phis */
|
||||
FOREACH_PHI(phi) {
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
if (source < 0) {
|
||||
FAIL(VARFMT " negative source\n", VAR(phi->ssa_var));
|
||||
}
|
||||
if (!is_in_phi_use_chain(ssa, source, phi)) {
|
||||
FAIL(VARFMT " not in phi use chain of %d\n", VAR(phi->ssa_var), source);
|
||||
}
|
||||
if (ssa->vars[source].var != ssa->vars[phi->ssa_var].var) {
|
||||
FAIL(VARFMT " source of phi for " VARFMT "\n", VAR(source), VAR(phi->ssa_var));
|
||||
}
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
if (ssa->vars[phi->ssa_var].definition_phi != phi) {
|
||||
FAIL(VARFMT " does not define this phi\n", VAR(phi->ssa_var));
|
||||
}
|
||||
} FOREACH_PHI_END();
|
||||
|
||||
/* Blocks */
|
||||
for (i = 0; i < cfg->blocks_count; i++) {
|
||||
zend_basic_block *block = &cfg->blocks[i];
|
||||
int *predecessors = &cfg->predecessors[block->predecessor_offset];
|
||||
int s, j;
|
||||
|
||||
if (i != 0 && block->start < (block-1)->start + (block-1)->len) {
|
||||
FAIL("Block %d start %d smaller previous end %d\n",
|
||||
i, block->start, (block-1)->start + (block-1)->len);
|
||||
}
|
||||
if (i != cfg->blocks_count-1 && block->start + block->len > (block+1)->start) {
|
||||
FAIL("Block %d end %d greater next start %d\n",
|
||||
i, block->start + block->len, (block+1)->start);
|
||||
}
|
||||
|
||||
for (j = block->start; j < block->start + block->len; j++) {
|
||||
if (cfg->map[j] != i) {
|
||||
FAIL("Instr " INSTRFMT " not associated with block %d\n", INSTR(j), i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(block->flags & ZEND_BB_REACHABLE)) {
|
||||
if (ssa->blocks[i].phis) {
|
||||
FAIL("Unreachable block %d has phis\n", i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (s = 0; s < block->successors_count; s++) {
|
||||
zend_basic_block *next_block;
|
||||
if (block->successors[s] < 0) {
|
||||
FAIL("Successor number %d of %d negative", s, i);
|
||||
}
|
||||
next_block = &cfg->blocks[block->successors[s]];
|
||||
if (!(next_block->flags & ZEND_BB_REACHABLE)) {
|
||||
FAIL("Successor %d of %d not reachable\n", block->successors[s], i);
|
||||
}
|
||||
if (!is_in_predecessors(cfg, next_block, i)) {
|
||||
FAIL("Block %d predecessors missing %d\n", block->successors[s], i);
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < block->predecessors_count; j++) {
|
||||
if (predecessors[j] >= 0) {
|
||||
int k;
|
||||
zend_basic_block *prev_block = &cfg->blocks[predecessors[j]];
|
||||
if (!(prev_block->flags & ZEND_BB_REACHABLE)) {
|
||||
FAIL("Predecessor %d of %d not reachable\n", predecessors[j], i);
|
||||
}
|
||||
if (!is_in_successors(prev_block, i)) {
|
||||
FAIL("Block %d successors missing %d\n", predecessors[j], i);
|
||||
}
|
||||
for (k = 0; k < block->predecessors_count; k++) {
|
||||
if (k != j && predecessors[k] == predecessors[j]) {
|
||||
FAIL("Block %d has duplicate predecessor %d\n", i, predecessors[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
|
@ -441,6 +441,9 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
|
|||
flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
|
||||
}
|
||||
break;
|
||||
case ZEND_FUNC_GET_ARGS:
|
||||
flags |= ZEND_FUNC_VARARG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -598,7 +601,8 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
|
|||
/* Build CFG, Step 4, Mark Reachable Basic Blocks */
|
||||
zend_mark_reachable_blocks(op_array, cfg, 0);
|
||||
|
||||
cfg->dynamic = (flags & ZEND_FUNC_INDIRECT_VAR_ACCESS);
|
||||
cfg->dynamic = (flags & ZEND_FUNC_INDIRECT_VAR_ACCESS) != 0;
|
||||
cfg->vararg = (flags & ZEND_FUNC_VARARG) != 0;
|
||||
|
||||
if (func_flags) {
|
||||
*func_flags |= flags;
|
||||
|
@ -646,9 +650,13 @@ int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */
|
|||
for (j = 0; j < cfg->blocks_count; j++) {
|
||||
if (blocks[j].flags & ZEND_BB_REACHABLE) {
|
||||
for (s = 0; s < blocks[j].successors_count; s++) {
|
||||
zend_basic_block *b = blocks + blocks[j].successors[s];
|
||||
predecessors[b->predecessor_offset + b->predecessors_count] = j;
|
||||
b->predecessors_count++;
|
||||
/* SWITCH_STRING/LONG may have few following identical successors */
|
||||
if (s == 0 || blocks[j].successors[s-1] != blocks[j].successors[s]) {
|
||||
zend_basic_block *b = blocks + blocks[j].successors[s];
|
||||
|
||||
predecessors[b->predecessor_offset + b->predecessors_count] = j;
|
||||
b->predecessors_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ typedef struct _zend_cfg {
|
|||
unsigned int split_at_calls : 1;
|
||||
unsigned int split_at_recv : 1;
|
||||
unsigned int dynamic : 1; /* accesses varables by name */
|
||||
unsigned int vararg : 1; /* uses func_get_args() */
|
||||
} zend_cfg;
|
||||
|
||||
/* Build Flags */
|
||||
|
|
|
@ -282,7 +282,8 @@ int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ss
|
|||
}
|
||||
} else {
|
||||
for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
|
||||
if (p->sources[j] >= 0 && ssa->vars[p->sources[j]].no_val) {
|
||||
ZEND_ASSERT(p->sources[j] >= 0);
|
||||
if (ssa->vars[p->sources[j]].no_val) {
|
||||
ssa_vars[p->sources[j]].no_val = 0; /* used indirectly */
|
||||
zend_bitset_incl(worklist, p->sources[j]);
|
||||
}
|
||||
|
@ -854,7 +855,8 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
|
|||
}
|
||||
} else {
|
||||
for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
|
||||
if (p->sources[i] >= 0 && ssa->var_info[p->sources[i]].has_range) {
|
||||
ZEND_ASSERT(p->sources[i] >= 0);
|
||||
if (ssa->var_info[p->sources[i]].has_range) {
|
||||
/* union */
|
||||
tmp->underflow |= ssa->var_info[p->sources[i]].range.underflow;
|
||||
tmp->min = MIN(tmp->min, ssa->var_info[p->sources[i]].range.min);
|
||||
|
@ -3328,17 +3330,18 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
|
|||
}
|
||||
UPDATE_SSA_TYPE(tmp, j);
|
||||
for (i = 0; i < blocks[p->block].predecessors_count; i++) {
|
||||
if (p->sources[i] >= 0) {
|
||||
zend_ssa_var_info *info = &ssa_var_info[p->sources[i]];
|
||||
if (info->type & MAY_BE_OBJECT) {
|
||||
if (first) {
|
||||
ce = info->ce;
|
||||
is_instanceof = info->is_instanceof;
|
||||
first = 0;
|
||||
} else {
|
||||
is_instanceof |= info->is_instanceof;
|
||||
ce = join_class_entries(ce, info->ce, &is_instanceof);
|
||||
}
|
||||
zend_ssa_var_info *info;
|
||||
|
||||
ZEND_ASSERT(p->sources[i] >= 0);
|
||||
info = &ssa_var_info[p->sources[i]];
|
||||
if (info->type & MAY_BE_OBJECT) {
|
||||
if (first) {
|
||||
ce = info->ce;
|
||||
is_instanceof = info->is_instanceof;
|
||||
first = 0;
|
||||
} else {
|
||||
is_instanceof |= info->is_instanceof;
|
||||
ce = join_class_entries(ce, info->ce, &is_instanceof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3924,6 +3927,302 @@ void zend_inference_check_recursive_dependencies(zend_op_array *op_array)
|
|||
free_alloca(worklist, use_heap);
|
||||
}
|
||||
|
||||
int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa)
|
||||
{
|
||||
uint32_t t1 = OP1_INFO();
|
||||
uint32_t t2 = OP2_INFO();
|
||||
|
||||
if (opline->op1_type == IS_CV) {
|
||||
if (t1 & MAY_BE_UNDEF) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_UNSET_VAR:
|
||||
case ZEND_ISSET_ISEMPTY_VAR:
|
||||
if (opline->extended_value & ZEND_QUICK_SET) {
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
|
||||
case ZEND_ISSET_ISEMPTY_PROP_OBJ:
|
||||
case ZEND_ASSIGN:
|
||||
case ZEND_ASSIGN_DIM:
|
||||
case ZEND_ASSIGN_REF:
|
||||
case ZEND_BIND_GLOBAL:
|
||||
case ZEND_FETCH_DIM_IS:
|
||||
case ZEND_FETCH_OBJ_IS:
|
||||
case ZEND_SEND_REF:
|
||||
break;
|
||||
default:
|
||||
/* undefined variable warning */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
|
||||
if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_CASE:
|
||||
case ZEND_FE_FETCH_R:
|
||||
case ZEND_FE_FETCH_RW:
|
||||
case ZEND_FETCH_LIST:
|
||||
case ZEND_QM_ASSIGN:
|
||||
case ZEND_SEND_VAL:
|
||||
case ZEND_SEND_VAL_EX:
|
||||
case ZEND_SEND_VAR:
|
||||
case ZEND_SEND_VAR_EX:
|
||||
case ZEND_SEND_VAR_NO_REF:
|
||||
case ZEND_SEND_VAR_NO_REF_EX:
|
||||
case ZEND_SEND_REF:
|
||||
case ZEND_SEPARATE:
|
||||
case ZEND_END_SILENCE:
|
||||
break;
|
||||
default:
|
||||
/* destructor may be called */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opline->op2_type == IS_CV) {
|
||||
if (t2 & MAY_BE_UNDEF) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_ASSIGN_REF:
|
||||
break;
|
||||
default:
|
||||
/* undefined variable warning */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
|
||||
if (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
|
||||
switch (opline->opcode) {
|
||||
case ZEND_ASSIGN:
|
||||
break;
|
||||
default:
|
||||
/* destructor may be called */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (opline->opcode) {
|
||||
case ZEND_NOP:
|
||||
case ZEND_IS_IDENTICAL:
|
||||
case ZEND_IS_NOT_IDENTICAL:
|
||||
case ZEND_QM_ASSIGN:
|
||||
case ZEND_JMP:
|
||||
case ZEND_CHECK_VAR:
|
||||
case ZEND_MAKE_REF:
|
||||
case ZEND_SEND_VAR:
|
||||
case ZEND_BEGIN_SILENCE:
|
||||
case ZEND_END_SILENCE:
|
||||
case ZEND_SEND_VAL:
|
||||
case ZEND_SEND_REF:
|
||||
case ZEND_SEND_VAR_EX:
|
||||
case ZEND_FREE:
|
||||
case ZEND_SEPARATE:
|
||||
case ZEND_TYPE_CHECK:
|
||||
case ZEND_DEFINED:
|
||||
case ZEND_ISSET_ISEMPTY_THIS:
|
||||
case ZEND_COALESCE:
|
||||
case ZEND_SWITCH_LONG:
|
||||
case ZEND_SWITCH_STRING:
|
||||
case ZEND_ISSET_ISEMPTY_VAR:
|
||||
return 0;
|
||||
case ZEND_INIT_FCALL:
|
||||
/* can't throw, because call is resolved at compile time */
|
||||
return 0;
|
||||
case ZEND_BIND_GLOBAL:
|
||||
if ((opline+1)->opcode == ZEND_BIND_GLOBAL) {
|
||||
return zend_may_throw(opline + 1, op_array, ssa);
|
||||
}
|
||||
return 0;
|
||||
case ZEND_ADD:
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_DIV:
|
||||
case ZEND_MOD:
|
||||
if (!OP2_HAS_RANGE() ||
|
||||
(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
|
||||
/* Division by zero */
|
||||
return 1;
|
||||
}
|
||||
/* break missing intentionally */
|
||||
case ZEND_SUB:
|
||||
case ZEND_MUL:
|
||||
case ZEND_POW:
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_SL:
|
||||
case ZEND_SR:
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
!OP2_HAS_RANGE() ||
|
||||
OP2_MIN_RANGE() < 0;
|
||||
case ZEND_CONCAT:
|
||||
case ZEND_FAST_CONCAT:
|
||||
return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_BW_OR:
|
||||
case ZEND_BW_AND:
|
||||
case ZEND_BW_XOR:
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_BW_NOT:
|
||||
return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
||||
case ZEND_BOOL_NOT:
|
||||
case ZEND_PRE_INC:
|
||||
case ZEND_POST_INC:
|
||||
case ZEND_PRE_DEC:
|
||||
case ZEND_POST_DEC:
|
||||
case ZEND_JMPZ:
|
||||
case ZEND_JMPNZ:
|
||||
case ZEND_JMPZNZ:
|
||||
case ZEND_JMPZ_EX:
|
||||
case ZEND_JMPNZ_EX:
|
||||
case ZEND_BOOL:
|
||||
case ZEND_JMP_SET:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case ZEND_BOOL_XOR:
|
||||
return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT);
|
||||
case ZEND_IS_EQUAL:
|
||||
case ZEND_IS_NOT_EQUAL:
|
||||
case ZEND_IS_SMALLER:
|
||||
case ZEND_IS_SMALLER_OR_EQUAL:
|
||||
case ZEND_CASE:
|
||||
case ZEND_SPACESHIP:
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_NULL
|
||||
|| (t2 & MAY_BE_ANY) == MAY_BE_NULL) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT)) || (t2 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT));
|
||||
case ZEND_ASSIGN_ADD:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN_DIV:
|
||||
case ZEND_ASSIGN_MOD:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
if (!OP2_HAS_RANGE() ||
|
||||
(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
|
||||
/* Division by zero */
|
||||
return 1;
|
||||
}
|
||||
/* break missing intentionally */
|
||||
case ZEND_ASSIGN_SUB:
|
||||
case ZEND_ASSIGN_MUL:
|
||||
case ZEND_ASSIGN_POW:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN_SL:
|
||||
case ZEND_ASSIGN_SR:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
!OP2_HAS_RANGE() ||
|
||||
OP2_MIN_RANGE() < 0;
|
||||
case ZEND_ASSIGN_CONCAT:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN_BW_OR:
|
||||
case ZEND_ASSIGN_BW_AND:
|
||||
case ZEND_ASSIGN_BW_XOR:
|
||||
if (opline->extended_value != 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
|
||||
&& (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
|
||||
return 0;
|
||||
}
|
||||
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
|
||||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ASSIGN:
|
||||
case ZEND_UNSET_VAR:
|
||||
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
|
||||
case ZEND_ASSIGN_DIM:
|
||||
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE)) || opline->op2_type == IS_UNUSED ||
|
||||
(t2 & (MAY_BE_UNDEF|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
||||
case ZEND_ROPE_INIT:
|
||||
case ZEND_ROPE_ADD:
|
||||
case ZEND_ROPE_END:
|
||||
return t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT);
|
||||
case ZEND_INIT_ARRAY:
|
||||
case ZEND_ADD_ARRAY_ELEMENT:
|
||||
return (opline->op2_type != IS_UNUSED) && (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
||||
case ZEND_STRLEN:
|
||||
return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
|
||||
case ZEND_COUNT:
|
||||
return (t1 & MAY_BE_ANY) != MAY_BE_ARRAY;
|
||||
case ZEND_RECV_INIT:
|
||||
if (Z_CONSTANT_P(CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants))) {
|
||||
return 1;
|
||||
}
|
||||
if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
|
||||
uint32_t arg_num = opline->op1.num;
|
||||
zend_arg_info *cur_arg_info;
|
||||
|
||||
if (EXPECTED(arg_num <= op_array->num_args)) {
|
||||
cur_arg_info = &op_array->arg_info[arg_num-1];
|
||||
} else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
|
||||
cur_arg_info = &op_array->arg_info[op_array->num_args];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return ZEND_TYPE_IS_SET(cur_arg_info->type);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
case ZEND_FETCH_IS:
|
||||
return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
|
||||
return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case ZEND_FETCH_DIM_IS:
|
||||
return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
|
||||
case ZEND_CAST:
|
||||
switch (opline->extended_value) {
|
||||
case IS_NULL:
|
||||
return 0;
|
||||
case _IS_BOOL:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case IS_LONG:
|
||||
case IS_DOUBLE:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case IS_STRING:
|
||||
return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
|
||||
case IS_ARRAY:
|
||||
return (t1 & MAY_BE_OBJECT);
|
||||
case IS_OBJECT:
|
||||
return (t1 & MAY_BE_ARRAY);
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
#define MAY_BE_RC1 (1<<27) /* may be non-reference with refcount == 1 */
|
||||
#define MAY_BE_RCN (1<<28) /* may be non-reference with refcount > 1 */
|
||||
|
||||
#define MAY_HAVE_DTOR \
|
||||
(MAY_BE_OBJECT|MAY_BE_RESOURCE \
|
||||
|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE)
|
||||
|
||||
#define DEFINE_SSA_OP_HAS_RANGE(opN) \
|
||||
static zend_always_inline zend_bool _ssa_##opN##_has_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
|
||||
|
@ -263,6 +266,8 @@ void zend_func_return_info(const zend_op_array *op_array,
|
|||
int widening,
|
||||
zend_ssa_var_info *ret);
|
||||
|
||||
int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa);
|
||||
|
||||
END_EXTERN_C()
|
||||
|
||||
#endif /* ZEND_INFERENCE_H */
|
||||
|
|
|
@ -53,6 +53,25 @@ void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, zval *name, zval*
|
|||
zend_hash_add(ctx->constants, Z_STR_P(name), &val);
|
||||
}
|
||||
|
||||
zend_uchar zend_compound_assign_to_binary_op(zend_uchar opcode)
|
||||
{
|
||||
switch (opcode) {
|
||||
case ZEND_ASSIGN_ADD: return ZEND_ADD;
|
||||
case ZEND_ASSIGN_SUB: return ZEND_SUB;
|
||||
case ZEND_ASSIGN_MUL: return ZEND_MUL;
|
||||
case ZEND_ASSIGN_DIV: return ZEND_DIV;
|
||||
case ZEND_ASSIGN_MOD: return ZEND_MOD;
|
||||
case ZEND_ASSIGN_SL: return ZEND_SL;
|
||||
case ZEND_ASSIGN_SR: return ZEND_SR;
|
||||
case ZEND_ASSIGN_CONCAT: return ZEND_CONCAT;
|
||||
case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR;
|
||||
case ZEND_ASSIGN_BW_AND: return ZEND_BW_AND;
|
||||
case ZEND_ASSIGN_BW_XOR: return ZEND_BW_XOR;
|
||||
case ZEND_ASSIGN_POW: return ZEND_POW;
|
||||
EMPTY_SWITCH_DEFAULT_CASE()
|
||||
}
|
||||
}
|
||||
|
||||
int zend_optimizer_eval_binary_op(zval *result, zend_uchar opcode, zval *op1, zval *op2) /* {{{ */
|
||||
{
|
||||
binary_op_type binary_op = get_binary_op(opcode);
|
||||
|
@ -237,11 +256,53 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array,
|
|||
zend_op *opline,
|
||||
zval *val)
|
||||
{
|
||||
zend_op *target_opline;
|
||||
|
||||
switch (opline->opcode) {
|
||||
case ZEND_JMPZ:
|
||||
if (zend_is_true(val)) {
|
||||
MAKE_NOP(opline);
|
||||
} else {
|
||||
opline->opcode = ZEND_JMP;
|
||||
COPY_NODE(opline->op1, opline->op2);
|
||||
opline->op2_type = IS_UNUSED;
|
||||
}
|
||||
zval_ptr_dtor_nogc(val);
|
||||
return 1;
|
||||
case ZEND_JMPNZ:
|
||||
if (zend_is_true(val)) {
|
||||
opline->opcode = ZEND_JMP;
|
||||
COPY_NODE(opline->op1, opline->op2);
|
||||
opline->op2_type = IS_UNUSED;
|
||||
} else {
|
||||
MAKE_NOP(opline);
|
||||
}
|
||||
zval_ptr_dtor_nogc(val);
|
||||
return 1;
|
||||
case ZEND_JMPZNZ:
|
||||
if (zend_is_true(val)) {
|
||||
target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
|
||||
} else {
|
||||
target_opline = ZEND_OP2_JMP_ADDR(opline);
|
||||
}
|
||||
ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
|
||||
opline->op1_type = IS_UNUSED;
|
||||
opline->extended_value = 0;
|
||||
opline->opcode = ZEND_JMP;
|
||||
zval_ptr_dtor_nogc(val);
|
||||
return 1;
|
||||
case ZEND_FREE:
|
||||
MAKE_NOP(opline);
|
||||
zval_ptr_dtor_nogc(val);
|
||||
return 1;
|
||||
case ZEND_SEND_VAR_EX:
|
||||
case ZEND_FETCH_DIM_W:
|
||||
case ZEND_FETCH_DIM_RW:
|
||||
case ZEND_FETCH_DIM_FUNC_ARG:
|
||||
case ZEND_FETCH_DIM_UNSET:
|
||||
case ZEND_ASSIGN_DIM:
|
||||
case ZEND_RETURN_BY_REF:
|
||||
return 0;
|
||||
case ZEND_INIT_STATIC_METHOD_CALL:
|
||||
case ZEND_CATCH:
|
||||
case ZEND_FETCH_CONSTANT:
|
||||
|
@ -305,6 +366,8 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
|
|||
zend_op *opline,
|
||||
zval *val)
|
||||
{
|
||||
zval tmp;
|
||||
|
||||
switch (opline->opcode) {
|
||||
case ZEND_ASSIGN_REF:
|
||||
case ZEND_FAST_CALL:
|
||||
|
@ -331,7 +394,13 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
|
|||
break;
|
||||
case ZEND_INIT_FCALL:
|
||||
REQUIRES_STRING(val);
|
||||
zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val));
|
||||
if (Z_REFCOUNT_P(val) == 1) {
|
||||
zend_str_tolower(Z_STRVAL_P(val), Z_STRLEN_P(val));
|
||||
} else {
|
||||
ZVAL_STR(&tmp, zend_string_tolower(Z_STR_P(val)));
|
||||
zval_ptr_dtor_nogc(val);
|
||||
val = &tmp;
|
||||
}
|
||||
opline->op2.constant = zend_optimizer_add_literal(op_array, val);
|
||||
alloc_cache_slots_op2(op_array, opline, 1);
|
||||
break;
|
||||
|
@ -482,6 +551,23 @@ void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var)
|
|||
}
|
||||
}
|
||||
|
||||
void zend_optimizer_remove_live_range_ex(zend_op_array *op_array, uint32_t var, uint32_t start)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
|
||||
while (i < op_array->last_live_range) {
|
||||
if (op_array->live_range[i].var == var
|
||||
&& op_array->live_range[i].start == start) {
|
||||
op_array->last_live_range--;
|
||||
if (i < op_array->last_live_range) {
|
||||
memmove(&op_array->live_range[i], &op_array->live_range[i+1], (op_array->last_live_range - i) * sizeof(zend_live_range));
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
int zend_optimizer_replace_by_const(zend_op_array *op_array,
|
||||
zend_op *opline,
|
||||
zend_uchar type,
|
||||
|
@ -973,13 +1059,24 @@ static void zend_optimize(zend_op_array *op_array,
|
|||
/* pass 11:
|
||||
* - Compact literals table
|
||||
*/
|
||||
if (ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) {
|
||||
if ((ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) &&
|
||||
(!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) ||
|
||||
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) {
|
||||
zend_optimizer_compact_literals(op_array, ctx);
|
||||
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_11) {
|
||||
zend_dump_op_array(op_array, 0, "after pass 11", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if ((ZEND_OPTIMIZER_PASS_13 & ctx->optimization_level) &&
|
||||
(!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) ||
|
||||
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) {
|
||||
zend_optimizer_compact_vars(op_array);
|
||||
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_13) {
|
||||
zend_dump_op_array(op_array, 0, "after pass 13", NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) {
|
||||
return;
|
||||
}
|
||||
|
@ -1176,7 +1273,7 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
|
|||
for (i = 0; i < call_graph.op_arrays_count; i++) {
|
||||
func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
|
||||
if (func_info) {
|
||||
zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa);
|
||||
zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, func_info->call_map);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1186,6 +1283,24 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (debug_level & ZEND_DUMP_AFTER_PASS_11) {
|
||||
zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 11", NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_13 & optimization_level) {
|
||||
for (i = 0; i < call_graph.op_arrays_count; i++) {
|
||||
zend_optimizer_compact_vars(call_graph.op_arrays[i]);
|
||||
if (debug_level & ZEND_DUMP_AFTER_PASS_13) {
|
||||
zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 13", NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
|
||||
for (i = 0; i < call_graph.op_arrays_count; i++) {
|
||||
zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]);
|
||||
|
|
|
@ -32,13 +32,13 @@
|
|||
#define ZEND_OPTIMIZER_PASS_5 (1<<4) /* CFG based optimization */
|
||||
#define ZEND_OPTIMIZER_PASS_6 (1<<5) /* DFA based optimization */
|
||||
#define ZEND_OPTIMIZER_PASS_7 (1<<6) /* CALL GRAPH optimization */
|
||||
#define ZEND_OPTIMIZER_PASS_8 (1<<7)
|
||||
#define ZEND_OPTIMIZER_PASS_8 (1<<7) /* SCCP (constant propagation) */
|
||||
#define ZEND_OPTIMIZER_PASS_9 (1<<8) /* TMP VAR usage */
|
||||
#define ZEND_OPTIMIZER_PASS_10 (1<<9) /* NOP removal */
|
||||
#define ZEND_OPTIMIZER_PASS_11 (1<<10) /* Merge equal constants */
|
||||
#define ZEND_OPTIMIZER_PASS_12 (1<<11) /* Adjust used stack */
|
||||
#define ZEND_OPTIMIZER_PASS_13 (1<<12)
|
||||
#define ZEND_OPTIMIZER_PASS_14 (1<<13)
|
||||
#define ZEND_OPTIMIZER_PASS_13 (1<<12) /* Remove unused variables */
|
||||
#define ZEND_OPTIMIZER_PASS_14 (1<<13) /* DCE (dead code elimination) */
|
||||
#define ZEND_OPTIMIZER_PASS_15 (1<<14) /* Collect constants */
|
||||
#define ZEND_OPTIMIZER_PASS_16 (1<<15) /* Inline functions */
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#define ZEND_OPTIMIZER_INTERNAL_H
|
||||
|
||||
#include "zend_ssa.h"
|
||||
#include "zend_func_info.h"
|
||||
|
||||
#define ZEND_OP1_LITERAL(opline) (op_array)->literals[(opline)->op1.constant]
|
||||
#define ZEND_OP1_JMP_ADDR(opline) OP_JMP_ADDR(opline, (opline)->op1)
|
||||
|
@ -91,6 +92,7 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
|
|||
zval *val);
|
||||
|
||||
void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var);
|
||||
void zend_optimizer_remove_live_range_ex(zend_op_array *op_array, uint32_t var, uint32_t start);
|
||||
void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
void zend_optimizer_pass2(zend_op_array *op_array);
|
||||
void zend_optimizer_pass3(zend_op_array *op_array);
|
||||
|
@ -98,15 +100,19 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
|||
void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags);
|
||||
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa);
|
||||
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map);
|
||||
void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
void zend_optimizer_nop_removal(zend_op_array *op_array);
|
||||
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx);
|
||||
void zend_optimizer_compact_vars(zend_op_array *op_array);
|
||||
int zend_optimizer_is_disabled_func(const char *name, size_t len);
|
||||
zend_function *zend_optimizer_get_called_func(
|
||||
zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants);
|
||||
uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args);
|
||||
void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline);
|
||||
void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist);
|
||||
zend_uchar zend_compound_assign_to_binary_op(zend_uchar opcode);
|
||||
int sccp_optimize_op_array(zend_optimizer_ctx *ctx, zend_op_array *op_arrya, zend_ssa *ssa, zend_call_info **call_map);
|
||||
int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
| Authors: Dmitry Stogov <dmitry@zend.com> |
|
||||
| Nikita Popov <nikic@php.net> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
|
@ -22,6 +23,7 @@
|
|||
#include "zend_ssa.h"
|
||||
#include "zend_dump.h"
|
||||
#include "zend_inference.h"
|
||||
#include "Optimizer/zend_optimizer_internal.h"
|
||||
|
||||
static zend_bool dominates(const zend_basic_block *blocks, int a, int b) {
|
||||
while (blocks[b].level > blocks[a].level) {
|
||||
|
@ -1059,15 +1061,16 @@ int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_
|
|||
ssa_vars[phi->ssa_var].var = phi->var;
|
||||
ssa_vars[phi->ssa_var].definition_phi = phi;
|
||||
if (phi->pi >= 0) {
|
||||
if (phi->sources[0] >= 0) {
|
||||
zend_ssa_phi *p = ssa_vars[phi->sources[0]].phi_use_chain;
|
||||
while (p && p != phi) {
|
||||
p = zend_ssa_next_use_phi(ssa, phi->sources[0], p);
|
||||
}
|
||||
if (!p) {
|
||||
phi->use_chains[0] = ssa_vars[phi->sources[0]].phi_use_chain;
|
||||
ssa_vars[phi->sources[0]].phi_use_chain = phi;
|
||||
}
|
||||
zend_ssa_phi *p;
|
||||
|
||||
ZEND_ASSERT(phi->sources[0] >= 0);
|
||||
p = ssa_vars[phi->sources[0]].phi_use_chain;
|
||||
while (p && p != phi) {
|
||||
p = zend_ssa_next_use_phi(ssa, phi->sources[0], p);
|
||||
}
|
||||
if (!p) {
|
||||
phi->use_chains[0] = ssa_vars[phi->sources[0]].phi_use_chain;
|
||||
ssa_vars[phi->sources[0]].phi_use_chain = phi;
|
||||
}
|
||||
if (phi->has_range_constraint) {
|
||||
/* min and max variables can't be used together */
|
||||
|
@ -1084,15 +1087,16 @@ int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_
|
|||
int j;
|
||||
|
||||
for (j = 0; j < ssa->cfg.blocks[i].predecessors_count; j++) {
|
||||
if (phi->sources[j] >= 0) {
|
||||
zend_ssa_phi *p = ssa_vars[phi->sources[j]].phi_use_chain;
|
||||
while (p && p != phi) {
|
||||
p = zend_ssa_next_use_phi(ssa, phi->sources[j], p);
|
||||
}
|
||||
if (!p) {
|
||||
phi->use_chains[j] = ssa_vars[phi->sources[j]].phi_use_chain;
|
||||
ssa_vars[phi->sources[j]].phi_use_chain = phi;
|
||||
}
|
||||
zend_ssa_phi *p;
|
||||
|
||||
ZEND_ASSERT(phi->sources[j] >= 0);
|
||||
p = ssa_vars[phi->sources[j]].phi_use_chain;
|
||||
while (p && p != phi) {
|
||||
p = zend_ssa_next_use_phi(ssa, phi->sources[j], p);
|
||||
}
|
||||
if (!p) {
|
||||
phi->use_chains[j] = ssa_vars[phi->sources[j]].phi_use_chain;
|
||||
ssa_vars[phi->sources[j]].phi_use_chain = phi;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1161,6 +1165,420 @@ int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) /* {{{ */
|
|||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_ssa_remove_instr(zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op) /* {{{ */
|
||||
{
|
||||
if (ssa_op->result_use >= 0) {
|
||||
zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->result_use);
|
||||
ssa_op->result_use = -1;
|
||||
ssa_op->res_use_chain = -1;
|
||||
}
|
||||
if (ssa_op->op1_use >= 0) {
|
||||
if (ssa_op->op1_use != ssa_op->op2_use) {
|
||||
zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->op1_use);
|
||||
} else {
|
||||
ssa_op->op2_use_chain = ssa_op->op1_use_chain;
|
||||
}
|
||||
ssa_op->op1_use = -1;
|
||||
ssa_op->op1_use_chain = -1;
|
||||
}
|
||||
if (ssa_op->op2_use >= 0) {
|
||||
zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->op2_use);
|
||||
ssa_op->op2_use = -1;
|
||||
ssa_op->op2_use_chain = -1;
|
||||
}
|
||||
|
||||
/* We let the caller make sure that all defs are gone */
|
||||
ZEND_ASSERT(ssa_op->result_def == -1);
|
||||
ZEND_ASSERT(ssa_op->op1_def == -1);
|
||||
ZEND_ASSERT(ssa_op->op2_def == -1);
|
||||
|
||||
MAKE_NOP(opline);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static inline zend_ssa_phi **zend_ssa_next_use_phi_ptr(zend_ssa *ssa, int var, zend_ssa_phi *p) /* {{{ */
|
||||
{
|
||||
if (p->pi >= 0) {
|
||||
return &p->use_chains[0];
|
||||
} else {
|
||||
int j;
|
||||
for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
|
||||
if (p->sources[j] == var) {
|
||||
return &p->use_chains[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
ZEND_ASSERT(0);
|
||||
return NULL;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* May be called even if source is not used in the phi (useful when removing uses in a phi
|
||||
* with multiple identical operands) */
|
||||
static inline void zend_ssa_remove_use_of_phi_source(zend_ssa *ssa, zend_ssa_phi *phi, int source) /* {{{ */
|
||||
{
|
||||
zend_ssa_phi **cur = &ssa->vars[source].phi_use_chain;
|
||||
while (*cur && *cur != phi) {
|
||||
cur = zend_ssa_next_use_phi_ptr(ssa, source, *cur);
|
||||
}
|
||||
if (*cur) {
|
||||
*cur = zend_ssa_next_use_phi(ssa, source, *cur);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void zend_ssa_remove_uses_of_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
|
||||
{
|
||||
int source;
|
||||
FOREACH_PHI_SOURCE(phi, source) {
|
||||
zend_ssa_remove_use_of_phi_source(ssa, phi, source);
|
||||
} FOREACH_PHI_SOURCE_END();
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void zend_ssa_remove_phi_from_block(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
|
||||
{
|
||||
zend_ssa_block *block = &ssa->blocks[phi->block];
|
||||
zend_ssa_phi **cur = &block->phis;
|
||||
while (*cur != phi) {
|
||||
ZEND_ASSERT(*cur != NULL);
|
||||
cur = &(*cur)->next;
|
||||
}
|
||||
*cur = (*cur)->next;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static inline void zend_ssa_remove_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op) /* {{{ */
|
||||
{
|
||||
if (ssa_op->op1_def >= 0) {
|
||||
zend_ssa_remove_uses_of_var(ssa, ssa_op->op1_def);
|
||||
zend_ssa_remove_op1_def(ssa, ssa_op);
|
||||
}
|
||||
if (ssa_op->op2_def >= 0) {
|
||||
zend_ssa_remove_uses_of_var(ssa, ssa_op->op2_def);
|
||||
zend_ssa_remove_op2_def(ssa, ssa_op);
|
||||
}
|
||||
if (ssa_op->result_def >= 0) {
|
||||
zend_ssa_remove_uses_of_var(ssa, ssa_op->result_def);
|
||||
zend_ssa_remove_result_def(ssa, ssa_op);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static inline void zend_ssa_remove_phi_source(zend_ssa *ssa, zend_ssa_phi *phi, int pred_offset, int predecessors_count) /* {{{ */
|
||||
{
|
||||
int j, var_num = phi->sources[pred_offset];
|
||||
|
||||
predecessors_count--;
|
||||
if (pred_offset < predecessors_count) {
|
||||
memmove(phi->sources + pred_offset, phi->sources + pred_offset + 1, (predecessors_count - pred_offset) * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
/* Check if they same var is used in a different phi operand as well, in this case we don't
|
||||
* need to adjust the use chain (but may have to move the next pointer). */
|
||||
for (j = 0; j < predecessors_count; j++) {
|
||||
if (phi->sources[j] == var_num) {
|
||||
if (j < pred_offset) {
|
||||
ZEND_ASSERT(phi->use_chains[pred_offset] == NULL);
|
||||
return;
|
||||
}
|
||||
if (j >= pred_offset) {
|
||||
phi->use_chains[j] = phi->use_chains[pred_offset];
|
||||
phi->use_chains[pred_offset] = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Variable only used in one operand, remove the phi from the use chain. */
|
||||
zend_ssa_remove_use_of_phi_source(ssa, phi, var_num);
|
||||
phi->use_chains[pred_offset] = NULL;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_ssa_remove_phi(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
|
||||
{
|
||||
ZEND_ASSERT(phi->ssa_var >= 0);
|
||||
ZEND_ASSERT(ssa->vars[phi->ssa_var].use_chain < 0
|
||||
&& ssa->vars[phi->ssa_var].phi_use_chain == NULL);
|
||||
zend_ssa_remove_uses_of_phi_sources(ssa, phi);
|
||||
zend_ssa_remove_phi_from_block(ssa, phi);
|
||||
ssa->vars[phi->ssa_var].definition_phi = NULL;
|
||||
phi->ssa_var = -1;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num) /* {{{ */
|
||||
{
|
||||
zend_ssa_var *var = &ssa->vars[var_num];
|
||||
zend_ssa_phi *phi;
|
||||
int use;
|
||||
FOREACH_PHI_USE(var, phi) {
|
||||
int i, end = NUM_PHI_SOURCES(phi);
|
||||
for (i = 0; i < end; i++) {
|
||||
if (phi->sources[i] == var_num) {
|
||||
phi->use_chains[i] = NULL;
|
||||
}
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
var->phi_use_chain = NULL;
|
||||
FOREACH_USE(var, use) {
|
||||
zend_ssa_op *ssa_op = &ssa->ops[use];
|
||||
if (ssa_op->op1_use == var_num) {
|
||||
ssa_op->op1_use = -1;
|
||||
ssa_op->op1_use_chain = -1;
|
||||
}
|
||||
if (ssa_op->op2_use == var_num) {
|
||||
ssa_op->op2_use = -1;
|
||||
ssa_op->op2_use_chain = -1;
|
||||
}
|
||||
if (ssa_op->result_use == var_num) {
|
||||
ssa_op->result_use = -1;
|
||||
ssa_op->res_use_chain = -1;
|
||||
}
|
||||
} FOREACH_USE_END();
|
||||
var->use_chain = -1;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int i) /* {{{ */
|
||||
{
|
||||
zend_basic_block *block = &ssa->cfg.blocks[i];
|
||||
zend_ssa_block *ssa_block = &ssa->blocks[i];
|
||||
int *predecessors;
|
||||
zend_ssa_phi *phi;
|
||||
int j, s;
|
||||
|
||||
block->flags &= ~ZEND_BB_REACHABLE;
|
||||
|
||||
/* Removes phis in this block */
|
||||
for (phi = ssa_block->phis; phi; phi = phi->next) {
|
||||
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}
|
||||
|
||||
/* Remove instructions in this block */
|
||||
for (j = block->start; j < block->start + block->len; j++) {
|
||||
if (op_array->opcodes[j].opcode == ZEND_NOP) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (op_array->opcodes[j].result_type & (IS_TMP_VAR|IS_VAR)) {
|
||||
zend_optimizer_remove_live_range_ex(op_array, op_array->opcodes[j].result.var, j + 1);
|
||||
}
|
||||
zend_ssa_remove_defs_of_instr(ssa, &ssa->ops[j]);
|
||||
zend_ssa_remove_instr(ssa, &op_array->opcodes[j], &ssa->ops[j]);
|
||||
}
|
||||
|
||||
for (s = 0; s < block->successors_count; s++) {
|
||||
zend_basic_block *next_block = &ssa->cfg.blocks[block->successors[s]];
|
||||
zend_ssa_block *next_ssa_block = &ssa->blocks[block->successors[s]];
|
||||
zend_ssa_phi *phi;
|
||||
|
||||
/* Find at which predecessor offset this block is referenced */
|
||||
int pred_offset = -1;
|
||||
predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset];
|
||||
for (j = 0; j < next_block->predecessors_count; j++) {
|
||||
if (predecessors[j] == i) {
|
||||
pred_offset = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ZEND_ASSERT(pred_offset != -1);
|
||||
|
||||
/* For phis in successor blocks, remove the operands associated with this block */
|
||||
for (phi = next_ssa_block->phis; phi; phi = phi->next) {
|
||||
if (phi->pi >= 0) {
|
||||
if (phi->pi == i) {
|
||||
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
|
||||
zend_ssa_remove_phi(ssa, phi);
|
||||
}
|
||||
} else {
|
||||
ZEND_ASSERT(phi->sources[pred_offset] >= 0);
|
||||
zend_ssa_remove_phi_source(ssa, phi, pred_offset, next_block->predecessors_count);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove this predecessor */
|
||||
next_block->predecessors_count--;
|
||||
if (pred_offset < next_block->predecessors_count) {
|
||||
predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset + pred_offset];
|
||||
memmove(predecessors, predecessors + 1, (next_block->predecessors_count - pred_offset) * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove successors of predecessors */
|
||||
predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
|
||||
for (j = 0; j < block->predecessors_count; j++) {
|
||||
if (predecessors[j] >= 0) {
|
||||
zend_basic_block *prev_block = &ssa->cfg.blocks[predecessors[j]];
|
||||
|
||||
for (s = 0; s < prev_block->successors_count; s++) {
|
||||
if (prev_block->successors[s] == i) {
|
||||
memmove(prev_block->successors + s,
|
||||
prev_block->successors + s + 1,
|
||||
sizeof(int) * (prev_block->successors_count - s - 1));
|
||||
prev_block->successors_count--;
|
||||
s--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block->successors_count = 0;
|
||||
block->predecessors_count = 0;
|
||||
|
||||
/* Remove from dominators tree */
|
||||
if (block->idom >= 0) {
|
||||
j = ssa->cfg.blocks[block->idom].children;
|
||||
if (j == i) {
|
||||
ssa->cfg.blocks[block->idom].children = block->next_child;
|
||||
} else if (j >= 0) {
|
||||
while (ssa->cfg.blocks[j].next_child >= 0) {
|
||||
if (ssa->cfg.blocks[j].next_child == i) {
|
||||
ssa->cfg.blocks[j].next_child = block->next_child;
|
||||
break;
|
||||
}
|
||||
j = ssa->cfg.blocks[j].next_child;
|
||||
}
|
||||
}
|
||||
}
|
||||
block->idom = -1;
|
||||
block->level = -1;
|
||||
block->children = -1;
|
||||
block->next_child = -1;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void propagate_phi_type_widening(zend_ssa *ssa, int var) /* {{{ */
|
||||
{
|
||||
zend_ssa_phi *phi;
|
||||
FOREACH_PHI_USE(&ssa->vars[var], phi) {
|
||||
if (ssa->var_info[var].type & ~ssa->var_info[phi->ssa_var].type) {
|
||||
ssa->var_info[phi->ssa_var].type |= ssa->var_info[var].type;
|
||||
propagate_phi_type_widening(ssa, phi->ssa_var);
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
void zend_ssa_rename_var_uses(zend_ssa *ssa, int old, int new, zend_bool update_types) /* {{{ */
|
||||
{
|
||||
zend_ssa_var *old_var = &ssa->vars[old];
|
||||
zend_ssa_var *new_var = &ssa->vars[new];
|
||||
int use;
|
||||
zend_ssa_phi *phi;
|
||||
|
||||
ZEND_ASSERT(old >= 0 && new >= 0);
|
||||
ZEND_ASSERT(old != new);
|
||||
|
||||
/* Only a no_val is both variables are */
|
||||
new_var->no_val &= old_var->no_val;
|
||||
|
||||
/* Update ssa_op use chains */
|
||||
FOREACH_USE(old_var, use) {
|
||||
zend_ssa_op *ssa_op = &ssa->ops[use];
|
||||
|
||||
/* If the op already uses the new var, don't add the op to the use
|
||||
* list again. Instead move the use_chain to the correct operand. */
|
||||
zend_bool add_to_use_chain = 1;
|
||||
if (ssa_op->result_use == new) {
|
||||
add_to_use_chain = 0;
|
||||
} else if (ssa_op->op1_use == new) {
|
||||
if (ssa_op->result_use == old) {
|
||||
ssa_op->res_use_chain = ssa_op->op1_use_chain;
|
||||
ssa_op->op1_use_chain = -1;
|
||||
}
|
||||
add_to_use_chain = 0;
|
||||
} else if (ssa_op->op2_use == new) {
|
||||
if (ssa_op->result_use == old) {
|
||||
ssa_op->res_use_chain = ssa_op->op2_use_chain;
|
||||
ssa_op->op2_use_chain = -1;
|
||||
} else if (ssa_op->op1_use == old) {
|
||||
ssa_op->op1_use_chain = ssa_op->op2_use_chain;
|
||||
ssa_op->op2_use_chain = -1;
|
||||
}
|
||||
add_to_use_chain = 0;
|
||||
}
|
||||
|
||||
/* Perform the actual renaming */
|
||||
if (ssa_op->result_use == old) {
|
||||
ssa_op->result_use = new;
|
||||
}
|
||||
if (ssa_op->op1_use == old) {
|
||||
ssa_op->op1_use = new;
|
||||
}
|
||||
if (ssa_op->op2_use == old) {
|
||||
ssa_op->op2_use = new;
|
||||
}
|
||||
|
||||
/* Add op to use chain of new var (if it isn't already). We use the
|
||||
* first use chain of (result, op1, op2) that has the new variable. */
|
||||
if (add_to_use_chain) {
|
||||
if (ssa_op->result_use == new) {
|
||||
ssa_op->res_use_chain = new_var->use_chain;
|
||||
new_var->use_chain = use;
|
||||
} else if (ssa_op->op1_use == new) {
|
||||
ssa_op->op1_use_chain = new_var->use_chain;
|
||||
new_var->use_chain = use;
|
||||
} else {
|
||||
ZEND_ASSERT(ssa_op->op2_use == new);
|
||||
ssa_op->op2_use_chain = new_var->use_chain;
|
||||
new_var->use_chain = use;
|
||||
}
|
||||
}
|
||||
} FOREACH_USE_END();
|
||||
old_var->use_chain = -1;
|
||||
|
||||
/* Update phi use chains */
|
||||
FOREACH_PHI_USE(old_var, phi) {
|
||||
int j;
|
||||
zend_bool after_first_new_source = 0;
|
||||
|
||||
/* If the phi already uses the new var, find its use chain, as we may
|
||||
* need to move it to a different source operand. */
|
||||
zend_ssa_phi **existing_use_chain_ptr = NULL;
|
||||
for (j = 0; j < ssa->cfg.blocks[phi->block].predecessors_count; j++) {
|
||||
if (phi->sources[j] == new) {
|
||||
existing_use_chain_ptr = &phi->use_chains[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < ssa->cfg.blocks[phi->block].predecessors_count; j++) {
|
||||
if (phi->sources[j] == new) {
|
||||
after_first_new_source = 1;
|
||||
} else if (phi->sources[j] == old) {
|
||||
phi->sources[j] = new;
|
||||
|
||||
/* Either move existing use chain to this source, or add the phi
|
||||
* to the phi use chain of the new variables. Do this only once. */
|
||||
if (!after_first_new_source) {
|
||||
if (existing_use_chain_ptr) {
|
||||
phi->use_chains[j] = *existing_use_chain_ptr;
|
||||
*existing_use_chain_ptr = NULL;
|
||||
} else {
|
||||
phi->use_chains[j] = new_var->phi_use_chain;
|
||||
new_var->phi_use_chain = phi;
|
||||
}
|
||||
after_first_new_source = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure phi result types are not incorrectly narrow after renaming.
|
||||
* This should not normally happen, but can occur if we DCE an assignment
|
||||
* or unset and there is an improper phi-indirected use lateron. */
|
||||
// TODO Alternatively we could rerun type-inference after DCE
|
||||
if (update_types && (ssa->var_info[new].type & ~ssa->var_info[phi->ssa_var].type)) {
|
||||
ssa->var_info[phi->ssa_var].type |= ssa->var_info[new].type;
|
||||
propagate_phi_type_widening(ssa, phi->ssa_var);
|
||||
}
|
||||
} FOREACH_PHI_USE_END();
|
||||
old_var->phi_use_chain = NULL;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* tab-width: 4
|
||||
|
|
|
@ -139,6 +139,41 @@ int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_
|
|||
int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa);
|
||||
int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var);
|
||||
|
||||
void zend_ssa_remove_instr(zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op);
|
||||
void zend_ssa_remove_phi(zend_ssa *ssa, zend_ssa_phi *phi);
|
||||
void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num);
|
||||
void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int b);
|
||||
void zend_ssa_rename_var_uses(zend_ssa *ssa, int old_var, int new_var, zend_bool update_types);
|
||||
|
||||
static zend_always_inline void _zend_ssa_remove_def(zend_ssa_var *var)
|
||||
{
|
||||
ZEND_ASSERT(var->definition >= 0);
|
||||
ZEND_ASSERT(var->use_chain < 0);
|
||||
ZEND_ASSERT(!var->phi_use_chain);
|
||||
var->definition = -1;
|
||||
}
|
||||
|
||||
static zend_always_inline void zend_ssa_remove_result_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
|
||||
{
|
||||
zend_ssa_var *var = &ssa->vars[ssa_op->result_def];
|
||||
_zend_ssa_remove_def(var);
|
||||
ssa_op->result_def = -1;
|
||||
}
|
||||
|
||||
static zend_always_inline void zend_ssa_remove_op1_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
|
||||
{
|
||||
zend_ssa_var *var = &ssa->vars[ssa_op->op1_def];
|
||||
_zend_ssa_remove_def(var);
|
||||
ssa_op->op1_def = -1;
|
||||
}
|
||||
|
||||
static zend_always_inline void zend_ssa_remove_op2_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
|
||||
{
|
||||
zend_ssa_var *var = &ssa->vars[ssa_op->op2_def];
|
||||
_zend_ssa_remove_def(var);
|
||||
ssa_op->op2_def = -1;
|
||||
}
|
||||
|
||||
END_EXTERN_C()
|
||||
|
||||
static zend_always_inline int zend_ssa_next_use(const zend_ssa_op *ssa_op, int var, int use)
|
||||
|
@ -180,6 +215,95 @@ static zend_always_inline zend_bool zend_ssa_is_no_val_use(const zend_op *opline
|
|||
return 0;
|
||||
}
|
||||
|
||||
static zend_always_inline void zend_ssa_rename_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op) {
|
||||
/* Rename def to use if possible. Mark variable as not defined otherwise. */
|
||||
if (ssa_op->op1_def >= 0) {
|
||||
if (ssa_op->op1_use >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, ssa_op->op1_def, ssa_op->op1_use, 1);
|
||||
}
|
||||
ssa->vars[ssa_op->op1_def].definition = -1;
|
||||
ssa_op->op1_def = -1;
|
||||
}
|
||||
if (ssa_op->op2_def >= 0) {
|
||||
if (ssa_op->op2_use >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, ssa_op->op2_def, ssa_op->op2_use, 1);
|
||||
}
|
||||
ssa->vars[ssa_op->op2_def].definition = -1;
|
||||
ssa_op->op2_def = -1;
|
||||
}
|
||||
if (ssa_op->result_def >= 0) {
|
||||
if (ssa_op->result_use >= 0) {
|
||||
zend_ssa_rename_var_uses(ssa, ssa_op->result_def, ssa_op->result_use, 1);
|
||||
}
|
||||
ssa->vars[ssa_op->result_def].definition = -1;
|
||||
ssa_op->result_def = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#define NUM_PHI_SOURCES(phi) \
|
||||
((phi)->pi >= 0 ? 1 : (ssa->cfg.blocks[(phi)->block].predecessors_count))
|
||||
|
||||
/* FOREACH_USE and FOREACH_PHI_USE explicitly support "continue"
|
||||
* and changing the use chain of the current element */
|
||||
#define FOREACH_USE(var, use) do { \
|
||||
int _var_num = (var) - ssa->vars, next; \
|
||||
for (use = (var)->use_chain; use >= 0; use = next) { \
|
||||
next = zend_ssa_next_use(ssa->ops, _var_num, use);
|
||||
#define FOREACH_USE_END() \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FOREACH_PHI_USE(var, phi) do { \
|
||||
int _var_num = (var) - ssa->vars; \
|
||||
zend_ssa_phi *next_phi; \
|
||||
for (phi = (var)->phi_use_chain; phi; phi = next_phi) { \
|
||||
next_phi = zend_ssa_next_use_phi(ssa, _var_num, phi);
|
||||
#define FOREACH_PHI_USE_END() \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FOREACH_PHI_SOURCE(phi, source) do { \
|
||||
zend_ssa_phi *_phi = (phi); \
|
||||
int _i, _end = NUM_PHI_SOURCES(phi); \
|
||||
for (_i = 0; _i < _end; _i++) { \
|
||||
ZEND_ASSERT(_phi->sources[_i] >= 0); \
|
||||
source = _phi->sources[_i];
|
||||
#define FOREACH_PHI_SOURCE_END() \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FOREACH_PHI(phi) do { \
|
||||
int _i; \
|
||||
for (_i = 0; _i < ssa->cfg.blocks_count; _i++) { \
|
||||
phi = ssa->blocks[_i].phis; \
|
||||
for (; phi; phi = phi->next) {
|
||||
#define FOREACH_PHI_END() \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FOREACH_BLOCK(block) do { \
|
||||
int _i; \
|
||||
for (_i = 0; _i < ssa->cfg.blocks_count; _i++) { \
|
||||
(block) = &ssa->cfg.blocks[_i]; \
|
||||
if (!((block)->flags & ZEND_BB_REACHABLE)) { \
|
||||
continue; \
|
||||
}
|
||||
#define FOREACH_BLOCK_END() \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Does not support "break" */
|
||||
#define FOREACH_INSTR_NUM(i) do { \
|
||||
zend_basic_block *_block; \
|
||||
FOREACH_BLOCK(_block) { \
|
||||
uint32_t _end = _block->start + _block->len; \
|
||||
for ((i) = _block->start; (i) < _end; (i)++) {
|
||||
#define FOREACH_INSTR_NUM_END() \
|
||||
} \
|
||||
} FOREACH_BLOCK_END(); \
|
||||
} while (0)
|
||||
|
||||
#endif /* ZEND_SSA_H */
|
||||
|
||||
/*
|
||||
|
|
|
@ -410,6 +410,10 @@ fi
|
|||
Optimizer/zend_inference.c \
|
||||
Optimizer/zend_func_info.c \
|
||||
Optimizer/zend_call_graph.c \
|
||||
Optimizer/sccp.c \
|
||||
Optimizer/scdf.c \
|
||||
Optimizer/dce.c \
|
||||
Optimizer/compact_vars.c \
|
||||
Optimizer/zend_dump.c,
|
||||
shared,,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1,,yes)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ if (PHP_OPCACHE != "no") {
|
|||
zend_shared_alloc.c \
|
||||
shared_alloc_win32.c", true, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
|
||||
|
||||
ADD_SOURCES(configure_module_dirname + "/Optimizer", "zend_optimizer.c pass1_5.c pass2.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c", "opcache", "OptimizerObj");
|
||||
ADD_SOURCES(configure_module_dirname + "/Optimizer", "zend_optimizer.c pass1_5.c pass2.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c sccp.c scdf.c dce.c compact_vars.c zend_dump.c", "opcache", "OptimizerObj");
|
||||
|
||||
|
||||
ADD_FLAG('CFLAGS_OPCACHE', "/I " + configure_module_dirname);
|
||||
|
|
23
ext/opcache/tests/ssa_bug_007.phpt
Normal file
23
ext/opcache/tests/ssa_bug_007.phpt
Normal file
|
@ -0,0 +1,23 @@
|
|||
--TEST--
|
||||
Incorrect CFG/SSA construction for SWITCH with few identical successors
|
||||
--FILE--
|
||||
<?php
|
||||
function render($properties) {
|
||||
foreach ($properties as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'Trapped':
|
||||
if ($value == null) {
|
||||
$docInfo->$key = 1;
|
||||
}
|
||||
case 'CreationDate':
|
||||
case 'ModDate':
|
||||
$docInfo->$key = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
OK
|
||||
--EXPECT--
|
||||
OK
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue