Skip to content

Commit fcb1066

Browse files
committed
Avoid creating JS symbols for symbols only used in dynamic linking
Symbols that are exported using EMSCRIPTEN_KEEPALIVE are supposed to be exported to the outside world (i.e. on the Module object) and also be available to call JS within the module. Symbols exports for the purposed of dynamic linking so not need to be exported on the Module and are added (at runtime) to `wasmImports` which acts as a kind of global symbol table for the program. In in the case of `-sMAIN_MODULE=1` we export *all* symbols from all libraries, and prior to this change it was not possible to distingish between all the exported generated because of `--export-dynamic`, and the exports generated due to `EMSCRIPTEN_KEEPALIVE`. This change allows us to differentiate by running `wasm-ld` twice: once without `--export-dynamic` (to get the smaller list of `EMSCRIPTEN_KEEPALIVE`) and then once with `--export-dynamic` to produce the actual wasm that we output. This takes the list of exports that we turn in to JS globals from 7993 to 28, massively reducing the overhead of `-sMAIN_MODULE=1`.
1 parent cfc3ce9 commit fcb1066

File tree

2 files changed

+50
-26
lines changed

2 files changed

+50
-26
lines changed

tools/emscripten.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -290,22 +290,22 @@ def trim_asm_const_body(body):
290290
return body
291291

292292

293-
def create_global_exports(metadata):
294-
global_exports = []
295-
for k, v in metadata.global_exports.items():
293+
def create_global_exports(global_exports):
294+
lines = []
295+
for k, v in global_exports.items():
296296
v = int(v)
297297
if settings.RELOCATABLE:
298298
v += settings.GLOBAL_BASE
299299
mangled = asmjs_mangle(k)
300300
if settings.MINIMAL_RUNTIME:
301-
global_exports.append("var %s = %s;" % (mangled, v))
301+
lines.append("var %s = %s;" % (mangled, v))
302302
else:
303-
global_exports.append("var %s = Module['%s'] = %s;" % (mangled, mangled, v))
303+
lines.append("var %s = Module['%s'] = %s;" % (mangled, mangled, v))
304304

305-
return '\n'.join(global_exports)
305+
return '\n'.join(lines)
306306

307307

308-
def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True):
308+
def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadata=None):
309309
# Overview:
310310
# * Run wasm-emscripten-finalize to extract metadata and modify the binary
311311
# to use emscripten's wasm<->JS ABI
@@ -329,12 +329,6 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True):
329329
if settings.RELOCATABLE and settings.MEMORY64 == 2:
330330
metadata.imports += ['__memory_base32']
331331

332-
if settings.ASYNCIFY == 1:
333-
metadata.function_exports['asyncify_start_unwind'] = webassembly.FuncType([webassembly.Type.I32], [])
334-
metadata.function_exports['asyncify_stop_unwind'] = webassembly.FuncType([], [])
335-
metadata.function_exports['asyncify_start_rewind'] = webassembly.FuncType([webassembly.Type.I32], [])
336-
metadata.function_exports['asyncify_stop_rewind'] = webassembly.FuncType([], [])
337-
338332
# If the binary has already been finalized the settings have already been
339333
# updated and we can skip updating them.
340334
if finalize:
@@ -444,18 +438,31 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True):
444438
'// === Body ===\n',
445439
'// === Body ===\n\n' + extra_code + '\n')
446440

441+
if base_metadata:
442+
function_exports = base_metadata.function_exports
443+
global_exports = base_metadata.global_exports
444+
else:
445+
function_exports = metadata.function_exports
446+
global_exports = metadata.global_exports
447+
448+
if settings.ASYNCIFY == 1:
449+
function_exports['asyncify_start_unwind'] = webassembly.FuncType([webassembly.Type.I32], [])
450+
function_exports['asyncify_stop_unwind'] = webassembly.FuncType([], [])
451+
function_exports['asyncify_start_rewind'] = webassembly.FuncType([webassembly.Type.I32], [])
452+
function_exports['asyncify_stop_rewind'] = webassembly.FuncType([], [])
453+
447454
with open(outfile_js, 'w', encoding='utf-8') as out:
448455
out.write(pre)
449456
pre = None
450457

451-
receiving = create_receiving(metadata.function_exports)
458+
receiving = create_receiving(function_exports)
452459

453460
if settings.MINIMAL_RUNTIME:
454461
if settings.DECLARE_ASM_MODULE_EXPORTS:
455-
post = compute_minimal_runtime_initializer_and_exports(post, metadata.function_exports, receiving)
462+
post = compute_minimal_runtime_initializer_and_exports(post, function_exports, receiving)
456463
receiving = ''
457464

458-
module = create_module(receiving, metadata, forwarded_json['librarySymbols'])
465+
module = create_module(receiving, metadata, global_exports, forwarded_json['librarySymbols'])
459466

460467
metadata.library_definitions = forwarded_json['libraryDefinitions']
461468

