diff options
author | Ryan Kelly <ryan@rfk.id.au> | 2013-12-16 01:47:46 +1100 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2014-01-21 17:50:46 -0800 |
commit | 68d6af077f116819bddb5797d9f8c8bd37998bff (patch) | |
tree | ba14f6bd49e3c3acdd334c554224a0524640f470 | |
parent | 8a0a84373d188f9bcf60ca0121706305d4530dd3 (diff) |
Registerize based on full liveness analysis.
This does a much-more-expensive but much-more-thorough registerization
pass based a live-variable analysis for each function.
azakai: perform this on -O3 and above
-rwxr-xr-x | emcc | 5 | ||||
-rw-r--r-- | tools/js-optimizer.js | 1061 |
2 files changed, 1065 insertions, 1 deletions
@@ -2017,7 +2017,10 @@ try: js_optimizer_extra_info['sizeToOutline'] = shared.Settings.OUTLINING_LIMIT if (not closure or shared.Settings.ASM_JS) and shared.Settings.RELOOP and debug_level < 3: - js_optimizer_queue += ['registerize'] + if shared.Settings.ASM_JS and opt_level >= 3: + js_optimizer_queue += ['registerizeHarder'] + else: + js_optimizer_queue += ['registerize'] if opt_level > 0: if debug_level < 2 and shared.Settings.ASM_JS: js_optimizer_queue += ['minifyNames'] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 98cb4c49..39456d40 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -2066,6 +2066,1066 @@ function registerize(ast) { }); } + +// Assign variables to 'registers', coalescing them onto a smaller number of shared +// variables. +// +// This does the same job as 'registerize' above, but burns a lot more cycles trying +// to reduce the total number of register variables. Key points about the operation: +// +// * we decompose the AST into a flow graph and perform a full liveness +// analysis, to determine which variables are live at each point. +// +// * variables that are live concurrently are assigned to different registers. +// +// * variables that are linked via 'x=y' style statements are assigned the same +// register if possible, so that the redundant assignment can be removed. +// (e.g. assignments used to pass state around through loops). +// +// * any code that cannot be reached through the flow-graph is removed. +// (e.g. redundant break statements like 'break L123; break;'). +// +// * any assignments that we can prove are not subsequently used are removed. +// (e.g. unnecessary assignments to the 'label' variable). +// +function registerizeHarder(ast) { + assert(asm); + + traverseGeneratedFunctions(ast, function(fun) { + + var asmData = normalizeAsm(fun); + + var localVars = asmData.vars; + for (var name in asmData.params) { + localVars[name] = asmData.params[name]; + } + + // Utilities for allocating register variables. + // We need distinct register pools for each type of variable. + + var allRegsByType = [{}, {}, {}]; + var regPrefixByType = ['i', 'd', 'f']; + var nextReg = 1; + + function createReg(forName) { + // Create a new register of type suitable for the given variable name. + var allRegs = allRegsByType[localVars[forName]]; + reg = nextReg++; + allRegs[reg] = regPrefixByType[localVars[forName]] + reg; + return reg; + } + + // Traverse the tree in execution order and synthesize a basic flow-graph. + // It's convenient to build a kind of "dual" graph where the nodes identify + // the junctions between blocks at which control-flow may branch, and each + // basic block is an edge connecting two such junctions. + // For each junction we store: + // * set of blocks that originate at the junction + // * set of blocks that terminate at the junction + // 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) + + var junctions = []; + var blocks = []; + var curEntryJunction = null; + var nextBasicBlock = null; + var isInExpr = 0; + var activeLabels = [{}]; + var nextLoopLabel = null; + + var ENTRY_JUNCTION = 0; + var EXIT_JUNCTION = 1; + var ENTRY_BLOCK = 0; + + function addJunction() { + // Create a new junction, without inserting it into the graph. + // This is useful for e.g. pre-allocating an exit node. + var id = junctions.length; + junctions[id] = {id: id, inblocks: {}, outblocks: {}}; + return id; + } + + function markJunction(id) { + // Mark current traversal location as a junction. + // This makes a new basic block exiting at this position. + if (id === undefined || id === null) { + id = addJunction(); + } + joinJunction(id); + return id; + } + + function setJunction(id) { + // Set the next entry junction to the given id. + // This can be used to enter at a previously-declared point. + assert(nextBasicBlock.nodes.length === 0, 'refusing to abandon an in-progress basic block') + curEntryJunction = id; + } + + function joinJunction(id) { + // Complete the pending basic block by exiting at this position. + // This can be used to exit at a previously-declared point. + if (curEntryJunction !== null) { + nextBasicBlock.id = blocks.length; + nextBasicBlock.entry = curEntryJunction; + nextBasicBlock.exit = id; + junctions[curEntryJunction].outblocks[nextBasicBlock.id] = 1; + junctions[id].inblocks[nextBasicBlock.id] = 1; + blocks.push(nextBasicBlock); + } + nextBasicBlock = { id: null, entry: null, exit: null, pre: {}, nodes: [], isexpr: [], use: {}, kill: {} }; + curEntryJunction = id; + return id; + } + + function pushActiveLabels(onContinue, onBreak) { + // Push the target junctions for continuing/breaking a loop. + // This should be called before traversing into a loop. + var newLabels = copy(activeLabels[activeLabels.length-1]); + newLabels[null] = [onContinue, onBreak]; + if (nextLoopLabel) { + newLabels[nextLoopLabel] = [onContinue, onBreak]; + nextLoopLabel = null; + } + activeLabels.push(newLabels); + } + + function popActiveLabels() { + // Pop the target junctions for continuing/breaking a loop. + // This should be called after traversing into a loop. + activeLabels.pop(); + } + + function markNonLocalJump(type, label) { + // Complete a block via 'return', 'break' or 'continue'. + // This joins the targetted junction and then sets the current junction to null. + // Any code traversed before we get back an existing junction is dead code. + if (type === 'return') { + joinJunction(EXIT_JUNCTION); + } else { + label = label ? label : null; + var targets = activeLabels[activeLabels.length-1][label]; + assert(targets, 'jump to unknown label'); + if (type === 'continue') { + joinJunction(targets[0]); + } else if (type === 'break') { + joinJunction(targets[1]); + } else { + assert(false, 'unknown jump node type'); + } + } + setJunction(null); + } + + function addUseNode(node) { + // Mark a use of the given name node in the current basic block. + assert(node[0] === 'name', 'not a use node'); + var name = node[1]; + if (name in localVars) { + nextBasicBlock.nodes.push(node); + nextBasicBlock.isexpr.push(isInExpr); + if (!nextBasicBlock.kill[name]) { + nextBasicBlock.use[name] = 1; + } + } + } + + function addKillNode(node) { + // Mark an assignment to the given name node in the current basic block. + assert(node[0] === 'assign', 'not a kill node'); + assert(node[1] === true, 'not a kill node'); + assert(node[2][0] === 'name', 'not a kill node'); + var name = node[2][1]; + if (name in localVars) { + nextBasicBlock.nodes.push(node); + nextBasicBlock.isexpr.push(isInExpr); + nextBasicBlock.kill[name] = 1; + } + } + + function lookThroughCasts(node) { + // Look through value-preserving casts, like "x | 0" => "x" + if (node[0] === 'binary' && node[1] === '|') { + if (node[3][0] === 'num' && node[3][1] === 0) { + return lookThroughCasts(node[2]); + } + } + 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 isTrueNode(node) { + // Check if the given node is statically truthy. + return (node[0] === 'num' && node[1] != 0); + } + + function isFalseNode(node) { + // Check if the given node is statically falsy. + return (node[0] === 'num' && node[1] == 0); + } + + function morphNode(node, newNode) { + // In-place morph a node into some other type of node. + var i = 0; + while (i < node.length && i < newNode.length) { + node[i] = newNode[i]; + i++; + } + while (i < newNode.length) { + node.push(newNode[i]); + i++; + } + if (i < node.length) { + node.splice(i); + } + } + + function buildFlowGraph(node) { + // Recursive function to build up the flow-graph. + // It walks the tree in execution order, calling the above state-management + // functions at appropriate points in the traversal. + var type = node[0]; + + // Any code traversed without an active entry junction must be dead, + // as the resulting block could never be entered. Let's remove it. + if (curEntryJunction === null && junctions.length > 0) { + morphNode(node, ['block', []]); + return; + } + + // Traverse each node type according to its particular control-flow semantics. + switch (type) { + case 'defun': + var jEntry = markJunction(); + assert(jEntry === ENTRY_JUNCTION); + var jExit = addJunction(); + assert(jExit === EXIT_JUNCTION); + for (var i = 0; i < node[3].length; i++) { + buildFlowGraph(node[3][i]); + } + joinJunction(jExit); + break; + case 'if': + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + var jEnter = markJunction(); + addPreCondTrue(node[1]); + if (node[2]) { + buildFlowGraph(node[2]); + } + var jExit = markJunction(); + setJunction(jEnter); + addPreCondFalse(node[1]); + if (node[3]) { + buildFlowGraph(node[3]); + } + joinJunction(jExit); + break; + case 'conditional': + isInExpr++; + buildFlowGraph(node[1]); + var jEnter = markJunction(); + addPreCondTrue(node[1]); + if (node[2]) { + buildFlowGraph(node[2]); + } + var jExit = markJunction(); + setJunction(jEnter); + addPreCondFalse(node[1]); + if (node[3]) { + buildFlowGraph(node[3]); + } + joinJunction(jExit); + isInExpr--; + break; + case 'while': + // Special-case "while (1) {}" to use fewer junctions, + // since emscripten generates a lot of these. + if (isTrueNode(node[1])) { + var jLoop = markJunction(); + var jExit = addJunction(); + pushActiveLabels(jLoop, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jLoop); + setJunction(jExit); + } else { + var jCond = markJunction(); + var jLoop = addJunction(); + var jExit = addJunction(); + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + joinJunction(jLoop); + pushActiveLabels(jCond, jExit); + addPreCondTrue(node[1]); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jCond); + // An empty basic-block linking condition exit to loop exit. + setJunction(jLoop); + joinJunction(jExit); + } + break; + case 'do': + // Special-case "do {} while (1)" and "do {} while (0)" to use + // fewer junctions, since emscripten generates a lot of these. + if (isFalseNode(node[1])) { + var jExit = addJunction(); + pushActiveLabels(jExit, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jExit); + } else if (isTrueNode(node[1])) { + var jLoop = markJunction(); + var jExit = addJunction(); + pushActiveLabels(jLoop, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jLoop); + setJunction(jExit); + } else { + var jLoop = markJunction(); + var jCond = addJunction(); + var jCondExit = addJunction(); + var jExit = addJunction(); + pushActiveLabels(jCond, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jCond); + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + joinJunction(jCondExit); + joinJunction(jLoop); + setJunction(jCondExit); + joinJunction(jExit) + } + break; + case 'for': + var jTest = addJunction(); + var jBody = addJunction(); + var jStep = addJunction(); + var jExit = addJunction(); + buildFlowGraph(node[1]); + joinJunction(jTest); + isInExpr++; + buildFlowGraph(node[2]); + isInExpr--; + joinJunction(jBody); + pushActiveLabels(jStep, jExit); + buildFlowGraph(node[4]); + popActiveLabels(); + joinJunction(jStep); + buildFlowGraph(node[3]); + joinJunction(jTest); + setJunction(jBody); + joinJunction(jExit); + break; + case 'label': + assert(node[2][0] in BREAK_CAPTURERS, 'label on non-loop, non-switch statement') + nextLoopLabel = node[1]; + buildFlowGraph(node[2]); + break; + case 'switch': + // This is horrific. It need to capture the default flow-through + // logic of sequential case bodies, as well as the theoretical + // sequential evaluation of each case clause. + // TODO: simplify based on asmjs switch-statement restrictions. + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + var jCheckExit = markJunction(); + var jExit = addJunction(); + pushActiveLabels(null, jExit); + // Process all cases as a sequential chain of checks. + // Process all case bodies as one big flow-through statement. + // They might break themselves out of it but this implements the + // default fall-through case logic. + var hasDefault = false; + var jPrevCaseExit = jCheckExit; + var jPrevBodyExit = jCheckExit; + for (var i=0; i<node[2].length; i++) { + // In the gerneral case we'll need a basic block for the case clause. + // Try to avoid it for common, simple, non-var-using cases. + if (!node[2][i][0]) { + hasDefault = true; + } else { + if (node[2][i][0][0] !== 'num') { + setJunction(jPrevCaseExit); + isInExpr++; + buildFlowGraph(node[2][i][0]); + isInExpr--; + jPrevCaseExit = markJunction(); + } + } + // The next case body flows from the exit of the prev one, + // or may be entered directly from the case statement exit. + setJunction(jPrevCaseExit); + if (jPrevBodyExit !== jCheckExit) { + joinJunction(jPrevBodyExit); + } + for (var j = 0; j < node[2][i][1].length; j++) { + buildFlowGraph(node[2][i][1][j]); + } + jPrevBodyExit = markJunction(); + } + markJunction(jExit); + // If there was no default case, we also need an empty block + // linking straight from entry to exit. + if (!hasDefault && jCheckExit !== jPrevBodyExit) { + setJunction(jCheckExit); + joinJunction(jExit); + } + popActiveLabels() + break; + case 'return': + if (node[1]) { + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + } + markNonLocalJump(type); + break; + case 'break': + case 'continue': + markNonLocalJump(type, node[1]); + break; + case 'assign': + isInExpr++; + buildFlowGraph(node[3]); + isInExpr--; + if (node[1] === true && node[2][0] === 'name') { + addKillNode(node); + } else { + buildFlowGraph(node[2]); + } + break; + case 'name': + addUseNode(node); + break; + case 'block': + case 'toplevel': + if (node[1]) { + for (var i = 0; i < node[1].length; i++) { + buildFlowGraph(node[1][i]); + } + } + break; + case 'stat': + buildFlowGraph(node[1]); + break; + case 'unary-prefix': + case 'unary-postfix': + isInExpr++; + buildFlowGraph(node[2]); + isInExpr--; + break; + case 'binary': + isInExpr++; + buildFlowGraph(node[2]); + buildFlowGraph(node[3]); + isInExpr--; + break; + case 'call': + isInExpr++; + buildFlowGraph(node[1]); + if (node[2]) { + for (var i = 0; i < node[2].length; i++) { + buildFlowGraph(node[2][i]); + } + } + isInExpr--; + break; + case 'seq': + case 'sub': + isInExpr++; + buildFlowGraph(node[1]); + buildFlowGraph(node[2]); + isInExpr--; + break; + case 'dot': + case 'throw': + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + break; + case 'num': + case 'string': + case 'var': + break; + default: + printErr(JSON.stringify(node)); + assert(false, 'unsupported node type: ' + type); + } + } + buildFlowGraph(fun); + + assert(setSize(junctions[ENTRY_JUNCTION].inblocks) === 0, 'function entry must have no incoming blocks'); + assert(setSize(junctions[EXIT_JUNCTION].outblocks) === 0, 'function exit must have no outgoing blocks'); + assert(blocks[ENTRY_BLOCK].entry === ENTRY_JUNCTION, 'block zero must be the initial block'); + + // Fix up implicit jumps done by assigning to the 'label' variable. + // If a block ends with an assignment to 'label' and there's another block + // with that value of 'label' as precondition, we tweak the flow graph so + // that the former jumps straight to the later. + + var labelledBlocks = {}; + var labelledJumps = []; + 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] === '==') { + // 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. + if (labelCond[1] in labelledBlocks) { + labelledBlocks = {}; + labelledJumps = []; + break FINDLABELLEDBLOCKS; + } + labelledBlocks[labelCond[1]] = block; + } + // Does it assign a specific label value at exit? + if ('label' in block.kill) { + var finalNode = block.nodes[block.nodes.length - 1]; + if (finalNode[0] === 'assign' && finalNode[2][1] === 'label') { + // If labels are computed dynamically then all bets are off. + // This can happen due to indirect branching in llvm output. + if (finalNode[3][0] !== 'num') { + labelledBlocks = {}; + labelledJumps = []; + break FINDLABELLEDBLOCKS; + } + labelledJumps.push([finalNode[3][1], block]); + } else { + // If label is assigned a non-zero value elsewhere in the block + // then all bets are off. This can happen e.g. due to outlining + // saving/restoring label to the stack. + for (var j = 0; j < block.nodes.length - 1; j++) { + if (block.nodes[j][0] === 'assign' && block.nodes[j][2][1] === 'label') { + if (block.nodes[j][3][0] !== 'num' && block.nodes[j][3][1] !== 0) { + labelledBlocks = {}; + labelledJumps = []; + break FINDLABELLEDBLOCKS; + } + } + } + } + } + } + for (var labelVal in labelledBlocks) { + var block = labelledBlocks[labelVal]; + // Disconnect it from the graph, and create a + // new junction for jumps targetting this label. + delete junctions[block.entry].outblocks[block.id]; + block.entry = addJunction(); + junctions[block.entry].outblocks[block.id] = 1; + // Add a fake use of 'label' to keep it alive in predecessor. + block.use['label'] = 1; + block.nodes.unshift(['name', 'label']); + block.isexpr.unshift(1); + } + for (var i = 0; i < labelledJumps.length; i++) { + var labelVal = labelledJumps[i][0]; + var block = labelledJumps[i][1]; + var targetBlock = labelledBlocks[labelVal]; + if (targetBlock) { + // Redirect its exit to entry of the target block. + delete junctions[block.exit].inblocks[block.id]; + block.exit = targetBlock.entry; + junctions[block.exit].inblocks[block.id] = 1; + } + } + delete labelledBlocks; + delete labelledJumps; + + // Do a backwards data-flow analysis to determine the set of live + // variables at each junction, and to use this information to eliminate + // any unused assignments. + // We run two nested phases. The inner phase builds the live set for each + // junction. The outer phase uses this to try to eliminate redundant + // stores in each basic block, which might in turn affect liveness info. + + function analyzeJunction(junc) { + // Update the live set for this junction. + var live = {}; + for (var b in junc.outblocks) { + var block = blocks[b]; + var liveSucc = junctions[block.exit].live || {}; + for (var name in liveSucc) { + if (!(name in block.kill)) { + live[name] = 1; + } + } + for (var name in block.use) { + live[name] = 1; + } + } + junc.live = live; + } + + function analyzeBlock(block) { + // Update information about the behaviour of the block. + // This includes the standard 'use' and 'kill' information, + // plus a 'link' set naming values that flow through from entry + // to exit, possibly changing names via simple 'x=y' assignments. + // As we go, we eliminate assignments if the variable is not + // subsequently used. + var live = copy(junctions[block.exit].live); + var use = {}; + var kill = {}; + var link = {}; + var lastUseLoc = {}; + var firstDeadLoc = {}; + var lastKillLoc = {}; + for (var name in live) { + link[name] = name; + lastUseLoc[name] = block.nodes.length; + firstDeadLoc[name] = block.nodes.length; + } + for (var j = block.nodes.length - 1; j >=0 ; j--) { + var node = block.nodes[j]; + if (node[0] === 'name') { + var name = node[1]; + live[name] = 1; + use[name] = j; + if (lastUseLoc[name] === undefined) { + lastUseLoc[name] = j; + firstDeadLoc[name] = j; + } + } else { + var name = node[2][1]; + // We only keep assignments if they will be subsequently used. + if (name in live) { + kill[name] = 1; + delete use[name]; + delete live[name]; + firstDeadLoc[name] = j; + if (lastUseLoc[name] === undefined) { + lastUseLoc[name] = j; + } + if (lastKillLoc[name] === undefined) { + lastKillLoc[name] = j; + } + // If it's an "x=y" and "y" is not live, then we can create a + // flow-through link from "y" to "x". If not then there's no + // flow-through link for "x". + var oldLink = link[name]; + if (oldLink) { + delete link[name]; + if (node[3][0] === 'name') { + if (node[3][1] in localVars) { + link[node[3][1]] = oldLink; + } + } + } + } else { + // The result of this assignment is never used, so delete it. + // We may need to keep the RHS for its value or its side-effects. + function removeUnusedNodes(j, n) { + for (var name in lastUseLoc) { + lastUseLoc[name] -= n; + } + for (var name in lastKillLoc) { + lastKillLoc[name] -= n; + } + for (var name in firstDeadLoc) { + firstDeadLoc[name] -= n; + } + block.nodes.splice(j, n); + block.isexpr.splice(j, n); + } + if (block.isexpr[j] || hasSideEffects(node[3])) { + morphNode(node, node[3]); + removeUnusedNodes(j, 1); + } else { + var numUsesInExpr = 0; + traverse(node[3], function(node, type) { + if (type === 'name' && node[1] in localVars) { + numUsesInExpr++; + } + }); + morphNode(node, ['block', []]); + j = j - numUsesInExpr; + removeUnusedNodes(j, 1 + numUsesInExpr); + } + } + } + } + block.use = use; + block.kill = kill; + block.link = link; + block.lastUseLoc = lastUseLoc; + block.firstDeadLoc = firstDeadLoc; + block.lastKillLoc = lastKillLoc; + } + + var jWorklistMap = { EXIT_JUNCTION: 1 }; + var jWorklist = [EXIT_JUNCTION]; + var bWorklistMap = {}; + var bWorklist = []; + + // Be sure to visit every junction at least once. + // This avoids missing some vars because we disconnected them + // when processing the labelled jumps. + for (var i = junctions.length - 1; i >= EXIT_JUNCTION; i--) { + jWorklistMap[i] = 1; + jWorklist.push(i); + } + + while (jWorklist.length > 0) { + // Iterate on just the junctions until we get stable live sets. + // The first run of this loop will grow the live sets to their maximal size. + // Subsequent runs will shrink them based on eliminated in-block uses. + while (jWorklist.length > 0) { + var junc = junctions[jWorklist.pop()]; + delete jWorklistMap[junc.id]; + var oldLive = junc.live || null; + analyzeJunction(junc); + if (!sortedJsonCompare(oldLive, junc.live)) { + // Live set changed, updated predecessor blocks and junctions. + for (var b in junc.inblocks) { + if (!(b in bWorklistMap)) { + bWorklistMap[b] = 1; + bWorklist.push(b); + } + var jPred = blocks[b].entry; + if (!(jPred in jWorklistMap)) { + jWorklistMap[jPred] = 1; + jWorklist.push(jPred); + } + } + } + } + // Now update the blocks based on the calculated live sets. + while (bWorklist.length > 0) { + var block = blocks[bWorklist.pop()]; + delete bWorklistMap[block.id]; + var oldUse = block.use; + analyzeBlock(block); + if (!sortedJsonCompare(oldUse, block.use)) { + // The use set changed, re-process the entry junction. + if (!(block.entry in jWorklistMap)) { + jWorklistMap[block.entry] = 1; + jWorklist.push(block.entry); + } + } + } + } + + // Insist that all function parameters are alive at function entry. + // This ensures they will be assigned independent registers, even + // if they happen to be unused. + + for (var name in asmData.params) { + junctions[ENTRY_JUNCTION].live[name] = 1; + } + + // For variables that are live at one or more junctions, we assign them + // a consistent register for the entire scope of the function. Find pairs + // of variable that cannot use the same register (the "conflicts") as well + // as pairs of variables that we'd like to have share the same register + // (the "links"). + + var junctionVariables = {}; + + function initializeJunctionVariable(name) { + junctionVariables[name] = { conf: {}, link: {}, excl: {}, reg: null }; + } + + for (var i = 0; i < junctions.length; i++) { + var junc = junctions[i]; + for (var name in junc.live) { + if (!junctionVariables[name]) initializeJunctionVariable(name); + // It conflicts with all other names live at this junction. + for (var otherName in junc.live) { + if (otherName == name) continue; + junctionVariables[name].conf[otherName] = 1; + } + for (var b in junc.outblocks) { + // It conflits with any output vars of successor blocks, + // if they're assigned before it goes dead in that block. + block = blocks[b]; + var jSucc = junctions[block.exit]; + for (var otherName in jSucc.live) { + if (junc.live[otherName]) continue; + if (block.lastKillLoc[otherName] < block.firstDeadLoc[name]) { + if (!junctionVariables[otherName]) initializeJunctionVariable(otherName); + junctionVariables[name].conf[otherName] = 1; + junctionVariables[otherName].conf[name] = 1; + } + } + // It links with any linkages in the outgoing blocks. + var linkName = block.link[name]; + if (linkName && linkName !== name) { + if (!junctionVariables[linkName]) initializeJunctionVariable(linkName); + junctionVariables[name].link[linkName] = 1; + junctionVariables[linkName].link[name] = 1; + } + } + } + } + + // Attempt to sort the junction variables to heuristically reduce conflicts. + // Simple starting point: handle the most-conflicted variables first. + // This seems to work pretty well. + + var sortedJunctionVariables = keys(junctionVariables); + sortedJunctionVariables.sort(function(name1, name2) { + var jv1 = junctionVariables[name1]; + var jv2 = junctionVariables[name2]; + if (jv1.numConfs === undefined) { + jv1.numConfs = setSize(jv1.conf); + } + if (jv2.numConfs === undefined) { + jv2.numConfs = setSize(jv2.conf); + } + return jv2.numConfs - jv1.numConfs; + }); + + // We can now assign a register to each junction variable. + // Process them in order, trying available registers until we find + // one that works, and propagating the choice to linked/conflicted + // variables as we go. + + function tryAssignRegister(name, reg) { + // Try to assign the given register to the given variable, + // and propagate that choice throughout the graph. + // Returns true if successful, false if there was a conflict. + var jv = junctionVariables[name]; + if (jv.reg !== null) { + return jv.reg === reg; + } + if (jv.excl[reg]) { + return false; + } + jv.reg = reg; + // Exclude use of this register at all conflicting variables. + for (var confName in jv.conf) { + junctionVariables[confName].excl[reg] = 1; + } + // Try to propagate it into linked variables. + // It's not an error if we can't. + for (var linkName in jv.link) { + tryAssignRegister(linkName, reg); + } + return true; + } + + NEXTVARIABLE: + for (var i = 0; i < sortedJunctionVariables.length; i++) { + var name = sortedJunctionVariables[i]; + // It may already be assigned due to linked-variable propagation. + if (junctionVariables[name].reg !== null) { + continue NEXTVARIABLE; + } + // Try to use existing registers first. + var allRegs = allRegsByType[localVars[name]]; + for (var reg in allRegs) { + if (tryAssignRegister(name, reg)) { + continue NEXTVARIABLE; + } + } + // They're all taken, create a new one. + tryAssignRegister(name, createReg(name)); + } + + // Each basic block can now be processed in turn. + // There may be internal-use-only variables that still need a register + // assigned, but they can be treated just for this block. We know + // that all inter-block variables are in a good state thanks to + // junction variable consistency. + + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + if (block.nodes.length === 0) continue; + var jEnter = junctions[block.entry]; + var jExit = junctions[block.exit]; + // Calculate the full internal liveness states for this block. + var liveness = [{}]; + for (var name in jExit.live) { + liveness[0][name] = 1; + } + for (var j=block.nodes.length-1; j>=0; j--) { + var node = block.nodes[j]; + liveness.unshift(copy(liveness[0])); + if (node[0] === 'assign') { + var name = node[2][1]; + delete liveness[0][name]; + } else if (node[0] === 'name') { + var name = node[1]; + liveness[0][name] = 1; + } else { + assert(false, 'unexpected node type: ' + node[0]); + } + } + assert(liveness.length == block.nodes.length + 1); + assert(setSize(setSub(liveness[0], jEnter.live)) == 0); + // Mark the point at which each output reg gets assigned. + // Variables that live past this point must not be assigned + // to that register. + var outputAssignLoc = {}; + var outputVars = {}; + for (var name in jExit.live) { + var reg = junctionVariables[name].reg; + assert(reg !== null, 'output variable doesnt have a register'); + outputAssignLoc[reg] = block.lastKillLoc[name]; + outputVars[reg] = name; + } + // Scan through in execution order, allocating registers on demand. + // Be careful to avoid conflicts with the output registers. + // We consume free registers in last-used order, which helps to + // eliminate "x=y" assignments that are the last use of "y". + var assignedRegs = {}; + var freeRegsByType = copy(allRegsByType); + // Begin with all live vars assigned per the entry-point. + for (var name in liveness[0]) { + var reg = junctionVariables[name].reg; + assert(reg !== null, 'input variable doesnt have a register'); + assignedRegs[name] = reg; + delete freeRegsByType[localVars[name]][reg]; + } + for (var j = 0; j < freeRegsByType.length; j++) { + freeRegsByType[j] = keys(freeRegsByType[j]); + } + // Scan through the nodes in sequence, modifying each node in-place + // and freeing registers according to the calculated liveness info. + for (var j = 0; j < block.nodes.length; j++) { + var node = block.no |