As of February 2019 in Chrome Version 71.0.3578.98
on Mac , the following program throws Uncaught RangeError: Maximum call stack size exceeded error.
at a count of 16516
.
const a = x => { console.log(x) a(x + 1) } a(1)
I've done quite a bit of Googling, but wasn't able to find any articles discussing Chrome or other browser support for Tail Call Optimization (TCO) or any future plans to implement it.
My two questions are:
The posts that I've found are mostly old (2016 or earlier) or simply confusing. e.g. https://www.chromestatus.com/feature/5516876633341952
Javascript does not optimize tail calls, so I implemented tail call optimization in Javascript itself, and tested it on various browsers. For those not familiar with tail calls, the first two sections explain it.
Tail call optimization is the specific use of tail calls in a function or subroutine that eliminate the need for additional stack frames. Tail call optimization can be part of efficient programming and the use of the values that subroutines return to a program to achieve more agile results or use fewer resources.
Some C compilers, such as gcc and clang, can perform tail call optimization (TCO).
TCO, or rather, Tail Call Elimination in JavaScript -- also often referred to as Proper Tail Calls (PTC) in discussions -- is a long and sad story.
Around 2011, TC39 (the JavaScript standards committee) decided to adopt mandatory TCE for the forthcoming ES6 standard, with consensus from all major browser vendors.
In 2015, the new standard was officially adopted, under the name EcmaScript 2015. At this point, no browser had actually implemented TCE, mostly because there were too many new features in ES2015 that were deemed more important to get out. (Today's process for JS feature proposals and their adoption, which includes the requirement of two implementations in production engines, did not yet exist for ES6.)
In early 2016, both Safari and Chrome implemented TCE. Safari announced shipping it, while Chrome kept it behind an Experimental Feature flag. Other browsers (Firefox and Internet Explorer / Edge) started looking into it as well and had second thoughts. Discussion evolved whether this is a viable feature after all. Edge had problems implementing it efficiently for the Windows ABI, Firefox was concerned about the developer experience of calls "missing" from stack traces (an issue that was already discussed at length in 2011).
In an attempt to address some of these concerns while rescuing the tail call feature, several members, including the Chrome and Edge teams, proposed to make tail calls explicit, i.e., require return statements to be annotated with an additional keyword to opt into tail call semantics. These so-called "syntactic tail calls" (STC) were implemented in Chrome as a proof of concept.
At the May 2016 TC39 meeting the issue of tail calls was discussed extensively for almost an entire day with no resolution. Firefox and Edge made clear that they would not implement TCE as specified in the standard. Firefox members proposed to take it out. Safari and Chrome did not agree with that, and the Safari team made clear that they have no intention of unshipping TCE. The proposal for syntactic tail calls was rejected as well, especially by Safari. The committee was in an impasse. You can read the meeting notes of this discussion.
Technically, this impasse still exists today, as far as I am aware. Practically speaking, though, tail calls for JavaScript are pretty much dead, and it's unclear whether they will ever come back. At least that was the conclusion of the Chrome team after the disastrous meeting, which led to the decision to remove the implementation of tail calls from Chrome, in order to simplify the engine and prevent bit rot. They are still available in Safari.
Disclosure: I was a member of TC39 and of the Chrome/V8 team until 2017, so my views may be biased.
Even tho TCO seems to be a pipe dream for all of us, by using a trampoline
technique, you can easily convert your code to run as if it is being tail optimized.
const a = x => { if(x > 500000) { console.log(x); return; } return ()=> a(x + 1); //you return a function, it hasn't been called yet } const trampoline = fn => (...args) => { let result = fn(...args) //repeatedly call the function till you hit your base case while (typeof result === 'function') { result = result(); } return result; } var t = trampoline(a); t(1);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With