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.js164
1 files changed, 105 insertions, 59 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index 21d521fd..c8766bc6 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -140,6 +140,8 @@ var ALTER_FLOW = set('break', 'continue', 'return');
var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch');
var CONTINUE_CAPTURERS = LOOP;
+var FUNCTIONS_THAT_ALWAYS_THROW = set('abort', '___resumeException', '___cxa_throw', '___cxa_rethrow');
+
var NULL_NODE = ['name', 'null'];
var UNDEFINED_NODE = ['unary-prefix', 'void', ['num', 0]];
var TRUE_NODE = ['unary-prefix', '!', ['num', 0]];
@@ -722,14 +724,19 @@ function simplifyExpressions(ast) {
if (correct === 'HEAP32') {
define[3] = ['binary', '|', define[3], ['num', 0]];
} else {
- define[3] = ['unary-prefix', '+', define[3]];
+ define[3] = makeAsmCoercion(define[3], asmPreciseF32 ? ASM_FLOAT : ASM_DOUBLE);
}
// do we want a simplifybitops on the new values here?
});
info.uses.forEach(function(use) {
use[2][1][1] = correct;
});
- asmData.vars[v] = 1 - asmData.vars[v];
+ var correctType;
+ switch(asmData.vars[v]) {
+ case ASM_INT: correctType = asmPreciseF32 ? ASM_FLOAT : ASM_DOUBLE; break;
+ case ASM_FLOAT: case ASM_DOUBLE: correctType = ASM_INT; break;
+ }
+ asmData.vars[v] = correctType;
}
}
denormalizeAsm(ast, asmData);
@@ -1138,7 +1145,9 @@ function optimizeShiftsAggressive(ast) {
// or such. Simplifying these saves space and time.
function simplifyNotCompsDirect(node) {
if (node[0] === 'unary-prefix' && node[1] === '!') {
- if (node[2][0] === 'binary') {
+ // de-morgan's laws do not work on floats, due to nans >:(
+ if (node[2][0] === 'binary' && (!asm || (((node[2][2][0] === 'binary' && node[2][2][1] === '|') || node[2][2][0] === 'num') &&
+ ((node[2][3][0] === 'binary' && node[2][3][1] === '|') || node[2][3][0] === 'num')))) {
switch(node[2][1]) {
case '<': return ['binary', '>=', node[2][2], node[2][3]];
case '>': return ['binary', '<=', node[2][2], node[2][3]];
@@ -1577,7 +1586,7 @@ function makeAsmVarDef(v, type) {
case ASM_INT: return [v, ['num', 0]];
case ASM_DOUBLE: return [v, ['unary-prefix', '+', ['num', 0]]];
case ASM_FLOAT: return [v, ['call', ['name', 'Math_fround'], [['num', 0]]]];
- default: throw 'wha?';
+ default: throw 'wha? ' + JSON.stringify([node, type]) + new Error().stack;
}
}
@@ -1593,6 +1602,7 @@ function normalizeAsm(func) {
params: {}, // ident => ASM_* type
vars: {}, // ident => ASM_* type
inlines: [], // list of inline assembly copies
+ ret: undefined,
};
// process initial params
var stats = func[3];
@@ -1629,7 +1639,7 @@ function normalizeAsm(func) {
}
i++;
}
- // finally, look for other var definitions and collect them
+ // look for other var definitions and collect them
while (i < stats.length) {
traverse(stats[i], function(node, type) {
if (type === 'var') {
@@ -1658,6 +1668,11 @@ function normalizeAsm(func) {
});
i++;
}
+ // look for final 'return' statement to get return type.
+ var retStmt = stats[stats.length - 1];
+ if (retStmt && retStmt[0] === 'return' && retStmt[1]) {
+ data.ret = detectAsmCoercion(retStmt[1]);
+ }
//printErr('normalized \n\n' + astToSrc(func) + '\n\nwith: ' + JSON.stringify(data));
return data;
}
@@ -1710,6 +1725,17 @@ function denormalizeAsm(func, data) {
}
});
}
+ // ensure that there's a final 'return' statement if needed.
+ if (data.ret !== undefined) {
+ var retStmt = stats[stats.length - 1];
+ if (!retStmt || retStmt[0] !== 'return') {
+ var retVal = ['num', 0];
+ if (data.ret !== ASM_INT) {
+ retVal = makeAsmCoercion(retVal, data.ret);
+ }
+ stats.push(['return', retVal]);
+ }
+ }
//printErr('denormalized \n\n' + astToSrc(func) + '\n\n');
}
@@ -2050,6 +2076,7 @@ function registerize(ast) {
params: {},
vars: {},
inlines: asmData.inlines,
+ ret: asmData.ret,
};
for (var i = 1; i < nextReg; i++) {
var reg = fullNames[i];
@@ -2125,11 +2152,11 @@ function registerizeHarder(ast) {
// For each block we store:
// * a single entry junction
// * a single exit junction
- // * any preconditions satisfied at entry to the block
// * a 'use' and 'kill' set of names for the block
// * full sequence of 'name' and 'assign' nodes in the block
// * whether each such node appears as part of a larger expression
// (and therefore cannot be safely eliminated)
+ // * set of labels that can be used to jump to this block
var junctions = [];
var blocks = [];
@@ -2157,18 +2184,24 @@ function registerizeHarder(ast) {
if (id === undefined || id === null) {
id = addJunction();
}
- joinJunction(id);
+ joinJunction(id, true);
return id;
}
- function setJunction(id) {
+ function setJunction(id, force) {
// Set the next entry junction to the given id.
// This can be used to enter at a previously-declared point.
+ // You can't return to a junction with no incoming blocks
+ // unless the 'force' parameter is specified.
assert(nextBasicBlock.nodes.length === 0, 'refusing to abandon an in-progress basic block')
- currEntryJunction = id;
+ if (force || setSize(junctions[id].inblocks) > 0) {
+ currEntryJunction = id;
+ } else {
+ currEntryJunction = null;
+ }
}
- function joinJunction(id) {
+ function joinJunction(id, force) {
// Complete the pending basic block by exiting at this position.
// This can be used to exit at a previously-declared point.
if (currEntryJunction !== null) {
@@ -2179,8 +2212,8 @@ function registerizeHarder(ast) {
junctions[id].inblocks[nextBasicBlock.id] = 1;
blocks.push(nextBasicBlock);
}
- nextBasicBlock = { id: null, entry: null, exit: null, pre: {}, nodes: [], isexpr: [], use: {}, kill: {} };
- currEntryJunction = id;
+ nextBasicBlock = { id: null, entry: null, exit: null, labels: {}, nodes: [], isexpr: [], use: {}, kill: {} };
+ setJunction(id, force);
return id;
}
@@ -2220,7 +2253,7 @@ function registerizeHarder(ast) {
assert(false, 'unknown jump node type');
}
}
- setJunction(null);
+ currEntryJunction = null;
}
function addUseNode(node) {
@@ -2259,29 +2292,10 @@ function registerizeHarder(ast) {
return node;
}
- function addPreCondTrue(node) {
- // Add pre-conditions implied by truth of the
- // given node to the current basic block.
- assert(nextBasicBlock.nodes.length === 0, 'cant add preconditions to an in-progress basic block')
- if (node[0] === 'binary' && node[1] === '==') {
- var lhs = lookThroughCasts(node[2]);
- var rhs = lookThroughCasts(node[3]);
- if (lhs[0] === 'name' && rhs[0] === 'num') {
- nextBasicBlock.pre[lhs[1]] = ['==', rhs[1]];
- }
- }
- }
-
- function addPreCondFalse(node) {
- // Add pre-conditions implied by falsehood of the
- // given node to the current basic block.
- assert(nextBasicBlock.nodes.length === 0, 'cant add preconditions to an in-progress basic block')
- if (node[0] === 'binary' && node[1] === '==') {
- var lhs = lookThroughCasts(node[2]);
- var rhs = lookThroughCasts(node[3]);
- if (lhs[0] === 'name' && rhs[0] === 'num') {
- nextBasicBlock.pre[lhs[1]] = ['!=', rhs[1]];
- }
+ function addBlockLabel(node) {
+ assert(nextBasicBlock.nodes.length === 0, 'cant add label to an in-progress basic block')
+ if (node[0] === 'num') {
+ nextBasicBlock.labels[node[1]] = 1;
}
}
@@ -2341,13 +2355,19 @@ function registerizeHarder(ast) {
buildFlowGraph(node[1]);
isInExpr--;
var jEnter = markJunction();
- addPreCondTrue(node[1]);
+ var jExit = addJunction();
if (node[2]) {
+ // Detect and mark "if (label == N) { <labelled block> }".
+ if (node[1][0] === 'binary' && node[1][1] === '==') {
+ var lhs = lookThroughCasts(node[1][2]);
+ if (lhs[0] === 'name' && lhs[1] === 'label') {
+ addBlockLabel(lookThroughCasts(node[1][3]));
+ }
+ }
buildFlowGraph(node[2]);
}
- var jExit = markJunction();
+ joinJunction(jExit);
setJunction(jEnter);
- addPreCondFalse(node[1]);
if (node[3]) {
buildFlowGraph(node[3]);
}
@@ -2357,13 +2377,12 @@ function registerizeHarder(ast) {
isInExpr++;
buildFlowGraph(node[1]);
var jEnter = markJunction();
- addPreCondTrue(node[1]);
+ var jExit = addJunction();
if (node[2]) {
buildFlowGraph(node[2]);
}
- var jExit = markJunction();
+ joinJunction(jExit);
setJunction(jEnter);
- addPreCondFalse(node[1]);
if (node[3]) {
buildFlowGraph(node[3]);
}
@@ -2390,7 +2409,6 @@ function registerizeHarder(ast) {
isInExpr--;
joinJunction(jLoop);
pushActiveLabels(jCond, jExit);
- addPreCondTrue(node[1]);
buildFlowGraph(node[2]);
popActiveLabels();
joinJunction(jCond);
@@ -2462,38 +2480,46 @@ function registerizeHarder(ast) {
case 'switch':
// Emscripten generates switch statements of a very limited
// form: all case clauses are numeric literals, and all
- // case bodies end with a break. So it's basically equivalent
- // to a multi-way 'if' statement.
+ // case bodies end with a (maybe implicit) break. So it's
+ // basically equivalent to a multi-way 'if' statement.
isInExpr++;
buildFlowGraph(node[1]);
isInExpr--;
+ var condition = lookThroughCasts(node[1]);
var jCheckExit = markJunction();
var jExit = addJunction();
pushActiveLabels(null, jExit);
var hasDefault = false;
for (var i=0; i<node[2].length; i++) {
setJunction(jCheckExit);
+ // All case clauses are either 'default' or a numeric literal.
if (!node[2][i][0]) {
hasDefault = true;
} else {
- if (node[2][i][0][0] !== 'num') {
- if (node[2][i][0][0] !== 'unary-prefix' || node[2][i][0][2][0] !== 'num') {
- assert(false, 'non-numeric switch case clause');
- }
+ // Detect switches dispatching to labelled blocks.
+ if (condition[0] === 'name' && condition[1] === 'label') {
+ addBlockLabel(lookThroughCasts(node[2][i][0]));
}
- addPreCondTrue(['binary', '==', node[1], node[2][i][0]]);
}
for (var j = 0; j < node[2][i][1].length; j++) {
buildFlowGraph(node[2][i][1][j]);
}
- if (currEntryJunction !== null, 'switch case body did not break');
+ // Control flow will never actually reach the end of the case body.
+ // If there's live code here, assume it jumps to case exit.
+ if (currEntryJunction !== null && nextBasicBlock.nodes.length > 0) {
+ if (node[2][i][0]) {
+ markNonLocalJump('return');
+ } else {
+ joinJunction(jExit);
+ }
+ }
}
// If there was no default case, we also need an empty block
// linking straight from the test evaluation to the exit.
if (!hasDefault) {
setJunction(jCheckExit);
}
- markJunction(jExit);
+ joinJunction(jExit);
popActiveLabels()
break;
case 'return':
@@ -2553,6 +2579,13 @@ function registerizeHarder(ast) {
}
}
isInExpr--;
+ // If the call is statically known to throw,
+ // treat it as a jump to function exit.
+ if (!isInExpr && node[1][0] === 'name') {
+ if (node[1][1] in FUNCTIONS_THAT_ALWAYS_THROW) {
+ markNonLocalJump('return');
+ }
+ }
break;
case 'seq':
case 'sub':
@@ -2592,18 +2625,17 @@ function registerizeHarder(ast) {
FINDLABELLEDBLOCKS:
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
- // Does it have a specific label value as precondition?
- var labelCond = block.pre['label'];
- if (labelCond && labelCond[0] === '==') {
+ // Does it have any labels as preconditions to its entry?
+ for (var labelVal in block.labels) {
// If there are multiple blocks with the same label, all bets are off.
// This seems to happen sometimes for short blocks that end with a return.
// TODO: it should be safe to merge the duplicates if they're identical.
- if (labelCond[1] in labelledBlocks) {
+ if (labelVal in labelledBlocks) {
labelledBlocks = {};
labelledJumps = [];
break FINDLABELLEDBLOCKS;
}
- labelledBlocks[labelCond[1]] = block;
+ labelledBlocks[labelVal] = block;
}
// Does it assign a specific label value at exit?
if ('label' in block.kill) {
@@ -3096,6 +3128,7 @@ function registerizeHarder(ast) {
params: {},
vars: {},
inlines: asmData.inlines,
+ ret: asmData.ret,
};
for (var i = 1; i < nextReg; i++) {
var reg;
@@ -5066,6 +5099,17 @@ function safeHeap(ast) {
});
}
+function optimizeFrounds(ast) {
+ // collapse fround(fround(..)), which can happen due to elimination
+ function fix(node) {
+ traverseChildren(node, fix);
+ if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround' && node[2][0][0] === 'call' && node[2][0][1][0] === 'name' && node[2][0][1][1] === 'Math_fround') {
+ return node[2][0];
+ }
+ }
+ traverseChildren(ast, fix);
+}
+
// 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)
@@ -5094,7 +5138,7 @@ function fixDotZero(js) {
function asmLastOpts(ast) {
traverseGeneratedFunctions(ast, function(fun) {
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' && node[2].length == 2) {
// 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
@@ -5153,7 +5197,7 @@ function asmLastOpts(ast) {
// Passes table
-var minifyWhitespace = false, printMetadata = true, asm = false, last = false;
+var minifyWhitespace = false, printMetadata = true, asm = false, asmPreciseF32 = false, last = false;
var passes = {
// passes
@@ -5177,11 +5221,13 @@ var passes = {
relocate: relocate,
outline: outline,
safeHeap: safeHeap,
+ optimizeFrounds: optimizeFrounds,
// flags
minifyWhitespace: function() { minifyWhitespace = true },
noPrintMetadata: function() { printMetadata = false },
asm: function() { asm = true },
+ asmPreciseF32: function() { asmPreciseF32 = true },
last: function() { last = true },
};