improve collapse_vars on AST_Var (#1828)
Perform the same cascaded scanning within `var` statement as we do on array of statements.
This commit is contained in:
195
lib/compress.js
195
lib/compress.js
@@ -644,21 +644,21 @@ merge(Compressor.prototype, {
|
|||||||
|
|
||||||
var scope = compressor.find_parent(AST_Scope);
|
var scope = compressor.find_parent(AST_Scope);
|
||||||
var toplevel = compressor.option("toplevel");
|
var toplevel = compressor.option("toplevel");
|
||||||
for (var stat_index = statements.length; --stat_index >= 0;) {
|
var stat_index;
|
||||||
var stat = statements[stat_index];
|
var prev_stat_index;
|
||||||
|
var def_stat_index;
|
||||||
var var_names_seen = Object.create(null);
|
var stat;
|
||||||
var side_effects_encountered = false;
|
var var_defs;
|
||||||
var lvalues_encountered = false;
|
var var_defs_index;
|
||||||
var lvalues = Object.create(null);
|
for (stat_index = statements.length; --stat_index >= 0;) {
|
||||||
var prev_stat_index, var_defs, var_defs_index;
|
stat = statements[stat_index];
|
||||||
|
|
||||||
// Scan variable definitions from right to left.
|
// Scan variable definitions from right to left.
|
||||||
if (stat instanceof AST_Definitions) {
|
if (stat instanceof AST_Definitions) {
|
||||||
prev_stat_index = stat_index;
|
prev_stat_index = stat_index;
|
||||||
var_defs = stat.definitions;
|
var_defs = stat.definitions;
|
||||||
for (var_defs_index = var_defs.length - 1; --var_defs_index >= 0;) {
|
for (def_stat_index = var_defs.length; --def_stat_index >= 1;) {
|
||||||
if (collapse(var_defs[var_defs_index + 1])) break;
|
stat = var_defs[def_stat_index];
|
||||||
|
scan_var_defs(def_stat_index);
|
||||||
}
|
}
|
||||||
} else if (stat_index > 0) {
|
} else if (stat_index > 0) {
|
||||||
// The variable definition must precede a statement.
|
// The variable definition must precede a statement.
|
||||||
@@ -666,103 +666,107 @@ merge(Compressor.prototype, {
|
|||||||
var prev_stat = statements[prev_stat_index];
|
var prev_stat = statements[prev_stat_index];
|
||||||
if (!(prev_stat instanceof AST_Definitions)) continue;
|
if (!(prev_stat instanceof AST_Definitions)) continue;
|
||||||
var_defs = prev_stat.definitions;
|
var_defs = prev_stat.definitions;
|
||||||
for (var_defs_index = var_defs.length; --var_defs_index >= 0;) {
|
scan_var_defs(var_defs.length);
|
||||||
if (collapse(stat)) break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return statements;
|
return statements;
|
||||||
|
|
||||||
function collapse(stat) {
|
function scan_var_defs(end_pos) {
|
||||||
var var_decl = var_defs[var_defs_index];
|
var var_names_seen = Object.create(null);
|
||||||
// `drop_unused()` shuffles variables without values to the top,
|
var side_effects_encountered = false;
|
||||||
// so we can terminate upon first sighting as an optimization.
|
var lvalues_encountered = false;
|
||||||
if (var_decl.value == null) return true;
|
var lvalues = Object.create(null);
|
||||||
var var_name = var_decl.name.name;
|
for (var_defs_index = end_pos; --var_defs_index >= 0;) {
|
||||||
|
var var_decl = var_defs[var_defs_index];
|
||||||
|
// `drop_unused()` shuffles variables without values to the top,
|
||||||
|
// so we can terminate upon first sighting as an optimization.
|
||||||
|
if (var_decl.value == null) break;
|
||||||
|
var var_name = var_decl.name.name;
|
||||||
|
|
||||||
// Bail if we've seen a var definition of same name before.
|
// Bail if we've seen a var definition of same name before.
|
||||||
if (var_name in var_names_seen) return true;
|
if (var_name in var_names_seen) break;
|
||||||
var_names_seen[var_name] = true;
|
var_names_seen[var_name] = true;
|
||||||
|
|
||||||
// Only interested in non-constant values.
|
// Only interested in non-constant values.
|
||||||
if (var_decl.value.is_constant()) return;
|
if (var_decl.value.is_constant()) continue;
|
||||||
|
|
||||||
// Only interested in cases with just one reference to the variable.
|
// Only interested in cases with just one reference to the variable.
|
||||||
var def = var_decl.name.definition();
|
var def = var_decl.name.definition();
|
||||||
if (def.references.length !== 1
|
if (def.references.length !== 1
|
||||||
|| var_name == "arguments" || (!toplevel && def.global)) {
|
|| var_name == "arguments" || (!toplevel && def.global)) {
|
||||||
side_effects_encountered = true;
|
side_effects_encountered = true;
|
||||||
return;
|
continue;
|
||||||
}
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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 ref = def.references[0];
|
||||||
var_decl.value.walk(tw);
|
|
||||||
|
|
||||||
// Replace the non-constant single use var in statement if side effect free.
|
// Don't replace ref if eval() or with statement in scope.
|
||||||
var unwind = false;
|
if (ref.scope.uses_eval || ref.scope.uses_with) break;
|
||||||
var tt = new TreeTransformer(
|
|
||||||
function preorder(node) {
|
// Restrict var replacement to constants if side effects encountered.
|
||||||
if (unwind || node instanceof AST_Scope && node !== scope) return node;
|
if (side_effects_encountered |= lvalues_encountered) continue;
|
||||||
var parent = tt.parent();
|
|
||||||
if (node instanceof AST_Try
|
var value_has_side_effects = var_decl.value.has_side_effects(compressor);
|
||||||
|| node instanceof AST_With
|
// Non-constant single use vars can only be replaced in same scope.
|
||||||
|| node instanceof AST_Case
|
if (ref.scope !== scope) {
|
||||||
|| node instanceof AST_IterationStatement
|
side_effects_encountered |= value_has_side_effects;
|
||||||
|| (parent instanceof AST_If && node !== parent.condition)
|
continue;
|
||||||
|| (parent instanceof AST_Conditional && node !== parent.condition)
|
}
|
||||||
|| (node instanceof AST_SymbolRef
|
|
||||||
&& value_has_side_effects
|
// Detect lvalues in var value.
|
||||||
&& !are_references_in_scope(node.definition(), scope))
|
var tw = new TreeWalker(function(node){
|
||||||
|| (parent instanceof AST_Binary
|
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
|
||||||
&& (parent.operator == "&&" || parent.operator == "||")
|
lvalues[node.name] = lvalues_encountered = true;
|
||||||
&& 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
|
var_decl.value.walk(tw);
|
||||||
&& def.orig[0] instanceof AST_SymbolDefun) return true;
|
|
||||||
if (def.scope !== scope) return false;
|
// Replace the non-constant single use var in statement if side effect free.
|
||||||
var refs = def.references;
|
var unwind = false;
|
||||||
for (var i = 0, len = refs.length; i < len; i++) {
|
var tt = new TreeTransformer(
|
||||||
if (refs[i].scope !== scope) return false;
|
function preorder(node) {
|
||||||
|
if (unwind || node instanceof AST_Scope && node !== scope) return node;
|
||||||
|
var parent = tt.parent();
|
||||||
|
if (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;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
function postorder(node) {
|
stat.transform(tt);
|
||||||
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) {
|
function is_lvalue(node, parent) {
|
||||||
@@ -777,6 +781,7 @@ merge(Compressor.prototype, {
|
|||||||
var_decl.value = null;
|
var_decl.value = null;
|
||||||
|
|
||||||
var_defs.splice(var_defs_index, 1);
|
var_defs.splice(var_defs_index, 1);
|
||||||
|
def_stat_index--;
|
||||||
if (var_defs.length === 0) {
|
if (var_defs.length === 0) {
|
||||||
statements.splice(prev_stat_index, 1);
|
statements.splice(prev_stat_index, 1);
|
||||||
stat_index--;
|
stat_index--;
|
||||||
|
|||||||
@@ -1654,3 +1654,26 @@ iife_2: {
|
|||||||
}(bar());
|
}(bar());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var_defs: {
|
||||||
|
options = {
|
||||||
|
collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true,
|
||||||
|
comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true,
|
||||||
|
keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var f1 = function(x, y) {
|
||||||
|
var a, b, r = x + y, q = r * r, z = q - r, a = z, b = 7;
|
||||||
|
console.log(a + b);
|
||||||
|
};
|
||||||
|
f1("1", 0);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var f1 = function(x, y) {
|
||||||
|
var r = x + y, a = r * r - r, b = 7;
|
||||||
|
console.log(a + b);
|
||||||
|
};
|
||||||
|
f1("1", 0);
|
||||||
|
}
|
||||||
|
expect_stdout: "97"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user