text: /*
     +----------------------------------------------------------------------+
     | Zend OPcache                                                         |
     +----------------------------------------------------------------------+
     | Copyright (c) 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: Andi Gutmans <andi@php.net>                                 |
     |          Zeev Suraski <zeev@php.net>                                 |
     |          Stanislav Malyshev <stas@zend.com>                          |
     |          Dmitry Stogov <dmitry@php.net>                              |
     +----------------------------------------------------------------------+
  */
  
  #include "php.h"
  #include "Optimizer/zend_optimizer.h"
  #include "Optimizer/zend_optimizer_internal.h"
  #include "zend_API.h"
  #include "zend_constants.h"
  #include "zend_execute.h"
  #include "zend_vm.h"
  #include "zend_bitset.h"
  #include "zend_cfg.h"
  #include "zend_dump.h"
  
  /* Checks if a constant (like "true") may be replaced by its value */
  int zend_optimizer_get_persistent_constant(zend_string *name, zval *result, int copy)
  {
  	zval *zv;
  	zend_constant *c = zend_hash_find_ptr(EG(zend_constants), name);
  	if (c) {
  		if ((ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT)
  		 && (!(ZEND_CONSTANT_FLAGS(c) & CONST_NO_FILE_CACHE)
  		  || !(CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE))) {
  			ZVAL_COPY_VALUE(result, &c->value);
  			if (copy) {
  				Z_TRY_ADDREF_P(result);
  			}
  			return 1;
  		} else {
  			return 0;
  		}
  	}
  
  	/* Special constants null/true/false can always be substituted. */
  	zv = zend_get_special_const(ZSTR_VAL(name), ZSTR_LEN(name));
  	if (zv) {
  		ZVAL_COPY_VALUE(result, zv);
  		return 1;
  	}
  	return 0;
  }
  
  /* CFG back references management */
  
  #define DEL_SOURCE(from, to)
  #define ADD_SOURCE(from, to)
  
  /* Data dependencies macros */
  
  #define VAR_SOURCE(op) Tsource[VAR_NUM(op.var)]
  #define SET_VAR_SOURCE(opline) Tsource[VAR_NUM(opline->result.var)] = opline
  
  static void strip_leading_nops(zend_op_array *op_array, zend_basic_block *b)
  {
  	zend_op *opcodes = op_array->opcodes;
  
  	do {
  		b->start++;
  		b->len--;
  	} while (b->len > 0 && opcodes[b->start].opcode == ZEND_NOP);
  }
  
  static void strip_nops(zend_op_array *op_array, zend_basic_block *b)
  {
  	uint32_t i, j;
  
  	if (b->len == 0) {
  		return;
  	}
  
  	if (op_array->opcodes[b->start].opcode == ZEND_NOP) {
  		strip_leading_nops(op_array, b);
  	}
  
  	if (b->len == 0) {
  		return;
  	}
  
  	/* strip the inside NOPs */
  	i = j = b->start + 1;
  	while (i < b->start + b->len) {
  		if (op_array->opcodes[i].opcode != ZEND_NOP) {
  			if (i != j) {
  				op_array->opcodes[j] = op_array->opcodes[i];
  			}
  			j++;
  		}
  		i++;
  	}
  	b->len = j - b->start;
  	while (j < i) {
  		MAKE_NOP(op_array->opcodes + j);
  		j++;
  	}
  }
  
  static int get_const_switch_target(zend_cfg *cfg, zend_op_array *op_array, zend_basic_block *block, zend_op *opline, zval *val) {
  	HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
  	zval *zv;
  	if ((opline->opcode == ZEND_SWITCH_LONG && Z_TYPE_P(val) != IS_LONG)
  			|| (opline->opcode == ZEND_SWITCH_STRING && Z_TYPE_P(val) != IS_STRING)) {
  		/* fallback to next block */
  		return block->successors[block->successors_count - 1];
  	}
  	if (Z_TYPE_P(val) == IS_LONG) {
  		zv = zend_hash_index_find(jumptable, Z_LVAL_P(val));
  	} else {
  		ZEND_ASSERT(Z_TYPE_P(val) == IS_STRING);
  		zv = zend_hash_find(jumptable, Z_STR_P(val));
  	}
  	if (!zv) {
  		/* default */
  		return block->successors[block->successors_count - 2];
  	}
  	return cfg->map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))];
  }
  
  static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_op **Tsource, uint32_t *opt_count)
  {
  	zend_op *opline, *src;
  	zend_op *end, *last_op = NULL;
  
  	if (block->len == 0) {
  		return;
  	}
  
  	if (op_array->opcodes[block->start].opcode == ZEND_NOP) {
  		/* remove leading NOPs */
  		strip_leading_nops(op_array, block);
  	}
  
  	opline = op_array->opcodes + block->start;
  	end = opline + block->len;
  	while (opline < end) {
  		/* Constant Propagation: strip X = QM_ASSIGN(const) */
  		if (opline->op1_type == IS_TMP_VAR &&
  		    opline->opcode != ZEND_FREE) {
  			src = VAR_SOURCE(opline->op1);
  			if (src &&
  			    src->opcode == ZEND_QM_ASSIGN &&
  			    src->op1_type == IS_CONST
  			) {
  				znode_op op1 = opline->op1;
  				if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) {
  					COPY_NODE(opline->result, opline->op1);
  					COPY_NODE(opline->op1, src->op1);
  					VAR_SOURCE(op1) = NULL;
  					MAKE_NOP(src);
  					++(*opt_count);
  				} else {
  					zval c;
  					ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src));
  					if (zend_optimizer_update_op1_const(op_array, opline, &c)) {
  						VAR_SOURCE(op1) = NULL;
  						literal_dtor(&ZEND_OP1_LITERAL(src));
  						MAKE_NOP(src);
  						++(*opt_count);
  						switch (opline->opcode) {
  							case ZEND_JMPZ:
  								if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
  									MAKE_NOP(opline);
  									DEL_SOURCE(block, block->successors[0]);
  									block->successors_count = 1;
  									block->successors[0] = block->successors[1];
  								} else {
  									opline->opcode = ZEND_JMP;
  									COPY_NODE(opline->op1, opline->op2);
  									DEL_SOURCE(block, block->successors[1]);
  									block->successors_count = 1;
  								}
  								break;
  							case ZEND_JMPNZ:
  								if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
  									opline->opcode = ZEND_JMP;
  									COPY_NODE(opline->op1, opline->op2);
  									DEL_SOURCE(block, block->successors[1]);
  									block->successors_count = 1;
  								} else {
  									MAKE_NOP(opline);
  									DEL_SOURCE(block, block->successors[0]);
  									block->successors_count = 1;
  									block->successors[0] = block->successors[1];
  								}
  								break;
  							case ZEND_JMPZNZ:
  								if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
  									zend_op *target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
  									ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
  									DEL_SOURCE(block, block->successors[0]);
  									block->successors[0] = block->successors[1];
  								} else {
  									zend_op *target_opline = ZEND_OP2_JMP_ADDR(opline);
  									ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline);
  									DEL_SOURCE(block, block->successors[0]);
  								}
  								block->successors_count = 1;
  								opline->op1_type = IS_UNUSED;
  								opline->extended_value = 0;
  								opline->opcode = ZEND_JMP;
  								break;
  							default:
  								break;
  						}
  					} else {
  						zval_ptr_dtor_nogc(&c);
  					}
  				}
  			}
  		}
  
  		/* Constant Propagation: strip X = QM_ASSIGN(const) */
  		if (opline->op2_type == IS_TMP_VAR) {
  			src = VAR_SOURCE(opline->op2);
  			if (src &&
  			    src->opcode == ZEND_QM_ASSIGN &&
  			    src->op1_type == IS_CONST) {
  
  				znode_op op2 = opline->op2;
  				zval c;
  
  				ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src));
  				if (zend_optimizer_update_op2_const(op_array, opline, &c)) {
  					VAR_SOURCE(op2) = NULL;
  					literal_dtor(&ZEND_OP1_LITERAL(src));
  					MAKE_NOP(src);
  					++(*opt_count);
  				} else {
  					zval_ptr_dtor_nogc(&c);
  				}
  			}
  		}
  
  		if (opline->opcode == ZEND_ECHO) {
  			if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
  				src = VAR_SOURCE(opline->op1);
  				if (src &&
  				    src->opcode == ZEND_CAST &&
  				    src->extended_value == IS_STRING) {
  					/* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */
  					VAR_SOURCE(opline->op1) = NULL;
  					COPY_NODE(opline->op1, src->op1);
  					MAKE_NOP(src);
  					++(*opt_count);
  				}
  			}
  
  			if (opline->op1_type == IS_CONST) {
  				if (last_op && last_op->opcode == ZEND_ECHO &&
  				    last_op->op1_type == IS_CONST &&
  				    Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_DOUBLE &&
  				    Z_TYPE(ZEND_OP1_LITERAL(last_op)) != IS_DOUBLE) {
  					/* compress consecutive ECHO's.
  					 * Float to string conversion may be affected by current
  					 * locale setting.
  					 */
  					int l, old_len;
  
  					if (Z_TYPE(ZEND_OP1_LITERAL(opline)) != IS_STRING) {
  						convert_to_string(&ZEND_OP1_LITERAL(opline));
  					}
  					if (Z_TYPE(ZEND_OP1_LITERAL(last_op)) != IS_STRING) {
  						convert_to_string(&ZEND_OP1_LITERAL(last_op));
  					}
  					old_len = Z_STRLEN(ZEND_OP1_LITERAL(last_op));
  					l = old_len + Z_STRLEN(ZEND_OP1_LITERAL(opline));
  					if (!Z_REFCOUNTED(ZEND_OP1_LITERAL(last_op))) {
  						zend_string *tmp = zend_string_alloc(l, 0);
  						memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP1_LITERAL(last_op)), old_len);
  						Z_STR(ZEND_OP1_LITERAL(last_op)) = tmp;
  					} else {
  						Z_STR(ZEND_OP1_LITERAL(last_op)) = zend_string_extend(Z_STR(ZEND_OP1_LITERAL(last_op)), l, 0);
  					}
  					Z_TYPE_INFO(ZEND_OP1_LITERAL(last_op)) = IS_STRING_EX;
  					memcpy(Z_STRVAL(ZEND_OP1_LITERAL(last_op)) + old_len, Z_STRVAL(ZEND_OP1_LITERAL(opline)), Z_STRLEN(ZEND_OP1_LITERAL(opline)));
  					Z_STRVAL(ZEND_OP1_LITERAL(last_op))[l] = '\0';
  					zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline));
  					ZVAL_STR(&ZEND_OP1_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP1_LITERAL(last_op))));
  					ZVAL_NULL(&ZEND_OP1_LITERAL(last_op));
  					MAKE_NOP(last_op);
  					++(*opt_count);
  				}
  				last_op = opline;
  			} else {
  				last_op = NULL;
  			}
  		} else {
  			last_op = NULL;
  		}
  
  		switch (opline->opcode) {
  
  			case ZEND_FREE:
  				if (opline->op1_type == IS_TMP_VAR) {
  					src = VAR_SOURCE(opline->op1);
  					if (src &&
  					    (src->opcode == ZEND_BOOL || src->opcode == ZEND_BOOL_NOT)) {
  						/* T = BOOL(X), FREE(T) => T = BOOL(X) */
  						/* The remaining BOOL is removed by a separate optimization */
  						VAR_SOURCE(opline->op1) = NULL;
  						MAKE_NOP(opline);
  						++(*opt_count);
  					}
  				} else if (opline->op1_type == IS_VAR) {
  					src = VAR_SOURCE(opline->op1);
  					/* V = OP, FREE(V) => OP. NOP */
  					if (src &&
  					    src->opcode != ZEND_FETCH_R &&
  					    src->opcode != ZEND_FETCH_STATIC_PROP_R &&
  					    src->opcode != ZEND_FETCH_DIM_R &&
  					    src->opcode != ZEND_FETCH_OBJ_R &&
  					    src->opcode != ZEND_NEW) {
  						src->result_type = IS_UNUSED;
  						MAKE_NOP(opline);
  						++(*opt_count);
  					}
  				}
  				break;
  
  #if 0
  		/* pre-evaluate functions:
  		   constant(x)
  		   function_exists(x)
  		   extension_loaded(x)
  		   BAD: interacts badly with Accelerator
  		*/
  		if((opline->op1_type & IS_VAR) &&
  		   VAR_SOURCE(opline->op1) && VAR_SOURCE(opline->op1)->opcode == ZEND_DO_CF_FCALL &&
  		   VAR_SOURCE(opline->op1)->extended_value == 1) {
  			zend_op *fcall = VAR_SOURCE(opline->op1);
  			zend_op *sv = fcall-1;
  			if(sv >= block->start_opline && sv->opcode == ZEND_SEND_VAL &&
  			   sv->op1_type == IS_CONST && Z_TYPE(OPLINE_OP1_LITERAL(sv)) == IS_STRING &&
  			   Z_LVAL(OPLINE_OP2_LITERAL(sv)) == 1
  			   ) {
  				zval *arg = &OPLINE_OP1_LITERAL(sv);
  				char *fname = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].function_name;
  				int flen = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].name_len;
  				if((flen == sizeof("function_exists")-1 && zend_binary_strcasecmp(fname, flen, "function_exists", sizeof("function_exists")-1) == 0) ||
  						  (flen == sizeof("is_callable")-1 && zend_binary_strcasecmp(fname, flen, "is_callable", sizeof("is_callable")-1) == 0)
  						  ) {
  					zend_function *function;
  					if((function = zend_hash_find_ptr(EG(function_table), Z_STR_P(arg))) != NULL) {
  						literal_dtor(arg);
  						MAKE_NOP(sv);
  						MAKE_NOP(fcall);
  						LITERAL_BOOL(opline->op1, 1);
  						opline->op1_type = IS_CONST;
  					}
  				} else if(flen == sizeof("constant")-1 && zend_binary_strcasecmp(fname, flen, "constant", sizeof("constant")-1) == 0) {
  					zval c;
  					if(zend_optimizer_get_persistent_constant(Z_STR_P(arg), &c, 1 ELS_CC) != 0) {
  						literal_dtor(arg);
  						MAKE_NOP(sv);
  						MAKE_NOP(fcall);
  						ZEND_OP1_LITERAL(opline) = zend_optimizer_add_literal(op_array, &c);
  						/* no copy ctor - get already copied it */
  						opline->op1_type = IS_CONST;
  					}
  				} else if(flen == sizeof("extension_loaded")-1 && zend_binary_strcasecmp(fname, flen, "extension_loaded", sizeof("extension_loaded")-1) == 0) {
  					if(zend_hash_exists(&module_registry, Z_STR_P(arg))) {
  						literal_dtor(arg);
  						MAKE_NOP(sv);
  						MAKE_NOP(fcall);
  						LITERAL_BOOL(opline->op1, 1);
  						opline->op1_type = IS_CONST;
  					}
  				}
  			}
  		}
  #endif
  
  			case ZEND_FETCH_LIST_R:
  			case ZEND_FETCH_LIST_W:
  				if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
  					/* LIST variable will be deleted later by FREE */
  					Tsource[VAR_NUM(opline->op1.var)] = NULL;
  				}
  				break;
  
  			case ZEND_SWITCH_LONG:
  			case ZEND_SWITCH_STRING:
  				if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
  					/* SWITCH variable will be deleted later by FREE, so we can't optimize it */
  					Tsource[VAR_NUM(opline->op1.var)] = NULL;
  					break;
  				}
  				if (opline->op1_type == IS_CONST) {
  					int target = get_const_switch_target(cfg, op_array, block, opline, &ZEND_OP1_LITERAL(opline));
  					literal_dtor(&ZEND_OP1_LITERAL(opline));
  					literal_dtor(&ZEND_OP2_LITERAL(opline));
  					opline->opcode = ZEND_JMP;
  					opline->op1_type = IS_UNUSED;
  					opline->op2_type = IS_UNUSED;
  					block->successors_count = 1;
  					block->successors[0] = target;
  				}
  				break;
  
  			case ZEND_CASE:
  			case ZEND_COPY_TMP:
  				if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
  					/* Variable will be deleted later by FREE, so we can't optimize it */
  					Tsource[VAR_NUM(opline->op1.var)] = NULL;
  					break;
  				}
  				/* break missing intentionally */
  
  			case ZEND_IS_EQUAL:
  			case ZEND_IS_NOT_EQUAL:
  				if (opline->op1_type == IS_CONST &&
  				    opline->op2_type == IS_CONST) {
  					goto optimize_constant_binary_op;
  				}
  		        /* IS_EQ(TRUE, X)      => BOOL(X)
  		         * IS_EQ(FALSE, X)     => BOOL_NOT(X)
  		         * IS_NOT_EQ(TRUE, X)  => BOOL_NOT(X)
  		         * IS_NOT_EQ(FALSE, X) => BOOL(X)
  		         * CASE(TRUE, X)       => BOOL(X)
  		         * CASE(FALSE, X)      => BOOL_NOT(X)
  		         */
  				if (opline->op1_type == IS_CONST &&
  					(Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_FALSE ||
  					 Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_TRUE)) {
  					/* Optimization of comparison with "null" is not safe,
  					 * because ("0" == null) is not equal to !("0")
  					 */
  					opline->opcode =
  						((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP1_LITERAL(opline))) == IS_TRUE)) ?
  						ZEND_BOOL : ZEND_BOOL_NOT;
  					COPY_NODE(opline->op1, opline->op2);
  					SET_UNUSED(opline->op2);
  					++(*opt_count);
  					goto optimize_bool;
  				} else if (opline->op2_type == IS_CONST &&
  				           (Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_FALSE ||
  				            Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_TRUE)) {
  					/* Optimization of comparison with "null" is not safe,
  					 * because ("0" == null) is not equal to !("0")
  					 */
  					opline->opcode =
  						((opline->opcode != ZEND_IS_NOT_EQUAL) == ((Z_TYPE(ZEND_OP2_LITERAL(opline))) == IS_TRUE)) ?
  						ZEND_BOOL : ZEND_BOOL_NOT;
  					SET_UNUSED(opline->op2);
  					++(*opt_count);
  					goto optimize_bool;
  				}
  				break;
  
  			case ZEND_BOOL:
  			case ZEND_BOOL_NOT:
  			optimize_bool:
  				if (opline->op1_type == IS_CONST) {
  					goto optimize_const_unary_op;
  				}
  				if (opline->op1_type == IS_TMP_VAR &&
  				    !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
  					src = VAR_SOURCE(opline->op1);
  					if (src) {
  						switch (src->opcode) {
  							case ZEND_BOOL_NOT:
  								/* T = BOOL_NOT(X) + BOOL(T) -> NOP, BOOL_NOT(X) */
  								VAR_SOURCE(opline->op1) = NULL;
  								COPY_NODE(opline->op1, src->op1);
  								opline->opcode = (opline->opcode == ZEND_BOOL) ? ZEND_BOOL_NOT : ZEND_BOOL;
  								MAKE_NOP(src);
  								++(*opt_count);
  								goto optimize_bool;
  							case ZEND_BOOL:
  								/* T = BOOL(X) + BOOL(T) -> NOP, BOOL(X) */
  								VAR_SOURCE(opline->op1) = NULL;
  								COPY_NODE(opline->op1, src->op1);
  								MAKE_NOP(src);
  								++(*opt_count);
  								goto optimize_bool;
  							case ZEND_IS_EQUAL:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									src->opcode = ZEND_IS_NOT_EQUAL;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  							case ZEND_IS_NOT_EQUAL:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									src->opcode = ZEND_IS_EQUAL;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  							case ZEND_IS_IDENTICAL:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									src->opcode = ZEND_IS_NOT_IDENTICAL;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  							case ZEND_IS_NOT_IDENTICAL:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									src->opcode = ZEND_IS_IDENTICAL;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  							case ZEND_IS_SMALLER:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									zend_uchar tmp_type;
  									uint32_t tmp;
  
  									src->opcode = ZEND_IS_SMALLER_OR_EQUAL;
  									tmp_type = src->op1_type;
  									src->op1_type = src->op2_type;
  									src->op2_type = tmp_type;
  									tmp = src->op1.num;
  									src->op1.num = src->op2.num;
  									src->op2.num = tmp;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  							case ZEND_IS_SMALLER_OR_EQUAL:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									zend_uchar tmp_type;
  									uint32_t tmp;
  
  									src->opcode = ZEND_IS_SMALLER;
  									tmp_type = src->op1_type;
  									src->op1_type = src->op2_type;
  									src->op2_type = tmp_type;
  									tmp = src->op1.num;
  									src->op1.num = src->op2.num;
  									src->op2.num = tmp;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  							case ZEND_ISSET_ISEMPTY_CV:
  							case ZEND_ISSET_ISEMPTY_VAR:
  							case ZEND_ISSET_ISEMPTY_DIM_OBJ:
  							case ZEND_ISSET_ISEMPTY_PROP_OBJ:
  							case ZEND_ISSET_ISEMPTY_STATIC_PROP:
  							case ZEND_INSTANCEOF:
  							case ZEND_TYPE_CHECK:
  							case ZEND_DEFINED:
  							case ZEND_IN_ARRAY:
  							case ZEND_ARRAY_KEY_EXISTS:
  								if (opline->opcode == ZEND_BOOL_NOT) {
  									break;
  								}
  								COPY_NODE(src->result, opline->result);
  								SET_VAR_SOURCE(src);
  								MAKE_NOP(opline);
  								++(*opt_count);
  								break;
  						}
  					}
  				}
  				break;
  
  			case ZEND_JMPZ:
  			case ZEND_JMPNZ:
  			case ZEND_JMPZ_EX:
  			case ZEND_JMPNZ_EX:
  			case ZEND_JMPZNZ:
  			optimize_jmpznz:
  				if (opline->op1_type == IS_TMP_VAR &&
  				    (!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var)) ||
  				     (opline->result_type == opline->op1_type &&
  				      opline->result.var == opline->op1.var))) {
  					src = VAR_SOURCE(opline->op1);
  					if (src) {
  						if (src->opcode == ZEND_BOOL_NOT &&
  						    opline->opcode != ZEND_JMPZ_EX &&
  						    opline->opcode != ZEND_JMPNZ_EX) {
  							VAR_SOURCE(opline->op1) = NULL;
  							COPY_NODE(opline->op1, src->op1);
  							if (opline->opcode == ZEND_JMPZ) {
  								/* T = BOOL_NOT(X) + JMPZ(T) -> NOP, JMPNZ(X) */
  								opline->opcode = ZEND_JMPNZ;
  							} else if (opline->opcode == ZEND_JMPNZ) {
  								/* T = BOOL_NOT(X) + JMPNZ(T) -> NOP, JMPZ(X) */
  								opline->opcode = ZEND_JMPZ;
  #if 0
  							} else if (opline->opcode == ZEND_JMPZ_EX) {
  								/* T = BOOL_NOT(X) + JMPZ_EX(T) -> NOP, JMPNZ_EX(X) */
  								opline->opcode = ZEND_JMPNZ_EX;
  							} else if (opline->opcode == ZEND_JMPNZ_EX) {
  								/* T = BOOL_NOT(X) + JMPNZ_EX(T) -> NOP, JMPZ_EX(X) */
  								opline->opcode = ZEND_JMPZ;
  #endif
  							} else {
  								/* T = BOOL_NOT(X) + JMPZNZ(T,L1,L2) -> NOP, JMPZNZ(X,L2,L1) */
  								uint32_t tmp;
  
  								ZEND_ASSERT(opline->opcode == ZEND_JMPZNZ);
  								tmp = block->successors[0];
  								block->successors[0] = block->successors[1];
  								block->successors[1] = tmp;
  							}
  							MAKE_NOP(src);
  							++(*opt_count);
  							goto optimize_jmpznz;
  						} else if (src->opcode == ZEND_BOOL ||
  						           src->opcode == ZEND_QM_ASSIGN) {
  							VAR_SOURCE(opline->op1) = NULL;
  							COPY_NODE(opline->op1, src->op1);
  							MAKE_NOP(src);
  							++(*opt_count);
  							goto optimize_jmpznz;
  						}
  					}
  				}
  				break;
  
  			case ZEND_CONCAT:
  			case ZEND_FAST_CONCAT:
  				if (opline->op1_type == IS_CONST &&
  				    opline->op2_type == IS_CONST) {
  					goto optimize_constant_binary_op;
  				}
  
  				if (opline->op2_type == IS_CONST &&
  				    opline->op1_type == IS_TMP_VAR) {
  
  					src = VAR_SOURCE(opline->op1);
  				    if (src &&
  					    (src->opcode == ZEND_CONCAT ||
  					     src->opcode == ZEND_FAST_CONCAT) &&
  					    src->op2_type == IS_CONST) {
  						/* compress consecutive CONCATs */
  						int l, old_len;
  
  						if (Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_STRING) {
  							convert_to_string(&ZEND_OP2_LITERAL(opline));
  						}
  						if (Z_TYPE(ZEND_OP2_LITERAL(src)) != IS_STRING) {
  							convert_to_string(&ZEND_OP2_LITERAL(src));
  						}
  
  						VAR_SOURCE(opline->op1) = NULL;
  						COPY_NODE(opline->op1, src->op1);
  						old_len = Z_STRLEN(ZEND_OP2_LITERAL(src));
  						l = old_len + Z_STRLEN(ZEND_OP2_LITERAL(opline));
  						if (!Z_REFCOUNTED(ZEND_OP2_LITERAL(src))) {
  							zend_string *tmp = zend_string_alloc(l, 0);
  							memcpy(ZSTR_VAL(tmp), Z_STRVAL(ZEND_OP2_LITERAL(src)), old_len);
  							Z_STR(ZEND_OP2_LITERAL(src)) = tmp;
  						} else {
  							Z_STR(ZEND_OP2_LITERAL(src)) = zend_string_extend(Z_STR(ZEND_OP2_LITERAL(src)), l, 0);
  						}
  						Z_TYPE_INFO(ZEND_OP2_LITERAL(src)) = IS_STRING_EX;
  						memcpy(Z_STRVAL(ZEND_OP2_LITERAL(src)) + old_len, Z_STRVAL(ZEND_OP2_LITERAL(opline)), Z_STRLEN(ZEND_OP2_LITERAL(opline)));
  						Z_STRVAL(ZEND_OP2_LITERAL(src))[l] = '\0';
  						zval_ptr_dtor_str(&ZEND_OP2_LITERAL(opline));
  						ZVAL_STR(&ZEND_OP2_LITERAL(opline), zend_new_interned_string(Z_STR(ZEND_OP2_LITERAL(src))));
  						ZVAL_NULL(&ZEND_OP2_LITERAL(src));
  						MAKE_NOP(src);
  						++(*opt_count);
  					}
  				}
  
  				if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
  					src = VAR_SOURCE(opline->op1);
  					if (src &&
  					    src->opcode == ZEND_CAST &&
  					    src->extended_value == IS_STRING &&
  					    src->op1_type != IS_CONST) {
  						/* convert T1 = CAST(STRING, X), T2 = CONCAT(T1, Y) to T2 = CONCAT(X,Y) */
  						VAR_SOURCE(opline->op1) = NULL;
  						COPY_NODE(opline->op1, src->op1);
  						MAKE_NOP(src);
  						++(*opt_count);
  					}
  	            }
  				if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
  					src = VAR_SOURCE(opline->op2);
  					if (src &&
  					    src->opcode == ZEND_CAST &&
  					    src->extended_value == IS_STRING &&
  					    src->op1_type != IS_CONST) {
  						/* convert T1 = CAST(STRING, X), T2 = CONCAT(Y, T1) to T2 = CONCAT(Y,X) */
  						zend_op *src = VAR_SOURCE(opline->op2);
  						VAR_SOURCE(opline->op2) = NULL;
  						COPY_NODE(opline->op2, src->op1);
  						MAKE_NOP(src);
  						++(*opt_count);
  					}
  				}
  				if (opline->op1_type == IS_CONST &&
  				    Z_TYPE(ZEND_OP1_LITERAL(opline)) == IS_STRING &&
  				    Z_STRLEN(ZEND_OP1_LITERAL(opline)) == 0) {
  					/* convert CONCAT('', X) => CAST(STRING, X) */
  					literal_dtor(&ZEND_OP1_LITERAL(opline));
  					opline->opcode = ZEND_CAST;
  					opline->extended_value = IS_STRING;
  					COPY_NODE(opline->op1, opline->op2);
  					opline->op2_type = IS_UNUSED;
  					opline->op2.var = 0;
  					++(*opt_count);
  				} else if (opline->op2_type == IS_CONST &&
  			           Z_TYPE(ZEND_OP2_LITERAL(opline)) == IS_STRING &&
  			           Z_STRLEN(ZEND_OP2_LITERAL(opline)) == 0) {
  					/* convert CONCAT(X, '') => CAST(STRING, X) */
  					literal_dtor(&ZEND_OP2_LITERAL(opline));
  					opline->opcode = ZEND_CAST;
  					opline->extended_value = IS_STRING;
  					opline->op2_type = IS_UNUSED;
  					opline->op2.var = 0;
  					++(*opt_count);
  				} else if (opline->opcode == ZEND_CONCAT &&
  				           (opline->op1_type == IS_CONST ||
  				            (opline->op1_type == IS_TMP_VAR &&
  				             VAR_SOURCE(opline->op1) &&
  				             (VAR_SOURCE(opline->op1)->opcode == ZEND_FAST_CONCAT ||
  				              VAR_SOURCE(opline->op1)->opcode == ZEND_ROPE_END ||
  				              VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CONSTANT ||
  				              VAR_SOURCE(opline->op1)->opcode == ZEND_FETCH_CLASS_CONSTANT))) &&
  				           (opline->op2_type == IS_CONST ||
  				            (opline->op2_type == IS_TMP_VAR &&
  				             VAR_SOURCE(opline->op2) &&
  				             (VAR_SOURCE(opline->op2)->opcode == ZEND_FAST_CONCAT ||
  				              VAR_SOURCE(opline->op2)->opcode == ZEND_ROPE_END ||
  				              VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CONSTANT ||
  				              VAR_SOURCE(opline->op2)->opcode == ZEND_FETCH_CLASS_CONSTANT)))) {
  					opline->opcode = ZEND_FAST_CONCAT;
  					++(*opt_count);
  				}
  				break;
  
  			case ZEND_ADD:
  			case ZEND_SUB:
  			case ZEND_MUL:
  			case ZEND_DIV:
  			case ZEND_MOD:
  			case ZEND_SL:
  			case ZEND_SR:
  			case ZEND_IS_SMALLER:
  			case ZEND_IS_SMALLER_OR_EQUAL:
  			case ZEND_IS_IDENTICAL:
  			case ZEND_IS_NOT_IDENTICAL:
  			case ZEND_BOOL_XOR:
  			case ZEND_BW_OR:
  			case ZEND_BW_AND:
  			case ZEND_BW_XOR:
  				if (opline->op1_type == IS_CONST &&
  				    opline->op2_type == IS_CONST) {
  					/* evaluate constant expressions */
  					zval result;
  
  optimize_constant_binary_op:
  					if (zend_optimizer_eval_binary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline), &ZEND_OP2_LITERAL(opline)) == SUCCESS) {
  						literal_dtor(&ZEND_OP1_LITERAL(opline));
  						literal_dtor(&ZEND_OP2_LITERAL(opline));
  						opline->opcode = ZEND_QM_ASSIGN;
  						SET_UNUSED(opline->op2);
  						zend_optimizer_update_op1_const(op_array, opline, &result);
  						++(*opt_count);
  					}
  				}
  				break;
  
  			case ZEND_BW_NOT:
  				if (opline->op1_type == IS_CONST) {
  					/* evaluate constant unary ops */
  					zval result;
  
  optimize_const_unary_op:
  					if (zend_optimizer_eval_unary_op(&result, opline->opcode, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
  						literal_dtor(&ZEND_OP1_LITERAL(opline));
  						opline->opcode = ZEND_QM_ASSIGN;
  						zend_optimizer_update_op1_const(op_array, opline, &result);
  						++(*opt_count);
  					}
  				}
  				break;
  
  			case ZEND_CAST:
  				if (opline->op1_type == IS_CONST) {
  					/* cast of constant operand */
  					zval result;
  
  					if (zend_optimizer_eval_cast(&result, opline->extended_value, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
  						literal_dtor(&ZEND_OP1_LITERAL(opline));
  						opline->opcode = ZEND_QM_ASSIGN;
  						opline->extended_value = 0;
  						zend_optimizer_update_op1_const(op_array, opline, &result);
  						++(*opt_count);
  					}
  				}
  				break;
  
  			case ZEND_STRLEN:
  				if (opline->op1_type == IS_CONST) {
  					zval result;
  
  					if (zend_optimizer_eval_strlen(&result, &ZEND_OP1_LITERAL(opline)) == SUCCESS) {
  						literal_dtor(&ZEND_OP1_LITERAL(opline));
  						opline->opcode = ZEND_QM_ASSIGN;
  						zend_optimizer_update_op1_const(op_array, opline, &result);
  						++(*opt_count);
  					}
  				}
  				break;
  
  			case ZEND_RETURN:
  			case ZEND_EXIT:
  				if (opline->op1_type == IS_TMP_VAR) {
  					src = VAR_SOURCE(opline->op1);
  					if (src && src->opcode == ZEND_QM_ASSIGN) {
  						zend_op *op = src + 1;
  						zend_bool optimize = 1;
  
  						while (op < opline) {
  							if ((op->op1_type == opline->op1_type
  							  && op->op1.var == opline->op1.var)
  							 || (op->op2_type == opline->op1_type
  							  && op->op2.var == opline->op1.var)) {
  								optimize = 0;
  								break;
  							}
  							op++;
  						}
  
  						if (optimize) {
  							/* T = QM_ASSIGN(X), RETURN(T) to NOP, RETURN(X) */
  							VAR_SOURCE(opline->op1) = NULL;
  							COPY_NODE(opline->op1, src->op1);
  							MAKE_NOP(src);
  							++(*opt_count);
  						}
  					}
  				}
  				break;
  
  			case ZEND_QM_ASSIGN:
  				if (opline->op1_type == opline->result_type &&
  				    opline->op1.var == opline->result.var) {
  					/* strip T = QM_ASSIGN(T) */
  					MAKE_NOP(opline);
  					++(*opt_count);
  				} else if (opline->op1_type == IS_TMP_VAR &&
  				           opline->result_type == IS_TMP_VAR &&
  				           !zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
  					/* T1 = ..., T2 = QM_ASSIGN(T1) to T2 = ..., NOP */
  					src = VAR_SOURCE(opline->op1);
  					if (src) {
  						src->result.var = opline->result.var;
  						VAR_SOURCE(opline->op1) = NULL;
  						VAR_SOURCE(opline->result) = src;
  						MAKE_NOP(opline);
  						++(*opt_count);
  					}
  				}
  				break;
  		}
  
  		/* get variable source */
  		if (opline->result_type & (IS_VAR|IS_TMP_VAR)) {
  			SET_VAR_SOURCE(opline);
  		}
  		opline++;
  	}
  }
  
  /* Rebuild plain (optimized) op_array from CFG */
  static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_optimizer_ctx *ctx)
  {
  	zend_basic_block *blocks = cfg->blocks;
  	zend_basic_block *end = blocks + cfg->blocks_count;
  	zend_basic_block *b;
  	zend_op *new_opcodes;
  	zend_op *opline;
  	uint32_t len = 0;
  	int n;
  
  	for (b = blocks; b < end; b++) {
  		if (b->len == 0) {
  			continue;
  		}
  		if (b->flags & ZEND_BB_REACHABLE) {
  			opline = op_array->opcodes + b->start + b->len - 1;
  			if (opline->opcode == ZEND_JMP) {
  				zend_basic_block *next = b + 1;
  
  				while (next < end && !(next->flags & ZEND_BB_REACHABLE)) {
  					next++;
  				}
  				if (next < end && next == blocks + b->successors[0]) {
  					/* JMP to the next block - strip it */
  					MAKE_NOP(opline);
  					b->len--;
  				}
  			} else if (b->len == 1 && opline->opcode == ZEND_NOP) {
  				/* skip empty block */
  				b->len--;
  			}
  			len += b->len;
  		} else {
  			/* this block will not be used, delete all constants there */
  			zend_op *op = op_array->opcodes + b->start;
  			zend_op *end = op + b->len;
  			for (; op < end; op++) {
  				if (op->op1_type == IS_CONST) {
  					literal_dtor(&ZEND_OP1_LITERAL(op));
  				}
  				if (op->op2_type == IS_CONST) {
  					literal_dtor(&ZEND_OP2_LITERAL(op));
  				}
  			}
  		}
  	}
  
  	new_opcodes = emalloc(len * sizeof(zend_op));
  	opline = new_opcodes;
  
  	/* Copy code of reachable blocks into a single buffer */
  	for (b = blocks; b < end; b++) {
  		if (b->flags & ZEND_BB_REACHABLE) {
  			memcpy(opline, op_array->opcodes + b->start, b->len * sizeof(zend_op));
  			b->start = opline - new_opcodes;
  			opline += b->len;
  		}
  	}
  
  	/* adjust jump targets */
  	efree(op_array->opcodes);
  	op_array->opcodes = new_opcodes;
  	op_array->last = len;
  
  	for (b = blocks; b < end; b++) {
  		if (!(b->flags & ZEND_BB_REACHABLE) || b->len == 0) {
  			continue;
  		}
  		opline = op_array->opcodes + b->start + b->len - 1;
  		switch (opline->opcode) {
  			case ZEND_FAST_CALL:
  			case ZEND_JMP:
  				ZEND_SET_OP_JMP_ADDR(opline, opline->op1, new_opcodes + blocks[b->successors[0]].start);
  				break;
  			case ZEND_JMPZNZ:
  				opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[1]].start);
  				/* break missing intentionally */
  			case ZEND_JMPZ:
  			case ZEND_JMPNZ:
  			case ZEND_JMPZ_EX:
  			case ZEND_JMPNZ_EX:
  			case ZEND_FE_RESET_R:
  			case ZEND_FE_RESET_RW:
  			case ZEND_JMP_SET:
  			case ZEND_COALESCE:
  			case ZEND_ASSERT_CHECK:
  				ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
  				break;
  			case ZEND_CATCH:
  				if (!(opline->extended_value & ZEND_LAST_CATCH)) {
  					ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
  				}
  				break;
  			case ZEND_FE_FETCH_R:
  			case ZEND_FE_FETCH_RW:
  				opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[0]].start);
  				break;
  			case ZEND_SWITCH_LONG:
  			case ZEND_SWITCH_STRING:
  			{
  				HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
  				zval *zv;
  				uint32_t s = 0;
  				ZEND_ASSERT(b->successors_count == 2 + zend_hash_num_elements(jumptable));
  
  				ZEND_HASH_FOREACH_VAL(jumptable, zv) {
  					Z_LVAL_P(zv) = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start);
  				} ZEND_HASH_FOREACH_END();
  				opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start);
  				break;
  			}
  		}
  	}
  
  	/* adjust exception jump targets & remove unused try_catch_array entries */
  	if (op_array->last_try_catch) {
  		int i, j;
  		uint32_t *map;
  		ALLOCA_FLAG(use_heap);
  
  		map = (uint32_t *)do_alloca(sizeof(uint32_t) * op_array->last_try_catch, use_heap);
  		for (i = 0, j = 0; i< op_array->last_try_catch; i++) {
  			if (blocks[cfg->map[op_array->try_catch_array[i].try_op]].flags & ZEND_BB_REACHABLE) {
  				map[i] = j;
  				op_array->try_catch_array[j].try_op = blocks[cfg->map[op_array->try_catch_array[i].try_op]].start;
  				if (op_array->try_catch_array[i].catch_op) {
  					op_array->try_catch_array[j].catch_op = blocks[cfg->map[op_array->try_catch_array[i].catch_op]].start;
  				} else {
  					op_array->try_catch_array[j].catch_op =  0;
  				}
  				if (op_array->try_catch_array[i].finally_op) {
  					op_array->try_catch_array[j].finally_op = blocks[cfg->map[op_array->try_catch_array[i].finally_op]].start;
  				} else {
  					op_array->try_catch_array[j].finally_op =  0;
  				}
  				if (!op_array->try_catch_array[i].finally_end) {
  					op_array->try_catch_array[j].finally_end = 0;
  				} else {
  					op_array->try_catch_array[j].finally_end = blocks[cfg->map[op_array->try_catch_array[i].finally_end]].start;
  				}
  				j++;
  			}
  		}
  		if (i != j) {
  			op_array->last_try_catch = j;
  			if (j == 0) {
  				efree(op_array->try_catch_array);
  				op_array->try_catch_array = NULL;
  			}
  
  			if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
  				zend_op *opline = new_opcodes;
  				zend_op *end = opline + len;
  				while (opline < end) {
  					if (opline->opcode == ZEND_FAST_RET &&
  					    opline->op2.num != (uint32_t)-1 &&
  					    opline->op2.num < (uint32_t)j) {
  						opline->op2.num = map[opline->op2.num];
  					}
  					opline++;
  				}
  			}
  		}
  		free_alloca(map, use_heap);
  	}
  
  	/* adjust early binding list */
  	if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) {
  		ZEND_ASSERT(op_array == &ctx->script->main_op_array);
  		ctx->script->first_early_binding_opline =
  			zend_build_delayed_early_binding_list(op_array);
  	}
  
  	/* rebuild map (just for printing) */
  	memset(cfg->map, -1, sizeof(int) * op_array->last);
  	for (n = 0; n < cfg->blocks_count; n++) {
  		if (cfg->blocks[n].flags & ZEND_BB_REACHABLE) {
  			cfg->map[cfg->blocks[n].start] = n;
  		}
  	}
  }
  
  static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_array, zend_cfg *cfg, uint32_t *opt_count)
  {
  	/* last_op is the last opcode of the current block */
  	zend_basic_block *blocks = cfg->blocks;
  	zend_op *last_op;
  
  	if (block->len == 0) {
  		return;
  	}
  
  	last_op = op_array->opcodes + block->start + block->len - 1;
  	switch (last_op->opcode) {
  		case ZEND_JMP:
  			{
  				zend_basic_block *target_block = blocks + block->successors[0];
  				zend_op *target = op_array->opcodes + target_block->start;
  				int next = (block - blocks) + 1;
  
  				while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) {
  					/* find used one */
  					next++;
  				}
  
  				/* JMP(next) -> NOP */
  				if (block->successors[0] == next) {
  					MAKE_NOP(last_op);
  					++(*opt_count);
  					block->len--;
  					break;
  				}
  
  				if (target->opcode == ZEND_JMP &&
  					block->successors[0] != target_block->successors[0] &&
  					!(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMP L, L: JMP L1 -> JMP L1 */
  					*last_op = *target;
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMPZNZ &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */
  					*last_op = *target;
  					if (last_op->op1_type == IS_CONST) {
  						zval zv;
  						ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op));
  						last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv);
  					}
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors_count = 2;
  					block->successors[0] = target_block->successors[0];
  					block->successors[1] = target_block->successors[1];
  					ADD_SOURCE(block, block->successors[0]);
  					ADD_SOURCE(block, block->successors[1]);
  					++(*opt_count);
  				} else if ((target->opcode == ZEND_RETURN ||
  				            target->opcode == ZEND_RETURN_BY_REF ||
  				            target->opcode == ZEND_EXIT) &&
  				           !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
  					/* JMP L, L: RETURN to immediate RETURN */
  					*last_op = *target;
  					if (last_op->op1_type == IS_CONST) {
  						zval zv;
  						ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(last_op));
  						last_op->op1.constant = zend_optimizer_add_literal(op_array, &zv);
  					}
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors_count = 0;
  					++(*opt_count);
  #if 0
  				/* Temporarily disabled - see bug #0025274 */
  				} else if (0&& block->op1_to != block &&
  			           block->op1_to != blocks &&
  						   op_array->last_try_catch == 0 &&
  				           target->opcode != ZEND_FREE) {
  				    /* Block Reordering (saves one JMP on each "for" loop iteration)
  				     * It is disabled for some cases (ZEND_FREE)
  				     * which may break register allocation.
              	     */
  					zend_bool can_reorder = 0;
  					zend_block_source *cs = block->op1_to->sources;
  
  					/* the "target" block doesn't had any followed block */
  					while(cs) {
  						if (cs->from->follow_to == block->op1_to) {
  							can_reorder = 0;
  							break;
  						}
  						cs = cs->next;
  					}
  					if (can_reorder) {
  						next = block->op1_to;
  						/* the "target" block is not followed by current "block" */
  						while (next->follow_to != NULL) {
  							if (next->follow_to == block) {
  								can_reorder = 0;
  								break;
  							}
  							next = next->follow_to;
  						}
  						if (can_reorder) {
  							zend_basic_block *prev = blocks;
  
  							while (prev->next != block->op1_to) {
  								prev = prev->next;
  							}
  							prev->next = next->next;
  							next->next = block->next;
  							block->next = block->op1_to;
  
  							block->follow_to = block->op1_to;
  							block->op1_to = NULL;
  							MAKE_NOP(last_op);
  							block->len--;
  							if(block->len == 0) {
  								/* this block is nothing but NOP now */
  								delete_code_block(block, ctx);
  							}
  							break;
  						}
  					}
  #endif
  				}
  			}
  			break;
  
  		case ZEND_JMPZ:
  		case ZEND_JMPNZ:
  			/* constant conditional JMPs */
  			if (last_op->op1_type == IS_CONST) {
  				int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(last_op));
  
  				if (last_op->opcode == ZEND_JMPZ) {
  					should_jmp = !should_jmp;
  				}
  				literal_dtor(&ZEND_OP1_LITERAL(last_op));
  				last_op->op1_type = IS_UNUSED;
  				if (should_jmp) {
  					/* JMPNZ(true) -> JMP */
  					last_op->opcode = ZEND_JMP;
  					DEL_SOURCE(block, block->successors[1]);
  					block->successors_count = 1;
  				} else {
  					/* JMPNZ(false) -> NOP */
  					MAKE_NOP(last_op);
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors_count = 1;
  					block->successors[0] = block->successors[1];
  				}
  				++(*opt_count);
  				break;
  			}
  
  			if (block->successors[0] == block->successors[1]) {
  				/* L: JMP[N]Z(X, L+1) -> NOP or FREE(X) */
  
  				if (last_op->op1_type == IS_CV) {
  					last_op->opcode = ZEND_CHECK_VAR;
  					last_op->op2.num = 0;
  				} else if (last_op->op1_type & (IS_VAR|IS_TMP_VAR)) {
  					last_op->opcode = ZEND_FREE;
  					last_op->op2.num = 0;
  				} else {
  					MAKE_NOP(last_op);
  				}
  				block->successors_count = 1;
  				++(*opt_count);
  				break;
  			}
  
  			if (1) {
  				zend_uchar same_type = last_op->op1_type;
  				uint32_t same_var = last_op->op1.var;
  				zend_op *target;
  				zend_op *target_end;
  				zend_basic_block *target_block = blocks + block->successors[0];
  
  next_target:
  				target = op_array->opcodes + target_block->start;
  				target_end = target + target_block->len;
  				while (target < target_end && target->opcode == ZEND_NOP) {
  					target++;
  				}
  
  				/* next block is only NOP's */
  				if (target == target_end) {
  					target_block = blocks + target_block->successors[0];
  					++(*opt_count);
  					goto next_target;
  				} else if (target->opcode == INV_COND(last_op->opcode) &&
  				           same_var == target->op1.var &&
  				           same_type == target->op1_type &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZ(X, L), L: JMPNZ(X, L2) -> JMPZ(X, L+1) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[1];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == INV_COND_EX(last_op->opcode) &&
  				           same_var == target->op1.var &&
  				           same_type == target->op1_type &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZ(X, L), L: T = JMPNZ_EX(X, L2) -> T = JMPZ_EX(X, L+1) */
  					last_op->opcode += 3;
  					COPY_NODE(last_op->result, target->result);
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[1];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == last_op->opcode &&
  				           same_var == target->op1.var &&
  				           same_type == target->op1_type &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZ(X, L), L: JMPZ(X, L2) -> JMPZ(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMP &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZ(X, L), L: JMP(L2) -> JMPZ(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMPZNZ &&
  				           same_var == target->op1.var &&
  				           same_type == target->op1_type &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZ(X, L), L: JMPZNZ(X, L2, L3) -> JMPZ(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					if (last_op->opcode == ZEND_JMPZ) {
  						block->successors[0] = target_block->successors[0];
  					} else {
  						block->successors[0] = target_block->successors[1];
  					}
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				}
  			}
  
  			if (last_op->opcode == ZEND_JMPZ || last_op->opcode == ZEND_JMPNZ) {
  				zend_op *target;
  				zend_op *target_end;
  				zend_basic_block *target_block;
  
  				while (1) {
  					target_block = blocks + block->successors[1];
  					target = op_array->opcodes + target_block->start;
  					target_end = op_array->opcodes + target_block->start + 1;
  					while (target < target_end && target->opcode == ZEND_NOP) {
  						target++;
  					}
  
  					/* next block is only NOP's */
  					if (target == target_end && !(target_block->flags & ZEND_BB_PROTECTED)) {
  						DEL_SOURCE(block, block->successors[1]);
  						block->successors[1] = target_block->successors[0];
  						ADD_SOURCE(block, block->successors[1]);
  						++(*opt_count);
  					} else {
  						break;
  					}
  				}
  
  				if (target->opcode == ZEND_JMP &&
  					!(target_block->flags & ZEND_BB_PROTECTED)) {
  
  					if (!(target_block->flags & ZEND_BB_TARGET)) {
  						int next = (target_block - blocks) + 1;
  
  						while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) {
  							/* find used one */
  							next++;
  						}
  						if (next < cfg->blocks_count &&
  						    block->successors[0] == next) {
  							/* JMPZ(X,L1) JMP(L2) L1: -> JMPNZ(X,L2) NOP*/
  
  							last_op->opcode = INV_COND(last_op->opcode);
  
  							DEL_SOURCE(block, block->successors[1]);
  							block->successors[0] = target_block->successors[0];
  							block->successors[1] = next;
  							ADD_SOURCE(block, block->successors[1]);
  
  							target_block->flags &= ~ZEND_BB_REACHABLE;
  							MAKE_NOP(target);
  
  							blocks[next].flags |= ZEND_BB_FOLLOW;
  
  							break;
  						}
  					}
  
  					/* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */
  					DEL_SOURCE(block, block->successors[1]);
  					if (last_op->opcode == ZEND_JMPZ) {
  						block->successors[1] = target_block->successors[0];
  						ADD_SOURCE(block, block->successors[1]);
  					} else {
  						block->successors[1] = block->successors[0];
  						block->successors[0] = target_block->successors[0];
  						ADD_SOURCE(block, block->successors[0]);
  					}
  					last_op->opcode = ZEND_JMPZNZ;
  					++(*opt_count);
  				}
  			}
  			break;
  
  		case ZEND_JMPNZ_EX:
  		case ZEND_JMPZ_EX:
  			/* constant conditional JMPs */
  			if (last_op->op1_type == IS_CONST) {
  				int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(last_op));
  
  				if (last_op->opcode == ZEND_JMPZ_EX) {
  					should_jmp = !should_jmp;
  				}
  				if (!should_jmp) {
  					/* T = JMPZ_EX(true,L)   -> T = QM_ASSIGN(true)
  					 * T = JMPNZ_EX(false,L) -> T = QM_ASSIGN(false)
  					 */
  					last_op->opcode = ZEND_QM_ASSIGN;
  					SET_UNUSED(last_op->op2);
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors_count = 1;
  					block->successors[0] = block->successors[1];
  					++(*opt_count);
  				}
  				break;
  			}
  
  			if (1) {
  				zend_op *target, *target_end;
  				zend_basic_block *target_block;
  				zend_uchar same_type = last_op->op1_type;
  				uint32_t same_var = last_op->op1.var;
  				uint32_t same_res_var = last_op->result.var;
  
  				target_block = blocks + block->successors[0];
  next_target_ex:
  				target = op_array->opcodes + target_block->start;
  				target_end = target + target_block->len;
  				while (target < target_end && target->opcode == ZEND_NOP) {
  					target++;
  				}
   				/* next block is only NOP's */
  				if (target == target_end) {
  					target_block = blocks + target_block->successors[0];
  					++(*opt_count);
  					goto next_target_ex;
  				} else if (target->opcode == last_op->opcode-3 &&
  				           ((same_var == target->op1.var &&
  				             same_type == target->op1_type) ||
  				            (same_res_var  == target->op1.var &&
  				             target->op1_type == IS_TMP_VAR)) &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* T = JMPZ_EX(X, L1), L1: JMPZ({X|T}, L2) -> T = JMPZ_EX(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == INV_EX_COND(last_op->opcode) &&
  				           ((same_var == target->op1.var &&
  				             same_type == target->op1_type) ||
  				            (same_res_var == target->op1.var &&
  				             target->op1_type == IS_TMP_VAR)) &&
  				            !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* T = JMPZ_EX(X, L1), L1: JMPNZ({X|T1}, L2) -> T = JMPZ_EX(X, L1+1) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[1];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == INV_EX_COND_EX(last_op->opcode) &&
  				           same_res_var == target->result.var &&
  				           ((same_var == target->op1.var &&
  				             same_type == target->op1_type) ||
  				            (same_res_var == target->op1.var &&
  				             target->op1_type == IS_TMP_VAR)) &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* T = JMPZ_EX(X, L1), L1: T = JMPNZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L1+1) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[1];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == last_op->opcode &&
  				           same_res_var == target->result.var &&
  				           ((same_var == target->op1.var &&
  				             same_type == target->op1_type) ||
  				            (same_res_var == target->op1.var &&
  				             target->op1_type == IS_TMP_VAR)) &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* T = JMPZ_EX(X, L1), L1: T = JMPZ_EX({X|T}, L2) -> T = JMPZ_EX(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMP &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* T = JMPZ_EX(X, L), L: JMP(L2) -> T = JMPZ(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMPZNZ &&
  				           ((same_var == target->op1.var &&
  				             same_type == target->op1_type) ||
  				            (same_res_var == target->op1.var &&
  				             target->op1_type == IS_TMP_VAR)) &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* T = JMPZ_EX(X, L), L: JMPZNZ({X|T}, L2, L3) -> T = JMPZ_EX(X, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					if (last_op->opcode == ZEND_JMPZ_EX) {
  						block->successors[0] = target_block->successors[0];
  					} else {
  						block->successors[0] = target_block->successors[1];
  					}
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				}
  			}
  			break;
  
  		case ZEND_JMPZNZ: {
  			int next = (block - blocks) + 1;
  
  			while (next < cfg->blocks_count && !(blocks[next].flags & ZEND_BB_REACHABLE)) {
  				/* find first accessed one */
  				next++;
  			}
  
  			if (last_op->op1_type == IS_CONST) {
  				if (!zend_is_true(&ZEND_OP1_LITERAL(last_op))) {
  					/* JMPZNZ(false,L1,L2) -> JMP(L1) */
  					literal_dtor(&ZEND_OP1_LITERAL(last_op));
  					last_op->opcode = ZEND_JMP;
  					SET_UNUSED(last_op->op1);
  					SET_UNUSED(last_op->op2);
  					DEL_SOURCE(block, block->successors[1]);
  					block->successors_count = 1;
  				} else {
  					/* JMPZNZ(true,L1,L2) -> JMP(L2) */
  					literal_dtor(&ZEND_OP1_LITERAL(last_op));
  					last_op->opcode = ZEND_JMP;
  					SET_UNUSED(last_op->op1);
  					SET_UNUSED(last_op->op2);
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors_count = 1;
  					block->successors[0] = block->successors[1];
  				}
  				++(*opt_count);
  			} else if (block->successors[0] == block->successors[1]) {
  				/* both goto the same one - it's JMP */
  				if (!(last_op->op1_type & (IS_VAR|IS_TMP_VAR))) {
  					/* JMPZNZ(?,L,L) -> JMP(L) */
  					last_op->opcode = ZEND_JMP;
  					SET_UNUSED(last_op->op1);
  					SET_UNUSED(last_op->op2);
  					block->successors_count = 1;
  					++(*opt_count);
  				}
  			} else if (block->successors[0] == next) {
  				/* jumping to next on Z - can follow to it and jump only on NZ */
  				/* JMPZNZ(X,L1,L2) L1: -> JMPNZ(X,L2) */
  				last_op->opcode = ZEND_JMPNZ;
  				block->successors[0] = block->successors[1];
  				block->successors[1] = next;
  				++(*opt_count);
  				/* no need to add source */
  			} else if (block->successors[1] == next) {
  				/* jumping to next on NZ - can follow to it and jump only on Z */
  				/* JMPZNZ(X,L1,L2) L2: -> JMPZ(X,L1) */
  				last_op->opcode = ZEND_JMPZ;
  				++(*opt_count);
  				/* no need to add source */
  			}
  
  			if (last_op->opcode == ZEND_JMPZNZ) {
  				zend_uchar same_type = last_op->op1_type;
  				zend_uchar same_var = last_op->op1.var;
  				zend_op *target;
  				zend_op *target_end;
  				zend_basic_block *target_block = blocks + block->successors[0];
  
  next_target_znz:
  				target = op_array->opcodes + target_block->start;
  				target_end = target + target_block->len;
  				while (target < target_end && target->opcode == ZEND_NOP) {
  					target++;
  				}
  				/* next block is only NOP's */
  				if (target == target_end) {
  					target_block = blocks + target_block->successors[0];
  					++(*opt_count);
  					goto next_target_znz;
  				} else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
  				           same_var == target->op1.var &&
  				           same_type == target->op1_type &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  				    /* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMPNZ &&
  				           same_var == target->op1.var &&
  				           same_type == target->op1_type &&
  				           !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[1];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				} else if (target->opcode == ZEND_JMP &&
  					       !(target_block->flags & ZEND_BB_PROTECTED)) {
  					/* JMPZNZ(X, L1, L2), L1: JMP(L3) -> JMPZNZ(X, L3, L2) */
  					DEL_SOURCE(block, block->successors[0]);
  					block->successors[0] = target_block->successors[0];
  					ADD_SOURCE(block, block->successors[0]);
  					++(*opt_count);
  				}
  			}
  			break;
  		}
  	}
  }
  
  /* Global data dependencies */
  
  /* Find a set of variables which are used outside of the block where they are
   * defined. We won't apply some optimization patterns for such variables. */
  static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx)
  {
  	int n;
  	zend_basic_block *block, *next_block;
  	uint32_t var_num;
  	uint32_t bitset_len;
  	zend_bitset usage;
  	zend_bitset defined_here;
  	void *checkpoint;
  	zend_op *opline, *end;
  
  
  	if (op_array->T == 0) {
  		/* shortcut - if no Ts, nothing to do */
  		return;
  	}
  
  	checkpoint = zend_arena_checkpoint(ctx->arena);
  	bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
  	defined_here = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
  
  	zend_bitset_clear(defined_here, bitset_len);
  	for (n = 1; n < cfg->blocks_count; n++) {
  		block = cfg->blocks + n;
  
  		if (!(block->flags & ZEND_BB_REACHABLE)) {
  			continue;
  		}
  
  		opline = op_array->opcodes + block->start;
  		end = opline + block->len;
  		if (!(block->flags & ZEND_BB_FOLLOW) ||
  		    (block->flags & ZEND_BB_TARGET)) {
  			/* Skip continuation of "extended" BB */
  			zend_bitset_clear(defined_here, bitset_len);
  		}
  
  		while (opline<end) {
  			if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
  				var_num = VAR_NUM(opline->op1.var);
  				if (!zend_bitset_in(defined_here, var_num)) {
  					zend_bitset_incl(used_ext, var_num);
  				}
  			}
  			if (opline->op2_type == IS_VAR) {
  				var_num = VAR_NUM(opline->op2.var);
  				if (opline->opcode == ZEND_FE_FETCH_R ||
  				    opline->opcode == ZEND_FE_FETCH_RW) {
  					/* these opcode use the op2 as result */
  					zend_bitset_incl(defined_here, var_num);
  				} else if (!zend_bitset_in(defined_here, var_num)) {
  					zend_bitset_incl(used_ext, var_num);
  				}
  			} else if (opline->op2_type == IS_TMP_VAR) {
  				var_num = VAR_NUM(opline->op2.var);
  				if (!zend_bitset_in(defined_here, var_num)) {
  					zend_bitset_incl(used_ext, var_num);
  				}
  			}
  
  			if (opline->result_type == IS_VAR) {
  				var_num = VAR_NUM(opline->result.var);
  				zend_bitset_incl(defined_here, var_num);
  			} else if (opline->result_type == IS_TMP_VAR) {
  				var_num = VAR_NUM(opline->result.var);
  				switch (opline->opcode) {
  					case ZEND_ADD_ARRAY_ELEMENT:
  					case ZEND_ADD_ARRAY_UNPACK:
  					case ZEND_ROPE_ADD:
  						/* these opcodes use the result as argument */
  						if (!zend_bitset_in(defined_here, var_num)) {
  							zend_bitset_incl(used_ext, var_num);
  						}
  						break;
  					default :
  						zend_bitset_incl(defined_here, var_num);
  				}
  			}
  			opline++;
  		}
  	}
  
  	if (ctx->debug_level & ZEND_DUMP_BLOCK_PASS_VARS) {
  		int printed = 0;
  		uint32_t i;
  
  		for (i = op_array->last_var; i< op_array->T; i++) {
  			if (zend_bitset_in(used_ext, i)) {
  				if (!printed) {
  					fprintf(stderr, "NON-LOCAL-VARS: %d", i);
  					printed = 1;
  				} else {
  					fprintf(stderr, ", %d", i);
  				}
  			}
  		}
  		if (printed) {
  			fprintf(stderr, "\n");
  		}
  	}
  
  	usage = defined_here;
  	next_block = NULL;
  	for (n = cfg->blocks_count; n > 0;) {
  		block = cfg->blocks + (--n);
  
  		if (!(block->flags & ZEND_BB_REACHABLE) || block->len == 0) {
  			continue;
  		}
  
  		end = op_array->opcodes + block->start;
  		opline = end + block->len - 1;
  		if (!next_block ||
  		    !(next_block->flags & ZEND_BB_FOLLOW) ||
  		    (next_block->flags & ZEND_BB_TARGET)) {
  			/* Skip continuation of "extended" BB */
  			zend_bitset_copy(usage, used_ext, bitset_len);
  		} else if (block->successors_count > 1) {
  			zend_bitset_union(usage, used_ext, bitset_len);
  		}
  		next_block = block;
  
  		while (opline >= end) {
  			/* usage checks */
  			if (opline->result_type == IS_VAR) {
  				if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) {
  					switch (opline->opcode) {
  						case ZEND_ASSIGN_OP:
  						case ZEND_ASSIGN_DIM_OP:
  						case ZEND_ASSIGN_OBJ_OP:
  						case ZEND_ASSIGN_STATIC_PROP_OP:
  						case ZEND_PRE_INC:
  						case ZEND_PRE_DEC:
  						case ZEND_ASSIGN:
  						case ZEND_ASSIGN_REF:
  						case ZEND_DO_FCALL:
  						case ZEND_DO_ICALL:
  						case ZEND_DO_UCALL:
  						case ZEND_DO_FCALL_BY_NAME:
  							opline->result_type = IS_UNUSED;
  							break;
  					}
  				} else {
  					zend_bitset_excl(usage, VAR_NUM(opline->result.var));
  				}
  			} else if (opline->result_type == IS_TMP_VAR) {
  				if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) {
  					switch (opline->opcode) {
  						case ZEND_POST_INC:
  						case ZEND_POST_DEC:
  						case ZEND_POST_INC_OBJ:
  						case ZEND_POST_DEC_OBJ:
  						case ZEND_POST_INC_STATIC_PROP:
  						case ZEND_POST_DEC_STATIC_PROP:
  							opline->opcode -= 2;
  							opline->result_type = IS_UNUSED;
  							break;
  						case ZEND_QM_ASSIGN:
  						case ZEND_BOOL:
  						case ZEND_BOOL_NOT:
  							if (opline->op1_type == IS_CV) {
  								opline->opcode = ZEND_CHECK_VAR;
  								SET_UNUSED(opline->result);
  							} else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
  								opline->opcode = ZEND_FREE;
  								SET_UNUSED(opline->result);
  							} else {
  								if (opline->op1_type == IS_CONST) {
  									literal_dtor(&ZEND_OP1_LITERAL(opline));
  								}
  								MAKE_NOP(opline);
  							}
  							break;
  						case ZEND_JMPZ_EX:
  						case ZEND_JMPNZ_EX:
  							opline->opcode -= 3;
  							SET_UNUSED(opline->result);
  							break;
  						case ZEND_ADD_ARRAY_ELEMENT:
  						case ZEND_ADD_ARRAY_UNPACK:
  						case ZEND_ROPE_ADD:
  							zend_bitset_incl(usage, VAR_NUM(opline->result.var));
  							break;
  					}
  				} else {
  					switch (opline->opcode) {
  						case ZEND_ADD_ARRAY_ELEMENT:
  						case ZEND_ADD_ARRAY_UNPACK:
  						case ZEND_ROPE_ADD:
  							break;
  						default:
  							zend_bitset_excl(usage, VAR_NUM(opline->result.var));
  							break;
  					}
  				}
  			}
  
  			if (opline->op2_type == IS_VAR) {
  				switch (opline->opcode) {
  					case ZEND_FE_FETCH_R:
  					case ZEND_FE_FETCH_RW:
  						zend_bitset_excl(usage, VAR_NUM(opline->op2.var));
  						break;
  					default:
  						zend_bitset_incl(usage, VAR_NUM(opline->op2.var));
  						break;
  				}
  			} else if (opline->op2_type == IS_TMP_VAR) {
  				zend_bitset_incl(usage, VAR_NUM(opline->op2.var));
  			}
  
  			if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
  				zend_bitset_incl(usage, VAR_NUM(opline->op1.var));
  			}
  
  			opline--;
  		}
  	}
  
  	zend_arena_release(&ctx->arena, checkpoint);
  }
  
  static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg, uint32_t *opt_count)
  {
  	int i;
  	zend_basic_block *b, *bb;
  	zend_basic_block *prev = NULL;
  
  	for (i = 0; i < cfg->blocks_count; i++) {
  		b = cfg->blocks + i;
  		if (b->flags & ZEND_BB_REACHABLE) {
  			if ((b->flags & ZEND_BB_FOLLOW) &&
  			    !(b->flags & (ZEND_BB_TARGET | ZEND_BB_PROTECTED)) &&
  			    prev && prev->successors_count == 1 && prev->successors[0] == i)
  			{
  				zend_op *last_op = op_array->opcodes + prev->start + prev->len - 1;
  				if (prev->len != 0 && last_op->opcode == ZEND_JMP) {
  					MAKE_NOP(last_op);
  				}
  
  				for (bb = prev + 1; bb != b; bb++) {
  					zend_op *op = op_array->opcodes + bb->start;
  					zend_op *end = op + bb->len;
  					while (op < end) {
  						if (op->op1_type == IS_CONST) {
  							literal_dtor(&ZEND_OP1_LITERAL(op));
  						}
  						if (op->op2_type == IS_CONST) {
  							literal_dtor(&ZEND_OP2_LITERAL(op));
  						}
  						MAKE_NOP(op);
  						op++;
  					}
  					/* make block empty */
  					bb->len = 0;
  				}
  
  				/* re-link */
  				prev->flags |= (b->flags & ZEND_BB_EXIT);
  				prev->len = b->start + b->len - prev->start;
  				prev->successors_count = b->successors_count;
  				if (b->successors != b->successors_storage) {
  					prev->successors = b->successors;
  					b->successors = b->successors_storage;
  				} else {
  					memcpy(prev->successors, b->successors, b->successors_count * sizeof(int));
  				}
  
  				/* unlink & make block empty and unreachable */
  				b->flags = 0;
  				b->len = 0;
  				b->successors_count = 0;
  				++(*opt_count);
  			} else {
  				prev = b;
  			}
  		}
  	}
  }
  
  #define PASSES 3
  
  void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
  {
  	zend_cfg cfg;
  	zend_basic_block *blocks, *end, *b;
  	int pass;
  	uint32_t bitset_len;
  	zend_bitset usage;
  	void *checkpoint;
  	zend_op **Tsource;
  	uint32_t opt_count;
  
      /* Build CFG */
  	checkpoint = zend_arena_checkpoint(ctx->arena);
  	if (zend_build_cfg(&ctx->arena, op_array, 0, &cfg) != SUCCESS) {
  		zend_arena_release(&ctx->arena, checkpoint);
  		return;
  	}
  
  	if (cfg.blocks_count * (op_array->last_var + op_array->T) > 64 * 1024 * 1024) {
  		zend_arena_release(&ctx->arena, checkpoint);
  		return;
  	}
  
  	if (ctx->debug_level & ZEND_DUMP_BEFORE_BLOCK_PASS) {
  		zend_dump_op_array(op_array, ZEND_DUMP_CFG, "before block pass", &cfg);
  	}
  
  	bitset_len = zend_bitset_len(op_array->last_var + op_array->T);
  	Tsource = zend_arena_calloc(&ctx->arena, op_array->last_var + op_array->T, sizeof(zend_op *));
  	usage = zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE);
  
  	blocks = cfg.blocks;
  	end = blocks + cfg.blocks_count;
  	for (pass = 0; pass < PASSES; pass++) {
  		opt_count = 0;
  
  		/* Compute data dependencies */
  		zend_bitset_clear(usage, bitset_len);
  		zend_t_usage(&cfg, op_array, usage, ctx);
  
  		/* optimize each basic block separately */
  		for (b = blocks; b < end; b++) {
  			if (!(b->flags & ZEND_BB_REACHABLE)) {
  				continue;
  			}
  			/* we track data dependencies only insight a single basic block */
  			if (!(b->flags & ZEND_BB_FOLLOW) ||
  			    (b->flags & ZEND_BB_TARGET)) {
  				/* Skip continuation of "extended" BB */
  				memset(Tsource, 0, (op_array->last_var + op_array->T) * sizeof(zend_op *));
  			}
  			zend_optimize_block(b, op_array, usage, &cfg, Tsource, &opt_count);
  		}
  
  		/* Eliminate NOPs */
  		for (b = blocks; b < end; b++) {
  			if (b->flags & ZEND_BB_REACHABLE) {
  				strip_nops(op_array, b);
  			}
  		}
  
  		/* Jump optimization for each block */
  		for (b = blocks; b < end; b++) {
  			if (b->flags & ZEND_BB_REACHABLE) {
  				zend_jmp_optimization(b, op_array, &cfg, &opt_count);
  			}
  		}
  
  		/* Eliminate unreachable basic blocks */
  		zend_cfg_remark_reachable_blocks(op_array, &cfg);
  
  		/* Merge Blocks */
  		zend_merge_blocks(op_array, &cfg, &opt_count);
  
  		if (opt_count == 0) {
  			break;
  		}
  	}
  
  	zend_bitset_clear(usage, bitset_len);
  	zend_t_usage(&cfg, op_array, usage, ctx);
  	assemble_code_blocks(&cfg, op_array, ctx);
  
  	if (ctx->debug_level & ZEND_DUMP_AFTER_BLOCK_PASS) {
  		zend_dump_op_array(op_array, ZEND_DUMP_CFG | ZEND_DUMP_HIDE_UNREACHABLE, "after block pass", &cfg);
  	}
  
  	/* Destroy CFG */
  	zend_arena_release(&ctx->arena, checkpoint);
  }
  