implement hoist_props (#2396)

fixes #2377
This commit is contained in:
Alex Lam S.L
2017-10-25 03:38:11 +08:00
committed by GitHub
parent 74ae16f9f8
commit 4178289c38
3 changed files with 450 additions and 9 deletions

View File

@@ -60,6 +60,7 @@ function Compressor(options, false_by_default) {
expression : false, expression : false,
global_defs : {}, global_defs : {},
hoist_funs : !false_by_default, hoist_funs : !false_by_default,
hoist_props : false,
hoist_vars : false, hoist_vars : false,
ie8 : false, ie8 : false,
if_return : !false_by_default, if_return : !false_by_default,
@@ -190,6 +191,7 @@ merge(Compressor.prototype, {
if (node._squeezed) return node; if (node._squeezed) return node;
var was_scope = false; var was_scope = false;
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
node = node.hoist_properties(this);
node = node.hoist_declarations(this); node = node.hoist_declarations(this);
was_scope = true; was_scope = true;
} }
@@ -547,6 +549,7 @@ merge(Compressor.prototype, {
} }
function reset_def(def) { function reset_def(def) {
def.direct_access = false;
def.escaped = false; def.escaped = false;
if (def.scope.uses_eval) { if (def.scope.uses_eval) {
def.fixed = false; def.fixed = false;
@@ -604,15 +607,19 @@ merge(Compressor.prototype, {
|| parent instanceof AST_Return && node === parent.value && node.scope !== d.scope || parent instanceof AST_Return && node === parent.value && node.scope !== d.scope
|| parent instanceof AST_VarDef && node === parent.value) { || parent instanceof AST_VarDef && node === parent.value) {
d.escaped = true; d.escaped = true;
return;
} else if (parent instanceof AST_Array || parent instanceof AST_Object) { } else if (parent instanceof AST_Array || parent instanceof AST_Object) {
mark_escaped(d, parent, parent, level + 1); mark_escaped(d, parent, parent, level + 1);
} else if (parent instanceof AST_PropAccess && node === parent.expression) { } else if (parent instanceof AST_PropAccess && node === parent.expression) {
mark_escaped(d, parent, read_property(value, parent.property), level + 1); value = read_property(value, parent.property);
mark_escaped(d, parent, value, level + 1);
if (value) return;
} }
if (level == 0) d.direct_access = true;
} }
}); });
AST_SymbolRef.DEFMETHOD("fixed_value", function() { AST_Symbol.DEFMETHOD("fixed_value", function() {
var fixed = this.definition().fixed; var fixed = this.definition().fixed;
if (!fixed || fixed instanceof AST_Node) return fixed; if (!fixed || fixed instanceof AST_Node) return fixed;
return fixed(); return fixed();
@@ -2478,11 +2485,11 @@ merge(Compressor.prototype, {
})); }));
} }
switch (body.length) { switch (body.length) {
case 0: case 0:
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node); return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
case 1: case 1:
return body[0]; return body[0];
default: default:
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body body: body
}); });
@@ -2678,6 +2685,71 @@ merge(Compressor.prototype, {
return self; return self;
}); });
AST_Scope.DEFMETHOD("hoist_properties", function(compressor){
var self = this;
if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
var defs_by_id = Object.create(null);
var var_names = Object.create(null);
self.enclosed.forEach(function(def) {
var_names[def.name] = true;
});
self.variables.each(function(def, name) {
var_names[name] = true;
});
return self.transform(new TreeTransformer(function(node) {
if (node instanceof AST_VarDef) {
var sym = node.name, def, value;
if (sym.scope === self
&& !(def = sym.definition()).escaped
&& !def.single_use
&& !def.direct_access
&& (value = sym.fixed_value()) === node.value
&& value instanceof AST_Object) {
var defs = new Dictionary();
var assignments = [];
value.properties.forEach(function(prop) {
assignments.push(make_node(AST_VarDef, node, {
name: make_sym(prop.key),
value: prop.value
}));
});
defs_by_id[def.id] = defs;
return MAP.splice(assignments);
}
}
if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) {
var defs = defs_by_id[node.expression.definition().id];
if (defs) {
var key = node.property;
if (key instanceof AST_Node) key = key.getValue();
var def = defs.get(key);
var sym = make_node(AST_SymbolRef, node, {
name: def.name,
scope: node.expression.scope,
thedef: def
});
sym.reference({});
return sym;
}
}
function make_sym(key) {
var prefix = sym.name + "_" + key.toString().replace(/[^a-z_$]+/ig, "_");
var name = prefix;
for (var i = 0; var_names[name]; i++) name = prefix + "$" + i;
var new_var = make_node(sym.CTOR, sym, {
name: name,
scope: self
});
var def = self.def_variable(new_var);
defs.set(key, def);
self.enclosed.push(def);
var_names[name] = true;
return new_var;
}
}));
});
// drop_side_effect_free() // drop_side_effect_free()
// remove side-effect-free parts which only affects return value // remove side-effect-free parts which only affects return value
(function(def){ (function(def){

View File

@@ -0,0 +1,371 @@
issue_2377_1: {
options = {
evaluate: true,
inline: true,
hoist_props: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var obj = {
foo: 1,
bar: 2,
square: function(x) {
return x * x;
},
cube: function(x) {
return x * x * x;
},
};
console.log(obj.foo, obj.cube(3));
}
expect: {
var obj_foo = 1, obj_cube = function(x) {
return x * x * x;
};
console.log(obj_foo, obj_cube(3));
}
expect_stdout: "1 27"
}
issue_2377_2: {
options = {
evaluate: true,
inline: true,
hoist_props: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var obj = {
foo: 1,
bar: 2,
square: function(x) {
return x * x;
},
cube: function(x) {
return x * x * x;
},
};
console.log(obj.foo, obj.cube(3));
}
expect: {
console.log(1, function(x) {
return x * x * x;
}(3));
}
expect_stdout: "1 27"
}
issue_2377_3: {
options = {
evaluate: true,
inline: true,
hoist_props: true,
passes: 3,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var obj = {
foo: 1,
bar: 2,
square: function(x) {
return x * x;
},
cube: function(x) {
return x * x * x;
},
};
console.log(obj.foo, obj.cube(3));
}
expect: {
console.log(1, 27);
}
expect_stdout: "1 27"
}
direct_access_1: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = 0;
var obj = {
a: 1,
b: 2,
};
for (var k in obj) a++;
console.log(a, obj.a);
}
expect: {
var a = 0;
var obj = {
a: 1,
b: 2,
};
for (var k in obj) a++;
console.log(a, obj.a);
}
expect_stdout: "2 1"
}
direct_access_2: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = { a: 1 };
var f = function(k) {
if (o[k]) return "PASS";
};
console.log(f("a"));
}
expect: {
var o = { a: 1 };
console.log(function(k) {
if (o[k]) return "PASS";
}("a"));
}
expect_stdout: "PASS"
}
direct_access_3: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = { a: 1 };
o.b;
console.log(o.a);
}
expect: {
var o = { a: 1 };
o.b;
console.log(o.a);
}
expect_stdout: "1"
}
single_use: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var obj = {
bar: function() {
return 42;
},
};
console.log(obj.bar());
}
expect: {
console.log({
bar: function() {
return 42;
},
}.bar());
}
}
name_collision_1: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
}
input: {
var obj_foo = 1;
var obj_bar = 2;
function f() {
var obj = {
foo: 3,
bar: 4,
"b-r": 5,
"b+r": 6,
"b!r": 7,
};
console.log(obj_foo, obj.foo, obj.bar, obj["b-r"], obj["b+r"], obj["b!r"]);
}
f();
}
expect: {
var obj_foo = 1;
var obj_bar = 2;
function f() {
var obj_foo$0 = 3,
obj_bar = 4,
obj_b_r = 5,
obj_b_r$0 = 6,
obj_b_r$1 = 7;
console.log(obj_foo, obj_foo$0, obj_bar, obj_b_r, obj_b_r$0, obj_b_r$1);
}
f();
}
expect_stdout: "1 3 4 5 6 7"
}
name_collision_2: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {
p: 1,
0: function(x) {
return x;
},
1: function(x) {
return x + 1;
}
}, o__$0 = 2, o__$1 = 3;
console.log(o.p === o.p, o[0](4), o[1](5), o__$0, o__$1);
}
expect: {
var o_p = 1,
o__ = function(x) {
return x;
},
o__$2 = function(x) {
return x + 1;
},
o__$0 = 2,
o__$1 = 3;
console.log(o_p === o_p, o__(4), o__$2(5), o__$0, o__$1);
}
expect_stdout: "true 4 6 2 3"
}
name_collision_3: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {
p: 1,
0: function(x) {
return x;
},
1: function(x) {
return x + 1;
}
}, o__$0 = 2, o__$1 = 3;
console.log(o.p === o.p, o[0](4), o[1](5));
}
expect: {
var o_p = 1,
o__ = function(x) {
return x;
},
o__$2 = function(x) {
return x + 1;
},
o__$0 = 2,
o__$1 = 3;
console.log(o_p === o_p, o__(4), o__$2(5));
}
expect_stdout: "true 4 6"
}
contains_this_1: {
options = {
evaluate: true,
hoist_props: true,
inline: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
u: function() {
return this === this;
},
p: 1
};
console.log(o.p, o.p);
}
expect: {
console.log(1, 1);
}
expect_stdout: "1 1"
}
contains_this_2: {
options = {
evaluate: true,
hoist_props: true,
inline: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
u: function() {
return this === this;
},
p: 1
};
console.log(o.p, o.p, o.u);
}
expect: {
console.log(1, 1, function() {
return this === this;
});
}
expect_stdout: true
}
contains_this_3: {
options = {
evaluate: true,
hoist_props: true,
inline: true,
passes: 2,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
u: function() {
return this === this;
},
p: 1
};
console.log(o.p, o.p, o.u());
}
expect: {
var o = {
u: function() {
return this === this;
},
p: 1
};
console.log(o.p, o.p, o.u());
}
expect_stdout: "1 1 true"
}

View File

@@ -16,11 +16,9 @@
{}, {},
{ {
"compress": { "compress": {
"toplevel": true "hoist_props": true
}, },
"mangle": { "toplevel": true
"toplevel": true
}
}, },
{ {
"compress": { "compress": {