The interpreter rewrites itself, then Graal compiles it
PHP goes through a hand-written lexer and a Pratt parser and comes out as a tree of nodes. Each node watches the types that flow through it and specializes. If a + has only ever seen integers, it ends up as one machine add plus an overflow check. If it ever does overflow, it switches to floats, which is what PHP 8 does anyway.
When a function gets hot, Graal compiles the interpreter and the specialized tree together into machine code (partial evaluation). Call sites remember what they call, so small functions get inlined into their callers. That's where the wins over PHP's tracing JIT come from: recursion, integer loops, and code split into lots of little helpers.
// the integer case: one add, one overflow branch @Specialization(rewriteOn = ArithmeticException.class) long doLong(long a, long b) { return Math.addExact(a, b); } @Specialization double doDouble(double a, double b) { return a + b; }