These opcodes are used for optimizations and cached for the lifetime of the PHP process (across multiple requests).
Lower level bytecode lacks these higher level references (it usually has a lot of indices that index into raw lookup tables, but no real symbols that refer to results of computations and other variables).
Can you point me to some paper/reference about this? AFAIK it doesn't matter whether you have an AST or bytecode. Optimizations are done on an IR anyways.
That’s how I’ve always see “AST” used, but not the following:
yet can express the required data flows and dependencies so the trivial substitutions/replacements/caching and refactorings can be done at this level.
Is what you describe some flavor of ‘augmented’ AST? Does that have a name other than AST?
Perhaps the advantage here is that PHP really all that dynamic. PHP is much closer in design to taking Java and compiling it on every request than it is like Python or Ruby.
$moo = "hello";
$zork = "moo";
echo $$zork; // hello
It doesn't get much more dynamic than that.Although PHP does support the above syntax, it's actually pretty rare in production code. PHP can be optimized like JavaScript is: perform direct variable/member access but provide a slow path for these kinds of dynamic lookups.
<?php
eval('class Hello {public static function you(){echo "Yes!";}}');
Hello::you();
or this: <?php
file_put_contents('c.php', '<?php class Hello {public static function you(){echo "Yes!";}}');
require('c.php');
Hello::you();
Thus generating a class at runtime-ish.With a proper intermediate representation you'd connect that $$ variable lookup directly to the original variable's edge.
If not please correct me.
echo $FOO[$BAR];
echo $$BAZ;
>>>
push FOO.ptr
push BAR.ptr
call hash_get
push ax
call print
push local_table // static per compiled scope
push BAZ.ptr
call hash_get
push [ax+24] // entry->value
call print
You'd probably want to add error handling (to both cases) and accessing a global variable that didn't exist when the code was compiled will likely be slow, but if becomes a preformance bottleneck you can always rewrite it to something easier to compile.The _get and _set magic methods (called when getting/setting missing properties) are quite common especially for frameworks.
> use DynAsm (developed for LuaJIT project) for generation of native code
But at the very least, this exercise will ensure that DynAsm will be actively used by a few people. So even if this PHP JIT effort fails, the underlying JIT technology will be more widely understood and hopefully better documented for other projects.
Can u give an example or link to a relevant article. Context: my area is more of high-level program (e.g. shape) analysis
Removing the object allocation saves time and puts less pressure on the GC. Replacing the instance variables with local variables may also allow the compiler to optimize some things that it would not have attempted to optimize before. For example, storing them on machine registers.
But would be surprised that a LLVM compiler with a custom-designed heuristic for specific optimizations cant manage this.
These problems may be polynomial etc etc, but MOST of the code generated should follow common patterns
I will have to give this some thought.
Thanks for the reply (also GP)
Most serious JIT engines do! For example V8, LuaJIT, Graal, C2.
> But would be surprised that a LLVM compiler with a custom-designed heuristic for specific optimizations cant manage this.
Rubinius for example tried writing custom optimisations like you're describing and didn't get very far. LLVM is not designed for these kind of optimisations - it understands memory reads and writes, not high-level semantic information about arrays and objects.
In practice, it just doesn't seem to work.
As I say I think it's a bare minimum for making a dynamic language genuinely fast. I gave a lecture about this https://www.youtube.com/watch?v=b1NTaVQPt1E.
Ideal is also a classic https://www.oracle.com/technetwork/java/javase/tech/c2-ir95-....
I believe their plan is to emit low-level code directly via DynAsm, without their own intermediate representation.
This kind of approach has been tried again and again and again and every project doing this either not got the results they wanted and given up or has had to go back and add a proper intermediate representation.
Examples include Rust adding MIR before emitting LLVM, Rubinius trying to emit LLVM directly from Ruby and not getting good results so giving up, JRuby adding an IR before emitting Java byte code, Dropbox's Pyston implementation of Python that emitted LLVM directly where they gave up entirely, etc, etc, etc.
Low level backends are not going to do the kind of key optimisations such as scalar replacement of aggregates that are table stakes for making a dynamic language perform well - they just don't have the high-level semantic model of the language needed to do it.
Maybe they've got some new ideas, or maybe simplicity is a key constraint for them, but I predict from experience that they will need an intermediate representation to get the results that they want.