diff options
-rwxr-xr-x | emcc | 60 | ||||
-rw-r--r-- | emlink.py | 293 | ||||
-rwxr-xr-x | emscripten.py | 40 | ||||
-rw-r--r-- | src/intertyper.js | 2 | ||||
-rw-r--r-- | src/jsifier.js | 69 | ||||
-rw-r--r-- | src/library.js | 4 | ||||
-rw-r--r-- | src/library_gl.js | 112 | ||||
-rw-r--r-- | src/modules.js | 17 | ||||
-rw-r--r-- | src/parseTools.js | 25 | ||||
-rw-r--r-- | src/preamble.js | 5 | ||||
-rw-r--r-- | src/settings.js | 6 | ||||
-rwxr-xr-x | tests/runner.py | 251 | ||||
-rw-r--r-- | tools/js-optimizer.js | 32 | ||||
-rw-r--r-- | tools/js_optimizer.py | 31 | ||||
-rw-r--r-- | tools/shared.py | 40 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre-output.js | 10 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre.js | 12 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-relocate-output.js | 8 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-relocate.js | 10 |
19 files changed, 849 insertions, 178 deletions
@@ -1066,6 +1066,17 @@ try: logging.warning('disabling LLVM optimizations, need typed arrays mode 2 for them') llvm_opts = 0 + if shared.Settings.MAIN_MODULE: + assert not shared.Settings.SIDE_MODULE + shared.Settings.INCLUDE_FULL_LIBRARY = 1 + elif shared.Settings.SIDE_MODULE: + assert not shared.Settings.MAIN_MODULE + + if shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE: + assert not memory_init_file, 'memory init file is not supported with module linking' + shared.Settings.LINKABLE = 1 # TODO: add FORCE_DCE option for the brave people that do want to dce here and in side modules + debug_level = max(debug_level, 2) + ## Compile source code to bitcode logging.debug('compiling to bitcode') @@ -1140,7 +1151,9 @@ try: extra_files_to_link = [] if not LEAVE_INPUTS_RAW and not AUTODEBUG and \ - not shared.Settings.BUILD_AS_SHARED_LIB == 2: # shared lib 2 use the library in the parent + not shared.Settings.BUILD_AS_SHARED_LIB == 2 and \ + not shared.Settings.SIDE_MODULE: # shared libraries/side modules link no C libraries, need them in parent + # Check if we need to include some libraries that we compile. (We implement libc ourselves in js, but # compile a malloc implementation and stdlibc++.) @@ -1352,7 +1365,9 @@ try: return 'SDL_Init' in all_needed and ('malloc' not in all_needed or 'free' not in all_needed) # Settings this in the environment will avoid checking dependencies and make building big projects a little faster + # 1 means include everything; otherwise it can be the name of a lib (libcxx, etc.) force = os.environ.get('EMCC_FORCE_STDLIBS') + force_all = force == '1' # Scan symbols all_needed = set() @@ -1370,7 +1385,8 @@ try: ('libcxxabi', create_libcxxabi, apply_libcxxabi, libcxxabi_symbols), ('sdl', create_sdl, apply_sdl, sdl_symbols), ('libc', create_libc, apply_libc, libc_symbols)]: - if not force: + force_this = force_all or force == name + if not force_this: need = set() has = set() for symbols in symbolses: @@ -1383,12 +1399,13 @@ try: if haz in need: need.remove(haz) if shared.Settings.VERBOSE: logging.debug('considering %s: we need %s and have %s' % (name, str(need), str(has))) - if (force or len(need) > 0) and apply_(need): - # We need to build and link the library in - logging.debug('including %s' % name) - libfile = shared.Cache.get(name, create) - extra_files_to_link.append(libfile) - force = True + if force_this or len(need) > 0: + force_all = True + if apply_(need): + # We need to build and link the library in + logging.debug('including %s' % name) + libfile = shared.Cache.get(name, create) + extra_files_to_link.append(libfile) # First, combine the bitcode files if there are several. We must also link if we have a singleton .a if len(input_files) + len(extra_files_to_link) > 1 or \ @@ -1396,7 +1413,7 @@ try: linker_inputs = temp_files + extra_files_to_link logging.debug('linking: ' + str(linker_inputs)) t0 = time.time() - shared.Building.link(linker_inputs, in_temp(target_basename + '.bc')) + shared.Building.link(linker_inputs, in_temp(target_basename + '.bc'), force_archive_contents = len(filter(lambda temp: not temp.endswith(STATICLIB_SUFFIXES), temp_files)) == 0) t1 = time.time() logging.debug(' linking took %.2f seconds' % (t1 - t0)) final = in_temp(target_basename + '.bc') @@ -1433,17 +1450,16 @@ try: shared.Building.llvm_opt(in_temp(target_basename + '.bc'), ['-O3']) if DEBUG: save_intermediate('opt', 'bc') - if shared.Building.can_build_standalone(): - # If we can LTO, do it before dce, since it opens up dce opportunities - if llvm_lto and llvm_lto != 2 and shared.Building.can_use_unsafe_opts(): - if not shared.Building.can_inline(): link_opts.append('-disable-inlining') - # do not internalize in std-link-opts - it ignores internalize-public-api-list - and add a manual internalize - link_opts += ['-disable-internalize'] + shared.Building.get_safe_internalize() + ['-std-link-opts'] - else: - # At minimum remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it) - link_opts += shared.Building.get_safe_internalize() + ['-globaldce'] - shared.Building.llvm_opt(in_temp(target_basename + '.bc'), link_opts) - if DEBUG: save_intermediate('linktime', 'bc') + # If we can LTO, do it before dce, since it opens up dce opportunities + if shared.Building.can_build_standalone() and llvm_lto and llvm_lto != 2 and shared.Building.can_use_unsafe_opts(): + if not shared.Building.can_inline(): link_opts.append('-disable-inlining') + # do not internalize in std-link-opts - it ignores internalize-public-api-list - and add a manual internalize + link_opts += ['-disable-internalize'] + shared.Building.get_safe_internalize() + ['-std-link-opts'] + else: + # At minimum remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it) + link_opts += shared.Building.get_safe_internalize() + ['-globaldce'] + shared.Building.llvm_opt(in_temp(target_basename + '.bc'), link_opts) + if DEBUG: save_intermediate('linktime', 'bc') if save_bc: shutil.copyfile(final, save_bc) @@ -1584,7 +1600,7 @@ try: if closure and shared.Settings.ASM_JS: js_optimizer_queue += ['closure'] - js_optimizer_queue += ['last'] + if not shared.Settings.SIDE_MODULE: js_optimizer_queue += ['last'] # side modules are not finalized until after relocation flush_js_optimizer_queue() @@ -1611,7 +1627,7 @@ try: if os.path.abspath(memfile) != os.path.abspath(memfile): shutil.copyfile(memfile, temp_memfile) return 'loadMemoryInitializer("%s");' % os.path.basename(memfile) - src = re.sub('/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, Runtime\.GLOBAL_BASE\)', repl, src, count=1) + src = re.sub(shared.JS.memory_initializer_pattern, repl, src, count=1) open(final + '.mem.js', 'w').write(src) final += '.mem.js' js_transform_tempfiles[-1] = final # simple text substitution preserves comment line number mappings diff --git a/emlink.py b/emlink.py new file mode 100644 index 00000000..0a209cc7 --- /dev/null +++ b/emlink.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python2 + +''' +Fast static linker for emscripten outputs. Specifically this links asm.js modules. + +See https://github.com/kripken/emscripten/wiki/Linking +''' + +import os, subprocess, sys, re +from tools import shared +from tools import js_optimizer + +try: + me, main, side, out = sys.argv[:4] +except: + print >> sys.stderr, 'usage: emlink.py [main module] [side module] [output name]' + sys.exit(1) + +print 'Main module:', main +print 'Side module:', side +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] = value + #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' + + 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:] + + # 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, 'external variable %s is still not defined after linking' % key + for key, value in all_imports.iteritems(): check_import(key, value) + + # 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) + +side.relocate_into(main) +main.write(out) + diff --git a/emscripten.py b/emscripten.py index 56f59273..df0587f9 100755 --- a/emscripten.py +++ b/emscripten.py @@ -11,6 +11,7 @@ headers, for the libc implementation in JS). import os, sys, json, optparse, subprocess, re, time, multiprocessing, functools +from tools import shared from tools import jsrun, cache as cache_module, tempfiles from tools.response_file import read_response_file @@ -25,7 +26,6 @@ def get_configuration(): if hasattr(get_configuration, 'configuration'): return get_configuration.configuration - from tools import shared configuration = shared.Configuration(environ=os.environ) get_configuration.configuration = configuration return configuration @@ -341,8 +341,18 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, ret = re.sub(r'"?{{{ BA_([\w\d_$]+)\|([\w\d_$]+) }}}"?,0,0,0', lambda m: split_32(blockaddrs[m.groups(0)[0]][m.groups(0)[1]]), js) return re.sub(r'"?{{{ BA_([\w\d_$]+)\|([\w\d_$]+) }}}"?', lambda m: str(blockaddrs[m.groups(0)[0]][m.groups(0)[1]]), ret) + pre = blockaddrsize(indexize(pre)) + + if settings.get('ASM_JS'): + # move postsets into the asm module + class PostSets: js = '' + def handle_post_sets(m): + PostSets.js = m.group(0) + return '\n' + pre = re.sub(r'function runPostSets[^}]+}', handle_post_sets, pre) + #if DEBUG: outfile.write('// pre\n') - outfile.write(blockaddrsize(indexize(pre))) + outfile.write(pre) pre = None #if DEBUG: outfile.write('// funcs\n') @@ -453,23 +463,12 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, } ''' % (sig, i, args, arg_coercions, jsret)) - args = ','.join(['a' + str(i) for i in range(1, len(sig))]) - args = 'index' + (',' if args else '') + args - # C++ exceptions are numbers, and longjmp is a string 'longjmp' - asm_setup += ''' -function invoke_%s(%s) { - try { - %sModule["dynCall_%s"](%s); - } catch(e) { - if (typeof e !== 'number' && e !== 'longjmp') throw e; - asm["setThrew"](1, 0); - } -} -''' % (sig, args, 'return ' if sig[0] != 'v' else '', sig, args) + asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' basic_funcs.append('invoke_%s' % sig) # calculate exports exported_implemented_functions = list(exported_implemented_functions) + exported_implemented_functions.append('runPostSets') exports = [] if not simple: for export in exported_implemented_functions + asm_runtime_funcs + function_tables: @@ -491,6 +490,15 @@ function invoke_%s(%s) { ''.join([' var ' + g + '=env.' + math_fix(g) + ';\n' for g in basic_funcs + global_funcs]) asm_global_vars = ''.join([' var ' + g + '=env.' + g + '|0;\n' for g in basic_vars + global_vars]) + \ ''.join([' var ' + g + '=+env.' + g + ';\n' for g in basic_float_vars]) + # In linkable modules, we need to add some explicit globals for global variables that can be linked and used across modules + if settings.get('MAIN_MODULE') or settings.get('SIDE_MODULE'): + assert settings.get('TARGET_LE32'), 'TODO: support x86 target when linking modules (needs offset of 4 and not 8 here)' + for key, value in forwarded_json['Variables']['globals'].iteritems(): + if value.get('linkable'): + init = forwarded_json['Variables']['indexedGlobals'][key] + 8 # 8 is Runtime.GLOBAL_BASE / STATIC_BASE + if settings.get('SIDE_MODULE'): init = '(H_BASE+' + str(init) + ')|0' + asm_global_vars += ' var %s=%s;\n' % (key, str(init)) + # sent data the_global = '{ ' + ', '.join(['"' + math_fix(s) + '": ' + s for s in fundamentals]) + ' }' sending = '{ ' + ', '.join(['"' + math_fix(s) + '": ' + s for s in basic_funcs + global_funcs + basic_vars + basic_float_vars + global_vars]) + ' }' @@ -578,7 +586,7 @@ var asm = (function(global, env, buffer) { value = value|0; tempRet%d = value; } -''' % (i, i) for i in range(10)])] + funcs_js + [''' +''' % (i, i) for i in range(10)])] + [PostSets.js + '\n'] + funcs_js + [''' %s return %s; diff --git a/src/intertyper.js b/src/intertyper.js index 6da30ae8..94d937e1 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -502,7 +502,7 @@ function intertyper(data, sidePass, baseLineNums) { } else { // variable var ident = item.tokens[0].text; - var private_ = findTokenText(item, 'private') >= 0; + var private_ = findTokenText(item, 'private') >= 0 || findTokenText(item, 'internal') >= 0; cleanOutTokens(LLVM.GLOBAL_MODIFIERS, item.tokens, [2, 3]); var external = false; if (item.tokens[2].text === 'external') { diff --git a/src/jsifier.js b/src/jsifier.js index 885fbc30..3f8c184c 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -264,9 +264,9 @@ function JSify(data, functionsOnly, givenFunctions) { assert(!item.lines); // FIXME remove this, after we are sure it isn't needed var ret = [item]; if (item.ident == '_llvm_global_ctors') { - item.JS = '\n__ATINIT__ = __ATINIT__.concat([\n' + - item.ctors.map(function(ctor) { return ' { func: function() { ' + ctor + '() } }' }).join(',\n') + - '\n]);\n'; + item.JS = '\n/* global initializers */ __ATINIT__.push(' + + item.ctors.map(function(ctor) { return '{ func: function() { ' + ctor + '() } }' }).join(',') + + ');\n'; return ret; } @@ -286,6 +286,11 @@ function JSify(data, functionsOnly, givenFunctions) { allocator = 'ALLOC_NONE'; } + if (ASM_JS && (MAIN_MODULE || SIDE_MODULE) && !item.private_ && !NAMED_GLOBALS && isIndexableGlobal(item.ident)) { + // We need this to be named (and it normally would not be), so that it can be linked to and used from other modules + Variables.globals[item.ident].linkable = 1; + } + if (isBSS(item)) { var length = calcAllocatedSize(item.type); length = Runtime.alignMemory(length); @@ -374,22 +379,11 @@ function JSify(data, functionsOnly, givenFunctions) { // Set the actual value in a postset, since it may be a global variable. We also order by dependencies there Variables.globals[item.ident].targetIdent = item.value.ident; var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value); - var fix = ''; - if (BUILD_AS_SHARED_LIB == 2 && !item.private_) { - var target = item.ident; - if (isFunctionType(item.type)) { - target = item.value.ident; // the other side does not know this is an alias/function table index. So make it the alias target. - var varData = Variables.globals[target]; - assert(!varData, 'multi-level aliasing does not work yet in shared lib 2 exports'); - } - fix = '\nif (globalScope) { assert(!globalScope["' + item.ident + '"]); globalScope["' + item.ident + '"] = ' + target + ' }' + if ((MAIN_MODULE || SIDE_MODULE) && isFunctionType(item.type)) { + var target = item.value.ident; + if (!Functions.aliases[target]) Functions.aliases[target] = []; + Functions.aliases[target].push(item.ident); } - ret.push({ - intertype: 'GlobalVariablePostSet', - ident: item.ident, - dependencies: set([value]), - JS: item.ident + ' = ' + value + ';' + fix - }); return ret; } }); @@ -847,6 +841,19 @@ function JSify(data, functionsOnly, givenFunctions) { } func.JS = func.JS.replace(/\n *;/g, '\n'); // remove unneeded lines + + if (MAIN_MODULE || SIDE_MODULE) { + // Clone the function for each of its aliases. We do not know which name it will be used by in another module, + // and we do not have a heavyweight metadata system to resolve aliases during linking + var aliases = Functions.aliases[func.ident]; + if (aliases) { + var body = func.JS.substr(func.JS.indexOf('(')); + aliases.forEach(function(alias) { + func.JS += '\n' + 'function ' + alias + body; + }); + } + } + return func; } }); @@ -1254,7 +1261,7 @@ function JSify(data, functionsOnly, givenFunctions) { case 'xchg': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, param2, type, null, null, null, null, ',') + ',tempValue)'; case 'cmpxchg': { var param3 = finalizeLLVMParameter(item.params[2]); - return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==(' + param2 + '|0) ? ' + makeSetValue(param1, 0, param3, type, null, null, null, null, ',') + ' : 0),tempValue)'; + return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==(' + param2 + '|0) ? ' + asmCoercion(makeSetValue(param1, 0, param3, type, null, null, null, null, ','), 'i32') + ' : 0),tempValue)'; } default: throw 'unhandled atomic op: ' + item.op; } @@ -1502,7 +1509,7 @@ function JSify(data, functionsOnly, givenFunctions) { // This is a call through an invoke_*, either a forced one, or a setjmp-required one // note: no need to update argsTypes at this point if (byPointerForced) Functions.unimplementedFunctions[callIdent] = sig; - args.unshift(byPointerForced ? Functions.getIndex(callIdent) : asmCoercion(callIdent, 'i32')); + args.unshift(byPointerForced ? Functions.getIndex(callIdent, undefined, sig) : asmCoercion(callIdent, 'i32')); callIdent = 'invoke_' + sig; } } else if (SAFE_DYNCALLS) { @@ -1582,6 +1589,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (phase == 'pre' && !Variables.generatedGlobalBase) { Variables.generatedGlobalBase = true; // Globals are done, here is the rest of static memory + assert((TARGET_LE32 && Runtime.GLOBAL_BASE == 8) || (TARGET_X86 && Runtime.GLOBAL_BASE == 4)); // this is assumed in e.g. relocations for linkable modules print('STATIC_BASE = ' + Runtime.GLOBAL_BASE + ';\n'); print('STATICTOP = STATIC_BASE + ' + Runtime.alignMemory(Variables.nextIndexedOffset) + ';\n'); } @@ -1615,11 +1623,11 @@ function JSify(data, functionsOnly, givenFunctions) { print('/* no memory initializer */'); // test purposes } - // Run postsets right before main, and after the memory initializer has been set up + // Define postsets. These will be run in ATINIT, right before global initializers (which might need the postsets). We cannot + // run them now because the memory initializer might not have been applied yet. print('function runPostSets() {\n'); print(itemsDict.GlobalVariablePostSet.map(function(item) { return item.JS }).join('\n')); print('}\n'); - print('if (!awaitingMemoryInitializer) runPostSets();\n'); // if we load the memory initializer, this is done later if (USE_TYPED_ARRAYS == 2) { print('var tempDoublePtr = Runtime.alignMemory(allocate(12, "i8", ALLOC_STATIC), 8);\n'); @@ -1729,8 +1737,8 @@ function JSify(data, functionsOnly, givenFunctions) { } } }); - print(read('fastLong.js')); } + print(read('fastLong.js')); print('// EMSCRIPTEN_END_FUNCS\n'); print(read('long.js')); } else { @@ -1792,6 +1800,21 @@ function JSify(data, functionsOnly, givenFunctions) { substrate.addItems(data.functionStubs, 'FunctionStub'); assert(data.functions.length == 0); } else { + if (phase == 'pre') { + // ensure there is a global ctors, for runPostSets + if ('_llvm_global_ctors' in data.globalVariables) { + data.globalVariables._llvm_global_ctors.ctors.unshift('runPostSets'); // run postsets right before global initializers + hasCtors = true; + } else { + substrate.addItems([{ + intertype: 'GlobalVariableStub', + ident: '_llvm_global_ctors', + type: '[1 x { i32, void ()* }]', + ctors: ["runPostSets"], + }], 'GlobalVariable'); + } + } + substrate.addItems(sortGlobals(data.globalVariables), 'GlobalVariable'); substrate.addItems(data.aliass, 'Alias'); substrate.addItems(data.functions, 'FunctionSplitter'); diff --git a/src/library.js b/src/library.js index 822f4319..bf969fd7 100644 --- a/src/library.js +++ b/src/library.js @@ -8271,4 +8271,8 @@ function autoAddDeps(object, name) { } } +// Add aborting stubs for various libc stuff needed by libc++ +['pthread_cond_signal', 'pthread_equal', 'wcstol', 'wcstoll', 'wcstoul', 'wcstoull', 'wcstof', 'wcstod', 'wcstold', 'swprintf', 'pthread_join', 'pthread_detach', 'strcoll_l', 'strxfrm_l', 'wcscoll_l', 'toupper_l', 'tolower_l', 'iswspace_l', 'iswprint_l', 'iswcntrl_l', 'iswupper_l', 'iswlower_l', 'iswalpha_l', 'iswdigit_l', 'iswpunct_l', 'iswxdigit_l', 'iswblank_l', 'wcsxfrm_l', 'towupper_l', 'towlower_l'].forEach(function(aborter) { + LibraryManager.library[aborter] = function() { throw 'TODO: ' + aborter }; +}); diff --git a/src/library_gl.js b/src/library_gl.js index 1fa0cc9c..e59492cf 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -1260,6 +1260,8 @@ var LibraryGL = { return Module.ctx.isFramebuffer(fb); }, +#if DISABLE_GL_EMULATION == 0 + // GL emulation: provides misc. functionality not present in OpenGL ES 2.0 or WebGL $GLEmulation__postset: 'GLEmulation.init();', @@ -4139,6 +4141,44 @@ var LibraryGL = { }, glRotatef: 'glRotated', + glDrawBuffer: function() { throw 'glDrawBuffer: TODO' }, + glReadBuffer: function() { throw 'glReadBuffer: TODO' }, + + glLightfv: function() { throw 'glLightfv: TODO' }, + glLightModelfv: function() { throw 'glLightModelfv: TODO' }, + glMaterialfv: function() { throw 'glMaterialfv: TODO' }, + + glTexGeni: function() { throw 'glTexGeni: TODO' }, + glTexGenfv: function() { throw 'glTexGenfv: TODO' }, + glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') }, + glTexEnvf: function() { Runtime.warnOnce('glTexEnvf: TODO') }, + glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') }, + + glTexImage1D: function() { throw 'glTexImage1D: TODO' }, + glTexCoord3f: function() { throw 'glTexCoord3f: TODO' }, + glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' }, + + glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') }, + + // Open GLES1.1 compatibility + + glGenFramebuffersOES : 'glGenFramebuffers', + glGenRenderbuffersOES : 'glGenRenderbuffers', + glBindFramebufferOES : 'glBindFramebuffer', + glBindRenderbufferOES : 'glBindRenderbuffer', + glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv', + glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer', + glRenderbufferStorageOES : 'glRenderbufferStorage', + glCheckFramebufferStatusOES : 'glCheckFramebufferStatus', + glDeleteFramebuffersOES : 'glDeleteFramebuffers', + glDeleteRenderbuffersOES : 'glDeleteRenderbuffers', + glGenVertexArraysOES: 'glGenVertexArrays', + glDeleteVertexArraysOES: 'glDeleteVertexArrays', + glBindVertexArrayOES: 'glBindVertexArray', + glFramebufferTexture2DOES: 'glFramebufferTexture2D', + +#endif // DISABLE_GL_EMULATION == 0 + // GLU gluPerspective: function(fov, aspect, near, far) { @@ -4205,25 +4245,6 @@ var LibraryGL = { _glOrtho(left, right, bottom, top, -1, 1); }, - glDrawBuffer: function() { throw 'glDrawBuffer: TODO' }, - glReadBuffer: function() { throw 'glReadBuffer: TODO' }, - - glLightfv: function() { throw 'glLightfv: TODO' }, - glLightModelfv: function() { throw 'glLightModelfv: TODO' }, - glMaterialfv: function() { throw 'glMaterialfv: TODO' }, - - glTexGeni: function() { throw 'glTexGeni: TODO' }, - glTexGenfv: function() { throw 'glTexGenfv: TODO' }, - glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') }, - glTexEnvf: function() { Runtime.warnOnce('glTexEnvf: TODO') }, - glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') }, - - glTexImage1D: function() { throw 'glTexImage1D: TODO' }, - glTexCoord3f: function() { throw 'glTexCoord3f: TODO' }, - glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' }, - - glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') }, - // GLES2 emulation glVertexAttribPointer__sig: 'viiiiii', @@ -4351,23 +4372,6 @@ var LibraryGL = { glGetError__sig: 'i', glFrontFace__sig: 'vi', glSampleCoverage__sig: 'vi', - - // Open GLES1.1 compatibility - - glGenFramebuffersOES : 'glGenFramebuffers', - glGenRenderbuffersOES : 'glGenRenderbuffers', - glBindFramebufferOES : 'glBindFramebuffer', - glBindRenderbufferOES : 'glBindRenderbuffer', - glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv', - glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer', - glRenderbufferStorageOES : 'glRenderbufferStorage', - glCheckFramebufferStatusOES : 'glCheckFramebufferStatus', - glDeleteFramebuffersOES : 'glDeleteFramebuffers', - glDeleteRenderbuffersOES : 'glDeleteRenderbuffers', - glGenVertexArraysOES: 'glGenVertexArrays', - glDeleteVertexArraysOES: 'glDeleteVertexArrays', - glBindVertexArrayOES: 'glBindVertexArray', - glFramebufferTexture2DOES: 'glFramebufferTexture2D', }; @@ -4409,25 +4413,27 @@ var LibraryGL = { autoAddDeps(LibraryGL, '$GL'); -// Emulation requires everything else, potentially -LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.slice(0); // the __deps object is shared -var glFuncs = []; -for (var item in LibraryGL) { - if (item != '$GLEmulation' && item.substr(-6) != '__deps' && item.substr(-9) != '__postset' && item.substr(-5) != '__sig' && item.substr(0, 2) == 'gl') { - glFuncs.push(item); - } -} -LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs); -LibraryGL.$GLEmulation__deps.push(function() { - for (var func in Functions.getIndex.tentative) { - Functions.getIndex(func); - Functions.unimplementedFunctions[func] = LibraryGL[func.substr(1) + '__sig']; +if (!DISABLE_GL_EMULATION) { + // Emulation requires everything else, potentially + LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.slice(0); // the __deps object is shared + var glFuncs = []; + for (var item in LibraryGL) { + if (item != '$GLEmulation' && item.substr(-6) != '__deps' && item.substr(-9) != '__postset' && item.substr(-5) != '__sig' && item.substr(0, 2) == 'gl') { + glFuncs.push(item); + } } -}); + LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs); + LibraryGL.$GLEmulation__deps.push(function() { + for (var func in Functions.getIndex.tentative) { + Functions.getIndex(func); + Functions.unimplementedFunctions[func] = LibraryGL[func.substr(1) + '__sig']; + } + }); -if (FORCE_GL_EMULATION) { - LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation'); - LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation'); + if (FORCE_GL_EMULATION) { + LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation'); + LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation'); + } } mergeInto(LibraryManager.library, LibraryGL); diff --git a/src/modules.js b/src/modules.js index d26acbd5..a6aa2644 100644 --- a/src/modules.js +++ b/src/modules.js @@ -245,6 +245,8 @@ var Functions = { blockAddresses: {}, // maps functions to a map of block labels to label ids + aliases: {}, // in shared modules (MAIN_MODULE or SHARED_MODULE), a list of aliases for functions that have them + getSignature: function(returnType, argTypes, hasVarArgs) { var sig = returnType == 'void' ? 'v' : (isIntImplemented(returnType) ? 'i' : 'f'); for (var i = 0; i < argTypes.length; i++) { @@ -257,25 +259,32 @@ var Functions = { }, // Mark a function as needing indexing. Python will coordinate them all - getIndex: function(ident, doNotCreate) { + getIndex: function(ident, doNotCreate, sig) { if (doNotCreate && !(ident in this.indexedFunctions)) { if (!Functions.getIndex.tentative) Functions.getIndex.tentative = {}; // only used by GL emulation; TODO: generalize when needed Functions.getIndex.tentative[ident] = 0; } + var ret; if (phase != 'post' && singlePhase) { if (!doNotCreate) this.indexedFunctions[ident] = 0; // tell python we need this indexized - return "'{{ FI_" + toNiceIdent(ident) + " }}'"; // something python will replace later + ret = "'{{ FI_" + toNiceIdent(ident) + " }}'"; // something python will replace later } else { if (!singlePhase) return 'NO_INDEX'; // Should not index functions in post - var ret = this.indexedFunctions[ident]; + ret = this.indexedFunctions[ident]; if (!ret) { if (doNotCreate) return '0'; ret = this.nextIndex; this.nextIndex += 2; // Need to have indexes be even numbers, see |polymorph| test this.indexedFunctions[ident] = ret; } - return ret.toString(); + ret = ret.toString(); + } + if (SIDE_MODULE && sig) { // sig can be undefined for the GL library functions + ret = '((F_BASE_' + sig + ' + ' + ret + ')|0)'; + } else if (BUILD_AS_SHARED_LIB) { + ret = '(FUNCTION_TABLE_OFFSET + ' + ret + ')'; } + return ret; }, getTable: function(sig) { diff --git a/src/parseTools.js b/src/parseTools.js index 6bc0b7ea..dfd4b7ed 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -492,7 +492,9 @@ function makeGlobalUse(ident) { UNINDEXABLE_GLOBALS[ident] = 1; return ident; } - return (Runtime.GLOBAL_BASE + index).toString(); + var ret = (Runtime.GLOBAL_BASE + index).toString(); + if (SIDE_MODULE) ret = '(H_BASE+' + ret + ')'; + return ret; } return ident; } @@ -1227,15 +1229,11 @@ function indexizeFunctions(value, type) { var out = {}; if (type && isFunctionType(type, out) && value[0] === '_') { // checking for _ differentiates from $ (local vars) // add signature to library functions that we now know need indexing - if (!(value in Functions.implementedFunctions) && !(value in Functions.unimplementedFunctions)) { - Functions.unimplementedFunctions[value] = Functions.getSignature(out.returnType, out.segments ? out.segments.map(function(segment) { return segment[0].text }) : []); - } - - if (BUILD_AS_SHARED_LIB) { - return '(FUNCTION_TABLE_OFFSET + ' + Functions.getIndex(value) + ')'; - } else { - return Functions.getIndex(value); + var sig = Functions.implementedFunctions[value] || Functions.unimplementedFunctions[value]; + if (!sig) { + sig = Functions.unimplementedFunctions[value] = Functions.getSignature(out.returnType, out.segments ? out.segments.map(function(segment) { return segment[0].text }) : []); } + return Functions.getIndex(value, undefined, sig); } return value; } @@ -1627,6 +1625,9 @@ function makePointer(slab, pos, allocator, type, ptr, finalMemoryInitialization) // writing out into memory, without a normal allocation. We put all of these into a single big chunk. assert(typeof slab == 'object'); assert(slab.length % QUANTUM_SIZE == 0, slab.length); // must be aligned already + if (SIDE_MODULE && typeof ptr == 'string') { + ptr = parseInt(ptr.substring(ptr.indexOf('+'), ptr.length-1)); // parse into (H_BASE+X) + } var offset = ptr - Runtime.GLOBAL_BASE; for (var i = 0; i < slab.length; i++) { memoryInitialization[offset + i] = slab[i]; @@ -2146,7 +2147,11 @@ function processMathop(item) { } case 'select': return idents[0] + ' ? ' + makeCopyI64(idents[1]) + ' : ' + makeCopyI64(idents[2]); case 'ptrtoint': return makeI64(idents[0], 0); - case 'inttoptr': return '(' + idents[0] + '[0])'; // just directly truncate the i64 to a 'pointer', which is an i32 + case 'inttoptr': { + var m = /\(?\[(\d+),\d+\]\)?/.exec(idents[0]); + if (m) return m[1]; // constant, can just parse it right now + return '(' + idents[0] + '[0])'; // just directly truncate the i64 to a 'pointer', which is an i32 + } // Dangerous, rounded operations. TODO: Fully emulate case 'add': { if (PRECISE_I64_MATH) { diff --git a/src/preamble.js b/src/preamble.js index 55a8a3a5..ed148d9e 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -887,8 +887,6 @@ __ATEXIT__.push({ func: function() { PGOMonitor.dump() } }); addPreRun(function() { addRunDependency('pgo') }); #endif -var awaitingMemoryInitializer = false; - function loadMemoryInitializer(filename) { function applyData(data) { #if USE_TYPED_ARRAYS == 2 @@ -896,7 +894,6 @@ function loadMemoryInitializer(filename) { #else allocate(data, 'i8', ALLOC_NONE, STATIC_BASE); #endif - runPostSets(); } // always do this asynchronously, to keep shell and web as similar as possible @@ -911,8 +908,6 @@ function loadMemoryInitializer(filename) { }); } }); - - awaitingMemoryInitializer = false; } // === Body === diff --git a/src/settings.js b/src/settings.js index dff52adf..7f9dca3b 100644 --- a/src/settings.js +++ b/src/settings.js @@ -186,6 +186,8 @@ var GL_MAX_TEMP_BUFFER_SIZE = 2097152; // How large GL emulation temp buffers ar var GL_UNSAFE_OPTS = 1; // Enables some potentially-unsafe optimizations in GL emulation code var FULL_ES2 = 0; // Forces support for all GLES2 features, not just the WebGL-friendly subset. var FORCE_GL_EMULATION = 0; // Forces inclusion of full GL emulation code. +var DISABLE_GL_EMULATION = 0; // Disable inclusion of full GL emulation code. Useful when you don't want emulation + // but do need INCLUDE_FULL_LIBRARY or MAIN_MODULE. var DISABLE_EXCEPTION_CATCHING = 0; // Disables generating code to actually catch exceptions. If the code you // are compiling does not actually rely on catching exceptions (but the @@ -296,6 +298,10 @@ var SHOW_LABELS = 0; // Show labels in the generated code var PRINT_SPLIT_FILE_MARKER = 0; // Prints markers in Javascript generation to split the file later on. See emcc --split option. +var MAIN_MODULE = 0; // A main module is a file compiled in a way that allows us to link it to + // a side module using emlink.py. +var SIDE_MODULE = 0; // Corresponds to MAIN_MODULE + var BUILD_AS_SHARED_LIB = 0; // Whether to build the code as a shared library // 0 here means this is not a shared lib: It is a main file. // All shared library options (1 and 2) are currently deprecated XXX diff --git a/tests/runner.py b/tests/runner.py index 2ce72240..51745180 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -16,7 +16,7 @@ so you may prefer to use fewer cores here. ''' from subprocess import Popen, PIPE, STDOUT -import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, difflib, webbrowser, hashlib, threading, platform, BaseHTTPServer, multiprocessing, functools, stat +import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, re, difflib, webbrowser, hashlib, threading, platform, BaseHTTPServer, multiprocessing, functools, stat if len(sys.argv) == 1: print ''' @@ -265,6 +265,12 @@ process(sys.argv[1]) else: assert 'memory initializer */' in open(filename + '.o.js').read() + def validate_asmjs(self, err): + if 'uccessfully compiled asm.js code' in err and 'asm.js link error' not in err: + print >> sys.stderr, "[was asm.js'ified]" + elif 'asm.js' in err: # if no asm.js error, then not an odin build + raise Exception("did NOT asm.js'ify") + def run_generated_code(self, engine, filename, args=[], check_timeout=True, output_nicerizer=None): stdout = os.path.join(self.get_dir(), 'stdout') # use files, as PIPE can get too full and hang us stderr = os.path.join(self.get_dir(), 'stderr') @@ -279,10 +285,7 @@ process(sys.argv[1]) out = open(stdout, 'r').read() err = open(stderr, 'r').read() if engine == SPIDERMONKEY_ENGINE and Settings.ASM_JS: - if 'uccessfully compiled asm.js code' in err and 'asm.js link error' not in err: - print >> sys.stderr, "[was asm.js'ified]" - elif 'asm.js' in err: # if no asm.js error, then not an odin build - raise Exception("did NOT asm.js'ify") + self.validate_asmjs(err) if output_nicerizer: ret = output_nicerizer(out, err) else: @@ -8582,7 +8585,7 @@ def process(filename): del os.environ['EMCC_DEBUG'] for debug in [1,2]: def clean(text): - return text.replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n') + return text.replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('{\n}', '{}') 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 @@ -9637,7 +9640,9 @@ def process(filename): # objects when generating source maps, so we want to make sure the # optimizer can deal with both types. out_file = re.sub(' *//@.*$', '', out_file, flags=re.MULTILINE) - self.assertIdentical(no_maps_file, out_file) + def clean(code): + return code.replace('{\n}', '{}') + self.assertIdentical(clean(no_maps_file), clean(out_file)) map_filename = out_filename + '.map' data = json.load(open(map_filename, 'r')) self.assertIdentical(out_filename, data['file']) @@ -10606,6 +10611,215 @@ f.close() self.assertContained('hello from lib', run_js(os.path.join(self.get_dir(), 'a.out.js'))) assert not os.path.exists('a.out') and not os.path.exists('a.exe'), 'Must not leave unneeded linker stubs' + def test_static_link(self): + def test(name, header, main, side, expected, args=[], suffix='cpp', first=True): + print name + #t = main ; main = side ; side = t + original_main = main + original_side = side + if header: open(os.path.join(self.get_dir(), 'header.h'), 'w').write(header) + if type(main) == str: + open(os.path.join(self.get_dir(), 'main.' + suffix), 'w').write(main) + main = ['main.' + suffix] + if type(side) == str: + open(os.path.join(self.get_dir(), 'side.' + suffix), 'w').write(side) + side = ['side.' + suffix] + Popen([PYTHON, EMCC] + side + ['-o', 'side.js', '-s', 'SIDE_MODULE=1', '-O2'] + args).communicate() + # TODO: test with and without DISABLE_GL_EMULATION, check that file sizes change + Popen([PYTHON, EMCC] + main + ['-o', 'main.js', '-s', 'MAIN_MODULE=1', '-O2', '-s', 'DISABLE_GL_EMULATION=1'] + args).communicate() + Popen([PYTHON, EMLINK, 'main.js', 'side.js', 'together.js'], stdout=PIPE).communicate() + assert os.path.exists('together.js') + out = run_js('together.js', engine=SPIDERMONKEY_ENGINE, stderr=PIPE, full_output=True) + self.assertContained(expected, out) + self.validate_asmjs(out) + if first: + shutil.copyfile('together.js', 'first.js') + test(name + ' (reverse)', header, original_side, original_main, expected, args, suffix, False) # test reverse order + + # test a simple call from one module to another. only one has a string (and constant memory initialization for it) + test('basics', '', ''' + #include <stdio.h> + extern int sidey(); + int main() { + printf("other says %d.", sidey()); + return 0; + } + ''', ''' + int sidey() { return 11; } + ''', 'other says 11.') + + # finalization of float variables should pass asm.js validation + test('floats', '', ''' + #include <stdio.h> + extern float sidey(); + int main() { + printf("other says %.2f.", sidey()+1); + return 0; + } + ''', ''' + float sidey() { return 11.5; } + ''', 'other says 12.50') + + # memory initialization in both + test('multiple memory inits', '', r''' + #include <stdio.h> + extern void sidey(); + int main() { + printf("hello from main\n"); + sidey(); + return 0; + } + ''', r''' + #include <stdio.h> + void sidey() { printf("hello from side\n"); } + ''', 'hello from main\nhello from side\n') + + # function pointers + test('fp1', 'typedef void (*voidfunc)();', r''' + #include <stdio.h> + #include "header.h" + voidfunc sidey(voidfunc f); + void a() { printf("hello from funcptr\n"); } + int main() { + sidey(a)(); + return 0; + } + ''', ''' + #include "header.h" + voidfunc sidey(voidfunc f) { return f; } + ''', 'hello from funcptr\n') + + # Global initializer + test('global init', '', r''' + #include <stdio.h> + struct Class { + Class() { printf("a new Class\n"); } + }; + static Class c; + int main() { + return 0; + } + ''', r''' + void nothing() {} + ''', 'a new Class\n') + + # Multiple global initializers (LLVM generates overlapping names for them) + test('global inits', r''' + #include <stdio.h> + struct Class { + Class(const char *name) { printf("new %s\n", name); } + }; + ''', r''' + #include "header.h" + static Class c("main"); + int main() { + return 0; + } + ''', r''' + #include "header.h" + static Class c("side"); + ''', ['new main\nnew side\n', 'new side\nnew main\n']) + + # Class code used across modules + test('codecall', r''' + #include <stdio.h> + struct Class { + Class(const char *name); + }; + ''', r''' + #include "header.h" + int main() { + Class c("main"); + return 0; + } + ''', r''' + #include "header.h" + Class::Class(const char *name) { printf("new %s\n", name); } + ''', ['new main\n']) + + # malloc usage in both modules + test('malloc', r''' + #include <stdlib.h> + #include <string.h> + char *side(const char *data); + ''', r''' + #include <stdio.h> + #include "header.h" + int main() { + char *temp = side("hello through side\n"); + char *ret = (char*)malloc(strlen(temp)+1); + strcpy(ret, temp); + temp[1] = 'x'; + puts(ret); + return 0; + } + ''', r''' + #include "header.h" + char *side(const char *data) { + char *ret = (char*)malloc(strlen(data)+1); + strcpy(ret, data); + return ret; + } + ''', ['hello through side\n']) + + # libc usage in one modules. must force libc inclusion in the main module if that isn't the one using mallinfo() + try: + os.environ['EMCC_FORCE_STDLIBS'] = 'libc' + test('malloc-1', r''' + #include <string.h> + int side(); + ''', r''' + #include <stdio.h> + #include "header.h" + int main() { + printf("|%d|\n", side()); + return 0; + } + ''', r''' + #include <stdlib.h> + #include <malloc.h> + #include "header.h" + int side() { + struct mallinfo m = mallinfo(); + return m.arena > 1; + } + ''', ['|1|\n']) + finally: + del os.environ['EMCC_FORCE_STDLIBS'] + + # iostream usage in one and std::string in both + test('iostream', r''' + #include <iostream> + #include <string> + std::string side(); + ''', r''' + #include "header.h" + int main() { + std::cout << "hello from main " << side() << std::endl; + return 0; + } + ''', r''' + #include "header.h" + std::string side() { return "and hello from side"; } + ''', ['hello from main and hello from side\n']) + + # zlib compression library. tests function pointers in initializers and many other things + test('zlib', '', open(path_from_root('tests', 'zlib', 'example.c'), 'r').read(), + self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']), + open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(), + args=['-I' + path_from_root('tests', 'zlib')], suffix='c') + + # bullet physics engine. tests all the things + test('bullet', '', open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp'), 'r').read(), + self.get_library('bullet', [os.path.join('src', '.libs', 'libBulletDynamics.a'), + os.path.join('src', '.libs', 'libBulletCollision.a'), + os.path.join('src', '.libs', 'libLinearMath.a')]), + [open(path_from_root('tests', 'bullet', 'output.txt'), 'r').read(), # different roundings + open(path_from_root('tests', 'bullet', 'output2.txt'), 'r').read(), + open(path_from_root('tests', 'bullet', 'output3.txt'), 'r').read()], + args=['-I' + path_from_root('tests', 'bullet', 'src')]) + + def test_symlink(self): if os.name == 'nt': return self.skip('Windows FS does not need to be tested for symlinks support, since it does not have them.') @@ -13893,7 +14107,22 @@ elif 'sanity' in str(sys.argv): def test_firstrun(self): for command in commands: wipe() - output = self.do(command) + + def make_executable(name): + with open(os.path.join(temp_bin, name), 'w') as f: + os.fchmod(f.fileno(), stat.S_IRWXU) + + try: + temp_bin = tempfile.mkdtemp() + old_environ_path = os.environ['PATH'] + os.environ['PATH'] = temp_bin + os.pathsep + old_environ_path + make_executable('llvm-dis') + make_executable('node') + make_executable('python2') + output = self.do(command) + finally: + os.environ['PATH'] = old_environ_path + shutil.rmtree(temp_bin) self.assertContained('Welcome to Emscripten!', output) self.assertContained('This is the first time any of the Emscripten tools has been run.', output) @@ -13901,6 +14130,12 @@ elif 'sanity' in str(sys.argv): self.assertContained('It contains our best guesses for the important paths, which are:', output) self.assertContained('LLVM_ROOT', output) self.assertContained('NODE_JS', output) + self.assertContained('PYTHON', output) + if platform.system() is not 'Windows': + # os.chmod can't make files executable on Windows + self.assertIdentical(temp_bin, re.search("^ *LLVM_ROOT *= (.*)$", output, re.M).group(1)) + self.assertIdentical(os.path.join(temp_bin, 'node'), re.search("^ *NODE_JS *= (.*)$", output, re.M).group(1)) + self.assertIdentical(os.path.join(temp_bin, 'python2'), re.search("^ *PYTHON *= (.*)$", output, re.M).group(1)) self.assertContained('Please edit the file if any of those are incorrect', output) self.assertContained('This command will now exit. When you are done editing those paths, re-run it.', output) assert output.split()[-1].endswith('===='), 'We should have stopped: ' + output diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d04807a7..1de06e4c 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -405,13 +405,16 @@ function removeUnneededLabelSettings(ast) { // Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^'); +var COMPARE_OPS = set('<', '<=', '>', '>=', '==', '===', '!='); function simplifyExpressionsPre(ast) { - // Look for (x&A)<<B>>B and replace it with X&A if possible. - function simplifySignExtends(ast) { + // Simplify common expressions used to perform integer conversion operations + // in cases where no conversion is needed. + function simplifyIntegerConversions(ast) { traverse(ast, function(node, type) { if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' && node[2][0] === 'binary' && node[2][1] === '<<' && node[2][3][0] === 'num' && node[3][1] === node[2][3][1]) { + // Transform (x&A)<<B>>B to X&A. var innerNode = node[2][2]; var shifts = node[3][1]; if (innerNode[0] === 'binary' && innerNode[1] === '&' && innerNode[3][0] === 'num') { @@ -420,6 +423,15 @@ function simplifyExpressionsPre(ast) { return innerNode; } } + } else if (type === 'binary' && node[1] === '&' && node[3][0] === 'num') { + // Rewrite (X < Y) & 1 to (X < Y)|0. (Subsequent passes will eliminate + // the |0 if possible.) + var input = node[2]; + var amount = node[3][1]; + if (input[0] === 'binary' && (input[1] in COMPARE_OPS) && amount == 1) { + node[1] = '|'; + node[3][1] = 0; + } } }); } @@ -763,7 +775,7 @@ function simplifyExpressionsPre(ast) { } traverseGeneratedFunctions(ast, function(func) { - simplifySignExtends(func); + simplifyIntegerConversions(func); simplifyBitops(func); joinAdditions(func); // simplifyZeroComp(func); TODO: investigate performance @@ -2730,8 +2742,9 @@ function relocate(ast) { assert(asm); // we also assume we are normalized var replacements = extraInfo.replacements; - var fBase = extraInfo.fBase; + var fBases = extraInfo.fBases; var hBase = extraInfo.hBase; + var m; traverse(ast, function(node, type) { switch(type) { @@ -2743,13 +2756,14 @@ function relocate(ast) { case 'binary': { if (node[1] == '+' && node[2][0] == 'name') { var base = null; - if (node[2][1] == 'F_BASE') { - base = fBase; - } else if (node[2][1] == 'H_BASE') { + if (node[2][1] == 'H_BASE') { base = hBase; + } else if (m = /^F_BASE_(\w+)$/.exec(node[2][1])) { + base = fBases[m[1]] || 0; // 0 if the parent has no function table for this, but the child does. so no relocation needed } - if (base) { + if (base !== null) { var other = node[3]; + if (base === 0) return other; if (other[0] == 'num') { other[1] += base; return other; @@ -2887,7 +2901,7 @@ arguments_.slice(1).forEach(function(arg) { passes[arg](ast); }); if (asm && last) { - asmLoopOptimizer(ast); + asmLoopOptimizer(ast); // TODO: move out of last, to make last faster when done later (as in side modules) prepDotZero(ast); } var js = astToSrc(ast, minifyWhitespace), old; diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index 905ae835..a4e1ca6c 100644 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -20,6 +20,7 @@ WINDOWS = sys.platform.startswith('win') DEBUG = os.environ.get('EMCC_DEBUG') func_sig = re.compile('( *)function ([_\w$]+)\(') +import_sig = re.compile('var ([_\w$]+) *=[^;]+;') class Minifier: ''' @@ -95,6 +96,11 @@ class Minifier: 'globals': self.globs }) +start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n' +end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n' +start_asm_marker = '// EMSCRIPTEN_START_ASM\n' +end_asm_marker = '// EMSCRIPTEN_END_ASM\n' + def run_on_chunk(command): filename = command[2] # XXX hackish #print >> sys.stderr, 'running js optimizer command', ' '.join(command), '""""', open(filename).read() @@ -107,7 +113,7 @@ def run_on_chunk(command): if DEBUG and not shared.WINDOWS: print >> sys.stderr, '.' # Skip debug progress indicator on Windows, since it doesn't buffer well with multiple threads printing to console. return filename -def run_on_js(filename, passes, js_engine, jcache, source_map=False): +def run_on_js(filename, passes, js_engine, jcache, source_map=False, extra_info=None): if isinstance(jcache, bool) and jcache: jcache = shared.JCache if jcache: shared.JCache.ensure() @@ -129,17 +135,14 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False): generated = set(eval(suffix[len(suffix_marker)+1:])) # Find markers - start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n' - end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n' start_funcs = js.find(start_funcs_marker) end_funcs = js.rfind(end_funcs_marker) - #assert (start_funcs >= 0) == (end_funcs >= 0) == (not not suffix) + + know_generated = suffix or start_funcs >= 0 minify_globals = 'registerizeAndMinify' in passes and 'asm' in passes if minify_globals: passes = map(lambda p: p if p != 'registerizeAndMinify' else 'registerize', passes) - start_asm_marker = '// EMSCRIPTEN_START_ASM\n' - end_asm_marker = '// EMSCRIPTEN_END_ASM\n' start_asm = js.find(start_asm_marker) end_asm = js.rfind(end_asm_marker) assert (start_asm >= 0) == (end_asm >= 0) @@ -148,14 +151,14 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False): if closure: passes = filter(lambda p: p != 'closure', passes) # we will do it manually - if not suffix and jcache: + if not know_generated and jcache: # JCache cannot be used without metadata, since it might reorder stuff, and that's dangerous since only generated can be reordered # This means jcache does not work after closure compiler runs, for example. But you won't get much benefit from jcache with closure # anyhow (since closure is likely the longest part of the build). if DEBUG: print >>sys.stderr, 'js optimizer: no metadata, so disabling jcache' jcache = False - if suffix: + if know_generated: if not minify_globals: pre = js[:start_funcs + len(start_funcs_marker)] post = js[end_funcs + len(end_funcs_marker):] @@ -189,7 +192,7 @@ EMSCRIPTEN_FUNCS(); minify_info = minifier.serialize() #if DEBUG: print >> sys.stderr, 'minify info:', minify_info # remove suffix if no longer needed - if 'last' in passes: + if suffix and 'last' in passes: suffix_start = post.find(suffix_marker) suffix_end = post.find('\n', suffix_start) post = post[:suffix_start] + post[suffix_end:] @@ -209,7 +212,7 @@ EMSCRIPTEN_FUNCS(); if m: ident = m.group(2) else: - if suffix: continue # ignore whitespace + if know_generated: continue # ignore whitespace ident = 'anon_%d' % i assert ident funcs.append((ident, func)) @@ -249,8 +252,12 @@ EMSCRIPTEN_FUNCS(); f.write(chunk) f.write(suffix_marker) if minify_globals: + assert not extra_info f.write('\n') f.write('// EXTRA_INFO:' + minify_info) + elif extra_info: + f.write('\n') + f.write('// EXTRA_INFO:' + json.dumps(extra_info)) f.close() return temp_file filenames = [write_chunk(chunks[i], i) for i in range(len(chunks))] @@ -329,6 +336,6 @@ EMSCRIPTEN_FUNCS(); return filename -def run(filename, passes, js_engine, jcache, source_map=False): - return temp_files.run_and_clean(lambda: run_on_js(filename, passes, js_engine, jcache, source_map)) +def run(filename, passes, js_engine, jcache, source_map=False, extra_info=None): + return temp_files.run_and_clean(lambda: run_on_js(filename, passes, js_engine, jcache, source_map, extra_info)) diff --git a/tools/shared.py b/tools/shared.py index 776001cd..d35924c6 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -205,7 +205,7 @@ else: config_file = '\n'.join(config_file) # autodetect some default paths config_file = config_file.replace('{{{ EMSCRIPTEN_ROOT }}}', __rootpath__) - llvm_root = find_executable('llvm-dis') or '/usr/bin' + llvm_root = os.path.dirname(find_executable('llvm-dis') or '/usr/bin/llvm-dis') config_file = config_file.replace('{{{ LLVM_ROOT }}}', llvm_root) node = find_executable('node') or find_executable('nodejs') or 'node' config_file = config_file.replace('{{{ NODE }}}', node) @@ -400,6 +400,7 @@ EMAR = path_from_root('emar') EMRANLIB = path_from_root('emranlib') EMLIBTOOL = path_from_root('emlibtool') EMCONFIG = path_from_root('em-config') +EMLINK = path_from_root('emlink.py') EMMAKEN = path_from_root('tools', 'emmaken.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') @@ -866,7 +867,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e return generated_libs @staticmethod - def link(files, target): + def link(files, target, force_archive_contents=False): actual_files = [] unresolved_symbols = set(['main']) # tracking unresolveds is necessary for .a linking, see below. (and main is always a necessary symbol) resolved_symbols = set() @@ -917,7 +918,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # Link in the .o if it provides symbols, *or* this is a singleton archive (which is apparently an exception in gcc ld) #print >> sys.stderr, 'need', content, '?', unresolved_symbols, 'and we can supply', new_symbols.defs #print >> sys.stderr, content, 'DEF', new_symbols.defs, '\n' - if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1: + if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1 or force_archive_contents: if Building.is_bitcode(content): #print >> sys.stderr, ' adding object', content, '\n' resolved_symbols = resolved_symbols.union(new_symbols.defs) @@ -1100,6 +1101,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e @staticmethod def get_safe_internalize(): + if not Building.can_build_standalone(): return [] # do not internalize anything exps = expand_response(Settings.EXPORTED_FUNCTIONS) if '_malloc' not in exps: exps.append('_malloc') # needed internally, even if user did not add to EXPORTED_FUNCTIONS exports = ','.join(map(lambda exp: exp[1:], exps)) @@ -1209,8 +1211,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e return opts @staticmethod - def js_optimizer(filename, passes, jcache, debug): - return js_optimizer.run(filename, passes, listify(NODE_JS), jcache, debug) + def js_optimizer(filename, passes, jcache=False, debug=False, extra_info=None): + return js_optimizer.run(filename, passes, listify(NODE_JS), jcache, debug, extra_info) @staticmethod def closure_compiler(filename, pretty=True): @@ -1350,9 +1352,35 @@ JCache = cache.JCache(Cache) chunkify = cache.chunkify class JS: + memory_initializer_pattern = '/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, ([\dRuntime\.GLOBAL_BASE+]+)\)' + no_memory_initializer_pattern = '/\* no memory initializer \*/' + + memory_staticbump_pattern = 'STATICTOP = STATIC_BASE \+ (\d+);' + + global_initializers_pattern = '/\* global initializers \*/ __ATINIT__.push\((.+)\);' + @staticmethod def to_nice_ident(ident): # limited version of the JS function toNiceIdent - return ident.replace('%', '$').replace('@', '_'); + return ident.replace('%', '$').replace('@', '_') + + @staticmethod + def make_invoke(sig, named=True): + args = ','.join(['a' + str(i) for i in range(1, len(sig))]) + args = 'index' + (',' if args else '') + args + # C++ exceptions are numbers, and longjmp is a string 'longjmp' + return '''function%s(%s) { + try { + %sModule["dynCall_%s"](%s); + } catch(e) { + if (typeof e !== 'number' && e !== 'longjmp') throw e; + asm["setThrew"](1, 0); + } +}''' % ((' invoke_' + sig) if named else '', args, 'return ' if sig[0] != 'v' else '', sig, args) + + @staticmethod + def align(x, by): + while x % by != 0: x += 1 + return x # Compression of code and data for smaller downloads class Compression: diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index 301a2ec8..59a42010 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -119,6 +119,16 @@ function sign_extension_simplification() { print(5); } } +function compare_result_simplification() { + HEAP32[$4] = HEAP32[$5] < HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] > HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] <= HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] <= HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] == HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] === HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] != HEAP32[$6]; + var x = HEAP32[$5] != HEAP32[$6] | 0; +} function tempDoublePtr($45, $14, $28, $42) { $45 = $45 | 0; $14 = $14 | 0; diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index c7c92124..7ca941fa 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -122,6 +122,18 @@ function sign_extension_simplification() { print(5); } } +function compare_result_simplification() { + // Eliminate these '&1's. + HEAP32[$4] = (HEAP32[$5] < HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] > HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] == HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] === HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] != HEAP32[$6]) & 1; + // Convert the &1 to |0 here, since we don't get an implicit coersion. + var x = (HEAP32[$5] != HEAP32[$6]) & 1; +} function tempDoublePtr($45, $14, $28, $42) { $45 = $45 | 0; $14 = $14 | 0; diff --git a/tools/test-js-optimizer-asm-relocate-output.js b/tools/test-js-optimizer-asm-relocate-output.js index 6a197e81..2f8294c5 100644 --- a/tools/test-js-optimizer-asm-relocate-output.js +++ b/tools/test-js-optimizer-asm-relocate-output.js @@ -1,9 +1,9 @@ function leaveMeAlone(c) {} function fixed(a, b) {} function a(x, y) { - fixed(34, 12); - fixed(34 | 0, 12 | 0); - leaveMeAlone(10 + x, 33 + y); - leaveMeAlone(10 + x | 0, 33 + y | 0); + fixed(34, 4); + fixed(34 | 0, 102 | 0); + leaveMeAlone(2 + x, 33 + y); + leaveMeAlone(x | 0, 33 + y | 0); } diff --git a/tools/test-js-optimizer-asm-relocate.js b/tools/test-js-optimizer-asm-relocate.js index a45bc2f0..5402c9f6 100644 --- a/tools/test-js-optimizer-asm-relocate.js +++ b/tools/test-js-optimizer-asm-relocate.js @@ -3,10 +3,10 @@ function leaveMeAlone(c) { function replaceMe(a, b) { } function a(x, y) { - replaceMe(H_BASE + 1, F_BASE + 2); - replaceMe(H_BASE + 1 | 0, F_BASE + 2 | 0); - leaveMeAlone(F_BASE + x, H_BASE + y); - leaveMeAlone(F_BASE + x | 0, H_BASE + y | 0); + replaceMe(H_BASE + 1, F_BASE_vii + 2); + replaceMe(H_BASE + 1 | 0, F_BASE_vi + 2 | 0); + leaveMeAlone(F_BASE_vii + x, H_BASE + y); + leaveMeAlone(F_BASE_vUNKNOWN + x | 0, H_BASE + y | 0); } // EMSCRIPTEN_GENERATED_FUNCTIONS -// EXTRA_INFO: { "replacements": { "replaceMe": "fixed" }, "hBase": 33, "fBase": 10 } +// EXTRA_INFO: { "replacements": { "replaceMe": "fixed" }, "hBase": 33, "fBases": { "vii": 2, "vi": 100, "v": 20 } } |