Use `asmLibraryArg` when resolving symbols dynamically (#13949)
Use `asmLibraryArg` as the primary symbol map used in dynamic linking. Previously we would use the `Module` object itself when looking up symbols and when merging in new symbols. After this change we continue to export new dynamic symbols on the Module object but we might want to consider changing that in the future. One of the issues that this fixes is that it allows JS library symbols that were exported on `asmLibraryArg` but *not* exported on the Module object (i.e. not included in DEFAULT_LIBRARY_FUNCS_TO_INCLUDE) to be visible to the dynamic linker. The test here requires that since it needs to assign a GOT entry to a JS library function that is not exported via DEFAULT_LIBRARY_FUNCS_TO_INCLUDE. Fixes: #13786
This commit is contained in:
Родитель
eb36c3b3b6
Коммит
99942ab74d
19
emcc.py
19
emcc.py
|
@ -651,20 +651,19 @@ def filter_out_duplicate_dynamic_libs(inputs):
|
|||
def process_dynamic_libs(dylibs):
|
||||
for dylib in dylibs:
|
||||
imports = webassembly.get_imports(dylib)
|
||||
new_exports = []
|
||||
for imp in imports:
|
||||
if imp.kind not in (webassembly.ExternType.FUNC, webassembly.ExternType.GLOBAL):
|
||||
continue
|
||||
new_exports.append(imp.field)
|
||||
logger.debug('Adding exports based on `%s`: %s', dylib, new_exports)
|
||||
settings.EXPORTED_FUNCTIONS.extend(shared.asmjs_mangle(e) for e in new_exports)
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(new_exports)
|
||||
building.user_requested_exports.update(shared.asmjs_mangle(e) for e in new_exports)
|
||||
imports = [i.field for i in imports if i.kind in (webassembly.ExternType.FUNC, webassembly.ExternType.GLOBAL)]
|
||||
settings.SIDE_MODULE_IMPORTS.extend(imports)
|
||||
logger.debug('Adding symbols requirements from `%s`: %s', dylib, imports)
|
||||
|
||||
exports = webassembly.get_exports(dylib)
|
||||
for export in exports:
|
||||
settings.SIDE_MODULE_EXPORTS.append(export.name)
|
||||
|
||||
mangled_imports = [shared.asmjs_mangle(e) for e in settings.SIDE_MODULE_IMPORTS]
|
||||
settings.EXPORTED_FUNCTIONS.extend(mangled_imports)
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(settings.SIDE_MODULE_IMPORTS)
|
||||
building.user_requested_exports.update(mangled_imports)
|
||||
|
||||
|
||||
def unmangle_symbols_from_cmdline(symbols):
|
||||
def unmangle(x):
|
||||
|
@ -1514,7 +1513,7 @@ def phase_setup(state):
|
|||
settings.RELOCATABLE = 1
|
||||
|
||||
if settings.MAIN_MODULE:
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getDylinkMetadata']
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getDylinkMetadata', '$mergeLibSymbols']
|
||||
|
||||
if settings.RELOCATABLE:
|
||||
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
|
||||
|
|
|
@ -11,23 +11,19 @@ var LibraryDylink = {
|
|||
#if !WASM_BIGINT
|
||||
if (direct) {
|
||||
// First look for the orig$ symbol which is the symbols without
|
||||
// any legalization performed. Here we look on the 'asm' object
|
||||
// to avoid any JS wrapping of the symbol.
|
||||
sym = Module['asm']['orig$' + symName];
|
||||
// any legalization performed.
|
||||
sym = asmLibraryArg['orig$' + symName];
|
||||
}
|
||||
#endif
|
||||
// Then look for the unmangled name itself.
|
||||
if (!sym) {
|
||||
sym = Module['asm'][symName];
|
||||
sym = asmLibraryArg[symName];
|
||||
}
|
||||
// fall back to the mangled name on the module object which could include
|
||||
// JavaScript functions and wrapped native functions.
|
||||
#if !WASM_BIGINT
|
||||
if (!sym && direct) {
|
||||
sym = Module['_orig$' + symName];
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check for the symbol on the Module object. This is the only
|
||||
// way to dynamically access JS library symbols that were not
|
||||
// referenced by the main module (and therefore not part of the
|
||||
// initial set of symbols included in asmLibraryArg when it
|
||||
// was declared.
|
||||
if (!sym) {
|
||||
sym = Module[asmjsMangle(symName)];
|
||||
}
|
||||
|
@ -328,10 +324,10 @@ var LibraryDylink = {
|
|||
|
||||
// Module.symbols <- libModule.symbols (flags.global handler)
|
||||
$mergeLibSymbols__deps: ['$asmjsMangle'],
|
||||
$mergeLibSymbols: function(libModule, libName) {
|
||||
$mergeLibSymbols: function(exports, libName) {
|
||||
// add symbols into global namespace TODO: weak linking etc.
|
||||
for (var sym in libModule) {
|
||||
if (!libModule.hasOwnProperty(sym)) {
|
||||
for (var sym in exports) {
|
||||
if (!exports.hasOwnProperty(sym)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -341,23 +337,24 @@ var LibraryDylink = {
|
|||
// We should copy the symbols (which include methods and variables) from SIDE_MODULE to MAIN_MODULE.
|
||||
|
||||
if (!asmLibraryArg.hasOwnProperty(sym)) {
|
||||
asmLibraryArg[sym] = libModule[sym];
|
||||
}
|
||||
|
||||
var module_sym = asmjsMangle(sym);
|
||||
|
||||
if (!Module.hasOwnProperty(module_sym)) {
|
||||
Module[module_sym] = libModule[sym];
|
||||
asmLibraryArg[sym] = exports[sym];
|
||||
}
|
||||
#if ASSERTIONS == 2
|
||||
else {
|
||||
var curr = Module[sym], next = libModule[sym];
|
||||
var curr = asmLibraryArg[sym], next = exports[sym];
|
||||
// don't warn on functions - might be odr, linkonce_odr, etc.
|
||||
if (!(typeof curr === 'function' && typeof next === 'function')) {
|
||||
err("warning: symbol '" + sym + "' from '" + libName + "' already exists (duplicate symbol? or weak linking, which isn't supported yet?)"); // + [curr, ' vs ', next]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Export native export on the Module object.
|
||||
// TODO(sbc): Do all users want this? Should we skip this by default?
|
||||
var module_sym = asmjsMangle(sym);
|
||||
if (!Module.hasOwnProperty(module_sym)) {
|
||||
Module[module_sym] = exports[sym];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -436,7 +433,7 @@ var LibraryDylink = {
|
|||
// are. To do that here, we use a JS proxy (another option would
|
||||
// be to inspect the binary directly).
|
||||
var proxyHandler = {
|
||||
'get': function(obj, prop) {
|
||||
'get': function(stubs, prop) {
|
||||
// symbols that should be local to this module
|
||||
switch (prop) {
|
||||
case '__memory_base':
|
||||
|
@ -444,21 +441,26 @@ var LibraryDylink = {
|
|||
case '__table_base':
|
||||
return tableBase;
|
||||
}
|
||||
if (prop in obj) {
|
||||
return obj[prop]; // already present
|
||||
if (prop in asmLibraryArg) {
|
||||
// No stub needed, symbol already exists in symbol table
|
||||
return asmLibraryArg[prop];
|
||||
}
|
||||
// otherwise this is regular function import - call it indirectly
|
||||
var resolved;
|
||||
return obj[prop] = function() {
|
||||
if (!resolved) resolved = resolveSymbol(prop, true);
|
||||
return resolved.apply(null, arguments);
|
||||
};
|
||||
// Return a stub function that will resolve the symbol
|
||||
// when first called.
|
||||
if (!(prop in stubs)) {
|
||||
var resolved;
|
||||
stubs[prop] = function() {
|
||||
if (!resolved) resolved = resolveSymbol(prop, true);
|
||||
return resolved.apply(null, arguments);
|
||||
};
|
||||
}
|
||||
return stubs[prop];
|
||||
}
|
||||
};
|
||||
var proxy = new Proxy(asmLibraryArg, proxyHandler);
|
||||
var proxy = new Proxy({}, proxyHandler);
|
||||
var info = {
|
||||
'GOT.mem': new Proxy(asmLibraryArg, GOTHandler),
|
||||
'GOT.func': new Proxy(asmLibraryArg, GOTHandler),
|
||||
'GOT.mem': new Proxy({}, GOTHandler),
|
||||
'GOT.func': new Proxy({}, GOTHandler),
|
||||
'env': proxy,
|
||||
{{{ WASI_MODULE_NAME }}}: proxy,
|
||||
};
|
||||
|
|
|
@ -951,6 +951,7 @@ function createWasm() {
|
|||
if (metadata.neededDynlibs) {
|
||||
dynamicLibraries = metadata.neededDynlibs.concat(dynamicLibraries);
|
||||
}
|
||||
mergeLibSymbols(exports, 'main')
|
||||
#endif
|
||||
|
||||
#if !IMPORTED_MEMORY
|
||||
|
|
|
@ -25,6 +25,10 @@ var WASM_FUNCTION_EXPORTS = [];
|
|||
// underscore.
|
||||
var SIDE_MODULE_EXPORTS = [];
|
||||
|
||||
// All symbols imported by side modules. These are symbols that the main
|
||||
// module (or other side modules) will need to provide.
|
||||
var SIDE_MODULE_IMPORTS = [];
|
||||
|
||||
// stores the base name of the output file (-o TARGET_BASENAME.js)
|
||||
var TARGET_BASENAME = '';
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <emscripten.h>
|
||||
|
||||
extern void jsPrintHello();
|
||||
|
||||
void (*fnPtr)() = &jsPrintHello;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Indirect call to jsPrintHello
|
||||
fnPtr();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
mergeInto(LibraryManager.library, {
|
||||
jsPrintHello__sig: "v",
|
||||
jsPrintHello: function() {
|
||||
console.log("Hello, world! from JS");
|
||||
}
|
||||
});
|
|
@ -3594,7 +3594,7 @@ ok
|
|||
return self.dylink_testf(main, side, expected, force_c, **kwargs)
|
||||
|
||||
def dylink_testf(self, main, side, expected=None, force_c=False, main_emcc_args=[],
|
||||
need_reverse=True, auto_load=True, main_module=2, **kwargs):
|
||||
need_reverse=True, auto_load=True, **kwargs):
|
||||
self.maybe_closure()
|
||||
# Same as dylink_test but takes source code as filenames on disc.
|
||||
old_args = self.emcc_args.copy()
|
||||
|
@ -3617,7 +3617,7 @@ ok
|
|||
shutil.move(out_file, 'liblib.so')
|
||||
|
||||
# main settings
|
||||
self.set_setting('MAIN_MODULE', main_module)
|
||||
self.set_setting('MAIN_MODULE', 2)
|
||||
self.clear_setting('SIDE_MODULE')
|
||||
if auto_load:
|
||||
# Normally we don't report undefined symbols when linking main modules but
|
||||
|
@ -3644,7 +3644,7 @@ ok
|
|||
# Test the reverse as well. There we flip the role of the side module and main module.
|
||||
# - We add --no-entry since the side module doesn't have a `main`
|
||||
self.dylink_testf(side, main, expected, force_c, main_emcc_args + ['--no-entry'],
|
||||
need_reverse=False, main_module=main_module, **kwargs)
|
||||
need_reverse=False, **kwargs)
|
||||
|
||||
def do_basic_dylink_test(self, **kwargs):
|
||||
self.dylink_test(r'''
|
||||
|
@ -4329,8 +4329,6 @@ res64 - external 64\n''', header='''
|
|||
@with_both_exception_handling
|
||||
@needs_dylink
|
||||
def test_dylink_raii_exceptions(self):
|
||||
# MAIN_MODULE=1 still needed in this test due to:
|
||||
# https://github.com/emscripten-core/emscripten/issues/13786
|
||||
self.dylink_test(main=r'''
|
||||
#include <stdio.h>
|
||||
extern int side();
|
||||
|
@ -4357,7 +4355,7 @@ res64 - external 64\n''', header='''
|
|||
volatile ifdi p = func_with_special_sig;
|
||||
return p(2.18281, 3.14159, 42);
|
||||
}
|
||||
''', expected=['special 2.182810 3.141590 42\ndestroy\nfrom side: 1337.\n'], main_module=1)
|
||||
''', expected=['special 2.182810 3.141590 42\ndestroy\nfrom side: 1337.\n'])
|
||||
|
||||
@needs_dylink
|
||||
@disabled('https://github.com/emscripten-core/emscripten/issues/12815')
|
||||
|
@ -4599,12 +4597,9 @@ res64 - external 64\n''', header='''
|
|||
}
|
||||
'''
|
||||
|
||||
# MAIN_MODULE=1 still needed in this test due to:
|
||||
# https://github.com/emscripten-core/emscripten/issues/13786
|
||||
self.dylink_test(main=main,
|
||||
side=side,
|
||||
header=header,
|
||||
main_module=1,
|
||||
expected='success')
|
||||
|
||||
@needs_dylink
|
||||
|
@ -8500,6 +8495,12 @@ NODEFS is no longer included by default; build with -lnodefs.js
|
|||
self.set_setting('MAIN_MODULE')
|
||||
self.do_runf(test_file('core', 'test_gl_get_proc_address.c'))
|
||||
|
||||
@needs_dylink
|
||||
def test_main_module_js_symbol(self):
|
||||
self.set_setting('MAIN_MODULE', 2)
|
||||
self.emcc_args += ['--js-library', test_file('core', 'test_main_module_js_symbol.js')]
|
||||
self.do_runf(test_file('core', 'test_main_module_js_symbol.c'))
|
||||
|
||||
def test_REVERSE_DEPS(self):
|
||||
create_file('connect.c', '#include <sys/socket.h>\nint main() { return (int)&connect; }')
|
||||
self.run_process([EMCC, 'connect.c'])
|
||||
|
|
|
@ -696,6 +696,14 @@ function emitDCEGraph(ast) {
|
|||
emptyOut(node); // ignore this in the second pass; this does not root
|
||||
return;
|
||||
}
|
||||
if (value.right.type === 'Literal') {
|
||||
// this is
|
||||
// var x = Module['x'] = 1234;
|
||||
// this form occurs when global addresses are exported from the
|
||||
// module. It doesn't constitute a usage.
|
||||
assert(typeof value.right.value === 'number');
|
||||
emptyOut(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -956,16 +956,27 @@ def metadce(js_file, wasm_file, minify_whitespace, debug_info):
|
|||
logger.debug('running meta-DCE')
|
||||
temp_files = configuration.get_temp_files()
|
||||
# first, get the JS part of the graph
|
||||
extra_info = '{ "exports": [' + ','.join(f'["{asmjs_mangle(x)}", "{x}"]' for x in settings.WASM_FUNCTION_EXPORTS) + ']}'
|
||||
if settings.MAIN_MODULE:
|
||||
# For the main module we include all exports as possible roots, not just function exports.
|
||||
# This means that any usages of data symbols within the JS or in the side modules can/will keep
|
||||
# these exports alive on the wasm module.
|
||||
# This is important today for weak data symbols that are defined by the main and the side module
|
||||
# (i.e. RTTI info). We want to make sure the main module's symbols get added to asmLibraryArg
|
||||
# when the main module is loaded. If this doesn't happen then the symbols in the side module
|
||||
# will take precedence.
|
||||
exports = settings.WASM_EXPORTS
|
||||
else:
|
||||
exports = settings.WASM_FUNCTION_EXPORTS
|
||||
extra_info = '{ "exports": [' + ','.join(f'["{asmjs_mangle(x)}", "{x}"]' for x in exports) + ']}'
|
||||
|
||||
txt = acorn_optimizer(js_file, ['emitDCEGraph', 'noPrint'], return_output=True, extra_info=extra_info)
|
||||
graph = json.loads(txt)
|
||||
# ensure that functions expected to be exported to the outside are roots
|
||||
required_symbols = user_requested_exports.union(set(settings.SIDE_MODULE_IMPORTS))
|
||||
for item in graph:
|
||||
if 'export' in item:
|
||||
export = item['export']
|
||||
# wasm backend's exports are prefixed differently inside the wasm
|
||||
export = asmjs_mangle(export)
|
||||
if export in user_requested_exports or settings.EXPORT_ALL:
|
||||
export = asmjs_mangle(item['export'])
|
||||
if settings.EXPORT_ALL or export in required_symbols:
|
||||
item['root'] = True
|
||||
# in standalone wasm, always export the memory
|
||||
if not settings.IMPORTED_MEMORY:
|
||||
|
|
Загрузка…
Ссылка в новой задаче