rewrite handle_if_return
authorMihai Bazon <mihai@bazon.net>
Sun, 16 Sep 2012 12:46:20 +0000 (15:46 +0300)
committerMihai Bazon <mihai@bazon.net>
Sun, 16 Sep 2012 12:46:20 +0000 (15:46 +0300)
optimizations of if/return/continue seem to be even better now

lib/compress.js
lib/output.js
lib/utils.js
test/compress/conditionals.js

index f8f4d17..2f28654 100644 (file)
@@ -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;
     });
 
index 602fa60..9622159 100644 (file)
@@ -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");
index 6f8334e..f8f4102 100644 (file)
@@ -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;
index 92e5d61..4dc27b8 100644 (file)
@@ -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();
             }
         }
     }