bytecodealliance / bytecodealliance/wasmtime
DRC collector: use a better free list
- Dominant language
- Rust
- Stars
- 18.6k
- Forks
- 1.8k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 121
Description
We use a very simple, first-fit free list for allocation in the DRC collector right now:
https://github.com/bytecodealliance/wasmtime/blob/57ba95e92eff1d02f6b05ab86b9e81b35bd69d28/crates/wasmtime/src/runtime/vm/gc/enabled/free_list.rs#L5
We should use something better, that has size classes and good hueristics and all that.
But also, we would ideally not write and maintain what is essentially our own `malloc` implementation ourselves, since getting all the details right and tuning them correctly for a variety of workloads is a lot of effort. The problem is that our malloc is outside the memory space that it is divvying up and parceling out: it is not inside the GC heap using and returning pointers within the GC heap, it is in the VM on the outside and is returning indices into the sandboxed region that makes up the GC heap, rather than pointers.
But... why not just compile `dlmalloc` (or something) to Wasm, have `dlmalloc` use the GC heap as its linear memory, and make GC allocations call `dlmalloc`'s allocation and deallocation routines? That would give us a battle-hardened allocator that is most certainly faster than our free list pretty much For Free and with trivial long-term maintenance burden. Compiling `dlmalloc` to Wasm, with the right flags and exported symbols, results in a ~5-6 KiB binary that doesn't use any Wasm globals or `funcref` tables and uses only ~500 bytes of static data. It seems very doable.
This will rely on a lot of the same kind of infrastructure we want to have for [compile-time builtins](https://github.com/bytecodealliance/rfcs/pull/43).
Random kinks to work out:
* Do we compile `dlmalloc` into every GC-type-using module, similar to trampolines? (Including modules that just use `externref` and not full Wasm GC.)
* This means there would be some duplicate copies of the code when you load multiple GC-type-using modules in the system, which is fine from a correctness POV, but is undesirable from a disk space usage POV, and a memory usage POV *if* you're on a system without virtual memory.
* Or do we try and have only a single copy of `dlmalloc` in a system that is running multiple modules?
* We could compile `dlmalloc-gc-heap.wasm` on demand the first time a gc-type-using module is compiled, but that requires that wasmtime was built with a compiler itself.
* We could compile `dlmalloc-gc-heap.wasm` at Wasmtime build time and `include_bytes!(...)` it into the Wasmtime binary, but that means additional, bootstrap-y, cyclic dependencies where you need a wasmtime to build wasmtime.
* We could require that configuring Wasmtime to use the DRC collector means you have to give a `wasmtime::Drc` object to the `wasmtime::Config::collector` method, and you can either `wasmtime::Drc::new()` when wasmtime is built with a compiler or `wasmtime::Drc::from_serialized(...)` otherwise. However, this raises questions about `impl Default for Config` since `Config` contains the configured collector, which by default is the DRC collector. Does it no longer impl `Default` when there is no compiler? Do we change the default collector when there is no compiler? Do we just remove that `Default` impl?
Contributor guide
Assessment
This issue has not been assessed yet.