From d40950b741d67bafc91a102ce2c6e862af46fd11 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 2 Jul 2017 01:05:14 +0800 Subject: [PATCH 1/4] improve `inline` efficiency (#2188) ... by teaching `collapse_vars` some new tricks. fixes #2187 --- lib/compress.js | 114 ++++++++++++++++++--------------- test/compress/collapse_vars.js | 35 +++++++++- test/compress/drop-unused.js | 5 +- test/compress/functions.js | 6 +- test/compress/issue-281.js | 24 +++++-- test/mocha/glob.js | 4 +- 6 files changed, 119 insertions(+), 69 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 352be282834..dffdd6ed1f0 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -714,15 +714,23 @@ merge(Compressor.prototype, { var candidates = []; var stat_index = statements.length; while (--stat_index >= 0) { + // Treat parameters as collapsible in IIFE, i.e. + // function(a, b){ ... }(x()); + // would be translated into equivalent assignments: + // var a = x(), b = undefined; + if (stat_index == 0 && compressor.option("unused")) extract_args(); + // Find collapsible assignments extract_candidates(statements[stat_index]); while (candidates.length > 0) { var candidate = candidates.pop(); var lhs = get_lhs(candidate); if (!lhs || is_lhs_read_only(lhs)) continue; + // Locate symbols which may execute code outside of scanning range var lvalues = get_lvalues(candidate); if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; var side_effects = value_has_side_effects(candidate); - var hit = false, abort = false, replaced = false; + var hit = candidate.name instanceof AST_SymbolFunarg; + var abort = false, replaced = false; var tt = new TreeTransformer(function(node, descend) { if (abort) return node; // Skip nodes before `candidate` as quickly as possible @@ -803,6 +811,35 @@ merge(Compressor.prototype, { } } + function extract_args() { + var iife, fn = compressor.self(); + if (fn instanceof AST_Function + && !fn.name + && !fn.uses_arguments + && !fn.uses_eval + && (iife = compressor.parent()) instanceof AST_Call + && iife.expression === fn) { + fn.argnames.forEach(function(sym, i) { + var arg = iife.args[i]; + if (!arg) arg = make_node(AST_Undefined, sym); + else arg.walk(new TreeWalker(function(node) { + if (!arg) return true; + if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { + var s = node.definition().scope; + if (s !== scope) while (s = s.parent_scope) { + if (s === scope) return true; + } + arg = null; + } + })); + if (arg) candidates.push(make_node(AST_VarDef, sym, { + name: sym, + value: arg + })); + }); + } + } + function extract_candidates(expr) { if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor) || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) { @@ -823,7 +860,7 @@ merge(Compressor.prototype, { function get_lhs(expr) { if (expr instanceof AST_VarDef) { var def = expr.name.definition(); - if (def.orig.length > 1 + if (def.orig.length > 1 && !(expr.name instanceof AST_SymbolFunarg) || def.references.length == 1 && !compressor.exposed(def)) { return make_node(AST_SymbolRef, expr.name, expr.name); } @@ -3174,69 +3211,42 @@ merge(Compressor.prototype, { if (exp instanceof AST_Function) { if (compressor.option("inline") && !exp.name - && exp.body.length == 1 && !exp.uses_arguments && !exp.uses_eval + && exp.body.length == 1 + && all(exp.argnames, function(arg) { + return arg.__unused; + }) && !self.has_pure_annotation(compressor)) { var value; if (stat instanceof AST_Return) { - value = stat.value.clone(true); + value = stat.value; } else if (stat instanceof AST_SimpleStatement) { value = make_node(AST_UnaryPrefix, stat, { operator: "void", - expression: stat.body.clone(true) + expression: stat.body }); } if (value) { - var fn = exp.clone(); - fn.argnames = []; - fn.body = []; - if (exp.argnames.length > 0) { - fn.body.push(make_node(AST_Var, self, { - definitions: exp.argnames.map(function(sym, i) { - var arg = self.args[i]; - return make_node(AST_VarDef, sym, { - name: sym, - value: arg ? arg.clone(true) : make_node(AST_Undefined, self) - }); - }) - })); - } - if (self.args.length > exp.argnames.length) { - fn.body.push(make_node(AST_SimpleStatement, self, { - body: make_sequence(self, self.args.slice(exp.argnames.length).map(function(node) { - return node.clone(true); - })) - })); - } - fn.body.push(make_node(AST_Return, self, { - value: value - })); - var body = fn.transform(compressor).body; - if (body.length == 0) return make_node(AST_Undefined, self); - if (body.length == 1 && body[0] instanceof AST_Return) { - value = body[0].value; - if (!value) return make_node(AST_Undefined, self); - var tw = new TreeWalker(function(node) { - if (value === self) return true; - if (node instanceof AST_SymbolRef) { - var ref = node.scope.find_variable(node); - if (ref && ref.scope.parent_scope === fn.parent_scope) { - value = self; - return true; - } - } - if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { - value = self; + var tw = new TreeWalker(function(node) { + if (!value) return true; + if (node instanceof AST_SymbolRef) { + var ref = node.scope.find_variable(node); + if (ref && ref.scope.parent_scope === fn.parent_scope) { + value = null; return true; } - }); - value.walk(tw); - if (value !== self) value = best_of(compressor, value, self); - } else { - value = self; - } - if (value !== self) return value; + } + if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { + value = null; + return true; + } + }); + value.walk(tw); + } + if (value) { + var args = self.args.concat(value); + return make_sequence(self, args).transform(compressor); } } if (compressor.option("side_effects") && all(exp.body, is_empty)) { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 158d1b4afbd..f3eb78166aa 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1978,10 +1978,10 @@ chained_3: { } expect: { console.log(function(a, b) { - var c = a, c = b; + var c = 1, c = b; b++; return c; - }(1, 2)); + }(0, 2)); } expect_stdout: "2" } @@ -2186,3 +2186,34 @@ compound_assignment: { } expect_stdout: "4" } + +issue_2187: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var a = 1; + !function(foo) { + foo(); + var a = 2; + console.log(a); + }(function() { + console.log(a); + }); + } + expect: { + var a = 1; + !function(foo) { + foo(); + var a = 2; + console.log(a); + }(function() { + console.log(a); + }); + } + expect_stdout: [ + "1", + "2", + ] +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 08fc7b18537..e1acdc10541 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1113,6 +1113,7 @@ issue_2105: { options = { collapse_vars: true, inline: true, + passes: 2, reduce_vars: true, side_effects: true, unused: true, @@ -1138,7 +1139,7 @@ issue_2105: { }); } expect: { - !void function() { + (function() { var quux = function() { console.log("PASS"); }; @@ -1148,7 +1149,7 @@ issue_2105: { quux(); } }; - }().prop(); + })().prop(); } expect_stdout: "PASS" } diff --git a/test/compress/functions.js b/test/compress/functions.js index c8efc12c4f3..f411afa2a81 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -468,11 +468,9 @@ issue_2114_1: { } expect: { var c = 0; - !function() { - 0; - }((c += 1, c = 1 + c, function() { + c = 1 + (c += 1), function() { var b = void (b && (b.b += (c += 1, 0))); - }())); + }(); console.log(c); } expect_stdout: "2" diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js index 9b8c8bfd468..65871a84dab 100644 --- a/test/compress/issue-281.js +++ b/test/compress/issue-281.js @@ -419,7 +419,7 @@ wrap_iife_in_return_call: { expect_exact: '(void console.log("test"))();' } -pure_annotation: { +pure_annotation_1: { options = { inline: true, side_effects: true, @@ -432,6 +432,20 @@ pure_annotation: { expect_exact: "" } +pure_annotation_2: { + options = { + collapse_vars: true, + inline: true, + side_effects: true, + } + input: { + /*@__PURE__*/(function(n) { + console.log("hello", n); + }(42)); + } + expect_exact: "" +} + drop_fargs: { options = { cascade: true, @@ -449,9 +463,7 @@ drop_fargs: { } expect: { var a = 1; - !function() { - a++; - }(++a && a.var); + ++a && a.var, a++; console.log(a); } expect_stdout: "3" @@ -474,9 +486,7 @@ keep_fargs: { } expect: { var a = 1; - !function(a_1) { - a++; - }(++a && a.var); + ++a && a.var, a++; console.log(a); } expect_stdout: "3" diff --git a/test/mocha/glob.js b/test/mocha/glob.js index 8567ecb3367..eb5d477d6a1 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -26,12 +26,12 @@ describe("bin/uglifyjs with input file globs", function() { }); }); it("bin/uglifyjs with multiple input file globs.", function(done) { - var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel'; + var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel,passes=2'; exec(command, function(err, stdout) { if (err) throw err; - assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n'); + assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",22);\n'); done(); }); }); From 8b69a3d18e38a63565dce1758dba79615b54cc79 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 2 Jul 2017 04:28:11 +0800 Subject: [PATCH 2/4] drop argument value after `collapse_vars` (#2190) --- lib/compress.js | 14 +++++++++--- test/compress/collapse_vars.js | 41 +++++++++++++++++++++++++++++++++- test/compress/drop-unused.js | 2 +- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index dffdd6ed1f0..eb3ba75656c 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -902,6 +902,14 @@ merge(Compressor.prototype, { } function remove_candidate(expr) { + if (expr.name instanceof AST_SymbolFunarg) { + var index = compressor.self().argnames.indexOf(expr.name); + var args = compressor.parent().args; + if (args[index]) args[index] = make_node(AST_Number, args[index], { + value: 0 + }); + return true; + } var found = false; return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) { if (found) return node; @@ -3205,7 +3213,7 @@ merge(Compressor.prototype, { var value = stat.value; if (!value || value.is_constant_expression()) { var args = self.args.concat(value || make_node(AST_Undefined, self)); - return make_sequence(self, args).transform(compressor); + return make_sequence(self, args).optimize(compressor); } } if (exp instanceof AST_Function) { @@ -3246,12 +3254,12 @@ merge(Compressor.prototype, { } if (value) { var args = self.args.concat(value); - return make_sequence(self, args).transform(compressor); + return make_sequence(self, args).optimize(compressor); } } if (compressor.option("side_effects") && all(exp.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); - return make_sequence(self, args).transform(compressor); + return make_sequence(self, args).optimize(compressor); } } if (compressor.option("drop_console")) { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index f3eb78166aa..24f8ffa30aa 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2187,7 +2187,7 @@ compound_assignment: { expect_stdout: "4" } -issue_2187: { +issue_2187_1: { options = { collapse_vars: true, unused: true, @@ -2217,3 +2217,42 @@ issue_2187: { "2", ] } + +issue_2187_2: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var b = 1; + console.log(function(a) { + return a && ++b; + }(b--)); + } + expect: { + var b = 1; + console.log(function(a) { + return b-- && ++b; + }()); + } + expect_stdout: "1" +} + +issue_2187_3: { + options = { + collapse_vars: true, + inline: true, + unused: true, + } + input: { + var b = 1; + console.log(function(a) { + return a && ++b; + }(b--)); + } + expect: { + var b = 1; + console.log(b-- && ++b); + } + expect_stdout: "1" +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index e1acdc10541..a44107aebb3 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1113,7 +1113,7 @@ issue_2105: { options = { collapse_vars: true, inline: true, - passes: 2, + passes: 3, reduce_vars: true, side_effects: true, unused: true, From 2dde41615a22ec16b52028a5b09a42b50e4ee094 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 2 Jul 2017 17:24:22 +0800 Subject: [PATCH 3/4] v3.0.23 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 78873b3c2dd..ab311195c5f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.0.22", + "version": "3.0.23", "engines": { "node": ">=0.8.0" }, From 5ea1da2d42d8063028b8178df1174f9752ab2c22 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Sun, 2 Jul 2017 18:15:16 +0800 Subject: [PATCH 4/4] handle `AST_Expansion` in `collapse_vars` & `inline` --- lib/compress.js | 69 ++++++++++++++++++++++++------------ test/compress/arrow.js | 4 +-- test/compress/drop-unused.js | 4 +-- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 56b9e8ce989..9e1d8232af7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -848,6 +848,21 @@ merge(Compressor.prototype, { } } + function has_overlapping_symbol(fn, arg) { + var found = false; + arg.walk(new TreeWalker(function(node) { + if (found) return true; + if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { + var s = node.definition().scope; + if (s !== scope) while (s = s.parent_scope) { + if (s === scope) return true; + } + found = true; + } + })); + return found; + } + function extract_args() { var iife, fn = compressor.self(); if (is_func_expr(fn) @@ -860,23 +875,27 @@ merge(Compressor.prototype, { return !(arg instanceof AST_Expansion); })) { fn.argnames.forEach(function(sym, i) { - if (!(sym instanceof AST_SymbolFunarg)) return; - var arg = iife.args[i]; - if (!arg) arg = make_node(AST_Undefined, sym); - else arg.walk(new TreeWalker(function(node) { - if (!arg) return true; - if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) { - var s = node.definition().scope; - if (s !== scope) while (s = s.parent_scope) { - if (s === scope) return true; - } - arg = null; + if (sym instanceof AST_Expansion) { + var elements = iife.args.slice(i); + if (all(elements, function(arg) { + return !has_overlapping_symbol(fn, arg); + })) { + candidates.push(make_node(AST_VarDef, sym, { + name: sym.expression, + value: make_node(AST_Array, iife, { + elements: elements + }) + })); } - })); - if (arg) candidates.push(make_node(AST_VarDef, sym, { - name: sym, - value: arg - })); + } else { + var arg = iife.args[i]; + if (!arg) arg = make_node(AST_Undefined, sym); + else if (has_overlapping_symbol(fn, arg)) arg = null; + if (arg) candidates.push(make_node(AST_VarDef, sym, { + name: sym, + value: arg + })); + } }); } } @@ -945,11 +964,16 @@ merge(Compressor.prototype, { function remove_candidate(expr) { if (expr.name instanceof AST_SymbolFunarg) { - var index = compressor.self().argnames.indexOf(expr.name); - var args = compressor.parent().args; - if (args[index]) args[index] = make_node(AST_Number, args[index], { - value: 0 - }); + var iife = compressor.parent(), argnames = compressor.self().argnames; + var index = argnames.indexOf(expr.name); + if (index < 0) { + iife.args.length = Math.min(iife.args.length, argnames.length - 1); + } else { + var args = iife.args; + if (args[index]) args[index] = make_node(AST_Number, args[index], { + value: 0 + }); + } return true; } var found = false; @@ -3154,7 +3178,7 @@ merge(Compressor.prototype, { var pos = 0, last = 0; for (var i = 0, len = self.args.length; i < len; i++) { if (fn.argnames[i] instanceof AST_Expansion) { - if (fn.argnames[i].__unused) while (i < len) { + if (fn.argnames[i].expression.__unused) while (i < len) { var node = self.args[i++].drop_side_effect_free(compressor); if (node) { self.args[pos++] = node; @@ -3403,6 +3427,7 @@ merge(Compressor.prototype, { && !exp.uses_eval && (exp.body instanceof AST_Node || exp.body.length == 1) && all(exp.argnames, function(arg) { + if (arg instanceof AST_Expansion) return arg.expression.__unused; return arg.__unused; }) && !self.has_pure_annotation(compressor)) { diff --git a/test/compress/arrow.js b/test/compress/arrow.js index 2d9eb73b4c2..fc19eeb58da 100644 --- a/test/compress/arrow.js +++ b/test/compress/arrow.js @@ -374,7 +374,7 @@ issue_2105_2: { expect_stdout: "PASS" node_version: ">=6" } -/* + issue_2136_2: { options = { arrows: true, @@ -430,7 +430,7 @@ issue_2136_3: { expect_stdout: "2" node_version: ">=6" } -*/ + call_args: { options = { arrows: true, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 18170dec8e2..c08d57847e7 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1358,7 +1358,7 @@ issue_2136_1: { expect_stdout: "[]" node_version: ">=6" } -/* + issue_2136_2: { options = { collapse_vars: true, @@ -1410,7 +1410,7 @@ issue_2136_3: { expect_stdout: "2" node_version: ">=6" } -*/ + issue_2163: { options = { pure_funcs: [ "pure" ],