Skip to content

Commit

Permalink
Fix dynCall API after regression from PR emscripten-core#12059. Fixes…
Browse files Browse the repository at this point in the history
  • Loading branch information
juj committed Jan 20, 2021
1 parent ee483fc commit df01a0d
Show file tree
Hide file tree
Showing 11 changed files with 591 additions and 60 deletions.
5 changes: 5 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,9 @@ def default_setting(name, new_default):
if shared.Settings.CLOSURE_WARNINGS not in ['quiet', 'warn', 'error']:
exit_with_error('Invalid option -s CLOSURE_WARNINGS=%s specified! Allowed values are "quiet", "warn" or "error".' % shared.Settings.CLOSURE_WARNINGS)

# Calling function pointers from JS libraries is default runtime functionality, so always include the functionality. (to be DCEd if not used)
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$dynCall']

if shared.Settings.MAIN_MODULE:
assert not shared.Settings.SIDE_MODULE
if shared.Settings.MAIN_MODULE == 1:
Expand Down Expand Up @@ -2420,11 +2423,13 @@ def consume_arg_file():
options.llvm_opts = ['-Os']
options.requested_level = 2
shared.Settings.SHRINK_LEVEL = 1
shared.Settings.USE_LEGACY_DYNCALLS = 0 # In -Os builds, use a more size compact, but slower 'wasmTable.get()' method of accessing function pointers
settings_changes.append('INLINING_LIMIT=50')
elif options.requested_level == 'z':
options.llvm_opts = ['-Oz']
options.requested_level = 2
shared.Settings.SHRINK_LEVEL = 2
shared.Settings.USE_LEGACY_DYNCALLS = 0 # In -Oz builds, use a more size compact, but slower 'wasmTable.get()' method of accessing function pointers
settings_changes.append('INLINING_LIMIT=25')
shared.Settings.OPT_LEVEL = validate_arg_level(options.requested_level, 3, 'Invalid optimization level: ' + arg, clamp=True)
elif check_arg('--js-opts'):
Expand Down
40 changes: 38 additions & 2 deletions emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,45 @@
WASM_INIT_FUNC = '__wasm_call_ctors'


def make_wasm_table_static_dyncaller(func):
sig = func.replace('dynCall_', '')
ret = '' if sig[0] == 'v' else 'return '
args = '' # 'a0, a1, a2, ..., aN'
ptr_args = '' # 'ptr, a0, a1, a2, ..., aN'
i = 0
while i < len(sig)-1:
if i > 0: args += ', '
args += 'a' + str(i)
i += 1
ptr_args = ('ptr, ' + args) if len(args) > 0 else 'ptr'

dyncall = ('dyncalls["' + sig + '"]') if shared.Settings.MINIMAL_RUNTIME else ('Module["' + func + '"]')
wasmTableGet = 'wasmTableGet' if shared.Settings.SHRINK_LEVEL == 0 else 'wasmTable.get'
return 'function ' + func + '(' + ptr_args + ') { ' + ret + dyncall + '(' + ptr_args + '); }\n'


def compute_minimal_runtime_initializer_and_exports(post, exports, receiving):
# Declare all exports out to global JS scope so that JS library functions can access them in a
# way that minifies well with Closure
# e.g. var a,b,c,d,e,f;
exports_that_are_not_initializers = [x for x in exports if x not in WASM_INIT_FUNC]
# In Wasm backend the exports are still unmangled at this point, so mangle the names here
exports_that_are_not_initializers = [asmjs_mangle(x) for x in exports_that_are_not_initializers]

static_dyncall_sig_functions = ''

if shared.Settings.USE_LEGACY_DYNCALLS or not shared.Settings.WASM_BIGINT:
if len([x for x in exports_that_are_not_initializers if x.startswith('dynCall_')]) > 0:
exports_that_are_not_initializers += ['dynCalls = {}']
else:
for x in exports_that_are_not_initializers:
if x.startswith('dynCall_'):
static_dyncall_sig_functions += make_wasm_table_static_dyncaller(x) + '\n'

exports_that_are_not_initializers = [x for x in exports_that_are_not_initializers if not x.startswith('dynCall_')]

post = post.replace('/*** ASM_MODULE_EXPORTS_DECLARES ***/', 'var ' + ',\n '.join(exports_that_are_not_initializers) + ';')
post = post.replace('/*** STATIC_DYNCALL_SIG_FUNCTIONS ***/', static_dyncall_sig_functions)

