New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parser performance improvements #5322

Merged
merged 5 commits into from Sep 11, 2017
Jump to file or symbol
Failed to load files and symbols.
+220 −98
Diff settings

Always

Just for now

View
@@ -10,6 +10,7 @@ const acorn = require("acorn-dynamic-import").default;
const Tapable = require("tapable");
const json5 = require("json5");
const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
const StackedSetMap = require("./util/StackedSetMap");
function joinRanges(startRange, endRange) {
if(!endRange) return startRange;
@@ -37,6 +38,36 @@ const POSSIBLE_AST_OPTIONS = [{
}
}];
class TrackingSet {
constructor(set) {
this.set = set;
this.set2 = new Set();
this.stack = set.stack;
}
add(item) {
this.set2.add(item);
return this.set.add(item);
}
delete(item) {
this.set2.delete(item);
return this.set.delete(item);
}
has(item) {
return this.set.has(item);
}
createChild() {
return this.set.createChild();
}
getAddedItems() {
return this.set2;
}
}
class Parser extends Tapable {
constructor(options) {
super();
@@ -207,8 +238,8 @@ class Parser extends Tapable {
let res;
let name;
if(expr.argument.type === "Identifier") {
name = this.scope.renames["$" + expr.argument.name] || expr.argument.name;
if(this.scope.definitions.indexOf(name) === -1) {
name = this.scope.renames.get(expr.argument.name) || expr.argument.name;
if(!this.scope.definitions.has(name)) {
res = this.applyPluginsBailResult1("evaluate typeof " + name, expr);
if(res !== undefined) return res;
}
@@ -248,8 +279,8 @@ class Parser extends Tapable {
return new BasicEvaluatedExpression().setString("undefined").setRange(expr.range);
});
this.plugin("evaluate Identifier", function(expr) {
const name = this.scope.renames["$" + expr.name] || expr.name;
if(this.scope.definitions.indexOf(expr.name) === -1) {
const name = this.scope.renames.get(expr.name) || expr.name;
if(!this.scope.definitions.has(expr.name)) {
const result = this.applyPluginsBailResult1("evaluate Identifier " + name, expr);
if(result) return result;
return new BasicEvaluatedExpression().setIdentifier(name).setRange(expr.range);
@@ -258,7 +289,7 @@ class Parser extends Tapable {
}
});
this.plugin("evaluate ThisExpression", function(expr) {
const name = this.scope.renames.$this;
const name = this.scope.renames.get("this");
if(name) {
const result = this.applyPluginsBailResult1("evaluate Identifier " + name, expr);
if(result) return result;
@@ -283,7 +314,7 @@ class Parser extends Tapable {
const param = this.evaluateExpression(expr.callee.object);
if(!param) return;
const property = expr.callee.property.name || expr.callee.property.value;
return this.applyPluginsBailResult("evaluate CallExpression ." + property, expr, param);
return this.applyPluginsBailResult2("evaluate CallExpression ." + property, expr, param);
});
this.plugin("evaluate CallExpression .replace", function(expr, param) {
if(!param.isString()) return;
@@ -625,8 +656,8 @@ class Parser extends Tapable {
// Declarations
prewalkFunctionDeclaration(statement) {
if(statement.id) {
this.scope.renames["$" + statement.id.name] = undefined;
this.scope.definitions.push(statement.id.name);
this.scope.renames.set(statement.id.name, null);
this.scope.definitions.add(statement.id.name);
}
}
@@ -646,20 +677,20 @@ class Parser extends Tapable {
prewalkImportDeclaration(statement) {
const source = statement.source.value;
this.applyPluginsBailResult("import", statement, source);
this.applyPluginsBailResult2("import", statement, source);
statement.specifiers.forEach(function(specifier) {
const name = specifier.local.name;
this.scope.renames["$" + name] = undefined;
this.scope.definitions.push(name);
this.scope.renames.set(name, null);
this.scope.definitions.add(name);
switch(specifier.type) {
case "ImportDefaultSpecifier":
this.applyPluginsBailResult("import specifier", statement, source, "default", name);
this.applyPluginsBailResult4("import specifier", statement, source, "default", name);
break;
case "ImportSpecifier":
this.applyPluginsBailResult("import specifier", statement, source, specifier.imported.name, name);
this.applyPluginsBailResult4("import specifier", statement, source, specifier.imported.name, name);
break;
case "ImportNamespaceSpecifier":
this.applyPluginsBailResult("import specifier", statement, source, null, name);
this.applyPluginsBailResult4("import specifier", statement, source, null, name);
break;
}
}, this);
@@ -669,21 +700,24 @@ class Parser extends Tapable {
let source;
if(statement.source) {
source = statement.source.value;
this.applyPluginsBailResult("export import", statement, source);
this.applyPluginsBailResult2("export import", statement, source);
} else {
this.applyPluginsBailResult1("export", statement);
}
if(statement.declaration) {
if(/Expression$/.test(statement.declaration.type)) {
throw new Error("Doesn't occur?");
} else {
if(!this.applyPluginsBailResult("export declaration", statement, statement.declaration)) {
const pos = this.scope.definitions.length;
if(!this.applyPluginsBailResult2("export declaration", statement, statement.declaration)) {
const originalDefinitions = this.scope.definitions;
const tracker = new TrackingSet(this.scope.definitions);
this.scope.definitions = tracker;
this.prewalkStatement(statement.declaration);
const newDefs = this.scope.definitions.slice(pos);
const newDefs = Array.from(tracker.getAddedItems());
this.scope.definitions = originalDefinitions;
for(let index = newDefs.length - 1; index >= 0; index--) {
const def = newDefs[index];
this.applyPluginsBailResult("export specifier", statement, def, def, index);
this.applyPluginsBailResult4("export specifier", statement, def, def, index);
}
}
}
@@ -696,9 +730,9 @@ class Parser extends Tapable {
{
const name = specifier.exported.name;
if(source)
this.applyPluginsBailResult("export import specifier", statement, source, specifier.local.name, name, specifierIndex);
this.applyPluginsBailResult5("export import specifier", statement, source, specifier.local.name, name, specifierIndex);
else
this.applyPluginsBailResult("export specifier", statement, specifier.local.name, name, specifierIndex);
this.applyPluginsBailResult4("export specifier", statement, specifier.local.name, name, specifierIndex);
break;
}
}
@@ -714,34 +748,37 @@ class Parser extends Tapable {
prewalkExportDefaultDeclaration(statement) {
if(/Declaration$/.test(statement.declaration.type)) {
const pos = this.scope.definitions.length;
const originalDefinitions = this.scope.definitions;
const tracker = new TrackingSet(this.scope.definitions);
this.scope.definitions = tracker;
this.prewalkStatement(statement.declaration);
const newDefs = this.scope.definitions.slice(pos);
const newDefs = Array.from(tracker.getAddedItems());
this.scope.definitions = originalDefinitions;
for(let index = 0, len = newDefs.length; index < len; index++) {
const def = newDefs[index];

This comment has been minimized.

@Kovensky

Kovensky Aug 11, 2017

Collaborator

This can be a for (const def of tracker.getAddedItems()) but I don’t know what performance impact it’d have.

@Kovensky

Kovensky Aug 11, 2017

Collaborator

This can be a for (const def of tracker.getAddedItems()) but I don’t know what performance impact it’d have.

This comment has been minimized.

@sokra

sokra Aug 11, 2017

Member

for(;;) is twice as fast as for(of), even in node 8.3.0.

forEach is 40x slower...

@sokra

sokra Aug 11, 2017

Member

for(;;) is twice as fast as for(of), even in node 8.3.0.

forEach is 40x slower...

this.applyPluginsBailResult("export specifier", statement, def, "default");
this.applyPluginsBailResult3("export specifier", statement, def, "default");
}
}
}
walkExportDefaultDeclaration(statement) {
this.applyPluginsBailResult1("export", statement);
if(/Declaration$/.test(statement.declaration.type)) {
if(!this.applyPluginsBailResult("export declaration", statement, statement.declaration)) {
if(!this.applyPluginsBailResult2("export declaration", statement, statement.declaration)) {
this.walkStatement(statement.declaration);
}
} else {
this.walkExpression(statement.declaration);
if(!this.applyPluginsBailResult("export expression", statement, statement.declaration)) {
this.applyPluginsBailResult("export specifier", statement, statement.declaration, "default");
if(!this.applyPluginsBailResult2("export expression", statement, statement.declaration)) {
this.applyPluginsBailResult3("export specifier", statement, statement.declaration, "default");
}
}
}
prewalkExportAllDeclaration(statement) {
const source = statement.source.value;
this.applyPluginsBailResult("export import", statement, source);
this.applyPluginsBailResult("export import specifier", statement, source, null, null, 0);
this.applyPluginsBailResult2("export import", statement, source);
this.applyPluginsBailResult5("export import specifier", statement, source, null, null, 0);
}
prewalkVariableDeclaration(statement) {
@@ -756,8 +793,8 @@ class Parser extends Tapable {
prewalkClassDeclaration(statement) {
if(statement.id) {
this.scope.renames["$" + statement.id.name] = undefined;
this.scope.definitions.push(statement.id.name);
this.scope.renames.set(statement.id.name, null);
this.scope.definitions.add(statement.id.name);
}
}
@@ -798,9 +835,8 @@ class Parser extends Tapable {
this.enterPattern(declarator.id, (name, decl) => {
if(!this.applyPluginsBailResult1("var-" + declarator.kind + " " + name, decl)) {
if(!this.applyPluginsBailResult1("var " + name, decl)) {
this.scope.renames["$" + name] = undefined;
if(this.scope.definitions.indexOf(name) < 0)
this.scope.definitions.push(name);
this.scope.renames.set(name, null);
this.scope.definitions.add(name);
}
}
});
@@ -819,9 +855,8 @@ class Parser extends Tapable {
if(renameIdentifier && declarator.id.type === "Identifier" && this.applyPluginsBailResult1("can-rename " + renameIdentifier, declarator.init)) {
// renaming with "var a = b;"
if(!this.applyPluginsBailResult1("rename " + renameIdentifier, declarator.init)) {
this.scope.renames["$" + declarator.id.name] = this.scope.renames["$" + renameIdentifier] || renameIdentifier;
const idx = this.scope.definitions.indexOf(declarator.id.name);
if(idx >= 0) this.scope.definitions.splice(idx, 1);
this.scope.renames.set(declarator.id.name, this.scope.renames.get(renameIdentifier) || renameIdentifier);
this.scope.definitions.delete(declarator.id.name);
}
} else {
this.walkPattern(declarator.id);
@@ -979,23 +1014,22 @@ class Parser extends Tapable {
if(expression.left.type === "Identifier" && renameIdentifier && this.applyPluginsBailResult1("can-rename " + renameIdentifier, expression.right)) {
// renaming "a = b;"
if(!this.applyPluginsBailResult1("rename " + renameIdentifier, expression.right)) {
this.scope.renames["$" + expression.left.name] = renameIdentifier;
const idx = this.scope.definitions.indexOf(expression.left.name);
if(idx >= 0) this.scope.definitions.splice(idx, 1);
this.scope.renames.set(expression.left.name, renameIdentifier);
this.scope.definitions.delete(expression.left.name);
}
} else if(expression.left.type === "Identifier") {
if(!this.applyPluginsBailResult1("assigned " + expression.left.name, expression)) {
this.walkExpression(expression.right);
}
this.scope.renames["$" + expression.left.name] = undefined;
this.scope.renames.set(expression.left.name, null);
if(!this.applyPluginsBailResult1("assign " + expression.left.name, expression)) {
this.walkExpression(expression.left);
}
} else {
this.walkExpression(expression.right);
this.walkPattern(expression.left);
this.enterPattern(expression.left, (name, decl) => {
this.scope.renames["$" + name] = undefined;
this.scope.renames.set(name, null);
});
}
}
@@ -1061,13 +1095,13 @@ class Parser extends Tapable {
return !args[idx];
}), () => {
if(renameThis) {
this.scope.renames.$this = renameThis;
this.scope.renames.set("this", renameThis);
}
for(let i = 0; i < args.length; i++) {
const param = args[i];
if(!param) continue;
if(!params[i] || params[i].type !== "Identifier") continue;
this.scope.renames["$" + params[i].name] = param;
this.scope.renames.set(params[i].name, param);
}
if(functionExpression.body.type === "BlockStatement") {
this.prewalkStatement(functionExpression.body);
@@ -1079,7 +1113,7 @@ class Parser extends Tapable {
if(expression.callee.type === "MemberExpression" &&
expression.callee.object.type === "FunctionExpression" &&
!expression.callee.computed &&
(["call", "bind"]).indexOf(expression.callee.property.name) >= 0 &&
(expression.callee.property.name === "call" || expression.callee.property.name === "bind") &&
expression.arguments &&
expression.arguments.length > 0
) {
@@ -1133,8 +1167,8 @@ class Parser extends Tapable {
}
walkIdentifier(expression) {
if(this.scope.definitions.indexOf(expression.name) === -1) {
const result = this.applyPluginsBailResult1("expression " + (this.scope.renames["$" + expression.name] || expression.name), expression);
if(!this.scope.definitions.has(expression.name)) {
const result = this.applyPluginsBailResult1("expression " + (this.scope.renames.get(expression.name) || expression.name), expression);
if(result === true)
return;
}
@@ -1145,23 +1179,23 @@ class Parser extends Tapable {
this.scope = {
inTry: false,
inShorthand: false,
definitions: oldScope.definitions.slice(),
renames: Object.create(oldScope.renames)
definitions: oldScope.definitions.createChild(),
renames: oldScope.renames.createChild()
};
this.scope.renames.$this = undefined;
this.scope.renames.set("this", null);
for(let paramIndex = 0, len = params.length; paramIndex < len; paramIndex++) {
const param = params[paramIndex];
if(typeof param !== "string") {
this.enterPattern(param, param => {
this.scope.renames["$" + param] = undefined;
this.scope.definitions.push(param);
this.scope.renames.set(param, null);
this.scope.definitions.add(param);
});
} else {
this.scope.renames["$" + param] = undefined;
this.scope.definitions.push(param);
this.scope.renames.set(param, null);
this.scope.definitions.add(param);
}
}
@@ -1343,12 +1377,12 @@ class Parser extends Tapable {
const oldComments = this.comments;
this.scope = {
inTry: false,
definitions: [],
renames: {}
definitions: new StackedSetMap(),
renames: new StackedSetMap()
};
const state = this.state = initialState || {};
this.comments = comments;
if(this.applyPluginsBailResult("program", ast, comments) === undefined) {
if(this.applyPluginsBailResult2("program", ast, comments) === undefined) {
this.prewalkStatements(ast.body);
this.walkStatements(ast.body);
}
@@ -1401,11 +1435,11 @@ class Parser extends Tapable {
}
let free;
if(expr.type === "Identifier") {
free = this.scope.definitions.indexOf(expr.name) === -1;
exprName.push(this.scope.renames["$" + expr.name] || expr.name);
} else if(expr.type === "ThisExpression" && this.scope.renames.$this) {
free = !this.scope.definitions.has(expr.name);
exprName.push(this.scope.renames.get(expr.name) || expr.name);
} else if(expr.type === "ThisExpression" && this.scope.renames.get("this")) {
free = true;
exprName.push(this.scope.renames.$this);
exprName.push(this.scope.renames.get("this"));
} else if(expr.type === "ThisExpression") {
free = false;
exprName.push("this");
@@ -1429,3 +1463,4 @@ class Parser extends Tapable {
Parser.ECMA_VERSION = ECMA_VERSION;
module.exports = Parser;
Parser.StackedSetMap = StackedSetMap;
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.