diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-03-09 20:01:14 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-03-09 20:01:14 -0800 |
commit | 0f538ae57bb65af11efb7372661ba7c929c98d61 (patch) | |
tree | 091e8ed588207b317af07455bac65654b183245a /tools/js-optimizer.js | |
parent | 5c5a9dd4bf35d95ffe83e655fe2a237241f0a96a (diff) | |
parent | 4794e95b9ba2ab9104d6da7b0a6c7bf31c2f812a (diff) |
Merge branch 'asm_minifier' into incoming
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 175 |
1 files changed, 151 insertions, 24 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 9c744fa3..834c99e3 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -143,6 +143,8 @@ 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; + function srcToAst(src) { return uglify.parser.parse(src); } @@ -218,12 +220,15 @@ function traverseGenerated(ast, pre, post, stack) { function traverseGeneratedFunctions(ast, callback) { assert(generatedFunctions); - traverse(ast, function(node) { - if (node[0] == 'defun') { - callback(node); - return null; + if (ast[0] == 'toplevel') { + var stats = ast[1]; + for (var i = 0; i < stats.length; i++) { + var curr = stats[i]; + if (curr[0] == 'defun') callback(curr); } - }); + } else if (ast[0] == 'defun') { + callback(ast); + } } // Walk the ast in a simple way, with an understanding of which JS variables are defined) @@ -535,9 +540,31 @@ function simplifyExpressionsPre(ast) { }); } + function addFinalReturns(ast) { + traverseGeneratedFunctions(ast, function(fun) { + var returnType = null; + traverse(fun, function(node, type) { + if (type == 'return' && node[1]) { + returnType = detectAsmCoercion(node[1]); + } + }); + // Add a final return if one is missing. + if (returnType !== null) { + var stats = getStatements(fun); + var last = stats[stats.length-1]; + if (last[0] != 'return') { + var returnValue = ['num', 0]; + if (returnType == ASM_DOUBLE) returnValue = ['unary-prefix', '+', returnValue]; + stats.push(['return', returnValue]); + } + } + }); + } + simplifyBitops(ast); joinAdditions(ast); // simplifyZeroComp(ast); TODO: investigate performance + if (asm) addFinalReturns(ast); } // In typed arrays mode 2, we can have @@ -1300,7 +1327,9 @@ function normalizeAsm(func) { var node = stats[i]; if (node[0] != 'stat' || node[1][0] != 'assign' || node[1][2][0] != 'name') break; node = node[1]; - data.params[node[2][1]] = detectAsmCoercion(node[3]); + var name = node[2][1]; + if (func[2] && func[2].indexOf(name) < 0) break; // not an assign into a parameter, but a global + data.params[name] = detectAsmCoercion(node[3]); stats[i] = emptyNode(); i++; } @@ -1393,7 +1422,10 @@ function denormalizeAsm(func, data) { //printErr('denormalized \n\n' + astToSrc(func) + '\n\n'); } -// Very simple 'registerization', coalescing of variables into a smaller number. +// 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 do not optimize when there are switches, so this pass only makes sense with // relooping. // TODO: Consider how this fits in with the rest of the optimization toolchain. Do @@ -1420,7 +1452,6 @@ function registerize(ast) { // We also mark local variables - i.e., having a var definition var localVars = {}; var hasSwitch = false; // we cannot optimize variables if there is a switch - var returnType = null; // for asm traverse(fun, function(node, type) { if (type == 'var') { node[1].forEach(function(defined) { localVars[defined[0]] = 1 }); @@ -1432,11 +1463,74 @@ function registerize(ast) { } } else if (type == 'switch') { hasSwitch = true; - } else if (asm && type == 'return' && node[1]) { - returnType = detectAsmCoercion(node[1]); } }); vacuum(fun); + if (minifierInfo) { + assert(asm); + var usedGlobals = {}; + var nextLocal = 0; + // Minify globals using the mapping we were given + traverse(fun, function(node, type) { + if (type == 'name') { + var name = node[1]; + var minified = minifierInfo.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]) { + // trying to minify a global into a name used locally. rename all the locals + var newName = '$_newLocal_' + (nextLocal++); + assert(!localVars[newName]); + if (params[minified]) { + params[newName] = 1; + delete params[minified]; + } + localVars[newName] = 1; + delete localVars[minified]; + asmData.vars[newName] = asmData.vars[minified]; + delete asmData.vars[minified]; + asmData.params[newName] = asmData.params[minified]; + delete asmData.params[minified]; + traverse(fun, function(node, type) { + if (type == 'name' && node[1] == minified) { + node[1] = newName; + } + }); + if (fun[2]) { + for (var i = 0; i < fun[2].length; i++) { + if (fun[2][i] == minified) fun[2][i] = newName; + } + } + } + node[1] = minified; + usedGlobals[minified] = 1; + } + } + }); + assert(fun[1] in minifierInfo.globals, fun[1]); + fun[1] = minifierInfo.globals[fun[1]]; + assert(fun[1]); + var nextRegName = 0; + } + var regTypes = {}; + function getNewRegName(num, name) { + if (!asm) return 'r' + num; + var type = asmData.vars[name]; + if (!minifierInfo) { + 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++]; + if (!usedGlobals[ret]) { + regTypes[ret] = type; + return ret; + } + } + assert('ran out of names'); + } // Find the # of uses of each variable. // While doing so, check if all a variable's uses are dominated in a simple // way by a simple assign, if so, then we can assign its register to it @@ -1521,7 +1615,7 @@ function registerize(ast) { saved++; } else { reg = nextReg++; - fullNames[reg] = (asm ? (asmData.vars[name] ? 'd' : 'i') : 'r') + reg; // TODO: even smaller names + fullNames[reg] = getNewRegName(reg, name); if (params[name]) paramRegs[reg] = 1; } varRegs[name] = reg; @@ -1565,7 +1659,7 @@ function registerize(ast) { if (loopRegs[loops]) { if (asm) { loopRegs[loops].forEach(function(loopReg) { - freeRegsClasses[fullNames[loopReg][0] == 'i' ? ASM_INT : ASM_DOUBLE].push(loopReg); + freeRegsClasses[regTypes[fullNames[loopReg]]].push(loopReg); }); } else { freeRegsClasses = freeRegsClasses.concat(loopRegs[loops]); @@ -1601,7 +1695,7 @@ function registerize(ast) { }; for (var i = 1; i < nextReg; i++) { var reg = fullNames[i]; - var type = reg[0] == 'i' ? ASM_INT : ASM_DOUBLE + var type = regTypes[reg]; if (!paramRegs[i]) { finalAsmData.vars[reg] = type; } else { @@ -1610,17 +1704,6 @@ function registerize(ast) { } } denormalizeAsm(fun, finalAsmData); - // Add a final return if one is missing. This is not strictly a register operation, but - // this pass traverses the entire AST anyhow so adding it here is efficient. - if (returnType !== null) { - var stats = getStatements(fun); - var last = stats[stats.length-1]; - if (last[0] != 'return') { - var returnValue = ['num', 0]; - if (returnType == ASM_DOUBLE) returnValue = ['unary-prefix', '+', returnValue]; - stats.push(['return', returnValue]); - } - } } }); } @@ -2155,6 +2238,43 @@ function eliminateMemSafe(ast) { eliminate(ast, true); } +function minifyGlobals(ast) { + var minified = {}; + var next = 0; + var first = true; // do not minify initial 'var asm =' + // find the globals + traverse(ast, function(node, type) { + if (type == 'var') { + if (first) { + first = false; + return; + } + 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++]; + } + } + }); + // 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++]; + } + // apply minification + traverse(ast, function(node, type) { + if (type == 'name') { + var name = node[1]; + if (name in minified) { + node[1] = minified[name]; + } + } + }); + suffix = '// MINIFY_INFO:' + JSON.stringify(minified); +} + // 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) { @@ -2200,6 +2320,7 @@ var passes = { registerize: registerize, eliminate: eliminate, eliminateMemSafe: eliminateMemSafe, + minifyGlobals: minifyGlobals, compress: function() { compress = true }, noPrintMetadata: function() { printMetadata = false }, asm: function() { asm = true }, @@ -2208,10 +2329,15 @@ var passes = { // Main +var suffix = ''; + 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)); arguments_.slice(1).forEach(function(arg) { passes[arg](ast); @@ -2231,4 +2357,5 @@ do { } while (js != old); print(js); print('\n'); +print(suffix); |