/* pass 2:
 * - convert non-numeric constants to numeric constants in numeric operators
 * - optimize constant conditional JMPs
 * - optimize static BRKs and CONTs
 */

if (ZEND_OPTIMIZER_PASS_2 & OPTIMIZATION_LEVEL) {
	zend_op *opline;
	zend_op *end = op_array->opcodes + op_array->last;

	opline = op_array->opcodes;
	while (opline < end) {
		switch (opline->opcode) {
			case ZEND_ADD:
			case ZEND_SUB:
			case ZEND_MUL:
			case ZEND_DIV:
				if (ZEND_OP1_TYPE(opline) == IS_CONST) {
					if (ZEND_OP1_LITERAL(opline).type == IS_STRING) {
						convert_scalar_to_number(&ZEND_OP1_LITERAL(opline) TSRMLS_CC);
					}
				}
				/* break missing *intentionally* - the assign_op's may only optimize op2 */
			case ZEND_ASSIGN_ADD:
			case ZEND_ASSIGN_SUB:
			case ZEND_ASSIGN_MUL:
			case ZEND_ASSIGN_DIV:
				if (opline->extended_value != 0) {
					/* object tristate op - don't attempt to optimize it! */
					break;
				}
				if (ZEND_OP2_TYPE(opline) == IS_CONST) {
					if (ZEND_OP2_LITERAL(opline).type == IS_STRING) {
						convert_scalar_to_number(&ZEND_OP2_LITERAL(opline) TSRMLS_CC);
					}
				}
				break;

			case ZEND_MOD:
			case ZEND_SL:
			case ZEND_SR:
				if (ZEND_OP1_TYPE(opline) == IS_CONST) {
					if (ZEND_OP1_LITERAL(opline).type != IS_LONG) {
						convert_to_long(&ZEND_OP1_LITERAL(opline));
					}
				}
				/* break missing *intentionally - the assign_op's may only optimize op2 */
			case ZEND_ASSIGN_MOD:
			case ZEND_ASSIGN_SL:
			case ZEND_ASSIGN_SR:
				if (opline->extended_value != 0) {
					/* object tristate op - don't attempt to optimize it! */
					break;
				}
				if (ZEND_OP2_TYPE(opline) == IS_CONST) {
					if (ZEND_OP2_LITERAL(opline).type != IS_LONG) {
						convert_to_long(&ZEND_OP2_LITERAL(opline));
					}
				}
				break;

			case ZEND_CONCAT:
				if (ZEND_OP1_TYPE(opline) == IS_CONST) {
					if (ZEND_OP1_LITERAL(opline).type != IS_STRING) {
						convert_to_string(&ZEND_OP1_LITERAL(opline));
					}
				}
				/* break missing *intentionally - the assign_op's may only optimize op2 */
			case ZEND_ASSIGN_CONCAT:
				if (opline->extended_value != 0) {
					/* object tristate op - don't attempt to optimize it! */
					break;
				}
				if (ZEND_OP2_TYPE(opline) == IS_CONST) {
					if (ZEND_OP2_LITERAL(opline).type != IS_STRING) {
						convert_to_string(&ZEND_OP2_LITERAL(opline));
					}
				}
				break;

			case ZEND_JMPZ_EX:
			case ZEND_JMPNZ_EX:
				/* convert Ti = JMPZ_EX(Ti, L) to JMPZ(Ti, L) */
				if (0 && /* FIXME: temporary disable unsafe pattern */
				    ZEND_OP1_TYPE(opline) == IS_TMP_VAR &&
				    ZEND_RESULT_TYPE(opline) == IS_TMP_VAR &&
				    ZEND_OP1(opline).var == ZEND_RESULT(opline).var) {
					opline->opcode -= 3;
				/* convert Ti = JMPZ_EX(C, L) => Ti = QM_ASSIGN(C)
				   in case we know it wouldn't jump */
				} else if (ZEND_OP1_TYPE(opline) == IS_CONST) {
#if ZEND_EXTENSION_API_NO > PHP_5_6_X_API_NO
					int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline) TSRMLS_CC);
#else
					int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline));
