diff --git a/tests/optimizer/constructor-output.js b/tests/optimizer/constructor-output.js index 2321bb9751e5e..e2db2337010bf 100644 --- a/tests/optimizer/constructor-output.js +++ b/tests/optimizer/constructor-output.js @@ -24,16 +24,15 @@ function instantiate(o, p, q) { function i() { return g(10, 20) + h(30); } - - - function g(a, b) { return h(a + b); } + function h(a) { return a + 1; } + // EMSCRIPTEN_END_FUNCS return { diff --git a/tests/optimizer/test-js-optimizer-minifyLocals-output.js b/tests/optimizer/test-js-optimizer-minifyLocals-output.js index 41039196e2cfa..f7aab6b912bdf 100644 --- a/tests/optimizer/test-js-optimizer-minifyLocals-output.js +++ b/tests/optimizer/test-js-optimizer-minifyLocals-output.js @@ -19,8 +19,8 @@ function d(f, g, h) { } function l() { - a : while (1) { - b : while (1) { + a: while (1) { + b: while (1) { if (c()) { if (d()) { break a; @@ -36,5 +36,5 @@ function l() { } } } - a : while (1) {} + a: while (1) {} } diff --git a/tests/optimizer/wasm2js-output.js b/tests/optimizer/wasm2js-output.js index 695f11a1d892b..1e4f3539abf6e 100644 --- a/tests/optimizer/wasm2js-output.js +++ b/tests/optimizer/wasm2js-output.js @@ -40,9 +40,9 @@ function E(a, b, c) { return; } e = a + c | 0; - a : { + a: { if (!((a ^ b) & 3)) { - b : { + b: { if ((c | 0) < 1) { c = a; break b; @@ -66,7 +66,7 @@ function E(a, b, c) { } } a = e & -4; - c : { + c: { if (a >>> 0 < 64) { break c; } @@ -148,6 +148,7 @@ function E(a, b, c) { } } } + function H(a, b, c) { a = a | 0; b = b | 0; @@ -165,9 +166,9 @@ function H(a, b, c) { e = b + c | 0; i = 2; b = d + 16 | 0; - a : { - b : { - c : { + a: { + b: { + c: { if (!D(x(f[a + 60 >> 2], d + 16 | 0, 2, d + 12 | 0) | 0)) { while (1) { g = f[d + 12 >> 2]; @@ -216,12 +217,13 @@ function H(a, b, c) { z = d + 32 | 0; return a | 0; } + function B(a, b) { var e = 0, h = 0, i = 0, j = 0, k = 0; P(); j = 1024; e = f[b + 16 >> 2]; - a : { + a: { if (!e) { if (C(b)) { break a; @@ -232,7 +234,7 @@ function B(a, b) { if (e - i >>> 0 < a >>> 0) { return c[f[b + 36 >> 2]](b, 1024, a) | 0; } - b : { + b: { if (d[b + 75 | 0] < 0) { break b; } @@ -263,13 +265,14 @@ function B(a, b) { } return h; } + function L(a) { var b = 0, e = 0, g = 0; b = z - 16 | 0; z = b; d[b + 15 | 0] = 10; e = f[a + 16 >> 2]; - a : { + a: { if (!e) { if (C(a)) { break a; @@ -288,6 +291,7 @@ function L(a) { } z = b + 16 | 0; } + function J() { var a = 0, b = 0, c = 0; b = 1024; @@ -314,16 +318,17 @@ function J() { } return b - 1024 | 0; } + function K() { var a = 0, b = 0; a = f[260]; a; b = J(); - a : { + a: { if ((((M(b, a) | 0) != (b | 0) ? -1 : 0) | 0) < 0) { break a; } - b : { + b: { if (g[a + 75 | 0] == 10) { break b; } @@ -338,6 +343,7 @@ function K() { L(a); } } + function C(a) { var b = 0; b = g[a + 74 | 0]; @@ -355,11 +361,12 @@ function C(a) { f[a + 16 >> 2] = b + f[a + 48 >> 2]; return 0; } + function M(a, b) { var c = 0, d = 0; c = a; d = c; - a : { + a: { if (f[b + 76 >> 2] <= -1) { b = B(c, b); break a; @@ -371,6 +378,7 @@ function M(a, b) { } return b; } + function F(a, b, c, d) { a = a | 0; b = b | 0; @@ -379,6 +387,7 @@ function F(a, b, c, d) { A = 0; return 0; } + function D(a) { if (!a) { return 0; @@ -386,24 +395,25 @@ function D(a) { f[300] = a; return -1; } + function N(a, b) { a = a | 0; b = b | 0; K(); return 0; } + function I(a) { a = a | 0; return u() | 0; } + function G(a) { a = a | 0; return 0; } -function O() {} - - +function O() {} // EMSCRIPTEN_END_FUNCS diff --git a/tests/test_other.py b/tests/test_other.py index 461ee3540511d..2743bfd8f6a7c 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1860,7 +1860,8 @@ def test_js_optimizer(self): 'growableHeap', 'unsignPointers', 'asanify', - 'safeHeap' + 'safeHeap', + 'minifyLocals', ] for input, expected, passes in [ (path_from_root('tests', 'optimizer', 'test-js-optimizer-minifyGlobals.js'), open(path_from_root('tests', 'optimizer', 'test-js-optimizer-minifyGlobals-output.js')).read(), diff --git a/tools/acorn-optimizer.js b/tools/acorn-optimizer.js index e2808c51e4cb4..b384013767f43 100644 --- a/tools/acorn-optimizer.js +++ b/tools/acorn-optimizer.js @@ -1334,6 +1334,170 @@ function safeHeap(ast) { }); } +// Name minification + +var RESERVED = new Set(['do', 'if', 'in', 'for', 'new', 'try', 'var', 'env', 'let', 'case', 'else', 'enum', 'void', 'this', 'void', 'with']); +var VALID_MIN_INITS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$'; +var VALID_MIN_LATERS = VALID_MIN_INITS + '0123456789'; + +var minifiedNames = []; +var minifiedState = [0]; + +// Make sure the nth index in minifiedNames exists. Done 100% deterministically. +function ensureMinifiedNames(n) { + while (minifiedNames.length < n+1) { + // generate the current name + var name = VALID_MIN_INITS[minifiedState[0]]; + for (var i = 1; i < minifiedState.length; i++) { + name += VALID_MIN_LATERS[minifiedState[i]]; + } + if (!RESERVED.has(name)) minifiedNames.push(name); + // increment the state + var i = 0; + while (1) { + minifiedState[i]++; + if (minifiedState[i] < (i === 0 ? VALID_MIN_INITS : VALID_MIN_LATERS).length) break; + // overflow + minifiedState[i] = 0; + i++; + // will become 0 after increment in next loop head + if (i === minifiedState.length) minifiedState.push(-1); + } + } +} + +function minifyLocals(ast) { + // We are given a mapping of global names to their minified forms. + assert(extraInfo && extraInfo.globals); + + for (var fun of ast.body) { + if (!fun.type === 'FunctionDeclaration') { + continue; + } + // Find the list of local names, including params. + var localNames = new Set(); + for (var param of fun.params) { + localNames.add(param.name); + } + simpleWalk(fun, { + VariableDeclaration(node, c) { + for (var dec of node.declarations) { + localNames.add(dec.id.name) + } + } + }); + + function isLocalName(name) { + return localNames.has(name); + } + + // Names old to new names. + var newNames = new Map(); + + // The names in use, that must not be collided with. + var usedNames = new Set; + + // Put the function name aside. We don't want to traverse it as it is not + // in the scope of itself. + var funId = fun.id; + fun.id = null; + + // Find all the globals that we need to minify using pre-assigned names. + // Don't actually minify them yet as that might interfere with local + // variable names; just mark them as used, and what their new name will be. + simpleWalk(fun, { + Identifier(node, c) { + var name = node.name; + if (!isLocalName(name)) { + var minified = extraInfo.globals[name]; + if (minified){ + newNames.set(name, minified); + usedNames.add(minified); + } + } + }, + CallExpression(node, c) { + // We should never call a local name, as in asm.js-style code our + // locals are just numbers, not functions; functions are all declared + // in the outer scope. If a local is called, that is a bug. + if (node.callee.type === 'Identifier') { + assert(!isLocalName(node.callee.name), 'cannot call a local'); + } + } + }); + + // The first time we encounter a local name, we assign it a/ minified name + // that's not currently in use. Allocating on demand means they're processed + // in a predictable order, which is very handy for testing/debugging + // purposes. + var nextMinifiedName = 0; + + function getNextMinifiedName() { + while (1) { + ensureMinifiedNames(nextMinifiedName); + var minified = minifiedNames[nextMinifiedName++]; + // TODO: we can probably remove !isLocalName here + if (!usedNames.has(minified) && !isLocalName(minified)) { + return minified; + } + } + } + + // Traverse and minify all names. First the function parameters. + for (var param of fun.params) { + var minified = getNextMinifiedName(); + newNames.set(param.name, minified); + param.name = minified; + } + + // Label minification is done in a separate namespace. + var labelNames = new Map(); + var nextMinifiedLabel = 0; + function getNextMinifiedLabel() { + ensureMinifiedNames(nextMinifiedLabel); + return minifiedNames[nextMinifiedLabel++]; + } + + // Finally, the function body. + recursiveWalk(fun, { + Identifier(node) { + var name = node.name; + if (newNames.has(name)) { + node.name = newNames.get(name); + } else if (isLocalName(name)) { + minified = getNextMinifiedName(); + newNames.set(name, minified); + node.name = minified; + } + }, + LabeledStatement(node, c) { + if (!labelNames.has(node.label.name)) { + labelNames.set(node.label.name, getNextMinifiedLabel()); + } + node.label.name = labelNames.get(node.label.name); + c(node.body); + }, + BreakStatement(node, c) { + if (node.label) { + node.label.name = labelNames.get(node.label.name); + } + }, + ContinueStatement(node, c) { + if (node.label) { + node.label.name = labelNames.get(node.label.name); + } + }, + }); + + // Finally, the function name, after restoring it. + fun.id = funId; + assert(extraInfo.globals.hasOwnProperty(fun.id.name)); + fun.id.name = extraInfo.globals[fun.id.name]; + } +} + +// Utilities + function reattachComments(ast, comments) { var symbols = []; @@ -1444,9 +1608,11 @@ var registry = { applyDCEGraphRemovals: applyDCEGraphRemovals, minifyWhitespace: function() { minifyWhitespace = true }, noPrint: function() { noPrint = true }, + last: function() {}, // TODO: remove 'last' in the python driver code dump: function() { dump(ast) }, growableHeap: growableHeap, unsignPointers: unsignPointers, + minifyLocals: minifyLocals, asanify: asanify, safeHeap: safeHeap, }; diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index a23e91420cbe8..76fd09fdb371a 100755 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -515,135 +515,6 @@ function ensureMinifiedNames(n) { // make sure the nth index in minifiedNames ex } } -function minifyLocals(ast) { - assert(extraInfo && extraInfo.globals); - - traverseGeneratedFunctions(ast, function(fun, type) { - // Find the list of local names, including params. - if (asm) { - // TODO: we can avoid modifying at all here - we just need a list of local vars+params - var asmData = normalizeAsm(fun); - denormalizeAsm(fun, asmData); - } else { - // non-asm.js code - scan the whole function, which is inefficient - var localNames = {}; - for (var param of fun[2]) { - localNames[param] = 1; - } - traverse(fun, function(node, type) { - if (type === 'var') { - node[1].forEach(function(defn) { - var name = defn[0]; - localNames[name] = 1; - }); - } - }); - } - - var newNames = {}; - var usedNames = {}; - - // Find all the globals that we need to minify using - // pre-assigned names. Don't actually minify them yet - // as that might interfere with local variable names. - function isLocalName(name) { - if (asm) { - return name in asmData.vars || name in asmData.params; - } else { - return Object.prototype.hasOwnProperty.call(localNames, name); - } - } - traverse(fun, function(node, type) { - if (type === 'name') { - var name = node[1]; - if (!isLocalName(name)) { - var minified = extraInfo.globals[name]; - if (minified){ - newNames[name] = minified; - usedNames[minified] = 1; - } - } - } else if (type === 'call') { - // We should never call a local name, as in asm.js-style code our - // locals are just numbers, not functions; functions are all declared - // in the outer scope. If a local is called, that is a bug. - if (node[1][0] === 'name') { - var name = node[1][1]; - assert(!isLocalName(name), 'cannot call a local'); - } - } - }); - - // The first time we encounter a local name, we assign it a - // minified name that's not currently in use. Allocating on - // demand means they're processed in a predictable order, - // which is very handy for testing/debugging purposes. - var nextMinifiedName = 0; - function getNextMinifiedName() { - var minified; - while (1) { - ensureMinifiedNames(nextMinifiedName); - minified = minifiedNames[nextMinifiedName++]; - // TODO: we can probably remove !isLocalName here - if (!usedNames[minified] && !isLocalName(minified)) { - return minified; - } - } - } - - // We can also minify loop labels, using a separate namespace - // to the variable declarations. - var newLabels = {}; - var nextMinifiedLabel = 0; - function getNextMinifiedLabel() { - ensureMinifiedNames(nextMinifiedLabel); - return minifiedNames[nextMinifiedLabel++]; - } - - // Traverse and minify all names. - assert(extraInfo.globals.hasOwnProperty(fun[1])); - fun[1] = extraInfo.globals[fun[1]]; - assert(fun[1] && typeof fun[1] === 'string'); - if (fun[2]) { - for (var i = 0; i < fun[2].length; i++) { - var minified = getNextMinifiedName(); - newNames[fun[2][i]] = minified; - fun[2][i] = minified; - } - } - traverse(fun[3], function(node, type) { - if (type === 'name') { - var name = node[1]; - var minified = newNames[name]; - if (minified) { - node[1] = minified; - } else if (isLocalName(name)) { - minified = getNextMinifiedName(); - newNames[name] = minified; - node[1] = minified; - } - } else if (type === 'var') { - node[1].forEach(function(defn) { - var name = defn[0]; - if (!(name in newNames)) { - newNames[name] = getNextMinifiedName(); - } - defn[0] = newNames[name]; - }); - } else if (type === 'label') { - if (!newLabels[node[1]]) { - newLabels[node[1]] = getNextMinifiedLabel(); - } - node[1] = newLabels[node[1]]; - } else if (type === 'break' || type === 'continue') { - if (node[1]) { - node[1] = newLabels[node[1]]; - } - } - }); - }); -} - // Passes table var minifyWhitespace = false, printMetadata = true, asm = false, @@ -655,7 +526,6 @@ var passes = { dumpAst: dumpAst, dumpSrc: dumpSrc, minifyGlobals: minifyGlobals, - minifyLocals: minifyLocals, noop: function() {}, // flags diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index 071154b9e4a70..bf91cb7c27798 100755 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -34,6 +34,7 @@ def path_from_root(*pathelems): JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js') +ACORN_OPTIMIZER = path_from_root('tools', 'acorn-optimizer.js') NUM_CHUNKS_PER_CORE = 3 MIN_CHUNK_SIZE = int(os.environ.get('EMCC_JSOPT_MIN_CHUNK_SIZE') or 512 * 1024) # configuring this is just for debugging purposes @@ -329,7 +330,7 @@ def write_chunk(chunk, i): with ToolchainProfiler.profile_block('run_optimizer'): if len(filenames): - commands = [config.NODE_JS + [JS_OPTIMIZER, f, 'noPrintMetadata'] + passes for f in filenames] + commands = [config.NODE_JS + [ACORN_OPTIMIZER, f] + passes for f in filenames] cores = min(cores, len(filenames)) if len(chunks) > 1 and cores >= 2: