by Enrico Zimuel
Principal Software Engineer at Elastic

Enrico Zimuel

@ezimuel

PHP 8 beta4

17 September 2020

Installation

  • Download source code PHP-8.0.0beta4
  • Compile (for GNU/Linux):
    • ./configure --prefix=/opt/php/php8 --enable-opcache --with-zlib --enable-zip --enable-json --enable-sockets --without-pear
    • make
    • sudo make install
    • PHP 8 will be installed in /opt/php/php8
  • More info: Compiling PHP 8 from source with JIT support

New features (some)

  • JIT compiler (RFC)
  • Attributes (RFC)
  • Constructor property promotion (RFC)
  • Union types (RFC)
  • Static return type (RFC)
  • Weak maps (RFC)
  • Stringable interface (RFC)

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

How JIT 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)

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

JIT benchmark

Execution of Zend/bench.php (more information)

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

Attributes

Attributes (RFC)

  • Attributes add metadata to PHP code: classes, properties, methods, functions
  • Syntax is #[Attribute], read the Shorter Syntax RFC
  • This will replace docblocks for adding metadata,
    e.g. // @Route("/home")

Custom Attributes

  • Custom attributes are simple classes, annotated with
    #[Attribute]

#[Attribute]
class ListensTo
{
    public string $event;

    public function __construct(string $event)
    {
        $this->event = $event;
    }
}

Custom Attributes (2)


class ProductSubscriber
{
    #[ListensTo(ProductCreated::class)]
    public function onProductCreated(ProductCreated $event) { }

    #[ListensTo(ProductDeleted::class)]
    public function onProductDeleted(ProductDeleted $event) { }
}

Get attributes at runtime


$reflection = new ReflectionClass(ProductSubscriber::class);

foreach ($reflection->getMethods() as $method) {
    foreach ($method->getAttributes(ListensTo::class) as $attr) {
        $listener = $attr->newInstance();
        var_dump($listener); 
    }
}
// object(ListensTo)#5 (1) {
//    ["event"]=>
//    string(14) "ProductCreated"
// }
// object(ListensTo)#4 (1) {
//    ["event"]=>
//    string(14) "ProductDeleted"
// } 

ReflectionAttribute

					
class ReflectionAttribute
{
    public function getName(): string;
    public function getArguments(): array;
    public function newInstance(): object;
}

Built-in attributes (WIP)

  • #[Deprecated], to trigger a deprecated error (PR)
    
    #[Deprecated("use test2() instead")]
    function test() { }
    
    test();
    // Deprecated: Function test() is deprecated,
    // use test2() instead	
    
  • #[Jit], to enable JIT for a specific function

Constructor Property Promotion

Constructor Property


class Point {
    public float $x;
    public float $y;
    public float $z;
	
    public function __construct(
        float $x = 0.0,
        float $y = 0.0,
        float $z = 0.0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

Constructor Property Promotion


class Point {
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
    ) {}
}	

Property promotion rules

  • Only in constructors
  • No duplicate allowed
  • Untyped properties are allowed
  • Combining promoted- and normal properties
  • Access promoted properties from the constructor body

More information: Brent Roose, PHP 8: Constructor property promotion

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 A {
    public function __construct($params = []) {
        $this->params = $params;
    }

    public function create($params): static {
        return new static($params);
    }
}
class B extends A {
}

$b = new B();
$result = $b->create(['x' => 'y']);
var_dump($result); // object(B) {["params"]=> ["x" => "y"]}

Fluent interfaces


class A {
    public function doWhatever(): static {
        // Do whatever.
        return $this;
    }
}
$a = new A();
$result = $a->doWhatever(); // $result === $a

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) { }	

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+)

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 Release date

References

Thanks!

Follow me: @ezimuel

Subscribe to Leeds PHP 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.