From 0e4f826ab5d4e28ca44b6b31eac92c56b16eb132 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 27 Sep 2026 23:26:23 +0200 Subject: [PATCH 1/2] JIT: Use interned constants for offsets in jit_ADD_OFFSET() `jit_ADD_OFFSET()` created the offset through `jit_CONST_ADDR()`, which returns a unique (non-interned) constant. IR's own folding of nested offsets, `ADD(ADD(x, c1), c2)`, creates an interned constant for `c1+c2`. The same address can therefore end up with two different offset refs, and CSE and store-to-load forwarding, which both need an identical address ref, miss it. The uniqueing exists likely for the exit addresses mostly, but for offsets this is wrong. For example, the type store and the type load of the same zval end up with different refs: ```php function count_big($n) { $c = 0; for ($i = 0; $i < $n; $i++) { $big = $i > 5; if ($big) { $c++; } } return $c; } ``` Loop body before: ```asm movl %esi, 0x88(%r14) cmpb $3, 0x88(%r14) jne jit$$trace_exit_4 ``` After: ```asm movl %esi, 0x88(%r14) cmpb $3, %sil ; no reload jne jit$$trace_exit_4 ``` In general, more redundant loads can be avoided, and in some cases type guards can be eliminated due to store->load forwarding. --- ext/opcache/jit/zend_jit_ir.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index c7716eb96ff8..808d7e0fc8da 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -591,7 +591,8 @@ static ir_ref jit_CONST_OPCODE_HANDLER_FUNC(zend_jit_ctx *jit, zend_vm_opcode_ha static ir_ref jit_ADD_OFFSET(zend_jit_ctx *jit, ir_ref addr, uintptr_t offset) { if (offset) { - addr = ir_ADD_A(addr, ir_CONST_ADDR(offset)); + /* Use the same constant as IR's folding of nested offsets, to allow CSE and load forwarding */ + addr = ir_ADD_A(addr, ir_const_addr(&jit->ctx, offset)); } return addr; } From d2e9b7ed0232595c8a3e04183e282ebb1eea7115 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Mon, 28 Sep 2026 23:53:15 +0200 Subject: [PATCH 2/2] get rid of php-specific interning table --- ext/opcache/jit/zend_jit_ir.c | 91 ++--------------------------------- 1 file changed, 5 insertions(+), 86 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 808d7e0fc8da..a464de1dbc3b 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -89,8 +89,6 @@ # define IR_OPCODE_HANDLER_RET IR_ADDR #endif -#undef ir_CONST_ADDR -#define ir_CONST_ADDR(_addr) jit_CONST_ADDR(jit, (uintptr_t)(_addr)) #define ir_CONST_FUNC(_addr) jit_CONST_FUNC(jit, (uintptr_t)(_addr), 0) #define ir_CONST_FC_FUNC(_addr) jit_CONST_FUNC(jit, (uintptr_t)(_addr), IR_FASTCALL_FUNC) #define ir_CAST_FC_FUNC(_addr) ir_fold2(_ir_CTX, IR_OPT(IR_PROTO, IR_ADDR), (_addr), \ @@ -101,7 +99,7 @@ ir_proto_0(_ir_CTX, IR_FASTCALL_FUNC, IR_OPCODE_HANDLER_RET)) #define ir_CONST_FUNC_PROTO(_addr, _proto) \ - jit_CONST_FUNC_PROTO(jit, (uintptr_t)(_addr), (_proto)) + ir_const_func_addr(_ir_CTX, (uintptr_t)(_addr), (_proto)) #undef ir_ADD_OFFSET #define ir_ADD_OFFSET(_addr, _offset) \ @@ -316,9 +314,6 @@ typedef struct _zend_jit_ctx { zend_jit_reg_var *ra; int delay_var; ir_refs *delay_refs; - ir_ref eg_exception_addr; - HashTable addr_hash; - ir_ref stub_addr[jit_last_stub]; } zend_jit_ctx; typedef int8_t zend_reg; @@ -531,46 +526,6 @@ static ir_ref jit_TLS(zend_jit_ctx *jit) } #endif -static ir_ref jit_CONST_ADDR(zend_jit_ctx *jit, uintptr_t addr) -{ - ir_ref ref; - zval *zv; - - if (addr == 0) { - return IR_NULL; - } - zv = zend_hash_index_lookup(&jit->addr_hash, addr); - if (Z_TYPE_P(zv) == IS_LONG) { - ref = Z_LVAL_P(zv); - ZEND_ASSERT(jit->ctx.ir_base[ref].opt == IR_OPT(IR_ADDR, IR_ADDR)); - } else { - ref = ir_unique_const_addr(&jit->ctx, addr); - ZVAL_LONG(zv, ref); - } - return ref; -} - -static ir_ref jit_CONST_FUNC_PROTO(zend_jit_ctx *jit, uintptr_t addr, ir_ref proto) -{ - ir_ref ref; - ir_insn *insn; - zval *zv; - - ZEND_ASSERT(addr != 0); - zv = zend_hash_index_lookup(&jit->addr_hash, addr); - if (Z_TYPE_P(zv) == IS_LONG) { - ref = Z_LVAL_P(zv); - ZEND_ASSERT(jit->ctx.ir_base[ref].opt == IR_OPT(IR_FUNC_ADDR, IR_ADDR) && jit->ctx.ir_base[ref].proto == proto); - } else { - ref = ir_unique_const_addr(&jit->ctx, addr); - insn = &jit->ctx.ir_base[ref]; - insn->optx = IR_OPT(IR_FUNC_ADDR, IR_ADDR); - insn->proto = proto; - ZVAL_LONG(zv, ref); - } - return ref; -} - static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags) { #if defined(IR_TARGET_X86) @@ -580,7 +535,7 @@ static ir_ref jit_CONST_FUNC(zend_jit_ctx *jit, uintptr_t addr, uint16_t flags) ir_ref proto = 0; #endif - return jit_CONST_FUNC_PROTO(jit, addr, proto); + return ir_const_func_addr(&jit->ctx, addr, proto); } static ir_ref jit_CONST_OPCODE_HANDLER_FUNC(zend_jit_ctx *jit, zend_vm_opcode_handler_t handler) @@ -591,56 +546,24 @@ static ir_ref jit_CONST_OPCODE_HANDLER_FUNC(zend_jit_ctx *jit, zend_vm_opcode_ha static ir_ref jit_ADD_OFFSET(zend_jit_ctx *jit, ir_ref addr, uintptr_t offset) { if (offset) { - /* Use the same constant as IR's folding of nested offsets, to allow CSE and load forwarding */ - addr = ir_ADD_A(addr, ir_const_addr(&jit->ctx, offset)); + addr = ir_ADD_A(addr, ir_CONST_ADDR(offset)); } return addr; } static ir_ref jit_EG_exception(zend_jit_ctx *jit) { -#ifdef ZTS return jit_EG(exception); -#else - ir_ref ref = jit->eg_exception_addr; - - if (UNEXPECTED(!ref)) { - ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)&EG(exception)); - jit->eg_exception_addr = ref; - } - return ref; -#endif } static ir_ref jit_STUB_ADDR(zend_jit_ctx *jit, jit_stub_id id) { - ir_ref ref = jit->stub_addr[id]; - - if (UNEXPECTED(!ref)) { - ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)zend_jit_stub_handlers[id]); - jit->stub_addr[id] = ref; - } - return ref; + return ir_CONST_ADDR(zend_jit_stub_handlers[id]); } static ir_ref jit_STUB_FUNC_ADDR(zend_jit_ctx *jit, jit_stub_id id, uint16_t flags) { - ir_ref ref = jit->stub_addr[id]; - ir_insn *insn; - - if (UNEXPECTED(!ref)) { - ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)zend_jit_stub_handlers[id]); - insn = &jit->ctx.ir_base[ref]; - insn->optx = IR_OPT(IR_FUNC_ADDR, IR_ADDR); -#if defined(IR_TARGET_X86) - /* TODO: dummy prototype (only flags matter) ??? */ - insn->proto = flags ? ir_proto_0(&jit->ctx, flags, IR_I32) : 0; -#else - insn->proto = 0; -#endif - jit->stub_addr[id] = ref; - } - return ref; + return jit_CONST_FUNC(jit, (uintptr_t)zend_jit_stub_handlers[id], flags); } static void jit_SNAPSHOT(zend_jit_ctx *jit, ir_ref addr) @@ -2842,9 +2765,6 @@ static void zend_jit_init_ctx(zend_jit_ctx *jit, uint32_t flags) jit->ra = NULL; jit->delay_var = -1; jit->delay_refs = NULL; - jit->eg_exception_addr = 0; - zend_hash_init(&jit->addr_hash, 64, NULL, NULL, 0); - memset(jit->stub_addr, 0, sizeof(jit->stub_addr)); ir_START(); } @@ -2854,7 +2774,6 @@ static int zend_jit_free_ctx(zend_jit_ctx *jit) if (jit->name) { zend_string_release(jit->name); } - zend_hash_destroy(&jit->addr_hash); ir_free(&jit->ctx); return 1; }