From a83b28503f4673aeb744b45306500584a6480e29 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sun, 23 Sep 2012 12:47:34 +0300 Subject: [PATCH] properly drop mutually-referring declarations that are not otherwise referenced and have no side effects --- lib/ast.js | 8 ++- lib/compress.js | 144 +++++++++++++++++++++++++++++++---------------- lib/scope.js | 23 +++++--- lib/transform.js | 17 +++--- lib/utils.js | 12 ++++ 5 files changed, 137 insertions(+), 67 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 19d9fbcd..3291ed83 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -57,7 +57,9 @@ function DEFNODE(type, props, methods, base) { var proto = base && new base; if (proto && proto.initialize || (methods && methods.initialize)) code += "this.initialize();"; - code += " } }"; + code += " } "; + code += "if (!this.$self) this.$self = this;"; + code += " } "; var ctor = new Function(code)(); if (proto) { ctor.prototype = proto; @@ -89,7 +91,7 @@ function DEFNODE(type, props, methods, base) { var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_before file", { }, null); -var AST_Node = DEFNODE("Node", "start end", { +var AST_Node = DEFNODE("Node", "$self start end", { clone: function() { return new this.CTOR(this); }, @@ -592,7 +594,7 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $documentation: "Base class for all symbols", }); -var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", null, { +var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", }, AST_Symbol); diff --git a/lib/compress.js b/lib/compress.js index 6813925e..a93b39b9 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -64,8 +64,7 @@ function Compressor(options, false_by_default) { evaluate : !false_by_default, booleans : !false_by_default, loops : !false_by_default, - unused_func : !false_by_default, - unused_vars : !false_by_default, + unused : !false_by_default, hoist_funs : !false_by_default, hoist_vars : !false_by_default, if_return : !false_by_default, @@ -816,50 +815,113 @@ function Compressor(options, false_by_default) { return self; }); - AST_Scope.DEFMETHOD("drop_unused_vars", function(compressor){ - if (compressor.option("unused_vars")) { - var self = this; - var tw = new TreeWalker(function(node){ + AST_Scope.DEFMETHOD("drop_unused", function(compressor){ + var self = this; + if (compressor.option("unused") + && !(self instanceof AST_Toplevel) + && !self.uses_eval + ) { + var in_use = []; + // pass 1: find out which symbols are directly used in + // this scope (not in nested scopes). + var scope = this; + var tw = new TreeWalker(function(node, descend){ if (node !== self) { - if (node instanceof AST_Scope) + if (node instanceof AST_Defun) { return true; // don't go in nested scopes - if (node instanceof AST_Definitions) { - if (!(tw.parent() instanceof AST_ForIn)) { - var a = node.definitions; - for (var i = a.length; --i >= 0;) { - var def = a[i]; - var sym = def.name; - if (sym.unreferenced()) { - var warn = { - name: sym.name, - file: sym.start.file, - line: sym.start.line, - col: sym.start.col - }; - if (def.value && def.value.has_side_effects()) { - compressor.warn("Side effects in initialization of unreferenced variable {name} [{file}:{line},{col}]", warn); - } else { - compressor.warn("Dropping unreferenced variable {name} [{file}:{line},{col}]", warn); - a.splice(i, 1); - } - } + } + if (node instanceof AST_Definitions && scope === self) { + node.definitions.forEach(function(def){ + if (def.value && def.value.has_side_effects()) { + def.value.walk(tw); } - } + }); return true; } - if (!(node instanceof AST_Statement)) { - return true; // pointless to visit expressions + if (node instanceof AST_SymbolRef && !(node instanceof AST_LabelRef)) { + push_uniq(in_use, node.definition()); + return true; + } + if (node instanceof AST_Scope) { + var save_scope = scope; + scope = node; + descend(); + scope = save_scope; + return true; } } }); - this.walk(tw); + self.walk(tw); + // pass 2: for every used symbol we need to walk its + // initialization code to figure out if it uses other + // symbols (that may not be in_use). + for (var i = 0; i < in_use.length; ++i) { + in_use[i].orig.forEach(function(decl){ + // undeclared globals will be instanceof AST_SymbolRef + if (decl instanceof AST_SymbolDeclaration) { + decl.init.forEach(function(init){ + var tw = new TreeWalker(function(node){ + if (node instanceof AST_SymbolRef + && node.definition().scope.$self === self.$self) { + push_uniq(in_use, node.definition()); + } + }); + init.walk(tw); + }); + } + }); + } + // pass 3: we should drop declarations not in_use + var tt = new TreeTransformer( + function before(node, descend) { + if (node instanceof AST_Defun && node !== self) { + if (!member(node.name.definition(), in_use)) { + compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { + name : node.name.name, + file : node.name.start.file, + line : node.name.start.line, + col : node.name.start.col + }); + return make_node(AST_EmptyStatement, node); + } + return node; + } + if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { + var def = node.definitions.filter(function(def){ + if (member(def.name.definition(), in_use)) return true; + var w = { + name : def.name.name, + file : def.name.start.file, + line : def.name.start.line, + col : def.name.start.col + }; + if (def.value && def.value.has_side_effects()) { + compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w); + return true; + } + compressor.warn("Dropping unused variable {name} [{file}:{line},{col}]", w); + return false; + }); + if (def.length == 0) { + return make_node(AST_EmptyStatement, node); + } + if (def.length != node.definitions.length) { + node.definitions = def; + return node; + } + } + if (node instanceof AST_Scope && node !== self) + return node; + } + ); + self.transform(tt); } }); AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){ var hoist_funs = compressor.option("hoist_funs"); var hoist_vars = compressor.option("hoist_vars"); - this.drop_unused_vars(compressor); + this.drop_unused(compressor); if (hoist_funs || hoist_vars) { var self = this; var hoisted = []; @@ -1264,7 +1326,7 @@ function Compressor(options, false_by_default) { }); AST_Function.DEFMETHOD("optimize", function(compressor){ - if (compressor.option("unused_func")) { + if (compressor.option("unused")) { if (this.name && this.name.unreferenced()) { this.name = null; } @@ -1272,22 +1334,6 @@ function Compressor(options, false_by_default) { return this; }); - AST_Defun.DEFMETHOD("optimize", function(compressor){ - if (compressor.option("unused_func")) { - if (this.name.unreferenced() - && !(this.parent_scope instanceof AST_Toplevel)) { - compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { - name: this.name.name, - file: this.start.file, - line: this.start.line, - col: this.start.col - }); - return make_node(AST_EmptyStatement, this); - } - } - return this; - }); - SQUEEZE(AST_Call, function(self, compressor){ self = self.clone(); self.expression = self.expression.squeeze(compressor); diff --git a/lib/scope.js b/lib/scope.js index d089b9da..833a96ef 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -101,6 +101,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ delete labels[l.name]; return true; // no descend again } + if (node instanceof AST_SymbolDeclaration) { + node.init_scope_vars(); + } if (node instanceof AST_Symbol) { node.scope = scope; } @@ -130,6 +133,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ } else if (node instanceof AST_SymbolLambda) { scope.def_function(node); + node.init.push(tw.parent()); } else if (node instanceof AST_SymbolDefun) { // Careful here, the scope where this should be defined is @@ -138,9 +142,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ // instanceof AST_Scope) but we get to the symbol a bit // later. (node.scope = scope.parent_scope).def_function(node); + node.init.push(tw.parent()); } else if (node instanceof AST_SymbolVar) { scope.def_variable(node); + var def = tw.parent(); + if (def.value) node.init.push(def); } else if (node instanceof AST_SymbolCatch) { // XXX: this is wrong according to ECMA-262 (12.4). the @@ -233,6 +240,10 @@ AST_SymbolRef.DEFMETHOD("reference", function() { } }); +AST_SymbolDeclaration.DEFMETHOD("init_scope_vars", function(){ + this.init = []; +}); + AST_Label.DEFMETHOD("init_scope_vars", function(){ this.references = []; }); @@ -318,7 +329,7 @@ AST_LoopControl.DEFMETHOD("target", function(){ return this.loopcontrol_target; }); -AST_Toplevel.DEFMETHOD("mangle_names", function(){ +AST_Toplevel.DEFMETHOD("mangle_names", function(sort){ // We only need to mangle declaration nodes. Special logic wired // into the code generator will display the mangled name if it's // present (and for AST_SymbolRef-s it'll use the mangled name of @@ -353,13 +364,9 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(){ }); this.walk(tw); - // strangely, if we try to give more frequently used variables - // shorter name, the size after gzip seems to be higher! so leave - // this commented out I guess... - // - // to_mangle = mergeSort(to_mangle, function(a, b){ - // return b.references.length - a.references.length; - // }); + if (sort) to_mangle = mergeSort(to_mangle, function(a, b){ + return b.references.length - a.references.length; + }); to_mangle.forEach(function(def){ def.mangle() }); }); diff --git a/lib/transform.js b/lib/transform.js index 743dd6e7..df51eac3 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -68,14 +68,17 @@ TreeTransformer.prototype = { node.DEFMETHOD("transform", function(tw, in_list){ var x, y; tw.push(this); - x = tw.before(this, function(){ - descend(x, tw); - }, in_list); + x = tw.before(this, descend, in_list); if (x === undefined) { - x = this.clone(); - descend(x, tw); - y = tw.after(this, in_list); - if (y !== undefined) x = y; + if (!tw.after) { + x = this; + descend(x, tw); + } else { + x = this.clone(); + descend(x, tw); + y = tw.after(this, in_list); + if (y !== undefined) x = y; + } } tw.pop(); return x; diff --git a/lib/utils.js b/lib/utils.js index f8f4102a..72b525b6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -190,3 +190,15 @@ function mergeSort(array, cmp) { }; return _ms(array); }; + +function set_difference(a, b) { + return a.filter(function(el){ + return b.indexOf(el) < 0; + }); +}; + +function set_intersection(a, b) { + return a.filter(function(el){ + return b.indexOf(el) >= 0; + }); +}; -- 2.34.1