Hacker News new | ask | show | jobs
by munchler 1469 days ago
[From the article]

Why are browser vendors ignoring PTC? V8 chalks it up to two main reasons:

* It makes it more difficult to understand during debugging how execution arrived at a certain point since the stack contains discontinuities, and

* error.stack contains less information about execution flow which may break telemetry software that collects and analyzes client-side errors.

2 comments

> It makes it more difficult to understand during debugging how execution arrived at a certain point since the stack contains discontinuities

That's a weird complaint, considering that stacks don't describe "how execution arrived at a certain point". In fact, stacks don't describe the past at all; rather, they describe the future of what's left to do (AKA the "continuation").

For example, consider this code:

    function foo() {
      const bar = someComplexFunction();
      performSomeEffect();
      baz(bar);
    }
If an error occurs somewhere inside `baz`, the stack trace won't mention anything about `someComplexFunction`, or `performSomeEffect`, or the vast majority of "how we arrived at" the call to `baz`. Yet it will tell us exactly what was remaining to do (namely, `baz` and `foo`).

If we eliminate tail calls, stack traces are still an exact description of the continuation. The difference is that "remaining work" doesn't include a bunch of useless identity functions (i.e. redundant stack frames with no further work to do)

For execution, a stack is a continuation. For debugging, we pretend like it's a historical record, and mostly get away with it. Various things break the correspondence slightly. TCO breaks it a lot more.

Debugging is important. It doesn't get enough respect. Stacks are a pretty critical component of debugging, for better or worse.

It would be great if we didn't depend on this fiction quite so much. With native code, there are definitely alternative options now, such as rr[1] and Pernosco[2] where if you want to look back in time—well, you just go back in time. For JavaScript, that's becoming more and more possible with things like Replay[3]. Perhaps before long, the debugging argument will just go away.

[1] https://rr-project.org/

[2] https://pernos.co/

[3] https://www.replay.io/

The hardware stack has always been a crutch that in retrospect was probably a bad idea. We use it for jobs it's not well-suited for (like parameter passing, local variables, and debugging) and it has held back better flow control mechanisms like delimited and first-class continuations. And of course TCO, which wouldn't even be a thing if everybody didn't automatically assume a stack pointer was involved with every call. (Hard to imagine? Yes, but plenty of other flow control models exist.)

Stacks are still useful for low-level jobs like register spilling and interrupt handlers, and they make memory management of such data easy. Nevertheless on modern machines with multicore processors running message-passing programs, the limitations of what can be done in high-level code with a one-dimensional stack pointer should now be obvious.

Stack frames also capture locals, and those often provide a lot of information about what just happened. I've had cases before where this was instrumental to figuring out the cause of the bug, and other cases where it likely would have been if TCO hasn't wiped out that information (in C++).
If you're writing imperative code with side-effects (or mixed-style) like much classic JS code is, the existence of foo on the call stack indicates that performSomeEffect has been run, and thus it's side-effects on our global state when we enter baz has to be accounted for.

Is it an ideal style to write code in? No. Does real code have this problem, Yes!

This is such a weird complaint given that Erlang exists, with tail calls, and proper async, and..., and is used to create complex software