Новый JIT-движок для PHP 8.4 и PHP 9
Дмитрий Стогов представил информацию о новом JIT-движке, который будет использоваться в следующих версиях PHP
Как вы знаете JIT появился в PHP 8.0 и представлял из себя компиляцию "на лету" - Just In Time, так же вы вероятно знаете, что PHP является интерпретируемым языком, он не компилирует программы в прямом смысле этого значения, как, например это делают C или Rust.
Чтобы включить JIT нужно было указать несколько параметров конфига php.ini.
Новый JIT движок PHP
Дмитрий Стогов, один из мейнтейнеров PHP поделился информацией в internal рассылках PHP о новом JIT-движке, который будет использоваться в следующих мажорных версиях PHP. Теперь это настоящий оптимизирующий компилятор с промежуточным представлением, аналогичный серверному компилятору Java HotSpot.
Он создает основу для будущих улучшений и устраняет многие низкоуровневые детали существующего PHP JIT. Вместо поддержки ассемблерного кода для разных процессоров теперь PHP генерирует один IR и передает его независимому от PHP JIT-движку.
Старая реализация JIT будет сохранена на некоторое время. Все желающие могут просмотреть код https://github.com/php/php-src/pull/12079.
Новая реализация JIT основана на IR-Lightweight JIT Compilation Framework и в отличии от старого JIT-подхода в PHP 8.*, который генерирует собственный код непосредственно из байт-кода PHP, эта реализация генерирует промежуточное представление (IR) и делегирует все задачи более низкого уровня в IR Framework. IR для JIT похож на абстрактное синтаксическое дерево (AST) для компилятора.
По сравнению с классическими оптимизирующими компиляторами (такими как GCC и LLVM), IR Framework использует очень короткий конвейер оптимизации. В сочетании с компактным IR-представлением это делает его чрезвычайно быстрым и позволяет генерировать довольно хороший машинный код за разумное время.
IR-пример
Попробуем сгенерировать код для следующей функции:
int32_t mandelbrot(double x, double y)
{
double cr = y - 0.5;
double ci = x;
double zi = 0.0;
double zr = 0.0;
int i = 0;
while(1) {
i++;
double temp = zr * zi;
double zr2 = zr * zr;
double zi2 = zi * zi;
zr = zr2 - zi2 + cr;
zi = temp + temp + ci;
if (zi2 + zr2 > 16)
return i;
if (i > 1000)
return 0;
}
}
Это можно сделать через API построения IR с помощью следующего кода:
void gen_mandelbrot(ir_ctx *ctx)
{
ir_START();
ir_ref x = ir_PARAM(IR_DOUBLE, "x", 1);
ir_ref y = ir_PARAM(IR_DOUBLE, "y", 2);
ir_ref cr = ir_SUB_D(y, ir_CONST_DOUBLE(0.5));
ir_ref ci = ir_COPY_D(x);
ir_ref zi = ir_COPY_D(ir_CONST_DOUBLE(0.0));
ir_ref zr = ir_COPY_D(ir_CONST_DOUBLE(0.0));
ir_ref i = ir_COPY_D(ir_CONST_I32(0));
ir_ref loop = ir_LOOP_BEGIN(ir_END());
ir_ref zi_1 = ir_PHI_2(zi, IR_UNUSED);
ir_ref zr_1 = ir_PHI_2(zr, IR_UNUSED);
ir_ref i_1 = ir_PHI_2(i, IR_UNUSED);
ir_ref i_2 = ir_ADD_I32(i_1, ir_CONST_I32(1));
ir_ref temp = ir_MUL_D(zr_1, zi_1);
ir_ref zr2 = ir_MUL_D(zr_1, zr_1);
ir_ref zi2 = ir_MUL_D(zi_1, zi_1);
ir_ref zr_2 = ir_ADD_D(ir_SUB_D(zr2, zi2), cr);
ir_ref zi_2 = ir_ADD_D(ir_ADD_D(temp, temp), ci);
ir_ref if_1 = ir_IF(ir_GT(ir_ADD_D(zi2, zr2), ir_CONST_DOUBLE(16.0)));
ir_IF_TRUE(if_1);
ir_RETURN(i_2);
ir_IF_FALSE(if_1);
ir_ref if_2 = ir_IF(ir_GT(i_2, ir_CONST_I32(1000)));
ir_IF_TRUE(if_2);
ir_RETURN(ir_CONST_I32(0));
ir_IF_FALSE(if_2);
ir_ref loop_end = ir_LOOP_END();
/* close loop */
ir_MERGE_SET_OP(loop, 2, loop_end);
ir_PHI_SET_OP(zi_1, 2, zi_2);
ir_PHI_SET_OP(zr_1, 2, zr_2);
ir_PHI_SET_OP(i_1, 2, i_2);
}
Текстовое представление IR после системной независимой оптимизации:
{
uintptr_t c_1 = 0;
bool c_2 = 0;
bool c_3 = 1;
double c_4 = 0.5;
double c_5 = 0;
int32_t c_6 = 0;
int32_t c_7 = 1;
double c_8 = 16;
int32_t c_9 = 1000;
l_1 = START(l_22);
double d_2 = PARAM(l_1, "x", 1);
double d_3 = PARAM(l_1, "y", 2);
double d_4 = SUB(d_3, c_4);
l_5 = END(l_1);
l_6 = LOOP_BEGIN(l_5, l_29);
double d_7 = PHI(l_6, c_5, d_28);
double d_8 = PHI(l_6, c_5, d_26);
int32_t d_9 = PHI(l_6, c_6, d_10);
int32_t d_10 = ADD(d_9, c_7);
double d_11 = MUL(d_8, d_8);
double d_12 = MUL(d_7, d_7);
double d_13 = ADD(d_12, d_11);
bool d_14 = GT(d_13, c_8);
l_15 = IF(l_6, d_14);
l_16 = IF_TRUE(l_15);
l_17 = RETURN(l_16, d_10);
l_18 = IF_FALSE(l_15);
bool d_19 = GT(d_10, c_9);
l_20 = IF(l_18, d_19);
l_21 = IF_TRUE(l_20);
l_22 = RETURN(l_21, c_6, l_17);
l_23 = IF_FALSE(l_20);
double d_24 = MUL(d_7, d_8);
double d_25 = SUB(d_11, d_12);
double d_26 = ADD(d_25, d_4);
double d_27 = ADD(d_24, d_24);
double d_28 = ADD(d_27, d_2);
l_29 = LOOP_END(l_23);
}
Окончательно сгенерированный код:
test:
subsd .L4(%rip), %xmm1
xorpd %xmm3, %xmm3
xorpd %xmm2, %xmm2
xorl %eax, %eax
.L1:
leal 1(%rax), %eax
movapd %xmm2, %xmm4
mulsd %xmm2, %xmm4
movapd %xmm3, %xmm5
mulsd %xmm3, %xmm5
movapd %xmm5, %xmm6
addsd %xmm4, %xmm6
ucomisd .L5(%rip), %xmm6
ja .L2
cmpl $0x3e8, %eax
jg .L3
mulsd %xmm2, %xmm3
subsd %xmm5, %xmm4
movapd %xmm4, %xmm2
addsd %xmm1, %xmm2
addsd %xmm3, %xmm3
addsd %xmm0, %xmm3
jmp .L1
.L2:
retq
.L3:
xorl %eax, %eax
retq
.rodata
.L4:
.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x3f
.L5:
.db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x40
Визуализированный график:
Ключевые преимущества новой реализации JIT
- Использование IR открывает возможности для лучшей оптимизации и распределения регистров (полученный собственный код более эффективен).
- PHP не нужно заботиться о большинстве деталей низкого уровня (различные процессоры, соглашения о вызовах, детали TLS и т. д.).
- Гораздо проще реализовать поддержку новых целей (например, RISCV)
- IR-фреймворк будет разрабатываться отдельно от PHP и может принимать вклад от других проектов (новые оптимизации, улучшения, исправления ошибок).
Недостатки новой реализации JIT
К сожалению JIT-компиляция становится медленнее (это практически незаметно для tracing JIT, но function JIT-компиляция того же Wordpress становится в 4 раза медленнее)
В заключении
Новая реализация JIT успешно проходит все рабочие процессы CI, включая Nightly, но она еще не доработана и может вызывать сбои. Чтобы снизить риски, новый патч не удаляет старую реализацию JIT (то есть такую же, как JIT PHP 8.3). Можно собрать PHP со старым JIT, настроив его с помощью --disable-opcache-jit-ir. В будущем старая реализация будет удалена.
Спасибо Дмитрию за вновь проделанную колоссальную работу.

Кстати, если вы еще не в нашем уютном телеграм канале "Делаем из мухи слона", то можете присоединиться к нам, ведь можно читать те же посты, но в вашем удобном мессенджере
Что думаешь?