aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2012-11-09 16:28:02 -0800
committerAlon Zakai <alonzakai@gmail.com>2012-11-09 16:28:02 -0800
commita0979a4bf7db9e53b6ffe26077406b14db8afa06 (patch)
treeaa8890e7d8a893e4746d2c63de0bdccdc0862676
parentadc36358d3efd4efaf09defff7d090ba71ea6003 (diff)
parentd27357b5fea169eb7a61e46e609381083834f704 (diff)
Merge branch 'relooper2' into incoming
-rwxr-xr-xemcc3
-rw-r--r--src/analyzer.js373
-rw-r--r--src/compiler.js3
-rw-r--r--src/jsifier.js148
-rw-r--r--src/library.js2
-rw-r--r--src/relooper.js7
-rwxr-xr-xtests/runner.py3
-rw-r--r--tools/js-optimizer.js28
-rw-r--r--tools/test-js-optimizer-output.js28
-rw-r--r--tools/test-js-optimizer-regs.js2
-rw-r--r--tools/test-js-optimizer-t3.js20
-rw-r--r--tools/test-js-optimizer.js144
12 files changed, 222 insertions, 539 deletions
diff --git a/emcc b/emcc
index 1b2be959..29855f03 100755
--- a/emcc
+++ b/emcc
@@ -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