/*
   +----------------------------------------------------------------------+
   | Zend Engine, e-SSA based Type & Range Inference                      |
   +----------------------------------------------------------------------+
   | 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:           |
   | https://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>                              |
   +----------------------------------------------------------------------+
*/

#include "zend_compile.h"
#include "zend_generators.h"
#include "zend_inference.h"
#include "zend_func_info.h"
#include "zend_call_graph.h"
#include "zend_closures.h"
#include "zend_worklist.h"
#include "zend_optimizer_internal.h"

/* The used range inference algorithm is described in:
 *     V. Campos, R. Rodrigues, I. de Assis Costa and F. Pereira.
 *     "Speed and Precision in Range Analysis", SBLP'12.
 *
 * There are a couple degrees of freedom, we use:
 *  * Propagation on SCCs.
 *  * e-SSA for live range splitting.
 *  * Only intra-procedural inference.
 *  * Widening with warmup passes, but without jump sets.
 */

/* Whether to handle symbolic range constraints */
#define SYM_RANGE

/* Whether to handle negative range constraints */
/* Negative range inference is buggy, so disabled for now */
#undef NEG_RANGE

/* Number of warmup passes to use prior to widening */
#define RANGE_WARMUP_PASSES 16

/* Logging for range inference in general */
#if 0
#define LOG_SSA_RANGE(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG_SSA_RANGE(...)
#endif

/* Logging for negative range constraints */
#if 0
#define LOG_NEG_RANGE(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG_NEG_RANGE(...)
#endif

/* Pop elements in unspecified order from worklist until it is empty */
#define WHILE_WORKLIST(worklist, len, i) do { \
	bool _done = 0; \
	while (!_done) { \
		_done = 1; \
		ZEND_BITSET_FOREACH(worklist, len, i) { \
			zend_bitset_excl(worklist, i); \
			_done = 0;

#define WHILE_WORKLIST_END() \
		} ZEND_BITSET_FOREACH_END(); \
	} \
} while (0)

#define CHECK_SCC_VAR(var2) \
	do { \
		if (!ssa->vars[var2].no_val) { \
			if (ssa->vars[var2].scc < 0) { \
				zend_ssa_check_scc_var(op_array, ssa, var2, index, stack); \
			} \
			if (ssa->vars[var2].scc < ssa->vars[var].scc) { \
				ssa->vars[var].scc = ssa->vars[var2].scc; \
				is_root = 0; \
			} \
		} \
	} while (0)

#define CHECK_SCC_ENTRY(var2) \
	do { \
		if (ssa->vars[var2].scc != ssa->vars[var].scc) { \
			ssa->vars[var2].scc_entry = 1; \
		} \
	} while (0)

#define ADD_SCC_VAR(_var) \
	do { \
		if (ssa->vars[_var].scc == scc && \
		    !(ssa->var_info[_var].type & MAY_BE_REF)) { \
			zend_bitset_incl(worklist, _var); \
		} \
	} while (0)

#define ADD_SCC_VAR_1(_var) \
	do { \
		if (ssa->vars[_var].scc == scc && \
		    !(ssa->var_info[_var].type & MAY_BE_REF) && \
		    !zend_bitset_in(visited, _var)) { \
			zend_bitset_incl(worklist, _var); \
		} \
	} while (0)

#define FOR_EACH_DEFINED_VAR(line, MACRO) \
	do { \
		if (ssa->ops[line].op1_def >= 0) { \
			MACRO(ssa->ops[line].op1_def); \
		} \
		if (ssa->ops[line].op2_def >= 0) { \
			MACRO(ssa->ops[line].op2_def); \
		} \
		if (ssa->ops[line].result_def >= 0) { \
			MACRO(ssa->ops[line].result_def); \
		} \
		if (op_array->opcodes[line].opcode == ZEND_OP_DATA) { \
			if (ssa->ops[line-1].op1_def >= 0) { \
				MACRO(ssa->ops[line-1].op1_def); \
			} \
			if (ssa->ops[line-1].op2_def >= 0) { \
				MACRO(ssa->ops[line-1].op2_def); \
			} \
			if (ssa->ops[line-1].result_def >= 0) { \
				MACRO(ssa->ops[line-1].result_def); \
			} \
		} else if ((uint32_t)line+1 < op_array->last && \
		           op_array->opcodes[line+1].opcode == ZEND_OP_DATA) { \
			if (ssa->ops[line+1].op1_def >= 0) { \
				MACRO(ssa->ops[line+1].op1_def); \
			} \
			if (ssa->ops[line+1].op2_def >= 0) { \
				MACRO(ssa->ops[line+1].op2_def); \
			} \
			if (ssa->ops[line+1].result_def >= 0) { \
				MACRO(ssa->ops[line+1].result_def); \
			} \
		} \
	} while (0)


#define FOR_EACH_VAR_USAGE(_var, MACRO) \
	do { \
		zend_ssa_phi *p = ssa->vars[_var].phi_use_chain; \
		int use = ssa->vars[_var].use_chain; \
		while (use >= 0) { \
			FOR_EACH_DEFINED_VAR(use, MACRO); \
			use = zend_ssa_next_use(ssa->ops, _var, use); \
		} \
		p = ssa->vars[_var].phi_use_chain; \
		while (p) { \
			MACRO(p->ssa_var); \
			p = zend_ssa_next_use_phi(ssa, _var, p); \
		} \
	} while (0)

static inline bool add_will_overflow(zend_long a, zend_long b) {
	return (b > 0 && a > ZEND_LONG_MAX - b)
		|| (b < 0 && a < ZEND_LONG_MIN - b);
}
#if 0
static inline bool sub_will_overflow(zend_long a, zend_long b) {
	return (b > 0 && a < ZEND_LONG_MIN + b)
		|| (b < 0 && a > ZEND_LONG_MAX + b);
}
#endif

#if 0
/* Recursive Pearce's SCC algorithm implementation */
static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, zend_worklist_stack *stack) /* {{{ */
{
	int is_root = 1;
#ifdef SYM_RANGE
	zend_ssa_phi *p;
#endif

	ssa->vars[var].scc = *index;
	(*index)++;

	FOR_EACH_VAR_USAGE(var, CHECK_SCC_VAR);

#ifdef SYM_RANGE
	/* Process symbolic control-flow constraints */
	p = ssa->vars[var].sym_use_chain;
	while (p) {
		CHECK_SCC_VAR(p->ssa_var);
		p = p->sym_use_chain;
	}
#endif

	if (is_root) {
		ssa->sccs--;
		while (stack->len > 0) {
			int var2 = zend_worklist_stack_peek(stack);
			if (ssa->vars[var2].scc < ssa->vars[var].scc) {
				break;
			}
			zend_worklist_stack_pop(stack);
			ssa->vars[var2].scc = ssa->sccs;
			(*index)--;
		}
		ssa->vars[var].scc = ssa->sccs;
		ssa->vars[var].scc_entry = 1;
		(*index)--;
	} else {
		zend_worklist_stack_push(stack, var);
	}
}
/* }}} */

ZEND_API void zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
	int index = 0;
	zend_worklist_stack stack;
	int j;
	ALLOCA_FLAG(stack_use_heap)

	ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap);

	/* Find SCCs using Pearce's algorithm. */
	ssa->sccs = ssa->vars_count;
	for (j = 0; j < ssa->vars_count; j++) {
		if (!ssa->vars[j].no_val && ssa->vars[j].scc < 0) {
			zend_ssa_check_scc_var(op_array, ssa, j, &index, &stack);
		}
	}

	if (ssa->sccs) {
		/* Shift SCC indexes. */
		for (j = 0; j < ssa->vars_count; j++) {
			if (ssa->vars[j].scc >= 0) {
				ssa->vars[j].scc -= ssa->sccs;
			}
		}
	}
	ssa->sccs = ssa->vars_count - ssa->sccs;

	for (j = 0; j < ssa->vars_count; j++) {
		if (ssa->vars[j].scc >= 0) {
			int var = j;
			FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY);
		}
	}

	ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap);
}
/* }}} */

#else
/* Iterative Pearce's SCC algorithm implementation */

typedef struct _zend_scc_iterator {
	int               state;
	int               last;
	union {
		int           use;
		zend_ssa_phi *phi;
	};
} zend_scc_iterator;

static int zend_scc_next(const zend_op_array *op_array, zend_ssa *ssa, int var, zend_scc_iterator *iterator) /* {{{ */
{
	zend_ssa_phi *phi;
	int use, var2;

	switch (iterator->state) {
		case 0:                       goto state_0;
		case 1:  use = iterator->use; goto state_1;
		case 2:  use = iterator->use; goto state_2;
		case 3:  use = iterator->use; goto state_3;
		case 4:  use = iterator->use; goto state_4;
		case 5:  use = iterator->use; goto state_5;
		case 6:  use = iterator->use; goto state_6;
		case 7:  use = iterator->use; goto state_7;
		case 8:  use = iterator->use; goto state_8;
		case 9:  phi = iterator->phi; goto state_9;
#ifdef SYM_RANGE
		case 10: phi = iterator->phi; goto state_10;
#endif
		case 11:                      goto state_11;
	}

state_0:
	use = ssa->vars[var].use_chain;
	while (use >= 0) {
		iterator->use = use;
		var2 = ssa->ops[use].op1_def;
		if (var2 >= 0 && !ssa->vars[var2].no_val) {
			iterator->state = 1;
			return var2;
		}
state_1:
		var2 = ssa->ops[use].op2_def;
		if (var2 >= 0 && !ssa->vars[var2].no_val) {
			iterator->state = 2;
			return var2;
		}
state_2:
		var2 = ssa->ops[use].result_def;
		if (var2 >= 0 && !ssa->vars[var2].no_val) {
			iterator->state = 3;
			return var2;
		}
state_3:
		if (op_array->opcodes[use].opcode == ZEND_OP_DATA) {
			var2 = ssa->ops[use-1].op1_def;
			if (var2 >= 0 && !ssa->vars[var2].no_val) {
				iterator->state = 4;
				return var2;
			}
state_4:
			var2 = ssa->ops[use-1].op2_def;
			if (var2 >= 0 && !ssa->vars[var2].no_val) {
				iterator->state = 5;
				return var2;
			}
state_5:
			var2 = ssa->ops[use-1].result_def;
			if (var2 >= 0 && !ssa->vars[var2].no_val) {
				iterator->state = 8;
				return var2;
			}
		} else if ((uint32_t)use+1 < op_array->last &&
		           op_array->opcodes[use+1].opcode == ZEND_OP_DATA) {
			var2 = ssa->ops[use+1].op1_def;
			if (var2 >= 0 && !ssa->vars[var2].no_val) {
				iterator->state = 6;
				return var2;
			}
state_6:
			var2 = ssa->ops[use+1].op2_def;
			if (var2 >= 0 && !ssa->vars[var2].no_val) {
				iterator->state = 7;
				return var2;
			}
state_7:
			var2 = ssa->ops[use+1].result_def;
			if (var2 >= 0 && !ssa->vars[var2].no_val) {
				iterator->state = 8;
				return var2;
			}
		}
state_8:
		use = zend_ssa_next_use(ssa->ops, var, use);
	}

	phi = ssa->vars[var].phi_use_chain;
	while (phi) {
		var2 = phi->ssa_var;
		if (!ssa->vars[var2].no_val) {
			iterator->state = 9;
			iterator->phi = phi;
			return var2;
		}
state_9:
		phi = zend_ssa_next_use_phi(ssa, var, phi);
	}

#ifdef SYM_RANGE
	/* Process symbolic control-flow constraints */
	phi = ssa->vars[var].sym_use_chain;
	while (phi) {
		var2 = phi->ssa_var;
		if (!ssa->vars[var2].no_val) {
			iterator->state = 10;
			iterator->phi = phi;
			return var2;
		}
state_10:
		phi = phi->sym_use_chain;
	}
#endif

	iterator->state = 11;
state_11:
	return -1;
}
/* }}} */

static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, zend_worklist_stack *stack, zend_worklist_stack *vstack, zend_scc_iterator *iterators) /* {{{ */
{
restart:
	zend_worklist_stack_push(vstack, var);
	iterators[var].state = 0;
	iterators[var].last = -1;
	ssa->vars[var].scc_entry = 1;
	ssa->vars[var].scc = *index;
	(*index)++;

	while (vstack->len > 0) {
		var = zend_worklist_stack_peek(vstack);
		while (1) {
			int var2;

			if (iterators[var].last >= 0) {
				/* finish edge */
				var2 = iterators[var].last;
				if (ssa->vars[var2].scc < ssa->vars[var].scc) {
					ssa->vars[var].scc = ssa->vars[var2].scc;
					ssa->vars[var].scc_entry = 0;
				}
			}
			var2 = zend_scc_next(op_array, ssa, var, iterators + var);
			iterators[var].last = var2;
			if (var2 < 0) break;
			/* begin edge */
			if (ssa->vars[var2].scc < 0) {
				var = var2;
				goto restart;
			}
		}

		/* finish visiting */
		zend_worklist_stack_pop(vstack);
		if (ssa->vars[var].scc_entry) {
			ssa->sccs--;
			while (stack->len > 0) {
				int var2 = zend_worklist_stack_peek(stack);
				if (ssa->vars[var2].scc < ssa->vars[var].scc) {
					break;
				}
				zend_worklist_stack_pop(stack);
				ssa->vars[var2].scc = ssa->sccs;
				(*index)--;
			}
			ssa->vars[var].scc = ssa->sccs;
			(*index)--;
		} else {
			zend_worklist_stack_push(stack, var);
		}
	}
}
/* }}} */

ZEND_API void zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
	int index = 0;
	zend_worklist_stack stack, vstack;
	zend_scc_iterator *iterators;
	int j;
	ALLOCA_FLAG(stack_use_heap)
	ALLOCA_FLAG(vstack_use_heap)
	ALLOCA_FLAG(iterators_use_heap)

	iterators = do_alloca(sizeof(zend_scc_iterator) * ssa->vars_count, iterators_use_heap);
	ZEND_WORKLIST_STACK_ALLOCA(&vstack, ssa->vars_count, vstack_use_heap);
	ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap);

	/* Find SCCs using Pearce's algorithm. */
	ssa->sccs = ssa->vars_count;
	for (j = 0; j < ssa->vars_count; j++) {
		if (!ssa->vars[j].no_val && ssa->vars[j].scc < 0) {
			zend_ssa_check_scc_var(op_array, ssa, j, &index, &stack, &vstack, iterators);
		}
	}

	if (ssa->sccs) {
		/* Shift SCC indexes. */
		for (j = 0; j < ssa->vars_count; j++) {
			if (ssa->vars[j].scc >= 0) {
				ssa->vars[j].scc -= ssa->sccs;
			}
		}
	}
	ssa->sccs = ssa->vars_count - ssa->sccs;

	for (j = 0; j < ssa->vars_count; j++) {
		if (ssa->vars[j].scc >= 0) {
			int var = j;
			FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY);
		}
	}

	ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap);
	ZEND_WORKLIST_STACK_FREE_ALLOCA(&vstack, vstack_use_heap);
	free_alloca(iterators, iterators_use_heap);
}
/* }}} */

#endif

ZEND_API void zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
	zend_ssa_var *ssa_vars = ssa->vars;
	zend_ssa_op *ssa_ops = ssa->ops;
	int ssa_vars_count = ssa->vars_count;
	zend_bitset worklist;
	int i, j, use;
	zend_ssa_phi *p;
	ALLOCA_FLAG(use_heap);

	if (!op_array->function_name || !ssa->vars || !ssa->ops) {
		return;
	}

	worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap);
	memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));

	for (i = 0; i < ssa_vars_count; i++) {
		ssa_vars[i].no_val = 1; /* mark as unused */
		use = ssa->vars[i].use_chain;
		while (use >= 0) {
			if (!zend_ssa_is_no_val_use(&op_array->opcodes[use], &ssa->ops[use], i)) {
				ssa_vars[i].no_val = 0; /* used directly */
				zend_bitset_incl(worklist, i);
				break;
			}
			use = zend_ssa_next_use(ssa_ops, i, use);
		}
	}

	WHILE_WORKLIST(worklist, zend_bitset_len(ssa_vars_count), i) {
		if (ssa_vars[i].definition_phi) {
			/* mark all possible sources as used */
			p = ssa_vars[i].definition_phi;
			if (p->pi >= 0) {
				if (ssa_vars[p->sources[0]].no_val) {
					ssa_vars[p->sources[0]].no_val = 0; /* used indirectly */
					zend_bitset_incl(worklist, p->sources[0]);
				}
			} else {
				for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
					ZEND_ASSERT(p->sources[j] >= 0);
					if (ssa->vars[p->sources[j]].no_val) {
						ssa_vars[p->sources[j]].no_val = 0; /* used indirectly */
						zend_bitset_incl(worklist, p->sources[j]);
					}
				}
			}
		}
	} WHILE_WORKLIST_END();

	free_alloca(worklist, use_heap);
}
/* }}} */

/* From "Hacker's Delight" */
static zend_ulong minOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
{
	zend_ulong m, temp;

	m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
	while (m != 0) {
		if (~a & c & m) {
			temp = (a | m) & -m;
			if (temp <= b) {
				a = temp;
				break;
			}
		} else if (a & ~c & m) {
			temp = (c | m) & -m;
			if (temp <= d) {
				c = temp;
				break;
			}
		}
		m = m >> 1;
	}
	return a | c;
}

static zend_ulong maxOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
{
	zend_ulong m, temp;

	m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
	while (m != 0) {
		if (b & d & m) {
			temp = (b - m) | (m - 1);
			if (temp >= a) {
				b = temp;
				break;
			}
			temp = (d - m) | (m - 1);
			if (temp >= c) {
				d = temp;
				break;
			}
		}
		m = m >> 1;
	}
	return b | d;
}

static zend_ulong minAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
{
	zend_ulong m, temp;

	m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
	while (m != 0) {
		if (~a & ~c & m) {
			temp = (a | m) & -m;
			if (temp <= b) {
				a = temp;
				break;
			}
			temp = (c | m) & -m;
			if (temp <= d) {
				c = temp;
				break;
			}
		}
		m = m >> 1;
	}
	return a & c;
}

static zend_ulong maxAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
{
	zend_ulong m, temp;

	m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
	while (m != 0) {
		if (b & ~d & m) {
			temp = (b | ~m) | (m - 1);
			if (temp >= a) {
				b = temp;
				break;
			}
		} else if (~b & d & m) {
			temp = (d | ~m) | (m - 1);
			if (temp >= c) {
				d = temp;
				break;
			}
		}
		m = m >> 1;
	}
	return b & d;
}

/* Based on "Hacker's Delight" */

/*
0: + + + + 0 0 0 0 => 0 0 + min/max
2: + + - + 0 0 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d)
3: + + - - 0 0 1 1 => 1 1 - min/max
8: - + + + 1 0 0 0 => 1 0 ? min(a,-1,b,d)/max(0,b,c,d)
a: - + - + 1 0 1 0 => 1 0 ? MIN(a,c)/max(0,b,0,d)
b: - + - - 1 0 1 1 => 1 1 - c/-1
c: - - + + 1 1 0 0 => 1 1 - min/max
e: - - - + 1 1 1 0 => 1 1 - a/-1
f  - - - - 1 1 1 1 => 1 1 - min/max
*/
static void zend_ssa_range_or(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp)
{
	int x = ((a < 0) ? 8 : 0) |
	        ((b < 0) ? 4 : 0) |
	        ((c < 0) ? 2 : 0) |
	        ((d < 0) ? 1 : 0);
	switch (x) {
		case 0x0:
		case 0x3:
		case 0xc:
		case 0xf:
			tmp->min = minOR(a, b, c, d);
			tmp->max = maxOR(a, b, c, d);
			break;
		case 0x2:
			tmp->min = minOR(a, b, c, -1);
			tmp->max = maxOR(a, b, 0, d);
			break;
		case 0x8:
			tmp->min = minOR(a, -1, c, d);
			tmp->max = maxOR(0, b, c, d);
			break;
		case 0xa:
			tmp->min = MIN(a, c);
			tmp->max = maxOR(0, b, 0, d);
			break;
		case 0xb:
			tmp->min = c;
			tmp->max = -1;
			break;
		case 0xe:
			tmp->min = a;
			tmp->max = -1;
			break;
	}
}

