diff options
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 164 |
1 files changed, 105 insertions, 59 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 21d521fd..c8766bc6 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -140,6 +140,8 @@ var ALTER_FLOW = set('break', 'continue', 'return'); var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch'); var CONTINUE_CAPTURERS = LOOP; +var FUNCTIONS_THAT_ALWAYS_THROW = set('abort', '___resumeException', '___cxa_throw', '___cxa_rethrow'); + var NULL_NODE = ['name', 'null']; var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]]; var TRUE_NODE = ['unary-prefix', '!', ['num', 0]]; @@ -722,14 +724,19 @@ function simplifyExpressions(ast) { if (correct === 'HEAP32') { define[3] = ['binary', '|', define[3], ['num', 0]]; } else { - define[3] = ['unary-prefix', '+', define[3]]; + define[3] = makeAsmCoercion(define[3], asmPreciseF32 ? ASM_FLOAT : ASM_DOUBLE); } // 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]; + var correctType; + switch(asmData.vars[v]) { + case ASM_INT: correctType = asmPreciseF32 ? ASM_FLOAT : ASM_DOUBLE; break; + case ASM_FLOAT: case ASM_DOUBLE: correctType = ASM_INT; break; + } + asmData.vars[v] = correctType; } } denormalizeAsm(ast, asmData); @@ -1138,7 +1145,9 @@ function optimizeShiftsAggressive(ast) { // or such. Simplifying these saves space and time. function simplifyNotCompsDirect(node) { if (node[0] === 'unary-prefix' && node[1] === '!') { - if (node[2][0] === 'binary') { + // de-morgan's laws do not work on floats, due to nans >:( + if (node[2][0] === 'binary' && (!asm || (((node[2][2][0] === 'binary' && node[2][2][1] === '|') || node[2][2][0] === 'num') && + ((node[2][3][0] === 'binary' && node[2][3][1] === '|') || node[2][3][0] === 'num')))) { switch(node[2][1]) { case '<': return ['binary', '>=', node[2][2], node[2][3]]; case '>': return ['binary', '<=', node[2][2], node[2][3]]; @@ -1577,7 +1586,7 @@ function makeAsmVarDef(v, type) { case ASM_INT: return [v, ['num', 0]]; case ASM_DOUBLE: return [v, ['unary-prefix', '+', ['num', 0]]]; case ASM_FLOAT: return [v, ['call', ['name', 'Math_fround'], [['num', 0]]]]; - default: throw 'wha?'; + default: throw 'wha? ' + JSON.stringify([node, type]) + new Error().stack; } } @@ -1593,6 +1602,7 @@ function normalizeAsm(func) { params: {}, // ident => ASM_* type vars: {}, // ident => ASM_* type inlines: [], // list of inline assembly copies + ret: undefined, }; // process initial params var stats = func[3]; @@ -1629,7 +1639,7 @@ function normalizeAsm(func) { } i++; } - // finally, look for other var definitions and collect them + // look for other var definitions and collect them while (i < stats.length) { traverse(stats[i], function(node, type) { if (type === 'var') { @@ -1658,6 +1668,11 @@ function normalizeAsm(func) { }); i++; } + // look for final 'return' statement to get return type. + var retStmt = stats[stats.length - 1]; + if (retStmt && retStmt[0] === 'return' && retStmt[1]) { + data.ret = detectAsmCoercion(retStmt[1]); + } //printErr('normalized \n\n' + astToSrc(func) + '\n\nwith: ' + JSON.stringify(data)); return data; } @@ -1710,6 +1725,17 @@ function denormalizeAsm(func, data) { } }); } + // ensure that there's a final 'return' statement if needed. + if (data.ret !== undefined) { + var retStmt = stats[stats.length - 1]; + if (!retStmt || retStmt[0] !== 'return') { + var retVal = ['num', 0]; + if (data.ret !== ASM_INT) { + retVal = makeAsmCoercion(retVal, data.ret); + } + stats.push(['return', retVal]); + } + } //printErr('denormalized \n\n' + astToSrc(func) + '\n\n'); } @@ -2050,6 +2076,7 @@ function registerize(ast) { params: {}, vars: {}, inlines: asmData.inlines, + ret: asmData.ret, }; for (var i = 1; i < nextReg; i++) { var reg = fullNames[i]; @@ -2125,11 +2152,11 @@ function registerizeHarder(ast) { // For each block we store: // * a single entry junction // * a single exit junction - // * any preconditions satisfied at entry to the block // * a 'use' and 'kill' set of names for the block // * full sequence of 'name' and 'assign' nodes in the block // * whether each such node appears as part of a larger expression // (and therefore cannot be safely eliminated) + // * set of labels that can be used to jump to this block var junctions = []; var blocks = []; @@ -2157,18 +2184,24 @@ function registerizeHarder(ast) { if (id === undefined || id === null) { id = addJunction(); } - joinJunction(id); + joinJunction(id, true); return id; } - function setJunction(id) { + function setJunction(id, force) { // Set the next entry junction to the given id. // This can be used to enter at a previously-declared point. + // You can't return to a junction with no incoming blocks + // unless the 'force' parameter is specified. assert(nextBasicBlock.nodes.length === 0, 'refusing to abandon an in-progress basic block') - currEntryJunction = id; + if (force || setSize(junctions[id].inblocks) > 0) { + currEntryJunction = id; + } else { + currEntryJunction = null; + } } - function joinJunction(id) { + function joinJunction(id, force) { // Complete the pending basic block by exiting at this position. // This can be used to exit at a previously-declared point. if (currEntryJunction !== null) { @@ -2179,8 +2212,8 @@ function registerizeHarder(ast) { junctions[id].inblocks[nextBasicBlock.id] = 1; blocks.push(nextBasicBlock); } - nextBasicBlock = { id: null, entry: null, exit: null, pre: {}, nodes: [], isexpr: [], use: {}, kill: {} }; - currEntryJunction = id; + nextBasicBlock = { id: null, entry: null, exit: null, labels: {}, nodes: [], isexpr: [], use: {}, kill: {} }; + setJunction(id, force); return id; } @@ -2220,7 +2253,7 @@ function registerizeHarder(ast) { assert(false, 'unknown jump node type'); } } - setJunction(null); + currEntryJunction = null; } function addUseNode(node) { @@ -2259,29 +2292,10 @@ function registerizeHarder(ast) { return node; } - function addPreCondTrue(node) { - // Add pre-conditions implied by truth of the - // given node to the current basic block. - assert(nextBasicBlock.nodes.length === 0, 'cant add preconditions to an in-progress basic block') - if (node[0] === 'binary' && node[1] === '==') { - var lhs = lookThroughCasts(node[2]); - var rhs = lookThroughCasts(node[3]); - if (lhs[0] === 'name' && rhs[0] === 'num') { - nextBasicBlock.pre[lhs[1]] = ['==', rhs[1]]; - } - } - } - - function addPreCondFalse(node) { - // Add pre-conditions implied by falsehood of the - // given node to the current basic block. - assert(nextBasicBlock.nodes.length === 0, 'cant add preconditions to an in-progress basic block') - if (node[0] === 'binary' && node[1] === '==') { - var lhs = lookThroughCasts(node[2]); - var rhs = lookThroughCasts(node[3]); - if (lhs[0] === 'name' && rhs[0] === 'num') { - nextBasicBlock.pre[lhs[1]] = ['!=', rhs[1]]; - } + function addBlockLabel(node) { + assert(nextBasicBlock.nodes.length === 0, 'cant add label to an in-progress basic block') + if (node[0] === 'num') { + nextBasicBlock.labels[node[1]] = 1; } } @@ -2341,13 +2355,19 @@ function registerizeHarder(ast) { buildFlowGraph(node[1]); isInExpr--; var jEnter = markJunction(); - addPreCondTrue(node[1]); + var jExit = addJunction(); if (node[2]) { + // Detect and mark "if (label == N) { <labelled block> }". + if (node[1][0] === 'binary' && node[1][1] === '==') { + var lhs = lookThroughCasts(node[1][2]); + if (lhs[0] === 'name' && lhs[1] === 'label') { + addBlockLabel(lookThroughCasts(node[1][3])); + } + } buildFlowGraph(node[2]); } - var jExit = markJunction(); + joinJunction(jExit); setJunction(jEnter); - addPreCondFalse(node[1]); if (node[3]) { buildFlowGraph(node[3]); } @@ -2357,13 +2377,12 @@ function registerizeHarder(ast) { isInExpr++; buildFlowGraph(node[1]); var jEnter = markJunction(); - addPreCondTrue(node[1]); + var jExit = addJunction(); if (node[2]) { buildFlowGraph(node[2]); } - var jExit = markJunction(); + joinJunction(jExit); setJunction(jEnter); - addPreCondFalse(node[1]); if (node[3]) { buildFlowGraph(node[3]); } @@ -2390,7 +2409,6 @@ function registerizeHarder(ast) { isInExpr--; joinJunction(jLoop); pushActiveLabels(jCond, jExit); - addPreCondTrue(node[1]); buildFlowGraph(node[2]); popActiveLabels(); joinJunction(jCond); @@ -2462,38 +2480,46 @@ function registerizeHarder(ast) { case 'switch': // Emscripten generates switch statements of a very limited // form: all case clauses are numeric literals, and all - // case bodies end with a break. So it's basically equivalent - // to a multi-way 'if' statement. + // case bodies end with a (maybe implicit) break. So it's + // basically equivalent to a multi-way 'if' statement. isInExpr++; buildFlowGraph(node[1]); isInExpr--; + var condition = lookThroughCasts(node[1]); var jCheckExit = markJunction(); var jExit = addJunction(); pushActiveLabels(null, jExit); var hasDefault = false; for (var i=0; i<node[2].length; i++) { setJunction(jCheckExit); + // All case clauses are either 'default' or a numeric literal. if (!node[2][i][0]) { hasDefault = true; } else { - if (node[2][i][0][0] !== 'num') { - if (node[2][i][0][0] !== 'unary-prefix' || node[2][i][0][2][0] !== 'num') { - assert(false, 'non-numeric switch case clause'); - } + // Detect switches dispatching to labelled blocks. + if (condition[0] === 'name' && condition[1] === 'label') { + addBlockLabel(lookThroughCasts(node[2][i][0])); } - addPreCondTrue(['binary', '==', node[1], node[2][i][0]]); } for (var j = 0; j < node[2][i][1].length; j++) { buildFlowGraph(node[2][i][1][j]); } - if (currEntryJunction !== null, 'switch case body did not break'); + // Control flow will never actually reach the end of the case body. + // If there's live code here, assume it jumps to case exit. + if (currEntryJunction !== null && nextBasicBlock.nodes.length > 0) { + if (node[2][i][0]) { + markNonLocalJump('return'); + } else { + joinJunction(jExit); + } + } } // If there was no default case, we also need an empty block // linking straight from the test evaluation to the exit. if (!hasDefault) { setJunction(jCheckExit); } - markJunction(jExit); + joinJunction(jExit); popActiveLabels() break; case 'return': @@ -2553,6 +2579,13 @@ function registerizeHarder(ast) { } } isInExpr--; + // If the call is statically known to throw, + // treat it as a jump to function exit. + if (!isInExpr && node[1][0] === 'name') { + if (node[1][1] in FUNCTIONS_THAT_ALWAYS_THROW) { + markNonLocalJump('return'); + } + } break; case 'seq': case 'sub': @@ -2592,18 +2625,17 @@ function registerizeHarder(ast) { FINDLABELLEDBLOCKS: for (var i = 0; i < blocks.length; i++) { var block = blocks[i]; - // Does it have a specific label value as precondition? - var labelCond = block.pre['label']; - if (labelCond && labelCond[0] === '==') { + // Does it have any labels as preconditions to its entry? + for (var labelVal in block.labels) { // If there are multiple blocks with the same label, all bets are off. // This seems to happen sometimes for short blocks that end with a return. // TODO: it should be safe to merge the duplicates if they're identical. - if (labelCond[1] in labelledBlocks) { + if (labelVal in labelledBlocks) { labelledBlocks = {}; labelledJumps = []; break FINDLABELLEDBLOCKS; } - labelledBlocks[labelCond[1]] = block; + labelledBlocks[labelVal] = block; } // Does it assign a specific label value at exit? if ('label' in block.kill) { @@ -3096,6 +3128,7 @@ function registerizeHarder(ast) { params: {}, vars: {}, inlines: asmData.inlines, + ret: asmData.ret, }; for (var i = 1; i < nextReg; i++) { var reg; @@ -5066,6 +5099,17 @@ function safeHeap(ast) { }); } +function optimizeFrounds(ast) { + // collapse fround(fround(..)), which can happen due to elimination + function fix(node) { + traverseChildren(node, fix); + if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround' && node[2][0][0] === 'call' && node[2][0][1][0] === 'name' && node[2][0][1][1] === 'Math_fround') { + return node[2][0]; + } + } + traverseChildren(ast, fix); +} + // 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) @@ -5094,7 +5138,7 @@ function fixDotZero(js) { function asmLastOpts(ast) { traverseGeneratedFunctions(ast, function(fun) { traverse(fun, function(node, type) { - if (type === 'while' && node[1][0] === 'num' && node[1][1] === 1 && node[2][0] === 'block') { + if (type === 'while' && node[1][0] === 'num' && node[1][1] === 1 && node[2][0] === 'block' && node[2].length == 2) { // 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 @@ -5153,7 +5197,7 @@ function asmLastOpts(ast) { // Passes table -var minifyWhitespace = false, printMetadata = true, asm = false, last = false; +var minifyWhitespace = false, printMetadata = true, asm = false, asmPreciseF32 = false, last = false; var passes = { // passes @@ -5177,11 +5221,13 @@ var passes = { relocate: relocate, outline: outline, safeHeap: safeHeap, + optimizeFrounds: optimizeFrounds, // flags minifyWhitespace: function() { minifyWhitespace = true }, noPrintMetadata: function() { printMetadata = false }, asm: function() { asm = true }, + asmPreciseF32: function() { asmPreciseF32 = true }, last: function() { last = true }, }; |