| #!/usr/bin/env python2 |
| # 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. |
| |
| """emcc - compiler helper script |
| ============================= |
| |
| emcc is a drop-in replacement for a compiler like gcc or clang. |
| |
| See emcc --help for details. |
| |
| emcc can be influenced by a few environment variables: |
| |
| EMCC_DEBUG - "1" will log out useful information during compilation, as well as |
| save each compiler step as an emcc-* file in the temp dir |
| (by default /tmp/emscripten_temp). "2" will save additional emcc-* |
| steps, that would normally not be separately produced (so this |
| slows down compilation). |
| |
| EMMAKEN_NO_SDK - Will tell emcc *not* to use the emscripten headers. Instead |
| your system headers will be used. |
| |
| EMMAKEN_COMPILER - The compiler to be used, if you don't want the default clang. |
| """ |
| |
| from __future__ import print_function |
| |
| import json |
| import logging |
| import os |
| import re |
| import shlex |
| import shutil |
| import stat |
| import sys |
| import time |
| import base64 |
| from subprocess import PIPE |
| |
| import emscripten |
| from tools import shared, system_libs, client_mods, js_optimizer, jsrun, colored_logger |
| from tools.shared import unsuffixed, unsuffixed_basename, WINDOWS, safe_copy, safe_move, run_process, asbytes, read_and_preprocess, exit_with_error, DEBUG |
| from tools.response_file import substitute_response_files |
| from tools.minimal_runtime_shell import generate_minimal_runtime_html |
| import tools.line_endings |
| from tools.toolchain_profiler import ToolchainProfiler |
| if __name__ == '__main__': |
| ToolchainProfiler.record_process_start() |
| |
| try: |
| from urllib.parse import quote |
| except ImportError: |
| # Python 2 compatibility |
| from urllib import quote |
| |
| logger = logging.getLogger('emcc') |
| |
| # endings = dot + a suffix, safe to test by filename.endswith(endings) |
| C_ENDINGS = ('.c', '.C', '.i') |
| CXX_ENDINGS = ('.cpp', '.cxx', '.cc', '.c++', '.CPP', '.CXX', '.CC', '.C++', '.ii') |
| OBJC_ENDINGS = ('.m', '.mi') |
| OBJCXX_ENDINGS = ('.mm', '.mii') |
| SPECIAL_ENDINGLESS_FILENAMES = ('/dev/null' if not WINDOWS else 'NUL',) |
| |
| SOURCE_ENDINGS = C_ENDINGS + CXX_ENDINGS + OBJC_ENDINGS + OBJCXX_ENDINGS + SPECIAL_ENDINGLESS_FILENAMES |
| C_ENDINGS = C_ENDINGS + SPECIAL_ENDINGLESS_FILENAMES # consider the special endingless filenames like /dev/null to be C |
| |
| JS_CONTAINING_ENDINGS = ('.js', '.mjs', '.html') |
| OBJECT_FILE_ENDINGS = ('.bc', '.o', '.obj', '.lo') |
| DYNAMICLIB_ENDINGS = ('.dylib', '.so') # Windows .dll suffix is not included in this list, since those are never linked to directly on the command line. |
| STATICLIB_ENDINGS = ('.a',) |
| ASSEMBLY_ENDINGS = ('.ll',) |
| HEADER_ENDINGS = ('.h', '.hxx', '.hpp', '.hh', '.H', '.HXX', '.HPP', '.HH') |
| WASM_ENDINGS = ('.wasm',) |
| |
| SUPPORTED_LINKER_FLAGS = ( |
| '--start-group', '--end-group', |
| '-(', '-)', |
| '--whole-archive', '--no-whole-archive', |
| '-whole-archive', '-no-whole-archive' |
| ) |
| |
| SUPPORTED_LLD_LINKER_FLAG_PREFIXES = ( |
| '-l', |
| '-L', |
| '--trace-symbol', |
| '-debug', |
| '--export') |
| |
| SUPPORTED_LLD_LINKER_FLAGS = ( |
| '--fatal-warnings', |
| '--no-check-features', |
| '--trace', |
| '--no-threads', |
| '-mllvm' |
| ) |
| |
| |
| LIB_PREFIXES = ('', 'lib') |
| |
| DEFERRED_RESPONSE_FILES = ('EMTERPRETIFY_BLACKLIST', 'EMTERPRETIFY_WHITELIST', 'EMTERPRETIFY_SYNCLIST') |
| |
| # Mapping of emcc opt levels to llvm opt levels. We use llvm opt level 3 in emcc |
| # opt levels 2 and 3 (emcc 3 is unsafe opts, so unsuitable for the only level to |
| # get llvm opt level 3, and speed-wise emcc level 2 is already the slowest/most |
| # optimizing level) |
| LLVM_OPT_LEVEL = { |
| 0: ['-O0'], |
| 1: ['-O1'], |
| 2: ['-O3'], |
| 3: ['-O3'], |
| } |
| |
| # Do not compile .ll files into .bc, just compile them with emscripten directly |
| # Not recommended, this is mainly for the test runner, or if you have some other |
| # specific need. |
| # One major limitation with this mode is that libc and libc++ cannot be |
| # added in. Also, LLVM optimizations will not be done, nor dead code elimination |
| LEAVE_INPUTS_RAW = int(os.environ.get('EMCC_LEAVE_INPUTS_RAW', '0')) |
| |
| # If emcc is running with LEAVE_INPUTS_RAW and then launches an emcc to build |
| # something like the struct info, then we don't want LEAVE_INPUTS_RAW to be |
| # active in that emcc subprocess. |
| if LEAVE_INPUTS_RAW: |
| del os.environ['EMCC_LEAVE_INPUTS_RAW'] |
| |
| # 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 |
| AUTODEBUG = os.environ.get('EMCC_AUTODEBUG') |
| |
| # Target options |
| final = None |
| |
| 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', |
| } |
| |
| |
| # 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 isinstance(final, list): |
| logger.debug('(not saving intermediate %s because deferring linking)' % name) |
| return |
| shared.Building.save_intermediate(final, name + '.' + suffix) |
| |
| |
| def save_intermediate_with_wasm(name, wasm_binary): |
| if not DEBUG: |
| return |
| save_intermediate(name) # save the js |
| shared.Building.save_intermediate(wasm_binary, name + '.wasm') |
| |
| |
| class TimeLogger(object): |
| last = time.time() |
| |
| @staticmethod |
| def update(): |
| TimeLogger.last = time.time() |
| |
| |
| def log_time(name): |
| """Log out times for emcc stages""" |
| if DEBUG: |
| now = time.time() |
| logger.debug('emcc step "%s" took %.2f seconds', name, now - TimeLogger.last) |
| TimeLogger.update() |
| |
| |
| def base64_encode(b): |
| b64 = base64.b64encode(b) |
| if type(b64) == bytes: |
| return b64.decode('ascii') |
| else: |
| return b64 |
| |
| |
| class EmccOptions(object): |
| def __init__(self): |
| self.opt_level = 0 |
| self.debug_level = 0 |
| self.shrink_level = 0 |
| self.requested_debug = '' |
| self.profiling = False |
| self.profiling_funcs = False |
| self.tracing = False |
| self.emit_symbol_map = False |
| self.js_opts = None |
| self.force_js_opts = False |
| self.llvm_opts = None |
| self.llvm_lto = None |
| self.default_cxx_std = '-std=c++03' # Enforce a consistent C++ standard when compiling .cpp files, if user does not specify one on the cmdline. |
| self.use_closure_compiler = None |
| self.closure_args = [] |
| self.js_transform = None |
| self.pre_js = '' # before all js |
| self.post_js = '' # after all js |
| self.preload_files = [] |
| self.embed_files = [] |
| self.exclude_files = [] |
| self.ignore_dynamic_linking = False |
| self.shell_path = shared.path_from_root('src', 'shell.html') |
| self.source_map_base = '' |
| self.emrun = False |
| self.cpu_profiler = False |
| self.thread_profiler = False |
| self.memory_profiler = False |
| self.save_bc = False |
| self.memory_init_file = None |
| self.use_preload_cache = False |
| self.no_heap_copy = False |
| self.use_preload_plugins = False |
| self.proxy_to_worker = False |
| self.default_object_extension = '.o' |
| self.valid_abspaths = [] |
| self.separate_asm = False |
| self.cfi = False |
| # Specifies the line ending format to use for all generated text files. |
| # Defaults to using the native EOL on each platform (\r\n on Windows, \n on |
| # Linux & MacOS) |
| self.output_eol = os.linesep |
| self.binaryen_passes = [] |
| # Whether we will expand the full path of any input files to remove any |
| # symlinks. |
| self.expand_symlinks = True |
| |
| |
| def use_source_map(options): |
| return options.debug_level >= 4 |
| |
| |
| def will_metadce(options): |
| return options.opt_level >= 3 or options.shrink_level >= 1 |
| |
| |
| class JSOptimizer(object): |
| def __init__(self, target, options, js_transform_tempfiles, in_temp): |
| self.queue = [] |
| self.extra_info = {} |
| self.queue_history = [] |
| self.blacklist = os.environ.get('EMCC_JSOPT_BLACKLIST', '').split(',') |
| self.minify_whitespace = False |
| self.cleanup_shell = False |
| |
| self.target = target |
| self.opt_level = options.opt_level |
| self.debug_level = options.debug_level |
| self.emit_symbol_map = options.emit_symbol_map |
| self.profiling_funcs = options.profiling_funcs |
| self.use_closure_compiler = options.use_closure_compiler |
| self.closure_args = options.closure_args |
| |
| self.js_transform_tempfiles = js_transform_tempfiles |
| self.in_temp = in_temp |
| |
| def flush(self, title='js_opts'): |
| self.queue = [p for p in self.queue if p not in self.blacklist] |
| |
| assert not shared.Settings.WASM_BACKEND, 'JSOptimizer should not run with pure wasm output' |
| |
| if self.extra_info is not None and len(self.extra_info) == 0: |
| self.extra_info = None |
| |
| if len(self.queue) and not(not shared.Settings.ASM_JS and len(self.queue) == 1 and self.queue[0] == 'last'): |
| passes = self.queue[:] |
| |
| if DEBUG != 2 or len(passes) < 2: |
| # by assumption, our input is JS, and our output is JS. If a pass is going to run in the native optimizer in C++, then we |
| # must give it JSON and receive from it JSON |
| chunks = [] |
| curr = [] |
| for p in passes: |
| if len(curr) == 0: |
| curr.append(p) |
| else: |
| native = js_optimizer.use_native(p, source_map=use_source_map(self)) |
| last_native = js_optimizer.use_native(curr[-1], source_map=use_source_map(self)) |
| if native == last_native: |
| curr.append(p) |
| else: |
| curr.append('emitJSON') |
| chunks.append(curr) |
| curr = ['receiveJSON', p] |
| if len(curr): |
| chunks.append(curr) |
| if len(chunks) == 1: |
| self.run_passes(chunks[0], title, just_split=False, just_concat=False) |
| else: |
| for i, chunk in enumerate(chunks): |
| self.run_passes(chunk, 'js_opts_' + str(i), |
| just_split='receiveJSON' in chunk, |
| just_concat='emitJSON' in chunk) |
| else: |
| # DEBUG 2, run each pass separately |
| extra_info = self.extra_info |
| for p in passes: |
| self.queue = [p] |
| self.flush(p) |
| self.extra_info = extra_info # flush wipes it |
| log_time('part of js opts') |
| self.queue_history += self.queue |
| self.queue = [] |
| self.extra_info = {} |
| |
| def run_passes(self, passes, title, just_split, just_concat): |
| global final |
| passes = ['asm'] + passes |
| if shared.Settings.PRECISE_F32: |
| passes = ['asmPreciseF32'] + passes |
| if (self.emit_symbol_map or shared.Settings.CYBERDWARF) and 'minifyNames' in passes: |
| passes += ['symbolMap=' + shared.replace_or_append_suffix(self.target, '.symbols')] |
| if self.profiling_funcs and 'minifyNames' in passes: |
| passes += ['profilingFuncs'] |
| if self.minify_whitespace and 'last' in passes: |
| passes += ['minifyWhitespace'] |
| if self.cleanup_shell and 'last' in passes: |
| passes += ['cleanup'] |
| logger.debug('applying js optimization passes: %s', ' '.join(passes)) |
| final = shared.Building.js_optimizer(final, passes, use_source_map(self), |
| self.extra_info, just_split=just_split, |
| just_concat=just_concat, |
| output_filename=self.in_temp(os.path.basename(final) + '.jsopted.js'), |
| extra_closure_args=self.closure_args) |
| self.js_transform_tempfiles.append(final) |
| save_intermediate(title, suffix='js' if 'emitJSON' not in passes else 'json') |
| |
| def do_minify(self): |
| """minifies the code. |
| |
| this is also when we do certain optimizations that must be done right before or after minification |
| """ |
| if self.opt_level >= 2: |
| if self.debug_level < 2 and not self.use_closure_compiler == 2: |
| self.queue += ['minifyNames'] |
| if self.debug_level == 0: |
| self.minify_whitespace = True |
| |
| if self.use_closure_compiler == 1: |
| self.queue += ['closure'] |
| elif self.debug_level <= 2 and shared.Settings.FINALIZE_ASM_JS and not self.use_closure_compiler: |
| self.cleanup_shell = True |
| |
| |
| def embed_memfile(options): |
| return (shared.Settings.SINGLE_FILE or |
| (shared.Settings.MEM_INIT_METHOD == 0 and |
| (not shared.Settings.MAIN_MODULE and |
| not shared.Settings.SIDE_MODULE and |
| not use_source_map(options)))) |
| |
| |
| def apply_settings(changes): |
| """Take a list of settings in form `NAME=VALUE` and apply them to the global |
| Settings object. |
| """ |
| |
| def standardize_setting_change(key, value): |
| # Handle aliases in settings flags. These are settings whose name |
| # has changed. |
| settings_aliases = { |
| 'BINARYEN': 'WASM', |
| 'BINARYEN_MEM_MAX': 'WASM_MEM_MAX', |
| # TODO: change most (all?) other BINARYEN* names to WASM* |
| } |
| key = settings_aliases.get(key, key) |
| # boolean NO_X settings are aliases for X |
| # (note that *non*-boolean setting values have special meanings, |
| # and we can't just flip them, so leave them as-is to be |
| # handled in a special way later) |
| if key.startswith('NO_') and value in ('0', '1'): |
| key = key[3:] |
| value = str(1 - int(value)) |
| return key, value |
| |
| for change in changes: |
| key, value = change.split('=', 1) |
| key, value = standardize_setting_change(key, value) |
| |
| if key in shared.Settings.internal_settings: |
| exit_with_error('%s is an internal setting and cannot be set from command line', key) |
| |
| # In those settings fields that represent amount of memory, translate suffixes to multiples of 1024. |
| if key in ('TOTAL_STACK', 'TOTAL_MEMORY', 'MEMORY_GROWTH_LINEAR_STEP', 'MEMORY_GROWTH_GEOMETRIC_STEP', |
| 'GL_MAX_TEMP_BUFFER_SIZE', 'WASM_MEM_MAX', 'DEFAULT_PTHREAD_STACK_SIZE'): |
| value = str(shared.expand_byte_size_suffixes(value)) |
| |
| if value[0] == '@': |
| if key not in DEFERRED_RESPONSE_FILES: |
| value = open(value[1:]).read() |
| else: |
| value = value.replace('\\', '\\\\') |
| try: |
| value = parse_value(value) |
| except Exception as e: |
| exit_with_error('a problem occured in evaluating the content after a "-s", specifically "%s": %s', change, str(e)) |
| setattr(shared.Settings, key, value) |
| |
| if shared.Settings.WASM_BACKEND and key == 'BINARYEN_TRAP_MODE': |
| exit_with_error('BINARYEN_TRAP_MODE is not supported by the LLVM wasm backend') |
| |
| if key == 'EXPORTED_FUNCTIONS': |
| # used for warnings in emscripten.py |
| shared.Settings.USER_EXPORTED_FUNCTIONS = shared.Settings.EXPORTED_FUNCTIONS[:] |
| |
| |
| def find_output_arg(args): |
| """Find and remove any -o arguments. The final one takes precedence. |
| Return the final -o target along with the remaining (non-o) arguments. |
| """ |
| outargs = [] |
| specified_target = None |
| use_next = False |
| for arg in args: |
| if use_next: |
| specified_target = arg |
| use_next = False |
| continue |
| if arg == '-o': |
| use_next = True |
| elif arg.startswith('-o'): |
| specified_target = arg[2:] |
| else: |
| outargs.append(arg) |
| return specified_target, outargs |
| |
| |
| def do_emscripten(infile, memfile): |
| # Run Emscripten |
| outfile = infile + '.o.js' |
| with ToolchainProfiler.profile_block('emscripten.py'): |
| emscripten.run(infile, outfile, memfile) |
| |
| # Detect compilation crashes and errors |
| assert os.path.exists(outfile), 'Emscripten failed to generate .js' |
| |
| return outfile |
| |
| |
| def ensure_archive_index(archive_file): |
| # Fastcomp linking works without archive indexes. |
| if not shared.Settings.WASM_BACKEND or not shared.Settings.AUTO_ARCHIVE_INDEXES: |
| return |
| # Ignore stderr since llvm-nm prints "no symbols" to stderr for each object that has no symbols |
| stdout = run_process([shared.LLVM_NM, '--print-armap', archive_file], stdout=PIPE, stderr=PIPE).stdout |
| stdout = stdout.strip() |
| # Ignore empty archives |
| if not stdout: |
| return |
| if stdout.startswith('Archive map\n') or stdout.startswith('Archive index\n'): |
| return |
| shared.warning('%s: archive is missing an index; Use emar when creating libraries to ensure an index is created', archive_file) |
| shared.warning('%s: adding index', archive_file) |
| run_process([shared.LLVM_RANLIB, archive_file]) |
| |
| |
| # |
| # Main run() function |
| # |
| def run(args): |
| global final |
| target = None |
| |
| # Additional compiler flags that we treat as if they were passed to us on the |
| # commandline |
| EMCC_CFLAGS = os.environ.get('EMCC_CFLAGS') |
| if DEBUG: |
| cmd = ' '.join(args) |
| if EMCC_CFLAGS: |
| cmd += ' + ' + EMCC_CFLAGS |
| logger.warning('invocation: ' + cmd + ' (in ' + os.getcwd() + ')') |
| if EMCC_CFLAGS: |
| args.extend(shlex.split(EMCC_CFLAGS)) |
| |
| # Strip args[0] (program name) |
| args = args[1:] |
| |
| if DEBUG and LEAVE_INPUTS_RAW: |
| logger.warning('leaving inputs raw') |
| |
| if '--emscripten-cxx' in args: |
| run_via_emxx = True |
| args = [x for x in args if x != '--emscripten-cxx'] |
| else: |
| run_via_emxx = False |
| |
| misc_temp_files = shared.configuration.get_temp_files() |
| |
| # Handle some global flags |
| |
| # read response files very early on |
| try: |
| args = substitute_response_files(args) |
| except IOError as e: |
| exit_with_error(e) |
| |
| if '--help' in args: |
| # Documentation for emcc and its options must be updated in: |
| # site/source/docs/tools_reference/emcc.rst |
| # A prebuilt local version of the documentation is available at: |
| # site/build/text/docs/tools_reference/emcc.txt |
| # (it is read from there and printed out when --help is invoked) |
| # You can also build docs locally as HTML or other formats in site/ |
| # An online HTML version (which may be of a different version of Emscripten) |
| # is up at http://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html |
| |
| with open(shared.path_from_root('site', 'build', 'text', 'docs', 'tools_reference', 'emcc.txt'), 'r') as f: |
| print(f.read()) |
| |
| print(''' |
| ------------------------------------------------------------------ |
| |
| emcc: supported targets: llvm bitcode, javascript, NOT elf |
| (autoconf likes to see elf above to enable shared object support) |
| ''') |
| return 0 |
| |
| if '--version' in args: |
| try: |
| revision = run_process(['git', 'show'], stdout=PIPE, stderr=PIPE, cwd=shared.path_from_root()).stdout.splitlines()[0] |
| except Exception: |
| revision = '(unknown revision)' |
| print('''emcc (Emscripten gcc/clang-like replacement) %s (%s) |
| Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt) |
| This is free and open source software under the MIT license. |
| There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| ''' % (shared.EMSCRIPTEN_VERSION, revision)) |
| return 0 |
| |
| if len(args) == 1 and args[0] == '-v': # -v with no inputs |
| # autoconf likes to see 'GNU' in the output to enable shared object support |
| print('emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) %s' % shared.EMSCRIPTEN_VERSION, file=sys.stderr) |
| code = run_process([shared.CLANG_CC, '-v'], check=False).returncode |
| shared.check_sanity(force=True) |
| return code |
| |
| shared.check_sanity(force=DEBUG) |
| |
| # This check comes after check_sanity because test_sanity expects this. |
| if not args: |
| logger.warning('no input files') |
| return 1 |
| |
| if '-dumpmachine' in args: |
| print(shared.get_llvm_target()) |
| return 0 |
| |
| if '-dumpversion' in args: # gcc's doc states "Print the compiler version [...] and don't do anything else." |
| print(shared.EMSCRIPTEN_VERSION) |
| return 0 |
| |
| if '--cflags' in args: |
| # fake running the command, to see the full args we pass to clang |
| debug_env = os.environ.copy() |
| args = [x for x in args if x != '--cflags'] |
| with misc_temp_files.get_file(suffix='.o') as temp_target: |
| input_file = 'hello_world.c' |
| cmd = [shared.PYTHON, sys.argv[0], shared.path_from_root('tests', input_file), '-v', '-c', '-o', temp_target] + args |
| proc = run_process(cmd, stderr=PIPE, env=debug_env, check=False) |
| if proc.returncode != 0: |
| print(proc.stderr) |
| exit_with_error('error getting cflags') |
| lines = [x for x in proc.stderr.splitlines() if shared.CLANG_CC in x and input_file in x] |
| parts = shlex.split(lines[0].replace('\\', '\\\\')) |
| parts = [x for x in parts if x not in ['-c', '-o', '-v', '-emit-llvm'] and input_file not in x and temp_target not in x] |
| print(' '.join(shared.Building.doublequote_spaces(parts[1:]))) |
| return 0 |
| |
| # Default to using C++ even when run as `emcc`. |
| # This means that emcc will act as a C++ linker when no source files are |
| # specified. However, when a C source is specified we do default to C. |
| # This differs to clang and gcc where the default is always C unless run as |
| # clang++/g++. |
| use_cxx = True |
| |
| def get_language_mode(args): |
| return_next = False |
| for item in args: |
| if return_next: |
| return item |
| if item == '-x': |
| return_next = True |
| continue |
| if item.startswith('-x'): |
| return item[2:] |
| return None |
| |
| def has_c_source(args): |
| for a in args: |
| if a[0] != '-' and a.endswith(C_ENDINGS + OBJC_ENDINGS): |
| return True |
| return False |
| |
| language_mode = get_language_mode(args) |
| has_fixed_language_mode = language_mode is not None |
| if language_mode == 'c': |
| use_cxx = False |
| |
| if not has_fixed_language_mode: |
| if not run_via_emxx and has_c_source(args): |
| use_cxx = False |
| |
| def is_minus_s_for_emcc(args, i): |
| # -s OPT=VALUE or -s OPT are interpreted as emscripten flags. |
| # -s by itself is a linker option (alias for --strip-all) |
| assert args[i] == '-s' |
| if len(args) > i + 1: |
| arg = args[i + 1] |
| if arg.split('=')[0].isupper(): |
| return True |
| |
| logger.debug('treating -s as linker option and not as -s OPT=VALUE for js compilation') |
| return False |
| |
| # If this is a configure-type thing, do not compile to JavaScript, instead use clang |
| # to compile to a native binary (using our headers, so things make sense later) |
| CONFIGURE_CONFIG = (os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in args) and not os.environ.get('EMMAKEN_JUST_CONFIGURE_RECURSE') |
| CMAKE_CONFIG = 'CMakeFiles/cmTryCompileExec.dir' in ' '.join(args)# or 'CMakeCCompilerId' in ' '.join(args) |
| if CONFIGURE_CONFIG or CMAKE_CONFIG: |
| # XXX use this to debug configure stuff. ./configure's generally hide our |
| # normal output including stderr so we write to a file |
| debug_configure = 0 |
| |
| if debug_configure: |
| tempout = '/tmp/emscripten_temp/out' |
| if not os.path.exists(tempout): |
| open(tempout, 'w').write('//\n') |
| |
| src = None |
| for arg in args: |
| if arg.endswith(SOURCE_ENDINGS): |
| try: |
| src = open(arg).read() |
| if debug_configure: |
| open(tempout, 'a').write('============= ' + arg + '\n' + src + '\n=============\n\n') |
| except IOError: |
| pass |
| |
| # if CONFIGURE_CC is defined, use that. let's you use local gcc etc. if you need that |
| compiler = os.environ.get('CONFIGURE_CC') or shared.EMXX |
| if 'CXXCompiler' not in ' '.join(args) and not use_cxx: |
| compiler = shared.to_cc(compiler) |
| |
| def filter_emscripten_options(argv): |
| skip_next = False |
| for idx, arg in enumerate(argv): |
| if skip_next: |
| skip_next = False |
| continue |
| if arg != '--tracing': |
| yield arg |
| |
| if compiler in (shared.EMCC, shared.EMXX): |
| compiler = [shared.PYTHON, compiler] |
| else: |
| compiler = [compiler] |
| cmd = compiler + list(filter_emscripten_options(args)) |
| # configure tests want a more shell-like style, where we emit return codes on exit() |
| cmd += ['-s', 'NO_EXIT_RUNTIME=0'] |
| # use node.js raw filesystem access, to behave just like a native executable |
| cmd += ['-s', 'NODERAWFS=1'] |
| |
| logger.debug('just configuring: ' + ' '.join(cmd)) |
| if debug_configure: |
| open(tempout, 'a').write('emcc, just configuring: ' + ' '.join(cmd) + '\n\n') |
| |
| only_object = '-c' in cmd |
| for i in reversed(range(len(cmd) - 1)): # Last -o directive should take precedence, if multiple are specified |
| if cmd[i] == '-o': |
| if not only_object: |
| cmd[i + 1] += '.js' |
| target = cmd[i + 1] |
| break |
| if not target: |
| target = 'a.out.js' |
| os.environ['EMMAKEN_JUST_CONFIGURE_RECURSE'] = '1' |
| ret = run_process(cmd, check=False).returncode |
| os.environ['EMMAKEN_JUST_CONFIGURE_RECURSE'] = '' |
| if not os.path.exists(target): |
| # note that emcc -c will cause target to have the wrong value here; |
| # but then, we don't care about bitcode outputs anyhow, below, so |
| # skipping returning early is fine |
| return ret |
| if target.endswith('.js'): |
| shutil.copyfile(target, unsuffixed(target)) |
| target = unsuffixed(target) |
| if not target.endswith(OBJECT_FILE_ENDINGS): |
| src = open(target).read() |
| full_node = ' '.join(shared.NODE_JS) |
| if os.path.sep not in full_node: |
| full_node = '/usr/bin/' + full_node # TODO: use whereis etc. And how about non-*NIX? |
| open(target, 'w').write('#!' + full_node + '\n' + src) # add shebang |
| try: |
| os.chmod(target, stat.S_IMODE(os.stat(target).st_mode) | stat.S_IXUSR) # make executable |
| except OSError: |
| pass # can fail if e.g. writing the executable to /dev/null |
| return ret |
| |
| CXX = os.environ.get('EMMAKEN_COMPILER', shared.CLANG_CPP) |
| CC = shared.to_cc(CXX) |
| |
| EMMAKEN_CFLAGS = os.environ.get('EMMAKEN_CFLAGS') |
| if EMMAKEN_CFLAGS: |
| args += shlex.split(EMMAKEN_CFLAGS) |
| |
| # ---------------- Utilities --------------- |
| |
| def suffix(name): |
| """Return the file extension""" |
| return os.path.splitext(name)[1] |
| |
| seen_names = {} |
| |
| def uniquename(name): |
| if name not in seen_names: |
| seen_names[name] = str(len(seen_names)) |
| return unsuffixed(name) + '_' + seen_names[name] + suffix(name) |
| |
| # ---------------- End configs ------------- |
| |
| # Check if a target is specified on the command line |
| specified_target, args = find_output_arg(args) |
| |
| # specified_target is the user-specified one, target is what we will generate |
| if specified_target: |
| target = specified_target |
| # 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) |
| else: |
| target = 'a.out.js' |
| |
| shared.Settings.TARGET_BASENAME = target_basename = unsuffixed_basename(target) |
| |
| final_suffix = suffix(target) |
| |
| temp_dir = shared.get_emscripten_temp_dir() |
| |
| def in_temp(name): |
| return os.path.join(temp_dir, os.path.basename(name)) |
| |
| def get_file_suffix(filename): |
| """Parses the essential suffix of a filename, discarding Unix-style version |
| numbers in the name. For example for 'libz.so.1.2.8' returns '.so'""" |
| if filename in SPECIAL_ENDINGLESS_FILENAMES: |
| return filename |
| while filename: |
| filename, suffix = os.path.splitext(filename) |
| if not suffix[1:].isdigit(): |
| return suffix |
| return '' |
| |
| def optimizing(opts): |
| return '-O0' not in opts |
| |
| def need_llvm_debug_info(options): |
| return options.debug_level >= 3 or shared.Settings.CYBERDWARF or shared.Settings.FULL_DWARF |
| |
| with ToolchainProfiler.profile_block('parse arguments and setup'): |
| ## Parse args |
| |
| newargs = list(args) |
| |
| # Scan and strip emscripten specific cmdline warning flags. |
| # This needs to run before other cmdline flags have been parsed, so that |
| # warnings are properly printed during arg parse. |
| newargs = shared.WarningManager.capture_warnings(newargs) |
| |
| for i in range(len(newargs)): |
| if newargs[i] in ('-l', '-L', '-I'): |
| # Scan for individual -l/-L/-I arguments and concatenate the next arg on |
| # if there is no suffix |
| newargs[i] += newargs[i + 1] |
| newargs[i + 1] = '' |
| |
| options, settings_changes, newargs = parse_args(newargs) |
| |
| if use_cxx: |
| clang_compiler = CXX |
| # If user did not specify a default -std for C++ code, specify the emscripten default. |
| if options.default_cxx_std: |
| newargs += [options.default_cxx_std] |
| else: |
| # Compiling C code with .c files, don't enforce a default C++ std. |
| clang_compiler = CC |
| |
| if '-print-search-dirs' in newargs: |
| return run_process([clang_compiler, '-print-search-dirs'], check=False).returncode |
| |
| if options.emrun: |
| options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n' |
| options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n' |
| # emrun mode waits on program exit |
| shared.Settings.EXIT_RUNTIME = 1 |
| |
| if options.cpu_profiler: |
| options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n' |
| |
| if options.memory_profiler: |
| shared.Settings.MEMORYPROFILER = 1 |
| |
| if options.thread_profiler: |
| options.post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n' |
| |
| if options.js_opts is None: |
| options.js_opts = options.opt_level >= 2 |
| |
| if options.llvm_opts is None: |
| options.llvm_opts = LLVM_OPT_LEVEL[options.opt_level] |
| elif type(options.llvm_opts) == int: |
| options.llvm_opts = ['-O%d' % options.llvm_opts] |
| |
| if options.memory_init_file is None: |
| options.memory_init_file = options.opt_level >= 2 |
| |
| # TODO: support source maps with js_transform |
| if options.js_transform and use_source_map(options): |
| logger.warning('disabling source maps because a js transform is being done') |
| options.debug_level = 3 |
| |
| if DEBUG: |
| start_time = time.time() # done after parsing arguments, which might affect debug state |
| |
| for i in range(len(newargs)): |
| if newargs[i] == '-s': |
| if is_minus_s_for_emcc(newargs, i): |
| key = newargs[i + 1] |
| # If not = is specified default to 1 |
| if '=' not in key: |
| key += '=1' |
| |
| # Special handling of browser version targets. A version -1 means that the specific version |
| # is not supported at all. Replace those with INT32_MAX to make it possible to compare e.g. |
| # #if MIN_FIREFOX_VERSION < 68 |
| try: |
| if re.match(r'MIN_.*_VERSION(=.*)?', key) and int(key.split('=')[1]) < 0: |
| key = key.split('=')[0] + '=0x7FFFFFFF' |
| except Exception: |
| pass |
| |
| settings_changes.append(key) |
| newargs[i] = newargs[i + 1] = '' |
| if key == 'WASM_BACKEND=1': |
| exit_with_error('do not set -s WASM_BACKEND, instead set EMCC_WASM_BACKEND=1 in the environment') |
| newargs = [arg for arg in newargs if arg] |
| |
| settings_key_changes = set() |
| for s in settings_changes: |
| key, value = s.split('=', 1) |
| settings_key_changes.add(key) |
| |
| # Find input files |
| |
| # These three arrays are used to store arguments of different types for |
| # type-specific processing. In order to shuffle the arguments back together |
| # after processing, all of these arrays hold tuples (original_index, value). |
| # Note that the index part of the tuple can have a fractional part for input |
| # arguments that expand into multiple processed arguments, as in -Wl,-f1,-f2. |
| input_files = [] |
| libs = [] |
| link_flags = [] |
| |
| # All of the above arg lists entries contain indexes into the full argument |
| # list. In order to add extra implicit args (embind.cc, etc) below, we keep a |
| # counter for the next index that should be used. |
| next_arg_index = len(newargs) |
| |
| has_source_inputs = False |
| has_header_inputs = False |
| lib_dirs = [] |
| |
| has_dash_c = '-c' in newargs |
| has_dash_S = '-S' in newargs |
| has_dash_E = '-E' in newargs |
| link_to_object = False |
| executable_endings = JS_CONTAINING_ENDINGS + ('.wasm',) |
| compile_only = has_dash_c or has_dash_S or has_dash_E |
| |
| def add_link_flag(i, f): |
| # Filter out libraries that musl includes in libc itself, or which we |
| # otherwise provide implicitly. |
| if f in ('-lm', '-lrt', '-ldl', '-lpthread'): |
| return |
| if f.startswith('-l'): |
| libs.append((i, f[2:])) |
| if f.startswith('-L'): |
| lib_dirs.append(f[2:]) |
| |
| link_flags.append((i, f)) |
| |
| # find input files with a simple heuristic. we should really analyze |
| # based on a full understanding of gcc params, right now we just assume that |
| # what is left contains no more |-x OPT| things |
| for i in range(len(newargs)): |
| arg = newargs[i] |
| if i > 0: |
| prev = newargs[i - 1] |
| if prev in ('-MT', '-MF', '-MQ', '-D', '-U', '-o', '-x', |
| '-Xpreprocessor', '-include', '-imacros', '-idirafter', |
| '-iprefix', '-iwithprefix', '-iwithprefixbefore', |
| '-isysroot', '-imultilib', '-A', '-isystem', '-iquote', |
| '-install_name', '-compatibility_version', |
| '-current_version', '-I', '-L', '-include-pch'): |
| continue # ignore this gcc-style argument |
| |
| if options.expand_symlinks and os.path.islink(arg) and get_file_suffix(os.path.realpath(arg)) in SOURCE_ENDINGS + OBJECT_FILE_ENDINGS + DYNAMICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS: |
| arg = os.path.realpath(arg) |
| |
| if not arg.startswith('-'): |
| if not os.path.exists(arg): |
| exit_with_error('%s: No such file or directory ("%s" was expected to be an input file, based on the commandline arguments provided)', arg, arg) |
| |
| file_suffix = get_file_suffix(arg) |
| if file_suffix in SOURCE_ENDINGS + OBJECT_FILE_ENDINGS + DYNAMICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS or shared.Building.is_ar(arg): # we already removed -o <target>, so all these should be inputs |
| newargs[i] = '' |
| if file_suffix.endswith(SOURCE_ENDINGS): |
| input_files.append((i, arg)) |
| has_source_inputs = True |
| elif file_suffix.endswith(HEADER_ENDINGS): |
| input_files.append((i, arg)) |
| has_header_inputs = True |
| elif file_suffix.endswith(ASSEMBLY_ENDINGS) or shared.Building.is_bitcode(arg) or shared.Building.is_ar(arg): |
| input_files.append((i, arg)) |
| elif shared.Building.is_wasm(arg): |
| if not shared.Settings.WASM_BACKEND: |
| exit_with_error('fastcomp is not compatible with wasm object files:' + arg) |
| input_files.append((i, arg)) |
| elif file_suffix.endswith(STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS): |
| # if it's not, and it's a library, just add it to libs to find later |
| libname = unsuffixed_basename(arg) |
| for prefix in LIB_PREFIXES: |
| if not prefix: |
| continue |
| if libname.startswith(prefix): |
| libname = libname[len(prefix):] |
| break |
| libs.append((i, libname)) |
| newargs[i] = '' |
| else: |
| logger.warning(arg + ' is not a valid input file') |
| elif file_suffix.endswith(STATICLIB_ENDINGS): |
| if not shared.Building.is_ar(arg): |
| if shared.Building.is_bitcode(arg): |
| message = arg + ': File has a suffix of a static library ' + str(STATICLIB_ENDINGS) + ', but instead is an LLVM bitcode file! When linking LLVM bitcode files, use one of the suffixes ' + str(OBJECT_FILE_ENDINGS) |
| else: |
| message = arg + ': Unknown format, not a static library!' |
| exit_with_error(message) |
| else: |
| if has_fixed_language_mode: |
| newargs[i] = '' |
| input_files.append((i, arg)) |
| has_source_inputs = True |
| else: |
| exit_with_error(arg + ": Input file has an unknown suffix, don't know what to do with it!") |
| elif arg.startswith('-r'): |
| link_to_object = True |
| newargs[i] = '' |
| elif arg.startswith('-L'): |
| add_link_flag(i, arg) |
| newargs[i] = '' |
| elif arg.startswith('-l'): |
| add_link_flag(i, arg) |
| newargs[i] = '' |
| elif arg.startswith('-Wl,'): |
| # Multiple comma separated link flags can be specified. Create fake |
| # fractional indices for these: -Wl,a,b,c,d at index 4 becomes: |
| # (4, a), (4.25, b), (4.5, c), (4.75, d) |
| link_flags_to_add = arg.split(',')[1:] |
| for flag_index, flag in enumerate(link_flags_to_add): |
| add_link_flag(i + float(flag_index) / len(link_flags_to_add), flag) |
| newargs[i] = '' |
| elif arg == '-s': |
| # -s and some other compiler flags are normally passed onto the linker |
| # TODO(sbc): Pass this and other flags through when using lld |
| # link_flags.append((i, arg)) |
| newargs[i] = '' |
| elif arg == '-': |
| input_files.append((i, arg)) |
| newargs[i] = '' |
| newargs = [a for a in newargs if a] |
| |
| if has_dash_c or has_dash_S: |
| assert has_source_inputs or has_header_inputs, 'Must have source code or header inputs to use -c or -S' |
| if has_dash_c: |
| if '-emit-llvm' in newargs: |
| final_suffix = '.bc' |
| else: |
| final_suffix = options.default_object_extension |
| elif has_dash_S: |
| if '-emit-llvm' in newargs: |
| final_suffix = '.ll' |
| else: |
| final_suffix = '.s' |
| target = target_basename + final_suffix |
| |
| if len(input_files) > 1 and specified_target: |
| exit_with_error('cannot specify -o with -c/-S and multiple source files') |
| |
| if has_dash_E: |
| final_suffix = '.eout' # not bitcode, not js; but just result from preprocessing stage of the input file |
| if '-M' in newargs or '-MM' in newargs: |
| final_suffix = '.mout' # not bitcode, not js; but just dependency rule of the input file |
| |
| # target is now finalized, can finalize other _target s |
| if final_suffix == '.mjs': |
| shared.Settings.EXPORT_ES6 = 1 |
| shared.Settings.MODULARIZE = 1 |
| js_target = target |
| else: |
| js_target = unsuffixed(target) + '.js' |
| |
| asm_target = unsuffixed(js_target) + '.asm.js' # might not be used, but if it is, this is the name |
| wasm_text_target = asm_target.replace('.asm.js', '.wat') # ditto, might not be used |
| wasm_binary_target = asm_target.replace('.asm.js', '.wasm') # ditto, might not be used |
| wasm_source_map_target = shared.replace_or_append_suffix(wasm_binary_target, '.map') |
| |
| if final_suffix == '.html' and not options.separate_asm and 'PRECISE_F32=2' in settings_changes: |
| options.separate_asm = True |
| shared.warning('forcing separate asm output (--separate-asm), because -s PRECISE_F32=2 was passed.') |
| if options.separate_asm: |
| shared.Settings.SEPARATE_ASM = shared.JS.get_subresource_location(asm_target) |
| |
| # Libraries are searched before settings_changes are applied, so apply the |
| # value for STRICT from command line already now. |
| |
| def get_last_setting_change(setting): |
| return ([None] + [x for x in settings_changes if x.startswith(setting + '=')])[-1] |
| |
| strict_cmdline = get_last_setting_change('STRICT') |
| if strict_cmdline: |
| shared.Settings.STRICT = int(strict_cmdline.split('=', 1)[1]) |
| |
| if options.separate_asm and final_suffix != '.html': |
| shared.WarningManager.warn('SEPARATE_ASM') |
| |
| # Apply optimization level settings |
| shared.Settings.apply_opt_level(opt_level=options.opt_level, shrink_level=options.shrink_level, noisy=True) |
| |
| # For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also |
| # want to opt out of ERROR_ON_UNDEFINED_SYMBOLS. |
| if 'WARN_ON_UNDEFINED_SYMBOLS=0' in settings_changes: |
| shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS = 0 |
| |
| # Set ASM_JS default here so that we can override it from the command line. |
| shared.Settings.ASM_JS = 1 if options.opt_level > 0 else 2 |
| |
| # Apply -s settings in newargs here (after optimization levels, so they can override them) |
| apply_settings(settings_changes) |
| |
| shared.verify_settings() |
| |
| def filter_out_dynamic_libs(inputs): |
| # If not compiling to JS, then we are compiling to an intermediate bitcode |
| # objects or library, so ignore dynamic linking, since multiple dynamic |
| # linkings can interfere with each other |
| if get_file_suffix(target) not in JS_CONTAINING_ENDINGS or options.ignore_dynamic_linking: |
| def check(input_file): |
| if get_file_suffix(input_file) in DYNAMICLIB_ENDINGS: |
| if not options.ignore_dynamic_linking: |
| shared.warning('ignoring dynamic library %s because not compiling to JS or HTML, remember to link it when compiling to JS or HTML at the end', os.path.basename(input_file)) |
| return False |
| else: |
| return True |
| return [f for f in inputs if check(f[1])] |
| return inputs |
| |
| input_files = filter_out_dynamic_libs(input_files) |
| |
| if len(input_files) == 0: |
| exit_with_error('no input files\nnote that input files without a known suffix are ignored, make sure your input files end with one of: ' + str(SOURCE_ENDINGS + OBJECT_FILE_ENDINGS + DYNAMICLIB_ENDINGS + STATICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS)) |
| |
| # Note the exports the user requested |
| shared.Building.user_requested_exports = shared.Settings.EXPORTED_FUNCTIONS[:] |
| |
| # -s ASSERTIONS=1 implies the heaviest stack overflow check mode. Set the implication here explicitly to avoid having to |
| # do preprocessor "#if defined(ASSERTIONS) || defined(STACK_OVERFLOW_CHECK)" in .js files, which is not supported. |
| if shared.Settings.ASSERTIONS: |
| shared.Settings.STACK_OVERFLOW_CHECK = 2 |
| |
| if shared.Settings.WASM_OBJECT_FILES and not shared.Settings.WASM_BACKEND: |
| if 'WASM_OBJECT_FILES=1' in settings_changes: |
| exit_with_error('WASM_OBJECT_FILES can only be used with wasm backend') |
| shared.Settings.WASM_OBJECT_FILES = 0 |
| |
| if shared.Settings.STRICT: |
| shared.Settings.STRICT_JS = 1 |
| shared.Settings.AUTO_JS_LIBRARIES = 0 |
| shared.Settings.AUTO_ARCHIVE_INDEXES = 0 |
| |
| if AUTODEBUG: |
| shared.Settings.AUTODEBUG = 1 |
| |
| # Use settings |
| |
| if options.debug_level > 1 and options.use_closure_compiler: |
| shared.warning('disabling closure because debug info was requested') |
| options.use_closure_compiler = False |
| |
| if shared.Settings.EMTERPRETIFY_FILE and shared.Settings.SINGLE_FILE: |
| exit_with_error('cannot have both EMTERPRETIFY_FILE and SINGLE_FILE enabled at the same time') |
| |
| if shared.Settings.WASM == 2 and shared.Settings.SINGLE_FILE: |
| exit_with_error('cannot have both WASM=2 and SINGLE_FILE enabled at the same time (pick either JS to target with -s WASM=0 or Wasm to target with -s WASM=1)') |
| |
| if shared.Settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and shared.Settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION: |
| exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION are mutually exclusive!') |
| |
| if options.emrun: |
| if shared.Settings.MINIMAL_RUNTIME: |
| exit_with_error('--emrun is not compatible with -s MINIMAL_RUNTIME=1') |
| shared.Settings.EXPORTED_RUNTIME_METHODS.append('addOnExit') |
| |
| if options.use_closure_compiler: |
| shared.Settings.USE_CLOSURE_COMPILER = options.use_closure_compiler |
| # when we emit asm.js, closure 2 would break that, so warn (note that |
| # with wasm2js in the wasm backend, we don't emit asm.js anyhow) |
| if options.use_closure_compiler == 2 and shared.Settings.ASM_JS == 1 and not shared.Settings.WASM_BACKEND: |
| shared.WarningManager.warn('ALMOST_ASM', 'not all asm.js optimizations are possible with --closure 2, disabling those - your code will be run more slowly') |
| shared.Settings.ASM_JS = 2 |
| |
| if shared.Settings.MAIN_MODULE: |
| assert not shared.Settings.SIDE_MODULE |
| if shared.Settings.MAIN_MODULE == 1: |
| shared.Settings.INCLUDE_FULL_LIBRARY = 1 |
| elif shared.Settings.SIDE_MODULE: |
| assert not shared.Settings.MAIN_MODULE |
| # memory init file is not supported with asm.js side modules, must be executable synchronously (for dlopen) |
| options.memory_init_file = False |
| |
| if shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE: |
| assert shared.Settings.ASM_JS, 'module linking requires asm.js output (-s ASM_JS=1)' |
| if shared.Settings.MAIN_MODULE == 1 or shared.Settings.SIDE_MODULE == 1: |
| shared.Settings.LINKABLE = 1 |
| shared.Settings.EXPORT_ALL = 1 |
| shared.Settings.RELOCATABLE = 1 |
| assert not options.use_closure_compiler, 'cannot use closure compiler on shared modules' |
| # shared modules need memory utilities to allocate their memory |
| shared.Settings.EXPORTED_RUNTIME_METHODS += [ |
| 'allocate', |
| 'getMemory', |
| ] |
| |
| if shared.Settings.RELOCATABLE: |
| shared.Settings.ALLOW_TABLE_GROWTH = 1 |
| |
| # Reconfigure the cache now that settings have been applied. Some settings |
| # such as WASM_OBJECT_FILES and SIDE_MODULE/MAIN_MODULE effect which cache |
| # directory we use. |
| shared.reconfigure_cache() |
| |
| if not compile_only: |
| ldflags = shared.emsdk_ldflags(newargs) |
| for f in ldflags: |
| newargs.append(f) |
| add_link_flag(len(newargs), f) |
| |
| if not shared.Settings.STRICT: |
| # The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code |
| # in strict mode. Code should use the define __EMSCRIPTEN__ instead. |
| newargs += ['-DEMSCRIPTEN'] |
| |
| newargs = shared.COMPILER_OPTS + shared.get_cflags(newargs) + newargs |
| if not link_to_object and not compile_only and final_suffix not in executable_endings: |
| # TODO(sbc): Remove this emscripten-specific special case. |
| shared.warning('Assuming object file output in the absence of `-c`, based on output filename. Please add with `-c` or `-r` to avoid this warning') |
| link_to_object = True |
| |
| using_lld = shared.Settings.WASM_BACKEND and not (link_to_object and not shared.Settings.WASM_OBJECT_FILES) |
| |
| def is_supported_link_flag(f): |
| if f in SUPPORTED_LINKER_FLAGS: |
| return True |
| if using_lld: |
| # Add flags here to allow -Wl, options to be passed all the way through |
| # to the linker. |
| if any(f.startswith(prefix) for prefix in SUPPORTED_LLD_LINKER_FLAG_PREFIXES): |
| return True |
| if f in SUPPORTED_LLD_LINKER_FLAGS: |
| return True |
| else: |
| # Silently ignore -l/-L flags when not using lld. If using lld allow |
| # them to pass through the linker |
| if f.startswith('-l') or f.startswith('-L'): |
| return False |
| shared.warning('ignoring unsupported linker flag: `%s`', f) |
| return False |
| |
| link_flags = [f for f in link_flags if is_supported_link_flag(f[1])] |
| |
| if shared.Settings.MINIMAL_RUNTIME: |
| # Remove the default exported functions 'memcpy', 'memset', 'malloc', 'free', etc. - those should only be linked in if used |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = [] |
| |
| if shared.Settings.STACK_OVERFLOW_CHECK: |
| if shared.Settings.MINIMAL_RUNTIME: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$abortStackOverflow'] |
| else: |
| shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie', 'abortStackOverflow'] |
| |
| if shared.Settings.MODULARIZE_INSTANCE: |
| shared.Settings.MODULARIZE = 1 |
| |
| if shared.Settings.MODULARIZE: |
| assert not options.proxy_to_worker, '-s MODULARIZE=1 and -s MODULARIZE_INSTANCE=1 are not compatible with --proxy-to-worker (if you want to run in a worker with -s MODULARIZE=1, you likely want to do the worker side setup manually)' |
| # MODULARIZE's .then() method uses onRuntimeInitialized currently, so make sure |
| # it is expected to be used. |
| shared.Settings.INCOMING_MODULE_JS_API += ['onRuntimeInitialized'] |
| |
| if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: |
| shared.Settings.ALIASING_FUNCTION_POINTERS = 0 |
| |
| if shared.Settings.LEGACY_VM_SUPPORT: |
| if not shared.Settings.WASM or shared.Settings.WASM2JS: |
| shared.Settings.POLYFILL_OLD_MATH_FUNCTIONS = 1 |
| |
| # Support all old browser versions |
| shared.Settings.MIN_FIREFOX_VERSION = 0 |
| shared.Settings.MIN_SAFARI_VERSION = 0 |
| shared.Settings.MIN_IE_VERSION = 0 |
| shared.Settings.MIN_EDGE_VERSION = 0 |
| shared.Settings.MIN_CHROME_VERSION = 0 |
| |
| if shared.Settings.MIN_SAFARI_VERSION <= 9 and (not shared.Settings.WASM or shared.Settings.WASM2JS): |
| shared.Settings.WORKAROUND_IOS_9_RIGHT_SHIFT_BUG = 1 |
| |
| if shared.Settings.MIN_CHROME_VERSION <= 37: |
| shared.Settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 1 |
| |
| # Silently drop any individual backwards compatibility emulation flags that are known never to occur on browsers that support WebAssembly. |
| if shared.Settings.WASM and not shared.Settings.WASM2JS: |
| shared.Settings.POLYFILL_OLD_MATH_FUNCTIONS = 0 |
| shared.Settings.WORKAROUND_IOS_9_RIGHT_SHIFT_BUG = 0 |
| shared.Settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 0 |
| |
| if shared.Settings.STB_IMAGE and final_suffix in JS_CONTAINING_ENDINGS: |
| input_files.append((next_arg_index, shared.path_from_root('third_party', 'stb_image.c'))) |
| next_arg_index += 1 |
| shared.Settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free'] |
| # stb_image 2.x need to have STB_IMAGE_IMPLEMENTATION defined to include the implementation when compiling |
| newargs.append('-DSTB_IMAGE_IMPLEMENTATION') |
| |
| if shared.Settings.USE_WEBGL2: |
| shared.Settings.MAX_WEBGL_VERSION = 2 |
| |
| forced_stdlibs = [] |
| |
| if shared.Settings.ASMFS and final_suffix in JS_CONTAINING_ENDINGS: |
| forced_stdlibs.append('libasmfs') |
| newargs.append('-D__EMSCRIPTEN_ASMFS__=1') |
| next_arg_index += 1 |
| shared.Settings.FILESYSTEM = 0 |
| shared.Settings.SYSCALLS_REQUIRE_FILESYSTEM = 0 |
| shared.Settings.FETCH = 1 |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_asmfs.js')) |
| |
| # Explicitly drop linking in a malloc implementation if program is not using any dynamic allocation calls. |
| if not shared.Settings.USES_DYNAMIC_ALLOC: |
| shared.Settings.MALLOC = 'none' |
| |
| if shared.Settings.MALLOC == 'emmalloc': |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_emmalloc.js')) |
| |
| if shared.Settings.FETCH and final_suffix in JS_CONTAINING_ENDINGS: |
| forced_stdlibs.append('libfetch') |
| next_arg_index += 1 |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_fetch.js')) |
| if shared.Settings.USE_PTHREADS: |
| shared.Settings.FETCH_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.fetch.js' |
| |
| if not shared.Settings.USE_PTHREADS or not shared.Settings.FETCH: |
| shared.Settings.USE_FETCH_WORKER = 0 |
| |
| # In asm.js+pthreads we can use a fetch worker, which is made from the main |
| # asm.js code. That lets us do sync operations by blocking on the worker etc. |
| # In the wasm backend we don't have a fetch worker implemented yet, however, |
| # we can still do basic synchronous fetches in the same places: if we can |
| # block on another thread then we aren't the main thread, and if we aren't |
| # the main thread then synchronous xhrs are legitimate. |
| if shared.Settings.FETCH and shared.Settings.WASM_BACKEND: |
| shared.Settings.USE_FETCH_WORKER = 0 |
| |
| if shared.Settings.DEMANGLE_SUPPORT: |
| shared.Settings.EXPORTED_FUNCTIONS += ['___cxa_demangle'] |
| forced_stdlibs.append('libc++abi') |
| |
| if shared.Settings.FULL_ES3: |
| shared.Settings.FULL_ES2 = 1 |
| |
| if shared.Settings.EMBIND: |
| forced_stdlibs.append('libembind') |
| |
| if not shared.Settings.MINIMAL_RUNTIME: |
| # Always need malloc and free to be kept alive and exported, for internal use and other modules |
| shared.Settings.EXPORTED_FUNCTIONS += ['_malloc', '_free'] |
| if shared.Settings.WASM_BACKEND: |
| # setjmp/longjmp and exception handling JS code depends on this so we |
| # include it by default. Should be eliminated by meta-DCE if unused. |
| shared.Settings.EXPORTED_FUNCTIONS += ['_setThrew'] |
| |
| if shared.Settings.RELOCATABLE and not shared.Settings.DYNAMIC_EXECUTION: |
| exit_with_error('cannot have both DYNAMIC_EXECUTION=0 and RELOCATABLE enabled at the same time, since RELOCATABLE needs to eval()') |
| |
| if shared.Settings.SIDE_MODULE and shared.Settings.GLOBAL_BASE != -1: |
| exit_with_error('Cannot set GLOBAL_BASE when building SIDE_MODULE') |
| |
| if shared.Settings.RELOCATABLE: |
| if 'EMULATED_FUNCTION_POINTERS' not in settings_key_changes and not shared.Settings.WASM_BACKEND: |
| shared.Settings.EMULATED_FUNCTION_POINTERS = 2 # by default, use optimized function pointer emulation |
| shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS = 0 |
| shared.Settings.WARN_ON_UNDEFINED_SYMBOLS = 0 |
| |
| if shared.Settings.ASYNCIFY: |
| if not shared.Settings.WASM_BACKEND: |
| exit_with_error('ASYNCIFY has been removed from fastcomp. There is a new implementation which can be used in the upstream wasm backend.') |
| |
| if shared.Settings.EMTERPRETIFY: |
| shared.Settings.FINALIZE_ASM_JS = 0 |
| shared.Settings.SIMPLIFY_IFS = 0 # this is just harmful for emterpreting |
| shared.Settings.EXPORTED_FUNCTIONS += ['emterpret'] |
| if not options.js_opts: |
| logger.debug('enabling js opts for EMTERPRETIFY') |
| options.js_opts = True |
| options.force_js_opts = True |
| if options.use_closure_compiler == 2: |
| exit_with_error('EMTERPRETIFY requires valid asm.js, and is incompatible with closure 2 which disables that') |
| assert not use_source_map(options), 'EMTERPRETIFY is not compatible with source maps (maps are not useful in emterpreted code, and splitting out non-emterpreted source maps is not yet implemented)' |
| |
| if shared.Settings.DISABLE_EXCEPTION_THROWING and not shared.Settings.DISABLE_EXCEPTION_CATCHING: |
| exit_with_error("DISABLE_EXCEPTION_THROWING was set (probably from -fno-exceptions) but is not compatible with enabling exception catching (DISABLE_EXCEPTION_CATCHING=0). If you don't want exceptions, set DISABLE_EXCEPTION_CATCHING to 1; if you do want exceptions, don't link with -fno-exceptions") |
| |
| if shared.Settings.DEAD_FUNCTIONS: |
| if not options.js_opts: |
| logger.debug('enabling js opts for DEAD_FUNCTIONS') |
| options.js_opts = True |
| options.force_js_opts = True |
| |
| if options.proxy_to_worker: |
| shared.Settings.PROXY_TO_WORKER = 1 |
| |
| if options.use_preload_plugins or len(options.preload_files) or len(options.embed_files): |
| if shared.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 |
| shared.Settings.FORCE_FILESYSTEM = 1 |
| |
| if options.proxy_to_worker or options.use_preload_plugins: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$Browser'] |
| |
| if not shared.Settings.MINIMAL_RUNTIME: |
| # In non-MINIMAL_RUNTIME, the core runtime depends on these functions to be present. (In MINIMAL_RUNTIME, they are |
| # no longer always bundled in) |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$demangle', '$demangleAll', '$jsStackTrace', '$stackTrace'] |
| |
| if shared.Settings.FILESYSTEM: |
| if shared.Settings.SUPPORT_ERRNO: |
| shared.Settings.EXPORTED_FUNCTIONS += ['___errno_location'] # so FS can report errno back to C |
| # 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 disabled NO_EXIT_RUNTIME) |
| if shared.Settings.EXIT_RUNTIME or shared.Settings.ASSERTIONS: |
| shared.Settings.EXPORTED_FUNCTIONS += ['_fflush'] |
| |
| if shared.Settings.GLOBAL_BASE < 0: |
| # default if nothing else sets it |
| if shared.Settings.WASM: |
| # a higher global base is useful for optimizing load/store offsets, as it |
| # enables the --post-emscripten pass |
| shared.Settings.GLOBAL_BASE = 1024 |
| else: |
| shared.Settings.GLOBAL_BASE = 8 |
| |
| if shared.Settings.SAFE_HEAP: |
| # SAFE_HEAP check includes calling emscripten_get_sbrk_ptr(). |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_get_sbrk_ptr'] |
| |
| if not shared.Settings.DECLARE_ASM_MODULE_EXPORTS: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$exportAsmFunctions'] |
| |
| if shared.Settings.USE_PTHREADS: |
| if shared.Settings.USE_PTHREADS == 2: |
| exit_with_error('USE_PTHREADS=2 is not longer supported') |
| if shared.Settings.ALLOW_MEMORY_GROWTH: |
| if not shared.Settings.WASM: |
| exit_with_error('Memory growth is not supported with pthreads without wasm') |
| else: |
| logging.warning('USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271') |
| # UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer |
| shared.Settings.TEXTDECODER = 0 |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_pthread.js')) |
| newargs.append('-D__EMSCRIPTEN_PTHREADS__=1') |
| if shared.Settings.WASM_BACKEND: |
| newargs += ['-pthread'] |
| # some pthreads code is in asm.js library functions, which are auto-exported; for the wasm backend, we must |
| # manually export them |
| shared.Settings.EXPORTED_FUNCTIONS += [ |
| '_emscripten_get_global_libc', '___pthread_tsd_run_dtors', |
| '__register_pthread_ptr', '_pthread_self', |
| '___emscripten_pthread_data_constructor', '_emscripten_futex_wake'] |
| |
| # set location of worker.js |
| shared.Settings.PTHREAD_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.worker.js' |
| else: |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_pthread_stub.js')) |
| |
| if shared.Settings.FORCE_FILESYSTEM and not shared.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) |
| if not shared.Settings.ASMFS: |
| shared.Settings.EXPORTED_RUNTIME_METHODS += [ |
| 'FS_createFolder', |
| 'FS_createPath', |
| 'FS_createDataFile', |
| 'FS_createPreloadedFile', |
| 'FS_createLazyFile', |
| 'FS_createLink', |
| 'FS_createDevice', |
| 'FS_unlink' |
| ] |
| |
| shared.Settings.EXPORTED_RUNTIME_METHODS += [ |
| 'getMemory', |
| 'addRunDependency', |
| 'removeRunDependency', |
| 'calledRun', |
| ] |
| |
| if shared.Settings.USE_PTHREADS: |
| shared.Settings.EXPORTED_RUNTIME_METHODS += ['establishStackSpace'] |
| |
| # memalign is used to ensure allocated thread stacks are aligned. |
| shared.Settings.EXPORTED_FUNCTIONS += ['_memalign'] |
| |
| # dynCall_ii is used to call pthread entry points in worker.js (as |
| # metadce does not consider worker.js, which is external, we must |
| # consider it a user export, i.e., one which can never be removed). |
| shared.Building.user_requested_exports += ['dynCall_ii'] |
| |
| if shared.Settings.PROXY_TO_PTHREAD: |
| shared.Settings.EXPORTED_FUNCTIONS += ['_proxy_main'] |
| |
| # pthread stack setup: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$establishStackSpaceInJsModule'] |
| shared.Settings.EXPORTED_FUNCTIONS += ['establishStackSpaceInJsModule'] |
| |
| if shared.Settings.MODULARIZE: |
| # MODULARIZE+USE_PTHREADS mode requires extra exports out to Module so that worker.js |
| # can access them: |
| |
| # general threading variables: |
| shared.Settings.EXPORTED_RUNTIME_METHODS += ['PThread', 'ExitStatus'] |
| |
| # stack check: |
| if shared.Settings.STACK_OVERFLOW_CHECK: |
| shared.Settings.EXPORTED_RUNTIME_METHODS += ['writeStackCookie', 'checkStackCookie'] |
| |
| if shared.Settings.LINKABLE: |
| exit_with_error('-s LINKABLE=1 is not supported with -s USE_PTHREADS>0!') |
| if shared.Settings.SIDE_MODULE: |
| exit_with_error('-s SIDE_MODULE=1 is not supported with -s USE_PTHREADS>0!') |
| if shared.Settings.MAIN_MODULE: |
| exit_with_error('-s MAIN_MODULE=1 is not supported with -s USE_PTHREADS>0!') |
| if shared.Settings.EMTERPRETIFY: |
| exit_with_error('-s EMTERPRETIFY=1 is not supported with -s USE_PTHREADS>0!') |
| if shared.Settings.PROXY_TO_WORKER: |
| exit_with_error('--proxy-to-worker is not supported with -s USE_PTHREADS>0! Use the option -s PROXY_TO_PTHREAD=1 if you want to run the main thread of a multithreaded application in a web worker.') |
| else: |
| if shared.Settings.PROXY_TO_PTHREAD: |
| exit_with_error('-s PROXY_TO_PTHREAD=1 requires -s USE_PTHREADS to work!') |
| |
| # Enable minification of asm.js imports on -O1 and higher if -g1 or lower is used. |
| if options.opt_level >= 1 and options.debug_level < 2 and not shared.Settings.WASM: |
| shared.Settings.MINIFY_ASMJS_IMPORT_NAMES = 1 |
| |
| if shared.Settings.WASM: |
| if not shared.Building.need_asm_js_file(): |
| asm_target = asm_target.replace('.asm.js', '.temp.asm.js') |
| misc_temp_files.note(asm_target) |
| |
| if shared.Settings.WASM: |
| if shared.Settings.TOTAL_MEMORY % 65536 != 0: |
| exit_with_error('For wasm, TOTAL_MEMORY must be a multiple of 64KB, was ' + str(shared.Settings.TOTAL_MEMORY)) |
| else: |
| if shared.Settings.TOTAL_MEMORY < 16 * 1024 * 1024: |
| exit_with_error('TOTAL_MEMORY must be at least 16MB, was ' + str(shared.Settings.TOTAL_MEMORY)) |
| if shared.Settings.TOTAL_MEMORY % (16 * 1024 * 1024) != 0: |
| exit_with_error('For asm.js, TOTAL_MEMORY must be a multiple of 16MB, was ' + str(shared.Settings.TOTAL_MEMORY)) |
| if shared.Settings.TOTAL_MEMORY < shared.Settings.TOTAL_STACK: |
| exit_with_error('TOTAL_MEMORY must be larger than TOTAL_STACK, was ' + str(shared.Settings.TOTAL_MEMORY) + ' (TOTAL_STACK=' + str(shared.Settings.TOTAL_STACK) + ')') |
| if shared.Settings.WASM_MEM_MAX != -1 and shared.Settings.WASM_MEM_MAX % 65536 != 0: |
| exit_with_error('WASM_MEM_MAX must be a multiple of 64KB, was ' + str(shared.Settings.WASM_MEM_MAX)) |
| if shared.Settings.MEMORY_GROWTH_LINEAR_STEP != -1 and shared.Settings.MEMORY_GROWTH_LINEAR_STEP % 65536 != 0: |
| exit_with_error('MEMORY_GROWTH_LINEAR_STEP must be a multiple of 64KB, was ' + str(shared.Settings.MEMORY_GROWTH_LINEAR_STEP)) |
| if shared.Settings.USE_PTHREADS and shared.Settings.WASM and shared.Settings.ALLOW_MEMORY_GROWTH and shared.Settings.WASM_MEM_MAX == -1: |
| exit_with_error('If pthreads and memory growth are enabled, WASM_MEM_MAX must be set') |
| |
| if shared.Settings.EXPORT_ES6 and not shared.Settings.MODULARIZE: |
| exit_with_error('EXPORT_ES6 requires MODULARIZE to be set') |
| |
| if shared.Settings.MODULARIZE and not shared.Settings.DECLARE_ASM_MODULE_EXPORTS: |
| # When MODULARIZE option is used, currently requires declaring all module exports |
| # individually - TODO: this could be optimized |
| exit_with_error('DECLARE_ASM_MODULE_EXPORTS=0 is not compatible with MODULARIZE') |
| |
| # When not declaring asm module exports in outer scope one by one, disable minifying |
| # asm.js/wasm module export names so that the names can be passed directly to the outer scope. |
| # Also, if using library_exports.js API, disable minification so that the feature can work. |
| if not shared.Settings.DECLARE_ASM_MODULE_EXPORTS or 'exports.js' in [x for _, x in libs]: |
| shared.Settings.MINIFY_ASMJS_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. |
| # For example, ASYNCIFY_LAZY_LOAD_CODE needs to identify import names, and wasm2js |
| # needs to use the getTempRet0 imports (otherwise, it may create new ones to replace |
| # the old, which would break). |
| if will_metadce(options) and \ |
| options.opt_level >= 2 and \ |
| options.debug_level <= 2 and \ |
| not shared.Settings.LINKABLE and \ |
| not shared.Settings.STANDALONE_WASM and \ |
| not shared.Settings.AUTODEBUG and \ |
| not shared.Settings.ASSERTIONS and \ |
| not shared.Settings.RELOCATABLE and \ |
| not target.endswith(WASM_ENDINGS) and \ |
| not shared.Settings.ASYNCIFY_LAZY_LOAD_CODE and \ |
| not shared.Settings.WASM2JS and \ |
| shared.Settings.MINIFY_ASMJS_EXPORT_NAMES: |
| shared.Settings.MINIFY_WASM_IMPORTS_AND_EXPORTS = 1 |
| # in fastcomp it's inconvenient to minify module names as there is the |
| # asm2wasm module etc. |
| if shared.Settings.WASM_BACKEND: |
| shared.Settings.MINIFY_WASM_IMPORTED_MODULES = 1 |
| |
| # In MINIMAL_RUNTIME when modularizing, by default output asm.js module under the same name as |
| # the JS module. This allows code to share same loading function for both JS and asm.js modules, |
| # to save code size. The intent is that loader code captures the function variable from global |
| # scope to XHR loader local scope when it finishes loading, to avoid polluting global JS scope |
| # with variables. This provides safety via encapsulation. See src/shell_minimal_runtime.html for |
| # an example. |
| if shared.Settings.MINIMAL_RUNTIME and not shared.Settings.SEPARATE_ASM_MODULE_NAME and not shared.Settings.WASM and shared.Settings.MODULARIZE: |
| shared.Settings.SEPARATE_ASM_MODULE_NAME = 'var ' + shared.Settings.EXPORT_NAME |
| |
| if shared.Settings.MODULARIZE and shared.Settings.SEPARATE_ASM and not shared.Settings.WASM and not shared.Settings.SEPARATE_ASM_MODULE_NAME: |
| exit_with_error('Targeting asm.js with --separate-asm and -s MODULARIZE=1 requires specifying the target variable name to which the asm.js module is loaded into. See https://github.com/emscripten-core/emscripten/pull/7949 for details') |
| # Apply default option if no custom name is provided |
| if not shared.Settings.SEPARATE_ASM_MODULE_NAME: |
| shared.Settings.SEPARATE_ASM_MODULE_NAME = 'Module["asm"]' |
| elif shared.Settings.WASM: |
| exit_with_error('-s SEPARATE_ASM_MODULE_NAME option only applies to when targeting asm.js, not with WebAssembly!') |
| |
| if shared.Settings.MINIMAL_RUNTIME: |
| # Minimal runtime uses a different default shell file |
| if options.shell_path == shared.path_from_root('src', 'shell.html'): |
| options.shell_path = shared.path_from_root('src', 'shell_minimal_runtime.html') |
| |
| if shared.Settings.ASSERTIONS and shared.Settings.MINIMAL_RUNTIME: |
| # In ASSERTIONS-builds, functions UTF8ArrayToString() and stringToUTF8Array() (which are not JS library functions), both |
| # use warnOnce(), which in MINIMAL_RUNTIME is a JS library function, so explicitly have to mark dependency to warnOnce() |
| # in that case. If string functions are turned to library functions in the future, then JS dependency tracking can be |
| # used and this special directive can be dropped. |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$warnOnce'] |
| |
| # Require explicit -lfoo.js flags to link with JS libraries. |
| shared.Settings.AUTO_JS_LIBRARIES = 0 |
| |
| # In asm.js always use memory init file to get the best code size, other modes are not currently supported. |
| if not shared.Settings.WASM and not shared.Settings.WASM_BACKEND: |
| options.memory_init_file = True |
| |
| if shared.Settings.MODULARIZE and not shared.Settings.MODULARIZE_INSTANCE and shared.Settings.EXPORT_NAME == 'Module' and final_suffix == '.html' and \ |
| (options.shell_path == shared.path_from_root('src', 'shell.html') or options.shell_path == shared.path_from_root('src', 'shell_minimal.html')): |
| exit_with_error('Due to collision in variable name "Module", the shell file "' + options.shell_path + '" is not compatible with build options "-s MODULARIZE=1 -s EXPORT_NAME=Module". Either provide your own shell file, change the name of the export to something else to avoid the name collision. (see https://github.com/emscripten-core/emscripten/issues/7950 for details)') |
| |
| if final_suffix in WASM_ENDINGS: |
| # 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. |
| if shared.Settings.WASM_BACKEND and not shared.Settings.SIDE_MODULE: |
| shared.Settings.STANDALONE_WASM = 1 |
| js_target = misc_temp_files.get(suffix='.js').name |
| |
| if shared.Settings.WASM: |
| if shared.Settings.SINGLE_FILE: |
| # placeholder strings for JS glue, to be replaced with subresource locations in do_binaryen |
| shared.Settings.WASM_TEXT_FILE = shared.FilenameReplacementStrings.WASM_TEXT_FILE |
| shared.Settings.WASM_BINARY_FILE = shared.FilenameReplacementStrings.WASM_BINARY_FILE |
| shared.Settings.ASMJS_CODE_FILE = shared.FilenameReplacementStrings.ASMJS_CODE_FILE |
| else: |
| # set file locations, so that JS glue can find what it needs |
| shared.Settings.WASM_TEXT_FILE = shared.JS.escape_for_js_string(os.path.basename(wasm_text_target)) |
| shared.Settings.WASM_BINARY_FILE = shared.JS.escape_for_js_string(os.path.basename(wasm_binary_target)) |
| shared.Settings.ASMJS_CODE_FILE = shared.JS.escape_for_js_string(os.path.basename(asm_target)) |
| shared.Settings.ASM_JS = 2 # when targeting wasm, we use a wasm Memory, but that is not compatible with asm.js opts |
| if shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS: |
| shared.warning('for wasm there is no need to set ELIMINATE_DUPLICATE_FUNCTIONS, the binaryen optimizer does it automatically') |
| shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS = 0 |
| # default precise-f32 to on, since it works well in wasm |
| shared.Settings.PRECISE_F32 = 1 |
| if options.js_opts and not options.force_js_opts: |
| options.js_opts = None |
| logger.debug('asm.js opts not forced by user or an option that depends them, and we do not intend to run the asm.js, so disabling and leaving opts to the binaryen optimizer') |
| if options.use_closure_compiler == 2 and not shared.Settings.WASM2JS: |
| exit_with_error('closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm') |
| if any(s.startswith('MEM_INIT_METHOD=') for s in settings_changes): |
| exit_with_error('MEM_INIT_METHOD is not supported in wasm. Memory will be embedded in the wasm binary if threads are not used, and included in a separate file if threads are used.') |
| if shared.Settings.WASM2JS: |
| shared.Settings.MAYBE_WASM2JS = 1 |
| # wasm2js does not support passive segments or atomics |
| if shared.Settings.USE_PTHREADS: |
| exit_with_error('WASM2JS does not yet support pthreads') |
| # in wasm2js, keep the mem init in the wasm itself if we can and if the |
| # options wouldn't tell a js build to use a separate mem init file |
| shared.Settings.MEM_INIT_IN_WASM = not options.memory_init_file or shared.Settings.SINGLE_FILE |
| else: |
| # wasm includes the mem init in the wasm binary. The exception is |
| # wasm2js, which behaves more like js. |
| options.memory_init_file = True |
| shared.Settings.MEM_INIT_IN_WASM = True if shared.Settings.WASM_BACKEND else not shared.Settings.USE_PTHREADS |
| |
| # WASM_ASYNC_COMPILATION and SWAPPABLE_ASM_MODULE do not have a meaning in MINIMAL_RUNTIME (always async) |
| if not shared.Settings.MINIMAL_RUNTIME and shared.Settings.WASM_ASYNC_COMPILATION: |
| # async compilation requires a swappable module - we swap it in when it's ready |
| shared.Settings.SWAPPABLE_ASM_MODULE = 1 |
| |
| if shared.Settings.SWAPPABLE_ASM_MODULE and not shared.Settings.DECLARE_ASM_MODULE_EXPORTS: |
| # Swappable wasm module/asynchronous wasm compilation requires an indirect stub |
| # function generated to each function export from wasm module, so cannot use the |
| # concise form of grabbing exports that does not need to refer to each export individually. |
| exit_with_error('DECLARE_ASM_MODULE_EXPORTS=0 is not comptabible with SWAPPABLE_ASM_MODULE') |
| |
| # wasm side modules have suffix .wasm |
| if shared.Settings.SIDE_MODULE and target.endswith('.js'): |
| shared.warning('output suffix .js requested, but wasm side modules are just wasm files; emitting only a .wasm, no .js') |
| |
| if options.separate_asm: |
| exit_with_error('cannot --separate-asm when emitting wasm, since not emitting asm.js') |
| |
| sanitize = set() |
| |
| for arg in newargs: |
| if arg.startswith('-fsanitize='): |
| sanitize.update(arg.split('=', 1)[1].split(',')) |
| elif arg.startswith('-fno-sanitize='): |
| sanitize.difference_update(arg.split('=', 1)[1].split(',')) |
| |
| if sanitize: |
| shared.Settings.USE_OFFSET_CONVERTER = 1 |
| |
| if not shared.Settings.WASM_BACKEND: |
| exit_with_error('Sanitizers are not compatible with the fastcomp backend. Please upgrade to the upstream wasm backend by following these instructions: https://v8.dev/blog/emscripten-llvm-wasm#testing') |
| |
| if sanitize & UBSAN_SANITIZERS: |
| if '-fsanitize-minimal-runtime' in newargs: |
| shared.Settings.UBSAN_RUNTIME = 1 |
| else: |
| shared.Settings.UBSAN_RUNTIME = 2 |
| |
| if 'leak' in sanitize: |
| shared.Settings.USE_LSAN = 1 |
| shared.Settings.EXIT_RUNTIME = 1 |
| |
| if shared.Settings.LINKABLE: |
| exit_with_error('LSan does not support dynamic linking') |
| |
| if 'address' in sanitize: |
| shared.Settings.USE_ASAN = 1 |
| |
| shared.Settings.GLOBAL_BASE = shared.Settings.ASAN_SHADOW_SIZE |
| shared.Settings.TOTAL_MEMORY += shared.Settings.ASAN_SHADOW_SIZE |
| assert shared.Settings.TOTAL_MEMORY < 2**32 |
| |
| if shared.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 shared.Settings.LINKABLE: |
| exit_with_error('ASan does not support dynamic linking') |
| |
| if sanitize and '-g4' in args: |
| shared.Settings.LOAD_SOURCE_MAP = 1 |
| |
| if shared.Settings.WASM_BACKEND: |
| options.js_opts = None |
| |
| # wasm backend output can benefit from the binaryen optimizer (in asm2wasm, |
| # we run the optimizer during asm2wasm itself). use it, if not overridden. |
| |
| # BINARYEN_PASSES and BINARYEN_EXTRA_PASSES are comma-separated, and we support both '-'-prefixed and unprefixed pass names |
| def parse_passes(string): |
| passes = string.split(',') |
| passes = [('--' + p) if p[0] != '-' else p for p in passes if p] |
| return passes |
| |
| passes = [] |
| if 'BINARYEN_PASSES' in settings_key_changes: |
| if shared.Settings.BINARYEN_PASSES: |
| passes += parse_passes(shared.Settings.BINARYEN_PASSES) |
| else: |
| # safe heap must run before post-emscripten, so post-emscripten can apply the sbrk ptr |
| if shared.Settings.SAFE_HEAP: |
| passes += ['--safe-heap'] |
| passes += ['--post-emscripten'] |
| # always inline __original_main into main, as otherwise it makes debugging confusing, |
| # and doing so is never bad for code size |
| passes += ['--inline-main'] |
| if not shared.Settings.EXIT_RUNTIME: |
| passes += ['--no-exit-runtime'] |
| if options.opt_level > 0 or options.shrink_level > 0: |
| passes += [shared.Building.opt_level_to_str(options.opt_level, options.shrink_level)] |
| elif shared.Settings.STANDALONE_WASM: |
| # even if not optimizing, make an effort to remove all unused imports and |
| # exports, to make the wasm as standalone as possible |
| passes += ['--remove-unused-module-elements'] |
| if shared.Settings.GLOBAL_BASE >= 1024: # hardcoded value in the binaryen pass |
| passes += ['--low-memory-unused'] |
| if options.debug_level < 3: |
| passes += ['--strip-debug'] |
| if not shared.Settings.EMIT_PRODUCERS_SECTION: |
| passes += ['--strip-producers'] |
| if shared.Settings.AUTODEBUG and shared.Settings.WASM_OBJECT_FILES: |
| # adding '--flatten' here may make these even more effective |
| passes += ['--instrument-locals'] |
| passes += ['--log-execution'] |
| passes += ['--instrument-memory'] |
| passes += ['--legalize-js-interface'] |
| if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: |
| # note that this pass must run before asyncify, as if it runs afterwards we only |
| # generate the byn$fpcast_emu functions after asyncify runs, and so we wouldn't |
| # be able to whitelist them etc. |
| passes += ['--fpcast-emu'] |
| if shared.Settings.ASYNCIFY: |
| # TODO: allow whitelist as in asyncify |
| passes += ['--asyncify'] |
| if shared.Settings.ASSERTIONS: |
| passes += ['--pass-arg=asyncify-asserts'] |
| if shared.Settings.ASYNCIFY_IGNORE_INDIRECT: |
| passes += ['--pass-arg=asyncify-ignore-indirect'] |
| else: |
| # 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. |
| shared.Settings.ASYNCIFY_IMPORTS += ['invoke_*'] |
| # with pthreads we may call main through the __call_main mechanism, which can |
| # therefore reach anything in the program, so mark it as possibly causing a |
| # sleep (the asyncify analysis doesn't look through JS, just wasm, so it can't |
| # see what it itself calls) |
| if shared.Settings.USE_PTHREADS: |
| shared.Settings.ASYNCIFY_IMPORTS += ['__call_main'] |
| if shared.Settings.ASYNCIFY_IMPORTS: |
| # 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 |
| |
| shared.Settings.ASYNCIFY_IMPORTS = [get_full_import_name(i) for i in shared.Settings.ASYNCIFY_IMPORTS] |
| |
| passes += ['--pass-arg=asyncify-imports@%s' % ','.join(shared.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(')'): |
| shared.warning('''emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):''') |
| shared.warning(''' ''' + item) |
| shared.warning('''This may indicate improper escaping that led to splitting inside your names.''') |
| shared.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_WHITELIST=["foo(int, char)", "bar"]' ''') |
| break |
| |
| if shared.Settings.ASYNCIFY_BLACKLIST: |
| check_human_readable_list(shared.Settings.ASYNCIFY_BLACKLIST) |
| passes += ['--pass-arg=asyncify-blacklist@%s' % ','.join(shared.Settings.ASYNCIFY_BLACKLIST)] |
| if shared.Settings.ASYNCIFY_WHITELIST: |
| check_human_readable_list(shared.Settings.ASYNCIFY_WHITELIST) |
| passes += ['--pass-arg=asyncify-whitelist@%s' % ','.join(shared.Settings.ASYNCIFY_WHITELIST)] |
| if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS: |
| passes += ['--ignore-implicit-traps'] |
| if shared.Settings.BINARYEN_EXTRA_PASSES: |
| passes += parse_passes(shared.Settings.BINARYEN_EXTRA_PASSES) |
| options.binaryen_passes = passes |
| |
| # to bootstrap struct_info, we need binaryen |
| os.environ['EMCC_WASM_BACKEND_BINARYEN'] = '1' |
| |
| # run safe-heap as a binaryen pass in fastcomp wasm, while in the wasm backend we |
| # run it in binaryen_passes so that it can be synchronized with the sbrk ptr |
| if shared.Settings.SAFE_HEAP and shared.Building.is_wasm_only() and not shared.Settings.WASM_BACKEND: |
| options.binaryen_passes += ['--safe-heap'] |
| if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS and not shared.Settings.WASM_BACKEND: |
| # emulated function pointer casts is emulated in fastcomp wasm using a binaryen pass |
| options.binaryen_passes += ['--fpcast-emu'] |
| # we also need emulated function pointers for that, as we need a single flat |
| # table, as is standard in wasm, and not asm.js split ones. |
| shared.Settings.EMULATED_FUNCTION_POINTERS = 1 |
| |
| if shared.Settings.WASM2JS: |
| if not shared.Settings.WASM_BACKEND: |
| exit_with_error('wasm2js is only available in the upstream wasm backend path') |
| if use_source_map(options): |
| exit_with_error('wasm2js does not support source maps yet (debug in wasm for now)') |
| |
| if shared.Settings.EVAL_CTORS and not shared.Settings.WASM: |
| # for asm.js: this option is not a js optimizer pass, but does run the js optimizer internally, so |
| # we need to generate proper code for that (for wasm, we run a binaryen tool for this) |
| shared.Settings.RUNNING_JS_OPTS = 1 |
| |
| # memory growth does not work in dynamic linking, except for wasm |
| if (shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE) and shared.Settings.ALLOW_MEMORY_GROWTH and not shared.Settings.WASM: |
| exit_with_error('memory growth is not supported with shared asm.js modules') |
| |
| if shared.Settings.MINIMAL_RUNTIME: |
| if shared.Settings.EMTERPRETIFY: |
| exit_with_error('-s EMTERPRETIFY=1 is not supported with -s MINIMAL_RUNTIME=1') |
| |
| if shared.Settings.USE_PTHREADS: |
| exit_with_error('-s USE_PTHREADS=1 is not yet supported with -s MINIMAL_RUNTIME=1') |
| |
| if shared.Settings.PRECISE_F32 == 2: |
| exit_with_error('-s PRECISE_F32=2 is not supported with -s MINIMAL_RUNTIME=1') |
| |
| if shared.Settings.ALLOW_MEMORY_GROWTH and shared.Settings.ASM_JS == 1: |
| # this is an issue in asm.js, but not wasm |
| if not shared.Settings.WASM: |
| shared.WarningManager.warn('ALMOST_ASM') |
| shared.Settings.ASM_JS = 2 # memory growth does not validate as asm.js http://discourse.wicg.io/t/request-for-comments-switching-resizing-heaps-in-asm-js/641/23 |
| |
| if shared.Settings.NODE_CODE_CACHING: |
| if shared.Settings.WASM_ASYNC_COMPILATION: |
| exit_with_error('NODE_CODE_CACHING requires sync compilation (WASM_ASYNC_COMPILATION=0)') |
| if not shared.Settings.target_environment_may_be('node'): |
| exit_with_error('NODE_CODE_CACHING only works in node, but target environments do not include it') |
| |
| # safe heap in asm.js uses the js optimizer (in wasm-only mode we can use binaryen) |
| if shared.Settings.SAFE_HEAP and not shared.Building.is_wasm_only(): |
| if not options.js_opts: |
| logger.debug('enabling js opts for SAFE_HEAP') |
| options.js_opts = True |
| options.force_js_opts = True |
| |
| if options.js_opts: |
| shared.Settings.RUNNING_JS_OPTS = 1 |
| |
| if shared.Settings.CYBERDWARF: |
| options.debug_level = max(options.debug_level, 2) |
| shared.Settings.BUNDLED_CD_DEBUG_FILE = target + ".cd" |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_cyberdwarf.js')) |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_debugger_toolkit.js')) |
| newargs.append('-g') |
| |
| if options.tracing: |
| if shared.Settings.ALLOW_MEMORY_GROWTH: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout'] |
| |
| if shared.Settings.STANDALONE_WASM: |
| if not shared.Settings.WASM_BACKEND: |
| exit_with_error('STANDALONE_WASM is only available in the upstream wasm backend path') |
| if shared.Settings.USE_PTHREADS: |
| exit_with_error('STANDALONE_WASM does not support pthreads yet') |
| # the wasm must be runnable without the JS, so there cannot be anything that |
| # requires JS legalization |
| shared.Settings.LEGALIZE_JS_FFI = 0 |
| |
| if shared.Settings.WASM_BACKEND: |
| if shared.Settings.SIMD: |
| newargs.append('-msimd128') |
| if shared.Settings.USE_PTHREADS: |
| newargs.append('-pthread') |
| else: |
| # We leave the -O option in place so that the clang front-end runs in that |
| # optimization mode, but we disable the actual optimization passes, as we'll |
| # run them separately. |
| if options.opt_level > 0: |
| newargs.append('-mllvm') |
| newargs.append('-disable-llvm-optzns') |
| |
| if not shared.Settings.LEGALIZE_JS_FFI: |
| assert shared.Building.is_wasm_only(), 'LEGALIZE_JS_FFI incompatible with RUNNING_JS_OPTS.' |
| |
| shared.Settings.EMSCRIPTEN_VERSION = shared.EMSCRIPTEN_VERSION |
| shared.Settings.OPT_LEVEL = options.opt_level |
| shared.Settings.DEBUG_LEVEL = options.debug_level |
| shared.Settings.PROFILING_FUNCS = options.profiling_funcs |
| shared.Settings.SOURCE_MAP_BASE = options.source_map_base or '' |
| |
| ## Compile source code to bitcode |
| |
| logger.debug('compiling to bitcode') |
| |
| temp_files = [] |
| |
| # exit block 'parse arguments and setup' |
| log_time('parse arguments and setup') |
| |
| if DEBUG: |
| # we are about to start using temp dirs. serialize access to the temp dir |
| # when using EMCC_DEBUG, since we don't want multiple processes would to |
| # use it at once, they might collide if they happen to use the same |
| # tempfile names |
| shared.Cache.acquire_cache_lock() |
| |
| try: |
| with ToolchainProfiler.profile_block('compile inputs'): |
| # Precompiled headers support |
| if has_header_inputs: |
| headers = [header for _, header in input_files] |
| for header in headers: |
| assert header.endswith(HEADER_ENDINGS), 'if you have one header input, we assume you want to precompile headers, and cannot have source files or other inputs as well: ' + str(headers) + ' : ' + header |
| args = newargs + headers |
| if specified_target: |
| args += ['-o', specified_target] |
| args = system_libs.process_args(args, shared.Settings) |
| logger.debug("running (for precompiled headers): " + clang_compiler + ' ' + ' '.join(args)) |
| return run_process([clang_compiler] + args, check=False).returncode |
| |
| # For asm.js, the generated JavaScript could preserve LLVM value names, which can be useful for debugging. |
| if options.debug_level >= 3 and not shared.Settings.WASM and not shared.Settings.WASM_BACKEND: |
| newargs.append('-fno-discard-value-names') |
| |
| def is_link_flag(flag): |
| return any(flag.startswith(x) for x in ('-l', '-L', '-Wl,')) |
| |
| compile_args = [a for a in newargs if a and not is_link_flag(a)] |
| |
| # Bitcode args generation code |
| def get_clang_command(input_files): |
| args = [clang_compiler] + compile_args + input_files |
| if not shared.Building.can_inline(): |
| args.append('-fno-inline-functions') |
| # For fastcomp backend, no LLVM IR functions should ever be annotated |
| # 'optnone', because that would skip running the SimplifyCFG pass on |
| # them, which is required to always run to clean up LandingPadInst |
| # instructions that are not needed. |
| if not shared.Settings.WASM_BACKEND: |
| args += ['-Xclang', '-disable-O0-optnone'] |
| args = system_libs.process_args(args, shared.Settings) |
| return args |
| |
| # preprocessor-only (-E) support |
| if has_dash_E or '-M' in newargs or '-MM' in newargs or '-fsyntax-only' in newargs: |
| input_files = [x[1] for x in input_files] |
| cmd = get_clang_command(input_files) |
| if specified_target: |
| cmd += ['-o', specified_target] |
| # Do not compile, but just output the result from preprocessing stage or |
| # output the dependency rule. Warning: clang and gcc behave differently |
| # with -MF! (clang seems to not recognize it) |
| logger.debug(('just preprocessor ' if has_dash_E else 'just dependencies: ') + ' '.join(cmd)) |
| return run_process(cmd, check=False).returncode |
| |
| def get_object_filename(input_file): |
| if compile_only and len(input_files) == 1: |
| # no need for a temp file, just emit to the right place |
| if specified_target: |
| return specified_target |
| else: |
| return unsuffixed_basename(input_files[0][1]) + options.default_object_extension |
| else: |
| return in_temp(unsuffixed(uniquename(input_file)) + options.default_object_extension) |
| |
| def compile_source_file(i, input_file): |
| logger.debug('compiling source file: ' + input_file) |
| output_file = get_object_filename(input_file) |
| temp_files.append((i, output_file)) |
| cmd = get_clang_command([input_file]) + ['-c', '-o', output_file] |
| if shared.Settings.WASM_BACKEND and shared.Settings.RELOCATABLE: |
| cmd.append('-fPIC') |
| cmd.append('-fvisibility=default') |
| if shared.Settings.WASM_OBJECT_FILES: |
| for a in shared.Building.llvm_backend_args(): |
| cmd += ['-mllvm', a] |
| else: |
| cmd.append('-emit-llvm') |
| shared.print_compiler_stage(cmd) |
| shared.check_call(cmd) |
| if output_file != '-': |
| assert(os.path.exists(output_file)) |
| |
| # First, generate LLVM bitcode. For each input file, we get base.o with bitcode |
| for i, input_file in input_files: |
| file_ending = get_file_suffix(input_file) |
| if file_ending.endswith(SOURCE_ENDINGS): |
| compile_source_file(i, input_file) |
| else: |
| if file_ending.endswith(OBJECT_FILE_ENDINGS): |
| logger.debug('using object file: ' + input_file) |
| temp_files.append((i, input_file)) |
| elif file_ending.endswith(DYNAMICLIB_ENDINGS): |
| logger.debug('using shared library: ' + input_file) |
| temp_files.append((i, input_file)) |
| elif shared.Building.is_ar(input_file): |
| logger.debug('using static library: ' + input_file) |
| ensure_archive_index(input_file) |
| temp_files.append((i, input_file)) |
| elif file_ending.endswith(ASSEMBLY_ENDINGS): |
| if not LEAVE_INPUTS_RAW: |
| logger.debug('assembling assembly file: ' + input_file) |
| temp_file = in_temp(unsuffixed(uniquename(input_file)) + '.o') |
| shared.Building.llvm_as(input_file, temp_file) |
| temp_files.append((i, temp_file)) |
| else: |
| if has_fixed_language_mode: |
| compile_source_file(i, input_file) |
| else: |
| exit_with_error(input_file + ': Unknown file suffix when compiling to LLVM bitcode!') |
| |
| # exit block 'compile inputs' |
| log_time('compile inputs') |
| |
| with ToolchainProfiler.profile_block('process inputs'): |
| if not LEAVE_INPUTS_RAW and not shared.Settings.WASM_BACKEND: |
| assert len(temp_files) == len(input_files) |
| |
| # Optimize source files |
| if optimizing(options.llvm_opts): |
| for pos, (_, input_file) in enumerate(input_files): |
| file_ending = get_file_suffix(input_file) |
| if file_ending.endswith(SOURCE_ENDINGS): |
| temp_file = temp_files[pos][1] |
| logger.debug('optimizing %s', input_file) |
| new_temp_file = in_temp(unsuffixed(uniquename(temp_file)) + '.o') |
| # after optimizing, lower intrinsics to libc calls so that our linking code |
| # will find them (otherwise, llvm.cos.f32() will not link in cosf(), and |
| # we end up calling out to JS for Math.cos). |
| opts = options.llvm_opts + ['-lower-non-em-intrinsics'] |
| shared.Building.llvm_opt(temp_file, opts, new_temp_file) |
| temp_files[pos] = (temp_files[pos][0], new_temp_file) |
| |
| # If we were just compiling stop here |
| if compile_only: |
| if not specified_target: |
| assert len(temp_files) == len(input_files) |
| for tempf, inputf in zip(temp_files, input_files): |
| safe_move(tempf[1], unsuffixed_basename(inputf[1]) + final_suffix) |
| else: |
| # Specifying -o with multiple input source files is not allowed. |
| # We error out much earlier in this case. |
| assert len(input_files) == 1 |
| input_file = input_files[0][1] |
| temp_file = temp_files[0][1] |
| bitcode_target = specified_target if specified_target else unsuffixed_basename(input_file) + final_suffix |
| if temp_file != input_file: |
| safe_move(temp_file, bitcode_target) |
| else: |
| shutil.copyfile(temp_file, bitcode_target) |
| temp_output_base = unsuffixed(temp_file) |
| if os.path.exists(temp_output_base + '.d'): |
| # There was a .d file generated, from -MD or -MMD and friends, save a copy of it to where the output resides, |
| # adjusting the target name away from the temporary file name to the specified target. |
| # It will be deleted with the rest of the temporary directory. |
| deps = open(temp_output_base + '.d').read() |
| deps = deps.replace(temp_output_base + options.default_object_extension, specified_target) |
| with open(os.path.join(os.path.dirname(specified_target), os.path.basename(unsuffixed(input_file) + '.d')), "w") as out_dep: |
| out_dep.write(deps) |
| |
| # exit block 'process inputs' |
| log_time('process inputs') |
| |
| if compile_only: |
| logger.debug('stopping after compile phase') |
| return 0 |
| |
| # Decide what we will link |
| consumed = process_libraries(libs, lib_dirs, temp_files) |
| # Filter out libraries that are actually JS libs |
| link_flags = [l for l in link_flags if l[0] not in consumed] |
| temp_files = filter_out_dynamic_libs(temp_files) |
| |
| linker_inputs = [val for _, val in sorted(temp_files + link_flags)] |
| |
| if link_to_object: |
| with ToolchainProfiler.profile_block('linking to object file'): |
| # We have a specified target (-o <target>), which is not JavaScript or HTML, and |
| # we have multiple files: Link them |
| if shared.Settings.SIDE_MODULE: |
| exit_with_error('SIDE_MODULE must only be used when compiling to an executable shared library, and not when emitting an object file. That is, you should be emitting a .wasm file (for wasm) or a .js file (for asm.js). Note that when compiling to a typical native suffix for a shared library (.so, .dylib, .dll; which many build systems do) then Emscripten emits an object file, which you should then compile to .wasm or .js with SIDE_MODULE.') |
| if final_suffix.lower() in ('.so', '.dylib', '.dll'): |
| shared.warning('When Emscripten compiles to a typical native suffix for shared libraries (.so, .dylib, .dll) then it emits an object file. You should then compile that to an emscripten SIDE_MODULE (using that flag) with suffix .wasm (for wasm) or .js (for asm.js). (You may also want to adapt your build system to emit the more standard suffix for a an object file, \'.bc\' or \'.o\', which would avoid this warning.)') |
| logger.debug('link_to_object: ' + str(linker_inputs) + ' -> ' + specified_target) |
| if len(temp_files) == 1: |
| temp_file = temp_files[0][1] |
| # skip running the linker and just copy the object file |
| shutil.copyfile(temp_file, specified_target) |
| else: |
| shared.Building.link_to_object(linker_inputs, specified_target) |
| logger.debug('stopping after linking to object file') |
| return 0 |
| |
| ## Continue on to create JavaScript |
| |
| with ToolchainProfiler.profile_block('calculate system libraries'): |
| # link in ports and system libraries, if necessary |
| if not LEAVE_INPUTS_RAW and \ |
| not shared.Settings.BOOTSTRAPPING_STRUCT_INFO and \ |
| not shared.Settings.SIDE_MODULE: # shared libraries/side modules link no C libraries, need them in parent |
| extra_files_to_link = system_libs.get_ports(shared.Settings) |
| extra_files_to_link += system_libs.calculate([f for _, f in sorted(temp_files)] + extra_files_to_link, in_temp, stdout_=None, stderr_=None, forced=forced_stdlibs) |
| linker_inputs += extra_files_to_link |
| |
| # exit block 'calculate system libraries' |
| log_time('calculate system libraries') |
| |
| def dedup_list(lst): |
| rtn = [] |
| for item in lst: |
| if item not in rtn: |
| rtn.append(item) |
| return rtn |
| |
| # Make a final pass over shared.Settings.EXPORTED_FUNCTIONS to remove any |
| # duplication between functions added by the driver/libraries and function |
| # specified by the user |
| shared.Settings.EXPORTED_FUNCTIONS = dedup_list(shared.Settings.EXPORTED_FUNCTIONS) |
| |
| with ToolchainProfiler.profile_block('link'): |
| # final will be an array if linking is deferred, otherwise a normal string. |
| if shared.Settings.WASM_BACKEND: |
| DEFAULT_FINAL = in_temp(target_basename + '.wasm') |
| else: |
| DEFAULT_FINAL = in_temp(target_basename + '.bc') |
| |
| def get_final(): |
| global final |
| if isinstance(final, list): |
| final = DEFAULT_FINAL |
| return final |
| |
| # First, combine the bitcode files if there are several. We must also link if we have a singleton .a |
| perform_link = len(linker_inputs) > 1 or shared.Settings.WASM_BACKEND |
| if not perform_link and not LEAVE_INPUTS_RAW: |
| is_bc = suffix(temp_files[0][1]) in OBJECT_FILE_ENDINGS |
| is_dylib = suffix(temp_files[0][1]) in DYNAMICLIB_ENDINGS |
| is_ar = shared.Building.is_ar(temp_files[0][1]) |
| perform_link = not (is_bc or is_dylib) and is_ar |
| if perform_link: |
| logger.debug('linking: ' + str(linker_inputs)) |
| # force archive contents to all be included, if just archives, or if linking shared modules |
| force_archive_contents = all(t.endswith(STATICLIB_ENDINGS) for _, t in temp_files) or shared.Settings.LINKABLE |
| |
| # if EMCC_DEBUG=2 then we must link now, so the temp files are complete. |
| # if using the wasm backend, we might be using vanilla LLVM, which does not allow our fastcomp deferred linking opts. |
| # TODO: we could check if this is a fastcomp build, and still speed things up here |
| just_calculate = DEBUG != 2 and not shared.Settings.WASM_BACKEND |
| if shared.Settings.WASM_BACKEND: |
| # If LTO is enabled then use the -O opt level as the LTO level |
| if options.llvm_lto: |
| lto_level = options.opt_level |
| else: |
| lto_level = 0 |
| final = shared.Building.link_lld(linker_inputs, DEFAULT_FINAL, lto_level=lto_level) |
| else: |
| final = shared.Building.link(linker_inputs, DEFAULT_FINAL, force_archive_contents=force_archive_contents, just_calculate=just_calculate) |
| else: |
| logger.debug('skipping linking: ' + str(linker_inputs)) |
| if not LEAVE_INPUTS_RAW: |
| _, temp_file = temp_files[0] |
| _, input_file = input_files[0] |
| final = in_temp(target_basename + '.bc') |
| if temp_file != input_file: |
| shutil.move(temp_file, final) |
| else: |
| shutil.copyfile(temp_file, final) |
| else: |
| _, input_file = input_files[0] |
| final = in_temp(input_file) |
| shutil.copyfile(input_file, final) |
| |
| # exit block 'link' |
| log_time('link') |
| |
| if not shared.Settings.WASM_BACKEND: |
| with ToolchainProfiler.profile_block('post-link'): |
| if DEBUG: |
| logger.debug('saving intermediate processing steps to %s', shared.get_emscripten_temp_dir()) |
| if not LEAVE_INPUTS_RAW: |
| save_intermediate('basebc', 'bc') |
| |
| # Optimize, if asked to |
| if not LEAVE_INPUTS_RAW: |
| # remove LLVM debug if we are not asked for it |
| link_opts = [] |
| if not need_llvm_debug_info(options): |
| link_opts += ['-strip-debug'] |
| if not shared.Settings.ASSERTIONS: |
| link_opts += ['-disable-verify'] |
| else: |
| # when verifying, LLVM debug info has some tricky linking aspects, and llvm-link will |
| # disable the type map in that case. we added linking to opt, so we need to do |
| # something similar, which we can do with a param to opt |
| link_opts += ['-disable-debug-info-type-map'] |
| |
| if options.llvm_lto is not None and options.llvm_lto >= 2 and optimizing(options.llvm_opts): |
| logger.debug('running LLVM opts as pre-LTO') |
| final = shared.Building.llvm_opt(final, options.llvm_opts, DEFAULT_FINAL) |
| save_intermediate('opt', 'bc') |
| |
| # If we can LTO, do it before dce, since it opens up dce opportunities |
| if (not shared.Settings.LINKABLE) and options.llvm_lto and options.llvm_lto != 2: |
| if not shared.Building.can_inline(): |
| link_opts.append('-disable-inlining') |
| # add a manual internalize with the proper things we need to be kept alive during lto |
| link_opts += shared.Building.get_safe_internalize() + ['-std-link-opts'] |
| # execute it now, so it is done entirely before we get to the stage of legalization etc. |
| final = shared.Building.llvm_opt(final, link_opts, DEFAULT_FINAL) |
| save_intermediate('lto', 'bc') |
| link_opts = [] |
| else: |
| # At minimum remove dead functions etc., this potentially saves a |
| # lot in the size of the generated code (and the time to compile it) |
| link_opts += shared.Building.get_safe_internalize() + ['-globaldce'] |
| |
| if options.cfi: |
| if use_cxx: |
| link_opts.append("-wholeprogramdevirt") |
| link_opts.append("-lowertypetests") |
| |
| if AUTODEBUG: |
| # let llvm opt directly emit ll, to skip writing and reading all the bitcode |
| link_opts += ['-S'] |
| final = shared.Building.llvm_opt(final, link_opts, get_final() + '.link.ll') |
| save_intermediate('linktime', 'll') |
| else: |
| if len(link_opts) > 0: |
| final = shared.Building.llvm_opt(final, link_opts, DEFAULT_FINAL) |
| save_intermediate('linktime', 'bc') |
| if options.save_bc: |
| shutil.copyfile(final, options.save_bc) |
| |
| # Prepare .ll for Emscripten |
| if LEAVE_INPUTS_RAW: |
| assert len(input_files) == 1 |
| if options.save_bc: |
| save_intermediate('ll', 'll') |
| |
| if AUTODEBUG: |
| logger.debug('autodebug') |
| next = get_final() + '.ad.ll' |
| run_process([shared.PYTHON, shared.AUTODEBUGGER, final, next]) |
| final = next |
| save_intermediate('autodebug', 'll') |
| |
| assert not isinstance(final, list), 'we must have linked the final files, if linking was deferred, by this point' |
| |
| # exit block 'post-link' |
| log_time('post-link') |
| |
| with ToolchainProfiler.profile_block('emscript'): |
| # Emscripten |
| logger.debug('LLVM => JS') |
| if options.memory_init_file: |
| shared.Settings.MEM_INIT_METHOD = 1 |
| else: |
| assert shared.Settings.MEM_INIT_METHOD != 1 |
| |
| if embed_memfile(options): |
| shared.Settings.SUPPORT_BASE64_EMBEDDING = 1 |
| |
| final = do_emscripten(final, shared.replace_or_append_suffix(target, '.mem')) |
| save_intermediate('original') |
| |
| if shared.Settings.WASM_BACKEND: |
| # we also received wat and wasm at this stage |
| temp_basename = unsuffixed(final) |
| wasm_temp = temp_basename + '.wasm' |
| shutil.move(wasm_temp, wasm_binary_target) |
| if use_source_map(options): |
| shutil.move(wasm_temp + '.map', wasm_source_map_target) |
| |
| if shared.Settings.CYBERDWARF: |
| cd_target = final + '.cd' |
| shutil.move(cd_target, shared.replace_or_append_suffix(target, '.cd')) |
| |
| # exit block 'emscript' |
| log_time('emscript (llvm => executable code)') |
| |
| with ToolchainProfiler.profile_block('source transforms'): |
| # Embed and preload files |
| if len(options.preload_files) or len(options.embed_files): |
| |
| # Also, MEMFS is not aware of heap resizing feature in wasm, so if MEMFS and memory growth are used together, force |
| # no_heap_copy to be enabled. |
| if shared.Settings.ALLOW_MEMORY_GROWTH and not options.no_heap_copy: |
| logger.info('Enabling --no-heap-copy because -s ALLOW_MEMORY_GROWTH=1 is being used with file_packager.py (pass --no-heap-copy to suppress this notification)') |
| options.no_heap_copy = True |
| |
| logger.debug('setting up files') |
| file_args = ['--from-emcc', '--export-name=' + shared.Settings.EXPORT_NAME] |
| if len(options.preload_files): |
| file_args.append('--preload') |
| file_args += options.preload_files |
| if len(options.embed_files): |
| file_args.append('--embed') |
| file_args += options.embed_files |
| if len(options.exclude_files): |
| file_args.append('--exclude') |
| file_args += options.exclude_files |
| if options.use_preload_cache: |
| file_args.append('--use-preload-cache') |
| if options.no_heap_copy: |
| file_args.append('--no-heap-copy') |
| if shared.Settings.LZ4: |
| file_args.append('--lz4') |
| if options.use_preload_plugins: |
| file_args.append('--use-preload-plugins') |
| file_code = run_process([shared.PYTHON, shared.FILE_PACKAGER, unsuffixed(target) + '.data'] + file_args, stdout=PIPE).stdout |
| options.pre_js = file_code + options.pre_js |
| |
| # Apply pre and postjs files |
| if options.pre_js or options.post_js: |
| logger.debug('applying pre/postjses') |
| src = open(final).read() |
| final += '.pp.js' |
| if WINDOWS: # Avoid duplicating \r\n to \r\r\n when writing out. |
| if options.pre_js: |
| options.pre_js = options.pre_js.replace('\r\n', '\n') |
| if options.post_js: |
| options.post_js = options.post_js.replace('\r\n', '\n') |
| with open(final, 'w') as f: |
| # pre-js code goes right after the Module integration code (so it |
| # can use Module), we have a marker for it |
| f.write(src.replace('// {{PRE_JSES}}', options.pre_js)) |
| f.write(options.post_js) |
| options.pre_js = src = options.post_js = None |
| save_intermediate('pre-post') |
| |
| # Apply a source code transformation, if requested |
| if options.js_transform: |
| shutil.copyfile(final, final + '.tr.js') |
| final += '.tr.js' |
| posix = not shared.WINDOWS |
| logger.debug('applying transform: %s', options.js_transform) |
| shared.check_call(shared.Building.remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final)])) |
| save_intermediate('transformed') |
| |
| js_transform_tempfiles = [final] |
| |
| # exit block 'source transforms' |
| log_time('source transforms') |
| |
| with ToolchainProfiler.profile_block('memory initializer'): |
| memfile = None |
| if (not shared.Settings.WASM_BACKEND and (shared.Settings.MEM_INIT_METHOD > 0 or embed_memfile(options))) or \ |
| (shared.Settings.WASM_BACKEND and not shared.Settings.MEM_INIT_IN_WASM): |
| memfile = shared.replace_or_append_suffix(target, '.mem') |
| |
| if memfile: |
| if shared.Settings.WASM_BACKEND: |
| # For the wasm backend, we don't have any memory info in JS. All we need to do |
| # is set the memory initializer url. |
| src = open(final).read() |
| src = src.replace('var memoryInitializer = null;', 'var memoryInitializer = "%s";' % os.path.basename(memfile)) |
| open(final + '.mem.js', 'w').write(src) |
| final += '.mem.js' |
| else: |
| # Non-wasm backend path: Strip the memory initializer out of the asmjs file |
| shared.try_delete(memfile) |
| |
| def parse_mem_bytes(s): |
| membytes = [int(x or '0') for x in s.split(',')] |
| while membytes and membytes[-1] == 0: |
| membytes.pop() |
| return membytes |
| |
| def repl(m): |
| # handle chunking of the memory initializer |
| s = m.group(1) |
| if len(s) == 0: |
| return '' # don't emit 0-size ones |
| membytes = parse_mem_bytes(s) |
| if not membytes: |
| return '' |
| if shared.Settings.MEM_INIT_METHOD == 2: |
| # memory initializer in a string literal |
| return "memoryInitializer = '%s';" % shared.JS.generate_string_initializer(membytes) |
| open(memfile, 'wb').write(bytearray(membytes)) |
| if DEBUG: |
| # Copy into temp dir as well, so can be run there too |
| shared.safe_copy(memfile, os.path.join(shared.get_emscripten_temp_dir(), os.path.basename(memfile))) |
| if not shared.Settings.WASM or not shared.Settings.MEM_INIT_IN_WASM: |
| return 'memoryInitializer = "%s";' % shared.JS.get_subresource_location(memfile, embed_memfile(options)) |
| else: |
| return '' |
| |
| if shared.Settings.MINIMAL_RUNTIME and (shared.Settings.MEM_INIT_METHOD == 0 or shared.Settings.SINGLE_FILE): |
| # In MINIMAL_RUNTIME emit the base64 memory initializer directly into a HEAPU8.set() statement. |
| mem_init_data = re.search(shared.JS.memory_initializer_pattern, open(final).read()) |
| src = open(final).read().replace('{{{ BASE64_MEMORY_INITIALIZER }}}', base64_encode(bytearray(parse_mem_bytes(mem_init_data.group(1))))) |
| src = re.sub(shared.JS.memory_initializer_pattern, '', src) |
| else: |
| src = re.sub(shared.JS.memory_initializer_pattern, repl, open(final).read(), count=1) |
| open(final + '.mem.js', 'w').write(src) |
| final += '.mem.js' |
| src = None |
| js_transform_tempfiles[-1] = final # simple text substitution preserves comment line number mappings |
| if os.path.exists(memfile): |
| save_intermediate('meminit') |
| logger.debug('wrote memory initialization to %s', memfile) |
| else: |
| logger.debug('did not see memory initialization') |
| |
| if shared.Settings.USE_PTHREADS: |
| target_dir = os.path.dirname(os.path.abspath(target)) |
| worker_output = os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE) |
| with open(worker_output, 'w') as f: |
| f.write(shared.read_and_preprocess(shared.path_from_root('src', 'worker.js'), expand_macros=True)) |
| |
| # Minify the worker.js file in optimized builds |
| if (options.opt_level >= 1 or options.shrink_level >= 1) and not options.debug_level: |
| minified_worker = shared.Building.js_optimizer_no_asmjs(worker_output, ['minifyWhitespace'], acorn=True, return_output=True) |
| open(worker_output, 'w').write(minified_worker) |
| |
| # Generate the fetch.js worker script for multithreaded emscripten_fetch() support if targeting pthreads. |
| if shared.Settings.USE_FETCH_WORKER: |
| shared.make_fetch_worker(final, shared.Settings.FETCH_WORKER_FILE) |
| |
| # exit block 'memory initializer' |
| log_time('memory initializer') |
| |
| optimizer = JSOptimizer( |
| target=target, |
| options=options, |
| js_transform_tempfiles=js_transform_tempfiles, |
| in_temp=in_temp, |
| ) |
| with ToolchainProfiler.profile_block('js opts'): |
| # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing |
| if shared.Settings.DEAD_FUNCTIONS: |
| optimizer.queue += ['eliminateDeadFuncs'] |
| optimizer.extra_info['dead_functions'] = shared.Settings.DEAD_FUNCTIONS |
| |
| if options.opt_level >= 1 and options.js_opts: |
| logger.debug('running js post-opts') |
| |
| if DEBUG == 2: |
| # Clean up the syntax a bit |
| optimizer.queue += ['noop'] |
| |
| def get_eliminate(): |
| if shared.Settings.ALLOW_MEMORY_GROWTH: |
| return 'eliminateMemSafe' |
| else: |
| return 'eliminate' |
| |
| if options.opt_level >= 2: |
| optimizer.queue += [get_eliminate()] |
| |
| if shared.Settings.AGGRESSIVE_VARIABLE_ELIMINATION: |
| # note that this happens before registerize/minification, which can obfuscate the name of 'label', which is tricky |
| optimizer.queue += ['aggressiveVariableElimination'] |
| |
| optimizer.queue += ['simplifyExpressions'] |
| |
| if shared.Settings.EMTERPRETIFY: |
| # emterpreter code will not run through a JS optimizing JIT, do more work ourselves |
| optimizer.queue += ['localCSE'] |
| |
| if shared.Settings.EMTERPRETIFY: |
| # add explicit label setting, as we will run aggressiveVariableElimination late, *after* 'label' is no longer notable by name |
| optimizer.queue += ['safeLabelSetting'] |
| |
| if options.opt_level >= 1 and options.js_opts: |
| if options.opt_level >= 2: |
| # simplify ifs if it is ok to make the code somewhat unreadable, |
| # with commaified code breaks late aggressive variable elimination) |
| # do not do this with binaryen, as commaifying confuses binaryen call type detection (FIXME, in theory, but unimportant) |
| debugging = options.debug_level == 0 or options.profiling |
| if shared.Settings.SIMPLIFY_IFS and debugging and not shared.Settings.WASM: |
| optimizer.queue += ['simplifyIfs'] |
| |
| if shared.Settings.PRECISE_F32: |
| optimizer.queue += ['optimizeFrounds'] |
| |
| if options.js_opts: |
| if shared.Settings.SAFE_HEAP and not shared.Building.is_wasm_only(): |
| optimizer.queue += ['safeHeap'] |
| |
| if options.opt_level >= 2 and options.debug_level < 3: |
| if options.opt_level >= 3 or options.shrink_level > 0: |
| optimizer.queue += ['registerizeHarder'] |
| else: |
| optimizer.queue += ['registerize'] |
| |
| # NOTE: Important that this comes after registerize/registerizeHarder |
| if shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS and options.opt_level >= 2: |
| optimizer.flush() |
| shared.Building.eliminate_duplicate_funcs(final) |
| save_intermediate('dfe') |
| |
| if shared.Settings.EVAL_CTORS and options.memory_init_file and not use_source_map(options) and not shared.Settings.WASM: |
| optimizer.flush() |
| shared.Building.eval_ctors(final, memfile) |
| save_intermediate('eval-ctors') |
| |
| if options.js_opts: |
| # some compilation modes require us to minify later or not at all |
| if not shared.Settings.EMTERPRETIFY and not shared.Settings.WASM: |
| optimizer.do_minify() |
| |
| if options.opt_level >= 2: |
| optimizer.queue += ['asmLastOpts'] |
| |
| if shared.Settings.FINALIZE_ASM_JS: |
| optimizer.queue += ['last'] |
| |
| optimizer.flush() |
| |
| if options.use_closure_compiler == 2 and not shared.Settings.WASM_BACKEND: |
| optimizer.flush() |
| |
| logger.debug('running closure') |
| # no need to add this to js_transform_tempfiles, because closure and |
| # debug_level > 0 are never simultaneously true |
| final = shared.Building.closure_compiler(final, pretty=options.debug_level >= 1, |
| extra_closure_args=options.closure_args) |
| save_intermediate('closure') |
| |
| log_time('js opts') |
| |
| with ToolchainProfiler.profile_block('final emitting'): |
| if shared.Settings.EMTERPRETIFY: |
| emterpretify(js_target, optimizer, options) |
| |
| # Remove some trivial whitespace |
| # TODO: do not run when compress has already been done on all parts of the code |
| # src = open(final).read() |
| # src = re.sub(r'\n+[ \n]*\n+', '\n', src) |
| # open(final, 'w').write(src) |
| |
| # Bundle symbol data in with the cyberdwarf file |
| if shared.Settings.CYBERDWARF: |
| run_process([shared.PYTHON, shared.path_from_root('tools', 'emdebug_cd_merger.py'), shared.replace_or_append_suffix(target, '.cd'), shared.replace_or_append_suffix(target, '.symbols')]) |
| |
| if use_source_map(options) and not shared.Settings.WASM: |
| emit_js_source_maps(target, optimizer.js_transform_tempfiles) |
| |
| # track files that will need native eols |
| generated_text_files_with_native_eols = [] |
| |
| if (options.separate_asm or shared.Settings.WASM) and not shared.Settings.WASM_BACKEND: |
| separate_asm_js(final, asm_target) |
| if not shared.Settings.SINGLE_FILE: |
| generated_text_files_with_native_eols += [asm_target] |
| |
| if shared.Settings.WASM: |
| do_binaryen(target, asm_target, options, memfile, wasm_binary_target, |
| wasm_text_target, wasm_source_map_target, misc_temp_files, |
| optimizer) |
| |
| if shared.Settings.MODULARIZE: |
| modularize() |
| |
| module_export_name_substitution() |
| |
| # Run a final regex 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 shared.Settings.MINIMAL_RUNTIME == 2 and shared.Settings.USE_CLOSURE_COMPILER and options.debug_level == 0 and not shared.Settings.SINGLE_FILE: |
| # Process .js runtime file |
| shared.run_process([shared.PYTHON, shared.path_from_root('tools', 'hacky_postprocess_around_closure_limitations.py'), final]) |
| # Process .asm.js file |
| if not shared.Settings.WASM: |
| shared.run_process([shared.PYTHON, shared.path_from_root('tools', 'hacky_postprocess_around_closure_limitations.py'), asm_target]) |
| |
| # The JS is now final. Move it to its final location |
| shutil.move(final, js_target) |
| |
| if not shared.Settings.SINGLE_FILE: |
| generated_text_files_with_native_eols += [js_target] |
| |
| # If we were asked to also generate HTML, do that |
| if final_suffix == '.html': |
| generate_html(target, options, js_target, target_basename, |
| asm_target, wasm_binary_target, |
| memfile, optimizer) |
| else: |
| if options.proxy_to_worker: |
| generate_worker_js(target, js_target, target_basename) |
| |
| if embed_memfile(options) and memfile: |
| shared.try_delete(memfile) |
| |
| for f in generated_text_files_with_native_eols: |
| tools.line_endings.convert_line_endings_in_file(f, os.linesep, options.output_eol) |
| log_time('final emitting') |
| # exit block 'final emitting' |
| |
| finally: |
| if DEBUG: |
| shared.Cache.release_cache_lock() |
| |
| if DEBUG: |
| logger.debug('total time: %.2f seconds', (time.time() - start_time)) |
| |
| return 0 |
| |
| |
| def parse_args(newargs): |
| options = EmccOptions() |
| settings_changes = [] |
| should_exit = False |
| |
| def check_bad_eq(arg): |
| if '=' in arg: |
| exit_with_error('Invalid parameter (do not use "=" with "--" options)') |
| |
| for i in range(len(newargs)): |
| # On Windows Vista (and possibly others), excessive spaces in the command line |
| # leak into the items in this array, so trim e.g. 'foo.cpp ' -> 'foo.cpp' |
| newargs[i] = newargs[i].strip() |
| if newargs[i].startswith('-O'): |
| # Let -O default to -O2, which is what gcc does. |
| options.requested_level = newargs[i][2:] or '2' |
| if options.requested_level == 's': |
| options.llvm_opts = ['-Os'] |
| options.requested_level = 2 |
| options.shrink_level = 1 |
| settings_changes.append('INLINING_LIMIT=50') |
| elif options.requested_level == 'z': |
| options.llvm_opts = ['-Oz'] |
| options.requested_level = 2 |
| options.shrink_level = 2 |
| settings_changes.append('INLINING_LIMIT=25') |
| options.opt_level = validate_arg_level(options.requested_level, 3, 'Invalid optimization level: ' + newargs[i], clamp=True) |
| elif newargs[i].startswith('--js-opts'): |
| check_bad_eq(newargs[i]) |
| options.js_opts = int(newargs[i + 1]) |
| if options.js_opts: |
| options.force_js_opts = True |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--llvm-opts'): |
| check_bad_eq(newargs[i]) |
| options.llvm_opts = parse_value(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--llvm-lto'): |
| check_bad_eq(newargs[i]) |
| options.llvm_lto = int(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--closure-args'): |
| check_bad_eq(newargs[i]) |
| args = newargs[i + 1] |
| options.closure_args += shlex.split(args) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--closure'): |
| check_bad_eq(newargs[i]) |
| options.use_closure_compiler = int(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--js-transform'): |
| check_bad_eq(newargs[i]) |
| options.js_transform = newargs[i + 1] |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--pre-js'): |
| check_bad_eq(newargs[i]) |
| options.pre_js += open(newargs[i + 1]).read() + '\n' |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--post-js'): |
| check_bad_eq(newargs[i]) |
| options.post_js += open(newargs[i + 1]).read() + '\n' |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--minify'): |
| check_bad_eq(newargs[i]) |
| assert newargs[i + 1] == '0', '0 is the only supported option for --minify; 1 has been deprecated' |
| options.debug_level = max(1, options.debug_level) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('-g'): |
| options.requested_debug = newargs[i] |
| requested_level = newargs[i][2:] or '3' |
| if is_int(requested_level): |
| # the -gX value is the debug level (-g1, -g2, etc.) |
| options.debug_level = validate_arg_level(requested_level, 4, 'Invalid debug level: ' + newargs[i]) |
| # if we don't need to preserve LLVM debug info, do not keep this flag |
| # for clang |
| if options.debug_level < 3: |
| newargs[i] = '' |
| else: |
| # until we support full DWARF info, limit the clang frontend to just |
| # emit line tables, which can be represented in source maps |
| newargs[i] = '-gline-tables-only' |
| else: |
| if requested_level.startswith('force_dwarf'): |
| # Force clang to generate full debug info using -g. Set the FULL_DWARF |
| # setting to avoid stripping it out later. |
| newargs[i] = '-g' |
| shared.Settings.FULL_DWARF = 1 |
| shared.warning('gforce_dwarf is a temporary option that will eventually disappear') |
| # a non-integer level can be something like -gline-tables-only. keep |
| # the flag for the clang frontend to emit the appropriate DWARF info. |
| # set the emscripten debug level to 3 so that we do not remove that |
| # debug info during link (during compile, this does not make a |
| # difference). |
| options.debug_level = 3 |
| elif newargs[i] == '-profiling' or newargs[i] == '--profiling': |
| options.debug_level = max(options.debug_level, 2) |
| options.profiling = True |
| newargs[i] = '' |
| elif newargs[i] == '-profiling-funcs' or newargs[i] == '--profiling-funcs': |
| options.profiling_funcs = True |
| newargs[i] = '' |
| elif newargs[i] == '--tracing' or newargs[i] == '--memoryprofiler': |
| if newargs[i] == '--memoryprofiler': |
| options.memory_profiler = True |
| options.tracing = True |
| newargs[i] = '' |
| newargs.append('-D__EMSCRIPTEN_TRACING__=1') |
| settings_changes.append("EMSCRIPTEN_TRACING=1") |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'library_trace.js')) |
| elif newargs[i] == '--emit-symbol-map': |
| options.emit_symbol_map = True |
| shared.Settings.EMIT_SYMBOL_MAP = 1 |
| newargs[i] = '' |
| elif newargs[i] == '--bind': |
| shared.Settings.EMBIND = 1 |
| newargs[i] = '' |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'embind', 'emval.js')) |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(shared.path_from_root('src', 'embind', 'embind.js')) |
| if options.default_cxx_std: |
| # Force C++11 for embind code, but only if user has not explicitly overridden a standard. |
| options.default_cxx_std = '-std=c++11' |
| elif newargs[i].startswith('-std=') or newargs[i].startswith('--std='): |
| # User specified a standard to use, clear Emscripten from specifying it. |
| options.default_cxx_std = None |
| elif newargs[i].startswith('--embed-file'): |
| check_bad_eq(newargs[i]) |
| options.embed_files.append(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--preload-file'): |
| check_bad_eq(newargs[i]) |
| options.preload_files.append(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--exclude-file'): |
| check_bad_eq(newargs[i]) |
| options.exclude_files.append(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--use-preload-cache'): |
| options.use_preload_cache = True |
| newargs[i] = '' |
| elif newargs[i].startswith('--no-heap-copy'): |
| options.no_heap_copy = True |
| newargs[i] = '' |
| elif newargs[i].startswith('--use-preload-plugins'): |
| options.use_preload_plugins = True |
| newargs[i] = '' |
| elif newargs[i] == '--ignore-dynamic-linking': |
| options.ignore_dynamic_linking = True |
| newargs[i] = '' |
| elif newargs[i] == '-v': |
| shared.PRINT_STAGES = True |
| shared.check_sanity(force=True) |
| elif newargs[i].startswith('--shell-file'): |
| check_bad_eq(newargs[i]) |
| options.shell_path = newargs[i + 1] |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--source-map-base'): |
| check_bad_eq(newargs[i]) |
| options.source_map_base = newargs[i + 1] |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i].startswith('--js-library'): |
| check_bad_eq(newargs[i]) |
| shared.Settings.SYSTEM_JS_LIBRARIES.append(os.path.abspath(newargs[i + 1])) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--remove-duplicates': |
| shared.warning('--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase') |
| newargs[i] = '' |
| elif newargs[i] == '--jcache': |
| logger.error('jcache is no longer supported') |
| newargs[i] = '' |
| elif newargs[i] == '--cache': |
| check_bad_eq(newargs[i]) |
| os.environ['EM_CACHE'] = os.path.normpath(newargs[i + 1]) |
| shared.reconfigure_cache() |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--clear-cache': |
| logger.info('clearing cache as requested by --clear-cache') |
| shared.Cache.erase() |
| shared.check_sanity(force=True) # this is a good time for a sanity check |
| should_exit = True |
| elif newargs[i] == '--clear-ports': |
| logger.info('clearing ports and cache as requested by --clear-ports') |
| system_libs.Ports.erase() |
| shared.Cache.erase() |
| shared.check_sanity(force=True) # this is a good time for a sanity check |
| should_exit = True |
| elif newargs[i] == '--show-ports': |
| system_libs.show_ports() |
| should_exit = True |
| elif newargs[i] == '--save-bc': |
| check_bad_eq(newargs[i]) |
| options.save_bc = newargs[i + 1] |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--memory-init-file': |
| check_bad_eq(newargs[i]) |
| options.memory_init_file = int(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--proxy-to-worker': |
| options.proxy_to_worker = True |
| newargs[i] = '' |
| elif newargs[i] == '--valid-abspath': |
| options.valid_abspaths.append(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--separate-asm': |
| options.separate_asm = True |
| newargs[i] = '' |
| elif newargs[i].startswith(('-I', '-L')): |
| options.path_name = newargs[i][2:] |
| if os.path.isabs(options.path_name) and not is_valid_abspath(options, options.path_name): |
| # Of course an absolute path to a non-system-specific library or header |
| # is fine, and you can ignore this warning. The danger are system headers |
| # that are e.g. x86 specific and nonportable. The emscripten bundled |
| # headers are modified to be portable, local system ones are generally not. |
| shared.WarningManager.warn( |
| 'ABSOLUTE_PATHS', '-I or -L of an absolute path "' + newargs[i] + |
| '" encountered. If this is to a local system header/library, it may ' |
| 'cause problems (local system files make sense for compiling natively ' |
| 'on your system, but not necessarily to JavaScript).') |
| elif newargs[i] == '--emrun': |
| options.emrun = True |
| newargs[i] = '' |
| elif newargs[i] == '--cpuprofiler': |
| options.cpu_profiler = True |
| newargs[i] = '' |
| elif newargs[i] == '--threadprofiler': |
| options.thread_profiler = True |
| settings_changes.append('PTHREADS_PROFILING=1') |
| newargs[i] = '' |
| elif newargs[i] == '-fno-exceptions': |
| settings_changes.append('DISABLE_EXCEPTION_CATCHING=1') |
| settings_changes.append('DISABLE_EXCEPTION_THROWING=1') |
| elif newargs[i] == '-fexceptions': |
| settings_changes.append('DISABLE_EXCEPTION_CATCHING=0') |
| settings_changes.append('DISABLE_EXCEPTION_THROWING=0') |
| elif newargs[i] == '--default-obj-ext': |
| newargs[i] = '' |
| options.default_object_extension = newargs[i + 1] |
| if not options.default_object_extension.startswith('.'): |
| options.default_object_extension = '.' + options.default_object_extension |
| newargs[i + 1] = '' |
| elif newargs[i].startswith("-fsanitize=cfi"): |
| options.cfi = True |
| elif newargs[i] == "--output_eol": |
| if newargs[i + 1].lower() == 'windows': |
| options.output_eol = '\r\n' |
| elif newargs[i + 1].lower() == 'linux': |
| options.output_eol = '\n' |
| else: |
| exit_with_error('Invalid value "' + newargs[i + 1] + '" to --output_eol!') |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--generate-config': |
| optarg = newargs[i + 1] |
| path = os.path.expanduser(optarg) |
| if os.path.exists(path): |
| exit_with_error('File ' + optarg + ' passed to --generate-config already exists!') |
| else: |
| shared.generate_config(optarg) |
| should_exit = True |
| # Record SIMD setting because it controls whether the autovectorizer runs |
| elif newargs[i] == '-msimd128': |
| settings_changes.append('SIMD=1') |
| elif newargs[i] == '-mno-simd128': |
| settings_changes.append('SIMD=0') |
| # Record USE_PTHREADS setting because it controls whether --shared-memory is passed to lld |
| elif newargs[i] == '-pthread': |
| settings_changes.append('USE_PTHREADS=1') |
| elif newargs[i] in ('-fno-diagnostics-color', '-fdiagnostics-color=never'): |
| colored_logger.disable() |
| elif newargs[i] == '-no-canonical-prefixes': |
| options.expand_symlinks = False |
| elif newargs[i] == '-Werror': |
| shared.Settings.WARNINGS_ARE_ERRORS = 1 |
| elif newargs[i] == '-fno-rtti': |
| shared.Settings.USE_RTTI = 0 |
| |
| if should_exit: |
| sys.exit(0) |
| |
| newargs = [arg for arg in newargs if arg] |
| return options, settings_changes, newargs |
| |
| |
| def emterpretify(js_target, optimizer, options): |
| global final |
| optimizer.flush('pre-emterpretify') |
| logger.debug('emterpretifying') |
| blacklist = shared.Settings.EMTERPRETIFY_BLACKLIST |
| whitelist = shared.Settings.EMTERPRETIFY_WHITELIST |
| synclist = shared.Settings.EMTERPRETIFY_SYNCLIST |
| if type(blacklist) == list: |
| blacklist = json.dumps(blacklist) |
| if type(whitelist) == list: |
| whitelist = json.dumps(whitelist) |
| if type(synclist) == list: |
| synclist = json.dumps(synclist) |
| |
| args = [shared.PYTHON, |
| shared.path_from_root('tools', 'emterpretify.py'), |
| js_target, |
| final + '.em.js', |
| blacklist, |
| whitelist, |
| synclist, |
| str(shared.Settings.SWAPPABLE_ASM_MODULE)] |
| if shared.Settings.EMTERPRETIFY_ASYNC: |
| args += ['ASYNC=1'] |
| if shared.Settings.EMTERPRETIFY_ADVISE: |
| args += ['ADVISE=1'] |
| if options.profiling or options.profiling_funcs: |
| args += ['PROFILING=1'] |
| if shared.Settings.ASSERTIONS: |
| args += ['ASSERTIONS=1'] |
| if shared.Settings.PRECISE_F32: |
| args += ['FROUND=1'] |
| if shared.Settings.ALLOW_MEMORY_GROWTH: |
| args += ['MEMORY_SAFE=1'] |
| if shared.Settings.EMTERPRETIFY_FILE: |
| args += ['FILE="' + shared.Settings.EMTERPRETIFY_FILE + '"'] |
| |
| try: |
| # move temp js to final position, alongside its mem init file |
| shutil.move(final, js_target) |
| shared.check_call(args) |
| finally: |
| shared.try_delete(js_target) |
| |
| final = final + '.em.js' |
| |
| if shared.Settings.EMTERPRETIFY_ADVISE: |
| logger.warning('halting compilation due to EMTERPRETIFY_ADVISE') |
| sys.exit(0) |
| |
| # minify (if requested) after emterpreter processing, and finalize output |
| logger.debug('finalizing emterpreted code') |
| shared.Settings.FINALIZE_ASM_JS = 1 |
| if not shared.Settings.WASM: |
| optimizer.do_minify() |
| optimizer.queue += ['last'] |
| optimizer.flush('finalizing-emterpreted-code') |
| |
| # finalize the original as well, if we will be swapping it in (TODO: add specific option for this) |
| if shared.Settings.SWAPPABLE_ASM_MODULE: |
| real = final |
| original = js_target + '.orig.js' # the emterpretify tool saves the original here |
| final = original |
| logger.debug('finalizing original (non-emterpreted) code at ' + final) |
| if not shared.Settings.WASM: |
| optimizer.do_minify() |
| optimizer.queue += ['last'] |
| optimizer.flush('finalizing-original-code') |
| safe_copy(final, original) |
| final = real |
| |
| |
| def emit_js_source_maps(target, js_transform_tempfiles): |
| logger.debug('generating source maps') |
| jsrun.run_js_tool(shared.path_from_root('tools', 'source-maps', 'sourcemapper.js'), |
| shared.NODE_JS, js_transform_tempfiles + |
| ['--sourceRoot', os.getcwd(), |
| '--mapFileBaseName', target, |
| '--offset', '0']) |
| |
| |
| def separate_asm_js(final, asm_target): |
| """Separate out the asm.js code, if asked. Or, if necessary for another option""" |
| logger.debug('separating asm') |
| shared.check_call([shared.PYTHON, shared.path_from_root('tools', 'separate_asm.py'), final, asm_target, final, shared.Settings.SEPARATE_ASM_MODULE_NAME]) |
| |
| |
| def do_binaryen(target, asm_target, options, memfile, wasm_binary_target, |
| wasm_text_target, wasm_source_map_target, misc_temp_files, |
| optimizer): |
| global final |
| logger.debug('using binaryen') |
| if use_source_map(options) and not shared.Settings.SOURCE_MAP_BASE: |
| logger.warning("Wasm source map won't be usable in a browser without --source-map-base") |
| binaryen_bin = shared.Building.get_binaryen_bin() |
| # whether we need to emit -g (function name debug info) in the final wasm |
| debug_info = options.debug_level >= 2 or options.profiling_funcs |
| # whether we need to emit -g in the intermediate binaryen invocations (but not necessarily at the very end). |
| # this is necessary for emitting a symbol map at the end. |
| intermediate_debug_info = bool(debug_info or options.emit_symbol_map or shared.Settings.ASYNCIFY_WHITELIST or shared.Settings.ASYNCIFY_BLACKLIST) |
| emit_symbol_map = options.emit_symbol_map or shared.Settings.CYBERDWARF |
| # finish compiling to WebAssembly, using asm2wasm, if we didn't already emit WebAssembly directly using the wasm backend. |
| if not shared.Settings.WASM_BACKEND: |
| if DEBUG: |
| # save the asm.js input |
| shared.Building.save_intermediate(asm_target, 'asmjs.js') |
| cmd = [os.path.join(binaryen_bin, 'asm2wasm'), asm_target, '--total-memory=' + str(shared.Settings.TOTAL_MEMORY)] |
| if shared.Settings.BINARYEN_TRAP_MODE not in ('js', 'clamp', 'allow'): |
| exit_with_error('invalid BINARYEN_TRAP_MODE value: ' + shared.Settings.BINARYEN_TRAP_MODE + ' (should be js/clamp/allow)') |
| cmd += ['--trap-mode=' + shared.Settings.BINARYEN_TRAP_MODE] |
| if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS: |
| cmd += ['--ignore-implicit-traps'] |
| # pass optimization level to asm2wasm (if not optimizing, or which passes we should run was overridden, do not optimize) |
| if options.opt_level > 0: |
| cmd.append(shared.Building.opt_level_to_str(options.opt_level, options.shrink_level)) |
| # import mem init file if it exists, and if we will not be using asm.js as a binaryen method (as it needs the mem init file, of course) |
| mem_file_exists = options.memory_init_file and os.path.exists(memfile) |
| import_mem_init = mem_file_exists and shared.Settings.MEM_INIT_IN_WASM |
| if import_mem_init: |
| cmd += ['--mem-init=' + memfile] |
| if not shared.Settings.RELOCATABLE: |
| cmd += ['--mem-base=' + str(shared.Settings.GLOBAL_BASE)] |
| # various options imply that the imported table may not be the exact size as |
| # the wasm module's own table segments |
| if shared.Settings.RELOCATABLE or shared.Settings.RESERVED_FUNCTION_POINTERS > 0 or shared.Settings.EMULATED_FUNCTION_POINTERS: |
| cmd += ['--table-max=-1'] |
| if shared.Settings.SIDE_MODULE: |
| cmd += ['--mem-max=-1'] |
| elif shared.Settings.WASM_MEM_MAX >= 0: |
| cmd += ['--mem-max=' + str(shared.Settings.WASM_MEM_MAX)] |
| if shared.Settings.LEGALIZE_JS_FFI != 1: |
| cmd += ['--no-legalize-javascript-ffi'] |
| if shared.Building.is_wasm_only(): |
| cmd += ['--wasm-only'] # this asm.js is code not intended to run as asm.js, it is only ever going to be wasm, an can contain special fastcomp-wasm support |
| if shared.Settings.USE_PTHREADS: |
| cmd += ['--enable-threads'] |
| if intermediate_debug_info: |
| cmd += ['-g'] |
| if emit_symbol_map: |
| cmd += ['--symbolmap=' + shared.replace_or_append_suffix(target, '.symbols')] |
| # we prefer to emit a binary, as it is more efficient. however, when we |
| # want full debug info support (not just function names), then we must |
| # emit text (at least until wasm gains support for debug info in binaries) |
| target_binary = options.debug_level < 3 |
| if target_binary: |
| cmd += ['-o', wasm_binary_target] |
| else: |
| cmd += ['-o', wasm_text_target, '-S'] |
| cmd += shared.Building.get_binaryen_feature_flags() |
| logger.debug('asm2wasm (asm.js => WebAssembly): ' + ' '.join(cmd)) |
| TimeLogger.update() |
| shared.check_call(cmd) |
| |
| if not target_binary: |
| cmd = [os.path.join(binaryen_bin, 'wasm-as'), wasm_text_target, '-o', wasm_binary_target, '--all-features', '--disable-bulk-memory'] |
| if intermediate_debug_info: |
| cmd += ['-g'] |
| if use_source_map(options): |
| cmd += ['--source-map=' + wasm_source_map_target] |
| cmd += ['--source-map-url=' + options.source_map_base + os.path.basename(wasm_binary_target) + '.map'] |
| logger.debug('wasm-as (text => binary): ' + ' '.join(cmd)) |
| shared.check_call(cmd) |
| if import_mem_init: |
| # remove the mem init file in later processing; it does not need to be prefetched in the html, etc. |
| if DEBUG: |
| safe_move(memfile, os.path.join(shared.get_emscripten_temp_dir(), os.path.basename(memfile))) |
| else: |
| os.unlink(memfile) |
| log_time('asm2wasm') |
| if options.binaryen_passes: |
| if '--post-emscripten' in options.binaryen_passes: |
| # the value of the sbrk pointer has been computed by the JS compiler, and we can apply it in the wasm |
| # (we can't add this value when we placed post-emscripten in the proper position in the list of |
| # passes because that was before the value was computed) |
| options.binaryen_passes += ['--pass-arg=emscripten-sbrk-ptr@%d' % shared.Settings.DYNAMICTOP_PTR] |
| if shared.Settings.STANDALONE_WASM: |
| options.binaryen_passes += ['--pass-arg=emscripten-sbrk-val@%d' % shared.Settings.DYNAMIC_BASE] |
| shared.Building.save_intermediate(wasm_binary_target, 'pre-byn.wasm') |
| args = options.binaryen_passes |
| shared.Building.run_wasm_opt(wasm_binary_target, |
| wasm_binary_target, |
| args=args, |
| debug=intermediate_debug_info) |
| if shared.Settings.BINARYEN_SCRIPTS: |
| binaryen_scripts = os.path.join(shared.BINARYEN_ROOT, 'scripts') |
| script_env = os.environ.copy() |
| root_dir = os.path.abspath(os.path.dirname(__file__)) |
| if script_env.get('PYTHONPATH'): |
| script_env['PYTHONPATH'] += ':' + root_dir |
| else: |
| script_env['PYTHONPATH'] = root_dir |
| for script in shared.Settings.BINARYEN_SCRIPTS.split(','): |
| logger.debug('running binaryen script: ' + script) |
| shared.check_call([shared.PYTHON, os.path.join(binaryen_scripts, script), final, wasm_text_target], env=script_env) |
| if shared.Settings.EVAL_CTORS: |
| shared.Building.save_intermediate(wasm_binary_target, 'pre-ctors.wasm') |
| shared.Building.eval_ctors(final, wasm_binary_target, binaryen_bin, debug_info=intermediate_debug_info) |
| |
| # after generating the wasm, do some final operations |
| if shared.Settings.SIDE_MODULE and not shared.Settings.WASM_BACKEND: |
| wso = shared.WebAssembly.make_shared_library(final, wasm_binary_target, shared.Settings.RUNTIME_LINKED_LIBS) |
| # replace the wasm binary output with the dynamic library. |
| # TODO: use a specific suffix for such files? |
| shutil.move(wso, wasm_binary_target) |
| if not DEBUG: |
| os.unlink(asm_target) # we don't need the asm.js, it can just confuse |
| |
| # after generating the wasm, do some final operations |
| if shared.Settings.EMIT_EMSCRIPTEN_METADATA: |
| shared.WebAssembly.add_emscripten_metadata(final, wasm_binary_target) |
| |
| if shared.Settings.SIDE_MODULE: |
| sys.exit(0) # and we are done. |
| |
| # pthreads memory growth requires some additional JS fixups |
| if shared.Settings.USE_PTHREADS and shared.Settings.ALLOW_MEMORY_GROWTH: |
| final = shared.Building.apply_wasm_memory_growth(final) |
| |
| if options.opt_level >= 2 and options.debug_level <= 2: |
| # minify the JS |
| optimizer.do_minify() # calculate how to minify |
| save_intermediate_with_wasm('preclean', wasm_binary_target) |
| final = shared.Building.minify_wasm_js(js_file=final, |
| wasm_file=wasm_binary_target, |
| expensive_optimizations=will_metadce(options), |
| minify_whitespace=optimizer.minify_whitespace, |
| debug_info=intermediate_debug_info) |
| save_intermediate_with_wasm('postclean', wasm_binary_target) |
| |
| if shared.Settings.ASYNCIFY_LAZY_LOAD_CODE: |
| if not shared.Settings.ASYNCIFY: |
| exit_with_error('ASYNCIFY_LAZY_LOAD_CODE requires ASYNCIFY') |
| shared.Building.asyncify_lazy_load_code(wasm_binary_target, options, debug=intermediate_debug_info) |
| |
| def preprocess_wasm2js_script(): |
| wasm2js = read_and_preprocess(shared.path_from_root('src', 'wasm2js.js')) |
| # We do not currently have a setup to preprocess {{{ }}} settings in user scripts, so manually |
| # expand the settings that wasm2js.js actually uses. |
| wasm2js = wasm2js.replace('{{{ WASM_PAGE_SIZE }}}', '65536') |
| for opt in ['RESERVED_FUNCTION_POINTERS']: |
| wasm2js = wasm2js.replace('{{{ %s }}}' % opt, str(shared.Settings.get(opt))) |
| for opt in ['WASM_TABLE_SIZE']: |
| wasm2js = wasm2js.replace("{{{ getQuoted('%s') }}}" % opt, str(shared.Settings.get(opt))) |
| return wasm2js |
| |
| def run_closure_compiler(final): |
| final = shared.Building.closure_compiler(final, pretty=not optimizer.minify_whitespace, |
| extra_closure_args=options.closure_args) |
| save_intermediate_with_wasm('closure', wasm_binary_target) |
| return final |
| |
| if options.use_closure_compiler: |
| final = run_closure_compiler(final) |
| |
| symbols_file = target + '.symbols' if options.emit_symbol_map else None |
| |
| if shared.Settings.WASM2JS: |
| if shared.Settings.WASM == 2: |
| wasm2js_template = wasm_binary_target + '.js' |
| open(wasm2js_template, 'w').write(preprocess_wasm2js_script()) |
| else: |
| wasm2js_template = final |
| |
| wasm2js = shared.Building.wasm2js(wasm2js_template, |
| wasm_binary_target, |
| opt_level=options.opt_level, |
| minify_whitespace=optimizer.minify_whitespace, |
| use_closure_compiler=options.use_closure_compiler, |
| debug_info=intermediate_debug_info, |
| symbols_file=symbols_file) |
| if shared.Settings.WASM == 2: |
| shutil.copyfile(wasm2js, wasm2js_template) |
| shared.try_delete(wasm2js) |
| |
| if shared.Settings.WASM != 2: |
| final = wasm2js |
| |
| save_intermediate('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 wasm2js handles the symbol map itself (as it manipulates and then |
| # replaces the wasm with js) |
| if options.emit_symbol_map and not shared.Settings.WASM2JS: |
| shared.Building.handle_final_wasm_symbols(wasm_file=wasm_binary_target, symbols_file=symbols_file, debug_info=debug_info) |
| save_intermediate_with_wasm('symbolmap', wasm_binary_target) |
| |
| # replace placeholder strings with correct subresource locations |
| if shared.Settings.SINGLE_FILE: |
| js = open(final).read() |
| |
| if '{{{ WASM_BINARY_DATA }}}' in js: |
| js = js.replace('{{{ WASM_BINARY_DATA }}}', base64_encode(open(wasm_binary_target, 'rb').read())) |
| |
| for target, replacement_string, should_embed in ( |
| (wasm_binary_target, |
| shared.FilenameReplacementStrings.WASM_BINARY_FILE, |
| True), |
| (asm_target, |
| shared.FilenameReplacementStrings.ASMJS_CODE_FILE, |
| False), |
| ): |
| if should_embed and os.path.isfile(target): |
| js = js.replace(replacement_string, shared.JS.get_subresource_location(target)) |
| else: |
| js = js.replace(replacement_string, '') |
| shared.try_delete(target) |
| with open(final, 'w') as f: |
| f.write(js) |
| |
| # if targeting only JS, delete the redundant temporary |
| # .wasm output file |
| if shared.Settings.WASM == 1 and shared.Settings.WASM2JS: |
| shared.try_delete(wasm_binary_target) |
| |
| |
| def modularize(): |
| global final |
| logger.debug('Modularizing, assigning to var ' + shared.Settings.EXPORT_NAME) |
| src = open(final).read() |
| |
| # TODO: exports object generation for MINIMAL_RUNTIME |
| exports_object = '{}' if shared.Settings.MINIMAL_RUNTIME else shared.Settings.EXPORT_NAME |
| |
| src = ''' |
| function(%(EXPORT_NAME)s) { |
| %(EXPORT_NAME)s = %(EXPORT_NAME)s || {}; |
| |
| %(src)s |
| |
| return %(exports_object)s |
| } |
| ''' % { |
| 'EXPORT_NAME': shared.Settings.EXPORT_NAME, |
| 'src': src, |
| 'exports_object': exports_object |
| } |
| |
| if not shared.Settings.MODULARIZE_INSTANCE: |
| if shared.Settings.MINIMAL_RUNTIME and not shared.Settings.USE_PTHREADS: |
| # Single threaded MINIMAL_RUNTIME programs do not need access to |
| # document.currentScript, so a simple export declaration is enough. |
| src = 'var %s=%s' % (shared.Settings.EXPORT_NAME, src) |
| else: |
| script_url_node = "" |
| # When MODULARIZE this JS may be executed later, |
| # after document.currentScript is gone, so we save it. |
| # (when MODULARIZE_INSTANCE, an instance is created |
| # immediately anyhow, like in non-modularize mode) |
| # In EXPORT_ES6 + USE_PTHREADS the 'thread' is actually an ES6 module webworker running in strict mode, |
| # so doesn't have access to 'document'. In this case use 'import.meta' instead. |
| if shared.Settings.EXPORT_ES6 and shared.Settings.USE_ES6_IMPORT_META: |
| script_url = "import.meta.url" |
| else: |
| script_url = "typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined" |
| if shared.Settings.target_environment_may_be('node'): |
| script_url_node = "if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;" |
| src = ''' |
| var %(EXPORT_NAME)s = (function() { |
| var _scriptDir = %(script_url)s; |
| %(script_url_node)s |
| return (%(src)s); |
| })(); |
| ''' % { |
| 'EXPORT_NAME': shared.Settings.EXPORT_NAME, |
| 'script_url': script_url, |
| 'script_url_node': script_url_node, |
| 'src': src |
| } |
| else: |
| # Create the MODULARIZE_INSTANCE instance |
| # Note that we notice the global Module object, just like in normal |
| # non-MODULARIZE mode (while MODULARIZE has the user create the instances, |
| # and the user can decide whether to use Module there or something |
| # else etc.). |
| src = ''' |
| var %(EXPORT_NAME)s = (%(src)s)(typeof %(EXPORT_NAME)s === 'object' ? %(EXPORT_NAME)s : {}); |
| ''' % { |
| 'EXPORT_NAME': shared.Settings.EXPORT_NAME, |
| 'src': src |
| } |
| |
| final = final + '.modular.js' |
| with open(final, 'w') as f: |
| f.write(src) |
| |
| # Export using a UMD style export, or ES6 exports if selected |
| |
| if shared.Settings.EXPORT_ES6: |
| f.write('''export default %s;''' % shared.Settings.EXPORT_NAME) |
| elif not shared.Settings.MINIMAL_RUNTIME: |
| f.write('''if (typeof exports === 'object' && typeof module === 'object') |
| module.exports = %(EXPORT_NAME)s; |
| else if (typeof define === 'function' && define['amd']) |
| define([], function() { return %(EXPORT_NAME)s; }); |
| else if (typeof exports === 'object') |
| exports["%(EXPORT_NAME)s"] = %(EXPORT_NAME)s; |
| ''' % { |
| 'EXPORT_NAME': shared.Settings.EXPORT_NAME |
| }) |
| |
| save_intermediate('modularized') |
| |
| |
| def module_export_name_substitution(): |
| global final |
| logger.debug('Private module export name substitution with ' + shared.Settings.EXPORT_NAME) |
| src = open(final).read() |
| final = final + '.module_export_name_substitution.js' |
| if shared.Settings.MINIMAL_RUNTIME: |
| # In MINIMAL_RUNTIME the Module object is always present to provide the .asm.js/.wasm content |
| replacement = shared.Settings.EXPORT_NAME |
| else: |
| replacement = "typeof %(EXPORT_NAME)s !== 'undefined' ? %(EXPORT_NAME)s : {}" % {"EXPORT_NAME": shared.Settings.EXPORT_NAME} |
| with open(final, 'w') as f: |
| src = src.replace(shared.JS.module_export_name_substitution_pattern, replacement) |
| # For Node.js and other shell environments, create an unminified Module object so that |
| # loading external .asm.js file that assigns to Module['asm'] works even when Closure is used. |
| if shared.Settings.MINIMAL_RUNTIME and (shared.Settings.target_environment_may_be('node') or |
| shared.Settings.target_environment_may_be('shell')): |
| src = 'if(typeof Module==="undefined"){var Module={};}' + src |
| f.write(src) |
| save_intermediate('module_export_name_substitution') |
| |
| |
| def generate_traditional_runtime_html(target, options, js_target, target_basename, |
| asm_target, wasm_binary_target, |
| memfile, optimizer): |
| script = ScriptSource() |
| |
| shell = read_and_preprocess(options.shell_path) |
| assert '{{{ SCRIPT }}}' in shell, 'HTML shell must contain {{{ SCRIPT }}} , see src/shell.html for an example' |
| base_js_target = os.path.basename(js_target) |
| |
| asm_mods = [] |
| |
| if options.proxy_to_worker: |
| proxy_worker_filename = (shared.Settings.PROXY_TO_WORKER_FILENAME or target_basename) + '.js' |
| worker_js = worker_js_script(proxy_worker_filename) |
| script.inline = (''' |
| var filename = '%s'; |
| if ((',' + window.location.search.substr(1) + ',').indexOf(',noProxy,') < 0) { |
| console.log('running code in a web worker'); |
| ''' % shared.JS.get_subresource_location(proxy_worker_filename)) + worker_js + ''' |
| } else { |
| // note: no support for code mods (PRECISE_F32==2) |
| console.log('running code on the main thread'); |
| var fileBytes = tryParseAsDataURI(filename); |
| var script = document.createElement('script'); |
| if (fileBytes) { |
| script.innerHTML = intArrayToString(fileBytes); |
| } else { |
| script.src = filename; |
| } |
| document.body.appendChild(script); |
| } |
| ''' |
| else: |
| # Normal code generation path |
| script.src = base_js_target |
| |
| asm_mods = client_mods.get_mods(shared.Settings, |
| minified='minifyNames' in optimizer.queue_history, |
| separate_asm=options.separate_asm) |
| |
| if not shared.Settings.SINGLE_FILE: |
| if shared.Settings.EMTERPRETIFY_FILE: |
| # We need to load the emterpreter file before anything else, it has to be synchronously ready |
| script.un_src() |
| script.inline = ''' |
| var emterpretURL = '%s'; |
| var emterpretXHR = new XMLHttpRequest(); |
| emterpretXHR.open('GET', emterpretURL, true); |
| emterpretXHR.responseType = 'arraybuffer'; |
| emterpretXHR.onload = function() { |
| if (emterpretXHR.status === 200 || emterpretXHR.status === 0) { |
| Module.emterpreterFile = emterpretXHR.response; |
| } else { |
| var emterpretURLBytes = tryParseAsDataURI(emterpretURL); |
| if (emterpretURLBytes) { |
| Module.emterpreterFile = emterpretURLBytes.buffer; |
| } |
| } |
| %s |
| }; |
| emterpretXHR.send(null); |
| ''' % (shared.JS.get_subresource_location(shared.Settings.EMTERPRETIFY_FILE), script.inline) |
| |
| if options.memory_init_file and not shared.Settings.MEM_INIT_IN_WASM: |
| # start to load the memory init file in the HTML, in parallel with the JS |
| script.un_src() |
| script.inline = (''' |
| var memoryInitializer = '%s'; |
| memoryInitializer = Module['locateFile'] ? Module['locateFile'](memoryInitializer, '') : memoryInitializer; |
| Module['memoryInitializerRequestURL'] = memoryInitializer; |
| var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest(); |
| meminitXHR.open('GET', memoryInitializer, true); |
| meminitXHR.responseType = 'arraybuffer'; |
| meminitXHR.send(null); |
| ''' % shared.JS.get_subresource_location(memfile)) + script.inline |
| |
| # Download .asm.js if --separate-asm was passed in an asm.js build, or if 'asmjs' is one |
| # of the wasm run methods. |
| if not options.separate_asm or shared.Settings.WASM: |
| if len(asm_mods): |
| exit_with_error('no --separate-asm means no client code mods are possible') |
| else: |
| script.un_src() |
| if len(asm_mods) == 0: |
| # just load the asm, then load the rest |
| script.inline = ''' |
| var filename = '%s'; |
| var fileBytes = tryParseAsDataURI(filename); |
| var script = document.createElement('script'); |
| if (fileBytes) { |
| script.innerHTML = intArrayToString(fileBytes); |
| } else { |
| script.src = filename; |
| } |
| script.onload = function() { |
| setTimeout(function() { |
| %s |
| }, 1); // delaying even 1ms is enough to allow compilation memory to be reclaimed |
| }; |
| document.body.appendChild(script); |
| ''' % (shared.JS.get_subresource_location(asm_target), script.inline) |
| else: |
| # may need to modify the asm code, load it as text, modify, and load asynchronously |
| script.inline = ''' |
| var codeURL = '%s'; |
| var codeXHR = new XMLHttpRequest(); |
| codeXHR.open('GET', codeURL, true); |
| codeXHR.onload = function() { |
| var code; |
| if (codeXHR.status === 200 || codeXHR.status === 0) { |
| code = codeXHR.responseText; |
| } else { |
| var codeURLBytes = tryParseAsDataURI(codeURL); |
| if (codeURLBytes) { |
| code = intArrayToString(codeURLBytes); |
| } |
| } |
| %s |
| var blob = new Blob([code], { type: 'text/javascript' }); |
| codeXHR = null; |
| var src = URL.createObjectURL(blob); |
| var script = document.createElement('script'); |
| script.src = src; |
| script.onload = function() { |
| setTimeout(function() { |
| %s |
| }, 1); // delaying even 1ms is enough to allow compilation memory to be reclaimed |
| URL.revokeObjectURL(script.src); |
| }; |
| document.body.appendChild(script); |
| }; |
| codeXHR.send(null); |
| ''' % (shared.JS.get_subresource_location(asm_target), '\n'.join(asm_mods), script.inline) |
| |
| if shared.Settings.WASM and not shared.Settings.WASM_ASYNC_COMPILATION: |
| # We need to load the wasm file before anything else, it has to be synchronously ready TODO: optimize |
| script.un_src() |
| script.inline = ''' |
| var wasmURL = '%s'; |
| var wasmXHR = new XMLHttpRequest(); |
| wasmXHR.open('GET', wasmURL, true); |
| wasmXHR.responseType = 'arraybuffer'; |
| wasmXHR.onload = function() { |
| if (wasmXHR.status === 200 || wasmXHR.status === 0) { |
| Module.wasmBinary = wasmXHR.response; |
| } else { |
| var wasmURLBytes = tryParseAsDataURI(wasmURL); |
| if (wasmURLBytes) { |
| Module.wasmBinary = wasmURLBytes.buffer; |
| } |
| } |
| %s |
| }; |
| wasmXHR.send(null); |
| ''' % (shared.JS.get_subresource_location(wasm_binary_target), script.inline) |
| |
| if shared.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) { |
| // 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, shared.JS.get_subresource_location(wasm_binary_target) + '.js') |
| |
| # when script.inline isn't empty, add required helper functions such as tryParseAsDataURI |
| if script.inline: |
| for filename in ('arrayUtils.js', 'base64Utils.js', 'URIUtils.js'): |
| content = read_and_preprocess(shared.path_from_root('src', filename)) |
| script.inline = content + script.inline |
| |
| script.inline = 'var ASSERTIONS = %s;\n%s' % (shared.Settings.ASSERTIONS, script.inline) |
| |
| # inline script for SINGLE_FILE output |
| if shared.Settings.SINGLE_FILE: |
| js_contents = script.inline or '' |
| if script.src: |
| js_contents += open(js_target).read() |
| shared.try_delete(js_target) |
| script.src = None |
| script.inline = js_contents |
| |
| html_contents = shell.replace('{{{ SCRIPT }}}', script.replacement()) |
| html_contents = tools.line_endings.convert_line_endings(html_contents, '\n', options.output_eol) |
| with open(target, 'wb') as f: |
| f.write(asbytes(html_contents)) |
| |
| |
| def minify_html(filename, options): |
| opts = [] |
| # -g1 and greater retain whitespace and comments in source |
| if options.debug_level == 0: |
| opts += ['--collapse-whitespace', |
| '--collapse-inline-tag-whitespace', |
| '--remove-comments', |
| '--remove-tag-whitespace', |
| '--sort-attributes', |
| '--sort-class-name'] |
| # -g2 and greater do not minify HTML at all |
| if options.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: |
| # '--remove-optional-tags': removes e.g. <head></head> and <body></body> 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) |
| |
| if options.debug_level >= 2: |
| return |
| |
| logger.debug('minifying HTML file ' + filename) |
| size_before = os.path.getsize(filename) |
| start_time = time.time() |
| run_process(shared.NODE_JS + [shared.path_from_root('third_party', 'html-minifier', 'cli.js'), filename, '-o', filename] + opts) |
| |
| elapsed_time = time.time() - start_time |
| size_after = os.path.getsize(filename) |
| delta = size_after - size_before |
| logger.debug('HTML minification took {:.2f}'.format(elapsed_time) + ' seconds, and shrunk size of ' + filename + ' from ' + str(size_before) + ' to ' + str(size_after) + ' bytes, delta=' + str(delta) + ' ({:+.2f}%)'.format(delta * 100.0 / size_before)) |
| |
| |
| def generate_html(target, options, js_target, target_basename, |
| asm_target, wasm_binary_target, |
| memfile, optimizer): |
| logger.debug('generating HTML') |
| |
| if shared.Settings.EXPORT_NAME != 'Module' and \ |
| not shared.Settings.MINIMAL_RUNTIME and \ |
| options.shell_path == shared.path_from_root('src', '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)') |
| |
| if shared.Settings.MINIMAL_RUNTIME: |
| generate_minimal_runtime_html(target, options, js_target, target_basename, asm_target, |
| wasm_binary_target, memfile, optimizer) |
| else: |
| generate_traditional_runtime_html(target, options, js_target, target_basename, asm_target, |
| wasm_binary_target, memfile, optimizer) |
| |
| if shared.Settings.MINIFY_HTML and (options.opt_level >= 1 or options.shrink_level >= 1): |
| minify_html(target, options) |
| |
| |
| def generate_worker_js(target, js_target, target_basename): |
| # compiler output is embedded as base64 |
| if shared.Settings.SINGLE_FILE: |
| proxy_worker_filename = shared.JS.get_subresource_location(js_target) |
| |
| # compiler output goes in .worker.js file |
| else: |
| shutil.move(js_target, unsuffixed(js_target) + '.worker.js') |
| worker_target_basename = target_basename + '.worker' |
| proxy_worker_filename = (shared.Settings.PROXY_TO_WORKER_FILENAME or worker_target_basename) + '.js' |
| |
| target_contents = worker_js_script(proxy_worker_filename) |
| open(target, 'w').write(target_contents) |
| |
| |
| def worker_js_script(proxy_worker_filename): |
| web_gl_client_src = open(shared.path_from_root('src', 'webGLClient.js')).read() |
| idb_store_src = open(shared.path_from_root('src', 'IDBStore.js')).read() |
| proxy_client_src = ( |
| open(shared.path_from_root('src', 'proxyClient.js')).read() |
| .replace('{{{ filename }}}', proxy_worker_filename) |
| .replace('{{{ IDBStore.js }}}', idb_store_src) |
| ) |
| |
| return web_gl_client_src + '\n' + proxy_client_src |
| |
| |
| def process_libraries(libs, lib_dirs, temp_files): |
| libraries = [] |
| consumed = [] |
| |
| # Find library files |
| for i, lib in libs: |
| logger.debug('looking for library "%s"', lib) |
| suffixes = STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS |
| |
| found = False |
| for prefix in LIB_PREFIXES: |
| for suff in suffixes: |
| name = prefix + lib + suff |
| for lib_dir in lib_dirs: |
| path = os.path.join(lib_dir, name) |
| if os.path.exists(path): |
| logger.debug('found library "%s" at %s', lib, path) |
| temp_files.append((i, path)) |
| consumed.append(i) |
| found = True |
| break |
| if found: |
| break |
| if found: |
| break |
| if not found: |
| jslibs = shared.Building.path_to_system_js_libraries(lib) |
| if jslibs: |
| libraries += jslibs |
| consumed.append(i) |
| |
| shared.Settings.SYSTEM_JS_LIBRARIES += libraries |
| return consumed |
| |
| |
| class ScriptSource(object): |
| 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 |
| self.inline = ''' |
| var script = document.createElement('script'); |
| script.src = "%s"; |
| document.body.appendChild(script); |
| ''' % self.src |
| self.src = None |
| |
| def replacement(self): |
| """Returns 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: |
| return '<script async type="text/javascript" src="%s"></script>' % quote(self.src) |
| else: |
| return '<script>\n%s\n</script>' % self.inline |
| |
| |
| def is_valid_abspath(options, path_name): |
| # Any path that is underneath the emscripten repository root must be ok. |
| if shared.path_from_root().replace('\\', '/') in path_name.replace('\\', '/'): |
| return True |
| |
| def in_directory(root, child): |
| # make both path absolute |
| root = os.path.realpath(root) |
| child = os.path.realpath(child) |
| |
| # return true, if the common prefix of both is equal to directory |
| # e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b |
| return os.path.commonprefix([root, child]) == root |
| |
| for valid_abspath in options.valid_abspaths: |
| if in_directory(valid_abspath, path_name): |
| return True |
| return False |
| |
| |
| def parse_value(text): |
| # Note that using response files can introduce whitespace, if the file |
| # has a newline at the end. For that reason, we rstrip() in relevant |
| # places here. |
| def parse_string_value(text): |
| first = text[0] |
| if first == "'" or first == '"': |
| text = text.rstrip() |
| assert text[-1] == text[0] and len(text) > 1, 'unclosed opened quoted string. expected final character to be "%s" and length to be greater than 1 in "%s"' % (text[0], text) |
| return text[1:-1] |
| return text |
| |
| def parse_string_list_members(text): |
| sep = ',' |
| values = text.split(sep) |
| result = [] |
| index = 0 |
| while True: |
| current = values[index].lstrip() # Cannot safely rstrip for cases like: "HERE-> ," |
| if not len(current): |
| exit_with_error('string array should not contain an empty value') |
| first = current[0] |
| if not(first == "'" or first == '"'): |
| result.append(current.rstrip()) |
| else: |
| start = index |
| while True: # Continue until closing quote found |
| if index >= len(values): |
| exit_with_error("unclosed quoted string. expected final character to be '%s' in '%s'" % (first, values[start])) |
| new = values[index].rstrip() |
| if new and new[-1] == first: |
| if start == index: |
| result.append(current.rstrip()[1:-1]) |
| else: |
| result.append((current + sep + new)[1:-1]) |
| break |
| else: |
| current += sep + values[index] |
| index += 1 |
| |
| index += 1 |
| if index >= len(values): |
| break |
| return result |
| |
| def parse_string_list(text): |
| text = text.rstrip() |
| if text[-1] != ']': |
| exit_with_error('unclosed opened string list. expected final character to be "]" in "%s"' % (text)) |
| inner = text[1:-1] |
| if inner.strip() == "": |
| return [] |
| return parse_string_list_members(inner) |
| |
| if text[0] == '[': |
| # if json parsing fails, we fall back to our own parser, which can handle a few |
| # simpler syntaxes |
| try: |
| return json.loads(text) |
| except ValueError: |
| return parse_string_list(text) |
| |
| try: |
| return int(text) |
| except ValueError: |
| return parse_string_value(text) |
| |
| |
| def validate_arg_level(level_string, max_level, err_msg, clamp=False): |
| try: |
| level = int(level_string) |
| except ValueError: |
| raise Exception(err_msg) |
| if clamp: |
| if level > max_level: |
| logger.warning("optimization level '-O" + level_string + "' is not supported; using '-O" + str(max_level) + "' instead") |
| level = max_level |
| if not 0 <= level <= max_level: |
| raise Exception(err_msg) |
| return level |
| |
| |
| def is_int(s): |
| try: |
| int(s) |
| return True |
| except ValueError: |
| return False |
| |
| |
| if __name__ == '__main__': |
| try: |
| sys.exit(run(sys.argv)) |
| except KeyboardInterrupt: |
| logger.warning('KeyboardInterrupt') |
| sys.exit(1) |