aboutsummaryrefslogtreecommitdiff
path: root/tools/js-optimizer.js
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2013-07-16 13:41:37 -0700
committerAlon Zakai <alonzakai@gmail.com>2013-07-16 13:41:37 -0700
commitb0d268d121d8868be33d8633b09499b34a4db45f (patch)
treed67eaf4f5e200b5b5f57099c46a1413d43cbd287 /tools/js-optimizer.js
parent6b730836aa53f6b4896f24dd8a4b456669ae4f1a (diff)
parent475e72dc5539d9c59fc267927441a502c14a178f (diff)
Merge branch 'incoming'
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r--tools/js-optimizer.js1138
1 files changed, 852 insertions, 286 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index 7561abc8..71a59921 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -11,6 +11,7 @@
// *** Environment setup code ***
var arguments_ = [];
+var debug = false;
var ENVIRONMENT_IS_NODE = typeof process === 'object';
var ENVIRONMENT_IS_WEB = typeof window === 'object';
@@ -102,7 +103,7 @@ function globalEval(x) {
eval.call(null, x);
}
-if (typeof load == 'undefined' && typeof read != 'undefined') {
+if (typeof load === 'undefined' && typeof read != 'undefined') {
this['load'] = function(f) {
globalEval(read(f));
};
@@ -135,6 +136,9 @@ var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch');
var NAME_OR_NUM = set('name', 'num');
var ASSOCIATIVE_BINARIES = set('+', '*', '|', '&', '^');
+var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch');
+var CONTINUE_CAPTURERS = LOOP;
+
var NULL_NODE = ['name', 'null'];
var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]];
var TRUE_NODE = ['unary-prefix', '!', ['num', 0]];
@@ -146,11 +150,12 @@ var generatedFunctions = false; // whether we have received only generated funct
var extraInfo = null;
function srcToAst(src) {
- return uglify.parser.parse(src);
+ return uglify.parser.parse(src, false, debug);
}
function astToSrc(ast, minifyWhitespace) {
return uglify.uglify.gen_code(ast, {
+ debug: debug,
ascii_only: true,
beautify: !minifyWhitespace,
indent_level: 1
@@ -162,10 +167,10 @@ function astToSrc(ast, minifyWhitespace) {
function traverseChildren(node, traverse, pre, post, stack) {
for (var i = 0; i < node.length; i++) {
var subnode = node[i];
- if (typeof subnode == 'object' && subnode && subnode.length) {
+ if (Array.isArray(subnode)) {
var subresult = traverse(subnode, pre, post, stack);
- if (subresult == true) return true;
- if (subresult !== null && typeof subresult == 'object') node[i] = subresult;
+ if (subresult === true) return true;
+ if (subresult !== null && typeof subresult === 'object') node[i] = subresult;
}
}
}
@@ -186,16 +191,16 @@ function traverseChildren(node, traverse, pre, post, stack) {
// was stopped, true. Otherwise undefined.
function traverse(node, pre, post, stack) {
var type = node[0], result, len;
- var relevant = typeof type == 'string';
+ var relevant = typeof type === 'string';
if (relevant) {
if (stack) len = stack.length;
var result = pre(node, type, stack);
- if (result == true) return true;
- if (result !== null && typeof result == 'object') node = result; // Continue processing on this node
- if (stack && len == stack.length) stack.push(0);
+ if (result === true) return true;
+ if (result && result !== null) node = result; // Continue processing on this node
+ if (stack && len === stack.length) stack.push(0);
}
if (result !== null) {
- if (traverseChildren(node, traverse, pre, post, stack) == true) return true;
+ if (traverseChildren(node, traverse, pre, post, stack) === true) return true;
}
if (relevant) {
if (post) {
@@ -211,7 +216,7 @@ function traverse(node, pre, post, stack) {
function traverseGenerated(ast, pre, post, stack) {
assert(generatedFunctions);
traverse(ast, function(node) {
- if (node[0] == 'defun') {
+ if (node[0] === 'defun') {
traverse(node, pre, post, stack);
return null;
}
@@ -220,13 +225,13 @@ function traverseGenerated(ast, pre, post, stack) {
function traverseGeneratedFunctions(ast, callback) {
assert(generatedFunctions);
- if (ast[0] == 'toplevel') {
+ if (ast[0] === 'toplevel') {
var stats = ast[1];
for (var i = 0; i < stats.length; i++) {
var curr = stats[i];
- if (curr[0] == 'defun') callback(curr);
+ if (curr[0] === 'defun') callback(curr);
}
- } else if (ast[0] == 'defun') {
+ } else if (ast[0] === 'defun') {
callback(ast);
}
}
@@ -236,7 +241,7 @@ function traverseWithVariables(ast, callback) {
traverse(ast, function(node, type, stack) {
if (type in FUNCTION) {
stack.push({ type: 'function', vars: node[2] });
- } else if (type == 'var') {
+ } else if (type === 'var') {
// Find our function, add our vars
var func = stack[stack.length-1];
if (func) {
@@ -244,12 +249,12 @@ function traverseWithVariables(ast, callback) {
}
}
}, function(node, type, stack) {
- if (type == 'toplevel' || type in FUNCTION) {
+ if (type === 'toplevel' || type in FUNCTION) {
// We know all of the variables that are seen here, proceed to do relevant replacements
var allVars = stack.map(function(item) { return item ? item.vars : [] }).reduce(concatenator, []); // FIXME dictionary for speed?
traverse(node, function(node2, type2, stack2) {
// Be careful not to look into our inner functions. They have already been processed.
- if (sum(stack2) > 1 || (type == 'toplevel' && sum(stack2) == 1)) return;
+ if (sum(stack2) > 1 || (type === 'toplevel' && sum(stack2) === 1)) return;
if (type2 in FUNCTION) stack2.push(1);
return callback(node2, type2, allVars);
}, null, []);
@@ -262,7 +267,7 @@ function emptyNode() { // XXX do we need to create new nodes here? can't we reus
}
function isEmptyNode(node) {
- return node.length == 2 && node[0] == 'toplevel' && node[1].length == 0;
+ return node.length === 2 && node[0] === 'toplevel' && node[1].length === 0;
}
// Passes
@@ -284,7 +289,7 @@ function unGlobalize(ast) {
throw 'this is deprecated!'; // and does not work with parallel compilation
- assert(ast[0] == 'toplevel');
+ assert(ast[0] === 'toplevel');
var values = {};
// Find global renamings of the relevant values
ast[1].forEach(function(node, i) {
@@ -305,7 +310,7 @@ function unGlobalize(ast) {
ast[1][i][1][j] = emptyNode();
var assigned = false;
traverseWithVariables(ast, function(node, type, allVars) {
- if (type == 'assign' && node[2][0] == 'name' && node[2][1] == ident) assigned = true;
+ if (type === 'assign' && node[2][0] === 'name' && node[2][1] === ident) assigned = true;
});
ast[1][i][1][j] = [ident, value];
if (!assigned) {
@@ -315,12 +320,12 @@ function unGlobalize(ast) {
return true;
});
- if (node[1].length == 0) {
+ if (node[1].length === 0) {
ast[1][i] = emptyNode();
}
});
traverseWithVariables(ast, function(node, type, allVars) {
- if (type == 'name') {
+ if (type === 'name') {
var ident = node[1];
if (ident in values && allVars.indexOf(ident) < 0) {
return copy(values[ident]);
@@ -343,9 +348,9 @@ function unGlobalize(ast) {
// is now explicit.
function removeAssignsToUndefined(ast) {
traverse(ast, function(node, type) {
- if (type == 'assign' && jsonCompare(node[3], ['unary-prefix', 'void', ['num', 0]])) {
+ if (type === 'assign' && jsonCompare(node[3], ['unary-prefix', 'void', ['num', 0]])) {
return emptyNode();
- } else if (type == 'var') {
+ } else if (type === 'var') {
node[1] = node[1].map(function(varItem, j) {
var ident = varItem[0];
var value = varItem[1];
@@ -359,10 +364,10 @@ function removeAssignsToUndefined(ast) {
while (modified) {
modified = false;
traverse(ast, function(node, type) {
- if (type == 'assign' && jsonCompare(node[3], emptyNode())) {
+ if (type === 'assign' && jsonCompare(node[3], emptyNode())) {
modified = true;
return emptyNode();
- } else if (type == 'var') {
+ } else if (type === 'var') {
node[1] = node[1].map(function(varItem, j) {
var ident = varItem[0];
var value = varItem[1];
@@ -380,19 +385,19 @@ function removeAssignsToUndefined(ast) {
// are actually necessary. It's easy to clean those up now.
function removeUnneededLabelSettings(ast) {
traverse(ast, function(node, type) {
- if (type == 'defun') { // all of our compiled code is in defun nodes
+ if (type === 'defun') { // all of our compiled code is in defun nodes
// Find all checks
var checked = {};
traverse(node, function(node, type) {
- if (type == 'binary' && node[1] == '==' && node[2][0] == 'name' && node[2][1] == 'label') {
- assert(node[3][0] == 'num');
+ if (type === 'binary' && node[1] === '==' && node[2][0] === 'name' && node[2][1] === 'label') {
+ assert(node[3][0] === 'num');
checked[node[3][1]] = 1;
}
});
// Remove unneeded sets
traverse(node, function(node, type) {
- if (type == 'assign' && node[2][0] == 'name' && node[2][1] == 'label') {
- assert(node[3][0] == 'num');
+ if (type === 'assign' && node[2][0] === 'name' && node[2][1] === 'label') {
+ assert(node[3][0] === 'num');
if (!(node[3][1] in checked)) return emptyNode();
}
});
@@ -403,21 +408,33 @@ function removeUnneededLabelSettings(ast) {
// Various expression simplifications. Pre run before closure (where we still have metadata), Post run after.
var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^');
+var COMPARE_OPS = set('<', '<=', '>', '>=', '==', '===', '!=', '!==');
function simplifyExpressionsPre(ast) {
- // Look for (x&A)<<B>>B and replace it with X&A if possible.
- function simplifySignExtends(ast) {
+ // Simplify common expressions used to perform integer conversion operations
+ // in cases where no conversion is needed.
+ function simplifyIntegerConversions(ast) {
traverse(ast, function(node, type) {
- if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num' &&
- node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' && node[3][1] == node[2][3][1]) {
+ if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' &&
+ node[2][0] === 'binary' && node[2][1] === '<<' && node[2][3][0] === 'num' && node[3][1] === node[2][3][1]) {
+ // Transform (x&A)<<B>>B to X&A.
var innerNode = node[2][2];
var shifts = node[3][1];
- if (innerNode[0] == 'binary' && innerNode[1] == '&' && innerNode[3][0] == 'num') {
+ if (innerNode[0] === 'binary' && innerNode[1] === '&' && innerNode[3][0] === 'num') {
var mask = innerNode[3][1];
- if (mask << shifts >> shifts == mask) {
+ if (mask << shifts >> shifts === mask) {
return innerNode;
}
}
+ } else if (type === 'binary' && node[1] === '&' && node[3][0] === 'num') {
+ // Rewrite (X < Y) & 1 to (X < Y)|0. (Subsequent passes will eliminate
+ // the |0 if possible.)
+ var input = node[2];
+ var amount = node[3][1];
+ if (input[0] === 'binary' && (input[1] in COMPARE_OPS) && amount == 1) {
+ node[1] = '|';
+ node[3][1] = 0;
+ }
}
});
}
@@ -444,9 +461,10 @@ function simplifyExpressionsPre(ast) {
while (rerun) {
rerun = false;
traverse(ast, function process(node, type, stack) {
- if (type == 'binary' && node[1] == '|') {
- if (node[2][0] == 'num' && node[3][0] == 'num') {
- return ['num', node[2][1] | node[3][1]];
+ if (type === 'binary' && node[1] === '|') {
+ if (node[2][0] === 'num' && node[3][0] === 'num') {
+ node[2][1] |= node[3][1];
+ return node[2];
}
var go = false;
if (jsonCompare(node[2], ZERO)) {
@@ -466,9 +484,9 @@ function simplifyExpressionsPre(ast) {
for (var i = stack.length-1; i >= 0; i--) {
if (stack[i] >= 1) {
if (asm) {
- if (stack[stack.length-1] < 2 && node[2][0] == 'call') break; // we can only remove multiple |0s on these
+ if (stack[stack.length-1] < 2 && node[2][0] === 'call') break; // we can only remove multiple |0s on these
if (stack[stack.length-1] < 1 && (node[2][0] in COERCION_REQUIRING_OPS ||
- (node[2][0] == 'binary' && node[2][1] in COERCION_REQUIRING_BINARIES))) break; // we can remove |0 or >>2
+ (node[2][0] === 'binary' && node[2][1] in COERCION_REQUIRING_BINARIES))) break; // we can remove |0 or >>2
}
// we will replace ourselves with the non-zero side. Recursively process that node.
var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other;
@@ -479,16 +497,18 @@ function simplifyExpressionsPre(ast) {
}
rerun = true;
return process(result, result[0], stack);
- } else if (stack[i] == -1) {
+ } else if (stack[i] === -1) {
break; // Too bad, we can't
}
}
stack.push(2); // From here on up, no need for this kind of correction, it's done at the top
// (Add this at the end, so it is only added if we did not remove it)
- } else if (type == 'binary' && node[1] in USEFUL_BINARY_OPS) {
+ } else if (type === 'binary' && node[1] in USEFUL_BINARY_OPS) {
stack.push(1);
- } else if ((type == 'binary' && node[1] in SAFE_BINARY_OPS) || type == 'num' || type == 'name') {
+ } else if ((type === 'binary' && node[1] in SAFE_BINARY_OPS) || type === 'num' || type === 'name') {
stack.push(0); // This node is safe in that it does not interfere with this optimization
+ } else if (type === 'unary-prefix' && node[1] === '~') {
+ stack.push(1);
} else {
stack.push(-1); // This node is dangerous! Give up if you see this before you see '1'
}
@@ -503,7 +523,7 @@ function simplifyExpressionsPre(ast) {
var heapBits, heapUnsigned;
function parseHeap(name) {
if (name.substr(0, 4) != 'HEAP') return false;
- heapUnsigned = name[4] == 'U';
+ heapUnsigned = name[4] === 'U';
heapBits = parseInt(name.substr(heapUnsigned ? 5 : 4));
return true;
}
@@ -511,21 +531,21 @@ function simplifyExpressionsPre(ast) {
var hasTempDoublePtr = false, rerunOrZeroPass = false;
traverse(ast, function(node, type) {
- if (type == 'name') {
- if (node[1] == 'tempDoublePtr') hasTempDoublePtr = true;
- } else if (type == 'binary' && node[1] == '&' && node[3][0] == 'num') {
- if (node[2][0] == 'num') return ['num', node[2][1] & node[3][1]];
+ if (type === 'name') {
+ if (node[1] === 'tempDoublePtr') hasTempDoublePtr = true;
+ } else if (type === 'binary' && node[1] === '&' && node[3][0] === 'num') {
+ if (node[2][0] === 'num') return ['num', node[2][1] & node[3][1]];
var input = node[2];
var amount = node[3][1];
- if (input[0] == 'binary' && input[1] == '&' && input[3][0] == 'num') {
+ if (input[0] === 'binary' && input[1] === '&' && input[3][0] === 'num') {
// Collapse X & 255 & 1
node[3][1] = amount & input[3][1];
node[2] = input[2];
- } else if (input[0] == 'sub' && input[1][0] == 'name') {
+ } else if (input[0] === 'sub' && input[1][0] === 'name') {
// HEAP8[..] & 255 => HEAPU8[..]
var name = input[1][1];
if (parseHeap(name)) {
- if (amount == Math.pow(2, heapBits)-1) {
+ if (amount === Math.pow(2, heapBits)-1) {
if (!heapUnsigned) {
input[1][1] = 'HEAPU' + heapBits; // make unsigned
}
@@ -539,14 +559,21 @@ function simplifyExpressionsPre(ast) {
}
}
}
- } else if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num' &&
- node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' &&
- node[2][2][0] == 'sub' && node[2][2][1][0] == 'name') {
+ } else if (type === 'binary' && node[1] === '^') {
+ // LLVM represents bitwise not as xor with -1. Translate it back to an actual bitwise not.
+ if (node[3][0] === 'unary-prefix' && node[3][1] === '-' && node[3][2][0] === 'num' &&
+ node[3][2][1] === 1 &&
+ !(node[2][0] == 'unary-prefix' && node[2][1] == '~')) { // avoid creating ~~~ which is confusing for asm given the role of ~~
+ return ['unary-prefix', '~', node[2]];
+ }
+ } else if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' &&
+ node[2][0] === 'binary' && node[2][1] === '<<' && node[2][3][0] === 'num' &&
+ node[2][2][0] === 'sub' && node[2][2][1][0] === 'name') {
// collapse HEAPU?8[..] << 24 >> 24 etc. into HEAP8[..] | 0
var amount = node[3][1];
var name = node[2][2][1][1];
- if (amount == node[2][3][1] && parseHeap(name)) {
- if (heapBits == 32 - amount) {
+ if (amount === node[2][3][1] && parseHeap(name)) {
+ if (heapBits === 32 - amount) {
node[2][2][1][1] = 'HEAP' + heapBits;
node[1] = '|';
node[2] = node[2][2];
@@ -555,32 +582,34 @@ function simplifyExpressionsPre(ast) {
return node;
}
}
- } else if (type == 'assign') {
+ } else if (type === 'assign') {
// optimizations for assigning into HEAP32 specifically
- if (node[1] === true && node[2][0] == 'sub' && node[2][1][0] == 'name' && node[2][1][1] == 'HEAP32') {
+ if (node[1] === true && node[2][0] === 'sub' && node[2][1][0] === 'name' && node[2][1][1] === 'HEAP32') {
// HEAP32[..] = x | 0 does not need the | 0 (unless it is a mandatory |0 of a call)
- if (node[3][0] == 'binary' && node[3][1] == '|') {
- if (node[3][2][0] == 'num' && node[3][2][1] == 0 && node[3][3][0] != 'call') {
+ if (node[3][0] === 'binary' && node[3][1] === '|') {
+ if (node[3][2][0] === 'num' && node[3][2][1] === 0 && node[3][3][0] != 'call') {
node[3] = node[3][3];
- } else if (node[3][3][0] == 'num' && node[3][3][1] == 0 && node[3][2][0] != 'call') {
+ } else if (node[3][3][0] === 'num' && node[3][3][1] === 0 && node[3][2][0] != 'call') {
node[3] = node[3][2];
}
}
}
var value = node[3];
- if (value[0] == 'binary' && value[1] == '|') {
+ if (value[0] === 'binary' && value[1] === '|') {
// canonicalize order of |0 to end
- if (value[2][0] == 'num' && value[2][1] == 0) {
+ if (value[2][0] === 'num' && value[2][1] === 0) {
var temp = value[2];
value[2] = value[3];
value[3] = temp;
}
// if a seq ends in an |0, remove an external |0
// note that it is only safe to do this in assigns, like we are doing here (return (x, y|0); is not valid)
- if (value[2][0] == 'seq' && value[2][2][0] == 'binary' && value[2][2][1] in USEFUL_BINARY_OPS) {
+ if (value[2][0] === 'seq' && value[2][2][0] === 'binary' && value[2][2][1] in USEFUL_BINARY_OPS) {
node[3] = value[2];
}
}
+ } else if (type == 'sub' && node[1][0] == 'name' && /^FUNCTION_TABLE.*/.exec(node[1][1])) {
+ return null; // do not traverse subchildren here, we should not collapse 55 & 126. TODO: optimize this into a nonvirtual call (also because we lose some other opts here)!
}
});
@@ -589,27 +618,28 @@ function simplifyExpressionsPre(ast) {
if (asm) {
if (hasTempDoublePtr) {
traverse(ast, function(node, type) {
- if (type == 'assign') {
- if (node[1] === true && node[2][0] == 'sub' && node[2][1][0] == 'name' && node[2][1][1] == 'HEAP32') {
+ if (type === 'assign') {
+ if (node[1] === true && node[2][0] === 'sub' && node[2][1][0] === 'name' && node[2][1][1] === 'HEAP32') {
// remove bitcasts that are now obviously pointless, e.g.
// HEAP32[$45 >> 2] = HEAPF32[tempDoublePtr >> 2] = ($14 < $28 ? $14 : $28) - $42, HEAP32[tempDoublePtr >> 2] | 0;
var value = node[3];
- if (value[0] == 'seq' && value[1][0] == 'assign' && value[1][2][0] == 'sub' && value[1][2][1][0] == 'name' && value[1][2][1][1] == 'HEAPF32' &&
- value[1][2][2][0] == 'binary' && value[1][2][2][2][0] == 'name' && value[1][2][2][2][1] == 'tempDoublePtr') {
+ if (value[0] === 'seq' && value[1][0] === 'assign' && value[1][2][0] === 'sub' && value[1][2][1][0] === 'name' && value[1][2][1][1] === 'HEAPF32' &&
+ value[1][2][2][0] === 'binary' && value[1][2][2][2][0] === 'name' && value[1][2][2][2][1] === 'tempDoublePtr') {
// transform to HEAPF32[$45 >> 2] = ($14 < $28 ? $14 : $28) - $42;
node[2][1][1] = 'HEAPF32';
node[3] = value[1][3];
}
}
- } else if (type == 'seq') {
+ } else if (type === 'seq') {
// (HEAP32[tempDoublePtr >> 2] = HEAP32[$37 >> 2], +HEAPF32[tempDoublePtr >> 2])
// ==>
// +HEAPF32[$37 >> 2]
- if (node[0] == 'seq' && node[1][0] == 'assign' && node[1][2][0] == 'sub' && node[1][2][1][0] == 'name' &&
- (node[1][2][1][1] == 'HEAP32' || node[1][2][1][1] == 'HEAPF32') &&
- node[1][2][2][0] == 'binary' && node[1][2][2][2][0] == 'name' && node[1][2][2][2][1] == 'tempDoublePtr' &&
- node[1][3][0] == 'sub' && node[1][3][1][0] == 'name' && (node[1][3][1][1] == 'HEAP32' || node[1][3][1][1] == 'HEAPF32')) {
- if (node[1][2][1][1] == 'HEAP32') {
+ if (node[0] === 'seq' && node[1][0] === 'assign' && node[1][2][0] === 'sub' && node[1][2][1][0] === 'name' &&
+ (node[1][2][1][1] === 'HEAP32' || node[1][2][1][1] === 'HEAPF32') &&
+ node[1][2][2][0] === 'binary' && node[1][2][2][2][0] === 'name' && node[1][2][2][2][1] === 'tempDoublePtr' &&
+ node[1][3][0] === 'sub' && node[1][3][1][0] === 'name' && (node[1][3][1][1] === 'HEAP32' || node[1][3][1][1] === 'HEAPF32') &&
+ node[2][0] !== 'seq') { // avoid (x, y, z) which can be used for tempDoublePtr on doubles for alignment fixes
+ if (node[1][2][1][1] === 'HEAP32') {
node[1][3][1][1] = 'HEAPF32';
return ['unary-prefix', '+', node[1][3]];
} else {
@@ -624,11 +654,11 @@ function simplifyExpressionsPre(ast) {
// the other heap type, then eliminate the bitcast
var bitcastVars = {};
traverse(ast, function(node, type) {
- if (type == 'assign' && node[1] === true && node[2][0] == 'name') {
+ if (type === 'assign' && node[1] === true && node[2][0] === 'name') {
var value = node[3];
- if (value[0] == 'seq' && value[1][0] == 'assign' && value[1][2][0] == 'sub' && value[1][2][1][0] == 'name' &&
- (value[1][2][1][1] == 'HEAP32' || value[1][2][1][1] == 'HEAPF32') &&
- value[1][2][2][0] == 'binary' && value[1][2][2][2][0] == 'name' && value[1][2][2][2][1] == 'tempDoublePtr') {
+ if (value[0] === 'seq' && value[1][0] === 'assign' && value[1][2][0] === 'sub' && value[1][2][1][0] === 'name' &&
+ (value[1][2][1][1] === 'HEAP32' || value[1][2][1][1] === 'HEAPF32') &&
+ value[1][2][2][0] === 'binary' && value[1][2][2][2][0] === 'name' && value[1][2][2][2][1] === 'tempDoublePtr') {
var name = node[2][1];
if (!bitcastVars[name]) bitcastVars[name] = {
define_HEAP32: 0, define_HEAPF32: 0, use_HEAP32: 0, use_HEAPF32: 0, bad: false, namings: 0, defines: [], uses: []
@@ -639,15 +669,15 @@ function simplifyExpressionsPre(ast) {
}
});
traverse(ast, function(node, type) {
- if (type == 'name' && bitcastVars[node[1]]) {
+ if (type === 'name' && bitcastVars[node[1]]) {
bitcastVars[node[1]].namings++;
- } else if (type == 'assign' && node[1] === true) {
+ } else if (type === 'assign' && node[1] === true) {
var value = node[3];
- if (value[0] == 'name') {
+ if (value[0] === 'name') {
var name = value[1];
if (bitcastVars[name]) {
var target = node[2];
- if (target[0] == 'sub' && target[1][0] == 'name' && (target[1][1] == 'HEAP32' || target[1][1] == 'HEAPF32')) {
+ if (target[0] === 'sub' && target[1][0] === 'name' && (target[1][1] === 'HEAP32' || target[1][1] === 'HEAPF32')) {
bitcastVars[name]['use_' + target[1][1]]++;
bitcastVars[name].uses.push(node);
}
@@ -659,14 +689,14 @@ function simplifyExpressionsPre(ast) {
for (var v in bitcastVars) {
var info = bitcastVars[v];
// good variables define only one type, use only one type, have definitions and uses, and define as a different type than they use
- if (info.define_HEAP32*info.define_HEAPF32 == 0 && info.use_HEAP32*info.use_HEAPF32 == 0 &&
+ if (info.define_HEAP32*info.define_HEAPF32 === 0 && info.use_HEAP32*info.use_HEAPF32 === 0 &&
info.define_HEAP32+info.define_HEAPF32 > 0 && info.use_HEAP32+info.use_HEAPF32 > 0 &&
- info.define_HEAP32*info.use_HEAP32 == 0 && info.define_HEAPF32*info.use_HEAPF32 == 0 &&
- v in asmData.vars && info.namings == info.define_HEAP32+info.define_HEAPF32+info.use_HEAP32+info.use_HEAPF32) {
+ info.define_HEAP32*info.use_HEAP32 === 0 && info.define_HEAPF32*info.use_HEAPF32 === 0 &&
+ v in asmData.vars && info.namings === info.define_HEAP32+info.define_HEAPF32+info.use_HEAP32+info.use_HEAPF32) {
var correct = info.use_HEAP32 ? 'HEAPF32' : 'HEAP32';
info.defines.forEach(function(define) {
define[3] = define[3][1][3];
- if (correct == 'HEAP32') {
+ if (correct === 'HEAP32') {
define[3] = ['binary', '|', define[3], ['num', 0]];
} else {
define[3] = ['unary-prefix', '+', define[3]];
@@ -684,7 +714,7 @@ function simplifyExpressionsPre(ast) {
// optimize num >> num, in asm we need this here since we do not run optimizeShifts
traverse(ast, function(node, type) {
- if (type == 'binary' && node[1] == '>>' && node[2][0] == 'num' && node[3][0] == 'num') {
+ if (type === 'binary' && node[1] === '>>' && node[2][0] === 'num' && node[3][0] === 'num') {
node[0] = 'num';
node[1] = node[2][1] >> node[3][1];
node.length = 2;
@@ -700,15 +730,16 @@ function simplifyExpressionsPre(ast) {
while (rerun) {
rerun = false;
traverse(ast, function(node, type) {
- if (type == 'binary' && node[1] == '+') {
- if (node[2][0] == 'num' && node[3][0] == 'num') {
+ if (type === 'binary' && node[1] === '+') {
+ if (node[2][0] === 'num' && node[3][0] === 'num') {
rerun = true;
- return ['num', node[2][1] + node[3][1]];
+ node[2][1] += node[3][1];
+ return node[2];
}
for (var i = 2; i <= 3; i++) {
var ii = 5-i;
for (var j = 2; j <= 3; j++) {
- if (node[i][0] == 'num' && node[ii][0] == 'binary' && node[ii][1] == '+' && node[ii][j][0] == 'num') {
+ if (node[i][0] === 'num' && node[ii][0] === 'binary' && node[ii][1] === '+' && node[ii][j][0] === 'num') {
rerun = true;
node[ii][j][1] += node[i][1];
return node[ii];
@@ -720,15 +751,15 @@ function simplifyExpressionsPre(ast) {
}
}
- // if (x == 0) can be if (!x), etc.
+ // if (x === 0) can be if (!x), etc.
function simplifyZeroComp(ast) {
traverse(ast, function(node, type) {
var binary;
- if (type == 'if' && (binary = node[1])[0] == 'binary') {
- if ((binary[1] == '!=' || binary[1] == '!==') && binary[3][0] == 'num' && binary[3][1] == 0) {
+ if (type === 'if' && (binary = node[1])[0] === 'binary') {
+ if ((binary[1] === '!=' || binary[1] === '!==') && binary[3][0] === 'num' && binary[3][1] === 0) {
node[1] = binary[2];
return node;
- } else if ((binary[1] == '==' || binary[1] == '===') && binary[3][0] == 'num' && binary[3][1] == 0) {
+ } else if ((binary[1] === '==' || binary[1] === '===') && binary[3][0] === 'num' && binary[3][1] === 0) {
node[1] = ['unary-prefix', '!', binary[2]];
return node;
}
@@ -740,7 +771,7 @@ function simplifyExpressionsPre(ast) {
// Add final returns when necessary
var returnType = null;
traverse(fun, function(node, type) {
- if (type == 'return' && node[1]) {
+ if (type === 'return' && node[1]) {
returnType = detectAsmCoercion(node[1]);
}
});
@@ -750,14 +781,14 @@ function simplifyExpressionsPre(ast) {
var last = stats[stats.length-1];
if (last[0] != 'return') {
var returnValue = ['num', 0];
- if (returnType == ASM_DOUBLE) returnValue = ['unary-prefix', '+', returnValue];
+ if (returnType === ASM_DOUBLE) returnValue = ['unary-prefix', '+', returnValue];
stats.push(['return', returnValue]);
}
}
}
traverseGeneratedFunctions(ast, function(func) {
- simplifySignExtends(func);
+ simplifyIntegerConversions(func);
simplifyBitops(func);
joinAdditions(func);
// simplifyZeroComp(func); TODO: investigate performance
@@ -800,11 +831,11 @@ function optimizeShiftsInternal(ast, conservative) {
// vars
// XXX if var has >>=, ignore it here? That means a previous pass already optimized it
var hasSwitch = traverse(fun, function(node, type) {
- if (type == 'var') {
+ if (type === 'var') {
node[1].forEach(function(arg) {
newVar(arg[0], false, arg[1]);
});
- } else if (type == 'switch') {
+ } else if (type === 'switch') {
// The relooper can't always optimize functions, and we currently don't work with
// switch statements when optimizing shifts. Bail.
return true;
@@ -817,25 +848,25 @@ function optimizeShiftsInternal(ast, conservative) {
// optimize for code size, not speed.
traverse(fun, function(node, type, stack) {
stack.push(node);
- if (type == 'name' && vars[node[1]] && stack[stack.length-2][0] != 'assign') {
+ if (type === 'name' && vars[node[1]] && stack[stack.length-2][0] != 'assign') {
vars[node[1]].uses++;
- } else if (type == 'assign' && node[2][0] == 'name' && vars[node[2][1]]) {
+ } else if (type === 'assign' && node[2][0] === 'name' && vars[node[2][1]]) {
vars[node[2][1]].defs++;
}
}, null, []);
// First, break up elements inside a shift. This lets us see clearly what to do next.
traverse(fun, function(node, type) {
- if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num') {
+ if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num') {
var shifts = node[3][1];
if (shifts <= MAX_SHIFTS) {
// Push the >> inside the value elements
function addShift(subNode) {
- if (subNode[0] == 'binary' && subNode[1] == '+') {
+ if (subNode[0] === 'binary' && subNode[1] === '+') {
subNode[2] = addShift(subNode[2]);
subNode[3] = addShift(subNode[3]);
return subNode;
}
- if (subNode[0] == 'name' && !subNode[2]) { // names are returned with a shift, but we also note their being shifted
+ if (subNode[0] === 'name' && !subNode[2]) { // names are returned with a shift, but we also note their being shifted
var name = subNode[1];
if (vars[name]) {
vars[name].timesShifted[shifts]++;
@@ -849,7 +880,7 @@ function optimizeShiftsInternal(ast, conservative) {
}
});
traverse(fun, function(node, type) {
- if (node[0] == 'name' && node[2]) {
+ if (node[0] === 'name' && node[2]) {
return node.slice(0, 2); // clean up our notes
}
});
@@ -858,7 +889,7 @@ function optimizeShiftsInternal(ast, conservative) {
for (var name in vars) {
var data = vars[name];
var totalTimesShifted = sum(data.timesShifted);
- if (totalTimesShifted == 0) {
+ if (totalTimesShifted === 0) {
continue;
}
if (totalTimesShifted != Math.max.apply(null, data.timesShifted)) {
@@ -868,7 +899,7 @@ function optimizeShiftsInternal(ast, conservative) {
if (funFinished[name]) continue;
// We have one shift size (and possible unshifted uses). Consider replacing this variable with a shifted clone. If
// the estimated benefit is >0, we will do it
- if (data.defs == 1) {
+ if (data.defs === 1) {
data.benefit = totalTimesShifted - 2*(data.defs + (data.param ? 1 : 0));
}
if (conservative) data.benefit = 0;
@@ -884,7 +915,7 @@ function optimizeShiftsInternal(ast, conservative) {
//printErr(JSON.stringify(vars));
function cleanNotes() { // We need to mark 'name' nodes as 'processed' in some passes here; this cleans the notes up
traverse(fun, function(node, type) {
- if (node[0] == 'name' && node[2]) {
+ if (node[0] === 'name' && node[2]) {
return node.slice(0, 2);
}
});
@@ -906,7 +937,7 @@ function optimizeShiftsInternal(ast, conservative) {
}
traverse(fun, function(node, type, stack) { // add shift to assignments
stack.push(node);
- if (node[0] == 'assign' && node[1] === true && node[2][0] == 'name' && needsShift(node[2][1]) && !node[2][2]) {
+ if (node[0] === 'assign' && node[1] === true && node[2][0] === 'name' && needsShift(node[2][1]) && !node[2][2]) {
var name = node[2][1];
var data = vars[name];
var parent = stack[stack.length-3];
@@ -914,7 +945,7 @@ function optimizeShiftsInternal(ast, conservative) {
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') {
+ } else if (node[0] === 'var') {
var args = node[1];
for (var i = 0; i < args.length; i++) {
var arg = args[i];
@@ -930,14 +961,14 @@ function optimizeShiftsInternal(ast, conservative) {
cleanNotes();
traverse(fun, function(node, type, stack) { // replace shifted name with new variable
stack.push(node);
- if (node[0] == 'binary' && node[1] == '>>' && node[2][0] == 'name' && needsShift(node[2][1]) && node[3][0] == 'num') {
+ if (node[0] === 'binary' && node[1] === '>>' && node[2][0] === 'name' && needsShift(node[2][1]) && node[3][0] === 'num') {
var name = node[2][1];
var data = vars[name];
var parent = stack[stack.length-2];
// Don't modify in |x$sN = x >> 2|, in normal assigns and in var assigns
- if (parent[0] == 'assign' && parent[2][0] == 'name' && parent[2][1] == name + '$s' + data.primaryShift) return;
- if (parent[0] == name + '$s' + data.primaryShift) return;
- if (node[3][1] == data.primaryShift) {
+ if (parent[0] === 'assign' && parent[2][0] === 'name' && parent[2][1] === name + '$s' + data.primaryShift) return;
+ if (parent[0] === name + '$s' + data.primaryShift) return;
+ if (node[3][1] === data.primaryShift) {
return ['name', name + '$s' + data.primaryShift];
}
}
@@ -948,8 +979,8 @@ function optimizeShiftsInternal(ast, conservative) {
while (more) { // combine shifts in the same direction as an optimization
more = false;
traverse(fun, function(node, type) {
- if (node[0] == 'binary' && node[1] in SIMPLE_SHIFTS && node[2][0] == 'binary' && node[2][1] == node[1] &&
- node[3][0] == 'num' && node[2][3][0] == 'num') { // do not turn a << b << c into a << b + c; while logically identical, it is slower
+ if (node[0] === 'binary' && node[1] in SIMPLE_SHIFTS && node[2][0] === 'binary' && node[2][1] === node[1] &&
+ node[3][0] === 'num' && node[2][3][0] === 'num') { // do not turn a << b << c into a << b + c; while logically identical, it is slower
more = true;
return ['binary', node[1], node[2][2], ['num', node[3][1] + node[2][3][1]]];
}
@@ -958,32 +989,32 @@ function optimizeShiftsInternal(ast, conservative) {
// Before recombining, do some additional optimizations
traverse(fun, function(node, type) {
// Apply constant shifts onto constants
- if (type == 'binary' && node[1] == '>>' && node[2][0] == 'num' && node[3][0] == 'num' && node[3][1] <= MAX_SHIFTS) {
+ if (type === 'binary' && node[1] === '>>' && node[2][0] === 'num' && node[3][0] === 'num' && node[3][1] <= MAX_SHIFTS) {
var subNode = node[2];
var shifts = node[3][1];
var result = subNode[1] / Math.pow(2, shifts);
- if (result % 1 == 0) {
+ if (result % 1 === 0) {
subNode[1] = result;
return subNode;
}
}
// Optimize the case of ($a*80)>>2 into ($a*20)|0
- if (type == 'binary' && node[1] in SIMPLE_SHIFTS &&
- node[2][0] == 'binary' && node[2][1] == '*') {
+ if (type === 'binary' && node[1] in SIMPLE_SHIFTS &&
+ node[2][0] === 'binary' && node[2][1] === '*') {
var mulNode = node[2];
- if (mulNode[2][0] == 'num') {
+ if (mulNode[2][0] === 'num') {
var temp = mulNode[2];
mulNode[2] = mulNode[3];
mulNode[3] = temp;
}
- if (mulNode[3][0] == 'num') {
- if (node[1] == '<<') {
+ if (mulNode[3][0] === 'num') {
+ if (node[1] === '<<') {
mulNode[3][1] *= Math.pow(2, node[3][1]);
node[1] = '|';
node[3][1] = 0;
return node;
} else {
- if (mulNode[3][1] % Math.pow(2, node[3][1]) == 0) {
+ if (mulNode[3][1] % Math.pow(2, node[3][1]) === 0) {
mulNode[3][1] /= Math.pow(2, node[3][1]);
node[1] = '|';
node[3][1] = 0;
@@ -994,8 +1025,8 @@ function optimizeShiftsInternal(ast, conservative) {
}
/* XXX - theoretically useful optimization(s), but commented out as not helpful in practice
// Transform (x << 2) >> 2 into x & mask or something even simpler
- if (type == 'binary' && node[1] == '>>' && node[3][0] == 'num' &&
- node[2][0] == 'binary' && node[2][1] == '<<' && node[2][3][0] == 'num' && node[3][1] == node[2][3][1]) {
+ if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' &&
+ node[2][0] === 'binary' && node[2][1] === '<<' && node[2][3][0] === 'num' && node[3][1] === node[2][3][1]) {
var subNode = node[2];
var shifts = node[3][1];
var mask = ((0xffffffff << shifts) >>> shifts) | 0;
@@ -1008,11 +1039,11 @@ function optimizeShiftsInternal(ast, conservative) {
// Re-combine remaining shifts, to undo the breaking up we did before. may require reordering inside +'s
traverse(fun, function(node, type, stack) {
stack.push(node);
- if (type == 'binary' && node[1] == '+' && (stack[stack.length-2][0] != 'binary' || stack[stack.length-2][1] != '+')) {
+ if (type === 'binary' && node[1] === '+' && (stack[stack.length-2][0] != 'binary' || stack[stack.length-2][1] !== '+')) {
// 'Flatten' added items
var addedItems = [];
function flatten(node) {
- if (node[0] == 'binary' && node[1] == '+') {
+ if (node[0] === 'binary' && node[1] === '+') {
flatten(node[2]);
flatten(node[3]);
} else {
@@ -1025,15 +1056,15 @@ function optimizeShiftsInternal(ast, conservative) {
function originalOrderKey(item) {
return -originalOrder.indexOf(item);
}
- if (node[0] == 'binary' && node[1] in SIMPLE_SHIFTS) {
- if (node[3][0] == 'num' && node[3][1] <= MAX_SHIFTS) return 2*node[3][1] + (node[1] == '>>' ? 100 : 0); // 0-106
- return (node[1] == '>>' ? 20000 : 10000) + originalOrderKey(node);
+ if (node[0] === 'binary' && node[1] in SIMPLE_SHIFTS) {
+ if (node[3][0] === 'num' && node[3][1] <= MAX_SHIFTS) return 2*node[3][1] + (node[1] === '>>' ? 100 : 0); // 0-106
+ return (node[1] === '>>' ? 20000 : 10000) + originalOrderKey(node);
}
- if (node[0] == 'num') return -20000 + node[1];
+ if (node[0] === 'num') return -20000 + node[1];
return -10000 + originalOrderKey(node); // Don't modify the original order if we don't modify anything
}
for (var i = 0; i < addedItems.length; i++) {
- if (addedItems[i][0] == 'string') return; // this node is not relevant for us
+ if (addedItems[i][0] === 'string') return; // this node is not relevant for us
}
addedItems.sort(function(node1, node2) {
return key(node1) - key(node2);
@@ -1042,7 +1073,7 @@ function optimizeShiftsInternal(ast, conservative) {
var i = 0;
while (i < addedItems.length-1) { // re-combine inside addedItems
var k = key(addedItems[i]), k1 = key(addedItems[i+1]);
- if (k == k1 && k >= 0 && k1 <= 106) {
+ if (k === k1 && k >= 0 && k1 <= 106) {
addedItems[i] = ['binary', addedItems[i][1], ['binary', '+', addedItems[i][2], addedItems[i+1][2]], addedItems[i][3]];
addedItems.splice(i+1, 1);
} else {
@@ -1051,7 +1082,7 @@ function optimizeShiftsInternal(ast, conservative) {
}
var num = 0;
for (i = 0; i < addedItems.length; i++) { // combine all numbers into one
- if (addedItems[i][0] == 'num') {
+ if (addedItems[i][0] === 'num') {
num += addedItems[i][1];
addedItems.splice(i, 1);
i--;
@@ -1063,7 +1094,7 @@ function optimizeShiftsInternal(ast, conservative) {
// so it might take more space, but normally at most one more digit).
var added = false;
for (i = 0; i < addedItems.length; i++) {
- if (addedItems[i][0] == 'binary' && addedItems[i][1] == '>>' && addedItems[i][3][0] == 'num' && addedItems[i][3][1] <= MAX_SHIFTS) {
+ if (addedItems[i][0] === 'binary' && addedItems[i][1] === '>>' && addedItems[i][3][0] === 'num' && addedItems[i][3][1] <= MAX_SHIFTS) {
addedItems[i] = ['binary', '>>', ['binary', '+', addedItems[i][2], ['num', num << addedItems[i][3][1]]], addedItems[i][3]];
added = true;
}
@@ -1100,8 +1131,8 @@ function optimizeShiftsAggressive(ast) {
// if (!(x < 5))
// or such. Simplifying these saves space and time.
function simplifyNotCompsDirect(node) {
- if (node[0] == 'unary-prefix' && node[1] == '!') {
- if (node[2][0] == 'binary') {
+ if (node[0] === 'unary-prefix' && node[1] === '!') {
+ if (node[2][0] === 'binary') {
switch(node[2][1]) {
case '<': return ['binary', '>=', node[2][2], node[2][3]];
case '>': return ['binary', '<=', node[2][2], node[2][3]];
@@ -1112,7 +1143,7 @@ function simplifyNotCompsDirect(node) {
case '===': return ['binary', '!==', node[2][2], node[2][3]];
case '!==': return ['binary', '===', node[2][2], node[2][3]];
}
- } else if (node[2][0] == 'unary-prefix' && node[2][1] == '!') {
+ } else if (node[2][0] === 'unary-prefix' && node[2][1] === '!') {
return node[2][2];
}
}
@@ -1135,8 +1166,8 @@ var NO_SIDE_EFFECTS = set('num', 'name');
function hasSideEffects(node) { // this is 99% incomplete!
if (node[0] in NO_SIDE_EFFECTS) return false;
- if (node[0] == 'unary-prefix') return hasSideEffects(node[2]);
- if (node[0] == 'binary') return hasSideEffects(node[2]) || hasSideEffects(node[3]);
+ if (node[0] === 'unary-prefix') return hasSideEffects(node[2]);
+ if (node[0] === 'binary') return hasSideEffects(node[2]) || hasSideEffects(node[3]);
return true;
}
@@ -1145,8 +1176,8 @@ function hasSideEffects(node) { // this is 99% incomplete!
function vacuum(ast) {
function isEmpty(node) {
if (!node) return true;
- if (node[0] == 'toplevel' && (!node[1] || node[1].length == 0)) return true;
- if (node[0] == 'block' && (!node[1] || (typeof node[1] != 'object') || node[1].length == 0 || (node[1].length == 1 && isEmpty(node[1])))) return true;
+ if (node[0] === 'toplevel' && (!node[1] || node[1].length === 0)) return true;
+ 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, si) {
@@ -1156,7 +1187,7 @@ function vacuum(ast) {
var i = 0;
while (i < statements.length) {
var subNode = statements[i];
- if (subNode[0] == 'block') {
+ if (subNode[0] === 'block') {
statements.splice.apply(statements, [i, 1].concat(subNode[1] || []));
changed = true;
} else {
@@ -1176,20 +1207,20 @@ function vacuum(ast) {
var ret;
switch(node[0]) {
case 'block': {
- if (node[1] && node[1].length == 1 && node[1][0][0] == 'block') {
+ if (node[1] && node[1].length === 1 && node[1][0][0] === 'block') {
return node[1][0];
- } else if (typeof node[1] == 'object') {
+ } else if (typeof node[1] === 'object') {
ret = simplifyList(node, 1);
if (ret) return ret;
}
} break;
case 'stat': {
- if (node[1][0] == 'block') {
+ if (node[1][0] === 'block') {
return node[1];
}
} break;
case 'defun': {
- if (node[3].length == 1 && node[3][0][0] == 'block') {
+ if (node[3].length === 1 && node[3][0][0] === 'block') {
node[3] = node[3][0][1];
return node;
} else {
@@ -1198,19 +1229,19 @@ function vacuum(ast) {
}
} break;
case 'do': {
- if (node[1][0] == 'num' && node[2][0] == 'toplevel' && (!node[2][1] || node[2][1].length == 0)) {
+ if (node[1][0] === 'num' && node[2][0] === 'toplevel' && (!node[2][1] || node[2][1].length === 0)) {
return emptyNode();
} else if (isEmpty(node[2]) && !hasSideEffects(node[1])) {
return emptyNode();
}
} break;
case 'label': {
- if (node[2][0] == 'toplevel' && (!node[2][1] || node[2][1].length == 0)) {
+ if (node[2][0] === 'toplevel' && (!node[2][1] || node[2][1].length === 0)) {
return emptyNode();
}
} break;
case 'if': {
- var empty2 = isEmpty(node[2]), empty3 = isEmpty(node[3]), has3 = node.length == 4;
+ var empty2 = isEmpty(node[2]), empty3 = isEmpty(node[3]), has3 = node.length === 4;
if (!empty2 && empty3 && has3) { // empty else clauses
return node.slice(0, 3);
} else if (empty2 && !empty3) { // empty if blocks
@@ -1232,9 +1263,9 @@ function vacuum(ast) {
}
function getStatements(node) {
- if (node[0] == 'defun') {
+ if (node[0] === 'defun') {
return node[3];
- } else if (node[0] == 'block') {
+ } else if (node[0] === 'block') {
return node[1];
} else {
return null;
@@ -1242,9 +1273,9 @@ function getStatements(node) {
}
// Multiple blocks from the relooper are, in general, implemented by
-// if (label == x) { } else if ..
+// if (label === x) { } else if ..
// and branching into them by
-// if (condition) { label == x } else ..
+// if (condition) { label === x } else ..
// We can hoist the multiple block into the condition, thus removing code and one 'if' check
function hoistMultiples(ast) {
traverseGeneratedFunctions(ast, function(node) {
@@ -1261,10 +1292,10 @@ function hoistMultiples(ast) {
var postInner = post;
var shellLabel = false, shellDo = false;
while (true) {
- if (postInner[0] == 'label') {
+ if (postInner[0] === 'label') {
shellLabel = postInner[1];
postInner = postInner[2];
- } else if (postInner[0] == 'do') {
+ } else if (postInner[0] === 'do') {
shellDo = postInner[1];
postInner = postInner[2][1][0];
} else {
@@ -1273,19 +1304,19 @@ function hoistMultiples(ast) {
}
if (postInner[0] != 'if') continue;
// Look into this if, and its elseifs
- while (postInner && postInner[0] == 'if') {
+ while (postInner && 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');
+ 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');
+ 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) {
+ 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. We can also throw away the label setting as its goal has already been achieved
found = true;
modifiedI = true;
@@ -1315,16 +1346,16 @@ function hoistMultiples(ast) {
// 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 (node[0] === 'if') {
var replaced;
if (replaced = tryEliminate(node[2])) node[2] = replaced;
if (node[3] && (replaced = tryEliminate(node[3]))) node[3] = replaced;
} else {
- if (node[0] == 'block' && node[1] && node[1].length > 0) {
+ if (node[0] === 'block' && node[1] && node[1].length > 0) {
var subNode = node[1][node[1].length-1];
- if (subNode[0] == 'stat' && subNode[1][0] == 'assign' && subNode[1][2][0] == 'name' &&
- subNode[1][2][1] == 'label' && subNode[1][3][0] == 'num') {
- if (node[1].length == 1) {
+ if (subNode[0] === 'stat' && subNode[1][0] === 'assign' && subNode[1][2][0] === 'name' &&
+ subNode[1][2][1] === 'label' && subNode[1][3][0] === 'num') {
+ if (node[1].length === 1) {
return emptyNode();
} else {
node[1].splice(node[1].length-1, 1);
@@ -1336,9 +1367,9 @@ function hoistMultiples(ast) {
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];
+ 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(node);
@@ -1348,7 +1379,7 @@ function hoistMultiples(ast) {
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') {
+ if (curr[0] === 'if' && next[0] != 'if' && next[0] != 'label' && next[0] != 'do' && next[0] != 'while') {
tryEliminate(curr);
}
}
@@ -1366,7 +1397,7 @@ function hoistMultiples(ast) {
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') {
+ 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)) {
@@ -1394,7 +1425,7 @@ function loopOptimizer(ast) {
var neededDos = [];
// Find unneeded labels
traverseGenerated(ast, function(node, type, stack) {
- if (type == 'label' && node[2][0] in LOOP) {
+ if (type === 'label' && node[2][0] in LOOP) {
// this is a labelled loop. we don't know if it's needed yet. Mark its label for removal for now now.
stack.push(node);
node[1] = '+' + node[1];
@@ -1410,12 +1441,12 @@ function loopOptimizer(ast) {
assert(lastLoop, 'Cannot break/continue without a Label');
while (i >= 0 && !lastLabel) {
if (stack[i][0] in LOOP) break; // another loop in the middle - no label for lastLoop
- if (stack[i][0] == 'label') lastLabel = stack[i];
+ if (stack[i][0] === 'label') lastLabel = stack[i];
i--;
}
var ident = node[1]; // there may not be a label ident if this is a simple break; or continue;
var plus = '+' + ident;
- if (lastLabel && ident && (ident == lastLabel[1] || plus == lastLabel[1])) {
+ if (lastLabel && ident && (ident === lastLabel[1] || plus === lastLabel[1])) {
// If this is a 'do' loop, this break means we actually need it.
neededDos.push(lastLoop);
// We don't need the control flow command to have a label - it's referring to the current loop
@@ -1428,7 +1459,7 @@ function loopOptimizer(ast) {
// Find the label node that needs to stay alive
stack.forEach(function(label) {
if (!label) return;
- if (label[1] == plus) label[1] = label[1].substr(1); // Remove '+', marking it as needed
+ if (label[1] === plus) label[1] = label[1].substr(1); // Remove '+', marking it as needed
});
}
}
@@ -1438,12 +1469,12 @@ function loopOptimizer(ast) {
var more = false;
// Remove unneeded labels
traverseGenerated(ast, function(node, type) {
- if (type == 'label' && node[1][0] == '+') {
+ if (type === 'label' && node[1][0] === '+') {
more = true;
var ident = node[1].substr(1);
// Remove label from loop flow commands
traverse(node[2], function(node2, type) {
- if (type in LOOP_FLOW && node2[1] == ident) {
+ if (type in LOOP_FLOW && node2[1] === ident) {
return [node2[0]];
}
});
@@ -1453,13 +1484,13 @@ function loopOptimizer(ast) {
// Remove unneeded one-time loops. We need such loops if (1) they have a label, or (2) they have a direct break so they are in neededDos.
// First, add all labeled loops of this nature to neededDos
traverseGenerated(ast, function(node, type) {
- if (type == 'label' && node[2][0] == 'do') {
+ if (type === 'label' && node[2][0] === 'do') {
neededDos.push(node[2]);
}
});
// Remove unneeded dos, we know who they are now
traverseGenerated(ast, function(node, type) {
- if (type == 'do' && neededDos.indexOf(node) < 0) {
+ if (type === 'do' && neededDos.indexOf(node) < 0) {
assert(jsonCompare(node[1], ['num', 0]), 'Trying to remove a one-time do loop that is not one of our generated ones.;');
more = true;
return node[2];
@@ -1485,7 +1516,7 @@ function loopOptimizer(ast) {
function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i.e., the same assigns, but without a var definition
ret = ret || [];
ret[0] = 'stat';
- if (vars.length == 1) {
+ if (vars.length === 1) {
ret[1] = ['assign', true, ['name', vars[0][0]], vars[0][1]];
} else {
ret[1] = [];
@@ -1505,18 +1536,28 @@ function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i
var ASM_INT = 0;
var ASM_DOUBLE = 1;
-function detectAsmCoercion(node) {
+function detectAsmCoercion(node, asmInfo) {
// for params, +x vs x|0, for vars, 0.0 vs 0
- if (node[0] == 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE;
- return node[0] == 'unary-prefix' ? ASM_DOUBLE : ASM_INT;
+ if (node[0] === 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE;
+ if (node[0] === 'unary-prefix') return ASM_DOUBLE;
+ if (asmInfo && node[0] == 'name') {
+ if (node[1] in asmInfo.vars) return asmInfo.vars[node[1]];
+ if (node[1] in asmInfo.params) return asmInfo.params[node[1]];
+ }
+ return ASM_INT;
}
function makeAsmParamCoercion(param, type) {
- return type == ASM_INT ? ['binary', '|', ['name', param], ['num', 0]] : ['unary-prefix', '+', ['name', param]];
+ return type === ASM_INT ? ['binary', '|', ['name', param], ['num', 0]] : ['unary-prefix', '+', ['name', param]];
}
function makeAsmVarDef(v, type) {
- return [v, type == ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]];
+ return [v, type === ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]];
+}
+
+function getAsmType(asmInfo, name) {
+ if (name in asmInfo.vars) return asmInfo.vars[name];
+ return asmInfo.params[name];
}
function normalizeAsm(func) {
@@ -1548,7 +1589,7 @@ function normalizeAsm(func) {
var name = v[0];
var value = v[1];
if (!(name in data.vars)) {
- assert(value[0] == 'num' || (value[0] == 'unary-prefix' && value[2][0] == 'num')); // must be valid coercion no-op
+ assert(value[0] === 'num' || (value[0] === 'unary-prefix' && value[2][0] === 'num')); // must be valid coercion no-op
data.vars[name] = detectAsmCoercion(value);
v.length = 1; // make an un-assigning var
} else {
@@ -1560,7 +1601,7 @@ function normalizeAsm(func) {
// finally, look for other var definitions and collect them
while (i < stats.length) {
traverse(stats[i], function(node, type) {
- if (type == 'var') {
+ if (type === 'var') {
for (var j = 0; j < node[1].length; j++) {
var v = node[1][j];
var name = v[0];
@@ -1575,8 +1616,8 @@ function normalizeAsm(func) {
}
}
unVarify(node[1], node);
- } else if (type == 'dot') {
- if (node[1][0] == 'name' && node[1][1] == 'Math') {
+ } else if (type === 'dot') {
+ if (node[1][0] === 'name' && node[1][1] === 'Math') {
// transform Math.max to Math_max; we forward in the latter version
node[0] = 'name';
node[1] = 'Math_' + node[2];
@@ -1594,7 +1635,7 @@ function denormalizeAsm(func, data) {
var stats = func[3];
// Remove var definitions, if any
for (var i = 0; i < stats.length; i++) {
- if (stats[i][0] == 'var') {
+ if (stats[i][0] === 'var') {
stats[i] = emptyNode();
} else {
if (!isEmptyNode(stats[i])) break;
@@ -1663,7 +1704,7 @@ function registerize(ast) {
var localVars = {};
var hasSwitch = false; // we cannot optimize variables if there is a switch, unless in asm mode
traverse(fun, function(node, type) {
- if (type == 'var') {
+ 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) {
@@ -1671,7 +1712,7 @@ function registerize(ast) {
} else {
return emptyNode();
}
- } else if (type == 'switch') {
+ } else if (type === 'switch') {
hasSwitch = true;
}
});
@@ -1682,7 +1723,7 @@ function registerize(ast) {
var nextLocal = 0;
// Minify globals using the mapping we were given
traverse(fun, function(node, type) {
- if (type == 'name') {
+ if (type === 'name') {
var name = node[1];
var minified = extraInfo.globals[name];
if (minified) {
@@ -1702,13 +1743,13 @@ function registerize(ast) {
asmData.params[newName] = asmData.params[minified];
delete asmData.params[minified];
traverse(fun, function(node, type) {
- if (type == 'name' && node[1] == minified) {
+ if (type === 'name' && node[1] === minified) {
node[1] = newName;
}
});
if (fun[2]) {
for (var i = 0; i < fun[2].length; i++) {
- if (fun[2][i] == minified) fun[2][i] = newName;
+ if (fun[2][i] === minified) fun[2][i] = newName;
}
}
}
@@ -1761,15 +1802,15 @@ function registerize(ast) {
level--;
}
traverse(fun, function possibilifier(node, type) {
- if (type == 'name') {
+ 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') {
+ } 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
@@ -1877,11 +1918,11 @@ function registerize(ast) {
}
varUses[name]--;
assert(varUses[name] >= 0);
- if (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])) {
+ if (loops === 0 || (optimizables[name] && !optimizableLoops[name])) {
// free register
freeRegs.push(reg);
} else {
@@ -1894,7 +1935,7 @@ function registerize(ast) {
return true;
}
traverse(fun, function(node, type) { // XXX we rely on traversal order being the same as execution order here
- if (type == 'name') {
+ if (type === 'name') {
var name = node[1];
if (decUse(name)) {
node[1] = fullNames[varRegs[name]];
@@ -2001,10 +2042,10 @@ var IGNORABLE_ELIMINATOR_SCAN_NODES = set('num', 'toplevel', 'string', 'break',
var ABORTING_ELIMINATOR_SCAN_NODES = set('new', 'object', 'function', 'defun', 'for', 'while', 'array', 'throw'); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body)
function isTempDoublePtrAccess(node) { // these are used in bitcasts; they are not really affecting memory, and should cause no invalidation
- assert(node[0] == 'sub');
- return (node[2][0] == 'name' && node[2][1] == 'tempDoublePtr') ||
- (node[2][0] == 'binary' && ((node[2][2][0] == 'name' && node[2][2][1] == 'tempDoublePtr') ||
- (node[2][3][0] == 'name' && node[2][3][1] == 'tempDoublePtr')));
+ assert(node[0] === 'sub');
+ return (node[2][0] === 'name' && node[2][1] === 'tempDoublePtr') ||
+ (node[2][0] === 'binary' && ((node[2][2][0] === 'name' && node[2][2][1] === 'tempDoublePtr') ||
+ (node[2][3][0] === 'name' && node[2][3][1] === 'tempDoublePtr')));
}
function eliminate(ast, memSafe) {
@@ -2049,9 +2090,9 @@ function eliminate(ast, memSafe) {
var name = node[1];
if (!uses[name]) uses[name] = 0;
uses[name]++;
- } else if (type == 'assign') {
+ } else if (type === 'assign') {
var target = node[2];
- if (target[0] == 'name') {
+ if (target[0] === 'name') {
var name = target[1];
if (!(name in definitions)) definitions[name] = 0;
definitions[name]++;
@@ -2063,7 +2104,7 @@ function eliminate(ast, memSafe) {
namings[name]++; // offset it here, this tracks the total times we are named
}
}
- } else if (type == 'switch') {
+ } else if (type === 'switch') {
hasSwitch = true;
}
});
@@ -2076,7 +2117,7 @@ function eliminate(ast, memSafe) {
if (hasSwitch && !asm) return;
var potentials = {}; // local variables with 1 definition and 1 use
- var sideEffectFree = {}; // whether a local variable has no side effects in its definition
+ var sideEffectFree = {}; // whether a local variable has no side effects in its definition. Only relevant when there are no uses
function unprocessVariable(name) {
if (name in potentials) delete potentials[name];
@@ -2085,9 +2126,9 @@ function eliminate(ast, memSafe) {
if (name in varsToTryToRemove) delete varsToTryToRemove[name];
}
function processVariable(name) {
- if (definitions[name] == 1 && uses[name] == 1) {
+ if (definitions[name] === 1 && uses[name] === 1) {
potentials[name] = 1;
- } else if (uses[name] == 0 && (!definitions[name] || definitions[name] <= 1)) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow)
+ } else if (uses[name] === 0 && (!definitions[name] || definitions[name] <= 1)) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow) (no definition means it is a function parameter, or a local with just |var x;| but no defining assignment)
var hasSideEffects = false;
var value = values[name];
if (value) {
@@ -2095,8 +2136,8 @@ function eliminate(ast, memSafe) {
// First, pattern-match
// (HEAP32[((tempDoublePtr)>>2)]=((HEAP32[(($_sroa_0_0__idx1)>>2)])|0),HEAP32[(((tempDoublePtr)+(4))>>2)]=((HEAP32[((($_sroa_0_0__idx1)+(4))>>2)])|0),(+(HEAPF64[(tempDoublePtr)>>3])))
// which has no side effects and is the special form of converting double to i64.
- if (!(value[0] == 'seq' && value[1][0] == 'assign' && value[1][2][0] == 'sub' && value[1][2][2][0] == 'binary' && value[1][2][2][1] == '>>' &&
- value[1][2][2][2][0] == 'name' && value[1][2][2][2][1] == 'tempDoublePtr')) {
+ if (!(value[0] === 'seq' && value[1][0] === 'assign' && value[1][2][0] === 'sub' && value[1][2][2][0] === 'binary' && value[1][2][2][1] === '>>' &&
+ value[1][2][2][2][0] === 'name' && value[1][2][2][2][1] === 'tempDoublePtr')) {
// If not that, then traverse and scan normally.
traverse(value, function(node, type) {
if (!(type in NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS)) {
@@ -2114,7 +2155,7 @@ function eliminate(ast, memSafe) {
// appearing in it, and possibly eliminate again
if (value) {
traverse(value, function(node, type) {
- if (type == 'name') {
+ if (type === 'name') {
var name = node[1];
node[1] = ''; // we can remove this - it will never be shown, and should not be left to confuse us as we traverse
if (name in locals) {
@@ -2152,7 +2193,7 @@ function eliminate(ast, memSafe) {
var usesGlobals = false, usesMemory = false, deps = {}, doesCall = false;
var ignoreName = false; // one-time ignorings of names, as first op in sub and call
traverse(value, function(node, type) {
- if (type == 'name') {
+ if (type === 'name') {
if (!ignoreName) {
var name = node[1];
if (!(name in locals)) {
@@ -2164,10 +2205,10 @@ function eliminate(ast, memSafe) {
} else {
ignoreName = false;
}
- } else if (type == 'sub') {
+ } else if (type === 'sub') {
usesMemory = true;
ignoreName = true;
- } else if (type == 'call') {
+ } else if (type === 'call') {
usesGlobals = true;
usesMemory = true;
doesCall = true;
@@ -2255,10 +2296,10 @@ function eliminate(ast, memSafe) {
//nesting++; // printErr-related
//printErr(spaces(2*(nesting+1)) + 'trav: ' + JSON.stringify(node).substr(0, 50) + ' : ' + keys(tracked) + ' : ' + [allowTracking, ignoreSub, ignoreName]);
var type = node[0];
- if (type == 'assign') {
+ if (type === 'assign') {
var target = node[2];
var value = node[3];
- var nameTarget = target[0] == 'name';
+ var nameTarget = target[0] === 'name';
traverseInOrder(target, true, nameTarget); // evaluate left
traverseInOrder(value); // evaluate right
// do the actual assignment
@@ -2275,7 +2316,7 @@ function eliminate(ast, memSafe) {
}
// if we can track this name (that we assign into), and it has 0 uses and we want to remove its 'var'
// definition - then remove it right now, there is no later chance
- if (allowTracking && (name in varsToRemove) && uses[name] == 0) {
+ if (allowTracking && (name in varsToRemove) && uses[name] === 0) {
track(name, node[3], node);
doEliminate(name, node);
}
@@ -2290,13 +2331,13 @@ function eliminate(ast, memSafe) {
} else {
if (allowTracking) track(name, node[3], node);
}
- } else if (target[0] == 'sub') {
+ } else if (target[0] === 'sub') {
if (!isTempDoublePtrAccess(target) && !memoryInvalidated) {
invalidateMemory();
memoryInvalidated = true;
}
}
- } else if (type == 'sub') {
+ } else if (type === 'sub') {
traverseInOrder(node[1], false, !memSafe); // evaluate inner
traverseInOrder(node[2]); // evaluate outer
// ignoreSub means we are a write (happening later), not a read
@@ -2307,7 +2348,7 @@ function eliminate(ast, memSafe) {
callsInvalidated = true;
}
}
- } else if (type == 'var') {
+ } else if (type === 'var') {
var vars = node[1];
for (var i = 0; i < vars.length; i++) {
var name = vars[i][0];
@@ -2319,7 +2360,7 @@ function eliminate(ast, memSafe) {
} else {
invalidateByDep(name);
}
- if (vars.length == 1 && name in varsToTryToRemove && value) {
+ if (vars.length === 1 && name in varsToTryToRemove && value) {
// replace it in-place
value = ['stat', value];
node.length = value.length;
@@ -2330,7 +2371,7 @@ function eliminate(ast, memSafe) {
}
}
}
- } else if (type == 'binary') {
+ } else if (type === 'binary') {
var flipped = false;
if (node[1] in ASSOCIATIVE_BINARIES && !(node[2][0] in NAME_OR_NUM) && node[3][0] in NAME_OR_NUM) { // TODO recurse here?
// associatives like + and * can be reordered in the simple case of one of the sides being a name, since we assume they are all just numbers
@@ -2346,7 +2387,7 @@ function eliminate(ast, memSafe) {
node[2] = node[3];
node[3] = temp;
}
- } else if (type == 'name') {
+ } else if (type === 'name') {
if (!ignoreName) { // ignoreName means we are the name of something like a call or a sub - irrelevant for us
var name = node[1];
if (name in tracked) {
@@ -2356,10 +2397,10 @@ function eliminate(ast, memSafe) {
callsInvalidated = true;
}
}
- } else if (type == 'unary-prefix' || type == 'unary-postfix') {
+ } else if (type === 'unary-prefix' || type === 'unary-postfix') {
traverseInOrder(node[2]);
} else if (type in IGNORABLE_ELIMINATOR_SCAN_NODES) {
- } else if (type == 'call') {
+ } else if (type === 'call') {
traverseInOrder(node[1], false, true);
var args = node[2];
for (var i = 0; i < args.length; i++) {
@@ -2374,7 +2415,7 @@ function eliminate(ast, memSafe) {
invalidateMemory();
memoryInvalidated = true;
}
- } else if (type == 'if') {
+ } else if (type === 'if') {
if (allowTracking) {
traverseInOrder(node[1]); // can eliminate into condition, but nowhere else
if (!callsInvalidated) { // invalidate calls, since we cannot eliminate them into an if that may not execute!
@@ -2390,38 +2431,38 @@ function eliminate(ast, memSafe) {
} else {
tracked = {};
}
- } else if (type == 'block') {
+ } else if (type === 'block') {
var stats = node[1];
if (stats) {
for (var i = 0; i < stats.length; i++) {
traverseInOrder(stats[i]);
}
}
- } else if (type == 'stat') {
+ } else if (type === 'stat') {
traverseInOrder(node[1]);
- } else if (type == 'label') {
+ } else if (type === 'label') {
traverseInOrder(node[2]);
- } else if (type == 'seq') {
+ } else if (type === 'seq') {
traverseInOrder(node[1]);
traverseInOrder(node[2]);
- } else if (type == 'do') {
- if (node[1][0] == 'num' && node[1][1] == 0) { // one-time loop
+ } else if (type === 'do') {
+ if (node[1][0] === 'num' && node[1][1] === 0) { // one-time loop
traverseInOrder(node[2]);
} else {
tracked = {};
}
- } else if (type == 'return') {
+ } else if (type === 'return') {
if (node[1]) traverseInOrder(node[1]);
- } else if (type == 'conditional') {
+ } else if (type === 'conditional') {
traverseInOrder(node[1]);
traverseInOrder(node[2]);
traverseInOrder(node[3]);
- } else if (type == 'switch') {
+ } else if (type === 'switch') {
traverseInOrder(node[1]);
var cases = node[2];
for (var i = 0; i < cases.length; i++) {
var c = cases[i];
- assert(c[0] === null || c[0][0] == 'num' || (c[0][0] == 'unary-prefix' && c[0][2][0] == 'num'));
+ assert(c[0] === null || c[0][0] === 'num' || (c[0][0] === 'unary-prefix' && c[0][2][0] === 'num'));
var stats = c[1];
for (var j = 0; j < stats.length; j++) {
traverseInOrder(stats[j]);
@@ -2440,7 +2481,7 @@ function eliminate(ast, memSafe) {
}
//var eliminationLimit = 0; // used to debugging purposes
function doEliminate(name, node) {
- //if (eliminationLimit == 0) return;
+ //if (eliminationLimit === 0) return;
//eliminationLimit--;
//printErr('elim!!!!! ' + name);
// yes, eliminate!
@@ -2449,9 +2490,9 @@ function eliminate(ast, memSafe) {
delete tracked[name];
var defNode = info.defNode;
if (!sideEffectFree[name]) {
- if (defNode[0] == 'var') {
+ if (defNode[0] === 'var') {
defNode[1].forEach(function(pair) {
- if (pair[0] == name) {
+ if (pair[0] === name) {
value = pair[1];
}
});
@@ -2469,7 +2510,7 @@ function eliminate(ast, memSafe) {
node[i] = value[i];
}
} else {
- // empty it out in-place
+ // This has no side effects and no uses, empty it out in-place
node.length = 0;
node[0] = 'toplevel';
node[1] = [];
@@ -2477,7 +2518,7 @@ function eliminate(ast, memSafe) {
}
traverse(func, function(block) {
// Look for statements, including while-switch pattern
- var stats = getStatements(block) || (block[0] == 'while' && block[2][0] == 'switch' ? [block[2]] : stats);
+ var stats = getStatements(block) || (block[0] === 'while' && block[2][0] === 'switch' ? [block[2]] : stats);
if (!stats) return;
//printErr('Stats: ' + JSON.stringify(stats).substr(0,100));
tracked = {};
@@ -2486,9 +2527,11 @@ function eliminate(ast, memSafe) {
var node = stats[i];
//printErr('StatBlock[' + i + '] => ' + JSON.stringify(node).substr(0,100));
var type = node[0];
- if (type == 'stat') {
+ if (type === 'stat') {
node = node[1];
type = node[0];
+ } else if (type == 'return' && i < stats.length-1) {
+ stats.length = i+1; // remove any code after a return
}
// Check for things that affect elimination
if (type in ELIMINATION_SAFE_NODES) {
@@ -2507,7 +2550,7 @@ function eliminate(ast, memSafe) {
// pre
if (type === 'var') {
node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] });
- if (node[1].length == 0) {
+ if (node[1].length === 0) {
// wipe out an empty |var;|
node[0] = 'toplevel';
node[1] = [];
@@ -2515,7 +2558,7 @@ function eliminate(ast, memSafe) {
}
}, function(node, type) {
// post
- if (type == 'name') {
+ if (type === 'name') {
var name = node[1];
if (name in helperReplacements) {
node[1] = helperReplacements[name];
@@ -2527,30 +2570,30 @@ function eliminate(ast, memSafe) {
} else {
seenUses[name]++;
}
- } else if (type == 'while') {
+ } else if (type === 'while') {
// try to remove loop helper variables specifically
var stats = node[2][1];
var last = stats[stats.length-1];
- if (last && last[0] == 'if' && last[2][0] == 'block' && last[3] && last[3][0] == 'block') {
+ if (last && last[0] === 'if' && last[2][0] === 'block' && last[3] && last[3][0] === 'block') {
var ifTrue = last[2];
var ifFalse = last[3];
var flip = false;
- if (ifFalse[1][0] && ifFalse[1][0][0] == 'break') { // canonicalize break in the if
+ if (ifFalse[1][0] && ifFalse[1][0][0] === 'break') { // canonicalize break in the if
var temp = ifFalse;
ifFalse = ifTrue;
ifTrue = temp;
flip = true;
}
- if (ifTrue[1][0] && ifTrue[1][0][0] == 'break') {
+ if (ifTrue[1][0] && ifTrue[1][0][0] === 'break') {
var assigns = ifFalse[1];
var loopers = [], helpers = [];
for (var i = 0; i < assigns.length; i++) {
- if (assigns[i][0] == 'stat' && assigns[i][1][0] == 'assign') {
+ if (assigns[i][0] === 'stat' && assigns[i][1][0] === 'assign') {
var assign = assigns[i][1];
- if (assign[1] === true && assign[2][0] == 'name' && assign[3][0] == 'name') {
+ if (assign[1] === true && assign[2][0] === 'name' && assign[3][0] === 'name') {
var looper = assign[2][1];
var helper = assign[3][1];
- if (definitions[helper] == 1 && seenUses[looper] == namings[looper] &&
+ if (definitions[helper] === 1 && seenUses[looper] === namings[looper] &&
!helperReplacements[helper] && !helperReplacements[looper]) {
loopers.push(looper);
helpers.push(helper);
@@ -2566,11 +2609,11 @@ function eliminate(ast, memSafe) {
var found = -1;
for (var i = stats.length-2; i >= 0; i--) {
var curr = stats[i];
- if (curr[0] == 'stat' && curr[1][0] == 'assign') {
+ if (curr[0] === 'stat' && curr[1][0] === 'assign') {
var currAssign = curr[1];
- if (currAssign[1] === true && currAssign[2][0] == 'name') {
+ if (currAssign[1] === true && currAssign[2][0] === 'name') {
var to = currAssign[2][1];
- if (to == helper) {
+ if (to === helper) {
found = i;
break;
}
@@ -2582,7 +2625,7 @@ function eliminate(ast, memSafe) {
for (var i = found+1; i < stats.length && !looperUsed; i++) {
var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition
traverse(curr, function(node, type) {
- if (type == 'name' && node[1] == looper) {
+ if (type === 'name' && node[1] === looper) {
looperUsed = true;
return true;
}
@@ -2592,7 +2635,7 @@ function eliminate(ast, memSafe) {
}
for (var l = 0; l < helpers.length; l++) {
for (var k = 0; k < helpers.length; k++) {
- if (l != k && helpers[l] == helpers[k]) return; // it is complicated to handle a shared helper, abort
+ if (l != k && helpers[l] === helpers[k]) return; // it is complicated to handle a shared helper, abort
}
}
// hurrah! this is safe to do
@@ -2602,7 +2645,7 @@ function eliminate(ast, memSafe) {
var helper = helpers[l];
varsToRemove[helper] = 2;
traverse(node, function(node, type) { // replace all appearances of helper with looper
- if (type == 'name' && node[1] == helper) node[1] = looper;
+ if (type === 'name' && node[1] === helper) node[1] = looper;
});
helperReplacements[helper] = looper; // replace all future appearances of helper with looper
helperReplacements[looper] = looper; // avoid any further attempts to optimize looper in this manner (seenUses is wrong anyhow, too)
@@ -2620,7 +2663,7 @@ function eliminate(ast, memSafe) {
if (asm) {
for (var v in varsToRemove) {
- if (varsToRemove[v] == 2) delete asmData.vars[v];
+ if (varsToRemove[v] === 2) delete asmData.vars[v];
}
denormalizeAsm(func, asmData);
}
@@ -2634,14 +2677,14 @@ function eliminate(ast, memSafe) {
this.run = function() {
traverse(this.node, function(node, type) {
- if (type === 'binary' && node[1] == '+') {
+ if (type === 'binary' && node[1] === '+') {
var names = [];
var num = 0;
var has_num = false;
var fail = false;
traverse(node, function(subNode, subType) {
if (subType === 'binary') {
- if (subNode[1] != '+') {
+ if (subNode[1] !== '+') {
fail = true;
return false;
}
@@ -2682,7 +2725,7 @@ function minifyGlobals(ast) {
var first = true; // do not minify initial 'var asm ='
// find the globals
traverse(ast, function(node, type) {
- if (type == 'var') {
+ if (type === 'var') {
if (first) {
first = false;
return;
@@ -2703,7 +2746,7 @@ function minifyGlobals(ast) {
}
// apply minification
traverse(ast, function(node, type) {
- if (type == 'name') {
+ if (type === 'name') {
var name = node[1];
if (name in minified) {
node[1] = minified[name];
@@ -2724,8 +2767,9 @@ function relocate(ast) {
assert(asm); // we also assume we are normalized
var replacements = extraInfo.replacements;
- var fBase = extraInfo.fBase;
+ var fBases = extraInfo.fBases;
var hBase = extraInfo.hBase;
+ var m;
traverse(ast, function(node, type) {
switch(type) {
@@ -2737,13 +2781,14 @@ function relocate(ast) {
case 'binary': {
if (node[1] == '+' && node[2][0] == 'name') {
var base = null;
- if (node[2][1] == 'F_BASE') {
- base = fBase;
- } else if (node[2][1] == 'H_BASE') {
+ if (node[2][1] == 'H_BASE') {
base = hBase;
+ } else if (m = /^F_BASE_(\w+)$/.exec(node[2][1])) {
+ base = fBases[m[1]] || 0; // 0 if the parent has no function table for this, but the child does. so no relocation needed
}
- if (base) {
+ if (base !== null) {
var other = node[3];
+ if (base === 0) return other;
if (other[0] == 'num') {
other[1] += base;
return other;
@@ -2766,14 +2811,502 @@ function relocate(ast) {
});
}
+// Break up very large functions
+
+var NODES_WITHOUT_ELIMINATION_SENSITIVITY = set('name', 'num', 'binary', 'unary-prefix');
+var FAST_ELIMINATION_BINARIES = setUnion(setUnion(USEFUL_BINARY_OPS, COMPARE_OPS), set('+'));
+
+function outline(ast) {
+ function measureSize(ast) {
+ var size = 0;
+ traverse(ast, function() {
+ size++;
+ });
+ return size;
+ }
+
+ function aggressiveVariableElimination(func, asmData) {
+ // This removes as many variables as possible. This is often not the best thing because it increases
+ // code size, but it is far preferable to the risk of split functions needing to do more spilling. Specifically,
+ // it finds 'trivial' variables: ones with 1 definition, and that definition is not sensitive to any changes: it
+ // only depends on constants and local variables that are themselves trivial. We can unquestionably eliminate
+ // such variables in a trivial manner.
+
+ var assignments = {};
+ var appearances = {};
+ var defs = {};
+ var considered = {};
+
+ traverse(func, function(node, type) {
+ if (type == 'assign' && node[2][0] == 'name') {
+ var name = node[2][1];
+ if (name in asmData.vars) {
+ assignments[name] = (assignments[name] || 0) + 1;
+ appearances[name] = (appearances[name] || 0) - 1; // this appearance is a definition, offset the counting later
+ defs[name] = node;
+ } else {
+ if (name in asmData.params) {
+ considered[name] = true; // this parameter is not ssa, it must be in a hand-optimized function, so it is not trivial
+ }
+ }
+ } else if (type == 'name') {
+ var name = node[1];
+ if (name in asmData.vars) {
+ appearances[name] = (appearances[name] || 0) + 1;
+ }
+ }
+ });
+
+ var allTrivials = {}; // key of a trivial var => size of its (expanded) value, at least 1
+
+ // three levels of variables:
+ // 1. trivial: 1 def (or less), uses nothing sensitive, can be eliminated
+ // 2. safe: 1 def (or less), can be used in a trivial, but cannot itself be eliminated
+ // 3. sensitive: uses a global or memory or something else that prevents trivial elimination.
+
+ function assessTriviality(name) {
+ // only care about vars with 0-1 assignments of (0 for parameters), and can ignore label (which is not explicitly initialized, but cannot be eliminated ever anyhow)
+ if (assignments[name] > 1 || (!(name in asmData.vars) && !(name in asmData.params)) || name == 'label') return false;
+ if (considered[name]) return allTrivials[name];
+ considered[name] = true;
+ var sensitive = false;
+ var size = 0, originalSize = 0;
+ var def = defs[name];
+ if (def) {
+ var value = def[3];
+ originalSize = measureSize(value);
+ if (value) {
+ traverse(value, function recurseValue(node, type) {
+ var one = node[1];
+ if (!(type in NODES_WITHOUT_ELIMINATION_SENSITIVITY)) { // || (type == 'binary' && !(one in FAST_ELIMINATION_BINARIES))) {
+ sensitive = true;
+ return true;
+ }
+ if (type == 'name' && !assessTriviality(one)) {
+ if (assignments[one] > 1 || (!(one in asmData.vars) && !(one in asmData.params))) {
+ sensitive = true; // directly using something sensitive
+ return true;
+ } // otherwise, not trivial, but at least safe.
+ }
+ // if this is a name, it must be a trivial variable (or a safe one) and we know its size
+ size += ((type == 'name') ? allTrivials[one] : 1) || 1;
+ });
+ }
+ }
+ if (!sensitive) {
+ size = size || 1;
+ originalSize = originalSize || 1;
+ var factor = ((appearances[name] - 1) || 0) * (size - originalSize); // If no size change or just one appearance, always ok to trivially eliminate. otherwise, tradeoff
+ if (factor <= 12) {
+ allTrivials[name] = size; // trivial!
+ return true;
+ }
+ }
+ return false;
+ }
+ for (var name in asmData.vars) {
+ assessTriviality(name);
+ }
+ var trivials = {};
+
+ for (var name in allTrivials) { // from now on, ignore parameters
+ if (name in asmData.vars) trivials[name] = true;
+ }
+
+ allTrivials = {};
+
+ var values = {};
+
+ function evaluate(name) {
+ var node = values[name];
+ if (node) return node;
+ values[node] = null; // prevent infinite recursion
+ var def = defs[name];
+ if (def) {
+ node = def[3];
+ if (node[0] == 'name') {
+ var name2 = node[1];
+ if (name2 in trivials) {
+ node = evaluate(name2);
+ }
+ } else {
+ traverse(node, function(node, type) {
+ if (type == 'name') {
+ var name2 = node[1];
+ if (name2 in trivials) {
+ return evaluate(name2);
+ }
+ }
+ });
+ }
+ values[name] = node;
+ }
+ return node;
+ }
+
+ for (var name in trivials) {
+ evaluate(name);
+ }
+
+ for (var name in trivials) {
+ var def = defs[name];
+ if (def) {
+ def.length = 0;
+ def[0] = 'toplevel';
+ def[1] = [];
+ }
+ delete asmData.vars[name];
+ }
+
+ // Perform replacements TODO: save list of uses objects before, replace directly, avoid extra traverse
+ traverse(func, function(node, type) {
+ if (type == 'name') {
+ var name = node[1];
+ if (name in trivials) {
+ var value = values[name];
+ if (!value) throw 'missing value: ' + [func[1], name, values[name]] + ' - faulty reliance on asm zero-init?';
+ return copy(value); // must copy, or else the same object can be used multiple times
+ }
+ }
+ });
+ }
+
+ // Prepares information for spilling of local variables
+ function analyzeFunction(func, asmData) {
+ var stack = []; // list of variables, each gets 8 bytes
+ for (var name in asmData.params) {
+ stack.push(name);
+ }
+ for (var name in asmData.vars) {
+ stack.push(name);
+ }
+ asmData.stackPos = {};
+ for (var i = 0; i < stack.length; i++) {
+ asmData.stackPos[stack[i]] = i*8;
+ }
+ // Reserve an extra two spots: one for control flow var, the other for control flow data
+ asmData.stackSize = (stack.length + 2)*8;
+ asmData.controlStackPos = asmData.stackSize - 16;
+ asmData.controlDataStackPos = asmData.stackSize - 8;
+ asmData.splitCounter = 0;
+ }
+
+ // Analyze uses - reads and writes - of variables in part of the AST of a function
+ function analyzeCode(func, asmData, ast) {
+ var labels = {};
+ var labelCounter = 1; // 0 means no label
+
+ traverse(ast, function(node, type) {
+ if ((type == 'label' || type in LOOP_FLOW) && node[1] && !(node[1] in labels)) {
+ labels[node[1]] = labelCounter++;
+ }
+ });
+
+ var writes = {};
+ var appearances = {};
+ var hasReturn = false, hasBreak = false, hasContinue = false;
+ var breaks = {}; // set of labels we break or continue
+ var continues = {}; // to. '0' is an unlabeled one
+ var breakCapturers = 0;
+ var continueCapturers = 0;
+
+ traverse(ast, function(node, type) {
+ if (type == 'assign' && node[2][0] == 'name') {
+ var name = node[2][1];
+ if (name in asmData.vars || name in asmData.params) {
+ writes[name] = 0;
+ appearances[name] = (appearances[name] || 0) - 1; // this appearance is a definition, offset the counting later
+ }
+ } else if (type == 'name') {
+ var name = node[1];
+ if (name in asmData.vars || name in asmData.params) {
+ appearances[name] = (appearances[name] || 0) + 1;
+ }
+ } else if (type == 'return') {
+ hasReturn = true;
+ } else if (type == 'break') {
+ var label = node[1] || 0;
+ if (!label && breakCapturers > 0) return; // no label, and captured
+ if (label && (label in labels)) return; // label, and defined in this code, so captured
+ breaks[label || 0] = 0;
+ hasBreak = true;
+ } else if (type == 'continue') {
+ var label = node[1] || 0;
+ if (!label && continueCapturers > 0) return; // no label, and captured
+ if (label && (label in labels)) return; // label, and defined in this code, so captured
+ continues[label || 0] = 0;
+ hasContinue = true;
+ } else {
+ if (type in BREAK_CAPTURERS) {
+ breakCapturers++;
+ }
+ if (type in CONTINUE_CAPTURERS) {
+ continueCapturers++;
+ }
+ }
+ }, function(node, type) {
+ if (type in BREAK_CAPTURERS) {
+ breakCapturers--;
+ }
+ if (type in CONTINUE_CAPTURERS) {
+ continueCapturers--;
+ }
+ });
+
+ var reads = {};
+
+ for (var name in appearances) {
+ if (appearances[name] > 0) reads[name] = 0;
+ }
+
+ return { writes: writes, reads: reads, hasReturn: hasReturn, breaks: breaks, continues: continues, labels: labels };
+ }
+
+ function makeAssign(dst, src) {
+ return ['assign', true, dst, src];
+ }
+ function makeStackAccess(type, pos) { // TODO: float64, not 32
+ return ['sub', ['name', type == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', pos]], ['num', '2']]];
+ }
+ function makeIf(cond, then, else_) {
+ var ret = ['if', cond, ['block', then]];
+ if (else_) ret.push(['block', else_]);
+ return ret;
+ }
+ function makeComparison(left, comp, right) {
+ return ['binary', comp, left, right];
+ }
+
+ var CONTROL_BREAK = 1, CONTROL_BREAK_LABEL = 2, CONTROL_CONTINUE = 3, CONTROL_CONTINUE_LABEL = 4, CONTROL_RETURN_VOID = 5, CONTROL_RETURN_INT = 6, CONTROL_RETURN_DOUBLE = 7;
+
+ var sizeToOutline = extraInfo.sizeToOutline;
+ var level = 0;
+
+ function doOutline(func, asmData, stats, start, end) {
+ printErr(' do outline ' + [func[1], level, 'range:', start, end, 'of', stats.length]);
+ var code = stats.slice(start, end+1);
+ var newIdent = func[1] + '$' + (asmData.splitCounter++);
+ // add spills and reads before and after the call to the outlined code, and in the outlined code itself
+ var codeInfo = analyzeCode(func, asmData, code);
+ var reps = [];
+ for (var v in codeInfo.reads) {
+ if (v != 'sp') {
+ reps.push(['stat', ['assign', true, ['sub', ['name', getAsmType(asmData, v) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], ['name', v]]]);
+ code.unshift(['stat', ['assign', true, ['name', v], ['sub', ['name', getAsmType(asmData, v) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]]]]);
+ }
+ }
+ reps.push(['stat', ['call', ['name', newIdent], [['name', 'sp']]]]);
+ for (var v in codeInfo.writes) {
+ reps.push(['stat', ['assign', true, ['name', v], ['sub', ['name', getAsmType(asmData, v) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]]]]);
+ code.push(['stat', ['assign', true, ['sub', ['name', getAsmType(asmData, v) == ASM_INT ? 'HEAP32' : 'HEAPF32'], ['binary', '>>', ['binary', '+', ['name', 'sp'], ['num', asmData.stackPos[v]]], ['num', '2']]], ['name', v]]]);
+ }
+ // Generate new function
+ if (codeInfo.hasReturn || codeInfo.hasBreak || codeInfo.hasContinue) {
+ // we need to capture all control flow using a top-level labeled one-time loop in the outlined function
+ code = [['label', 'OL', ['do', ['num', 0], ['block', code]]]];
+ var breakCapturers = 0;
+ var continueCapturers = 0;
+ traverse(code, function(node, type) {
+ // replace all break/continue/returns with code to break out of the main one-time loop, and set the control data
+ if (type == 'return') {
+ var ret = ['break', 'OL'];
+ if (!node[1]) {
+ ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', CONTROL_RETURN_VOID]), ret];
+ } else {
+ var type = detectAsmCoercion(node[1], asmData);
+ ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', type == ASM_INT ? CONTROL_RETURN_INT : CONTROL_RETURN_DOUBLE]), ret];
+ ret = ['seq', makeAssign(makeStackAccess(type, asmData.controlDataStackPos), node[1]), ret];
+ }
+ return ret;
+ } else if (type == 'break') {
+ var label = node[1] || 0;
+ if (label == 'OL') return; // this was just added before us, it is new replacement code
+ if (!label && breakCapturers > 0) return; // no label, and captured
+ if (label && (label in codeInfo.labels)) return; // label, and defined in this code, so captured
+ var ret = ['break', 'OL'];
+ ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', label ? CONTROL_BREAK_LABEL : CONTROL_BREAK]), ret];
+ if (label) {
+ assert(label in codeInfo.labels, label + ' in ' + keys(codeInfo.labels));
+ ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlDataStackPos), ['num', codeInfo.labels[label]]), ret];
+ }
+ return ret;
+ } else if (type == 'continue') {
+ var label = node[1] || 0;
+ if (!label && continueCapturers > 0) return; // no label, and captured
+ if (label && (label in codeInfo.labels)) return; // label, and defined in this code, so captured
+ var ret = ['break', 'OL'];
+ ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos), ['num', label ? CONTROL_CONTINUE_LABEL : CONTROL_CONTINUE]), ret];
+ if (label) {
+ ret = ['seq', makeAssign(makeStackAccess(ASM_INT, asmData.controlDataStackPos), ['num', codeInfo.labels[label]]), ret];
+ }
+ return ret;
+ } else {
+ if (type in BREAK_CAPTURERS) {
+ breakCapturers++;
+ }
+ if (type in CONTINUE_CAPTURERS) {
+ continueCapturers++;
+ }
+ }
+ }, function(node, type) {
+ if (type in BREAK_CAPTURERS) {
+ breakCapturers--;
+ }
+ if (type in CONTINUE_CAPTURERS) {
+ continueCapturers--;
+ }
+ });
+ // read the control data at the callsite to the outlined function
+ if (codeInfo.hasReturn) {
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_RETURN_VOID]),
+ [['stat', ['return']]]
+ ));
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_RETURN_INT]),
+ [['stat', ['return', makeStackAccess(ASM_INT, asmData.controlDataStackPos)]]]
+ ));
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_RETURN_DOUBLE]),
+ [['stat', ['return', makeStackAccess(ASM_DOUBLE, asmData.controlDataStackPos)]]]
+ ));
+ }
+ if (codeInfo.hasBreak) {
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_BREAK]),
+ ['stat', ['break']]
+ ));
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_BREAK_LABEL]),
+ ['stat', ['break', makeStackAccess(ASM_INT, asmData.controlDataStackPos)]] // XXX here and below, need a switch overall possible labels
+ ));
+ }
+ if (codeInfo.hasContinue) {
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_CONTINUE]),
+ ['stat', ['break']]
+ ));
+ reps.push(makeIf(
+ makeComparison(makeStackAccess(ASM_INT, asmData.controlStackPos), '==', ['num', CONTROL_CONTINUE_LABEL]),
+ ['stat', ['continue', makeStackAccess(ASM_INT, asmData.controlDataStackPos)]] // XXX
+ ));
+ }
+ }
+ var newFunc = ['defun', newIdent, ['sp'], code];
+ var newAsmData = { params: { sp: ASM_INT }, vars: {} };
+ for (var v in codeInfo.reads) {
+ newAsmData.vars[v] = getAsmType(asmData, v);
+ }
+ for (var v in codeInfo.writes) {
+ newAsmData.vars[v] = getAsmType(asmData, v);
+ }
+ denormalizeAsm(newFunc, newAsmData);
+ // replace in stats
+ stats.splice.apply(stats, [start, end-start+1].concat(reps));
+ return [newFunc];
+ }
+
+ function outlineStatements(func, asmData, stats, maxSize) {
+ level++;
+ if (measureSize(stats) < sizeToOutline) return;
+ var ret = [];
+ var sizeSeen = 0;
+ var end = stats.length-1;
+ var i = stats.length;
+ while (--i >= 0) {
+ var stat = stats[i];
+ var size = measureSize(stat);
+ //printErr(level + ' size ' + [i, size]);
+ if (size >= sizeToOutline) {
+ // this by itself is big enough to inline, recurse into it and find statements to split on
+ var subStatements = null;
+ traverse(stat, function(node, type) {
+ if (type == 'block') {
+ if (measureSize(node) >= sizeToOutline) {
+ var subRet = outlineStatements(func, asmData, node[1], maxSize);
+ if (subRet && subRet.length > 0) ret.push.apply(ret, subRet);
+ }
+ return null; // do not recurse into children, outlineStatements will do so if necessary
+ }
+ });
+ sizeSeen = 0;
+ continue;
+ }
+ sizeSeen += size;
+ // If this is big enough to outline, but no too big (if very close to the size of the full function,
+ // outlining is pointless; remove stats from the end to try to achieve the good case), then outline.
+ // Also, try to reduce the size if it is much larger than the hoped-for size
+ while ((sizeSeen > maxSize || sizeSeen > 2*sizeToOutline) && i < end) {
+ sizeSeen -= measureSize(stats[end]);
+ if (sizeSeen >= sizeToOutline) {
+ end--;
+ } else {
+ sizeSeen += measureSize(stats[end]); // abort, this took away too much
+ break;
+ }
+ }
+ if (sizeSeen >= sizeToOutline && sizeSeen <= maxSize) {
+ ret.push.apply(ret, doOutline(func, asmData, stats, i, end)); // outline [i, .. ,end] inclusive
+ sizeSeen = 0;
+ end = i-1;
+ }
+ }
+ level--;
+ return ret;
+ }
+
+ //
+
+ if (ast[0] !== 'toplevel') {
+ assert(ast[0] == 'defun');
+ ast = ['toplevel', [ast]];
+ }
+
+ var funcs = ast[1];
+
+ var more = true;
+ while (more) {
+ more = false;
+
+ var newFuncs = [];
+
+ funcs.forEach(function(func) {
+ var asmData = normalizeAsm(func);
+ var size = measureSize(func);
+ if (size >= sizeToOutline) {
+ aggressiveVariableElimination(func, asmData);
+ analyzeFunction(func, asmData);
+ var ret = outlineStatements(func, asmData, getStatements(func), 0.5*size);
+ if (ret && ret.length > 0) newFuncs.push.apply(newFuncs, ret);
+ }
+ denormalizeAsm(func, asmData);
+ });
+
+ // TODO: control flow: route returns and breaks. outlined code should have all breaks/continues/returns break into the outermost scope,
+ // after setting a state variable, etc.
+
+ if (newFuncs.length > 0) {
+ // We have outlined. Add stack support: header in which we allocate enough stack space TODO
+ // If sp was not present before, add it and before each return, pop the stack. also a final pop if not ending with a return TODO
+ // (none of this should be done in inner functions, of course, just the original)
+
+ // add new functions to the toplevel, or create a toplevel if there isn't one
+ ast[1].push.apply(ast[1], newFuncs);
+
+ funcs = newFuncs;
+ more = true;
+ }
+ }
+}
+
// Last pass utilities
// Change +5 to DOT$ZERO(5). We then textually change 5 to 5.0 (uglify's ast cannot differentiate between 5 and 5.0 directly)
function prepDotZero(ast) {
traverse(ast, function(node, type) {
- if (type == 'unary-prefix' && node[1] == '+') {
- if (node[2][0] == 'num' ||
- (node[2][0] == 'unary-prefix' && node[2][1] == '-' && node[2][2][0] == 'num')) {
+ if (type === 'unary-prefix' && node[1] === '+') {
+ if (node[2][0] === 'num' ||
+ (node[2][0] === 'unary-prefix' && node[2][1] === '-' && node[2][2][0] === 'num')) {
return ['call', ['name', 'DOT$ZERO'], [node[2]]];
}
}
@@ -2781,7 +3314,7 @@ function prepDotZero(ast) {
}
function fixDotZero(js) {
return js.replace(/DOT\$ZERO\(([-+]?(0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)/g, function(m, num) {
- if (num.substr(0, 2) == '0x' || num.substr(0, 3) == '-0x') {
+ if (num.substr(0, 2) === '0x' || num.substr(0, 3) === '-0x') {
return eval(num) + '.0';
}
if (num.indexOf('.') >= 0) return num;
@@ -2791,22 +3324,45 @@ function fixDotZero(js) {
});
}
-function asmLoopOptimizer(ast) {
+function asmLastOpts(ast) {
traverseGeneratedFunctions(ast, function(fun) {
- // This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops
- // into shapes that might confuse other passes
traverse(fun, function(node, type) {
- if (type == 'while' && node[1][0] == 'num' && node[1][1] == 1 && node[2][0] == 'block') {
+ if (type === 'while' && node[1][0] === 'num' && node[1][1] === 1 && node[2][0] === 'block') {
+ // This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops
+ // into shapes that might confuse other passes
+
// while (1) { .. if (..) { break } } ==> do { .. } while(..)
var stats = node[2][1];
var last = stats[stats.length-1];
- if (last && last[0] == 'if' && !last[3] && last[2][0] == 'block' && last[2][1][0][0] == 'break' && !last[2][1][0][1]) {
+ if (last && last[0] === 'if' && !last[3] && last[2][0] === 'block' && last[2][1][0] && last[2][1][0][0] === 'break' && !last[2][1][0][1]) {
+ var abort = false;
+ var stack = 0;
+ traverse(stats, function(node, type) {
+ if (type == 'continue') {
+ if (stack == 0 || node[1]) { // abort if labeled (we do not analyze labels here yet), or a continue directly on us
+ abort = true;
+ return true;
+ }
+ } else if (type in LOOP) {
+ stack++;
+ }
+ }, function(node, type) {
+ if (type in LOOP) {
+ stack--;
+ }
+ });
+ if (abort) return;
var conditionToBreak = last[1];
stats.pop();
node[0] = 'do';
node[1] = simplifyNotCompsDirect(['unary-prefix', '!', conditionToBreak]);
return node;
}
+ } else if (type == 'binary' && node[1] == '&' && node[3][0] == 'unary-prefix' && node[3][1] == '-' && node[3][2][0] == 'num' && node[3][2][1] == 1) {
+ // Change &-1 into |0, at this point the hint is no longer needed
+ node[1] = '|';
+ node[3] = node[3][2];
+ node[3][1] = 0;
}
});
});
@@ -2833,6 +3389,7 @@ var passes = {
eliminateMemSafe: eliminateMemSafe,
minifyGlobals: minifyGlobals,
relocate: relocate,
+ outline: outline,
minifyWhitespace: function() { minifyWhitespace = true },
noPrintMetadata: function() { printMetadata = false },
asm: function() { asm = true },
@@ -2843,6 +3400,14 @@ var passes = {
var suffix = '';
+arguments_ = arguments_.filter(function (arg) {
+ if (!/^--/.test(arg)) return true;
+
+ if (arg === '--debug') debug = true;
+ else throw new Error('Unrecognized flag: ' + arg);
+});
+
+
var src = read(arguments_[0]);
var ast = srcToAst(src);
//printErr(JSON.stringify(ast)); throw 1;
@@ -2851,11 +3416,12 @@ var extraInfoStart = src.indexOf('// EXTRA_INFO:')
if (extraInfoStart > 0) extraInfo = JSON.parse(src.substr(extraInfoStart + 14));
//printErr(JSON.stringify(extraInfo));
+
arguments_.slice(1).forEach(function(arg) {
passes[arg](ast);
});
if (asm && last) {
- asmLoopOptimizer(ast);
+ asmLastOpts(ast); // TODO: move out of last, to make last faster when done later (as in side modules)
prepDotZero(ast);
}
var js = astToSrc(ast, minifyWhitespace), old;