Compiling PHP 8 from source with JIT support

Initially, this post was to apply to the experiments with JIT alone. However, I encountered some problems at the time of compiling PHP so I decided to share this experience.

UPDATE (2019-04-19)
If you want you can use prepared docker image from my other blog post: How to run PHP 8 with JIT support using Docker.

If you don't know what JIT is and why it will we implemented in PHP 8 please read Joe Watkins PHP GR8 blog post.

In my opinion, the more people will be able to experiment with JIT the better it will be.

Let's start at the very beginning. The process outlined in this post applies to Ubuntu, but the rest of the systems will look the same (with some exceptions: Windows, sorry).

Dependencies

We will need several dependencies to compile PHP from source.

sudo apt-get update
sudo apt-get install git build-essential 
libgccjit-6-dev libzip-dev autoconf re2c bison libxml2-dev -y

It may turn out that this is not enough. There is then a general rule: try to install missing dependency with lib prefix and/or -dev suffix. For example, when you encounter an error like this (in configure step):

configure: error: xml2-config not found. 
Please check your libxml2 installation.

You can run this command to install missing libxml2:

sudo apt-get install libxml2-dev

After installing the dependencies, we will need the source code.

Clone PHP source

cd ~
git clone https://github.com/php/php-src.git
cd php-src

By default, we land on the master branch, so in current case, it will be PHP 8.0.0-dev

Configure

Since ./configure file is missing we must use ./buildconf to generate one:

./buildconf

Next, we want to configure our compilation. You can use --prefix flag to set destination dir.

./configure --prefix=/opt/php/php8 --enable-opcache --with-zlib 
--enable-zip --enable-sockets --without-pear

To see all available options:

./configure --help

When everything goes well you should see:

Generating files
configure: creating ./config.status
creating main/internal_functions.c
creating main/internal_functions_cli.c
+--------------------------------------------------------------------+
| License:                                                           |
| This software is subject to the PHP License, available in this     |
| distribution in the file LICENSE.  By continuing this installation |
| process, you are bound by the terms of this license agreement.     |
| If you do not agree with the terms of this license, you must abort |
| the installation process at this point.                            |
+--------------------------------------------------------------------+

Thank you for using PHP.

config.status: creating main/build-defs.h
config.status: creating scripts/phpize
config.status: creating scripts/man1/phpize.1
config.status: creating scripts/php-config
config.status: creating scripts/man1/php-config.1
config.status: creating sapi/cli/php.1
config.status: creating sapi/phpdbg/phpdbg.1
config.status: creating sapi/cgi/php-cgi.1
config.status: creating ext/phar/phar.1
config.status: creating ext/phar/phar.phar.1
config.status: creating main/php_config.h
config.status: executing default commands

We are now ready to build the project.

Build

Before you issue a build command, it is worth checking how many cores your machine has:

nproc

Now you can use this number to make build faster with -j flag:

make -j4

This will use 4 cores to make a build. After make is done, you should see:

Generating phar.php
Generating phar.phar
PEAR package PHP_Archive not installed: 
 generated phar will require PHP's phar extension be enabled.
directorytreeiterator.inc
directorygraphiterator.inc
pharcommand.inc
clicommand.inc
invertedregexiterator.inc
phar.inc

Build complete.
Don't forget to run 'make test'.

If you have more time you can test your compiled PHP:

make test

Next, we want to make compiled binary available in an independent place.

Install

sudo make install

This command will install PHP in --prefix destination (provided during configuration). Now check if new compiled PHP works:

/opt/php/php8/bin/php -v

PHP 8.0.0-dev (cli) (built: Apr  5 2019 11:19:45) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies

It starts to get interesting.

Enable extension

And this is the point about which I completely forgot. Without it, there will be no support for JIT because it is an integral part of opcache.

PHP JIT is implemented as an almost independent part of OPcache. It may be enabled/disabled at PHP compile time and at run-time.

We want to enable opcache extension. Since there is no configuration file you can create new one. First lets check correct path:

$ /opt/php/php8/bin/php --ini
Configuration File (php.ini) Path: /opt/php/php8/lib
Loaded Configuration File:         (none)

You can copy php.ini-development or php.ini-production from source directory (and rename it to php.ini) or create new one.

At this moment, we will create a new empty file and load the extension.

cd /opt/php/php8/lib
sudo touch php.ini
echo 'zend_extension=opcache.so' | sudo tee php.ini 

You can check if everything goes correclty either with -v option or -m option:

/opt/php/php8/bin/php -v            
PHP 8.0.0-dev (cli) (built: Apr  5 2019 11:19:45) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologies

Elegantly, you have the latest PHP version. We can say that you have bleeding edge PHP version 🚀.

Check JIT

To check if JIT works and can optimize your code create simple script jit.php:

for ($i=0; $i<100; $i++) {
    echo $i;
}

First run it and check if works:

/opt/php/php8/bin/php jit.php

To enable JIT we must provide additional ini flags: opcache.enable_cli=1 opcache.jit_buffer_size=50000000 opcache.jit=1235

/opt/php/php8/bin/php -d opcache.enable_cli=1 
-d opcache.jit_buffer_size=50000000 -d opcache.jit=1235 jit.php

You will find more details about new settings in RFC

At first glance, it does not change anything, if you want to be sure that JIT works add opcache.jit_debug=1:

/opt/php/php8/bin/php -d opcache.enable_cli=1 
-d opcache.jit_buffer_size=50000000 -d opcache.jit=1235 
-d opcache.jit_debug=1 jit.php

You should see generated assembler code:

.L1:
        test $0x1, 0x9(%rdi)
        jnz .L8
.L2:
        mov $0x0, (%rdi)
        mov $0x4, 0x8(%rdi)
.L3:
        mov $EG(exception), %rax
        cmp $0x0, (%rax)
        jnz JIT$$exception_handler
        jmp .L5
.L4:
        mov $0x7fd319514630, %r15
        mov $0x5555cc61c100, %rax

You can also check performance with/without JIT using Zend/bench.php file from the source directory.

In the next entry, I will present the results of experiments as JIT affects performance in machine learning tasks.

Happy JITing 😉

Arkadiusz Kondas
Don't miss new blog posts and subscribe.

Sources