opt out of tail recursion optimisation if the owner id has been reassigned - fixes #744

This commit is contained in:
Sebastian McKenzie 2015-02-11 11:27:50 +11:00
parent 56a953df64
commit db93c52182
4 changed files with 46 additions and 2 deletions

View File

@ -64,11 +64,16 @@ TailCallTransformer.prototype.run = function () {
var scope = this.scope; var scope = this.scope;
var node = this.node; var node = this.node;
// only tail recursion can be optimized as for now, // only tail recursion can be optimized as for now, so we can skip anonymous
// so we can skip anonymous functions entirely // functions entirely
var ownerId = this.ownerId; var ownerId = this.ownerId;
if (!ownerId) return; if (!ownerId) return;
// check if the ownerId has been reassigned, if it has then it's not safe to
// perform optimisations
var ownerIdInfo = this.scope.getBindingInfo(ownerId.name);
if (!ownerIdInfo || ownerIdInfo.reassigned) return;
// traverse the function and look for tail recursion // traverse the function and look for tail recursion
scope.traverse(node, firstPass, this); scope.traverse(node, firstPass, this);

View File

@ -229,6 +229,14 @@ Scope.prototype.registerDeclaration = function (node) {
} }
}; };
Scope.prototype.registerBindingReassignment = function (node) {
var ids = t.getBindingIdentifiers(node);
for (var name in ids) {
var info = this.getBindingInfo(name);
if (info) info.reassigned = true;
}
};
Scope.prototype.registerBinding = function (kind, node) { Scope.prototype.registerBinding = function (kind, node) {
if (!kind) throw new ReferenceError("no `kind`"); if (!kind) throw new ReferenceError("no `kind`");
@ -241,6 +249,7 @@ Scope.prototype.registerBinding = function (kind, node) {
this.bindings[name] = { this.bindings[name] = {
typeAnnotation: this.getTypeAnnotation(name, id, node), typeAnnotation: this.getTypeAnnotation(name, id, node),
reassigned: false,
identifier: id, identifier: id,
scope: this, scope: this,
kind: kind kind: kind
@ -302,6 +311,8 @@ var programReferenceVisitor = {
state.addGlobal(node); state.addGlobal(node);
} else if (t.isLabeledStatement(node)) { } else if (t.isLabeledStatement(node)) {
state.addGlobal(node); state.addGlobal(node);
} else if (t.isAssignmentExpression(node)) {
scope.registerBindingReassignment(node);
} }
} }
}; };

View File

@ -0,0 +1,13 @@
// we need to deopt `test` if it's reassigned as we can't be certain of it's
// state, ie. it could have been rebound or dereferenced
function test(exit) {
if (exit) {
return this.x;
}
return test(true);
}
test = test.bind({ x: "yay" });
console.log(test());

View File

@ -0,0 +1,15 @@
"use strict";
// we need to deopt `test` if it's reassigned as we can't be certain of it's
// state, ie. it could have been rebound or dereferenced
function test(exit) {
if (exit) {
return this.x;
}
return test(true);
}
test = test.bind({ x: "yay" });
console.log(test());