Skip to content

Commit

Permalink
Update API and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
juj committed Jan 20, 2021
1 parent df01a0d commit f9059f1
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 22 deletions.
5 changes: 3 additions & 2 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,8 @@ def default_setting(name, new_default):
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.WASM_DYNCALLS:
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$dynCall']

if shared.Settings.MAIN_MODULE:
assert not shared.Settings.SIDE_MODULE
Expand Down Expand Up @@ -1710,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
Expand Down
13 changes: 8 additions & 5 deletions emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def compute_minimal_runtime_initializer_and_exports(post, exports, receiving):

static_dyncall_sig_functions = ''

if shared.Settings.USE_LEGACY_DYNCALLS or not shared.Settings.WASM_BIGINT:
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:
Expand All @@ -75,7 +75,8 @@ def compute_minimal_runtime_initializer_and_exports(post, exports, receiving):
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)
# 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)
Expand Down Expand Up @@ -430,7 +431,7 @@ 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 True:##shared.Settings.USE_LEGACY_DYNCALLS:
# we need to add all dyncalls to the wasm
Expand Down Expand Up @@ -728,7 +729,9 @@ 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:

# 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 = []
Expand All @@ -745,7 +748,7 @@ def create_receiving(exports):
# _main = asm["_main"];
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 ''
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:
Expand Down
15 changes: 13 additions & 2 deletions src/library_dyncall.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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; })(); }}}
{{{ (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
Expand All @@ -17,8 +17,10 @@ mergeInto(LibraryManager.library, {
return func;
},

#if WASM_DYNCALLS
$dynCall__deps: ['$wbind'],
$bindDynCall__deps: ['$wbind'],
#endif
$wbindArray__deps: ['$wbind'],
#else
$wbind: function(funcPtr) {
Expand All @@ -37,6 +39,7 @@ mergeInto(LibraryManager.library, {
: 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) {
Expand All @@ -55,13 +58,21 @@ mergeInto(LibraryManager.library, {
: 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
});
4 changes: 4 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
File renamed without changes.
42 changes: 31 additions & 11 deletions tests/test_dyncalls.js → tests/core/test_dyncalls.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,81 @@
mergeInto(LibraryManager.library, {
test_dyncalls_vijdf__deps: ['$getDynCaller', '$bindDynCall', '$wbind', '$wbindArray'],
test_dyncalls_vijdf__deps: [
#if WASM_DYNCALLS
'$getDynCaller', '$bindDynCall',
#endif
'$wbind', '$wbindArray'],
test_dyncalls_vijdf: function(funcPtr) {
#if WASM_BIGINT != 2
#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, [3, /*lo=*/4, /*hi=*/5, 6, 7]); // Available only in WASM_BIGINT != 2 builds
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, 4, /*lo=*/5, /*hi=*/6, 7, 8]); // Available only in WASM_BIGINT != 2 builds
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)(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
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)([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
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_BIGINT != 2
#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, [3, 4]); // Available only in WASM_BIGINT != 2 builds
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, 4, 5]); // Available only in WASM_BIGINT != 2 builds
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)([5, 6]); // Available only in WASM_BIGINT != 2 builds
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)(2, 3); // Available in all builds, but in WASM_BIGINT==0 builds cannot be used to call int64 signatures
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)
Expand Down
18 changes: 18 additions & 0 deletions tests/core/test_dyncalls.out
Original file line number Diff line number Diff line change
@@ -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
12 changes: 10 additions & 2 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8270,9 +8270,17 @@ 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', 'test_dyncalls.js')]
self.do_run_in_out_file_test('tests', 'test_dyncalls.c')
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
Expand Down

0 comments on commit f9059f1

Please sign in to comment.