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.js177
1 files changed, 177 insertions, 0 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index b51fe22e..2f6b2ff3 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -119,6 +119,8 @@ load('utility.js');
var FUNCTION = set('defun', 'function');
var LOOP = set('do', 'while', 'for');
var LOOP_FLOW = set('break', 'continue');
+var ASSIGN_OR_ALTER = set('assign', 'unary-postfix', 'unary-prefix');
+var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch');
var NULL_NODE = ['name', 'null'];
var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]];
@@ -1140,6 +1142,180 @@ function loopOptimizer(ast) {
vacuum(ast);
}
+// Very simple 'registerization', coalescing of variables into a smaller number.
+// We do not optimize when there are switches, so this pass only makes sense with
+// relooping.
+// TODO: Consider how this fits in with the rest of the optimization toolchain. Do
+// we still need the eliminator? Closure? And in what order? Perhaps just
+// closure simple?
+function registerize(ast) {
+ traverseGeneratedFunctions(ast, function(fun) {
+ // Replace all var definitions with assignments; we will add var definitions at the top after we registerize
+ // We also mark local variables - i.e., having a var definition
+ var localVars = {};
+ var hasSwitch = false; // we cannot optimize variables if there is a switch
+ traverse(fun, function(node, type) {
+ if (type == 'var') {
+ node[1].forEach(function(defined) { localVars[defined[0]] = 1 });
+ var vars = node[1].filter(function(varr) { return varr[1] });
+ if (vars.length > 1) {
+ var ret = ['stat', []];
+ var curr = ret[1];
+ for (var i = 0; i < vars.length-1; i++) {
+ curr[0] = 'seq';
+ curr[1] = ['assign', true, ['name', vars[i][0]], vars[i][1]];
+ if (i != vars.length-2) curr = curr[2] = [];
+ }
+ curr[2] = ['assign', true, ['name', vars[vars.length-1][0]], vars[vars.length-1][1]];
+ return ret;
+ } else if (vars.length == 1) {
+ return ['stat', ['assign', true, ['name', vars[0][0]], vars[0][1]]];
+ } else {
+ return emptyNode();
+ }
+ } else if (type == 'switch') {
+ hasSwitch = true;
+ }
+ });
+ vacuum(fun);
+ // Find the # of uses of each variable.
+ // While doing so, check if all a variable's uses are dominated in a simple
+ // way by a simple assign, if so, then we can assign its register to it
+ // just for its definition to its last use, and not to the entire toplevel loop,
+ // we call such variables "optimizable"
+ var varUses = {};
+ var level = 1;
+ var levelDominations = {};
+ var varLevels = {};
+ var possibles = {};
+ var unoptimizables = {};
+ traverse(fun, function(node, type) {
+ if (type == 'name') {
+ var name = node[1];
+ if (localVars[name]) {
+ if (!varUses[name]) varUses[name] = 0;
+ varUses[name]++;
+ if (possibles[name] && !varLevels[name]) unoptimizables[name] = 1; // used outside of simple domination
+ }
+ } else if (type == 'assign' && typeof node[1] != 'string') {
+ if (node[2] && node[2][0] == 'name') {
+ var name = node[2][1];
+ // if local and not yet used, this might be optimizable if we dominate
+ // all other uses
+ if (localVars[name] && !varUses[name] && !varLevels[name]) {
+ possibles[name] = 1;
+ varLevels[name] = level;
+ if (!levelDominations[level]) levelDominations[level] = {};
+ levelDominations[level][name] = 1;
+ }
+ }
+ } else if (type in CONTROL_FLOW) {
+ level++;
+ }
+ }, function(node, type) {
+ if (type in CONTROL_FLOW) {
+ // Invalidate all dominating on this level, further users make it unoptimizable
+ for (var name in levelDominations[level]) {
+ varLevels[name] = 0;
+ }
+ levelDominations[level] = null;
+ level--;
+ }
+ });
+ var optimizables = {};
+ if (!hasSwitch) {
+ for (var possible in possibles) {
+ if (!unoptimizables[possible]) optimizables[possible] = 1;
+ }
+ }
+ // Go through the function's code, assigning 'registers'.
+ // The only tricky bit is to keep variables locked on a register through loops,
+ // since they can potentially be returned to. Optimizable variables lock onto
+ // loops that they enter, unoptimizable variables lock in a conservative way
+ // into the topmost loop.
+ // Note that we cannot lock onto a variable in a loop if it was used and free'd
+ // before! (then they could overwrite us in the early part of the loop). For now
+ // we just use a fresh register to make sure we avoid this, but it could be
+ // optimized to check for safe registers (free, and not used in this loop level).
+ var varRegs = {}; // maps variables to the register they will use all their life
+ var freeRegs = [];
+ var nextReg = 1;
+ var fullNames = {};
+ var loopRegs = {}; // for each loop nesting level, the list of bound variables
+ var loops = 0; // 0 is toplevel, 1 is first loop, etc
+ var saved = 0;
+ var activeOptimizables = {};
+ var optimizableLoops = {};
+ function decUse(name) {
+ if (!varUses[name]) return false; // no uses left, or not a relevant variable
+ if (optimizables[name]) activeOptimizables[name] = 1;
+ var reg = varRegs[name];
+ if (!reg) {
+ // acquire register
+ if (optimizables[name] && freeRegs.length > 0) {
+ reg = freeRegs.pop();
+ saved++;
+ } else {
+ reg = nextReg++;
+ fullNames[reg] = 'r' + reg; // TODO: even smaller names
+ }
+ varRegs[name] = reg;
+ }
+ varUses[name]--;
+ assert(varUses[name] >= 0);
+ if (varUses[name] == 0) {
+ if (optimizables[name]) delete activeOptimizables[name];
+ // If we are not in a loop, or we are optimizable and not bound to a loop
+ // (we might have been in one but left it), we can free the register now.
+ if (loops == 0 || (optimizables[name] && !optimizableLoops[name])) {
+ // free register
+ freeRegs.push(reg);
+ } else {
+ // when the relevant loop is exited, we will free the register
+ var releventLoop = optimizables[name] ? (optimizableLoops[name] || 1) : 1;
+ if (!loopRegs[releventLoop]) loopRegs[releventLoop] = [];
+ loopRegs[releventLoop].push(reg);
+ }
+ }
+ return true;
+ }
+ traverse(fun, function(node, type) { // XXX we rely on traversal order being the same as execution order here
+ if (type == 'name') {
+ var name = node[1];
+ if (decUse(name)) {
+ node[1] = fullNames[varRegs[name]];
+ }
+ } else if (type in LOOP) {
+ loops++;
+ // Active optimizables lock onto this loop, if not locked onto one that encloses this one
+ for (var name in activeOptimizables) {
+ if (!optimizableLoops[name]) {
+ optimizableLoops[name] = loops;
+ }
+ }
+ }
+ }, function(node, type) {
+ if (type in LOOP) {
+ // Free registers that were locked to this loop
+ if (loopRegs[loops]) {
+ freeRegs = freeRegs.concat(loopRegs[loops]);
+ loopRegs[loops] = [];
+ }
+ loops--;
+ }
+ });
+ // Add vars at the beginning
+ if (nextReg > 1) {
+ var vars = [];
+ for (var i = 1; i < nextReg; i++) {
+ vars.push([fullNames[i]]);
+ }
+ getStatements(fun).unshift(['var', vars]);
+ }
+ printErr(fun[1] + ': saved ' + saved + ' / ' + (saved + nextReg - 1) + ' vars through registerization'); // not totally accurate
+ });
+}
+
// Passes table
var compress = false;
@@ -1156,6 +1332,7 @@ var passes = {
simplifyExpressionsPost: simplifyExpressionsPost,
hoistMultiples: hoistMultiples,
loopOptimizer: loopOptimizer,
+ registerize: registerize,
compress: function() { compress = true; }
};