Live data from Hacker News

What happened to proper tail calls in JavaScript? (2021)

mgmarlow.com

141–146 of 146 posts

Re: What happened to proper tail calls in JavaScript? (2021)

#141

Earlier quoted context omitted.

Being able to guarantee tail calls is a useful optimization in far more cases than code replaceable by loops. I’ve used it myself where dispatch targets themselves are dynamic (so can’t be trivially made into a loop) for significant performance gains. Some other folks who’ve done the same thing (and can post publicly) for code that’s not just “must recurse because loops are for lowly imperative serfs”: * https://blog…

Interesting use case, didn't occur to me that tail calls can also just be a performance optimisation technique to help out the compiler and branch predictor. I assumed hot loops could be implemented just as well using GOTOs, but maybe not?

Tail calls are basically GOTOs, yeah. The bring a HUGE benefit of very clearly defining state flow between gotos which makes the compiler's job super easy.

Re: What happened to proper tail calls in JavaScript? (2021)

#142
post #11

Earlier quoted context omitted.

Perhaps with a syntax for tail calls, you could do it not at the end of a function.

It needs to be at the end of the calling function so you can throw the calling function's stack frame away, since it's still in use. Getting rid of the calling stack frame is what proper tail calls is about.

I was suggesting something like bash `exec`

Re: What happened to proper tail calls in JavaScript? (2021)

#143
post #140

Earlier quoted context omitted.

AFAICT, your question doesn't make sense. But I guess you are thinking, "what if we should be using the TAIL_CALL syntax for performance but most people don't, and it's exarbated inside a tight loop?" If so, I'll try to explain: TCO is not about making code faster, it's about making it not eat up all the (stack) memory. It's a memory, not speed, optimization[1]. (OK, using less memory does make it somewhat faster (ev…

> It's a memory, not speed, optimization I assumed it was a speed optimization too, since a goto is faster than creating a whole stack frame and then destroying it again. Is that not the case? By “inside a loop”, I meant a loop that is independent of the function, like this: function foo() { if (…) { foo(); } } for (let i = 0; i Here it won't cause a stack overflow either way (if foo is used on small data), but I'd a…

> since a goto is faster than creating a whole stack frame

Yes, as I mentioned in parentheses--creating a stack frame costs because it is writing to memory, other than that it's just an increment of the stack pointer register in compiled/JITted code. But it's not a large cost, so you usually won't notice it or at least not much. Especially if your recursion depth is small, as then the memory writes might not leave the CPU cache. Sure, making some code 10% faster by way of TCO can still be useful, and it's probably why C compilers are doing it (they don't guarantee general TCO so you're limited in what you can do with it, so the only guaranteed advantage is a little speed gain), but if we're talking some JavaScript app there will be worse problems. Especially, the problem that with large enough data you're going to allocate lots of temporary memory for the stack--that's something a user will notice. Especially if the memory temporarily used is not given back to the OS, which is the case in C, and I rather expect JavaScript as well, which will mean that every tab running JavaScript will have some amount of RAM tied down to (even if rare) temporary stack use.

> and then destroying it again

This is then really just a decrement of the stack pointer.

Re: What happened to proper tail calls in JavaScript? (2021)

#144
post #140

Earlier quoted context omitted.

> It's a memory, not speed, optimization I assumed it was a speed optimization too, since a goto is faster than creating a whole stack frame and then destroying it again. Is that not the case? By “inside a loop”, I meant a loop that is independent of the function, like this: function foo() { if (…) { foo(); } } for (let i = 0; i Here it won't cause a stack overflow either way (if foo is used on small data), but I'd a…

> since a goto is faster than creating a whole stack frame Yes, as I mentioned in parentheses--creating a stack frame costs because it is writing to memory, other than that it's just an increment of the stack pointer register in compiled/JITted code. But it's not a large cost, so you usually won't notice it or at least not much. Especially if your recursion depth is small, as then the memory writes might not leave th…

That makes sense, thanks. Now I'm curious, if popping a stack frame is just decrementing a pointer and the contents stay there, isn't it a security vulnerability?

Re: What happened to proper tail calls in JavaScript? (2021)

#145
post #144

Earlier quoted context omitted.

> since a goto is faster than creating a whole stack frame Yes, as I mentioned in parentheses--creating a stack frame costs because it is writing to memory, other than that it's just an increment of the stack pointer register in compiled/JITted code. But it's not a large cost, so you usually won't notice it or at least not much. Especially if your recursion depth is small, as then the memory writes might not leave th…

That makes sense, thanks. Now I'm curious, if popping a stack frame is just decrementing a pointer and the contents stay there, isn't it a security vulnerability?

Only if there's a way to access it. From within JavaScript you don't have access to it, unless there's a security hole in the VM. But yes, programs handling encryption keys generally overwrite the memory holding them before freeing it / letting it go, at least those written in C/C++. Either for the case of a security hole allowing access to such memory, or so that it doesn't stay around in process memory where another process on the same system with the necessary permissions (on Linux either root or the same user) can access its memory via debugging APIs. But it's not done for normal data.

Re: What happened to proper tail calls in JavaScript? (2021)

#146
post #121
post #118

Earlier quoted context omitted.

I don't quite get the argument. Isn't this similar to e.g requiring the `z` parameter to be annotated with something that ensures that it is a small number? ("What is the downside of having special syntax for that?") My undrstanding is that a tail call is a tail call , and the variant with +1 is not that, thus being a candidate to be looked at. Perhaps also IDEs can help here if it's difficult to spot?

> Isn't this similar to e.g requiring the `z` parameter to be annotated with something that ensures that it is a small number? You mean something like "unsigned short int" :) ? Another example would be "const". What's the point - either the variable is const or it isn't. Programmer can just remove the mutation. > My undrstanding is that a tail call is a tail call, and the variant with +1 is not that, thus being a can…

I guess we can just agree to disagree here.

Regarding compiler vs IDE, I'd say that since it doesn't change the meaning of the code, just the depth of recursion, then IDE would fit better. Perhaps you could also ask for info on tail-call optimized functions from the compiler giving it a flag, but otherwise I don't think whether something was optimized or not doesn't matter much (depending on what you work on, of course).

Post reply on HN