From 10a1523ee60ed90b7544b0d913f0c5d228e56a14 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 6 Jan 2022 21:13:37 +0000 Subject: [PATCH] enhance `collapse_vars` (#5268) --- lib/compress.js | 90 +++++++++++++++++++++++----------- test/compress/assignments.js | 19 +++++++ test/compress/collapse_vars.js | 37 ++++++++++++++ test/compress/sequences.js | 2 +- 4 files changed, 119 insertions(+), 29 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 9320d5a7..a7ceaf3e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1943,16 +1943,46 @@ Compressor.prototype.compress = function(node) { if (stop_after === node) abort = true; return node; } - // Stop immediately if these node types are encountered var parent = scanner.parent(); - if (should_stop(node, parent)) { - abort = true; - return node; - } // Stop only if candidate is found within conditional branches if (!stop_if_hit && in_conditional(node, parent)) { stop_if_hit = parent; } + // Cascade compound assignments + if (compound && scan_lhs && can_replace && !stop_if_hit + && node instanceof AST_Assign && node.operator != "=" && node.left.equivalent_to(lhs)) { + replaced++; + changed = true; + AST_Node.info("Cascading {node} [{file}:{line},{col}]", { + node: node, + file: node.start.file, + line: node.start.line, + col: node.start.col, + }); + can_replace = false; + node.right.transform(scanner); + clear_write_only(candidate); + var assign = make_node(AST_Assign, node, { + operator: "=", + left: node.left, + right: make_node(AST_Binary, node, { + operator: node.operator.slice(0, -1), + left: abort ? candidate : make_node(AST_Binary, candidate, { + operator: compound, + left: lhs, + right: rvalue, + }), + right: node.right, + }), + }); + abort = true; + return assign; + } + // Stop immediately if these node types are encountered + if (should_stop(node, parent)) { + abort = true; + return node; + } // Skip transient nodes caused by single-use variable replacement if (node.single_use && parent instanceof AST_VarDef && parent.value === node) return node; // Replace variable with assignment when found @@ -2006,13 +2036,8 @@ Compressor.prototype.compress = function(node) { right: rvalue, }); } - var assign = candidate; - while (assign.write_only) { - assign.write_only = false; - if (!(assign instanceof AST_Assign)) break; - assign = assign.right; - } - assign = candidate.clone(); + clear_write_only(candidate); + var assign = candidate.clone(); assign.right = rvalue; return assign; } @@ -2095,6 +2120,12 @@ Compressor.prototype.compress = function(node) { if (node instanceof AST_SymbolRef && node.definition() === def) { if (is_lhs(node, multi_replacer.parent())) return node; if (!--replaced) abort = true; + AST_Node.info("Replacing {node} [{file}:{line},{col}]", { + node: node, + file: node.start.file, + line: node.start.line, + col: node.start.col, + }); var ref = rvalue.clone(); ref.scope = node.scope; ref.reference(); @@ -2138,6 +2169,7 @@ Compressor.prototype.compress = function(node) { var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor); var scan_rhs = foldable(candidate); if (!scan_lhs && !scan_rhs) continue; + var compound = candidate instanceof AST_Assign && candidate.operator.slice(0, -1); var funarg = candidate.name instanceof AST_SymbolFunarg; var may_throw = return_false; if (candidate.may_throw(compressor)) { @@ -2154,13 +2186,7 @@ Compressor.prototype.compress = function(node) { var lvalues = get_lvalues(candidate); var lhs_local = is_lhs_local(lhs); var rhs_value = get_rvalue(candidate); - var rvalue; - if (rhs_value instanceof AST_Sequence - && !(candidate instanceof AST_Assign && candidate.operator != "=")) { - rvalue = rhs_value.tail_node(); - } else { - rvalue = rhs_value; - } + var rvalue = !compound && rhs_value instanceof AST_Sequence ? rhs_value.tail_node() : rhs_value; if (!side_effects) side_effects = value_has_side_effects(); var check_destructured = in_try || !lhs_local ? function(node) { return node instanceof AST_Destructured; @@ -2834,7 +2860,6 @@ Compressor.prototype.compress = function(node) { function get_lhs(expr) { if (expr instanceof AST_Assign) { var lhs = expr.left; - if (expr.operator != "=") return lhs; if (!(lhs instanceof AST_SymbolRef)) return lhs; var def = lhs.definition(); if (scope.uses_arguments && is_funarg(def)) return lhs; @@ -2849,7 +2874,7 @@ Compressor.prototype.compress = function(node) { assign_pos = 0; } } - mangleable_var(expr.right); + if (expr.operator == "=") mangleable_var(expr.right); return lhs; } if (expr instanceof AST_Binary) return expr.right.left; @@ -2952,6 +2977,14 @@ Compressor.prototype.compress = function(node) { }; } + function clear_write_only(assign) { + while (assign.write_only) { + assign.write_only = false; + if (!(assign instanceof AST_Assign)) break; + assign = assign.right; + } + } + function may_be_global(node) { if (node instanceof AST_SymbolRef) { node = node.fixed_value(); @@ -3118,12 +3151,13 @@ Compressor.prototype.compress = function(node) { function is_lhs_local(lhs) { var sym = root_expr(lhs); - return sym instanceof AST_SymbolRef - && sym.definition().scope.resolve() === scope - && !(in_loop - && (lvalues.has(sym.name) && lvalues.get(sym.name)[0] !== lhs - || candidate instanceof AST_Unary - || candidate instanceof AST_Assign && candidate.operator != "=")); + if (!(sym instanceof AST_SymbolRef)) return false; + if (sym.definition().scope.resolve() !== scope) return false; + if (!in_loop) return true; + if (compound) return false; + if (candidate instanceof AST_Unary) return false; + var lvalue = lvalues.get(sym.name); + return !lvalue || lvalue[0] === lhs; } function value_has_side_effects() { @@ -7099,7 +7133,7 @@ Compressor.prototype.compress = function(node) { log(node.name, text); } else { AST_Node.info(text + " [{file}:{line},{col}]", { - name: node.print_to_string(), + name: node, file: node.start.file, line: node.start.line, col : node.start.col, diff --git a/test/compress/assignments.js b/test/compress/assignments.js index 9e831721..84904102 100644 --- a/test/compress/assignments.js +++ b/test/compress/assignments.js @@ -530,6 +530,25 @@ logical_collapse_vars_2: { node_version: ">=15" } +logical_collapse_vars_3: { + options = { + collapse_vars: true, + } + input: { + var a = 6; + a *= 7; + a ??= "FAIL"; + console.log(a); + } + expect: { + var a = 6; + a = a * 7 ?? "FAIL"; + console.log(a); + } + expect_stdout: "42" + node_version: ">=15" +} + logical_reduce_vars: { options = { evaluate: true, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 03a375bd..e05b3d47 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2995,6 +2995,43 @@ compound_assignment_4: { expect_stdout: "PASS" } +compound_assignment_5: { + options = { + collapse_vars: true, + } + input: { + var a = 0, b; + a += 42; + b && (a *= null); + console.log(a); + } + expect: { + var a = 0, b; + a += 42; + b && (a *= null); + console.log(a); + } + expect_stdout: "42" +} + +compound_assignment_6: { + options = { + collapse_vars: true, + } + input: { + var a; + a ^= 6; + a *= a + 1; + console.log(a); + } + expect: { + var a; + a = (a ^= 6) * (a + 1); + console.log(a); + } + expect_stdout: "42" +} + issue_2187_1: { options = { collapse_vars: true, diff --git a/test/compress/sequences.js b/test/compress/sequences.js index efd8150d..59e0d563 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -774,7 +774,7 @@ side_effects_cascade_3: { } expect: { function f(a, b) { - (b += a) || (b = a) || (b -= a, b ^= a), + (b += a) || (b = a) || (b = b - a ^ a), a--; } }