WeakContainers need to look at the Cell::State bits to know if their
weak pointees got swept by garbage collection. So we must do this before
potentially freeing one or more HeapBlocks by notifying the allocator
that a block became empty.
Instead of iterating *all* swept cells when pruning weak containers,
only iterate the cells actually *in* the container.
Also, instead of compiling a list of all swept cells, we can simply
check the Cell::state() flag to know if something should be pruned.
VM now has a string cache which tracks all live PrimitiveStrings and
reuses an existing one if possible. This drastically reduces the number
of GC-allocated strings in many real-word situations.
The way that transition avoidance (foo_without_transition) was
implemented led to shapes being unshareable and caused shape explosion
instead, precisely what we were trying to avoid.
This patch removes all the attempts to avoid transitioning shapes, and
instead *adds* transitions when changing an object's prototype.
This makes transitions flow naturally, and as a result we end up with
way fewer shape objects in real-world situations.
When we run out of big problems, we can get back to avoiding transitions
as an optimization, but for now, let's avoid ballooning our processes
with a unique shape for every object.
This patch ups the max number of heap allocations between each GC
from 10'000 to 100'000. This is still relatively aggressive but already
does a good job of cutting down on time spent in GC.
This patch adds a `-z` option to js and test-js. When run in this mode,
garbage cells are never actually destroyed. We instead keep them around
in a special zombie state.
This allows us to validate that zombies don't get marked in future GC
scans (since there were not supposed to be any more references!) :^)
Cells get notified when they become a zombie (via did_become_zombie())
and this is used by WeakContainer cells to deregister themselves from
the heap.
Make this API take a Span<Cell*> instead of a Vector<Cell*>&.
This is behavior neutral, but stops the API looking like it wants to
do mutable things to the Vector.
This should fix the flaky tests of test-js.
It also fixes the tests when running with the -g flag since the values
will not be garbage collected too soon.
- Replace the misleading abuse of the m_transitions_enabled flag for the
fast path without lookup with a new m_initialized boolean that's set
either by Heap::allocate() after calling the Object's initialize(), or
by the GlobalObject in its special initialize_global_object(). This
makes it work regardless of the shape's uniqueness.
- When we're adding a new property past the initialization phase,
there's no need to do a second metadata lookup to retrieve the storage
value offset - it's known to always be the shape's property count
minus one. Also, instead of doing manual storage resizing and
assignment via indexing, just use Vector::append().
- When we didn't add a new property but are overwriting an existing one,
the property count and therefore storage value offset doesn't change,
so we don't have to retrieve it either.
As a result, Object::set_shape() is now solely responsible for updating
the m_shape pointer and is not resizing storage anymore, so I moved it
into the header.
Making userspace provide a global string ID was silly, and made the API
extremely difficult to use correctly in a global profiling context.
Instead, simply make the kernel do the string ID allocation for us.
This also allows us to convert the string storage to a Vector in the
kernel (and an array in the JSON profile data.)
Almost everything in LibJS includes Cell.h, don't force all code to
include AK/TypeCasts.h + AK/String.h. Instead include them where they
are actually used and required.
Previously, EnvironmentRecord was a JS::Object. This was done because
GlobalObject inherited from EnvironmentRecord. Now that this is no
longer the case, we can simplify things by making EnvironmentRecord
inherit from Cell directly.
This also removes the need for environment records to have a shape,
which was awkward. This will be removed in the following patch.
Mark the entirety of a heap block's storage poisoned at construction.
Unpoison all of a Cell's memory before allocating it, and re-poison as
much as possible on deallocation. Unfortunately, the entirety of the
FreelistEntry must be kept unpoisoned in order for reallocation to work
correctly.
Decreasing the size of FreelistEntry or adding a larger redzone to Cells
would make the instrumentation even better.
Use this to avoid creating a 16 byte cell allocator on x86_64, where the
size of FreelistEntry is 24 bytes. Every JS::Cell must be at least the
size of the FreelistEntry or things start crashing, so the 16 byte
allocator was wasted on that platform.
This is the coarsest grained ASAN instrumentation possible for the LibJS
heap. Future instrumentation could add red-zones to heap block
allocations, and poison the entire heap block and only un-poison used
cells at the CellAllocator level.
The previous VERIFY() call checked that aligned_alloc() didn't return
MAP_FAILED. When out of memory aligned_alloc() returns a null pointer
so let's check for that instead.
This patch adds a BlockAllocator to the GC heap where we now cache up to
64 HeapBlock-sized mmap's that get recycled when allocating HeapBlocks.
This improves test-js runtime performance by ~35%, pretty cool! :^)
So far we only have two states: Live and Dead. In the future, we can
add additional states to support incremental sweeping and/or multi-
stage cell destruction.
If we're able to allocate cells from a freelist, we should always
prefer that over the lazy freelist, since this may further defer
faulting in additional memory for the HeapBlock.
Thanks to @gunnarbeutner for pointing this out. :^)