# Copyright 2011 The Emscripten Authors. All rights reserved. # Emscripten is available under two separate licenses, the MIT license and the # University of Illinois/NCSA Open Source License. Both these licenses can be # found in the LICENSE file. import base64 import json import logging import os import re import shlex import shutil import stat from subprocess import PIPE from urllib.parse import quote from . import ( building, cache, config, diagnostics, emscripten, extract_metadata, feature_matrix, js_manipulation, ports, shared, system_libs, utils, webassembly, ) from .cmdline import OFormat from .feature_matrix import Feature from .minimal_runtime_shell import generate_minimal_runtime_html from .settings import ( DEPRECATED_SETTINGS, EXPERIMENTAL_SETTINGS, INCOMPATIBLE_SETTINGS, JS_ONLY_SETTINGS, default_setting, settings, user_settings, ) from .shared import DEBUG, DYLIB_EXTENSIONS, do_replace, in_temp from .toolchain_profiler import ToolchainProfiler from .utils import ( WINDOWS, delete_file, exit_with_error, get_file_suffix, read_file, safe_copy, unsuffixed, unsuffixed_basename, write_file, ) logger = logging.getLogger('link') DEFAULT_SHELL_HTML = utils.path_from_root('html/shell.html') DEFAULT_ASYNCIFY_IMPORTS = ['__asyncjs__*'] DEFAULT_ASYNCIFY_EXPORTS = [ 'main', '__main_argc_argv', ] VALID_ENVIRONMENTS = {'web', 'webview', 'worker', 'node', 'shell', 'worklet'} EXECUTABLE_EXTENSIONS = ['.wasm', '.html', '.js', '.mjs', '.out', ''] # Supported LLD flags which we will pass through to the linker. SUPPORTED_LINKER_FLAGS = ( '--start-group', '--end-group', '-(', '-)', '--whole-archive', '--no-whole-archive', '-whole-archive', '-no-whole-archive', '-rpath', ) # Unsupported LLD flags which we will ignore. # Maps to true if the flag takes an argument. UNSUPPORTED_LLD_FLAGS = { # macOS-specific linker flag that libtool (ltmain.sh) will if macOS is detected. '-bind_at_load': False, # wasm-ld doesn't support soname or other dynamic linking flags (yet). Ignore them # in order to aid build systems that want to pass these flags. '-allow-shlib-undefined': False, '-rpath-link': True, '-version-script': True, '-install_name': True, } UBSAN_SANITIZERS = { 'alignment', 'bool', 'builtin', 'bounds', 'enum', 'float-cast-overflow', 'float-divide-by-zero', 'function', 'implicit-unsigned-integer-truncation', 'implicit-signed-integer-truncation', 'implicit-integer-sign-change', 'integer-divide-by-zero', 'nonnull-attribute', 'null', 'nullability-arg', 'nullability-assign', 'nullability-return', 'object-size', 'pointer-overflow', 'return', 'returns-nonnull-attribute', 'shift', 'signed-integer-overflow', 'unreachable', 'unsigned-integer-overflow', 'vla-bound', 'vptr', 'undefined', 'undefined-trap', 'implicit-integer-truncation', 'implicit-integer-arithmetic-value-change', 'implicit-conversion', 'integer', 'nullability', } final_js = None # this function uses the global 'final' variable, which contains the current # final output file. if a method alters final, and calls this method, then it # must modify final globally (i.e. it can't receive final as a param and # return it) # TODO: refactor all this, a singleton that abstracts over the final output # and saving of intermediates def save_intermediate(name, suffix='js'): if not DEBUG: return if not final_js: logger.debug(f'(not saving intermediate {name} because not generating JS)') return building.save_intermediate(final_js, f'{name}.{suffix}') def save_intermediate_with_wasm(name, wasm_binary): if not DEBUG: return save_intermediate(name) # save the js building.save_intermediate(wasm_binary, name + '.wasm') def base64_encode(filename): data = utils.read_binary(filename) b64 = base64.b64encode(data) return b64.decode('ascii') def align_to_wasm_page_boundary(address): page_size = webassembly.WASM_PAGE_SIZE return ((address + (page_size - 1)) // page_size) * page_size def will_metadce(): # The metadce JS parsing code does not currently support the JS that gets generated # when assertions are enabled. if settings.ASSERTIONS: return False return settings.OPT_LEVEL >= 3 or settings.SHRINK_LEVEL >= 1 def setup_environment_settings(): # The worker environment is automatically added if any of the pthread or Worker features are used. # Note: we need to actually modify ENVIRONMENTS variable here before the parsing, # because some JS code reads it back so modifying parsed info alone is not sufficient. maybe_web_worker = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT if settings.SHARED_MEMORY and settings.ENVIRONMENT: settings.ENVIRONMENT.append('worker') if settings.AUDIO_WORKLET: settings.ENVIRONMENT.append('worklet') # Environment setting based on user input if any(x for x in settings.ENVIRONMENT if x not in VALID_ENVIRONMENTS): exit_with_error(f'Invalid environment specified in "ENVIRONMENT": {settings.ENVIRONMENT}. Should be one of: {",".join(VALID_ENVIRONMENTS)}') settings.ENVIRONMENT_MAY_BE_WEB = not settings.ENVIRONMENT or 'web' in settings.ENVIRONMENT settings.ENVIRONMENT_MAY_BE_WEBVIEW = not settings.ENVIRONMENT or 'webview' in settings.ENVIRONMENT settings.ENVIRONMENT_MAY_BE_NODE = not settings.ENVIRONMENT or 'node' in settings.ENVIRONMENT settings.ENVIRONMENT_MAY_BE_SHELL = not settings.ENVIRONMENT or 'shell' in settings.ENVIRONMENT settings.ENVIRONMENT_MAY_BE_WORKER = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET = not settings.ENVIRONMENT or 'worklet' in settings.ENVIRONMENT if not settings.ENVIRONMENT_MAY_BE_NODE: if 'MIN_NODE_VERSION' in user_settings and settings.MIN_NODE_VERSION != feature_matrix.UNSUPPORTED: diagnostics.warning('unused-command-line-argument', 'ignoring MIN_NODE_VERSION because `node` environment is not enabled') settings.MIN_NODE_VERSION = feature_matrix.UNSUPPORTED if not (settings.ENVIRONMENT_MAY_BE_WEB or maybe_web_worker or settings.ENVIRONMENT_MAY_BE_WEBVIEW): for browser in ('FIREFOX', 'SAFARI', 'CHROME'): key = f'MIN_{browser}_VERSION' if key in user_settings and settings[key] != feature_matrix.UNSUPPORTED: diagnostics.warning('unused-command-line-argument', 'ignoring %s because `web`, `worker` and `webview` environments are not enabled', key) settings[key] = feature_matrix.UNSUPPORTED def generate_js_sym_info(): """Run the JS compiler to generate a list of all available JS symbols. This must be done separately for each linker invocation since the list of symbols depends on what settings are used. TODO(sbc): Find a way to optimize this. Potentially we could add a super-set mode of the JS compiler that would generate a list of all possible symbols that could be checked in. """ output = emscripten.compile_javascript(symbols_only=True) # When running in symbols_only mode compiler.mjs outputs symbol metadata as JSON. return json.loads(output) @ToolchainProfiler.profile_block('JS symbol generation') def get_js_sym_info(): # Avoiding using the cache when generating struct info since # this step is performed while the cache is locked. if DEBUG or settings.BOOTSTRAPPING_STRUCT_INFO or config.FROZEN_CACHE: return generate_js_sym_info() content_hash = emscripten.generate_js_compiler_input_hash(symbols_only=True) def generate_json(): library_syms = generate_js_sym_info() return json.dumps(library_syms, separators=(',', ':'), indent=2) # Limit of the overall size of the cache. # This code will get test coverage since a full test run of `other` or `core` # generates ~1000 unique symbol lists. file_content = emscripten.get_cached_file('symbol_lists', f'{content_hash}.json', generate_json, cache_limit=500) return json.loads(file_content) def filter_link_flags(flags, using_lld): def is_supported(f): if using_lld: for flag, takes_arg in UNSUPPORTED_LLD_FLAGS.items(): # lld allows various flags to have either a single -foo or double --foo if f.startswith((flag, '-' + flag)): diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f) # Skip the next argument if this linker flag takes an argument and that # argument was not specified separately (i.e. it was specified as # single arg containing an `=` char.) skip_next = takes_arg and '=' not in f return False, skip_next return True, False else: if not f.startswith('-') or f in SUPPORTED_LINKER_FLAGS: return True, False # Silently ignore -l/-L flags when not using lld. If using lld allow # them to pass through the linker if f.startswith(('-l', '-L')): return False, False diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f) return False, False results = [] skip_next = False for f in flags: if skip_next: skip_next = False continue keep, skip_next = is_supported(f) if keep: results.append(f) return results def fix_windows_newlines(text): # Avoid duplicating \r\n to \r\r\n when writing out text. if WINDOWS: text = text.replace('\r\n', '\n') return text def read_js_files(files): contents = [] for f in files: content = read_file(f) if content.startswith('#preprocess\n'): contents.append(building.read_and_preprocess(f, expand_macros=True)) else: contents.append(content) contents = '\n'.join(contents) return fix_windows_newlines(contents) def should_run_binaryen_optimizer(): # run the binaryen optimizer in -O2+. in -O0 we don't need it obviously, while # in -O1 we don't run it as the LLVM optimizer has been run, and it does the # great majority of the work; not running the binaryen optimizer in that case # keeps -O1 mostly-optimized while compiling quickly and without rewriting # DWARF etc. return settings.OPT_LEVEL >= 2 def get_binaryen_lowering_passes(): passes = [] # The following features are all enabled in llvm by default and therefore # enabled in the emscripten system libraries. This means that we need to # lower them away using binaryen passes, if they are not enabled in the # feature matrix. # This can happen if the feature is explicitly disabled on the command line, # or when targeting an VM/engine that does not support the feature. # List of [, , ] triples. features = [ [Feature.SIGN_EXT, '--signext-lowering', ['--enable-sign-ext']], [Feature.NON_TRAPPING_FPTOINT, '--llvm-nontrapping-fptoint-lowering', ['--enable-nontrapping-float-to-int']], [Feature.BULK_MEMORY, '--llvm-memory-copy-fill-lowering', ['--enable-bulk-memory', '--enable-bulk-memory-opt']], ] for feature, lowering_flag, feature_flags in features: if not feature_matrix.caniuse(feature): logger.debug(f'lowering {feature.name} feature due to incompatible target browser engines') for f in feature_flags: # Remove features from binaryen_features, otherwise future runs of binaryen # could re-introduce the feature. if f in building.binaryen_features: building.binaryen_features.remove(f) passes.append(lowering_flag) return passes def get_binaryen_passes(options): passes = get_binaryen_lowering_passes() optimizing = should_run_binaryen_optimizer() # safe heap must run before post-emscripten, so post-emscripten can apply the sbrk ptr if settings.SAFE_HEAP: passes += ['--safe-heap'] if settings.EMULATE_FUNCTION_POINTER_CASTS: # fpcast-emu must run before -Ox so that directize (inside -Ox) sees # the rewritten table entries with matching types. It must also run # before asyncify so the byn$fpcast-emu thunks get instrumented. passes += ['--fpcast-emu'] if optimizing: # wasm-emscripten-finalize will strip the features section for us # automatically, but if we did not modify the wasm then we didn't run it, # and in an optimized build we strip it manually here. (note that in an # unoptimized build we might end up with the features section, if we neither # optimize nor run wasm-emscripten-finalize, but a few extra bytes in the # binary don't matter in an unoptimized build) passes += ['--strip-target-features'] passes += ['--post-emscripten'] if settings.SIDE_MODULE: passes += ['--pass-arg=post-emscripten-side-module'] passes += [building.opt_level_to_str(settings.OPT_LEVEL, settings.SHRINK_LEVEL)] # when optimizing, use the fact that low memory is never used (1024 is a # hardcoded value in the binaryen pass). we also cannot do it when the stack # is first, as then the stack is in the low memory that should be unused. if settings.GLOBAL_BASE >= 1024 and not settings.STACK_FIRST: passes += ['--low-memory-unused'] if options.fast_math: passes += ['--fast-math'] if settings.AUTODEBUG: # adding '--flatten' here may make these even more effective passes += ['--instrument-locals'] passes += ['--log-execution'] passes += ['--instrument-memory'] if settings.LEGALIZE_JS_FFI: # legalize it again now, as the instrumentation may need it passes += ['--legalize-js-interface'] passes += building.js_legalization_pass_flags() if settings.ASYNCIFY == 1: passes += ['--asyncify'] if settings.MAIN_MODULE: passes += ['--pass-arg=asyncify-export-globals'] elif settings.SIDE_MODULE: passes += ['--pass-arg=asyncify-import-globals'] if settings.ASSERTIONS: passes += ['--pass-arg=asyncify-asserts'] if settings.ASYNCIFY_ADVISE: passes += ['--pass-arg=asyncify-verbose'] if settings.ASYNCIFY_IGNORE_INDIRECT: passes += ['--pass-arg=asyncify-ignore-indirect'] if settings.ASYNCIFY_PROPAGATE_ADD: passes += ['--pass-arg=asyncify-propagate-addlist'] passes += ['--pass-arg=asyncify-imports@%s' % ','.join(settings.ASYNCIFY_IMPORTS)] # shell escaping can be confusing; try to emit useful warnings def check_human_readable_list(items): for item in items: if item.count('(') != item.count(')'): logger.warning('emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):') logger.warning(' ' + item) logger.warning('This may indicate improper escaping that led to splitting inside your names.') logger.warning('Try using a response file. e.g: -sASYNCIFY_ONLY=@funcs.txt. The format is a simple') logger.warning('text file, one line per function.') break if settings.ASYNCIFY_REMOVE: check_human_readable_list(settings.ASYNCIFY_REMOVE) passes += ['--pass-arg=asyncify-removelist@%s' % ','.join(settings.ASYNCIFY_REMOVE)] if settings.ASYNCIFY_ADD: check_human_readable_list(settings.ASYNCIFY_ADD) passes += ['--pass-arg=asyncify-addlist@%s' % ','.join(settings.ASYNCIFY_ADD)] if settings.ASYNCIFY_ONLY: check_human_readable_list(settings.ASYNCIFY_ONLY) passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(settings.ASYNCIFY_ONLY)] if settings.MEMORY64 == 2: passes += ['--memory64-lowering', '--table64-lowering'] if settings.BINARYEN_IGNORE_IMPLICIT_TRAPS: passes += ['--ignore-implicit-traps'] # normally we can assume the memory, if imported, has not been modified # beforehand (in fact, in most cases the memory is not even imported anyhow, # but it is still safe to pass the flag), and is therefore filled with zeros. # the one exception is dynamic linking of a side module: the main module is ok # as it is loaded first, but the side module may be assigned memory that was # previously used. if optimizing and not settings.SIDE_MODULE: passes += ['--zero-filled-memory'] # LLVM output always has immutable initial table contents: the table is # fixed and may only be appended to at runtime (that is true even in # relocatable mode) if optimizing: passes += ['--pass-arg=directize-initial-contents-immutable'] if settings.BINARYEN_EXTRA_PASSES: # BINARYEN_EXTRA_PASSES is comma-separated, and we support both '-'-prefixed and # unprefixed pass names extras = settings.BINARYEN_EXTRA_PASSES.split(',') passes += [('--' + p) if p[0] != '-' else p for p in extras if p] # If we are going to run metadce then that means we will be running binaryen # tools after the main invocation, whose flags are determined here # (specifically we will run metadce and possibly also wasm-opt for import/ # export minification). And when we run such a tool it will "undo" any # StackIR optimizations (since the conversion to BinaryenIR undoes them as it # restructures the code). We could re-run those opts, but it is most efficient # to just not do them now if we'll invoke other tools later, and we'll do them # only in the very last invocation. if will_metadce(): passes += ['--no-stack-ir'] return passes def make_js_executable(script): src = read_file(script) # By default, the resulting script will run under the version of node in the PATH. if settings.EXECUTABLE == 1: settings.EXECUTABLE = '/usr/bin/env node' logger.debug(f'adding `#!` to JavaScript file: {settings.EXECUTABLE}') # add shebang with open(script, 'w', encoding='utf-8') as f: f.write(f'#!{settings.EXECUTABLE}\n') f.write(src) try: os.chmod(script, stat.S_IMODE(os.stat(script).st_mode) | stat.S_IXUSR) # make executable except OSError: pass # can fail if e.g. writing the executable to /dev/null def do_split_module(wasm_file, options): os.replace(wasm_file, wasm_file + '.orig') args = ['--instrument'] if options.requested_debug: # Tell wasm-split to preserve function names. args += ['-g'] building.run_binaryen_command('wasm-split', wasm_file + '.orig', outfile=wasm_file, args=args) def get_worker_js_suffix(): return '.worker.mjs' if settings.EXPORT_ES6 else '.worker.js' def setup_pthreads(): # pthreads + dynamic linking has certain limitations if settings.MAIN_MODULE: diagnostics.warning('experimental', 'dynamic linking + pthreads is experimental') if settings.ALLOW_MEMORY_GROWTH and not settings.GROWABLE_ARRAYBUFFERS: diagnostics.warning('pthreads-mem-growth', '-pthread + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271') default_setting('DEFAULT_PTHREAD_STACK_SIZE', settings.STACK_SIZE) # Functions needs by runtime_pthread.js settings.REQUIRED_EXPORTS += [ '_emscripten_thread_free_data', '_emscripten_thread_crashed', ] if settings.MAIN_MODULE: settings.REQUIRED_EXPORTS += [ '_emscripten_dlsync_self', '_emscripten_dlsync_self_async', '_emscripten_proxy_dlsync', '_emscripten_proxy_dlsync_async', '__dl_seterr', ] # runtime_pthread.js depends on these library symbols settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$PThread', '$establishStackSpace', '$invokeEntryPoint', ] if settings.MINIMAL_RUNTIME: building.user_requested_exports.add('exit') def set_initial_memory(): user_specified_initial_heap = 'INITIAL_HEAP' in user_settings # INITIAL_HEAP cannot be used when the memory object is created in JS: we don't know # the size of static data here and thus the total initial memory size. if settings.IMPORTED_MEMORY: if user_specified_initial_heap: # Some of these could (and should) be implemented. exit_with_error('INITIAL_HEAP is currently not compatible with IMPORTED_MEMORY') # The default for imported memory is to fall back to INITIAL_MEMORY. settings.INITIAL_HEAP = -1 if not user_specified_initial_heap: # For backwards compatibility, we will only use INITIAL_HEAP by default when the user # specified neither INITIAL_MEMORY nor MAXIMUM_MEMORY. Both place an upper bounds on # the overall initial linear memory (stack + static data + heap), and we do not know # the size of static data at this stage. Setting any non-zero initial heap value in # this scenario would risk pushing users over the limit they have set. user_specified_initial = settings.INITIAL_MEMORY != -1 user_specified_maximum = 'MAXIMUM_MEMORY' in user_settings or 'WASM_MEM_MAX' in user_settings or 'BINARYEN_MEM_MAX' in user_settings if user_specified_initial or user_specified_maximum: settings.INITIAL_HEAP = -1 # Apply the default if we are going with INITIAL_MEMORY. if settings.INITIAL_HEAP == -1 and settings.INITIAL_MEMORY == -1: default_setting('INITIAL_MEMORY', 16 * 1024 * 1024) def check_memory_setting(setting): if settings[setting] % webassembly.WASM_PAGE_SIZE != 0: exit_with_error(f'{setting} must be a multiple of WebAssembly page size (64KiB), was {settings[setting]}') if settings[setting] >= 2**53: exit_with_error(f'{setting} must be smaller than 2^53 bytes due to JS Numbers (doubles) being used to hold pointer addresses in JS side') # Due to the aforementioned lack of knowledge about the static data size, we delegate # checking the overall consistency of these settings to wasm-ld. if settings.INITIAL_HEAP != -1: check_memory_setting('INITIAL_HEAP') if settings.INITIAL_MEMORY != -1: check_memory_setting('INITIAL_MEMORY') if settings.INITIAL_MEMORY < settings.STACK_SIZE: exit_with_error(f'INITIAL_MEMORY must be larger than STACK_SIZE, was {settings.INITIAL_MEMORY} (STACK_SIZE={settings.STACK_SIZE})') check_memory_setting('MAXIMUM_MEMORY') if settings.MEMORY_GROWTH_LINEAR_STEP != -1: check_memory_setting('MEMORY_GROWTH_LINEAR_STEP') # Set an upper estimate of what MAXIMUM_MEMORY should be. Take note that this value # may not be precise, and is only an upper bound of the exact value calculated later # by the linker. def set_max_memory(): # With INITIAL_HEAP, we only know the lower bound on initial memory size. initial_memory_known = settings.INITIAL_MEMORY != -1 if not settings.ALLOW_MEMORY_GROWTH: if 'MAXIMUM_MEMORY' in user_settings: diagnostics.warning('unused-command-line-argument', 'MAXIMUM_MEMORY is only meaningful with ALLOW_MEMORY_GROWTH') # Optimization: lower the default maximum memory to initial memory if possible. if initial_memory_known: settings.MAXIMUM_MEMORY = settings.INITIAL_MEMORY # Automatically up the default maximum when the user requested a large minimum. if 'MAXIMUM_MEMORY' not in user_settings: if settings.ALLOW_MEMORY_GROWTH: if any([settings.INITIAL_HEAP != -1 and settings.INITIAL_HEAP >= 2 * 1024 * 1024 * 1024, initial_memory_known and settings.INITIAL_MEMORY > 2 * 1024 * 1024 * 1024]): settings.MAXIMUM_MEMORY = 4 * 1024 * 1024 * 1024 # INITIAL_MEMORY sets a lower bound for MAXIMUM_MEMORY if initial_memory_known and settings.INITIAL_MEMORY > settings.MAXIMUM_MEMORY: settings.MAXIMUM_MEMORY = settings.INITIAL_MEMORY # A similar check for INITIAL_HEAP would not be precise and so is delegated to wasm-ld. if initial_memory_known and settings.MAXIMUM_MEMORY < settings.INITIAL_MEMORY: exit_with_error('MAXIMUM_MEMORY cannot be less than INITIAL_MEMORY') def inc_initial_memory(delta): # Both INITIAL_HEAP and INITIAL_MEMORY can be set at the same time. Increment both. if settings.INITIAL_HEAP != -1: settings.INITIAL_HEAP += delta if settings.INITIAL_MEMORY != -1: settings.INITIAL_MEMORY += delta def check_browser_versions(): # Map of setting all VM version settings to the minimum version # we support. min_version_settings = { 'MIN_FIREFOX_VERSION': feature_matrix.OLDEST_SUPPORTED_FIREFOX, 'MIN_CHROME_VERSION': feature_matrix.OLDEST_SUPPORTED_CHROME, 'MIN_SAFARI_VERSION': feature_matrix.OLDEST_SUPPORTED_SAFARI, 'MIN_NODE_VERSION': feature_matrix.OLDEST_SUPPORTED_NODE, } if settings.LEGACY_VM_SUPPORT: # Default all browser versions to zero for key in min_version_settings: default_setting(key, 0) for key, oldest in min_version_settings.items(): if settings[key] != 0 and settings[key] < oldest: exit_with_error(f'{key} older than {oldest} is not supported') def add_system_js_lib(lib): lib = utils.path_from_root('src/lib', lib) assert os.path.exists(lib) settings.JS_LIBRARIES.append(lib) def check_settings(): for s, reason in DEPRECATED_SETTINGS.items(): if s in user_settings: diagnostics.warning('deprecated', f'{s} is deprecated ({reason}). Please open a bug if you have a continuing need for this setting') for name, msg in EXPERIMENTAL_SETTINGS.items(): if getattr(settings, name): diagnostics.warning('experimental', msg) for a, b, reason in INCOMPATIBLE_SETTINGS: invert_b = b.startswith('NO_') if invert_b: b = b[3:] b_val = getattr(settings, b) if invert_b: b_val = not b_val if getattr(settings, a) and b_val: msg = f'{a} is not compatible with {b}' if invert_b: msg += '=0' if reason: msg += f' ({reason})' exit_with_error(msg) @ToolchainProfiler.profile() def setup_sanitizers(options): assert options.sanitize if settings.WASM_WORKERS: exit_with_error('WASM_WORKERS is not currently compatible with `-fsanitize` tools') if 'leak' in options.sanitize or 'address' in options.sanitize: # These symbols are needed by `noLeakCheck` which used to implement # the `__noleakcheck` attribute. However this dependency is not yet represented in the JS # symbol graph generated when we run the compiler with `--symbols-only`. settings.REQUIRED_EXPORTS += ['__lsan_disable', '__lsan_enable'] if ('leak' in options.sanitize or 'address' in options.sanitize) and not settings.ALLOW_MEMORY_GROWTH: # Increase the minimum memory requirements to account for extra memory # that the sanitizers might need (in addition to the shadow memory # requirements handled below). # These values are designed be an over-estimate of the actual requirements and # are based on experimentation with different tests/programs under asan and # lsan. inc_initial_memory(50 * 1024 * 1024) if settings.PTHREADS: inc_initial_memory(50 * 1024 * 1024) if options.sanitize & UBSAN_SANITIZERS: if options.sanitize_minimal_runtime: settings.UBSAN_RUNTIME = 1 else: settings.UBSAN_RUNTIME = 2 if 'leak' in options.sanitize: settings.USE_LSAN = 1 default_setting('EXIT_RUNTIME', 1) if 'address' in options.sanitize: settings.USE_ASAN = 1 default_setting('EXIT_RUNTIME', 1) if not settings.UBSAN_RUNTIME: settings.UBSAN_RUNTIME = 2 settings.REQUIRED_EXPORTS += emscripten.ASAN_C_HELPERS if settings.ASYNCIFY and not settings.ASYNCIFY_ONLY: # we do not want asyncify to instrument these helpers - they just access # memory as small getters/setters, so they cannot pause anyhow, and also # we access them in the runtime as we prepare to rewind, which would hit # an asyncify assertion, if asyncify instrumented them. # # note that if ASYNCIFY_ONLY was set by the user then we do not need to # do anything (as the user's list won't contain these functions), and if # we did add them, the pass would assert on incompatible lists, hence the # condition in the above if. settings.ASYNCIFY_REMOVE.append("__asan_*") if settings.ASAN_SHADOW_SIZE != -1: diagnostics.warning('emcc', 'ASAN_SHADOW_SIZE is ignored and will be removed in a future release') if 'GLOBAL_BASE' in user_settings: exit_with_error("ASan does not support custom GLOBAL_BASE") # Increase the INITIAL_MEMORY and shift GLOBAL_BASE to account for # the ASan shadow region which starts at address zero. # The shadow region is 1/8th the size of the total memory and is # itself part of the total memory. # We use the following variables in this calculation: # - user_mem : memory usable/visible by the user program. # - shadow_size : memory used by asan for shadow memory. # - total_mem : the sum of the above. this is the size of the wasm memory (and must be aligned to WASM_PAGE_SIZE) user_mem = settings.MAXIMUM_MEMORY if not settings.ALLOW_MEMORY_GROWTH and settings.INITIAL_MEMORY != -1: user_mem = settings.INITIAL_MEMORY # Given the know value of user memory size we can work backwards # to find the total memory and the shadow size based on the fact # that the user memory is 7/8ths of the total memory. # (i.e. user_mem == total_mem * 7 / 8 # TODO-Bug?: this does not look to handle 4GB MAXIMUM_MEMORY correctly. total_mem = user_mem * 8 / 7 # But we might need to re-align to wasm page size total_mem = int(align_to_wasm_page_boundary(total_mem)) # The shadow size is 1/8th the resulting rounded up size shadow_size = total_mem // 8 # We start our global data after the shadow memory. # We don't need to worry about alignment here. wasm-ld will take care of that. settings.GLOBAL_BASE = shadow_size # Adjust INITIAL_MEMORY (if needed) to account for the shifted global base. if settings.INITIAL_MEMORY != -1: if settings.ALLOW_MEMORY_GROWTH: settings.INITIAL_MEMORY += align_to_wasm_page_boundary(shadow_size) else: settings.INITIAL_MEMORY = total_mem if settings.SAFE_HEAP: # SAFE_HEAP instruments ASan's shadow memory accesses. # Since the shadow memory starts at 0, the act of accessing the shadow memory is detected # by SAFE_HEAP as a null pointer dereference. exit_with_error('ASan does not work with SAFE_HEAP') if settings.MEMORY64: exit_with_error('MEMORY64 does not yet work with ASAN') if settings.GENERATE_SOURCE_MAP: settings.LOAD_SOURCE_MAP = 1 def get_dylibs(options, linker_args): """Find all the Wasm dynamic libraries specified on the command line. This can either be via `-lfoo` or via `libfoo.so` directly. """ dylibs = [] for arg in linker_args: if arg.startswith('-l'): for ext in DYLIB_EXTENSIONS: path = find_library('lib' + arg[2:] + ext, options.lib_dirs) if path and building.is_wasm_dylib(path): dylibs.append(path) elif building.is_wasm_dylib(arg): dylibs.append(arg) return dylibs @ToolchainProfiler.profile_block('linker_setup') def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915 """Future modifications should consider refactoring to reduce complexity. * The McCabe cyclomatiic complexity is currently 244 vs 10 recommended. * There are currently 252 branches vs 12 recommended. * There are currently 563 statements vs 50 recommended. To revalidate these numbers, run `ruff check --select=C901,PLR091`. """ setup_environment_settings() apply_library_settings(linker_args) if settings.SIDE_MODULE or settings.MAIN_MODULE: default_setting('FAKE_DYLIBS', 0) if options.shared and not settings.FAKE_DYLIBS: default_setting('SIDE_MODULE', 1) if not settings.FAKE_DYLIBS: options.dylibs = get_dylibs(options, linker_args) # If there are any dynamically linked libraries on the command line then # need to enable `MAIN_MODULE` in order to produce JS code that can load them. if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs: default_setting('MAIN_MODULE', 2) linker_args += calc_extra_ldflags(options) # We used to do this check during on startup during `check_sanity`, but # we now only do it when linking, in order to reduce the overhead when # only compiling. if not shared.SKIP_SUBPROCS: shared.check_llvm_version() autoconf = os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in options.input_files or 'conftest.cpp' in options.input_files if autoconf: # configure tests want a more shell-like style, where we emit return codes on exit() settings.EXIT_RUNTIME = 1 # use node.js raw filesystem access, to behave just like a native executable settings.NODERAWFS = 1 # Add `#!` line to output JS and make it executable. settings.EXECUTABLE = config.NODE_JS[0] # autoconf declares functions without their proper signatures, and STRICT causes that to trip up by passing --fatal-warnings to the linker. if settings.STRICT: exit_with_error('autoconfiguring is not compatible with STRICT') if settings.OPT_LEVEL >= 1: default_setting('ASSERTIONS', 0) if options.emrun: options.pre_js.append(utils.path_from_root('src/emrun_prejs.js')) options.post_js.append(utils.path_from_root('src/emrun_postjs.js')) if settings.MINIMAL_RUNTIME: exit_with_error('--emrun is not compatible with MINIMAL_RUNTIME') # emrun mode waits on program exit if user_settings.get('EXIT_RUNTIME') == '0': exit_with_error('--emrun is not compatible with EXIT_RUNTIME=0') settings.EXIT_RUNTIME = 1 # emrun_postjs.js needs this library function. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnExit'] if options.cpu_profiler: options.post_js.append(utils.path_from_root('src/cpuprofiler.js')) # Unless RUNTIME_DEBUG is explicitly set then we enable it when any of the # more specific debug settings are present. default_setting('RUNTIME_DEBUG', int(settings.LIBRARY_DEBUG or settings.GL_DEBUG or settings.DYLINK_DEBUG or settings.OPENAL_DEBUG or settings.SYSCALL_DEBUG or settings.WEBSOCKET_DEBUG or settings.SOCKET_DEBUG or settings.FETCH_DEBUG or settings.EXCEPTION_DEBUG or settings.PTHREADS_DEBUG or settings.ASYNCIFY_DEBUG)) if options.memory_profiler: settings.MEMORYPROFILER = 1 if settings.PTHREADS_PROFILING: options.post_js.append(utils.path_from_root('src/threadprofiler.js')) settings.REQUIRED_EXPORTS.append('emscripten_main_runtime_thread_id') # threadprofiler.js needs these library functions. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnInit', '$addOnExit'] # TODO: support source maps with js_transform if options.js_transform and settings.GENERATE_SOURCE_MAP: logger.warning('disabling source maps because a js transform is being done') settings.GENERATE_SOURCE_MAP = 0 # options.output_file is the user-specified one, target is what we will generate if options.output_file: target = options.output_file # check for the existence of the output directory now, to avoid having # to do so repeatedly when each of the various output files (.mem, .wasm, # etc) are written. This gives a more useful error message than the # IOError and python backtrace that users would otherwise see. dirname = os.path.dirname(target) if dirname and not os.path.isdir(dirname): exit_with_error("specified output file (%s) is in a directory that does not exist" % target) elif autoconf: # Autoconf expects the executable output file to be called `a.out` target = 'a.out' elif settings.SIDE_MODULE: target = 'a.out.wasm' else: target = 'a.out.js' final_suffix = get_file_suffix(target) # Set the EXPORT_ES6 default early since it affects the setting of the # default oformat below. if settings.WASM_ESM_INTEGRATION or settings.SOURCE_PHASE_IMPORTS or settings.MODULARIZE == 'instance': default_setting('EXPORT_ES6', 1) # If no output format was specified we try to deduce the format based on # the output filename extension if not options.oformat and (options.relocatable or (options.shared and settings.FAKE_DYLIBS and not settings.SIDE_MODULE)): # With FAKE_DYLIBS we generate an normal object file rather than an shared object. # This is linked with `wasm-ld --relocatable` or (`llvm-link` in the case of LTO). if final_suffix in EXECUTABLE_EXTENSIONS: diagnostics.warning('emcc', '-shared/-r used with executable output suffix. This behaviour is deprecated. Please remove -shared/-r to build an executable or avoid the executable suffix (%s) when building object files.' % final_suffix) else: if options.shared and 'FAKE_DYLIBS' not in user_settings: diagnostics.warning('emcc', 'linking a library with `-shared` will emit a static object file (FAKE_DYLIBS defaults to true). If you want to build a runtime shared library use the SIDE_MODULE or FAKE_DYLIBS=0.') options.oformat = OFormat.OBJECT if not options.oformat: if settings.SIDE_MODULE or final_suffix == '.wasm': options.oformat = OFormat.WASM elif final_suffix == '.html': options.oformat = OFormat.HTML elif final_suffix == '.mjs' or settings.EXPORT_ES6: options.oformat = OFormat.MJS else: options.oformat = OFormat.JS if options.oformat in {OFormat.WASM, OFormat.OBJECT}: for s in JS_ONLY_SETTINGS: if s in user_settings: diagnostics.warning('unused-command-line-argument', f'{s} is only valid when generating JavaScript output') # When there is no final suffix or the suffix is `.out` (as in `a.out`) then default to # making the resulting file exectuable. if settings.ENVIRONMENT_MAY_BE_NODE and options.oformat == OFormat.JS and final_suffix in {'', '.out'}: default_setting('EXECUTABLE', 1) if settings.EXECUTABLE and not settings.ENVIRONMENT_MAY_BE_NODE: exit_with_error('EXECUTABLE requires `node` in ENVRIONMENT') if options.oformat == OFormat.MJS: default_setting('EXPORT_ES6', 1) settings.OUTPUT_FORMAT = options.oformat.name if settings.SUPPORT_BIG_ENDIAN and settings.WASM2JS: exit_with_error('WASM2JS is currently not compatible with SUPPORT_BIG_ENDIAN') if settings.WASM_ESM_INTEGRATION: default_setting('MODULARIZE', 'instance') if not settings.EXPORT_ES6: exit_with_error('WASM_ESM_INTEGRATION requires EXPORT_ES6') if settings.MODULARIZE != 'instance': exit_with_error('WASM_ESM_INTEGRATION requires MODULARIZE=instance') if settings.MAIN_MODULE: exit_with_error('WASM_ESM_INTEGRATION is not compatible with dynamic linking') if settings.ASYNCIFY: exit_with_error('WASM_ESM_INTEGRATION is not compatible with -sASYNCIFY') if settings.WASM_WORKERS: exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS') if not settings.WASM_ASYNC_COMPILATION: exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM_ASYNC_COMPILATION=0') if not settings.WASM: exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM2JS') if settings.ABORT_ON_WASM_EXCEPTIONS: exit_with_error('WASM_ESM_INTEGRATION is not compatible with ABORT_ON_WASM_EXCEPTIONS') if settings.MODULARIZE and settings.MODULARIZE not in {1, 'instance'}: exit_with_error(f'Invalid setting "{settings.MODULARIZE}" for MODULARIZE.') def limit_incoming_module_api(): if options.oformat == OFormat.HTML and options.shell_html == DEFAULT_SHELL_HTML: # Our default shell.html file has minimal set of INCOMING_MODULE_JS_API elements that it expects default_setting('INCOMING_MODULE_JS_API', 'canvas,monitorRunDependencies,onAbort,onExit,print,setStatus'.split(',')) else: default_setting('INCOMING_MODULE_JS_API', []) if settings.ASYNCIFY == 1: # ASYNCIFY=1 wraps only wasm exports so we need to enable legacy # dyncalls via dynCall_xxx exports. # See: https://github.com/emscripten-core/emscripten/issues/12066 settings.DYNCALLS = 1 if settings.MODULARIZE == 'instance': diagnostics.warning('experimental', 'MODULARIZE=instance is still experimental. Many features may not work or will change.') if not settings.EXPORT_ES6: exit_with_error('MODULARIZE=instance requires EXPORT_ES6') if settings.MINIMAL_RUNTIME: exit_with_error('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME') if options.use_preload_plugins or len(options.preload_files): exit_with_error('MODULARIZE=instance is not compatible with --embed-file/--preload-file') if settings.MINIMAL_RUNTIME and len(options.preload_files): exit_with_error('MINIMAL_RUNTIME is not compatible with --preload-file') if options.oformat in {OFormat.WASM, OFormat.BARE}: if options.emit_tsd: exit_with_error('Wasm only output is not compatible with --emit-tsd') # If the user asks directly for a wasm file then this *is* the target wasm_target = target elif settings.SINGLE_FILE or settings.WASM == 0: # In SINGLE_FILE or WASM2JS mode the wasm file is not part of the output at # all so we generate it the temp directory. wasm_target = in_temp(utils.replace_suffix(target, '.wasm')) else: # Otherwise the wasm file is produced alongside the final target. wasm_target = get_secondary_target(target, '.wasm') if settings.SAFE_HEAP not in {0, 1, 2}: exit_with_error('SAFE_HEAP must be 0, 1 or 2') if not settings.WASM: # When the user requests non-wasm output, we enable wasm2js. that is, # we still compile to wasm normally, but we compile the final output # to js. settings.WASM = 1 settings.WASM2JS = 1 if settings.WASM == 2: # Requesting both Wasm and Wasm2JS support settings.WASM2JS = 1 if settings.WASM2JS: # Wasm bigint doesn't make sense with wasm2js, since it controls how the # wasm and JS interact. if user_settings.get('WASM_BIGINT') and settings.WASM_BIGINT: exit_with_error('WASM_BIGINT=1 is not compatible with wasm2js') settings.WASM_BIGINT = 0 feature_matrix.disable_feature(Feature.JS_BIGINT_INTEGRATION) if options.oformat == OFormat.WASM and not settings.SIDE_MODULE: # if the output is just a wasm file, it will normally be a standalone one, # as there is no JS. an exception are side modules, as we can't tell at # compile time whether JS will be involved or not - the main module may # have JS, and the side module is expected to link against that. # we also do not support standalone mode in fastcomp. settings.STANDALONE_WASM = 1 if settings.LZ4: settings.EXPORTED_RUNTIME_METHODS += ['LZ4'] if settings.PURE_WASI: settings.STANDALONE_WASM = 1 settings.WASM_BIGINT = 1 # WASI does not support Emscripten (JS-based) exception catching, which the # JS-based longjmp support also uses. Emscripten EH is by default disabled # so we don't need to do anything here. if not settings.WASM_EXCEPTIONS: default_setting('SUPPORT_LONGJMP', 0) if options.no_entry: settings.EXPECT_MAIN = 0 elif settings.STANDALONE_WASM: if '_main' in settings.EXPORTED_FUNCTIONS: # TODO(sbc): Make this into a warning? logger.debug('including `_main` in EXPORTED_FUNCTIONS is not necessary in standalone mode') else: # In normal non-standalone mode we have special handling of `_main` in EXPORTED_FUNCTIONS. # 1. If the user specifies exports, but doesn't include `_main` we assume they want to build a # reactor. # 2. If the user doesn't export anything we default to exporting `_main` (unless `--no-entry` # is specified (see above). if 'EXPORTED_FUNCTIONS' in user_settings: if '_main' in settings.USER_EXPORTS: settings.EXPORTED_FUNCTIONS.remove('_main') settings.EXPORT_IF_DEFINED.append('main') else: settings.EXPECT_MAIN = 0 else: settings.EXPORT_IF_DEFINED.append('main') if settings.STANDALONE_WASM: # In STANDALONE_WASM mode we either build a command or a reactor. # See https://github.com/WebAssembly/WASI/blob/main/design/application-abi.md # For a command we always want EXIT_RUNTIME=1 # For a reactor we always want EXIT_RUNTIME=0 if 'EXIT_RUNTIME' in user_settings: exit_with_error('explicitly setting EXIT_RUNTIME not compatible with STANDALONE_WASM. EXIT_RUNTIME will always be True for programs (with a main function) and False for reactors (not main function).') settings.EXIT_RUNTIME = settings.EXPECT_MAIN settings.IGNORE_MISSING_MAIN = 0 # the wasm must be runnable without the JS, so there cannot be anything that # requires JS legalization default_setting('LEGALIZE_JS_FFI', 0) if 'MEMORY_GROWTH_LINEAR_STEP' in user_settings: exit_with_error('MEMORY_GROWTH_LINEAR_STEP is not compatible with STANDALONE_WASM') if 'MEMORY_GROWTH_GEOMETRIC_CAP' in user_settings: exit_with_error('MEMORY_GROWTH_GEOMETRIC_CAP is not compatible with STANDALONE_WASM') # Note the exports the user requested building.user_requested_exports.update(settings.EXPORTED_FUNCTIONS) if '_main' in settings.EXPORTED_FUNCTIONS or 'main' in settings.EXPORT_IF_DEFINED: settings.EXPORT_IF_DEFINED.append('__main_argc_argv') elif settings.ASSERTIONS and not settings.STANDALONE_WASM and not options.no_entry: # In debug builds when `main` is not explicitly requested as an # export we still add it to EXPORT_IF_DEFINED so that we can warn # users who forget to explicitly export `main`. # See other.test_warn_unexported_main. # This is not needed in STANDALONE_WASM mode since we export _start # (unconditionally) rather than main. settings.EXPORT_IF_DEFINED += ['main', '__main_argc_argv'] if settings.ASSERTIONS: # Exceptions are thrown with a stack trace by default when ASSERTIONS is # set and when building with either -fexceptions or -fwasm-exceptions. if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES: exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled') if settings.WASM_EXCEPTIONS or not settings.DISABLE_EXCEPTION_CATCHING: settings.EXCEPTION_STACK_TRACES = 1 # -sASSERTIONS implies basic stack overflow checks, and ASSERTIONS=2 # implies full stack overflow checks. However, we don't set this default in # PURE_WASI, or when we are linking without standard libraries because # STACK_OVERFLOW_CHECK depends on emscripten_stack_get_end which is defined # in libcompiler-rt. if not settings.PURE_WASI and not options.nostdlib and not options.nodefaultlibs: default_setting('STACK_OVERFLOW_CHECK', max(settings.ASSERTIONS, settings.STACK_OVERFLOW_CHECK)) # For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also # want to opt out of ERROR_ON_UNDEFINED_SYMBOLS. if user_settings.get('WARN_ON_UNDEFINED_SYMBOLS') == '0': default_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) # It is unlikely that developers targeting "native web" APIs with MINIMAL_RUNTIME need # errno support by default. if settings.MINIMAL_RUNTIME: # Require explicit -lfoo.js flags to link with JS libraries. default_setting('AUTO_JS_LIBRARIES', 0) # When using MINIMAL_RUNTIME, symbols should only be exported if requested. default_setting('EXPORT_KEEPALIVE', 0) if settings.EXPORT_ES6 and not settings.MODULARIZE: # EXPORT_ES6 requires output to be a module if 'MODULARIZE' in user_settings: exit_with_error('EXPORT_ES6 requires MODULARIZE to be set') settings.MODULARIZE = 1 if options.oformat == OFormat.HTML: if not options.shell_html: # Minimal runtime uses a different default shell file if settings.MINIMAL_RUNTIME: options.shell_html = options.shell_html = utils.path_from_root('html/shell_minimal_runtime.html') else: options.shell_html = DEFAULT_SHELL_HTML if options.shell_html == DEFAULT_SHELL_HTML: settings.EXPORTED_RUNTIME_METHODS.append('requestFullscreen') elif options.shell_html: diagnostics.warning('unused-command-line-argument', '--shell-file ignored when not generating html output') if settings.STRICT: if not settings.EXPORT_ES6: default_setting('STRICT_JS', 1) default_setting('DEFAULT_TO_CXX', 0) default_setting('IGNORE_MISSING_MAIN', 0) default_setting('AUTO_NATIVE_LIBRARIES', 0) if settings.MAIN_MODULE != 1: # These two settings cannot be disabled with MAIN_MODULE=1 because all symbols # are needed in this mode. default_setting('AUTO_JS_LIBRARIES', 0) default_setting('ALLOW_UNIMPLEMENTED_SYSCALLS', 0) limit_incoming_module_api() for prop in settings.INCOMING_MODULE_JS_API: if prop not in settings.ALL_INCOMING_MODULE_JS_API: diagnostics.warning('unused-command-line-argument', f'invalid entry in INCOMING_MODULE_JS_API: {prop}') settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$wasmMemory', '$HEAP8', '$HEAPU8', '$HEAP16', '$HEAPU16', '$HEAP32', '$HEAPU32', '$HEAPF32', '$HEAPF64', ] if 'noExitRuntime' in settings.INCOMING_MODULE_JS_API: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$noExitRuntime') # Default to TEXTDECODER=2 (always use TextDecoder to decode UTF-8 strings) # in -Oz builds, since custom decoder for UTF-8 takes up space. # When supporting shell environments, do not do this as TextDecoder is not # widely supported there. # In Audio Worklets TextDecoder API is intentionally not exposed # (https://github.com/WebAudio/web-audio-api/issues/2499) so we also need to # keep the JavaScript-based fallback. if settings.SHRINK_LEVEL >= 2 and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET and \ not settings.ENVIRONMENT_MAY_BE_SHELL: default_setting('TEXTDECODER', 2) # If set to 1, we will run the autodebugger (the automatic debugging tool, see # tools/autodebugger). Note that this will disable inclusion of libraries. This # is useful because including dlmalloc makes it hard to compare native and js # builds if os.environ.get('EMCC_AUTODEBUG'): settings.AUTODEBUG = 1 # Use settings if settings.JS_MATH and settings.MAIN_MODULE == 1: # MODULE_MODULE=1 adds`--whole-archive` around all the system libraries which # results in duplicate math symbols when JS_MATH is used. exit_with_error('JS_MATH is not compatible with dynamic linking (MAIN_MODULE=1)') if settings.WASM == 2 and settings.SINGLE_FILE: exit_with_error('cannot have both WASM=2 and SINGLE_FILE enabled at the same time') if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and options.oformat != OFormat.HTML: exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output') if settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION and not settings.MINIMAL_RUNTIME: exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION requires MINIMAL_RUNTIME') if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and not settings.MINIMAL_RUNTIME: exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION requires MINIMAL_RUNTIME') if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and options.oformat != OFormat.HTML: exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output') if options.use_closure_compiler: settings.USE_CLOSURE_COMPILER = 1 if 'CLOSURE_WARNINGS' in user_settings: if settings.CLOSURE_WARNINGS not in {'quiet', 'warn', 'error'}: exit_with_error('invalid option -sCLOSURE_WARNINGS=%s specified! Allowed values are "quiet", "warn" or "error".' % settings.CLOSURE_WARNINGS) closure_warnings = diagnostics.manager.warnings['closure'] if settings.CLOSURE_WARNINGS == 'error': closure_warnings['error'] = True closure_warnings['enabled'] = True elif settings.CLOSURE_WARNINGS == 'warn': closure_warnings['error'] = False closure_warnings['enabled'] = True elif settings.CLOSURE_WARNINGS == 'quiet': closure_warnings['error'] = False closure_warnings['enabled'] = False if not settings.MINIMAL_RUNTIME: if not settings.BOOTSTRAPPING_STRUCT_INFO: if settings.DYNCALLS: # Include dynCall() function by default in DYNCALLS builds in classic runtime; in MINIMAL_RUNTIME, must add this explicitly. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$dynCall'] if settings.ASSERTIONS: # "checkUnflushedContent()" and "missingLibrarySymbol()" depend on warnOnce settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$warnOnce'] settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getValue', '$setValue'] settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$ExitStatus'] # Certain configurations require the removeRunDependency/addRunDependency system. if settings.LOAD_SOURCE_MAP or (settings.WASM_ASYNC_COMPILATION and not settings.MODULARIZE): settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addRunDependency', '$removeRunDependency'] if settings.ABORT_ON_WASM_EXCEPTIONS or settings.SPLIT_MODULE: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$wasmTable'] if settings.MAIN_MODULE: assert not settings.SIDE_MODULE if settings.MAIN_MODULE == 1: settings.INCLUDE_FULL_LIBRARY = 1 # Called from preamble.js once the main module is instantiated. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$loadDylibs'] settings.REQUIRED_EXPORTS += ['__stack_pointer'] if settings.MAIN_MODULE == 1 or settings.SIDE_MODULE == 1: settings.LINKABLE = 1 if settings.LINKABLE and settings.USER_EXPORTS: diagnostics.warning('unused-command-line-argument', 'EXPORTED_FUNCTIONS is not valid with LINKABLE set (normally due to SIDE_MODULE=1/MAIN_MODULE=1) since all functions are exported this mode. To export only a subset use SIDE_MODULE=2/MAIN_MODULE=2') if settings.MAIN_MODULE: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$getDylinkMetadata', '$mergeLibSymbols', ] if settings.PTHREADS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$registerTLSInit', ] if settings.MAIN_MODULE: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$reportUndefinedSymbols', '$relocateExports', '$GOTHandler', ] # shared modules need memory utilities to allocate their memory settings.ALLOW_TABLE_GROWTH = 1 # various settings require sbrk() access if settings.EMSCRIPTEN_TRACING or \ settings.SAFE_HEAP or \ settings.MEMORYPROFILER: settings.REQUIRED_EXPORTS += ['sbrk'] if settings.MEMORYPROFILER: settings.REQUIRED_EXPORTS += ['__heap_base', 'emscripten_stack_get_base', 'emscripten_stack_get_end', 'emscripten_stack_get_current'] settings.INCOMING_MODULE_JS_API += ['preRun'] settings.ASYNCIFY_ADD = unmangle_symbols_from_cmdline(settings.ASYNCIFY_ADD) settings.ASYNCIFY_REMOVE = unmangle_symbols_from_cmdline(settings.ASYNCIFY_REMOVE) settings.ASYNCIFY_ONLY = unmangle_symbols_from_cmdline(settings.ASYNCIFY_ONLY) if settings.EMULATE_FUNCTION_POINTER_CASTS: # Emulated casts forces a wasm ABI of (i64, i64, ...) in the table, which # means all table functions are illegal for JS to call directly. Use # dyncalls which call into the wasm, which then does an indirect call. settings.DYNCALLS = 1 if options.oformat != OFormat.OBJECT and final_suffix in {'.o', '.bc', '.so', '.dylib'} and not settings.SIDE_MODULE: diagnostics.warning('emcc', 'object file output extension (%s) used for non-object output. If you meant to build an object file please use `-c, `-r`, or `-shared`' % final_suffix) if settings.SUPPORT_BIG_ENDIAN: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [ '$LE_HEAP_STORE_U16', '$LE_HEAP_STORE_I16', '$LE_HEAP_STORE_U32', '$LE_HEAP_STORE_I32', '$LE_HEAP_STORE_U64', '$LE_HEAP_STORE_I64', '$LE_HEAP_STORE_F32', '$LE_HEAP_STORE_F64', '$LE_HEAP_LOAD_U16', '$LE_HEAP_LOAD_I16', '$LE_HEAP_LOAD_U32', '$LE_HEAP_LOAD_I32', '$LE_HEAP_LOAD_U64', '$LE_HEAP_LOAD_I64', '$LE_HEAP_LOAD_F32', '$LE_HEAP_LOAD_F64', '$LE_ATOMICS_NATIVE_BYTE_ORDER', '$LE_ATOMICS_ADD', '$LE_ATOMICS_AND', '$LE_ATOMICS_COMPAREEXCHANGE', '$LE_ATOMICS_EXCHANGE', '$LE_ATOMICS_ISLOCKFREE', '$LE_ATOMICS_LOAD', '$LE_ATOMICS_NOTIFY', '$LE_ATOMICS_OR', '$LE_ATOMICS_STORE', '$LE_ATOMICS_SUB', '$LE_ATOMICS_WAIT', '$LE_ATOMICS_WAITASYNC', '$LE_ATOMICS_XOR', ] if settings.RUNTIME_DEBUG or settings.ASSERTIONS or settings.STACK_OVERFLOW_CHECK or settings.PTHREADS_PROFILING or settings.GL_ASSERTIONS: # Lots of code in debug/assertion blocks uses ptrToString. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$ptrToString'] if settings.STACK_OVERFLOW_CHECK: settings.REQUIRED_EXPORTS += [ 'emscripten_stack_get_end', 'emscripten_stack_get_free', 'emscripten_stack_get_base', 'emscripten_stack_get_current', # We call this function during startup which caches the stack limits # in wasm globals allowing get_base/get_free to be super fast. # See compiler-rt/stack_limits.S. 'emscripten_stack_init', ] if settings.STACK_OVERFLOW_CHECK >= 2: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$setStackLimits'] check_browser_versions() if settings.POLYFILL: # Emscripten requires certain ES6+ constructs by default in library code # - (various ES6 operators available in all browsers listed below) # - https://caniuse.com/mdn-javascript_operators_nullish_coalescing: # FF:72 CHROME:80 SAFARI:13.1 NODE:14 # - https://caniuse.com/mdn-javascript_operators_optional_chaining: # FF:74 CHROME:80 SAFARI:13.1 NODE:14 # - https://caniuse.com/mdn-javascript_operators_logical_or_assignment: # FF:79 CHROME:85 SAFARI:14 NODE:16 # Taking the highest requirements gives is our minimum: # Max Version: FF:79 CHROME:85 SAFARI:14 NODE:16 # TODO: replace this with feature matrix in the future. settings.TRANSPILE = (settings.MIN_FIREFOX_VERSION < 79 or settings.MIN_CHROME_VERSION < 85 or settings.MIN_SAFARI_VERSION < 140000) if settings.STB_IMAGE: settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free'] if settings.USE_WEBGL2: settings.MAX_WEBGL_VERSION = 2 # MIN_WEBGL_VERSION=2 implies MAX_WEBGL_VERSION=2 if settings.MIN_WEBGL_VERSION == 2: default_setting('MAX_WEBGL_VERSION', 2) if settings.MIN_WEBGL_VERSION > settings.MAX_WEBGL_VERSION: exit_with_error('MIN_WEBGL_VERSION must be smaller or equal to MAX_WEBGL_VERSION!') if options.use_preload_plugins or len(options.preload_files) or len(options.embed_files): if settings.NODERAWFS: exit_with_error('--preload-file and --embed-file cannot be used with NODERAWFS which disables virtual filesystem') # if we include any files, or intend to use preload plugins, then we definitely need filesystem support settings.FORCE_FILESYSTEM = 1 if options.preload_files: # File preloading uses `Module['preRun']`. settings.INCOMING_MODULE_JS_API.append('preRun') if settings.FORCE_FILESYSTEM and not settings.FILESYSTEM: exit_with_error('`-sFORCE_FILESYSTEM` cannot be used with `-sFILESYSTEM=0`') if settings.WASMFS: settings.FILESYSTEM = 1 settings.SYSCALLS_REQUIRE_FILESYSTEM = 0 add_system_js_lib('libwasmfs.js') if settings.ASSERTIONS: # used in assertion checks for unflushed content settings.REQUIRED_EXPORTS += ['wasmfs_flush'] if settings.FORCE_FILESYSTEM or settings.INCLUDE_FULL_LIBRARY: # Add exports for the JS API. Like the old JS FS, WasmFS by default # includes just what JS parts it actually needs, and FORCE_FILESYSTEM is # required to force all of it to be included if the user wants to use the # JS API directly. (INCLUDE_FULL_LIBRARY also causes this code to be # included, as the entire JS library can refer to things that require # these exports.) settings.REQUIRED_EXPORTS += [ 'emscripten_builtin_memalign', 'wasmfs_create_file', 'wasmfs_unmount', '_wasmfs_mount', '_wasmfs_read_file', '_wasmfs_write_file', '_wasmfs_open', '_wasmfs_close', '_wasmfs_write', '_wasmfs_pwrite', '_wasmfs_rename', '_wasmfs_mkdir', '_wasmfs_unlink', '_wasmfs_chdir', '_wasmfs_mknod', '_wasmfs_rmdir', '_wasmfs_mmap', '_wasmfs_munmap', '_wasmfs_msync', '_wasmfs_read', '_wasmfs_pread', '_wasmfs_symlink', '_wasmfs_truncate', '_wasmfs_ftruncate', '_wasmfs_stat', '_wasmfs_lstat', '_wasmfs_chmod', '_wasmfs_fchmod', '_wasmfs_lchmod', '_wasmfs_utime', '_wasmfs_llseek', '_wasmfs_identify', '_wasmfs_readlink', '_wasmfs_readdir_start', '_wasmfs_readdir_get', '_wasmfs_readdir_finish', '_wasmfs_get_cwd', ] if settings.FULL_ES3: settings.FULL_ES2 = 1 settings.MAX_WEBGL_VERSION = max(2, settings.MAX_WEBGL_VERSION) if settings.MAIN_READS_PARAMS and not settings.STANDALONE_WASM: # callMain depends on _emscripten_stack_alloc settings.REQUIRED_EXPORTS += ['_emscripten_stack_alloc'] if settings.SUPPORT_LONGJMP == 'emscripten' or not settings.DISABLE_EXCEPTION_CATCHING: # make_invoke depends on stackSave and stackRestore settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stackSave', '$stackRestore'] if settings.SIDE_MODULE and 'GLOBAL_BASE' in user_settings: diagnostics.warning('unused-command-line-argument', 'GLOBAL_BASE is not compatible with SIDE_MODULE') if options.use_preload_plugins: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$Browser'] if not settings.BOOTSTRAPPING_STRUCT_INFO: if settings.DYNAMIC_EXECUTION == 2 and not settings.MINIMAL_RUNTIME: # Used by makeEval in the DYNAMIC_EXECUTION == 2 case settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stackTrace'] if not settings.STANDALONE_WASM and (settings.EXIT_RUNTIME or settings.ASSERTIONS): # to flush streams on FS exit, we need to be able to call fflush # we only include it if the runtime is exitable, or when ASSERTIONS # (ASSERTIONS will check that streams do not need to be flushed, # helping people see when they should have enabled EXIT_RUNTIME) settings.EXPORT_IF_DEFINED += ['fflush'] if settings.SAFE_HEAP: # SAFE_HEAP check includes calling emscripten_get_sbrk_ptr() from wasm settings.REQUIRED_EXPORTS += ['emscripten_get_sbrk_ptr', 'emscripten_stack_get_base'] if not settings.DECLARE_ASM_MODULE_EXPORTS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$assignWasmExports'] if settings.ALLOW_MEMORY_GROWTH: # Setting ALLOW_MEMORY_GROWTH turns off ABORTING_MALLOC, as in that mode we default to # the behavior of trying to grow and returning 0 from malloc on failure, like # a standard system would. However, if the user sets the flag it # overrides that. default_setting('ABORTING_MALLOC', 0) if settings.EMBIND: # Workaround for embind+LTO issue: # https://github.com/emscripten-core/emscripten/issues/21653 settings.REQUIRED_EXPORTS.append('__getTypeName') if settings.PTHREADS or settings.WASM_WORKERS: settings.REQUIRED_EXPORTS.append('_embind_initialize_bindings') # Needed to assign the embind exports to the ES exports. if settings.MODULARIZE == 'instance': settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnPostCtor'] if options.emit_tsd: settings.EMIT_TSD = True if settings.PTHREADS: setup_pthreads() add_system_js_lib('libpthread.js') if settings.PROXY_TO_PTHREAD: settings.PTHREAD_POOL_SIZE_STRICT = 0 settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$runtimeKeepalivePush'] else: if settings.PROXY_TO_PTHREAD: exit_with_error('-sPROXY_TO_PTHREAD requires -pthread to work!') add_system_js_lib('libpthread_stub.js') if settings.MEMORY64: # Any "pointers" passed to JS will now be i64's, in both modes. settings.WASM_BIGINT = 1 if settings.WASM_WORKERS or (settings.SHARED_MEMORY and not settings.PTHREADS): add_system_js_lib('libwasm_worker.js') if settings.WASM_WORKERS: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$_wasmWorkerInitializeRuntime'] # Set min browser versions based on certain settings such as WASM_BIGINT, # PTHREADS, AUDIO_WORKLET # Such setting must be set before this point feature_matrix.apply_min_browser_versions() # TODO(sbc): Find make a generic way to expose the feature matrix to JS # compiler rather then adding them all ad-hoc as internal settings settings.SUPPORTS_PROMISE_ANY = feature_matrix.caniuse(Feature.PROMISE_ANY) default_setting('WASM_BIGINT', feature_matrix.caniuse(Feature.JS_BIGINT_INTEGRATION)) if settings.WASM_BIGINT: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$HEAP64', '$HEAPU64'] if settings.AUDIO_WORKLET: add_system_js_lib('libwebaudio.js') settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$getWasmTableEntry') if not settings.MINIMAL_RUNTIME: if 'preRun' in settings.INCOMING_MODULE_JS_API: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$addOnPreRun') if 'postRun' in settings.INCOMING_MODULE_JS_API: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$addOnPostRun') if settings.FORCE_FILESYSTEM and not settings.MINIMAL_RUNTIME: # when the filesystem is forced, we export by default methods that filesystem usage # may need, including filesystem usage from standalone file packager output (i.e. # file packages not built together with emcc, but that are loaded at runtime # separately, and they need emcc's output to contain the support they need) settings.EXPORTED_RUNTIME_METHODS += [ 'FS_createPath', 'FS_createDataFile', 'FS_preloadFile', 'FS_unlink', ] if not settings.WASMFS: # The old FS has some functionality that WasmFS lacks. settings.EXPORTED_RUNTIME_METHODS += [ 'FS_createLazyFile', 'FS_createDevice', ] settings.EXPORTED_RUNTIME_METHODS += [ 'addRunDependency', 'removeRunDependency', ] if settings.PTHREADS or settings.WASM_WORKERS: settings.IMPORTED_MEMORY = 1 set_initial_memory() # When not declaring wasm module exports in outer scope one by one, disable minifying # wasm module export names so that the names can be passed directly to the outer scope. # Also, if using libexports.js API, disable minification so that the feature can work. if not settings.DECLARE_ASM_MODULE_EXPORTS or '-lexports.js' in linker_args: settings.MINIFY_WASM_EXPORT_NAMES = 0 # Enable minification of wasm imports and exports when appropriate, if we # are emitting an optimized JS+wasm combo (then the JS knows how to load the minified names). # Things that process the JS after this operation would be done must disable this. # JSPI does not support this optimization yet as it has a hardcoded check for 'main' as an # export name. TODO if will_metadce() and \ settings.OPT_LEVEL >= 2 and \ settings.DEBUG_LEVEL <= 2 and \ options.oformat not in {OFormat.WASM, OFormat.BARE} and \ settings.ASYNCIFY != 2 and \ not settings.LINKABLE and \ not settings.STANDALONE_WASM and \ not settings.AUTODEBUG and \ not settings.ASSERTIONS and \ not settings.MAIN_MODULE and \ settings.MINIFY_WASM_EXPORT_NAMES: settings.MINIFY_WASM_IMPORTS_AND_EXPORTS = 1 settings.MINIFY_WASM_IMPORTED_MODULES = 1 if settings.WASM_BIGINT: settings.LEGALIZE_JS_FFI = 0 if settings.SINGLE_FILE and settings.GENERATE_SOURCE_MAP: diagnostics.warning('emcc', 'SINGLE_FILE disables source map support (which requires a .map file)') settings.GENERATE_SOURCE_MAP = 0 if options.use_closure_compiler == 2 and not settings.WASM2JS: exit_with_error('closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm') if settings.AUTODEBUG: settings.REQUIRED_EXPORTS += ['_emscripten_tempret_set'] if settings.LEGALIZE_JS_FFI: settings.REQUIRED_EXPORTS += ['__get_temp_ret', '__set_temp_ret'] if settings.SPLIT_MODULE: settings.INCOMING_MODULE_JS_API += ['loadSplitModule'] # wasm side modules have suffix .wasm if settings.SIDE_MODULE and utils.suffix(target) in {'.js', '.mjs'}: diagnostics.warning('emcc', 'JavaScript output suffix requested, but wasm side modules are just wasm files; emitting only a .wasm, no .js') if options.sanitize: setup_sanitizers(options) if settings.USE_ASAN or settings.SAFE_HEAP: # ASan and SAFE_HEAP check address 0 themselves settings.CHECK_NULL_WRITES = 0 if 'GLOBAL_BASE' not in user_settings and not settings.SHRINK_LEVEL and not settings.OPT_LEVEL and not settings.USE_ASAN: # When optimizing for size it helps to put static data first before # the stack (since this makes instructions for accessing this data # use a smaller LEB encoding). # However, for debugability is better to have the stack come first # (because stack overflows will trap rather than corrupting data). settings.STACK_FIRST = True if '--stack-first' in linker_args: settings.STACK_FIRST = True if settings.USE_ASAN: exit_with_error('--stack-first is not compatible with asan') if 'GLOBAL_BASE' in user_settings: exit_with_error('--stack-first is not compatible with -sGLOBAL_BASE') set_max_memory() # check if we can address the 2GB mark and higher. if not settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 * 1024 * 1024 * 1024: settings.CAN_ADDRESS_2GB = 1 if settings.MAX_WEBGL_VERSION >= 2: settings.WEBGL_USE_GARBAGE_FREE_APIS = 1 # Some browsers have issues using the WebGL2 garbage-free APIs when the # memory offsets are over 2^31 or 2^32 # For firefox see: https://bugzil.la/1838218 if settings.MIN_FIREFOX_VERSION != feature_matrix.UNSUPPORTED and settings.MAXIMUM_MEMORY > 2 ** 31: settings.WEBGL_USE_GARBAGE_FREE_APIS = 0 # For chrome see: https://crbug.com/324992397 if settings.MIN_CHROME_VERSION != feature_matrix.UNSUPPORTED and settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 ** 32: settings.WEBGL_USE_GARBAGE_FREE_APIS = 0 if settings.WEBGL_USE_GARBAGE_FREE_APIS and settings.MIN_WEBGL_VERSION >= 2: settings.INCLUDE_WEBGL1_FALLBACK = 0 if settings.MINIMAL_RUNTIME: if settings.EXIT_RUNTIME: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['proc_exit', '$callRuntimeCallbacks'] else: # MINIMAL_RUNTIME only needs callRuntimeCallbacks in certain cases, but the normal runtime # always does. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks'] if settings.EXIT_RUNTIME and not settings.STANDALONE_WASM: # Internal function implemented in musl that calls any functions registered # via `atexit` et al. With STANDALONE_WASM this is all taken care of via # _start and exit handling in musl, but with the normal emscripten ABI we # need to be able to call these explicitly. settings.REQUIRED_EXPORTS += ['__funcs_on_exit'] if settings.BUILD_AS_WORKER: # The worker code in src/build_as_worker.js depends on realloc settings.REQUIRED_EXPORTS += ['realloc'] options.post_js.append(utils.path_from_root('src/build_as_worker.js')) # Emscripten exception handling can generate invoke calls, and they call # setThrew(). We cannot handle this using deps_info as the invokes are not # emitted because of library function usage, but by codegen itself. if not settings.DISABLE_EXCEPTION_CATCHING: settings.REQUIRED_EXPORTS += ['setThrew'] if settings.ASYNCIFY: if not settings.ASYNCIFY_IGNORE_INDIRECT: # if we are not ignoring indirect calls, then we must treat invoke_* as if # they are indirect calls, since that is what they do - we can't see their # targets statically. settings.ASYNCIFY_IMPORTS += ['invoke_*'] # add the default imports settings.ASYNCIFY_IMPORTS += DEFAULT_ASYNCIFY_IMPORTS # add the default exports (only used for JSPI) settings.ASYNCIFY_EXPORTS += DEFAULT_ASYNCIFY_EXPORTS # return the full import name, including module. The name may # already have a module prefix; if not, we assume it is "env". def get_full_import_name(name): if '.' in name: return name return 'env.' + name settings.ASYNCIFY_IMPORTS = [get_full_import_name(i) for i in settings.ASYNCIFY_IMPORTS] if settings.ASYNCIFY == 2: diagnostics.warning('experimental', '-sJSPI (ASYNCIFY=2) is still experimental') if settings.WASM2JS: if settings.GENERATE_SOURCE_MAP: exit_with_error('wasm2js does not support source maps yet (debug in wasm for now)') if settings.MEMORY64: exit_with_error('wasm2js does not support MEMORY64') if settings.WASM_BIGINT: exit_with_error('wasm2js does not support WASM_BIGINT') if settings.CAN_ADDRESS_2GB: exit_with_error('wasm2js does not support >2gb address space') if settings.NODE_CODE_CACHING: if settings.WASM_ASYNC_COMPILATION: exit_with_error('NODE_CODE_CACHING requires sync compilation (WASM_ASYNC_COMPILATION=0)') if not settings.ENVIRONMENT_MAY_BE_NODE: exit_with_error('NODE_CODE_CACHING only works in node, but target environments do not include it') if settings.SINGLE_FILE: exit_with_error('NODE_CODE_CACHING saves a file on the side and is not compatible with SINGLE_FILE') if not js_manipulation.isidentifier(settings.EXPORT_NAME): exit_with_error(f'EXPORT_NAME is not a valid JS identifier: `{settings.EXPORT_NAME}`') if settings.EMSCRIPTEN_TRACING: add_system_js_lib('libtrace.js') settings.INCOMING_MODULE_JS_API += ['onMalloc', 'onRealloc', 'onFree', 'onSbrkGrow'] if settings.ALLOW_MEMORY_GROWTH: settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout'] settings.REQUIRED_EXPORTS += ['emscripten_stack_get_current', 'emscripten_stack_get_base', 'emscripten_stack_get_end'] settings.EMSCRIPTEN_VERSION = utils.EMSCRIPTEN_VERSION if settings.RETAIN_COMPILER_SETTINGS: settings.PUBLIC_SETTINGS = [k for k in settings.attrs.keys() if k not in settings.internal_settings] # Also include EMSCRIPTEN_VERSION from the internal settings since there are # known usage of this with `emscripten_get_compiler_setting`. settings.PUBLIC_SETTINGS.append('EMSCRIPTEN_VERSION') settings.SOURCE_MAP_BASE = options.source_map_base or '' settings.LINK_AS_CXX = (shared.run_via_emxx or settings.DEFAULT_TO_CXX) and not options.nostdlibxx # WASMFS itself is written in C++, and needs C++ standard libraries if settings.WASMFS: settings.LINK_AS_CXX = True # Some settings make no sense when not linking as C++ if not settings.LINK_AS_CXX: cxx_only_settings = [ 'EXCEPTION_DEBUG', 'DISABLE_EXCEPTION_CATCHING', 'EXCEPTION_CATCHING_ALLOWED', 'DISABLE_EXCEPTION_THROWING', ] for setting in cxx_only_settings: if setting in user_settings: diagnostics.warning('linkflags', 'setting `%s` is not meaningful unless linking as C++', setting) if settings.WASM_EXCEPTIONS: settings.REQUIRED_EXPORTS += ['__trap'] if settings.EXCEPTION_STACK_TRACES: # If the user explicitly gave EXCEPTION_STACK_TRACES=1 without enabling EH, # errors out. if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS: exit_with_error('EXCEPTION_STACK_TRACES requires either of -fexceptions or -fwasm-exceptions') if settings.SIDE_MODULE: # For side modules, we ignore all REQUIRED_EXPORTS that might have been added above. # They all come from either libc or compiler-rt. The exception is __wasm_call_ctors # and _emscripten_tls_init which are per-module exports. settings.REQUIRED_EXPORTS.clear() if not settings.STANDALONE_WASM: # in standalone mode, crt1 will call the constructors from inside the wasm settings.REQUIRED_EXPORTS.append('__wasm_call_ctors') if settings.PTHREADS: settings.REQUIRED_EXPORTS.append('_emscripten_tls_init') if settings.NODERAWFS: default_setting('NODE_HOST_ENV', 1) if not settings.ENVIRONMENT_MAY_BE_NODE: # Node-specific settings only make sense if ENVIRONMENT_MAY_BE_NODE if settings.NODERAWFS: diagnostics.warning('unused-command-line-argument', 'NODERAWFS ignored since `node` not in `ENVIRONMENT`') if settings.NODE_CODE_CACHING: diagnostics.warning('unused-command-line-argument', 'NODE_CODE_CACHING ignored since `node` not in `ENVIRONMENT`') settings.PRE_JS_FILES = options.pre_js settings.POST_JS_FILES = options.post_js settings.MINIFY_WHITESPACE = settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL == 0 and not options.no_minify # Closure might be run if we run it ourselves, or if whitespace is not being # minified. In the latter case we keep both whitespace and comments, and the # purpose of the comments might be closure compiler, so also perform all # adjustments necessary to ensure that works (which amounts to a few more # comments; adding some more of them is not an issue in such a build which # includes all comments and whitespace anyhow). if settings.USE_CLOSURE_COMPILER or not settings.MINIFY_WHITESPACE: settings.MAYBE_CLOSURE_COMPILER = 1 check_settings() return target, wasm_target @ToolchainProfiler.profile_block('calculate system libraries') def phase_calculate_system_libraries(options): extra_files_to_link = [] # Link in ports and system libraries, if necessary if not settings.SIDE_MODULE: # Ports are always linked into the main module, never the side module. extra_files_to_link += ports.get_libs(settings) extra_files_to_link += system_libs.calculate(options) return extra_files_to_link @ToolchainProfiler.profile_block('link') def phase_link(linker_args, wasm_target, js_syms): logger.debug(f'linking: {linker_args}') # Make a final pass over settings.EXPORTED_FUNCTIONS to remove any # duplication between functions added by the driver/libraries and function # specified by the user settings.EXPORTED_FUNCTIONS = dedup_list(settings.EXPORTED_FUNCTIONS) settings.REQUIRED_EXPORTS = dedup_list(settings.REQUIRED_EXPORTS) settings.EXPORT_IF_DEFINED = dedup_list(settings.EXPORT_IF_DEFINED) rtn = None if settings.LINKABLE and not settings.EXPORT_ALL: # In LINKABLE mode we pass `--export-dynamic` along with `--whole-archive`. This results # in over 7000 exports, which cannot be distinguished from the few symbols we explicitly # export via EMSCRIPTEN_KEEPALIVE or EXPORTED_FUNCTIONS. # In order to avoid unnecessary exported symbols on the `Module` object we run the linker # twice in this mode: # 1. Without `--export-dynamic` to get the base exports # 2. With `--export-dynamic` to get the actual linkable Wasm binary # TODO(sbc): Remove this double execution of wasm-ld if we ever find a way to # distinguish EMSCRIPTEN_KEEPALIVE exports from `--export-dynamic` exports. settings.LINKABLE = False building.link_lld(linker_args, wasm_target, external_symbols=js_syms) settings.LINKABLE = True rtn = extract_metadata.extract_metadata(wasm_target) building.link_lld(linker_args, wasm_target, external_symbols=js_syms) return rtn @ToolchainProfiler.profile_block('post link') def phase_post_link(options, in_wasm, wasm_target, target, js_syms, base_metadata=None): global final_js target_basename = unsuffixed_basename(target) if options.oformat != OFormat.WASM: final_js = in_temp(target_basename + '.js') settings.TARGET_BASENAME = unsuffixed_basename(target) if options.oformat in {OFormat.JS, OFormat.MJS}: js_target = target else: js_target = get_secondary_target(target, '.js') settings.TARGET_JS_NAME = os.path.basename(js_target) metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) if settings.EMBIND_AOT: phase_embind_aot(options, wasm_target, js_syms) if options.emit_tsd: phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata) if options.js_transform: phase_source_transforms(options) phase_binaryen(target, options, wasm_target) # If we are not emitting any JS then we are all done now if options.oformat != OFormat.WASM: phase_final_emitting(options, target, js_target, wasm_target) @ToolchainProfiler.profile_block('emscript') def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata): # Emscripten logger.debug('emscript') # No need to support base64 embedding in wasm2js mode since # the module is already in JS format. if settings.SINGLE_FILE and not settings.SINGLE_FILE_BINARY_ENCODE and not settings.WASM2JS: settings.SUPPORT_BASE64_EMBEDDING = 1 settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$base64Decode') if shared.SKIP_SUBPROCS: return metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms, base_metadata=base_metadata) save_intermediate('original') return metadata def run_embind_gen(options, wasm_target, js_syms, extra_settings): # Save settings so they can be restored after TS generation. original_settings = settings.backup() settings.attrs.update(extra_settings) settings.EMBIND_GEN_MODE = True if settings.MAIN_MODULE: # Copy libraries to the temp directory so they can be used when running # in node. for f in options.input_files: if building.is_wasm_dylib(f): safe_copy(f, in_temp('')) # Ignore any options or settings that can conflict with running the TS # generation output. # Don't invoke the program's `main` function. settings.INVOKE_RUN = False # Ignore -sMODULARIZE which could otherwise affect how we run the module # to generate the bindings. settings.MODULARIZE = False # Disable ESM integration to avoid enabling the experimental feature in node. settings.WASM_ESM_INTEGRATION = False # Don't include any custom user JS or files. settings.PRE_JS_FILES = [] settings.POST_JS_FILES = [] # Force node since that is where the tool runs. if 'node' not in settings.ENVIRONMENT: settings.ENVIRONMENT.append('node') settings.MIN_NODE_VERSION = feature_matrix.OLDEST_SUPPORTED_NODE settings.MINIMAL_RUNTIME = 0 # Required function to trigger TS generation. settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks', '$addRunDependency', '$removeRunDependency'] settings.EXPORT_ES6 = False # Disable proxying and thread pooling so a worker is not automatically created. settings.PROXY_TO_PTHREAD = False settings.PTHREAD_POOL_SIZE = 0 # Assume wasm support at binding generation time settings.WASM2JS = 0 # Disable minify since the binaryen pass has not been run yet to change the # import names. settings.MINIFY_WASM_IMPORTED_MODULES = False setup_environment_settings() # Use a separate Wasm file so the JS does not need to be modified after emscripten.emscript. settings.SINGLE_FILE = False # Replace embind with the TypeScript generation version. for i, lib in enumerate(settings.JS_LIBRARIES): dirname, basename = os.path.split(lib) if basename == 'libembind.js': settings.JS_LIBRARIES[i] = os.path.join(dirname, 'libembind_gen.js') # The final version of the memory64 proposal is not implemented until node # v24, so we need to lower it away in order to execute the binary at build # time. # TODO Remove lowering when emsdk version of node is >= 24 and just require it. if settings.MEMORY64: settings.MEMORY64 = 2 if settings.MAXIMUM_MEMORY and settings.MAXIMUM_MEMORY > 2147483648: settings.MAXIMUM_MEMORY = 2147483648 # Source maps haven't been generated yet and aren't needed to run embind_gen. settings.LOAD_SOURCE_MAP = 0 settings.GENERATE_SOURCE_MAP = 0 outfile_js = in_temp('tsgen.js') # The Wasm outfile may be modified by emscripten.emscript, so use a temporary file. outfile_wasm = in_temp('tsgen.wasm') metadata = emscripten.emscript(wasm_target, outfile_wasm, outfile_js, js_syms, finalize=False) # Strip out any Wasm features that can't run in older versions of node. wasm_opt_args = [] if '--enable-relaxed-simd' in metadata.features: # Relaxed SIMD isn't supported on older versions of node. wasm_opt_args += ['--remove-relaxed-simd'] if '--enable-memory64' in metadata.features: # See comment above about lowering memory64. wasm_opt_args += ['--memory64-lowering', '--table64-lowering'] if wasm_opt_args: building.run_wasm_opt(outfile_wasm, outfile_wasm, wasm_opt_args) # Build the flags needed by Node.js to properly run the output file. node_args = [] if settings.WASM_EXCEPTIONS: node_args += shared.node_exception_flags(config.NODE_JS) # Run the generated JS file with the proper flags to generate the TypeScript bindings. output_file = in_temp('embind_generated_output.js') shared.run_js_tool(outfile_js, [output_file], node_args) settings.restore(original_settings) return read_file(output_file) @ToolchainProfiler.profile_block('emit tsd') def phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata): logger.debug('emit tsd') filename = options.emit_tsd embind_tsd = '' if settings.EMBIND: embind_tsd = run_embind_gen(options, wasm_target, js_syms, {'EMBIND_AOT': False}) all_tsd = emscripten.create_tsd(metadata, embind_tsd) out_file = os.path.join(os.path.dirname(js_target), filename) write_file(out_file, all_tsd) @ToolchainProfiler.profile_block('embind aot js') def phase_embind_aot(options, wasm_target, js_syms): out = run_embind_gen(options, wasm_target, js_syms, {}) if DEBUG: write_file(in_temp('embind_aot.json'), out) out = json.loads(out) src = read_file(final_js) src = do_replace(src, '<<< EMBIND_AOT_INVOKERS >>>', out['invokers']) if settings.MODULARIZE == 'instance': # Add ES module exports for the embind exports. decls = '\n'.join([f'export var {name};' for name in out['publicSymbols']]) # Assign the runtime exports from Module to the ES export. assigns = '\n'.join([f'{name} = Module[\'{name}\'];' for name in out['publicSymbols']]) exports = f''' // start embind exports function assignEmbindExports() {{ {assigns} }}; addOnPostCtor(assignEmbindExports); {decls} // end embind exports''' src += exports write_file(final_js, src) if settings.WASM_ESM_INTEGRATION: # With ESM integration the embind exports also need to be exported by the main file. settings.EXPORTED_RUNTIME_METHODS.extend(out['publicSymbols']) # for Popen, we cannot have doublequotes, so provide functionality to # remove them when needed. def remove_quotes(arg): if isinstance(arg, list): return [remove_quotes(a) for a in arg] if arg.startswith('"') and arg.endswith('"'): return arg[1:-1].replace('\\"', '"') elif arg.startswith("'") and arg.endswith("'"): return arg[1:-1].replace("\\'", "'") else: return arg @ToolchainProfiler.profile_block('source transforms') def phase_source_transforms(options): # Apply a source code transformation, if requested global final_js safe_copy(final_js, final_js + '.tr.js') final_js += '.tr.js' posix = not WINDOWS logger.debug('applying transform: %s', options.js_transform) shared.check_call(remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final_js)])) save_intermediate('transformed') # Unmangle previously mangled `import.meta` and `await import` references in # both main code and libraries. # See also: `preprocess` in parseTools.js. def fix_js_mangling(js_file): # We don't apply these mangliings except in MODULARIZE/EXPORT_ES6 modes. if not settings.MODULARIZE: return src = read_file(js_file) write_file(js_file, src .replace('EMSCRIPTEN$IMPORT$META', 'import.meta') .replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import') .replace('EMSCRIPTEN$AWAIT(', 'await (')) save_intermediate('js-mangling') def node_detection_code(): return "globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'" def create_esm_wrapper(wrapper_file, support_target, wasm_target): js_exports = building.user_requested_exports.union(settings.EXPORTED_RUNTIME_METHODS) js_exports = ', '.join(sorted(js_exports)) wrapper = [] wrapper.append('// The wasm module must be imported here first before the support file') wrapper.append('// in order to avoid issues with circular dependencies.') wrapper.append(f"import * as unused from './{settings.WASM_BINARY_FILE}';") support_url = f'./{os.path.basename(support_target)}' if js_exports: wrapper.append(f"export {{ default, {js_exports} }} from '{support_url}';") else: wrapper.append(f"export {{ default }} from '{support_url}';") if settings.ENVIRONMENT_MAY_BE_NODE: wrapper.append(f''' // When run as the main module under node, create the module directly. This will // execute any startup code along with main (if it exists). import init from '{support_url}'; const isNode = {node_detection_code()}; if (isNode) {{ const url = await import('node:url'); const isMainModule = url.pathToFileURL(process.argv[1]).href === import.meta.url; if (isMainModule) await init(); }}''') write_file(wrapper_file, '\n'.join(wrapper) + '\n') # FIXME(sbc): This is a huge hack to rename the imports in the # wasm file. Find a better way to do this. wasm_dis = os.path.join(building.get_binaryen_bin(), 'wasm-dis') mod = shared.check_call([wasm_dis, wasm_target], stdout=PIPE).stdout mod = mod.replace('(import "env"', f'(import "{support_url}"') mod = mod.replace('(import "wasi_snapshot_preview1"', f'(import "{support_url}"') wasm_as = os.path.join(building.get_binaryen_bin(), 'wasm-as') cmd = [wasm_as, '--all-features', '-o', wasm_target, '-'] if settings.EMIT_NAME_SECTION: cmd.append('-g') shared.check_call(cmd, input=mod) def convert_line_endings_in_file(filename, to_eol): if to_eol == os.linesep: assert os.path.exists(filename) return # No conversion needed text = read_file(filename) write_file(filename, text, line_endings=to_eol) @ToolchainProfiler.profile_block('final emitting') def phase_final_emitting(options, target, js_target, wasm_target): global final_js if shared.SKIP_SUBPROCS: return if settings.MODULARIZE and settings.MODULARIZE != 'instance': modularize() elif settings.USE_CLOSURE_COMPILER: module_export_name_substitution() # Run a final optimization pass to clean up items that were not possible to # optimize by Closure, or unoptimalities that were left behind by processing # steps that occurred after Closure. if settings.MINIMAL_RUNTIME == 2 and settings.USE_CLOSURE_COMPILER and settings.DEBUG_LEVEL == 0: args = [final_js, '-o', final_js] if not settings.MINIFY_WHITESPACE: args.append('--pretty') shared.run_js_tool(utils.path_from_root('tools/unsafe_optimizations.mjs'), args, cwd=utils.path_from_root('.')) save_intermediate('unsafe-optimizations') # Finally, rerun Closure compile with simple optimizations. It will be able # to further minify the code. (n.b. it would not be safe to run in advanced # mode) final_js = building.closure_compiler(final_js, advanced=False, extra_closure_args=settings.CLOSURE_ARGS) # Run unsafe_optimizations.js once more. This allows the cleanup of newly # unused things that closure compiler leaves behind (e.g `new Float64Array(x)`). shared.run_js_tool(utils.path_from_root('tools/unsafe_optimizations.mjs'), [final_js, '-o', final_js], cwd=utils.path_from_root('.')) save_intermediate('unsafe-optimizations2') fix_js_mangling(final_js) # Apply pre and postjs files if options.extern_pre_js or options.extern_post_js: extern_pre_js = read_js_files(options.extern_pre_js) extern_post_js = read_js_files(options.extern_post_js) logger.debug('applying extern pre/postjses') src = read_file(final_js) final_js += '.epp.js' with open(final_js, 'w', encoding='utf-8') as f: f.write(extern_pre_js) f.write(src) f.write(extern_post_js) save_intermediate('extern-pre-post') js_manipulation.handle_license(final_js) # The JS is now final. Move it to its final location if settings.WASM_ESM_INTEGRATION: support_target = unsuffixed(js_target) + '.support.mjs' move_file(final_js, support_target) create_esm_wrapper(js_target, support_target, wasm_target) if settings.PTHREADS: support_target = unsuffixed(js_target) + '.pthread.mjs' pthread_code = building.read_and_preprocess(utils.path_from_root('src/pthread_esm_startup.mjs'), expand_macros=True) write_file(support_target, pthread_code) fix_js_mangling(support_target) else: move_file(final_js, js_target) target_basename = unsuffixed_basename(target) convert_line_endings_in_file(js_target, options.output_eol) # If we were asked to also generate HTML, do that if options.oformat == OFormat.HTML: generate_html(target, options, js_target, target_basename, wasm_target) if settings.SPLIT_MODULE: do_split_module(wasm_target, options) if settings.EXECUTABLE: make_js_executable(js_target) @ToolchainProfiler.profile_block('binaryen') def phase_binaryen(target, options, wasm_target): global final_js logger.debug('using binaryen') # whether we need to emit -g (function name debug info) in the final wasm debug_function_names = settings.DEBUG_LEVEL >= 2 or settings.EMIT_NAME_SECTION # whether we need to emit -g in the intermediate binaryen invocations (but not # necessarily at the very end). this is necessary if we depend on debug info # during compilation, even if we do not emit it at the end. # we track the number of causes for needing intermediate debug info so # that we can stop emitting it when possible - in particular, that is # important so that we stop emitting it before the end, and it is not in the # final binary (if it shouldn't be) intermediate_debug_info = 0 if debug_function_names: intermediate_debug_info += 1 if options.emit_symbol_map: intermediate_debug_info += 1 if settings.ASYNCIFY == 1: intermediate_debug_info += 1 # run wasm-opt if we have work for it: either passes, or if we are using # source maps (which requires some extra processing to keep the source map # but remove DWARF) passes = get_binaryen_passes(options) if passes: # if asyncify is used, we will use it in the next stage, and so if it is # the only reason we need intermediate debug info, we can stop keeping it if settings.ASYNCIFY == 1: intermediate_debug_info -= 1 # currently binaryen's DWARF support will limit some optimizations; warn on # that. see https://github.com/emscripten-core/emscripten/issues/15269 if settings.GENERATE_DWARF and should_run_binaryen_optimizer(): diagnostics.warning('limited-postlink-optimizations', 'running limited binaryen optimizations because DWARF info requested (or indirectly required)') with ToolchainProfiler.profile_block('wasm_opt'): building.run_wasm_opt(wasm_target, wasm_target, args=passes, debug=intermediate_debug_info) building.save_intermediate(wasm_target, 'byn.wasm') if settings.EVAL_CTORS: with ToolchainProfiler.profile_block('eval_ctors'): building.eval_ctors(final_js, wasm_target, debug_info=intermediate_debug_info) building.save_intermediate(wasm_target, 'ctors.wasm') # after generating the wasm, do some final operations if final_js: # >=2GB heap support requires pointers in JS to be unsigned. rather than # require all pointers to be unsigned by default, which increases code size # a little, keep them signed, and just unsign them here if we need that. if settings.CAN_ADDRESS_2GB: with ToolchainProfiler.profile_block('use_unsigned_pointers_in_js'): final_js = building.use_unsigned_pointers_in_js(final_js) if settings.USE_ASAN: final_js = building.instrument_js_for_asan(final_js) if settings.SAFE_HEAP: final_js = building.instrument_js_for_safe_heap(final_js) # shared memory growth requires some additional JS fixups. # note that we must do this after handling of unsigned pointers. unsigning # adds some >>> 0 things, while growth will replace a HEAP8 with a call to # a method to get the heap, and that call would not be recognized by the # unsigning pass. # we also must do this after the asan or safe_heap instrumentation, as they # wouldn't be able to recognize patterns produced by the growth pass. if settings.SHARED_MEMORY and settings.ALLOW_MEMORY_GROWTH and not settings.GROWABLE_ARRAYBUFFERS: with ToolchainProfiler.profile_block('apply_wasm_memory_growth'): final_js = building.apply_wasm_memory_growth(final_js) if settings.SUPPORT_BIG_ENDIAN: with ToolchainProfiler.profile_block('little_endian_heap'): final_js = building.little_endian_heap(final_js) if settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL <= 2: # minify the JS. Do not minify whitespace if Closure is used, so that # Closure can print out readable error messages (Closure will then # minify whitespace afterwards) with ToolchainProfiler.profile_block('minify_wasm'): save_intermediate_with_wasm('preclean', wasm_target) final_js = building.minify_wasm_js(js_file=final_js, wasm_file=wasm_target, expensive_optimizations=will_metadce(), debug_info=intermediate_debug_info) save_intermediate_with_wasm('postclean', wasm_target) if options.use_closure_compiler: with ToolchainProfiler.profile_block('closure_compile'): final_js = building.closure_compiler(final_js, extra_closure_args=settings.CLOSURE_ARGS) save_intermediate('closure') if settings.TRANSPILE: with ToolchainProfiler.profile_block('transpile'): final_js = building.transpile(final_js) save_intermediate('transpile') # Run acorn one more time to minify whitespace after babel runs if settings.MINIFY_WHITESPACE: final_js = building.acorn_optimizer(final_js, ['--minify-whitespace']) symbols_file = None if options.emit_symbol_map: symbols_file = shared.replace_or_append_suffix(target, '.symbols') if settings.WASM2JS: symbols_file_js = None if settings.WASM == 2: # With normal wasm2js mode this file gets included as part of the # preamble, but with WASM=2 its a separate file. wasm2js_polyfill = building.read_and_preprocess(utils.path_from_root('src/wasm2js.js'), expand_macros=True) wasm2js_template = wasm_target + '.js' write_file(wasm2js_template, wasm2js_polyfill) # generate secondary file for JS symbols if options.emit_symbol_map: symbols_file_js = shared.replace_or_append_suffix(wasm2js_template, '.symbols') else: wasm2js_template = final_js if options.emit_symbol_map: symbols_file_js = shared.replace_or_append_suffix(target, '.symbols') wasm2js = building.wasm2js(wasm2js_template, wasm_target, opt_level=settings.OPT_LEVEL, use_closure_compiler=options.use_closure_compiler, debug_info=debug_function_names, symbols_file=symbols_file, symbols_file_js=symbols_file_js) shared.get_temp_files().note(wasm2js) if settings.WASM == 2: safe_copy(wasm2js, wasm2js_template) if settings.WASM != 2: final_js = wasm2js save_intermediate('wasm2js') generating_wasm = settings.WASM == 2 or not settings.WASM2JS # emit the final symbols, either in the binary or in a symbol map. # this will also remove debug info if we only kept it around in the intermediate invocations. # note that if we aren't emitting a binary (like in wasm2js) then we don't # have anything to do here. if options.emit_symbol_map: intermediate_debug_info -= 1 if generating_wasm: building.write_symbol_map(wasm_target, symbols_file) if not intermediate_debug_info: building.strip_sections(wasm_target, wasm_target, ['name']) if settings.GENERATE_DWARF and settings.SEPARATE_DWARF and generating_wasm: # if the dwarf filename wasn't provided, use the default target + a suffix wasm_file_with_dwarf = settings.SEPARATE_DWARF if wasm_file_with_dwarf is True: # Historically this file has been called `.wasm.debug.wasm` # TODO(sbc): Should this just be `.debug.wasm` wasm_file_with_dwarf = get_secondary_target(target, '.wasm.debug.wasm') building.emit_debug_on_side(wasm_target, wasm_file_with_dwarf) # we have finished emitting the wasm, and so intermediate debug info will # definitely no longer be used tracking it. if debug_function_names: intermediate_debug_info -= 1 assert intermediate_debug_info == 0 # strip debug info if it was not already stripped by the last command if not debug_function_names and building.binaryen_kept_debug_info and generating_wasm: building.strip_sections(wasm_target, wasm_target, ['name']) # replace placeholder strings with correct subresource locations if final_js and settings.SINGLE_FILE and not settings.WASM2JS: js = read_file(final_js) if settings.SINGLE_FILE_BINARY_ENCODE: js = do_replace(js, '"<<< WASM_BINARY_DATA >>>"', binary_encode(wasm_target)) else: js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(wasm_target)) delete_file(wasm_target) write_file(final_js, js) def modularize(): global final_js logger.debug(f'Modularizing, creating factory function called `{settings.EXPORT_NAME}`') modularize_src = building.read_and_preprocess(utils.path_from_root('src/modularize.js'), expand_macros=True) if settings.MINIFY_WHITESPACE: with shared.get_temp_files().get_file(suffix='.js') as tmp: write_file(tmp, modularize_src) minified_file = building.acorn_optimizer(tmp, ['--minify-whitespace']) modularize_src = read_file(minified_file) # Replace INNER_JS_CODE in the minified code full_src = do_replace(modularize_src, '"<<< INNER_JS_CODE >>>"', read_file(final_js)) final_js += '.modular.js' write_file(final_js, full_src) shared.get_temp_files().note(final_js) save_intermediate('modularized') # FIXME(https://github.com/emscripten-core/emscripten/issues/24558): Running acorn at this # late phase seems to cause OOM (some kind of infinite loop perhaps) in node. # Instead we minify src/modularize.js in isolation above. # if settings.MINIFY_WHITESPACE: # final_js = building.acorn_optimizer(final_js, ['--minify-whitespace']) def module_export_name_substitution(): assert not settings.MODULARIZE global final_js logger.debug(f'Private module export name substitution with {settings.EXPORT_NAME}') src = read_file(final_js) final_js += '.module_export_name_substitution.js' if settings.MINIMAL_RUNTIME and not settings.ENVIRONMENT_MAY_BE_NODE and not settings.ENVIRONMENT_MAY_BE_SHELL and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET: # On the web, with MINIMAL_RUNTIME, the Module object is always provided # via the shell html in order to provide the .asm.js/.wasm content. replacement = settings.EXPORT_NAME else: replacement = "typeof %(EXPORT_NAME)s != 'undefined' ? %(EXPORT_NAME)s : {}" % {"EXPORT_NAME": settings.EXPORT_NAME} new_src = re.sub(r'{\s*[\'"]?__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__[\'"]?:\s*1\s*}', replacement, src) assert new_src != src, 'Unable to find Closure syntax __EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__ in source!' write_file(final_js, new_src) shared.get_temp_files().note(final_js) save_intermediate('module_export_name_substitution') def generate_traditional_runtime_html(target, options, js_target, wasm_target): script = ScriptSource() if settings.EXPORT_NAME != 'Module' and options.shell_html == DEFAULT_SHELL_HTML: # the minimal runtime shell HTML is designed to support changing the export # name, but the normal one does not support that currently exit_with_error('customizing EXPORT_NAME requires that the HTML be customized to use that name (see https://github.com/emscripten-core/emscripten/issues/10086)') shell = building.read_and_preprocess(options.shell_html) if '{{{ SCRIPT }}}' not in shell: exit_with_error('HTML shell must contain {{{ SCRIPT }}}, see src/shell.html for an example') if settings.SINGLE_FILE: js_contents = script.inline or '' js_contents += read_file(js_target) script.inline = read_file(js_target) delete_file(js_target) else: script.src = os.path.basename(js_target) if not settings.WASM_ASYNC_COMPILATION: # We need to load the wasm file before anything else, since it # has be synchronously ready. script.un_src() script.inline = ''' fetch(%s).then((result) => result.arrayBuffer()) .then((buf) => { Module.wasmBinary = buf; %s; }); ''' % (get_subresource_location(wasm_target), script.inline) if settings.WASM == 2: # If target browser does not support WebAssembly, we need to load # the .wasm.js file before the main .js file. script.un_src() script.inline = ''' function loadMainJs() { %s } if (!window.WebAssembly || location.search.indexOf('_rwasm=0') > 0) { // Current browser does not support WebAssembly, load the .wasm.js JavaScript fallback // before the main JS runtime. var wasm2js = document.createElement('script'); wasm2js.src = %s; wasm2js.onload = loadMainJs; document.body.appendChild(wasm2js); } else { // Current browser supports Wasm, proceed with loading the main JS runtime. loadMainJs(); } ''' % (script.inline, get_subresource_location_js(wasm_target + '.js')) shell = do_replace(shell, '{{{ SCRIPT }}}', script.replacement()) shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME) shell = shell.replace('{{{ SHELL_CSS }}}', utils.read_file(utils.path_from_root('html/shell.css'))) logo_filename = utils.path_from_root('media/powered_by_logo_shell.png') logo_b64 = base64_encode(logo_filename) shell = shell.replace('{{{ SHELL_LOGO }}}', f'') check_output_file(target) write_file(target, shell) @ToolchainProfiler.profile() def minify_html(filename): if settings.DEBUG_LEVEL >= 2: return opts = [] # -g1 and greater retain whitespace and comments in source if settings.DEBUG_LEVEL == 0: opts += ['--collapse-whitespace', '--remove-comments', '--remove-tag-whitespace', '--sort-attributes', '--sort-class-name'] # -g2 and greater do not minify HTML at all if settings.DEBUG_LEVEL <= 1: opts += ['--decode-entities', '--collapse-boolean-attributes', '--remove-attribute-quotes', '--remove-redundant-attributes', '--remove-script-type-attributes', '--remove-style-link-type-attributes', '--use-short-doctype', '--minify-css', 'true', '--minify-js', 'true'] # html-minifier also has the following options, but they look unsafe for use: # '--collapse-inline-tag-whitespace': removes whitespace between inline tags in visible text, # causing words to be joined together. See # https://github.com/terser/html-minifier-terser/issues/179 # https://github.com/emscripten-core/emscripten/issues/22188 # '--remove-optional-tags': removes e.g. and tags from the page. # (Breaks at least browser.test_sdl2glshader) # '--remove-empty-attributes': removes all attributes with whitespace-only values. # (Breaks at least browser.test_asmfs_hello_file) # '--remove-empty-elements': removes all elements with empty contents. # (Breaks at least browser.test_asm_swapping) logger.debug(f'minifying HTML file {filename}') size_before = os.path.getsize(filename) shared.check_call(shared.get_npm_cmd('html-minifier-terser') + [filename, '-o', filename] + opts, env=shared.env_with_node_in_path()) # HTML minifier will turn all null bytes into an escaped two-byte sequence "\0". Turn those back to single byte sequences. def unescape_nulls(filename): data = read_file(filename) out = [] in_escape = False i = 0 while i < len(data): ch = data[i] i += 1 if ch == '\\': if in_escape: out.append('\\\\') in_escape = not in_escape elif in_escape: in_escape = False if ch == '0': out.append('\x00') # Convert '\\0' (5Ch 00h) into '\0' (00h) elif ch == 'x' and data[i] == '0' and data[i + 1] == '0': out.append('\x00') # Oddly html-minifier generates both "\\0" and "\\x00", so handle that too. i += 2 else: out.append('\\') out.append(ch) else: out.append(ch) write_file(filename, ''.join(out)) unescape_nulls(filename) size_after = os.path.getsize(filename) delta = size_after - size_before logger.debug(f'HTML minification shrunk {filename} from {size_before} to {size_after} bytes, delta={delta} ({delta * 100.0 / size_before:+.2f}%)') def generate_html(target, options, js_target, target_basename, wasm_target): logger.debug('generating HTML') if settings.MINIMAL_RUNTIME: generate_minimal_runtime_html(target, options, js_target, target_basename) else: generate_traditional_runtime_html(target, options, js_target, wasm_target) if settings.MINIFY_HTML and (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1): minify_html(target) convert_line_endings_in_file(target, options.output_eol) def find_library(lib, lib_dirs): for lib_dir in lib_dirs: path = os.path.join(lib_dir, lib) if os.path.isfile(path): logger.debug('found library "%s" at %s', lib, path) return path return None def map_to_js_libs(library_name): """Map a library name to one or more JS libraries. Given the name of a special Emscripten-implemented system library, return a list of absolute paths to JS library files, inside emscripten/src/, that should be included. 'None' means there is no mapping and the library will be processed by the linker as a require for normal native library. """ # Some native libraries are implemented in Emscripten as system side JS libraries library_map = { 'embind': ['libembind.js', 'libemval.js'], 'EGL': ['libegl.js'], 'GL': ['libwebgl.js', 'libhtml5_webgl.js'], 'webgl.js': ['libwebgl.js', 'libhtml5_webgl.js'], 'GLESv2': ['libwebgl.js'], # N.b. there is no GLESv3 to link to (note [f] in https://www.khronos.org/registry/implementers_guide.html) 'GLEW': ['libglew.js'], 'glfw': ['libglfw.js'], 'glfw3': ['libglfw.js'], 'GLU': [], 'glut': ['libglut.js'], 'openal': ['libopenal.js'], 'X11': ['libxlib.js'], 'SDL': ['libsdl.js'], 'uuid': ['libuuid.js'], 'fetch': ['libfetch.js'], 'websocket': ['libwebsocket.js'], # These 4 libraries are separate under glibc but are all rolled into # libc with musl. For compatibility with glibc we just ignore them # completely. 'dl': [], 'm': [], 'rt': [], 'pthread': [], # This is the name of GNU's C++ standard library. We ignore it here # for compatibility with GNU toolchains. 'stdc++': [], 'SDL2_mixer': [], } if library_name in library_map: libs = library_map[library_name] logger.debug('Mapping library `%s` to JS libraries: %s' % (library_name, libs)) return libs return None def process_libraries(options, flags): """Process `-l` and `--js-library` flags.""" new_flags = [] system_libs_map = system_libs.Library.get_usable_variations() for flag in flags: if flag.startswith('--js-library='): js_lib = flag.split('=', 1)[1] settings.JS_LIBRARIES.append(js_lib) continue if not flag.startswith('-l'): new_flags.append(flag) continue lib = flag.removeprefix('-l') logger.debug('looking for library "%s"', lib) js_libs = map_to_js_libs(lib) if js_libs is not None: for l in js_libs: add_system_js_lib(l) # We don't need to resolve system libraries to absolute paths here, we can just # let wasm-ld handle that. However, we do want to map to the correct variant. # For example we map `-lc` to `-lc-mt` if we are building with threading support. if 'lib' + lib in system_libs_map: lib = system_libs_map['lib' + lib].get_link_flag() new_flags.append(lib) continue if js_libs is not None: continue if lib.endswith('.js'): name = 'lib' + lib path = find_library(name, options.lib_dirs) if not path: exit_with_error(f'unable to find library {flag}') settings.JS_LIBRARIES.append(os.path.abspath(path)) continue static_lib = f'lib{lib}.a' if not settings.MAIN_MODULE and not find_library(static_lib, options.lib_dirs): # Normally we can rely on the native linker to expand `-l` args. # However, emscripten also supports fake `.so` files that are actually # just regular object files. This means we need to support `.so` files even # when statically linking. The native linker (wasm-ld) will otherwise # ignore .so files in this mode. found_dylib = False for ext in DYLIB_EXTENSIONS: name = 'lib' + lib + ext path = find_library(name, options.lib_dirs) if path and not building.is_wasm_dylib(path): found_dylib = True new_flags.append(path) break if found_dylib: continue new_flags.append(flag) return new_flags def apply_library_settings(linker_args): for arg in linker_args: if not arg.startswith('-l'): continue library_name = arg[2:] settings_map = { 'embind': {'EMBIND': 1}, 'glfw': {'USE_GLFW': 2}, 'glfw3': {'USE_GLFW': 3}, 'SDL': {'USE_SDL': 1}, 'SDL2_mixer': {'USE_SDL_MIXER': 2}, } if library_name in settings_map: for key, value in settings_map[library_name].items(): default_setting(key, value) class ScriptSource: def __init__(self): self.src = None # if set, we have a script to load with a src attribute self.inline = None # if set, we have the contents of a script to write inline in a script def un_src(self): """Use this if you want to modify the script and need it to be inline.""" if self.src is None: return quoted_src = quote(self.src) if settings.EXPORT_ES6: self.inline = f''' import("./{quoted_src}").then(exports => exports.default(Module)) ''' else: self.inline = f''' var script = document.createElement('script'); script.src = "{quoted_src}"; document.body.appendChild(script); ''' self.src = None def replacement(self): """Return the script tag to replace the {{{ SCRIPT }}} tag in the target.""" assert (self.src or self.inline) and not (self.src and self.inline) if self.src: src = quote(self.src) filename = f'./{src}' if settings.EXPORT_ES6: return f''' ''' else: if settings.MODULARIZE: return f'' else: return f'' else: return f'' def filter_out_fake_dynamic_libs(options, inputs): """Filter out "fake" dynamic libraries that are really just intermediate object files.""" def is_fake_dylib(input_file): if get_file_suffix(input_file) in DYLIB_EXTENSIONS and os.path.exists(input_file) and not building.is_wasm_dylib(input_file): if not options.ignore_dynamic_linking: diagnostics.warning('emcc', 'ignoring dynamic library %s when generating an object file, this will need to be included explicitly in the final link', os.path.basename(input_file)) return True else: return False return [f for f in inputs if not is_fake_dylib(f)] def filter_out_duplicate_fake_dynamic_libs(inputs): """Filter out duplicate "fake" shared libraries (intermediate object files). See test_core.py:test_redundant_link """ seen = set() def check(input_file): if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file): abspath = os.path.abspath(input_file) if abspath in seen: return False seen.add(abspath) return True return [f for f in inputs if check(f)] def process_dynamic_libs(dylibs, lib_dirs): extras = [] seen = set() to_process = dylibs.copy() while to_process: dylib = to_process.pop() dylink = webassembly.parse_dylink_section(dylib) for needed in dylink.needed: if needed in seen: continue path = find_library(needed, lib_dirs) if path: extras.append(path) seen.add(needed) else: exit_with_error(f'{os.path.normpath(dylib)}: shared library dependency not found in library path: `{needed}`. (library path: {lib_dirs}') to_process.append(path) dylibs += extras for dylib in dylibs: exports = webassembly.get_exports(dylib) exports = {e.name for e in exports} # EM_JS function are exports with a special prefix. We need to strip # this prefix to get the actual symbol name. For the main module, this # is handled by extract_metadata.py. exports = [e.removeprefix('__em_js__') for e in exports] settings.SIDE_MODULE_EXPORTS.extend(sorted(exports)) imports = webassembly.get_imports(dylib) imports = [i.field for i in imports if i.kind in {webassembly.ExternType.FUNC, webassembly.ExternType.GLOBAL, webassembly.ExternType.TAG}] # For now we ignore `invoke_` functions imported by side modules and rely # on the dynamic linker to create them on the fly. # TODO(sbc): Integrate with metadata.invoke_funcs that comes from the # main module to avoid creating new invoke functions at runtime. imports = set(imports) imports = {i for i in imports if not i.startswith('invoke_')} weak_imports = webassembly.get_weak_imports(dylib) strong_imports = sorted(imports.difference(weak_imports)) logger.debug('Adding symbols requirements from `%s`: %s', dylib, imports) mangled_imports = [shared.asmjs_mangle(e) for e in sorted(imports)] mangled_strong_imports = [shared.asmjs_mangle(e) for e in strong_imports] for sym in weak_imports: mangled = shared.asmjs_mangle(sym) if mangled not in settings.SIDE_MODULE_IMPORTS and mangled not in building.user_requested_exports: settings.WEAK_IMPORTS.append(sym) settings.SIDE_MODULE_IMPORTS.extend(mangled_imports) settings.EXPORT_IF_DEFINED.extend(sorted(imports)) settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(sorted(imports)) building.user_requested_exports.update(mangled_strong_imports) def unmangle_symbols_from_cmdline(symbols): def unmangle(x): return x.replace('.', ' ').replace('#', '&').replace('?', ',') if type(symbols) is list: return [unmangle(x) for x in symbols] return unmangle(symbols) def get_secondary_target(target, ext): # Depending on the output format emscripten creates zero or more secondary # output files (e.g. the .wasm file when creating JS output, or the # .js and the .wasm file when creating html output. # This function names the secondary output files, while ensuring they # never collide with the primary one. base = unsuffixed(target) if get_file_suffix(target) == ext: base += '_' return base + ext def dedup_list(lst): # Since we require python 3.6, that ordering of dictionaries is guaranteed # to be insertion order so we can use 'dict' here but not 'set'. return list(dict.fromkeys(lst)) def check_output_file(f): if os.path.isdir(f): exit_with_error(f'cannot write output file `{f}`: Is a directory') def move_file(src, dst): logging.debug('move: %s -> %s', src, dst) check_output_file(dst) src = os.path.abspath(src) dst = os.path.abspath(dst) if src == dst: return if dst == os.devnull: return shutil.move(src, dst) def binary_encode(filename): """Encode the given binary byte array as a compact UTF-8 string. Each byte value is encoded as UTF-8, except for specific byte values that are escaped as two bytes. This kind of encoding results in a string that will compress well by both gzip and brotli, unlike base64 encoding binary data would do. """ data = utils.read_binary(filename) # Decide whether to enclose the generated binary data in single-quotes '' or # double-quotes "" by looking at which character ends up requiring fewer # escapes of that string character. num_single_quotes = data.count(ord("'")) num_double_quotes = data.count(ord('"')) quote_char = ord("'") if num_single_quotes < num_double_quotes else ord('"') out = bytearray(len(data) * 2 + 2) # Size output buffer conservatively out[0] = quote_char # Emit string start quote i = 1 for d in data: if d == quote_char: buf = [ord('\\'), d] # Escape the string quote character with a backslash since we are writing the binary data inside a string. elif d == ord('\r'): buf = [ord('\\'), ord('r')] # Escape carriage return 0x0D as \r -> 2 bytes elif d == ord('\n'): buf = [ord('\\'), ord('n')] # Escape newline 0x0A as \n -> 2 bytes elif d == ord('\\'): buf = [ord('\\'), ord('\\')] # Escape backslash \ as \\ -> 2 bytes else: buf = chr(d).encode('utf-8') # Otherwise write the original value encoded in UTF-8 (1 or 2 bytes). for b in buf: # Write the bytes to output buffer out[i] = b i += 1 out[i] = quote_char # Emit string end quote i += 1 return out[0:i].decode('utf-8') # Crop output buffer to the actual used size # Returns the subresource location for run-time access def get_subresource_location(path, mimetype='application/octet-stream'): if settings.SINGLE_FILE: if settings.SINGLE_FILE_BINARY_ENCODE: return binary_encode(path) return f'"data:{mimetype};base64,{base64_encode(path)}"' else: return f'"{os.path.basename(path)}"' def get_subresource_location_js(path): return get_subresource_location(path, 'text/javascript') @ToolchainProfiler.profile() def package_files(options, target): rtn = [] logger.debug('setting up files') file_args = ['--from-emcc'] if options.preload_files: file_args.append('--preload') file_args += options.preload_files if options.embed_files: file_args.append('--embed') file_args += options.embed_files if options.exclude_files: file_args.append('--exclude') file_args += options.exclude_files if options.use_preload_cache: file_args.append('--use-preload-cache') if settings.LZ4: file_args.append('--lz4') if options.use_preload_plugins: file_args.append('--use-preload-plugins') if not settings.ENVIRONMENT_MAY_BE_NODE: file_args.append('--no-node') if options.embed_files: if settings.MEMORY64: file_args += ['--wasm64'] object_file = in_temp('embedded_files.o') file_args += ['--obj-output=' + object_file] rtn.append(object_file) cmd = building.get_command_with_possible_response_file( [shared.FILE_PACKAGER, utils.replace_suffix(target, '.data')] + file_args) if options.preload_files: # Preloading files uses --pre-js code that runs before the module is loaded. file_code = shared.check_call(cmd, stdout=PIPE).stdout js_manipulation.add_files_pre_js(settings.PRE_JS_FILES, file_code) else: # Otherwise, we are embedding files, which does not require --pre-js code, # and instead relies on a static constructor to populate the filesystem. shared.check_call(cmd) return rtn @ToolchainProfiler.profile_block('calculate linker inputs') def phase_calculate_linker_inputs(options, linker_args): using_lld = not (options.oformat == OFormat.OBJECT and settings.LTO) linker_args = filter_link_flags(linker_args, using_lld) # If we are linking to an intermediate object then ignore other # "fake" dynamic libraries, since otherwise we will end up with # multiple copies in the final executable. if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking: linker_args = filter_out_fake_dynamic_libs(options, linker_args) else: linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args) if settings.MAIN_MODULE: process_dynamic_libs(options.dylibs, options.lib_dirs) return linker_args def calc_extra_ldflags(options): extra_args = [] system_libpath = str(cache.get_lib_dir(absolute=True)) system_js_path = utils.path_from_root('src', 'lib') options.lib_dirs.append(system_libpath) options.lib_dirs.append(system_js_path) extra_args.append('-L' + system_libpath) extra_args.append('-L' + system_js_path) if settings.FETCH: extra_args.append('-lfetch') if settings.STB_IMAGE: extra_args.append('-lstb_image') if settings.WASMFS and settings.NODERAWFS: # wasmfs will be included normally in system_libs.py, but we must include # noderawfs in a forced manner so that it is always linked in (the hook it # implements can remain unimplemented, so it won't be linked in # automatically) # TODO: find a better way to do this extra_args.append('--whole-archive') extra_args.append('-lwasmfs_noderawfs') extra_args.append('--no-whole-archive') return extra_args def run_post_link(wasm_input, options, linker_args): settings.limit_settings(None) target, wasm_target = phase_linker_setup(options, linker_args) process_libraries(options, linker_args) phase_post_link(options, wasm_input, wasm_target, target, {}) def run(options, linker_args): # We have now passed the compile phase, allow reading/writing of all settings. settings.limit_settings(None) if settings.RUNTIME_LINKED_LIBS: linker_args += settings.RUNTIME_LINKED_LIBS if not linker_args: exit_with_error('no input files') if options.output_file and options.output_file.startswith('-'): exit_with_error(f'invalid output filename: `{options.output_file}`') target, wasm_target = phase_linker_setup(options, linker_args) linker_args = process_libraries(options, linker_args) # Link object files using wasm-ld or llvm-link (for bitcode linking) linker_args = phase_calculate_linker_inputs(options, linker_args) # Embed and preload files if len(options.preload_files) or len(options.embed_files): linker_args += package_files(options, target) if options.oformat == OFormat.OBJECT: logger.debug(f'link_to_object: {linker_args} -> {target}') building.link_to_object(linker_args, target) logger.debug('stopping after linking to object file') return 0 system_libs = phase_calculate_system_libraries(options) # Only add system libraries that have not already been specified. # This avoids issues where the user explicitly includes, for example, `-lGL`. # This is not normally a problem except in the case of -sMAIN_MODULE=1 where # the duplicate library would result in duplicate symbols. for s in system_libs: if s.startswith('-l') and s in linker_args: continue linker_args.append(s) js_syms = {} if (not settings.SIDE_MODULE or settings.ASYNCIFY) and not shared.SKIP_SUBPROCS: js_info = get_js_sym_info() if not settings.SIDE_MODULE: js_syms = js_info['deps'] if settings.LINKABLE: for native_deps in js_syms.values(): settings.REQUIRED_EXPORTS += native_deps else: def add_js_deps(sym): if sym in js_syms: native_deps = js_syms[sym] if native_deps: settings.REQUIRED_EXPORTS += native_deps for sym in settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE: add_js_deps(sym) for sym in js_info['extraLibraryFuncs']: add_js_deps(sym) for sym in settings.EXPORTED_RUNTIME_METHODS: add_js_deps(shared.demangle_c_symbol_name(sym)) for sym in settings.EXPORTED_FUNCTIONS: add_js_deps(shared.demangle_c_symbol_name(sym)) if settings.ASYNCIFY: settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:] settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']] base_metadata = phase_link(linker_args, wasm_target, js_syms) # Special handling for when the user passed '-Wl,--version'. In this case the linker # does not create the output file, but just prints its version and exits with 0. if '--version' in linker_args: return 0 # TODO(sbc): In theory we should really run the whole pipeline even if the output is # /dev/null, but that will take some refactoring if target == os.devnull: return 0 # Perform post-link steps (unless we are running bare mode) if options.oformat != OFormat.BARE: phase_post_link(options, wasm_target, wasm_target, target, js_syms, base_metadata) return 0