diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index a958a2453c4e..e43f04f6be10 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3702,8 +3702,8 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) *dasm_ptr = (void**)*dasm_ptr + sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]); #elif defined(IR_TARGET_AARCH64) zend_jit_stub_handlers = dasm_buf; - *dasm_ptr = (void**)*dasm_ptr + (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 2; - memset(zend_jit_stub_handlers, 0, (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 2 * sizeof(void*)); + *dasm_ptr = (void**)*dasm_ptr + (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 3; + memset(zend_jit_stub_handlers, 0, (sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0])) * 3 * sizeof(void*)); #endif *dasm_ptr = (void*)ZEND_MM_ALIGNED_SIZE_EX(((size_t)(*dasm_ptr)), 16); zend_jit_protect(); @@ -3889,6 +3889,9 @@ void zend_jit_restart(void) /* restore JIT buffer pos */ dasm_ptr[0] = dasm_ptr[1]; +#if defined(IR_TARGET_AARCH64) + zend_jit_restore_stub_handlers(); +#endif zend_jit_trace_restart(); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 50851d75abb5..8e5678fee492 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -449,6 +449,24 @@ static void zend_jit_commit_veneers(void) } } } + +/* zend_jit_commit_veneers() replaces the address of a stub by the address of a veneer emitted + * after it. zend_jit_restart() rewinds the buffer below such veneers, so it restores the + * original addresses, kept after the pending veneers. */ +static void zend_jit_save_stub_handlers(void) +{ + int count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]); + + memcpy(zend_jit_stub_handlers + count * 2, zend_jit_stub_handlers, count * sizeof(void*)); +} + +static void zend_jit_restore_stub_handlers(void) +{ + int count = sizeof(zend_jit_stubs) / sizeof(zend_jit_stubs[0]); + + memcpy(zend_jit_stub_handlers, zend_jit_stub_handlers + count * 2, count * sizeof(void*)); + memset(zend_jit_stub_handlers + count, 0, count * sizeof(void*)); +} #endif static bool zend_jit_prefer_const_addr_load(zend_jit_ctx *jit, uintptr_t addr) @@ -2969,6 +2987,9 @@ static void zend_jit_setup_stubs(void) } zend_jit_free_ctx(&jit); } +#if defined(IR_TARGET_AARCH64) + zend_jit_save_stub_handlers(); +#endif } #define REGISTER_HELPER(n) \ diff --git a/ext/opcache/tests/jit/restart_stub_veneers.phpt b/ext/opcache/tests/jit/restart_stub_veneers.phpt new file mode 100644 index 000000000000..d6b048ef662a --- /dev/null +++ b/ext/opcache/tests/jit/restart_stub_veneers.phpt @@ -0,0 +1,117 @@ +--TEST-- +JIT stub handlers must not point to veneers discarded by an OPcache restart +--DESCRIPTION-- +On AArch64, traces that are out of branch range of a stub get a veneer, and the address of the +stub in zend_jit_stub_handlers[] is replaced by the address of that veneer. Code compiled after a +restart must not branch to veneers in the discarded part of the JIT buffer. Needs more than 1 MB +of JIT code, and different code after the restart than before it. +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +opcache.jit_buffer_size=16M +--CONFLICTS-- +server +--FILE-- + \$v) { if ((\$v + \$k) % " . ($m + 2) . " === 0) { \$s += \$v * $m; } else { \$s ^= \$j; } \$s .= ''; \$s = (int)\$s; }\n" + . " \$str = str_repeat('x', \$k % 7); \$s += strlen(\$str);\n" + . " return \$s;\n }\n"; + } + file_put_contents("$dir/a/C$i.php", $src . "}\n"); +} + +/* Workload B: polymorphic branches, closures and caught exceptions */ +for ($i = 0; $i < 400; $i++) { + $src = "d[\$o]); }\n" + . " public function offsetGet(mixed \$o): mixed { return \$this->d[\$o] ?? null; }\n" + . " public function offsetSet(mixed \$o, mixed \$v): void { \$this->d[\$o] = \$v; }\n" + . " public function offsetUnset(mixed \$o): void { unset(\$this->d[\$o]); }\n" + . " public function run(array \$a): mixed { \$r = 0; foreach (\$a as \$k => \$v) {\n" + . " if (is_int(\$v)) { \$r += \$v * $i; } elseif (is_string(\$v)) { \$r .= substr(\$v, 0, 2); } elseif (is_array(\$v)) { \$r = count(\$v) + (int)\$r; } else { \$r = \$v ?? \$r; }\n" + . " \$this[\$k % 17] = \$r; \$x = fn(\$y) => \$y . \$k; \$r = is_string(\$r) ? strlen(\$x(\$r)) : \$r; }\n" + . " try { if (\$r % " . (($i % 9) + 2) . " === 0) { throw new RuntimeException('x'); } } catch (Throwable \$e) { \$r++; }\n" + . " return \$r; }\n}\n"; + file_put_contents("$dir/b/P$i.php", $src); +} + +file_put_contents("$dir/a.inc", <<<'PHP' +run($mix); +} +PHP); +file_put_contents("$dir/b.php", " +--CLEAN-- + +--EXPECT-- +b ok +b ok +bool(true) +ab ok +ab ok