enhance collapse_vars (#3613)

This commit is contained in:
Alex Lam S.L
2019-11-29 17:45:49 +08:00
committed by GitHub
parent 5a88c30d65
commit 1b61a81b5d
2 changed files with 260 additions and 49 deletions

View File

@@ -1116,7 +1116,7 @@ merge(Compressor.prototype, {
var args; var args;
var candidates = []; var candidates = [];
var stat_index = statements.length; var stat_index = statements.length;
var scanner = new TreeTransformer(function(node) { var scanner = new TreeTransformer(function(node, descend) {
if (abort) return node; if (abort) return node;
// Skip nodes before `candidate` as quickly as possible // Skip nodes before `candidate` as quickly as possible
if (!hit) { if (!hit) {
@@ -1151,11 +1151,16 @@ merge(Compressor.prototype, {
return node; return node;
} }
if (is_lhs(node, parent)) { if (is_lhs(node, parent)) {
if (value_def) replaced++; if (value_def && !hit_rhs) {
assign_used = true;
replaced++;
}
return node;
} else if (value_def) {
if (!hit_rhs) replaced++;
return node; return node;
} else { } else {
replaced++; replaced++;
if (value_def) return node;
} }
CHANGED = abort = true; CHANGED = abort = true;
AST_Node.info("Collapsing {name} [{file}:{line},{col}]", { AST_Node.info("Collapsing {name} [{file}:{line},{col}]", {
@@ -1188,6 +1193,14 @@ merge(Compressor.prototype, {
stop_after = node; stop_after = node;
if (node instanceof AST_Scope) abort = true; if (node instanceof AST_Scope) abort = true;
} }
// Scan but don't replace inside getter/setter
if (node instanceof AST_Accessor) {
var replace = can_replace;
can_replace = false;
descend(node, scanner);
can_replace = replace;
return node;
}
return handle_custom_scan_order(node); return handle_custom_scan_order(node);
}, function(node) { }, function(node) {
if (abort) return; if (abort) return;
@@ -1200,9 +1213,28 @@ merge(Compressor.prototype, {
if (!hit) { if (!hit) {
if (node !== hit_stack[hit_index]) return node; if (node !== hit_stack[hit_index]) return node;
hit_index++; hit_index++;
if (hit_index < hit_stack.length) return; switch (hit_stack.length - hit_index) {
hit = true; case 0:
return node; hit = true;
if (assign_used) return node;
if (node instanceof AST_VarDef) return node;
def.replaced++;
var parent = multi_replacer.parent();
if (parent instanceof AST_Sequence && parent.tail_node() !== node) {
value_def.replaced++;
return MAP.skip;
}
return get_rvalue(candidate);
case 1:
if (!assign_used && node.body === candidate) {
hit = true;
def.replaced++;
value_def.replaced++;
return null;
}
default:
return;
}
} }
// Replace variable when found // Replace variable when found
if (node instanceof AST_SymbolRef if (node instanceof AST_SymbolRef
@@ -1215,7 +1247,8 @@ merge(Compressor.prototype, {
} }
// Skip (non-executed) functions and (leading) default case in switch statements // Skip (non-executed) functions and (leading) default case in switch statements
if (node instanceof AST_Default || node instanceof AST_Scope) return node; if (node instanceof AST_Default || node instanceof AST_Scope) return node;
}); }, patch_sequence);
var force_single;
while (--stat_index >= 0) { while (--stat_index >= 0) {
// Treat parameters as collapsible in IIFE, i.e. // Treat parameters as collapsible in IIFE, i.e.
// function(a, b){ ... }(x()); // function(a, b){ ... }(x());
@@ -1248,7 +1281,10 @@ merge(Compressor.prototype, {
} : side_effects_external : return_false; } : side_effects_external : return_false;
var funarg = candidate.name instanceof AST_SymbolFunarg; var funarg = candidate.name instanceof AST_SymbolFunarg;
var hit = funarg; var hit = funarg;
var abort = false, replaced = 0, can_replace = !args || !hit; var abort = false;
var replaced = 0;
var assign_used = false;
var can_replace = !args || !hit;
if (!can_replace) { if (!can_replace) {
for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
args[j].transform(scanner); args[j].transform(scanner);
@@ -1260,20 +1296,22 @@ merge(Compressor.prototype, {
} }
if (value_def) { if (value_def) {
var def = lhs.definition(); var def = lhs.definition();
if (abort) { var referenced = def.references.length - def.replaced;
var referenced = def.references.length - def.replaced; if (candidate instanceof AST_Assign) referenced--;
if (candidate instanceof AST_Assign) referenced--; if (replaced && referenced == replaced) {
if (referenced > replaced) { abort = false;
replaced = false; } else if (candidate instanceof AST_Assign) {
} else { candidates.push(hit_stack);
abort = false; force_single = true;
} continue;
} else {
replaced = false;
} }
if (!abort) { if (replaced) {
hit_index = 0; hit_index = 0;
hit = funarg; hit = funarg;
for (var i = stat_index; !abort && i < statements.length; i++) { for (var i = stat_index; !abort && i < statements.length; i++) {
statements[i].transform(multi_replacer); if (!statements[i].transform(multi_replacer)) statements.splice(i--, 1);
} }
value_def.single_use = false; value_def.single_use = false;
} }
@@ -1502,9 +1540,12 @@ merge(Compressor.prototype, {
function find_stop(node, level) { function find_stop(node, level) {
var parent = scanner.parent(level); var parent = scanner.parent(level);
if (parent instanceof AST_Array) return node; if (parent instanceof AST_Array) return value_def ? find_stop(parent, level + 1) : node;
if (parent instanceof AST_Assign) return node; if (parent instanceof AST_Assign) return node;
if (parent instanceof AST_Binary) return node; if (parent instanceof AST_Binary) {
if (!value_def || parent.left !== node) return node;
return find_stop(parent, level + 1);
}
if (parent instanceof AST_Call) return node; if (parent instanceof AST_Call) return node;
if (parent instanceof AST_Case) return node; if (parent instanceof AST_Case) return node;
if (parent instanceof AST_Conditional) return node; if (parent instanceof AST_Conditional) return node;
@@ -1512,7 +1553,9 @@ merge(Compressor.prototype, {
if (parent instanceof AST_Exit) return node; if (parent instanceof AST_Exit) return node;
if (parent instanceof AST_If) return node; if (parent instanceof AST_If) return node;
if (parent instanceof AST_IterationStatement) return node; if (parent instanceof AST_IterationStatement) return node;
if (parent instanceof AST_ObjectKeyVal) return node; if (parent instanceof AST_ObjectKeyVal) {
return value_def ? find_stop(scanner.parent(level + 1), level + 2) : node;
}
if (parent instanceof AST_PropAccess) return node; if (parent instanceof AST_PropAccess) return node;
if (parent instanceof AST_Sequence) { if (parent instanceof AST_Sequence) {
return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1); return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
@@ -1557,8 +1600,11 @@ merge(Compressor.prototype, {
return null; return null;
} }
function mangleable_var(var_def) { function mangleable_var(value) {
var value = var_def.value; if (force_single) {
force_single = false;
return;
}
if (!(value instanceof AST_SymbolRef)) return; if (!(value instanceof AST_SymbolRef)) return;
var def = value.definition(); var def = value.definition();
if (def.undeclared) return; if (def.undeclared) return;
@@ -1573,11 +1619,21 @@ merge(Compressor.prototype, {
var referenced = def.references.length - def.replaced; var referenced = def.references.length - def.replaced;
var declared = def.orig.length - def.eliminated; var declared = def.orig.length - def.eliminated;
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg) if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
|| (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) { || (referenced > 1 ? mangleable_var(expr.value) : !compressor.exposed(def))) {
return make_node(AST_SymbolRef, expr.name, expr.name); return make_node(AST_SymbolRef, expr.name, expr.name);
} }
} else if (expr instanceof AST_Assign) {
var lhs = expr.left;
if (expr.operator == "=" && lhs instanceof AST_SymbolRef) {
var def = lhs.definition();
if (def.references[0] === lhs) {
var referenced = def.references.length - def.replaced;
if (referenced > 1) mangleable_var(expr.right);
}
}
return lhs;
} else { } else {
return expr[expr instanceof AST_Assign ? "left" : "expression"]; return expr.expression;
} }
} }
@@ -1689,20 +1745,15 @@ merge(Compressor.prototype, {
node.value = null; node.value = null;
return node; return node;
} }
var parent = this.parent(); return in_list ? MAP.skip : null;
if (!parent) return in_list ? MAP.skip : null; }, patch_sequence));
if (parent instanceof AST_Sequence) return MAP.skip; }
var value = expr;
do { function patch_sequence(node) {
value = get_rvalue(value); if (node instanceof AST_Sequence) switch (node.expressions.length) {
} while (value instanceof AST_Assign); case 0: return null;
return value; case 1: return node.expressions[0];
}, function(node) { }
if (node instanceof AST_Sequence) switch (node.expressions.length) {
case 0: return null;
case 1: return node.expressions[0];
}
}));
} }
function is_lhs_local(lhs) { function is_lhs_local(lhs) {

View File

@@ -1348,8 +1348,7 @@ collapse_vars_array_3: {
} }
expect: { expect: {
function f(a) { function f(a) {
var b; return [ a, a, a ];
return [ b = a, b, b ];
} }
console.log(f().length); console.log(f().length);
} }
@@ -1487,11 +1486,10 @@ collapse_vars_object_3: {
} }
expect: { expect: {
function f(a) { function f(a) {
var b;
return { return {
p: b = a, p: a,
q: b, q: a,
r: b, r: a,
}; };
} }
console.log(f("PASS").r); console.log(f("PASS").r);
@@ -6304,7 +6302,7 @@ assign_left: {
} }
expect: { expect: {
console.log(function(a, b) { console.log(function(a, b) {
(b = a).p.q = "PASS"; a.p.q = "PASS";
return a.p.q; return a.p.q;
}({p: {}})); }({p: {}}));
} }
@@ -6317,12 +6315,12 @@ sub_property: {
} }
input: { input: {
console.log(function(a, b) { console.log(function(a, b) {
return a[(b = a, b.length - 1)]; return a[b = a, b.length - 1];
}([ "FAIL", "PASS" ])); }([ "FAIL", "PASS" ]));
} }
expect: { expect: {
console.log(function(a, b) { console.log(function(a, b) {
return a[(b = a).length - 1]; return a[a.length - 1];
}([ "FAIL", "PASS" ])); }([ "FAIL", "PASS" ]));
} }
expect_stdout: "PASS" expect_stdout: "PASS"
@@ -6754,7 +6752,7 @@ local_value_replacement: {
} }
expect: { expect: {
function f(a, b) { function f(a, b) {
(a = b) && g(a); b && g(b);
} }
function g(c) { function g(c) {
console.log(c); console.log(c);
@@ -6805,3 +6803,165 @@ array_in_object_2: {
} }
expect_stdout: "1 1" expect_stdout: "1 1"
} }
array_in_conditional: {
options = {
collapse_vars: true,
}
input: {
var a = 1, b = 2, c;
console.log(c && [ b = a ], a, b);
}
expect: {
var a = 1, b = 2, c;
console.log(c && [ b = a ], a, b);
}
expect_stdout: "undefined 1 2"
}
object_in_conditional: {
options = {
collapse_vars: true,
}
input: {
var a = 1, b = 2, c;
console.log(c && {
p: b = a
}, a, b);
}
expect: {
var a = 1, b = 2, c;
console.log(c && {
p: b = a
}, a, b);
}
expect_stdout: "undefined 1 2"
}
sequence_in_iife_1: {
options = {
collapse_vars: true,
}
input: {
var a = "foo", b = 42;
(function() {
var c = (b = a, b);
})();
console.log(a, b);
}
expect: {
var a = "foo", b = 42;
(function() {
var c = b = a;
})();
console.log(a, b);
}
expect_stdout: "foo foo"
}
sequence_in_iife_2: {
options = {
collapse_vars: true,
inline: true,
passes: 2,
side_effects: true,
unused: true,
}
input: {
var a = "foo", b = 42;
(function() {
var c = (b = a, b);
})();
console.log(a, b);
}
expect: {
var a = "foo", b = 42;
console.log(a, a);
}
expect_stdout: "foo foo"
}
retain_assign: {
options = {
collapse_vars: true,
}
input: {
var a = 42, b, c = "FAIL";
b = a;
b++ && (c = "PASS");
console.log(c);
}
expect: {
var a = 42, b, c = "FAIL";
b = a;
b++ && (c = "PASS");
console.log(c);
}
expect_stdout: "PASS"
}
getter_side_effect: {
options = {
collapse_vars: true,
}
input: {
var c = "FAIL";
(function(a) {
var b;
(b = a) && {
get foo() {
a = 0;
}
}.foo;
b && (c = "PASS");
})(42);
console.log(c);
}
expect: {
var c = "FAIL";
(function(a) {
var b;
(b = a) && {
get foo() {
a = 0;
}
}.foo;
b && (c = "PASS");
})(42);
console.log(c);
}
expect_stdout: "PASS"
}
setter_side_effect: {
options = {
collapse_vars: true,
}
input: {
var c = "FAIL";
(function(a) {
var b;
(b = a) && ({
set foo(v) {
a = v;
}
}.foo = 0);
b && (c = "PASS");
})(42);
console.log(c);
}
expect: {
var c = "FAIL";
(function(a) {
var b;
(b = a) && ({
set foo(v) {
a = v;
}
}.foo = 0);
b && (c = "PASS");
})(42);
console.log(c);
}
expect_stdout: "PASS"
}