diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-06-11 09:46:33 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-06-11 09:46:33 -0700 |
commit | 0ad87244178badf26cd5c8e0ed88116e87026472 (patch) | |
tree | db19d53d4548391d316fec33954ed7d5d3cfb68b /tools/js-optimizer.js | |
parent | a1eb425371aa310e074b06d80d56adf71d6f5383 (diff) | |
parent | 886e3158cf5d95a2c2721e5eb9a1c3ac4461f805 (diff) |
Merge branch 'incoming'
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 383 |
1 files changed, 324 insertions, 59 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 09791150..07317e0a 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -402,10 +402,12 @@ function removeUnneededLabelSettings(ast) { // Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. +var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^'); + 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) { + traverse(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]; @@ -427,13 +429,12 @@ function simplifyExpressionsPre(ast) { // 'useful' mathops already |0 anyhow. function simplifyBitops(ast) { - var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^'); 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 ZERO = ['num', 0]; var rerun = true; while (rerun) { rerun = false; - traverseGenerated(ast, function process(node, type, stack) { + 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]]; @@ -479,8 +480,12 @@ function simplifyExpressionsPre(ast) { return true; } - traverseGenerated(ast, function(node, type) { - if (type == 'binary' && node[1] == '&' && node[3][0] == 'num') { + var hasTempDoublePtr = false; + + traverse(ast, function(node, type) { + if (type == 'name') { + if (node[1] == 'tempDoublePtr') hasTempDoublePtr = true; + } else 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]; @@ -522,12 +527,133 @@ function simplifyExpressionsPre(ast) { return node; } } + } else if (type == 'assign') { + // optimizations for assigning into HEAP32 specifically + if (node[1] === true && node[2][0] == 'sub' && node[2][1][0] == 'name' && node[2][1][1] == 'HEAP32') { + // HEAP32[..] = x | 0 does not need the | 0 (unless it is a mandatory |0 of a call) + if (node[3][0] == 'binary' && node[3][1] == '|') { + if (node[3][2][0] == 'num' && node[3][2][1] == 0 && node[3][3][0] != 'call') { + node[3] = node[3][3]; + } else if (node[3][3][0] == 'num' && node[3][3][1] == 0 && node[3][2][0] != 'call') { + node[3] = node[3][2]; + } + } + } + var value = node[3]; + if (value[0] == 'binary' && value[1] == '|') { + // canonicalize order of |0 to end + if (value[2][0] == 'num' && value[2][1] == 0) { + var temp = value[2]; + value[2] = value[3]; + value[3] = temp; + } + // if a seq ends in an |0, remove an external |0 + // note that it is only safe to do this in assigns, like we are doing here (return (x, y|0); is not valid) + if (value[2][0] == 'seq' && value[2][2][0] == 'binary' && value[2][2][1] in USEFUL_BINARY_OPS) { + node[3] = value[2]; + } + } } }); if (asm) { + if (hasTempDoublePtr) { + traverse(ast, function(node, type) { + if (type == 'assign') { + if (node[1] === true && node[2][0] == 'sub' && node[2][1][0] == 'name' && node[2][1][1] == 'HEAP32') { + // remove bitcasts that are now obviously pointless, e.g. + // HEAP32[$45 >> 2] = HEAPF32[tempDoublePtr >> 2] = ($14 < $28 ? $14 : $28) - $42, HEAP32[tempDoublePtr >> 2] | 0; + var value = node[3]; + if (value[0] == 'seq' && value[1][0] == 'assign' && value[1][2][0] == 'sub' && value[1][2][1][0] == 'name' && value[1][2][1][1] == 'HEAPF32' && + value[1][2][2][0] == 'binary' && value[1][2][2][2][0] == 'name' && value[1][2][2][2][1] == 'tempDoublePtr') { + // transform to HEAPF32[$45 >> 2] = ($14 < $28 ? $14 : $28) - $42; + node[2][1][1] = 'HEAPF32'; + node[3] = value[1][3]; + } + } + } else if (type == 'seq') { + // (HEAP32[tempDoublePtr >> 2] = HEAP32[$37 >> 2], +HEAPF32[tempDoublePtr >> 2]) + // ==> + // +HEAPF32[$37 >> 2] + if (node[0] == 'seq' && node[1][0] == 'assign' && node[1][2][0] == 'sub' && node[1][2][1][0] == 'name' && + (node[1][2][1][1] == 'HEAP32' || node[1][2][1][1] == 'HEAPF32') && + node[1][2][2][0] == 'binary' && node[1][2][2][2][0] == 'name' && node[1][2][2][2][1] == 'tempDoublePtr' && + node[1][3][0] == 'sub' && node[1][3][1][0] == 'name' && (node[1][3][1][1] == 'HEAP32' || node[1][3][1][1] == 'HEAPF32')) { + if (node[1][2][1][1] == 'HEAP32') { + node[1][3][1][1] = 'HEAPF32'; + return ['unary-prefix', '+', node[1][3]]; + } else { + node[1][3][1][1] = 'HEAP32'; + return ['binary', '|', node[1][3], ['num', 0]]; + } + } + } + }); + + // finally, wipe out remaining ones by finding cases where all assignments to X are bitcasts, and all uses are writes to + // the other heap type, then eliminate the bitcast + var bitcastVars = {}; + traverse(ast, function(node, type) { + if (type == 'assign' && node[1] === true && node[2][0] == 'name') { + var value = node[3]; + if (value[0] == 'seq' && value[1][0] == 'assign' && value[1][2][0] == 'sub' && value[1][2][1][0] == 'name' && + (value[1][2][1][1] == 'HEAP32' || value[1][2][1][1] == 'HEAPF32') && + value[1][2][2][0] == 'binary' && value[1][2][2][2][0] == 'name' && value[1][2][2][2][1] == 'tempDoublePtr') { + var name = node[2][1]; + if (!bitcastVars[name]) bitcastVars[name] = { + define_HEAP32: 0, define_HEAPF32: 0, use_HEAP32: 0, use_HEAPF32: 0, bad: false, namings: 0, defines: [], uses: [] + }; + bitcastVars[name]['define_' + value[1][2][1][1]]++; + bitcastVars[name].defines.push(node); + } + } + }); + traverse(ast, function(node, type) { + if (type == 'name' && bitcastVars[node[1]]) { + bitcastVars[node[1]].namings++; + } else if (type == 'assign' && node[1] === true) { + var value = node[3]; + if (value[0] == 'name') { + var name = value[1]; + if (bitcastVars[name]) { + var target = node[2]; + if (target[0] == 'sub' && target[1][0] == 'name' && (target[1][1] == 'HEAP32' || target[1][1] == 'HEAPF32')) { + bitcastVars[name]['use_' + target[1][1]]++; + bitcastVars[name].uses.push(node); + } + } + } + } + }); + var asmData = normalizeAsm(ast); + for (var v in bitcastVars) { + var info = bitcastVars[v]; + // good variables define only one type, use only one type, have definitions and uses, and define as a different type than they use + if (info.define_HEAP32*info.define_HEAPF32 == 0 && info.use_HEAP32*info.use_HEAPF32 == 0 && + info.define_HEAP32+info.define_HEAPF32 > 0 && info.use_HEAP32+info.use_HEAPF32 > 0 && + info.define_HEAP32*info.use_HEAP32 == 0 && info.define_HEAPF32*info.use_HEAPF32 == 0 && + v in asmData.vars && info.namings == info.define_HEAP32+info.define_HEAPF32+info.use_HEAP32+info.use_HEAPF32) { + var correct = info.use_HEAP32 ? 'HEAPF32' : 'HEAP32'; + info.defines.forEach(function(define) { + define[3] = define[3][1][3]; + if (correct == 'HEAP32') { + define[3] = ['binary', '|', define[3], ['num', 0]]; + } else { + define[3] = ['unary-prefix', '+', define[3]]; + } + // do we want a simplifybitops on the new values here? + }); + info.uses.forEach(function(use) { + use[2][1][1] = correct; + }); + asmData.vars[v] = 1 - asmData.vars[v]; + } + } + denormalizeAsm(ast, asmData); + } + // optimize num >> num, in asm we need this here since we do not run optimizeShifts - traverseGenerated(ast, function(node, type) { + traverse(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]; @@ -543,7 +669,7 @@ function simplifyExpressionsPre(ast) { var rerun = true; while (rerun) { rerun = false; - traverseGenerated(ast, function(node, type) { + traverse(ast, function(node, type) { if (type == 'binary' && node[1] == '+') { if (node[2][0] == 'num' && node[3][0] == 'num') { rerun = true; @@ -566,7 +692,7 @@ function simplifyExpressionsPre(ast) { // if (x == 0) can be if (!x), etc. function simplifyZeroComp(ast) { - traverseGenerated(ast, function(node, type) { + traverse(ast, function(node, type) { var binary; if (type == 'if' && (binary = node[1])[0] == 'binary') { if ((binary[1] == '!=' || binary[1] == '!==') && binary[3][0] == 'num' && binary[3][1] == 0) { @@ -580,40 +706,40 @@ function simplifyExpressionsPre(ast) { }); } - function asmOpts(ast) { + function asmOpts(fun) { // 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]); + 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); + traverseGeneratedFunctions(ast, function(func) { + simplifySignExtends(func); + simplifyBitops(func); + joinAdditions(func); + // simplifyZeroComp(func); TODO: investigate performance + if (asm) asmOpts(func); + }); } // In typed arrays mode 2, we can have @@ -950,24 +1076,28 @@ function optimizeShiftsAggressive(ast) { // we then get // if (!(x < 5)) // or such. Simplifying these saves space and time. -function simplifyNotComps(ast) { - traverse(ast, function(node, type) { - if (type == 'unary-prefix' && node[1] == '!' && node[2][0] == 'binary') { - if (node[2][1] == '<') { - return ['binary', '>=', node[2][2], node[2][3]]; - } else if (node[2][1] == '>') { - return ['binary', '<=', node[2][2], node[2][3]]; - } else if (node[2][1] == '==') { - return ['binary', '!=', node[2][2], node[2][3]]; - } else if (node[2][1] == '!=') { - return ['binary', '==', node[2][2], node[2][3]]; - } else if (node[2][1] == '===') { - return ['binary', '!==', node[2][2], node[2][3]]; - } else if (node[2][1] == '!==') { - return ['binary', '===', node[2][2], node[2][3]]; - } +function simplifyNotCompsDirect(node) { + if (node[0] == 'unary-prefix' && node[1] == '!') { + if (node[2][0] == 'binary') { + switch(node[2][1]) { + case '<': return ['binary', '>=', node[2][2], node[2][3]]; + case '>': return ['binary', '<=', node[2][2], node[2][3]]; + case '<=': return ['binary', '>', node[2][2], node[2][3]]; + case '>=': return ['binary', '<', node[2][2], node[2][3]]; + case '==': return ['binary', '!=', node[2][2], node[2][3]]; + case '!=': return ['binary', '==', node[2][2], node[2][3]]; + case '===': return ['binary', '!==', node[2][2], node[2][3]]; + case '!==': return ['binary', '===', node[2][2], node[2][3]]; + } + } else if (node[2][0] == 'unary-prefix' && node[2][1] == '!') { + return node[2][2]; } - }); + } + return node; +} + +function simplifyNotComps(ast) { + traverse(ast, simplifyNotCompsDirect); } function simplifyExpressionsPost(ast) { @@ -1216,8 +1346,7 @@ function hoistMultiples(ast) { var temp = node[3]; node[3] = node[2]; node[2] = temp; - node[1] = ['unary-prefix', '!', node[1]]; - simplifyNotComps(node[1]); // bake the ! into the expression + node[1] = simplifyNotCompsDirect(['unary-prefix', '!', node[1]]); stat1 = node[2][1]; stat2 = node[3][1]; } @@ -1844,6 +1973,13 @@ var NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS = set('name', 'num', 'string', 'binar 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', '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 isTempDoublePtrAccess(node) { // these are used in bitcasts; they are not really affecting memory, and should cause no invalidation + assert(node[0] == 'sub'); + return (node[2][0] == 'name' && node[2][1] == 'tempDoublePtr') || + (node[2][0] == 'binary' && ((node[2][2][0] == 'name' && node[2][2][1] == 'tempDoublePtr') || + (node[2][3][0] == 'name' && node[2][3][1] == 'tempDoublePtr'))); +} + function eliminate(ast, memSafe) { // Find variables that have a single use, and if they can be eliminated, do so traverseGeneratedFunctions(ast, function(func, type) { @@ -1853,6 +1989,7 @@ function eliminate(ast, memSafe) { // First, find the potentially eliminatable functions: that have one definition and one use var definitions = {}; var uses = {}; + var namings = {}; 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) @@ -1895,6 +2032,8 @@ function eliminate(ast, memSafe) { if (!values[name]) values[name] = node[3]; if (node[1] === true) { // not +=, -= etc., just = uses[name]--; // because the name node will show up by itself in the previous case + if (!namings[name]) namings[name] = 0; + namings[name]++; // offset it here, this tracks the total times we are named } } } else if (type == 'switch') { @@ -1902,6 +2041,10 @@ function eliminate(ast, memSafe) { } }); + for (var used in uses) { + namings[used] = (namings[used] || 0) + uses[used]; + } + // we cannot eliminate variables if there is a switch if (hasSwitch && !asm) return; @@ -1971,7 +2114,7 @@ function eliminate(ast, memSafe) { //printErr('locals: ' + JSON.stringify(locals)); //printErr('varsToRemove: ' + JSON.stringify(varsToRemove)); //printErr('varsToTryToRemove: ' + JSON.stringify(varsToTryToRemove)); - definitions = values = null; + values = null; //printErr('potentials: ' + JSON.stringify(potentials)); // We can now proceed through the function. In each list of statements, we try to eliminate var tracked = {}; @@ -2121,7 +2264,7 @@ function eliminate(ast, memSafe) { if (allowTracking) track(name, node[3], node); } } else if (target[0] == 'sub') { - if (!memoryInvalidated) { + if (!isTempDoublePtrAccess(target) && !memoryInvalidated) { invalidateMemory(); memoryInvalidated = true; } @@ -2129,7 +2272,8 @@ function eliminate(ast, memSafe) { } else if (type == 'sub') { traverseInOrder(node[1], false, !memSafe); // evaluate inner traverseInOrder(node[2]); // evaluate outer - if (!ignoreSub) { // ignoreSub means we are a write (happening later), not a read + // ignoreSub means we are a write (happening later), not a read + if (!ignoreSub && !isTempDoublePtrAccess(node)) { // do the memory access if (!callsInvalidated) { invalidateCalls(); @@ -2326,9 +2470,11 @@ function eliminate(ast, memSafe) { //printErr('delete StatBlock'); }); - // clean up vars - //printErr('cleaning up ' + JSON.stringify(varsToRemove)); + var seenUses = {}, helperReplacements = {}; // for looper-helper optimization + + // clean up vars, and loop variable elimination traverse(func, function(node, type) { + // pre if (type === 'var') { node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] }); if (node[1].length == 0) { @@ -2337,6 +2483,103 @@ function eliminate(ast, memSafe) { node[1] = []; } } + }, function(node, type) { + // post + if (type == 'name') { + var name = node[1]; + if (name in helperReplacements) { + node[1] = helperReplacements[name]; + return; // no need to track this anymore, we can't loop-optimize more than once + } + // track how many uses we saw. we need to know when a variable is no longer used (hence we run this in the post) + if (!(name in seenUses)) { + seenUses[name] = 1; + } else { + seenUses[name]++; + } + } else if (type == 'while') { + // try to remove loop helper variables specifically + var stats = node[2][1]; + var last = stats[stats.length-1]; + if (last && last[0] == 'if' && last[2][0] == 'block' && last[3] && last[3][0] == 'block') { + var ifTrue = last[2]; + var ifFalse = last[3]; + var flip = false; + if (ifFalse[1][0][0] == 'break') { // canonicalize break in the if + var temp = ifFalse; + ifFalse = ifTrue; + ifTrue = temp; + flip = true; + } + if (ifTrue[1][0][0] == 'break') { + var assigns = ifFalse[1]; + var loopers = [], helpers = []; + for (var i = 0; i < assigns.length; i++) { + if (assigns[i][0] == 'stat' && assigns[i][1][0] == 'assign') { + var assign = assigns[i][1]; + if (assign[1] === true && assign[2][0] == 'name' && assign[3][0] == 'name') { + var looper = assign[2][1]; + var helper = assign[3][1]; + if (definitions[helper] == 1 && seenUses[looper] == namings[looper] && + !helperReplacements[helper] && !helperReplacements[looper]) { + loopers.push(looper); + helpers.push(helper); + } + } + } + } + if (loopers.length < assigns.length) return; // TODO: handle the case where can can just eliminate one. (we can't optimize the break, but we can remove the var at least) + for (var l = 0; l < loopers.length; l++) { + var looper = loopers[l]; + var helper = helpers[l]; + // the remaining issue is whether loopers are used after the assignment to helper and before the last line (where we assign to it) + var found = -1; + for (var i = stats.length-2; i >= 0; i--) { + var curr = stats[i]; + if (curr[0] == 'stat' && curr[1][0] == 'assign') { + var currAssign = curr[1]; + if (currAssign[1] === true && currAssign[2][0] == 'name') { + var to = currAssign[2][1]; + if (to == helper) { + found = i; + break; + } + } + } + } + if (found < 0) return; + var looperUsed = false; + for (var i = found+1; i < stats.length && !looperUsed; i++) { + var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition + traverse(curr, function(node, type) { + if (type == 'name' && node[1] == looper) { + looperUsed = true; + return true; + } + }); + } + if (looperUsed) return; + } + // hurrah! this is safe to do + for (var l = 0; l < loopers.length; l++) { + var looper = loopers[l]; + var helper = helpers[l]; + varsToRemove[helper] = 2; + traverse(node, function(node, type) { // replace all appearances of helper with looper + if (type == 'name' && node[1] == helper) node[1] = looper; + }); + helperReplacements[helper] = looper; // replace all future appearances of helper with looper + helperReplacements[looper] = looper; // avoid any further attempts to optimize looper in this manner (seenUses is wrong anyhow, too) + } + // simplify the if. we remove the if branch, leaving only the else + if (flip) { + last[1] = simplifyNotCompsDirect(['unary-prefix', '!', last[1]]); + last[2] = last[3]; + } + last.pop(); + } + } + } }); if (asm) { @@ -2455,6 +2698,27 @@ function fixDotZero(js) { }); } +function asmLoopOptimizer(ast) { + traverseGeneratedFunctions(ast, function(fun) { + // This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops + // into shapes that might confuse other passes + traverse(fun, function(node, type) { + if (type == 'while' && node[1][0] == 'num' && node[1][1] == 1 && node[2][0] == 'block') { + // while (1) { .. if (..) { break } } ==> do { .. } while(..) + var stats = node[2][1]; + var last = stats[stats.length-1]; + if (last && last[0] == 'if' && !last[3] && last[2][0] == 'block' && last[2][1][0][0] == 'break' && !last[2][1][0][1]) { + var conditionToBreak = last[1]; + stats.pop(); + node[0] = 'do'; + node[1] = simplifyNotCompsDirect(['unary-prefix', '!', conditionToBreak]); + return node; + } + } + }); + }); +} + // Passes table var compress = false, printMetadata = true, asm = false, last = false; @@ -2498,6 +2762,7 @@ arguments_.slice(1).forEach(function(arg) { passes[arg](ast); }); if (asm && last) { + asmLoopOptimizer(ast); prepDotZero(ast); } var js = astToSrc(ast, compress), old; |