aboutsummaryrefslogtreecommitdiff
path: root/tools/js-optimizer.js
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2011-12-20 10:32:59 -0800
committerAlon Zakai <alonzakai@gmail.com>2011-12-20 11:04:45 -0800
commit8060c58d107a76d78526714e81c18e1344793620 (patch)
treed731c9b1246062474b4c8183679e45957146b190 /tools/js-optimizer.js
parent2f3cb58579bbca6645b144de339b73c19f37abed (diff)
hoist multiples into branchings right before them
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r--tools/js-optimizer.js142
1 files changed, 132 insertions, 10 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index bf971951..4d4ff1a9 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -357,6 +357,136 @@ function simplifyExpressionsPost(ast) {
simplifyNotComps(ast);
}
+// Multiple blocks from the relooper are, in general, implemented by
+// if (__label__ == x) { } else if ..
+// and branching into them by
+// if (condition) { __label__ == x } else ..
+// We can hoist the multiple block into the condition, thus removing code and one 'if' check
+function hoistMultiples(ast) {
+ ast[1].forEach(function(node, i) {
+ var type = node[0];
+ if (type == 'defun') {
+ var statements = node[3];
+ for (var i = 0; i < statements.length-1; i++) {
+ var modified = false;
+ var pre = statements[i];
+ if (pre[0] != 'if') continue;
+ var post = statements[i+1];
+ // Look into some block types. shell() will then recreate the shell that we looked into
+ var postInner = post;
+ var shell = function(x) { return x };
+ while (true) {
+ /*if (postInner[0] == 'block') {
+ postInner = postInner[1][0];
+ } else */if (postInner[0] == 'label') {
+ shell = (function(oldShell, oldPostInner) {
+ return function(x) {
+ return oldShell(['label', oldPostInner[1], x]);
+ };
+ })(shell, postInner);
+ postInner = postInner[2];
+ } else if (postInner[0] == 'do') {
+ shell = (function(oldShell, oldPostInner) {
+ return function(x) {
+ return oldShell(['do', copy(oldPostInner[1]), ['block', [x]]]);
+ }
+ })(shell, postInner);;
+ postInner = postInner[2][1][0];
+ } else {
+ break; // give up
+ }
+ }
+ if (postInner[0] != 'if') continue;
+ // Look into this if, and its elseifs
+ while (postInner) {
+ assert(postInner[0] == 'if');
+ var cond = postInner[1];
+ if (cond[0] == 'binary' && cond[1] == '==' && cond[2][0] == 'name' && cond[2][1] == '__label__') {
+ assert(cond[3][0] == 'num');
+ // We have a valid Multiple check here. Try to hoist it, look for the source in |pre| and its else's
+ var labelNum = cond[3][1];
+ var labelBlock = postInner[2];
+ assert(labelBlock[0] == 'block');
+ var found = false;
+ traverse(pre, function(preNode, preType) {
+ if (!found && preType == 'assign' && preNode[2][0] == 'name' && preNode[2][1] == '__label__') {
+ assert(preNode[3][0] == 'num');
+ if (preNode[3][1] == labelNum) {
+ // That's it! Hoist away
+ found = true;
+ modified = true;
+ postInner[2] = ['block', []];
+ return ['block', [preNode].concat(labelBlock[1])];
+ }
+ }
+ });
+ }
+ postInner = postInner[3]; // Proceed to look in the else clause
+ }
+ if (modified) {
+ statements[i] = shell(pre);
+ }
+ }
+ }
+ });
+
+ // Clear out empty ifs and blocks, and redundant blocks/stats
+ var more = true;
+ while (more) {
+ more = false;
+ traverse(ast, function(node, type) {
+ if (type == 'if' && node[2][0] == 'block' && node[2][1].length == 0) {
+ more = true;
+ if (node[2][2]) { // if there is an else, return that
+ return node[2][2];
+ } else {
+ return emptyNode();
+ }
+ } else if (type == 'block' && !node[1]) {
+ return emptyNode();
+ } else if (type == 'block' && (node[1].length == 0 || (node[1].length == 1 && jsonCompare(node[1][0], emptyNode())))) {
+ more = true;
+ return emptyNode();
+ } else if (type == 'block' && node[1].length == 1 && node[1][0][0] == 'block') {
+ more = true;
+ return node[1][0];
+ } else if (type == 'stat' && node[1][0] == 'block') {
+ more = true;
+ return node[1];
+ } else if (type == 'block') {
+ var pre = node[1].length;
+ node[1] = node[1].filter(function(blockItem) { return !jsonCompare(blockItem, emptyNode()) });
+ if (node[1].length < pre) {
+ more = true;
+ return node;
+ }
+ } else if (type == 'defun' && node[3].length == 1 && node[3][0][0] == 'block') {
+ more = true;
+ node[3] = node[3][0][1];
+ return node;
+ } else if (type == 'defun') {
+ var pre = node[3].length;
+ node[3] = node[3].filter(function(blockItem) { return !jsonCompare(blockItem, emptyNode()) });
+ if (node[3].length < pre) {
+ more = true;
+ return node;
+ }
+ } else if (type == 'do' && node[1][0] == 'num' && jsonCompare(node[2], emptyNode())) {
+ more = true;
+ return emptyNode();
+ } else if (type == 'label' && jsonCompare(node[2], emptyNode())) {
+ more = true;
+ return emptyNode();
+ } else if (type == 'if' && jsonCompare(node[3], emptyNode())) { // empty else clauses
+ node[3] = null;
+ return node;
+ }
+ });
+ }
+}
+
+// Simplifies loops
+// WARNING: This assumes all loops and breaks/continues are labelled
function loopOptimizer(ast) {
// Remove unneeded labels and one-time (do while(0)) loops. It is convenient to do these both at once.
function passTwo(ast) {
@@ -442,6 +572,7 @@ var passes = {
//removeUnneededLabelSettings: removeUnneededLabelSettings,
simplifyExpressionsPre: simplifyExpressionsPre,
simplifyExpressionsPost: simplifyExpressionsPost,
+ hoistMultiples: hoistMultiples,
loopOptimizer: loopOptimizer
};
@@ -457,16 +588,7 @@ if (metadata) setGeneratedFunctions(metadata);
arguments.forEach(function(arg) {
passes[arg](ast);
});
-/* TODO: run only on generated functions (but, some passes look at globals...)
-arguments.forEach(function(arg) {
- ast[1].forEach(function(node, i) {
- if (node[0] == 'defun' && isGenerated(node[1])) {
- passes[arg](node);
- }
- });
-});
-*/
-
+ast = srcToAst(astToSrc(ast)); // re-parse, to simplify a little
print(astToSrc(ast));
if (metadata) print(metadata + '\n');