aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMihai Bazon <mihai@bazon.net>2012-09-03 19:38:45 +0300
committerMihai Bazon <mihai@bazon.net>2012-09-03 19:38:45 +0300
commit37eecc16a419361672c0c055e0246ee7deb439b7 (patch)
treea8c55550206b25f5f55a9ed87508195430688690 /lib
parentf03138daa805c01293a2b60f96231989906aca59 (diff)
downloadtracifyjs-37eecc16a419361672c0c055e0246ee7deb439b7.tar.gz
tracifyjs-37eecc16a419361672c0c055e0246ee7deb439b7.zip
more optimizations for ifs/conditionals
(XXX: should add tests before anything else)
Diffstat (limited to 'lib')
-rw-r--r--lib/compress.js169
1 files changed, 117 insertions, 52 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 139d54a8..91a6c590 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -60,7 +60,7 @@ function Compressor(options, false_by_default) {
keep_comps : !false_by_default,
drop_debugger : !false_by_default,
unsafe : !false_by_default,
- ifs : !false_by_default,
+ conditionals : !false_by_default,
comparations : !false_by_default,
evaluate : !false_by_default,
@@ -88,6 +88,10 @@ function Compressor(options, false_by_default) {
return this;
});
+ AST_Node.DEFMETHOD("optimize", function(){
+ return this;
+ });
+
function make_node(ctor, orig, props) {
if (!props) props = {};
if (!props.start) props.start = orig.start;
@@ -143,7 +147,7 @@ function Compressor(options, false_by_default) {
if (stat instanceof AST_Defun) {
a.push(stat);
}
- else if (compressor.option("warnings")) {
+ else {
stat.walk(new TreeWalker(function(node){
if (node instanceof AST_Definitions || node instanceof AST_Defun) {
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
@@ -254,13 +258,22 @@ function Compressor(options, false_by_default) {
node.DEFMETHOD("is_string", func);
});
- // function best_of(ast1, ast2) {
- // return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1;
- // };
+ function best_of(ast1, ast2) {
+ return ast1.print_to_string({ beautify: false }).length >
+ ast2.print_to_string({ beautify: false }).length
+ ? ast2 : ast1;
+ };
// methods to evaluate a constant expression
(function (def){
- AST_Node.DEFMETHOD("evaluate", function(compressor, constant, not_constant){
+ // The evaluate method returns an array with one or two
+ // elements. If the node has been successfully reduced to a
+ // constant, then the second element tells us the value;
+ // otherwise the second element is missing. The first element
+ // of the array is always an AST_Node descendant; when
+ // evaluation was successful it's a node that represents the
+ // constant; otherwise it's the original node.
+ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this;
try {
var val = this._eval(), ast;
@@ -290,15 +303,13 @@ function Compressor(options, false_by_default) {
type: typeof val
}));
}
- if (constant) return constant(ast, val);
- return ast;
+ return [ ast, val ];
} catch(ex) {
if (ex !== def) throw ex;
- if (not_constant) return not_constant(this);
- return this;
+ return [ this ];
}
});
- function evaluate(node) {
+ function ev(node) {
return node._eval();
};
def(AST_Node, function(){
@@ -310,47 +321,47 @@ function Compressor(options, false_by_default) {
def(AST_UnaryPrefix, function(){
var e = this.expression;
switch (this.operator) {
- case "!": return !evaluate(e);
- case "typeof": return typeof evaluate(e);
- case "~": return ~evaluate(e);
- case "-": return -evaluate(e);
- case "+": return +evaluate(e);
+ case "!": return !ev(e);
+ case "typeof": return typeof ev(e);
+ case "~": return ~ev(e);
+ case "-": return -ev(e);
+ case "+": return +ev(e);
}
throw def;
});
def(AST_Binary, function(){
var left = this.left, right = this.right;
switch (this.operator) {
- case "&&" : return evaluate(left) && evaluate(right);
- case "||" : return evaluate(left) || evaluate(right);
- case "|" : return evaluate(left) | evaluate(right);
- case "&" : return evaluate(left) & evaluate(right);
- case "^" : return evaluate(left) ^ evaluate(right);
- case "+" : return evaluate(left) + evaluate(right);
- case "*" : return evaluate(left) * evaluate(right);
- case "/" : return evaluate(left) / evaluate(right);
- case "%" : return evaluate(left) % evaluate(right);
- case "-" : return evaluate(left) - evaluate(right);
- case "<<" : return evaluate(left) << evaluate(right);
- case ">>" : return evaluate(left) >> evaluate(right);
- case ">>>" : return evaluate(left) >>> evaluate(right);
- case "==" : return evaluate(left) == evaluate(right);
- case "===" : return evaluate(left) === evaluate(right);
- case "!=" : return evaluate(left) != evaluate(right);
- case "!==" : return evaluate(left) !== evaluate(right);
- case "<" : return evaluate(left) < evaluate(right);
- case "<=" : return evaluate(left) <= evaluate(right);
- case ">" : return evaluate(left) > evaluate(right);
- case ">=" : return evaluate(left) >= evaluate(right);
- case "in" : return evaluate(left) in evaluate(right);
- case "instanceof" : return evaluate(left) instanceof evaluate(right);
+ case "&&" : return ev(left) && ev(right);
+ case "||" : return ev(left) || ev(right);
+ case "|" : return ev(left) | ev(right);
+ case "&" : return ev(left) & ev(right);
+ case "^" : return ev(left) ^ ev(right);
+ case "+" : return ev(left) + ev(right);
+ case "*" : return ev(left) * ev(right);
+ case "/" : return ev(left) / ev(right);
+ case "%" : return ev(left) % ev(right);
+ case "-" : return ev(left) - ev(right);
+ case "<<" : return ev(left) << ev(right);
+ case ">>" : return ev(left) >> ev(right);
+ case ">>>" : return ev(left) >>> ev(right);
+ case "==" : return ev(left) == ev(right);
+ case "===" : return ev(left) === ev(right);
+ case "!=" : return ev(left) != ev(right);
+ case "!==" : return ev(left) !== ev(right);
+ case "<" : return ev(left) < ev(right);
+ case "<=" : return ev(left) <= ev(right);
+ case ">" : return ev(left) > ev(right);
+ case ">=" : return ev(left) >= ev(right);
+ case "in" : return ev(left) in ev(right);
+ case "instanceof" : return ev(left) instanceof ev(right);
}
throw def;
});
def(AST_Conditional, function(){
- return evaluate(this.condition)
- ? evaluate(this.consequent)
- : evaluate(this.alternative);
+ return ev(this.condition)
+ ? ev(this.consequent)
+ : ev(this.alternative);
});
})(function(node, func){
node.DEFMETHOD("_eval", func);
@@ -371,8 +382,8 @@ function Compressor(options, false_by_default) {
throw new Error("Cannot evaluate a statement");
});
def(AST_UnaryPrefix, function(){
- if (this.operator == "!" && this.expression.is_boolean())
- return this.expression();
+ if (this.operator == "!")
+ return this.expression;
return basic_negation(this);
});
def(AST_Seq, function(compressor){
@@ -498,14 +509,58 @@ function Compressor(options, false_by_default) {
self.body = self.body.squeeze(compressor);
if (self.alternative)
self.alternative = self.alternative.squeeze(compressor);
- if (!compressor.option("ifs")) return self;
+ return compressor.option("conditionals") ? self.optimize(compressor) : self;
+ });
+
+ AST_If.DEFMETHOD("optimize", function(compressor){
+ // if condition can be statically determined, warn and drop
+ // one of the blocks. note, statically determined implies
+ // “has no side effects”; also it doesn't work for cases like
+ // `x && true`, though it probably should.
+ var self = this;
+ var cond = self.condition.evaluate(compressor);
+ if (cond.length == 2) {
+ if (cond[1]) {
+ AST_Node.warn("Condition always true [{line},{col}]", self.condition.start);
+ return self.body;
+ } else {
+ AST_Node.warn("Condition always false [{line},{col}]", self.condition.start);
+ return self.alternative || new AST_EmptyStatement(self);
+ }
+ }
+ if (self.body instanceof AST_SimpleStatement
+ && self.alternative instanceof AST_SimpleStatement) {
+ return make_node(AST_SimpleStatement, self, {
+ body: make_node(AST_Conditional, self, {
+ condition : self.condition,
+ consequent : self.body.body,
+ alternative : self.alternative.body
+ }).optimize(compressor)
+ });
+ }
+ if (!self.alternative && self.body instanceof AST_SimpleStatement) {
+ return make_node(AST_SimpleStatement, self, {
+ body: make_node(AST_Binary, self, {
+ operator: "&&",
+ left: self.condition,
+ right: self.body.body
+ }).optimize(compressor)
+ });
+ }
+ if (self.body instanceof AST_EmptyStatement
+ && self.alternative
+ && !(self.alternative instanceof AST_EmptyStatement)) {
+ return make_node(AST_SimpleStatement, self, {
+ body: make_node(AST_Binary, self, {
+ operator: "||",
+ left: self.condition,
+ right: self.alternative.body
+ }).optimize(compressor)
+ });
+ }
return self;
});
- AST_If.DEFMETHOD("switch_branches", function(){
-
- });
-
SQUEEZE(AST_Switch, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
@@ -599,14 +654,14 @@ function Compressor(options, false_by_default) {
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
self = self.clone();
self.expression = self.expression.squeeze(compressor);
- return self.evaluate(compressor);
+ return self.evaluate(compressor)[0];
});
SQUEEZE(AST_Binary, function(self, compressor){
self = self.clone();
self.left = self.left.squeeze(compressor);
self.right = self.right.squeeze(compressor);
- return self.evaluate(compressor);
+ return self.evaluate(compressor)[0];
});
SQUEEZE(AST_Assign, function(self, compressor){
@@ -621,7 +676,17 @@ function Compressor(options, false_by_default) {
self.condition = self.condition.squeeze(compressor);
self.consequent = self.consequent.squeeze(compressor);
self.alternative = self.alternative.squeeze(compressor);
- return self;
+ return compressor.option("conditionals") ? self.optimize(compressor) : self;
+ });
+
+ AST_Conditional.DEFMETHOD("optimize", function(compressor){
+ var self = this;
+ var rev = self.clone();
+ rev.condition = rev.condition.negate(compressor);
+ var tmp = rev.consequent;
+ rev.consequent = rev.alternative;
+ rev.alternative = tmp;
+ return best_of(self, rev);
});
SQUEEZE(AST_Array, function(self, compressor){