aboutsummaryrefslogtreecommitdiff
path: root/tools/js-optimizer.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r--tools/js-optimizer.js153
1 files changed, 106 insertions, 47 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index f9b2cc1c..ab509f14 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -667,36 +667,44 @@ function optimizeShiftsAggressive(ast) {
optimizeShiftsInternal(ast, false);
}
-function simplifyExpressionsPost(ast) {
- // We often have branchings that are simplified so one end vanishes, and
- // we then get
- // if (!(x < 5))
- // or such. Simplifying these saves space and time.
- function simplifyNotComps(ast) {
- traverse(ast, function(node, type) {
- if (type == 'unary-prefix' && node[1] == '!' && node[2][0] == 'binary') {
- if (node[2][1] == '<') {
- return ['binary', '>=', node[2][2], node[2][3]];
- } else if (node[2][1] == '>') {
- return ['binary', '<=', node[2][2], node[2][3]];
- } else if (node[2][1] == '==') {
- return ['binary', '!=', node[2][2], node[2][3]];
- } else if (node[2][1] == '!=') {
- return ['binary', '==', node[2][2], node[2][3]];
- } else if (node[2][1] == '===') {
- return ['binary', '!==', node[2][2], node[2][3]];
- } else if (node[2][1] == '!==') {
- return ['binary', '===', node[2][2], node[2][3]];
- }
+// We often have branchings that are simplified so one end vanishes, and
+// we then get
+// if (!(x < 5))
+// or such. Simplifying these saves space and time.
+function simplifyNotComps(ast) {
+ traverse(ast, function(node, type) {
+ if (type == 'unary-prefix' && node[1] == '!' && node[2][0] == 'binary') {
+ if (node[2][1] == '<') {
+ return ['binary', '>=', node[2][2], node[2][3]];
+ } else if (node[2][1] == '>') {
+ return ['binary', '<=', node[2][2], node[2][3]];
+ } else if (node[2][1] == '==') {
+ return ['binary', '!=', node[2][2], node[2][3]];
+ } else if (node[2][1] == '!=') {
+ return ['binary', '==', node[2][2], node[2][3]];
+ } else if (node[2][1] == '===') {
+ return ['binary', '!==', node[2][2], node[2][3]];
+ } else if (node[2][1] == '!==') {
+ return ['binary', '===', node[2][2], node[2][3]];
}
- });
- }
-
- // Go
+ }
+ });
+}
+function simplifyExpressionsPost(ast) {
simplifyNotComps(ast);
}
+function hasSideEffects(node) { // this is 99% incomplete and wrong! It just works on __label__ == X and number literals
+ if (node[0] == 'num') return false;
+ if (node[0] == 'binary' && (node[1] == '==' || node[1] == '!=') && node[2][0] == 'name' &&
+ node[3][0] == 'num') {
+ return false;
+ } else {
+ return true;
+ }
+}
+
// Clear out empty ifs and blocks, and redundant blocks/stats and so forth
function vacuum(ast) {
function isEmpty(node) {
@@ -729,18 +737,9 @@ function vacuum(ast) {
}
var type = node[0];
if (type == 'defun' && isGenerated(node[1])) {
+ simplifyNotComps(node);
traverse(node, function(node, type) {
- if (type == 'if') {
- if (((node[2][0] == 'block' && (!node[2][1] || node[2][1].length == 0)) ||
- jsonCompare(node[2], emptyNode())) && !node[3]) {
- more = true;
- return emptyNode();
- } else if (node[3] && isEmpty(node[3])) {
- more = true;
- node[3] = null;
- return node;
- }
- } else if (type == 'block' && node[1] && node[1].length == 1 && node[1][0][0] == 'block') {
+ if (type == 'block' && node[1] && node[1].length == 1 && node[1][0][0] == 'block') {
more = true;
return node[1][0];
} else if (type == 'stat' && node[1][0] == 'block') {
@@ -762,9 +761,25 @@ function vacuum(ast) {
} else if (type == 'label' && jsonCompare(node[2], emptyNode())) {
more = true;
return emptyNode();
- } else if (type == 'if' && isEmpty(node[3])) { // empty else clauses
- node[3] = null;
- return node;
+ } else if (type == 'if') {
+ var empty2 = isEmpty(node[2]), empty3 = isEmpty(node[3]), has3 = node.length == 4;
+ if (!empty2 && empty3 && has3) { // empty else clauses
+ more = true;
+ return node.slice(0, 3);
+ } else if (empty2 && !empty3) { // empty if blocks
+ more = true;
+ return ['if', ['unary-prefix', '!', node[1]], node[3]];
+ } else if (empty2 && empty3) {
+ more = true;
+ if (hasSideEffects(node[1])) {
+ return ['stat', node[1]];
+ } else {
+ return emptyNode();
+ }
+ }
+ } else if (type == 'do' && isEmpty(node[2]) && !hasSideEffects(node[1])) {
+ more = true;
+ return emptyNode();
}
});
}
@@ -772,6 +787,16 @@ function vacuum(ast) {
}
}
+function getStatements(node) {
+ if (node[0] == 'defun') {
+ return node[3];
+ } else if (node[0] == 'block') {
+ return node[1];
+ } else {
+ return null;
+ }
+}
+
// Multiple blocks from the relooper are, in general, implemented by
// if (__label__ == x) { } else if ..
// and branching into them by
@@ -781,12 +806,7 @@ function hoistMultiples(ast) {
ast[1].forEach(function(node, i) {
if (!(node[0] == 'defun' && isGenerated(node[1]))) return;
traverse(node, function(node, type) {
- var statements = null;
- if (type == 'defun') {
- statements = node[3];
- } else if (type == 'block') {
- statements = node[1];
- }
+ var statements = getStatements(node);
if (!statements) return;
var modified = false;
for (var i = 0; i < statements.length-1; i++) {
@@ -833,11 +853,11 @@ function hoistMultiples(ast) {
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
+ // That's it! Hoist away. We can also throw away the __label__ setting as its goal has already been achieved
found = true;
modifiedI = true;
postInner[2] = ['block', []];
- return ['block', [preNode].concat(labelBlock[1])];
+ return labelBlock;
}
}
});
@@ -850,6 +870,45 @@ function hoistMultiples(ast) {
}
if (modified) return node;
});
+
+ // After hoisting in this function, it is safe to remove { __label__ = x; } blocks, because
+ // if they were leading to the next code right after them, they would be hoisted, and if they
+ // are going to some other place entirely, they would break or continue. The only risky
+ // situation is if the code after us is a multiple, in which case we might be checking for
+ // this label inside it (or in a later multiple, even)
+ function tryEliminate(node) {
+ if (node[0] == 'if') {
+ if (tryEliminate(node[2])) node[2] = emptyNode();
+ if (node[3] && tryEliminate(node[3])) node[3] = emptyNode();
+ } else {
+ if (node[0] == 'block' && node[1] && node[1].length == 1) {
+ var subNode = node[1][0];
+ if (subNode[0] == 'stat' && subNode[1][0] == 'assign' && subNode[1][2][0] == 'name' &&
+ subNode[1][2][1] == '__label__' && subNode[1][3][0] == 'num') {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ function getActualStatement(node) { // find the actual active statement, ignoring a label and one-time do loop
+ if (node[0] == 'label') node = node[2];
+ if (node[0] == 'do') node = node[2];
+ if (node[0] == 'block' && node[1].length == 1) node = node[1][0];
+ return node;
+ }
+ vacuum([0, [node]]);
+ traverse(node, function(node, type) {
+ var statements = getStatements(node);
+ if (!statements) return;
+ for (var i = 0; i < statements.length-1; i++) {
+ var curr = getActualStatement(statements[i]);
+ var next = statements[i+1];
+ if (curr[0] == 'if' && next[0] != 'if' && next[0] != 'label' && next[0] != 'do' && next[0] != 'while') {
+ tryEliminate(curr);
+ }
+ }
+ });
});
vacuum(ast);