Compare commits

...

4 Commits

Author SHA1 Message Date
Sebastian McKenzie
fdda013d07 v3.5.3 2015-02-08 22:17:05 +11:00
Sebastian McKenzie
64477d934d add 3.5.3 changelog 2015-02-08 22:10:00 +11:00
Sebastian McKenzie
45c507056a add back old tail call implementation 2015-02-08 22:09:41 +11:00
Sebastian McKenzie
730b96c195 3.5.2 2015-02-08 21:23:55 +11:00
14 changed files with 478 additions and 54 deletions

View File

@@ -11,6 +11,10 @@
_Note: Gaps between patch versions are faulty/broken releases._
## 3.5.3
* Enable `es6.tailCall` transformer with the first implementation that only works with self referencing calls until we can implement nested tail calls elegantly.
## 3.5.2
* Disable `es6.tailCall` temporairly after reports of it breaking.

View File

@@ -0,0 +1,13 @@
{
var ARGUMENTS_ID = arguments,
THIS_ID = this,
SHOULD_CONTINUE_ID,
RESULT_ID;
do {
SHOULD_CONTINUE_ID = false;
RESULT_ID = FUNCTION.apply(THIS_ID, ARGUMENTS_ID);
} while(SHOULD_CONTINUE_ID);
return RESULT_ID;
}

View File

@@ -0,0 +1,116 @@
"use strict";
var t = require("../../../types");
function transformExpression(node, scope, state) {
if (!node) return;
return (function subTransform(node) {
switch (node.type) {
case "ConditionalExpression":
// any value of ternary operator can be final one
subTransform(node.consequent);
subTransform(node.alternate);
break;
case "LogicalExpression":
// only right expression can be final and so optimized
subTransform(node.right);
break;
case "SequenceExpression":
// only last element of sequence can be optimized
var seq = node.expressions;
subTransform(seq[seq.length - 1]);
break;
case "CallExpression":
var callee = node.callee, thisBinding;
var args = [callee];
// bind `this` to object in member expressions
if (t.isMemberExpression(callee)) {
var object = state.wrapSideEffect(callee.object);
callee.object = object.expr;
thisBinding = object.ref;
}
if (node.arguments.length > 0 || thisBinding) {
args.push(t.arrayExpression(node.arguments));
}
if (thisBinding) {
args.push(thisBinding);
}
node.callee = state.getHelperRef();
node.arguments = args;
break;
}
})(node);
}
var functionChildrenVisitor = {
enter: function (node, parent, scope, state) {
if (t.isReturnStatement(node)) {
// prevent entrance by current visitor
this.skip();
// transform return argument into statement if
// it contains tail recursion
transformExpression(node.argument, scope, state);
} else if (t.isFunction(node)) {
// inner function's bodies are irrelevant
this.skip();
} else if (t.isTryStatement(parent)) {
if (node === parent.block) {
// `try`-blocks can't be optimized
this.skip();
} else if (parent.finalizer && node !== parent.finalizer) {
// `catch` clause followed by `finally` can't be optimized
this.skip();
}
}
}
};
var functionVisitor = {
enter: function (node, parent, scope, state) {
// traverse all child nodes of this function and find `arguments` and `this`
scope.traverse(node, functionChildrenVisitor, state);
return this.skip();
}
};
exports.FunctionDeclaration =
exports.FunctionExpression = function (node, parent, scope, file) {
var tempId, helperRef;
var state = {
ownerId: node.id,
getHelperRef: function () {
return helperRef = helperRef || file.addHelper("tail-call");
},
wrapSideEffect: function (node) {
if (t.isIdentifier(node) || t.isLiteral(node)) {
return {expr: node, ref: node};
}
tempId = tempId || scope.generateUidIdentifier("temp");
return {
expr: t.assignmentExpression("=", tempId, node),
ref: tempId
};
}
};
// traverse the function and look for tail recursion
scope.traverse(node, functionVisitor, state);
if (tempId) {
t.ensureBlock(node).body.unshift(t.variableDeclaration("var", [
t.variableDeclarator(tempId)
]));
}
};

