aboutsummaryrefslogtreecommitdiff
path: root/lib/compress.js
diff options
context:
space:
mode:
authorMihai Bazon <mihai@bazon.net>2012-09-14 15:36:38 +0300
committerMihai Bazon <mihai@bazon.net>2012-09-14 15:36:38 +0300
commit924aa580602a4ad94f1079dcd157286314066553 (patch)
treeacd65dd576ded856e5e7cc3b15ee473539b645ac /lib/compress.js
parent93b973c99dd25aeff66ff31f2881e9ce252eaac6 (diff)
downloadtracifyjs-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.js245
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: