improve reset_opt_flags() (#2610)

This commit is contained in:
Alex Lam S.L
2017-12-17 23:01:08 +08:00
committed by GitHub
parent 21794c9b8d
commit 7918a50d52

View File

@@ -293,14 +293,12 @@ merge(Compressor.prototype, {
if (index >= 0) { if (index >= 0) {
node.body[index] = node.body[index].transform(tt); node.body[index] = node.body[index].transform(tt);
} }
} } else if (node instanceof AST_If) {
if (node instanceof AST_If) {
node.body = node.body.transform(tt); node.body = node.body.transform(tt);
if (node.alternative) { if (node.alternative) {
node.alternative = node.alternative.transform(tt); node.alternative = node.alternative.transform(tt);
} }
} } else if (node instanceof AST_With) {
if (node instanceof AST_With) {
node.body = node.body.transform(tt); node.body = node.body.transform(tt);
} }
return node; return node;
@@ -308,292 +306,10 @@ merge(Compressor.prototype, {
self.transform(tt); self.transform(tt);
}); });
AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { (function(def){
var reduce_vars = compressor.option("reduce_vars"); def(AST_Node, noop);
var unused = compressor.option("unused");
// Stack of look-up tables to keep track of whether a `SymbolDef` has been
// properly assigned before use:
// - `push()` & `pop()` when visiting conditional branches
// - backup & restore via `save_ids` when visiting out-of-order sections
var safe_ids = Object.create(null);
var suppressor = new TreeWalker(function(node) {
if (!(node instanceof AST_Symbol)) return;
var d = node.definition();
if (!d) return;
if (node instanceof AST_SymbolRef) d.references.push(node);
d.fixed = false;
});
var in_loop = null;
var loop_ids = Object.create(null);
var tw = new TreeWalker(function(node, descend) {
node._squeezed = false;
node._optimized = false;
if (reduce_vars) {
if (node instanceof AST_Toplevel) node.globals.each(reset_def);
if (node instanceof AST_Scope) node.variables.each(reset_def);
if (node instanceof AST_SymbolRef) {
var d = node.definition();
d.references.push(node);
if (d.references.length == 1
&& !d.fixed
&& d.orig[0] instanceof AST_SymbolDefun) {
loop_ids[d.id] = in_loop;
}
var value;
if (d.fixed === undefined || !safe_to_read(d) || d.single_use == "m") {
d.fixed = false;
} else if (d.fixed) {
value = node.fixed_value();
if (value && ref_once(d)) {
d.single_use = value instanceof AST_Lambda
|| d.scope === node.scope && value.is_constant_expression();
} else {
d.single_use = false;
}
if (is_modified(node, value, 0, is_immutable(value))) {
if (d.single_use) {
d.single_use = "m";
} else {
d.fixed = false;
}
}
}
mark_escaped(d, node.scope, node, value, 0);
}
if (node instanceof AST_SymbolCatch) {
node.definition().fixed = false;
}
if (node instanceof AST_VarDef) {
var d = node.name.definition();
if (d.fixed === undefined || safe_to_assign(d, node.value)) {
if (node.value) {
d.fixed = function() {
return node.value;
};
loop_ids[d.id] = in_loop;
mark(d, false);
descend();
} else {
d.fixed = null;
}
mark(d, true);
return true;
} else if (node.value) {
d.fixed = false;
}
}
if (node instanceof AST_Assign
&& node.operator == "="
&& node.left instanceof AST_SymbolRef) {
var d = node.left.definition();
if (safe_to_assign(d, node.right)
|| d.fixed === undefined && all(d.orig, function(sym) {
return sym instanceof AST_SymbolVar;
})) {
d.references.push(node.left);
d.fixed = function() {
return node.right;
};
mark(d, false);
node.right.walk(tw);
mark(d, true);
return true;
}
}
if (node instanceof AST_Defun) {
node.inlined = false;
var d = node.name.definition();
if (compressor.exposed(d) || safe_to_read(d)) {
d.fixed = false;
} else {
d.fixed = node;
d.single_use = ref_once(d);
loop_ids[d.id] = in_loop;
mark(d, true);
}
var save_ids = safe_ids;
safe_ids = Object.create(null);
descend();
safe_ids = save_ids;
return true;
}
if (node instanceof AST_Function) {
node.inlined = false;
push();
var iife;
if (!node.name
&& (iife = tw.parent()) instanceof AST_Call
&& iife.expression === node) {
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
if (!node.uses_arguments && d.fixed === undefined) {
d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
loop_ids[d.id] = in_loop;
mark(d, true);
} else {
d.fixed = false;
}
});
}
descend();
pop();
return true;
}
if (node instanceof AST_Accessor) {
push();
descend();
pop();
return true;
}
if (node instanceof AST_Binary && lazy_op(node.operator)) {
node.left.walk(tw);
push();
node.right.walk(tw);
pop();
return true;
}
if (node instanceof AST_Conditional) {
node.condition.walk(tw);
push();
node.consequent.walk(tw);
pop();
push();
node.alternative.walk(tw);
pop();
return true;
}
if (node instanceof AST_If) {
node.condition.walk(tw);
push();
node.body.walk(tw);
pop();
if (node.alternative) {
push();
node.alternative.walk(tw);
pop();
}
return true;
}
if (node instanceof AST_Do) {
var saved_loop = in_loop;
in_loop = node;
push();
node.body.walk(tw);
node.condition.walk(tw);
pop();
in_loop = saved_loop;
return true;
}
if (node instanceof AST_While) {
var saved_loop = in_loop;
in_loop = node;
push();
node.condition.walk(tw);
node.body.walk(tw);
pop();
in_loop = saved_loop;
return true;
}
if (node instanceof AST_LabeledStatement) {
push();
node.body.walk(tw);
pop();
return true;
}
if (node instanceof AST_For) {
if (node.init) node.init.walk(tw);
var saved_loop = in_loop;
in_loop = node;
if (node.condition) {
push();
node.condition.walk(tw);
pop();
}
push();
node.body.walk(tw);
pop();
if (node.step) {
push();
node.step.walk(tw);
pop();
}
in_loop = saved_loop;
return true;
}
if (node instanceof AST_ForIn) {
node.init.walk(suppressor);
node.object.walk(tw);
var saved_loop = in_loop;
in_loop = node;
push();
node.body.walk(tw);
pop();
in_loop = saved_loop;
return true;
}
if (node instanceof AST_Try) {
push();
walk_body(node, tw);
pop();
if (node.bcatch) {
push();
node.bcatch.walk(tw);
pop();
}
if (node.bfinally) node.bfinally.walk(tw);
return true;
}
if (node instanceof AST_SwitchBranch) {
push();
descend();
pop();
return true;
}
}
});
this.walk(tw);
function mark(def, safe) { function reset_def(compressor, def) {
safe_ids[def.id] = safe;
}
function safe_to_read(def) {
if (safe_ids[def.id]) {
if (def.fixed == null) {
var orig = def.orig[0];
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig);
}
return true;
}
return def.fixed instanceof AST_Defun;
}
function safe_to_assign(def, value) {
if (!HOP(safe_ids, def.id)) return false;
if (!safe_to_read(def)) return false;
if (def.fixed === false) return false;
if (def.fixed != null && (!value || def.references.length > 0)) return false;
return all(def.orig, function(sym) {
return !(sym instanceof AST_SymbolDefun
|| sym instanceof AST_SymbolLambda);
});
}
function push() {
safe_ids = Object.create(safe_ids);
}
function pop() {
safe_ids = Object.getPrototypeOf(safe_ids);
}
function reset_def(def) {
def.direct_access = false; def.direct_access = false;
def.escaped = false; def.escaped = false;
if (def.scope.uses_eval || def.scope.uses_with) { if (def.scope.uses_eval || def.scope.uses_with) {
@@ -608,12 +324,53 @@ merge(Compressor.prototype, {
def.single_use = undefined; def.single_use = undefined;
} }
function ref_once(def) { function reset_variables(compressor, node) {
return unused node.variables.each(function(def) {
reset_def(compressor, def);
});
}
function push(tw) {
tw.safe_ids = Object.create(tw.safe_ids);
}
function pop(tw) {
tw.safe_ids = Object.getPrototypeOf(tw.safe_ids);
}
function mark(tw, def, safe) {
tw.safe_ids[def.id] = safe;
}
function safe_to_read(tw, def) {
if (tw.safe_ids[def.id]) {
if (def.fixed == null) {
var orig = def.orig[0];
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig);
}
return true;
}
return def.fixed instanceof AST_Defun;
}
function safe_to_assign(tw, def, value) {
if (!HOP(tw.safe_ids, def.id)) return false;
if (!safe_to_read(tw, def)) return false;
if (def.fixed === false) return false;
if (def.fixed != null && (!value || def.references.length > 0)) return false;
return all(def.orig, function(sym) {
return !(sym instanceof AST_SymbolDefun
|| sym instanceof AST_SymbolLambda);
});
}
function ref_once(tw, compressor, def) {
return compressor.option("unused")
&& !def.scope.uses_eval && !def.scope.uses_eval
&& !def.scope.uses_with && !def.scope.uses_with
&& def.references.length == 1 && def.references.length == 1
&& loop_ids[def.id] === in_loop; && tw.loop_ids[def.id] === tw.in_loop;
} }
function is_immutable(value) { function is_immutable(value) {
@@ -642,7 +399,7 @@ merge(Compressor.prototype, {
return value instanceof AST_SymbolRef && value.fixed_value() || value; return value instanceof AST_SymbolRef && value.fixed_value() || value;
} }
function is_modified(node, value, level, immutable) { function is_modified(tw, node, value, level, immutable) {
var parent = tw.parent(level); var parent = tw.parent(level);
if (is_lhs(node, parent) if (is_lhs(node, parent)
|| !immutable || !immutable
@@ -652,16 +409,16 @@ merge(Compressor.prototype, {
|| !(parent instanceof AST_New) && value.contains_this())) { || !(parent instanceof AST_New) && value.contains_this())) {
return true; return true;
} else if (parent instanceof AST_Array) { } else if (parent instanceof AST_Array) {
return is_modified(parent, parent, level + 1); return is_modified(tw, parent, parent, level + 1);
} else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
var obj = tw.parent(level + 1); var obj = tw.parent(level + 1);
return is_modified(obj, obj, level + 2); return is_modified(tw, obj, obj, level + 2);
} else if (parent instanceof AST_PropAccess && parent.expression === node) { } else if (parent instanceof AST_PropAccess && parent.expression === node) {
return !immutable && is_modified(parent, read_property(value, parent.property), level + 1); return !immutable && is_modified(tw, parent, read_property(value, parent.property), level + 1);
} }
} }
function mark_escaped(d, scope, node, value, level) { function mark_escaped(tw, d, scope, node, value, level) {
var parent = tw.parent(level); var parent = tw.parent(level);
if (value) { if (value) {
if (value.is_constant()) return; if (value.is_constant()) return;
@@ -677,17 +434,278 @@ merge(Compressor.prototype, {
|| parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Binary && lazy_op(parent.operator)
|| parent instanceof AST_Conditional && node !== parent.condition || parent instanceof AST_Conditional && node !== parent.condition
|| parent instanceof AST_Sequence && node === parent.tail_node()) { || parent instanceof AST_Sequence && node === parent.tail_node()) {
mark_escaped(d, scope, parent, parent, level + 1); mark_escaped(tw, d, scope, parent, parent, level + 1);
} else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
var obj = tw.parent(level + 1); var obj = tw.parent(level + 1);
mark_escaped(d, scope, obj, obj, level + 2); mark_escaped(tw, d, scope, obj, obj, level + 2);
} else if (parent instanceof AST_PropAccess && node === parent.expression) { } else if (parent instanceof AST_PropAccess && node === parent.expression) {
value = read_property(value, parent.property); value = read_property(value, parent.property);
mark_escaped(d, scope, parent, value, level + 1); mark_escaped(tw, d, scope, parent, value, level + 1);
if (value) return; if (value) return;
} }
if (level == 0) d.direct_access = true; if (level == 0) d.direct_access = true;
} }
var suppressor = new TreeWalker(function(node) {
if (!(node instanceof AST_Symbol)) return;
var d = node.definition();
if (!d) return;
if (node instanceof AST_SymbolRef) d.references.push(node);
d.fixed = false;
});
def(AST_Accessor, function(tw, descend) {
push(tw);
descend();
pop(tw);
return true;
});
def(AST_Assign, function(tw) {
var node = this;
if (node.operator != "=" || !(node.left instanceof AST_SymbolRef)) return;
var d = node.left.definition();
if (safe_to_assign(tw, d, node.right)
|| d.fixed === undefined && all(d.orig, function(sym) {
return sym instanceof AST_SymbolVar;
})) {
d.references.push(node.left);
d.fixed = function() {
return node.right;
};
mark(tw, d, false);
node.right.walk(tw);
mark(tw, d, true);
return true;
}
});
def(AST_Binary, function(tw) {
if (!lazy_op(this.operator)) return;
this.left.walk(tw);
push(tw);
this.right.walk(tw);
pop(tw);
return true;
});
def(AST_Conditional, function(tw) {
this.condition.walk(tw);
push(tw);
this.consequent.walk(tw);
pop(tw);
push(tw);
this.alternative.walk(tw);
pop(tw);
return true;
});
def(AST_Defun, function(tw, descend, compressor) {
reset_variables(compressor, this);
this.inlined = false;
var d = this.name.definition();
if (compressor.exposed(d) || safe_to_read(tw, d)) {
d.fixed = false;
} else {
d.fixed = this;
d.single_use = ref_once(tw, compressor, d);
tw.loop_ids[d.id] = tw.in_loop;
mark(tw, d, true);
}
var save_ids = tw.safe_ids;
tw.safe_ids = Object.create(null);
descend();
tw.safe_ids = save_ids;
return true;
});
def(AST_Do, function(tw) {
var saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
this.body.walk(tw);
this.condition.walk(tw);
pop(tw);
tw.in_loop = saved_loop;
return true;
});
def(AST_For, function(tw) {
if (this.init) this.init.walk(tw);
var saved_loop = tw.in_loop;
tw.in_loop = this;
if (this.condition) {
push(tw);
this.condition.walk(tw);
pop(tw);
}
push(tw);
this.body.walk(tw);
pop(tw);
if (this.step) {
push(tw);
this.step.walk(tw);
pop(tw);
}
tw.in_loop = saved_loop;
return true;
});
def(AST_ForIn, function(tw) {
this.init.walk(suppressor);
this.object.walk(tw);
var saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
this.body.walk(tw);
pop(tw);
tw.in_loop = saved_loop;
return true;
});
def(AST_Function, function(tw, descend, compressor) {
var node = this;
reset_variables(compressor, node);
node.inlined = false;
push(tw);
var iife;
if (!node.name
&& (iife = tw.parent()) instanceof AST_Call
&& iife.expression === node) {
// Virtually turn IIFE parameters into variable definitions:
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
// So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
if (!node.uses_arguments && d.fixed === undefined) {
d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
tw.loop_ids[d.id] = tw.in_loop;
mark(tw, d, true);
} else {
d.fixed = false;
}
});
}
descend();
pop(tw);
return true;
});
def(AST_If, function(tw) {
this.condition.walk(tw);
push(tw);
this.body.walk(tw);
pop(tw);
if (this.alternative) {
push(tw);
this.alternative.walk(tw);
pop(tw);
}
return true;
});
def(AST_LabeledStatement, function(tw) {
push(tw);
this.body.walk(tw);
pop(tw);
return true;
});
def(AST_SwitchBranch, function(tw, descend) {
push(tw);
descend();
pop(tw);
return true;
});
def(AST_SymbolCatch, function() {
this.definition().fixed = false;
});
def(AST_SymbolRef, function(tw, descend, compressor) {
var d = this.definition();
d.references.push(this);
if (d.references.length == 1
&& !d.fixed
&& d.orig[0] instanceof AST_SymbolDefun) {
tw.loop_ids[d.id] = tw.in_loop;
}
var value;
if (d.fixed === undefined || !safe_to_read(tw, d) || d.single_use == "m") {
d.fixed = false;
} else if (d.fixed) {
value = this.fixed_value();
if (value && ref_once(tw, compressor, d)) {
d.single_use = value instanceof AST_Lambda
|| d.scope === this.scope && value.is_constant_expression();
} else {
d.single_use = false;
}
if (is_modified(tw, this, value, 0, is_immutable(value))) {
if (d.single_use) {
d.single_use = "m";
} else {
d.fixed = false;
}
}
}
mark_escaped(tw, d, this.scope, this, value, 0);
});
def(AST_Toplevel, function(tw, descend, compressor) {
this.globals.each(function(def) {
reset_def(compressor, def);
});
reset_variables(compressor, this);
});
def(AST_Try, function(tw) {
push(tw);
walk_body(this, tw);
pop(tw);
if (this.bcatch) {
push(tw);
this.bcatch.walk(tw);
pop(tw);
}
if (this.bfinally) this.bfinally.walk(tw);
return true;
});
def(AST_VarDef, function(tw, descend) {
var node = this;
var d = node.name.definition();
if (d.fixed === undefined || safe_to_assign(tw, d, node.value)) {
if (node.value) {
d.fixed = function() {
return node.value;
};
tw.loop_ids[d.id] = tw.in_loop;
mark(tw, d, false);
descend();
} else {
d.fixed = null;
}
mark(tw, d, true);
return true;
} else if (node.value) {
d.fixed = false;
}
});
def(AST_While, function(tw) {
var saved_loop = tw.in_loop;
tw.in_loop = this;
push(tw);
this.condition.walk(tw);
this.body.walk(tw);
pop(tw);
tw.in_loop = saved_loop;
return true;
});
})(function(node, func){
node.DEFMETHOD("reduce_vars", func);
});
AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
var reduce_vars = compressor.option("reduce_vars");
var tw = new TreeWalker(function(node, descend) {
node._squeezed = false;
node._optimized = false;
if (reduce_vars) return node.reduce_vars(tw, descend, compressor);
});
// Stack of look-up tables to keep track of whether a `SymbolDef` has been
// properly assigned before use:
// - `push()` & `pop()` when visiting conditional branches
// - backup & restore via `save_ids` when visiting out-of-order sections
tw.safe_ids = Object.create(null);
tw.in_loop = null;
tw.loop_ids = Object.create(null);
this.walk(tw);
}); });
AST_Symbol.DEFMETHOD("fixed_value", function() { AST_Symbol.DEFMETHOD("fixed_value", function() {