Goal: have a profiler which is aware of the PyPy JIT and which shows which percentage of the time have been spent in which loops.
Long term goal: integrate the data collected by the profiler with the jitviewer.
The idea is record an event in the PYPYLOG everytime we enter and exit a loop or a bridge.
[100] {jit-profile-enter loop1 # e.g. an entry bridge [101] jit-profile-enter} ... [200] {jit-profile-enter loop0 # JUMP from loop1 to loop0 [201] jit-profile-enter} ... [500] {jit-profile-exit loop0 # e.g. because of a failing guard [501] jit-profile-exit}
In this example, the exiting from loop1 is implicit because we are entering loop0. So, we spent 200-100=100 ticks in the entry bridge, and 500-200=300 ticks in the actual loop.
“Inner bridges” are those bridges which jump back to the loop where they originate from. There are two possible ways of dealing with them:
- we ignore them: we record when we enter the loop, but not when we jump to a compiled inner bridge. The exit event will be recorded only in case of a non-compiled guard failure or a JUMP to another loop
- we record the enter/exit of each inner bridge
The disadvantage of solution (2) is that there are certain loops which takes bridges at everty single iteration. So, in this case we would record a huge number of events, possibly adding a lot of overhead and thus making the profiled data useless.
Ways to enter:
- just after the tracing/compilation
- from the interpreter, if the loop has already been compiled
- from another loop, via a JUMP operation
- from a hot guard failure (which we ignore, in case we choose solution (1) above)
- XXX: am I missing anything?
Ways to exit:
- guard failure (entering blackhole)
- guard failure (jumping to a bridge) (ignored in case of solution (1))
- jump to another loop
- XXX: am I missing anything?
About call_assembler: I think that at the beginning, we should just ignore call_assembler: the time spent inside the call will be accounted to the loop calling it.