Maximally minimal stack trace standardization

Steve Fink sphink at gmail.com
Mon Sep 29 12:19:22 PDT 2014


On 09/29/2014 09:14 AM, Sam Tobin-Hochstadt wrote:
> On Mon, Sep 29, 2014 at 10:55 AM, John Lenz <concavelenz at gmail.com> wrote:
>> I really have no idea what the behavior should be in the faces of optimized
>> tail calls (which is must broader than simply self recursive methods that
>> can be rewritten as a loop).   I've seen various suggestions (a capped call
>> history) but I'm curious how efficient functional languages deal with this.
> Different functional languages do a variety of things here:
>
> - simply show the current stack, without the functions that made tail
> calls (this is probably the most common)
> - have a bounded buffer for stack traces
> - implement tail calls via a trampoline; this has the side-effect that
> the stack has "recent" tail calls in it already
>
> I'm sure there are other choices here that people have made.

"Stack traces" are really an overload of (at least?) 3 different concepts:

1. A record of how execution reached the current state. What debuggers
want, mostly.
2. The continuation from this point on - what function will be returned
to when the current function returns normally, recursively up the call
chain.
3. A description of the actual state of the stack.

In all of these, the semantics of the youngest frame are different from
all other frames in the stack trace.

For #2, thrown exceptions make the implied continuation ordering a lie,
or at least a little more nuanced. You sort of want to see what frames
will catch exceptions. (But that's not a trivial determination if you
have some "native" frames mixed in there, with arbitrary logic for
determining whether to catch or propagate an exception. Even JS frames
may re-throw.)

Inlined functions may cause gaps in #1 and #2, unless the implementation
takes pains to fill them in with dummy frames (in which case it's not
really #3 anymore.)

Unless the implementation plays games, tail calls can make #1 lie as
well. You really called f(), but it doesn't appear because its frame was
used for executing g() before pushing the remaining frames on your
stack. Tail calls don't really muck with #2 afaict.

All three meanings are legitimate things to want, and all of them
require some implementation effort. Even #3 is tricky with a JIT
involved. And I'm not even considering floating generator frames, which
may not fit into a linear structure at all. Or when users want "long
stacks" for callbacks, where the stack in effect when a callback was set
is relevant.



More information about the es-discuss mailing list