diff --git a/emcc.py b/emcc.py index bacecdbfcdde..6c445f9e1820 100755 --- a/emcc.py +++ b/emcc.py @@ -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: @@ -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'): diff --git a/emscripten.py b/emscripten.py index 739307014a2e..1fc10a512b60 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 + '"]') + 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 @@ -44,7 +61,21 @@ 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.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) @@ -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: @@ -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 = [] @@ -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. diff --git a/src/library.js b/src/library.js index d5e9d6280e7b..50d082463f0d 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 000000000000..4889b8d103f0 --- /dev/null +++ b/src/library_dyncall.js @@ -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); + } +}); diff --git a/src/modules.js b/src/modules.js index 7f81ea91f759..9bda4688f757 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/postamble_minimal.js b/src/postamble_minimal.js index 8d325784dea3..193d98ae7e9b 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_internal.js b/src/settings_internal.js index 8f792db17521..f709b0227774 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 000000000000..03c6163ae3af --- /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/test_core.py b/tests/test_core.py index 2eca4a334008..88ae8139e3e4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -8270,6 +8270,10 @@ 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')) + def test_dyncalls(self): + self.emcc_args += ['--js-library', path_from_root('tests', 'test_dyncalls.js')] + self.do_run_in_out_file_test('tests', 'test_dyncalls.c') + # Generate tests for everything def make_run(name, emcc_args, settings=None, env=None): diff --git a/tests/test_dyncalls.c b/tests/test_dyncalls.c new file mode 100644 index 000000000000..fb8de6060a93 --- /dev/null +++ b/tests/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/test_dyncalls.js b/tests/test_dyncalls.js new file mode 100644 index 000000000000..6c0ae3fa9199 --- /dev/null +++ b/tests/test_dyncalls.js @@ -0,0 +1,66 @@ +mergeInto(LibraryManager.library, { + test_dyncalls_vijdf__deps: ['$getDynCaller', '$bindDynCall', '$wbind', '$wbindArray'], + test_dyncalls_vijdf: function(funcPtr) { +#if WASM_BIGINT != 2 + // 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, [3, /*lo=*/4, /*hi=*/5, 6, 7]); // 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, 4, /*lo=*/5, /*hi=*/6, 7, 8]); // 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 +#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)(2, BigInt(3) | (BigInt(4) << BigInt(32)), 5, 6); // 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)([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 +#endif + }, + + test_dyncalls_iii: function(funcPtr) { +#if WASM_BIGINT != 2 + // 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, [3, 4]); // 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, 4, 5]); // 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)([5, 6]); // Available only in WASM_BIGINT != 2 builds + console.log('iii returned ' + ret); +#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)(2, 3); // 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); + } +});