From 509896a41022dbc76cc75b985ce0ec35702279ef Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 15 Dec 2021 00:08:24 +0000 Subject: [PATCH] improve `compress` efficiency (#5220) --- lib/compress.js | 176 ++++++++++++++++++----------------- test/compress/arrows.js | 2 +- test/compress/classes.js | 4 +- test/compress/drop-unused.js | 4 +- test/compress/functions.js | 1 + test/compress/negate-iife.js | 5 +- test/compress/rests.js | 4 +- 7 files changed, 100 insertions(+), 96 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 3102d76f..ef84a75c 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -7929,12 +7929,17 @@ Compressor.prototype.compress = function(node) { function drop_returns(compressor, exp) { var arrow = is_arrow(exp); var async = is_async(exp); + var changed = false; var drop_body = false; if (arrow && compressor.option("arrows")) { if (!exp.value) { drop_body = true; } else if (!async || is_primitive(compressor, exp.value)) { - exp.value = exp.value.drop_side_effect_free(compressor); + var dropped = exp.value.drop_side_effect_free(compressor); + if (dropped !== exp.value) { + changed = true; + exp.value = dropped; + } } } else if (exp instanceof AST_AsyncFunction || exp instanceof AST_Function) { if (exp.name) { @@ -7951,6 +7956,7 @@ Compressor.prototype.compress = function(node) { if (async && !is_primitive(compressor, value)) return node; value = value.drop_side_effect_free(compressor, true); } + changed = true; if (!value) return make_node(AST_EmptyStatement, node); return make_node(AST_SimpleStatement, node, { body: value }); }); @@ -7958,7 +7964,11 @@ Compressor.prototype.compress = function(node) { var value = node.value; if (value) { if (async && !is_primitive(compressor, value)) return; - node.value = value.drop_side_effect_free(compressor); + var dropped = value.drop_side_effect_free(compressor); + if (dropped !== value) { + changed = true; + node.value = dropped; + } } }); } @@ -7967,6 +7977,7 @@ Compressor.prototype.compress = function(node) { var body = node.body; if (body instanceof AST_Await) { if (is_primitive(compressor, body.expression)) { + changed = true; body = body.expression.drop_side_effect_free(compressor, true); if (!body) return make_node(AST_EmptyStatement, node); node.body = body; @@ -7976,8 +7987,10 @@ Compressor.prototype.compress = function(node) { for (var i = exprs.length; --i >= 0;) { var tail = exprs[i]; if (!(tail instanceof AST_Await)) break; - if (!is_primitive(compressor, tail.expression)) break; - if (exprs[i] = tail.expression.drop_side_effect_free(compressor)) break; + var value = tail.expression; + if (!is_primitive(compressor, value)) break; + changed = true; + if (exprs[i] = value.drop_side_effect_free(compressor)) break; } switch (i) { case -1: @@ -8021,7 +8034,7 @@ Compressor.prototype.compress = function(node) { return make_node(ctor, exp, exp); } } - return drop_body && exp.clone(); + return changed && exp.clone(); } def(AST_Call, function(compressor, first_in_statement) { var self = this; @@ -10369,42 +10382,46 @@ Compressor.prototype.compress = function(node) { OPT(AST_UnaryPrefix, function(self, compressor) { var op = self.operator; var exp = self.expression; - if (compressor.option("evaluate") && op == "delete" && !may_not_delete(exp)) { - return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor); - } if (compressor.option("sequences") && can_lift()) { var seq = lift_sequence_in_expression(self, compressor); if (seq !== self) return seq.optimize(compressor); } - if (compressor.option("side_effects") && op == "void") { + switch (op) { + case "-": + if (exp instanceof AST_Infinity) exp = exp.transform(compressor); + // avoids infinite recursion of numerals + if (exp instanceof AST_Number || exp instanceof AST_Infinity) return self; + break; + case "!": + if (!compressor.option("booleans")) break; + if (exp.is_truthy()) return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor); + if (compressor.in_boolean_context()) { + // !!foo ---> foo, if we're in boolean context + if (exp instanceof AST_UnaryPrefix && exp.operator == "!") return exp.expression; + if (exp instanceof AST_Binary) { + self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor))); + } + } + break; + case "delete": + if (!compressor.option("evaluate")) break; + if (may_not_delete(exp)) break; + return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor); + case "typeof": + if (!compressor.option("booleans")) break; + if (!compressor.in_boolean_context()) break; + // typeof always returns a non-empty string, thus always truthy + AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start); + var exprs = [ make_node(AST_True, self) ]; + if (!(exp instanceof AST_SymbolRef && can_drop_symbol(exp, compressor))) exprs.unshift(exp); + return make_sequence(self, exprs).optimize(compressor); + case "void": + if (!compressor.option("side_effects")) break; exp = exp.drop_side_effect_free(compressor); if (!exp) return make_node(AST_Undefined, self).optimize(compressor); self.expression = exp; return self; } - if (compressor.option("booleans")) { - if (op == "!" && exp.is_truthy()) { - return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor); - } else if (compressor.in_boolean_context()) switch (op) { - case "!": - if (exp instanceof AST_UnaryPrefix && exp.operator == "!") { - // !!foo ---> foo, if we're in boolean context - return exp.expression; - } - if (exp instanceof AST_Binary) { - self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor))); - } - break; - case "typeof": - // typeof always returns a non-empty string, thus it's - // always true in booleans - AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start); - var exprs = [ make_node(AST_True, self) ]; - if (!(exp instanceof AST_SymbolRef && can_drop_symbol(exp, compressor))) exprs.unshift(exp); - return make_sequence(self, exprs).optimize(compressor); - } - } - if (op == "-" && exp instanceof AST_Infinity) exp = exp.transform(compressor); if (compressor.option("evaluate") && exp instanceof AST_Binary && SIGN_OPS[op] @@ -10414,14 +10431,12 @@ Compressor.prototype.compress = function(node) { operator: exp.operator, left: make_node(AST_UnaryPrefix, exp.left, { operator: op, - expression: exp.left + expression: exp.left, }), - right: exp.right + right: exp.right, }); } - // avoids infinite recursion of numerals - return op == "-" && (exp instanceof AST_Number || exp instanceof AST_Infinity) - ? self : try_evaluate(compressor, self); + return try_evaluate(compressor, self); function may_not_delete(node) { return node instanceof AST_Infinity @@ -10582,32 +10597,6 @@ Compressor.prototype.compress = function(node) { } OPT(AST_Binary, function(self, compressor) { - function reversible() { - return self.left.is_constant() - || self.right.is_constant() - || !self.left.has_side_effects(compressor) - && !self.right.has_side_effects(compressor); - } - function reverse(op) { - if (reversible()) { - if (op) self.operator = op; - var tmp = self.left; - self.left = self.right; - self.right = tmp; - } - } - function swap_chain() { - var rhs = self.right; - self.left = make_node(AST_Binary, self, { - operator: self.operator, - left: self.left, - right: rhs.left, - start: self.left.start, - end: rhs.left.end - }); - self.right = rhs.right; - self.left = self.left.transform(compressor); - } if (commutativeOperators[self.operator] && self.right.is_constant() && !self.left.is_constant() @@ -11260,6 +11249,35 @@ Compressor.prototype.compress = function(node) { && self.left.expression instanceof AST_Number && self.left.expression.value == 1; } } + + function reversible() { + return self.left.is_constant() + || self.right.is_constant() + || !self.left.has_side_effects(compressor) + && !self.right.has_side_effects(compressor); + } + + function reverse(op) { + if (reversible()) { + if (op) self.operator = op; + var tmp = self.left; + self.left = self.right; + self.right = tmp; + } + } + + function swap_chain() { + var rhs = self.right; + self.left = make_node(AST_Binary, self, { + operator: self.operator, + left: self.left, + right: rhs.left, + start: self.left.start, + end: rhs.left.end + }); + self.right = rhs.right; + self.left = self.left.transform(compressor); + } }); OPT(AST_SymbolExport, function(self) { @@ -11593,9 +11611,7 @@ Compressor.prototype.compress = function(node) { if (lhs && is_atomic(lhs, self)) return self; return make_node(AST_UnaryPrefix, self, { operator: "void", - expression: make_node(AST_Number, self, { - value: 0 - }) + expression: make_node(AST_Number, self, { value: 0 }), }); }); @@ -11607,12 +11623,8 @@ Compressor.prototype.compress = function(node) { } return make_node(AST_Binary, self, { operator: "/", - left: make_node(AST_Number, self, { - value: 1 - }), - right: make_node(AST_Number, self, { - value: 0 - }) + left: make_node(AST_Number, self, { value: 1 }), + right: make_node(AST_Number, self, { value: 0 }), }); }); @@ -11622,12 +11634,8 @@ Compressor.prototype.compress = function(node) { if (!lhs && !find_scope(compressor).find_variable("NaN")) return self; return make_node(AST_Binary, self, { operator: "/", - left: make_node(AST_Number, self, { - value: 0 - }), - right: make_node(AST_Number, self, { - value: 0 - }) + left: make_node(AST_Number, self, { value: 0 }), + right: make_node(AST_Number, self, { value: 0 }), }); }); @@ -12199,9 +12207,7 @@ Compressor.prototype.compress = function(node) { OPT(AST_Boolean, function(self, compressor) { if (!compressor.option("booleans")) return self; - if (compressor.in_boolean_context()) return make_node(AST_Number, self, { - value: +self.value - }); + if (compressor.in_boolean_context()) return make_node(AST_Number, self, { value: +self.value }); var p = compressor.parent(); if (p instanceof AST_Binary && (p.operator == "==" || p.operator == "!=")) { AST_Node.warn("Non-strict equality against boolean: {operator} {value} [{file}:{line},{col}]", { @@ -12211,15 +12217,11 @@ Compressor.prototype.compress = function(node) { line : p.start.line, col : p.start.col, }); - return make_node(AST_Number, self, { - value: +self.value - }); + return make_node(AST_Number, self, { value: +self.value }); } return make_node(AST_UnaryPrefix, self, { operator: "!", - expression: make_node(AST_Number, self, { - value: 1 - self.value - }) + expression: make_node(AST_Number, self, { value: 1 - self.value }), }); }); diff --git a/test/compress/arrows.js b/test/compress/arrows.js index 54886d58..709fc638 100644 --- a/test/compress/arrows.js +++ b/test/compress/arrows.js @@ -508,7 +508,7 @@ drop_value: { ((a, b) => a + b)(console.log(42)); } expect: { - ((a, b) => {})(console.log(42)); + void console.log(42); } expect_stdout: "42" node_version: ">=4" diff --git a/test/compress/classes.js b/test/compress/classes.js index 2ac97b02..1b1aea16 100644 --- a/test/compress/classes.js +++ b/test/compress/classes.js @@ -890,7 +890,7 @@ keep_fnames: { issue_805_1: { options = { inline: true, - passes: 2, + passes: 3, pure_getters: "strict", reduce_vars: true, sequences: true, @@ -926,7 +926,7 @@ issue_805_1: { issue_805_2: { options = { inline: true, - passes: 2, + passes: 3, pure_getters: "strict", reduce_vars: true, sequences: true, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 7873f4f4..be28ebe7 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1765,7 +1765,7 @@ issue_2846: { issue_805_1: { options = { inline: true, - passes: 2, + passes: 3, pure_getters: "strict", reduce_vars: true, sequences: true, @@ -1798,7 +1798,7 @@ issue_805_1: { issue_805_2: { options = { inline: true, - passes: 2, + passes: 3, pure_getters: "strict", reduce_vars: true, sequences: true, diff --git a/test/compress/functions.js b/test/compress/functions.js index 8cf776fa..2841f9e6 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -3862,6 +3862,7 @@ issue_3679_1: { options = { collapse_vars: true, inline: true, + passes: 2, pure_getters: "strict", reduce_vars: true, side_effects: true, diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 620c098a..813b9069 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -389,6 +389,7 @@ issue_1288_side_effects: { options = { conditionals: true, negate_iife: true, + sequences: true, side_effects: true, } input: { @@ -409,10 +410,10 @@ issue_1288_side_effects: { })(0); } expect: { - w; + w, x || function() { x = {}; - }(); + }(), y; } } diff --git a/test/compress/rests.js b/test/compress/rests.js index f377f637..ce27b6eb 100644 --- a/test/compress/rests.js +++ b/test/compress/rests.js @@ -866,9 +866,9 @@ issue_4575: { } expect: { (function(a) { - (function a(...d) { + (function(d) { console.log(d.length); - })(); + })([]); })(); } expect_stdout: "0"