diff --git a/lib/compress.js b/lib/compress.js index 670a3b0d..592e3bba 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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){ diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js new file mode 100644 index 00000000..627798ad --- /dev/null +++ b/test/compress/hoist_props.js @@ -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()); + } +} diff --git a/test/ufuzz.json b/test/ufuzz.json index cb014b12..0d737d31 100644 --- a/test/ufuzz.json +++ b/test/ufuzz.json @@ -16,11 +16,9 @@ {}, { "compress": { - "toplevel": true + "hoist_props": true }, - "mangle": { - "toplevel": true - } + "toplevel": true }, { "compress": {