Description
As of today JS functions can be directly supplied as imports, but they cannot be directly added to table. table.set
only accepts native WebAssembly functions. As of today, there is no way to convert JS functions to WebAssembly functions. An API to creating WebAssembly functions is proposed in https://github.com/WebAssembly/js-types, but that requires the caller to know function signature ahead of time.
Rather than converting JS functions to WebAssembly functions for the purposes of adding them to tables, could we not simply allow JS functions to be added directly?
When supplied as imports, JS functions have universal polymorphic behaviour in that one can supply any JS function to any import, and indeed to all imports. No signature checking is done, and the provider of the function doesn't need to know the signature ahead of time. The number of arguments doesn't even need to match. This is a nice property to have in dynamic languages and in particular is makes lazy binding and dynamic linking easier.
For example, this property means we can use Proxy object or resolve imports without being aware of the signature of an import:
https://github.com/emscripten-core/emscripten/blob/main/src/library_dylink.js#L470
A simplified version of this code allows use to use single function to resolve symbols dynamically at runtime:
function makeHandler(name) {
return function() {
return resolveSymbol(name).apply(null, arguments);
};
}
While these universal (I guess you could call them variadic?) functions work fine as function imports, they are not permitted by table.set
. This means that when we do dyanmic linking in emscripten today its easy to do lazy binding function imports, but lazy binding of function address imports is not possible, at least not without also knowing the signature of the function. I can't take the result of the makeHandler
function at pass it to table.set
.
To work around this limitation we used are currently considering adding adding extra signature information in a custom section so that table addresses can be dynamically assigned before all modules in the graph are loaded.
Is there any fundamental reason why we can't just do table.set(myHandler)
and have that handler universally usable by call_indirect
.. it might mean that the call_indirect could be slightly more efficient since the signature check could be skipped (since JS functions can't/don't do signature checks IIUC).