enable collapsing within AST_Definitions
This commit is contained in:
parent
7dd1e0f34b
commit
761792e2e5
236
lib/compress.js
236
lib/compress.js
|
|
@ -647,132 +647,141 @@ merge(Compressor.prototype, {
|
|||
for (var stat_index = statements.length; --stat_index >= 0;) {
|
||||
var stat = statements[stat_index];
|
||||
|
||||
// The variable definition must precede a statement.
|
||||
if (stat_index <= 0) break;
|
||||
var prev_stat_index = stat_index - 1;
|
||||
var prev_stat = statements[prev_stat_index];
|
||||
if (!(prev_stat instanceof AST_Definitions)) continue;
|
||||
var var_defs = prev_stat.definitions;
|
||||
if (var_defs == null) continue;
|
||||
|
||||
var var_names_seen = {};
|
||||
var var_names_seen = Object.create(null);
|
||||
var side_effects_encountered = false;
|
||||
var lvalues_encountered = false;
|
||||
var lvalues = {};
|
||||
var lvalues = Object.create(null);
|
||||
var prev_stat_index, var_defs, var_defs_index;
|
||||
|
||||
// Scan variable definitions from right to left.
|
||||
for (var var_defs_index = var_defs.length; --var_defs_index >= 0;) {
|
||||
|
||||
// Obtain var declaration and var name with basic sanity check.
|
||||
var var_decl = var_defs[var_defs_index];
|
||||
if (var_decl.value == null) break;
|
||||
var var_name = var_decl.name.name;
|
||||
if (!var_name || !var_name.length) break;
|
||||
|
||||
// Bail if we've seen a var definition of same name before.
|
||||
if (var_name in var_names_seen) break;
|
||||
var_names_seen[var_name] = true;
|
||||
|
||||
// Only interested in cases with just one reference to the variable.
|
||||
var def = var_decl.name.definition();
|
||||
if (def.references.length !== 1
|
||||
|| var_name == "arguments" || (!toplevel && def.global)) {
|
||||
side_effects_encountered = true;
|
||||
continue;
|
||||
if (stat instanceof AST_Definitions) {
|
||||
prev_stat_index = stat_index;
|
||||
var_defs = stat.definitions;
|
||||
for (var_defs_index = var_defs.length - 1; --var_defs_index >= 0;) {
|
||||
if (collapse(var_defs[var_defs_index + 1])) break;
|
||||
}
|
||||
var ref = def.references[0];
|
||||
|
||||
// Don't replace ref if eval() or with statement in scope.
|
||||
if (ref.scope.uses_eval || ref.scope.uses_with) break;
|
||||
|
||||
// Constant single use vars can be replaced in any scope.
|
||||
if (var_decl.value.is_constant()) {
|
||||
var ctt = new TreeTransformer(function(node) {
|
||||
var parent = ctt.parent();
|
||||
if (parent instanceof AST_IterationStatement
|
||||
&& (parent.condition === node || parent.init === node)) {
|
||||
return node;
|
||||
}
|
||||
if (node === ref)
|
||||
return replace_var(node, parent, true);
|
||||
});
|
||||
stat.transform(ctt);
|
||||
continue;
|
||||
} else if (stat_index > 0) {
|
||||
// The variable definition must precede a statement.
|
||||
prev_stat_index = stat_index - 1;
|
||||
var prev_stat = statements[prev_stat_index];
|
||||
if (!(prev_stat instanceof AST_Definitions)) continue;
|
||||
var_defs = prev_stat.definitions;
|
||||
for (var_defs_index = var_defs.length; --var_defs_index >= 0;) {
|
||||
if (collapse(stat)) break;
|
||||
}
|
||||
|
||||
// Restrict var replacement to constants if side effects encountered.
|
||||
if (side_effects_encountered |= lvalues_encountered) continue;
|
||||
|
||||
var value_has_side_effects = var_decl.value.has_side_effects(compressor);
|
||||
// Non-constant single use vars can only be replaced in same scope.
|
||||
if (ref.scope !== scope) {
|
||||
side_effects_encountered |= value_has_side_effects;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect lvalues in var value.
|
||||
var tw = new TreeWalker(function(node){
|
||||
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
|
||||
lvalues[node.name] = lvalues_encountered = true;
|
||||
}
|
||||
});
|
||||
var_decl.value.walk(tw);
|
||||
|
||||
// Replace the non-constant single use var in statement if side effect free.
|
||||
var unwind = false;
|
||||
var tt = new TreeTransformer(
|
||||
function preorder(node) {
|
||||
if (unwind) return node;
|
||||
var parent = tt.parent();
|
||||
if (node instanceof AST_Lambda
|
||||
|| node instanceof AST_Try
|
||||
|| node instanceof AST_With
|
||||
|| node instanceof AST_Case
|
||||
|| node instanceof AST_IterationStatement
|
||||
|| (parent instanceof AST_If && node !== parent.condition)
|
||||
|| (parent instanceof AST_Conditional && node !== parent.condition)
|
||||
|| (node instanceof AST_SymbolRef
|
||||
&& value_has_side_effects
|
||||
&& !are_references_in_scope(node.definition(), scope))
|
||||
|| (parent instanceof AST_Binary
|
||||
&& (parent.operator == "&&" || parent.operator == "||")
|
||||
&& node === parent.right)
|
||||
|| (parent instanceof AST_Switch && node !== parent.expression)) {
|
||||
return side_effects_encountered = unwind = true, node;
|
||||
}
|
||||
function are_references_in_scope(def, scope) {
|
||||
if (def.orig.length === 1
|
||||
&& def.orig[0] instanceof AST_SymbolDefun) return true;
|
||||
if (def.scope !== scope) return false;
|
||||
var refs = def.references;
|
||||
for (var i = 0, len = refs.length; i < len; i++) {
|
||||
if (refs[i].scope !== scope) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
function postorder(node) {
|
||||
if (unwind) return node;
|
||||
if (node === ref)
|
||||
return unwind = true, replace_var(node, tt.parent(), false);
|
||||
if (side_effects_encountered |= node.has_side_effects(compressor))
|
||||
return unwind = true, node;
|
||||
if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
|
||||
side_effects_encountered = true;
|
||||
return unwind = true, node;
|
||||
}
|
||||
}
|
||||
);
|
||||
stat.transform(tt);
|
||||
}
|
||||
}
|
||||
|
||||
return statements;
|
||||
|
||||
function collapse(stat) {
|
||||
var var_decl = var_defs[var_defs_index];
|
||||
if (var_decl.value == null) return true;
|
||||
var var_name = var_decl.name.name;
|
||||
if (!var_name || !var_name.length) return true;
|
||||
|
||||
// Bail if we've seen a var definition of same name before.
|
||||
if (var_name in var_names_seen) return true;
|
||||
var_names_seen[var_name] = true;
|
||||
|
||||
// Only interested in cases with just one reference to the variable.
|
||||
var def = var_decl.name.definition();
|
||||
if (def.references.length !== 1
|
||||
|| var_name == "arguments" || (!toplevel && def.global)) {
|
||||
side_effects_encountered = true;
|
||||
return;
|
||||
}
|
||||
var ref = def.references[0];
|
||||
|
||||
// Don't replace ref if eval() or with statement in scope.
|
||||
if (ref.scope.uses_eval || ref.scope.uses_with) return true;
|
||||
|
||||
// Constant single use vars can be replaced in any scope.
|
||||
if (var_decl.value.is_constant()) {
|
||||
var ctt = new TreeTransformer(function(node) {
|
||||
var parent = ctt.parent();
|
||||
if (parent instanceof AST_IterationStatement
|
||||
&& (parent.condition === node || parent.init === node)) {
|
||||
return node;
|
||||
}
|
||||
if (node === ref)
|
||||
return replace_var(var_decl, node, parent, true);
|
||||
});
|
||||
stat.transform(ctt);
|
||||
return;
|
||||
}
|
||||
|
||||
// Restrict var replacement to constants if side effects encountered.
|
||||
if (side_effects_encountered |= lvalues_encountered) return;
|
||||
|
||||
var value_has_side_effects = var_decl.value.has_side_effects(compressor);
|
||||
// Non-constant single use vars can only be replaced in same scope.
|
||||
if (ref.scope !== scope) {
|
||||
side_effects_encountered |= value_has_side_effects;
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect lvalues in var value.
|
||||
var tw = new TreeWalker(function(node){
|
||||
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
|
||||
lvalues[node.name] = lvalues_encountered = true;
|
||||
}
|
||||
});
|
||||
var_decl.value.walk(tw);
|
||||
|
||||
// Replace the non-constant single use var in statement if side effect free.
|
||||
var unwind = false;
|
||||
var tt = new TreeTransformer(
|
||||
function preorder(node) {
|
||||
if (unwind) return node;
|
||||
var parent = tt.parent();
|
||||
if (node instanceof AST_Lambda
|
||||
|| node instanceof AST_Try
|
||||
|| node instanceof AST_With
|
||||
|| node instanceof AST_Case
|
||||
|| node instanceof AST_IterationStatement
|
||||
|| (parent instanceof AST_If && node !== parent.condition)
|
||||
|| (parent instanceof AST_Conditional && node !== parent.condition)
|
||||
|| (node instanceof AST_SymbolRef
|
||||
&& value_has_side_effects
|
||||
&& !are_references_in_scope(node.definition(), scope))
|
||||
|| (parent instanceof AST_Binary
|
||||
&& (parent.operator == "&&" || parent.operator == "||")
|
||||
&& node === parent.right)
|
||||
|| (parent instanceof AST_Switch && node !== parent.expression)) {
|
||||
return side_effects_encountered = unwind = true, node;
|
||||
}
|
||||
function are_references_in_scope(def, scope) {
|
||||
if (def.orig.length === 1
|
||||
&& def.orig[0] instanceof AST_SymbolDefun) return true;
|
||||
if (def.scope !== scope) return false;
|
||||
var refs = def.references;
|
||||
for (var i = 0, len = refs.length; i < len; i++) {
|
||||
if (refs[i].scope !== scope) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
function postorder(node) {
|
||||
if (unwind) return node;
|
||||
if (node === ref)
|
||||
return unwind = true, replace_var(var_decl, node, tt.parent(), false);
|
||||
if (side_effects_encountered |= node.has_side_effects(compressor))
|
||||
return unwind = true, node;
|
||||
if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
|
||||
side_effects_encountered = true;
|
||||
return unwind = true, node;
|
||||
}
|
||||
}
|
||||
);
|
||||
stat.transform(tt);
|
||||
}
|
||||
|
||||
function is_lvalue(node, parent) {
|
||||
return node instanceof AST_SymbolRef && is_lhs(node, parent);
|
||||
}
|
||||
function replace_var(node, parent, is_constant) {
|
||||
|
||||
function replace_var(var_decl, node, parent, is_constant) {
|
||||
if (is_lvalue(node, parent)) return node;
|
||||
|
||||
// Remove var definition and return its value to the TreeTransformer to replace.
|
||||
|
|
@ -789,7 +798,7 @@ merge(Compressor.prototype, {
|
|||
|
||||
compressor.info("Collapsing {type} {name} [{file}:{line},{col}]", {
|
||||
type: is_constant ? "constant" : "variable",
|
||||
name: var_name,
|
||||
name: var_decl.name.name,
|
||||
file: node.start.file,
|
||||
line: node.start.line,
|
||||
col: node.start.col
|
||||
|
|
@ -1737,6 +1746,7 @@ merge(Compressor.prototype, {
|
|||
def(AST_SymbolRef, function(compressor){
|
||||
return this.undeclared();
|
||||
});
|
||||
def(AST_SymbolDeclaration, return_false);
|
||||
def(AST_Object, function(compressor){
|
||||
return any(this.properties, compressor);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -667,7 +667,7 @@ collapse_vars_lvalues: {
|
|||
function f4(x) { var a = (x -= 3); return x + a; }
|
||||
function f5(x) { var w = e1(), v = e2(), c = v = --x; return (w = x) - c; }
|
||||
function f6(x) { var w = e1(), v = e2(); return (v = --x) - (w = x); }
|
||||
function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; }
|
||||
function f7(x) { var w = e1(), c = e2() - x; return (w = x) - c; }
|
||||
function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); }
|
||||
function f9(x) { var w = e1(); return e2() - x - (w = x); }
|
||||
}
|
||||
|
|
@ -699,7 +699,7 @@ collapse_vars_lvalues_drop_assign: {
|
|||
function f4(x) { var a = (x -= 3); return x + a; }
|
||||
function f5(x) { var v = (e1(), e2()), c = v = --x; return x - c; }
|
||||
function f6(x) { e1(), e2(); return --x - x; }
|
||||
function f7(x) { var v = (e1(), e2()), c = v - x; return x - c; }
|
||||
function f7(x) { var c = (e1(), e2() - x); return x - c; }
|
||||
function f8(x) { var v = (e1(), e2()); return x - (v - x); }
|
||||
function f9(x) { e1(); return e2() - x - x; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user