From: Mihai Bazon Date: Sun, 16 Sep 2012 12:46:20 +0000 (+0300) Subject: rewrite handle_if_return X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=7b6a402916fe697049c38f6ee89c644fcf93c8d4;p=UglifyJS.git rewrite handle_if_return optimizations of if/return/continue seem to be even better now --- diff --git a/lib/compress.js b/lib/compress.js index f8f4d17f..2f286545 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -156,7 +156,7 @@ function Compressor(options, false_by_default) { function eliminate_spurious_blocks(statements) { return statements.reduce(function(a, stat){ if (stat instanceof AST_BlockStatement) { - a.push.apply(a, stat.body); + a.push.apply(a, eliminate_spurious_blocks(stat.body)); } else if (!(stat instanceof AST_EmptyStatement)) { a.push(stat); } @@ -164,6 +164,21 @@ function Compressor(options, false_by_default) { }, []); }; + function as_statement_array(thing) { + if (thing === null) return []; + if (thing instanceof AST_BlockStatement) return thing.body; + if (thing instanceof AST_EmptyStatement) return []; + if (thing instanceof AST_StatementBase) return [ thing ]; + throw new Error("Can't convert thing to statement array"); + }; + + function is_empty(thing) { + if (thing === null) return true; + if (thing instanceof AST_EmptyStatement) return true; + if (thing instanceof AST_BlockStatement) return thing.body.length == 0; + return false; + }; + function tighten_body(statements, compressor) { var CHANGED; statements = do_list(statements, compressor, true); @@ -181,12 +196,130 @@ function Compressor(options, false_by_default) { if (compressor.option("join_vars")) { statements = join_consecutive_vars(statements, compressor); } + statements = eliminate_spurious_blocks(statements); } while (CHANGED); return statements; + function handle_if_return(statements, compressor) { + var self = compressor.self(); + var in_lambda = self instanceof AST_Lambda; + var last = statements.length - 1; + var ret = []; + loop: for (var i = statements.length; --i >= 0;) { + var stat = statements[i]; + switch (true) { + case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0): + CHANGED = true; + // note, ret.length is probably always zero + // because we drop unreachable code before this + // step. nevertheless, it's good to check. + continue loop; + case stat instanceof AST_If: + if (stat.body instanceof AST_Return) { + //--- + // pretty silly case, but: + // if (foo()) return; return; ==> foo(); return; + if (((in_lambda && ret.length == 0) + || (ret[0] instanceof AST_Return && !ret[0].value)) + && !stat.body.value && !stat.alternative) { + CHANGED = true; + var cond = make_node(AST_SimpleStatement, stat.condition, { + body: stat.condition + }).optimize(compressor); + ret.unshift(cond); + continue loop; + } + //--- + // if (foo()) return x; return y; ==> return foo() ? x : y; + if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) { + CHANGED = true; + stat = stat.clone(); + stat.alternative = ret[0]; + ret[0] = stat.squeeze(compressor); + continue loop; + } + //--- + // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; + if ((ret.length == 0 || ret[0] instanceof AST_Return) && stat.body.value && !stat.alternative && in_lambda) { + CHANGED = true; + stat = stat.clone(); + stat.alternative = ret[0] || make_node(AST_Return, stat, { + value: make_node(AST_Undefined, stat) + }); + ret[0] = stat.squeeze(compressor); + continue loop; + } + //--- + // if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... } + if (!stat.body.value && in_lambda) { + CHANGED = true; + stat = stat.clone(); + stat.condition = stat.condition.negate(compressor); + stat.body = make_node(AST_BlockStatement, stat, { + body: as_statement_array(stat.alternative).concat(ret) + }); + stat.alternative = null; + ret = [ stat.squeeze(compressor) ]; + continue loop; + } + //--- + if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement + && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) { + CHANGED = true; + ret.push(make_node(AST_Return, ret[0], { + value: make_node(AST_Undefined, ret[0]) + }).squeeze(compressor)); + ret = as_statement_array(stat.alternative).concat(ret); + ret.unshift(stat); + continue loop; + } + } + + var ab = aborts(stat.body); + if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) + || (ab instanceof AST_Continue && self === ab.target()))) { + CHANGED = true; + var body = tighten_body(as_statement_array(stat.body).slice(0, -1), compressor); + stat = stat.clone(); + stat.condition = stat.condition.negate(compressor); + stat.body = make_node(AST_BlockStatement, stat, { + body: ret + }); + stat.alternative = make_node(AST_BlockStatement, stat, { + body: body + }); + ret = [ stat.squeeze(compressor) ]; + continue loop; + } + + var ab = aborts(stat.alternative); + if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) + || (ab instanceof AST_Continue && self === ab.target()))) { + CHANGED = true; + stat = stat.clone(); + stat.body = make_node(AST_BlockStatement, stat.body, { + body: tighten_body(as_statement_array(stat.body).concat(ret), compressor) + }); + stat.alternative = make_node(AST_BlockStatement, stat.alternative, { + body: tighten_body(as_statement_array(stat.alternative).slice(0, -1), compressor) + }); + ret = [ stat.squeeze(compressor) ]; + continue loop; + } + + ret.unshift(stat); + break; + default: + ret.unshift(stat); + break; + } + } + return ret; + }; + /// XXX: this function is UGLY and kinda wrong. /// I think it would be cleaner if it operates backwards. - function handle_if_return(statements, compressor) { + function handle_if_return_2(statements, compressor) { var self = compressor.self(); var in_lambda = self instanceof AST_Lambda; var last = statements.length - 1; @@ -677,12 +810,15 @@ function Compressor(options, false_by_default) { }); // tell me if a statement aborts + function aborts(thing) { + return thing && thing.aborts(); + }; (function(def){ def(AST_StatementBase, function(){ return null }); def(AST_Jump, function(){ return this }); def(AST_BlockStatement, function(){ var n = this.body.length; - return n > 0 && this.body[n - 1].aborts(); + return n > 0 && aborts(this.body[n - 1]); }); })(function(node, func){ node.DEFMETHOD("aborts", func); @@ -938,6 +1074,7 @@ function Compressor(options, false_by_default) { } } } + if (is_empty(self.alternative)) self.alternative = null; var negated = self.condition.negate(compressor); var negated_is_best = best_of(self.condition, negated) === negated; if (self.alternative && negated_is_best) { @@ -947,8 +1084,7 @@ function Compressor(options, false_by_default) { self.body = self.alternative || new AST_EmptyStatement(); self.alternative = tmp; } - if (self.body instanceof AST_EmptyStatement - && self.alternative instanceof AST_EmptyStatement) { + if (is_empty(self.body) && is_empty(self.alternative)) { return make_node(AST_SimpleStatement, self.condition, { body: self.condition }); @@ -963,9 +1099,7 @@ function Compressor(options, false_by_default) { }).optimize(compressor) }); } - if ((!self.alternative - || self.alternative instanceof AST_EmptyStatement) - && self.body instanceof AST_SimpleStatement) { + if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (negated_is_best) return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "||", @@ -999,7 +1133,7 @@ function Compressor(options, false_by_default) { value: make_node(AST_Conditional, self, { condition : self.condition, consequent : self.body.value, - alternative : self.alternative.value + alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor) }).optimize(compressor) }); } @@ -1010,11 +1144,10 @@ function Compressor(options, false_by_default) { operator: "&&", left: self.condition, right: self.body.condition - }); + }).optimize(compressor); self.body = self.body.body; } - var abort = self.body.aborts(); - if (abort) { + if (aborts(self.body)) { if (self.alternative) { var alt = self.alternative; self.alternative = null; @@ -1023,6 +1156,15 @@ function Compressor(options, false_by_default) { }).optimize(compressor); } } + if (aborts(self.alternative)) { + var body = self.body; + self.body = self.alternative; + 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; }); @@ -1125,7 +1267,7 @@ function Compressor(options, false_by_default) { return self.optimize(compressor); }); - AST_Lambda.DEFMETHOD("optimize", function(compressor){ + AST_Function.DEFMETHOD("optimize", function(compressor){ if (compressor.option("unused_func")) { if (this.name && this.name.unreferenced()) { this.name = null; @@ -1324,6 +1466,19 @@ function Compressor(options, false_by_default) { if (this.operator.length == 2) this.operator += "="; } break; + case "&&": + case "||": + if (this.left instanceof AST_UnaryPrefix && this.left.operator == "!" + && this.right instanceof AST_UnaryPrefix && this.right.operator == "!") { + this.left = this.left.expression; + this.right = this.right.expression; + this.operator = this.operator == "&&" ? "||" : "&&"; + return make_node(AST_UnaryPrefix, this, { + operator: "!", + expression: this + }).optimize(compressor); + } + break; } if (compressor.option("booleans") && compressor.in_boolean_context()) switch (this.operator) { case "&&": @@ -1435,16 +1590,16 @@ function Compressor(options, false_by_default) { }); AST_Undefined.DEFMETHOD("optimize", function(compressor){ - if (compressor.option("unsafe") && !(compressor.parent() instanceof AST_Array)) { - return make_node(AST_Sub, this, { - expression: make_node(AST_Array, this, { - elements: [] - }), - property: make_node(AST_Number, this, { - value: 0 - }) - }); - } + // if (compressor.option("unsafe") && !(compressor.parent() instanceof AST_Array)) { + // return make_node(AST_Sub, this, { + // expression: make_node(AST_Array, this, { + // elements: [] + // }), + // property: make_node(AST_Number, this, { + // value: 0 + // }) + // }); + // } return this; }); diff --git a/lib/output.js b/lib/output.js index 602fa608..96221599 100644 --- a/lib/output.js +++ b/lib/output.js @@ -892,6 +892,7 @@ function OutputStream(options) { DEFPRINT(AST_Undefined, function(self, output){ // XXX: should add more options for this output.print("void 0"); + //output.print("[][0]"); }); DEFPRINT(AST_This, function(self, output){ output.print("this"); diff --git a/lib/utils.js b/lib/utils.js index 6f8334ee..f8f4102a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -109,23 +109,23 @@ function defaults(args, defs) { function noop() {}; var MAP = (function(){ - function MAP(a, f, o) { + function MAP(a, f, backwards) { var ret = [], top = [], i; function doit() { - var val = f.call(o, a[i], i); + var val = f(a[i], i); var is_last = val instanceof Last; if (is_last) val = val.v; if (val instanceof AtTop) { val = val.v; if (val instanceof Splice) { - top.push.apply(top, val.v); + top.push.apply(top, backwards ? val.v.slice().reverse() : val.v); } else { top.push(val); } } else if (val !== skip) { if (val instanceof Splice) { - ret.push.apply(ret, val.v); + ret.push.apply(ret, backwards ? val.v.slice().reverse() : val.v); } else { ret.push(val); } @@ -133,7 +133,13 @@ var MAP = (function(){ return is_last; }; if (a instanceof Array) { - for (i = 0; i < a.length; ++i) if (doit()) break; + if (backwards) { + for (i = a.length; --i >= 0;) if (doit()) break; + ret.reverse(); + top.reverse(); + } else { + for (i = 0; i < a.length; ++i) if (doit()) break; + } } else { for (i in a) if (HOP(a, i)) if (doit()) break; diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 92e5d61b..4dc27b85 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -90,7 +90,9 @@ ifs_4: { ifs_5: { options = { - if_return: true + if_return: true, + conditionals: true, + comparations: true, }; input: { function f() { @@ -117,7 +119,7 @@ ifs_5: { function g() { if (!(foo || bar || baz || baa)) { a(); - b() + b(); } } }