一昨日の記事を書いた後、末尾呼び出し最適化の実装にバグがあって速攻で消されたようです。

  • v3.5.0: 末尾呼び出し最適化の実装が入る
  • v3.5.1: 他のTailオブジェクトと混同して判定されてしまうため、Tail.prototype._isTailDescriptor というのを付けて対処
  • v3.5.2: _tailCall をつかった末尾呼び出し最適化がコメントアウト
  • v3.5.3: v3.5.0 以前の末尾再帰最適化の実装に戻す

少し見たところ、以下のようなケースでうまく動かないようです。

function foo() {
  return "foo";
}

function fooWrapper() {
  return foo();
}

function test() {
  var str = fooWrapper();
  if (str !== "foo") {
    throw new Error();
  }
}

function testWrapper() {
  return test();
}

testWrapper();

なぜこのケースでうまく動かないのか見ていきます。
このコードを 6to5(v3.5.1) で変換したものがこちらです。

"use strict";

var _tailCall = (function() {
    function Tail(func, args, context) {
        this.func = func;
        this.args = args;
        this.context = context;
    }
    Tail.prototype._isTailDescriptor = true;
    var isRunning = false;
    return function(func, args, context) {
        var result = new Tail(func, args, context);
        if (!isRunning) {
            isRunning = true;
            do {
                result = result.func.apply(result.context, result.args);
            } while (result instanceof Tail || result && result._isTailDescriptor);
            isRunning = false;
        }
        return result;
    };
})();

function foo() {
    return "foo";
}

function fooWrapper() {
    return _tailCall(foo);
}

function test() {
    var str = fooWrapper();
    if (str !== "foo") {
        throw new Error();
    }
}

function testWrapper() {
    return _tailCall(test);
}

testWrapper();

呼び出し順序は以下の順序です。

testWrapper
  _tailCall(test)
    test
      fooWrapper
        _tailCall(foo)

ここで問題となるのが _tailCall(foo) です。
これはもともと return foo(); なので "foo" が返るはずです。
しかし、この呼び出し順序では最初の _tailCall(test) が呼ばれた時に isRunnningtrue になるため、 _tailCall(foo) は Tail オブジェクトを返します。
var str = fooWrapper();str の値が Tail オブジェクトになってしまうため、変換前の結果と異なってしまいます。

今回のように他の末尾呼び出ししている function の戻り値を変数に代入するようなケースでもうまく動くような修正が入らなければ、再び末尾呼び出し最適化を有効にするのは難しそうな気配を感じます。
(現在はもともと入ってた末尾再帰最適化での高速化を行っているようです)
そのほかにもパフォーマンスの問題もあるので大変そうですが、はやく再び有効になると良いなあと思います。