def load_metadata_wasm(metadata_raw, DEBUG): try: metadata_json = json.loads(metadata_raw) except Exception: logger.error( 'emscript: failure to parse metadata output from wasm-emscripten-finalize. raw output is: \n' + metadata_raw) raise metadata = { 'declares': [], 'globalImports': [], 'staticBump': 0, 'tableSize': 0, 'exports': [], 'namedGlobals': {}, 'emJsFuncs': {}, 'asmConsts': {}, 'invokeFuncs': [], 'features': [], 'mainReadsParams': 1, } legacy_keys = set( ['implementedFunctions', 'initializers', 'simd', 'externs']) assert 'tableSize' in metadata_json.keys() for key, value in metadata_json.items(): if key in legacy_keys: continue if key not in metadata: exit_with_error( 'unexpected metadata key received from wasm-emscripten-finalize: %s', key) metadata[key] = value # Support older metadata when asmConsts values were lists. We only use the first element # nowadays # TODO(sbc): remove this once binaryen has been changed to only emit the single element metadata['asmConsts'] = { k: v[0] if type(v) is list else v for k, v in metadata['asmConsts'].items() } if DEBUG: logger.debug("Metadata parsed: " + pprint.pformat(metadata)) # Calculate the subset of exports that were explicitly marked with llvm.used. # These are any exports that were not requested on the command line and are # not known auto-generated system functions. unexpected_exports = [ e for e in metadata['exports'] if treat_as_user_function(e) ] unexpected_exports = [asmjs_mangle(e) for e in unexpected_exports] unexpected_exports = [ e for e in unexpected_exports if e not in settings.EXPORTED_FUNCTIONS ] building.user_requested_exports.update(unexpected_exports) settings.EXPORTED_FUNCTIONS.extend(unexpected_exports) return metadata
def create_sending(invoke_funcs, metadata): em_js_funcs = set(metadata['emJsFuncs'].keys()) declares = [asmjs_mangle(d) for d in metadata['declares']] externs = [asmjs_mangle(e) for e in metadata['globalImports']] send_items = set(invoke_funcs + declares + externs) send_items.update(em_js_funcs) def fix_import_name(g): # Unlike fastcomp the wasm backend doesn't use the '_' prefix for native # symbols. Emscripten currently expects symbols to start with '_' so we # artificially add them to the output of emscripten-wasm-finalize and them # strip them again here. # note that we don't do this for EM_JS functions (which, rarely, may have # a '_' prefix) if g.startswith('_') and g not in em_js_funcs: return g[1:] return g send_items_map = OrderedDict() for name in send_items: internal_name = fix_import_name(name) if internal_name in send_items_map: exit_with_error('duplicate symbol in exports to wasm: %s', name) send_items_map[internal_name] = name add_standard_wasm_imports(send_items_map) sorted_keys = sorted(send_items_map.keys()) return '{\n ' + ',\n '.join('"' + k + '": ' + send_items_map[k] for k in sorted_keys) + '\n}'
def report_missing_symbols(js_library_funcs): # Report any symbol that was explicitly exported but is present neither # as a native function nor as a JS library function. defined_symbols = set( asmjs_mangle(e) for e in settings.WASM_EXPORTS).union(js_library_funcs) missing = set(settings.USER_EXPORTED_FUNCTIONS) - defined_symbols for symbol in sorted(missing): diagnostics.warning('undefined', f'undefined exported symbol: "{symbol}"') # Special hanlding for the `_main` symbol if settings.STANDALONE_WASM: # standalone mode doesn't use main, and it always reports missing entry point at link time. # In this mode we never expect _main in the export list. return # PROXY_TO_PTHREAD only makes sense with a main(), so error if one is # missing. note that when main() might arrive from another module we cannot # error here. if settings.PROXY_TO_PTHREAD and '_main' not in defined_symbols and \ not settings.RELOCATABLE: exit_with_error( 'PROXY_TO_PTHREAD proxies main() for you, but no main exists') if settings.IGNORE_MISSING_MAIN: # The default mode for emscripten is to ignore the missing main function allowing # maximum compatibility. return if settings.EXPECT_MAIN and 'main' not in settings.WASM_EXPORTS: # For compatibility with the output of wasm-ld we use the same wording here in our # error message as if wasm-ld had failed (i.e. in LLD_REPORT_UNDEFINED mode). exit_with_error( 'entry symbol not defined (pass --no-entry to suppress): main')
def load_metadata_json(metadata_raw): try: metadata_json = json.loads(metadata_raw) except Exception: logger.error( 'emscript: failure to parse metadata output from wasm-emscripten-finalize. raw output is: \n' + metadata_raw) raise metadata = { 'declares': [], 'globalImports': [], 'exports': [], 'namedGlobals': {}, 'emJsFuncs': {}, 'asmConsts': {}, 'invokeFuncs': [], 'features': [], 'mainReadsParams': 1, } for key, value in metadata_json.items(): if key not in metadata: exit_with_error( 'unexpected metadata key received from wasm-emscripten-finalize: %s', key) metadata[key] = value if DEBUG: logger.debug("Metadata parsed: " + pprint.pformat(metadata)) return metadata
def error_on_legacy_suite_names(args): for a in args: if a.startswith('wasm') and not any( a.startswith(p) for p in ('wasm2js', 'wasmfs', 'wasm64')): new = a.replace('wasm', 'core', 1) utils.exit_with_error( '`%s` test suite has been replaced with `%s`', a, new)
def c_to_s(c): if c == 'WASM_RT_I32': return 'i' elif c == 'WASM_RT_I64': return 'j' elif c == 'WASM_RT_F32': return 'f' elif c == 'WASM_RT_F64': return 'd' else: exit_with_error('invalid wasm2c type element:' + str(c))
def s_to_c(s): if s == 'v': return 'void' elif s == 'i': return 'u32' elif s == 'j': return 'u64' elif s == 'f': return 'f32' elif s == 'd': return 'f64' else: exit_with_error('invalid sig element:' + str(s))
def load_metadata_json(metadata_raw): try: metadata_json = json.loads(metadata_raw) except Exception: logger.error( 'emscript: failure to parse metadata output from wasm-emscripten-finalize. raw output is: \n' + metadata_raw) raise metadata = { 'declares': [], 'globalImports': [], 'exports': [], 'namedGlobals': {}, 'emJsFuncs': {}, 'asmConsts': {}, 'invokeFuncs': [], 'features': [], 'mainReadsParams': 1, } for key, value in metadata_json.items(): if key not in metadata: exit_with_error( 'unexpected metadata key received from wasm-emscripten-finalize: %s', key) metadata[key] = value if DEBUG: logger.debug("Metadata parsed: " + pprint.pformat(metadata)) expected_exports = set(settings.EXPORTED_FUNCTIONS) expected_exports.update(asmjs_mangle(s) for s in settings.REQUIRED_EXPORTS) # Calculate the subset of exports that were explicitly marked with llvm.used. # These are any exports that were not requested on the command line and are # not known auto-generated system functions. unexpected_exports = [ e for e in metadata['exports'] if treat_as_user_function(e) ] unexpected_exports = [asmjs_mangle(e) for e in unexpected_exports] unexpected_exports = [ e for e in unexpected_exports if e not in expected_exports ] building.user_requested_exports.update(unexpected_exports) settings.EXPORTED_FUNCTIONS.extend(unexpected_exports) return metadata
def compare_metadata(metadata, pymetadata): if sorted(metadata.keys()) != sorted(pymetadata.keys()): print(sorted(metadata.keys())) print(sorted(pymetadata.keys())) exit_with_error('metadata keys mismatch') for key in metadata: old = metadata[key] new = pymetadata[key] if key == 'features': old = sorted(old) new = sorted(new) if old != new: print(key) open(path_from_root('first.txt'), 'w').write(pprint.pformat(old)) open(path_from_root('second.txt'), 'w').write(pprint.pformat(new)) print(pprint.pformat(old)) print(pprint.pformat(new)) exit_with_error('metadata mismatch')
def retrieve(): # retrieve from remote server logger.info(f'retrieving port: {name} from {url}') try: import requests response = requests.get(url) data = response.content except ImportError: from urllib.request import urlopen f = urlopen(url) data = f.read() if sha512hash: actual_hash = hashlib.sha512(data).hexdigest() if actual_hash != sha512hash: utils.exit_with_error( f'Unexpected hash: {actual_hash}\n' 'If you are updating the port, please update the hash.' ) utils.write_binary(fullpath, data)
def read_ports(): expected_attrs = ['get', 'clear', 'process_args', 'show', 'needed'] for filename in os.listdir(ports_dir): if not filename.endswith('.py') or filename == '__init__.py': continue filename = os.path.splitext(filename)[0] port = __import__(filename, globals(), level=1) ports.append(port) port.name = filename ports_by_name[port.name] = port for a in expected_attrs: assert hasattr(port, a), 'port %s is missing %s' % (port, a) if not hasattr(port, 'process_dependencies'): port.process_dependencies = lambda x: 0 if not hasattr(port, 'linker_setup'): port.linker_setup = lambda x, y: 0 if not hasattr(port, 'deps'): port.deps = [] for dep in port.deps: if dep not in ports_by_name: utils.exit_with_error('unknown dependency in port: %s' % dep)
def emscript(in_wasm, out_wasm, outfile_js, memfile, DEBUG): # Overview: # * Run wasm-emscripten-finalize to extract metadata and modify the binary # to use emscripten's wasm<->JS ABI # * Use the metadata to generate the JS glue that goes with the wasm if settings.SINGLE_FILE: # placeholder strings for JS glue, to be replaced with subresource locations in do_binaryen settings.WASM_BINARY_FILE = '<<< WASM_BINARY_FILE >>>' else: # set file locations, so that JS glue can find what it needs settings.WASM_BINARY_FILE = js_manipulation.escape_for_js_string( os.path.basename(out_wasm)) metadata = finalize_wasm(in_wasm, out_wasm, memfile, DEBUG) update_settings_glue(metadata, DEBUG) if settings.SIDE_MODULE: if metadata['asmConsts']: exit_with_error('EM_ASM is not supported in side modules') if metadata['emJsFuncs']: exit_with_error('EM_JS is not supported in side modules') logger.debug('emscript: skipping remaining js glue generation') return if DEBUG: logger.debug('emscript: js compiler glue') t = time.time() # memory and global initializers if settings.RELOCATABLE: dylink_sec = webassembly.parse_dylink_section(in_wasm) static_bump = align_memory(dylink_sec.mem_size) set_memory(static_bump) logger.debug('stack_base: %d, stack_max: %d, heap_base: %d', settings.STACK_BASE, settings.STACK_MAX, settings.HEAP_BASE) # When building relocatable output (e.g. MAIN_MODULE) the reported table # size does not include the reserved slot at zero for the null pointer. # So we need to offset the elements by 1. if settings.INITIAL_TABLE == -1: settings.INITIAL_TABLE = dylink_sec.table_size + 1 glue, forwarded_data = compile_settings() if DEBUG: logger.debug(' emscript: glue took %s seconds' % (time.time() - t)) t = time.time() forwarded_json = json.loads(forwarded_data) pre, post = glue.split('// EMSCRIPTEN_END_FUNCS') exports = metadata['exports'] if settings.ASYNCIFY: exports += [ 'asyncify_start_unwind', 'asyncify_stop_unwind', 'asyncify_start_rewind', 'asyncify_stop_rewind' ] report_missing_symbols(forwarded_json['libraryFunctions']) if not outfile_js: logger.debug('emscript: skipping remaining js glue generation') return if settings.MINIMAL_RUNTIME: # In MINIMAL_RUNTIME, atinit exists in the postamble part post = apply_static_code_hooks(forwarded_json, post) else: # In regular runtime, atinits etc. exist in the preamble part pre = apply_static_code_hooks(forwarded_json, pre) asm_consts = create_asm_consts(metadata) em_js_funcs = create_em_js(metadata) asm_const_pairs = ['%s: %s' % (key, value) for key, value in asm_consts] asm_const_map = 'var ASM_CONSTS = {\n ' + ', \n '.join( asm_const_pairs) + '\n};\n' pre = pre.replace('// === Body ===', ('// === Body ===\n\n' + asm_const_map + '\n'.join(em_js_funcs) + '\n')) with open(outfile_js, 'w') as out: out.write(normalize_line_endings(pre)) pre = None invoke_funcs = metadata['invokeFuncs'] sending = create_sending(invoke_funcs, metadata) receiving = create_receiving(exports) if settings.MINIMAL_RUNTIME: if settings.DECLARE_ASM_MODULE_EXPORTS: post = compute_minimal_runtime_initializer_and_exports( post, exports, receiving) receiving = '' module = create_module(sending, receiving, invoke_funcs, metadata) write_output_file(out, module) out.write(normalize_line_endings(post)) module = None
def fetch_project(name, url, subdir, sha512hash=None): # To compute the sha512 hash, run `curl URL | sha512sum`. fullname = os.path.join(Ports.get_dir(), name) # EMCC_LOCAL_PORTS: A hacky way to use a local directory for a port. This # is not tested but can be useful for debugging # changes to a port. # # if EMCC_LOCAL_PORTS is set, we use a local directory as our ports. This is useful # for testing. This env var should be in format # name=dir,name=dir # e.g. # sdl2=/home/username/dev/ports/SDL2 # so you could run # EMCC_LOCAL_PORTS="sdl2=/home/alon/Dev/ports/SDL2" ./tests/runner.py browser.test_sdl2_mouse # this will simply copy that directory into the ports directory for sdl2, and use that. It also # clears the build, so that it is rebuilt from that source. local_ports = os.environ.get('EMCC_LOCAL_PORTS') if local_ports: logger.warning('using local ports: %s' % local_ports) local_ports = [ pair.split('=', 1) for pair in local_ports.split(',') ] with shared.Cache.lock(): for local in local_ports: if name == local[0]: path = local[1] if name not in ports_by_name: utils.exit_with_error('%s is not a known port' % name) port = ports_by_name[name] if not hasattr(port, 'SUBDIR'): logger.error( f'port {name} lacks .SUBDIR attribute, which we need in order to override it locally, please update it' ) sys.exit(1) subdir = port.SUBDIR target = os.path.join(fullname, subdir) if os.path.exists(target) and not dir_is_newer( path, target): logger.warning( f'not grabbing local port: {name} from {path} to {fullname} (subdir: {subdir}) as the destination {target} is newer (run emcc --clear-ports if that is incorrect)' ) else: logger.warning( f'grabbing local port: {name} from {path} to {fullname} (subdir: {subdir})' ) shared.try_delete(fullname) shutil.copytree(path, target) Ports.clear_project_build(name) return url_filename = url.rsplit('/')[-1] ext = url_filename.split('.', 1)[1] fullpath = fullname + '.' + ext if name not in Ports.name_cache: # only mention each port once in log logger.debug(f'including port: {name}') logger.debug(f' (at {fullname})') Ports.name_cache.add(name) def retrieve(): # retrieve from remote server logger.info(f'retrieving port: {name} from {url}') try: import requests response = requests.get(url) data = response.content except ImportError: from urllib.request import urlopen f = urlopen(url) data = f.read() if sha512hash: actual_hash = hashlib.sha512(data).hexdigest() if actual_hash != sha512hash: utils.exit_with_error( f'Unexpected hash: {actual_hash}\n' 'If you are updating the port, please update the hash.' ) utils.write_binary(fullpath, data) marker = os.path.join(fullname, '.emscripten_url') def unpack(): logger.info(f'unpacking port: {name}') shared.safe_ensure_dirs(fullname) shutil.unpack_archive(filename=fullpath, extract_dir=fullname) utils.write_file(marker, url + '\n') def up_to_date(): if os.path.exists(marker): if utils.read_file(marker).strip() == url: return True return False # before acquiring the lock we have an early out if the port already exists if up_to_date(): return # main logic. do this under a cache lock, since we don't want multiple jobs to # retrieve the same port at once with shared.Cache.lock(): if os.path.exists(fullpath): # Another early out in case another process build the library while we were # waiting for the lock if up_to_date(): return # file exists but tag is bad logger.warning( 'local copy of port is not correct, retrieving from remote server' ) shared.try_delete(fullname) shared.try_delete(fullpath) retrieve() unpack() # we unpacked a new version, clear the build in the cache Ports.clear_project_build(name)
def do_wasm2c(infile): assert settings.STANDALONE_WASM WASM2C = config.NODE_JS + [path_from_root('node_modules/wasm2c/wasm2c.js')] WASM2C_DIR = path_from_root('node_modules/wasm2c') c_file = unsuffixed(infile) + '.wasm.c' h_file = unsuffixed(infile) + '.wasm.h' cmd = WASM2C + [infile, '-o', c_file] check_call(cmd) total = '''\ /* * This file was generated by emcc+wasm2c. To compile it, use something like * * $CC FILE.c -O2 -lm -DWASM_RT_MAX_CALL_STACK_DEPTH=8000 */ ''' SEP = '\n/* ==================================== */\n' def bundle_file(filename): nonlocal total with open(filename) as f: total += '// ' + filename + '\n' + f.read() + SEP # hermeticize the C file, by bundling in the wasm2c/ includes headers = [(WASM2C_DIR, 'wasm-rt.h'), (WASM2C_DIR, 'wasm-rt-impl.h'), ('', h_file)] for header in headers: bundle_file(os.path.join(header[0], header[1])) # add the wasm2c output bundle_file(c_file) # add the wasm2c runtime bundle_file(os.path.join(WASM2C_DIR, 'wasm-rt-impl.c')) # add the support code support_files = ['base.c'] if settings.AUTODEBUG: support_files.append('autodebug.c') if settings.EXPECT_MAIN: # TODO: add an option for direct OS access. For now, do that when building # an executable with main, as opposed to a library support_files.append('os.c') support_files.append('main.c') else: support_files.append('os_sandboxed.c') support_files.append('reactor.c') # for a reactor, also append wasmbox_* API definitions with open(h_file, 'a') as f: f.write(''' // wasmbox_* API // TODO: optional prefixing extern void wasmbox_init(void); ''') for support_file in support_files: bundle_file(path_from_root(f'tools/wasm2c/{support_file}')) # remove #includes of the headers we bundled for header in headers: total = total.replace('#include "%s"\n' % header[1], '/* include of %s */\n' % header[1]) # generate the necessary invokes invokes = [] for sig in re.findall(r"\/\* import\: 'env' 'invoke_(\w+)' \*\/", total): all_func_types = get_func_types(total) def name(i): return 'a' + str(i) wabt_sig = sig[0] + 'i' + sig[1:] typed_args = [ s_to_c(sig[i]) + ' ' + name(i) for i in range(1, len(sig)) ] full_typed_args = ['u32 fptr'] + typed_args types = [s_to_c(sig[i]) for i in range(1, len(sig))] args = [name(i) for i in range(1, len(sig))] c_func_type = s_to_c( sig[0]) + ' (*)(' + (', '.join(types) if types else 'void') + ')' if sig not in all_func_types: exit_with_error('could not find signature ' + sig + ' in function types ' + str(all_func_types)) type_index = all_func_types[sig] invokes.append( r''' IMPORT_IMPL(%(return_type)s, Z_envZ_invoke_%(sig)sZ_%(wabt_sig)s, (%(full_typed_args)s), { VERBOSE_LOG("invoke\n"); // waka u32 sp = WASM_RT_ADD_PREFIX(Z_stackSaveZ_iv)(); if (next_setjmp >= MAX_SETJMP_STACK) { abort_with_message("too many nested setjmps"); } u32 id = next_setjmp++; int result = setjmp(setjmp_stack[id]); %(declare_return)s if (result == 0) { %(receive)sCALL_INDIRECT(w2c___indirect_function_table, %(c_func_type)s, %(type_index)s, fptr %(args)s); /* if we got here, no longjmp or exception happened, we returned normally */ } else { /* A longjmp or an exception took us here. */ WASM_RT_ADD_PREFIX(Z_stackRestoreZ_vi)(sp); WASM_RT_ADD_PREFIX(Z_setThrewZ_vii)(1, 0); } next_setjmp--; %(return)s }); ''' % { 'return_type': s_to_c(sig[0]) if sig[0] != 'v' else 'void', 'sig': sig, 'wabt_sig': wabt_sig, 'full_typed_args': ', '.join(full_typed_args), 'type_index': type_index, 'c_func_type': c_func_type, 'args': (', ' + ', '.join(args)) if args else '', 'declare_return': (s_to_c(sig[0]) + ' returned_value = 0;') if sig[0] != 'v' else '', 'receive': 'returned_value = ' if sig[0] != 'v' else '', 'return': 'return returned_value;' if sig[0] != 'v' else '' }) total += '\n'.join(invokes) # adjust sandboxing TRAP_OOB = 'TRAP(OOB)' assert total.count(TRAP_OOB) == 2 if settings.WASM2C_SANDBOXING == 'full': pass # keep it elif settings.WASM2C_SANDBOXING == 'none': total = total.replace(TRAP_OOB, '{}') elif settings.WASM2C_SANDBOXING == 'mask': assert not settings.ALLOW_MEMORY_GROWTH assert (settings.INITIAL_MEMORY & (settings.INITIAL_MEMORY - 1)) == 0, 'poewr of 2' total = total.replace(TRAP_OOB, '{}') MEM_ACCESS = '[addr]' assert total.count(MEM_ACCESS) == 3, '2 from wasm2c, 1 from runtime' total = total.replace(MEM_ACCESS, '[addr & %d]' % (settings.INITIAL_MEMORY - 1)) else: exit_with_error('bad sandboxing') # adjust prefixing: emit simple output that works with multiple libraries, # each compiled into its own single .c file, by adding 'static' in some places # TODO: decide on the proper pattern for this in an upstream discussion in # wasm2c; another option would be to prefix all these things. for rep in [ 'uint32_t wasm_rt_register_func_type(', 'void wasm_rt_trap(', 'void wasm_rt_allocate_memory(', 'uint32_t wasm_rt_grow_memory(', 'void wasm_rt_allocate_table(', 'jmp_buf g_jmp_buf', 'uint32_t g_func_type_count', 'FuncType* g_func_types', 'uint32_t wasm_rt_call_stack_depth', 'uint32_t g_saved_call_stack_depth', ]: # remove 'extern' from declaration total = total.replace('extern ' + rep, rep) # add 'static' to implementation old = total total = total.replace(rep, 'static ' + rep) assert old != total, f'did not find "{rep}"' # write out the final file with open(c_file, 'w') as out: out.write(total)
def emscript(in_wasm, out_wasm, outfile_js, memfile): # Overview: # * Run wasm-emscripten-finalize to extract metadata and modify the binary # to use emscripten's wasm<->JS ABI # * Use the metadata to generate the JS glue that goes with the wasm if settings.SINGLE_FILE: # placeholder strings for JS glue, to be replaced with subresource locations in do_binaryen settings.WASM_BINARY_FILE = '<<< WASM_BINARY_FILE >>>' else: # set file locations, so that JS glue can find what it needs settings.WASM_BINARY_FILE = js_manipulation.escape_for_js_string( os.path.basename(out_wasm)) metadata = finalize_wasm(in_wasm, out_wasm, memfile) update_settings_glue(out_wasm, metadata) if not settings.WASM_BIGINT and metadata['emJsFuncs']: module = webassembly.Module(in_wasm) types = module.get_types() import_map = {} for imp in module.get_imports(): import_map[imp.field] = imp for em_js_func, raw in metadata.get('emJsFuncs', {}).items(): c_sig = raw.split('<::>')[0].strip('()') if not c_sig or c_sig == 'void': c_sig = [] else: c_sig = c_sig.split(',') if em_js_func in import_map: imp = import_map[em_js_func] assert (imp.kind == webassembly.ExternType.FUNC) signature = types[imp.type] if len(signature.params) != len(c_sig): diagnostics.warning( 'em-js-i64', 'using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `%s` (%s, %s)', em_js_func, c_sig, signature.params) if settings.SIDE_MODULE: if metadata['asmConsts']: exit_with_error('EM_ASM is not supported in side modules') if metadata['emJsFuncs']: exit_with_error('EM_JS is not supported in side modules') logger.debug('emscript: skipping remaining js glue generation') return if DEBUG: logger.debug('emscript: js compiler glue') t = time.time() # memory and global initializers if settings.RELOCATABLE: dylink_sec = webassembly.parse_dylink_section(in_wasm) static_bump = align_memory(dylink_sec.mem_size) set_memory(static_bump) logger.debug('stack_base: %d, stack_max: %d, heap_base: %d', settings.STACK_BASE, settings.STACK_MAX, settings.HEAP_BASE) # When building relocatable output (e.g. MAIN_MODULE) the reported table # size does not include the reserved slot at zero for the null pointer. # So we need to offset the elements by 1. if settings.INITIAL_TABLE == -1: settings.INITIAL_TABLE = dylink_sec.table_size + 1 if settings.ASYNCIFY: metadata['globalImports'] += [ '__asyncify_state', '__asyncify_data' ] invoke_funcs = metadata['invokeFuncs'] if invoke_funcs: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getWasmTableEntry'] glue, forwarded_data = compile_settings() if DEBUG: logger.debug(' emscript: glue took %s seconds' % (time.time() - t)) t = time.time() forwarded_json = json.loads(forwarded_data) if forwarded_json['warnings']: diagnostics.warning('js-compiler', 'warnings in JS library compilation') pre, post = glue.split('// EMSCRIPTEN_END_FUNCS') if settings.ASSERTIONS: pre += "function checkIncomingModuleAPI() {\n" for sym in settings.ALL_INCOMING_MODULE_JS_API: if sym not in settings.INCOMING_MODULE_JS_API: pre += f" ignoredModuleProp('{sym}');\n" pre += "}\n" exports = metadata['exports'] if settings.ASYNCIFY: exports += [ 'asyncify_start_unwind', 'asyncify_stop_unwind', 'asyncify_start_rewind', 'asyncify_stop_rewind' ] report_missing_symbols(forwarded_json['librarySymbols']) if not outfile_js: logger.debug('emscript: skipping remaining js glue generation') return if settings.MINIMAL_RUNTIME: # In MINIMAL_RUNTIME, atinit exists in the postamble part post = apply_static_code_hooks(forwarded_json, post) else: # In regular runtime, atinits etc. exist in the preamble part pre = apply_static_code_hooks(forwarded_json, pre) asm_consts = create_asm_consts(metadata) em_js_funcs = create_em_js(metadata) asm_const_pairs = ['%s: %s' % (key, value) for key, value in asm_consts] asm_const_map = 'var ASM_CONSTS = {\n ' + ', \n '.join( asm_const_pairs) + '\n};\n' pre = pre.replace('// === Body ===', ('// === Body ===\n\n' + asm_const_map + '\n'.join(em_js_funcs) + '\n')) with open(outfile_js, 'w', encoding='utf-8') as out: out.write(normalize_line_endings(pre)) pre = None sending = create_sending(invoke_funcs, metadata) receiving = create_receiving(exports) if settings.MINIMAL_RUNTIME: if settings.DECLARE_ASM_MODULE_EXPORTS: post = compute_minimal_runtime_initializer_and_exports( post, exports, receiving) receiving = '' module = create_module(sending, receiving, invoke_funcs, metadata) write_output_file(out, module) out.write(normalize_line_endings(post)) module = None