Skip to content

Commit

Permalink
Prepare for fastcomp update to fix cross-module function pointers (em…
Browse files Browse the repository at this point in the history
…scripten-core#9393)

There is upstream fastcomp change coming which addresses function
pointer equality between wasm modules (i.e. SIDE_MODULE/MAIN_MODULE):
emscripten-core/emscripten-fastcomp#259

This change prepares for that change by handling the new
externFunctions metadata attribute.  This new key represents a list
of all non-static functions in a relocatable module which are address
taken.  The module will call fp$<name>() in order to find out the
runtime address (table entry) for such functions.

Once both these changes land they they will fix emscripten-core#8268.
  • Loading branch information
sbc100 authored and belraquib committed Dec 23, 2020
1 parent 0f5ad5a commit c647a19
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 5 deletions.
23 changes: 21 additions & 2 deletions emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ def parse_fastcomp_output(backend_output, DEBUG):
logger.error('emscript: failure to parse metadata output from compiler backend. raw output is: \n' + metadata_raw)
raise

# This key is being added to fastcomp but doesn't exist in the current
# version.
metadata.setdefault('externFunctions', [])

if 'externUses' not in metadata:
exit_with_error('Your fastcomp compiler is out of date, please update! (need >= 1.38.26)')

Expand Down Expand Up @@ -354,6 +358,7 @@ def move_preasm(m):
global_funcs = sorted(global_funcs.difference(set(global_vars)).difference(implemented_functions))
if shared.Settings.RELOCATABLE:
global_funcs += ['g$' + extern for extern in metadata['externs']]
global_funcs += ['fp$' + extern for extern in metadata['externFunctions']]

# Tracks the set of used (minified) function names in
# JS symbols imported to asm.js module.
Expand Down Expand Up @@ -663,6 +668,7 @@ def update_settings_glue(metadata, DEBUG):
all_funcs = shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE + [shared.JS.to_nice_ident(d) for d in metadata['declares']]
implemented_funcs = [x[1:] for x in metadata['implementedFunctions']]
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = sorted(set(all_funcs).difference(implemented_funcs))

shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [x[1:] for x in metadata['externs']]

if metadata['simd']:
Expand Down Expand Up @@ -1503,14 +1509,27 @@ def create_asm_setup(debug_tables, function_table_data, invoke_function_names, m

def check(extern):
if shared.Settings.ASSERTIONS:
return ('\n assert(%sModule["%s"], "external global `%s` is missing.' % (side, extern, extern) +
return ('\n assert(%sModule["%s"] || %s, "external symbol `%s` is missing.' % (side, extern, extern, extern) +
'perhaps a side module was not linked in? if this symbol was expected to arrive '
'from a system library, try to build the MAIN_MODULE with '
'EMCC_FORCE_STDLIBS=1 in the environment");')
return ''

for extern in metadata['externs']:
asm_setup += 'var g$' + extern + ' = function() {' + check(extern) + '\n return ' + side + 'Module["' + extern + '"];\n}\n'
for extern in metadata['externFunctions']:
barename, sig = extern.split('$')
fullname = "fp$" + extern
key = '%sModule["%s"]' % (side, fullname)
asm_setup += '''\
var %s = function() {
if (!%s) { %s
var fid = addFunction(%sModule["%s"] || %s, "%s");
%s = fid;
}
return %s;
}
''' % (fullname, key, check(barename), side, barename, barename, sig, key, key)

asm_setup += create_invoke_wrappers(invoke_function_names)
asm_setup += setup_function_pointers(function_table_sigs)
Expand Down Expand Up @@ -1778,7 +1797,7 @@ def create_fp_accessors(metadata):
var func = Module['%(mangled)s'];
if (!func)
func = %(mangled)s;
var fp = addFunctionWasm(func, '%(sig)s');
var fp = addFunction(func, '%(sig)s');
Module['%(full)s'] = function() { return fp };
return fp;
}
Expand Down
5 changes: 4 additions & 1 deletion src/support.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ function loadWebAssemblyModule(binary, flags) {
return obj[prop] = function() {
if (!fp) {
var f = resolveSymbol(name, 'function');
fp = addFunctionWasm(f, sig);
fp = addFunction(f, sig);
}
return fp;
};
Expand Down Expand Up @@ -763,10 +763,13 @@ function removeFunctionWasm(index) {
// 'sig' parameter is required for the llvm backend but only when func is not
// already a WebAssembly function.
function addFunction(func, sig) {
#if ASSERTIONS
assert(typeof func !== 'undefined');
#if ASSERTIONS == 2
if (typeof sig === 'undefined') {
err('warning: addFunction(): You should provide a wasm function signature string as a second argument. This is not necessary for asm.js and asm2wasm, but can be required for the LLVM wasm backend, so it is recommended for full portability.');
}
#endif // ASSERTIONS == 2
#endif // ASSERTIONS

#if WASM_BACKEND
Expand Down
7 changes: 5 additions & 2 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3638,9 +3638,12 @@ def test_dylink_asmjs_funcpointers(self):
printf("main\n");
EM_ASM({
// make the function table sizes a non-power-of-two
alignFunctionTables();
Module['FUNCTION_TABLE_v'].push(0, 0, 0, 0, 0);
var newSize = alignFunctionTables();
//out('old size of function tables: ' + newSize);
while ((newSize & 3) !== 3) {
Module['FUNCTION_TABLE_v'].push(0);
newSize = alignFunctionTables();
}
//out('new size of function tables: ' + newSize);
// when masked, the two function pointers 1 and 2 should not happen to fall back to the right place
assert(((newSize+1) & 3) !== 1 || ((newSize+2) & 3) !== 2);
Expand Down

0 comments on commit c647a19

Please sign in to comment.