few more optimizations:

- do multiple passes in tighten_body if it was changed
- transform if (foo) return x; return y; ==> return foo?x:y
- don't optimize !0 as true (use best_of after evaluation of constant expr)

With hoist_vars off we now beat UglifyJS v1 on jQuery-1.8.1
This commit is contained in:
Mihai Bazon
2012-09-13 15:20:57 +03:00
parent f5027ec1fc
commit d72c1d1293
2 changed files with 137 additions and 106 deletions

View File

@@ -67,6 +67,7 @@ function Compressor(options, false_by_default) {
unused_func : !false_by_default, unused_func : !false_by_default,
hoist_funs : !false_by_default, hoist_funs : !false_by_default,
hoist_vars : !false_by_default, hoist_vars : !false_by_default,
if_return : !false_by_default,
warnings : true warnings : true
}); });
@@ -93,6 +94,7 @@ function Compressor(options, false_by_default) {
push_node : function(node) { stack.push(node) }, push_node : function(node) { stack.push(node) },
pop_node : function() { return stack.pop() }, pop_node : function() { return stack.pop() },
stack : function() { return stack }, stack : function() { return stack },
self : function() { return stack[stack.length - 1] },
parent : function(n) { parent : function(n) {
return stack[stack.length - 2 - (n || 0)]; return stack[stack.length - 2 - (n || 0)];
}, },
@@ -122,9 +124,9 @@ function Compressor(options, false_by_default) {
}; };
function SQUEEZE(nodetype, squeeze) { function SQUEEZE(nodetype, squeeze) {
nodetype.DEFMETHOD("squeeze", function(compressor, block, index){ nodetype.DEFMETHOD("squeeze", function(compressor){
compressor.push_node(this); compressor.push_node(this);
var new_node = squeeze(this, compressor, block, index); var new_node = squeeze(this, compressor);
compressor.pop_node(); compressor.pop_node();
return new_node !== undefined ? new_node : this; return new_node !== undefined ? new_node : this;
}); });
@@ -156,94 +158,148 @@ function Compressor(options, false_by_default) {
}; };
function tighten_body(statements, compressor) { function tighten_body(statements, compressor) {
var CHANGED;
statements = do_list(statements, compressor, true); statements = do_list(statements, compressor, true);
if (compressor.option("dead_code")) { do {
statements = eliminate_dead_code(statements, compressor); CHANGED = false;
} if (compressor.option("dead_code")) {
if (compressor.option("sequences")) { statements = eliminate_dead_code(statements, compressor);
statements = sequencesize(statements, compressor); }
} if (compressor.option("sequences")) {
statements = sequencesize(statements, compressor);
}
if (compressor.option("if_return")) {
statements = handle_if_return(statements, compressor);
}
} while (CHANGED);
return statements; return statements;
function handle_if_return(statements, compressor) {
var in_lambda = compressor.self() instanceof AST_Lambda;
var last = statements.length - 1;
return MAP(statements, function(stat, i){
if (stat instanceof AST_If
&& stat.body instanceof AST_Return
&& !stat.body.value
&& !stat.alternative
&& in_lambda) {
CHANGED = true;
if (i < last) {
var rest = statements.slice(i + 1);
var cond = stat.condition;
while (rest[0] instanceof AST_If
&& rest[0].body instanceof AST_Return
&& !rest[0].alternative) {
cond = make_node(AST_Binary, rest[0], {
operator: "||",
left: cond,
right: rest[0].condition
});
rest.shift();
}
return MAP.last(make_node(AST_If, stat, {
condition: cond.negate(compressor),
body: make_node(AST_BlockStatement, stat, {
body: rest
}).optimize(compressor)
}).optimize(compressor));
} else {
return make_node(AST_SimpleStatement, stat, {
body: stat.condition
}).optimize(compressor);
}
}
if (stat instanceof AST_If
&& stat.body instanceof AST_Return
&& stat.body.value
&& !stat.alternative
&& i < last
&& statements[i + 1] instanceof AST_Return
&& statements[i + 1].value) {
CHANGED = true;
return MAP.last(make_node(AST_If, stat, {
condition: stat.condition,
body: stat.body,
alternative: statements[i + 1]
}).optimize(compressor));
}
return stat;
});
};
function eliminate_dead_code(statements, compressor) {
var has_quit = false;
return statements.reduce(function(a, stat){
if (has_quit) {
extract_declarations_from_unreachable_code(compressor, stat, a);
} else {
a.push(stat);
if (stat instanceof AST_Jump) {
has_quit = true;
}
}
return a;
}, []);
};
// 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;
}
else {
a.push(cur);
prev = cur;
}
return a;
}, []);
return statements;
};
}; };
function extract_declarations_from_unreachable_code(compressor, stat, target) { function extract_declarations_from_unreachable_code(compressor, stat, target) {
warn_dead_code(stat); warn_dead_code(stat);
stat.walk(new TreeWalker(function(node){ stat.walk(new TreeWalker(function(node){
if (node instanceof AST_Definitions || node instanceof AST_Defun) { if (node instanceof AST_Definitions) {
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start); compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
if (node instanceof AST_Definitions) { node = node.clone();
node = node.clone(); node.remove_initializers();
node.remove_initializers(); target.push(node);
target.push(node); return true;
} }
else if (node instanceof AST_Defun) { if (node instanceof AST_Defun) {
target.push(node); target.push(node);
} return true;
return true; // no point to descend
} }
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
// also don't descend any other nested scopes
return true; return true;
} }
})); }));
}; };
function eliminate_dead_code(statements, compressor) {
var has_quit = false;
return statements.reduce(function(a, stat){
if (has_quit) {
if (stat instanceof AST_Defun) {
a.push(stat);
}
else {
extract_declarations_from_unreachable_code(compressor, stat, a);
};
} else {
a.push(stat);
if (stat instanceof AST_Jump) {
has_quit = true;
}
}
return a;
}, []);
};
// 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) {
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) {
// it only makes sense to do this transformation
// if the AST gets to a single statement.
var seq = make_node(AST_Seq, prev, {
first: prev.body,
second: cur.value
});
cur.value = seq;
a.pop();
a.push(cur);
return a;
}
else {
a.push(cur);
prev = cur;
}
return a;
}, []);
return statements;
};
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type // methods to determine whether an expression has a boolean result type
@@ -337,7 +393,7 @@ function Compressor(options, false_by_default) {
type: typeof val type: typeof val
})); }));
} }
return [ ast, val ]; return [ best_of(ast, this), val ];
} catch(ex) { } catch(ex) {
if (ex !== def) throw ex; if (ex !== def) throw ex;
return [ this ]; return [ this ];
@@ -432,8 +488,7 @@ function Compressor(options, false_by_default) {
var self = this.clone(); var self = this.clone();
self.consequent = self.consequent.negate(compressor); self.consequent = self.consequent.negate(compressor);
self.alternative = self.alternative.negate(compressor); self.alternative = self.alternative.negate(compressor);
//return best_of(basic_negation(this), self); return best_of(basic_negation(this), self);
return self;
}); });
def(AST_Binary, function(compressor){ def(AST_Binary, function(compressor){
var self = this.clone(), op = this.operator; var self = this.clone(), op = this.operator;
@@ -723,16 +778,16 @@ function Compressor(options, false_by_default) {
return self; return self;
}); });
SQUEEZE(AST_If, function(self, compressor, block, index){ SQUEEZE(AST_If, function(self, compressor){
self = self.clone(); self = self.clone();
self.condition = self.condition.squeeze(compressor); self.condition = self.condition.squeeze(compressor);
self.body = self.body.squeeze(compressor); self.body = self.body.squeeze(compressor);
if (self.alternative) if (self.alternative)
self.alternative = self.alternative.squeeze(compressor); self.alternative = self.alternative.squeeze(compressor);
return self.optimize(compressor, block, index); return self.optimize(compressor);
}); });
AST_If.DEFMETHOD("optimize", function(compressor, block, index){ AST_If.DEFMETHOD("optimize", function(compressor){
var self = this; var self = this;
if (!compressor.option("conditionals")) return self; if (!compressor.option("conditionals")) return self;
// if condition can be statically determined, warn and drop // if condition can be statically determined, warn and drop
@@ -827,29 +882,6 @@ function Compressor(options, false_by_default) {
}).optimize(compressor) }).optimize(compressor)
}); });
} }
if (self.body instanceof AST_Return
&& !self.body.value
&& !self.alternative
&& index < block.length - 1) {
if (compressor.parent() instanceof AST_Lambda) {
var rest = tighten_body(block.slice(index + 1), compressor);
var cond = self.condition;
while (rest[0] instanceof AST_If && rest[0].body instanceof AST_Return && !rest[0].alternative) {
cond = make_node(AST_Binary, rest[0], {
operator: "||",
left: cond,
right: rest[0].condition
});
rest.shift();
}
return MAP.last(make_node(AST_If, self, {
condition: cond.negate(compressor),
body: make_node(AST_BlockStatement, block[index + 1], {
body: rest
}).optimize(compressor)
}).optimize(compressor));
}
}
if (self.body instanceof AST_If if (self.body instanceof AST_If
&& !self.body.alternative && !self.body.alternative
&& !self.alternative) { && !self.alternative) {
@@ -1081,7 +1113,6 @@ function Compressor(options, false_by_default) {
}); });
SQUEEZE(AST_UnaryPrefix, function(self, compressor){ SQUEEZE(AST_UnaryPrefix, function(self, compressor){
// need to determine the context before cloning the node
self = self.clone(); self = self.clone();
self.expression = self.expression.squeeze(compressor); self.expression = self.expression.squeeze(compressor);
return self.optimize(compressor); return self.optimize(compressor);

View File

@@ -90,7 +90,7 @@ ifs_4: {
ifs_5: { ifs_5: {
options = { options = {
conditionals: true if_return: true
}; };
input: { input: {
function f() { function f() {