diff options
-rw-r--r-- | AUTHORS | 2 | ||||
-rwxr-xr-x | emcc | 60 | ||||
-rwxr-xr-x | emscripten.py | 12 | ||||
-rw-r--r-- | src/jsifier.js | 12 | ||||
-rw-r--r-- | src/library.js | 2 | ||||
-rw-r--r-- | src/library_browser.js | 7 | ||||
-rw-r--r-- | src/modules.js | 16 | ||||
-rw-r--r-- | src/parseTools.js | 13 | ||||
-rw-r--r-- | src/preamble.js | 6 | ||||
-rw-r--r-- | src/settings.js | 5 | ||||
-rwxr-xr-x | tests/fuzz/creduce_tester.py | 53 | ||||
-rwxr-xr-x | tests/fuzz/csmith_driver.py | 36 | ||||
-rw-r--r-- | tests/glbook/CH13_ParticleSystem.png | bin | 5106 -> 4921 bytes | |||
-rwxr-xr-x | tests/runner.py | 75 | ||||
-rw-r--r-- | tools/js_optimizer.py | 18 | ||||
-rw-r--r-- | tools/shared.py | 74 |
16 files changed, 304 insertions, 87 deletions
@@ -50,3 +50,5 @@ 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) + @@ -1,4 +1,5 @@ #!/usr/bin/env python2 +# -*- Mode: python -*- ''' emcc - compiler helper script @@ -118,6 +119,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() @@ -1054,29 +1077,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 +1112,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 diff --git a/emscripten.py b/emscripten.py index b3e153c1..dc5d5f5b 100755 --- a/emscripten.py +++ b/emscripten.py @@ -43,7 +43,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 +131,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() @@ -160,12 +160,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 +317,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..ff43c8c6 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,8 @@ 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) { + callIdent = '(tempInt=' + callIdent + ',tempInt < 0 || tempInt >= FUNCTION_TABLE.length-1 ? abort("dyncall error") : tempInt)'; } callIdent = Functions.getTable(sig) + '[' + callIdent + ']'; } @@ -1567,9 +1569,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..d0f73fdd 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; 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/modules.js b/src/modules.js index 695abbe7..7f8a959b 100644 --- a/src/modules.js +++ b/src/modules.js @@ -330,6 +330,22 @@ var Functions = { } } } + if (SAFE_DYNCALLS) { + assert(!ASM_JS, 'cannot emit safe dyncalls in asm'); + for (var j = 0; j < table.length; j++) { + if (table[j] == 0) { + table[j] = "function() { abort('dyncall error') }"; + } + } + } + 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. 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..a7731e7f 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -742,17 +742,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/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 Binary files differindex ff9c3496..4b69414f 100644 --- a/tests/glbook/CH13_ParticleSystem.png +++ b/tests/glbook/CH13_ParticleSystem.png diff --git a/tests/runner.py b/tests/runner.py index c356d218..d4ec2501 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' 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): @@ -10520,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() @@ -10570,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 @@ -10585,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/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 ebf803da..1d189cc6 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -294,7 +294,7 @@ except: CANONICAL_TEMP_DIR = os.path.join(TEMP_DIR, 'emscripten_temp') EMSCRIPTEN_TEMP_DIR = None -DEBUG = os.environ.get('EMCC_DEBUG') +DEBUG = int(os.environ.get('EMCC_DEBUG') or 0) if DEBUG: try: EMSCRIPTEN_TEMP_DIR = CANONICAL_TEMP_DIR @@ -554,7 +554,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 @@ -563,6 +563,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: @@ -749,12 +750,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 @@ -807,7 +812,26 @@ 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 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) + response_fh = os.fdopen(response_fd, 'w') + for arg in actual_files: + response_fh.write(arg + "\n") + response_fh.close() + link_cmd = [LLVM_LINK, "@" + response_file, '-o', target] + + 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) @@ -866,8 +890,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: @@ -888,6 +918,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 @@ -1088,24 +1119,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': @@ -1181,6 +1194,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 @@ -1269,9 +1286,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 @@ -1334,6 +1353,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] |