8 is coming:
the new PHP


by Enrico Zimuel
Principal Software Engineer @ Elastic


PUG Torino, Mar. 31, 2020

About me

PHP 8

New features* (some)

  • JIT compiler (RFC)
  • Union types (RFC)
  • Static return type (RFC)
  • Weak maps (RFC)
  • ::class on objects (RFC)
  • Stringable interface (RFC)

* accepted

JIT

What's JIT?

    A Just-In-Time (JIT) compiler is a feature of the run-time interpreter, that instead of interpreting bytecode every time a method is invoked, will compile the bytecode into machine code, and then invoke this object code instead

PHP execution

Example


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

Opcode:


L0 (2):     ASSIGN CV0($i) int(0)
L1 (2):     JMP L4
L2 (3):     ECHO CV0($i)
L3 (2):     PRE_INC CV0($i)
L4 (2):     T1 = IS_SMALLER CV0($i) int(100)
L5 (2):     JMPNZ T1 L2
L6 (5):     RETURN int(1)

php -d opcache.opt_debug_level=0x20000 -d opcache.enable_cli=1 test.php

Opcache

JIT

JIT machine code


	sub $0x10, %rsp
	lea 0x50(%r14), %rdi
	cmp $0xa, 0x8(%rdi)
	jnz .L1
	mov (%rdi), %rdi
	cmp $0x0, 0x18(%rdi)
	jnz .L6
	add $0x8, %rdi
.L1:
	test $0x1, 0x9(%rdi)
	jnz .L7
	mov $0x0, (%rdi)
	mov $0x4, 0x8(%rdi)
.L2:
	mov $EG(exception), %rax
	cmp $0x0, (%rax)
	jnz JIT$$exception_handler
	jmp .L4
.L3:
	mov $0x7fcc67e2e630, %r15
	mov $0x561f82773690, %rax
	call *%rax
	mov $EG(exception), %rax
	cmp $0x0, (%rax)
	jnz JIT$$exception_handler
	cmp $0x4, 0x58(%r14)
	jnz .L9
	add $0x1, 0x50(%r14)
.L4:
	mov $EG(vm_interrupt), %rax
	cmp $0x0, (%rax)
	jnz .L11
	cmp $0x4, 0x58(%r14)
	jnz .L12
	cmp $0x64, 0x50(%r14)
	jl .L3
.L5:
	mov $0x7fcc67e2e6b0, %r15
	add $0x10, %rsp
	mov $ZEND_RETURN_SPEC_CONST_LABEL, %rax
	jmp *%rax
	sub $0x10, %rsp
	jmp .L4
.L6:
	mov $0x7fcc67e2e5c0, %rsi
	mov %r15, (%r14)
	mov $zend_jit_assign_const_to_typed_ref, %rax
	call *%rax
	jmp .L2
.L7:
	mov (%rdi), %rax
	mov %rax, (%rsp)
	mov $0x0, (%rdi)
	mov $0x4, 0x8(%rdi)
	mov (%rsp), %rdi
	sub $0x1, (%rdi)
	jnz .L8
	mov %r15, (%r14)
	mov $rc_dtor_func, %rax
	call *%rax
	jmp .L2
.L8:
	mov 0x4(%rdi), %eax
	and $0xfffffc10, %eax
	cmp $0x10, %eax
	jnz .L2
	mov $gc_possible_root, %rax
	call *%rax
	jmp .L2
.L9:
	mov %r15, (%r14)
	lea 0x50(%r14), %rdi
	cmp $0xa, 0x8(%rdi)
	jnz .L10
	mov (%rdi), %rsi
	cmp $0x0, 0x18(%rsi)
	lea 0x8(%rsi), %rdi
	jz .L10
	mov $0x0, %rdx
	mov $zend_jit_pre_inc_typed_ref, %rax
	call *%rax
	mov $EG(exception), %rax
	cmp $0x0, (%rax)
	jnz JIT$$exception_handler
	jmp .L4
.L10:
	mov $increment_function, %rax
	call *%rax
	jmp .L4
.L11:
	mov $0x7fcc67e2e670, %r15
	jmp JIT$$interrupt_handler
.L12:
	cmp $0x5, 0x58(%r14)
	jnz .L13
	mov $0x64, %rax
	vcvtsi2sd %rax, %xmm0, %xmm0
	vucomisd 0x50(%r14), %xmm0
	ja .L3
	jmp .L5
.L13:
	mov $0x7fcc67e2e670, %rax
	mov %rax, (%r14)
	lea 0x50(%r14), %rsi
	mov $0x7fcc67e2e5d0, %rdx
	lea 0x60(%r14), %rdi
	mov $compare_function, %rax
	call *%rax
	mov $EG(exception), %rax
	cmp $0x0, (%rax)
	jnz JIT$$exception_handler_undef
	cmp $0x0, 0x60(%r14)
	jl .L3
	jmp .L5

