some speedup and more savings from unused vars that have side effects in initialization
authorMihai Bazon <mihai@bazon.net>
Wed, 26 Sep 2012 13:43:14 +0000 (16:43 +0300)
committerMihai Bazon <mihai@bazon.net>
Wed, 26 Sep 2012 16:05:49 +0000 (19:05 +0300)
lib/compress.js

index 5c47e13..6870fa6 100644 (file)
@@ -75,12 +75,26 @@ merge(Compressor.prototype, {
             AST_Node.warn.apply(AST_Node, arguments);
     },
     before: function(node, descend, in_list) {
+        if (node._squeezed) return node;
         node = node.clone();
         if (node instanceof AST_Scope) {
+            node.drop_unused(this);
             node = node.hoist_declarations(this);
         }
         descend(node, this);
         node = node.optimize(this);
+        if (node instanceof AST_Scope) {
+            // dead code removal might leave further unused declarations.
+            // this'll usually save very few bytes, but the performance
+            // hit seems negligible so I'll just drop it here.
+
+            // no point to repeat warnings.
+            var save_warnings = this.options.warnings;
+            this.options.warnings = false;
+            node.drop_unused(this);
+            this.options.warnings = save_warnings;
+        }
+        node._squeezed = true;
         return node;
     }
 });
@@ -89,7 +103,12 @@ merge(Compressor.prototype, {
 
     function OPT(node, optimizer) {
         node.DEFMETHOD("optimize", function(compressor){
-            return optimizer(this, compressor);
+            var self = this;
+            if (self._optimized) return self;
+            var opt = optimizer(self, compressor);
+            opt._optimized = true;
+            if (opt === self) return opt;
+            return opt.transform(compressor);
         });
     };
 
@@ -104,8 +123,10 @@ merge(Compressor.prototype, {
 
     function make_node(ctor, orig, props) {
         if (!props) props = {};
-        if (!props.start) props.start = orig.start;
-        if (!props.end) props.end = orig.end;
+        if (orig) {
+            if (!props.start) props.start = orig.start;
+            if (!props.end) props.end = orig.end;
+        }
         return new ctor(props);
     };
 
@@ -173,9 +194,6 @@ merge(Compressor.prototype, {
                     // step.  nevertheless, it's good to check.
                     continue loop;
                   case stat instanceof AST_If:
-                    // compressor.warn("Current if: {code}", {
-                    //     code: stat.condition.print_to_string()
-                    // });
                     if (stat.body instanceof AST_Return) {
                         //---
                         // pretty silly case, but:
@@ -186,7 +204,7 @@ merge(Compressor.prototype, {
                             CHANGED = true;
                             var cond = make_node(AST_SimpleStatement, stat.condition, {
                                 body: stat.condition
-                            }).optimize(compressor);
+                            });
                             ret.unshift(cond);
                             continue loop;
                         }
@@ -323,7 +341,7 @@ merge(Compressor.prototype, {
                 } else {
                     left = AST_Seq.cons(left, right);
                 }
-                return left.optimize(compressor);
+                return left.transform(compressor);
             };
             var ret = [], prev = null;
             statements.forEach(function(stat){
@@ -478,7 +496,7 @@ merge(Compressor.prototype, {
                   case "string":
                     ast = make_node(AST_String, this, {
                         value: val
-                    });
+                    }).optimize(compressor);
                     break;
                   case "number":
                     ast = make_node(isNaN(val) ? AST_NaN : AST_Number, this, {
@@ -493,7 +511,7 @@ merge(Compressor.prototype, {
                     break;
                   default:
                     if (val === null) {
-                        ast = make_node(AST_Null, this);
+                        ast = make_node(AST_Null, this).optimize(compressor);
                         break;
                     }
                     throw new Error(string_template("Can't handle constant of type: {type}", {
@@ -838,19 +856,43 @@ merge(Compressor.prototype, {
                                 col  : def.name.start.col
                             };
                             if (def.value && def.value.has_side_effects()) {
+                                def._unused_side_effects = true;
                                 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) {
+                        var side_effects = [];
+                        def = mergeSort(def, function(a, b){
+                            if (!a.value && b.value) return -1;
+                            if (!b.value && a.value) return 1;
+                            return 0;
+                        });
+                        while (def.length > 0 && def[def.length - 1]._unused_side_effects) {
+                            side_effects.unshift(def.pop().value);
+                        }
+                        if (side_effects.length > 0) {
+                            side_effects = make_node(AST_BlockStatement, node, {
+                                body: side_effects.map(function(ss){
+                                    return make_node(AST_SimpleStatement, ss, { body: ss });
+                                })
+                            });
+                        } else {
+                            side_effects = null;
+                        }
+                        if (def.length == 0 && !side_effects) {
                             return make_node(AST_EmptyStatement, node);
                         }
-                        if (def.length != node.definitions.length) {
-                            node.definitions = def;
-                            return node;
+                        if (def.length == 0) {
+                            return side_effects;
+                        }
+                        node.definitions = def;
+                        if (side_effects) {
+                            side_effects.body.unshift(node);
+                            node = side_effects;
                         }
+                        return node;
                     }
                     if (node instanceof AST_Scope && node !== self)
                         return node;
@@ -864,7 +906,6 @@ merge(Compressor.prototype, {
         var hoist_funs = compressor.option("hoist_funs");
         var hoist_vars = compressor.option("hoist_vars");
         var self = this;
-        self.drop_unused(compressor);
         if (hoist_funs || hoist_vars) {
             var dirs = [];
             var hoisted = [];
@@ -949,7 +990,7 @@ merge(Compressor.prototype, {
                 if (compressor.option("dead_code")) {
                     var a = [];
                     extract_declarations_from_unreachable_code(compressor, self.body, a);
-                    return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
+                    return make_node(AST_BlockStatement, self, { body: a });
                 }
             } else {
                 return self.body;
@@ -958,21 +999,6 @@ merge(Compressor.prototype, {
         return self;
     });
 
-    // while(cond){ ... } ==> for(;cond;){ ... }
-    //
-    // not helpful, it seems (output is a bit bigger after gzip)
-    //
-    // OPT(AST_While, function(self, compressor){
-    //     var self = AST_DWLoop.prototype.optimize.call(self, compressor);
-    //     if (self instanceof AST_While) {
-    //         self = make_node(AST_For, self, {
-    //             condition: self.condition,
-    //             body: self.body
-    //         }).optimize(compressor);
-    //     }
-    //     return self;
-    // });
-
     OPT(AST_For, function(self, compressor){
         var cond = self.condition;
         if (cond) {
@@ -1030,13 +1056,13 @@ merge(Compressor.prototype, {
             }
         }
         if (is_empty(self.alternative)) self.alternative = null;
-        var negated = self.condition.negate(compressor).optimize(compressor);
+        var negated = self.condition.negate(compressor);
         var negated_is_best = best_of(self.condition, negated) === negated;
         if (self.alternative && negated_is_best) {
             negated_is_best = false; // because we already do the switch here.
             self.condition = negated;
             var tmp = self.body;
-            self.body = self.alternative || new AST_EmptyStatement();
+            self.body = self.alternative || make_node(AST_EmptyStatement);
             self.alternative = tmp;
         }
         if (is_empty(self.body) && is_empty(self.alternative)) {
@@ -1051,7 +1077,7 @@ merge(Compressor.prototype, {
                     condition   : self.condition,
                     consequent  : self.body.body,
                     alternative : self.alternative.body
-                }).optimize(compressor)
+                })
             });
         }
         if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
@@ -1060,14 +1086,14 @@ merge(Compressor.prototype, {
                     operator : "||",
                     left     : negated,
                     right    : self.body.body
-                }).optimize(compressor)
+                })
             });
             return make_node(AST_SimpleStatement, self, {
                 body: make_node(AST_Binary, self, {
                     operator : "&&",
                     left     : self.condition,
                     right    : self.body.body
-                }).optimize(compressor)
+                })
             });
         }
         if (self.body instanceof AST_EmptyStatement
@@ -1078,7 +1104,7 @@ merge(Compressor.prototype, {
                     operator : "||",
                     left     : self.condition,
                     right    : self.alternative.body
-                }).optimize(compressor)
+                })
             });
         }
         if (self.body instanceof AST_Exit
@@ -1089,7 +1115,7 @@ merge(Compressor.prototype, {
                     condition   : self.condition,
                     consequent  : self.body.value,
                     alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor)
-                }).optimize(compressor)
+                })
             });
         }
         if (self.body instanceof AST_If
@@ -1099,7 +1125,7 @@ merge(Compressor.prototype, {
                 operator: "&&",
                 left: self.condition,
                 right: self.body.condition
-            }).optimize(compressor);
+            });
             self.body = self.body.body;
         }
         if (aborts(self.body)) {
@@ -1108,17 +1134,17 @@ merge(Compressor.prototype, {
                 self.alternative = null;
                 return make_node(AST_BlockStatement, self, {
                     body: [ self, alt ]
-                }).optimize(compressor);
+                });
             }
         }
         if (aborts(self.alternative)) {
             var body = self.body;
             self.body = self.alternative;
-            self.condition = negated_is_best ? negated : self.condition.negate(compressor).optimize(compressor);
+            self.condition = negated_is_best ? negated : self.condition.negate(compressor);
             self.alternative = null;
             return make_node(AST_BlockStatement, self, {
                 body: [ self, body ]
-            }).optimize(compressor);
+            });
         }
         return self;
     });
