Commit 3748255726b for php.net
commit 3748255726b84b80ec128485b3885bd801df0049
Author: Dmitry Stogov <dmitry@php.net>
Date: Wed Apr 1 19:54:10 2026 +0300
Update IR (#21594)
IR commit: 88a61bfde4657c4f639bcacae1e4cb77e1c23de4
diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c
index f6058c5abe5..a02332e0d39 100644
--- a/ext/opcache/jit/ir/ir.c
+++ b/ext/opcache/jit/ir/ir.c
@@ -1443,24 +1443,6 @@ bool ir_use_list_add(ir_ctx *ctx, ir_ref to, ir_ref ref)
}
}
-static int ir_ref_cmp(const void *p1, const void *p2)
-{
- return *(ir_ref*)p1 - *(ir_ref*)p2;
-}
-
-void ir_use_list_sort(ir_ctx *ctx, ir_ref ref)
-{
- ir_use_list *use_list;
- uint32_t n;
-
- IR_ASSERT(ref > 0);
- use_list = &ctx->use_lists[ref];
- n = use_list->count;
- if (n > 1) {
- qsort(ctx->use_edges + use_list->refs, n, sizeof(ir_ref), ir_ref_cmp);
- }
-}
-
void ir_replace(ir_ctx *ctx, ir_ref ref, ir_ref new_ref)
{
int i, j, n, *p, use;
diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc
index 5a6718b77c1..bdf6b027b9f 100644
--- a/ext/opcache/jit/ir/ir_aarch64.dasc
+++ b/ext/opcache/jit/ir/ir_aarch64.dasc
@@ -720,7 +720,7 @@ get_arg_hints:
break;
case IR_PARAM:
constraints->def_reg = ir_get_param_reg(ctx, ref);
- flags = 0;
+ flags = (constraints->def_reg != IR_REG_NONE) ? IR_USE_SHOULD_BE_IN_REG : 0;
break;
case IR_PI:
case IR_PHI:
diff --git a/ext/opcache/jit/ir/ir_builder.h b/ext/opcache/jit/ir/ir_builder.h
index 03add759065..084216a0634 100644
--- a/ext/opcache/jit/ir/ir_builder.h
+++ b/ext/opcache/jit/ir/ir_builder.h
@@ -654,7 +654,7 @@ void _ir_TAILCALL_3(ir_ctx *ctx, ir_type type, ir_ref func, ir_ref arg1, ir_re
void _ir_TAILCALL_4(ir_ctx *ctx, ir_type type, ir_ref func, ir_ref arg1, ir_ref arg2, ir_ref arg3, ir_ref arg4);
void _ir_TAILCALL_5(ir_ctx *ctx, ir_type type, ir_ref func, ir_ref arg1, ir_ref arg2, ir_ref arg3, ir_ref arg4, ir_ref arg5);
void _ir_TAILCALL_6(ir_ctx *ctx, ir_type type, ir_ref func, ir_ref arg1, ir_ref arg2, ir_ref arg3, ir_ref arg4, ir_ref arg5, ir_ref arg6);
-ir_ref _ir_TAILCALL_N(ir_ctx *ctx, ir_type type, ir_ref func, uint32_t count, ir_ref *args);
+void _ir_TAILCALL_N(ir_ctx *ctx, ir_type type, ir_ref func, uint32_t count, ir_ref *args);
ir_ref _ir_ALLOCA(ir_ctx *ctx, ir_ref size);
void _ir_AFREE(ir_ctx *ctx, ir_ref size);
ir_ref _ir_VLOAD(ir_ctx *ctx, ir_type type, ir_ref var);
diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c
index 7edb012f617..1b45eb834ce 100644
--- a/ext/opcache/jit/ir/ir_gcm.c
+++ b/ext/opcache/jit/ir/ir_gcm.c
@@ -408,8 +408,6 @@ static bool ir_split_partially_dead_node(ir_ctx *ctx, ir_ref ref, uint32_t b)
}
/* Reconstruct IR: Update DEF->USE lists, CFG mapping and etc */
- ctx->use_lists = ir_mem_realloc(ctx->use_lists, ctx->insns_count * sizeof(ir_use_list));
- ctx->cfg_map = ir_mem_realloc(ctx->cfg_map, ctx->insns_count * sizeof(uint32_t));
n = ctx->use_lists[ref].refs;
for (i = 0; i < clones_count; i++) {
clone = clones[i].ref;
@@ -428,6 +426,7 @@ static bool ir_split_partially_dead_node(ir_ctx *ctx, ir_ref ref, uint32_t b)
uint32_t u = clones[i].use;
while (u != (uint32_t)-1) {
+ uint32_t src = uses[u].block;
use = uses[u].ref;
ctx->use_edges[n++] = use;
u = uses[u].next;
@@ -437,9 +436,11 @@ static bool ir_split_partially_dead_node(ir_ctx *ctx, ir_ref ref, uint32_t b)
ir_ref k, l = insn->inputs_count;
if (insn->op == IR_PHI) {
- for (k = 1; k <= l; k++) {
- if (ir_insn_op(insn, k) == ref) {
- j = ctx->cfg_map[ir_insn_op(&ctx->ir_base[insn->op1], k - 1)];
+ ir_insn *merge = &ctx->ir_base[insn->op1];
+ for (k = 2; k <= l; k++) {
+ j = ctx->cfg_map[ir_insn_op(merge, k - 1)];
+ if (j == src) {
+ IR_ASSERT(ir_insn_op(insn, k) == ref);
if (j != clones[i].block) {
uint32_t dom_depth = ctx->cfg_blocks[clones[i].block].dom_depth;
while (ctx->cfg_blocks[j].dom_depth > dom_depth) {
diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h
index 96b81a0fcd7..9e3a3a171b4 100644
--- a/ext/opcache/jit/ir/ir_private.h
+++ b/ext/opcache/jit/ir/ir_private.h
@@ -1047,7 +1047,6 @@ void ir_use_list_remove_one(ir_ctx *ctx, ir_ref def, ir_ref use);
void ir_use_list_replace_all(ir_ctx *ctx, ir_ref def, ir_ref use, ir_ref new_use);
void ir_use_list_replace_one(ir_ctx *ctx, ir_ref def, ir_ref use, ir_ref new_use);
bool ir_use_list_add(ir_ctx *ctx, ir_ref def, ir_ref use);
-void ir_use_list_sort(ir_ctx *ctx, ir_ref def);
IR_ALWAYS_INLINE ir_ref ir_next_control(const ir_ctx *ctx, ir_ref ref)
{
@@ -1100,6 +1099,7 @@ void ir_iter_add_uses(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist);
void ir_iter_replace(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist);
void ir_iter_update_op(ir_ctx *ctx, ir_ref ref, uint32_t idx, ir_ref new_val, ir_bitqueue *worklist);
void ir_iter_opt(ir_ctx *ctx, ir_bitqueue *worklist);
+void ir_iter_cleanup(ir_ctx *ctx);
/*** IR Basic Blocks info ***/
#define IR_IS_BB_START(op) \
diff --git a/ext/opcache/jit/ir/ir_ra.c b/ext/opcache/jit/ir/ir_ra.c
index 4a893410d49..aff9aa7bab3 100644
--- a/ext/opcache/jit/ir/ir_ra.c
+++ b/ext/opcache/jit/ir/ir_ra.c
@@ -3190,6 +3190,7 @@ static ir_reg ir_allocate_blocked_reg(ir_ctx *ctx, ir_live_interval *ival, ir_li
return IR_REG_NONE;
}
if (split_pos >= blockPos[reg]) {
+try_next_available_register:
IR_REGSET_EXCL(available, reg);
if (IR_REGSET_IS_EMPTY(available)) {
fprintf(stderr, "LSRA Internal Error: Unsolvable conflict. Allocation is not possible\n");
@@ -3222,31 +3223,33 @@ static ir_reg ir_allocate_blocked_reg(ir_ctx *ctx, ir_live_interval *ival, ir_li
IR_LOG_LSRA_CONFLICT(" ---- Conflict with active", other, overlap);
split_pos = ir_last_use_pos_before(other, ival->range.start, IR_USE_MUST_BE_IN_REG | IR_USE_SHOULD_BE_IN_REG);
- if (split_pos == 0) {
- split_pos = ival->range.start;
- }
- split_pos = ir_find_optimal_split_position(ctx, other, split_pos, ival->range.start, 1);
- if (split_pos > other->range.start) {
- child = ir_split_interval_at(ctx, other, split_pos);
- if (prev) {
- prev->list_next = other->list_next;
+ if (split_pos) {
+ split_pos = ir_find_optimal_split_position(ctx, other, split_pos, ival->range.start, 1);
+ if (split_pos > other->range.start) {
+ child = ir_split_interval_at(ctx, other, split_pos);
+ if (prev) {
+ prev->list_next = other->list_next;
+ } else {
+ *active = other->list_next;
+ }
+ IR_LOG_LSRA(" ---- Finish", other, "");
} else {
- *active = other->list_next;
+ goto try_next_available_register;
}
- IR_LOG_LSRA(" ---- Finish", other, "");
} else {
child = other;
- other->reg = IR_REG_NONE;
- if (prev) {
- prev->list_next = other->list_next;
- } else {
- *active = other->list_next;
- }
- IR_LOG_LSRA(" ---- Spill and Finish", other, " (it must not be in reg)");
}
split_pos = ir_first_use_pos_after(child, ival->range.start, IR_USE_MUST_BE_IN_REG | IR_USE_SHOULD_BE_IN_REG) - 1; // TODO: ???
if (split_pos > child->range.start && split_pos < child->end) {
+ if (child == other) {
+ other->reg = IR_REG_NONE;
+ if (prev) {
+ prev->list_next = other->list_next;
+ } else {
+ *active = other->list_next;
+ }
+ }
ir_live_pos opt_split_pos = ir_find_optimal_split_position(ctx, child, ival->range.start, split_pos, 1);
if (opt_split_pos > child->range.start) {
split_pos = opt_split_pos;
@@ -3259,6 +3262,8 @@ static ir_reg ir_allocate_blocked_reg(ir_ctx *ctx, ir_live_interval *ival, ir_li
// TODO: this may cause endless loop
ir_add_to_unhandled(unhandled, child);
IR_LOG_LSRA(" ---- Queue", child, "");
+ } else {
+ goto try_next_available_register;
}
}
break;
diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c
index 6478ec69756..921790fd92b 100644
--- a/ext/opcache/jit/ir/ir_sccp.c
+++ b/ext/opcache/jit/ir/ir_sccp.c
@@ -603,7 +603,7 @@ static IR_NEVER_INLINE void ir_sccp_analyze(const ir_ctx *ctx, ir_sccp_val *_val
bool may_benefit = 0;
bool has_top = 0;
- if (_values[i].op != IR_TOP) {
+ if (_values[i].op != IR_TOP || insn->op == IR_COPY) {
may_benefit = 1;
}
@@ -987,6 +987,7 @@ static void ir_sccp_remove_if(ir_ctx *ctx, const ir_sccp_val *_values, ir_ref re
insn->optx = IR_OPTX(IR_END, IR_VOID, 1);
next_insn = &ctx->ir_base[dst];
next_insn->op = IR_BEGIN;
+ next_insn->op2 = IR_UNUSED;
}
}
@@ -2726,7 +2727,16 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re
}
return 1;
- } else if (cond->op != IR_OVERFLOW && insn->op2 <= cond_ref && insn->op3 <= cond_ref) {
+ } else if (insn->op2 <= cond_ref && insn->op3 <= cond_ref
+ && cond->op != IR_OVERFLOW
+ // TODO: temporary disable IF-conversion for RLOAD.
+ // We don't track anti-dependencies in GCM and Local Scheduling.
+ // As result COND may be scheduled below the following RSTORE.
+ // See: https://github.com/dstogov/ir/issues/132
+ && cond->op != IR_RLOAD
+ && !((cond->op >= IR_EQ && cond->op <= IR_UNORDERED)
+ && ((!IR_IS_CONST_REF(cond->op1) && ctx->ir_base[cond->op1].op == IR_RLOAD)
+ || (!IR_IS_CONST_REF(cond->op2) && ctx->ir_base[cond->op2].op == IR_RLOAD)))) {
/* COND
*
* prev prev
@@ -2968,9 +2978,11 @@ static bool ir_try_split_if(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue
if_false->optx = IR_OPTX(IR_BEGIN, IR_VOID, 1);
if_false->op1 = end1_ref;
+ if_false->op2 = IR_UNUSED;
if_true->optx = IR_OPTX(IR_BEGIN, IR_VOID, 1);
if_true->op1 = end2_ref;
+ if_true->op2 = IR_UNUSED;
ir_bitqueue_add(worklist, if_false_ref);
ir_bitqueue_add(worklist, if_true_ref);
@@ -3008,6 +3020,7 @@ static bool ir_try_split_if(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue
if_true->optx = IR_BEGIN;
if_true->op1 = IR_UNUSED;
+ if_true->op2 = IR_UNUSED;
ctx->flags2 &= ~IR_CFG_REACHABLE;
@@ -3157,9 +3170,11 @@ static bool ir_try_split_if_cmp(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqu
if_false->optx = IR_OPTX(IR_BEGIN, IR_VOID, 1);
if_false->op1 = end1_ref;
+ if_false->op2 = IR_UNUSED;
if_true->optx = IR_OPTX(IR_BEGIN, IR_VOID, 1);
if_true->op1 = end2_ref;
+ if_true->op2 = IR_UNUSED;
ir_bitqueue_add(worklist, if_false_ref);
ir_bitqueue_add(worklist, if_true_ref);
@@ -3201,6 +3216,7 @@ static bool ir_try_split_if_cmp(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqu
if_true->optx = IR_BEGIN;
if_true->op1 = IR_UNUSED;
+ if_true->op2 = IR_UNUSED;
ctx->flags2 &= ~IR_CFG_REACHABLE;
@@ -3487,7 +3503,9 @@ static void ir_iter_optimize_if(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqu
if_true = &ctx->ir_base[if_true_ref];
if_false = &ctx->ir_base[if_false_ref];
if_true->op = IR_BEGIN;
+ if_true->op2 = IR_UNUSED;
if_false->op = IR_BEGIN;
+ if_false->op2 = IR_UNUSED;
if (ir_ref_is_true(ctx, condition)) {
if_false->op1 = IR_UNUSED;
ir_use_list_remove_one(ctx, ref, if_false_ref);
@@ -3750,6 +3768,47 @@ void ir_iter_opt(ir_ctx *ctx, ir_bitqueue *worklist)
}
}
+void ir_iter_cleanup(ir_ctx *ctx)
+{
+ ir_bitqueue iter_worklist;
+ ir_bitqueue cfg_worklist;
+ ir_ref i, n;
+ ir_insn *insn;
+
+ ir_bitqueue_init(&cfg_worklist, ctx->insns_count);
+ ir_bitqueue_init(&iter_worklist, ctx->insns_count);
+
+ /* Remove unused nodes */
+ for (i = IR_UNUSED + 1, insn = ctx->ir_base + i; i < ctx->insns_count;) {
+ if (IR_IS_FOLDABLE_OP(insn->op)) {
+ if (insn->op != IR_NOP && ctx->use_lists[i].count == 0) {
+ ir_iter_remove_insn(ctx, i, &iter_worklist);
+ }
+ } else if (insn->op == IR_IF || insn->op == IR_MERGE) {
+ ir_bitqueue_add(&cfg_worklist, i);
+ }
+ n = insn->inputs_count;
+ n = ir_insn_inputs_to_len(n);
+ i += n;
+ insn += n;
+ }
+
+ while ((i = ir_bitqueue_pop(&iter_worklist)) >= 0) {
+ insn = &ctx->ir_base[i];
+ if (IR_IS_FOLDABLE_OP(insn->op)) {
+ if (ctx->use_lists[i].count == 0) {
+ ir_iter_remove_insn(ctx, i, &iter_worklist);
+ }
+ }
+ }
+
+ /* Cleanup Control Flow */
+ ir_iter_opt(ctx, &cfg_worklist);
+
+ ir_bitqueue_free(&iter_worklist);
+ ir_bitqueue_free(&cfg_worklist);
+}
+
int ir_sccp(ir_ctx *ctx)
{
ir_bitqueue sccp_worklist, iter_worklist;
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc
index 049c341cc8f..9cd41c37ffe 100644
--- a/ext/opcache/jit/ir/ir_x86.dasc
+++ b/ext/opcache/jit/ir/ir_x86.dasc
@@ -1666,7 +1666,7 @@ get_arg_hints:
break;
case IR_PARAM:
constraints->def_reg = ir_get_param_reg(ctx, ref);
- flags = 0;
+ flags = (constraints->def_reg != IR_REG_NONE) ? IR_USE_SHOULD_BE_IN_REG : 0;
break;
case IR_PI:
case IR_PHI: