Porting Druntime to WebAssembly
The MVP version of WebAssembly is widely supported across browsers. Work continues to implement additional features, see the proposal tracking page.
The port will adhere to the limitations of WebAssembly. Features unavailable in WebAssembly will be unavailable in this port. This is in contrast with something like emscripten where missing functionality is often emulated. Most of the missing features in WebAssembly are in the proposal phase and are expected to be available in due time.
Another important goal is to keep the changes in druntime to a minimum. This port will not reimplement anything unless absolutely necessary.
This approach is to ensure the port can be completed in a reasonable timeframe, without going into myraid tangents. The goal is to quickly enable developers to compile D applications to WebAssembly. There are plenty of things that can be improved incrementally afterwards, preferably in such a way to also benefit other targets.
Again, the most important thing is to enable developers to use D to compile to WebAssembly. Things like binary size, while important, are second to that.
DMD and GDC don't have a WebAssembly backend, only LDC can be used to compile to WebAssembly. None of the required changes are compiler specific and if any of the other compilers got a WebAssembly backend in the future, it would be able to use this port as well.
WebAssembly itself has no syscalls, and the only intrinsics it provides are to query and grow its linear memory. This poses a problem because druntime is build on top of libc.
While it is certainly possible to reduce the dependency on libc and to reimplement some of its functions, that goes against the approach outlined above. For that reason this port will use WASI-libc to provide an implementation of libc that conforms to the WASI api.
WASI is the official WebAssembly System Interface and provides a POSIX like API. Read more about it on wasi.dev.
Adding WASI-libc requires minimal change, and it keeps the option open for incremental improvements by replacing current libc calls with implementations in pure D or with direct calls to WASI.
The added benefit is that it allows D code to run inside environments where WASI is already supported. E.g.:
These correspond to quering the clock, exiting the process and writing to stdout/err (core druntime doesn't open files).
In short, using WASI-libc makes porting druntime a lot easier because it doens't require refactoring druntime to avoid libc nor does it require reimplementing functions like snprintf, malloc/free, etc.
The next section describes what language features are going to be supported.
Since the GC is the foundation of many language features it needs to be fully supported.
WebAssembly has a single data section where both immutable and mutable globals are stored, as well as a downward growing stack. It uses a 32-bit address space and it always starts from 0, both of which increases the chance of a false pointer. WebAssembly as such is not super favorable to a conservative GC, but it is assumed WebAssembly programs use relatively little memory, offsetting this problem. I am unaware of how much the current GC is precise, but that would offset the problem as well. Since this port uses the same GC, any improvements there would spill over to this port.
The only unknown part is how to dump the registers to the stack to ensure no pointers are held in the registers only.
With the GC the following language features are supported:
- Dynamic arrays (as well as appending and concatenation)
- Associative arrays (as well as insertion, removal or lookups)
- Taking the address of (i.e. making a delegate to) a nested function that accesses variables in an outer scope
- A function literal that accesses variables in an outer scope
Assembly won't be supported. Inline LLVM IR most likely will be.
WebAssembly support for SIMD is still in proposal phase and won't be supported in this port until after it is accepted. It is mostly waiting for it to be available in LLVM and having browsers support it.
Threads, thread-local storage, mutexes and atomics won't be supported. There is a Thread Proposal and until that is accepted they are out of scope of this initial port.
Exceptions can be thrown but not catched. A thrown exception will terminate the program. Exceptions are still in the proposal phase. When the proposal is accepted exceptions can be fully supported.
Will be supported. But due to limited support for exceptions, the unittest runner will stop after the first assertion fails.
The only problem I forsee is that web apis are sometimes asynchronous and you cannot wait for them for completion in a unittest since that will block the main thread. Either D would need asynchronous unittests or some other mechanism needs to be developed for them. Possibly a library solution.
There are function signature mismatches whenever reals are used. WASI-libc's ABI uses 2 i64's for a real parameter and uses a i32 (pointer) in the first parameter whenever a function returns a real, while LDC simply emits a single f64 in both cases. I haven't investigated it properly but it likely can be be fixed in LDC.
Just a couple of days ago the Chrome DevTools team announced support for DWARF debugging. Since D already has full DWARF debugging information nothing else needs to be done.
Some miscellaneous items that are supported.
- TypeInfo and ModuleInfo
- Static module constructors and destructors
- Probably others I have missed.