diff options
author | Mihai Bazon <mihai@bazon.net> | 2012-09-14 15:36:38 +0300 |
---|---|---|
committer | Mihai Bazon <mihai@bazon.net> | 2012-09-14 15:36:38 +0300 |
commit | 924aa580602a4ad94f1079dcd157286314066553 (patch) | |
tree | acd65dd576ded856e5e7cc3b15ee473539b645ac /lib/compress.js | |
parent | 93b973c99dd25aeff66ff31f2881e9ce252eaac6 (diff) | |
download | tracifyjs-924aa580602a4ad94f1079dcd157286314066553.tar.gz tracifyjs-924aa580602a4ad94f1079dcd157286314066553.zip |
more optimizations that v1 does and some cleanups
- a = a + x ==> a+=x
- joining consecutive var statements (hoisting is not always desirable)
- x == false ==> x == 0, x != true ==> x != 1
- x, x ==> x; x = exp(), x ==> x = exp()
- discarding useless break-s
Diffstat (limited to 'lib/compress.js')
-rw-r--r-- | lib/compress.js | 245 |
1 files changed, 187 insertions, 58 deletions
diff --git a/lib/compress.js b/lib/compress.js index 15eab988..52dc0113 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -68,6 +68,8 @@ function Compressor(options, false_by_default) { hoist_funs : !false_by_default, hoist_vars : !false_by_default, if_return : !false_by_default, + join_vars : !false_by_default, + cascade : !false_by_default, warnings : true }); @@ -116,6 +118,11 @@ function Compressor(options, false_by_default) { return this; }); + AST_Node.DEFMETHOD("equivalent_to", function(node){ + // XXX: this is a rather expensive way to test two node's equivalence: + return this.print_to_string() == node.print_to_string(); + }); + function make_node(ctor, orig, props) { if (!props) props = {}; if (!props.start) props.start = orig.start; @@ -171,6 +178,9 @@ function Compressor(options, false_by_default) { if (compressor.option("if_return")) { statements = handle_if_return(statements, compressor); } + if (compressor.option("join_vars")) { + statements = join_consecutive_vars(statements, compressor); + } } while (CHANGED); return statements; @@ -210,11 +220,11 @@ function Compressor(options, false_by_default) { } } if (stat instanceof AST_If - && stat.body instanceof AST_Return + && stat.body instanceof AST_Exit && stat.body.value && !stat.alternative && i < last - && statements[i + 1] instanceof AST_Return + && statements[i + 1].TYPE == stat.body.TYPE && statements[i + 1].value) { CHANGED = true; return MAP.last(make_node(AST_If, stat, { @@ -223,6 +233,27 @@ function Compressor(options, false_by_default) { alternative: statements[i + 1] }).optimize(compressor)); } + if (stat instanceof AST_If + && stat.alternative instanceof AST_Exit) { + CHANGED = true; + return MAP.last(MAP.splice([ + // 1. negate IF and put the exit in the consequent + make_node(AST_If, stat, { + condition: stat.condition.negate(compressor), + body: stat.alternative + }), + // 2. the IF body can now go outside the if + stat.body + // and 3. add the rest of the statements + ].concat(statements.slice(i + 1)))); + return MAP.last(make_node(AST_If, stat, { + condition: stat.condition.negate(compressor), + body: stat.alternative, + alternative: make_node(AST_BlockStatement, stat.body, { + body: [ stat.body ].concat(statements.slice(i + 1)) + }) + }).optimize(compressor)); + } return stat; }); }; @@ -242,46 +273,63 @@ function Compressor(options, false_by_default) { }, []); }; - // XXX: this is destructive -- it modifies tree nodes. - function sequencesize(statements) { - var prev = null, last = statements.length - 1; - if (last) statements = statements.reduce(function(a, cur, i){ - if (prev instanceof AST_SimpleStatement - && cur instanceof AST_SimpleStatement) { - CHANGED = true; - var seq = make_node(AST_Seq, prev, { - first: prev.body, - second: cur.body - }); - prev.body = seq; - } - else if (i == last - && cur instanceof AST_Exit && cur.value - && a.length > 0 - && prev instanceof AST_SimpleStatement) { - CHANGED = true; - var seq = make_node(AST_Seq, prev, { - first: prev.body, - second: cur.value - }); - cur.value = seq; - a.pop(); - a.push(cur); - return a; + function sequencesize(statements, compressor) { + if (statements.length < 2) return statements; + var seq = [], ret = []; + function push_seq() { + seq = AST_Seq.from_array(seq); + if (seq) ret.push(make_node(AST_SimpleStatement, seq, { + body: seq + })); + seq = []; + }; + statements.forEach(function(stat){ + if (stat instanceof AST_SimpleStatement) seq.push(stat.body); + else push_seq(), ret.push(stat); + }); + push_seq(); + + // if the last node is return or throw, we can mix in the + // previous sequence which might help reducing the list to + // a single statement. + var exit = ret[ret.length - 1], prev = ret[ret.length - 2]; + if (prev instanceof AST_SimpleStatement + && exit instanceof AST_Exit && exit.value) { + ret.pop(); + ret.pop(); + if (prev.body instanceof AST_Seq) { + prev.body.add(exit.value); + prev.body = prev.body.optimize(compressor); + exit.value = prev.body; } else { - a.push(cur); - prev = cur; + exit.value = AST_Seq.cons(prev.body, exit.value).optimize(compressor); + } + ret.push(exit); + } + + CHANGED = ret.length != statements.length; + return ret; + }; + + function join_consecutive_vars(statements, compressor) { + var prev = null; + return statements.reduce(function(a, stat){ + if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) { + prev.definitions = prev.definitions.concat(stat.definitions); + CHANGED = true; + } else { + prev = stat; + a.push(stat); } return a; }, []); - return statements; }; }; function extract_declarations_from_unreachable_code(compressor, stat, target) { - warn_dead_code(stat); + compressor.warn("Dropping unreachable code [{line},{col}]", stat.start); stat.walk(new TreeWalker(function(node){ if (node instanceof AST_Definitions) { compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start); @@ -322,7 +370,7 @@ function Compressor(options, false_by_default) { return this.operator == "=" && this.right.is_boolean(); }); def(AST_Seq, function(){ - return this.second.is_boolean(); + return this.cdr.is_boolean(); }); def(AST_True, function(){ return true }); def(AST_False, function(){ return true }); @@ -481,7 +529,7 @@ function Compressor(options, false_by_default) { }); def(AST_Seq, function(compressor){ var self = this.clone(); - self.second = self.second.negate(compressor); + self.cdr = self.cdr.negate(compressor); return self; }); def(AST_Conditional, function(compressor){ @@ -550,6 +598,21 @@ function Compressor(options, false_by_default) { || this.operator == "--"; }); def(AST_SymbolRef, function(){ return false }); + def(AST_Object, function(){ + for (var i = this.properties.length; --i >= 0;) + if (this.properties[i].has_side_effects()) + return true; + return false; + }); + def(AST_ObjectProperty, function(){ + return this.value.has_side_effects(); + }); + def(AST_Array, function(){ + for (var i = this.elements.length; --i >= 0;) + if (this.elements[i].has_side_effects()) + return true; + return false; + }); })(function(node, func){ node.DEFMETHOD("has_side_effects", func); }); @@ -634,7 +697,7 @@ function Compressor(options, false_by_default) { } }); self.walk(tw); - if (vars_found > 0 && vardecl.length > 1) { + if (vars_found > 0 && vardecl.length > 0) { vardecl.forEach(function(v){ v.hoisted = true }); var node = make_node(AST_Var, self, { definitions: Object.keys(vars).map(function(name){ @@ -657,7 +720,7 @@ function Compressor(options, false_by_default) { AST_SimpleStatement.DEFMETHOD("optimize", function(compressor){ if (!this.body.has_side_effects()) { - AST_Node.warn("Dropping side-effect-free statement [{line},{col}]", this.start); + compressor.warn("Dropping side-effect-free statement [{line},{col}]", this.start); return make_node(AST_EmptyStatement, this); } return this; @@ -674,10 +737,6 @@ function Compressor(options, false_by_default) { return self.optimize(compressor); }); - function warn_dead_code(node) { - AST_Node.warn("Dropping unreachable code [{line},{col}]", node.start); - }; - AST_DWLoop.DEFMETHOD("optimize", function(compressor){ var self = this; var cond = self.condition.evaluate(compressor); @@ -798,7 +857,7 @@ function Compressor(options, false_by_default) { self.condition = cond[0]; if (cond.length == 2) { if (cond[1]) { - AST_Node.warn("Condition always true [{line},{col}]", self.condition.start); + compressor.warn("Condition always true [{line},{col}]", self.condition.start); if (compressor.option("dead_code")) { var a = []; if (self.alternative) { @@ -808,7 +867,7 @@ function Compressor(options, false_by_default) { return make_node(AST_BlockStatement, self, { body: a }); } } else { - AST_Node.warn("Condition always false [{line},{col}]", self.condition.start); + compressor.warn("Condition always false [{line},{col}]", self.condition.start); if (compressor.option("dead_code")) { var a = []; extract_declarations_from_unreachable_code(compressor, self.body, a); @@ -909,7 +968,17 @@ function Compressor(options, false_by_default) { self = self.clone(); self.expression = self.expression.squeeze(compressor); self.body = self.body.squeeze(compressor); - return self; + return self.optimize(compressor); + }); + + AST_Switch.DEFMETHOD("optimize", function(compressor){ + var last_branch = this.body.body[this.body.body.length - 1]; + if (last_branch) { + var stat = last_branch.body[last_branch.body.length - 1]; // last statement + if (stat instanceof AST_Break && !stat.label) + last_branch.body.pop(); + } + return this; }); SQUEEZE(AST_Case, function(self, compressor){ @@ -952,8 +1021,8 @@ function Compressor(options, false_by_default) { var first = list[0]; if (list.length == 1) return first; return make_node(AST_Seq, first, { - first: first, - second: seq(list.slice(1)) + car: first, + cdr: seq(list.slice(1)) }); })(assignments); }); @@ -1007,7 +1076,7 @@ function Compressor(options, false_by_default) { if (compressor.option("unused_func")) { if (this.name.unreferenced() && !(this.parent_scope instanceof AST_Toplevel)) { - AST_Node.warn("Dropping unused function {name} [{line},{col}]", { + compressor.warn("Dropping unused function {name} [{line},{col}]", { name: this.name.name, line: this.start.line, col: this.start.col @@ -1077,12 +1146,32 @@ function Compressor(options, false_by_default) { } } } + return this; }); SQUEEZE(AST_Seq, function(self, compressor){ self = self.clone(); - self.first = self.first.squeeze(compressor); - self.second = self.second.squeeze(compressor); + self.car = self.car.squeeze(compressor); + self.cdr = self.cdr.squeeze(compressor); + return self.optimize(compressor); + }); + + AST_Seq.DEFMETHOD("optimize", function(compressor){ + var self = this; + if (self.cdr instanceof AST_Seq) + self.cdr = self.cdr.optimize(compressor); + if (compressor.option("cascade")) { + if (self.car instanceof AST_Assign + && !self.car.left.has_side_effects() + && self.car.left.equivalent_to(self.cdr)) { + return self.car; + } + if (!self.car.has_side_effects() + && !self.cdr.has_side_effects() + && self.car.equivalent_to(self.cdr)) { + return self.car; + } + } return self; }); @@ -1131,7 +1220,7 @@ function Compressor(options, false_by_default) { case "typeof": // typeof always returns a non-empty string, thus it's // always true in booleans - AST_Node.warn("Boolean expression always true [{line},{col}]", this.start); + compressor.warn("Boolean expression always true [{line},{col}]", this.start); return make_node(AST_True, this).optimize(compressor); } } @@ -1160,7 +1249,7 @@ function Compressor(options, false_by_default) { var ll = this.left.evaluate(compressor), left = ll[0]; var rr = this.right.evaluate(compressor), right = rr[0]; if ((ll.length == 2 && !ll[1]) || (rr.length == 2 && !rr[1])) { - AST_Node.warn("Boolean && always false [{line},{col}]", this.start); + compressor.warn("Boolean && always false [{line},{col}]", this.start); return make_node(AST_False, this).optimize(compressor); } if (ll.length == 2 && ll[1]) { @@ -1174,7 +1263,7 @@ function Compressor(options, false_by_default) { var ll = this.left.evaluate(compressor), left = ll[0]; var rr = this.right.evaluate(compressor), right = rr[0]; if ((ll.length == 2 && ll[1]) || (rr.length == 2 && rr[1])) { - AST_Node.warn("Boolean || always true [{line},{col}]", this.start); + compressor.warn("Boolean || always true [{line},{col}]", this.start); return make_node(AST_True, this).optimize(compressor); } if (ll.length == 2 && !ll[1]) { @@ -1189,7 +1278,7 @@ function Compressor(options, false_by_default) { var rr = this.right.evaluate(compressor), right = rr[0]; if ((ll.length == 2 && ll[0] instanceof AST_String && ll[1]) || (rr.length == 2 && rr[0] instanceof AST_String && rr[1])) { - AST_Node.warn("+ in boolean context always true [{line},{col}]", this.start); + compressor.warn("+ in boolean context always true [{line},{col}]", this.start); return make_node(AST_True, this).optimize(compressor); } break; @@ -1207,7 +1296,34 @@ function Compressor(options, false_by_default) { self.left = self.right; self.right = tmp; }; - if (self instanceof AST_Binary) switch (self.operator) { + switch (self.operator) { + case "==": + case "!=": + var ll = self.left.evaluate(compressor); + var rr = self.right.evaluate(compressor); + if (ll.length == 2 && typeof ll[1] == "boolean") { + compressor.warn("Non-strict equality against boolean: {operator} {value} [{line},{col}]", { + operator : self.operator, + value : ll[1], + line : self.start.line, + col : self.start.col + }); + self.left = make_node(AST_Number, self.left, { + value: +ll[1] + }); + } + if (rr.length == 2 && typeof rr[1] == "boolean") { + compressor.warn("Non-strict equality against boolean {operator} {value} [{line},{col}]", { + operator : self.operator, + value : rr[1], + line : self.start.line, + col : self.start.col + }); + self.right = make_node(AST_Number, self.right, { + value: +rr[1] + }); + } + break; case "<": reverse(">"); break; case "<=": reverse(">="); break; } @@ -1219,7 +1335,21 @@ function Compressor(options, false_by_default) { self = self.clone(); self.left = self.left.squeeze(compressor); self.right = self.right.squeeze(compressor); - return self; + return self.optimize(compressor); + }); + + var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; + AST_Assign.DEFMETHOD("optimize", function(compressor){ + if (this.operator == "=" + && this.left instanceof AST_SymbolRef + && this.right instanceof AST_Binary + && this.right.left instanceof AST_SymbolRef + && this.right.left.name == this.left.name + && member(this.right.operator, ASSIGN_OPS)) { + this.operator = this.right.operator + "="; + this.right = this.right.right; + } + return this; }); SQUEEZE(AST_Conditional, function(self, compressor){ @@ -1236,10 +1366,10 @@ function Compressor(options, false_by_default) { var cond = self.condition.evaluate(compressor); if (cond.length == 2) { if (cond[1]) { - AST_Node.warn("Condition always true [{line},{col}]", self.start); + compressor.warn("Condition always true [{line},{col}]", self.start); return self.consequent; } else { - AST_Node.warn("Condition always false [{line},{col}]", self.start); + compressor.warn("Condition always false [{line},{col}]", self.start); return self.alternative; } } @@ -1256,8 +1386,7 @@ function Compressor(options, false_by_default) { if (consequent instanceof AST_Assign && alternative instanceof AST_Assign && consequent.operator == alternative.operator - // XXX: this is a rather expensive way to test two node's equivalence: - && consequent.left.print_to_string() == alternative.left.print_to_string() + && consequent.left.equivalent_to(alternative.left) ) { /* * Stuff like this: |