Add tail recursion optimization.

As per ES6, VMs should perform tail call optimization and prevent growth of call stack.
This adds tail call optimization for recursion case (when function has explicit name and calls itself in `return`).
Cross-function optimization is not currently performed as it's more complicated and requires value tracking.
This commit is contained in:
Ingvar Stepanyan
2015-02-06 13:55:51 +02:00
parent ad60d49611
commit 5b2216b348
11 changed files with 419 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
(function f(n) {
if (n <= 0) {
console.log(this, arguments);
return "foo";
}
return Math.random() > 0.5 ? f.call(this, n - 1) : f.apply(this, [n - 1]);
})(1e6) === "foo";

View File

@@ -0,0 +1,27 @@
"use strict";
(function f() {
var _arguments = arguments,
_this = this,
_shouldContinue,
_result;
do {
_shouldContinue = false;
_result = (function (n) {
if (n <= 0) {
console.log(this, arguments);
return "foo";
}
if (Math.random() > 0.5) {
_arguments = [n - 1];
_this = this;
return _shouldContinue = true;
} else {
_arguments = [n - 1];
_this = this;
return _shouldContinue = true;
}
}).apply(_this, _arguments);
} while (_shouldContinue);
return _result;
})(1000000) === "foo";

View File

@@ -0,0 +1,3 @@
(function f(n) {
return n <= 0 ? "foo" : (doSmth(), getTrueValue() && (getFalseValue() || f(n - 1)));
})(1e6, true) === "foo";

View File

@@ -0,0 +1,30 @@
"use strict";
(function f() {
var _arguments = arguments,
_this = this,
_shouldContinue,
_result;
do {
_shouldContinue = false;
_result = (function (n) {
var _left;
if (n <= 0) {
return "foo";
} else {
doSmth();
if (!(_left = getTrueValue())) {
return _left;
}
if (_left = getFalseValue()) {
return _left;
}
_arguments = [n - 1];
_this = undefined;
return _shouldContinue = true;
}
}).apply(_this, _arguments);
} while (_shouldContinue);
return _result;
})(1000000, true) === "foo";

View File

@@ -0,0 +1,8 @@
(function f(n, /* should be undefined after first pass */ m) {
if (n <= 0) {
return "foo";
}
// Should be clean (undefined) on each pass
var local;
return f(n - 1);
})(1e6, true) === "foo";

View File

@@ -0,0 +1,22 @@
"use strict";
(function f() {
var _arguments = arguments,
_this = this,
_shouldContinue,
_result;
do {
_shouldContinue = false;
_result = (function (n, /* should be undefined after first pass */m) {
if (n <= 0) {
return "foo";
}
// Should be clean (undefined) on each pass
var local;
_arguments = [n - 1];
_this = undefined;
return _shouldContinue = true;
}).apply(_this, _arguments);
} while (_shouldContinue);
return _result;
})(1000000, true) === "foo";

View File

@@ -0,0 +1,39 @@
(function f(n) {
if (n <= 0) {
return "foo";
}
try {
return f(n - 1);
} catch (e) {}
})(1e6) === "foo";
(function f(n) {
if (n <= 0) {
return "foo";
}
try {
throw new Error();
} catch (e) {
return f(n - 1);
}
})(1e6) === "foo";
(function f(n) {
if (n <= 0) {
return "foo";
}
try {
throw new Error();
} catch (e) {
return f(n - 1);
} finally {}
})(1e6) === "foo";
(function f(n) {
if (n <= 0) {
return "foo";
}
try {} finally {
return f(n - 1);
}
})(1e6) === "foo";

View File

@@ -0,0 +1,65 @@
"use strict";
(function f(n) {
if (n <= 0) {
return "foo";
}
try {
return f(n - 1);
} catch (e) {}
})(1000000) === "foo";
(function f() {
var _arguments = arguments,
_this = this,
_shouldContinue,
_result;
do {
_shouldContinue = false;
_result = (function (n) {
if (n <= 0) {
return "foo";
}
try {
throw new Error();
} catch (e) {
_arguments = [n - 1];
_this = undefined;
return _shouldContinue = true;
}
}).apply(_this, _arguments);
} while (_shouldContinue);
return _result;
})(1000000) === "foo";
(function f(n) {
if (n <= 0) {
return "foo";
}
try {
throw new Error();
} catch (e) {
return f(n - 1);
} finally {}
})(1000000) === "foo";
(function f() {
var _arguments = arguments,
_this = this,
_shouldContinue,
_result;
do {
_shouldContinue = false;
_result = (function (n) {
if (n <= 0) {
return "foo";
}
try {} finally {
_arguments = [n - 1];
_this = undefined;
return _shouldContinue = true;
}
}).apply(_this, _arguments);
} while (_shouldContinue);
return _result;
})(1000000) === "foo";