diff options
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r-- | tools/js-optimizer.js | 94 |
1 files changed, 64 insertions, 30 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d84c664a..7094cdfc 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -15,6 +15,7 @@ var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIR if (ENVIRONMENT_IS_NODE) { // Expose functionality in the same simple way that the shells work + // Note that we pollute the global namespace here, otherwise we break in node print = function(x) { process['stdout'].write(x + '\n'); }; @@ -33,26 +34,30 @@ if (ENVIRONMENT_IS_NODE) { return ret; }; + load = function(f) { + globalEval(read(f)); + }; + arguments_ = process['argv'].slice(2); } else if (ENVIRONMENT_IS_SHELL) { // Polyfill over SpiderMonkey/V8 differences if (!this['read']) { - read = function(f) { snarf(f) }; + this['read'] = function(f) { snarf(f) }; } - if (!this['arguments']) { + if (typeof scriptArgs != 'undefined') { arguments_ = scriptArgs; - } else { + } else if (typeof arguments != 'undefined') { arguments_ = arguments; } } else if (ENVIRONMENT_IS_WEB) { - print = printErr = function(x) { + this['print'] = printErr = function(x) { console.log(x); }; - read = function(url) { + this['read'] = function(url) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); xhr.send(null); @@ -65,7 +70,7 @@ if (ENVIRONMENT_IS_NODE) { } else if (ENVIRONMENT_IS_WORKER) { // We can do very little here... - load = importScripts; + this['load'] = importScripts; } else { throw 'Unknown runtime environment. Where are we?'; @@ -76,17 +81,17 @@ function globalEval(x) { } if (typeof load == 'undefined' && typeof read != 'undefined') { - load = function(f) { + this['load'] = function(f) { globalEval(read(f)); }; } if (typeof printErr === 'undefined') { - printErr = function(){}; + this['printErr'] = function(){}; } if (typeof print === 'undefined') { - print = printErr; + this['print'] = printErr; } // *** Environment setup code *** @@ -239,7 +244,7 @@ function emptyNode() { // Dump the AST. Useful for debugging. For example, // node tools/js-optimizer.js ABSOLUTE_PATH_TO_FILE dumpAst function dumpAst(ast) { - printErr(JSON.stringify(ast)); + printErr(JSON.stringify(ast, null, ' ')); } function dumpSrc(ast) { @@ -576,16 +581,10 @@ function optimizeShiftsInternal(ast, conservative) { var name = node[2][1]; var data = vars[name]; var parent = stack[stack.length-3]; - var parentIndex; - if (parent[0] == 'defun') { - parentIndex = 3; - } else if (parent[0] == 'block') { - parentIndex = 1; - } else { - throw 'Invalid parent for assign-shift: ' + dump(parent); - } - var i = parent[parentIndex].indexOf(stack[stack.length-2]); - parent[parentIndex].splice(i+1, 0, ['stat', ['assign', true, ['name', name + '$s' + data.primaryShift], ['binary', '>>', ['name', name, true], ['num', data.primaryShift]]]]); + var statements = getStatements(parent); + assert(statements, 'Invalid parent for assign-shift: ' + dump(parent)); + var i = statements.indexOf(stack[stack.length-2]); + statements.splice(i+1, 0, ['stat', ['assign', true, ['name', name + '$s' + data.primaryShift], ['binary', '>>', ['name', name, true], ['num', data.primaryShift]]]]); } else if (node[0] == 'var') { var args = node[1]; for (var i = 0; i < args.length; i++) { @@ -801,18 +800,24 @@ function vacuum(ast) { if (node[0] == 'block' && (!node[1] || (typeof node[1] != 'object') || node[1].length == 0 || (node[1].length == 1 && isEmpty(node[1])))) return true; return false; } - function simplifyList(node, i) { + function simplifyList(node, si) { var changed = false; - var pre = node[i].length; - node[i] = node[i].filter(function(node) { return !isEmpty(node) }); - if (node[i].length < pre) changed = true; - // Also, seek blocks with single items we can simplify - node[i] = node[i].map(function(subNode) { - if (subNode[0] == 'block' && typeof subNode[1] == 'object' && subNode[1].length == 1 && subNode[1][0][0] == 'if') { - return subNode[1][0]; + // Merge block items into this list, thus removing unneeded |{ .. }|'s + var statements = node[si]; + var i = 0; + while (i < statements.length) { + var subNode = statements[i]; + if (subNode[0] == 'block') { + statements.splice.apply(statements, [i, 1].concat(subNode[1] || [])); + changed = true; + } else { + i++; } - return subNode; - }); + } + // Remove empty items + var pre = node[si].length; + node[si] = node[si].filter(function(node) { return !isEmpty(node) }); + if (node[si].length < pre) changed = true; if (changed) { return node; } @@ -1002,6 +1007,35 @@ function hoistMultiples(ast) { }); vacuum(ast); + + // Afterpass: Reduce + // if (..) { .. break|continue } else { .. } + // to + // if (..) { .. break|continue } .. + traverseGenerated(ast, function(container, type) { + var statements = getStatements(container); + if (!statements) return; + for (var i = 0; i < statements.length; i++) { + var node = statements[i]; + if (node[0] == 'if' && node[2][0] == 'block' && node[3] && node[3][0] == 'block') { + var stat1 = node[2][1], stat2 = node[3][1]; + // If break|continue in the latter and not the former, reverse them + if (!(stat1[stat1.length-1][0] in LOOP_FLOW) && (stat2[stat2.length-1][0] in LOOP_FLOW)) { + var temp = node[3]; + node[3] = node[2]; + node[2] = temp; + node[1] = ['unary-prefix', '!', node[1]]; + simplifyNotComps(node[1]); // bake the ! into the expression + stat1 = node[2][1]; + stat2 = node[3][1]; + } + if (stat1[stat1.length-1][0] in LOOP_FLOW) { + statements.splice.apply(statements, [i+1, 0].concat(stat2)); + node[3] = null; + } + } + } + }); } // Simplifies loops |