aboutsummaryrefslogtreecommitdiff
path: root/tools/js-optimizer.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/js-optimizer.js')
-rw-r--r--tools/js-optimizer.js283
1 files changed, 188 insertions, 95 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index 07317e0a..67cd8066 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -143,17 +143,17 @@ var FALSE_NODE = ['unary-prefix', '!', ['num', 1]];
var GENERATED_FUNCTIONS_MARKER = '// EMSCRIPTEN_GENERATED_FUNCTIONS';
var generatedFunctions = false; // whether we have received only generated functions
-var minifierInfo = null;
+var extraInfo = null;
function srcToAst(src) {
return uglify.parser.parse(src);
}
-function astToSrc(ast, compress) {
+function astToSrc(ast, minifyWhitespace) {
return uglify.uglify.gen_code(ast, {
ascii_only: true,
- beautify: !compress,
- indent_level: 2
+ beautify: !minifyWhitespace,
+ indent_level: 1
});
}
@@ -429,19 +429,47 @@ function simplifyExpressionsPre(ast) {
// 'useful' mathops already |0 anyhow.
function simplifyBitops(ast) {
- var SAFE_BINARY_OPS = set('+', '-', '*'); // division is unsafe as it creates non-ints in JS; mod is unsafe as signs matter so we can't remove |0's
+ var SAFE_BINARY_OPS;
+ if (asm) {
+ SAFE_BINARY_OPS = set('+', '-'); // division is unsafe as it creates non-ints in JS; mod is unsafe as signs matter so we can't remove |0's; mul does not nest with +,- in asm
+ } else {
+ SAFE_BINARY_OPS = set('+', '-', '*');
+ }
+ var COERCION_REQUIRING_OPS = set('sub', 'unary-prefix'); // ops that in asm must be coerced right away
+ var COERCION_REQUIRING_BINARIES = set('*', '/', '%'); // binary ops that in asm must be coerced
var ZERO = ['num', 0];
- var rerun = true;
- 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]];
- } else if (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO)) {
+
+ function removeMultipleOrZero() {
+ var rerun = true;
+ 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]];
+ }
+ var go = false;
+ if (jsonCompare(node[2], ZERO)) {
+ // canonicalize order
+ var temp = node[3];
+ node[3] = node[2];
+ node[2] = temp;
+ go = true;
+ } else if (jsonCompare(node[3], ZERO)) {
+ go = true;
+ }
+ if (!go) {
+ stack.push(1);
+ return;
+ }
// We might be able to remove this correction
for (var i = stack.length-1; i >= 0; i--) {
- if (stack[i] == 1) {
+ 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] < 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
+ }
// we will replace ourselves with the non-zero side. Recursively process that node.
var result = jsonCompare(node[2], ZERO) ? node[3] : node[2], other;
// replace node in-place
@@ -453,23 +481,23 @@ function simplifyExpressionsPre(ast) {
return process(result, result[0], stack);
} else if (stack[i] == -1) {
break; // Too bad, we can't
- } else if (asm) {
- break; // we must keep a coercion right on top of a heap access in asm mode
}
}
+ 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) {
+ stack.push(1);
+ } 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 {
+ stack.push(-1); // This node is dangerous! Give up if you see this before you see '1'
}
- stack.push(1); // 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) {
- stack.push(1);
- } 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 {
- stack.push(-1); // This node is dangerous! Give up if you see this before you see '1'
- }
- }, null, []);
+ }, null, []);
+ }
}
+ removeMultipleOrZero();
+
// & and heap-related optimizations
var heapBits, heapUnsigned;
@@ -480,7 +508,7 @@ function simplifyExpressionsPre(ast) {
return true;
}
- var hasTempDoublePtr = false;
+ var hasTempDoublePtr = false, rerunOrZeroPass = false;
traverse(ast, function(node, type) {
if (type == 'name') {
@@ -515,7 +543,6 @@ function simplifyExpressionsPre(ast) {
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
- // TODO: run this before | 0 | 0 removal, because we generate | 0
var amount = node[3][1];
var name = node[2][2][1][1];
if (amount == node[2][3][1] && parseHeap(name)) {
@@ -524,6 +551,7 @@ function simplifyExpressionsPre(ast) {
node[1] = '|';
node[2] = node[2][2];
node[3][1] = 0;
+ rerunOrZeroPass = true;
return node;
}
}
@@ -556,6 +584,8 @@ function simplifyExpressionsPre(ast) {
}
});
+ if (rerunOrZeroPass) removeMultipleOrZero();
+
if (asm) {
if (hasTempDoublePtr) {
traverse(ast, function(node, type) {
@@ -707,18 +737,11 @@ function simplifyExpressionsPre(ast) {
}
function asmOpts(fun) {
- // 1. Add final returns when necessary
- // 2. Remove unneeded coercions on function calls that have no targets (eliminator removed it)
+ // Add final returns when necessary
var returnType = null;
traverse(fun, function(node, type) {
if (type == 'return' && node[1]) {
returnType = detectAsmCoercion(node[1]);
- } else if (type == 'stat') {
- var inner = node[1];
- if ((inner[0] == 'binary' && inner[1] in ASSOCIATIVE_BINARIES && inner[2][0] == 'call' && inner[3][0] == 'num') ||
- (inner[0] == 'unary-prefix' && inner[1] == '+' && inner[2][0] == 'call')) {
- node[1] = inner[2];
- }
}
});
// Add a final return if one is missing.
@@ -1093,11 +1116,15 @@ function simplifyNotCompsDirect(node) {
return node[2][2];
}
}
- return node;
+ if (!simplifyNotCompsPass) return node;
}
+var simplifyNotCompsPass = false;
+
function simplifyNotComps(ast) {
+ simplifyNotCompsPass = true;
traverse(ast, simplifyNotCompsDirect);
+ simplifyNotCompsPass = false;
}
function simplifyExpressionsPost(ast) {
@@ -1607,7 +1634,7 @@ function denormalizeAsm(func, data) {
// Very simple 'registerization', coalescing of variables into a smaller number,
// as part of minification. Globals-level minification began in a previous pass,
-// we receive minifierInfo which tells us how to rename globals. (Only in asm.js.)
+// we receive extraInfo which tells us how to rename globals. (Only in asm.js.)
//
// We do not optimize when there are switches, so this pass only makes sense with
// relooping.
@@ -1649,7 +1676,7 @@ function registerize(ast) {
}
});
vacuum(fun);
- if (minifierInfo) {
+ if (extraInfo) {
assert(asm);
var usedGlobals = {};
var nextLocal = 0;
@@ -1657,7 +1684,7 @@ function registerize(ast) {
traverse(fun, function(node, type) {
if (type == 'name') {
var name = node[1];
- var minified = minifierInfo.globals[name];
+ var minified = extraInfo.globals[name];
if (minified) {
assert(!localVars[name], name); // locals must not shadow globals, or else we don't know which is which
if (localVars[minified]) {
@@ -1690,8 +1717,8 @@ function registerize(ast) {
}
}
});
- assert(fun[1] in minifierInfo.globals, fun[1]);
- fun[1] = minifierInfo.globals[fun[1]];
+ assert(fun[1] in extraInfo.globals, fun[1]);
+ fun[1] = extraInfo.globals[fun[1]];
assert(fun[1]);
var nextRegName = 0;
}
@@ -1699,14 +1726,14 @@ function registerize(ast) {
function getNewRegName(num, name) {
if (!asm) return 'r' + num;
var type = asmData.vars[name];
- if (!minifierInfo) {
+ if (!extraInfo) {
var ret = (type ? 'd' : 'i') + num;
regTypes[ret] = type;
return ret;
}
// find the next free minified name that is not used by a global that shows up in this function
- while (nextRegName < minifierInfo.names.length) {
- var ret = minifierInfo.names[nextRegName++];
+ while (nextRegName < extraInfo.names.length) {
+ var ret = extraInfo.names[nextRegName++];
if (!usedGlobals[ret]) {
regTypes[ret] = type;
return ret;
@@ -2411,7 +2438,10 @@ function eliminate(ast, memSafe) {
}
traverseInOrder(node);
}
+ //var eliminationLimit = 0; // used to debugging purposes
function doEliminate(name, node) {
+ //if (eliminationLimit == 0) return;
+ //eliminationLimit--;
//printErr('elim!!!!! ' + name);
// yes, eliminate!
varsToRemove[name] = 2; // both assign and var definitions can have other vars we must clean up
@@ -2560,7 +2590,13 @@ function eliminate(ast, memSafe) {
}
if (looperUsed) return;
}
+ 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
+ }
+ }
// hurrah! this is safe to do
+ //printErr("ELIM LOOP VAR " + JSON.stringify(loopers) + ' :: ' + JSON.stringify(helpers));
for (var l = 0; l < loopers.length; l++) {
var looper = loopers[l];
var helper = helpers[l];
@@ -2590,48 +2626,50 @@ function eliminate(ast, memSafe) {
}
});
- // A class for optimizing expressions. We know that it is legitimate to collapse
- // 5+7 in the generated code, as it will always be numerical, for example. XXX do we need this? here?
- function ExpressionOptimizer(node) {
- this.node = node;
-
- this.run = function() {
- traverse(this.node, function(node, type) {
- 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 (!asm) { // TODO: deprecate in non-asm too
+ // A class for optimizing expressions. We know that it is legitimate to collapse
+ // 5+7 in the generated code, as it will always be numerical, for example. XXX do we need this? here?
+ function ExpressionOptimizer(node) {
+ this.node = node;
+
+ this.run = function() {
+ traverse(this.node, function(node, type) {
+ 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] != '+') {
+ fail = true;
+ return false;
+ }
+ } else if (subType === 'name') {
+ names.push(subNode[1]);
+ return;
+ } else if (subType === 'num') {
+ num += subNode[1];
+ has_num = true;
+ return;
+ } else {
fail = true;
return false;
}
- } else if (subType === 'name') {
- names.push(subNode[1]);
- return;
- } else if (subType === 'num') {
- num += subNode[1];
- has_num = true;
- return;
- } else {
- fail = true;
- return false;
- }
- });
- if (!fail && has_num) {
- var ret = ['num', num];
- for (var i = 0; i < names.length; i++) {
- ret = ['binary', '+', ['name', names[i]], ret];
+ });
+ if (!fail && has_num) {
+ var ret = ['num', num];
+ for (var i = 0; i < names.length; i++) {
+ ret = ['binary', '+', ['name', names[i]], ret];
+ }
+ return ret;
}
- return ret;
}
- }
- });
- };
+ });
+ };
+ }
+ new ExpressionOptimizer(ast).run();
}
- new ExpressionOptimizer(ast).run();
}
function eliminateMemSafe(ast) {
@@ -2652,16 +2690,16 @@ function minifyGlobals(ast) {
var vars = node[1];
for (var i = 0; i < vars.length; i++) {
var name = vars[i][0];
- assert(next < minifierInfo.names.length);
- vars[i][0] = minified[name] = minifierInfo.names[next++];
+ assert(next < extraInfo.names.length);
+ vars[i][0] = minified[name] = extraInfo.names[next++];
}
}
});
// add all globals in function chunks, i.e. not here but passed to us
- for (var i = 0; i < minifierInfo.globals.length; i++) {
- name = minifierInfo.globals[i];
- assert(next < minifierInfo.names.length);
- minified[name] = minifierInfo.names[next++];
+ for (var i = 0; i < extraInfo.globals.length; i++) {
+ name = extraInfo.globals[i];
+ assert(next < extraInfo.names.length);
+ minified[name] = extraInfo.names[next++];
}
// apply minification
traverse(ast, function(node, type) {
@@ -2672,9 +2710,64 @@ function minifyGlobals(ast) {
}
}
});
- suffix = '// MINIFY_INFO:' + JSON.stringify(minified);
+ suffix = '// EXTRA_INFO:' + JSON.stringify(minified);
}
+// Relocation pass for a shared module (for the functions part of the module)
+//
+// 1. Replace function names with alternate names as defined (to avoid colliding with
+// names in the main module we are being linked to)
+// 2. Hardcode function table offsets from F_BASE+x to const+x if x is a variable, or
+// the constant sum of the base + offset
+// 3. Hardcode heap offsets from H_BASE as well
+function relocate(ast) {
+ assert(asm); // we also assume we are normalized
+
+ var replacements = extraInfo.replacements;
+ var fBase = extraInfo.fBase;
+ var hBase = extraInfo.hBase;
+
+ traverse(ast, function(node, type) {
+ switch(type) {
+ case 'name': case 'defun': {
+ var rep = replacements[node[1]];
+ if (rep) node[1] = rep;
+ break;
+ }
+ 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') {
+ base = hBase;
+ }
+ if (base) {
+ var other = node[3];
+ if (other[0] == 'num') {
+ other[1] += base;
+ return other;
+ } else {
+ node[2] = ['num', base];
+ }
+ }
+ }
+ break;
+ }
+ case 'var': {
+ var vars = node[1];
+ for (var i = 0; i < vars.length; i++) {
+ var name = vars[i][0];
+ assert(!(name in replacements)); // cannot shadow functions we are replacing TODO: fix that
+ }
+ break;
+ }
+ }
+ });
+}
+
+// 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) {
@@ -2721,7 +2814,7 @@ function asmLoopOptimizer(ast) {
// Passes table
-var compress = false, printMetadata = true, asm = false, last = false;
+var minifyWhitespace = false, printMetadata = true, asm = false, last = false;
var passes = {
dumpAst: dumpAst,
@@ -2739,11 +2832,11 @@ var passes = {
eliminate: eliminate,
eliminateMemSafe: eliminateMemSafe,
minifyGlobals: minifyGlobals,
- compress: function() { compress = true },
+ relocate: relocate,
+ minifyWhitespace: function() { minifyWhitespace = true },
noPrintMetadata: function() { printMetadata = false },
asm: function() { asm = true },
last: function() { last = true },
- closure: function(){} // handled in python
};
// Main
@@ -2754,9 +2847,9 @@ var src = read(arguments_[0]);
var ast = srcToAst(src);
//printErr(JSON.stringify(ast)); throw 1;
generatedFunctions = src.indexOf(GENERATED_FUNCTIONS_MARKER) >= 0;
-var minifierInfoStart = src.indexOf('// MINIFY_INFO:')
-if (minifierInfoStart > 0) minifierInfo = JSON.parse(src.substr(minifierInfoStart + 15));
-//printErr(JSON.stringify(minifierInfo));
+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);
@@ -2765,7 +2858,7 @@ if (asm && last) {
asmLoopOptimizer(ast);
prepDotZero(ast);
}
-var js = astToSrc(ast, compress), old;
+var js = astToSrc(ast, minifyWhitespace), old;
if (asm && last) {
js = fixDotZero(js);
}