From cfe3a98ce50a1eb844654da57b4ef47a750feda5 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 4 Jan 2018 01:03:33 +0800 Subject: [PATCH] drop `unused` assignment based on `reduce_vars` (#2709) --- lib/compress.js | 45 +++++++++---- lib/scope.js | 5 +- test/compress/drop-unused.js | 122 +++++++++++++++++++++++++++++++++++ test/compress/reduce_vars.js | 41 +++++++++++- test/compress/typeof.js | 40 ++++++++++++ 5 files changed, 238 insertions(+), 15 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 1e096ade..fd59fb32 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -311,6 +311,7 @@ merge(Compressor.prototype, { def(AST_Node, noop); function reset_def(compressor, def) { + def.assignments = 0; def.direct_access = false; def.escaped = false; if (def.scope.uses_eval || def.scope.uses_with) { @@ -364,6 +365,7 @@ merge(Compressor.prototype, { } function safe_to_assign(tw, def, value) { + if (def.fixed === undefined) return true; if (def.fixed === null && def.safe_ids) { def.safe_ids[def.id] = false; delete def.safe_ids; @@ -372,7 +374,7 @@ merge(Compressor.prototype, { if (!HOP(tw.safe_ids, def.id)) return false; if (!safe_to_read(tw, def)) return false; if (def.fixed === false) return false; - if (def.fixed != null && (!value || def.references.length > 0)) return false; + if (def.fixed != null && (!value || def.references.length > def.assignments)) return false; return all(def.orig, function(sym) { return !(sym instanceof AST_SymbolDefun || sym instanceof AST_SymbolLambda); @@ -477,6 +479,7 @@ merge(Compressor.prototype, { var d = node.left.definition(); if (safe_to_assign(tw, d, node.right)) { d.references.push(node.left); + d.assignments++; d.fixed = function() { return node.right; }; @@ -662,7 +665,7 @@ merge(Compressor.prototype, { def(AST_VarDef, function(tw, descend) { var node = this; var d = node.name.definition(); - if (d.fixed === undefined || safe_to_assign(tw, d, node.value)) { + if (safe_to_assign(tw, d, node.value)) { if (node.value) { d.fixed = function() { return node.value; @@ -2717,6 +2720,7 @@ merge(Compressor.prototype, { }; var in_use = []; var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use + var fixed_ids = Object.create(null); if (self instanceof AST_Toplevel && compressor.top_retain) { self.variables.each(function(def) { if (compressor.top_retain(def) && !(def.id in in_use_ids)) { @@ -2763,6 +2767,9 @@ merge(Compressor.prototype, { if (def.value.has_side_effects(compressor)) { def.value.walk(tw); } + if (def.name.fixed_value() === def.value) { + fixed_ids[node_def.id] = true; + } } }); return true; @@ -2786,12 +2793,16 @@ merge(Compressor.prototype, { var parent = tt.parent(); if (drop_vars) { var sym = assign_as_unused(node); - if (sym instanceof AST_SymbolRef - && !(sym.definition().id in in_use_ids)) { + if (sym instanceof AST_SymbolRef) { + var def = sym.definition(); + var in_use = def.id in in_use_ids; if (node instanceof AST_Assign) { - return maintain_this_binding(parent, node, node.right.transform(tt)); - } - return make_node(AST_Number, node, { + if (!in_use + || def.id in fixed_ids + && node.left.fixed_value() !== node.right) { + return maintain_this_binding(parent, node, node.right.transform(tt)); + } + } else if (!in_use) return make_node(AST_Number, node, { value: 0 }); } @@ -2851,13 +2862,16 @@ merge(Compressor.prototype, { operator: "=", left: make_node(AST_SymbolRef, def.name, def.name), right: def.value - })); + }).transform(tt)); } remove(var_defs, def); sym.eliminated++; return; } } + if (def.value && sym.id in fixed_ids && def.name.fixed_value() !== def.value) { + def.value = def.value.drop_side_effect_free(compressor); + } if (def.value) { if (side_effects.length > 0) { if (tail.length > 0) { @@ -2962,14 +2976,19 @@ merge(Compressor.prototype, { self.transform(tt); function scan_ref_scoped(node, descend) { - var sym; - if ((sym = assign_as_unused(node)) instanceof AST_SymbolRef - && self.variables.get(sym.name) === sym.definition()) { - if (node instanceof AST_Assign) node.right.walk(tw); + var node_def, sym = assign_as_unused(node); + if (sym instanceof AST_SymbolRef + && self.variables.get(sym.name) === (node_def = sym.definition())) { + if (node instanceof AST_Assign) { + node.right.walk(tw); + if (node.left.fixed_value() === node.right) { + fixed_ids[node_def.id] = true; + } + } return true; } if (node instanceof AST_SymbolRef) { - var node_def = node.definition(); + node_def = node.definition(); if (!(node_def.id in in_use_ids)) { in_use_ids[node_def.id] = true; in_use.push(node_def); diff --git a/lib/scope.js b/lib/scope.js index 79b2475d..de92fc94 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -151,7 +151,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.references = []; } if (node instanceof AST_SymbolLambda) { - defun.def_function(node); + defun.def_function(node, defun); } else if (node instanceof AST_SymbolDefun) { // Careful here, the scope where this should be defined is @@ -318,6 +318,9 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol, init){ var def = this.variables.get(symbol.name); if (def) { def.orig.push(symbol); + if (def.init && (def.scope !== symbol.scope || def.init instanceof AST_Function)) { + def.init = init; + } } else { def = new SymbolDef(this, symbol, init); this.variables.set(symbol.name, def); diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 21d4b7ce..5ad489b9 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1522,3 +1522,125 @@ issue_2665: { } expect_stdout: "-1" } + +double_assign_1: { + options = { + passes: 2, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + function f1() { + var a = {}; + var a = []; + return a; + } + function f2() { + var a = {}; + a = []; + return a; + } + function f3() { + a = {}; + var a = []; + return a; + } + function f4(a) { + a = {}; + a = []; + return a; + } + function f5(a) { + var a = {}; + a = []; + return a; + } + function f6(a) { + a = {}; + var a = []; + return a; + } + console.log(f1(), f2(), f3(), f4(), f5(), f6()); + } + expect: { + function f1() { + return []; + } + function f2() { + var a; + a = []; + return a; + } + function f3() { + return []; + } + function f4(a) { + a = []; + return a; + } + function f5(a) { + a = []; + return a; + } + function f6(a) { + a = []; + return a; + } + console.log(f1(), f2(), f3(), f4(), f5(), f6()); + } + expect_stdout: true +} + +double_assign_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + for (var i = 0; i < 2; i++) + a = void 0, a = {}, console.log(a); + var a; + } + expect: { + for (var i = 0; i < 2; i++) + void 0, a = {}, console.log(a); + var a; + } +} + +double_assign_3: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + for (var i = 0; i < 2; i++) + a = void 0, a = { a: a }, console.log(a); + var a; + } + expect: { + for (var i = 0; i < 2; i++) + a = void 0, a = { a: a }, console.log(a); + var a; + } +} + +cascade_drop_assign: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a, b = a = "PASS"; + console.log(b); + } + expect: { + var b = "PASS"; + console.log(b); + } + expect_stdout: "PASS" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index e370e5be..3d993b90 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -299,7 +299,7 @@ unsafe_evaluate_modified: { console.log(function(){ var o={p:1}; o.p++; console.log(o.p); return o.p; }()); console.log(function(){ var o={p:2}; --o.p; console.log(o.p); return o.p; }()); console.log(function(){ var o={p:3}; o.p += ""; console.log(o.p); return o.p; }()); - console.log(function(){ var o={p:4}; o = {}; console.log(o.p); return o.p; }()); + console.log(function(){ var o; o = {}; console.log(o.p); return o.p; }()); console.log(function(){ var o={p:5}; o.p = -9; console.log(o.p); return o.p; }()); function inc() { this.p++; } console.log(function(){ var o={p:6}; inc.call(o); console.log(o.p); return o.p; }()); @@ -5237,3 +5237,42 @@ defun_catch_6: { } expect_stdout: "42" } + +duplicate_lambda_defun_name_1: { + options = { + reduce_vars: true, + } + input: { + console.log(function f(a) { + function f() {} + return f.length; + }()); + } + expect: { + console.log(function f(a) { + function f() {} + return f.length; + }()); + } + expect_stdout: "0" +} + +duplicate_lambda_defun_name_2: { + options = { + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + console.log(function f(a) { + function f() {} + return f.length; + }()); + } + expect: { + console.log(function(a) { + return function() {}.length; + }()); + } + expect_stdout: "0" +} diff --git a/test/compress/typeof.js b/test/compress/typeof.js index 9eaf05e4..72e77beb 100644 --- a/test/compress/typeof.js +++ b/test/compress/typeof.js @@ -138,3 +138,43 @@ typeof_defun_2: { "2", ] } + +duplicate_defun_arg_name: { + options = { + evaluate: true, + reduce_vars: true, + typeofs: true, + } + input: { + function long_name(long_name) { + return typeof long_name; + } + console.log(typeof long_name, long_name()); + } + expect: { + function long_name(long_name) { + return typeof long_name; + } + console.log(typeof long_name, long_name()); + } + expect_stdout: "function undefined" +} + +duplicate_lambda_arg_name: { + options = { + evaluate: true, + reduce_vars: true, + typeofs: true, + } + input: { + console.log(function long_name(long_name) { + return typeof long_name; + }()); + } + expect: { + console.log(function long_name(long_name) { + return typeof long_name; + }()); + } + expect_stdout: "undefined" +} -- 2.34.1