inline functions with AST_Var (#2688)

This commit is contained in:
Alex Lam S.L
2018-01-03 01:54:44 +08:00
committed by GitHub
parent 6dead95eb3
commit 7d3cddf9d6
3 changed files with 360 additions and 53 deletions

View File

@@ -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 - `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 - `join_vars` (default: `true`) -- join consecutive `var` statements

View File

@@ -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"]; var pure_funcs = this.options["pure_funcs"];
if (typeof pure_funcs == "function") { if (typeof pure_funcs == "function") {
this.pure_funcs = pure_funcs; this.pure_funcs = pure_funcs;
@@ -4030,7 +4031,7 @@ merge(Compressor.prototype, {
if (compressor.option("inline") if (compressor.option("inline")
&& !fn.uses_arguments && !fn.uses_arguments
&& !fn.uses_eval && !fn.uses_eval
&& fn.body.length == 1 && (value = can_flatten_body(stat))
&& (exp === fn ? !fn.name && (exp === fn ? !fn.name
: compressor.option("unused") : compressor.option("unused")
&& (def = exp.definition()).references.length == 1 && (def = exp.definition()).references.length == 1
@@ -4038,11 +4039,8 @@ merge(Compressor.prototype, {
&& fn.is_constant_expression(exp.scope)) && fn.is_constant_expression(exp.scope))
&& !self.pure && !self.pure
&& !fn.contains_this() && !fn.contains_this()
&& can_flatten_args(fn) && can_inject_symbols()) {
&& (value = flatten_body(stat))) { return make_sequence(self, flatten_fn()).optimize(compressor);
var expressions = flatten_args(fn);
expressions.push(value.clone(true));
return make_sequence(self, expressions).optimize(compressor);
} }
if (compressor.option("side_effects") && all(fn.body, is_empty)) { if (compressor.option("side_effects") && all(fn.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self)); var args = self.args.concat(make_node(AST_Undefined, self));
@@ -4072,19 +4070,44 @@ merge(Compressor.prototype, {
} }
return self; return self;
function can_flatten_args(fn) { function return_value(stat) {
var catches = Object.create(null); if (!stat) return make_node(AST_Undefined, self);
do { if (stat instanceof AST_Return) {
scope = compressor.parent(++level); if (!stat.value) return make_node(AST_Undefined, self);
if (scope instanceof AST_Catch) { return stat.value.clone(true);
catches[scope.argname.name] = true; }
} else if (scope instanceof AST_IterationStatement) { if (stat instanceof AST_SimpleStatement) {
in_loop = []; return make_node(AST_UnaryPrefix, stat, {
} else if (scope instanceof AST_SymbolRef) { operator: "void",
if (scope.fixed_value() instanceof AST_Scope) return false; 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++) { for (var i = 0, len = fn.argnames.length; i < len; i++) {
var arg = fn.argnames[i]; var arg = fn.argnames[i];
if (arg.__unused) continue; if (arg.__unused) continue;
@@ -4096,43 +4119,101 @@ merge(Compressor.prototype, {
} }
if (in_loop) in_loop.push(arg.definition()); 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) { function can_inject_vars(catches, safe_to_inject) {
var decls = []; var len = fn.body.length;
var expressions = []; 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;) { for (var len = fn.argnames.length, i = len; --i >= 0;) {
var name = fn.argnames[i]; var name = fn.argnames[i];
var value = self.args[i]; var value = self.args[i];
if (name.__unused || scope.var_names()[name.name]) { if (name.__unused || scope.var_names()[name.name]) {
if (value) { if (value) expressions.push(value);
expressions.unshift(value);
}
} else { } 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); var symbol = make_node(AST_SymbolVar, name, name);
def.orig.push(symbol); name.definition().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);
if (!value && in_loop) value = make_node(AST_Undefined, self); if (!value && in_loop) value = make_node(AST_Undefined, self);
if (value) expressions.unshift(make_node(AST_Assign, self, { append_var(decls, expressions, symbol, value);
operator: "=",
left: sym,
right: value
}));
} }
} }
decls.reverse();
expressions.reverse();
for (i = len, len = self.args.length; i < len; i++) { for (i = len, len = self.args.length; i < len; i++) {
expressions.push(self.args[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) { if (decls.length) {
i = scope.body.indexOf(compressor.parent(level - 1)) + 1; i = scope.body.indexOf(compressor.parent(level - 1)) + 1;
scope.body.splice(i, 0, make_node(AST_Var, fn, { scope.body.splice(i, 0, make_node(AST_Var, fn, {
@@ -4141,17 +4222,6 @@ merge(Compressor.prototype, {
} }
return expressions; 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){ OPT(AST_New, function(self, compressor){

View File

@@ -1694,3 +1694,234 @@ loop_init_arg: {
} }
expect_stdout: "PASS" 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"
}