From b24eb22c6b0ebbb3ec0274d76571864762c6393f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 1 Jun 2020 13:55:23 +0100 Subject: [PATCH] enhance `reduce_vars` (#3942) --- lib/compress.js | 300 ++++++++++++++++++--------------- test/compress/collapse_vars.js | 26 ++- test/compress/drop-unused.js | 64 ++++++- test/compress/evaluate.js | 12 +- test/compress/functions.js | 1 + test/compress/keep_fargs.js | 2 - test/compress/reduce_vars.js | 47 ++---- 7 files changed, 260 insertions(+), 192 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index adae9338..3728884f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -447,7 +447,7 @@ merge(Compressor.prototype, { } function mark(tw, def, safe) { - tw.safe_ids[def.id] = safe; + tw.safe_ids[def.id] = safe && {}; } function push_ref(def, ref) { @@ -455,44 +455,37 @@ merge(Compressor.prototype, { def.last_ref = ref; } - 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]) { + var safe = tw.safe_ids[def.id]; + if (safe) { + if (!HOP(tw.safe_ids, def.id)) safe.read = safe.read ? true : tw.safe_ids; if (def.fixed == null) { if (is_arguments(def)) return false; if (def.global && def.name == "arguments") return false; def.fixed = make_node(AST_Undefined, def.orig[0]); + return true; } - return true; + return !safe.assign || safe.assign === tw.safe_ids; } return def.fixed instanceof AST_Defun; } - function safe_to_assign(tw, def, value) { + function safe_to_assign(tw, def) { if (def.fixed === undefined) return true; if (def.fixed === null && def.safe_ids) { def.safe_ids[def.id] = false; delete def.safe_ids; return true; } - if (!HOP(tw.safe_ids, def.id)) return false; + var safe = tw.safe_ids[def.id]; + if (!HOP(tw.safe_ids, def.id)) { + if (!safe) return false; + safe.assign = safe.assign ? true : tw.safe_ids; + } if (!safe_to_read(tw, def)) return false; if (def.fixed === false) return false; - if (def.fixed != null && (!value || def.references.length > def.assignments)) return false; + if (def.fixed != null && safe.read && safe.read !== tw.safe_ids) return false; return all(def.orig, function(sym) { return !(sym instanceof AST_SymbolLambda); }); @@ -573,35 +566,40 @@ merge(Compressor.prototype, { mark_assignment_to_arguments(sym); return; } - if (sym.fixed) delete sym.fixed; var d = sym.definition(); - var safe = safe_to_assign(tw, d, node.right); d.assignments++; - var fixed = d.fixed; - if (!fixed && node.operator != "=") return; var eq = node.operator == "="; var value = eq ? node.right : node; if (is_modified(compressor, tw, node, value, 0)) return; - sym.fixed = d.fixed = eq ? function() { - return node.right; - } : function() { - var value = fixed instanceof AST_Node ? fixed : fixed(); - return value && make_node(AST_Binary, node, { - operator: node.operator.slice(0, -1), - left: value, - right: node.right - }); - }; - if (!safe) return; - push_ref(d, sym); - mark(tw, d, false); - node.right.walk(tw); - mark(tw, d, true); - if (eq) { - mark_escaped(tw, d, sym.scope, node, value, 0, 1); - set_assign(tw, d, node); + var safe = (eq || safe_to_read(tw, d)) && safe_to_assign(tw, d); + var fixed = d.fixed; + if (safe) { + push_ref(d, sym); + mark(tw, d, false); + node.right.walk(tw); + mark(tw, d, true); + if (eq) mark_escaped(tw, d, sym.scope, node, value, 0, 1); } else { - add_assign(tw, d, node); + descend(); + } + if (fixed !== false && d.fixed !== false) { + if (eq) { + sym.fixed = d.fixed = function() { + return node.right; + }; + } else { + if (fixed == null) fixed = make_node(AST_Undefined, d.orig[0]); + sym.fixed = d.fixed = function() { + var value = fixed instanceof AST_Node ? fixed : fixed(); + return value && make_node(AST_Binary, node, { + operator: node.operator.slice(0, -1), + left: value, + right: node.right + }); + }; + } + sym.fixed.assigns = eq || !fixed.assigns ? [] : fixed.assigns.slice(); + sym.fixed.assigns.push(node); } return true; }); @@ -740,13 +738,13 @@ merge(Compressor.prototype, { fn.argnames.forEach(function(arg, i) { var d = arg.definition(); if (d.fixed === undefined && (!fn.uses_arguments || tw.has_directive("use strict"))) { + tw.loop_ids[d.id] = tw.in_loop; + mark(tw, d, true); var value = iife.args[i]; d.fixed = function() { var j = fn.argnames.indexOf(arg); return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); }; - tw.loop_ids[d.id] = tw.in_loop; - mark(tw, d, true); } else { d.fixed = false; } @@ -787,7 +785,6 @@ merge(Compressor.prototype, { this.definition().fixed = false; }); def(AST_SymbolRef, function(tw, descend, compressor) { - if (this.fixed) delete this.fixed; var d = this.definition(); push_ref(d, this); if (d.references.length == 1 @@ -826,6 +823,7 @@ merge(Compressor.prototype, { } mark_escaped(tw, d, this.scope, this, value, 0, 1); } + this.fixed = d.fixed; var parent; if (d.fixed instanceof AST_Defun && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) { @@ -864,51 +862,63 @@ merge(Compressor.prototype, { mark_assignment_to_arguments(exp); return; } - if (exp.fixed) delete exp.fixed; var d = exp.definition(); - var safe = safe_to_assign(tw, d, true); d.assignments++; + var safe = safe_to_read(tw, d) && safe_to_assign(tw, d); var fixed = d.fixed; - if (!fixed) return; - d.fixed = function() { - var value = fixed instanceof AST_Node ? fixed : fixed(); - return value && make_node(AST_Binary, node, { - operator: node.operator.slice(0, -1), - left: make_node(AST_UnaryPrefix, node, { - operator: "+", - expression: value - }), - right: make_node(AST_Number, node, { - value: 1 - }) - }); - }; - exp.fixed = node instanceof AST_UnaryPrefix ? d.fixed : function() { - var value = fixed instanceof AST_Node ? fixed : fixed(); - return value && make_node(AST_UnaryPrefix, node, { - operator: "+", - expression: value - }); - }; - if (!safe) return; - push_ref(d, exp); - mark(tw, d, true); - add_assign(tw, d, node); + if (safe) { + push_ref(d, exp); + mark(tw, d, true); + } else { + descend(); + } + if (fixed !== false && d.fixed !== false) { + if (fixed == null) fixed = make_node(AST_Undefined, d.orig[0]); + d.fixed = function() { + var value = fixed instanceof AST_Node ? fixed : fixed(); + return value && make_node(AST_Binary, node, { + operator: node.operator.slice(0, -1), + left: make_node(AST_UnaryPrefix, node, { + operator: "+", + expression: value + }), + right: make_node(AST_Number, node, { + value: 1 + }) + }); + }; + d.fixed.assigns = fixed.assigns ? fixed.assigns.slice() : []; + d.fixed.assigns.push(node); + if (node instanceof AST_UnaryPrefix) { + exp.fixed = d.fixed; + } else { + exp.fixed = function() { + var value = fixed instanceof AST_Node ? fixed : fixed(); + return value && make_node(AST_UnaryPrefix, node, { + operator: "+", + expression: value + }); + }; + exp.fixed.assigns = fixed.assigns; + } + } return true; }); def(AST_VarDef, function(tw, descend) { var node = this; var d = node.name.definition(); if (node.value) { - if (safe_to_assign(tw, d, node.value)) { - d.fixed = function() { - return node.value; - }; + if (safe_to_assign(tw, d)) { tw.loop_ids[d.id] = tw.in_loop; mark(tw, d, false); descend(); mark(tw, d, true); - set_assign(tw, d, node); + if (d.fixed !== false) { + d.fixed = function() { + return node.value; + }; + d.fixed.assigns = [ node ]; + } return true; } else { d.fixed = false; @@ -933,8 +943,6 @@ 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); @@ -951,6 +959,7 @@ merge(Compressor.prototype, { function reset_flags(node) { node._squeezed = false; node._optimized = false; + delete node.fixed; if (node instanceof AST_Scope) delete node._var_names; } }); @@ -4222,7 +4231,7 @@ merge(Compressor.prototype, { })) return; return sym; }; - var chained = Object.create(null); + var assign_in_use = Object.create(null); var in_use = []; var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use var value_read = Object.create(null); @@ -4235,7 +4244,7 @@ merge(Compressor.prototype, { } }); } - var assign_in_use = new Dictionary(); + var assignments = new Dictionary(); var var_defs_by_id = new Dictionary(); var initializations = new Dictionary(); // pass 1: find out which symbols are directly used in @@ -4282,7 +4291,7 @@ merge(Compressor.prototype, { } else { initializations.add(node_def.id, def.value); } - match_assigns(node_def, def); + assignments.add(node_def.id, def); } }); return true; @@ -4300,6 +4309,33 @@ merge(Compressor.prototype, { init.walk(tw); }); } + Object.keys(assign_in_use).forEach(function(id) { + var assigns = assign_in_use[id]; + if (!assigns) { + delete assign_in_use[id]; + return; + } + assigns = assigns.reduce(function(in_use, assigns) { + assigns.forEach(function(assign) { + push_uniq(in_use, assign); + }); + return in_use; + }, []); + var in_use = (assignments.get(id) || []).filter(function(node) { + return 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); + }, assigns); + }); + if (assigns.length == in_use.length) { + assign_in_use[id] = in_use; + } else { + delete assign_in_use[id]; + } + }); var drop_fn_name = compressor.option("keep_fnames") ? return_false : compressor.option("ie8") ? function(def) { return !compressor.exposed(def) && def.references.length == def.replaced; } : function(def) { @@ -4487,7 +4523,7 @@ merge(Compressor.prototype, { right: def.value }); var index = indexOf_assign(sym, def); - if (index >= 0) assign_in_use.get(sym.id)[index] = assign; + if (index >= 0) assign_in_use[sym.id][index] = assign; sym.eliminated++; return assign.transform(tt); })); @@ -4605,30 +4641,20 @@ 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 track_assigns(def, node) { + if (def.scope !== self) return false; + if (!def.fixed || !node.fixed) assign_in_use[def.id] = false; + return assign_in_use[def.id] !== false; + } + + function add_assigns(def, node) { + if (!assign_in_use[def.id]) assign_in_use[def.id] = []; + if (node.fixed.assigns) push_uniq(assign_in_use[def.id], node.fixed.assigns); } 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; + var nodes = assign_in_use[def.id]; + return nodes && nodes.indexOf(node); } function verify_safe_usage(def, read, modified) { @@ -4653,14 +4679,12 @@ merge(Compressor.prototype, { function scan_ref_scoped(node, descend, init) { 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); + var def = node.left.definition(); + if (def.scope === self) assignments.add(def.id, 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 def = node.expression.definition(); + if (def.scope === self) assignments.add(def.id, node); } var node_def, props = [], sym = assign_as_unused(node, props); if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) { @@ -4686,6 +4710,7 @@ merge(Compressor.prototype, { } } } + track_assigns(node_def, sym); return true; } if (node instanceof AST_SymbolRef) { @@ -4694,6 +4719,7 @@ merge(Compressor.prototype, { in_use_ids[node_def.id] = true; in_use.push(node_def); } + if (track_assigns(node_def, node)) add_assigns(node_def, node); return true; } if (node instanceof AST_Scope) { @@ -7641,7 +7667,8 @@ merge(Compressor.prototype, { def.replaced++; return value; } - if (fixed && def.should_replace === undefined) { + var local = self.fixed !== def.fixed; + if (fixed && (local || def.should_replace !== false)) { var init; if (fixed instanceof AST_This) { if (!(def.orig[0] instanceof AST_SymbolFunarg) && same_scope(def)) { @@ -7659,36 +7686,33 @@ merge(Compressor.prototype, { } } if (init) { - var value_length = init.optimize(compressor).print_to_string().length; - var fn; - if (has_symbol_ref(fixed)) { - fn = function() { - var result = init.optimize(compressor); - return result === init ? result.clone(true) : result; - }; - } else { - value_length = Math.min(value_length, fixed.print_to_string().length); - fn = function() { - var result = best_of_expression(init.optimize(compressor), fixed); - return result === init || result === fixed ? result.clone(true) : result; - }; + if (!local && def.should_replace === undefined) { + var value_length = init.optimize(compressor).print_to_string().length; + if (!has_symbol_ref(fixed)) { + value_length = Math.min(value_length, fixed.print_to_string().length); + } + var name_length = def.name.length; + if (compressor.option("unused") && !compressor.exposed(def)) { + var referenced = def.references.length - def.replaced; + name_length += (name_length + 2 + value_length) / (referenced - def.assignments); + } + var delta = value_length - Math.floor(name_length); + def.should_replace = delta < compressor.eval_threshold; } - var name_length = def.name.length; - if (compressor.option("unused") && !compressor.exposed(def)) { - var referenced = def.references.length - def.replaced; - name_length += (name_length + 2 + value_length) / (referenced - def.assignments); + if (local || def.should_replace) { + var value; + if (has_symbol_ref(fixed)) { + value = init.optimize(compressor); + if (value === init) value = value.clone(true); + } else { + value = best_of_expression(init.optimize(compressor), fixed); + if (value === init || value === fixed) value = value.clone(true); + } + def.replaced++; + return value; } - var delta = value_length - Math.floor(name_length); - def.should_replace = delta < compressor.eval_threshold ? fn : false; - } else { - def.should_replace = false; } } - if (def.should_replace) { - var value = def.should_replace(); - def.replaced++; - return value; - } } return self; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 6244e03f..546e0d3b 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3001,8 +3001,6 @@ issue_2298: { expect: { !function() { (function() { - var a = undefined; - var undefined = a++; try { !function(b) { (void 0)[1] = "foo"; @@ -3836,20 +3834,19 @@ issue_2436_3: { }(o)); } expect: { - var o = { - a: 1, - b: 2, - }; console.log(function(c) { - o = { + ({ a: 3, b: 4, - }; + }); return { x: c.a, y: c.b, }; - }(o)); + }({ + a: 1, + b: 2, + })); } expect_stdout: true } @@ -4071,16 +4068,15 @@ issue_2436_10: { }(o).join(" ")); } expect: { - var o = { - a: 1, - b: 2, - }; function f(n) { - o = { b: 3 }; + ({ b: 3 }); return n; } console.log([ - (c = o).a, + (c = { + a: 1, + b: 2, + }).a, f(c.b), c.b, ].join(" ")); diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index e3166970..b21ca399 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1729,7 +1729,7 @@ chained_3: { } expect: { console.log(function(a, b) { - var c = b; + var c = 2; b++; return c; }(0, 2)); @@ -2557,7 +2557,7 @@ issue_3899: { console.log(typeof a); } expect: { - ++a; + 0; var a = function() { return 2; }; @@ -2565,3 +2565,63 @@ issue_3899: { } expect_stdout: "function" } + +cross_scope_assign_chain: { + options = { + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a, b = 0; + (function() { + a = b; + a++; + while (b++); + })(); + console.log(a ? "PASS" : "FAIL"); + } + expect: { + var a, b = 0; + (function() { + a = b; + a++; + while (b++); + })(); + console.log(a ? "PASS" : "FAIL"); + } + expect_stdout: "PASS" +} + +assign_if_assign_read: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function(a) { + var b; + do { + b = "FAIL"; + if (Array.isArray(a)) { + b = a[0]; + console.log(b); + } + } while (!console); + })([ "PASS" ]); + } + expect: { + (function(a) { + var b; + do { + "FAIL"; + if (Array.isArray(a)) { + b = a[0]; + console.log(b); + } + } while (!console); + })([ "PASS" ]); + } + expect_stdout: "PASS" +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 0cf1d892..6deabafa 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -2412,9 +2412,9 @@ issue_3887: { expect: { (function(b) { try { - b-- && console.log("PASS"); + 1, console.log("PASS"); } catch (a_2) {} - })(1); + })(); } expect_stdout: "PASS" } @@ -2478,10 +2478,10 @@ issue_3920: { console.log(a); } expect: { - var a = function(b) { - return (b[b = 0] = 0) >= (b ? 0 : 1); - }("foo"); - console.log(a); + (function(b) { + "foo"[0] = 0; + })(); + console.log(false); } expect_stdout: "false" } diff --git a/test/compress/functions.js b/test/compress/functions.js index 9607c593..1383dbe9 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -2336,6 +2336,7 @@ issue_3274: { inline: true, join_vars: true, loops: true, + passes: 2, reduce_vars: true, unused: true, } diff --git a/test/compress/keep_fargs.js b/test/compress/keep_fargs.js index c2a4ceef..e1737081 100644 --- a/test/compress/keep_fargs.js +++ b/test/compress/keep_fargs.js @@ -306,8 +306,6 @@ issue_2298: { expect: { !function() { (function() { - var a = undefined; - var undefined = a++; try { !function() { (void 0)[1] = "foo"; diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 9168e81b..cb7599a9 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -905,7 +905,7 @@ use_before_var: { } expect: { function f(){ - console.log(t); + console.log(void 0); var t = 1; } } @@ -981,12 +981,12 @@ inner_var_for_1: { expect: { function f() { var a = 1; - x(1, b, d); - for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) { + x(1, void 0, d); + for (var b = 2, c = 3; x(1, 2, 3, d); x(1, 2, 3, d)) { var d = 4, e = 5; - x(1, b, 3, d, e); + x(1, 2, 3, d, e); } - x(1, b, 3, d, e); + x(1, 2, 3, d, e); } } } @@ -1521,9 +1521,7 @@ func_inline: { expect: { function f() { console.log(1 + h()); - var h = function() { - return 2; - }; + var h; } } } @@ -2372,8 +2370,7 @@ delay_def: { return; } function g() { - return a; - var a = 1; + return; } console.log(f(), g()); } @@ -2395,7 +2392,7 @@ delay_def_lhs: { expect: { console.log(function() { long_name++; - return long_name; + return NaN; var long_name; }()); } @@ -2651,11 +2648,9 @@ var_assign_5: { } expect: { !function() { - var a; !function(b) { - a = 2, - console.log(a, b); - }(a); + console.log(2, void 0); + }(); }(); } expect_stdout: "2 undefined" @@ -4725,7 +4720,7 @@ escape_conditional: { function bar() {} (function() { var thing = baz(); - if (thing !== (thing = baz())) + if (thing !== baz()) console.log("FAIL"); else console.log("PASS"); @@ -4763,7 +4758,7 @@ escape_sequence: { function bar() {} (function() { var thing = baz(); - if (thing !== (thing = baz())) + if (thing !== baz()) console.log("FAIL"); else console.log("PASS"); @@ -4808,7 +4803,7 @@ escape_throw: { function foo() {} (function() { var thing = baz(); - if (thing !== (thing = baz())) + if (thing !== baz()) console.log("FAIL"); else console.log("PASS"); @@ -4845,7 +4840,7 @@ escape_local_conditional: { } (function() { var thing = baz(); - if (thing !== (thing = baz())) + if (thing !== baz()) console.log("PASS"); else console.log("FAIL"); @@ -4882,7 +4877,7 @@ escape_local_sequence: { } (function() { var thing = baz(); - if (thing !== (thing = baz())) + if (thing !== baz()) console.log("PASS"); else console.log("FAIL"); @@ -4926,7 +4921,7 @@ escape_local_throw: { } (function() { var thing = baz(); - if (thing !== (thing = baz())) + if (thing !== baz()) console.log("PASS"); else console.log("FAIL"); @@ -4980,11 +4975,7 @@ inverted_var: { console.log(1, 2, 3, 4, 5, function c() { c = 6; return c; - }(), 7, function() { - c = 8; - return c; - var c = "foo"; - }()); + }(), 7, 8); } expect_stdout: true } @@ -5180,9 +5171,7 @@ defun_var_3: { var a = 42, b; } expect: { - function a() {} - console.log(typeof a, "function"); - var a = 42; + console.log("function", "function"); } expect_stdout: "function function" } -- 2.34.1