# Generate assignments from all asm.js/wasm exports out to the JS variables above: e.g. a = asm['a']; b = asm['b'];
post = post.replace('/*** ASM_MODULE_EXPORTS ***/', receiving)
Expand Down Expand Up @@ -401,7 +432,7 @@ def finalize_wasm(infile, outfile, memfile, DEBUG):
args.append('-g')
if shared.Settings.WASM_BIGINT:
args.append('--bigint')
if shared.Settings.USE_LEGACY_DYNCALLS:
if True:##shared.Settings.USE_LEGACY_DYNCALLS:
# we need to add all dyncalls to the wasm
modify_wasm = True
else:
Expand Down Expand Up @@ -697,6 +728,8 @@ def create_receiving(exports):
return ''

exports_that_are_not_initializers = [x for x in exports if x != WASM_INIT_FUNC]
if not shared.Settings.USE_LEGACY_DYNCALLS and shared.Settings.WASM_BIGINT:
exports_that_are_not_initializers = [x for x in exports_that_are_not_initializers if not x.startswith('dynCall_')]

receiving = []

Expand All @@ -710,7 +743,10 @@ def create_receiving(exports):
# WebAssembly.instantiate(Module["wasm"], imports).then((function(output) {
# var asm = output.instance.exports;
# _main = asm["_main"];
receiving += [asmjs_mangle(s) + ' = asm["' + s + '"];' for s in exports_that_are_not_initializers]
for s in exports_that_are_not_initializers:
mangled = asmjs_mangle(s)
dynCallAssignment = ('dynCalls["' + s.replace('dynCall_', '') + '"] = ') if shared.Settings.USE_LEGACY_DYNCALLS or not shared.Settings.WASM_BIGINT and mangled.startswith('dynCall_') else ''
receiving += [dynCallAssignment + mangled + ' = asm["' + s + '"];']
else:
if shared.Settings.MINIMAL_RUNTIME:
# In wasm2js exports can be directly processed at top level, i.e.
Expand Down
56 changes: 0 additions & 56 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -3696,62 +3696,6 @@ LibraryManager.library = {
});
},

#if USE_LEGACY_DYNCALLS || !WASM_BIGINT
$dynCallLegacy: function(sig, ptr, args) {
#if ASSERTIONS
assert(('dynCall_' + sig) in Module, 'bad function pointer type - no table for sig \'' + sig + '\'');
if (args && args.length) {
// j (64-bit integer) must be passed in as two numbers [low 32, high 32].
assert(args.length === sig.substring(1).replace(/j/g, '--').length);
} else {
assert(sig.length == 1);
}
#endif
if (args && args.length) {
return Module['dynCall_' + sig].apply(null, [ptr].concat(args));
}
return Module['dynCall_' + sig].call(null, ptr);
},
$dynCall__deps: ['$dynCallLegacy'],

// Used in library code to get JS function from wasm function pointer.
// All callers should use direct table access where possible and only fall
// back to this function if needed.
$getDynCaller__deps: ['$dynCall'],
$getDynCaller: function(sig, ptr) {
#if !USE_LEGACY_DYNCALLS
assert(sig.indexOf('j') >= 0, 'getDynCaller should only be called with i64 sigs')
#endif
var argCache = [];
return function() {
argCache.length = arguments.length;
for (var i = 0; i < arguments.length; i++) {
argCache[i] = arguments[i];
}
return dynCall(sig, ptr, argCache);
};
},
#endif

$dynCall: function(sig, ptr, args) {
#if USE_LEGACY_DYNCALLS
return dynCallLegacy(sig, ptr, args);
#else
#if !WASM_BIGINT
// Without WASM_BIGINT support we cannot directly call function with i64 as
// part of thier signature, so we rely the dynCall functions generated by
// wasm-emscripten-finalize
if (sig.indexOf('j') != -1) {
return dynCallLegacy(sig, ptr, args);
}
#endif
#if ASSERTIONS
assert(wasmTable.get(ptr), 'missing table entry in dynCall: ' + ptr);
#endif
return wasmTable.get(ptr).apply(null, args)
#endif
},