/*
0: + + + + 0 0 0 0 => 0 0 + min/max
2: + + - + 0 0 1 0 => 0 0 + 0/b
3: + + - - 0 0 1 1 => 0 0 + min/max
8: - + + + 1 0 0 0 => 0 0 + 0/d
a: - + - + 1 0 1 0 => 1 0 ? min(a,-1,c,-1)/NAX(b,d)
b: - + - - 1 0 1 1 => 1 0 ? min(a,-1,c,d)/max(0,b,c,d)
c: - - + + 1 1 0 0 => 1 1 - min/max
e: - - - + 1 1 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d)
f  - - - - 1 1 1 1 => 1 1 - min/max
*/
static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp)
{
	int x = ((a < 0) ? 8 : 0) |
	        ((b < 0) ? 4 : 0) |
	        ((c < 0) ? 2 : 0) |
	        ((d < 0) ? 1 : 0);
	switch (x) {
		case 0x0:
		case 0x3:
		case 0xc:
		case 0xf:
			tmp->min = minAND(a, b, c, d);
			tmp->max = maxAND(a, b, c, d);
			break;
		case 0x2:
			tmp->min = 0;
			tmp->max = b;
			break;
		case 0x8:
			tmp->min = 0;
			tmp->max = d;
			break;
		case 0xa:
			tmp->min = minAND(a, -1, c, -1);
			tmp->max = MAX(b, d);
			break;
		case 0xb:
			tmp->min = minAND(a, -1, c, d);
			tmp->max = maxAND(0, b, c, d);
			break;
		case 0xe:
			tmp->min = minAND(a, b, c, -1);
			tmp->max = maxAND(a, b, 0, d);
			break;
	}
}

static inline bool zend_abs_range(
		zend_long min, zend_long max, zend_long *abs_min, zend_long *abs_max) {
	if (min == ZEND_LONG_MIN) {
		/* Cannot take absolute value of LONG_MIN  */
		return 0;
	}

	if (min >= 0) {
		*abs_min = min;
		*abs_max = max;
	} else if (max <= 0) {
		*abs_min = -max;
		*abs_max = -min;
	} else {
		/* Range crossing zero */
		*abs_min = 0;
		*abs_max = MAX(max, -min);
	}

	return 1;
}

static inline zend_long safe_shift_left(zend_long n, zend_long s) {
	return (zend_long) ((zend_ulong) n << (zend_ulong) s);
}

static inline bool shift_left_overflows(zend_long n, zend_long s) {
	/* This considers shifts that shift in the sign bit to be overflowing as well */
	if (n >= 0) {
		return s >= SIZEOF_ZEND_LONG * 8 - 1 || safe_shift_left(n, s) < n;
	} else {
		return s >= SIZEOF_ZEND_LONG * 8 || safe_shift_left(n, s) > n;
	}
}

/* If b does not divide a exactly, return the two adjacent values between which the real result
 * lies. */
static void float_div(zend_long a, zend_long b, zend_long *r1, zend_long *r2) {
	*r1 = *r2 = a / b;
	if (a % b != 0) {
		if (*r2 < 0) {
			(*r2)--;
		} else {
			(*r2)++;
		}
	}
}

static bool zend_inference_calc_binary_op_range(
		const zend_op_array *op_array, const zend_ssa *ssa,
		const zend_op *opline, const zend_ssa_op *ssa_op, uint8_t opcode, zend_ssa_range *tmp) {
	zend_long op1_min, op2_min, op1_max, op2_max, t1, t2, t3, t4;

	switch (opcode) {
		case ZEND_ADD:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				op1_min = OP1_MIN_RANGE();
				op2_min = OP2_MIN_RANGE();
				op1_max = OP1_MAX_RANGE();
				op2_max = OP2_MAX_RANGE();
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					zend_add_will_overflow(op1_min, op2_min)) {
					tmp->underflow = 1;
					tmp->min = ZEND_LONG_MIN;
				} else {
					tmp->min = op1_min + op2_min;
				}
				if (OP1_RANGE_OVERFLOW() ||
					OP2_RANGE_OVERFLOW() ||
					zend_add_will_overflow(op1_max, op2_max)) {
					tmp->overflow = 1;
					tmp->max = ZEND_LONG_MAX;
				} else {
					tmp->max = op1_max + op2_max;
				}
				return 1;
			}
			break;
		case ZEND_SUB:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				op1_min = OP1_MIN_RANGE();
				op2_min = OP2_MIN_RANGE();
				op1_max = OP1_MAX_RANGE();
				op2_max = OP2_MAX_RANGE();
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_OVERFLOW() ||
					zend_sub_will_overflow(op1_min, op2_max)) {
					tmp->underflow = 1;
					tmp->min = ZEND_LONG_MIN;
				} else {
					tmp->min = op1_min - op2_max;
				}
				if (OP1_RANGE_OVERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					zend_sub_will_overflow(op1_max, op2_min)) {
					tmp->overflow = 1;
					tmp->max = ZEND_LONG_MAX;
				} else {
					tmp->max = op1_max - op2_min;
				}
				return 1;
			}
			break;
		case ZEND_MUL:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				double dummy;
				zend_long t1_overflow, t2_overflow, t3_overflow, t4_overflow;
				op1_min = OP1_MIN_RANGE();
				op2_min = OP2_MIN_RANGE();
				op1_max = OP1_MAX_RANGE();
				op2_max = OP2_MAX_RANGE();
				/* Suppress uninit variable warnings, these will only be used if the overflow
				 * flags are all false. */
				t1 = t2 = t3 = t4 = 0;
				ZEND_SIGNED_MULTIPLY_LONG(op1_min, op2_min, t1, dummy, t1_overflow);
				ZEND_SIGNED_MULTIPLY_LONG(op1_min, op2_max, t2, dummy, t2_overflow);
				ZEND_SIGNED_MULTIPLY_LONG(op1_max, op2_min, t3, dummy, t3_overflow);
				ZEND_SIGNED_MULTIPLY_LONG(op1_max, op2_max, t4, dummy, t4_overflow);
				(void) dummy;

				// FIXME: more careful overflow checks?
				if (OP1_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW()  ||
					t1_overflow || t2_overflow || t3_overflow || t4_overflow
				) {
					tmp->underflow = 1;
					tmp->overflow = 1;
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
					tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
				}
				return 1;
			}
			break;
		case ZEND_DIV:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				op1_min = OP1_MIN_RANGE();
				op2_min = OP2_MIN_RANGE();
				op1_max = OP1_MAX_RANGE();
				op2_max = OP2_MAX_RANGE();

				/* If op2 crosses zero, then floating point values close to zero might be
				 * possible, which will result in arbitrarily large results (overflow). Also
				 * avoid dividing LONG_MIN by -1, which is UB. */
				if (OP1_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() ||
					(op2_min <= 0 && op2_max >= 0) ||
					(op1_min == ZEND_LONG_MIN && op2_max == -1)
				) {
					tmp->underflow = 1;
					tmp->overflow = 1;
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					zend_long t1_, t2_, t3_, t4_;
					float_div(op1_min, op2_min, &t1, &t1_);
					float_div(op1_min, op2_max, &t2, &t2_);
					float_div(op1_max, op2_min, &t3, &t3_);
					float_div(op1_max, op2_max, &t4, &t4_);

					tmp->min = MIN(MIN(MIN(t1, t2), MIN(t3, t4)), MIN(MIN(t1_, t2_), MIN(t3_, t4_)));
					tmp->max = MAX(MAX(MAX(t1, t2), MAX(t3, t4)), MAX(MAX(t1_, t2_), MAX(t3_, t4_)));
				}
				return 1;
			}
			break;
		case ZEND_MOD:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW()  ||
					OP2_RANGE_OVERFLOW()) {
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					zend_long op2_abs_min, op2_abs_max;

					op1_min = OP1_MIN_RANGE();
					op2_min = OP2_MIN_RANGE();
					op1_max = OP1_MAX_RANGE();
					op2_max = OP2_MAX_RANGE();
					if (!zend_abs_range(op2_min, op2_max, &op2_abs_min, &op2_abs_max)) {
						break;
					}

					if (op2_abs_max == 0) {
						/* Always modulus by zero, nothing we can do */
						break;
					}
					if (op2_abs_min == 0) {
						/* Ignore the modulus by zero case, which will throw */
						op2_abs_min++;
					}

					if (op1_min >= 0) {
						tmp->min = op1_max < op2_abs_min ? op1_min : 0;
						tmp->max = MIN(op1_max, op2_abs_max - 1);
					} else if (op1_max <= 0) {
						tmp->min = MAX(op1_min, -op2_abs_max + 1);
						tmp->max = op1_min > -op2_abs_min ? op1_max : 0;
					} else {
						tmp->min = MAX(op1_min, -op2_abs_max + 1);
						tmp->max = MIN(op1_max, op2_abs_max - 1);
					}
				}
				return 1;
			}
			break;
		case ZEND_SL:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW() ||
					OP2_RANGE_OVERFLOW()) {
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					op1_min = OP1_MIN_RANGE();
					op2_min = OP2_MIN_RANGE();
					op1_max = OP1_MAX_RANGE();
					op2_max = OP2_MAX_RANGE();

					/* Shifts by negative numbers will throw, ignore them */
					if (op2_min < 0) {
						op2_min = 0;
					}
					if (op2_max < 0) {
						op2_max = 0;
					}

					if (shift_left_overflows(op1_min, op2_max)
							|| shift_left_overflows(op1_max, op2_max)) {
						tmp->min = ZEND_LONG_MIN;
						tmp->max = ZEND_LONG_MAX;
					} else {
						t1 = safe_shift_left(op1_min, op2_min);
						t2 = safe_shift_left(op1_min, op2_max);
						t3 = safe_shift_left(op1_max, op2_min);
						t4 = safe_shift_left(op1_max, op2_max);
						tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
						tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
					}
				}
				return 1;
			}
			break;
		case ZEND_SR:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW() ||
					OP2_RANGE_OVERFLOW()) {
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					op1_min = OP1_MIN_RANGE();
					op2_min = OP2_MIN_RANGE();
					op1_max = OP1_MAX_RANGE();
					op2_max = OP2_MAX_RANGE();

					/* Shifts by negative numbers will throw, ignore them */
					if (op2_min < 0) {
						op2_min = 0;
					}
					if (op2_max < 0) {
						op2_max = 0;
					}

					/* Shifts by more than the integer size will be 0 or -1 */
					if (op2_min >= SIZEOF_ZEND_LONG * 8) {
						op2_min = SIZEOF_ZEND_LONG * 8 - 1;
					}
					if (op2_max >= SIZEOF_ZEND_LONG * 8) {
						op2_max = SIZEOF_ZEND_LONG * 8 - 1;
					}

					t1 = op1_min >> op2_min;
					t2 = op1_min >> op2_max;
					t3 = op1_max >> op2_min;
					t4 = op1_max >> op2_max;
					tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
					tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
				}
				return 1;
			}
			break;
		case ZEND_BW_OR:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW() ||
					OP2_RANGE_OVERFLOW()) {
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					op1_min = OP1_MIN_RANGE();
					op2_min = OP2_MIN_RANGE();
					op1_max = OP1_MAX_RANGE();
					op2_max = OP2_MAX_RANGE();
					zend_ssa_range_or(op1_min, op1_max, op2_min, op2_max, tmp);
				}
				return 1;
			}
			break;
		case ZEND_BW_AND:
			if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
				if (OP1_RANGE_UNDERFLOW() ||
					OP2_RANGE_UNDERFLOW() ||
					OP1_RANGE_OVERFLOW() ||
					OP2_RANGE_OVERFLOW()) {
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
				} else {
					op1_min = OP1_MIN_RANGE();
					op2_min = OP2_MIN_RANGE();
					op1_max = OP1_MAX_RANGE();
					op2_max = OP2_MAX_RANGE();
					zend_ssa_range_and(op1_min, op1_max, op2_min, op2_max, tmp);
				}
				return 1;
			}
			break;
		case ZEND_BW_XOR:
			// TODO
			break;
		EMPTY_SWITCH_DEFAULT_CASE()
	}
	return 0;
}

static bool zend_inference_calc_range(const zend_op_array *op_array, const zend_ssa *ssa, int var, int widening, int narrowing, zend_ssa_range *tmp)
{
	uint32_t line;
	const zend_op *opline;
	const zend_ssa_op *ssa_op;

	if (ssa->vars[var].definition_phi) {
		const zend_ssa_phi *p = ssa->vars[var].definition_phi;
		int i;

		tmp->underflow = 0;
		tmp->min = ZEND_LONG_MAX;
		tmp->max = ZEND_LONG_MIN;
		tmp->overflow = 0;
		if (p->pi >= 0 && p->has_range_constraint) {
			const zend_ssa_range_constraint *constraint = &p->constraint.range;
			if (constraint->negative) {
				int src1 = p->sources[0];

				if (ssa->var_info[src1].has_range) {
					*tmp = ssa->var_info[src1].range;
					if (constraint->range.min == constraint->range.max
					 && !constraint->range.underflow
					 && !constraint->range.overflow
					 && p->constraint.range.min_ssa_var < 0
					 && p->constraint.range.max_ssa_var < 0
					 && ssa->vars[src1].definition >= 0) {
						/* Check for constrained induction variable */
						line = ssa->vars[src1].definition;
						opline = op_array->opcodes + line;
						switch (opline->opcode) {
							case ZEND_PRE_DEC:
							case ZEND_POST_DEC:
								if (!tmp->underflow) {
									const zend_ssa_phi *p = ssa->vars[ssa->ops[line].op1_use].definition_phi;

									if (p && p->pi < 0
									 && ssa->cfg.blocks[p->block].predecessors_count == 2
									 && p->sources[1] == var
									 && ssa->var_info[p->sources[0]].has_range
									 && ssa->var_info[p->sources[0]].range.min > constraint->range.max) {
										tmp->min = constraint->range.max + 1;
									}
								}
								break;
							case ZEND_PRE_INC:
							case ZEND_POST_INC:
								if (!tmp->overflow) {
									const zend_ssa_phi *p = ssa->vars[ssa->ops[line].op1_use].definition_phi;

									if (p && p->pi < 0
									 && ssa->cfg.blocks[p->block].predecessors_count == 2
									 && p->sources[1] == var
									 && ssa->var_info[p->sources[0]].has_range
									 && ssa->var_info[p->sources[0]].range.max < constraint->range.min) {
										tmp->max = constraint->range.min - 1;
									}
								}
								break;
						}
					}
				} else if (narrowing) {
					tmp->underflow = 1;
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
					tmp->overflow = 1;
				}

#ifdef NEG_RANGE
				if (constraint->min_ssa_var < 0 &&
				    constraint->max_ssa_var < 0 &&
				    ssa->var_info[p->ssa_var].has_range) {
					LOG_NEG_RANGE("%s() #%d [%ld..%ld] -> [%ld..%ld]?\n",
						ZSTR_VAL(op_array->function_name),
						p->ssa_var,
						ssa->var_info[p->ssa_var].range.min,
						ssa->var_info[p->ssa_var].range.max,
						tmp->min,
						tmp->max);
					if (constraint->negative == NEG_USE_LT &&
					    tmp->max >= constraint->range.min) {
						tmp->overflow = 0;
						tmp->max = constraint->range.min - 1;
						LOG_NEG_RANGE("  => [%ld..%ld]\n", tmp->min, tmp->max);
					} else if (constraint->negative == NEG_USE_GT &&
					           tmp->min <= constraint->range.max) {
						tmp->underflow = 0;
						tmp->min = constraint->range.max + 1;
						LOG_NEG_RANGE("  => [%ld..%ld]\n", tmp->min, tmp->max);
					}
				}
#endif
			} else if (ssa->var_info[p->sources[0]].has_range) {
				/* intersection */
				*tmp = ssa->var_info[p->sources[0]].range;
				if (constraint->min_ssa_var < 0) {
					tmp->underflow = constraint->range.underflow && tmp->underflow;
					tmp->min = MAX(constraint->range.min, tmp->min);
#ifdef SYM_RANGE
				} else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
					tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow && tmp->underflow;
					if (!add_will_overflow(ssa->var_info[constraint->min_ssa_var].range.min, constraint->range.min)) {
						tmp->min = MAX(ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min, tmp->min);
					}
#endif
				}
				if (constraint->max_ssa_var < 0) {
					tmp->max = MIN(constraint->range.max, tmp->max);
					tmp->overflow = constraint->range.overflow && tmp->overflow;
#ifdef SYM_RANGE
				} else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
					if (!add_will_overflow(ssa->var_info[constraint->max_ssa_var].range.max, constraint->range.max)) {
						tmp->max = MIN(ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max, tmp->max);
					}
					tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow && tmp->overflow;
#endif
				}
			} else if (narrowing) {
				if (constraint->min_ssa_var < 0) {
					tmp->underflow = constraint->range.underflow;
					tmp->min = constraint->range.min;
#ifdef SYM_RANGE
				} else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
					if (add_will_overflow(ssa->var_info[constraint->min_ssa_var].range.min, constraint->range.min)) {
						tmp->underflow = 1;
						tmp->min = ZEND_LONG_MIN;
					} else {
						tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow;
						tmp->min = ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min;
					}
#endif
				} else {
					tmp->underflow = 1;
					tmp->min = ZEND_LONG_MIN;
				}
				if (constraint->max_ssa_var < 0) {
					tmp->max = constraint->range.max;
					tmp->overflow = constraint->range.overflow;
#ifdef SYM_RANGE
				} else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
					if (add_will_overflow(ssa->var_info[constraint->max_ssa_var].range.max, constraint->range.max)) {
						tmp->overflow = 1;
						tmp->max = ZEND_LONG_MAX;
					} else {
						tmp->max = ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max;
						tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow;
					}
#endif
				} else {
					tmp->max = ZEND_LONG_MAX;
					tmp->overflow = 1;
				}
			}
		} else {
			for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
				ZEND_ASSERT(p->sources[i] >= 0);
				if (ssa->var_info[p->sources[i]].has_range) {
					/* union */
					tmp->underflow |= ssa->var_info[p->sources[i]].range.underflow;
					tmp->min = MIN(tmp->min, ssa->var_info[p->sources[i]].range.min);
					tmp->max = MAX(tmp->max, ssa->var_info[p->sources[i]].range.max);
					tmp->overflow |= ssa->var_info[p->sources[i]].range.overflow;
				} else if (narrowing) {
					tmp->underflow = 1;
					tmp->min = ZEND_LONG_MIN;
					tmp->max = ZEND_LONG_MAX;
					tmp->overflow = 1;
				}
			}
		}
		return (tmp->min <= tmp->max);
	} else if (ssa->vars[var].definition < 0) {
		return 0;
	}
	line = ssa->vars[var].definition;
	opline = op_array->opcodes + line;
	ssa_op = &ssa->ops[line];

	return zend_inference_propagate_range(op_array, ssa, opline, ssa_op, var, tmp);
}

