summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS3
-rwxr-xr-xemar2
-rwxr-xr-xemcc91
-rwxr-xr-xemscripten.py29
-rw-r--r--src/jsifier.js13
-rw-r--r--src/library.js71
-rw-r--r--src/library_browser.js7
-rw-r--r--src/library_gl.js46
-rw-r--r--src/library_sdl.js2
-rw-r--r--src/modules.js11
-rw-r--r--src/parseTools.js13
-rw-r--r--src/preamble.js16
-rw-r--r--src/settings.js5
-rw-r--r--tests/cubegeom.c6
-rwxr-xr-xtests/fuzz/creduce_tester.py53
-rwxr-xr-xtests/fuzz/csmith_driver.py36
-rw-r--r--tests/glbook/CH13_ParticleSystem.pngbin5106 -> 4921 bytes
-rwxr-xr-xtests/runner.py87
-rw-r--r--tests/websockets.c5
-rw-r--r--tools/js_optimizer.py18
-rw-r--r--tools/shared.py100
21 files changed, 481 insertions, 133 deletions
diff --git a/AUTHORS b/AUTHORS
index c157c468..296d4150 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -50,3 +50,6 @@ a license to everyone to use it as detailed in LICENSE.)
* Bruce Mitchener, Jr. <bruce.mitchener@gmail.com>
* Michael Bishop <mbtyke@gmail.com>
* Roger Braun <roger@rogerbraun.net>
+* Vladimir Vukicevic <vladimir@pobox.com> (copyright owned by Mozilla Foundation)
+* Lorant Pinter <lorant.pinter@prezi.com>
+
diff --git a/emar b/emar
index 60498b8f..5646f444 100755
--- a/emar
+++ b/emar
@@ -11,6 +11,8 @@ import os, subprocess, sys
from tools import shared
DEBUG = os.environ.get('EMCC_DEBUG')
+if DEBUG == "0":
+ DEBUG = None
newargs = [shared.LLVM_AR] + sys.argv[1:]
diff --git a/emcc b/emcc
index 64c65d47..cb60e881 100755
--- a/emcc
+++ b/emcc
@@ -1,4 +1,5 @@
#!/usr/bin/env python2
+# -*- Mode: python -*-
'''
emcc - compiler helper script
@@ -90,7 +91,10 @@ LLVM_OPT_LEVEL = {
3: 3,
}
-DEBUG = int(os.environ.get('EMCC_DEBUG') or 0)
+DEBUG = os.environ.get('EMCC_DEBUG')
+if DEBUG == "0":
+ DEBUG = None
+
TEMP_DIR = os.environ.get('EMCC_TEMP_DIR')
LEAVE_INPUTS_RAW = os.environ.get('EMCC_LEAVE_INPUTS_RAW') # Do not compile .ll files into .bc, just compile them with emscripten directly
# Not recommended, this is mainly for the test runner, or if you have some other
@@ -118,6 +122,28 @@ if len(sys.argv) == 1:
print 'emcc: no input files'
exit(1)
+# read response files very early on
+response_file = True
+while response_file:
+ response_file = None
+ for index in range(1, len(sys.argv)):
+ if sys.argv[index][0] == '@':
+ # found one, loop again next time
+ print >>sys.stderr, 'emcc: using response file: %s' % response_file
+ response_file = sys.argv[index][1:]
+ if not os.path.exists(response_file):
+ print >>sys.stderr, 'emcc: error: Response file not found: %s' % response_file
+ exit(1)
+
+ response_fd = open(response_file, 'r')
+ extra_args = shlex.split(response_fd.read())
+ response_fd.close()
+
+ # slice in extra_args in place of the response file arg
+ sys.argv[index:index+1] = extra_args
+ #if DEBUG: print >>sys.stderr, "Expanded response file: " + " | ".join(sys.argv)
+ break
+
if sys.argv[1] == '--version':
revision = '(unknown revision)'
here = os.getcwd()
@@ -374,6 +400,25 @@ Options that are modified or new in %s include:
for a later incremental build (where you also
enable it) to be sped up.
+ Caching works separately on 4 parts of compilation:
+ 'pre' which is types and global variables; that
+ information is then fed into 'funcs' which are
+ the functions (which we parallelize), and then
+ 'post' which adds final information based on
+ the functions (e.g., do we need long64 support
+ code). Finally, 'jsfuncs' are JavaScript-level
+ optimizations. Each of the 4 parts can be cached
+ separately, but note that they can affect each
+ other: If you recompile a single C++ file that
+ changes a global variable - e.g., adds, removes
+ or modifies a global variable, say by adding
+ a printf or by adding a compile-time timestamp,
+ then 'pre' cannot be loaded from the cache. And
+ since 'pre's output is sent to 'funcs' and 'post',
+ they will get invalidated as well, and only
+ 'jsfuncs' will be cached. So avoid modifying
+ globals to let caching work fully.
+
--clear-cache Manually clears the cache of compiled
emscripten system libraries (libc++,
libc++abi, libc). This is normally
@@ -1054,29 +1099,32 @@ try:
libcxxabi_symbols = filter(lambda symbol: symbol not in libc_symbols, libcxxabi_symbols)
libcxxabi_symbols = set(libcxxabi_symbols)
- force = False # If we have libcxx, we must force inclusion of libc, since libcxx uses new internally. Note: this is kind of hacky
-
+ # If we have libcxx, we must force inclusion of libc, since libcxx uses new internally. Note: this is kind of hacky
+ # Settings this in the environment will avoid checking dependencies and make building big projects a little faster
+ force = os.environ.get('EMCC_FORCE_STDLIBS')
+ has = need = None
for name, create, fix, library_symbols in [('libcxx', create_libcxx, fix_libcxx, libcxx_symbols),
('libcxxabi', create_libcxxabi, fix_libcxxabi, libcxxabi_symbols),
('libc', create_libc, fix_libc, libc_symbols)]:
- need = set()
- has = set()
- for temp_file in temp_files:
- symbols = shared.Building.llvm_nm(temp_file)
- for library_symbol in library_symbols:
- if library_symbol in symbols.undefs:
- need.add(library_symbol)
- if library_symbol in symbols.defs:
- has.add(library_symbol)
- for haz in has: # remove symbols that are supplied by another of the inputs
- if haz in need:
- need.remove(haz)
- if DEBUG: print >> sys.stderr, 'emcc: considering including %s: we need %s and have %s' % (name, str(need), str(has))
+ if not force:
+ need = set()
+ has = set()
+ for temp_file in temp_files:
+ symbols = shared.Building.llvm_nm(temp_file)
+ for library_symbol in library_symbols:
+ if library_symbol in symbols.undefs:
+ need.add(library_symbol)
+ if library_symbol in symbols.defs:
+ has.add(library_symbol)
+ for haz in has: # remove symbols that are supplied by another of the inputs
+ if haz in need:
+ need.remove(haz)
+ if DEBUG: print >> sys.stderr, 'emcc: considering including %s: we need %s and have %s' % (name, str(need), str(has))
if force or len(need) > 0:
# We need to build and link the library in
if DEBUG: print >> sys.stderr, 'emcc: including %s' % name
libfile = shared.Cache.get(name, create)
- if len(has) > 0:
+ if has and len(has) > 0:
# remove the symbols we do not need
fixed = in_temp(uniquename(libfile)) + '.bc'
shutil.copyfile(libfile, fixed)
@@ -1086,7 +1134,7 @@ try:
libfile = fixed
extra_files_to_link.append(libfile)
force = True
- if fix:
+ if fix and need:
fix(need)
# First, combine the bitcode files if there are several. We must also link if we have a singleton .a
@@ -1094,7 +1142,10 @@ try:
(not LEAVE_INPUTS_RAW and not (suffix(temp_files[0]) in BITCODE_SUFFIXES or suffix(temp_files[0]) in DYNAMICLIB_SUFFIXES) and shared.Building.is_ar(temp_files[0])):
linker_inputs = temp_files + extra_files_to_link
if DEBUG: print >> sys.stderr, 'emcc: linking: ', linker_inputs
+ t0 = time.time()
shared.Building.link(linker_inputs, in_temp(target_basename + '.bc'))
+ t1 = time.time()
+ if DEBUG: print >> sys.stderr, 'emcc: linking took %.2f seconds' % (t1 - t0)
final = in_temp(target_basename + '.bc')
else:
if not LEAVE_INPUTS_RAW:
@@ -1212,7 +1263,7 @@ try:
def flush_js_optimizer_queue():
global final, js_optimizer_queue
if len(js_optimizer_queue) > 0 and not(len(js_optimizer_queue) == 1 and js_optimizer_queue[0] == 'last'):
- if DEBUG < 2:
+ if DEBUG != '2':
if shared.Settings.ASM_JS:
js_optimizer_queue = ['asm'] + js_optimizer_queue
if DEBUG: print >> sys.stderr, 'emcc: applying js optimization passes:', js_optimizer_queue
@@ -1231,7 +1282,7 @@ try:
if opt_level >= 1:
if DEBUG: print >> sys.stderr, 'emcc: running pre-closure post-opts'
- if DEBUG >= 2:
+ if DEBUG == '2':
# Clean up the syntax a bit
final = shared.Building.js_optimizer(final, [], jcache)
if DEBUG: save_intermediate('pretty')
diff --git a/emscripten.py b/emscripten.py
index b3e153c1..704e2b25 100755
--- a/emscripten.py
+++ b/emscripten.py
@@ -21,6 +21,9 @@ WARNING: You should normally never use this! Use emcc instead.
from tools import shared
DEBUG = os.environ.get('EMCC_DEBUG')
+if DEBUG == "0":
+ DEBUG = None
+DEBUG_CACHE = DEBUG and "cache" in DEBUG
__rootpath__ = os.path.abspath(os.path.dirname(__file__))
def path_from_root(*pathelems):
@@ -43,7 +46,7 @@ def scan(ll, settings):
if len(blockaddrs) > 0:
settings['NECESSARY_BLOCKADDRS'] = blockaddrs
-NUM_CHUNKS_PER_CORE = 5
+NUM_CHUNKS_PER_CORE = 1.25
MIN_CHUNK_SIZE = 1024*1024
MAX_CHUNK_SIZE = float(os.environ.get('EMSCRIPT_MAX_CHUNK_SIZE') or 'inf') # configuring this is just for debugging purposes
@@ -131,7 +134,7 @@ def emscript(infile, settings, outfile, libraries=[]):
settings_file = temp_files.get('.txt').name
def save_settings():
global settings_text
- settings_text = json.dumps(settings)
+ settings_text = json.dumps(settings, sort_keys=True)
s = open(settings_file, 'w')
s.write(settings_text)
s.close()
@@ -145,7 +148,21 @@ def emscript(infile, settings, outfile, libraries=[]):
if jcache:
keys = [pre_input, settings_text, ','.join(libraries)]
shortkey = shared.JCache.get_shortkey(keys)
+ if DEBUG_CACHE: print >>sys.stderr, 'shortkey', shortkey
+
out = shared.JCache.get(shortkey, keys)
+
+ if DEBUG_CACHE and not out:
+ dfpath = os.path.join(shared.TEMP_DIR, "ems_" + shortkey)
+ dfp = open(dfpath, 'w')
+ dfp.write(pre_input);
+ dfp.write("\n\n========================== settings_text\n\n");
+ dfp.write(settings_text);
+ dfp.write("\n\n========================== libraries\n\n");
+ dfp.write("\n".join(libraries))
+ dfp.close()
+ print >>sys.stderr, ' cache miss, key data dumped to %s' % dfpath
+
if out and DEBUG: print >> sys.stderr, ' loading pre from jcache'
if not out:
open(pre_file, 'w').write(pre_input)
@@ -160,12 +177,12 @@ def emscript(infile, settings, outfile, libraries=[]):
# Phase 2 - func
- cores = multiprocessing.cpu_count()
+ cores = int(os.environ.get('EMCC_CORES') or multiprocessing.cpu_count())
assert cores >= 1
if cores > 1:
- intended_num_chunks = cores * NUM_CHUNKS_PER_CORE
+ intended_num_chunks = int(round(cores * NUM_CHUNKS_PER_CORE))
chunk_size = max(MIN_CHUNK_SIZE, total_ll_size / intended_num_chunks)
- chunk_size += 3*len(meta) # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task)
+ chunk_size += 3*len(meta) + len(forwarded_data)/3 # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) and forwarded data (less expensive but potentially significant)
chunk_size = min(MAX_CHUNK_SIZE, chunk_size)
else:
chunk_size = MAX_CHUNK_SIZE # if 1 core, just use the max chunk size
@@ -317,7 +334,7 @@ def emscript(infile, settings, outfile, libraries=[]):
params = ','.join(['p%d' % p for p in range(len(sig)-1)])
coercions = ';'.join(['p%d = %sp%d%s' % (p, '+' if sig[p+1] != 'i' else '', p, '' if sig[p+1] != 'i' else '|0') for p in range(len(sig)-1)]) + ';'
ret = '' if sig[0] == 'v' else ('return %s0' % ('+' if sig[0] != 'i' else ''))
- return ('function %s(%s) { %s abort(%d); %s };' % (bad, params, coercions, i, ret), raw.replace('[0,', '[' + bad + ',').replace(',0,', ',' + bad + ',').replace(',0,', ',' + bad + ',').replace(',0]', ',' + bad + ']').replace(',0]', ',' + bad + ']'))
+ return ('function %s(%s) { %s abort(%d); %s };' % (bad, params, coercions, i, ret), raw.replace('[0,', '[' + bad + ',').replace(',0,', ',' + bad + ',').replace(',0,', ',' + bad + ',').replace(',0]', ',' + bad + ']').replace(',0]', ',' + bad + ']').replace(',0\n', ',' + bad + '\n'))
infos = [make_table(sig, raw) for sig, raw in last_forwarded_json['Functions']['tables'].iteritems()]
function_tables_defs = '\n'.join([info[0] for info in infos] + [info[1] for info in infos])
diff --git a/src/jsifier.js b/src/jsifier.js
index 4af522b4..cb234061 100644
--- a/src/jsifier.js
+++ b/src/jsifier.js
@@ -505,7 +505,7 @@ function JSify(data, functionsOnly, givenFunctions) {
item.JS = '';
} else if (LibraryManager.library.hasOwnProperty(shortident)) {
item.JS = addFromLibrary(shortident);
- } else {
+ } else if (!LibraryManager.library.hasOwnProperty(shortident + '__inline')) {
item.JS = 'var ' + item.ident + '; // stub for ' + item.ident;
if (WARN_ON_UNDEFINED_SYMBOLS || ASM_JS) { // always warn on undefs in asm, since it breaks validation
warn('Unresolved symbol: ' + item.ident);
@@ -1401,6 +1401,9 @@ function JSify(data, functionsOnly, givenFunctions) {
if (ASM_JS) {
assert(returnType.search(/\("'\[,/) == -1); // XXX need isFunctionType(type, out)
callIdent = '(' + callIdent + ')&{{{ FTM_' + sig + ' }}}'; // the function table mask is set in emscripten.py
+ } else if (SAFE_DYNCALLS) {
+ assert(!ASM_JS, 'cannot emit safe dyncalls in asm');
+ callIdent = '(tempInt=' + callIdent + ',tempInt < 0 || tempInt >= FUNCTION_TABLE.length-1 || !FUNCTION_TABLE[tempInt] ? abort("dyncall error: ' + sig + ' " + FUNCTION_TABLE_NAMES[tempInt]) : tempInt)';
}
callIdent = Functions.getTable(sig) + '[' + callIdent + ']';
}
@@ -1567,9 +1570,11 @@ function JSify(data, functionsOnly, givenFunctions) {
var shellParts = read(shellFile).split('{{BODY}}');
print(shellParts[1]);
// Print out some useful metadata (for additional optimizations later, like the eliminator)
- print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + JSON.stringify(keys(Functions.implementedFunctions).filter(function(func) {
- return IGNORED_FUNCTIONS.indexOf(func.ident) < 0;
- })) + '\n');
+ if (EMIT_GENERATED_FUNCTIONS) {
+ print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + JSON.stringify(keys(Functions.implementedFunctions).filter(function(func) {
+ return IGNORED_FUNCTIONS.indexOf(func.ident) < 0;
+ })) + '\n');
+ }
PassManager.serialize();
diff --git a/src/library.js b/src/library.js
index 885fe1e0..1dcc57ad 100644
--- a/src/library.js
+++ b/src/library.js
@@ -4533,7 +4533,7 @@ LibraryManager.library = {
while ((i|0) < (num|0)) {
var v1 = {{{ makeGetValueAsm('p1', 'i', 'i8', true) }}};
var v2 = {{{ makeGetValueAsm('p2', 'i', 'i8', true) }}};
- if ((v1|0) != (v2|0)) return (v1|0) > (v2|0) ? 1 : -1;
+ if ((v1|0) != (v2|0)) return ((v1|0) > (v2|0) ? 1 : -1)|0;
i = (i+1)|0;
}
return 0;
@@ -6701,6 +6701,9 @@ LibraryManager.library = {
pthread_mutexattr_destroy: function() {},
pthread_mutex_lock: function() {},
pthread_mutex_unlock: function() {},
+ pthread_mutex_trylock: function() {
+ return 0;
+ },
pthread_cond_init: function() {},
pthread_cond_destroy: function() {},
pthread_cond_broadcast: function() {},
@@ -6743,11 +6746,12 @@ LibraryManager.library = {
pthread_key_create: function(key, destructor) {
if (!_pthread_key_create.keys) _pthread_key_create.keys = {};
- _pthread_key_create.keys[key] = null;
+ // values start at 0
+ _pthread_key_create.keys[key] = 0;
},
pthread_getspecific: function(key) {
- return _pthread_key_create.keys[key];
+ return _pthread_key_create.keys[key] || 0;
},
pthread_setspecific: function(key, value) {
@@ -7258,23 +7262,56 @@ LibraryManager.library = {
},
select: function(nfds, readfds, writefds, exceptfds, timeout) {
- // only readfds are supported, not writefds or exceptfds
+ // readfds are supported,
+ // writefds checks socket open status
+ // exceptfds not supported
// timeout is always 0 - fully async
- assert(!writefds && !exceptfds);
- var ret = 0;
- var l = {{{ makeGetValue('readfds', 0, 'i32') }}};
- var h = {{{ makeGetValue('readfds', 4, 'i32') }}};
- nfds = Math.min(64, nfds); // fd sets have 64 bits
- for (var fd = 0; fd < nfds; fd++) {
- var bit = fd % 32, int = fd < 32 ? l : h;
- if (int & (1 << bit)) {
- // index is in the set, check if it is ready for read
- var info = Sockets.fds[fd];
- if (!info) continue;
- if (info.hasData()) ret++;
+ assert(!exceptfds);
+
+ function canRead(info) {
+ // make sure hasData exists.
+ // we do create it when the socket is connected,
+ // but other implementations may create it lazily
+ return info.hasData && info.hasData();
+ }
+
+ function canWrite(info) {
+ // make sure socket exists.
+ // we do create it when the socket is connected,
+ // but other implementations may create it lazily
+ return info.socket && (info.socket.readyState == info.socket.OPEN);
+ }
+
+ function checkfds(nfds, fds, can) {
+ if (!fds) return 0;
+
+ var bitsSet = 0;
+ var dstLow = 0;
+ var dstHigh = 0;
+ var srcLow = {{{ makeGetValue('fds', 0, 'i32') }}};
+ var srcHigh = {{{ makeGetValue('fds', 4, 'i32') }}};
+ nfds = Math.min(64, nfds); // fd sets have 64 bits
+
+ for (var fd = 0; fd < nfds; fd++) {
+ var mask = 1 << (fd % 32), int = fd < 32 ? srcLow : srcHigh;
+ if (int & mask) {
+ // index is in the set, check if it is ready for read
+ var info = Sockets.fds[fd];
+ if (info && can(info)) {
+ // set bit
+ fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask);
+ bitsSet++;
+ }
+ }
}
+
+ {{{ makeSetValue('fds', 0, 'dstLow', 'i32') }}};
+ {{{ makeSetValue('fds', 4, 'dstHigh', 'i32') }}};
+ return bitsSet;
}
- return ret;
+
+ return checkfds(nfds, readfds, canRead)
+ + checkfds(nfds, writefds, canWrite);
},
// pty.h
diff --git a/src/library_browser.js b/src/library_browser.js
index e9396d69..5b19a360 100644
--- a/src/library_browser.js
+++ b/src/library_browser.js
@@ -204,7 +204,12 @@ mergeInto(LibraryManager.library, {
var ctx;
try {
if (useWebGL) {
- ctx = canvas.getContext('experimental-webgl', { alpha: false });
+ ctx = canvas.getContext('experimental-webgl', {
+ alpha: false,
+#if GL_TESTING
+ preserveDrawingBuffer: true
+#endif
+ });
} else {
ctx = canvas.getContext('2d');
}
diff --git a/src/library_gl.js b/src/library_gl.js
index 027b2841..995f2358 100644
--- a/src/library_gl.js
+++ b/src/library_gl.js
@@ -1016,6 +1016,7 @@ var LibraryGL = {
0x809E: 1, // GL_SAMPLE_ALPHA_TO_COVERAGE
0x80A0: 1 // GL_SAMPLE_COVERAGE
};
+
_glEnable = function(cap) {
// Clean up the renderer on any change to the rendering state. The optimization of
// skipping renderer setup is aimed at the case of multiple glDraw* right after each other
@@ -1057,6 +1058,17 @@ var LibraryGL = {
return Module.ctx.isEnabled(cap);
};
+ var glGetBooleanv = _glGetBooleanv;
+ _glGetBooleanv = function(pname, p) {
+ var attrib = GLEmulation.getAttributeFromCapability(pname);
+ if (attrib !== null) {
+ var result = GL.immediate.enabledClientAttributes[attrib];
+ {{{ makeSetValue('p', '0', 'result === true ? 1 : 0', 'i8') }}};
+ return;
+ }
+ glGetBooleanv(pname, p);
+ };
+
var glGetIntegerv = _glGetIntegerv;
_glGetIntegerv = function(pname, params) {
switch (pname) {
@@ -1411,6 +1423,22 @@ var LibraryGL = {
};
},
+ getAttributeFromCapability: function(cap) {
+ var attrib = null;
+ switch (cap) {
+ case 0x8078: // GL_TEXTURE_COORD_ARRAY
+ case 0x0de1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it
+ attrib = GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture; break;
+ case 0x8074: // GL_VERTEX_ARRAY
+ attrib = GL.immediate.VERTEX; break;
+ case 0x8075: // GL_NORMAL_ARRAY
+ attrib = GL.immediate.NORMAL; break;
+ case 0x8076: // GL_COLOR_ARRAY
+ attrib = GL.immediate.COLOR; break;
+ }
+ return attrib;
+ },
+
getProcAddress: function(name) {
name = name.replace('EXT', '').replace('ARB', '');
// Do the translation carefully because of closure
@@ -2469,22 +2497,12 @@ var LibraryGL = {
// ClientState/gl*Pointer
glEnableClientState: function(cap, disable) {
- var attrib;
- switch(cap) {
- case 0x8078: // GL_TEXTURE_COORD_ARRAY
- case 0x0de1: // GL_TEXTURE_2D - XXX not according to spec, and not in desktop GL, but works in some GLES1.x apparently, so support it
- attrib = GL.immediate.TEXTURE0 + GL.immediate.clientActiveTexture; break;
- case 0x8074: // GL_VERTEX_ARRAY
- attrib = GL.immediate.VERTEX; break;
- case 0x8075: // GL_NORMAL_ARRAY
- attrib = GL.immediate.NORMAL; break;
- case 0x8076: // GL_COLOR_ARRAY
- attrib = GL.immediate.COLOR; break;
- default:
+ var attrib = GLEmulation.getAttributeFromCapability(cap);
+ if (attrib === null) {
#if ASSERTIONS
- Module.printErr('WARNING: unhandled clientstate: ' + cap);
+ Module.printErr('WARNING: unhandled clientstate: ' + cap);
#endif
- return;
+ return;
}
if (disable && GL.immediate.enabledClientAttributes[attrib]) {
GL.immediate.enabledClientAttributes[attrib] = false;
diff --git a/src/library_sdl.js b/src/library_sdl.js
index e02e1e62..3b412daa 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -1181,8 +1181,10 @@ var LibrarySDL = {
// SDL Mixer
Mix_Init: function(flags) {
+ if (!flags) return 0;
return 8; /* MIX_INIT_OGG */
},
+ Mix_Quit: function(){},
Mix_OpenAudio: function(frequency, format, channels, chunksize) {
SDL.allocateChannels(32);
diff --git a/src/modules.js b/src/modules.js
index 695abbe7..712d8a78 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -330,12 +330,23 @@ var Functions = {
}
}
}
+ if (table.length > 20) {
+ // add some newlines in the table, for readability
+ var j = 10;
+ while (j+10 < table.length) {
+ table[j] += '\n';
+ j += 10;
+ }
+ }
var indices = table.toString().replace('"', '');
if (BUILD_AS_SHARED_LIB) {
// Shared libraries reuse the parent's function table.
tables[t] = Functions.getTable(t) + '.push.apply(' + Functions.getTable(t) + ', [' + indices + ']);\n';
} else {
tables[t] = 'var ' + Functions.getTable(t) + ' = [' + indices + '];\n';
+ if (SAFE_DYNCALLS) {
+ tables[t] += 'var FUNCTION_TABLE_NAMES = ' + JSON.stringify(table).replace(/\n/g, '').replace(/,0/g, ',0\n') + ';\n';
+ }
}
}
if (!generated && !ASM_JS) {
diff --git a/src/parseTools.js b/src/parseTools.js
index 7d1a7cd9..6e0d6e32 100644
--- a/src/parseTools.js
+++ b/src/parseTools.js
@@ -103,6 +103,11 @@ function isNiceIdent(ident, loose) {
}
}
+function isJSVar(ident) {
+ return /^\(?[$_]?[\w$_\d ]*\)+$/.test(ident);
+
+}
+
function isStructPointerType(type) {
// This test is necessary for clang - in llvm-gcc, we
// could check for %struct. The downside is that %1 can
@@ -988,7 +993,8 @@ function getHeapOffset(offset, type, forceAsm) {
if (shifts != 0) {
return '(' + offset + '>>' + shifts + ')';
} else {
- return offset;
+ // we need to guard against overflows here, HEAP[U]8 expects a guaranteed int
+ return isJSVar(offset) ? offset : '(' + offset + '|0)';
}
}
}
@@ -1393,7 +1399,8 @@ function getFastValue(a, op, b, type) {
if (!(type in Runtime.FLOAT_TYPES)) {
// if guaranteed small enough to not overflow into a double, do a normal multiply
var bits = getBits(type) || 32; // default is 32-bit multiply for things like getelementptr indexes
- if ((isNumber(a) && Math.abs(a) < TWO_TWENTY) || (isNumber(b) && Math.abs(b) < TWO_TWENTY) || bits < 32) {
+ // Note that we can emit simple multiple in non-asm.js mode, but asm.js will not parse "16-bit" multiple, so must do imul there
+ if ((isNumber(a) && Math.abs(a) < TWO_TWENTY) || (isNumber(b) && Math.abs(b) < TWO_TWENTY) || (bits < 32 && !ASM_JS)) {
return '(((' + a + ')*(' + b + '))&' + ((Math.pow(2, bits)-1)|0) + ')'; // keep a non-eliminatable coercion directly on this
}
return 'Math.imul(' + a + ',' + b + ')';
@@ -1541,7 +1548,7 @@ function makePointer(slab, pos, allocator, type, ptr) {
var ret = '';
var index = 0;
while (index < array.length) {
- ret = (ret ? ret + '.concat(' : '') + '[' + array.slice(index, index + chunkSize).map(JSON.stringify) + ']' + (ret ? ')' : '');
+ ret = (ret ? ret + '.concat(' : '') + '[' + array.slice(index, index + chunkSize).map(JSON.stringify) + ']' + (ret ? ')\n' : '');
index += chunkSize;
}
return ret;
diff --git a/src/preamble.js b/src/preamble.js
index 05269c6e..6f4b49de 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -447,7 +447,7 @@ function allocate(slab, types, allocator, ptr) {
}
#endif
- var i = 0, type;
+ var i = 0, type, typeSize, previousType;
while (i < size) {
var curr = slab[i];
@@ -469,7 +469,13 @@ function allocate(slab, types, allocator, ptr) {
#endif
setValue(ret+i, curr, type);
- i += Runtime.getNativeTypeSize(type);
+
+ // no need to look up size unless type changes, so cache it
+ if (previousType !== type) {
+ typeSize = Runtime.getNativeTypeSize(type);
+ previousType = type;
+ }
+ i += typeSize;
}
return ret;
@@ -742,17 +748,19 @@ Module['writeArrayToMemory'] = writeArrayToMemory;
{{{ unSign }}}
{{{ reSign }}}
-if (!Math.imul) Math.imul = function(a, b) {
#if PRECISE_I32_MUL
+if (!Math.imul) Math.imul = function(a, b) {
var ah = a >>> 16;
var al = a & 0xffff;
var bh = b >>> 16;
var bl = b & 0xffff;
return (al*bl + ((ah*bl + al*bh) << 16))|0;
+};
#else
+Math.imul = function(a, b) {
return (a*b)|0; // fast but imprecise
-#endif
};
+#endif
// A counter of dependencies for calling run(). If we need to
// do asynchronous work before running, increment this and
diff --git a/src/settings.js b/src/settings.js
index df9da926..308afddc 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -128,6 +128,8 @@ var SAFE_HEAP = 0; // Check each write to the heap, for example, this will give
// that 3 is the option you usually want here.
var SAFE_HEAP_LOG = 0; // Log out all SAFE_HEAP operations
+var SAFE_DYNCALLS = 0; // Show stack traces on missing function pointer/virtual method calls
+
var ASM_HEAP_LOG = 0; // Simple heap logging, like SAFE_HEAP_LOG but cheaper, and in asm.js
var CORRUPTION_CHECK = 0; // When enabled, will emit a buffer area at the beginning and
@@ -158,6 +160,7 @@ var SOCKET_DEBUG = 0; // Log out socket/network data transfer.
var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime
// option, in this case GL.debug.
+var GL_TESTING = 0; // When enabled, sets preserveDrawingBuffer in the context, to allow tests to work (but adds overhead)
var GL_MAX_TEMP_BUFFER_SIZE = 2097152; // How large GL emulation temp buffers are
var GL_UNSAFE_OPTS = 1; // Enables some potentially-unsafe optimizations in GL emulation code
@@ -326,6 +329,8 @@ var EXPLICIT_ZEXT = 0; // If 1, generate an explicit conversion of zext i1 to i3
var NECESSARY_BLOCKADDRS = []; // List of (function, block) for all block addresses that are taken.
+var EMIT_GENERATED_FUNCTIONS = 0; // whether to emit the list of generated functions, needed for external JS optimization passes
+
// Compiler debugging options
var DEBUG_TAGS_SHOWING = [];
// Some useful items:
diff --git a/tests/cubegeom.c b/tests/cubegeom.c
index 6158b124..787b8373 100644
--- a/tests/cubegeom.c
+++ b/tests/cubegeom.c
@@ -45,6 +45,8 @@ void verify() {
int main(int argc, char *argv[])
{
+ int temp; // testing
+
SDL_Surface *screen;
if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) {
printf("Unable to initialize SDL: %s\n", SDL_GetError());
@@ -124,7 +126,9 @@ int main(int argc, char *argv[])
glActiveTexture(GL_TEXTURE0);
+ glGetBooleanv(GL_VERTEX_ARRAY, &temp); assert(!temp);
glEnableClientState(GL_VERTEX_ARRAY);
+ glGetBooleanv(GL_VERTEX_ARRAY, &temp); assert(temp);
GLuint arrayBuffer, elementBuffer;
glGenBuffers(1, &arrayBuffer);
@@ -207,7 +211,6 @@ int main(int argc, char *argv[])
glNormalPointer(GL_BYTE, 32, (void*)12);
glColorPointer(4, GL_UNSIGNED_BYTE, 32, (void*)28);
- int temp; // test glGetPointerv, glGetIntegerv
glGetPointerv(GL_VERTEX_ARRAY_POINTER, &temp); assert(temp == 0);
glGetPointerv(GL_COLOR_ARRAY_POINTER, &temp); assert(temp == 28);
glGetPointerv(GL_TEXTURE_COORD_ARRAY_POINTER, &temp); assert(temp == 16);
@@ -220,6 +223,7 @@ int main(int argc, char *argv[])
glGetIntegerv(GL_TEXTURE_COORD_ARRAY_SIZE, &temp); assert(temp == 2);
glGetIntegerv(GL_TEXTURE_COORD_ARRAY_TYPE, &temp); assert(temp == GL_FLOAT);
glGetIntegerv(GL_TEXTURE_COORD_ARRAY_STRIDE, &temp); assert(temp == 32);
+ glGetBooleanv(GL_VERTEX_ARRAY, &temp); assert(temp);
glBindTexture(GL_TEXTURE_2D, texture); // diffuse?
glActiveTexture(GL_TEXTURE0);
diff --git a/tests/fuzz/creduce_tester.py b/tests/fuzz/creduce_tester.py
new file mode 100755
index 00000000..c3460e9d
--- /dev/null
+++ b/tests/fuzz/creduce_tester.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+
+'''
+Runs csmith, a C fuzzer, and looks for bugs
+'''
+
+import os, sys, difflib
+from subprocess import Popen, PIPE, STDOUT
+
+sys.path += [os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'tools')]
+import shared
+
+filename = sys.argv[1]
+print 'testing file', filename
+
+print '2) Compile natively'
+shared.try_delete(filename)
+shared.execute([shared.CLANG_CC, '-O2', filename + '.c', '-o', filename] + CSMITH_CFLAGS, stderr=PIPE)
+assert os.path.exists(filename)
+print '3) Run natively'
+try:
+ correct = shared.timeout_run(Popen([filename], stdout=PIPE, stderr=PIPE), 3)
+except Exception, e:
+ print 'Failed or infinite looping in native, skipping', e
+ notes['invalid'] += 1
+ os.exit(0) # boring
+
+print '4) Compile JS-ly and compare'
+
+def try_js(args):
+ shared.try_delete(filename + '.js')
+ shared.execute([shared.EMCC, '-O2', '-s', 'ASM_JS=1', '-s', 'PRECISE_I64_MATH=1', '-s', 'PRECISE_I32_MUL=1', filename + '.c', '-o', filename + '.js'] + CSMITH_CFLAGS + args, stderr=PIPE)
+ assert os.path.exists(filename + '.js')
+ js = shared.run_js(filename + '.js', stderr=PIPE, engine=engine1)
+ assert correct == js, ''.join([a.rstrip()+'\n' for a in difflib.unified_diff(correct.split('\n'), js.split('\n'), fromfile='expected', tofile='actual')])
+
+# Try normally, then try unaligned because csmith does generate nonportable code that requires x86 alignment
+ok = False
+normal = True
+for args, note in [([], None), (['-s', 'UNALIGNED_MEMORY=1'], 'unaligned')]:
+ try:
+ try_js(args)
+ ok = True
+ if note:
+ notes[note] += 1
+ break
+ except Exception, e:
+ print e
+ normal = False
+if not ok: sys.exit(1)
+
+sys.exit(0) # boring
+
diff --git a/tests/fuzz/csmith_driver.py b/tests/fuzz/csmith_driver.py
index b893436a..1cb85451 100755
--- a/tests/fuzz/csmith_driver.py
+++ b/tests/fuzz/csmith_driver.py
@@ -10,22 +10,27 @@ from subprocess import Popen, PIPE, STDOUT
sys.path += [os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'tools')]
import shared
+engine1 = eval('shared.' + sys.argv[1]) if len(sys.argv) > 1 else shared.JS_ENGINES[0]
+engine2 = eval('shared.' + sys.argv[2]) if len(sys.argv) > 2 else None
+
+print 'testing js engines', engine1, engine2
+
CSMITH = os.path.expanduser('~/Dev/csmith/src/csmith')
CSMITH_CFLAGS = ['-I' + os.path.expanduser('~/Dev/csmith/runtime/')]
filename = os.path.join(shared.CANONICAL_TEMP_DIR, 'fuzzcode')
-shared.DEFAULT_TIMEOUT = 3
+shared.DEFAULT_TIMEOUT = 1
tried = 0
-notes = { 'invalid': 0, 'unaligned': 0 }
+notes = { 'invalid': 0, 'unaligned': 0, 'embug': 0 }
while 1:
print 'Tried %d, notes: %s' % (tried, notes)
tried += 1
print '1) Generate C'
- shared.execute([CSMITH, '--no-volatiles', '--no-math64', '--max-block-depth', '2', '--max-block-size', '2', '--max-expr-complexity', '2', '--max-funcs', '2'], stdout=open(filename + '.c', 'w'))
+ shared.execute([CSMITH, '--no-volatiles', '--no-math64'], stdout=open(filename + '.c', 'w'))
print '2) Compile natively'
shared.try_delete(filename)
@@ -45,11 +50,12 @@ while 1:
shared.try_delete(filename + '.js')
shared.execute([shared.EMCC, '-O2', '-s', 'ASM_JS=1', '-s', 'PRECISE_I64_MATH=1', '-s', 'PRECISE_I32_MUL=1', filename + '.c', '-o', filename + '.js'] + CSMITH_CFLAGS + args, stderr=PIPE)
assert os.path.exists(filename + '.js')
- js = shared.run_js(filename + '.js', stderr=PIPE) #, engine=...)
+ js = shared.run_js(filename + '.js', stderr=PIPE, engine=engine1, check_timeout=True)
assert correct == js, ''.join([a.rstrip()+'\n' for a in difflib.unified_diff(correct.split('\n'), js.split('\n'), fromfile='expected', tofile='actual')])
# Try normally, then try unaligned because csmith does generate nonportable code that requires x86 alignment
ok = False
+ normal = True
for args, note in [([], None), (['-s', 'UNALIGNED_MEMORY=1'], 'unaligned')]:
try:
try_js(args)
@@ -59,7 +65,11 @@ while 1:
break
except Exception, e:
print e
- if not ok: break
+ normal = False
+ if not ok:
+ print "EMSCRIPTEN BUG"
+ notes['embug'] += 1
+ continue #break
#if not ok:
# try: # finally, try with safe heap. if that is triggered, this is nonportable code almost certainly
# try_js(['-s', 'SAFE_HEAP=1'])
@@ -72,3 +82,19 @@ while 1:
# else:
# break
+ # This is ok. Try in secondary JS engine too
+ if engine2 and normal:
+ try:
+ js2 = shared.run_js(filename + '.js', stderr=PIPE, engine=engine2, full_output=True, check_timeout=True)
+ except:
+ print 'failed to run in secondary', js2
+ break
+
+ # asm.js testing
+ assert 'warning: Successfully compiled asm.js code' in js2, 'must validate'
+ js2 = js2.replace('\nwarning: Successfully compiled asm.js code\n', '')
+
+ assert js2 == correct, ''.join([a.rstrip()+'\n' for a in difflib.unified_diff(correct.split('\n'), js2.split('\n'), fromfile='expected', tofile='actual')]) + 'ODIN FAIL'
+ print 'odin ok'
+
+
diff --git a/tests/glbook/CH13_ParticleSystem.png b/tests/glbook/CH13_ParticleSystem.png
index ff9c3496..4b69414f 100644
--- a/tests/glbook/CH13_ParticleSystem.png
+++ b/tests/glbook/CH13_ParticleSystem.png
Binary files differ
diff --git a/tests/runner.py b/tests/runner.py
index 8410f888..9cf40543 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -507,6 +507,8 @@ if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv) and 'brows
'''
self.do_run(src, 'hello, world!')
+ assert 'EMSCRIPTEN_GENERATED_FUNCTIONS' not in open(self.in_dir('src.cpp.o.js')).read(), 'must not emit this unneeded internal thing'
+
def test_intvars(self):
if self.emcc_args == None: return self.skip('needs ta2')
@@ -1153,6 +1155,8 @@ m_divisor is 1091269979
self.do_run(src, '3217489085')
def test_i32_mul_semiprecise(self):
+ if Settings.ASM_JS: return self.skip('asm is always fully precise')
+
Settings.PRECISE_I32_MUL = 0 # we want semiprecise here
src = r'''
@@ -2802,6 +2806,37 @@ Exiting setjmp function, level: 0, prev_jmp: -1
''' % addr
self.do_run(src, 'segmentation fault' if addr.isdigit() else 'marfoosh')
+ def test_safe_dyncalls(self):
+ if Settings.ASM_JS: return self.skip('asm does not support missing function stack traces')
+ if Settings.SAFE_HEAP: return self.skip('safe heap warning will appear instead')
+ if self.emcc_args is None: return self.skip('need libc')
+
+ Settings.SAFE_DYNCALLS = 1
+
+ for cond, body, work in [(True, True, False), (True, False, False), (False, True, True), (False, False, False)]:
+ print cond, body, work
+ src = r'''
+ #include <stdio.h>
+
+ struct Classey {
+ virtual void doIt() = 0;
+ };
+
+ struct D1 : Classey {
+ virtual void doIt() BODY;
+ };
+
+ int main(int argc, char **argv)
+ {
+ Classey *p = argc COND 100 ? new D1() : NULL;
+ printf("%p\n", p);
+ p->doIt();
+
+ return 0;
+ }
+ '''.replace('COND', '==' if cond else '!=').replace('BODY', r'{ printf("all good\n"); }' if body else '')
+ self.do_run(src, 'dyncall error: vi' if not work else 'all good')
+
def test_dynamic_cast(self):
if self.emcc_args is None: return self.skip('need libcxxabi')
@@ -7130,6 +7165,14 @@ def process(filename):
self.assertIdentical(clean(open('release.js').read()), clean(open('debug%d.js' % debug).read())) # EMCC_DEBUG=1 mode must not generate different code!
print >> sys.stderr, 'debug check %d passed too' % debug
+ try:
+ os.environ['EMCC_FORCE_STDLIBS'] = '1'
+ print 'EMCC_FORCE_STDLIBS'
+ do_test()
+ finally:
+ del os.environ['EMCC_FORCE_STDLIBS']
+ print >> sys.stderr, 'EMCC_FORCE_STDLIBS ok'
+
try_delete(CANONICAL_TEMP_DIR)
else:
print >> sys.stderr, 'not doing debug check'
@@ -9436,8 +9479,8 @@ f.close()
try:
os.environ['EMCC_DEBUG'] = '1'
for asm, linkable, chunks, js_chunks in [
- (0, 0, 3, 2), (0, 1, 7, 4),
- (1, 0, 3, 2), (1, 1, 7, 5)
+ (0, 0, 2, 2), (0, 1, 4, 4),
+ (1, 0, 2, 2), (1, 1, 4, 5)
]:
print asm, linkable, chunks, js_chunks
output, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_libcxx.cpp'), '-O1', '-s', 'LINKABLE=%d' % linkable, '-s', 'ASM_JS=%d' % asm], stdout=PIPE, stderr=PIPE).communicate()
@@ -10329,56 +10372,56 @@ elif 'browser' in str(sys.argv):
# SDL, OpenGL, textures, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-gray-purple.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_ogl.c'), '-O2', '--minify', '0', '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_ogl.c'), '-O2', '--minify', '0', '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with gray at the top.', '/report_result?0')
def test_sdl_ogl_defaultmatrixmode(self):
# SDL, OpenGL, textures, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-gray-purple.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_ogl_defaultMatrixMode.c'), '--minify', '0', '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_ogl_defaultMatrixMode.c'), '--minify', '0', '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with gray at the top.', '/report_result?0')
def test_sdl_ogl_p(self):
# Immediate mode with pointers
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-gray.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_ogl_p.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_ogl_p.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with gray at the top.', '/report_result?0')
def test_sdl_fog_simple(self):
# SDL, OpenGL, textures, fog, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-fog-simple.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_simple.c'), '-O2', '--minify', '0', '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_simple.c'), '-O2', '--minify', '0', '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with fog.', '/report_result?0')
def test_sdl_fog_negative(self):
# SDL, OpenGL, textures, fog, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-fog-negative.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_negative.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_negative.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with fog.', '/report_result?0')
def test_sdl_fog_density(self):
# SDL, OpenGL, textures, fog, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-fog-density.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_density.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_density.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with fog.', '/report_result?0')
def test_sdl_fog_exp2(self):
# SDL, OpenGL, textures, fog, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-fog-exp2.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_exp2.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_exp2.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with fog.', '/report_result?0')
def test_sdl_fog_linear(self):
# SDL, OpenGL, textures, fog, immediate mode. Closure for more coverage
shutil.copyfile(path_from_root('tests', 'screenshot.png'), os.path.join(self.get_dir(), 'screenshot.png'))
self.reftest(path_from_root('tests', 'screenshot-fog-linear.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_linear.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png']).communicate()
+ Popen([PYTHON, EMCC, path_from_root('tests', 'sdl_fog_linear.c'), '-o', 'something.html', '--pre-js', 'reftest.js', '--preload-file', 'screenshot.png', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see an image with fog.', '/report_result?0')
def test_worker(self):
@@ -10477,7 +10520,6 @@ elif 'browser' in str(sys.argv):
def chunked_server(support_byte_ranges):
class ChunkedServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
- @staticmethod
def sendheaders(s, extra=[], length=len(data)):
s.send_response(200)
s.send_header("Content-Length", str(length))
@@ -10491,11 +10533,14 @@ elif 'browser' in str(sys.argv):
s.end_headers()
def do_HEAD(s):
- ChunkedServerHandler.sendheaders(s)
-
+ s.sendheaders()
+
+ def do_OPTIONS(s):
+ s.sendheaders([("Access-Control-Allow-Headers", "Range")], 0)
+
def do_GET(s):
if not support_byte_ranges:
- ChunkedServerHandler.sendheaders(s)
+ s.sendheaders()
s.wfile.write(data)
else:
(start, end) = s.headers.get("range").split("=")[1].split("-")
@@ -10503,7 +10548,7 @@ elif 'browser' in str(sys.argv):
end = int(end)
end = min(len(data)-1, end)
length = end-start+1
- ChunkedServerHandler.sendheaders(s,[],length)
+ s.sendheaders([],length)
s.wfile.write(data[start:end+1])
s.wfile.close()
httpd = BaseHTTPServer.HTTPServer(('localhost', 11111), ChunkedServerHandler)
@@ -10518,26 +10563,26 @@ elif 'browser' in str(sys.argv):
def test_glgears(self):
self.reftest(path_from_root('tests', 'gears.png'))
Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_gles.c'), '-o', 'something.html',
- '-DHAVE_BUILTIN_SINCOS', '--pre-js', 'reftest.js']).communicate()
+ '-DHAVE_BUILTIN_SINCOS', '--pre-js', 'reftest.js', '-s', 'GL_TESTING=1']).communicate()
self.run_browser('something.html', 'You should see animating gears.', '/report_result?0')
def test_glgears_animation(self):
Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_gles.c'), '-o', 'something.html',
- '-DHAVE_BUILTIN_SINCOS',
+ '-DHAVE_BUILTIN_SINCOS', '-s', 'GL_TESTING=1',
'--shell-file', path_from_root('tests', 'hello_world_gles_shell.html')]).communicate()
self.run_browser('something.html', 'You should see animating gears.', '/report_gl_result?true')
def test_glgears_bad(self):
# Make sure that OpenGL ES is not available if typed arrays are not used
Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_gles.c'), '-o', 'something.html',
- '-DHAVE_BUILTIN_SINCOS',
+ '-DHAVE_BUILTIN_SINCOS', '-s', 'GL_TESTING=1',
'-s', 'USE_TYPED_ARRAYS=0',
'--shell-file', path_from_root('tests', 'hello_world_gles_shell.html')]).communicate()
self.run_browser('something.html', 'You should not see animating gears.', '/report_gl_result?false')
def test_glgears_deriv(self):
self.reftest(path_from_root('tests', 'gears.png'))
- Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_gles_deriv.c'), '-o', 'something.html',
+ Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_gles_deriv.c'), '-o', 'something.html', '-s', 'GL_TESTING=1',
'-DHAVE_BUILTIN_SINCOS', '--pre-js', 'reftest.js']).communicate()
self.run_browser('something.html', 'You should see animating gears.', '/report_result?0')
src = open('something.html').read()
@@ -10568,7 +10613,7 @@ elif 'browser' in str(sys.argv):
args = ['--preload-file', 'smoke.tga', '-O2'] # test optimizations and closure here as well for more coverage
self.reftest(book_path(basename.replace('.bc', '.png')))
- Popen([PYTHON, EMCC, program, '-o', 'program.html', '--pre-js', 'reftest.js'] + args).communicate()
+ Popen([PYTHON, EMCC, program, '-o', 'program.html', '--pre-js', 'reftest.js', '-s', 'GL_TESTING=1'] + args).communicate()
self.run_browser('program.html', '', '/report_result?0')
def btest(self, filename, expected=None, reference=None, reference_slack=0, args=[]): # TODO: use in all other tests
@@ -10583,7 +10628,7 @@ elif 'browser' in str(sys.argv):
expected = [str(i) for i in range(0, reference_slack+1)]
shutil.copyfile(path_from_root('tests', filename), os.path.join(self.get_dir(), filename))
self.reftest(path_from_root('tests', reference))
- args = args + ['--pre-js', 'reftest.js']
+ args = args + ['--pre-js', 'reftest.js', '-s', 'GL_TESTING=1']
Popen([PYTHON, EMCC, os.path.join(self.get_dir(), filename), '-o', 'test.html'] + args).communicate()
if type(expected) is str: expected = [expected]
self.run_browser('test.html', '.', ['/report_result?' + e for e in expected])
diff --git a/tests/websockets.c b/tests/websockets.c
index 34aa44b4..8e719baa 100644
--- a/tests/websockets.c
+++ b/tests/websockets.c
@@ -27,14 +27,19 @@ unsigned int get_all_buf(int sock, char* output, unsigned int maxsize)
assert(select(64, &sett, NULL, NULL, NULL) == 0); // empty set
FD_SET(sock, &sett);
assert(select(0, &sett, NULL, NULL, NULL) == 0); // max FD to check is 0
+ assert(FD_ISSET(sock, &sett) == 0);
+ FD_SET(sock, &sett);
int select_says_yes = select(64, &sett, NULL, NULL, NULL);
// ioctl check for IO
int bytes;
if (ioctl(sock, FIONREAD, &bytes) || bytes == 0) {
not_always_data = 1;
+ assert(FD_ISSET(sock, &sett) == 0);
return 0;
}
+
+ assert(FD_ISSET(sock, &sett));
assert(select_says_yes); // ioctl must agree with select
char buffer[1024];
diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py
index cbf64486..52cae6e5 100644
--- a/tools/js_optimizer.py
+++ b/tools/js_optimizer.py
@@ -10,7 +10,9 @@ def path_from_root(*pathelems):
JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js')
-BEST_JS_PROCESS_SIZE = 1024*1024
+NUM_CHUNKS_PER_CORE = 1.5
+MIN_CHUNK_SIZE = 1024*1024
+MAX_CHUNK_SIZE = 20*1024*1024
WINDOWS = sys.platform.startswith('win')
@@ -74,6 +76,8 @@ def run_on_js(filename, passes, js_engine, jcache):
assert gen_end > gen_start
pre = js[:gen_start]
post = js[gen_end:]
+ if 'last' in passes:
+ post = post.replace(suffix, '') # no need to write out the metadata - nothing after us needs it
js = js[gen_start:gen_end]
else:
pre = ''
@@ -98,7 +102,11 @@ def run_on_js(filename, passes, js_engine, jcache):
total_size = len(js)
js = None
- chunks = shared.JCache.chunkify(funcs, BEST_JS_PROCESS_SIZE, 'jsopt' if jcache else None)
+ cores = int(os.environ.get('EMCC_CORES') or multiprocessing.cpu_count())
+ intended_num_chunks = int(round(cores * NUM_CHUNKS_PER_CORE))
+ chunk_size = min(MAX_CHUNK_SIZE, max(MIN_CHUNK_SIZE, total_size / intended_num_chunks))
+
+ chunks = shared.JCache.chunkify(funcs, chunk_size, 'jsopt' if jcache else None)
if jcache:
# load chunks from cache where we can # TODO: ignore small chunks
@@ -134,15 +142,15 @@ def run_on_js(filename, passes, js_engine, jcache):
commands = map(lambda filename: [js_engine, JS_OPTIMIZER, filename, 'noPrintMetadata'] + passes, filenames)
#print [' '.join(command) for command in commands]
- cores = min(multiprocessing.cpu_count(), filenames)
+ cores = min(cores, filenames)
if len(chunks) > 1 and cores >= 2:
# We can parallelize
- if DEBUG: print >> sys.stderr, 'splitting up js optimization into %d chunks, using %d cores (total: %.2f MB)' % (len(chunks), cores, total_size/(1024*1024.))
+ if DEBUG: print >> sys.stderr, 'splitting up js optimization into %d chunks of size %d, using %d cores (total: %.2f MB)' % (len(chunks), chunk_size, cores, total_size/(1024*1024.))
pool = multiprocessing.Pool(processes=cores)
filenames = pool.map(run_on_chunk, commands, chunksize=1)
else:
# We can't parallize, but still break into chunks to avoid uglify/node memory issues
- if len(chunks) > 1 and DEBUG: print >> sys.stderr, 'splitting up js optimization into %d chunks' % (len(chunks))
+ if len(chunks) > 1 and DEBUG: print >> sys.stderr, 'splitting up js optimization into %d chunks of size %d' % (len(chunks), chunk_size)
filenames = [run_on_chunk(command) for command in commands]
else:
filenames = []
diff --git a/tools/shared.py b/tools/shared.py
index d391caa2..524145fa 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -298,6 +298,10 @@ CANONICAL_TEMP_DIR = os.path.join(TEMP_DIR, 'emscripten_temp')
EMSCRIPTEN_TEMP_DIR = None
DEBUG = os.environ.get('EMCC_DEBUG')
+if DEBUG == "0":
+ DEBUG = None
+DEBUG_CACHE = DEBUG and "cache" in DEBUG
+
if DEBUG:
try:
EMSCRIPTEN_TEMP_DIR = CANONICAL_TEMP_DIR
@@ -557,7 +561,7 @@ class Settings:
ret = []
for key, value in Settings.__dict__.iteritems():
if key == key.upper(): # this is a hack. all of our settings are ALL_CAPS, python internals are not
- jsoned = json.dumps(value)
+ jsoned = json.dumps(value, sort_keys=True)
ret += ['-s', key + '=' + jsoned]
return ret
@@ -566,6 +570,7 @@ class Settings:
if opt_level >= 1:
Settings.ASSERTIONS = 0
Settings.DISABLE_EXCEPTION_CATCHING = 1
+ Settings.EMIT_GENERATED_FUNCTIONS = 1
if opt_level >= 2:
Settings.RELOOP = 1
if opt_level >= 3:
@@ -752,12 +757,16 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
resolved_symbols = set()
temp_dirs = []
files = map(os.path.abspath, files)
+ has_ar = False
+ for f in files:
+ has_ar = has_ar or Building.is_ar(f)
for f in files:
if not Building.is_ar(f):
if Building.is_bitcode(f):
- new_symbols = Building.llvm_nm(f)
- resolved_symbols = resolved_symbols.union(new_symbols.defs)
- unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs)
+ if has_ar:
+ new_symbols = Building.llvm_nm(f)
+ resolved_symbols = resolved_symbols.union(new_symbols.defs)
+ unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs)
actual_files.append(f)
else:
# Extract object files from ar archives, and link according to gnu ld semantics
@@ -810,7 +819,37 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
# Finish link
actual_files = unique_ordered(actual_files) # tolerate people trying to link a.so a.so etc.
if DEBUG: print >>sys.stderr, 'emcc: llvm-linking:', actual_files
- output = Popen([LLVM_LINK] + actual_files + ['-o', target], stdout=PIPE).communicate()[0]
+
+ # check for too-long command line
+ link_cmd = [LLVM_LINK] + actual_files + ['-o', target]
+ # 8k is a bit of an arbitrary limit, but a reasonable one
+ # for max command line size before we use a respose file
+ response_file = None
+ if WINDOWS and len(' '.join(link_cmd)) > 8192:
+ if DEBUG: print >>sys.stderr, 'using response file for llvm-link'
+ [response_fd, response_file] = mkstemp(suffix='.response', dir=TEMP_DIR)
+
+ link_cmd = [LLVM_LINK, "@" + response_file]
+
+ response_fh = os.fdopen(response_fd, 'w')
+ for arg in actual_files:
+ # we can't put things with spaces in the response file
+ if " " in arg:
+ link_cmd.append(arg)
+ else:
+ response_fh.write(arg + "\n")
+ response_fh.close()
+ link_cmd.append("-o")
+ link_cmd.append(target)
+
+ if len(' '.join(link_cmd)) > 8192:
+ print >>sys.stderr, 'emcc: warning: link command line is very long, even with response file -- use paths with no spaces'
+
+ output = Popen(link_cmd, stdout=PIPE).communicate()[0]
+
+ if response_file:
+ os.unlink(response_file)
+
assert os.path.exists(target) and (output is None or 'Could not open input file' not in output), 'Linking error: ' + output
for temp_dir in temp_dirs:
try_delete(temp_dir)
@@ -869,8 +908,14 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
assert os.path.exists(output_filename), 'Could not create bc file: ' + output
return output_filename
+ nm_cache = {} # cache results of nm - it can be slow to run
+
@staticmethod
def llvm_nm(filename, stdout=PIPE, stderr=None):
+ if filename in Building.nm_cache:
+ #if DEBUG: print >> sys.stderr, 'loading nm results for %s from cache' % filename
+ return Building.nm_cache[filename]
+
# LLVM binary ==> list of symbols
output = Popen([LLVM_NM, filename], stdout=stdout, stderr=stderr).communicate()[0]
class ret:
@@ -891,6 +936,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
ret.defs = set(ret.defs)
ret.undefs = set(ret.undefs)
ret.commons = set(ret.commons)
+ Building.nm_cache[filename] = ret
return ret
@staticmethod
@@ -1091,24 +1137,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
@staticmethod
def is_bitcode(filename):
- # checks if a file contains LLVM bitcode
- # if the file doesn't exist or doesn't have valid symbols, it isn't bitcode
- try:
- defs = Building.llvm_nm(filename, stderr=PIPE)
- # If no symbols found, it might just be an empty bitcode file, try to dis it
- if len(defs.defs) + len(defs.undefs) + len(defs.commons) == 0:
- # llvm-nm 3.0 has a bug when reading symbols from ar files
- # so try to see if we're dealing with an ar file, in which
- # case we should try to dis it.
- if not Building.is_ar(filename):
- test_ll = os.path.join(EMSCRIPTEN_TEMP_DIR, 'test.ll')
- Building.llvm_dis(filename, test_ll)
- assert os.path.exists(test_ll)
- try_delete(test_ll)
- except Exception, e:
- if DEBUG: print >> sys.stderr, 'shared.Building.is_bitcode failed to test whether file \'%s\' is a llvm bitcode file! Failed on exception: %s' % (filename, e)
- return False
-
# look for magic signature
b = open(filename, 'r').read(4)
if b[0] == 'B' and b[1] == 'C':
@@ -1184,6 +1212,10 @@ class Cache:
except:
pass
try_delete(RELOOPER)
+ try:
+ open(Cache.dirname + '__last_clear', 'w').write('last clear: ' + time.asctime() + '\n')
+ except:
+ print >> sys.stderr, 'failed to save last clear time'
# Request a cached file. If it isn't in the cache, it will be created with
# the given creator function
@@ -1226,29 +1258,30 @@ class JCache:
# Returns a cached value, if it exists. Make sure the full key matches
@staticmethod
def get(shortkey, keys):
- #if DEBUG: print >> sys.stderr, 'jcache get?', shortkey
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache get?', shortkey
cachename = JCache.get_cachename(shortkey)
if not os.path.exists(cachename):
- #if DEBUG: print >> sys.stderr, 'jcache none at all'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache none at all'
return
data = cPickle.Unpickler(open(cachename, 'rb')).load()
if len(data) != 2:
- #if DEBUG: print >> sys.stderr, 'jcache error in get'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache error in get'
return
oldkeys = data[0]
if len(oldkeys) != len(keys):
- #if DEBUG: print >> sys.stderr, 'jcache collision (a)'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache collision (a)'
return
for i in range(len(oldkeys)):
if oldkeys[i] != keys[i]:
- #if DEBUG: print >> sys.stderr, 'jcache collision (b)'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache collision (b)'
return
- #if DEBUG: print >> sys.stderr, 'jcache win'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache win'
return data[1]
# Sets the cached value for a key (from get_key)
@staticmethod
def set(shortkey, keys, value):
+ if DEBUG_CACHE: print >> sys.stderr, 'save to cache', shortkey
cachename = JCache.get_cachename(shortkey)
cPickle.Pickler(open(cachename, 'wb')).dump([keys, value])
#if DEBUG:
@@ -1272,9 +1305,11 @@ class JCache:
if os.path.exists(chunking_file):
try:
previous_mapping = cPickle.Unpickler(open(chunking_file, 'rb')).load() # maps a function identifier to the chunk number it will be in
- #if DEBUG: print >> sys.stderr, 'jscache previous mapping', previous_mapping
- except:
- pass
+ if DEBUG: print >> sys.stderr, 'jscache previous mapping of size %d loaded from %s' % (len(previous_mapping), chunking_file)
+ except Exception, e:
+ print >> sys.stderr, 'Failed to load and unpickle previous chunking file at %s: ' % chunking_file, e
+ else:
+ print >> sys.stderr, 'Previous chunking file not found at %s' % chunking_file
chunks = []
if previous_mapping:
# initialize with previous chunking
@@ -1337,6 +1372,7 @@ class JCache:
assert ident not in new_mapping, 'cannot have duplicate names in jcache chunking'
new_mapping[ident] = i
cPickle.Pickler(open(chunking_file, 'wb')).dump(new_mapping)
+ if DEBUG: print >> sys.stderr, 'jscache mapping of size %d saved to %s' % (len(new_mapping), chunking_file)
#if DEBUG:
# for i in range(len(chunks)):
# chunk = chunks[i]