From 7918a50d52809a854d6808c7a97f87f8e256506e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 17 Dec 2017 23:01:08 +0800 Subject: [PATCH] improve `reset_opt_flags()` (#2610) --- lib/compress.js | 604 +++++++++++++++++++++++++----------------------- 1 file changed, 311 insertions(+), 293 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index ebcae482..bccd7630 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -293,14 +293,12 @@ merge(Compressor.prototype, { if (index >= 0) { node.body[index] = node.body[index].transform(tt); } - } - if (node instanceof AST_If) { + } else if (node instanceof AST_If) { node.body = node.body.transform(tt); if (node.alternative) { node.alternative = node.alternative.transform(tt); } - } - if (node instanceof AST_With) { + } else if (node instanceof AST_With) { node.body = node.body.transform(tt); } return node; @@ -308,262 +306,44 @@ merge(Compressor.prototype, { self.transform(tt); }); - AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { - var reduce_vars = compressor.option("reduce_vars"); - 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; - } + (function(def){ + def(AST_Node, noop); + + function reset_def(compressor, def) { + def.direct_access = false; + def.escaped = false; + if (def.scope.uses_eval || def.scope.uses_with) { + def.fixed = false; + } else if (!compressor.exposed(def)) { + def.fixed = undefined; + } else { + def.fixed = false; } - }); - this.walk(tw); + def.references = []; + def.should_replace = undefined; + def.single_use = undefined; + } + + function reset_variables(compressor, node) { + node.variables.each(function(def) { + reset_def(compressor, def); + }); + } - function mark(def, safe) { - safe_ids[def.id] = safe; + function push(tw) { + tw.safe_ids = Object.create(tw.safe_ids); } - function safe_to_read(def) { - if (safe_ids[def.id]) { + 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; @@ -574,9 +354,9 @@ merge(Compressor.prototype, { 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; + 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) { @@ -585,35 +365,12 @@ merge(Compressor.prototype, { }); } - 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.escaped = false; - if (def.scope.uses_eval || def.scope.uses_with) { - def.fixed = false; - } else if (!compressor.exposed(def)) { - def.fixed = undefined; - } else { - def.fixed = false; - } - def.references = []; - def.should_replace = undefined; - def.single_use = undefined; - } - - function ref_once(def) { - return unused + function ref_once(tw, compressor, def) { + return compressor.option("unused") && !def.scope.uses_eval && !def.scope.uses_with && def.references.length == 1 - && loop_ids[def.id] === in_loop; + && tw.loop_ids[def.id] === tw.in_loop; } function is_immutable(value) { @@ -642,7 +399,7 @@ merge(Compressor.prototype, { 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); if (is_lhs(node, parent) || !immutable @@ -652,16 +409,16 @@ merge(Compressor.prototype, { || !(parent instanceof AST_New) && value.contains_this())) { return true; } 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) { 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) { - 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); if (value) { if (value.is_constant()) return; @@ -677,17 +434,278 @@ merge(Compressor.prototype, { || parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Conditional && node !== parent.condition || 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) { 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) { 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 (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() { -- 2.34.1