diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-11-09 16:28:02 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-11-09 16:28:02 -0800 |
commit | a0979a4bf7db9e53b6ffe26077406b14db8afa06 (patch) | |
tree | aa8890e7d8a893e4746d2c63de0bdccdc0862676 | |
parent | adc36358d3efd4efaf09defff7d090ba71ea6003 (diff) | |
parent | d27357b5fea169eb7a61e46e609381083834f704 (diff) |
Merge branch 'relooper2' into incoming
-rwxr-xr-x | emcc | 3 | ||||
-rw-r--r-- | src/analyzer.js | 373 | ||||
-rw-r--r-- | src/compiler.js | 3 | ||||
-rw-r--r-- | src/jsifier.js | 148 | ||||
-rw-r--r-- | src/library.js | 2 | ||||
-rw-r--r-- | src/relooper.js | 7 | ||||
-rwxr-xr-x | tests/runner.py | 3 | ||||
-rw-r--r-- | tools/js-optimizer.js | 28 | ||||
-rw-r--r-- | tools/test-js-optimizer-output.js | 28 | ||||
-rw-r--r-- | tools/test-js-optimizer-regs.js | 2 | ||||
-rw-r--r-- | tools/test-js-optimizer-t3.js | 20 | ||||
-rw-r--r-- | tools/test-js-optimizer.js | 144 |
12 files changed, 222 insertions, 539 deletions
@@ -1115,9 +1115,6 @@ try: final = shared.Building.js_optimizer(final, []) if DEBUG: save_intermediate('pretty') - if shared.Settings.RELOOP: - js_optimizer_queue += ['hoistMultiples', 'loopOptimizer'] - def get_eliminate(): return 'eliminate' if not shared.Settings.ALLOW_MEMORY_GROWTH else 'eliminateMemSafe' diff --git a/src/analyzer.js b/src/analyzer.js index a6a37400..71f795a8 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -1490,388 +1490,23 @@ function analyzer(data, sidePass) { // ReLooper - reconstruct nice loops, as much as possible substrate.addActor('Relooper', { processItem: function(item) { - var that = this; function finish() { - that.forwardItem(item, 'LoopOptimizer'); - } - - // Tools - - function calcLabelBranchingData(labels, labelsDict) { - labels.forEach(function(label) { - label.outLabels = []; - label.inLabels = []; - label.hasReturn = false; - label.hasBreak = false; - }); - // Find direct branchings - labels.forEach(function(label) { - var line = label.lines[label.lines.length-1]; - operateOnLabels(line, function process(item, id) { - if (item[id][0] == 'B') { // BREAK, BCONT, BNOPP, BJSET - label.hasBreak = true; - } else { - label.outLabels.push(item[id]); - labelsDict[item[id]].inLabels.push(label.ident); - } - }); - label.hasReturn |= line.intertype == 'return'; - }); - // Find all incoming and all outgoing - recursively - labels.forEach(function(label) { - label.allInLabels = []; - label.allOutLabels = []; - }); - - // First, find allInLabels. TODO: use typed arrays here to optimize this for memory and speed - var more = true, nextModified, modified = set(getLabelIds(labels)); - while (more) { - more = false; - nextModified = {}; - for (var labelId in modified) { - var label = labelsDict[labelId]; - var temp = label.inLabels; - label.inLabels.forEach(function(label2Id) { - temp = temp.concat(labelsDict[label2Id].allInLabels); - }); - temp = dedup(temp); - if (temp.length > label.allInLabels.length) { - label.allInLabels = temp; - for (var i = 0; i < label.outLabels.length; i++) { - nextModified[label.outLabels[i]] = true; - } - more = true; - } - } - modified = nextModified; - } - - // Infer allOutLabels from allInLabels, they are mirror images - labels.forEach(function(label) { - label.allInLabels.forEach(function(inLabelId) { - labelsDict[inLabelId].allOutLabels.push(label.ident); - }); - }); - - labels.forEach(function(label) { - if (dcheck('relooping')) { - 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 set, for speed (we mainly do lookups here) and code clarity (x in Xlabels) - // Also removes duplicates (which we can get in llvm switches) - // TODO do we need all these? - label.outLabels = set(label.outLabels); - label.inLabels = set(label.inLabels); - label.allOutLabels = set(label.allOutLabels); - label.allInLabels = set(label.allInLabels); - }); - } - - var idCounter = 0; - function makeBlockId(entries) { - idCounter++; - return '$_$' + idCounter; + item.__finalResult__ = true; + return [item]; } - - // There are X main kinds of blocks: - // - //---------------------------------------------------------------------------------------- - // - // 'emulated': A soup of labels, implemented as a barbaric switch in a loop. Any - // label can get to any label. No block follows this. - // - // '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, each independent, - // finally leading outside into another block afterwards - // For now, we do this in a loop, so we can break out of it easily to get - // to the labels afterwards. TODO: Optimize that out - // function makeBlock(labels, entries, labelsDict, forceEmulated) { if (labels.length == 0) return null; dprint('relooping', 'prelooping: ' + entries + ',' + labels.length + ' labels'); assert(entries && entries[0]); // need at least 1 entry - var blockId = makeBlockId(entries); - var emulated = { type: 'emulated', - id: blockId, + id: 'B', labels: labels, entries: entries.slice(0) }; - if (!RELOOP || forceEmulated) return emulated; - - calcLabelBranchingData(labels, labelsDict); - - var s_entries = set(entries); - dprint('relooping', 'makeBlock: ' + entries + ',' + labels.length + ' labels'); - - var entryLabels = entries.map(function(entry) { return labelsDict[entry] }); - assert(entryLabels[0]); - - var canReturn = false, mustReturn = true; - entryLabels.forEach(function(entryLabel) { - var curr = values(entryLabel.inLabels).length > 0; - canReturn = canReturn || curr; - mustReturn = mustReturn && curr; - }); - - // Remove unreachables - allOutLabels = {}; - entryLabels.forEach(function(entryLabel) { - mergeInto(allOutLabels, entryLabel.allOutLabels); - }); - labels = labels.filter(function(label) { return label.ident in s_entries || label.ident in allOutLabels }); - - // === (simple) 'emulated' === - - if (entries.length == 1 && !canReturn) { - var entry = entries[0]; - var entryLabel = entryLabels[0]; - var others = labels.filter(function(label) { return label.ident != entry }); - - var nextEntries = keys(entryLabel.outLabels); - dprint('relooping', ' Creating simple emulated, outlabels: ' + nextEntries); - nextEntries.forEach(function(nextEntry) { - replaceLabelLabels([entryLabel], set(nextEntry), 'BJSET|' + nextEntry); // Just SET __label__ - no break or continue or whatnot - }); - return { - type: 'emulated', - id: blockId, - labels: [entryLabel], - entries: entries, - next: makeBlock(others, keys(entryLabel.outLabels), labelsDict) - }; - } - - // === 'reloop' away a loop, if we need to === - - function makeLoop() { - var ret = { - type: 'reloop', - id: blockId, - needBlockId: true, - entries: entries, - labels: labels - }; - - // Find internal and external labels - var split_ = splitter(labels, function(label) { - // External labels are those that are (1) not an entry, and (2) cannot reach an entry. In other words, - // the labels inside the loop are the entries and those that can return to the entries. - return !(label.ident in s_entries) && values(setIntersect(s_entries, label.allOutLabels)).length == 0; - }); - var externals = split_.splitOut; - var internals = split_.leftIn; - var externalsLabels = set(getLabelIds(externals)); - - if (dcheck('relooping')) dprint(' Creating reloop: Inner: ' + dump(getLabelIds(internals)) + ', Exxer: ' + dump(externalsLabels)); - - if (ASSERTIONS) { - // Verify that no external can reach an internal - var inLabels = set(getLabelIds(internals)); - externals.forEach(function(external) { - if (values(setIntersect(external.outLabels, inLabels)).length > 0) { - dprint('relooping', 'Found an external that wants to reach an internal, fallback to emulated?'); - throw "Spaghetti label flow"; - } - }); - } - - // We will be in a loop, |continue| gets us back to the entry - var pattern = 'BCONT|' + blockId; - if (entries.length == 1) { - // We are returning to a loop that has one entry, so we don't need to set __label__ - pattern = 'BCNOL|' + blockId; - } - entries.forEach(function(entry) { - replaceLabelLabels(internals, set(entries), pattern); - }); - - // Find the entries of the external labels - var externalsEntries = {}; - internals.forEach(function(internal) { - mergeInto(externalsEntries, setIntersect(internal.outLabels, externalsLabels)); - }); - externalsEntries = keys(externalsEntries); - - // We also want to include additional labels inside the loop. If the loop has just one exit label, - // then that is fine - keep the loop small by having the next code outside, and do not set __label__ in - // that break. If there is more than one, though, we can avoid __label__ checks in a multiple outside - // by hoisting labels into the loop. - if (externalsEntries.length > 1) { - (function() { - // If an external entry would make the loop too big, don't hoist - var maxHoist = Infinity; //sum(internals.map(function(internal) { return internal.lines.length })); - var avoid = externalsEntries.map(function(l) { return labelsDict[l] }); - var totalNewEntries = {}; - for (var i = 0; i < externalsEntries.length; i++) { - var exitLabel = labelsDict[externalsEntries[i]]; - // Check if hoisting this external entry is worthwhile. We first do a dry run, aborting on - // loops (which we never hoist, to avoid over-nesting) or on seeing too many labels would be hoisted - // (to avoid enlarging loops too much). If the dry run succeeded, it will stop when it reaches - // places where we rejoin other external entries. - var seen, newEntries; - function prepare() { - seen = {}; - newEntries = {}; - } - function hoist(label, dryRun) { // returns false if aborting - if (seen[label.ident]) return true; - seen[label.ident] = true; - if (label.ident in label.allInLabels) return false; // loop, abort - if (isReachable(label, avoid, exitLabel)) { - // We rejoined, so this is a new external entry - newEntries[label.ident] = true; - return true; - } - // Hoistable. - if (!dryRun) { - dprint('relooping', 'Hoisting into loop: ' + label.ident); - internals.push(label); - externals = externals.filter(function(l) { return l != label }); // not very efficient at all TODO: optimize - } - for (var outLabelId in label.outLabels) { - var outLabel = labelsDict[outLabelId]; - if (!hoist(outLabel, dryRun)) return false; - } - return true; - } - prepare(); - if (hoist(exitLabel, true)) { - var seenList = unset(seen); - var num = sum(seenList.map(function(seen) { return labelsDict[seen].lines.length })); - // Only hoist if the sizes make sense - if (seenList.length >= 1 && num <= maxHoist) { // && unset(newEntries).length <= 1) { - prepare(); - hoist(exitLabel); - mergeInto(totalNewEntries, newEntries); - externalsEntries.splice(i, 1); - i--; - } - } - } - externalsLabels = set(getLabelIds(externals)); - externalsEntries = keys(set(externalsEntries.concat(unset(totalNewEntries)))); - assert(externalsEntries.length > 0 || externals.length == 0); - })(); - } - - // To get to any of our (not our parents') exit labels, we will break. - if (dcheck('relooping')) dprint('for exit purposes, Replacing: ' + dump(externalsLabels)); - if (externals.length > 0) { - assert(externalsEntries.length > 0); - var pattern = 'BREAK|' + blockId; - if (externalsEntries.length == 1) { - // We are breaking out of a loop and have one entry after it, so we don't need to set __label__ - pattern = 'BRNOL|' + blockId; - } - replaceLabelLabels(internals, externalsLabels, pattern); - if (dcheck('relooping')) dprint('externalsEntries: ' + dump(externalsEntries)); - } - - // inner - ret.inner = makeBlock(internals, entries, labelsDict); - - if (externals.length > 0) { - // outer - ret.next = makeBlock(externals, externalsEntries, labelsDict); - } - - return ret; - } - - // XXX change this logic? - if (entries.length === 1 && canReturn) return makeLoop(); - - // === handle multiple branches from the entry with a 'multiple' === - // - // For each entry, try to 'build it out' as much as possible. Add labels, until - // * hit a post label - // * hit a label reachable by another actual entry - - dprint('relooping', 'trying multiple...'); - - var shouldNotReach = entryLabels; - var handlingNow = []; - var actualEntryLabels = []; - var postEntryLabels = {}; - entryLabels.forEach(function(entryLabel) { - entryLabel.blockChildren = []; - var visited = {}; - function tryAdd(label) { - if (label.ident in visited) return; - visited[label.ident] = true; - if (!isReachable(label, shouldNotReach, entryLabel)) { - entryLabel.blockChildren.push(label); - handlingNow.push(label); - keys(label.outLabels).forEach(function(outLabelId) { tryAdd(labelsDict[outLabelId]) }); - } else { - postEntryLabels[label.ident] = true; // This will be an entry in the next block - } - } - tryAdd(entryLabel); - if (entryLabel.blockChildren.length > 0) { - dprint('relooping', ' Considering multiple, found a valid entry, ' + entryLabel.ident); - actualEntryLabels.push(entryLabel); - } - }); - - if (dcheck('relooping')) dprint(' Considering multiple, canHandle: ' + getLabelIds(handlingNow)); - - if (handlingNow.length > 0) { - // This is a 'multiple' - - var actualEntries = getLabelIds(actualEntryLabels); - if (dcheck('relooping')) dprint(' Creating multiple, with entries: ' + actualEntries + ', post entries: ' + dump(postEntryLabels)); - actualEntryLabels.forEach(function(actualEntryLabel) { - if (dcheck('relooping')) dprint(' creating sub-block in multiple for ' + actualEntryLabel.ident + ' : ' + getLabelIds(actualEntryLabel.blockChildren) + ' ::: ' + actualEntryLabel.blockChildren.length); - - var pattern = 'BREAK|' + blockId; - if (keys(postEntryLabels).length == 1) { - // We are breaking out of a multiple and have one entry after it, so we don't need to set __label__ - pattern = 'BRNOL|' + blockId; - } - keys(postEntryLabels).forEach(function(post) { - replaceLabelLabels(actualEntryLabel.blockChildren, set(post), pattern); - }); - - // Create child block - actualEntryLabel.block = makeBlock(actualEntryLabel.blockChildren, [actualEntryLabel.blockChildren[0].ident], labelsDict); - }); - return { - type: 'multiple', - id: blockId, - needBlockId: true, - entries: actualEntries, - entryLabels: actualEntryLabels, - labels: handlingNow, - next: makeBlock(labels.filter(function(label) { return handlingNow.indexOf(label) == -1 }), keys(postEntryLabels), labelsDict) - }; - } - - assert(canReturn, 'If not a multiple, must be able to create a loop'); - - return makeLoop(); + return emulated; } - - // TODO: each of these can be run in parallel item.functions.forEach(function(func) { dprint('relooping', "// relooping function: " + func.ident); func.block = makeBlock(func.labels, [toNiceIdent(func.labels[0].ident)], func.labelsDict, func.forceEmulated); diff --git a/src/compiler.js b/src/compiler.js index 3220c977..3ce53b1e 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -13,7 +13,7 @@ try { // *** Environment setup code *** var arguments_ = []; -var ENVIRONMENT_IS_NODE = typeof process === 'object'; +var ENVIRONMENT_IS_NODE = typeof process === 'object' && typeof require === 'function'; var ENVIRONMENT_IS_WEB = typeof window === 'object'; var ENVIRONMENT_IS_WORKER = typeof importScripts === 'function'; var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; @@ -194,6 +194,7 @@ load('parseTools.js'); load('intertyper.js'); load('analyzer.js'); load('jsifier.js'); +if (RELOOP) load('relooper.js') globalEval(processMacros(preprocess(read('runtime.js')))); Runtime.QUANTUM_SIZE = QUANTUM_SIZE; diff --git a/src/jsifier.js b/src/jsifier.js index 21628079..9a3d6ae2 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -7,6 +7,7 @@ var STRUCT_LIST = set('struct', 'list'); var UNDERSCORE_OPENPARENS = set('_', '('); +var RELOOP_IGNORED_LASTS = set('return', 'unreachable', 'resume'); // JSifier function JSify(data, functionsOnly, givenFunctions) { @@ -565,14 +566,14 @@ function JSify(data, functionsOnly, givenFunctions) { if (true) { // TODO: optimize away when not needed if (CLOSURE_ANNOTATIONS) func.JS += '/** @type {number} */'; - func.JS += ' var __label__;\n'; + func.JS += ' var label;\n'; } // Walk function blocks and generate JS function walkBlock(block, indent) { if (!block) return ''; dprint('relooping', 'walking block: ' + block.type + ',' + block.entries + ' : ' + block.labels.length); - function getLabelLines(label, indent) { + function getLabelLines(label, indent, relooping) { if (!label) return ''; var ret = ''; if (LABEL_DEBUG) { @@ -589,23 +590,35 @@ function JSify(data, functionsOnly, givenFunctions) { } // for special labels we care about (for phi), mark that we visited them - return ret + label.lines.map(function(line) { return line.JS + (Debugging.on ? Debugging.getComment(line.lineNum) : '') }) + var i = 0; + return ret + label.lines.map(function(line) { + var JS = line.JS; + if (relooping && i == label.lines.length-1) { + if (line.intertype == 'branch' || line.intertype == 'switch') { + JS = ''; // just branching operations - done in the relooper, so nothing need be done here + } else if (line.intertype == 'invoke') { + JS = line.reloopingJS; // invokes have code that is not rendered in the relooper (the call inside a try-catch) + } + } + i++; + return JS + (Debugging.on ? Debugging.getComment(line.lineNum) : ''); + }) .join('\n') .split('\n') // some lines include line breaks .map(function(line) { return indent + line }) .join('\n'); } var ret = ''; - if (block.type == 'emulated') { + if (!RELOOP || func.forceEmulated) { // TODO: also if just 1 label? if (block.labels.length > 1) { if (block.entries.length == 1) { - ret += indent + '__label__ = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + getOriginalLabelId(block.entries[0]) + ' */' : '') + '\n'; + ret += indent + 'label = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + getOriginalLabelId(block.entries[0]) + ' */' : '') + '\n'; } // otherwise, should have been set before! if (func.setjmpTable) { var setjmpTable = {}; ret += indent + 'var setjmpTable = {'; func.setjmpTable.forEach(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into - ret += '"' + getLabelId(triple[0]) + '": ' + 'function(value) { __label__ = ' + getLabelId(triple[1]) + '; ' + triple[2] + ' = value },'; + ret += '"' + getLabelId(triple[0]) + '": ' + 'function(value) { label = ' + getLabelId(triple[1]) + '; ' + triple[2] + ' = value },'; }); ret += 'dummy: 0'; ret += '};\n'; @@ -614,12 +627,12 @@ function JSify(data, functionsOnly, givenFunctions) { if (func.setjmpTable) { ret += 'try { '; } - ret += 'switch(__label__) {\n'; + ret += 'switch(label) {\n'; ret += block.labels.map(function(label) { return indent + ' case ' + getLabelId(label.ident) + ': ' + (SHOW_LABELS ? '// ' + getOriginalLabelId(label.ident) : '') + '\n' + getLabelLines(label, indent + ' '); }).join('\n'); - ret += '\n' + indent + ' default: assert(0, "bad label: " + __label__);\n' + indent + '}'; + ret += '\n' + indent + ' default: assert(0, "bad label: " + label);\n' + indent + '}'; if (func.setjmpTable) { ret += ' } catch(e) { if (!e.longjmp) throw(e); setjmpTable[e.label](e.value) }'; } @@ -627,38 +640,50 @@ function JSify(data, functionsOnly, givenFunctions) { ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0], indent); } ret += '\n'; - } else if (block.type == 'reloop') { - ret += indent + block.id + ': while(1) { ' + (SHOW_LABELS ? ' /* ' + block.entries + + ' */' : '') + '\n'; - ret += walkBlock(block.inner, indent + ' '); - ret += indent + '}\n'; - } else if (block.type == 'multiple') { - var first = true; - var multipleIdent = ''; - ret += indent + block.id + ': do { \n'; - multipleIdent = ' '; - // TODO: Find out cases where the final if/case is not needed - where we know we must be in a specific label at that point - var SWITCH_IN_MULTIPLE = 0; // This appears to never be worth it, for no amount of labels - if (SWITCH_IN_MULTIPLE && block.entryLabels.length >= 2) { - ret += indent + multipleIdent + 'switch(__label__) {\n'; - block.entryLabels.forEach(function(entryLabel) { - ret += indent + multipleIdent + ' case ' + getLabelId(entryLabel.ident) + ': {\n'; - ret += walkBlock(entryLabel.block, indent + ' ' + multipleIdent); - ret += indent + multipleIdent + ' } break;\n'; - }); - ret += indent + multipleIdent + '}\n'; - } else { - block.entryLabels.forEach(function(entryLabel) { - ret += indent + multipleIdent + (first ? '' : 'else ') + 'if (__label__ == ' + getLabelId(entryLabel.ident) + ') {\n'; - ret += walkBlock(entryLabel.block, indent + ' ' + multipleIdent); - ret += indent + multipleIdent + '}\n'; - first = false; - }); - } - ret += indent + '} while(0);\n'; } else { - throw "Walked into an invalid block type: " + block.type; + // Reloop multiple blocks using the compiled relooper + + //Relooper.setDebug(1); + Relooper.init(); + + var blockMap = {}; + // add blocks + for (var i = 0; i < block.labels.length; i++) { + var label = block.labels[i]; + var content = getLabelLines(label, '', true); + //printErr(func.ident + ' : ' + label.ident + ' : ' + content + '\n'); + blockMap[label.ident] = Relooper.addBlock(content); + } + // add branchings + function relevant(x) { return x && x.length > 2 ? x : 0 } // ignores ';' which valueJS and label*JS can be if empty + for (var i = 0; i < block.labels.length; i++) { + var label = block.labels[i]; + var ident = label.ident; + var last = label.lines[label.lines.length-1]; + //printErr('zz last ' + dump(last)); + if (last.intertype == 'branch') { + if (last.label) { // 1 target + Relooper.addBranch(blockMap[ident], blockMap[last.label], 0, relevant(last.labelJS)); + } else { // 2 targets + Relooper.addBranch(blockMap[ident], blockMap[last.labelTrue], last.valueJS, relevant(last.labelTrueJS)); + Relooper.addBranch(blockMap[ident], blockMap[last.labelFalse], 0, relevant(last.labelFalseJS)); + } + } else if (last.intertype == 'switch') { + last.groupedLabels.forEach(function(switchLabel) { + Relooper.addBranch(blockMap[ident], blockMap[switchLabel.label], switchLabel.value, relevant(switchLabel.labelJS)); + }); + Relooper.addBranch(blockMap[ident], blockMap[last.defaultLabel], 0, relevant(last.defaultLabelJS)); + } else if (last.intertype == 'invoke') { + Relooper.addBranch(blockMap[ident], blockMap[last.toLabel], '!__THREW__', relevant(last.toLabelJS)); + Relooper.addBranch(blockMap[ident], blockMap[last.unwindLabel], 0, relevant(last.unwindLabelJS)); + } else if (last.intertype in RELOOP_IGNORED_LASTS) { + } else { + throw 'unknown reloop last line: ' + last.intertype; + } + } + ret += Relooper.render(blockMap[block.entries[0]]); } - return ret + walkBlock(block.next, indent); + return ret; } func.JS += walkBlock(func.block, ' '); // Finalize function @@ -687,6 +712,7 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS += 'if (globalScope) { assert(!globalScope["' + func.ident + '"]); globalScope["' + func.ident + '"] = ' + func.ident + ' }'; } + func.JS = func.JS.replace(/\n *;/g, '\n'); // remove unneeded lines return func; } }); @@ -821,7 +847,7 @@ function JSify(data, functionsOnly, givenFunctions) { var parts = label.split('|'); var trueLabel = parts[1] || ''; var oldLabel = parts[2] || ''; - var labelSetting = oldLabel ? '__label__ = ' + getLabelId(oldLabel) + ';' + + var labelSetting = oldLabel ? 'label = ' + getLabelId(oldLabel) + ';' + (SHOW_LABELS ? ' /* to: ' + getOriginalLabelId(cleanLabel(oldLabel)) + ' */' : '') : ''; // TODO: optimize away the setting if (label[1] == 'R') { if (label[2] == 'N') { // BRNOL: break, no label setting @@ -842,7 +868,7 @@ function JSify(data, functionsOnly, givenFunctions) { } } else { if (!labelIsVariable) label = getLabelId(label); - return pre + '__label__ = ' + label + ';' + (SHOW_LABELS ? ' /* to: ' + getOriginalLabelId(cleanLabel(label)) + ' */' : '') + ' break;'; + return pre + 'label = ' + label + ';' + (SHOW_LABELS ? ' /* to: ' + getOriginalLabelId(cleanLabel(label)) + ' */' : '') + ' break;'; } } @@ -918,11 +944,11 @@ function JSify(data, functionsOnly, givenFunctions) { makeFuncLineActor('branch', function(item) { var phiSets = calcPhiSets(item); if (!item.value) { - return getPhiSetsForLabel(phiSets, item.label) + makeBranch(item.label, item.currLabelId); + return (item.labelJS = getPhiSetsForLabel(phiSets, item.label)) + makeBranch(item.label, item.currLabelId); } else { - var condition = finalizeLLVMParameter(item.value); - var labelTrue = getPhiSetsForLabel(phiSets, item.labelTrue) + makeBranch(item.labelTrue, item.currLabelId); - var labelFalse = getPhiSetsForLabel(phiSets, item.labelFalse) + makeBranch(item.labelFalse, item.currLabelId); + var condition = item.valueJS = finalizeLLVMParameter(item.value); + var labelTrue = (item.labelTrueJS = getPhiSetsForLabel(phiSets, item.labelTrue)) + makeBranch(item.labelTrue, item.currLabelId); + var labelFalse = (item.labelFalseJS = getPhiSetsForLabel(phiSets, item.labelFalse)) + makeBranch(item.labelFalse, item.currLabelId); if (labelTrue == ';' && labelFalse == ';') return ';'; var head = 'if (' + condition + ') { '; var head2 = 'if (!(' + condition + ')) { '; @@ -940,11 +966,11 @@ function JSify(data, functionsOnly, givenFunctions) { makeFuncLineActor('switch', function(item) { // TODO: Find a case where switch is important, and benchmark that. var SWITCH_IN_SWITCH = 1; var phiSets = calcPhiSets(item); - // Consolidate checks that go to the same label. This is important because it makes the - // js optimizer hoistMultiples much easier to implement (we hoist into one place, not - // many). + // Consolidate checks that go to the same label. This is important because it makes the relooper simpler and faster. var targetLabels = {}; // for each target label, the list of values going to it + var switchLabelMap = {}; item.switchLabels.forEach(function(switchLabel) { + switchLabelMap[switchLabel.label] = switchLabel; if (!targetLabels[switchLabel.label]) { targetLabels[switchLabel.label] = []; } @@ -953,20 +979,33 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = ''; var first = true; var signedIdent = makeSignOp(item.ident, item.type, 're'); // we need to standardize for purpose of comparison + if (RELOOP) { + item.groupedLabels = []; + } for (var targetLabel in targetLabels) { if (!first) { ret += 'else '; } else { first = false; } - ret += 'if (' + targetLabels[targetLabel].map(function(value) { + var value = targetLabels[targetLabel].map(function(value) { return makeComparison(signedIdent, makeSignOp(value, item.type, 're'), item.type) - }).join(' || ') + ') {\n'; - ret += ' ' + getPhiSetsForLabel(phiSets, targetLabel) + makeBranch(targetLabel, item.currLabelId || null) + '\n'; + }).join(' || '); + ret += 'if (' + value + ') {\n'; + var phiSet = getPhiSetsForLabel(phiSets, targetLabel); + ret += ' ' + phiSet + makeBranch(targetLabel, item.currLabelId || null) + '\n'; ret += '}\n'; + if (RELOOP) { + item.groupedLabels.push({ + label: targetLabel, + value: value, + labelJS: phiSet + }); + } } if (item.switchLabels.length > 0) ret += 'else {\n'; - ret += getPhiSetsForLabel(phiSets, item.defaultLabel) + makeBranch(item.defaultLabel, item.currLabelId) + '\n'; + var phiSet = item.defaultLabelJS = getPhiSetsForLabel(phiSets, item.defaultLabel); + ret += phiSet + makeBranch(item.defaultLabel, item.currLabelId) + '\n'; if (item.switchLabels.length > 0) ret += '}\n'; if (item.value) { ret += ' ' + toNiceIdent(item.value); @@ -1015,8 +1054,11 @@ function JSify(data, functionsOnly, givenFunctions) { } item.assignTo = null; } - ret += 'if (!__THREW__) { ' + getPhiSetsForLabel(phiSets, item.toLabel) + makeBranch(item.toLabel, item.currLabelId) - + ' } else { ' + getPhiSetsForLabel(phiSets, item.unwindLabel) + makeBranch(item.unwindLabel, item.currLabelId) + ' }'; + item.reloopingJS = ret; // everything but the actual branching (which the relooper will do for us) + item.toLabelJS = getPhiSetsForLabel(phiSets, item.toLabel); + item.unwindLabelJS = getPhiSetsForLabel(phiSets, item.unwindLabel); + ret += 'if (!__THREW__) { ' + item.toLabelJS + makeBranch(item.toLabel, item.currLabelId) + + ' } else { ' + item.unwindLabelJS + makeBranch(item.unwindLabel, item.currLabelId) + ' }'; return ret; }); makeFuncLineActor('atomic', function(item) { diff --git a/src/library.js b/src/library.js index c7fe9fcb..fd5e0fae 100644 --- a/src/library.js +++ b/src/library.js @@ -5841,7 +5841,7 @@ LibraryManager.library = { setjmp__inline: function(env) { // Save the label - return '(' + makeSetValue(env, '0', '__label__', 'i32') + ', 0)'; + return '(' + makeSetValue(env, '0', 'label', 'i32') + ', 0)'; }, longjmp: function(env, value) { diff --git a/src/relooper.js b/src/relooper.js new file mode 100644 index 00000000..65d81281 --- /dev/null +++ b/src/relooper.js @@ -0,0 +1,7 @@ +// Relooper, (C) 2012 Alon Zakai, MIT license, https://github.com/kripken/Relooper +var Relooper = (function() { +function ca(b){throw b}var fa=void 0,ha=!0,ia=null,ja=!1;function ka(){return(function(){})}function la(b){return(function(){return b})}var ma;try{this.Module=Module}catch(ra){this.Module=Module={}}var sa="object"===typeof process&&"function"===typeof require,ua="object"===typeof window,va="function"===typeof importScripts,ya=!ua&&!sa&&!va;if(sa){Module.print=(function(b){process.stdout.write(b+"\n")});Module.printErr=(function(b){process.stderr.write(b+"\n")});var za=require("fs"),Ea=require("path");Module.read=(function(b){var b=Ea.normalize(b),c=za.readFileSync(b).toString();!c&&b!=Ea.resolve(b)&&(b=path.join(__dirname,"..","src",b),c=za.readFileSync(b).toString());return c});Module.load=(function(b){Fa(read(b))});Module.arguments||(Module.arguments=process.argv.slice(2))}ya&&(Module.print=print,"undefined"!=typeof printErr&&(Module.printErr=printErr),Module.read="undefined"!=typeof read?read:(function(b){snarf(b)}),Module.arguments||("undefined"!=typeof scriptArgs?Module.arguments=scriptArgs:"undefined"!=typeof arguments&&(Module.arguments=arguments)));ua&&!va&&(Module.print||(Module.print=(function(b){console.log(b)})),Module.printErr||(Module.printErr=(function(b){console.log(b)})));if(ua||va){Module.read=(function(b){var c=new XMLHttpRequest;c.open("GET",b,ja);c.send(ia);return c.responseText}),Module.arguments||"undefined"!=typeof arguments&&(Module.arguments=arguments)}va&&(Module.print||(Module.print=ka()),Module.load=importScripts);!va&&!ua&&!sa&&!ya&&ca("Unknown runtime environment. Where are we?");function Fa(b){eval.call(ia,b)}"undefined"==!Module.load&&Module.read&&(Module.load=(function(b){Fa(Module.read(b))}));Module.print||(Module.print=ka());Module.printErr||(Module.printErr=Module.print);Module.arguments||(Module.arguments=[]);Module.print=Module.print;Module.H=Module.printErr;Module.preRun||(Module.preRun=[]);Module.postRun||(Module.postRun=[]);function Ga(b){if(La==1){return 1}var c={"%i1":1,"%i8":1,"%i16":2,"%i32":4,"%i64":8,"%float":4,"%double":8}["%"+b];if(!c){if(b.charAt(b.length-1)=="*"){c=La}else{if(b[0]=="i"){b=parseInt(b.substr(1));Ma(b%8==0);c=b/8}}}return c}function Na(){var b=[],c=0;this.Bb=(function(d){d=d&255;if(c){b.push(d);c--}if(b.length==0){if(d<128){return String.fromCharCode(d)}b.push(d);c=d>191&&d<224?1:2;return""}if(c>0){return""}var d=b[0],e=b[1],f=b[2],d=d>191&&d<224?String.fromCharCode((d&31)<<6|e&63):String.fromCharCode((d&15)<<12|(e&63)<<6|f&63);b.length=0;return d});this.Ik=(function(b){for(var b=unescape(encodeURIComponent(b)),c=[],f=0;f<b.length;f++){c.push(b.charCodeAt(f))}return c})}function Qa(b){var c=a;a=a+b;a=a+3>>2<<2;return c}function Ra(b){var c=Sa;Sa=Sa+b;Sa=Sa+3>>2<<2;Sa>=Va&&Za("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value ( "+Va+"), (2) compile with ALLOW_MEMORY_GROWTH which adjusts the |