[Pkg-javascript-commits] [uglifyjs] 248/491: implement `hoist_props` (#2396)

Jonas Smedegaard dr at jones.dk
Wed Feb 14 19:51:41 UTC 2018


This is an automated email from the git hooks/post-receive script.

js pushed a commit to annotated tag debian/3.3.10-1
in repository uglifyjs.

commit 4178289c382caf2eb3464390370dd1400a23468a
Author: Alex Lam S.L <alexlamsl at gmail.com>
Date:   Wed Oct 25 03:38:11 2017 +0800

    implement `hoist_props` (#2396)
    
    fixes #2377
---
 lib/compress.js              |  82 +++++++++-
 test/compress/hoist_props.js | 371 +++++++++++++++++++++++++++++++++++++++++++
 test/ufuzz.json              |   6 +-
 3 files changed, 450 insertions(+), 9 deletions(-)

diff --git a/lib/compress.js b/lib/compress.js
index 670a3b0..9f41071 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 0000000..2e8343a
--- /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 cb014b1..0d737d3 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": {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/uglifyjs.git



More information about the Pkg-javascript-commits mailing list