<p dir="auto">Have a look at this:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function foo (a, b) {
  if (b) {
    return foo(b);
  } else {
    return a;
  }
}"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-en">foo</span> <span class="pl-kos">(</span><span class="pl-s1">a</span><span class="pl-kos">,</span> <span class="pl-s1">b</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">b</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-en">foo</span><span class="pl-kos">(</span><span class="pl-s1">b</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-s1">a</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">One would expect <code class="notranslate">foo("Michael", "Jackson")</code> to evaluate to "Jackson", yes? Yes. Except it doesn't. Rather, the function never returns and your computer probably heats up a little.</p>
<p dir="auto">A quick look at the compiled code shows what the problem is:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function foo(_x, _x2) {
  var _again = true;

  _function: while (_again) {
    var a = _x,
        b = _x2;               // (1)
    _again = false;

    if (b) {                   // (2)
      _x = b;                  // (3)
      _again = true;
      continue _function;
    } else {
      return a;
    }
  }
}"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-en">foo</span><span class="pl-kos">(</span><span class="pl-s1">_x</span><span class="pl-kos">,</span> <span class="pl-s1">_x2</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">var</span> <span class="pl-s1">_again</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span><span class="pl-kos">;</span>

  _function: <span class="pl-k">while</span> <span class="pl-kos">(</span><span class="pl-s1">_again</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">var</span> <span class="pl-s1">a</span> <span class="pl-c1">=</span> <span class="pl-s1">_x</span><span class="pl-kos">,</span>
        <span class="pl-s1">b</span> <span class="pl-c1">=</span> <span class="pl-s1">_x2</span><span class="pl-kos">;</span>               <span class="pl-c">// (1)</span>
    <span class="pl-s1">_again</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span><span class="pl-kos">;</span>

    <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">b</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>                   <span class="pl-c">// (2)</span>
      <span class="pl-s1">_x</span> <span class="pl-c1">=</span> <span class="pl-s1">b</span><span class="pl-kos">;</span>                  <span class="pl-c">// (3)</span>
      <span class="pl-s1">_again</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span><span class="pl-kos">;</span>
      <span class="pl-k">continue</span> _function<span class="pl-kos">;</span>
    <span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
      <span class="pl-k">return</span> <span class="pl-s1">a</span><span class="pl-kos">;</span>
    <span class="pl-kos">}</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">At <code class="notranslate">(3)</code> we simulate the tail call <code class="notranslate">foo(b)</code> by setting <code class="notranslate">_x</code> to <code class="notranslate">b</code> and jumping back to the start of the loop. Only problem is, <code class="notranslate">_x2</code> is still the same value as <code class="notranslate">b</code> so <code class="notranslate">(1)</code> is a noop on the second go round. Obviously this means that <code class="notranslate">(2)</code> evaluates true and we just keep spinning like that.</p>
<p dir="auto">Solution: Reset all parameters, not just the ones passed to the tail call. Unless, perhaps, you can be certain they (the unpassed parameters) won't be dereferenced again, but that seems like difficult logic to implement for very little performance gain.</p>
<p dir="auto">But wait! There's more!</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function foo (a, b) {
  if (arguments.length == 2) {
    return foo(() =&gt; console.log(a, b));
  } else {
    return a;
  }
}

foo(&quot;Michael&quot;, &quot;Jackson&quot;)();"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-en">foo</span> <span class="pl-kos">(</span><span class="pl-s1">a</span><span class="pl-kos">,</span> <span class="pl-s1">b</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-smi">arguments</span><span class="pl-kos">.</span><span class="pl-c1">length</span> <span class="pl-c1">==</span> <span class="pl-c1">2</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-en">foo</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s1">a</span><span class="pl-kos">,</span> <span class="pl-s1">b</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-s1">a</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span>

<span class="pl-en">foo</span><span class="pl-kos">(</span><span class="pl-s">"Michael"</span><span class="pl-kos">,</span> <span class="pl-s">"Jackson"</span><span class="pl-kos">)</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">Can you guess what this prints? No cheating now...</p>
<p dir="auto">Yes that's right! It prints something like:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="function () { return console.log(a, b); } Jackson"><pre class="notranslate"><code class="notranslate">function () { return console.log(a, b); } Jackson
</code></pre></div>
<p dir="auto">This is because the lambda closes over <code class="notranslate">a</code>, but <code class="notranslate">a</code> is promptly reset by the TCE loop from "Michael" to the lambda itself.</p>
<p dir="auto">Solution?: check for parameters being closed over with the resulting closure escaping the scope of the current TCE loop iteration. Surround them with an extra closure or <code class="notranslate">with</code> or something? Not sure what would be performant.</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/0fb29840a4f13c89fbc5d2a8d64cfe3ec2cc37f15d9bad7210eba6309b4e30ca/687474703a2f2f692e696d6775722e636f6d2f775a51484d755a2e6a7067"><img src="https://camo.githubusercontent.com/0fb29840a4f13c89fbc5d2a8d64cfe3ec2cc37f15d9bad7210eba6309b4e30ca/687474703a2f2f692e696d6775722e636f6d2f775a51484d755a2e6a7067" alt="" data-canonical-src="http://i.imgur.com/wZQHMuZ.jpg" style="max-width: 100%;"></a></p>
<p dir="auto">Sorry I couldn't resist.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="function callbackPerInt(count, cb){
  if (count &lt; 0) return;

  cb(function(){ return count; });

  return callbackPerInt(count - 1, cb);
}"><pre class="notranslate"><code class="notranslate">function callbackPerInt(count, cb){
  if (count &lt; 0) return;

  cb(function(){ return count; });

  return callbackPerInt(count - 1, cb);
}
</code></pre></div>
<p dir="auto">Currently, TCO makes this</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="function callbackPerInt(_x, _x2) {
  var _again = true;

  _function: while (_again) {
    _again = false;
    var count = _x,
        cb = _x2;

    if (count &lt; 0) return;

    cb(function () {
      return count;
    });

    _x = count - 1;
    _x2 = cb;
    _again = true;
    continue _function;
  }
}"><pre class="notranslate"><code class="notranslate">function callbackPerInt(_x, _x2) {
  var _again = true;

  _function: while (_again) {
    _again = false;
    var count = _x,
        cb = _x2;

    if (count &lt; 0) return;

    cb(function () {
      return count;
    });

    _x = count - 1;
    _x2 = cb;
    _again = true;
    continue _function;
  }
}
</code></pre></div>
<p dir="auto">which means that the value returned by the final callback depends on when the function passed to the callback is called. If it's called synchronously inside <code class="notranslate">cb</code> then it will return the correct value, but otherwise it'll a later version of <code class="notranslate">count</code>.</p>
<p dir="auto">I don't know the spec, but I'd guess we either need to skip optimizing, or the while loop body needs to be treated like all function-scoped variables are block-scoped inside the while loop like a normal while loop.</p>