Commit a8135c8021b for php
commit a8135c8021b1da9c5f9814efd9e32b25e229b80d
Author: Nora Dossche <7771979+ndossche@users.noreply.github.com>
Date: Fri Oct 2 20:30:18 2026 +0200
JIT: Add fast path for object arguments in RECV (#24052)
* JIT: Add fast path for object arguments in RECV
* Review
diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index 9d8e14c0217..80d3287a420 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -2544,7 +2544,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
}
goto done;
case ZEND_RECV:
- if (!zend_jit_recv(&ctx, opline, op_array)) {
+ if (!zend_jit_recv(&ctx, opline, op_array, NULL)) {
goto jit_failure;
}
goto done;
@@ -2618,7 +2618,7 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
/* TODO May need reference unwrapping. */
break;
}
- if (!zend_jit_verify_return_type(&ctx, opline, op_array, OP1_INFO())) {
+ if (!zend_jit_verify_return_type(&ctx, opline, op_array, OP1_INFO(), NULL)) {
goto jit_failure;
}
goto done;
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index c6d4927d41b..9ea95627907 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -10738,24 +10738,104 @@ static int zend_jit_constructor(zend_jit_ctx *jit, const zend_op *opline, const
return 1;
}
-static int zend_jit_verify_arg_type(zend_jit_ctx *jit, const zend_op *opline, zend_arg_info *arg_info, bool check_exception)
+static bool zend_jit_class_is_persistent(const zend_class_entry *ce)
+{
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+#ifdef ZEND_OPCACHE_SHM_REATTACHMENT
+ /* ASLR, see zend_jit_may_be_modified() */
+ return false;
+#else
+ return true;
+#endif
+ }
+ return (ce->ce_flags & ZEND_ACC_IMMUTABLE) != 0;
+}
+
+/* Checks whether a class "ce" always satisfies the type "type".
+ * This must take into account different requests.
+ * Checks against names of parents and interfaces. */
+static bool zend_jit_class_satisfies_type(const zend_class_entry *ce, zend_type type)
+{
+ const zend_type *single_type;
+
+ /* Simplification: skip intersection types */
+ if (!ce || !zend_jit_class_is_persistent(ce) || ZEND_TYPE_IS_INTERSECTION(type)) {
+ return false;
+ }
+
+ ZEND_TYPE_FOREACH(type, single_type) {
+ /* Intersection members of DNF types are not handled */
+ if (ZEND_TYPE_HAS_NAME(*single_type)) {
+ const zend_string *name = ZEND_TYPE_NAME(*single_type);
+
+ for (const zend_class_entry *parent = ce; parent; parent = parent->parent) {
+ if (zend_string_equals_ci(parent->name, name)) {
+ return true;
+ }
+ }
+ for (uint32_t i = 0; i < ce->num_interfaces; i++) {
+ if (zend_string_equals_ci(ce->interfaces[i]->name, name)) {
+ return true;
+ }
+ }
+ }
+ } ZEND_TYPE_FOREACH_END();
+
+ return false;
+}
+
+/* Emit a fast path for an object of the known class "ce".
+ * Leaves the control flow in the "no match" path, and adds the matching path to "end_inputs". */
+static void zend_jit_known_class_type_fast_path(zend_jit_ctx *jit, ir_ref ref, bool is_object, const zend_class_entry *ce, ir_ref *end_inputs)
+{
+ ir_ref not_object = IR_UNUSED, if_ce;
+
+ if (!is_object) {
+ // JIT: Z_TYPE_P(ref) == IS_OBJECT
+ ir_ref if_object = jit_if_Z_TYPE_ref(jit, ref, ir_CONST_U8(IS_OBJECT));
+ ir_IF_FALSE(if_object);
+ not_object = ir_END();
+ ir_IF_TRUE(if_object);
+ }
+
+ // JIT: Z_OBJ_P(ref)->ce == ce
+ if_ce = ir_IF(ir_EQ(
+ ir_LOAD_A(ir_ADD_OFFSET(jit_Z_PTR_ref(jit, ref), offsetof(zend_object, ce))),
+ ir_CONST_ADDR(ce)));
+ ir_IF_TRUE(if_ce);
+ ir_END_list(*end_inputs);
+
+ ir_IF_FALSE(if_ce);
+ if (not_object) {
+ ir_MERGE_WITH(not_object);
+ }
+}
+
+static int zend_jit_verify_arg_type(zend_jit_ctx *jit, const zend_op *opline, zend_arg_info *arg_info, bool check_exception, const zend_class_entry *known_ce)
{
zend_jit_addr res_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FP, opline->result.var);
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type) & MAY_BE_ANY;
- ir_ref ref, fast_path = IR_UNUSED;
+ ir_ref ref, fast_path = IR_UNUSED, end_inputs = IR_UNUSED;
+ uint8_t type = IS_UNKNOWN;
ref = jit_ZVAL_ADDR(jit, res_addr);
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE
&& JIT_G(current_frame)
&& JIT_G(current_frame)->prev) {
zend_jit_trace_stack *stack = JIT_G(current_frame)->stack;
- uint8_t type = STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var));
+ type = STACK_TYPE(stack, EX_VAR_TO_NUM(opline->result.var));
if (type != IS_UNKNOWN && (type_mask & (1u << type))) {
return 1;
}
}
+ if (!ZEND_ARG_SEND_MODE(arg_info)
+ && (type == IS_UNKNOWN || type == IS_OBJECT)
+ && zend_jit_class_satisfies_type(known_ce, arg_info->type)) {
+ zend_jit_known_class_type_fast_path(jit, ref, type == IS_OBJECT, known_ce, &end_inputs);
+ }
+
if (ZEND_ARG_SEND_MODE(arg_info)) {
if (opline->opcode == ZEND_RECV_INIT) {
ref = jit_ZVAL_DEREF_ref(jit, ref);
@@ -10794,10 +10874,15 @@ static int zend_jit_verify_arg_type(zend_jit_ctx *jit, const zend_op *opline, ze
ir_MERGE_WITH(fast_path);
}
+ if (end_inputs) {
+ ir_END_list(end_inputs);
+ ir_MERGE_list(end_inputs);
+ }
+
return 1;
}
-static int zend_jit_recv(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array)
+static int zend_jit_recv(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, const zend_class_entry *known_ce)
{
uint32_t arg_num = opline->op1.num;
zend_arg_info *arg_info = NULL;
@@ -10839,7 +10924,7 @@ static int zend_jit_recv(zend_jit_ctx *jit, const zend_op *opline, const zend_op
}
if (arg_info) {
- if (!zend_jit_verify_arg_type(jit, opline, arg_info, true)) {
+ if (!zend_jit_verify_arg_type(jit, opline, arg_info, true, known_ce)) {
return 0;
}
}
@@ -10908,7 +10993,7 @@ static int zend_jit_recv_init(zend_jit_ctx *jit, const zend_op *opline, const ze
if (!ZEND_TYPE_IS_SET(arg_info->type)) {
break;
}
- if (!zend_jit_verify_arg_type(jit, opline, arg_info, may_throw)) {
+ if (!zend_jit_verify_arg_type(jit, opline, arg_info, may_throw, NULL)) {
return 0;
}
} while (0);
@@ -10917,14 +11002,22 @@ static int zend_jit_recv_init(zend_jit_ctx *jit, const zend_op *opline, const ze
return 1;
}
-static bool zend_jit_verify_return_type(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info)
+static bool zend_jit_verify_return_type(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, const zend_class_entry *known_ce)
{
zend_arg_info *arg_info = &op_array->arg_info[-1];
ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type));
zend_jit_addr op1_addr = OP1_ADDR();
bool needs_slow_check = true;
uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type) & MAY_BE_ANY;
- ir_ref fast_path = IR_UNUSED;
+ ir_ref fast_path = IR_UNUSED, end_inputs = IR_UNUSED;
+
+ if ((op1_info & MAY_BE_OBJECT)
+ && !(type_mask & MAY_BE_OBJECT)
+ && Z_MODE(op1_addr) == IS_MEM_ZVAL
+ && zend_jit_class_satisfies_type(known_ce, arg_info->type)) {
+ zend_jit_known_class_type_fast_path(jit, jit_ZVAL_ADDR(jit, op1_addr),
+ (op1_info & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_OBJECT, known_ce, &end_inputs);
+ }
if (type_mask != 0) {
if (((op1_info & MAY_BE_ANY) & type_mask) == 0) {
@@ -10969,6 +11062,11 @@ static bool zend_jit_verify_return_type(zend_jit_ctx *jit, const zend_op *opline
}
}
+ if (end_inputs) {
+ ir_END_list(end_inputs);
+ ir_MERGE_list(end_inputs);
+ }
+
return true;
}
diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c
index 1ce0b468a1b..68461973f3f 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -6194,7 +6194,7 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer,
ssa_op = orig_ssa_op;
goto done;
case ZEND_RECV:
- if (!zend_jit_recv(&ctx, opline, op_array)) {
+ if (!zend_jit_recv(&ctx, opline, op_array, op1_ce)) {
goto jit_failure;
}
goto done;
@@ -6317,7 +6317,7 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer,
/* TODO May need reference unwrapping. */
break;
}
- if (!zend_jit_verify_return_type(&ctx, opline, op_array, op1_info)) {
+ if (!zend_jit_verify_return_type(&ctx, opline, op_array, op1_info, op1_ce)) {
goto jit_failure;
}
goto done;
@@ -7920,6 +7920,10 @@ static void zend_jit_dump_trace(zend_jit_trace_rec *trace_buffer, zend_ssa *tssa
const char *type = ((op3_type & ~IS_TRACE_INDIRECT) == 0) ? "undef" : zend_get_type_by_const(op3_type & ~(IS_TRACE_REFERENCE|IS_TRACE_INDIRECT));
fprintf(stderr, " op3(%s%s)", ref, type);
}
+ } else if (opline->opcode == ZEND_RECV && (p+1)->op == ZEND_JIT_TRACE_OP1_TYPE) {
+ /* Class of the received object */
+ p++;
+ fprintf(stderr, " ; arg(object of class %s)", ZSTR_VAL(p->ce->name));
}
if ((p+1)->op == ZEND_JIT_TRACE_VAL_INFO) {
uint8_t val_type;
diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c
index d203ed70dae..fc40931c7ea 100644
--- a/ext/opcache/jit/zend_jit_vm_helpers.c
+++ b/ext/opcache/jit/zend_jit_vm_helpers.c
@@ -851,6 +851,16 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex,
}
}
op1_type |= flags;
+ } else if (opline->opcode == ZEND_RECV) {
+ /* Record the class of an object passed to a class-typed parameter */
+ ZEND_ASSERT(opline->op1.num <= op_array->num_args);
+ if (ZEND_TYPE_IS_COMPLEX(op_array->arg_info[opline->op1.num - 1].type)) {
+ zval *zv = EX_VAR(opline->result.var);
+
+ if (Z_TYPE_P(zv) == IS_OBJECT) {
+ ce1 = Z_OBJCE_P(zv);
+ }
+ }
} else if (opline->op1_type == IS_UNUSED && (op_array->fn_flags & ZEND_ACC_CLOSURE)) {
uint32_t op1_flags = ZEND_VM_OP1_FLAGS(zend_get_opcode_flags(opline->opcode));
if ((op1_flags & ZEND_VM_OP_MASK) == ZEND_VM_OP_THIS) {