#endif
					if (opline->opcode == ZEND_JMPZ_EX) {
						should_jmp = !should_jmp;
					}
					if (!should_jmp) {
						opline->opcode = ZEND_QM_ASSIGN;
						SET_UNUSED(opline->op2);
					}
				}
				break;

			case ZEND_JMPZ:
			case ZEND_JMPNZ:
				if (ZEND_OP1_TYPE(opline) == IS_CONST) {
#if ZEND_EXTENSION_API_NO > PHP_5_6_X_API_NO
					int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline) TSRMLS_CC);
#else
					int should_jmp = zend_is_true(&ZEND_OP1_LITERAL(opline));
#endif

					if (opline->opcode == ZEND_JMPZ) {
						should_jmp = !should_jmp;
					}
					literal_dtor(&ZEND_OP1_LITERAL(opline));
					ZEND_OP1_TYPE(opline) = IS_UNUSED;
					if (should_jmp) {
						opline->opcode = ZEND_JMP;
						COPY_NODE(opline->op1, opline->op2);
					} else {
						MAKE_NOP(opline);
					}
					break;
				}
				if ((opline + 1)->opcode == ZEND_JMP) {
					/* JMPZ(X, L1), JMP(L2) => JMPZNZ(X, L1, L2) */
					/* JMPNZ(X, L1), JMP(L2) => JMPZNZ(X, L2, L1) */
					if (ZEND_OP2(opline).opline_num == ZEND_OP1(opline + 1).opline_num) {
						/* JMPZ(X, L1), JMP(L1) => NOP, JMP(L1) */
						MAKE_NOP(opline);
					} else {
						if (opline->opcode == ZEND_JMPZ) {
							opline->extended_value = ZEND_OP1(opline + 1).opline_num;
						} else {
							opline->extended_value = ZEND_OP2(opline).opline_num;
							COPY_NODE(opline->op2, (opline + 1)->op1);
						}
						opline->opcode = ZEND_JMPZNZ;
					}
				}
				break;

			case ZEND_JMPZNZ:
				if (ZEND_OP1_TYPE(opline) == IS_CONST) {
					int opline_num;
#if ZEND_EXTENSION_API_NO > PHP_5_6_X_API_NO
					if (zend_is_true(&ZEND_OP1_LITERAL(opline) TSRMLS_CC)) {
#else
					if (zend_is_true(&ZEND_OP1_LITERAL(opline))) {
#endif
						opline_num = opline->extended_value; /* JMPNZ */
					} else {
						opline_num = ZEND_OP2(opline).opline_num; /* JMPZ */
					}
					literal_dtor(&ZEND_OP1_LITERAL(opline));
					ZEND_OP1(opline).opline_num = opline_num;
					ZEND_OP1_TYPE(opline) = IS_UNUSED;
					opline->opcode = ZEND_JMP;
				}
				break;

			case ZEND_BRK:
			case ZEND_CONT:
				{
				    zend_brk_cont_element *jmp_to;
					int array_offset;
					int nest_levels;
					int dont_optimize = 0;

					if (ZEND_OP2_TYPE(opline) != IS_CONST) {
						break;
					}
					convert_to_long(&ZEND_OP2_LITERAL(opline));
					nest_levels = ZEND_OP2_LITERAL(opline).value.lval;

					array_offset = ZEND_OP1(opline).opline_num;
					while (1) {
						if (array_offset == -1) {
							dont_optimize = 1; /* don't optimize this bogus break/continue, let the executor shout */
							break;
						}
						jmp_to = &op_array->brk_cont_array[array_offset];
						array_offset = jmp_to->parent;
						if (--nest_levels > 0) {
							if (opline->opcode == ZEND_BRK &&
							    (op_array->opcodes[jmp_to->brk].opcode == ZEND_FREE ||
							     op_array->opcodes[jmp_to->brk].opcode == ZEND_SWITCH_FREE)) {
								dont_optimize = 1;
								break;
							}
						} else {
							break;
						}
					}

					if (dont_optimize) {
						break;
					}

					/* optimize - convert to a JMP */
					switch (opline->opcode) {
						case ZEND_BRK:
							MAKE_NOP(opline);
							ZEND_OP1(opline).opline_num = jmp_to->brk;
							break;
						case ZEND_CONT:
							MAKE_NOP(opline);
							ZEND_OP1(opline).opline_num = jmp_to->cont;
							break;
					}
					opline->opcode = ZEND_JMP;
					/* MAKE_NOP() already set op1 and op2 to IS_UNUSED */
				}
				break;
		}
		opline++;
	}
}
