3310 строки
136 KiB
Python
Executable File
3310 строки
136 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright 2011 The Emscripten Authors. All rights reserved.
|
|
# Emscripten is available under two separate licenses, the MIT license and the
|
|
# University of Illinois/NCSA Open Source License. Both these licenses can be
|
|
# found in the LICENSE file.
|
|
|
|
"""emcc - compiler helper script
|
|
=============================
|
|
|
|
emcc is a drop-in replacement for a compiler like gcc or clang.
|
|
|
|
See emcc --help for details.
|
|
|
|
emcc can be influenced by a few environment variables:
|
|
|
|
EMCC_DEBUG - "1" will log out useful information during compilation, as well as
|
|
save each compiler step as an emcc-* file in the temp dir
|
|
(by default /tmp/emscripten_temp). "2" will save additional emcc-*
|
|
steps, that would normally not be separately produced (so this
|
|
slows down compilation).
|
|
|
|
EMMAKEN_NO_SDK - Will tell emcc *not* to use the emscripten headers. Instead
|
|
your system headers will be used.
|
|
"""
|
|
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import shlex
|
|
import stat
|
|
import sys
|
|
import time
|
|
import base64
|
|
from enum import Enum
|
|
from subprocess import PIPE
|
|
|
|
import emscripten
|
|
from tools import shared, system_libs
|
|
from tools import colored_logger, diagnostics, building
|
|
from tools.shared import unsuffixed, unsuffixed_basename, WINDOWS, safe_move, safe_copy
|
|
from tools.shared import run_process, asbytes, read_and_preprocess, exit_with_error, DEBUG
|
|
from tools.shared import do_replace
|
|
from tools.response_file import substitute_response_files
|
|
from tools.minimal_runtime_shell import generate_minimal_runtime_html
|
|
import tools.line_endings
|
|
from tools.toolchain_profiler import ToolchainProfiler
|
|
from tools import js_manipulation
|
|
from tools import wasm2c
|
|
from tools import webassembly
|
|
from tools import config
|
|
|
|
if __name__ == '__main__':
|
|
ToolchainProfiler.record_process_start()
|
|
|
|
try:
|
|
from urllib.parse import quote
|
|
except ImportError:
|
|
# Python 2 compatibility
|
|
from urllib import quote
|
|
|
|
logger = logging.getLogger('emcc')
|
|
|
|
# endings = dot + a suffix, safe to test by filename.endswith(endings)
|
|
C_ENDINGS = ('.c', '.i')
|
|
CXX_ENDINGS = ('.cpp', '.cxx', '.cc', '.c++', '.CPP', '.CXX', '.C', '.CC', '.C++', '.ii')
|
|
OBJC_ENDINGS = ('.m', '.mi')
|
|
OBJCXX_ENDINGS = ('.mm', '.mii')
|
|
ASSEMBLY_CPP_ENDINGS = ('.S',)
|
|
SPECIAL_ENDINGLESS_FILENAMES = (os.devnull,)
|
|
|
|
SOURCE_ENDINGS = C_ENDINGS + CXX_ENDINGS + OBJC_ENDINGS + OBJCXX_ENDINGS + SPECIAL_ENDINGLESS_FILENAMES + ASSEMBLY_CPP_ENDINGS
|
|
C_ENDINGS = C_ENDINGS + SPECIAL_ENDINGLESS_FILENAMES # consider the special endingless filenames like /dev/null to be C
|
|
|
|
EXECUTABLE_ENDINGS = ('.wasm', '.html', '.js', '.mjs', '.out', '')
|
|
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', '.s')
|
|
HEADER_ENDINGS = ('.h', '.hxx', '.hpp', '.hh', '.H', '.HXX', '.HPP', '.HH')
|
|
|
|
# Supported LLD flags which we will pass through to the linker.
|
|
SUPPORTED_LINKER_FLAGS = (
|
|
'--start-group', '--end-group',
|
|
'-(', '-)',
|
|
'--whole-archive', '--no-whole-archive',
|
|
'-whole-archive', '-no-whole-archive'
|
|
)
|
|
|
|
# Unsupported LLD flags which we will ignore.
|
|
# Maps to true if the flag takes an argument.
|
|
UNSUPPORTED_LLD_FLAGS = {
|
|
# macOS-specific linker flag that libtool (ltmain.sh) will if macOS is detected.
|
|
'-bind_at_load': False,
|
|
'-M': False,
|
|
# wasm-ld doesn't support soname or other dynamic linking flags (yet). Ignore them
|
|
# in order to aid build systems that want to pass these flags.
|
|
'-soname': True,
|
|
'-allow-shlib-undefined': False,
|
|
'-rpath': True,
|
|
'-rpath-link': True,
|
|
'-version-script': True,
|
|
}
|
|
|
|
LIB_PREFIXES = ('', 'lib')
|
|
|
|
DEFAULT_ASYNCIFY_IMPORTS = [
|
|
'emscripten_sleep', 'emscripten_wget', 'emscripten_wget_data', 'emscripten_idb_load',
|
|
'emscripten_idb_store', 'emscripten_idb_delete', 'emscripten_idb_exists',
|
|
'emscripten_idb_load_blob', 'emscripten_idb_store_blob', 'SDL_Delay',
|
|
'emscripten_scan_registers', 'emscripten_lazy_load_code',
|
|
'emscripten_fiber_swap',
|
|
'wasi_snapshot_preview1.fd_sync', '__wasi_fd_sync', '_emval_await']
|
|
|
|
# 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'],
|
|
}
|
|
|
|
# Target options
|
|
final_js = None
|
|
|
|
UBSAN_SANITIZERS = {
|
|
'alignment',
|
|
'bool',
|
|
'builtin',
|
|
'bounds',
|
|
'enum',
|
|
'float-cast-overflow',
|
|
'float-divide-by-zero',
|
|
'function',
|
|
'implicit-unsigned-integer-truncation',
|
|
'implicit-signed-integer-truncation',
|
|
'implicit-integer-sign-change',
|
|
'integer-divide-by-zero',
|
|
'nonnull-attribute',
|
|
'null',
|
|
'nullability-arg',
|
|
'nullability-assign',
|
|
'nullability-return',
|
|
'object-size',
|
|
'pointer-overflow',
|
|
'return',
|
|
'returns-nonnull-attribute',
|
|
'shift',
|
|
'signed-integer-overflow',
|
|
'unreachable',
|
|
'unsigned-integer-overflow',
|
|
'vla-bound',
|
|
'vptr',
|
|
'undefined',
|
|
'undefined-trap',
|
|
'implicit-integer-truncation',
|
|
'implicit-integer-arithmetic-value-change',
|
|
'implicit-conversion',
|
|
'integer',
|
|
'nullability',
|
|
}
|
|
|
|
|
|
VALID_ENVIRONMENTS = ('web', 'webview', 'worker', 'node', 'shell')
|
|
|
|
|
|
# this function uses the global 'final' variable, which contains the current
|
|
# final output file. if a method alters final, and calls this method, then it
|
|
# must modify final globally (i.e. it can't receive final as a param and
|
|
# return it)
|
|
# TODO: refactor all this, a singleton that abstracts over the final output
|
|
# and saving of intermediates
|
|
def save_intermediate(name, suffix='js'):
|
|
if not DEBUG:
|
|
return
|
|
if not final_js:
|
|
logger.debug('(not saving intermediate %s because not generating JS)' % name)
|
|
return
|
|
building.save_intermediate(final_js, name + '.' + suffix)
|
|
|
|
|
|
def save_intermediate_with_wasm(name, wasm_binary):
|
|
if not DEBUG:
|
|
return
|
|
save_intermediate(name) # save the js
|
|
building.save_intermediate(wasm_binary, name + '.wasm')
|
|
|
|
|
|
class TimeLogger(object):
|
|
last = time.time()
|
|
|
|
@staticmethod
|
|
def update():
|
|
TimeLogger.last = time.time()
|
|
|
|
|
|
def log_time(name):
|
|
"""Log out times for emcc stages"""
|
|
if DEBUG:
|
|
now = time.time()
|
|
logger.debug('emcc step "%s" took %.2f seconds', name, now - TimeLogger.last)
|
|
TimeLogger.update()
|
|
|
|
|
|
def base64_encode(b):
|
|
b64 = base64.b64encode(b)
|
|
if type(b64) == bytes:
|
|
return b64.decode('ascii')
|
|
else:
|
|
return b64
|
|
|
|
|
|
class OFormat(Enum):
|
|
WASM = 1
|
|
JS = 2
|
|
MJS = 3
|
|
HTML = 4
|
|
BARE = 5
|
|
|
|
|
|
class EmccOptions(object):
|
|
def __init__(self):
|
|
self.output_file = None
|
|
self.post_link = False
|
|
self.executable = False
|
|
self.compiler_wrapper = None
|
|
self.oformat = None
|
|
self.requested_debug = ''
|
|
self.profiling = False
|
|
self.profiling_funcs = False
|
|
self.tracing = False
|
|
self.emit_symbol_map = False
|
|
self.llvm_opts = None
|
|
self.use_closure_compiler = None
|
|
self.closure_args = []
|
|
self.js_transform = None
|
|
self.pre_js = '' # before all js
|
|
self.post_js = '' # after all js
|
|
self.extern_pre_js = '' # before all js, external to optimized code
|
|
self.extern_post_js = '' # after all js, external to optimized code
|
|
self.preload_files = []
|
|
self.embed_files = []
|
|
self.exclude_files = []
|
|
self.ignore_dynamic_linking = False
|
|
self.shell_path = shared.path_from_root('src', 'shell.html')
|
|
self.source_map_base = ''
|
|
self.emrun = False
|
|
self.cpu_profiler = False
|
|
self.thread_profiler = False
|
|
self.memory_profiler = False
|
|
self.memory_init_file = None
|
|
self.use_preload_cache = False
|
|
self.use_preload_plugins = False
|
|
self.proxy_to_worker = False
|
|
self.default_object_extension = '.o'
|
|
self.valid_abspaths = []
|
|
self.cfi = False
|
|
# Specifies the line ending format to use for all generated text files.
|
|
# Defaults to using the native EOL on each platform (\r\n on Windows, \n on
|
|
# Linux & MacOS)
|
|
self.output_eol = os.linesep
|
|
self.no_entry = False
|
|
self.shared = False
|
|
self.relocatable = False
|
|
|
|
|
|
def will_metadce():
|
|
# The metadce JS parsing code does not currently support the JS that gets generated
|
|
# when assertions are enabled.
|
|
if shared.Settings.ASSERTIONS:
|
|
return False
|
|
return shared.Settings.OPT_LEVEL >= 3 or shared.Settings.SHRINK_LEVEL >= 1
|
|
|
|
|
|
def setup_environment_settings():
|
|
# Environment setting based on user input
|
|
environments = shared.Settings.ENVIRONMENT.split(',')
|
|
if any([x for x in environments if x not in VALID_ENVIRONMENTS]):
|
|
exit_with_error('Invalid environment specified in "ENVIRONMENT": ' + shared.Settings.ENVIRONMENT + '. Should be one of: ' + ','.join(VALID_ENVIRONMENTS))
|
|
|
|
shared.Settings.ENVIRONMENT_MAY_BE_WEB = not shared.Settings.ENVIRONMENT or 'web' in environments
|
|
shared.Settings.ENVIRONMENT_MAY_BE_WEBVIEW = not shared.Settings.ENVIRONMENT or 'webview' in environments
|
|
shared.Settings.ENVIRONMENT_MAY_BE_NODE = not shared.Settings.ENVIRONMENT or 'node' in environments
|
|
shared.Settings.ENVIRONMENT_MAY_BE_SHELL = not shared.Settings.ENVIRONMENT or 'shell' in environments
|
|
|
|
# The worker case also includes Node.js workers when pthreads are
|
|
# enabled and Node.js is one of the supported environments for the build to
|
|
# run on. Node.js workers are detected as a combination of
|
|
# ENVIRONMENT_IS_WORKER and ENVIRONMENT_IS_NODE.
|
|
shared.Settings.ENVIRONMENT_MAY_BE_WORKER = \
|
|
not shared.Settings.ENVIRONMENT or \
|
|
'worker' in environments or \
|
|
(shared.Settings.ENVIRONMENT_MAY_BE_NODE and shared.Settings.USE_PTHREADS)
|
|
|
|
if not shared.Settings.ENVIRONMENT_MAY_BE_WORKER and shared.Settings.PROXY_TO_WORKER:
|
|
exit_with_error('If you specify --proxy-to-worker and specify a "-s ENVIRONMENT=" directive, it must include "worker" as a target! (Try e.g. -s ENVIRONMENT=web,worker)')
|
|
|
|
if not shared.Settings.ENVIRONMENT_MAY_BE_WORKER and shared.Settings.USE_PTHREADS:
|
|
exit_with_error('When building with multithreading enabled and a "-s ENVIRONMENT=" directive is specified, it must include "worker" as a target! (Try e.g. -s ENVIRONMENT=web,worker)')
|
|
|
|
|
|
def minify_whitespace():
|
|
return shared.Settings.OPT_LEVEL >= 2 and shared.Settings.DEBUG_LEVEL == 0
|
|
|
|
|
|
def embed_memfile():
|
|
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 shared.Settings.GENERATE_SOURCE_MAP)))
|
|
|
|
|
|
def expand_byte_size_suffixes(value):
|
|
"""Given a string with KB/MB size suffixes, such as "32MB", computes how
|
|
many bytes that is and returns it as an integer.
|
|
"""
|
|
value = value.strip()
|
|
match = re.match(r'^(\d+)\s*([kmgt]?b)?$', value, re.I)
|
|
if not match:
|
|
exit_with_error("invalid byte size `%s`. Valid suffixes are: kb, mb, gb, tb" % value)
|
|
value, suffix = match.groups()
|
|
value = int(value)
|
|
if suffix:
|
|
size_suffixes = {suffix: 1024 ** i for i, suffix in enumerate(['b', 'kb', 'mb', 'gb', 'tb'])}
|
|
value *= size_suffixes[suffix.lower()]
|
|
return value
|
|
|
|
|
|
def apply_settings(changes):
|
|
"""Take a list of settings in form `NAME=VALUE` and apply them to the global
|
|
Settings object.
|
|
"""
|
|
|
|
def standardize_setting_change(key, value):
|
|
# boolean NO_X settings are aliases for X
|
|
# (note that *non*-boolean setting values have special meanings,
|
|
# and we can't just flip them, so leave them as-is to be
|
|
# handled in a special way later)
|
|
if key.startswith('NO_') and value in ('0', '1'):
|
|
key = key[3:]
|
|
value = str(1 - int(value))
|
|
return key, value
|
|
|
|
for change in changes:
|
|
key, value = change.split('=', 1)
|
|
key, value = standardize_setting_change(key, value)
|
|
|
|
if key in shared.Settings.internal_settings:
|
|
exit_with_error('%s is an internal setting and cannot be set from command line', key)
|
|
|
|
# map legacy settings which have aliases to the new names
|
|
# but keep the original key so errors are correctly reported via the `setattr` below
|
|
user_key = key
|
|
if key in shared.Settings.legacy_settings and key in shared.Settings.alt_names:
|
|
key = shared.Settings.alt_names[key]
|
|
|
|
# In those settings fields that represent amount of memory, translate suffixes to multiples of 1024.
|
|
if key in ('TOTAL_STACK', 'INITIAL_MEMORY', 'MEMORY_GROWTH_LINEAR_STEP', 'MEMORY_GROWTH_GEOMETRIC_STEP',
|
|
'GL_MAX_TEMP_BUFFER_SIZE', 'MAXIMUM_MEMORY', 'DEFAULT_PTHREAD_STACK_SIZE'):
|
|
value = str(expand_byte_size_suffixes(value))
|
|
|
|
if value and value[0] == '@':
|
|
filename = value[1:]
|
|
if not os.path.exists(filename):
|
|
exit_with_error('%s: file not found parsing argument: %s' % (filename, change))
|
|
value = open(filename).read()
|
|
else:
|
|
value = value.replace('\\', '\\\\')
|
|
try:
|
|
value = parse_value(value)
|
|
except Exception as e:
|
|
exit_with_error('a problem occurred in evaluating the content after a "-s", specifically "%s": %s', change, str(e))
|
|
|
|
# Do some basic type checking by comparing to the existing settings.
|
|
# Sadly we can't do this generically in the SettingsManager since there are settings
|
|
# that so change types internally over time.
|
|
existing = getattr(shared.Settings, user_key, None)
|
|
if existing is not None:
|
|
# We only currently worry about lists vs non-lists.
|
|
if (type(existing) == list) != (type(value) == list):
|
|
exit_with_error('setting `%s` expects `%s` but got `%s`' % (user_key, type(existing), type(value)))
|
|
setattr(shared.Settings, user_key, value)
|
|
|
|
if key == 'EXPORTED_FUNCTIONS':
|
|
# used for warnings in emscripten.py
|
|
shared.Settings.USER_EXPORTED_FUNCTIONS = shared.Settings.EXPORTED_FUNCTIONS[:]
|
|
|
|
# TODO(sbc): Remove this legacy way.
|
|
if key == 'WASM_OBJECT_FILES':
|
|
shared.Settings.LTO = 0 if value else 'full'
|
|
|
|
|
|
def is_ar_file_with_missing_index(archive_file):
|
|
# We parse the archive header outselves because llvm-nm --print-armap is slower and less
|
|
# reliable.
|
|
# See: https://github.com/emscripten-core/emscripten/issues/10195
|
|
archive_header = b'!<arch>\n'
|
|
file_header_size = 60
|
|
|
|
with open(archive_file, 'rb') as f:
|
|
header = f.read(len(archive_header))
|
|
if header != archive_header:
|
|
# This is not even an ar file
|
|
return False
|
|
file_header = f.read(file_header_size)
|
|
if len(file_header) != file_header_size:
|
|
# We don't have any file entires at all so we don't consider the index missing
|
|
return False
|
|
|
|
name = file_header[:16].strip()
|
|
# If '/' is the name of the first file we have an index
|
|
return name != b'/'
|
|
|
|
|
|
def ensure_archive_index(archive_file):
|
|
# Fastcomp linking works without archive indexes.
|
|
if not shared.Settings.AUTO_ARCHIVE_INDEXES:
|
|
return
|
|
if is_ar_file_with_missing_index(archive_file):
|
|
diagnostics.warning('emcc', '%s: archive is missing an index; Use emar when creating libraries to ensure an index is created', archive_file)
|
|
diagnostics.warning('emcc', '%s: adding index', archive_file)
|
|
run_process([shared.LLVM_RANLIB, archive_file])
|
|
|
|
|
|
def get_all_js_syms():
|
|
# Runs the js compiler to generate a list of all symbols available in the JS
|
|
# libraries. This must be done separately for each linker invokation since the
|
|
# list of symbols depends on what settings are used.
|
|
# TODO(sbc): Find a way to optimize this. Potentially we could add a super-set
|
|
# mode of the js compiler that would generate a list of all possible symbols
|
|
# that could be checked in.
|
|
old_full = shared.Settings.INCLUDE_FULL_LIBRARY
|
|
try:
|
|
# Temporarily define INCLUDE_FULL_LIBRARY since we want a full list
|
|
# of all available JS library functions.
|
|
shared.Settings.INCLUDE_FULL_LIBRARY = True
|
|
shared.Settings.ONLY_CALC_JS_SYMBOLS = True
|
|
emscripten.generate_struct_info()
|
|
glue, forwarded_data = emscripten.compile_settings()
|
|
forwarded_json = json.loads(forwarded_data)
|
|
library_fns = forwarded_json['Functions']['libraryFunctions']
|
|
library_fns_list = []
|
|
for name in library_fns:
|
|
if shared.is_c_symbol(name):
|
|
name = shared.demangle_c_symbol_name(name)
|
|
library_fns_list.append(name)
|
|
finally:
|
|
shared.Settings.ONLY_CALC_JS_SYMBOLS = False
|
|
shared.Settings.INCLUDE_FULL_LIBRARY = old_full
|
|
|
|
return library_fns_list
|
|
|
|
|
|
def filter_link_flags(flags, using_lld):
|
|
def is_supported(f):
|
|
if using_lld:
|
|
for flag, takes_arg in UNSUPPORTED_LLD_FLAGS.items():
|
|
# lld allows various flags to have either a single -foo or double --foo
|
|
if f.startswith(flag) or f.startswith('-' + flag):
|
|
diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f)
|
|
return False, takes_arg
|
|
return True, False
|
|
else:
|
|
if f in SUPPORTED_LINKER_FLAGS:
|
|
return True, False
|
|
# Silently ignore -l/-L flags when not using lld. If using lld allow
|
|
# them to pass through the linker
|
|
if f.startswith('-l') or f.startswith('-L'):
|
|
return False, False
|
|
diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f)
|
|
return False, False
|
|
|
|
results = []
|
|
skip_next = False
|
|
for f in flags:
|
|
if skip_next:
|
|
skip_next = False
|
|
continue
|
|
keep, skip_next = is_supported(f[1])
|
|
if keep:
|
|
results.append(f)
|
|
|
|
return results
|
|
|
|
|
|
def fix_windows_newlines(text):
|
|
# Avoid duplicating \r\n to \r\r\n when writing out text.
|
|
if WINDOWS:
|
|
text = text.replace('\r\n', '\n')
|
|
return text
|
|
|
|
|
|
def cxx_to_c_compiler(cxx):
|
|
# Convert C++ compiler name into C compiler name
|
|
dirname, basename = os.path.split(cxx)
|
|
basename = basename.replace('clang++', 'clang').replace('g++', 'gcc').replace('em++', 'emcc')
|
|
return os.path.join(dirname, basename)
|
|
|
|
|
|
def get_binaryen_passes():
|
|
# run the binaryen optimizer in -O2+. in -O0 we don't need it obviously, while
|
|
# in -O1 we don't run it as the LLVM optimizer has been run, and it does the
|
|
# great majority of the work; not running the binaryen optimizer in that case
|
|
# keeps -O1 mostly-optimized while compiling quickly and without rewriting
|
|
# DWARF etc.
|
|
run_binaryen_optimizer = shared.Settings.OPT_LEVEL >= 2
|
|
|
|
passes = []
|
|
# safe heap must run before post-emscripten, so post-emscripten can apply the sbrk ptr
|
|
if shared.Settings.SAFE_HEAP:
|
|
passes += ['--safe-heap']
|
|
if shared.Settings.MEMORY64 == 2:
|
|
passes += ['--memory64-lowering']
|
|
if run_binaryen_optimizer:
|
|
passes += ['--post-emscripten']
|
|
if not shared.Settings.EXIT_RUNTIME:
|
|
passes += ['--no-exit-runtime']
|
|
if run_binaryen_optimizer:
|
|
passes += [building.opt_level_to_str(shared.Settings.OPT_LEVEL, shared.Settings.SHRINK_LEVEL)]
|
|
elif shared.Settings.STANDALONE_WASM:
|
|
# even if not optimizing, make an effort to remove all unused imports and
|
|
# exports, to make the wasm as standalone as possible
|
|
passes += ['--remove-unused-module-elements']
|
|
# when optimizing, use the fact that low memory is never used (1024 is a
|
|
# hardcoded value in the binaryen pass)
|
|
if run_binaryen_optimizer and shared.Settings.GLOBAL_BASE >= 1024:
|
|
passes += ['--low-memory-unused']
|
|
if shared.Settings.AUTODEBUG:
|
|
# adding '--flatten' here may make these even more effective
|
|
passes += ['--instrument-locals']
|
|
passes += ['--log-execution']
|
|
passes += ['--instrument-memory']
|
|
if shared.Settings.LEGALIZE_JS_FFI:
|
|
# legalize it again now, as the instrumentation may need it
|
|
passes += ['--legalize-js-interface']
|
|
if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS:
|
|
# note that this pass must run before asyncify, as if it runs afterwards we only
|
|
# generate the byn$fpcast_emu functions after asyncify runs, and so we wouldn't
|
|
# be able to further process them.
|
|
passes += ['--fpcast-emu']
|
|
if shared.Settings.ASYNCIFY:
|
|
passes += ['--asyncify']
|
|
if shared.Settings.ASSERTIONS:
|
|
passes += ['--pass-arg=asyncify-asserts']
|
|
if shared.Settings.ASYNCIFY_ADVISE:
|
|
passes += ['--pass-arg=asyncify-verbose']
|
|
if shared.Settings.ASYNCIFY_IGNORE_INDIRECT:
|
|
passes += ['--pass-arg=asyncify-ignore-indirect']
|
|
passes += ['--pass-arg=asyncify-imports@%s' % ','.join(shared.Settings.ASYNCIFY_IMPORTS)]
|
|
|
|
# shell escaping can be confusing; try to emit useful warnings
|
|
def check_human_readable_list(items):
|
|
for item in items:
|
|
if item.count('(') != item.count(')'):
|
|
logger.warning('''emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):''')
|
|
logger.warning(''' ''' + item)
|
|
logger.warning('''This may indicate improper escaping that led to splitting inside your names.''')
|
|
logger.warning('''Try to quote the entire argument, like this: -s 'ASYNCIFY_ONLY=["foo(int, char)", "bar"]' ''')
|
|
break
|
|
|
|
if shared.Settings.ASYNCIFY_REMOVE:
|
|
check_human_readable_list(shared.Settings.ASYNCIFY_REMOVE)
|
|
passes += ['--pass-arg=asyncify-removelist@%s' % ','.join(shared.Settings.ASYNCIFY_REMOVE)]
|
|
if shared.Settings.ASYNCIFY_ADD:
|
|
check_human_readable_list(shared.Settings.ASYNCIFY_ADD)
|
|
passes += ['--pass-arg=asyncify-addlist@%s' % ','.join(shared.Settings.ASYNCIFY_ADD)]
|
|
if shared.Settings.ASYNCIFY_ONLY:
|
|
check_human_readable_list(shared.Settings.ASYNCIFY_ONLY)
|
|
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(shared.Settings.ASYNCIFY_ONLY)]
|
|
if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
|
|
passes += ['--ignore-implicit-traps']
|
|
# normally we can assume the memory, if imported, has not been modified
|
|
# beforehand (in fact, in most cases the memory is not even imported anyhow,
|
|
# but it is still safe to pass the flag), and is therefore filled with zeros.
|
|
# the one exception is dynamic linking of a side module: the main module is ok
|
|
# as it is loaded first, but the side module may be assigned memory that was
|
|
# previously used.
|
|
if run_binaryen_optimizer and not shared.Settings.SIDE_MODULE:
|
|
passes += ['--zero-filled-memory']
|
|
|
|
if shared.Settings.BINARYEN_EXTRA_PASSES:
|
|
# BINARYEN_EXTRA_PASSES is comma-separated, and we support both '-'-prefixed and
|
|
# unprefixed pass names
|
|
extras = shared.Settings.BINARYEN_EXTRA_PASSES.split(',')
|
|
passes += [('--' + p) if p[0] != '-' else p for p in extras if p]
|
|
|
|
return passes
|
|
|
|
|
|
def make_js_executable(script):
|
|
src = open(script).read()
|
|
cmd = shared.shlex_join(config.JS_ENGINE)
|
|
if not os.path.isabs(config.JS_ENGINE[0]):
|
|
# TODO: use whereis etc. And how about non-*NIX?
|
|
cmd = '/usr/bin/env -S ' + cmd
|
|
logger.debug('adding `#!` to JavaScript file: %s' % cmd)
|
|
# add shebang
|
|
with open(script, 'w') as f:
|
|
f.write('#!%s\n' % cmd)
|
|
f.write(src)
|
|
try:
|
|
os.chmod(script, stat.S_IMODE(os.stat(script).st_mode) | stat.S_IXUSR) # make executable
|
|
except OSError:
|
|
pass # can fail if e.g. writing the executable to /dev/null
|
|
|
|
|
|
def do_split_module(wasm_file):
|
|
os.rename(wasm_file, wasm_file + '.orig')
|
|
args = ['--instrument']
|
|
building.run_binaryen_command('wasm-split', wasm_file + '.orig', outfile=wasm_file, args=args)
|
|
|
|
|
|
def is_dash_s_for_emcc(args, i):
|
|
# -s OPT=VALUE or -s OPT or -sOPT are all interpreted as emscripten flags.
|
|
# -s by itself is a linker option (alias for --strip-all)
|
|
if args[i] == '-s':
|
|
if len(args) <= i + 1:
|
|
return False
|
|
arg = args[i + 1]
|
|
else:
|
|
arg = args[i][2:]
|
|
arg = arg.split('=')[0]
|
|
return arg.isidentifier() and arg.isupper()
|
|
|
|
|
|
def parse_s_args(args):
|
|
settings_changes = []
|
|
for i in range(len(args)):
|
|
if args[i].startswith('-s'):
|
|
if is_dash_s_for_emcc(args, i):
|
|
if args[i] == '-s':
|
|
key = args[i + 1]
|
|
args[i + 1] = ''
|
|
else:
|
|
key = args[i][2:]
|
|
args[i] = ''
|
|
|
|
# If not = is specified default to 1
|
|
if '=' not in key:
|
|
key += '=1'
|
|
|
|
# Special handling of browser version targets. A version -1 means that the specific version
|
|
# is not supported at all. Replace those with INT32_MAX to make it possible to compare e.g.
|
|
# #if MIN_FIREFOX_VERSION < 68
|
|
if re.match(r'MIN_.*_VERSION(=.*)?', key):
|
|
try:
|
|
if int(key.split('=')[1]) < 0:
|
|
key = key.split('=')[0] + '=0x7FFFFFFF'
|
|
except Exception:
|
|
pass
|
|
|
|
settings_changes.append(key)
|
|
|
|
newargs = [a for a in args if a]
|
|
return (settings_changes, newargs)
|
|
|
|
|
|
def calc_cflags(options):
|
|
# Flags we pass to the compiler when building C/C++ code
|
|
# We add these to the user's flags (newargs), but not when building .s or .S assembly files
|
|
cflags = []
|
|
|
|
if options.tracing:
|
|
cflags.append('-D__EMSCRIPTEN_TRACING__=1')
|
|
|
|
if shared.Settings.USE_PTHREADS:
|
|
cflags.append('-D__EMSCRIPTEN_PTHREADS__=1')
|
|
|
|
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.
|
|
cflags.append('-DEMSCRIPTEN')
|
|
|
|
# if exception catching is disabled, we can prevent that code from being
|
|
# generated in the frontend
|
|
if shared.Settings.DISABLE_EXCEPTION_CATCHING == 1 and not shared.Settings.EXCEPTION_HANDLING:
|
|
cflags.append('-fignore-exceptions')
|
|
|
|
if shared.Settings.INLINING_LIMIT:
|
|
cflags.append('-fno-inline-functions')
|
|
|
|
if shared.Settings.RELOCATABLE:
|
|
cflags.append('-fPIC')
|
|
cflags.append('-fvisibility=default')
|
|
|
|
if shared.Settings.LTO:
|
|
cflags.append('-flto=' + shared.Settings.LTO)
|
|
else:
|
|
# With LTO mode these args get passed instead
|
|
# at link time when the backend runs.
|
|
for a in building.llvm_backend_args():
|
|
cflags += ['-mllvm', a]
|
|
|
|
return cflags
|
|
|
|
|
|
def get_file_suffix(filename):
|
|
"""Parses the essential suffix of a filename, discarding Unix-style version
|
|
numbers in the name. For example for 'libz.so.1.2.8' returns '.so'"""
|
|
if filename in SPECIAL_ENDINGLESS_FILENAMES:
|
|
return filename
|
|
while filename:
|
|
filename, suffix = os.path.splitext(filename)
|
|
if not suffix[1:].isdigit():
|
|
return suffix
|
|
return ''
|
|
|
|
|
|
def in_temp(name):
|
|
temp_dir = shared.get_emscripten_temp_dir()
|
|
return os.path.join(temp_dir, os.path.basename(name))
|
|
|
|
|
|
run_via_emxx = False
|
|
|
|
|
|
#
|
|
# Main run() function
|
|
#
|
|
def run(args):
|
|
target = None
|
|
|
|
# Additional compiler flags that we treat as if they were passed to us on the
|
|
# commandline
|
|
EMCC_CFLAGS = os.environ.get('EMCC_CFLAGS')
|
|
if DEBUG:
|
|
cmd = shared.shlex_join(args)
|
|
if EMCC_CFLAGS:
|
|
cmd += ' + ' + EMCC_CFLAGS
|
|
logger.warning('invocation: ' + cmd + ' (in ' + os.getcwd() + ')')
|
|
if EMCC_CFLAGS:
|
|
args.extend(shlex.split(EMCC_CFLAGS))
|
|
|
|
# Strip args[0] (program name)
|
|
args = args[1:]
|
|
|
|
misc_temp_files = shared.configuration.get_temp_files()
|
|
|
|
# Handle some global flags
|
|
|
|
# read response files very early on
|
|
try:
|
|
args = substitute_response_files(args)
|
|
except IOError as e:
|
|
exit_with_error(e)
|
|
|
|
if '--help' in args:
|
|
# Documentation for emcc and its options must be updated in:
|
|
# site/source/docs/tools_reference/emcc.rst
|
|
# This then gets built (via: `make -C site text`) to:
|
|
# site/build/text/docs/tools_reference/emcc.txt
|
|
# This then needs to be copied to its final home in docs/emcc.txt from where
|
|
# we read it here. We have CI rules that ensure its always up-to-date.
|
|
with open(shared.path_from_root('docs', 'emcc.txt'), 'r') as f:
|
|
print(f.read())
|
|
|
|
print('''
|
|
------------------------------------------------------------------
|
|
|
|
emcc: supported targets: llvm bitcode, WebAssembly, NOT elf
|
|
(autoconf likes to see elf above to enable shared object support)
|
|
''')
|
|
return 0
|
|
|
|
if '--version' in args:
|
|
# if the emscripten folder is not a git repo, don't run git show - that can
|
|
# look up and find the revision in a parent directory that is a git repo
|
|
revision = ''
|
|
if os.path.exists(shared.path_from_root('.git')):
|
|
revision = run_process(['git', 'rev-parse', 'HEAD'], stdout=PIPE, stderr=PIPE, cwd=shared.path_from_root()).stdout.strip()
|
|
elif os.path.exists(shared.path_from_root('emscripten-revision.txt')):
|
|
revision = open(shared.path_from_root('emscripten-revision.txt')).read().strip()
|
|
if revision:
|
|
revision = ' (%s)' % revision
|
|
print('''%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.
|
|
''' % (version_string(), revision))
|
|
return 0
|
|
|
|
if run_via_emxx:
|
|
clang = shared.CLANG_CXX
|
|
else:
|
|
clang = shared.CLANG_CC
|
|
|
|
if len(args) == 1 and args[0] == '-v': # -v with no inputs
|
|
# autoconf likes to see 'GNU' in the output to enable shared object support
|
|
print(version_string(), file=sys.stderr)
|
|
return shared.check_call([clang, '-v'] + shared.get_clang_flags(), check=False).returncode
|
|
|
|
if '-dumpmachine' in args:
|
|
print(shared.get_llvm_target())
|
|
return 0
|
|
|
|
if '-dumpversion' in args: # gcc's doc states "Print the compiler version [...] and don't do anything else."
|
|
print(shared.EMSCRIPTEN_VERSION)
|
|
return 0
|
|
|
|
if '--cflags' in args:
|
|
# fake running the command, to see the full args we pass to clang
|
|
args = [x for x in args if x != '--cflags']
|
|
with misc_temp_files.get_file(suffix='.o') as temp_target:
|
|
input_file = 'hello_world.c'
|
|
cmd = [shared.PYTHON, sys.argv[0], shared.path_from_root('tests', input_file), '-v', '-c', '-o', temp_target] + args
|
|
proc = run_process(cmd, stderr=PIPE, check=False)
|
|
if proc.returncode != 0:
|
|
print(proc.stderr)
|
|
exit_with_error('error getting cflags')
|
|
lines = [x for x in proc.stderr.splitlines() if clang in x and input_file in x]
|
|
parts = shlex.split(lines[0].replace('\\', '\\\\'))
|
|
parts = [x for x in parts if x not in ['-c', '-o', '-v', '-emit-llvm'] and input_file not in x and temp_target not in x]
|
|
print(shared.shlex_join(parts[1:]))
|
|
return 0
|
|
|
|
shared.check_sanity()
|
|
|
|
def get_language_mode(args):
|
|
return_next = False
|
|
for item in args:
|
|
if return_next:
|
|
return item
|
|
if item == '-x':
|
|
return_next = True
|
|
continue
|
|
if item.startswith('-x'):
|
|
return item[2:]
|
|
return ''
|
|
|
|
language_mode = get_language_mode(args)
|
|
|
|
EMMAKEN_CFLAGS = os.environ.get('EMMAKEN_CFLAGS')
|
|
if EMMAKEN_CFLAGS:
|
|
args += 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] + shared.suffix(name)
|
|
|
|
# ---------------- End configs -------------
|
|
|
|
def optimizing(opts):
|
|
return '-O0' not in opts
|
|
|
|
def need_llvm_debug_info():
|
|
return shared.Settings.DEBUG_LEVEL >= 3
|
|
|
|
with ToolchainProfiler.profile_block('parse arguments and setup'):
|
|
## Parse args
|
|
|
|
newargs = list(args)
|
|
|
|
# Scan and strip emscripten specific cmdline warning flags.
|
|
# This needs to run before other cmdline flags have been parsed, so that
|
|
# warnings are properly printed during arg parse.
|
|
newargs = diagnostics.capture_warnings(newargs)
|
|
|
|
if not config.config_file:
|
|
diagnostics.warning('deprecated', 'Specifying EM_CONFIG as a python literal is deprecated. Please use a file instead.')
|
|
|
|
for i in range(len(newargs)):
|
|
if newargs[i] in ('-l', '-L', '-I'):
|
|
# Scan for individual -l/-L/-I arguments and concatenate the next arg on
|
|
# if there is no suffix
|
|
newargs[i] += newargs[i + 1]
|
|
newargs[i + 1] = ''
|
|
|
|
options, settings_changes, user_js_defines, newargs = parse_args(newargs)
|
|
|
|
if options.post_link or options.oformat == OFormat.BARE:
|
|
diagnostics.warning('experimental', '--oformat=base/--post-link are experimental and subject to change.')
|
|
|
|
if '-print-search-dirs' in newargs:
|
|
return run_process([clang, '-print-search-dirs'], check=False).returncode
|
|
|
|
if options.emrun:
|
|
options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n'
|
|
options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n'
|
|
# emrun mode waits on program exit
|
|
shared.Settings.EXIT_RUNTIME = 1
|
|
|
|
if options.cpu_profiler:
|
|
options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n'
|
|
|
|
if options.memory_profiler:
|
|
shared.Settings.MEMORYPROFILER = 1
|
|
|
|
if options.thread_profiler:
|
|
options.post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n'
|
|
|
|
if options.llvm_opts is None:
|
|
options.llvm_opts = LLVM_OPT_LEVEL[shared.Settings.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 = shared.Settings.OPT_LEVEL >= 2
|
|
|
|
# TODO: support source maps with js_transform
|
|
if options.js_transform and shared.Settings.GENERATE_SOURCE_MAP:
|
|
logger.warning('disabling source maps because a js transform is being done')
|
|
shared.Settings.DEBUG_LEVEL = 3
|
|
|
|
explicit_settings_changes, newargs = parse_s_args(newargs)
|
|
settings_changes += explicit_settings_changes
|
|
|
|
settings_key_changes = {}
|
|
for s in settings_changes:
|
|
key, value = s.split('=', 1)
|
|
settings_key_changes[key] = value
|
|
|
|
# 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 = []
|
|
|
|
has_header_inputs = False
|
|
lib_dirs = []
|
|
|
|
has_dash_c = '-c' in newargs
|
|
has_dash_S = '-S' in newargs
|
|
has_dash_E = '-E' in newargs
|
|
|
|
compile_only = has_dash_c or has_dash_S or has_dash_E
|
|
|
|
def add_link_flag(i, f):
|
|
if f.startswith('-l'):
|
|
libs.append((i, f[2:]))
|
|
if f.startswith('-L'):
|
|
lib_dirs.append(f[2:])
|
|
|
|
link_flags.append((i, f))
|
|
|
|
# find input files with a simple heuristic. we should really analyze
|
|
# based on a full understanding of gcc params, right now we just assume that
|
|
# what is left contains no more |-x OPT| things
|
|
skip = False
|
|
for i in range(len(newargs)):
|
|
if skip:
|
|
skip = False
|
|
continue
|
|
|
|
arg = newargs[i]
|
|
if arg in ('-MT', '-MF', '-MJ', '-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',
|
|
'-Xlinker', '-Xclang'):
|
|
skip = True
|
|
|
|
if not arg.startswith('-'):
|
|
# os.devnul should always be reported as existing but there is bug in windows
|
|
# python before 3.8:
|
|
# https://bugs.python.org/issue1311
|
|
if not os.path.exists(arg) and arg != os.devnull:
|
|
exit_with_error('%s: No such file or directory ("%s" was expected to be an input file, based on the commandline arguments provided)', arg, arg)
|
|
file_suffix = get_file_suffix(arg)
|
|
if file_suffix in SOURCE_ENDINGS + DYNAMICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS or building.is_ar(arg):
|
|
# we already removed -o <target>, so all these should be inputs
|
|
newargs[i] = ''
|
|
if file_suffix in SOURCE_ENDINGS or (has_dash_c and file_suffix == '.bc'):
|
|
input_files.append((i, arg))
|
|
elif file_suffix in HEADER_ENDINGS:
|
|
input_files.append((i, arg))
|
|
has_header_inputs = True
|
|
elif file_suffix in ASSEMBLY_ENDINGS or building.is_bitcode(arg) or building.is_ar(arg):
|
|
input_files.append((i, arg))
|
|
elif building.is_wasm(arg):
|
|
input_files.append((i, arg))
|
|
elif file_suffix in (STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS):
|
|
# if it's not, and it's a library, just add it to libs to find later
|
|
libname = unsuffixed_basename(arg)
|
|
for prefix in LIB_PREFIXES:
|
|
if not prefix:
|
|
continue
|
|
if libname.startswith(prefix):
|
|
libname = libname[len(prefix):]
|
|
break
|
|
libs.append((i, libname))
|
|
elif file_suffix in STATICLIB_ENDINGS:
|
|
if not building.is_ar(arg):
|
|
if 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 .bc or .o.'
|
|
else:
|
|
message = arg + ': Unknown format, not a static library!'
|
|
exit_with_error(message)
|
|
else:
|
|
newargs[i] = ''
|
|
input_files.append((i, arg))
|
|
elif arg.startswith('-L'):
|
|
add_link_flag(i, arg)
|
|
newargs[i] = ''
|
|
elif arg.startswith('-l'):
|
|
add_link_flag(i, arg)
|
|
newargs[i] = ''
|
|
elif arg.startswith('-Wl,'):
|
|
# Multiple comma separated link flags can be specified. Create fake
|
|
# fractional indices for these: -Wl,a,b,c,d at index 4 becomes:
|
|
# (4, a), (4.25, b), (4.5, c), (4.75, d)
|
|
link_flags_to_add = arg.split(',')[1:]
|
|
for flag_index, flag in enumerate(link_flags_to_add):
|
|
add_link_flag(i + float(flag_index) / len(link_flags_to_add), flag)
|
|
newargs[i] = ''
|
|
elif arg == '-Xlinker':
|
|
add_link_flag(i + 1, newargs[i + 1])
|
|
newargs[i] = ''
|
|
newargs[i + 1] = ''
|
|
elif arg == '-s':
|
|
# -s and some other compiler flags are normally passed onto the linker
|
|
# TODO(sbc): Pass this and other flags through when using lld
|
|
# link_flags.append((i, arg))
|
|
newargs[i] = ''
|
|
elif arg == '-':
|
|
input_files.append((i, arg))
|
|
newargs[i] = ''
|
|
newargs = [a for a in newargs if a]
|
|
|
|
# Libraries are searched before settings_changes are applied, so apply the
|
|
# value for STRICT from command line already now.
|
|
|
|
strict_cmdline = settings_key_changes.get('STRICT')
|
|
if strict_cmdline:
|
|
shared.Settings.STRICT = int(strict_cmdline)
|
|
|
|
# Apply optimization level settings
|
|
shared.Settings.apply_opt_level(opt_level=shared.Settings.OPT_LEVEL, shrink_level=shared.Settings.SHRINK_LEVEL, noisy=True)
|
|
|
|
# For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also
|
|
# want to opt out of ERROR_ON_UNDEFINED_SYMBOLS.
|
|
if 'WARN_ON_UNDEFINED_SYMBOLS=0' in settings_changes:
|
|
shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS = 0
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME or 'MINIMAL_RUNTIME=1' in settings_changes or 'MINIMAL_RUNTIME=2' in settings_changes:
|
|
# Remove the default exported functions 'malloc', 'free', etc. those should only be linked in if used
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = []
|
|
|
|
# Apply -s settings in newargs here (after optimization levels, so they can override them)
|
|
apply_settings(settings_changes)
|
|
|
|
specified_target = options.output_file
|
|
|
|
if os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in args:
|
|
# configure tests want a more shell-like style, where we emit return codes on exit()
|
|
shared.Settings.EXIT_RUNTIME = 1
|
|
# use node.js raw filesystem access, to behave just like a native executable
|
|
shared.Settings.NODERAWFS = 1
|
|
# Add `#!` line to output JS and make it executable.
|
|
options.executable = True
|
|
# Autoconf expects the executable output file to be called `a.out`
|
|
default_target_name = 'a.out'
|
|
elif shared.Settings.SIDE_MODULE:
|
|
default_target_name = 'a.out.wasm'
|
|
else:
|
|
default_target_name = 'a.out.js'
|
|
|
|
# specified_target is the user-specified one, target is what we will generate
|
|
if specified_target:
|
|
target = specified_target
|
|
# check for the existence of the output directory now, to avoid having
|
|
# to do so repeatedly when each of the various output files (.mem, .wasm,
|
|
# etc) are written. This gives a more useful error message than the
|
|
# IOError and python backtrace that users would otherwise see.
|
|
dirname = os.path.dirname(target)
|
|
if dirname and not os.path.isdir(dirname):
|
|
exit_with_error("specified output file (%s) is in a directory that does not exist" % target)
|
|
else:
|
|
target = default_target_name
|
|
|
|
shared.Settings.TARGET_BASENAME = target_basename = unsuffixed_basename(target)
|
|
|
|
final_suffix = get_file_suffix(target)
|
|
|
|
if has_dash_c or has_dash_S or has_dash_E or '-M' in newargs or '-MM' in newargs:
|
|
if has_dash_c:
|
|
if '-emit-llvm' in newargs:
|
|
options.default_object_extension = '.bc'
|
|
elif has_dash_S:
|
|
if '-emit-llvm' in newargs:
|
|
options.default_object_extension = '.ll'
|
|
else:
|
|
options.default_object_extension = '.s'
|
|
elif '-M' in newargs or '-MM' in newargs:
|
|
options.default_object_extension = '.mout' # not bitcode, not js; but just dependency rule of the input file
|
|
|
|
if specified_target:
|
|
if len(input_files) > 1:
|
|
exit_with_error('cannot specify -o with -c/-S/-E/-M and multiple source files')
|
|
else:
|
|
target = target_basename + options.default_object_extension
|
|
|
|
# If no output format was sepecific we try to imply the format based on
|
|
# the output filename extension.
|
|
if not options.oformat:
|
|
if shared.Settings.SIDE_MODULE or final_suffix == '.wasm':
|
|
options.oformat = OFormat.WASM
|
|
elif final_suffix == '.mjs':
|
|
options.oformat = OFormat.MJS
|
|
elif final_suffix == '.html':
|
|
options.oformat = OFormat.HTML
|
|
else:
|
|
options.oformat = OFormat.JS
|
|
|
|
if options.oformat == OFormat.MJS:
|
|
shared.Settings.EXPORT_ES6 = 1
|
|
shared.Settings.MODULARIZE = 1
|
|
|
|
if options.oformat in (OFormat.WASM, OFormat.BARE):
|
|
# If the user asks directly for a wasm file then this *is* the target
|
|
wasm_target = target
|
|
else:
|
|
# Otherwise the wasm file is produced alongside the final target.
|
|
wasm_target = unsuffixed(target) + '.wasm'
|
|
|
|
# Apply user -jsD settings
|
|
for s in user_js_defines:
|
|
shared.Settings.attrs[s[0]] = s[1]
|
|
|
|
shared.verify_settings()
|
|
|
|
if (options.oformat == OFormat.WASM or shared.Settings.PURE_WASI) and not shared.Settings.SIDE_MODULE:
|
|
# if the output is just a wasm file, it will normally be a standalone one,
|
|
# as there is no JS. an exception are side modules, as we can't tell at
|
|
# compile time whether JS will be involved or not - the main module may
|
|
# have JS, and the side module is expected to link against that.
|
|
# we also do not support standalone mode in fastcomp.
|
|
shared.Settings.STANDALONE_WASM = 1
|
|
|
|
if shared.Settings.LZ4:
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += ['LZ4']
|
|
|
|
if shared.Settings.WASM2C:
|
|
# wasm2c only makes sense with standalone wasm - there will be no JS,
|
|
# just wasm and then C
|
|
shared.Settings.STANDALONE_WASM = 1
|
|
# wasm2c doesn't need any special handling of i64, we have proper i64
|
|
# handling on the FFI boundary, which is exactly like the case of JS with
|
|
# BigInt support
|
|
shared.Settings.WASM_BIGINT = 1
|
|
|
|
if options.no_entry:
|
|
shared.Settings.EXPECT_MAIN = 0
|
|
elif shared.Settings.STANDALONE_WASM:
|
|
if '_main' in shared.Settings.EXPORTED_FUNCTIONS:
|
|
# TODO(sbc): Make this into a warning?
|
|
logger.debug('including `_main` in EXPORTED_FUNCTIONS is not necessary in standalone mode')
|
|
else:
|
|
# In normal non-standalone mode we have special handling of `_main` in EXPORTED_FUNCTIONS.
|
|
# 1. If the user specifies exports, but doesn't include `_main` we assume they want to build a
|
|
# reactor.
|
|
# 2. If the user doesn't export anything we default to exporting `_main` (unless `--no-entry`
|
|
# is specified (see above).
|
|
if 'EXPORTED_FUNCTIONS' in settings_key_changes:
|
|
if '_main' not in shared.Settings.USER_EXPORTED_FUNCTIONS:
|
|
shared.Settings.EXPECT_MAIN = 0
|
|
else:
|
|
assert not shared.Settings.EXPORTED_FUNCTIONS
|
|
shared.Settings.EXPORTED_FUNCTIONS = ['_main']
|
|
|
|
if shared.Settings.STANDALONE_WASM:
|
|
# In STANDALONE_WASM mode we either build a command or a reactor.
|
|
# See https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md
|
|
# For a command we always want EXIT_RUNTIME=1
|
|
# For a reactor we always want EXIT_RUNTIME=0
|
|
if 'EXIT_RUNTIME' in settings_key_changes:
|
|
exit_with_error('Explictly setting EXIT_RUNTIME not compatible with STANDALONE_WASM. EXIT_RUNTIME will always be True for programs (with a main function) and False for reactors (not main function).')
|
|
shared.Settings.EXIT_RUNTIME = shared.Settings.EXPECT_MAIN
|
|
|
|
def filter_out_dynamic_libs(inputs):
|
|
# If not compiling to JS, then we are compiling to an intermediate bitcode
|
|
# objects or library, so ignore dynamic linking, since multiple dynamic
|
|
# linkings can interfere with each other
|
|
if final_suffix not in EXECUTABLE_ENDINGS or options.ignore_dynamic_linking:
|
|
def check(input_file):
|
|
if get_file_suffix(input_file) in DYNAMICLIB_ENDINGS:
|
|
if not options.ignore_dynamic_linking:
|
|
diagnostics.warning('emcc', 'ignoring dynamic library %s because not compiling to JS or HTML, remember to link it when compiling to JS or HTML at the end', os.path.basename(input_file))
|
|
return False
|
|
else:
|
|
return True
|
|
return [f for f in inputs if check(f[1])]
|
|
return inputs
|
|
|
|
def filter_out_duplicate_dynamic_libs(inputs):
|
|
# Filter out duplicate shared libraries.
|
|
# See test_core.py:test_redundant_link
|
|
seen = set()
|
|
rtn = []
|
|
for i in inputs:
|
|
if get_file_suffix(i[1]) in DYNAMICLIB_ENDINGS and os.path.exists(i[1]):
|
|
abspath = os.path.abspath(i[1])
|
|
if abspath in seen:
|
|
continue
|
|
seen.add(abspath)
|
|
rtn.append(i)
|
|
return rtn
|
|
|
|
input_files = filter_out_dynamic_libs(input_files)
|
|
input_files = filter_out_duplicate_dynamic_libs(input_files)
|
|
|
|
if not input_files and not link_flags:
|
|
exit_with_error('no input files')
|
|
|
|
# Note the exports the user requested
|
|
building.user_requested_exports = shared.Settings.EXPORTED_FUNCTIONS[:]
|
|
|
|
def default_setting(name, new_default):
|
|
if name not in settings_key_changes:
|
|
setattr(shared.Settings, name, new_default)
|
|
|
|
# -s ASSERTIONS=1 implies basic stack overflow checks, and ASSERTIONS=2
|
|
# implies full stack overflow checks (unless the user specifically set
|
|
# something else)
|
|
if shared.Settings.ASSERTIONS:
|
|
default_setting('STACK_OVERFLOW_CHECK', max(shared.Settings.ASSERTIONS, shared.Settings.STACK_OVERFLOW_CHECK))
|
|
|
|
if shared.Settings.LLD_REPORT_UNDEFINED or shared.Settings.STANDALONE_WASM:
|
|
# Reporting undefined symbols at wasm-ld time requires us to know if we have a `main` function
|
|
# or not, as does standalone wasm mode.
|
|
# TODO(sbc): Remove this once this becomes the default
|
|
shared.Settings.IGNORE_MISSING_MAIN = 0
|
|
|
|
if shared.Settings.STRICT:
|
|
default_setting('STRICT_JS', 1)
|
|
default_setting('AUTO_JS_LIBRARIES', 0)
|
|
default_setting('AUTO_NATIVE_LIBRARIES', 0)
|
|
default_setting('AUTO_ARCHIVE_INDEXES', 0)
|
|
default_setting('IGNORE_MISSING_MAIN', 0)
|
|
default_setting('DEFAULT_TO_CXX', 0)
|
|
|
|
# If set to 1, we will run the autodebugger (the automatic debugging tool, see
|
|
# tools/autodebugger). Note that this will disable inclusion of libraries. This
|
|
# is useful because including dlmalloc makes it hard to compare native and js
|
|
# builds
|
|
if os.environ.get('EMCC_AUTODEBUG'):
|
|
shared.Settings.AUTODEBUG = 1
|
|
|
|
# Use settings
|
|
|
|
if shared.Settings.DEBUG_LEVEL > 1 and options.use_closure_compiler:
|
|
diagnostics.warning('emcc', 'disabling closure because debug info was requested')
|
|
options.use_closure_compiler = False
|
|
|
|
if shared.Settings.WASM == 2 and shared.Settings.SINGLE_FILE:
|
|
exit_with_error('cannot have both WASM=2 and SINGLE_FILE enabled at the same time')
|
|
|
|
if shared.Settings.SEPARATE_DWARF and shared.Settings.WASM2JS:
|
|
exit_with_error('cannot have both SEPARATE_DWARF and WASM2JS at the same time (as there is no wasm file)')
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and shared.Settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION:
|
|
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION are mutually exclusive!')
|
|
|
|
if options.emrun:
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
exit_with_error('--emrun is not compatible with -s MINIMAL_RUNTIME=1')
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS.append('addOnExit')
|
|
|
|
if options.use_closure_compiler:
|
|
shared.Settings.USE_CLOSURE_COMPILER = options.use_closure_compiler
|
|
|
|
if shared.Settings.CLOSURE_WARNINGS not in ['quiet', 'warn', 'error']:
|
|
exit_with_error('Invalid option -s CLOSURE_WARNINGS=%s specified! Allowed values are "quiet", "warn" or "error".' % shared.Settings.CLOSURE_WARNINGS)
|
|
|
|
# Include dynCall() function by default in DYNCALLS builds in classic runtime; in MINIMAL_RUNTIME, must add this explicitly.
|
|
if shared.Settings.DYNCALLS and not shared.Settings.MINIMAL_RUNTIME:
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$dynCall']
|
|
|
|
if shared.Settings.MAIN_MODULE:
|
|
assert not shared.Settings.SIDE_MODULE
|
|
if shared.Settings.MAIN_MODULE == 1:
|
|
shared.Settings.INCLUDE_FULL_LIBRARY = 1
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$preloadDylibs']
|
|
elif shared.Settings.SIDE_MODULE:
|
|
assert not shared.Settings.MAIN_MODULE
|
|
# memory init file is not supported with side modules, must be executable synchronously (for dlopen)
|
|
options.memory_init_file = False
|
|
|
|
if shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE:
|
|
if shared.Settings.MAIN_MODULE == 1 or shared.Settings.SIDE_MODULE == 1:
|
|
shared.Settings.LINKABLE = 1
|
|
shared.Settings.EXPORT_ALL = 1
|
|
shared.Settings.RELOCATABLE = 1
|
|
|
|
if shared.Settings.RELOCATABLE:
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$reportUndefinedSymbols', '$relocateExports', '$GOTHandler']
|
|
if options.use_closure_compiler:
|
|
exit_with_error('cannot use closure compiler on shared modules')
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
exit_with_error('MINIMAL_RUNTIME is not compatible with relocatable output')
|
|
if shared.Settings.WASM2JS:
|
|
exit_with_error('WASM2JS is not compatible with relocatable output')
|
|
# shared modules need memory utilities to allocate their memory
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += ['allocate']
|
|
shared.Settings.ALLOW_TABLE_GROWTH = 1
|
|
|
|
# various settings require sbrk() access
|
|
if shared.Settings.DETERMINISTIC or \
|
|
shared.Settings.EMSCRIPTEN_TRACING or \
|
|
shared.Settings.MALLOC == 'emmalloc' or \
|
|
shared.Settings.SAFE_HEAP or \
|
|
shared.Settings.MEMORYPROFILER:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_sbrk']
|
|
|
|
if shared.Settings.MEMORYPROFILER:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['___heap_base',
|
|
'_emscripten_stack_get_base',
|
|
'_emscripten_stack_get_end',
|
|
'_emscripten_stack_get_current']
|
|
|
|
if shared.Settings.ASYNCIFY_LAZY_LOAD_CODE:
|
|
shared.Settings.ASYNCIFY = 1
|
|
|
|
if shared.Settings.ASYNCIFY:
|
|
# See: https://github.com/emscripten-core/emscripten/issues/12065
|
|
# See: https://github.com/emscripten-core/emscripten/issues/12066
|
|
shared.Settings.DYNCALLS = 1
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_base',
|
|
'_emscripten_stack_get_end',
|
|
'_emscripten_stack_set_limits']
|
|
|
|
# SSEx is implemented on top of SIMD128 instruction set, but do not pass SSE flags to LLVM
|
|
# so it won't think about generating native x86 SSE code.
|
|
newargs = [x for x in newargs if x not in shared.SIMD_INTEL_FEATURE_TOWER and x not in shared.SIMD_NEON_FLAGS]
|
|
|
|
link_to_object = False
|
|
if options.shared or options.relocatable:
|
|
# Until we have a better story for actually producing runtime shared libraries
|
|
# we support a compatibility mode where shared libraries are actually just
|
|
# object files linked with `wasm-ld --relocatable` or `llvm-link` in the case
|
|
# of LTO.
|
|
if final_suffix in EXECUTABLE_ENDINGS:
|
|
diagnostics.warning('emcc', '-shared/-r used with executable output suffix. This behaviour is deprecated. Please remove -shared/-r to build an executable or avoid the executable suffix (%s) when building object files.' % final_suffix)
|
|
else:
|
|
if options.shared:
|
|
diagnostics.warning('emcc', 'linking a library with `-shared` will emit a static object file. This is a form of emulation to support existing build systems. If you want to build a runtime shared library use the SIDE_MODULE setting.')
|
|
link_to_object = True
|
|
|
|
if shared.Settings.STACK_OVERFLOW_CHECK:
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$abortStackOverflow']
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_end', '_emscripten_stack_get_free']
|
|
if shared.Settings.RELOCATABLE:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_set_limits']
|
|
else:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_init']
|
|
if shared.Settings.STACK_OVERFLOW_CHECK == 2:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_base']
|
|
|
|
if shared.Settings.MODULARIZE:
|
|
assert not options.proxy_to_worker, '-s MODULARIZE=1 is not compatible with --proxy-to-worker (if you want to run in a worker with -s MODULARIZE=1, you likely want to do the worker side setup manually)'
|
|
# in MINIMAL_RUNTIME we may not need to emit the Promise code, as the
|
|
# HTML output creates a singleton instance, and it does so without the
|
|
# Promise. However, in Pthreads mode the Promise is used for worker
|
|
# creation.
|
|
if shared.Settings.MINIMAL_RUNTIME and options.oformat == OFormat.HTML and not shared.Settings.USE_PTHREADS:
|
|
shared.Settings.EXPORT_READY_PROMISE = 0
|
|
|
|
if shared.Settings.LEGACY_VM_SUPPORT:
|
|
if shared.Settings.WASM2JS:
|
|
shared.Settings.POLYFILL_OLD_MATH_FUNCTIONS = 1
|
|
|
|
# Support all old browser versions
|
|
shared.Settings.MIN_FIREFOX_VERSION = 0
|
|
shared.Settings.MIN_SAFARI_VERSION = 0
|
|
shared.Settings.MIN_IE_VERSION = 0
|
|
shared.Settings.MIN_EDGE_VERSION = 0
|
|
shared.Settings.MIN_CHROME_VERSION = 0
|
|
|
|
if shared.Settings.MIN_SAFARI_VERSION <= 9 and shared.Settings.WASM2JS:
|
|
shared.Settings.WORKAROUND_IOS_9_RIGHT_SHIFT_BUG = 1
|
|
|
|
if shared.Settings.MIN_CHROME_VERSION <= 37:
|
|
shared.Settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 1
|
|
|
|
setup_environment_settings()
|
|
|
|
# Silently drop any individual backwards compatibility emulation flags that are known never to occur on browsers that support WebAssembly.
|
|
if not shared.Settings.WASM2JS:
|
|
shared.Settings.POLYFILL_OLD_MATH_FUNCTIONS = 0
|
|
shared.Settings.WORKAROUND_IOS_9_RIGHT_SHIFT_BUG = 0
|
|
shared.Settings.WORKAROUND_OLD_WEBGL_UNIFORM_UPLOAD_IGNORED_OFFSET_BUG = 0
|
|
|
|
if shared.Settings.STB_IMAGE and final_suffix in EXECUTABLE_ENDINGS:
|
|
input_files.append((len(newargs), shared.path_from_root('third_party', 'stb_image.c')))
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free']
|
|
# stb_image 2.x need to have STB_IMAGE_IMPLEMENTATION defined to include the implementation
|
|
# when compiling
|
|
newargs.append('-DSTB_IMAGE_IMPLEMENTATION')
|
|
|
|
if shared.Settings.USE_WEBGL2:
|
|
shared.Settings.MAX_WEBGL_VERSION = 2
|
|
|
|
if not shared.Settings.GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS and shared.Settings.GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS:
|
|
exit_with_error('-s GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS=0 only makes sense with -s GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS=0!')
|
|
|
|
forced_stdlibs = []
|
|
|
|
if shared.Settings.ASMFS and final_suffix in EXECUTABLE_ENDINGS:
|
|
forced_stdlibs.append('libasmfs')
|
|
shared.Settings.FILESYSTEM = 0
|
|
shared.Settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
|
|
shared.Settings.FETCH = 1
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_asmfs.js')))
|
|
|
|
# Explicitly drop linking in a malloc implementation if program is not using any dynamic allocation calls.
|
|
if not shared.Settings.USES_DYNAMIC_ALLOC:
|
|
shared.Settings.MALLOC = 'none'
|
|
|
|
if shared.Settings.MALLOC == 'emmalloc':
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_emmalloc.js')))
|
|
|
|
if shared.Settings.FETCH and final_suffix in EXECUTABLE_ENDINGS:
|
|
forced_stdlibs.append('libfetch')
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_fetch.js')))
|
|
if shared.Settings.USE_PTHREADS:
|
|
shared.Settings.FETCH_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.fetch.js'
|
|
|
|
if shared.Settings.DEMANGLE_SUPPORT:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['___cxa_demangle']
|
|
|
|
if shared.Settings.FULL_ES3:
|
|
shared.Settings.FULL_ES2 = 1
|
|
shared.Settings.MAX_WEBGL_VERSION = max(2, shared.Settings.MAX_WEBGL_VERSION)
|
|
|
|
if shared.Settings.EMBIND:
|
|
forced_stdlibs.append('libembind')
|
|
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_stackSave', '_stackRestore', '_stackAlloc']
|
|
if not shared.Settings.STANDALONE_WASM:
|
|
# in standalone mode, crt1 will call the constructors from inside the wasm
|
|
shared.Settings.EXPORTED_FUNCTIONS.append('___wasm_call_ctors')
|
|
|
|
if shared.Settings.RELOCATABLE and not shared.Settings.DYNAMIC_EXECUTION:
|
|
exit_with_error('cannot have both DYNAMIC_EXECUTION=0 and RELOCATABLE enabled at the same time, since RELOCATABLE needs to eval()')
|
|
|
|
if shared.Settings.SIDE_MODULE and shared.Settings.GLOBAL_BASE != -1:
|
|
exit_with_error('Cannot set GLOBAL_BASE when building SIDE_MODULE')
|
|
|
|
if shared.Settings.RELOCATABLE:
|
|
default_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0)
|
|
default_setting('WARN_ON_UNDEFINED_SYMBOLS', 0)
|
|
|
|
if shared.Settings.DISABLE_EXCEPTION_THROWING and not shared.Settings.DISABLE_EXCEPTION_CATCHING:
|
|
exit_with_error("DISABLE_EXCEPTION_THROWING was set (probably from -fno-exceptions) but is not compatible with enabling exception catching (DISABLE_EXCEPTION_CATCHING=0). If you don't want exceptions, set DISABLE_EXCEPTION_CATCHING to 1; if you do want exceptions, don't link with -fno-exceptions")
|
|
|
|
if options.proxy_to_worker:
|
|
shared.Settings.PROXY_TO_WORKER = 1
|
|
|
|
if options.use_preload_plugins or len(options.preload_files) or len(options.embed_files):
|
|
if shared.Settings.NODERAWFS:
|
|
exit_with_error('--preload-file and --embed-file cannot be used with NODERAWFS which disables virtual filesystem')
|
|
# if we include any files, or intend to use preload plugins, then we definitely need filesystem support
|
|
shared.Settings.FORCE_FILESYSTEM = 1
|
|
|
|
if options.proxy_to_worker or options.use_preload_plugins:
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$Browser']
|
|
|
|
if not shared.Settings.MINIMAL_RUNTIME:
|
|
# In non-MINIMAL_RUNTIME, the core runtime depends on these functions to be present. (In MINIMAL_RUNTIME, they are
|
|
# no longer always bundled in)
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$demangle', '$demangleAll', '$jsStackTrace', '$stackTrace']
|
|
|
|
if shared.Settings.FILESYSTEM:
|
|
# to flush streams on FS exit, we need to be able to call fflush
|
|
# we only include it if the runtime is exitable, or when ASSERTIONS
|
|
# (ASSERTIONS will check that streams do not need to be flushed,
|
|
# helping people see when they should have enabled EXIT_RUNTIME)
|
|
if shared.Settings.EXIT_RUNTIME or shared.Settings.ASSERTIONS:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_fflush']
|
|
|
|
if shared.Settings.SUPPORT_ERRNO:
|
|
# so setErrNo JS library function can report errno back to C
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['___errno_location']
|
|
|
|
if shared.Settings.SAFE_HEAP:
|
|
# SAFE_HEAP check includes calling emscripten_get_sbrk_ptr() from wasm
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_get_sbrk_ptr', '_emscripten_stack_get_base']
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$unSign']
|
|
|
|
if not shared.Settings.DECLARE_ASM_MODULE_EXPORTS:
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$exportAsmFunctions']
|
|
|
|
if shared.Settings.ALLOW_MEMORY_GROWTH:
|
|
# Setting ALLOW_MEMORY_GROWTH turns off ABORTING_MALLOC, as in that mode we default to
|
|
# the behavior of trying to grow and returning 0 from malloc on failure, like
|
|
# a standard system would. However, if the user sets the flag it
|
|
# overrides that.
|
|
default_setting('ABORTING_MALLOC', 0)
|
|
|
|
if shared.Settings.USE_PTHREADS:
|
|
if shared.Settings.USE_PTHREADS == 2:
|
|
exit_with_error('USE_PTHREADS=2 is not longer supported')
|
|
if shared.Settings.ALLOW_MEMORY_GROWTH:
|
|
diagnostics.warning('pthreads-mem-growth', 'USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271')
|
|
# UTF8Decoder.decode doesn't work with a view of a SharedArrayBuffer
|
|
shared.Settings.TEXTDECODER = 0
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_pthread.js')))
|
|
newargs += ['-pthread']
|
|
# some pthreads code is in asm.js library functions, which are auto-exported; for the wasm backend, we must
|
|
# manually export them
|
|
|
|
shared.Settings.EXPORTED_FUNCTIONS += [
|
|
'___emscripten_pthread_data_constructor',
|
|
'___pthread_tsd_run_dtors',
|
|
'__emscripten_call_on_thread',
|
|
'__emscripten_do_dispatch_to_thread',
|
|
'__emscripten_main_thread_futex',
|
|
'__emscripten_thread_init',
|
|
'_emscripten_current_thread_process_queued_calls',
|
|
'__emscripten_allow_main_runtime_queued_calls',
|
|
'_emscripten_futex_wake',
|
|
'_emscripten_get_global_libc',
|
|
'_emscripten_main_browser_thread_id',
|
|
'_emscripten_main_thread_process_queued_calls',
|
|
'_emscripten_register_main_browser_thread_id',
|
|
'_emscripten_run_in_main_runtime_thread_js',
|
|
'_emscripten_stack_set_limits',
|
|
'_emscripten_sync_run_in_main_thread_2',
|
|
'_emscripten_sync_run_in_main_thread_4',
|
|
'_emscripten_tls_init',
|
|
'_pthread_self',
|
|
]
|
|
# Some of these symbols are using by worker.js but otherwise unreferenced.
|
|
# Because emitDCEGraph only considered the main js file, and not worker.js
|
|
# we have explictly mark these symbols as user-exported so that they will
|
|
# kept alive through DCE.
|
|
# TODO: Find a less hacky way to do this, perhaps by also scanning worker.js
|
|
# for roots.
|
|
building.user_requested_exports.append('_emscripten_tls_init')
|
|
building.user_requested_exports.append('_emscripten_current_thread_process_queued_calls')
|
|
|
|
# set location of worker.js
|
|
shared.Settings.PTHREAD_WORKER_FILE = unsuffixed(os.path.basename(target)) + '.worker.js'
|
|
else:
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_pthread_stub.js')))
|
|
|
|
if shared.Settings.FORCE_FILESYSTEM and not shared.Settings.MINIMAL_RUNTIME:
|
|
# when the filesystem is forced, we export by default methods that filesystem usage
|
|
# may need, including filesystem usage from standalone file packager output (i.e.
|
|
# file packages not built together with emcc, but that are loaded at runtime
|
|
# separately, and they need emcc's output to contain the support they need)
|
|
if not shared.Settings.ASMFS:
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += [
|
|
'FS_createPath',
|
|
'FS_createDataFile',
|
|
'FS_createPreloadedFile',
|
|
'FS_createLazyFile',
|
|
'FS_createDevice',
|
|
'FS_unlink'
|
|
]
|
|
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += [
|
|
'addRunDependency',
|
|
'removeRunDependency',
|
|
]
|
|
|
|
if not shared.Settings.MINIMAL_RUNTIME or shared.Settings.EXIT_RUNTIME:
|
|
# MINIMAL_RUNTIME only needs callRuntimeCallbacks in certain cases, but the normal runtime
|
|
# always does.
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks']
|
|
|
|
if shared.Settings.USE_PTHREADS:
|
|
# memalign is used to ensure allocated thread stacks are aligned.
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_memalign']
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
building.user_requested_exports += ['exit']
|
|
|
|
if shared.Settings.PROXY_TO_PTHREAD:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_proxy_main']
|
|
|
|
# pthread stack setup and other necessary utilities
|
|
def include_and_export(name):
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$' + name]
|
|
shared.Settings.EXPORTED_FUNCTIONS += [name]
|
|
|
|
include_and_export('establishStackSpace')
|
|
include_and_export('invokeEntryPoint')
|
|
if not shared.Settings.MINIMAL_RUNTIME:
|
|
# noExitRuntime does not apply to MINIMAL_RUNTIME.
|
|
include_and_export('getNoExitRuntime')
|
|
|
|
if shared.Settings.MODULARIZE:
|
|
if shared.Settings.EXPORT_NAME == 'Module':
|
|
exit_with_error('pthreads + MODULARIZE currently require you to set -s EXPORT_NAME=Something (see settings.js) to Something != Module, so that the .worker.js file can work')
|
|
|
|
# MODULARIZE+USE_PTHREADS mode requires extra exports out to Module so that worker.js
|
|
# can access them:
|
|
|
|
# general threading variables:
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += ['PThread']
|
|
|
|
# To keep code size to minimum, MINIMAL_RUNTIME does not utilize the global ExitStatus
|
|
# object, only regular runtime has it.
|
|
if not shared.Settings.MINIMAL_RUNTIME:
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += ['ExitStatus']
|
|
|
|
if shared.Settings.SIDE_MODULE:
|
|
diagnostics.warning('experimental', '-s SIDE_MODULE + pthreads is experimental')
|
|
elif shared.Settings.MAIN_MODULE:
|
|
diagnostics.warning('experimental', '-s MAIN_MODULE + pthreads is experimental')
|
|
elif shared.Settings.LINKABLE:
|
|
diagnostics.warning('experimental', '-s LINKABLE + pthreads is experimental')
|
|
|
|
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!')
|
|
|
|
def check_memory_setting(setting):
|
|
if shared.Settings[setting] % webassembly.WASM_PAGE_SIZE != 0:
|
|
exit_with_error(f'{setting} must be a multiple of WebAssembly page size (64KiB), was {shared.Settings[setting]}')
|
|
|
|
check_memory_setting('INITIAL_MEMORY')
|
|
if shared.Settings.INITIAL_MEMORY >= 2 * 1024 * 1024 * 1024:
|
|
exit_with_error('INITIAL_MEMORY must be less than 2GB due to current spec limitations')
|
|
if shared.Settings.INITIAL_MEMORY < shared.Settings.TOTAL_STACK:
|
|
exit_with_error(f'INITIAL_MEMORY must be larger than TOTAL_STACK, was {shared.Settings.INITIAL_MEMORY} (TOTAL_STACK={shared.Settings.TOTAL_STACK})')
|
|
if shared.Settings.MAXIMUM_MEMORY != -1:
|
|
check_memory_setting('MAXIMUM_MEMORY')
|
|
if shared.Settings.MEMORY_GROWTH_LINEAR_STEP != -1:
|
|
check_memory_setting('MEMORY_GROWTH_LINEAR_STEP')
|
|
if shared.Settings.USE_PTHREADS and shared.Settings.ALLOW_MEMORY_GROWTH and shared.Settings.MAXIMUM_MEMORY == -1:
|
|
exit_with_error('If pthreads and memory growth are enabled, MAXIMUM_MEMORY must be set')
|
|
|
|
if shared.Settings.EXPORT_ES6 and not shared.Settings.MODULARIZE:
|
|
# EXPORT_ES6 requires output to be a module
|
|
if 'MODULARIZE' in settings_key_changes:
|
|
exit_with_error('EXPORT_ES6 requires MODULARIZE to be set')
|
|
shared.Settings.MODULARIZE = 1
|
|
|
|
if shared.Settings.MODULARIZE and not shared.Settings.DECLARE_ASM_MODULE_EXPORTS:
|
|
# When MODULARIZE option is used, currently requires declaring all module exports
|
|
# individually - TODO: this could be optimized
|
|
exit_with_error('DECLARE_ASM_MODULE_EXPORTS=0 is not compatible with MODULARIZE')
|
|
|
|
# When not declaring asm module exports in outer scope one by one, disable minifying
|
|
# asm.js/wasm module export names so that the names can be passed directly to the outer scope.
|
|
# Also, if using library_exports.js API, disable minification so that the feature can work.
|
|
if not shared.Settings.DECLARE_ASM_MODULE_EXPORTS or 'exports.js' in [x for _, x in libs]:
|
|
shared.Settings.MINIFY_ASMJS_EXPORT_NAMES = 0
|
|
|
|
# Enable minification of wasm imports and exports when appropriate, if we
|
|
# are emitting an optimized JS+wasm combo (then the JS knows how to load the minified names).
|
|
# Things that process the JS after this operation would be done must disable this.
|
|
# For example, ASYNCIFY_LAZY_LOAD_CODE needs to identify import names.
|
|
if will_metadce() and \
|
|
shared.Settings.OPT_LEVEL >= 2 and \
|
|
shared.Settings.DEBUG_LEVEL <= 2 and \
|
|
options.oformat not in (OFormat.WASM, OFormat.BARE) and \
|
|
not shared.Settings.LINKABLE and \
|
|
not shared.Settings.STANDALONE_WASM and \
|
|
not shared.Settings.AUTODEBUG and \
|
|
not shared.Settings.ASSERTIONS and \
|
|
not shared.Settings.RELOCATABLE and \
|
|
not shared.Settings.ASYNCIFY_LAZY_LOAD_CODE and \
|
|
shared.Settings.MINIFY_ASMJS_EXPORT_NAMES:
|
|
shared.Settings.MINIFY_WASM_IMPORTS_AND_EXPORTS = 1
|
|
shared.Settings.MINIFY_WASM_IMPORTED_MODULES = 1
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
# Minimal runtime uses a different default shell file
|
|
if options.shell_path == shared.path_from_root('src', 'shell.html'):
|
|
options.shell_path = shared.path_from_root('src', 'shell_minimal_runtime.html')
|
|
|
|
if shared.Settings.ASSERTIONS and shared.Settings.MINIMAL_RUNTIME:
|
|
# In ASSERTIONS-builds, functions UTF8ArrayToString() and stringToUTF8Array() (which are not JS library functions), both
|
|
# use warnOnce(), which in MINIMAL_RUNTIME is a JS library function, so explicitly have to mark dependency to warnOnce()
|
|
# in that case. If string functions are turned to library functions in the future, then JS dependency tracking can be
|
|
# used and this special directive can be dropped.
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$warnOnce']
|
|
|
|
# Require explicit -lfoo.js flags to link with JS libraries.
|
|
shared.Settings.AUTO_JS_LIBRARIES = 0
|
|
|
|
if shared.Settings.MODULARIZE and shared.Settings.EXPORT_NAME == 'Module' and options.oformat == OFormat.HTML and \
|
|
(options.shell_path == shared.path_from_root('src', 'shell.html') or options.shell_path == shared.path_from_root('src', 'shell_minimal.html')):
|
|
exit_with_error('Due to collision in variable name "Module", the shell file "' + options.shell_path + '" is not compatible with build options "-s MODULARIZE=1 -s EXPORT_NAME=Module". Either provide your own shell file, change the name of the export to something else to avoid the name collision. (see https://github.com/emscripten-core/emscripten/issues/7950 for details)')
|
|
|
|
if shared.Settings.STANDALONE_WASM:
|
|
if shared.Settings.USE_PTHREADS:
|
|
exit_with_error('STANDALONE_WASM does not support pthreads yet')
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
exit_with_error('MINIMAL_RUNTIME reduces JS size, and is incompatible with STANDALONE_WASM which focuses on ignoring JS anyhow and being 100% wasm')
|
|
# the wasm must be runnable without the JS, so there cannot be anything that
|
|
# requires JS legalization
|
|
shared.Settings.LEGALIZE_JS_FFI = 0
|
|
|
|
# TODO(sbc): Remove WASM2JS here once the size regression it would introduce has been fixed.
|
|
if shared.Settings.USE_PTHREADS or shared.Settings.RELOCATABLE or shared.Settings.ASYNCIFY_LAZY_LOAD_CODE or shared.Settings.WASM2JS:
|
|
shared.Settings.IMPORTED_MEMORY = 1
|
|
|
|
if shared.Settings.WASM_BIGINT:
|
|
shared.Settings.LEGALIZE_JS_FFI = 0
|
|
|
|
shared.Settings.GENERATE_SOURCE_MAP = shared.Settings.DEBUG_LEVEL >= 4 and not shared.Settings.SINGLE_FILE
|
|
|
|
if options.use_closure_compiler == 2 and not shared.Settings.WASM2JS:
|
|
exit_with_error('closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm')
|
|
|
|
if any(s.startswith('MEM_INIT_METHOD=') for s in settings_changes):
|
|
exit_with_error('MEM_INIT_METHOD is not supported in wasm. Memory will be embedded in the wasm binary if threads are not used, and included in a separate file if threads are used.')
|
|
|
|
if shared.Settings.WASM2JS:
|
|
shared.Settings.MAYBE_WASM2JS = 1
|
|
# when using wasm2js, if the memory segments are in the wasm then they
|
|
# end up converted by wasm2js into base64 encoded JS. alternatively, we
|
|
# can use a .mem file like asm.js used to.
|
|
# generally we follow what the options tell us to do (which is to use
|
|
# a .mem file in most cases, since it is binary & compact). however, for
|
|
# pthreads we must keep the memory segments in the wasm as they will be
|
|
# passive segments which the .mem format cannot handle.
|
|
shared.Settings.MEM_INIT_IN_WASM = not options.memory_init_file or shared.Settings.SINGLE_FILE or shared.Settings.USE_PTHREADS
|
|
else:
|
|
# wasm includes the mem init in the wasm binary. The exception is
|
|
# wasm2js, which behaves more like js.
|
|
options.memory_init_file = True
|
|
shared.Settings.MEM_INIT_IN_WASM = True
|
|
|
|
# wasm side modules have suffix .wasm
|
|
if shared.Settings.SIDE_MODULE and target.endswith('.js'):
|
|
diagnostics.warning('emcc', 'output suffix .js requested, but wasm side modules are just wasm files; emitting only a .wasm, no .js')
|
|
|
|
sanitize = set()
|
|
|
|
for arg in newargs:
|
|
if arg.startswith('-fsanitize='):
|
|
sanitize.update(arg.split('=', 1)[1].split(','))
|
|
elif arg.startswith('-fno-sanitize='):
|
|
sanitize.difference_update(arg.split('=', 1)[1].split(','))
|
|
|
|
if sanitize:
|
|
shared.Settings.USE_OFFSET_CONVERTER = 1
|
|
shared.Settings.EXPORTED_FUNCTIONS += [
|
|
'_memalign',
|
|
'_emscripten_builtin_memalign',
|
|
'_emscripten_builtin_malloc',
|
|
'_emscripten_builtin_free',
|
|
'___heap_base',
|
|
'___global_base'
|
|
]
|
|
|
|
if shared.Settings.USE_OFFSET_CONVERTER and shared.Settings.USE_PTHREADS:
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += ['WasmOffsetConverter']
|
|
|
|
if sanitize & UBSAN_SANITIZERS:
|
|
if '-fsanitize-minimal-runtime' in newargs:
|
|
shared.Settings.UBSAN_RUNTIME = 1
|
|
else:
|
|
shared.Settings.UBSAN_RUNTIME = 2
|
|
|
|
if 'leak' in sanitize:
|
|
shared.Settings.USE_LSAN = 1
|
|
shared.Settings.EXIT_RUNTIME = 1
|
|
|
|
if shared.Settings.LINKABLE:
|
|
exit_with_error('LSan does not support dynamic linking')
|
|
|
|
if 'address' in sanitize:
|
|
shared.Settings.USE_ASAN = 1
|
|
if not shared.Settings.UBSAN_RUNTIME:
|
|
shared.Settings.UBSAN_RUNTIME = 2
|
|
|
|
shared.Settings.EXPORTED_FUNCTIONS += [
|
|
'_emscripten_builtin_memset',
|
|
'_asan_c_load_1', '_asan_c_load_1u',
|
|
'_asan_c_load_2', '_asan_c_load_2u',
|
|
'_asan_c_load_4', '_asan_c_load_4u',
|
|
'_asan_c_load_f', '_asan_c_load_d',
|
|
'_asan_c_store_1', '_asan_c_store_1u',
|
|
'_asan_c_store_2', '_asan_c_store_2u',
|
|
'_asan_c_store_4', '_asan_c_store_4u',
|
|
'_asan_c_store_f', '_asan_c_store_d',
|
|
]
|
|
|
|
if shared.Settings.ASAN_SHADOW_SIZE != -1:
|
|
diagnostics.warning('emcc', 'ASAN_SHADOW_SIZE is ignored and will be removed in a future release')
|
|
|
|
if shared.Settings.GLOBAL_BASE != -1:
|
|
exit_with_error("ASan does not support custom GLOBAL_BASE")
|
|
|
|
max_mem = shared.Settings.INITIAL_MEMORY
|
|
if shared.Settings.ALLOW_MEMORY_GROWTH:
|
|
max_mem = shared.Settings.MAXIMUM_MEMORY
|
|
if max_mem == -1:
|
|
exit_with_error('ASan requires a finite MAXIMUM_MEMORY')
|
|
|
|
shadow_size = max_mem // 8
|
|
shared.Settings.GLOBAL_BASE = shadow_size
|
|
|
|
if shared.Settings.SAFE_HEAP:
|
|
# SAFE_HEAP instruments ASan's shadow memory accesses.
|
|
# Since the shadow memory starts at 0, the act of accessing the shadow memory is detected
|
|
# by SAFE_HEAP as a null pointer dereference.
|
|
exit_with_error('ASan does not work with SAFE_HEAP')
|
|
|
|
if shared.Settings.LINKABLE:
|
|
exit_with_error('ASan does not support dynamic linking')
|
|
|
|
if sanitize and '-g4' in args:
|
|
shared.Settings.LOAD_SOURCE_MAP = 1
|
|
|
|
if shared.Settings.LOAD_SOURCE_MAP and shared.Settings.USE_PTHREADS:
|
|
shared.Settings.EXPORTED_RUNTIME_METHODS += ['WasmSourceMap']
|
|
|
|
if shared.Settings.GLOBAL_BASE == -1:
|
|
# default if nothing else sets it
|
|
# a higher global base is useful for optimizing load/store offsets, as it
|
|
# enables the --post-emscripten pass
|
|
shared.Settings.GLOBAL_BASE = 1024
|
|
|
|
# various settings require malloc/free support from JS
|
|
if shared.Settings.RELOCATABLE or \
|
|
shared.Settings.BUILD_AS_WORKER or \
|
|
shared.Settings.USE_WEBGPU or \
|
|
shared.Settings.USE_PTHREADS or \
|
|
shared.Settings.OFFSCREENCANVAS_SUPPORT or \
|
|
shared.Settings.LEGACY_GL_EMULATION or \
|
|
shared.Settings.DISABLE_EXCEPTION_CATCHING != 1 or \
|
|
shared.Settings.ASYNCIFY or \
|
|
shared.Settings.ASMFS or \
|
|
shared.Settings.DEMANGLE_SUPPORT or \
|
|
shared.Settings.FORCE_FILESYSTEM or \
|
|
shared.Settings.STB_IMAGE or \
|
|
shared.Settings.EMBIND or \
|
|
shared.Settings.FETCH or \
|
|
shared.Settings.PROXY_POSIX_SOCKETS or \
|
|
options.memory_profiler or \
|
|
sanitize:
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_malloc', '_free']
|
|
|
|
if shared.Settings.ASYNCIFY:
|
|
if not shared.Settings.ASYNCIFY_IGNORE_INDIRECT:
|
|
# if we are not ignoring indirect calls, then we must treat invoke_* as if
|
|
# they are indirect calls, since that is what they do - we can't see their
|
|
# targets statically.
|
|
shared.Settings.ASYNCIFY_IMPORTS += ['invoke_*']
|
|
# with pthreads we may call main through the __call_main mechanism, which can
|
|
# therefore reach anything in the program, so mark it as possibly causing a
|
|
# sleep (the asyncify analysis doesn't look through JS, just wasm, so it can't
|
|
# see what it itself calls)
|
|
if shared.Settings.USE_PTHREADS:
|
|
shared.Settings.ASYNCIFY_IMPORTS += ['__call_main']
|
|
# add the default imports
|
|
shared.Settings.ASYNCIFY_IMPORTS += DEFAULT_ASYNCIFY_IMPORTS
|
|
|
|
# return the full import name, including module. The name may
|
|
# already have a module prefix; if not, we assume it is "env".
|
|
def get_full_import_name(name):
|
|
if '.' in name:
|
|
return name
|
|
return 'env.' + name
|
|
|
|
shared.Settings.ASYNCIFY_IMPORTS = [get_full_import_name(i) for i in shared.Settings.ASYNCIFY_IMPORTS]
|
|
|
|
if shared.Settings.WASM2JS and shared.Settings.GENERATE_SOURCE_MAP:
|
|
exit_with_error('wasm2js does not support source maps yet (debug in wasm for now)')
|
|
|
|
if shared.Settings.NODE_CODE_CACHING:
|
|
if shared.Settings.WASM_ASYNC_COMPILATION:
|
|
exit_with_error('NODE_CODE_CACHING requires sync compilation (WASM_ASYNC_COMPILATION=0)')
|
|
if not shared.Settings.target_environment_may_be('node'):
|
|
exit_with_error('NODE_CODE_CACHING only works in node, but target environments do not include it')
|
|
if shared.Settings.SINGLE_FILE:
|
|
exit_with_error('NODE_CODE_CACHING saves a file on the side and is not compatible with SINGLE_FILE')
|
|
|
|
if options.tracing and shared.Settings.ALLOW_MEMORY_GROWTH:
|
|
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout']
|
|
shared.Settings.EXPORTED_FUNCTIONS += ['_emscripten_stack_get_current',
|
|
'_emscripten_stack_get_base',
|
|
'_emscripten_stack_get_end']
|
|
|
|
# Any "pointers" passed to JS will now be i64's, in both modes.
|
|
if shared.Settings.MEMORY64:
|
|
if settings_key_changes.get('WASM_BIGINT') == '0':
|
|
exit_with_error('MEMORY64 is not compatible with WASM_BIGINT=0')
|
|
shared.Settings.WASM_BIGINT = 1
|
|
|
|
# check if we can address the 2GB mark and higher: either if we start at
|
|
# 2GB, or if we allow growth to either any amount or to 2GB or more.
|
|
if shared.Settings.INITIAL_MEMORY > 2 * 1024 * 1024 * 1024 or \
|
|
(shared.Settings.ALLOW_MEMORY_GROWTH and
|
|
(shared.Settings.MAXIMUM_MEMORY < 0 or
|
|
shared.Settings.MAXIMUM_MEMORY > 2 * 1024 * 1024 * 1024)):
|
|
shared.Settings.CAN_ADDRESS_2GB = 1
|
|
|
|
shared.Settings.EMSCRIPTEN_VERSION = shared.EMSCRIPTEN_VERSION
|
|
shared.Settings.PROFILING_FUNCS = options.profiling_funcs
|
|
shared.Settings.SOURCE_MAP_BASE = options.source_map_base or ''
|
|
|
|
## Compile source code to bitcode
|
|
|
|
logger.debug('compiling to bitcode')
|
|
|
|
temp_files = []
|
|
|
|
# exit block 'parse arguments and setup'
|
|
log_time('parse arguments and setup')
|
|
|
|
if options.post_link:
|
|
process_libraries(libs, lib_dirs, temp_files)
|
|
if len(input_files) != 1:
|
|
exit_with_error('--post-link requires a single input file')
|
|
post_link(options, input_files[0][1], wasm_target, target)
|
|
return 0
|
|
|
|
with ToolchainProfiler.profile_block('compile inputs'):
|
|
def is_link_flag(flag):
|
|
if flag.startswith('-nostdlib'):
|
|
return True
|
|
return flag.startswith(('-l', '-L', '-Wl,'))
|
|
|
|
CXX = [shared.CLANG_CXX]
|
|
CC = [shared.CLANG_CC]
|
|
if config.COMPILER_WRAPPER:
|
|
logger.debug('using compiler wrapper: %s', config.COMPILER_WRAPPER)
|
|
CXX.insert(0, config.COMPILER_WRAPPER)
|
|
CC.insert(0, config.COMPILER_WRAPPER)
|
|
|
|
if 'EMMAKEN_COMPILER' in os.environ:
|
|
diagnostics.warning('deprecated', '`EMMAKEN_COMPILER` is deprecated.\n'
|
|
'To use an alteranative LLVM build set `LLVM_ROOT` in the config file (or `EM_LLVM_ROOT` env var).\n'
|
|
'To wrap invocations of clang use the `COMPILER_WRAPPER` setting (or `EM_COMPILER_WRAPPER` env var.\n')
|
|
CXX = [os.environ['EMMAKEN_COMPILER']]
|
|
CC = [cxx_to_c_compiler(os.environ['EMMAKEN_COMPILER'])]
|
|
|
|
compile_args = [a for a in newargs if a and not is_link_flag(a)]
|
|
cflags = calc_cflags(options)
|
|
system_libs.ensure_sysroot()
|
|
system_libs.add_ports_cflags(cflags, shared.Settings)
|
|
|
|
def use_cxx(src):
|
|
if 'c++' in language_mode or run_via_emxx:
|
|
return True
|
|
# Next consider the filename
|
|
if src.endswith(C_ENDINGS + OBJC_ENDINGS):
|
|
return False
|
|
if src.endswith(CXX_ENDINGS):
|
|
return True
|
|
# Finally fall back to the default
|
|
if shared.Settings.DEFAULT_TO_CXX:
|
|
# Default to using C++ even when run as `emcc`.
|
|
# This means that emcc will act as a C++ linker when no source files are
|
|
# specified.
|
|
# This differs to clang and gcc where the default is always C unless run as
|
|
# clang++/g++.
|
|
return True
|
|
return False
|
|
|
|
def get_compiler(cxx):
|
|
if cxx:
|
|
return CXX
|
|
return CC
|
|
|
|
def get_clang_command(src_file):
|
|
per_file_cflags = shared.get_cflags(args)
|
|
return get_compiler(use_cxx(src_file)) + cflags + per_file_cflags + compile_args + [src_file]
|
|
|
|
def get_clang_command_asm(src_file):
|
|
asflags = shared.get_clang_flags()
|
|
return get_compiler(use_cxx(src_file)) + asflags + compile_args + [src_file]
|
|
|
|
# preprocessor-only (-E) support
|
|
if has_dash_E or '-M' in newargs or '-MM' in newargs or '-fsyntax-only' in newargs:
|
|
for input_file in [x[1] for x in input_files]:
|
|
cmd = get_clang_command(input_file)
|
|
if specified_target:
|
|
cmd += ['-o', specified_target]
|
|
# Do not compile, but just output the result from preprocessing stage or
|
|
# output the dependency rule. Warning: clang and gcc behave differently
|
|
# with -MF! (clang seems to not recognize it)
|
|
logger.debug(('just preprocessor ' if has_dash_E else 'just dependencies: ') + ' '.join(cmd))
|
|
shared.check_call(cmd)
|
|
return 0
|
|
|
|
# Precompiled headers support
|
|
if has_header_inputs:
|
|
headers = [header for _, header in input_files]
|
|
for header in headers:
|
|
if not header.endswith(HEADER_ENDINGS):
|
|
exit_with_error('cannot mix precompile headers with non-header inputs: ' + str(headers) + ' : ' + header)
|
|
cmd = get_clang_command(header)
|
|
if specified_target:
|
|
cmd += ['-o', specified_target]
|
|
logger.debug("running (for precompiled headers): " + cmd[0] + ' ' + ' '.join(cmd[1:]))
|
|
shared.check_call(cmd)
|
|
return 0
|
|
|
|
def get_object_filename(input_file):
|
|
if compile_only:
|
|
# In compile-only mode we don't use any temp file. The object files
|
|
# are written directly to their final output locations.
|
|
if specified_target:
|
|
assert len(input_files) == 1
|
|
return specified_target
|
|
else:
|
|
return unsuffixed_basename(input_file) + options.default_object_extension
|
|
else:
|
|
return in_temp(unsuffixed(uniquename(input_file)) + options.default_object_extension)
|
|
|
|
def compile_source_file(i, input_file):
|
|
logger.debug('compiling source file: ' + input_file)
|
|
output_file = get_object_filename(input_file)
|
|
temp_files.append((i, output_file))
|
|
if get_file_suffix(input_file) in ASSEMBLY_ENDINGS:
|
|
cmd = get_clang_command_asm(input_file)
|
|
else:
|
|
cmd = get_clang_command(input_file)
|
|
if not has_dash_c:
|
|
cmd += ['-c']
|
|
cmd += ['-o', output_file]
|
|
shared.check_call(cmd)
|
|
if output_file not in ('-', os.devnull):
|
|
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_suffix = get_file_suffix(input_file)
|
|
if file_suffix in SOURCE_ENDINGS + ASSEMBLY_ENDINGS or (has_dash_c and file_suffix == '.bc'):
|
|
compile_source_file(i, input_file)
|
|
elif file_suffix in DYNAMICLIB_ENDINGS:
|
|
logger.debug('using shared library: ' + input_file)
|
|
temp_files.append((i, input_file))
|
|
elif building.is_ar(input_file):
|
|
logger.debug('using static library: ' + input_file)
|
|
ensure_archive_index(input_file)
|
|
temp_files.append((i, input_file))
|
|
elif language_mode:
|
|
compile_source_file(i, input_file)
|
|
elif input_file == '-':
|
|
exit_with_error('-E or -x required when input is from standard input')
|
|
else:
|
|
# Default to assuming the inputs are object files and pass them to the linker
|
|
logger.debug('using object file: ' + input_file)
|
|
temp_files.append((i, input_file))
|
|
|
|
# exit block 'compile inputs'
|
|
log_time('compile inputs')
|
|
|
|
if compile_only:
|
|
logger.debug('stopping after compile phase')
|
|
for flag in link_flags:
|
|
diagnostics.warning('unused-command-line-argument', "argument unused during compilation: '%s'" % flag[1])
|
|
return 0
|
|
|
|
if specified_target and specified_target.startswith('-'):
|
|
exit_with_error('invalid output filename: `%s`' % specified_target)
|
|
|
|
ldflags = shared.emsdk_ldflags(newargs)
|
|
for f in ldflags:
|
|
add_link_flag(sys.maxsize, f)
|
|
|
|
using_lld = not (link_to_object and shared.Settings.LTO)
|
|
link_flags = filter_link_flags(link_flags, using_lld)
|
|
|
|
# Decide what we will link
|
|
consumed = process_libraries(libs, lib_dirs, temp_files)
|
|
# Filter out libraries that are actually JS libs
|
|
link_flags = [l for l in link_flags if l[0] not in consumed]
|
|
temp_files = filter_out_dynamic_libs(temp_files)
|
|
|
|
linker_inputs = [val for _, val in sorted(temp_files + link_flags)]
|
|
|
|
if link_to_object:
|
|
with ToolchainProfiler.profile_block('linking to object file'):
|
|
logger.debug('link_to_object: ' + str(linker_inputs) + ' -> ' + target)
|
|
building.link_to_object(linker_inputs, target)
|
|
logger.debug('stopping after linking to object file')
|
|
return 0
|
|
|
|
if final_suffix in ('.o', '.bc', '.so', '.dylib') and not shared.Settings.SIDE_MODULE:
|
|
diagnostics.warning('emcc', 'generating an executable with an object extension (%s). If you meant to build an object file please use `-c, `-r`, or `-shared`' % final_suffix)
|
|
|
|
## Continue on to create JavaScript
|
|
|
|
with ToolchainProfiler.profile_block('calculate system libraries'):
|
|
# link in ports and system libraries, if necessary
|
|
if 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)
|
|
if '-nostdlib' not in newargs and '-nodefaultlibs' not in newargs:
|
|
link_as_cxx = run_via_emxx
|
|
# Traditionally we always link as C++. For compatibility we continue to do that,
|
|
# unless running in strict mode.
|
|
if not shared.Settings.STRICT and '-nostdlib++' not in newargs:
|
|
link_as_cxx = True
|
|
extra_files_to_link += system_libs.calculate([f for _, f in sorted(temp_files)] + extra_files_to_link, link_as_cxx, forced=forced_stdlibs)
|
|
linker_inputs += extra_files_to_link
|
|
|
|
# exit block 'calculate system libraries'
|
|
log_time('calculate system libraries')
|
|
|
|
def dedup_list(lst):
|
|
rtn = []
|
|
for item in lst:
|
|
if item not in rtn:
|
|
rtn.append(item)
|
|
return rtn
|
|
|
|
# Make a final pass over shared.Settings.EXPORTED_FUNCTIONS to remove any
|
|
# duplication between functions added by the driver/libraries and function
|
|
# specified by the user
|
|
shared.Settings.EXPORTED_FUNCTIONS = dedup_list(shared.Settings.EXPORTED_FUNCTIONS)
|
|
|
|
with ToolchainProfiler.profile_block('link'):
|
|
logger.debug('linking: ' + str(linker_inputs))
|
|
|
|
# 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
|
|
js_funcs = None
|
|
if shared.Settings.LLD_REPORT_UNDEFINED and shared.Settings.ERROR_ON_UNDEFINED_SYMBOLS:
|
|
js_funcs = get_all_js_syms()
|
|
log_time('JS symbol generation')
|
|
building.link_lld(linker_inputs, wasm_target, external_symbol_list=js_funcs)
|
|
# Special handling for when the user passed '-Wl,--version'. In this case the linker
|
|
# does not create the output file, but just prints its version and exits with 0.
|
|
if '--version' in linker_inputs:
|
|
return 0
|
|
|
|
# exit block 'link'
|
|
log_time('link')
|
|
|
|
if target == os.devnull:
|
|
# TODO(sbc): In theory we should really run the whole pipeline even if the output is
|
|
# /dev/null, but that will take some refactoring
|
|
return 0
|
|
|
|
# Perform post-link steps (unless we are running bare mode)
|
|
if options.oformat != OFormat.BARE:
|
|
post_link(options, wasm_target, wasm_target, target)
|
|
|
|
return 0
|
|
|
|
|
|
def post_link(options, in_wasm, wasm_target, target):
|
|
global final_js
|
|
|
|
target_basename = unsuffixed_basename(target)
|
|
|
|
if options.oformat != OFormat.WASM:
|
|
final_js = in_temp(target_basename + '.js')
|
|
|
|
if shared.Settings.MEM_INIT_IN_WASM:
|
|
memfile = None
|
|
else:
|
|
memfile = shared.replace_or_append_suffix(target, '.mem')
|
|
|
|
with ToolchainProfiler.profile_block('emscript'):
|
|
# Emscripten
|
|
logger.debug('emscript')
|
|
if options.memory_init_file:
|
|
shared.Settings.MEM_INIT_METHOD = 1
|
|
else:
|
|
assert shared.Settings.MEM_INIT_METHOD != 1
|
|
|
|
if embed_memfile():
|
|
shared.Settings.SUPPORT_BASE64_EMBEDDING = 1
|
|
|
|
emscripten.run(in_wasm, wasm_target, final_js, memfile)
|
|
save_intermediate('original')
|
|
|
|
# exit block 'emscript'
|
|
log_time('emscript)')
|
|
|
|
with ToolchainProfiler.profile_block('source transforms'):
|
|
# Embed and preload files
|
|
if len(options.preload_files) or len(options.embed_files):
|
|
logger.debug('setting up files')
|
|
file_args = ['--from-emcc', '--export-name=' + shared.Settings.EXPORT_NAME]
|
|
if len(options.preload_files):
|
|
file_args.append('--preload')
|
|
file_args += options.preload_files
|
|
if len(options.embed_files):
|
|
file_args.append('--embed')
|
|
file_args += options.embed_files
|
|
if len(options.exclude_files):
|
|
file_args.append('--exclude')
|
|
file_args += options.exclude_files
|
|
if options.use_preload_cache:
|
|
file_args.append('--use-preload-cache')
|
|
if shared.Settings.LZ4:
|
|
file_args.append('--lz4')
|
|
if options.use_preload_plugins:
|
|
file_args.append('--use-preload-plugins')
|
|
file_code = shared.check_call([shared.FILE_PACKAGER, unsuffixed(target) + '.data'] + file_args, stdout=PIPE).stdout
|
|
options.pre_js = js_manipulation.add_files_pre_js(options.pre_js, file_code)
|
|
|
|
# Apply pre and postjs files
|
|
if final_js and (options.pre_js or options.post_js):
|
|
logger.debug('applying pre/postjses')
|
|
src = open(final_js).read()
|
|
final_js += '.pp.js'
|
|
with open(final_js, 'w') as f:
|
|
# pre-js code goes right after the Module integration code (so it
|
|
# can use Module), we have a marker for it
|
|
f.write(do_replace(src, '// {{PRE_JSES}}', fix_windows_newlines(options.pre_js)))
|
|
f.write(fix_windows_newlines(options.post_js))
|
|
options.pre_js = src = options.post_js = None
|
|
save_intermediate('pre-post')
|
|
|
|
# Apply a source code transformation, if requested
|
|
if options.js_transform:
|
|
safe_copy(final_js, final_js + '.tr.js')
|
|
final_js += '.tr.js'
|
|
posix = not shared.WINDOWS
|
|
logger.debug('applying transform: %s', options.js_transform)
|
|
shared.check_call(building.remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final_js)]))
|
|
save_intermediate('transformed')
|
|
|
|
# exit block 'source transforms'
|
|
log_time('source transforms')
|
|
|
|
if memfile and not shared.Settings.MINIMAL_RUNTIME:
|
|
# MINIMAL_RUNTIME doesn't use `var memoryInitializer` but instead expects Module['mem'] to
|
|
# be loaded before the module. See src/postamble_minimal.js.
|
|
with ToolchainProfiler.profile_block('memory initializer'):
|
|
# For the wasm backend, we don't have any memory info in JS. All we need to do
|
|
# is set the memory initializer url.
|
|
src = open(final_js).read()
|
|
src = do_replace(src, '// {{MEM_INITIALIZER}}', 'var memoryInitializer = "%s";' % os.path.basename(memfile))
|
|
open(final_js + '.mem.js', 'w').write(src)
|
|
final_js += '.mem.js'
|
|
|
|
log_time('memory initializer')
|
|
|
|
with ToolchainProfiler.profile_block('binaryen'):
|
|
do_binaryen(target, options, wasm_target)
|
|
|
|
log_time('binaryen')
|
|
# If we are not emitting any JS then we are all done now
|
|
if options.oformat == OFormat.WASM:
|
|
return
|
|
|
|
with ToolchainProfiler.profile_block('final emitting'):
|
|
# Remove some trivial whitespace
|
|
# TODO: do not run when compress has already been done on all parts of the code
|
|
# src = open(final_js).read()
|
|
# src = re.sub(r'\n+[ \n]*\n+', '\n', src)
|
|
# open(final_js, 'w').write(src)
|
|
|
|
if shared.Settings.USE_PTHREADS:
|
|
target_dir = os.path.dirname(os.path.abspath(target))
|
|
worker_output = os.path.join(target_dir, shared.Settings.PTHREAD_WORKER_FILE)
|
|
with open(worker_output, 'w') as f:
|
|
f.write(shared.read_and_preprocess(shared.path_from_root('src', 'worker.js'), expand_macros=True))
|
|
|
|
# Minify the worker.js file in optimized builds
|
|
if (shared.Settings.OPT_LEVEL >= 1 or shared.Settings.SHRINK_LEVEL >= 1) and not shared.Settings.DEBUG_LEVEL:
|
|
minified_worker = building.acorn_optimizer(worker_output, ['minifyWhitespace'], return_output=True)
|
|
open(worker_output, 'w').write(minified_worker)
|
|
|
|
# track files that will need native eols
|
|
generated_text_files_with_native_eols = []
|
|
|
|
if shared.Settings.MODULARIZE:
|
|
modularize()
|
|
|
|
module_export_name_substitution()
|
|
|
|
# Run a final regex pass to clean up items that were not possible to optimize by Closure, or unoptimalities that were left behind
|
|
# by processing steps that occurred after Closure.
|
|
if shared.Settings.MINIMAL_RUNTIME == 2 and shared.Settings.USE_CLOSURE_COMPILER and shared.Settings.DEBUG_LEVEL == 0 and not shared.Settings.SINGLE_FILE:
|
|
# Process .js runtime file. Note that we need to handle the license text
|
|
# here, so that it will not confuse the hacky script.
|
|
shared.JS.handle_license(final_js)
|
|
shared.run_process([shared.PYTHON, shared.path_from_root('tools', 'hacky_postprocess_around_closure_limitations.py'), final_js])
|
|
|
|
# Apply pre and postjs files
|
|
if options.extern_pre_js or options.extern_post_js:
|
|
logger.debug('applying extern pre/postjses')
|
|
src = open(final_js).read()
|
|
final_js += '.epp.js'
|
|
with open(final_js, 'w') as f:
|
|
f.write(fix_windows_newlines(options.extern_pre_js))
|
|
f.write(src)
|
|
f.write(fix_windows_newlines(options.extern_post_js))
|
|
save_intermediate('extern-pre-post')
|
|
|
|
shared.JS.handle_license(final_js)
|
|
|
|
if options.oformat in (OFormat.JS, OFormat.MJS):
|
|
js_target = target
|
|
else:
|
|
js_target = unsuffixed(target) + '.js'
|
|
|
|
# The JS is now final. Move it to its final location
|
|
safe_move(final_js, js_target)
|
|
|
|
if not shared.Settings.SINGLE_FILE:
|
|
generated_text_files_with_native_eols += [js_target]
|
|
|
|
# If we were asked to also generate HTML, do that
|
|
if options.oformat == OFormat.HTML:
|
|
generate_html(target, options, js_target, target_basename,
|
|
wasm_target, memfile)
|
|
elif options.proxy_to_worker:
|
|
generate_worker_js(target, js_target, target_basename)
|
|
|
|
if embed_memfile() and memfile:
|
|
shared.try_delete(memfile)
|
|
|
|
if shared.Settings.SPLIT_MODULE:
|
|
diagnostics.warning('experimental', 'The SPLIT_MODULE setting is experimental and subject to change')
|
|
do_split_module(wasm_target)
|
|
|
|
for f in generated_text_files_with_native_eols:
|
|
tools.line_endings.convert_line_endings_in_file(f, os.linesep, options.output_eol)
|
|
|
|
if options.executable:
|
|
make_js_executable(js_target)
|
|
|
|
log_time('final emitting')
|
|
# exit block 'final emitting'
|
|
|
|
return 0
|
|
|
|
|
|
def version_string():
|
|
return 'emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) %s' % shared.EMSCRIPTEN_VERSION
|
|
|
|
|
|
def parse_args(newargs):
|
|
options = EmccOptions()
|
|
settings_changes = []
|
|
user_js_defines = []
|
|
should_exit = False
|
|
eh_enabled = False
|
|
wasm_eh_enabled = False
|
|
skip = False
|
|
|
|
for i in range(len(newargs)):
|
|
if skip:
|
|
skip = False
|
|
continue
|
|
|
|
# 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()
|
|
arg = newargs[i]
|
|
arg_value = None
|
|
|
|
def check_flag(value):
|
|
# Check for and consume a flag
|
|
if arg == value:
|
|
newargs[i] = ''
|
|
return True
|
|
return False
|
|
|
|
def check_arg(name):
|
|
nonlocal arg_value
|
|
if arg.startswith(name) and '=' in arg:
|
|
arg_value = arg.split('=', 1)[1]
|
|
newargs[i] = ''
|
|
return True
|
|
if arg == name:
|
|
if len(newargs) <= i + 1:
|
|
exit_with_error("option '%s' requires an argument" % arg)
|
|
arg_value = newargs[i + 1]
|
|
newargs[i] = ''
|
|
newargs[i + 1] = ''
|
|
return True
|
|
return False
|
|
|
|
def consume_arg():
|
|
nonlocal arg_value
|
|
assert arg_value is not None
|
|
rtn = arg_value
|
|
arg_value = None
|
|
return rtn
|
|
|
|
def consume_arg_file():
|
|
name = consume_arg()
|
|
if not os.path.isfile(name):
|
|
exit_with_error("'%s': file not found: '%s'" % (arg, name))
|
|
return name
|
|
|
|
if arg.startswith('-O'):
|
|
# Let -O default to -O2, which is what gcc does.
|
|
options.requested_level = arg[2:] or '2'
|
|
if options.requested_level == 's':
|
|
options.llvm_opts = ['-Os']
|
|
options.requested_level = 2
|
|
shared.Settings.SHRINK_LEVEL = 1
|
|
settings_changes.append('INLINING_LIMIT=50')
|
|
elif options.requested_level == 'z':
|
|
options.llvm_opts = ['-Oz']
|
|
options.requested_level = 2
|
|
shared.Settings.SHRINK_LEVEL = 2
|
|
settings_changes.append('INLINING_LIMIT=25')
|
|
shared.Settings.OPT_LEVEL = validate_arg_level(options.requested_level, 3, 'Invalid optimization level: ' + arg, clamp=True)
|
|
elif check_arg('--js-opts'):
|
|
logger.warning('--js-opts ignored when using llvm backend')
|
|
consume_arg()
|
|
elif check_arg('--llvm-opts'):
|
|
options.llvm_opts = parse_value(consume_arg())
|
|
elif arg.startswith('-flto'):
|
|
if '=' in arg:
|
|
shared.Settings.LTO = arg.split('=')[1]
|
|
else:
|
|
shared.Settings.LTO = "full"
|
|
elif check_arg('--llvm-lto'):
|
|
logger.warning('--llvm-lto ignored when using llvm backend')
|
|
consume_arg()
|
|
elif check_arg('--closure-args'):
|
|
args = consume_arg()
|
|
options.closure_args += shlex.split(args)
|
|
elif check_arg('--closure'):
|
|
options.use_closure_compiler = int(consume_arg())
|
|
elif check_arg('--js-transform'):
|
|
options.js_transform = consume_arg()
|
|
elif check_arg('--pre-js'):
|
|
options.pre_js += open(consume_arg_file()).read() + '\n'
|
|
elif check_arg('--post-js'):
|
|
options.post_js += open(consume_arg_file()).read() + '\n'
|
|
elif check_arg('--extern-pre-js'):
|
|
options.extern_pre_js += open(consume_arg_file()).read() + '\n'
|
|
elif check_arg('--extern-post-js'):
|
|
options.extern_post_js += open(consume_arg_file()).read() + '\n'
|
|
elif check_arg('--compiler-wrapper'):
|
|
config.COMPILER_WRAPPER = consume_arg()
|
|
elif check_flag('--post-link'):
|
|
options.post_link = True
|
|
elif check_arg('--oformat'):
|
|
formats = [f.lower() for f in OFormat.__members__]
|
|
fmt = consume_arg()
|
|
if fmt not in formats:
|
|
exit_with_error('invalid output format: `%s` (must be one of %s)' % (fmt, formats))
|
|
options.oformat = getattr(OFormat, fmt.upper())
|
|
elif check_arg('--minify'):
|
|
arg = consume_arg()
|
|
if arg != '0':
|
|
exit_with_error('0 is the only supported option for --minify; 1 has been deprecated')
|
|
shared.Settings.DEBUG_LEVEL = max(1, shared.Settings.DEBUG_LEVEL)
|
|
elif arg.startswith('-g'):
|
|
options.requested_debug = arg
|
|
requested_level = arg[2:] or '3'
|
|
if is_int(requested_level):
|
|
# the -gX value is the debug level (-g1, -g2, etc.)
|
|
shared.Settings.DEBUG_LEVEL = validate_arg_level(requested_level, 4, 'Invalid debug level: ' + arg)
|
|
# if we don't need to preserve LLVM debug info, do not keep this flag
|
|
# for clang
|
|
if shared.Settings.DEBUG_LEVEL < 3:
|
|
newargs[i] = ''
|
|
else:
|
|
# for 3+, report -g to clang as -g4 is not accepted
|
|
newargs[i] = '-g'
|
|
else:
|
|
if requested_level.startswith('force_dwarf'):
|
|
exit_with_error('gforce_dwarf was a temporary option and is no longer necessary (use -g)')
|
|
elif requested_level.startswith('separate-dwarf'):
|
|
# emit full DWARF but also emit it in a file on the side
|
|
newargs[i] = '-g'
|
|
# if a file is provided, use that; otherwise use the default location
|
|
# (note that we do not know the default location until all args have
|
|
# been parsed, so just note True for now).
|
|
if requested_level != 'separate-dwarf':
|
|
if not requested_level.startswith('separate-dwarf=') or requested_level.count('=') != 1:
|
|
exit_with_error('invalid -gseparate-dwarf=FILENAME notation')
|
|
shared.Settings.SEPARATE_DWARF = requested_level.split('=')[1]
|
|
else:
|
|
shared.Settings.SEPARATE_DWARF = True
|
|
# a non-integer level can be something like -gline-tables-only. keep
|
|
# the flag for the clang frontend to emit the appropriate DWARF info.
|
|
# set the emscripten debug level to 3 so that we do not remove that
|
|
# debug info during link (during compile, this does not make a
|
|
# difference).
|
|
shared.Settings.DEBUG_LEVEL = 3
|
|
elif check_flag('-profiling') or check_flag('--profiling'):
|
|
shared.Settings.DEBUG_LEVEL = max(shared.Settings.DEBUG_LEVEL, 2)
|
|
options.profiling = True
|
|
elif check_flag('-profiling-funcs') or check_flag('--profiling-funcs'):
|
|
options.profiling_funcs = True
|
|
elif newargs[i] == '--tracing' or newargs[i] == '--memoryprofiler':
|
|
if newargs[i] == '--memoryprofiler':
|
|
options.memory_profiler = True
|
|
options.tracing = True
|
|
newargs[i] = ''
|
|
settings_changes.append("EMSCRIPTEN_TRACING=1")
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'library_trace.js')))
|
|
elif check_flag('--emit-symbol-map'):
|
|
options.emit_symbol_map = True
|
|
shared.Settings.EMIT_SYMBOL_MAP = 1
|
|
elif check_flag('--bind'):
|
|
shared.Settings.EMBIND = 1
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'embind', 'emval.js')))
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((0, shared.path_from_root('src', 'embind', 'embind.js')))
|
|
elif check_arg('--embed-file'):
|
|
options.embed_files.append(consume_arg())
|
|
elif check_arg('--preload-file'):
|
|
options.preload_files.append(consume_arg())
|
|
elif check_arg('--exclude-file'):
|
|
options.exclude_files.append(consume_arg())
|
|
elif check_flag('--use-preload-cache'):
|
|
options.use_preload_cache = True
|
|
elif check_flag('--no-heap-copy'):
|
|
diagnostics.warning('legacy-settings', 'ignoring legacy flag --no-heap-copy (that is the only mode supported now)')
|
|
elif check_flag('--use-preload-plugins'):
|
|
options.use_preload_plugins = True
|
|
elif check_flag('--ignore-dynamic-linking'):
|
|
options.ignore_dynamic_linking = True
|
|
elif arg == '-v':
|
|
shared.PRINT_STAGES = True
|
|
elif check_arg('--shell-file'):
|
|
options.shell_path = consume_arg_file()
|
|
elif check_arg('--source-map-base'):
|
|
options.source_map_base = consume_arg()
|
|
elif check_flag('--no-entry'):
|
|
options.no_entry = True
|
|
elif check_arg('--js-library'):
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.append((i + 1, os.path.abspath(consume_arg_file())))
|
|
elif check_flag('--remove-duplicates'):
|
|
diagnostics.warning('legacy-settings', '--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase')
|
|
elif check_flag('--jcache'):
|
|
logger.error('jcache is no longer supported')
|
|
elif check_flag('--clear-cache'):
|
|
logger.info('clearing cache as requested by --clear-cache')
|
|
shared.Cache.erase()
|
|
shared.check_sanity(force=True) # this is a good time for a sanity check
|
|
should_exit = True
|
|
elif check_flag('--clear-ports'):
|
|
logger.info('clearing ports and cache as requested by --clear-ports')
|
|
system_libs.Ports.erase()
|
|
shared.Cache.erase()
|
|
shared.check_sanity(force=True) # this is a good time for a sanity check
|
|
should_exit = True
|
|
elif check_flag('--check'):
|
|
print(version_string(), file=sys.stderr)
|
|
shared.check_sanity(force=True)
|
|
should_exit = True
|
|
elif check_flag('--show-ports'):
|
|
system_libs.show_ports()
|
|
should_exit = True
|
|
elif check_arg('--memory-init-file'):
|
|
options.memory_init_file = int(consume_arg())
|
|
elif check_flag('--proxy-to-worker'):
|
|
options.proxy_to_worker = True
|
|
elif check_arg('--valid-abspath'):
|
|
options.valid_abspaths.append(consume_arg())
|
|
elif check_flag('--separate-asm'):
|
|
exit_with_error('cannot --separate-asm with the wasm backend, since not emitting asm.js')
|
|
elif arg.startswith(('-I', '-L')):
|
|
path_name = arg[2:]
|
|
if os.path.isabs(path_name) and not is_valid_abspath(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.
|
|
diagnostics.warning(
|
|
'absolute-paths', '-I or -L of an absolute path "' + arg +
|
|
'" 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 check_flag('--emrun'):
|
|
options.emrun = True
|
|
elif check_flag('--cpuprofiler'):
|
|
options.cpu_profiler = True
|
|
elif check_flag('--threadprofiler'):
|
|
options.thread_profiler = True
|
|
settings_changes.append('PTHREADS_PROFILING=1')
|
|
elif arg == '-fno-exceptions':
|
|
shared.Settings.DISABLE_EXCEPTION_CATCHING = 1
|
|
shared.Settings.DISABLE_EXCEPTION_THROWING = 1
|
|
shared.Settings.EXCEPTION_HANDLING = 0
|
|
elif arg == '-fexceptions':
|
|
eh_enabled = True
|
|
elif arg == '-fwasm-exceptions':
|
|
wasm_eh_enabled = True
|
|
elif arg == '-fignore-exceptions':
|
|
shared.Settings.DISABLE_EXCEPTION_CATCHING = 1
|
|
elif check_arg('--default-obj-ext'):
|
|
options.default_object_extension = consume_arg()
|
|
if not options.default_object_extension.startswith('.'):
|
|
options.default_object_extension = '.' + options.default_object_extension
|
|
elif arg == '-fsanitize=cfi':
|
|
options.cfi = True
|
|
elif check_arg('--output_eol'):
|
|
style = consume_arg()
|
|
if style.lower() == 'windows':
|
|
options.output_eol = '\r\n'
|
|
elif style.lower() == 'linux':
|
|
options.output_eol = '\n'
|
|
else:
|
|
exit_with_error('Invalid value "' + style + '" to --output_eol!')
|
|
elif check_arg('--generate-config'):
|
|
optarg = consume_arg()
|
|
path = os.path.expanduser(optarg)
|
|
if os.path.exists(path):
|
|
exit_with_error('File ' + optarg + ' passed to --generate-config already exists!')
|
|
else:
|
|
config.generate_config(optarg)
|
|
should_exit = True
|
|
# Record USE_PTHREADS setting because it controls whether --shared-memory is passed to lld
|
|
elif arg == '-pthread':
|
|
settings_changes.append('USE_PTHREADS=1')
|
|
elif arg in ('-fno-diagnostics-color', '-fdiagnostics-color=never'):
|
|
colored_logger.disable()
|
|
diagnostics.color_enabled = False
|
|
elif arg == '-fno-rtti':
|
|
shared.Settings.USE_RTTI = 0
|
|
elif arg == '-frtti':
|
|
shared.Settings.USE_RTTI = 1
|
|
elif arg.startswith('-jsD'):
|
|
key = arg[4:]
|
|
if '=' in key:
|
|
key, value = key.split('=')
|
|
else:
|
|
value = '1'
|
|
if key in shared.Settings.attrs:
|
|
exit_with_error(arg + ': cannot change built-in settings values with a -jsD directive. Pass -s ' + key + '=' + value + ' instead!')
|
|
user_js_defines += [(key, value)]
|
|
newargs[i] = ''
|
|
elif check_flag('-shared'):
|
|
options.shared = True
|
|
elif check_flag('-r'):
|
|
options.relocatable = True
|
|
elif check_arg('-o'):
|
|
options.output_file = consume_arg()
|
|
elif arg.startswith('-o'):
|
|
options.output_file = arg[2:]
|
|
newargs[i] = ''
|
|
elif arg == '-mllvm':
|
|
# Ignore the next argument rather than trying to parse it. This is needed
|
|
# because llvm args could, for example, start with `-o` and we don't want
|
|
# to confuse that with a normal `-o` flag.
|
|
skip = True
|
|
|
|
if should_exit:
|
|
sys.exit(0)
|
|
|
|
# TODO Currently -fexceptions only means Emscripten EH. Switch to wasm
|
|
# exception handling by default when -fexceptions is given when wasm
|
|
# exception handling becomes stable.
|
|
if wasm_eh_enabled:
|
|
shared.Settings.EXCEPTION_HANDLING = 1
|
|
shared.Settings.DISABLE_EXCEPTION_THROWING = 1
|
|
shared.Settings.DISABLE_EXCEPTION_CATCHING = 1
|
|
elif eh_enabled:
|
|
shared.Settings.EXCEPTION_HANDLING = 0
|
|
shared.Settings.DISABLE_EXCEPTION_THROWING = 0
|
|
shared.Settings.DISABLE_EXCEPTION_CATCHING = 0
|
|
|
|
newargs = [a for a in newargs if a]
|
|
return options, settings_changes, user_js_defines, newargs
|
|
|
|
|
|
def emit_js_source_maps(target, js_transform_tempfiles):
|
|
logger.debug('generating source maps')
|
|
shared.run_js_tool(shared.path_from_root('tools', 'source-maps', 'sourcemapper.js'),
|
|
js_transform_tempfiles +
|
|
['--sourceRoot', os.getcwd(),
|
|
'--mapFileBaseName', target,
|
|
'--offset', '0'])
|
|
|
|
|
|
def do_binaryen(target, options, wasm_target):
|
|
global final_js
|
|
logger.debug('using binaryen')
|
|
if shared.Settings.GENERATE_SOURCE_MAP and not shared.Settings.SOURCE_MAP_BASE:
|
|
logger.warning("Wasm source map won't be usable in a browser without --source-map-base")
|
|
# whether we need to emit -g (function name debug info) in the final wasm
|
|
debug_info = shared.Settings.DEBUG_LEVEL >= 2 or options.profiling_funcs
|
|
# whether we need to emit -g in the intermediate binaryen invocations (but not necessarily at the very end).
|
|
# this is necessary for emitting a symbol map at the end.
|
|
intermediate_debug_info = bool(debug_info or options.emit_symbol_map or shared.Settings.ASYNCIFY_ONLY or shared.Settings.ASYNCIFY_REMOVE or shared.Settings.ASYNCIFY_ADD)
|
|
# note that wasm-ld can strip DWARF info for us too (--strip-debug), but it
|
|
# also strips the Names section. so to emit just the Names section we don't
|
|
# tell wasm-ld to strip anything, and we do it here.
|
|
strip_debug = shared.Settings.DEBUG_LEVEL < 3
|
|
strip_producers = not shared.Settings.EMIT_PRODUCERS_SECTION
|
|
# run wasm-opt if we have work for it: either passes, or if we are using
|
|
# source maps (which requires some extra processing to keep the source map
|
|
# but remove DWARF)
|
|
passes = get_binaryen_passes()
|
|
if passes or shared.Settings.GENERATE_SOURCE_MAP:
|
|
# if we need to strip certain sections, and we have wasm-opt passes
|
|
# to run anyhow, do it with them.
|
|
if strip_debug:
|
|
passes += ['--strip-debug']
|
|
if strip_producers:
|
|
passes += ['--strip-producers']
|
|
building.save_intermediate(wasm_target, 'pre-byn.wasm')
|
|
building.run_wasm_opt(wasm_target,
|
|
wasm_target,
|
|
args=passes,
|
|
debug=intermediate_debug_info)
|
|
else:
|
|
# we are not running wasm-opt. if we need to strip certain sections
|
|
# then do so using llvm-objcopy which is fast and does not rewrite the
|
|
# code (which is better for debug info)
|
|
if strip_debug or strip_producers:
|
|
building.save_intermediate(wasm_target, 'pre-strip.wasm')
|
|
building.strip(wasm_target, wasm_target, debug=strip_debug, producers=strip_producers)
|
|
|
|
if shared.Settings.EVAL_CTORS:
|
|
building.save_intermediate(wasm_target, 'pre-ctors.wasm')
|
|
building.eval_ctors(final_js, wasm_target, debug_info=intermediate_debug_info)
|
|
|
|
# after generating the wasm, do some final operations
|
|
if shared.Settings.SIDE_MODULE:
|
|
webassembly.add_dylink_section(wasm_target, shared.Settings.RUNTIME_LINKED_LIBS)
|
|
|
|
if shared.Settings.EMIT_EMSCRIPTEN_METADATA:
|
|
diagnostics.warning('deprecated', 'We hope to remove support for EMIT_EMSCRIPTEN_METADATA. See https://github.com/emscripten-core/emscripten/issues/12231')
|
|
webassembly.add_emscripten_metadata(wasm_target)
|
|
|
|
if final_js:
|
|
# pthreads memory growth requires some additional JS fixups
|
|
if shared.Settings.USE_PTHREADS and shared.Settings.ALLOW_MEMORY_GROWTH:
|
|
final_js = building.apply_wasm_memory_growth(final_js)
|
|
|
|
# >=2GB heap support requires pointers in JS to be unsigned. rather than
|
|
# require all pointers to be unsigned by default, which increases code size
|
|
# a little, keep them signed, and just unsign them here if we need that.
|
|
if shared.Settings.CAN_ADDRESS_2GB:
|
|
final_js = building.use_unsigned_pointers_in_js(final_js)
|
|
|
|
if shared.Settings.USE_ASAN:
|
|
final_js = building.instrument_js_for_asan(final_js)
|
|
|
|
if shared.Settings.SAFE_HEAP:
|
|
final_js = building.instrument_js_for_safe_heap(final_js)
|
|
|
|
if shared.Settings.OPT_LEVEL >= 2 and shared.Settings.DEBUG_LEVEL <= 2:
|
|
# minify the JS. Do not minify whitespace if Closure is used, so that
|
|
# Closure can print out readable error messages (Closure will then
|
|
# minify whitespace afterwards)
|
|
save_intermediate_with_wasm('preclean', wasm_target)
|
|
final_js = building.minify_wasm_js(js_file=final_js,
|
|
wasm_file=wasm_target,
|
|
expensive_optimizations=will_metadce(),
|
|
minify_whitespace=minify_whitespace() and not options.use_closure_compiler,
|
|
debug_info=intermediate_debug_info)
|
|
save_intermediate_with_wasm('postclean', wasm_target)
|
|
|
|
if shared.Settings.ASYNCIFY_LAZY_LOAD_CODE:
|
|
building.asyncify_lazy_load_code(wasm_target, debug=intermediate_debug_info)
|
|
|
|
def preprocess_wasm2js_script():
|
|
return read_and_preprocess(shared.path_from_root('src', 'wasm2js.js'), expand_macros=True)
|
|
|
|
def run_closure_compiler():
|
|
global final_js
|
|
final_js = building.closure_compiler(final_js, pretty=not minify_whitespace(),
|
|
extra_closure_args=options.closure_args)
|
|
save_intermediate_with_wasm('closure', wasm_target)
|
|
|
|
if final_js and options.use_closure_compiler:
|
|
run_closure_compiler()
|
|
|
|
symbols_file = shared.replace_or_append_suffix(target, '.symbols') if options.emit_symbol_map else None
|
|
|
|
if shared.Settings.WASM2JS:
|
|
if shared.Settings.WASM == 2:
|
|
wasm2js_template = wasm_target + '.js'
|
|
open(wasm2js_template, 'w').write(preprocess_wasm2js_script())
|
|
else:
|
|
wasm2js_template = final_js
|
|
|
|
wasm2js = building.wasm2js(wasm2js_template,
|
|
wasm_target,
|
|
opt_level=shared.Settings.OPT_LEVEL,
|
|
minify_whitespace=minify_whitespace(),
|
|
use_closure_compiler=options.use_closure_compiler,
|
|
debug_info=debug_info,
|
|
symbols_file=symbols_file)
|
|
if shared.Settings.WASM == 2:
|
|
safe_copy(wasm2js, wasm2js_template)
|
|
shared.try_delete(wasm2js)
|
|
|
|
if shared.Settings.WASM != 2:
|
|
final_js = wasm2js
|
|
# if we only target JS, we don't need the wasm any more
|
|
shared.try_delete(wasm_target)
|
|
|
|
save_intermediate('wasm2js')
|
|
|
|
# emit the final symbols, either in the binary or in a symbol map.
|
|
# this will also remove debug info if we only kept it around in the intermediate invocations.
|
|
# note that if we aren't emitting a binary (like in wasm2js) then we don't
|
|
# have anything to do here.
|
|
if options.emit_symbol_map and os.path.exists(wasm_target):
|
|
building.handle_final_wasm_symbols(wasm_file=wasm_target, symbols_file=symbols_file, debug_info=debug_info)
|
|
save_intermediate_with_wasm('symbolmap', wasm_target)
|
|
|
|
if shared.Settings.DEBUG_LEVEL >= 3 and shared.Settings.SEPARATE_DWARF and os.path.exists(wasm_target):
|
|
building.emit_debug_on_side(wasm_target, shared.Settings.SEPARATE_DWARF)
|
|
|
|
if shared.Settings.WASM2C:
|
|
wasm2c.do_wasm2c(wasm_target)
|
|
|
|
# replace placeholder strings with correct subresource locations
|
|
if final_js and shared.Settings.SINGLE_FILE and not shared.Settings.WASM2JS:
|
|
js = open(final_js).read()
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(open(wasm_target, 'rb').read()))
|
|
else:
|
|
js = do_replace(js, '<<< WASM_BINARY_FILE >>>', shared.JS.get_subresource_location(wasm_target))
|
|
shared.try_delete(wasm_target)
|
|
with open(final_js, 'w') as f:
|
|
f.write(js)
|
|
|
|
|
|
def modularize():
|
|
global final_js
|
|
logger.debug('Modularizing, assigning to var ' + shared.Settings.EXPORT_NAME)
|
|
src = open(final_js).read()
|
|
|
|
return_value = shared.Settings.EXPORT_NAME
|
|
if shared.Settings.WASM_ASYNC_COMPILATION:
|
|
return_value += '.ready'
|
|
if not shared.Settings.EXPORT_READY_PROMISE:
|
|
return_value = '{}'
|
|
|
|
src = '''
|
|
function(%(EXPORT_NAME)s) {
|
|
%(EXPORT_NAME)s = %(EXPORT_NAME)s || {};
|
|
|
|
%(src)s
|
|
|
|
return %(return_value)s
|
|
}
|
|
''' % {
|
|
'EXPORT_NAME': shared.Settings.EXPORT_NAME,
|
|
'src': src,
|
|
'return_value': return_value
|
|
}
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME and not shared.Settings.USE_PTHREADS:
|
|
# Single threaded MINIMAL_RUNTIME programs do not need access to
|
|
# document.currentScript, so a simple export declaration is enough.
|
|
src = 'var %s=%s' % (shared.Settings.EXPORT_NAME, src)
|
|
else:
|
|
script_url_node = ""
|
|
# When MODULARIZE this JS may be executed later,
|
|
# after document.currentScript is gone, so we save it.
|
|
# In EXPORT_ES6 + USE_PTHREADS the 'thread' is actually an ES6 module webworker running in strict mode,
|
|
# so doesn't have access to 'document'. In this case use 'import.meta' instead.
|
|
if shared.Settings.EXPORT_ES6 and shared.Settings.USE_ES6_IMPORT_META:
|
|
script_url = "import.meta.url"
|
|
else:
|
|
script_url = "typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined"
|
|
if shared.Settings.target_environment_may_be('node'):
|
|
script_url_node = "if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;"
|
|
src = '''
|
|
var %(EXPORT_NAME)s = (function() {
|
|
var _scriptDir = %(script_url)s;
|
|
%(script_url_node)s
|
|
return (%(src)s);
|
|
})();
|
|
''' % {
|
|
'EXPORT_NAME': shared.Settings.EXPORT_NAME,
|
|
'script_url': script_url,
|
|
'script_url_node': script_url_node,
|
|
'src': src
|
|
}
|
|
|
|
final_js += '.modular.js'
|
|
with open(final_js, 'w') as f:
|
|
f.write(src)
|
|
|
|
# Export using a UMD style export, or ES6 exports if selected
|
|
|
|
if shared.Settings.EXPORT_ES6:
|
|
f.write('export default %s;' % shared.Settings.EXPORT_NAME)
|
|
elif not shared.Settings.MINIMAL_RUNTIME:
|
|
f.write('''\
|
|
if (typeof exports === 'object' && typeof module === 'object')
|
|
module.exports = %(EXPORT_NAME)s;
|
|
else if (typeof define === 'function' && define['amd'])
|
|
define([], function() { return %(EXPORT_NAME)s; });
|
|
else if (typeof exports === 'object')
|
|
exports["%(EXPORT_NAME)s"] = %(EXPORT_NAME)s;
|
|
''' % {'EXPORT_NAME': shared.Settings.EXPORT_NAME})
|
|
|
|
save_intermediate('modularized')
|
|
|
|
|
|
def module_export_name_substitution():
|
|
global final_js
|
|
logger.debug('Private module export name substitution with ' + shared.Settings.EXPORT_NAME)
|
|
src = open(final_js).read()
|
|
final_js += '.module_export_name_substitution.js'
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
# In MINIMAL_RUNTIME the Module object is always present to provide the .asm.js/.wasm content
|
|
replacement = shared.Settings.EXPORT_NAME
|
|
else:
|
|
replacement = "typeof %(EXPORT_NAME)s !== 'undefined' ? %(EXPORT_NAME)s : {}" % {"EXPORT_NAME": shared.Settings.EXPORT_NAME}
|
|
with open(final_js, 'w') as f:
|
|
src = re.sub(r'{\s*[\'"]?__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__[\'"]?:\s*1\s*}', replacement, src)
|
|
# For Node.js and other shell environments, create an unminified Module object so that
|
|
# loading external .asm.js file that assigns to Module['asm'] works even when Closure is used.
|
|
if shared.Settings.MINIMAL_RUNTIME and (shared.Settings.target_environment_may_be('node') or shared.Settings.target_environment_may_be('shell')):
|
|
src = 'if(typeof Module==="undefined"){var Module={};}\n' + src
|
|
f.write(src)
|
|
save_intermediate('module_export_name_substitution')
|
|
|
|
|
|
def generate_traditional_runtime_html(target, options, js_target, target_basename,
|
|
wasm_target, memfile):
|
|
script = ScriptSource()
|
|
|
|
shell = read_and_preprocess(options.shell_path)
|
|
assert '{{{ SCRIPT }}}' in shell, 'HTML shell must contain {{{ SCRIPT }}} , see src/shell.html for an example'
|
|
base_js_target = os.path.basename(js_target)
|
|
|
|
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 {
|
|
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
|
|
|
|
if not shared.Settings.SINGLE_FILE:
|
|
if memfile and not shared.Settings.MINIMAL_RUNTIME:
|
|
# 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
|
|
|
|
if not shared.Settings.WASM_ASYNC_COMPILATION:
|
|
# We need to load the wasm file before anything else, it has to be synchronously ready TODO: optimize
|
|
script.un_src()
|
|
script.inline = '''
|
|
var wasmURL = '%s';
|
|
var wasmXHR = new XMLHttpRequest();
|
|
wasmXHR.open('GET', wasmURL, true);
|
|
wasmXHR.responseType = 'arraybuffer';
|
|
wasmXHR.onload = function() {
|
|
if (wasmXHR.status === 200 || wasmXHR.status === 0) {
|
|
Module.wasmBinary = wasmXHR.response;
|
|
} else {
|
|
var wasmURLBytes = tryParseAsDataURI(wasmURL);
|
|
if (wasmURLBytes) {
|
|
Module.wasmBinary = wasmURLBytes.buffer;
|
|
}
|
|
}
|
|
%s
|
|
};
|
|
wasmXHR.send(null);
|
|
''' % (shared.JS.get_subresource_location(wasm_target), script.inline)
|
|
|
|
if shared.Settings.WASM == 2:
|
|
# If target browser does not support WebAssembly, we need to load the .wasm.js file before the main .js file.
|
|
script.un_src()
|
|
script.inline = '''
|
|
function loadMainJs() {
|
|
%s
|
|
}
|
|
if (!window.WebAssembly || location.search.indexOf('_rwasm=0') > 0) {
|
|
// Current browser does not support WebAssembly, load the .wasm.js JavaScript fallback
|
|
// before the main JS runtime.
|
|
var wasm2js = document.createElement('script');
|
|
wasm2js.src = '%s';
|
|
wasm2js.onload = loadMainJs;
|
|
document.body.appendChild(wasm2js);
|
|
} else {
|
|
// Current browser supports Wasm, proceed with loading the main JS runtime.
|
|
loadMainJs();
|
|
}
|
|
''' % (script.inline, shared.JS.get_subresource_location(wasm_target) + '.js')
|
|
|
|
# when script.inline isn't empty, add required helper functions such as tryParseAsDataURI
|
|
if script.inline:
|
|
for filename in ('arrayUtils.js', 'base64Utils.js', 'URIUtils.js'):
|
|
content = read_and_preprocess(shared.path_from_root('src', filename))
|
|
script.inline = content + script.inline
|
|
|
|
script.inline = 'var ASSERTIONS = %s;\n%s' % (shared.Settings.ASSERTIONS, script.inline)
|
|
|
|
# inline script for SINGLE_FILE output
|
|
if shared.Settings.SINGLE_FILE:
|
|
js_contents = script.inline or ''
|
|
if script.src:
|
|
js_contents += open(js_target).read()
|
|
shared.try_delete(js_target)
|
|
script.src = None
|
|
script.inline = js_contents
|
|
|
|
html_contents = do_replace(shell, '{{{ SCRIPT }}}', script.replacement())
|
|
html_contents = tools.line_endings.convert_line_endings(html_contents, '\n', options.output_eol)
|
|
with open(target, 'wb') as f:
|
|
f.write(asbytes(html_contents))
|
|
|
|
|
|
def minify_html(filename):
|
|
if shared.Settings.DEBUG_LEVEL >= 2:
|
|
return
|
|
|
|
opts = []
|
|
# -g1 and greater retain whitespace and comments in source
|
|
if shared.Settings.DEBUG_LEVEL == 0:
|
|
opts += ['--collapse-whitespace',
|
|
'--collapse-inline-tag-whitespace',
|
|
'--remove-comments',
|
|
'--remove-tag-whitespace',
|
|
'--sort-attributes',
|
|
'--sort-class-name']
|
|
# -g2 and greater do not minify HTML at all
|
|
if shared.Settings.DEBUG_LEVEL <= 1:
|
|
opts += ['--decode-entities',
|
|
'--collapse-boolean-attributes',
|
|
'--remove-attribute-quotes',
|
|
'--remove-redundant-attributes',
|
|
'--remove-script-type-attributes',
|
|
'--remove-style-link-type-attributes',
|
|
'--use-short-doctype',
|
|
'--minify-css', 'true',
|
|
'--minify-js', 'true']
|
|
|
|
# html-minifier also has the following options, but they look unsafe for use:
|
|
# '--remove-optional-tags': removes e.g. <head></head> and <body></body> tags from the page.
|
|
# (Breaks at least browser.test_sdl2glshader)
|
|
# '--remove-empty-attributes': removes all attributes with whitespace-only values.
|
|
# (Breaks at least browser.test_asmfs_hello_file)
|
|
# '--remove-empty-elements': removes all elements with empty contents.
|
|
# (Breaks at least browser.test_asm_swapping)
|
|
|
|
logger.debug('minifying HTML file ' + filename)
|
|
size_before = os.path.getsize(filename)
|
|
start_time = time.time()
|
|
shared.check_call(shared.get_npm_cmd('html-minifier-terser') + [filename, '-o', filename] + opts, env=shared.env_with_node_in_path())
|
|
|
|
elapsed_time = time.time() - start_time
|
|
size_after = os.path.getsize(filename)
|
|
delta = size_after - size_before
|
|
logger.debug('HTML minification took {:.2f}'.format(elapsed_time) + ' seconds, and shrunk size of ' + filename + ' from ' + str(size_before) + ' to ' + str(size_after) + ' bytes, delta=' + str(delta) + ' ({:+.2f}%)'.format(delta * 100.0 / size_before))
|
|
|
|
|
|
def generate_html(target, options, js_target, target_basename,
|
|
wasm_target, memfile):
|
|
logger.debug('generating HTML')
|
|
|
|
if shared.Settings.EXPORT_NAME != 'Module' and \
|
|
not shared.Settings.MINIMAL_RUNTIME and \
|
|
options.shell_path == shared.path_from_root('src', 'shell.html'):
|
|
# the minimal runtime shell HTML is designed to support changing the export
|
|
# name, but the normal one does not support that currently
|
|
exit_with_error('Customizing EXPORT_NAME requires that the HTML be customized to use that name (see https://github.com/emscripten-core/emscripten/issues/10086)')
|
|
|
|
if shared.Settings.MINIMAL_RUNTIME:
|
|
generate_minimal_runtime_html(target, options, js_target, target_basename)
|
|
else:
|
|
generate_traditional_runtime_html(target, options, js_target, target_basename,
|
|
wasm_target, memfile)
|
|
|
|
if shared.Settings.MINIFY_HTML and (shared.Settings.OPT_LEVEL >= 1 or shared.Settings.SHRINK_LEVEL >= 1):
|
|
minify_html(target)
|
|
|
|
|
|
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:
|
|
safe_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()
|
|
proxy_client_src = do_replace(proxy_client_src, '{{{ filename }}}', proxy_worker_filename)
|
|
proxy_client_src = do_replace(proxy_client_src, '{{{ IDBStore.js }}}', idb_store_src)
|
|
return web_gl_client_src + '\n' + proxy_client_src
|
|
|
|
|
|
def process_libraries(libs, lib_dirs, temp_files):
|
|
libraries = []
|
|
consumed = []
|
|
suffixes = list(STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS)
|
|
|
|
# Find library files
|
|
for i, lib in libs:
|
|
logger.debug('looking for library "%s"', lib)
|
|
|
|
found = False
|
|
for prefix in LIB_PREFIXES:
|
|
for suff in suffixes:
|
|
name = prefix + lib + suff
|
|
for lib_dir in lib_dirs:
|
|
path = os.path.join(lib_dir, name)
|
|
if os.path.exists(path):
|
|
logger.debug('found library "%s" at %s', lib, path)
|
|
temp_files.append((i, path))
|
|
consumed.append(i)
|
|
found = True
|
|
break
|
|
if found:
|
|
break
|
|
if found:
|
|
break
|
|
|
|
if not found:
|
|
jslibs = building.map_to_js_libs(lib)
|
|
if jslibs is not None:
|
|
libraries += [(i, jslib) for jslib in jslibs]
|
|
consumed.append(i)
|
|
elif building.map_and_apply_to_settings(lib):
|
|
consumed.append(i)
|
|
|
|
shared.Settings.SYSTEM_JS_LIBRARIES += libraries
|
|
|
|
# At this point processing SYSTEM_JS_LIBRARIES is finished, no more items will be added to it.
|
|
# Sort the input list from (order, lib_name) pairs to a flat array in the right order.
|
|
shared.Settings.SYSTEM_JS_LIBRARIES.sort(key=lambda lib: lib[0])
|
|
shared.Settings.SYSTEM_JS_LIBRARIES = [lib[1] for lib in shared.Settings.SYSTEM_JS_LIBRARIES]
|
|
return consumed
|
|
|
|
|
|
class ScriptSource(object):
|
|
def __init__(self):
|
|
self.src = None # if set, we have a script to load with a src attribute
|
|
self.inline = None # if set, we have the contents of a script to write inline in a script
|
|
|
|
def un_src(self):
|
|
"""Use this if you want to modify the script and need it to be inline."""
|
|
if self.src is None:
|
|
return
|
|
self.inline = '''
|
|
var script = document.createElement('script');
|
|
script.src = "%s";
|
|
document.body.appendChild(script);
|
|
''' % self.src
|
|
self.src = None
|
|
|
|
def replacement(self):
|
|
"""Returns the script tag to replace the {{{ SCRIPT }}} tag in the target"""
|
|
assert (self.src or self.inline) and not (self.src and self.inline)
|
|
if self.src:
|
|
return '<script async type="text/javascript" src="%s"></script>' % quote(self.src)
|
|
else:
|
|
return '<script>\n%s\n</script>' % self.inline
|
|
|
|
|
|
def is_valid_abspath(options, path_name):
|
|
# Any path that is underneath the emscripten repository root must be ok.
|
|
if shared.path_from_root().replace('\\', '/') in path_name.replace('\\', '/'):
|
|
return True
|
|
|
|
def in_directory(root, child):
|
|
# make both path absolute
|
|
root = os.path.realpath(root)
|
|
child = os.path.realpath(child)
|
|
|
|
# return true, if the common prefix of both is equal to directory
|
|
# e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
|
|
return os.path.commonprefix([root, child]) == root
|
|
|
|
for valid_abspath in options.valid_abspaths:
|
|
if in_directory(valid_abspath, path_name):
|
|
return True
|
|
return False
|
|
|
|
|
|
def parse_value(text):
|
|
if not text:
|
|
return text
|
|
|
|
# Note that using response files can introduce whitespace, if the file
|
|
# has a newline at the end. For that reason, we rstrip() in relevant
|
|
# places here.
|
|
def parse_string_value(text):
|
|
first = text[0]
|
|
if first == "'" or first == '"':
|
|
text = text.rstrip()
|
|
assert text[-1] == text[0] and len(text) > 1, 'unclosed opened quoted string. expected final character to be "%s" and length to be greater than 1 in "%s"' % (text[0], text)
|
|
return text[1:-1]
|
|
return text
|
|
|
|
def parse_string_list_members(text):
|
|
sep = ','
|
|
values = text.split(sep)
|
|
result = []
|
|
index = 0
|
|
while True:
|
|
current = values[index].lstrip() # Cannot safely rstrip for cases like: "HERE-> ,"
|
|
if not len(current):
|
|
exit_with_error('string array should not contain an empty value')
|
|
first = current[0]
|
|
if not(first == "'" or first == '"'):
|
|
result.append(current.rstrip())
|
|
else:
|
|
start = index
|
|
while True: # Continue until closing quote found
|
|
if index >= len(values):
|
|
exit_with_error("unclosed quoted string. expected final character to be '%s' in '%s'" % (first, values[start]))
|
|
new = values[index].rstrip()
|
|
if new and new[-1] == first:
|
|
if start == index:
|
|
result.append(current.rstrip()[1:-1])
|
|
else:
|
|
result.append((current + sep + new)[1:-1])
|
|
break
|
|
else:
|
|
current += sep + values[index]
|
|
index += 1
|
|
|
|
index += 1
|
|
if index >= len(values):
|
|
break
|
|
return result
|
|
|
|
def parse_string_list(text):
|
|
text = text.rstrip()
|
|
if text[-1] != ']':
|
|
exit_with_error('unclosed opened string list. expected final character to be "]" in "%s"' % (text))
|
|
inner = text[1:-1]
|
|
if inner.strip() == "":
|
|
return []
|
|
return parse_string_list_members(inner)
|
|
|
|
if text[0] == '[':
|
|
# if json parsing fails, we fall back to our own parser, which can handle a few
|
|
# simpler syntaxes
|
|
try:
|
|
return json.loads(text)
|
|
except ValueError:
|
|
return parse_string_list(text)
|
|
|
|
try:
|
|
return int(text)
|
|
except ValueError:
|
|
return parse_string_value(text)
|
|
|
|
|
|
def validate_arg_level(level_string, max_level, err_msg, clamp=False):
|
|
try:
|
|
level = int(level_string)
|
|
except ValueError:
|
|
raise Exception(err_msg)
|
|
if clamp:
|
|
if level > max_level:
|
|
logger.warning("optimization level '-O" + level_string + "' is not supported; using '-O" + str(max_level) + "' instead")
|
|
level = max_level
|
|
if not 0 <= level <= max_level:
|
|
raise Exception(err_msg)
|
|
return level
|
|
|
|
|
|
def is_int(s):
|
|
try:
|
|
int(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def main(args):
|
|
start_time = time.time()
|
|
ret = run(args)
|
|
logger.debug('total time: %.2f seconds', (time.time() - start_time))
|
|
return ret
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
sys.exit(main(sys.argv))
|
|
except KeyboardInterrupt:
|
|
logger.warning('KeyboardInterrupt')
|
|
sys.exit(1)
|