From e792511179199a6ec12ddabad5e7bd7e7d785271 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 24 Feb 2026 18:40:53 +0300 Subject: [PATCH 1/4] Update IR (#21288) IR commit: ef9341183cdd0489a188a87e74f5b02a359df21b --- ext/opcache/jit/ir/ir.c | 10 +- ext/opcache/jit/ir/ir_cfg.c | 320 +++++++++++++++++++-------------- ext/opcache/jit/ir/ir_emit.c | 4 + ext/opcache/jit/ir/ir_gcm.c | 21 ++- ext/opcache/jit/ir/ir_gdb.c | 4 + ext/opcache/jit/ir/ir_perf.c | 4 + ext/opcache/jit/ir/ir_sccp.c | 28 +-- ext/opcache/jit/ir/ir_x86.dasc | 2 +- 8 files changed, 232 insertions(+), 161 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index 3476b9bb0615a..f6058c5abe521 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -1420,13 +1420,21 @@ bool ir_use_list_add(ir_ctx *ctx, ir_ref to, ir_ref ref) if (old_size < new_size) { /* Reallocate the whole edges buffer (this is inefficient) */ ctx->use_edges = ir_mem_realloc(ctx->use_edges, new_size); + if (n == ctx->use_edges_count) { + ctx->use_edges[n] = ref; + use_list->count++; + ctx->use_edges_count++; + return 1; + } } else if (n == ctx->use_edges_count) { ctx->use_edges[n] = ref; use_list->count++; ctx->use_edges_count++; return 0; } - memcpy(ctx->use_edges + ctx->use_edges_count, ctx->use_edges + use_list->refs, use_list->count * sizeof(ir_ref)); + if (use_list->count) { + memcpy(ctx->use_edges + ctx->use_edges_count, ctx->use_edges + use_list->refs, use_list->count * sizeof(ir_ref)); + } use_list->refs = ctx->use_edges_count; ctx->use_edges[use_list->refs + use_list->count] = ref; use_list->count++; diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c index bd314dcedb1d7..357b985e63c8d 100644 --- a/ext/opcache/jit/ir/ir_cfg.c +++ b/ext/opcache/jit/ir/ir_cfg.c @@ -5,6 +5,10 @@ * Authors: Dmitry Stogov */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + #include "ir.h" #include "ir_private.h" @@ -188,9 +192,9 @@ static uint32_t IR_NEVER_INLINE ir_cfg_remove_dead_inputs(ir_ctx *ctx, uint32_t } } } - if (input > 0) { - ir_use_list_remove_one(ctx, input, bb->start); - } + IR_ASSERT(input > 0); + IR_ASSERT(ctx->use_lists[input].count == 1 && ctx->use_edges[ctx->use_lists[input].refs] == bb->start); + CLEAR_USES(input); } } j--; @@ -503,7 +507,8 @@ static void ir_remove_merge_input(ir_ctx *ctx, ir_ref merge, ir_ref from) } ir_mem_free(life_inputs); - ir_use_list_remove_all(ctx, from, merge); + IR_ASSERT(ctx->use_lists[from].count == 1 && ctx->use_edges[ctx->use_lists[from].refs] == merge); + CLEAR_USES(from); } /* CFG constructed after SCCP pass doesn't have unreachable BBs, otherwise they should be removed */ @@ -975,10 +980,107 @@ static bool ir_dominates(const ir_block *blocks, uint32_t b1, uint32_t b2) return b1 == b2; } +/* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ Graphs" and + * G. Ramalingam "Identifying Loops In Almost Linear Time". */ + +#define ENTRY_TIME(b) times[(b) * 2] +#define EXIT_TIME(b) times[(b) * 2 + 1] + +static IR_NEVER_INLINE void ir_collect_irreducible_loops(ir_ctx *ctx, uint32_t *times, ir_worklist *work, ir_list *list) +{ + ir_block *blocks = ctx->cfg_blocks; + uint32_t *edges = ctx->cfg_edges; + + IR_ASSERT(ir_list_len(list) != 0); + if (ir_list_len(list) > 1) { + /* Sort list to process irreducible loops in DFS order (insertion sort) */ + ir_ref *a = list->a.refs; + uint32_t n = ir_list_len(list); + uint32_t i = 1; + while (i < n) { + uint32_t j = i; + while (j > 0 && ENTRY_TIME(a[j-1]) > ENTRY_TIME(a[j])) { + ir_ref tmp = a[j]; + a[j] = a[j-1]; + a[j-1] = tmp; + j--; + } + i++; + } + } + while (ir_list_len(list)) { + uint32_t hdr = ir_list_pop(list); + ir_block *bb = &blocks[hdr]; + + IR_ASSERT(bb->flags & IR_BB_IRREDUCIBLE_LOOP); + IR_ASSERT(!bb->loop_depth); + if (!bb->loop_depth) { + /* process irreducible loop */ + + bb->flags |= IR_BB_LOOP_HEADER; + bb->loop_depth = 1; + if (ctx->ir_base[bb->start].op == IR_MERGE) { + ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; + } + + /* find the closing edge(s) of the irreucible loop */ + IR_ASSERT(bb->predecessors_count > 1); + IR_ASSERT(ir_worklist_len(work) == 0); + ir_bitset_clear(work->visited, ir_bitset_len(ir_worklist_capasity(work))); + ir_bitset_incl(work->visited, hdr); + + uint32_t *p = &edges[bb->predecessors]; + uint32_t n = bb->predecessors_count; + do { + uint32_t pred = *p; + if (ENTRY_TIME(pred) > ENTRY_TIME(hdr) && EXIT_TIME(pred) < EXIT_TIME(hdr)) { + IR_ASSERT(blocks[pred].loop_header == 0); + // blocks[pred].loop_header = 0; /* support for merged loops */ + ir_worklist_push(work, pred); + } + p++; + } while (--n); + + /* collect members of the irreducible loop */ + while (ir_worklist_len(work)) { + uint32_t b = ir_worklist_pop(work); + + bb = &blocks[b]; + bb->loop_header = hdr; + + uint32_t *p = &edges[bb->predecessors]; + uint32_t n = bb->predecessors_count; + + for (; n > 0; p++, n--) { + uint32_t pred = *p; + if (!ir_bitset_in(work->visited, pred)) { + if (blocks[pred].loop_header) { + if (blocks[pred].loop_header == b) continue; + do { + pred = blocks[pred].loop_header; + } while (blocks[pred].loop_header > 0); + } + if (ENTRY_TIME(pred) > ENTRY_TIME(hdr) && EXIT_TIME(pred) < EXIT_TIME(hdr)) { + /* "pred" is a descendant of "hdr" */ + ir_worklist_push(work, pred); + } else if (bb->predecessors_count > 1) { + /* another entry to the irreducible loop */ + bb->flags |= IR_BB_IRREDUCIBLE_LOOP; + if (ctx->ir_base[bb->start].op == IR_MERGE) { + ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; + } + } + } + } + } + } + } +} + int ir_find_loops(ir_ctx *ctx) { - uint32_t b, j, n, count; - uint32_t *entry_times, *exit_times, *sorted_blocks, time = 1; + uint32_t b, j, n; + uint32_t *times, *sorted_blocks, time = 1; ir_block *blocks = ctx->cfg_blocks; uint32_t *edges = ctx->cfg_edges; ir_worklist work; @@ -987,52 +1089,43 @@ int ir_find_loops(ir_ctx *ctx) return 1; } - /* We don't materialize the DJ spanning tree explicitly, as we are only interested in ancestor - * queries. These are implemented by checking entry/exit times of the DFS search. */ + /* Compute entry/exit times for the CFG DFS spanning tree to perform ancestor and back-edge queries. */ ir_worklist_init(&work, ctx->cfg_blocks_count + 1); - entry_times = ir_mem_malloc((ctx->cfg_blocks_count + 1) * 3 * sizeof(uint32_t)); - exit_times = entry_times + ctx->cfg_blocks_count + 1; - sorted_blocks = exit_times + ctx->cfg_blocks_count + 1; - - memset(entry_times, 0, (ctx->cfg_blocks_count + 1) * sizeof(uint32_t)); + times = ir_mem_malloc((ctx->cfg_blocks_count + 1) * 3 * sizeof(uint32_t)); + sorted_blocks = times + (ctx->cfg_blocks_count + 1) * 2; ir_worklist_push(&work, 1); + ENTRY_TIME(1) = time++; + while (ir_worklist_len(&work)) { ir_block *bb; - int child; -next: b = ir_worklist_peek(&work); - if (!entry_times[b]) { - entry_times[b] = time++; - } - /* Visit blocks immediately dominated by "b". */ + /* Visit successors of "b". */ +next: bb = &blocks[b]; - for (child = bb->dom_child; child > 0; child = blocks[child].dom_next_child) { - if (ir_worklist_push(&work, child)) { - goto next; - } - } - - /* Visit join edges. */ - if (bb->successors_count) { + n = bb->successors_count; + if (n) { uint32_t *p = edges + bb->successors; - for (j = 0; j < bb->successors_count; j++, p++) { + + for (; n > 0; p++, n--) { uint32_t succ = *p; - if (blocks[succ].idom == b) { - continue; - } else if (ir_worklist_push(&work, succ)) { + if (ir_worklist_push(&work, succ)) { + b = succ; + ENTRY_TIME(b) = time++; goto next; } } } - exit_times[b] = time++; + + EXIT_TIME(b) = time++; ir_worklist_pop(&work); } /* Sort blocks by level, which is the opposite order in which we want to process them */ + /* (Breadth First Search using "sorted_blocks" as a queue) */ sorted_blocks[1] = 1; j = 1; n = 2; @@ -1046,127 +1139,64 @@ int ir_find_loops(ir_ctx *ctx) } } } - count = n; + IR_ASSERT(n == ctx->cfg_blocks_count + 1); - /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ Graphs". */ +#if IR_DEBUG uint32_t prev_dom_depth = blocks[sorted_blocks[n - 1]].dom_depth; - uint32_t prev_irreducible = 0; +#endif + uint32_t irreducible_depth = 0; + ir_list irreducible_list = {0}; + while (n > 1) { b = sorted_blocks[--n]; ir_block *bb = &blocks[b]; IR_ASSERT(bb->dom_depth <= prev_dom_depth); - if (UNEXPECTED(prev_irreducible) && bb->dom_depth != prev_dom_depth) { - /* process delyed irreducible loops */ - do { - b = sorted_blocks[prev_irreducible]; - bb = &blocks[b]; - if ((bb->flags & IR_BB_IRREDUCIBLE_LOOP) && !bb->loop_depth) { - /* process irreducible loop */ - uint32_t hdr = b; - - bb->loop_depth = 1; - if (ctx->ir_base[bb->start].op == IR_MERGE) { - ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; - } - /* find the closing edge(s) of the irreucible loop */ - IR_ASSERT(bb->predecessors_count > 1); - uint32_t *p = &edges[bb->predecessors]; - j = bb->predecessors_count; - do { - uint32_t pred = *p; - - if (entry_times[pred] > entry_times[b] && exit_times[pred] < exit_times[b]) { - if (!ir_worklist_len(&work)) { - ir_bitset_clear(work.visited, ir_bitset_len(ir_worklist_capasity(&work))); - } - blocks[pred].loop_header = 0; /* support for merged loops */ - ir_worklist_push(&work, pred); - } - p++; - } while (--j); - if (ir_worklist_len(&work) == 0) continue; - - /* collect members of the irreducible loop */ - while (ir_worklist_len(&work)) { - b = ir_worklist_pop(&work); - if (b != hdr) { - ir_block *bb = &blocks[b]; - bb->loop_header = hdr; - if (bb->predecessors_count) { - uint32_t *p = &edges[bb->predecessors]; - uint32_t n = bb->predecessors_count; - do { - uint32_t pred = *p; - while (blocks[pred].loop_header > 0) { - pred = blocks[pred].loop_header; - } - if (pred != hdr) { - if (entry_times[pred] > entry_times[hdr] && exit_times[pred] < exit_times[hdr]) { - /* "pred" is a descendant of "hdr" */ - ir_worklist_push(&work, pred); - } else { - /* another entry to the irreducible loop */ - bb->flags |= IR_BB_IRREDUCIBLE_LOOP; - if (ctx->ir_base[bb->start].op == IR_MERGE) { - ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; - } - } - } - p++; - } while (--n); - } - } - } - } - } while (--prev_irreducible != n); - prev_irreducible = 0; - b = sorted_blocks[n]; - bb = &blocks[b]; + if (UNEXPECTED(bb->dom_depth < irreducible_depth)) { + ir_collect_irreducible_loops(ctx, times, &work, &irreducible_list); + irreducible_depth = 0; } if (bb->predecessors_count > 1) { bool irreducible = 0; + uint32_t b_entry_time = ENTRY_TIME(b); + uint32_t b_exit_time = EXIT_TIME(b); uint32_t *p = &edges[bb->predecessors]; - j = bb->predecessors_count; - do { + for (j = bb->predecessors_count; j > 0; p++, j--) { uint32_t pred = *p; - /* A join edge is one for which the predecessor does not - immediately dominate the successor. */ - if (bb->idom != pred) { - /* In a loop back-edge (back-join edge), the successor dominates - the predecessor. */ + /* Check back-edges */ + if (ENTRY_TIME(pred) >= b_entry_time && EXIT_TIME(pred) <= b_exit_time) { if (ir_dominates(blocks, b, pred)) { + /* In a loop back-edge (back-join edge), the successor dominates + the predecessor. */ if (!ir_worklist_len(&work)) { ir_bitset_clear(work.visited, ir_bitset_len(ir_worklist_capasity(&work))); } - blocks[pred].loop_header = 0; /* support for merged loops */ + IR_ASSERT(!blocks[pred].loop_header); + // blocks[pred].loop_header = 0; /* support for merged loops */ ir_worklist_push(&work, pred); } else { - /* Otherwise it's a cross-join edge. See if it's a branch - to an ancestor on the DJ spanning tree. */ - if (entry_times[pred] > entry_times[b] && exit_times[pred] < exit_times[b]) { - irreducible = 1; - break; - } + /* Otherwise it's a back-edge of irreducible loop. */ + irreducible = 1; + break; } } - p++; - } while (--j); + } if (UNEXPECTED(irreducible)) { - bb->flags |= IR_BB_LOOP_HEADER | IR_BB_IRREDUCIBLE_LOOP; + bb->flags |= IR_BB_IRREDUCIBLE_LOOP; ctx->flags2 |= IR_CFG_HAS_LOOPS | IR_IRREDUCIBLE_CFG; - /* Remember the position of the first irreducible loop to process all the irreducible loops - * after the reducible loops with the same dominator tree depth + /* Delay processing of all irreducible loops + * after all reducible loops with the same dominator tree depth */ - if (!prev_irreducible) { - prev_irreducible = n; - prev_dom_depth = bb->dom_depth; + irreducible_depth = bb->dom_depth; + if (!ir_list_capasity(&irreducible_list)) { + ir_list_init(&irreducible_list, 16); } + ir_list_push(&irreducible_list, b); ir_list_clear(&work.l); } else if (ir_worklist_len(&work)) { /* collect members of the reducible loop */ @@ -1178,35 +1208,47 @@ int ir_find_loops(ir_ctx *ctx) if (ctx->ir_base[bb->start].op == IR_MERGE) { ctx->ir_base[bb->start].op = IR_LOOP_BEGIN; } + ir_bitset_incl(work.visited, hdr); while (ir_worklist_len(&work)) { b = ir_worklist_pop(&work); if (b != hdr) { ir_block *bb = &blocks[b]; + + IR_ASSERT(!bb->loop_header); bb->loop_header = hdr; - if (bb->predecessors_count) { - uint32_t *p = &edges[bb->predecessors]; - uint32_t n = bb->predecessors_count; - do { - uint32_t pred = *p; - while (blocks[pred].loop_header > 0) { - pred = blocks[pred].loop_header; - } - if (pred != hdr) { + + uint32_t *p = &edges[bb->predecessors]; + uint32_t n = bb->predecessors_count; + for (; n > 0; p++, n--) { + uint32_t pred = *p; + if (!ir_bitset_in(work.visited, pred)) { + if (blocks[pred].loop_header) { + if (blocks[pred].loop_header == b) continue; + do { + pred = blocks[pred].loop_header; + } while (blocks[pred].loop_header > 0); ir_worklist_push(&work, pred); + } else { + ir_bitset_incl(work.visited, pred); + ir_list_push_unchecked(&work.l, pred); } - p++; - } while (--n); + } } } } } } } - IR_ASSERT(!prev_irreducible); + + IR_ASSERT(!irreducible_depth); + if (ir_list_capasity(&irreducible_list)) { + ir_list_free(&irreducible_list); + } if (ctx->flags2 & IR_CFG_HAS_LOOPS) { - for (n = 1; n < count; n++) { - b = sorted_blocks[n]; + n = ctx->cfg_blocks_count + 1; + for (j = 1; j < n; j++) { + b = sorted_blocks[j]; ir_block *bb = &blocks[b]; if (bb->loop_header > 0) { ir_block *loop = &blocks[bb->loop_header]; @@ -1237,7 +1279,7 @@ int ir_find_loops(ir_ctx *ctx) } } - ir_mem_free(entry_times); + ir_mem_free(times); ir_worklist_free(&work); return 1; diff --git a/ext/opcache/jit/ir/ir_emit.c b/ext/opcache/jit/ir/ir_emit.c index 1cadb099bce40..92b66eb035886 100644 --- a/ext/opcache/jit/ir/ir_emit.c +++ b/ext/opcache/jit/ir/ir_emit.c @@ -5,6 +5,10 @@ * Authors: Dmitry Stogov */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + #include "ir.h" #if defined(IR_TARGET_X86) || defined(IR_TARGET_X64) diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c index c644c188dca6f..7edb012f61724 100644 --- a/ext/opcache/jit/ir/ir_gcm.c +++ b/ext/opcache/jit/ir/ir_gcm.c @@ -842,6 +842,7 @@ static IR_NEVER_INLINE void ir_fix_bb_order(ir_ctx *ctx, ir_ref *_prev, ir_ref * xlat = ir_mem_malloc(count * sizeof(uint32_t)); ir_worklist_init(&worklist, count); ir_worklist_push(&worklist, 1); + /* Schedule blocks bottom-up. Place block only after all its successurs (except back-edges) are placed. */ while (ir_worklist_len(&worklist) != 0) { next: b = ir_worklist_peek(&worklist); @@ -849,7 +850,14 @@ static IR_NEVER_INLINE void ir_fix_bb_order(ir_ctx *ctx, ir_ref *_prev, ir_ref * n = bb->successors_count; if (n == 1) { succ = ctx->cfg_edges[bb->successors]; - if (ir_worklist_push(&worklist, succ)) { + if (ir_bitset_in(worklist.visited, succ)) { + /* already processed */ + } else if ((ctx->cfg_blocks[succ].flags & IR_BB_IRREDUCIBLE_LOOP) + && ((ctx->cfg_blocks[b].flags & IR_BB_LOOP_HEADER) ? + (ctx->cfg_blocks[succ].loop_header != b) : + (ctx->cfg_blocks[succ].loop_header != ctx->cfg_blocks[b].loop_header))) { + /* "side" entry of irreducible loop (ignore) */ + } else if (ir_worklist_push(&worklist, succ)) { goto next; } } else if (n > 1) { @@ -862,12 +870,11 @@ static IR_NEVER_INLINE void ir_fix_bb_order(ir_ctx *ctx, ir_ref *_prev, ir_ref * succ = *q; if (ir_bitset_in(worklist.visited, succ)) { /* already processed */ - } else if ((ctx->cfg_blocks[succ].flags & IR_BB_LOOP_HEADER) - && (succ == b || ctx->cfg_blocks[b].loop_header == succ)) { - /* back-edge of reducible loop */ } else if ((ctx->cfg_blocks[succ].flags & IR_BB_IRREDUCIBLE_LOOP) - && (ctx->cfg_blocks[succ].loop_header == ctx->cfg_blocks[b].loop_header)) { - /* closing edge of irreducible loop */ + && ((ctx->cfg_blocks[b].flags & IR_BB_LOOP_HEADER) ? + (ctx->cfg_blocks[succ].loop_header != b) : + (ctx->cfg_blocks[succ].loop_header != ctx->cfg_blocks[b].loop_header))) { + /* "side" entry of irreducible loop (ignore) */ } else if (!best) { best = succ; best_loop_depth = ctx->cfg_blocks[best].loop_depth; @@ -883,6 +890,8 @@ static IR_NEVER_INLINE void ir_fix_bb_order(ir_ctx *ctx, ir_ref *_prev, ir_ref * goto next; } } + + /* All successors of "b" are placed. Now we can place "b" itself. */ ir_worklist_pop(&worklist); count--; new_blocks[count] = *bb; diff --git a/ext/opcache/jit/ir/ir_gdb.c b/ext/opcache/jit/ir/ir_gdb.c index ecaf880301e65..8b5fba6b1533a 100644 --- a/ext/opcache/jit/ir/ir_gdb.c +++ b/ext/opcache/jit/ir/ir_gdb.c @@ -7,6 +7,10 @@ * Based on Mike Pall's implementation of GDB interface for LuaJIT. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + #include #include #include diff --git a/ext/opcache/jit/ir/ir_perf.c b/ext/opcache/jit/ir/ir_perf.c index dbb689b091f2b..e5a5e59374076 100644 --- a/ext/opcache/jit/ir/ir_perf.c +++ b/ext/opcache/jit/ir/ir_perf.c @@ -14,6 +14,10 @@ * perf report -i perf.data.jitted */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + #include #include #include diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c index bfec32b568fcf..6478ec6975629 100644 --- a/ext/opcache/jit/ir/ir_sccp.c +++ b/ext/opcache/jit/ir/ir_sccp.c @@ -880,7 +880,7 @@ static void ir_sccp_remove_insn(ir_ctx *ctx, const ir_sccp_val *_values, ir_ref *p = IR_UNUSED; /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ if (input > 0 && _values[input].op > IR_COPY) { - ir_use_list_remove_all(ctx, input, ref); + ir_use_list_remove_one(ctx, input, ref); if (ir_is_dead(ctx, input)) { /* schedule DCE */ ir_bitqueue_add(worklist, input); @@ -918,7 +918,7 @@ static void ir_sccp_replace_insn(ir_ctx *ctx, const ir_sccp_val *_values, ir_ref *p = IR_UNUSED; /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ if (input > 0 && _values[input].op > IR_COPY) { - ir_use_list_remove_all(ctx, input, ref); + ir_use_list_remove_one(ctx, input, ref); if (ir_is_dead(ctx, input)) { /* schedule DCE */ ir_bitqueue_add(worklist, input); @@ -1233,7 +1233,7 @@ static void ir_iter_remove_insn(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) ir_ref input = *p; *p = IR_UNUSED; if (input > 0) { - ir_use_list_remove_all(ctx, input, ref); + ir_use_list_remove_one(ctx, input, ref); if (ir_is_dead(ctx, input)) { /* schedule DCE */ ir_bitqueue_add(worklist, input); @@ -1301,7 +1301,7 @@ static void ir_iter_replace_insn(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bit ir_ref input = *p; *p = IR_UNUSED; if (input > 0) { - ir_use_list_remove_all(ctx, input, ref); + ir_use_list_remove_one(ctx, input, ref); if (ir_is_dead(ctx, input)) { /* schedule DCE */ ir_bitqueue_add(worklist, input); @@ -2427,7 +2427,7 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, next->op1 = root->op1; ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref); if (!IR_IS_CONST_REF(root->op2)) { - ir_use_list_remove_all(ctx, root->op2, root_ref); + ir_use_list_remove_one(ctx, root->op2, root_ref); if (ir_is_dead(ctx, root->op2)) { ir_bitqueue_add(worklist, root->op2); } @@ -2485,7 +2485,7 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref); if (!IR_IS_CONST_REF(root->op2)) { - ir_use_list_remove_all(ctx, root->op2, root_ref); + ir_use_list_remove_one(ctx, root->op2, root_ref); if (ir_is_dead(ctx, root->op2)) { ir_bitqueue_add(worklist, root->op2); } @@ -2612,10 +2612,10 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re next->op1 = root->op1; ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref); if (!IR_IS_CONST_REF(insn->op1)) { - ir_use_list_remove_all(ctx, insn->op1, cond_ref); + ir_use_list_remove_one(ctx, insn->op1, cond_ref); } if (!IR_IS_CONST_REF(insn->op2)) { - ir_use_list_remove_all(ctx, insn->op2, cond_ref); + ir_use_list_remove_one(ctx, insn->op2, cond_ref); } if (ctx->use_lists[cond_ref].count == 1) { @@ -2705,7 +2705,7 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref); ir_use_list_remove_one(ctx, insn->op1, neg_ref); if (!IR_IS_CONST_REF(insn->op1)) { - ir_use_list_remove_all(ctx, insn->op1, cond_ref); + ir_use_list_remove_one(ctx, insn->op1, cond_ref); } if (ctx->use_lists[cond_ref].count == 1) { @@ -2771,7 +2771,7 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re next->op1 = root->op1; ir_use_list_replace_one(ctx, cond_ref, root_ref, ref); ir_use_list_replace_one(ctx, root->op1, root_ref, next_ref); - ir_use_list_remove_all(ctx, root->op2, root_ref); + ir_use_list_remove_one(ctx, root->op2, root_ref); MAKE_NOP(root); CLEAR_USES(root_ref); MAKE_NOP(start1); CLEAR_USES(start1_ref); @@ -3035,8 +3035,8 @@ static bool ir_try_split_if(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue * IF_FALSE | MERGE * | | */ - ir_use_list_remove_all(ctx, merge_ref, cond_ref); - ir_use_list_remove_all(ctx, ref, if_true_ref); + ir_use_list_remove_one(ctx, merge_ref, cond_ref); + ir_use_list_remove_one(ctx, ref, if_true_ref); if (!IR_IS_CONST_REF(cond->op3)) { ir_use_list_replace_one(ctx, cond->op3, cond_ref, end2_ref); } @@ -3230,8 +3230,8 @@ static bool ir_try_split_if_cmp(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqu * | | */ - ir_use_list_remove_all(ctx, merge_ref, phi_ref); - ir_use_list_remove_all(ctx, ref, if_true_ref); + ir_use_list_remove_one(ctx, merge_ref, phi_ref); + ir_use_list_remove_one(ctx, ref, if_true_ref); if (!IR_IS_CONST_REF(phi->op3)) { ir_use_list_replace_one(ctx, phi->op3, phi_ref, insn->op2); } diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc index 9b369fadbcc54..049c341cc8fe3 100644 --- a/ext/opcache/jit/ir/ir_x86.dasc +++ b/ext/opcache/jit/ir/ir_x86.dasc @@ -12306,7 +12306,7 @@ next_block:; do { /* _cldemote(p); */ - asm volatile(".byte 0x0f, 0x1c, 0x06" :: "S" (p)); + __asm__ volatile(".byte 0x0f, 0x1c, 0x06" :: "S" (p)); p += 64; } while (p < start + size); } From ee1e5f295d44476de8224e27054de4a20a0a150e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 24 Feb 2026 11:10:09 -0500 Subject: [PATCH 2/4] PHP-8.5 is now for PHP 8.5.5-dev --- NEWS | 5 ++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index efa26ba5e2b3b..692a2ae52b287 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.5.4 +?? ??? ????, PHP 8.5.5 + + +12 Mar 2026, PHP 8.5.4 - Core: . Fixed bug GH-21029 (zend_mm_heap corrupted on Aarch64, LTO builds). (Arnaud) diff --git a/Zend/zend.h b/Zend/zend.h index 49684e113d626..2d214bab196d7 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.5.4-dev" +#define ZEND_VERSION "4.5.5-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index c4ce3ee2248b8..e4a477dae30fd 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.5.4-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.5.5-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index 1c1613d6d3d7f..859fde69b4259 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -4,5 +4,5 @@ #define PHP_MINOR_VERSION 5 #define PHP_RELEASE_VERSION 4 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.5.4-dev" +#define PHP_VERSION "8.5.5-dev" #define PHP_VERSION_ID 80504 From ec5a1e001db8455d89bc22d38d9dfd5bd17c0041 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Mon, 23 Feb 2026 22:25:10 +0100 Subject: [PATCH 3/4] Fix preloaded constant erroneously propagated to file-cached script Since GH-15021 preloaded constants are propagated to compiled scripts. This is problematic for file cache, which assumes all referenced zvals are either persistently allocated or local to the current script. However, preloaded constants live in shm as immutable, but not persistent. To solve this, we'd need to duplicate propagated constants in the optimizer when file cache is used. This is error prone given it needs to happen in many places. It's debatable whether constant propagation is even correct in this case, as running the preloaded script on a restart isn't guaranteed to produce the same result. Hence, avoid the issue for now by just not relying on preloaded symbols when file cache is used. Fixes GH-21052 Closes GH-21281 --- NEWS | 2 ++ Zend/Optimizer/zend_optimizer.c | 6 ++++++ ext/opcache/tests/gh21052.phpt | 32 ++++++++++++++++++++++++++++++++ ext/opcache/tests/gh21052_a.inc | 13 +++++++++++++ ext/opcache/tests/gh21052_b.inc | 5 +++++ 5 files changed, 58 insertions(+) create mode 100644 ext/opcache/tests/gh21052.phpt create mode 100644 ext/opcache/tests/gh21052_a.inc create mode 100644 ext/opcache/tests/gh21052_b.inc diff --git a/NEWS b/NEWS index 28187bd8590ed..b23bd68b83821 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,8 @@ PHP NEWS (Petr Sumbera) . Fixed bug GH-21227 (Borked SCCP of array containing partial object). (ilutov) + . Fixed bug GH-21052 (Preloaded constant erroneously propagated to file-cached + script). (ilutov) - OpenSSL: . Fix a bunch of leaks and error propagation. (ndossche) diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 25f16d252a831..9017572118e28 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -799,6 +799,9 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename) zend_class_entry *ce = Z_PTR_P(ce_zv); if (ce->ce_flags & ZEND_ACC_PRELOADED) { + if (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE) { + return true; + } Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val)); size_t offset = ce_bucket - EG(class_table)->arData; if (offset < EG(persistent_classes_count)) { @@ -817,6 +820,9 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename) return false; } else if (fbc->type == ZEND_USER_FUNCTION) { if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) { + if (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE) { + return true; + } Bucket *fbc_bucket = (Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val)); size_t offset = fbc_bucket - EG(function_table)->arData; if (offset < EG(persistent_functions_count)) { diff --git a/ext/opcache/tests/gh21052.phpt b/ext/opcache/tests/gh21052.phpt new file mode 100644 index 0000000000000..d9d0430b9eef1 --- /dev/null +++ b/ext/opcache/tests/gh21052.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-21052: Preloaded constant erroneously propagated to file-cached script +--CREDITS-- +Grummfy +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_cache="{TMP}" +opcache.preload={PWD}/gh21052_a.inc +--SKIPIF-- + +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + string(3) "foo" +} +array(1) { + [0]=> + string(3) "foo" +} +array(1) { + [0]=> + string(3) "foo" +} diff --git a/ext/opcache/tests/gh21052_a.inc b/ext/opcache/tests/gh21052_a.inc new file mode 100644 index 0000000000000..f9614369798b8 --- /dev/null +++ b/ext/opcache/tests/gh21052_a.inc @@ -0,0 +1,13 @@ + Date: Tue, 24 Feb 2026 11:54:25 -0500 Subject: [PATCH 4/4] Fix missed php_version changes --- main/php_version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/php_version.h b/main/php_version.h index 859fde69b4259..aae2ff3232597 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 5 -#define PHP_RELEASE_VERSION 4 +#define PHP_RELEASE_VERSION 5 #define PHP_EXTRA_VERSION "-dev" #define PHP_VERSION "8.5.5-dev" -#define PHP_VERSION_ID 80504 +#define PHP_VERSION_ID 80505