From 9a4bd76f4dc721dc8f5a3de0ad1ea279c3e3c310 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:18:22 +0100 Subject: [PATCH] Tracing JIT: Implement COALESCE as a guard resulting in a side-trace --- ext/opcache/jit/zend_jit_ir.c | 33 ++++++++++++++++++++++++++ ext/opcache/jit/zend_jit_trace.c | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index d7380b66e1c3..e4f0e122dcd5 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -7589,6 +7589,39 @@ static int zend_jit_identical(zend_jit_ctx *jit, return 1; } +/* This is only for the tracing JIT. + * `taken` is used for the recorded direction in the tracing jit. */ +static int zend_jit_coalesce(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, bool taken, const void *exit_addr) +{ + ir_ref ref = jit_ZVAL_ADDR(jit, op1_addr); + + ZEND_ASSERT(opline->op1_type & (IS_TMP_VAR|IS_CV)); + + if (opline->op1_type == IS_CV && (op1_info & MAY_BE_REF)) { + ref = jit_ZVAL_DEREF_ref(jit, ref); + op1_info &= ~MAY_BE_REF; + } + + if (exit_addr) { + // JIT: if (Z_TYPE_P(value) > IS_NULL) + ir_ref cond = ir_GT(jit_Z_TYPE_ref(jit, ref), ir_CONST_U8(IS_NULL)); + + if (taken) { + ir_GUARD(cond, ir_CONST_ADDR(exit_addr)); + } else { + ir_GUARD_NOT(cond, ir_CONST_ADDR(exit_addr)); + } + } + + if (taken) { + // JIT: ZVAL_COPY_VALUE(EX_VAR(opline->result.var), value); + // JIT: Z_ADDREF_P(EX_VAR(opline->result.var)) for CV + jit_ZVAL_COPY(jit, res_addr, -1, ZEND_ADDR_REF_ZVAL(ref), op1_info & ~(MAY_BE_UNDEF|MAY_BE_NULL), opline->op1_type == IS_CV); + } + + return 1; +} + static int zend_jit_bool_jmpznz(zend_jit_ctx *jit, const zend_op *opline, uint32_t op1_info, zend_jit_addr op1_addr, zend_jit_addr res_addr, uint32_t target_label, uint32_t target_label2, int may_throw, uint8_t branch_opcode, const void *exit_addr, bool warn_undef) { uint32_t true_label = -1; diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 9bdbf3a8a887..eb04cc5fd883 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -5761,6 +5761,46 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer, goto jit_failure; } goto done; + case ZEND_COALESCE: + if (!(opline->op1_type & (IS_TMP_VAR|IS_CV)) + /* COALESCE may have two identical successors due to DCE edge case. + * In that case we cannot tell the branch directions apart. */ + || OP_JMP_ADDR(opline, opline->op2) == opline + 1) { + break; + } + op1_info = OP1_INFO(); + CHECK_OP1_TRACE_TYPE(); + if ((p+1)->op == ZEND_JIT_TRACE_VM || (p+1)->op == ZEND_JIT_TRACE_END) { + bool taken; + uint32_t refined_type_mask; + + if ((p+1)->opline == OP_JMP_ADDR(opline, opline->op2)) { + taken = true; + refined_type_mask = op1_info & (MAY_BE_UNDEF|MAY_BE_NULL); + } else if ((p+1)->opline == opline + 1) { + taken = false; + refined_type_mask = op1_info & ((MAY_BE_ANY|MAY_BE_REF) - MAY_BE_NULL); + } else { + ZEND_UNREACHABLE(); + } + if (!refined_type_mask) { + /* unconditional branch */ + exit_addr = NULL; + } else { + /* Execute the other direction in VM or side trace. */ + uint32_t exit_point = zend_jit_trace_get_exit_point(opline, 0); + exit_addr = zend_jit_trace_get_exit_addr(exit_point); + if (!exit_addr) { + goto jit_failure; + } + } + if (!zend_jit_coalesce(&ctx, opline, op1_info, OP1_ADDR(), RES_ADDR(), taken, exit_addr)) { + goto jit_failure; + } + goto done; + } else { + ZEND_UNREACHABLE(); + } case ZEND_JMP_FRAMELESS: op1_info = OP1_INFO(); ZEND_ASSERT((p+1)->op == ZEND_JIT_TRACE_VM);