ZEND_API bool zend_inference_propagate_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp)
{
	tmp->underflow = 0;
	tmp->overflow = 0;
	switch (opline->opcode) {
		case ZEND_ADD:
		case ZEND_SUB:
		case ZEND_MUL:
		case ZEND_DIV:
		case ZEND_MOD:
		case ZEND_SL:
		case ZEND_SR:
		case ZEND_BW_OR:
		case ZEND_BW_AND:
		case ZEND_BW_XOR:
			if (ssa_op->result_def == var) {
				return zend_inference_calc_binary_op_range(
					op_array, ssa, opline, ssa_op, opline->opcode, tmp);
			}
			break;

		case ZEND_BW_NOT:
			if (ssa_op->result_def == var) {
				if (OP1_HAS_RANGE()) {
					if (OP1_RANGE_UNDERFLOW() ||
					    OP1_RANGE_OVERFLOW()) {
						tmp->min = ZEND_LONG_MIN;
						tmp->max = ZEND_LONG_MAX;
					} else {
						zend_long op1_min = OP1_MIN_RANGE();
						zend_long op1_max = OP1_MAX_RANGE();
						tmp->min = ~op1_max;
						tmp->max = ~op1_min;
					}
					return 1;
				}
			}
			break;
		case ZEND_CAST:
			if (ssa_op->op1_def == var) {
				if (ssa_op->op1_def >= 0) {
					if (OP1_HAS_RANGE()) {
						tmp->underflow = OP1_RANGE_UNDERFLOW();
						tmp->min = OP1_MIN_RANGE();
						tmp->max = OP1_MAX_RANGE();
						tmp->overflow  = OP1_RANGE_OVERFLOW();
						return 1;
					}
				}
			} else if (ssa_op->result_def == var) {
				if (opline->extended_value == IS_LONG) {
					if (OP1_HAS_RANGE()) {
						tmp->min = OP1_MIN_RANGE();
						tmp->max = OP1_MAX_RANGE();
						return 1;
					} else {
						tmp->min = ZEND_LONG_MIN;
						tmp->max = ZEND_LONG_MAX;
						return 1;
					}
				}
			}
			break;
		case ZEND_QM_ASSIGN:
		case ZEND_JMP_SET:
		case ZEND_COALESCE:
		case ZEND_COPY_TMP:
			if (ssa_op->op1_def == var) {
				if (ssa_op->op1_def >= 0) {
					if (OP1_HAS_RANGE()) {
						tmp->underflow = OP1_RANGE_UNDERFLOW();
						tmp->min = OP1_MIN_RANGE();
						tmp->max = OP1_MAX_RANGE();
						tmp->overflow  = OP1_RANGE_OVERFLOW();
						return 1;
					}
				}
			}
			if (ssa_op->result_def == var) {
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow  = OP1_RANGE_OVERFLOW();
					return 1;
				}
			}
			break;
		case ZEND_SEND_VAR:
			if (ssa_op->op1_def == var) {
				if (ssa_op->op1_def >= 0) {
					if (OP1_HAS_RANGE()) {
						tmp->underflow = OP1_RANGE_UNDERFLOW();
						tmp->min = OP1_MIN_RANGE();
						tmp->max = OP1_MAX_RANGE();
						tmp->overflow  = OP1_RANGE_OVERFLOW();
						return 1;
					}
				}
			}
			break;
		case ZEND_PRE_INC:
			if (ssa_op->op1_def == var || ssa_op->result_def == var) {
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow = OP1_RANGE_OVERFLOW();
					if (tmp->max < ZEND_LONG_MAX) {
						tmp->max++;
					} else {
						tmp->overflow = 1;
					}
					if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) {
						tmp->min++;
					}
					return 1;
				}
			}
			break;
		case ZEND_PRE_DEC:
			if (ssa_op->op1_def == var || ssa_op->result_def == var) {
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow = OP1_RANGE_OVERFLOW();
					if (tmp->min > ZEND_LONG_MIN) {
						tmp->min--;
					} else {
						tmp->underflow = 1;
					}
					if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) {
						tmp->max--;
					}
					return 1;
				}
			}
			break;
		case ZEND_POST_INC:
			if (ssa_op->op1_def == var || ssa_op->result_def == var) {
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow = OP1_RANGE_OVERFLOW();
					if (ssa_op->result_def == var) {
						return 1;
					}
					if (tmp->max < ZEND_LONG_MAX) {
						tmp->max++;
					} else {
						tmp->overflow = 1;
					}
					if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) {
						tmp->min++;
					}
					return 1;
				}
			}
			break;
		case ZEND_POST_DEC:
			if (ssa_op->op1_def == var || ssa_op->result_def == var) {
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow = OP1_RANGE_OVERFLOW();
					if (ssa_op->result_def == var) {
						return 1;
					}
					if (tmp->min > ZEND_LONG_MIN) {
						tmp->min--;
					} else {
						tmp->underflow = 1;
					}
					if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) {
						tmp->max--;
					}
					return 1;
				}
			}
			break;
		case ZEND_UNSET_DIM:
		case ZEND_UNSET_OBJ:
			if (ssa_op->op1_def == var) {
				/* If op1 is scalar, UNSET_DIM and UNSET_OBJ have no effect, so we can keep
				 * the previous ranges. */
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow  = OP1_RANGE_OVERFLOW();
					return 1;
				}
			}
			break;
		case ZEND_ASSIGN:
			if (ssa_op->op1_def == var || ssa_op->op2_def == var || ssa_op->result_def == var) {
				if (OP2_HAS_RANGE()) {
					tmp->min = OP2_MIN_RANGE();
					tmp->max = OP2_MAX_RANGE();
					tmp->underflow = OP2_RANGE_UNDERFLOW();
					tmp->overflow  = OP2_RANGE_OVERFLOW();
					return 1;
				}
			}
			break;
		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:
			if ((ssa_op+1)->op1_def == var) {
				opline++;
				ssa_op++;
				if (OP1_HAS_RANGE()) {
					tmp->min = OP1_MIN_RANGE();
					tmp->max = OP1_MAX_RANGE();
					tmp->underflow = OP1_RANGE_UNDERFLOW();
					tmp->overflow  = OP1_RANGE_OVERFLOW();
				}
				return 1;
			}
			break;
		case ZEND_ASSIGN_OP:
			if (opline->extended_value != ZEND_CONCAT
			 && opline->extended_value != ZEND_POW) {
				if (ssa_op->op1_def == var || ssa_op->result_def == var) {
					return zend_inference_calc_binary_op_range(
						op_array, ssa, opline, ssa_op,
						opline->extended_value, tmp);
				}
			}
			break;
		case ZEND_OP_DATA:
			if (ssa_op->op1_def == var) {
				if ((opline-1)->opcode == ZEND_ASSIGN_DIM ||
				    (opline-1)->opcode == ZEND_ASSIGN_OBJ ||
				    (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP ||
				    (opline-1)->opcode == ZEND_ASSIGN_DIM_OP ||
				    (opline-1)->opcode == ZEND_ASSIGN_OBJ_OP ||
				    (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
					if (OP1_HAS_RANGE()) {
						tmp->min = OP1_MIN_RANGE();
						tmp->max = OP1_MAX_RANGE();
						tmp->underflow = OP1_RANGE_UNDERFLOW();
						tmp->overflow  = OP1_RANGE_OVERFLOW();
						return 1;
					}
				}
				break;
			}
			break;
		case ZEND_RECV:
		case ZEND_RECV_INIT:
			if (ssa_op->result_def == var) {
				if (op_array->arg_info &&
				    opline->op1.num <= op_array->num_args) {
					zend_type type = op_array->arg_info[opline->op1.num-1].type;
					uint32_t mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
					if (mask == MAY_BE_LONG) {
						tmp->underflow = 0;
						tmp->min = ZEND_LONG_MIN;
						tmp->max = ZEND_LONG_MAX;
						tmp->overflow = 0;
						return 1;
					}
				}
			}
			break;
		case ZEND_STRLEN:
			if (ssa_op->result_def == var) {
#if SIZEOF_ZEND_LONG == 4
				/* The length of a string is a non-negative integer. However, on 32-bit
				 * platforms overflows into negative lengths may occur, so it's better
				 * to not assume any particular range. */
				tmp->min = ZEND_LONG_MIN;
#else
				tmp->min = 0;
#endif
				tmp->max = ZEND_LONG_MAX;
				return 1;
			}
			break;
		case ZEND_FUNC_NUM_ARGS:
			tmp->min = 0;
			tmp->max = ZEND_LONG_MAX;
			return 1;
		case ZEND_COUNT:
			/* count() on Countable objects may return negative numbers */
			tmp->min = ZEND_LONG_MIN;
			tmp->max = ZEND_LONG_MAX;
			return 1;
		case ZEND_DO_FCALL:
		case ZEND_DO_ICALL:
		case ZEND_DO_UCALL:
		case ZEND_DO_FCALL_BY_NAME:
			if (ssa_op->result_def == var) {
				const zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
				const zend_call_info *call_info;
				if (!func_info || !func_info->call_map) {
					break;
				}

				call_info = func_info->call_map[opline - op_array->opcodes];
				if (!call_info || call_info->is_prototype) {
					break;
				}
				if (call_info->callee_func->type == ZEND_USER_FUNCTION) {
					func_info = ZEND_FUNC_INFO(&call_info->callee_func->op_array);
					if (func_info && func_info->return_info.has_range) {
						*tmp = func_info->return_info.range;
						return 1;
					}
				}
//TODO: we can't use type inference for internal functions at this point ???
#if 0
					uint32_t type;

					type = zend_get_func_info(call_info, ssa);
					if (!(type & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)))) {
						tmp->underflow = 0;
						tmp->min = 0;
						tmp->max = 0;
						tmp->overflow = 0;
						if (type & MAY_BE_LONG) {
							tmp->min = ZEND_LONG_MIN;
							tmp->max = ZEND_LONG_MAX;
						} else if (type & MAY_BE_TRUE) {
							if (!(type & (MAY_BE_NULL|MAY_BE_FALSE))) {
								tmp->min = 1;
							}
							tmp->max = 1;
						}
						return 1;
					}
#endif
			}
			break;
		// FIXME: support for more opcodes
		default:
			break;
	}
	return 0;
}

static void zend_inference_init_range(const zend_op_array *op_array, zend_ssa *ssa, int var, bool underflow, zend_long min, zend_long max, bool overflow)
{
	if (underflow) {
		min = ZEND_LONG_MIN;
	}
	if (overflow) {
		max = ZEND_LONG_MAX;
	}
	ssa->var_info[var].has_range = 1;
	ssa->var_info[var].range.underflow = underflow;
	ssa->var_info[var].range.min = min;
	ssa->var_info[var].range.max = max;
	ssa->var_info[var].range.overflow = overflow;
	LOG_SSA_RANGE("  change range (init      SCC %2d) %2d [%s%ld..%ld%s]\n", ssa->vars[var].scc, var, (underflow?"-- ":""), min, max, (overflow?" ++":""));
}

static bool zend_inference_widening_meet(zend_ssa_var_info *var_info, zend_ssa_range *r)
{
	if (!var_info->has_range) {
		var_info->has_range = 1;
	} else {
		if (r->underflow ||
		    var_info->range.underflow ||
		    r->min < var_info->range.min) {
			r->underflow = 1;
			r->min = ZEND_LONG_MIN;
		} else {
			r->min = var_info->range.min;
		}
		if (r->overflow ||
		    var_info->range.overflow ||
		    r->max > var_info->range.max) {
			r->overflow = 1;
			r->max = ZEND_LONG_MAX;
		} else {
			r->max = var_info->range.max;
		}
		if (var_info->range.min == r->min &&
		    var_info->range.max == r->max &&
		    var_info->range.underflow == r->underflow &&
		    var_info->range.overflow == r->overflow) {
			return 0;
		}
	}
	var_info->range = *r;
	return 1;
}

static bool zend_ssa_range_widening(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc)
{
	zend_ssa_range tmp;

	if (zend_inference_calc_range(op_array, ssa, var, 1, 0, &tmp)) {
		if (zend_inference_widening_meet(&ssa->var_info[var], &tmp)) {
			LOG_SSA_RANGE("  change range (widening  SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
			return 1;
		}
	}
	return 0;
}

static bool zend_inference_narrowing_meet(zend_ssa_var_info *var_info, zend_ssa_range *r)
{
	if (!var_info->has_range) {
		var_info->has_range = 1;
	} else {
		if (!r->underflow &&
		    !var_info->range.underflow &&
		    var_info->range.min < r->min) {
			r->min = var_info->range.min;
		}
		if (!r->overflow &&
		    !var_info->range.overflow &&
		    var_info->range.max > r->max) {
			r->max = var_info->range.max;
		}
		if (r->underflow) {
			r->min = ZEND_LONG_MIN;
		}
		if (r->overflow) {
			r->max = ZEND_LONG_MAX;
		}
		if (var_info->range.min == r->min &&
		    var_info->range.max == r->max &&
		    var_info->range.underflow == r->underflow &&
		    var_info->range.overflow == r->overflow) {
			return 0;
		}
	}
	var_info->range = *r;
	return 1;
}

static bool zend_ssa_range_narrowing(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc)
{
	zend_ssa_range tmp;

	if (zend_inference_calc_range(op_array, ssa, var, 0, 1, &tmp)) {
		if (zend_inference_narrowing_meet(&ssa->var_info[var], &tmp)) {
			LOG_SSA_RANGE("  change range (narrowing SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
			return 1;
		}
	}
	return 0;
}

#ifdef NEG_RANGE
# define CHECK_INNER_CYCLE(var2) \
	do { \
		if (ssa->vars[var2].scc == ssa->vars[var].scc && \
		    !ssa->vars[var2].scc_entry && \
		    !zend_bitset_in(visited, var2) && \
			zend_check_inner_cycles(op_array, ssa, worklist, visited, var2)) { \
			return 1; \
		} \
	} while (0)

static bool zend_check_inner_cycles(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, zend_bitset visited, int var)
{
	if (zend_bitset_in(worklist, var)) {
		return 1;
	}
	zend_bitset_incl(worklist, var);
	FOR_EACH_VAR_USAGE(var, CHECK_INNER_CYCLE);
	zend_bitset_incl(visited, var);
	return 0;
}
#endif

static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ssa, const int *scc_var, const int *next_scc_var, int scc)
{
	int worklist_len = zend_bitset_len(ssa->vars_count);
	int j, n;
	zend_ssa_range tmp;
	ALLOCA_FLAG(use_heap)
	zend_bitset worklist = do_alloca(sizeof(zend_ulong) * worklist_len * 2, use_heap);
	zend_bitset visited = worklist + worklist_len;
#ifdef NEG_RANGE
	int has_inner_cycles = 0;

	memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
	memset(visited, 0, sizeof(zend_ulong) * worklist_len);
	j = scc_var[scc];
	while (j >= 0) {
		if (!zend_bitset_in(visited, j) &&
		    zend_check_inner_cycles(op_array, ssa, worklist, visited, j)) {
			has_inner_cycles = 1;
			break;
		}
		j = next_scc_var[j];
	}
#endif

	memset(worklist, 0, sizeof(zend_ulong) * worklist_len);

	for (n = 0; n < RANGE_WARMUP_PASSES; n++) {
		j= scc_var[scc];
		while (j >= 0) {
			if (ssa->vars[j].scc_entry
			 && !(ssa->var_info[j].type & MAY_BE_REF)) {
				zend_bitset_incl(worklist, j);
			}
			j = next_scc_var[j];
		}

		memset(visited, 0, sizeof(zend_ulong) * worklist_len);

		WHILE_WORKLIST(worklist, worklist_len, j) {
			if (zend_inference_calc_range(op_array, ssa, j, 0, 0, &tmp)) {
#ifdef NEG_RANGE
				if (!has_inner_cycles &&
				    ssa->var_info[j].has_range &&
				    ssa->vars[j].definition_phi &&
				    ssa->vars[j].definition_phi->pi >= 0 &&
					ssa->vars[j].definition_phi->has_range_constraint &&
				    ssa->vars[j].definition_phi->constraint.range.negative &&
				    ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0 &&
				    ssa->vars[j].definition_phi->constraint.range.max_ssa_var < 0) {
					zend_ssa_range_constraint *constraint =
						&ssa->vars[j].definition_phi->constraint.range;
					if (tmp.min == ssa->var_info[j].range.min &&
					    tmp.max == ssa->var_info[j].range.max) {
						if (constraint->negative == NEG_INIT) {
							LOG_NEG_RANGE("#%d INVARIANT\n", j);
							constraint->negative = NEG_INVARIANT;
						}
					} else if (tmp.min == ssa->var_info[j].range.min &&
					           tmp.max == ssa->var_info[j].range.max + 1 &&
					           tmp.max < constraint->range.min) {
						if (constraint->negative == NEG_INIT ||
						    constraint->negative == NEG_INVARIANT) {
							LOG_NEG_RANGE("#%d LT\n", j);
							constraint->negative = NEG_USE_LT;
//???NEG
						} else if (constraint->negative == NEG_USE_GT) {
							LOG_NEG_RANGE("#%d UNKNOWN\n", j);
							constraint->negative = NEG_UNKNOWN;
						}
					} else if (tmp.max == ssa->var_info[j].range.max &&
				               tmp.min == ssa->var_info[j].range.min - 1 &&
					           tmp.min > constraint->range.max) {
						if (constraint->negative == NEG_INIT ||
						    constraint->negative == NEG_INVARIANT) {
							LOG_NEG_RANGE("#%d GT\n", j);
							constraint->negative = NEG_USE_GT;
//???NEG
						} else if (constraint->negative == NEG_USE_LT) {
							LOG_NEG_RANGE("#%d UNKNOWN\n", j);
							constraint->negative = NEG_UNKNOWN;
						}
					} else {
						LOG_NEG_RANGE("#%d UNKNOWN\n", j);
						constraint->negative = NEG_UNKNOWN;
					}
				}
#endif
				if (zend_inference_narrowing_meet(&ssa->var_info[j], &tmp)) {
					LOG_SSA_RANGE("  change range (warmup %2d SCC %2d) %2d [%s%ld..%ld%s]\n", n, scc, j, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
					zend_bitset_incl(visited, j);
					FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR_1);
				}
			}
		} WHILE_WORKLIST_END();
	}
	free_alloca(worklist, use_heap);
}

static void zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
	int worklist_len = zend_bitset_len(ssa->vars_count);
	zend_bitset worklist;
	int *next_scc_var;
	int *scc_var;
	zend_ssa_phi *p;
	zend_ssa_range tmp;
	int scc, j;
	ALLOCA_FLAG(use_heap);

	worklist = do_alloca(
		ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len) +
		ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count) +
		sizeof(int) * ssa->sccs, use_heap);
	next_scc_var = (int*)((char*)worklist + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len));
	scc_var = (int*)((char*)next_scc_var + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count));

	LOG_SSA_RANGE("Range Inference\n");

	/* Create linked lists of SSA variables for each SCC */
	memset(scc_var, -1, sizeof(int) * ssa->sccs);
	for (j = 0; j < ssa->vars_count; j++) {
		if (ssa->vars[j].scc >= 0) {
			next_scc_var[j] = scc_var[ssa->vars[j].scc];
			scc_var[ssa->vars[j].scc] = j;
		}
	}

	for (scc = 0; scc < ssa->sccs; scc++) {
		j = scc_var[scc];
		if (next_scc_var[j] < 0) {
			/* SCC with a single element */
			if (ssa->var_info[j].type & MAY_BE_REF) {
				/* pass */
			} else if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
				zend_inference_init_range(op_array, ssa, j, tmp.underflow, tmp.min, tmp.max, tmp.overflow);
			} else {
				zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
			}
		} else {
			/* Find SCC entry points */
			memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
			do {
				if (ssa->vars[j].scc_entry
				 && !(ssa->var_info[j].type & MAY_BE_REF)) {
					zend_bitset_incl(worklist, j);
				}
				j = next_scc_var[j];
			} while (j >= 0);

#if RANGE_WARMUP_PASSES > 0
			zend_infer_ranges_warmup(op_array, ssa, scc_var, next_scc_var, scc);
			j = scc_var[scc];
			do {
				if (!(ssa->var_info[j].type & MAY_BE_REF)) {
					zend_bitset_incl(worklist, j);
				}
				j = next_scc_var[j];
			} while (j >= 0);
#endif

			/* widening */
			WHILE_WORKLIST(worklist, worklist_len, j) {
				if (zend_ssa_range_widening(op_array, ssa, j, scc)) {
					FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
				}
			} WHILE_WORKLIST_END();

			/* initialize missing ranges */
			for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
				if (!ssa->var_info[j].has_range
				 && !(ssa->var_info[j].type & MAY_BE_REF)) {
					zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
					FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
				}
			}

			/* widening (second round) */
			WHILE_WORKLIST(worklist, worklist_len, j) {
				if (zend_ssa_range_widening(op_array, ssa, j, scc)) {
					FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
				}
			} WHILE_WORKLIST_END();

			/* Add all SCC entry variables into worklist for narrowing */
			for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
				if (ssa->vars[j].definition_phi
				 && ssa->vars[j].definition_phi->pi < 0
				 && !(ssa->var_info[j].type & MAY_BE_REF)) {
					/* narrowing Phi functions first */
					zend_ssa_range_narrowing(op_array, ssa, j, scc);
				}
				zend_bitset_incl(worklist, j);
			}

			/* narrowing */
			WHILE_WORKLIST(worklist, worklist_len, j) {
				if (zend_ssa_range_narrowing(op_array, ssa, j, scc)) {
					FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
#ifdef SYM_RANGE
					/* Process symbolic control-flow constraints */
					p = ssa->vars[j].sym_use_chain;
					while (p) {
						ADD_SCC_VAR(p->ssa_var);
						p = p->sym_use_chain;
					}
#endif
				}
			} WHILE_WORKLIST_END();
		}
	}

	free_alloca(worklist, use_heap);
}
/* }}} */

static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
	if (alias == HTTP_RESPONSE_HEADER_ALIAS) {
		return MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_RC1 | MAY_BE_RCN;
	} else {
		return 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;
	}
}

#define UPDATE_SSA_TYPE(_type, _var)									\
	do {																\
		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; \
		}																\
		if (__var >= 0) {												\
			zend_ssa_var *__ssa_var = &ssa_vars[__var];					\
			if (__ssa_var->var < op_array->num_args) {					\
				if (__type & MAY_BE_RC1) {                              \
					/* TODO: may be captured by exception backtreace */ \
					__type |= MAY_BE_RCN;                               \
				}                                                       \
			}                                                           \
			if (__ssa_var->var < op_array->last_var) {					\
				if (__type & (MAY_BE_REF|MAY_BE_RCN)) {					\
					__type |= MAY_BE_RC1 | MAY_BE_RCN;					\
				}														\
				if ((__type & MAY_BE_RC1) && (__type & MAY_BE_STRING)) {\
					/* TODO: support for array keys and ($str . "")*/   \
					__type |= MAY_BE_RCN;                               \
				}                                                       \
				if ((__type & MAY_BE_RC1) && (__type & MAY_BE_OBJECT)) {\
					/* TODO: object may be captured by magic handlers */\
					__type |= MAY_BE_RCN;                               \
				}                                                       \
				if (__ssa_var->alias) {									\
					__type |= get_ssa_alias_types(__ssa_var->alias);	\
				}														\
			}															\
			if (ssa_var_info[__var].type != __type) { 					\
				ZEND_ASSERT(ssa_opcodes != NULL ||						\
					__ssa_var->var >= op_array->last_var ||				\
					(ssa_var_info[__var].type & MAY_BE_REF)				\
						== (__type & MAY_BE_REF));						\
				if (ssa_var_info[__var].type & ~__type) {				\
					emit_type_narrowing_warning(op_array, ssa, __var);	\
					return FAILURE;										\
				}														\
				ssa_var_info[__var].type = __type;						\
				if (update_worklist) { 									\
					add_usages(op_array, ssa, worklist, __var);			\
				}														\
			}															\
			/*zend_bitset_excl(worklist, var);*/						\
		}																\
	} while (0)

