[WIP] implement hoist_props

fixes #2377
This commit is contained in:
alexlamsl 2017-10-24 16:58:31 +08:00
parent 74ae16f9f8
commit 1893abd2cb
3 changed files with 231 additions and 8 deletions

View File

@ -60,6 +60,7 @@ function Compressor(options, false_by_default) {
expression : false,
global_defs : {},
hoist_funs : !false_by_default,
hoist_props : false,
hoist_vars : false,
ie8 : false,
if_return : !false_by_default,
@ -190,6 +191,7 @@ merge(Compressor.prototype, {
if (node._squeezed) return node;
var was_scope = false;
if (node instanceof AST_Scope) {
node = node.hoist_properties(this);
node = node.hoist_declarations(this);
was_scope = true;
}
@ -547,6 +549,7 @@ merge(Compressor.prototype, {
}
function reset_def(def) {
def.direct_access = false;
def.escaped = false;
if (def.scope.uses_eval) {
def.fixed = false;
@ -604,15 +607,18 @@ merge(Compressor.prototype, {
|| parent instanceof AST_Return && node === parent.value && node.scope !== d.scope
|| parent instanceof AST_VarDef && node === parent.value) {
d.escaped = true;
return;
} else if (parent instanceof AST_Array || parent instanceof AST_Object) {
mark_escaped(d, parent, parent, level + 1);
} else if (parent instanceof AST_PropAccess && node === parent.expression) {
mark_escaped(d, parent, read_property(value, parent.property), level + 1);
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;
if (!fixed || fixed instanceof AST_Node) return fixed;
return fixed();
@ -2478,11 +2484,11 @@ merge(Compressor.prototype, {
}));
}
switch (body.length) {
case 0:
case 0:
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
case 1:
case 1:
return body[0];
default:
default:
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
@ -2678,6 +2684,68 @@ merge(Compressor.prototype, {
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 tt = 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 = [
make_node(AST_VarDef, node, {
name: sym,
value: make_node(AST_Object, value, {
properties: []
})
})
];
value.properties.forEach(function(prop) {
var key = make_node(sym.CTOR, sym, {
name: make_node(AST_Sub, sym, {
expression: sym,
property: make_node(AST_String, prop, {
value: prop.key
})
}).print_to_string(),
scope: self
});
defs.set(prop.key, self.def_variable(key));
assignments.push(make_node(AST_VarDef, node, {
name: key,
value: prop.value
}));
});
if (assignments.length == 1) return assignments[0];
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
});
def.references.push(sym);
return sym;
}
}
});
return self.transform(tt);
});
// drop_side_effect_free()
// remove side-effect-free parts which only affects return value
(function(def){

View File

@ -0,0 +1,157 @@
issue_2377_1: {
options = {
evaluate: true,
inline: true,
hoist_props: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
mangle = {
toplevel: 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 n = 1, o = function(n) {
return n * n * n;
};
console.log(n, o(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,
}
mangle = {
toplevel: 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(n) {
return n * n * n;
}(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,
}
mangle = {
toplevel: 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: {
options = {
reduce_vars: true,
hoist_props: true,
toplevel: true,
unused: true,
}
mangle = {
toplevel: 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 o = {
a: 1,
b: 2,
};
for (var r in o) a++;
console.log(a, o.a);
}
expect_stdout: "2 1"
}
single_use: {
options = {
reduce_vars: true,
hoist_props: true,
toplevel: true,
unused: true,
}
mangle = {
toplevel: true,
}
input: {
var obj = {
bar: function() {
return 42;
},
};
console.log(obj.bar());
}
expect: {
console.log({
bar: function() {
return 42;
},
}.bar());
}
}

View File

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