Skip to content

Commit 26fec3f

Browse files
authored
Fix for weak undefined symbols in dynamic linking (#17164)
With this change we maintain a global set of weak undefined symbols (WeakSymbols), when modules are loaded the `dylink` section tells us which imports are weak imports. We then resolve weakly imported symbol addresses to zero without generating any errors. Sadly there is some asymmetry with the way the main module is loaded and the way side modules are, which makes this patch a litte more complex than I would like. For example, the weak imports of the main module are extracted at build time rather than runtime. Fixes: #12819
1 parent a36ffd3 commit 26fec3f

11 files changed

+96
-20
lines changed

emscripten.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,15 @@ def to_nice_ident(ident): # limited version of the JS function toNiceIdent
112112
return ident.replace('%', '$').replace('@', '_').replace('.', '_')
113113

114114

115-
def update_settings_glue(metadata):
115+
def get_weak_imports(main_wasm):
116+
dylink_sec = webassembly.parse_dylink_section(main_wasm)
117+
for symbols in dylink_sec.import_info.values():
118+
for symbol, flags in symbols.items():
119+
if flags & webassembly.SYMBOL_BINDING_MASK == webassembly.SYMBOL_BINDING_WEAK:
120+
settings.WEAK_IMPORTS.append(symbol)
121+
122+
123+
def update_settings_glue(wasm_file, metadata):
116124
optimize_syscalls(metadata['declares'])
117125

118126
# Integrate info from backend
@@ -124,6 +132,8 @@ def update_settings_glue(metadata):
124132
syms = set(syms).difference(metadata['exports'])
125133
syms.update(metadata['globalImports'])
126134
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = sorted(syms)
135+
if settings.MAIN_MODULE:
136+
get_weak_imports(wasm_file)
127137

128138
settings.WASM_EXPORTS = metadata['exports'] + list(metadata['namedGlobals'].keys())
129139
# Store function exports so that Closure and metadce can track these even in
@@ -296,7 +306,7 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile):
296306

297307
metadata = finalize_wasm(in_wasm, out_wasm, memfile)
298308

299-
update_settings_glue(metadata)
309+
update_settings_glue(out_wasm, metadata)
300310

301311
if not settings.WASM_BIGINT and metadata['emJsFuncs']:
302312
module = webassembly.Module(in_wasm)

src/compiler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ EXPORTED_FUNCTIONS = new Set(EXPORTED_FUNCTIONS);
5252
WASM_EXPORTS = new Set(WASM_EXPORTS);
5353
SIDE_MODULE_EXPORTS = new Set(SIDE_MODULE_EXPORTS);
5454
INCOMING_MODULE_JS_API = new Set(INCOMING_MODULE_JS_API);
55+
WEAK_IMPORTS = new Set(WEAK_IMPORTS);
5556

5657
// Side modules are pure wasm and have no JS
5758
assert(!SIDE_MODULE, 'JS compiler should not run on side modules');

src/jsifier.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,17 @@ function ${name}(${args}) {
197197
return '';
198198
}
199199

200-
let noExport = false;
200+
// This gets set to true in the case of dynamic linking for symbols that
201+
// are undefined in the main module. In this case we create a stub that
202+
// will resolve the correct symbol at runtime, or assert if its missing.
203+
let isStub = false;
201204

