some speedup and more savings from unused vars that have side effects in initialization
This commit is contained in:
132
lib/compress.js
132
lib/compress.js
@@ -75,12 +75,26 @@ merge(Compressor.prototype, {
|
||||
AST_Node.warn.apply(AST_Node, arguments);
|
||||
},
|
||||
before: function(node, descend, in_list) {
|
||||
if (node._squeezed) return node;
|
||||
node = node.clone();
|
||||
if (node instanceof AST_Scope) {
|
||||
node.drop_unused(this);
|
||||
node = node.hoist_declarations(this);
|
||||
}
|
||||
descend(node, this);
|
||||
node = node.optimize(this);
|
||||
if (node instanceof AST_Scope) {
|
||||
// dead code removal might leave further unused declarations.
|
||||
// this'll usually save very few bytes, but the performance
|
||||
// hit seems negligible so I'll just drop it here.
|
||||
|
||||
// no point to repeat warnings.
|
||||
var save_warnings = this.options.warnings;
|
||||
this.options.warnings = false;
|
||||
node.drop_unused(this);
|
||||
this.options.warnings = save_warnings;
|
||||
}
|
||||
node._squeezed = true;
|
||||
return node;
|
||||
}
|
||||
});
|
||||
@@ -89,7 +103,12 @@ merge(Compressor.prototype, {
|
||||
|
||||
function OPT(node, optimizer) {
|
||||
node.DEFMETHOD("optimize", function(compressor){
|
||||
return optimizer(this, compressor);
|
||||
var self = this;
|
||||
if (self._optimized) return self;
|
||||
var opt = optimizer(self, compressor);
|
||||
opt._optimized = true;
|
||||
if (opt === self) return opt;
|
||||
return opt.transform(compressor);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -104,8 +123,10 @@ merge(Compressor.prototype, {
|
||||
|
||||
function make_node(ctor, orig, props) {
|
||||
if (!props) props = {};
|
||||
if (!props.start) props.start = orig.start;
|
||||
if (!props.end) props.end = orig.end;
|
||||
if (orig) {
|
||||
if (!props.start) props.start = orig.start;
|
||||
if (!props.end) props.end = orig.end;
|
||||
}
|
||||
return new ctor(props);
|
||||
};
|
||||
|
||||
@@ -173,9 +194,6 @@ merge(Compressor.prototype, {
|
||||
// step. nevertheless, it's good to check.
|
||||
continue loop;
|
||||
case stat instanceof AST_If:
|
||||
// compressor.warn("Current if: {code}", {
|
||||
// code: stat.condition.print_to_string()
|
||||
// });
|
||||
if (stat.body instanceof AST_Return) {
|
||||
//---
|
||||
// pretty silly case, but:
|
||||
@@ -186,7 +204,7 @@ merge(Compressor.prototype, {
|
||||
CHANGED = true;
|
||||
var cond = make_node(AST_SimpleStatement, stat.condition, {
|
||||
body: stat.condition
|
||||
}).optimize(compressor);
|
||||
});
|
||||
ret.unshift(cond);
|
||||
continue loop;
|
||||
}
|
||||
@@ -323,7 +341,7 @@ merge(Compressor.prototype, {
|
||||
} else {
|
||||
left = AST_Seq.cons(left, right);
|
||||
}
|
||||
return left.optimize(compressor);
|
||||
return left.transform(compressor);
|
||||
};
|
||||
var ret = [], prev = null;
|
||||
statements.forEach(function(stat){
|
||||
@@ -478,7 +496,7 @@ merge(Compressor.prototype, {
|
||||
case "string":
|
||||
ast = make_node(AST_String, this, {
|
||||
value: val
|
||||
});
|
||||
}).optimize(compressor);
|
||||
break;
|
||||
case "number":
|
||||
ast = make_node(isNaN(val) ? AST_NaN : AST_Number, this, {
|
||||
@@ -493,7 +511,7 @@ merge(Compressor.prototype, {
|
||||
break;
|
||||
default:
|
||||
if (val === null) {
|
||||
ast = make_node(AST_Null, this);
|
||||
ast = make_node(AST_Null, this).optimize(compressor);
|
||||
break;
|
||||
}
|
||||
throw new Error(string_template("Can't handle constant of type: {type}", {
|
||||
@@ -838,19 +856,43 @@ merge(Compressor.prototype, {
|
||||
col : def.name.start.col
|
||||
};
|
||||
if (def.value && def.value.has_side_effects()) {
|
||||
def._unused_side_effects = true;
|
||||
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", w);
|
||||
return true;
|
||||
}
|
||||
compressor.warn("Dropping unused variable {name} [{file}:{line},{col}]", w);
|
||||
return false;
|
||||
});
|
||||
if (def.length == 0) {
|
||||
var side_effects = [];
|
||||
def = mergeSort(def, function(a, b){
|
||||
if (!a.value && b.value) return -1;
|
||||
if (!b.value && a.value) return 1;
|
||||
return 0;
|
||||
});
|
||||
while (def.length > 0 && def[def.length - 1]._unused_side_effects) {
|
||||
side_effects.unshift(def.pop().value);
|
||||
}
|
||||
if (side_effects.length > 0) {
|
||||
side_effects = make_node(AST_BlockStatement, node, {
|
||||
body: side_effects.map(function(ss){
|
||||
return make_node(AST_SimpleStatement, ss, { body: ss });
|
||||
})
|
||||
});
|
||||
} else {
|
||||
side_effects = null;
|
||||
}
|
||||
if (def.length == 0 && !side_effects) {
|
||||
return make_node(AST_EmptyStatement, node);
|
||||
}
|
||||
if (def.length != node.definitions.length) {
|
||||
node.definitions = def;
|
||||
return node;
|
||||
if (def.length == 0) {
|
||||
return side_effects;
|
||||
}
|
||||
node.definitions = def;
|
||||
if (side_effects) {
|
||||
side_effects.body.unshift(node);
|
||||
node = side_effects;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
if (node instanceof AST_Scope && node !== self)
|
||||
return node;
|
||||
@@ -864,7 +906,6 @@ merge(Compressor.prototype, {
|
||||
var hoist_funs = compressor.option("hoist_funs");
|
||||
var hoist_vars = compressor.option("hoist_vars");
|
||||
var self = this;
|
||||
self.drop_unused(compressor);
|
||||
if (hoist_funs || hoist_vars) {
|
||||
var dirs = [];
|
||||
var hoisted = [];
|
||||
@@ -949,7 +990,7 @@ merge(Compressor.prototype, {
|
||||
if (compressor.option("dead_code")) {
|
||||
var a = [];
|
||||
extract_declarations_from_unreachable_code(compressor, self.body, a);
|
||||
return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
|
||||
return make_node(AST_BlockStatement, self, { body: a });
|
||||
}
|
||||
} else {
|
||||
return self.body;
|
||||
@@ -958,21 +999,6 @@ merge(Compressor.prototype, {
|
||||
return self;
|
||||
});
|
||||
|
||||
// while(cond){ ... } ==> for(;cond;){ ... }
|
||||
//
|
||||
// not helpful, it seems (output is a bit bigger after gzip)
|
||||
//
|
||||
// OPT(AST_While, function(self, compressor){
|
||||
// var self = AST_DWLoop.prototype.optimize.call(self, compressor);
|
||||
// if (self instanceof AST_While) {
|
||||
// self = make_node(AST_For, self, {
|
||||
// condition: self.condition,
|
||||
// body: self.body
|
||||
// }).optimize(compressor);
|
||||
// }
|
||||
// return self;
|
||||
// });
|
||||
|
||||
OPT(AST_For, function(self, compressor){
|
||||
var cond = self.condition;
|
||||
if (cond) {
|
||||
@@ -1030,13 +1056,13 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
if (is_empty(self.alternative)) self.alternative = null;
|
||||
var negated = self.condition.negate(compressor).optimize(compressor);
|
||||
var negated = self.condition.negate(compressor);
|
||||
var negated_is_best = best_of(self.condition, negated) === negated;
|
||||
if (self.alternative && negated_is_best) {
|
||||
negated_is_best = false; // because we already do the switch here.
|
||||
self.condition = negated;
|
||||
var tmp = self.body;
|
||||
self.body = self.alternative || new AST_EmptyStatement();
|
||||
self.body = self.alternative || make_node(AST_EmptyStatement);
|
||||
self.alternative = tmp;
|
||||
}
|
||||
if (is_empty(self.body) && is_empty(self.alternative)) {
|
||||
@@ -1051,7 +1077,7 @@ merge(Compressor.prototype, {
|
||||
condition : self.condition,
|
||||
consequent : self.body.body,
|
||||
alternative : self.alternative.body
|
||||
}).optimize(compressor)
|
||||
})
|
||||
});
|
||||
}
|
||||
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
|
||||
@@ -1060,14 +1086,14 @@ merge(Compressor.prototype, {
|
||||
operator : "||",
|
||||
left : negated,
|
||||
right : self.body.body
|
||||
}).optimize(compressor)
|
||||
})
|
||||
});
|
||||
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
|
||||
@@ -1078,7 +1104,7 @@ merge(Compressor.prototype, {
|
||||
operator : "||",
|
||||
left : self.condition,
|
||||
right : self.alternative.body
|
||||
}).optimize(compressor)
|
||||
})
|
||||
});
|
||||
}
|
||||
if (self.body instanceof AST_Exit
|
||||
@@ -1089,7 +1115,7 @@ merge(Compressor.prototype, {
|
||||
condition : self.condition,
|
||||
consequent : self.body.value,
|
||||
alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor)
|
||||
}).optimize(compressor)
|
||||
})
|
||||
});
|
||||
}
|
||||
if (self.body instanceof AST_If
|
||||
@@ -1099,7 +1125,7 @@ merge(Compressor.prototype, {
|
||||
operator: "&&",
|
||||
left: self.condition,
|
||||
right: self.body.condition
|
||||
}).optimize(compressor);
|
||||
});
|
||||
self.body = self.body.body;
|
||||
}
|
||||
if (aborts(self.body)) {
|
||||
@@ -1108,17 +1134,17 @@ merge(Compressor.prototype, {
|
||||
self.alternative = null;
|
||||
return make_node(AST_BlockStatement, self, {
|
||||
body: [ self, alt ]
|
||||
}).optimize(compressor);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (aborts(self.alternative)) {
|
||||
var body = self.body;
|
||||
self.body = self.alternative;
|
||||
self.condition = negated_is_best ? negated : self.condition.negate(compressor).optimize(compressor);
|
||||
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;
|
||||
});
|
||||
@@ -1199,14 +1225,14 @@ merge(Compressor.prototype, {
|
||||
if (self.args.length != 1) {
|
||||
return make_node(AST_Array, self, {
|
||||
elements: self.args
|
||||
}).optimize(compressor);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Object":
|
||||
if (self.args.length == 0) {
|
||||
return make_node(AST_Object, self, {
|
||||
properties: []
|
||||
}).optimize(compressor);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "String":
|
||||
@@ -1238,7 +1264,7 @@ merge(Compressor.prototype, {
|
||||
case "Function":
|
||||
case "Error":
|
||||
case "Array":
|
||||
return make_node(AST_Call, self, self).optimize(compressor);
|
||||
return make_node(AST_Call, self, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1247,8 +1273,6 @@ merge(Compressor.prototype, {
|
||||
|
||||
OPT(AST_Seq, function(self, compressor){
|
||||
if (compressor.option("cascade")) {
|
||||
if (self.cdr instanceof AST_Seq)
|
||||
self.cdr = self.cdr.optimize(compressor);
|
||||
if (self.car instanceof AST_Assign
|
||||
&& !self.car.left.has_side_effects()
|
||||
&& self.car.left.equivalent_to(self.cdr)) {
|
||||
@@ -1277,7 +1301,7 @@ merge(Compressor.prototype, {
|
||||
// typeof always returns a non-empty string, thus it's
|
||||
// always true in booleans
|
||||
compressor.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_True, self).optimize(compressor);
|
||||
return make_node(AST_True, self);
|
||||
}
|
||||
}
|
||||
if (e instanceof AST_Binary) {
|
||||
@@ -1327,7 +1351,7 @@ merge(Compressor.prototype, {
|
||||
var rr = self.right.evaluate(compressor), right = rr[0];
|
||||
if ((ll.length > 1 && !ll[1]) || (rr.length > 1 && !rr[1])) {
|
||||
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_False, self).optimize(compressor);
|
||||
return make_node(AST_False, self);
|
||||
}
|
||||
if (ll.length > 1 && ll[1]) {
|
||||
return rr[0];
|
||||
@@ -1341,7 +1365,7 @@ merge(Compressor.prototype, {
|
||||
var rr = self.right.evaluate(compressor), right = rr[0];
|
||||
if ((ll.length > 1 && ll[1]) || (rr.length > 1 && rr[1])) {
|
||||
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_True, self).optimize(compressor);
|
||||
return make_node(AST_True, self);
|
||||
}
|
||||
if (ll.length > 1 && !ll[1]) {
|
||||
return rr[0];
|
||||
@@ -1356,7 +1380,7 @@ merge(Compressor.prototype, {
|
||||
if ((ll.length > 1 && ll[0] instanceof AST_String && ll[1]) ||
|
||||
(rr.length > 1 && rr[0] instanceof AST_String && rr[1])) {
|
||||
compressor.warn("+ in boolean context always true [{file}:{line},{col}]", self.start);
|
||||
return make_node(AST_True, self).optimize(compressor);
|
||||
return make_node(AST_True, self);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1390,9 +1414,9 @@ merge(Compressor.prototype, {
|
||||
OPT(AST_SymbolRef, function(self, compressor){
|
||||
if (self.undeclared()) switch (self.name) {
|
||||
case "undefined":
|
||||
return make_node(AST_Undefined, self).optimize(compressor);
|
||||
return make_node(AST_Undefined, self);
|
||||
case "NaN":
|
||||
return make_node(AST_NaN, self).optimize(compressor);
|
||||
return make_node(AST_NaN, self);
|
||||
}
|
||||
return self;
|
||||
});
|
||||
@@ -1433,7 +1457,7 @@ merge(Compressor.prototype, {
|
||||
if (self.condition instanceof AST_Seq) {
|
||||
var car = self.condition.car;
|
||||
self.condition = self.condition.cdr;
|
||||
return AST_Seq.cons(car, self.optimize(compressor)).optimize(compressor);
|
||||
return AST_Seq.cons(car, self);
|
||||
}
|
||||
var cond = self.condition.evaluate(compressor);
|
||||
if (cond.length > 1) {
|
||||
|
||||
Reference in New Issue
Block a user