Is there any way to get access to function pointers living inside a WebAssembly module?
For example, given the following "module" piled to WebAssembly:
extern void set_callback(void (*callback)(void *arg), void *arg);
static void callback(void *arg)
{
/* ... */
}
int main() {
set_callback(&callback, 0);
return 0;
}
Can an implementation of do_callback
in JavaScript invoke the callback without having to rely on an intermediary C function export to do the actual function call?
var instance = new WebAssembly.Instance(module, {
memory: /* ... */
env: {
set_callback: function set_callback(callbackptr, argptr) {
// We only got the pointer, is there any
},
},
});
By intermediary function export, I mean that I could add an internal function with public visibility.
do_callback(void (*callback)(void *arg), void *arg)
{
callback();
}
Then the JavaScript set_callback
function can call the function pointer via the delegate do_callback
function.
function set_callback(callbackptr, argptr) {
instance.exports.do_callback(callbackptr, argptr);
}
But, it's preferable to do this without having to go through that explicit indirection, is it possible, with function tables maybe?
Is there any way to get access to function pointers living inside a WebAssembly module?
For example, given the following "module" piled to WebAssembly:
extern void set_callback(void (*callback)(void *arg), void *arg);
static void callback(void *arg)
{
/* ... */
}
int main() {
set_callback(&callback, 0);
return 0;
}
Can an implementation of do_callback
in JavaScript invoke the callback without having to rely on an intermediary C function export to do the actual function call?
var instance = new WebAssembly.Instance(module, {
memory: /* ... */
env: {
set_callback: function set_callback(callbackptr, argptr) {
// We only got the pointer, is there any
},
},
});
By intermediary function export, I mean that I could add an internal function with public visibility.
do_callback(void (*callback)(void *arg), void *arg)
{
callback();
}
Then the JavaScript set_callback
function can call the function pointer via the delegate do_callback
function.
function set_callback(callbackptr, argptr) {
instance.exports.do_callback(callbackptr, argptr);
}
But, it's preferable to do this without having to go through that explicit indirection, is it possible, with function tables maybe?
Share Improve this question edited Jul 31, 2017 at 23:40 Casper Beyer asked Jul 29, 2017 at 9:37 Casper BeyerCasper Beyer 2,3012 gold badges23 silver badges36 bronze badges3 Answers
Reset to default 4 +250You can invoke function pointers from Javascript.
Function pointers are stored in a Table. When a function pointer is passed to Javascript you are receiving the integer index into the table for that function pointer. Pass that index to Table.prototype.get()
and you can call the function.
...
set_callback: function set_callback(callbackptr, argptr) {
tbl.get(callbackptr)(argptr);
},
...
You can read more about this on this MDN page under the Tables section: https://developer.mozilla/en-US/docs/WebAssembly/Using_the_JavaScript_API#Tables
Edit: Here is the minimal example I used to test this.
The first file is fptr.c
piled with emcc fptr.c -Os -s WASM=1 -s SIDE_MODULE=1 -o fptr.wasm
typedef int (*fptr_type)(void);
extern void pass_fptr_to_js(fptr_type fptr);
static int callback_0(void)
{
return 26;
}
static int callback_1(void)
{
return 42;
}
void run_test()
{
pass_fptr_to_js(callback_0);
pass_fptr_to_js(callback_1);
}
And here is fptr.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebAssembly Experiment</title>
</head>
<body>
<h3>Check the console.</h3>
<script type="text/javascript">
fetch('fptr.wasm').then(function(response) {
response.arrayBuffer().then(function(buffer) {
WebAssembly.pile(buffer).then(function(module) {
var imports = {};
imports.env = {};
imports.env.memoryBase = 0;
imports.env.memory = new WebAssembly.Memory({ initial: 256 });
imports.env.tableBase = 0;
imports.env.table = new WebAssembly.Table({ initial: 4, element: 'anyfunc' });
imports.env["abort"] = function() {
console.error("ABORT");
};
imports.env["_pass_fptr_to_js"] = function(fptr) {
console.log("table index: " + fptr + ", return value: " + imports.env.table.get(fptr)());
};
WebAssembly.instantiate(module, imports).then(function(instance) {
instance.exports["__post_instantiate"]();
instance.exports["_run_test"]();
});
});
});
});
</script>
</body>
</html>
The issue back then was that Clang basically did not implement call_indirect and did not actually populate the function table with anything during code generation.
Has been resolved in the current version of LLVM.
For some reason, the table approach is not working for me, so I did in the opposite direction: I implemented a function table in my source language (D).
This way, I can make it work by passing handles to javascript:
__gshared ubyte* function(ubyte* args)[] _annonymousFunctionTable;
extern(C) size_t _getFuncAddress(ubyte* fn);
export extern(C) ubyte* __callDFunction(size_t addr, ubyte* args)
{
return _annonymousFunctionTable[addr](args);
}
I don't call directly _getFuncAddress here because there is another way of sending functions I did for D for argument count and type checking.
This way, I can call any function I would like with how many arguments which I want without depending on the wasm implementation.