From 033d8d9405c82d18c221daae9049874f8924d9a2 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 6 Dec 2021 03:30:05 +0000 Subject: [PATCH] reduce memory pressure via bit fields (#5203) --- bin/uglifyjs | 12 ++++++--- lib/ast.js | 52 ++++++++++++++++++++++++++++++++++-- lib/compress.js | 12 ++++----- lib/output.js | 2 +- lib/parse.js | 5 ++-- lib/scope.js | 13 ++++++--- lib/utils.js | 18 +++++++++++++ test/compress/annotations.js | 4 +-- 8 files changed, 97 insertions(+), 21 deletions(-) diff --git a/bin/uglifyjs b/bin/uglifyjs index 6ee968f8..726cd881 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -10,7 +10,9 @@ var info = require("../package.json"); var path = require("path"); var UglifyJS = require("../tools/node"); -var skip_keys = [ "cname", "fixed", "inlined", "parent_scope", "scope", "uses_eval", "uses_with" ]; +var skip_keys = [ "cname", "fixed", "inlined", "length_read", "parent_scope", "scope" ]; +var truthy_keys = [ "optional", "pure", "terminal", "uses_arguments", "uses_eval", "uses_with" ]; + var files = {}; var options = {}; var short_forms = { @@ -430,7 +432,7 @@ function run() { case "thedef": return symdef(value); } - if (skip_key(key)) return; + if (skip_property(key, value)) return; if (value instanceof UglifyJS.AST_Token) return; if (value instanceof UglifyJS.Dictionary) return; if (value instanceof UglifyJS.AST_Node) { @@ -559,8 +561,10 @@ function parse_js(value, options, flag) { return options; } -function skip_key(key) { - return skip_keys.indexOf(key) >= 0; +function skip_property(key, value) { + return skip_keys.indexOf(key) >= 0 + // only skip truthy_keys if their value is falsy + || truthy_keys.indexOf(key) >= 0 && !value; } function symdef(def) { diff --git a/lib/ast.js b/lib/ast.js index 344d2eed..b21ea0cb 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -50,6 +50,8 @@ function DEFNODE(type, props, methods, base) { if (base && base.PROPS) props = props.concat(base.PROPS); var code = [ "return function AST_", type, "(props){", + // not essential, but speeds up compress by a few percent + "this._bits=0;", "if(props){", ]; props.forEach(function(prop) { @@ -135,6 +137,52 @@ var AST_Node = DEFNODE("Node", "start end", { }, }, null); +DEF_BITPROPS(AST_Node, [ + "_optimized", + "_squeezed", + // AST_Call + "call_only", + "collapse_scanning", + // AST_SymbolRef + "defined", + "evaluating", + "falsy", + // AST_SymbolRef + "in_arg", + // AST_Return + "in_bool", + // AST_SymbolRef + "is_undefined", + // AST_LambdaExpression + // AST_LambdaDefinition + "inlined", + // AST_Lambda + "length_read", + // AST_Yield + "nested", + // AST_Lambda + "new", + // AST_Call + // AST_PropAccess + "optional", + // AST_ClassProperty + "private", + // AST_Call + "pure", + // AST_Assign + "redundant", + // AST_ClassProperty + "static", + // AST_Call + // AST_PropAccess + "terminal", + "truthy", + // AST_Scope + "uses_eval", + // AST_Scope + "uses_with", +]); + (AST_Node.log_function = function(fn, verbose) { if (typeof fn != "function") { AST_Node.info = AST_Node.warn = noop; @@ -549,7 +597,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest uses_arguments", { argnames: "[(AST_DefaultValue|AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals", length_read: "[boolean/S] whether length property of this function is accessed", rest: "[(AST_Destructured|AST_SymbolFunarg)?] rest parameter, or null if absent", - uses_arguments: "[boolean/S] whether this function accesses the arguments array", + uses_arguments: "[boolean|number/S] whether this function accesses the arguments array", }, each_argname: function(visit) { var tw = new TreeWalker(function(node) { @@ -1295,7 +1343,7 @@ var AST_Call = DEFNODE("Call", "args expression optional pure terminal", { args: "[AST_Node*] array of arguments", expression: "[AST_Node] expression to invoke as function", optional: "[boolean] whether the expression is optional chaining", - pure: "[string/S] marker for side-effect-free call expression", + pure: "[boolean/S] marker for side-effect-free call expression", terminal: "[boolean] whether the chain has ended", }, walk: function(visitor) { diff --git a/lib/compress.js b/lib/compress.js index 1a1d969f..27878f1f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -898,7 +898,7 @@ Compressor.prototype.compress = function(node) { if (left.equivalent_to(right) && !left.has_side_effects(compressor)) { right.walk(tw); walk_prop(left); - node.__drop = true; + node.redundant = true; return true; } if (ld && right instanceof AST_LambdaExpression) { @@ -2274,7 +2274,7 @@ Compressor.prototype.compress = function(node) { stop_if_hit = if_hit; stop_after = after; can_replace = replace; - delete fn.collapse_scanning; + fn.collapse_scanning = false; if (!abort) return false; abort = false; return true; @@ -4919,7 +4919,7 @@ Compressor.prototype.compress = function(node) { } if (node instanceof AST_Scope && node !== fn) return true; })); - delete fn.evaluating; + fn.evaluating = false; if (!found) return; } return this; @@ -4935,7 +4935,7 @@ Compressor.prototype.compress = function(node) { }); fn.evaluating = true; val = val._eval(compressor, ignore_side_effects, cached, depth); - delete fn.evaluating; + fn.evaluating = false; } cached_args.forEach(function(node) { delete node._eval; @@ -8055,7 +8055,7 @@ Compressor.prototype.compress = function(node) { }) && all(fn.argnames, function(argname) { return !argname.match_symbol(return_false); }) && !(fn.rest && fn.rest.match_symbol(return_false)); - delete fn.new; + fn.new = false; return result; } function drop_class(self, compressor, first_in_statement) { @@ -11640,7 +11640,7 @@ Compressor.prototype.compress = function(node) { if (compressor.option("dead_code")) { if (self.left instanceof AST_PropAccess) { if (self.operator == "=") { - if (self.__drop) { + if (self.redundant) { var exprs = [ self.left.expression ]; if (self.left instanceof AST_Sub) exprs.push(self.left.property); exprs.push(self.right); diff --git a/lib/output.js b/lib/output.js index d7927dbc..5c8f5838 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1465,7 +1465,7 @@ function OutputStream(options) { parent = output.parent(level++); if (parent instanceof AST_Call && parent.expression === node) return; } while (parent instanceof AST_PropAccess && parent.expression === node); - output.print(typeof self.pure == "string" ? "/*" + self.pure + "*/" : "/*@__PURE__*/"); + output.print("/*@__PURE__*/"); } function print_call_args(self, output) { if (self.expression instanceof AST_Call || self.expression instanceof AST_Lambda) { diff --git a/lib/parse.js b/lib/parse.js index 1b23dc2e..982d9d48 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -2316,9 +2316,8 @@ function parse($TEXT, options) { var comments = start.comments_before; var i = HOP(start, "comments_before_length") ? start.comments_before_length : comments.length; while (--i >= 0) { - var match = /[@#]__PURE__/.exec(comments[i].value); - if (match) { - expr.pure = match[0]; + if (/[@#]__PURE__/.test(comments[i].value)) { + expr.pure = true; break; } } diff --git a/lib/scope.js b/lib/scope.js index a6026e59..be0dc08a 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -44,9 +44,8 @@ "use strict"; function SymbolDef(id, scope, orig, init) { + this._bits = 0; this.eliminated = 0; - this.exported = false; - this.global = false; this.id = id; this.init = init; this.mangled_name = null; @@ -55,7 +54,6 @@ function SymbolDef(id, scope, orig, init) { this.references = []; this.replaced = 0; this.scope = scope; - this.undeclared = false; } SymbolDef.prototype = { @@ -104,6 +102,15 @@ SymbolDef.prototype = { }, }; +DEF_BITPROPS(SymbolDef, [ + "const_redefs", + "cross_loop", + "direct_access", + "exported", + "global", + "undeclared", +]); + var unary_side_effects = makePredicate("delete ++ --"); function is_lhs(node, parent) { diff --git a/lib/utils.js b/lib/utils.js index 4c465844..57122575 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -273,3 +273,21 @@ function first_in_statement(stack, arrow, export_default) { return false; } } + +function DEF_BITPROPS(ctor, props) { + if (props.length > 31) throw new Error("Too many properties: " + props.length + "\n" + props.join(", ")); + props.forEach(function(name, pos) { + var mask = 1 << pos; + Object.defineProperty(ctor.prototype, name, { + get: function() { + return !!(this._bits & mask); + }, + set: function(val) { + if (val) + this._bits |= mask; + else + this._bits &= ~mask; + }, + }); + }); +} diff --git a/test/compress/annotations.js b/test/compress/annotations.js index e85c8d81..4fdbbd8a 100644 --- a/test/compress/annotations.js +++ b/test/compress/annotations.js @@ -442,9 +442,9 @@ compress_annotations_disabled_output_annotations_enabled: { } expect_exact: [ "/*@__PURE__*/a(3),", - "/*#__PURE__*/b(5),", + "/*@__PURE__*/b(5),", "c(side_effect),", - "/*#__PURE__*/d(effect());", + "/*@__PURE__*/d(effect());", ] }