From d6d2f5ced2ce7f16eb02138ec243c01fe2b5717a Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Wed, 17 Aug 2022 02:25:59 +0100 Subject: [PATCH] enhance `reduce_vars` (#5616) closes #5614 --- lib/compress.js | 68 ++++++++----- test/compress/dead-code.js | 2 - test/compress/issue-5614.js | 195 ++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+), 28 deletions(-) create mode 100644 test/compress/issue-5614.js diff --git a/lib/compress.js b/lib/compress.js index d86b2df5..6473da50 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1053,9 +1053,6 @@ Compressor.prototype.compress = function(node) { if (!fixed || sym.in_arg || !safe_to_assign(tw, d)) { walk(); d.fixed = false; - } else if (modified) { - walk(); - d.fixed = 0; } else { push_ref(d, sym); mark(tw, d); @@ -1064,7 +1061,8 @@ Compressor.prototype.compress = function(node) { d.single_use = false; } tw.loop_ids[d.id] = tw.in_loop; - sym.fixed = d.fixed = fixed; + d.fixed = modified ? 0 : fixed; + sym.fixed = fixed; sym.fixed.assigns = [ node ]; mark_escaped(tw, d, sym.scope, node, right, 0, 1); } @@ -1355,8 +1353,10 @@ Compressor.prototype.compress = function(node) { this.definition().fixed = false; }); def(AST_SymbolRef, function(tw, descend, compressor) { - var d = this.definition(); - push_ref(d, this); + var ref = this; + var d = ref.definition(); + var fixed = d.fixed || d.last_ref && d.last_ref.fixed; + push_ref(d, ref); if (d.references.length == 1 && !d.fixed && d.orig[0] instanceof AST_SymbolDefun) { tw.loop_ids[d.id] = tw.in_loop; } @@ -1371,14 +1371,27 @@ Compressor.prototype.compress = function(node) { if (!safe) return; safe.assign = true; }); - if (d.fixed === false || d.fixed === 0) { + if (d.single_use == "m" && d.fixed) { + d.fixed = 0; + d.single_use = false; + } + switch (d.fixed) { + case 0: + if (!safe_to_read(tw, d)) d.fixed = false; + case false: var redef = d.redefined(); - if (redef && cross_scope(d.scope, this.scope)) redef.single_use = false; - } else if (d.fixed === undefined || !safe_to_read(tw, d)) { + if (redef && cross_scope(d.scope, ref.scope)) redef.single_use = false; + break; + case undefined: d.fixed = false; - } else if (d.fixed) { - if (this.in_arg && d.orig[0] instanceof AST_SymbolLambda) this.fixed = d.scope; - var value = this.fixed_value(); + break; + default: + if (!safe_to_read(tw, d)) { + d.fixed = false; + break; + } + if (ref.in_arg && d.orig[0] instanceof AST_SymbolLambda) ref.fixed = d.scope; + var value = ref.fixed_value(); if (recursive) { d.recursive_refs++; } else if (value && ref_once(compressor, d)) { @@ -1387,12 +1400,12 @@ Compressor.prototype.compress = function(node) { && !value.pinned() && (!d.in_loop || tw.parent() instanceof AST_Call) || !d.in_loop - && d.scope === this.scope.resolve() + && d.scope === ref.scope.resolve() && value.is_constant_expression(); } else { d.single_use = false; } - if (is_modified(compressor, tw, this, value, 0, is_immutable(value), recursive)) { + if (is_modified(compressor, tw, ref, value, 0, is_immutable(value), recursive)) { if (d.single_use) { d.single_use = "m"; } else { @@ -1400,12 +1413,13 @@ Compressor.prototype.compress = function(node) { } } if (d.fixed && tw.loop_ids[d.id] !== tw.in_loop) d.cross_loop = true; - mark_escaped(tw, d, this.scope, this, value, 0, 1); + mark_escaped(tw, d, ref.scope, ref, value, 0, 1); + break; } - if (!this.fixed) this.fixed = d.fixed; + if (!ref.fixed) ref.fixed = d.fixed === 0 ? fixed : d.fixed; var parent; if (value instanceof AST_Lambda - && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) { + && !((parent = tw.parent()) instanceof AST_Call && parent.expression === ref)) { mark_fn_def(tw, d, value); } }); @@ -1578,8 +1592,9 @@ Compressor.prototype.compress = function(node) { this.walk(tw); }); - AST_Symbol.DEFMETHOD("fixed_value", function() { - var fixed = this.definition().fixed; + AST_Symbol.DEFMETHOD("fixed_value", function(ref_only) { + var def = this.definition(); + var fixed = def.fixed; if (fixed) { if (this.fixed) fixed = this.fixed; return (fixed instanceof AST_Node ? fixed : fixed()).tail_node(); @@ -1587,7 +1602,8 @@ Compressor.prototype.compress = function(node) { fixed = fixed === 0 && this.fixed; if (!fixed) return fixed; var value = (fixed instanceof AST_Node ? fixed : fixed()).tail_node(); - return value.is_constant() && value; + if (ref_only && def.escaped.depth != 1 && is_object(value, true)) return value; + if (value.is_constant()) return value; }); AST_SymbolRef.DEFMETHOD("is_immutable", function() { @@ -4510,7 +4526,7 @@ Compressor.prototype.compress = function(node) { if (is_arguments(def) && !def.scope.rest && all(def.scope.argnames, function(argname) { return argname instanceof AST_SymbolFunarg; })) return def.scope.uses_arguments > 2; - var fixed = this.fixed_value(); + var fixed = this.fixed_value(true); if (!fixed) return true; this._dot_throw = return_true; if (fixed._dot_throw(compressor)) { @@ -11454,14 +11470,14 @@ Compressor.prototype.compress = function(node) { var indexFns = makePredicate("indexOf lastIndexOf"); var commutativeOperators = makePredicate("== === != !== * & | ^"); - function is_object(node) { - if (node instanceof AST_Assign) return node.operator == "=" && is_object(node.right); - if (node instanceof AST_Sequence) return is_object(node.tail_node()); - if (node instanceof AST_SymbolRef) return is_object(node.fixed_value()); + function is_object(node, plain) { + if (node instanceof AST_Assign) return !plain && node.operator == "=" && is_object(node.right); + if (node instanceof AST_New) return !plain; + if (node instanceof AST_Sequence) return is_object(node.tail_node(), plain); + if (node instanceof AST_SymbolRef) return !plain && is_object(node.fixed_value()); return node instanceof AST_Array || node instanceof AST_Class || node instanceof AST_Lambda - || node instanceof AST_New || node instanceof AST_Object; } diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 37529b52..82443002 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -1485,8 +1485,6 @@ self_assignments_5: { } expect: { var i = 0, l = [ "FAIL", "PASS" ]; - l[0]; - l[0]; l[0] = l[1]; console.log(l[0], 2); } diff --git a/test/compress/issue-5614.js b/test/compress/issue-5614.js new file mode 100644 index 00000000..bdf6bfe1 --- /dev/null +++ b/test/compress/issue-5614.js @@ -0,0 +1,195 @@ +record_update: { + options = { + loops: true, + passes: 3, + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var value = { a: 42, b: "PASS" }; + var unused = _Utils_update(value, { b: "FAIL" }); + function _Utils_update(oldRecord, updatedFields) { + var newRecord = {}; + for (var key in oldRecord) + newRecord[key] = oldRecord[key]; + for (var key in updatedFields) + newRecord[key] = updatedFields[key]; + return newRecord; + } + } + expect: {} +} + +currying: { + options = { + inline: true, + passes: 2, + pure_getters: "strict", + reduce_funcs: true, + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + function F(arity, fun, wrapper) { + wrapper.a = arity; + wrapper.f = fun; + return wrapper; + } + function F2(fun) { + return F(2, fun, function(a) { + return function(b) { + return fun(a, b); + }; + }); + } + function _Utils_eq(x, y) { + var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack); + while (isEqual && (pair = stack.pop())) + isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack); + return isEqual; + } + var _Utils_equal = F2(_Utils_eq); + } + expect: {} +} + +conditional_property_write: { + options = { + pure_getters: "strict", + reduce_vars: true, + unused: true, + } + input: { + function f(a) { + var o = {}; + if (a) + o.p = console.log("foo"); + else + o.q = console.log("bar"); + o.r = console.log("baz"); + } + f(42); + f(null); + } + expect: { + function f(a) { + if (a) + console.log("foo"); + else + console.log("bar"); + console.log("baz"); + } + f(42); + f(null); + } + expect_stdout: [ + "foo", + "baz", + "bar", + "baz", + ] +} + +reassign_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = "PASS", b = "FAIL"; + (b = a).toString(); + console.log(b); + } + expect: { + var b = "FAIL"; + (b = "PASS").toString(); + console.log(b); + } + expect_stdout: "PASS" +} + +reassign_2: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + } + input: { + var a = "PASS"; + if (false) { + a = null + 0; + a(); + } + console.log(a); + } + expect: { + var a = "PASS"; + if (false) { + a = 0; + a(); + } + console.log(a); + } + expect_stdout: "PASS" +} + +reassign_3: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + } + input: { + var a = 0; + (a = a || "PASS").toString(); + console.log(a); + } + expect: { + var a = 0; + (a = (0, "PASS")).toString(); + console.log(a); + } + expect_stdout: "PASS" +} + +retain_instance_write: { + options = { + pure_getters: true, + reduce_vars: true, + unused: true, + } + input: { + function f(a) { + return a; + } + function g() { + var o = {}; + var b = new f(o); + if (console) + b.p = "PASS"; + return o; + } + console.log(g().p); + } + expect: { + function f(a) { + return a; + } + function g() { + var o = {}; + var b = new f(o); + if (console) + b.p = "PASS"; + return o; + } + console.log(g().p); + } + expect_stdout: "PASS" +}