From a0f4fd390a0a1af80964aab9754bf5358db575e2 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Sat, 18 Feb 2017 19:19:55 +0800 Subject: [PATCH] improve reduce_vars and fix a bug - update modified flag between compress() passes - support IIFE arguments - fix corner case with multiple definitions closes #1473 --- lib/compress.js | 41 ++++++++++-- lib/scope.js | 26 ++------ test/compress/reduce_vars.js | 124 ++++++++++++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 30 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 345a414a..72afe92a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -108,7 +108,8 @@ merge(Compressor.prototype, { compress: function(node) { var passes = +this.options.passes || 1; for (var pass = 0; pass < passes && pass < 3; ++pass) { - if (pass > 0) node.clear_opt_flags(); + if (pass > 0 || this.option("reduce_vars")) + node.reset_opt_flags(this); node = node.transform(this); } return node; @@ -167,19 +168,45 @@ merge(Compressor.prototype, { return this.print_to_string() == node.print_to_string(); }); - AST_Node.DEFMETHOD("clear_opt_flags", function(){ - this.walk(new TreeWalker(function(node){ + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor){ + var reduce_vars = compressor.option("reduce_vars"); + var tw = new TreeWalker(function(node){ + if (reduce_vars && node instanceof AST_Scope) { + node.variables.each(function(def) { + delete def.modified; + }); + } if (node instanceof AST_SymbolRef) { var d = node.definition(); - if (d && d.init) { + if (d.init) { delete d.init._evaluated; } + if (reduce_vars && (d.orig.length > 1 || isModified(node, 0))) { + d.modified = true; + } + } + if (reduce_vars && node instanceof AST_Call && node.expression instanceof AST_Function) { + node.expression.argnames.forEach(function(arg, i) { + arg.definition().init = node.args[i] || make_node(AST_Undefined, node); + }); } if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false; node._optimized = false; } - })); + }); + this.walk(tw); + + function isModified(node, level) { + var parent = tw.parent(level); + if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") + || parent instanceof AST_Assign && parent.left === node + || parent instanceof AST_Call && parent.expression === node) { + return true; + } else if (parent instanceof AST_PropAccess && parent.expression === node) { + return isModified(parent, level + 1); + } + } }); function make_node(ctor, orig, props) { @@ -459,7 +486,7 @@ merge(Compressor.prototype, { var_defs_removed = true; } // Further optimize statement after substitution. - stat.clear_opt_flags(); + stat.reset_opt_flags(compressor); compressor.warn("Replacing " + (is_constant ? "constant" : "variable") + " " + var_name + " [{file}:{line},{col}]", node.start); @@ -1158,7 +1185,7 @@ merge(Compressor.prototype, { this._evaluating = true; try { var d = this.definition(); - if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) { + if ((d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) { if (compressor.option("unsafe")) { if (!HOP(d.init, '_evaluated')) { d.init._evaluated = ev(d.init, compressor); diff --git a/lib/scope.js b/lib/scope.js index d5cadd34..6ad12616 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -183,17 +183,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var func = null; var globals = self.globals = new Dictionary(); var tw = new TreeWalker(function(node, descend){ - function isModified(node, level) { - var parent = tw.parent(level); - if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") - || parent instanceof AST_Assign && parent.left === node - || parent instanceof AST_Call && parent.expression === node) { - return true; - } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return isModified(parent, level + 1); - } - } - if (node instanceof AST_Lambda) { var prev_func = func; func = node; @@ -217,21 +206,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.scope.uses_arguments = true; } if (!sym) { - var g; if (globals.has(name)) { - g = globals.get(name); + sym = globals.get(name); } else { - g = new SymbolDef(self, globals.size(), node); - g.undeclared = true; - g.global = true; - globals.set(name, g); + sym = new SymbolDef(self, globals.size(), node); + sym.undeclared = true; + sym.global = true; + globals.set(name, sym); } - sym = g; } node.thedef = sym; - if (isModified(node, 0)) { - sym.modified = true; - } node.reference(options); return true; } diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 2301a92a..d9d02efa 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -108,8 +108,6 @@ modified: { } console.log(a + b); console.log(b + c); - // TODO: as "modified" is determined in "figure_out_scope", - // even "passes" wouldn't improve this any further console.log(a + c); console.log(a + b + c); } @@ -350,3 +348,125 @@ unsafe_evaluate_equality: { } } } + +passes: { + options = { + conditionals: true, + evaluate: true, + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + function f() { + var a = 1, b = 2, c = 3; + if (a) { + b = c; + } else { + c = b; + } + console.log(a + b); + console.log(b + c); + console.log(a + c); + console.log(a + b + c); + } + } + expect: { + function f() { + var b = 2, c = 3; + b = c; + console.log(1 + b); + console.log(b + 3); + console.log(4); + console.log(1 + b + 3); + } + } +} + +iife: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + !function(a, b, c) { + b++; + console.log(a - 1, b * 1, c + 2); + }(1, 2, 3); + } + expect: { + !function(a, b, c) { + b++; + console.log(0, 1 * b, 5); + }(1, 2, 3); + } +} + +iife_new: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + var A = new function(a, b, c) { + b++; + console.log(a - 1, b * 1, c + 2); + }(1, 2, 3); + } + expect: { + var A = new function(a, b, c) { + b++; + console.log(0, 1 * b, 5); + }(1, 2, 3); + } +} + +multi_def: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f(a) { + if (a) + var b = 1; + else + var b = 2 + console.log(b + 1); + } + } + expect: { + function f(a) { + if (a) + var b = 1; + else + var b = 2 + console.log(b + 1); + } + } +} + +multi_def_2: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + if (code == 16) + var bitsLength = 2, bitsOffset = 3, what = len; + else if (code == 17) + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + else if (code == 18) + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + var repeatLength = this.getBits(bitsLength) + bitsOffset; + } + expect: { + if (16 == code) + var bitsLength = 2, bitsOffset = 3, what = len; + else if (17 == code) + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + else if (18 == code) + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + var repeatLength = this.getBits(bitsLength) + bitsOffset; + } +} -- 2.34.1