Instead of keeping all the HeapBlocks in one big list, we now split it
into two levels:
- Heap has a set of Allocators, each with a specific cell size.
- Allocators have two lists of blocks, "full" and "usable".
Allocating a new cell no longer has to scan the entire set of blocks,
but instead just needs to find the right allocator and then pop a cell
from its freelist. If all the blocks in the allocator are full, a new
block will be created.
Blocks are moved from the "full" to "usable" list after sweeping has
determined that they are not completely empty and not completely full.
There are certainly many ways we can improve on this. This patch is
mostly about getting the new allocator architecture in place. :^)
Taking a big step towards a world of multiple global object, this patch
adds a new JS::VM object that houses the JS::Heap.
This means that the Heap moves out of Interpreter, and the same Heap
can now be used by multiple Interpreters, and can also outlive them.
The VM keeps a stack of Interpreter pointers. We push/pop on this
stack when entering/exiting execution with a given Interpreter.
This allows us to make this change without disturbing too much of
the existing code.
There is still a 1-to-1 relationship between Interpreter and the
global object. This will change in the future.
Ultimately, the goal here is to make Interpreter a transient object
that only needs to exist while you execute some code. Getting there
will take a lot more work though. :^)
Note that in LibWeb, the global JS::VM is called main_thread_vm(),
to distinguish it from future worker VM's.
The fact that a `MarkedValueList` had to be created was just annoying,
so here's an alternative.
This patchset also removes some (now) unneeded MarkedValueList.h includes.
You can now pass print_report=true to Heap::collect_garbage() and it
will print out a little summary of the time spent, and counts of
live vs freed cells and blocks.
Lagom now builds under macOS. Only two minor adjustments were required:
* LibCore TCP/UDP code can't use `SOCK_{NONBLOCK,CLOEXEC}` on macOS,
use ioctl() and fcntl() instead
* LibJS `Heap` code pthread usage ported to MacOS
A MarkedValueList is basically a Vector<JS::Value> that registers with
the Heap and makes sure that the stored values don't get GC'd.
Before this change, we were unsafely keeping Vector<JS::Value> in some
places, which is out-of-reach for the live reference finding logic
since Vector puts its elements on the heap by default.
We now pass all the JavaScript tests even when running with "js -g",
which does a GC on every heap allocation.
When the Heap is going down, it's our last chance to run destructors,
so add a separate collector mode where we simply skip over the marking
phase and go directly to sweeping. This causes everything to get swept
and all live cells get destroyed.
This way, valgrind reports 0 leaks on exit. :^)
This is pretty heavy and unoptimized, but it will do the trick for now.
Basically, Heap now has a HashTable<HandleImpl*> and you can call
JS::make_handle(T*) to construct a Handle<T> that guarantees that the
pointee will always survive GC until the Handle<T> is destroyed.
We now scan the stack and CPU registers for potential pointers into the
GC heap, and include any valid Cell pointers in the set of roots.
This works pretty well but we'll also need to solve marking of things
passed to native functions, since those are currently in Vector<Value>
and the Vector storage is on the heap (not scanned.)