From: Alex Lam S.L Date: Tue, 2 Jan 2018 17:54:44 +0000 (+0800) Subject: inline functions with `AST_Var` (#2688) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=7d3cddf9d624d169c6667a52e8d6f313d1b30159;p=UglifyJS.git inline functions with `AST_Var` (#2688) --- diff --git a/README.md b/README.md index 43793dcd..d7c47d72 100644 --- a/README.md +++ b/README.md @@ -640,7 +640,13 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `if_return` (default: `true`) -- optimizations for if/return and if/continue -- `inline` (default: `true`) -- embed simple functions +- `inline` (default: `true`) -- inline calls to function with simple/`return` statement: + - `false` -- same as `0` + - `0` -- disabled inlining + - `1` -- inline simple functions + - `2` -- inline functions with arguments + - `3` -- inline functions with arguments and variables + - `true` -- same as `3` - `join_vars` (default: `true`) -- join consecutive `var` statements diff --git a/lib/compress.js b/lib/compress.js index 32212688..06873df8 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -99,6 +99,7 @@ function Compressor(options, false_by_default) { }); } } + if (this.options["inline"] === true) this.options["inline"] = 3; var pure_funcs = this.options["pure_funcs"]; if (typeof pure_funcs == "function") { this.pure_funcs = pure_funcs; @@ -4030,7 +4031,7 @@ merge(Compressor.prototype, { if (compressor.option("inline") && !fn.uses_arguments && !fn.uses_eval - && fn.body.length == 1 + && (value = can_flatten_body(stat)) && (exp === fn ? !fn.name : compressor.option("unused") && (def = exp.definition()).references.length == 1 @@ -4038,11 +4039,8 @@ merge(Compressor.prototype, { && fn.is_constant_expression(exp.scope)) && !self.pure && !fn.contains_this() - && can_flatten_args(fn) - && (value = flatten_body(stat))) { - var expressions = flatten_args(fn); - expressions.push(value.clone(true)); - return make_sequence(self, expressions).optimize(compressor); + && can_inject_symbols()) { + return make_sequence(self, flatten_fn()).optimize(compressor); } if (compressor.option("side_effects") && all(fn.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); @@ -4072,19 +4070,44 @@ merge(Compressor.prototype, { } return self; - function can_flatten_args(fn) { - var catches = Object.create(null); - do { - scope = compressor.parent(++level); - if (scope instanceof AST_Catch) { - catches[scope.argname.name] = true; - } else if (scope instanceof AST_IterationStatement) { - in_loop = []; - } else if (scope instanceof AST_SymbolRef) { - if (scope.fixed_value() instanceof AST_Scope) return false; + function return_value(stat) { + if (!stat) return make_node(AST_Undefined, self); + if (stat instanceof AST_Return) { + if (!stat.value) return make_node(AST_Undefined, self); + return stat.value.clone(true); + } + if (stat instanceof AST_SimpleStatement) { + return make_node(AST_UnaryPrefix, stat, { + operator: "void", + expression: stat.body.clone(true) + }); + } + } + + function can_flatten_body(stat) { + var len = fn.body.length; + if (compressor.option("inline") < 3) { + return len == 1 && return_value(stat); + } + stat = null; + for (var i = 0; i < len; i++) { + var line = fn.body[i]; + if (line instanceof AST_Definitions) { + if (stat && !all(line.definitions, function(var_def) { + return !var_def.value; + })) { + return false; + } + } else if (stat) { + return false; + } else { + stat = line; } - } while (!(scope instanceof AST_Scope)); - var safe_to_inject = compressor.toplevel.vars || !(scope instanceof AST_Toplevel); + } + return return_value(stat); + } + + function can_inject_args(catches, defs, safe_to_inject) { for (var i = 0, len = fn.argnames.length; i < len; i++) { var arg = fn.argnames[i]; if (arg.__unused) continue; @@ -4096,43 +4119,101 @@ merge(Compressor.prototype, { } if (in_loop) in_loop.push(arg.definition()); } - return !in_loop || in_loop.length == 0 || !is_reachable(stat, in_loop); + return true; } - function flatten_args(fn) { - var decls = []; - var expressions = []; + function can_inject_vars(catches, safe_to_inject) { + var len = fn.body.length; + for (var i = 0; i < len; i++) { + var stat = fn.body[i]; + if (!(stat instanceof AST_Definitions)) continue; + if (!safe_to_inject) return false; + for (var j = stat.definitions.length; --j >= 0;) { + var name = stat.definitions[j].name; + if (catches[name.name] + || identifier_atom(name.name) + || scope.var_names()[name.name]) { + return false; + } + } + } + return true; + } + + function can_inject_symbols() { + var catches = Object.create(null); + do { + scope = compressor.parent(++level); + if (scope instanceof AST_Catch) { + catches[scope.argname.name] = true; + } else if (scope instanceof AST_IterationStatement) { + in_loop = []; + } else if (scope instanceof AST_SymbolRef) { + if (scope.fixed_value() instanceof AST_Scope) return false; + } + } while (!(scope instanceof AST_Scope)); + var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars; + var inline = compressor.option("inline"); + if (!can_inject_vars(catches, !in_loop && inline >= 3 && safe_to_inject)) return false; + if (!can_inject_args(catches, in_loop, inline >= 2 && safe_to_inject)) return false; + return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); + } + + function append_var(decls, expressions, name, value) { + var def = name.definition(); + scope.var_names()[name.name] = true; + scope.variables.set(name.name, def); + scope.enclosed.push(def); + decls.push(make_node(AST_VarDef, name, { + name: name, + value: null + })); + var sym = make_node(AST_SymbolRef, name, name); + def.references.push(sym); + if (value) expressions.push(make_node(AST_Assign, self, { + operator: "=", + left: sym, + right: value + })); + } + + function flatten_args(decls, expressions) { for (var len = fn.argnames.length, i = len; --i >= 0;) { var name = fn.argnames[i]; var value = self.args[i]; if (name.__unused || scope.var_names()[name.name]) { - if (value) { - expressions.unshift(value); - } + if (value) expressions.push(value); } else { - var def = name.definition(); - scope.var_names()[name.name] = true; - scope.variables.set(name.name, def); - scope.enclosed.push(def); var symbol = make_node(AST_SymbolVar, name, name); - def.orig.push(symbol); - decls.unshift(make_node(AST_VarDef, name, { - name: symbol, - value: null - })); - var sym = make_node(AST_SymbolRef, name, name); - def.references.push(sym); + name.definition().orig.push(symbol); if (!value && in_loop) value = make_node(AST_Undefined, self); - if (value) expressions.unshift(make_node(AST_Assign, self, { - operator: "=", - left: sym, - right: value - })); + append_var(decls, expressions, symbol, value); } } + decls.reverse(); + expressions.reverse(); for (i = len, len = self.args.length; i < len; i++) { expressions.push(self.args[i]); } + } + + function flatten_body(decls, expressions) { + for (i = 0, len = fn.body.length; i < len; i++) { + var stat = fn.body[i]; + if (stat instanceof AST_Definitions) { + stat.definitions.forEach(function(var_def) { + append_var(decls, expressions, var_def.name, var_def.value); + }); + } + } + expressions.push(value); + } + + function flatten_fn() { + var decls = []; + var expressions = []; + flatten_args(decls, expressions); + flatten_body(decls, expressions); if (decls.length) { i = scope.body.indexOf(compressor.parent(level - 1)) + 1; scope.body.splice(i, 0, make_node(AST_Var, fn, { @@ -4141,17 +4222,6 @@ merge(Compressor.prototype, { } return expressions; } - - function flatten_body(stat) { - if (stat instanceof AST_Return) { - return stat.value; - } else if (stat instanceof AST_SimpleStatement) { - return make_node(AST_UnaryPrefix, stat, { - operator: "void", - expression: stat.body - }); - } - } }); OPT(AST_New, function(self, compressor){ diff --git a/test/compress/functions.js b/test/compress/functions.js index dd98ba60..f38977b3 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -1694,3 +1694,234 @@ loop_init_arg: { } expect_stdout: "PASS" } + +inline_false: { + options = { + inline: false, + side_effects: true, + toplevel: true, + } + input: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect_stdout: [ + "1", + "2", + "3", + ] +} + +inline_0: { + options = { + inline: 0, + side_effects: true, + toplevel: true, + } + input: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect_stdout: [ + "1", + "2", + "3", + ] +} + +inline_1: { + options = { + inline: 1, + side_effects: true, + toplevel: true, + } + input: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect: { + console.log(1); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect_stdout: [ + "1", + "2", + "3", + ] +} + +inline_2: { + options = { + inline: 2, + side_effects: true, + toplevel: true, + } + input: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect: { + console.log(1); + a = 2, console.log(a); + var a; + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect_stdout: [ + "1", + "2", + "3", + ] +} + +inline_3: { + options = { + inline: 3, + side_effects: true, + toplevel: true, + } + input: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect: { + console.log(1); + a = 2, console.log(a); + var a; + b = 3, c = b, console.log(c); + var b, c; + } + expect_stdout: [ + "1", + "2", + "3", + ] +} + +inline_true: { + options = { + inline: true, + side_effects: true, + toplevel: true, + } + input: { + (function() { + console.log(1); + })(); + (function(a) { + console.log(a); + })(2); + (function(b) { + var c = b; + console.log(c); + })(3); + } + expect: { + console.log(1); + a = 2, console.log(a); + var a; + b = 3, c = b, console.log(c); + var b, c; + } + expect_stdout: [ + "1", + "2", + "3", + ] +} + +use_before_init_in_loop: { + options = { + inline: true, + toplevel: true, + } + input: { + var a = "PASS"; + for (var b = 2; --b >= 0;) (function() { + var c = function() { + return 1; + }(c && (a = "FAIL")); + })(); + console.log(a); + } + expect: { + var a = "PASS"; + for (var b = 2; --b >= 0;) (function() { + var c = (c && (a = "FAIL"), 1); + })(); + console.log(a); + } + expect_stdout: "PASS" +}