enhance `evaluate`, `functions` & `inline` (#3931)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 28 May 2020 23:05:47 +0000 (00:05 +0100)
committerGitHub <noreply@github.com>
Thu, 28 May 2020 23:05:47 +0000 (07:05 +0800)
lib/compress.js
test/compress/collapse_vars.js
test/compress/evaluate.js
test/compress/reduce_vars.js

index 9ddbfa4..daf60ee 100644 (file)
@@ -450,6 +450,11 @@ merge(Compressor.prototype, {
             tw.safe_ids[def.id] = safe;
         }
 
+        function push_ref(def, ref) {
+            def.references.push(ref);
+            def.last_ref = ref;
+        }
+
         function add_assign(tw, def, node) {
             if (def.fixed === false) return;
             tw.assigns.add(def.id, node);
@@ -550,7 +555,7 @@ merge(Compressor.prototype, {
             if (!(node instanceof AST_Symbol)) return;
             var d = node.definition();
             if (!d) return;
-            if (node instanceof AST_SymbolRef) d.references.push(node);
+            if (node instanceof AST_SymbolRef) push_ref(d, node);
             d.fixed = false;
         });
         def(AST_Accessor, function(tw, descend, compressor) {
@@ -588,7 +593,7 @@ merge(Compressor.prototype, {
                 });
             };
             if (!safe) return;
-            d.references.push(sym);
+            push_ref(d, sym);
             mark(tw, d, false);
             node.right.walk(tw);
             mark(tw, d, true);
@@ -784,7 +789,7 @@ merge(Compressor.prototype, {
         def(AST_SymbolRef, function(tw, descend, compressor) {
             if (this.fixed) delete this.fixed;
             var d = this.definition();
-            d.references.push(this);
+            push_ref(d, this);
             if (d.references.length == 1
                 && !d.fixed
                 && d.orig[0] instanceof AST_SymbolDefun) {
@@ -886,7 +891,7 @@ merge(Compressor.prototype, {
                 });
             };
             if (!safe) return;
-            d.references.push(exp);
+            push_ref(d, exp);
             mark(tw, d, true);
             add_assign(tw, d, node);
             return true;
@@ -3280,12 +3285,30 @@ merge(Compressor.prototype, {
             return this.value;
         });
         def(AST_Assign, function(compressor, ignore_side_effects, cached, depth) {
-            if (!ignore_side_effects) return this;
-            if (this.operator != "=") return this;
-            var node = this.right;
+            var lhs = this.left;
+            if (!ignore_side_effects) {
+                if (!(lhs instanceof AST_SymbolRef)) return this;
+                if (!HOP(lhs, "_eval") && !lhs.fixed) return this;
+                var def = lhs.definition();
+                if (def.undeclared) return this;
+                if (def.last_ref !== lhs) return this;
+                if (def.single_use == "m") return this;
+            }
+            var op = this.operator;
+            var node;
+            if (HOP(lhs, "_eval") || !lhs.fixed) {
+                node = op == "=" ? this.right : make_node(AST_Binary, this, {
+                    operator: op.slice(0, -1),
+                    left: lhs,
+                    right: this.right,
+                });
+            } else {
+                node = lhs;
+            }
             var value = node._eval(compressor, ignore_side_effects, cached, depth);
-            modified(this.left);
-            return value === node ? this : value;
+            if (value === node) return this;
+            modified(lhs);
+            return value;
         });
         def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) {
             if (!ignore_side_effects) return this;
@@ -3377,8 +3400,7 @@ merge(Compressor.prototype, {
               case "++":
               case "--":
                 if (!(e instanceof AST_SymbolRef)) return this;
-                var refs = e.definition().references;
-                if (!ignore_side_effects && refs[refs.length - 1] !== e) return this;
+                if (!ignore_side_effects && e.definition().last_ref !== e) return this;
                 if (HOP(e, "_eval")) v = +(op[0] + 1) + +v;
                 modified(e);
                 return v;
@@ -3388,11 +3410,11 @@ merge(Compressor.prototype, {
         def(AST_UnaryPostfix, function(compressor, ignore_side_effects, cached, depth) {
             var e = this.expression;
             if (!e.fixed) return this;
-            var refs = e.definition().references;
-            if (!ignore_side_effects && refs[refs.length - 1] !== e) return this;
+            if (!ignore_side_effects && e.definition().last_ref !== e) return this;
             var v = e._eval(compressor, ignore_side_effects, cached, depth + 1);
+            if (v === e) return this;
             modified(e);
-            return v === e ? this : +v;
+            return v;
         });
         var non_converting_binary = makePredicate("&& || === !==");
         def(AST_Binary, function(compressor, ignore_side_effects, cached, depth) {
@@ -4385,13 +4407,11 @@ merge(Compressor.prototype, {
                             var defun = make_node(AST_Defun, def, def.value);
                             defun.name = make_node(AST_SymbolDefun, def.name, def.name);
                             var name_def = def.name.scope.resolve().def_function(defun.name);
-                            if (old_def) def.value.walk(new TreeWalker(function(node) {
-                                if (node instanceof AST_SymbolRef && node.definition() === old_def) {
-                                    node.name = name_def.name;
-                                    node.thedef = name_def;
-                                    node.reference({});
-                                }
-                            }));
+                            if (old_def) old_def.references.forEach(function(node) {
+                                node.name = name_def.name;
+                                node.thedef = name_def;
+                                node.reference({});
+                            });
                             body.push(defun);
                         } else {
                             if (var_defs.length > 1 && sym.orig.indexOf(def.name) > sym.eliminated) {
@@ -6242,17 +6262,19 @@ merge(Compressor.prototype, {
                 && !fn.uses_arguments
                 && !fn.pinned()
                 && !(fn.name && fn instanceof AST_Function)
-                && (exp === fn
-                    || !recursive_ref(compressor, def = exp.definition()) && fn.is_constant_expression(exp.scope))
+                && (exp === fn || !recursive_ref(compressor, def = exp.definition())
+                    && fn.is_constant_expression(compressor.find_parent(AST_Scope)))
                 && (value = can_flatten_body(stat))
                 && !fn.contains_this()) {
+                var replacing = exp === fn || compressor.option("unused") && def.references.length - def.replaced == 1;
                 if (can_substitute_directly()) {
                     var args = self.args.slice();
+                    var refs = [];
                     args.push(value.clone(true).transform(new TreeTransformer(function(node) {
                         if (node instanceof AST_SymbolRef) {
                             var def = node.definition();
                             if (fn.variables.get(node.name) !== def) {
-                                if (exp !== fn) def.references.push(node);
+                                refs.push(node);
                                 return node;
                             }
                             var index = resolve_index(def);
@@ -6267,11 +6289,17 @@ merge(Compressor.prototype, {
                         return arg;
                     })).optimize(compressor);
                     node = maintain_this_binding(compressor, compressor.parent(), compressor.self(), node);
-                    if (best_of(compressor, self, node) === node) return node;
+                    if (replacing || best_of(compressor, self, node) === node) {
+                        refs.forEach(function(ref) {
+                            var def = ref.definition();
+                            def.references.push(ref);
+                            if (replacing) def.replaced++;
+                        });
+                        return node;
+                    }
                 }
                 var scope, in_loop, level = -1;
-                if ((exp === fn || compressor.option("unused") && def.references.length - def.replaced == 1)
-                    && can_inject_symbols()) {
+                if (replacing && can_inject_symbols()) {
                     fn._squeezed = true;
                     if (exp !== fn) fn.parent_scope = exp.scope;
                     var node = make_sequence(self, flatten_fn()).optimize(compressor);
@@ -6360,7 +6388,7 @@ merge(Compressor.prototype, {
             var begin;
             var in_order = [];
             var side_effects = false;
-            value.walk(new TreeWalker(function(node) {
+            value.walk(new TreeWalker(function(node, descend) {
                 if (abort) return true;
                 if (node instanceof AST_Binary && lazy_op[node.operator]
                     || node instanceof AST_Conditional) {
@@ -6386,7 +6414,11 @@ merge(Compressor.prototype, {
                     }
                     return;
                 }
-                if (node.has_side_effects(compressor)) side_effects = true;
+                if (node.has_side_effects(compressor)) {
+                    descend();
+                    side_effects = true;
+                    return true;
+                }
             }));
             if (abort) return;
             var end = self.args.length;
@@ -6395,8 +6427,8 @@ merge(Compressor.prototype, {
                 while (end-- > begin && fn.argnames[end] === in_order.pop());
                 end++;
             }
-            var scope = side_effects && compressor.find_parent(AST_Scope);
-            return end <= begin || all(self.args.slice(begin, end), side_effects ? function(funarg) {
+            var scope = side_effects && !in_order && compressor.find_parent(AST_Scope);
+            return end <= begin || all(self.args.slice(begin, end), scope ? function(funarg) {
                 return funarg.is_constant_expression(scope);
             } : function(funarg) {
                 return !funarg.has_side_effects(compressor);
@@ -6685,7 +6717,7 @@ merge(Compressor.prototype, {
             var seq = lift_sequence_in_expression(self, compressor);
             if (seq !== self) return seq.optimize(compressor);
         }
-        return self;
+        return try_evaluate(compressor, self);
     });
 
     var SIGN_OPS = makePredicate("+ -");
@@ -7832,7 +7864,7 @@ merge(Compressor.prototype, {
                 expression: self.left
             });
         }
-        return self;
+        return try_evaluate(compressor, self);
 
         function in_try(level, node) {
             var right = self.right;
index 737697a..82fb057 100644 (file)
@@ -62,7 +62,7 @@ collapse_vars_side_effects_1: {
     expect: {
         function f1() {
             var s = "abcdef", i = 2;
-            console.log.bind(console)(s.charAt(i++), s.charAt(i++), s.charAt(i++), 7);
+            console.log.bind(console)(s.charAt(i++), s.charAt(i++), s.charAt(4), 7);
         }
         function f2() {
             var s = "abcdef", i = 2;
@@ -74,13 +74,14 @@ collapse_vars_side_effects_1: {
                 log = console.log.bind(console),
                 x = s.charAt(i++),
                 y = s.charAt(i++);
-            log(x, s.charAt(i++), y, 7);
+            log(x, s.charAt(4), y, 7);
         }
         function f4() {
-            var i = 10,
-                x = i += 2,
-                y = i += 3;
-            console.log.bind(console)(x, i += 4, y, 19);
+            var i = 10;
+            i += 2,
+            i += 3,
+            i += 4;
+            console.log.bind(console)(12, 19, 15, 19);
         }
         f1(), f2(), f3(), f4();
     }
index db32145..868ad15 100644 (file)
@@ -2485,3 +2485,62 @@ issue_3920: {
     }
     expect_stdout: "false"
 }
+
+inlined_increment_prefix: {
+    options = {
+        evaluate: true,
+        inline: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 0;
+        (function() {
+            ++a;
+        })();
+        console.log(a += 0);
+    }
+    expect: {
+        var a = 0;
+        void ++a;
+        console.log(a += 0);
+    }
+    expect_stdout: "1"
+}
+
+inlined_increment_postfix: {
+    options = {
+        evaluate: true,
+        inline: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 0;
+        (function() {
+            a++;
+        })();
+        console.log(a += 0);
+    }
+    expect: {
+        var a = 0;
+        void a++;
+        console.log(a += 0);
+    }
+    expect_stdout: "1"
+}
+
+compound_assignment_to_property: {
+    options = {
+        evaluate: true,
+        unsafe: true,
+    }
+    input: {
+        1 + (0..p >>= 0) && console.log("PASS");
+    }
+    expect: {
+        1 + (0..p >>= 0),
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+}
index 94852d4..9168e81 100644 (file)
@@ -2724,8 +2724,8 @@ issue_1814_1: {
     expect: {
         !function() {
             !function(a) {
-                console.log(a++, 42);
-            }(0);
+                console.log(0, 42);
+            }();
         }();
     }
     expect_stdout: "0 42"
@@ -2751,8 +2751,8 @@ issue_1814_2: {
     expect: {
         !function() {
             !function(a) {
-                console.log("321", a++);
-            }(0);
+                console.log("321", 0);
+            }();
         }();
     }
     expect_stdout: "321 0"