diff options
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 527 |
1 files changed, 438 insertions, 89 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 103fb1fe..09791150 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -140,15 +140,10 @@ var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]]; var TRUE_NODE = ['unary-prefix', '!', ['num', 0]]; var FALSE_NODE = ['unary-prefix', '!', ['num', 1]]; -var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS:'; -var generatedFunctions = null; -function setGeneratedFunctions(metadata) { - var start = metadata.indexOf(GENERATED_FUNCTIONS_MARKER); - generatedFunctions = set(eval(metadata.substr(start + GENERATED_FUNCTIONS_MARKER.length))); -} -function isGenerated(ident) { - return ident in generatedFunctions; -} +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); @@ -216,7 +211,7 @@ function traverse(node, pre, post, stack) { function traverseGenerated(ast, pre, post, stack) { assert(generatedFunctions); traverse(ast, function(node) { - if (node[0] == 'defun' && isGenerated(node[1])) { + if (node[0] == 'defun') { traverse(node, pre, post, stack); return null; } @@ -225,12 +220,15 @@ function traverseGenerated(ast, pre, post, stack) { function traverseGeneratedFunctions(ast, callback) { assert(generatedFunctions); - traverse(ast, function(node) { - if (node[0] == 'defun' && isGenerated(node[1])) { - 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) @@ -405,6 +403,23 @@ function removeUnneededLabelSettings(ast) { // Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. function simplifyExpressionsPre(ast) { + // Look for (x&A)<<B>>B and replace it with X&A if possible. + function simplifySignExtends(ast) { + traverseGenerated(ast, function(node, type) { + if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num' && + node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' && node[3][1] == node[2][3][1]) { + var innerNode = node[2][2]; + var shifts = node[3][1]; + if (innerNode[0] == 'binary' && innerNode[1] == '&' && innerNode[3][0] == 'num') { + var mask = innerNode[3][1]; + if (mask << shifts >> shifts == mask) { + return innerNode; + } + } + } + }); + } + // When there is a bunch of math like (((8+5)|0)+12)|0, only the external |0 is needed, one correction is enough. // At each node, ((X|0)+Y)|0 can be transformed into (X+Y): The inner corrections are not needed // TODO: Is the same is true for 0xff, 0xffff? @@ -418,20 +433,32 @@ function simplifyExpressionsPre(ast) { var rerun = true; while (rerun) { rerun = false; - traverseGenerated(ast, function(node, type, stack) { - if (type == 'binary' && node[1] == '|' && (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO))) { - stack.push(1); // From here on up, no need for this kind of correction, it's done at the top - - // We might be able to remove this correction - for (var i = stack.length-2; i >= 0; i--) { - if (stack[i] == 1) { - // Great, we can eliminate - rerun = true; - return jsonCompare(node[2], ZERO) ? node[3] : node[2]; - } else if (stack[i] == -1) { - break; // Too bad, we can't + traverseGenerated(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)) { + // We might be able to remove this correction + for (var i = stack.length-1; i >= 0; i--) { + if (stack[i] == 1) { + // 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 + node.length = result.length; + for (var j = 0; j < result.length; j++) { + node[j] = result[j]; + } + rerun = true; + 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(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') { @@ -442,9 +469,19 @@ function simplifyExpressionsPre(ast) { }, null, []); } - // &-related optimizations + // & and heap-related optimizations + + var heapBits, heapUnsigned; + function parseHeap(name) { + if (name.substr(0, 4) != 'HEAP') return false; + heapUnsigned = name[4] == 'U'; + heapBits = parseInt(name.substr(heapUnsigned ? 5 : 4)); + return true; + } + traverseGenerated(ast, function(node, type) { if (type == 'binary' && node[1] == '&' && node[3][0] == 'num') { + if (node[2][0] == 'num') return ['num', node[2][1] & node[3][1]]; var input = node[2]; var amount = node[3][1]; if (input[0] == 'binary' && input[1] == '&' && input[3][0] == 'num') { @@ -454,19 +491,50 @@ function simplifyExpressionsPre(ast) { } else if (input[0] == 'sub' && input[1][0] == 'name') { // HEAP8[..] & 255 => HEAPU8[..] var name = input[1][1]; - if (name.substr(0, 4) == 'HEAP') { - var unsigned = name[4] == 'U'; - var bits = parseInt(name.substr(unsigned ? 5 : 4)); - if (amount == Math.pow(2, bits)-1) { - if (!unsigned) { - input[1][1] = 'HEAPU' + bits; // make unsigned + if (parseHeap(name)) { + if (amount == Math.pow(2, heapBits)-1) { + if (!heapUnsigned) { + input[1][1] = 'HEAPU' + heapBits; // make unsigned + } + if (asm) { + // we cannot return HEAPU8 without a coercion, but at least we do HEAP8 & 255 => HEAPU8 | 0 + node[1] = '|'; + node[3][1] = 0; + return node; } return input; } } } + } else if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num' && + 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)) { + if (heapBits == 32 - amount) { + node[2][2][1][1] = 'HEAP' + heapBits; + node[1] = '|'; + node[2] = node[2][2]; + node[3][1] = 0; + return node; + } + } } }); + + if (asm) { + // optimize num >> num, in asm we need this here since we do not run optimizeShifts + traverseGenerated(ast, function(node, type) { + if (type == 'binary' && node[1] == '>>' && node[2][0] == 'num' && node[3][0] == 'num') { + node[0] = 'num'; + node[1] = node[2][1] >> node[3][1]; + node.length = 2; + } + }); + } } // The most common mathop is addition, e.g. in getelementptr done repeatedly. We can join all of those, @@ -512,9 +580,40 @@ function simplifyExpressionsPre(ast) { }); } + function asmOpts(ast) { + // 1. Add final returns when necessary + // 2. Remove unneeded coercions on function calls that have no targets (eliminator removed it) + traverseGeneratedFunctions(ast, function(fun) { + 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. + 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]); + } + } + }); + } + + simplifySignExtends(ast); simplifyBitops(ast); joinAdditions(ast); // simplifyZeroComp(ast); TODO: investigate performance + if (asm) asmOpts(ast); } // In typed arrays mode 2, we can have @@ -879,7 +978,7 @@ var NO_SIDE_EFFECTS = set('num', 'name'); function hasSideEffects(node) { // this is 99% incomplete! if (node[0] in NO_SIDE_EFFECTS) return false; - if (node[0] == 'unary-prefix' && node[1] == '!') return hasSideEffects(node[2]); + if (node[0] == 'unary-prefix') return hasSideEffects(node[2]); if (node[0] == 'binary') return hasSideEffects(node[2]) || hasSideEffects(node[3]); return true; } @@ -1277,7 +1376,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++; } @@ -1304,6 +1405,19 @@ function normalizeAsm(func) { while (i < stats.length) { traverse(stats[i], function(node, type) { if (type == 'var') { + for (var j = 0; j < node[1].length; j++) { + var v = node[1][j]; + var name = v[0]; + var value = v[1]; + if (!(name in data.vars)) { + if (value[0] != 'name') { + data.vars[name] = detectAsmCoercion(value); // detect by coercion + } else { + var origin = value[1]; + data.vars[name] = data.vars[origin] || ASM_INT; // detect by origin variable, or assume int for non-locals + } + } + } unVarify(node[1], node); } else if (type == 'dot') { if (node[1][0] == 'name' && node[1][1] == 'Math') { @@ -1362,13 +1476,16 @@ 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 // we still need the eliminator? Closure? And in what order? Perhaps just // closure simple? -function registerize(ast, asm) { +function registerize(ast) { traverseGeneratedFunctions(ast, function(fun) { if (asm) var asmData = normalizeAsm(fun); // Add parameters as a first (fake) var (with assignment), so they get taken into consideration @@ -1388,7 +1505,7 @@ function registerize(ast, asm) { // Replace all var definitions with assignments; we will add var definitions at the top after we registerize // 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 hasSwitch = false; // we cannot optimize variables if there is a switch, unless in asm mode traverse(fun, function(node, type) { if (type == 'var') { node[1].forEach(function(defined) { localVars[defined[0]] = 1 }); @@ -1403,6 +1520,71 @@ function registerize(ast, asm) { } }); 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 @@ -1414,7 +1596,15 @@ function registerize(ast, asm) { var varLevels = {}; var possibles = {}; var unoptimizables = {}; - traverse(fun, function(node, type) { + function purgeLevel() { + // Invalidate all dominating on this level, further users make it unoptimizable + for (var name in levelDominations[level]) { + varLevels[name] = 0; + } + levelDominations[level] = null; + level--; + } + traverse(fun, function possibilifier(node, type) { if (type == 'name') { var name = node[1]; if (localVars[name]) { @@ -1435,24 +1625,61 @@ function registerize(ast, asm) { } } } else if (type in CONTROL_FLOW) { - level++; - } - }, function(node, type) { - if (type in CONTROL_FLOW) { - // Invalidate all dominating on this level, further users make it unoptimizable - for (var name in levelDominations[level]) { - varLevels[name] = 0; + // recurse children, in the context of a loop + switch(type) { + case 'while': case 'do': { + traverse(node[1], possibilifier); + level++; + traverse(node[2], possibilifier); + purgeLevel(); + break; + } + case 'for': { + traverse(node[1], possibilifier); + for (var i = 2; i <= 4; i++) { + level++; + traverse(node[i], possibilifier); + purgeLevel(); + } + break; + } + case 'if': { + traverse(node[1], possibilifier); + level++; + traverse(node[2], possibilifier); + purgeLevel(); + if (node[3]) { + level++; + traverse(node[3], possibilifier); + purgeLevel(); + } + break; + } + case 'switch': { + traverse(node[1], possibilifier); + var cases = node[2]; + for (var i = 0; i < cases.length; i++) { + level++; + traverse(cases[i][1], possibilifier); + purgeLevel(); + } + break; + } + default: throw dumpAst(node); } - levelDominations[level] = null; - level--; + return null; // prevent recursion into children, which we already did } }); var optimizables = {}; - if (!hasSwitch) { + if (!hasSwitch || asm) { for (var possible in possibles) { if (!unoptimizables[possible]) optimizables[possible] = 1; } } + + //printErr('optimizables: ' + JSON.stringify(optimizables)); + //printErr('unoptimizables: ' + JSON.stringify(unoptimizables)); + // Go through the function's code, assigning 'registers'. // The only tricky bit is to keep variables locked on a register through loops, // since they can potentially be returned to. Optimizable variables lock onto @@ -1487,7 +1714,7 @@ function registerize(ast, asm) { 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; @@ -1531,7 +1758,7 @@ function registerize(ast, asm) { 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]); @@ -1557,7 +1784,7 @@ function registerize(ast, asm) { fun[2].push(reg); } } - getStatements(fun).unshift(['var', vars]); + if (vars.length > 0) getStatements(fun).unshift(['var', vars]); } } else { //printErr('unfake params: \n\n' + astToSrc(fun) + '\n\n'); @@ -1567,7 +1794,7 @@ function registerize(ast, asm) { }; 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 { @@ -1580,10 +1807,6 @@ function registerize(ast, asm) { }); } -function registerizeAsm(ast) { - registerize(ast, true); -} - // Eliminator aka Expressionizer // // The goal of this pass is to eliminate unneeded variables (which represent one of the infinite registers in the LLVM @@ -1616,12 +1839,12 @@ function registerizeAsm(ast) { // In memSafe mode, we are more careful and assume functions can replace HEAP and FUNCTION_TABLE, which // can happen in ALLOW_MEMORY_GROWTH mode -var ELIMINATION_SAFE_NODES = set('var', 'assign', 'call', 'if', 'toplevel', 'do', 'return'); // do is checked carefully, however +var ELIMINATION_SAFE_NODES = set('var', 'assign', 'call', 'if', 'toplevel', 'do', 'return', 'label', 'switch'); // do is checked carefully, however var NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS = set('name', 'num', 'string', 'binary', 'sub', 'unary-prefix'); var IGNORABLE_ELIMINATOR_SCAN_NODES = set('num', 'toplevel', 'string', 'break', 'continue', 'dot'); // dot can only be STRING_TABLE.* -var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', 'switch', 'for', 'while', 'array', 'throw'); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body) +var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', 'for', 'while', 'array', 'throw'); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body) -function eliminate(ast, memSafe, asm) { +function eliminate(ast, memSafe) { // Find variables that have a single use, and if they can be eliminated, do so traverseGeneratedFunctions(ast, function(func, type) { if (asm) var asmData = normalizeAsm(func); @@ -1633,6 +1856,7 @@ function eliminate(ast, memSafe, asm) { var values = {}; var locals = {}; var varsToRemove = {}; // variables being removed, that we can eliminate all 'var x;' of (this refers to 'var' nodes we should remove) + // 1 means we should remove it, 2 means we successfully removed it var varsToTryToRemove = {}; // variables that have 0 uses, but have side effects - when we scan we can try to remove them // add arguments as locals if (func[2]) { @@ -1641,6 +1865,7 @@ function eliminate(ast, memSafe, asm) { } } // examine body and note locals + var hasSwitch = false; traverse(func, function(node, type) { if (type === 'var') { var node1 = node[1]; @@ -1672,31 +1897,74 @@ function eliminate(ast, memSafe, asm) { uses[name]--; // because the name node will show up by itself in the previous case } } + } else if (type == 'switch') { + hasSwitch = true; } }); + + // we cannot eliminate variables if there is a switch + if (hasSwitch && !asm) return; + var potentials = {}; // local variables with 1 definition and 1 use var sideEffectFree = {}; // whether a local variable has no side effects in its definition - for (var name in locals) { + + function unprocessVariable(name) { + if (name in potentials) delete potentials[name]; + if (name in varsToRemove) delete varsToRemove[name]; + if (name in sideEffectFree) delete sideEffectFree[name]; + if (name in varsToTryToRemove) delete varsToTryToRemove[name]; + } + function processVariable(name) { if (definitions[name] == 1 && uses[name] == 1) { potentials[name] = 1; } else if (uses[name] == 0 && (!definitions[name] || definitions[name] <= 1)) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow) var hasSideEffects = false; - if (values[name]) { - traverse(values[name], function(node, type) { - if (!(type in NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS)) { - hasSideEffects = true; // cannot remove this unused variable, constructing it has side effects - return true; - } - }); + var value = values[name]; + if (value) { + // TODO: merge with other side effect code + // First, pattern-match + // (HEAP32[((tempDoublePtr)>>2)]=((HEAP32[(($_sroa_0_0__idx1)>>2)])|0),HEAP32[(((tempDoublePtr)+(4))>>2)]=((HEAP32[((($_sroa_0_0__idx1)+(4))>>2)])|0),(+(HEAPF64[(tempDoublePtr)>>3]))) + // which has no side effects and is the special form of converting double to i64. + if (!(value[0] == 'seq' && value[1][0] == 'assign' && value[1][2][0] == 'sub' && value[1][2][2][0] == 'binary' && value[1][2][2][1] == '>>' && + value[1][2][2][2][0] == 'name' && value[1][2][2][2][1] == 'tempDoublePtr')) { + // If not that, then traverse and scan normally. + traverse(value, function(node, type) { + if (!(type in NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS)) { + hasSideEffects = true; // cannot remove this unused variable, constructing it has side effects + return true; + } + }); + } } if (!hasSideEffects) { - varsToRemove[name] = 1; // remove it normally + varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally sideEffectFree[name] = true; + // Each time we remove a variable with 0 uses, if its value has no + // side effects and vanishes too, then we can remove a use from variables + // appearing in it, and possibly eliminate again + if (value) { + traverse(value, function(node, type) { + if (type == 'name') { + var name = node[1]; + node[1] = ''; // we can remove this - it will never be shown, and should not be left to confuse us as we traverse + if (name in locals) { + uses[name]--; // cannot be infinite recursion since we descend an energy function + assert(uses[name] >= 0); + unprocessVariable(name); + processVariable(name); + } + } + }); + } } else { varsToTryToRemove[name] = 1; // try to remove it later during scanning } } } + for (var name in locals) { + processVariable(name); + } + //printErr('defs: ' + JSON.stringify(definitions)); //printErr('uses: ' + JSON.stringify(uses)); //printErr('values: ' + JSON.stringify(values)); @@ -1847,7 +2115,7 @@ function eliminate(ast, memSafe, asm) { for (var i = 0; i < value.length; i++) { node[i] = value[i]; } - varsToRemove[name] = 1; + varsToRemove[name] = 2; } } else { if (allowTracking) track(name, node[3], node); @@ -1887,7 +2155,7 @@ function eliminate(ast, memSafe, asm) { for (var i = 0; i < value.length; i++) { node[i] = value[i]; } - varsToRemove[name] = 1; + varsToRemove[name] = 2; } } } @@ -1942,13 +2210,14 @@ function eliminate(ast, memSafe, asm) { invalidateCalls(); callsInvalidated = true; } + allowTracking = false; traverseInOrder(node[2]); // 2 and 3 could be 'parallel', really.. if (node[3]) traverseInOrder(node[3]); allowTracking = true; + } else { tracked = {}; - abort = true; } } else if (type == 'block') { var stats = node[1]; @@ -1969,7 +2238,6 @@ function eliminate(ast, memSafe, asm) { traverseInOrder(node[2]); } else { tracked = {}; - abort = true; } } else if (type == 'return') { if (node[1]) traverseInOrder(node[1]); @@ -1977,6 +2245,17 @@ function eliminate(ast, memSafe, asm) { traverseInOrder(node[1]); traverseInOrder(node[2]); traverseInOrder(node[3]); + } else if (type == 'switch') { + traverseInOrder(node[1]); + var cases = node[2]; + for (var i = 0; i < cases.length; i++) { + var c = cases[i]; + assert(c[0] === null || c[0][0] == 'num' || (c[0][0] == 'unary-prefix' && c[0][2][0] == 'num')); + var stats = c[1]; + for (var j = 0; j < stats.length; j++) { + traverseInOrder(stats[j]); + } + } } else { if (!(type in ABORTING_ELIMINATOR_SCAN_NODES)) { printErr('unfamiliar eliminator scan node: ' + JSON.stringify(node)); @@ -1991,7 +2270,7 @@ function eliminate(ast, memSafe, asm) { function doEliminate(name, node) { //printErr('elim!!!!! ' + name); // yes, eliminate! - varsToRemove[name] = 1; // both assign and var definitions can have other vars we must clean up + varsToRemove[name] = 2; // both assign and var definitions can have other vars we must clean up var info = tracked[name]; delete tracked[name]; var defNode = info.defNode; @@ -2023,13 +2302,15 @@ function eliminate(ast, memSafe, asm) { } } traverse(func, function(block) { - var stats = getStatements(block); + // Look for statements, including while-switch pattern + var stats = getStatements(block) || (block[0] == 'while' && block[2][0] == 'switch' ? [block[2]] : stats); if (!stats) return; + //printErr('Stats: ' + JSON.stringify(stats).substr(0,100)); tracked = {}; //printErr('new StatBlock'); for (var i = 0; i < stats.length; i++) { var node = stats[i]; - //printErr('StatBlock[' + i + '] => ' + JSON.stringify(node)); + //printErr('StatBlock[' + i + '] => ' + JSON.stringify(node).substr(0,100)); var type = node[0]; if (type == 'stat') { node = node[1]; @@ -2049,7 +2330,7 @@ function eliminate(ast, memSafe, asm) { //printErr('cleaning up ' + JSON.stringify(varsToRemove)); traverse(func, function(node, type) { if (type === 'var') { - node[1] = node[1].filter(function(pair) { return !(pair[0] in varsToRemove) }); + node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] }); if (node[1].length == 0) { // wipe out an empty |var;| node[0] = 'toplevel'; @@ -2060,7 +2341,7 @@ function eliminate(ast, memSafe, asm) { if (asm) { for (var v in varsToRemove) { - delete asmData.vars[v]; + if (varsToRemove[v] == 2) delete asmData.vars[v]; } denormalizeAsm(func, asmData); } @@ -2114,13 +2395,69 @@ function eliminateMemSafe(ast) { eliminate(ast, true); } -function eliminateAsm(ast) { - eliminate(ast, false, 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) { + if (type == 'unary-prefix' && node[1] == '+') { + if (node[2][0] == 'num' || + (node[2][0] == 'unary-prefix' && node[2][1] == '-' && node[2][2][0] == 'num')) { + return ['call', ['name', 'DOT$ZERO'], [node[2]]]; + } + } + }); +} +function fixDotZero(js) { + return js.replace(/DOT\$ZERO\(([-+]?(0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)/g, function(m, num) { + if (num.substr(0, 2) == '0x' || num.substr(0, 3) == '-0x') { + return eval(num) + '.0'; + } + if (num.indexOf('.') >= 0) return num; + var e = num.indexOf('e'); + if (e < 0) return num + '.0'; + return num.substr(0, e) + '.0' + num.substr(e); + }); } // Passes table -var compress = false, printMetadata = true; +var compress = false, printMetadata = true, asm = false, last = false; var passes = { dumpAst: dumpAst, @@ -2135,27 +2472,38 @@ var passes = { hoistMultiples: hoistMultiples, loopOptimizer: loopOptimizer, registerize: registerize, - registerizeAsm: registerizeAsm, eliminate: eliminate, eliminateMemSafe: eliminateMemSafe, - eliminateAsm: eliminateAsm, - compress: function() { compress = true; }, - noPrintMetadata: function() { printMetadata = false; } + minifyGlobals: minifyGlobals, + compress: function() { compress = true }, + noPrintMetadata: function() { printMetadata = false }, + asm: function() { asm = true }, + last: function() { last = true }, + closure: function(){} // handled in python }; // Main +var suffix = ''; + var src = read(arguments_[0]); var ast = srcToAst(src); //printErr(JSON.stringify(ast)); throw 1; -var metadata = src.split('\n').filter(function(line) { return line.indexOf(GENERATED_FUNCTIONS_MARKER) >= 0 })[0]; -//assert(metadata, 'Must have EMSCRIPTEN_GENERATED_FUNCTIONS metadata'); -if (metadata) setGeneratedFunctions(metadata); +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); }); +if (asm && last) { + prepDotZero(ast); +} var js = astToSrc(ast, compress), old; +if (asm && last) { + js = fixDotZero(js); +} // remove unneeded newlines+spaces, and print do { @@ -2164,4 +2512,5 @@ do { } while (js != old); print(js); print('\n'); +print(suffix); |