Pre-compiling PHP files [closed]
Asked Answered
T

0

29

As a learning exercise, I'm trying to save the compiled state of a PHP file in order to execute it at a later time without having to go through zend_compile_file again.

The first thing I did was write an extension that hooks zend_compile_file. If the request is made on an uncompiled file (ie: file.php), it dumps the zend_op_array data to another file (ie: compiled-file.php). If the request is made on such a compiled file, it loads the data into a new zend_op_array and then returns it.

For simplicity sake, I have ignored everything related to classes and functions, so I don't expect my extension to works on a script that contains those. But other simpler scripts should work, I think?

Well, it works on very simple scripts, but often it just hangs and reach the maximum execution time limit. I've found that it always fails on false conditional branch. For example, this script would work:

<?php
$a = 10;
$b = 5;
if ($b < $a)
    echo $a;

While this one would just hangs:

<?php
$a = 10;
$b = 5;
if ($b > $a)
    echo $a;

My question: Am I correct in assuming that for simple scripts with no functions or classes, making a deep copy of zend_op_array and returning it should be enough to replicate PHP compilation? If not, what are the other steps I should take to make it works?

Here are the relevant files of my extension: opdumper.c oploader.c

EDIT: I managed to "fix" my problem by changing this code:

void dump_znode_op(FILE* fp, znode_op node, zend_uchar type)
{
    fwrite(&type, sizeof(type), 1, fp);
    switch(type) {
        case IS_UNDEF:
        case IS_UNUSED:
            break;
        ...
    }
}

to this one:

void dump_znode_op(FILE* fp, znode_op node, zend_uchar type)
{
    fwrite(&type, sizeof(type), 1, fp);
    switch(type) {
        case IS_UNDEF:
        case IS_UNUSED:
            fwrite(&(node.var), sizeof(node.var), 1, fp);
            break;
        ...
    }
}

(And of course, applying a similar fix to oploader.c)

Now I'm just even more confused... why would a znode_op with an IS_UNUSED type care about its value!??

Torosian answered 25/8, 2018 at 20:29 Comment(4)
IS_UNUSED is also used for immediate operands (u32 embedded in the opline). You can use zend_get_opcode_flags() to get extended information about what IS_UNUSED operands and extended_value are used for. I'd also recommend taking a look at github.com/php/php-src/blob/master/ext/opcache/…, which is an existing implementation of serialization to file.Cytochrome
For your particular example, you're likely seeing either a JMPZ or JMPNZ instruction. If you look at the VM definition in github.com/php/php-src/blob/master/Zend/zend_vm_def.h#L2517, you'll see that the second operand is marked as a JMP_ADDR, so your previous serialization was discarding the target address.Cytochrome
I think it would be useful to post the actual solution as an answer instead of an edit and accept it since this will prevent this question from appearing in the "unanswered questions" list.Swaraj
I'm not super deep into internals, but have you looked at how the opcode cache works for php SAPI servers (php-fpm, mod_php)? It must do something similar.Dubbin

© 2022 - 2024 — McMap. All rights reserved.