Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/link.py
4128 views
1
# Copyright 2011 The Emscripten Authors. All rights reserved.
2
# Emscripten is available under two separate licenses, the MIT license and the
3
# University of Illinois/NCSA Open Source License. Both these licenses can be
4
# found in the LICENSE file.
5
6
from .toolchain_profiler import ToolchainProfiler
7
8
import base64
9
import glob
10
import hashlib
11
import json
12
import logging
13
import os
14
import re
15
import shlex
16
import stat
17
import shutil
18
import time
19
from subprocess import PIPE
20
from urllib.parse import quote
21
22
from . import building
23
from . import cache
24
from . import config
25
from . import diagnostics
26
from . import emscripten
27
from . import feature_matrix
28
from . import filelock
29
from . import js_manipulation
30
from . import ports
31
from . import shared
32
from . import system_libs
33
from . import utils
34
from . import webassembly
35
from . import extract_metadata
36
from .cmdline import OFormat
37
from .utils import read_file, write_file, delete_file
38
from .utils import removeprefix, exit_with_error
39
from .shared import in_temp, safe_copy, do_replace
40
from .shared import DEBUG, WINDOWS, DYLIB_EXTENSIONS
41
from .shared import unsuffixed, unsuffixed_basename, get_file_suffix
42
from .settings import settings, default_setting, user_settings, JS_ONLY_SETTINGS, DEPRECATED_SETTINGS
43
from .minimal_runtime_shell import generate_minimal_runtime_html
44
45
logger = logging.getLogger('link')
46
47
DEFAULT_SHELL_HTML = utils.path_from_root('src/shell.html')
48
49
DEFAULT_ASYNCIFY_IMPORTS = ['__asyncjs__*']
50
51
DEFAULT_ASYNCIFY_EXPORTS = [
52
'main',
53
'__main_argc_argv',
54
]
55
56
VALID_ENVIRONMENTS = ('web', 'webview', 'worker', 'node', 'shell')
57
58
EXECUTABLE_EXTENSIONS = ['.wasm', '.html', '.js', '.mjs', '.out', '']
59
60
# Supported LLD flags which we will pass through to the linker.
61
SUPPORTED_LINKER_FLAGS = (
62
'--start-group', '--end-group',
63
'-(', '-)',
64
'--whole-archive', '--no-whole-archive',
65
'-whole-archive', '-no-whole-archive',
66
'-rpath',
67
)
68
69
# Unsupported LLD flags which we will ignore.
70
# Maps to true if the flag takes an argument.
71
UNSUPPORTED_LLD_FLAGS = {
72
# macOS-specific linker flag that libtool (ltmain.sh) will if macOS is detected.
73
'-bind_at_load': False,
74
# wasm-ld doesn't support soname or other dynamic linking flags (yet). Ignore them
75
# in order to aid build systems that want to pass these flags.
76
'-allow-shlib-undefined': False,
77
'-rpath-link': True,
78
'-version-script': True,
79
'-install_name': True,
80
}
81
82
UBSAN_SANITIZERS = {
83
'alignment',
84
'bool',
85
'builtin',
86
'bounds',
87
'enum',
88
'float-cast-overflow',
89
'float-divide-by-zero',
90
'function',
91
'implicit-unsigned-integer-truncation',
92
'implicit-signed-integer-truncation',
93
'implicit-integer-sign-change',
94
'integer-divide-by-zero',
95
'nonnull-attribute',
96
'null',
97
'nullability-arg',
98
'nullability-assign',
99
'nullability-return',
100
'object-size',
101
'pointer-overflow',
102
'return',
103
'returns-nonnull-attribute',
104
'shift',
105
'signed-integer-overflow',
106
'unreachable',
107
'unsigned-integer-overflow',
108
'vla-bound',
109
'vptr',
110
'undefined',
111
'undefined-trap',
112
'implicit-integer-truncation',
113
'implicit-integer-arithmetic-value-change',
114
'implicit-conversion',
115
'integer',
116
'nullability',
117
}
118
119
120
final_js = None
121
122
123
# this function uses the global 'final' variable, which contains the current
124
# final output file. if a method alters final, and calls this method, then it
125
# must modify final globally (i.e. it can't receive final as a param and
126
# return it)
127
# TODO: refactor all this, a singleton that abstracts over the final output
128
# and saving of intermediates
129
def save_intermediate(name, suffix='js'):
130
if not DEBUG:
131
return
132
if not final_js:
133
logger.debug(f'(not saving intermediate {name} because not generating JS)')
134
return
135
building.save_intermediate(final_js, f'{name}.{suffix}')
136
137
138
def save_intermediate_with_wasm(name, wasm_binary):
139
if not DEBUG:
140
return
141
save_intermediate(name) # save the js
142
building.save_intermediate(wasm_binary, name + '.wasm')
143
144
145
def base64_encode(filename):
146
data = utils.read_binary(filename)
147
b64 = base64.b64encode(data)
148
return b64.decode('ascii')
149
150
151
def align_to_wasm_page_boundary(address):
152
page_size = webassembly.WASM_PAGE_SIZE
153
return ((address + (page_size - 1)) // page_size) * page_size
154
155
156
def will_metadce():
157
# The metadce JS parsing code does not currently support the JS that gets generated
158
# when assertions are enabled.
159
if settings.ASSERTIONS:
160
return False
161
return settings.OPT_LEVEL >= 3 or settings.SHRINK_LEVEL >= 1
162
163
164
def setup_environment_settings():
165
# The worker environment is automatically added if any of the pthread or Worker features are used.
166
# Note: we need to actually modify ENVIRONMENTS variable here before the parsing,
167
# because some JS code reads it back so modifying parsed info alone is not sufficient.
168
if settings.SHARED_MEMORY and settings.ENVIRONMENT:
169
settings.ENVIRONMENT.append('worker')
170
171
# Environment setting based on user input
172
if any(x for x in settings.ENVIRONMENT if x not in VALID_ENVIRONMENTS):
173
exit_with_error(f'Invalid environment specified in "ENVIRONMENT": {settings.ENVIRONMENT}. Should be one of: {",".join(VALID_ENVIRONMENTS)}')
174
175
settings.ENVIRONMENT_MAY_BE_WEB = not settings.ENVIRONMENT or 'web' in settings.ENVIRONMENT
176
settings.ENVIRONMENT_MAY_BE_WEBVIEW = not settings.ENVIRONMENT or 'webview' in settings.ENVIRONMENT
177
settings.ENVIRONMENT_MAY_BE_NODE = not settings.ENVIRONMENT or 'node' in settings.ENVIRONMENT
178
settings.ENVIRONMENT_MAY_BE_SHELL = not settings.ENVIRONMENT or 'shell' in settings.ENVIRONMENT
179
settings.ENVIRONMENT_MAY_BE_WORKER = not settings.ENVIRONMENT or 'worker' in settings.ENVIRONMENT
180
181
if not settings.ENVIRONMENT_MAY_BE_NODE:
182
if 'MIN_NODE_VERSION' in user_settings:
183
diagnostics.warning('unused-command-line-argument', 'ignoring MIN_NODE_VERSION because `node` environment is not enabled')
184
settings.MIN_NODE_VERSION = feature_matrix.UNSUPPORTED
185
186
if not (settings.ENVIRONMENT_MAY_BE_WEB or settings.ENVIRONMENT_MAY_BE_WEBVIEW):
187
for browser in ('FIREFOX', 'SAFARI', 'CHROME'):
188
key = f'MIN_{browser}_VERSION'
189
if key in user_settings:
190
diagnostics.warning('unused-command-line-argument', 'ignoring %s because `web` and `webview` environments are not enabled', key)
191
settings[key] = feature_matrix.UNSUPPORTED
192
193
194
def generate_js_sym_info():
195
"""Runs the js compiler to generate a list of all symbols available in the JS
196
libraries. This must be done separately for each linker invocation since the
197
list of symbols depends on what settings are used.
198
TODO(sbc): Find a way to optimize this. Potentially we could add a super-set
199
mode of the js compiler that would generate a list of all possible symbols
200
that could be checked in.
201
"""
202
_, forwarded_data = emscripten.compile_javascript(symbols_only=True)
203
# When running in symbols_only mode compiler.mjs outputs a flat list of C symbols.
204
return json.loads(forwarded_data)
205
206
207
def get_cached_file(filetype, filename, generator, cache_limit):
208
"""This function implements a file cache which lives inside the main
209
emscripten cache directory but uses a per-file lock rather than a
210
cache-wide lock.
211
212
The cache is pruned (by removing the oldest files) if it grows above
213
a certain number of files.
214
"""
215
root = cache.get_path(filetype)
216
utils.safe_ensure_dirs(root)
217
218
cache_file = os.path.join(root, filename)
219
220
with filelock.FileLock(cache_file + '.lock'):
221
if os.path.exists(cache_file):
222
# Cache hit, read the file
223
file_content = read_file(cache_file)
224
else:
225
# Cache miss, generate the symbol list and write the file
226
file_content = generator()
227
write_file(cache_file, file_content)
228
229
if len([f for f in os.listdir(root) if not f.endswith('.lock')]) > cache_limit:
230
with filelock.FileLock(cache.get_path(f'{filetype}.lock')):
231
files = []
232
for f in os.listdir(root):
233
if not f.endswith('.lock'):
234
f = os.path.join(root, f)
235
files.append((f, os.path.getmtime(f)))
236
files.sort(key=lambda x: x[1])
237
# Delete all but the newest N files
238
for f, _ in files[:-cache_limit]:
239
with filelock.FileLock(f + '.lock'):
240
delete_file(f)
241
242
return file_content
243
244
245
@ToolchainProfiler.profile_block('JS symbol generation')
246
def get_js_sym_info():
247
# Avoiding using the cache when generating struct info since
248
# this step is performed while the cache is locked.
249
if DEBUG or settings.BOOTSTRAPPING_STRUCT_INFO or config.FROZEN_CACHE:
250
return generate_js_sym_info()
251
252
# We define a cache hit as when the settings and `--js-library` contents are
253
# identical.
254
# Ignore certain settings that can are no relevant to library deps. Here we
255
# skip PRE_JS_FILES/POST_JS_FILES which don't effect the library symbol list
256
# and can contain full paths to temporary files.
257
skip_settings = {'PRE_JS_FILES', 'POST_JS_FILES'}
258
input_files = [json.dumps(settings.external_dict(skip_keys=skip_settings), sort_keys=True, indent=2)]
259
jslibs = glob.glob(utils.path_from_root('src/lib') + '/lib*.js')
260
assert jslibs
261
input_files.extend(read_file(jslib) for jslib in sorted(jslibs))
262
for jslib in settings.JS_LIBRARIES:
263
input_files.append(read_file(jslib))
264
content = '\n'.join(input_files)
265
content_hash = hashlib.sha1(content.encode('utf-8')).hexdigest()
266
267
def generate_json():
268
library_syms = generate_js_sym_info()
269
return json.dumps(library_syms, separators=(',', ':'), indent=2)
270
271
# Limit of the overall size of the cache.
272
# This code will get test coverage since a full test run of `other` or `core`
273
# generates ~1000 unique symbol lists.
274
file_content = get_cached_file('symbol_lists', f'{content_hash}.json', generate_json, cache_limit=500)
275
return json.loads(file_content)
276
277
278
def filter_link_flags(flags, using_lld):
279
def is_supported(f):
280
if using_lld:
281
for flag, takes_arg in UNSUPPORTED_LLD_FLAGS.items():
282
# lld allows various flags to have either a single -foo or double --foo
283
if f.startswith((flag, '-' + flag)):
284
diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f)
285
# Skip the next argument if this linker flag takes and argument and that
286
# argument was not specified as a separately (i.e. it was specified as
287
# single arg containing an `=` char.)
288
skip_next = takes_arg and '=' not in f
289
return False, skip_next
290
return True, False
291
else:
292
if not f.startswith('-') or f in SUPPORTED_LINKER_FLAGS:
293
return True, False
294
# Silently ignore -l/-L flags when not using lld. If using lld allow
295
# them to pass through the linker
296
if f.startswith(('-l', '-L')):
297
return False, False
298
diagnostics.warning('linkflags', 'ignoring unsupported linker flag: `%s`', f)
299
return False, False
300
301
results = []
302
skip_next = False
303
for f in flags:
304
if skip_next:
305
skip_next = False
306
continue
307
keep, skip_next = is_supported(f)
308
if keep:
309
results.append(f)
310
311
return results
312
313
314
def fix_windows_newlines(text):
315
# Avoid duplicating \r\n to \r\r\n when writing out text.
316
if WINDOWS:
317
text = text.replace('\r\n', '\n')
318
return text
319
320
321
def read_js_files(files):
322
contents = []
323
for f in files:
324
content = read_file(f)
325
if content.startswith('#preprocess\n'):
326
contents.append(building.read_and_preprocess(f, expand_macros=True))
327
else:
328
contents.append(content)
329
contents = '\n'.join(contents)
330
return fix_windows_newlines(contents)
331
332
333
def should_run_binaryen_optimizer():
334
# run the binaryen optimizer in -O2+. in -O0 we don't need it obviously, while
335
# in -O1 we don't run it as the LLVM optimizer has been run, and it does the
336
# great majority of the work; not running the binaryen optimizer in that case
337
# keeps -O1 mostly-optimized while compiling quickly and without rewriting
338
# DWARF etc.
339
return settings.OPT_LEVEL >= 2
340
341
342
def get_binaryen_passes():
343
passes = []
344
optimizing = should_run_binaryen_optimizer()
345
# wasm-emscripten-finalize will strip the features section for us
346
# automatically, but if we did not modify the wasm then we didn't run it,
347
# and in an optimized build we strip it manually here. (note that in an
348
# unoptimized build we might end up with the features section, if we neither
349
# optimize nor run wasm-emscripten-finalize, but a few extra bytes in the
350
# binary don't matter in an unoptimized build)
351
if optimizing:
352
passes += ['--strip-target-features']
353
# safe heap must run before post-emscripten, so post-emscripten can apply the sbrk ptr
354
if settings.SAFE_HEAP:
355
passes += ['--safe-heap']
356
# sign-ext is enabled by default by llvm. If the target browser settings don't support
357
# this we lower it away here using a binaryen pass.
358
if not feature_matrix.caniuse(feature_matrix.Feature.SIGN_EXT):
359
logger.debug('lowering sign-ext feature due to incompatible target browser engines')
360
passes += ['--signext-lowering']
361
# nontrapping-fp is enabled by default in llvm. Lower it away if requested.
362
if not feature_matrix.caniuse(feature_matrix.Feature.NON_TRAPPING_FPTOINT):
363
logger.debug('lowering nontrapping-fp feature due to incompatible target browser engines')
364
passes += ['--llvm-nontrapping-fptoint-lowering']
365
if not feature_matrix.caniuse(feature_matrix.Feature.BULK_MEMORY):
366
logger.debug('lowering bulk-memory feature due to incompatible target browser engines')
367
passes += ['--llvm-memory-copy-fill-lowering']
368
if optimizing:
369
passes += ['--post-emscripten']
370
if settings.SIDE_MODULE:
371
passes += ['--pass-arg=post-emscripten-side-module']
372
if optimizing:
373
passes += [building.opt_level_to_str(settings.OPT_LEVEL, settings.SHRINK_LEVEL)]
374
# when optimizing, use the fact that low memory is never used (1024 is a
375
# hardcoded value in the binaryen pass). we also cannot do it when the stack
376
# is first, as then the stack is in the low memory that should be unused.
377
if optimizing and settings.GLOBAL_BASE >= 1024 and not settings.STACK_FIRST:
378
passes += ['--low-memory-unused']
379
if settings.AUTODEBUG:
380
# adding '--flatten' here may make these even more effective
381
passes += ['--instrument-locals']
382
passes += ['--log-execution']
383
passes += ['--instrument-memory']
384
if settings.LEGALIZE_JS_FFI:
385
# legalize it again now, as the instrumentation may need it
386
passes += ['--legalize-js-interface']
387
passes += building.js_legalization_pass_flags()
388
if settings.EMULATE_FUNCTION_POINTER_CASTS:
389
# note that this pass must run before asyncify, as if it runs afterwards we only
390
# generate the byn$fpcast_emu functions after asyncify runs, and so we wouldn't
391
# be able to further process them.
392
passes += ['--fpcast-emu']
393
if settings.ASYNCIFY == 1:
394
passes += ['--asyncify']
395
if settings.MAIN_MODULE or settings.SIDE_MODULE:
396
passes += ['--pass-arg=asyncify-relocatable']
397
if settings.ASSERTIONS:
398
passes += ['--pass-arg=asyncify-asserts']
399
if settings.ASYNCIFY_ADVISE:
400
passes += ['--pass-arg=asyncify-verbose']
401
if settings.ASYNCIFY_IGNORE_INDIRECT:
402
passes += ['--pass-arg=asyncify-ignore-indirect']
403
if settings.ASYNCIFY_PROPAGATE_ADD:
404
passes += ['--pass-arg=asyncify-propagate-addlist']
405
passes += ['--pass-arg=asyncify-imports@%s' % ','.join(settings.ASYNCIFY_IMPORTS)]
406
407
# shell escaping can be confusing; try to emit useful warnings
408
def check_human_readable_list(items):
409
for item in items:
410
if item.count('(') != item.count(')'):
411
logger.warning('emcc: ASYNCIFY list contains an item without balanced parentheses ("(", ")"):')
412
logger.warning(' ' + item)
413
logger.warning('This may indicate improper escaping that led to splitting inside your names.')
414
logger.warning('Try using a response file. e.g: [email protected]. The format is a simple')
415
logger.warning('text file, one line per function.')
416
break
417
418
if settings.ASYNCIFY_REMOVE:
419
check_human_readable_list(settings.ASYNCIFY_REMOVE)
420
passes += ['--pass-arg=asyncify-removelist@%s' % ','.join(settings.ASYNCIFY_REMOVE)]
421
if settings.ASYNCIFY_ADD:
422
check_human_readable_list(settings.ASYNCIFY_ADD)
423
passes += ['--pass-arg=asyncify-addlist@%s' % ','.join(settings.ASYNCIFY_ADD)]
424
if settings.ASYNCIFY_ONLY:
425
check_human_readable_list(settings.ASYNCIFY_ONLY)
426
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(settings.ASYNCIFY_ONLY)]
427
428
if settings.MEMORY64 == 2:
429
passes += ['--memory64-lowering', '--table64-lowering']
430
431
if settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
432
passes += ['--ignore-implicit-traps']
433
# normally we can assume the memory, if imported, has not been modified
434
# beforehand (in fact, in most cases the memory is not even imported anyhow,
435
# but it is still safe to pass the flag), and is therefore filled with zeros.
436
# the one exception is dynamic linking of a side module: the main module is ok
437
# as it is loaded first, but the side module may be assigned memory that was
438
# previously used.
439
if optimizing and not settings.SIDE_MODULE:
440
passes += ['--zero-filled-memory']
441
# LLVM output always has immutable initial table contents: the table is
442
# fixed and may only be appended to at runtime (that is true even in
443
# relocatable mode)
444
if optimizing:
445
passes += ['--pass-arg=directize-initial-contents-immutable']
446
447
if settings.BINARYEN_EXTRA_PASSES:
448
# BINARYEN_EXTRA_PASSES is comma-separated, and we support both '-'-prefixed and
449
# unprefixed pass names
450
extras = settings.BINARYEN_EXTRA_PASSES.split(',')
451
passes += [('--' + p) if p[0] != '-' else p for p in extras if p]
452
453
# If we are going to run metadce then that means we will be running binaryen
454
# tools after the main invocation, whose flags are determined here
455
# (specifically we will run metadce and possibly also wasm-opt for import/
456
# export minification). And when we run such a tool it will "undo" any
457
# StackIR optimizations (since the conversion to BinaryenIR undoes them as it
458
# restructures the code). We could re-run those opts, but it is most efficient
459
# to just not do them now if we'll invoke other tools later, and we'll do them
460
# only in the very last invocation.
461
if will_metadce():
462
passes += ['--no-stack-ir']
463
464
return passes
465
466
467
def make_js_executable(script):
468
src = read_file(script)
469
cmd = config.NODE_JS
470
if len(cmd) > 1 or not os.path.isabs(cmd[0]):
471
# Using -S (--split-string) here means that arguments to the executable are
472
# correctly parsed. We don't do this by default because old versions of env
473
# don't support -S.
474
cmd = '/usr/bin/env -S ' + shlex.join(cmd)
475
else:
476
cmd = shlex.join(cmd)
477
logger.debug('adding `#!` to JavaScript file: %s' % cmd)
478
# add shebang
479
with open(script, 'w') as f:
480
f.write('#!%s\n' % cmd)
481
f.write(src)
482
try:
483
os.chmod(script, stat.S_IMODE(os.stat(script).st_mode) | stat.S_IXUSR) # make executable
484
except OSError:
485
pass # can fail if e.g. writing the executable to /dev/null
486
487
488
def do_split_module(wasm_file, options):
489
os.replace(wasm_file, wasm_file + '.orig')
490
args = ['--instrument']
491
if options.requested_debug:
492
# Tell wasm-split to preserve function names.
493
args += ['-g']
494
building.run_binaryen_command('wasm-split', wasm_file + '.orig', outfile=wasm_file, args=args)
495
496
497
def get_worker_js_suffix():
498
return '.worker.mjs' if settings.EXPORT_ES6 else '.worker.js'
499
500
501
def setup_pthreads():
502
if settings.RELOCATABLE:
503
# pthreads + dynamic linking has certain limitations
504
if settings.SIDE_MODULE:
505
diagnostics.warning('experimental', '-sSIDE_MODULE + pthreads is experimental')
506
elif settings.MAIN_MODULE:
507
diagnostics.warning('experimental', '-sMAIN_MODULE + pthreads is experimental')
508
elif settings.LINKABLE:
509
diagnostics.warning('experimental', '-sLINKABLE + pthreads is experimental')
510
if settings.ALLOW_MEMORY_GROWTH and not settings.GROWABLE_ARRAYBUFFERS:
511
diagnostics.warning('pthreads-mem-growth', '-pthread + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271')
512
513
default_setting('DEFAULT_PTHREAD_STACK_SIZE', settings.STACK_SIZE)
514
515
# Functions needs by runtime_pthread.js
516
settings.REQUIRED_EXPORTS += [
517
'_emscripten_thread_free_data',
518
'_emscripten_thread_crashed',
519
]
520
521
if settings.MAIN_MODULE:
522
settings.REQUIRED_EXPORTS += [
523
'_emscripten_dlsync_self',
524
'_emscripten_dlsync_self_async',
525
'_emscripten_proxy_dlsync',
526
'_emscripten_proxy_dlsync_async',
527
'__dl_seterr',
528
]
529
530
# runtime_pthread.js depends on these library symbols
531
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
532
'$PThread',
533
'$establishStackSpace',
534
'$invokeEntryPoint',
535
]
536
537
if settings.MINIMAL_RUNTIME:
538
building.user_requested_exports.add('exit')
539
540
541
def set_initial_memory():
542
user_specified_initial_heap = 'INITIAL_HEAP' in user_settings
543
544
# INITIAL_HEAP cannot be used when the memory object is created in JS: we don't know
545
# the size of static data here and thus the total initial memory size.
546
if settings.IMPORTED_MEMORY:
547
if user_specified_initial_heap:
548
# Some of these could (and should) be implemented.
549
exit_with_error('INITIAL_HEAP is currently not compatible with IMPORTED_MEMORY (which is enabled indirectly via SHARED_MEMORY, RELOCATABLE, ASYNCIFY_LAZY_LOAD_CODE)')
550
# The default for imported memory is to fall back to INITIAL_MEMORY.
551
settings.INITIAL_HEAP = -1
552
553
if not user_specified_initial_heap:
554
# For backwards compatibility, we will only use INITIAL_HEAP by default when the user
555
# specified neither INITIAL_MEMORY nor MAXIMUM_MEMORY. Both place an upper bounds on
556
# the overall initial linear memory (stack + static data + heap), and we do not know
557
# the size of static data at this stage. Setting any non-zero initial heap value in
558
# this scenario would risk pushing users over the limit they have set.
559
user_specified_initial = settings.INITIAL_MEMORY != -1
560
user_specified_maximum = 'MAXIMUM_MEMORY' in user_settings or 'WASM_MEM_MAX' in user_settings or 'BINARYEN_MEM_MAX' in user_settings
561
if user_specified_initial or user_specified_maximum:
562
settings.INITIAL_HEAP = -1
563
564
# Apply the default if we are going with INITIAL_MEMORY.
565
if settings.INITIAL_HEAP == -1 and settings.INITIAL_MEMORY == -1:
566
default_setting('INITIAL_MEMORY', 16 * 1024 * 1024)
567
568
def check_memory_setting(setting):
569
if settings[setting] % webassembly.WASM_PAGE_SIZE != 0:
570
exit_with_error(f'{setting} must be a multiple of WebAssembly page size (64KiB), was {settings[setting]}')
571
if settings[setting] >= 2**53:
572
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')
573
574
# Due to the aforementioned lack of knowledge about the static data size, we delegate
575
# checking the overall consistency of these settings to wasm-ld.
576
if settings.INITIAL_HEAP != -1:
577
check_memory_setting('INITIAL_HEAP')
578
579
if settings.INITIAL_MEMORY != -1:
580
check_memory_setting('INITIAL_MEMORY')
581
if settings.INITIAL_MEMORY < settings.STACK_SIZE:
582
exit_with_error(f'INITIAL_MEMORY must be larger than STACK_SIZE, was {settings.INITIAL_MEMORY} (STACK_SIZE={settings.STACK_SIZE})')
583
584
check_memory_setting('MAXIMUM_MEMORY')
585
if settings.MEMORY_GROWTH_LINEAR_STEP != -1:
586
check_memory_setting('MEMORY_GROWTH_LINEAR_STEP')
587
588
589
# Set an upper estimate of what MAXIMUM_MEMORY should be. Take note that this value
590
# may not be precise, and is only an upper bound of the exact value calculated later
591
# by the linker.
592
def set_max_memory():
593
# With INITIAL_HEAP, we only know the lower bound on initial memory size.
594
initial_memory_known = settings.INITIAL_MEMORY != -1
595
596
if not settings.ALLOW_MEMORY_GROWTH:
597
if 'MAXIMUM_MEMORY' in user_settings:
598
diagnostics.warning('unused-command-line-argument', 'MAXIMUM_MEMORY is only meaningful with ALLOW_MEMORY_GROWTH')
599
# Optimization: lower the default maximum memory to initial memory if possible.
600
if initial_memory_known:
601
settings.MAXIMUM_MEMORY = settings.INITIAL_MEMORY
602
603
# Automatically up the default maximum when the user requested a large minimum.
604
if 'MAXIMUM_MEMORY' not in user_settings:
605
if settings.ALLOW_MEMORY_GROWTH:
606
if any([settings.INITIAL_HEAP != -1 and settings.INITIAL_HEAP >= 2 * 1024 * 1024 * 1024,
607
initial_memory_known and settings.INITIAL_MEMORY > 2 * 1024 * 1024 * 1024]):
608
settings.MAXIMUM_MEMORY = 4 * 1024 * 1024 * 1024
609
610
# INITIAL_MEMORY sets a lower bound for MAXIMUM_MEMORY
611
if initial_memory_known and settings.INITIAL_MEMORY > settings.MAXIMUM_MEMORY:
612
settings.MAXIMUM_MEMORY = settings.INITIAL_MEMORY
613
614
# A similar check for INITIAL_HEAP would not be precise and so is delegated to wasm-ld.
615
if initial_memory_known and settings.MAXIMUM_MEMORY < settings.INITIAL_MEMORY:
616
exit_with_error('MAXIMUM_MEMORY cannot be less than INITIAL_MEMORY')
617
618
619
def inc_initial_memory(delta):
620
# Both INITIAL_HEAP and INITIAL_MEMORY can be set at the same time. Increment both.
621
if settings.INITIAL_HEAP != -1:
622
settings.INITIAL_HEAP += delta
623
if settings.INITIAL_MEMORY != -1:
624
settings.INITIAL_MEMORY += delta
625
626
627
def check_browser_versions():
628
# Map of setting all VM version settings to the minimum version
629
# we support.
630
min_version_settings = {
631
'MIN_FIREFOX_VERSION': feature_matrix.OLDEST_SUPPORTED_FIREFOX,
632
'MIN_CHROME_VERSION': feature_matrix.OLDEST_SUPPORTED_CHROME,
633
'MIN_SAFARI_VERSION': feature_matrix.OLDEST_SUPPORTED_SAFARI,
634
'MIN_NODE_VERSION': feature_matrix.OLDEST_SUPPORTED_NODE,
635
}
636
637
if settings.LEGACY_VM_SUPPORT:
638
# Default all browser versions to zero
639
for key in min_version_settings:
640
default_setting(key, 0)
641
642
for key, oldest in min_version_settings.items():
643
if settings[key] != 0 and settings[key] < oldest:
644
exit_with_error(f'{key} older than {oldest} is not supported')
645
646
647
def add_system_js_lib(lib):
648
lib = utils.path_from_root('src/lib', lib)
649
assert os.path.exists(lib)
650
settings.JS_LIBRARIES.append(lib)
651
652
653
def report_incompatible_settings():
654
# List of incompatible settings, of the form (SETTINGS_A, SETTING_B, OPTIONAL_REASON_FOR_INCOMPAT)
655
incompatible_settings = [
656
('MINIMAL_RUNTIME', 'RELOCATABLE', None),
657
('WASM2JS', 'RELOCATABLE', None),
658
('MODULARIZE', 'PROXY_TO_WORKER', 'if you want to run in a worker with -sMODULARIZE, you likely want to do the worker side setup manually'),
659
('MODULARIZE', 'NO_DECLARE_ASM_MODULE_EXPORTS', None),
660
('EVAL_CTORS', 'WASM2JS', None),
661
('EVAL_CTORS', 'RELOCATABLE', 'movable segments'),
662
# In Asyncify exports can be called more than once, and this seems to not
663
# work properly yet (see test_emscripten_scan_registers).
664
('EVAL_CTORS', 'ASYNCIFY', None),
665
('PTHREADS_PROFILING', 'NO_ASSERTIONS', 'only works with ASSERTIONS enabled'),
666
('SOURCE_PHASE_IMPORTS', 'NO_EXPORT_ES6', None),
667
('STANDALONE_WASM', 'MINIMAL_RUNTIME', None),
668
('STRICT_JS', 'MODULARIZE', None),
669
('STRICT_JS', 'EXPORT_ES6', None),
670
('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION', 'MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION', 'they are mutually exclusive'),
671
('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION', 'SINGLE_FILE', None),
672
('MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION', 'SINGLE_FILE', None),
673
('SEPARATE_DWARF', 'WASM2JS', 'as there is no wasm file'),
674
('GL_SUPPORT_AUTOMATIC_ENABLE_EXTENSIONS', 'NO_GL_SUPPORT_SIMPLE_ENABLE_EXTENSIONS', None),
675
('MODULARIZE', 'NODEJS_CATCH_REJECTION', None),
676
('MODULARIZE', 'NODEJS_CATCH_EXIT', None),
677
]
678
679
for a, b, reason in incompatible_settings:
680
invert_b = b.startswith('NO_')
681
if invert_b:
682
b = b[3:]
683
684
b_val = getattr(settings, b)
685
if invert_b:
686
b_val = not b_val
687
688
if getattr(settings, a) and b_val:
689
msg = f'{a} is not compatible with {b}'
690
if invert_b:
691
msg += '=0'
692
if reason:
693
msg += f' ({reason})'
694
exit_with_error(msg)
695
696
697
@ToolchainProfiler.profile_block('linker_setup')
698
def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
699
"""Future modifications should consider refactoring to reduce complexity.
700
701
* The McCabe cyclomatiic complexity is currently 244 vs 10 recommended.
702
* There are currently 252 branches vs 12 recommended.
703
* There are currently 563 statements vs 50 recommended.
704
705
To revalidate these numbers, run `ruff check --select=C901,PLR091`.
706
"""
707
708
setup_environment_settings()
709
710
apply_library_settings(linker_args)
711
linker_args += calc_extra_ldflags(options)
712
713
# We used to do this check during on startup during `check_sanity`, but
714
# we now only do it when linking, in order to reduce the overhead when
715
# only compiling.
716
if not shared.SKIP_SUBPROCS:
717
shared.check_llvm_version()
718
719
autoconf = os.environ.get('EMMAKEN_JUST_CONFIGURE') or 'conftest.c' in options.input_files or 'conftest.cpp' in options.input_files
720
if autoconf:
721
# configure tests want a more shell-like style, where we emit return codes on exit()
722
settings.EXIT_RUNTIME = 1
723
# use node.js raw filesystem access, to behave just like a native executable
724
settings.NODERAWFS = 1
725
# Add `#!` line to output JS and make it executable.
726
options.executable = True
727
# autoconf declares functions without their proper signatures, and STRICT causes that to trip up by passing --fatal-warnings to the linker.
728
if settings.STRICT:
729
exit_with_error('autoconfiguring is not compatible with STRICT')
730
731
if settings.OPT_LEVEL >= 1:
732
default_setting('ASSERTIONS', 0)
733
734
if options.emrun:
735
options.pre_js.append(utils.path_from_root('src/emrun_prejs.js'))
736
options.post_js.append(utils.path_from_root('src/emrun_postjs.js'))
737
if settings.MINIMAL_RUNTIME:
738
exit_with_error('--emrun is not compatible with MINIMAL_RUNTIME')
739
# emrun mode waits on program exit
740
if user_settings.get('EXIT_RUNTIME') == '0':
741
exit_with_error('--emrun is not compatible with EXIT_RUNTIME=0')
742
settings.EXIT_RUNTIME = 1
743
# emrun_postjs.js needs this library function.
744
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnExit']
745
746
if options.cpu_profiler:
747
options.post_js.append(utils.path_from_root('src/cpuprofiler.js'))
748
749
# Unless RUNTIME_DEBUG is explicitly set then we enable it when any of the
750
# more specific debug settings are present.
751
default_setting('RUNTIME_DEBUG', int(settings.LIBRARY_DEBUG or
752
settings.GL_DEBUG or
753
settings.DYLINK_DEBUG or
754
settings.OPENAL_DEBUG or
755
settings.SYSCALL_DEBUG or
756
settings.WEBSOCKET_DEBUG or
757
settings.SOCKET_DEBUG or
758
settings.FETCH_DEBUG or
759
settings.EXCEPTION_DEBUG or
760
settings.PTHREADS_DEBUG or
761
settings.ASYNCIFY_DEBUG))
762
763
if options.memory_profiler:
764
settings.MEMORYPROFILER = 1
765
766
if settings.PTHREADS_PROFILING:
767
options.post_js.append(utils.path_from_root('src/threadprofiler.js'))
768
settings.REQUIRED_EXPORTS.append('emscripten_main_runtime_thread_id')
769
# threadprofiler.js needs these library functions.
770
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnInit', '$addOnExit']
771
772
# TODO: support source maps with js_transform
773
if options.js_transform and settings.GENERATE_SOURCE_MAP:
774
logger.warning('disabling source maps because a js transform is being done')
775
settings.GENERATE_SOURCE_MAP = 0
776
777
# options.output_file is the user-specified one, target is what we will generate
778
if options.output_file:
779
target = options.output_file
780
# check for the existence of the output directory now, to avoid having
781
# to do so repeatedly when each of the various output files (.mem, .wasm,
782
# etc) are written. This gives a more useful error message than the
783
# IOError and python backtrace that users would otherwise see.
784
dirname = os.path.dirname(target)
785
if dirname and not os.path.isdir(dirname):
786
exit_with_error("specified output file (%s) is in a directory that does not exist" % target)
787
elif autoconf:
788
# Autoconf expects the executable output file to be called `a.out`
789
target = 'a.out'
790
elif settings.SIDE_MODULE:
791
target = 'a.out.wasm'
792
else:
793
target = 'a.out.js'
794
795
final_suffix = get_file_suffix(target)
796
797
for s, reason in DEPRECATED_SETTINGS.items():
798
if s in user_settings:
799
diagnostics.warning('deprecated', f'{s} is deprecated ({reason}). Please open a bug if you have a continuing need for this setting')
800
801
# Set the EXPORT_ES6 default early since it affects the setting of the
802
# default oformat below.
803
if settings.WASM_ESM_INTEGRATION or settings.SOURCE_PHASE_IMPORTS or settings.MODULARIZE == 'instance':
804
default_setting('EXPORT_ES6', 1)
805
806
# If no output format was specified we try to deduce the format based on
807
# the output filename extension
808
if not options.oformat and (options.relocatable or (options.shared and not settings.SIDE_MODULE)):
809
# Until we have a better story for actually producing runtime shared libraries
810
# we support a compatibility mode where shared libraries are actually just
811
# object files linked with `wasm-ld --relocatable` or `llvm-link` in the case
812
# of LTO.
813
if final_suffix in EXECUTABLE_EXTENSIONS:
814
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)
815
else:
816
if options.shared:
817
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.')
818
options.oformat = OFormat.OBJECT
819
820
if not options.oformat:
821
if settings.SIDE_MODULE or final_suffix == '.wasm':
822
options.oformat = OFormat.WASM
823
elif final_suffix == '.html':
824
options.oformat = OFormat.HTML
825
elif final_suffix == '.mjs' or settings.EXPORT_ES6:
826
options.oformat = OFormat.MJS
827
else:
828
options.oformat = OFormat.JS
829
830
if options.oformat in (OFormat.WASM, OFormat.OBJECT):
831
for s in JS_ONLY_SETTINGS:
832
if s in user_settings:
833
diagnostics.warning('unused-command-line-argument', f'{s} is only valid when generating JavaScript output')
834
835
if options.oformat == OFormat.MJS:
836
default_setting('EXPORT_ES6', 1)
837
838
settings.OUTPUT_FORMAT = options.oformat.name
839
840
if settings.JS_BASE64_API:
841
diagnostics.warning('experimental', '-sJS_BASE64_API is still experimental and not yet supported in browsers')
842
843
if settings.GROWABLE_ARRAYBUFFERS:
844
diagnostics.warning('experimental', '-sGROWABLE_ARRAYBUFFERS is still experimental and not yet supported in browsers')
845
846
if settings.SUPPORT_BIG_ENDIAN:
847
diagnostics.warning('experimental', '-sSUPPORT_BIG_ENDIAN is experimental, not all features are fully supported.')
848
849
if settings.WASM_ESM_INTEGRATION:
850
diagnostics.warning('experimental', '-sWASM_ESM_INTEGRATION is still experimental and not yet supported in browsers')
851
default_setting('MODULARIZE', 'instance')
852
if not settings.EXPORT_ES6:
853
exit_with_error('WASM_ESM_INTEGRATION requires EXPORT_ES6')
854
if settings.MODULARIZE != 'instance':
855
exit_with_error('WASM_ESM_INTEGRATION requires MODULARIZE=instance')
856
if settings.RELOCATABLE:
857
exit_with_error('WASM_ESM_INTEGRATION is not compatible with dynamic linking')
858
if settings.ASYNCIFY:
859
exit_with_error('WASM_ESM_INTEGRATION is not compatible with -sASYNCIFY')
860
if settings.WASM_WORKERS:
861
exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM_WORKERS')
862
if not settings.WASM_ASYNC_COMPILATION:
863
exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM_ASYNC_COMPILATION=0')
864
if not settings.WASM:
865
exit_with_error('WASM_ESM_INTEGRATION is not compatible with WASM2JS')
866
if settings.ABORT_ON_WASM_EXCEPTIONS:
867
exit_with_error('WASM_ESM_INTEGRATION is not compatible with ABORT_ON_WASM_EXCEPTIONS')
868
869
if settings.WASM_JS_TYPES:
870
diagnostics.warning('experimental', '-sWASM_JS_TYPES is only supported under a flag in certain browsers')
871
872
if settings.MODULARIZE and settings.MODULARIZE not in [1, 'instance']:
873
exit_with_error(f'Invalid setting "{settings.MODULARIZE}" for MODULARIZE.')
874
875
def limit_incoming_module_api():
876
if options.oformat == OFormat.HTML and options.shell_path == DEFAULT_SHELL_HTML:
877
# Out default shell.html file has minimal set of INCOMING_MODULE_JS_API elements that it expects
878
default_setting('INCOMING_MODULE_JS_API', 'canvas,monitorRunDependencies,onAbort,onExit,print,setStatus'.split(','))
879
else:
880
default_setting('INCOMING_MODULE_JS_API', [])
881
882
if settings.ASYNCIFY == 1:
883
# ASYNCIFY=1 wraps only wasm exports so we need to enable legacy
884
# dyncalls via dynCall_xxx exports.
885
# See: https://github.com/emscripten-core/emscripten/issues/12066
886
settings.DYNCALLS = 1
887
888
if settings.MODULARIZE == 'instance':
889
diagnostics.warning('experimental', 'MODULARIZE=instance is still experimental. Many features may not work or will change.')
890
if not settings.EXPORT_ES6:
891
exit_with_error('MODULARIZE=instance requires EXPORT_ES6')
892
if settings.ASYNCIFY_LAZY_LOAD_CODE:
893
exit_with_error('MODULARIZE=instance is not compatible with -sASYNCIFY_LAZY_LOAD_CODE')
894
if settings.MINIMAL_RUNTIME:
895
exit_with_error('MODULARIZE=instance is not compatible with MINIMAL_RUNTIME')
896
if options.use_preload_plugins or len(options.preload_files):
897
exit_with_error('MODULARIZE=instance is not compatible with --embed-file/--preload-file')
898
899
if settings.MINIMAL_RUNTIME and len(options.preload_files):
900
exit_with_error('MINIMAL_RUNTIME is not compatible with --preload-file')
901
902
if options.oformat in (OFormat.WASM, OFormat.BARE):
903
if options.emit_tsd:
904
exit_with_error('Wasm only output is not compatible --emit-tsd')
905
# If the user asks directly for a wasm file then this *is* the target
906
wasm_target = target
907
elif settings.SINGLE_FILE or settings.WASM == 0:
908
# In SINGLE_FILE or WASM2JS mode the wasm file is not part of the output at
909
# all so we generate it the temp directory.
910
wasm_target = in_temp(shared.replace_suffix(target, '.wasm'))
911
else:
912
# Otherwise the wasm file is produced alongside the final target.
913
wasm_target = get_secondary_target(target, '.wasm')
914
915
if settings.SAFE_HEAP not in [0, 1, 2]:
916
exit_with_error('SAFE_HEAP must be 0, 1 or 2')
917
918
if not settings.WASM:
919
# When the user requests non-wasm output, we enable wasm2js. that is,
920
# we still compile to wasm normally, but we compile the final output
921
# to js.
922
settings.WASM = 1
923
settings.WASM2JS = 1
924
925
if settings.WASM == 2:
926
# Requesting both Wasm and Wasm2JS support
927
settings.WASM2JS = 1
928
929
if settings.WASM2JS:
930
# Wasm bigint doesn't make sense with wasm2js, since it controls how the
931
# wasm and JS interact.
932
if user_settings.get('WASM_BIGINT') and settings.WASM_BIGINT:
933
exit_with_error('WASM_BIGINT=1 is not compatible with wasm2js')
934
settings.WASM_BIGINT = 0
935
feature_matrix.disable_feature(feature_matrix.Feature.JS_BIGINT_INTEGRATION)
936
937
if options.oformat == OFormat.WASM and not settings.SIDE_MODULE:
938
# if the output is just a wasm file, it will normally be a standalone one,
939
# as there is no JS. an exception are side modules, as we can't tell at
940
# compile time whether JS will be involved or not - the main module may
941
# have JS, and the side module is expected to link against that.
942
# we also do not support standalone mode in fastcomp.
943
settings.STANDALONE_WASM = 1
944
945
if settings.LZ4:
946
settings.EXPORTED_RUNTIME_METHODS += ['LZ4']
947
948
if settings.PURE_WASI:
949
settings.STANDALONE_WASM = 1
950
settings.WASM_BIGINT = 1
951
# WASI does not support Emscripten (JS-based) exception catching, which the
952
# JS-based longjmp support also uses. Emscripten EH is by default disabled
953
# so we don't need to do anything here.
954
if not settings.WASM_EXCEPTIONS:
955
default_setting('SUPPORT_LONGJMP', 0)
956
957
if options.no_entry:
958
settings.EXPECT_MAIN = 0
959
elif settings.STANDALONE_WASM:
960
if '_main' in settings.EXPORTED_FUNCTIONS:
961
# TODO(sbc): Make this into a warning?
962
logger.debug('including `_main` in EXPORTED_FUNCTIONS is not necessary in standalone mode')
963
else:
964
# In normal non-standalone mode we have special handling of `_main` in EXPORTED_FUNCTIONS.
965
# 1. If the user specifies exports, but doesn't include `_main` we assume they want to build a
966
# reactor.
967
# 2. If the user doesn't export anything we default to exporting `_main` (unless `--no-entry`
968
# is specified (see above).
969
if 'EXPORTED_FUNCTIONS' in user_settings:
970
if '_main' in settings.USER_EXPORTS:
971
settings.EXPORTED_FUNCTIONS.remove('_main')
972
settings.EXPORT_IF_DEFINED.append('main')
973
else:
974
settings.EXPECT_MAIN = 0
975
else:
976
settings.EXPORT_IF_DEFINED.append('main')
977
978
if settings.STANDALONE_WASM:
979
# In STANDALONE_WASM mode we either build a command or a reactor.
980
# See https://github.com/WebAssembly/WASI/blob/main/design/application-abi.md
981
# For a command we always want EXIT_RUNTIME=1
982
# For a reactor we always want EXIT_RUNTIME=0
983
if 'EXIT_RUNTIME' in user_settings:
984
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).')
985
settings.EXIT_RUNTIME = settings.EXPECT_MAIN
986
settings.IGNORE_MISSING_MAIN = 0
987
# the wasm must be runnable without the JS, so there cannot be anything that
988
# requires JS legalization
989
default_setting('LEGALIZE_JS_FFI', 0)
990
if 'MEMORY_GROWTH_LINEAR_STEP' in user_settings:
991
exit_with_error('MEMORY_GROWTH_LINEAR_STEP is not compatible with STANDALONE_WASM')
992
if 'MEMORY_GROWTH_GEOMETRIC_CAP' in user_settings:
993
exit_with_error('MEMORY_GROWTH_GEOMETRIC_CAP is not compatible with STANDALONE_WASM')
994
995
# Note the exports the user requested
996
building.user_requested_exports.update(settings.EXPORTED_FUNCTIONS)
997
998
if '_main' in settings.EXPORTED_FUNCTIONS or 'main' in settings.EXPORT_IF_DEFINED:
999
settings.EXPORT_IF_DEFINED.append('__main_argc_argv')
1000
elif settings.ASSERTIONS and not settings.STANDALONE_WASM:
1001
# In debug builds when `main` is not explicitly requested as an
1002
# export we still add it to EXPORT_IF_DEFINED so that we can warn
1003
# users who forget to explicitly export `main`.
1004
# See other.test_warn_unexported_main.
1005
# This is not needed in STANDALONE_WASM mode since we export _start
1006
# (unconditionally) rather than main.
1007
settings.EXPORT_IF_DEFINED += ['main', '__main_argc_argv']
1008
1009
if settings.ASSERTIONS:
1010
# Exceptions are thrown with a stack trace by default when ASSERTIONS is
1011
# set and when building with either -fexceptions or -fwasm-exceptions.
1012
if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES:
1013
exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled')
1014
if settings.WASM_EXCEPTIONS or not settings.DISABLE_EXCEPTION_CATCHING:
1015
settings.EXCEPTION_STACK_TRACES = 1
1016
1017
# -sASSERTIONS implies basic stack overflow checks, and ASSERTIONS=2
1018
# implies full stack overflow checks. However, we don't set this default in
1019
# PURE_WASI, or when we are linking without standard libraries because
1020
# STACK_OVERFLOW_CHECK depends on emscripten_stack_get_end which is defined
1021
# in libcompiler-rt.
1022
if not settings.PURE_WASI and not options.nostdlib and not options.nodefaultlibs:
1023
default_setting('STACK_OVERFLOW_CHECK', max(settings.ASSERTIONS, settings.STACK_OVERFLOW_CHECK))
1024
1025
# For users that opt out of WARN_ON_UNDEFINED_SYMBOLS we assume they also
1026
# want to opt out of ERROR_ON_UNDEFINED_SYMBOLS.
1027
if user_settings.get('WARN_ON_UNDEFINED_SYMBOLS') == '0':
1028
default_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0)
1029
1030
# It is unlikely that developers targeting "native web" APIs with MINIMAL_RUNTIME need
1031
# errno support by default.
1032
if settings.MINIMAL_RUNTIME:
1033
# Require explicit -lfoo.js flags to link with JS libraries.
1034
default_setting('AUTO_JS_LIBRARIES', 0)
1035
# When using MINIMAL_RUNTIME, symbols should only be exported if requested.
1036
default_setting('EXPORT_KEEPALIVE', 0)
1037
1038
if settings.EXPORT_ES6 and not settings.MODULARIZE:
1039
# EXPORT_ES6 requires output to be a module
1040
if 'MODULARIZE' in user_settings:
1041
exit_with_error('EXPORT_ES6 requires MODULARIZE to be set')
1042
settings.MODULARIZE = 1
1043
1044
if not options.shell_path:
1045
# Minimal runtime uses a different default shell file
1046
if settings.MINIMAL_RUNTIME:
1047
options.shell_path = options.shell_path = utils.path_from_root('src/shell_minimal_runtime.html')
1048
else:
1049
options.shell_path = DEFAULT_SHELL_HTML
1050
1051
if settings.STRICT:
1052
if not settings.MODULARIZE:
1053
default_setting('STRICT_JS', 1)
1054
default_setting('DEFAULT_TO_CXX', 0)
1055
default_setting('IGNORE_MISSING_MAIN', 0)
1056
default_setting('AUTO_NATIVE_LIBRARIES', 0)
1057
if settings.MAIN_MODULE != 1:
1058
# These two settings cannot be disabled with MAIN_MODULE=1 because all symbols
1059
# are needed in this mode.
1060
default_setting('AUTO_JS_LIBRARIES', 0)
1061
default_setting('ALLOW_UNIMPLEMENTED_SYSCALLS', 0)
1062
limit_incoming_module_api()
1063
1064
for prop in settings.INCOMING_MODULE_JS_API:
1065
if prop not in settings.ALL_INCOMING_MODULE_JS_API:
1066
diagnostics.warning('unused-command-line-argument', f'invalid entry in INCOMING_MODULE_JS_API: {prop}')
1067
1068
if 'noExitRuntime' in settings.INCOMING_MODULE_JS_API:
1069
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$noExitRuntime')
1070
1071
# Default to TEXTDECODER=2 (always use TextDecoder to decode UTF-8 strings)
1072
# in -Oz builds, since custom decoder for UTF-8 takes up space.
1073
# When supporting shell environments, do not do this as TextDecoder is not
1074
# widely supported there.
1075
# In Audio Worklets TextDecoder API is intentionally not exposed
1076
# (https://github.com/WebAudio/web-audio-api/issues/2499) so we also need to
1077
# keep the JavaScript-based fallback.
1078
if settings.SHRINK_LEVEL >= 2 and not settings.AUDIO_WORKLET and \
1079
not settings.ENVIRONMENT_MAY_BE_SHELL:
1080
default_setting('TEXTDECODER', 2)
1081
1082
# If set to 1, we will run the autodebugger (the automatic debugging tool, see
1083
# tools/autodebugger). Note that this will disable inclusion of libraries. This
1084
# is useful because including dlmalloc makes it hard to compare native and js
1085
# builds
1086
if os.environ.get('EMCC_AUTODEBUG'):
1087
settings.AUTODEBUG = 1
1088
1089
# Use settings
1090
1091
if settings.WASM == 2 and settings.SINGLE_FILE:
1092
exit_with_error('cannot have both WASM=2 and SINGLE_FILE enabled at the same time')
1093
1094
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and options.oformat != OFormat.HTML:
1095
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output')
1096
1097
if settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION and not settings.MINIMAL_RUNTIME:
1098
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION requires MINIMAL_RUNTIME')
1099
1100
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and not settings.MINIMAL_RUNTIME:
1101
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION requires MINIMAL_RUNTIME')
1102
1103
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION and options.oformat != OFormat.HTML:
1104
exit_with_error('MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output')
1105
1106
if options.use_closure_compiler:
1107
settings.USE_CLOSURE_COMPILER = 1
1108
1109
if 'CLOSURE_WARNINGS' in user_settings:
1110
if settings.CLOSURE_WARNINGS not in ['quiet', 'warn', 'error']:
1111
exit_with_error('invalid option -sCLOSURE_WARNINGS=%s specified! Allowed values are "quiet", "warn" or "error".' % settings.CLOSURE_WARNINGS)
1112
closure_warnings = diagnostics.manager.warnings['closure']
1113
if settings.CLOSURE_WARNINGS == 'error':
1114
closure_warnings['error'] = True
1115
closure_warnings['enabled'] = True
1116
elif settings.CLOSURE_WARNINGS == 'warn':
1117
closure_warnings['error'] = False
1118
closure_warnings['enabled'] = True
1119
elif settings.CLOSURE_WARNINGS == 'quiet':
1120
closure_warnings['error'] = False
1121
closure_warnings['enabled'] = False
1122
1123
if not settings.MINIMAL_RUNTIME:
1124
if not settings.BOOTSTRAPPING_STRUCT_INFO:
1125
if settings.DYNCALLS:
1126
# Include dynCall() function by default in DYNCALLS builds in classic runtime; in MINIMAL_RUNTIME, must add this explicitly.
1127
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$dynCall']
1128
1129
if settings.ASSERTIONS:
1130
# "checkUnflushedContent()" and "missingLibrarySymbol()" depend on warnOnce
1131
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$warnOnce']
1132
1133
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getValue', '$setValue']
1134
1135
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$ExitStatus']
1136
1137
# Certain configurations require the removeRunDependency/addRunDependency system.
1138
if settings.LOAD_SOURCE_MAP or settings.PROXY_TO_WORKER or (settings.WASM_ASYNC_COMPILATION and not settings.MODULARIZE):
1139
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addRunDependency', '$removeRunDependency']
1140
1141
if settings.ABORT_ON_WASM_EXCEPTIONS or settings.SPLIT_MODULE:
1142
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$wasmTable']
1143
1144
if settings.MAIN_MODULE:
1145
assert not settings.SIDE_MODULE
1146
if settings.MAIN_MODULE == 1:
1147
settings.INCLUDE_FULL_LIBRARY = 1
1148
# Called from preamble.js once the main module is instantiated.
1149
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$loadDylibs']
1150
1151
if settings.MAIN_MODULE == 1 or settings.SIDE_MODULE == 1:
1152
settings.LINKABLE = 1
1153
1154
if settings.LINKABLE and settings.USER_EXPORTS:
1155
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')
1156
1157
if settings.MAIN_MODULE:
1158
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1159
'$getDylinkMetadata',
1160
'$mergeLibSymbols',
1161
]
1162
1163
if settings.PTHREADS:
1164
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1165
'$registerTLSInit',
1166
]
1167
1168
if settings.RELOCATABLE:
1169
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1170
'$reportUndefinedSymbols',
1171
'$relocateExports',
1172
'$GOTHandler',
1173
'__heap_base',
1174
'__stack_pointer',
1175
]
1176
1177
if settings.ASYNCIFY == 1:
1178
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1179
'__asyncify_state',
1180
'__asyncify_data',
1181
]
1182
1183
# shared modules need memory utilities to allocate their memory
1184
settings.ALLOW_TABLE_GROWTH = 1
1185
1186
# various settings require sbrk() access
1187
if settings.DETERMINISTIC or \
1188
settings.EMSCRIPTEN_TRACING or \
1189
settings.SAFE_HEAP or \
1190
settings.MEMORYPROFILER:
1191
settings.REQUIRED_EXPORTS += ['sbrk']
1192
1193
if settings.MEMORYPROFILER:
1194
settings.REQUIRED_EXPORTS += ['__heap_base',
1195
'emscripten_stack_get_base',
1196
'emscripten_stack_get_end',
1197
'emscripten_stack_get_current']
1198
1199
if settings.ASYNCIFY_LAZY_LOAD_CODE:
1200
settings.ASYNCIFY = 1
1201
1202
settings.ASYNCIFY_ADD = unmangle_symbols_from_cmdline(settings.ASYNCIFY_ADD)
1203
settings.ASYNCIFY_REMOVE = unmangle_symbols_from_cmdline(settings.ASYNCIFY_REMOVE)
1204
settings.ASYNCIFY_ONLY = unmangle_symbols_from_cmdline(settings.ASYNCIFY_ONLY)
1205
1206
if settings.EMULATE_FUNCTION_POINTER_CASTS:
1207
# Emulated casts forces a wasm ABI of (i64, i64, ...) in the table, which
1208
# means all table functions are illegal for JS to call directly. Use
1209
# dyncalls which call into the wasm, which then does an indirect call.
1210
settings.DYNCALLS = 1
1211
1212
if options.oformat != OFormat.OBJECT and final_suffix in ('.o', '.bc', '.so', '.dylib') and not settings.SIDE_MODULE:
1213
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)
1214
1215
if settings.SUPPORT_BIG_ENDIAN:
1216
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1217
'$LE_HEAP_STORE_U16',
1218
'$LE_HEAP_STORE_I16',
1219
'$LE_HEAP_STORE_U32',
1220
'$LE_HEAP_STORE_I32',
1221
'$LE_HEAP_STORE_F32',
1222
'$LE_HEAP_STORE_F64',
1223
'$LE_HEAP_LOAD_U16',
1224
'$LE_HEAP_LOAD_I16',
1225
'$LE_HEAP_LOAD_U32',
1226
'$LE_HEAP_LOAD_I32',
1227
'$LE_HEAP_LOAD_F32',
1228
'$LE_HEAP_LOAD_F64',
1229
'$LE_ATOMICS_NATIVE_BYTE_ORDER',
1230
'$LE_ATOMICS_ADD',
1231
'$LE_ATOMICS_AND',
1232
'$LE_ATOMICS_COMPAREEXCHANGE',
1233
'$LE_ATOMICS_EXCHANGE',
1234
'$LE_ATOMICS_ISLOCKFREE',
1235
'$LE_ATOMICS_LOAD',
1236
'$LE_ATOMICS_NOTIFY',
1237
'$LE_ATOMICS_OR',
1238
'$LE_ATOMICS_STORE',
1239
'$LE_ATOMICS_SUB',
1240
'$LE_ATOMICS_WAIT',
1241
'$LE_ATOMICS_WAITASYNC',
1242
'$LE_ATOMICS_XOR',
1243
]
1244
1245
if settings.RUNTIME_DEBUG or settings.ASSERTIONS or settings.STACK_OVERFLOW_CHECK or settings.PTHREADS_PROFILING or settings.GL_ASSERTIONS:
1246
# Lots of code in debug/assertion blocks uses ptrToString.
1247
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$ptrToString']
1248
1249
if settings.STACK_OVERFLOW_CHECK:
1250
settings.REQUIRED_EXPORTS += [
1251
'emscripten_stack_get_end',
1252
'emscripten_stack_get_free',
1253
'emscripten_stack_get_base',
1254
'emscripten_stack_get_current',
1255
]
1256
1257
# We call one of these two functions during startup which caches the stack limits
1258
# in wasm globals allowing get_base/get_free to be super fast.
1259
# See compiler-rt/stack_limits.S.
1260
if settings.RELOCATABLE:
1261
settings.REQUIRED_EXPORTS += ['emscripten_stack_set_limits']
1262
else:
1263
settings.REQUIRED_EXPORTS += ['emscripten_stack_init']
1264
1265
if settings.STACK_OVERFLOW_CHECK >= 2:
1266
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$setStackLimits']
1267
1268
check_browser_versions()
1269
1270
if settings.MIN_NODE_VERSION >= 150000:
1271
default_setting('NODEJS_CATCH_REJECTION', 0)
1272
1273
# Do not catch rejections or exits in modularize mode, as these options
1274
# are for use when running emscripten modules standalone
1275
# see https://github.com/emscripten-core/emscripten/issues/18723#issuecomment-1429236996
1276
if settings.MODULARIZE:
1277
default_setting('NODEJS_CATCH_REJECTION', 0)
1278
default_setting('NODEJS_CATCH_EXIT', 0)
1279
1280
if settings.POLYFILL:
1281
# Emscripten requires certain ES6+ constructs by default in library code
1282
# - (various ES6 operators available in all browsers listed below)
1283
# - https://caniuse.com/mdn-javascript_operators_nullish_coalescing:
1284
# FF:72 CHROME:80 SAFARI:13.1 NODE:14
1285
# - https://caniuse.com/mdn-javascript_operators_optional_chaining:
1286
# FF:74 CHROME:80 SAFARI:13.1 NODE:14
1287
# - https://caniuse.com/mdn-javascript_operators_logical_or_assignment:
1288
# FF:79 CHROME:85 SAFARI:14 NODE:16
1289
# Taking the highest requirements gives is our minimum:
1290
# Max Version: FF:79 CHROME:85 SAFARI:14 NODE:16
1291
# TODO: replace this with feature matrix in the future.
1292
settings.TRANSPILE = (settings.MIN_FIREFOX_VERSION < 79 or
1293
settings.MIN_CHROME_VERSION < 85 or
1294
settings.MIN_SAFARI_VERSION < 140000 or
1295
settings.MIN_NODE_VERSION < 160000)
1296
1297
if settings.STB_IMAGE:
1298
settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free']
1299
1300
if settings.USE_WEBGL2:
1301
settings.MAX_WEBGL_VERSION = 2
1302
1303
# MIN_WEBGL_VERSION=2 implies MAX_WEBGL_VERSION=2
1304
if settings.MIN_WEBGL_VERSION == 2:
1305
default_setting('MAX_WEBGL_VERSION', 2)
1306
1307
if settings.MIN_WEBGL_VERSION > settings.MAX_WEBGL_VERSION:
1308
exit_with_error('MIN_WEBGL_VERSION must be smaller or equal to MAX_WEBGL_VERSION!')
1309
1310
if options.use_preload_plugins or len(options.preload_files) or len(options.embed_files):
1311
if settings.NODERAWFS:
1312
exit_with_error('--preload-file and --embed-file cannot be used with NODERAWFS which disables virtual filesystem')
1313
# if we include any files, or intend to use preload plugins, then we definitely need filesystem support
1314
settings.FORCE_FILESYSTEM = 1
1315
1316
if options.preload_files:
1317
# File preloading uses `Module['preRun']`.
1318
settings.INCOMING_MODULE_JS_API.append('preRun')
1319
1320
if settings.FORCE_FILESYSTEM and not settings.FILESYSTEM:
1321
exit_with_error('`-sFORCE_FILESYSTEM` cannot be used with `-sFILESYSTEM=0`')
1322
1323
if settings.WASMFS:
1324
settings.FILESYSTEM = 1
1325
settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
1326
add_system_js_lib('libwasmfs.js')
1327
if settings.ASSERTIONS:
1328
# used in assertion checks for unflushed content
1329
settings.REQUIRED_EXPORTS += ['wasmfs_flush']
1330
if settings.FORCE_FILESYSTEM or settings.INCLUDE_FULL_LIBRARY:
1331
# Add exports for the JS API. Like the old JS FS, WasmFS by default
1332
# includes just what JS parts it actually needs, and FORCE_FILESYSTEM is
1333
# required to force all of it to be included if the user wants to use the
1334
# JS API directly. (INCLUDE_FULL_LIBRARY also causes this code to be
1335
# included, as the entire JS library can refer to things that require
1336
# these exports.)
1337
settings.REQUIRED_EXPORTS += [
1338
'emscripten_builtin_memalign',
1339
'wasmfs_create_file',
1340
'wasmfs_unmount',
1341
'_wasmfs_mount',
1342
'_wasmfs_read_file',
1343
'_wasmfs_write_file',
1344
'_wasmfs_open',
1345
'_wasmfs_close',
1346
'_wasmfs_write',
1347
'_wasmfs_pwrite',
1348
'_wasmfs_rename',
1349
'_wasmfs_mkdir',
1350
'_wasmfs_unlink',
1351
'_wasmfs_chdir',
1352
'_wasmfs_mknod',
1353
'_wasmfs_rmdir',
1354
'_wasmfs_mmap',
1355
'_wasmfs_munmap',
1356
'_wasmfs_msync',
1357
'_wasmfs_read',
1358
'_wasmfs_pread',
1359
'_wasmfs_symlink',
1360
'_wasmfs_truncate',
1361
'_wasmfs_ftruncate',
1362
'_wasmfs_stat',
1363
'_wasmfs_lstat',
1364
'_wasmfs_chmod',
1365
'_wasmfs_fchmod',
1366
'_wasmfs_lchmod',
1367
'_wasmfs_utime',
1368
'_wasmfs_llseek',
1369
'_wasmfs_identify',
1370
'_wasmfs_readlink',
1371
'_wasmfs_readdir_start',
1372
'_wasmfs_readdir_get',
1373
'_wasmfs_readdir_finish',
1374
'_wasmfs_get_cwd',
1375
]
1376
1377
if settings.FULL_ES3:
1378
settings.FULL_ES2 = 1
1379
settings.MAX_WEBGL_VERSION = max(2, settings.MAX_WEBGL_VERSION)
1380
1381
if settings.MAIN_READS_PARAMS and not settings.STANDALONE_WASM:
1382
# callMain depends on _emscripten_stack_alloc
1383
settings.REQUIRED_EXPORTS += ['_emscripten_stack_alloc']
1384
1385
if settings.SUPPORT_LONGJMP == 'emscripten' or not settings.DISABLE_EXCEPTION_CATCHING:
1386
# make_invoke depends on stackSave and stackRestore
1387
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stackSave', '$stackRestore']
1388
1389
if settings.RELOCATABLE:
1390
# TODO(https://reviews.llvm.org/D128515): Make this mandatory once
1391
# llvm change lands
1392
settings.EXPORT_IF_DEFINED.append('__wasm_apply_data_relocs')
1393
1394
if settings.SIDE_MODULE and 'GLOBAL_BASE' in user_settings:
1395
diagnostics.warning('unused-command-line-argument', 'GLOBAL_BASE is not compatible with SIDE_MODULE')
1396
1397
if settings.PROXY_TO_WORKER or options.use_preload_plugins:
1398
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$Browser']
1399
1400
if not settings.BOOTSTRAPPING_STRUCT_INFO:
1401
if settings.DYNAMIC_EXECUTION == 2 and not settings.MINIMAL_RUNTIME:
1402
# Used by makeEval in the DYNAMIC_EXECUTION == 2 case
1403
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stackTrace']
1404
1405
if not settings.STANDALONE_WASM and (settings.EXIT_RUNTIME or settings.ASSERTIONS):
1406
# to flush streams on FS exit, we need to be able to call fflush
1407
# we only include it if the runtime is exitable, or when ASSERTIONS
1408
# (ASSERTIONS will check that streams do not need to be flushed,
1409
# helping people see when they should have enabled EXIT_RUNTIME)
1410
settings.EXPORT_IF_DEFINED += ['fflush']
1411
1412
if settings.SAFE_HEAP:
1413
# SAFE_HEAP check includes calling emscripten_get_sbrk_ptr() from wasm
1414
settings.REQUIRED_EXPORTS += ['emscripten_get_sbrk_ptr', 'emscripten_stack_get_base']
1415
1416
if not settings.DECLARE_ASM_MODULE_EXPORTS:
1417
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$exportWasmSymbols']
1418
1419
if settings.ALLOW_MEMORY_GROWTH:
1420
# Setting ALLOW_MEMORY_GROWTH turns off ABORTING_MALLOC, as in that mode we default to
1421
# the behavior of trying to grow and returning 0 from malloc on failure, like
1422
# a standard system would. However, if the user sets the flag it
1423
# overrides that.
1424
default_setting('ABORTING_MALLOC', 0)
1425
1426
if settings.EMBIND:
1427
# Workaround for embind+LTO issue:
1428
# https://github.com/emscripten-core/emscripten/issues/21653
1429
settings.REQUIRED_EXPORTS.append('__getTypeName')
1430
if settings.PTHREADS or settings.WASM_WORKERS:
1431
settings.REQUIRED_EXPORTS.append('_embind_initialize_bindings')
1432
# Needed to assign the embind exports to the ES exports.
1433
if settings.MODULARIZE == 'instance':
1434
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$addOnPostCtor']
1435
1436
if options.emit_tsd:
1437
settings.EMIT_TSD = True
1438
1439
if settings.PTHREADS:
1440
setup_pthreads()
1441
add_system_js_lib('libpthread.js')
1442
if settings.PROXY_TO_PTHREAD:
1443
settings.PTHREAD_POOL_SIZE_STRICT = 0
1444
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$runtimeKeepalivePush']
1445
else:
1446
if settings.PROXY_TO_PTHREAD:
1447
exit_with_error('-sPROXY_TO_PTHREAD requires -pthread to work!')
1448
add_system_js_lib('libpthread_stub.js')
1449
1450
if settings.MEMORY64:
1451
# Any "pointers" passed to JS will now be i64's, in both modes.
1452
settings.WASM_BIGINT = 1
1453
1454
if settings.MEMORY64 and settings.RELOCATABLE:
1455
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('__table_base32')
1456
1457
if settings.WASM_WORKERS:
1458
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$_wasmWorkerInitializeRuntime']
1459
add_system_js_lib('libwasm_worker.js')
1460
1461
# Set min browser versions based on certain settings such as WASM_BIGINT,
1462
# PTHREADS, AUDIO_WORKLET
1463
# Such setting must be set before this point
1464
feature_matrix.apply_min_browser_versions()
1465
1466
# TODO(sbc): Find make a generic way to expose the feature matrix to JS
1467
# compiler rather then adding them all ad-hoc as internal settings
1468
settings.SUPPORTS_GLOBALTHIS = feature_matrix.caniuse(feature_matrix.Feature.GLOBALTHIS)
1469
settings.SUPPORTS_PROMISE_ANY = feature_matrix.caniuse(feature_matrix.Feature.PROMISE_ANY)
1470
if not settings.BULK_MEMORY:
1471
settings.BULK_MEMORY = feature_matrix.caniuse(feature_matrix.Feature.BULK_MEMORY)
1472
default_setting('WASM_BIGINT', feature_matrix.caniuse(feature_matrix.Feature.JS_BIGINT_INTEGRATION))
1473
1474
if settings.AUDIO_WORKLET:
1475
add_system_js_lib('libwebaudio.js')
1476
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$getWasmTableEntry')
1477
1478
if not settings.MINIMAL_RUNTIME:
1479
if 'preRun' in settings.INCOMING_MODULE_JS_API:
1480
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$addOnPreRun')
1481
if 'postRun' in settings.INCOMING_MODULE_JS_API:
1482
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$addOnPostRun')
1483
1484
if settings.FORCE_FILESYSTEM and not settings.MINIMAL_RUNTIME:
1485
# when the filesystem is forced, we export by default methods that filesystem usage
1486
# may need, including filesystem usage from standalone file packager output (i.e.
1487
# file packages not built together with emcc, but that are loaded at runtime
1488
# separately, and they need emcc's output to contain the support they need)
1489
settings.EXPORTED_RUNTIME_METHODS += [
1490
'FS_createPath',
1491
'FS_createDataFile',
1492
'FS_preloadFile',
1493
'FS_unlink',
1494
]
1495
if not settings.WASMFS:
1496
# The old FS has some functionality that WasmFS lacks.
1497
settings.EXPORTED_RUNTIME_METHODS += [
1498
'FS_createLazyFile',
1499
'FS_createDevice',
1500
]
1501
1502
settings.EXPORTED_RUNTIME_METHODS += [
1503
'addRunDependency',
1504
'removeRunDependency',
1505
]
1506
1507
if settings.PTHREADS or settings.WASM_WORKERS or settings.RELOCATABLE or settings.ASYNCIFY_LAZY_LOAD_CODE:
1508
settings.IMPORTED_MEMORY = 1
1509
1510
set_initial_memory()
1511
1512
# When not declaring wasm module exports in outer scope one by one, disable minifying
1513
# wasm module export names so that the names can be passed directly to the outer scope.
1514
# Also, if using libexports.js API, disable minification so that the feature can work.
1515
if not settings.DECLARE_ASM_MODULE_EXPORTS or '-lexports.js' in linker_args:
1516
settings.MINIFY_WASM_EXPORT_NAMES = 0
1517
1518
# Enable minification of wasm imports and exports when appropriate, if we
1519
# are emitting an optimized JS+wasm combo (then the JS knows how to load the minified names).
1520
# Things that process the JS after this operation would be done must disable this.
1521
# For example, ASYNCIFY_LAZY_LOAD_CODE needs to identify import names.
1522
# ASYNCIFY=2 does not support this optimization yet as it has a hardcoded
1523
# check for 'main' as an export name. TODO
1524
if will_metadce() and \
1525
settings.OPT_LEVEL >= 2 and \
1526
settings.DEBUG_LEVEL <= 2 and \
1527
options.oformat not in (OFormat.WASM, OFormat.BARE) and \
1528
settings.ASYNCIFY != 2 and \
1529
not settings.LINKABLE and \
1530
not settings.STANDALONE_WASM and \
1531
not settings.AUTODEBUG and \
1532
not settings.ASSERTIONS and \
1533
not settings.RELOCATABLE and \
1534
not settings.ASYNCIFY_LAZY_LOAD_CODE and \
1535
settings.MINIFY_WASM_EXPORT_NAMES:
1536
settings.MINIFY_WASM_IMPORTS_AND_EXPORTS = 1
1537
settings.MINIFY_WASM_IMPORTED_MODULES = 1
1538
1539
if settings.MODULARIZE and not (settings.EXPORT_ES6 and not settings.SINGLE_FILE) and \
1540
settings.EXPORT_NAME == 'Module' and options.oformat == OFormat.HTML and \
1541
(options.shell_path == DEFAULT_SHELL_HTML or options.shell_path == utils.path_from_root('src/shell_minimal.html')):
1542
exit_with_error(f'Due to collision in variable name "Module", the shell file "{options.shell_path}" is not compatible with build options "-sMODULARIZE -sEXPORT_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)')
1543
1544
if settings.WASM_BIGINT:
1545
settings.LEGALIZE_JS_FFI = 0
1546
1547
if settings.SINGLE_FILE and settings.GENERATE_SOURCE_MAP:
1548
diagnostics.warning('emcc', 'SINGLE_FILE disables source map support (which requires a .map file)')
1549
settings.GENERATE_SOURCE_MAP = 0
1550
1551
if options.use_closure_compiler == 2 and not settings.WASM2JS:
1552
exit_with_error('closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm')
1553
1554
if settings.AUTODEBUG:
1555
settings.REQUIRED_EXPORTS += ['_emscripten_tempret_set']
1556
1557
if settings.LEGALIZE_JS_FFI:
1558
settings.REQUIRED_EXPORTS += ['__get_temp_ret', '__set_temp_ret']
1559
1560
if settings.SPLIT_MODULE and settings.ASYNCIFY == 2:
1561
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['_load_secondary_module']
1562
1563
# wasm side modules have suffix .wasm
1564
if settings.SIDE_MODULE and shared.suffix(target) in ('.js', '.mjs'):
1565
diagnostics.warning('emcc', 'JavaScript output suffix requested, but wasm side modules are just wasm files; emitting only a .wasm, no .js')
1566
1567
if options.sanitize:
1568
if settings.WASM_WORKERS:
1569
exit_with_error('WASM_WORKERS is not currently compatible with `-fsanitize` tools')
1570
# These symbols are needed by `withBuiltinMalloc` which used to implement
1571
# the `__noleakcheck` attribute. However this dependency is not yet represented in the JS
1572
# symbol graph generated when we run the compiler with `--symbols-only`.
1573
settings.REQUIRED_EXPORTS += [
1574
'malloc',
1575
'calloc',
1576
'memalign',
1577
'free',
1578
'emscripten_builtin_malloc',
1579
'emscripten_builtin_calloc',
1580
'emscripten_builtin_memalign',
1581
'emscripten_builtin_free',
1582
]
1583
1584
if ('leak' in options.sanitize or 'address' in options.sanitize) and not settings.ALLOW_MEMORY_GROWTH:
1585
# Increase the minimum memory requirements to account for extra memory
1586
# that the sanitizers might need (in addition to the shadow memory
1587
# requirements handled below).
1588
# These values are designed be an over-estimate of the actual requirements and
1589
# are based on experimentation with different tests/programs under asan and
1590
# lsan.
1591
inc_initial_memory(50 * 1024 * 1024)
1592
if settings.PTHREADS:
1593
inc_initial_memory(50 * 1024 * 1024)
1594
1595
if options.sanitize & UBSAN_SANITIZERS:
1596
if options.sanitize_minimal_runtime:
1597
settings.UBSAN_RUNTIME = 1
1598
else:
1599
settings.UBSAN_RUNTIME = 2
1600
1601
if 'leak' in options.sanitize:
1602
settings.USE_LSAN = 1
1603
default_setting('EXIT_RUNTIME', 1)
1604
1605
if 'address' in options.sanitize:
1606
settings.USE_ASAN = 1
1607
default_setting('EXIT_RUNTIME', 1)
1608
if not settings.UBSAN_RUNTIME:
1609
settings.UBSAN_RUNTIME = 2
1610
1611
settings.REQUIRED_EXPORTS += emscripten.ASAN_C_HELPERS
1612
1613
if settings.ASYNCIFY and not settings.ASYNCIFY_ONLY:
1614
# we do not want asyncify to instrument these helpers - they just access
1615
# memory as small getters/setters, so they cannot pause anyhow, and also
1616
# we access them in the runtime as we prepare to rewind, which would hit
1617
# an asyncify assertion, if asyncify instrumented them.
1618
#
1619
# note that if ASYNCIFY_ONLY was set by the user then we do not need to
1620
# do anything (as the user's list won't contain these functions), and if
1621
# we did add them, the pass would assert on incompatible lists, hence the
1622
# condition in the above if.
1623
settings.ASYNCIFY_REMOVE.append("__asan_*")
1624
1625
if settings.ASAN_SHADOW_SIZE != -1:
1626
diagnostics.warning('emcc', 'ASAN_SHADOW_SIZE is ignored and will be removed in a future release')
1627
1628
if 'GLOBAL_BASE' in user_settings:
1629
exit_with_error("ASan does not support custom GLOBAL_BASE")
1630
1631
# Increase the INITIAL_MEMORY and shift GLOBAL_BASE to account for
1632
# the ASan shadow region which starts at address zero.
1633
# The shadow region is 1/8th the size of the total memory and is
1634
# itself part of the total memory.
1635
# We use the following variables in this calculation:
1636
# - user_mem : memory usable/visible by the user program.
1637
# - shadow_size : memory used by asan for shadow memory.
1638
# - total_mem : the sum of the above. this is the size of the wasm memory (and must be aligned to WASM_PAGE_SIZE)
1639
user_mem = settings.MAXIMUM_MEMORY
1640
if not settings.ALLOW_MEMORY_GROWTH and settings.INITIAL_MEMORY != -1:
1641
user_mem = settings.INITIAL_MEMORY
1642
1643
# Given the know value of user memory size we can work backwards
1644
# to find the total memory and the shadow size based on the fact
1645
# that the user memory is 7/8ths of the total memory.
1646
# (i.e. user_mem == total_mem * 7 / 8
1647
# TODO-Bug?: this does not look to handle 4GB MAXIMUM_MEMORY correctly.
1648
total_mem = user_mem * 8 / 7
1649
1650
# But we might need to re-align to wasm page size
1651
total_mem = int(align_to_wasm_page_boundary(total_mem))
1652
1653
# The shadow size is 1/8th the resulting rounded up size
1654
shadow_size = total_mem // 8
1655
1656
# We start our global data after the shadow memory.
1657
# We don't need to worry about alignment here. wasm-ld will take care of that.
1658
settings.GLOBAL_BASE = shadow_size
1659
1660
# Adjust INITIAL_MEMORY (if needed) to account for the shifted global base.
1661
if settings.INITIAL_MEMORY != -1:
1662
if settings.ALLOW_MEMORY_GROWTH:
1663
settings.INITIAL_MEMORY += align_to_wasm_page_boundary(shadow_size)
1664
else:
1665
settings.INITIAL_MEMORY = total_mem
1666
1667
if settings.SAFE_HEAP:
1668
# SAFE_HEAP instruments ASan's shadow memory accesses.
1669
# Since the shadow memory starts at 0, the act of accessing the shadow memory is detected
1670
# by SAFE_HEAP as a null pointer dereference.
1671
exit_with_error('ASan does not work with SAFE_HEAP')
1672
1673
if settings.MEMORY64:
1674
exit_with_error('MEMORY64 does not yet work with ASAN')
1675
1676
if settings.USE_ASAN or settings.SAFE_HEAP:
1677
# ASan and SAFE_HEAP check address 0 themselves
1678
settings.CHECK_NULL_WRITES = 0
1679
1680
if options.sanitize and settings.GENERATE_SOURCE_MAP:
1681
settings.LOAD_SOURCE_MAP = 1
1682
1683
if 'GLOBAL_BASE' not in user_settings and not settings.SHRINK_LEVEL and not settings.OPT_LEVEL and not settings.USE_ASAN:
1684
# When optimizing for size it helps to put static data first before
1685
# the stack (since this makes instructions for accessing this data
1686
# use a smaller LEB encoding).
1687
# However, for debugability is better to have the stack come first
1688
# (because stack overflows will trap rather than corrupting data).
1689
settings.STACK_FIRST = True
1690
1691
if '--stack-first' in linker_args:
1692
settings.STACK_FIRST = True
1693
if settings.USE_ASAN:
1694
exit_with_error('--stack-first is not compatible with asan')
1695
if 'GLOBAL_BASE' in user_settings:
1696
exit_with_error('--stack-first is not compatible with -sGLOBAL_BASE')
1697
1698
set_max_memory()
1699
1700
# check if we can address the 2GB mark and higher.
1701
if not settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 * 1024 * 1024 * 1024:
1702
settings.CAN_ADDRESS_2GB = 1
1703
1704
if settings.MAX_WEBGL_VERSION >= 2:
1705
settings.WEBGL_USE_GARBAGE_FREE_APIS = 1
1706
# Some browsers have issues using the WebGL2 garbage-free APIs when the
1707
# memory offsets are over 2^31 or 2^32
1708
# For firefox see: https://bugzilla.mozilla.org/show_bug.cgi?id=1838218
1709
if settings.MIN_FIREFOX_VERSION != feature_matrix.UNSUPPORTED and settings.MAXIMUM_MEMORY > 2 ** 31:
1710
settings.WEBGL_USE_GARBAGE_FREE_APIS = 0
1711
# For chrome see: https://crbug.com/324992397
1712
if settings.MIN_CHROME_VERSION != feature_matrix.UNSUPPORTED and settings.MEMORY64 and settings.MAXIMUM_MEMORY > 2 ** 32:
1713
settings.WEBGL_USE_GARBAGE_FREE_APIS = 0
1714
if settings.WEBGL_USE_GARBAGE_FREE_APIS and settings.MIN_WEBGL_VERSION >= 2:
1715
settings.INCLUDE_WEBGL1_FALLBACK = 0
1716
1717
if settings.MINIMAL_RUNTIME:
1718
if settings.EXIT_RUNTIME:
1719
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['proc_exit', '$callRuntimeCallbacks']
1720
else:
1721
# MINIMAL_RUNTIME only needs callRuntimeCallbacks in certain cases, but the normal runtime
1722
# always does.
1723
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks']
1724
1725
if settings.EXIT_RUNTIME and not settings.STANDALONE_WASM:
1726
# Internal function implemented in musl that calls any functions registered
1727
# via `atexit` et al. With STANDALONE_WASM this is all taken care of via
1728
# _start and exit handling in musl, but with the normal emscripten ABI we
1729
# need to be able to call these explicitly.
1730
settings.REQUIRED_EXPORTS += ['__funcs_on_exit']
1731
1732
# The worker code in src/postamble.js depends on realloc
1733
if settings.BUILD_AS_WORKER:
1734
settings.REQUIRED_EXPORTS += ['realloc']
1735
1736
if not settings.DISABLE_EXCEPTION_CATCHING:
1737
settings.REQUIRED_EXPORTS += [
1738
# For normal builds the entries in deps_info.py are enough to include
1739
# these symbols whenever __cxa_find_matching_catch_* functions are
1740
# found. However, under LTO these symbols don't exist prior to linking
1741
# so we include then unconditionally when exceptions are enabled.
1742
'__cxa_can_catch',
1743
1744
# __cxa_begin_catch depends on this but we can't use deps info in this
1745
# case because that only works for user-level code, and __cxa_begin_catch
1746
# can be used by the standard library.
1747
'__cxa_increment_exception_refcount',
1748
# Same for __cxa_end_catch
1749
'__cxa_decrement_exception_refcount',
1750
1751
# Emscripten exception handling can generate invoke calls, and they call
1752
# setThrew(). We cannot handle this using deps_info as the invokes are not
1753
# emitted because of library function usage, but by codegen itself.
1754
'setThrew',
1755
'__cxa_free_exception',
1756
]
1757
1758
if settings.ASYNCIFY:
1759
if not settings.ASYNCIFY_IGNORE_INDIRECT:
1760
# if we are not ignoring indirect calls, then we must treat invoke_* as if
1761
# they are indirect calls, since that is what they do - we can't see their
1762
# targets statically.
1763
settings.ASYNCIFY_IMPORTS += ['invoke_*']
1764
# add the default imports
1765
settings.ASYNCIFY_IMPORTS += DEFAULT_ASYNCIFY_IMPORTS
1766
# add the default exports (only used for ASYNCIFY == 2)
1767
settings.ASYNCIFY_EXPORTS += DEFAULT_ASYNCIFY_EXPORTS
1768
1769
# return the full import name, including module. The name may
1770
# already have a module prefix; if not, we assume it is "env".
1771
def get_full_import_name(name):
1772
if '.' in name:
1773
return name
1774
return 'env.' + name
1775
1776
settings.ASYNCIFY_IMPORTS = [get_full_import_name(i) for i in settings.ASYNCIFY_IMPORTS]
1777
1778
if settings.ASYNCIFY == 2:
1779
diagnostics.warning('experimental', '-sASYNCIFY=2 (JSPI) is still experimental')
1780
1781
if settings.SOURCE_PHASE_IMPORTS:
1782
diagnostics.warning('experimental', '-sSOURCE_PHASE_IMPORTS is still experimental and not yet supported in browsers')
1783
1784
if settings.WASM2JS:
1785
if settings.GENERATE_SOURCE_MAP:
1786
exit_with_error('wasm2js does not support source maps yet (debug in wasm for now)')
1787
if settings.MEMORY64:
1788
exit_with_error('wasm2js does not support MEMORY64')
1789
if settings.WASM_BIGINT:
1790
exit_with_error('wasm2js does not support WASM_BIGINT')
1791
if settings.CAN_ADDRESS_2GB:
1792
exit_with_error('wasm2js does not support >2gb address space')
1793
1794
if settings.NODE_CODE_CACHING:
1795
if settings.WASM_ASYNC_COMPILATION:
1796
exit_with_error('NODE_CODE_CACHING requires sync compilation (WASM_ASYNC_COMPILATION=0)')
1797
if not settings.ENVIRONMENT_MAY_BE_NODE:
1798
exit_with_error('NODE_CODE_CACHING only works in node, but target environments do not include it')
1799
if settings.SINGLE_FILE:
1800
exit_with_error('NODE_CODE_CACHING saves a file on the side and is not compatible with SINGLE_FILE')
1801
1802
if not js_manipulation.isidentifier(settings.EXPORT_NAME):
1803
exit_with_error(f'EXPORT_NAME is not a valid JS identifier: `{settings.EXPORT_NAME}`')
1804
1805
if settings.EMSCRIPTEN_TRACING:
1806
add_system_js_lib('libtrace.js')
1807
if settings.ALLOW_MEMORY_GROWTH:
1808
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout']
1809
settings.REQUIRED_EXPORTS += ['emscripten_stack_get_current',
1810
'emscripten_stack_get_base',
1811
'emscripten_stack_get_end']
1812
1813
settings.EMSCRIPTEN_VERSION = utils.EMSCRIPTEN_VERSION
1814
settings.SOURCE_MAP_BASE = options.source_map_base or ''
1815
1816
settings.LINK_AS_CXX = (shared.run_via_emxx or settings.DEFAULT_TO_CXX) and not options.nostdlibxx
1817
1818
# WASMFS itself is written in C++, and needs C++ standard libraries
1819
if settings.WASMFS:
1820
settings.LINK_AS_CXX = True
1821
1822
# Some settings make no sense when not linking as C++
1823
if not settings.LINK_AS_CXX:
1824
cxx_only_settings = [
1825
'EXCEPTION_DEBUG',
1826
'DISABLE_EXCEPTION_CATCHING',
1827
'EXCEPTION_CATCHING_ALLOWED',
1828
'DISABLE_EXCEPTION_THROWING',
1829
]
1830
for setting in cxx_only_settings:
1831
if setting in user_settings:
1832
diagnostics.warning('linkflags', 'setting `%s` is not meaningful unless linking as C++', setting)
1833
1834
if settings.WASM_EXCEPTIONS:
1835
settings.REQUIRED_EXPORTS += ['__trap']
1836
1837
if settings.EXCEPTION_STACK_TRACES:
1838
# If the user explicitly gave EXCEPTION_STACK_TRACES=1 without enabling EH,
1839
# errors out.
1840
if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS:
1841
exit_with_error('EXCEPTION_STACK_TRACES requires either of -fexceptions or -fwasm-exceptions')
1842
# EXCEPTION_STACK_TRACES implies EXPORT_EXCEPTION_HANDLING_HELPERS
1843
settings.EXPORT_EXCEPTION_HANDLING_HELPERS = True
1844
1845
# Make `getExceptionMessage` and other necessary functions available for use.
1846
if settings.EXPORT_EXCEPTION_HANDLING_HELPERS:
1847
# If the user explicitly gave EXPORT_EXCEPTION_HANDLING_HELPERS=1 without
1848
# enabling EH, errors out.
1849
if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS:
1850
exit_with_error('EXPORT_EXCEPTION_HANDLING_HELPERS requires either of -fexceptions or -fwasm-exceptions')
1851
# We also export refcount increasing and decreasing functions because if you
1852
# catch an exception, be it an Emscripten exception or a Wasm exception, in
1853
# JS, you may need to manipulate the refcount manually not to leak memory.
1854
# What you need to do is different depending on the kind of EH you use
1855
# (https://github.com/emscripten-core/emscripten/issues/17115).
1856
settings.EXPORTED_FUNCTIONS += ['getExceptionMessage', 'incrementExceptionRefcount', 'decrementExceptionRefcount']
1857
if settings.WASM_EXCEPTIONS:
1858
settings.REQUIRED_EXPORTS += ['__cpp_exception']
1859
1860
if settings.SIDE_MODULE:
1861
# For side modules, we ignore all REQUIRED_EXPORTS that might have been added above.
1862
# They all come from either libc or compiler-rt. The exception is __wasm_call_ctors
1863
# and _emscripten_tls_init which are per-module exports.
1864
settings.REQUIRED_EXPORTS.clear()
1865
1866
if not settings.STANDALONE_WASM:
1867
# in standalone mode, crt1 will call the constructors from inside the wasm
1868
settings.REQUIRED_EXPORTS.append('__wasm_call_ctors')
1869
if settings.PTHREADS:
1870
settings.REQUIRED_EXPORTS.append('_emscripten_tls_init')
1871
1872
settings.PRE_JS_FILES = options.pre_js
1873
settings.POST_JS_FILES = options.post_js
1874
1875
settings.MINIFY_WHITESPACE = settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL == 0 and not options.no_minify
1876
1877
# Closure might be run if we run it ourselves, or if whitespace is not being
1878
# minifed. In the latter case we keep both whitespace and comments, and the
1879
# purpose of the comments might be closure compiler, so also perform all
1880
# adjustments necessary to ensure that works (which amounts to a few more
1881
# comments; adding some more of them is not an issue in such a build which
1882
# includes all comments and whitespace anyhow).
1883
if settings.USE_CLOSURE_COMPILER or not settings.MINIFY_WHITESPACE:
1884
settings.MAYBE_CLOSURE_COMPILER = 1
1885
1886
report_incompatible_settings()
1887
1888
return target, wasm_target
1889
1890
1891
@ToolchainProfiler.profile_block('calculate system libraries')
1892
def phase_calculate_system_libraries(options):
1893
extra_files_to_link = []
1894
# Link in ports and system libraries, if necessary
1895
if not settings.SIDE_MODULE:
1896
# Ports are always linked into the main module, never the side module.
1897
extra_files_to_link += ports.get_libs(settings)
1898
extra_files_to_link += system_libs.calculate(options)
1899
return extra_files_to_link
1900
1901
1902
@ToolchainProfiler.profile_block('link')
1903
def phase_link(linker_args, wasm_target, js_syms):
1904
logger.debug(f'linking: {linker_args}')
1905
1906
# Make a final pass over settings.EXPORTED_FUNCTIONS to remove any
1907
# duplication between functions added by the driver/libraries and function
1908
# specified by the user
1909
settings.EXPORTED_FUNCTIONS = dedup_list(settings.EXPORTED_FUNCTIONS)
1910
settings.REQUIRED_EXPORTS = dedup_list(settings.REQUIRED_EXPORTS)
1911
settings.EXPORT_IF_DEFINED = dedup_list(settings.EXPORT_IF_DEFINED)
1912
1913
rtn = None
1914
if settings.LINKABLE and not settings.EXPORT_ALL:
1915
# In LINKABLE mode we pass `--export-dynamic` along with `--whole-archive`. This results
1916
# in over 7000 exports, which cannot be distinguished from the few symbols we explicitly
1917
# export via EMSCRIPTEN_KEEPALIVE or EXPORTED_FUNCTIONS.
1918
# In order to avoid unnecessary exported symbols on the `Module` object we run the linker
1919
# twice in this mode:
1920
# 1. Without `--export-dynamic` to get the base exports
1921
# 2. With `--export-dynamic` to get the actual linkable Wasm binary
1922
# TODO(sbc): Remove this double execution of wasm-ld if we ever find a way to
1923
# distinguish EMSCRIPTEN_KEEPALIVE exports from `--export-dynamic` exports.
1924
settings.LINKABLE = False
1925
building.link_lld(linker_args, wasm_target, external_symbols=js_syms)
1926
settings.LINKABLE = True
1927
rtn = extract_metadata.extract_metadata(wasm_target)
1928
1929
building.link_lld(linker_args, wasm_target, external_symbols=js_syms)
1930
return rtn
1931
1932
1933
@ToolchainProfiler.profile_block('post link')
1934
def phase_post_link(options, in_wasm, wasm_target, target, js_syms, base_metadata=None):
1935
global final_js
1936
1937
target_basename = unsuffixed_basename(target)
1938
1939
if options.oformat != OFormat.WASM:
1940
final_js = in_temp(target_basename + '.js')
1941
1942
settings.TARGET_BASENAME = unsuffixed_basename(target)
1943
1944
if options.oformat in (OFormat.JS, OFormat.MJS):
1945
js_target = target
1946
else:
1947
js_target = get_secondary_target(target, '.js')
1948
1949
settings.TARGET_JS_NAME = os.path.basename(js_target)
1950
1951
metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata)
1952
1953
if settings.EMBIND_AOT:
1954
phase_embind_aot(options, wasm_target, js_syms)
1955
1956
if options.emit_tsd:
1957
phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata)
1958
1959
if options.js_transform:
1960
phase_source_transforms(options)
1961
1962
phase_binaryen(target, options, wasm_target)
1963
1964
# If we are not emitting any JS then we are all done now
1965
if options.oformat != OFormat.WASM:
1966
phase_final_emitting(options, target, js_target, wasm_target)
1967
1968
1969
@ToolchainProfiler.profile_block('emscript')
1970
def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata):
1971
# Emscripten
1972
logger.debug('emscript')
1973
1974
# No need to support base64 embedding in wasm2js mode since
1975
# the module is already in JS format.
1976
if settings.SINGLE_FILE and not settings.WASM2JS:
1977
settings.SUPPORT_BASE64_EMBEDDING = 1
1978
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.append('$base64Decode')
1979
1980
if shared.SKIP_SUBPROCS:
1981
return
1982
1983
metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms, base_metadata=base_metadata)
1984
save_intermediate('original')
1985
return metadata
1986
1987
1988
def run_embind_gen(options, wasm_target, js_syms, extra_settings):
1989
# Save settings so they can be restored after TS generation.
1990
original_settings = settings.backup()
1991
settings.attrs.update(extra_settings)
1992
settings.EMBIND_GEN_MODE = True
1993
1994
if settings.MAIN_MODULE:
1995
# Copy libraries to the temp directory so they can be used when running
1996
# in node.
1997
for f in options.input_files:
1998
if building.is_wasm_dylib(f):
1999
safe_copy(f, in_temp(''))
2000
2001
# Ignore any options or settings that can conflict with running the TS
2002
# generation output.
2003
# Don't invoke the program's `main` function.
2004
settings.INVOKE_RUN = False
2005
# Ignore -sMODULARIZE which could otherwise effect how we run the module
2006
# to generate the bindings.
2007
settings.MODULARIZE = False
2008
# Disable ESM integration to avoid enabling the experimental feature in node.
2009
settings.WASM_ESM_INTEGRATION = False
2010
# Don't include any custom user JS or files.
2011
settings.PRE_JS_FILES = []
2012
settings.POST_JS_FILES = []
2013
# Force node since that is where the tool runs.
2014
settings.ENVIRONMENT = ['node']
2015
settings.MINIMAL_RUNTIME = 0
2016
# Required function to trigger TS generation.
2017
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$callRuntimeCallbacks', '$addRunDependency', '$removeRunDependency']
2018
settings.EXPORT_ES6 = False
2019
# Disable proxying and thread pooling so a worker is not automatically created.
2020
settings.PROXY_TO_PTHREAD = False
2021
settings.PTHREAD_POOL_SIZE = 0
2022
# Assume wasm support at binding generation time
2023
settings.WASM2JS = 0
2024
# Disable minify since the binaryen pass has not been run yet to change the
2025
# import names.
2026
settings.MINIFY_WASM_IMPORTED_MODULES = False
2027
setup_environment_settings()
2028
# Use a separate Wasm file so the JS does not need to be modified after emscripten.emscript.
2029
settings.SINGLE_FILE = False
2030
# Replace embind with the TypeScript generation version.
2031
for i, lib in enumerate(settings.JS_LIBRARIES):
2032
dirname, basename = os.path.split(lib)
2033
if basename == 'libembind.js':
2034
settings.JS_LIBRARIES[i] = os.path.join(dirname, 'libembind_gen.js')
2035
settings.MIN_NODE_VERSION = 160000 if settings.MEMORY64 else 150000
2036
# The final version of the memory64 proposal is not implemented until node
2037
# v24, so we need to lower it away in order to execute the binary at build
2038
# time.
2039
# TODO Remove lowering when emsdk version of node is >= 24 and just require it.
2040
if settings.MEMORY64:
2041
settings.MEMORY64 = 2
2042
# Source maps haven't been generated yet and aren't needed to run embind_gen.
2043
settings.LOAD_SOURCE_MAP = 0
2044
outfile_js = in_temp('tsgen.js')
2045
# The Wasm outfile may be modified by emscripten.emscript, so use a temporary file.
2046
outfile_wasm = in_temp('tsgen.wasm')
2047
emscripten.emscript(wasm_target, outfile_wasm, outfile_js, js_syms, finalize=False)
2048
# Build the flags needed by Node.js to properly run the output file.
2049
node_args = []
2050
if settings.MEMORY64:
2051
# See comment above about lowering memory64.
2052
building.run_wasm_opt(outfile_wasm, outfile_wasm, ['--memory64-lowering', '--table64-lowering'])
2053
if settings.WASM_EXCEPTIONS:
2054
node_args += shared.node_exception_flags(config.NODE_JS)
2055
# Run the generated JS file with the proper flags to generate the TypeScript bindings.
2056
output_file = in_temp('embind_generated_output.js')
2057
shared.run_js_tool(outfile_js, [output_file], node_args)
2058
settings.restore(original_settings)
2059
return read_file(output_file)
2060
2061
2062
@ToolchainProfiler.profile_block('emit tsd')
2063
def phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata):
2064
logger.debug('emit tsd')
2065
filename = options.emit_tsd
2066
embind_tsd = ''
2067
if settings.EMBIND:
2068
embind_tsd = run_embind_gen(options, wasm_target, js_syms, {'EMBIND_AOT': False})
2069
all_tsd = emscripten.create_tsd(metadata, embind_tsd)
2070
out_file = os.path.join(os.path.dirname(js_target), filename)
2071
write_file(out_file, all_tsd)
2072
2073
2074
@ToolchainProfiler.profile_block('embind aot js')
2075
def phase_embind_aot(options, wasm_target, js_syms):
2076
out = run_embind_gen(options, wasm_target, js_syms, {})
2077
if DEBUG:
2078
write_file(in_temp('embind_aot.json'), out)
2079
out = json.loads(out)
2080
src = read_file(final_js)
2081
src = do_replace(src, '<<< EMBIND_AOT_INVOKERS >>>', out['invokers'])
2082
if settings.MODULARIZE == 'instance':
2083
# Add ES module exports for the embind exports.
2084
decls = '\n'.join([f'export var {name};' for name in out['publicSymbols']])
2085
# Assign the runtime exports from Module to the ES export.
2086
assigns = '\n'.join([f'{name} = Module[\'{name}\'];' for name in out['publicSymbols']])
2087
exports = f'''
2088
// start embind exports
2089
function assignEmbindExports() {{ {assigns} }};
2090
addOnPostCtor(assignEmbindExports);
2091
{decls}
2092
// end embind exports'''
2093
src += exports
2094
write_file(final_js, src)
2095
if settings.WASM_ESM_INTEGRATION:
2096
# With ESM integration the embind exports also need to be exported by the main file.
2097
settings.EXPORTED_RUNTIME_METHODS.extend(out['publicSymbols'])
2098
2099
2100
# for Popen, we cannot have doublequotes, so provide functionality to
2101
# remove them when needed.
2102
def remove_quotes(arg):
2103
if isinstance(arg, list):
2104
return [remove_quotes(a) for a in arg]
2105
2106
if arg.startswith('"') and arg.endswith('"'):
2107
return arg[1:-1].replace('\\"', '"')
2108
elif arg.startswith("'") and arg.endswith("'"):
2109
return arg[1:-1].replace("\\'", "'")
2110
else:
2111
return arg
2112
2113
2114
@ToolchainProfiler.profile_block('source transforms')
2115
def phase_source_transforms(options):
2116
# Apply a source code transformation, if requested
2117
global final_js
2118
safe_copy(final_js, final_js + '.tr.js')
2119
final_js += '.tr.js'
2120
posix = not shared.WINDOWS
2121
logger.debug('applying transform: %s', options.js_transform)
2122
shared.check_call(remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final_js)]))
2123
save_intermediate('transformed')
2124
2125
2126
# Unmangle previously mangled `import.meta` and `await import` references in
2127
# both main code and libraries.
2128
# See also: `preprocess` in parseTools.js.
2129
def fix_js_mangling(js_file):
2130
# We don't apply these mangliings except in MODULARIZE/EXPORT_ES6 modes.
2131
if not settings.MODULARIZE:
2132
return
2133
2134
src = read_file(js_file)
2135
write_file(js_file, src
2136
.replace('EMSCRIPTEN$IMPORT$META', 'import.meta')
2137
.replace('EMSCRIPTEN$AWAIT$IMPORT', 'await import')
2138
.replace('EMSCRIPTEN$AWAIT(', 'await ('))
2139
save_intermediate('js-mangling')
2140
2141
2142
def node_detection_code():
2143
return "globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'"
2144
2145
2146
def create_esm_wrapper(wrapper_file, support_target, wasm_target):
2147
js_exports = building.user_requested_exports.union(settings.EXPORTED_RUNTIME_METHODS)
2148
js_exports = ', '.join(sorted(js_exports))
2149
2150
wrapper = []
2151
wrapper.append('// The wasm module must be imported here first before the support file')
2152
wrapper.append('// in order to avoid issues with circular dependencies.')
2153
wrapper.append(f"import * as unused from './{settings.WASM_BINARY_FILE}';")
2154
support_url = f'./{os.path.basename(support_target)}'
2155
if js_exports:
2156
wrapper.append(f"export {{ default, {js_exports} }} from '{support_url}';")
2157
else:
2158
wrapper.append(f"export {{ default }} from '{support_url}';")
2159
2160
if settings.ENVIRONMENT_MAY_BE_NODE:
2161
wrapper.append(f'''
2162
// When run as the main module under node, create the module directly. This will
2163
// execute any startup code along with main (if it exists).
2164
import init from '{support_url}';
2165
const isNode = {node_detection_code()};
2166
if (isNode) {{
2167
const url = await import('url');
2168
const isMainModule = url.pathToFileURL(process.argv[1]).href === import.meta.url;
2169
if (isMainModule) await init();
2170
}}''')
2171
2172
write_file(wrapper_file, '\n'.join(wrapper) + '\n')
2173
2174
# FIXME(sbc): This is a huge hack to rename the imports in the
2175
# wasm file. Find a better way to do this.
2176
wasm_dis = os.path.join(building.get_binaryen_bin(), 'wasm-dis')
2177
mod = shared.check_call([wasm_dis, wasm_target], stdout=PIPE).stdout
2178
mod = mod.replace('(import "env"', f'(import "{support_url}"')
2179
mod = mod.replace('(import "wasi_snapshot_preview1"', f'(import "{support_url}"')
2180
2181
wasm_as = os.path.join(building.get_binaryen_bin(), 'wasm-as')
2182
cmd = [wasm_as, '--all-features', '-o', wasm_target, '-']
2183
if settings.EMIT_NAME_SECTION:
2184
cmd.append('-g')
2185
shared.check_call(cmd, input=mod)
2186
2187
2188
@ToolchainProfiler.profile_block('final emitting')
2189
def phase_final_emitting(options, target, js_target, wasm_target):
2190
global final_js
2191
2192
if shared.SKIP_SUBPROCS:
2193
return
2194
2195
if settings.MODULARIZE and settings.MODULARIZE != 'instance':
2196
modularize()
2197
elif settings.USE_CLOSURE_COMPILER:
2198
module_export_name_substitution()
2199
2200
# Run a final optimization pass to clean up items that were not possible to
2201
# optimize by Closure, or unoptimalities that were left behind by processing
2202
# steps that occurred after Closure.
2203
if settings.MINIMAL_RUNTIME == 2 and settings.USE_CLOSURE_COMPILER and settings.DEBUG_LEVEL == 0:
2204
args = [final_js, '-o', final_js]
2205
if not settings.MINIFY_WHITESPACE:
2206
args.append('--pretty')
2207
shared.run_js_tool(utils.path_from_root('tools/unsafe_optimizations.mjs'), args, cwd=utils.path_from_root('.'))
2208
save_intermediate('unsafe-optimizations')
2209
# Finally, rerun Closure compile with simple optimizations. It will be able
2210
# to further minify the code. (n.b. it would not be safe to run in advanced
2211
# mode)
2212
final_js = building.closure_compiler(final_js, advanced=False, extra_closure_args=settings.CLOSURE_ARGS)
2213
# Run unsafe_optimizations.js once more. This allows the cleanup of newly
2214
# unused things that closure compiler leaves behind (e.g `new Float64Array(x)`).
2215
shared.run_js_tool(utils.path_from_root('tools/unsafe_optimizations.mjs'), [final_js, '-o', final_js], cwd=utils.path_from_root('.'))
2216
save_intermediate('unsafe-optimizations2')
2217
2218
fix_js_mangling(final_js)
2219
2220
# Apply pre and postjs files
2221
if options.extern_pre_js or options.extern_post_js:
2222
extern_pre_js = read_js_files(options.extern_pre_js)
2223
extern_post_js = read_js_files(options.extern_post_js)
2224
logger.debug('applying extern pre/postjses')
2225
src = read_file(final_js)
2226
final_js += '.epp.js'
2227
with open(final_js, 'w', encoding='utf-8') as f:
2228
f.write(extern_pre_js)
2229
f.write(src)
2230
f.write(extern_post_js)
2231
save_intermediate('extern-pre-post')
2232
2233
js_manipulation.handle_license(final_js)
2234
2235
# The JS is now final. Move it to its final location
2236
if settings.WASM_ESM_INTEGRATION:
2237
support_target = unsuffixed(js_target) + '.support.mjs'
2238
move_file(final_js, support_target)
2239
create_esm_wrapper(js_target, support_target, wasm_target)
2240
if settings.PTHREADS:
2241
support_target = unsuffixed(js_target) + '.pthread.mjs'
2242
pthread_code = building.read_and_preprocess(utils.path_from_root('src/pthread_esm_startup.mjs'), expand_macros=True)
2243
write_file(support_target, pthread_code)
2244
fix_js_mangling(support_target)
2245
else:
2246
move_file(final_js, js_target)
2247
2248
target_basename = unsuffixed_basename(target)
2249
2250
utils.convert_line_endings_in_file(js_target, options.output_eol)
2251
2252
# If we were asked to also generate HTML, do that
2253
if options.oformat == OFormat.HTML:
2254
generate_html(target, options, js_target, target_basename,
2255
wasm_target)
2256
elif settings.PROXY_TO_WORKER:
2257
generate_worker_js(target, options, js_target, target_basename)
2258
2259
if settings.SPLIT_MODULE:
2260
diagnostics.warning('experimental', 'the SPLIT_MODULE setting is experimental and subject to change')
2261
do_split_module(wasm_target, options)
2262
2263
if options.executable:
2264
make_js_executable(js_target)
2265
2266
2267
@ToolchainProfiler.profile_block('binaryen')
2268
def phase_binaryen(target, options, wasm_target):
2269
global final_js
2270
logger.debug('using binaryen')
2271
# whether we need to emit -g (function name debug info) in the final wasm
2272
debug_function_names = settings.DEBUG_LEVEL >= 2 or settings.EMIT_NAME_SECTION
2273
# whether we need to emit -g in the intermediate binaryen invocations (but not
2274
# necessarily at the very end). this is necessary if we depend on debug info
2275
# during compilation, even if we do not emit it at the end.
2276
# we track the number of causes for needing intermdiate debug info so
2277
# that we can stop emitting it when possible - in particular, that is
2278
# important so that we stop emitting it before the end, and it is not in the
2279
# final binary (if it shouldn't be)
2280
intermediate_debug_info = 0
2281
if debug_function_names:
2282
intermediate_debug_info += 1
2283
if options.emit_symbol_map:
2284
intermediate_debug_info += 1
2285
if settings.ASYNCIFY == 1:
2286
intermediate_debug_info += 1
2287
2288
# run wasm-opt if we have work for it: either passes, or if we are using
2289
# source maps (which requires some extra processing to keep the source map
2290
# but remove DWARF)
2291
passes = get_binaryen_passes()
2292
if passes:
2293
# if asyncify is used, we will use it in the next stage, and so if it is
2294
# the only reason we need intermediate debug info, we can stop keeping it
2295
if settings.ASYNCIFY == 1:
2296
intermediate_debug_info -= 1
2297
# currently binaryen's DWARF support will limit some optimizations; warn on
2298
# that. see https://github.com/emscripten-core/emscripten/issues/15269
2299
if settings.GENERATE_DWARF:
2300
diagnostics.warning('limited-postlink-optimizations', 'running limited binaryen optimizations because DWARF info requested (or indirectly required)')
2301
with ToolchainProfiler.profile_block('wasm_opt'):
2302
building.run_wasm_opt(wasm_target,
2303
wasm_target,
2304
args=passes,
2305
debug=intermediate_debug_info)
2306
building.save_intermediate(wasm_target, 'byn.wasm')
2307
2308
if settings.EVAL_CTORS:
2309
with ToolchainProfiler.profile_block('eval_ctors'):
2310
building.eval_ctors(final_js, wasm_target, debug_info=intermediate_debug_info)
2311
building.save_intermediate(wasm_target, 'ctors.wasm')
2312
2313
# after generating the wasm, do some final operations
2314
2315
if final_js:
2316
# >=2GB heap support requires pointers in JS to be unsigned. rather than
2317
# require all pointers to be unsigned by default, which increases code size
2318
# a little, keep them signed, and just unsign them here if we need that.
2319
if settings.CAN_ADDRESS_2GB:
2320
with ToolchainProfiler.profile_block('use_unsigned_pointers_in_js'):
2321
final_js = building.use_unsigned_pointers_in_js(final_js)
2322
2323
if settings.USE_ASAN:
2324
final_js = building.instrument_js_for_asan(final_js)
2325
2326
if settings.SAFE_HEAP:
2327
final_js = building.instrument_js_for_safe_heap(final_js)
2328
2329
# shared memory growth requires some additional JS fixups.
2330
# note that we must do this after handling of unsigned pointers. unsigning
2331
# adds some >>> 0 things, while growth will replace a HEAP8 with a call to
2332
# a method to get the heap, and that call would not be recognized by the
2333
# unsigning pass.
2334
# we also must do this after the asan or safe_heap instrumentation, as they
2335
# wouldn't be able to recognize patterns produced by the growth pass.
2336
if settings.SHARED_MEMORY and settings.ALLOW_MEMORY_GROWTH and not settings.GROWABLE_ARRAYBUFFERS:
2337
with ToolchainProfiler.profile_block('apply_wasm_memory_growth'):
2338
final_js = building.apply_wasm_memory_growth(final_js)
2339
2340
if settings.SUPPORT_BIG_ENDIAN:
2341
with ToolchainProfiler.profile_block('little_endian_heap'):
2342
final_js = building.little_endian_heap(final_js)
2343
2344
if settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL <= 2:
2345
# minify the JS. Do not minify whitespace if Closure is used, so that
2346
# Closure can print out readable error messages (Closure will then
2347
# minify whitespace afterwards)
2348
with ToolchainProfiler.profile_block('minify_wasm'):
2349
save_intermediate_with_wasm('preclean', wasm_target)
2350
final_js = building.minify_wasm_js(js_file=final_js,
2351
wasm_file=wasm_target,
2352
expensive_optimizations=will_metadce(),
2353
debug_info=intermediate_debug_info)
2354
save_intermediate_with_wasm('postclean', wasm_target)
2355
2356
if options.use_closure_compiler:
2357
with ToolchainProfiler.profile_block('closure_compile'):
2358
final_js = building.closure_compiler(final_js, extra_closure_args=settings.CLOSURE_ARGS)
2359
save_intermediate('closure')
2360
2361
if settings.TRANSPILE:
2362
with ToolchainProfiler.profile_block('transpile'):
2363
final_js = building.transpile(final_js)
2364
save_intermediate('transpile')
2365
# Run acorn one more time to minify whitespace after babel runs
2366
if settings.MINIFY_WHITESPACE:
2367
final_js = building.acorn_optimizer(final_js, ['--minify-whitespace'])
2368
2369
if settings.ASYNCIFY_LAZY_LOAD_CODE:
2370
with ToolchainProfiler.profile_block('asyncify_lazy_load_code'):
2371
building.asyncify_lazy_load_code(wasm_target, debug=intermediate_debug_info)
2372
2373
symbols_file = None
2374
if options.emit_symbol_map:
2375
symbols_file = shared.replace_or_append_suffix(target, '.symbols')
2376
2377
if settings.WASM2JS:
2378
symbols_file_js = None
2379
if settings.WASM == 2:
2380
# With normal wasm2js mode this file gets included as part of the
2381
# preamble, but with WASM=2 its a separate file.
2382
wasm2js_polyfill = building.read_and_preprocess(utils.path_from_root('src/wasm2js.js'), expand_macros=True)
2383
wasm2js_template = wasm_target + '.js'
2384
write_file(wasm2js_template, wasm2js_polyfill)
2385
# generate secondary file for JS symbols
2386
if options.emit_symbol_map:
2387
symbols_file_js = shared.replace_or_append_suffix(wasm2js_template, '.symbols')
2388
else:
2389
wasm2js_template = final_js
2390
if options.emit_symbol_map:
2391
symbols_file_js = shared.replace_or_append_suffix(target, '.symbols')
2392
2393
wasm2js = building.wasm2js(wasm2js_template,
2394
wasm_target,
2395
opt_level=settings.OPT_LEVEL,
2396
use_closure_compiler=options.use_closure_compiler,
2397
debug_info=debug_function_names,
2398
symbols_file=symbols_file,
2399
symbols_file_js=symbols_file_js)
2400
2401
shared.get_temp_files().note(wasm2js)
2402
2403
if settings.WASM == 2:
2404
safe_copy(wasm2js, wasm2js_template)
2405
2406
if settings.WASM != 2:
2407
final_js = wasm2js
2408
2409
save_intermediate('wasm2js')
2410
2411
generating_wasm = settings.WASM == 2 or not settings.WASM2JS
2412
2413
# emit the final symbols, either in the binary or in a symbol map.
2414
# this will also remove debug info if we only kept it around in the intermediate invocations.
2415
# note that if we aren't emitting a binary (like in wasm2js) then we don't
2416
# have anything to do here.
2417
if options.emit_symbol_map:
2418
intermediate_debug_info -= 1
2419
if generating_wasm:
2420
building.write_symbol_map(wasm_target, symbols_file)
2421
if not intermediate_debug_info:
2422
building.strip(wasm_target, wasm_target, sections=['name'])
2423
2424
if settings.GENERATE_DWARF and settings.SEPARATE_DWARF and generating_wasm:
2425
# if the dwarf filename wasn't provided, use the default target + a suffix
2426
wasm_file_with_dwarf = settings.SEPARATE_DWARF
2427
if wasm_file_with_dwarf is True:
2428
# Historically this file has been called `.wasm.debug.wasm`
2429
# TODO(sbc): Should this just be `.debug.wasm`
2430
wasm_file_with_dwarf = get_secondary_target(target, '.wasm.debug.wasm')
2431
building.emit_debug_on_side(wasm_target, wasm_file_with_dwarf)
2432
2433
# we have finished emitting the wasm, and so intermediate debug info will
2434
# definitely no longer be used tracking it.
2435
if debug_function_names:
2436
intermediate_debug_info -= 1
2437
assert intermediate_debug_info == 0
2438
# strip debug info if it was not already stripped by the last command
2439
if not debug_function_names and building.binaryen_kept_debug_info and generating_wasm:
2440
with ToolchainProfiler.profile_block('strip_name_section'):
2441
building.strip(wasm_target, wasm_target, debug=False, sections=["name"])
2442
2443
# replace placeholder strings with correct subresource locations
2444
if final_js and settings.SINGLE_FILE and not settings.WASM2JS:
2445
js = read_file(final_js)
2446
2447
js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(wasm_target))
2448
delete_file(wasm_target)
2449
write_file(final_js, js)
2450
2451
2452
def modularize():
2453
global final_js
2454
logger.debug(f'Modularizing, creating factory function called `{settings.EXPORT_NAME}`')
2455
modularize_src = building.read_and_preprocess(utils.path_from_root('src/modularize.js'), expand_macros=True)
2456
if settings.MINIFY_WHITESPACE:
2457
with shared.get_temp_files().get_file(suffix='.js') as tmp:
2458
write_file(tmp, modularize_src)
2459
minified_file = building.acorn_optimizer(tmp, ['--minify-whitespace'])
2460
modularize_src = read_file(minified_file)
2461
2462
# Replace INNER_JS_CODE in the minified code
2463
full_src = do_replace(modularize_src, '"<<< INNER_JS_CODE >>>"', read_file(final_js))
2464
final_js += '.modular.js'
2465
write_file(final_js, full_src)
2466
shared.get_temp_files().note(final_js)
2467
save_intermediate('modularized')
2468
2469
# FIXME(https://github.com/emscripten-core/emscripten/issues/24558): Running acorn at this
2470
# late phase seems to cause OOM (some kind of inifite loop perhaps) in node.
2471
# Instead we minify src/modularize.js in isolation above.
2472
#if settings.MINIFY_WHITESPACE:
2473
# final_js = building.acorn_optimizer(final_js, ['--minify-whitespace'])
2474
2475
2476
def module_export_name_substitution():
2477
assert not settings.MODULARIZE
2478
global final_js
2479
logger.debug(f'Private module export name substitution with {settings.EXPORT_NAME}')
2480
src = read_file(final_js)
2481
final_js += '.module_export_name_substitution.js'
2482
if settings.MINIMAL_RUNTIME and not settings.ENVIRONMENT_MAY_BE_NODE and not settings.ENVIRONMENT_MAY_BE_SHELL and not settings.AUDIO_WORKLET:
2483
# On the web, with MINIMAL_RUNTIME, the Module object is always provided
2484
# via the shell html in order to provide the .asm.js/.wasm content.
2485
replacement = settings.EXPORT_NAME
2486
else:
2487
replacement = "typeof %(EXPORT_NAME)s != 'undefined' ? %(EXPORT_NAME)s : {}" % {"EXPORT_NAME": settings.EXPORT_NAME}
2488
new_src = re.sub(r'{\s*[\'"]?__EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__[\'"]?:\s*1\s*}', replacement, src)
2489
assert new_src != src, 'Unable to find Closure syntax __EMSCRIPTEN_PRIVATE_MODULE_EXPORT_NAME_SUBSTITUTION__ in source!'
2490
write_file(final_js, new_src)
2491
shared.get_temp_files().note(final_js)
2492
save_intermediate('module_export_name_substitution')
2493
2494
2495
def generate_traditional_runtime_html(target, options, js_target, target_basename,
2496
wasm_target):
2497
script = ScriptSource()
2498
2499
if settings.EXPORT_NAME != 'Module' and options.shell_path == DEFAULT_SHELL_HTML:
2500
# the minimal runtime shell HTML is designed to support changing the export
2501
# name, but the normal one does not support that currently
2502
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)')
2503
2504
shell = building.read_and_preprocess(options.shell_path)
2505
if '{{{ SCRIPT }}}' not in shell:
2506
exit_with_error('HTML shell must contain {{{ SCRIPT }}}, see src/shell.html for an example')
2507
base_js_target = os.path.basename(js_target)
2508
2509
if settings.PROXY_TO_WORKER:
2510
proxy_worker_filename = (settings.PROXY_TO_WORKER_FILENAME or target_basename) + '.js'
2511
script.inline = worker_js_script(proxy_worker_filename)
2512
else:
2513
# Normal code generation path
2514
script.src = base_js_target
2515
2516
if settings.SINGLE_FILE:
2517
js_contents = script.inline or ''
2518
if script.src:
2519
js_contents += read_file(js_target)
2520
script.src = None
2521
script.inline = read_file(js_target)
2522
delete_file(js_target)
2523
else:
2524
if not settings.WASM_ASYNC_COMPILATION:
2525
# We need to load the wasm file before anything else, since it
2526
# has be synchronously ready.
2527
script.un_src()
2528
script.inline = '''
2529
fetch('%s').then((result) => result.arrayBuffer())
2530
.then((buf) => {
2531
Module.wasmBinary = buf;
2532
%s;
2533
});
2534
''' % (get_subresource_location(wasm_target), script.inline)
2535
2536
if settings.WASM == 2:
2537
# If target browser does not support WebAssembly, we need to load
2538
# the .wasm.js file before the main .js file.
2539
script.un_src()
2540
script.inline = '''
2541
function loadMainJs() {
2542
%s
2543
}
2544
if (!window.WebAssembly || location.search.indexOf('_rwasm=0') > 0) {
2545
// Current browser does not support WebAssembly, load the .wasm.js JavaScript fallback
2546
// before the main JS runtime.
2547
var wasm2js = document.createElement('script');
2548
wasm2js.src = '%s';
2549
wasm2js.onload = loadMainJs;
2550
document.body.appendChild(wasm2js);
2551
} else {
2552
// Current browser supports Wasm, proceed with loading the main JS runtime.
2553
loadMainJs();
2554
}
2555
''' % (script.inline, get_subresource_location_js(wasm_target + '.js'))
2556
2557
shell = do_replace(shell, '{{{ SCRIPT }}}', script.replacement())
2558
shell = shell.replace('{{{ SHELL_CSS }}}', utils.read_file(utils.path_from_root('src/shell.css')))
2559
logo_filename = utils.path_from_root('media/powered_by_logo_shell.png')
2560
logo_b64 = base64_encode(logo_filename)
2561
shell = shell.replace('{{{ SHELL_LOGO }}}', f'<img id="emscripten_logo" src="data:image/png;base64,{logo_b64}">')
2562
2563
check_output_file(target)
2564
write_file(target, shell)
2565
2566
2567
def minify_html(filename):
2568
if settings.DEBUG_LEVEL >= 2:
2569
return
2570
2571
opts = []
2572
# -g1 and greater retain whitespace and comments in source
2573
if settings.DEBUG_LEVEL == 0:
2574
opts += ['--collapse-whitespace',
2575
'--remove-comments',
2576
'--remove-tag-whitespace',
2577
'--sort-attributes',
2578
'--sort-class-name']
2579
# -g2 and greater do not minify HTML at all
2580
if settings.DEBUG_LEVEL <= 1:
2581
opts += ['--decode-entities',
2582
'--collapse-boolean-attributes',
2583
'--remove-attribute-quotes',
2584
'--remove-redundant-attributes',
2585
'--remove-script-type-attributes',
2586
'--remove-style-link-type-attributes',
2587
'--use-short-doctype',
2588
'--minify-css', 'true',
2589
'--minify-js', 'true']
2590
2591
# html-minifier also has the following options, but they look unsafe for use:
2592
# '--collapse-inline-tag-whitespace': removes whitespace between inline tags in visible text,
2593
# causing words to be joined together. See
2594
# https://github.com/terser/html-minifier-terser/issues/179
2595
# https://github.com/emscripten-core/emscripten/issues/22188
2596
# '--remove-optional-tags': removes e.g. <head></head> and <body></body> tags from the page.
2597
# (Breaks at least browser.test_sdl2glshader)
2598
# '--remove-empty-attributes': removes all attributes with whitespace-only values.
2599
# (Breaks at least browser.test_asmfs_hello_file)
2600
# '--remove-empty-elements': removes all elements with empty contents.
2601
# (Breaks at least browser.test_asm_swapping)
2602
2603
logger.debug(f'minifying HTML file {filename}')
2604
size_before = os.path.getsize(filename)
2605
start_time = time.time()
2606
shared.check_call(shared.get_npm_cmd('html-minifier-terser') + [filename, '-o', filename] + opts, env=shared.env_with_node_in_path())
2607
2608
elapsed_time = time.time() - start_time
2609
size_after = os.path.getsize(filename)
2610
delta = size_after - size_before
2611
logger.debug(f'HTML minification took {elapsed_time:.2f} seconds, and shrunk size of {filename} from {size_before} to {size_after} bytes, delta={delta} ({delta * 100.0 / size_before:+.2f}%)')
2612
2613
2614
def generate_html(target, options, js_target, target_basename, wasm_target):
2615
logger.debug('generating HTML')
2616
2617
if settings.MINIMAL_RUNTIME:
2618
generate_minimal_runtime_html(target, options, js_target, target_basename)
2619
else:
2620
generate_traditional_runtime_html(target, options, js_target, target_basename, wasm_target)
2621
2622
if settings.MINIFY_HTML and (settings.OPT_LEVEL >= 1 or settings.SHRINK_LEVEL >= 1):
2623
minify_html(target)
2624
2625
utils.convert_line_endings_in_file(target, options.output_eol)
2626
2627
2628
def generate_worker_js(target, options, js_target, target_basename):
2629
if settings.SINGLE_FILE:
2630
# compiler output is embedded as base64 data URL
2631
proxy_worker_filename = get_subresource_location_js(js_target)
2632
else:
2633
# compiler output goes in .worker.js file
2634
move_file(js_target, shared.replace_suffix(js_target, get_worker_js_suffix()))
2635
worker_target_basename = target_basename + '.worker'
2636
proxy_worker_filename = (settings.PROXY_TO_WORKER_FILENAME or worker_target_basename) + '.js'
2637
2638
target_contents = worker_js_script(proxy_worker_filename)
2639
utils.write_file(target, target_contents, options.output_eol)
2640
2641
2642
def worker_js_script(proxy_worker_filename):
2643
web_gl_client_src = read_file(utils.path_from_root('src/webGLClient.js'))
2644
proxy_client_src = building.read_and_preprocess(utils.path_from_root('src/proxyClient.js'), expand_macros=True)
2645
if not settings.SINGLE_FILE and not os.path.dirname(proxy_worker_filename):
2646
proxy_worker_filename = './' + proxy_worker_filename
2647
proxy_client_src = do_replace(proxy_client_src, '<<< filename >>>', proxy_worker_filename)
2648
return web_gl_client_src + '\n' + proxy_client_src
2649
2650
2651
def find_library(lib, lib_dirs):
2652
for lib_dir in lib_dirs:
2653
path = os.path.join(lib_dir, lib)
2654
if os.path.isfile(path):
2655
logger.debug('found library "%s" at %s', lib, path)
2656
return path
2657
return None
2658
2659
2660
def map_to_js_libs(library_name):
2661
"""Given the name of a special Emscripten-implemented system library, returns an
2662
pair containing
2663
1. Array of absolute paths to JS library files, inside emscripten/src/ that corresponds to the
2664
library name. `None` means there is no mapping and the library will be processed by the linker
2665
as a require for normal native library.
2666
2. Optional name of a corresponding native library to link in.
2667
"""
2668
# Some native libraries are implemented in Emscripten as system side JS libraries
2669
library_map = {
2670
'embind': ['libembind.js', 'libemval.js'],
2671
'EGL': ['libegl.js'],
2672
'GL': ['libwebgl.js', 'libhtml5_webgl.js'],
2673
'webgl.js': ['libwebgl.js', 'libhtml5_webgl.js'],
2674
'GLESv2': ['libwebgl.js'],
2675
# N.b. there is no GLESv3 to link to (note [f] in https://www.khronos.org/registry/implementers_guide.html)
2676
'GLEW': ['libglew.js'],
2677
'glfw': ['libglfw.js'],
2678
'glfw3': ['libglfw.js'],
2679
'GLU': [],
2680
'glut': ['libglut.js'],
2681
'openal': ['libopenal.js'],
2682
'X11': ['libxlib.js'],
2683
'SDL': ['libsdl.js'],
2684
'uuid': ['libuuid.js'],
2685
'fetch': ['libfetch.js'],
2686
'websocket': ['libwebsocket.js'],
2687
# These 4 libraries are separate under glibc but are all rolled into
2688
# libc with musl. For compatibility with glibc we just ignore them
2689
# completely.
2690
'dl': [],
2691
'm': [],
2692
'rt': [],
2693
'pthread': [],
2694
# This is the name of GNU's C++ standard library. We ignore it here
2695
# for compatibility with GNU toolchains.
2696
'stdc++': [],
2697
'SDL2_mixer': [],
2698
}
2699
2700
if library_name in library_map:
2701
libs = library_map[library_name]
2702
logger.debug('Mapping library `%s` to JS libraries: %s' % (library_name, libs))
2703
return libs
2704
2705
return None
2706
2707
2708
def process_libraries(options, flags):
2709
new_flags = []
2710
system_libs_map = system_libs.Library.get_usable_variations()
2711
2712
# Process `-l` and `--js-library` flags
2713
for flag in flags:
2714
if flag.startswith('--js-library='):
2715
js_lib = flag.split('=', 1)[1]
2716
settings.JS_LIBRARIES.append(js_lib)
2717
continue
2718
if not flag.startswith('-l'):
2719
new_flags.append(flag)
2720
continue
2721
lib = removeprefix(flag, '-l')
2722
2723
logger.debug('looking for library "%s"', lib)
2724
2725
js_libs = map_to_js_libs(lib)
2726
if js_libs is not None:
2727
for l in js_libs:
2728
add_system_js_lib(l)
2729
2730
# We don't need to resolve system libraries to absolute paths here, we can just
2731
# let wasm-ld handle that. However, we do want to map to the correct variant.
2732
# For example we map `-lc` to `-lc-mt` if we are building with threading support.
2733
if 'lib' + lib in system_libs_map:
2734
lib = system_libs_map['lib' + lib].get_link_flag()
2735
new_flags.append(lib)
2736
continue
2737
2738
if js_libs is not None:
2739
continue
2740
2741
if lib.endswith('.js'):
2742
name = 'lib' + lib
2743
path = find_library(name, options.lib_dirs)
2744
if not path:
2745
exit_with_error(f'unable to find library {flag}')
2746
settings.JS_LIBRARIES.append(os.path.abspath(path))
2747
continue
2748
2749
static_lib = f'lib{lib}.a'
2750
if not settings.RELOCATABLE and not find_library(static_lib, options.lib_dirs):
2751
# Normally we can rely on the native linker to expand `-l` args.
2752
# However, emscripten also supports `.so` files that are actually just
2753
# regular object file. This means we need to support `.so` files even
2754
# when statically linking. The native linker (wasm-ld) will otherwise
2755
# ignore .so files in this mode.
2756
found_dylib = False
2757
for ext in DYLIB_EXTENSIONS:
2758
name = 'lib' + lib + ext
2759
path = find_library(name, options.lib_dirs)
2760
if path:
2761
found_dylib = True
2762
new_flags.append(path)
2763
break
2764
2765
if found_dylib:
2766
continue
2767
2768
new_flags.append(flag)
2769
2770
return new_flags
2771
2772
2773
def apply_library_settings(linker_args):
2774
for arg in linker_args:
2775
if not arg.startswith('-l'):
2776
continue
2777
library_name = arg[2:]
2778
settings_map = {
2779
'embind': {'EMBIND': 1},
2780
'glfw': {'USE_GLFW': 2},
2781
'glfw3': {'USE_GLFW': 3},
2782
'SDL': {'USE_SDL': 1},
2783
'SDL2_mixer': {'USE_SDL_MIXER': 2},
2784
}
2785
2786
if library_name in settings_map:
2787
for key, value in settings_map[library_name].items():
2788
default_setting(key, value)
2789
2790
2791
class ScriptSource:
2792
def __init__(self):
2793
self.src = None # if set, we have a script to load with a src attribute
2794
self.inline = None # if set, we have the contents of a script to write inline in a script
2795
2796
def un_src(self):
2797
"""Use this if you want to modify the script and need it to be inline."""
2798
if self.src is None:
2799
return
2800
quoted_src = quote(self.src)
2801
if settings.EXPORT_ES6:
2802
self.inline = f'''
2803
import("./{quoted_src}").then(exports => exports.default(Module))
2804
'''
2805
else:
2806
self.inline = f'''
2807
var script = document.createElement('script');
2808
script.src = "{quoted_src}";
2809
document.body.appendChild(script);
2810
'''
2811
self.src = None
2812
2813
def replacement(self):
2814
"""Returns the script tag to replace the {{{ SCRIPT }}} tag in the target"""
2815
assert (self.src or self.inline) and not (self.src and self.inline)
2816
if self.src:
2817
src = quote(self.src)
2818
filename = f'./{src}'
2819
if settings.EXPORT_ES6:
2820
return f'''
2821
<script type="module">
2822
import initModule from "{filename}";
2823
initModule(Module);
2824
</script>
2825
'''
2826
else:
2827
return f'<script async type="text/javascript" src="{src}"></script>'
2828
else:
2829
return f'<script id="mainScript">\n{self.inline}\n</script>'
2830
2831
2832
def filter_out_fake_dynamic_libs(options, inputs):
2833
# Filters out "fake" dynamic libraries that are really just intermediate object files.
2834
def is_fake_dylib(input_file):
2835
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and os.path.exists(input_file) and not building.is_wasm_dylib(input_file):
2836
if not options.ignore_dynamic_linking:
2837
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))
2838
return True
2839
else:
2840
return False
2841
2842
return [f for f in inputs if not is_fake_dylib(f)]
2843
2844
2845
def filter_out_duplicate_dynamic_libs(inputs):
2846
seen = set()
2847
2848
# Filter out duplicate "fake" shared libraries (intermediate object files).
2849
# See test_core.py:test_redundant_link
2850
def check(input_file):
2851
if get_file_suffix(input_file) in DYLIB_EXTENSIONS and not building.is_wasm_dylib(input_file):
2852
abspath = os.path.abspath(input_file)
2853
if abspath in seen:
2854
return False
2855
seen.add(abspath)
2856
return True
2857
2858
return [f for f in inputs if check(f)]
2859
2860
2861
def process_dynamic_libs(dylibs, lib_dirs):
2862
extras = []
2863
seen = set()
2864
to_process = dylibs.copy()
2865
while to_process:
2866
dylib = to_process.pop()
2867
dylink = webassembly.parse_dylink_section(dylib)
2868
for needed in dylink.needed:
2869
if needed in seen:
2870
continue
2871
path = find_library(needed, lib_dirs)
2872
if path:
2873
extras.append(path)
2874
seen.add(needed)
2875
else:
2876
exit_with_error(f'{os.path.normpath(dylib)}: shared library dependency not found in library path: `{needed}`. (library path: {lib_dirs}')
2877
to_process.append(path)
2878
2879
dylibs += extras
2880
for dylib in dylibs:
2881
exports = webassembly.get_exports(dylib)
2882
exports = {e.name for e in exports}
2883
# EM_JS function are exports with a special prefix. We need to strip
2884
# this prefix to get the actual symbol name. For the main module, this
2885
# is handled by extract_metadata.py.
2886
exports = [removeprefix(e, '__em_js__') for e in exports]
2887
settings.SIDE_MODULE_EXPORTS.extend(sorted(exports))
2888
2889
imports = webassembly.get_imports(dylib)
2890
imports = [i.field for i in imports if i.kind in (webassembly.ExternType.FUNC, webassembly.ExternType.GLOBAL, webassembly.ExternType.TAG)]
2891
# For now we ignore `invoke_` functions imported by side modules and rely
2892
# on the dynamic linker to create them on the fly.
2893
# TODO(sbc): Integrate with metadata.invoke_funcs that comes from the
2894
# main module to avoid creating new invoke functions at runtime.
2895
imports = set(imports)
2896
imports = {i for i in imports if not i.startswith('invoke_')}
2897
weak_imports = webassembly.get_weak_imports(dylib)
2898
strong_imports = sorted(imports.difference(weak_imports))
2899
logger.debug('Adding symbols requirements from `%s`: %s', dylib, imports)
2900
2901
mangled_imports = [shared.asmjs_mangle(e) for e in sorted(imports)]
2902
mangled_strong_imports = [shared.asmjs_mangle(e) for e in strong_imports]
2903
for sym in weak_imports:
2904
mangled = shared.asmjs_mangle(sym)
2905
if mangled not in settings.SIDE_MODULE_IMPORTS and mangled not in building.user_requested_exports:
2906
settings.WEAK_IMPORTS.append(sym)
2907
settings.SIDE_MODULE_IMPORTS.extend(mangled_imports)
2908
settings.EXPORT_IF_DEFINED.extend(sorted(imports))
2909
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(sorted(imports))
2910
building.user_requested_exports.update(mangled_strong_imports)
2911
2912
2913
def unmangle_symbols_from_cmdline(symbols):
2914
def unmangle(x):
2915
return x.replace('.', ' ').replace('#', '&').replace('?', ',')
2916
2917
if type(symbols) is list:
2918
return [unmangle(x) for x in symbols]
2919
return unmangle(symbols)
2920
2921
2922
def get_secondary_target(target, ext):
2923
# Depending on the output format emscripten creates zero or more secondary
2924
# output files (e.g. the .wasm file when creating JS output, or the
2925
# .js and the .wasm file when creating html output.
2926
# Thus function names the secondary output files, while ensuring they
2927
# never collide with the primary one.
2928
base = unsuffixed(target)
2929
if get_file_suffix(target) == ext:
2930
base += '_'
2931
return base + ext
2932
2933
2934
def dedup_list(lst):
2935
# Since we require python 3.6, that ordering of dictionaries is guaranteed
2936
# to be insertion order so we can use 'dict' here but not 'set'.
2937
return list(dict.fromkeys(lst))
2938
2939
2940
def check_output_file(f):
2941
if os.path.isdir(f):
2942
exit_with_error(f'cannot write output file `{f}`: Is a directory')
2943
2944
2945
def move_file(src, dst):
2946
logging.debug('move: %s -> %s', src, dst)
2947
check_output_file(dst)
2948
src = os.path.abspath(src)
2949
dst = os.path.abspath(dst)
2950
if src == dst:
2951
return
2952
if dst == os.devnull:
2953
return
2954
shutil.move(src, dst)
2955
2956
2957
# Returns the subresource location for run-time access
2958
def get_subresource_location(path, mimetype='application/octet-stream'):
2959
if settings.SINGLE_FILE:
2960
return f'data:{mimetype};base64,{base64_encode(path)}'
2961
else:
2962
return os.path.basename(path)
2963
2964
2965
def get_subresource_location_js(path):
2966
return get_subresource_location(path, 'text/javascript')
2967
2968
2969
@ToolchainProfiler.profile()
2970
def package_files(options, target):
2971
rtn = []
2972
logger.debug('setting up files')
2973
file_args = ['--from-emcc']
2974
if options.preload_files:
2975
file_args.append('--preload')
2976
file_args += options.preload_files
2977
if options.embed_files:
2978
file_args.append('--embed')
2979
file_args += options.embed_files
2980
if options.exclude_files:
2981
file_args.append('--exclude')
2982
file_args += options.exclude_files
2983
if options.use_preload_cache:
2984
file_args.append('--use-preload-cache')
2985
if settings.LZ4:
2986
file_args.append('--lz4')
2987
if options.use_preload_plugins:
2988
file_args.append('--use-preload-plugins')
2989
if not settings.ENVIRONMENT_MAY_BE_NODE:
2990
file_args.append('--no-node')
2991
if options.embed_files:
2992
if settings.MEMORY64:
2993
file_args += ['--wasm64']
2994
object_file = in_temp('embedded_files.o')
2995
file_args += ['--obj-output=' + object_file]
2996
rtn.append(object_file)
2997
2998
cmd = building.get_command_with_possible_response_file(
2999
[shared.FILE_PACKAGER, shared.replace_suffix(target, '.data')] + file_args)
3000
if options.preload_files:
3001
# Preloading files uses --pre-js code that runs before the module is loaded.
3002
file_code = shared.check_call(cmd, stdout=PIPE).stdout
3003
js_manipulation.add_files_pre_js(settings.PRE_JS_FILES, file_code)
3004
else:
3005
# Otherwise, we are embedding files, which does not require --pre-js code,
3006
# and instead relies on a static constructor to populate the filesystem.
3007
shared.check_call(cmd)
3008
3009
return rtn
3010
3011
3012
@ToolchainProfiler.profile_block('calculate linker inputs')
3013
def phase_calculate_linker_inputs(options, linker_args):
3014
using_lld = not (options.oformat == OFormat.OBJECT and settings.LTO)
3015
3016
linker_args = filter_link_flags(linker_args, using_lld)
3017
3018
# If we are linking to an intermediate object then ignore other
3019
# "fake" dynamic libraries, since otherwise we will end up with
3020
# multiple copies in the final executable.
3021
if options.oformat == OFormat.OBJECT or options.ignore_dynamic_linking:
3022
linker_args = filter_out_fake_dynamic_libs(options, linker_args)
3023
else:
3024
linker_args = filter_out_duplicate_dynamic_libs(linker_args)
3025
3026
if settings.MAIN_MODULE:
3027
dylibs = [a for a in linker_args if building.is_wasm_dylib(a)]
3028
process_dynamic_libs(dylibs, options.lib_dirs)
3029
3030
return linker_args
3031
3032
3033
def calc_extra_ldflags(options):
3034
extra_args = []
3035
system_libpath = str(cache.get_lib_dir(absolute=True))
3036
system_js_path = utils.path_from_root('src', 'lib')
3037
options.lib_dirs.append(system_libpath)
3038
options.lib_dirs.append(system_js_path)
3039
extra_args.append('-L' + system_libpath)
3040
extra_args.append('-L' + system_js_path)
3041
3042
if settings.FETCH:
3043
extra_args.append('-lfetch')
3044
if settings.STB_IMAGE:
3045
extra_args.append('-lstb_image')
3046
if settings.WASMFS and settings.NODERAWFS:
3047
# wasmfs will be included normally in system_libs.py, but we must include
3048
# noderawfs in a forced manner so that it is always linked in (the hook it
3049
# implements can remain unimplemented, so it won't be linked in
3050
# automatically)
3051
# TODO: find a better way to do this
3052
extra_args.append('--whole-archive')
3053
extra_args.append('-lwasmfs_noderawfs')
3054
extra_args.append('--no-whole-archive')
3055
3056
return extra_args
3057
3058
3059
def run_post_link(wasm_input, options, linker_args):
3060
settings.limit_settings(None)
3061
target, wasm_target = phase_linker_setup(options, linker_args)
3062
process_libraries(options, linker_args)
3063
phase_post_link(options, wasm_input, wasm_target, target, {})
3064
3065
3066
def run(options, linker_args):
3067
# We have now passed the compile phase, allow reading/writing of all settings.
3068
settings.limit_settings(None)
3069
3070
if not linker_args:
3071
exit_with_error('no input files')
3072
3073
if options.output_file and options.output_file.startswith('-'):
3074
exit_with_error(f'invalid output filename: `{options.output_file}`')
3075
3076
target, wasm_target = phase_linker_setup(options, linker_args)
3077
3078
linker_args = process_libraries(options, linker_args)
3079
3080
# Link object files using wasm-ld or llvm-link (for bitcode linking)
3081
linker_args = phase_calculate_linker_inputs(options, linker_args)
3082
3083
# Embed and preload files
3084
if len(options.preload_files) or len(options.embed_files):
3085
linker_args += package_files(options, target)
3086
3087
if options.oformat == OFormat.OBJECT:
3088
logger.debug(f'link_to_object: {linker_args} -> {target}')
3089
building.link_to_object(linker_args, target)
3090
logger.debug('stopping after linking to object file')
3091
return 0
3092
3093
linker_args += phase_calculate_system_libraries(options)
3094
3095
js_syms = {}
3096
if (not settings.SIDE_MODULE or settings.ASYNCIFY) and not shared.SKIP_SUBPROCS:
3097
js_info = get_js_sym_info()
3098
if not settings.SIDE_MODULE:
3099
js_syms = js_info['deps']
3100
if settings.LINKABLE:
3101
for native_deps in js_syms.values():
3102
settings.REQUIRED_EXPORTS += native_deps
3103
else:
3104
def add_js_deps(sym):
3105
if sym in js_syms:
3106
native_deps = js_syms[sym]
3107
if native_deps:
3108
settings.REQUIRED_EXPORTS += native_deps
3109
3110
for sym in settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE:
3111
add_js_deps(sym)
3112
for sym in js_info['extraLibraryFuncs']:
3113
add_js_deps(sym)
3114
for sym in settings.EXPORTED_RUNTIME_METHODS:
3115
add_js_deps(shared.demangle_c_symbol_name(sym))
3116
for sym in settings.EXPORTED_FUNCTIONS:
3117
add_js_deps(shared.demangle_c_symbol_name(sym))
3118
if settings.ASYNCIFY:
3119
settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:]
3120
settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']]
3121
3122
base_metadata = phase_link(linker_args, wasm_target, js_syms)
3123
3124
# Special handling for when the user passed '-Wl,--version'. In this case the linker
3125
# does not create the output file, but just prints its version and exits with 0.
3126
if '--version' in linker_args:
3127
return 0
3128
3129
# TODO(sbc): In theory we should really run the whole pipeline even if the output is
3130
# /dev/null, but that will take some refactoring
3131
if target == os.devnull:
3132
return 0
3133
3134
# Perform post-link steps (unless we are running bare mode)
3135
if options.oformat != OFormat.BARE:
3136
phase_post_link(options, wasm_target, wasm_target, target, js_syms, base_metadata)
3137
3138
return 0
3139
3140