aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/compress.js82
-rw-r--r--test/compress/hoist_props.js371
-rw-r--r--test/ufuzz.json6
3 files changed, 450 insertions, 9 deletions
diff --git a/lib/compress.js b/lib/compress.js
index 670a3b0d..9f410718 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,19 @@ 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);
+ 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;
if (!fixed || fixed instanceof AST_Node) return fixed;
return fixed();
@@ -2478,11 +2485,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 +2685,71 @@ 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 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()
// 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..2e8343a6
--- /dev/null
+++ b/test/compress/hoist_props.js
@@ -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"
+}
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": {