aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/eliminator/eliminator-test-output.js3
-rw-r--r--tools/eliminator/eliminator-test.js10
-rw-r--r--tools/js-optimizer.js142
-rw-r--r--tools/response_file.py6
-rw-r--r--tools/shared.py43
5 files changed, 144 insertions, 60 deletions
diff --git a/tools/eliminator/eliminator-test-output.js b/tools/eliminator/eliminator-test-output.js
index 1a6506ed..0171e99b 100644
--- a/tools/eliminator/eliminator-test-output.js
+++ b/tools/eliminator/eliminator-test-output.js
@@ -6119,4 +6119,7 @@ function intoCond() {
HEAP32[$115 >> 2] = $NumWords;
}
}
+function math(a, b, c, d) {
+ print(Math_imul(d) + (Math_fround(c) + (a + Math_abs(b))));
+}
diff --git a/tools/eliminator/eliminator-test.js b/tools/eliminator/eliminator-test.js
index ffad69ea..ef17b388 100644
--- a/tools/eliminator/eliminator-test.js
+++ b/tools/eliminator/eliminator-test.js
@@ -8852,5 +8852,13 @@ function intoCond() {
HEAP32[$504 >> 2] = $503;
}
}
-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1", "_inflate", "_malloc", "_mallocNoU", "asm", "phi", "intoCond"]
+function math(a, b, c, d) {
+ var x, y, z, w;
+ x = a;
+ y = Math_abs(b);
+ z = Math_fround(c);
+ w = Math_imul(d);
+ print(x + y + z + w);
+}
+// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1", "_inflate", "_malloc", "_mallocNoU", "asm", "phi", "intoCond", "math"]
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index 36244298..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) {
@@ -3173,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;
@@ -3193,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;
@@ -3226,7 +3275,6 @@ function outline(ast) {
continueCapturers--;
}
});
- assert(hasReturn + hasReturnInt + hasReturnDouble <= 1);
var reads = {};
for (var v in namings) {
@@ -3234,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) {
@@ -3255,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) {
@@ -3314,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;
@@ -3339,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']]);
@@ -3402,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) {
@@ -3490,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];
diff --git a/tools/response_file.py b/tools/response_file.py
index f19cf8af..7f916752 100644
--- a/tools/response_file.py
+++ b/tools/response_file.py
@@ -1,4 +1,5 @@
import tempfile, os, sys, shlex
+import shared
# Routes the given cmdline param list in args into a new response file and returns the filename to it.
# The returned filename has a suffix '.rsp'.
@@ -9,6 +10,11 @@ def create_response_file(args, directory):
args = map(lambda p: p.replace('\\', '\\\\').replace('"', '\\"'), args)
response_fd.write('"' + '" "'.join(args) + '"')
response_fd.close()
+
+ # Register the created .rsp file to be automatically cleaned up once this process finishes, so that
+ # caller does not have to remember to do it.
+ shared.configuration.get_temp_files().note(response_filename)
+
return response_filename
# Reads a response file, and returns the list of cmdline params found in the file.
diff --git a/tools/shared.py b/tools/shared.py
index 862e1564..03a22f78 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -3,7 +3,7 @@ from subprocess import Popen, PIPE, STDOUT
from tempfile import mkstemp
from distutils.spawn import find_executable
import jsrun, cache, tempfiles
-from response_file import create_response_file
+import response_file
import logging, platform
def listify(x):
@@ -41,8 +41,8 @@ class WindowsPopen:
# emscripten.py supports reading args from a response file instead of cmdline.
# Use .rsp to avoid cmdline length limitations on Windows.
if len(args) >= 2 and args[1].endswith("emscripten.py"):
- self.response_filename = create_response_file(args[2:], TEMP_DIR)
- args = args[0:2] + ['@' + self.response_filename]
+ response_filename = response_file.create_response_file(args[2:], TEMP_DIR)
+ args = args[0:2] + ['@' + response_filename]
try:
# Call the process with fixed streams.
@@ -78,13 +78,6 @@ class WindowsPopen:
def kill(self):
return self.process.kill()
- def __del__(self):
- try:
- # Clean up the temporary response file that was used to spawn this process, so that we don't leave temp files around.
- tempfiles.try_delete(self.response_filename)
- except:
- pass # Mute all exceptions in dtor, particularly if we didn't use a response file, self.response_filename doesn't exist.
-
__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
def path_from_root(*pathelems):
return os.path.join(__rootpath__, *pathelems)
@@ -1214,7 +1207,13 @@ class Building:
# Run Emscripten
Settings.RELOOPER = Cache.get_path('relooper.js')
settings = Settings.serialize()
- compiler_output = jsrun.timeout_run(Popen([PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE), None, 'Compiling')
+ args = settings + extra_args
+ if WINDOWS:
+ args = ['@' + response_file.create_response_file(args, TEMP_DIR)]
+ cmdline = [PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + args
+ if jsrun.TRACK_PROCESS_SPAWNS:
+ logging.info('Executing emscripten.py compiler with cmdline "' + ' '.join(cmdline) + '"')
+ compiler_output = jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling')
#print compiler_output
# Detect compilation crashes and errors
@@ -1511,6 +1510,28 @@ class JS:
return ident.replace('%', '$').replace('@', '_')
@staticmethod
+ def make_initializer(sig, settings=None):
+ settings = settings or Settings
+ if sig == 'i':
+ return '0'
+ elif sig == 'f' and settings.get('PRECISE_F32'):
+ return 'Math_fround(0)'
+ else:
+ return '+0'
+
+ @staticmethod
+ def make_coercion(value, sig, settings=None):
+ settings = settings or Settings
+ if sig == 'i':
+ return value + '|0'
+ elif sig == 'f' and settings.get('PRECISE_F32'):
+ return 'Math_fround(' + value + ')'
+ elif sig == 'd' or sig == 'f':
+ return '+' + value
+ else:
+ return value
+
+ @staticmethod
def make_extcall(sig, named=True):
args = ','.join(['a' + str(i) for i in range(1, len(sig))])
args = 'index' + (',' if args else '') + args