diff options
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 120 |
1 files changed, 95 insertions, 25 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 32ed1cce..09791150 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -403,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? @@ -592,6 +609,7 @@ function simplifyExpressionsPre(ast) { }); } + simplifySignExtends(ast); simplifyBitops(ast); joinAdditions(ast); // simplifyZeroComp(ast); TODO: investigate performance @@ -1487,7 +1505,7 @@ function registerize(ast) { // 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 }); @@ -1578,7 +1596,15 @@ function registerize(ast) { 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]) { @@ -1599,24 +1625,61 @@ function registerize(ast) { } } } 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 @@ -1776,10 +1839,10 @@ function registerize(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', 'label'); // 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) { // Find variables that have a single use, and if they can be eliminated, do so @@ -1787,7 +1850,6 @@ function eliminate(ast, memSafe) { if (asm) var asmData = normalizeAsm(func); //printErr('eliminate in ' + func[1]); - // First, find the potentially eliminatable functions: that have one definition and one use var definitions = {}; var uses = {}; @@ -1841,12 +1903,7 @@ function eliminate(ast, memSafe) { }); // we cannot eliminate variables if there is a switch - if (traverse(func, function(node, type) { - if (type == 'switch') return true; - })) { - if (asm) denormalizeAsm(func, asmData); - return; - } + 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 @@ -2153,13 +2210,14 @@ function eliminate(ast, memSafe) { 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]; @@ -2180,7 +2238,6 @@ function eliminate(ast, memSafe) { traverseInOrder(node[2]); } else { tracked = {}; - abort = true; } } else if (type == 'return') { if (node[1]) traverseInOrder(node[1]); @@ -2188,6 +2245,17 @@ function eliminate(ast, memSafe) { 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)); @@ -2234,13 +2302,15 @@ function eliminate(ast, memSafe) { } } 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]; |