Merge branch 'master' into harmony-v3.1.10

This commit is contained in:
alexlamsl
2017-11-19 14:34:27 +08:00
10 changed files with 935 additions and 232 deletions

View File

@@ -386,6 +386,7 @@ merge(Compressor.prototype, {
}
}
if (node instanceof AST_Defun) {
node.inlined = false;
var d = node.name.definition();
if (compressor.exposed(d) || safe_to_read(d)) {
d.fixed = false;
@@ -402,6 +403,7 @@ merge(Compressor.prototype, {
return true;
}
if (is_func_expr(node)) {
node.inlined = false;
push();
var iife;
if (!node.name
@@ -835,8 +837,8 @@ merge(Compressor.prototype, {
});
function drop_decl(def) {
def._eliminiated = (def._eliminiated || 0) + 1;
if (def.orig.length == def._eliminiated) {
def.eliminated++;
if (def.orig.length == def.eliminated) {
def.scope.functions.del(def.name);
def.scope.variables.del(def.name);
}
@@ -878,6 +880,115 @@ merge(Compressor.prototype, {
var args;
var candidates = [];
var stat_index = statements.length;
var scanner = new TreeTransformer(function(node, descend) {
if (abort) return node;
// Skip nodes before `candidate` as quickly as possible
if (!hit) {
if (node === candidate) {
hit = true;
return node;
}
return;
}
// Stop immediately if these node types are encountered
var parent = scanner.parent();
if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
|| node instanceof AST_Await
|| node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
|| node instanceof AST_Debugger
|| node instanceof AST_Destructuring
|| node instanceof AST_IterationStatement && !(node instanceof AST_For)
|| node instanceof AST_SymbolRef && !node.is_declared(compressor)
|| node instanceof AST_Try
|| node instanceof AST_With
|| parent instanceof AST_For && node !== parent.init) {
abort = true;
return node;
}
// Replace variable with assignment when found
if (can_replace
&& !(node instanceof AST_SymbolDeclaration)
&& lhs.equivalent_to(node)) {
if (is_lhs(node, parent)) {
if (candidate.multiple) replaced++;
return node;
}
CHANGED = abort = true;
replaced++;
compressor.info("Collapsing {name} [{file}:{line},{col}]", {
name: node.print_to_string(),
file: node.start.file,
line: node.start.line,
col: node.start.col
});
if (candidate instanceof AST_UnaryPostfix) {
return make_node(AST_UnaryPrefix, candidate, candidate);
}
if (candidate instanceof AST_VarDef) {
if (candidate.multiple) {
abort = false;
return node;
}
var def = candidate.name.definition();
if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
def.replaced++;
return maintain_this_binding(parent, node, candidate.value);
}
return make_node(AST_Assign, candidate, {
operator: "=",
left: make_node(AST_SymbolRef, candidate.name, candidate.name),
right: candidate.value
});
}
candidate.write_only = false;
return candidate;
}
// These node types have child nodes that execute sequentially,
// but are otherwise not safe to scan into or beyond them.
var sym;
if (node instanceof AST_Call
|| node instanceof AST_Exit
|| node instanceof AST_PropAccess
&& (side_effects || node.expression.may_throw_on_access(compressor))
|| node instanceof AST_SymbolRef
&& (lvalues[node.name]
|| side_effects && !references_in_scope(node.definition()))
|| (sym = lhs_or_def(node))
&& (sym instanceof AST_PropAccess || sym.name in lvalues)
|| (side_effects || !replace_all)
&& (parent instanceof AST_Binary && lazy_op(parent.operator)
|| parent instanceof AST_Case
|| parent instanceof AST_Conditional
|| parent instanceof AST_If)) {
if (!(node instanceof AST_Scope)) descend(node, scanner);
abort = true;
return node;
}
// Skip (non-executed) functions and (leading) default case in switch statements
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
});
var multi_replacer = new TreeTransformer(function(node) {
if (abort) return node;
// Skip nodes before `candidate` as quickly as possible
if (!hit) {
if (node === candidate) {
hit = true;
return node;
}
return;
}
// Replace variable when found
if (node instanceof AST_SymbolRef
&& node.name == def.name) {
if (!--replaced) abort = true;
if (is_lhs(node, multi_replacer.parent())) return node;
def.replaced++;
value_def.replaced--;
return candidate.value;
}
// Skip (non-executed) functions and (leading) default case in switch statements
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
});
while (--stat_index >= 0) {
// Treat parameters as collapsible in IIFE, i.e.
// function(a, b){ ... }(x());
@@ -893,96 +1004,34 @@ merge(Compressor.prototype, {
// 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 one_off = lhs instanceof AST_Symbol && lhs.definition().references.length == 1;
var replace_all = candidate.multiple;
if (!replace_all && lhs instanceof AST_SymbolRef) {
var def = lhs.definition();
replace_all = def.references.length - def.replaced == 1;
}
var side_effects = value_has_side_effects(candidate);
var hit = candidate.name instanceof AST_SymbolFunarg;
var abort = false, replaced = false, can_replace = !args || !hit;
var tt = new TreeTransformer(function(node, descend) {
if (abort) return node;
// Skip nodes before `candidate` as quickly as possible
if (!hit) {
if (node === candidate) {
hit = true;
return node;
}
return;
}
// Stop immediately if these node types are encountered
var parent = tt.parent();
if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left)
|| node instanceof AST_Await
|| node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
|| node instanceof AST_Debugger
|| node instanceof AST_Destructuring
|| node instanceof AST_IterationStatement && !(node instanceof AST_For)
|| node instanceof AST_SymbolRef && !node.is_declared(compressor)
|| node instanceof AST_Try
|| node instanceof AST_With
|| parent instanceof AST_For && node !== parent.init) {
abort = true;
return node;
}
// Replace variable with assignment when found
if (can_replace
&& !(node instanceof AST_SymbolDeclaration)
&& !is_lhs(node, parent)
&& lhs.equivalent_to(node)) {
CHANGED = replaced = abort = true;
compressor.info("Collapsing {name} [{file}:{line},{col}]", {
name: node.print_to_string(),
file: node.start.file,
line: node.start.line,
col: node.start.col
});
if (candidate instanceof AST_UnaryPostfix) {
return make_node(AST_UnaryPrefix, candidate, candidate);
}
if (candidate instanceof AST_VarDef) {
var def = candidate.name.definition();
if (def.references.length == 1 && !compressor.exposed(def)) {
return maintain_this_binding(parent, node, candidate.value);
}
return make_node(AST_Assign, candidate, {
operator: "=",
left: make_node(AST_SymbolRef, candidate.name, candidate.name),
right: candidate.value
});
}
candidate.write_only = false;
return candidate;
}
// These node types have child nodes that execute sequentially,
// but are otherwise not safe to scan into or beyond them.
var sym;
if (node instanceof AST_Call
|| node instanceof AST_Exit
|| node instanceof AST_PropAccess
&& (side_effects || node.expression.may_throw_on_access(compressor))
|| node instanceof AST_SymbolRef
&& (lvalues[node.name]
|| side_effects && !references_in_scope(node.definition()))
|| (sym = lhs_or_def(node))
&& (sym instanceof AST_PropAccess || sym.name in lvalues)
|| (side_effects || !one_off)
&& (parent instanceof AST_Binary && lazy_op(parent.operator)
|| parent instanceof AST_Case
|| parent instanceof AST_Conditional
|| parent instanceof AST_If)) {
if (!(node instanceof AST_Scope)) descend(node, tt);
abort = true;
return node;
}
// Skip (non-executed) functions and (leading) default case in switch statements
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
});
var abort = false, replaced = 0, can_replace = !args || !hit;
if (!can_replace) {
for (var j = compressor.self().argnames.lastIndexOf(candidate.__name || candidate.name) + 1; j < args.length; j++) {
args[j].transform(tt);
for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
args[j].transform(scanner);
}
can_replace = true;
}
for (var i = stat_index; !abort && i < statements.length; i++) {
statements[i].transform(tt);
statements[i].transform(scanner);
}
if (candidate.multiple) {
var def = candidate.name.definition();
if (abort && def.references.length - def.replaced > replaced) replaced = false;
else {
abort = false;
hit = candidate.name instanceof AST_SymbolFunarg;
var value_def = candidate.value.definition();
for (var i = stat_index; !abort && i < statements.length; i++) {
statements[i].transform(multi_replacer);
}
}
}
if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
}
@@ -1025,7 +1074,7 @@ merge(Compressor.prototype, {
return !(arg instanceof AST_Expansion);
})) {
var fn_strict = compressor.has_directive("use strict");
if (fn_strict && fn.body.indexOf(fn_strict) < 0) fn_strict = false;
if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
var len = fn.argnames.length;
args = iife.args.slice(len);
var names = Object.create(null);
@@ -1081,12 +1130,22 @@ merge(Compressor.prototype, {
}
}
function mangleable_var(expr) {
var value = expr.value;
if (!(value instanceof AST_SymbolRef)) return false;
if (value.name == "arguments") return false;
if (value.definition().undeclared) return false;
expr.multiple = true;
return true;
}
function get_lhs(expr) {
if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) {
var def = expr.name.definition();
if (def.orig.length - (def._eliminiated || 0) > 1
&& !(expr.name instanceof AST_SymbolFunarg)
|| def.references.length == 1 && !compressor.exposed(def)) {
var declared = def.orig.length - def.eliminated;
var referenced = def.references.length - def.replaced;
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
|| (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) {
return make_node(AST_SymbolRef, expr.name, expr.name);
}
} else {
@@ -2655,12 +2714,14 @@ merge(Compressor.prototype, {
var def = tail.pop();
compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name));
remove(var_defs, def);
drop_decl(def.name.definition());
side_effects.unshift(make_node(AST_Assign, def, {
operator: "=",
left: make_node(AST_SymbolRef, def.name, def.name),
right: def.value
}));
def = def.name.definition();
drop_decl(def);
def.replaced--;
}
}
if (head.length > 0 || tail.length > 0) {
@@ -2885,17 +2946,29 @@ merge(Compressor.prototype, {
return self;
});
AST_Scope.DEFMETHOD("make_var_name", function(prefix) {
var var_names = this.var_names;
if (!var_names) {
this.var_names = var_names = Object.create(null);
this.enclosed.forEach(function(def) {
var_names[def.name] = true;
});
this.variables.each(function(def, name) {
var_names[name] = true;
});
}
prefix = prefix.replace(/[^a-z_$]+/ig, "_");
var name = prefix;
for (var i = 0; var_names[name]; i++) name = prefix + "$" + i;
var_names[name] = true;
return name;
});
AST_Scope.DEFMETHOD("hoist_properties", function(compressor){
var self = this;
if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
var defs_by_id = Object.create(null);
var var_names = Object.create(null);
self.enclosed.forEach(function(def) {
var_names[def.name] = true;
});
self.variables.each(function(def, name) {
var_names[name] = true;
});
var tt = new TreeTransformer(function(node) {
if (node instanceof AST_Definitions && tt.parent() instanceof AST_Export) return node;
if (node instanceof AST_VarDef) {
@@ -2904,6 +2977,7 @@ merge(Compressor.prototype, {
&& !(def = sym.definition()).escaped
&& !def.single_use
&& !def.direct_access
&& !top_retain(def)
&& (value = sym.fixed_value()) === node.value
&& value instanceof AST_Object) {
var defs = new Dictionary();
@@ -2935,17 +3009,13 @@ merge(Compressor.prototype, {
}
function make_sym(key) {
var prefix = sym.name + "_" + key.toString().replace(/[^a-z_$]+/ig, "_");
var name = prefix;
for (var i = 0; var_names[name]; i++) name = prefix + "$" + i;
var new_var = make_node(sym.CTOR, sym, {
name: name,
name: self.make_var_name(sym.name + "_" + key),
scope: self
});
var def = self.def_variable(new_var);
defs.set(key, def);
self.enclosed.push(def);
var_names[name] = true;
return new_var;
}
});
@@ -3569,136 +3639,139 @@ merge(Compressor.prototype, {
self.args.length = last;
}
if (compressor.option("unsafe")) {
if (is_undeclared_ref(exp)) {
switch (exp.name) {
case "Array":
if (self.args.length != 1) {
return make_node(AST_Array, self, {
elements: self.args
}).optimize(compressor);
}
break;
case "Object":
if (self.args.length == 0) {
return make_node(AST_Object, self, {
properties: []
});
}
break;
case "String":
if (self.args.length == 0) return make_node(AST_String, self, {
value: ""
});
if (self.args.length <= 1) return make_node(AST_Binary, self, {
left: self.args[0],
operator: "+",
right: make_node(AST_String, self, { value: "" })
if (is_undeclared_ref(exp)) switch (exp.name) {
case "Array":
if (self.args.length != 1) {
return make_node(AST_Array, self, {
elements: self.args
}).optimize(compressor);
break;
case "Number":
if (self.args.length == 0) return make_node(AST_Number, self, {
value: 0
});
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "+"
}).optimize(compressor);
case "Boolean":
if (self.args.length == 0) return make_node(AST_False, self);
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "!"
}),
operator: "!"
}).optimize(compressor);
break;
case "Symbol":
// Symbol's argument is only used for debugging.
self.args = [];
return self;
}
}
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
return make_node(AST_Binary, self, {
break;
case "Object":
if (self.args.length == 0) {
return make_node(AST_Object, self, {
properties: []
});
}
break;
case "String":
if (self.args.length == 0) return make_node(AST_String, self, {
value: ""
});
if (self.args.length <= 1) return make_node(AST_Binary, self, {
left: self.args[0],
operator: "+",
right: make_node(AST_String, self, { value: "" })
}).optimize(compressor);
break;
case "Number":
if (self.args.length == 0) return make_node(AST_Number, self, {
value: 0
});
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "+"
}).optimize(compressor);
case "Boolean":
if (self.args.length == 0) return make_node(AST_False, self);
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
expression: make_node(AST_UnaryPrefix, self, {
expression: self.args[0],
operator: "!"
}),
operator: "!"
}).optimize(compressor);
break;
case "Symbol":
// Symbol's argument is only used for debugging.
self.args = [];
return self;
} else if (exp instanceof AST_Dot) switch(exp.property) {
case "toString":
if (self.args.length == 0) return make_node(AST_Binary, self, {
left: make_node(AST_String, self, { value: "" }),
operator: "+",
right: exp.expression
}).optimize(compressor);
}
else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: {
var separator;
if (self.args.length > 0) {
separator = self.args[0].evaluate(compressor);
if (separator === self.args[0]) break EXIT; // not a constant
}
var elements = [];
var consts = [];
for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
var el = exp.expression.elements[i];
if (el instanceof AST_Expansion) break EXIT;
var value = el.evaluate(compressor);
if (value !== el) {
consts.push(value);
} else {
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
value: consts.join(separator)
}));
consts.length = 0;
break;
case "join":
if (exp.expression instanceof AST_Array) EXIT: {
var separator;
if (self.args.length > 0) {
separator = self.args[0].evaluate(compressor);
if (separator === self.args[0]) break EXIT; // not a constant
}
var elements = [];
var consts = [];
for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
var el = exp.expression.elements[i];
if (el instanceof AST_Expansion) break EXIT;
var value = el.evaluate(compressor);
if (value !== el) {
consts.push(value);
} else {
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
value: consts.join(separator)
}));
consts.length = 0;
}
elements.push(el);
}
elements.push(el);
}
}
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
value: consts.join(separator)
}));
}
if (elements.length == 0) return make_node(AST_String, self, { value: "" });
if (elements.length == 1) {
if (elements[0].is_string(compressor)) {
return elements[0];
if (consts.length > 0) {
elements.push(make_node(AST_String, self, {
value: consts.join(separator)
}));
}
return make_node(AST_Binary, elements[0], {
operator : "+",
left : make_node(AST_String, self, { value: "" }),
right : elements[0]
});
}
if (separator == "") {
var first;
if (elements[0].is_string(compressor)
|| elements[1].is_string(compressor)) {
first = elements.shift();
} else {
first = make_node(AST_String, self, { value: "" });
}
return elements.reduce(function(prev, el){
return make_node(AST_Binary, el, {
if (elements.length == 0) return make_node(AST_String, self, { value: "" });
if (elements.length == 1) {
if (elements[0].is_string(compressor)) {
return elements[0];
}
return make_node(AST_Binary, elements[0], {
operator : "+",
left : prev,
right : el
left : make_node(AST_String, self, { value: "" }),
right : elements[0]
});
}, first).optimize(compressor);
}
if (separator == "") {
var first;
if (elements[0].is_string(compressor)
|| elements[1].is_string(compressor)) {
first = elements.shift();
} else {
first = make_node(AST_String, self, { value: "" });
}
return elements.reduce(function(prev, el){
return make_node(AST_Binary, el, {
operator : "+",
left : prev,
right : el
});
}, first).optimize(compressor);
}
// need this awkward cloning to not affect original element
// best_of will decide which one to get through.
var node = self.clone();
node.expression = node.expression.clone();
node.expression.expression = node.expression.expression.clone();
node.expression.expression.elements = elements;
return best_of(compressor, self, node);
}
// need this awkward cloning to not affect original element
// best_of will decide which one to get through.
var node = self.clone();
node.expression = node.expression.clone();
node.expression.expression = node.expression.expression.clone();
node.expression.expression.elements = elements;
return best_of(compressor, self, node);
}
else if (exp instanceof AST_Dot && exp.expression.is_string(compressor) && exp.property == "charAt") {
var arg = self.args[0];
var index = arg ? arg.evaluate(compressor) : 0;
if (index !== arg) {
return make_node(AST_Sub, exp, {
expression: exp.expression,
property: make_node_from_constant(index | 0, arg || exp)
}).optimize(compressor);
break;
case "charAt":
if (exp.expression.is_string(compressor)) {
var arg = self.args[0];
var index = arg ? arg.evaluate(compressor) : 0;
if (index !== arg) {
return make_node(AST_Sub, exp, {
expression: exp.expression,
property: make_node_from_constant(index | 0, arg || exp)
}).optimize(compressor);
}
}
break;
}
}
if (compressor.option("unsafe_Func")
@@ -4510,16 +4583,21 @@ merge(Compressor.prototype, {
d.fixed = fixed = make_node(AST_Function, fixed, fixed);
}
if (d.single_use && fixed instanceof AST_Function) {
if (!compressor.option("reduce_funcs") && d.scope !== self.scope) {
if (d.scope !== self.scope
&& (!compressor.option("reduce_funcs")
|| d.escaped
|| fixed.inlined)) {
d.single_use = false;
} else if (d.escaped && d.scope !== self.scope || recursive_ref(compressor, d)) {
} else if (recursive_ref(compressor, d)) {
d.single_use = false;
} else if (d.scope !== self.scope || d.orig[0] instanceof AST_SymbolFunarg) {
d.single_use = fixed.is_constant_expression(self.scope);
if (d.single_use == "f") {
var scope = self.scope;
do {
if (scope.name) scope.name.definition().single_use = false;
if (scope instanceof AST_Defun || scope instanceof AST_Function) {
scope.inlined = true;
}
} while (scope = scope.parent_scope);
}
}