2016-02-01 11:25:19 -08:00
|
|
|
# WebAssembly experiment for musl libc with limited dynamic linking
|
2016-01-31 06:55:45 -08:00
|
|
|
|
2016-02-01 04:46:05 -08:00
|
|
|
A [musl experiment][].
|
2016-01-31 12:39:00 -08:00
|
|
|
|
2016-02-01 04:46:05 -08:00
|
|
|
[musl experiment]: https://github.com/WebAssembly/musl/blob/landing-branch/README.md
|
|
|
|
|
2016-02-01 11:14:27 -08:00
|
|
|
The goal of this prototype was to get a WebAssembly libc off the ground. Limited
|
|
|
|
dynamic linking (no cross-module function pointers) came out of it *for free*
|
|
|
|
which is mighty convenient. We should:
|
|
|
|
|
|
|
|
1. Focus on the libc aspect, with static linking.
|
|
|
|
2. As a secondary goal use limited dynamic linking to inform WebAssembly's
|
|
|
|
design.
|
|
|
|
|
|
|
|
**Note:** This experimental WebAssembly C library with limited dynamic linking
|
|
|
|
is a hack. Don't rely on it: it's meant to inform the design of
|
|
|
|
WebAssembly. Things are changing rapidly, so mixing different parts of the
|
|
|
|
toolchain may break from time to time, try to keep them all in sync. In
|
|
|
|
particular, the current WebAssembly design doesn't allow sharing heaps between
|
|
|
|
modules. It's a convenience API in the V8 implementation which may be removed in
|
|
|
|
the future.
|
|
|
|
|
|
|
|
In this experiment, limited dynamic linking is entirely done through JavaScript,
|
|
|
|
which is acting as the dynamic linker / loader. This merely uses the WebAssembly
|
|
|
|
object's capabilities as implemented today.
|
2016-01-31 06:55:45 -08:00
|
|
|
|
|
|
|
## Quick how-to
|
|
|
|
|
2016-02-01 04:46:05 -08:00
|
|
|
Pre-built Linux x86-64 Linux binaries are available on the [waterfall][], so are
|
|
|
|
`musl.wast`, `musl.wasm`, and `wasm.js`. The waterfall marks as green the
|
|
|
|
builds which are known to work properly. Click on a green build to download the
|
|
|
|
archived binaries. Check out its [last known good revision][]. You can build
|
|
|
|
everything yourself using the waterfall's [build.py][].
|
|
|
|
|
|
|
|
[waterfall]: https://wasm-stat.us
|
|
|
|
[last known good revision]: https://storage.googleapis.com/wasm-llvm/builds/git/lkgr
|
|
|
|
[build.py]: https://github.com/WebAssembly/waterfall/tree/master/src/build.py
|
2016-01-31 06:55:45 -08:00
|
|
|
|
|
|
|
Compile your program using LLVM:
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-02-01 04:46:05 -08:00
|
|
|
clang -S -O2 --target=wasm32-unknown-unknown foo.c
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:55:45 -08:00
|
|
|
Creates a `.s` assembly file. Link it:
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-01-31 06:55:45 -08:00
|
|
|
s2wasm foo.s -o foo.wast
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:55:45 -08:00
|
|
|
Creates a `.wast` WebAssembly s-expression. Assemble it:
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-01-31 06:55:45 -08:00
|
|
|
sexpr-wasm foo.wast -o foo.wasm
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:55:45 -08:00
|
|
|
You now have a WebAssembly binary file.
|
|
|
|
|
|
|
|
Run `.wasm` files which import libc functions:
|
2016-02-01 04:46:05 -08:00
|
|
|
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-01-31 06:55:45 -08:00
|
|
|
d8 --expose-wasm musl/arch/wasm32/wasm.js -- foo.wasm musl-out/musl.wasm
|
2016-01-31 06:57:10 -08:00
|
|
|
```
|
2016-02-01 04:46:05 -08:00
|
|
|
|
|
|
|
Or run it without musl, using only `wasm.js` to emulate libc:
|
|
|
|
|
|
|
|
```
|
|
|
|
d8 --expose-wasm musl/arch/wasm32/wasm.js -- foo.wasm
|
|
|
|
```
|
|
|
|
|
|
|
|
This may work... or not. [File bugs][] on what's broken, or send patches!
|
|
|
|
|
|
|
|
[File bugs]: https://github.com/WebAssembly/musl/issues
|
2016-01-31 06:55:45 -08:00
|
|
|
|
|
|
|
## libc + dynamic linking: how does it work?
|
|
|
|
|
|
|
|
In the current V8 implementation of WebAssembly binaries, each `.wasm` module:
|
|
|
|
|
|
|
|
* Declares it imports and its exports.
|
2016-02-01 04:46:05 -08:00
|
|
|
* Takes in a dictionary mapping Foreign Function Interface (FFI) names to
|
|
|
|
corresponding functions.
|
|
|
|
* Takes in its heap, an `ArrayBuffer`.
|
|
|
|
|
|
|
|
The [wasm.js][] file:
|
2016-01-31 06:55:45 -08:00
|
|
|
|
2016-02-01 04:46:05 -08:00
|
|
|
[wasm.js]: https://github.com/WebAssembly/musl/blob/wasm-prototype-1/arch/wasm32/wasm.js
|
2016-01-31 06:55:45 -08:00
|
|
|
|
|
|
|
* Initializes the heap.
|
2016-02-01 04:46:05 -08:00
|
|
|
* Implements a rudimentary C library in JavaScript, and adds these functions to
|
|
|
|
the FFI object.
|
2016-01-31 06:55:45 -08:00
|
|
|
* Loads `.wasm` files provided on the command-line, from last to first.
|
2016-02-01 04:46:05 -08:00
|
|
|
* Adds each exported function to the FFI object, sometimes shadowing the
|
|
|
|
JavaScript fallback.
|
|
|
|
* Loads the first `.wasm` file provided and calls its `main` function.
|
2016-01-31 06:55:45 -08:00
|
|
|
|
|
|
|
Each loaded `.wasm` file is initialized with the same heap. They all share the
|
|
|
|
same address space.
|
|
|
|
|
|
|
|
Calls from one WebAssembly module to another trampoline through JavaScript, but
|
|
|
|
they should optimize well. We should figure out what we suggest developers use,
|
|
|
|
so that the default pattern doesn't require gymnastics on the compiler's part.
|
|
|
|
|
2016-02-01 11:14:27 -08:00
|
|
|
Indirect calls from one WebAssembly module to another do not work.
|
|
|
|
|
2016-01-31 06:55:45 -08:00
|
|
|
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)
|
2016-01-31 12:41:25 -08:00
|
|
|
but it's silly. If WebAssembly modules were loadable, imports inspectable, and
|
2016-01-31 06:55:45 -08:00
|
|
|
FFI object provided later then we'd be better off. We could implement very fancy
|
2016-02-01 08:00:42 -08:00
|
|
|
lazy-loading, where the developer can handle load failures. We can easily
|
|
|
|
implement `dlopen` / `dlsym` / `dlclose` as demonstrated by the `<dlfcn.h>`
|
|
|
|
example below.
|
2016-01-31 06:55:45 -08:00
|
|
|
|
|
|
|
It would also be good to be able to specify compilation / execution separately.
|
2016-01-31 12:36:42 -08:00
|
|
|
|
|
|
|
## libc implementation details
|
|
|
|
|
|
|
|
The current libc implementation builds a subset of musl using the hacked-up
|
|
|
|
`libc.py` script. It excludes files which triggered bugs throughout the
|
|
|
|
toolchain, not that the files being built are bug free either.
|
|
|
|
|
|
|
|
The implementation is based on Emscripten's musl port, but is based on a much
|
2016-02-01 04:46:05 -08:00
|
|
|
more recent musl and has no modifications to musl's code: all changes are in the
|
2016-01-31 12:36:42 -08:00
|
|
|
`arch/wasm32` directory. It aims to only communicate to the embedder using a
|
|
|
|
syscall API, modeled after Linux' own syscall API. This may have shortcomings,
|
|
|
|
but it's a good thing to try out since we can revisit later. Note the
|
|
|
|
`musl_hack` functions in `wasm.js`: they fill in for functionality that's
|
|
|
|
currently been hacked out and which musl expects to import. It should be
|
2016-02-01 04:46:05 -08:00
|
|
|
exporting these instead of importing them. Maybe more functionality should be
|
|
|
|
implemented in JavaScript, but experience with NaCl and Emscripten leads us to
|
|
|
|
believe the syscall API is a good boundary.
|
2016-01-31 12:36:42 -08:00
|
|
|
|
|
|
|
The eventual goal is for the WebAssembly libc to be upstreamed to musl, and
|
|
|
|
that'll require *doing it right* according to the musl community. We also want
|
|
|
|
Emscripten to be able to use the same libc implementation. The approach in this
|
|
|
|
repository may not be the right one.
|
|
|
|
|
|
|
|
## Miscellaneous
|
|
|
|
|
2016-02-01 11:14:27 -08:00
|
|
|
Dynamic linking and pointer-less dynamic linking aren't in WebAssembly's current
|
|
|
|
MVP because we thought it would be hard. This repository shows that it's
|
|
|
|
*possible* to expose limited but useful functionality, we therefore may as well
|
2016-02-01 04:46:05 -08:00
|
|
|
design it right from the start, or make it entirely impossible for the MVP.
|
2016-01-31 12:36:42 -08:00
|
|
|
|
|
|
|
That'll including figuring out calling convention and ABI. Exports currently
|
|
|
|
don't declare their signature in a WebAssembly module, even though they are in
|
|
|
|
the binary format, and don't cause any failure when the APIs don't match. That
|
|
|
|
should be fixed.
|
2016-02-01 04:46:05 -08:00
|
|
|
|
|
|
|
We'll also need to figure out how to make memory segments relocatable, and the
|
|
|
|
AST references to the segments position independent. Do we even want to allow
|
2016-02-01 08:19:23 -08:00
|
|
|
non-relocatable segments? The current implementation overwrites previous
|
|
|
|
segments if they specify the same memory location.
|
2016-02-01 04:46:05 -08:00
|
|
|
|
|
|
|
It seems like user code should be managing all of the heap, the first module
|
|
|
|
that's loaded (even before libc) could therefore be a basic memory manager. The
|
|
|
|
dynamic loading mechanism (implemented in JavaScript) would then query this heap
|
|
|
|
manager to figure out where to locate segments, as well as to position user
|
|
|
|
stacks. libc's `malloc` would then use this basic memory manager to implement
|
|
|
|
runtime memory management, the same would be true for stack positioning, thread
|
|
|
|
stacks, and thread-local storage allocation.
|
|
|
|
|
2016-02-01 08:04:53 -08:00
|
|
|
Interesting applications can be built when modules *don't* share the same
|
|
|
|
heap. They need to communicate through copy-in / copy-out functionality (such as
|
|
|
|
Linux' `copy_from_user` / `copy_to_user` functions), and are then entirely
|
|
|
|
isolated from each other except for their API boundary. This allows applications
|
|
|
|
to instantiate their heap in a private closure and only expose APIs, providing
|
|
|
|
good isolation properties and preventing user code from overflow and other
|
|
|
|
security issues.
|
|
|
|
|
2016-02-01 04:46:05 -08:00
|
|
|
## Why do dynamic linking now?
|
|
|
|
|
|
|
|
These basic experiments are finding bugs in the toolchain, if anything they're
|
|
|
|
useful in making it more robust. It's also an unexpected usage of the APIs! It's
|
|
|
|
better that we find it now and figure out what it means.
|
|
|
|
|
|
|
|
Having a standalone `musl.wasm` is much simpler for code deployment and allows
|
|
|
|
caching.
|
|
|
|
|
|
|
|
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
|
|
|
|
lazy-load and lazy-compile each function as needed, and even unload them when
|
|
|
|
the program doesn't need them anymore.
|
2016-02-01 08:00:42 -08:00
|
|
|
|
|
|
|
## `<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
|
2016-02-01 11:14:27 -08:00
|
|
|
different tables (hence the "limited" nature of this hacky dynamic
|
|
|
|
linking). `call_indirect` can only call functions from the same module, whereas
|
|
|
|
`call_import` can call functions from another module by trampolining through
|
|
|
|
JavaScript. We could fix this by:
|
2016-02-01 08:00:42 -08:00
|
|
|
|
|
|
|
* Forcing developers to use a function such as `dlcall` and provide handles for
|
2016-02-01 08:07:36 -08:00
|
|
|
the module and symbol. `dlcall` would trampoline through JavaScript. This
|
|
|
|
requires that developers modify their code: C currently allows them to call
|
|
|
|
the `dlsym` result directly.
|
2016-02-01 08:00:42 -08:00
|
|
|
* 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.
|
2016-02-01 11:14:27 -08:00
|
|
|
|
|
|
|
This amounts to designing full dynamic linking correctly, which we may not want
|
|
|
|
to do for MVP.
|