few more optimizations:
authorMihai Bazon <mihai@bazon.net>
Thu, 13 Sep 2012 12:20:57 +0000 (15:20 +0300)
committerMihai Bazon <mihai@bazon.net>
Thu, 13 Sep 2012 12:20:57 +0000 (15:20 +0300)
- 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
test/compress/conditionals.js

index efdc6d8..15eab98 100644 (file)
@@ -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);
index 54c0751..92e5d61 100644 (file)
@@ -90,7 +90,7 @@ ifs_4: {
 
 ifs_5: {
     options = {
-        conditionals: true
+        if_return: true
     };
     input: {
         function f() {