Skip to content

Commit 4d16f75

Browse files
authored
Don't build MAIN_MODULE as RELOCATABLE (#25522)
The main advantage here is that main module no longer requires relocation entries for symbols defined locally. To show the benefits of this approach I build binaryan_wasm with `-sMAIN_MODULE=1` both before and after this change. Before: Wasm Size: 22.6M __wasm_apply_data_relocs size: 123k After: Wasm Size: 16.6M __wasm_apply_data_relocs size: 0 (no longer exists) Since this is quite a major change, I've kept the `-sRELOCATABLE` option available so folks that use this to get the old behaviour when linking their main module. In order to tests this path too now run all the `@needs_dylink` tests in test_core.py with and without `-sRELOCTABLE`. Hopefully we can remove this configuration completely at some point in the future. Fixes: #12682
1 parent ad27b57 commit 4d16f75

22 files changed

+222
-139
lines changed

‎ChangeLog.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ See docs/process.md for more on how version tagging works.
2626
exception here for `EMSCRIPTEN_VERSION` which is the only internal setting
2727
where we could find usage of `emscripten_get_compiler_setting` (in a global
2828
GitHub search). (#25667)
29+
- When using dynamic linking the main module is no longer built as a relocatable
30+
binary. This will significantly reduce the overhead of dynamic linking for
31+
the main program, for example, eliminating all internal relocations. If you
32+
encounter any issues with new default it is possible to revert to the old
33+
behaviour by adding `-sRELOCATABLE` when linking the main module. (#25522)
2934

3035
4.0.18 - 10/24/25
3136
-----------------

‎embuilder.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
'crtbegin',
121121
'libsanitizer_common_rt',
122122
'libubsan_rt',
123+
'libwasm_workers-debug',
123124
'libwasm_workers-debug-stub',
124125
'libfetch',
125126
'libfetch-mt',

‎emcc.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def phase_setup(options, state):
410410
'unused-command-line-argument',
411411
"linker flag ignored during compilation: '%s'" % arg)
412412

413-
if settings.MAIN_MODULE or settings.SIDE_MODULE:
413+
if settings.SIDE_MODULE:
414414
settings.RELOCATABLE = 1
415415

416416
if 'USE_PTHREADS' in user_settings:

‎src/jsifier.mjs‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function isDefined(symName) {
121121
}
122122
// 'invoke_' symbols are created at runtime in library_dylink.py so can
123123
// always be considered as defined.
124-
if (RELOCATABLE && symName.startsWith('invoke_')) {
124+
if ((MAIN_MODULE || RELOCATABLE) && symName.startsWith('invoke_')) {
125125
return true;
126126
}
127127
return false;
@@ -572,7 +572,7 @@ function(${args}) {
572572
if (!LibraryManager.library.hasOwnProperty(symbol)) {
573573
const isWeakImport = WEAK_IMPORTS.has(symbol);
574574
if (!isDefined(symbol) && !isWeakImport) {
575-
if (PROXY_TO_PTHREAD && !MAIN_MODULE && symbol == '__main_argc_argv') {
575+
if (PROXY_TO_PTHREAD && !(MAIN_MODULE || RELOCATABLE) && symbol == '__main_argc_argv') {
576576
error('PROXY_TO_PTHREAD proxies main() for you, but no main exists');
577577
return;
578578
}
@@ -603,7 +603,7 @@ function(${args}) {
603603

604604
// emit a stub that will fail at runtime
605605
var stubFunctionBody = `abort('missing function: ${symbol}');`
606-
if (RELOCATABLE) {
606+
if (RELOCATABLE || MAIN_MODULE) {
607607
// Create a stub for this symbol which can later be replaced by the
608608
// dynamic linker. If this stub is called before the symbol is
609609
// resolved assert in debug builds or trap in release builds.
@@ -762,8 +762,8 @@ function(${args}) {
762762
contentText = 'export ' + contentText;
763763
}
764764

765-
// Relocatable code needs signatures to create proper wrappers.
766-
if (sig && RELOCATABLE) {
765+
// Dynamic linking needs signatures to create proper wrappers.
766+
if (sig && (MAIN_MODULE || RELOCATABLE)) {
767767
if (!WASM_BIGINT) {
768768
sig = sig[0].replace('j', 'i') + sig.slice(1).replace(/j/g, 'ii');
769769
}
@@ -774,7 +774,7 @@ function(${args}) {
774774
}
775775
if (isStub) {
776776
contentText += `\n${mangled}.stub = true;`;
777-
if (ASYNCIFY && MAIN_MODULE) {
777+
if (ASYNCIFY && (MAIN_MODULE || RELOCATABLE)) {
778778
contentText += `\nasyncifyStubs['${symbol}'] = undefined;`;
779779
}
780780
}

‎src/lib/libcore.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,11 +2213,12 @@ addToLibrary({
22132213
__stack_high: '{{{ STACK_HIGH }}}',
22142214
__stack_low: '{{{ STACK_LOW }}}',
22152215
__global_base: '{{{ GLOBAL_BASE }}}',
2216-
#if ASYNCIFY == 1
2216+
#endif // RELOCATABLE
2217+
2218+
#if (MAIN_MODULE || RELOCATABLE) && ASYNCIFY == 1
22172219
__asyncify_state: "new WebAssembly.Global({'value': 'i32', 'mutable': true}, 0)",
22182220
__asyncify_data: "new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}, {{{ to64(0) }}})",
22192221
#endif
2220-
#endif // RELOCATABLE
22212222

22222223
_emscripten_fs_load_embedded_files__deps: ['$FS', '$PATH'],
22232224
_emscripten_fs_load_embedded_files: (ptr) => {

‎src/lib/libdylink.js‎

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
* Dynamic library loading
77
*/
88

9-
#if !RELOCATABLE
10-
#error "library_dylink.js requires RELOCATABLE"
9+
#if !MAIN_MODULE && !RELOCATABLE
10+
#error "library_dylink.js requires MAIN_MODULE or RELOCATABLE"
1111
#endif
1212

13+
{{{
14+
const UNDEFINED_ADDR = to64(-1);
15+
}}}
16+
1317
var LibraryDylink = {
1418
#if FILESYSTEM
1519
$registerWasmPlugin__deps: ['$preloadPlugins'],
@@ -170,10 +174,10 @@ var LibraryDylink = {
170174
get(obj, symName) {
171175
var rtn = GOT[symName];
172176
if (!rtn) {
173-
rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
174177
#if DYLINK_DEBUG == 2
175-
dbg("new GOT entry: " + symName);
178+
dbg(`new GOT entry: ${symName}`);
176179
#endif
180+
rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}, {{{ UNDEFINED_ADDR }}});
177181
}
178182
if (!currentModuleWeakSymbols.has(symName)) {
179183
// Any non-weak reference to a symbol marks it as `required`, which
@@ -189,6 +193,11 @@ var LibraryDylink = {
189193
$isInternalSym: (symName) => {
190194
// TODO: find a way to mark these in the binary or avoid exporting them.
191195
return [
196+
'memory',
197+
'__memory_base',
198+
'__table_base',
199+
'__stack_pointer',
200+
'__indirect_function_table',
192201
'__cpp_exception',
193202
'__c_longjmp',
194203
'__wasm_apply_data_relocs',
@@ -213,6 +222,7 @@ var LibraryDylink = {
213222

214223
$updateGOT__internal: true,
215224
$updateGOT__deps: ['$GOT', '$isInternalSym', '$addFunction'],
225+
$updateGOT__docs: '/** @param {boolean=} replace */',
216226
$updateGOT: (exports, replace) => {
217227
#if DYLINK_DEBUG
218228
dbg(`updateGOT: adding ${Object.keys(exports).length} symbols`);
@@ -230,8 +240,7 @@ var LibraryDylink = {
230240
}
231241
#endif
232242

233-
234-
var existingEntry = GOT[symName] && GOT[symName].value != 0;
243+
var existingEntry = GOT[symName] && GOT[symName].value != {{{ UNDEFINED_ADDR }}};
235244
if (replace || !existingEntry) {
236245
#if DYLINK_DEBUG == 2
237246
dbg(`updateGOT: before: ${symName} : ${GOT[symName]?.value}`);
@@ -252,7 +261,7 @@ var LibraryDylink = {
252261
#if DYLINK_DEBUG == 2
253262
dbg(`updateGOT: after: ${symName} : ${newValue} (${value})`);
254263
#endif
255-
GOT[symName] ||= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
264+
GOT[symName] ??= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
256265
GOT[symName].value = newValue;
257266
}
258267
#if DYLINK_DEBUG
@@ -280,9 +289,12 @@ var LibraryDylink = {
280289

281290
// Applies relocations to exported things.
282291
$relocateExports__internal: true,
283-
$relocateExports__deps: ['$updateGOT', '$isImmutableGlobal'],
284-
$relocateExports__docs: '/** @param {boolean=} replace */',
285-
$relocateExports: (exports, memoryBase, replace) => {
292+
$relocateExports__deps: ['$isImmutableGlobal'],
293+
$relocateExports: (exports, memoryBase = 0) => {
294+
#if DYLINK_DEBUG
295+
dbg(`relocateExports memoryBase=${memoryBase} count=${Object.keys(exports).length}`);
296+
#endif
297+
286298
function relocateExport(name, value) {
287299
#if SPLIT_MODULE
288300
// Do not modify exports synthesized by wasm-split
@@ -304,7 +316,6 @@ var LibraryDylink = {
304316
for (var e in exports) {
305317
relocated[e] = relocateExport(e, exports[e])
306318
}
307-
updateGOT(relocated, replace);
308319
return relocated;
309320
},
310321

@@ -315,13 +326,17 @@ var LibraryDylink = {
315326
dbg('reportUndefinedSymbols');
316327
#endif
317328
for (var [symName, entry] of Object.entries(GOT)) {
318-
if (entry.value == 0) {
329+
if (entry.value == {{{ UNDEFINED_ADDR }}}) {
330+
#if DYLINK_DEBUG
331+
dbg(`undef GOT entry: ${symName}`);
332+
#endif
319333
var value = resolveGlobalSymbol(symName, true).sym;
320334
if (!value && !entry.required) {
321335
// Ignore undefined symbols that are imported as weak.
322336
#if DYLINK_DEBUG
323337
dbg('ignoring undefined weak symbol:', symName);
324338
#endif
339+
entry.value = {{{ to64(0) }}};
325340
continue;
326341
}
327342
#if ASSERTIONS
@@ -343,7 +358,7 @@ var LibraryDylink = {
343358
entry.value = value;
344359
#endif
345360
} else {
346-
throw new Error(`bad export type for '${symName}': ${typeof value}`);
361+
throw new Error(`bad export type for '${symName}': ${typeof value} (${value})`);
347362
}
348363
}
349364
}
@@ -390,7 +405,7 @@ var LibraryDylink = {
390405
// Allocate memory even if malloc isn't ready yet. The allocated memory here
391406
// must be zero initialized since its used for all static data, including bss.
392407
$getMemory__noleakcheck: true,
393-
$getMemory__deps: ['$GOT', '__heap_base', '$alignMemory', 'calloc'],
408+
$getMemory__deps: ['$GOT', 'emscripten_get_sbrk_ptr', '__heap_base', '$alignMemory', 'calloc'],
394409
$getMemory: (size) => {
395410
// After the runtime is initialized, we must only use sbrk() normally.
396411
#if DYLINK_DEBUG
@@ -409,7 +424,27 @@ var LibraryDylink = {
409424
assert(end <= HEAP8.length, 'failure to getMemory - memory growth etc. is not supported there, call malloc/sbrk directly or increase INITIAL_MEMORY');
410425
#endif
411426
___heap_base = end;
427+
428+
// After allocating the memory from the start of the heap we need to ensure
429+
// that once the program starts it doesn't use this region. In relocatable
430+
// mode we can just update the __heap_base symbol that we are exporting to
431+
// the main module.
432+
// When not relocatable `__heap_base` is fixed and exported by the main
433+
// module, but we can update the `sbrk_ptr` value instead. We call
434+
// `_emscripten_get_sbrk_ptr` knowing that it is safe to call prior to
435+
// runtime initialization (unlike, the higher level sbrk function)
436+
#if RELOCATABLE
412437
GOT['__heap_base'].value = {{{ to64('end') }}};
438+
#else
439+
#if PTHREADS
440+
if (!ENVIRONMENT_IS_PTHREAD) {
441+
#endif
442+
var sbrk_ptr = _emscripten_get_sbrk_ptr();
443+
{{{ makeSetValue('sbrk_ptr', 0, 'end', '*') }}}
444+
#if PTHREADS
445+
}
446+
#endif
447+
#endif
413448
return ret;
414449
},
415450

@@ -622,7 +657,7 @@ var LibraryDylink = {
622657
* @param {number=} handle
623658
*/`,
624659
$loadWebAssemblyModule__deps: [
625-
'$loadDynamicLibrary', '$getMemory',
660+
'$loadDynamicLibrary', '$getMemory', '$updateGOT',
626661
'$relocateExports', '$resolveGlobalSymbol', '$GOTHandler',
627662
'$getDylinkMetadata', '$alignMemory',
628663
'$currentModuleWeakSymbols',
@@ -632,7 +667,7 @@ var LibraryDylink = {
632667
],
633668
$loadWebAssemblyModule: (binary, flags, libName, localScope, handle) => {
634669
#if DYLINK_DEBUG
635-
dbg('loadWebAssemblyModule:', libName);
670+
dbg('loadWebAssemblyModule:', libName, handle);
636671
#endif
637672
var metadata = getDylinkMetadata(binary);
638673

@@ -651,6 +686,9 @@ var LibraryDylink = {
651686
// exclusive access to it for the duration of this function. See the
652687
// locking in `dynlink.c`.
653688
var firstLoad = !handle || !{{{ makeGetValue('handle', C_STRUCTS.dso.mem_allocated, 'i8') }}};
689+
#if DYLINK_DEBUG
690+
dbg('firstLoad:', firstLoad);
691+
#endif
654692
if (firstLoad) {
655693
#endif
656694
// alignments are powers of 2
@@ -787,6 +825,7 @@ var LibraryDylink = {
787825
// add new entries to functionsInTableMap
788826
updateTableMap(tableBase, metadata.tableSize);
789827
moduleExports = relocateExports(instance.exports, memoryBase);
828+
updateGOT(moduleExports);
790829
#if ASYNCIFY
791830
moduleExports = Asyncify.instrumentWasmExports(moduleExports);
792831
#endif
@@ -875,18 +914,27 @@ var LibraryDylink = {
875914
if (applyRelocs) {
876915
if (runtimeInitialized) {
877916
#if DYLINK_DEBUG
878-
dbg('applyRelocs');
917+
dbg('running __wasm_apply_data_relocs');
879918
#endif
880919
applyRelocs();
881920
} else {
921+
#if DYLINK_DEBUG
922+
dbg('delaying __wasm_apply_data_relocs');
923+
#endif
882924
__RELOC_FUNCS__.push(applyRelocs);
883925
}
884926
}
885927
var init = moduleExports['__wasm_call_ctors'];
886928
if (init) {
887929
if (runtimeInitialized) {
930+
#if DYLINK_DEBUG
931+
dbg('running __wasm_call_ctors');
932+
#endif
888933
init();
889934
} else {
935+
#if DYLINK_DEBUG
936+
dbg('delaying __wasm_call_ctors');
937+
#endif
890938
// we aren't ready to run compiled code yet
891939
addOnPostCtor(init);
892940
}

‎src/lib/libglemu.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ assert(!FULL_ES3, 'cannot emulate both ES3 and legacy GL');
1010

1111
{{{
1212
const copySigs = (func) => {
13-
if (!RELOCATABLE) return '';
13+
if (!MAIN_MODULE && !RELOCATABLE) return '';
1414
return ` _${func}.sig = _emscripten_${func}.sig = orig_${func}.sig;`;
1515
};
1616
const fromPtr = (arg) => {

‎src/lib/libpthread.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ var LibraryPThread = {
594594
#if MAIN_MODULE
595595
$registerTLSInit: (tlsInitFunc, moduleExports, metadata) => {
596596
#if DYLINK_DEBUG
597-
dbg("registerTLSInit: " + tlsInitFunc);
597+
dbg('registerTLSInit:', tlsInitFunc, metadata?.tlsExports);
598598
#endif
599599
// In relocatable builds, we use the result of calling tlsInitFunc
600600
// (`_emscripten_tls_init`) to relocate the TLS exports of the module
@@ -613,7 +613,7 @@ var LibraryPThread = {
613613
}
614614
var tlsExports = {};
615615
metadata.tlsExports.forEach((s) => tlsExports[s] = moduleExports[s]);
616-
relocateExports(tlsExports, __tls_base, /*replace=*/true);
616+
updateGOT(relocateExports(tlsExports, __tls_base), /*replace=*/true);
617617
}
618618

619619
// Register this function so that its gets called for each thread on

‎src/lib/libwasm_worker.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#if LINKABLE
1616
#error "-sLINKABLE is not supported with -sWASM_WORKERS"
1717
#endif
18-
#if RELOCATABLE
18+
#if RELOCATABLE || MAIN_MODULE
1919
#error "dynamic linking is not supported with -sWASM_WORKERS"
2020
#endif
2121
#if PROXY_TO_WORKER

‎src/modules.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function calculateLibraries() {
9393
libraries.push('libsyscall.js');
9494
}
9595

96-
if (RELOCATABLE) {
96+
if (MAIN_MODULE || RELOCATABLE) {
9797
libraries.push('libdylink.js');
9898
}
9999

0 commit comments

Comments
 (0)