diff options
-rw-r--r-- | lib/compress.js | 132 |
1 files changed, 78 insertions, 54 deletions
diff --git a/lib/compress.js b/lib/compress.js index 5c47e13d..6870fa6e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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) { |