diff options
Diffstat (limited to 'src/analyzer.js')
-rw-r--r-- | src/analyzer.js | 398 |
1 files changed, 134 insertions, 264 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index aa9a8031..54f766cc 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -298,10 +298,22 @@ function analyzer(data) { // Tools - function replaceLabels(line, labelId, toLabelId) { + function cleanLabel(label) { + if (label[0] == 'B') { + return label.substr(5); + } else { + return label; + } + } + + function replaceLabels(line, labelIds, toLabelId) { + var ret = []; function process(item) { ['label', 'labelTrue', 'labelFalse', 'toLabel', 'unwindLabel', 'defaultLabel'].forEach(function(id) { - if (item[id] && item[id] == labelId) { + if (item[id] && item[id] in labelIds) { + dprint('relooping', 'zz replace ' + item[id] + ' with ' + toLabelId); + ret.push(item[id]); + item['old_' + id] = item[id]; // Save it; we need this later for labels before breaks, when we have multiple entries later item[id] = toLabelId; } }); @@ -312,22 +324,15 @@ function analyzer(data) { process(line); line.switchLabels.forEach(process); } + return ret; } - function replaceLabelLabels(label, labelId, toLabelId) { - label.lines.forEach(function(line) { replaceLabels(line, labelId, toLabelId) }); - return label; - } - - function replaceInLabels(labels, toReplace, replaceWith) { - assertEq(!replaceWith || toReplace.length == 1, true); // TODO: implement other case + function replaceLabelLabels(labels, labelIds, toLabelId) { + ret = []; labels.forEach(function(label) { - ['inLabels'].forEach(function(l) { - label[l] = label[l].map(function(labelId) { return toReplace.indexOf(labelId) == -1 ? labelId : replaceWith}) - .filter(function(labelId) { return !!labelId }); - }); + ret = ret.concat(replaceLabels(label.lines[label.lines.length-1], labelIds, toLabelId)); }); - return labels; + return ret; } function calcLabelBranchingData(labels, labelsDict) { @@ -368,8 +373,6 @@ function analyzer(data) { labels.forEach(function(label) { label.allInLabels = []; label.allOutLabels = []; - //! MustGetTo ignores return - |if (x) return; else Y| must get to Y. - label.mustGetHere = []; }); var worked = true; @@ -393,278 +396,145 @@ function analyzer(data) { }); } - // Find all mustGetTos - labels.forEach(function(label) { - function walk(path, label) { - // If all we are is a break/return - then stop here. Otherwise, continue to other path - if (label.hasReturn || (label.hasBreak && label.outLabels.length == 0)) { - return [path.concat([label])] - }; - if (path.indexOf(label) != -1) { - return []; // loop - drop this path - } - path = path.concat([label]); - return label.outLabels.map(function(outLabelId) { return walk(path, labelsDict[outLabelId]) }) - .reduce(function(a, b) { return a.concat(b) }, []) - .filter(function(path) { return path.length > 0 }); - } - var paths = walk([], label).map(function(path) { return getLabelIds(path) }); - var possibles = dedup(paths.reduce(function(a,b) { return a.concat(b) }, [])); - label.mustGetTo = possibles.filter(function(possible) { - return paths.filter(function(path) { return path.indexOf(possible) == -1 }) == 0; - }).filter(function(possible) { return possible != label.ident }); - }); - - labels.forEach(function(label) { - label.mustGetTo.forEach(function (mustGetTo) { - labelsDict[mustGetTo].mustGetHere.push(label.ident); - }); - }); - if (dcheck('relooping')) { labels.forEach(function(label) { - print('// label: ' + label.ident + ' :out : ' + JSON.stringify(label.outLabels)); - print('// ' + label.ident + ' :in : ' + JSON.stringify(label.inLabels)); - print('// ' + label.ident + ' :ALL out : ' + JSON.stringify(label.allOutLabels)); - print('// ' + label.ident + ' :ALL in : ' + JSON.stringify(label.allInLabels)); - print('// ZZZZZZ ' + label.ident + ' must get to all of ' + JSON.stringify(label.mustGetTo) + '\n'); + dprint('// label: ' + label.ident + ' :out : ' + JSON.stringify(label.outLabels)); + dprint('// ' + label.ident + ' :in : ' + JSON.stringify(label.inLabels)); + dprint('// ' + label.ident + ' :ALL out : ' + JSON.stringify(label.allOutLabels)); + dprint('// ' + label.ident + ' :ALL in : ' + JSON.stringify(label.allInLabels)); + + // Convert to searchables, for speed (we mainly do lookups here) and code clarity (x in Xlabels) + // FIXME TODO XXX do we need all these? + label.outLabels = searchable(label.outLabels); + label.inLabels = searchable(label.inLabels); + label.allOutLabels = searchable(label.allOutLabels); + label.allInLabels = searchable(label.allInLabels); }); } } -/* // Disabled merging as it seems it just removes a return now and then. - function mergeLabels(label1Ident, label2Ident) { - var label1 = func.labelsDict[label1Ident]; - var label2 = func.labelsDict[label2Ident]; - label1.lines.pop(); - label1.lines = label1.lines.concat(label2.lines); - label1.outLabels = label2.outLabels; - label2.lines = null; - func.labels = func.labels.filter(function(label) { return !!label.lines }); - delete func.labelsDict[label2.ident]; - replaceInLabels(func.labels, [label2.ident], label1.ident); - } + // There are X main kinds of blocks: + // + // 'emulated': A soup of labels, implemented as a barbaric switch in a loop + // + // 'reloop': That is a block of the following shape: + // + // loopX: while(1) { + // // internal labels, etc. Labels are internal to the current one, if + // // they can return to it. + // // + // // Such labels can either do |continue loopX| to get back to the entry label, + // // or set __label__ and do |break loopX| to get to any of the external entries + // // they need to get to. External labels, of course, are those that cannot + // // get to the entry + // } + // // external labels + // + // 'multiple': A block that branches into multiple subblocks, somehow. + // + // @param exitLabels Labels which we should not implement; our parent block will + // do them. These are the external labels for our parent. Note that + // they include BCONT etc., as the labels have been replaced to be that way. + // @param exitLabelsHit Exit labels which were actually encountered - we update that. XXX do we need this?! + function makeBlock(labels, entries, labelsDict, exitLabels, exitLabelsHit) { + dprint('relooping', 'prelooping: ' + entries + ',' + labels.length + ' labels'); + assert(entries); - // Merge trivial labels - var worked = true; - while (worked) { - worked = false; - func.labels.forEach(function(label) { - if (label.lines === null) return; // We were deleted - if (label.outLabels.length == 1 && - label.lines.slice(-1)[0].intertype == 'branch' && - label.lines.slice(-1)[0].label == label.outLabels[0] && - func.labelsDict[label.outLabels[0]].inLabels.length == 1) { - mergeLabels(label.ident, label.outLabels[0]); - worked = true; + calcLabelBranchingData(labels, labelsDict); + + function emulated() { + labels.forEach(function(label) { + for (l in label.outLabels) { + exitLabelsHit[l] = true; } }); + assert(entries[0]); + return { + type: 'emulated', + labels: labels, + entries: entries.slice(0), + }; } -*/ + if (!RELOOP) return emulated(); - // 'block': A self-enclosed part of the program, for example a loop or an if - function makeBlock(labels, entry, labelsDict) { - var def = { - type: 'emulated', // a block we cannot map to a nicer structure like a loop. We emulate it with a barbaric switch - labels: labels, - entry: entry, - }; - if (!RELOOP) return def; - - if (!entry) { - assertTrue(labels.length == 0); - return null; // Empty block - not even an entry - } + if (entries.length > 1) return emulated(); + var entry = entries[0]; + assert(entry); + dprint('relooping', 'Relooping: ' + entry + ',' + labels.length + ' labels'); - function getLastLine(block) { - if (!block) return null; // Completely empty block (probably had only a branch, - // which was optimized out) - dprint('relooping', 'get last line at: ' + block.labels[0].ident); - if (block.next) return getLastLine(block.next); - switch(block.type) { - case 'loop': - return getLastLine(block.rest); - case 'if': - case 'breakingif': - return getLastLine(block.ifTrue); - case 'emulated': - case 'simple': - if (block.labels.length == 1) { - return block.labels[0].lines.slice(-1)[0]; - } else { - return null; - } - } - } - function getAll(fromId, beforeIds) { - beforeIds = beforeIds ? beforeIds : []; - if (beforeIds && beforeIds.indexOf(fromId) != -1) return []; - var from = labelsDict[fromId]; - return dedup([from].concat( - from.outLabels.map(function(outLabel) { return getAll(outLabel, beforeIds.concat(fromId)) }) - .reduce(function(a,b) { return a.concat(b) }, []) - ), 'ident'); - } - function isolate(label) { - label.inLabels = []; - label.outLabels = []; - return label; - } - dprint('relooping', "\n\n// XXX MAKEBLOCK " + entry + ', num labels: ' + labels.length + ' and they are: ' + getLabelIds(labels)); - if (labels.length == 0 || !entry) { - dprint('relooping', '//empty labels or entry'); - return; - } - function forLabelLines(labels, func) { - labels.forEach(function(label) { - label.lines.forEach(function(line) { func(line, label) }); - }); - } + var entryLabel = labelsDict[entry]; + assert(entryLabel); + var lastLine = entryLabel.lines.slice(-1)[0]; + var others = labels.filter(function(label) { return label.ident != entry }); - // Begin + var canReturn = values(entryLabel.inLabels).length > 0; - calcLabelBranchingData(labels, labelsDict); + // === (simple) 'emulated' === - var split = splitter(labels, function(label) { return label.ident == entry }); - var first = split.splitOut[0]; - if (!first) { - dprint('relooping', "//no first line"); - return; - } - var others = split.leftIn; - var lastLine = first.lines.slice(-1)[0]; - dprint('relooping', "// makeBlock " + entry + ' : ' + getLabelIds(labels) + ' IN: ' + first.inLabels + ' OUT: ' + first.outLabels); - // If we have one outgoing, and none incoming - make this a block of 1, - // and move on the others (forgetting ourself, so they are now also - // totally self-enclosed, once we start them) - if (first.inLabels.length == 0 && first.outLabels.length == 1) { - dprint('relooping', ' Creating simple emulated'); + if (!canReturn && values(entryLabel.outLabels).length == 1) { + dprint('relooping', ' Creating simple emulated, outlabels: ' + keys(entryLabel.outLabels)); assertEq(lastLine.intertype, 'branch'); -// assertEq(!!lastLine.label, true); + var next = keys(entryLabel.outLabels)[0]; + if (next in exitLabels) { + exitLabelsHit[next] = true; + next = null; + } else { + replaceLabelLabels(others, searchable(entry)); + } + dprint('relooping', ' next: ' + next); + replaceLabelLabels([entryLabel], searchable(next), 'BNOPP'); // remove unneeded branch return { - type: 'simple', - labels: [replaceLabelLabels(first, first.outLabels[0], 'BNOPP')], + type: 'emulated', + labels: [entryLabel], entry: entry, - next: makeBlock(replaceInLabels(others, entry), first.outLabels[0], labelsDict), + next: next ? makeBlock(others, [next], labelsDict, exitLabels, exitLabelsHit) : null, }; } - // Looping structures - in some way, we can get back to here - if (first.outLabels.length > 0 && first.allInLabels.indexOf(entry) != -1) { - // Look for outsiders - labels no longer capable of getting here. Those must be - // outside the loop. Insiders are those that can get back to the entry - var split2 = splitter(others, function(label) { return label.allOutLabels.indexOf(entry) == -1 }); - var outsiders = split2.splitOut; - var insiders = split2.leftIn; - // Hopefully exactly one of the outsiders is a 'pivot' - a label to which all those leaving - // the loop must go. Then even some |if (falala) { ... break; }| will get ... - // as an outsider, but it will actually still be in the loop - var pivots = - outsiders.filter(function(outsider) { - return insiders.filter(function(insider) { - return insider.mustGetTo.indexOf(outsider.ident) == -1; - }) == 0; - }); - // Find the earliest pivot. They must be staggered, each leading to another, - // as all insiders must go through *all* of these. So we seek a pivot that - // is never reached by another pivot. That must be the one with fewest - // mustGetTo - var pivot = null; - if (pivots.length >= 1) { - pivots.sort(function(a, b) { return b.mustGetTo.length - a.mustGetTo.length }); - pivot = pivots[0]; - if (!(pivot.ident in searchable(first.outLabels))) { - // pivot must be right outside - no intermediates - // TODO: Handle this, so we can reloop a lot more stuff - pivot = null; - } - } - if (pivot) { // We have ourselves a loop - dprint('relooping', ' Creating LOOP : ' + entry + ' insiders: ' + getLabelIds(insiders) + ' to pivot: ' + pivot.ident); - var otherLoopLabels = insiders; - var loopLabels = insiders.concat([first]); - var nextLabels = outsiders; - // Rework branches out of the loop into new 'break' labels - forLabelLines(loopLabels, function(line) { - replaceLabels(line, pivot.ident, 'BREAK' + entry); - }); - // Rework branches to the inc part of the loop into |continues| - forLabelLines(loopLabels, function(line, label) { - if (0) {// XXX - make this work :label.outLabels.length == 1 && label.outLabels[0] == entry && !label.hasBreak && !label.hasReturn) { - // it can remove unneeded continues (but does too much right now, as the continue might have been - // placed into an emulated while(1) switch { } - replaceLabels(line, entry, 'BNOPP'); - } else { - replaceLabels(line, entry, 'BCONT' + entry); - } - }); - // Fix inc branch into rest - var nextEntry = null; - first.outLabels.forEach(function(outLabel) { - if (outLabel != pivot.ident) { - replaceLabels(lastLine, outLabel, 'BNOPP'); - nextEntry = outLabel; - } - }); - if (nextEntry == entry) { // We loop back to ourselves, the entire loop is just us - nextEntry = null; - } - dprint('relooping', "Entry into next: " + nextEntry); - var ret = { - type: 'loop', - labels: loopLabels, - entry: entry, - inc: makeBlock([isolate(first)], entry, labelsDict), - rest: makeBlock(replaceInLabels(otherLoopLabels, entry), nextEntry, labelsDict), - }; - dprint('relooping', ' getting last line for block starting with ' + entry + ' and nextEntry: ' + nextEntry); - var lastLoopLine = getLastLine(nextEntry ? ret.rest : ret.inc); - if (lastLoopLine) { - replaceLabels(lastLoopLine, 'BCONT' + entry, 'BNOPP'); // Last line will feed back into the loop anyhow - } - ret.next = makeBlock(replaceInLabels(nextLabels, getLabelIds(loopLabels)), pivot.ident, labelsDict); - return ret; - } - } - // Try an 'if' structure - if (first.outLabels.length == 2) { - if (labelsDict[first.outLabels[1]].mustGetTo.indexOf(first.outLabels[0]) != -1) { - first.outLabels.push(first.outLabels.shift()); // Reverse order - normalize. Very fast check anyhow - } - if (labelsDict[first.outLabels[0]].mustGetTo.indexOf(first.outLabels[1]) != -1) { - var ifLabelId = first.outLabels[0]; - var outLabelId = first.outLabels[1]; - // 0 - the if area. 1 - where we all exit to later - var ifTrueLabels = getAll(ifLabelId, [outLabelId]); - var ifLabels = ifTrueLabels.concat([first]); - var nextLabels = getAll(outLabelId); - // If we can get to the outside in more than 2 ways (one from if, one from True clause) - have breaks - var breaking = labelsDict[outLabelId].allInLabels.length > 2; - dprint('relooping', ' Creating XXX IF: ' + getLabelIds(ifTrueLabels) + ' to ' + outLabelId + ' ==> ' + getLabelIds(nextLabels) + ' breaking: ' + breaking); - if (breaking) { - // Rework branches out of the if into new 'break' labels - forLabelLines(ifTrueLabels, function(line) { - replaceLabels(line, outLabelId, 'BREAK' + entry); - }); - } - // Remove branching op - we will do it manually - replaceLabels(lastLine, ifLabelId, 'BNOPP'); - replaceLabels(lastLine, outLabelId, 'BNOPP'); - return { - type: (breaking ? 'breaking' : '') + 'if', - labels: ifLabels, - entry: entry, - ifVar: lastLine.ident, - check: makeBlock([isolate(first)], entry, labelsDict), - ifTrue: makeBlock(replaceInLabels(ifTrueLabels, entry), ifLabelId, labelsDict), - next: makeBlock(replaceInLabels(nextLabels, getLabelIds(ifLabels)), outLabelId, labelsDict), - }; - } + // === 'reloop' away a loop, if there is one === + + if (canReturn) { + var ret = { + type: 'reloop', + entry: entry, + labels: labels, + }; + + // Find internal and external labels + var split_ = splitter(labels, function(label) { return !(entry in label.allOutLabels) }); + var externals = split_.splitOut; + var internals = split_.leftIn; + var currExitLabels = set(getLabelIds(externals)); + + dprint('relooping', function() { return ' Creating reloop: Inner: ' + dump(getLabelIds(internals)) + ', Exxer: ' + dump(currExitLabels) }); + + replaceLabelLabels(internals, searchable(entry), 'BCONT' + entry); // we will be in a loop, |continue| gets us back to the entry + dprint('relooping', 'for exit purposes, Replacing: ' + dump(currExitLabels)); + var enteredExitLabels = replaceLabelLabels(internals, currExitLabels, 'BREAK' + entry); // to get to any of our (not our parents') exit labels, + // we will break. + dprint('relooping', dump(currExitLabels) + ' enteredExitLabels: ' + dump(enteredExitLabels)); + enteredExitLabels = enteredExitLabels.map(cleanLabel); + dprint('relooping', ' enteredExitLabels: ' + dump(enteredExitLabels)); + + // inner + var allExitLabels = mergeInto(set(currExitLabels), exitLabels); + var currExitLabelsHit = {}; + ret.inner = makeBlock(internals, [entry], labelsDict, allExitLabels, currExitLabelsHit); + + // outer + ret.outer = makeBlock(externals, enteredExitLabels, labelsDict, exitLabels, currExitLabelsHit); + mergeInto(exitLabelsHit, setSub(currExitLabelsHit, currExitLabels)); // Don't really need setSub, but nicer + + return ret; } + // === handle multiple branches from the entry with a 'multiple' === + + // TODO + // Give up on this structure - emulate it dprint('relooping', ' Creating complex emulated'); - return def; + return emulated(); } // TODO: each of these can be run in parallel @@ -674,7 +544,7 @@ function analyzer(data) { func.labels.forEach(function(label) { func.labelsDict[label.ident] = label; }); - func.block = makeBlock(func.labels, toNiceIdent('%entry'), func.labelsDict); + func.block = makeBlock(func.labels, [toNiceIdent('%entry')], func.labelsDict, {}, {}); }); return finish(); |