Any ideas why they’ve moved from a method-based JIT to tracing?
AFAIK tracing JITs are generally inferior to method-based ones, which is why none of the major JavaScript engines use tracing. Their only advantage seems to be (relative) simplicity, which is essential for the lightweight LuaJIT but not for PHP.
The method JIT they built before failed to provide any improvements on a real world app. Building a JIT compiler for a dynamic language that's actually faster than a fast interpreter is quite tricky!
There's a lot more to it than just compiling what the interpreter does. CRuby's JIT has a different approach using C templating rather than Dynasm and templating/context threading but it also fails to improve performance much for the same reasons.
Tracing JITs are really good at optimizing a small scope. What a tracing JIT does with recording a single path of linear control flow the method JITs also try to do with branch profiling and pruning.
It works really well for a particular style of Lua. For a language like Ruby or PHP it's a lot harder to make a tracing JIT work well on existing code.
The main problem is tail duplication causes the number of traces to increase exponentially with each branch. You have to either have trace heuristics which keep the traces very short or go half way to a method JIT and build a control flow graph.
I got to the tail explosion problem building a tracing JIT for CRuby and gave up.
That doesn't sound right. How would a trace-based JIT be easier to implement? There was a research project which made HotSpot into a trace-based JIT. It reported both faster compilation times, and superior quality of generated code.
(I don't know if the HotSpot folks ever considered adopting the changes.)
> How would a trace-based JIT be easier to implement?
When compiling at method-granularity, with incomplete information about types, complex features are required e.g. On-Stack Replacement (if the current method becomes "hot", it needs to be replaced by a compiled version), Inline Caches, Hidden Classes and Deoptimization.
In a trace-based JIT, all these features can be replaced by "just compile another trace".
We need all those complex features with tracing JITs too. Getting into a trace is basically OSR. Exiting a trace and restoring the interpreter state is basically an OSR exit or deoptimization. It's really just different terminology for the same thing. Inline caches on method calls become guards. We can't just ignore them.
LuaJIT does without hidden classes or property caching because on-trace it's able to eliminate table access and allocation. Performance tanks on OO Lua once you fall off trace. You can work around it with "compile another trace" in a sense that works as long as you have infinite cache.
AFAIK tracing JITs are generally inferior to method-based ones, which is why none of the major JavaScript engines use tracing. Their only advantage seems to be (relative) simplicity, which is essential for the lightweight LuaJIT but not for PHP.