| #!/usr/bin/env python2 |
| # -*- Mode: python -*- |
| """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 stat |
| import os |
| import sys |
| import shutil |
| import tempfile |
| import shlex |
| import time |
| import re |
| import logging |
| from subprocess import PIPE |
| |
| from tools import shared, jsrun, system_libs, client_mods, js_optimizer |
| from tools.shared import suffix, unsuffixed, unsuffixed_basename, WINDOWS, safe_copy, safe_move, run_process, asbytes, read_and_preprocess, exit_with_error |
| from tools.response_file import substitute_response_files |
| 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 |
| |
| # 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',) |
| |
| 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 |
| |
| BITCODE_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', '.wast') |
| |
| SUPPORTED_LINKER_FLAGS = ('--start-group', '-(', '--end-group', '-)') |
| |
| LIB_PREFIXES = ('', 'lib') |
| |
| JS_CONTAINING_SUFFIXES = ('js', 'html') |
| EXECUTABLE_SUFFIXES = JS_CONTAINING_SUFFIXES + ('wasm',) |
| |
| DEFERRED_REPONSE_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'], |
| } |
| |
| DEBUG = os.environ.get('EMCC_DEBUG') |
| if DEBUG == "0": |
| DEBUG = None |
| |
| # 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 = os.environ.get('EMCC_LEAVE_INPUTS_RAW') |
| |
| # 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') |
| |
| EMCC_CFLAGS = os.environ.get('EMCC_CFLAGS') # Additional compiler flags that we treat as if they were passed to us on the commandline |
| |
| # Target options |
| final = None |
| |
| |
| class Intermediate(object): |
| counter = 0 |
| |
| |
| # 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=None, suffix='js'): |
| if not DEBUG: |
| return |
| name = os.path.join(shared.get_emscripten_temp_dir(), 'emcc-%d%s.%s' % (Intermediate.counter, '' if name is None else '-' + name, suffix)) |
| if isinstance(final, list): |
| logging.debug('(not saving intermediate %s because deferring linking)' % name) |
| return |
| shutil.copyfile(final, name) |
| Intermediate.counter += 1 |
| |
| |
| def save_intermediate_with_wasm(name, wasm_binary): |
| if not DEBUG: |
| return |
| save_intermediate(name) # save the js |
| name = os.path.join(shared.get_emscripten_temp_dir(), 'emcc-%d-%s.wasm' % (Intermediate.counter - 1, name)) |
| shutil.copyfile(wasm_binary, name) |
| |
| |
| 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() |
| logging.debug('emcc step "%s" took %.2f seconds', name, now - TimeLogger.last) |
| TimeLogger.update() |
| |
| |
| 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.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 = None |
| self.js_libraries = [] |
| self.bind = False |
| 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 |
| |
| |
| def use_source_map(options): |
| return options.debug_level >= 4 |
| |
| |
| 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') or '').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.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=' + 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'] |
| logging.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')) |
| 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 shared.Settings.SPLIT_MEMORY: |
| # must be done before minification |
| self.queue += ['splitMemory', 'simplifyExpressions'] |
| |
| 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))) |
| |
| |
| # |
| # Main run() function |
| # |
| def run(): |
| global final |
| target = None |
| |
| if DEBUG: |
| logging.warning('invocation: ' + ' '.join(sys.argv) + (' + ' + EMCC_CFLAGS if EMCC_CFLAGS else '') + ' (in ' + os.getcwd() + ')') |
| if EMCC_CFLAGS: |
| sys.argv.extend(shlex.split(EMCC_CFLAGS)) |
| |
| if DEBUG and LEAVE_INPUTS_RAW: |
| logging.warning('leaving inputs raw') |
| |
| EMCC_CXX = '--emscripten-cxx' in sys.argv |
| sys.argv = [x for x in sys.argv if x != '--emscripten-cxx'] |
| |
| if len(sys.argv) <= 1 or ('--help' not in sys.argv and len(sys.argv) >= 2 and sys.argv[1] != '--version'): |
| shared.check_sanity(force=DEBUG) |
| |
| misc_temp_files = shared.configuration.get_temp_files() |
| |
| # Handle some global flags |
| |
| if len(sys.argv) == 1: |
| logging.warning('no input files') |
| return 1 |
| |
| # read response files very early on |
| sys.argv = substitute_response_files(sys.argv) |
| |
| if len(sys.argv) == 1 or '--help' in sys.argv: |
| # 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 |
| |
| print('''%s |
| |
| ------------------------------------------------------------------ |
| |
| emcc: supported targets: llvm bitcode, javascript, NOT elf |
| (autoconf likes to see elf above to enable shared object support) |
| ''' % (open(shared.path_from_root('site', 'build', 'text', 'docs', 'tools_reference', 'emcc.txt')).read())) |
| return 0 |
| |
| elif sys.argv[1] == '--version': |
| revision = '(unknown revision)' |
| here = os.getcwd() |
| os.chdir(shared.path_from_root()) |
| try: |
| revision = run_process(['git', 'show'], stdout=PIPE, stderr=PIPE).stdout.split('\n')[0] |
| except: |
| pass |
| finally: |
| os.chdir(here) |
| 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 |
| |
| elif len(sys.argv) == 2 and sys.argv[1] == '-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) |
| code = run_process([shared.CLANG, '-v'], check=False).returncode |
| shared.check_sanity(force=True) |
| return code |
| |
| elif '-dumpmachine' in sys.argv: |
| print(shared.get_llvm_target()) |
| return 0 |
| |
| elif '-dumpversion' in sys.argv: # gcc's doc states "Print the compiler version [...] and don't do anything else." |
| print(shared.EMSCRIPTEN_VERSION) |
| return 0 |
| |
| elif '--cflags' in sys.argv: |
| # fake running the command, to see the full args we pass to clang |
| debug_env = os.environ.copy() |
| debug_env['EMCC_DEBUG'] = '1' |
| args = [x for x in sys.argv if x != '--cflags'] |
| with misc_temp_files.get_file(suffix='.o') as temp_target: |
| input_file = 'hello_world.c' |
| err = run_process([shared.PYTHON] + args + [shared.path_from_root('tests', input_file), '-c', '-o', temp_target], stderr=PIPE, env=debug_env).stderr |
| lines = [x for x in err.split('\n') if shared.CLANG_CC in x and input_file in x] |
| line = re.search('running: (.*)', lines[0]).group(1) |
| parts = shlex.split(line.replace('\\', '\\\\')) |
| parts = [x for x in parts if x != '-c' and x != '-o' and input_file not in x and temp_target not in x and '-emit-llvm' not in x] |
| print(' '.join(shared.Building.doublequote_spaces(parts[1:]))) |
| return 0 |
| |
| def is_minus_s_for_emcc(newargs, i): |
| assert newargs[i] == '-s' |
| if i + 1 < len(newargs) and '=' in newargs[i + 1] and not newargs[i + 1].startswith('-'): # -s OPT=VALUE is for us, -s by itself is a linker option |
| return True |
| else: |
| logging.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 sys.argv) and not os.environ.get('EMMAKEN_JUST_CONFIGURE_RECURSE') |
| CMAKE_CONFIG = 'CMakeFiles/cmTryCompileExec.dir' in ' '.join(sys.argv)# or 'CMakeCCompilerId' in ' '.join(sys.argv) |
| if CONFIGURE_CONFIG or CMAKE_CONFIG: |
| debug_configure = 0 # XXX use this to debug configure stuff. ./configure's generally hide our normal output including stderr so we write to a file |
| |
| # Whether we fake configure tests using clang - the local, native compiler - or not. if not we generate JS and use node with a shebang |
| # Neither approach is perfect, you can try both, but may need to edit configure scripts in some cases |
| # By default we configure in js, which can break on local filesystem access, etc., but is otherwise accurate so we |
| # disable this if we think we have to. A value of '2' here will force JS checks in all cases. In summary: |
| # 0 - use native compilation for configure checks |
| # 1 - use js when we think it will work |
| # 2 - always use js for configure checks |
| use_js = int(os.environ.get('EMCONFIGURE_JS') or 2) |
| |
| if debug_configure: |
| tempout = '/tmp/emscripten_temp/out' |
| if not os.path.exists(tempout): |
| open(tempout, 'w').write('//\n') |
| |
| src = None |
| for arg in sys.argv: |
| if arg.endswith(SOURCE_ENDINGS): |
| try: |
| src = open(arg).read() |
| if debug_configure: |
| open(tempout, 'a').write('============= ' + arg + '\n' + src + '\n=============\n\n') |
| except: |
| pass |
| elif arg.endswith('.s'): |
| if debug_configure: |
| open(tempout, 'a').write('(compiling .s assembly, must use clang\n') |
| if use_js == 1: |
| use_js = 0 |
| elif arg == '-E' or arg == '-M' or arg == '-MM': |
| if use_js == 1: |
| use_js = 0 |
| |
| if src: |
| if 'fopen' in src and '"w"' in src: |
| if use_js == 1: |
| use_js = 0 # we cannot write to files from js! |
| if debug_configure: |
| open(tempout, 'a').write('Forcing clang since uses fopen to write\n') |
| |
| compiler = os.environ.get('CONFIGURE_CC') or (shared.CLANG if not use_js else shared.EMCC) # if CONFIGURE_CC is defined, use that. let's you use local gcc etc. if you need that |
| if not ('CXXCompiler' in ' '.join(sys.argv) or EMCC_CXX): |
| compiler = shared.to_cc(compiler) |
| |
| def filter_emscripten_options(argv): |
| idx = 0 |
| skip_next = False |
| for el in argv: |
| if skip_next: |
| skip_next = False |
| idx += 1 |
| continue |
| if not use_js and el == '-s' and is_minus_s_for_emcc(argv, idx): # skip -s X=Y if not using js for configure |
| skip_next = True |
| if not use_js and el == '--tracing': |
| pass |
| else: |
| yield el |
| idx += 1 |
| |
| if compiler == shared.EMCC: |
| compiler = [shared.PYTHON, shared.EMCC] |
| else: |
| compiler = [compiler] |
| cmd = compiler + list(filter_emscripten_options(sys.argv[1:])) |
| if not use_js: |
| cmd += shared.EMSDK_OPTS + ['-D__EMSCRIPTEN__'] |
| # The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code in strict mode. Code should use the define __EMSCRIPTEN__ instead. |
| if not shared.Settings.STRICT: |
| cmd += ['-DEMSCRIPTEN'] |
| if use_js: |
| cmd += ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1'] # configure tests should fail when an undefined symbol exists |
| cmd += ['-s', 'NO_EXIT_RUNTIME=0'] # configure tests want a more shell-like style, where we emit return codes on exit() |
| cmd += ['-s', 'NODERAWFS=1'] # use node.js raw filesystem access, to behave just like a native executable |
| # Disable wasm in configuration checks so that (1) we do not depend on wasm support just for configuration (perhaps the user does not intend |
| # to build to wasm; using asm.js only depends on js which we need anyhow), and (2) we don't have issues with a separate .wasm file |
| # on the side, async startup, etc.. |
| cmd += ['-s', 'WASM=0'] |
| |
| logging.debug('just configuring: ' + ' '.join(cmd)) |
| if debug_configure: |
| open(tempout, 'a').write('emcc, just configuring: ' + ' '.join(cmd) + '\n\n') |
| |
| if not use_js: |
| return run_process(cmd, check=False).returncode |
| else: |
| 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(BITCODE_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: |
| pass # can fail if e.g. writing the executable to /dev/null |
| return ret |
| |
| if os.environ.get('EMMAKEN_COMPILER'): |
| CXX = os.environ['EMMAKEN_COMPILER'] |
| else: |
| CXX = shared.CLANG |
| |
| CC = shared.to_cc(CXX) |
| |
| # If we got here from a redirection through emmakenxx.py, then force a C++ compiler here |
| if EMCC_CXX: |
| CC = CXX |
| |
| CC_ADDITIONAL_ARGS = shared.COMPILER_OPTS |
| |
| EMMAKEN_CFLAGS = os.environ.get('EMMAKEN_CFLAGS') |
| if EMMAKEN_CFLAGS: |
| sys.argv += shlex.split(EMMAKEN_CFLAGS) |
| |
| # ---------------- Utilities --------------- |
| |
| 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)) if suffix(name) else '') |
| |
| # ---------------- End configs ------------- |
| |
| if len(sys.argv) == 1 or sys.argv[1] in ['x', 't']: |
| # noop ar |
| logging.debug('just ar') |
| return 0 |
| |
| # Check if a target is specified |
| target = None |
| if any(arg.startswith('-o=') for arg in sys.argv): |
| raise Exception('Invalid syntax: do not use -o=X, use -o X') |
| |
| for i in reversed(range(len(sys.argv) - 1)): # Last -o directive should take precedence, if multiple are specified |
| if sys.argv[i] == '-o': |
| target = sys.argv[i + 1] |
| sys.argv = sys.argv[:i] + sys.argv[i + 2:] |
| break |
| |
| specified_target = target |
| target = specified_target if specified_target is not None else 'a.out.js' # specified_target is the user-specified one, target is what we will generate |
| target_basename = unsuffixed_basename(target) |
| |
| if '.' in target: |
| final_suffix = target.split('.')[-1] |
| else: |
| final_suffix = '' |
| |
| # Temporary file handling: we ensure that TEMP_DIR exists, which is the general |
| # location for all temp files from us on this system. We then create a temp dir |
| # under that, and store our main files there. As we process the main js file, |
| # we update the variable `final`, giving it an extra suffix each time, and |
| # relying on the entire dir going away for cleanup. For other miscellaneous |
| # temporary files (like in the js optimizer) we need to note() them so they |
| # get removed. |
| |
| temp_root = shared.TEMP_DIR |
| if not os.path.exists(temp_root): |
| try: |
| os.makedirs(temp_root) |
| except Exception as e: |
| if os.path.exists(temp_root): |
| pass # If running multiple emcc instances simultaneously, they may race to create the temp directory if it did not initially exist. In that case, we can proceed. |
| else: |
| raise |
| |
| temp_dir = tempfile.mkdtemp(dir=temp_root) |
| |
| def in_temp(name): |
| return os.path.join(temp_dir, os.path.basename(name)) |
| |
| # 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' |
| def filename_type_suffix(filename): |
| for i in reversed(filename.split('.')[1:]): |
| if not i.isdigit(): |
| return i |
| return '' |
| |
| def filename_type_ending(filename): |
| if filename in SPECIAL_ENDINGLESS_FILENAMES: |
| return filename |
| suffix = filename_type_suffix(filename) |
| return '' if not suffix else ('.' + suffix) |
| |
| def optimizing(opts): |
| return '-O0' not in opts |
| |
| use_cxx = True |
| |
| try: |
| with ToolchainProfiler.profile_block('parse arguments and setup'): |
| ## Parse args |
| |
| newargs = sys.argv[1:] |
| |
| # 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] = '' |
| |
| def detect_fixed_language_mode(args): |
| check_next = False |
| for item in args: |
| if check_next: |
| if item in ("c++", "c"): |
| return True |
| else: |
| check_next = False |
| if item.startswith("-x"): |
| lmode = item[2:] if len(item) > 2 else None |
| if lmode in ("c++", "c"): |
| return True |
| else: |
| check_next = True |
| continue |
| return False |
| |
| has_fixed_language_mode = detect_fixed_language_mode(newargs) |
| |
| options, settings_changes, newargs = parse_args(newargs) |
| |
| for arg in newargs: |
| if arg == '-xc': |
| use_cxx = False |
| break |
| elif arg == '-xc++': |
| use_cxx = True |
| break |
| elif not arg.startswith('-'): |
| if arg.endswith(C_ENDINGS + OBJC_ENDINGS): |
| use_cxx = False |
| |
| if not use_cxx: |
| options.default_cxx_std = '' # Compiling C code with .c files, don't enforce a default C++ std. |
| |
| call = CXX if use_cxx else CC |
| |
| # If user did not specify a default -std for C++ code, specify the emscripten default. |
| if options.default_cxx_std: |
| newargs = newargs + [options.default_cxx_std] |
| |
| 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.NO_EXIT_RUNTIME = 0 |
| |
| if options.cpu_profiler: |
| options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n' |
| |
| if options.memory_profiler: |
| options.post_js += open(shared.path_from_root('src', 'memoryprofiler.js')).read() + '\n' |
| |
| 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): |
| logging.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] |
| settings_changes.append(key) |
| newargs[i] = newargs[i + 1] = '' |
| assert key != 'WASM_BACKEND', 'do not set -s WASM_BACKEND, instead set EMCC_WASM_BACKEND=1 in the environment' |
| newargs = [arg for arg in newargs if arg is not ''] |
| |
| # 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* |
| } |
| settings_key_changes = set() |
| |
| def setting_sub(s): |
| key, rest = s.split('=', 1) |
| settings_key_changes.add(key) |
| return '='.join([settings_aliases.get(key, key), rest]) |
| |
| settings_changes = list(map(setting_sub, settings_changes)) |
| |
| # 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 = [shared.path_from_root('system', 'local', 'lib'), |
| shared.path_from_root('system', 'lib')] |
| for i in range(len(newargs)): # find input files XXX this 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 |
| 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 os.path.islink(arg) and os.path.realpath(arg).endswith(SOURCE_ENDINGS + BITCODE_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) |
| |
| arg_ending = filename_type_ending(arg) |
| if arg_ending.endswith(SOURCE_ENDINGS + BITCODE_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 arg_ending.endswith(SOURCE_ENDINGS): |
| input_files.append((i, arg)) |
| has_source_inputs = True |
| elif arg_ending.endswith(HEADER_ENDINGS): |
| input_files.append((i, arg)) |
| has_header_inputs = True |
| elif arg_ending.endswith(ASSEMBLY_ENDINGS) or shared.Building.is_bitcode(arg): # this should be bitcode, make sure it is valid |
| input_files.append((i, arg)) |
| elif arg_ending.endswith(STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS): |
| # if it's not, and it's a library, just add it to libs to find later |
| l = unsuffixed_basename(arg) |
| for prefix in LIB_PREFIXES: |
| if not prefix: |
| continue |
| if l.startswith(prefix): |
| l = l[len(prefix):] |
| break |
| libs.append((i, l)) |
| newargs[i] = '' |
| else: |
| logging.warning(arg + ' is not valid LLVM bitcode') |
| elif arg_ending.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(BITCODE_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('-L'): |
| lib_dirs.append(arg[2:]) |
| newargs[i] = '' |
| elif arg.startswith('-l'): |
| libs.append((i, arg[2:])) |
| 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): |
| link_flags.append((i + float(flag_index) / len(link_flags_to_add), flag)) |
| |
| newargs[i] = '' |
| |
| original_input_files = input_files[:] |
| |
| newargs = [a for a in newargs if a is not ''] |
| |
| # -c means do not link in gcc, and for us, the parallel is to not go all the way to JS, but stop at bitcode |
| has_dash_c = '-c' in newargs |
| if has_dash_c: |
| assert has_source_inputs or has_header_inputs, 'Must have source code or header inputs to use -c' |
| target = target_basename + '.o' |
| final_suffix = 'o' |
| if '-E' in newargs: |
| 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 |
| final_ending = ('.' + final_suffix) if len(final_suffix) else '' |
| |
| # target is now finalized, can finalize other _target s |
| 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', '.wast') # ditto, might not be used |
| wasm_binary_target = asm_target.replace('.asm.js', '.wasm') # ditto, might not be used |
| |
| if final_suffix == 'html' and not options.separate_asm and ('PRECISE_F32=2' in settings_changes or 'USE_PTHREADS=2' in settings_changes): |
| options.separate_asm = True |
| logging.warning('forcing separate asm output (--separate-asm), because -s PRECISE_F32=2 or -s USE_PTHREADS=2 was passed.') |
| if options.separate_asm: |
| shared.Settings.SEPARATE_ASM = shared.JS.get_subresource_location(asm_target) |
| |
| if 'EMCC_STRICT' in os.environ: |
| shared.Settings.STRICT = os.environ.get('EMCC_STRICT') != '0' |
| |
| # Libraries are searched before settings_changes are applied, so apply the value for STRICT and ERROR_ON_MISSING_LIBRARIES 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[len('STRICT='):]) |
| |
| if shared.Settings.STRICT: |
| shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS = 1 |
| shared.Settings.ERROR_ON_MISSING_LIBRARIES = 1 |
| |
| error_on_missing_libraries_cmdline = get_last_setting_change('ERROR_ON_MISSING_LIBRARIES') |
| if error_on_missing_libraries_cmdline: |
| shared.Settings.ERROR_ON_MISSING_LIBRARIES = int(error_on_missing_libraries_cmdline[len('ERROR_ON_MISSING_LIBRARIES='):]) |
| |
| settings_changes.append(system_js_libraries_setting_str(libs, lib_dirs, settings_changes, input_files)) |
| |
| # 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 filename_type_suffix(target) not in JS_CONTAINING_SUFFIXES or options.ignore_dynamic_linking: |
| def check(input_file): |
| if filename_type_ending(input_file) in DYNAMICLIB_ENDINGS: |
| if not options.ignore_dynamic_linking: |
| logging.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 |
| input_files = [f for f in input_files if check(f[1])] |
| |
| 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 + BITCODE_ENDINGS + DYNAMICLIB_ENDINGS + STATICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS)) |
| |
| newargs = CC_ADDITIONAL_ARGS + newargs |
| |
| 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) |
| |
| if os.environ.get('EMCC_FAST_COMPILER') == '0': |
| logging.critical('Non-fastcomp compiler is no longer available, please use fastcomp or an older version of emscripten') |
| return 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) |
| for change in settings_changes: |
| key, value = change.split('=', 1) |
| |
| # In those settings fields that represent amount of memory, translate suffixes to multiples of 1024. |
| if key in ['TOTAL_STACK', 'TOTAL_MEMORY', 'GL_MAX_TEMP_BUFFER_SIZE', 'SPLIT_MEMORY', 'WASM_MEM_MAX', 'DEFAULT_PTHREAD_STACK_SIZE']: |
| value = str(shared.expand_byte_size_suffixes(value)) |
| |
| original_exported_response = False |
| |
| if value[0] == '@': |
| if key not in DEFERRED_REPONSE_FILES: |
| if key == 'EXPORTED_FUNCTIONS': |
| original_exported_response = value |
| value = open(value[1:]).read() |
| else: |
| value = '"' + value + '"' |
| else: |
| value = value.replace('\\', '\\\\') |
| try: |
| setattr(shared.Settings, key, 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)) |
| |
| if key == 'EXPORTED_FUNCTIONS': |
| # used for warnings in emscripten.py |
| shared.Settings.ORIGINAL_EXPORTED_FUNCTIONS = original_exported_response or shared.Settings.EXPORTED_FUNCTIONS[:] |
| |
| shared.verify_settings() |
| |
| # Note the exports the user requested |
| shared.Building.user_requested_exports = shared.Settings.EXPORTED_FUNCTIONS[:] |
| |
| if options.bind: |
| # If we are using embind and generating JS, now is the time to link in bind.cpp |
| if final_suffix in JS_CONTAINING_SUFFIXES: |
| input_files.append((next_arg_index, shared.path_from_root('system', 'lib', 'embind', 'bind.cpp'))) |
| next_arg_index += 1 |
| |
| # -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 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. |
| shared.COMPILER_OPTS += ['-DEMSCRIPTEN'] |
| |
| # The system include path system/include/emscripten/ is deprecated, i.e. instead of #include <emscripten.h>, one should pass in #include <emscripten/emscripten.h>. |
| # This path is not available in Emscripten strict mode. |
| if shared.USE_EMSDK: |
| shared.C_INCLUDE_PATHS += [shared.path_from_root('system', 'include', 'emscripten')] |
| |
| # Use settings |
| |
| try: |
| assert shared.Settings.ASM_JS > 0, 'ASM_JS must be enabled in fastcomp' |
| assert shared.Settings.SAFE_HEAP in [0, 1], 'safe heap must be 0 or 1 in fastcomp' |
| assert shared.Settings.UNALIGNED_MEMORY == 0, 'forced unaligned memory not supported in fastcomp' |
| assert shared.Settings.FORCE_ALIGNED_MEMORY == 0, 'forced aligned memory is not supported in fastcomp' |
| assert shared.Settings.PGO == 0, 'pgo not supported in fastcomp' |
| assert shared.Settings.QUANTUM_SIZE == 4, 'altering the QUANTUM_SIZE is not supported' |
| except Exception as e: |
| logging.error('Compiler settings error: {}'.format(e)) |
| exit_with_error('Compiler settings are incompatible with fastcomp. You can fall back to the older compiler core, although that is not recommended, see http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html') |
| |
| assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' |
| |
| if options.debug_level > 1 and options.use_closure_compiler: |
| logging.warning('disabling closure because debug info was requested') |
| options.use_closure_compiler = False |
| |
| assert not (shared.Settings.EMTERPRETIFY_FILE and shared.Settings.SINGLE_FILE), 'cannot have both EMTERPRETIFY_FILE and SINGLE_FILE enabled at the same time' |
| |
| assert not (shared.Settings.NO_DYNAMIC_EXECUTION and options.use_closure_compiler), 'cannot have both NO_DYNAMIC_EXECUTION and closure compiler enabled at the same time' |
| |
| if options.emrun: |
| shared.Settings.EXPORTED_RUNTIME_METHODS.append('addOnExit') |
| |
| if options.use_closure_compiler: |
| shared.Settings.USE_CLOSURE_COMPILER = options.use_closure_compiler |
| if not shared.check_closure_compiler(): |
| exit_with_error('fatal: closure compiler is not configured correctly') |
| if options.use_closure_compiler == 2 and shared.Settings.ASM_JS == 1: |
| 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 != 2: |
| shared.Settings.INCLUDE_FULL_LIBRARY = 1 |
| elif shared.Settings.SIDE_MODULE: |
| assert not shared.Settings.MAIN_MODULE |
| options.memory_init_file = False # memory init file is not supported with asm.js side modules, must be executable synchronously (for dlopen) |
| |
| 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 != 2 and shared.Settings.SIDE_MODULE != 2: |
| shared.Settings.LINKABLE = 1 |
| shared.Settings.RELOCATABLE = 1 |
| shared.Settings.PRECISE_I64_MATH = 1 # other might use precise math, we need to be able to print it |
| 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.USE_PTHREADS: |
| # These runtime methods are called from pthread-main.js |
| shared.Settings.EXPORTED_RUNTIME_METHODS += ['establishStackSpace', 'dynCall_ii'] |
| |
| if shared.Settings.MODULARIZE_INSTANCE: |
| shared.Settings.MODULARIZE = 1 |
| |
| if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: |
| shared.Settings.ALIASING_FUNCTION_POINTERS = 0 |
| |
| if shared.Settings.LEGACY_VM_SUPPORT: |
| # legacy vms don't have wasm |
| assert not shared.Settings.WASM, 'LEGACY_VM_SUPPORT is only supported for asm.js, and not wasm. Build with -s WASM=0' |
| |
| if shared.Settings.SPLIT_MEMORY: |
| if shared.Settings.WASM: |
| logging.error('WASM is not compatible with SPLIT_MEMORY') |
| sys.exit(1) |
| assert shared.Settings.SPLIT_MEMORY > shared.Settings.TOTAL_STACK, 'SPLIT_MEMORY must be at least TOTAL_STACK (stack must fit in first chunk)' |
| assert shared.Settings.SPLIT_MEMORY & (shared.Settings.SPLIT_MEMORY - 1) == 0, 'SPLIT_MEMORY must be a power of 2' |
| if shared.Settings.ASM_JS == 1: |
| shared.WarningManager.warn('ALMOST_ASM', "not all asm.js optimizations are possible with SPLIT_MEMORY, disabling those.") |
| shared.Settings.ASM_JS = 2 |
| if shared.Settings.SAFE_HEAP: |
| shared.Settings.SAFE_HEAP = 0 |
| shared.Settings.SAFE_SPLIT_MEMORY = 1 # we use our own infrastructure |
| assert not shared.Settings.RELOCATABLE, 'no SPLIT_MEMORY with RELOCATABLE' |
| assert not shared.Settings.USE_PTHREADS, 'no SPLIT_MEMORY with pthreads' |
| if not options.js_opts: |
| options.js_opts = True |
| logging.debug('enabling js opts for SPLIT_MEMORY') |
| options.force_js_opts = True |
| if options.use_closure_compiler: |
| options.use_closure_compiler = False |
| logging.warning('cannot use closure compiler on split memory, for now, disabling') |
| |
| if shared.Settings.STB_IMAGE and final_suffix in JS_CONTAINING_SUFFIXES: |
| 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.ASMFS and final_suffix in JS_CONTAINING_SUFFIXES: |
| if shared.Settings.WASM: |
| logging.error('ASMFS not yet compatible with wasm (shared.make_fetch_worker is asm.js-specific)') |
| sys.exit(1) |
| input_files.append((next_arg_index, shared.path_from_root('system', 'lib', 'fetch', 'asmfs.cpp'))) |
| newargs.append('-D__EMSCRIPTEN_ASMFS__=1') |
| next_arg_index += 1 |
| shared.Settings.NO_FILESYSTEM = 1 |
| shared.Settings.FETCH = 1 |
| if not shared.Settings.USE_PTHREADS: |
| exit_with_error('-s ASMFS=1 requires either -s USE_PTHREADS=1 or -s USE_PTHREADS=2 to be set!') |
| |
| if shared.Settings.FETCH and final_suffix in JS_CONTAINING_SUFFIXES: |
| if shared.Settings.WASM: |
| logging.error('FETCH not yet compatible with wasm (shared.make_fetch_worker is asm.js-specific)') |
| sys.exit(1) |
| input_files.append((next_arg_index, shared.path_from_root('system', 'lib', 'fetch', 'emscripten_fetch.cpp'))) |
| next_arg_index += 1 |
| options.js_libraries.append(shared.path_from_root('src', 'library_fetch.js')) |
| |
| forced_stdlibs = [] |
| if shared.Settings.DEMANGLE_SUPPORT: |
| shared.Settings.EXPORTED_FUNCTIONS += ['___cxa_demangle'] |
| forced_stdlibs += ['libcxxabi'] |
| |
| if not shared.Settings.ONLY_MY_CODE: |
| if type(shared.Settings.EXPORTED_FUNCTIONS) in (list, tuple): |
| # always need malloc and free to be kept alive and exported, for internal use and other modules |
| for required_export in ['_malloc', '_free']: |
| if required_export not in shared.Settings.EXPORTED_FUNCTIONS: |
| shared.Settings.EXPORTED_FUNCTIONS.append(required_export) |
| else: |
| logging.debug('using response file for EXPORTED_FUNCTIONS, make sure it includes _malloc and _free') |
| |
| assert not (shared.Settings.NO_DYNAMIC_EXECUTION and shared.Settings.RELOCATABLE), 'cannot have both NO_DYNAMIC_EXECUTION and RELOCATABLE enabled at the same time, since RELOCATABLE needs to eval()' |
| |
| if shared.Settings.RELOCATABLE: |
| assert shared.Settings.GLOBAL_BASE < 1 |
| if shared.Settings.EMULATED_FUNCTION_POINTERS == 0: |
| shared.Settings.EMULATED_FUNCTION_POINTERS = 2 # by default, use optimized function pointer emulation |
| shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS = shared.Settings.WARN_ON_UNDEFINED_SYMBOLS = 0 |
| if not shared.Settings.SIDE_MODULE: |
| shared.Settings.EXPORT_ALL = 1 |
| |
| if shared.Settings.EMTERPRETIFY: |
| shared.Settings.FINALIZE_ASM_JS = 0 |
| # shared.Settings.GLOBAL_BASE = 8*256 # keep enough space at the bottom for a full stack frame, for z-interpreter |
| shared.Settings.SIMPLIFY_IFS = 0 # this is just harmful for emterpreting |
| shared.Settings.EXPORTED_FUNCTIONS += ['emterpret'] |
| if not options.js_opts: |
| logging.debug('enabling js opts for EMTERPRETIFY') |
| options.js_opts = True |
| options.force_js_opts = True |
| assert options.use_closure_compiler is not 2, '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.DEAD_FUNCTIONS: |
| if not options.js_opts: |
| logging.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): |
| assert not shared.Settings.NODERAWFS, '--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.NO_FILESYSTEM and not shared.Settings.ONLY_MY_CODE: |
| 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 not shared.Settings.NO_EXIT_RUNTIME or shared.Settings.ASSERTIONS: |
| shared.Settings.EXPORTED_FUNCTIONS += ['_fflush'] |
| |
| if shared.Settings.USE_PTHREADS: |
| if not any(s.startswith('PTHREAD_POOL_SIZE=') for s in settings_changes): |
| settings_changes.append('PTHREAD_POOL_SIZE=0') |
| options.js_libraries.append(shared.path_from_root('src', 'library_pthread.js')) |
| newargs.append('-D__EMSCRIPTEN_PTHREADS__=1') |
| shared.Settings.FORCE_FILESYSTEM = 1 # proxying of utime requires the filesystem |
| else: |
| options.js_libraries.append(shared.path_from_root('src', 'library_pthread_stub.js')) |
| |
| if shared.Settings.FORCE_FILESYSTEM: |
| # 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) |
| shared.Settings.EXPORTED_RUNTIME_METHODS += [ |
| 'FS_createFolder', |
| 'FS_createPath', |
| 'FS_createDataFile', |
| 'FS_createPreloadedFile', |
| 'FS_createLazyFile', |
| 'FS_createLink', |
| 'FS_createDevice', |
| 'FS_unlink', |
| 'getMemory', |
| 'addRunDependency', |
| 'removeRunDependency', |
| ] |
| |
| if shared.Settings.USE_PTHREADS: |
| 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!') |
| |
| if shared.Settings.OUTLINING_LIMIT: |
| if not options.js_opts: |
| logging.debug('enabling js opts as optional functionality implemented as a js opt was requested') |
| options.js_opts = True |
| options.force_js_opts = True |
| |
| if shared.Settings.WASM: |
| # When only targeting wasm, the .asm.js file is not executable, so is treated as an intermediate build file that can be cleaned up. |
| if shared.Building.is_wasm_only(): |
| 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.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.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 |
| if 'BINARYEN_PASSES' not in settings_key_changes: |
| if options.opt_level > 0 or options.shrink_level > 0: |
| shared.Settings.BINARYEN_PASSES = shared.Building.opt_level_to_str(options.opt_level, options.shrink_level) |
| |
| # to bootstrap struct_info, we need binaryen |
| os.environ['EMCC_WASM_BACKEND_BINARYEN'] = '1' |
| |
| 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 |
| shared.Settings.GLOBAL_BASE = 1024 # leave some room for mapping global vars |
| assert not shared.Settings.SPLIT_MEMORY, 'WebAssembly does not support split memory' |
| if shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS: |
| logging.warning('for wasm there is no need to set ELIMINATE_DUPLICATE_FUNCTIONS, the binaryen optimizer does it automatically') |
| shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS = 0 |
| if shared.Settings.OUTLINING_LIMIT: |
| logging.warning('for wasm there is usually no need to set OUTLINING_LIMIT, as VMs can handle large functions well anyhow') |
| # default precise-f32 to on, since it works well in wasm |
| # also always use f32s when asm.js is not in the picture |
| if ('PRECISE_F32=0' not in settings_changes and 'PRECISE_F32=2' not in settings_changes) or 'asmjs' not in shared.Settings.BINARYEN_METHOD: |
| shared.Settings.PRECISE_F32 = 1 |
| if options.js_opts and not options.force_js_opts and 'asmjs' not in shared.Settings.BINARYEN_METHOD: |
| options.js_opts = None |
| logging.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') |
| assert not options.use_closure_compiler == 2, 'closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm' |
| # for simplicity, we always have a mem init file, which may also be imported into the wasm module. |
| # * if we also supported js mem inits we'd have 4 modes |
| # * and js mem inits are useful for avoiding a side file, but the wasm module avoids that anyhow |
| if 'MEM_INIT_METHOD' in settings_changes: |
| logging.error('Mem init method selection 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.') |
| sys.exit(1) |
| options.memory_init_file = True |
| # async compilation requires wasm-only mode, and also not interpreting (the interpreter needs sync input) |
| if shared.Settings.BINARYEN_ASYNC_COMPILATION == 1 and shared.Building.is_wasm_only() and 'interpret' not in shared.Settings.BINARYEN_METHOD: |
| # async compilation requires a swappable module - we swap it in when it's ready |
| shared.Settings.SWAPPABLE_ASM_MODULE = 1 |
| else: |
| # if not wasm-only, we can't do async compilation as the build can run in other |
| # modes than wasm (like asm.js) which may not support an async step |
| shared.Settings.BINARYEN_ASYNC_COMPILATION = 0 |
| warning = 'This will reduce performance and compatibility (some browsers limit synchronous compilation), see https://github.com/kripken/emscripten/wiki/WebAssembly#codegen-effects' |
| if 'BINARYEN_ASYNC_COMPILATION=1' in settings_changes: |
| logging.warning('BINARYEN_ASYNC_COMPILATION requested, but disabled because of user options. ' + warning) |
| elif 'BINARYEN_ASYNC_COMPILATION=0' not in settings_changes: |
| logging.warning('BINARYEN_ASYNC_COMPILATION disabled due to user options. ' + warning) |
| # run safe-heap as a binaryen pass |
| if shared.Settings.SAFE_HEAP and shared.Building.is_wasm_only(): |
| if shared.Settings.BINARYEN_PASSES: |
| shared.Settings.BINARYEN_PASSES += ',' |
| shared.Settings.BINARYEN_PASSES += 'safe-heap' |
| if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: |
| # emulated function pointer casts is emulated in wasm using a binaryen pass |
| if shared.Settings.BINARYEN_PASSES: |
| shared.Settings.BINARYEN_PASSES += ',' |
| shared.Settings.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 |
| |
| # we will include the mem init data in the wasm, when we don't need the |
| # mem init file to be loadable by itself |
| shared.Settings.MEM_INIT_IN_WASM = 'asmjs' not in shared.Settings.BINARYEN_METHOD and \ |
| 'interpret-asm2wasm' not in shared.Settings.BINARYEN_METHOD and \ |
| not shared.Settings.USE_PTHREADS |
| |
| # wasm side modules have suffix .wasm |
| if shared.Settings.SIDE_MODULE and target.endswith('.js'): |
| logging.warning('output suffix .js requested, but wasm side modules are just wasm files; emitting only a .wasm, no .js') |
| |
| if options.separate_asm: |
| logging.error('cannot --separate-asm when emitting wasm, since not emitting asm.js') |
| sys.exit(1) |
| |
| # wasm outputs are only possible with a side wasm |
| if target.endswith(WASM_ENDINGS): |
| if not (shared.Settings.WASM and shared.Settings.SIDE_MODULE): |
| logging.warning('output file "%s" has a wasm suffix, but we cannot emit wasm by itself, except as a dynamic library (see SIDE_MODULE option). specify an output file with suffix .js or .html, and a wasm file will be created on the side' % target) |
| return 1 |
| |
| if shared.Settings.EVAL_CTORS: |
| if 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 |
| else: |
| if 'interpret' in shared.Settings.BINARYEN_METHOD: |
| logging.warning('disabling EVAL_CTORS as the bundled interpreter confuses the ctor tool') |
| shared.Settings.EVAL_CTORS = 0 |
| |
| # memory growth does not work in dynamic linking, except for wasm |
| if not shared.Settings.WASM and (shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE): |
| assert not shared.Settings.ALLOW_MEMORY_GROWTH, 'memory growth is not supported with shared asm.js modules' |
| |
| 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 or 'asmjs' in shared.Settings.BINARYEN_METHOD: |
| 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 |
| |
| # 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: |
| logging.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: |
| newargs.append('-g') |
| options.debug_level = max(options.debug_level, 2) |
| shared.Settings.BUNDLED_CD_DEBUG_FILE = target + ".cd" |
| options.js_libraries.append(shared.path_from_root('src', 'library_cyberdwarf.js')) |
| options.js_libraries.append(shared.path_from_root('src', 'library_debugger_toolkit.js')) |
| |
| if options.tracing: |
| if shared.Settings.ALLOW_MEMORY_GROWTH: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout'] |
| |
| if shared.Settings.ONLY_MY_CODE: |
| shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = [] |
| options.separate_asm = True |
| shared.Settings.FINALIZE_ASM_JS = False |
| |
| if shared.Settings.GLOBAL_BASE < 0: |
| shared.Settings.GLOBAL_BASE = 8 # default if nothing else sets it |
| |
| if shared.Settings.WASM_BACKEND: |
| if shared.Settings.SIMD: |
| newargs.append('-msimd128') |
| 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 and non-wasm BINARYEN_METHOD.' |
| |
| 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 |
| |
| logging.debug('compiling to bitcode') |
| |
| temp_files = [] |
| |
| # exit block 'parse arguments and setup' |
| log_time('parse arguments and setup') |
| |
| with ToolchainProfiler.profile_block('bitcodeize 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 + shared.EMSDK_CXX_OPTS + headers |
| if specified_target: |
| args += ['-o', specified_target] |
| args = system_libs.process_args(args, shared.Settings) |
| logging.debug("running (for precompiled headers): " + call + ' ' + ' '.join(args)) |
| return run_process([call] + args, check=False).returncode |
| |
| def get_bitcode_file(input_file): |
| if final_suffix not in JS_CONTAINING_SUFFIXES: |
| # no need for a temp file, just emit to the right place |
| if len(input_files) == 1: |
| # can just emit directly to the target |
| if specified_target: |
| if specified_target.endswith('/') or specified_target.endswith('\\') or os.path.isdir(specified_target): |
| return os.path.join(specified_target, os.path.basename(unsuffixed(input_file))) + options.default_object_extension |
| return specified_target |
| return unsuffixed(input_file) + final_ending |
| else: |
| if has_dash_c: |
| return unsuffixed(input_file) + options.default_object_extension |
| return in_temp(unsuffixed(uniquename(input_file)) + options.default_object_extension) |
| |
| # Request LLVM debug info if explicitly specified, or building bitcode with -g, or if building a source all the way to JS with -g |
| if use_source_map(options) or ((final_suffix not in JS_CONTAINING_SUFFIXES or (has_source_inputs and final_suffix in JS_CONTAINING_SUFFIXES)) and options.requested_debug == '-g'): |
| # do not save llvm debug info if js optimizer will wipe it out anyhow (but if source maps are used, keep it) |
| if use_source_map(options) or not (final_suffix in JS_CONTAINING_SUFFIXES and options.js_opts): |
| newargs.append('-g') # preserve LLVM debug info |
| options.debug_level = 4 |
| shared.Settings.DEBUG_LEVEL = 4 |
| |
| # Bitcode args generation code |
| def get_bitcode_args(input_files): |
| file_ending = filename_type_ending(input_files[0]) |
| args = [call] + newargs + input_files |
| if file_ending.endswith(CXX_ENDINGS): |
| args += shared.EMSDK_CXX_OPTS |
| 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 |
| |
| # -E preprocessor-only support |
| if '-E' in newargs or '-M' in newargs or '-MM' in newargs: |
| input_files = [x[1] for x in input_files] |
| cmd = get_bitcode_args(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) |
| logging.debug(('just preprocessor ' if '-E' in newargs else 'just dependencies: ') + ' '.join(cmd)) |
| return run_process(cmd, check=False).returncode |
| |
| def compile_source_file(i, input_file): |
| logging.debug('compiling source file: ' + input_file) |
| output_file = get_bitcode_file(input_file) |
| temp_files.append((i, output_file)) |
| args = get_bitcode_args([input_file]) + ['-emit-llvm', '-c', '-o', output_file] |
| logging.debug("running: " + ' '.join(shared.Building.doublequote_spaces(args))) # NOTE: Printing this line here in this specific format is important, it is parsed to implement the "emcc --cflags" command |
| if run_process(args, check=False).returncode != 0: |
| exit_with_error('compiler frontend failed to generate LLVM bitcode, halting') |
| 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 = filename_type_ending(input_file) |
| if file_ending.endswith(SOURCE_ENDINGS): |
| compile_source_file(i, input_file) |
| else: # bitcode |
| if file_ending.endswith(BITCODE_ENDINGS): |
| logging.debug('using bitcode file: ' + input_file) |
| temp_files.append((i, input_file)) |
| elif file_ending.endswith(DYNAMICLIB_ENDINGS) or shared.Building.is_ar(input_file): |
| logging.debug('using library file: ' + input_file) |
| temp_files.append((i, input_file)) |
| elif file_ending.endswith(ASSEMBLY_ENDINGS): |
| if not LEAVE_INPUTS_RAW: |
| logging.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 'bitcodeize inputs' |
| log_time('bitcodeize 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 = filename_type_ending(input_file) |
| if file_ending.endswith(SOURCE_ENDINGS): |
| temp_file = temp_files[pos][1] |
| logging.debug('optimizing %s', input_file) |
| # if DEBUG: |
| # shutil.copyfile(temp_file, os.path.join(shared.configuration.CANONICAL_TEMP_DIR, 'to_opt.bc')) # useful when LLVM opt aborts |
| 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) |
| |
| # Decide what we will link |
| stop_at_bitcode = final_suffix not in EXECUTABLE_SUFFIXES |
| |
| if stop_at_bitcode or not shared.Settings.WASM_BACKEND: |
| # Filter link flags, keeping only those that shared.Building.link knows |
| # how to deal with. We currently can't handle flags with options (like |
| # -Wl,-rpath,/bin:/lib, where /bin:/lib is an option for the -rpath |
| # flag). |
| link_flags = [f for f in link_flags if f[1] in SUPPORTED_LINKER_FLAGS] |
| |
| linker_inputs = [val for _, val in sorted(temp_files + link_flags)] |
| |
| # If we were just asked to generate bitcode, stop there |
| if stop_at_bitcode: |
| 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_ending) |
| else: |
| if 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_ending |
| 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) |
| else: |
| assert len(original_input_files) == 1 or not has_dash_c, 'fatal error: cannot specify -o with -c with multiple files' + str(sys.argv) + ':' + str(original_input_files) |
| # We have a specified target (-o <target>), which is not JavaScript or HTML, and |
| # we have multiple files: Link them |
| logging.debug('link: ' + str(linker_inputs) + specified_target) |
| # Sort arg tuples and pass the extracted values to link. |
| shared.Building.link(linker_inputs, specified_target) |
| logging.debug('stopping at bitcode') |
| 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 LLVM bitcode. 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 LLVM bitcode file, which you should then compile to .wasm or .js with SIDE_MODULE.') |
| if final_suffix.lower() in ['so', 'dylib', 'dll']: |
| logging.warning('When Emscripten compiles to a typical native suffix for shared libraries (.so, .dylib, .dll) then it emits an LLVM bitcode 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 file with LLVM bitcode, \'.bc\', which would avoid this warning.)') |
| return 0 |
| |
| # exit block 'process inputs' |
| log_time('process inputs') |
| |
| ## Continue on to create JavaScript |
| |
| with ToolchainProfiler.profile_block('calculate system libraries'): |
| logging.debug('will generate JavaScript') |
| |
| extra_files_to_link = [] |
| |
| # link in ports and system libraries, if necessary |
| if not LEAVE_INPUTS_RAW and \ |
| not shared.Settings.BUILD_AS_SHARED_LIB and \ |
| not shared.Settings.BOOTSTRAPPING_STRUCT_INFO and \ |
| not shared.Settings.ONLY_MY_CODE 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) |
| |
| # exit block 'calculate system libraries' |
| log_time('calculate system libraries') |
| |
| 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 |
| linker_inputs += extra_files_to_link |
| 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 BITCODE_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: |
| logging.debug('linking: ' + str(linker_inputs)) |
| # force archive contents to all be included, if just archives, or if linking shared modules |
| force_archive_contents = len([temp for i, temp in temp_files if not temp.endswith(STATICLIB_ENDINGS)]) == 0 or not shared.Building.can_build_standalone() |
| |
| # 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, options.llvm_opts, lto_level) |
| else: |
| final = shared.Building.link(linker_inputs, DEFAULT_FINAL, force_archive_contents=force_archive_contents, temp_files=misc_temp_files, just_calculate=just_calculate) |
| else: |
| 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: |
| logging.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 use_source_map(options) or shared.Settings.CYBERDWARF else ['-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): |
| logging.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 shared.Building.can_build_standalone() 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: |
| logging.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 |
| logging.debug('LLVM => JS') |
| extra_args = [] |
| if options.js_libraries: |
| extra_args = ['--libraries', ','.join(map(os.path.abspath, options.js_libraries))] |
| 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 = shared.Building.emscripten(final, append_ext=False, extra_args=extra_args) |
| save_intermediate('original') |
| |
| if shared.Settings.WASM_BACKEND: |
| # we also received wast and wasm at this stage |
| temp_basename = unsuffixed(final) |
| wasm_temp = temp_basename + '.wasm' |
| shutil.move(wasm_temp, wasm_binary_target) |
| open(wasm_text_target + '.mappedGlobals', 'w').write('{}') # no need for mapped globals for now, but perhaps some day |
| if use_source_map(options): |
| shutil.move(wasm_temp + '.map', wasm_binary_target + '.map') |
| |
| if shared.Settings.CYBERDWARF: |
| cd_target = final + '.cd' |
| shutil.move(cd_target, 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): |
| |
| # copying into the heap is risky when split - the chunks might be too small for the file package! |
| if shared.Settings.SPLIT_MEMORY and not options.no_heap_copy: |
| logging.info('Enabling --no-heap-copy because -s SPLIT_MEMORY=1 is being used with file_packager.py (pass --no-heap-copy to suppress this notification)') |
| options.no_heap_copy = True |
| |
| # 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: |
| logging.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 |
| |
| logging.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: |
| logging.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') |
| outfile = open(final, 'w') |
| # pre-js code goes right after the Module integration code (so it |
| # can use Module), we have a marker for it |
| outfile.write(src.replace('// {{PRE_JSES}}', options.pre_js)) |
| outfile.write(options.post_js) |
| outfile.close() |
| 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 |
| logging.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 shared.Settings.MEM_INIT_METHOD > 0 or embed_memfile(options): |
| memfile = target + '.mem' |
| shared.try_delete(memfile) |
| |
| 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 = [int(x or '0') for x in s.split(',')] |
| while membytes and membytes[-1] == 0: |
| membytes.pop() |
| 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 '' |
| |
| 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') |
| logging.debug('wrote memory initialization to %s', memfile) |
| else: |
| logging.debug('did not see memory initialization') |
| |
| if shared.Settings.USE_PTHREADS: |
| target_dir = os.path.dirname(os.path.abspath(target)) |
| shutil.copyfile(shared.path_from_root('src', 'pthread-main.js'), |
| os.path.join(target_dir, 'pthread-main.js')) |
| |
| # Generate the fetch-worker.js script for multithreaded emscripten_fetch() support if targeting pthreads. |
| if shared.Settings.FETCH and shared.Settings.USE_PTHREADS: |
| shared.make_fetch_worker(final, os.path.join(os.path.dirname(os.path.abspath(target)), 'fetch-worker.js')) |
| |
| # 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: |
| logging.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, and unless outlining (simplified ifs |
| # 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 shared.Settings.OUTLINING_LIMIT == 0 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 shared.Settings.OUTLINING_LIMIT > 0: |
| optimizer.queue += ['outline'] |
| optimizer.extra_info['sizeToOutline'] = shared.Settings.OUTLINING_LIMIT |
| |
| 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', 'js') |
| |
| 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', 'js') |
| |
| 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: |
| optimizer.flush() |
| |
| logging.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) |
| 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'), target + '.cd', 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) |
| generated_text_files_with_native_eols += [asm_target] |
| |
| if shared.Settings.WASM: |
| binaryen_method_sanity_check() |
| do_binaryen(target, asm_target, options, memfile, wasm_binary_target, |
| wasm_text_target, misc_temp_files, optimizer) |
| |
| if shared.Settings.MODULARIZE: |
| modularize() |
| |
| module_export_name_substitution() |
| |
| # The JS is now final. Move it to its final location |
| shutil.move(final, js_target) |
| |
| 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): |
| 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' |
| |
| if DEBUG: |
| logging.debug('total time: %.2f seconds', (time.time() - start_time)) |
| |
| finally: |
| try: |
| shutil.rmtree(temp_dir) |
| except: |
| pass |
| |
| return 0 |
| |
| |
| def parse_args(newargs): |
| options = EmccOptions() |
| settings_changes = [] |
| should_exit = False |
| |
| 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'): |
| 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'): |
| requested_level = newargs[i][2:] or '3' |
| options.debug_level = validate_arg_level(requested_level, 4, 'Invalid debug level: ' + newargs[i]) |
| options.requested_debug = newargs[i] |
| newargs[i] = '' |
| elif newargs[i] == '-profiling' or newargs[i] == '--profiling': |
| 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") |
| options.js_libraries.append(shared.path_from_root('src', 'library_trace.js')) |
| elif newargs[i] == '--emit-symbol-map': |
| options.emit_symbol_map = True |
| newargs[i] = '' |
| elif newargs[i] == '--bind': |
| options.bind = True |
| newargs[i] = '' |
| options.js_libraries.append(shared.path_from_root('src', 'embind', 'emval.js')) |
| options.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 = '' |
| 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.COMPILER_OPTS += ['-v'] |
| shared.check_sanity(force=True) |
| newargs[i] = '' |
| 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]) |
| options.js_libraries.append(newargs[i + 1]) |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--remove-duplicates': |
| logging.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': |
| logging.error('jcache is no longer supported') |
| newargs[i] = '' |
| elif newargs[i] == '--cache': |
| check_bad_eq(newargs[i]) |
| os.environ['EM_CACHE'] = newargs[i + 1] |
| shared.reconfigure_cache() |
| newargs[i] = '' |
| newargs[i + 1] = '' |
| elif newargs[i] == '--clear-cache': |
| logging.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': |
| logging.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] == '--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] == '-msse': |
| newargs.append('-D__SSE__=1') |
| newargs[i] = '' |
| elif newargs[i] == '-msse2': |
| newargs.append('-D__SSE__=1') |
| newargs.append('-D__SSE2__=1') |
| newargs[i] = '' |
| elif newargs[i] == '-msse3': |
| newargs.append('-D__SSE__=1') |
| newargs.append('-D__SSE2__=1') |
| newargs.append('-D__SSE3__=1') |
| newargs[i] = '' |
| elif newargs[i] == '-mssse3': |
| newargs.append('-D__SSE__=1') |
| newargs.append('-D__SSE2__=1') |
| newargs.append('-D__SSE3__=1') |
| newargs.append('-D__SSSE3__=1') |
| newargs[i] = '' |
| elif newargs[i] == '-msse4.1': |
| newargs.append('-D__SSE__=1') |
| newargs.append('-D__SSE2__=1') |
| newargs.append('-D__SSE3__=1') |
| newargs.append('-D__SSSE3__=1') |
| newargs.append('-D__SSE4_1__=1') |
| newargs[i] = '' |
| 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 |
| |
| if should_exit: |
| sys.exit(0) |
| |
| newargs = [arg for arg in newargs if arg is not ''] |
| return options, settings_changes, newargs |
| |
| |
| def emterpretify(js_target, optimizer, options): |
| global final |
| optimizer.flush('pre-emterpretify') |
| logging.debug('emterpretifying') |
| import json |
| try: |
| # move temp js to final position, alongside its mem init file |
| shutil.move(final, js_target) |
| args = [shared.PYTHON, |
| shared.path_from_root('tools', 'emterpretify.py'), |
| js_target, |
| final + '.em.js', |
| json.dumps(shared.Settings.EMTERPRETIFY_BLACKLIST), |
| json.dumps(shared.Settings.EMTERPRETIFY_WHITELIST), |
| json.dumps(shared.Settings.EMTERPRETIFY_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 + '"'] |
| run_process(args) |
| final = final + '.em.js' |
| finally: |
| shared.try_delete(js_target) |
| |
| if shared.Settings.EMTERPRETIFY_ADVISE: |
| logging.warning('halting compilation due to EMTERPRETIFY_ADVISE') |
| sys.exit(0) |
| |
| # minify (if requested) after emterpreter processing, and finalize output |
| logging.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 |
| logging.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): |
| logging.debug('generating source maps') |
| jsrun.run_js(shared.path_from_root('tools', 'source-maps', 'sourcemapper.js'), |
| shared.NODE_JS, js_transform_tempfiles + |
| ['--sourceRoot', os.getcwd(), |
| '--mapFileBaseName', target, |
| '--offset', str(0)]) |
| |
| |
| def separate_asm_js(final, asm_target): |
| """Separate out the asm.js code, if asked. Or, if necessary for another option""" |
| logging.debug('separating asm') |
| shared.check_call([shared.PYTHON, shared.path_from_root('tools', 'separate_asm.py'), final, asm_target, final]) |
| |
| # extra only-my-code logic |
| if shared.Settings.ONLY_MY_CODE: |
| temp = asm_target + '.only.js' |
| print(jsrun.run_js(shared.path_from_root('tools', 'js-optimizer.js'), shared.NODE_JS, args=[asm_target, 'eliminateDeadGlobals', 'last', 'asm'], stdout=open(temp, 'w'))) |
| shutil.move(temp, asm_target) |
| |
| |
| def binaryen_method_sanity_check(): |
| if shared.Settings.BINARYEN_METHOD: |
| methods = shared.Settings.BINARYEN_METHOD.split(',') |
| valid_methods = ['asmjs', 'native-wasm', 'interpret-s-expr', 'interpret-binary', 'interpret-asm2wasm'] |
| for m in methods: |
| if m.strip() not in valid_methods: |
| exit_with_error('Unrecognized BINARYEN_METHOD "' + m.strip() + '" specified! Please pass a comma-delimited list containing one or more of: ' + ','.join(valid_methods)) |
| |
| |
| def do_binaryen(target, asm_target, options, memfile, wasm_binary_target, |
| wasm_text_target, misc_temp_files, optimizer): |
| global final |
| logging.debug('using binaryen, with method: ' + shared.Settings.BINARYEN_METHOD) |
| binaryen_bin = shared.Building.get_binaryen_bin() |
| binaryen_lib = shared.Building.get_binaryen_lib() |
| # Emit wasm.js at the top of the js. This is *not* optimized with the rest of the code, since |
| # (1) it contains asm.js, whose validation would be broken, and (2) it's very large so it would |
| # be slow in cleanup/JSDCE etc. |
| # TODO: for html, it could be a separate script tag |
| # We need wasm.js if there is a chance the polyfill will be used. If the user sets |
| # BINARYEN_METHOD with something that doesn't use the polyfill, then we don't need it. |
| if not shared.Settings.BINARYEN_METHOD or 'interpret' in shared.Settings.BINARYEN_METHOD: |
| logging.debug('integrating wasm.js polyfill interpreter') |
| wasm_js = open(os.path.join(binaryen_lib, 'wasm.js')).read() |
| wasm_js = wasm_js.replace('EMSCRIPTEN_', 'emscripten_') # do not confuse the markers |
| js = open(final).read() |
| combined = open(final, 'w') |
| combined.write(wasm_js) |
| combined.write('\n//^wasm.js\n') |
| combined.write(js) |
| combined.close() |
| # normally we emit binary, but for debug info, we might emit text first |
| wrote_wasm_text = False |
| debug_info = options.debug_level >= 2 or options.profiling_funcs |
| 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.safe_copy(asm_target, os.path.join(shared.get_emscripten_temp_dir(), os.path.basename(asm_target))) |
| cmd = [os.path.join(binaryen_bin, 'asm2wasm'), asm_target, '--total-memory=' + str(shared.Settings.TOTAL_MEMORY)] |
| if shared.Settings.BINARYEN_TRAP_MODE in ('js', 'clamp', 'allow'): |
| cmd += ['--trap-mode=' + shared.Settings.BINARYEN_TRAP_MODE] |
| else: |
| exit_with_error('invalid BINARYEN_TRAP_MODE value: ' + shared.Settings.BINARYEN_TRAP_MODE + ' (should be js/clamp/allow)') |
| 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 debug_info: |
| cmd += ['-g'] |
| if emit_symbol_map: |
| cmd += ['--symbolmap=' + 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'] |
| wrote_wasm_text = True |
| logging.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] |
| if debug_info: |
| cmd += ['-g'] |
| if use_source_map(options): |
| cmd += ['--source-map=' + wasm_binary_target + '.map'] |
| if options.source_map_base: |
| cmd += ['--source-map-url=' + options.source_map_base + os.path.basename(wasm_binary_target) + '.map'] |
| logging.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 shared.Settings.BINARYEN_PASSES: |
| shutil.move(wasm_binary_target, wasm_binary_target + '.pre') |
| # BINARYEN_PASSES is comma-separated, and we support both '-'-prefixed and unprefixed pass names |
| passes = [('--' + p) if p[0] != '-' else p for p in shared.Settings.BINARYEN_PASSES.split(',')] |
| cmd = [os.path.join(binaryen_bin, 'wasm-opt'), wasm_binary_target + '.pre', '-o', wasm_binary_target] + passes |
| if debug_info: |
| cmd += ['-g'] # preserve the debug info |
| logging.debug('wasm-opt on BINARYEN_PASSES: ' + ' '.join(cmd)) |
| shared.check_call(cmd) |
| if not wrote_wasm_text and 'interpret-s-expr' in shared.Settings.BINARYEN_METHOD: |
| cmd = [os.path.join(binaryen_bin, 'wasm-dis'), wasm_binary_target, '-o', wasm_text_target] |
| logging.debug('wasm-dis (binary => text): ' + ' '.join(cmd)) |
| shared.check_call(cmd) |
| if shared.Settings.BINARYEN_SCRIPTS: |
| binaryen_scripts = os.path.join(shared.Settings.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(','): |
| logging.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: |
| if DEBUG: |
| save_intermediate('pre-eval-ctors', 'js') |
| shutil.copyfile(wasm_binary_target, os.path.join(shared.get_emscripten_temp_dir(), 'pre-eval-ctors.wasm')) |
| shared.Building.eval_ctors(final, wasm_binary_target, binaryen_bin, debug_info=debug_info) |
| # after generating the wasm, do some final operations |
| if not shared.Settings.WASM_BACKEND: |
| if shared.Settings.SIDE_MODULE: |
| wso = shared.WebAssembly.make_shared_library(final, wasm_binary_target) |
| # 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 |
| sys.exit(0) # and we are done. |
| if options.opt_level >= 2: |
| # minify the JS |
| optimizer.do_minify() # calculate how to minify |
| if optimizer.cleanup_shell or options.use_closure_compiler: |
| save_intermediate_with_wasm('preclean', wasm_binary_target) |
| final = shared.Building.minify_wasm_js(js_file=final, |
| wasm_file=wasm_binary_target, |
| expensive_optimizations=options.opt_level >= 3 or options.shrink_level > 0, |
| minify_whitespace=optimizer.minify_whitespace, |
| use_closure_compiler=options.use_closure_compiler, |
| debug_info=debug_info, |
| emit_symbol_map=emit_symbol_map) |
| save_intermediate_with_wasm('postclean', wasm_binary_target) |
| # replace placeholder strings with correct subresource locations |
| if shared.Settings.SINGLE_FILE: |
| f = open(final, 'r') |
| js = f.read() |
| f.close() |
| f = open(final, 'w') |
| for target, replacement_string, should_embed in ( |
| (wasm_text_target, shared.FilenameReplacementStrings.WASM_TEXT_FILE, True), |
| (wasm_binary_target, shared.FilenameReplacementStrings.WASM_BINARY_FILE, True), |
| (asm_target, shared.FilenameReplacementStrings.ASMJS_CODE_FILE, not shared.Building.is_wasm_only()) |
| ): |
| 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) |
| f.write(js) |
| f.close() |
| |
| |
| def modularize(): |
| global final |
| logging.debug('Modularizing, assigning to var ' + shared.Settings.EXPORT_NAME) |
| src = open(final).read() |
| final = final + '.modular.js' |
| f = open(final, 'w') |
| |
| # Included code may refer to Module (e.g. from file packager), so alias it |
| f.write('''var %(EXPORT_NAME)s = function(%(EXPORT_NAME)s) { |
| %(EXPORT_NAME)s = %(EXPORT_NAME)s || {}; |
| |
| %(src)s |
| |
| return %(EXPORT_NAME)s; |
| }; |
| %(EXPORT_NAME)s = %(EXPORT_NAME)s.bind({ |
| _currentScript: typeof document !== 'undefined' ? document.currentScript : undefined |
| })%(instantiate)s; |
| ''' % { |
| 'EXPORT_NAME': shared.Settings.EXPORT_NAME, |
| 'src': src, |
| 'instantiate': '()' if shared.Settings.MODULARIZE_INSTANCE else '' |
| }) |
| |
| # 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) |
| else: |
| 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 |
| }) |
| |
| f.close() |
| save_intermediate('modularized', 'js') |
| |
| |
| def module_export_name_substitution(): |
| global final |
| logging.debug('Private module export name substitution with ' + shared.Settings.EXPORT_NAME) |
| src = open(final).read() |
| final = final + '.module_export_name_substitution.js' |
| f = open(final, 'w') |
| replacement = "typeof %(EXPORT_NAME)s !== 'undefined' ? %(EXPORT_NAME)s : {}" % {"EXPORT_NAME": shared.Settings.EXPORT_NAME} |
| f.write(src.replace(shared.JS.module_export_name_substitution_pattern, replacement)) |
| f.close() |
| save_intermediate('module_export_name_substitution', 'js') |
| |
| |
| def generate_html(target, options, js_target, target_basename, |
| asm_target, wasm_binary_target, |
| memfile, optimizer): |
| script = ScriptSource() |
| |
| logging.debug('generating HTML') |
| 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 and 'asmjs' not in shared.Settings.BINARYEN_METHOD): |
| assert len(asm_mods) == 0, '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.BINARYEN_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) |
| |
| # when script.inline isn't empty, add required helper functions such as tryParseAsDataURI |
| if script.inline: |
| for file in ['arrayUtils.js', 'base64Utils.js', 'URIUtils.js']: |
| f = open(shared.path_from_root('src', file), 'r') |
| script.inline = f.read() + script.inline |
| f.close() |
| |
| 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 = open(js_target, 'r') |
| js_contents += js.read() |
| js.close() |
| shared.try_delete(js_target) |
| script.src = None |
| script.inline = js_contents |
| |
| html = open(target, 'wb') |
| html_contents = shell.replace('{{{ SCRIPT }}}', script.replacement()) |
| html_contents = tools.line_endings.convert_line_endings(html_contents, '\n', options.output_eol) |
| html.write(asbytes(html_contents)) |
| html.close() |
| |
| |
| 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 system_js_libraries_setting_str(libs, lib_dirs, settings_changes, input_files): |
| libraries = [] |
| |
| # Find library files |
| for i, lib in libs: |
| logging.debug('looking for library "%s"', lib) |
| found = False |
| for prefix in LIB_PREFIXES: |
| for suff in STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS: |
| name = prefix + lib + suff |
| for lib_dir in lib_dirs: |
| path = os.path.join(lib_dir, name) |
| if os.path.exists(path): |
| logging.debug('found library "%s" at %s', lib, path) |
| input_files.append((i, path)) |
| found = True |
| break |
| if found: |
| break |
| if found: |
| break |
| if not found: |
| libraries += shared.Building.path_to_system_js_libraries(lib) |
| |
| # Certain linker flags imply some link libraries to be pulled in by default. |
| libraries += shared.Building.path_to_system_js_libraries_for_settings(settings_changes) |
| return 'SYSTEM_JS_LIBRARIES="' + ','.join(libraries) + '"' |
| |
| |
| 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] |
| else: |
| 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-> ," |
| assert len(current), "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 |
| assert index < len(values), "unclosed quoted string. expected final character to be '%s' in '%s'" % (first, values[start]) |
| new = values[index].rstrip() |
| if not len(new) == 0 and new[-1] == first: |
| if start == index: |
| result.append(current[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 |
| |
| if text[0] == '[': |
| text = text.rstrip() |
| assert text[-1] == ']', 'unclosed opened string list. expected final character to be "]" in "%s"' % (text) |
| inner = text[1:-1] |
| if inner.strip() == "": |
| return [] |
| else: |
| return parse_string_list_members(inner, ",") |
| else: |
| try: |
| return int(text) |
| except ValueError: |
| return parse_string_value(text) |
| |
| |
| def check_bad_eq(arg): |
| assert '=' not in arg, 'Invalid parameter (do not use "=" with "--" options)' |
| |
| |
| def validate_arg_level(level_string, max_level, err_msg, clamp=False): |
| try: |
| level = int(level_string) |
| if clamp: |
| if level > max_level: |
| logging.warning("optimization level '-O" + level_string + "' is not supported; using '-O" + str(max_level) + "' instead") |
| level = max_level |
| assert 0 <= level <= max_level |
| except: |
| raise Exception(err_msg) |
| return level |
| |
| |
| if __name__ == '__main__': |
| try: |
| sys.exit(run()) |
| except KeyboardInterrupt: |
| logging.warning("KeyboardInterrupt") |
| sys.exit(1) |
| except shared.FatalError as e: |
| logging.error(str(e)) |
| sys.exit(1) |