Skip to content

Commit 4425035

Browse files
committed
Don't build MAIN_MODULE as RELOCATABLE
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) Fixes: #12682
1 parent 4a48420 commit 4425035

21 files changed

+188
-103
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ 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. (#25522)
2932

3033
4.0.18 - 10/24/25
3134
-----------------

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: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
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

1313
var LibraryDylink = {
@@ -170,10 +170,10 @@ var LibraryDylink = {
170170
get(obj, symName) {
171171
var rtn = GOT[symName];
172172
if (!rtn) {
173-
rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
174173
#if DYLINK_DEBUG == 2
175-
dbg("new GOT entry: " + symName);
174+
dbg(`new GOT entry: ${symName}`);
176175
#endif
176+
rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true}, {{{ to64(-1) }}});
177177
}
178178
if (!currentModuleWeakSymbols.has(symName)) {
179179
// Any non-weak reference to a symbol marks it as `required`, which
@@ -189,6 +189,11 @@ var LibraryDylink = {
189189
$isInternalSym: (symName) => {
190190
// TODO: find a way to mark these in the binary or avoid exporting them.
191191
return [
192+
'memory',
193+
'__memory_base',
194+
'__table_base',
195+
'__stack_pointer',
196+
'__indirect_function_table',
192197
'__cpp_exception',
193198
'__c_longjmp',
194199
'__wasm_apply_data_relocs',
@@ -213,6 +218,7 @@ var LibraryDylink = {
213218

214219
$updateGOT__internal: true,
215220
$updateGOT__deps: ['$GOT', '$isInternalSym', '$addFunction'],
221+
$updateGOT__docs: '/** @param {boolean=} replace */',
216222
$updateGOT: (exports, replace) => {
217223
#if DYLINK_DEBUG
218224
dbg(`updateGOT: adding ${Object.keys(exports).length} symbols`);
@@ -230,8 +236,7 @@ var LibraryDylink = {
230236
}
231237
#endif
232238

233-
234-
var existingEntry = GOT[symName] && GOT[symName].value != 0;
239+
var existingEntry = GOT[symName] && GOT[symName].value != {{{ to64(-1) }}};
235240
if (replace || !existingEntry) {
236241
#if DYLINK_DEBUG == 2
237242
dbg(`updateGOT: before: ${symName} : ${GOT[symName]?.value}`);
@@ -252,7 +257,7 @@ var LibraryDylink = {
252257
#if DYLINK_DEBUG == 2
253258
dbg(`updateGOT: after: ${symName} : ${newValue} (${value})`);
254259
#endif
255-
GOT[symName] ||= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
260+
GOT[symName] ??= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
256261
GOT[symName].value = newValue;
257262
}
258263
#if DYLINK_DEBUG
@@ -280,9 +285,12 @@ var LibraryDylink = {
280285

281286
// Applies relocations to exported things.
282287
$relocateExports__internal: true,
283-
$relocateExports__deps: ['$updateGOT', '$isImmutableGlobal'],
284-
$relocateExports__docs: '/** @param {boolean=} replace */',
285-
$relocateExports: (exports, memoryBase, replace) => {
288+
$relocateExports__deps: ['$isImmutableGlobal'],
289+
$relocateExports: (exports, memoryBase = 0) => {
290+
#if DYLINK_DEBUG
291+
dbg(`relocateExports memoryBase=${memoryBase} count=${Object.keys(exports).length}`);
292+
#endif
293+
286294
function relocateExport(name, value) {
287295
#if SPLIT_MODULE
288296
// Do not modify exports synthesized by wasm-split
@@ -304,7 +312,6 @@ var LibraryDylink = {
304312
for (var e in exports) {
305313
relocated[e] = relocateExport(e, exports[e])
306314
}
307-
updateGOT(relocated, replace);
308315
return relocated;
309316
},
310317

@@ -315,13 +322,17 @@ var LibraryDylink = {
315322
dbg('reportUndefinedSymbols');
316323
#endif
317324
for (var [symName, entry] of Object.entries(GOT)) {
318-
if (entry.value == 0) {
325+
if (entry.value == {{{ to64(-1) }}}) {
326+
#if DYLINK_DEBUG
327+
dbg(`undef GOT entry: ${symName}`);
328+
#endif
319329
var value = resolveGlobalSymbol(symName, true).sym;
320330
if (!value && !entry.required) {
321331
// Ignore undefined symbols that are imported as weak.
322332
#if DYLINK_DEBUG
323333
dbg('ignoring undefined weak symbol:', symName);
324334
#endif
335+
entry.value = {{{ to64(0) }}};
325336
continue;
326337
}
327338
#if ASSERTIONS
@@ -343,7 +354,7 @@ var LibraryDylink = {
343354
entry.value = value;
344355
#endif
345356
} else {
346-
throw new Error(`bad export type for '${symName}': ${typeof value}`);
357+
throw new Error(`bad export type for '${symName}': ${typeof value} (${value})`);
347358
}
348359
}
349360
}
@@ -390,7 +401,7 @@ var LibraryDylink = {
390401
// Allocate memory even if malloc isn't ready yet. The allocated memory here
391402
// must be zero initialized since its used for all static data, including bss.
392403
$getMemory__noleakcheck: true,
393-
$getMemory__deps: ['$GOT', '__heap_base', '$alignMemory', 'calloc'],
404+
$getMemory__deps: ['$GOT', 'emscripten_get_sbrk_ptr', '__heap_base', '$alignMemory', 'calloc'],
394405
$getMemory: (size) => {
395406
// After the runtime is initialized, we must only use sbrk() normally.
396407
#if DYLINK_DEBUG
@@ -409,7 +420,25 @@ var LibraryDylink = {
409420
assert(end <= HEAP8.length, 'failure to getMemory - memory growth etc. is not supported there, call malloc/sbrk directly or increase INITIAL_MEMORY');
410421
#endif
411422
___heap_base = end;
423+
424+
// After allocating the memory from the start the heap we need to ensure
425+
// that once the program starts it doesn't use this region. In relocatable
426+
// mode we can just updat ethe __heap_base symbol that we are exporting to
427+
// the main module.
428+
// When not relocatable `__heap_base` is fixed and exported by the main
429+
// module, but we can update the `sbrk_ptr` value instead.
430+
#if RELOCATABLE
412431
GOT['__heap_base'].value = {{{ to64('end') }}};
432+
#else
433+
#if PTHREADS
434+
if (!ENVIRONMENT_IS_PTHREAD) {
435+
#endif
436+
var sbrk_ptr = _emscripten_get_sbrk_ptr();
437+
{{{ makeSetValue('sbrk_ptr', 0, 'end', '*') }}}
438+
#if PTHREADS
439+
}
440+
#endif
441+
#endif
413442
return ret;
414443
},
415444

@@ -622,7 +651,7 @@ var LibraryDylink = {
622651
* @param {number=} handle
623652
*/`,
624653
$loadWebAssemblyModule__deps: [
625-
'$loadDynamicLibrary', '$getMemory',
654+
'$loadDynamicLibrary', '$getMemory', '$updateGOT',
626655
'$relocateExports', '$resolveGlobalSymbol', '$GOTHandler',
627656
'$getDylinkMetadata', '$alignMemory',
628657
'$currentModuleWeakSymbols',
@@ -632,7 +661,7 @@ var LibraryDylink = {
632661
],
633662
$loadWebAssemblyModule: (binary, flags, libName, localScope, handle) => {
634663
#if DYLINK_DEBUG
635-
dbg('loadWebAssemblyModule:', libName);
664+
dbg('loadWebAssemblyModule:', libName, handle);
636665
#endif
637666
var metadata = getDylinkMetadata(binary);
638667

@@ -651,6 +680,9 @@ var LibraryDylink = {
651680
// exclusive access to it for the duration of this function. See the
652681
// locking in `dynlink.c`.
653682
var firstLoad = !handle || !{{{ makeGetValue('handle', C_STRUCTS.dso.mem_allocated, 'i8') }}};
683+
#if DYLINK_DEBUG
684+
dbg('firstLoad:', firstLoad);
685+
#endif
654686
if (firstLoad) {
655687
#endif
656688
// alignments are powers of 2
@@ -787,6 +819,7 @@ var LibraryDylink = {
787819
// add new entries to functionsInTableMap
788820
updateTableMap(tableBase, metadata.tableSize);
789821
moduleExports = relocateExports(instance.exports, memoryBase);
822+
updateGOT(moduleExports);
790823
#if ASYNCIFY
791824
moduleExports = Asyncify.instrumentWasmExports(moduleExports);
792825
#endif
@@ -878,18 +911,27 @@ var LibraryDylink = {
878911
if (applyRelocs) {
879912
if (runtimeInitialized) {
880913
#if DYLINK_DEBUG
881-
dbg('applyRelocs');
914+
dbg('running __wasm_apply_data_relocs');
882915
#endif
883916
applyRelocs();
884917
} else {
918+
#if DYLINK_DEBUG
919+
dbg('delaying __wasm_apply_data_relocs');
920+
#endif
885921
__RELOC_FUNCS__.push(applyRelocs);
886922
}
887923
}
888924
var init = moduleExports['__wasm_call_ctors'];
889925
if (init) {
890926
if (runtimeInitialized) {
927+
#if DYLINK_DEBUG
928+
dbg('running __wasm_call_ctors');
929+
#endif
891930
init();
892931
} else {
932+
#if DYLINK_DEBUG
933+
dbg('delaying __wasm_call_ctors');
934+
#endif
893935
// we aren't ready to run compiled code yet
894936
addOnPostCtor(init);
895937
}

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)