From 57b4f2cc2a956533956550a20aca61240d370970 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Sat, 18 Jun 2022 00:46:29 -0400 Subject: [PATCH] Optimize property access evaluation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Given the input `a.b.c.d.e.f`, the previous code would call `_eval` 57 times: ``` 1 a.b 2 a.b.c 3 a.b 4 a.b 5 a.b.c.d 6 a.b.c 7 a.b 8 a.b 9 a.b.c 10 a.b 11 a.b 12 a.b.c.d.e 13 a.b.c.d 14 a.b.c 15 a.b 16 a.b 17 a.b.c 18 a.b 19 a.b 20 a.b.c.d 21 a.b.c 22 a.b 23 a.b 24 a.b.c 25 a.b 26 a.b 27 a.b.c.d.e.f 28 a.b.c.d.e 29 a.b.c.d 30 a.b.c 31 a.b 32 a.b 33 a.b.c 34 a.b 35 a.b 36 a.b.c.d 37 a.b.c 38 a.b 39 a.b 40 a.b.c 41 a.b 42 a.b 43 a.b.c.d.e 44 a.b.c.d 45 a.b.c 46 a.b 47 a.b 48 a.b.c 49 a.b 50 a.b 51 a.b.c.d 52 a.b.c 53 a.b 54 a.b 55 a.b.c 56 a.b 57 a.b ``` The first optimization is to only call `expression._eval(…)` a single time per evaluation of a `AST_PropAccess`. We've already called it to determine if we're a nullish, so there's no reason to do it a second time in the unsafe path. This alone brings us down to 15 calls: ``` 1 a.b 2 a.b.c 3 a.b 4 a.b.c.d 5 a.b.c 6 a.b 7 a.b.c.d.e 8 a.b.c.d 9 a.b.c 10 a.b 11 a.b.c.d.e.f 12 a.b.c.d.e 13 a.b.c.d 14 a.b.c 15 a.b ``` But, we can do even better by avoiding the evaluation mid–chain. There's no reason to evaluation unless the evaluation comes from the top of the chain (the rightmost property access). If anything can be optimized, it can optimize while we descend one time from that point. This brings us to 6 evaluations: ``` 1 a.b 2 a.b.c.d.e.f 3 a.b.c.d.e 4 a.b.c.d 5 a.b.c 6 a.b ``` (I'm not sure why that first one happens) --- lib/compress/evaluate.js | 23 +++++++++++------------ lib/compress/index.js | 6 ++++++ test/compress/evaluate.js | 14 ++++++++++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lib/compress/evaluate.js b/lib/compress/evaluate.js index 7e1931fa5..9cb432de7 100644 --- a/lib/compress/evaluate.js +++ b/lib/compress/evaluate.js @@ -342,7 +342,7 @@ const regexp_flags = new Set([ ]); def_eval(AST_PropAccess, function (compressor, depth) { - const obj = this.expression._eval(compressor, depth); + let obj = this.expression._eval(compressor, depth + 1); if (obj === nullish || (this.optional && obj == null)) return nullish; if (compressor.option("unsafe")) { var key = this.property; @@ -352,7 +352,6 @@ def_eval(AST_PropAccess, function (compressor, depth) { return this; } var exp = this.expression; - var val; if (is_undeclared_ref(exp)) { var aa; @@ -369,29 +368,29 @@ def_eval(AST_PropAccess, function (compressor, depth) { } if (!is_pure_native_value(exp.name, key)) return this; - val = global_objs[exp.name]; + obj = global_objs[exp.name]; } else { - val = exp._eval(compressor, depth + 1); - if (val instanceof RegExp) { + if (obj instanceof RegExp) { if (key == "source") { - return regexp_source_fix(val.source); + return regexp_source_fix(obj.source); } else if (key == "flags" || regexp_flags.has(key)) { - return val[key]; + return obj[key]; } } - if (!val || val === exp || !HOP(val, key)) + if (!obj || obj === exp || !HOP(obj, key)) return this; - if (typeof val == "function") + + if (typeof obj == "function") switch (key) { case "name": - return val.node.name ? val.node.name.name : ""; + return obj.node.name ? obj.node.name.name : ""; case "length": - return val.node.length_property(); + return obj.node.length_property(); default: return this; } } - return val[key]; + return obj[key]; } return this; }); diff --git a/lib/compress/index.js b/lib/compress/index.js index ab8d7cf83..1f7529c41 100644 --- a/lib/compress/index.js +++ b/lib/compress/index.js @@ -3799,6 +3799,12 @@ def_optimize(AST_Dot, function(self, compressor) { const sub = self.flatten_object(self.property, compressor); if (sub) return sub.optimize(compressor); } + + if (self.expression instanceof AST_PropAccess + && parent instanceof AST_PropAccess) { + return self; + } + let ev = self.evaluate(compressor); if (ev !== self) { ev = make_node_from_constant(ev, self).optimize(compressor); diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index a8b1a9535..970c3edd7 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1851,3 +1851,17 @@ regexp_property_eval: { console.log(false); } } + + +unsafe_deep_chain: { + options = { + evaluate: true, + unsafe: true, + } + input: { + a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z; + } + expect: { + a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z; + } +}