Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/emscripten.py
6162 views
1
# Copyright 2010 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
"""A small wrapper script around the core JS compiler. This calls that
7
compiler with the settings given to it. It can also read data from C/C++
8
header files (so that the JS compiler can see the constants in those
9
headers, for the libc implementation in JS).
10
"""
11
12
import glob
13
import hashlib
14
import json
15
import logging
16
import os
17
import pprint
18
import shutil
19
import subprocess
20
import sys
21
import textwrap
22
23
from tools import (
24
building,
25
cache,
26
config,
27
diagnostics,
28
extract_metadata,
29
filelock,
30
js_manipulation,
31
shared,
32
utils,
33
webassembly,
34
)
35
from tools.settings import settings, user_settings
36
from tools.shared import DEBUG, asmjs_mangle, in_temp
37
from tools.toolchain_profiler import ToolchainProfiler
38
from tools.utils import exit_with_error, path_from_root
39
40
sys.path.append(path_from_root('third_party'))
41
import leb128
42
43
logger = logging.getLogger('emscripten')
44
45
# helper functions for JS to call into C to do memory operations. these
46
# let us sanitize memory access from the JS side, by calling into C where
47
# it has been instrumented.
48
ASAN_C_HELPERS = ['__asan_loadN', '__asan_storeN']
49
50
51
def maybe_disable_filesystem(imports):
52
"""Disables filesystem if only a limited subset of syscalls is used.
53
54
Our syscalls are static, and so if we see a very limited set of them - in particular,
55
no open() syscall and just simple writing - then we don't need full filesystem support.
56
If FORCE_FILESYSTEM is set, we can't do this. We also don't do it if INCLUDE_FULL_LIBRARY, since
57
not including the filesystem would mean not including the full JS libraries, and the same for
58
MAIN_MODULE=1 since a side module might need the filesystem.
59
"""
60
if any(settings[s] for s in ('FORCE_FILESYSTEM', 'INCLUDE_FULL_LIBRARY')):
61
return
62
if settings.MAIN_MODULE == 1:
63
return
64
65
if settings.FILESYSTEM == 0:
66
# without filesystem support, it doesn't matter what syscalls need
67
settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
68
else:
69
# TODO(sbc): Find a better way to identify wasi syscalls
70
syscall_prefixes = ('__syscall_', 'fd_')
71
side_module_imports = [shared.demangle_c_symbol_name(s) for s in settings.SIDE_MODULE_IMPORTS]
72
all_imports = set(imports).union(side_module_imports)
73
syscalls = {d for d in all_imports if d.startswith(syscall_prefixes) or d == 'path_open'}
74
# check if the only filesystem syscalls are in: close, ioctl, llseek, write
75
# (without open, etc.. nothing substantial can be done, so we can disable
76
# extra filesystem support in that case)
77
if syscalls.issubset({
78
'__syscall_ioctl',
79
'fd_seek',
80
'fd_write',
81
'fd_close',
82
'fd_fdstat_get',
83
}):
84
if DEBUG:
85
logger.debug('very limited syscalls (%s) so disabling full filesystem support', ', '.join(str(s) for s in syscalls))
86
settings.SYSCALLS_REQUIRE_FILESYSTEM = 0
87
88
89
def is_int(x):
90
try:
91
int(x)
92
return True
93
except ValueError:
94
return False
95
96
97
def align_memory(addr):
98
return (addr + 15) & -16
99
100
101
def update_settings_glue(wasm_file, metadata, base_metadata):
102
maybe_disable_filesystem(metadata.imports)
103
104
# Integrate info from backend
105
if settings.SIDE_MODULE:
106
# we don't need any JS library contents in side modules
107
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = []
108
else:
109
syms = settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE + metadata.imports
110
syms = set(syms).difference(metadata.all_exports)
111
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = sorted(syms)
112
if settings.MAIN_MODULE:
113
settings.WEAK_IMPORTS += webassembly.get_weak_imports(wasm_file)
114
115
if base_metadata:
116
settings.WASM_EXPORTS = base_metadata.all_exports
117
else:
118
settings.WASM_EXPORTS = metadata.all_exports
119
settings.HAVE_EM_ASM = bool(settings.MAIN_MODULE or len(metadata.em_asm_consts) != 0)
120
121
if settings.MAIN_MODULE and settings.ASYNCIFY == 1:
122
# These will be exported from Wasm, but only once we run the asyncify pass.
123
settings.WASM_EXPORTS += ['__asyncify_state', '__asyncify_data']
124
125
# start with the MVP features, and add any detected features.
126
building.binaryen_features = ['--mvp-features'] + metadata.features
127
if settings.ASYNCIFY == 2:
128
building.binaryen_features += ['--enable-reference-types']
129
130
if settings.PTHREADS:
131
assert '--enable-threads' in building.binaryen_features
132
if settings.MEMORY64:
133
assert '--enable-memory64' in building.binaryen_features
134
135
settings.HAS_MAIN = bool(settings.MAIN_MODULE) or settings.PROXY_TO_PTHREAD or settings.STANDALONE_WASM or 'main' in settings.WASM_EXPORTS or '__main_argc_argv' in settings.WASM_EXPORTS
136
if settings.HAS_MAIN and not settings.MINIMAL_RUNTIME:
137
# Dependencies of `callMain`
138
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$exitJS', '$handleException']
139
140
# When using dynamic linking the main function might be in a side module.
141
# To be safe assume they do take input parameters.
142
settings.MAIN_READS_PARAMS = metadata.main_reads_params or bool(settings.MAIN_MODULE)
143
if settings.MAIN_READS_PARAMS and not settings.STANDALONE_WASM:
144
# callMain depends on this library function
145
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$stringToUTF8OnStack']
146
147
if settings.STACK_OVERFLOW_CHECK and not settings.SIDE_MODULE:
148
# writeStackCookie and checkStackCookie both rely on emscripten_stack_get_end being
149
# exported. In theory it should always be present since its defined in compiler-rt.
150
assert 'emscripten_stack_get_end' in metadata.function_exports
151
152
for deps in metadata.js_deps:
153
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(deps.split(','))
154
155
156
def apply_static_code_hooks(forwarded_json, code):
157
def inject_code_hooks(name):
158
nonlocal code
159
hook_code = forwarded_json[name]
160
if hook_code:
161
hook_code = f'// Begin {name} hooks\n {hook_code}\n // End {name} hooks'
162
else:
163
hook_code = f'// No {name} hooks'
164
code = code.replace(f'<<< {name} >>>', hook_code)
165
166
inject_code_hooks('ATMODULES')
167
inject_code_hooks('ATPRERUNS')
168
inject_code_hooks('ATINITS')
169
inject_code_hooks('ATPOSTCTORS')
170
if settings.HAS_MAIN:
171
inject_code_hooks('ATMAINS')
172
if not settings.MINIMAL_RUNTIME or settings.HAS_MAIN:
173
inject_code_hooks('ATPOSTRUNS')
174
if settings.EXIT_RUNTIME:
175
inject_code_hooks('ATEXITS')
176
177
return code
178
179
180
@ToolchainProfiler.profile()
181
def generate_js_compiler_input_hash(symbols_only=False):
182
# We define a cache hit as when all the settings and all the `--js-library`
183
# contents are identical.
184
# Ignore certain settings that are not relevant to library deps. Here we
185
# skip PRE_JS_FILES/POST_JS_FILES which don't affect the library symbol list
186
# and can contain full paths to temporary files.
187
skip_settings = {'PRE_JS_FILES', 'POST_JS_FILES'}
188
file_contents = [json.dumps(settings.external_dict(skip_keys=skip_settings), sort_keys=True, indent=2)]
189
190
files = glob.glob(utils.path_from_root('src/lib') + '/lib*.js')
191
# Also, include the js compiler code itself, in case it gets locally modified.
192
files += glob.glob(utils.path_from_root('src/*.mjs'))
193
files += settings.JS_LIBRARIES
194
if not symbols_only:
195
files += settings.PRE_JS_FILES
196
files += settings.POST_JS_FILES
197
# Also include other .js files that could be included in the output.
198
files += glob.glob(utils.path_from_root('src/*.js'))
199
200
for file in sorted(files):
201
file_contents.append(utils.read_file(file))
202
203
content = '\n'.join(file_contents)
204
content_hash = hashlib.sha1(content.encode('utf-8')).hexdigest()
205
return content_hash
206
207
208
@ToolchainProfiler.profile()
209
def compile_javascript(symbols_only=False):
210
stderr_file = os.environ.get('EMCC_STDERR_FILE')
211
if stderr_file:
212
stderr_file = os.path.abspath(stderr_file)
213
logger.info('logging stderr in js compiler phase into %s' % stderr_file)
214
stderr_file = open(stderr_file, 'w')
215
216
# Save settings to a file to work around v8 issue 1579
217
settings_json = json.dumps(settings.external_dict(), sort_keys=True, indent=2)
218
building.write_intermediate(settings_json, 'settings.json')
219
220
# Call js compiler. Passing `-` here mean read the settings from stdin.
221
args = ['-']
222
if symbols_only:
223
args += ['--symbols-only']
224
return shared.run_js_tool(path_from_root('tools/compiler.mjs'),
225
args, input=settings_json, stdout=subprocess.PIPE, stderr=stderr_file)
226
227
228
def set_memory(static_bump):
229
stack_low = align_memory(settings.GLOBAL_BASE + static_bump)
230
stack_high = align_memory(stack_low + settings.STACK_SIZE)
231
settings.STACK_HIGH = stack_high
232
settings.STACK_LOW = stack_low
233
settings.HEAP_BASE = align_memory(stack_high)
234
235
236
def report_missing_exports_wasm_only(metadata):
237
if diagnostics.is_enabled('undefined'):
238
defined_symbols = {asmjs_mangle(e) for e in metadata.all_exports}
239
missing = set(settings.USER_EXPORTS) - defined_symbols
240
for symbol in sorted(missing):
241
diagnostics.warning('undefined', f'undefined exported symbol: "{symbol}"')
242
243
244
def report_missing_exports(js_symbols):
245
if diagnostics.is_enabled('undefined'):
246
# Report any symbol that was explicitly exported but is present neither
247
# as a native function nor as a JS library function.
248
defined_symbols = {asmjs_mangle(e) for e in settings.WASM_EXPORTS}.union(js_symbols)
249
missing = set(settings.USER_EXPORTS) - defined_symbols
250
for symbol in sorted(missing):
251
diagnostics.warning('undefined', f'undefined exported symbol: "{symbol}"')
252
253
# Special handling for the `_main` symbol
254
255
if settings.STANDALONE_WASM:
256
# standalone mode doesn't use main, and it always reports missing entry point at link time.
257
# In this mode we never expect _main in the export list.
258
return
259
260
if settings.IGNORE_MISSING_MAIN:
261
# The default mode for emscripten is to ignore the missing main function allowing
262
# maximum compatibility.
263
return
264
265
if settings.EXPECT_MAIN:
266
all_exports = settings.WASM_EXPORTS + settings.SIDE_MODULE_EXPORTS
267
if 'main' not in all_exports and '__main_argc_argv' not in all_exports:
268
# For compatibility with the output of wasm-ld we use the same wording here in our
269
# error message as if wasm-ld had failed.
270
exit_with_error('entry symbol not defined (pass --no-entry to suppress): main')
271
272
273
# Test if the parentheses at body[openIdx] and body[closeIdx] are a match to
274
# each other.
275
def parentheses_match(body, openIdx, closeIdx):
276
if closeIdx < 0:
277
closeIdx += len(body)
278
count = 1
279
for i in range(openIdx + 1, closeIdx + 1):
280
if body[i] == body[openIdx]:
281
count += 1
282
elif body[i] == body[closeIdx]:
283
count -= 1
284
if count <= 0:
285
return i == closeIdx
286
return False
287
288
289
def trim_asm_const_body(body):
290
body = body.strip()
291
orig = None
292
while orig != body:
293
orig = body
294
if len(body) > 1 and body[0] == '"' and body[-1] == '"':
295
body = body[1:-1].replace('\\"', '"').strip()
296
if len(body) > 1 and body[0] == '{' and body[-1] == '}' and parentheses_match(body, 0, -1):
297
body = body[1:-1].strip()
298
if len(body) > 1 and body[0] == '(' and body[-1] == ')' and parentheses_match(body, 0, -1):
299
body = body[1:-1].strip()
300
return body
301
302
303
def get_cached_file(filetype, filename, generator, cache_limit):
304
"""This function implements a file cache which lives inside the main
305
emscripten cache directory but uses a per-file lock rather than a
306
cache-wide lock.
307
308
The cache is pruned (by removing the oldest files) if it grows above
309
a certain number of files.
310
"""
311
root = cache.get_path(filetype)
312
utils.safe_ensure_dirs(root)
313
314
cache_file = os.path.join(root, filename)
315
316
with filelock.FileLock(cache_file + '.lock'):
317
if os.path.exists(cache_file):
318
# Cache hit, read the file
319
file_content = utils.read_file(cache_file)
320
else:
321
# Cache miss, generate the symbol list and write the file
322
file_content = generator()
323
utils.write_file(cache_file, file_content)
324
325
if len([f for f in os.listdir(root) if not f.endswith('.lock')]) > cache_limit:
326
with filelock.FileLock(cache.get_path(f'{filetype}.lock')):
327
files = []
328
for f in os.listdir(root):
329
if not f.endswith('.lock'):
330
f = os.path.join(root, f)
331
files.append((f, os.path.getmtime(f)))
332
files.sort(key=lambda x: x[1])
333
# Delete all but the newest N files
334
for f, _ in files[:-cache_limit]:
335
with filelock.FileLock(f + '.lock'):
336
utils.delete_file(f)
337
338
return file_content
339
340
341
@ToolchainProfiler.profile()
342
def compile_javascript_cached():
343
# Avoiding using the cache when generating struct info since
344
# this step is performed while the cache is locked.
345
# Sadly we have to skip the caching whenever we have user JS libraries. This is because
346
# these libraries can import arbitrary other JS files (either vis node's `import` or via #include)
347
if DEBUG or settings.BOOTSTRAPPING_STRUCT_INFO or config.FROZEN_CACHE or settings.JS_LIBRARIES:
348
return compile_javascript()
349
350
content_hash = generate_js_compiler_input_hash()
351
352
# Limit of the overall size of the cache.
353
# This code will get test coverage since a full test run of `other` or `core`
354
# generates ~1000 unique outputs.
355
return get_cached_file('js_output', f'{content_hash}.js', compile_javascript, cache_limit=500)
356
357
358
def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadata=None):
359
# Overview:
360
# * Run wasm-emscripten-finalize to extract metadata and modify the binary
361
# to use emscripten's wasm<->JS ABI
362
# * Use the metadata to generate the JS glue that goes with the wasm
363
364
# set file locations, so that JS glue can find what it needs
365
settings.WASM_BINARY_FILE = js_manipulation.escape_for_js_string(os.path.basename(out_wasm))
366
367
if finalize:
368
metadata = finalize_wasm(in_wasm, out_wasm, js_syms)
369
else:
370
# Skip finalize and only extract the metadata.
371
if in_wasm != out_wasm:
372
shutil.copy(in_wasm, out_wasm)
373
metadata = get_metadata(in_wasm, out_wasm, False, [])
374
375
if settings.RELOCATABLE and settings.MEMORY64 == 2:
376
metadata.imports += ['__memory_base32']
377
378
# If the binary has already been finalized the settings have already been
379
# updated and we can skip updating them.
380
if finalize:
381
update_settings_glue(out_wasm, metadata, base_metadata)
382
383
if not settings.WASM_BIGINT and metadata.em_js_funcs:
384
for em_js_func, raw in metadata.em_js_funcs.items():
385
c_sig = raw.split('<::>')[0].strip('()')
386
if not c_sig or c_sig == 'void':
387
c_sig = []
388
else:
389
c_sig = c_sig.split(',')
390
signature = metadata.em_js_func_types.get(em_js_func)
391
if signature and len(signature.params) != len(c_sig):
392
diagnostics.warning('em-js-i64', 'using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `%s` (%s, %s)', em_js_func, c_sig, signature.params)
393
394
asm_consts = create_asm_consts(metadata)
395
em_js_funcs = create_em_js(metadata)
396
397
if settings.SIDE_MODULE:
398
# When building side modules, validate the EM_ASM and EM_JS string by running
399
# them through node. Without this step, syntax errors are not surfaced
400
# until runtime.
401
# We use subprocess directly here rather than shared.check_call since
402
# check_call doesn't support the `input` argument.
403
if asm_consts:
404
validate = '\n'.join([f'var tmp = {f};' for _, f in asm_consts])
405
proc = subprocess.run(config.NODE_JS + ['--check', '-'], input=validate.encode('utf-8'))
406
if proc.returncode:
407
exit_with_error(f'EM_ASM function validation failed (node returned {proc.returncode})')
408
409
if em_js_funcs:
410
validate = '\n'.join(em_js_funcs)
411
proc = subprocess.run(config.NODE_JS + ['--check', '-'], input=validate.encode('utf-8'))
412
if proc.returncode:
413
exit_with_error(f'EM_JS function validation failed (node returned {proc.returncode})')
414
415
if not outfile_js:
416
report_missing_exports_wasm_only(metadata)
417
logger.debug('emscript: skipping js glue generation')
418
return
419
420
for e in settings.EXPORTED_FUNCTIONS:
421
if not js_manipulation.isidentifier(e):
422
diagnostics.warning('js-compiler', f'export name is not a valid JS symbol: "{e}". Use `Module` or `wasmExports` to access this symbol')
423
424
# memory and global initializers
425
426
if settings.RELOCATABLE:
427
dylink_sec = webassembly.parse_dylink_section(in_wasm)
428
static_bump = align_memory(dylink_sec.mem_size)
429
set_memory(static_bump)
430
logger.debug('stack_low: %d, stack_high: %d, heap_base: %d', settings.STACK_LOW, settings.STACK_HIGH, settings.HEAP_BASE)
431
432
# When building relocatable output (e.g. MAIN_MODULE) the reported table
433
# size does not include the reserved slot at zero for the null pointer.
434
# So we need to offset the elements by 1.
435
if settings.INITIAL_TABLE == -1:
436
settings.INITIAL_TABLE = dylink_sec.table_size + 1
437
438
if metadata.invoke_funcs:
439
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getWasmTableEntry']
440
441
out = compile_javascript_cached()
442
assert '//FORWARDED_DATA:' in out, 'Did not receive forwarded data in pre output - process failed?'
443
glue, forwarded_data = out.split('//FORWARDED_DATA:', 1)
444
445
forwarded_json = json.loads(forwarded_data)
446
447
if forwarded_json['warnings']:
448
diagnostics.warning('js-compiler', 'warnings in JS library compilation')
449
450
pre, post = glue.split('// EMSCRIPTEN_END_FUNCS')
451
452
if settings.ASSERTIONS:
453
pre += "function checkIncomingModuleAPI() {\n"
454
for sym in settings.ALL_INCOMING_MODULE_JS_API:
455
if sym not in settings.INCOMING_MODULE_JS_API:
456
pre += f" ignoredModuleProp('{sym}');\n"
457
pre += "}\n"
458
459
report_missing_exports(forwarded_json['librarySymbols'])
460
461
asm_const_pairs = ['%s: %s' % (key, value) for key, value in asm_consts]
462
if asm_const_pairs or settings.MAIN_MODULE:
463
pre += 'var ASM_CONSTS = {\n ' + ', \n '.join(asm_const_pairs) + '\n};\n'
464
if em_js_funcs:
465
pre += '\n'.join(em_js_funcs) + '\n'
466
467
if base_metadata:
468
function_exports = base_metadata.function_exports
469
other_exports = base_metadata.other_exports
470
else:
471
function_exports = metadata.function_exports
472
other_exports = metadata.other_exports
473
474
other_exports = [e for e in other_exports if not shared.is_internal_global(e[0].name)]
475
476
if settings.ASYNCIFY == 1:
477
function_exports['asyncify_start_unwind'] = webassembly.FuncType([webassembly.Type.I32], [])
478
function_exports['asyncify_stop_unwind'] = webassembly.FuncType([], [])
479
function_exports['asyncify_start_rewind'] = webassembly.FuncType([webassembly.Type.I32], [])
480
function_exports['asyncify_stop_rewind'] = webassembly.FuncType([], [])
481
482
parts = [pre]
483
parts += create_module(metadata, function_exports, other_exports,
484
forwarded_json['librarySymbols'], forwarded_json['nativeAliases'])
485
parts.append(post)
486
settings.ALIASES = list(forwarded_json['nativeAliases'].keys())
487
488
full_js_module = ''.join(parts)
489
full_js_module = apply_static_code_hooks(forwarded_json, full_js_module)
490
utils.write_file(outfile_js, full_js_module)
491
492
metadata.library_definitions = forwarded_json['libraryDefinitions']
493
return metadata
494
495
496
@ToolchainProfiler.profile()
497
def get_metadata(infile, outfile, modify_wasm, args):
498
metadata = extract_metadata.extract_metadata(infile)
499
if modify_wasm:
500
# In some cases we still need to modify the wasm file
501
# using wasm-emscripten-finalize.
502
building.run_binaryen_command('wasm-emscripten-finalize',
503
infile=infile,
504
outfile=outfile,
505
args=args)
506
# When we do this we can generate new imports, so
507
# re-read parts of the metadata post-finalize
508
extract_metadata.update_metadata(outfile, metadata)
509
if DEBUG:
510
logger.debug("Metadata: " + pprint.pformat(metadata.__dict__))
511
return metadata
512
513
514
def finalize_wasm(infile, outfile, js_syms):
515
building.save_intermediate(infile, 'base.wasm')
516
args = []
517
518
# if we don't need to modify the wasm, don't tell finalize to emit a wasm file
519
modify_wasm = False
520
need_name_section = False
521
522
if settings.WASM2JS:
523
# wasm2js requires full legalization (and will do extra wasm binary
524
# later processing later anyhow)
525
modify_wasm = True
526
if settings.DEBUG_LEVEL >= 2 or settings.ASYNCIFY_ADD or settings.ASYNCIFY_ADVISE or settings.ASYNCIFY_ONLY or settings.ASYNCIFY_REMOVE or settings.EMIT_SYMBOL_MAP or settings.EMIT_NAME_SECTION:
527
need_name_section = True
528
args.append('-g')
529
if settings.WASM_BIGINT:
530
args.append('--bigint')
531
if settings.DYNCALLS:
532
# we need to add all dyncalls to the wasm
533
modify_wasm = True
534
else:
535
if settings.WASM_BIGINT:
536
args.append('--no-dyncalls')
537
else:
538
args.append('--dyncalls-i64')
539
# we need to add some dyncalls to the wasm
540
modify_wasm = True
541
if settings.AUTODEBUG:
542
# In AUTODEBUG mode we want to delay all legalization until later. This is hack
543
# to force wasm-emscripten-finalize not to do any legalization at all.
544
args.append('--bigint')
545
else:
546
if settings.LEGALIZE_JS_FFI:
547
# When we dynamically link our JS loader adds functions from wasm modules to
548
# the table. It must add the original versions of them, not legalized ones,
549
# so that indirect calls have the right type, so export those.
550
args += building.js_legalization_pass_flags()
551
modify_wasm = True
552
else:
553
args.append('--no-legalize-javascript-ffi')
554
if settings.SIDE_MODULE:
555
args.append('--side-module')
556
if settings.STACK_OVERFLOW_CHECK >= 2:
557
args.append('--check-stack-overflow')
558
modify_wasm = True
559
if settings.STANDALONE_WASM:
560
args.append('--standalone-wasm')
561
562
if settings.DEBUG_LEVEL >= 3:
563
args.append('--dwarf')
564
565
if infile != outfile:
566
shutil.copy(infile, outfile)
567
568
if settings.GENERATE_SOURCE_MAP:
569
building.emit_wasm_source_map(infile, outfile + '.map', outfile)
570
building.save_intermediate(outfile + '.map', 'base_wasm.map')
571
base_url = settings.SOURCE_MAP_BASE + os.path.basename(outfile) + '.map'
572
if modify_wasm:
573
# If we are already modifying, just let Binaryen add the sourcemap URL
574
args += ['--output-source-map-url=' + base_url]
575
else:
576
# Otherwise use objcopy. This avoids re-encoding the file (thus
577
# preserving DWARF) and is faster.
578
579
# Create a file with the contents of the sourceMappingURL section
580
with shared.get_temp_files().get_file('.bin') as url_file:
581
utils.write_binary(url_file,
582
leb128.u.encode(len(base_url)) + base_url.encode('utf-8'))
583
cmd = [shared.LLVM_OBJCOPY,
584
'--add-section',
585
'sourceMappingURL=' + url_file,
586
infile]
587
shared.check_call(cmd)
588
589
# For sections we no longer need, strip now to speed subsequent passes.
590
# If Binaryen is not needed, this is also our last chance to strip.
591
strip_sections = []
592
if not settings.EMIT_PRODUCERS_SECTION:
593
strip_sections += ['producers']
594
if not need_name_section:
595
strip_sections += ['name']
596
if not settings.GENERATE_DWARF:
597
strip_sections += ['.debug*']
598
599
if strip_sections:
600
building.save_intermediate(outfile, 'strip.wasm')
601
building.strip_sections(infile, outfile, strip_sections)
602
603
metadata = get_metadata(outfile, outfile, modify_wasm, args)
604
605
if settings.GENERATE_SOURCE_MAP:
606
building.save_intermediate(outfile + '.map', 'post_finalize.map')
607
608
expected_exports = set(settings.EXPORTED_FUNCTIONS)
609
expected_exports.update(asmjs_mangle(s) for s in settings.REQUIRED_EXPORTS)
610
expected_exports.update(asmjs_mangle(s) for s in settings.EXPORT_IF_DEFINED)
611
# Assume that when JS symbol dependencies are exported it is because they
612
# are needed by by a JS symbol and are not being explicitly exported due
613
# to EMSCRIPTEN_KEEPALIVE (llvm.used).
614
for deps in js_syms.values():
615
expected_exports.update(asmjs_mangle(s) for s in deps)
616
617
# Calculate the subset of exports that were explicitly marked as
618
# EMSCRIPTEN_KEEPALIVE (llvm.used).
619
# These are any exports that were not requested on the command line and are
620
# not known auto-generated system functions.
621
unexpected_exports = [e for e in metadata.all_exports if shared.is_user_export(e)]
622
unexpected_exports = [asmjs_mangle(e) for e in unexpected_exports]
623
unexpected_exports = [e for e in unexpected_exports if e not in expected_exports]
624
625
if not settings.STANDALONE_WASM and 'main' in metadata.all_exports or '__main_argc_argv' in metadata.all_exports:
626
if 'EXPORTED_FUNCTIONS' in user_settings and '_main' not in settings.USER_EXPORTS:
627
# If `_main` was unexpectedly exported we assume it was added to
628
# EXPORT_IF_DEFINED by `phase_linker_setup` in order that we can detect
629
# it and report this warning. After reporting the warning we explicitly
630
# ignore the export and run as if there was no main function since that
631
# is defined is behaviour for programs that don't include `_main` in
632
# EXPORTED_FUNCTIONS.
633
diagnostics.warning('unused-main', '`main` is defined in the input files, but `_main` is not in `EXPORTED_FUNCTIONS`. Add it to this list if you want `main` to run.')
634
if 'main' in metadata.all_exports:
635
metadata.all_exports.remove('main')
636
else:
637
metadata.all_exports.remove('__main_argc_argv')
638
else:
639
unexpected_exports.append('_main')
640
641
building.user_requested_exports.update(unexpected_exports)
642
settings.EXPORTED_FUNCTIONS.extend(unexpected_exports)
643
644
return metadata
645
646
647
def create_tsd_exported_runtime_methods(metadata):
648
# Use the TypeScript compiler to generate definitions for all of the runtime
649
# exports. The JS from the library any JS docs are included in the file used
650
# for generation.
651
js_doc = 'var RuntimeExports = {};\n'
652
for name in settings.EXPORTED_RUNTIME_METHODS:
653
docs = '/** @type {{any}} */'
654
snippet = ''
655
if name in metadata.library_definitions:
656
definition = metadata.library_definitions[name]
657
if definition['snippet']:
658
snippet = ' = ' + definition['snippet']
659
# Clear the doc so the type is either computed from the snippet or
660
# defined by the definition below.
661
docs = ''
662
if definition['docs']:
663
docs = definition['docs']
664
# TSC does not generate the correct type if there are jsdocs and nothing
665
# is assigned to the property.
666
if not snippet:
667
snippet = ' = null'
668
js_doc += f'{docs}\nRuntimeExports[\'{name}\']{snippet};\n'
669
670
js_doc_file = in_temp('jsdoc.js')
671
tsc_output_file = in_temp('jsdoc.d.ts')
672
utils.write_file(js_doc_file, js_doc)
673
tsc = shared.get_npm_cmd('tsc', missing_ok=True)
674
# Prefer the npm install'd version of tsc since we know that one is compatible
675
# with emscripten output.
676
if not tsc:
677
# Fall back to tsc in the user's PATH.
678
tsc = shutil.which('tsc')
679
if not tsc:
680
exit_with_error('tsc executable not found in node_modules or in $PATH')
681
# Use the full path from the which command so windows can find tsc.
682
tsc = [tsc]
683
cmd = tsc + ['--outFile', tsc_output_file,
684
'--skipLibCheck', # Avoid checking any of the user's types e.g. node_modules/@types.
685
'--declaration',
686
'--emitDeclarationOnly',
687
'--allowJs', js_doc_file]
688
shared.check_call(cmd, cwd=path_from_root())
689
return utils.read_file(tsc_output_file)
690
691
692
def create_tsd(metadata, embind_tsd):
693
out = '// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.\n'
694
if settings.EXPORTED_RUNTIME_METHODS:
695
out += create_tsd_exported_runtime_methods(metadata)
696
# Manually generate definitions for any Wasm function exports.
697
out += 'interface WasmModule {\n'
698
for name, functype in metadata.function_exports.items():
699
mangled = asmjs_mangle(name)
700
should_export = settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS
701
if not should_export:
702
continue
703
arguments = []
704
for index, type in enumerate(functype.params):
705
arguments.append(f"_{index}: {type_to_ts_type(type)}")
706
out += f' {mangled}({", ".join(arguments)}): '
707
assert len(functype.returns) <= 1, 'One return type only supported'
708
if functype.returns:
709
out += f'{type_to_ts_type(functype.returns[0])}'
710
else:
711
out += 'void'
712
out += ';\n'
713
out += '}\n'
714
out += f'\n{embind_tsd}'
715
# Combine all the various exports.
716
export_interfaces = 'WasmModule'
717
if settings.EXPORTED_RUNTIME_METHODS:
718
export_interfaces += ' & typeof RuntimeExports'
719
# Add in embind definitions.
720
if embind_tsd:
721
export_interfaces += ' & EmbindModule'
722
out += f'export type MainModule = {export_interfaces};\n'
723
if settings.MODULARIZE:
724
return_type = 'MainModule'
725
if settings.WASM_ASYNC_COMPILATION:
726
return_type = f'Promise<{return_type}>'
727
out += f'export default function MainModuleFactory (options?: unknown): {return_type};\n'
728
return out
729
730
731
def create_asm_consts(metadata):
732
asm_consts = {}
733
for addr, const in metadata.em_asm_consts.items():
734
body = trim_asm_const_body(const)
735
max_arity = 16
736
arity = 0
737
for i in range(max_arity):
738
if f'${i}' in const:
739
arity = i + 1
740
args = ', '.join(f'${i}' for i in range(arity))
741
if 'arguments' in body:
742
# arrow functions don't bind `arguments` so we have to use
743
# the old function syntax in this case
744
func = f'function({args}) {{ {body} }}'
745
else:
746
func = f'({args}) => {{ {body} }}'
747
if settings.RELOCATABLE:
748
addr += settings.GLOBAL_BASE
749
asm_consts[addr] = func
750
asm_consts = sorted(asm_consts.items())
751
return asm_consts
752
753
754
def type_to_sig(type):
755
# These should match the conversion in $sigToWasmTypes.
756
return {
757
webassembly.Type.I32: 'i',
758
webassembly.Type.I64: 'j',
759
webassembly.Type.F32: 'f',
760
webassembly.Type.F64: 'd',
761
webassembly.Type.EXTERNREF: 'e',
762
webassembly.Type.VOID: 'v',
763
}[type]
764
765
766
def type_to_ts_type(type):
767
return {
768
webassembly.Type.I32: 'number',
769
webassembly.Type.I64: 'BigInt',
770
webassembly.Type.F32: 'number',
771
webassembly.Type.F64: 'number',
772
webassembly.Type.EXTERNREF: 'any',
773
webassembly.Type.VOID: 'void',
774
}[type]
775
776
777
def func_type_to_sig(type):
778
parameters = [type_to_sig(param) for param in type.params]
779
if type.returns:
780
assert len(type.returns) == 1, "One return type expected."
781
ret = type.returns[0]
782
else:
783
ret = webassembly.Type.VOID
784
return type_to_sig(ret) + ''.join(parameters)
785
786
787
def create_em_js(metadata):
788
em_js_funcs = []
789
separator = '<::>'
790
for name, raw in metadata.em_js_funcs.items():
791
assert separator in raw
792
args, body = raw.split(separator, 1)
793
args = args[1:-1]
794
if args == 'void':
795
args = []
796
else:
797
args = args.split(',')
798
arg_names = [arg.split()[-1].replace('*', '') for arg in args if arg]
799
args = ','.join(arg_names)
800
func = f'function {name}({args}) {body}'
801
if settings.WASM_ESM_INTEGRATION:
802
# Like JS library function EM_JS functions are exported at the point of
803
# declaration in WASM_ESM_INTEGRATION node.
804
func = f'export {func}'
805
em_js_funcs.append(func)
806
if (settings.MAIN_MODULE or settings.ASYNCIFY == 2) and name in metadata.em_js_func_types:
807
sig = func_type_to_sig(metadata.em_js_func_types[name])
808
sig = f'{name}.sig = \'{sig}\';'
809
em_js_funcs.append(sig)
810
811
return em_js_funcs
812
813
814
def add_standard_wasm_imports(send_items_map):
815
extra_sent_items = []
816
817
if settings.SAFE_HEAP:
818
extra_sent_items.append('segfault')
819
extra_sent_items.append('alignfault')
820
821
# Special case for importing memory and table
822
# TODO(sbc): can we make these into normal library symbols?
823
if settings.IMPORTED_MEMORY:
824
send_items_map['memory'] = 'wasmMemory'
825
826
if settings.RELOCATABLE:
827
send_items_map['__indirect_function_table'] = 'wasmTable'
828
if settings.MEMORY64:
829
send_items_map['__table_base32'] = '___table_base32'
830
831
if settings.AUTODEBUG:
832
extra_sent_items += [
833
'log_execution',
834
'get_i32',
835
'get_i64',
836
'get_f32',
837
'get_f64',
838
'get_funcref',
839
'get_externref',
840
'get_anyref',
841
'get_exnref',
842
'set_i32',
843
'set_i64',
844
'set_f32',
845
'set_f64',
846
'set_funcref',
847
'set_externref',
848
'set_anyref',
849
'set_exnref',
850
'load_ptr',
851
'load_val_i32',
852
'load_val_i64',
853
'load_val_f32',
854
'load_val_f64',
855
'store_ptr',
856
'store_val_i32',
857
'store_val_i64',
858
'store_val_f32',
859
'store_val_f64',
860
'memory_grow_pre',
861
'memory_grow_post',
862
]
863
864
if settings.SPLIT_MODULE and settings.ASYNCIFY == 2:
865
# Calls to this function are generated by binaryen so it must be manually
866
# imported.
867
extra_sent_items.append('__load_secondary_module')
868
869
for s in extra_sent_items:
870
send_items_map[s] = s
871
872
873
def create_sending(metadata, library_symbols):
874
# Map of wasm imports to mangled/external/JS names
875
send_items_map = {name: name for name in metadata.invoke_funcs}
876
877
for name in metadata.imports:
878
if name in metadata.em_js_funcs:
879
# EM_JS functions are exported directly at the declaration site in
880
# WASM_ESM_INTEGRATION mode.
881
if not settings.WASM_ESM_INTEGRATION:
882
send_items_map[name] = name
883
else:
884
send_items_map[name] = asmjs_mangle(name)
885
886
add_standard_wasm_imports(send_items_map)
887
888
if settings.MAIN_MODULE:
889
# When including dynamic linking support, also add any JS library functions
890
# that are part of EXPORTED_FUNCTIONS (or in the case of MAIN_MODULE=1 add
891
# all JS library functions). This allows `dlsym(RTLD_DEFAULT)` to lookup JS
892
# library functions, since `wasmImports` acts as the global symbol table.
893
wasm_exports = set(metadata.function_exports)
894
library_symbols = set(library_symbols)
895
if settings.MAIN_MODULE == 1:
896
for f in library_symbols:
897
if shared.is_c_symbol(f):
898
demangled = shared.demangle_c_symbol_name(f)
899
if demangled in wasm_exports:
900
continue
901
send_items_map[demangled] = f
902
else:
903
for f in settings.EXPORTED_FUNCTIONS + settings.SIDE_MODULE_IMPORTS:
904
if f in library_symbols and shared.is_c_symbol(f):
905
demangled = shared.demangle_c_symbol_name(f)
906
if demangled in wasm_exports:
907
continue
908
send_items_map[demangled] = f
909
910
sorted_items = sorted(send_items_map.items())
911
912
if settings.WASM_ESM_INTEGRATION:
913
elems = []
914
for k, v in sorted_items:
915
elems.append(f'{v} as {k}')
916
elems = ',\n '.join(elems)
917
exports = '// Export JS functions to the wasm module with demangled names.\n'
918
exports += f"export {{\n {elems}\n}};"
919
return exports
920
921
prefix = ''
922
if settings.MAYBE_CLOSURE_COMPILER:
923
# This prevents closure compiler from minifying the field names in this
924
# object.
925
prefix = '/** @export */\n '
926
927
elems = []
928
for k, v in sorted_items:
929
if k == v:
930
elems.append(f'{prefix}{k}')
931
else:
932
elems.append(f'{prefix}{k}: {v}')
933
934
return '{\n ' + ',\n '.join(elems) + '\n}'
935
936
937
def create_reexports(metadata):
938
assert settings.WASM_ESM_INTEGRATION
939
exports = '// Re-export imported wasm functions to the JS entry point. These are user-facing and underscore mangled.\n'
940
wasm_exports = []
941
for exp in building.user_requested_exports:
942
if shared.is_c_symbol(exp):
943
demangled = shared.demangle_c_symbol_name(exp)
944
if demangled in metadata.em_js_funcs:
945
wasm_exports.append(f'{demangled} as {exp}')
946
elif demangled in settings.WASM_EXPORTS:
947
wasm_exports.append(exp)
948
elif demangled == 'main' and '__main_argc_argv' in settings.WASM_EXPORTS:
949
wasm_exports.append('_main')
950
exports += f"export {{ {', '.join(wasm_exports)} }};"
951
return exports
952
953
954
def install_debug_wrapper(sym):
955
if settings.MINIMAL_RUNTIME or not settings.ASSERTIONS:
956
return False
957
# The emscripten stack functions are called very early (by writeStackCookie) before
958
# the runtime is initialized so we can't create these wrappers that check for
959
# runtimeInitialized.
960
if sym.startswith(('__asan_', '__lsan_', 'emscripten_stack_', '_emscripten_stack_')):
961
return False
962
# `__trap` can occur before the runtime is initialized since it is used in abort.
963
# `emscripten_get_sbrk_ptr` can be called prior to runtime initialization by
964
# the dynamic linking code.
965
return sym not in ['__trap', 'emscripten_get_sbrk_ptr']
966
967
968
def should_export(sym):
969
return settings.EXPORT_ALL or (settings.EXPORT_KEEPALIVE and sym in settings.EXPORTED_FUNCTIONS)
970
971
972
def create_receiving(function_exports, other_exports, library_symbols, aliases):
973
generate_dyncall_assignment = 'dynCalls' in library_symbols
974
receiving = ['\n// Imports from the Wasm binary.']
975
976
if settings.WASM_ESM_INTEGRATION:
977
exported_symbols = [e[0].name for e in other_exports] + list(function_exports.keys())
978
exports = []
979
for sym in exported_symbols:
980
mangled = asmjs_mangle(sym)
981
if mangled != sym:
982
exports.append(f'{sym} as {mangled}')
983
else:
984
exports.append(sym)
985
for alias, target in aliases.items():
986
exports.append(f'{target} as {alias}')
987
988
receiving.append('import {')
989
receiving.append(' ' + ',\n '.join(exports))
990
receiving.append(f"}} from './{settings.WASM_BINARY_FILE}';")
991
992
if generate_dyncall_assignment:
993
receiving.append('\nfunction assignDynCalls() {')
994
receiving.append(' // Construct dynCalls mapping')
995
for sym in function_exports:
996
if sym.startswith('dynCall_'):
997
sig_str = sym.removeprefix('dynCall_')
998
receiving.append(f" dynCalls['{sig_str}'] = {sym};")
999
receiving.append('}')
1000
1001
return '\n'.join(receiving)
1002
1003
# When not declaring asm exports `assignWasmExports` is instead defined as a simple
1004
# library function.
1005
if not settings.DECLARE_ASM_MODULE_EXPORTS:
1006
return ''
1007
1008
# Exports are assigned inside a function to variables
1009
# existing in top level JS scope, i.e.
1010
# var _main;
1011
# function assignWasmExports(wasmExport) {
1012
# _main = wasmExports["_main"];
1013
exports = {name: sig for name, sig in function_exports.items() if name != building.WASM_CALL_CTORS}
1014
for export, info in other_exports:
1015
exports[export.name] = (export, info)
1016
1017
mangled = [asmjs_mangle(s) for s in exports] + list(aliases.keys())
1018
if settings.ASSERTIONS:
1019
# In debug builds we generate trapping functions in case
1020
# folks try to call/use a reference that was taken before the
1021
# wasm module is available.
1022
for sym in mangled:
1023
module_export = (settings.MODULARIZE or not settings.MINIMAL_RUNTIME) and should_export(sym) and settings.MODULARIZE != 'instance'
1024
if not js_manipulation.isidentifier(sym) and not module_export:
1025
continue
1026
assignment = f'var {sym}'
1027
if module_export:
1028
if js_manipulation.isidentifier(sym):
1029
assignment += f" = Module['{sym}']"
1030
else:
1031
assignment = f"Module['{sym}']"
1032
receiving.append(f"{assignment} = makeInvalidEarlyAccess('{sym}');")
1033
else:
1034
# Declare all exports in a single var statement
1035
sep = ',\n '
1036
receiving.append(f'var {sep.join(mangled)};\n')
1037
1038
if settings.MODULARIZE == 'instance':
1039
esm_exports = [e for e in mangled if should_export(e)]
1040
if esm_exports:
1041
esm_exports = ', '.join(esm_exports)
1042
receiving.append(f'export {{ {esm_exports} }};')
1043
1044
alias_inverse_map = {}
1045
logger.debug(json.dumps(aliases))
1046
for sym, alias in aliases.items():
1047
assert alias in exports, f'expected alias target ({alias}) to be exported'
1048
alias_inverse_map.setdefault(alias, []).append(sym)
1049
1050
do_module_exports = (settings.MODULARIZE or not settings.MINIMAL_RUNTIME) and settings.MODULARIZE != 'instance'
1051
receiving.append('\nfunction assignWasmExports(wasmExports) {')
1052
if settings.ASSERTIONS:
1053
for sym in exports:
1054
if settings.EMBIND_GEN_MODE and sym.startswith('asyncify_'):
1055
# EMBIND_GEN_MODE is run before binaryen so the asyncify exports that
1056
# are created by binaryen will be missing.
1057
continue
1058
receiving.append(f" assert(typeof wasmExports['{sym}'] != 'undefined', 'missing Wasm export: {sym}');")
1059
for sym, info in exports.items():
1060
is_function = type(info) == webassembly.FuncType
1061
mangled = asmjs_mangle(sym)
1062
assignment = mangled
1063
if generate_dyncall_assignment and is_function and sym.startswith('dynCall_'):
1064
sig_str = sym.removeprefix('dynCall_')
1065
assignment += f" = dynCalls['{sig_str}']"
1066
if do_module_exports and should_export(mangled):
1067
if js_manipulation.isidentifier(mangled):
1068
assignment += f" = Module['{mangled}']"
1069
else:
1070
assignment = f"Module['{mangled}']"
1071
elif not js_manipulation.isidentifier(mangled):
1072
# Symbol is not a valid JS identifier and also not exported. In this case we
1073
# have nothing to do here.
1074
continue
1075
1076
if sym in alias_inverse_map:
1077
for target in alias_inverse_map[sym]:
1078
assignment += f" = {target}"
1079
if do_module_exports and target in settings.EXPORTED_RUNTIME_METHODS:
1080
assignment += f" = Module['{target}']"
1081
if is_function and install_debug_wrapper(sym):
1082
nargs = len(info.params)
1083
receiving.append(f" {assignment} = createExportWrapper('{sym}', {nargs});")
1084
elif not is_function and info[0].kind == webassembly.ExternType.GLOBAL and not info[1].mutable:
1085
if settings.LEGACY_VM_SUPPORT:
1086
value = f"typeof wasmExports['{sym}'] == 'object' ? wasmExports['{sym}'].value : wasmExports['{sym}']"
1087
else:
1088
value = f"wasmExports['{sym}'].value"
1089
if settings.MEMORY64:
1090
value = f'Number({value})'
1091
elif settings.CAN_ADDRESS_2GB:
1092
value = f'({value}) >>> 0'
1093
receiving.append(f" {assignment} = {value};")
1094
else:
1095
receiving.append(f" {assignment} = wasmExports['{sym}'];")
1096
receiving.append('}')
1097
1098
return '\n'.join(receiving)
1099
1100
1101
def create_module(metadata, function_exports, other_exports, library_symbols, aliases):
1102
module = []
1103
module.append(create_receiving(function_exports, other_exports, library_symbols, aliases))
1104
1105
sending = create_sending(metadata, library_symbols)
1106
if settings.WASM_ESM_INTEGRATION:
1107
module.append(sending)
1108
else:
1109
if settings.PTHREADS or settings.WASM_WORKERS or (settings.IMPORTED_MEMORY and settings.MODULARIZE == 'instance'):
1110
sending = textwrap.indent(sending, ' ').strip()
1111
module.append('''\
1112
var wasmImports;
1113
function assignWasmImports() {
1114
wasmImports = %s;
1115
}''' % sending)
1116
else:
1117
module.append('var wasmImports = %s;' % sending)
1118
1119
if settings.SUPPORT_LONGJMP == 'emscripten' or not settings.DISABLE_EXCEPTION_CATCHING:
1120
module += create_invoke_wrappers(metadata)
1121
else:
1122
assert not metadata.invoke_funcs, "invoke_ functions exported but exceptions and longjmp are both disabled"
1123
1124
if settings.MEMORY64 or settings.CAN_ADDRESS_2GB:
1125
module.append(create_pointer_conversion_wrappers(metadata))
1126
1127
if settings.WASM_ESM_INTEGRATION:
1128
module.append(create_reexports(metadata))
1129
1130
module = [chunk for chunk in module if chunk]
1131
return '\n\n'.join(module) + '\n'
1132
1133
1134
def create_invoke_wrappers(metadata):
1135
"""Asm.js-style exception handling: invoke wrapper generation."""
1136
invoke_wrappers = []
1137
for invoke in metadata.invoke_funcs:
1138
sig = invoke.removeprefix('invoke_')
1139
invoke_wrappers.append(js_manipulation.make_invoke(sig))
1140
return invoke_wrappers
1141
1142
1143
def create_pointer_conversion_wrappers(metadata):
1144
# TODO(sbc): Move this into somewhere less static. Maybe it can become
1145
# part of library.js file, even though this metadata relates specifically
1146
# to native (non-JS) functions.
1147
#
1148
# The signature format here is similar to the one used for JS libraries
1149
# but with the following as the only valid char:
1150
# '_' - non-pointer argument (pass through unchanged)
1151
# 'p' - pointer/int53 argument (convert to/from BigInt)
1152
# 'P' - same as above but allow `undefined` too (requires extra check)
1153
mapping = {
1154
'sbrk': 'pP',
1155
'_emscripten_stack_alloc': 'pp',
1156
'emscripten_get_sbrk_ptr': 'p',
1157
'emscripten_builtin_malloc': 'pp',
1158
'emscripten_builtin_calloc': 'ppp',
1159
'wasmfs_create_node_backend': 'pp',
1160
'malloc': 'pp',
1161
'realloc': 'ppp',
1162
'calloc': 'ppp',
1163
'webidl_malloc': 'pp',
1164
'memalign': 'ppp',
1165
'memcmp': '_ppp',
1166
'memcpy': 'pppp',
1167
'__getTypeName': 'pp',
1168
'setThrew': '_p',
1169
'free': '_p',
1170
'webidl_free': '_p',
1171
'_emscripten_stack_restore': '_p',
1172
'fflush': '_p',
1173
'emscripten_stack_get_end': 'p',
1174
'emscripten_stack_get_base': 'p',
1175
'pthread_self': 'p',
1176
'emscripten_stack_get_current': 'p',
1177
'__errno_location': 'p',
1178
'emscripten_builtin_memalign': 'ppp',
1179
'emscripten_builtin_free': 'vp',
1180
'main': '__PP',
1181
'__main_argc_argv': '__PP',
1182
'emscripten_stack_set_limits': '_pp',
1183
'__set_stack_limits': '_pp',
1184
'__set_thread_state': '_p___',
1185
'__cxa_can_catch': '_ppp',
1186
'__cxa_increment_exception_refcount': '_p',
1187
'__cxa_decrement_exception_refcount': '_p',
1188
'__cxa_get_exception_ptr': 'pp',
1189
'_wasmfs_write_file': '_ppp',
1190
'_wasmfs_mknod': '_p__',
1191
'_wasmfs_symlink': '_pp',
1192
'_wasmfs_chmod': '_p_',
1193
'_wasmfs_lchmod': '_p_',
1194
'_wasmfs_get_cwd': 'p_',
1195
'_wasmfs_identify': '_p',
1196
'_wasmfs_read_file': '_ppp',
1197
'_wasmfs_node_record_dirent': '_pp_',
1198
'__dl_seterr': '_pp',
1199
'_emscripten_run_js_on_main_thread': '__p_p_',
1200
'_emscripten_run_js_on_main_thread_done': '_pp_',
1201
'_emscripten_thread_exit': '_p',
1202
'_emscripten_thread_init': '_p_____',
1203
'_emscripten_thread_free_data': '_p',
1204
'_emscripten_dlsync_self_async': '_p',
1205
'_emscripten_proxy_dlsync_async': '_pp',
1206
'_emscripten_wasm_worker_initialize': '_p_',
1207
'_emscripten_proxy_poll_finish': '_pp_',
1208
'_wasmfs_rename': '_pp',
1209
'_wasmfs_readlink': '_pp',
1210
'_wasmfs_truncate': '_p_',
1211
'_wasmfs_mmap': 'pp____',
1212
'_wasmfs_munmap': '_pp',
1213
'_wasmfs_msync': '_pp_',
1214
'_wasmfs_read': '__pp',
1215
'_wasmfs_pread': '__pp_',
1216
'_wasmfs_utime': '_p__',
1217
'_wasmfs_rmdir': '_p',
1218
'_wasmfs_unlink': '_p',
1219
'_wasmfs_mkdir': '_p_',
1220
'_wasmfs_open': '_p__',
1221
'_wasmfs_mount': '_pp',
1222
'_wasmfs_chdir': '_p',
1223
'asyncify_start_rewind': '_p',
1224
'asyncify_start_unwind': '_p',
1225
'__get_exception_message': '_ppp',
1226
'stbi_image_free': 'vp',
1227
'stbi_load': 'ppppp_',
1228
'stbi_load_from_memory': 'pp_ppp_',
1229
'strerror': 'p_',
1230
'emscripten_proxy_finish': '_p',
1231
'emscripten_proxy_execute_queue': '_p',
1232
'_emval_coro_resume': '_pp',
1233
'_emval_coro_reject': '_pp',
1234
'emscripten_main_runtime_thread_id': 'p',
1235
'_emscripten_set_offscreencanvas_size_on_thread': '_pp__',
1236
'fileno': '_p',
1237
'_emscripten_run_callback_on_thread': '_pp_ppp',
1238
'_emscripten_find_dylib': 'ppppp',
1239
}
1240
1241
for function in settings.SIGNATURE_CONVERSIONS:
1242
sym, sig = function.split(':')
1243
mapping[sym] = sig
1244
1245
for f in ASAN_C_HELPERS:
1246
mapping[f] = '_pp'
1247
1248
wrappers = '''
1249
// Argument name here must shadow the `wasmExports` global so
1250
// that it is recognised by metadce and minify-import-export-names
1251
// passes.
1252
function applySignatureConversions(wasmExports) {
1253
// First, make a copy of the incoming exports object
1254
wasmExports = Object.assign({}, wasmExports);
1255
'''
1256
1257
sigs_seen = set()
1258
wrap_functions = []
1259
for symbol, functype in metadata.function_exports.items():
1260
# dynCall_ functions are generated by binaryen. They all take a
1261
# function pointer as their first argument.
1262
# The second part of this check can be removed once the table64
1263
# llvm changes land.
1264
if symbol.startswith('dynCall_') and functype.params[0] == webassembly.Type.I64:
1265
sig = symbol.split('_')[-1]
1266
sig = ['p' if t == 'p' else '_' for t in sig]
1267
sig.insert(1, 'p')
1268
sig = ''.join(sig)
1269
mapping[symbol] = sig
1270
sig = mapping.get(symbol)
1271
if sig:
1272
if settings.MEMORY64:
1273
if sig not in sigs_seen:
1274
wrappers += js_manipulation.make_wasm64_wrapper(sig)
1275
sigs_seen.add(sig)
1276
wrap_functions.append(symbol)
1277
elif sig[0] == 'p':
1278
if sig not in sigs_seen:
1279
wrappers += js_manipulation.make_unsign_pointer_wrapper(sig)
1280
sigs_seen.add(sig)
1281
wrap_functions.append(symbol)
1282
1283
for f in wrap_functions:
1284
sig = mapping[f]
1285
wrappers += f"\n wasmExports['{f}'] = makeWrapper_{sig}(wasmExports['{f}']);"
1286
wrappers += '\n return wasmExports;\n}'
1287
1288
return wrappers
1289
1290