#define UPDATE_SSA_OBJ_TYPE(_ce, _is_instanceof, var)				    \
	do {                                                                \
		if (var >= 0) {													\
			zend_class_entry *__ce = (_ce);								\
			bool __is_instanceof = (_is_instanceof);					\
			if (__ce && (__ce->ce_flags & ZEND_ACC_FINAL)) {			\
				__is_instanceof = false;								\
			}															\
			if (ssa_var_info[var].ce != __ce ||							\
			    ssa_var_info[var].is_instanceof != __is_instanceof) {	\
				ssa_var_info[var].ce = __ce;							\
				ssa_var_info[var].is_instanceof = __is_instanceof;		\
				if (update_worklist) { 									\
					add_usages(op_array, ssa, worklist, var);			\
				}														\
			}															\
			/*zend_bitset_excl(worklist, var);*/						\
		}																\
	} while (0)

#define COPY_SSA_OBJ_TYPE(from_var, to_var) do { \
	if ((from_var) >= 0 && (ssa_var_info[(from_var)].type & MAY_BE_OBJECT) \
			&& ssa_var_info[(from_var)].ce && !(ssa_var_info[(to_var)].type & MAY_BE_REF)) { \
		UPDATE_SSA_OBJ_TYPE(ssa_var_info[(from_var)].ce, \
			ssa_var_info[(from_var)].is_instanceof, (to_var)); \
	} else { \
		UPDATE_SSA_OBJ_TYPE(NULL, 0, (to_var)); \
	} \
} while (0)

static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var)
{
	if (ssa->vars[var].phi_use_chain) {
		zend_ssa_phi *p = ssa->vars[var].phi_use_chain;
		do {
			zend_bitset_incl(worklist, p->ssa_var);
			p = zend_ssa_next_use_phi(ssa, var, p);
		} while (p);
	}
	if (ssa->vars[var].use_chain >= 0) {
		int use = ssa->vars[var].use_chain;
		zend_ssa_op *op;

		do {
			op = ssa->ops + use;
			if (op->result_def >= 0) {
				zend_bitset_incl(worklist, op->result_def);
			}
			if (op->op1_def >= 0) {
				zend_bitset_incl(worklist, op->op1_def);
			}
			if (op->op2_def >= 0) {
				zend_bitset_incl(worklist, op->op2_def);
			}
			if (op_array->opcodes[use].opcode == ZEND_OP_DATA) {
				op--;
				if (op->result_def >= 0) {
					zend_bitset_incl(worklist, op->result_def);
				}
				if (op->op1_def >= 0) {
					zend_bitset_incl(worklist, op->op1_def);
				}
				if (op->op2_def >= 0) {
					zend_bitset_incl(worklist, op->op2_def);
				}
			} else if (use + 1 < op_array->last
			 && op_array->opcodes[use + 1].opcode == ZEND_OP_DATA) {
				op++;
				if (op->result_def >= 0) {
					zend_bitset_incl(worklist, op->result_def);
				}
				if (op->op1_def >= 0) {
					zend_bitset_incl(worklist, op->op1_def);
				}
				if (op->op2_def >= 0) {
					zend_bitset_incl(worklist, op->op2_def);
				}
			}
			use = zend_ssa_next_use(ssa->ops, var, use);
		} while (use >= 0);
	}
}

static void emit_type_narrowing_warning(const zend_op_array *op_array, zend_ssa *ssa, int var)
{
	int def_op_num = ssa->vars[var].definition;
	const zend_op *def_opline = def_op_num >= 0 ? &op_array->opcodes[def_op_num] : NULL;
	const char *def_op_name = def_opline ? zend_get_opcode_name(def_opline->opcode) : "PHI";
	uint32_t lineno = def_opline ? def_opline->lineno : 0;
	zend_error_at(
		E_WARNING, op_array->filename, lineno,
		"Narrowing occurred during type inference of %s. Please file a bug report on https://github.com/php/php-src/issues", def_op_name);
#if ZEND_DEBUG
	ZEND_ASSERT(0 && "Narrowing during type inference");
#endif
}

ZEND_API uint32_t ZEND_FASTCALL zend_array_type_info(const zval *zv)
{
	HashTable *ht = Z_ARRVAL_P(zv);
	uint32_t tmp = MAY_BE_ARRAY;
	zend_string *str;
	zval *val;

	if (Z_REFCOUNTED_P(zv)) {
		tmp |= MAY_BE_RC1 | MAY_BE_RCN;
	} else {
		tmp |= MAY_BE_RCN;
	}

	if (zend_hash_num_elements(ht) == 0) {
		tmp |=  MAY_BE_ARRAY_EMPTY;
	} else if (HT_IS_PACKED(ht)) {
		tmp |= MAY_BE_ARRAY_PACKED;
		ZEND_HASH_PACKED_FOREACH_VAL(ht, val) {
			tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
		} ZEND_HASH_FOREACH_END();
	} else {
		ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(ht, str, val) {
			if (str) {
				tmp |= MAY_BE_ARRAY_STRING_HASH;
			} else {
				tmp |= MAY_BE_ARRAY_NUMERIC_HASH;
			}
			tmp |= 1 << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
		} ZEND_HASH_FOREACH_END();
	}
	return tmp;
}


ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, int write, int insert)
{
	uint32_t tmp = 0;

	if (t1 & MAY_BE_OBJECT) {
	    if (!write) {
			/* can't be REF  because of ZVAL_COPY_DEREF() usage */
			tmp |= MAY_BE_ANY | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
		} else {
			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;
	    }
		if (write) {
			tmp |= MAY_BE_INDIRECT;
		}
	}
	if (t1 & MAY_BE_ARRAY) {
		if (insert) {
			tmp |= MAY_BE_NULL;
		} else {
			tmp |= MAY_BE_NULL | ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
			if (tmp & MAY_BE_ARRAY) {
				tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
			}
			if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
				if (!write) {
					/* can't be REF  because of ZVAL_COPY_DEREF() usage */
					tmp |= MAY_BE_RCN;
					if ((op_type & (IS_VAR|IS_TMP_VAR)) && (t1 & MAY_BE_RC1)) {
						tmp |= MAY_BE_RC1;
					}
				} else if (t1 & MAY_BE_ARRAY_OF_REF) {
					tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
				} else {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				}
			}
		}
		if (write) {
			tmp |= MAY_BE_INDIRECT;
		}
	}
	if (t1 & MAY_BE_STRING) {
		tmp |= MAY_BE_STRING | MAY_BE_RC1;
		if (write) {
			tmp |= MAY_BE_NULL;
		}
	}
	if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
		tmp |= MAY_BE_NULL;
		if (write) {
			tmp |= MAY_BE_INDIRECT;
		}
	}
	if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) {
		if (!write) {
			tmp |= MAY_BE_NULL;
		}
	}
	return tmp;
}

static uint32_t assign_dim_array_result_type(
		uint32_t arr_type, uint32_t dim_type, uint32_t value_type, uint8_t dim_op_type) {
	uint32_t tmp = 0;
	/* Only add key type if we have a value type. We want to maintain the invariant that a
	 * key type exists iff a value type exists even in dead code that may use empty types. */
	if (value_type & (MAY_BE_ANY|MAY_BE_UNDEF)) {
		if (value_type & MAY_BE_UNDEF) {
			value_type |= MAY_BE_NULL;
		}
		if (dim_op_type == IS_UNUSED) {
			if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
				tmp |= MAY_BE_ARRAY_PACKED;
			}
			tmp |= MAY_BE_HASH_ONLY(arr_type) ? MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
		} else {
			if (dim_type & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
				if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
					tmp |= MAY_BE_ARRAY_PACKED;
				}
				tmp |= MAY_BE_HASH_ONLY(arr_type) ? MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
			}
			if (dim_type & MAY_BE_STRING) {
				tmp |= MAY_BE_ARRAY_KEY_STRING;
				if (dim_op_type != IS_CONST) {
					// FIXME: numeric string
					if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
						tmp |= MAY_BE_ARRAY_PACKED;
					}
					tmp |= MAY_BE_HASH_ONLY(arr_type) ? MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
				}
			}
			if (dim_type & (MAY_BE_UNDEF|MAY_BE_NULL)) {
				tmp |= MAY_BE_ARRAY_KEY_STRING;
			}
		}
	}
	/* Only add value type if we have a key type. It might be that the key type is illegal
	 * for arrays. */
	if (tmp & MAY_BE_ARRAY_KEY_ANY) {
		tmp |= (value_type & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
	}
	tmp &= ~MAY_BE_ARRAY_EMPTY;
	return tmp;
}

static uint32_t assign_dim_result_type(
		uint32_t arr_type, uint32_t dim_type, uint32_t value_type, uint8_t dim_op_type) {
	uint32_t tmp = arr_type & ~(MAY_BE_RC1|MAY_BE_RCN);

	if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
		tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
		tmp |= MAY_BE_ARRAY|MAY_BE_RC1;
	}
	if (tmp & (MAY_BE_ARRAY|MAY_BE_STRING)) {
		tmp |= MAY_BE_RC1;
	}
	if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
		tmp |= MAY_BE_RC1 | MAY_BE_RCN;
	}
	if (tmp & MAY_BE_ARRAY) {
		tmp |= assign_dim_array_result_type(arr_type, dim_type, value_type, dim_op_type);
	}
	return tmp;
}

/* For binary ops that have compound assignment operators */
static uint32_t binary_op_result_type(
		zend_ssa *ssa, uint8_t opcode, uint32_t t1, uint32_t t2, int result_var,
		zend_long optimization_level) {
	uint32_t tmp = 0;
	uint32_t t1_type = (t1 & MAY_BE_ANY) | (t1 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
	uint32_t t2_type = (t2 & MAY_BE_ANY) | (t2 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);

	if (!(ZEND_OPTIMIZER_IGNORE_OVERLOADING & optimization_level)) {
		/* Handle potentially overloaded operators.
		 * This could be made more precise by checking the class type, if known. */
		if ((t1_type & MAY_BE_OBJECT) || (t2_type & MAY_BE_OBJECT)) {
			/* This is somewhat GMP specific. */
			tmp |= MAY_BE_OBJECT | MAY_BE_FALSE | MAY_BE_RC1;
		}
	}

	switch (opcode) {
		case ZEND_ADD:
			if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
				if (result_var < 0 ||
					!ssa->var_info[result_var].has_range ||
				    ssa->var_info[result_var].range.underflow ||
				    ssa->var_info[result_var].range.overflow) {
					/* may overflow */
					tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
				} else {
					tmp |= MAY_BE_LONG;
				}
			} else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
				tmp |= MAY_BE_DOUBLE;
			} else if (t1_type == MAY_BE_ARRAY && t2_type == MAY_BE_ARRAY) {
				tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
				tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
				tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
			} else {
				tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
				if ((t1_type & MAY_BE_ARRAY) && (t2_type & MAY_BE_ARRAY)) {
					tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
					tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
					tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
				}
			}
			break;
		case ZEND_SUB:
		case ZEND_MUL:
			if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
				if (result_var < 0 ||
					!ssa->var_info[result_var].has_range ||
				    ssa->var_info[result_var].range.underflow ||
				    ssa->var_info[result_var].range.overflow) {
					/* may overflow */
					tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
				} else {
					tmp |= MAY_BE_LONG;
				}
			} else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
				tmp |= MAY_BE_DOUBLE;
			} else {
				tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
			}
			break;
		case ZEND_DIV:
		case ZEND_POW:
			if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
				tmp |= MAY_BE_DOUBLE;
			} else {
				tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
			}
			/* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special
			 * handling */
			break;
		case ZEND_MOD:
			tmp |= MAY_BE_LONG;
			/* Division by zero results in an exception, so it doesn't need any special handling */
			break;
		case ZEND_BW_OR:
		case ZEND_BW_AND:
		case ZEND_BW_XOR:
			if ((t1_type & MAY_BE_STRING) && (t2_type & MAY_BE_STRING)) {
				tmp |= MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN;
			}
			if ((t1_type & ~MAY_BE_STRING) || (t2_type & ~MAY_BE_STRING)) {
				tmp |= MAY_BE_LONG;
			}
			break;
		case ZEND_SL:
		case ZEND_SR:
			tmp |= MAY_BE_LONG;
			break;
		case ZEND_CONCAT:
		case ZEND_FAST_CONCAT:
			/* TODO: +MAY_BE_OBJECT ??? */
			tmp = MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN;
			break;
		EMPTY_SWITCH_DEFAULT_CASE()
	}
	return tmp;
}

static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
	uint32_t result_mask = type_mask & MAY_BE_ANY;
	if (type_mask & MAY_BE_VOID) {
		result_mask |= MAY_BE_NULL;
	}
	if (type_mask & MAY_BE_CALLABLE) {
		result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
	}
	if (type_mask & MAY_BE_STATIC) {
		result_mask |= MAY_BE_OBJECT;
	}
	if (type_mask & MAY_BE_ARRAY) {
		result_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
	}
	return result_mask;
}

static uint32_t zend_convert_type(const zend_script *script, zend_type type, zend_class_entry **pce)
{
	if (pce) {
		*pce = NULL;
	}

	if (!ZEND_TYPE_IS_SET(type)) {
		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;
	}

	uint32_t tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(type));
	if (ZEND_TYPE_IS_COMPLEX(type)) {
		tmp |= MAY_BE_OBJECT;
		if (pce) {
			/* As we only have space to store one CE,
			 * we use a plain object type for class unions. */
			if (ZEND_TYPE_HAS_NAME(type)) {
				zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
				// TODO: Pass through op_array.
				*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
				zend_string_release_ex(lcname, 0);
			}
		}
	}
	if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
		tmp |= MAY_BE_RC1 | MAY_BE_RCN;
	}
	return tmp;
}

ZEND_API uint32_t zend_fetch_arg_info_type(const zend_script *script, const zend_arg_info *arg_info, zend_class_entry **pce)
{
	return zend_convert_type(script, arg_info->type, pce);
}

static const zend_property_info *lookup_prop_info(const zend_class_entry *ce, zend_string *name, zend_class_entry *scope) {
	const zend_property_info *prop_info;

	/* If the class is linked, reuse the precise runtime logic. */
	if ((ce->ce_flags & ZEND_ACC_LINKED)
	 && (!scope || (scope->ce_flags & ZEND_ACC_LINKED))) {
		zend_class_entry *prev_scope = EG(fake_scope);
		EG(fake_scope) = scope;
		prop_info = zend_get_property_info(ce, name, 1);
		EG(fake_scope) = prev_scope;
		if (prop_info && prop_info != ZEND_WRONG_PROPERTY_INFO) {
			return prop_info;
		}
		return NULL;
	}

	/* Otherwise, handle only some safe cases */
	prop_info = zend_hash_find_ptr(&ce->properties_info, name);
	if (prop_info &&
		((prop_info->ce == scope) ||
		 (!scope && (prop_info->flags & ZEND_ACC_PUBLIC)))
	) {
		return prop_info;
	}
	return NULL;
}

static const zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op)
{
	const zend_property_info *prop_info = NULL;
	if (opline->op2_type == IS_CONST) {
		const zend_class_entry *ce = NULL;

		if (opline->op1_type == IS_UNUSED && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
			ce = op_array->scope;
		} else if (ssa_op->op1_use >= 0) {
			ce = ssa->var_info[ssa_op->op1_use].ce;
		}
		if (ce) {
			prop_info = lookup_prop_info(ce,
				Z_STR_P(CRT_CONSTANT(opline->op2)),
				op_array->scope);
			if (prop_info && (prop_info->flags & ZEND_ACC_STATIC)) {
				prop_info = NULL;
			}
		}
	}
	return prop_info;
}

static const zend_property_info *zend_fetch_static_prop_info(const zend_script *script, const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline)
{
	const zend_property_info *prop_info = NULL;
	if (opline->op1_type == IS_CONST) {
		zend_class_entry *ce = NULL;
		if (opline->op2_type == IS_UNUSED) {
			int fetch_type = opline->op2.num & ZEND_FETCH_CLASS_MASK;
			switch (fetch_type) {
				case ZEND_FETCH_CLASS_SELF:
				case ZEND_FETCH_CLASS_STATIC:
					/* We enforce that static property types cannot change during inheritance, so
					 * handling static the same way as self here is legal. */
					ce = op_array->scope;
					break;
				case ZEND_FETCH_CLASS_PARENT:
					if (op_array->scope && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
						ce = op_array->scope->parent;
					}
					break;
			}
		} else if (opline->op2_type == IS_CONST) {
			zval *zv = CRT_CONSTANT(opline->op2);
			ce = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(zv + 1));
		}

		if (ce) {
			zval *zv = CRT_CONSTANT(opline->op1);
			prop_info = lookup_prop_info(ce, Z_STR_P(zv), op_array->scope);
			if (prop_info && !(prop_info->flags & ZEND_ACC_STATIC)) {
				prop_info = NULL;
			}
		}
	}
	return prop_info;
}

static uint32_t zend_fetch_prop_type(const zend_script *script, const zend_property_info *prop_info, zend_class_entry **pce)
{
	if (!prop_info) {
		if (pce) {
			*pce = NULL;
		}
		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;
	}

	return zend_convert_type(script, prop_info->type, pce);
}

static bool result_may_be_separated(zend_ssa *ssa, zend_ssa_op *ssa_op)
{
	int tmp_var = ssa_op->result_def;

	if (ssa->vars[tmp_var].use_chain >= 0
	 && !ssa->vars[tmp_var].phi_use_chain) {
		zend_ssa_op *use_op = &ssa->ops[ssa->vars[tmp_var].use_chain];

		/* TODO: analyze instructions between ssa_op and use_op */
		if (use_op == ssa_op + 1) {
			if ((use_op->op1_use == tmp_var && use_op->op1_use_chain < 0)
			 || (use_op->op2_use == tmp_var && use_op->op2_use_chain < 0)) {
				return 0;
			}
		}
	}
	return 1;
}

