From d72c1d1293487b7e775d0a248f8c9c848b01502c Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 13 Sep 2012 15:20:57 +0300 Subject: [PATCH] few more optimizations: - do multiple passes in tighten_body if it was changed - transform if (foo) return x; return y; ==> return foo?x:y - don't optimize !0 as true (use best_of after evaluation of constant expr) With hoist_vars off we now beat UglifyJS v1 on jQuery-1.8.1 --- lib/compress.js | 237 +++++++++++++++++++--------------- test/compress/conditionals.js | 2 +- 2 files changed, 135 insertions(+), 104 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index efdc6d83..15eab988 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -67,6 +67,7 @@ function Compressor(options, false_by_default) { unused_func : !false_by_default, hoist_funs : !false_by_default, hoist_vars : !false_by_default, + if_return : !false_by_default, warnings : true }); @@ -93,6 +94,7 @@ function Compressor(options, false_by_default) { push_node : function(node) { stack.push(node) }, pop_node : function() { return stack.pop() }, stack : function() { return stack }, + self : function() { return stack[stack.length - 1] }, parent : function(n) { return stack[stack.length - 2 - (n || 0)]; }, @@ -122,9 +124,9 @@ function Compressor(options, false_by_default) { }; function SQUEEZE(nodetype, squeeze) { - nodetype.DEFMETHOD("squeeze", function(compressor, block, index){ + nodetype.DEFMETHOD("squeeze", function(compressor){ compressor.push_node(this); - var new_node = squeeze(this, compressor, block, index); + var new_node = squeeze(this, compressor); compressor.pop_node(); return new_node !== undefined ? new_node : this; }); @@ -156,92 +158,146 @@ function Compressor(options, false_by_default) { }; function tighten_body(statements, compressor) { + var CHANGED; statements = do_list(statements, compressor, true); - if (compressor.option("dead_code")) { - statements = eliminate_dead_code(statements, compressor); - } - if (compressor.option("sequences")) { - statements = sequencesize(statements, compressor); - } + do { + CHANGED = false; + if (compressor.option("dead_code")) { + statements = eliminate_dead_code(statements, compressor); + } + if (compressor.option("sequences")) { + statements = sequencesize(statements, compressor); + } + if (compressor.option("if_return")) { + statements = handle_if_return(statements, compressor); + } + } while (CHANGED); return statements; - }; - function extract_declarations_from_unreachable_code(compressor, stat, target) { - warn_dead_code(stat); - 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); - if (node instanceof AST_Definitions) { - node = node.clone(); - node.remove_initializers(); - target.push(node); + function handle_if_return(statements, compressor) { + var in_lambda = compressor.self() instanceof AST_Lambda; + var last = statements.length - 1; + return MAP(statements, function(stat, i){ + if (stat instanceof AST_If + && stat.body instanceof AST_Return + && !stat.body.value + && !stat.alternative + && in_lambda) { + CHANGED = true; + if (i < last) { + var rest = statements.slice(i + 1); + var cond = stat.condition; + while (rest[0] instanceof AST_If + && rest[0].body instanceof AST_Return + && !rest[0].alternative) { + cond = make_node(AST_Binary, rest[0], { + operator: "||", + left: cond, + right: rest[0].condition + }); + rest.shift(); + } + return MAP.last(make_node(AST_If, stat, { + condition: cond.negate(compressor), + body: make_node(AST_BlockStatement, stat, { + body: rest + }).optimize(compressor) + }).optimize(compressor)); + } else { + return make_node(AST_SimpleStatement, stat, { + body: stat.condition + }).optimize(compressor); + } } - else if (node instanceof AST_Defun) { - target.push(node); + if (stat instanceof AST_If + && stat.body instanceof AST_Return + && stat.body.value + && !stat.alternative + && i < last + && statements[i + 1] instanceof AST_Return + && statements[i + 1].value) { + CHANGED = true; + return MAP.last(make_node(AST_If, stat, { + condition: stat.condition, + body: stat.body, + alternative: statements[i + 1] + }).optimize(compressor)); } - return true; // no point to descend - } - if (node instanceof AST_Scope) { - // also don't descend any other nested scopes - return true; - } - })); - }; + return stat; + }); + }; - function eliminate_dead_code(statements, compressor) { - var has_quit = false; - return statements.reduce(function(a, stat){ - if (has_quit) { - if (stat instanceof AST_Defun) { + function eliminate_dead_code(statements, compressor) { + var has_quit = false; + return statements.reduce(function(a, stat){ + if (has_quit) { + extract_declarations_from_unreachable_code(compressor, stat, a); + } else { a.push(stat); + if (stat instanceof AST_Jump) { + has_quit = true; + } + } + return a; + }, []); + }; + + // XXX: this is destructive -- it modifies tree nodes. + function sequencesize(statements) { + var prev = null, last = statements.length - 1; + if (last) statements = statements.reduce(function(a, cur, i){ + if (prev instanceof AST_SimpleStatement + && cur instanceof AST_SimpleStatement) { + CHANGED = true; + var seq = make_node(AST_Seq, prev, { + first: prev.body, + second: cur.body + }); + prev.body = seq; + } + else if (i == last + && cur instanceof AST_Exit && cur.value + && a.length > 0 + && prev instanceof AST_SimpleStatement) { + CHANGED = true; + var seq = make_node(AST_Seq, prev, { + first: prev.body, + second: cur.value + }); + cur.value = seq; + a.pop(); + a.push(cur); + return a; } else { - extract_declarations_from_unreachable_code(compressor, stat, a); - }; - } else { - a.push(stat); - if (stat instanceof AST_Jump) { - has_quit = true; + a.push(cur); + prev = cur; } - } - return a; - }, []); + return a; + }, []); + return statements; + }; + }; - // XXX: this is destructive -- it modifies tree nodes. - function sequencesize(statements) { - var prev = null, last = statements.length - 1; - if (last) statements = statements.reduce(function(a, cur, i){ - if (prev instanceof AST_SimpleStatement - && cur instanceof AST_SimpleStatement) { - var seq = make_node(AST_Seq, prev, { - first: prev.body, - second: cur.body - }); - prev.body = seq; + function extract_declarations_from_unreachable_code(compressor, stat, target) { + warn_dead_code(stat); + stat.walk(new TreeWalker(function(node){ + if (node instanceof AST_Definitions) { + compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start); + node = node.clone(); + node.remove_initializers(); + target.push(node); + return true; } - else if (i == last - && cur instanceof AST_Exit && cur.value - && a.length > 0 - && prev instanceof AST_SimpleStatement) { - // it only makes sense to do this transformation - // if the AST gets to a single statement. - var seq = make_node(AST_Seq, prev, { - first: prev.body, - second: cur.value - }); - cur.value = seq; - a.pop(); - a.push(cur); - return a; + if (node instanceof AST_Defun) { + target.push(node); + return true; } - else { - a.push(cur); - prev = cur; + if (node instanceof AST_Scope) { + return true; } - return a; - }, []); - return statements; + })); }; /* -----[ boolean/negation helpers ]----- */ @@ -337,7 +393,7 @@ function Compressor(options, false_by_default) { type: typeof val })); } - return [ ast, val ]; + return [ best_of(ast, this), val ]; } catch(ex) { if (ex !== def) throw ex; return [ this ]; @@ -432,8 +488,7 @@ function Compressor(options, false_by_default) { var self = this.clone(); self.consequent = self.consequent.negate(compressor); self.alternative = self.alternative.negate(compressor); - //return best_of(basic_negation(this), self); - return self; + return best_of(basic_negation(this), self); }); def(AST_Binary, function(compressor){ var self = this.clone(), op = this.operator; @@ -723,16 +778,16 @@ function Compressor(options, false_by_default) { return self; }); - SQUEEZE(AST_If, function(self, compressor, block, index){ + SQUEEZE(AST_If, function(self, compressor){ self = self.clone(); self.condition = self.condition.squeeze(compressor); self.body = self.body.squeeze(compressor); if (self.alternative) self.alternative = self.alternative.squeeze(compressor); - return self.optimize(compressor, block, index); + return self.optimize(compressor); }); - AST_If.DEFMETHOD("optimize", function(compressor, block, index){ + AST_If.DEFMETHOD("optimize", function(compressor){ var self = this; if (!compressor.option("conditionals")) return self; // if condition can be statically determined, warn and drop @@ -827,29 +882,6 @@ function Compressor(options, false_by_default) { }).optimize(compressor) }); } - if (self.body instanceof AST_Return - && !self.body.value - && !self.alternative - && index < block.length - 1) { - if (compressor.parent() instanceof AST_Lambda) { - var rest = tighten_body(block.slice(index + 1), compressor); - var cond = self.condition; - while (rest[0] instanceof AST_If && rest[0].body instanceof AST_Return && !rest[0].alternative) { - cond = make_node(AST_Binary, rest[0], { - operator: "||", - left: cond, - right: rest[0].condition - }); - rest.shift(); - } - return MAP.last(make_node(AST_If, self, { - condition: cond.negate(compressor), - body: make_node(AST_BlockStatement, block[index + 1], { - body: rest - }).optimize(compressor) - }).optimize(compressor)); - } - } if (self.body instanceof AST_If && !self.body.alternative && !self.alternative) { @@ -1081,7 +1113,6 @@ function Compressor(options, false_by_default) { }); SQUEEZE(AST_UnaryPrefix, function(self, compressor){ - // need to determine the context before cloning the node self = self.clone(); self.expression = self.expression.squeeze(compressor); return self.optimize(compressor); diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 54c0751d..92e5d61b 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -90,7 +90,7 @@ ifs_4: { ifs_5: { options = { - conditionals: true + if_return: true }; input: { function f() { -- 2.34.1