php -d opcache.enable_cli=1 -d opcache.jit=1235
-d opcache.jit_buffer_size=64M -d opcache.jit_debug=1 test.php

JIT in PHP 8

  • JIT is an extension of Opcode Cache
  • How does JIT choose to generate machine code?
  • How can JIT generate machine code for all the CPUs?

How does JIT choose to generate machine code?

JIT configuration (CRTO)

  • opcache.jit consists of 4 decimal digits, e.g 1235
  • 1 = use AVX instructions (CPU optimization)
  • 2 = Register allocation
  • 3 = profile on the fly and compile hot functions (Trigger)
  • 5 = type inference & procedure analysis (Optimization level)

CPU Optimization

  • 0 = none
  • 1 = enable AVX instruction generation

Register allocation

  • 0 = don't perform register allocation
  • 1 = use local liner-scan register allocator
  • 2 = use global liner-scan register allocator

JIT Trigger

  • 0 = JIT all functions on first script load
  • 1 = JIT functions on first execution
  • 2 = Profile on first request and compile hot functions on second request
  • 3 = Profile on the fly and compile hot functions
  • 4 = Compile functions with @jit tag in doc-comments

Optimization level

  • 0 = don't JIT
  • 1 = minimal JIT (call standard VM handlers)
  • 2 = selective VM handler inlining
  • 3 = optimized JIT based on static type inference of individual function
  • 4 = optimized JIT based on static type inference and call tree
  • 5 = optimized JIT based on static type inference and inner procedure analyses

How can JIT generate machine code?

DynASM

  • PHP 8 JIT uses DynASM to generate machine code
  • Dynamic Assembler for code generation engines
  • Developed as tool of LuaJIT
  • CPU support: x86, x64, ARM, PowerPC and MIPS

JIT benchmark

Execution time (sec) of Zend/bench.php, using PHP 7.4.4 (Zend Engine 3.4.0)
and PHP 8.0.0-dev (Zend Engine 4.0.0-dev)

JIT on WordPress

  • PHP 8 no JIT: 190 req/sec
  • PHP 8 + JIT (CTRO 1235): 160 req/sec
  • PHP 8 + JIT (CTRO 1225): 189 req/sec

Source: PHP 8 et Just In Time Compilation

IO Bound vs. CPU Bound

  • JIT is useful for CPU intensive application (machine learning, image manipulation, etc).
  • JIT does not offer improvement for IO Bound application

Union types

Union types (RFC)


class Number {
    private int|float $number;
	
    public function setNumber(int|float $number): void {
        $this->number = $number;
    }
	
    public function getNumber(): int|float {
        return $this->number;
    }
}

Static return type

Static return type (RFC)


class Foo {
    public function __construct($params = [])
    {
        $this->params = $params;
    }

    public function createFromWhatever($whatever): static {
        return new static($whatever);
    }
}
	
$foo = new Foo();
var_dump($foo); // object(Foo)#1 { ["params"]=> [] }

$bar = $foo->createFromWhatever(['x' => 'y']);
var_dump($bar);	// object(Foo)#2 { ["params"]=> ["x" => "y"]}

Fluent interfaces


class Test {
    public function doWhatever(): static {
        // Do whatever.
        return $this;
    }
}	

Weak maps

Weak maps (RFC)

To provide "weakly referenced" map objects


$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);
// object(WeakMap)#1 (1) {
//   [0]=> [ 
//     "key" => object(stdClass)#2,
//     "value" => int(42)
//   ]
// }
	
// The object is destroyed here, key automatically removed
unset($obj);
var_dump($map);
// object(WeakMap)#1 (0) { }	

Weakmap class


final class WeakMap implements ArrayAccess, Countable, Traversable {
    public function offsetGet($object);
    public function offsetSet($object, $value): void;
    public function offsetExists($object): bool;
    public function offsetUnset($object): void;
    public function count(): int;
}	

Example: caching objects


class Foo {
    private WeakMap $cache;
	
    public function getSomethingWithCaching(object $obj) {
        return $this->cache[$obj] ??= $this->somethingExpensive($obj);
    }
	
    // ...
}

??= Null Coalescing Assignment (PHP 7.4+)

::class on objects

::class on objects (RFC)


$object = new stdClass;
var_dump($object::class); // "stdClass"
	
$object = null;
var_dump($object::class); // TypeError

Stringable interface

Stringable interface (RFC)

					
interface Stringable
{
    public function __toString(): string;
}

Stringable interface is automatically added to classes that implement the __toString() method

PHP 8

BC breaks

BC breaks

  • Reclassified engine warnings (RFC)
  • E_ALL as default (now is everything but E_NOTICE and E_DEPRECATED)
  • The @ operator no longer silences fatal errors
  • Concatenation precedence (RFC)
  • Others...

PHP 8 Release date

PHP 8 should be released on December 2020
(maybe Q1 2021)

References

Thanks!

Follow me: @ezimuel

Subscribe to PUG Torino on Meetup!



Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.