[Pkg-javascript-commits] [uglifyjs] 07/228: optimise binary operands with evaluate() (#1427)

Jonas Smedegaard dr at jones.dk
Sat Apr 15 14:25:11 UTC 2017


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository uglifyjs.

commit 0610c020b1544820be9898a285ab6c9066490552
Author: Alex Lam S.L <alexlamsl at gmail.com>
Date:   Thu Jan 26 19:16:50 2017 +0800

    optimise binary operands with evaluate() (#1427)
    
    - remove call to evaluate() in is_constant() and let nested optimize() does its job instead
    - reject RegExp in is_constant() and remove special case logic under collapse_vars
    - operands to conditionals optimisation are now always evaluate()-ed
    - throw error in constant_value() instead of returning undefined to catch possible bugs, similar to make_node_from_constant()
    - optimise binary boolean operators under `evaluate` instead of `conditionals`
---
 lib/compress.js               | 115 +++++++++++++++-----------
 test/compress/conditionals.js | 183 ++++++-----------------------------------
 test/compress/evaluate.js     | 184 ++++++++++++++++++++++++++++++++++++++++++
 test/compress/reduce_vars.js  |  24 +++---
 4 files changed, 288 insertions(+), 218 deletions(-)

diff --git a/lib/compress.js b/lib/compress.js
index 5c01962..4e45df9 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -347,7 +347,7 @@ merge(Compressor.prototype, {
                     if (ref.scope.uses_eval || ref.scope.uses_with) break;
 
                     // Constant single use vars can be replaced in any scope.
-                    if (!(var_decl.value instanceof AST_RegExp) && var_decl.value.is_constant(compressor)) {
+                    if (var_decl.value.is_constant()) {
                         var ctt = new TreeTransformer(function(node) {
                             if (node === ref)
                                 return replace_var(node, ctt.parent(), true);
@@ -1013,31 +1013,46 @@ merge(Compressor.prototype, {
             }
             return [ best_of(node, this), val ];
         });
-        AST_Node.DEFMETHOD("is_constant", function(compressor){
+        var unaryPrefix = makePredicate("! ~ - +");
+        AST_Node.DEFMETHOD("is_constant", function(){
             // Accomodate when compress option evaluate=false
-            // as well as the common constant expressions !0 and !1
-            return this instanceof AST_Constant
-                || (this instanceof AST_UnaryPrefix && this.operator == "!"
-                    && this.expression instanceof AST_Constant)
-                || this.evaluate(compressor).length > 1;
+            // as well as the common constant expressions !0 and -1
+            if (this instanceof AST_Constant) {
+                return !(this instanceof AST_RegExp);
+            } else {
+                return this instanceof AST_UnaryPrefix
+                    && this.expression instanceof AST_Constant
+                    && unaryPrefix(this.operator);
+            }
         });
         // Obtain the constant value of an expression already known to be constant.
-        // Result only valid iff this.is_constant(compressor) is true.
+        // Result only valid iff this.is_constant() is true.
         AST_Node.DEFMETHOD("constant_value", function(compressor){
             // Accomodate when option evaluate=false.
-            if (this instanceof AST_Constant) return this.value;
-            // Accomodate the common constant expressions !0 and !1 when option evaluate=false.
+            if (this instanceof AST_Constant && !(this instanceof AST_RegExp)) {
+                return this.value;
+            }
+            // Accomodate the common constant expressions !0 and -1 when option evaluate=false.
             if (this instanceof AST_UnaryPrefix
-                && this.operator == "!"
-                && this.expression instanceof AST_Constant) {
+                && this.expression instanceof AST_Constant) switch (this.operator) {
+              case "!":
                 return !this.expression.value;
+              case "~":
+                return ~this.expression.value;
+              case "-":
+                return -this.expression.value;
+              case "+":
+                return +this.expression.value;
+              default:
+                throw new Error(string_template("Cannot evaluate unary expression {value}", {
+                    value: this.print_to_string()
+                }));
             }
-            var result = this.evaluate(compressor)
+            var result = this.evaluate(compressor);
             if (result.length > 1) {
                 return result[1];
             }
-            // should never be reached
-            return undefined;
+            throw new Error(string_template("Cannot evaluate constant [{file}:{line},{col}]", this.start));
         });
         def(AST_Statement, function(){
             throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
@@ -2419,6 +2434,16 @@ merge(Compressor.prototype, {
     var commutativeOperators = makePredicate("== === != !== * & | ^");
 
     OPT(AST_Binary, function(self, compressor){
+        var lhs = self.left.evaluate(compressor);
+        var rhs = self.right.evaluate(compressor);
+        if (lhs.length > 1 && lhs[0].is_constant() !== self.left.is_constant()
+            || rhs.length > 1 && rhs[0].is_constant() !== self.right.is_constant()) {
+            return make_node(AST_Binary, self, {
+                operator: self.operator,
+                left: lhs[0],
+                right: rhs[0]
+            }).optimize(compressor);
+        }
         function reverse(op, force) {
             if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) {
                 if (op) self.operator = op;
@@ -2491,32 +2516,6 @@ merge(Compressor.prototype, {
             }
             break;
         }
-        if (compressor.option("conditionals")) {
-            if (self.operator == "&&") {
-                var ll = self.left.evaluate(compressor);
-                if (ll.length > 1) {
-                    if (ll[1]) {
-                        compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
-                        return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
-                    } else {
-                        compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
-                        return maintain_this_binding(compressor.parent(), self, ll[0]);
-                    }
-                }
-            }
-            else if (self.operator == "||") {
-                var ll = self.left.evaluate(compressor);
-                if (ll.length > 1) {
-                    if (ll[1]) {
-                        compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
-                        return maintain_this_binding(compressor.parent(), self, ll[0]);
-                    } else {
-                        compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
-                        return maintain_this_binding(compressor.parent(), self, self.right.evaluate(compressor)[0]);
-                    }
-                }
-            }
-        }
         if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) {
           case "&&":
             var ll = self.left.evaluate(compressor);
@@ -2590,6 +2589,30 @@ merge(Compressor.prototype, {
             return self.left;
         }
         if (compressor.option("evaluate")) {
+            switch (self.operator) {
+              case "&&":
+                if (self.left.is_constant()) {
+                    if (self.left.constant_value(compressor)) {
+                        compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
+                        return maintain_this_binding(compressor.parent(), self, self.right);
+                    } else {
+                        compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
+                        return maintain_this_binding(compressor.parent(), self, self.left);
+                    }
+                }
+                break;
+              case "||":
+                if (self.left.is_constant()) {
+                    if (self.left.constant_value(compressor)) {
+                        compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
+                        return maintain_this_binding(compressor.parent(), self, self.left);
+                    } else {
+                        compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
+                        return maintain_this_binding(compressor.parent(), self, self.right);
+                    }
+                }
+                break;
+            }
             if (self.operator == "+") {
                 if (self.left instanceof AST_Constant
                     && self.right instanceof AST_Binary
@@ -2816,14 +2839,14 @@ merge(Compressor.prototype, {
             });
         }
         // y?1:1 --> 1
-        if (consequent.is_constant(compressor)
-            && alternative.is_constant(compressor)
+        if (consequent.is_constant()
+            && alternative.is_constant()
             && consequent.equivalent_to(alternative)) {
-            var consequent_value = consequent.constant_value(compressor);
+            var consequent_value = consequent.evaluate(compressor)[0];
             if (self.condition.has_side_effects(compressor)) {
-                return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]);
+                return AST_Seq.from_array([self.condition, consequent_value]);
             } else {
-                return make_node_from_constant(compressor, consequent_value, self);
+                return consequent_value;
             }
         }
 
diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js
index 35cb26f..d88c5b9 100644
--- a/test/compress/conditionals.js
+++ b/test/compress/conditionals.js
@@ -635,166 +635,6 @@ ternary_boolean_alternative: {
     }
 }
 
-conditional_and: {
-    options = {
-        conditionals: true,
-        evaluate    : true
-    };
-    input: {
-        var a;
-        // compress these
-
-        a = true     && condition;
-        a = 1        && console.log("a");
-        a = 2 * 3    && 2 * condition;
-        a = 5 == 5   && condition + 3;
-        a = "string" && 4 - condition;
-        a = 5 + ""   && condition / 5;
-        a = -4.5     && 6 << condition;
-        a = 6        && 7;
-
-        a = false     && condition;
-        a = NaN       && console.log("b");
-        a = 0         && console.log("c");
-        a = undefined && 2 * condition;
-        a = null      && condition + 3;
-        a = 2 * 3 - 6 && 4 - condition;
-        a = 10 == 7   && condition / 5;
-        a = !"string" && 6 % condition;
-        a = 0         && 7;
-
-        // don't compress these
-
-        a = condition        && true;
-        a = console.log("a") && 2;
-        a = 4 - condition    && "string";
-        a = 6 << condition   && -4.5;
-
-        a = condition        && false;
-        a = console.log("b") && NaN;
-        a = console.log("c") && 0;
-        a = 2 * condition    && undefined;
-        a = condition + 3    && null;
-
-    }
-    expect: {
-        var a;
-
-        a = condition;
-        a = console.log("a");
-        a = 2 * condition;
-        a = condition + 3;
-        a = 4 - condition;
-        a = condition / 5;
-        a = 6 << condition;
-        a = 7;
-
-        a = false;
-        a = NaN;
-        a = 0;
-        a = void 0;
-        a = null;
-        a = 0;
-        a = false;
-        a = false;
-        a = 0;
-
-        a = condition        && true;
-        a = console.log("a") && 2;
-        a = 4 - condition    && "string";
-        a = 6 << condition   && -4.5;
-
-        a = condition        && false;
-        a = console.log("b") && NaN;
-        a = console.log("c") && 0;
-        a = 2 * condition    && void 0;
-        a = condition + 3    && null;
-    }
-}
-
-conditional_or: {
-    options = {
-        conditionals: true,
-        evaluate    : true
-    };
-    input: {
-        var a;
-        // compress these
-
-        a = true     || condition;
-        a = 1        || console.log("a");
-        a = 2 * 3    || 2 * condition;
-        a = 5 == 5   || condition + 3;
-        a = "string" || 4 - condition;
-        a = 5 + ""   || condition / 5;
-        a = -4.5     || 6 << condition;
-        a = 6        || 7;
-
-        a = false     || condition;
-        a = 0         || console.log("b");
-        a = NaN       || console.log("c");
-        a = undefined || 2 * condition;
-        a = null      || condition + 3;
-        a = 2 * 3 - 6 || 4 - condition;
-        a = 10 == 7   || condition / 5;
-        a = !"string" || 6 % condition;
-        a = null      || 7;
-
-        a = console.log(undefined && condition || null);
-        a = console.log(undefined || condition && null);
-
-        // don't compress these
-
-        a = condition        || true;
-        a = console.log("a") || 2;
-        a = 4 - condition    || "string";
-        a = 6 << condition   || -4.5;
-
-        a = condition        || false;
-        a = console.log("b") || NaN;
-        a = console.log("c") || 0;
-        a = 2 * condition    || undefined;
-        a = condition + 3    || null;
-
-    }
-    expect: {
-        var a;
-
-        a = true;
-        a = 1;
-        a = 6;
-        a = true;
-        a = "string";
-        a = "5";
-        a = -4.5;
-        a = 6;
-
-        a = condition;
-        a = console.log("b");
-        a = console.log("c");
-        a = 2 * condition;
-        a = condition + 3;
-        a = 4 - condition;
-        a = condition / 5;
-        a = 6 % condition;
-        a = 7;
-
-        a = console.log(null);
-        a = console.log(condition && null);
-
-        a = condition        || true;
-        a = console.log("a") || 2;
-        a = 4 - condition    || "string";
-        a = 6 << condition   || -4.5;
-
-        a = condition        || false;
-        a = console.log("b") || NaN;
-        a = console.log("c") || 0;
-        a = 2 * condition    || void 0;
-        a = condition + 3    || null;
-    }
-}
-
 trivial_boolean_ternary_expressions : {
     options = {
         conditionals: true,
@@ -906,3 +746,26 @@ issue_1154: {
         function g6() { return g(), "number"; }
     }
 }
+
+no_evaluate: {
+    options = {
+        conditionals: true,
+        evaluate    : false
+    }
+    input: {
+        function f(b) {
+            a = b ? !0 : !0;
+            a = b ? ~1 : ~1;
+            a = b ? -2 : -2;
+            a = b ? +3 : +3;
+        }
+    }
+    expect: {
+        function f(b) {
+            a = !0;
+            a = ~1;
+            a = -2;
+            a = +3;
+        }
+    }
+}
diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js
index c74c7b2..0ff157d 100644
--- a/test/compress/evaluate.js
+++ b/test/compress/evaluate.js
@@ -1,3 +1,187 @@
+and: {
+    options = {
+        evaluate: true
+    }
+    input: {
+        var a;
+        // compress these
+
+        a = true     && condition;
+        a = 1        && console.log("a");
+        a = 2 * 3    && 2 * condition;
+        a = 5 == 5   && condition + 3;
+        a = "string" && 4 - condition;
+        a = 5 + ""   && condition / 5;
+        a = -4.5     && 6 << condition;
+        a = 6        && 7;
+
+        a = false     && condition;
+        a = NaN       && console.log("b");
+        a = 0         && console.log("c");
+        a = undefined && 2 * condition;
+        a = null      && condition + 3;
+        a = 2 * 3 - 6 && 4 - condition;
+        a = 10 == 7   && condition / 5;
+        a = !"string" && 6 % condition;
+        a = 0         && 7;
+
+        // don't compress these
+
+        a = condition        && true;
+        a = console.log("a") && 2;
+        a = 4 - condition    && "string";
+        a = 6 << condition   && -4.5;
+
+        a = condition        && false;
+        a = console.log("b") && NaN;
+        a = console.log("c") && 0;
+        a = 2 * condition    && undefined;
+        a = condition + 3    && null;
+
+    }
+    expect: {
+        var a;
+
+        a = condition;
+        a = console.log("a");
+        a = 2 * condition;
+        a = condition + 3;
+        a = 4 - condition;
+        a = condition / 5;
+        a = 6 << condition;
+        a = 7;
+
+        a = false;
+        a = NaN;
+        a = 0;
+        a = void 0;
+        a = null;
+        a = 0;
+        a = false;
+        a = false;
+        a = 0;
+
+        a = condition        && true;
+        a = console.log("a") && 2;
+        a = 4 - condition    && "string";
+        a = 6 << condition   && -4.5;
+
+        a = condition        && false;
+        a = console.log("b") && NaN;
+        a = console.log("c") && 0;
+        a = 2 * condition    && void 0;
+        a = condition + 3    && null;
+    }
+}
+
+or: {
+    options = {
+        evaluate: true
+    }
+    input: {
+        var a;
+        // compress these
+
+        a = true     || condition;
+        a = 1        || console.log("a");
+        a = 2 * 3    || 2 * condition;
+        a = 5 == 5   || condition + 3;
+        a = "string" || 4 - condition;
+        a = 5 + ""   || condition / 5;
+        a = -4.5     || 6 << condition;
+        a = 6        || 7;
+
+        a = false     || condition;
+        a = 0         || console.log("b");
+        a = NaN       || console.log("c");
+        a = undefined || 2 * condition;
+        a = null      || condition + 3;
+        a = 2 * 3 - 6 || 4 - condition;
+        a = 10 == 7   || condition / 5;
+        a = !"string" || 6 % condition;
+        a = null      || 7;
+
+        a = console.log(undefined && condition || null);
+        a = console.log(undefined || condition && null);
+
+        // don't compress these
+
+        a = condition        || true;
+        a = console.log("a") || 2;
+        a = 4 - condition    || "string";
+        a = 6 << condition   || -4.5;
+
+        a = condition        || false;
+        a = console.log("b") || NaN;
+        a = console.log("c") || 0;
+        a = 2 * condition    || undefined;
+        a = condition + 3    || null;
+
+    }
+    expect: {
+        var a;
+
+        a = true;
+        a = 1;
+        a = 6;
+        a = true;
+        a = "string";
+        a = "5";
+        a = -4.5;
+        a = 6;
+
+        a = condition;
+        a = console.log("b");
+        a = console.log("c");
+        a = 2 * condition;
+        a = condition + 3;
+        a = 4 - condition;
+        a = condition / 5;
+        a = 6 % condition;
+        a = 7;
+
+        a = console.log(null);
+        a = console.log(condition && null);
+
+        a = condition        || true;
+        a = console.log("a") || 2;
+        a = 4 - condition    || "string";
+        a = 6 << condition   || -4.5;
+
+        a = condition        || false;
+        a = console.log("b") || NaN;
+        a = console.log("c") || 0;
+        a = 2 * condition    || void 0;
+        a = condition + 3    || null;
+    }
+}
+
+unary_prefix: {
+    options = {
+        evaluate: true
+    }
+    input: {
+        a = !0 && b;
+        a = !0 || b;
+        a = ~1 && b;
+        a = ~1 || b;
+        a = -2 && b;
+        a = -2 || b;
+        a = +3 && b;
+        a = +3 || b;
+    }
+    expect: {
+        a = b;
+        a = !0;
+        a = b;
+        a = -2;
+        a = b;
+        a = -2;
+        a = b;
+        a = 3;
+    }
+}
+
 negative_zero: {
     options = { evaluate: true }
     input: {
diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js
index c401ac6..2301a92 100644
--- a/test/compress/reduce_vars.js
+++ b/test/compress/reduce_vars.js
@@ -136,30 +136,30 @@ modified: {
         }
 
         function f2() {
-            var a = 1, b = 2, c = 3;
+            var b = 2, c = 3;
             b = c;
-            console.log(a + b);
-            console.log(b + c);
+            console.log(1 + b);
+            console.log(b + 3);
             console.log(4);
-            console.log(a + b + c);
+            console.log(1 + b + 3);
         }
 
         function f3() {
-            var a = 1, b = 2, c = 3;
+            var b = 2, c = 3;
             b *= c;
-            console.log(a + b);
-            console.log(b + c);
+            console.log(1 + b);
+            console.log(b + 3);
             console.log(4);
-            console.log(a + b + c);
+            console.log(1 + b + 3);
         }
 
         function f4() {
-            var a = 1, b = 2, c = 3;
+            var b = 2, c = 3;
             b = c;
-            console.log(a + b);
+            console.log(1 + b);
             console.log(b + c);
-            console.log(a + c);
-            console.log(a + b + c);
+            console.log(1 + c);
+            console.log(1 + b + c);
         }
 
         function f5(a) {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/uglifyjs.git



More information about the Pkg-javascript-commits mailing list