@@ -1199,14 +1225,14 @@ merge(Compressor.prototype, {
                     if (self.args.length != 1) {
                         return make_node(AST_Array, self, {
                             elements: self.args
-                        }).optimize(compressor);
+                        });
                     }
                     break;
                   case "Object":
                     if (self.args.length == 0) {
                         return make_node(AST_Object, self, {
                             properties: []
-                        }).optimize(compressor);
+                        });
                     }
                     break;
                   case "String":
@@ -1238,7 +1264,7 @@ merge(Compressor.prototype, {
                   case "Function":
                   case "Error":
                   case "Array":
-                    return make_node(AST_Call, self, self).optimize(compressor);
+                    return make_node(AST_Call, self, self);
                 }
             }
         }
@@ -1247,8 +1273,6 @@ merge(Compressor.prototype, {
 
     OPT(AST_Seq, function(self, compressor){
         if (compressor.option("cascade")) {
-            if (self.cdr instanceof AST_Seq)
-                self.cdr = self.cdr.optimize(compressor);
             if (self.car instanceof AST_Assign
                 && !self.car.left.has_side_effects()
                 && self.car.left.equivalent_to(self.cdr)) {
@@ -1277,7 +1301,7 @@ merge(Compressor.prototype, {
                 // typeof always returns a non-empty string, thus it's
                 // always true in booleans
                 compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
-                return make_node(AST_True, self).optimize(compressor);
+                return make_node(AST_True, self);
             }
         }
         if (e instanceof AST_Binary) {
@@ -1327,7 +1351,7 @@ merge(Compressor.prototype, {
             var rr = self.right.evaluate(compressor), right = rr[0];
             if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) {
                 compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
-                return make_node(AST_False, self).optimize(compressor);
+                return make_node(AST_False, self);
             }
             if (ll.length > 1 && ll[1]) {
                 return rr[0];
@@ -1341,7 +1365,7 @@ merge(Compressor.prototype, {
             var rr = self.right.evaluate(compressor), right = rr[0];
             if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) {
                 compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
-                return make_node(AST_True, self).optimize(compressor);
+                return make_node(AST_True, self);
             }
             if (ll.length > 1 && !ll[1]) {
                 return rr[0];
@@ -1356,7 +1380,7 @@ merge(Compressor.prototype, {
             if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1]) ||
                 (rr.length > 1 && rr[0] instanceof AST_String && rr[1])) {
                 compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
-                return make_node(AST_True, self).optimize(compressor);
+                return make_node(AST_True, self);
             }
             break;
         }
@@ -1390,9 +1414,9 @@ merge(Compressor.prototype, {
     OPT(AST_SymbolRef, function(self, compressor){
         if (self.undeclared()) switch (self.name) {
           case "undefined":
-            return make_node(AST_Undefined, self).optimize(compressor);
+            return make_node(AST_Undefined, self);
           case "NaN":
-            return make_node(AST_NaN, self).optimize(compressor);
+            return make_node(AST_NaN, self);
         }
         return self;
     });
@@ -1433,7 +1457,7 @@ merge(Compressor.prototype, {
         if (self.condition instanceof AST_Seq) {
             var car = self.condition.car;
             self.condition = self.condition.cdr;
-            return AST_Seq.cons(car, self.optimize(compressor)).optimize(compressor);
+            return AST_Seq.cons(car, self);
         }
         var cond = self.condition.evaluate(compressor);
         if (cond.length > 1) {