From: Mihai Bazon Date: Mon, 3 Sep 2012 16:38:45 +0000 (+0300) Subject: more optimizations for ifs/conditionals X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=37eecc16a419361672c0c055e0246ee7deb439b7;p=UglifyJS.git more optimizations for ifs/conditionals (XXX: should add tests before anything else) --- diff --git a/lib/compress.js b/lib/compress.js index 139d54a8..91a6c590 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -60,7 +60,7 @@ function Compressor(options, false_by_default) { keep_comps : !false_by_default, drop_debugger : !false_by_default, unsafe : !false_by_default, - ifs : !false_by_default, + conditionals : !false_by_default, comparations : !false_by_default, evaluate : !false_by_default, @@ -88,6 +88,10 @@ function Compressor(options, false_by_default) { return this; }); + AST_Node.DEFMETHOD("optimize", function(){ + return this; + }); + function make_node(ctor, orig, props) { if (!props) props = {}; if (!props.start) props.start = orig.start; @@ -143,7 +147,7 @@ function Compressor(options, false_by_default) { if (stat instanceof AST_Defun) { a.push(stat); } - else if (compressor.option("warnings")) { + else { stat.walk(new TreeWalker(function(node){ if (node instanceof AST_Definitions || node instanceof AST_Defun) { compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start); @@ -254,13 +258,22 @@ function Compressor(options, false_by_default) { node.DEFMETHOD("is_string", func); }); - // function best_of(ast1, ast2) { - // return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1; - // }; + function best_of(ast1, ast2) { + return ast1.print_to_string({ beautify: false }).length > + ast2.print_to_string({ beautify: false }).length + ? ast2 : ast1; + }; // methods to evaluate a constant expression (function (def){ - AST_Node.DEFMETHOD("evaluate", function(compressor, constant, not_constant){ + // The evaluate method returns an array with one or two + // elements. If the node has been successfully reduced to a + // constant, then the second element tells us the value; + // otherwise the second element is missing. The first element + // of the array is always an AST_Node descendant; when + // evaluation was successful it's a node that represents the + // constant; otherwise it's the original node. + AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; try { var val = this._eval(), ast; @@ -290,15 +303,13 @@ function Compressor(options, false_by_default) { type: typeof val })); } - if (constant) return constant(ast, val); - return ast; + return [ ast, val ]; } catch(ex) { if (ex !== def) throw ex; - if (not_constant) return not_constant(this); - return this; + return [ this ]; } }); - function evaluate(node) { + function ev(node) { return node._eval(); }; def(AST_Node, function(){ @@ -310,47 +321,47 @@ function Compressor(options, false_by_default) { def(AST_UnaryPrefix, function(){ var e = this.expression; switch (this.operator) { - case "!": return !evaluate(e); - case "typeof": return typeof evaluate(e); - case "~": return ~evaluate(e); - case "-": return -evaluate(e); - case "+": return +evaluate(e); + case "!": return !ev(e); + case "typeof": return typeof ev(e); + case "~": return ~ev(e); + case "-": return -ev(e); + case "+": return +ev(e); } throw def; }); def(AST_Binary, function(){ var left = this.left, right = this.right; switch (this.operator) { - case "&&" : return evaluate(left) && evaluate(right); - case "||" : return evaluate(left) || evaluate(right); - case "|" : return evaluate(left) | evaluate(right); - case "&" : return evaluate(left) & evaluate(right); - case "^" : return evaluate(left) ^ evaluate(right); - case "+" : return evaluate(left) + evaluate(right); - case "*" : return evaluate(left) * evaluate(right); - case "/" : return evaluate(left) / evaluate(right); - case "%" : return evaluate(left) % evaluate(right); - case "-" : return evaluate(left) - evaluate(right); - case "<<" : return evaluate(left) << evaluate(right); - case ">>" : return evaluate(left) >> evaluate(right); - case ">>>" : return evaluate(left) >>> evaluate(right); - case "==" : return evaluate(left) == evaluate(right); - case "===" : return evaluate(left) === evaluate(right); - case "!=" : return evaluate(left) != evaluate(right); - case "!==" : return evaluate(left) !== evaluate(right); - case "<" : return evaluate(left) < evaluate(right); - case "<=" : return evaluate(left) <= evaluate(right); - case ">" : return evaluate(left) > evaluate(right); - case ">=" : return evaluate(left) >= evaluate(right); - case "in" : return evaluate(left) in evaluate(right); - case "instanceof" : return evaluate(left) instanceof evaluate(right); + case "&&" : return ev(left) && ev(right); + case "||" : return ev(left) || ev(right); + case "|" : return ev(left) | ev(right); + case "&" : return ev(left) & ev(right); + case "^" : return ev(left) ^ ev(right); + case "+" : return ev(left) + ev(right); + case "*" : return ev(left) * ev(right); + case "/" : return ev(left) / ev(right); + case "%" : return ev(left) % ev(right); + case "-" : return ev(left) - ev(right); + case "<<" : return ev(left) << ev(right); + case ">>" : return ev(left) >> ev(right); + case ">>>" : return ev(left) >>> ev(right); + case "==" : return ev(left) == ev(right); + case "===" : return ev(left) === ev(right); + case "!=" : return ev(left) != ev(right); + case "!==" : return ev(left) !== ev(right); + case "<" : return ev(left) < ev(right); + case "<=" : return ev(left) <= ev(right); + case ">" : return ev(left) > ev(right); + case ">=" : return ev(left) >= ev(right); + case "in" : return ev(left) in ev(right); + case "instanceof" : return ev(left) instanceof ev(right); } throw def; }); def(AST_Conditional, function(){ - return evaluate(this.condition) - ? evaluate(this.consequent) - : evaluate(this.alternative); + return ev(this.condition) + ? ev(this.consequent) + : ev(this.alternative); }); })(function(node, func){ node.DEFMETHOD("_eval", func); @@ -371,8 +382,8 @@ function Compressor(options, false_by_default) { throw new Error("Cannot evaluate a statement"); }); def(AST_UnaryPrefix, function(){ - if (this.operator == "!" && this.expression.is_boolean()) - return this.expression(); + if (this.operator == "!") + return this.expression; return basic_negation(this); }); def(AST_Seq, function(compressor){ @@ -498,14 +509,58 @@ function Compressor(options, false_by_default) { self.body = self.body.squeeze(compressor); if (self.alternative) self.alternative = self.alternative.squeeze(compressor); - if (!compressor.option("ifs")) return self; + return compressor.option("conditionals") ? self.optimize(compressor) : self; + }); + + AST_If.DEFMETHOD("optimize", function(compressor){ + // if condition can be statically determined, warn and drop + // one of the blocks. note, statically determined implies + // “has no side effects”; also it doesn't work for cases like + // `x && true`, though it probably should. + var self = this; + var cond = self.condition.evaluate(compressor); + if (cond.length == 2) { + if (cond[1]) { + AST_Node.warn("Condition always true [{line},{col}]", self.condition.start); + return self.body; + } else { + AST_Node.warn("Condition always false [{line},{col}]", self.condition.start); + return self.alternative || new AST_EmptyStatement(self); + } + } + if (self.body instanceof AST_SimpleStatement + && self.alternative instanceof AST_SimpleStatement) { + return make_node(AST_SimpleStatement, self, { + body: make_node(AST_Conditional, self, { + condition : self.condition, + consequent : self.body.body, + alternative : self.alternative.body + }).optimize(compressor) + }); + } + if (!self.alternative && self.body instanceof AST_SimpleStatement) { + 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 + && self.alternative + && !(self.alternative instanceof AST_EmptyStatement)) { + return make_node(AST_SimpleStatement, self, { + body: make_node(AST_Binary, self, { + operator: "||", + left: self.condition, + right: self.alternative.body + }).optimize(compressor) + }); + } return self; }); - AST_If.DEFMETHOD("switch_branches", function(){ - - }); - SQUEEZE(AST_Switch, function(self, compressor){ self = self.clone(); self.expression = self.expression.squeeze(compressor); @@ -599,14 +654,14 @@ function Compressor(options, false_by_default) { SQUEEZE(AST_UnaryPrefix, function(self, compressor){ self = self.clone(); self.expression = self.expression.squeeze(compressor); - return self.evaluate(compressor); + return self.evaluate(compressor)[0]; }); SQUEEZE(AST_Binary, function(self, compressor){ self = self.clone(); self.left = self.left.squeeze(compressor); self.right = self.right.squeeze(compressor); - return self.evaluate(compressor); + return self.evaluate(compressor)[0]; }); SQUEEZE(AST_Assign, function(self, compressor){ @@ -621,7 +676,17 @@ function Compressor(options, false_by_default) { self.condition = self.condition.squeeze(compressor); self.consequent = self.consequent.squeeze(compressor); self.alternative = self.alternative.squeeze(compressor); - return self; + return compressor.option("conditionals") ? self.optimize(compressor) : self; + }); + + AST_Conditional.DEFMETHOD("optimize", function(compressor){ + var self = this; + var rev = self.clone(); + rev.condition = rev.condition.negate(compressor); + var tmp = rev.consequent; + rev.consequent = rev.alternative; + rev.alternative = tmp; + return best_of(self, rev); }); SQUEEZE(AST_Array, function(self, compressor){