diff options
author | Alon Zakai <alonzakai@gmail.com> | 2014-02-10 15:16:53 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2014-02-10 15:16:53 -0800 |
commit | b5f9734ee91a55bdc7176ee28ed125beed02c353 (patch) | |
tree | 67d8433cfb8f2d7aad2b7f4bdd1ffd02ed8ffd5b /tools | |
parent | ed896c9c00edc7c7bdd4c4cfaca057509c3cd628 (diff) | |
parent | fa4f4857166885f1dcfc3a85712473fb4711f08a (diff) |
Merge pull request #2071 from rfk/rfk/fix-test-longjmp-throw-asm3
Fix asm3.test_longjmp_throw
Diffstat (limited to 'tools')
-rw-r--r-- | tools/js-optimizer.js | 132 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-regs-harder-output.js | 3 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-regs-harder.js | 3 |
3 files changed, 85 insertions, 53 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 6724e77f..fc8b0561 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]]; @@ -1600,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]; @@ -1636,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') { @@ -1665,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; } @@ -1717,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'); } @@ -2057,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]; @@ -2132,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 = []; @@ -2164,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) { @@ -2186,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; } @@ -2227,7 +2253,7 @@ function registerizeHarder(ast) { assert(false, 'unknown jump node type'); } } - setJunction(null); + currEntryJunction = null; } function addUseNode(node) { @@ -2266,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; } } @@ -2348,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]); } @@ -2364,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]); } @@ -2397,7 +2409,6 @@ function registerizeHarder(ast) { isInExpr--; joinJunction(jLoop); pushActiveLabels(jCond, jExit); - addPreCondTrue(node[1]); buildFlowGraph(node[2]); popActiveLabels(); joinJunction(jCond); @@ -2469,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': @@ -2560,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': @@ -2599,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) { @@ -3103,6 +3128,7 @@ function registerizeHarder(ast) { params: {}, vars: {}, inlines: asmData.inlines, + ret: asmData.ret, }; for (var i = 1; i < nextReg; i++) { var reg; diff --git a/tools/test-js-optimizer-asm-regs-harder-output.js b/tools/test-js-optimizer-asm-regs-harder-output.js index 448fb67a..e1df42cb 100644 --- a/tools/test-js-optimizer-asm-regs-harder-output.js +++ b/tools/test-js-optimizer-asm-regs-harder-output.js @@ -123,6 +123,9 @@ function linkedVars() { i1 = i1 - 1; } } + if (i2 < i1) { + break; + } } return i2 + i1; } diff --git a/tools/test-js-optimizer-asm-regs-harder.js b/tools/test-js-optimizer-asm-regs-harder.js index cf44c1dd..0231a215 100644 --- a/tools/test-js-optimizer-asm-regs-harder.js +++ b/tools/test-js-optimizer-asm-regs-harder.js @@ -143,6 +143,9 @@ function linkedVars() { outer1 = inner1_1; outer2 = inner2_1; } + if (outer1 < outer2) { + break; + } } return outer1 + outer2; } |