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