diff options
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 283 |
1 files changed, 188 insertions, 95 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 07317e0a..67cd8066 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -143,17 +143,17 @@ var FALSE_NODE = ['unary-prefix', '!', ['num', 1]]; var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS'; var generatedFunctions = false; // whether we have received only generated functions -var minifierInfo = null; +var extraInfo = null; function srcToAst(src) { return uglify.parser.parse(src); } -function astToSrc(ast, compress) { +function astToSrc(ast, minifyWhitespace) { return uglify.uglify.gen_code(ast, { ascii_only: true, - beautify: !compress, - indent_level: 2 + beautify: !minifyWhitespace, + indent_level: 1 }); } @@ -429,19 +429,47 @@ function simplifyExpressionsPre(ast) { // 'useful' mathops already |0 anyhow. function simplifyBitops(ast) { - var SAFE_BINARY_OPS = set('+', '-', '*'); // division is unsafe as it creates non-ints in JS; mod is unsafe as signs matter so we can't remove |0's + var SAFE_BINARY_OPS; + if (asm) { + SAFE_BINARY_OPS = set('+', '-'); // division is unsafe as it creates non-ints in JS; mod is unsafe as signs matter so we can't remove |0's; mul does not nest with +,- in asm + } else { + SAFE_BINARY_OPS = set('+', '-', '*'); + } + var COERCION_REQUIRING_OPS = set('sub', 'unary-prefix'); // ops that in asm must be coerced right away + var COERCION_REQUIRING_BINARIES = set('*', '/', '%'); // binary ops that in asm must be coerced var ZERO = ['num', 0]; - var rerun = true; - while (rerun) { - rerun = false; - traverse(ast, function process(node, type, stack) { - if (type == 'binary' && node[1] == '|') { - if (node[2][0] == 'num' && node[3][0] == 'num') { - return ['num', node[2][1] | node[3][1]]; - } else if (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO)) { + + function removeMultipleOrZero() { + var rerun = true; + while (rerun) { + rerun = false; + traverse(ast, function process(node, type, stack) { + if (type == 'binary' && node[1] == '|') { + if (node[2][0] == 'num' && node[3][0] == 'num') { + return ['num', node[2][1] | node[3][1]]; + } + var go = false; + if (jsonCompare(node[2], ZERO)) { + // canonicalize order + var temp = node[3]; + node[3] = node[2]; + node[2] = temp; + go = true; + } else if (jsonCompare(node[3], ZERO)) { + go = true; + } + if (!go) { + stack.push(1); + return; + } // We might be able to remove this correction for (var i = stack.length-1; i >= 0; i--) { - if (stack[i] == 1) { + if (stack[i] >= 1) { + if (asm) { + if (stack[stack.length-1] < 2 && node[2][0] == 'call') break; // we can only remove multiple |0s on these + if (stack[stack.length-1] < 1 && (node[2][0] in COERCION_REQUIRING_OPS || + (node[2][0] == 'binary' && node[2][1] in COERCION_REQUIRING_BINARIES))) break; // we can remove |0 or >>2 + } // we will replace ourselves with the non-zero side. Recursively process that node. var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other; // replace node in-place @@ -453,23 +481,23 @@ function simplifyExpressionsPre(ast) { return process(result, result[0], stack); } else if (stack[i] == -1) { break; // Too bad, we can't - } else if (asm) { - break; // we must keep a coercion right on top of a heap access in asm mode } } + stack.push(2); // From here on up, no need for this kind of correction, it's done at the top + // (Add this at the end, so it is only added if we did not remove it) + } else if (type == 'binary' && node[1] in USEFUL_BINARY_OPS) { + stack.push(1); + } else if ((type == 'binary' && node[1] in SAFE_BINARY_OPS) || type == 'num' || type == 'name') { + stack.push(0); // This node is safe in that it does not interfere with this optimization + } else { + stack.push(-1); // This node is dangerous! Give up if you see this before you see '1' } - stack.push(1); // From here on up, no need for this kind of correction, it's done at the top - // (Add this at the end, so it is only added if we did not remove it) - } else if (type == 'binary' && node[1] in USEFUL_BINARY_OPS) { - stack.push(1); - } else if ((type == 'binary' && node[1] in SAFE_BINARY_OPS) || type == 'num' || type == 'name') { - stack.push(0); // This node is safe in that it does not interfere with this optimization - } else { - stack.push(-1); // This node is dangerous! Give up if you see this before you see '1' - } - }, null, []); + }, null, []); + } } + removeMultipleOrZero(); + // & and heap-related optimizations var heapBits, heapUnsigned; @@ -480,7 +508,7 @@ function simplifyExpressionsPre(ast) { return true; } - var hasTempDoublePtr = false; + var hasTempDoublePtr = false, rerunOrZeroPass = false; traverse(ast, function(node, type) { if (type == 'name') { @@ -515,7 +543,6 @@ function simplifyExpressionsPre(ast) { node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' && node[2][2][0] == 'sub' && node[2][2][1][0] == 'name') { // collapse HEAPU?8[..] << 24 >> 24 etc. into HEAP8[..] | 0 - // TODO: run this before | 0 | 0 removal, because we generate | 0 var amount = node[3][1]; var name = node[2][2][1][1]; if (amount == node[2][3][1] && parseHeap(name)) { @@ -524,6 +551,7 @@ function simplifyExpressionsPre(ast) { node[1] = '|'; node[2] = node[2][2]; node[3][1] = 0; + rerunOrZeroPass = true; return node; } } @@ -556,6 +584,8 @@ function simplifyExpressionsPre(ast) { } }); + if (rerunOrZeroPass) removeMultipleOrZero(); + if (asm) { if (hasTempDoublePtr) { traverse(ast, function(node, type) { @@ -707,18 +737,11 @@ function simplifyExpressionsPre(ast) { } function asmOpts(fun) { - // 1. Add final returns when necessary - // 2. Remove unneeded coercions on function calls that have no targets (eliminator removed it) + // Add final returns when necessary var returnType = null; traverse(fun, function(node, type) { if (type == 'return' && node[1]) { returnType = detectAsmCoercion(node[1]); - } else if (type == 'stat') { - var inner = node[1]; - if ((inner[0] == 'binary' && inner[1] in ASSOCIATIVE_BINARIES && inner[2][0] == 'call' && inner[3][0] == 'num') || - (inner[0] == 'unary-prefix' && inner[1] == '+' && inner[2][0] == 'call')) { - node[1] = inner[2]; - } } }); // Add a final return if one is missing. @@ -1093,11 +1116,15 @@ function simplifyNotCompsDirect(node) { return node[2][2]; } } - return node; + if (!simplifyNotCompsPass) return node; } +var simplifyNotCompsPass = false; + function simplifyNotComps(ast) { + simplifyNotCompsPass = true; traverse(ast, simplifyNotCompsDirect); + simplifyNotCompsPass = false; } function simplifyExpressionsPost(ast) { @@ -1607,7 +1634,7 @@ function denormalizeAsm(func, data) { // Very simple 'registerization', coalescing of variables into a smaller number, // as part of minification. Globals-level minification began in a previous pass, -// we receive minifierInfo which tells us how to rename globals. (Only in asm.js.) +// we receive extraInfo which tells us how to rename globals. (Only in asm.js.) // // We do not optimize when there are switches, so this pass only makes sense with // relooping. @@ -1649,7 +1676,7 @@ function registerize(ast) { } }); vacuum(fun); - if (minifierInfo) { + if (extraInfo) { assert(asm); var usedGlobals = {}; var nextLocal = 0; @@ -1657,7 +1684,7 @@ function registerize(ast) { traverse(fun, function(node, type) { if (type == 'name') { var name = node[1]; - var minified = minifierInfo.globals[name]; + var minified = extraInfo.globals[name]; if (minified) { assert(!localVars[name], name); // locals must not shadow globals, or else we don't know which is which if (localVars[minified]) { @@ -1690,8 +1717,8 @@ function registerize(ast) { } } }); - assert(fun[1] in minifierInfo.globals, fun[1]); - fun[1] = minifierInfo.globals[fun[1]]; + assert(fun[1] in extraInfo.globals, fun[1]); + fun[1] = extraInfo.globals[fun[1]]; assert(fun[1]); var nextRegName = 0; } @@ -1699,14 +1726,14 @@ function registerize(ast) { function getNewRegName(num, name) { if (!asm) return 'r' + num; var type = asmData.vars[name]; - if (!minifierInfo) { + if (!extraInfo) { var ret = (type ? 'd' : 'i') + num; regTypes[ret] = type; return ret; } // find the next free minified name that is not used by a global that shows up in this function - while (nextRegName < minifierInfo.names.length) { - var ret = minifierInfo.names[nextRegName++]; + while (nextRegName < extraInfo.names.length) { + var ret = extraInfo.names[nextRegName++]; if (!usedGlobals[ret]) { regTypes[ret] = type; return ret; @@ -2411,7 +2438,10 @@ function eliminate(ast, memSafe) { } traverseInOrder(node); } + //var eliminationLimit = 0; // used to debugging purposes function doEliminate(name, node) { + //if (eliminationLimit == 0) return; + //eliminationLimit--; //printErr('elim!!!!! ' + name); // yes, eliminate! varsToRemove[name] = 2; // both assign and var definitions can have other vars we must clean up @@ -2560,7 +2590,13 @@ function eliminate(ast, memSafe) { } if (looperUsed) return; } + for (var l = 0; l < helpers.length; l++) { + for (var k = 0; k < helpers.length; k++) { + if (l != k && helpers[l] == helpers[k]) return; // it is complicated to handle a shared helper, abort + } + } // hurrah! this is safe to do + //printErr("ELIM LOOP VAR " + JSON.stringify(loopers) + ' :: ' + JSON.stringify(helpers)); for (var l = 0; l < loopers.length; l++) { var looper = loopers[l]; var helper = helpers[l]; @@ -2590,48 +2626,50 @@ function eliminate(ast, memSafe) { } }); - // A class for optimizing expressions. We know that it is legitimate to collapse - // 5+7 in the generated code, as it will always be numerical, for example. XXX do we need this? here? - function ExpressionOptimizer(node) { - this.node = node; - - this.run = function() { - traverse(this.node, function(node, type) { - if (type === 'binary' && node[1] == '+') { - var names = []; - var num = 0; - var has_num = false; - var fail = false; - traverse(node, function(subNode, subType) { - if (subType === 'binary') { - if (subNode[1] != '+') { + if (!asm) { // TODO: deprecate in non-asm too + // A class for optimizing expressions. We know that it is legitimate to collapse + // 5+7 in the generated code, as it will always be numerical, for example. XXX do we need this? here? + function ExpressionOptimizer(node) { + this.node = node; + + this.run = function() { + traverse(this.node, function(node, type) { + if (type === 'binary' && node[1] == '+') { + var names = []; + var num = 0; + var has_num = false; + var fail = false; + traverse(node, function(subNode, subType) { + if (subType === 'binary') { + if (subNode[1] != '+') { + fail = true; + return false; + } + } else if (subType === 'name') { + names.push(subNode[1]); + return; + } else if (subType === 'num') { + num += subNode[1]; + has_num = true; + return; + } else { fail = true; return false; } - } else if (subType === 'name') { - names.push(subNode[1]); - return; - } else if (subType === 'num') { - num += subNode[1]; - has_num = true; - return; - } else { - fail = true; - return false; - } - }); - if (!fail && has_num) { - var ret = ['num', num]; - for (var i = 0; i < names.length; i++) { - ret = ['binary', '+', ['name', names[i]], ret]; + }); + if (!fail && has_num) { + var ret = ['num', num]; + for (var i = 0; i < names.length; i++) { + ret = ['binary', '+', ['name', names[i]], ret]; + } + return ret; } - return ret; } - } - }); - }; + }); + }; + } + new ExpressionOptimizer(ast).run(); } - new ExpressionOptimizer(ast).run(); } function eliminateMemSafe(ast) { @@ -2652,16 +2690,16 @@ function minifyGlobals(ast) { var vars = node[1]; for (var i = 0; i < vars.length; i++) { var name = vars[i][0]; - assert(next < minifierInfo.names.length); - vars[i][0] = minified[name] = minifierInfo.names[next++]; + assert(next < extraInfo.names.length); + vars[i][0] = minified[name] = extraInfo.names[next++]; } } }); // add all globals in function chunks, i.e. not here but passed to us - for (var i = 0; i < minifierInfo.globals.length; i++) { - name = minifierInfo.globals[i]; - assert(next < minifierInfo.names.length); - minified[name] = minifierInfo.names[next++]; + for (var i = 0; i < extraInfo.globals.length; i++) { + name = extraInfo.globals[i]; + assert(next < extraInfo.names.length); + minified[name] = extraInfo.names[next++]; } // apply minification traverse(ast, function(node, type) { @@ -2672,9 +2710,64 @@ function minifyGlobals(ast) { } } }); - suffix = '// MINIFY_INFO:' + JSON.stringify(minified); + suffix = '// EXTRA_INFO:' + JSON.stringify(minified); } +// Relocation pass for a shared module (for the functions part of the module) +// +// 1. Replace function names with alternate names as defined (to avoid colliding with +// names in the main module we are being linked to) +// 2. Hardcode function table offsets from F_BASE+x to const+x if x is a variable, or +// the constant sum of the base + offset +// 3. Hardcode heap offsets from H_BASE as well +function relocate(ast) { + assert(asm); // we also assume we are normalized + + var replacements = extraInfo.replacements; + var fBase = extraInfo.fBase; + var hBase = extraInfo.hBase; + + traverse(ast, function(node, type) { + switch(type) { + case 'name': case 'defun': { + var rep = replacements[node[1]]; + if (rep) node[1] = rep; + break; + } + case 'binary': { + if (node[1] == '+' && node[2][0] == 'name') { + var base = null; + if (node[2][1] == 'F_BASE') { + base = fBase; + } else if (node[2][1] == 'H_BASE') { + base = hBase; + } + if (base) { + var other = node[3]; + if (other[0] == 'num') { + other[1] += base; + return other; + } else { + node[2] = ['num', base]; + } + } + } + break; + } + case 'var': { + var vars = node[1]; + for (var i = 0; i < vars.length; i++) { + var name = vars[i][0]; + assert(!(name in replacements)); // cannot shadow functions we are replacing TODO: fix that + } + break; + } + } + }); +} + +// Last pass utilities + // Change +5 to DOT$ZERO(5). We then textually change 5 to 5.0 (uglify's ast cannot differentiate between 5 and 5.0 directly) function prepDotZero(ast) { traverse(ast, function(node, type) { @@ -2721,7 +2814,7 @@ function asmLoopOptimizer(ast) { // Passes table -var compress = false, printMetadata = true, asm = false, last = false; +var minifyWhitespace = false, printMetadata = true, asm = false, last = false; var passes = { dumpAst: dumpAst, @@ -2739,11 +2832,11 @@ var passes = { eliminate: eliminate, eliminateMemSafe: eliminateMemSafe, minifyGlobals: minifyGlobals, - compress: function() { compress = true }, + relocate: relocate, + minifyWhitespace: function() { minifyWhitespace = true }, noPrintMetadata: function() { printMetadata = false }, asm: function() { asm = true }, last: function() { last = true }, - closure: function(){} // handled in python }; // Main @@ -2754,9 +2847,9 @@ var src = read(arguments_[0]); var ast = srcToAst(src); //printErr(JSON.stringify(ast)); throw 1; generatedFunctions = src.indexOf(GENERATED_FUNCTIONS_MARKER) >= 0; -var minifierInfoStart = src.indexOf('// MINIFY_INFO:') -if (minifierInfoStart > 0) minifierInfo = JSON.parse(src.substr(minifierInfoStart + 15)); -//printErr(JSON.stringify(minifierInfo)); +var extraInfoStart = src.indexOf('// EXTRA_INFO:') +if (extraInfoStart > 0) extraInfo = JSON.parse(src.substr(extraInfoStart + 14)); +//printErr(JSON.stringify(extraInfo)); arguments_.slice(1).forEach(function(arg) { passes[arg](ast); @@ -2765,7 +2858,7 @@ if (asm && last) { asmLoopOptimizer(ast); prepDotZero(ast); } -var js = astToSrc(ast, compress), old; +var js = astToSrc(ast, minifyWhitespace), old; if (asm && last) { js = fixDotZero(js); } |