static zend_always_inline zend_result _zend_update_type_info(
			const zend_op_array *op_array,
			zend_ssa            *ssa,
			const zend_script   *script,
			zend_bitset          worklist,
			const zend_op       *opline,
			zend_ssa_op         *ssa_op,
			const zend_op      **ssa_opcodes,
			zend_long            optimization_level,
			bool            update_worklist)
{
	uint32_t t1, t2;
	uint32_t tmp, orig;
	zend_ssa_var *ssa_vars = ssa->vars;
	zend_ssa_var_info *ssa_var_info = ssa->var_info;
	zend_class_entry *ce;
	int j;

	if (opline->opcode == ZEND_OP_DATA) {
		opline--;
		ssa_op--;
	}

	t1 = OP1_INFO();
	t2 = OP2_INFO();

	/* 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
	 * code may assume that operands have at least one type. */
	if (!(t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))
	 || !(t2 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))
	 || (ssa_op->result_use >= 0 && !(RES_USE_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS)))
	 || ((opline->opcode == ZEND_ASSIGN_DIM_OP
	   || opline->opcode == ZEND_ASSIGN_OBJ_OP
	   || opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP
	   || opline->opcode == ZEND_ASSIGN_DIM
	   || opline->opcode == ZEND_ASSIGN_OBJ)
	    && !(OP1_DATA_INFO() & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS)) /*&& 0*/)) {
		tmp = 0;
		if (ssa_op->result_def >= 0 && !(ssa_var_info[ssa_op->result_def].type & MAY_BE_REF)) {
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
		}
		if (ssa_op->op1_def >= 0 && !(ssa_var_info[ssa_op->op1_def].type & MAY_BE_REF)) {
			UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
		}
		if (ssa_op->op2_def >= 0 && !(ssa_var_info[ssa_op->op2_def].type & MAY_BE_REF)) {
			UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
		}
		if (opline->opcode == ZEND_ASSIGN_DIM_OP
		 || opline->opcode == ZEND_ASSIGN_OBJ_OP
		 || opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP
		 || opline->opcode == ZEND_ASSIGN_DIM
		 || opline->opcode == ZEND_ASSIGN_OBJ) {
			if ((ssa_op+1)->op1_def >= 0 && !(ssa_var_info[(ssa_op+1)->op1_def].type & MAY_BE_REF)) {
				UPDATE_SSA_TYPE(tmp, (ssa_op+1)->op1_def);
			}
		}
		return SUCCESS;
	}

	switch (opline->opcode) {
		case ZEND_ADD:
		case ZEND_SUB:
		case ZEND_MUL:
		case ZEND_DIV:
		case ZEND_POW:
		case ZEND_MOD:
		case ZEND_BW_OR:
		case ZEND_BW_AND:
		case ZEND_BW_XOR:
		case ZEND_SL:
		case ZEND_SR:
		case ZEND_CONCAT:
			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;
			if (t1 & MAY_BE_STRING) {
				tmp |= MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN;
			}
			if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) {
				tmp |= MAY_BE_LONG;
			}
			if (!(ZEND_OPTIMIZER_IGNORE_OVERLOADING & optimization_level)) {
				if (t1 & MAY_BE_OBJECT) {
					/* Potentially overloaded operator. */
					tmp |= MAY_BE_OBJECT | MAY_BE_RC1;
				}
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			break;
		case ZEND_BEGIN_SILENCE:
			UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
			break;
		case ZEND_BOOL_NOT:
		case ZEND_BOOL_XOR:
		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_INSTANCEOF:
		case ZEND_JMPZ_EX:
		case ZEND_JMPNZ_EX:
		case ZEND_CASE:
		case ZEND_CASE_STRICT:
		case ZEND_BOOL:
		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_ASSERT_CHECK:
		case ZEND_IN_ARRAY:
		case ZEND_ARRAY_KEY_EXISTS:
			UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
			break;
		case ZEND_CAST:
			if (ssa_op->op1_def >= 0) {
				tmp = t1;
				if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) &&
				    (opline->extended_value == IS_ARRAY ||
				     opline->extended_value == IS_OBJECT)) {
					tmp |= MAY_BE_RCN;
				} else if ((t1 & MAY_BE_STRING) &&
				    opline->extended_value == IS_STRING) {
					tmp |= MAY_BE_RCN;
				}
				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)) {
				if ((tmp & MAY_BE_ANY) == (t1 & MAY_BE_ANY)) {
					tmp |= (t1 & MAY_BE_RC1) | MAY_BE_RCN;
				} else if ((opline->extended_value == IS_ARRAY ||
							opline->extended_value == IS_OBJECT) &&
						   (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT))) {
						tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				} else if (opline->extended_value == IS_STRING &&
						   (t1 & (MAY_BE_STRING|MAY_BE_OBJECT))) {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				} else {
					tmp |= MAY_BE_RC1;
					if (opline->extended_value == IS_ARRAY
					 && (t1 & (MAY_BE_UNDEF|MAY_BE_NULL))) {
						tmp |= MAY_BE_RCN;
					}
				}
			}
			if (opline->extended_value == IS_ARRAY) {
				if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL)) {
					tmp |= MAY_BE_ARRAY_EMPTY;
				}
				if (t1 & MAY_BE_ARRAY) {
					tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF);
				}
				if (t1 & MAY_BE_OBJECT) {
					tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
				} else if (t1 & (MAY_BE_ANY - MAY_BE_NULL)) {
					tmp |= ((t1 & (MAY_BE_ANY - MAY_BE_NULL)) << MAY_BE_ARRAY_SHIFT) | ((t1 & MAY_BE_NULL) ? MAY_BE_ARRAY_KEY_LONG : MAY_BE_ARRAY_PACKED);
				}
			}
			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_op->op1_def >= 0) {
				tmp = t1;
				if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
					tmp |= MAY_BE_RCN;
				}
				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;
			if (opline->opcode != ZEND_COPY_TMP || opline->op1_type != IS_VAR) {
				tmp &= ~MAY_BE_REF;
			}
			if (t1 & MAY_BE_UNDEF) {
				tmp |= MAY_BE_NULL;
			}
			if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
				tmp |= (t1 & (MAY_BE_RC1|MAY_BE_RCN));
				if (opline->opcode == ZEND_COPY_TMP || opline->op1_type == IS_CV) {
					tmp |= MAY_BE_RCN;
				}
			}
			if (opline->opcode == ZEND_COALESCE || opline->opcode == ZEND_JMP_SET) {
				/* COALESCE and JMP_SET result can't be null */
				tmp &= ~MAY_BE_NULL;
				if (opline->opcode == ZEND_JMP_SET) {
					/* JMP_SET result can't be false either */
					tmp &= ~MAY_BE_FALSE;
				}
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
			break;
		case ZEND_JMP_NULL:
		{
			uint32_t short_circuiting_type = opline->extended_value & ZEND_SHORT_CIRCUITING_CHAIN_MASK;
			if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EXPR) {
				tmp = MAY_BE_NULL;
			} else if (short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) {
				tmp = MAY_BE_FALSE;
			} else {
				ZEND_ASSERT(short_circuiting_type == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY);
				tmp = MAY_BE_TRUE;
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			break;
		}
		case ZEND_ASSIGN_OP:
		case ZEND_ASSIGN_DIM_OP:
		case ZEND_ASSIGN_OBJ_OP:
		case ZEND_ASSIGN_STATIC_PROP_OP:
		{
			const zend_property_info *prop_info = NULL;
			orig = 0;
			tmp = 0;
			if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
				prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
				orig = t1;
				t1 = zend_fetch_prop_type(script, prop_info, NULL);
				t2 = OP1_DATA_INFO();
			} 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, opline->op1_type, 1, 0);
				t2 = OP1_DATA_INFO();
			} 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, NULL);
				t2 = OP1_DATA_INFO();
			} else {
				if (t1 & MAY_BE_REF) {
			        tmp |= MAY_BE_REF;
				}
			}

			tmp |= binary_op_result_type(
				ssa, opline->extended_value, t1, t2,
				opline->opcode == ZEND_ASSIGN_OP ? ssa_op->op1_def : -1, optimization_level);
			if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY)) {
				tmp |= MAY_BE_RC1 | MAY_BE_RCN;
			}
			if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
				tmp |= MAY_BE_RC1 | MAY_BE_RCN;
			}

			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_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) {
					orig = (orig & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
					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_OP) {
				/* Nothing to do */
			} else {
				if (opline->opcode == ZEND_ASSIGN_OP && ssa_op->result_def >= 0 && (tmp & MAY_BE_RC1)) {
					tmp |= MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			if (ssa_op->result_def >= 0) {
				ce = NULL;
				if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
					if (opline->op2_type == IS_UNUSED) {
						/* When appending to an array and the LONG_MAX key is already used
						 * null will be returned. */
						tmp |= MAY_BE_NULL;
					}
					if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
						/* Arrays and objects cannot be used as keys. */
						tmp |= MAY_BE_NULL;
					}
					if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) {
						/* null and false are implicitly converted to array, anything else
						 * results in a null return value. */
						tmp |= MAY_BE_NULL;
					}
					if (tmp & MAY_BE_REF) {
						/* Typed reference may cause auto conversion */
						tmp |= MAY_BE_ANY;
					}
				} else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
					/* The return value must also satisfy the property type */
					if (prop_info) {
						t1 = zend_fetch_prop_type(script, prop_info, &ce);
						if ((t1 & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_LONG
						 && (tmp & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_DOUBLE) {
							/* DOUBLE may be auto-converted to LONG */
							tmp |= MAY_BE_LONG;
							tmp &= ~MAY_BE_DOUBLE;
						} else if ((t1 & (MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)) == MAY_BE_STRING
						 && (tmp & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
							/* LONG/DOUBLE may be auto-converted to STRING */
							tmp |= MAY_BE_STRING;
							tmp &= ~(MAY_BE_LONG|MAY_BE_DOUBLE);
						}
						tmp &= t1;
					} else {
						tmp |= MAY_BE_LONG | MAY_BE_STRING;
					}
				} else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
					/* The return value must also satisfy the property type */
					if (prop_info) {
						t1 = zend_fetch_prop_type(script, prop_info, &ce);
						if ((t1 & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_LONG
						 && (tmp & (MAY_BE_LONG|MAY_BE_DOUBLE)) == MAY_BE_DOUBLE) {
							/* DOUBLE may be auto-converted to LONG */
							tmp |= MAY_BE_LONG;
							tmp &= ~MAY_BE_DOUBLE;
						} else if ((t1 & (MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)) == MAY_BE_STRING
						 && (tmp & (MAY_BE_LONG|MAY_BE_DOUBLE))) {
							/* LONG/DOUBLE may be auto-converted to STRING */
							tmp |= MAY_BE_STRING;
							tmp &= ~(MAY_BE_LONG|MAY_BE_DOUBLE);
						}
						tmp &= t1;
					} else {
						tmp |= MAY_BE_LONG | MAY_BE_STRING;
					}
				} else {
					if (tmp & MAY_BE_REF) {
						/* Typed reference may cause auto conversion */
						tmp |= MAY_BE_ANY;
					}
				}
				tmp &= ~MAY_BE_REF;
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
				if (ce) {
					UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
				}
			}
			break;
		}
		case ZEND_PRE_INC:
		case ZEND_PRE_DEC:
			tmp = 0;
			if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
				tmp |= MAY_BE_RC1;
				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_op->op1_use].has_range ||
				    (opline->opcode == ZEND_PRE_DEC &&
				     (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_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 {
					tmp |= MAY_BE_LONG;
				}
			} else {
				if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
					if (opline->opcode == ZEND_PRE_INC) {
						tmp |= MAY_BE_LONG;
					} else {
						tmp |= MAY_BE_NULL;
					}
				}
				if (t1 & MAY_BE_LONG) {
					tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
				}
				if (t1 & MAY_BE_DOUBLE) {
					tmp |= MAY_BE_DOUBLE;
				}
				if (t1 & MAY_BE_STRING) {
					tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE;
				}
				tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_OBJECT);
			}
			if (ssa_op->result_def >= 0) {
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			if (ssa_op->op1_def >= 0) {
				if (t1 & MAY_BE_REF) {
					tmp |= MAY_BE_REF;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			break;
		case ZEND_POST_INC:
		case ZEND_POST_DEC:
			if (ssa_op->result_def >= 0) {
				tmp = 0;
				if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
					tmp |= MAY_BE_RC1|MAY_BE_RCN;
				}
				tmp |= t1 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RCN);
				if (t1 & MAY_BE_UNDEF) {
					tmp |= MAY_BE_NULL;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			tmp = 0;
			if (t1 & MAY_BE_REF) {
				tmp |= MAY_BE_REF;
			}
			if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
				tmp |= MAY_BE_RC1;
			}
			if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
				if (!ssa_var_info[ssa_op->op1_use].has_range ||
				     (opline->opcode == ZEND_POST_DEC &&
				      (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_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 {
					tmp |= MAY_BE_LONG;
				}
			} else {
				if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
					if (opline->opcode == ZEND_POST_INC) {
						tmp |= MAY_BE_LONG;
					} else {
						tmp |= MAY_BE_NULL;
					}
				}
				if (t1 & MAY_BE_LONG) {
					tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
				}
				if (t1 & MAY_BE_DOUBLE) {
					tmp |= MAY_BE_DOUBLE;
				}
				if (t1 & MAY_BE_STRING) {
					tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE;
				}
				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_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);
				tmp |= ssa->var_info[ssa_op->op1_def].type & (MAY_BE_ARRAY_PACKED|MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH);
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
			}
			if (ssa_op->result_def >= 0) {
				tmp = 0;
				if (t1 & MAY_BE_STRING) {
					tmp |= MAY_BE_STRING | MAY_BE_NULL;
				}
				if (t1 & MAY_BE_OBJECT) {
					tmp |= (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF);
				}
				if (t1 & (MAY_BE_ARRAY|MAY_BE_FALSE|MAY_BE_NULL|MAY_BE_UNDEF)) {
					tmp |= (OP1_DATA_INFO() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));

					if (OP1_DATA_INFO() & MAY_BE_UNDEF) {
						tmp |= MAY_BE_NULL;
					}
					if (t1 & MAY_BE_ARRAY_OF_REF) {
						/* A scalar type conversion may occur when assigning to a typed reference. */
						tmp |= MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING;
					}
				}
				if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) {
					tmp |= MAY_BE_NULL;
				}
				tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			if ((ssa_op+1)->op1_def >= 0) {
				opline++;
				ssa_op++;
				tmp = OP1_INFO();
				if (tmp & (MAY_BE_ANY | MAY_BE_REF)) {
					if (tmp & MAY_BE_RC1) {
						tmp |= MAY_BE_RCN;
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			break;
		case ZEND_ASSIGN_OBJ:
			if (opline->op1_type == IS_CV) {
				zend_class_entry *ce = ssa_var_info[ssa_op->op1_use].ce;
				bool add_rc = (t1 & (MAY_BE_OBJECT|MAY_BE_REF)) && (!ce
					|| ce->__set
					/* Non-default write_property may be set within create_object. */
					|| ce->create_object
					|| ce->default_object_handlers->write_property != zend_std_write_property
					|| ssa_var_info[ssa_op->op1_use].is_instanceof);
				tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN))|(add_rc ? (MAY_BE_RC1|MAY_BE_RCN) : 0);
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
			}
			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, ssa_op), &ce);
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
				if (ce) {
					UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
				}
			}
			if ((ssa_op+1)->op1_def >= 0) {
				opline++;
				ssa_op++;
				tmp = OP1_INFO();
				if (tmp & MAY_BE_RC1) {
					tmp |= MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			break;
		case ZEND_ASSIGN_STATIC_PROP:
			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_op->result_def);
			}
			if ((ssa_op+1)->op1_def >= 0) {
				opline++;
				ssa_op++;
				tmp = OP1_INFO();
				if (tmp & MAY_BE_RC1) {
					tmp |= MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			break;
		case ZEND_PRE_INC_OBJ:
		case ZEND_PRE_DEC_OBJ:
		case ZEND_POST_INC_OBJ:
		case ZEND_POST_DEC_OBJ:
			if (opline->op1_type == IS_CV) {
				tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
			}
			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_op->result_def);
			}
			break;
		case ZEND_ASSIGN:
			if (ssa_op->op2_def >= 0) {
				tmp = t2;
				if (tmp & MAY_BE_RC1) {
					tmp |= MAY_BE_RCN;
				}
				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) {
				tmp |= MAY_BE_NULL;
			}
			if (t1 & MAY_BE_REF) {
				tmp |= MAY_BE_REF;
			}
			if (t2 & MAY_BE_REF) {
				tmp |= MAY_BE_RC1 | MAY_BE_RCN;
			} else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
				tmp |= t2 & (MAY_BE_RC1|MAY_BE_RCN);
			} else if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) {
				tmp |= MAY_BE_RCN;
			}
			if (RETURN_VALUE_USED(opline) && (tmp & MAY_BE_RC1)) {
				tmp |= MAY_BE_RCN;
			}
			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_op->op1_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op1_def);
			}
			if (ssa_op->result_def >= 0) {
				if (tmp & MAY_BE_REF) {
					/* A scalar type conversion may occur when assigning to a typed reference. */
					tmp &= ~MAY_BE_REF;
					tmp |= MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN;
				}
				if ((tmp & (MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_RCN) {
					/* refcount may be indirectly decremented. Make an exception if the result is used in the next instruction */
					if (!ssa_opcodes) {
						if (ssa->vars[ssa_op->result_def].use_chain < 0
						 || opline + 1 != op_array->opcodes + ssa->vars[ssa_op->result_def].use_chain) {
							tmp |= MAY_BE_RC1;
					    }
					} else {
						if (ssa->vars[ssa_op->result_def].use_chain < 0
						 || opline + 1 != ssa_opcodes[ssa->vars[ssa_op->result_def].use_chain]) {
							tmp |= MAY_BE_RC1;
					    }
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
			}
			break;
		case ZEND_ASSIGN_REF:
// TODO: ???
			if (opline->op2_type == IS_CV) {
				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_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;
			} else {
				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_op->op1_def);
			if (ssa_op->result_def >= 0) {
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			break;
		case ZEND_ASSIGN_OBJ_REF:
			if (opline->op1_type == IS_CV && ssa_op->op1_def >= 0) {
				tmp = t1;
				if (tmp & MAY_BE_OBJECT) {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
			}

			t2 = OP1_DATA_INFO();
			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 {
				tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
			}
			if (t2 & MAY_BE_UNDEF) {
				tmp |= MAY_BE_NULL;
			}
			if (ssa_op->result_def >= 0) {
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			if ((opline+1)->op1_type == IS_CV) {
				opline++;
				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_op->op1_def);
			}
			break;
		case ZEND_ASSIGN_STATIC_PROP_REF:
			if (ssa_op->result_def >= 0) {
				UPDATE_SSA_TYPE(MAY_BE_REF, ssa_op->result_def);
			}
			if ((opline+1)->op1_type == IS_CV) {
				opline++;
				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_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
				| ((opline->extended_value & ZEND_BIND_REF) ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN));
			if (opline->extended_value & ZEND_BIND_IMPLICIT) {
				tmp |= MAY_BE_UNDEF;
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			break;
		case ZEND_BIND_INIT_STATIC_OR_JMP:
			tmp = MAY_BE_UNDEF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_REF;
			UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			break;
		case ZEND_SEND_VAR:
			if (ssa_op->op1_def >= 0) {
				tmp = t1;
				if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
					tmp |= MAY_BE_RCN;
				}
				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_op->op2_def >= 0) {
				if (opline->extended_value & ZEND_BIND_REF) {
					tmp = t2 | MAY_BE_REF;
				} else {
					tmp = t2 & ~(MAY_BE_RC1|MAY_BE_RCN);
					if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) {
						tmp |= MAY_BE_RCN;
					}
				}
				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_op->op1_def >= 0) {
				if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
					tmp = t1 | MAY_BE_REF;
				} else {
					tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
					if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
						tmp |= MAY_BE_RCN;
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
			}
			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_op->result_def);
			}
			break;
		case ZEND_SEND_VAR_EX:
		case ZEND_SEND_FUNC_ARG:
			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_op->op1_def);
			}
			break;
		case ZEND_SEND_REF:
			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_op->op1_def);
			}
			break;
		case ZEND_SEND_UNPACK:
			if (ssa_op->op1_def >= 0) {
				tmp = t1;
				if (t1 & MAY_BE_ARRAY) {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
					if (t1 & MAY_BE_ARRAY_OF_ANY) {
						/* SEND_UNPACK may acquire references into the array */
						tmp |= MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
					}
				}
				if (t1 & MAY_BE_OBJECT) {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				}
				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_op->result_def);
			break;
		case ZEND_RECV:
		case ZEND_RECV_INIT:
		case ZEND_RECV_VARIADIC:
		{
			/* Typehinting */
			zend_arg_info *arg_info = &op_array->arg_info[opline->op1.num-1];

			ce = NULL;
			tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
			if (ZEND_ARG_SEND_MODE(arg_info)) {
				tmp |= MAY_BE_REF;
				ce = NULL;
			}

			if (opline->opcode == ZEND_RECV_VARIADIC) {
				uint32_t elem_type = tmp & MAY_BE_REF
					? MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF
					: (tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
				tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|elem_type;
				ce = NULL;
			}

			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			if (ce) {
				UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
			} else {
				UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
			}
			break;
		}
		case ZEND_DECLARE_ANON_CLASS:
			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_op->result_def);
			}
			break;
		case ZEND_FETCH_CLASS:
			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_op->result_def);
						} else {
							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_op->result_def);
						} else {
							UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
						}
						break;
					case ZEND_FETCH_CLASS_STATIC:
						if (op_array->scope && (op_array->scope->ce_flags & ZEND_ACC_FINAL)) {
							UPDATE_SSA_OBJ_TYPE(op_array->scope, 0, ssa_op->result_def);
						} else {
							UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
						}
						break;
					default:
						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 = zend_optimizer_get_class_entry(script, op_array, Z_STR_P(zv+1));
					UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
				} else {
					UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
				}
			} else {
				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;
			ce = zend_optimizer_get_class_entry_from_op1(script, op_array, opline);
			if (ce) {
				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);
				if (!ssa_var_info[ssa_op->result_def].is_instanceof) {
					ce = ssa_var_info[ssa_op->op1_use].ce;
				}
			} else {
				UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
			}
			/* New objects without constructors cannot escape. */
			if (ce
			 && !ce->constructor
			 && !ce->create_object
			 && ce->default_object_handlers->get_constructor == zend_std_get_constructor) {
				tmp &= ~MAY_BE_RCN;
			}
			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_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 (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) {
						tmp |= MAY_BE_NULL;
					}
				} else if ((t1 & (MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_REF) {
					tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
					if (t1 & MAY_BE_UNDEF) {
						tmp |= MAY_BE_NULL;
					}
				} else if (t1 & MAY_BE_REF) {
					tmp = (MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | t1);
				} else {
					tmp = t1;
					if (t1 & MAY_BE_RC1) {
						tmp |= MAY_BE_RCN;
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			if (ssa_op->result_def >= 0) {
				uint32_t arr_type;
				if (opline->opcode == ZEND_INIT_ARRAY) {
					arr_type = 0;
				} else {
					arr_type = RES_USE_INFO();
				}
				tmp = MAY_BE_RC1|MAY_BE_ARRAY|arr_type;
				if (opline->opcode == ZEND_INIT_ARRAY && opline->op1_type == IS_UNUSED) {
					tmp |= MAY_BE_ARRAY_EMPTY;
				}
				if (opline->op1_type != IS_UNUSED
				 && (opline->op2_type == IS_UNUSED
				  || (t2 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE|MAY_BE_STRING)))) {
					tmp |= assign_dim_array_result_type(arr_type, t2, t1, opline->op2_type);
					if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
						tmp |= MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			break;
		case ZEND_ADD_ARRAY_UNPACK:
			tmp = ssa_var_info[ssa_op->result_use].type;
			ZEND_ASSERT(tmp & MAY_BE_ARRAY);
			tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
			if (t1 & MAY_BE_OBJECT) {
				tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			break;
		case ZEND_UNSET_CV:
			tmp = MAY_BE_UNDEF;
			if (!op_array->function_name) {
				/* In global scope, we know nothing */
				tmp |= MAY_BE_REF;
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			break;
		case ZEND_UNSET_DIM:
		case ZEND_UNSET_OBJ:
			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_op->op1_def >= 0) {
				tmp = t1;
				if (opline->opcode == ZEND_FE_RESET_RW) {
					tmp |= MAY_BE_REF;
				} else if (t1 & MAY_BE_RC1) {
					tmp |= MAY_BE_RCN;
				}
				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) {
//???
				tmp = MAY_BE_REF | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT));
			} 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));
			}
			/* The result is set to UNDEF for invalid foreach inputs. */
			if ((t1 & (MAY_BE_ANY | MAY_BE_UNDEF)) & ~(MAY_BE_ARRAY | MAY_BE_OBJECT)) {
				tmp |= MAY_BE_UNDEF;
			}
			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:
			tmp = 0;
			if (opline->op2_type == IS_CV) {
				tmp = t2 & MAY_BE_REF;
			}
			if (t1 & MAY_BE_OBJECT) {
				if (opline->opcode == ZEND_FE_FETCH_RW) {
					tmp |= MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
				} else {
					tmp |= MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
					if (opline->op2_type != IS_CV) {
						tmp |= MAY_BE_REF;
					}
				}
			}
			if (t1 & MAY_BE_ARRAY) {
				if (opline->opcode == ZEND_FE_FETCH_RW) {
					tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
				} else {
					tmp |= ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
					if (tmp & MAY_BE_ARRAY) {
						tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
					}
					if (t1 & MAY_BE_ARRAY_OF_REF) {
						tmp |= MAY_BE_RC1 | MAY_BE_RCN;
						if (opline->op2_type != IS_CV) {
							tmp |= MAY_BE_REF;
						}
					} else if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
						tmp |= MAY_BE_RC1 | MAY_BE_RCN;
					}
				}
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
			if (ssa_op->result_def >= 0) {
				tmp = (ssa_op->result_use >= 0) ? RES_USE_INFO() : 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;
				}
				if (t1 & MAY_BE_ARRAY) {
					if (t1 & MAY_BE_ARRAY_KEY_LONG) {
						tmp |= MAY_BE_LONG;
					}
					if (t1 & MAY_BE_ARRAY_KEY_STRING) {
						tmp |= MAY_BE_STRING | MAY_BE_RCN;
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			break;
		case ZEND_FETCH_DIM_R:
		case ZEND_FETCH_DIM_IS:
		case ZEND_FETCH_DIM_RW:
		case ZEND_FETCH_DIM_W:
		case ZEND_FETCH_DIM_UNSET:
		case ZEND_FETCH_DIM_FUNC_ARG:
		case ZEND_FETCH_LIST_R:
		case ZEND_FETCH_LIST_W:
			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 ||
				    opline->opcode == ZEND_FETCH_DIM_RW ||
				    opline->opcode == ZEND_FETCH_DIM_FUNC_ARG ||
				    opline->opcode == ZEND_FETCH_LIST_W) {
					if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
						if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG) {
							tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
						}
						tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
					}
					if (t1 & (MAY_BE_STRING|MAY_BE_ARRAY)) {
						tmp |= MAY_BE_RC1;
						if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) {
							tmp |= t1 & MAY_BE_RCN;
						}
					}
					if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
						tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN);
					}
					if (opline->op2_type == IS_UNUSED) {
						if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
							key_type |= MAY_BE_ARRAY_PACKED;
						}
						if (t1 & MAY_BE_ARRAY) {
							key_type |= MAY_BE_HASH_ONLY(t1) ?
								MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
						}
					} else {
						if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
							if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
								key_type |= MAY_BE_ARRAY_PACKED;
							}
							if (t1 & MAY_BE_ARRAY) {
								key_type |= MAY_BE_HASH_ONLY(t1) ?
									MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
						    }
						}
						if (t2 & MAY_BE_STRING) {
							key_type |= MAY_BE_ARRAY_KEY_STRING;
							if (opline->op2_type != IS_CONST) {
								// FIXME: numeric string
								if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
									key_type |= MAY_BE_ARRAY_PACKED;
								}
								if (t1 & MAY_BE_ARRAY) {
									key_type |= MAY_BE_HASH_ONLY(t1) ?
										MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
							    }
							}
						}
						if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
							key_type |= MAY_BE_ARRAY_KEY_STRING;
						}
					}
				} else if (opline->opcode == ZEND_FETCH_DIM_UNSET) {
					if (t1 & MAY_BE_ARRAY) {
						tmp |= MAY_BE_RC1;
					}
					if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
						tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN);
					}
				}
				if ((key_type & (MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING))
						&& (opline->opcode == ZEND_FETCH_DIM_RW
						|| opline->opcode == ZEND_FETCH_DIM_W
						|| opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
						|| opline->opcode == ZEND_FETCH_LIST_W)) {
					j = ssa_vars[ssa_op->result_def].use_chain;
					if (j < 0) {
						/* no uses */
						tmp |= key_type | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_NULL;
					}
					while (j >= 0) {
						uint8_t opcode;

						if (!ssa_opcodes) {
							if (j != (opline - op_array->opcodes) + 1) {
								/* Use must be in next opline */
								tmp |= key_type | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
								break;
							}
							opcode = op_array->opcodes[j].opcode;
						} else {
							if (ssa_opcodes[j] != opline + 1) {
								/* Use must be in next opline */
								tmp |= key_type | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
								break;
							}
							opcode = ssa_opcodes[j]->opcode;
						}
						switch (opcode) {
							case ZEND_FETCH_DIM_W:
							case ZEND_FETCH_DIM_RW:
							case ZEND_FETCH_DIM_FUNC_ARG:
							case ZEND_FETCH_LIST_W:
							case ZEND_ASSIGN_DIM:
							case ZEND_ASSIGN_DIM_OP:
								tmp |= key_type | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_ARRAY;
								break;
							case ZEND_SEND_VAR_EX:
							case ZEND_SEND_FUNC_ARG:
							case ZEND_SEND_VAR_NO_REF:
							case ZEND_SEND_VAR_NO_REF_EX:
							case ZEND_SEND_REF:
							case ZEND_ASSIGN_REF:
							case ZEND_YIELD:
							case ZEND_INIT_ARRAY:
							case ZEND_ADD_ARRAY_ELEMENT:
							case ZEND_RETURN_BY_REF:
							case ZEND_VERIFY_RETURN_TYPE:
							case ZEND_MAKE_REF:
							case ZEND_FE_RESET_RW:
								tmp |= key_type | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
								break;
							case ZEND_PRE_INC:
							case ZEND_PRE_DEC:
							case ZEND_POST_INC:
							case ZEND_POST_DEC:
								if (tmp & MAY_BE_ARRAY_OF_LONG) {
									/* may overflow */
									tmp |= key_type | MAY_BE_ARRAY_OF_DOUBLE;
								} else if (!(tmp & (MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE))) {
									tmp |= key_type | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE;
								}
								break;
							case ZEND_FETCH_OBJ_W:
							case ZEND_FETCH_OBJ_RW:
							case ZEND_FETCH_OBJ_FUNC_ARG:
							case ZEND_ASSIGN_OBJ:
							case ZEND_ASSIGN_OBJ_OP:
							case ZEND_ASSIGN_OBJ_REF:
							case ZEND_PRE_INC_OBJ:
							case ZEND_PRE_DEC_OBJ:
							case ZEND_POST_INC_OBJ:
							case ZEND_POST_DEC_OBJ:
								/* These will result in an error exception, unless the element
								 * is already an object. */
								break;
							case ZEND_SEND_VAR:
							case ZEND_FETCH_DIM_R:
								/* This can occur if a DIM_FETCH_FUNC_ARG with UNUSED op2 is left
								 * behind, because it can't be converted to DIM_FETCH_R. */
								break;
							case ZEND_FREE:
								/* This may happen if the using opcode is DCEd.  */
								break;
							EMPTY_SWITCH_DEFAULT_CASE()
						}
						j = zend_ssa_next_use(ssa->ops, ssa_op->result_def, j);
						if (j >= 0) {
							tmp |= key_type | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
							break;
						}
					}
					if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG) {
						tmp &= ~MAY_BE_ARRAY_EMPTY;
					}
				}
				if (!(tmp & MAY_BE_ARRAY)
				 || (tmp & MAY_BE_ARRAY_KEY_ANY)
				 || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
				 || opline->opcode == ZEND_FETCH_DIM_R
				 || opline->opcode == ZEND_FETCH_DIM_IS
				 || opline->opcode == ZEND_FETCH_DIM_UNSET
				 || opline->opcode == ZEND_FETCH_LIST_R) {
					UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				} else {
					/* invalid key type */
					return SUCCESS;
				}
				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(
				opline->opcode != ZEND_FETCH_LIST_R ? t1 : ((t1 & ~MAY_BE_STRING) | MAY_BE_NULL),
				opline->op1_type,
				opline->opcode != ZEND_FETCH_DIM_R && opline->opcode != ZEND_FETCH_DIM_IS
					&& opline->opcode != ZEND_FETCH_LIST_R,
				opline->op2_type == IS_UNUSED);
			if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG && (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE))) {
				tmp |= MAY_BE_NULL;
			}
			if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) {
				tmp |= MAY_BE_NULL;
			}
			if ((tmp & (MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_RCN && opline->result_type == IS_TMP_VAR) {
				/* refcount may be indirectly decremented. Make an exception if the result is used in the next instruction */
				if (!ssa_opcodes) {
					if (ssa->vars[ssa_op->result_def].use_chain < 0
					 || opline + 1 != op_array->opcodes + ssa->vars[ssa_op->result_def].use_chain) {
						tmp |= MAY_BE_RC1;
				    }
				} else {
					if (ssa->vars[ssa_op->result_def].use_chain < 0
					 || opline + 1 != ssa_opcodes[ssa->vars[ssa_op->result_def].use_chain]) {
						tmp |= MAY_BE_RC1;
				    }
				}
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			break;
		case ZEND_FETCH_THIS:
			if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
				UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_op->result_def);
			}
			UPDATE_SSA_TYPE(MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
			break;
		case ZEND_FETCH_OBJ_R:
		case ZEND_FETCH_OBJ_IS:
		case ZEND_FETCH_OBJ_RW:
		case ZEND_FETCH_OBJ_W:
		case ZEND_FETCH_OBJ_UNSET:
		case ZEND_FETCH_OBJ_FUNC_ARG:
			if (ssa_op->result_def >= 0) {
				uint32_t tmp = 0;
				ce = NULL;
				if (opline->op1_type != IS_UNUSED
						&& (t1 & (MAY_BE_ANY | MAY_BE_UNDEF) & ~MAY_BE_OBJECT)) {
					tmp |= MAY_BE_NULL;
				}
				if (opline->op1_type == IS_UNUSED || (t1 & MAY_BE_OBJECT)) {
					const zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
					tmp |= zend_fetch_prop_type(script, prop_info, &ce);
					if (opline->opcode != ZEND_FETCH_OBJ_R && opline->opcode != ZEND_FETCH_OBJ_IS) {
						tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
						if ((opline->extended_value & ZEND_FETCH_OBJ_FLAGS) == ZEND_FETCH_DIM_WRITE) {
							tmp |= MAY_BE_UNDEF;
						}
						ce = NULL;
					} else if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR)) || !(t1 & MAY_BE_RC1)) {
						const zend_class_entry *ce = NULL;

						if (opline->op1_type == IS_UNUSED) {
							ce = op_array->scope;
						} else if (ssa_op->op1_use >= 0 && !ssa->var_info[ssa_op->op1_use].is_instanceof) {
							ce = ssa->var_info[ssa_op->op1_use].ce;
						}
						/* Unset properties will resort back to __get/__set */
						if (ce
						 && !ce->create_object
						 && ce->default_object_handlers->read_property == zend_std_read_property
						 && !ce->__get
						 && !result_may_be_separated(ssa, ssa_op)) {
							tmp &= ~MAY_BE_RC1;
						}
						if (opline->opcode == ZEND_FETCH_OBJ_IS) {
							/* IS check may return null for uninitialized typed property. */
							tmp |= MAY_BE_NULL;
						}
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
				if (ce) {
					UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
				}
			}
			break;
		case ZEND_FETCH_STATIC_PROP_R:
		case ZEND_FETCH_STATIC_PROP_IS:
		case ZEND_FETCH_STATIC_PROP_RW:
		case ZEND_FETCH_STATIC_PROP_W:
		case ZEND_FETCH_STATIC_PROP_UNSET:
		case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
			tmp = zend_fetch_prop_type(script,
				zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
			if (opline->opcode != ZEND_FETCH_STATIC_PROP_R
					&& opline->opcode != ZEND_FETCH_STATIC_PROP_IS) {
				tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
				if ((opline->extended_value & ZEND_FETCH_OBJ_FLAGS) == ZEND_FETCH_DIM_WRITE) {
					tmp |= MAY_BE_UNDEF;
				}
				ce = NULL;
			} else {
				if (!result_may_be_separated(ssa, ssa_op)) {
					tmp &= ~MAY_BE_RC1;
				}
				if (opline->opcode == ZEND_FETCH_STATIC_PROP_IS) {
					tmp |= MAY_BE_UNDEF;
				}
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			if (ce) {
				UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
			}
			break;
		case ZEND_FRAMELESS_ICALL_1:
		case ZEND_FRAMELESS_ICALL_2:
		case ZEND_FRAMELESS_ICALL_3:
			if (ssa_op->op1_def >= 0) {
				ZEND_ASSERT(ssa_op->op1_use >= 0);
				tmp = ssa->var_info[ssa_op->op1_use].type;
				if (tmp & MAY_BE_RC1) {
					tmp |= MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			if (ssa_op->op2_def >= 0) {
				ZEND_ASSERT(ssa_op->op2_use >= 0);
				tmp = ssa->var_info[ssa_op->op2_use].type;
				if (tmp & MAY_BE_RC1) {
					tmp |= MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
			}
			if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
				zend_ssa_op *next_ssa_op = ssa_op + 1;
				if (next_ssa_op->op1_def >= 0) {
					ZEND_ASSERT(next_ssa_op->op1_use >= 0);
					tmp = ssa->var_info[next_ssa_op->op1_use].type;
					if (tmp & MAY_BE_RC1) {
						tmp |= MAY_BE_RCN;
					}
					UPDATE_SSA_TYPE(tmp, next_ssa_op->op1_def);
				}
			}
			ZEND_FALLTHROUGH;
		case ZEND_FRAMELESS_ICALL_0:
		case ZEND_DO_FCALL:
		case ZEND_DO_ICALL:
		case ZEND_DO_UCALL:
		case ZEND_DO_FCALL_BY_NAME:
			if (ssa_op->result_def >= 0) {
				zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
				zend_call_info *call_info;

				if (!func_info || !func_info->call_map) {
					goto unknown_opcode;
				}
				call_info = func_info->call_map[opline - op_array->opcodes];
				if (!call_info) {
					goto unknown_opcode;
				}

				zend_class_entry *ce;
				bool ce_is_instanceof;
				tmp = zend_get_func_info(call_info, ssa, &ce, &ce_is_instanceof);
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
				if (ce) {
					UPDATE_SSA_OBJ_TYPE(ce, ce_is_instanceof, ssa_op->result_def);
				}
			}
			break;
		case ZEND_CALLABLE_CONVERT:
			UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def);
			UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
			break;
		case ZEND_FETCH_CONSTANT:
			UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
			break;
		case ZEND_FETCH_CLASS_CONSTANT: {
			bool is_prototype;
			const zend_class_constant *cc = zend_fetch_class_const_info(script, op_array, opline, &is_prototype);
			if (!cc || !ZEND_TYPE_IS_SET(cc->type)) {
				UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
				break;
			}
			UPDATE_SSA_TYPE(zend_convert_type(script, cc->type, &ce), ssa_op->result_def);
			if (ce) {
				UPDATE_SSA_OBJ_TYPE(ce, /* is_instanceof */ true, ssa_op->result_def);
			}
			break;
		}
		case ZEND_STRLEN:
		case ZEND_COUNT:
		case ZEND_FUNC_NUM_ARGS:
			UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
			break;
		case ZEND_FUNC_GET_ARGS:
			UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ARRAY|MAY_BE_ARRAY_EMPTY|MAY_BE_ARRAY_PACKED|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
			break;
		case ZEND_GET_CLASS:
		case ZEND_GET_CALLED_CLASS:
			UPDATE_SSA_TYPE(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_op->result_def);
			break;
		case ZEND_TYPE_CHECK: {
			uint32_t expected_type_mask = opline->extended_value;
			if (t1 & MAY_BE_UNDEF) {
				t1 |= MAY_BE_NULL;
			}
			tmp = 0;
			if (t1 & expected_type_mask) {
				tmp |= MAY_BE_TRUE;
				if ((t1 & expected_type_mask) & MAY_BE_RESOURCE) {
					tmp |= MAY_BE_FALSE;
				}
			}
			if (t1 & (MAY_BE_ANY - expected_type_mask)) {
				tmp |= MAY_BE_FALSE;
			}
			UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			break;
		}
		case ZEND_DEFINED:
			UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
			break;
		case ZEND_VERIFY_RETURN_TYPE:
			if (t1 & MAY_BE_REF) {
				tmp = t1;
				ce = NULL;
			} else {
				zend_arg_info *ret_info = op_array->arg_info - 1;
				tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
				if ((tmp & MAY_BE_NULL) && opline->op1_type == IS_CV) {
					tmp |= MAY_BE_UNDEF;
				}
				tmp |= (t1 & MAY_BE_INDIRECT);

				// TODO: We could model more precisely how illegal types are converted.
				uint32_t extra_types = t1 & ~tmp;
				if (!extra_types) {
					tmp &= t1;
				}
			}
			if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
				if (ce) {
					UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->op1_def);
				} else {
					UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->op1_def);
				}
			} else {
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
				if (ce) {
					UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
				} else {
					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_op->result_def);
			if (ssa_op->op1_def >= 0) {
				UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
			}
			break;
		case ZEND_CATCH:
			/* Forbidden opcodes */
			ZEND_UNREACHABLE();
			break;
		case ZEND_FETCH_CLASS_NAME:
			UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RCN, ssa_op->result_def);
			break;
		case ZEND_ISSET_ISEMPTY_THIS:
			UPDATE_SSA_TYPE(MAY_BE_BOOL, ssa_op->result_def);
			break;
		case ZEND_DECLARE_LAMBDA_FUNCTION:
			UPDATE_SSA_TYPE(MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
			UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
			break;
		case ZEND_PRE_DEC_STATIC_PROP:
		case ZEND_PRE_INC_STATIC_PROP:
		case ZEND_POST_DEC_STATIC_PROP:
		case ZEND_POST_INC_STATIC_PROP: {
			if (ssa_op->result_def >= 0) {
				const zend_property_info *prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
				zend_class_entry *prop_ce;
				tmp = zend_fetch_prop_type(script, prop_info, &prop_ce);
				/* Internal objects may result in essentially anything. */
				if (tmp & MAY_BE_OBJECT) {
					goto unknown_opcode;
				}
				tmp &= MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_BOOL|MAY_BE_NULL;
				if (tmp & MAY_BE_STRING) {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			break;
		}
		case ZEND_SPACESHIP:
			UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
			break;
		case ZEND_FETCH_GLOBALS:
			UPDATE_SSA_TYPE(MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
			break;
		default:
#ifdef ZEND_DEBUG_TYPE_INFERENCE
			if (ssa_op->result_def >= 0) {
				switch (opline->opcode) {
					case ZEND_FETCH_R:
					case ZEND_FETCH_W:
					case ZEND_FETCH_RW:
					case ZEND_FETCH_IS:
					case ZEND_FETCH_UNSET:
					case ZEND_YIELD_FROM:
					/* Currently unimplemented due to some assumptions in JIT. See:
					 * https://github.com/php/php-src/pull/13304#issuecomment-1926668141 */
					case ZEND_SEPARATE:
						break;
					default:
						fprintf(stderr, "Missing result type inference for opcode %s, line %d\n", zend_get_opcode_name(opline->opcode), opline->lineno);
						break;
				}
			}
			if (ssa_op->op1_def >= 0) {
				fprintf(stderr, "Missing op1 type inference for opcode %s, line %d\n", zend_get_opcode_name(opline->opcode), opline->lineno);
			}
			if (ssa_op->op2_def >= 0) {
				fprintf(stderr, "Missing op2 type inference for opcode %s, line %d\n", zend_get_opcode_name(opline->opcode), opline->lineno);
			}
#endif
			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_op->op1_def);
			}
unknown_opcode:
			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) {
					if (opline->opcode == ZEND_FETCH_R || opline->opcode == ZEND_FETCH_IS) {
						/* Variable reference counter may be decremented before use */
						/* See: ext/opcache/tests/jit/fetch_r_001.phpt */
						tmp |= MAY_BE_RC1 | MAY_BE_RCN;
					} else {
						tmp |= MAY_BE_RC1 | MAY_BE_RCN;
					}
				} else if (opline->result_type == IS_CV) {
					tmp |= MAY_BE_RC1 | MAY_BE_RCN;
				} else {
					tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
					switch (opline->opcode) {
						case ZEND_FETCH_W:
						case ZEND_FETCH_RW:
						case ZEND_FETCH_FUNC_ARG:
						case ZEND_FETCH_UNSET:
						case ZEND_FETCH_DIM_W:
						case ZEND_FETCH_DIM_RW:
						case ZEND_FETCH_DIM_FUNC_ARG:
						case ZEND_FETCH_DIM_UNSET:
						case ZEND_FETCH_OBJ_W:
						case ZEND_FETCH_OBJ_RW:
						case ZEND_FETCH_OBJ_FUNC_ARG:
						case ZEND_FETCH_OBJ_UNSET:
						case ZEND_FETCH_STATIC_PROP_W:
						case ZEND_FETCH_STATIC_PROP_RW:
						case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
						case ZEND_FETCH_STATIC_PROP_UNSET:
							tmp |= MAY_BE_INDIRECT;
							break;
					}
				}
				UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
			}
			break;
	}

	return SUCCESS;
}

