From 75945580bce42cfd4a40b5416e24788efae2c149 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 3 Sep 2025 11:48:39 +0200 Subject: [PATCH] Fix deoptimization after exit during inc/dec When the assumption that (PRE|POST)_(INC|DEC) overflows turns out to be false and we exit, effects are lost if op1 or result were in regs. Fix by updating the stack map before creating the exit point. Fixes GH-19669 Closes GH-19680 --- NEWS | 4 ++++ ext/opcache/jit/zend_jit_ir.c | 27 ++++++++++++-------------- ext/opcache/tests/jit/gh19669-001.phpt | 24 +++++++++++++++++++++++ ext/opcache/tests/jit/gh19669-002.phpt | 24 +++++++++++++++++++++++ 4 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 ext/opcache/tests/jit/gh19669-001.phpt create mode 100644 ext/opcache/tests/jit/gh19669-002.phpt diff --git a/NEWS b/NEWS index 51c763e9a8c..7917590336e 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,10 @@ PHP NEWS checks). (timwolla) . The __sleep() and __wakeup() magic methods have been deprecated. (Girgias) +- Opcache: + . Fixed bug GH-19669 (assertion failure in zend_jit_trace_type_to_info_ex). + (Arnaud) + - URI: . Fixed bug GH-19780 (InvalidUrlException should check $errors argument). (nielsdos) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 57f7e189e6c..e0f8677f328 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -4808,33 +4808,30 @@ static int zend_jit_inc_dec(zend_jit_ctx *jit, const zend_op *opline, uint32_t o int32_t exit_point; const void *exit_addr; zend_jit_trace_stack *stack; - uint32_t old_res_info = 0; + uint32_t old_res_info = 0, old_op1_info = 0; stack = JIT_G(current_frame)->stack; if (opline->result_type != IS_UNUSED) { old_res_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var)); + SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_LONG, 0); if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) { - SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var), IS_LONG, 0); + SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), ref); + } else { + SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->result.var), op1_lval_ref); } } + old_op1_info = STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var)); + SET_STACK_TYPE(stack, EX_VAR_TO_NUM(opline->op1.var), IS_LONG, 0); + SET_STACK_REF(stack, EX_VAR_TO_NUM(opline->op1.var), ref); + exit_point = zend_jit_trace_get_exit_point(opline + 1, 0); exit_addr = zend_jit_trace_get_exit_addr(exit_point); - if ((opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_PRE_DEC) && - opline->result_type != IS_UNUSED) { - if_overflow = ir_IF(ir_OVERFLOW(ref)); - ir_IF_FALSE_cold(if_overflow); - jit_set_Z_LVAL(jit, res_addr, ref); - if (Z_MODE(res_addr) != IS_REG) { - jit_set_Z_TYPE_INFO(jit, res_addr, IS_LONG); - } - jit_SIDE_EXIT(jit, ir_CONST_ADDR(exit_addr)); - ir_IF_TRUE(if_overflow); - } else { - ir_GUARD(ir_OVERFLOW(ref), ir_CONST_ADDR(exit_addr)); - } + ir_GUARD(ir_OVERFLOW(ref), ir_CONST_ADDR(exit_addr)); + if (opline->result_type != IS_UNUSED) { SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->result.var), old_res_info); } + SET_STACK_INFO(stack, EX_VAR_TO_NUM(opline->op1.var), old_op1_info); } else { if_overflow = ir_IF(ir_OVERFLOW(ref)); ir_IF_FALSE(if_overflow); diff --git a/ext/opcache/tests/jit/gh19669-001.phpt b/ext/opcache/tests/jit/gh19669-001.phpt new file mode 100644 index 00000000000..7d63643bb01 --- /dev/null +++ b/ext/opcache/tests/jit/gh19669-001.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-19669: assertion failure zend_jit_trace_type_to_info_ex +--CREDITS-- +YuanchengJiang +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(-3) diff --git a/ext/opcache/tests/jit/gh19669-002.phpt b/ext/opcache/tests/jit/gh19669-002.phpt new file mode 100644 index 00000000000..373356bcd06 --- /dev/null +++ b/ext/opcache/tests/jit/gh19669-002.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-19669 002: assertion failure zend_jit_trace_type_to_info_ex +--CREDITS-- +YuanchengJiang +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(-10)