$callRuntimeCallbacks: function(callbacks) {
while(callbacks.length > 0) {
var callback = callbacks.shift();
Expand Down
67 changes: 67 additions & 0 deletions src/library_dyncall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
mergeInto(LibraryManager.library, {
{{{ (function() { global.wbind = function() { return SHRINK_LEVEL == 0 ? 'wbind' : 'wasmTable.get'; }; return null; })(); }}}
{{{ (function() { global.getDynCaller = function(sig) { return MINIMAL_RUNTIME ? `dynCalls[${sig}]` : `Module["dynCall_${sig}]`; }; return null; })(); }}}

#if SHRINK_LEVEL == 0
// A mirror copy of contents of wasmTable in JS side, to avoid relatively
// slow wasmTable.get() call. Only used when not compiling with -Os or -Oz.
_wasmTableMirror: [],

$wbind__deps: ['_wasmTableMirror'],
$wbind: function(funcPtr) {
var func = __wasmTableMirror[funcPtr];
if (!func) {
if (funcPtr >= __wasmTableMirror.length) __wasmTableMirror.length = funcPtr + 1;
__wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr);
}
return func;
},

$dynCall__deps: ['$wbind'],
$bindDynCall__deps: ['$wbind'],
$wbindArray__deps: ['$wbind'],
#else
$wbind: function(funcPtr) {
// In -Os and -Oz builds, do not implement a JS side wasm table mirror for small
// code size, but directly access wasmTable, which is a bit slower.
return wasmTable.get(funcPtr);
},
#endif

// A helper that binds a wasm function into a form that can be called by passing all
// the parameters in an array, e.g. wbindArray(func)([param1, param2, ..., paramN]).
$wbindArray: function(funcPtr) {
var func = {{{wbind()}}}(funcPtr);
return func.length
? function(args) { return func.apply(null, args); }
: function() { return func(); }
},

// A helper that returns a function that can be used to invoke function pointers, i.e.
// getDynCaller('vi')(funcPtr, myInt);
$getDynCaller: function(sig, funcPtr) {
return {{{getDynCaller('sig')}}};
},

$bindDynCall: function(sig, funcPtr) {
// For int64 signatures, use the dynCall_sig dispatch mechanism.
if (sig.includes('j')) return function(args) {
return {{{getDynCaller('sig')}}}.apply(null, [funcPtr].concat(args));
}
// For non-int64 signatures, invoke via the wasm table.
var func = {{{wbind()}}}(funcPtr);
return func.length
? function(args) { return func.apply(null, args); }
: function() { return func(); }
},

$dynCall: function(sig, funcPtr, args) {
// For int64 signatures, use the dynCall_sig dispatch mechanism.
if (sig.includes('j')) {
return {{{getDynCaller('sig')}}}.apply(null, [funcPtr].concat(args));
}

// For non-int64 signatures, invoke via the wasm table.
return {{{wbind()}}}(funcPtr).apply(null, args);
}
});
1 change: 1 addition & 0 deletions src/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ var LibraryManager = {
// Core system libraries (always linked against)
var libraries = [
'library.js',
'library_dyncall.js',
'library_stack.js',
'library_formatString.js',
'library_math.js',
Expand Down
5 changes: 4 additions & 1 deletion src/postamble_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ function loadWasmModuleToWorkers() {
/*** ASM_MODULE_EXPORTS_DECLARES ***/
#endif

/*** STATIC_DYNCALL_SIG_FUNCTIONS ***/


#if MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION
// https://caniuse.com/#feat=wasm and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming
// Firefox 52 added Wasm support, but only Firefox 58 added instantiateStreaming.
Expand Down Expand Up @@ -222,7 +225,7 @@ WebAssembly.instantiate(Module['wasm'], imports).then(function(output) {
initRuntime(asm);
#if USE_PTHREADS && PTHREAD_POOL_SIZE
if (!ENVIRONMENT_IS_PTHREAD) loadWasmModuleToWorkers();
#if !PTHREAD_POOL_DELAY_LOAD
#if !PTHREAD_POOL_DELAY_LOAD
else
#endif
ready();
Expand Down
2 changes: 1 addition & 1 deletion src/settings_internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ var EXPECT_MAIN = 1;
// MODULARIZE, and returned from the factory function.
var EXPORT_READY_PROMISE = 1;

var USE_LEGACY_DYNCALLS = 0;
var USE_LEGACY_DYNCALLS = 1;

// struct_info that is either generated or cached
var STRUCT_INFO = '';
Expand Down
Loading

0 comments on commit df01a0d

Please sign in to comment.