diff --git a/ChangeLog.md b/ChangeLog.md index 280f0e9bc0fe1..2c39936677144 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,13 @@ See docs/process.md for more on how version tagging works. 3.1.58 (in development) ----------------------- +- The `-sMAIN_MODULE=1` mode no longer exports all the main module symbols on + `Module` object. This saves a huge about of generated JS code due the fact + that `-sMAIN_MODULE=1` includes *all* native symbols in your program as well + is from the standard library. The generated JS code for a simple program + in this mode is reduced from from 3.3mb to 0.5mb. The current implementation + of this feature requires wasm-ld to be on the program twice which could have a + noticeable effect on link times. (#21785) - In `-sMODULARIZE` mode, the argument passed into the module constructor is no longer mutated in place. The expectation is that the module instance will be available via the constructor return value. Attempting to access methods diff --git a/tools/emscripten.py b/tools/emscripten.py index f73e2e1675b4c..87e999080ae07 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -128,7 +128,7 @@ def align_memory(addr): return (addr + 15) & -16 -def update_settings_glue(wasm_file, metadata): +def update_settings_glue(wasm_file, metadata, base_metadata): maybe_disable_filesystem(metadata.imports) # Integrate info from backend @@ -142,7 +142,10 @@ def update_settings_glue(wasm_file, metadata): if settings.MAIN_MODULE: settings.WEAK_IMPORTS += webassembly.get_weak_imports(wasm_file) - settings.WASM_EXPORTS = metadata.all_exports + if base_metadata: + settings.WASM_EXPORTS = base_metadata.all_exports + else: + settings.WASM_EXPORTS = metadata.all_exports settings.WASM_GLOBAL_EXPORTS = list(metadata.global_exports.keys()) settings.HAVE_EM_ASM = bool(settings.MAIN_MODULE or len(metadata.em_asm_consts) != 0) @@ -290,22 +293,22 @@ def trim_asm_const_body(body): return body -def create_global_exports(metadata): - global_exports = [] - for k, v in metadata.global_exports.items(): +def create_global_exports(global_exports): + lines = [] + for k, v in global_exports.items(): v = int(v) if settings.RELOCATABLE: v += settings.GLOBAL_BASE mangled = asmjs_mangle(k) if settings.MINIMAL_RUNTIME: - global_exports.append("var %s = %s;" % (mangled, v)) + lines.append("var %s = %s;" % (mangled, v)) else: - global_exports.append("var %s = Module['%s'] = %s;" % (mangled, mangled, v)) + lines.append("var %s = Module['%s'] = %s;" % (mangled, mangled, v)) - return '\n'.join(global_exports) + return '\n'.join(lines) -def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True): +def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadata=None): # Overview: # * Run wasm-emscripten-finalize to extract metadata and modify the binary # to use emscripten's wasm<->JS ABI @@ -329,16 +332,10 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True): if settings.RELOCATABLE and settings.MEMORY64 == 2: metadata.imports += ['__memory_base32'] - if settings.ASYNCIFY == 1: - metadata.function_exports['asyncify_start_unwind'] = webassembly.FuncType([webassembly.Type.I32], []) - metadata.function_exports['asyncify_stop_unwind'] = webassembly.FuncType([], []) - metadata.function_exports['asyncify_start_rewind'] = webassembly.FuncType([webassembly.Type.I32], []) - metadata.function_exports['asyncify_stop_rewind'] = webassembly.FuncType([], []) - # If the binary has already been finalized the settings have already been # updated and we can skip updating them. if finalize: - update_settings_glue(out_wasm, metadata) + update_settings_glue(out_wasm, metadata, base_metadata) if not settings.WASM_BIGINT and metadata.em_js_funcs: import_map = {} @@ -444,18 +441,31 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True): '// === Body ===\n', '// === Body ===\n\n' + extra_code + '\n') + if base_metadata: + function_exports = base_metadata.function_exports + global_exports = base_metadata.global_exports + else: + function_exports = metadata.function_exports + global_exports = metadata.global_exports + + if settings.ASYNCIFY == 1: + function_exports['asyncify_start_unwind'] = webassembly.FuncType([webassembly.Type.I32], []) + function_exports['asyncify_stop_unwind'] = webassembly.FuncType([], []) + function_exports['asyncify_start_rewind'] = webassembly.FuncType([webassembly.Type.I32], []) + function_exports['asyncify_stop_rewind'] = webassembly.FuncType([], []) + with open(outfile_js, 'w', encoding='utf-8') as out: out.write(pre) pre = None - receiving = create_receiving(metadata.function_exports) + receiving = create_receiving(function_exports) if settings.MINIMAL_RUNTIME: if settings.DECLARE_ASM_MODULE_EXPORTS: - post = compute_minimal_runtime_initializer_and_exports(post, metadata.function_exports, receiving) + post = compute_minimal_runtime_initializer_and_exports(post, function_exports, receiving) receiving = '' - module = create_module(receiving, metadata, forwarded_json['librarySymbols']) + module = create_module(receiving, metadata, global_exports, forwarded_json['librarySymbols']) metadata.library_definitions = forwarded_json['libraryDefinitions'] @@ -638,8 +648,7 @@ def create_tsd(metadata, embind_tsd): out += create_tsd_exported_runtime_methods(metadata) # Manually generate defintions for any Wasm function exports. out += 'interface WasmModule {\n' - function_exports = metadata.function_exports - for name, types in function_exports.items(): + for name, types in metadata.function_exports.items(): mangled = asmjs_mangle(name) should_export = settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS if not should_export: @@ -950,8 +959,8 @@ def create_receiving(function_exports): return '\n'.join(receiving) + '\n' -def create_module(receiving, metadata, library_symbols): - receiving += create_global_exports(metadata) +def create_module(receiving, metadata, global_exports, library_symbols): + receiving += create_global_exports(global_exports) module = [] sending = create_sending(metadata, library_symbols) diff --git a/tools/link.py b/tools/link.py index f227047b094b1..898903e1049ed 100644 --- a/tools/link.py +++ b/tools/link.py @@ -32,6 +32,7 @@ from . import system_libs from . import utils from . import webassembly +from . import extract_metadata from .utils import read_file, read_binary, write_file, delete_file from .utils import removeprefix, exit_with_error from .shared import in_temp, safe_copy, do_replace, OFormat @@ -1847,11 +1848,28 @@ def phase_link(linker_arguments, wasm_target, js_syms): settings.REQUIRED_EXPORTS = dedup_list(settings.REQUIRED_EXPORTS) settings.EXPORT_IF_DEFINED = dedup_list(settings.EXPORT_IF_DEFINED) + rtn = None + if settings.LINKABLE and not settings.EXPORT_ALL: + # In LINKABLE mode we pass `--export-dynamic` along with `--whole-archive`. This results + # in over 7000 exports, which cannot be distinguished from the few symbols we explicitly + # export via EMSCRIPTEN_KEEPALIVE or EXPORTED_FUNCTIONS. + # In order to avoid unnecessary exported symbols on the `Module` object we run the linker + # twice in this mode: + # 1. Without `--export-dynamic` to get the base exports + # 2. With `--export-dynamic` to get the actual linkable Wasm binary + # TODO(sbc): Remove this double execution of wasm-ld if we ever find a way to + # distinguish EMSCRIPTEN_KEEPALIVE exports from `--export-dynamic` exports. + settings.LINKABLE = False + building.link_lld(linker_arguments, wasm_target, external_symbols=js_syms) + settings.LINKABLE = True + rtn = extract_metadata.extract_metadata(wasm_target) + building.link_lld(linker_arguments, wasm_target, external_symbols=js_syms) + return rtn @ToolchainProfiler.profile_block('post link') -def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms): +def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms, base_metadata=None): global final_js target_basename = unsuffixed_basename(target) @@ -1868,7 +1886,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms): settings.TARGET_JS_NAME = os.path.basename(state.js_target) - metadata = phase_emscript(in_wasm, wasm_target, js_syms) + metadata = phase_emscript(in_wasm, wasm_target, js_syms, base_metadata) if settings.EMBIND_AOT: phase_embind_aot(wasm_target, js_syms) @@ -1887,7 +1905,7 @@ def phase_post_link(options, state, in_wasm, wasm_target, target, js_syms): @ToolchainProfiler.profile_block('emscript') -def phase_emscript(in_wasm, wasm_target, js_syms): +def phase_emscript(in_wasm, wasm_target, js_syms, base_metadata): # Emscripten logger.debug('emscript') @@ -1898,7 +1916,7 @@ def phase_emscript(in_wasm, wasm_target, js_syms): if shared.SKIP_SUBPROCS: return - metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms) + metadata = emscripten.emscript(in_wasm, wasm_target, final_js, js_syms, base_metadata=base_metadata) save_intermediate('original') return metadata @@ -3068,24 +3086,27 @@ def run(linker_inputs, options, state, newargs): js_info = get_js_sym_info() if not settings.SIDE_MODULE: js_syms = js_info['deps'] - - def add_js_deps(sym): - if sym in js_syms: - native_deps = js_syms[sym] - if native_deps: - settings.REQUIRED_EXPORTS += native_deps - - for sym in settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE: - add_js_deps(sym) - for sym in js_info['extraLibraryFuncs']: - add_js_deps(sym) - for sym in settings.EXPORTED_RUNTIME_METHODS: - add_js_deps(shared.demangle_c_symbol_name(sym)) + if settings.LINKABLE: + for native_deps in js_syms.values(): + settings.REQUIRED_EXPORTS += native_deps + else: + def add_js_deps(sym): + if sym in js_syms: + native_deps = js_syms[sym] + if native_deps: + settings.REQUIRED_EXPORTS += native_deps + + for sym in settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE: + add_js_deps(sym) + for sym in js_info['extraLibraryFuncs']: + add_js_deps(sym) + for sym in settings.EXPORTED_RUNTIME_METHODS: + add_js_deps(shared.demangle_c_symbol_name(sym)) if settings.ASYNCIFY: settings.ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS = settings.ASYNCIFY_IMPORTS[:] settings.ASYNCIFY_IMPORTS += ['*.' + x for x in js_info['asyncFuncs']] - phase_link(linker_arguments, wasm_target, js_syms) + base_metadata = phase_link(linker_arguments, wasm_target, js_syms) # Special handling for when the user passed '-Wl,--version'. In this case the linker # does not create the output file, but just prints its version and exits with 0. @@ -3099,6 +3120,6 @@ def add_js_deps(sym): # Perform post-link steps (unless we are running bare mode) if options.oformat != OFormat.BARE: - phase_post_link(options, state, wasm_target, wasm_target, target, js_syms) + phase_post_link(options, state, wasm_target, wasm_target, target, js_syms, base_metadata) return 0