diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rwxr-xr-x | emcc | 25 | ||||
-rw-r--r-- | emlink.py | 268 | ||||
-rw-r--r-- | src/library.js | 28 | ||||
-rw-r--r-- | src/postamble.js | 26 | ||||
-rw-r--r-- | src/preamble.js | 67 | ||||
-rw-r--r-- | tests/qsort/benchmark.cpp | 43 | ||||
-rwxr-xr-x | tests/runner.py | 39 | ||||
-rw-r--r-- | tools/asm_module.py | 271 | ||||
-rw-r--r-- | tools/file_packager.py | 2 | ||||
-rwxr-xr-x | tools/merge_asm.py | 26 | ||||
-rw-r--r-- | tools/shared.py | 10 | ||||
-rwxr-xr-x | tools/split_asm.py | 30 |
13 files changed, 513 insertions, 323 deletions
@@ -91,4 +91,5 @@ a license to everyone to use it as detailed in LICENSE.) * Ryan Kelly (ryan@rfk.id.au) * Michael Lelli <toadking@toadking.com> * Yu Kobayashi <yukoba@accelart.jp> +* Pin Zhang <zhangpin04@gmail.com> @@ -53,6 +53,15 @@ from tools import shared, jsrun from tools.shared import Compression, execute, suffix, unsuffixed, unsuffixed_basename from tools.response_file import read_response_file +CXX_SUFFIXES = ('.cpp', '.cxx', '.cc') +SOURCE_SUFFIXES = ('.c', '.cpp', '.cxx', '.cc', '.m', '.mm') +BITCODE_SUFFIXES = ('.bc', '.o', '.obj') +DYNAMICLIB_SUFFIXES = ('.dylib', '.so', '.dll') +STATICLIB_SUFFIXES = ('.a',) +ASSEMBLY_SUFFIXES = ('.ll',) +LIB_PREFIXES = ('', 'lib') +JS_CONTAINING_SUFFIXES = ('js', 'html') + # Mapping of emcc opt levels to llvm opt levels. We use llvm opt level 3 in emcc opt # levels 2 and 3 (emcc 3 is unsafe opts, so unsuitable for the only level to get # llvm opt level 3, and speed-wise emcc level 2 is already the slowest/most optimizing @@ -532,7 +541,7 @@ if CONFIGURE_CONFIG or CMAKE_CONFIG: if debug_configure: open(tempout, 'a').write('============= ' + arg + '\n' + src + '\n=============\n\n') except: pass - if arg.endswith('.s'): + elif arg.endswith('.s'): if debug_configure: open(tempout, 'a').write('(compiling .s assembly, must use clang\n') use_js = 0 @@ -615,15 +624,6 @@ if EMMAKEN_CFLAGS: CC_ADDITIONAL_ARGS += shlex.split(EMMAKEN_CFLAGS) # ---------------- Utilities --------------- -SOURCE_SUFFIXES = ('.c', '.cpp', '.cxx', '.cc', '.m', '.mm') -BITCODE_SUFFIXES = ('.bc', '.o', '.obj') -DYNAMICLIB_SUFFIXES = ('.dylib', '.so', '.dll') -STATICLIB_SUFFIXES = ('.a',) -ASSEMBLY_SUFFIXES = ('.ll',) -LIB_PREFIXES = ('', 'lib') - -JS_CONTAINING_SUFFIXES = ('js', 'html') - seen_names = {} def uniquename(name): if name not in seen_names: @@ -787,6 +787,7 @@ try: newargs[i+1] = '' elif newargs[i].startswith('--minify'): check_bad_eq(newargs[i]) + assert newargs[i+1] == '0', '0 is the only supported option for --minify; 1 has been deprecated' debug_level = max(1, debug_level) newargs[i] = '' newargs[i+1] = '' @@ -1010,7 +1011,7 @@ try: logging.error('no input files\nnote that input files without a known suffix are ignored, make sure your input files end with one of: ' + str(SOURCE_SUFFIXES + BITCODE_SUFFIXES + DYNAMICLIB_SUFFIXES + STATICLIB_SUFFIXES + ASSEMBLY_SUFFIXES)) exit(0) - newargs += CC_ADDITIONAL_ARGS + newargs = CC_ADDITIONAL_ARGS + newargs assert not (Compression.on and final_suffix != 'html'), 'Compression only works when generating HTML' @@ -1105,6 +1106,8 @@ try: output_file = in_temp(unsuffixed(uniquename(input_file)) + '.o') temp_files.append(output_file) args = newargs + ['-emit-llvm', '-c', input_file, '-o', output_file] + if input_file.endswith(CXX_SUFFIXES): + args += shared.EMSDK_CXX_OPTS logging.debug("running:" + call + ' ' + ' '.join(args)) execute([call] + args) # let compiler frontend print directly, so colors are saved (PIPE kills that) if not os.path.exists(output_file): @@ -6,9 +6,9 @@ Fast static linker for emscripten outputs. Specifically this links asm.js module See https://github.com/kripken/emscripten/wiki/Linking ''' -import os, subprocess, sys, re +import sys from tools import shared -from tools import js_optimizer +from tools.asm_module import AsmModule try: me, main, side, out = sys.argv[:4] @@ -22,270 +22,6 @@ print 'Output:', out shared.try_delete(out) -class AsmModule(): - def __init__(self, filename): - self.filename = filename - self.js = open(filename).read() - - self.start_asm = self.js.find(js_optimizer.start_asm_marker) - self.start_funcs = self.js.find(js_optimizer.start_funcs_marker) - self.end_funcs = self.js.rfind(js_optimizer.end_funcs_marker) - self.end_asm = self.js.rfind(js_optimizer.end_asm_marker) - - # pre - self.pre_js = self.js[:self.start_asm] - - # heap initializer - self.staticbump = int(re.search(shared.JS.memory_staticbump_pattern, self.pre_js).group(1)) - if self.staticbump: - self.mem_init_js = re.search(shared.JS.memory_initializer_pattern, self.pre_js).group(0) - - # global initializers - global_inits = re.search(shared.JS.global_initializers_pattern, self.pre_js) - if global_inits: - self.global_inits_js = global_inits.group(0) - self.global_inits = map(lambda init: init.split('{')[2][1:].split('(')[0], global_inits.groups(0)[0].split(',')) - else: - self.global_inits_js = '' - self.global_inits = [] - - # imports (and global variables) - first_var = self.js.find('var ', self.js.find('var ', self.start_asm)+4) - self.pre_imports_js = self.js[self.start_asm:first_var] - self.imports_js = self.js[first_var:self.start_funcs] - self.imports = {} - for imp in js_optimizer.import_sig.finditer(self.imports_js): - key, value = imp.group(0).split('var ')[1][:-1].split('=', 1) - self.imports[key.strip()] = value.strip() - #print >> sys.stderr, 'imports', self.imports - - # funcs - self.funcs_js = self.js[self.start_funcs:self.end_funcs] - self.funcs = set([m.group(2) for m in js_optimizer.func_sig.finditer(self.funcs_js)]) - #print 'funcs', self.funcs - - # tables and exports - post_js = self.js[self.end_funcs:self.end_asm] - ret = post_js.find('return') - self.tables_js = post_js[:ret] - self.exports_js = post_js[ret:] - self.tables = self.parse_tables(self.tables_js) - self.exports = set([export.strip() for export in self.exports_js[self.exports_js.find('{')+1:self.exports_js.find('}')].split(',')]) - - # post - self.post_js = self.js[self.end_asm:] - self.sendings = {} - for sending in [sending.strip() for sending in self.post_js[self.post_js.find('}, { ')+5:self.post_js.find(' }, buffer);')].split(',')]: - colon = sending.find(':') - self.sendings[sending[:colon].replace('"', '')] = sending[colon+1:].strip() - self.module_defs = set(re.findall('var [\w\d_$]+ = Module\["[\w\d_$]+"\] = asm\["[\w\d_$]+"\];\n', self.post_js)) - - def relocate_into(self, main): - # heap initializer - if self.staticbump > 0: - new_mem_init = self.mem_init_js[:self.mem_init_js.rfind(', ')] + ', Runtime.GLOBAL_BASE+%d)' % main.staticbump - main.pre_js = re.sub(shared.JS.memory_staticbump_pattern, 'STATICTOP = STATIC_BASE + %d;\n' % (main.staticbump + side.staticbump) + new_mem_init, main.pre_js, count=1) - - # Find function name replacements TODO: do not rename duplicate names with duplicate contents, just merge them - replacements = {} - for func in self.funcs: - rep = func - while rep in main.funcs: - rep += '_' - replacements[func] = rep - #print >> sys.stderr, 'replacements:', replacements - - # sendings: add invokes for new tables - all_sendings = main.sendings - added_sending = False - for table in self.tables: - if table not in main.tables: - sig = table[table.rfind('_')+1:] - all_sendings['invoke_%s' % sig] = shared.JS.make_invoke(sig, named=False) - added_sending = True - - # imports - all_imports = main.imports - for key, value in self.imports.iteritems(): - if key in self.funcs or key in main.funcs: continue # external function in one module, implemented in the other - value_concrete = '.' not in value # env.key means it is an import, an external value, and not a concrete one - main_value = main.imports.get(key) - main_value_concrete = main_value and '.' not in main_value - if value_concrete and main_value_concrete: continue # standard global var - if not main_value or value_concrete: - if '+' in value: - # relocate - value = value.replace('(', '').replace(')', '').replace('| 0', '').replace('|0', '').replace(' ', '') - left, right = value.split('+') - assert left == 'H_BASE' - value = str(main.staticbump + int(right)) - all_imports[key] = value - if (value_concrete or main_value_concrete) and key in all_sendings: - del all_sendings[key] # import of external value no longer needed - main.imports_js = '\n'.join(['var %s = %s;' % (key, value) for key, value in all_imports.iteritems()]) + '\n' - - # check for undefined references to global variables - def check_import(key, value): - if value.startswith('+') or value.endswith('|0'): # ignore functions - if key not in all_sendings: - print >> sys.stderr, 'warning: external variable %s is still not defined after linking' % key - all_sendings[key] = '0' - for key, value in all_imports.iteritems(): check_import(key, value) - - if added_sending: - sendings_js = ', '.join(['%s: %s' % (key, value) for key, value in all_sendings.iteritems()]) - sendings_start = main.post_js.find('}, { ')+5 - sendings_end = main.post_js.find(' }, buffer);') - main.post_js = main.post_js[:sendings_start] + sendings_js + main.post_js[sendings_end:] - - # tables - f_bases = {} - f_sizes = {} - for table, data in self.tables.iteritems(): - main.tables[table] = self.merge_tables(table, main.tables.get(table), data, replacements, f_bases, f_sizes) - main.combine_tables() - #print >> sys.stderr, 'f bases', f_bases - - # relocate - temp = shared.Building.js_optimizer(self.filename, ['asm', 'relocate', 'last'], extra_info={ - 'replacements': replacements, - 'fBases': f_bases, - 'hBase': main.staticbump - }) - #print >> sys.stderr, 'relocated side into', temp - relocated_funcs = AsmModule(temp) - shared.try_delete(temp) - main.extra_funcs_js = relocated_funcs.funcs_js.replace(js_optimizer.start_funcs_marker, '\n') - - # update function table uses - ft_marker = 'FUNCTION_TABLE_' - - def update_fts(what): - updates = [] - i = 1 # avoid seeing marker in recursion - while 1: - i = what.find(ft_marker, i) - if i < 0: break; - start = i - end = what.find('[', start) - table = what[i:end] - if table not in f_sizes: - # table was not modified - i += len(ft_marker) - continue - nesting = 1 - while nesting > 0: - next = what.find(']', end+1) - nesting -= 1 - nesting += what.count('[', end+1, next) - end = next - assert end > 0 - mask = what.rfind('&', start, end) - assert mask > 0 and end - mask <= 13 - fixed = update_fts(what[start:mask+1] + str(f_sizes[table]-1) + ']') - updates.append((start, end, fixed)) - i = end # additional function table uses were done by recursion - # apply updates - if len(updates) == 0: return what - parts = [] - so_far = 0 - for i in range(len(updates)): - start, end, fixed = updates[i] - parts.append(what[so_far:start]) - parts.append(fixed) - so_far = end+1 - parts.append(what[so_far:]) - return ''.join(parts) - - main.funcs_js = update_fts(main.funcs_js) - main.extra_funcs_js = update_fts(main.extra_funcs_js) - - # global initializers - if self.global_inits: - my_global_inits = map(lambda init: replacements[init] if init in replacements else init, self.global_inits) - all_global_inits = map(lambda init: '{ func: function() { %s() } }' % init, main.global_inits + my_global_inits) - all_global_inits_js = '/* global initializers */ __ATINIT__.push(' + ','.join(all_global_inits) + ');' - if main.global_inits: - target = main.global_inits_js - else: - target = '// === Body ===\n' - all_global_inits_js = target + all_global_inits_js - main.pre_js = main.pre_js.replace(target, all_global_inits_js) - - # exports - def rep_exp(export): - key, value = export.split(':') - if key in replacements: - repped = replacements[key] - return repped + ': ' + repped - return export - my_exports = map(rep_exp, self.exports) - exports = main.exports.union(my_exports) - main.exports_js = 'return {' + ','.join(list(exports)) + '};\n})\n' - - # post - def rep_def(deff): - key = deff.split(' ')[1] - if key in replacements: - rep = replacements[key] - return 'var %s = Module["%s"] = asm["%s"];\n' % (rep, rep, rep) - return deff - my_module_defs = map(rep_def, self.module_defs) - new_module_defs = set(my_module_defs).difference(main.module_defs) - if len(new_module_defs) > 0: - position = main.post_js.find('Runtime.') # Runtime is the start of the hardcoded ones - main.post_js = main.post_js[:position] + ''.join(list(new_module_defs)) + '\n' + main.post_js[position:] - - def write(self, out): - f = open(out, 'w') - f.write(self.pre_js) - f.write(self.pre_imports_js) - f.write(self.imports_js) - f.write(self.funcs_js) - f.write(self.extra_funcs_js) - f.write(self.tables_js) - f.write(self.exports_js) - f.write(self.post_js) - f.close() - - # Utilities - - def parse_tables(self, js): - tables = {} - parts = js.split(';') - for part in parts: - if '=' not in part: continue - part = part.split('var ')[1] - name, data = part.split(' = ') - tables[name] = data - return tables - - def merge_tables(self, table, main, side, replacements, f_bases, f_sizes): - sig = table.split('_')[-1] - side = side[1:-1].split(',') - side = map(lambda f: replacements[f] if f in replacements else f, side) - if not main: - f_bases[sig] = 0 - f_sizes[table] = len(side) - return '[' + ','.join(side) + ']' - main = main[1:-1].split(',') - # TODO: handle non-aliasing case too - assert len(main) % 2 == 0 - f_bases[sig] = len(main) - ret = main + side - size = 2 - while size < len(ret): size *= 2 - aborter = ret[1] # we can assume odd indexes have an aborting function with the right signature - ret = ret + [aborter]*(size - len(ret)) - assert len(ret) == size - f_sizes[table] = size - return '[' + ','.join(ret) + ']' - - def combine_tables(self): - self.tables_js = '// EMSCRIPTEN_END_FUNCS\n' - for table, data in self.tables.iteritems(): - self.tables_js += 'var %s = %s;\n' % (table, data) - main = AsmModule(main) side = AsmModule(side) diff --git a/src/library.js b/src/library.js index 7bb4ca77..143d7c28 100644 --- a/src/library.js +++ b/src/library.js @@ -3945,7 +3945,11 @@ LibraryManager.library = { bsearch: function(key, base, num, size, compar) { var cmp = function(x, y) { - return Runtime.dynCall('iii', compar, [x, y]) +#if ASM_JS + return Module['dynCall_iii'](compar, x, y); +#else + return FUNCTION_TABLE[compar](x, y); +#endif }; var left = 0; var right = num; @@ -3955,7 +3959,6 @@ LibraryManager.library = { mid = (left + right) >>> 1; addr = base + (mid * size); test = cmp(key, addr); - if (test < 0) { right = mid; } else if (test > 0) { @@ -4175,13 +4178,14 @@ LibraryManager.library = { if (num == 0 || size == 0) return; // forward calls to the JavaScript sort method // first, sort the items logically - var comparator = function(x, y) { - return Runtime.dynCall('iii', cmp, [x, y]); - } var keys = []; for (var i = 0; i < num; i++) keys.push(i); keys.sort(function(a, b) { - return comparator(base+a*size, base+b*size); +#if ASM_JS + return Module['dynCall_iii'](cmp, base+a*size, base+b*size); +#else + return FUNCTION_TABLE[cmp](base+a*size, base+b*size); +#endif }); // apply the sort var temp = _malloc(num*size); @@ -4954,7 +4958,17 @@ LibraryManager.library = { (chr >= {{{ charCode('{') }}} && chr <= {{{ charCode('~') }}}); }, isspace: function(chr) { - return chr in { 32: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0 }; + switch(chr) { + case 32: + case 9: + case 10: + case 11: + case 12: + case 13: + return true; + default: + return false; + }; }, isblank: function(chr) { return chr == {{{ charCode(' ') }}} || chr == {{{ charCode('\t') }}}; diff --git a/src/postamble.js b/src/postamble.js index 8fe3b65d..25a50bfc 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -6,7 +6,7 @@ var inMain; Module['callMain'] = Module.callMain = function callMain(args) { assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on __ATMAIN__)'); - assert(!Module['preRun'] || Module['preRun'].length == 0, 'cannot call main when preRun functions remain to be called'); + assert(__ATPRERUN__.length == 0, 'cannot call main when preRun functions remain to be called'); args = args || []; @@ -74,17 +74,11 @@ function run(args) { return; } - if (Module['preRun']) { - if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; - var toRun = Module['preRun']; - Module['preRun'] = []; - for (var i = toRun.length-1; i >= 0; i--) { - toRun[i](); - } - if (runDependencies > 0) { - // a preRun added a dependency, run will be called later - return; - } + preRun(); + + if (runDependencies > 0) { + // a preRun added a dependency, run will be called later + return; } function doRun() { @@ -96,12 +90,8 @@ function run(args) { if (Module['_main'] && shouldRunNow) { Module['callMain'](args); } - if (Module['postRun']) { - if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; - while (Module['postRun'].length > 0) { - Module['postRun'].pop()(); - } - } + + postRun(); } if (Module['setStatus']) { diff --git a/src/preamble.js b/src/preamble.js index 57376ad3..2955c885 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -705,37 +705,74 @@ function callRuntimeCallbacks(callbacks) { } } -var __ATINIT__ = []; // functions called during startup -var __ATMAIN__ = []; // functions called when main() is to be run -var __ATEXIT__ = []; // functions called during shutdown +var __ATPRERUN__ = []; // functions called before the runtime is initialized +var __ATINIT__ = []; // functions called during startup +var __ATMAIN__ = []; // functions called when main() is to be run +var __ATEXIT__ = []; // functions called during shutdown +var __ATPOSTRUN__ = []; // functions called after the runtime has exited var runtimeInitialized = false; +function preRun() { + // compatibility - merge in anything from Module['preRun'] at this time + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + callRuntimeCallbacks(__ATPRERUN__); +} + function ensureInitRuntime() { if (runtimeInitialized) return; runtimeInitialized = true; callRuntimeCallbacks(__ATINIT__); } + function preMain() { callRuntimeCallbacks(__ATMAIN__); } + function exitRuntime() { callRuntimeCallbacks(__ATEXIT__); } -Module['addOnInit'] = Module.addOnInit = function addOnInit(cb) { +function postRun() { + // compatibility - merge in anything from Module['postRun'] at this time + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + callRuntimeCallbacks(__ATPOSTRUN__); +} + +function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); +} +Module['addOnPreRun'] = Module.addOnPreRun = addOnPreRun; + +function addOnInit(cb) { __ATINIT__.unshift(cb); -}; +} +Module['addOnInit'] = Module.addOnInit = addOnInit; -Module['addOnPreMain'] = Module.addOnPreMain = function addOnPreMain(cb) { +function addOnPreMain(cb) { __ATMAIN__.unshift(cb); -}; +} +Module['addOnPreMain'] = Module.addOnPreMain = addOnPreMain; -Module['addOnExit'] = Module.addOnExit = function addOnExit(cb) { +function addOnExit(cb) { __ATEXIT__.unshift(cb); -}; +} +Module['addOnExit'] = Module.addOnExit = addOnExit; -// TODO add onprerun, onpostrun +function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); +} +Module['addOnPostRun'] = Module.addOnPostRun = addOnPostRun; // Tools @@ -873,12 +910,6 @@ Module['removeRunDependency'] = removeRunDependency; Module["preloadedImages"] = {}; // maps url to image data Module["preloadedAudios"] = {}; // maps url to audio data -function addPreRun(func) { - if (!Module['preRun']) Module['preRun'] = []; - else if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; - Module['preRun'].push(func); -} - #if PGO var PGOMonitor = { called: {}, @@ -893,7 +924,7 @@ var PGOMonitor = { }; Module['PGOMonitor'] = PGOMonitor; __ATEXIT__.push({ func: function() { PGOMonitor.dump() } }); -addPreRun(function() { addRunDependency('pgo') }); +addOnPreRun(function() { addRunDependency('pgo') }); #endif function loadMemoryInitializer(filename) { @@ -906,7 +937,7 @@ function loadMemoryInitializer(filename) { } // always do this asynchronously, to keep shell and web as similar as possible - addPreRun(function() { + addOnPreRun(function() { if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) { applyData(Module['readBinary'](filename)); } else { diff --git a/tests/qsort/benchmark.cpp b/tests/qsort/benchmark.cpp new file mode 100644 index 00000000..950ca437 --- /dev/null +++ b/tests/qsort/benchmark.cpp @@ -0,0 +1,43 @@ +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +typedef unsigned int uint32; + +int cmp_uint(const void *i1, const void *i2) { + if (*static_cast<const uint32*>(i1) > + *static_cast<const uint32*>(i2)) + return 1; + + if (*static_cast<const uint32*>(i1) < + *static_cast<const uint32*>(i2)) + return -1; + + return 0; +} + +int main() { + clock_t start = clock(); + const size_t TIMES = 10000; + for (size_t i = 0; i < TIMES; i++) { + const size_t num = 100; + uint32 rnd[num] = { \ + 407, 236, 765, 529, 24, 13, 577, 900, 242, 245, \ + 782, 972, 514, 100, 596, 470, 680, 65, 370, 788, \ + 44, 330, 579, 314, 914, 399, 100, 945, 992, 412, \ + 308, 102, 895, 529, 216, 422, 851, 778, 28, 804, \ + 325, 975, 961, 623, 922, 667, 141, 755, 416, 575, \ + 712, 503, 174, 675, 14, 647, 544, 881, 858, 621, \ + 26, 283, 460, 252, 146, 16, 571, 570, 14, 143, \ + 674, 985, 477, 386, 932, 490, 611, 127, 702, 619, \ + 104, 892, 58, 635, 663, 424, 714, 740, 229, 538, \ + 167, 181, 193, 193, 657, 778, 217, 573, 764, 745}; + + qsort(rnd, num, sizeof(uint32), cmp_uint); + } + clock_t end = clock(); + + float diff = (((float)end - (float)start) / CLOCKS_PER_SEC ) * 1000; + printf("cost %fms\n", diff); +} + diff --git a/tests/runner.py b/tests/runner.py index 55d666e9..9f037164 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -10733,6 +10733,27 @@ f.close() os.chdir(path_from_root('tests')) # Move away from the directory we are about to remove. shutil.rmtree(tempdirname) + def test_nostdincxx(self): + try: + old = os.environ.get('EMCC_LLVM_TARGET') or '' + for compiler in [EMCC, EMXX]: + for target in ['i386-pc-linux-gnu', 'le32-unknown-nacl']: + print compiler, target + os.environ['EMCC_LLVM_TARGET'] = target + out, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.cpp'), '-v'], stdout=PIPE, stderr=PIPE).communicate() + out2, err2 = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.cpp'), '-v', '-nostdinc++'], stdout=PIPE, stderr=PIPE).communicate() + assert out == out2 + def focus(e): + assert 'search starts here:' in e, e + assert e.count('End of search list.') == 1, e + return e[e.index('search starts here:'):e.index('End of search list.')+20] + err = focus(err) + err2 = focus(err2) + assert err == err2, err + '\n\n\n\n' + err2 + finally: + if old: + os.environ['EMCC_LLVM_TARGET'] = old + def test_failure_error_code(self): for compiler in [EMCC, EMXX]: # Test that if one file is missing from the build, then emcc shouldn't succeed, and shouldn't try to produce an output file. @@ -14906,12 +14927,30 @@ fi finally: del os.environ['EMCC_DEBUG'] + restore() + + def ensure_cache(): + self.do([EMCC, '-O2', path_from_root('tests', 'hello_world.c')]) + # Manual cache clearing + ensure_cache() assert os.path.exists(EMCC_CACHE) output = self.do([EMCC, '--clear-cache']) assert ERASING_MESSAGE in output assert not os.path.exists(EMCC_CACHE) + # Changing LLVM_ROOT, even without altering .emscripten, clears the cache + ensure_cache() + old = os.environ.get('LLVM') + try: + os.environ['LLVM'] = 'waka' + assert os.path.exists(EMCC_CACHE) + output = self.do([EMCC]) + assert ERASING_MESSAGE in output + assert not os.path.exists(EMCC_CACHE) + finally: + if old: os.environ['LLVM'] = old + try_delete(CANONICAL_TEMP_DIR) def test_relooper(self): diff --git a/tools/asm_module.py b/tools/asm_module.py new file mode 100644 index 00000000..e54cfc21 --- /dev/null +++ b/tools/asm_module.py @@ -0,0 +1,271 @@ + +import sys, re + +import shared, js_optimizer + + +class AsmModule(): + def __init__(self, filename): + self.filename = filename + self.js = open(filename).read() + + self.start_asm = self.js.find(js_optimizer.start_asm_marker) + self.start_funcs = self.js.find(js_optimizer.start_funcs_marker) + self.end_funcs = self.js.rfind(js_optimizer.end_funcs_marker) + self.end_asm = self.js.rfind(js_optimizer.end_asm_marker) + + # pre and asm + self.pre_js = self.js[:self.start_asm] + self.asm_js = self.js[self.start_asm:self.end_asm] + + # heap initializer + self.staticbump = int(re.search(shared.JS.memory_staticbump_pattern, self.pre_js).group(1)) + if self.staticbump: + self.mem_init_js = re.search(shared.JS.memory_initializer_pattern, self.pre_js).group(0) + + # global initializers + global_inits = re.search(shared.JS.global_initializers_pattern, self.pre_js) + if global_inits: + self.global_inits_js = global_inits.group(0) + self.global_inits = map(lambda init: init.split('{')[2][1:].split('(')[0], global_inits.groups(0)[0].split(',')) + else: + self.global_inits_js = '' + self.global_inits = [] + + # imports (and global variables) + first_var = self.js.find('var ', self.js.find('var ', self.start_asm)+4) + self.pre_imports_js = self.js[self.start_asm:first_var] + self.imports_js = self.js[first_var:self.start_funcs] + self.imports = {} + for imp in js_optimizer.import_sig.finditer(self.imports_js): + key, value = imp.group(0).split('var ')[1][:-1].split('=', 1) + self.imports[key.strip()] = value.strip() + #print >> sys.stderr, 'imports', self.imports + + # funcs + self.funcs_js = self.js[self.start_funcs:self.end_funcs] + self.funcs = set([m.group(2) for m in js_optimizer.func_sig.finditer(self.funcs_js)]) + #print 'funcs', self.funcs + + # tables and exports + post_js = self.js[self.end_funcs:self.end_asm] + ret = post_js.find('return') + self.tables_js = post_js[:ret] + self.exports_js = post_js[ret:] + self.tables = self.parse_tables(self.tables_js) + self.exports = set([export.strip() for export in self.exports_js[self.exports_js.find('{')+1:self.exports_js.find('}')].split(',')]) + + # post + self.post_js = self.js[self.end_asm:] + self.sendings = {} + for sending in [sending.strip() for sending in self.post_js[self.post_js.find('}, { ')+5:self.post_js.find(' }, buffer);')].split(',')]: + colon = sending.find(':') + self.sendings[sending[:colon].replace('"', '')] = sending[colon+1:].strip() + self.module_defs = set(re.findall('var [\w\d_$]+ = Module\["[\w\d_$]+"\] = asm\["[\w\d_$]+"\];\n', self.post_js)) + + def relocate_into(self, main): + # heap initializer + if self.staticbump > 0: + new_mem_init = self.mem_init_js[:self.mem_init_js.rfind(', ')] + ', Runtime.GLOBAL_BASE+%d)' % main.staticbump + main.pre_js = re.sub(shared.JS.memory_staticbump_pattern, 'STATICTOP = STATIC_BASE + %d;\n' % (main.staticbump + self.staticbump) + new_mem_init, main.pre_js, count=1) + + # Find function name replacements TODO: do not rename duplicate names with duplicate contents, just merge them + replacements = {} + for func in self.funcs: + rep = func + while rep in main.funcs: + rep += '_' + replacements[func] = rep + #print >> sys.stderr, 'replacements:', replacements + + # sendings: add invokes for new tables + all_sendings = main.sendings + added_sending = False + for table in self.tables: + if table not in main.tables: + sig = table[table.rfind('_')+1:] + all_sendings['invoke_%s' % sig] = shared.JS.make_invoke(sig, named=False) + added_sending = True + + # imports + all_imports = main.imports + for key, value in self.imports.iteritems(): + if key in self.funcs or key in main.funcs: continue # external function in one module, implemented in the other + value_concrete = '.' not in value # env.key means it is an import, an external value, and not a concrete one + main_value = main.imports.get(key) + main_value_concrete = main_value and '.' not in main_value + if value_concrete and main_value_concrete: continue # standard global var + if not main_value or value_concrete: + if '+' in value: + # relocate + value = value.replace('(', '').replace(')', '').replace('| 0', '').replace('|0', '').replace(' ', '') + left, right = value.split('+') + assert left == 'H_BASE' + value = str(main.staticbump + int(right)) + all_imports[key] = value + if (value_concrete or main_value_concrete) and key in all_sendings: + del all_sendings[key] # import of external value no longer needed + main.imports_js = '\n'.join(['var %s = %s;' % (key, value) for key, value in all_imports.iteritems()]) + '\n' + + # check for undefined references to global variables + def check_import(key, value): + if value.startswith('+') or value.endswith('|0'): # ignore functions + if key not in all_sendings: + print >> sys.stderr, 'warning: external variable %s is still not defined after linking' % key + all_sendings[key] = '0' + for key, value in all_imports.iteritems(): check_import(key, value) + + if added_sending: + sendings_js = ', '.join(['%s: %s' % (key, value) for key, value in all_sendings.iteritems()]) + sendings_start = main.post_js.find('}, { ')+5 + sendings_end = main.post_js.find(' }, buffer);') + main.post_js = main.post_js[:sendings_start] + sendings_js + main.post_js[sendings_end:] + + # tables + f_bases = {} + f_sizes = {} + for table, data in self.tables.iteritems(): + main.tables[table] = self.merge_tables(table, main.tables.get(table), data, replacements, f_bases, f_sizes) + main.combine_tables() + #print >> sys.stderr, 'f bases', f_bases + + # relocate + temp = shared.Building.js_optimizer(self.filename, ['asm', 'relocate', 'last'], extra_info={ + 'replacements': replacements, + 'fBases': f_bases, + 'hBase': main.staticbump + }) + #print >> sys.stderr, 'relocated side into', temp + relocated_funcs = AsmModule(temp) + shared.try_delete(temp) + main.extra_funcs_js = relocated_funcs.funcs_js.replace(js_optimizer.start_funcs_marker, '\n') + + # update function table uses + ft_marker = 'FUNCTION_TABLE_' + + def update_fts(what): + updates = [] + i = 1 # avoid seeing marker in recursion + while 1: + i = what.find(ft_marker, i) + if i < 0: break; + start = i + end = what.find('[', start) + table = what[i:end] + if table not in f_sizes: + # table was not modified + i += len(ft_marker) + continue + nesting = 1 + while nesting > 0: + next = what.find(']', end+1) + nesting -= 1 + nesting += what.count('[', end+1, next) + end = next + assert end > 0 + mask = what.rfind('&', start, end) + assert mask > 0 and end - mask <= 13 + fixed = update_fts(what[start:mask+1] + str(f_sizes[table]-1) + ']') + updates.append((start, end, fixed)) + i = end # additional function table uses were done by recursion + # apply updates + if len(updates) == 0: return what + parts = [] + so_far = 0 + for i in range(len(updates)): + start, end, fixed = updates[i] + parts.append(what[so_far:start]) + parts.appe |