Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ PHP NEWS
- Opcache:
. Fixed range inference of assignments through typed references.
(Ilia Alshanetsky)
. Fixed bug GH-17770 (JIT-enabled scripts were not stored in the file cache).
(gregharmon, splitice)
. Fixed bug GH-23693 (Tracing JIT produces wrong results for a guard on a
loop-invariant addition). (Ilia Alshanetsky)
. Fixed OSS-Fuzz #545352966 (default value AST of an SHM-persisted partial).
Expand Down
41 changes: 41 additions & 0 deletions ext/opcache/jit/zend_jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -3323,6 +3323,47 @@ static int zend_jit_setup_hot_counters(zend_op_array *op_array)
return SUCCESS;
}

void zend_jit_restore_original_handler(const zend_op_array *op_array, uint32_t index, zend_op *opline)
{
zend_func_info *func_info = ZEND_FUNC_INFO(op_array);

if (func_info) {
if (func_info->flags & ZEND_FUNC_JIT_ON_HOT_TRACE) {
zend_jit_op_array_trace_extension *ext = (zend_jit_op_array_trace_extension*)func_info;
opline->handler = ext->trace_info[index].orig_handler;
return;
}
if (func_info->flags & ZEND_FUNC_JIT_ON_HOT_COUNTERS) {
zend_jit_op_array_hot_extension *ext = (zend_jit_op_array_hot_extension*)func_info;
opline->handler = ext->orig_handlers[index];
return;
}
if (func_info->flags & (ZEND_FUNC_JIT_ON_FIRST_EXEC | ZEND_FUNC_JIT_ON_PROF_REQUEST)) {
zend_jit_op_array_extension *ext = (zend_jit_op_array_extension*)func_info;
uint32_t entry = 0;

if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
while (entry < op_array->last
&& (op_array->opcodes[entry].opcode == ZEND_RECV
|| op_array->opcodes[entry].opcode == ZEND_RECV_INIT)) {
entry++;
}
}
if (index == entry) {
opline->handler = ext->orig_handler;
return;
}
}
}

/* Script-load JIT does not retain a function extension. Only its generated
* handlers need to be rebuilt; other VM handlers may be specialized. */
if (dasm_buf && (uintptr_t)op_array->opcodes[index].handler >= (uintptr_t)dasm_buf
&& (uintptr_t)op_array->opcodes[index].handler < (uintptr_t)dasm_end) {
ZEND_VM_SET_OPCODE_HANDLER(opline);
}
}

#include "jit/zend_jit_trace.c"

int zend_jit_op_array(zend_op_array *op_array, zend_script *script)
Expand Down
1 change: 1 addition & 0 deletions ext/opcache/jit/zend_jit.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ extern zend_jit_globals jit_globals;

int zend_jit_op_array(zend_op_array *op_array, zend_script *script);
int zend_jit_script(zend_script *script);
void zend_jit_restore_original_handler(const zend_op_array *op_array, uint32_t index, zend_op *opline);
void zend_jit_unprotect(void);
void zend_jit_protect(void);
void zend_jit_init(void);
Expand Down
130 changes: 130 additions & 0 deletions ext/opcache/tests/jit/gh17770_file_cache.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
--TEST--
GH-17770: JIT scripts survive a file cache round trip
--EXTENSIONS--
opcache
--INI--
opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=16M
--SKIPIF--
<?php
if (!function_exists('proc_open')) die('skip proc_open is unavailable');
$extension = ini_get('extension_dir') . DIRECTORY_SEPARATOR
. (PHP_OS_FAMILY === 'Windows' ? 'php_opcache.dll' : 'opcache.so');
$cmd = [PHP_BINARY, '-n'];
if (file_exists($extension)) {
array_push($cmd, '-d', 'zend_extension="' . $extension . '"');
}
array_push($cmd, '-d', 'opcache.enable_cli=1', '-d', 'opcache.jit_buffer_size=16M',
'-d', 'opcache.jit=tracing', '-r', 'echo (int) (opcache_get_status(false)["jit"]["on"] ?? false);');
$process = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
if (proc_close($process) !== 0 || $output !== '1') die('skip JIT is unavailable');
?>
--FILE--
<?php
$dir = __DIR__ . '/gh17770_file_cache';
$script = $dir . '/script.php';
@mkdir($dir);

$source = <<<'PHP'
<?php
function gh17770_value(int $value): int {
return $value + 2;
}
function gh17770_array($values): int {
$values[0] = 42;
return $values[0];
}
class Gh17770Box {
public function value(): int {
return gh17770_value(40);
}
}
$box = new Gh17770Box;
for ($i = 0; $i < 1000; $i++) {
$box->value();
}
echo $box->value(), "\n";
echo gh17770_array([]), "\n";
PHP;

