diff --git a/emcc.py b/emcc.py index bacecdbfcdde9..4e0ef61b26fa4 100755 --- a/emcc.py +++ b/emcc.py @@ -1282,6 +1282,10 @@ 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) + if shared.Settings.WASM_DYNCALLS: + 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: @@ -1707,7 +1711,7 @@ def check_memory_setting(setting): if shared.Settings.USE_PTHREADS or shared.Settings.RELOCATABLE or shared.Settings.ASYNCIFY_LAZY_LOAD_CODE or shared.Settings.WASM2JS: shared.Settings.IMPORTED_MEMORY = 1 - if shared.Settings.WASM_BIGINT: + if shared.Settings.WASM_BIGINT and not shared.Settings.WASM_DYNCALLS: shared.Settings.LEGALIZE_JS_FFI = 0 shared.Settings.GENERATE_SOURCE_MAP = shared.Settings.DEBUG_LEVEL >= 4 and not shared.Settings.SINGLE_FILE @@ -2420,11 +2424,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'): diff --git a/emscripten.py b/emscripten.py index 739307014a2e6..9fc0096f5c1be 100644 --- a/emscripten.py +++ b/emscripten.py @@ -37,6 +37,23 @@ 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 + '"]') + 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 @@ -44,7 +61,22 @@ def compute_minimal_runtime_initializer_and_exports(post, exports, receiving): 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.WASM_DYNCALLS: + 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) + ';') +# TODO: Check codegen again +# 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) @@ -399,9 +431,9 @@ def finalize_wasm(infile, outfile, memfile, DEBUG): # (which matches what llvm+lld has given us) if shared.Settings.DEBUG_LEVEL >= 2 or shared.Settings.ASYNCIFY_ADD or shared.Settings.ASYNCIFY_ADVISE or shared.Settings.ASYNCIFY_ONLY or shared.Settings.ASYNCIFY_REMOVE or shared.Settings.EMIT_SYMBOL_MAP or shared.Settings.PROFILING_FUNCS: args.append('-g') - if shared.Settings.WASM_BIGINT: + if shared.Settings.WASM_BIGINT and not shared.Settings.WASM_DYNCALLS: # TODO: This may be troublematic? args.append('--bigint') - if shared.Settings.USE_LEGACY_DYNCALLS: + if shared.Settings.WASM_DYNCALLS: # we need to add all dyncalls to the wasm modify_wasm = True else: @@ -698,6 +730,10 @@ def create_receiving(exports): exports_that_are_not_initializers = [x for x in exports if x != WASM_INIT_FUNC] + # If we are not building with dynCall() support, filter out all the dynCall_ exports. + if not shared.Settings.WASM_DYNCALLS: + exports_that_are_not_initializers = [x for x in exports_that_are_not_initializers if not x.startswith('dynCall_')] + receiving = [] # with WASM_ASYNC_COMPILATION that asm object may not exist at this point in time @@ -710,7 +746,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.WASM_DYNCALLS 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. diff --git a/src/library.js b/src/library.js index d5e9d6280e7bc..50d082463f0dc 100644 --- a/src/library.js +++ b/src/library.js @@ -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(); diff --git a/src/library_dyncall.js b/src/library_dyncall.js new file mode 100644 index 0000000000000..f1964a879ab5a --- /dev/null +++ b/src/library_dyncall.js @@ -0,0 +1,75 @@ +mergeInto(LibraryManager.library, { +#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; + }, + +#if WASM_DYNCALLS + $dynCall__deps: ['$wbind'], + $bindDynCall__deps: ['$wbind'], +#endif + $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(); } + }, + +#if WASM_DYNCALLS + // 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(); } + }, + +#if SHRINK_LEVEL + $dynCall__deps: ['$bindDynCall'], +#endif + $dynCall: function(sig, funcPtr, args) { +#if SHRINK_LEVEL + return bindDynCall(sig, funcPtr)(args); +#else + // 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); +#endif + }, +#endif +}); diff --git a/src/modules.js b/src/modules.js index 7f81ea91f7593..9bda4688f7578 100644 --- a/src/modules.js +++ b/src/modules.js @@ -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', diff --git a/src/parseTools.js b/src/parseTools.js index 59419d86545b3..18fa926d19cf8 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1253,3 +1253,11 @@ function makeMalloc(source, param) { } return `abort('malloc was not included, but is needed in ${source}. Adding "_malloc" to EXPORTED_FUNCTIONS should fix that. This may be a bug in the compiler, please file an issue.');` } + +function wbind() { + return SHRINK_LEVEL ? 'wasmTable.get' : 'wbind'; +} + +function getDynCaller(sig) { + return MINIMAL_RUNTIME ? `dynCalls[${sig}]` : `Module["dynCall_"+${sig}]`; +} diff --git a/src/postamble_minimal.js b/src/postamble_minimal.js index 8d325784dea3c..193d98ae7e9b9 100644 --- a/src/postamble_minimal.js +++ b/src/postamble_minimal.js @@ -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. @@ -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(); diff --git a/src/settings.js b/src/settings.js index 3e0e97fe13847..6e3c054e60b54 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1145,6 +1145,10 @@ var BINARYEN_EXTRA_PASSES = ""; // (This option was formerly called BINARYEN_ASYNC_COMPILATION) var WASM_ASYNC_COMPILATION = 1; +// If true, enables emitting dynCall() and dynCall_sig() based function pointer +// invokers to call function pointers from JS to Wasm. +var WASM_DYNCALLS = 1; + // WebAssembly integration with JavaScript BigInt. When enabled we don't need // to legalize i64s into pairs of i32s, as the wasm VM will use a BigInt where // an i64 is used. diff --git a/src/settings_internal.js b/src/settings_internal.js index 8f792db17521b..f709b02277741 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -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 = ''; diff --git a/tests/bench_dyncall.c b/tests/bench_dyncall.c new file mode 100644 index 0000000000000..03c6163ae3af6 --- /dev/null +++ b/tests/bench_dyncall.c @@ -0,0 +1,381 @@ +#include +#include + +void v() {} +int iffddjj(float f, float g, double d, double e, int64_t i, int64_t j) { return 1;} + +typedef void (*vptr)(); +typedef int (*iffddjjptr)(float f, float g, double d, double e, int64_t i, int64_t j); + +EM_JS(double, bench_static_direct_call_v, (vptr func), { + testStarted('bench_static_direct_call_v'); + var numRuns = 11; + var results = []; + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + dynCall_v(func); + dynCall_v(func); + dynCall_v(func); + dynCall_v(func); + dynCall_v(func); + dynCall_v(func); + dynCall_v(func); + dynCall_v(func); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_static_direct_call_v', results); + } + }, 1); +}); + +EM_JS(double, bench_static_bound_call_v, (vptr func), { + testStarted('bench_static_bound_call_v'); + var numRuns = 11; + var results = []; + var boundFunc = bindDynCall('v', func); + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_static_bound_call_v', results); + } + }, 1); +}); + +EM_JS(double, bench_dynamic_direct_call_v, (vptr func), { + testStarted('bench_dynamic_direct_call_v'); + var numRuns = 11; + var results = []; + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + dynCall('v', func); + dynCall('v', func); + dynCall('v', func); + dynCall('v', func); + dynCall('v', func); + dynCall('v', func); + dynCall('v', func); + dynCall('v', func); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_dynamic_direct_call_v', results); + } + }, 1); +}); + +EM_JS(double, bench_dynamic_bound_call_v, (vptr func), { + testStarted('bench_dynamic_bound_call_v'); + var numRuns = 11; + var results = []; + var boundFunc = bindDynCallArray('v', func); + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + boundFunc(func); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_dynamic_bound_call_v', results); + } + }, 1); +}); + +EM_JS(double, bench_static_direct_call_iffddjj, (iffddjjptr func), { + testStarted('bench_static_direct_call_iffddjj'); + var numRuns = 11; + var results = []; + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + dynCall_iffddjj(func, 42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_static_direct_call_iffddjj', results); + } + }, 1); +}); + +EM_JS(double, bench_static_bound_call_iffddjj, (iffddjjptr func), { + testStarted('bench_static_bound_call_iffddjj'); + var numRuns = 11; + var results = []; + var boundFunc = bindDynCall('iffddjj', func); + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_static_bound_call_iffddjj', results); + } + }, 1); +}); + +EM_JS(double, bench_dynamic_direct_call_iffddjj, (iffddjjptr func), { + testStarted('bench_dynamic_direct_call_iffddjj'); + var numRuns = 11; + var results = []; + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + dynCall('iffddjj', func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_dynamic_direct_call_iffddjj', results); + } + }, 1); +}); + +EM_JS(double, bench_dynamic_bound_call_iffddjj, (iffddjjptr func), { + testStarted('bench_dynamic_bound_call_iffddjj'); + var numRuns = 11; + var results = []; + var boundFunc = bindDynCallArray('iffddjj', func); + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_dynamic_bound_call_iffddjj', results); + } + }, 1); +}); + + + + + + + + + + + + + + + + + + + + + + + + +EM_JS(double, bench_wbind_static_direct_call_iffddjj, (iffddjjptr func), { + testStarted('bench_wbind_static_direct_call_iffddjj'); + var numRuns = 11; + var results = []; + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + wbind(func)(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_wbind_static_direct_call_iffddjj', results); + } + }, 1); +}); + +EM_JS(double, bench_wbind_static_bound_call_iffddjj, (iffddjjptr func), { + testStarted('bench_wbind_static_bound_call_iffddjj'); + var numRuns = 11; + var results = []; + var boundFunc = wbind(func); + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + boundFunc(42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_wbind_static_bound_call_iffddjj', results); + } + }, 1); +}); + +EM_JS(double, bench_wbind_dynamic_direct_call_iffddjj, (iffddjjptr func), { + testStarted('bench_wbind_dynamic_direct_call_iffddjj'); + var numRuns = 11; + var results = []; + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + wbindArray(func, [42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_wbind_dynamic_direct_call_iffddjj', results); + } + }, 1); +}); + +EM_JS(double, bench_wbind_dynamic_bound_call_iffddjj, (iffddjjptr func), { + testStarted('bench_wbind_dynamic_bound_call_iffddjj'); + var numRuns = 11; + var results = []; + var boundFunc = wbindArray(func); + var run = setInterval(function() { + var t0 = tick(); + for(var i = 0; i < 100000; ++i) { + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + boundFunc([42.5, 16.5, 13.3, -12.1, 2, 2, 3, 3]); + } + var t1 = tick(); + if (numRuns-- >= 0) { + results.push(t1 - t0); + } else { + clearInterval(run); + testFinished('bench_wbind_dynamic_bound_call_iffddjj', results); + } + }, 1); +}); + + + + + +int main() { + EM_ASM({ + var g = ENVIRONMENT_IS_NODE ? global : window; + g.node_tick = function() { var t = process.hrtime(); return t[0]*1e3 + t[1]/1e6; }; + g.tick = function() { return ENVIRONMENT_IS_NODE ? node_tick() : performance.now(); }; + g.avg = function(arr) { var sum = 0; arr.forEach((val) => { sum += val; }); return sum / arr.length; }; + g.testsPending = {}; + g.testStarted = function(name) { g.testsPending[name] = true; }; + g.testFinished = function(name, results) { + results.splice(0, 2); // Remove first two runs to remove any results from JIT warmup + var numRuns = results.length; + var result = avg(results); + console.log(name + ': ' + result + ' msecs (averaged over ' + numRuns + ' runs)'); + delete g.testsPending[name]; + if (Object.keys(testsPending).length == 0) exit(0); + } + }); + bench_dynamic_direct_call_v(v); + bench_dynamic_bound_call_v(v); + bench_static_direct_call_v(v); + bench_static_bound_call_v(v); + + bench_dynamic_direct_call_iffddjj(iffddjj); + bench_dynamic_bound_call_iffddjj(iffddjj); + bench_static_direct_call_iffddjj(iffddjj); + bench_static_bound_call_iffddjj(iffddjj); +} diff --git a/tests/core/test_dyncalls.c b/tests/core/test_dyncalls.c new file mode 100644 index 0000000000000..fb8de6060a93d --- /dev/null +++ b/tests/core/test_dyncalls.c @@ -0,0 +1,24 @@ +#include +#include +#include + +void vijdf(int i, int64_t j, double d, float f) +{ + EM_ASM(console.log('vijdf: i='+$0+',jlo='+$1+',jhi='+$2+',d='+$3+',f='+$4), i, (uint32_t)j, (uint32_t)(((uint64_t)j) >> 32), d, f); +} + +int iii(int i, int j) +{ + EM_ASM(console.log('iii: i='+$0+',j='+$1), i, j); + return 42; +} + + +void test_dyncalls_vijdf(void(*)(int, int64_t, double, float)); +void test_dyncalls_iii(int(*)(int, int)); + +int main() +{ + test_dyncalls_vijdf(&vijdf); + test_dyncalls_iii(&iii); +} diff --git a/tests/core/test_dyncalls.js b/tests/core/test_dyncalls.js new file mode 100644 index 0000000000000..d1d65ee8322b5 --- /dev/null +++ b/tests/core/test_dyncalls.js @@ -0,0 +1,86 @@ +mergeInto(LibraryManager.library, { + test_dyncalls_vijdf__deps: [ +#if WASM_DYNCALLS + '$getDynCaller', '$bindDynCall', +#endif + '$wbind', '$wbindArray'], + test_dyncalls_vijdf: function(funcPtr) { +#if WASM_DYNCALLS + // 1. Directly access a function pointer via a static signature (32-bit ABI) + // (this is the fastest way to call a function pointer when the signature is statically known in WASM_BIGINT==0 builds) + dynCall_vijdf(funcPtr, 1, /*lo=*/2, /*hi=*/3, 4, 5); // Available only in WASM_BIGINT != 2 builds + + // 2. Access a function pointer using the convenience/legacy 'dynCall' function (32-bit ABI) + // (this form should never be used, it is suboptimal for performance, but provided for legacy compatibility) + dynCall('vijdf', funcPtr, [2, /*lo=*/3, /*hi=*/4, 5, 6]); // Available only in WASM_BIGINT != 2 builds + + // 3. Obtain a dynamic function caller to a given signature and call it with .apply() (32-bit ABI) + // (this form should be used when dealing with a dynamic input signature problem with varying length of function args, and funcPtr + args are fused together in one array) + getDynCaller('vijdf').apply(null, [funcPtr, 3, /*lo=*/4, /*hi=*/5, 6, 7]); // Available only in WASM_BIGINT != 2 builds + + // 4. Obtain a function wrapper to given function pointer and call it by submitting args in an array (32-bit ABI) + // (this form should be used when dealing with a dynamic input signature problem with varying length of function args, but funcPtr and args params are dealt with separately) + bindDynCall('vijdf', funcPtr)([4, /*lo=*/5, /*hi=*/6, 7, 8]); // Available only in WASM_BIGINT != 2 builds +#else + // Appease test runner and output the same text if not building with WASM_DYNCALLS enabled. + wbind(funcPtr)(1, BigInt(2) | (BigInt(3) << BigInt(32)), 4, 5); + wbind(funcPtr)(2, BigInt(3) | (BigInt(4) << BigInt(32)), 5, 6); + wbind(funcPtr)(3, BigInt(4) | (BigInt(5) << BigInt(32)), 6, 7); + wbind(funcPtr)(4, BigInt(5) | (BigInt(6) << BigInt(32)), 7, 8); +#endif + +#if WASM_BIGINT + // 5. Directly access a function pointer via a static signature (64-bit ABI) + // (this is the fastest way to call a function pointer when the signature is statically known in WASM_BIGINT>0 builds) + wbind(funcPtr)(5, BigInt(6) | (BigInt(7) << BigInt(32)), 8, 9); // Available in all builds, but in WASM_BIGINT==0 builds cannot be used to call int64 signatures + + // 6. Obtain an array form access to the specified signature. (64-bit ABI) + // (this form should be used when dealing with a dynamic input signature problem with varying length of function args) + wbindArray(funcPtr)([6, BigInt(7) | (BigInt(8) << BigInt(32)), 9, 10]); // Available in all builds, but in WASM_BIGINT==0 builds cannot be used to call int64 signatures +#else + // Appease test runner and output the same text if not building with WASM_BIGINT enabled. + dynCall_vijdf(funcPtr, 5, /*lo=*/6, /*hi=*/7, 8, 9); + dynCall_vijdf(funcPtr, 6, /*lo=*/7, /*hi=*/8, 9, 10); +#endif + }, + + test_dyncalls_iii: function(funcPtr) { +#if WASM_DYNCALLS + // 1. Directly access a function pointer via a static signature (32-bit ABI) + // (this is the fastest way to call a function pointer when the signature is statically known in WASM_BIGINT==0 builds) + var ret = dynCall_iii(funcPtr, 1, 2); // Available only in WASM_BIGINT != 2 builds + console.log('iii returned ' + ret); + + // 2. Access a function pointer using the convenience/legacy 'dynCall' function (32-bit ABI) + // (this form should never be used, it is suboptimal for performance, but provided for legacy compatibility) + var ret = dynCall('iii', funcPtr, [2, 3]); // Available only in WASM_BIGINT != 2 builds + console.log('iii returned ' + ret); + + // 3. Obtain a dynamic function caller to a given signature and call it with .apply() (32-bit ABI) + // (this form should be used when dealing with a dynamic input signature problem with varying length of function args, and funcPtr + args are fused together in one array) + var ret = getDynCaller('iii').apply(null, [funcPtr, 3, 4]); // Available only in WASM_BIGINT != 2 builds + console.log('iii returned ' + ret); + + // 4. Obtain a function wrapper to given function pointer and call it by submitting args in an array (32-bit ABI) + // (this form should be used when dealing with a dynamic input signature problem with varying length of function args, but funcPtr and args params are dealt with separately) + var ret = bindDynCall('iii', funcPtr)([4, 5]); // Available only in WASM_BIGINT != 2 builds + console.log('iii returned ' + ret); +#else + // Appease test runner and output the same text if not building with WASM_DYNCALLS enabled. + console.log('iii returned ' + wbind(funcPtr)(1, 2)); + console.log('iii returned ' + wbind(funcPtr)(2, 3)); + console.log('iii returned ' + wbind(funcPtr)(3, 4)); + console.log('iii returned ' + wbind(funcPtr)(4, 5)); +#endif + + // 5. Directly access a function pointer via a static signature (64-bit ABI) + // (this is the fastest way to call a function pointer when the signature is statically known in WASM_BIGINT>0 builds) + var ret = wbind(funcPtr)(5, 6); // Available in all builds, but in WASM_BIGINT==0 builds cannot be used to call int64 signatures + console.log('iii returned ' + ret); + + // 6. Obtain an array form access to the specified signature. (64-bit ABI) + // (this form should be used when dealing with a dynamic input signature problem with varying length of function args) + var ret = wbindArray(funcPtr)([6, 7]); // Available in all builds, but in WASM_BIGINT==0 builds cannot be used to call int64 signatures + console.log('iii returned ' + ret); + } +}); diff --git a/tests/core/test_dyncalls.out b/tests/core/test_dyncalls.out new file mode 100644 index 0000000000000..b34cff768e984 --- /dev/null +++ b/tests/core/test_dyncalls.out @@ -0,0 +1,18 @@ +vijdf: i=1,jlo=2,jhi=3,d=4,f=5 +vijdf: i=2,jlo=3,jhi=4,d=5,f=6 +vijdf: i=3,jlo=4,jhi=5,d=6,f=7 +vijdf: i=4,jlo=5,jhi=6,d=7,f=8 +vijdf: i=5,jlo=6,jhi=7,d=8,f=9 +vijdf: i=6,jlo=7,jhi=8,d=9,f=10 +iii: i=1,j=2 +iii returned 42 +iii: i=2,j=3 +iii returned 42 +iii: i=3,j=4 +iii returned 42 +iii: i=4,j=5 +iii returned 42 +iii: i=5,j=6 +iii returned 42 +iii: i=6,j=7 +iii returned 42 \ No newline at end of file diff --git a/tests/test_core.py b/tests/test_core.py index 2eca4a3340086..7d42eb710ba9d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8270,6 +8270,18 @@ def test_gl_main_module(self): self.set_setting('MAIN_MODULE') self.do_runf(path_from_root('tests', 'core', 'test_gl_get_proc_address.c')) + @also_with_wasm_bigint + def test_dyncalls(self): + self.emcc_args += ['--js-library', path_from_root('tests', 'core', 'test_dyncalls.js')] + self.do_run_in_out_file_test('tests', 'core', 'test_dyncalls.c') + + @no_asan('asan does not yet work in MINIMAL_RUNTIME') + @also_with_wasm_bigint + def test_dyncalls_minimal_runtime(self): + self.set_setting('MINIMAL_RUNTIME') + self.emcc_args += ['--js-library', path_from_root('tests', 'core', 'test_dyncalls.js')] + self.do_run_in_out_file_test('tests', 'core', 'test_dyncalls.c') + # Generate tests for everything def make_run(name, emcc_args, settings=None, env=None):