properly drop mutually-referring declarations that are not otherwise
authorMihai Bazon <mihai@bazon.net>
Sun, 23 Sep 2012 09:47:34 +0000 (12:47 +0300)
committerMihai Bazon <mihai@bazon.net>
Sun, 23 Sep 2012 09:47:34 +0000 (12:47 +0300)
referenced and have no side effects

lib/ast.js
lib/compress.js
lib/scope.js
lib/transform.js
lib/utils.js

index 19d9fbc..3291ed8 100644 (file)
@@ -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);
 
index 6813925..a93b39b 100644 (file)
@@ -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);
index d089b9d..833a96e 100644 (file)
@@ -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() });
 });
index 743dd6e..df51eac 100644 (file)
@@ -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;
index f8f4102..72b525b 100644 (file)
@@ -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;
+    });
+};