Merge branch 'better-tail-recursion'
This commit is contained in:
@@ -1,15 +1,3 @@
|
||||
{
|
||||
var ARGUMENTS_ID = arguments,
|
||||
THIS_ID = this,
|
||||
SHOULD_CONTINUE_ID,
|
||||
RESULT_ID;
|
||||
|
||||
var CALLEE_ID = FUNCTION;
|
||||
|
||||
do {
|
||||
SHOULD_CONTINUE_ID = false;
|
||||
RESULT_ID = CALLEE_ID.apply(THIS_ID, ARGUMENTS_ID);
|
||||
} while(SHOULD_CONTINUE_ID);
|
||||
|
||||
return RESULT_ID;
|
||||
FUNCTION_ID:while (true) BLOCK
|
||||
}
|
||||
|
||||
@@ -73,7 +73,9 @@ exports.Function = function (node, parent, scope, file) {
|
||||
var left = param.left;
|
||||
var right = param.right;
|
||||
|
||||
node.params[i] = scope.generateUidIdentifier("x");
|
||||
var placeholder = scope.generateUidIdentifier("x");
|
||||
placeholder._isDefaultPlaceholder = true;
|
||||
node.params[i] = placeholder;
|
||||
|
||||
if (!state.iife) {
|
||||
if (t.isIdentifier(right) && scope.hasOwnReference(right.name)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
var _ = require("lodash");
|
||||
var util = require("../../../util");
|
||||
var t = require("../../../types");
|
||||
|
||||
@@ -27,7 +28,7 @@ function transformExpression(node, scope, state) {
|
||||
} else {
|
||||
node.alternate = returnBlock(node.alternate);
|
||||
}
|
||||
return node;
|
||||
return [node];
|
||||
|
||||
case "LogicalExpression":
|
||||
// only call in right-value of can be optimized
|
||||
@@ -96,71 +97,137 @@ function transformExpression(node, scope, state) {
|
||||
|
||||
state.hasTailRecursion = true;
|
||||
|
||||
return [
|
||||
t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
state.getArgumentsId(),
|
||||
args || t.arrayExpression(node.arguments)
|
||||
)),
|
||||
var body = [];
|
||||
|
||||
t.expressionStatement(t.assignmentExpression(
|
||||
if (!t.isThisExpression(thisBinding)) {
|
||||
body.push(t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
state.getThisId(),
|
||||
thisBinding || t.identifier("undefined")
|
||||
)),
|
||||
)));
|
||||
}
|
||||
|
||||
t.returnStatement(t.assignmentExpression(
|
||||
"=",
|
||||
state.getShouldContinueId(),
|
||||
t.literal(true)
|
||||
))
|
||||
];
|
||||
if (!args) {
|
||||
args = t.arrayExpression(node.arguments);
|
||||
}
|
||||
|
||||
var argumentsId = state.getArgumentsId();
|
||||
var params = state.getParams();
|
||||
|
||||
body.push(t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
argumentsId,
|
||||
args
|
||||
)));
|
||||
|
||||
var i, param;
|
||||
|
||||
if (t.isArrayExpression(args)) {
|
||||
var elems = args.elements;
|
||||
for (i = 0; i < elems.length && i < params.length; i++) {
|
||||
param = params[i];
|
||||
var elem = elems[i] || (elems[i] = t.identifier("undefined"));
|
||||
if (!param._isDefaultPlaceholder) {
|
||||
elems[i] = t.assignmentExpression("=", param, elem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.setsArguments = true;
|
||||
for (i = 0; i < params.length; i++) {
|
||||
param = params[i];
|
||||
if (!param._isDefaultPlaceholder) {
|
||||
body.push(t.expressionStatement(t.assignmentExpression(
|
||||
"=",
|
||||
param,
|
||||
t.memberExpression(argumentsId, t.literal(i), true)
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.push(t.continueStatement(state.getFunctionId()));
|
||||
|
||||
return body;
|
||||
}
|
||||
})(node);
|
||||
}
|
||||
|
||||
var functionChildrenVisitor = {
|
||||
// Looks for and replaces tail recursion calls.
|
||||
var firstPass = {
|
||||
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
|
||||
return transformExpression(node.argument, scope, state);
|
||||
} else if (t.isFunction(node)) {
|
||||
this.skip();
|
||||
} else if (t.isTryStatement(parent)) {
|
||||
if (node === parent.block) {
|
||||
this.skip();
|
||||
} else if (parent.finalizer && node !== parent.finalizer) {
|
||||
this.skip();
|
||||
}
|
||||
} else if (t.isFunction(node)) {
|
||||
this.skip();
|
||||
} else if (t.isVariableDeclaration(node)) {
|
||||
this.skip();
|
||||
state.vars.push(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var functionVisitor = {
|
||||
// Hoists up function declarations, replaces `this` and `arguments` and
|
||||
// marks them as needed.
|
||||
var secondPass = {
|
||||
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();
|
||||
if (t.isThisExpression(node)) {
|
||||
state.needsThis = true;
|
||||
return state.getThisId();
|
||||
} else if (t.isReferencedIdentifier(node, parent, { name: "arguments" })) {
|
||||
state.needsArguments = true;
|
||||
return state.getArgumentsId();
|
||||
} else if (t.isFunction(node)) {
|
||||
this.skip();
|
||||
if (t.isFunctionDeclaration(node)) {
|
||||
node = t.variableDeclaration("var", [
|
||||
t.variableDeclarator(node.id, t.toExpression(node))
|
||||
]);
|
||||
node._blockHoist = 2;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.FunctionDeclaration =
|
||||
exports.FunctionExpression = function (node, parent, scope) {
|
||||
// Optimizes recursion by removing `this` and `arguments`
|
||||
// if they are not used.
|
||||
var thirdPass = {
|
||||
enter: function (node, parent, scope, state) {
|
||||
if (!t.isExpressionStatement(node)) return;
|
||||
var expr = node.expression;
|
||||
if (!t.isAssignmentExpression(expr)) return;
|
||||
if (!state.needsThis && expr.left === state.getThisId()) {
|
||||
this.remove();
|
||||
} else if (!state.needsArguments && expr.left === state.getArgumentsId() && t.isArrayExpression(expr.right)) {
|
||||
return _.map(expr.right.elements, function (elem) {
|
||||
return t.expressionStatement(elem);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Function = 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 argumentsId, thisId, leftId, functionId, params, paramDecls;
|
||||
|
||||
var state = {
|
||||
hasTailRecursion: false,
|
||||
needsThis: false,
|
||||
needsArguments: false,
|
||||
setsArguments: false,
|
||||
ownerId: ownerId,
|
||||
vars: [],
|
||||
|
||||
getArgumentsId: function () {
|
||||
return argumentsId = argumentsId || scope.generateUidIdentifier("arguments");
|
||||
@@ -170,37 +237,88 @@ exports.FunctionExpression = function (node, parent, scope) {
|
||||
return thisId = thisId || scope.generateUidIdentifier("this");
|
||||
},
|
||||
|
||||
getShouldContinueId: function () {
|
||||
return shouldContinueId = shouldContinueId || scope.generateUidIdentifier("shouldContinue");
|
||||
},
|
||||
|
||||
getLeftId: function () {
|
||||
return leftId = leftId || scope.generateUidIdentifier("left");
|
||||
},
|
||||
|
||||
getFunctionId: function () {
|
||||
return functionId = functionId || scope.generateUidIdentifier("function");
|
||||
},
|
||||
|
||||
getParams: function () {
|
||||
if (!params) {
|
||||
params = node.params;
|
||||
paramDecls = [];
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var param = params[i];
|
||||
if (!param._isDefaultPlaceholder) {
|
||||
paramDecls.push(t.variableDeclarator(
|
||||
param,
|
||||
params[i] = scope.generateUidIdentifier("x")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
};
|
||||
|
||||
// traverse the function and look for tail recursion
|
||||
scope.traverse(node, functionVisitor, state);
|
||||
scope.traverse(node, firstPass, state);
|
||||
|
||||
if (!state.hasTailRecursion) return;
|
||||
|
||||
var block = t.ensureBlock(node);
|
||||
scope.traverse(node, secondPass, state);
|
||||
|
||||
if (leftId) {
|
||||
block.body.unshift(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(leftId)
|
||||
]));
|
||||
if (!state.needsThis || !state.needsArguments) {
|
||||
scope.traverse(node, thirdPass, state);
|
||||
}
|
||||
|
||||
var resultId = scope.generateUidIdentifier("result");
|
||||
state.getShouldContinueId();
|
||||
var body = t.ensureBlock(node).body;
|
||||
|
||||
if (state.vars.length > 0) {
|
||||
body.unshift(t.expressionStatement(
|
||||
_(state.vars)
|
||||
.map(function (decl) {
|
||||
return decl.declarations;
|
||||
})
|
||||
.flatten()
|
||||
.reduceRight(function (expr, decl) {
|
||||
return t.assignmentExpression("=", decl.id, expr);
|
||||
}, t.identifier("undefined"))
|
||||
));
|
||||
}
|
||||
|
||||
if (paramDecls.length > 0) {
|
||||
body.unshift(t.variableDeclaration("var", paramDecls));
|
||||
}
|
||||
|
||||
node.body = util.template("tail-call-body", {
|
||||
SHOULD_CONTINUE_ID: shouldContinueId,
|
||||
ARGUMENTS_ID: argumentsId,
|
||||
RESULT_ID: resultId,
|
||||
CALLEE_ID: scope.generateUidIdentifier("callee"),
|
||||
FUNCTION: t.functionExpression(null, node.params, block),
|
||||
THIS_ID: thisId,
|
||||
THIS_ID: thisId,
|
||||
ARGUMENTS_ID: argumentsId,
|
||||
FUNCTION_ID: state.getFunctionId(),
|
||||
BLOCK: node.body
|
||||
});
|
||||
|
||||
var topVars = [];
|
||||
|
||||
if (state.needsThis) {
|
||||
topVars.push(t.variableDeclarator(state.getThisId(), t.thisExpression()));
|
||||
}
|
||||
|
||||
if (state.needsArguments || state.setsArguments) {
|
||||
var decl = t.variableDeclarator(state.getArgumentsId());
|
||||
if (state.needsArguments) {
|
||||
decl.init = t.identifier("arguments");
|
||||
}
|
||||
topVars.push(decl);
|
||||
}
|
||||
|
||||
if (leftId) {
|
||||
topVars.push(t.variableDeclarator(leftId));
|
||||
}
|
||||
|
||||
if (topVars.length > 0) {
|
||||
node.body.body.unshift(t.variableDeclaration("var", topVars));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,6 +63,8 @@ module.exports = {
|
||||
// needs to be after `es6.blockScoping` due to needing `letReferences` set on blocks
|
||||
"es6.blockScopingTDZ": require("./es6/block-scoping-tdz"),
|
||||
|
||||
// needs to be after `es6.parameters.*` and `es6.blockScoping` due to needing pure
|
||||
// identifiers in parameters and variable declarators
|
||||
"es6.tailCall": require("./es6/tail-call"),
|
||||
|
||||
regenerator: require("./other/regenerator"),
|
||||
|
||||
@@ -408,7 +408,7 @@ Scope.prototype.addBindingToFunctionScope = function (node, kind) {
|
||||
extend(scope.bindings, ids);
|
||||
extend(scope.references, ids);
|
||||
|
||||
if (kind) extend(scope.bindingKinds[kind], ids)
|
||||
if (kind) extend(scope.bindingKinds[kind], ids);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,6 +84,9 @@ exports.sourceMapToComment = function (map) {
|
||||
|
||||
var templateVisitor = {
|
||||
enter: function (node, parent, scope, nodes) {
|
||||
if (t.isExpressionStatement(node)) {
|
||||
node = node.expression;
|
||||
}
|
||||
if (t.isIdentifier(node) && has(nodes, node.name)) {
|
||||
return nodes[node.name];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user