View File

@@ -1,52 +1,118 @@
"use strict";
/*
var util = require("../../../util");
var t = require("../../../types");
function returnBlock(expr) {
return t.blockStatement([t.returnStatement(expr)]);
}
function transformExpression(node, scope, state) {
if (!node) return;
return (function subTransform(node) {
switch (node.type) {
case "ConditionalExpression":
// any value of ternary operator can be final one
subTransform(node.consequent);
subTransform(node.alternate);
break;
var callConsequent = subTransform(node.consequent);
var callAlternate = subTransform(node.alternate);
if (!callConsequent && !callAlternate) {
return;
}
// if ternary operator had tail recursion in value, convert to optimized if-statement
node.type = "IfStatement";
node.consequent = callConsequent ? t.toBlock(callConsequent) : returnBlock(node.consequent);
if (callAlternate) {
node.alternate = t.isIfStatement(callAlternate) ? callAlternate : t.toBlock(callAlternate);
} else {
node.alternate = returnBlock(node.alternate);
}
return node;
case "LogicalExpression":
// only right expression can be final and so optimized
subTransform(node.right);
break;
// only call in right-value of can be optimized
var callRight = subTransform(node.right);
if (!callRight) {
return;
}
// cache left value as it might have side-effects
var leftId = state.getLeftId();
var testExpr = t.assignmentExpression(
"=",
leftId,
node.left
);
if (node.operator === "&&") {
testExpr = t.unaryExpression("!", testExpr);
}
return [t.ifStatement(testExpr, returnBlock(leftId))].concat(callRight);
case "SequenceExpression":
// only last element of sequence can be optimized
var seq = node.expressions;
subTransform(seq[seq.length - 1]);
break;
// only last element can be optimized
var lastCall = subTransform(seq[seq.length - 1]);
if (!lastCall) {
return;
}
// remove converted expression from sequence
// and convert to regular expression if needed
if (--seq.length === 1) {
node = seq[0];
}
return [t.expressionStatement(node)].concat(lastCall);
case "CallExpression":
var callee = node.callee, thisBinding;
var args = [callee];
var callee = node.callee, prop, thisBinding, args;
// bind `this` to object in member expressions
if (t.isMemberExpression(callee)) {
var object = state.wrapSideEffect(callee.object);
callee.object = object.expr;
thisBinding = object.ref;
if (t.isMemberExpression(callee, { computed: false }) &&
t.isIdentifier(prop = callee.property)) {
switch (prop.name) {
case "call":
args = t.arrayExpression(node.arguments.slice(1));
break;
case "apply":
args = node.arguments[1] || t.identifier("undefined");
break;
default:
return;
}
thisBinding = node.arguments[0];
callee = callee.object;
}
if (node.arguments.length > 0 || thisBinding) {
args.push(t.arrayExpression(node.arguments));
// only tail recursion can be optimized as for now
if (!t.isIdentifier(callee) || !scope.bindingEquals(callee.name, state.ownerId)) {
return;
}
if (thisBinding) {
args.push(thisBinding);
}
state.hasTailRecursion = true;
node.callee = state.getHelperRef();
node.arguments = args;
break;
return [
t.expressionStatement(t.assignmentExpression(
"=",
state.getArgumentsId(),
args || t.arrayExpression(node.arguments)
)),
t.expressionStatement(t.assignmentExpression(
"=",
state.getThisId(),
thisBinding || t.identifier("undefined")
)),
t.returnStatement(t.assignmentExpression(
"=",
state.getShouldContinueId(),
t.literal(true)
))
];
}
})(node);
}
@@ -58,17 +124,19 @@ var functionChildrenVisitor = {
this.skip();
// transform return argument into statement if
// it contains tail recursion
transformExpression(node.argument, scope, state);
return transformExpression(node.argument, scope, state);
} else if (t.isFunction(node)) {
// inner function's bodies are irrelevant
this.skip();
return this.skip();
} else if (t.isTryStatement(parent)) {
if (node === parent.block) {
// `try`-blocks can't be optimized
this.skip();
} else if (parent.finalizer && node !== parent.finalizer) {
// `catch` clause followed by `finally` can't be optimized
this.skip();
return this.skip();
} else if (node === parent.finalizer) {
return;
} else {
if (parent.finalizer) {
this.skip();
}
return;
}
}
}
@@ -84,35 +152,56 @@ var functionVisitor = {
};
exports.FunctionDeclaration =
exports.FunctionExpression = function (node, parent, scope, file) {
var tempId, helperRef;
exports.FunctionExpression = function (node, parent, scope) {
// only tail recursion can be optimized as for now,
// so we can skip anonymous functions entirely
var ownerId = node.id;
if (!ownerId) return;
var argumentsId, thisId, shouldContinueId, leftId;
var state = {
ownerId: node.id,
hasTailRecursion: false,
ownerId: ownerId,
getHelperRef: function () {
return helperRef = helperRef || file.addHelper("tail-call");
getArgumentsId: function () {
return argumentsId = argumentsId || scope.generateUidIdentifier("arguments");
},
wrapSideEffect: function (node) {
if (t.isIdentifier(node) || t.isLiteral(node)) {
return {expr: node, ref: node};
}
tempId = tempId || scope.generateUidIdentifier("temp");
return {
expr: t.assignmentExpression("=", tempId, node),
ref: tempId
};
getThisId: function () {
return thisId = thisId || scope.generateUidIdentifier("this");
},
getShouldContinueId: function () {
return shouldContinueId = shouldContinueId || scope.generateUidIdentifier("shouldContinue");
},
getLeftId: function () {
return leftId = leftId || scope.generateUidIdentifier("left");
}
};
// traverse the function and look for tail recursion
scope.traverse(node, functionVisitor, state);
if (tempId) {
t.ensureBlock(node).body.unshift(t.variableDeclaration("var", [
t.variableDeclarator(tempId)
if (!state.hasTailRecursion) return;
var block = t.ensureBlock(node);
if (leftId) {
block.body.unshift(t.variableDeclaration("var", [
t.variableDeclarator(leftId)
]));
}
var resultId = scope.generateUidIdentifier("result");
state.getShouldContinueId();
node.body = util.template("tail-call-body", {
SHOULD_CONTINUE_ID: shouldContinueId,
ARGUMENTS_ID: argumentsId,
RESULT_ID: resultId,
FUNCTION: t.functionExpression(null, node.params, block),
THIS_ID: thisId,
});
};
*/

View File

@@ -1,7 +1,7 @@
{
"name": "6to5",
"description": "Turn ES6 code into readable vanilla ES5 with source maps",
"version": "3.5.2",
"version": "3.5.3",
"author": "Sebastian McKenzie <sebmck@gmail.com>",
"homepage": "https://6to5.org/",
"repository": "6to5/6to5",

View File

@@ -1,7 +1,7 @@
{
"name": "6to5-runtime",
"description": "6to5 selfContained runtime",
"version": "3.5.1",
"version": "3.5.2",
"repository": "6to5/6to5",
"author": "Sebastian McKenzie <sebmck@gmail.com>"
}

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(n) {
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(n) {
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 = getDefaultValue(), /* 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,23 @@
"use strict";
(function f(_x, /* should be undefined after first pass */m) {
var _arguments = arguments,
_this = this,
_shouldContinue,
_result;
do {
_shouldContinue = false;
_result = (function (_x, m) {
var n = arguments[0] === undefined ? getDefaultValue() : arguments[0];
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(n) {
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(n) {
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";