dlfcn.h implementation and detailed example.

This commit is contained in:
JF Bastien 2016-02-01 08:00:42 -08:00
parent 13f9666606
commit acc1d6453b
2 changed files with 136 additions and 11 deletions

View File

@ -99,8 +99,9 @@ A WebAssembly module with un-met imports will throw. This can be handled, add
the missing function as a stub to FFI, and then load again (loop until success) the missing function as a stub to FFI, and then load again (loop until success)
but it's silly. If WebAssembly modules were loadable, imports inspectable, and but it's silly. If WebAssembly modules were loadable, imports inspectable, and
FFI object provided later then we'd be better off. We could implement very fancy FFI object provided later then we'd be better off. We could implement very fancy
lazy-loading, where the developer can handle load failures. We could also easily lazy-loading, where the developer can handle load failures. We can easily
implement `dlopen` / `dlsym` / `dlclose`. implement `dlopen` / `dlsym` / `dlclose` as demonstrated by the `<dlfcn.h>`
example below.
It would also be good to be able to specify compilation / execution separately. It would also be good to be able to specify compilation / execution separately.
@ -162,3 +163,79 @@ Developers are in control: they can do the equivalent of `-ffunction-sections`
and `-fdata-sections` but emit one `.wasm` file per section. This allows them to and `-fdata-sections` but emit one `.wasm` file per section. This allows them to
lazy-load and lazy-compile each function as needed, and even unload them when lazy-load and lazy-compile each function as needed, and even unload them when
the program doesn't need them anymore. the program doesn't need them anymore.
## `<dlfcn.h>` example
This example doesn't use `musl.wasm`, it currently only uses `wasm.js`. musl
could be used for this, it would be much cleaner (e.g. `dlerror` could work and
return `const char *` as it should), but it requires hooking up syscalls
properly.
Create `dlhello.c`:
```
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
typedef void (*world_type)();
void *handle = dlopen("dlworld.wasm", RTLD_NOW);
if (!handle) {
puts("dlopen failed:");
puts(dlerror());
abort();
}
dlerror();
world_type world = (world_type)dlsym(handle, "world");
const char *err = dlerror();
if (err) {
puts("dlsym failed:");
puts(err);
abort();
}
world();
dlclose(handle);
return 0;
}
```
And `dlworld.c`:
```
#include <stdio.h>
void world() { puts("World!"); }
```
Compile the programs:
```
clang -S -O2 --target=wasm32-unknown-unknown ./dlhello.c
clang -S -O2 --target=wasm32-unknown-unknown ./dlworld.c
s2wasm dlhello.s -o dlhello.wast
s2wasm dlworld.s -o dlworld.wast
sexpr-wasm dlhello.wast -o dlhello.wasm
sexpr-wasm dlworld.wast -o dlworld.wasm
```
Execute it:
```
d8 --expose-wasm musl/arch/wasm32/wasm.js -- dlhello.wasm
```
Note that this currently doesn't work because the `dlsym` implementation returns
the function from another module, and the implementation puts the functions in
different tables. We could fix this by:
* Forcing developers to use a function such as `dlcall` and provide handles for
the module and symbol.
* Map functions from all module into the same table.
* Map functions from other modules into the current one when `dlsym` is invoked,
e.g. adding new functions to the `_WASMEXP_` instance. This also requires
tracking `dlclose` properly.

View File

@ -25,6 +25,17 @@ var HEAP_SIZE_BYTES = 1 << 20;
var heap = new ArrayBuffer(HEAP_SIZE_BYTES); var heap = new ArrayBuffer(HEAP_SIZE_BYTES);
var heap_uint8 = new Uint8Array(heap); var heap_uint8 = new Uint8Array(heap);
// Heap access helpers.
function charFromHeap(ptr) { return String.fromCharCode(heap_uint8[ptr]); }
function stringFromHeap(ptr) {
var str = '';
for (var i = ptr; heap_uint8[i] != 0; ++i)
str += charFromHeap(i);
return str;
}
// Exceptions.
function TerminateWasmException(value) { function TerminateWasmException(value) {
this.value = value; this.value = value;
this.message = 'Terminating WebAssembly'; this.message = 'Terminating WebAssembly';
@ -319,11 +330,7 @@ var stdio = (function() {
stdout_buf += String.fromCharCode(character); stdout_buf += String.fromCharCode(character);
return character; return character;
}, },
puts: function(str) { puts: function(str) { stdout_buf += stringFromHeap(str) + '\n'; },
for (var i = 0; heap_uint8[str + i] != 0; ++i)
stdout_buf += String.fromCharCode(heap_uint8[str + i]);
stdout_buf += '\n';
},
ungetc: NYI('ungetc'), ungetc: NYI('ungetc'),
// Direct input/output. // Direct input/output.
@ -539,6 +546,10 @@ var unix = (function() {
var OPEN_MAX = 256; var OPEN_MAX = 256;
var open_files = new Uint8Array(OPEN_MAX); var open_files = new Uint8Array(OPEN_MAX);
var dlfcn = {};
var dlfcn_handle_to_filename = {};
var dlfcn_max_handle = 0;
return { return {
// <dlfcn.h> constants. // <dlfcn.h> constants.
RTLD_LAZY: 1, RTLD_LAZY: 1,
@ -552,10 +563,47 @@ var unix = (function() {
RTLD_DI_LINKMAP: 2, RTLD_DI_LINKMAP: 2,
// <dlfcn.h> // <dlfcn.h>
dlclose: NYI('dlclose'), dlclose: function(handle) {
dlerror: NYI('dlerror'), var filename = dlfcn_handle_to_filename[handle];
dlopen: NYI('dlopen'), if (!filename) NYI('dlclose of invalid handle')();
dlsym: NYI('dlsym'), dlfcn[filename].refcount -= 1;
if (dlfcn[filename].refcount == 0)
dlfcn[filename] = undefined;
return 0; },
dlerror: function() {
// TODO: implement error handling.
return 0; },
dlopen: function(filename, flags) {
if (!filename) NYI('dlopen(NULL, ...);')();
var fs = stringFromHeap(filename);
if (dlfcn[fs]) {
dlfcn[fs].refcount += 1;
return dlfcn[fs].handle;
}
if (flags & unix.RTLD_LAZY) NYI('dlopen with flag RTLD_LAZY')();
if (~flags & unix.RTLD_NOW) NYI('dlopen without flag RTDL_NOW')();
if (flags & unix.RTLD_NOLOAD) NYI('dlopen with flag RTLD_NOLOAD')();
if (flags & unix.RTLD_NODELETE) NYI('dlopen with flag RTLD_NODELETE')();
if (flags & unix.RTLD_GLOBAL) NYI('dlopen with flag RTLD_GLOBAL')();
// TODO: other flags.
var handle = ++dlfcn_max_handle;
dlfcn[fs] = {
refcount: 0,
module: load_wasm(fs),
handle: handle
};
dlfcn_handle_to_filename[handle] = fs;
return handle; },
dlsym: function(handle, symbol) {
var filename = dlfcn_handle_to_filename[handle];
if (!filename) NYI('dlsym of invalid handle')();
if (!symbol) NYI('dlsym of NULL symbol')();
var ss = stringFromHeap(symbol);
// TODO: error handling when module doesn't contain symbol.
for (var m in dlfcn[filename].module)
if (m == ss)
return m;
NYI('dlsym with symbol not found in handle')(); },
dladdr: NYI('dladdr'), dladdr: NYI('dladdr'),
dlinfo: NYI('dlinfo'), dlinfo: NYI('dlinfo'),