The stack in Klink is a spaghetti stack (as usual for Scheme). It's
also heterogeneous. While call frames are the "main" thing, and they
are what is popped when one combiner is evaluated, the stack includes:
-
call frames
-
guards
-
dynamic variables
So to that list I added profile structures. They contain:
-
Pointer to the combiner in question
-
Count of eval-cycles so far.
That much I've already written. For profiling, each _klink_cycle will
push the combiner it is using. That's unless the same combiner is
already being profiled in this frame, which happens when a combiner
recurses as a tail call.
That's because _klink_cycle stores the profiling node above the
original frame (ie, one place further away from the leaves of the
stack). It has to, because it pops the frame that made that combiner
launch. If it stored tail calls too, recursive functions could
accumulate many profiling frames. In particular, the REPL loop would
have a ton of them, because it's implemented as a recursive combiner.
I may make profiling count distinct tail calls, but for now I'm just
treating tail calls as continuances of the original call.
This design will not show calls that never terminate. It's not clear
that they could be meaningfully profiled. This specifically includes
calls that never continue normally.
This design also doesn't try to handle re-entered continuations. The
second time a continuation is used, the elapsed number of
_klink_cycle counts bears no relation to how much evaluation the
call caused. Again, it's not clear that this situation can be
meaningfully profiled. I will probably just ignore returns other than
the first time.
It's also not clear what this should do when a call's continuation is
stored (say in user environment), and later is used for the first
time. It would be misleading to count every _klink_cycle between
the two times. So this design is far from perfect too.