ZEND_API zend_result 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) {
		while (ce->parent) {
			rank++;
			ce = ce->parent;
		}
	}
	return rank;
}

/* Compute least common ancestor on class inheritance tree only */
static zend_class_entry *join_class_entries(
		zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) {
	uint32_t rank1, rank2;
	if (ce1 == ce2) {
		return ce1;
	}
	if (!ce1 || !ce2) {
		return NULL;
	}

	rank1 = get_class_entry_rank(ce1);
	rank2 = get_class_entry_rank(ce2);

	while (rank1 != rank2) {
		if (rank1 > rank2) {
			ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
			rank1--;
		} else {
			ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
			rank2--;
		}
	}

	while (ce1 != ce2) {
		ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
		ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
	}

	if (ce1) {
		*is_instanceof = 1;
	}
	return ce1;
}

static bool safe_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) {
	if (ce1 == ce2) {
		return 1;
	}
	if (!(ce1->ce_flags & ZEND_ACC_LINKED)) {
		/* This case could be generalized, similarly to unlinked_instanceof */
		return 0;
	}
	return instanceof_function(ce1, ce2);
}

static zend_result zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist, zend_long optimization_level)
{
	zend_basic_block *blocks = ssa->cfg.blocks;
	zend_ssa_var *ssa_vars = ssa->vars;
	zend_ssa_var_info *ssa_var_info = ssa->var_info;
	int ssa_vars_count = ssa->vars_count;
	int i, j;
	uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
	bool update_worklist = 1;
	const zend_op **ssa_opcodes = NULL;

	while (!zend_bitset_empty(worklist, worklist_len)) {
		j = zend_bitset_first(worklist, worklist_len);
		zend_bitset_excl(worklist, j);
		if (ssa_vars[j].definition_phi) {
			zend_ssa_phi *p = ssa_vars[j].definition_phi;
			if (p->pi >= 0) {
				zend_class_entry *ce = ssa_var_info[p->sources[0]].ce;
				int is_instanceof = ssa_var_info[p->sources[0]].is_instanceof;
				tmp = get_ssa_var_info(ssa, p->sources[0]);

				if (!p->has_range_constraint) {
					zend_ssa_type_constraint *constraint = &p->constraint.type;
					tmp &= constraint->type_mask;
					if (!(tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
						tmp &= ~(MAY_BE_RC1|MAY_BE_RCN);
					}
					if ((tmp & MAY_BE_OBJECT) && constraint->ce && ce != constraint->ce) {
						if (!ce) {
							ce = constraint->ce;
							is_instanceof = 1;
						} else if (is_instanceof && safe_instanceof(constraint->ce, ce)) {
							ce = constraint->ce;
						} else {
							/* Ignore the constraint (either ce instanceof constraint->ce or
							 * they are unrelated, as far as we can statically determine) */
						}
					}
				}

				UPDATE_SSA_TYPE(tmp, j);
				if (tmp & MAY_BE_REF) {
					UPDATE_SSA_OBJ_TYPE(NULL, 0, j);
				} else {
					UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j);
				}
			} else {
				int first = 1;
				int is_instanceof = 0;
				zend_class_entry *ce = NULL;

				tmp = 0;
				for (i = 0; i < blocks[p->block].predecessors_count; i++) {
					tmp |= get_ssa_var_info(ssa, p->sources[i]);
				}
				UPDATE_SSA_TYPE(tmp, j);
				for (i = 0; i < blocks[p->block].predecessors_count; i++) {
					zend_ssa_var_info *info;

					ZEND_ASSERT(p->sources[i] >= 0);
					info = &ssa_var_info[p->sources[i]];
					if (info->type & MAY_BE_OBJECT) {
						if (first) {
							ce = info->ce;
							is_instanceof = info->is_instanceof;
							first = 0;
						} else {
							is_instanceof |= info->is_instanceof;
							ce = join_class_entries(ce, info->ce, &is_instanceof);
						}
					}
				}
				UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j);
			}
		} else if (ssa_vars[j].definition >= 0) {
			i = ssa_vars[j].definition;
			if (_zend_update_type_info(op_array, ssa, script, worklist, op_array->opcodes + i, ssa->ops + i, NULL, optimization_level, 1) == FAILURE) {
				return FAILURE;
			}
		}
	}
	return SUCCESS;
}

