diff options
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 153 |
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); |