if (!changed && last_changed == 3) break;
}
if (compressor.option("inline") >= 4) {
- if (inline_last_iife(statements, compressor)) changed = 4;
+ if (inline_iife(statements, compressor)) changed = 4;
if (!changed && last_changed == 4) break;
}
if (compressor.sequences_limit > 0) {
return statements.length != len;
}
- function inline_last_iife(statements, compressor) {
- if (!in_lambda) return false;
+ function inline_iife(statements, compressor) {
+ var changed = false;
var index = statements.length - 1;
- var stat = statements[index];
- if (!(stat instanceof AST_SimpleStatement)) return false;
- var body = stat.body;
- if (body instanceof AST_UnaryPrefix) {
- if (unary_side_effects[body.operator]) return false;
- body = body.expression;
+ if (in_lambda && index >= 0) {
+ var inlined = statements[index].try_inline(compressor, block_scope);
+ if (inlined) {
+ statements[index--] = inlined;
+ changed = true;
+ }
}
- var inlined = make_node(AST_UnaryPrefix, stat, {
- operator: "void",
- expression: body,
- }).try_inline(compressor, block_scope);
- if (!inlined) return false;
- statements[index] = inlined;
- return true;
+ for (; index >= 0; index--) {
+ var inlined = statements[index].try_inline(compressor, block_scope, true, in_loop);
+ if (!inlined) continue;
+ statements[index] = inlined;
+ changed = true;
+ }
+ return changed;
}
function sequencesize(statements, compressor) {
if (stat.init) {
prev.definitions = prev.definitions.concat(stat.init.definitions);
}
+ stat = stat.clone();
defs = stat.init = prev;
statements[j] = merge_defns(stat);
changed = true;
var sym = def.name.definition();
var drop_sym = is_var ? can_drop_symbol(def.name) : is_safe_lexical(sym);
if (!drop_sym || !drop_vars || sym.id in in_use_ids) {
- if (value && indexOf_assign(sym, def) < 0) {
+ if (value && (indexOf_assign(sym, def) < 0 || self_assign(value.tail_node()))) {
value = value.drop_side_effect_free(compressor);
if (value) {
AST_Node.warn("Side effects in definition of variable {name} [{file}:{line},{col}]", template(def.name));
sym.eliminated++;
}
+ function self_assign(ref) {
+ return ref instanceof AST_SymbolRef && ref.definition() === sym;
+ }
+
function assigned_once(fn, refs) {
if (refs.length == 0) return fn === def.name.fixed_value();
return all(refs, function(ref) {
if (!has_default) has_default = 1;
var arg = has_default == 1 && self.args[index];
if (arg && !is_undefined(arg)) has_default = 2;
- if (has_arg_refs(argname.value)) return false;
+ if (has_arg_refs(fn, argname.value)) return false;
argname = argname.name;
}
if (argname instanceof AST_Destructured) {
has_destructured = true;
- if (has_arg_refs(argname)) return false;
+ if (has_arg_refs(fn, argname)) return false;
}
return true;
- }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn.rest));
+ }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn, fn.rest));
var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor);
if (can_inline && stat instanceof AST_Return) {
var value = stat.value;
}
return try_evaluate(compressor, self);
- function has_arg_refs(node) {
- var found = false;
- node.walk(new TreeWalker(function(node) {
- if (found) return true;
- if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) {
- return found = true;
- }
- }));
- return found;
- }
-
function make_void_lhs(orig) {
return make_node(AST_Dot, orig, {
expression: make_node(AST_Array, orig, { elements: [] }),
if (arg_used.has(name.name)) return false;
if (var_exists(defined, name.name)) return false;
if (!all(stat.enclosed, function(def) {
- return def.scope === stat || !defined.has(def.name);
+ return def.scope === scope || def.scope === stat || !defined.has(def.name);
})) return false;
if (in_loop) in_loop.push(name.definition());
continue;
if (scope.init === child) continue;
if (scope.object === child) continue;
in_loop = [];
- } else if (scope instanceof AST_SymbolRef) {
- if (scope.fixed_value() instanceof AST_Scope) return false;
}
} while (!(scope instanceof AST_Scope));
insert = scope.body.indexOf(child) + 1;
if (value) expressions.push(value);
} else {
var symbol = make_node(AST_SymbolVar, name, name);
- name.definition().orig.push(symbol);
+ var def = name.definition();
+ def.orig.push(symbol);
+ def.eliminated++;
if (name.unused !== undefined) {
append_var(decls, expressions, symbol);
if (value) expressions.push(value);
def.references.push(ref);
var symbol = make_node(AST_SymbolVar, name, name);
def.orig.push(symbol);
+ def.eliminated++;
append_var(decls, expressions, symbol);
}
}
- function flatten_var(name) {
- var redef = name.definition().redefined();
- if (redef) {
- name = name.clone();
- name.thedef = redef;
- }
- return name;
- }
-
function flatten_vars(decls, expressions) {
var args = [ insert, 0 ];
var decl_var = [], expr_var = [], expr_loop = [], exprs = [];
- for (var i = 0; i < fn.body.length; i++) {
- var stat = fn.body[i];
- if (stat instanceof AST_LambdaDefinition) {
- if (in_loop) {
- var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name));
- var def = name.definition();
- def.fixed = false;
- def.orig.push(name);
- append_var(decls, expressions, name, to_func_expr(stat, true));
- } else {
- var def = stat.name.definition();
- scope.functions.set(def.name, def);
- scope.variables.set(def.name, def);
- scope.enclosed.push(def);
- scope.var_names().set(def.name, true);
- args.push(stat);
- }
- continue;
- }
+ fn.body.filter(in_loop ? function(stat) {
+ if (!(stat instanceof AST_LambdaDefinition)) return true;
+ var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name));
+ var def = name.definition();
+ def.fixed = false;
+ def.orig.push(name);
+ def.eliminated++;
+ append_var(decls, expressions, name, to_func_expr(stat, true));
+ return false;
+ } : function(stat) {
+ if (!(stat instanceof AST_LambdaDefinition)) return true;
+ var def = stat.name.definition();
+ scope.functions.set(def.name, def);
+ scope.variables.set(def.name, def);
+ scope.enclosed.push(def);
+ scope.var_names().set(def.name, true);
+ args.push(stat);
+ return false;
+ }).forEach(function(stat) {
if (!(stat instanceof AST_Var)) {
if (stat instanceof AST_SimpleStatement) exprs.push(stat.body);
- continue;
+ return;
}
for (var j = 0; j < stat.definitions.length; j++) {
var var_def = stat.definitions[j];
exprs = [];
}
append_var(decl_var, expr_var, name, value);
- if (in_loop && !arg_used.has(name.name)) {
+ if (in_loop && !arg_used.has(name.name) && !fn.functions.has(name.name)) {
var def = fn.variables.get(name.name);
var sym = make_node(AST_SymbolRef, name, name);
def.references.push(sym);
}));
}
}
- }
+ });
[].push.apply(decls, decl_var);
[].push.apply(expressions, expr_loop);
[].push.apply(expressions, expr_var);
}
var args = flatten_vars(decls, expressions);
expressions.push(value);
- if (decls.length) args.push(make_node(AST_Var, fn, {
- definitions: decls
- }));
+ if (decls.length) args.push(make_node(AST_Var, fn, { definitions: decls }));
[].splice.apply(scope.body, args);
fn.enclosed.forEach(function(def) {
if (scope.var_names().has(def.name)) return;
var reachable = false;
var find_ref = new TreeWalker(function(node) {
if (reachable) return true;
- if (node instanceof AST_SymbolRef && member(node.definition(), defs)) {
- return reachable = true;
- }
+ if (node instanceof AST_SymbolRef && member(node.definition(), defs)) return reachable = true;
});
var scan_scope = new TreeWalker(function(node) {
if (reachable) return true;
}
});
+ function flatten_var(name) {
+ var redef = name.definition().redefined();
+ if (redef) {
+ name = name.clone();
+ name.thedef = redef;
+ }
+ return name;
+ }
+
+ function has_arg_refs(fn, node) {
+ var found = false;
+ node.walk(new TreeWalker(function(node) {
+ if (found) return true;
+ if (node instanceof AST_SymbolRef && fn.variables.get(node.name) === node.definition()) {
+ return found = true;
+ }
+ }));
+ return found;
+ }
+
(function(def) {
def(AST_Node, noop);
- def(AST_Await, function(compressor, scope) {
- return this.expression.try_inline(compressor, scope);
+ def(AST_Assign, noop);
+ def(AST_Await, function(compressor, scope, no_return, in_loop) {
+ return this.expression.try_inline(compressor, scope, no_return, in_loop);
});
- def(AST_Call, function(compressor, scope) {
+ def(AST_Binary, function(compressor, scope, no_return, in_loop) {
+ if (no_return === undefined) return;
+ var self = this;
+ var op = self.operator;
+ if (!lazy_op[op]) return;
+ var inlined = self.right.try_inline(compressor, scope, no_return, in_loop);
+ if (!inlined) return;
+ return make_node(AST_If, self, {
+ condition: make_condition(self.left),
+ body: inlined,
+ alternative: null,
+ });
+
+ function make_condition(cond) {
+ switch (op) {
+ case "&&":
+ return cond;
+ case "||":
+ return cond.negate(compressor);
+ case "??":
+ return make_node(AST_Binary, self, {
+ operator: "===",
+ left: make_node(AST_Undefined, self).transform(compressor),
+ right: cond,
+ });
+ }
+ }
+ });
+ def(AST_BlockStatement, function(compressor, scope, no_return, in_loop) {
+ if (no_return) return;
+ if (!this.variables) return;
+ var body = this.body;
+ var last = body.length - 1;
+ if (last < 0) return;
+ var inlined = body[last].try_inline(compressor, this, no_return, in_loop);
+ if (!inlined) return;
+ body[last] = inlined;
+ return this;
+ });
+ def(AST_Call, function(compressor, scope, no_return, in_loop) {
if (compressor.option("inline") < 4) return;
var call = this;
if (call.is_expr_pure(compressor)) return;
if (!(fn instanceof AST_LambdaExpression)) return;
if (fn.name) return;
if (fn.uses_arguments) return;
- if (is_arrow(fn) && fn.value) return;
if (is_generator(fn)) return;
+ var arrow = is_arrow(fn);
+ if (arrow && fn.value) return;
+ if (fn.body[0] instanceof AST_Directive) return;
if (fn.contains_this()) return;
if (!scope) scope = find_scope(compressor);
var defined = new Dictionary();
});
scope = scope.parent_scope;
}
- if (is_async(fn) && !(compressor.option("awaits") && is_async(scope))) return;
+ if (scope instanceof AST_Toplevel && fn.variables.size() > (arrow ? 0 : 1)) {
+ if (!compressor.toplevel.vars) return;
+ if (fn.functions.size() > 0 && !compressor.toplevel.funcs) return;
+ }
+ var async = is_async(fn);
+ if (async && !(compressor.option("awaits") && is_async(scope))) return;
var names = scope.var_names();
+ if (in_loop) in_loop = [];
if (!fn.variables.all(function(def, name) {
+ if (in_loop) in_loop.push(def);
if (!defined.has(name) && !names.has(name)) return true;
if (name != "arguments") return false;
if (scope.uses_arguments) return false;
return def.references.length == def.replaced;
})) return;
- var safe = true;
- fn.each_argname(function(argname) {
- if (!all(argname.definition().orig, function(sym) {
- return !(sym instanceof AST_SymbolDefun);
- })) safe = false;
- });
- if (!safe) return;
+ if (in_loop && in_loop.length > 0 && is_reachable(fn, in_loop)) return;
+ var simple_argnames = true;
+ if (!all(fn.argnames, function(argname) {
+ var abort = false;
+ var tw = new TreeWalker(function(node) {
+ if (abort) return true;
+ if (node instanceof AST_DefaultValue) {
+ if (has_arg_refs(fn, node.value)) return abort = true;
+ node.name.walk(tw);
+ return true;
+ }
+ if (node instanceof AST_DestructuredKeyVal) {
+ if (node.key instanceof AST_Node && has_arg_refs(fn, node.key)) return abort = true;
+ node.value.walk(tw);
+ return true;
+ }
+ if (node instanceof AST_SymbolFunarg && !all(node.definition().orig, function(sym) {
+ return !(sym instanceof AST_SymbolDefun);
+ })) return abort = true;
+ });
+ argname.walk(tw);
+ if (abort) return false;
+ if (!(argname instanceof AST_SymbolFunarg)) simple_argnames = false;
+ return true;
+ })) return;
+ if (fn.rest) {
+ if (has_arg_refs(fn, fn.rest)) return;
+ simple_argnames = false;
+ }
+ if (no_return && !all(fn.body, function(stat) {
+ var abort = false;
+ stat.walk(new TreeWalker(function(node) {
+ if (abort) return true;
+ if (async && node instanceof AST_Await || node instanceof AST_Return) return abort = true;
+ if (node instanceof AST_Scope && node !== fn) return true;
+ }));
+ return !abort;
+ })) return;
if (!safe_from_await_yield(fn, avoid_await_yield(scope))) return;
fn.functions.each(function(def, name) {
scope.functions.set(name, def);
});
+ var body = [];
fn.variables.each(function(def, name) {
+ names.set(name, true);
+ scope.enclosed.push(def);
scope.variables.set(name, def);
def.single_use = false;
+ if (!in_loop || fn.functions.has(name)) return;
+ if (def.references.length == def.replaced) return;
+ var sym = flatten_var(def.orig[0]);
+ if (sym.TYPE != "SymbolVar") return;
+ var ref = make_node(AST_SymbolRef, sym, sym);
+ sym.definition().references.push(ref);
+ body.push(make_node(AST_SimpleStatement, sym, {
+ body: make_node(AST_Assign, sym, {
+ operator: "=",
+ left: ref,
+ right: make_node(AST_Undefined, sym).transform(compressor),
+ }),
+ }));
});
if (fn.variables.has("NaN")) scope.transform(new TreeTransformer(function(node) {
if (node instanceof AST_NaN) return make_node(AST_Binary, node, {
right: make_node(AST_Number, node, { value: 0 }),
});
}));
- var body = [], defs = Object.create(null), syms = new Dictionary();
- if (fn.rest || !all(fn.argnames, function(argname) {
- return argname instanceof AST_SymbolFunarg;
- }) || !all(call.args, function(arg) {
+ var defs = Object.create(null), syms = new Dictionary();
+ if (simple_argnames && all(call.args, function(arg) {
return !(arg instanceof AST_Spread);
})) {
- body.push(make_node(AST_Var, call, {
- definitions: [ make_node(AST_VarDef, call, {
- name: make_node(AST_DestructuredArray, call, {
- elements: fn.argnames.map(function(argname) {
- if (argname.unused) return make_node(AST_Hole, argname);
- return argname.convert_symbol(AST_SymbolVar, process);
- }),
- rest: fn.rest && fn.rest.convert_symbol(AST_SymbolVar, process),
- }),
- value: make_node(AST_Array, call, { elements: call.args.slice() }),
- }) ],
- }));
- } else {
var values = call.args.slice();
fn.argnames.forEach(function(argname) {
var value = values.shift();
body.push(make_node(AST_Var, call, {
definitions: [ make_node(AST_VarDef, call, {
name: argname.convert_symbol(AST_SymbolVar, process),
- value: value || make_node(AST_Undefined, call).optimize(compressor),
+ value: value || make_node(AST_Undefined, call).transform(compressor),
}) ],
}));
});
if (values.length) body.push(make_node(AST_SimpleStatement, call, {
body: make_sequence(call, values),
}));
+ } else {
+ body.push(make_node(AST_Var, call, {
+ definitions: [ make_node(AST_VarDef, call, {
+ name: make_node(AST_DestructuredArray, call, {
+ elements: fn.argnames.map(function(argname) {
+ if (argname.unused) return make_node(AST_Hole, argname);
+ return argname.convert_symbol(AST_SymbolVar, process);
+ }),
+ rest: fn.rest && fn.rest.convert_symbol(AST_SymbolVar, process),
+ }),
+ value: make_node(AST_Array, call, { elements: call.args.slice() }),
+ }) ],
+ }));
}
syms.each(function(orig, id) {
- [].unshift.apply(defs[id].orig, orig);
- });
- var inlined = make_node(AST_BlockStatement, call, {
- body: body.concat(fn.body, make_node(AST_Return, call, { value: null })),
- });
- if (is_async(fn)) scan_local_returns(inlined, function(node) {
- var value = node.value;
- if (!value) return;
- if (is_undefined(value)) return;
- node.value = make_node(AST_Await, call, { expression: value });
+ var def = defs[id];
+ [].unshift.apply(def.orig, orig);
+ def.eliminated += orig.length;
});
+ [].push.apply(body, in_loop ? fn.body.filter(function(stat) {
+ if (!(stat instanceof AST_LambdaDefinition)) return true;
+ var name = make_node(AST_SymbolVar, stat.name, flatten_var(stat.name));
+ var def = name.definition();
+ def.fixed = false;
+ def.orig.push(name);
+ def.eliminated++;
+ body.push(make_node(AST_Var, stat, {
+ definitions: [ make_node(AST_VarDef, stat, {
+ name: name,
+ value: to_func_expr(stat, true),
+ }) ],
+ }));
+ return false;
+ }) : fn.body);
+ var inlined = make_node(AST_BlockStatement, call, { body: body });
+ if (!no_return) {
+ if (async) scan_local_returns(inlined, function(node) {
+ var value = node.value;
+ if (!value) return;
+ if (is_undefined(value)) return;
+ node.value = make_node(AST_Await, call, { expression: value });
+ });
+ body.push(make_node(AST_Return, call, { value: null }));
+ }
return inlined;
function process(sym, argname) {
syms.add(def.id, sym);
}
});
+ def(AST_Conditional, function(compressor, scope, no_return, in_loop) {
+ var self = this;
+ var body = self.consequent.try_inline(compressor, scope, no_return, in_loop);
+ var alt = self.alternative.try_inline(compressor, scope, no_return, in_loop);
+ if (!body && !alt) return;
+ return make_node(AST_If, self, {
+ condition: self.condition,
+ body: body || make_body(self.consequent),
+ alternative: alt || make_body(self.alternative),
+ });
+
+ function make_body(value) {
+ if (no_return) return make_node(AST_SimpleStatement, value, { body: value });
+ return make_node(AST_Return, value, { value: value });
+ }
+ });
+ def(AST_For, function(compressor, scope, no_return, in_loop) {
+ var body = this.body.try_inline(compressor, scope, true, true);
+ if (body) this.body = body;
+ var inlined = this.init;
+ if (inlined) {
+ inlined = inlined.try_inline(compressor, scope, true, in_loop);
+ if (inlined) {
+ this.init = null;
+ inlined.body.push(this);
+ return inlined;
+ }
+ }
+ return body && this;
+ });
+ def(AST_ForEnumeration, function(compressor, scope, no_return, in_loop) {
+ var body = this.body.try_inline(compressor, scope, true, true);
+ if (body) this.body = body;
+ var obj = this.object;
+ if (obj instanceof AST_Sequence) {
+ var inlined = inline_sequence(compressor, scope, true, in_loop, obj, 1);
+ if (inlined) {
+ this.object = obj.tail_node();
+ inlined.body.push(this);
+ return inlined;
+ }
+ }
+ return body && this;
+ });
+ def(AST_If, function(compressor, scope, no_return, in_loop) {
+ var body = this.body.try_inline(compressor, scope, no_return, in_loop);
+ if (body) this.body = body;
+ var alt = this.alternative;
+ if (alt) {
+ alt = alt.try_inline(compressor, scope, no_return, in_loop);
+ if (alt) this.alternative = alt;
+ }
+ var cond = this.condition;
+ if (cond instanceof AST_Sequence) {
+ var inlined = inline_sequence(compressor, scope, true, in_loop, cond, 1);
+ if (inlined) {
+ this.condition = cond.tail_node();
+ inlined.body.push(this);
+ return inlined;
+ }
+ }
+ return (body || alt) && this;
+ });
+ def(AST_IterationStatement, function(compressor, scope, no_return, in_loop) {
+ var body = this.body.try_inline(compressor, scope, true, true);
+ if (!body) return;
+ this.body = body;
+ return this;
+ });
+ def(AST_LabeledStatement, function(compressor, scope, no_return, in_loop) {
+ var body = this.body.try_inline(compressor, scope, no_return, in_loop);
+ if (!body) return;
+ this.body = body;
+ return this;
+ });
def(AST_New, noop);
- def(AST_Sequence, function(compressor, scope) {
- var inlined = this.tail_node().try_inline(compressor, scope);
- if (inlined) return make_node(AST_BlockStatement, this, {
- body: [
- make_node(AST_SimpleStatement, this, {
- body: make_sequence(this, this.expressions.slice(0, -1)),
- }),
- inlined,
- ],
+ function inline_sequence(compressor, scope, no_return, in_loop, node, skip) {
+ var body = [], exprs = node.expressions, no_ret = no_return;
+ for (var i = exprs.length - (skip || 0), j = i; --i >= 0; no_ret = true) {
+ var inlined = exprs[i].try_inline(compressor, scope, no_ret, in_loop);
+ if (!inlined) continue;
+ flush();
+ body.push(inlined);
+ }
+ if (body.length == 0) return;
+ flush();
+ if (!no_return && body[0] instanceof AST_SimpleStatement) {
+ body[0] = make_node(AST_Return, node, { value: body[0].body });
+ }
+ return make_node(AST_BlockStatement, node, { body: body.reverse() });
+
+ function flush() {
+ if (j > i + 1) body.push(make_node(AST_SimpleStatement, node, {
+ body: make_sequence(node, exprs.slice(i + 1, j)),
+ }));
+ j = i;
+ }
+ }
+ def(AST_Sequence, function(compressor, scope, no_return, in_loop) {
+ return inline_sequence(compressor, scope, no_return, in_loop, this);
+ });
+ def(AST_SimpleStatement, function(compressor, scope, no_return, in_loop) {
+ var body = this.body;
+ while (body instanceof AST_UnaryPrefix) {
+ var op = body.operator;
+ if (unary_side_effects[op]) break;
+ if (op == "void") break;
+ body = body.expression;
+ }
+ if (!no_return && !is_undefined(body)) body = make_node(AST_UnaryPrefix, this, {
+ operator: "void",
+ expression: body,
});
+ return body.try_inline(compressor, scope, no_return || false, in_loop);
});
- def(AST_UnaryPrefix, function(compressor, scope) {
+ def(AST_UnaryPrefix, function(compressor, scope, no_return, in_loop) {
var self = this;
var op = self.operator;
if (unary_side_effects[op]) return;
- var inlined = self.expression.try_inline(compressor, scope);
+ if (!no_return && op == "void") no_return = false;
+ var inlined = self.expression.try_inline(compressor, scope, no_return, in_loop);
if (!inlined) return;
- scan_local_returns(inlined, function(node) {
+ if (!no_return) scan_local_returns(inlined, function(node) {
node.in_bool = false;
var value = node.value;
if (op == "void") {
});
return inlined;
});
- def(AST_Yield, function(compressor, scope) {
+ def(AST_With, function(compressor, scope, no_return, in_loop) {
+ var body = this.body.try_inline(compressor, scope, no_return, in_loop);
+ if (body) this.body = body;
+ var exp = this.expression;
+ if (exp instanceof AST_Sequence) {
+ var inlined = inline_sequence(compressor, scope, true, in_loop, exp, 1);
+ if (inlined) {
+ this.expression = exp.tail_node();
+ inlined.body.push(this);
+ return inlined;
+ }
+ }
+ return body && this;
+ });
+ def(AST_Yield, function(compressor, scope, no_return, in_loop) {
if (!compressor.option("yields")) return;
if (!this.nested) return;
var call = this.expression;
}
call = call.clone();
call.expression = fn;
- return call.try_inline(compressor, scope);
+ return call.try_inline(compressor, scope, no_return, in_loop);
});
})(function(node, func) {
node.DEFMETHOD("try_inline", func);