202205
if (!LibraryManager.library.hasOwnProperty(ident)) {
203206
if (ONLY_CALC_JS_SYMBOLS) {
204207
return;
205208
}
206-
if (!isDefined(ident)) {
209+
const isWeakImport = WEAK_IMPORTS.has(ident);
210+
if (!isDefined(ident) && !isWeakImport) {
207211
let undefinedSym = ident;
208212
if (ident === '__main_argc_argv') {
209213
undefinedSym = 'main';
@@ -239,7 +243,7 @@ function ${name}(${args}) {
239243
}
240244
const functionBody = assertion + `return ${target}.apply(null, arguments);`;
241245
LibraryManager.library[ident] = new Function(functionBody);
242-
noExport = true;
246+
isStub = true;
243247
}
244248
}
245249

@@ -383,7 +387,7 @@ function ${name}(${args}) {
383387
const sig = LibraryManager.library[ident + '__sig'];
384388
// asm module exports are done in emscripten.py, after the asm module is ready. Here
385389
// we also export library methods as necessary.
386-
if ((EXPORT_ALL || EXPORTED_FUNCTIONS.has(finalName)) && !noExport) {
390+
if ((EXPORT_ALL || EXPORTED_FUNCTIONS.has(finalName)) && !isStub) {
387391
contentText += `\nModule["${finalName}"] = ${finalName};`;
388392
}
389393
// Relocatable code needs signatures to create proper wrappers. Stack
@@ -394,6 +398,9 @@ function ${name}(${args}) {
394398
if (sig && (RELOCATABLE || ASYNCIFY == 2)) {
395399
contentText += `\n${finalName}.sig = '${sig}';`;
396400
}
401+
if (isStub) {
402+
contentText += `\n${finalName}.stub = true;`;
403+
}
397404

398405
let commentText = '';
399406
if (LibraryManager.library[ident + '__docs']) {

src/library_dylink.js

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ var LibraryDylink = {
2020
#endif
2121
if (!sym) {
2222
sym = asmLibraryArg[symName];
23+
// Ignore 'stub' symbols that are auto-generated as part of the original
24+
// `asmLibraryArg` used to instantate the main module.
25+
if (sym && sym.stub) sym = undefined;
2326
}
2427

2528
// Check for the symbol on the Module object. This is the only
@@ -44,20 +47,28 @@ var LibraryDylink = {
4447
},
4548

4649
$GOT: {},
50+
$CurrentModuleWeakSymbols: '=new Set({{{ JSON.stringify(Array.from(WEAK_IMPORTS)) }}})',
4751

4852
// Create globals to each imported symbol. These are all initialized to zero
4953
// and get assigned later in `updateGOT`
5054
$GOTHandler__internal: true,
51-
$GOTHandler__deps: ['$GOT'],
55+
$GOTHandler__deps: ['$GOT', '$CurrentModuleWeakSymbols'],
5256
$GOTHandler: {
5357
'get': function(obj, symName) {
54-
if (!GOT[symName]) {
55-
GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
58+
var rtn = GOT[symName];
59+
if (!rtn) {
60+
rtn = GOT[symName] = new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
5661
#if DYLINK_DEBUG
5762
err("new GOT entry: " + symName);
5863
#endif
5964
}
60-
return GOT[symName]
65+
if (!CurrentModuleWeakSymbols.has(symName)) {
66+
// Any non-weak reference to a symbol marks it as `required`, which
67+
// enabled `reportUndefinedSymbols` to report undefeind symbol errors
68+
// correctly.
69+
rtn.required = true;
70+
}
71+
return rtn;
6172
}
6273
},
6374

@@ -175,6 +186,13 @@ var LibraryDylink = {
175186
for (var symName in GOT) {
176187
if (GOT[symName].value == 0) {
177188
var value = resolveGlobalSymbol(symName, true)
189+
if (!value && !GOT[symName].required) {
190+
// Ignore undefined symbols that are imported as weak.
191+
#if DYLINK_DEBUG
192+
err('ignoring undefined weak symbol: ' + symName);
193+
#endif
194+
continue;
195+
}
178196
#if ASSERTIONS
179197
assert(value, 'undefined symbol `' + symName + '`. perhaps a side module was not linked in? if this global was expected to arrive from a system library, try to build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment');
180198
#endif
@@ -349,7 +367,7 @@ var LibraryDylink = {
349367
name = getString();
350368
}
351369

352-
var customSection = { neededDynlibs: [], tlsExports: {} };
370+
var customSection = { neededDynlibs: [], tlsExports: new Set(), weakImports: new Set() };
353371
if (name == 'dylink') {
354372
customSection.memorySize = getLEB();
355373
customSection.memoryAlign = getLEB();
@@ -368,7 +386,10 @@ var LibraryDylink = {
368386
var WASM_DYLINK_MEM_INFO = 0x1;
369387
var WASM_DYLINK_NEEDED = 0x2;
370388
var WASM_DYLINK_EXPORT_INFO = 0x3;
389+
var WASM_DYLINK_IMPORT_INFO = 0x4;
371390
var WASM_SYMBOL_TLS = 0x100;
391+
var WASM_SYMBOL_BINDING_MASK = 0x3;
392+
var WASM_SYMBOL_BINDING_WEAK = 0x1;
372393
while (offset < end) {
373394
var subsectionType = getU8();
374395
var subsectionSize = getLEB();
@@ -389,7 +410,17 @@ var LibraryDylink = {
389410
var symname = getString();
390411
var flags = getLEB();
391412
if (flags & WASM_SYMBOL_TLS) {
392-
customSection.tlsExports[symname] = 1;
413+
customSection.tlsExports.add(symname);
414+
}
415+
}
416+
} else if (subsectionType === WASM_DYLINK_IMPORT_INFO) {
417+
var count = getLEB();
418+
while (count--) {
419+
var modname = getString();
420+
var symname = getString();
421+
var flags = getLEB();
422+
if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) {
423+
customSection.weakImports.add(symname);
393424
}
394425
}
395426
} else {
@@ -471,9 +502,12 @@ var LibraryDylink = {
471502
'$loadDynamicLibrary', '$createInvokeFunction', '$getMemory',
472503
'$relocateExports', '$resolveGlobalSymbol', '$GOTHandler',
473504
'$getDylinkMetadata', '$alignMemory', '$zeroMemory',
505+
'$alignMemory', '$zeroMemory',
506+
'$CurrentModuleWeakSymbols', '$alignMemory', '$zeroMemory',
474507
],
475508
$loadWebAssemblyModule: function(binary, flags, handle) {
476509
var metadata = getDylinkMetadata(binary);
510+
CurrentModuleWeakSymbols = metadata.weakImports;
477511
#if ASSERTIONS
478512
var originalTable = wasmTable;
479513
#endif

src/library_pthread.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,14 +491,13 @@ var LibraryPThread = {
491491
if (!__tls_base) {
492492
#if ASSERTIONS
493493
// __tls_base should never be zero if there are tls exports
494-
assert(__tls_base || Object.keys(metadata.tlsExports).length == 0);
494+
assert(__tls_base || metadata.tlsExports.size == 0);
495495
#endif
496496
return;
497497
}
498-
for (var sym in metadata.tlsExports) {
499-
metadata.tlsExports[sym] = moduleExports[sym];
500-
}
501-
relocateExports(metadata.tlsExports, __tls_base, /*replace=*/true);
498+
var tlsExports = {};
499+
metadata.tlsExports.forEach((s) => tlsExports[s] = moduleExports[s]);
500+
relocateExports(tlsExports, __tls_base, /*replace=*/true);
502501
}
503502

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

src/settings_internal.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,10 @@ var TRANSPILE_TO_ES5 = false;
226226

227227
// A copy of the default the default INCOMING_MODULE_JS_API. (Soon to
228228
// include additional items).
229-
var ALL_INCOMING_MODULE_JS_API = []
229+
var ALL_INCOMING_MODULE_JS_API = [];
230+
231+
// List of all imports that are weak, and therefore allowed to be undefined at
232+
// runtime. This is used by the JS compiler to avoid build-time warnings/errors
233+
// when weak symbols are undefined. Only applies in the case of dyanmic linking
234+
// (MAIN_MODULE).
235+
var WEAK_IMPORTS = [];

tests/other/test_extern_weak.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ extern int __attribute__((weak)) externData;
66

77
int main() {
88
printf("externFunc: %p\n", externFunc);
9-
printf("externData: %p\n", &externData);
9+
printf("externData: %p\n", &externData);
10+
if (externFunc)
11+
printf("externFunc value: %d\n", externFunc());
12+
if (externData)
13+
printf("externData value: %d\n", externData);
1014
return 0;
1115
}
1216

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
externFunc value: 42
2+
externData value: 43

tests/other/test_extern_weak_side.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
int externFunc() {
2+
return 42;
3+
}
4+
5+
int externData = 43;

tests/test_other.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8274,9 +8274,12 @@ def test_fflush_fs_exit(self):
82748274
def test_extern_weak(self):
82758275
self.do_other_test('test_extern_weak.c')
82768276

8277-
@disabled('https://github.com/emscripten-core/emscripten/issues/12819')
82788277
def test_extern_weak_dynamic(self):
8278+
# If the symbols are left undefined we should get the same expected output as with static
8279+
# linking (i.e. null symbol addresses);
82798280
self.do_other_test('test_extern_weak.c', emcc_args=['-sMAIN_MODULE=2'])
8281+
self.run_process([EMCC, '-o', 'libside.wasm', test_file('other/test_extern_weak_side.c'), '-sSIDE_MODULE'])
8282+
self.do_run_in_out_file_test(test_file('other/test_extern_weak.c'), out_suffix='.resolved', emcc_args=['-sMAIN_MODULE=2', 'libside.wasm'])
82808283

82818284
def test_main_module_without_main(self):
82828285
create_file('pre.js', r'''

0 commit comments

Comments
 (0)