import base64
import json
import logging
import os
import re
import shlex
import shutil
import stat
from subprocess import PIPE
from urllib.parse import quote
from . import (
building,
cache,
config,
diagnostics,
emscripten,
extract_metadata,
feature_matrix,
js_manipulation,
ports,
shared,
system_libs,
utils,
webassembly,
)
from .cmdline import OFormat
from .feature_matrix import Feature
from .minimal_runtime_shell import generate_minimal_runtime_html
from .settings import (
DEPRECATED_SETTINGS,
EXPERIMENTAL_SETTINGS,
INCOMPATIBLE_SETTINGS,
JS_ONLY_SETTINGS,
default_setting,
settings,
user_settings,
)
from .shared import DEBUG, DYLIB_EXTENSIONS, do_replace, in_temp
from .toolchain_profiler import ToolchainProfiler
from .utils import (
WINDOWS,
delete_file,
exit_with_error,
get_file_suffix,
read_file,
safe_copy,
unsuffixed,
unsuffixed_basename,
write_file,
)
logger = logging.getLogger('link')
DEFAULT_SHELL_HTML = utils.path_from_root('html/shell.html')
DEFAULT_ASYNCIFY_IMPORTS = ['__asyncjs__*']
DEFAULT_ASYNCIFY_EXPORTS = [
'main',
'__main_argc_argv',
]
VALID_ENVIRONMENTS = {'web', 'webview', 'worker', 'node', 'shell', 'worklet'}
EXECUTABLE_EXTENSIONS = ['.wasm', '.html', '.js', '.mjs', '.out', '']
SUPPORTED_LINKER_FLAGS = (
'--start-group', '--end-group',
'-(', '-)',
'--whole-archive', '--no-whole-archive',
'-whole-archive', '-no-whole-archive',
'-rpath',
)
UNSUPPORTED_LLD_FLAGS = {
'-bind_at_load': False,
'-allow-shlib-undefined': False,
'-rpath-link': True,
'-version-script': True,
'-install_name': True,
}
UBSAN_SANITIZERS = {
'alignment',
'bool',
'builtin',
'bounds',
'enum',
'float-cast-overflow',
'float-divide-by-zero',
'function',
'implicit-unsigned-integer-truncation',
'implicit-signed-integer-truncation',
'implicit-integer-sign-change',
'integer-divide-by-zero',
'nonnull-attribute',
'null',
'nullability-arg',
'nullability-assign',
'nullability-return',
'object-size',
'pointer-overflow',
'return',
'returns-nonnull-attribute',
'shift',
'signed-integer-overflow',
'unreachable',
'unsigned-integer-overflow',
'vla-bound',
'vptr',
'undefined',
'undefined-trap',
'implicit-integer-truncation',
'implicit-integer-arithmetic-value-change',
'implicit-conversion',
'integer',
'nullability',
}
final_js = None
def save_intermediate(name, suffix='js'):
if not DEBUG:
return
if not final_js:
logger.debug(f'(not saving intermediate {name} because not generating JS)')
return
building.save_intermediate(final_js, f'{name}.{suffix}')
def save_intermediate_with_wasm(name, wasm_binary):
if not DEBUG:
return
save_intermediate(name)
building.save_intermediate(wasm_binary, name + '.wasm')
def base64_encode(filename):
data = utils.read_binary(filename)
b64 = base64.b64encode(data)
return b64.decode('ascii')
def align_to_wasm_page_boundary(address):
page_size = webassembly.WASM_PAGE_SIZE
return ((address + (page_size - 1)) // page_size) * page_size
def will_metadce():
if settings.ASSERTIONS:
return False
return settings.OPT_LEVEL >= 3 or settings.SHRINK_LEVEL >= 1
def setup_environment_settings():
maybe_web_worker = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT
if settings.SHARED_MEMORY and settings.ENVIRONMENT:
settings.ENVIRONMENT.append('worker')
if settings.AUDIO_WORKLET:
settings.ENVIRONMENT.append('worklet')
if any(x for x in settings.ENVIRONMENT if x not in VALID_ENVIRONMENTS):
exit_with_error(f'Invalid environment specified in "ENVIRONMENT": {settings.ENVIRONMENT}. Should be one of: {",".join(VALID_ENVIRONMENTS)}')
settings.ENVIRONMENT_MAY_BE_WEB = not settings.ENVIRONMENT or 'web' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_WEBVIEW = not settings.ENVIRONMENT or 'webview' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_NODE = not settings.ENVIRONMENT or 'node' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_SHELL = not settings.ENVIRONMENT or 'shell' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_WORKER = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT
settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET = not settings.ENVIRONMENT or 'worklet' in settings.ENVIRONMENT
if not settings.ENVIRONMENT_MAY_BE_NODE:
if 'MIN_NODE_VERSION' in user_settings and settings.MIN_NODE_VERSION != feature_matrix.UNSUPPORTED:
diagnostics.warning('unused-command-line-argument', 'ignoring MIN_NODE_VERSION because `node` environment is not enabled')
settings.MIN_NODE_VERSION = feature_matrix.UNSUPPORTED
if not (settings.ENVIRONMENT_MAY_BE_WEB or maybe_web_worker or settings.ENVIRONMENT_MAY_BE_WEBVIEW):
for browser in ('FIREFOX', 'SAFARI', 'CHROME'):
key = f'MIN_{browser}_VERSION'
if key in user_settings and settings[key] != feature_matrix.UNSUPPORTED:
diagnostics.warning('unused-command-line-argument', 'ignoring %s because `web`, `worker` and `webview` environments are not enabled', key)
settings[key] = feature_matrix.UNSUPPORTED
def generate_js_sym_info():
"""Runs the js compiler to generate a list of all symbols available in the JS
libraries. This must be done separately for each linker invocation since the
list of symbols depends on what settings are used.
TODO(sbc): Find a way to optimize this. Potentially we could add a super-set
mode of the js compiler that would generate a list of all possible symbols
that could be checked in.
"""
output = emscripten.compile_javascript(symbols_only=True)
return json.loads(output)
@ToolchainProfiler.profile_block('JS symbol generation')
def get_js_sym_info():
if DEBUG or settings.BOOTSTRAPPING_STRUCT_INFO or config.FROZEN_CACHE:
return generate_js_sym_info()
content_hash = emscripten.generate_js_compiler_input_hash(symbols_only=True)
def generate_json():
library_syms = generate_js_sym_info()
return json.dumps(library_syms, separators=(',', ':'), indent=2)
file_content = emscripten.get_cached_file('symbol_lists', f'{content_hash}.json', generate_json, cache_limit=500)
return json.loads(file_content)
def filter_link_flags(flags, using_lld):
def is_supported(f):
if using_lld:
for flag, takes_arg in UNSUPPORTED_LLD_FLAGS.items():
if f.startswith((flag, '-' + flag)):
diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f)
skip_next = takes_arg and '=' not in f
return False, skip_next
return True, False
else:
if not f.startswith('-') or f in SUPPORTED_LINKER_FLAGS:
return True, False
if f.startswith(('-l', '-L')):
return False, False
diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f)
return False, False
results = []
skip_next = False
for f in flags:
if skip_next:
skip_next = False
continue
keep, skip_next = is_supported(f)
if keep:
results.append(f)
return results
def fix_windows_newlines(text):
if WINDOWS:
text = text.replace('\r\n', '\n')
return text
def read_js_files(files):
contents = []
for f in files:
content = read_file(f)
if content.startswith('#preprocess\n'):
contents.append(building.read_and_preprocess(f, expand_macros=True))
else:
contents.append(content)
contents = '\n'.join(contents)
return fix_windows_newlines(contents)
def should_run_binaryen_optimizer():
return settings.OPT_LEVEL >= 2
def get_binaryen_lowering_passes():
passes = []
features = [
[Feature.SIGN_EXT, '--signext-lowering', ['--enable-sign-ext']],
[Feature.NON_TRAPPING_FPTOINT, '--llvm-nontrapping-fptoint-lowering', ['--enable-nontrapping-float-to-int']],
[Feature.BULK_MEMORY, '--llvm-memory-copy-fill-lowering', ['--enable-bulk-memory', '--enable-bulk-memory-opt']],
]
for feature, lowering_flag, feature_flags in features:
if not feature_matrix.caniuse(feature):
logger.debug(f'lowering {feature.name} feature due to incompatible target browser engines')
for f in feature_flags:
if f in building.binaryen_features:
building.binaryen_features.remove(f)
passes.append(lowering_flag)
return passes
def get_binaryen_passes(options):
passes = get_binaryen_lowering_passes()
optimizing = should_run_binaryen_optimizer()
if settings.SAFE_HEAP:
passes += ['--safe-heap']
if optimizing:
passes += ['--strip-target-features']
passes += ['--post-emscripten']
if settings.SIDE_MODULE:
passes += ['--pass-arg=post-emscripten-side-module']
passes += [building.opt_level_to_str(settings.OPT_LEVEL, settings.SHRINK_LEVEL)]
if settings.GLOBAL_BASE >= 1024 and not settings.STACK_FIRST:
passes += ['--low-memory-unused']
if options.fast_math:
passes += ['--fast-math']
if settings.AUTODEBUG:
passes += ['--instrument-locals']
passes += ['--log-execution']
passes += ['--instrument-memory']
if settings.LEGALIZE_JS_FFI:
passes += ['--legalize-js-interface']
passes += building.js_legalization_pass_flags()
if settings.EMULATE_FUNCTION_POINTER_CASTS:
passes += ['--fpcast-emu']
if settings.ASYNCIFY == 1:
passes += ['--asyncify']
if settings.MAIN_MODULE:
passes += ['--pass-arg=asyncify-export-globals']
elif settings.RELOCATABLE:
passes += ['--pass-arg=asyncify-import-globals']
if settings.ASSERTIONS:
passes += ['--pass-arg=asyncify-asserts']
if settings.ASYNCIFY_ADVISE:
passes += ['--pass-arg=asyncify-verbose']
if settings.ASYNCIFY_IGNORE_INDIRECT:
passes += ['--pass-arg=asyncify-ignore-indirect']
if settings.ASYNCIFY_PROPAGATE_ADD:
passes += ['--pass-arg=asyncify-propagate-addlist']
passes += ['--pass-arg=asyncify-imports@%s' % ','.join(settings.ASYNCIFY_IMPORTS)]
def check_human_readable_list(items):
for item in items:
if item.count('(') != item.count(')'):
logger.warning('emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):')
logger.warning(' ' + item)
logger.warning('This may indicate improper escaping that led to splitting inside your names.')
logger.warning('Try using a response file. e.g: [email protected]. The format is a simple')
logger.warning('text file, one line per function.')
break
if settings.ASYNCIFY_REMOVE:
check_human_readable_list(settings.ASYNCIFY_REMOVE)
passes += ['--pass-arg=asyncify-removelist@%s' % ','.join(settings.ASYNCIFY_REMOVE)]
if settings.ASYNCIFY_ADD:
check_human_readable_list(settings.ASYNCIFY_ADD)
passes += ['--pass-arg=asyncify-addlist@%s' % ','.join(settings.ASYNCIFY_ADD)]
if settings.ASYNCIFY_ONLY:
check_human_readable_list(settings.ASYNCIFY_ONLY)
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(settings.ASYNCIFY_ONLY)]
if settings.MEMORY64 == 2:
passes += ['--memory64-lowering', '--table64-lowering']
if settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
passes += ['--ignore-implicit-traps']
if optimizing and not settings.SIDE_MODULE:
passes += ['--zero-filled-memory']
if optimizing:
passes += ['--pass-arg=directize-initial-contents-immutable']
if settings.BINARYEN_EXTRA_PASSES:
extras = settings.BINARYEN_EXTRA_PASSES.split(',')
passes += [('--' + p) if p[0] != '-' else p for p in extras if p]
if will_metadce():
passes += ['--no-stack-ir']
return passes
def make_js_executable(script):
src = read_file(script)
if settings.EXECUTABLE == 1:
settings.EXECUTABLE = '/usr/bin/env node'
logger.debug(f'adding `#!` to JavaScript file: {settings.EXECUTABLE}')
with open(script, 'w') as f:
f.write(f'#!{settings.EXECUTABLE}\n')
f.write(src)
try:
os.chmod(script, stat.S_IMODE(os.stat(script).st_mode) | stat.S_IXUSR)
except OSError:
pass
def do_split_module(wasm_file, options):
os.replace(wasm_file, wasm_file + '.orig')
args = ['--instrument']
if options.requested_debug:
args += ['-g']
building.run_binaryen_command('wasm-split', wasm_file + '.orig', outfile=wasm_file, args=args)
def get_worker_js_suffix():
return '.worker.mjs' if settings.EXPORT_ES6 else '.worker.js'
def setup_pthreads():
if settings.MAIN_MODULE or settings.RELOCATABLE:
diagnostics.warning('experimental', 'dynamic linking + pthreads is experimental')
if settings.ALLOW_MEMORY_GROWTH and not settings.GROWABLE_ARRAYBUFFERS:
diagnostics.warning('pthreads-mem-growth', '-pthread + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271')
default_setting('DEFAULT_PTHREAD_STACK_SIZE', settings.STACK_SIZE)
settings.REQUIRED_EXPORTS += [
'_emscripten_thread_free_data',
'_emscripten_thread_crashed',
]
if settings.MAIN_MODULE:
settings.REQUIRED_EXPORTS += [
'_emscripten_dlsync_self',
'_emscripten_dlsync_self_async',
'_emscripten_proxy_dlsync',
'_emscripten_proxy_dlsync_async',
'__dl_seterr',
]
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$PThread',
'$establishStackSpace',
'$invokeEntryPoint',
]
if settings.MINIMAL_RUNTIME:
building.user_requested_exports.add('exit')
def set_initial_memory():
user_specified_initial_heap = 'INITIAL_HEAP' in user_settings
if settings.IMPORTED_MEMORY:
if user_specified_initial_heap:
exit_with_error('INITIAL_HEAP is currently not compatible with IMPORTED_MEMORY (which is enabled indirectly via SHARED_MEMORY, RELOCATABLE)')
settings.INITIAL_HEAP = -1
if not user_specified_initial_heap:
user_specified_initial = settings.INITIAL_MEMORY != -1
user_specified_maximum = 'MAXIMUM_MEMORY' in user_settings or 'WASM_MEM_MAX' in user_settings or 'BINARYEN_MEM_MAX' in user_settings
if user_specified_initial or user_specified_maximum:
settings.INITIAL_HEAP = -1
if settings.INITIAL_HEAP == -1 and settings.INITIAL_MEMORY == -1:
default_setting('INITIAL_MEMORY', 16 * 1024 * 1024)
def check_memory_setting(setting):
if settings[setting] % webassembly.WASM_PAGE_SIZE != 0:
exit_with_error(f'{setting} must be a multiple of WebAssembly page size (64KiB), was {settings[setting]}')
if settings[setting] >= 2**53:
exit_with_error(f'{setting} must be smaller than 2^53 bytes due to JS Numbers (doubles) being used to hold pointer addresses in JS side')
if settings.INITIAL_HEAP != -1:
check_memory_setting('INITIAL_HEAP')
if settings.INITIAL_MEMORY != -1:
check_memory_setting('INITIAL_MEMORY')
if settings.INITIAL_MEMORY < settings.STACK_SIZE:
exit_with_error(f'INITIAL_MEMORY must be larger than STACK_SIZE, was {settings.INITIAL_MEMORY} (STACK_SIZE={settings.STACK_SIZE})')
check_memory_setting('MAXIMUM_MEMORY')
if settings.MEMORY_GROWTH_LINEAR_STEP != -1:
check_memory_setting('MEMORY_GROWTH_LINEAR_STEP')
def set_max_memory():
initial_memory_known = settings.INITIAL_MEMORY != -1
if not settings.ALLOW_MEMORY_GROWTH:
if 'MAXIMUM_MEMORY' in user_settings:
diagnostics.warning('unused-command-line-argument', 'MAXIMUM_MEMORY is only meaningful with ALLOW_MEMORY_GROWTH')
if initial_memory_known:
settings.MAXIMUM_MEMORY = settings.INITIAL_MEMORY
if 'MAXIMUM_MEMORY' not in user_settings:
if settings.ALLOW_MEMORY_GROWTH:
if any([settings.INITIAL_HEAP != -1 and settings.INITIAL_HEAP >= 2 * 1024 * 1024 * 1024,
initial_memory_known and settings.INITIAL_MEMORY > 2 * 1024 * 1024 * 1024]):
settings.MAXIMUM_MEMORY = 4 * 1024 * 1024 * 1024
if initial_memory_known and settings.INITIAL_MEMORY > settings.MAXIMUM_MEMORY:
settings.MAXIMUM_MEMORY = settings.INITIAL_MEMORY
if initial_memory_known and settings.MAXIMUM_MEMORY < settings.INITIAL_MEMORY:
exit_with_error('MAXIMUM_MEMORY cannot be less than INITIAL_MEMORY')
def inc_initial_memory(delta):
if settings.INITIAL_HEAP != -1:
settings.INITIAL_HEAP += delta
if settings.INITIAL_MEMORY != -1:
settings.INITIAL_MEMORY += delta
def check_browser_versions():
min_version_settings = {
'MIN_FIREFOX_VERSION': feature_matrix.OLDEST_SUPPORTED_FIREFOX,
'MIN_CHROME_VERSION': feature_matrix.OLDEST_SUPPORTED_CHROME,
'MIN_SAFARI_VERSION': feature_matrix.OLDEST_SUPPORTED_SAFARI,
'MIN_NODE_VERSION': feature_matrix.OLDEST_SUPPORTED_NODE,
}
if settings.LEGACY_VM_SUPPORT:
for key in min_version_settings:
default_setting(key, 0)
for key, oldest in min_version_settings.items():
if settings[key] != 0 and settings[key] < oldest:
exit_with_error(f'{key} older than {oldest} is not supported')
def add_system_js_lib(lib):
lib = utils.path_from_root('src/lib', lib)
assert os.path.exists(lib)
settings.JS_LIBRARIES.append(lib)
def check_settings():
for s, reason in DEPRECATED_SETTINGS.items():
if s in user_settings:
diagnostics.warning('deprecated', f'{s} is deprecated ({reason}). Please open a bug if you have a continuing need for this setting')
for name, msg in EXPERIMENTAL_SETTINGS.items():
if getattr(settings, name):
diagnostics.warning('experimental', msg)
for a, b, reason in INCOMPATIBLE_SETTINGS:
invert_b = b.startswith('NO_')
if invert_b:
b = b[3:]
b_val = getattr(settings, b)
if invert_b:
b_val = not b_val
if getattr(settings, a) and b_val:
msg = f'{a} is not compatible with {b}'
if invert_b:
msg += '=0'
if reason:
msg += f' ({reason})'
exit_with_error(msg)
@ToolchainProfiler.profile()
def setup_sanitizers(options):
assert(options.sanitize)
if settings.WASM_WORKERS:
exit_with_error('WASM_WORKERS is not currently compatible with `-fsanitize` tools')
if 'leak' in options.sanitize or 'address' in options.sanitize:
settings.REQUIRED_EXPORTS += ['__lsan_disable', '__lsan_enable']
if ('leak' in options.sanitize or 'address' in options.sanitize) and not settings.ALLOW_MEMORY_GROWTH:
inc_initial_memory(50 * 1024 * 1024)
if settings.PTHREADS:
inc_initial_memory(50 * 1024 * 1024)
if options.sanitize & UBSAN_SANITIZERS:
if options.sanitize_minimal_runtime:
settings.UBSAN_RUNTIME = 1
else:
settings.UBSAN_RUNTIME = 2
if 'leak' in options.sanitize:
settings.USE_LSAN = 1
default_setting('EXIT_RUNTIME', 1)
if 'address' in options.sanitize:
settings.USE_ASAN = 1
default_setting('EXIT_RUNTIME', 1)
if not settings.UBSAN_RUNTIME:
settings.UBSAN_RUNTIME = 2
settings.REQUIRED_EXPORTS += emscripten.ASAN_C_HELPERS
if settings.ASYNCIFY and not settings.ASYNCIFY_ONLY:
settings.ASYNCIFY_REMOVE.append("__asan_*")
if settings.ASAN_SHADOW_SIZE != -1:
diagnostics.warning('emcc', 'ASAN_SHADOW_SIZE is ignored and will be removed in a future release')
if 'GLOBAL_BASE' in user_settings:
exit_with_error("ASan does not support custom GLOBAL_BASE")
user_mem = settings.MAXIMUM_MEMORY
if not settings.ALLOW_MEMORY_GROWTH and settings.INITIAL_MEMORY != -1:
user_mem = settings.INITIAL_MEMORY
total_mem = user_mem * 8 / 7
total_mem = int(align_to_wasm_page_boundary(total_mem))
shadow_size = total_mem // 8
settings.GLOBAL_BASE = shadow_size
if settings.INITIAL_MEMORY != -1:
if settings.ALLOW_MEMORY_GROWTH:
settings.INITIAL_MEMORY += align_to_wasm_page_boundary(shadow_size)
else:
settings.INITIAL_MEMORY = total_mem
if settings.SAFE_HEAP:
exit_with_error('ASan does not work with SAFE_HEAP')
if settings.MEMORY64:
exit_with_error('MEMORY64 does not yet work with ASAN')
if settings.GENERATE_SOURCE_MAP:
settings.LOAD_SOURCE_MAP = 1
def get_dylibs(options, linker_args):
"""Find all the Wasm dynamic libraries specified on the command line,
either via `-lfoo` or via `libfoo.so` directly."""
dylibs = []
for arg in linker_args:
if arg.startswith('-l'):
for ext in DYLIB_EXTENSIONS:
path = find_library('lib' + arg[2:] + ext, options.lib_dirs)
if path and building.is_wasm_dylib(path):
dylibs.append(path)
elif building.is_wasm_dylib(arg):
dylibs.append(arg)
return dylibs
@ToolchainProfiler.profile_block('linker_setup')
def phase_linker_setup(options, linker_args):
"""Future modifications should consider refactoring to reduce complexity.
* The McCabe cyclomatiic complexity is currently 244 vs 10 recommended.
* There are currently 252 branches vs 12 recommended.
* There are currently 563 statements vs 50 recommended.
To revalidate these numbers, run `ruff check --select=C901,PLR091`.
"""
setup_environment_settings()
apply_library_settings(linker_args)
if settings.SIDE_MODULE or settings.MAIN_MODULE:
default_setting('FAKE_DYLIBS', 0)
if options.shared and not settings.FAKE_DYLIBS:
default_setting('SIDE_MODULE', 1)
default_setting('RELOCATABLE', 1)
if not settings.FAKE_DYLIBS:
options.dylibs = get_dylibs(options, linker_args)
if not settings.MAIN_MODULE and not settings.SIDE_MODULE and options.dylibs:
default_setting('MAIN_MODULE', 2)
linker_args += calc_extra_ldflags(options)
if not shared.SKIP_SUBPROCS:
shared.check_llvm_version()
autoconf = os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in options.input_files or 'conftest.cpp' in options.input_files
if autoconf:
settings.EXIT_RUNTIME = 1
settings.NODERAWFS = 1
settings.EXECUTABLE = config.NODE_JS[0]
if settings.STRICT:
exit_with_error('autoconfiguring is not compatible with STRICT')
if settings.OPT_LEVEL >= 1:
default_setting('ASSERTIONS', 0)
if options.emrun:
options.pre_js.append(utils.path_from_root('src/emrun_prejs.js'))
options.post_js.append(utils.path_from_root('src/emrun_postjs.js'))
if settings.MINIMAL_RUNTIME:
exit_with_error('--emrun is not compatible with MINIMAL_RUNTIME')
if user_settings.get('EXIT_RUNTIME') == '0':
exit_with_error('--emrun is not compatible with EXIT_RUNTIME=0')
settings.EXIT_RUNTIME = 1
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnExit']
if options.cpu_profiler:
options.post_js.append(utils.path_from_root('src/cpuprofiler.js'))
default_setting('RUNTIME_DEBUG', int(settings.LIBRARY_DEBUG or
settings.GL_DEBUG or
settings.DYLINK_DEBUG or
settings.OPENAL_DEBUG or
settings.SYSCALL_DEBUG or
settings.WEBSOCKET_DEBUG or
settings.SOCKET_DEBUG or
settings.FETCH_DEBUG or
settings.EXCEPTION_DEBUG or
settings.PTHREADS_DEBUG or
settings.ASYNCIFY_DEBUG))
if options.memory_profiler:
settings.MEMORYPROFILER = 1
if settings.PTHREADS_PROFILING:
options.post_js.append(utils.path_from_root('src/threadprofiler.js'))
settings.REQUIRED_EXPORTS.append('emscripten_main_runtime_thread_id')
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnInit', '$addOnExit']
if options.js_transform and settings.GENERATE_SOURCE_MAP:
logger.warning('disabling source maps because a js transform is being done')
settings.GENERATE_SOURCE_MAP = 0
if options.output_file:
target = options.output_file
dirname = os.path.dirname(target)
if dirname and not os.path.isdir(dirname):
exit_with_error("specified output file (%s) is in a directory that does not exist" % target)
elif autoconf:
target = 'a.out'
elif settings.SIDE_MODULE:
target = 'a.out.wasm'
else:
target = 'a.out.js'
final_suffix = get_file_suffix(target)
if settings.WASM_ESM_INTEGRATION or settings.SOURCE_PHASE_IMPORTS or settings.MODULARIZE == 'instance':
default_setting('EXPORT_ES6', 1)
if not options.oformat and (options.relocatable or (options.shared and settings.FAKE_DYLIBS and not settings.SIDE_MODULE)):
if final_suffix in EXECUTABLE_EXTENSIONS:
diagnostics.warning('emcc', '-shared/-r used with executable output suffix. This behaviour is deprecated. Please remove -shared/-r to build an executable or avoid the executable suffix (%s) when building object files.' % final_suffix)
else:
if options.shared and 'FAKE_DYLIBS' not in user_settings:
diagnostics.warning('emcc', 'linking a library with `-shared` will emit a static object file (FAKE_DYLIBS defaults to true). If you want to build a runtime shared library use the SIDE_MODULE or FAKE_DYLIBS=0.')
options.oformat = OFormat.OBJECT
if not options.oformat:
if settings.SIDE_MODULE or final_suffix == '.wasm':
options.oformat = OFormat.WASM
elif final_suffix == '.html':
options.oformat = OFormat.HTML
elif final_suffix == '.mjs' or settings.EXPORT_ES6:
options.oformat = OFormat.MJS
else:
options.oformat = OFormat.JS
if options.oformat in (OFormat.WASM, OFormat.OBJECT):
for s in JS_ONLY_SETTINGS:
if s in user_settings:
diagnostics.warning('unused-command-line-argument', f'{s} is only valid when generating JavaScript output')
if settings.ENVIRONMENT_MAY_BE_NODE and options.oformat == OFormat.JS and final_suffix in ('', '.out'):
default_setting('EXECUTABLE', 1)
if settings.EXECUTABLE and not settings.ENVIRONMENT_MAY_BE_NODE:
exit_with_error('EXECUTABLE requires `node` in ENVRIONMENT')
if options.oformat == OFormat.MJS:
default_setting('EXPORT_ES6', 1)
settings.OUTPUT_FORMAT = options.oformat.name
if settings.SUPPORT_BIG_ENDIAN and settings.WASM2JS:
exit_with_error('WASM2JS is currently not compatible with SUPPORT_BIG_ENDIAN')
if settings.WASM_ESM_INTEGRATION:
default_setting('MODULARIZE', 'instance')
if not settings.EXPORT_ES6:
exit_with_error('WASM_ESM_INTEGRATION requires EXPORT_ES6')
if settings.MODULARIZE != 'instance':
exit_with_error('WASM_ESM_INTEGRATION requires MODULARIZE=instance')
if settings.RELOCATABLE:
exit_with_error('WASM_ESM_INTEGRATION is not compatible with dynamic linking')
if settings.ASYNCIFY:
exit_with_error('WASM_ESM_INTEGRATION is not compatible with -sASYNCIFY')
if settings.WASM_WORKERS:
exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS')
if not settings.WASM_ASYNC_COMPILATION:
exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM_ASYNC_COMPILATION=0')
if not settings.WASM:
exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM2JS')
if settings.ABORT_ON_WASM_EXCEPTIONS:
exit_with_error('WASM_ESM_INTEGRATION is not compatible with ABORT_ON_WASM_EXCEPTIONS')
if settings.MODULARIZE and settings.MODULARIZE not in [1, 'instance']:
exit_with_error(f'Invalid setting "{settings.MODULARIZE}" for MODULARIZE.')
def limit_incoming_module_api():
if options.oformat == OFormat.HTML and options.shell_html == DEFAULT_SHELL_HTML:
default_setting('INCOMING_MODULE_JS_API', 'canvas,monitorRunDependencies,onAbort,onExit,print,setStatus'.split(','))
else:
default_setting('INCOMING_MODULE_JS_API', [])
if settings.ASYNCIFY == 1:
settings.DYNCALLS = 1
if settings.MODULARIZE == 'instance':
diagnostics.warning('experimental', 'MODULARIZE=instance is still experimental. Many features may not work or will change.')
if not settings.EXPORT_ES6:
exit_with_error('MODULARIZE=instance requires EXPORT_ES6')
if settings.MINIMAL_RUNTIME:
exit_with_error('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
if options.use_preload_plugins or len(options.preload_files):
exit_with_error('MODULARIZE=instance is not compatible with --embed-file/--preload-file')
if settings.MINIMAL_RUNTIME and len(options.preload_files):
exit_with_error('MINIMAL_RUNTIME is not compatible with --preload-file')
if options.oformat in (OFormat.WASM, OFormat.BARE):
if options.emit_tsd:
exit_with_error('Wasm only output is not compatible with --emit-tsd')
wasm_target = target
elif settings.SINGLE_FILE or settings.WASM == 0:
wasm_target = in_temp(utils.replace_suffix(target, '.wasm'))
else:
wasm_target = get_secondary_target(target, '.wasm')
if settings.SAFE_HEAP not in [0, 1, 2]:
exit_with_error('SAFE_HEAP must be 0, 1 or 2')
if not settings.WASM:
settings.WASM = 1
settings.WASM2JS = 1
if settings.WASM == 2:
settings.WASM2JS = 1
if settings.WASM2JS:
if user_settings.get('WASM_BIGINT') and settings.WASM_BIGINT:
exit_with_error('WASM_BIGINT=1 is not compatible with wasm2js')
settings.WASM_BIGINT = 0
feature_matrix.disable_feature(Feature.JS_BIGINT_INTEGRATION)
if options.oformat == OFormat.WASM and not settings.SIDE_MODULE:
settings.STANDALONE_WASM = 1
if settings.LZ4:
settings.EXPORTED_RUNTIME_METHODS += ['LZ4']
if settings.PURE_WASI:
settings.STANDALONE_WASM = 1
settings.WASM_BIGINT = 1
if not settings.WASM_EXCEPTIONS:
default_setting('SUPPORT_LONGJMP', 0)
if options.no_entry:
settings.EXPECT_MAIN = 0
elif settings.STANDALONE_WASM:
if '_main' in settings.EXPORTED_FUNCTIONS:
logger.debug('including `_main` in EXPORTED_FUNCTIONS is not necessary in standalone mode')
else:
if 'EXPORTED_FUNCTIONS' in user_settings:
if '_main' in settings.USER_EXPORTS:
settings.EXPORTED_FUNCTIONS.remove('_main')
settings.EXPORT_IF_DEFINED.append('main')
else:
settings.EXPECT_MAIN = 0
else:
settings.EXPORT_IF_DEFINED.append('main')
if settings.STANDALONE_WASM:
if 'EXIT_RUNTIME' in user_settings:
exit_with_error('explicitly setting EXIT_RUNTIME not compatible with STANDALONE_WASM. EXIT_RUNTIME will always be True for programs (with a main function) and False for reactors (not main function).')
settings.EXIT_RUNTIME = settings.EXPECT_MAIN
settings.IGNORE_MISSING_MAIN = 0
default_setting('LEGALIZE_JS_FFI', 0)
if 'MEMORY_GROWTH_LINEAR_STEP' in user_settings:
exit_with_error('MEMORY_GROWTH_LINEAR_STEP is not compatible with STANDALONE_WASM')
if 'MEMORY_GROWTH_GEOMETRIC_CAP' in user_settings:
exit_with_error('MEMORY_GROWTH_GEOMETRIC_CAP is not compatible with STANDALONE_WASM')
building.user_requested_exports.update(settings.EXPORTED_FUNCTIONS)
if '_main' in settings.EXPORTED_FUNCTIONS or 'main' in settings.EXPORT_IF_DEFINED:
settings.EXPORT_IF_DEFINED.append('__main_argc_argv')
elif settings.ASSERTIONS and not settings.STANDALONE_WASM and not options.no_entry:
settings.EXPORT_IF_DEFINED += ['main', '__main_argc_argv']
if settings.ASSERTIONS:
if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES:
exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled')
if settings.WASM_EXCEPTIONS or not settings.DISABLE_EXCEPTION_CATCHING:
settings.EXCEPTION_STACK_TRACES = 1
if not settings.PURE_WASI and not options.nostdlib and not options.nodefaultlibs:
default_setting('STACK_OVERFLOW_CHECK', max(settings.ASSERTIONS, settings.STACK_OVERFLOW_CHECK))
if user_settings.get('WARN_ON_UNDEFINED_SYMBOLS') == '0':
default_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0)
if settings.MINIMAL_RUNTIME:
default_setting('AUTO_JS_LIBRARIES', 0)
default_setting('EXPORT_KEEPALIVE', 0)
if settings.EXPORT_ES6 and not settings.MODULARIZE:
if 'MODULARIZE' in user_settings:
exit_with_error('EXPORT_ES6 requires MODULARIZE to be set')
settings.MODULARIZE = 1
if options.oformat == OFormat.HTML:
if not options.shell_html:
if settings.MINIMAL_RUNTIME:
options.shell_html = options.shell_html = utils.path_from_root('html/shell_minimal_runtime.html')
else:
options.shell_html = DEFAULT_SHELL_HTML
if options.shell_html == DEFAULT_SHELL_HTML:
settings.EXPORTED_RUNTIME_METHODS.append('requestFullscreen')
elif options.shell_html:
diagnostics.warning('unused-command-line-argument', '--shell-file ignored when not generating html output')
if settings.STRICT:
if not settings.MODULARIZE:
default_setting('STRICT_JS', 1)
default_setting('DEFAULT_TO_CXX', 0)
default_setting('IGNORE_MISSING_MAIN', 0)
default_setting('AUTO_NATIVE_LIBRARIES', 0)
if settings.MAIN_MODULE != 1:
default_setting('AUTO_JS_LIBRARIES', 0)
default_setting('ALLOW_UNIMPLEMENTED_SYSCALLS', 0)
limit_incoming_module_api()
for prop in settings.INCOMING_MODULE_JS_API:
if prop not in settings.ALL_INCOMING_MODULE_JS_API:
diagnostics.warning('unused-command-line-argument', f'invalid entry in INCOMING_MODULE_JS_API: {prop}')
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$wasmMemory')
if 'noExitRuntime' in settings.INCOMING_MODULE_JS_API:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$noExitRuntime')
if settings.SHRINK_LEVEL >= 2 and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET and \
not settings.ENVIRONMENT_MAY_BE_SHELL:
default_setting('TEXTDECODER', 2)
if os.environ.get('EMCC_AUTODEBUG'):
settings.AUTODEBUG = 1
if settings.JS_MATH and settings.MAIN_MODULE == 1:
exit_with_error('JS_MATH is not compatible with dynamic linking (MAIN_MODULE=1)')
if settings.WASM == 2 and settings.SINGLE_FILE:
exit_with_error('cannot have both WASM=2 and SINGLE_FILE enabled at the same time')
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and options.oformat != OFormat.HTML:
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output')
if settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION and not settings.MINIMAL_RUNTIME:
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION requires MINIMAL_RUNTIME')
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and not settings.MINIMAL_RUNTIME:
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION requires MINIMAL_RUNTIME')
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and options.oformat != OFormat.HTML:
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output')
if options.use_closure_compiler:
settings.USE_CLOSURE_COMPILER = 1
if 'CLOSURE_WARNINGS' in user_settings:
if settings.CLOSURE_WARNINGS not in ['quiet', 'warn', 'error']:
exit_with_error('invalid option -sCLOSURE_WARNINGS=%s specified! Allowed values are "quiet", "warn" or "error".' % settings.CLOSURE_WARNINGS)
closure_warnings = diagnostics.manager.warnings['closure']
if settings.CLOSURE_WARNINGS == 'error':
closure_warnings['error'] = True
closure_warnings['enabled'] = True
elif settings.CLOSURE_WARNINGS == 'warn':
closure_warnings['error'] = False
closure_warnings['enabled'] = True
elif settings.CLOSURE_WARNINGS == 'quiet':
closure_warnings['error'] = False
closure_warnings['enabled'] = False
if not settings.MINIMAL_RUNTIME:
if not settings.BOOTSTRAPPING_STRUCT_INFO:
if settings.DYNCALLS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$dynCall']
if settings.ASSERTIONS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$warnOnce']
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getValue', '$setValue']
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$ExitStatus']
if settings.LOAD_SOURCE_MAP or (settings.WASM_ASYNC_COMPILATION and not settings.MODULARIZE):
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addRunDependency', '$removeRunDependency']
if settings.ABORT_ON_WASM_EXCEPTIONS or settings.SPLIT_MODULE:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$wasmTable']
if settings.MAIN_MODULE:
assert not settings.SIDE_MODULE
if settings.MAIN_MODULE == 1:
settings.INCLUDE_FULL_LIBRARY = 1
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$loadDylibs']
if not settings.RELOCATABLE:
settings.REQUIRED_EXPORTS += ['__stack_pointer']
if settings.MAIN_MODULE == 1 or settings.SIDE_MODULE == 1:
settings.LINKABLE = 1
if settings.LINKABLE and settings.USER_EXPORTS:
diagnostics.warning('unused-command-line-argument', 'EXPORTED_FUNCTIONS is not valid with LINKABLE set (normally due to SIDE_MODULE=1/MAIN_MODULE=1) since all functions are exported this mode. To export only a subset use SIDE_MODULE=2/MAIN_MODULE=2')
if settings.MAIN_MODULE:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$getDylinkMetadata',
'$mergeLibSymbols',
]
if settings.PTHREADS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$registerTLSInit',
]
if settings.MAIN_MODULE or settings.RELOCATABLE:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$reportUndefinedSymbols',
'$relocateExports',
'$GOTHandler',
]
settings.ALLOW_TABLE_GROWTH = 1
if settings.DETERMINISTIC or \
settings.EMSCRIPTEN_TRACING or \
settings.SAFE_HEAP or \
settings.MEMORYPROFILER:
settings.REQUIRED_EXPORTS += ['sbrk']
if settings.MEMORYPROFILER:
settings.REQUIRED_EXPORTS += ['__heap_base',
'emscripten_stack_get_base',
'emscripten_stack_get_end',
'emscripten_stack_get_current']
settings.ASYNCIFY_ADD = unmangle_symbols_from_cmdline(settings.ASYNCIFY_ADD)
settings.ASYNCIFY_REMOVE = unmangle_symbols_from_cmdline(settings.ASYNCIFY_REMOVE)
settings.ASYNCIFY_ONLY = unmangle_symbols_from_cmdline(settings.ASYNCIFY_ONLY)
if settings.EMULATE_FUNCTION_POINTER_CASTS:
settings.DYNCALLS = 1
if options.oformat != OFormat.OBJECT and final_suffix in ('.o', '.bc', '.so', '.dylib') and not settings.SIDE_MODULE:
diagnostics.warning('emcc', 'object file output extension (%s) used for non-object output. If you meant to build an object file please use `-c, `-r`, or `-shared`' % final_suffix)
if settings.SUPPORT_BIG_ENDIAN:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$LE_HEAP_STORE_U16',
'$LE_HEAP_STORE_I16',
'$LE_HEAP_STORE_U32',
'$LE_HEAP_STORE_I32',
'$LE_HEAP_STORE_U64',
'$LE_HEAP_STORE_I64',
'$LE_HEAP_STORE_F32',
'$LE_HEAP_STORE_F64',
'$LE_HEAP_LOAD_U16',
'$LE_HEAP_LOAD_I16',
'$LE_HEAP_LOAD_U32',
'$LE_HEAP_LOAD_I32',
'$LE_HEAP_LOAD_U64',
'$LE_HEAP_LOAD_I64',
'$LE_HEAP_LOAD_F32',
'$LE_HEAP_LOAD_F64',
'$LE_ATOMICS_NATIVE_BYTE_ORDER',
'$LE_ATOMICS_ADD',
'$LE_ATOMICS_AND',
'$LE_ATOMICS_COMPAREEXCHANGE',
'$LE_ATOMICS_EXCHANGE',
'$LE_ATOMICS_ISLOCKFREE',
'$LE_ATOMICS_LOAD',
'$LE_ATOMICS_NOTIFY',
'$LE_ATOMICS_OR',
'$LE_ATOMICS_STORE',
'$LE_ATOMICS_SUB',
'$LE_ATOMICS_WAIT',
'$LE_ATOMICS_WAITASYNC',
'$LE_ATOMICS_XOR',
]
if settings.RUNTIME_DEBUG or settings.ASSERTIONS or settings.STACK_OVERFLOW_CHECK or settings.PTHREADS_PROFILING or settings.GL_ASSERTIONS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$ptrToString']
if settings.STACK_OVERFLOW_CHECK:
settings.REQUIRED_EXPORTS += [
'emscripten_stack_get_end',
'emscripten_stack_get_free',
'emscripten_stack_get_base',
'emscripten_stack_get_current',
]
if settings.RELOCATABLE:
settings.REQUIRED_EXPORTS += ['emscripten_stack_set_limits']
else:
settings.REQUIRED_EXPORTS += ['emscripten_stack_init']
if settings.STACK_OVERFLOW_CHECK >= 2:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$setStackLimits']
check_browser_versions()
if settings.MIN_NODE_VERSION >= 150000:
default_setting('NODEJS_CATCH_REJECTION', 0)
if settings.MODULARIZE:
default_setting('NODEJS_CATCH_REJECTION', 0)
if settings.POLYFILL:
settings.TRANSPILE = (settings.MIN_FIREFOX_VERSION < 79 or
settings.MIN_CHROME_VERSION < 85 or
settings.MIN_SAFARI_VERSION < 140000 or
settings.MIN_NODE_VERSION < 160000)
if settings.STB_IMAGE:
settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free']
if settings.USE_WEBGL2:
settings.MAX_WEBGL_VERSION = 2
if settings.MIN_WEBGL_VERSION == 2:
default_setting('MAX_WEBGL_VERSION', 2)
if settings.MIN_WEBGL_VERSION > settings.MAX_WEBGL_VERSION:
exit_with_error('MIN_WEBGL_VERSION must be smaller or equal to MAX_WEBGL_VERSION!')
if options.use_preload_plugins or len(options.preload_files) or len(options.embed_files):
if settings.NODERAWFS:
exit_with_error('--preload-file and --embed-file cannot be used with NODERAWFS which disables virtual filesystem')
settings.FORCE_FILESYSTEM = 1
if options.preload_files:
settings.INCOMING_MODULE_JS_API.append('preRun')
if settings.FORCE_FILESYSTEM and not settings.FILESYSTEM:
exit_with_error('`-sFORCE_FILESYSTEM` cannot be used with `-sFILESYSTEM=0`')
if settings.WASMFS:
settings.FILESYSTEM = 1
settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
add_system_js_lib('libwasmfs.js')
if settings.ASSERTIONS:
settings.REQUIRED_EXPORTS += ['wasmfs_flush']
if settings.FORCE_FILESYSTEM or settings.INCLUDE_FULL_LIBRARY:
settings.REQUIRED_EXPORTS += [
'emscripten_builtin_memalign',
'wasmfs_create_file',
'wasmfs_unmount',
'_wasmfs_mount',
'_wasmfs_read_file',
'_wasmfs_write_file',
'_wasmfs_open',
'_wasmfs_close',
'_wasmfs_write',
'_wasmfs_pwrite',
'_wasmfs_rename',
'_wasmfs_mkdir',
'_wasmfs_unlink',
'_wasmfs_chdir',
'_wasmfs_mknod',
'_wasmfs_rmdir',
'_wasmfs_mmap',
'_wasmfs_munmap',
'_wasmfs_msync',
'_wasmfs_read',
'_wasmfs_pread',
'_wasmfs_symlink',
'_wasmfs_truncate',
'_wasmfs_ftruncate',
'_wasmfs_stat',
'_wasmfs_lstat',
'_wasmfs_chmod',
'_wasmfs_fchmod',
'_wasmfs_lchmod',
'_wasmfs_utime',
'_wasmfs_llseek',
'_wasmfs_identify',
'_wasmfs_readlink',
'_wasmfs_readdir_start',
'_wasmfs_readdir_get',
'_wasmfs_readdir_finish',
'_wasmfs_get_cwd',
]
if settings.FULL_ES3:
settings.FULL_ES2 = 1
settings.MAX_WEBGL_VERSION = max(2, settings.MAX_WEBGL_VERSION)
if settings.MAIN_READS_PARAMS and not settings.STANDALONE_WASM:
settings.REQUIRED_EXPORTS += ['_emscripten_stack_alloc']
if settings.SUPPORT_LONGJMP == 'emscripten' or not settings.DISABLE_EXCEPTION_CATCHING:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stackSave', '$stackRestore']
if settings.SIDE_MODULE and 'GLOBAL_BASE' in user_settings:
diagnostics.warning('unused-command-line-argument', 'GLOBAL_BASE is not compatible with SIDE_MODULE')
if options.use_preload_plugins:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$Browser']
if not settings.BOOTSTRAPPING_STRUCT_INFO:
if settings.DYNAMIC_EXECUTION == 2 and not settings.MINIMAL_RUNTIME:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stackTrace']
if not settings.STANDALONE_WASM and (settings.EXIT_RUNTIME or settings.ASSERTIONS):
settings.EXPORT_IF_DEFINED += ['fflush']
if settings.SAFE_HEAP:
settings.REQUIRED_EXPORTS += ['emscripten_get_sbrk_ptr', 'emscripten_stack_get_base']
if not settings.DECLARE_ASM_MODULE_EXPORTS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$assignWasmExports']
if settings.ALLOW_MEMORY_GROWTH:
default_setting('ABORTING_MALLOC', 0)
if settings.EMBIND:
settings.REQUIRED_EXPORTS.append('__getTypeName')
if settings.PTHREADS or settings.WASM_WORKERS:
settings.REQUIRED_EXPORTS.append('_embind_initialize_bindings')
if settings.MODULARIZE == 'instance':
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnPostCtor']
if options.emit_tsd:
settings.EMIT_TSD = True
if settings.PTHREADS:
setup_pthreads()
add_system_js_lib('libpthread.js')
if settings.PROXY_TO_PTHREAD:
settings.PTHREAD_POOL_SIZE_STRICT = 0
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$runtimeKeepalivePush']
else:
if settings.PROXY_TO_PTHREAD:
exit_with_error('-sPROXY_TO_PTHREAD requires -pthread to work!')
add_system_js_lib('libpthread_stub.js')
if settings.MEMORY64:
settings.WASM_BIGINT = 1
if settings.MEMORY64 and settings.RELOCATABLE:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__table_base32')
if settings.WASM_WORKERS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$_wasmWorkerInitializeRuntime']
add_system_js_lib('libwasm_worker.js')
feature_matrix.apply_min_browser_versions()
settings.SUPPORTS_PROMISE_ANY = feature_matrix.caniuse(Feature.PROMISE_ANY)
default_setting('WASM_BIGINT', feature_matrix.caniuse(Feature.JS_BIGINT_INTEGRATION))
if settings.AUDIO_WORKLET:
add_system_js_lib('libwebaudio.js')
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$getWasmTableEntry')
if not settings.MINIMAL_RUNTIME:
if 'preRun' in settings.INCOMING_MODULE_JS_API:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$addOnPreRun')
if 'postRun' in settings.INCOMING_MODULE_JS_API:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$addOnPostRun')
if settings.FORCE_FILESYSTEM and not settings.MINIMAL_RUNTIME:
settings.EXPORTED_RUNTIME_METHODS += [
'FS_createPath',
'FS_createDataFile',
'FS_preloadFile',
'FS_unlink',
]
if not settings.WASMFS:
settings.EXPORTED_RUNTIME_METHODS += [
'FS_createLazyFile',
'FS_createDevice',
]
settings.EXPORTED_RUNTIME_METHODS += [
'addRunDependency',
'removeRunDependency',
]
if settings.PTHREADS or settings.WASM_WORKERS or settings.RELOCATABLE:
settings.IMPORTED_MEMORY = 1
set_initial_memory()
if not settings.DECLARE_ASM_MODULE_EXPORTS or '-lexports.js' in linker_args:
settings.MINIFY_WASM_EXPORT_NAMES = 0
if will_metadce() and \
settings.OPT_LEVEL >= 2 and \
settings.DEBUG_LEVEL <= 2 and \
options.oformat not in (OFormat.WASM, OFormat.BARE) and \
settings.ASYNCIFY != 2 and \
not settings.LINKABLE and \
not settings.STANDALONE_WASM and \
not settings.AUTODEBUG and \
not settings.ASSERTIONS and \
not settings.RELOCATABLE and \
not settings.MAIN_MODULE and \
settings.MINIFY_WASM_EXPORT_NAMES:
settings.MINIFY_WASM_IMPORTS_AND_EXPORTS = 1
settings.MINIFY_WASM_IMPORTED_MODULES = 1
if settings.WASM_BIGINT:
settings.LEGALIZE_JS_FFI = 0
if settings.SINGLE_FILE and settings.GENERATE_SOURCE_MAP:
diagnostics.warning('emcc', 'SINGLE_FILE disables source map support (which requires a .map file)')
settings.GENERATE_SOURCE_MAP = 0
if options.use_closure_compiler == 2 and not settings.WASM2JS:
exit_with_error('closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm')
if settings.AUTODEBUG:
settings.REQUIRED_EXPORTS += ['_emscripten_tempret_set']
if settings.LEGALIZE_JS_FFI:
settings.REQUIRED_EXPORTS += ['__get_temp_ret', '__set_temp_ret']
if settings.SPLIT_MODULE:
settings.INCOMING_MODULE_JS_API += ['loadSplitModule']
if settings.ASYNCIFY == 2:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['_load_secondary_module']
if settings.SIDE_MODULE and utils.suffix(target) in ('.js', '.mjs'):
diagnostics.warning('emcc', 'JavaScript output suffix requested, but wasm side modules are just wasm files; emitting only a .wasm, no .js')
if options.sanitize:
setup_sanitizers(options)
if settings.USE_ASAN or settings.SAFE_HEAP:
settings.CHECK_NULL_WRITES = 0
if 'GLOBAL_BASE' not in user_settings and not settings.SHRINK_LEVEL and not settings.OPT_LEVEL and not settings.USE_ASAN:
settings.STACK_FIRST = True
if '--stack-first' in linker_args:
settings.STACK_FIRST = True
if settings.USE_ASAN:
exit_with_error('--stack-first is not compatible with asan')
if 'GLOBAL_BASE' in user_settings:
exit_with_error('--stack-first is not compatible with -sGLOBAL_BASE')
set_max_memory()
if not settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 * 1024 * 1024 * 1024:
settings.CAN_ADDRESS_2GB = 1
if settings.MAX_WEBGL_VERSION >= 2:
settings.WEBGL_USE_GARBAGE_FREE_APIS = 1
if settings.MIN_FIREFOX_VERSION != feature_matrix.UNSUPPORTED and settings.MAXIMUM_MEMORY > 2 ** 31:
settings.WEBGL_USE_GARBAGE_FREE_APIS = 0
if settings.MIN_CHROME_VERSION != feature_matrix.UNSUPPORTED and settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 ** 32:
settings.WEBGL_USE_GARBAGE_FREE_APIS = 0
if settings.WEBGL_USE_GARBAGE_FREE_APIS and settings.MIN_WEBGL_VERSION >= 2:
settings.INCLUDE_WEBGL1_FALLBACK = 0
if settings.MINIMAL_RUNTIME:
if settings.EXIT_RUNTIME:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['proc_exit', '$callRuntimeCallbacks']
else:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks']
if settings.EXIT_RUNTIME and not settings.STANDALONE_WASM:
settings.REQUIRED_EXPORTS += ['__funcs_on_exit']
if settings.BUILD_AS_WORKER:
settings.REQUIRED_EXPORTS += ['realloc']
options.post_js.append(utils.path_from_root('src/build_as_worker.js'))
if not settings.DISABLE_EXCEPTION_CATCHING:
settings.REQUIRED_EXPORTS += [
'__cxa_can_catch',
'__cxa_increment_exception_refcount',
'__cxa_decrement_exception_refcount',
'setThrew',
'__cxa_free_exception',
]
if settings.ASYNCIFY:
if not settings.ASYNCIFY_IGNORE_INDIRECT:
settings.ASYNCIFY_IMPORTS += ['invoke_*']
settings.ASYNCIFY_IMPORTS += DEFAULT_ASYNCIFY_IMPORTS
settings.ASYNCIFY_EXPORTS += DEFAULT_ASYNCIFY_EXPORTS
def get_full_import_name(name):
if '.' in name:
return name
return 'env.' + name
settings.ASYNCIFY_IMPORTS = [get_full_import_name(i) for i in settings.ASYNCIFY_IMPORTS]
if settings.ASYNCIFY == 2:
diagnostics.warning('experimental', '-sJSPI (ASYNCIFY=2) is still experimental')
if settings.WASM2JS:
if settings.GENERATE_SOURCE_MAP:
exit_with_error('wasm2js does not support source maps yet (debug in wasm for now)')
if settings.MEMORY64:
exit_with_error('wasm2js does not support MEMORY64')
if settings.WASM_BIGINT:
exit_with_error('wasm2js does not support WASM_BIGINT')
if settings.CAN_ADDRESS_2GB:
exit_with_error('wasm2js does not support >2gb address space')
if settings.NODE_CODE_CACHING:
if settings.WASM_ASYNC_COMPILATION:
exit_with_error('NODE_CODE_CACHING requires sync compilation (WASM_ASYNC_COMPILATION=0)')
if not settings.ENVIRONMENT_MAY_BE_NODE:
exit_with_error('NODE_CODE_CACHING only works in node, but target environments do not include it')
if settings.SINGLE_FILE:
exit_with_error('NODE_CODE_CACHING saves a file on the side and is not compatible with SINGLE_FILE')
if not js_manipulation.isidentifier(settings.EXPORT_NAME):
exit_with_error(f'EXPORT_NAME is not a valid JS identifier: `{settings.EXPORT_NAME}`')
if settings.EMSCRIPTEN_TRACING:
add_system_js_lib('libtrace.js')
if settings.ALLOW_MEMORY_GROWTH:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout']
settings.REQUIRED_EXPORTS += ['emscripten_stack_get_current',
'emscripten_stack_get_base',
'emscripten_stack_get_end']
settings.EMSCRIPTEN_VERSION = utils.EMSCRIPTEN_VERSION
if settings.RETAIN_COMPILER_SETTINGS:
settings.PUBLIC_SETTINGS = [k for k in settings.attrs.keys() if k not in settings.internal_settings]
settings.PUBLIC_SETTINGS.append('EMSCRIPTEN_VERSION')
settings.SOURCE_MAP_BASE = options.source_map_base or ''
settings.LINK_AS_CXX = (shared.run_via_emxx or settings.DEFAULT_TO_CXX) and not options.nostdlibxx
if settings.WASMFS:
settings.LINK_AS_CXX = True
if not settings.LINK_AS_CXX:
cxx_only_settings = [
'EXCEPTION_DEBUG',
'DISABLE_EXCEPTION_CATCHING',
'EXCEPTION_CATCHING_ALLOWED',
'DISABLE_EXCEPTION_THROWING',
]
for setting in cxx_only_settings:
if setting in user_settings:
diagnostics.warning('linkflags', 'setting `%s` is not meaningful unless linking as C++', setting)
if settings.WASM_EXCEPTIONS:
settings.REQUIRED_EXPORTS += ['__trap']
if settings.EXCEPTION_STACK_TRACES:
if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS:
exit_with_error('EXCEPTION_STACK_TRACES requires either of -fexceptions or -fwasm-exceptions')
settings.EXPORT_EXCEPTION_HANDLING_HELPERS = True
if settings.EXPORT_EXCEPTION_HANDLING_HELPERS:
if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS:
exit_with_error('EXPORT_EXCEPTION_HANDLING_HELPERS requires either of -fexceptions or -fwasm-exceptions')
settings.EXPORTED_FUNCTIONS += ['getExceptionMessage', 'incrementExceptionRefcount', 'decrementExceptionRefcount']
if settings.WASM_EXCEPTIONS:
settings.REQUIRED_EXPORTS += ['__cpp_exception']
if settings.SIDE_MODULE:
settings.REQUIRED_EXPORTS.clear()
if not settings.STANDALONE_WASM:
settings.REQUIRED_EXPORTS.append('__wasm_call_ctors')
if settings.PTHREADS:
settings.REQUIRED_EXPORTS.append('_emscripten_tls_init')
if settings.NODERAWFS:
default_setting('NODE_HOST_ENV', 1)
if not settings.ENVIRONMENT_MAY_BE_NODE:
if settings.NODERAWFS:
diagnostics.warning('unused-command-line-argument', 'NODERAWFS ignored since `node` not in `ENVIRONMENT`')
if settings.NODE_CODE_CACHING:
diagnostics.warning('unused-command-line-argument', 'NODE_CODE_CACHING ignored since `node` not in `ENVIRONMENT`')
if settings.NODEJS_CATCH_EXIT:
diagnostics.warning('unused-command-line-argument', 'NODEJS_CATCH_EXIT ignored since `node` not in `ENVIRONMENT`')
if settings.NODEJS_CATCH_REJECTION and 'NODEJS_CATCH_REJECTION' in user_settings:
diagnostics.warning('unused-command-line-argument', 'NODEJS_CATCH_REJECTION ignored since `node` not in `ENVIRONMENT`')
settings.PRE_JS_FILES = options.pre_js
settings.POST_JS_FILES = options.post_js
settings.MINIFY_WHITESPACE = settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL == 0 and not options.no_minify
if settings.USE_CLOSURE_COMPILER or not settings.MINIFY_WHITESPACE:
settings.MAYBE_CLOSURE_COMPILER = 1
check_settings()
return target, wasm_target
@ToolchainProfiler.profile_block('calculate system libraries')
def phase_calculate_system_libraries(options):
extra_files_to_link = []
if not settings.SIDE_MODULE:
extra_files_to_link += ports.get_libs(settings)
extra_files_to_link += system_libs.calculate(options)
return extra_files_to_link
@ToolchainProfiler.profile_block('link')
def phase_link(linker_args, wasm_target, js_syms):
logger.debug(f'linking: {linker_args}')
settings.EXPORTED_FUNCTIONS = dedup_list(settings.EXPORTED_FUNCTIONS)
settings.REQUIRED_EXPORTS = dedup_list(settings.REQUIRED_EXPORTS)
settings.EXPORT_IF_DEFINED = dedup_list(settings.EXPORT_IF_DEFINED)
rtn = None
if settings.LINKABLE and not settings.EXPORT_ALL:
settings.LINKABLE = False
building.link_lld(linker_args, wasm_target, external_symbols=js_syms)
settings.LINKABLE = True
rtn = extract_metadata.extract_metadata(wasm_target)
building.link_lld(linker_args, wasm_target, external_symbols=js_syms)
return rtn
@ToolchainProfiler.profile_block('post link')
def phase_post_link(options, in_wasm, wasm_target, target, js_syms, base_metadata=None):
global final_js
target_basename = unsuffixed_basename(target)
if options.oformat != OFormat.WASM:
final_js = in_temp(target_basename + '.js')
settings.TARGET_BASENAME = unsuffixed_basename(target)
if options.oformat in (OFormat.JS, OFormat.MJS):
js_target = target
else:
js_target = get_secondary_target(target, '.js')
settings.TARGET_JS_NAME = os.path.basename(js_target)
metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata)
if settings.EMBIND_AOT:
phase_embind_aot(options, wasm_target, js_syms)
if options.emit_tsd:
phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata)
if options.js_transform:
phase_source_transforms(options)
phase_binaryen(target, options, wasm_target)
if options.oformat != OFormat.WASM:
phase_final_emitting(options, target, js_target, wasm_target)
@ToolchainProfiler.profile_block('emscript')
def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata):
logger.debug('emscript')
if settings.SINGLE_FILE and not settings.WASM2JS:
settings.SUPPORT_BASE64_EMBEDDING = 1
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$base64Decode')
if shared.SKIP_SUBPROCS:
return
metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms, base_metadata=base_metadata)
save_intermediate('original')
return metadata
def run_embind_gen(options, wasm_target, js_syms, extra_settings):
original_settings = settings.backup()
settings.attrs.update(extra_settings)
settings.EMBIND_GEN_MODE = True
if settings.MAIN_MODULE:
for f in options.input_files:
if building.is_wasm_dylib(f):
safe_copy(f, in_temp(''))
settings.INVOKE_RUN = False
settings.MODULARIZE = False
settings.WASM_ESM_INTEGRATION = False
settings.PRE_JS_FILES = []
settings.POST_JS_FILES = []
if 'node' not in settings.ENVIRONMENT:
settings.ENVIRONMENT.append('node')
settings.MINIMAL_RUNTIME = 0
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks', '$addRunDependency', '$removeRunDependency']
settings.EXPORT_ES6 = False
settings.PROXY_TO_PTHREAD = False
settings.PTHREAD_POOL_SIZE = 0
settings.WASM2JS = 0
settings.MINIFY_WASM_IMPORTED_MODULES = False
setup_environment_settings()
settings.SINGLE_FILE = False
for i, lib in enumerate(settings.JS_LIBRARIES):
dirname, basename = os.path.split(lib)
if basename == 'libembind.js':
settings.JS_LIBRARIES[i] = os.path.join(dirname, 'libembind_gen.js')
settings.MIN_NODE_VERSION = 160000 if settings.MEMORY64 else 150000
if settings.MEMORY64:
settings.MEMORY64 = 2
settings.LOAD_SOURCE_MAP = 0
outfile_js = in_temp('tsgen.js')
outfile_wasm = in_temp('tsgen.wasm')
emscripten.emscript(wasm_target, outfile_wasm, outfile_js, js_syms, finalize=False)
node_args = []
if settings.MEMORY64:
building.run_wasm_opt(outfile_wasm, outfile_wasm, ['--memory64-lowering', '--table64-lowering'])
if settings.WASM_EXCEPTIONS:
node_args += shared.node_exception_flags(config.NODE_JS)
output_file = in_temp('embind_generated_output.js')
shared.run_js_tool(outfile_js, [output_file], node_args)
settings.restore(original_settings)
return read_file(output_file)
@ToolchainProfiler.profile_block('emit tsd')
def phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata):
logger.debug('emit tsd')
filename = options.emit_tsd
embind_tsd = ''
if settings.EMBIND:
embind_tsd = run_embind_gen(options, wasm_target, js_syms, {'EMBIND_AOT': False})
all_tsd = emscripten.create_tsd(metadata, embind_tsd)
out_file = os.path.join(os.path.dirname(js_target), filename)
write_file(out_file, all_tsd)
@ToolchainProfiler.profile_block('embind aot js')
def phase_embind_aot(options, wasm_target, js_syms):
out = run_embind_gen(options, wasm_target, js_syms, {})
if DEBUG:
write_file(in_temp('embind_aot.json'), out)
out = json.loads(out)
src = read_file(final_js)
src = do_replace(src, '<<< EMBIND_AOT_INVOKERS >>>', out['invokers'])
if settings.MODULARIZE == 'instance':
decls = '\n'.join([f'export var {name};' for name in out['publicSymbols']])
assigns = '\n'.join([f'{name} = Module[\'{name}\'];' for name in out['publicSymbols']])
exports = f'''
// start embind exports
function assignEmbindExports() {{ {assigns} }};
addOnPostCtor(assignEmbindExports);
{decls}
// end embind exports'''
src += exports
write_file(final_js, src)
if settings.WASM_ESM_INTEGRATION:
settings.EXPORTED_RUNTIME_METHODS.extend(out['publicSymbols'])
def remove_quotes(arg):
if isinstance(arg, list):
return [remove_quotes(a) for a in arg]
if arg.startswith('"') and arg.endswith('"'):
return arg[1:-1].replace('\\"', '"')
elif arg.startswith("'") and arg.endswith("'"):
return arg[1:-1].replace("\\'", "'")
else:
return arg
@ToolchainProfiler.profile_block('source transforms')
def phase_source_transforms(options):
global final_js
safe_copy(final_js, final_js + '.tr.js')
final_js += '.tr.js'
posix = not WINDOWS
logger.debug('applying transform: %s', options.js_transform)
shared.check_call(remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final_js)]))
save_intermediate('transformed')
def fix_js_mangling(js_file):
if not settings.MODULARIZE:
return
src = read_file(js_file)
write_file(js_file, src
.replace('EMSCRIPTEN$IMPORT$META', 'import.meta')
.replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import')
.replace('EMSCRIPTEN$AWAIT(', 'await ('))
save_intermediate('js-mangling')
def node_detection_code():
return "globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'"
def create_esm_wrapper(wrapper_file, support_target, wasm_target):
js_exports = building.user_requested_exports.union(settings.EXPORTED_RUNTIME_METHODS)
js_exports = ', '.join(sorted(js_exports))
wrapper = []
wrapper.append('// The wasm module must be imported here first before the support file')
wrapper.append('// in order to avoid issues with circular dependencies.')
wrapper.append(f"import * as unused from './{settings.WASM_BINARY_FILE}';")
support_url = f'./{os.path.basename(support_target)}'
if js_exports:
wrapper.append(f"export {{ default, {js_exports} }} from '{support_url}';")
else:
wrapper.append(f"export {{ default }} from '{support_url}';")
if settings.ENVIRONMENT_MAY_BE_NODE:
wrapper.append(f'''
// When run as the main module under node, create the module directly. This will
// execute any startup code along with main (if it exists).
import init from '{support_url}';
const isNode = {node_detection_code()};
if (isNode) {{
const url = await import('node:url');
const isMainModule = url.pathToFileURL(process.argv[1]).href === import.meta.url;
if (isMainModule) await init();
}}''')
write_file(wrapper_file, '\n'.join(wrapper) + '\n')
wasm_dis = os.path.join(building.get_binaryen_bin(), 'wasm-dis')
mod = shared.check_call([wasm_dis, wasm_target], stdout=PIPE).stdout
mod = mod.replace('(import "env"', f'(import "{support_url}"')
mod = mod.replace('(import "wasi_snapshot_preview1"', f'(import "{support_url}"')
wasm_as = os.path.join(building.get_binaryen_bin(), 'wasm-as')
cmd = [wasm_as, '--all-features', '-o', wasm_target, '-']
if settings.EMIT_NAME_SECTION:
cmd.append('-g')
shared.check_call(cmd, input=mod)
@ToolchainProfiler.profile_block('final emitting')
def phase_final_emitting(options, target, js_target, wasm_target):
global final_js
if shared.SKIP_SUBPROCS:
return
if settings.MODULARIZE and settings.MODULARIZE != 'instance':
modularize()
elif settings.USE_CLOSURE_COMPILER:
module_export_name_substitution()
if settings.MINIMAL_RUNTIME == 2 and settings.USE_CLOSURE_COMPILER and settings.DEBUG_LEVEL == 0:
args = [final_js, '-o', final_js]
if not settings.MINIFY_WHITESPACE:
args.append('--pretty')
shared.run_js_tool(utils.path_from_root('tools/unsafe_optimizations.mjs'), args, cwd=utils.path_from_root('.'))
save_intermediate('unsafe-optimizations')
final_js = building.closure_compiler(final_js, advanced=False, extra_closure_args=settings.CLOSURE_ARGS)
shared.run_js_tool(utils.path_from_root('tools/unsafe_optimizations.mjs'), [final_js, '-o', final_js], cwd=utils.path_from_root('.'))
save_intermediate('unsafe-optimizations2')
fix_js_mangling(final_js)
if options.extern_pre_js or options.extern_post_js:
extern_pre_js = read_js_files(options.extern_pre_js)
extern_post_js = read_js_files(options.extern_post_js)
logger.debug('applying extern pre/postjses')
src = read_file(final_js)
final_js += '.epp.js'
with open(final_js, 'w', encoding='utf-8') as f:
f.write(extern_pre_js)
f.write(src)
f.write(extern_post_js)
save_intermediate('extern-pre-post')
js_manipulation.handle_license(final_js)
if settings.WASM_ESM_INTEGRATION:
support_target = unsuffixed(js_target) + '.support.mjs'
move_file(final_js, support_target)
create_esm_wrapper(js_target, support_target, wasm_target)
if settings.PTHREADS:
support_target = unsuffixed(js_target) + '.pthread.mjs'
pthread_code = building.read_and_preprocess(utils.path_from_root('src/pthread_esm_startup.mjs'), expand_macros=True)
write_file(support_target, pthread_code)
fix_js_mangling(support_target)
else:
move_file(final_js, js_target)
target_basename = unsuffixed_basename(target)
utils.convert_line_endings_in_file(js_target, options.output_eol)
if options.oformat == OFormat.HTML:
generate_html(target, options, js_target, target_basename,
wasm_target)
if settings.SPLIT_MODULE:
do_split_module(wasm_target, options)
if settings.EXECUTABLE:
make_js_executable(js_target)
@ToolchainProfiler.profile_block('binaryen')
def phase_binaryen(target, options, wasm_target):
global final_js
logger.debug('using binaryen')
debug_function_names = settings.DEBUG_LEVEL >= 2 or settings.EMIT_NAME_SECTION
intermediate_debug_info = 0
if debug_function_names:
intermediate_debug_info += 1
if options.emit_symbol_map:
intermediate_debug_info += 1
if settings.ASYNCIFY == 1:
intermediate_debug_info += 1
passes = get_binaryen_passes(options)
if passes:
if settings.ASYNCIFY == 1:
intermediate_debug_info -= 1
if settings.GENERATE_DWARF and should_run_binaryen_optimizer():
diagnostics.warning('limited-postlink-optimizations', 'running limited binaryen optimizations because DWARF info requested (or indirectly required)')
with ToolchainProfiler.profile_block('wasm_opt'):
building.run_wasm_opt(wasm_target,
wasm_target,
args=passes,
debug=intermediate_debug_info)
building.save_intermediate(wasm_target, 'byn.wasm')
if settings.EVAL_CTORS:
with ToolchainProfiler.profile_block('eval_ctors'):
building.eval_ctors(final_js, wasm_target, debug_info=intermediate_debug_info)
building.save_intermediate(wasm_target, 'ctors.wasm')
if final_js:
if settings.CAN_ADDRESS_2GB:
with ToolchainProfiler.profile_block('use_unsigned_pointers_in_js'):
final_js = building.use_unsigned_pointers_in_js(final_js)
if settings.USE_ASAN:
final_js = building.instrument_js_for_asan(final_js)
if settings.SAFE_HEAP:
final_js = building.instrument_js_for_safe_heap(final_js)
if settings.SHARED_MEMORY and settings.ALLOW_MEMORY_GROWTH and not settings.GROWABLE_ARRAYBUFFERS:
with ToolchainProfiler.profile_block('apply_wasm_memory_growth'):
final_js = building.apply_wasm_memory_growth(final_js)
if settings.SUPPORT_BIG_ENDIAN:
with ToolchainProfiler.profile_block('little_endian_heap'):
final_js = building.little_endian_heap(final_js)
if settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL <= 2:
with ToolchainProfiler.profile_block('minify_wasm'):
save_intermediate_with_wasm('preclean', wasm_target)
final_js = building.minify_wasm_js(js_file=final_js,
wasm_file=wasm_target,
expensive_optimizations=will_metadce(),
debug_info=intermediate_debug_info)
save_intermediate_with_wasm('postclean', wasm_target)
if options.use_closure_compiler:
with ToolchainProfiler.profile_block('closure_compile'):
final_js = building.closure_compiler(final_js, extra_closure_args=settings.CLOSURE_ARGS)
save_intermediate('closure')
if settings.TRANSPILE:
with ToolchainProfiler.profile_block('transpile'):
final_js = building.transpile(final_js)
save_intermediate('transpile')
if settings.MINIFY_WHITESPACE:
final_js = building.acorn_optimizer(final_js, ['--minify-whitespace'])
symbols_file = None
if options.emit_symbol_map:
symbols_file = shared.replace_or_append_suffix(target, '.symbols')
if settings.WASM2JS:
symbols_file_js = None
if settings.WASM == 2:
wasm2js_polyfill = building.read_and_preprocess(utils.path_from_root('src/wasm2js.js'), expand_macros=True)
wasm2js_template = wasm_target + '.js'
write_file(wasm2js_template, wasm2js_polyfill)
if options.emit_symbol_map:
symbols_file_js = shared.replace_or_append_suffix(wasm2js_template, '.symbols')
else:
wasm2js_template = final_js
if options.emit_symbol_map:
symbols_file_js = shared.replace_or_append_suffix(target, '.symbols')
wasm2js = building.wasm2js(wasm2js_template,
wasm_target,
opt_level=settings.OPT_LEVEL,
use_closure_compiler=options.use_closure_compiler,
debug_info=debug_function_names,
symbols_file=symbols_file,
symbols_file_js=symbols_file_js)
shared.get_temp_files().note(wasm2js)
if settings.WASM == 2:
safe_copy(wasm2js, wasm2js_template)
if settings.WASM != 2:
final_js = wasm2js
save_intermediate('wasm2js')
generating_wasm = settings.WASM == 2 or not settings.WASM2JS
if options.emit_symbol_map:
intermediate_debug_info -= 1
if generating_wasm:
building.write_symbol_map(wasm_target, symbols_file)
if not intermediate_debug_info:
building.strip_sections(wasm_target, wasm_target, ['name'])
if settings.GENERATE_DWARF and settings.SEPARATE_DWARF and generating_wasm:
wasm_file_with_dwarf = settings.SEPARATE_DWARF
if wasm_file_with_dwarf is True:
wasm_file_with_dwarf = get_secondary_target(target, '.wasm.debug.wasm')
building.emit_debug_on_side(wasm_target, wasm_file_with_dwarf)
if debug_function_names:
intermediate_debug_info -= 1
assert intermediate_debug_info == 0
if not debug_function_names and building.binaryen_kept_debug_info and generating_wasm:
building.strip_sections(wasm_target, wasm_target, ['name'])
if final_js and settings.SINGLE_FILE and not settings.WASM2JS:
js = read_file(final_js)
if settings.SINGLE_FILE_BINARY_ENCODE:
js = do_replace(js, '"<<< WASM_BINARY_DATA >>>"', binary_encode(wasm_target))
else:
js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(wasm_target))
delete_file(wasm_target)
write_file(final_js, js)
def modularize():
global final_js
logger.debug(f'Modularizing, creating factory function called `{settings.EXPORT_NAME}`')
modularize_src = building.read_and_preprocess(utils.path_from_root('src/modularize.js'), expand_macros=True)
if settings.MINIFY_WHITESPACE:
with shared.get_temp_files().get_file(suffix='.js') as tmp:
write_file(tmp, modularize_src)
minified_file = building.acorn_optimizer(tmp, ['--minify-whitespace'])
modularize_src = read_file(minified_file)
full_src = do_replace(modularize_src, '"<<< INNER_JS_CODE >>>"', read_file(final_js))
final_js += '.modular.js'
write_file(final_js, full_src)
shared.get_temp_files().note(final_js)
save_intermediate('modularized')
def module_export_name_substitution():
assert not settings.MODULARIZE
global final_js
logger.debug(f'Private module export name substitution with {settings.EXPORT_NAME}')
src = read_file(final_js)
final_js += '.module_export_name_substitution.js'
if settings.MINIMAL_RUNTIME and not settings.ENVIRONMENT_MAY_BE_NODE and not settings.ENVIRONMENT_MAY_BE_SHELL and not settings.ENVIRONMENT_MAY_BE_AUDIO_WORKLET:
replacement = settings.EXPORT_NAME
else:
replacement = "typeof %(EXPORT_NAME)s != 'undefined' ? %(EXPORT_NAME)s : {}" % {"EXPORT_NAME": settings.EXPORT_NAME}
new_src = re.sub(r'{\s*[\'"]?__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__[\'"]?:\s*1\s*}', replacement, src)
assert new_src != src, 'Unable to find Closure syntax __EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__ in source!'
write_file(final_js, new_src)
shared.get_temp_files().note(final_js)
save_intermediate('module_export_name_substitution')
def generate_traditional_runtime_html(target, options, js_target, wasm_target):
script = ScriptSource()
if settings.EXPORT_NAME != 'Module' and options.shell_html == DEFAULT_SHELL_HTML:
exit_with_error('customizing EXPORT_NAME requires that the HTML be customized to use that name (see https://github.com/emscripten-core/emscripten/issues/10086)')
shell = building.read_and_preprocess(options.shell_html)
if '{{{ SCRIPT }}}' not in shell:
exit_with_error('HTML shell must contain {{{ SCRIPT }}}, see src/shell.html for an example')
if settings.SINGLE_FILE:
js_contents = script.inline or ''
js_contents += read_file(js_target)
script.inline = read_file(js_target)
delete_file(js_target)
else:
script.src = os.path.basename(js_target)
if not settings.WASM_ASYNC_COMPILATION:
script.un_src()
script.inline = '''
fetch(%s).then((result) => result.arrayBuffer())
.then((buf) => {
Module.wasmBinary = buf;
%s;
});
''' % (get_subresource_location(wasm_target), script.inline)
if settings.WASM == 2:
script.un_src()
script.inline = '''
function loadMainJs() {
%s
}
if (!window.WebAssembly || location.search.indexOf('_rwasm=0') > 0) {
// Current browser does not support WebAssembly, load the .wasm.js JavaScript fallback
// before the main JS runtime.
var wasm2js = document.createElement('script');
wasm2js.src = %s;
wasm2js.onload = loadMainJs;
document.body.appendChild(wasm2js);
} else {
// Current browser supports Wasm, proceed with loading the main JS runtime.
loadMainJs();
}
''' % (script.inline, get_subresource_location_js(wasm_target + '.js'))
shell = do_replace(shell, '{{{ SCRIPT }}}', script.replacement())
shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME)
shell = shell.replace('{{{ SHELL_CSS }}}', utils.read_file(utils.path_from_root('html/shell.css')))
logo_filename = utils.path_from_root('media/powered_by_logo_shell.png')
logo_b64 = base64_encode(logo_filename)
shell = shell.replace('{{{ SHELL_LOGO }}}', f'<img id="emscripten_logo" src="data:image/png;base64,{logo_b64}">')
check_output_file(target)
write_file(target, shell)
@ToolchainProfiler.profile()
def minify_html(filename):
if settings.DEBUG_LEVEL >= 2:
return
opts = []
if settings.DEBUG_LEVEL == 0:
opts += ['--collapse-whitespace',
'--remove-comments',
'--remove-tag-whitespace',
'--sort-attributes',
'--sort-class-name']
if settings.DEBUG_LEVEL <= 1:
opts += ['--decode-entities',
'--collapse-boolean-attributes',
'--remove-attribute-quotes',
'--remove-redundant-attributes',
'--remove-script-type-attributes',
'--remove-style-link-type-attributes',
'--use-short-doctype',
'--minify-css', 'true',
'--minify-js', 'true']
logger.debug(f'minifying HTML file {filename}')
size_before = os.path.getsize(filename)
shared.check_call(shared.get_npm_cmd('html-minifier-terser') + [filename, '-o', filename] + opts, env=shared.env_with_node_in_path())
def unescape_nulls(filename):
data = read_file(filename)
out = []
in_escape = False
i = 0
while i < len(data):
ch = data[i]
i += 1
if ch == '\\':
if in_escape:
out.append('\\\\')
in_escape = not in_escape
elif in_escape:
in_escape = False
if ch == '0':
out.append('\x00')
elif ch == 'x' and data[i] == '0' and data[i + 1] == '0':
out.append('\x00')
i += 2
else:
out.append('\\')
out.append(ch)
else:
out.append(ch)
write_file(filename, ''.join(out))
unescape_nulls(filename)
size_after = os.path.getsize(filename)
delta = size_after - size_before
logger.debug(f'HTML minification shrunk {filename} from {size_before} to {size_after} bytes, delta={delta} ({delta * 100.0 / size_before:+.2f}%)')
def generate_html(target, options, js_target, target_basename, wasm_target):
logger.debug('generating HTML')
if settings.MINIMAL_RUNTIME:
generate_minimal_runtime_html(target, options, js_target, target_basename)
else:
generate_traditional_runtime_html(target, options, js_target, wasm_target)
if settings.MINIFY_HTML and (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1):
minify_html(target)
utils.convert_line_endings_in_file(target, options.output_eol)
def find_library(lib, lib_dirs):
for lib_dir in lib_dirs:
path = os.path.join(lib_dir, lib)
if os.path.isfile(path):
logger.debug('found library "%s" at %s', lib, path)
return path
return None
def map_to_js_libs(library_name):
"""Given the name of a special Emscripten-implemented system library, returns an
pair containing
1. Array of absolute paths to JS library files, inside emscripten/src/ that corresponds to the
library name. `None` means there is no mapping and the library will be processed by the linker
as a require for normal native library.
2. Optional name of a corresponding native library to link in.
"""
library_map = {
'embind': ['libembind.js', 'libemval.js'],
'EGL': ['libegl.js'],
'GL': ['libwebgl.js', 'libhtml5_webgl.js'],
'webgl.js': ['libwebgl.js', 'libhtml5_webgl.js'],
'GLESv2': ['libwebgl.js'],
'GLEW': ['libglew.js'],
'glfw': ['libglfw.js'],
'glfw3': ['libglfw.js'],
'GLU': [],
'glut': ['libglut.js'],
'openal': ['libopenal.js'],
'X11': ['libxlib.js'],
'SDL': ['libsdl.js'],
'uuid': ['libuuid.js'],
'fetch': ['libfetch.js'],
'websocket': ['libwebsocket.js'],
'dl': [],
'm': [],
'rt': [],
'pthread': [],
'stdc++': [],
'SDL2_mixer': [],
}
if library_name in library_map:
libs = library_map[library_name]
logger.debug('Mapping library `%s` to JS libraries: %s' % (library_name, libs))
return libs
return None
def process_libraries(options, flags):
"""Process `-l` and `--js-library` flags."""
new_flags = []
system_libs_map = system_libs.Library.get_usable_variations()
for flag in flags:
if flag.startswith('--js-library='):
js_lib = flag.split('=', 1)[1]
settings.JS_LIBRARIES.append(js_lib)
continue
if not flag.startswith('-l'):
new_flags.append(flag)
continue
lib = flag.removeprefix('-l')
logger.debug('looking for library "%s"', lib)
js_libs = map_to_js_libs(lib)
if js_libs is not None:
for l in js_libs:
add_system_js_lib(l)
if 'lib' + lib in system_libs_map:
lib = system_libs_map['lib' + lib].get_link_flag()
new_flags.append(lib)
continue
if js_libs is not None:
continue
if lib.endswith('.js'):
name = 'lib' + lib
path = find_library(name, options.lib_dirs)
if not path:
exit_with_error(f'unable to find library {flag}')
settings.JS_LIBRARIES.append(os.path.abspath(path))
continue
static_lib = f'lib{lib}.a'
if not settings.RELOCATABLE and not settings.MAIN_MODULE and not find_library(static_lib, options.lib_dirs):
found_dylib = False
for ext in DYLIB_EXTENSIONS:
name = 'lib' + lib + ext
path = find_library(name, options.lib_dirs)
if path and not building.is_wasm_dylib(path):
found_dylib = True
new_flags.append(path)
break
if found_dylib:
continue
new_flags.append(flag)
return new_flags
def apply_library_settings(linker_args):
for arg in linker_args:
if not arg.startswith('-l'):
continue
library_name = arg[2:]
settings_map = {
'embind': {'EMBIND': 1},
'glfw': {'USE_GLFW': 2},
'glfw3': {'USE_GLFW': 3},
'SDL': {'USE_SDL': 1},
'SDL2_mixer': {'USE_SDL_MIXER': 2},
}
if library_name in settings_map:
for key, value in settings_map[library_name].items():
default_setting(key, value)
class ScriptSource:
def __init__(self):
self.src = None
self.inline = None
def un_src(self):
"""Use this if you want to modify the script and need it to be inline."""
if self.src is None:
return
quoted_src = quote(self.src)
if settings.EXPORT_ES6:
self.inline = f'''
import("./{quoted_src}").then(exports => exports.default(Module))
'''
else:
self.inline = f'''
var script = document.createElement('script');
script.src = "{quoted_src}";
document.body.appendChild(script);
'''
self.src = None
def replacement(self):
"""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:
src = quote(self.src)
filename = f'./{src}'
if settings.EXPORT_ES6:
return f'''
<script type="module">
import initModule from "{filename}";
initModule(Module);
</script>
'''
else:
if settings.MODULARIZE:
return f'<script type="text/javascript" src="{src}"></script>'
else:
return f'<script async type="text/javascript" src="{src}"></script>'
else:
return f'<script id="mainScript">\n{self.inline}\n</script>'
def filter_out_fake_dynamic_libs(options, inputs):
"""Filter out "fake" dynamic libraries that are really just intermediate object files."""
def is_fake_dylib(input_file):
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and os.path.exists(input_file) and not building.is_wasm_dylib(input_file):
if not options.ignore_dynamic_linking:
diagnostics.warning('emcc', 'ignoring dynamic library %s when generating an object file, this will need to be included explicitly in the final link', os.path.basename(input_file))
return True
else:
return False
return [f for f in inputs if not is_fake_dylib(f)]
def filter_out_duplicate_fake_dynamic_libs(inputs):
"""Filter out duplicate "fake" shared libraries (intermediate object files).
See test_core.py:test_redundant_link
"""
seen = set()
def check(input_file):
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
abspath = os.path.abspath(input_file)
if abspath in seen:
return False
seen.add(abspath)
return True
return [f for f in inputs if check(f)]
def process_dynamic_libs(dylibs, lib_dirs):
extras = []
seen = set()
to_process = dylibs.copy()
while to_process:
dylib = to_process.pop()
dylink = webassembly.parse_dylink_section(dylib)
for needed in dylink.needed:
if needed in seen:
continue
path = find_library(needed, lib_dirs)
if path:
extras.append(path)
seen.add(needed)
else:
exit_with_error(f'{os.path.normpath(dylib)}: shared library dependency not found in library path: `{needed}`. (library path: {lib_dirs}')
to_process.append(path)
dylibs += extras
for dylib in dylibs:
exports = webassembly.get_exports(dylib)
exports = {e.name for e in exports}
exports = [e.removeprefix('__em_js__') for e in exports]
settings.SIDE_MODULE_EXPORTS.extend(sorted(exports))
imports = webassembly.get_imports(dylib)
imports = [i.field for i in imports if i.kind in (webassembly.ExternType.FUNC, webassembly.ExternType.GLOBAL, webassembly.ExternType.TAG)]
imports = set(imports)
imports = {i for i in imports if not i.startswith('invoke_')}
weak_imports = webassembly.get_weak_imports(dylib)
strong_imports = sorted(imports.difference(weak_imports))
logger.debug('Adding symbols requirements from `%s`: %s', dylib, imports)
mangled_imports = [shared.asmjs_mangle(e) for e in sorted(imports)]
mangled_strong_imports = [shared.asmjs_mangle(e) for e in strong_imports]
for sym in weak_imports:
mangled = shared.asmjs_mangle(sym)
if mangled not in settings.SIDE_MODULE_IMPORTS and mangled not in building.user_requested_exports:
settings.WEAK_IMPORTS.append(sym)
settings.SIDE_MODULE_IMPORTS.extend(mangled_imports)
settings.EXPORT_IF_DEFINED.extend(sorted(imports))
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(sorted(imports))
building.user_requested_exports.update(mangled_strong_imports)
def unmangle_symbols_from_cmdline(symbols):
def unmangle(x):
return x.replace('.', ' ').replace('#', '&').replace('?', ',')
if type(symbols) is list:
return [unmangle(x) for x in symbols]
return unmangle(symbols)
def get_secondary_target(target, ext):
base = unsuffixed(target)
if get_file_suffix(target) == ext:
base += '_'
return base + ext
def dedup_list(lst):
return list(dict.fromkeys(lst))
def check_output_file(f):
if os.path.isdir(f):
exit_with_error(f'cannot write output file `{f}`: Is a directory')
def move_file(src, dst):
logging.debug('move: %s -> %s', src, dst)
check_output_file(dst)
src = os.path.abspath(src)
dst = os.path.abspath(dst)
if src == dst:
return
if dst == os.devnull:
return
shutil.move(src, dst)
def binary_encode(filename):
"""This function encodes the given binary byte array to a UTF-8 string, by
encoding each byte values as UTF-8, except for specific byte values that
are escaped as two bytes. This kind of encoding results in a string that will
compress well by both gzip and brotli, unlike base64 encoding binary data
would do.
"""
data = utils.read_binary(filename)
num_single_quotes = data.count(ord("'"))
num_double_quotes = data.count(ord('"'))
quote_char = ord("'") if num_single_quotes < num_double_quotes else ord('"')
out = bytearray(len(data) * 2 + 2)
out[0] = quote_char
i = 1
for d in data:
if d == quote_char:
buf = [ord('\\'), d]
elif d == ord('\r'):
buf = [ord('\\'), ord('r')]
elif d == ord('\n'):
buf = [ord('\\'), ord('n')]
elif d == ord('\\'):
buf = [ord('\\'), ord('\\')]
else:
buf = chr(d).encode('utf-8')
for b in buf:
out[i] = b
i += 1
out[i] = quote_char
i += 1
return out[0:i].decode('utf-8')
def get_subresource_location(path, mimetype='application/octet-stream'):
if settings.SINGLE_FILE:
if settings.SINGLE_FILE_BINARY_ENCODE:
return binary_encode(path)
return f'"data:{mimetype};base64,{base64_encode(path)}"'
else:
return f'"{os.path.basename(path)}"'
def get_subresource_location_js(path):
return get_subresource_location(path, 'text/javascript')
@ToolchainProfiler.profile()
def package_files(options, target):
rtn = []
logger.debug('setting up files')
file_args = ['--from-emcc']
if options.preload_files:
file_args.append('--preload')
file_args += options.preload_files
if options.embed_files:
file_args.append('--embed')
file_args += options.embed_files
if options.exclude_files:
file_args.append('--exclude')
file_args += options.exclude_files
if options.use_preload_cache:
file_args.append('--use-preload-cache')
if settings.LZ4:
file_args.append('--lz4')
if options.use_preload_plugins:
file_args.append('--use-preload-plugins')
if not settings.ENVIRONMENT_MAY_BE_NODE:
file_args.append('--no-node')
if options.embed_files:
if settings.MEMORY64:
file_args += ['--wasm64']
object_file = in_temp('embedded_files.o')
file_args += ['--obj-output=' + object_file]
rtn.append(object_file)
cmd = building.get_command_with_possible_response_file(
[shared.FILE_PACKAGER, utils.replace_suffix(target, '.data')] + file_args)
if options.preload_files:
file_code = shared.check_call(cmd, stdout=PIPE).stdout
js_manipulation.add_files_pre_js(settings.PRE_JS_FILES, file_code)
else:
shared.check_call(cmd)
return rtn
@ToolchainProfiler.profile_block('calculate linker inputs')
def phase_calculate_linker_inputs(options, linker_args):
using_lld = not (options.oformat == OFormat.OBJECT and settings.LTO)
linker_args = filter_link_flags(linker_args, using_lld)
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
else:
linker_args = filter_out_duplicate_fake_dynamic_libs(linker_args)
if settings.MAIN_MODULE:
process_dynamic_libs(options.dylibs, options.lib_dirs)
return linker_args
def calc_extra_ldflags(options):
extra_args = []
system_libpath = str(cache.get_lib_dir(absolute=True))
system_js_path = utils.path_from_root('src', 'lib')
options.lib_dirs.append(system_libpath)
options.lib_dirs.append(system_js_path)
extra_args.append('-L' + system_libpath)
extra_args.append('-L' + system_js_path)
if settings.FETCH:
extra_args.append('-lfetch')
if settings.STB_IMAGE:
extra_args.append('-lstb_image')
if settings.WASMFS and settings.NODERAWFS:
extra_args.append('--whole-archive')
extra_args.append('-lwasmfs_noderawfs')
extra_args.append('--no-whole-archive')
return extra_args
def run_post_link(wasm_input, options, linker_args):
settings.limit_settings(None)
target, wasm_target = phase_linker_setup(options, linker_args)
process_libraries(options, linker_args)
phase_post_link(options, wasm_input, wasm_target, target, {})
def run(options, linker_args):
settings.limit_settings(None)
if settings.RUNTIME_LINKED_LIBS:
linker_args += settings.RUNTIME_LINKED_LIBS
if not linker_args:
exit_with_error('no input files')
if options.output_file and options.output_file.startswith('-'):
exit_with_error(f'invalid output filename: `{options.output_file}`')
target, wasm_target = phase_linker_setup(options, linker_args)
linker_args = process_libraries(options, linker_args)
linker_args = phase_calculate_linker_inputs(options, linker_args)
if len(options.preload_files) or len(options.embed_files):
linker_args += package_files(options, target)
if options.oformat == OFormat.OBJECT:
logger.debug(f'link_to_object: {linker_args} -> {target}')
building.link_to_object(linker_args, target)
logger.debug('stopping after linking to object file')
return 0
system_libs = phase_calculate_system_libraries(options)
for s in system_libs:
if s.startswith('-l') and s in linker_args:
continue
linker_args.append(s)
js_syms = {}
if (not settings.SIDE_MODULE or settings.ASYNCIFY) and not shared.SKIP_SUBPROCS:
js_info = get_js_sym_info()
if not settings.SIDE_MODULE:
js_syms = js_info['deps']
if settings.LINKABLE:
for native_deps in js_syms.values():
settings.REQUIRED_EXPORTS += native_deps
else:
def add_js_deps(sym):
if sym in js_syms:
native_deps = js_syms[sym]
if native_deps:
settings.REQUIRED_EXPORTS += native_deps
for sym in settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE:
add_js_deps(sym)
for sym in js_info['extraLibraryFuncs']:
add_js_deps(sym)
for sym in settings.EXPORTED_RUNTIME_METHODS:
add_js_deps(shared.demangle_c_symbol_name(sym))
for sym in settings.EXPORTED_FUNCTIONS:
add_js_deps(shared.demangle_c_symbol_name(sym))
if settings.ASYNCIFY:
settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:]
settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']]
base_metadata = phase_link(linker_args, wasm_target, js_syms)
if '--version' in linker_args:
return 0
if target == os.devnull:
return 0
if options.oformat != OFormat.BARE:
phase_post_link(options, wasm_target, wasm_target, target, js_syms, base_metadata)
return 0