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.js146
1 files changed, 98 insertions, 48 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index 022bdf47..57ce0071 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -618,6 +618,7 @@ function simplifyExpressions(ast) {
if (asm) {
if (hasTempDoublePtr) {
+ var asmData = normalizeAsm(ast);
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') {
@@ -642,7 +643,7 @@ function simplifyExpressions(ast) {
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]];
+ return makeAsmCoercion(node[1][3], detectAsmCoercion(node[2]));
} else {
node[1][3][1][1] = 'HEAP32';
return ['binary', '|', node[1][3], ['num', 0]];
@@ -686,7 +687,6 @@ function simplifyExpressions(ast) {
}
}
});
- var asmData = normalizeAsm(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
@@ -1142,13 +1142,28 @@ function simplifyNotComps(ast) {
simplifyNotCompsPass = false;
}
-var NO_SIDE_EFFECTS = set('num', 'name');
+function callHasSideEffects(node) { // checks if the call itself (not the args) has side effects (or is not statically known)
+ return !(node[1][0] === 'name' && /^Math_/.test(node[1][1]));
+}
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]);
- return true;
+ switch (node[0]) {
+ case 'num': case 'name': case 'string': return false;
+ case 'unary-prefix': return hasSideEffects(node[2]);
+ case 'binary': return hasSideEffects(node[2]) || hasSideEffects(node[3]);
+ case 'sub': return hasSideEffects(node[1]) || hasSideEffects(node[2]);
+ case 'call': {
+ if (callHasSideEffects(node)) return true;
+ // This is a statically known call, with no side effects. only args can side effect us
+ var args = node[2];
+ var num = args.length;
+ for (var i = 0; i < num; i++) {
+ if (hasSideEffects(args[i])) return true;
+ }
+ return false;
+ }
+ default: return true;
+ }
}
// Clear out empty ifs and blocks, and redundant blocks/stats and so forth
@@ -1515,21 +1530,33 @@ function unVarify(vars, ret) { // transform var x=1, y=2 etc. into (x=1, y=2), i
// annotations, plus explicit metadata) and denormalize (vice versa)
var ASM_INT = 0;
var ASM_DOUBLE = 1;
+var ASM_FLOAT = 2;
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;
if (node[0] === 'unary-prefix') return ASM_DOUBLE;
+ if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround') return ASM_FLOAT;
if (asmInfo && node[0] == 'name') return getAsmType(node[1], asmInfo);
return ASM_INT;
}
function makeAsmCoercion(node, type) {
- return type === ASM_INT ? ['binary', '|', node, ['num', 0]] : ['unary-prefix', '+', node];
+ switch (type) {
+ case ASM_INT: return ['binary', '|', node, ['num', 0]];
+ case ASM_DOUBLE: return ['unary-prefix', '+', node];
+ case ASM_FLOAT: return ['call', ['name', 'Math_fround'], [node]];
+ default: throw 'wha? ' + JSON.stringify([node, type]) + new Error().stack;
+ }
}
function makeAsmVarDef(v, type) {
- return [v, type === ASM_INT ? ['num', 0] : ['unary-prefix', '+', ['num', 0]]];
+ switch (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?';
+ }
}
function getAsmType(name, asmInfo) {
@@ -1568,7 +1595,8 @@ 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
+ || (value[0] === 'call' && value[1][0] === 'name' && value[1][1] === 'Math_fround'));
data.vars[name] = detectAsmCoercion(value);
v.length = 1; // make an un-assigning var
} else {
@@ -1917,7 +1945,7 @@ function registerize(ast) {
// we just use a fresh register to make sure we avoid this, but it could be
// optimized to check for safe registers (free, and not used in this loop level).
var varRegs = {}; // maps variables to the register they will use all their life
- var freeRegsClasses = asm ? [[], []] : []; // two classes for asm, one otherwise
+ var freeRegsClasses = asm ? [[], [], []] : []; // two classes for asm, one otherwise XXX - hardcoded length
var nextReg = 1;
var fullNames = {};
var loopRegs = {}; // for each loop nesting level, the list of bound variables
@@ -2031,6 +2059,33 @@ function registerize(ast) {
}
}
denormalizeAsm(fun, finalAsmData);
+ if (extraInfo && extraInfo.globals) {
+ // minify in asm var definitions, that denormalizeAsm just generated
+ function minify(value) {
+ if (value && value[0] === 'call' && value[1][0] === 'name') {
+ var name = value[1][1];
+ var minified = extraInfo.globals[name];
+ if (minified) {
+ value[1][1] = minified;
+ }
+ }
+ }
+ var stats = fun[3];
+ for (var i = 0; i < stats.length; i++) {
+ var line = stats[i];
+ if (i >= fun[2].length && line[0] !== 'var') break; // when we pass the arg and var coercions, break
+ if (line[0] === 'stat') {
+ assert(line[1][0] === 'assign');
+ minify(line[1][3]);
+ } else {
+ assert(line[0] === 'var');
+ var pairs = line[1];
+ for (var j = 0; j < pairs.length; j++) {
+ minify(pairs[j][1]);
+ }
+ }
+ }
+ }
}
});
}
@@ -2068,7 +2123,6 @@ function registerize(ast) {
// can happen in ALLOW_MEMORY_GROWTH mode
var ELIMINATION_SAFE_NODES = set('var', 'assign', 'call', 'if', 'toplevel', 'do', 'return', 'label', 'switch'); // do is checked carefully, however
-var NODES_WITHOUT_ELIMINATION_SIDE_EFFECTS = set('name', 'num', 'string', 'binary', 'sub', 'unary-prefix');
var IGNORABLE_ELIMINATOR_SCAN_NODES = set('num', 'toplevel', 'string', 'break', 'continue', 'dot'); // dot can only be STRING_TABLE.*
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)
@@ -2160,7 +2214,7 @@ function eliminate(ast, memSafe) {
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) (no definition means it is a function parameter, or a local with just |var x;| but no defining assignment)
- var hasSideEffects = false;
+ var sideEffects = false;
var value = values[name];
if (value) {
// TODO: merge with other side effect code
@@ -2170,15 +2224,10 @@ function eliminate(ast, memSafe) {
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)) {
- hasSideEffects = true; // cannot remove this unused variable, constructing it has side effects
- return true;
- }
- });
+ sideEffects = hasSideEffects(value);
}
}
- if (!hasSideEffects) {
+ if (!sideEffects) {
varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally
sideEffectFree[name] = true;
// Each time we remove a variable with 0 uses, if its value has no
@@ -2437,14 +2486,16 @@ function eliminate(ast, memSafe) {
for (var i = 0; i < args.length; i++) {
traverseInOrder(args[i]);
}
- // these two invalidations will also invalidate calls
- if (!globalsInvalidated) {
- invalidateGlobals();
- globalsInvalidated = true;
- }
- if (!memoryInvalidated) {
- invalidateMemory();
- memoryInvalidated = true;
+ if (callHasSideEffects(node)) {
+ // these two invalidations will also invalidate calls
+ if (!globalsInvalidated) {
+ invalidateGlobals();
+ globalsInvalidated = true;
+ }
+ if (!memoryInvalidated) {
+ invalidateMemory();
+ memoryInvalidated = true;
+ }
}
} else if (type === 'if') {
if (allowTracking) {
@@ -2485,6 +2536,10 @@ function eliminate(ast, memSafe) {
} else if (type === 'return') {
if (node[1]) traverseInOrder(node[1]);
} else if (type === 'conditional') {
+ if (!callsInvalidated) { // invalidate calls, since we cannot eliminate them into a branch of an LLVM select/JS conditional that does not execute
+ invalidateCalls();
+ callsInvalidated = true;
+ }
traverseInOrder(node[1]);
traverseInOrder(node[2]);
traverseInOrder(node[3]);
@@ -3169,7 +3224,7 @@ function outline(ast) {
var writes = {};
var namings = {};
- var hasReturn = false, hasReturnInt = false, hasReturnDouble = false, hasBreak = false, hasContinue = false;
+ var hasReturn = false, hasReturnType = {}, hasBreak = false, hasContinue = false;
var breaks = {}; // set of labels we break or continue
var continues = {}; // to (name -> id, just like labels)
var breakCapturers = 0;
@@ -3189,10 +3244,8 @@ function outline(ast) {
} else if (type == 'return') {
if (!node[1]) {
hasReturn = true;
- } else if (detectAsmCoercion(node[1]) == ASM_INT) {
- hasReturnInt = true;
} else {
- hasReturnDouble = true;
+ hasReturnType[detectAsmCoercion(node[1])] = true;
}
} else if (type == 'break') {
var label = node[1] || 0;
@@ -3222,7 +3275,6 @@ function outline(ast) {
continueCapturers--;
}
});
- assert(hasReturn + hasReturnInt + hasReturnDouble <= 1);
var reads = {};
for (var v in namings) {
@@ -3230,7 +3282,7 @@ function outline(ast) {
if (actualReads > 0) reads[v] = actualReads;
}
- return { writes: writes, reads: reads, hasReturn: hasReturn, hasReturnInt: hasReturnInt, hasReturnDouble: hasReturnDouble, hasBreak: hasBreak, hasContinue: hasContinue, breaks: breaks, continues: continues, labels: labels };
+ return { writes: writes, reads: reads, hasReturn: hasReturn, hasReturnType: hasReturnType, hasBreak: hasBreak, hasContinue: hasContinue, breaks: breaks, continues: continues, labels: labels };
}
function makeAssign(dst, src) {
@@ -3251,7 +3303,10 @@ function outline(ast) {
return ['switch', value, cases];
}
- 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 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, CONTROL_RETURN_FLOAT = 8;
+ function controlFromAsmType(asmType) {
+ return CONTROL_RETURN_INT + (asmType | 0); // assumes ASM_INT starts at 0, and order of these two is identical!
+ }
var sizeToOutline = null; // customized per function and as we make progress
function calculateThreshold(func, asmData) {
@@ -3310,7 +3365,7 @@ function outline(ast) {
});
// Generate new function
- if (codeInfo.hasReturn || codeInfo.hasReturnInt || codeInfo.hasReturnDouble || codeInfo.hasBreak || codeInfo.hasContinue) {
+ if (codeInfo.hasReturn || codeInfo.hasReturnType[ASM_INT] || codeInfo.hasReturnType[ASM_DOUBLE] || codeInfo.hasReturnType[ASM_FLOAT] || codeInfo.hasBreak || codeInfo.hasContinue) {
// we need to capture all control flow using a top-level labeled one-time loop in the outlined function
var breakCapturers = 0;
var continueCapturers = 0;
@@ -3335,7 +3390,7 @@ function outline(ast) {
ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', CONTROL_RETURN_VOID])]);
} else {
var type = detectAsmCoercion(node[1], asmData);
- ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', type == ASM_INT ? CONTROL_RETURN_INT : CONTROL_RETURN_DOUBLE])]);
+ ret.push(['stat', makeAssign(makeStackAccess(ASM_INT, asmData.controlStackPos(outlineIndex)), ['num', controlFromAsmType(type)])]);
ret.push(['stat', makeAssign(makeStackAccess(type, asmData.controlDataStackPos(outlineIndex)), node[1])]);
}
ret.push(['stat', ['break', 'OL']]);
@@ -3398,16 +3453,10 @@ function outline(ast) {
[['stat', ['return']]]
));
}
- if (codeInfo.hasReturnInt) {
+ for (var returnType in codeInfo.hasReturnType) {
reps.push(makeIf(
- makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', CONTROL_RETURN_INT]),
- [['stat', ['return', makeAsmCoercion(['name', 'tempInt'], ASM_INT)]]]
- ));
- }
- if (codeInfo.hasReturnDouble) {
- reps.push(makeIf(
- makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', CONTROL_RETURN_DOUBLE]),
- [['stat', ['return', makeAsmCoercion(['name', 'tempDouble'], ASM_DOUBLE)]]]
+ makeComparison(makeAsmCoercion(['name', 'tempValue'], ASM_INT), '==', ['num', controlFromAsmType(returnType)]),
+ [['stat', ['return', makeAsmCoercion(['name', 'tempInt'], returnType | 0)]]]
));
}
if (codeInfo.hasBreak) {
@@ -3486,8 +3535,9 @@ function outline(ast) {
var last = getStatements(func)[getStatements(func).length-1];
if (last[0] === 'stat') last = last[1];
if (last[0] !== 'return') {
- if (allCodeInfo.hasReturnInt || allCodeInfo.hasReturnDouble) {
- getStatements(func).push(['stat', ['return', makeAsmCoercion(['num', 0], allCodeInfo.hasReturnInt ? ASM_INT : ASM_DOUBLE)]]);
+ for (var returnType in codeInfo.hasReturnType) {
+ getStatements(func).push(['stat', ['return', makeAsmCoercion(['num', 0], returnType | 0)]]);
+ break;
}
}
outliningParents[newIdent] = func[1];