static bool is_narrowable_instr(zend_op *opline)  {
	return opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB
		|| opline->opcode == ZEND_MUL || opline->opcode == ZEND_DIV;
}

static bool is_effective_op1_double_cast(zend_op *opline, zval *op2) {
	return (opline->opcode == ZEND_ADD && Z_LVAL_P(op2) == 0)
		|| (opline->opcode == ZEND_SUB && Z_LVAL_P(op2) == 0)
		|| (opline->opcode == ZEND_MUL && Z_LVAL_P(op2) == 1)
		|| (opline->opcode == ZEND_DIV && Z_LVAL_P(op2) == 1);
}
static bool is_effective_op2_double_cast(zend_op *opline, zval *op1) {
	/* In PHP it holds that (double)(0-$int) is bitwise identical to 0.0-(double)$int,
	 * so allowing SUB here is fine. */
	return (opline->opcode == ZEND_ADD && Z_LVAL_P(op1) == 0)
		|| (opline->opcode == ZEND_SUB && Z_LVAL_P(op1) == 0)
		|| (opline->opcode == ZEND_MUL && Z_LVAL_P(op1) == 1);
}

/* This function recursively checks whether it's possible to convert an integer variable
 * initialization to a double initialization. The basic idea is that if the value is used
 * only in add/sub/mul/div ("narrowable" instructions) with a double result value, then it
 * will be cast to double at that point anyway, so we may as well do it earlier already.
 *
 * The tricky case are chains of operations, where it's not necessarily a given that converting
 * an integer to double before the chain of operations is the same as converting it after the
 * chain. What this function does is detect two cases where it is safe:
 *  * If the operations only involve constants, then we can simply verify that performing the
 *    calculation on integers and doubles yields the same value.
 *  * Even if one operand is not known, we may be able to determine that the operations with the
 *    integer replaced by a double only acts as an effective double cast on the unknown operand.
 *    E.g. 0+$i and 0.0+$i only differ by that cast. If then the consuming instruction of this
 *    result will perform a double cast anyway, the conversion is safe.
 *
 * The checks happens recursively, while keeping track of which variables are already visited to
 * avoid infinite loops. An iterative, worklist driven approach would be possible, but the state
 * management more cumbersome to implement, so we don't bother for now.
 */
static bool can_convert_to_double(
		const zend_op_array *op_array, zend_ssa *ssa, int var_num,
		zval *value, zend_bitset visited) {
	zend_ssa_var *var = &ssa->vars[var_num];
	zend_ssa_phi *phi;
	int use;
	uint32_t type;

	if (zend_bitset_in(visited, var_num)) {
		return 1;
	}
	zend_bitset_incl(visited, var_num);

	for (use = var->use_chain; use >= 0; use = zend_ssa_next_use(ssa->ops, var_num, use)) {
		zend_op *opline = &op_array->opcodes[use];
		zend_ssa_op *ssa_op = &ssa->ops[use];

		if (zend_ssa_is_no_val_use(opline, ssa_op, var_num)) {
			continue;
		}

		if (!is_narrowable_instr(opline)) {
			return 0;
		}

		/* Instruction always returns double, the conversion is certainly fine */
		type = ssa->var_info[ssa_op->result_def].type;
		if ((type & MAY_BE_ANY) == MAY_BE_DOUBLE) {
			continue;
		}

		/* UNDEF signals that the previous result is an effective double cast, this is only allowed
		 * if this instruction would have done the cast anyway (previous check). */
		if (Z_ISUNDEF_P(value)) {
			return 0;
		}

		/* Check that narrowing can actually be useful */
		if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
			return 0;
		}

		{
			/* For calculation on original values */
			zval orig_op1, orig_op2, orig_result;
			/* For calculation with var_num cast to double */
			zval dval_op1, dval_op2, dval_result;

			ZVAL_UNDEF(&orig_op1);
			ZVAL_UNDEF(&dval_op1);
			if (ssa_op->op1_use == var_num) {
				ZVAL_COPY_VALUE(&orig_op1, value);
				ZVAL_DOUBLE(&dval_op1, (double) Z_LVAL_P(value));
			} else if (opline->op1_type == IS_CONST) {
				zval *zv = CRT_CONSTANT(opline->op1);
				if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
					ZVAL_COPY_VALUE(&orig_op1, zv);
					ZVAL_COPY_VALUE(&dval_op1, zv);
				}
			}

			ZVAL_UNDEF(&orig_op2);
			ZVAL_UNDEF(&dval_op2);
			if (ssa_op->op2_use == var_num) {
				ZVAL_COPY_VALUE(&orig_op2, value);
				ZVAL_DOUBLE(&dval_op2, (double) Z_LVAL_P(value));
			} else if (opline->op2_type == IS_CONST) {
				zval *zv = CRT_CONSTANT(opline->op2);
				if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
					ZVAL_COPY_VALUE(&orig_op2, zv);
					ZVAL_COPY_VALUE(&dval_op2, zv);
				}
			}

			ZEND_ASSERT(!Z_ISUNDEF(orig_op1) || !Z_ISUNDEF(orig_op2));
			if (Z_ISUNDEF(orig_op1)) {
				if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op2) == 0) {
					ZVAL_LONG(&orig_result, 0);
				} else if (is_effective_op1_double_cast(opline, &orig_op2)) {
					ZVAL_UNDEF(&orig_result);
				} else {
					return 0;
				}
			} else if (Z_ISUNDEF(orig_op2)) {
				if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op1) == 0) {
					ZVAL_LONG(&orig_result, 0);
				} else if (is_effective_op2_double_cast(opline, &orig_op1)) {
					ZVAL_UNDEF(&orig_result);
				} else {
					return 0;
				}
			} else {
				uint8_t opcode = opline->opcode;

				if (opcode == ZEND_ASSIGN_OP) {
					opcode = opline->extended_value;
				}

				/* Avoid division by zero */
				if (opcode == ZEND_DIV && zval_get_double(&orig_op2) == 0.0) {
					return 0;
				}

				get_binary_op(opcode)(&orig_result, &orig_op1, &orig_op2);
				get_binary_op(opcode)(&dval_result, &dval_op1, &dval_op2);
				ZEND_ASSERT(Z_TYPE(dval_result) == IS_DOUBLE);
				if (zval_get_double(&orig_result) != Z_DVAL(dval_result)) {
					return 0;
				}
			}

			if (!can_convert_to_double(op_array, ssa, ssa_op->result_def, &orig_result, visited)) {
				return 0;
			}
		}
	}

	for (phi = var->phi_use_chain; phi; phi = zend_ssa_next_use_phi(ssa, var_num, phi)) {
		/* Check that narrowing can actually be useful */
		type = ssa->var_info[phi->ssa_var].type;
		if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
			return 0;
		}

		if (!can_convert_to_double(op_array, ssa, phi->ssa_var, value, visited)) {
			return 0;
		}
	}

	return 1;
}

static zend_result zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
{
	uint32_t bitset_len = zend_bitset_len(ssa->vars_count);
	zend_bitset visited, worklist;
	int i, v;
	zend_op *opline;
	bool narrowed = 0;
	ALLOCA_FLAG(use_heap)

	visited = ZEND_BITSET_ALLOCA(2 * bitset_len, use_heap);
	worklist = visited + bitset_len;

	zend_bitset_clear(worklist, bitset_len);

	for (v = op_array->last_var; v < ssa->vars_count; v++) {
		if ((ssa->var_info[v].type & (MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF)) != MAY_BE_LONG) continue;
		if (ssa->vars[v].definition < 0) continue;
		if (ssa->vars[v].no_val) continue;
		opline = op_array->opcodes + ssa->vars[v].definition;
		/* Go through assignments of literal integers and check if they can be converted to
		 * doubles instead, in the hope that we'll narrow long|double to double. */
		if (opline->opcode == ZEND_ASSIGN && opline->result_type == IS_UNUSED &&
				opline->op1_type == IS_CV && opline->op2_type == IS_CONST) {
			zval *value = CRT_CONSTANT(opline->op2);

			zend_bitset_clear(visited, bitset_len);
			if (can_convert_to_double(op_array, ssa, v, value, visited)) {
				narrowed = 1;
				ssa->var_info[v].use_as_double = 1;
				/* The "visited" vars are exactly those which may change their type due to
				 * narrowing. Reset their types and add them to the type inference worklist */
				ZEND_BITSET_FOREACH(visited, bitset_len, i) {
					ssa->var_info[i].type &= ~MAY_BE_ANY;
				} ZEND_BITSET_FOREACH_END();
				zend_bitset_union(worklist, visited, bitset_len);
			}
		}
	}

	if (!narrowed) {
		free_alloca(visited, use_heap);
		return SUCCESS;
	}

	if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) == FAILURE) {
		free_alloca(visited, use_heap);
		return FAILURE;
	}

	free_alloca(visited, use_heap);
	return SUCCESS;
}

static bool is_recursive_tail_call(const zend_op_array *op_array,
                                  zend_op             *opline)
{
	zend_func_info *info = ZEND_FUNC_INFO(op_array);

	if (info->ssa.ops && info->ssa.vars && info->call_map &&
	    info->ssa.ops[opline - op_array->opcodes].op1_use >= 0 &&
	    info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition >= 0) {

		zend_op *op = op_array->opcodes + info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition;

		if (op->opcode == ZEND_DO_UCALL) {
			zend_call_info *call_info = info->call_map[op - op_array->opcodes];
			if (call_info && op_array == &call_info->callee_func->op_array) {
				return 1;
			}
		}
	}
	return 0;
}

uint32_t zend_get_return_info_from_signature_only(
		const zend_function *func, const zend_script *script,
		zend_class_entry **ce, bool *ce_is_instanceof, bool use_tentative_return_info) {
	uint32_t type;
	if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE &&
		(use_tentative_return_info || !ZEND_ARG_TYPE_IS_TENTATIVE(func->common.arg_info - 1))
	) {
		zend_arg_info *ret_info = func->common.arg_info - 1;
		type = zend_fetch_arg_info_type(script, ret_info, ce);
		*ce_is_instanceof = ce != NULL;
	} else {
		type = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
			| MAY_BE_RC1 | MAY_BE_RCN;
		*ce = NULL;
		*ce_is_instanceof = false;
	}

	/* For generators RETURN_REFERENCE refers to the yielded values. */
	if ((func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
			&& !(func->common.fn_flags & ZEND_ACC_GENERATOR)) {
		type |= MAY_BE_REF;
		*ce = NULL;
		*ce_is_instanceof = 0;
	}
	return type;
}

ZEND_API void zend_init_func_return_info(
	const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret)
{
	ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE));

	zend_ssa_range tmp_range = {0, 0, 0, 0};
	bool is_instanceof = false;
	ret->type = zend_get_return_info_from_signature_only(
		(zend_function *) op_array, script, &ret->ce, &is_instanceof, /* use_tentative_return_info */ 1);
	ret->is_instanceof = is_instanceof;
	ret->range = tmp_range;
	ret->has_range = 0;
}

static void zend_func_return_info(const zend_op_array   *op_array,
                                  const zend_script     *script,
                                  int                    recursive,
                                  int                    widening,
                                  zend_ssa_var_info     *ret)
{
	zend_func_info *info = ZEND_FUNC_INFO(op_array);
	zend_ssa *ssa = &info->ssa;
	int blocks_count = info->ssa.cfg.blocks_count;
	zend_basic_block *blocks = info->ssa.cfg.blocks;
	int j;
	uint32_t t1;
	uint32_t tmp = 0;
	zend_class_entry *tmp_ce = NULL;
	int tmp_is_instanceof = -1;
	zend_class_entry *arg_ce;
	int arg_is_instanceof;
	zend_ssa_range tmp_range = {0, 0, 0, 0};
	int tmp_has_range = -1;

	if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
		ret->type = MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN;
		ret->ce = zend_ce_generator;
		ret->is_instanceof = 0;
		ret->range = tmp_range;
		ret->has_range = 0;
		return;
	}

	if (!ret->type) {
		/* We will intersect the type later. */
		ret->type = MAY_BE_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY
			| MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF;
	}

	for (j = 0; j < blocks_count; j++) {
		if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
			zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;

			if (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_RETURN_BY_REF) {
				zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[opline - op_array->opcodes] : NULL;
				if (!recursive && ssa_op && info->ssa.var_info &&
				    ssa_op->op1_use >= 0 &&
				    info->ssa.var_info[ssa_op->op1_use].recursive) {
					continue;
				}
				if (is_recursive_tail_call(op_array, opline)) {
					continue;
				}
				t1 = OP1_INFO();
				if (t1 & MAY_BE_UNDEF) {
					t1 |= MAY_BE_NULL;
				}
				if (opline->opcode == ZEND_RETURN) {
					if (t1 & MAY_BE_RC1) {
						t1 |= MAY_BE_RCN;
					}
					t1 &= ~(MAY_BE_UNDEF | MAY_BE_REF);
				} else {
					t1 |= MAY_BE_REF;
					t1 &= ~(MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN);
				}
				tmp |= t1;

				if (ssa_op && info->ssa.var_info &&
				    ssa_op->op1_use >= 0 && !(t1 & MAY_BE_REF) &&
				    info->ssa.var_info[ssa_op->op1_use].ce) {
					arg_ce = info->ssa.var_info[ssa_op->op1_use].ce;
					arg_is_instanceof = info->ssa.var_info[ssa_op->op1_use].is_instanceof;
				} else {
					arg_ce = NULL;
					arg_is_instanceof = 0;
				}

				if (tmp_is_instanceof < 0) {
					tmp_ce = arg_ce;
					tmp_is_instanceof = arg_is_instanceof;
				} else if (arg_ce && arg_ce == tmp_ce) {
					if (tmp_is_instanceof != arg_is_instanceof) {
						tmp_is_instanceof = 1;
					}
				} else {
					tmp_ce = NULL;
					tmp_is_instanceof = 0;
				}

				if (opline->op1_type == IS_CONST) {
					zval *zv = CRT_CONSTANT(opline->op1);

					if (Z_TYPE_P(zv) == IS_LONG) {
						if (tmp_has_range < 0) {
							tmp_has_range = 1;
							tmp_range.underflow = 0;
							tmp_range.min = Z_LVAL_P(zv);
							tmp_range.max = Z_LVAL_P(zv);
							tmp_range.overflow = 0;
						} else if (tmp_has_range) {
							if (!tmp_range.underflow) {
								tmp_range.min = MIN(tmp_range.min, Z_LVAL_P(zv));
							}
							if (!tmp_range.overflow) {
								tmp_range.max = MAX(tmp_range.max, Z_LVAL_P(zv));
							}
						}
					} else {
						tmp_has_range = 0;
					}
				} else if (ssa_op && info->ssa.var_info && ssa_op->op1_use >= 0) {
					if (info->ssa.var_info[ssa_op->op1_use].has_range) {
						if (tmp_has_range < 0) {
							tmp_has_range = 1;
							tmp_range = info->ssa.var_info[ssa_op->op1_use].range;
						} else if (tmp_has_range) {
							/* union */
							if (info->ssa.var_info[ssa_op->op1_use].range.underflow) {
								tmp_range.underflow = 1;
								tmp_range.min = ZEND_LONG_MIN;
							} else {
								tmp_range.min = MIN(tmp_range.min, info->ssa.var_info[ssa_op->op1_use].range.min);
							}
							if (info->ssa.var_info[ssa_op->op1_use].range.overflow) {
								tmp_range.overflow = 1;
								tmp_range.max = ZEND_LONG_MAX;
							} else {
								tmp_range.max = MAX(tmp_range.max, info->ssa.var_info[ssa_op->op1_use].range.max);
							}
						}
					} else if (!widening) {
						tmp_has_range = 1;
						tmp_range.underflow = 1;
						tmp_range.min = ZEND_LONG_MIN;
						tmp_range.max = ZEND_LONG_MAX;
						tmp_range.overflow = 1;
					}
				} else {
					tmp_has_range = 0;
				}
			}
		}
	}

	if (!(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
		if (tmp_is_instanceof < 0) {
			tmp_is_instanceof = 0;
			tmp_ce = NULL;
		}
		if (tmp_has_range < 0) {
			tmp_has_range = 0;
		}
		ret->ce = tmp_ce;
		ret->is_instanceof = tmp_is_instanceof;
	}
	ret->type &= tmp;
	ret->range = tmp_range;
	ret->has_range = tmp_has_range;
}

