From 3766d5c9621f56dc01176670b7d0bd0c7ef0b325 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 3 May 2020 10:38:28 +0100 Subject: [PATCH] enhance `unused` (#3839) --- lib/compress.js | 100 +++++++++++++++++++++++++++-------- test/compress/drop-unused.js | 24 ++++++++- 2 files changed, 101 insertions(+), 23 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index d584bdf3..394184bc 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -358,7 +358,6 @@ merge(Compressor.prototype, { function reset_def(tw, compressor, def) { def.assignments = 0; def.bool_fn = 0; - def.chained = false; def.cross_loop = false; def.direct_access = false; def.escaped = []; @@ -447,6 +446,20 @@ merge(Compressor.prototype, { tw.safe_ids[def.id] = safe; } + function add_assign(tw, def, node) { + if (def.fixed === false) return; + tw.assigns.add(def.id, node); + } + + function set_assign(tw, def, node) { + if (def.fixed === false) return; + var assigns = tw.assigns.get(def.id); + if (assigns) assigns.forEach(function(node) { + node.assigns = assigns; + }); + tw.assigns.set(def.id, def.assigns = [ node ]); + } + function safe_to_read(tw, def) { if (def.single_use == "m") return false; if (tw.safe_ids[def.id]) { @@ -561,7 +574,6 @@ merge(Compressor.prototype, { var eq = node.operator == "="; var value = eq ? node.right : node; if (is_modified(compressor, tw, node, value, 0)) return; - if (!eq) d.chained = true; sym.fixed = d.fixed = eq ? function() { return node.right; } : function() { @@ -576,7 +588,12 @@ merge(Compressor.prototype, { mark(tw, d, false); node.right.walk(tw); mark(tw, d, true); - if (eq) mark_escaped(tw, d, sym.scope, node, value, 0, 1); + if (eq) { + mark_escaped(tw, d, sym.scope, node, value, 0, 1); + set_assign(tw, d, node); + } else { + add_assign(tw, d, node); + } return true; }); def(AST_Binary, function(tw) { @@ -821,7 +838,6 @@ merge(Compressor.prototype, { d.assignments++; var fixed = d.fixed; if (!fixed) return; - d.chained = true; exp.fixed = d.fixed = function() { return make_node(AST_Binary, node, { operator: node.operator.slice(0, -1), @@ -837,6 +853,7 @@ merge(Compressor.prototype, { if (!safe) return; d.references.push(exp); mark(tw, d, true); + add_assign(tw, d, node); return true; }); def(AST_VarDef, function(tw, descend) { @@ -851,6 +868,7 @@ merge(Compressor.prototype, { mark(tw, d, false); descend(); mark(tw, d, true); + set_assign(tw, d, node); return true; } else { d.fixed = false; @@ -875,6 +893,8 @@ merge(Compressor.prototype, { reset_flags(node); return node.reduce_vars(tw, descend, compressor); } : reset_flags); + // Assignment chains + tw.assigns = new Dictionary(); // Flow control for visiting `AST_Defun`s tw.defun_ids = Object.create(null); tw.defun_visited = Object.create(null); @@ -895,10 +915,10 @@ merge(Compressor.prototype, { } }); - AST_Symbol.DEFMETHOD("fixed_value", function(final) { + AST_Symbol.DEFMETHOD("fixed_value", function() { var fixed = this.definition().fixed; if (!fixed) return fixed; - if (!final && this.fixed) fixed = this.fixed; + if (this.fixed) fixed = this.fixed; return fixed instanceof AST_Node ? fixed : fixed(); }); @@ -1062,6 +1082,10 @@ merge(Compressor.prototype, { return node instanceof AST_SymbolRef && node.definition().undeclared; } + function get_rvalue(expr) { + return expr[expr instanceof AST_Assign ? "right" : "value"]; + } + var global_names = makePredicate("Array Boolean clearInterval clearTimeout console Date decodeURI decodeURIComponent encodeURI encodeURIComponent Error escape eval EvalError Function isFinite isNaN JSON Math Number parseFloat parseInt RangeError ReferenceError RegExp Object setInterval setTimeout String SyntaxError TypeError unescape URIError"); AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) { return this.defined @@ -1779,10 +1803,6 @@ merge(Compressor.prototype, { return candidate instanceof AST_Assign && candidate.operator == "=" && candidate.right; } - function get_rvalue(expr) { - return expr[expr instanceof AST_Assign ? "right" : "value"]; - } - function invariant(expr) { if (expr instanceof AST_Array) return false; if (expr instanceof AST_Binary && lazy_op[expr.operator]) { @@ -4061,9 +4081,9 @@ merge(Compressor.prototype, { })) return; return sym; }; + var chained = Object.create(null); var in_use = []; var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use - var fixed_ids = Object.create(null); var value_read = Object.create(null); var value_modified = Object.create(null); if (self instanceof AST_Toplevel && compressor.top_retain) { @@ -4074,6 +4094,7 @@ merge(Compressor.prototype, { } }); } + var assign_in_use = new Dictionary(); var var_defs_by_id = new Dictionary(); var initializations = new Dictionary(); // pass 1: find out which symbols are directly used in @@ -4115,13 +4136,12 @@ merge(Compressor.prototype, { } } if (def.value) { - initializations.add(node_def.id, def.value); if (def.value.has_side_effects(compressor)) { def.value.walk(tw); + } else { + initializations.add(node_def.id, def.value); } - if (!node_def.chained && def.name.fixed_value(true) === def.value) { - fixed_ids[node_def.id] = def; - } + match_assigns(node_def, def); } }); return true; @@ -4160,7 +4180,7 @@ merge(Compressor.prototype, { var in_use = def.id in in_use_ids; var value; if (node instanceof AST_Assign) { - if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) { + if (!in_use || node.left === sym && indexOf_assign(def, node) < 0) { value = get_rhs(node); if (node.write_only === true) { value = value.drop_side_effect_free(compressor) || make_node(AST_Number, node, { @@ -4168,7 +4188,7 @@ merge(Compressor.prototype, { }); } } - } else if (!in_use) { + } else if (!in_use || node.expression === sym && indexOf_assign(def, node) < 0) { value = make_node(AST_Number, node, { value: 0 }); @@ -4234,7 +4254,7 @@ merge(Compressor.prototype, { if (def.value) def.value = def.value.transform(tt); var sym = def.name.definition(); if (!drop_vars || sym.id in in_use_ids) { - if (def.value && sym.id in fixed_ids && fixed_ids[sym.id] !== def) { + if (def.value && indexOf_assign(sym, def) < 0) { def.value = def.value.drop_side_effect_free(compressor); } var var_defs = var_defs_by_id.get(sym.id); @@ -4326,7 +4346,8 @@ merge(Compressor.prototype, { left: ref, right: def.value }); - if (fixed_ids[sym.id] === def) fixed_ids[sym.id] = assign; + var index = indexOf_assign(sym, def); + if (index >= 0) assign_in_use.get(sym.id)[index] = assign; sym.eliminated++; return assign.transform(tt); })); @@ -4444,6 +4465,32 @@ merge(Compressor.prototype, { }; } + function match_assigns(def, node) { + if (!def.fixed) return; + if (!def.assigns) return; + if (find_if(node instanceof AST_Unary ? function(assign) { + return assign === node; + } : function(assign) { + if (assign === node) return true; + if (assign instanceof AST_Unary) return false; + return get_rvalue(assign) === get_rvalue(node); + }, def.assigns)) { + assign_in_use.add(def.id, node); + } + } + + function indexOf_assign(def, node) { + if (!def.fixed) return; + if (!def.assigns) return; + var assigns = assign_in_use.get(def.id); + if (!assigns) return; + if (assigns.length != def.assigns.length) return; + var index = assigns.indexOf(node); + if (index >= 0 || !chained[def.id] || node.assigns && all(node.assigns, function(assign) { + return assign.write_only || assign.operator == "=" || assign instanceof AST_VarDef; + })) return index; + } + function verify_safe_usage(def, read, modified) { if (def.id in in_use_ids) return; if (read && modified) { @@ -4465,6 +4512,18 @@ merge(Compressor.prototype, { } function scan_ref_scoped(node, descend, init) { + if (scope === self) { + if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef) { + var node_def = node.left.definition(); + if (node.operator != "=") chained[node_def.id] = true; + match_assigns(node_def, node); + } + if (node instanceof AST_Unary && node.expression instanceof AST_SymbolRef) { + var node_def = node.expression.definition(); + chained[node_def.id] = true; + match_assigns(node_def, node); + } + } var node_def, props = [], sym = assign_as_unused(node, props); if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) { props.forEach(function(prop) { @@ -4479,9 +4538,6 @@ merge(Compressor.prototype, { right.walk(tw); } if (node.left === sym) { - if (!node_def.chained && sym.fixed_value(true) === right) { - fixed_ids[node_def.id] = node; - } if (!node.write_only) { verify_safe_usage(node_def, true, value_modified[node_def.id]); } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index a39dd480..d7d92bb2 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -2489,7 +2489,7 @@ drop_duplicated_var_catch: { } } -issue_3802: { +issue_3802_1: { options = { functions: true, reduce_vars: true, @@ -2510,3 +2510,25 @@ issue_3802: { } expect_stdout: "function" } + +issue_3802_2: { + options = { + functions: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = 0; + a += 0; + var a = function() {}; + console.log(typeof a); + } + expect: { + 0; + function a() {}; + console.log(typeof a); + } + expect_stdout: "function" +} -- 2.34.1