aboutsummaryrefslogtreecommitdiff
path: root/lib/compress.js
diff options
context:
space:
mode:
authorMihai Bazon <mihai@bazon.net>2012-09-16 15:46:20 +0300
committerMihai Bazon <mihai@bazon.net>2012-09-16 15:46:20 +0300
commit7b6a402916fe697049c38f6ee89c644fcf93c8d4 (patch)
tree2bf8a1ba4d2449ec26d688438e153b61f13f909a /lib/compress.js
parent397bf56d2597d4c2849e380e103b92b87303785f (diff)
downloadtracifyjs-7b6a402916fe697049c38f6ee89c644fcf93c8d4.tar.gz
tracifyjs-7b6a402916fe697049c38f6ee89c644fcf93c8d4.zip
rewrite handle_if_return
optimizations of if/return/continue seem to be even better now
Diffstat (limited to 'lib/compress.js')
-rw-r--r--lib/compress.js201
1 files changed, 178 insertions, 23 deletions
diff --git a/lib/compress.js b/lib/compress.js
index f8f4d17f..2f286545 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -156,7 +156,7 @@ function Compressor(options, false_by_default) {
function eliminate_spurious_blocks(statements) {
return statements.reduce(function(a, stat){
if (stat instanceof AST_BlockStatement) {
- a.push.apply(a, stat.body);
+ a.push.apply(a, eliminate_spurious_blocks(stat.body));
} else if (!(stat instanceof AST_EmptyStatement)) {
a.push(stat);
}
@@ -164,6 +164,21 @@ function Compressor(options, false_by_default) {
}, []);
};
+ function as_statement_array(thing) {
+ if (thing === null) return [];
+ if (thing instanceof AST_BlockStatement) return thing.body;
+ if (thing instanceof AST_EmptyStatement) return [];
+ if (thing instanceof AST_StatementBase) return [ thing ];
+ throw new Error("Can't convert thing to statement array");
+ };
+
+ function is_empty(thing) {
+ if (thing === null) return true;
+ if (thing instanceof AST_EmptyStatement) return true;
+ if (thing instanceof AST_BlockStatement) return thing.body.length == 0;
+ return false;
+ };
+
function tighten_body(statements, compressor) {
var CHANGED;
statements = do_list(statements, compressor, true);
@@ -181,12 +196,130 @@ function Compressor(options, false_by_default) {
if (compressor.option("join_vars")) {
statements = join_consecutive_vars(statements, compressor);
}
+ statements = eliminate_spurious_blocks(statements);
} while (CHANGED);
return statements;
+ function handle_if_return(statements, compressor) {
+ var self = compressor.self();
+ var in_lambda = self instanceof AST_Lambda;
+ var last = statements.length - 1;
+ var ret = [];
+ loop: for (var i = statements.length; --i >= 0;) {
+ var stat = statements[i];
+ switch (true) {
+ case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0):
+ CHANGED = true;
+ // note, ret.length is probably always zero
+ // because we drop unreachable code before this
+ // step. nevertheless, it's good to check.
+ continue loop;
+ case stat instanceof AST_If:
+ if (stat.body instanceof AST_Return) {
+ //---
+ // pretty silly case, but:
+ // if (foo()) return; return; ==> foo(); return;
+ if (((in_lambda && ret.length == 0)
+ || (ret[0] instanceof AST_Return && !ret[0].value))
+ && !stat.body.value && !stat.alternative) {
+ CHANGED = true;
+ var cond = make_node(AST_SimpleStatement, stat.condition, {
+ body: stat.condition
+ }).optimize(compressor);
+ ret.unshift(cond);
+ continue loop;
+ }
+ //---
+ // if (foo()) return x; return y; ==> return foo() ? x : y;
+ if (ret[0] instanceof AST_Return && stat.body.value && ret[0].value && !stat.alternative) {
+ CHANGED = true;
+ stat = stat.clone();
+ stat.alternative = ret[0];
+ ret[0] = stat.squeeze(compressor);
+ continue loop;
+ }
+ //---
+ // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined;
+ if ((ret.length == 0 || ret[0] instanceof AST_Return) && stat.body.value && !stat.alternative && in_lambda) {
+ CHANGED = true;
+ stat = stat.clone();
+ stat.alternative = ret[0] || make_node(AST_Return, stat, {
+ value: make_node(AST_Undefined, stat)
+ });
+ ret[0] = stat.squeeze(compressor);
+ continue loop;
+ }
+ //---
+ // if (foo()) return; [ else x... ]; y... ==> if (!foo()) { x...; y... }
+ if (!stat.body.value && in_lambda) {
+ CHANGED = true;
+ stat = stat.clone();
+ stat.condition = stat.condition.negate(compressor);
+ stat.body = make_node(AST_BlockStatement, stat, {
+ body: as_statement_array(stat.alternative).concat(ret)
+ });
+ stat.alternative = null;
+ ret = [ stat.squeeze(compressor) ];
+ continue loop;
+ }
+ //---
+ if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement
+ && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) {
+ CHANGED = true;
+ ret.push(make_node(AST_Return, ret[0], {
+ value: make_node(AST_Undefined, ret[0])
+ }).squeeze(compressor));
+ ret = as_statement_array(stat.alternative).concat(ret);
+ ret.unshift(stat);
+ continue loop;
+ }
+ }
+
+ var ab = aborts(stat.body);
+ if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
+ || (ab instanceof AST_Continue && self === ab.target()))) {
+ CHANGED = true;
+ var body = tighten_body(as_statement_array(stat.body).slice(0, -1), compressor);
+ stat = stat.clone();
+ stat.condition = stat.condition.negate(compressor);
+ stat.body = make_node(AST_BlockStatement, stat, {
+ body: ret
+ });
+ stat.alternative = make_node(AST_BlockStatement, stat, {
+ body: body
+ });
+ ret = [ stat.squeeze(compressor) ];
+ continue loop;
+ }
+
+ var ab = aborts(stat.alternative);
+ if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
+ || (ab instanceof AST_Continue && self === ab.target()))) {
+ CHANGED = true;
+ stat = stat.clone();
+ stat.body = make_node(AST_BlockStatement, stat.body, {
+ body: tighten_body(as_statement_array(stat.body).concat(ret), compressor)
+ });
+ stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
+ body: tighten_body(as_statement_array(stat.alternative).slice(0, -1), compressor)
+ });
+ ret = [ stat.squeeze(compressor) ];
+ continue loop;
+ }
+
+ ret.unshift(stat);
+ break;
+ default:
+ ret.unshift(stat);
+ break;
+ }
+ }
+ return ret;
+ };
+
/// XXX: this function is UGLY and kinda wrong.
/// I think it would be cleaner if it operates backwards.
- function handle_if_return(statements, compressor) {
+ function handle_if_return_2(statements, compressor) {
var self = compressor.self();
var in_lambda = self instanceof AST_Lambda;
var last = statements.length - 1;
@@ -677,12 +810,15 @@ function Compressor(options, false_by_default) {
});
// tell me if a statement aborts
+ function aborts(thing) {
+ return thing && thing.aborts();
+ };
(function(def){
def(AST_StatementBase, function(){ return null });
def(AST_Jump, function(){ return this });
def(AST_BlockStatement, function(){
var n = this.body.length;
- return n > 0 && this.body[n - 1].aborts();
+ return n > 0 && aborts(this.body[n - 1]);
});
})(function(node, func){
node.DEFMETHOD("aborts", func);
@@ -938,6 +1074,7 @@ function Compressor(options, false_by_default) {
}
}
}
+ if (is_empty(self.alternative)) self.alternative = null;
var negated = self.condition.negate(compressor);
var negated_is_best = best_of(self.condition, negated) === negated;
if (self.alternative && negated_is_best) {
@@ -947,8 +1084,7 @@ function Compressor(options, false_by_default) {
self.body = self.alternative || new AST_EmptyStatement();
self.alternative = tmp;
}
- if (self.body instanceof AST_EmptyStatement
- && self.alternative instanceof AST_EmptyStatement) {
+ if (is_empty(self.body) && is_empty(self.alternative)) {
return make_node(AST_SimpleStatement, self.condition, {
body: self.condition
});
@@ -963,9 +1099,7 @@ function Compressor(options, false_by_default) {
}).optimize(compressor)
});
}
- if ((!self.alternative
- || self.alternative instanceof AST_EmptyStatement)
- && self.body instanceof AST_SimpleStatement) {
+ if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
if (negated_is_best) return make_node(AST_SimpleStatement, self, {
body: make_node(AST_Binary, self, {
operator : "||",
@@ -999,7 +1133,7 @@ function Compressor(options, false_by_default) {
value: make_node(AST_Conditional, self, {
condition : self.condition,
consequent : self.body.value,
- alternative : self.alternative.value
+ alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor)
}).optimize(compressor)
});
}
@@ -1010,11 +1144,10 @@ function Compressor(options, false_by_default) {
operator: "&&",
left: self.condition,
right: self.body.condition
- });
+ }).optimize(compressor);
self.body = self.body.body;
}
- var abort = self.body.aborts();
- if (abort) {
+ if (aborts(self.body)) {
if (self.alternative) {
var alt = self.alternative;
self.alternative = null;
@@ -1023,6 +1156,15 @@ function Compressor(options, false_by_default) {
}).optimize(compressor);
}
}
+ if (aborts(self.alternative)) {
+ var body = self.body;
+ self.body = self.alternative;
+ self.condition = negated_is_best ? negated : self.condition.negate(compressor);
+ self.alternative = null;
+ return make_node(AST_BlockStatement, self, {
+ body: [ self, body ]
+ }).optimize(compressor);
+ }
return self;
});
@@ -1125,7 +1267,7 @@ function Compressor(options, false_by_default) {
return self.optimize(compressor);
});
- AST_Lambda.DEFMETHOD("optimize", function(compressor){
+ AST_Function.DEFMETHOD("optimize", function(compressor){
if (compressor.option("unused_func")) {
if (this.name && this.name.unreferenced()) {
this.name = null;
@@ -1324,6 +1466,19 @@ function Compressor(options, false_by_default) {
if (this.operator.length == 2) this.operator += "=";
}
break;
+ case "&&":
+ case "||":
+ if (this.left instanceof AST_UnaryPrefix && this.left.operator == "!"
+ && this.right instanceof AST_UnaryPrefix && this.right.operator == "!") {
+ this.left = this.left.expression;
+ this.right = this.right.expression;
+ this.operator = this.operator == "&&" ? "||" : "&&";
+ return make_node(AST_UnaryPrefix, this, {
+ operator: "!",
+ expression: this
+ }).optimize(compressor);
+ }
+ break;
}
if (compressor.option("booleans") && compressor.in_boolean_context()) switch (this.operator) {
case "&&":
@@ -1435,16 +1590,16 @@ function Compressor(options, false_by_default) {
});
AST_Undefined.DEFMETHOD("optimize", function(compressor){
- if (compressor.option("unsafe") && !(compressor.parent() instanceof AST_Array)) {
- return make_node(AST_Sub, this, {
- expression: make_node(AST_Array, this, {
- elements: []
- }),
- property: make_node(AST_Number, this, {
- value: 0
- })
- });
- }
+ // if (compressor.option("unsafe") && !(compressor.parent() instanceof AST_Array)) {
+ // return make_node(AST_Sub, this, {
+ // expression: make_node(AST_Array, this, {
+ // elements: []
+ // }),
+ // property: make_node(AST_Number, this, {
+ // value: 0
+ // })
+ // });
+ // }
return this;
});