The first version of JIT for PHP was released in PHP-8.0 (https://wiki.php.net/rfc/jit). That time we took a quite simple approach. We are generating native code directly from Zend VM byte code. This approach allowed to deliver JIT in a quite short time, but it also created significant limitations in possible optimizations. Smart optimizations require a more formal and detailed Intermediate Representation (IR). The introduction of the AArch64 JIT back-end in PHP-8.1, made the maintenance of JIT a bit more complex, because now we often have to update assembler code in two back-ends.
A smarter JIT compiler with some Intermediate Representation was planned a long time. The real work was started in January 2022. This proposal is the result of these 1.75 years of work.
I propose a new JIT implementation that is based on a separately developed IR Framework.
Instead of separate back-ends that generated x86 and AArch64 code, now we have a single back-end that constructs IR.
This IR is passed to the framework that performs: machine independent optimizations, register allocation, scheduling, and native code generation. The new back-end doesn't have to care about many old low-level details, e.g.: register assignment, different calling conventions, etc.
On the other hand, sometimes the construction of IR may be more complex because all the dependencies must be consistently reflected. This helps to find many errors during formal IR verification.
The new JIT implementation will affect only a few core PHP developers who are involved in the JIT development or maintenance.
JIT is a self-contained subsystem, and the changes in the implementation details (even big changes) don't affect others.
The proposed patch makes few changes outside ext/opcache/jit. It also very carefully adds new IR related code into zend_jit.c and zend_jit_trace.c, keeping the ability to work with old and new back-ends.
At the current state the PR doesn't remove the sources of the old JIT implementation. It is possible to switch to the old implementation by configuring PHP with --disable-opcache-jit-ir.
JIT is a quite complex subsystem. Its first implementation will likely have bugs, but I am confident that these can be addressed. We have more time to fix potential issues, the sooner this proposal passes, and the patch is merged.
The main goal of a separate IR Framework development is the collaboration with other compiler experts (sharing expertise, contribution, etc.). I'm doing my best to make the IR project interesting outside the PHP world and involve others. In case of success, this will reduce the PHP “bus-factor”.
The necessary part of the IR Framework is embedded into the PHP source tree, and won't introduce any new external dependencies.
The details of the IR framework are complex. This presentation explains design ideas and makes overview of the most important implementaion details.
There are no BC breaks in regard to user land PHP code.
next (master branch)
The current new PHP JIT implements exactly the same set of PHP related features and optimizations as the old one.
Because the IR Framework provides more optimizations and has a smarter Register Allocator, the JIT produces a bit faster (5-10%) and smaller code.
This is visible with bench.php and micro_bench.php. The speed of the real-life applications is not affected.
The compilation speed of the Tracing JIT is almost the same. The compilation speed of the Function JIT is up to 4 times slower (tested on Wordpress).
This is quite a good result for an Optimizing JIT compiler. In my tests, PHP with the new JIT may produce ~15MB of native code per second.
On JIT supported platforms (x86, x86_64, AArch64), the HYBRID VM interpreter starts to save and restore all the persistent CPU registers at execute_ex() prologue and epilogue.
This allows elimination of the similar save/restore code in each JIT-ed function or trace. CLANG and MSVC builds that use CALL VM are not impacted.
JIT is implemented as a part of Opcache, so the PR changes the build process for Opcache. No other changes are present.
The PR does not introduce new php.ini detectives or changes any default values.
It adds few new IR related JIT debugging capabilities that may be enabled through opcache.jit_debug directive bits:
SAPIs are not affected.
PHP extensions are not affected.
There are no new constants.
Should we keep the old implementation of the JIT for a while, or change the patch to replace it straight away?
JIT is developed as a self-contained feature, and doesn't affect anything else directly.
The usage of IR opens the door for more powerful optimizations. Some of these are going to be PHP independent (e.g.): the planned introduction of new loop optimization passes, improvement in LOAD/STORE motion and redundancy, improvements to the code generator.
The other part is PHP specific (for example, we can extend CPU register usage for more instructions).
It is also possible to support new JIT targets (e.g. RISCV), almost independently of PHP.
Finally, we may try to completely avoid the manual IR construction in JIT. We may introduce a single formal specification for VM instructions in a C-like language, convert it to IR and then use partial evaluation to generate VM and JIT handlers (similar to Truffle).
Voting opened 2023-10-06 and closes 2023-10-19.
Secondary vote: Should we keep the old JIT implementation for a while or remove it right after merge?
Note, that old implementation is not going to be tested (by CI) and we can't guarantee its work anyway.