function runScript(string $script, string $cache, string $jit, bool $cacheOnly): void {
$extension = ini_get('extension_dir') . DIRECTORY_SEPARATOR
. (PHP_OS_FAMILY === 'Windows' ? 'php_opcache.dll' : 'opcache.so');
$cmd = [PHP_BINARY, '-n'];
if (file_exists($extension)) {
array_push($cmd, '-d', 'zend_extension="' . $extension . '"');
}
array_push($cmd,
'-d', 'opcache.enable_cli=1',
'-d', 'opcache.jit_buffer_size=16M',
'-d', 'opcache.jit=' . $jit,
'-d', 'opcache.jit_hot_func=1',
'-d', 'opcache.file_cache="' . $cache . '"',
'-d', 'opcache.file_cache_only=' . (int) $cacheOnly,
'-d', 'opcache.validate_timestamps=0',
'-d', 'opcache.file_update_protection=0',
'-r', '$before = opcache_get_status(false)["jit"]["buffer_free"] ?? 0; '
. 'require $argv[1]; '
. '$after = opcache_get_status(false)["jit"]["buffer_free"] ?? 0; '
. 'echo (int) ($before > $after), "\\n";',
$script,
);
$process = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
$output = stream_get_contents($pipes[1]);
$errors = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$status = proc_close($process);
if ($status !== 0 || $output !== ($cacheOnly ? "42\n42\n0\n" : "42\n42\n1\n") || $errors !== '') {
throw new RuntimeException("JIT $jit, file_cache_only=" . (int) $cacheOnly
. ": exit=$status, output=" . var_export($output, true) . ", errors=$errors");
}
}

foreach (['1205', '1215', '1235', '1255'] as $jit) {
$cache = $dir . '/' . $jit;
@mkdir($cache);
file_put_contents($script, $source);
runScript($script, $cache, $jit, false);

$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache));
$found = false;
foreach ($files as $file) {
if ($file->isFile() && $file->getExtension() === 'bin') {
$found = true;
break;
}
}
if (!$found) {
throw new RuntimeException("JIT $jit did not write a file cache entry");
}

file_put_contents($script, '<?php echo "stale source\\n";');
runScript($script, $cache, $jit, true);
runScript($script, $cache, $jit, false);
echo "$jit OK\n";
}
?>
--CLEAN--
<?php
$dir = __DIR__ . '/gh17770_file_cache';
if (is_dir($dir)) {
$entries = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST,
);
foreach ($entries as $entry) {
$entry->isDir() ? rmdir($entry->getPathname()) : unlink($entry->getPathname());
}
rmdir($dir);
}
?>
--EXPECT--
1205 OK
1215 OK
1235 OK
1255 OK
33 changes: 26 additions & 7 deletions ext/opcache/zend_file_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "zend_accelerator_hash.h"

#ifdef HAVE_JIT
#include "Optimizer/zend_func_info.h"
#include "jit/zend_jit.h"
#endif

Expand Down Expand Up @@ -500,6 +501,10 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
zend_file_cache_metainfo *info,
void *buf)
{
#ifdef HAVE_JIT
/* Function info and JIT extensions refer to shared memory and generated code. */
ZEND_SET_FUNC_INFO(op_array, NULL);
#endif
ZEND_MAP_PTR_INIT(op_array->static_variables_ptr, NULL);
ZEND_MAP_PTR_INIT(op_array->run_time_cache, NULL);

Expand Down Expand Up @@ -555,6 +560,11 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra

{
zend_op *opline, *end;
#ifdef HAVE_JIT
zend_op *start_opline;
const zend_op_array *original =
(const zend_op_array*)((char*)script->mem + ((char*)op_array - (char*)buf));
#endif

#if !ZEND_USE_ABS_CONST_ADDR
zval *literals = op_array->literals;
Expand All @@ -565,7 +575,13 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
opline = op_array->opcodes;
UNSERIALIZE_PTR(opline);
end = opline + op_array->last;
#ifdef HAVE_JIT
start_opline = opline;
#endif
while (opline < end) {
#ifdef HAVE_JIT
zend_jit_restore_original_handler(original, (uint32_t)(opline - start_opline), opline);
#endif
if (opline->opcode == ZEND_OP_DATA
&& (opline-1)->opcode == ZEND_DECLARE_ATTRIBUTED_CONST
) {
Expand Down Expand Up @@ -1163,13 +1179,6 @@ int zend_file_cache_script_store(zend_persistent_script *script, bool in_shm)
zend_file_cache_metainfo info;
void *mem, *buf;

#ifdef HAVE_JIT
/* FIXME: dump jited codes out to file cache? */
if (JIT_G(on)) {
return FAILURE;
}
#endif

if (ZCG(accel_directives).file_cache_read_only) {
return FAILURE;
}
Expand Down Expand Up @@ -2123,6 +2132,16 @@ zend_persistent_script *zend_file_cache_script_load_ex(zend_file_handle *file_ha
script->corrupted = false;

if (cache_it) {
#ifdef HAVE_JIT
if (JIT_G(on)) {
/* The file cache contains bytecode only. Rebuild JIT in shared memory. */
zend_shared_alloc_init_xlat_table();
zend_jit_unprotect();
zend_jit_script(&script->script);
zend_jit_protect();
zend_shared_alloc_destroy_xlat_table();
}
#endif
ZCSG(map_ptr_last) = CG(map_ptr_last);
script->dynamic_members.last_used = ZCG(request_time);

Expand Down
Loading