static zend_result zend_infer_types(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
{
	int ssa_vars_count = ssa->vars_count;
	int j;
	zend_bitset worklist;
	ALLOCA_FLAG(use_heap);

	worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap);
	memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));

	/* Type Inference */
	for (j = op_array->last_var; j < ssa_vars_count; j++) {
		zend_bitset_incl(worklist, j);
	}

	if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) == FAILURE) {
		free_alloca(worklist,  use_heap);
		return FAILURE;
	}

	if (optimization_level & ZEND_OPTIMIZER_NARROW_TO_DOUBLE) {
		/* Narrowing integer initialization to doubles */
		zend_type_narrowing(op_array, script, ssa, optimization_level);
	}

	if (ZEND_FUNC_INFO(op_array)) {
		zend_func_return_info(op_array, script, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info);
	}

	free_alloca(worklist,  use_heap);
	return SUCCESS;
}

static void zend_mark_cv_references(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa)
{
	int var, def;
	const zend_op *opline;
	zend_arg_info *arg_info;
	uint32_t worklist_len = zend_bitset_len(ssa->vars_count);
	zend_bitset worklist;
	ALLOCA_FLAG(use_heap);

	worklist = do_alloca(sizeof(zend_ulong) * worklist_len, use_heap);
	memset(worklist, 0, sizeof(zend_ulong) * worklist_len);

	/* Collect SSA variables which definitions creates PHP reference */
	for (var = 0; var < ssa->vars_count; var++) {
		def = ssa->vars[var].definition;
		if (def >= 0 && ssa->vars[var].var < op_array->last_var) {
			opline = op_array->opcodes + def;
			if (ssa->ops[def].result_def == var) {
				switch (opline->opcode) {
					case ZEND_RECV:
					case ZEND_RECV_INIT:
						arg_info = &op_array->arg_info[opline->op1.num-1];
						if (!ZEND_ARG_SEND_MODE(arg_info)) {
							continue;
						}
						break;
					default:
						continue;
				}
			} else if (ssa->ops[def].op1_def == var) {
				switch (opline->opcode) {
					case ZEND_ASSIGN_REF:
					case ZEND_MAKE_REF:
					case ZEND_FE_RESET_RW:
					case ZEND_BIND_GLOBAL:
					case ZEND_SEND_REF:
					case ZEND_SEND_VAR_EX:
					case ZEND_SEND_FUNC_ARG:
					case ZEND_BIND_INIT_STATIC_OR_JMP:
						break;
					case ZEND_INIT_ARRAY:
					case ZEND_ADD_ARRAY_ELEMENT:
						if (!(opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) {
							continue;
						}
						break;
					case ZEND_BIND_STATIC:
						if (!(opline->extended_value & ZEND_BIND_REF)) {
							continue;
						}
						break;
					case ZEND_YIELD:
						if (!(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
							continue;
						}
						break;
					case ZEND_OP_DATA:
						switch ((opline-1)->opcode) {
							case ZEND_ASSIGN_OBJ_REF:
							case ZEND_ASSIGN_STATIC_PROP_REF:
								break;
							default:
								continue;
						}
						break;
					default:
						continue;
				}
			} else if (ssa->ops[def].op2_def == var) {
				switch (opline->opcode) {
					case ZEND_ASSIGN_REF:
					case ZEND_FE_FETCH_RW:
						break;
					case ZEND_BIND_LEXICAL:
						if (!(opline->extended_value & ZEND_BIND_REF)) {
							continue;
						}
						break;
					default:
						continue;
				}
			} else {
				ZEND_UNREACHABLE();
			}
			zend_bitset_incl(worklist, var);
		} else if (ssa->var_info[var].type & MAY_BE_REF) {
			zend_bitset_incl(worklist, var);
		} else if (ssa->vars[var].alias == SYMTABLE_ALIAS) {
			zend_bitset_incl(worklist, var);
		}
	}

	/* Set and propagate MAY_BE_REF */
	WHILE_WORKLIST(worklist, worklist_len, var) {

		ssa->var_info[var].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 (ssa->vars[var].phi_use_chain) {
			zend_ssa_phi *p = ssa->vars[var].phi_use_chain;
			do {
				if (!(ssa->var_info[p->ssa_var].type & MAY_BE_REF)) {
					zend_bitset_incl(worklist, p->ssa_var);
				}
				p = zend_ssa_next_use_phi(ssa, var, p);
			} while (p);
		}

		if (ssa->vars[var].use_chain >= 0) {
			int use = ssa->vars[var].use_chain;
			FOREACH_USE(&ssa->vars[var], use) {
				zend_ssa_op *op = ssa->ops + use;
				if (op->op1_use == var && op->op1_def >= 0) {
					if (!(ssa->var_info[op->op1_def].type & MAY_BE_REF)) {
						/* Unset breaks references (outside global scope). */
						if (op_array->opcodes[use].opcode == ZEND_UNSET_CV
								&& op_array->function_name) {
							continue;
						}
						zend_bitset_incl(worklist, op->op1_def);
					}
				}
				if (op->op2_use == var && op->op2_def >= 0) {
					if (!(ssa->var_info[op->op2_def].type & MAY_BE_REF)) {
						zend_bitset_incl(worklist, op->op2_def);
					}
				}
				if (op->result_use == var && op->result_def >= 0) {
					if (!(ssa->var_info[op->result_def].type & MAY_BE_REF)) {
						zend_bitset_incl(worklist, op->result_def);
					}
				}
			} FOREACH_USE_END();
		}
	} WHILE_WORKLIST_END();

	free_alloca(worklist,  use_heap);
}

ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level) /* {{{ */
{
	zend_ssa_var_info *ssa_var_info;
	int i;

	if (!ssa->var_info) {
		ssa->var_info = zend_arena_calloc(arena, ssa->vars_count, sizeof(zend_ssa_var_info));
	}
	ssa_var_info = ssa->var_info;

	if (!op_array->function_name) {
		for (i = 0; i < op_array->last_var; i++) {
			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;
			ssa_var_info[i].has_range = 0;
		}
	} else {
		for (i = 0; i < op_array->last_var; i++) {
			ssa_var_info[i].type = MAY_BE_UNDEF;
			ssa_var_info[i].has_range = 0;
			if (ssa->vars[i].alias) {
				ssa_var_info[i].type |= get_ssa_alias_types(ssa->vars[i].alias);
			}
		}
	}
	for (i = op_array->last_var; i < ssa->vars_count; i++) {
		ssa_var_info[i].type = 0;
		ssa_var_info[i].has_range = 0;
	}

	zend_mark_cv_references(op_array, script, ssa);

	zend_infer_ranges(op_array, ssa);

	if (zend_infer_types(op_array, script, ssa, optimization_level) == FAILURE) {
		return FAILURE;
	}

	return SUCCESS;
}
/* }}} */

ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, const zend_ssa *ssa, uint32_t t1, uint32_t t2)
{
	if (opline->op1_type == IS_CV) {
		if (t1 & MAY_BE_UNDEF) {
			switch (opline->opcode) {
				case ZEND_UNSET_VAR:
				case ZEND_ISSET_ISEMPTY_VAR:
					return 1;
				case ZEND_ISSET_ISEMPTY_DIM_OBJ:
				case ZEND_ISSET_ISEMPTY_PROP_OBJ:
				case ZEND_ASSIGN:
				case ZEND_ASSIGN_DIM:
				case ZEND_ASSIGN_REF:
				case ZEND_BIND_GLOBAL:
				case ZEND_BIND_STATIC:
				case ZEND_BIND_INIT_STATIC_OR_JMP:
				case ZEND_FETCH_DIM_IS:
				case ZEND_FETCH_OBJ_IS:
				case ZEND_SEND_REF:
				case ZEND_UNSET_CV:
				case ZEND_ISSET_ISEMPTY_CV:
				case ZEND_MAKE_REF:
				case ZEND_FETCH_DIM_W:
					break;
				default:
					/* undefined variable warning */
					return 1;
			}
		}
	} else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
		if ((t1 & MAY_BE_RC1)
		 && (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
			switch (opline->opcode) {
				case ZEND_CASE:
				case ZEND_CASE_STRICT:
				case ZEND_FE_FETCH_R:
				case ZEND_FE_FETCH_RW:
				case ZEND_FETCH_LIST_R:
				case ZEND_QM_ASSIGN:
				case ZEND_SEND_VAL:
				case ZEND_SEND_VAL_EX:
				case ZEND_SEND_VAR:
				case ZEND_SEND_VAR_EX:
				case ZEND_SEND_FUNC_ARG:
				case ZEND_SEND_VAR_NO_REF:
				case ZEND_SEND_VAR_NO_REF_EX:
				case ZEND_SEND_REF:
				case ZEND_SEPARATE:
				case ZEND_END_SILENCE:
				case ZEND_MAKE_REF:
					break;
				default:
					/* destructor may be called */
					return 1;
			}
		}
	}

	if (opline->op2_type == IS_CV) {
		if (t2 & MAY_BE_UNDEF) {
			switch (opline->opcode) {
				case ZEND_ASSIGN_REF:
				case ZEND_FE_FETCH_R:
				case ZEND_FE_FETCH_RW:
					break;
				default:
					/* undefined variable warning */
					return 1;
			}
		}
	} else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
		if ((t2 & MAY_BE_RC1)
		 && (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
			switch (opline->opcode) {
				case ZEND_ASSIGN:
				case ZEND_FE_FETCH_R:
				case ZEND_FE_FETCH_RW:
					break;
				default:
					/* destructor may be called */
					return 1;
			}
		}
	}

	switch (opline->opcode) {
		case ZEND_NOP:
		case ZEND_QM_ASSIGN:
		case ZEND_JMP:
		case ZEND_CHECK_VAR:
		case ZEND_MAKE_REF:
		case ZEND_BEGIN_SILENCE:
		case ZEND_END_SILENCE:
		case ZEND_FREE:
		case ZEND_FE_FREE:
		case ZEND_SEPARATE:
		case ZEND_TYPE_CHECK:
		case ZEND_DEFINED:
		case ZEND_ISSET_ISEMPTY_THIS:
		case ZEND_COALESCE:
		case ZEND_SWITCH_LONG:
		case ZEND_SWITCH_STRING:
		case ZEND_MATCH:
		case ZEND_ISSET_ISEMPTY_VAR:
		case ZEND_ISSET_ISEMPTY_CV:
		case ZEND_FUNC_NUM_ARGS:
		case ZEND_FUNC_GET_ARGS:
		case ZEND_COPY_TMP:
		case ZEND_JMP_NULL:
		case ZEND_JMP_FRAMELESS:
			return 0;
		case ZEND_IS_IDENTICAL:
		case ZEND_IS_NOT_IDENTICAL:
		case ZEND_CASE_STRICT:
			/* Array to array comparison may lead to recursion. */
			return (t1 & t2) & MAY_BE_ARRAY_OF_ARRAY;
		case ZEND_SEND_VAR:
		case ZEND_SEND_VAL:
		case ZEND_SEND_REF:
		case ZEND_SEND_VAR_EX:
		case ZEND_SEND_FUNC_ARG:
		case ZEND_CHECK_FUNC_ARG:
			/* May throw for named params. */
			return opline->op2_type == IS_CONST;
		case ZEND_INIT_FCALL:
			/* can't throw, because call is resolved at compile time */
			return 0;
		case ZEND_BIND_GLOBAL:
			if ((opline+1)->opcode == ZEND_BIND_GLOBAL) {
				return zend_may_throw(opline + 1, ssa_op ? ssa_op + 1 : NULL, op_array, ssa);
			}
			return 0;
		case ZEND_ADD:
			if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
			 && (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
				return 0;
			}
			return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
				(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_DIV:
			if (!OP2_HAS_RANGE() ||
				(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
				/* Division by zero */
				return 1;
			}
			ZEND_FALLTHROUGH;
		case ZEND_SUB:
		case ZEND_MUL:
		case ZEND_POW:
			return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
				(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		/* Ops may throw if not an integer */
		case ZEND_MOD:
			if (!OP2_HAS_RANGE() ||
				(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
				/* Division by zero */
				return 1;
			}
			ZEND_FALLTHROUGH;
		case ZEND_SL:
		case ZEND_SR:
			return (t1 & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
				(t2 & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
				!OP2_HAS_RANGE() ||
				OP2_MIN_RANGE() < 0;
		case ZEND_CONCAT:
		case ZEND_FAST_CONCAT:
			return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
				(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
		case ZEND_BW_OR:
		case ZEND_BW_AND:
		case ZEND_BW_XOR:
			if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
			 && (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
				return 0;
			}
			return (t1 & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
				(t2 & (MAY_BE_STRING|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_BW_NOT:
			return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_PRE_INC:
		case ZEND_POST_INC:
			return (t1 & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		/* null emits a warning as it has no effect compared to ++ which converts the value to 1 */
		case ZEND_PRE_DEC:
		case ZEND_POST_DEC:
			return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_BOOL_NOT:
		case ZEND_JMPZ:
		case ZEND_JMPNZ:
		case ZEND_JMPZ_EX:
		case ZEND_JMPNZ_EX:
		case ZEND_BOOL:
		case ZEND_JMP_SET:
			return (t1 & MAY_BE_OBJECT);
		case ZEND_BOOL_XOR:
			return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT);
		case ZEND_IS_EQUAL:
		case ZEND_IS_NOT_EQUAL:
		case ZEND_IS_SMALLER:
		case ZEND_IS_SMALLER_OR_EQUAL:
		case ZEND_CASE:
		case ZEND_SPACESHIP:
			if ((t1 & MAY_BE_ANY) == MAY_BE_NULL
			 || (t2 & MAY_BE_ANY) == MAY_BE_NULL) {
				return 0;
			}
			return (t1 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT)) || (t2 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT));
		case ZEND_ASSIGN_OP:
			if (opline->extended_value == ZEND_ADD) {
				if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
				 && (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
					return 0;
				}
				return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
					(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
			} else if (opline->extended_value == ZEND_DIV ||
				opline->extended_value == ZEND_MOD) {
				if (!OP2_HAS_RANGE() ||
					(OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
					/* Division by zero */
					return 1;
				}
				return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
					(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
			} else if (opline->extended_value == ZEND_SUB ||
				opline->extended_value == ZEND_MUL ||
				opline->extended_value == ZEND_POW) {
				return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
					(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
			} else if (opline->extended_value == ZEND_SL ||
				opline->extended_value == ZEND_SR) {
				return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
					(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
					!OP2_HAS_RANGE() ||
					OP2_MIN_RANGE() < 0;
			} else if (opline->extended_value == ZEND_CONCAT) {
				return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
					(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
			} else if (opline->extended_value == ZEND_BW_OR ||
				opline->extended_value == ZEND_BW_AND ||
				opline->extended_value == ZEND_BW_XOR) {
				if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
				 && (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
					return 0;
				}
				return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
					(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
			}
			return 1;
		case ZEND_ASSIGN:
			if (t1 & MAY_BE_REF) {
				return 1;
			}
			ZEND_FALLTHROUGH;
		case ZEND_UNSET_VAR:
			return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
		case ZEND_BIND_STATIC:
		case ZEND_BIND_INIT_STATIC_OR_JMP:
			if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
				/* Destructor may throw. */
				return 1;
			}
			return 0;
		case ZEND_ASSIGN_DIM:
			if ((opline+1)->op1_type == IS_CV) {
				if (OP1_DATA_INFO() & MAY_BE_UNDEF) {
					return 1;
				}
			}
			if (t1 & (MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_REF)) {
				return 1;
			}
			return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE)) || opline->op2_type == IS_UNUSED ||
				(t2 & (MAY_BE_UNDEF|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_ASSIGN_OBJ:
			if (t1 & (MAY_BE_ANY-MAY_BE_OBJECT)) {
				return 1;
			}
			if ((opline+1)->op1_type == IS_CV) {
				if (OP1_DATA_INFO() & MAY_BE_UNDEF) {
					return 1;
				}
			}
			if (ssa_op->op1_use) {
				const zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
				const zend_class_entry *ce = var_info->ce;

				if (var_info->is_instanceof
				 || !ce
				 || ce->create_object
				 || ce->default_object_handlers->write_property != zend_std_write_property
				 || ce->default_object_handlers->get_property_ptr_ptr != zend_std_get_property_ptr_ptr
				 || ce->__get
				 || ce->__set
				 || ce->parent) {
					return 1;
				}

				if (opline->op2_type != IS_CONST) {
					return 1;
				}

				zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op2));
				if (ZSTR_LEN(prop_name) > 0 && ZSTR_VAL(prop_name)[0] == '\0') {
					return 1;
				}

				zend_property_info *prop_info =
					zend_hash_find_ptr(&ce->properties_info, prop_name);
				if (prop_info) {
					if (ZEND_TYPE_IS_SET(prop_info->type)) {
						return 1;
					}
					return !(prop_info->flags & ZEND_ACC_PUBLIC)
						&& prop_info->ce != op_array->scope;
				} else {
					return !(ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES);
				}
			}
			return 1;
		case ZEND_ROPE_INIT:
		case ZEND_ROPE_ADD:
		case ZEND_ROPE_END:
			return t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT);
		case ZEND_INIT_ARRAY:
			return (opline->op2_type != IS_UNUSED) && (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_ADD_ARRAY_ELEMENT:
			return (opline->op2_type == IS_UNUSED) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_STRLEN:
			return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
		case ZEND_COUNT:
			return (t1 & MAY_BE_ANY) != MAY_BE_ARRAY;
		case ZEND_RECV_INIT:
			if (Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_CONSTANT_AST) {
				return 1;
			}
			if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
				uint32_t arg_num = opline->op1.num;
				const zend_arg_info *cur_arg_info;

				if (EXPECTED(arg_num <= op_array->num_args)) {
					cur_arg_info = &op_array->arg_info[arg_num-1];
				} else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
					cur_arg_info = &op_array->arg_info[op_array->num_args];
				} else {
					return 0;
				}
				return ZEND_TYPE_IS_SET(cur_arg_info->type);
			} else {
				return 0;
			}
		case ZEND_FETCH_IS:
			return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
		case ZEND_ISSET_ISEMPTY_DIM_OBJ:
			return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT));
		case ZEND_FETCH_DIM_IS:
			return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
		case ZEND_CAST:
			switch (opline->extended_value) {
				case IS_LONG:
				case IS_DOUBLE:
					return (t1 & MAY_BE_OBJECT);
				case IS_STRING:
					return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
				case IS_ARRAY:
					return (t1 & MAY_BE_OBJECT);
				case IS_OBJECT:
					return 0;
				EMPTY_SWITCH_DEFAULT_CASE()
			}
			/* GCC is getting confused here for the Wimplicit-fallthrough warning with
			 * EMPTY_SWITCH_DEFAULT_CASE() macro */
			return 0;
		case ZEND_ARRAY_KEY_EXISTS:
			if ((t2 & MAY_BE_ANY) != MAY_BE_ARRAY) {
				return 1;
			}
			if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
				return 1;
			}
			return 0;
		case ZEND_FE_RESET_R:
		case ZEND_FE_RESET_RW:
			if ((t1 & (MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
				return 1;
			}
			return 0;
		case ZEND_FE_FETCH_R:
			if ((t1 & (MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
				return 1;
			}
			if (opline->op2_type == IS_CV
			 && (t2 & MAY_BE_RC1)
			 && (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
				return 1;
			}
			return 0;
		case ZEND_FETCH_DIM_W:
		case ZEND_FETCH_LIST_W:
			if (t1 & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
				return 1;
			}
			if (t2 & (MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
				return 1;
			}
			if (opline->op2_type == IS_UNUSED) {
				return 1;
			}
			return 0;
		default:
			return 1;
	}
}

ZEND_API bool zend_may_throw(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, const zend_ssa *ssa)
{
	return zend_may_throw_ex(opline, ssa_op, op_array, ssa, OP1_INFO(), OP2_INFO());
}
