There Used To Be A Pun Here, But It Was Scalar Replaced
Jnthn just recently merged Partial Escape Analysis to MoarVM's master branch. What this means is that objects allocated by the code can now sometimes be allocated on the call frame's stack instead. That means the GC is no longer involved for this particular allocation.
It's particularly called "partial" because the optimization can be done even when the lifetime of the object in question is not fully understood. Here's an example to hopefully make this clearer:
Imagine code like 1 / $middle / $divisor
. This would create a Rat from 1
and $middle
and pass that to the division operator along with $divisor
(let's assume that's a Num, aka floating point number, and that $middle
is an Int). Clearly, the Rat object created from 1
and $middle
won't be needed any more after the last division is done. However, it would normally be allocated in the garbage collectors nursery area. It will stick around until the next minor collection, which happens when the nursery is full, which by default happens at 4 megabytes of data allocated.
So what can the optimizer do about this? First of all, if the code has been run a bunch of times already, spesh will likely have observed that the calls to the division operators are always the same, and inlined their code into the caller's frame. What does the lifetime of the first Rat object look like now? It gets created from the integers 1
and $middle
, then in order to divide it by a Num
it will be coerced to a Num
itself, and then the Num
gets divided by $divisor
to give the final result.
Creating the Rat
object is a tiny bit more complicated than just storing the numerator and denominator, since it will be normalized (for example, 10 / 15
would be stored as 2 / 3
instead). After that, the division operator will grab the numerator and denominator to divide them and get a Num
from them. What is the Rat
object necessary for in the first place?
That question can easily be answered: In order to select the right method and operator candidates to use, the type must be known. However, the type can just be stashed away in a temporary register, which can then be used in type checks. When that transformation is done, the numerator and denominator values can also live in registers instead of attributes of an object. Whenever one of the attributes is used somewhere, the register will supply the value instead.
So now that all operators and such are inlined,