@@ -638,8 +645,7 @@ def create_tsd(metadata, embind_tsd):
638645
out += create_tsd_exported_runtime_methods(metadata)
639646
# Manually generate defintions for any Wasm function exports.
640647
out += 'interface WasmModule {\n'
641-
function_exports = metadata.function_exports
642-
for name, types in function_exports.items():
648+
for name, types in metadata.function_exports.items():
643649
mangled = asmjs_mangle(name)
644650
should_export = settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS
645651
if not should_export:
@@ -950,8 +956,8 @@ def create_receiving(function_exports):
950956
return '\n'.join(receiving) + '\n'
951957

952958

953-
def create_module(receiving, metadata, library_symbols):
954-
receiving += create_global_exports(metadata)
959+
def create_module(receiving, metadata, global_exports, library_symbols):
960+
receiving += create_global_exports(global_exports)
955961
module = []
956962

957963
sending = create_sending(metadata, library_symbols)

tools/link.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from . import system_libs
3333
from . import utils
3434
from . import webassembly
35+
from . import extract_metadata
3536
from .utils import read_file, read_binary, write_file, delete_file
3637
from .utils import removeprefix, exit_with_error
3738
from .shared import in_temp, safe_copy, do_replace, OFormat
@@ -1847,11 +1848,28 @@ def phase_link(linker_arguments, wasm_target, js_syms):
18471848
settings.REQUIRED_EXPORTS = dedup_list(settings.REQUIRED_EXPORTS)
18481849
settings.EXPORT_IF_DEFINED = dedup_list(settings.EXPORT_IF_DEFINED)
18491850

1851+
rtn = None
1852+
if settings.LINKABLE:
1853+
# In LINKABLE mode we pass `--export-dynamic` along with `--whole-archive`. This results
1854+
# over 7000 exports, which cannot be distingished from the few symbols we explicitly
1855+
# export via EMSCRIPTEN_KEEPALIVE or EXPORTED_FUNCTIONS.
1856+
# In order to be able limit the number of symbols we export on the `Module` object we
1857+
# run the linker twice in this mode.
1858+
# 1. Without `--export-dynamic` to get the base exports
1859+
# 2. With `--export-dynamic` to get the actual linkable Wasm binary
1860+
# TODO(sbc): Remove this double execution of wasm-ld if we ever find a way to
1861+
# distingiush EMSCRIPTEN_KEEPALIVE exports from `--export-dynamic` exports.
1862+
settings.LINKABLE = False
1863+
building.link_lld(linker_arguments, wasm_target, external_symbols=js_syms)
1864+
settings.LINKABLE = True
1865+
rtn = extract_metadata.extract_metadata(wasm_target)
1866+
18501867
building.link_lld(linker_arguments, wasm_target, external_symbols=js_syms)
1868+
return rtn
18511869

18521870

18531871
@ToolchainProfiler.profile_block('post link')
1854-
def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms):
1872+
def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_metadata=None):
18551873
global final_js
18561874

18571875
target_basename = unsuffixed_basename(target)
@@ -1868,7 +1886,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms):
18681886

18691887
settings.TARGET_JS_NAME = os.path.basename(state.js_target)
18701888

1871-
metadata = phase_emscript(in_wasm, wasm_target, js_syms)
1889+
metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata)
18721890

18731891
if settings.EMBIND_AOT:
18741892
phase_embind_aot(wasm_target, js_syms)
@@ -1887,7 +1905,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms):
18871905

18881906

18891907
@ToolchainProfiler.profile_block('emscript')
1890-
def phase_emscript(in_wasm, wasm_target, js_syms):
1908+
def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata):
18911909
# Emscripten
18921910
logger.debug('emscript')
18931911

@@ -1898,7 +1916,7 @@ def phase_emscript(in_wasm, wasm_target, js_syms):
18981916
if shared.SKIP_SUBPROCS:
18991917
return
19001918

1901-
metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms)
1919+
metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms, base_metadata=base_metadata)
19021920
save_intermediate('original')
19031921
return metadata
19041922

@@ -2992,7 +3010,7 @@ def add_js_deps(sym):
29923010
settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:]
29933011
settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']]
29943012

2995-
phase_link(linker_arguments, wasm_target, js_syms)
3013+
base_metadata = phase_link(linker_arguments, wasm_target, js_syms)
29963014

29973015
# Special handling for when the user passed '-Wl,--version'. In this case the linker
29983016
# does not create the output file, but just prints its version and exits with 0.
@@ -3006,6 +3024,6 @@ def add_js_deps(sym):
30063024

30073025
# Perform post-link steps (unless we are running bare mode)
30083026
if options.oformat != OFormat.BARE:
3009-
phase_post_link(options, state, wasm_target, wasm_target, target, js_syms)
3027+
phase_post_link(options, state, wasm_target, wasm_target, target, js_syms, base_metadata)
30103028

30113029
return 0

0 commit comments

Comments
 (0)