From b35f4c5a83247251922e1d695576db29e821aee1 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 10 Apr 2020 03:48:24 +0100 Subject: [PATCH] enhance `inline` (#3767) --- lib/compress.js | 118 +++++++++++++++++++++++++---------- test/compress/functions.js | 71 +++++++++++++++------ test/compress/issue-2719.js | 2 +- test/compress/reduce_vars.js | 3 +- test/input/rename/input.js | 2 +- test/mocha/cli.js | 8 +-- 6 files changed, 144 insertions(+), 60 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index f03e0eef..79614864 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -5819,38 +5819,45 @@ merge(Compressor.prototype, { var args = self.args.concat(value || make_node(AST_Undefined, self)); return make_sequence(self, args).optimize(compressor); } - var funarg, pos; - if (value instanceof AST_SymbolRef - && (funarg = resolve_funarg(value.definition().orig)) - && (pos = fn.argnames.indexOf(funarg)) >= 0 - && (pos >= self.args.length - 1 || all(self.args.slice(pos), function(funarg) { - return !funarg.has_side_effects(compressor); - }))) { - var args = self.args.slice(); - args.push(args.splice(pos, 1)[0] || make_node(AST_Undefined, self)); - var node = make_sequence(self, args).optimize(compressor); - return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node); - } } if (is_func) { - var def, value, scope, in_loop, level = -1; + var def, value; if (can_inline && !fn.uses_arguments && !fn.pinned() && !(fn.name && fn instanceof AST_Function) && (value = can_flatten_body(stat)) && (exp === fn - || compressor.option("unused") - && (def = exp.definition()).references.length == 1 - && !recursive_ref(compressor, def) - && fn.is_constant_expression(exp.scope)) - && !self.pure - && !fn.contains_this() - && can_inject_symbols()) { - fn._squeezed = true; - if (exp !== fn) fn.parent_scope = exp.scope; - var node = make_sequence(self, flatten_fn()).optimize(compressor); - return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node); + || !recursive_ref(compressor, def = exp.definition) && fn.is_constant_expression(exp.scope)) + && !fn.contains_this()) { + if (can_substitute_directly()) { + var args = self.args.slice(); + args.push(value.clone(true).transform(new TreeTransformer(function(node) { + if (node instanceof AST_SymbolRef) { + var def = node.definition(); + if (fn.variables.get(node.name) !== def) return node; + var index = resolve_index(def); + var arg = args[index]; + if (!arg) return make_node(AST_Undefined, self); + args[index] = null; + var parent = this.parent(); + return parent ? maintain_this_binding(compressor, parent, node, arg) : arg; + } + }))); + var node = make_sequence(self, args.filter(function(arg) { + return arg; + })).optimize(compressor); + node = maintain_this_binding(compressor, compressor.parent(), compressor.self(), node); + if (best_of(compressor, self, node) === node) return node; + } + var scope, in_loop, level = -1; + if ((exp === fn || compressor.option("unused") && exp.definition().references.length == 1) + && can_inject_symbols()) { + fn._squeezed = true; + if (exp !== fn) fn.parent_scope = exp.scope; + var node = make_sequence(self, flatten_fn()).optimize(compressor); + return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node); + } } if (compressor.option("side_effects") && all(fn.body, is_empty) @@ -5877,14 +5884,6 @@ merge(Compressor.prototype, { } return try_evaluate(compressor, self); - function resolve_funarg(orig) { - var funarg; - for (var i = 0; orig[i] instanceof AST_SymbolFunarg; i++) { - funarg = orig[i]; - } - return funarg; - } - function return_value(stat) { if (!stat) return make_node(AST_Undefined, self); if (stat instanceof AST_Return) { @@ -5924,6 +5923,61 @@ merge(Compressor.prototype, { return return_value(stat); } + function resolve_index(def) { + for (var i = fn.argnames.length; --i >= 0;) { + if (fn.argnames[i].definition() === def) return i; + } + } + + function can_substitute_directly() { + if (compressor.option("inline") <= 1 && fn.argnames.length) return; + if (fn.variables.size() > fn.argnames.length + 1) return; + if (!all(fn.argnames, function(argname) { + return argname.definition().references.length < 2; + })) return; + var abort = false; + var begin; + var in_order = []; + var side_effects = false; + value.walk(new TreeWalker(function(node) { + if (abort) return true; + if (node instanceof AST_Binary && lazy_op[node.operator] + || node instanceof AST_Conditional) { + in_order = null; + return; + } + if (node instanceof AST_Scope) return abort = true; + var def; + if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === (def = node.definition())) { + if (def.init instanceof AST_Defun) return abort = true; + if (is_lhs(node, this.parent())) return abort = true; + var index = resolve_index(def); + if (!(begin < index)) begin = index; + if (!in_order) return; + if (side_effects) { + in_order = null; + } else { + in_order.push(fn.argnames[index]); + } + return; + } + if (node.has_side_effects(compressor)) side_effects = true; + })); + if (abort) return; + var end = self.args.length; + if (in_order && fn.argnames.length >= end) { + end = fn.argnames.length; + while (end-- > begin && fn.argnames[end] === in_order.pop()); + end++; + } + var scope = side_effects && compressor.find_parent(AST_Scope); + return end <= begin || all(self.args.slice(begin, end), side_effects ? function(funarg) { + return funarg.is_constant_expression(scope); + } : function(funarg) { + return !funarg.has_side_effects(compressor); + }); + } + function var_exists(defined, name) { return defined[name] || identifier_atom[name] || scope.var_names()[name]; } diff --git a/test/compress/functions.js b/test/compress/functions.js index b1a44ffa..4a192d59 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -1020,9 +1020,7 @@ issue_2616: { } expect: { var c = "FAIL"; - !function(NaN) { - (true << NaN) - 0/0 || (c = "PASS"); - }([]); + (true << []) - NaN || (c = "PASS"); console.log(c); } expect_stdout: "PASS" @@ -1607,7 +1605,31 @@ duplicate_argnames_2: { } expect: { var a = "PASS"; - console, b && (a = "FAIL"); + console, void 0 && (a = "FAIL"); + console.log(a); + } + expect_stdout: "PASS" +} + +duplicate_argnames_3: { + options = { + inline: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = "FAIL"; + function f(b, b, b) { + b && (a = "PASS"); + } + f(null, 0, console, "42".toString()); + console.log(a); + } + expect: { + var a = "FAIL"; + b = console, "42".toString(), b && (a = "PASS"); var b; console.log(a); } @@ -1766,8 +1788,7 @@ inline_2: { } expect: { console.log(1); - a = 2, console.log(a); - var a; + console.log(2); (function(b) { var c = b; console.log(c); @@ -1800,8 +1821,7 @@ inline_3: { } expect: { console.log(1); - a = 2, console.log(a); - var a; + console.log(2); b = 3, c = b, console.log(c); var b, c; } @@ -1832,8 +1852,7 @@ inline_true: { } expect: { console.log(1); - a = 2, console.log(a); - var a; + console.log(2); b = 3, c = b, console.log(c); var b, c; } @@ -1897,7 +1916,24 @@ duplicate_arg_var_2: { }("PA")); } expect: { - console.log((b = "PA", b + "SS")); + console.log("PA" + "SS"); + } + expect_stdout: "PASS" +} + +duplicate_arg_var_3: { + options = { + inline: true, + toplevel: true, + } + input: { + console.log(function(b) { + return b + "SS"; + var b; + }("PA", "42".toString())); + } + expect: { + console.log((b = "PA", "42".toString(), b + "SS")); var b; } expect_stdout: "PASS" @@ -2045,10 +2081,8 @@ issue_3016_1: { expect: { var b = 1; do { - a = 3, - a[b]; + 3[b]; } while(0); - var a; console.log(b); } expect_stdout: "1" @@ -2556,10 +2590,9 @@ cross_references_2: { options = { collapse_vars: true, evaluate: true, - hoist_props: true, inline: true, - passes: 4, - pure_getters: true, + passes: 6, + properties: true, reduce_vars: true, sequences: true, side_effects: true, @@ -3685,9 +3718,7 @@ pr_3595_3: { var g = [ "PASS" ]; console.log(function(problem) { return g[problem]; - }(function(arg) { - return g.indexOf(arg); - }("PASS"))); + }(g.indexOf("PASS"))); } expect_stdout: "PASS" } diff --git a/test/compress/issue-2719.js b/test/compress/issue-2719.js index dc37f404..f11d0077 100644 --- a/test/compress/issue-2719.js +++ b/test/compress/issue-2719.js @@ -26,7 +26,7 @@ warn: { }().length); } expect_warnings: [ - "WARN: Function.prototype.caller not supported [test/compress/issue-2719.js:5,19]", "WARN: Function.prototype.arguments not supported [test/compress/issue-2719.js:5,19]", + "WARN: Function.prototype.caller not supported [test/compress/issue-2719.js:5,19]", ] } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 6db0a477..85ca6bd6 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2289,11 +2289,10 @@ redefine_farg_2: { console.log(f([]), g([]), h([])); } expect: { - console.log((a = [], typeof a), "number",function(a, b) { + console.log(typeof [], "number",function(a, b) { a = b; return typeof a; }([])); - var a; } expect_stdout: "object number undefined" } diff --git a/test/input/rename/input.js b/test/input/rename/input.js index 3008433a..7aff03a5 100644 --- a/test/input/rename/input.js +++ b/test/input/rename/input.js @@ -1,6 +1,6 @@ function f(x) { return g(x); function g(x) { - return x + 1; + return x + x; } } diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 2e68fbf3..82f76c0d 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -674,7 +674,7 @@ describe("bin/uglifyjs", function() { var command = uglifyjscmd + " test/input/rename/input.js --rename"; exec(command, function(err, stdout, stderr) { if (err) throw err; - assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c+1}}\n"); + assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c+c}}\n"); done(); }); }); @@ -682,7 +682,7 @@ describe("bin/uglifyjs", function() { var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2 --no-rename"; exec(command, function(err, stdout, stderr) { if (err) throw err; - assert.strictEqual(stdout, "function f(n){return function(n){return n+1}(n)}\n"); + assert.strictEqual(stdout, "function f(n){return function(n){return n+n}(n)}\n"); done(); }); }); @@ -690,7 +690,7 @@ describe("bin/uglifyjs", function() { var command = uglifyjscmd + " test/input/rename/input.js -mc passes=2"; exec(command, function(err, stdout, stderr) { if (err) throw err; - assert.strictEqual(stdout, "function f(n){return n+1}\n"); + assert.strictEqual(stdout, "function f(n){return n+n}\n"); done(); }); }); @@ -698,7 +698,7 @@ describe("bin/uglifyjs", function() { var command = uglifyjscmd + " test/input/rename/input.js -c passes=2"; exec(command, function(err, stdout, stderr) { if (err) throw err; - assert.strictEqual(stdout, "function f(x){return function(x){return x+1}(x)}\n"); + assert.strictEqual(stdout, "function f(x){return function(x){return x+x}(x)}\n"); done(); }); }); -- 2.34.1