]> granicus.if.org Git - php/commitdiff
Tracing JIT (it doesn't support register allocation yet)
authorDmitry Stogov <dmitry@zend.com>
Fri, 13 Mar 2020 19:11:07 +0000 (22:11 +0300)
committerDmitry Stogov <dmitry@zend.com>
Fri, 13 Mar 2020 19:11:07 +0000 (22:11 +0300)
Use opcache.jit=1255 to swith it on (the third digit 5 really matters)
Use opcache.jit_debug=0xff001 to see how it works and what code it generates

17 files changed:
ext/opcache/Optimizer/zend_dfg.c
ext/opcache/Optimizer/zend_dfg.h
ext/opcache/Optimizer/zend_dump.c
ext/opcache/Optimizer/zend_dump.h
ext/opcache/Optimizer/zend_inference.c
ext/opcache/Optimizer/zend_inference.h
ext/opcache/Optimizer/zend_ssa.c
ext/opcache/Optimizer/zend_ssa.h
ext/opcache/jit/Makefile.frag
ext/opcache/jit/Makefile.frag.w32
ext/opcache/jit/zend_jit.c
ext/opcache/jit/zend_jit.h
ext/opcache/jit/zend_jit_disasm_x86.c
ext/opcache/jit/zend_jit_internal.h
ext/opcache/jit/zend_jit_trace.c [new file with mode: 0644]
ext/opcache/jit/zend_jit_vm_helpers.c
ext/opcache/jit/zend_jit_x86.dasc

index 3bb76fb05c747417f70a1010a9437df97479a998..fda6b4f9a89f68a3aa5b0f2a1fd1eee711ebb7bf 100644 (file)
 #include "zend_compile.h"
 #include "zend_dfg.h"
 
+static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_array, const zend_op *opline, uint32_t build_flags, zend_bitset use, zend_bitset def) /* {{{ */
+{
+       uint32_t var_num;
+       const zend_op *next;
+
+       if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+               var_num = EX_VAR_TO_NUM(opline->op1.var);
+               if (!zend_bitset_in(def, var_num)) {
+                       zend_bitset_incl(use, var_num);
+               }
+       }
+       if (((opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
+         && opline->opcode != ZEND_FE_FETCH_R
+         && opline->opcode != ZEND_FE_FETCH_RW)
+        || (opline->op2_type == IS_CV)) {
+               var_num = EX_VAR_TO_NUM(opline->op2.var);
+               if (!zend_bitset_in(def, var_num)) {
+                       zend_bitset_incl(use, var_num);
+               }
+       }
+       if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
+        && opline->result_type == IS_CV
+        && opline->opcode != ZEND_RECV) {
+               var_num = EX_VAR_TO_NUM(opline->result.var);
+               if (!zend_bitset_in(def, var_num)) {
+                       zend_bitset_incl(use, var_num);
+               }
+       }
+
+       switch (opline->opcode) {
+               case ZEND_ASSIGN:
+                       if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
+                               zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+                       }
+                       if (opline->op1_type == IS_CV) {
+add_op1_def:
+                               zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op1.var));
+                       }
+                       break;
+               case ZEND_ASSIGN_REF:
+                       if (opline->op2_type == IS_CV) {
+                               zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_DIM:
+               case ZEND_ASSIGN_OBJ:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               var_num = EX_VAR_TO_NUM(next->op1.var);
+                               if (!zend_bitset_in(def, var_num)) {
+                                       zend_bitset_incl(use, var_num);
+                               }
+                               if (build_flags & ZEND_SSA_RC_INFERENCE && next->op1_type == IS_CV) {
+                                       zend_bitset_incl(def, var_num);
+                               }
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_OBJ_REF:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               var_num = EX_VAR_TO_NUM(next->op1.var);
+                               if (!zend_bitset_in(def, var_num)) {
+                                       zend_bitset_incl(use, var_num);
+                               }
+                               if (next->op1_type == IS_CV) {
+                                       zend_bitset_incl(def, var_num);
+                               }
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_STATIC_PROP:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               var_num = EX_VAR_TO_NUM(next->op1.var);
+                               if (!zend_bitset_in(def, var_num)) {
+                                       zend_bitset_incl(use, var_num);
+                               }
+#if 0
+                               if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
+                                       zend_bitset_incl(def, var_num);
+                               }
+#endif
+                       }
+                       break;
+               case ZEND_ASSIGN_STATIC_PROP_REF:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               var_num = EX_VAR_TO_NUM(next->op1.var);
+                               if (!zend_bitset_in(def, var_num)) {
+                                       zend_bitset_incl(use, var_num);
+                               }
+                               if (next->op1_type == IS_CV) {
+                                       zend_bitset_incl(def, var_num);
+                               }
+                       }
+                       break;
+               case ZEND_ASSIGN_STATIC_PROP_OP:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               var_num = EX_VAR_TO_NUM(next->op1.var);
+                               if (!zend_bitset_in(def, var_num)) {
+                                       zend_bitset_incl(use, var_num);
+                               }
+                       }
+                       break;
+               case ZEND_ASSIGN_DIM_OP:
+               case ZEND_ASSIGN_OBJ_OP:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               var_num = EX_VAR_TO_NUM(next->op1.var);
+                               if (!zend_bitset_in(def, var_num)) {
+                                       zend_bitset_incl(use, var_num);
+                               }
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_OP:
+               case ZEND_PRE_INC:
+               case ZEND_PRE_DEC:
+               case ZEND_POST_INC:
+               case ZEND_POST_DEC:
+               case ZEND_BIND_GLOBAL:
+               case ZEND_BIND_STATIC:
+               case ZEND_SEND_VAR_NO_REF:
+               case ZEND_SEND_VAR_NO_REF_EX:
+               case ZEND_SEND_VAR_EX:
+               case ZEND_SEND_FUNC_ARG:
+               case ZEND_SEND_REF:
+               case ZEND_SEND_UNPACK:
+               case ZEND_FE_RESET_RW:
+               case ZEND_MAKE_REF:
+               case ZEND_PRE_INC_OBJ:
+               case ZEND_PRE_DEC_OBJ:
+               case ZEND_POST_INC_OBJ:
+               case ZEND_POST_DEC_OBJ:
+               case ZEND_UNSET_DIM:
+               case ZEND_UNSET_OBJ:
+               case ZEND_FETCH_DIM_W:
+               case ZEND_FETCH_DIM_RW:
+               case ZEND_FETCH_DIM_FUNC_ARG:
+               case ZEND_FETCH_DIM_UNSET:
+               case ZEND_FETCH_LIST_W:
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_SEND_VAR:
+               case ZEND_CAST:
+               case ZEND_QM_ASSIGN:
+               case ZEND_JMP_SET:
+               case ZEND_COALESCE:
+               case ZEND_FE_RESET_R:
+                       if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ADD_ARRAY_UNPACK:
+                       var_num = EX_VAR_TO_NUM(opline->result.var);
+                       if (!zend_bitset_in(def, var_num)) {
+                               zend_bitset_incl(use, var_num);
+                       }
+                       break;
+               case ZEND_ADD_ARRAY_ELEMENT:
+                       var_num = EX_VAR_TO_NUM(opline->result.var);
+                       if (!zend_bitset_in(def, var_num)) {
+                               zend_bitset_incl(use, var_num);
+                       }
+                       /* break missing intentionally */
+               case ZEND_INIT_ARRAY:
+                       if (((build_flags & ZEND_SSA_RC_INFERENCE)
+                                               || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF))
+                                       && opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_YIELD:
+                       if (opline->op1_type == IS_CV
+                                       && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
+                                               || (build_flags & ZEND_SSA_RC_INFERENCE))) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_UNSET_CV:
+                       goto add_op1_def;
+               case ZEND_VERIFY_RETURN_TYPE:
+                       if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_FE_FETCH_R:
+               case ZEND_FE_FETCH_RW:
+#if 0
+                       /* This special case was handled above the switch */
+                       if (opline->op2_type != IS_CV) {
+                               op2_use = -1; /* not used */
+                       }
+#endif
+                       zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+                       break;
+               case ZEND_BIND_LEXICAL:
+                       if ((opline->extended_value & ZEND_BIND_REF) || (build_flags & ZEND_SSA_RC_INFERENCE)) {
+                               zend_bitset_incl(def, EX_VAR_TO_NUM(opline->op2.var));
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+               zend_bitset_incl(def, EX_VAR_TO_NUM(opline->result.var));
+       }
+}
+/* }}} */
+
+void zend_dfg_add_use_def_op(const zend_op_array *op_array, const zend_op *opline, uint32_t build_flags, zend_bitset use, zend_bitset def) /* {{{ */
+{
+       _zend_dfg_add_use_def_op(op_array, opline, build_flags, use, def);
+}
+/* }}} */
+
 int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg *dfg, uint32_t build_flags) /* {{{ */
 {
        int set_size;
@@ -27,7 +257,6 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg
        int blocks_count = cfg->blocks_count;
        zend_bitset tmp, def, use, in, out;
        int k;
-       uint32_t var_num;
        int j;
 
        set_size = dfg->size;
@@ -40,162 +269,19 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg
        /* Collect "def" and "use" sets */
        for (j = 0; j < blocks_count; j++) {
                zend_op *opline, *end;
+               zend_bitset b_use, b_def;
+
                if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
                        continue;
                }
 
                opline = op_array->opcodes + blocks[j].start;
                end = opline + blocks[j].len;
+               b_use = DFG_BITSET(use, set_size, j);
+               b_def = DFG_BITSET(def, set_size, j);
                for (; opline < end; opline++) {
                        if (opline->opcode != ZEND_OP_DATA) {
-                               zend_op *next = opline + 1;
-                               if (next < end && next->opcode == ZEND_OP_DATA) {
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               var_num = EX_VAR_TO_NUM(next->op1.var);
-                                               if (next->op1_type == IS_CV && (opline->opcode == ZEND_ASSIGN_OBJ_REF
-                                                               || opline->opcode == ZEND_ASSIGN_STATIC_PROP_REF)) {
-                                                       DFG_SET(use, set_size, j, var_num);
-                                                       DFG_SET(def, set_size, j, var_num);
-                                               } else {
-                                                       if (!DFG_ISSET(def, set_size, j, var_num)) {
-                                                               DFG_SET(use, set_size, j, var_num);
-                                                       }
-                                               }
-                                       }
-                                       if (next->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               var_num = EX_VAR_TO_NUM(next->op2.var);
-                                               if (!DFG_ISSET(def, set_size, j, var_num)) {
-                                                       DFG_SET(use, set_size, j, var_num);
-                                               }
-                                       }
-                               }
-                               if (opline->op1_type == IS_CV) {
-                                       var_num = EX_VAR_TO_NUM(opline->op1.var);
-                                       switch (opline->opcode) {
-                                       case ZEND_ADD_ARRAY_ELEMENT:
-                                       case ZEND_INIT_ARRAY:
-                                               if ((build_flags & ZEND_SSA_RC_INFERENCE)
-                                                               || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) {
-                                                       goto op1_def;
-                                               }
-                                               goto op1_use;
-                                       case ZEND_FE_RESET_R:
-                                       case ZEND_SEND_VAR:
-                                       case ZEND_CAST:
-                                       case ZEND_QM_ASSIGN:
-                                       case ZEND_JMP_SET:
-                                       case ZEND_COALESCE:
-                                               if (build_flags & ZEND_SSA_RC_INFERENCE) {
-                                                       goto op1_def;
-                                               }
-                                               goto op1_use;
-                                       case ZEND_YIELD:
-                                               if ((build_flags & ZEND_SSA_RC_INFERENCE)
-                                                               || (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
-                                                       goto op1_def;
-                                               }
-                                               goto op1_use;
-                                       case ZEND_UNSET_CV:
-                                       case ZEND_ASSIGN:
-                                       case ZEND_ASSIGN_REF:
-                                       case ZEND_ASSIGN_OBJ_REF:
-                                       case ZEND_BIND_GLOBAL:
-                                       case ZEND_BIND_STATIC:
-                                       case ZEND_SEND_VAR_EX:
-                                       case ZEND_SEND_FUNC_ARG:
-                                       case ZEND_SEND_REF:
-                                       case ZEND_SEND_VAR_NO_REF:
-                                       case ZEND_SEND_VAR_NO_REF_EX:
-                                       case ZEND_FE_RESET_RW:
-                                       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_POST_INC:
-                                       case ZEND_POST_DEC:
-                                       case ZEND_ASSIGN_DIM:
-                                       case ZEND_ASSIGN_OBJ:
-                                       case ZEND_UNSET_DIM:
-                                       case ZEND_UNSET_OBJ:
-                                       case ZEND_FETCH_DIM_W:
-                                       case ZEND_FETCH_DIM_RW:
-                                       case ZEND_FETCH_DIM_FUNC_ARG:
-                                       case ZEND_FETCH_DIM_UNSET:
-                                       case ZEND_FETCH_LIST_W:
-                                       case ZEND_VERIFY_RETURN_TYPE:
-                                       case ZEND_PRE_INC_OBJ:
-                                       case ZEND_PRE_DEC_OBJ:
-                                       case ZEND_POST_INC_OBJ:
-                                       case ZEND_POST_DEC_OBJ:
-op1_def:
-                                               /* `def` always come along with dtor or separation,
-                                                * thus the origin var info might be also `use`d in the feature(CG) */
-                                               DFG_SET(use, set_size, j, var_num);
-                                               DFG_SET(def, set_size, j, var_num);
-                                               break;
-                                       default:
-op1_use:
-                                               if (!DFG_ISSET(def, set_size, j, var_num)) {
-                                                       DFG_SET(use, set_size, j, var_num);
-                                               }
-                                       }
-                               } else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
-                                       var_num = EX_VAR_TO_NUM(opline->op1.var);
-                                       if (!DFG_ISSET(def, set_size, j, var_num)) {
-                                               DFG_SET(use, set_size, j, var_num);
-                                       }
-                                       if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) {
-                                               DFG_SET(def, set_size, j, var_num);
-                                       }
-                               }
-                               if (opline->op2_type == IS_CV) {
-                                       var_num = EX_VAR_TO_NUM(opline->op2.var);
-                                       switch (opline->opcode) {
-                                               case ZEND_ASSIGN:
-                                                       if (build_flags & ZEND_SSA_RC_INFERENCE) {
-                                                               goto op2_def;
-                                                       }
-                                                       goto op2_use;
-                                               case ZEND_BIND_LEXICAL:
-                                                       if ((build_flags & ZEND_SSA_RC_INFERENCE) || (opline->extended_value & ZEND_BIND_REF)) {
-                                                               goto op2_def;
-                                                       }
-                                                       goto op2_use;
-                                               case ZEND_ASSIGN_REF:
-                                               case ZEND_FE_FETCH_R:
-                                               case ZEND_FE_FETCH_RW:
-op2_def:
-                                                       // FIXME: include into "use" too ...?
-                                                       DFG_SET(use, set_size, j, var_num);
-                                                       DFG_SET(def, set_size, j, var_num);
-                                                       break;
-                                               default:
-op2_use:
-                                                       if (!DFG_ISSET(def, set_size, j, var_num)) {
-                                                               DFG_SET(use, set_size, j, var_num);
-                                                       }
-                                                       break;
-                                       }
-                               } else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
-                                       var_num = EX_VAR_TO_NUM(opline->op2.var);
-                                       if (opline->opcode == ZEND_FE_FETCH_R || opline->opcode == ZEND_FE_FETCH_RW) {
-                                               DFG_SET(def, set_size, j, var_num);
-                                       } else {
-                                               if (!DFG_ISSET(def, set_size, j, var_num)) {
-                                                       DFG_SET(use, set_size, j, var_num);
-                                               }
-                                       }
-                               }
-                               if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                       var_num = EX_VAR_TO_NUM(opline->result.var);
-                                       if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
-                                        && opline->result_type == IS_CV) {
-                                               DFG_SET(use, set_size, j, var_num);
-                                       }
-                                       DFG_SET(def, set_size, j, var_num);
-                               }
+                               _zend_dfg_add_use_def_op(op_array, opline, build_flags, b_use, b_def);
                        }
                }
        }
index 9720f79cc17ee1216245ea1c2b99d57f503dbd38..a675187794d647aa223070f8fcac928d4299b46d 100644 (file)
@@ -44,6 +44,7 @@ typedef struct _zend_dfg {
 BEGIN_EXTERN_C()
 
 int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg *dfg, uint32_t build_flags);
+void zend_dfg_add_use_def_op(const zend_op_array *op_array, const zend_op *opline, uint32_t build_flags, zend_bitset use, zend_bitset def);
 
 END_EXTERN_C()
 
index 59288fabddabdf90913d5bd6952a6e14c33233f9..1a18f8727a4c8a22d9435aee965c458f37f0ce70 100644 (file)
@@ -169,6 +169,9 @@ static void zend_dump_type_info(uint32_t info, zend_class_entry *ce, int is_inst
        int first = 1;
 
        fprintf(stderr, " [");
+       if (info & MAY_BE_GUARD) {
+               fprintf(stderr, "!");
+       }
        if (info & MAY_BE_UNDEF) {
                if (first) first = 0; else fprintf(stderr, ", ");
                fprintf(stderr, "undef");
@@ -329,7 +332,7 @@ static void zend_dump_ssa_var_info(const zend_ssa *ssa, int ssa_var_num, uint32_
                dump_flags);
 }
 
-static void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa, int ssa_var_num, zend_uchar var_type, int var_num, uint32_t dump_flags)
+void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa, int ssa_var_num, zend_uchar var_type, int var_num, uint32_t dump_flags)
 {
        if (ssa_var_num >= 0) {
                fprintf(stderr, "#%d.", ssa_var_num);
@@ -402,21 +405,16 @@ static void zend_dump_range_constraint(const zend_op_array *op_array, const zend
        }
 }
 
-void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const void *data)
+void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const zend_ssa *ssa, const zend_ssa_op *ssa_op)
 {
        const char *name = zend_get_opcode_name(opline->opcode);
        uint32_t flags = zend_get_opcode_flags(opline->opcode);
        uint32_t n = 0;
-       const zend_ssa *ssa = NULL;
 
-       if (dump_flags & ZEND_DUMP_SSA) {
-               ssa = (const zend_ssa*)data;
-       }
-
-       if (!ssa || !ssa->ops || ssa->ops[opline - op_array->opcodes].result_use < 0) {
+       if (!ssa_op || ssa_op->result_use < 0) {
                if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                       if (ssa && ssa->ops && ssa->ops[opline - op_array->opcodes].result_def >= 0) {
-                               int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_def;
+                       if (ssa_op && ssa_op->result_def >= 0) {
+                               int ssa_var_num = ssa_op->result_def;
                                zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
                        } else {
                                zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var));
@@ -582,12 +580,12 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
        if (opline->op1_type == IS_CONST) {
                zend_dump_const(CRT_CONSTANT(opline->op1));
        } else if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-               if (ssa && ssa->ops) {
-                       int ssa_var_num = ssa->ops[opline - op_array->opcodes].op1_use;
+               if (ssa_op) {
+                       int ssa_var_num = ssa_op->op1_use;
                        if (ssa_var_num >= 0) {
                                fprintf(stderr, " ");
                                zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var), dump_flags);
-                       } else if (ssa->ops[opline - op_array->opcodes].op1_def < 0) {
+                       } else if (ssa_op->op1_def < 0) {
                                fprintf(stderr, " ");
                                zend_dump_var(op_array, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var));
                        }
@@ -595,8 +593,8 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
                        fprintf(stderr, " ");
                        zend_dump_var(op_array, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var));
                }
-               if (ssa && ssa->ops) {
-                       int ssa_var_num = ssa->ops[opline - op_array->opcodes].op1_def;
+               if (ssa_op) {
+                       int ssa_var_num = ssa_op->op1_def;
                        if (ssa_var_num >= 0) {
                                fprintf(stderr, " -> ");
                                zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op1_type, EX_VAR_TO_NUM(opline->op1.var), dump_flags);
@@ -643,12 +641,12 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
                        zend_dump_const(op);
                }
        } else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-               if (ssa && ssa->ops) {
-                       int ssa_var_num = ssa->ops[opline - op_array->opcodes].op2_use;
+               if (ssa_op) {
+                       int ssa_var_num = ssa_op->op2_use;
                        if (ssa_var_num >= 0) {
                                fprintf(stderr, " ");
                                zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var), dump_flags);
-                       } else if (ssa->ops[opline - op_array->opcodes].op2_def < 0) {
+                       } else if (ssa_op->op2_def < 0) {
                                fprintf(stderr, " ");
                                zend_dump_var(op_array, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var));
                        }
@@ -656,8 +654,8 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
                        fprintf(stderr, " ");
                        zend_dump_var(op_array, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var));
                }
-               if (ssa && ssa->ops) {
-                       int ssa_var_num = ssa->ops[opline - op_array->opcodes].op2_def;
+               if (ssa_op) {
+                       int ssa_var_num = ssa_op->op2_def;
                        if (ssa_var_num >= 0) {
                                fprintf(stderr, " -> ");
                                zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->op2_type, EX_VAR_TO_NUM(opline->op2.var), dump_flags);
@@ -697,10 +695,10 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
        } else if (opline->result_type & IS_SMART_BRANCH_JMPNZ) {
                fprintf(stderr, " jmpnz");
 #endif
-       } else if (ssa && ssa->ops && ssa->ops[opline - op_array->opcodes].result_use >= 0) {
+       } else if (ssa_op && ssa_op->result_use >= 0) {
                if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                       if (ssa && ssa->ops) {
-                               int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_use;
+                       if (ssa_op) {
+                               int ssa_var_num = ssa_op->result_use;
                                if (ssa_var_num >= 0) {
                                        fprintf(stderr, " ");
                                        zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
@@ -709,8 +707,8 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
                                fprintf(stderr, " ");
                                zend_dump_var(op_array, opline->result_type, EX_VAR_TO_NUM(opline->result.var));
                        }
-                       if (ssa && ssa->ops) {
-                               int ssa_var_num = ssa->ops[opline - op_array->opcodes].result_def;
+                       if (ssa_op) {
+                               int ssa_var_num = ssa_op->result_def;
                                if (ssa_var_num >= 0) {
                                        fprintf(stderr, " -> ");
                                        zend_dump_ssa_var(op_array, ssa, ssa_var_num, opline->result_type, EX_VAR_TO_NUM(opline->result.var), dump_flags);
@@ -723,6 +721,8 @@ void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, cons
 static void zend_dump_op_line(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const void *data)
 {
        int len = 0;
+       const zend_ssa *ssa = NULL;
+       zend_ssa_op *ssa_op = NULL;
 
        if (dump_flags & ZEND_DUMP_NUMERIC_OPLINES) {
                len = fprintf(stderr, "%04u:", (uint32_t)(opline - op_array->opcodes));
@@ -731,7 +731,14 @@ static void zend_dump_op_line(const zend_op_array *op_array, const zend_basic_bl
        }
        fprintf(stderr, "%*c", 12-len, ' ');
 
-       zend_dump_op(op_array, b, opline, dump_flags, data);
+       if (dump_flags & ZEND_DUMP_SSA) {
+               ssa = (const zend_ssa*)data;
+               if (ssa && ssa->ops) {
+                       ssa_op = &ssa->ops[opline - op_array->opcodes];
+               }
+       }
+
+       zend_dump_op(op_array, b, opline, dump_flags, ssa, ssa_op);
        fprintf(stderr, "\n");
 }
 
index 5f1968fd23852f391112794ecc0f493375663297..669ac699809e55273599cea42f4356d1dd6cdaef 100644 (file)
 BEGIN_EXTERN_C()
 
 void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, const char *msg, const void *data);
-void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const void *data);
+void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *b, const zend_op *opline, uint32_t dump_flags, const zend_ssa *ssa, const zend_ssa_op *ssa_op);
 void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg);
 void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zend_dfg *dfg);
 void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa);
 void zend_dump_variables(const zend_op_array *op_array);
 void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa, uint32_t dump_flags);
+void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *ssa, int ssa_var_num, zend_uchar var_type, int var_num, uint32_t dump_flags);
 void zend_dump_var(const zend_op_array *op_array, zend_uchar var_type, int var_num);
 void zend_dump_op_array_name(const zend_op_array *op_array);
 void zend_dump_const(const zval *zv);
index e11c3cd9c74ba6ff8be14d797134da5e76e4d868..2be19fcf8a9f3aeb41e1b96a57c1a95bbf3f65d3 100644 (file)
@@ -1828,7 +1828,7 @@ static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
 
 #define UPDATE_SSA_TYPE(_type, _var)                                                                   \
        do {                                                                                                                            \
-               uint32_t __type = (_type);                                                                              \
+               uint32_t __type = (_type) & ~MAY_BE_GUARD;                                              \
                int __var = (_var);                                                                                             \
                if (__type & MAY_BE_REF) {                                                                              \
                        __type |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; \
@@ -1848,12 +1848,19 @@ static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
                                }                                                                                                               \
                        }                                                                                                                       \
                        if (ssa_var_info[__var].type != __type) {                                       \
-                               if (ssa_var_info[__var].type & ~__type) {                               \
-                                       emit_type_narrowing_warning(op_array, ssa, __var);      \
-                                       return FAILURE;                                                                         \
+                               if (update_worklist) {                                                                  \
+                                       if (ssa_var_info[__var].type & ~__type) {                       \
+                                               emit_type_narrowing_warning(op_array, ssa, __var);\
+                                               return FAILURE;                                                                 \
+                                       }                                                                                                       \
+                                       ssa_var_info[__var].type = __type;                                      \
+                                       add_usages(op_array, ssa, worklist, __var);                     \
+                               } else {                                                                                                \
+                                       if (ssa_var_info[__var].type & ~__type) {                       \
+                                               return FAILURE;                                                                 \
+                                       }                                                                                                       \
+                                       ssa_var_info[__var].type = __type;                                      \
                                }                                                                                                               \
-                               ssa_var_info[__var].type = __type;                                              \
-                               add_usages(op_array, ssa, worklist, __var);                             \
                        }                                                                                                                       \
                        /*zend_bitset_excl(worklist, var);*/                                            \
                }                                                                                                                               \
@@ -1866,7 +1873,9 @@ static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
                            ssa_var_info[var].is_instanceof != (_is_instanceof)) {  \
                                ssa_var_info[var].ce = (_ce);                                               \
                                ssa_var_info[var].is_instanceof = (_is_instanceof);     \
-                               add_usages(op_array, ssa, worklist, var);                               \
+                               if (update_worklist) {                                                                  \
+                                       add_usages(op_array, ssa, worklist, var);                       \
+                               }                                                                                                               \
                        }                                                                                                                       \
                        /*zend_bitset_excl(worklist, var);*/                                            \
                }                                                                                                                               \
@@ -2207,7 +2216,7 @@ static zend_property_info *lookup_prop_info(zend_class_entry *ce, zend_string *n
        return NULL;
 }
 
-static zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, int i)
+static zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op)
 {
        zend_property_info *prop_info = NULL;
        if (opline->op2_type == IS_CONST) {
@@ -2215,8 +2224,8 @@ static zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, z
 
                if (opline->op1_type == IS_UNUSED) {
                        ce = op_array->scope;
-               } else if (ssa->ops[i].op1_use >= 0) {
-                       ce = ssa->var_info[ssa->ops[i].op1_use].ce;
+               } else if (ssa_op->op1_use >= 0) {
+                       ce = ssa->var_info[ssa_op->op1_use].ce;
                }
                if (ce) {
                        prop_info = lookup_prop_info(ce,
@@ -2294,17 +2303,19 @@ static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_in
        return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
 }
 
-static int zend_update_type_info(const zend_op_array *op_array,
-                                  zend_ssa            *ssa,
-                                  const zend_script   *script,
-                                  zend_bitset          worklist,
-                                  int                  i,
-                                  zend_long            optimization_level)
+static zend_always_inline int _zend_update_type_info(
+                       const zend_op_array *op_array,
+                       zend_ssa            *ssa,
+                       const zend_script   *script,
+                       zend_bitset          worklist,
+                       zend_op             *opline,
+                       zend_ssa_op         *ssa_op,
+                       const zend_op      **ssa_opcodes,
+                       zend_long            optimization_level,
+                       zend_bool            update_worklist)
 {
        uint32_t t1, t2;
        uint32_t tmp, orig;
-       zend_op *opline = op_array->opcodes + i;
-       zend_ssa_op *ssa_ops = ssa->ops;
        zend_ssa_var *ssa_vars = ssa->vars;
        zend_ssa_var_info *ssa_var_info = ssa->var_info;
        zend_class_entry *ce;
@@ -2312,11 +2323,11 @@ static int zend_update_type_info(const zend_op_array *op_array,
 
        if (opline->opcode == ZEND_OP_DATA) {
                opline--;
-               i--;
+               ssa_op--;
        }
 
-       t1 = OP1_INFO();
-       t2 = OP2_INFO();
+       t1 = OP1_INFO_EX();
+       t2 = OP2_INFO_EX();
 
        /* If one of the operands cannot have any type, this means the operand derives from
         * unreachable code. Propagate the empty result early, so that that the following
@@ -2324,14 +2335,14 @@ static int zend_update_type_info(const zend_op_array *op_array,
        if (!(t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))
                || !(t2 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))) {
                tmp = 0;
-               if (ssa_ops[i].result_def >= 0) {
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+               if (ssa_op->result_def >= 0) {
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                }
-               if (ssa_ops[i].op1_def >= 0) {
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+               if (ssa_op->op1_def >= 0) {
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                }
-               if (ssa_ops[i].op2_def >= 0) {
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
+               if (ssa_op->op2_def >= 0) {
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
                }
                return 1;
        }
@@ -2349,8 +2360,8 @@ static int zend_update_type_info(const zend_op_array *op_array,
                case ZEND_SL:
                case ZEND_SR:
                case ZEND_CONCAT:
-                       tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_ops[i].result_def, optimization_level);
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_op->result_def, optimization_level);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_BW_NOT:
                        tmp = 0;
@@ -2366,10 +2377,10 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                        tmp |= MAY_BE_OBJECT | MAY_BE_RC1;
                                }
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_BEGIN_SILENCE:
-                       UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
                        break;
                case ZEND_BOOL_NOT:
                case ZEND_BOOL_XOR:
@@ -2392,10 +2403,10 @@ static int zend_update_type_info(const zend_op_array *op_array,
                case ZEND_ASSERT_CHECK:
                case ZEND_IN_ARRAY:
                case ZEND_ARRAY_KEY_EXISTS:
-                       UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
                        break;
                case ZEND_CAST:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = t1;
                                if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) &&
                                    (opline->op1_type == IS_CV) &&
@@ -2407,8 +2418,8 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                    opline->extended_value == IS_STRING) {
                                        tmp |= MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
                        tmp = 1 << opline->extended_value;
                        if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
@@ -2435,19 +2446,19 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                        tmp |= ((t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT) | ((t1 & MAY_BE_ANY)? MAY_BE_ARRAY_KEY_LONG : 0);
                                }
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_QM_ASSIGN:
                case ZEND_JMP_SET:
                case ZEND_COALESCE:
                case ZEND_COPY_TMP:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = t1;
                                if ((t1 & (MAY_BE_RC1|MAY_BE_REF)) && (opline->op1_type == IS_CV)) {
                                        tmp |= MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
                        tmp = t1 & ~(MAY_BE_UNDEF|MAY_BE_REF);
                        if (t1 & MAY_BE_UNDEF) {
@@ -2467,8 +2478,8 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                        tmp &= ~MAY_BE_FALSE;
                                }
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
-                       COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+                       COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
                        break;
                case ZEND_ASSIGN_OP:
                case ZEND_ASSIGN_DIM_OP:
@@ -2479,21 +2490,21 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        orig = 0;
                        tmp = 0;
                        if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
-                               prop_info = zend_fetch_prop_info(op_array, ssa, opline, i);
+                               prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
                                orig = t1;
                                t1 = zend_fetch_prop_type(script, prop_info, &ce);
-                               t2 = OP1_DATA_INFO();
+                               t2 = OP1_DATA_INFO_EX();
                        } else if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
                                if (t1 & MAY_BE_ARRAY_OF_REF) {
                                tmp |= MAY_BE_REF;
                                }
                                orig = t1;
                                t1 = zend_array_element_type(t1, 1, 0);
-                               t2 = OP1_DATA_INFO();
+                               t2 = OP1_DATA_INFO_EX();
                        } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
                                prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
                                t1 = zend_fetch_prop_type(script, prop_info, &ce);
-                               t2 = OP1_DATA_INFO();
+                               t2 = OP1_DATA_INFO_EX();
                        } else {
                                if (t1 & MAY_BE_REF) {
                                tmp |= MAY_BE_REF;
@@ -2501,7 +2512,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        }
 
                        tmp |= binary_op_result_type(
-                               ssa, opline->extended_value, t1, t2, ssa_ops[i].op1_def, optimization_level);
+                               ssa, opline->extended_value, t1, t2, ssa_op->op1_def, optimization_level);
                        if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY)) {
                                tmp |= MAY_BE_RC1;
                        }
@@ -2511,9 +2522,9 @@ static int zend_update_type_info(const zend_op_array *op_array,
 
                        if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
                                if (opline->op1_type == IS_CV) {
-                                       orig = assign_dim_result_type(orig, OP2_INFO(), tmp, opline->op2_type);
-                                       UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
-                                       COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                                       orig = assign_dim_result_type(orig, OP2_INFO_EX(), tmp, opline->op2_type);
+                                       UPDATE_SSA_TYPE(orig, ssa_op->op1_def);
+                                       COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                                }
                        } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
                                if (opline->op1_type == IS_CV) {
@@ -2526,15 +2537,15 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                        orig |= (MAY_BE_RC1|MAY_BE_RCN);
                                                }
                                        }
-                                       UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
-                                       COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                                       UPDATE_SSA_TYPE(orig, ssa_op->op1_def);
+                                       COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                                }
                        } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP) {
                                /* Nothing to do */
                        } else {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                ce = NULL;
                                if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
                                        if (opline->op2_type == IS_UNUSED) {
@@ -2569,9 +2580,9 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                        }
                                }
                                tmp &= ~MAY_BE_REF;
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                                if (ce) {
-                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
                                }
                        }
                        break;
@@ -2584,18 +2595,18 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        }
                        if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
                                tmp |= MAY_BE_RC1;
-                               if (ssa_ops[i].result_def >= 0) {
+                               if (ssa_op->result_def >= 0) {
                                        tmp |= MAY_BE_RCN;
                                }
                        }
                        if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
-                               if (!ssa_var_info[ssa_ops[i].op1_use].has_range ||
+                               if (!ssa_var_info[ssa_op->op1_use].has_range ||
                                    (opline->opcode == ZEND_PRE_DEC &&
-                                    (ssa_var_info[ssa_ops[i].op1_use].range.underflow ||
-                                     ssa_var_info[ssa_ops[i].op1_use].range.min == ZEND_LONG_MIN)) ||
+                                    (ssa_var_info[ssa_op->op1_use].range.underflow ||
+                                     ssa_var_info[ssa_op->op1_use].range.min == ZEND_LONG_MIN)) ||
                                     (opline->opcode == ZEND_PRE_INC &&
-                                     (ssa_var_info[ssa_ops[i].op1_use].range.overflow ||
-                                      ssa_var_info[ssa_ops[i].op1_use].range.max == ZEND_LONG_MAX))) {
+                                     (ssa_var_info[ssa_op->op1_use].range.overflow ||
+                                      ssa_var_info[ssa_op->op1_use].range.max == ZEND_LONG_MAX))) {
                                        /* may overflow */
                                        tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
                                } else {
@@ -2620,16 +2631,16 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                }
                                tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY);
                        }
-                       if (ssa_ops[i].op1_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                       if (ssa_op->op1_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       if (ssa_op->result_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
                case ZEND_POST_INC:
                case ZEND_POST_DEC:
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                tmp = 0;
                                if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
                                        tmp |= MAY_BE_RC1|MAY_BE_RCN;
@@ -2638,7 +2649,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (t1 & MAY_BE_UNDEF) {
                                        tmp |= MAY_BE_NULL;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        tmp = 0;
                        if (t1 & MAY_BE_REF) {
@@ -2648,13 +2659,13 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                tmp |= MAY_BE_RC1;
                        }
                        if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
-                               if (!ssa_var_info[ssa_ops[i].op1_use].has_range ||
+                               if (!ssa_var_info[ssa_op->op1_use].has_range ||
                                     (opline->opcode == ZEND_POST_DEC &&
-                                     (ssa_var_info[ssa_ops[i].op1_use].range.underflow ||
-                                      ssa_var_info[ssa_ops[i].op1_use].range.min == ZEND_LONG_MIN)) ||
+                                     (ssa_var_info[ssa_op->op1_use].range.underflow ||
+                                      ssa_var_info[ssa_op->op1_use].range.min == ZEND_LONG_MIN)) ||
                                      (opline->opcode == ZEND_POST_INC &&
-                                      (ssa_var_info[ssa_ops[i].op1_use].range.overflow ||
-                                       ssa_var_info[ssa_ops[i].op1_use].range.max == ZEND_LONG_MAX))) {
+                                      (ssa_var_info[ssa_op->op1_use].range.overflow ||
+                                       ssa_var_info[ssa_op->op1_use].range.max == ZEND_LONG_MAX))) {
                                        /* may overflow */
                                        tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
                                } else {
@@ -2679,23 +2690,23 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                }
                                tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY);
                        }
-                       if (ssa_ops[i].op1_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                       if (ssa_op->op1_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_ASSIGN_DIM:
                        if (opline->op1_type == IS_CV) {
-                               tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type);
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO_EX(), opline->op2_type);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                tmp = 0;
                                if (t1 & MAY_BE_STRING) {
                                        tmp |= MAY_BE_STRING;
                                }
                                if (t1 & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_STRING)) {
-                                       tmp |= (OP1_DATA_INFO() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
+                                       tmp |= (OP1_DATA_INFO_EX() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
 
                                        if (opline->op2_type == IS_UNUSED) {
                                                /* When appending to an array and the LONG_MAX key is already used
@@ -2716,18 +2727,18 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (t1 & MAY_BE_OBJECT) {
                                        tmp |= MAY_BE_REF;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
-                       if ((opline+1)->op1_type == IS_CV && ssa_ops[i+1].op1_def >= 0) {
+                       if ((opline+1)->op1_type == IS_CV && (ssa_op+1)->op1_def >= 0) {
                                opline++;
-                               i++;
-                               tmp = OP1_INFO();
+                               ssa_op++;
+                               tmp = OP1_INFO_EX();
                                if (tmp & (MAY_BE_ANY | MAY_BE_REF)) {
                                        if (tmp & MAY_BE_RC1) {
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_ASSIGN_OBJ:
@@ -2740,28 +2751,28 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (tmp & MAY_BE_OBJECT) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                // TODO: If there is no __set we might do better
                                tmp = zend_fetch_prop_type(script,
-                                       zend_fetch_prop_info(op_array, ssa, opline, i), &ce);
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                                       zend_fetch_prop_info(op_array, ssa, opline, ssa_op), &ce);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                                if (ce) {
-                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
                                }
                        }
                        if ((opline+1)->op1_type == IS_CV) {
                                opline++;
-                               i++;
-                               tmp = OP1_INFO();
+                               ssa_op++;
+                               tmp = OP1_INFO_EX();
                                if (tmp & (MAY_BE_ANY | MAY_BE_REF)) {
                                        if (tmp & MAY_BE_RC1) {
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_PRE_INC_OBJ:
@@ -2777,24 +2788,24 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (tmp & MAY_BE_OBJECT) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                // TODO: ???
                                tmp = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
                case ZEND_ASSIGN:
-                       if (opline->op2_type == IS_CV && ssa_ops[i].op2_def >= 0) {
+                       if (opline->op2_type == IS_CV && ssa_op->op2_def >= 0) {
                                tmp = t2;
                                if (tmp & (MAY_BE_ANY | MAY_BE_REF)) {
                                        if (tmp & MAY_BE_RC1) {
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
                        }
                        tmp = t2 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN);
                        if (t2 & MAY_BE_UNDEF) {
@@ -2813,17 +2824,17 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (RETURN_VALUE_USED(opline) && (tmp & MAY_BE_RC1)) {
                                tmp |= MAY_BE_RCN;
                        }
-                       if (ssa_ops[i].op1_def >= 0) {
-                               if (ssa_var_info[ssa_ops[i].op1_def].use_as_double) {
+                       if (ssa_op->op1_def >= 0) {
+                               if (ssa_var_info[ssa_op->op1_def].use_as_double) {
                                        tmp &= ~MAY_BE_LONG;
                                        tmp |= MAY_BE_DOUBLE;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_ops[i].result_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def);
+                       if (ssa_op->result_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_op->result_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
                        }
                        break;
                case ZEND_ASSIGN_REF:
@@ -2833,7 +2844,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (t2 & MAY_BE_UNDEF) {
                                        tmp |= MAY_BE_NULL;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
                        }
                        if (opline->op2_type == IS_VAR && opline->extended_value == ZEND_RETURNS_FUNCTION) {
                                tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
@@ -2843,9 +2854,9 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (t2 & MAY_BE_UNDEF) {
                                tmp |= MAY_BE_NULL;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                       if (ssa_ops[i].result_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                       if (ssa_op->result_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
                case ZEND_ASSIGN_OBJ_REF:
@@ -2858,11 +2869,11 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (tmp & MAY_BE_OBJECT) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
 
-                       t2 = OP1_DATA_INFO();
+                       t2 = OP1_DATA_INFO_EX();
                        if ((opline+1)->op1_type == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION)) {
                                tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
                        } else {
@@ -2871,30 +2882,30 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (t2 & MAY_BE_UNDEF) {
                                tmp |= MAY_BE_NULL;
                        }
-                       if (ssa_ops[i].result_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       if (ssa_op->result_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        if ((opline+1)->op1_type == IS_CV) {
                                opline++;
-                               i++;
+                               ssa_op++;
                                tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
                                if (t2 & MAY_BE_UNDEF) {
                                        tmp |= MAY_BE_NULL;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_ASSIGN_STATIC_PROP_REF:
                        if ((opline+1)->op1_type == IS_CV) {
                                opline++;
-                               i++;
-                               UPDATE_SSA_TYPE(MAY_BE_REF, ssa_ops[i].op1_def);
+                               ssa_op++;
+                               UPDATE_SSA_TYPE(MAY_BE_REF, ssa_op->op1_def);
                        }
                        break;
                case ZEND_BIND_GLOBAL:
                        tmp = MAY_BE_REF | MAY_BE_ANY
                                | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        break;
                case ZEND_BIND_STATIC:
                        tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
@@ -2902,20 +2913,20 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (opline->extended_value & ZEND_BIND_IMPLICIT) {
                                tmp |= MAY_BE_UNDEF;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        break;
                case ZEND_SEND_VAR:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = t1;
                                if ((t1 & (MAY_BE_RC1|MAY_BE_REF)) && (opline->op1_type == IS_CV)) {
                                        tmp |= MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
                        break;
                case ZEND_BIND_LEXICAL:
-                       if (ssa_ops[i].op2_def >= 0) {
+                       if (ssa_op->op2_def >= 0) {
                                if (opline->extended_value & ZEND_BIND_REF) {
                                        tmp = t2 | MAY_BE_REF;
                                } else {
@@ -2924,12 +2935,12 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op2_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op2_def);
                        }
                        break;
                case ZEND_YIELD:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
                                        tmp = t1 | MAY_BE_REF;
                                } else {
@@ -2938,30 +2949,30 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
                                        | MAY_BE_RC1 | MAY_BE_RCN;
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
                case ZEND_SEND_VAR_EX:
                case ZEND_SEND_FUNC_ARG:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = (t1 & MAY_BE_UNDEF)|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_SEND_REF:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_SEND_UNPACK:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = t1;
                                if (t1 & MAY_BE_ARRAY) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN;
@@ -2973,14 +2984,14 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                if (t1 & MAY_BE_OBJECT) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_FAST_CONCAT:
                case ZEND_ROPE_INIT:
                case ZEND_ROPE_ADD:
                case ZEND_ROPE_END:
-                       UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
                        break;
                case ZEND_RECV:
                case ZEND_RECV_INIT:
@@ -3008,8 +3019,8 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        }
 #if 0
                        /* We won't receive unused arguments */
-                       if (ssa_vars[ssa_ops[i].result_def].use_chain < 0 &&
-                           ssa_vars[ssa_ops[i].result_def].phi_use_chain == NULL &&
+                       if (ssa_vars[ssa_op->result_def].use_chain < 0 &&
+                           ssa_vars[ssa_op->result_def].phi_use_chain == NULL &&
                            op_array->arg_info &&
                            opline->op1.num <= op_array->num_args &&
                            op_array->arg_info[opline->op1.num-1].class_name == NULL &&
@@ -3017,81 +3028,81 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                tmp = MAY_BE_UNDEF|MAY_BE_RCN;
                        }
 #endif
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        if (func_info &&
                            (int)opline->op1.num-1 < func_info->num_args &&
                            func_info->arg_info[opline->op1.num-1].info.ce) {
                                UPDATE_SSA_OBJ_TYPE(
                                        func_info->arg_info[opline->op1.num-1].info.ce,
                                        func_info->arg_info[opline->op1.num-1].info.is_instanceof,
-                                       ssa_ops[i].result_def);
+                                       ssa_op->result_def);
                        } else if (ce) {
-                               UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+                               UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
                        } else {
-                               UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                               UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                        }
                        break;
                }
                case ZEND_DECLARE_ANON_CLASS:
-                       UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_op->result_def);
                        if (script && (ce = zend_hash_find_ptr(&script->class_table, Z_STR_P(CRT_CONSTANT(opline->op1)))) != NULL) {
-                               UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def);
+                               UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
                        }
                        break;
                case ZEND_FETCH_CLASS:
-                       UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_op->result_def);
                        if (opline->op2_type == IS_UNUSED) {
                                switch (opline->op1.num & ZEND_FETCH_CLASS_MASK) {
                                        case ZEND_FETCH_CLASS_SELF:
                                                if (op_array->scope) {
-                                                       UPDATE_SSA_OBJ_TYPE(op_array->scope, 0, ssa_ops[i].result_def);
+                                                       UPDATE_SSA_OBJ_TYPE(op_array->scope, 0, ssa_op->result_def);
                                                } else {
-                                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                                                }
                                                break;
                                        case ZEND_FETCH_CLASS_PARENT:
                                                if (op_array->scope && op_array->scope->parent && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
-                                                       UPDATE_SSA_OBJ_TYPE(op_array->scope->parent, 0, ssa_ops[i].result_def);
+                                                       UPDATE_SSA_OBJ_TYPE(op_array->scope->parent, 0, ssa_op->result_def);
                                                } else {
-                                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                                                }
                                                break;
                                        case ZEND_FETCH_CLASS_STATIC:
                                        default:
-                                               UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                                               UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                                                break;
                                }
                        } else if (opline->op2_type == IS_CONST) {
                                zval *zv = CRT_CONSTANT(opline->op2);
                                if (Z_TYPE_P(zv) == IS_STRING) {
                                        ce = get_class_entry(script, Z_STR_P(zv+1));
-                                       UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
                                } else {
-                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                                }
                        } else {
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
                        }
                        break;
                case ZEND_NEW:
                        tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT;
                        if (opline->op1_type == IS_CONST &&
                            (ce = get_class_entry(script, Z_STR_P(CRT_CONSTANT(opline->op1)+1))) != NULL) {
-                               UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def);
-                       } else if ((t1 & MAY_BE_CLASS) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
-                               UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def);
+                               UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+                       } else if ((t1 & MAY_BE_CLASS) && ssa_op->op1_use >= 0 && ssa_var_info[ssa_op->op1_use].ce) {
+                               UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_op->op1_use].ce, ssa_var_info[ssa_op->op1_use].is_instanceof, ssa_op->result_def);
                        } else {
-                               UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                               UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_CLONE:
-                       UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def);
-                       COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
+                       COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
                        break;
                case ZEND_INIT_ARRAY:
                case ZEND_ADD_ARRAY_ELEMENT:
-                       if (opline->op1_type == IS_CV && ssa_ops[i].op1_def >= 0) {
+                       if (opline->op1_type == IS_CV && ssa_op->op1_def >= 0) {
                                if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
                                        tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
                                        if (t1 & MAY_BE_UNDEF) {
@@ -3110,12 +3121,12 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                tmp = MAY_BE_RC1|MAY_BE_ARRAY;
-                               if (ssa_ops[i].result_use >= 0) {
-                                       tmp |= ssa_var_info[ssa_ops[i].result_use].type;
+                               if (ssa_op->result_use >= 0) {
+                                       tmp |= ssa_var_info[ssa_op->result_use].type;
                                }
                                if (opline->op1_type != IS_UNUSED) {
                                        tmp |= (t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
@@ -3143,11 +3154,11 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                }
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
                case ZEND_ADD_ARRAY_UNPACK:
-                       tmp = ssa_var_info[ssa_ops[i].result_use].type;
+                       tmp = ssa_var_info[ssa_op->result_use].type;
                        ZEND_ASSERT(tmp & MAY_BE_ARRAY);
                        /* Ignore string keys as they will throw. */
                        if (t1 & MAY_BE_ARRAY_KEY_LONG) {
@@ -3156,7 +3167,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (t1 & MAY_BE_OBJECT) {
                                tmp |= MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_UNSET_CV:
                        tmp = MAY_BE_UNDEF;
@@ -3164,18 +3175,18 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                /* In global scope, we know nothing */
                                tmp |= MAY_BE_REF;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        break;
                case ZEND_UNSET_DIM:
                case ZEND_UNSET_OBJ:
-                       if (ssa_ops[i].op1_def >= 0) {
-                               UPDATE_SSA_TYPE(t1, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                       if (ssa_op->op1_def >= 0) {
+                               UPDATE_SSA_TYPE(t1, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
                        break;
                case ZEND_FE_RESET_R:
                case ZEND_FE_RESET_RW:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = t1;
                                if (opline->opcode == ZEND_FE_RESET_RW) {
                                        tmp |= MAY_BE_REF;
@@ -3184,8 +3195,8 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                tmp |= MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
                        if (opline->opcode == ZEND_FE_RESET_RW) {
 //???
@@ -3193,8 +3204,8 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        } else {
                                tmp = MAY_BE_RC1 | MAY_BE_RCN | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
-                       COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+                       COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
                        break;
                case ZEND_FE_FETCH_R:
                case ZEND_FE_FETCH_RW:
@@ -3221,9 +3232,9 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                        }
                                }
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
-                       if (ssa_ops[i].result_def >= 0) {
-                               tmp = (ssa_ops[i].result_use >= 0) ? RES_USE_INFO() : 0;
+                       UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+                       if (ssa_op->result_def >= 0) {
+                               tmp = (ssa_op->result_use >= 0) ? RES_USE_INFO_EX() : 0;
                                if (t1 & MAY_BE_OBJECT) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
                                }
@@ -3235,7 +3246,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                tmp |= MAY_BE_STRING | MAY_BE_RCN;
                                        }
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
                case ZEND_FETCH_DIM_R:
@@ -3246,7 +3257,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
                case ZEND_FETCH_DIM_FUNC_ARG:
                case ZEND_FETCH_LIST_R:
                case ZEND_FETCH_LIST_W:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                uint32_t key_type = 0;
                                tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
                                if (opline->opcode == ZEND_FETCH_DIM_W ||
@@ -3297,10 +3308,18 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                || opline->opcode == ZEND_FETCH_DIM_W
                                                || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
                                                || opline->opcode == ZEND_FETCH_LIST_W) {
-                                       j = ssa_vars[ssa_ops[i].result_def].use_chain;
+                                       j = ssa_vars[ssa_op->result_def].use_chain;
                                        while (j >= 0) {
-                                               ZEND_ASSERT(j == i + 1 && "Use must be in next opline");
-                                               switch (op_array->opcodes[j].opcode) {
+                                               zend_uchar opcode;
+
+                                               if (!ssa_opcodes) {
+                                                       ZEND_ASSERT(j == (opline - op_array->opcodes) + 1 && "Use must be in next opline");
+                                                       opcode = op_array->opcodes[j].opcode;
+                                               } else {
+                                                       ZEND_ASSERT(ssa_opcodes[j] == opline + 1 && "Use must be in next opline");
+                                                       opcode = ssa_opcodes[j]->opcode;
+                                               }
+                                               switch (opcode) {
                                                        case ZEND_FETCH_DIM_W:
                                                        case ZEND_FETCH_DIM_RW:
                                                        case ZEND_FETCH_DIM_FUNC_ARG:
@@ -3354,18 +3373,18 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                                                break;
                                                        EMPTY_SWITCH_DEFAULT_CASE()
                                                }
-                                               j = zend_ssa_next_use(ssa_ops, ssa_ops[i].result_def, j);
+                                               j = zend_ssa_next_use(ssa->ops, ssa_op->result_def, j);
                                                ZEND_ASSERT(j < 0 && "There should only be one use");
                                        }
                                }
                                if ((tmp & MAY_BE_ARRAY) && (tmp & MAY_BE_ARRAY_KEY_ANY)) {
-                                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                                } else {
                                        /* invalid key type */
                                        tmp = (tmp & (MAY_BE_RC1|MAY_BE_RCN)) | (t1 & ~(MAY_BE_RC1|MAY_BE_RCN));
-                                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                                       UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                                }
-                               COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+                               COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
                        }
                        /* FETCH_LIST on a string behaves like FETCH_R on null */
                        tmp = zend_array_element_type(
@@ -3375,11 +3394,11 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) {
                                tmp |= MAY_BE_NULL;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_FETCH_THIS:
-                       UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_ops[i].result_def);
-                       UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def);
+                       UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_op->result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
                        break;
                case ZEND_FETCH_OBJ_R:
                case ZEND_FETCH_OBJ_IS:
@@ -3387,15 +3406,15 @@ static int zend_update_type_info(const zend_op_array *op_array,
                case ZEND_FETCH_OBJ_W:
                case ZEND_FETCH_OBJ_UNSET:
                case ZEND_FETCH_OBJ_FUNC_ARG:
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                tmp = zend_fetch_prop_type(script,
-                                       zend_fetch_prop_info(op_array, ssa, opline, i), &ce);
+                                       zend_fetch_prop_info(op_array, ssa, opline, ssa_op), &ce);
                                if (opline->result_type != IS_TMP_VAR) {
                                        tmp |= MAY_BE_REF;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                                if (ce) {
-                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
                                }
                        }
                        break;
@@ -3410,16 +3429,16 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        if (opline->result_type != IS_TMP_VAR) {
                                tmp |= MAY_BE_REF;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        if (ce) {
-                               UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+                               UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
                        }
                        break;
                case ZEND_DO_FCALL:
                case ZEND_DO_ICALL:
                case ZEND_DO_UCALL:
                case ZEND_DO_FCALL_BY_NAME:
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
                                zend_call_info *call_info;
 
@@ -3431,46 +3450,46 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                        goto unknown_opcode;
                                }
                                tmp = zend_get_func_info(call_info, ssa);
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                                if (call_info->callee_func->type == ZEND_USER_FUNCTION) {
                                        func_info = ZEND_FUNC_INFO(&call_info->callee_func->op_array);
                                        if (func_info) {
                                                UPDATE_SSA_OBJ_TYPE(
                                                        func_info->return_info.ce,
                                                        func_info->return_info.is_instanceof,
-                                                       ssa_ops[i].result_def);
+                                                       ssa_op->result_def);
                                        }
                                }
                        }
                        break;
                case ZEND_FETCH_CONSTANT:
                case ZEND_FETCH_CLASS_CONSTANT:
-                       UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
                        break;
                case ZEND_STRLEN:
                        tmp = MAY_BE_LONG;
                        if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) {
                                tmp |= MAY_BE_NULL;
                        }
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        break;
                case ZEND_COUNT:
                case ZEND_FUNC_NUM_ARGS:
-                       UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
                        break;
                case ZEND_FUNC_GET_ARGS:
-                       UPDATE_SSA_TYPE(MAY_BE_RC1| MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_RC1| MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
                        break;
                case ZEND_GET_CLASS:
                case ZEND_GET_CALLED_CLASS:
-                       UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_STRING|MAY_BE_RCN, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_STRING|MAY_BE_RCN, ssa_op->result_def);
                        break;
                case ZEND_GET_TYPE:
-                       UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
                        break;
                case ZEND_TYPE_CHECK:
                case ZEND_DEFINED:
-                       UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_ops[i].result_def);
+                       UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
                        break;
                case ZEND_VERIFY_RETURN_TYPE:
                        if (t1 & MAY_BE_REF) {
@@ -3481,26 +3500,26 @@ static int zend_update_type_info(const zend_op_array *op_array,
                                tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
                        }
                        if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                                if (ce) {
-                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].op1_def);
+                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->op1_def);
                                } else {
-                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
+                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->op1_def);
                                }
                        } else {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                                if (ce) {
-                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
                                } else {
-                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+                                       UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
                                }
                        }
                        break;
                case ZEND_MAKE_REF:
                        tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
-                       UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
-                       if (ssa_ops[i].op1_def >= 0) {
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                       UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+                       if (ssa_op->op1_def >= 0) {
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
                        break;
                case ZEND_CATCH:
@@ -3510,18 +3529,18 @@ static int zend_update_type_info(const zend_op_array *op_array,
                        break;
                default:
 unknown_opcode:
-                       if (ssa_ops[i].op1_def >= 0) {
+                       if (ssa_op->op1_def >= 0) {
                                tmp = MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
                        }
-                       if (ssa_ops[i].result_def >= 0) {
+                       if (ssa_op->result_def >= 0) {
                                tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
                                if (opline->result_type == IS_TMP_VAR) {
                                        tmp |= MAY_BE_RC1 | MAY_BE_RCN;
                                } else {
                                        tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
                                }
-                               UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+                               UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
                        }
                        break;
        }
@@ -3529,6 +3548,18 @@ unknown_opcode:
        return SUCCESS;
 }
 
+int zend_update_type_info(
+                       const zend_op_array *op_array,
+                       zend_ssa            *ssa,
+                       const zend_script   *script,
+                       zend_op             *opline,
+                       zend_ssa_op         *ssa_op,
+                       const zend_op      **ssa_opcodes,
+                       zend_long            optimization_level)
+{
+       return _zend_update_type_info(op_array, ssa, script, NULL, opline, ssa_op, ssa_opcodes, optimization_level, 0);
+}
+
 static uint32_t get_class_entry_rank(zend_class_entry *ce) {
        uint32_t rank = 0;
        if (ce->ce_flags & ZEND_ACC_LINKED) {
@@ -3583,6 +3614,7 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
        int ssa_vars_count = ssa->vars_count;
        int i, j;
        uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
+       zend_bool update_worklist = 1;
 
        while (!zend_bitset_empty(worklist, worklist_len)) {
                j = zend_bitset_first(worklist, worklist_len);
@@ -3645,7 +3677,7 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
                        }
                } else if (ssa_vars[j].definition >= 0) {
                        i = ssa_vars[j].definition;
-                       if (zend_update_type_info(op_array, ssa, script, worklist, i, optimization_level) == FAILURE) {
+                       if (_zend_update_type_info(op_array, ssa, script, worklist, op_array->opcodes + i, ssa->ops + i, NULL, optimization_level, 1) == FAILURE) {
                                return FAILURE;
                        }
                }
@@ -4220,10 +4252,10 @@ void zend_inference_check_recursive_dependencies(zend_op_array *op_array)
        free_alloca(worklist, use_heap);
 }
 
-int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa)
+int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa)
 {
-       uint32_t t1 = OP1_INFO();
-       uint32_t t2 = OP2_INFO();
+       uint32_t t1 = OP1_INFO_EX();
+       uint32_t t2 = OP2_INFO_EX();
 
        if (opline->op1_type == IS_CV) {
                if (t1 & MAY_BE_UNDEF) {
@@ -4331,7 +4363,7 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                        return 0;
                case ZEND_BIND_GLOBAL:
                        if ((opline+1)->opcode == ZEND_BIND_GLOBAL) {
-                               return zend_may_throw(opline + 1, op_array, ssa);
+                               return zend_may_throw_ex(opline + 1, ssa_op + 1, op_array, ssa);
                        }
                        return 0;
                case ZEND_ADD:
@@ -4343,8 +4375,8 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                                (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)) {
+                       if (!OP2_HAS_RANGE_EX() ||
+                               (OP2_MIN_RANGE_EX() <= 0 && OP2_MAX_RANGE_EX() >= 0)) {
                                /* Division by zero */
                                return 1;
                        }
@@ -4358,8 +4390,8 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                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;
+                               !OP2_HAS_RANGE_EX() ||
+                               OP2_MIN_RANGE_EX() < 0;
                case ZEND_CONCAT:
                case ZEND_FAST_CONCAT:
                        return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
@@ -4411,8 +4443,8 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                                        (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
                        } else if (opline->extended_value == ZEND_DIV ||
                                opline->extended_value == ZEND_MOD) {
-                               if (!OP2_HAS_RANGE() ||
-                                       (OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
+                               if (!OP2_HAS_RANGE_EX() ||
+                                       (OP2_MIN_RANGE_EX() <= 0 && OP2_MAX_RANGE_EX() >= 0)) {
                                        /* Division by zero */
                                        return 1;
                                }
@@ -4427,8 +4459,8 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                                opline->extended_value == 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;
+                                       !OP2_HAS_RANGE_EX() ||
+                                       OP2_MIN_RANGE_EX() < 0;
                        } else if (opline->extended_value == ZEND_CONCAT) {
                                return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
                                        (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
@@ -4452,7 +4484,7 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                        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:
                        if ((opline+1)->op1_type == IS_CV) {
-                               if (_ssa_op1_info(op_array, ssa, opline+1) & MAY_BE_UNDEF) {
+                               if (_ssa_op1_info_ex(op_array, ssa, opline+1, ssa_op+1) & MAY_BE_UNDEF) {
                                        return 1;
                                }
                        }
@@ -4462,8 +4494,8 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                        if (t1 & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_OBJECT))) {
                                return 1;
                        }
-                       if (ssa->ops[opline - op_array->opcodes].op1_use) {
-                               zend_ssa_var_info *var_info = ssa->var_info + ssa->ops[opline - op_array->opcodes].op1_use;
+                       if (ssa_op->op1_use) {
+                               zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
                                zend_class_entry *ce = var_info->ce;
 
                                if (var_info->is_instanceof ||
@@ -4555,3 +4587,8 @@ int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ss
                        return 1;
        }
 }
+
+int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa)
+{
+       return zend_may_throw_ex(opline, &ssa->ops[opline - op_array->opcodes], op_array, ssa);
+}
index f01f6cc7865cf5fcdc184552947156520eb43b06..c5db1feede6e8c903b5f1e2d2639ca7f0ea93737 100644 (file)
@@ -26,6 +26,7 @@
 /* Bitmask for type inference (zend_ssa_var_info.type) */
 #include "zend_type_info.h"
 
+#define MAY_BE_GUARD                (1<<28) /* needs type guard */
 #define MAY_BE_IN_REG               (1<<29) /* value allocated in CPU register */
 
 //TODO: remome MAY_BE_RC1, MAY_BE_RCN???
        |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) \
+       static zend_always_inline zend_bool _ssa_##opN##_has_range_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
        { \
                if (opline->opN##_type == IS_CONST) { \
                        zval *zv = CRT_CONSTANT(opline->opN); \
                        return (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_TRUE || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_NULL); \
                } else { \
                        return (opline->opN##_type != IS_UNUSED && \
-                       ssa->ops && \
                        ssa->var_info && \
-                       ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \
-                           ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range); \
+                       ssa_op->opN##_use >= 0 && \
+                           ssa->var_info[ssa_op->opN##_use].has_range); \
                } \
                return 0; \
+       } \
+       static zend_always_inline zend_bool _ssa_##opN##_has_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       { \
+               return _ssa_##opN##_has_range_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 #define DEFINE_SSA_OP_MIN_RANGE(opN) \
-       static zend_always_inline zend_long _ssa_##opN##_min_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       static zend_always_inline zend_long _ssa_##opN##_min_range_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
        { \
                if (opline->opN##_type == IS_CONST) { \
                        zval *zv = CRT_CONSTANT(opline->opN); \
                                return 0; \
                        } \
                } else if (opline->opN##_type != IS_UNUSED && \
-                   ssa->ops && \
                    ssa->var_info && \
-                   ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \
-                   ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \
-                       return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.min; \
+                   ssa_op->opN##_use >= 0 && \
+                   ssa->var_info[ssa_op->opN##_use].has_range) { \
+                       return ssa->var_info[ssa_op->opN##_use].range.min; \
                } \
                return ZEND_LONG_MIN; \
+       } \
+       static zend_always_inline zend_long _ssa_##opN##_min_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       { \
+               return _ssa_##opN##_min_range_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 #define DEFINE_SSA_OP_MAX_RANGE(opN) \
-       static zend_always_inline zend_long _ssa_##opN##_max_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       static zend_always_inline zend_long _ssa_##opN##_max_range_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
        { \
                if (opline->opN##_type == IS_CONST) { \
                        zval *zv = CRT_CONSTANT(opline->opN); \
                                return 0; \
                        } \
                } else if (opline->opN##_type != IS_UNUSED && \
-                   ssa->ops && \
                    ssa->var_info && \
-                   ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \
-                   ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \
-                       return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.max; \
+                   ssa_op->opN##_use >= 0 && \
+                   ssa->var_info[ssa_op->opN##_use].has_range) { \
+                       return ssa->var_info[ssa_op->opN##_use].range.max; \
                } \
                return ZEND_LONG_MAX; \
+       } \
+       static zend_always_inline zend_long _ssa_##opN##_max_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       { \
+               return _ssa_##opN##_max_range_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 #define DEFINE_SSA_OP_RANGE_UNDERFLOW(opN) \
-       static zend_always_inline char _ssa_##opN##_range_underflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       static zend_always_inline char _ssa_##opN##_range_underflow_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
        { \
                if (opline->opN##_type == IS_CONST) { \
                        zval *zv = CRT_CONSTANT(opline->opN); \
                                return 0; \
                        } \
                } else if (opline->opN##_type != IS_UNUSED && \
-                   ssa->ops && \
                    ssa->var_info && \
-                   ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \
-                   ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \
-                       return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.underflow; \
+                   ssa_op->opN##_use >= 0 && \
+                   ssa->var_info[ssa_op->opN##_use].has_range) { \
+                       return ssa->var_info[ssa_op->opN##_use].range.underflow; \
                } \
                return 1; \
+       } \
+       static zend_always_inline char _ssa_##opN##_range_underflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       { \
+               return _ssa_##opN##_range_underflow_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 #define DEFINE_SSA_OP_RANGE_OVERFLOW(opN) \
-       static zend_always_inline char _ssa_##opN##_range_overflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       static zend_always_inline char _ssa_##opN##_range_overflow_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
        { \
                if (opline->opN##_type == IS_CONST) { \
                        zval *zv = CRT_CONSTANT(opline->opN); \
                                return 0; \
                        } \
                } else if (opline->opN##_type != IS_UNUSED && \
-                   ssa->ops && \
                    ssa->var_info && \
-                   ssa->ops[opline - op_array->opcodes].opN##_use >= 0 && \
-                   ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].has_range) { \
-                       return ssa->var_info[ssa->ops[opline - op_array->opcodes].opN##_use].range.overflow; \
+                   ssa_op->opN##_use >= 0 && \
+                   ssa->var_info[ssa_op->opN##_use].has_range) { \
+                       return ssa->var_info[ssa_op->opN##_use].range.overflow; \
                } \
                return 1; \
+       } \
+       static zend_always_inline char _ssa_##opN##_range_overflow(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       { \
+               return _ssa_##opN##_range_overflow_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 DEFINE_SSA_OP_HAS_RANGE(op1)
@@ -158,6 +174,17 @@ DEFINE_SSA_OP_RANGE_OVERFLOW(op2)
 #define OP2_RANGE_UNDERFLOW()   (_ssa_op2_range_underflow (op_array, ssa, opline))
 #define OP2_RANGE_OVERFLOW()    (_ssa_op2_range_overflow (op_array, ssa, opline))
 
+#define OP1_HAS_RANGE_EX()       (_ssa_op1_has_range_ex (op_array, ssa, opline, ssa_op))
+#define OP1_MIN_RANGE_EX()       (_ssa_op1_min_range_ex (op_array, ssa, opline, ssa_op))
+#define OP1_MAX_RANGE_EX()       (_ssa_op1_max_range_ex (op_array, ssa, opline, ssa_op))
+#define OP1_RANGE_UNDERFLOW_EX() (_ssa_op1_range_underflow_ex (op_array, ssa, opline, ssa_op))
+#define OP1_RANGE_OVERFLOW_EX()  (_ssa_op1_range_overflow_ex (op_array, ssa, opline, ssa_op))
+#define OP2_HAS_RANGE_EX()       (_ssa_op2_has_range_ex (op_array, ssa, opline, ssa_op))
+#define OP2_MIN_RANGE_EX()       (_ssa_op2_min_range_ex (op_array, ssa, opline, ssa_op))
+#define OP2_MAX_RANGE_EX()       (_ssa_op2_max_range_ex (op_array, ssa, opline, ssa_op))
+#define OP2_RANGE_UNDERFLOW_EX() (_ssa_op2_range_underflow_ex (op_array, ssa, opline, ssa_op))
+#define OP2_RANGE_OVERFLOW_EX()  (_ssa_op2_range_overflow_ex (op_array, ssa, opline, ssa_op))
+
 static zend_always_inline uint32_t _const_op_type(const zval *zv) {
        if (Z_TYPE_P(zv) == IS_CONSTANT_AST) {
                return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
@@ -204,19 +231,27 @@ static zend_always_inline uint32_t get_ssa_var_info(const zend_ssa *ssa, int ssa
 }
 
 #define DEFINE_SSA_OP_INFO(opN) \
-       static zend_always_inline uint32_t _ssa_##opN##_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       static zend_always_inline uint32_t _ssa_##opN##_info_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
        {                                                                                                                                               \
                if (opline->opN##_type == IS_CONST) {                                                   \
                        return _const_op_type(CRT_CONSTANT(opline->opN)); \
                } else { \
-                       return get_ssa_var_info(ssa, ssa->ops ? ssa->ops[opline - op_array->opcodes].opN##_use : -1); \
+                       return get_ssa_var_info(ssa, ssa->var_info ? ssa_op->opN##_use : -1); \
                } \
+       } \
+       static zend_always_inline uint32_t _ssa_##opN##_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
+       { \
+               return _ssa_##opN##_info_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 #define DEFINE_SSA_OP_DEF_INFO(opN) \
+       static zend_always_inline uint32_t _ssa_##opN##_def_info_ex(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op) \
+       { \
+               return get_ssa_var_info(ssa, ssa->var_info ? ssa_op->opN##_def : -1); \
+       } \
        static zend_always_inline uint32_t _ssa_##opN##_def_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline) \
        { \
-               return get_ssa_var_info(ssa, ssa->ops ? ssa->ops[opline - op_array->opcodes].opN##_def : -1); \
+               return _ssa_##opN##_def_info_ex(op_array, ssa, opline, ssa->ops + (opline - op_array->opcodes)); \
        }
 
 
@@ -238,6 +273,17 @@ DEFINE_SSA_OP_DEF_INFO(result)
 #define OP2_DATA_DEF_INFO()     (_ssa_op2_def_info(op_array, ssa, (opline+1)))
 #define RES_INFO()              (_ssa_result_def_info(op_array, ssa, opline))
 
+#define OP1_INFO_EX()           (_ssa_op1_info_ex(op_array, ssa, opline, ssa_op))
+#define OP2_INFO_EX()           (_ssa_op2_info_ex(op_array, ssa, opline, ssa_op))
+#define OP1_DATA_INFO_EX()      (_ssa_op1_info_ex(op_array, ssa, (opline+1), (ssa_op+1)))
+#define OP2_DATA_INFO_EX()      (_ssa_op2_info_ex(op_array, ssa, (opline+1), (ssa_op+1)))
+#define RES_USE_INFO_EX()       (_ssa_result_info_ex(op_array, ssa, opline, ssa_op))
+#define OP1_DEF_INFO_EX()       (_ssa_op1_def_info_ex(op_array, ssa, opline, ssa_op))
+#define OP2_DEF_INFO_EX()       (_ssa_op2_def_info_ex(op_array, ssa, opline, ssa_op))
+#define OP1_DATA_DEF_INFO_EX()  (_ssa_op1_def_info_ex(op_array, ssa, (opline+1), (ssa_op+1)))
+#define OP2_DATA_DEF_INFO_EX()  (_ssa_op2_def_info_ex(op_array, ssa, (opline+1), (ssa_op+1)))
+#define RES_INFO_EX()           (_ssa_result_def_info_ex(op_array, ssa, opline, ssa_op))
+
 static zend_always_inline zend_bool zend_add_will_overflow(zend_long a, zend_long b) {
        return (b > 0 && a > ZEND_LONG_MAX - b)
                || (b < 0 && a < ZEND_LONG_MIN - b);
@@ -274,8 +320,17 @@ void zend_func_return_info(const zend_op_array   *op_array,
                            int                    widening,
                            zend_ssa_var_info     *ret);
 
+int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa);
 int zend_may_throw(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa);
 
+int zend_update_type_info(const zend_op_array *op_array,
+                          zend_ssa            *ssa,
+                          const zend_script   *script,
+                          zend_op             *opline,
+                          zend_ssa_op         *ssa_op,
+                          const zend_op      **ssa_opcodes,
+                          zend_long            optimization_level);
+
 END_EXTERN_C()
 
 #endif /* ZEND_INFERENCE_H */
index 4cd9619f2045993c77fd8dbd391b9a97c35b307b..10e772d4bafcfc7a96ae58eaae82b2cb9a23e8fa 100644 (file)
@@ -537,6 +537,237 @@ static void place_essa_pis(
 }
 /* }}} */
 
+static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *opline, uint32_t k, uint32_t build_flags, int ssa_vars_count, zend_ssa_op *ssa_ops, int *var) /* {{{ */
+{
+       const zend_op *next;
+
+       if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+               ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(opline->op1.var)];
+               //USE_SSA_VAR(op_array->last_var + opline->op1.var)
+       }
+       if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+               ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)];
+               //USE_SSA_VAR(op_array->last_var + opline->op2.var)
+       }
+       if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
+        && opline->result_type == IS_CV
+        && opline->opcode != ZEND_RECV) {
+               ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
+               //USE_SSA_VAR(op_array->last_var + opline->result.var)
+       }
+
+       switch (opline->opcode) {
+               case ZEND_ASSIGN:
+                       if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
+                               ssa_ops[k].op2_def = ssa_vars_count;
+                               var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+                               ssa_vars_count++;
+                               //NEW_SSA_VAR(opline->op2.var)
+                       }
+                       if (opline->op1_type == IS_CV) {
+add_op1_def:
+                               ssa_ops[k].op1_def = ssa_vars_count;
+                               var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count;
+                               ssa_vars_count++;
+                               //NEW_SSA_VAR(opline->op1.var)
+                       }
+                       break;
+               case ZEND_ASSIGN_REF:
+                       if (opline->op2_type == IS_CV) {
+                               ssa_ops[k].op2_def = ssa_vars_count;
+                               var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+                               ssa_vars_count++;
+                               //NEW_SSA_VAR(opline->op2.var)
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_DIM:
+               case ZEND_ASSIGN_OBJ:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
+                               if (build_flags & ZEND_SSA_RC_INFERENCE && next->op1_type == IS_CV) {
+                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
+                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+                                       ssa_vars_count++;
+                                       //NEW_SSA_VAR(next->op1.var)
+                               }
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_OBJ_REF:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
+                               if (next->op1_type == IS_CV) {
+                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
+                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+                                       ssa_vars_count++;
+                                       //NEW_SSA_VAR(next->op1.var)
+                               }
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_STATIC_PROP:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
+#if 0
+                               if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
+                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
+                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+                                       ssa_vars_count++;
+                                       //NEW_SSA_VAR(next->op1.var)
+                               }
+#endif
+                       }
+                       break;
+               case ZEND_ASSIGN_STATIC_PROP_REF:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
+                               if (next->op1_type == IS_CV) {
+                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
+                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+                                       ssa_vars_count++;
+                                       //NEW_SSA_VAR(next->op1.var)
+                               }
+                       }
+                       break;
+               case ZEND_ASSIGN_STATIC_PROP_OP:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
+                       }
+                       break;
+               case ZEND_ASSIGN_DIM_OP:
+               case ZEND_ASSIGN_OBJ_OP:
+                       next = opline + 1;
+                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
+                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
+                       }
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ASSIGN_OP:
+               case ZEND_PRE_INC:
+               case ZEND_PRE_DEC:
+               case ZEND_POST_INC:
+               case ZEND_POST_DEC:
+               case ZEND_BIND_GLOBAL:
+               case ZEND_BIND_STATIC:
+               case ZEND_SEND_VAR_NO_REF:
+               case ZEND_SEND_VAR_NO_REF_EX:
+               case ZEND_SEND_VAR_EX:
+               case ZEND_SEND_FUNC_ARG:
+               case ZEND_SEND_REF:
+               case ZEND_SEND_UNPACK:
+               case ZEND_FE_RESET_RW:
+               case ZEND_MAKE_REF:
+               case ZEND_PRE_INC_OBJ:
+               case ZEND_PRE_DEC_OBJ:
+               case ZEND_POST_INC_OBJ:
+               case ZEND_POST_DEC_OBJ:
+               case ZEND_UNSET_DIM:
+               case ZEND_UNSET_OBJ:
+               case ZEND_FETCH_DIM_W:
+               case ZEND_FETCH_DIM_RW:
+               case ZEND_FETCH_DIM_FUNC_ARG:
+               case ZEND_FETCH_DIM_UNSET:
+               case ZEND_FETCH_LIST_W:
+                       if (opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_SEND_VAR:
+               case ZEND_CAST:
+               case ZEND_QM_ASSIGN:
+               case ZEND_JMP_SET:
+               case ZEND_COALESCE:
+               case ZEND_FE_RESET_R:
+                       if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_ADD_ARRAY_UNPACK:
+                       ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
+                       break;
+               case ZEND_ADD_ARRAY_ELEMENT:
+                       ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
+                       /* break missing intentionally */
+               case ZEND_INIT_ARRAY:
+                       if (((build_flags & ZEND_SSA_RC_INFERENCE)
+                                               || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF))
+                                       && opline->op1_type == IS_CV) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_YIELD:
+                       if (opline->op1_type == IS_CV
+                                       && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
+                                               || (build_flags & ZEND_SSA_RC_INFERENCE))) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_UNSET_CV:
+                       goto add_op1_def;
+               case ZEND_VERIFY_RETURN_TYPE:
+                       if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+                               goto add_op1_def;
+                       }
+                       break;
+               case ZEND_FE_FETCH_R:
+               case ZEND_FE_FETCH_RW:
+                       if (opline->op2_type != IS_CV) {
+                               ssa_ops[k].op2_use = -1; /* not used */
+                       }
+                       ssa_ops[k].op2_def = ssa_vars_count;
+                       var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+                       ssa_vars_count++;
+                       //NEW_SSA_VAR(opline->op2.var)
+                       break;
+               case ZEND_BIND_LEXICAL:
+                       if ((opline->extended_value & ZEND_BIND_REF) || (build_flags & ZEND_SSA_RC_INFERENCE)) {
+                               ssa_ops[k].op2_def = ssa_vars_count;
+                               var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
+                               ssa_vars_count++;
+                               //NEW_SSA_VAR(opline->op2.var)
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
+               ssa_ops[k].result_def = ssa_vars_count;
+               var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count;
+               ssa_vars_count++;
+               //NEW_SSA_VAR(op_array->last_var + opline->result.var)
+       }
+
+       return ssa_vars_count;
+}
+/* }}} */
+
+int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *opline, uint32_t k, uint32_t build_flags, int ssa_vars_count, zend_ssa_op *ssa_ops, int *var) /* {{{ */
+{
+       return _zend_ssa_rename_op(op_array, opline, k, build_flags, ssa_vars_count, ssa_ops, var);
+}
+/* }}} */
+
 static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, int *var, int n) /* {{{ */
 {
        zend_basic_block *blocks = ssa->cfg.blocks;
@@ -544,7 +775,7 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags,
        zend_ssa_op *ssa_ops = ssa->ops;
        int ssa_vars_count = ssa->vars_count;
        int i, j;
-       zend_op *opline, *end, *next;
+       zend_op *opline, *end;
        int *tmp = NULL;
        ALLOCA_FLAG(use_heap = 0);
 
@@ -574,223 +805,7 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags,
        for (; opline < end; opline++) {
                uint32_t k = opline - op_array->opcodes;
                if (opline->opcode != ZEND_OP_DATA) {
-
-                       if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                               ssa_ops[k].op1_use = var[EX_VAR_TO_NUM(opline->op1.var)];
-                               //USE_SSA_VAR(op_array->last_var + opline->op1.var)
-                       }
-                       if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                               ssa_ops[k].op2_use = var[EX_VAR_TO_NUM(opline->op2.var)];
-                               //USE_SSA_VAR(op_array->last_var + opline->op2.var)
-                       }
-                       if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
-                        && opline->result_type == IS_CV
-                        && opline->opcode != ZEND_RECV) {
-                               ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
-                               //USE_SSA_VAR(op_array->last_var + opline->result.var)
-                       }
-
-                       switch (opline->opcode) {
-                               case ZEND_ASSIGN:
-                                       if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
-                                               ssa_ops[k].op2_def = ssa_vars_count;
-                                               var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
-                                               ssa_vars_count++;
-                                               //NEW_SSA_VAR(opline->op2.var)
-                                       }
-                                       if (opline->op1_type == IS_CV) {
-add_op1_def:
-                                               ssa_ops[k].op1_def = ssa_vars_count;
-                                               var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count;
-                                               ssa_vars_count++;
-                                               //NEW_SSA_VAR(opline->op1.var)
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_REF:
-                                       if (opline->op2_type == IS_CV) {
-                                               ssa_ops[k].op2_def = ssa_vars_count;
-                                               var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
-                                               ssa_vars_count++;
-                                               //NEW_SSA_VAR(opline->op2.var)
-                                       }
-                                       if (opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_DIM:
-                               case ZEND_ASSIGN_OBJ:
-                                       next = opline + 1;
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
-                                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
-                                               if (build_flags & ZEND_SSA_RC_INFERENCE && next->op1_type == IS_CV) {
-                                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
-                                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
-                                                       ssa_vars_count++;
-                                                       //NEW_SSA_VAR(next->op1.var)
-                                               }
-                                       }
-                                       if (opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_OBJ_REF:
-                                       next = opline + 1;
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
-                                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
-                                               if (next->op1_type == IS_CV) {
-                                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
-                                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
-                                                       ssa_vars_count++;
-                                                       //NEW_SSA_VAR(next->op1.var)
-                                               }
-                                       }
-                                       if (opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_STATIC_PROP:
-                                       next = opline + 1;
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
-                                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
-#if 0
-                                               if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
-                                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
-                                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
-                                                       ssa_vars_count++;
-                                                       //NEW_SSA_VAR(next->op1.var)
-                                               }
-#endif
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_STATIC_PROP_REF:
-                                       next = opline + 1;
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
-                                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
-                                               if (next->op1_type == IS_CV) {
-                                                       ssa_ops[k + 1].op1_def = ssa_vars_count;
-                                                       var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
-                                                       ssa_vars_count++;
-                                                       //NEW_SSA_VAR(next->op1.var)
-                                               }
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_STATIC_PROP_OP:
-                                       next = opline + 1;
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
-                                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_DIM_OP:
-                               case ZEND_ASSIGN_OBJ_OP:
-                                       next = opline + 1;
-                                       if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                                               ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
-                                               //USE_SSA_VAR(op_array->last_var + next->op1.var);
-                                       }
-                                       if (opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_ASSIGN_OP:
-                               case ZEND_PRE_INC:
-                               case ZEND_PRE_DEC:
-                               case ZEND_POST_INC:
-                               case ZEND_POST_DEC:
-                               case ZEND_BIND_GLOBAL:
-                               case ZEND_BIND_STATIC:
-                               case ZEND_SEND_VAR_NO_REF:
-                               case ZEND_SEND_VAR_NO_REF_EX:
-                               case ZEND_SEND_VAR_EX:
-                               case ZEND_SEND_FUNC_ARG:
-                               case ZEND_SEND_REF:
-                               case ZEND_SEND_UNPACK:
-                               case ZEND_FE_RESET_RW:
-                               case ZEND_MAKE_REF:
-                               case ZEND_PRE_INC_OBJ:
-                               case ZEND_PRE_DEC_OBJ:
-                               case ZEND_POST_INC_OBJ:
-                               case ZEND_POST_DEC_OBJ:
-                               case ZEND_UNSET_DIM:
-                               case ZEND_UNSET_OBJ:
-                               case ZEND_FETCH_DIM_W:
-                               case ZEND_FETCH_DIM_RW:
-                               case ZEND_FETCH_DIM_FUNC_ARG:
-                               case ZEND_FETCH_DIM_UNSET:
-                               case ZEND_FETCH_LIST_W:
-                                       if (opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_SEND_VAR:
-                               case ZEND_CAST:
-                               case ZEND_QM_ASSIGN:
-                               case ZEND_JMP_SET:
-                               case ZEND_COALESCE:
-                               case ZEND_FE_RESET_R:
-                                       if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_ADD_ARRAY_UNPACK:
-                                       ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
-                                       break;
-                               case ZEND_ADD_ARRAY_ELEMENT:
-                                       ssa_ops[k].result_use = var[EX_VAR_TO_NUM(opline->result.var)];
-                                       /* break missing intentionally */
-                               case ZEND_INIT_ARRAY:
-                                       if (((build_flags & ZEND_SSA_RC_INFERENCE)
-                                                               || (opline->extended_value & ZEND_ARRAY_ELEMENT_REF))
-                                                       && opline->op1_type == IS_CV) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_YIELD:
-                                       if (opline->op1_type == IS_CV
-                                                       && ((op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)
-                                                               || (build_flags & ZEND_SSA_RC_INFERENCE))) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_UNSET_CV:
-                                       goto add_op1_def;
-                               case ZEND_VERIFY_RETURN_TYPE:
-                                       if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
-                                               goto add_op1_def;
-                                       }
-                                       break;
-                               case ZEND_FE_FETCH_R:
-                               case ZEND_FE_FETCH_RW:
-                                       if (opline->op2_type != IS_CV) {
-                                               ssa_ops[k].op2_use = -1; /* not used */
-                                       }
-                                       ssa_ops[k].op2_def = ssa_vars_count;
-                                       var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
-                                       ssa_vars_count++;
-                                       //NEW_SSA_VAR(opline->op2.var)
-                                       break;
-                               case ZEND_BIND_LEXICAL:
-                                       if ((opline->extended_value & ZEND_BIND_REF) || (build_flags & ZEND_SSA_RC_INFERENCE)) {
-                                               ssa_ops[k].op2_def = ssa_vars_count;
-                                               var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
-                                               ssa_vars_count++;
-                                               //NEW_SSA_VAR(opline->op2.var)
-                                       }
-                                       break;
-                               default:
-                                       break;
-                       }
-
-                       if (opline->result_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
-                               ssa_ops[k].result_def = ssa_vars_count;
-                               var[EX_VAR_TO_NUM(opline->result.var)] = ssa_vars_count;
-                               ssa_vars_count++;
-                               //NEW_SSA_VAR(op_array->last_var + opline->result.var)
-                       }
+                       ssa_vars_count = _zend_ssa_rename_op(op_array, opline, k, build_flags, ssa_vars_count, ssa_ops, var);
                }
        }
 
index 5abe44c93d6eb1ca8e9310c68523e19e17bc6c3d..02544506fea5179d4dd0993274a911b0a5d96561 100644 (file)
@@ -142,6 +142,7 @@ typedef struct _zend_ssa {
 BEGIN_EXTERN_C()
 
 int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa);
+int zend_ssa_rename_op(const zend_op_array *op_array, const zend_op *opline, uint32_t k, uint32_t build_flags, int ssa_vars_count, zend_ssa_op *ssa_ops, int *var);
 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);
 
index 02e44c2654c0ebc2c47451f9fbbe1aa944df84be..d4f97de76df4fc039a4d2ba78d3a5d6a88d8f844 100644 (file)
@@ -13,5 +13,6 @@ $(builddir)/jit/zend_jit.lo: \
        $(srcdir)/jit/zend_jit_perf_dump.c \
        $(srcdir)/jit/zend_jit_oprofile.c \
        $(srcdir)/jit/zend_jit_vtune.c \
+       $(srcdir)/jit/zend_jit_trace.c \
        $(srcdir)/jit/zend_elf.c
 
index 282f81bb7427059deedbf09cefcf9aecd94cb69a..306c89f029bd44fc9c0ce2cd6d7e5b7e915b3cb0 100644 (file)
@@ -13,4 +13,5 @@ $(BUILD_DIR)\opcache_jit\zend_jit.obj: \
        ext/opcache/jit/zend_jit_gdb.c \
        ext/opcache/jit/zend_jit_perf_dump.c \
        ext/opcache/jit/zend_jit_oprofile.c \
+       ext/opcache/jit/zend_jit_trace.c \
        ext/opcache/jit/zend_jit_vtune.c
index 1b3fbcee2f2c94c08671372e2cb75338452fe453..7b13d3ce54ad6d3456677f2a7e19ff8b2e2a3efb 100644 (file)
@@ -24,7 +24,6 @@
 #include "Zend/zend_constants.h"
 #include "zend_smart_str.h"
 #include "jit/zend_jit.h"
-#include "jit/zend_jit_internal.h"
 
 #ifdef HAVE_JIT
 
 #include "Optimizer/zend_call_graph.h"
 #include "Optimizer/zend_dump.h"
 
+#include "jit/zend_jit_internal.h"
+
+#ifdef ZTS
+int zend_jit_globals_id;
+#else
+zend_jit_globals jit_globals;
+#endif
+
 //#define CONTEXT_THREADED_JIT
 #define ZEND_JIT_USE_RC_INFERENCE
 
@@ -49,6 +56,7 @@
 
 #define JIT_PREFIX      "JIT$"
 #define JIT_STUB_PREFIX "JIT$$"
+#define TRACE_PREFIX    "TRACE-"
 
 #define DASM_M_GROW(ctx, t, p, sz, need) \
   do { \
@@ -97,11 +105,17 @@ static zend_long jit_bisect_pos = 0;
 static const void *zend_jit_runtime_jit_handler = NULL;
 static const void *zend_jit_profile_jit_handler = NULL;
 static const void *zend_jit_func_counter_handler = NULL;
+static const void *zend_jit_ret_counter_handler = NULL;
 static const void *zend_jit_loop_counter_handler = NULL;
 
 static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa);
 static void ZEND_FASTCALL zend_runtime_jit(void);
 
+static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline, zend_jit_trace_rec *trace);
+static uint32_t zend_jit_trace_get_exit_point(const zend_op *from_opline, const zend_op *to_opline, zend_jit_trace_rec *trace);
+static const void *zend_jit_trace_get_exit_addr(uint32_t n);
+static void zend_jit_trace_add_code(const void *start, uint32_t size);
+
 static zend_bool zend_ssa_is_last_use(const zend_op_array *op_array, const zend_ssa *ssa, int var, int use)
 {
        if (ssa->vars[var].phi_use_chain) {
@@ -133,22 +147,26 @@ static zend_bool zend_long_is_power_of_two(zend_long x)
        return (x > 0) && !(x & (x - 1));
 }
 
-#define OP_RANGE(line, opN) \
+#define OP_RANGE_EX(ssa_op, opN) \
        (((opline->opN##_type & (IS_TMP_VAR|IS_VAR|IS_CV)) && \
-         ssa->ops && \
-         ssa->var_info && \
-         ssa->ops[line].opN##_use >= 0 && \
-         ssa->var_info[ssa->ops[line].opN##_use].has_range) ? \
-        &ssa->var_info[ssa->ops[line].opN##_use].range : NULL)
+         (ssa_op)->opN##_use >= 0 && \
+         ssa->var_info[(ssa_op)->opN##_use].has_range) ? \
+        &ssa->var_info[(ssa_op)->opN##_use].range : NULL)
+
+#define OP_RANGE(line, opN) \
+       (ssa->var_info ? OP_RANGE_EX(ssa->ops + (line), opN) : NULL)
 
 #define OP1_RANGE()      OP_RANGE(opline - op_array->opcodes, op1)
 #define OP2_RANGE()      OP_RANGE(opline - op_array->opcodes, op2)
 #define OP1_DATA_RANGE() OP_RANGE(opline - op_array->opcodes + 1, op1)
 
+#define OP1_RANGE_EX()      OP_RANGE_EX(ssa_op, op1)
+#define OP2_RANGE_EX()      OP_RANGE_EX(ssa_op, op2)
+#define OP1_DATA_RANGE_EX() OP_RANGE_EX(ssa_op + 1, op1)
+
 #include "dynasm/dasm_x86.h"
 #include "jit/zend_jit_x86.h"
 #include "jit/zend_jit_helpers.c"
-#include "jit/zend_jit_x86.c"
 #include "jit/zend_jit_disasm_x86.c"
 #ifndef _WIN32
 #include "jit/zend_jit_gdb.c"
@@ -159,6 +177,8 @@ static zend_bool zend_long_is_power_of_two(zend_long x)
 #endif
 #include "jit/zend_jit_vtune.c"
 
+#include "jit/zend_jit_x86.c"
+
 #if _WIN32
 # include <Windows.h>
 #else
@@ -168,8 +188,6 @@ static zend_bool zend_long_is_power_of_two(zend_long x)
 # endif
 #endif
 
-#define DASM_ALIGNMENT 16
-
 ZEND_EXT_API void zend_jit_status(zval *ret)
 {
        zval stats;
@@ -220,7 +238,8 @@ static void *dasm_link_and_encode(dasm_State             **dasm_state,
                                   zend_ssa                *ssa,
                                   const zend_op           *rt_opline,
                                   zend_lifetime_interval **ra,
-                                  const char              *name)
+                                  const char              *name,
+                                  zend_bool                is_trace)
 {
        size_t size;
        int ret;
@@ -289,6 +308,10 @@ static void *dasm_link_and_encode(dasm_State             **dasm_state,
        entry = *dasm_ptr;
        *dasm_ptr = (void*)((char*)*dasm_ptr + ZEND_MM_ALIGNED_SIZE_EX(size, DASM_ALIGNMENT));
 
+       if (is_trace) {
+               zend_jit_trace_add_code(entry, size);
+       }
+
        if (op_array && ssa) {
                int b;
 
@@ -339,7 +362,7 @@ static void *dasm_link_and_encode(dasm_State             **dasm_state,
        } else {
            if (ZCG(accel_directives).jit_debug & (ZEND_JIT_DEBUG_ASM_STUBS|ZEND_JIT_DEBUG_ASM)) {
                        zend_jit_disasm_add_symbol(name, (uintptr_t)entry, size);
-                   if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_ASM_STUBS) {
+                   if (is_trace || (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_ASM_STUBS) != 0) {
                                zend_jit_disasm(
                                        name,
                                        (op_array && op_array->filename) ? ZSTR_VAL(op_array->filename) : NULL,
@@ -411,9 +434,8 @@ static void *dasm_link_and_encode(dasm_State             **dasm_state,
        return entry;
 }
 
-static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa)
+static int zend_may_overflow_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa)
 {
-       uint32_t num;
        int res;
 
        if (!ssa->ops || !ssa->var_info) {
@@ -422,21 +444,18 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
        switch (opline->opcode) {
                case ZEND_PRE_INC:
                case ZEND_POST_INC:
-                       num = opline - op_array->opcodes;
-                       res = ssa->ops[num].op1_def;
+                       res = ssa_op->op1_def;
                        return (res < 0 ||
                                !ssa->var_info[res].has_range ||
                                ssa->var_info[res].range.overflow);
                case ZEND_PRE_DEC:
                case ZEND_POST_DEC:
-                       num = opline - op_array->opcodes;
-                       res = ssa->ops[num].op1_def;
+                       res = ssa_op->op1_def;
                        return (res < 0 ||
                                !ssa->var_info[res].has_range ||
                                ssa->var_info[res].range.underflow);
                case ZEND_ADD:
-                       num = opline - op_array->opcodes;
-                       res = ssa->ops[num].result_def;
+                       res = ssa_op->result_def;
                        if (res < 0 ||
                            !ssa->var_info[res].has_range) {
                                return 1;
@@ -444,11 +463,11 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                        if (ssa->var_info[res].range.underflow) {
                                zend_long op1_min, op2_min;
 
-                               if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                               if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                        return 1;
                                }
-                               op1_min = OP1_MIN_RANGE();
-                               op2_min = OP2_MIN_RANGE();
+                               op1_min = OP1_MIN_RANGE_EX();
+                               op2_min = OP2_MIN_RANGE_EX();
                                if (zend_add_will_overflow(op1_min, op2_min)) {
                                        return 1;
                                }
@@ -456,19 +475,18 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                        if (ssa->var_info[res].range.overflow) {
                                zend_long op1_max, op2_max;
 
-                               if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                               if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                        return 1;
                                }
-                               op1_max = OP1_MAX_RANGE();
-                               op2_max = OP2_MAX_RANGE();
+                               op1_max = OP1_MAX_RANGE_EX();
+                               op2_max = OP2_MAX_RANGE_EX();
                                if (zend_add_will_overflow(op1_max, op2_max)) {
                                        return 1;
                                }
                        }
                        return 0;
                case ZEND_SUB:
-                       num = opline - op_array->opcodes;
-                       res = ssa->ops[num].result_def;
+                       res = ssa_op->result_def;
                        if (res < 0 ||
                            !ssa->var_info[res].has_range) {
                                return 1;
@@ -476,11 +494,11 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                        if (ssa->var_info[res].range.underflow) {
                                zend_long op1_min, op2_max;
 
-                               if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                               if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                        return 1;
                                }
-                               op1_min = OP1_MIN_RANGE();
-                               op2_max = OP2_MAX_RANGE();
+                               op1_min = OP1_MIN_RANGE_EX();
+                               op2_max = OP2_MAX_RANGE_EX();
                                if (zend_sub_will_overflow(op1_min, op2_max)) {
                                        return 1;
                                }
@@ -488,27 +506,25 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                        if (ssa->var_info[res].range.overflow) {
                                zend_long op1_max, op2_min;
 
-                               if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                               if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                        return 1;
                                }
-                               op1_max = OP1_MAX_RANGE();
-                               op2_min = OP2_MIN_RANGE();
+                               op1_max = OP1_MAX_RANGE_EX();
+                               op2_min = OP2_MIN_RANGE_EX();
                                if (zend_sub_will_overflow(op1_max, op2_min)) {
                                        return 1;
                                }
                        }
                        return 0;
                case ZEND_MUL:
-                       num = opline - op_array->opcodes;
-                       res = ssa->ops[num].result_def;
+                       res = ssa_op->result_def;
                        return (res < 0 ||
                                !ssa->var_info[res].has_range ||
                                ssa->var_info[res].range.underflow ||
                                ssa->var_info[res].range.overflow);
                case ZEND_ASSIGN_OP:
                        if (opline->extended_value == ZEND_ADD) {
-                               num = opline - op_array->opcodes;
-                               res = ssa->ops[num].op1_def;
+                               res = ssa_op->op1_def;
                                if (res < 0 ||
                                    !ssa->var_info[res].has_range) {
                                        return 1;
@@ -516,11 +532,11 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                                if (ssa->var_info[res].range.underflow) {
                                        zend_long op1_min, op2_min;
 
-                                       if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                                       if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                                return 1;
                                        }
-                                       op1_min = OP1_MIN_RANGE();
-                                       op2_min = OP2_MIN_RANGE();
+                                       op1_min = OP1_MIN_RANGE_EX();
+                                       op2_min = OP2_MIN_RANGE_EX();
                                        if (zend_add_will_overflow(op1_min, op2_min)) {
                                                return 1;
                                        }
@@ -528,19 +544,18 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                                if (ssa->var_info[res].range.overflow) {
                                        zend_long op1_max, op2_max;
 
-                                       if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                                       if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                                return 1;
                                        }
-                                       op1_max = OP1_MAX_RANGE();
-                                       op2_max = OP2_MAX_RANGE();
+                                       op1_max = OP1_MAX_RANGE_EX();
+                                       op2_max = OP2_MAX_RANGE_EX();
                                        if (zend_add_will_overflow(op1_max, op2_max)) {
                                                return 1;
                                        }
                                }
                                return 0;
                        } else if (opline->extended_value == ZEND_SUB) {
-                               num = opline - op_array->opcodes;
-                               res = ssa->ops[num].op1_def;
+                               res = ssa_op->op1_def;
                                if (res < 0 ||
                                    !ssa->var_info[res].has_range) {
                                        return 1;
@@ -548,11 +563,11 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                                if (ssa->var_info[res].range.underflow) {
                                        zend_long op1_min, op2_max;
 
-                                       if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                                       if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                                return 1;
                                        }
-                                       op1_min = OP1_MIN_RANGE();
-                                       op2_max = OP2_MAX_RANGE();
+                                       op1_min = OP1_MIN_RANGE_EX();
+                                       op2_max = OP2_MAX_RANGE_EX();
                                        if (zend_sub_will_overflow(op1_min, op2_max)) {
                                                return 1;
                                        }
@@ -560,19 +575,18 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
                                if (ssa->var_info[res].range.overflow) {
                                        zend_long op1_max, op2_min;
 
-                                       if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
+                                       if (!OP1_HAS_RANGE_EX() || !OP2_HAS_RANGE_EX()) {
                                                return 1;
                                        }
-                                       op1_max = OP1_MAX_RANGE();
-                                       op2_min = OP2_MIN_RANGE();
+                                       op1_max = OP1_MAX_RANGE_EX();
+                                       op2_min = OP2_MIN_RANGE_EX();
                                        if (zend_sub_will_overflow(op1_max, op2_min)) {
                                                return 1;
                                        }
                                }
                                return 0;
                        } else if (opline->extended_value == ZEND_MUL) {
-                               num = opline - op_array->opcodes;
-                               res = ssa->ops[num].op1_def;
+                               res = ssa_op->op1_def;
                                return (res < 0 ||
                                        !ssa->var_info[res].has_range ||
                                        ssa->var_info[res].range.underflow ||
@@ -583,6 +597,11 @@ static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_arra
        }
 }
 
+static int zend_may_overflow(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa)
+{
+       return zend_may_overflow_ex(opline, &ssa->ops[opline - op_array->opcodes], op_array, ssa);
+}
+
 static int zend_jit_build_cfg(const zend_op_array *op_array, zend_cfg *cfg)
 {
        uint32_t flags;
@@ -2417,7 +2436,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                goto done;
                                        case ZEND_INIT_FCALL:
                                        case ZEND_INIT_FCALL_BY_NAME:
-                                               if (!zend_jit_init_fcall(&dasm_state, opline, b, op_array, ssa, call_level)) {
+                                               if (!zend_jit_init_fcall(&dasm_state, opline, b, op_array, ssa, call_level, NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2466,7 +2485,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                        case ZEND_DO_ICALL:
                                        case ZEND_DO_FCALL_BY_NAME:
                                        case ZEND_DO_FCALL:
-                                               if (!zend_jit_do_fcall(&dasm_state, opline, op_array, ssa, call_level, b + 1)) {
+                                               if (!zend_jit_do_fcall(&dasm_state, opline, op_array, ssa, call_level, b + 1, NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2503,7 +2522,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                                OP2_INFO(), OP2_REG_ADDR(),
                                                                res_addr,
                                                                zend_may_throw(opline, op_array, ssa),
-                                                               smart_branch_opcode, target_label, target_label2)) {
+                                                               smart_branch_opcode, target_label, target_label2,
+                                                               NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2530,7 +2550,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                                OP2_INFO(), OP2_REG_ADDR(),
                                                                RES_REG_ADDR(),
                                                                zend_may_throw(opline, op_array, ssa),
-                                                               smart_branch_opcode, target_label, target_label2)) {
+                                                               smart_branch_opcode, target_label, target_label2,
+                                                               NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2550,7 +2571,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                        smart_branch_opcode = 0;
                                                        target_label = target_label2 = (uint32_t)-1;
                                                }
-                                               if (!zend_jit_defined(&dasm_state, opline, op_array, smart_branch_opcode, target_label, target_label2)) {
+                                               if (!zend_jit_defined(&dasm_state, opline, op_array, smart_branch_opcode, target_label, target_label2, NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2574,7 +2595,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                        smart_branch_opcode = 0;
                                                        target_label = target_label2 = (uint32_t)-1;
                                                }
-                                               if (!zend_jit_type_check(&dasm_state, opline, op_array, OP1_INFO(), smart_branch_opcode, target_label, target_label2)) {
+                                               if (!zend_jit_type_check(&dasm_state, opline, op_array, OP1_INFO(), smart_branch_opcode, target_label, target_label2, NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2589,9 +2610,38 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                        if (!zend_jit_tail_handler(&dasm_state, opline)) {
                                                                goto jit_failure;
                                                        }
-                                               } else if (!zend_jit_return(&dasm_state, opline, op_array, ssa,
-                                                               op1_info, OP1_REG_ADDR())) {
-                                                       goto jit_failure;
+                                               } else {
+                                                       int j;
+
+                                                       if (!zend_jit_return(&dasm_state, opline, op_array,
+                                                                       op1_info, OP1_REG_ADDR())) {
+                                                               goto jit_failure;
+                                                       }
+                                                       if (jit_return_label >= 0) {
+                                                               if (!zend_jit_jmp(&dasm_state, jit_return_label)) {
+                                                                       goto jit_failure;
+                                                               }
+                                                               goto done;
+                                                       }
+                                                       jit_return_label = ssa->cfg.blocks_count * 2;
+                                                       if (!zend_jit_label(&dasm_state, jit_return_label)) {
+                                                               goto jit_failure;
+                                                       }
+                                                       if (!zend_jit_leave_frame(&dasm_state)) {
+                                                               goto jit_failure;
+                                                       }
+                                                       for (j = 0 ; j < op_array->last_var; j++) {
+                                                               uint32_t info = zend_ssa_cv_info(opline, op_array, ssa, j);
+
+                                                               if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
+                                                                       if (!zend_jit_free_cv(&dasm_state, opline, op_array, info, j)) {
+                                                                               goto jit_failure;
+                                                                       }
+                                                               }
+                                                       }
+                                                   if (!zend_jit_leave_func(&dasm_state, opline, op_array, NULL)) {
+                                                               goto jit_failure;
+                                                   }
                                                }
                                                goto done;
                                        case ZEND_BOOL:
@@ -2599,7 +2649,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                if (!zend_jit_bool_jmpznz(&dasm_state, opline, op_array,
                                                                OP1_INFO(), OP1_REG_ADDR(), RES_REG_ADDR(),
                                                                -1, -1,
-                                                               zend_may_throw(opline, op_array, ssa))) {
+                                                               zend_may_throw(opline, op_array, ssa),
+                                                               opline->opcode, NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2625,7 +2676,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                if (!zend_jit_bool_jmpznz(&dasm_state, opline, op_array,
                                                                OP1_INFO(), OP1_REG_ADDR(), res_addr,
                                                                ssa->cfg.blocks[b].successors[0], ssa->cfg.blocks[b].successors[1],
-                                                               zend_may_throw(opline, op_array, ssa))) {
+                                                               zend_may_throw(opline, op_array, ssa),
+                                                               opline->opcode, NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2666,7 +2718,8 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                if (!zend_jit_isset_isempty_dim(&dasm_state, opline, op_array,
                                                                OP1_INFO(), OP2_INFO(),
                                                                zend_may_throw(opline, op_array, ssa),
-                                                               smart_branch_opcode, target_label, target_label2)) {
+                                                               smart_branch_opcode, target_label, target_label2,
+                                                               NULL)) {
                                                        goto jit_failure;
                                                }
                                                goto done;
@@ -2895,7 +2948,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
                                                                zend_jit_call(&dasm_state, next_opline, b + 1);
                                                                is_terminated = 1;
                                                        } else {
-                                                               zend_jit_do_fcall(&dasm_state, next_opline, op_array, ssa, call_level, b + 1);
+                                                               zend_jit_do_fcall(&dasm_state, next_opline, op_array, ssa, call_level, b + 1, NULL);
                                                        }
                                                }
                                        }
@@ -2916,7 +2969,7 @@ done:
                }
        }
 
-       handler = dasm_link_and_encode(&dasm_state, op_array, ssa, rt_opline, ra, NULL);
+       handler = dasm_link_and_encode(&dasm_state, op_array, ssa, rt_opline, ra, NULL, 0);
        if (!handler) {
                goto jit_failure;
        }
@@ -3072,17 +3125,16 @@ void zend_jit_check_funcs(HashTable *function_table, zend_bool is_method) {
 void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend_op *opline)
 {
        zend_op_array *op_array = &EX(func)->op_array;
-       zend_jit_op_array_extension *jit_extension;
+       zend_jit_op_array_hot_extension *jit_extension;
        uint32_t i;
 
        zend_shared_alloc_lock();
-       jit_extension = (zend_jit_op_array_extension*)ZEND_FUNC_INFO(op_array);
+       jit_extension = (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(op_array);
 
        if (jit_extension) {
                SHM_UNPROTECT();
                zend_jit_unprotect();
 
-               *(jit_extension->counter) = ZEND_JIT_HOT_COUNTER_INIT;
                for (i = 0; i < op_array->last; i++) {
                        op_array->opcodes[i].handler = jit_extension->orig_handlers[i];
                }
@@ -3103,7 +3155,7 @@ void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend
 static int zend_jit_setup_hot_counters(zend_op_array *op_array)
 {
        zend_op *opline = op_array->opcodes;
-       zend_jit_op_array_extension *jit_extension;
+       zend_jit_op_array_hot_extension *jit_extension;
        zend_cfg cfg;
        uint32_t i;
 
@@ -3114,7 +3166,7 @@ static int zend_jit_setup_hot_counters(zend_op_array *op_array)
                return FAILURE;
        }
 
-       jit_extension = (zend_jit_op_array_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_extension) + (op_array->last - 1) * sizeof(void*));
+       jit_extension = (zend_jit_op_array_hot_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_hot_extension) + (op_array->last - 1) * sizeof(void*));
        jit_extension->counter = &zend_jit_hot_counters[zend_jit_op_array_hash(op_array) & (ZEND_HOT_COUNTERS_COUNT - 1)];
        for (i = 0; i < op_array->last; i++) {
                jit_extension->orig_handlers[i] = op_array->opcodes[i].handler;
@@ -3158,6 +3210,8 @@ static int zend_needs_manual_jit(const zend_op_array *op_array)
        return 0;
 }
 
+#include "jit/zend_jit_trace.c"
+
 ZEND_EXT_API int zend_jit_op_array(zend_op_array *op_array, zend_script *script)
 {
        if (dasm_ptr == NULL) {
@@ -3195,6 +3249,8 @@ ZEND_EXT_API int zend_jit_op_array(zend_op_array *op_array, zend_script *script)
                return SUCCESS;
        } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_COUNTERS) {
                return zend_jit_setup_hot_counters(op_array);
+       } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+               return zend_jit_setup_hot_trace_counters(op_array);
        } else if (zend_jit_trigger == ZEND_JIT_ON_SCRIPT_LOAD) {
                return zend_real_jit_func(op_array, script, NULL);
        } else if (zend_jit_trigger == ZEND_JIT_ON_DOC_COMMENT) {
@@ -3230,7 +3286,8 @@ ZEND_EXT_API int zend_jit_script(zend_script *script)
 
        if (zend_jit_trigger == ZEND_JIT_ON_FIRST_EXEC ||
            zend_jit_trigger == ZEND_JIT_ON_PROF_REQUEST ||
-           zend_jit_trigger == ZEND_JIT_ON_HOT_COUNTERS) {
+           zend_jit_trigger == ZEND_JIT_ON_HOT_COUNTERS ||
+           zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
                for (i = 0; i < call_graph.op_arrays_count; i++) {
                        ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL);
                        if (zend_jit_op_array(call_graph.op_arrays[i], script) != SUCCESS) {
@@ -3381,7 +3438,7 @@ static int zend_jit_make_stubs(void)
                if (!zend_jit_stubs[i].stub(&dasm_state)) {
                        return 0;
                }
-               if (!dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, zend_jit_stubs[i].name)) {
+               if (!dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, zend_jit_stubs[i].name, 0)) {
                        return 0;
                }
        }
@@ -3392,7 +3449,7 @@ static int zend_jit_make_stubs(void)
                        if (!zend_jit_hybrid_runtime_jit_stub(&dasm_state)) {
                                return 0;
                        }
-                       zend_jit_runtime_jit_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_runtime_jit");
+                       zend_jit_runtime_jit_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_runtime_jit", 0);
                        if (!zend_jit_runtime_jit_handler) {
                                return 0;
                        }
@@ -3401,7 +3458,7 @@ static int zend_jit_make_stubs(void)
                        if (!zend_jit_hybrid_profile_jit_stub(&dasm_state)) {
                                return 0;
                        }
-                       zend_jit_profile_jit_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_profile_jit");
+                       zend_jit_profile_jit_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_profile_jit", 0);
                        if (!zend_jit_profile_jit_handler) {
                                return 0;
                        }
@@ -3410,7 +3467,7 @@ static int zend_jit_make_stubs(void)
                        if (!zend_jit_hybrid_func_counter_stub(&dasm_state)) {
                                return 0;
                        }
-                       zend_jit_func_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_func_counter");
+                       zend_jit_func_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_func_counter", 0);
                        if (!zend_jit_func_counter_handler) {
                                return 0;
                        }
@@ -3419,26 +3476,73 @@ static int zend_jit_make_stubs(void)
                        if (!zend_jit_hybrid_loop_counter_stub(&dasm_state)) {
                                return 0;
                        }
-                       zend_jit_loop_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_loop_counter");
+                       zend_jit_loop_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_loop_counter", 0);
+                       if (!zend_jit_loop_counter_handler) {
+                               return 0;
+                       }
+               } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                       dasm_setup(&dasm_state, dasm_actions);
+                       if (!zend_jit_hybrid_func_trace_counter_stub(&dasm_state)) {
+                               return 0;
+                       }
+                       zend_jit_func_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_func_counter", 0);
+                       if (!zend_jit_func_counter_handler) {
+                               return 0;
+                       }
+
+                       dasm_setup(&dasm_state, dasm_actions);
+                       if (!zend_jit_hybrid_ret_trace_counter_stub(&dasm_state)) {
+                               return 0;
+                       }
+                       zend_jit_ret_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_ret_counter", 0);
+                       if (!zend_jit_ret_counter_handler) {
+                               return 0;
+                       }
+
+                       dasm_setup(&dasm_state, dasm_actions);
+                       if (!zend_jit_hybrid_loop_trace_counter_stub(&dasm_state)) {
+                               return 0;
+                       }
+                       zend_jit_loop_counter_handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, "JIT$$hybrid_loop_counter", 0);
                        if (!zend_jit_loop_counter_handler) {
                                return 0;
                        }
                }
        } else {
-               zend_jit_runtime_jit_handler = (const void*)zend_runtime_jit;
-               zend_jit_profile_jit_handler = (const void*)zend_jit_profile_helper;
-               zend_jit_func_counter_handler = (const void*)zend_jit_func_counter_helper;
-               zend_jit_loop_counter_handler = (const void*)zend_jit_loop_counter_helper;
+               if (zend_jit_trigger == ZEND_JIT_ON_FIRST_EXEC) {
+                       zend_jit_runtime_jit_handler = (const void*)zend_runtime_jit;
+               } else if (zend_jit_trigger == ZEND_JIT_ON_PROF_REQUEST) {
+                       zend_jit_profile_jit_handler = (const void*)zend_jit_profile_helper;
+               } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_COUNTERS) {
+                       zend_jit_func_counter_handler = (const void*)zend_jit_func_counter_helper;
+                       zend_jit_loop_counter_handler = (const void*)zend_jit_loop_counter_helper;
+               } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                       zend_jit_func_counter_handler = (const void*)zend_jit_func_trace_helper;
+                       zend_jit_ret_counter_handler = (const void*)zend_jit_ret_trace_helper;
+                       zend_jit_loop_counter_handler = (const void*)zend_jit_loop_trace_helper;
+               }
        }
 
        dasm_free(&dasm_state);
        return 1;
 }
 
+static void zend_jit_globals_ctor(zend_jit_globals *jit_globals)
+{
+       memset(jit_globals, 0, sizeof(zend_jit_globals));
+       zend_jit_trace_init_caches();
+}
+
 ZEND_EXT_API int zend_jit_startup(zend_long jit, void *buf, size_t size, zend_bool reattached)
 {
        int ret;
 
+#ifdef ZTS
+       zend_jit_globals_id = ts_allocate_id(&zend_jit_globals_id, sizeof(zend_jit_globals), (ts_allocate_ctor) zend_jit_globals_ctor, NULL);
+#else
+       zend_jit_globals_ctor(&jit_globals);
+#endif
+
        zend_jit_level = ZEND_JIT_LEVEL(jit);
        zend_jit_trigger = ZEND_JIT_TRIGGER(jit);
        zend_jit_reg_alloc = ZEND_JIT_REG_ALLOC(jit);
@@ -3549,6 +3653,12 @@ ZEND_EXT_API int zend_jit_startup(zend_long jit, void *buf, size_t size, zend_bo
 #endif
        }
 
+       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+               if (zend_jit_trace_startup() != SUCCESS) {
+                       return FAILURE;
+               }
+       }
+
        return SUCCESS;
 }
 
@@ -3587,6 +3697,14 @@ ZEND_EXT_API void zend_jit_activate(void)
                for (i = 0; i < ZEND_HOT_COUNTERS_COUNT; i++) {
                        zend_jit_hot_counters[i] = ZEND_JIT_HOT_COUNTER_INIT;
                }
+       } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+               int i;
+
+               for (i = 0; i < ZEND_HOT_COUNTERS_COUNT; i++) {
+                       zend_jit_hot_counters[i] = ZEND_JIT_TRACE_COUNTER_INIT;
+               }
+
+               zend_jit_trace_reset_caches();
        }
 }
 
index 5234660398067370925c5f47f735182c3c4e577d..aea15ea1b713ef9b5edcc303961972b0a0d7419a 100644 (file)
@@ -33,6 +33,7 @@
 #define ZEND_JIT_ON_PROF_REQUEST   2     /* compile the most frequently caled on first request functions */
 #define ZEND_JIT_ON_HOT_COUNTERS   3     /* compile functions after N calls or loop iterations */
 #define ZEND_JIT_ON_DOC_COMMENT    4     /* compile functions with "@jit" tag in doc-comments */
+#define ZEND_JIT_ON_HOT_TRACE      5     /* trace functions after N calls or loop iterations */
 
 #define ZEND_JIT_TRIGGER(n)        (((n) / 10) % 10)
 
 
 #define ZEND_JIT_DEBUG_GDB       (1<<8)
 
+#define ZEND_JIT_DEBUG_TRACE_START     (1<<12)
+#define ZEND_JIT_DEBUG_TRACE_STOP      (1<<13)
+#define ZEND_JIT_DEBUG_TRACE_COMPILED  (1<<14)
+#define ZEND_JIT_DEBUG_TRACE_EXIT      (1<<15)
+#define ZEND_JIT_DEBUG_TRACE_ABORT     (1<<16)
+#define ZEND_JIT_DEBUG_TRACE_BLACKLIST (1<<17)
+#define ZEND_JIT_DEBUG_TRACE_BYTECODE  (1<<18)
+#define ZEND_JIT_DEBUG_TRACE_TSSA      (1<<19)
+
 ZEND_EXT_API int  zend_jit_op_array(zend_op_array *op_array, zend_script *script);
 ZEND_EXT_API int  zend_jit_script(zend_script *script);
 ZEND_EXT_API void zend_jit_unprotect(void);
index 05ece4d2766f5d88278795deb9179c91728965db..d784170d7c43858a2d69cfc044ca7feb80ac1836 100644 (file)
@@ -137,6 +137,10 @@ static void zend_jit_disasm_add_symbol(const char *name,
                                }
                        } else {
                                ZEND_ASSERT(sym->addr == node->addr);
+                               if (strcmp(name, node->name) == 0 && sym->end < node->end) {
+                                       /* reduce size of the existing symbol */
+                                       node->end = sym->end;
+                               }
                                free(sym);
                                return;
                        }
@@ -397,6 +401,7 @@ static int zend_jit_disasm_init(void)
        REGISTER_HELPER(zend_jit_int_extend_stack_helper);
        REGISTER_HELPER(zend_jit_leave_nested_func_helper);
        REGISTER_HELPER(zend_jit_leave_top_func_helper);
+       REGISTER_HELPER(zend_jit_leave_func_helper);
        REGISTER_HELPER(zend_jit_symtable_find);
        REGISTER_HELPER(zend_jit_hash_index_lookup_rw);
        REGISTER_HELPER(zend_jit_hash_index_lookup_w);
@@ -448,6 +453,7 @@ static int zend_jit_disasm_init(void)
        REGISTER_HELPER(zend_jit_pre_dec);
        REGISTER_HELPER(zend_runtime_jit);
        REGISTER_HELPER(zend_jit_hot_func);
+       REGISTER_HELPER(zend_jit_check_constant);
 #undef  REGISTER_HELPER
 
 #ifndef _WIN32
index f327fd5128067f0f2e1f171d081d9f6b6d226a52..504cb67065f7278f9b2d923c5385f6b31dae49a4 100644 (file)
@@ -33,18 +33,11 @@ extern int zend_jit_profile_counter_rid;
 
 extern int16_t zend_jit_hot_counters[ZEND_HOT_COUNTERS_COUNT];
 
-void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend_op *opline);
-
-typedef struct _zend_jit_op_array_extension {
-       int16_t    *counter;
-       const void *orig_handlers[1];
-} zend_jit_op_array_extension;
-
-static zend_always_inline zend_long zend_jit_op_array_hash(const zend_op_array *op_array)
+static zend_always_inline zend_long zend_jit_hash(const void *ptr)
 {
        uintptr_t x;
 
-       x = (uintptr_t)op_array->opcodes >> 3;
+       x = (uintptr_t)ptr >> 3;
 #if SIZEOF_SIZE_T == 4
        x = ((x >> 16) ^ x) * 0x45d9f3b;
        x = ((x >> 16) ^ x) * 0x45d9f3b;
@@ -57,6 +50,16 @@ static zend_always_inline zend_long zend_jit_op_array_hash(const zend_op_array *
        return x;
 }
 
+void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend_op *opline);
+
+typedef struct _zend_jit_op_array_hot_extension {
+       int16_t    *counter;
+       const void *orig_handlers[1];
+} zend_jit_op_array_hot_extension;
+
+#define zend_jit_op_array_hash(op_array) \
+       zend_jit_hash((op_array)->opcodes)
+
 extern const zend_op *zend_jit_halt_op;
 
 #ifdef HAVE_GCC_GLOBAL_REGS
@@ -78,6 +81,10 @@ extern const zend_op *zend_jit_halt_op;
                handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); \
                return; \
        } while(0)
+# define ZEND_OPCODE_TAIL_CALL_EX(handler, arg) do { \
+               handler(arg ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC); \
+               return; \
+       } while(0)
 #else
 # define EXECUTE_DATA_D                       zend_execute_data* execute_data
 # define EXECUTE_DATA_C                       execute_data
@@ -96,6 +103,9 @@ extern const zend_op *zend_jit_halt_op;
 # define ZEND_OPCODE_TAIL_CALL(handler)       do { \
                return handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); \
        } while(0)
+# define ZEND_OPCODE_TAIL_CALL_EX(handler, arg) do { \
+               return handler(arg ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC); \
+       } while(0)
 #endif
 
 /* VM handlers */
@@ -104,6 +114,7 @@ typedef ZEND_OPCODE_HANDLER_RET (ZEND_FASTCALL *zend_vm_opcode_handler_t)(ZEND_O
 /* VM helpers */
 ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(uint32_t call_info EXECUTE_DATA_DC);
 ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(uint32_t call_info EXECUTE_DATA_DC);
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_func_helper(uint32_t call_info EXECUTE_DATA_DC);
 
 ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_profile_helper(ZEND_OPCODE_HANDLER_ARGS);
 
@@ -116,4 +127,267 @@ void ZEND_FASTCALL zend_jit_deprecated_helper(OPLINE_D);
 void ZEND_FASTCALL zend_jit_get_constant(const zval *key, uint32_t flags);
 int  ZEND_FASTCALL zend_jit_check_constant(const zval *key);
 
+/* Tracer */
+#define zend_jit_opline_hash(opline) \
+       zend_jit_hash(opline)
+
+#define ZEND_JIT_TRACE_FUNC_COST      (1*250)
+#define ZEND_JIT_TRACE_RET_COST     (1*250-1)
+#define ZEND_JIT_TRACE_LOOP_COST      (2*250)
+#define ZEND_JIT_TRACE_COUNTER_INIT (127*250)
+
+#define ZEND_JIT_TRACE_MAX_TRACES        1024 /* max number of traces */
+#define ZEND_JIT_TRACE_MAX_LENGTH        1024 /* max length of single trace */
+#define ZEND_JIT_TRACE_MAX_EXITS          512 /* max number of side exits per trace */
+#define ZEND_JIT_TRACE_MAX_SIDE_TRACES    128 /* max number of side traces of a root trace */
+#define ZEND_JIT_TRACE_MAX_EXIT_COUNTERS 8192 /* max number of side exits for all trace */
+
+#define ZEND_JIT_TRACE_MAX_FUNCS           30 /* max number of different functions in a single trace */
+#define ZEND_JIT_TRACE_MAX_CALL_DEPTH      10 /* max depth of inlined calls */
+#define ZEND_JIT_TRACE_MAX_RET_DEPTH        4 /* max depth of inlined returns */
+#define ZEND_JIT_TRACE_MAX_RECURSION        2 /* max number of recursive inlined calls */
+#define ZEND_JIT_TRACE_MAX_UNROLL_LOOPS     8 /* max number of unrolled loops */
+
+#define ZEND_JIT_TRACE_HOT_SIDE_COUNT       8 /* number of exits before taking side trace */
+#define ZEND_JIT_TRACE_HOT_RETURN_COUNT     8 /* number of returns before taking continuation trace */
+
+#define ZEND_JIT_TRACE_MAX_ROOT_FAILURES   16 /* number of attemts to record/compile a root trace */
+#define ZEND_JIT_TRACE_MAX_SIDE_FAILURES    4 /* number of attemts to record/compile a side trace */
+
+#define ZEND_JIT_TRACE_BAD_ROOT_SLOTS      64 /* number of slots in bad root trace cache */
+
+#define ZEND_JIT_TRACE_STOP(_) \
+       _(LOOP,              "loop") \
+       _(RECURSIVE_CALL,    "recursive call") \
+       _(RECURSIVE_RET,     "recursive return") \
+       _(RETURN,            "return") \
+       _(LINK,              "link to another trace") \
+       /* compilation and linking successful */ \
+       _(COMPILED,          "compiled") \
+       _(ALREADY_DONE,      "already prcessed") \
+       /* failures */ \
+       _(ERROR,             "error")                          /* not used */ \
+       _(NOT_SUPPORTED,     "not supported instructions") \
+       _(EXCEPTION,         "exception") \
+       _(TOO_LONG,          "trace too long") \
+       _(TOO_DEEP,          "trace too deep") \
+       _(TOO_DEEP_RET,      "trace too deep return") \
+       _(DEEP_RECURSION,    "deep recursion") \
+       _(LOOP_UNROLL,       "loop unroll limit reached") \
+       _(LOOP_EXIT,         "exit from loop") \
+       _(BLACK_LIST,        "trace blacklisted") \
+       _(INNER_LOOP,        "inner loop")                     /* trace it */ \
+       _(COMPILED_LOOP,     "compiled loop") \
+       _(TOPLEVEL,          "toplevel") \
+       _(TRAMPOLINE,        "trampoline call") \
+       _(BAD_FUNC,          "bad function call") \
+       _(HALT,              "exit from interpreter") \
+       _(COMPILER_ERROR,    "JIT compilation error") \
+       /* no recoverable error (blacklist immediately) */ \
+       _(NO_SHM,            "insufficient shared memory") \
+       _(TOO_MANY_TRACES,   "too many traces") \
+       _(TOO_MANY_CHILDREN, "too many side traces") \
+       _(TOO_MANY_EXITS,    "too many side exits") \
+
+#define ZEND_JIT_TRACE_STOP_NAME(name, description) \
+       ZEND_JIT_TRACE_STOP_ ## name,
+
+typedef enum _zend_jit_trace_stop {
+       ZEND_JIT_TRACE_STOP(ZEND_JIT_TRACE_STOP_NAME)
+} zend_jit_trace_stop;
+
+#define ZEND_JIT_TRACE_STOP_OK(ret) \
+       (ret < ZEND_JIT_TRACE_STOP_COMPILED)
+
+#define ZEND_JIT_TRACE_STOP_DONE(ret) \
+       (ret < ZEND_JIT_TRACE_STOP_ERROR)
+
+#define ZEND_JIT_TRACE_STOP_REPEAT(ret) \
+       (ret == ZEND_JIT_TRACE_STOP_INNER_LOOP)
+
+#define ZEND_JIT_TRACE_STOP_MAY_RECOVER(ret) \
+       (ret <= ZEND_JIT_TRACE_STOP_COMPILER_ERROR)
+
+#define ZEND_JIT_TRACE_START_MASK      0xf
+
+#define ZEND_JIT_TRACE_START_LOOP   (1<<0)
+#define ZEND_JIT_TRACE_START_ENTER  (1<<1)
+#define ZEND_JIT_TRACE_START_RETURN (1<<2)
+#define ZEND_JIT_TRACE_START_SIDE   (1<<3) /* used for side traces */
+
+#define ZEND_JIT_TRACE_JITED        (1<<4)
+#define ZEND_JIT_TRACE_BLACKLISTED  (1<<5)
+#define ZEND_JIT_TRACE_UNSUPPORTED  (1<<6)
+
+#define ZEND_JIT_TRACE_SUPPORTED    0
+
+#define ZEND_JIT_EXIT_JITED         (1<<0)
+#define ZEND_JIT_EXIT_BLACKLISTED   (1<<1)
+
+typedef union _zend_op_trace_info {
+       zend_op dummy; /* the size of this structure must be the same as zend_op */
+       struct {
+               const void *orig_handler;
+               const void *call_handler;
+               int16_t    *counter;
+               uint8_t     trace_flags;
+       };
+} zend_op_trace_info;
+
+typedef struct _zend_jit_op_array_trace_extension {
+       zend_func_info func_info;
+       size_t offset; /* offset from "zend_op" to corresponding "op_info" */
+       zend_op_trace_info trace_info[1];
+} zend_jit_op_array_trace_extension;
+
+#define ZEND_OP_TRACE_INFO(opline, offset) \
+       ((zend_op_trace_info*)(((char*)opline) + offset))
+
+/* Recorder */
+typedef enum _zend_jit_trace_op {
+       ZEND_JIT_TRACE_VM,
+       ZEND_JIT_TRACE_OP1_TYPE,
+       ZEND_JIT_TRACE_OP2_TYPE,
+       ZEND_JIT_TRACE_INIT_CALL,
+       ZEND_JIT_TRACE_DO_ICALL,
+       ZEND_JIT_TRACE_ENTER,
+       ZEND_JIT_TRACE_BACK,
+       ZEND_JIT_TRACE_START,
+       ZEND_JIT_TRACE_END,
+} zend_jit_trace_op;
+
+#define IS_UNKNOWN 255 /* may be used for zend_jit_trace_rec.op?_type */
+#define IS_TRACE_REFERENCE (1<<5)
+
+typedef struct _zend_jit_trace_rec {
+       uint8_t   op;    /* zend_jit_trace_op */
+       union {
+               struct {
+                       uint8_t op1_type;/* recorded zval op1_type for ZEND_JIT_TRACE_VM */
+                       uint8_t op2_type;/* recorded zval op2_type for ZEND_JIT_TRACE_VM */
+                       uint8_t op3_type;/* recorded zval for op_data.op1_type for ZEND_JIT_TRACE_VM */
+               };
+               struct {
+                       union {
+                               uint8_t   recursive; /* part of recursive return sequence for ZEND_JIT_TRACE_BACK */
+                               int8_t    return_value_used; /* for ZEND_JIT_TRACE_ENTER */
+                               uint8_t   fake; /* for ZEND_JIT_TRACE_INIT_FCALL */
+                       };
+                       uint8_t first_ssa_var; /* may be used for ZEND_JIT_TRACE_ENTER and ZEND_JIT_TRACE_BACK */
+               };
+               struct {
+                       uint8_t  start;  /* ZEND_JIT_TRACE_START_MASK for ZEND_JIT_TRACE_START/END */
+                       uint8_t  stop;   /* zend_jit_trace_stop for ZEND_JIT_TRACE_START/END */
+                       uint8_t  level;  /* recursive return level for ZEND_JIT_TRACE_START */
+               };
+       };
+       union {
+               const void             *ptr;
+               const zend_function    *func;
+               const zend_op_array    *op_array;
+               const zend_op          *opline;
+               const zend_class_entry *ce;
+       };
+} zend_jit_trace_rec;
+
+typedef struct _zend_jit_trace_start_rec {
+       uint8_t  op;     /* zend_jit_trace_op */
+       uint8_t  start;  /* ZEND_JIT_TRACE_START_MASK for ZEND_JIT_TRACE_START/END */
+       uint8_t  stop;   /* zend_jit_trace_stop for ZEND_JIT_TRACE_START/END */
+       uint8_t  level;  /* recursive return level for ZEND_JIT_TRACE_START */
+       const zend_op_array *op_array;
+       const zend_op *opline;
+} zend_jit_trace_start_rec;
+
+#define ZEND_JIT_TRACE_START_REC_SIZE 2
+
+typedef struct _zend_jit_trace_exit_info {
+       const zend_op *opline;     /* opline where VM should continue execution */
+       uint32_t       stack_size;
+       uint32_t       stack_offset;
+} zend_jit_trace_exit_info;
+
+typedef int32_t zend_jit_trace_stack;
+
+typedef struct _zend_jit_trace_info {
+       uint32_t                  id;            /* trace id */
+       uint32_t                  root;          /* root trace id or self id for root traces */
+       uint32_t                  parent;        /* parent trace id or 0 for root traces */
+       uint32_t                  link;          /* link trace id or self id for loop) */
+       uint32_t                  exit_count;    /* number of side exits */
+       uint32_t                  child_count;   /* number of side traces for root traces */
+       uint32_t                  code_size;     /* size of native code */
+       uint32_t                  exit_counters; /* offset in exit counters array */
+       uint32_t                  stack_map_size;
+       const void               *code_start;    /* address of native code */
+       zend_jit_trace_exit_info *exit_info;     /* info about side exits */
+       zend_jit_trace_stack     *stack_map;
+       //uint32_t    loop_offset;
+} zend_jit_trace_info;
+
+typedef struct _zend_jit_trace_stack_frame zend_jit_trace_stack_frame;
+
+struct _zend_jit_trace_stack_frame {
+       zend_jit_trace_stack_frame *call;
+       zend_jit_trace_stack_frame *prev;
+       const zend_function        *func;
+       union {
+               struct {
+                       int8_t              return_value_used;
+                       int8_t              nested;
+                       int8_t              num_args;
+               };
+               int                     return_ssa_var;
+       };
+       zend_jit_trace_stack        stack[1];
+};
+
+typedef struct _zend_jit_globals {
+       zend_jit_trace_stack_frame *current_frame;
+
+       const zend_op *bad_root_cache_opline[ZEND_JIT_TRACE_BAD_ROOT_SLOTS];
+       uint8_t bad_root_cache_count[ZEND_JIT_TRACE_BAD_ROOT_SLOTS];
+       uint8_t bad_root_cache_stop[ZEND_JIT_TRACE_BAD_ROOT_SLOTS];
+       uint32_t bad_root_slot;
+
+       uint8_t  exit_counters[ZEND_JIT_TRACE_MAX_EXIT_COUNTERS];
+} zend_jit_globals;
+
+#ifdef ZTS
+# define JIT_G(v) ZEND_TSRMG(zend_jit_globals_id, zend_jit_globals *, v)
+extern int zend_jit_globals_id;
+#else
+# define JIT_G(v) (jit_globals.v)
+extern zend_jit_globals jit_globals;
+#endif
+
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_func_trace_helper(ZEND_OPCODE_HANDLER_ARGS);
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_ret_trace_helper(ZEND_OPCODE_HANDLER_ARGS);
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_loop_trace_helper(ZEND_OPCODE_HANDLER_ARGS);
+
+int ZEND_FASTCALL zend_jit_trace_hot_root(zend_execute_data *execute_data, const zend_op *opline);
+int ZEND_FASTCALL zend_jit_trace_exit(uint32_t trace_num, uint32_t exit_num);
+zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *execute_data, const zend_op *opline, zend_jit_trace_rec *trace_buffer, uint8_t start);
+
+static zend_always_inline const zend_op* zend_jit_trace_get_exit_opline(zend_jit_trace_rec *trace, const zend_op *opline, zend_bool *exit_if_true)
+{
+       if (trace->op == ZEND_JIT_TRACE_VM || trace->op == ZEND_JIT_TRACE_END) {
+               if (trace->opline == opline + 1) {
+                       /* not taken branch */
+                       *exit_if_true = opline->opcode == ZEND_JMPNZ;
+                       return OP_JMP_ADDR(opline, opline->op2);
+               } else if (trace->opline == OP_JMP_ADDR(opline, opline->op2)) {
+                       /* taken branch */
+                       *exit_if_true = opline->opcode == ZEND_JMPZ;
+                       return opline + 1;
+               } else {
+                       ZEND_ASSERT(0);
+               }
+       } else  {
+               ZEND_ASSERT(0);
+       }
+       *exit_if_true = 0;
+       return NULL;
+}
+
 #endif /* ZEND_JIT_INTERNAL_H */
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
new file mode 100644 (file)
index 0000000..abc2a01
--- /dev/null
@@ -0,0 +1,3972 @@
+/*
+   +----------------------------------------------------------------------+
+   | Zend JIT                                                             |
+   +----------------------------------------------------------------------+
+   | 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: Dmitry Stogov <dmitry@php.net>                              |
+   +----------------------------------------------------------------------+
+*/
+
+static zend_jit_trace_info *zend_jit_traces = NULL;
+static const void **zend_jit_exit_groups = NULL;
+
+#define ZEND_JIT_COUNTER_NUM   zend_jit_traces[0].root
+#define ZEND_JIT_TRACE_NUM     zend_jit_traces[0].id
+#define ZEND_JIT_EXIT_NUM      zend_jit_traces[0].exit_count
+#define ZEND_JIT_EXIT_COUNTERS zend_jit_traces[0].exit_counters
+
+static int zend_jit_trace_startup(void)
+{
+       zend_jit_traces = (zend_jit_trace_info*)zend_shared_alloc(sizeof(zend_jit_trace_info) * ZEND_JIT_TRACE_MAX_TRACES);
+       if (!zend_jit_traces) {
+               return FAILURE;
+       }
+       zend_jit_exit_groups = (const void**)zend_shared_alloc(sizeof(void*) * (ZEND_JIT_TRACE_MAX_EXITS/ZEND_JIT_EXIT_POINTS_PER_GROUP));
+       if (!zend_jit_exit_groups) {
+               return FAILURE;
+       }
+       ZEND_JIT_TRACE_NUM = 1;
+       ZEND_JIT_COUNTER_NUM = 0;
+       ZEND_JIT_EXIT_NUM = 0;
+       ZEND_JIT_EXIT_COUNTERS = 0;
+       return SUCCESS;
+}
+
+static const void *zend_jit_trace_allocate_exit_group(uint32_t n)
+{
+       dasm_State* dasm_state = NULL;
+       const void *entry;
+       char name[32];
+
+       dasm_init(&dasm_state, DASM_MAXSECTION);
+       dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX);
+       dasm_setup(&dasm_state, dasm_actions);
+       zend_jit_trace_exit_group_stub(&dasm_state, n);
+
+       sprintf(name, "jit$$trace_exit_%d", n);
+       entry = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, name, 0);
+       dasm_free(&dasm_state);
+
+#ifdef HAVE_DISASM
+    if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_ASM) {
+               uint32_t i;
+
+               for (i = 0; i < ZEND_JIT_EXIT_POINTS_PER_GROUP; i++) {
+                       sprintf(name, "jit$$trace_exit_%d", n + i);
+                       zend_jit_disasm_add_symbol(name, (uintptr_t)entry + (i * ZEND_JIT_EXIT_POINTS_SPACING), ZEND_JIT_EXIT_POINTS_SPACING);
+               }
+       }
+#endif
+
+       return entry;
+}
+
+static const void *zend_jit_trace_allocate_exit_point(uint32_t n)
+{
+       const void *group = NULL;
+
+       if (UNEXPECTED(n >= ZEND_JIT_TRACE_MAX_EXITS)) {
+               return NULL;
+       }
+       do {
+               group = zend_jit_trace_allocate_exit_group(ZEND_JIT_EXIT_NUM);
+               if (!group) {
+                       return NULL;
+               }
+               zend_jit_exit_groups[ZEND_JIT_EXIT_NUM / ZEND_JIT_EXIT_POINTS_PER_GROUP] =
+                       group;
+               ZEND_JIT_EXIT_NUM += ZEND_JIT_EXIT_POINTS_PER_GROUP;
+       } while (n >= ZEND_JIT_EXIT_NUM);
+    return (const void*)
+               ((const char*)group +
+               ((n % ZEND_JIT_EXIT_POINTS_PER_GROUP) * ZEND_JIT_EXIT_POINTS_SPACING));
+}
+
+static const void *zend_jit_trace_get_exit_addr(uint32_t n)
+{
+       if (UNEXPECTED(n >= ZEND_JIT_EXIT_NUM)) {
+               return zend_jit_trace_allocate_exit_point(n);
+       }
+    return (const void*)
+               ((const char*)zend_jit_exit_groups[n / ZEND_JIT_EXIT_POINTS_PER_GROUP] +
+               ((n % ZEND_JIT_EXIT_POINTS_PER_GROUP) * ZEND_JIT_EXIT_POINTS_SPACING));
+}
+
+static uint32_t zend_jit_trace_get_exit_point(const zend_op *from_opline, const zend_op *to_opline, zend_jit_trace_rec *trace)
+{
+       zend_jit_trace_info *t = &zend_jit_traces[ZEND_JIT_TRACE_NUM];
+       uint32_t exit_point;
+       const zend_op_array *op_array = &JIT_G(current_frame)->func->op_array;
+       uint32_t stack_offset = (uint32_t)-1;
+       uint32_t stack_size = op_array->last_var + op_array->T;
+       zend_jit_trace_stack *stack = NULL;
+
+       if (stack_size) {
+               stack = JIT_G(current_frame)->stack;
+               do {
+                       if (stack[stack_size-1] != IS_UNKNOWN) {
+                               break;
+                       }
+                       stack_size--;
+               } while (stack_size);
+       }
+
+       /* Try to reuse exit points */
+       if (to_opline != NULL && t->exit_count > 0) {
+               uint32_t i = t->exit_count;
+
+               do {
+                       i--;
+                       if (stack_size == 0
+                        || (t->exit_info[i].stack_size >= stack_size
+                         && memcmp(t->stack_map + t->exit_info[i].stack_offset, stack, stack_size * sizeof(zend_jit_trace_stack)) == 0)) {
+                               stack_offset = t->exit_info[i].stack_offset;
+                               if (t->exit_info[i].opline == to_opline) {
+                                       return i;
+                               }
+                       }
+               } while (i > 0);
+       }
+
+       exit_point = t->exit_count;
+       if (exit_point < ZEND_JIT_TRACE_MAX_EXITS) {
+               if (stack_size != 0 && stack_offset == (uint32_t)-1) {
+                       stack_offset = t->stack_map_size;
+                       t->stack_map_size += stack_size;
+                       // TODO: reduce number of reallocations ???
+                       t->stack_map = erealloc(t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack));
+                       memcpy(t->stack_map + stack_offset, stack, stack_size * sizeof(zend_jit_trace_stack));
+               }
+               t->exit_count++;
+               t->exit_info[exit_point].opline = to_opline;
+               t->exit_info[exit_point].stack_size = stack_size;
+               t->exit_info[exit_point].stack_offset = stack_offset;
+       }
+
+       return exit_point;
+}
+
+static void zend_jit_trace_add_code(const void *start, uint32_t size)
+{
+       zend_jit_trace_info *t = &zend_jit_traces[ZEND_JIT_TRACE_NUM];
+
+       t->code_start = start;
+       t->code_size  = size;
+}
+
+static uint32_t zend_jit_find_trace(const void *addr)
+{
+       uint32_t i;
+
+       for (i = 1; i < ZEND_JIT_TRACE_NUM; i++) {
+               if (zend_jit_traces[i].code_start == addr) {
+                       return i;
+               }
+       }
+       ZEND_ASSERT(0);
+       return 0;
+}
+
+static zend_string *zend_jit_trace_name(const zend_op_array *op_array, uint32_t lineno)
+{
+       smart_str buf = {0};
+
+       smart_str_appends(&buf, TRACE_PREFIX);
+       smart_str_append_long(&buf, (zend_long)ZEND_JIT_TRACE_NUM);
+       smart_str_appendc(&buf, '$');
+       if (op_array->function_name) {
+               if (op_array->scope) {
+                       smart_str_appendl(&buf, ZSTR_VAL(op_array->scope->name), ZSTR_LEN(op_array->scope->name));
+                       smart_str_appends(&buf, "::");
+                       smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name));
+               } else {
+                       smart_str_appendl(&buf, ZSTR_VAL(op_array->function_name), ZSTR_LEN(op_array->function_name));
+               }
+       } else if (op_array->filename) {
+               smart_str_appendl(&buf, ZSTR_VAL(op_array->filename), ZSTR_LEN(op_array->filename));
+       }
+       smart_str_appendc(&buf, '$');
+       smart_str_append_long(&buf, (zend_long)lineno);
+       smart_str_0(&buf);
+       return buf.s;
+}
+
+static int zend_jit_trace_may_exit(const zend_op_array *op_array, const zend_op *opline, zend_jit_trace_rec *trace)
+{
+       switch (opline->opcode) {
+               case ZEND_IS_IDENTICAL:
+               case ZEND_IS_NOT_IDENTICAL:
+               case ZEND_IS_EQUAL:
+               case ZEND_IS_NOT_EQUAL:
+               case ZEND_IS_SMALLER:
+               case ZEND_IS_SMALLER_OR_EQUAL:
+               case ZEND_CASE:
+               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->result_type & (IS_SMART_BRANCH_JMPNZ | IS_SMART_BRANCH_JMPZ)) {
+                               /* smart branch */
+                               return 1;
+                       }
+                       break;
+               case ZEND_JMPZNZ:
+               case ZEND_JMPZ:
+               case ZEND_JMPNZ:
+               case ZEND_JMPZ_EX:
+               case ZEND_JMPNZ_EX:
+               case ZEND_JMP_SET:
+               case ZEND_COALESCE:
+               case ZEND_FE_RESET_R:
+               case ZEND_FE_RESET_RW:
+               case ZEND_ASSERT_CHECK:
+               case ZEND_FE_FETCH_R:
+               case ZEND_FE_FETCH_RW:
+               case ZEND_SWITCH_LONG:
+               case ZEND_SWITCH_STRING:
+                       /* branch opcdoes */
+                       return 1;
+               case ZEND_NEW:
+                       if (opline->extended_value == 0 && (opline+1)->opcode == ZEND_DO_FCALL) {
+                               /* NEW may skip constructor without arguments */
+                               return 1;
+                       }
+                       break;
+               case ZEND_CATCH:
+               case ZEND_FAST_CALL:
+               case ZEND_FAST_RET:
+               case ZEND_GENERATOR_CREATE:
+               case ZEND_GENERATOR_RETURN:
+               case ZEND_EXIT:
+               case ZEND_YIELD:
+               case ZEND_YIELD_FROM:
+               case ZEND_INCLUDE_OR_EVAL:
+                       /* unsupported */
+                       return 1;
+               case ZEND_DO_FCALL:
+                       /* potentially polymorphic call */
+                       return 1;
+#if 0
+               case ZEND_DO_UCALL:
+               case ZEND_DO_FCALL_BY_NAME:
+                       /* monomorphic call */
+                       // TODO: recompilation may change traget ???
+                       return 0;
+#endif
+               case ZEND_RETURN_BY_REF:
+               case ZEND_RETURN:
+                       /* return */
+                       return trace->op == ZEND_JIT_TRACE_BACK && trace->recursive;
+               default:
+                       break;
+       }
+       return 0;
+}
+
+static zend_always_inline uint32_t zend_jit_trace_type_to_info_ex(zend_uchar type, uint32_t info)
+{
+       if (type == IS_UNKNOWN) {
+               return info;
+       }
+       ZEND_ASSERT(info & (1 << type));
+       if (type < IS_STRING) {
+               return (1 << type);
+       } else if (type != IS_ARRAY) {
+               return (1 << type) | (info & (MAY_BE_RC1|MAY_BE_RCN));
+       } else {
+               return MAY_BE_ARRAY | (info & (MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_RC1|MAY_BE_RCN));
+       }
+}
+
+static zend_always_inline uint32_t zend_jit_trace_type_to_info(zend_uchar type)
+{
+       return zend_jit_trace_type_to_info_ex(type, -1);
+}
+
+#define STACK_VAR_TYPE(_var) \
+       ((zend_uchar)stack[EX_VAR_TO_NUM(_var)])
+
+#define SET_STACK_VAR_TYPE(_var, _type) do { \
+               stack[EX_VAR_TO_NUM(_var)] = _type; \
+       } while (0)
+
+#define CHECK_OP_TRACE_TYPE(_var, _ssa_var, op_info, op_type) do { \
+               if (op_type != IS_UNKNOWN) { \
+                       if ((op_info & MAY_BE_GUARD) != 0 \
+                        && op_type != STACK_VAR_TYPE(_var)) { \
+                               if (!zend_jit_type_guard(&dasm_state, opline, _var, op_type)) { \
+                                       goto jit_failure; \
+                               } \
+                               op_info &= ~MAY_BE_GUARD; \
+                               ssa->var_info[_ssa_var].type &= op_info; \
+                       } \
+                       SET_STACK_VAR_TYPE(_var, op_type); \
+               } \
+       } while (0)
+
+#define USE_OP_TRACE_TYPE(_type, _var, op_info) do { \
+               if (_type & (IS_TMP_VAR|IS_VAR|IS_CV)) { \
+                       op_info = zend_jit_trace_type_to_info_ex(STACK_VAR_TYPE(_var), op_info); \
+               } \
+       } while (0)
+
+#define CHECK_OP1_TRACE_TYPE() \
+       CHECK_OP_TRACE_TYPE(opline->op1.var, ssa_op->op1_use, op1_info, op1_type)
+#define CHECK_OP2_TRACE_TYPE() \
+       CHECK_OP_TRACE_TYPE(opline->op2.var, ssa_op->op2_use, op2_info, op2_type)
+#define CHECK_OP1_DATA_TRACE_TYPE() \
+       CHECK_OP_TRACE_TYPE((opline+1)->op1.var, (ssa_op+1)->op1_use, op1_data_info, op3_type)
+#define USE_OP1_TRACE_TYPE() \
+       USE_OP_TRACE_TYPE(opline->op1_type, opline->op1.var, op1_info)
+#define USE_OP2_TRACE_TYPE() \
+       USE_OP_TRACE_TYPE(opline->op2_type, opline->op2.var, op2_info)
+#define USE_OP1_DATA_TRACE_TYPE() \
+       USE_OP_TRACE_TYPE((opline+1)->op1_type, (opline+1)->op1.var, op1_data_info)
+#define USE_RES_TRACE_TYPE() \
+       USE_OP_TRACE_TYPE(opline->result_type, opline->result.var, res_use_info)
+#define SET_OP1_STACK_VAR_TYPE(_type) \
+       SET_STACK_VAR_TYPE(opline->op1.var, _type)
+#define SET_OP2_STACK_VAR_TYPE( _type) \
+       SET_STACK_VAR_TYPE(opline->op2.var, _type)
+#define SET_OP1_DATA_STACK_VAR_TYPE(_type) \
+       SET_STACK_VAR_TYPE((opline+1)->op1.var, _type)
+#define SET_RES_STACK_VAR_TYPE(_type) \
+       SET_STACK_VAR_TYPE(opline->result.var, _type)
+
+static zend_always_inline size_t zend_jit_trace_frame_size(const zend_op_array *op_array)
+{
+       if (op_array->type == ZEND_USER_FUNCTION) {
+               return offsetof(zend_jit_trace_stack_frame, stack) + ZEND_MM_ALIGNED_SIZE((op_array->last_var + op_array->T) * sizeof(zend_jit_trace_stack));
+       } else {
+               return offsetof(zend_jit_trace_stack_frame, stack);
+       }
+}
+
+static zend_jit_trace_stack_frame* zend_jit_trace_call_frame(zend_jit_trace_stack_frame *frame, const zend_op_array *op_array)
+{
+       return (zend_jit_trace_stack_frame*)((char*)frame + zend_jit_trace_frame_size(op_array));
+}
+
+static zend_jit_trace_stack_frame* zend_jit_trace_ret_frame(zend_jit_trace_stack_frame *frame, const zend_op_array *op_array)
+{
+       return (zend_jit_trace_stack_frame*)((char*)frame - zend_jit_trace_frame_size(op_array));
+}
+
+static void zend_jit_trace_send_type(const zend_op *opline, zend_jit_trace_stack_frame *call, zend_uchar type)
+{
+       zend_jit_trace_stack *stack = call->stack;
+       const zend_op_array *op_array = &call->func->op_array;
+       uint32_t arg_num = opline->op2.num;
+
+       if (arg_num > op_array->num_args) {
+               return;
+       }
+       if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
+               zend_arg_info *arg_info;
+
+               ZEND_ASSERT(arg_num <= op_array->num_args);
+               arg_info = &op_array->arg_info[arg_num-1];
+
+               if (ZEND_TYPE_IS_SET(arg_info->type)) {
+                       if (!(ZEND_TYPE_FULL_MASK(arg_info->type) & (1u << type))) {
+                               return;
+                       }
+               }
+       }
+       SET_STACK_VAR_TYPE(opline->result.var, type);
+}
+
+static zend_ssa *zend_jit_trace_build_ssa(const zend_op_array *op_array, zend_script *script)
+{
+       zend_jit_op_array_trace_extension *jit_extension;
+       zend_ssa *ssa;
+
+       jit_extension =
+               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+       memset(&jit_extension->func_info, 0, sizeof(jit_extension->func_info));
+       jit_extension->func_info.num_args = -1;
+       jit_extension->func_info.return_value_used = -1;
+       ssa = &jit_extension->func_info.ssa;
+
+       if (zend_jit_level >= ZEND_JIT_LEVEL_OPT_FUNC) {
+               do {
+                       if (zend_jit_op_array_analyze1(op_array, script, ssa) != SUCCESS) {
+                               break;
+                       }
+
+                       if (zend_jit_level >= ZEND_JIT_LEVEL_OPT_FUNCS) {
+                               if (zend_analyze_calls(&CG(arena), script, ZEND_CALL_TREE, (zend_op_array*)op_array, &jit_extension->func_info) != SUCCESS) {
+                                       break;
+                               }
+                               jit_extension->func_info.call_map = zend_build_call_map(&CG(arena), &jit_extension->func_info, (zend_op_array*)op_array);
+                               if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+                                       zend_init_func_return_info(op_array, script, &jit_extension->func_info.return_info);
+                               }
+                       }
+
+                       if (zend_jit_op_array_analyze2(op_array, script, ssa) != SUCCESS) {
+                               break;
+                       }
+
+                       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_SSA) {
+                               zend_dump_op_array(op_array, ZEND_DUMP_HIDE_UNREACHABLE|ZEND_DUMP_RC_INFERENCE|ZEND_DUMP_SSA, "JIT", ssa);
+                       }
+                       return ssa;
+               } while (0);
+       }
+
+       memset(ssa, 0, sizeof(zend_ssa));
+       ssa->cfg.blocks_count = 1;
+       return ssa;
+}
+
+static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa);
+
+static zend_always_inline int zend_jit_trace_op_len(const zend_op *opline)
+{
+       int len;
+
+       switch (opline->opcode) {
+               case ZEND_ASSIGN_DIM:
+               case ZEND_ASSIGN_OBJ:
+               case ZEND_ASSIGN_STATIC_PROP:
+               case ZEND_ASSIGN_DIM_OP:
+               case ZEND_ASSIGN_OBJ_OP:
+               case ZEND_ASSIGN_STATIC_PROP_OP:
+               case ZEND_ASSIGN_OBJ_REF:
+               case ZEND_ASSIGN_STATIC_PROP_REF:
+                       return 2; /* OP_DATA */
+               case ZEND_RECV_INIT:
+                       len = 1;
+                       opline++;
+                       while (opline->opcode == ZEND_RECV_INIT) {
+                               len++;
+                               opline++;
+                       }
+                       return len;
+               case ZEND_BIND_GLOBAL:
+                       len = 1;
+                       opline++;
+                       while (opline->opcode == ZEND_BIND_GLOBAL) {
+                               len++;
+                               opline++;
+                       }
+                       return len;
+//             case ZEND_IS_IDENTICAL:
+//             case ZEND_IS_NOT_IDENTICAL:
+//             case ZEND_IS_EQUAL:
+//             case ZEND_IS_NOT_EQUAL:
+//             case ZEND_IS_SMALLER:
+//             case ZEND_IS_SMALLER_OR_EQUAL:
+//             case ZEND_CASE:
+//             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:
+               default:
+                       if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                               return 2; /* JMPZ/JMPNZ */
+                       }
+                       return 1;
+       }
+}
+
+static int zend_jit_trace_add_phis(zend_jit_trace_rec *trace_buffer, uint32_t ssa_vars_count, zend_ssa *tssa, int *var)
+{
+       const zend_op_array *op_array;
+       zend_jit_trace_rec *p;
+       int k, vars_count;
+       zend_bitset use, def;
+       uint32_t build_flags = ZEND_SSA_RC_INFERENCE | ZEND_SSA_USE_CV_RESULTS;
+       uint32_t set_size;
+       zend_ssa_phi *prev = NULL;
+       int level = 0;
+       ALLOCA_FLAG(use_heap);
+
+       op_array = trace_buffer->op_array;
+       set_size = zend_bitset_len(op_array->last_var + op_array->T);
+       use = ZEND_BITSET_ALLOCA(set_size * 2, use_heap);
+       memset(use, 0, set_size * 2 * ZEND_BITSET_ELM_SIZE);
+       def = use + set_size;
+       p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE;
+       for (;;p++) {
+               if (p->op == ZEND_JIT_TRACE_VM && level == 0) {
+                       const zend_op *opline = p->opline;
+                       int len;
+
+                       zend_dfg_add_use_def_op(op_array, opline, build_flags, use, def);
+                       len = zend_jit_trace_op_len(opline);
+                       while (len > 1) {
+                               opline++;
+                               if (opline->opcode != ZEND_OP_DATA) {
+                                       zend_dfg_add_use_def_op(op_array, opline, build_flags, use, def);
+                               }
+                               len--;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) {
+               } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) {
+               } else if (p->op == ZEND_JIT_TRACE_ENTER) {
+                       level++;
+               } else if (p->op == ZEND_JIT_TRACE_BACK) {
+                       if (level == 0) {
+                               // Phi for recursive calls and returns are not supporte yet ???
+                               assert(0);
+                       } else {
+                               level--;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_END) {
+                       break;
+               }
+       }
+
+       zend_bitset_intersection(use, def, set_size);
+
+       if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) {
+               vars_count = op_array->last_var;
+       } else {
+               vars_count = op_array->last_var + op_array->T;
+       }
+       for (k = 0; k < vars_count; k++) {
+               if (zend_bitset_in(use, k)) {
+                       zend_ssa_phi *phi = zend_arena_calloc(&CG(arena), 1,
+                               ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)) +
+                               ZEND_MM_ALIGNED_SIZE(sizeof(int) * 2) +
+                               sizeof(void*) * 2);
+                       phi->sources = (int*)(((char*)phi) + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ssa_phi)));
+                       phi->sources[0] = var[k];
+                       phi->sources[1] = -1;
+                       phi->use_chains = (zend_ssa_phi**)(((char*)phi->sources) + ZEND_MM_ALIGNED_SIZE(sizeof(int) * 2));
+                       phi->pi = -1;
+                       phi->var = k;
+                       phi->ssa_var = ssa_vars_count;
+                       var[k] = ssa_vars_count;
+                       ssa_vars_count++;
+                       phi->block = 1;
+                       if (prev) {
+                               prev->next = phi;
+                       } else {
+                               tssa->blocks[1].phis = phi;
+                       }
+                       prev = phi;
+               }
+       }
+
+       free_alloca(use, use_heap);
+
+       return ssa_vars_count;
+}
+
+static int zend_jit_trace_copy_ssa_var_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op **tssa_opcodes, zend_ssa *tssa, int ssa_var)
+{
+       int var, use;
+       zend_ssa_op *op;
+       zend_ssa_var_info *info;
+       unsigned int no_val;
+
+       if (tssa->vars[ssa_var].phi_use_chain) {
+               // TODO: this may be incorrect ???
+               var = tssa->vars[ssa_var].phi_use_chain->ssa_var;
+       } else {
+               var = ssa_var;
+       }
+       use = tssa->vars[var].use_chain;
+       if (use >= 0) {
+               ZEND_ASSERT((tssa_opcodes[use] - op_array->opcodes) < op_array->last);
+               op = ssa->ops + (tssa_opcodes[use] - op_array->opcodes);
+               if (tssa->ops[use].op1_use == var) {
+                       no_val = ssa->vars[op->op1_use].no_val;
+                       info = ssa->var_info + op->op1_use;
+               } else if (tssa->ops[use].op2_use == var) {
+                       no_val = ssa->vars[op->op2_use].no_val;
+                       info = ssa->var_info + op->op2_use;
+               } else if (tssa->ops[use].result_use == var) {
+                       no_val = ssa->vars[op->result_use].no_val;
+                       info = ssa->var_info + op->result_use;
+               } else {
+                       assert(0);
+               }
+               tssa->vars[ssa_var].no_val = no_val;
+               memcpy(&tssa->var_info[ssa_var], info, sizeof(zend_ssa_var_info));
+               return 1;
+       }
+       return 0;
+}
+
+static int zend_jit_trace_copy_ssa_var_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op **tssa_opcodes, zend_ssa *tssa, int ssa_var)
+{
+       int def;
+       zend_ssa_op *op;
+       zend_ssa_var_info *info;
+       unsigned int no_val;
+
+       def = tssa->vars[ssa_var].definition;
+       if (def >= 0) {
+               ZEND_ASSERT((tssa_opcodes[def] - op_array->opcodes) < op_array->last);
+               op = ssa->ops + (tssa_opcodes[def] - op_array->opcodes);
+               if (tssa->ops[def].op1_def == ssa_var) {
+                       no_val = ssa->vars[op->op1_def].no_val;
+                       info = ssa->var_info + op->op1_def;
+               } else if (tssa->ops[def].op2_def == ssa_var) {
+                       no_val = ssa->vars[op->op2_def].no_val;
+                       info = ssa->var_info + op->op2_def;
+               } else if (tssa->ops[def].result_def == ssa_var) {
+                       no_val = ssa->vars[op->result_def].no_val;
+                       info = ssa->var_info + op->result_def;
+               } else {
+                       assert(0);
+               }
+
+               tssa->vars[ssa_var].no_val = no_val;
+
+               if (info->has_range) {
+                       if (tssa->var_info[ssa_var].has_range) {
+                               tssa->var_info[ssa_var].range.min = MAX(tssa->var_info[ssa_var].range.min, info->range.min);
+                               tssa->var_info[ssa_var].range.max = MIN(tssa->var_info[ssa_var].range.max, info->range.max);
+                               tssa->var_info[ssa_var].range.underflow = tssa->var_info[ssa_var].range.underflow && info->range.underflow;
+                               tssa->var_info[ssa_var].range.overflow = tssa->var_info[ssa_var].range.overflow && info->range.overflow;
+                       } else {
+                               tssa->var_info[ssa_var].has_range = 1;
+                               tssa->var_info[ssa_var].range = info->range;
+                       }
+               }
+               return 1;
+       }
+       return 0;
+}
+
+static int zend_jit_trace_restrict_ssa_var_info(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op **tssa_opcodes, zend_ssa *tssa, int ssa_var)
+{
+       int def;
+       zend_ssa_op *op;
+       zend_ssa_var_info *info;
+
+       def = tssa->vars[ssa_var].definition;
+       if (def >= 0) {
+               ZEND_ASSERT((tssa_opcodes[def] - op_array->opcodes) < op_array->last);
+               op = ssa->ops + (tssa_opcodes[def] - op_array->opcodes);
+               if (tssa->ops[def].op1_def == ssa_var) {
+                       info = ssa->var_info + op->op1_def;
+               } else if (tssa->ops[def].op2_def == ssa_var) {
+                       info = ssa->var_info + op->op2_def;
+               } else if (tssa->ops[def].result_def == ssa_var) {
+                       info = ssa->var_info + op->result_def;
+               } else {
+                       assert(0);
+               }
+               tssa->var_info[ssa_var].type &= info->type;
+               if (info->ce) {
+                       if (tssa->var_info[ssa_var].ce) {
+                               ZEND_ASSERT(tssa->var_info[ssa_var].ce == info->ce);
+                               tssa->var_info[ssa_var].is_instanceof =
+                                       tssa->var_info[ssa_var].is_instanceof && info->is_instanceof;
+                       } else {
+                               tssa->var_info[ssa_var].ce = info->ce;
+                               tssa->var_info[ssa_var].is_instanceof = info->is_instanceof;
+                       }
+               }
+               if (info->has_range) {
+                       if (tssa->var_info[ssa_var].has_range) {
+                               tssa->var_info[ssa_var].range.min = MAX(tssa->var_info[ssa_var].range.min, info->range.min);
+                               tssa->var_info[ssa_var].range.max = MIN(tssa->var_info[ssa_var].range.max, info->range.max);
+                               tssa->var_info[ssa_var].range.underflow = tssa->var_info[ssa_var].range.underflow && info->range.underflow;
+                               tssa->var_info[ssa_var].range.overflow = tssa->var_info[ssa_var].range.overflow && info->range.overflow;
+                       } else {
+                               tssa->var_info[ssa_var].has_range = 1;
+                               tssa->var_info[ssa_var].range = info->range;
+                       }
+               }
+               return 1;
+       }
+       return 0;
+}
+
+static int find_return_ssa_var(zend_jit_trace_rec *p, zend_ssa_op *ssa_op)
+{
+       while (1) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       if (p->opline->opcode == ZEND_DO_UCALL
+                        || p->opline->opcode == ZEND_DO_FCALL_BY_NAME
+                        || p->opline->opcode == ZEND_DO_FCALL) {
+                               if (p->opline->result_type != IS_UNUSED) {
+                                       return ssa_op->result_def;
+                               }
+                       }
+                       return -1;
+               } else if (p->op == ZEND_JIT_TRACE_OP1_TYPE || p->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                       /*skip */
+               } else {
+                       return -1;
+               }
+               p--;
+       }
+}
+
+static int find_call_num_args(zend_jit_trace_rec *p)
+{
+       while (1) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       if (p->opline->opcode == ZEND_INIT_FCALL
+                        || p->opline->opcode == ZEND_INIT_FCALL_BY_NAME
+                        || p->opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME
+                        || p->opline->opcode == ZEND_INIT_DYNAMIC_CALL
+                        || p->opline->opcode == ZEND_INIT_USER_CALL
+                        || p->opline->opcode == ZEND_NEW
+                        || p->opline->opcode == ZEND_INIT_METHOD_CALL
+                        || p->opline->opcode == ZEND_INIT_STATIC_METHOD_CALL) {
+                               if (p->opline->extended_value <= 127) {
+                                       return p->opline->extended_value;
+                               } else {
+                                       return -1;
+                               }
+                       }
+                       return -1;
+               } else if (p->op == ZEND_JIT_TRACE_OP1_TYPE || p->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                       /*skip */
+               } else {
+                       return -1;
+               }
+               p--;
+       }
+}
+
+static int is_checked_guard(const zend_ssa *tssa, const zend_op **ssa_opcodes, uint32_t var, uint32_t phi_var)
+{
+       if ((tssa->var_info[phi_var].type & MAY_BE_ANY) == MAY_BE_LONG) {
+               uint32_t idx = tssa->vars[var].definition;
+
+               if (idx >= 0) {
+                       if (tssa->ops[idx].op1_def == var) {
+                               const zend_op *opline = ssa_opcodes[idx];
+                               if (opline->opcode == ZEND_PRE_DEC
+                                || opline->opcode == ZEND_PRE_INC
+                                || opline->opcode == ZEND_POST_DEC
+                                || opline->opcode == ZEND_POST_INC) {
+                                       return 1;
+                               }
+                       }
+                       if (tssa->ops[idx].result_def == var) {
+                               const zend_op *opline = ssa_opcodes[idx];
+                               if (opline->opcode == ZEND_ADD
+                                || opline->opcode == ZEND_SUB
+                                || opline->opcode == ZEND_MUL
+                                || opline->opcode == ZEND_PRE_DEC
+                                || opline->opcode == ZEND_PRE_INC
+                                || opline->opcode == ZEND_POST_DEC
+                                || opline->opcode == ZEND_POST_INC) {
+                                       return 1;
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+
+static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uint32_t parent_trace, uint32_t exit_num, zend_script *script, const zend_op_array **op_arrays, int *num_op_arrays_ptr)
+{
+       zend_ssa *tssa;
+       zend_ssa_op *ssa_ops, *op;
+       zend_ssa_var *ssa_vars;
+       zend_ssa_var_info *ssa_var_info;
+       const zend_op_array *op_array;
+       const zend_op *opline;
+       const zend_op **ssa_opcodes;
+       zend_jit_trace_rec *p;
+       int i, v, idx, len, ssa_ops_count, vars_count, ssa_vars_count;
+       int *var;
+       uint32_t build_flags = ZEND_SSA_RC_INFERENCE | ZEND_SSA_USE_CV_RESULTS;
+       uint32_t optimization_level = ZCG(accel_directives).optimization_level;
+       int call_level, level, num_op_arrays;
+       size_t frame_size, stack_top, stack_size, stack_bottom;
+       zend_jit_op_array_trace_extension *jit_extension;
+       zend_ssa *ssa;
+       zend_jit_trace_stack_frame *frame, *top, *call;
+       zend_ssa_var_info return_value_info;
+
+       /* 1. Count number of TSSA opcodes;
+        *    Count number of activation frames;
+        *    Calculate size of abstract stack;
+        *    Construct regular SSA for involved op_array */
+       op_array = trace_buffer->op_array;
+       stack_top = stack_size = zend_jit_trace_frame_size(op_array);
+       stack_bottom = 0;
+       p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE;
+       ssa_ops_count = 0;
+       call_level = 0;
+       level = 0;
+       num_op_arrays = 0;
+       /* Remember op_array to cleanup */
+       op_arrays[num_op_arrays++] = op_array;
+       /* Build SSA */
+       ssa = zend_jit_trace_build_ssa(op_array, script);
+       for (;;p++) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       ssa_ops_count += zend_jit_trace_op_len(p->opline);
+               } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) {
+                       call_level++;
+                       stack_top += zend_jit_trace_frame_size(p->op_array);
+                       if (stack_top > stack_size) {
+                               stack_size = stack_top;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) {
+                       frame_size = zend_jit_trace_frame_size(p->op_array);
+                       if (call_level == 0) {
+                               if (stack_top + frame_size > stack_size) {
+                                       stack_size = stack_top + frame_size;
+                               }
+                       } else {
+                               call_level--;
+                               stack_top -= frame_size;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_ENTER) {
+                       op_array = p->op_array;
+                       if (call_level == 0) {
+                               stack_top += zend_jit_trace_frame_size(op_array);
+                               if (stack_top > stack_size) {
+                                       stack_size = stack_top;
+                               }
+                       } else {
+                               call_level--;
+                       }
+                       level++;
+                       jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                       ssa = &jit_extension->func_info.ssa;
+                       if (ssa->cfg.blocks_count) {
+                               /* pass */
+                       } else if (num_op_arrays == ZEND_JIT_TRACE_MAX_FUNCS) {
+                               /* Too many functions in single trace */
+                               return NULL;
+                       } else {
+                               /* Remember op_array to cleanup */
+                               op_arrays[num_op_arrays++] = op_array;
+                               /* Build SSA */
+                               ssa = zend_jit_trace_build_ssa(op_array, script);
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_BACK) {
+                       if (level == 0) {
+                               stack_bottom += zend_jit_trace_frame_size(op_array);;
+                               jit_extension =
+                                       (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                               ssa = &jit_extension->func_info.ssa;
+                               if (ssa->cfg.blocks_count) {
+                                       /* pass */
+                               } else if (num_op_arrays == ZEND_JIT_TRACE_MAX_FUNCS) {
+                                       /* Too many functions in single trace */
+                                       return NULL;
+                               } else {
+                                       /* Remember op_array to cleanup */
+                                       op_arrays[num_op_arrays++] = op_array;
+                                       /* Build SSA */
+                                       ssa = zend_jit_trace_build_ssa(op_array, script);
+                               }
+                       } else {
+                               stack_top -= zend_jit_trace_frame_size(op_array);
+                               level--;
+                       }
+                       op_array = p->op_array;
+               } else if (p->op == ZEND_JIT_TRACE_END) {
+                       break;
+               }
+       }
+       *num_op_arrays_ptr = num_op_arrays;
+
+       /* 2. Construct TSSA */
+       tssa = zend_arena_calloc(&CG(arena), 1, sizeof(zend_ssa));
+       tssa->cfg.blocks = zend_arena_calloc(&CG(arena), 2, sizeof(zend_basic_block));
+       tssa->blocks = zend_arena_calloc(&CG(arena), 2, sizeof(zend_ssa_block));
+       tssa->cfg.predecessors = zend_arena_calloc(&CG(arena), 2, sizeof(int));
+       tssa->ops = ssa_ops = zend_arena_alloc(&CG(arena), ssa_ops_count * sizeof(zend_ssa_op));
+       memset(ssa_ops, -1, ssa_ops_count * sizeof(zend_ssa_op));
+       ssa_opcodes = zend_arena_calloc(&CG(arena), ssa_ops_count, sizeof(zend_op*));
+       JIT_G(current_frame) = frame = (zend_jit_trace_stack_frame*)((char*)zend_arena_alloc(&CG(arena), stack_bottom + stack_size) + stack_bottom);
+
+       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+               tssa->cfg.blocks_count = 2;
+               tssa->cfg.edges_count = 2;
+
+               tssa->cfg.predecessors[0] = 0;
+               tssa->cfg.predecessors[1] = 1;
+
+               tssa->cfg.blocks[0].flags = ZEND_BB_START|ZEND_BB_REACHABLE;
+               tssa->cfg.blocks[0].successors_count = 1;
+               tssa->cfg.blocks[0].predecessors_count = 0;
+               tssa->cfg.blocks[0].successors = tssa->cfg.blocks[0].successors_storage;
+               tssa->cfg.blocks[0].successors[0] = 1;
+
+               tssa->cfg.blocks[0].flags = ZEND_BB_FOLLOW|ZEND_BB_TARGET|ZEND_BB_LOOP_HEADER|ZEND_BB_REACHABLE;
+               tssa->cfg.blocks[1].successors_count = 1;
+               tssa->cfg.blocks[1].predecessors_count = 2;
+               tssa->cfg.blocks[1].successors = tssa->cfg.blocks[1].successors_storage;
+               tssa->cfg.blocks[1].successors[1] = 1;
+       } else {
+               tssa->cfg.blocks_count = 1;
+               tssa->cfg.edges_count = 0;
+
+               tssa->cfg.blocks[0].flags = ZEND_BB_START|ZEND_BB_EXIT|ZEND_BB_REACHABLE;
+               tssa->cfg.blocks[0].successors_count = 0;
+               tssa->cfg.blocks[0].predecessors_count = 0;
+       }
+
+       op_array = trace_buffer->op_array;
+       if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) {
+               ssa_vars_count = op_array->last_var;
+       } else {
+               ssa_vars_count = op_array->last_var + op_array->T;
+       }
+       var = frame->stack;
+       for (i = 0; i < ssa_vars_count; i++) {
+               var[i] = i;
+       }
+
+       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+               // TODO: For tracing, it's possible, to create pseudo Phi functions
+               //       at the end of loop, without this additional pass (like LuaJIT) ???
+               ssa_vars_count = zend_jit_trace_add_phis(trace_buffer, ssa_vars_count, tssa, var);
+       }
+
+       p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE;
+       idx = 0;
+       level = 0;
+       for (;;p++) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       opline = p->opline;
+                       ssa_opcodes[idx] = opline;
+                       ssa_vars_count = zend_ssa_rename_op(op_array, opline, idx, build_flags, ssa_vars_count, ssa_ops, var);
+                       idx++;
+                       len = zend_jit_trace_op_len(p->opline);
+                       while (len > 1) {
+                               opline++;
+                               ssa_opcodes[idx] = opline;
+                               if (opline->opcode != ZEND_OP_DATA) {
+                                       ssa_vars_count = zend_ssa_rename_op(op_array, opline, idx, build_flags, ssa_vars_count, ssa_ops, var);
+                               }
+                               idx++;
+                               len--;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_ENTER) {
+                       frame = zend_jit_trace_call_frame(frame, op_array);
+                       var = frame->stack;
+                       op_array = p->op_array;
+                       level++;
+                       ZEND_ASSERT(ssa_vars_count < 0xff);
+                       p->first_ssa_var = ssa_vars_count;
+                       for (i = 0; i < op_array->last_var; i++) {
+                               var[i] = ssa_vars_count++;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_BACK) {
+                       op_array = p->op_array;
+                       frame = zend_jit_trace_ret_frame(frame, op_array);
+                       var = frame->stack;
+                       if (level == 0) {
+                               ZEND_ASSERT(ssa_vars_count <= 0xff);
+                               p->first_ssa_var = ssa_vars_count;
+                               for (i = 0; i < op_array->last_var + op_array->T; i++) {
+                                       var[i] = ssa_vars_count++;
+                               }
+                       } else {
+                               level--;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_END) {
+                       break;
+               }
+       }
+
+       op_array = trace_buffer->op_array;
+       tssa->vars_count = ssa_vars_count;
+       tssa->vars = ssa_vars = zend_arena_calloc(&CG(arena), tssa->vars_count, sizeof(zend_ssa_var));
+       if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) {
+               vars_count = op_array->last_var;
+       } else {
+               vars_count = op_array->last_var + op_array->T;
+       }
+       i = 0;
+       while (i < vars_count) {
+               ssa_vars[i].var = i;
+               ssa_vars[i].scc = -1;
+               ssa_vars[i].definition = -1;
+               ssa_vars[i].use_chain = -1;
+               i++;
+       }
+       while (i < tssa->vars_count) {
+               ssa_vars[i].var = -1;
+               ssa_vars[i].scc = -1;
+               ssa_vars[i].definition = -1;
+               ssa_vars[i].use_chain = -1;
+               i++;
+       }
+
+       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+               /* Update Phi sources */
+               zend_ssa_phi *phi = tssa->blocks[1].phis;
+
+               while (phi) {
+                       phi->sources[1] = var[phi->var];
+                       ssa_vars[phi->ssa_var].var = phi->var;
+                       ssa_vars[phi->ssa_var].definition_phi = phi;
+                       ssa_vars[phi->sources[0]].phi_use_chain = phi;
+                       ssa_vars[phi->sources[1]].phi_use_chain = phi;
+                       phi = phi->next;
+               }
+       }
+
+       /* 3. Compute use-def chains */
+       idx = (ssa_ops_count - 1);
+       op = ssa_ops + idx;
+       while (idx >= 0) {
+               opline = ssa_opcodes[idx];
+               if (op->op1_use >= 0) {
+                       op->op1_use_chain = ssa_vars[op->op1_use].use_chain;
+                       ssa_vars[op->op1_use].use_chain = idx;
+               }
+               if (op->op2_use >= 0 && op->op2_use != op->op1_use) {
+                       op->op2_use_chain = ssa_vars[op->op2_use].use_chain;
+                       ssa_vars[op->op2_use].use_chain = idx;
+               }
+               if (op->result_use >= 0 && op->result_use != op->op1_use && op->result_use != op->op2_use) {
+                       op->res_use_chain = ssa_vars[op->result_use].use_chain;
+                       ssa_vars[op->result_use].use_chain = idx;
+               }
+               if (op->op1_def >= 0) {
+                       ssa_vars[op->op1_def].var = EX_VAR_TO_NUM(opline->op1.var);
+                       ssa_vars[op->op1_def].definition = idx;
+               }
+               if (op->op2_def >= 0) {
+                       ssa_vars[op->op2_def].var = EX_VAR_TO_NUM(opline->op2.var);
+                       ssa_vars[op->op2_def].definition = idx;
+               }
+               if (op->result_def >= 0) {
+                       ssa_vars[op->result_def].var = EX_VAR_TO_NUM(opline->result.var);
+                       ssa_vars[op->result_def].definition = idx;
+               }
+               op--;
+               idx--;
+       }
+
+       /* 4. Type inference */
+       op_array = trace_buffer->op_array;
+       jit_extension =
+               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+       ssa = &jit_extension->func_info.ssa;
+
+       tssa->var_info = ssa_var_info = zend_arena_calloc(&CG(arena), tssa->vars_count, sizeof(zend_ssa_var_info));
+
+       if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) {
+               i = 0;
+               while (i < op_array->last_var) {
+                       if (!ssa->var_info
+                        || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, i)) {
+                               if (i < op_array->num_args) {
+                                       if (op_array->arg_info) {
+                                               zend_arg_info *arg_info = &op_array->arg_info[i];
+                                               zend_class_entry *ce;
+                                               uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
+
+                                               if (ZEND_ARG_SEND_MODE(arg_info)) {
+                                                       tmp |= MAY_BE_REF;
+                                               }
+                                               ssa_var_info[i].type = tmp;
+                                       } else {
+                                               ssa_var_info[i].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY  | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+                                       }
+                               } else {
+                                       ssa_var_info[i].type = MAY_BE_UNDEF;
+                               }
+                       }
+                       i++;
+               }
+       } else {
+               int parent_vars_count = 0;
+               zend_jit_trace_stack *parent_stack = NULL;
+
+               i = 0;
+               if (parent_trace) {
+                       parent_vars_count = MIN(zend_jit_traces[parent_trace].exit_info[exit_num].stack_size,
+                               op_array->last_var + op_array->T);
+                       if (parent_vars_count) {
+                               parent_stack =
+                                       zend_jit_traces[parent_trace].stack_map +
+                                       zend_jit_traces[parent_trace].exit_info[exit_num].stack_offset;
+                       }
+               }
+               while (i < op_array->last_var + op_array->T) {
+                       if (!ssa->var_info
+                        || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, i)) {
+                               if (i < op_array->last_var) {
+                                       ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY  | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+                               } else {
+                                       ssa_var_info[i].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY  | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+                               }
+                       }
+                       if (i < parent_vars_count) {
+                               /* Initialize TSSA variable from parent trace */
+                               zend_uchar op_type = parent_stack[i];
+
+                               if (op_type != IS_UNKNOWN) {
+                                       ssa_var_info[i].type &= zend_jit_trace_type_to_info(op_type);
+                               }
+                       }
+                       i++;
+               }
+       }
+
+       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+               /* Propagae initial value through Phi functions */
+               zend_ssa_phi *phi = tssa->blocks[1].phis;
+
+               while (phi) {
+                       if (!ssa->var_info
+                        || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, phi->ssa_var)) {
+                               ssa_var_info[phi->ssa_var].type = ssa_var_info[phi->sources[0]].type;
+                       }
+                       phi = phi->next;
+               }
+       }
+
+       frame = JIT_G(current_frame);
+       top = zend_jit_trace_call_frame(frame, op_array);
+       frame->call = NULL;
+       frame->prev = NULL;
+       frame->func = (const zend_function*)op_array;
+       frame->return_ssa_var = -1;
+       for (i = 0; i < op_array->last_var + op_array->T; i++) {
+               frame->stack[i] = -1;
+       }
+       memset(&return_value_info, 0, sizeof(return_value_info));
+
+       p = trace_buffer + ZEND_JIT_TRACE_START_REC_SIZE;
+       idx = 0;
+       level = 0;
+       for (;;p++) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       uint8_t op1_type, op2_type, op3_type;
+
+                       // TODO: use types recorded in trace (add guards) ???
+                       // TODO: range inference ???
+                       opline = p->opline;
+
+
+                       op1_type = p->op1_type;
+                       op2_type = p->op2_type;
+                       op3_type = p->op3_type;
+                       if (op1_type & IS_TRACE_REFERENCE) {
+                               op1_type = IS_UNKNOWN;
+                       }
+                       if (op2_type & IS_TRACE_REFERENCE) {
+                               op2_type = IS_UNKNOWN;
+                       }
+                       if (op3_type & IS_TRACE_REFERENCE) {
+                               op3_type = IS_UNKNOWN;
+                       }
+
+                       if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) {
+                               // TODO: support for recorded classes ???
+                               p++;
+                       }
+                       if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                               // TODO: support for recorded classes ???
+                               p++;
+                       }
+
+                       switch (opline->opcode) {
+                               case ZEND_ASSIGN_DIM_OP:
+                               case ZEND_ASSIGN_DIM:
+                                       if (tssa->ops[idx+1].op1_use >= 0 && op3_type != IS_UNKNOWN) {
+                                               zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx+1].op1_use];
+                                               if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op3_type)) {
+                                                       info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op3_type, info->type);
+                                               }
+                                       }
+                                       /* break missing intentionally */
+                               case ZEND_BW_OR:
+                               case ZEND_BW_AND:
+                               case ZEND_BW_XOR:
+                               case ZEND_SL:
+                               case ZEND_SR:
+                               case ZEND_MOD:
+                               case ZEND_ADD:
+                               case ZEND_SUB:
+                               case ZEND_MUL:
+//                             case ZEND_DIV: // TODO: check for division by zero ???
+                               case ZEND_CONCAT:
+                               case ZEND_FAST_CONCAT:
+                               case ZEND_ASSIGN_OP:
+                               case ZEND_IS_EQUAL:
+                               case ZEND_IS_NOT_EQUAL:
+                               case ZEND_IS_SMALLER:
+                               case ZEND_IS_SMALLER_OR_EQUAL:
+                               case ZEND_CASE:
+                               case ZEND_IS_IDENTICAL:
+                               case ZEND_IS_NOT_IDENTICAL:
+                               case ZEND_FETCH_DIM_R:
+                               case ZEND_FETCH_DIM_IS:
+                               case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+                                       if (tssa->ops[idx].op2_use >= 0 && op2_type != IS_UNKNOWN) {
+                                               zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op2_use];
+                                               if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op2_type)) {
+                                                       info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op2_type, info->type);
+                                               }
+                                       }
+                                       /* break missing intentionally */
+                               case ZEND_PRE_INC:
+                               case ZEND_PRE_DEC:
+                               case ZEND_POST_INC:
+                               case ZEND_POST_DEC:
+                               case ZEND_QM_ASSIGN:
+                               case ZEND_BOOL:
+                               case ZEND_BOOL_NOT:
+                               case ZEND_JMPZ:
+                               case ZEND_JMPNZ:
+                               case ZEND_JMPZNZ:
+                               case ZEND_JMPZ_EX:
+                               case ZEND_JMPNZ_EX:
+                                       if (tssa->ops[idx].op1_use >= 0 && op1_type != IS_UNKNOWN) {
+                                               zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op1_use];
+                                               if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op1_type)) {
+                                                       info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op1_type, info->type);
+                                               }
+                                       }
+                                       break;
+                               case ZEND_ASSIGN:
+                                       if (tssa->ops[idx].op2_use >= 0 && op2_type != IS_UNKNOWN) {
+                                               zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op2_use];
+                                               if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op2_type)) {
+                                                       info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op2_type, info->type);
+                                               }
+                                       }
+                                       break;
+                               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:
+                                       if (tssa->ops[idx].op1_use >= 0 && op1_type != IS_UNKNOWN) {
+                                               zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op1_use];
+                                               if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op1_type)) {
+                                                       info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op1_type, info->type);
+                                               }
+                                       }
+                                       /* Propagate argument type */
+                                       if (frame->call
+                                        && frame->call->func->type == ZEND_USER_FUNCTION
+                                        && opline->op2.num <= frame->call->func->op_array.num_args) {
+                                               uint32_t info;
+
+                                               if (opline->op1_type == IS_CONST) {
+                                                       info = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op1)));
+                                               } else {
+                                                       ZEND_ASSERT(ssa_ops[idx].op1_use >= 0);
+                                                       info = ssa_var_info[ssa_ops[idx].op1_use].type & ~MAY_BE_GUARD;
+                                               }
+                                               if (frame->call->func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
+                                                       zend_arg_info *arg_info;
+
+                                                       ZEND_ASSERT(frame->call->func->op_array.arg_info);
+                                                       arg_info = &frame->call->func->op_array.arg_info[opline->op2.num - 1];
+                                                       if (ZEND_TYPE_IS_SET(arg_info->type)) {
+                                                               info &= ZEND_TYPE_FULL_MASK(arg_info->type);
+                                                               if (!info) {
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                               if (info & MAY_BE_UNDEF) {
+                                                       info |= MAY_BE_NULL;
+                                                       info &= ~MAY_BE_UNDEF;
+                                               }
+                                               if (ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) {
+                                                       info |= MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_KEY_ANY;
+                                               }
+                                               frame->call->stack[opline->op2.num - 1] = info;
+                                       }
+                                       break;
+                               case ZEND_RETURN:
+                                       if (tssa->ops[idx].op1_use >= 0 && op1_type != IS_UNKNOWN) {
+                                               zend_ssa_var_info *info = &ssa_var_info[ssa_ops[idx].op1_use];
+                                               if ((info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1 << op1_type)) {
+                                                       info->type = MAY_BE_GUARD | zend_jit_trace_type_to_info_ex(op1_type, info->type);
+                                               }
+                                       }
+                                       /* Propagate return value types */
+                                       if (opline->op1_type == IS_UNUSED) {
+                                               return_value_info.type = MAY_BE_NULL;
+                                       } else if (opline->op1_type == IS_CONST) {
+                                               return_value_info.type = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op1)));
+                                       } else {
+                                               ZEND_ASSERT(ssa_ops[idx].op1_use >= 0);
+                                               return_value_info = ssa_var_info[ssa_ops[idx].op1_use];
+                                               return_value_info.type &= ~MAY_BE_GUARD;
+                                       }
+                                       break;
+                               default:
+                                       break;
+                       }
+                       len = zend_jit_trace_op_len(opline);
+                       if (ssa->var_info) {
+                               /* Add statically inferred ranges */
+                               if (ssa_ops[idx].op1_def >= 0) {
+                                       zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def);
+                               }
+                               if (ssa_ops[idx].op2_def >= 0) {
+                                       zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def);
+                               }
+                               if (ssa_ops[idx].result_def >= 0) {
+                                       zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def);
+                               }
+                               if (len == 2 && (opline+1)->opcode == ZEND_OP_DATA) {
+                                       if (ssa_ops[idx+1].op1_def >= 0) {
+                                               zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx+1].op1_def);
+                                       }
+                                       if (ssa_ops[idx+1].op2_def >= 0) {
+                                               zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx+1].op2_def);
+                                       }
+                                       if (ssa_ops[idx+1].result_def >= 0) {
+                                               zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx+1].result_def);
+                                       }
+                               }
+                       }
+                       if (opline->opcode == ZEND_RECV_INIT
+                        && !(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+                               /* RECV_INIT always copy the constant */
+                               ssa_var_info[ssa_ops[idx].result_def].type = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op2)));
+                       } else {
+                               if (zend_update_type_info(op_array, tssa, script, (zend_op*)opline, ssa_ops + idx, ssa_opcodes, optimization_level) == FAILURE) {
+                                       // TODO:
+                                       assert(0);
+                               }
+                       }
+                       if (ssa->var_info) {
+                               /* Add statically inferred restrictions */
+                               if (ssa_ops[idx].op1_def >= 0) {
+                                       if ((opline->opcode == ZEND_SEND_VAR_EX
+                                         || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
+                                         || opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG)
+                                        && frame
+                                        && frame->call
+                                        && frame->call->func
+                                        && !ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) {
+                                               ssa_var_info[ssa_ops[idx].op1_def] = ssa_var_info[ssa_ops[idx].op1_use];
+                                               if (opline->opcode == ZEND_SEND_VAR_EX) {
+                                                       ssa_var_info[ssa_ops[idx].op1_def].type &= ~MAY_BE_GUARD;
+                                               }
+                                               if (ssa_var_info[ssa_ops[idx].op1_def].type & MAY_BE_RC1) {
+                                                       ssa_var_info[ssa_ops[idx].op1_def].type |= MAY_BE_RCN;
+                                               }
+                                       } else {
+                                               zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def);
+                                       }
+                               }
+                               if (ssa_ops[idx].op2_def >= 0) {
+                                       zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def);
+                               }
+                               if (ssa_ops[idx].result_def >= 0) {
+                                       zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def);
+                               }
+                       }
+                       idx++;
+                       while (len > 1) {
+                               opline++;
+                               if (opline->opcode != ZEND_OP_DATA) {
+                                       if (ssa->var_info) {
+                                               /* Add statically inferred ranges */
+                                               if (ssa_ops[idx].op1_def >= 0) {
+                                                       zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def);
+                                               }
+                                               if (ssa_ops[idx].op2_def >= 0) {
+                                                       zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def);
+                                               }
+                                               if (ssa_ops[idx].result_def >= 0) {
+                                                       zend_jit_trace_copy_ssa_var_range(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def);
+                                               }
+                                       }
+                                       if (opline->opcode == ZEND_RECV_INIT
+                                        && !(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+                                               /* RECV_INIT always copy the constant */
+                                               ssa_var_info[ssa_ops[idx].result_def].type = zend_jit_trace_type_to_info(Z_TYPE_P(RT_CONSTANT(opline, opline->op2)));
+                                       } else {
+                                               if (zend_update_type_info(op_array, tssa, script, (zend_op*)opline, ssa_ops + idx, ssa_opcodes, optimization_level) == FAILURE) {
+                                                       // TODO:
+                                                       assert(0);
+                                               }
+                                       }
+                               }
+                               if (ssa->var_info) {
+                                       /* Add statically inferred restrictions */
+                                       if (ssa_ops[idx].op1_def >= 0) {
+                                               zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op1_def);
+                                       }
+                                       if (ssa_ops[idx].op2_def >= 0) {
+                                               zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].op2_def);
+                                       }
+                                       if (ssa_ops[idx].result_def >= 0) {
+                                               zend_jit_trace_restrict_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, ssa_ops[idx].result_def);
+                                       }
+                               }
+                               idx++;
+                               len--;
+                       }
+
+               } else if (p->op == ZEND_JIT_TRACE_ENTER) {
+                       op_array = p->op_array;
+                       jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                       ssa = &jit_extension->func_info.ssa;
+
+                       call = frame->call;
+                       if (!call) {
+                               /* Trace missed INIT_FCALL opcode */
+                               call = top;
+                               call->call = NULL;
+                               call->prev = NULL;
+                               call->func = (const zend_function*)op_array;
+                               top = zend_jit_trace_call_frame(top, op_array);
+                               for (i = 0; i < op_array->last_var + op_array->T; i++) {
+                                       call->stack[i] = -1;
+                               }
+                       } else {
+                               ZEND_ASSERT(&call->func->op_array == op_array);
+                       }
+                       frame->call = call->prev;
+                       call->prev = frame;
+                       call->return_ssa_var = find_return_ssa_var(p - 1, ssa_ops + (idx -1));
+                       frame = call;
+
+                       level++;
+                       i = 0;
+                       v = p->first_ssa_var;
+                       while (i < op_array->last_var) {
+                               if (!ssa->var_info
+                                || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, v)) {
+                                       if (i < op_array->num_args) {
+                                               if (op_array->arg_info) {
+                                                       zend_arg_info *arg_info = &op_array->arg_info[i];
+                                                       zend_class_entry *ce;
+                                                       uint32_t tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
+
+                                                       if (ZEND_ARG_SEND_MODE(arg_info)) {
+                                                               tmp |= MAY_BE_REF;
+                                                       }
+                                                       ssa_var_info[v].type = tmp;
+                                               } else {
+                                                       ssa_var_info[v].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY  | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+                                               }
+                                       } else {
+                                               ssa_var_info[v].type = MAY_BE_UNDEF;
+                                       }
+                               }
+                               if (i < op_array->num_args) {
+                                       /* Propagate argument type */
+                                       ssa_var_info[v].type &= frame->stack[i];
+                               }
+                               i++;
+                               v++;
+                       }
+
+               } else if (p->op == ZEND_JIT_TRACE_BACK) {
+                       op_array = p->op_array;
+                       jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                       ssa = &jit_extension->func_info.ssa;
+                       if (level == 0) {
+                               i = 0;
+                               v = p->first_ssa_var;
+                               while (i < op_array->last_var) {
+                                       if (!ssa->var_info
+                                        || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, v)) {
+                                               ssa_var_info[v].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY  | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+                                       }
+                                       i++;
+                                       v++;
+                               }
+                               while (i < op_array->last_var + op_array->T) {
+                                       if (!ssa->var_info
+                                        || !zend_jit_trace_copy_ssa_var_info(op_array, ssa, ssa_opcodes, tssa, v)) {
+                                               ssa_var_info[v].type = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY  | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+                                       }
+                                       i++;
+                                       v++;
+                               }
+                               if (return_value_info.type != 0) {
+                                       if ((p+1)->op == ZEND_JIT_TRACE_VM) {
+                                               const zend_op *opline = (p+1)->opline - 1;
+                                               if (opline->result_type != IS_UNUSED) {
+                                                       ssa_var_info[
+                                                               p->first_ssa_var +
+                                                               EX_VAR_TO_NUM(opline->result.var)] = return_value_info;
+                                               }
+                                       }
+                                       memset(&return_value_info, 0, sizeof(return_value_info));
+                               }
+                       } else {
+                               level--;
+                               if (return_value_info.type != 0) {
+                                       if ((p+1)->op == ZEND_JIT_TRACE_VM) {
+                                               const zend_op *opline = (p+1)->opline - 1;
+                                               if (opline->result_type != IS_UNUSED) {
+                                                       if (frame->return_ssa_var >= 0) {
+                                                               ssa_var_info[frame->return_ssa_var] = return_value_info;
+                                                       }
+                                               }
+                                       }
+                                       memset(&return_value_info, 0, sizeof(return_value_info));
+                               }
+                       }
+
+                       top = frame;
+                       if (frame->prev) {
+                               frame = frame->prev;
+                               ZEND_ASSERT(&frame->func->op_array == op_array);
+                       } else {
+                               frame = zend_jit_trace_ret_frame(frame, op_array);
+                               frame->call = NULL;
+                               frame->prev = NULL;
+                               frame->func = (const zend_function*)op_array;
+                               frame->return_ssa_var = -1;
+                               for (i = 0; i < op_array->last_var + op_array->T; i++) {
+                                       frame->stack[i] = -1;
+                               }
+                       }
+
+               } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) {
+                       call = top;
+                       call->call = NULL;
+                       call->prev = frame->call;
+                       call->func = p->func;
+                       frame->call = call;
+                       top = zend_jit_trace_call_frame(top, p->op_array);
+                       if (p->func->type == ZEND_USER_FUNCTION) {
+                               for (i = 0; i < p->op_array->last_var + p->op_array->T; i++) {
+                                       call->stack[i] = -1;
+                               }
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) {
+                       call = frame->call;
+                       if (call) {
+                               top = call;
+                               frame->call = call->prev;
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_END) {
+                       break;
+               }
+       }
+
+       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+               /* Propagate guards through Phi sources */
+               zend_ssa_phi *phi = tssa->blocks[1].phis;
+
+               while (phi) {
+                       uint32_t t  = ssa_var_info[phi->ssa_var].type;
+                       uint32_t t0 = ssa_var_info[phi->sources[0]].type;
+                       uint32_t t1 = ssa_var_info[phi->sources[1]].type;
+
+                       if (t & MAY_BE_GUARD) {
+                               if (((t0 | t1) & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) {
+                                       if (!((t0 | t1) & MAY_BE_GUARD)) {
+                                               ssa_var_info[phi->ssa_var].type = t & ~MAY_BE_GUARD;
+                                       }
+                               } else if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) {
+                                       if (!(t1 & MAY_BE_GUARD)) {
+                                               ssa_var_info[phi->ssa_var].type = t & ~MAY_BE_GUARD;
+                                               ssa_var_info[phi->sources[0]].type = t | MAY_BE_GUARD;
+                                       }
+                               } else {
+                                       if ((t0 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) {
+                                               ssa_var_info[phi->sources[0]].type = MAY_BE_GUARD | (t & t0);
+                                       }
+                                       if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != (t & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF))) {
+                                               if (is_checked_guard(tssa, ssa_opcodes, phi->sources[1], phi->ssa_var)) {
+                                                       ssa_var_info[phi->sources[1]].type = MAY_BE_GUARD | (t & t1);
+                                                       ssa_var_info[phi->ssa_var].type = t & ~MAY_BE_GUARD;
+                                               }
+                                       }
+                               }
+                       }
+                       phi = phi->next;
+               }
+       }
+
+       if (UNEXPECTED(ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_TSSA)) {
+               zend_jit_dump_trace(trace_buffer, tssa);
+               fprintf(stderr, "---- TRACE analysed\n");
+       }
+
+       return tssa;
+}
+
+static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t parent_trace, uint32_t exit_num)
+{
+       const void *handler = NULL;
+       dasm_State* dasm_state = NULL;
+       zend_script *script = NULL;
+       zend_lifetime_interval **ra = NULL;
+       zend_string *name = NULL;
+       void *checkpoint;
+       const zend_op_array *op_array;
+       zend_ssa *ssa, *op_array_ssa;
+       zend_jit_trace_rec *p;
+       int call_level = -1; // TODO: proper support for inlined functions ???
+       zend_jit_op_array_trace_extension *jit_extension;
+       int num_op_arrays = 0;
+       zend_jit_trace_info *t;
+       const zend_op_array *op_arrays[ZEND_JIT_TRACE_MAX_FUNCS];
+       zend_uchar smart_branch_opcode;
+       const void *exit_addr;
+       uint32_t op1_info, op1_def_info, op2_info, res_info, res_use_info, op1_data_info;
+       zend_bool send_result = 0;
+       zend_jit_addr op1_addr, op1_def_addr, op2_addr, op2_def_addr, res_addr;
+       zend_class_entry *ce;
+       uint32_t i;
+       zend_jit_trace_stack_frame *frame, *top, *call;
+       zend_jit_trace_stack *stack;
+       zend_uchar res_type = IS_UNKNOWN;
+       const zend_op *opline, *orig_opline;
+       const zend_ssa_op *ssa_op, *orig_ssa_op;
+
+       checkpoint = zend_arena_checkpoint(CG(arena));
+
+       ssa = zend_jit_trace_build_tssa(trace_buffer, parent_trace, exit_num, script, op_arrays, &num_op_arrays);
+
+       p = trace_buffer;
+       ZEND_ASSERT(p->op == ZEND_JIT_TRACE_START);
+       op_array = p->op_array;
+       frame = JIT_G(current_frame);
+       top = zend_jit_trace_call_frame(frame, op_array);
+       frame->call = NULL;
+       frame->prev = NULL;
+       frame->func = (const zend_function*)op_array;
+       frame->return_value_used = -1;
+       frame->nested = 0;
+       frame->num_args = -1;
+       stack = frame->stack;
+
+       if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) {
+               i = 0;
+               while (i < op_array->last_var) {
+                       if (!(ssa->var_info[i].type & MAY_BE_GUARD)
+                        && has_concrete_type(ssa->var_info[i].type)) {
+                               stack[i] = concrete_type(ssa->var_info[i].type);
+                       } else if (i < op_array->num_args) {
+                               stack[i] = IS_UNKNOWN;
+                       } else {
+                               stack[i] = IS_UNDEF;
+                       }
+                       i++;
+               }
+       } else {
+               int parent_vars_count = 0;
+               zend_jit_trace_stack *parent_stack = NULL;
+
+               i = 0;
+               if (parent_trace) {
+                       parent_vars_count = MIN(zend_jit_traces[parent_trace].exit_info[exit_num].stack_size,
+                               op_array->last_var + op_array->T);
+                       if (parent_vars_count) {
+                               parent_stack =
+                                       zend_jit_traces[parent_trace].stack_map +
+                                       zend_jit_traces[parent_trace].exit_info[exit_num].stack_offset;
+                       }
+               }
+               while (i < op_array->last_var + op_array->T) {
+                       if (!(ssa->var_info[i].type & MAY_BE_GUARD)
+                        && has_concrete_type(ssa->var_info[i].type)) {
+                               stack[i] = concrete_type(ssa->var_info[i].type);
+                       } else if (i < parent_vars_count
+                        && (zend_uchar)parent_stack[i] != IS_UNKNOWN) {
+                               stack[i] = parent_stack[i];
+                       } else {
+                               stack[i] = IS_UNKNOWN;
+                       }
+                       i++;
+               }
+       }
+
+       opline = ((zend_jit_trace_start_rec*)p)->opline;
+       name = zend_jit_trace_name(op_array, opline->lineno);
+       p += ZEND_JIT_TRACE_START_REC_SIZE;
+
+       dasm_init(&dasm_state, DASM_MAXSECTION);
+       dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX);
+       dasm_setup(&dasm_state, dasm_actions);
+
+       jit_extension =
+               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+       op_array_ssa = &jit_extension->func_info.ssa;
+
+       // TODO: register allocation ???
+
+       dasm_growpc(&dasm_state, 1); /* trace needs just one global lable for loop */
+
+       zend_jit_align_func(&dasm_state);
+       if (!parent_trace) {
+               zend_jit_prologue(&dasm_state);
+       }
+       zend_jit_trace_begin(&dasm_state, ZEND_JIT_TRACE_NUM);
+
+       if (!parent_trace) {
+               zend_jit_set_opline(&dasm_state, opline);
+       } else {
+               if (!((uintptr_t)zend_jit_traces[parent_trace].exit_info[exit_num].opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED))) {
+                       zend_jit_trace_opline_guard(&dasm_state, opline);
+                       zend_jit_set_opline(&dasm_state, opline);
+               } else {
+                       zend_jit_reset_opline(&dasm_state, opline);
+               }
+       }
+
+       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP
+        || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL
+        || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) {
+
+               if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+                       /* Check loop-invariant varaible types */
+                       for (i = 0; i < op_array->last_var + op_array->T; i++) {
+                               uint32_t info = ssa->var_info[i].type;
+
+                               ZEND_ASSERT(ssa->vars[i].definition == -1);
+                               ZEND_ASSERT(ssa->vars[i].definition_phi == NULL);
+                               ZEND_ASSERT(ssa->vars[i].var == i);
+
+                               if (info & MAY_BE_GUARD) {
+                                       if (ssa->vars[i].use_chain != -1
+                                        || (ssa->vars[i].phi_use_chain
+                                         && !(ssa->var_info[ssa->vars[i].phi_use_chain->ssa_var].type & MAY_BE_GUARD))) {
+                                               if (!zend_jit_type_guard(&dasm_state, opline, EX_NUM_TO_VAR(i), concrete_type(info))) {
+                                                       goto jit_failure;
+                                               }
+                                               ssa->var_info[i].type = info & ~MAY_BE_GUARD;
+                                               stack[i] = concrete_type(info);
+                                       }
+                               }
+                       }
+               }
+
+               zend_jit_label(&dasm_state, 0); /* start of of trace loop */
+
+               if (trace_buffer->stop != ZEND_JIT_TRACE_STOP_RECURSIVE_RET) {
+                       // TODO: interupt exit may require deoptimization through side exit ???
+                       zend_jit_check_timeout(&dasm_state, opline);
+               }
+       }
+
+       ssa_op = ssa->ops;
+       for (;;p++) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       uint8_t op1_type = p->op1_type;
+                       uint8_t op2_type = p->op2_type;
+                       uint8_t op3_type = p->op3_type;
+
+                       opline = p->opline;
+                       if (op1_type & IS_TRACE_REFERENCE) {
+                               op1_type = IS_UNKNOWN;
+                       }
+                       if (op2_type & IS_TRACE_REFERENCE) {
+                               op2_type = IS_UNKNOWN;
+                       }
+                       if (op3_type & IS_TRACE_REFERENCE) {
+                               op3_type = IS_UNKNOWN;
+                       }
+
+                       if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) {
+                               // TODO: support for recorded classes ???
+                               p++;
+                       }
+                       if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                               // TODO: support for recorded classes ???
+                               p++;
+                       }
+
+#if 0
+                       // TODO: call level calculation doesn't work for traces ???
+                       switch (opline->opcode) {
+                               case ZEND_INIT_FCALL:
+                               case ZEND_INIT_FCALL_BY_NAME:
+                               case ZEND_INIT_NS_FCALL_BY_NAME:
+                               case ZEND_INIT_METHOD_CALL:
+                               case ZEND_INIT_DYNAMIC_CALL:
+                               case ZEND_INIT_STATIC_METHOD_CALL:
+                               case ZEND_INIT_USER_CALL:
+                               case ZEND_NEW:
+                                       call_level++;
+                       }
+#endif
+
+                       if (zend_jit_level >= ZEND_JIT_LEVEL_INLINE) {
+                               switch (opline->opcode) {
+                                       case ZEND_PRE_INC:
+                                       case ZEND_PRE_DEC:
+                                       case ZEND_POST_INC:
+                                       case ZEND_POST_DEC:
+                                               if (opline->op1_type != IS_CV) {
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               if (!(op1_info & MAY_BE_LONG)) {
+                                                       break;
+                                               }
+                                               if (opline->result_type != IS_UNUSED) {
+                                                       res_use_info = RES_USE_INFO_EX();
+                                                       USE_RES_TRACE_TYPE();
+                                                       res_info = RES_INFO_EX();
+                                                       res_addr = RES_REG_ADDR();
+                                               } else {
+                                                       res_use_info = -1;
+                                                       res_info = -1;
+                                                       res_addr = 0;
+                                               }
+                                               op1_def_info = OP1_DEF_INFO_EX();
+                                               if (!zend_jit_inc_dec(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(),
+                                                               op1_def_info, OP1_DEF_REG_ADDR(),
+                                                               res_use_info, res_info,
+                                                               res_addr,
+                                                               (op1_def_info & MAY_BE_LONG) && (op1_def_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) && zend_may_overflow_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               if ((op1_def_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) {
+                                                       ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD;
+                                                       if (opline->result_type != IS_UNUSED) {
+                                                               ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_BW_OR:
+                                       case ZEND_BW_AND:
+                                       case ZEND_BW_XOR:
+                                       case ZEND_SL:
+                                       case ZEND_SR:
+                                       case ZEND_MOD:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
+                                                       break;
+                                               }
+                                               if (!(op1_info & MAY_BE_LONG)
+                                                || !(op2_info & MAY_BE_LONG)) {
+                                                       break;
+                                               }
+                                               if (opline->result_type == IS_TMP_VAR
+                                                && (p+1)->op == ZEND_JIT_TRACE_VM
+                                                && (p+1)->opline == opline + 1
+                                                && (opline+1)->opcode == ZEND_SEND_VAL
+                                                && (opline+1)->op1_type == IS_TMP_VAR
+                                                && (opline+1)->op1.var == opline->result.var) {
+                                                       p++;
+                                                       if (frame->call) {
+                                                               uint8_t res_type = p->op1_type;
+                                                               if (res_type & IS_TRACE_REFERENCE) {
+                                                                       res_type = IS_UNKNOWN;;
+                                                               }
+                                                               if (res_type != IS_UNKNOWN) {
+                                                                       zend_jit_trace_send_type(opline+1, frame->call, res_type);
+                                                               }
+                                                       }
+                                                       while ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE ||
+                                                             (p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                                                               p++;
+                                                       }
+                                                       send_result = 1;
+                                                       res_use_info = -1;
+                                                       res_addr = 0; /* set inside backend */
+                                               } else {
+                                                       send_result = 0;
+                                                       res_use_info = RES_USE_INFO_EX();
+                                                       USE_RES_TRACE_TYPE();
+                                                       res_addr = RES_REG_ADDR();
+                                               }
+                                               res_info = RES_INFO_EX();
+                                               if (!zend_jit_long_math(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_RANGE_EX(), OP1_REG_ADDR(),
+                                                               op2_info, OP2_RANGE_EX(), OP2_REG_ADDR(),
+                                                               res_use_info, res_info, res_addr,
+                                                               send_result,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_ADD:
+                                       case ZEND_SUB:
+                                       case ZEND_MUL:
+//                                     case ZEND_DIV: // TODO: check for division by zero ???
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
+                                                       break;
+                                               }
+                                               if (!(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE)) ||
+                                                   !(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
+                                                       break;
+                                               }
+                                               if (opline->result_type == IS_TMP_VAR
+                                                && (p+1)->op == ZEND_JIT_TRACE_VM
+                                                && (p+1)->opline == opline + 1
+                                                && (opline+1)->opcode == ZEND_SEND_VAL
+                                                && (opline+1)->op1_type == IS_TMP_VAR
+                                                && (opline+1)->op1.var == opline->result.var) {
+                                                       p++;
+                                                       if (frame->call
+                                                        && frame->call->func->type == ZEND_USER_FUNCTION) {
+                                                               uint8_t res_type = p->op1_type;
+                                                               if (res_type & IS_TRACE_REFERENCE) {
+                                                                       res_type = IS_UNKNOWN;;
+                                                               }
+                                                               if (res_type != IS_UNKNOWN) {
+                                                                       zend_jit_trace_send_type(opline+1, frame->call, res_type);
+                                                               }
+                                                       }
+                                                       while ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE ||
+                                                             (p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                                                               p++;
+                                                       }
+                                                       send_result = 1;
+                                                       res_use_info = -1;
+                                                       res_addr = 0; /* set inside backend */
+                                               } else {
+                                                       send_result = 0;
+                                                       res_use_info = RES_USE_INFO_EX();
+                                                       USE_RES_TRACE_TYPE();
+                                                       res_addr = RES_REG_ADDR();
+                                               }
+                                               res_info = RES_INFO_EX();
+                                               if (!zend_jit_math(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(),
+                                                               op2_info, OP2_REG_ADDR(),
+                                                               res_use_info, res_info, res_addr,
+                                                               send_result,
+                                                               (res_info & MAY_BE_LONG) && (res_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) && zend_may_overflow_ex(opline, ssa_op, op_array, ssa),
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               if ((res_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) {
+                                                       ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
+                                               }
+                                               goto done;
+                                       case ZEND_CONCAT:
+                                       case ZEND_FAST_CONCAT:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
+                                                       break;
+                                               }
+                                               if (!(op1_info & MAY_BE_STRING) ||
+                                                   !(op2_info & MAY_BE_STRING)) {
+                                                       break;
+                                               }
+                                               if (opline->result_type == IS_TMP_VAR
+                                                && (p+1)->op == ZEND_JIT_TRACE_VM
+                                                && (p+1)->opline == opline + 1
+                                                && (opline+1)->opcode == ZEND_SEND_VAL
+                                                && (opline+1)->op1_type == IS_TMP_VAR
+                                                && (opline+1)->op1.var == opline->result.var) {
+                                                       p++;
+                                                       if (frame->call
+                                                        && frame->call->func->type == ZEND_USER_FUNCTION) {
+                                                               uint8_t res_type = p->op1_type;
+                                                               if (res_type & IS_TRACE_REFERENCE) {
+                                                                       res_type = IS_UNKNOWN;;
+                                                               }
+                                                               if (res_type != IS_UNKNOWN) {
+                                                                       zend_jit_trace_send_type(opline+1, frame->call, res_type);
+                                                               }
+                                                       }
+                                                       while ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE ||
+                                                             (p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                                                               p++;
+                                                       }
+                                                       send_result = 1;
+                                               } else {
+                                                       send_result = 0;
+                                               }
+                                               res_info = RES_INFO_EX();
+                                               if (!zend_jit_concat(&dasm_state, opline, op_array,
+                                                               op1_info, op2_info, res_info, send_result,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_ASSIGN_OP:
+                                               if (opline->extended_value == ZEND_POW
+                                                || opline->extended_value == ZEND_DIV) {
+                                                       // TODO: check for division by zero ???
+                                                       break;
+                                               }
+                                               if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) {
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((op1_info & MAY_BE_UNDEF) || (op2_info & MAY_BE_UNDEF)) {
+                                                       break;
+                                               }
+                                               if (opline->extended_value == ZEND_ADD
+                                                || opline->extended_value == ZEND_SUB
+                                                || opline->extended_value == ZEND_MUL
+                                                || opline->extended_value == ZEND_DIV) {
+                                                       if (!(op1_info & (MAY_BE_LONG|MAY_BE_DOUBLE))
+                                                        || !(op2_info & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
+                                                               break;
+                                                       }
+                                               } else if (opline->extended_value == ZEND_BW_OR
+                                                || opline->extended_value == ZEND_BW_AND
+                                                || opline->extended_value == ZEND_BW_XOR
+                                                || opline->extended_value == ZEND_SL
+                                                || opline->extended_value == ZEND_SR
+                                                || opline->extended_value == ZEND_MOD) {
+                                                       if (!(op1_info & MAY_BE_LONG)
+                                                        || !(op2_info & MAY_BE_LONG)) {
+                                                               break;
+                                                       }
+                                               } else if (opline->extended_value == ZEND_CONCAT) {
+                                                       if (!(op1_info & MAY_BE_STRING)
+                                                        || !(op2_info & MAY_BE_STRING)) {
+                                                               break;
+                                                       }
+                                               }
+                                               op1_def_info = OP1_DEF_INFO_EX();
+                                               if (!zend_jit_assign_op(&dasm_state, opline, op_array,
+                                                               op1_info, op1_def_info, OP1_RANGE_EX(),
+                                                               op2_info, OP2_RANGE_EX(),
+                                                               (op1_def_info & MAY_BE_LONG) && (op1_def_info & (MAY_BE_DOUBLE|MAY_BE_GUARD)) && zend_may_overflow_ex(opline, ssa_op, op_array, ssa),
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               if ((op1_def_info & (MAY_BE_ANY|MAY_BE_GUARD)) == (MAY_BE_LONG|MAY_BE_GUARD)) {
+                                                       ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD;
+                                                       if (opline->result_type != IS_UNUSED) {
+                                                               ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_ASSIGN_DIM_OP:
+                                               if (opline->extended_value == ZEND_POW
+                                                || opline->extended_value == ZEND_DIV) {
+                                                       // TODO: check for division by zero ???
+                                                       break;
+                                               }
+                                               if (opline->op1_type != IS_CV || opline->result_type != IS_UNUSED) {
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               op1_data_info = OP1_DATA_INFO_EX();
+                                               CHECK_OP1_DATA_TRACE_TYPE();
+                                               op1_def_info = OP1_DEF_INFO_EX();
+                                               if (!zend_jit_assign_dim_op(&dasm_state, opline, op_array,
+                                                               op1_info, op1_def_info, op2_info,
+                                                               op1_data_info, OP1_DATA_RANGE(),
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_ASSIGN_DIM:
+                                               if (opline->op1_type != IS_CV) {
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               op1_data_info = OP1_DATA_INFO_EX();
+                                               CHECK_OP1_DATA_TRACE_TYPE();
+                                               if (!zend_jit_assign_dim(&dasm_state, opline, op_array,
+                                                               op1_info, op2_info, op1_data_info,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_ASSIGN:
+                                               if (opline->op1_type != IS_CV) {
+                                                       break;
+                                               }
+                                               if (opline->result_type == IS_UNUSED) {
+                                                       res_addr = 0;
+                                                       res_info = -1;
+                                               } else {
+                                                       res_addr = RES_REG_ADDR();
+                                                       res_info = RES_INFO_EX();
+                                               }
+                                               op2_addr = OP2_REG_ADDR();
+                                               if (ra
+                                                && ssa_op->op2_def >= 0
+                                                && !ssa->vars[ssa_op->op2_def].no_val) {
+                                                       op2_def_addr = OP2_DEF_REG_ADDR();
+                                               } else {
+                                                       op2_def_addr = op2_addr;
+                                               }
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               op1_info = OP1_INFO_EX();
+                                               op1_def_info = OP1_DEF_INFO_EX();
+                                               USE_OP1_TRACE_TYPE();
+                                               if (!zend_jit_assign(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(),
+                                                               op1_def_info, OP1_DEF_REG_ADDR(),
+                                                               op2_info, op2_addr, op2_def_addr,
+                                                               res_info, res_addr,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_QM_ASSIGN:
+                                               op1_addr = OP1_REG_ADDR();
+                                               if (ra
+                                                && ssa_op->op1_def >= 0
+                                                && !ssa->vars[ssa_op->op1_def].no_val) {
+                                                       op1_def_addr = OP1_DEF_REG_ADDR();
+                                               } else {
+                                                       op1_def_addr = op1_addr;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();//???USE_OP1_TRACE_TYPE();
+                                               res_info = RES_INFO_EX();
+                                               if (!zend_jit_qm_assign(&dasm_state, opline, op_array,
+                                                               op1_info, op1_addr, op1_def_addr,
+                                                               res_info, RES_REG_ADDR())) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_INIT_FCALL:
+                                       case ZEND_INIT_FCALL_BY_NAME:
+                                               if (!zend_jit_init_fcall(&dasm_state, opline, op_array_ssa->cfg.map ? op_array_ssa->cfg.map[opline - op_array->opcodes] : -1, op_array, op_array_ssa, call_level, p + 1)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_SEND_VAL:
+                                       case ZEND_SEND_VAL_EX:
+                                               if (opline->opcode == ZEND_SEND_VAL_EX
+                                                && opline->op2.num > MAX_ARG_FLAG_NUM) {
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE(); //???USE_OP1_TRACE_TYPE();
+                                               if (!zend_jit_send_val(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR())) {
+                                                       goto jit_failure;
+                                               }
+                                               if (frame->call
+                                                && frame->call->func->type == ZEND_USER_FUNCTION) {
+                                                       if (opline->op1_type == IS_CONST) {
+                                                               zend_jit_trace_send_type(opline, frame->call, Z_TYPE_P(RT_CONSTANT(opline, opline->op1)));
+                                                       } else if (op1_type != IS_UNKNOWN) {
+                                                               zend_jit_trace_send_type(opline, frame->call, op1_type);
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_SEND_REF:
+                                               op1_info = OP1_INFO_EX();
+                                               USE_OP1_TRACE_TYPE();
+                                               if (!zend_jit_send_ref(&dasm_state, opline, op_array,
+                                                               op1_info, 0)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_SEND_VAR:
+                                       case ZEND_SEND_VAR_EX:
+                                       case ZEND_SEND_VAR_NO_REF:
+                                       case ZEND_SEND_VAR_NO_REF_EX:
+                                               if ((opline->opcode == ZEND_SEND_VAR_EX
+                                                 || opline->opcode == ZEND_SEND_VAR_NO_REF_EX)
+                                                && opline->op2.num > MAX_ARG_FLAG_NUM) {
+                                                       break;
+                                               }
+                                               op1_addr = OP1_REG_ADDR();
+                                               if (ra
+                                                && ssa_op->op1_def >= 0
+                                                && !ssa->vars[ssa_op->op1_def].no_val) {
+                                                       op1_def_addr = OP1_DEF_REG_ADDR();
+                                               } else {
+                                                       op1_def_addr = op1_addr;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE(); //???USE_OP1_TRACE_TYPE();
+                                               if (!zend_jit_send_var(&dasm_state, opline, op_array,
+                                                               op1_info, op1_addr, op1_def_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               if (frame->call
+                                                && frame->call->func->type == ZEND_USER_FUNCTION) {
+                                                       if (opline->opcode == ZEND_SEND_VAR_EX
+                                                        && ARG_SHOULD_BE_SENT_BY_REF(frame->call->func, opline->op2.num)) {
+                                                               // TODO: this may require invalidation, if caller is changed ???
+                                                               goto done;
+                                                       }
+                                                       if (op1_type != IS_UNKNOWN) {
+                                                               zend_jit_trace_send_type(opline, frame->call, op1_type);
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_DO_UCALL:
+                                       case ZEND_DO_ICALL:
+                                       case ZEND_DO_FCALL_BY_NAME:
+                                       case ZEND_DO_FCALL:
+                                               if (!zend_jit_do_fcall(&dasm_state, opline, op_array, op_array_ssa, call_level, -1, p + 1)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_IS_EQUAL:
+                                       case ZEND_IS_NOT_EQUAL:
+                                       case ZEND_IS_SMALLER:
+                                       case ZEND_IS_SMALLER_OR_EQUAL:
+                                       case ZEND_CASE:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                                                       zend_bool exit_if_true = 0;
+                                                       const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true);
+                                                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1);
+
+                                                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                                       if (!exit_addr) {
+                                                               goto jit_failure;
+                                                       }
+                                                       smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ;
+                                               } else {
+                                                       smart_branch_opcode = 0;
+                                                       exit_addr = NULL;
+                                               }
+                                               if (!zend_jit_cmp(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(),
+                                                               op2_info, OP2_REG_ADDR(),
+                                                               RES_REG_ADDR(),
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa),
+                                                               smart_branch_opcode, -1, -1, exit_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_IS_IDENTICAL:
+                                       case ZEND_IS_NOT_IDENTICAL:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                                                       zend_bool exit_if_true = 0;
+                                                       const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true);
+                                                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1);
+
+                                                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                                       if (!exit_addr) {
+                                                               goto jit_failure;
+                                                       }
+                                                       if (opline->opcode == ZEND_IS_NOT_IDENTICAL) {
+                                                               exit_if_true = !exit_if_true;
+                                                       }
+                                                       smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ;
+                                               } else {
+                                                       smart_branch_opcode = 0;
+                                                       exit_addr = NULL;
+                                               }
+                                               if (!zend_jit_identical(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(),
+                                                               op2_info, OP2_REG_ADDR(),
+                                                               RES_REG_ADDR(),
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa),
+                                                               smart_branch_opcode, -1, -1, exit_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_DEFINED:
+                                               if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                                                       zend_bool exit_if_true = 0;
+                                                       const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true);
+                                                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1);
+
+                                                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                                       if (!exit_addr) {
+                                                               goto jit_failure;
+                                                       }
+                                                       smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ;
+                                               } else {
+                                                       smart_branch_opcode = 0;
+                                                       exit_addr = NULL;
+                                               }
+                                               if (!zend_jit_defined(&dasm_state, opline, op_array, smart_branch_opcode, -1, -1, exit_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_TYPE_CHECK:
+                                               if (opline->extended_value == MAY_BE_RESOURCE) {
+                                                       // TODO: support for is_resource() ???
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               USE_OP1_TRACE_TYPE();
+                                               if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                                                       zend_bool exit_if_true = 0;
+                                                       const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true);
+                                                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1);
+
+                                                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                                       if (!exit_addr) {
+                                                               goto jit_failure;
+                                                       }
+                                                       smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ;
+                                               } else {
+                                                       smart_branch_opcode = 0;
+                                                       exit_addr = NULL;
+                                               }
+                                               if (!zend_jit_type_check(&dasm_state, opline, op_array, op1_info, smart_branch_opcode, -1, -1, exit_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_RETURN:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               if (opline->op1_type == IS_CONST) {
+                                                       res_type = Z_TYPE_P(RT_CONSTANT(opline, opline->op1));
+                                               } else if (op1_type != IS_UNKNOWN) {
+                                                       res_type = op1_type;
+                                               }
+                                               if (op_array->type == ZEND_EVAL_CODE
+                                                // TODO: support for top-level code
+                                                || !op_array->function_name
+                                                // TODO: support for IS_UNDEF ???
+                                                || (op1_info & MAY_BE_UNDEF)) {
+                                                       if (!zend_jit_tail_handler(&dasm_state, opline)) {
+                                                               goto jit_failure;
+                                                       }
+                                               } else {
+                                                       int j;
+
+                                                       if (!zend_jit_return(&dasm_state, opline, op_array,
+                                                                       op1_info, OP1_REG_ADDR())) {
+                                                               goto jit_failure;
+                                                       }
+                                                   if (!zend_jit_leave_frame(&dasm_state)) {
+                                                               goto jit_failure;
+                                                   }
+                                                       for (j = 0 ; j < op_array->last_var; j++) {
+                                                               // TODO: get info from trace ???
+                                                               uint32_t info = zend_ssa_cv_info(opline, op_array, op_array_ssa, j);
+                                                               zend_uchar type = stack[j];
+
+                                                               info = zend_jit_trace_type_to_info_ex(type, info);
+                                                               if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
+                                                                       if (!zend_jit_free_cv(&dasm_state, opline, op_array, info, j)) {
+                                                                               goto jit_failure;
+                                                                       }
+                                                               }
+                                                       }
+                                                       if (!zend_jit_leave_func(&dasm_state, opline, op_array, p + 1)) {
+                                                               goto jit_failure;
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_BOOL:
+                                       case ZEND_BOOL_NOT:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               if (!zend_jit_bool_jmpznz(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(), RES_REG_ADDR(),
+                                                               -1, -1,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa),
+                                                               opline->opcode, NULL)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_JMPZ:
+                                       case ZEND_JMPNZ:
+                                               if (/*opline > op_array->opcodes + ssa->cfg.blocks[b].start && ??? */
+                                                   opline->op1_type == IS_TMP_VAR &&
+                                                   ((opline-1)->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                                                       /* smart branch */
+                                                       break;
+                                               }
+                                               /* break missing intentionally */
+                                       case ZEND_JMPZNZ:
+                                       case ZEND_JMPZ_EX:
+                                       case ZEND_JMPNZ_EX:
+                                               if (opline->result_type == IS_UNDEF) {
+                                                       res_addr = 0;
+                                               } else {
+                                                       res_addr = RES_REG_ADDR();
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               if ((p+1)->op == ZEND_JIT_TRACE_VM || (p+1)->op == ZEND_JIT_TRACE_END) {
+                                                       const zend_op *exit_opline = NULL;
+                                                       uint32_t exit_point;
+
+                                                       if ((p+1)->opline == OP_JMP_ADDR(opline, opline->op2)) {
+                                                               /* taken branch */
+                                                               if (opline->opcode == ZEND_JMPNZ_EX) {
+                                                                       smart_branch_opcode = ZEND_JMPZ_EX;
+                                                               } else if (opline->opcode == ZEND_JMPZ_EX) {
+                                                                       smart_branch_opcode = ZEND_JMPNZ_EX;
+                                                               } else if (opline->opcode == ZEND_JMPNZ) {
+                                                                       smart_branch_opcode = ZEND_JMPZ;
+                                                               } else {
+                                                                       smart_branch_opcode = ZEND_JMPNZ;
+                                                               }
+                                                               exit_opline = (opline->opcode == ZEND_JMPZNZ) ?
+                                                                       ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) :
+                                                                       opline + 1;
+                                                       } else if (opline->opcode == ZEND_JMPZNZ) {
+                                                               ZEND_ASSERT((p+1)->opline == ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value));
+                                                               smart_branch_opcode = ZEND_JMPZ;
+                                                               exit_opline = OP_JMP_ADDR(opline, opline->op2);
+                                                       } else if ((p+1)->opline == opline + 1) {
+                                                               /* not taken branch */
+                                                               smart_branch_opcode = opline->opcode;
+                                                               exit_opline = OP_JMP_ADDR(opline, opline->op2);
+                                                       } else {
+                                                               ZEND_ASSERT(0);
+                                                       }
+                                                       exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p+1);
+                                                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                                       if (!exit_addr) {
+                                                               goto jit_failure;
+                                                       }
+                                               } else  {
+                                                       ZEND_ASSERT(0);
+                                               }
+                                               if (!zend_jit_bool_jmpznz(&dasm_state, opline, op_array,
+                                                               op1_info, OP1_REG_ADDR(), res_addr,
+                                                               -1, -1,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa),
+                                                               smart_branch_opcode, exit_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_FETCH_DIM_R:
+                                       case ZEND_FETCH_DIM_IS:
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               res_info = RES_INFO_EX();
+                                               if (!zend_jit_fetch_dim_read(&dasm_state, opline, op_array,
+                                                               op1_info, op2_info, res_info,
+                                                               (
+                                                                       (op1_info & MAY_BE_ANY) != MAY_BE_ARRAY ||
+                                                                       (op2_info & (MAY_BE_ANY - (MAY_BE_LONG|MAY_BE_STRING))) != 0 ||
+                                                                       ((op1_info & MAY_BE_UNDEF) != 0 &&
+                                                                               opline->opcode == ZEND_FETCH_DIM_R) ||
+                                                                       ((opline->op1_type & (IS_TMP_VAR|IS_VAR)) != 0 &&
+                                                                               (op1_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) != 0) ||
+                                                                       (op2_info & MAY_BE_UNDEF) != 0 ||
+                                                                       ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) != 0 &&
+                                                                               (op2_info & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) != 0)))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                               goto done;
+                                       case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+                                               if ((opline->extended_value & ZEND_ISEMPTY)) {
+                                                       // TODO: support for empty() ???
+                                                       break;
+                                               }
+                                               op1_info = OP1_INFO_EX();
+                                               CHECK_OP1_TRACE_TYPE();
+                                               op2_info = OP2_INFO_EX();
+                                               CHECK_OP2_TRACE_TYPE();
+                                               if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0) {
+                                                       zend_bool exit_if_true = 0;
+                                                       const zend_op *exit_opline = zend_jit_trace_get_exit_opline(p + 1, opline + 1, &exit_if_true);
+                                                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, p + 1);
+
+                                                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                                       if (!exit_addr) {
+                                                               goto jit_failure;
+                                                       }
+                                                       smart_branch_opcode = exit_if_true ? ZEND_JMPNZ : ZEND_JMPZ;
+                                               } else {
+                                                       smart_branch_opcode = 0;
+                                                       exit_addr = NULL;
+                                               }
+                                               if (!zend_jit_isset_isempty_dim(&dasm_state, opline, op_array,
+                                                               op1_info, op2_info,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa),
+                                                               smart_branch_opcode, -1, -1,
+                                                               exit_addr)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_FETCH_OBJ_R:
+                                       case ZEND_FETCH_OBJ_IS:
+                                               if (opline->op2_type != IS_CONST
+                                                || Z_TYPE_P(RT_CONSTANT(opline, opline->op2)) != IS_STRING
+                                                || Z_STRVAL_P(RT_CONSTANT(opline, opline->op2))[0] == '\0') {
+                                                       break;
+                                               }
+                                               ce = NULL;
+                                               if (opline->op1_type == IS_UNUSED) {
+                                                       op1_info = MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN;
+                                                       ce = op_array->scope;
+                                               } else {
+                                                       op1_info = OP1_INFO_EX();
+                                                       if (ssa->var_info && ssa->ops) {
+                                                               if (ssa_op->op1_use >= 0) {
+                                                                       zend_ssa_var_info *op1_ssa = ssa->var_info + ssa_op->op1_use;
+                                                                       if (op1_ssa->ce && !op1_ssa->is_instanceof && !op1_ssa->ce->create_object) {
+                                                                               ce = op1_ssa->ce;
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                               if (!(op1_info & MAY_BE_OBJECT)) {
+                                                       break;
+                                               }
+                                               if (!zend_jit_fetch_obj_read(&dasm_state, opline, op_array,
+                                                               op1_info, ce,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_BIND_GLOBAL:
+                                               orig_opline = opline;
+                                               orig_ssa_op = ssa_op;
+                                               while (1) {
+                                                       if (!ssa->ops || !ssa->var_info) {
+                                                               op1_info = MAY_BE_ANY|MAY_BE_REF;
+                                                       } else {
+                                                               op1_info = OP1_INFO_EX();
+                                                       }
+                                                       if (!zend_jit_bind_global(&dasm_state, opline, op_array, op1_info)) {
+                                                               goto jit_failure;
+                                                       }
+                                                       if ((opline+1)->opcode == ZEND_BIND_GLOBAL) {
+                                                               opline++;
+                                                               ssa_op++;
+                                                       } else {
+                                                               break;
+                                                       }
+                                               }
+                                               opline = orig_opline;
+                                               ssa_op = orig_ssa_op;
+                                               goto done;
+                                       case ZEND_RECV:
+                                               if (!zend_jit_recv(&dasm_state, opline, op_array)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_RECV_INIT:
+                                               orig_opline = opline;
+                                               orig_ssa_op = ssa_op;
+                                               while (1) {
+                                                       if (!zend_jit_recv_init(&dasm_state, opline, op_array,
+                                                                       (opline + 1)->opcode != ZEND_RECV_INIT,
+                                                                       zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                               goto jit_failure;
+                                                       }
+                                                       if ((opline+1)->opcode == ZEND_RECV_INIT) {
+                                                               opline++;
+                                                               ssa_op++;
+                                                       } else {
+                                                               break;
+                                                       }
+                                               }
+                                               opline = orig_opline;
+                                               ssa_op = orig_ssa_op;
+                                               goto done;
+                                       case ZEND_FREE:
+                                       case ZEND_FE_FREE:
+                                               op1_info = OP1_INFO_EX();
+                                               USE_OP1_TRACE_TYPE();
+                                               if (!zend_jit_free(&dasm_state, opline, op_array, op1_info,
+                                                               zend_may_throw_ex(opline, ssa_op, op_array, ssa))) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+                                       case ZEND_ECHO:
+                                               if (opline->op1_type != IS_CONST
+                                                || Z_TYPE_P(RT_CONSTANT(opline, opline->op1)) != IS_STRING) {
+                                                       break;
+                                               }
+                                               if (!zend_jit_echo(&dasm_state, opline, op_array)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+#if 0
+                                       case ZEND_SWITCH_LONG:
+                                       case ZEND_SWITCH_STRING:
+                                               if (!zend_jit_switch(&dasm_state, opline, op_array, op_array_ssa)) {
+                                                       goto jit_failure;
+                                               }
+                                               goto done;
+#endif
+//                                     case ZEND_INIT_NS_FCALL_BY_NAME:
+                                               // TODO: we may need a guard after INIT_NS_FCALL???
+                                       case ZEND_INIT_METHOD_CALL:
+                                       case ZEND_INIT_DYNAMIC_CALL:
+                                               if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) {
+                                                       goto jit_failure;
+                                               }
+                                               if ((p+1)->op == ZEND_JIT_TRACE_INIT_CALL) {
+                                                       if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) {
+                                                               goto jit_failure;
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_INIT_STATIC_METHOD_CALL:
+                                               if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) {
+                                                       goto jit_failure;
+                                               }
+                                               if ((opline->op1_type != IS_CONST
+                                                 || opline->op2_type != IS_CONST)
+                                                && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL) {
+                                                       if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) {
+                                                               goto jit_failure;
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_INIT_USER_CALL:
+                                               if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) {
+                                                       goto jit_failure;
+                                               }
+                                               if (opline->op2_type != IS_CONST
+                                                && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL) {
+                                                       if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) {
+                                                               goto jit_failure;
+                                                       }
+                                               }
+                                               goto done;
+                                       case ZEND_NEW:
+                                               if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) {
+                                                       goto jit_failure;
+                                               }
+                                               if (opline->op1_type != IS_CONST
+                                                && (p+1)->op == ZEND_JIT_TRACE_INIT_CALL) {
+                                                       if (!zend_jit_init_fcall_guard(&dasm_state, opline, (p+1)->func)) {
+                                                               goto jit_failure;
+                                                       }
+                                               }
+                                               goto done;
+                                       default:
+                                               break;
+                               }
+                       }
+
+                       if (opline->opcode != ZEND_NOP && opline->opcode != ZEND_JMP) {
+                               if (!zend_jit_trace_handler(&dasm_state, op_array, opline, zend_may_throw_ex(opline, ssa_op, op_array, ssa), p + 1)) {
+                                       goto jit_failure;
+                               }
+                       }
+
+done:
+#if 0
+                       // TODO: call level calculation doesn't work for traces ???
+                       switch (opline->opcode) {
+                               case ZEND_DO_FCALL:
+                               case ZEND_DO_ICALL:
+                               case ZEND_DO_UCALL:
+                               case ZEND_DO_FCALL_BY_NAME:
+                                       call_level--;
+                       }
+#endif
+
+                       /* Keep information about known types on abstract stack */
+                       if (ssa_op->result_def >= 0) {
+                               zend_uchar type = IS_UNKNOWN;
+
+                               if ((opline->result_type & (IS_SMART_BRANCH_JMPZ|IS_SMART_BRANCH_JMPNZ)) != 0
+                                || send_result) {
+                                       /* we didn't set result variable */
+                                       type = IS_UNKNOWN;
+                               } else if (!(ssa->var_info[ssa_op->result_def].type & MAY_BE_GUARD)
+                                && has_concrete_type(ssa->var_info[ssa_op->result_def].type)) {
+                                       type = concrete_type(ssa->var_info[ssa_op->result_def].type);
+                               } else if (opline->opcode == ZEND_QM_ASSIGN) {
+                                       if (opline->op1_type != IS_CONST) {
+                                               /* copy */
+                                               type = STACK_VAR_TYPE(opline->op1.var);
+                                       }
+                               } else if (opline->opcode == ZEND_ASSIGN) {
+                                       if (opline->op2_type != IS_CONST) {
+                                               /* copy */
+                                               type = STACK_VAR_TYPE(opline->op2.var);
+                                       }
+                               } else if (opline->opcode == ZEND_POST_INC
+                        || opline->opcode == ZEND_POST_DEC) {
+                                       /* copy */
+                                       type = STACK_VAR_TYPE(opline->op1.var);
+                               }
+                               SET_RES_STACK_VAR_TYPE(type);
+                               if (type != IS_UNKNOWN) {
+                                       ssa->var_info[ssa_op->result_def].type &= ~MAY_BE_GUARD;
+                               }
+                       }
+                       if (ssa_op->op1_def >= 0) {
+                               zend_uchar type = IS_UNKNOWN;
+
+                               if (!(ssa->var_info[ssa_op->op1_def].type & MAY_BE_GUARD)
+                                && has_concrete_type(ssa->var_info[ssa_op->op1_def].type)) {
+                                       type = concrete_type(ssa->var_info[ssa_op->op1_def].type);
+                               } else if (opline->opcode == ZEND_ASSIGN) {
+                                       if (!(OP1_INFO_EX() & MAY_BE_REF)
+                                        || STACK_VAR_TYPE(opline->op1.var) != IS_UNKNOWN) {
+                                               if (opline->op2_type != IS_CONST) {
+                                                       /* copy */
+                                                       type = STACK_VAR_TYPE(opline->op2.var);
+                                               }
+                                       }
+                               } else if (opline->opcode == ZEND_SEND_VAR
+                                || opline->opcode == ZEND_CAST
+                                || opline->opcode == ZEND_QM_ASSIGN
+                                || opline->opcode == ZEND_JMP_SET
+                                || opline->opcode == ZEND_COALESCE
+                                || opline->opcode == ZEND_FE_RESET_R) {
+                                       /* keep old value */
+                                       type = STACK_VAR_TYPE(opline->op1.var);
+                               }
+                               SET_OP1_STACK_VAR_TYPE(type);
+                               if (type != IS_UNKNOWN) {
+                                       ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD;
+                               }
+                       }
+                       if (ssa_op->op2_def >= 0) {
+                               zend_uchar type = IS_UNKNOWN;
+
+                               if (!(ssa->var_info[ssa_op->op2_def].type & MAY_BE_GUARD)
+                                && has_concrete_type(ssa->var_info[ssa_op->op2_def].type)) {
+                                       type = concrete_type(ssa->var_info[ssa_op->op2_def].type);
+                               } else if (opline->opcode == ZEND_ASSIGN) {
+                                       /* keep old value */
+                                       type = STACK_VAR_TYPE(opline->op2.var);
+                               }
+                               SET_OP2_STACK_VAR_TYPE(type);
+                               if (type != IS_UNKNOWN) {
+                                       ssa->var_info[ssa_op->op2_def].type &= ~MAY_BE_GUARD;
+                               }
+                       }
+
+                       switch (opline->opcode) {
+                               case ZEND_ASSIGN_DIM:
+                               case ZEND_ASSIGN_OBJ:
+                               case ZEND_ASSIGN_STATIC_PROP:
+                               case ZEND_ASSIGN_DIM_OP:
+                               case ZEND_ASSIGN_OBJ_OP:
+                               case ZEND_ASSIGN_STATIC_PROP_OP:
+                               case ZEND_ASSIGN_OBJ_REF:
+                               case ZEND_ASSIGN_STATIC_PROP_REF:
+                                       /* OP_DATA */
+                                       ssa_op++;
+                                       opline++;
+                                       if (ssa_op->op1_def >= 0) {
+                                               zend_uchar type = IS_UNKNOWN;
+
+                                               if (!(ssa->var_info[ssa_op->op1_def].type & MAY_BE_GUARD)
+                                                && has_concrete_type(ssa->var_info[ssa_op->op1_def].type)) {
+                                                       type = concrete_type(ssa->var_info[ssa_op->op1_def].type);
+                                               } else if ((opline-1)->opcode == ZEND_ASSIGN_DIM
+                                                || (opline-1)->opcode == ZEND_ASSIGN_OBJ
+                                                || (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP) {
+                                                       /* keep old value */
+                                                       type = STACK_VAR_TYPE(opline->op1.var);
+                                               }
+                                               SET_OP1_STACK_VAR_TYPE(type);
+                                               if (type != IS_UNKNOWN) {
+                                                       ssa->var_info[ssa_op->op1_def].type &= ~MAY_BE_GUARD;
+                                               }
+                                       }
+                                       ssa_op++;
+                                       break;
+                               case ZEND_RECV_INIT:
+                                   ssa_op++;
+                                       opline++;
+                                       while (opline->opcode == ZEND_RECV_INIT) {
+                                               if (ssa_op->result_def >= 0) {
+                                                       zend_uchar type = IS_UNKNOWN;
+
+                                                       if (!(ssa->var_info[ssa_op->result_def].type & MAY_BE_GUARD)
+                                                        && has_concrete_type(ssa->var_info[ssa_op->result_def].type)) {
+                                                               type = concrete_type(ssa->var_info[ssa_op->result_def].type);
+                                                       }
+                                                       SET_RES_STACK_VAR_TYPE(type);
+                                               }
+                                               ssa_op++;
+                                               opline++;
+                                       }
+                                       break;
+                               case ZEND_BIND_GLOBAL:
+                                       ssa_op++;
+                                       opline++;
+                                       while (opline->opcode == ZEND_BIND_GLOBAL) {
+                                               if (ssa_op->op1_def >= 0) {
+                                                       zend_uchar type = IS_UNKNOWN;
+
+                                                       if (!(ssa->var_info[ssa_op->op1_def].type & MAY_BE_GUARD)
+                                                        && has_concrete_type(ssa->var_info[ssa_op->op1_def].type)) {
+                                                               type = concrete_type(ssa->var_info[ssa_op->op1_def].type);
+                                                       }
+                                                       SET_OP1_STACK_VAR_TYPE(type);
+                                               }
+                                               ssa_op++;
+                                               opline++;
+                                       }
+                                       break;
+                               default:
+                                       ssa_op += zend_jit_trace_op_len(opline);
+                                       break;
+                       }
+
+                       if (send_result) {
+                               ssa_op++;
+                               send_result = 0;
+                       }
+
+               } else if (p->op == ZEND_JIT_TRACE_ENTER) {
+                       op_array = (zend_op_array*)p->op_array;
+                       jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                       op_array_ssa = &jit_extension->func_info.ssa;
+                       call = frame->call;
+                       if (!call) {
+                               /* Trace missed INIT_FCALL opcode */
+                               call = top;
+                               call->call = NULL;
+                               call->prev = NULL;
+                               call->func = (const zend_function*)op_array;
+                               call->nested = 0;
+                               call->num_args = -1; // TODO: should be possible to get the real number ???
+                               top = zend_jit_trace_call_frame(top, op_array);
+                               i = 0;
+                               while (i < p->op_array->num_args) {
+                                       /* Initialize abstract stack using SSA */
+                                       if (!(ssa->var_info[p->first_ssa_var + i].type & MAY_BE_GUARD)
+                                        && has_concrete_type(ssa->var_info[p->first_ssa_var + i].type)) {
+                                               call->stack[i] = concrete_type(ssa->var_info[p->first_ssa_var + i].type);
+                                       } else {
+                                               call->stack[i] = IS_UNKNOWN;
+                                       }
+                                       i++;
+                               }
+                               while (i < p->op_array->last_var) {
+                                       call->stack[i] = IS_UNDEF;
+                                       i++;
+                               }
+                               while (i < p->op_array->last_var + p->op_array->T) {
+                                       call->stack[i] = IS_UNKNOWN;
+                                       i++;
+                               }
+                       } else {
+                               ZEND_ASSERT(&call->func->op_array == op_array);
+                       }
+                       frame->call = call->prev;
+                       call->prev = frame;
+                       call->return_value_used = p->return_value_used;
+                       JIT_G(current_frame) = frame = call;
+                       stack = frame->stack;
+                       zend_jit_set_opline(&dasm_state, (p+1)->opline);
+               } else if (p->op == ZEND_JIT_TRACE_BACK) {
+                       op_array = (zend_op_array*)p->op_array;
+                       jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                       op_array_ssa = &jit_extension->func_info.ssa;
+                       top = frame;
+                       if (frame->prev) {
+                               frame = frame->prev;
+                               stack = frame->stack;
+                               ZEND_ASSERT(&frame->func->op_array == op_array);
+                       } else {
+                               frame = zend_jit_trace_ret_frame(frame, op_array);
+                               frame->call = NULL;
+                               frame->prev = NULL;
+                               frame->func = (const zend_function*)op_array;
+                               frame->return_value_used = -1;
+                               frame->nested = 0;
+                               frame->num_args = -1;
+                               stack = frame->stack;
+                               for (i = 0; i < op_array->last_var + op_array->T; i++) {
+                                       /* Initialize abstract stack using SSA */
+                                       if (!(ssa->var_info[p->first_ssa_var + i].type & MAY_BE_GUARD)
+                                        && has_concrete_type(ssa->var_info[p->first_ssa_var + i].type)) {
+                                               stack[i] = concrete_type(ssa->var_info[p->first_ssa_var + i].type);
+                                       } else {
+                                               stack[i] = IS_UNKNOWN;
+                                       }
+                               }
+                       }
+                       JIT_G(current_frame) = frame;
+                       if (res_type != IS_UNKNOWN
+                        && (p+1)->op == ZEND_JIT_TRACE_VM) {
+                               const zend_op *opline = (p+1)->opline - 1;
+                               if (opline->result_type != IS_UNUSED) {
+                                   SET_RES_STACK_VAR_TYPE(res_type);
+                               }
+                       }
+                       res_type = IS_UNKNOWN;
+               } else if (p->op == ZEND_JIT_TRACE_END) {
+                       break;
+               } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) {
+                       call = top;
+                       call->call = NULL;
+                       call->prev = frame->call;
+                       call->func = p->func;
+                       call->nested = 1;
+                       call->num_args = find_call_num_args(p-1);
+                       frame->call = call;
+                       top = zend_jit_trace_call_frame(top, p->op_array);
+                       if (p->func->type == ZEND_USER_FUNCTION) {
+                               i = 0;
+                               while (i < p->op_array->num_args) {
+                                       /* Types of arguments are going to be stored in abstract stack when proseccin SEV onstruction */
+                                       call->stack[i] = IS_UNKNOWN;
+                                       i++;
+                               }
+                               while (i < p->op_array->last_var) {
+                                       call->stack[i] = IS_UNDEF;
+                                       i++;
+                               }
+                               while (i < p->op_array->last_var + p->op_array->T) {
+                                       call->stack[i] = IS_UNKNOWN;
+                                       i++;
+                               }
+                       }
+                       if (p->fake) {
+                               if (!zend_jit_init_fcall_guard(&dasm_state, NULL, p->func)) {
+                                       goto jit_failure;
+                               }
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) {
+                       call = frame->call;
+                       if (call) {
+                               top = call;
+                               frame->call = call->prev;
+                       }
+               } else {
+                       ZEND_ASSERT(0);
+               }
+       }
+
+       ZEND_ASSERT(p->op == ZEND_JIT_TRACE_END);
+
+       t = &zend_jit_traces[ZEND_JIT_TRACE_NUM];
+
+       if (p->stop == ZEND_JIT_TRACE_STOP_LOOP
+        || p->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL
+        || p->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) {
+               if (p->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+                       if (!zend_jit_set_valid_ip(&dasm_state, p->opline)) {
+                               goto jit_failure;
+                       }
+               }
+               t->link = ZEND_JIT_TRACE_NUM;
+               zend_jit_jmp(&dasm_state, 0); /* jump back to start of the trace loop */
+       } else if (p->stop == ZEND_JIT_TRACE_STOP_LINK) {
+               if (!zend_jit_set_valid_ip(&dasm_state, p->opline)) {
+                       goto jit_failure;
+               }
+               t->link = zend_jit_find_trace(p->opline->handler);
+               zend_jit_trace_link_to_root(&dasm_state, p->opline->handler);
+       } else if (p->stop == ZEND_JIT_TRACE_STOP_RETURN) {
+               zend_jit_trace_return(&dasm_state);
+       } else {
+               // TODO: not implemented ???
+               ZEND_ASSERT(0 && p->stop);
+       }
+
+       if (ZEND_JIT_EXIT_COUNTERS + t->exit_count >= ZEND_JIT_TRACE_MAX_EXIT_COUNTERS) {
+               goto jit_failure;
+       }
+
+       handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, ZSTR_VAL(name), 1);
+
+jit_failure:
+       dasm_free(&dasm_state);
+
+       if (name) {
+               zend_string_release(name);
+       }
+
+       /* Clenup used op_arrays */
+       while (num_op_arrays > 0) {
+               op_array = op_arrays[--num_op_arrays];
+               jit_extension =
+                       (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+
+               memset(&jit_extension->func_info, 0, sizeof(jit_extension->func_info));
+               jit_extension->func_info.num_args = -1;
+               jit_extension->func_info.return_value_used = -1;
+       }
+
+       zend_arena_release(&CG(arena), checkpoint);
+
+       JIT_G(current_frame) = NULL;
+
+       return handler;
+}
+
+static int zend_jit_trace_exit_needs_deoptimization(uint32_t trace_num, uint32_t exit_num)
+{
+       const zend_op *opline = zend_jit_traces[trace_num].exit_info[exit_num].opline;
+
+       opline = (const zend_op*)((uintptr_t)opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED));
+       if (opline) {
+               return 1;
+       }
+
+       return 0;
+}
+
+static const void *zend_jit_trace_exit_to_vm(uint32_t trace_num, uint32_t exit_num)
+{
+       const void *handler = NULL;
+       dasm_State* dasm_state = NULL;
+       void *checkpoint;
+       char name[32];
+       const zend_op *opline;
+
+       if (!zend_jit_trace_exit_needs_deoptimization(trace_num, exit_num)) {
+               return dasm_labels[zend_lbtrace_escape];
+       }
+
+       checkpoint = zend_arena_checkpoint(CG(arena));;
+
+       sprintf(name, "ESCAPE-%d-%d", trace_num, exit_num);
+
+       dasm_init(&dasm_state, DASM_MAXSECTION);
+       dasm_setupglobal(&dasm_state, dasm_labels, zend_lb_MAX);
+       dasm_setup(&dasm_state, dasm_actions);
+       dasm_growpc(&dasm_state, 0);
+
+       zend_jit_align_func(&dasm_state);
+
+       // TODO: Generate deoptimization code ???
+
+       opline = zend_jit_traces[trace_num].exit_info[exit_num].opline;
+       opline = (const zend_op*)((uintptr_t)opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED));
+       if (opline) {
+               zend_jit_set_ip(&dasm_state, opline);
+       }
+
+       zend_jit_trace_return(&dasm_state);
+
+       handler = dasm_link_and_encode(&dasm_state, NULL, NULL, NULL, NULL, name, 1);
+
+       dasm_free(&dasm_state);
+
+       zend_arena_release(&CG(arena), checkpoint);
+
+       return handler;
+}
+
+static zend_jit_trace_stop zend_jit_compile_root_trace(zend_jit_trace_rec *trace_buffer, const zend_op *opline, size_t offset)
+{
+       zend_jit_trace_stop ret;
+       const void *handler;
+       zend_jit_trace_info *t;
+       zend_jit_trace_exit_info exit_info[ZEND_JIT_TRACE_MAX_EXITS];
+
+       zend_shared_alloc_lock();
+
+       /* Checks under lock */
+       if ((ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & ZEND_JIT_TRACE_JITED)) {
+               ret = ZEND_JIT_TRACE_STOP_ALREADY_DONE;
+       } else if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) {
+               ret = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES;
+       } else {
+               SHM_UNPROTECT();
+               zend_jit_unprotect();
+
+               t = &zend_jit_traces[ZEND_JIT_TRACE_NUM];
+
+               t->id = ZEND_JIT_TRACE_NUM;
+               t->root = ZEND_JIT_TRACE_NUM;
+               t->parent = 0;
+               t->link = 0;
+               t->exit_count = 0;
+               t->child_count = 0;
+               t->stack_map_size = 0;
+               t->exit_info = exit_info;
+               t->stack_map = NULL;
+
+               handler = zend_jit_trace(trace_buffer, 0, 0);
+
+               if (handler) {
+                       zend_jit_trace_exit_info *shared_exit_info = NULL;
+
+                       t->exit_info = NULL;
+                       if (t->exit_count) {
+                               /* reallocate exit_info into shared memory */
+                               shared_exit_info = (zend_jit_trace_exit_info*)zend_shared_alloc(
+                                       sizeof(zend_jit_trace_exit_info) * t->exit_count);
+
+                               if (!shared_exit_info) {
+                                   if (t->stack_map) {
+                                               efree(t->stack_map);
+                                               t->stack_map = NULL;
+                                       }
+                                       ret = ZEND_JIT_TRACE_STOP_NO_SHM;
+                                       goto exit;
+                               }
+                               memcpy(shared_exit_info, exit_info,
+                                       sizeof(zend_jit_trace_exit_info) * t->exit_count);
+                               t->exit_info = shared_exit_info;
+                       }
+
+                   if (t->stack_map_size) {
+                               zend_jit_trace_stack *shared_stack_map = (zend_jit_trace_stack*)zend_shared_alloc(t->stack_map_size * sizeof(zend_jit_trace_stack));
+                               if (!shared_stack_map) {
+                                       ret = ZEND_JIT_TRACE_STOP_NO_SHM;
+                                       goto exit;
+                               }
+                               memcpy(shared_stack_map, t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack));
+                               efree(t->stack_map);
+                               t->stack_map = shared_stack_map;
+                   }
+
+                       t->exit_counters = ZEND_JIT_EXIT_COUNTERS;
+                       ZEND_JIT_EXIT_COUNTERS += t->exit_count;
+
+                       ((zend_op*)opline)->handler = handler;
+
+                       ZEND_JIT_TRACE_NUM++;
+                       ZEND_OP_TRACE_INFO(opline, offset)->trace_flags |= ZEND_JIT_TRACE_JITED;
+
+                       ret = ZEND_JIT_TRACE_STOP_COMPILED;
+               } else if (t->exit_count >= ZEND_JIT_TRACE_MAX_EXITS ||
+                          ZEND_JIT_EXIT_COUNTERS + t->exit_count >= ZEND_JIT_TRACE_MAX_EXIT_COUNTERS) {
+                   if (t->stack_map) {
+                               efree(t->stack_map);
+                               t->stack_map = NULL;
+                       }
+                       ret = ZEND_JIT_TRACE_STOP_TOO_MANY_EXITS;
+               } else {
+                   if (t->stack_map) {
+                               efree(t->stack_map);
+                               t->stack_map = NULL;
+                       }
+                       ret = ZEND_JIT_TRACE_STOP_COMPILER_ERROR;
+               }
+
+exit:
+               zend_jit_protect();
+               SHM_PROTECT();
+       }
+
+       zend_shared_alloc_unlock();
+
+       return ret;
+}
+
+static void zend_jit_blacklist_root_trace(const zend_op *opline, size_t offset)
+{
+       zend_shared_alloc_lock();
+
+       if (!(ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & ZEND_JIT_TRACE_BLACKLISTED)) {
+               SHM_UNPROTECT();
+               zend_jit_unprotect();
+
+               ((zend_op*)opline)->handler =
+                       ZEND_OP_TRACE_INFO(opline, offset)->orig_handler;
+
+               ZEND_OP_TRACE_INFO(opline, offset)->trace_flags |= ZEND_JIT_TRACE_BLACKLISTED;
+
+               zend_jit_protect();
+               SHM_PROTECT();
+       }
+
+       zend_shared_alloc_unlock();
+}
+
+static zend_bool zend_jit_trace_is_bad_root(const zend_op *opline, zend_jit_trace_stop stop, size_t offset)
+{
+       const zend_op **cache_opline = JIT_G(bad_root_cache_opline);
+       uint8_t *cache_count = JIT_G(bad_root_cache_count);
+       uint8_t *cache_stop = JIT_G(bad_root_cache_stop);
+       uint32_t cache_slot = JIT_G(bad_root_slot);
+       uint32_t i;
+
+       for (i = 0; i < ZEND_JIT_TRACE_BAD_ROOT_SLOTS; i++) {
+               if (cache_opline[i] == opline) {
+                       if (cache_count[i] >= ZEND_JIT_TRACE_MAX_ROOT_FAILURES - 1) {
+                               cache_opline[i] = NULL;
+                               return 1;
+                       } else {
+#if 0
+                               if (ZEND_OP_TRACE_INFO(opline, offset)->counter) {
+                                       *ZEND_OP_TRACE_INFO(opline, offset)->counter =
+                                               random() % ZEND_JIT_TRACE_COUNTER_MAX;
+                               }
+#endif
+                               cache_count[i]++;
+                               cache_stop[i] = stop;
+                               return 0;
+                       }
+               }
+       }
+       i = cache_slot;
+       cache_opline[i] = opline;
+       cache_count[i] = 1;
+       cache_stop[i] = stop;
+       cache_slot = (i + 1) % ZEND_JIT_TRACE_BAD_ROOT_SLOTS;
+       JIT_G(bad_root_slot) = cache_slot;
+       return 0;
+}
+
+#define ZEND_JIT_TRACE_STOP_DESCRIPTION(name, description) \
+       description,
+
+static const char * zend_jit_trace_stop_description[] = {
+       ZEND_JIT_TRACE_STOP(ZEND_JIT_TRACE_STOP_DESCRIPTION)
+};
+
+static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa)
+{
+       zend_jit_trace_rec *p = trace_buffer;
+       const zend_op_array *op_array;
+       const zend_op *opline;
+       uint32_t level = 1 + trace_buffer[0].level;
+       int idx, len, i, v, vars_count, call_level;
+
+       ZEND_ASSERT(p->op == ZEND_JIT_TRACE_START);
+       op_array = p->op_array;
+       p += ZEND_JIT_TRACE_START_REC_SIZE;
+       idx = 0;
+       call_level = 0;
+
+       if (tssa && tssa->var_info) {
+               if (trace_buffer->start == ZEND_JIT_TRACE_START_ENTER) {
+                       vars_count = op_array->last_var;
+               } else {
+                       vars_count = op_array->last_var + op_array->T;
+               }
+               for (i = 0; i < vars_count; i++) {
+                       if (tssa->vars[i].use_chain >= 0 || tssa->vars[i].phi_use_chain) {
+                               fprintf(stderr, "    %*c;", level, ' ');
+                               zend_dump_ssa_var(op_array, tssa, i, 0, i, ZEND_DUMP_RC_INFERENCE);
+                               fprintf(stderr, "\n");
+                       }
+               }
+               if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP
+                || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL
+                || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) {
+                       fprintf(stderr, "LOOP:\n");
+                       if (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP) {
+                               zend_ssa_phi *p = tssa->blocks[1].phis;
+
+                               while (p) {
+                                       fprintf(stderr, "     ;");
+                                       zend_dump_ssa_var(op_array, tssa, p->ssa_var, 0, p->var, ZEND_DUMP_RC_INFERENCE);
+                                       fprintf(stderr, " = Phi(");
+                                       zend_dump_ssa_var(op_array, tssa, p->sources[0], 0, p->var, ZEND_DUMP_RC_INFERENCE);
+                                       fprintf(stderr, ", ");
+                                       zend_dump_ssa_var(op_array, tssa, p->sources[1], 0, p->var, ZEND_DUMP_RC_INFERENCE);
+                                       fprintf(stderr, ")\n");
+                                       p = p->next;
+                               }
+                       }
+               }
+       }
+
+       while (1) {
+               if (p->op == ZEND_JIT_TRACE_VM) {
+                       uint8_t op1_type, op2_type, op3_type;
+
+                       opline = p->opline;
+                       fprintf(stderr, "%04d%*c",
+                               (int)(opline - op_array->opcodes),
+                               level, ' ');
+                       zend_dump_op(op_array, NULL, opline, ZEND_DUMP_NUMERIC_OPLINES|ZEND_DUMP_RC_INFERENCE, tssa, (tssa && tssa->ops) ? tssa->ops + idx : NULL);
+
+                       op1_type = p->op1_type;
+                       op2_type = p->op2_type;
+                       op3_type = p->op3_type;
+                       if (op1_type != IS_UNKNOWN || op2_type != IS_UNKNOWN || op3_type != IS_UNKNOWN) {
+                               fprintf(stderr, " ;");
+                               if (op1_type != IS_UNKNOWN) {
+                                       const char *ref = (op1_type & IS_TRACE_REFERENCE) ? "&" : "";
+                                       if ((p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) {
+                                               p++;
+                                               fprintf(stderr, " op1(%sobject of class %s)", ref,
+                                                       ZSTR_VAL(p->ce->name));
+                                       } else {
+                                               const char *type = (op1_type == 0) ? "undef" : zend_get_type_by_const(op1_type & ~IS_TRACE_REFERENCE);
+                                               fprintf(stderr, " op1(%s%s)", ref, type);
+                                       }
+                               }
+                               if (op2_type != IS_UNKNOWN) {
+                                       const char *ref = (op2_type & IS_TRACE_REFERENCE) ? "&" : "";
+                                       if ((p+1)->op == ZEND_JIT_TRACE_OP2_TYPE) {
+                                               p++;
+                                               fprintf(stderr, " op2(%sobject of class %s)", ref,
+                                                       ZSTR_VAL(p->ce->name));
+                                       } else {
+                                               const char *type = (op2_type == 0) ? "undef" : zend_get_type_by_const(op2_type & ~IS_TRACE_REFERENCE);
+                                               fprintf(stderr, " op2(%s%s)", ref, type);
+                                       }
+                               }
+                               if (op3_type != IS_UNKNOWN) {
+                                       const char *ref = (op3_type & IS_TRACE_REFERENCE) ? "&" : "";
+                                       const char *type = (op3_type == 0) ? "undef" : zend_get_type_by_const(op3_type & ~IS_TRACE_REFERENCE);
+                                       fprintf(stderr, " op3(%s%s)", ref, type);
+                               }
+                       }
+                       fprintf(stderr, "\n");
+                       idx++;
+
+                       len = zend_jit_trace_op_len(opline);
+                       while (len > 1) {
+                               opline++;
+                               fprintf(stderr, "%04d%*c;",
+                                       (int)(opline - op_array->opcodes),
+                                       level, ' ');
+                               zend_dump_op(op_array, NULL, opline, ZEND_DUMP_NUMERIC_OPLINES|ZEND_DUMP_RC_INFERENCE, tssa, (tssa && tssa->ops) ? tssa->ops + idx : NULL);
+                               idx++;
+                               len--;
+                               fprintf(stderr, "\n");
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_ENTER) {
+                       op_array = p->op_array;
+                       fprintf(stderr, "    %*c>enter %s%s%s\n",
+                               level, ' ',
+                               op_array->scope ? ZSTR_VAL(op_array->scope->name) : "",
+                               op_array->scope ? "::" : "",
+                               op_array->function_name ?
+                                       ZSTR_VAL(op_array->function_name) :
+                                       ZSTR_VAL(op_array->filename));
+                       level++;
+                       if (tssa && tssa->var_info) {
+                               call_level++;
+                               v = p->first_ssa_var;
+                               vars_count = op_array->last_var;
+                               for (i = 0; i < vars_count; i++, v++) {
+                                       if (tssa->vars[v].use_chain >= 0 || tssa->vars[v].phi_use_chain) {
+                                               fprintf(stderr, "    %*c;", level, ' ');
+                                               zend_dump_ssa_var(op_array, tssa, v, 0, i, ZEND_DUMP_RC_INFERENCE);
+                                               fprintf(stderr, "\n");
+                                       }
+                               }
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_BACK) {
+                       op_array = p->op_array;
+                       level--;
+                       fprintf(stderr, "    %*c<back %s%s%s\n",
+                               level, ' ',
+                               op_array->scope ? ZSTR_VAL(op_array->scope->name) : "",
+                               op_array->scope ? "::" : "",
+                               op_array->function_name ?
+                                       ZSTR_VAL(op_array->function_name) :
+                                       ZSTR_VAL(op_array->filename));
+                       if (tssa && tssa->var_info) {
+                               if (call_level == 0) {
+                                       v = p->first_ssa_var;
+                                       vars_count = op_array->last_var + op_array->T;
+                                       for (i = 0; i < vars_count; i++, v++) {
+                                               if (tssa->vars[v].use_chain >= 0 || tssa->vars[v].phi_use_chain) {
+                                                       fprintf(stderr, "    %*c;", level, ' ');
+                                                       zend_dump_ssa_var(op_array, tssa, v, 0, i, ZEND_DUMP_RC_INFERENCE);
+                                                       fprintf(stderr, "\n");
+                                               }
+                                       }
+                               } else {
+                                       call_level--;
+                               }
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_INIT_CALL) {
+                       if (p->func != (zend_function*)&zend_pass_function) {
+                               fprintf(stderr, p->fake ? "    %*c>fake_init %s%s%s\n" : "    %*c>init %s%s%s\n",
+                                       level, ' ',
+                                       p->func->common.scope ? ZSTR_VAL(p->func->common.scope->name) : "",
+                                       p->func->common.scope ? "::" : "",
+                                       ZSTR_VAL(p->func->common.function_name));
+                       } else {
+                               fprintf(stderr, "    %*c>skip\n",
+                                       level, ' ');
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) {
+                       if (p->func != (zend_function*)&zend_pass_function) {
+                               fprintf(stderr, "    %*c>call %s%s%s\n",
+                                       level, ' ',
+                                       p->func->common.scope ? ZSTR_VAL(p->func->common.scope->name) : "",
+                                       p->func->common.scope ? "::" : "",
+                                       ZSTR_VAL(p->func->common.function_name));
+                       } else {
+                               fprintf(stderr, "    %*c>skip\n",
+                                       level, ' ');
+                       }
+               } else if (p->op == ZEND_JIT_TRACE_END) {
+                       break;
+               }
+               p++;
+       }
+}
+
+static zend_always_inline const char *zend_jit_trace_star_desc(uint8_t trace_flags)
+{
+       if (trace_flags & ZEND_JIT_TRACE_START_LOOP) {
+               return "loop";
+       } else if (trace_flags & ZEND_JIT_TRACE_START_ENTER) {
+               return "enter";
+       } else if (trace_flags & ZEND_JIT_TRACE_START_RETURN) {
+               return "return";
+       } else {
+               ZEND_ASSERT(0);
+               return "???";
+       }
+}
+
+int ZEND_FASTCALL zend_jit_trace_hot_root(zend_execute_data *execute_data, const zend_op *opline)
+{
+       const zend_op *orig_opline;
+       zend_jit_trace_stop stop;
+       zend_op_array *op_array;
+       zend_jit_op_array_trace_extension *jit_extension;
+       size_t offset;
+       uint32_t trace_num;
+       zend_jit_trace_rec trace_buffer[ZEND_JIT_TRACE_MAX_LENGTH];
+
+       ZEND_ASSERT(EX(func)->type == ZEND_USER_FUNCTION);
+       ZEND_ASSERT(opline >= EX(func)->op_array.opcodes &&
+               opline < EX(func)->op_array.opcodes + EX(func)->op_array.last);
+
+repeat:
+       trace_num = ZEND_JIT_TRACE_NUM;
+       orig_opline = opline;
+       op_array = &EX(func)->op_array;
+       jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+       offset = jit_extension->offset;
+
+       EX(opline) = opline;
+
+       /* Lock-free check if the root trace was already JIT-ed or blacklist-ed in another process */
+       if (ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & (ZEND_JIT_TRACE_JITED|ZEND_JIT_TRACE_BLACKLISTED)) {
+               return 0;
+       }
+
+       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_START) {
+               fprintf(stderr, "---- TRACE %d start (%s) %s() %s:%d\n",
+                       trace_num,
+                       zend_jit_trace_star_desc(ZEND_OP_TRACE_INFO(opline, offset)->trace_flags),
+                       EX(func)->op_array.function_name ?
+                               ZSTR_VAL(EX(func)->op_array.function_name) : "$main",
+                       ZSTR_VAL(EX(func)->op_array.filename),
+                       opline->lineno);
+       }
+
+       if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) {
+               stop = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES;
+               goto abort;
+       }
+
+       stop = zend_jit_trace_execute(execute_data, opline, trace_buffer,
+               ZEND_OP_TRACE_INFO(opline, offset)->trace_flags & ZEND_JIT_TRACE_START_MASK);
+
+       if (stop == ZEND_JIT_TRACE_STOP_TOPLEVEL) {
+               /* op_array may be already deallocated */
+               if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) {
+                       fprintf(stderr, "---- TRACE %d abort (%s)\n",
+                               trace_num,
+                               zend_jit_trace_stop_description[stop]);
+               }
+               goto blacklist;
+       }
+
+       if (UNEXPECTED(ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BYTECODE)) {
+               zend_jit_dump_trace(trace_buffer, NULL);
+       }
+
+       if (ZEND_JIT_TRACE_STOP_OK(stop)) {
+               if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_STOP) {
+                       if (stop == ZEND_JIT_TRACE_STOP_LINK) {
+                               uint32_t link_to = zend_jit_find_trace(EG(current_execute_data)->opline->handler);;
+                               fprintf(stderr, "---- TRACE %d stop (link to %d)\n",
+                                       trace_num,
+                                       link_to);
+                       } else {
+                               fprintf(stderr, "---- TRACE %d stop (%s)\n",
+                                       trace_num,
+                                       zend_jit_trace_stop_description[stop]);
+                       }
+               }
+               stop = zend_jit_compile_root_trace(trace_buffer, orig_opline, offset);
+               if (EXPECTED(ZEND_JIT_TRACE_STOP_DONE(stop))) {
+                       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_COMPILED) {
+                               fprintf(stderr, "---- TRACE %d %s\n",
+                                       trace_num,
+                                       zend_jit_trace_stop_description[stop]);
+                       }
+               } else {
+                       goto abort;
+               }
+       } else {
+abort:
+               if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) {
+                       fprintf(stderr, "---- TRACE %d abort (%s)\n",
+                               trace_num,
+                               zend_jit_trace_stop_description[stop]);
+               }
+blacklist:
+               if (!ZEND_JIT_TRACE_STOP_MAY_RECOVER(stop)
+                || zend_jit_trace_is_bad_root(orig_opline, stop, offset)) {
+                       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BLACKLIST) {
+                               fprintf(stderr, "---- TRACE %d blacklisted\n",
+                                       trace_num);
+                       }
+                       zend_jit_blacklist_root_trace(orig_opline, offset);
+               }
+               if (ZEND_JIT_TRACE_STOP_REPEAT(stop)) {
+                       execute_data = EG(current_execute_data);
+                       opline = EX(opline);
+                       goto repeat;
+               }
+       }
+
+       if (ZCG(accel_directives).jit_debug & (ZEND_JIT_DEBUG_TRACE_STOP|ZEND_JIT_DEBUG_TRACE_ABORT|ZEND_JIT_DEBUG_TRACE_COMPILED|ZEND_JIT_DEBUG_TRACE_BLACKLIST)) {
+               fprintf(stderr, "\n");
+       }
+
+       return (stop == ZEND_JIT_TRACE_STOP_HALT) ? -1 : 0;
+}
+
+static void zend_jit_blacklist_trace_exit(uint32_t trace_num, uint32_t exit_num)
+{
+       const void *handler;
+
+       zend_shared_alloc_lock();
+
+       if (!((uintptr_t)zend_jit_traces[trace_num].exit_info[exit_num].opline & ZEND_JIT_EXIT_BLACKLISTED)) {
+               SHM_UNPROTECT();
+               zend_jit_unprotect();
+
+               handler = zend_jit_trace_exit_to_vm(trace_num, exit_num);
+
+               if (handler) {
+                       zend_jit_link_side_trace(
+                               zend_jit_traces[trace_num].code_start,
+                               zend_jit_traces[trace_num].code_size,
+                               exit_num,
+                               handler);
+               }
+
+               zend_jit_traces[trace_num].exit_info[exit_num].opline = (const zend_op*)
+                   ((uintptr_t)zend_jit_traces[trace_num].exit_info[exit_num].opline | ZEND_JIT_EXIT_BLACKLISTED);
+
+               zend_jit_protect();
+               SHM_PROTECT();
+       }
+
+       zend_shared_alloc_unlock();
+}
+
+static zend_bool zend_jit_trace_exit_is_bad(uint32_t trace_num, uint32_t exit_num)
+{
+       uint8_t *counter = JIT_G(exit_counters) +
+               zend_jit_traces[trace_num].exit_counters + exit_num;
+
+       if (*counter + 1 >= ZEND_JIT_TRACE_HOT_SIDE_COUNT + ZEND_JIT_TRACE_MAX_SIDE_FAILURES) {
+               return 1;
+       }
+       (*counter)++;
+       return 0;
+}
+
+static zend_bool zend_jit_trace_exit_is_hot(uint32_t trace_num, uint32_t exit_num)
+{
+       uint8_t *counter = JIT_G(exit_counters) +
+               zend_jit_traces[trace_num].exit_counters + exit_num;
+
+       if (*counter + 1 >= ZEND_JIT_TRACE_HOT_SIDE_COUNT) {
+               return 1;
+       }
+       (*counter)++;
+       return 0;
+}
+
+static zend_jit_trace_stop zend_jit_compile_side_trace(zend_jit_trace_rec *trace_buffer, uint32_t parent_num, uint32_t exit_num)
+{
+       zend_jit_trace_stop ret;
+       const void *handler;
+       zend_jit_trace_info *t;
+       zend_jit_trace_exit_info exit_info[ZEND_JIT_TRACE_MAX_EXITS];
+
+       zend_shared_alloc_lock();
+
+       /* Checks under lock */
+       if (((uintptr_t)zend_jit_traces[parent_num].exit_info[exit_num].opline & ZEND_JIT_EXIT_JITED)) {
+               ret = ZEND_JIT_TRACE_STOP_ALREADY_DONE;
+       } else if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) {
+               ret = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES;
+       } else if (zend_jit_traces[zend_jit_traces[parent_num].root].child_count >= ZEND_JIT_TRACE_MAX_SIDE_TRACES) {
+               ret = ZEND_JIT_TRACE_STOP_TOO_MANY_CHILDREN;
+       } else {
+               SHM_UNPROTECT();
+               zend_jit_unprotect();
+
+               t = &zend_jit_traces[ZEND_JIT_TRACE_NUM];
+
+               t->id = ZEND_JIT_TRACE_NUM;
+               t->root = zend_jit_traces[parent_num].root;
+               t->parent = parent_num;
+               t->link = 0;
+               t->exit_count = 0;
+               t->child_count = 0;
+               t->stack_map_size = 0;
+               t->exit_info = exit_info;
+               t->stack_map = NULL;
+
+               handler = zend_jit_trace(trace_buffer, parent_num, exit_num);
+
+               if (handler) {
+                       zend_jit_trace_exit_info *shared_exit_info = NULL;
+
+                       t->exit_info = NULL;
+                       if (t->exit_count) {
+                               /* reallocate exit_info into shared memory */
+                               shared_exit_info = (zend_jit_trace_exit_info*)zend_shared_alloc(
+                                       sizeof(zend_jit_trace_exit_info) * t->exit_count);
+
+                               if (!shared_exit_info) {
+                                   if (t->stack_map) {
+                                               efree(t->stack_map);
+                                               t->stack_map = NULL;
+                                       }
+                                       ret = ZEND_JIT_TRACE_STOP_NO_SHM;
+                                       goto exit;
+                               }
+                               memcpy(shared_exit_info, exit_info,
+                                       sizeof(zend_jit_trace_exit_info) * t->exit_count);
+                               t->exit_info = shared_exit_info;
+                       }
+
+                   if (t->stack_map_size) {
+                               zend_jit_trace_stack *shared_stack_map = (zend_jit_trace_stack*)zend_shared_alloc(t->stack_map_size * sizeof(zend_jit_trace_stack));
+                               if (!shared_stack_map) {
+                                       efree(t->stack_map);
+                                       ret = ZEND_JIT_TRACE_STOP_NO_SHM;
+                                       goto exit;
+                               }
+                               memcpy(shared_stack_map, t->stack_map, t->stack_map_size * sizeof(zend_jit_trace_stack));
+                               efree(t->stack_map);
+                               t->stack_map = shared_stack_map;
+                   }
+
+                       zend_jit_link_side_trace(
+                               zend_jit_traces[parent_num].code_start,
+                               zend_jit_traces[parent_num].code_size,
+                               exit_num,
+                               handler);
+
+                       t->exit_counters = ZEND_JIT_EXIT_COUNTERS;
+                       ZEND_JIT_EXIT_COUNTERS += t->exit_count;
+
+                       zend_jit_traces[zend_jit_traces[parent_num].root].child_count++;
+                       ZEND_JIT_TRACE_NUM++;
+                       zend_jit_traces[parent_num].exit_info[exit_num].opline = (const zend_op*)
+                           ((uintptr_t)zend_jit_traces[parent_num].exit_info[exit_num].opline | ZEND_JIT_EXIT_JITED);
+
+                       ret = ZEND_JIT_TRACE_STOP_COMPILED;
+               } else if (t->exit_count >= ZEND_JIT_TRACE_MAX_EXITS ||
+                          ZEND_JIT_EXIT_COUNTERS + t->exit_count >= ZEND_JIT_TRACE_MAX_EXIT_COUNTERS) {
+                   if (t->stack_map) {
+                               efree(t->stack_map);
+                               t->stack_map = NULL;
+                       }
+                       ret = ZEND_JIT_TRACE_STOP_TOO_MANY_EXITS;
+               } else {
+                   if (t->stack_map) {
+                               efree(t->stack_map);
+                               t->stack_map = NULL;
+                       }
+                       ret = ZEND_JIT_TRACE_STOP_COMPILER_ERROR;
+               }
+
+exit:
+               zend_jit_protect();
+               SHM_PROTECT();
+       }
+
+       zend_shared_alloc_unlock();
+
+       return ret;
+}
+
+int ZEND_FASTCALL zend_jit_trace_hot_side(zend_execute_data *execute_data, uint32_t parent_num, uint32_t exit_num)
+{
+       zend_jit_trace_stop stop;
+       uint32_t trace_num;
+       zend_jit_trace_rec trace_buffer[ZEND_JIT_TRACE_MAX_LENGTH];
+
+       trace_num = ZEND_JIT_TRACE_NUM;
+
+       /* Lock-free check if the side trace was already JIT-ed or blacklist-ed in another process */
+       if ((uintptr_t)zend_jit_traces[parent_num].exit_info[exit_num].opline & (ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)) {
+               return 0;
+       }
+
+       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_START) {
+               fprintf(stderr, "---- TRACE %d start (side trace %d/%d) %s() %s:%d\n",
+                       trace_num, parent_num, exit_num,
+                       EX(func)->op_array.function_name ?
+                               ZSTR_VAL(EX(func)->op_array.function_name) : "$main",
+                       ZSTR_VAL(EX(func)->op_array.filename),
+                       EX(opline)->lineno);
+       }
+
+       if (ZEND_JIT_TRACE_NUM >= ZEND_JIT_TRACE_MAX_TRACES) {
+               stop = ZEND_JIT_TRACE_STOP_TOO_MANY_TRACES;
+               goto abort;
+       }
+
+       if (zend_jit_traces[zend_jit_traces[parent_num].root].child_count >= ZEND_JIT_TRACE_MAX_SIDE_TRACES) {
+               stop = ZEND_JIT_TRACE_STOP_TOO_MANY_CHILDREN;
+               goto abort;
+       }
+
+       stop = zend_jit_trace_execute(execute_data, EX(opline), trace_buffer, ZEND_JIT_TRACE_START_SIDE);
+
+       if (stop == ZEND_JIT_TRACE_STOP_TOPLEVEL) {
+               /* op_array may be already deallocated */
+               if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) {
+                       fprintf(stderr, "---- TRACE %d abort (%s)\n",
+                               trace_num,
+                               zend_jit_trace_stop_description[stop]);
+               }
+               goto blacklist;
+       }
+
+       if (UNEXPECTED(ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BYTECODE)) {
+               zend_jit_dump_trace(trace_buffer, NULL);
+       }
+
+       if (ZEND_JIT_TRACE_STOP_OK(stop)) {
+               if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_STOP) {
+                       if (stop == ZEND_JIT_TRACE_STOP_LINK) {
+                               uint32_t link_to = zend_jit_find_trace(EG(current_execute_data)->opline->handler);;
+                               fprintf(stderr, "---- TRACE %d stop (link to %d)\n",
+                                       trace_num,
+                                       link_to);
+                       } else {
+                               fprintf(stderr, "---- TRACE %d stop (%s)\n",
+                                       trace_num,
+                                       zend_jit_trace_stop_description[stop]);
+                       }
+               }
+               if (EXPECTED(stop != ZEND_JIT_TRACE_STOP_LOOP)) {
+                       stop = zend_jit_compile_side_trace(trace_buffer, parent_num, exit_num);
+               } else {
+                       const zend_op_array *op_array = trace_buffer[0].op_array;
+                       zend_jit_op_array_trace_extension *jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+                       const zend_op *opline = trace_buffer[1].opline;
+
+                       stop = zend_jit_compile_root_trace(trace_buffer, opline, jit_extension->offset);
+               }
+               if (EXPECTED(ZEND_JIT_TRACE_STOP_DONE(stop))) {
+                       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_COMPILED) {
+                               fprintf(stderr, "---- TRACE %d %s\n",
+                                       trace_num,
+                                       zend_jit_trace_stop_description[stop]);
+                       }
+               } else {
+                       goto abort;
+               }
+       } else {
+abort:
+               if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_ABORT) {
+                       fprintf(stderr, "---- TRACE %d abort (%s)\n",
+                               trace_num,
+                               zend_jit_trace_stop_description[stop]);
+               }
+blacklist:
+               if (!ZEND_JIT_TRACE_STOP_MAY_RECOVER(stop)
+                || zend_jit_trace_exit_is_bad(parent_num, exit_num)) {
+                       zend_jit_blacklist_trace_exit(parent_num, exit_num);
+                       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_BLACKLIST) {
+                               fprintf(stderr, "---- EXIT %d/%d blacklisted\n",
+                                       parent_num, exit_num);
+                       }
+               }
+       }
+
+       if (ZCG(accel_directives).jit_debug & (ZEND_JIT_DEBUG_TRACE_STOP|ZEND_JIT_DEBUG_TRACE_ABORT|ZEND_JIT_DEBUG_TRACE_COMPILED|ZEND_JIT_DEBUG_TRACE_BLACKLIST)) {
+               fprintf(stderr, "\n");
+       }
+
+       return (stop == ZEND_JIT_TRACE_STOP_HALT) ? -1 : 0;
+}
+
+int ZEND_FASTCALL zend_jit_trace_exit(uint32_t trace_num, uint32_t exit_num)
+{
+       zend_execute_data *execute_data = EG(current_execute_data);
+       const zend_op *opline;
+       zend_jit_trace_info *t = &zend_jit_traces[trace_num];
+
+       // TODO: Deoptimizatoion of VM stack state ???
+
+       /* Lock-free check if the side trace was already JIT-ed or blacklist-ed in another process */
+       // TODO: We may remoive this, becaus of the same check in zend_jit_trace_hot_side() ???
+       opline = t->exit_info[exit_num].opline;
+       if ((uintptr_t)opline & (ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED)) {
+               opline = (const zend_op*)((uintptr_t)opline & ~(ZEND_JIT_EXIT_JITED|ZEND_JIT_EXIT_BLACKLISTED));
+               if (opline) {
+                       /* Set VM opline to continue interpretation */
+                       EX(opline) = opline;
+               }
+               return 0;
+       }
+
+       if (opline) {
+               /* Set VM opline to continue interpretation */
+               EX(opline) = opline;
+       }
+
+       ZEND_ASSERT(EX(func)->type == ZEND_USER_FUNCTION);
+       ZEND_ASSERT(EX(opline) >= EX(func)->op_array.opcodes &&
+               EX(opline) < EX(func)->op_array.opcodes + EX(func)->op_array.last);
+
+       if (ZCG(accel_directives).jit_debug & ZEND_JIT_DEBUG_TRACE_EXIT) {
+               fprintf(stderr, "     TRACE %d exit %d %s() %s:%d\n",
+                       trace_num,
+                       exit_num,
+                       EX(func)->op_array.function_name ?
+                               ZSTR_VAL(EX(func)->op_array.function_name) : "$main",
+                       ZSTR_VAL(EX(func)->op_array.filename),
+                       EX(opline)->lineno);
+       }
+
+       if (zend_jit_trace_exit_is_hot(trace_num, exit_num)) {
+               return zend_jit_trace_hot_side(execute_data, trace_num, exit_num);
+       }
+
+       return 0;
+}
+
+static zend_always_inline uint8_t zend_jit_trace_supported(const zend_op *opline)
+{
+       switch (opline->opcode) {
+               case ZEND_CATCH:
+               case ZEND_FAST_CALL:
+               case ZEND_FAST_RET:
+               case ZEND_GENERATOR_CREATE:
+               case ZEND_GENERATOR_RETURN:
+               case ZEND_EXIT:
+               case ZEND_YIELD:
+               case ZEND_YIELD_FROM:
+               case ZEND_INCLUDE_OR_EVAL:
+                       return ZEND_JIT_TRACE_UNSUPPORTED;
+               default:
+                       return ZEND_JIT_TRACE_SUPPORTED;
+       }
+}
+
+static int zend_jit_setup_hot_trace_counters(zend_op_array *op_array)
+{
+       zend_op *opline;
+       zend_jit_op_array_trace_extension *jit_extension;
+       zend_cfg cfg;
+       uint32_t i;
+
+       ZEND_ASSERT(zend_jit_func_counter_handler != NULL);
+       ZEND_ASSERT(zend_jit_ret_counter_handler != NULL);
+       ZEND_ASSERT(zend_jit_loop_counter_handler != NULL);
+       ZEND_ASSERT(sizeof(zend_op_trace_info) == sizeof(zend_op));
+
+       if (zend_jit_build_cfg(op_array, &cfg) != SUCCESS) {
+               return FAILURE;
+       }
+
+       jit_extension = (zend_jit_op_array_trace_extension*)zend_shared_alloc(sizeof(zend_jit_op_array_trace_extension) + (op_array->last - 1) * sizeof(zend_op_trace_info));
+       memset(&jit_extension->func_info, 0, sizeof(jit_extension->func_info));
+       jit_extension->func_info.num_args = -1;
+       jit_extension->func_info.return_value_used = -1;
+       jit_extension->offset = (char*)jit_extension->trace_info - (char*)op_array->opcodes;
+       for (i = 0; i < op_array->last; i++) {
+               jit_extension->trace_info[i].orig_handler = op_array->opcodes[i].handler;
+               jit_extension->trace_info[i].call_handler = zend_get_opcode_handler_func(&op_array->opcodes[i]);
+               jit_extension->trace_info[i].counter = NULL;
+               jit_extension->trace_info[i].trace_flags =
+                       zend_jit_trace_supported(&op_array->opcodes[i]);
+       }
+       ZEND_SET_FUNC_INFO(op_array, (void*)jit_extension);
+
+       opline = op_array->opcodes;
+       if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+               while (opline->opcode == ZEND_RECV || opline->opcode == ZEND_RECV_INIT) {
+                       opline++;
+               }
+       }
+
+       if (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) {
+               /* function entry */
+               opline->handler = (const void*)zend_jit_func_counter_handler;
+               ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter =
+                       &zend_jit_hot_counters[ZEND_JIT_COUNTER_NUM];
+               ZEND_JIT_COUNTER_NUM = (ZEND_JIT_COUNTER_NUM + 1) % ZEND_HOT_COUNTERS_COUNT;
+               ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags |=
+                       ZEND_JIT_TRACE_START_ENTER;
+       }
+
+       for (i = 0; i < cfg.blocks_count; i++) {
+               if (cfg.blocks[i].flags & ZEND_BB_REACHABLE) {
+                       if (cfg.blocks[i].flags & ZEND_BB_ENTRY) {
+                               /* continuation after return from function call */
+                               opline = op_array->opcodes + cfg.blocks[i].start;
+                               if (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) {
+                                       opline->handler = (const void*)zend_jit_ret_counter_handler;
+                                       if (!ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter) {
+                                               ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter =
+                                                       &zend_jit_hot_counters[ZEND_JIT_COUNTER_NUM];
+                                               ZEND_JIT_COUNTER_NUM = (ZEND_JIT_COUNTER_NUM + 1) % ZEND_HOT_COUNTERS_COUNT;
+                                       }
+                                       ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags |=
+                                               ZEND_JIT_TRACE_START_RETURN;
+                               }
+                       }
+                       if (cfg.blocks[i].flags & ZEND_BB_LOOP_HEADER) {
+                               /* loop header */
+                               opline = op_array->opcodes + cfg.blocks[i].start;
+                               if (!(ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_UNSUPPORTED)) {
+                                       opline->handler = (const void*)zend_jit_loop_counter_handler;
+                                       if (!ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter) {
+                                               ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->counter =
+                                                       &zend_jit_hot_counters[ZEND_JIT_COUNTER_NUM];
+                                               ZEND_JIT_COUNTER_NUM = (ZEND_JIT_COUNTER_NUM + 1) % ZEND_HOT_COUNTERS_COUNT;
+                                       }
+                                       ZEND_OP_TRACE_INFO(opline, jit_extension->offset)->trace_flags |=
+                                               ZEND_JIT_TRACE_START_LOOP;
+                               }
+                       }
+               }
+       }
+
+       return SUCCESS;
+}
+
+static void zend_jit_trace_init_caches(void)
+{
+       memset(JIT_G(bad_root_cache_opline), 0, sizeof(JIT_G(bad_root_cache_opline)));
+       memset(JIT_G(bad_root_cache_count), 0, sizeof(JIT_G(bad_root_cache_count)));
+       memset(JIT_G(bad_root_cache_stop), 0, sizeof(JIT_G(bad_root_cache_count)));
+       JIT_G(bad_root_slot) = 0;
+
+       memset(JIT_G(exit_counters), 0, sizeof(JIT_G(exit_counters)));
+}
+
+static void zend_jit_trace_reset_caches(void)
+{
+}
index 9862fd1b421ee187d536cb66a173400ba00360d0..9f23beda1742b219ef98dee1ed43bbab10cd6256 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <ZendAccelerator.h>
 #include "Optimizer/zend_func_info.h"
+#include "Optimizer/zend_call_graph.h"
 #include "zend_jit.h"
 #include "zend_jit_internal.h"
 
@@ -98,6 +99,15 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(uint32_t ca
 #endif
 }
 
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_func_helper(uint32_t call_info EXECUTE_DATA_DC)
+{
+       if (call_info & ZEND_CALL_TOP) {
+               ZEND_OPCODE_TAIL_CALL_EX(zend_jit_leave_top_func_helper, call_info);
+       } else {
+               ZEND_OPCODE_TAIL_CALL_EX(zend_jit_leave_nested_func_helper, call_info);
+       }
+}
+
 void ZEND_FASTCALL zend_jit_copy_extra_args_helper(EXECUTE_DATA_D)
 {
        zend_op_array *op_array = &EX(func)->op_array;
@@ -182,8 +192,8 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_profile_helper(ZEND_OPCODE_HANDLE
 
 ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_func_counter_helper(ZEND_OPCODE_HANDLER_ARGS)
 {
-       zend_jit_op_array_extension *jit_extension =
-               (zend_jit_op_array_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
+       zend_jit_op_array_hot_extension *jit_extension =
+               (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
 #ifndef HAVE_GCC_GLOBAL_REGS
        const zend_op *opline = EX(opline);
 #endif
@@ -191,6 +201,7 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_func_counter_helper(ZEND_OPCODE_H
        *(jit_extension->counter) -= ZEND_JIT_HOT_FUNC_COST;
 
        if (UNEXPECTED(*(jit_extension->counter) <= 0)) {
+               *(jit_extension->counter) = ZEND_JIT_HOT_COUNTER_INIT;
                zend_jit_hot_func(execute_data, opline);
                ZEND_OPCODE_RETURN();
        } else {
@@ -201,8 +212,8 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_func_counter_helper(ZEND_OPCODE_H
 
 ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_loop_counter_helper(ZEND_OPCODE_HANDLER_ARGS)
 {
-       zend_jit_op_array_extension *jit_extension =
-               (zend_jit_op_array_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
+       zend_jit_op_array_hot_extension *jit_extension =
+               (zend_jit_op_array_hot_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
 #ifndef HAVE_GCC_GLOBAL_REGS
        const zend_op *opline = EX(opline);
 #endif
@@ -210,6 +221,7 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_loop_counter_helper(ZEND_OPCODE_H
        *(jit_extension->counter) -= ZEND_JIT_HOT_LOOP_COST;
 
        if (UNEXPECTED(*(jit_extension->counter) <= 0)) {
+               *(jit_extension->counter) = ZEND_JIT_HOT_COUNTER_INIT;
                zend_jit_hot_func(execute_data, opline);
                ZEND_OPCODE_RETURN();
        } else {
@@ -266,3 +278,607 @@ int ZEND_FASTCALL zend_jit_check_constant(const zval *key)
 {
        return _zend_quick_get_constant(key, 0, 1);
 }
+
+static zend_always_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_trace_counter_helper(uint32_t cost ZEND_OPCODE_HANDLER_ARGS_DC)
+{
+       zend_jit_op_array_trace_extension *jit_extension =
+               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
+       size_t offset = jit_extension->offset;
+#ifndef HAVE_GCC_GLOBAL_REGS
+       const zend_op *opline = EX(opline);
+#endif
+
+       *(ZEND_OP_TRACE_INFO(opline, offset)->counter) -= ZEND_JIT_TRACE_FUNC_COST;
+
+       if (UNEXPECTED(*(ZEND_OP_TRACE_INFO(opline, offset)->counter) <= 0)) {
+               *(ZEND_OP_TRACE_INFO(opline, offset)->counter) = ZEND_JIT_TRACE_COUNTER_INIT;
+               if (UNEXPECTED(zend_jit_trace_hot_root(execute_data, opline) < 0)) {
+#ifndef HAVE_GCC_GLOBAL_REGS
+                       return -1;
+#endif
+               }
+#ifdef HAVE_GCC_GLOBAL_REGS
+               execute_data = EG(current_execute_data);
+               opline = EX(opline);
+               return;
+#else
+               return 1;
+#endif
+       } else {
+               zend_vm_opcode_handler_t handler = (zend_vm_opcode_handler_t)ZEND_OP_TRACE_INFO(opline, offset)->orig_handler;
+               ZEND_OPCODE_TAIL_CALL(handler);
+       }
+}
+
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_func_trace_helper(ZEND_OPCODE_HANDLER_ARGS)
+{
+       ZEND_OPCODE_TAIL_CALL_EX(zend_jit_trace_counter_helper, ZEND_JIT_TRACE_FUNC_COST);
+}
+
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_ret_trace_helper(ZEND_OPCODE_HANDLER_ARGS)
+{
+       ZEND_OPCODE_TAIL_CALL_EX(zend_jit_trace_counter_helper, ZEND_JIT_TRACE_RET_COST);
+}
+
+ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_loop_trace_helper(ZEND_OPCODE_HANDLER_ARGS)
+{
+       ZEND_OPCODE_TAIL_CALL_EX(zend_jit_trace_counter_helper, ZEND_JIT_TRACE_LOOP_COST);
+}
+
+#define TRACE_RECORD(_op, _ptr) \
+       trace_buffer[idx].op = _op; \
+       trace_buffer[idx].ptr = _ptr; \
+       idx++; \
+       if (idx >= ZEND_JIT_TRACE_MAX_LENGTH - 1) { \
+               stop = ZEND_JIT_TRACE_STOP_TOO_LONG; \
+               break; \
+       }
+
+#define TRACE_RECORD_VM(_op, _ptr, _op1_type, _op2_type, _op3_type) \
+       trace_buffer[idx].op = _op; \
+       trace_buffer[idx].op1_type = _op1_type; \
+       trace_buffer[idx].op2_type = _op2_type; \
+       trace_buffer[idx].op3_type = _op3_type; \
+       trace_buffer[idx].ptr = _ptr; \
+       idx++; \
+       if (idx >= ZEND_JIT_TRACE_MAX_LENGTH - 1) { \
+               stop = ZEND_JIT_TRACE_STOP_TOO_LONG; \
+               break; \
+       }
+
+#define TRACE_RECORD_ENTER(_op, _return_value_used, _ptr) \
+       trace_buffer[idx].op = _op; \
+       trace_buffer[idx].return_value_used = _return_value_used; \
+       trace_buffer[idx].ptr = _ptr; \
+       idx++; \
+       if (idx >= ZEND_JIT_TRACE_MAX_LENGTH - 1) { \
+               stop = ZEND_JIT_TRACE_STOP_TOO_LONG; \
+               break; \
+       }
+
+#define TRACE_RECORD_INIT(_op, _fake, _ptr) \
+       trace_buffer[idx].op = _op; \
+       trace_buffer[idx].fake = _fake; \
+       trace_buffer[idx].ptr = _ptr; \
+       idx++; \
+       if (idx >= ZEND_JIT_TRACE_MAX_LENGTH - 1) { \
+               stop = ZEND_JIT_TRACE_STOP_TOO_LONG; \
+               break; \
+       }
+
+#define TRACE_RECORD_BACK(_op, _recursive, _ptr) \
+       trace_buffer[idx].op = _op; \
+       trace_buffer[idx].recursive = _recursive; \
+       trace_buffer[idx].ptr = _ptr; \
+       idx++; \
+       if (idx >= ZEND_JIT_TRACE_MAX_LENGTH - 1) { \
+               stop = ZEND_JIT_TRACE_STOP_TOO_LONG; \
+               break; \
+       }
+
+#define TRACE_RECORD_TYPE(_op, _ptr) \
+       trace_buffer[idx].op = _op; \
+       trace_buffer[idx].ptr = _ptr; \
+       idx++; \
+       if (idx >= ZEND_JIT_TRACE_MAX_LENGTH - 1) { \
+               stop = ZEND_JIT_TRACE_STOP_TOO_LONG; \
+               break; \
+       }
+
+#define TRACE_START(_op, _start, _ptr) \
+       trace_buffer[0].op = _op; \
+       trace_buffer[0].start = _start; \
+       trace_buffer[0].level = 0; \
+       trace_buffer[0].ptr = _ptr; \
+       idx = ZEND_JIT_TRACE_START_REC_SIZE;
+
+#define TRACE_END(_op, _stop, _ptr) \
+       trace_buffer[idx].op   = _op; \
+       trace_buffer[idx].start = trace_buffer[idx].start; \
+       trace_buffer[idx].stop = trace_buffer[0].stop = _stop; \
+       trace_buffer[idx].level = trace_buffer[0].level = ret_level ? ret_level + 1 : 0; \
+       trace_buffer[idx].ptr  = _ptr;
+
+#ifndef ZEND_JIT_RECORD_RECURSIVE_RETURN
+# define ZEND_JIT_RECORD_RECURSIVE_RETURN 1
+#endif
+
+static int zend_jit_trace_recursive_call_count(const zend_op_array *op_array, const zend_op_array **unrolled_calls, int ret_level, int level)
+{
+       int i;
+       int count = 0;
+
+       for (i = ret_level; i < level; i++) {
+               count += (unrolled_calls[i] == op_array);
+       }
+       return count;
+}
+
+static int zend_jit_trace_recursive_ret_count(const zend_op_array *op_array, const zend_op_array **unrolled_calls, int ret_level)
+{
+       int i;
+       int count = 0;
+
+       for (i = 0; i < ret_level; i++) {
+               count += (unrolled_calls[i] == op_array);
+       }
+       return count;
+}
+
+static int zend_jit_trace_has_recursive_ret(zend_execute_data *ex, const zend_op_array *orig_op_array, const zend_op *orig_opline, int ret_level)
+{
+       while (ex != NULL && ret_level < ZEND_JIT_TRACE_MAX_RET_DEPTH) {
+               if (&ex->func->op_array == orig_op_array && ex->opline + 1 == orig_opline) {
+                       return 1;
+               }
+               ex = ex->prev_execute_data;
+               ret_level++;
+       }
+       return 0;
+}
+
+static int zend_jit_trace_bad_inner_loop(const zend_op *opline)
+{
+       const zend_op **cache_opline = JIT_G(bad_root_cache_opline);
+       uint8_t *cache_count = JIT_G(bad_root_cache_count);
+       uint8_t *cache_stop = JIT_G(bad_root_cache_stop);
+       uint32_t i;
+
+       for (i = 0; i < ZEND_JIT_TRACE_BAD_ROOT_SLOTS; i++) {
+               if (cache_opline[i] == opline) {
+                       if ((cache_stop[i] == ZEND_JIT_TRACE_STOP_INNER_LOOP
+                         || cache_stop[i] == ZEND_JIT_TRACE_STOP_LOOP_EXIT)
+                        && cache_count[i] > ZEND_JIT_TRACE_MAX_ROOT_FAILURES / 2) {
+                               return 1;
+                       }
+                       break;
+               }
+       }
+       return 0;
+}
+
+static int zend_jit_trace_bad_compiled_loop(const zend_op *opline)
+{
+       const zend_op **cache_opline = JIT_G(bad_root_cache_opline);
+       uint8_t *cache_count = JIT_G(bad_root_cache_count);
+       uint8_t *cache_stop = JIT_G(bad_root_cache_stop);
+       uint32_t i;
+
+       for (i = 0; i < ZEND_JIT_TRACE_BAD_ROOT_SLOTS; i++) {
+               if (cache_opline[i] == opline) {
+                       if (cache_stop[i] == ZEND_JIT_TRACE_STOP_COMPILED_LOOP
+                        && cache_count[i] >= ZEND_JIT_TRACE_MAX_ROOT_FAILURES - 1) {
+                               return 1;
+                       }
+                       break;
+               }
+       }
+       return 0;
+}
+
+static int zend_jit_trace_bad_loop_exit(const zend_op *opline)
+{
+       const zend_op **cache_opline = JIT_G(bad_root_cache_opline);
+       uint8_t *cache_count = JIT_G(bad_root_cache_count);
+       uint8_t *cache_stop = JIT_G(bad_root_cache_stop);
+       uint32_t i;
+
+       for (i = 0; i < ZEND_JIT_TRACE_BAD_ROOT_SLOTS; i++) {
+               if (cache_opline[i] == opline) {
+                       if (cache_stop[i] == ZEND_JIT_TRACE_STOP_LOOP_EXIT
+                        && cache_count[i] >= ZEND_JIT_TRACE_MAX_ROOT_FAILURES - 1) {
+                               return 1;
+                       }
+                       break;
+               }
+       }
+       return 0;
+}
+
+static int zend_jit_trace_record_fake_init_call(zend_execute_data *call, zend_jit_trace_rec *trace_buffer, int idx)
+{
+       zend_jit_trace_stop stop ZEND_ATTRIBUTE_UNUSED = ZEND_JIT_TRACE_STOP_ERROR;
+
+       do {
+               if (call->prev_execute_data) {
+                       idx = zend_jit_trace_record_fake_init_call(call->prev_execute_data, trace_buffer, idx);
+               }
+               TRACE_RECORD_INIT(ZEND_JIT_TRACE_INIT_CALL, 1, call->func);
+       } while (0);
+       return idx;
+}
+
+/*
+ *  Trace Linking Rules
+ *  ===================
+ *
+ *                                          flags
+ *          +----------+----------+----------++----------+----------+----------+
+ *          |                                ||              JIT               |
+ *          +----------+----------+----------++----------+----------+----------+
+ *   start  |   LOOP   |  ENTER   |  RETURN  ||   LOOP   |  ENTER   |  RETURN  |
+ * +========+==========+==========+==========++==========+==========+==========+
+ * | LOOP   |   loop   |          | loop-ret || COMPILED |   LINK   |   LINK   |
+ * +--------+----------+----------+----------++----------+----------+----------+
+ * | ENTER  |INNER_LOOP| rec-call |  return  ||   LINK   |   LINK   |   LINK   |
+ * +--------+----------+----------+----------++----------+----------+----------+
+ * | RETURN |INNER_LOOP|          |  rec-ret ||   LINK   |          |   LINK   |
+ * +--------+----------+----------+----------++----------+----------+----------+
+ * | SIDE   |  unroll  |          |  return  ||   LINK   |   LINK   |   LINK   |
+ * +--------+----------+----------+----------++----------+----------+----------+
+ *
+ * loop:       LOOP if "cycle" and level == 0, otherwise INNER_LOOP
+ * INNER_LOOP: abort recording and start new one (wit for loop)
+ * COMPILED:   abort recording (wait while side exit creates outer loop)
+ * unroll:     continue recording while unroll limit reached
+ * rec-call:   RECURSIVE_CALL if "cycle" and level > N, otherwise continue
+ * loop-ret:   LOOP_EXIT if level == 0, otherwise continue (wait for loop)
+ * return:     RETURN if level == 0
+ * rec_ret:    RECURSIVE_RET if "cycle" and ret_level > N, otherwise continue
+ *
+ */
+
+zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, const zend_op *op, zend_jit_trace_rec *trace_buffer, uint8_t start)
+{
+#ifdef HAVE_GCC_GLOBAL_REGS
+       zend_execute_data *save_execute_data = execute_data;
+       const zend_op *save_opline = opline;
+#endif
+       const zend_op *orig_opline;
+       zend_jit_trace_stop stop = ZEND_JIT_TRACE_STOP_ERROR;
+       int level = 0;
+       int ret_level = 0;
+       zend_vm_opcode_handler_t handler;
+       zend_jit_op_array_trace_extension *jit_extension;
+       size_t offset;
+       int idx, count;
+       uint8_t  trace_flags, op1_type, op2_type, op3_type;
+       int backtrack_recursion = -1;
+       int backtrack_ret_recursion = -1;
+       int backtrack_ret_recursion_level = 0;
+       int loop_unroll_limit = 0;
+       const zend_op_array *unrolled_calls[ZEND_JIT_TRACE_MAX_CALL_DEPTH + ZEND_JIT_TRACE_MAX_RET_DEPTH];
+#if ZEND_JIT_DETECT_UNROLLED_LOOPS
+       uint32_t unrolled_loops[ZEND_JIT_TRACE_MAX_UNROLL_LOOPS];
+#endif
+       zend_bool is_toplevel;
+#ifdef HAVE_GCC_GLOBAL_REGS
+       zend_execute_data *prev_execute_data = ex;
+
+       execute_data = ex;
+       opline = EX(opline) = op;
+#else
+       int rc;
+       zend_execute_data *execute_data = ex;
+       const zend_op *opline = EX(opline);
+#endif
+       zend_execute_data *prev_call = EX(call);
+
+       orig_opline = opline;
+
+       jit_extension =
+               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
+       offset = jit_extension->offset;
+
+       TRACE_START(ZEND_JIT_TRACE_START, start, &EX(func)->op_array);
+       ((zend_jit_trace_start_rec*)trace_buffer)->opline = opline;
+       is_toplevel = EX(func)->op_array.function_name == NULL;
+
+
+       if (prev_call) {
+               idx = zend_jit_trace_record_fake_init_call(prev_call, trace_buffer, idx);
+       }
+
+       while (1) {
+               if (UNEXPECTED(opline->opcode == ZEND_HANDLE_EXCEPTION)) {
+                       /* Abort trace because of exception */
+                       stop = ZEND_JIT_TRACE_STOP_EXCEPTION;
+                       break;
+               }
+
+               op1_type = op2_type = op3_type = IS_UNKNOWN;
+               if ((opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV))
+                &&     (opline->opcode != ZEND_ROPE_ADD && opline->opcode != ZEND_ROPE_END)) {
+                       zval *zv = EX_VAR(opline->op1.var);
+                       op1_type = Z_TYPE_P(zv);
+                       if (op1_type == IS_INDIRECT) {
+                               zv = Z_INDIRECT_P(zv);
+                               op1_type = Z_TYPE_P(zv);
+                       }
+                       if (op1_type == IS_REFERENCE) {
+                               op1_type = Z_TYPE_P(Z_REFVAL_P(zv)) | IS_TRACE_REFERENCE;
+                       }
+               }
+               if (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+                       zval *zv = EX_VAR(opline->op2.var);
+                       op2_type = Z_TYPE_P(zv);
+                       if (op2_type == IS_INDIRECT) {
+                               zv = Z_INDIRECT_P(zv);
+                               op2_type = Z_TYPE_P(zv);
+                       }
+                       if (op2_type == IS_REFERENCE) {
+                               op2_type = Z_TYPE_P(Z_REFVAL_P(zv)) | IS_TRACE_REFERENCE;
+                       }
+               }
+               if (opline->opcode == ZEND_ASSIGN_DIM ||
+                       opline->opcode == ZEND_ASSIGN_OBJ ||
+                       opline->opcode == ZEND_ASSIGN_STATIC_PROP ||
+                       opline->opcode == ZEND_ASSIGN_DIM_OP ||
+                       opline->opcode == ZEND_ASSIGN_OBJ_OP ||
+                       opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP ||
+                       opline->opcode == ZEND_ASSIGN_OBJ_REF ||
+                       opline->opcode == ZEND_ASSIGN_STATIC_PROP_REF) {
+                       if ((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+                               zval *zv = EX_VAR((opline+1)->op1.var);
+                               op3_type = Z_TYPE_P(zv);
+                               if (op3_type == IS_INDIRECT) {
+                                       zv = Z_INDIRECT_P(zv);
+                                       op3_type = Z_TYPE_P(zv);
+                               }
+                               if (op3_type == IS_REFERENCE) {
+                                       op3_type = Z_TYPE_P(Z_REFVAL_P(zv)) | IS_TRACE_REFERENCE;
+                               }
+                       }
+               }
+
+               TRACE_RECORD_VM(ZEND_JIT_TRACE_VM, opline, op1_type, op2_type, op3_type);
+
+               if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+                       zval *var = EX_VAR(opline->op1.var);
+                       uint8_t type = Z_TYPE_P(var);
+
+                       if (type == IS_OBJECT) {
+                               TRACE_RECORD_TYPE(ZEND_JIT_TRACE_OP1_TYPE, Z_OBJCE_P(var));
+                       }
+               }
+
+               if (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+                       zval *var = EX_VAR(opline->op2.var);
+                       uint8_t type = Z_TYPE_P(var);
+
+                       if (type == IS_OBJECT) {
+                               TRACE_RECORD_TYPE(ZEND_JIT_TRACE_OP2_TYPE, Z_OBJCE_P(var));
+                       }
+               }
+
+               switch (opline->opcode) {
+                       case ZEND_DO_FCALL:
+                       case ZEND_DO_ICALL:
+                       case ZEND_DO_UCALL:
+                       case ZEND_DO_FCALL_BY_NAME:
+                               if (EX(call)->func->type == ZEND_INTERNAL_FUNCTION) {
+                                       TRACE_RECORD(ZEND_JIT_TRACE_DO_ICALL, EX(call)->func);
+                               }
+                               break;
+                       default:
+                               break;
+               }
+
+               handler = (zend_vm_opcode_handler_t)ZEND_OP_TRACE_INFO(opline, offset)->call_handler;
+#ifdef HAVE_GCC_GLOBAL_REGS
+               handler();
+               if (UNEXPECTED(opline == zend_jit_halt_op)) {
+                       stop = ZEND_JIT_TRACE_STOP_HALT;
+                       break;
+               }
+               if (UNEXPECTED(execute_data != prev_execute_data)) {
+                       if (execute_data->prev_execute_data == prev_execute_data) {
+#else
+               rc = handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
+               if (rc != 0) {
+                       if (rc < 0) {
+                               stop = ZEND_JIT_TRACE_STOP_HALT;
+                               break;
+                       }
+                       execute_data = EG(current_execute_data);
+                       opline = EX(opline);
+                       if (rc == 1) {
+#endif
+                               /* Enter into function */
+                               if (level > ZEND_JIT_TRACE_MAX_CALL_DEPTH) {
+                                       stop = ZEND_JIT_TRACE_STOP_TOO_DEEP;
+                                       break;
+                               }
+
+                               if (EX(func)->op_array.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
+                                       /* TODO: Can we continue recording ??? */
+                                       stop = ZEND_JIT_TRACE_STOP_TRAMPOLINE;
+                                       break;
+                               }
+
+                               TRACE_RECORD_ENTER(ZEND_JIT_TRACE_ENTER, EX(return_value) != NULL, &EX(func)->op_array);
+
+                               count = zend_jit_trace_recursive_call_count(&EX(func)->op_array, unrolled_calls, ret_level, level);
+
+                               if (opline == orig_opline) {
+                                       if (count + 1 >= ZEND_JIT_TRACE_MAX_RECURSION) {
+                                               stop = ZEND_JIT_TRACE_STOP_RECURSIVE_CALL;
+                                               break;
+                                       }
+                                       backtrack_recursion = idx;
+                               } else if (count >= ZEND_JIT_TRACE_MAX_RECURSION) {
+                                       stop = ZEND_JIT_TRACE_STOP_DEEP_RECURSION;
+                                       break;
+                               }
+
+                               unrolled_calls[ret_level + level] = &EX(func)->op_array;
+                               level++;
+                       } else {
+                               /* Return from function */
+                               if (level == 0) {
+                                       if (is_toplevel) {
+                                               stop = ZEND_JIT_TRACE_STOP_TOPLEVEL;
+                                               break;
+#if ZEND_JIT_RECORD_RECURSIVE_RETURN
+                                       } else if (start == ZEND_JIT_TRACE_START_RETURN
+                                               && execute_data->prev_execute_data
+                                               && execute_data->prev_execute_data->func
+                                               && execute_data->prev_execute_data->func->type == ZEND_USER_FUNCTION
+                                               && zend_jit_trace_has_recursive_ret(execute_data, trace_buffer[0].op_array, orig_opline, ret_level)) {
+                                               if (ret_level > ZEND_JIT_TRACE_MAX_RET_DEPTH) {
+                                                       stop = ZEND_JIT_TRACE_STOP_TOO_DEEP_RET;
+                                                       break;
+                                               }
+                                               TRACE_RECORD_BACK(ZEND_JIT_TRACE_BACK, 1, &EX(func)->op_array);
+                                               count = zend_jit_trace_recursive_ret_count(&EX(func)->op_array, unrolled_calls, ret_level);
+                                               if (opline == orig_opline) {
+                                                       if (count + 1 >= ZEND_JIT_TRACE_MAX_RECURSION) {
+                                                               stop = ZEND_JIT_TRACE_STOP_RECURSIVE_RET;
+                                                               break;
+                                                       }
+                                                       backtrack_ret_recursion = idx;
+                                                       backtrack_ret_recursion_level = ret_level;
+                                               } else if (count >= ZEND_JIT_TRACE_MAX_RECURSION) {
+                                                       stop = ZEND_JIT_TRACE_STOP_DEEP_RECURSION;
+                                                       break;
+                                               }
+
+                                               unrolled_calls[ret_level] = &EX(func)->op_array;
+                                               ret_level++;
+                                               is_toplevel = EX(func)->op_array.function_name == NULL;
+#endif
+                                       } else if (start & ZEND_JIT_TRACE_START_LOOP
+                                        && !zend_jit_trace_bad_loop_exit(orig_opline)) {
+                                               /* Fail to try close the loop.
+                                                  If this doesn't work terminate it. */
+                                               stop = ZEND_JIT_TRACE_STOP_LOOP_EXIT;
+                                               break;
+                                       } else {
+                                               stop = ZEND_JIT_TRACE_STOP_RETURN;
+                                               break;
+                                       }
+                               } else {
+                                       level--;
+                                       TRACE_RECORD_BACK(ZEND_JIT_TRACE_BACK, 0, &EX(func)->op_array);
+                               }
+                       }
+#ifdef HAVE_GCC_GLOBAL_REGS
+                       prev_execute_data = execute_data;
+#endif
+                       jit_extension =
+                               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(&EX(func)->op_array);
+                       if (UNEXPECTED(!jit_extension)) {
+                               stop = ZEND_JIT_TRACE_STOP_BAD_FUNC;
+                               break;
+                       }
+                       offset = jit_extension->offset;
+               }
+               if (EX(call) != prev_call) {
+                       if (trace_buffer[idx-1].op != ZEND_JIT_TRACE_BACK
+                        && EX(call)
+                        && EX(call)->prev_execute_data == prev_call) {
+                               if (EX(call)->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
+                                       /* TODO: Can we continue recording ??? */
+                                       stop = ZEND_JIT_TRACE_STOP_TRAMPOLINE;
+                                       break;
+                               }
+                               TRACE_RECORD_INIT(ZEND_JIT_TRACE_INIT_CALL, 0, EX(call)->func);
+                       }
+                       prev_call = EX(call);
+               }
+
+#ifndef HAVE_GCC_GLOBAL_REGS
+               opline = EX(opline);
+#endif
+
+               trace_flags = ZEND_OP_TRACE_INFO(opline, offset)->trace_flags;
+               if (trace_flags) {
+                       if (trace_flags & ZEND_JIT_TRACE_JITED) {
+                               if (trace_flags & ZEND_JIT_TRACE_START_LOOP) {
+                                       if ((start & ZEND_JIT_TRACE_START_LOOP) != 0
+                                        && level + ret_level == 0
+                                        && !zend_jit_trace_bad_compiled_loop(orig_opline)) {
+                                               /* Fail to try close outer loop throgh side exit.
+                                                  If this doesn't work just link. */
+                                               stop = ZEND_JIT_TRACE_STOP_COMPILED_LOOP;
+                                               break;
+                                       } else {
+                                               stop = ZEND_JIT_TRACE_STOP_LINK;
+                                               break;
+                                       }
+                               } else if (trace_flags & ZEND_JIT_TRACE_START_ENTER) {
+                                       if (start != ZEND_JIT_TRACE_START_RETURN) {
+                                               // TODO: We may try to inline function ???
+                                               stop = ZEND_JIT_TRACE_STOP_LINK;
+                                               break;
+                                       }
+                               } else {
+                                       stop = ZEND_JIT_TRACE_STOP_LINK;
+                                       break;
+                               }
+                       } else if (trace_flags & ZEND_JIT_TRACE_BLACKLISTED) {
+                               stop = ZEND_JIT_TRACE_STOP_BLACK_LIST;
+                               break;
+                       } else if (trace_flags & ZEND_JIT_TRACE_START_LOOP) {
+                               if (start != ZEND_JIT_TRACE_START_SIDE) {
+                                       if (opline == orig_opline && level + ret_level == 0) {
+                                               stop = ZEND_JIT_TRACE_STOP_LOOP;
+                                               break;
+                                       }
+                                       /* Fail to try creating a trace for inner loop first.
+                                          If this doesn't work try unroling loop. */
+                                       if (!zend_jit_trace_bad_inner_loop(opline)) {
+                                               stop = ZEND_JIT_TRACE_STOP_INNER_LOOP;
+                                               break;
+                                       }
+                               }
+                               if (loop_unroll_limit < ZEND_JIT_TRACE_MAX_UNROLL_LOOPS) {
+                                       loop_unroll_limit++;
+                               } else {
+                                       stop = ZEND_JIT_TRACE_STOP_LOOP_UNROLL;
+                                       break;
+                               }
+                       } else if (trace_flags & ZEND_JIT_TRACE_UNSUPPORTED) {
+                               TRACE_RECORD(ZEND_JIT_TRACE_VM, opline);
+                               stop = ZEND_JIT_TRACE_STOP_NOT_SUPPORTED;
+                               break;
+                       }
+               }
+       }
+
+       if (!ZEND_JIT_TRACE_STOP_OK(stop)) {
+               if (backtrack_recursion > 0) {
+                       idx = backtrack_recursion;
+                       stop = ZEND_JIT_TRACE_STOP_RECURSIVE_CALL;
+               } else if (backtrack_ret_recursion > 0) {
+                       idx = backtrack_ret_recursion;
+                       ret_level = backtrack_ret_recursion_level;
+                       stop = ZEND_JIT_TRACE_STOP_RECURSIVE_RET;
+               }
+       }
+
+       TRACE_END(ZEND_JIT_TRACE_END, stop, opline);
+
+#ifdef HAVE_GCC_GLOBAL_REGS
+       if (stop != ZEND_JIT_TRACE_STOP_HALT) {
+               EX(opline) = opline;
+       }
+#endif
+
+#ifdef HAVE_GCC_GLOBAL_REGS
+       execute_data = save_execute_data;
+       opline = save_opline;
+#endif
+
+       return stop;
+}
index 1ba4a61b6eaa4497cec6636b114d830d01ad3309..de5138d00a28ae1e803d5a20c8eb2021ada7f877 100644 (file)
@@ -91,6 +91,8 @@
 
 |.define HYBRID_SPAD, 16     // padding for stack alignment
 
+#define DASM_ALIGNMENT 16
+
 /* According to x86 and x86_64 ABI, CPU stack has to be 16 byte aligned to
  * guarantee proper alignment of 128-bit SSE data allocated on stack.
  * With broken alignment any execution of SSE code, including calls to
 #include "Zend/zend_cpuinfo.h"
 #include "jit/zend_jit_x86.h"
 
+#ifdef HAVE_VALGRIND
+# include <valgrind/valgrind.h>
+#endif
+
 /* The generated code may contain tautological comparisons, ignore them. */
 #if defined(__clang__)
 # pragma clang diagnostic push
@@ -903,10 +909,10 @@ static void* dasm_labels[zend_lb_MAX];
 ||     }
 ||     if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
 ||             if (dst_def_info == MAY_BE_DOUBLE) {
-||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != MAY_BE_DOUBLE) {
+||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
 |                              SET_ZVAL_TYPE_INFO dst_addr, IS_DOUBLE
 ||                     }
-||             } else if (((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1<<Z_TYPE_P(zv))) || (dst_info & (MAY_BE_STRING|MAY_BE_ARRAY)) != 0) {
+||             } else if (((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (1<<Z_TYPE_P(zv))) || (dst_info & (MAY_BE_STRING|MAY_BE_ARRAY)) != 0) {
 |                      SET_ZVAL_TYPE_INFO dst_addr, Z_TYPE_INFO_P(zv)
 ||             }
 ||     }
@@ -993,10 +999,10 @@ static void* dasm_labels[zend_lb_MAX];
 ||     }
 ||     if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
 ||             if (dst_def_info == MAY_BE_DOUBLE) {
-||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != MAY_BE_DOUBLE) {
+||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
 |                              SET_ZVAL_TYPE_INFO dst_addr, IS_DOUBLE
 ||                     }
-||             } else if (((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (1<<Z_TYPE_P(zv))) || (dst_info & (MAY_BE_STRING|MAY_BE_ARRAY)) != 0) {
+||             } else if (((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (1<<Z_TYPE_P(zv))) || (dst_info & (MAY_BE_STRING|MAY_BE_ARRAY)) != 0) {
 |                      SET_ZVAL_TYPE_INFO dst_addr, Z_TYPE_INFO_P(zv)
 ||             }
 ||     }
@@ -1057,7 +1063,7 @@ static void* dasm_labels[zend_lb_MAX];
 ||     if ((src_info & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE)) &&
 ||     has_concrete_type(src_info & MAY_BE_ANY)) {
 ||             if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
-||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
+||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
 ||                             zend_uchar type = concrete_type(src_info);
 |                              SET_ZVAL_TYPE_INFO dst_addr, type
 ||                     }
@@ -1138,7 +1144,7 @@ static void* dasm_labels[zend_lb_MAX];
 ||         has_concrete_type(src_info & MAY_BE_ANY)) {
 ||             zend_uchar type = concrete_type(src_info);
 ||             if (Z_MODE(dst_addr) == IS_MEM_ZVAL) {
-||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
+||                     if ((dst_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_GUARD)) != (src_info & (MAY_BE_ANY|MAY_BE_UNDEF))) {
 |                              SET_ZVAL_TYPE_INFO dst_addr, type
 ||                     }
 ||             }
@@ -1555,6 +1561,7 @@ static zend_bool delayed_call_chain;
 static uint32_t  delayed_call_level;
 static const zend_op *last_valid_opline;
 static int jit_return_label;
+static uint32_t current_trace_num;
 
 /* bit helpers */
 
@@ -2198,6 +2205,7 @@ static int zend_jit_hybrid_func_counter_stub(dasm_State **Dst)
        |               jmp aword [r1+r2*4+4]
        |       .endif
        |1:
+       |       mov word [r2], ZEND_JIT_HOT_COUNTER_INIT
        |       mov FCARG1a, FP
        |       GET_IP FCARG2a
        |       EXT_CALL zend_jit_hot_func, r0
@@ -2230,6 +2238,7 @@ static int zend_jit_hybrid_loop_counter_stub(dasm_State **Dst)
        |               jmp aword [r1+r2*4+4]
        |       .endif
        |1:
+       |       mov word [r2], ZEND_JIT_HOT_COUNTER_INIT
        |       mov FCARG1a, FP
        |       GET_IP FCARG2a
        |       EXT_CALL zend_jit_hot_func, r0
@@ -2237,6 +2246,157 @@ static int zend_jit_hybrid_loop_counter_stub(dasm_State **Dst)
        return 1;
 }
 
+static int zend_jit_hybrid_trace_counter_stub(dasm_State **Dst, uint32_t cost)
+{
+       |       mov r0, EX->func
+       |       mov r1, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
+       |       mov r1, aword [r1 + offsetof(zend_jit_op_array_trace_extension, offset)]
+       |       mov     r2, aword [IP + r1 + offsetof(zend_op_trace_info, counter)]
+       |       sub word [r2], cost
+       |       jle >1
+       |       jmp aword [IP + r1]
+       |1:
+       |       mov word [r2], ZEND_JIT_TRACE_COUNTER_INIT
+       |       mov FCARG1a, FP
+       |       GET_IP FCARG2a
+       |       EXT_CALL zend_jit_trace_hot_root, r0
+       |       test eax, eax // TODO : remove this check at least for HYBRID VM ???
+       |       jl >1
+       |       MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
+       |       LOAD_OPLINE
+       |       JMP_IP
+       |1:
+       |       EXT_JMP zend_jit_halt_op->handler, r0
+       return 1;
+}
+
+static int zend_jit_hybrid_func_trace_counter_stub(dasm_State **Dst)
+{
+       |->hybrid_func_counter:
+
+       return zend_jit_hybrid_trace_counter_stub(Dst, ZEND_JIT_TRACE_FUNC_COST);
+}
+
+static int zend_jit_hybrid_ret_trace_counter_stub(dasm_State **Dst)
+{
+       |->hybrid_ret_counter:
+
+       return zend_jit_hybrid_trace_counter_stub(Dst, ZEND_JIT_TRACE_RET_COST);
+}
+
+static int zend_jit_hybrid_loop_trace_counter_stub(dasm_State **Dst)
+{
+       |->hybrid_loop_counter:
+
+       return zend_jit_hybrid_trace_counter_stub(Dst, ZEND_JIT_TRACE_LOOP_COST);
+}
+
+static int zend_jit_trace_halt_stub(dasm_State **Dst)
+{
+       |->trace_halt:
+       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+               |       add r4, HYBRID_SPAD
+               |       EXT_JMP zend_jit_halt_op->handler, r0
+       } else if (GCC_GLOBAL_REGS) {
+               |       add r4, SPAD // stack alignment
+               |       ret // PC must be zero
+       } else {
+               |       mov FP, aword T2 // restore FP
+               |       mov RX, aword T3 // restore IP
+               |       add r4, NR_SPAD // stack alignment
+               |       mov r0, -1 // ZEND_VM_RETURN
+               |       ret
+       }
+       return 1;
+}
+
+static int zend_jit_trace_exit_stub(dasm_State **Dst)
+{
+       |->trace_exit:
+       |
+       |       // TODO: Save CPU registers ???
+       |
+       |       // trace_num = EG(reserved)[zend_func_info_rid]
+       |       MEM_OP2_2_ZTS mov, FCARG1a, aword, executor_globals, reserved[zend_func_info_rid], r0
+       |       // exit_num = POP
+       |       pop FCARG2a
+       |       // EX(opline) = opline
+       |       SAVE_OPLINE
+       |       // zend_jit_trace_exit(trace_num, exit_num)
+       |       EXT_CALL zend_jit_trace_exit, r0
+       |       // execute_data = EG(current_excute_data)
+       |       MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
+       |       test eax, eax
+       |       jl ->trace_halt
+       |       // opline = EX(opline)
+       |       LOAD_OPLINE
+
+       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+#if 1
+               //TODO: this doesn't work for exit from first instruction ???
+               |       add r4, HYBRID_SPAD
+               |       JMP_IP
+#else
+               |       mov r0, EX->func
+               |       mov r1, aword [r0 + offsetof(zend_op_array, reserved[zend_func_info_rid])]
+               |       mov r1, aword [r1 + offsetof(zend_jit_op_array_trace_extension, offset)]
+               |       jmp aword [IP + r1]
+#endif
+       } else if (GCC_GLOBAL_REGS) {
+               |       add r4, SPAD // stack alignment
+               |       JMP_IP
+       } else {
+               |       mov FP, aword T2 // restore FP
+               |       mov RX, aword T3 // restore IP
+               |       add r4, NR_SPAD // stack alignment
+               |       mov r0, 1 // ZEND_VM_ENTER
+               |       ret
+       }
+
+       return 1;
+}
+
+static int zend_jit_trace_escape_stub(dasm_State **Dst)
+{
+       |->trace_escape:
+       |
+       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+               |       add r4, HYBRID_SPAD
+               |       JMP_IP
+       } else if (GCC_GLOBAL_REGS) {
+               |       add r4, SPAD // stack alignment
+               |       JMP_IP
+       } else {
+               |       mov FP, aword T2 // restore FP
+               |       mov RX, aword T3 // restore IP
+               |       add r4, NR_SPAD // stack alignment
+               |       mov r0, 1 // ZEND_VM_ENTER
+               |       ret
+       }
+
+       return 1;
+}
+
+/* Keep 32 exit points in a single code block */
+#define ZEND_JIT_EXIT_POINTS_SPACING   4  // push byte + short jmp = bytes
+#define ZEND_JIT_EXIT_POINTS_PER_GROUP 32 // number of continuous exit points
+
+static int zend_jit_trace_exit_group_stub(dasm_State **Dst, uint32_t n)
+{
+       uint32_t i;
+
+       for (i = 0; i < ZEND_JIT_EXIT_POINTS_PER_GROUP - 1; i++) {
+               |       push byte i
+               |       .byte 0xeb, (4*(ZEND_JIT_EXIT_POINTS_PER_GROUP-i)-6) // jmp >1
+       }
+       |       push byte i
+       |// 1:
+       |       .byte 0x81, 0x04, 0x24; .dword n // add aword [r4], n
+       |       jmp ->trace_exit
+
+       return 1;
+}
+
 #ifdef CONTEXT_THREADED_JIT
 static int zend_jit_context_threaded_call_stub(dasm_State **Dst)
 {
@@ -2280,6 +2440,9 @@ static const zend_jit_stub zend_jit_stubs[] = {
        JIT_STUB(undefined_function),
        JIT_STUB(negative_shift),
        JIT_STUB(mod_by_zero),
+       JIT_STUB(trace_halt),
+       JIT_STUB(trace_exit),
+       JIT_STUB(trace_escape),
        JIT_STUB(double_one),
 #ifdef CONTEXT_THREADED_JIT
        JIT_STUB(context_threaded_call),
@@ -2415,6 +2578,12 @@ static int zend_jit_setup(void)
        return SUCCESS;
 }
 
+static ZEND_ATTRIBUTE_UNUSED int zend_jit_trap(dasm_State **Dst)
+{
+       |       int3
+       return 1;
+}
+
 static int zend_jit_align_func(dasm_State **Dst)
 {
        reuse_ip = 0;
@@ -2533,6 +2702,379 @@ static int zend_jit_check_exception_undef_result(dasm_State **Dst, const zend_op
        return zend_jit_check_exception(Dst);
 }
 
+static int zend_jit_trace_begin(dasm_State **Dst, uint32_t trace_num)
+{
+       current_trace_num = trace_num;
+
+       |       //EG(reserved)[zend_func_info_rid] = trace_num;
+       |       MEM_OP2_1_ZTS mov, aword, executor_globals, reserved[zend_func_info_rid], trace_num, r0
+
+       return 1;
+}
+
+static int zend_jit_trace_opline_guard(dasm_State **Dst, const zend_op *opline)
+{
+       uint32_t exit_point = zend_jit_trace_get_exit_point(NULL, NULL, NULL);
+       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+       if (!exit_addr) {
+               return 0;
+       }
+       |       CMP_IP opline
+       |       jne &exit_addr
+
+       return 1;
+}
+
+/* This taken from LuaJIT. Thanks to Mike Pall. */
+static uint32_t _asm_x86_inslen(const uint8_t* p)
+{
+       static const uint8_t map_op1[256] = {
+               0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x20,
+               0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,
+               0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,
+               0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,
+#if defined(__x86_64__) || defined(_M_X64)
+               0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
+#else
+               0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,
+#endif
+               0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,
+               0x51,0x51,0x92,0x92,0x10,0x10,0x12,0x11,0x45,0x86,0x52,0x93,0x51,0x51,0x51,0x51,
+               0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,
+               0x93,0x86,0x93,0x93,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+               0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x47,0x51,0x51,0x51,0x51,0x51,
+#if defined(__x86_64__) || defined(_M_X64)
+               0x59,0x59,0x59,0x59,0x51,0x51,0x51,0x51,0x52,0x45,0x51,0x51,0x51,0x51,0x51,0x51,
+#else
+               0x55,0x55,0x55,0x55,0x51,0x51,0x51,0x51,0x52,0x45,0x51,0x51,0x51,0x51,0x51,0x51,
+#endif
+               0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
+               0x93,0x93,0x53,0x51,0x70,0x71,0x93,0x86,0x54,0x51,0x53,0x51,0x51,0x52,0x51,0x51,
+               0x92,0x92,0x92,0x92,0x52,0x52,0x51,0x51,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
+               0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x45,0x45,0x47,0x52,0x51,0x51,0x51,0x51,
+               0x10,0x51,0x10,0x10,0x51,0x51,0x63,0x66,0x51,0x51,0x51,0x51,0x51,0x51,0x92,0x92
+       };
+       static const uint8_t map_op2[256] = {
+               0x93,0x93,0x93,0x93,0x52,0x52,0x52,0x52,0x52,0x52,0x51,0x52,0x51,0x93,0x52,0x94,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x34,0x51,0x35,0x51,0x51,0x51,0x51,0x51,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x53,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x94,0x54,0x54,0x54,0x93,0x93,0x93,0x52,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x52,0x52,0x52,0x93,0x94,0x93,0x51,0x51,0x52,0x52,0x52,0x93,0x94,0x93,0x93,0x93,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x94,0x93,0x93,0x93,0x93,0x93,
+               0x93,0x93,0x94,0x93,0x94,0x94,0x94,0x93,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
+               0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x52
+       };
+       uint32_t result = 0;
+       uint32_t prefixes = 0;
+       uint32_t x = map_op1[*p];
+
+       for (;;) {
+               switch (x >> 4) {
+                       case 0:
+                               return result + x + (prefixes & 4);
+                       case 1:
+                               prefixes |= x;
+                               x = map_op1[*++p];
+                               result++;
+                               break;
+                       case 2:
+                               x = map_op2[*++p];
+                               break;
+                       case 3:
+                               p++;
+                               goto mrm;
+                       case 4:
+                               result -= (prefixes & 2);
+                               /* fallthrough */
+                       case 5:
+                               return result + (x & 15);
+                       case 6: /* Group 3. */
+                               if (p[1] & 0x38) {
+                                       x = 2;
+                               } else if ((prefixes & 2) && (x == 0x66)) {
+                                       x = 4;
+                               }
+                               goto mrm;
+                       case 7: /* VEX c4/c5. */
+#if !defined(__x86_64__) && !defined(_M_X64)
+                               if (p[1] < 0xc0) {
+                                       x = 2;
+                                       goto mrm;
+                               }
+#endif
+                               if (x == 0x70) {
+                                       x = *++p & 0x1f;
+                                       result++;
+                                       if (x >= 2) {
+                                               p += 2;
+                                               result += 2;
+                                               goto mrm;
+                                       }
+                               }
+                               p++;
+                               result++;
+                               x = map_op2[*++p];
+                               break;
+                       case 8:
+                               result -= (prefixes & 2);
+                               /* fallthrough */
+                       case 9:
+mrm:
+                               /* ModR/M and possibly SIB. */
+                               result += (x & 15);
+                               x = *++p;
+                               switch (x >> 6) {
+                                       case 0:
+                                               if ((x & 7) == 5) {
+                                                       return result + 4;
+                                               }
+                                               break;
+                                       case 1:
+                                               result++;
+                                               break;
+                                       case 2:
+                                               result += 4;
+                                               break;
+                                       case 3:
+                                               return result;
+                               }
+                               if ((x & 7) == 4) {
+                                       result++;
+                                       if (x < 0x40 && (p[1] & 7) == 5) {
+                                               result += 4;
+                                       }
+                               }
+                               return result;
+               }
+       }
+}
+
+static int zend_jit_patch(const void *code, size_t size, const void *from_addr, const void *to_addr)
+{
+       int ret = 0;
+       uint8_t *p = (uint8_t*)code;
+       uint8_t *end = p + size - 5;
+
+       while (p < end) {
+               if ((*(uint16_t*)p & 0xf0ff) == 0x800f && p + *(int32_t*)(p+2) == (uint8_t*)from_addr - 6) {
+                       *(int32_t*)(p+2) = ((uint8_t*)to_addr - (p + 6));
+                       ret++;
+               } else if (*p == 0xe9 && p + *(int32_t*)(p+1) == (uint8_t*)from_addr - 5) {
+                       *(int32_t*)(p+1) = ((uint8_t*)to_addr - (p + 5));
+                       ret++;
+               }
+               p += _asm_x86_inslen(p);
+       }
+#ifdef HAVE_VALGRIND
+       VALGRIND_DISCARD_TRANSLATIONS(code, size);
+#endif
+       return ret;
+}
+
+static int zend_jit_link_side_trace(const void *code, size_t size, uint32_t exit_num, const void *addr)
+{
+       return zend_jit_patch(code, size, zend_jit_trace_get_exit_addr(exit_num), addr);
+}
+
+static int zend_jit_trace_link_to_root(dasm_State **Dst, const void *code)
+{
+       const void *exit_addr;
+       size_t prologue_size;
+
+       /* Skip prologue. */
+       // TODO: don't hardcode this ???
+       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+               // sub r4, HYBRID_SPAD
+#if defined(__x86_64__) || defined(_M_X64)
+               prologue_size = 4;
+#else
+               prologue_size = 3;
+#endif
+       } else if (GCC_GLOBAL_REGS) {
+               // sub r4, SPAD // stack alignment
+#if defined(__x86_64__) || defined(_M_X64)
+               prologue_size = 4;
+#else
+               prologue_size = 3;
+#endif
+       } else {
+               // sub r4, NR_SPAD // stack alignment
+               // mov aword T2, FP // save FP
+               // mov aword T3, RX // save IP
+               // mov FP, FCARG1a
+#if defined(__x86_64__) || defined(_M_X64)
+               prologue_size = 17;
+#else
+               prologue_size = 12;
+#endif
+       }
+       exit_addr = (const void*)((const char*)code + prologue_size);
+
+       |       jmp &exit_addr
+       return 1;
+}
+
+static int zend_jit_trace_return(dasm_State **Dst)
+{
+#if 0
+       |       jmp ->trace_escape
+#else
+       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+               |       add r4, HYBRID_SPAD
+               |       JMP_IP
+       } else if (GCC_GLOBAL_REGS) {
+               |       add r4, SPAD // stack alignment
+               |       JMP_IP
+       } else {
+               |       mov FP, aword T2 // restore FP
+               |       mov RX, aword T3 // restore IP
+               |       add r4, NR_SPAD // stack alignment
+               |       mov r0, 2 // ZEND_VM_LEAVE
+               |       ret
+       }
+#endif
+       return 1;
+}
+
+static int zend_jit_type_guard(dasm_State **Dst, const zend_op *opline, uint32_t var, uint8_t type)
+{
+       int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+       if (!exit_addr) {
+               return 0;
+       }
+       |       IF_NOT_Z_TYPE FP + var, type, &exit_addr
+
+       return 1;
+}
+
+static int zend_jit_trace_handler(dasm_State **Dst, const zend_op_array *op_array, const zend_op *opline, int may_throw, zend_jit_trace_rec *trace)
+{
+       zend_jit_op_array_trace_extension *jit_extension =
+               (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array);
+       size_t offset = jit_extension->offset;
+       const void *handler =
+               (zend_vm_opcode_handler_t)ZEND_OP_TRACE_INFO(opline, offset)->call_handler;
+
+       if (!zend_jit_set_valid_ip(Dst, opline)) {
+               return 0;
+       }
+       if (!GCC_GLOBAL_REGS) {
+               |       mov FCARG1a, FP
+       }
+       |       EXT_CALL handler, r0
+       if (may_throw) {
+               zend_jit_check_exception(Dst);
+       }
+
+       if (!GCC_GLOBAL_REGS) {
+               if (opline->opcode == ZEND_RETURN ||
+                   opline->opcode == ZEND_DO_UCALL ||
+                   opline->opcode == ZEND_DO_FCALL_BY_NAME ||
+                   opline->opcode == ZEND_DO_FCALL) {
+                       |       MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
+               }
+       }
+
+       if (zend_jit_trace_may_exit(op_array, opline, trace)) {
+               // TODO: try to avoid this check ???
+               if (opline->opcode == ZEND_RETURN) {
+                       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+                               |       cmp IP, zend_jit_halt_op
+                               |       je ->trace_halt
+                       } else if (GCC_GLOBAL_REGS) {
+                               |       test IP, IP
+                               |       je ->trace_halt
+                       } else {
+                               |       test eax, eax
+                               |       jl ->trace_halt
+                       }
+               }
+               while (trace->op != ZEND_JIT_TRACE_VM && trace->op != ZEND_JIT_TRACE_END) {
+                       trace++;
+               }
+               if (trace->op != ZEND_JIT_TRACE_END || trace->stop != ZEND_JIT_TRACE_STOP_RETURN) {
+                       const zend_op *next_opline = trace->opline;
+                       const zend_op *exit_opline = NULL;
+                       uint32_t exit_point;
+                       const void *exit_addr;
+
+                       if (zend_is_smart_branch(opline)) {
+                               zend_bool exit_if_true = 0;
+                               exit_opline = zend_jit_trace_get_exit_opline(trace, opline + 1, &exit_if_true);
+                       } else {
+                               switch (opline->opcode) {
+                                       case ZEND_JMPZ:
+                                       case ZEND_JMPNZ:
+                                       case ZEND_JMPZ_EX:
+                                       case ZEND_JMPNZ_EX:
+                                       case ZEND_JMP_SET:
+                                       case ZEND_COALESCE:
+                                       case ZEND_FE_RESET_R:
+                                       case ZEND_FE_RESET_RW:
+                                               exit_opline = (trace->opline == opline + 1) ?
+                                                       OP_JMP_ADDR(opline, opline->op2) :
+                                                       opline + 1;
+                                               break;
+                                       case ZEND_JMPZNZ:
+                                               exit_opline = (trace->opline == OP_JMP_ADDR(opline, opline->op2)) ?
+                                                       ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) :
+                                                       OP_JMP_ADDR(opline, opline->op2);
+                                               break;
+                                       case ZEND_FE_FETCH_R:
+                                       case ZEND_FE_FETCH_RW:
+                                               exit_opline = (trace->opline == opline + 1) ?
+                                                       ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value) :
+                                                       opline + 1;
+                                               break;
+
+                               }
+                       }
+                       exit_point = zend_jit_trace_get_exit_point(opline, exit_opline, trace);
+                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+                       if (!exit_addr) {
+                               return 0;
+                       }
+                       |       CMP_IP next_opline
+                       |       jne &exit_addr
+               }
+       } else {
+               while (trace->op != ZEND_JIT_TRACE_VM && trace->op != ZEND_JIT_TRACE_END) {
+                       trace++;
+               }
+               // TODO: remove this ???
+               if (opline->opcode == ZEND_RETURN
+                && trace->op == ZEND_JIT_TRACE_END
+                && trace->stop == ZEND_JIT_TRACE_STOP_RETURN) {
+                       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+                               |       cmp IP, zend_jit_halt_op
+                               |       je ->trace_halt
+                       } else if (GCC_GLOBAL_REGS) {
+                               |       test IP, IP
+                               |       je ->trace_halt
+                       } else {
+                               |       test eax, eax
+                               |       jl ->trace_halt
+                       }
+               }
+       }
+
+       last_valid_opline = trace->opline;
+
+       return 1;
+}
+
 static int zend_jit_handler(dasm_State **Dst, const zend_op *opline, int may_throw)
 {
        const void *handler;
@@ -2810,7 +3352,11 @@ static int zend_jit_inc_dec(dasm_State **Dst, const zend_op *opline, const zend_
                |       LONG_OP_WITH_CONST sub, op1_def_addr, Z_L(1)
        }
 
-       if (may_overflow) {
+       if (may_overflow && (op1_def_info & MAY_BE_GUARD)) {
+               int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+               const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+               |       jo &exit_addr
+       } else if (may_overflow) {
                |       jo >1
                if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) &&
                    opline->result_type != IS_UNUSED) {
@@ -3024,19 +3570,25 @@ static int zend_jit_math_long_long(dasm_State    **Dst,
                }
        }
        if (may_overflow) {
-               |       jo >1
+               if (res_info & MAY_BE_GUARD) {
+                       int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                       |       jo &exit_addr
+               } else {
+                       |       jo >1
+               }
        }
 
        if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
                |       SET_ZVAL_LVAL res_addr, Ra(result_reg)
                if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
-                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_LONG) {
+                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
                                |       SET_ZVAL_TYPE_INFO res_addr, IS_LONG
                        }
                }
        }
 
-       if (may_overflow) {
+       if (may_overflow && !(res_info & MAY_BE_GUARD)) {
                zend_reg tmp_reg1 = ZREG_XMM0;
                zend_reg tmp_reg2 = ZREG_XMM1;
 
@@ -3077,7 +3629,7 @@ static int zend_jit_math_long_long(dasm_State    **Dst,
                        |       SSE_SET_ZVAL_DVAL res_addr, tmp_reg1
                } while (0);
 
-               if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_DOUBLE) {
+               if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
                        |       SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
                }
                |       jmp >2
@@ -3107,7 +3659,7 @@ static int zend_jit_math_long_double(dasm_State    **Dst,
        |       SSE_SET_ZVAL_DVAL res_addr, result_reg
 
        if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
-               if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_DOUBLE) {
+               if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
                        |       SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
                }
        }
@@ -3170,7 +3722,7 @@ static int zend_jit_math_double_long(dasm_State    **Dst,
 
        if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
                if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
-                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_DOUBLE) {
+                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
                                |       SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
                        }
                }
@@ -3241,7 +3793,7 @@ static int zend_jit_math_double_double(dasm_State    **Dst,
 
        if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
                if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
-                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_DOUBLE) {
+                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_DOUBLE) {
                                |       SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE
                        }
                }
@@ -3686,7 +4238,7 @@ static int zend_jit_long_math_helper(dasm_State    **Dst,
        |       SET_ZVAL_LVAL res_addr, Ra(result_reg)
        if (Z_MODE(res_addr) == IS_MEM_ZVAL) {
                if (Z_MODE(op1_addr) != IS_MEM_ZVAL || Z_REG(op1_addr) != Z_REG(res_addr) || Z_OFFSET(op1_addr) != Z_OFFSET(res_addr)) {
-                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) != MAY_BE_LONG) {
+                       if ((res_use_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_GUARD)) != MAY_BE_LONG) {
                                |       SET_ZVAL_TYPE_INFO res_addr, IS_LONG
                        }
                }
@@ -3908,6 +4460,15 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
 {
        zend_jit_addr op2_addr = OP2_ADDR();
        zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
+       const void *exit_addr = NULL;
+
+       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
+               int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+               exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+               if (!exit_addr) {
+                       return 0;
+               }
+       }
 
        if (op2_info & MAY_BE_LONG) {
                if (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_LONG)) {
@@ -3936,6 +4497,8 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                        |.endif
                                        if (type == BP_JIT_IS) {
                                                |       jbe >9 // NOT_FOUND
+                                       } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && (type == BP_VAR_R || type == BP_VAR_RW)) {
+                                               |       jbe &exit_addr
                                        } else {
                                                |       jbe >2 // NOT_FOUND
                                        }
@@ -3963,6 +4526,8 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                |.endif
                                if (type == BP_JIT_IS) {
                                        |       jbe >9 // NOT_FOUND
+                               } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && (type == BP_VAR_R || type == BP_VAR_RW)) {
+                                       |       jbe &exit_addr
                                } else {
                                        |       jbe >2 // NOT_FOUND
                                }
@@ -4000,8 +4565,14 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                        if (Z_MODE(op2_addr) == IS_CONST_ZVAL) {
                                                zend_long val = Z_LVAL_P(Z_ZV(op2_addr));
                                                if (val >= 0 && val < HT_MAX_SIZE) {
-                                                       |       jmp >2 // NOT_FOUND
+                                                       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
+                                                               |       jmp &exit_addr
+                                                       } else {
+                                                               |       jmp >2 // NOT_FOUND
+                                                       }
                                                }
+                                       } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
+                                               |       jmp &exit_addr
                                        } else {
                                                |       jmp >2 // NOT_FOUND
                                        }
@@ -4009,15 +4580,21 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                }
                                |       EXT_CALL zend_hash_index_find, r0
                                |       test r0, r0
-                               |       jz >2 // NOT_FOUND
+                               if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
+                                       |       jz &exit_addr
+                               } else {
+                                       |       jz >2 // NOT_FOUND
+                               }
                                |.cold_code
                                |2:
                                switch (type) {
                                        case BP_VAR_R:
-                                               |       // zend_error(E_NOTICE,"Undefined offset: " ZEND_LONG_FMT, hval);
-                                               |       // retval = &EG(uninitialized_zval);
-                                               |       UNDEFINED_OFFSET opline
-                                               |       jmp >9
+                                               if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE) {
+                                                       |       // zend_error(E_NOTICE,"Undefined offset: " ZEND_LONG_FMT, hval);
+                                                       |       // retval = &EG(uninitialized_zval);
+                                                       |       UNDEFINED_OFFSET opline
+                                                       |       jmp >9
+                                               }
                                                break;
                                        case BP_VAR_IS:
                                        case BP_VAR_UNSET:
@@ -4032,12 +4609,16 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                break;
                        case BP_VAR_RW:
                                |2:
-                               |       SAVE_VALID_OPLINE opline
-                               |       // zend_error(E_NOTICE,"Undefined offset: " ZEND_LONG_FMT, hval);
-                               |       //retval = zend_hash_index_update(ht, hval, &EG(uninitialized_zval));
-                               |       EXT_CALL zend_jit_fetch_dimension_rw_long_helper, r0
+                               if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE) {
+                                       |       SAVE_VALID_OPLINE opline
+                                       |       // zend_error(E_NOTICE,"Undefined offset: " ZEND_LONG_FMT, hval);
+                                       |       //retval = zend_hash_index_update(ht, hval, &EG(uninitialized_zval));
+                                       |       EXT_CALL zend_jit_fetch_dimension_rw_long_helper, r0
+                               }
                                if (op1_info & MAY_BE_ARRAY_KEY_LONG) {
-                                       |       jmp >8
+                                       if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE) {
+                                               |       jmp >8
+                                       }
                                        |4:
                                        |       SAVE_VALID_OPLINE opline
                                        |       EXT_CALL zend_jit_hash_index_lookup_rw, r0
@@ -4119,7 +4700,11 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                        |       EXT_CALL _zend_hash_find_known_hash, r0
                                }
                                |       test r0, r0
-                               |       jz >2 // NOT_FOUND
+                               if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && type == BP_VAR_R) {
+                                       |       jz &exit_addr
+                               } else {
+                                       |       jz >2 // NOT_FOUND
+                               }
                                |       // if (UNEXPECTED(Z_TYPE_P(retval) == IS_INDIRECT))
                                |       IF_Z_TYPE r0, IS_INDIRECT, >1 // SLOW
                                |.cold_code
@@ -4130,9 +4715,11 @@ static int zend_jit_fetch_dimension_address_inner(dasm_State **Dst, const zend_o
                                |2:
                                switch (type) {
                                        case BP_VAR_R:
-                                               //      zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(offset_key));
-                                               |       UNDEFINED_INDEX opline
-                                               |       jmp >9
+                                               if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE) {
+                                                       //      zend_error(E_NOTICE, "Undefined index: %s", ZSTR_VAL(offset_key));
+                                                       |       UNDEFINED_INDEX opline
+                                                       |       jmp >9
+                                               }
                                                break;
                                        case BP_VAR_IS:
                                        case BP_VAR_UNSET:
@@ -4978,7 +5565,7 @@ static int zend_jit_assign_op(dasm_State **Dst, const zend_op *opline, const zen
        return result;
 }
 
-static int zend_jit_cmp_long_long(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp_long_long(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        zend_bool swap = 0;
 
@@ -5050,24 +5637,54 @@ static int zend_jit_cmp_long_long(dasm_State **Dst, const zend_op *opline, zend_
                                case ZEND_IS_EQUAL:
                                case ZEND_IS_IDENTICAL:
                                case ZEND_CASE:
-                                       | jne => target_label
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                       }
                                        break;
                                case ZEND_IS_NOT_EQUAL:
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
+                                       break;
                                case ZEND_IS_NOT_IDENTICAL:
-                                       | je => target_label
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER:
                                        if (swap) {
-                                               | jle => target_label
+                                               if (exit_addr) {
+                                                       | jle &exit_addr
+                                               } else {
+                                                       | jle => target_label
+                                               }
                                        } else {
-                                               | jge => target_label
+                                               if (exit_addr) {
+                                                       | jge &exit_addr
+                                               } else {
+                                                       | jge => target_label
+                                               }
                                        }
                                        break;
                                case ZEND_IS_SMALLER_OR_EQUAL:
                                        if (swap) {
-                                               | jl => target_label
+                                               if (exit_addr) {
+                                                       | jl &exit_addr
+                                               } else {
+                                                       | jl => target_label
+                                               }
                                        } else {
-                                               | jg => target_label
+                                               if (exit_addr) {
+                                                       | jg &exit_addr
+                                               } else {
+                                                       | jg => target_label
+                                               }
                                        }
                                        break;
                                default:
@@ -5079,24 +5696,54 @@ static int zend_jit_cmp_long_long(dasm_State **Dst, const zend_op *opline, zend_
                                case ZEND_IS_EQUAL:
                                case ZEND_IS_IDENTICAL:
                                case ZEND_CASE:
-                                       | je => target_label
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
                                        break;
                                case ZEND_IS_NOT_EQUAL:
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                       }
+                                       break;
                                case ZEND_IS_NOT_IDENTICAL:
-                                       | jne => target_label
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER:
                                        if (swap) {
-                                               | jg => target_label
+                                               if (exit_addr) {
+                                                       | jg &exit_addr
+                                               } else {
+                                                       | jg => target_label
+                                               }
                                        } else {
-                                               | jl => target_label
+                                               if (exit_addr) {
+                                                       | jl &exit_addr
+                                               } else {
+                                                       | jl => target_label
+                                               }
                                        }
                                        break;
                                case ZEND_IS_SMALLER_OR_EQUAL:
                                        if (swap) {
-                                               | jge => target_label
+                                               if (exit_addr) {
+                                                       | jge &exit_addr
+                                               } else {
+                                                       | jge => target_label
+                                               }
                                        } else {
-                                               | jle => target_label
+                                               if (exit_addr) {
+                                                       | jle &exit_addr
+                                               } else {
+                                                       | jle => target_label
+                                               }
                                        }
                                        break;
                                default:
@@ -5170,7 +5817,7 @@ static int zend_jit_cmp_long_long(dasm_State **Dst, const zend_op *opline, zend_
        return 1;
 }
 
-static int zend_jit_cmp_double_common(dasm_State **Dst, const zend_op *opline, zend_jit_addr res_addr, zend_bool swap, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp_double_common(dasm_State **Dst, const zend_op *opline, zend_jit_addr res_addr, zend_bool swap, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        if (smart_branch_opcode) {
                if (smart_branch_opcode == ZEND_JMPZ) {
@@ -5178,29 +5825,65 @@ static int zend_jit_cmp_double_common(dasm_State **Dst, const zend_op *opline, z
                                case ZEND_IS_EQUAL:
                                case ZEND_IS_IDENTICAL:
                                case ZEND_CASE:
-                                       | jne => target_label
-                                       | jp => target_label
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                               | jp &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                               | jp => target_label
+                                       }
                                        break;
                                case ZEND_IS_NOT_EQUAL:
-                               case ZEND_IS_NOT_IDENTICAL:
                                        | jp >1
-                                       | je => target_label
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
                                        |1:
                                        break;
+                               case ZEND_IS_NOT_IDENTICAL:
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                               | jp &exit_addr
+                                       } else {
+                                               | jp >1
+                                               | je => target_label
+                                               |1:
+                                       }
+                                       break;
                                case ZEND_IS_SMALLER:
                                        if (swap) {
-                                               | jbe => target_label
+                                               if (exit_addr) {
+                                                       | jbe &exit_addr
+                                               } else {
+                                                       | jbe => target_label
+                                               }
                                        } else {
-                                               | jae => target_label
-                                               | jp => target_label
+                                               if (exit_addr) {
+                                                       | jae &exit_addr
+                                                       | jp &exit_addr
+                                               } else {
+                                                       | jae => target_label
+                                                       | jp => target_label
+                                               }
                                        }
                                        break;
                                case ZEND_IS_SMALLER_OR_EQUAL:
                                        if (swap) {
-                                               | jb => target_label
+                                               if (exit_addr) {
+                                                       | jb &exit_addr
+                                               } else {
+                                                       | jb => target_label
+                                               }
                                        } else {
-                                               | ja => target_label
-                                               | jp => target_label
+                                               if (exit_addr) {
+                                                       | ja &exit_addr
+                                                       | jp &exit_addr
+                                               } else {
+                                                       | ja => target_label
+                                                       | jp => target_label
+                                               }
                                        }
                                        break;
                                default:
@@ -5212,29 +5895,63 @@ static int zend_jit_cmp_double_common(dasm_State **Dst, const zend_op *opline, z
                                case ZEND_IS_IDENTICAL:
                                case ZEND_CASE:
                                        | jp >1
-                                       | je => target_label
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
                                        |1:
                                        break;
                                case ZEND_IS_NOT_EQUAL:
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                               | jp &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                               | jp => target_label
+                                       }
+                                       break;
                                case ZEND_IS_NOT_IDENTICAL:
-                                       | jne => target_label
-                                       | jp => target_label
+                                       if (exit_addr) {
+                                               |1:
+                                               | je &exit_addr
+                                               |1:
+                                       } else {
+                                               | jne => target_label
+                                               | jp => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER:
                                        if (swap) {
-                                               | ja => target_label
+                                               if (exit_addr) {
+                                                       | ja &exit_addr
+                                               } else {
+                                                       | ja => target_label
+                                               }
                                        } else {
                                                | jp >1
-                                               | jb => target_label
+                                               if (exit_addr) {
+                                                       | jb &exit_addr
+                                               } else {
+                                                       | jb => target_label
+                                               }
                                                |1:
                                        }
                                        break;
                                case ZEND_IS_SMALLER_OR_EQUAL:
                                        if (swap) {
-                                               | jae => target_label
+                                               if (exit_addr) {
+                                                       | jae &exit_addr
+                                               } else {
+                                                       | jae => target_label
+                                               }
                                        } else {
                                                | jp >1
-                                               | jbe => target_label
+                                               if (exit_addr) {
+                                                       | jbe &exit_addr
+                                               } else {
+                                                       | jbe => target_label
+                                               }
                                                |1:
                                        }
                                        break;
@@ -5431,27 +6148,27 @@ static int zend_jit_cmp_double_common(dasm_State **Dst, const zend_op *opline, z
        return 1;
 }
 
-static int zend_jit_cmp_long_double(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp_long_double(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        zend_reg tmp_reg = ZREG_XMM0;
 
        |       SSE_GET_ZVAL_LVAL tmp_reg, op1_addr
        |       SSE_AVX_OP ucomisd, vucomisd, tmp_reg, op2_addr
 
-       return zend_jit_cmp_double_common(Dst, opline, res_addr, 0, smart_branch_opcode, target_label, target_label2);
+       return zend_jit_cmp_double_common(Dst, opline, res_addr, 0, smart_branch_opcode, target_label, target_label2, exit_addr);
 }
 
-static int zend_jit_cmp_double_long(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp_double_long(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        zend_reg tmp_reg = ZREG_XMM0;
 
        |       SSE_GET_ZVAL_LVAL tmp_reg, op2_addr
        |       SSE_AVX_OP ucomisd, vucomisd, tmp_reg, op1_addr
 
-       return zend_jit_cmp_double_common(Dst, opline, res_addr, /* swap */ 1, smart_branch_opcode, target_label, target_label2);
+       return zend_jit_cmp_double_common(Dst, opline, res_addr, /* swap */ 1, smart_branch_opcode, target_label, target_label2, exit_addr);
 }
 
-static int zend_jit_cmp_double_double(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp_double_double(dasm_State **Dst, const zend_op *opline, zend_jit_addr op1_addr, zend_jit_addr op2_addr, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        zend_bool swap = 0;
 
@@ -5467,10 +6184,10 @@ static int zend_jit_cmp_double_double(dasm_State **Dst, const zend_op *opline, z
                |       SSE_AVX_OP ucomisd, vucomisd, tmp_reg, op2_addr
        }
 
-       return zend_jit_cmp_double_common(Dst, opline, res_addr, swap, smart_branch_opcode, target_label, target_label2);
+       return zend_jit_cmp_double_common(Dst, opline, res_addr, swap, smart_branch_opcode, target_label, target_label2, exit_addr);
 }
 
-static int zend_jit_cmp_slow(dasm_State **Dst, const zend_op *opline, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp_slow(dasm_State **Dst, const zend_op *opline, zend_jit_addr res_addr, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        |       LONG_OP_WITH_CONST cmp, res_addr, Z_L(0)
        if (smart_branch_opcode) {
@@ -5502,16 +6219,32 @@ static int zend_jit_cmp_slow(dasm_State **Dst, const zend_op *opline, zend_jit_a
                        switch (opline->opcode) {
                                case ZEND_IS_EQUAL:
                                case ZEND_CASE:
-                                       | jne => target_label
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                       }
                                        break;
                                case ZEND_IS_NOT_EQUAL:
-                                       | je => target_label
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER:
-                                       | jge => target_label
+                                       if (exit_addr) {
+                                               | jge &exit_addr
+                                       } else {
+                                               | jge => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER_OR_EQUAL:
-                                       | jg => target_label
+                                       if (exit_addr) {
+                                               | jg &exit_addr
+                                       } else {
+                                               | jg => target_label
+                                       }
                                        break;
                                default:
                                        ZEND_ASSERT(0);
@@ -5521,16 +6254,32 @@ static int zend_jit_cmp_slow(dasm_State **Dst, const zend_op *opline, zend_jit_a
                        switch (opline->opcode) {
                                case ZEND_IS_EQUAL:
                                case ZEND_CASE:
-                                       | je => target_label
+                                       if (exit_addr) {
+                                               | je &exit_addr
+                                       } else {
+                                               | je => target_label
+                                       }
                                        break;
                                case ZEND_IS_NOT_EQUAL:
-                                       | jne => target_label
+                                       if (exit_addr) {
+                                               | jne &exit_addr
+                                       } else {
+                                               | jne => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER:
-                                       | jl => target_label
+                                       if (exit_addr) {
+                                               | jl &exit_addr
+                                       } else {
+                                               | jl => target_label
+                                       }
                                        break;
                                case ZEND_IS_SMALLER_OR_EQUAL:
-                                       | jle => target_label
+                                       if (exit_addr) {
+                                               | jle &exit_addr
+                                       } else {
+                                               | jle => target_label
+                                       }
                                        break;
                                default:
                                        ZEND_ASSERT(0);
@@ -5583,7 +6332,7 @@ static int zend_jit_cmp_slow(dasm_State **Dst, const zend_op *opline, zend_jit_a
        return 1;
 }
 
-static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, zend_jit_addr res_addr, int may_throw, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, zend_jit_addr res_addr, int may_throw, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        zend_bool same_ops = (opline->op1_type == opline->op2_type) && (opline->op1.var == opline->op2.var);
        zend_bool has_slow;
@@ -5610,7 +6359,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                                if (op2_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
                                        |       IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
                                }
-                               if (!zend_jit_cmp_long_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                               if (!zend_jit_cmp_long_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                        return 0;
                                }
                                |       jmp >6
@@ -5619,7 +6368,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                                |       IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >9
                        }
                }
-               if (!zend_jit_cmp_long_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+               if (!zend_jit_cmp_long_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                        return 0;
                }
                if (op1_info & MAY_BE_DOUBLE) {
@@ -5636,7 +6385,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                                                |       IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
                                        }
                                }
-                               if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                               if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                        return 0;
                                }
                                |       jmp >6
@@ -5646,7 +6395,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                                if (op2_info & (MAY_BE_ANY-(MAY_BE_LONG|MAY_BE_DOUBLE))) {
                                        |       IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >9
                                }
-                               if (!zend_jit_cmp_double_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                               if (!zend_jit_cmp_double_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                        return 0;
                                }
                                |       jmp >6
@@ -5667,7 +6416,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                                        |       IF_NOT_ZVAL_TYPE op2_addr, IS_DOUBLE, >9
                                }
                        }
-                       if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                       if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                return 0;
                        }
                }
@@ -5679,7 +6428,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                        if (op2_info & (MAY_BE_ANY-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
                                |       IF_NOT_ZVAL_TYPE op2_addr, IS_LONG, >9
                        }
-                       if (!zend_jit_cmp_double_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                       if (!zend_jit_cmp_double_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                return 0;
                        }
                        if (op2_info & MAY_BE_DOUBLE) {
@@ -5701,7 +6450,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                                        |       IF_NOT_ZVAL_TYPE op1_addr, IS_DOUBLE, >9
                                }
                        }
-                       if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                       if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                return 0;
                        }
                }
@@ -5713,7 +6462,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                        if (op1_info & (MAY_BE_ANY-(MAY_BE_DOUBLE|MAY_BE_LONG))) {
                                |       IF_NOT_ZVAL_TYPE op1_addr, IS_LONG, >9
                        }
-                       if (!zend_jit_cmp_long_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
+                       if (!zend_jit_cmp_long_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                                return 0;
                        }
                        if (op1_info & MAY_BE_DOUBLE) {
@@ -5794,7 +6543,7 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
                if (may_throw) {
                        zend_jit_check_exception_undef_result(Dst, opline);
                }
-               if (!zend_jit_cmp_slow(Dst, opline, res_addr, smart_branch_opcode, target_label, target_label2)) {
+               if (!zend_jit_cmp_slow(Dst, opline, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
                        return 0;
                }
                if (has_slow) {
@@ -5808,12 +6557,12 @@ static int zend_jit_cmp(dasm_State **Dst, const zend_op *opline, const zend_op_a
        return 1;
 }
 
-static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, zend_jit_addr res_addr, int may_throw, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, uint32_t op2_info, zend_jit_addr op2_addr, zend_jit_addr res_addr, int may_throw, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        uint32_t identical_label = (uint32_t)-1;
        uint32_t not_identical_label = (uint32_t)-1;
 
-       if (smart_branch_opcode) {
+       if (smart_branch_opcode && !exit_addr) {
                if (opline->opcode == ZEND_IS_IDENTICAL) {
                        if (smart_branch_opcode == ZEND_JMPZ) {
                                not_identical_label = target_label;
@@ -5841,6 +6590,20 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                }
        }
 
+       if ((op1_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG &&
+           (op2_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
+               if (!zend_jit_cmp_long_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
+                       return 0;
+               }
+               return 1;
+       } else if ((op1_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE &&
+                  (op2_info & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_DOUBLE) {
+               if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2, exit_addr)) {
+                       return 0;
+               }
+               return 1;
+       }
+
        if ((op1_info & MAY_BE_UNDEF) && (op2_info & MAY_BE_UNDEF)) {
                op1_info |= MAY_BE_NULL;
                op2_info |= MAY_BE_NULL;
@@ -5956,7 +6719,11 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                }
                if (smart_branch_opcode) {
                        zend_jit_check_exception_undef_result(Dst, opline);
-                       if (not_identical_label != (uint32_t)-1) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPZ) {
+                                       |       jmp &exit_addr
+                               }
+                       } else if (not_identical_label != (uint32_t)-1) {
                                |       jmp =>not_identical_label
                        }
                } else {
@@ -5968,7 +6735,11 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                   concrete_type(op1_info) == concrete_type(op2_info) &&
                   concrete_type(op1_info) <= IS_TRUE) {
                if (smart_branch_opcode) {
-                       if (identical_label != (uint32_t)-1) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPNZ) {
+                                       |       jmp &exit_addr
+                               }
+                       } else if (identical_label != (uint32_t)-1) {
                                |       jmp =>identical_label
                        }
                } else {
@@ -5977,7 +6748,11 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
        } else if (Z_MODE(op1_addr) == IS_CONST_ZVAL && Z_MODE(op2_addr) == IS_CONST_ZVAL) {
                if (zend_is_identical(Z_ZV(op1_addr), Z_ZV(op2_addr))) {
                        if (smart_branch_opcode) {
-                               if (identical_label != (uint32_t)-1) {
+                               if (exit_addr) {
+                                       if (smart_branch_opcode == ZEND_JMPNZ) {
+                                               |       jmp &exit_addr
+                                       }
+                               } else if (identical_label != (uint32_t)-1) {
                                        |       jmp =>identical_label
                                }
                        } else {
@@ -5985,7 +6760,11 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                        }
                } else {
                        if (smart_branch_opcode) {
-                               if (not_identical_label != (uint32_t)-1) {
+                               if (exit_addr) {
+                                       if (smart_branch_opcode == ZEND_JMPZ) {
+                                               |       jmp &exit_addr
+                                       }
+                               } else if (not_identical_label != (uint32_t)-1) {
                                        |       jmp =>not_identical_label
                                }
                        } else {
@@ -6002,12 +6781,16 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                                |       SAVE_VALID_OPLINE opline
                                |       FREE_OP opline->op2_type, opline->op2, op2_info, 1, op_array, opline
                                zend_jit_check_exception_undef_result(Dst, opline);
-                               if (identical_label != (uint32_t)-1) {
+                               if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
+                                       |       jmp &exit_addr
+                               } else if (identical_label != (uint32_t)-1) {
                                        |       jmp =>identical_label
                                } else {
                                        |       jmp >9
                                }
                                |8:
+                       } else if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
+                               |       je &exit_addr
                        } else if (identical_label != (uint32_t)-1) {
                                |       je =>identical_label
                        } else {
@@ -6029,7 +6812,11 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                        |       FREE_OP opline->op2_type, opline->op2, op2_info, 1, op_array, opline
                        zend_jit_check_exception_undef_result(Dst, opline);
                }
-               if (smart_branch_opcode && not_identical_label != (uint32_t)-1) {
+               if (exit_addr) {
+                       if (smart_branch_opcode == ZEND_JMPZ) {
+                               |       jmp &exit_addr
+                       }
+               } else if (smart_branch_opcode && not_identical_label != (uint32_t)-1) {
                        |       jmp =>not_identical_label
                }
        } else if (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_TYPE_P(Z_ZV(op2_addr)) <= IS_TRUE) {
@@ -6042,12 +6829,16 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                                |       SAVE_VALID_OPLINE opline
                                |       FREE_OP opline->op1_type, opline->op1, op1_info, 1, op_array, opline
                                zend_jit_check_exception_undef_result(Dst, opline);
-                               if (identical_label != (uint32_t)-1) {
+                               if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
+                                       |       jmp &exit_addr
+                               } else if (identical_label != (uint32_t)-1) {
                                        |       jmp =>identical_label
                                } else {
                                        |       jmp >9
                                }
                                |8:
+                       } else if (exit_addr && smart_branch_opcode == ZEND_JMPNZ) {
+                               |       je &exit_addr
                        } else if (identical_label != (uint32_t)-1) {
                                |       je =>identical_label
                        } else {
@@ -6069,18 +6860,14 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                        |       FREE_OP opline->op1_type, opline->op1, op1_info, 1, op_array, opline
                        zend_jit_check_exception_undef_result(Dst, opline);
                }
-               if (smart_branch_opcode && not_identical_label != (uint32_t)-1) {
-                       |       jmp =>not_identical_label
-               }
-       } else if ((op1_info & MAY_BE_ANY) == MAY_BE_LONG &&
-                  (op2_info & MAY_BE_ANY) == MAY_BE_LONG) {
-               if (!zend_jit_cmp_long_long(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
-                       return 0;
-               }
-       } else if ((op1_info & MAY_BE_ANY) == MAY_BE_DOUBLE &&
-                  (op2_info & MAY_BE_ANY) == MAY_BE_DOUBLE) {
-               if (!zend_jit_cmp_double_double(Dst, opline, op1_addr, op2_addr, res_addr, smart_branch_opcode, target_label, target_label2)) {
-                       return 0;
+               if (smart_branch_opcode) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPZ) {
+                                       |       jmp &exit_addr
+                               }
+                       } else if (not_identical_label != (uint32_t)-1) {
+                               |       jmp =>not_identical_label
+                       }
                }
        } else {
                if (opline->op1_type == IS_CONST) {
@@ -6103,7 +6890,13 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
                        }
                if (smart_branch_opcode) {
                        |       test al, al
-                       if (not_identical_label != (uint32_t)-1) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPNZ) {
+                                       |       jnz &exit_addr
+                               } else {
+                                       |       jz &exit_addr
+                               }
+                       } else if (not_identical_label != (uint32_t)-1) {
                                |       jz =>not_identical_label
                                if (identical_label != (uint32_t)-1) {
                                        |       jmp =>identical_label
@@ -6127,11 +6920,10 @@ static int zend_jit_identical(dasm_State **Dst, const zend_op *opline, const zen
        if (may_throw) {
                zend_jit_check_exception(Dst);
        }
-
        return 1;
 }
 
-static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, uint32_t target_label, uint32_t target_label2, int may_throw)
+static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, uint32_t target_label, uint32_t target_label2, int may_throw, zend_uchar branch_opcode, const void *exit_addr)
 {
        uint32_t true_label = -1;
        uint32_t false_label = -1;
@@ -6139,22 +6931,22 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
        zend_bool set_bool_not = 0;
        zend_bool jmp_done = 0;
 
-       if (opline->opcode == ZEND_JMPZ) {
+       if (branch_opcode == ZEND_BOOL) {
+               set_bool = 1;
+       } else if (branch_opcode == ZEND_BOOL_NOT) {
+               set_bool = 1;
+               set_bool_not = 1;
+       } else if (branch_opcode == ZEND_JMPZ) {
                false_label = target_label;
-       } else if (opline->opcode == ZEND_JMPNZ) {
+       } else if (branch_opcode == ZEND_JMPNZ) {
                true_label = target_label;
-       } else if (opline->opcode == ZEND_JMPZNZ) {
+       } else if (branch_opcode == ZEND_JMPZNZ) {
                true_label = target_label2;
                false_label = target_label;
-       } else if (opline->opcode == ZEND_BOOL) {
-               set_bool = 1;
-       } else if (opline->opcode == ZEND_BOOL_NOT) {
-               set_bool = 1;
-               set_bool_not = 1;
-       } else if (opline->opcode == ZEND_JMPZ_EX) {
+       } else if (branch_opcode == ZEND_JMPZ_EX) {
                set_bool = 1;
                false_label = target_label;
-       } else if (opline->opcode == ZEND_JMPNZ_EX) {
+       } else if (branch_opcode == ZEND_JMPNZ_EX) {
                set_bool = 1;
                true_label = target_label;
        } else {
@@ -6225,7 +7017,13 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                                    if ((op1_info & MAY_BE_LONG) &&
                                        !(op1_info & MAY_BE_UNDEF) &&
                                        !set_bool) {
-                                               if (false_label != (uint32_t)-1) {
+                                               if (exit_addr) {
+                                                       if (branch_opcode == ZEND_JMPNZ) {
+                                                               |       jl >9
+                                                       } else {
+                                                               |       jl &exit_addr
+                                                       }
+                                               } else if (false_label != (uint32_t)-1) {
                                                        |       jl =>false_label
                                                } else {
                                                        |       jl >9
@@ -6245,7 +7043,32 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                                                }
                                        }
                                } else {
-                                       if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
+                                       if (exit_addr) {
+                                               if (set_bool) {
+                                                       |       jne >1
+                                                       |       SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
+                                                       if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                                               |       jmp &exit_addr
+                                                       } else {
+                                                               |       jmp >9
+                                                       }
+                                                       |1:
+                                                       |       SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
+                                                       if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
+                                                               if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_LONG))) {
+                                                                       |       jne &exit_addr
+                                                               }
+                                                       }
+                                               } else {
+                                                       if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                                               |       je &exit_addr
+                                                       } else if (!(op1_info & (MAY_BE_UNDEF|MAY_BE_LONG))) {
+                                                               |       jne &exit_addr
+                                                       } else {
+                                                               |       je >9
+                                                       }
+                                               }
+                                       } else if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
                                                if (set_bool) {
                                                        |       jne >1
                                                        |       SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
@@ -6297,11 +7120,19 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                                        }
                                }
 
-                               if (false_label != (uint32_t)-1) {
+                               if (exit_addr) {
+                                       if (branch_opcode == ZEND_JMPZ || branch_opcode == ZEND_JMPZ_EX) {
+                                               |       jmp &exit_addr
+                                       }
+                               } else if (false_label != (uint32_t)-1) {
                                        |       jmp =>false_label
                                }
                                if (op1_info & MAY_BE_ANY) {
-                                       if (false_label == (uint32_t)-1) {
+                                       if (exit_addr) {
+                                               if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                                       |       jmp >9
+                                               }
+                                       } else if (false_label == (uint32_t)-1) {
                                                |       jmp >9
                                        }
                                        |.code
@@ -6309,7 +7140,13 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                        }
 
                        if (!jmp_done) {
-                               if (false_label != (uint32_t)-1) {
+                               if (exit_addr) {
+                                       if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                               |       jmp >9
+                                       } else if (op1_info & MAY_BE_LONG) {
+                                               |       jmp &exit_addr
+                                       }
+                               } else if (false_label != (uint32_t)-1) {
                                        |       jmp =>false_label
                                } else if (op1_info & MAY_BE_LONG) {
                                        |       jmp >9
@@ -6339,7 +7176,13 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                        }
                        |       SET_ZVAL_TYPE_INFO res_addr, eax
                }
-               if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
+               if (exit_addr) {
+                       if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                               |       jne &exit_addr
+                       } else {
+                               |       je &exit_addr
+                       }
+               } else if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
                        if (true_label != (uint32_t)-1) {
                                |       jne =>true_label
                                if (false_label != (uint32_t)-1) {
@@ -6360,7 +7203,20 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                |       SSE_AVX_OP ucomisd, vucomisd, ZREG_XMM0, op1_addr
 
                if (set_bool) {
-                       if (false_label != (uint32_t)-1) { // JMPZ_EX
+                       if (exit_addr) {
+                               if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                       |       jp >1
+                                       |       SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
+                                       |       jne &exit_addr
+                                       |1:
+                                       |       SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
+                               } else {
+                                       |       SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
+                                       |       jp &exit_addr
+                                       |       je &exit_addr
+                                       |       SET_ZVAL_TYPE_INFO res_addr, IS_TRUE
+                               }
+                       } else if (false_label != (uint32_t)-1) { // JMPZ_EX
                                |       SET_ZVAL_TYPE_INFO res_addr, IS_FALSE
                                |       jp >1
                                |       je => false_label
@@ -6391,20 +7247,31 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                        }
                } else {
                        ZEND_ASSERT(true_label != (uint32_t)-1 || false_label != (uint32_t)-1);
-                       if (false_label != (uint32_t)-1) {
-                               |       jp =>false_label
+                       if (exit_addr) {
+                               if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                       |       jp >1
+                                       |       jne &exit_addr
+                                       |1:
+                               } else {
+                                       |       jp &exit_addr
+                                       |       je &exit_addr
+                               }
                        } else {
-                               |       jp >1
-                       }
-                       if (true_label != (uint32_t)-1) {
-                               |       jne =>true_label
                                if (false_label != (uint32_t)-1) {
-                                       |       jmp =>false_label
+                                       |       jp =>false_label
+                               } else {
+                                       |       jp >1
                                }
-                       } else {
-                               |       je =>false_label
+                               if (true_label != (uint32_t)-1) {
+                                       |       jne =>true_label
+                                       if (false_label != (uint32_t)-1) {
+                                               |       jmp =>false_label
+                                       }
+                               } else {
+                                       |       je =>false_label
+                               }
+                               |1:
                        }
-                       |1:
                }
        } else if (op1_info & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG))) {
                if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
@@ -6443,7 +7310,14 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                                |       add eax, 2
                        }
                        |       SET_ZVAL_TYPE_INFO res_addr, eax
-                       if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
+                       if (exit_addr) {
+                               |       CMP_ZVAL_TYPE res_addr, IS_FALSE
+                               if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                       |       jne &exit_addr
+                               } else {
+                                       |       je &exit_addr
+                               }
+                       } else if (true_label != (uint32_t)-1 || false_label != (uint32_t)-1) {
                                |       CMP_ZVAL_TYPE res_addr, IS_FALSE
                                if (true_label != (uint32_t)-1) {
                                        |       jne =>true_label
@@ -6462,7 +7336,19 @@ static int zend_jit_bool_jmpznz(dasm_State **Dst, const zend_op *opline, const z
                        }
                } else {
                        |       test r0, r0
-                       if (true_label != (uint32_t)-1) {
+                       if (exit_addr) {
+                               if (branch_opcode == ZEND_JMPNZ || branch_opcode == ZEND_JMPNZ_EX) {
+                                       |       jne &exit_addr
+                                       if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
+                                               |       jmp >9
+                                       }
+                               } else {
+                                       |       je &exit_addr
+                                       if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)) {
+                                               |       jmp >9
+                                       }
+                               }
+                       } else if (true_label != (uint32_t)-1) {
                                |       jne =>true_label
                                if (false_label != (uint32_t)-1) {
                                        |       jmp =>false_label
@@ -6533,7 +7419,7 @@ static int zend_jit_assign(dasm_State **Dst, const zend_op *opline, const zend_o
        return 1;
 }
 
-static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_function *func)
+static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_function *func, const void *exit_addr)
 {
        uint32_t used_stack;
 
@@ -6572,27 +7458,32 @@ static int zend_jit_push_call_frame(dasm_State **Dst, const zend_op *opline, con
        } else {
                |       cmp r2, FCARG1a
        }
-       |       jb >1
-       |       // EG(vm_stack_top) = (zval*)((char*)call + used_stack);
-       |.cold_code
-       |1:
-       |       SAVE_VALID_OPLINE opline
-       if (func) {
-               |       mov FCARG1d, used_stack
-       }
+
+       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+               |       jb &exit_addr
+       } else {
+               |       jb >1
+               |       // EG(vm_stack_top) = (zval*)((char*)call + used_stack);
+               |.cold_code
+               |1:
+               |       SAVE_VALID_OPLINE opline
+               if (func) {
+                       |       mov FCARG1d, used_stack
+               }
 #ifdef _WIN32
-       if (0) {
+               if (0) {
 #else
-       if (func && func->type == ZEND_INTERNAL_FUNCTION) {
+               if (func && func->type == ZEND_INTERNAL_FUNCTION) {
 #endif
-               |       EXT_CALL zend_jit_int_extend_stack_helper, r0
-       } else {
-               |       mov FCARG2a, r0
-               |       EXT_CALL zend_jit_extend_stack_helper, r0
-       }
-       |       mov RX, r0
-       |       jmp >1
-       |.code
+                       |       EXT_CALL zend_jit_int_extend_stack_helper, r0
+               } else {
+                       |       mov FCARG2a, r0
+                       |       EXT_CALL zend_jit_extend_stack_helper, r0
+               }
+               |       mov RX, r0
+               |       jmp >1
+               |.code
+       }
 
        if (func) {
                |       MEM_OP2_1_ZTS add, aword, executor_globals, vm_stack_top, used_stack, r2
@@ -6730,11 +7621,83 @@ static int zend_jit_needs_call_chain(zend_call_info *call_info, uint32_t b, cons
        }
 }
 
-static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t b, const zend_op_array *op_array, zend_ssa *ssa, int call_level)
+static int zend_jit_init_fcall_guard(dasm_State **Dst, const zend_op *opline, const zend_function *func)
+{
+       int32_t exit_point;
+       const void *exit_addr;
+
+       if (func->type == ZEND_INTERNAL_FUNCTION) {
+#ifdef ZEND_WIN32
+               // TODO: ASLR may cause different addresses in different workers ???
+               return 0;
+#endif
+       } else if (func->type == ZEND_USER_FUNCTION) {
+               if (!zend_accel_in_shm(func->op_array.opcodes)) {
+                       /* op_array and op_array->opcodes are not persistent. We can't link. */
+                       return 0;
+               }
+       } else {
+               ZEND_ASSERT(0);
+               return 0;
+       }
+
+       exit_point = zend_jit_trace_get_exit_point(opline, opline ? (opline+1) : NULL, NULL);
+       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+       if (!exit_addr) {
+               return 0;
+       }
+
+       if (func->type == ZEND_USER_FUNCTION
+        && !(func->common.fn_flags & ZEND_ACC_IMMUTABLE)) {
+               const zend_op *opcodes = func->op_array.opcodes;
+
+               |       // call = EX(call);
+               |       mov r1, EX->call
+               |       mov r1, aword EX:r1->func
+               |   .if X64
+               ||              if (!IS_SIGNED_32BIT(opcodes)) {
+               |                       mov64 r0, ((ptrdiff_t)opcodes)
+               |                       cmp aword [r1 + offsetof(zend_op_array, opcodes)], r0
+               ||              } else {
+               |                       cmp aword [r1 + offsetof(zend_op_array, opcodes)], opcodes
+               ||              }
+               |       .else
+               |               cmp aword [r1 + offsetof(zend_op_array, opcodes)], opcodes
+               |       .endif
+               |       jne &exit_addr
+       } else {
+               |       // call = EX(call);
+               |       mov r1, EX->call
+               |   .if X64
+               ||              if (!IS_SIGNED_32BIT(func)) {
+               |                       mov64 r0, ((ptrdiff_t)func)
+               |                       cmp aword EX:r1->func, r0
+               ||              } else {
+               |                       cmp aword EX:r1->func, func
+               ||              }
+               |       .else
+               |               cmp aword EX:r1->func, func
+               |       .endif
+               |       jne &exit_addr
+       }
+
+       return 1;
+}
+
+static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t b, const zend_op_array *op_array, zend_ssa *ssa, int call_level, zend_jit_trace_rec *trace)
 {
        zend_func_info *info = ZEND_FUNC_INFO(op_array);
        zend_call_info *call_info = NULL;
        zend_function *func = NULL;
+       const void *exit_addr = NULL;
+
+       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+               int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+               exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+               if (!exit_addr) {
+                       return 0;
+               }
+       }
 
        if (delayed_call_chain) {
                if (!zend_jit_save_call_chain(Dst, delayed_call_level)) {
@@ -6752,6 +7715,14 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t
                }
        }
 
+       if (!func
+        && trace
+        && trace->op == ZEND_JIT_TRACE_INIT_CALL
+        && (opline->opcode == ZEND_INIT_FCALL || opline->opcode == ZEND_INIT_FCALL_BY_NAME)) {
+               /* TODO: add guard ??? */
+               func = (zend_function*)trace->func;
+       }
+
 #ifdef _WIN32
        if (0) {
 #else
@@ -6791,19 +7762,23 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t
                        |       mov aword [r1 + opline->result.num], r0
                        |       test r0, r0
                        |       jnz >3
-                       |       // SAVE_OPLINE();
-                       |       SAVE_VALID_OPLINE opline
-                       |       jmp ->undefined_function
+                       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                               |       jmp &exit_addr
+                       } else {
+                               |       // SAVE_OPLINE();
+                               |       SAVE_VALID_OPLINE opline
+                               |       jmp ->undefined_function
+                       }
                }
                |.code
                |3:
        }
 
-       if (!zend_jit_push_call_frame(Dst, opline, op_array, func)) {
+       if (!zend_jit_push_call_frame(Dst, opline, op_array, func, exit_addr)) {
                return 0;
        }
 
-       if (zend_jit_needs_call_chain(call_info, b, op_array, ssa, opline)) {
+       if (trace || zend_jit_needs_call_chain(call_info, b, op_array, ssa, opline)) {
                if (!zend_jit_save_call_chain(Dst, call_level)) {
                        return 0;
                }
@@ -6815,7 +7790,7 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t
        return 1;
 }
 
-static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ssa, zend_call_info *call_info)
+static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ssa, const zend_call_info *call_info)
 {
        uint32_t num_args = 0;
        zend_function *func = call_info->callee_func;
@@ -6839,13 +7814,15 @@ static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ss
        return num_args;
 }
 
-static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, int call_level, unsigned int next_block)
+static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, int call_level, unsigned int next_block, zend_jit_trace_rec *trace)
 {
        zend_func_info *info = ZEND_FUNC_INFO(op_array);
        zend_call_info *call_info = NULL;
-       zend_function *func = NULL;
+       const zend_function *func = NULL;
        uint32_t i;
        zend_jit_addr res_addr;
+       uint32_t call_num_args = 0;
+       zend_bool unknown_num_args = 0;
 
        if (RETURN_VALUE_USED(opline)) {
                res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
@@ -6859,6 +7836,10 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
 #endif
        }
 
+       if ((opline-1)->opcode == ZEND_SEND_UNPACK|| (opline-1)->opcode == ZEND_SEND_ARRAY) {
+               unknown_num_args = 1;
+       }
+
        if (info) {
                call_info = info->callee_info;
                while (call_info && call_info->caller_call_opline != opline) {
@@ -6872,25 +7853,51 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                /* resolve function ar run time */
        } else if (func->type == ZEND_USER_FUNCTION) {
                ZEND_ASSERT(opline->opcode != ZEND_DO_ICALL);
-               if (call_info->num_args > func->op_array.num_args ||
-                   (opline-1)->opcode == ZEND_SEND_UNPACK ||
-                   (opline-1)->opcode == ZEND_SEND_ARRAY) {
-                       goto fallback;
-               }
+               call_num_args = call_info->num_args;
        } else if (func->type == ZEND_INTERNAL_FUNCTION) {
                ZEND_ASSERT(opline->opcode != ZEND_DO_UCALL);
+               call_num_args = call_info->num_args;
 #if ZEND_DEBUG
                if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
-                       goto fallback;
+                       // TODO: Mow most internal functions have type hints ???
+                       if (!trace) {
+                               goto fallback;
+                       }
                }
 #endif
-               if ((opline-1)->opcode == ZEND_SEND_UNPACK || (opline-1)->opcode == ZEND_SEND_ARRAY) {
-                       goto fallback;
-               }
        } else {
                ZEND_ASSERT(0);
        }
 
+       if (trace && !func) {
+               if (trace->op == ZEND_JIT_TRACE_DO_ICALL) {
+                       ZEND_ASSERT(trace->func->type == ZEND_INTERNAL_FUNCTION);
+#ifndef ZEND_WIN32
+                       // TODO: ASLR may cause different addresses in different workers ???
+                       func = trace->func;
+                       if (JIT_G(current_frame) &&
+                           JIT_G(current_frame)->call &&
+                           JIT_G(current_frame)->call->num_args >= 0) {
+                               call_num_args = JIT_G(current_frame)->call->num_args;
+                       } else {
+                               unknown_num_args = 1;
+                       }
+#endif
+               } else if (trace->op == ZEND_JIT_TRACE_ENTER) {
+                       ZEND_ASSERT(trace->func->type == ZEND_USER_FUNCTION);
+                       if (zend_accel_in_shm(trace->func->op_array.opcodes)) {
+                               func = trace->func;
+                               if (JIT_G(current_frame) &&
+                                   JIT_G(current_frame)->call &&
+                                   JIT_G(current_frame)->call->num_args >= 0) {
+                                       call_num_args = JIT_G(current_frame)->call->num_args;
+                               } else {
+                                       unknown_num_args = 1;
+                               }
+                       }
+               }
+       }
+
        if (!reuse_ip) {
                zend_jit_start_reuse_ip();
                |       // call = EX(call);
@@ -6923,20 +7930,31 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
 
        if (opline->opcode == ZEND_DO_FCALL) {
                if (!func) {
-                       |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
-                       |       jnz >1
-                       |.cold_code
-                       |1:
-                       if (!GCC_GLOBAL_REGS) {
-                               |       mov FCARG1a, RX
+                       if (trace) {
+                               uint32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                               const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+                               if (!exit_addr) {
+                                       return 0;
+                               }
+                               |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
+                               |       jnz &exit_addr
+                       } else {
+                               |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
+                               |       jnz >1
+                               |.cold_code
+                               |1:
+                               if (!GCC_GLOBAL_REGS) {
+                                       |       mov FCARG1a, RX
+                               }
+                               |       EXT_CALL zend_jit_deprecated_helper, r0
+                               |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
+                               |       jne ->exception_handler
+                               |       mov r0, EX:RX->func // reload
+                               |       jmp >1
+                               |.code
+                               |1:
                        }
-                       |       EXT_CALL zend_jit_deprecated_helper, r0
-                       |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
-                       |       jne ->exception_handler
-                       |       mov r0, EX:RX->func // reload
-                       |       jmp >1
-                       |.code
-                       |1:
                } else if (func->common.fn_flags & ZEND_ACC_DEPRECATED) {
                        if (!GCC_GLOBAL_REGS) {
                                |       mov FCARG1a, RX
@@ -6944,7 +7962,6 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                        |       EXT_CALL zend_jit_deprecated_helper, r0
                        |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
                        |       jne ->exception_handler
-                       |       mov r0, EX:RX->func // reload
                }
        }
 
@@ -6969,8 +7986,10 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                        |       mov aword EX:RX->return_value, 0
                }
 
-               if (func) {
-                       for (i = call_info->num_args; i < func->op_array.last_var; i++) {
+               if (func
+                && !unknown_num_args
+                && call_num_args <= func->op_array.num_args) {
+                       for (i = call_num_args; i < func->op_array.last_var; i++) {
                                uint32_t n = EX_NUM_TO_VAR(i);
                                |       SET_Z_TYPE_INFO RX + n, IS_UNDEF
                        }
@@ -6980,7 +7999,7 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                if (!func || func->op_array.cache_size) {
                        if (func && op_array == &func->op_array) {
                                /* recursive call */
-                               if (func->op_array.cache_size > sizeof(void*)) {
+                               if (trace || func->op_array.cache_size > sizeof(void*)) {
                                        |       mov r2, EX->run_time_cache
                                        |       mov EX:RX->run_time_cache, r2
                                }
@@ -7009,13 +8028,16 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                |       mov FP, RX
 
                |       // opline = op_array->opcodes;
-               if (func) {
+               if (func
+                && !unknown_num_args
+                && call_num_args <= func->op_array.num_args) {
                        uint32_t num_args;
 
-                       if (func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
+                       if ((func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0
+                         && call_info) {
                                num_args = skip_valid_arguments(op_array, ssa, call_info);
                        } else {
-                               num_args = call_info->num_args;
+                               num_args = call_num_args;
                        }
                        if (func && zend_accel_in_shm(func->op_array.opcodes)) {
                                |       LOAD_IP_ADDR (func->op_array.opcodes + num_args)
@@ -7037,7 +8059,7 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                                }
                        }
 
-                       if (op_array == &func->op_array) {
+                       if (!trace && op_array == &func->op_array) {
                                /* recursive call */
 #ifdef CONTEXT_THREADED_JIT
                                |       call >1
@@ -7051,45 +8073,83 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
 #endif
                                return 1;
                        }
+               } else if (func
+                && !unknown_num_args
+                && call_num_args > func->op_array.num_args) {
+                       if (func && zend_accel_in_shm(func->op_array.opcodes)) {
+                               |       LOAD_IP_ADDR (func->op_array.opcodes)
+                       } else if (GCC_GLOBAL_REGS) {
+                               |       mov IP, aword [r0 + offsetof(zend_op_array, opcodes)]
+                       } else {
+                               |       mov FCARG1a, aword [r0 + offsetof(zend_op_array, opcodes)]
+                               |       mov aword EX->opline, FCARG1a
+                       }
+                       if (!GCC_GLOBAL_REGS) {
+                               |       mov FCARG1a, FP
+                       }
+                       |       EXT_CALL zend_jit_copy_extra_args_helper, r0
+                       for (i = call_num_args; i < func->op_array.last_var; i++) {
+                               uint32_t n = EX_NUM_TO_VAR(i);
+                               |       SET_Z_TYPE_INFO FP + n, IS_UNDEF
+                       }
                } else {
                        |       // opline = op_array->opcodes
-                       if (GCC_GLOBAL_REGS) {
+                       if (func && zend_accel_in_shm(func->op_array.opcodes)) {
+                               |       LOAD_IP_ADDR (func->op_array.opcodes)
+                       } else if (GCC_GLOBAL_REGS) {
                                |       mov IP, aword [r0 + offsetof(zend_op_array, opcodes)]
                        } else {
                                |       mov FCARG1a, aword [r0 + offsetof(zend_op_array, opcodes)]
                                |       mov aword EX->opline, FCARG1a
                        }
-                       |       // first_extra_arg = op_array->num_args;
-                       |       mov edx, dword [r0 + offsetof(zend_op_array, num_args)]
-                       |       // num_args = EX_NUM_ARGS();
-                       |       mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)]
-                       |       //      if (UNEXPECTED(num_args > first_extra_arg))
-                       |       cmp edx, ecx
-                       |       jl >1
+                       if (func) {
+                               |       // num_args = EX_NUM_ARGS();
+                               |       mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)]
+                               |       //      if (UNEXPECTED(num_args > first_extra_arg))
+                               |       cmp ecx, (func->op_array.num_args)
+                       } else {
+                               |       // first_extra_arg = op_array->num_args;
+                               |       mov edx, dword [r0 + offsetof(zend_op_array, num_args)]
+                               |       // num_args = EX_NUM_ARGS();
+                               |       mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)]
+                               |       //      if (UNEXPECTED(num_args > first_extra_arg))
+                               |       cmp ecx, edx
+                       }
+                       |       jg >1
                        |.cold_code
                        |1:
                        if (!GCC_GLOBAL_REGS) {
                                |       mov FCARG1a, FP
                        }
                        |       EXT_CALL zend_jit_copy_extra_args_helper, r0
-                       |       mov r0, EX->func // reload
+                       if (!func) {
+                               |       mov r0, EX->func // reload
+                       }
                        |       mov ecx, dword [FP + offsetof(zend_execute_data, This.u2.num_args)] // reload
                        |       jmp >1
                        |.code
-                       |       // if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0))
-                       |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_HAS_TYPE_HINTS
-                       |       jnz >1
-                       |       // opline += num_args;
-                       |.if X64
-                               |       movsxd r2, ecx
-                               |       imul r2, r2, sizeof(zend_op)
-                       |.else
-                               |       imul r2, ecx, sizeof(zend_op)
-                       |.endif
-                       |       ADD_IP r2
+                       if (!func || (func->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0) {
+                               if (!func) {
+                                       |       // if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0))
+                                       |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_HAS_TYPE_HINTS
+                                       |       jnz >1
+                               }
+                               |       // opline += num_args;
+                               |.if X64
+                                       |       movsxd r2, ecx
+                                       |       imul r2, r2, sizeof(zend_op)
+                               |.else
+                                       |       imul r2, ecx, sizeof(zend_op)
+                               |.endif
+                               |       ADD_IP r2
+                       }
                        |1:
                        |       // if (EXPECTED((int)num_args < op_array->last_var)) {
-                       |       mov edx, dword [r0 + offsetof(zend_op_array, last_var)]
+                       if (func) {
+                               |       mov edx, (func->op_array.last_var)
+                       } else {
+                               |       mov edx, dword [r0 + offsetof(zend_op_array, last_var)]
+                       }
                        |       sub edx, ecx
                        |       jle >3 //???
                        |       // zval *var = EX_VAR_NUM(num_args);
@@ -7106,24 +8166,34 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                        |3:
                }
 
+               if (trace) {
+                       if (!func && (opline->opcode != ZEND_DO_UCALL)) {
+                               |       jmp >9
+                       }
+               } else {
 #ifdef CONTEXT_THREADED_JIT
-               |       call ->context_threaded_call
-               if (!func && (opline->opcode != ZEND_DO_UCALL)) {
-                       |       jmp >9
-               }
+                       |       call ->context_threaded_call
+                       if (!func && (opline->opcode != ZEND_DO_UCALL)) {
+                               |       jmp >9
+                       }
+                       |       call ->context_threaded_call
+                       if (!func) {
+                               |       jmp >9
+                       }
 #else
-               if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
-                       |       add r4, HYBRID_SPAD
-                       |       JMP_IP
-               } else if (GCC_GLOBAL_REGS) {
-                       |       add r4, SPAD // stack alignment
-                       |       JMP_IP
-               } else {
-                       |       mov FP, aword T2 // restore FP
-                       |       mov RX, aword T3 // restore IP
-                       |       add r4, NR_SPAD // stack alignment
-                       |       mov r0, 1 // ZEND_VM_ENTER
-                       |       ret
+                       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+                               |       add r4, HYBRID_SPAD
+                               |       JMP_IP
+                       } else if (GCC_GLOBAL_REGS) {
+                               |       add r4, SPAD // stack alignment
+                               |       JMP_IP
+                       } else {
+                               |       mov FP, aword T2 // restore FP
+                               |       mov RX, aword T3 // restore IP
+                               |       add r4, NR_SPAD // stack alignment
+                               |       mov r0, 1 // ZEND_VM_ENTER
+                               |       ret
+                       }
                }
 #endif
        }
@@ -7135,20 +8205,31 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                }
                if (opline->opcode == ZEND_DO_FCALL_BY_NAME) {
                        if (!func) {
-                               |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
-                               |       jnz >1
-                               |.cold_code
-                               |1:
-                               if (!GCC_GLOBAL_REGS) {
-                                       |       mov FCARG1a, RX
+                               if (trace) {
+                                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                                       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+                                       if (!exit_addr) {
+                                               return 0;
+                                       }
+                                       |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
+                                       |       jnz &exit_addr
+                               } else {
+                                       |       test dword [r0 + offsetof(zend_op_array, fn_flags)], ZEND_ACC_DEPRECATED
+                                       |       jnz >1
+                                               |.cold_code
+                                       |1:
+                                       if (!GCC_GLOBAL_REGS) {
+                                               |       mov FCARG1a, RX
+                                       }
+                                       |       EXT_CALL zend_jit_deprecated_helper, r0
+                                       |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
+                                       |       jne ->exception_handler
+                                       |       mov r0, EX:RX->func // reload
+                                       |       jmp >1
+                                       |.code
+                                       |1:
                                }
-                               |       EXT_CALL zend_jit_deprecated_helper, r0
-                               |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
-                               |       jne ->exception_handler
-                               |       mov r0, EX:RX->func // reload
-                               |       jmp >1
-                               |.code
-                               |1:
                        } else if (func->common.fn_flags & ZEND_ACC_DEPRECATED) {
                                if (!GCC_GLOBAL_REGS) {
                                        |       mov FCARG1a, RX
@@ -7183,12 +8264,23 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                        |       call aword [r0 + offsetof(zend_internal_function, handler)]
                }
 
+               if (trace) {
+                       // TODO: This is a qucik dirty fix ??????
+                       //
+                       // Internal function may call another trace that,
+                       // replaces EG(trace_id) and the following side exit
+                       // from this trace is going to be mad !!!!!!
+                       //
+                       // Lets set EG(trace_id) once again...
+                       zend_jit_trace_begin(Dst, current_trace_num);
+               }
+
                |       // EG(current_execute_data) = execute_data;
                |       MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, FP, r0
 
                |       // zend_vm_stack_free_args(call);
-               if (func) {
-                       for (i = 0; i < call_info->num_args; i++ ) {
+               if (func && call_info && !unknown_num_args) {
+                       for (i = 0; i < call_num_args; i++ ) {
                                uint32_t offset = EX_NUM_TO_VAR(i);
                                |       ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_RX, offset), MAY_BE_ANY|MAY_BE_RC1|MAY_BE_RCN, 0, 1, 0, opline
                        }
@@ -7213,15 +8305,21 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
                        |2:
                }
 
-               |       // zend_vm_stack_free_call_frame(call);
-               |       test byte [RX + offsetof(zend_execute_data, This.u1.type_info) + 2], (ZEND_CALL_ALLOCATED >> 16)
-               |       jnz >1
-               |.cold_code
-               |1:
-               |       mov FCARG1a, RX
-               |       EXT_CALL zend_jit_free_call_frame, r0
-               |       jmp >1
-               |.code
+               if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE ||
+                   !JIT_G(current_frame) ||
+                   !JIT_G(current_frame)->call ||
+                   !JIT_G(current_frame)->call->nested) {
+
+                       |       // zend_vm_stack_free_call_frame(call);
+                       |       test byte [RX + offsetof(zend_execute_data, This.u1.type_info) + 2], (ZEND_CALL_ALLOCATED >> 16)
+                       |       jnz >1
+                       |.cold_code
+                       |1:
+                       |       mov FCARG1a, RX
+                       |       EXT_CALL zend_jit_free_call_frame, r0
+                       |       jmp >1
+                       |.code
+               }
                |       MEM_OP2_1_ZTS mov, aword, executor_globals, vm_stack_top, RX, r0
                |1:
 
@@ -7261,9 +8359,12 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
        }
 
        return 1;
-
+#if ZEND_DEBUG
 fallback:
        /* fallback to subroutine threading */
+       if (trace) {
+               return zend_jit_trace_handler(Dst, op_array, opline, zend_may_throw(opline, op_array, ssa), trace);
+       }
        if (opline->opcode == ZEND_DO_FCALL ||
            opline->opcode == ZEND_DO_UCALL ||
            opline->opcode == ZEND_DO_FCALL_BY_NAME ){
@@ -7271,6 +8372,7 @@ fallback:
        } else {
                return zend_jit_handler(Dst, opline, zend_may_throw(opline, op_array, ssa));
        }
+#endif
 }
 
 static int zend_jit_send_val(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr)
@@ -7289,18 +8391,36 @@ static int zend_jit_send_val(dasm_State **Dst, const zend_op *opline, const zend
        if (opline->opcode == ZEND_SEND_VAL_EX) {
                uint32_t mask = ZEND_SEND_BY_REF << ((arg_num + 3) * 2);
 
-               |       mov r0, EX:RX->func
-               if (arg_num <= MAX_ARG_FLAG_NUM) {
+               ZEND_ASSERT(arg_num <= MAX_ARG_FLAG_NUM);
+
+               if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE
+                && JIT_G(current_frame)
+                && JIT_G(current_frame)->call
+                && JIT_G(current_frame)->call->func) {
+                       if (ARG_MUST_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
+                               /* Don't generate code that always throws exception */
+                               return 0;
+                       }
+               } else if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                       int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                       if (!exit_addr) {
+                               return 0;
+                       }
+                       |       mov r0, EX:RX->func
                        |       test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
-                       |       jnz     >1
+                       |       jnz     &exit_addr
                } else {
-                       ZEND_ASSERT(0);
+                       |       mov r0, EX:RX->func
+                       |       test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
+                       |       jnz     >1
+                       |.cold_code
+                       |1:
+                       |       SAVE_VALID_OPLINE opline
+                       |       jmp ->throw_cannot_pass_by_ref
+                       |.code
+
                }
-               |.cold_code
-               |1:
-               |       SAVE_VALID_OPLINE opline
-               |       jmp ->throw_cannot_pass_by_ref
-               |.code
        }
 
        arg_addr = ZEND_ADDR_MEM_ZVAL(ZREG_RX, opline->result.var);
@@ -7308,12 +8428,12 @@ static int zend_jit_send_val(dasm_State **Dst, const zend_op *opline, const zend
        if (opline->op1_type == IS_CONST) {
                zval *zv = RT_CONSTANT(opline, opline->op1);
 
-               |       ZVAL_COPY_CONST arg_addr, -1, -1, zv, r0
+               |       ZVAL_COPY_CONST arg_addr, MAY_BE_ANY, MAY_BE_ANY, zv, r0
                if (Z_REFCOUNTED_P(zv)) {
                        |       ADDREF_CONST zv, r0
                }
        } else {
-               |       ZVAL_COPY_VALUE arg_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_R2
+               |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
        }
 
        return 1;
@@ -7382,11 +8502,11 @@ static int zend_jit_send_ref(dasm_State **Dst, const zend_op *opline, const zend
                        zend_jit_addr val_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R1, 0);
 
                        |       mov r1, aword T1 // restore
-                       |       ZVAL_COPY_VALUE ref_addr, -1, val_addr, op1_info, ZREG_R2, ZREG_R2
+                       |       ZVAL_COPY_VALUE ref_addr, MAY_BE_ANY, val_addr, op1_info, ZREG_R2, ZREG_R2
                        |       SET_ZVAL_PTR val_addr, r0
                        |       SET_ZVAL_TYPE_INFO val_addr, IS_REFERENCE_EX
                } else {
-                       |       ZVAL_COPY_VALUE ref_addr, -1, op1_addr, op1_info, ZREG_R1, ZREG_R2
+                       |       ZVAL_COPY_VALUE ref_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
                        |       SET_ZVAL_PTR op1_addr, r0
                        |       SET_ZVAL_TYPE_INFO op1_addr, IS_REFERENCE_EX
                }
@@ -7418,46 +8538,93 @@ static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend
                |       mov RX, EX->call
        }
 
-       if (opline->opcode == ZEND_SEND_VAR_EX || opline->opcode == ZEND_SEND_VAR_NO_REF_EX) {
-               uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
+       if (opline->opcode == ZEND_SEND_VAR_EX) {
+               if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE
+                && JIT_G(current_frame)
+                && JIT_G(current_frame)->call
+                && JIT_G(current_frame)->call->func) {
+                       if (ARG_SHOULD_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
+                               if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 0)) {
+                                       return 0;
+                               }
+                               return 1;
+                       }
+               } else {
+                       uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
 
-               |       mov r0, EX:RX->func
-               if (arg_num <= MAX_ARG_FLAG_NUM) {
+                       |       mov r0, EX:RX->func
                        |       test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
                        |       jnz     >1
-               } else {
-                       ZEND_ASSERT(0);
+                       |.cold_code
+                       |1:
+                       if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 1)) {
+                               return 0;
+                       }
+                       |       jmp >7
+                       |.code
                }
+       } else if (opline->opcode == ZEND_SEND_VAR_NO_REF_EX) {
+               if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE
+                && JIT_G(current_frame)
+                && JIT_G(current_frame)->call
+                && JIT_G(current_frame)->call->func) {
+                       if (ARG_SHOULD_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
 
-               |.cold_code
-               |1:
+                               |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
 
-               if (opline->opcode == ZEND_SEND_VAR_EX) {
-                       if (!zend_jit_send_ref(Dst, opline, op_array, op1_info, 1)) {
-                               return 0;
+                               if (!ARG_MAY_BE_SENT_BY_REF(JIT_G(current_frame)->call->func, arg_num)) {
+                                       if (!(op1_info & MAY_BE_REF)) {
+                                               /* Don't generate code that always throws exception */
+                                               return 0;
+                                       } else {
+                                               int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                                               const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                                               if (!exit_addr) {
+                                                       return 0;
+                                               }
+                                               |       cmp cl, IS_REFERENCE
+                                               |       jne &exit_addr
+                                       }
+                               }
+                               return 1;
                        }
-               } else if (opline->opcode == ZEND_SEND_VAR_NO_REF_EX) {
+               } else {
+                       uint32_t mask = (ZEND_SEND_BY_REF|ZEND_SEND_PREFER_REF) << ((arg_num + 3) * 2);
+
+                       |       mov r0, EX:RX->func
+                       |       test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
+                       |       jnz     >1
+                       |.cold_code
+                       |1:
+
                        mask = ZEND_SEND_PREFER_REF << ((arg_num + 3) * 2);
 
-                       |       ZVAL_COPY_VALUE arg_addr, -1, op1_addr, op1_info, ZREG_R1, ZREG_R2
+                       |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
                        if (op1_info & MAY_BE_REF) {
                                |       cmp cl, IS_REFERENCE
                                |       je >7
                        }
                        |       test dword [r0 + offsetof(zend_function, quick_arg_flags)], mask
                        |       jnz >7
-                       |       SAVE_VALID_OPLINE opline
-                       |       LOAD_ZVAL_ADDR FCARG1a, arg_addr
-                       |       EXT_CALL zend_jit_only_vars_by_reference, r0
-                       if (!zend_jit_check_exception(Dst)) {
-                               return 0;
+                       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                               int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                               const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                               if (!exit_addr) {
+                                       return 0;
+                               }
+                               |       jmp &exit_addr
+                       } else {
+                               |       SAVE_VALID_OPLINE opline
+                               |       LOAD_ZVAL_ADDR FCARG1a, arg_addr
+                               |       EXT_CALL zend_jit_only_vars_by_reference, r0
+                               if (!zend_jit_check_exception(Dst)) {
+                                       return 0;
+                               }
+                               |       jmp >7
                        }
-               } else {
-                       ZEND_ASSERT(0);
-               }
 
-               |       jmp >7
-               |.code
+                       |.code
+               }
        }
 
        if (op1_info & MAY_BE_UNDEF) {
@@ -7479,16 +8646,25 @@ static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend
        }
 
        if (opline->opcode == ZEND_SEND_VAR_NO_REF) {
-               |       ZVAL_COPY_VALUE arg_addr, -1, op1_addr, op1_info, ZREG_R1, ZREG_R2
+               |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R1, ZREG_R2
                if (op1_info & MAY_BE_REF) {
                        |       cmp cl, IS_REFERENCE
                        |       je >7
                }
-               |       SAVE_VALID_OPLINE opline
-               |       LOAD_ZVAL_ADDR FCARG1a, arg_addr
-               |       EXT_CALL zend_jit_only_vars_by_reference, r0
-               if (!zend_jit_check_exception(Dst)) {
-                       return 0;
+               if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                       int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                       if (!exit_addr) {
+                               return 0;
+                       }
+                       |       jmp &exit_addr
+               } else {
+                       |       SAVE_VALID_OPLINE opline
+                       |       LOAD_ZVAL_ADDR FCARG1a, arg_addr
+                       |       EXT_CALL zend_jit_only_vars_by_reference, r0
+                       if (!zend_jit_check_exception(Dst)) {
+                               return 0;
+                       }
                }
        } else if (op1_info & (MAY_BE_ANY|MAY_BE_REF)) {
                if (op1_info & MAY_BE_REF) {
@@ -7497,7 +8673,7 @@ static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend
 
                                |       LOAD_ZVAL_ADDR FCARG1a, op1_addr
                                |       ZVAL_DEREF FCARG1a, op1_info
-                               |       ZVAL_COPY_VALUE arg_addr, -1, val_addr, op1_info, ZREG_R0, ZREG_R2
+                               |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, val_addr, op1_info, ZREG_R0, ZREG_R2
                                |       TRY_ADDREF op1_info, ah, r2
                        } else {
                                zend_jit_addr ref_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 8);
@@ -7508,7 +8684,7 @@ static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend
                                |       // zend_refcounted *ref = Z_COUNTED_P(retval_ptr);
                                |       GET_ZVAL_PTR FCARG1a, op1_addr
                                |       // ZVAL_COPY_VALUE(return_value, &ref->value);
-                               |       ZVAL_COPY_VALUE arg_addr, -1, ref_addr, op1_info, ZREG_R0, ZREG_R2
+                               |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, ref_addr, op1_info, ZREG_R0, ZREG_R2
                                |       GC_DELREF FCARG1a
                                |       je >1
                                |       IF_NOT_REFCOUNTED ah, >2
@@ -7518,7 +8694,7 @@ static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend
                                |       EFREE_REG_24 op_array, opline
                                |       jmp >2
                                |.code
-                               |       ZVAL_COPY_VALUE arg_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_R2
+                               |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
                                |2:
                        }
                } else {
@@ -7530,7 +8706,7 @@ static int zend_jit_send_var(dasm_State **Dst, const zend_op *opline, const zend
                                        op1_addr= op1_def_addr;
                                }
                        }
-                       |       ZVAL_COPY_VALUE arg_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_R2
+                       |       ZVAL_COPY_VALUE arg_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
                        if (opline->op1_type == IS_CV) {
                                |       TRY_ADDREF op1_info, ah, r2
                        }
@@ -7593,14 +8769,14 @@ static int zend_jit_smart_false(dasm_State **Dst, const zend_op *opline, int jmp
        return 1;
 }
 
-static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        uint32_t defined_label = (uint32_t)-1;
        uint32_t undefined_label = (uint32_t)-1;
        zval *zv = RT_CONSTANT(opline, opline->op1);
-       zend_jit_addr res_addr;
+       zend_jit_addr res_addr = 0;
 
-       if (smart_branch_opcode) {
+       if (smart_branch_opcode && !exit_addr) {
                if (smart_branch_opcode == ZEND_JMPZ) {
                        undefined_label = target_label;
                } else if (smart_branch_opcode == ZEND_JMPNZ) {
@@ -7625,8 +8801,15 @@ static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, const zend_
        |       MEM_OP2_2_ZTS mov, FCARG1a, aword, executor_globals, zend_constants, FCARG1a
        |       shr     r0, 1
        |       cmp dword [FCARG1a + offsetof(HashTable, nNumOfElements)], eax
+
        if (smart_branch_opcode) {
-               if (undefined_label != (uint32_t)-1) {
+               if (exit_addr) {
+                       if (smart_branch_opcode == ZEND_JMPZ) {
+                               |       jz &exit_addr
+                       } else {
+                               |       jz >3
+                       }
+               } else if (undefined_label != (uint32_t)-1) {
                        |       jz =>undefined_label
                } else {
                        |       jz >3
@@ -7639,7 +8822,14 @@ static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, const zend_
        |       LOAD_ADDR FCARG1a, zv
        |       EXT_CALL zend_jit_check_constant, r0
        |       test r0, r0
-       if (smart_branch_opcode) {
+       if (exit_addr) {
+               if (smart_branch_opcode == ZEND_JMPNZ) {
+                       |       jnz >3
+               } else {
+                       |       jz >3
+               }
+               |       jmp &exit_addr
+       } else if (smart_branch_opcode) {
                if (undefined_label != (uint32_t)-1) {
                        |       jnz =>undefined_label
                } else {
@@ -7659,7 +8849,11 @@ static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, const zend_
        }
        |.code
        if (smart_branch_opcode) {
-               if (defined_label != (uint32_t)-1) {
+               if (exit_addr) {
+                       if (smart_branch_opcode == ZEND_JMPNZ) {
+                               |       jmp &exit_addr
+                       }
+               } else if (defined_label != (uint32_t)-1) {
                        |       jmp =>defined_label
                }
        } else {
@@ -7671,7 +8865,7 @@ static int zend_jit_defined(dasm_State **Dst, const zend_op *opline, const zend_
        return 1;
 }
 
-static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        uint32_t  mask;
        zend_uchar type;
@@ -7691,11 +8885,23 @@ static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const ze
                |       EXT_CALL zend_jit_undefined_op_helper, r0
                zend_jit_check_exception_undef_result(Dst, opline);
                if (opline->extended_value & MAY_BE_NULL) {
-                       if (!zend_jit_smart_true(Dst, opline, (op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0, smart_branch_opcode, target_label, target_label2)) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPNZ) {
+                                       |       jmp &exit_addr
+                               } else if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0) {
+                                       |       jmp >7
+                               }
+                       } else if (!zend_jit_smart_true(Dst, opline, (op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0, smart_branch_opcode, target_label, target_label2)) {
                                return 0;
                        }
                } else {
-                       if (!zend_jit_smart_false(Dst, opline, (op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0, smart_branch_opcode, target_label)) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPZ) {
+                                       |       jmp &exit_addr
+                               } else if ((op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0) {
+                                       |       jmp >7
+                               }
+                       } else if (!zend_jit_smart_false(Dst, opline, (op1_info & (MAY_BE_ANY|MAY_BE_REF)) != 0, smart_branch_opcode, target_label)) {
                                return 0;
                        }
                }
@@ -7719,14 +8925,22 @@ static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const ze
                                type = 0;
                }
 
-               if (!(op1_info & (MAY_BE_ANY - mask))) {
+               if (!(op1_info & MAY_BE_GUARD) && !(op1_info & (MAY_BE_ANY - mask))) {
                        |       FREE_OP opline->op1_type, opline->op1, op1_info, 1, op_array, opline
-                       if (!zend_jit_smart_true(Dst, opline, 0, smart_branch_opcode, target_label, target_label2)) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPNZ) {
+                                       |       jmp &exit_addr
+                               }
+                       } else if (!zend_jit_smart_true(Dst, opline, 0, smart_branch_opcode, target_label, target_label2)) {
                                return 0;
                        }
-           } else if (!(op1_info & mask)) {
+           } else if (!(op1_info & MAY_BE_GUARD) && !(op1_info & mask)) {
                        |       FREE_OP opline->op1_type, opline->op1, op1_info, 1, op_array, opline
-                       if (!zend_jit_smart_false(Dst, opline, 0, smart_branch_opcode, target_label)) {
+                       if (exit_addr) {
+                               if (smart_branch_opcode == ZEND_JMPZ) {
+                                       |       jmp &exit_addr
+                               }
+                       } else if (!zend_jit_smart_false(Dst, opline, 0, smart_branch_opcode, target_label)) {
                                return 0;
                        }
                } else {
@@ -7785,7 +8999,13 @@ static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const ze
                                |       mov eax, 1
                                |       shl eax, cl
                                |       test eax, mask
-                               if (smart_branch_opcode) {
+                               if (exit_addr) {
+                                       if (smart_branch_opcode == ZEND_JMPNZ) {
+                                               |       jne &exit_addr
+                                       } else {
+                                               |       je &exit_addr
+                                       }
+                               } else if (smart_branch_opcode) {
                                        if (smart_branch_opcode == ZEND_JMPZ) {
                                                |       je =>target_label
                                        } else if (smart_branch_opcode == ZEND_JMPNZ) {
@@ -7854,7 +9074,13 @@ static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const ze
                                                |       cmp byte [FP + opline->op1.var + 8], type
                                        }
                                }
-                               if (smart_branch_opcode) {
+                               if (exit_addr) {
+                                       if (smart_branch_opcode == ZEND_JMPNZ) {
+                                               |       je &exit_addr
+                                       } else {
+                                               |       jne &exit_addr
+                                       }
+                               } else if (smart_branch_opcode) {
                                        if (smart_branch_opcode == ZEND_JMPZ) {
                                                |       jne =>target_label
                                        } else if (smart_branch_opcode == ZEND_JMPNZ) {
@@ -7883,75 +9109,99 @@ static int zend_jit_type_check(dasm_State **Dst, const zend_op *opline, const ze
        return 1;
 }
 
-static int zend_jit_free_compiled_variables(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa)
+static uint32_t zend_ssa_cv_info(const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, uint32_t var)
 {
-    uint32_t i, j, info;
+    uint32_t j, info;
 
-       // Use type inference to avoid useless zval_ptr_dtor()
-       for (i = 0 ; i < op_array->last_var; i++) {
-               if (ssa->vars && ssa->var_info) {
-                       info = ssa->var_info[i].type;
-                       for (j = op_array->last_var; j < ssa->vars_count; j++) {
-                               if (ssa->vars[j].var == i) {
-                                       info |= ssa->var_info[j].type;
-                               }
+       if (ssa->vars && ssa->var_info) {
+               info = ssa->var_info[var].type;
+               for (j = op_array->last_var; j < ssa->vars_count; j++) {
+                       if (ssa->vars[j].var == var) {
+                               info |= ssa->var_info[j].type;
                        }
-               } else {
-                       info = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF;
                }
+       } else {
+               info = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF;
+       }
 
 #ifdef ZEND_JIT_USE_RC_INFERENCE
-               /* Refcount may be increased by RETURN opcode */
-               if ((info & MAY_BE_RC1) && !(info & MAY_BE_RCN)) {
-                       for (j = 0; j < ssa->cfg.blocks_count; j++) {
-                               if ((ssa->cfg.blocks[j].flags & ZEND_BB_REACHABLE) &&
-                                   ssa->cfg.blocks[j].len > 0) {
-                                       const zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].start + ssa->cfg.blocks[j].len - 1;
-
-                                       if (opline->opcode == ZEND_RETURN) {
-                                               if (opline->op1_type == IS_CV && opline->op1.var == EX_NUM_TO_VAR(i)) {
-                                                       info |= MAY_BE_RCN;
-                                                       break;
-                                               }
+       /* Refcount may be increased by RETURN opcode */
+       if ((info & MAY_BE_RC1) && !(info & MAY_BE_RCN)) {
+               for (j = 0; j < ssa->cfg.blocks_count; j++) {
+                       if ((ssa->cfg.blocks[j].flags & ZEND_BB_REACHABLE) &&
+                           ssa->cfg.blocks[j].len > 0) {
+                               const zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].start + ssa->cfg.blocks[j].len - 1;
+
+                               if (opline->opcode == ZEND_RETURN) {
+                                       if (opline->op1_type == IS_CV && opline->op1.var == EX_NUM_TO_VAR(var)) {
+                                               info |= MAY_BE_RCN;
+                                               break;
                                        }
                                }
                        }
                }
+       }
 #endif
 
-               if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
-                       uint32_t offset = EX_NUM_TO_VAR(i);
-                       | ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_FP, offset), info, 1, 1, 0, opline
-               }
-       }
-       return 1;
+       return info;
 }
 
-static int zend_jit_leave_func(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa)
+static int zend_jit_leave_frame(dasm_State **Dst)
 {
-       // Avoid multiple leave sequences
-       if (jit_return_label >= 0) {
-               | jmp =>jit_return_label
-               return 1;
-       }
-
-       jit_return_label = ssa->cfg.blocks_count * 2;
-
-       |=>jit_return_label:
-
        |       // EG(current_execute_data) = EX(prev_execute_data);
        |       mov r0, EX->prev_execute_data
        |       MEM_OP2_1_ZTS mov, aword, executor_globals, current_execute_data, r0, r2
+       return 1;
+}
 
-       // i_free_compiled_variables(execute_data);
-       if (!zend_jit_free_compiled_variables(Dst, opline, op_array, ssa)) {
-               return 0;
+static int zend_jit_free_cv(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t info, uint32_t var)
+{
+       if (info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
+               uint32_t offset = EX_NUM_TO_VAR(var);
+               | ZVAL_PTR_DTOR ZEND_ADDR_MEM_ZVAL(ZREG_FP, offset), info, 1, 1, 0, opline
        }
+       return 1;
+}
 
+static int zend_jit_leave_func(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_jit_trace_rec *trace)
+{
        /* ZEND_CALL_FAKE_CLOSURE handled on slow path to eliminate check for ZEND_CALL_CLOSURE on fast path */
        |       mov FCARG1d, dword [FP + offsetof(zend_execute_data, This.u1.type_info)]
        |       test FCARG1d, (ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_FAKE_CLOSURE)
-       |       jnz ->leave_function_handler
+       if (trace && trace->op != ZEND_JIT_TRACE_END) {
+               |       jnz >1
+               |.cold_code
+               |1:
+               if (!GCC_GLOBAL_REGS) {
+                       |       mov FCARG2a, FP
+               }
+               |       EXT_CALL zend_jit_leave_func_helper, r0
+
+               if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE ||
+                   !JIT_G(current_frame) ||
+                   !JIT_G(current_frame)->nested) {
+                       // TODO: try to avoid this check ???
+                       if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+                               |       cmp IP, zend_jit_halt_op
+                               |       je ->trace_halt
+                       } else if (GCC_GLOBAL_REGS) {
+                               |       test IP, IP
+                               |       je ->trace_halt
+                       } else {
+                               |       test eax, eax
+                               |       jl ->trace_halt
+                       }
+               }
+
+               if (!GCC_GLOBAL_REGS) {
+                       |       // execute_data = EG(current_execute_data)
+                       |       MEM_OP2_2_ZTS mov, FP, aword, executor_globals, current_execute_data, r0
+               }
+               |       jmp >8
+               |.code
+       } else {
+               |       jnz ->leave_function_handler
+       }
 
        if ((op_array->scope && !(op_array->fn_flags & ZEND_ACC_STATIC)) ||
            (op_array->fn_flags & ZEND_ACC_CLOSURE)) {
@@ -7975,12 +9225,53 @@ static int zend_jit_leave_func(dasm_State **Dst, const zend_op *opline, const ze
        |       MEM_OP2_1_ZTS mov, aword, executor_globals, vm_stack_top, FP, r0
        |       // execute_data = EX(prev_execute_data);
        |       mov FP, EX->prev_execute_data
-       |       // if (EG(exception))
-       |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
-       |       LOAD_OPLINE
-       |       jne ->leave_throw_handler
-       |       // opline = EX(opline) + 1
-       |       ADD_IP sizeof(zend_op)
+
+       |9:
+       if (trace) {
+               if (trace->op != ZEND_JIT_TRACE_END
+                && (trace->op != ZEND_JIT_TRACE_BACK || !trace->recursive)) {
+                       zend_jit_reset_opline(Dst, NULL);
+               } else {
+                       // TODO: exception handling for tracing JIT ???
+                       |       LOAD_OPLINE
+                       |       ADD_IP sizeof(zend_op)
+               }
+
+               |8:
+
+               if (opline->opcode == ZEND_RETURN
+                && trace->op == ZEND_JIT_TRACE_BACK
+                && trace->recursive) {
+                       const zend_op *next_opline = trace->opline;
+                       uint32_t exit_point = zend_jit_trace_get_exit_point(opline, NULL, trace);
+                       const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+                       trace++;
+                       ZEND_ASSERT(trace->op == ZEND_JIT_TRACE_VM || trace->op == ZEND_JIT_TRACE_END);
+                       next_opline = trace->opline;
+                       exit_point = zend_jit_trace_get_exit_point(opline, NULL, trace);
+                       exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+                       if (!exit_addr) {
+                               return 0;
+                       }
+                       |       // TODO: exception handling ???
+                       |       CMP_IP next_opline
+                       |       jne &exit_addr
+
+                       last_valid_opline = trace->opline;
+
+                       return 1;
+               }
+               return 1;
+       } else {
+               |       // if (EG(exception))
+               |       MEM_OP2_1_ZTS cmp, aword, executor_globals, exception, 0, r0
+               |       LOAD_OPLINE
+               |       jne ->leave_throw_handler
+               |       // opline = EX(opline) + 1
+               |       ADD_IP sizeof(zend_op)
+       }
+
        if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
                |       add r4, HYBRID_SPAD
 #ifdef CONTEXT_THREADED_JIT
@@ -8021,56 +9312,77 @@ static int zend_jit_leave_func(dasm_State **Dst, const zend_op *opline, const ze
        return 1;
 }
 
-static int zend_jit_return(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, zend_ssa *ssa, uint32_t op1_info, zend_jit_addr op1_addr)
+static int zend_jit_return(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_jit_addr op1_addr)
 {
        zend_jit_addr ret_addr;
+       int8_t return_value_used;
 
        ZEND_ASSERT(op_array->type != ZEND_EVAL_CODE && op_array->function_name);
        ZEND_ASSERT(!(op1_info & MAY_BE_UNDEF));
 
+       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE && JIT_G(current_frame)) {
+               return_value_used = JIT_G(current_frame)->return_value_used;
+       } else {
+               return_value_used = -1;
+       }
+
        // if (!EX(return_value))
        if (Z_MODE(op1_addr) == IS_REG && Z_REG(op1_addr) == ZREG_R1) {
-               |       mov r2, EX->return_value
-               |       test r2, r2
+               if (return_value_used != 0) {
+                       |       mov r2, EX->return_value
+               }
+               if (return_value_used == -1) {
+                       |       test r2, r2
+               }
                ret_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R2, 0);
        } else {
-               |       mov r1, EX->return_value
-               |       test r1, r1
+               if (return_value_used != 0) {
+                       |       mov r1, EX->return_value
+               }
+               if (return_value_used == -1) {
+                       |       test r1, r1
+               }
                ret_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R1, 0);
        }
        if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) &&
            (op1_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
-               |       jz >1
-               |.cold_code
-               |1:
-               if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
-                       if (jit_return_label >= 0) {
-                               |       IF_NOT_ZVAL_REFCOUNTED op1_addr, =>jit_return_label
-                       } else {
-                               |       IF_NOT_ZVAL_REFCOUNTED op1_addr, >9
-                       }
+               if (return_value_used == -1) {
+                       |       jz >1
+                       |.cold_code
+                       |1:
                }
-               |       GET_ZVAL_PTR FCARG1a, op1_addr
-               |       GC_DELREF FCARG1a
-               if (RC_MAY_BE_1(op1_info)) {
-                       if (RC_MAY_BE_N(op1_info)) {
+               if (return_value_used != 1) {
+                       if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)-(MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
                                if (jit_return_label >= 0) {
-                                       |       jnz =>jit_return_label
+                                       |       IF_NOT_ZVAL_REFCOUNTED op1_addr, =>jit_return_label
                                } else {
-                                       |       jnz >9
+                                       |       IF_NOT_ZVAL_REFCOUNTED op1_addr, >9
                                }
                        }
-                       |       //SAVE_OPLINE()
-                       |       ZVAL_DTOR_FUNC op1_info, opline
-                       |       //????mov r1, EX->return_value // reload ???
-               }
-               if (jit_return_label >= 0) {
-                       |       jmp =>jit_return_label
-               } else {
-                       |       jmp >9
+                       |       GET_ZVAL_PTR FCARG1a, op1_addr
+                       |       GC_DELREF FCARG1a
+                       if (RC_MAY_BE_1(op1_info)) {
+                               if (RC_MAY_BE_N(op1_info)) {
+                                       if (jit_return_label >= 0) {
+                                               |       jnz =>jit_return_label
+                                       } else {
+                                               |       jnz >9
+                                       }
+                               }
+                               |       //SAVE_OPLINE()
+                               |       ZVAL_DTOR_FUNC op1_info, opline
+                               |       //????mov r1, EX->return_value // reload ???
+                       }
+                       if (return_value_used == -1) {
+                               if (jit_return_label >= 0) {
+                                       |       jmp =>jit_return_label
+                               } else {
+                                       |       jmp >9
+                               }
+                               |.code
+                       }
                }
-               |.code
-       } else {
+       } else if (return_value_used == -1) {
                if (jit_return_label >= 0) {
                        |       jz =>jit_return_label
                } else {
@@ -8078,21 +9390,25 @@ static int zend_jit_return(dasm_State **Dst, const zend_op *opline, const zend_o
                }
        }
 
+       if (return_value_used == 0) {
+               return 1;
+       }
+
        if (opline->op1_type == IS_CONST) {
                zval *zv = RT_CONSTANT(opline, opline->op1);
-               |       ZVAL_COPY_CONST ret_addr, -1, -1, zv, r0
+               |       ZVAL_COPY_CONST ret_addr, MAY_BE_ANY, MAY_BE_ANY, zv, r0
                if (Z_REFCOUNTED_P(zv)) {
                        |       ADDREF_CONST zv, r0
                }
        } else if (opline->op1_type == IS_TMP_VAR) {
-               |       ZVAL_COPY_VALUE ret_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_R2
+               |       ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
        } else if (opline->op1_type == IS_CV) {
                if (op1_info & MAY_BE_REF) {
                        |       LOAD_ZVAL_ADDR r0, op1_addr
                        |       ZVAL_DEREF r0, op1_info
                        op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
                }
-               |       ZVAL_COPY_VALUE ret_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_R2
+               |       ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
                |       // TODO: JIT: if (EXPECTED(!(EX_CALL_INFO() & ZEND_CALL_CODE))) ZVAL_NULL(retval_ptr); ???
                |       TRY_ADDREF op1_info, ah, r2
        } else {
@@ -8105,7 +9421,7 @@ static int zend_jit_return(dasm_State **Dst, const zend_op *opline, const zend_o
                        |       // zend_refcounted *ref = Z_COUNTED_P(retval_ptr);
                        |       GET_ZVAL_PTR r0, op1_addr
                        |       // ZVAL_COPY_VALUE(return_value, &ref->value);
-                       |       ZVAL_COPY_VALUE ret_addr, -1, ref_addr, op1_info, ZREG_R2, ZREG_R2
+                       |       ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, ref_addr, op1_info, ZREG_R2, ZREG_R2
                        |       GC_DELREF r0
                        |       je >2
                        |       // if (IS_REFCOUNTED())
@@ -8131,12 +9447,11 @@ static int zend_jit_return(dasm_State **Dst, const zend_op *opline, const zend_o
                        }
                        |.code
                }
-               |       ZVAL_COPY_VALUE ret_addr, -1, op1_addr, op1_info, ZREG_R0, ZREG_R2
+               |       ZVAL_COPY_VALUE ret_addr, MAY_BE_ANY, op1_addr, op1_info, ZREG_R0, ZREG_R2
        }
 
        |9:
-       //JIT: ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
-       return zend_jit_leave_func(Dst, opline, op_array, ssa);
+       return 1;
 }
 
 static int zend_jit_fetch_dim_read(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, uint32_t op2_info, uint32_t res_info, int may_throw)
@@ -8316,7 +9631,7 @@ static int zend_jit_fetch_dim_read(dasm_State **Dst, const zend_op *opline, cons
        return 1;
 }
 
-static int zend_jit_isset_isempty_dim(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, uint32_t op2_info, int may_throw, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2)
+static int zend_jit_isset_isempty_dim(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, uint32_t op2_info, int may_throw, zend_uchar smart_branch_opcode, uint32_t target_label, uint32_t target_label2, const void *exit_addr)
 {
        zend_jit_addr op1_addr, op2_addr, res_addr;
 
@@ -8385,7 +9700,13 @@ static int zend_jit_isset_isempty_dim(dasm_State **Dst, const zend_op *opline, c
                }
        }
        if (!(opline->extended_value & ZEND_ISEMPTY)) {
-               if (smart_branch_opcode) {
+               if (exit_addr) {
+                       if (smart_branch_opcode == ZEND_JMPNZ) {
+                               |       jmp &exit_addr
+                       } else {
+                               |       jmp >8
+                       }
+               } else if (smart_branch_opcode) {
                        if (smart_branch_opcode == ZEND_JMPZ) {
                                |       jmp =>target_label2
                        } else if (smart_branch_opcode == ZEND_JMPNZ) {
@@ -8413,7 +9734,11 @@ static int zend_jit_isset_isempty_dim(dasm_State **Dst, const zend_op *opline, c
                }
        }
        if (!(opline->extended_value & ZEND_ISEMPTY)) {
-               if (smart_branch_opcode) {
+               if (exit_addr) {
+                       if (smart_branch_opcode == ZEND_JMPZ) {
+                               |       jmp &exit_addr
+                       }
+               } else if (smart_branch_opcode) {
                        if (smart_branch_opcode == ZEND_JMPZ) {
                                |       jmp =>target_label
                        } else if (smart_branch_opcode == ZEND_JMPNZ) {
@@ -8695,8 +10020,11 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zen
        zval *zv = RT_CONSTANT(opline, opline->op2);
        zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
 
-       |       cmp dword EX->This.u2.num_args, arg_num
-       |       jae >5
+       if (zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE ||
+           (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
+               |       cmp dword EX->This.u2.num_args, arg_num
+               |       jae >5
+       }
        |       ZVAL_COPY_CONST res_addr, -1, -1, zv, r0
        if (Z_REFCOUNTED_P(zv)) {
        |       ADDREF_CONST zv, r0
@@ -8890,7 +10218,7 @@ static zend_bool zend_may_be_dynamic_property(zend_class_entry *ce, zend_string
        return 0;
 }
 
-static int zend_jit_fetch_obj_read(dasm_State **Dst, zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_class_entry *ce, int may_throw)
+static int zend_jit_fetch_obj_read(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, zend_class_entry *ce, int may_throw)
 {
        zval *member;
        uint32_t offset;
@@ -8917,7 +10245,17 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, zend_op *opline, const zend
                        op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_R0, 0);
                }
                if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY)- MAY_BE_OBJECT)) {
-                       |       IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >7
+                       if (zend_jit_trigger == ZEND_JIT_ON_HOT_TRACE) {
+                               int32_t exit_point = zend_jit_trace_get_exit_point(opline, opline, NULL);
+                               const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point);
+
+                               if (!exit_addr) {
+                                       return 0;
+                               }
+                               |       IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, &exit_addr
+                       } else {
+                               |       IF_NOT_ZVAL_TYPE op1_addr, IS_OBJECT, >7
+                       }
                }
                |       GET_ZVAL_PTR FCARG1a, op1_addr
        }
@@ -8995,7 +10333,7 @@ static int zend_jit_fetch_obj_read(dasm_State **Dst, zend_op *opline, const zend
                |       jmp >9
        }
 
-       if (op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)- MAY_BE_OBJECT)) {
+       if ((op1_info & ((MAY_BE_UNDEF|MAY_BE_ANY|MAY_BE_REF)- MAY_BE_OBJECT))  && zend_jit_trigger != ZEND_JIT_ON_HOT_TRACE) {
                |7:
                if (opline->opcode == ZEND_FETCH_OBJ_R) {
                        |       SAVE_VALID_OPLINE opline