Even if the pointer value is const, the value they point to is not
necessarily const, so these functions should not add the qualifier.
This also removes the redundant non-const implementations of these
operators.
Doing things in the destructor of a GC-allocated object isn't always
safe, in case it involves accessing other GC-allocated objects.
If they were already swept by GC, we'd be poking into freed memory.
This patch adds a separate finalization pass where GC calls finalize()
on every unmarked cell that's about to be deleted.
It's safe to access other GC objects in finalize(), even if they're
also unmarked.
This was previously indirectly forcing Heap/Handle.h to include it
instead. This will let us include Handle.h from PropertyKey, which will
allow us to solve a different issue.
SafeFunction automatically registers its closure memory area in a place
where the JS garbage collector can find it.
This means that you can capture JS::Value and arbitrary pointers into
the GC heap in closures, as long as you're using a SafeFunction, and the
GC will not zap those values!
There's probably some performance impact from this, and there's a lot of
things that could be nicer/smarter about it, but let's build something
that ensures safety first, and we can worry about performance later. :^)
JS::Value stores 48 bit pointers to separately allocated objects in its
payload. On x86-64, canonical addresses have their top 16 bits set to
the same value as bit 47, effectively meaning that the value has to be
sign-extended to get the pointer. AArch64, however, expects the topmost
bits to be all zeros.
This commit gates sign extension behind `#if ARCH(X86_64)`, and adds an
`#error` for unsupported architectures, so that we do not forget to
think about pointer handling when porting to a new architecture.
Fixes#15290FixesSerenityOS/ladybird#56
This is a monster patch that turns all EventTargets into GC-allocated
PlatformObjects. Their C++ wrapper classes are removed, and the LibJS
garbage collector is now responsible for their lifetimes.
There's a fair amount of hacks and band-aids in this patch, and we'll
have a lot of cleanup to do after this.
These are two new smart pointers that are really just raw pointers under
the hood. The initial benefit is all in the names, they allow us to
declare that we're pointing at something in the GC heap.
Later we may also find ways to add debugging logic or static analysis to
these types.
This is a continuation of the previous five commits.
A first big step into the direction of no longer having to pass a realm
(or currently, a global object) trough layers upon layers of AOs!
Unlike the create() APIs we can safely assume that this is only ever
called when a running execution context and therefore current realm
exists. If not, you can always manually allocate the Error and put it in
a Completion :^)
In the spec, throw exceptions implicitly use the current realm's
intrinsics as well: https://tc39.es/ecma262/#sec-throw-an-exception
This is a continuation of the previous three commits.
Now that create() receives the allocating realm, we can simply forward
that to allocate(), which accounts for the majority of these changes.
Additionally, we can get rid of the realm_from_global_object() in one
place, with one more remaining in VM::throw_completion().
This is a continuation of the previous two commits.
As allocating a JS cell already primarily involves a realm instead of a
global object, and we'll need to pass one to the allocate() function
itself eventually (it's bridged via the global object right now), the
create() functions need to receive a realm as well.
The plan is for this to be the highest-level function that actually
receives a realm and passes it around, AOs on an even higher level will
use the "current realm" concept via VM::current_realm() as that's what
the spec assumes; passing around realms (or global objects, for that
matter) on higher AO levels is pointless and unlike for allocating
individual objects, which may happen outside of regular JS execution, we
don't need control over the specific realm that is being used there.
This is a continuation of the previous commit.
Calling initialize() is the first thing that's done after allocating a
cell on the JS heap - and in the common case of allocating an object,
that's where properties are assigned and intrinsics occasionally
accessed.
Since those are supposed to live on the realm eventually, this is
another step into that direction.
Using the fact that there are 2^52-2 NaN representations we can
"NaN-box" all the Values possible. This means that Value no longer has
an explicit "Type" but that information is now stored in the bits of a
double. This is done by "tagging" the top two bytes of the double.
For a full explanation see the large comment with asserts at the top of
Value.
We can also use the exact representation of the tags to make checking
properties like nullish, or is_cell quicker. But the largest gains are
in the fact that the size of a Value is now halved.
The SunSpider and other benchmarks have been ran to confirm that there
are no regressions in performance compared to the previous
implementation. The tests never performed worse and in some cases
performed better. But the biggest differences can be seen in memory
usage when large arrays are allocated. A simple test which allocates a
1000 arrays of size 100000 has roughly half the memory usage.
There is also space in the representations for future expansions such as
tuples and records.
To ensure that Values on the stack and registers are not lost during
garbage collection we also have to add a check to the Heap to check for
any of the cell tags and extracting the canonical form of the pointer
if it matches.
These are mostly minor mistakes I've encountered while working on the
removal of StringView(char const*). The usage of builder.put_string over
Format<FormatString>::format is preferrable as it will avoid the
indirection altogether when there's no formatting to be done. Similarly,
there is no need to do format(builder, "{}", number) when
builder.put_u64(number) works equally well.
Additionally a few Strings where only constant strings were used are
replaced with StringViews.
Previously this would've said `make_handle(Value(1234))` is null, as it
did not contain a cell (but rather a plain Value), which made throwing
primitives spin forever in BC mode.
This helps make the overall codebase consistent. `class_name()` in
`Kernel` is always `StringView`, but not elsewhere.
Additionally, this results in the `strlen` (which needs to be done
when printing or other operations) always being computed at
compile-time.
Note: MarkedVector is still relatively new and has zero users right now,
so these changes don't affect any code other than the class itself.
Reasons for this are the rather limited API:
- Despite the name and unlike MarkedValueList, MarkedVector isn't
actually a Vector, it *wraps* a Vector. This means that plenty of
convenient APIs are unavailable and have to be exported on the class
separately and forwarded to the internal Vector, or need to go through
the exposed Span - both not great options.
- Exposing append(Cell*) and prepend(Cell*) on the base class means that
it was possible to append any Cell type, not just T! All the strong
typing guarantees are basically gone, and MarkedVector doesn't do much
more than casting Cells to the appropriate type through the exposed
Span.
All of this combined means that MarkedVector - in its current form -
doesn't provide much value over MarkedValueList, and that we have to
maintain two separate, yet almost identical classes.
Let's fix this!
The updated MarkedVector steals various concepts from the existing
MarkedValueList, especially the ability to copy. On the other hand, it
remains generic enough to handle both Cell* and Value for T, making
MarkedValueList effectively redundant :^)
Additionally, by inheriting from Vector we get all the current and
future APIs without having to select and expose them separately.
MarkedVectorBase remains and takes care of communicating creation and
destruction of the class to the heap. Visiting the contained values is
handled via a pure virtual method gather_roots(), which is being called
by the Heap's function of the same name; much like the VM has one.
From there, values are added to the roots HashTable if they are cells
for T = Value, and unconditionally for any other T.
As a small additional improvement the template now also takes an
inline_capacity parameter, defaulting to 32, and forwards it to the
Vector template; allowing for possible future optimizations of current
uses of MarkedValueList, which hard-codes it to 32.
This feature had bitrotted somewhat and would trigger errors because
PrimitiveStrings were "destroyed" but because of this mode they were not
removed from the string cache. Even fixing that case running test-js
with the options still failed in more places.
Two of our most frequently allocated objects are Shape (88 bytes)
and DeclarativeEnvironment (80 bytes). Putting these into 128-byte
cells was quite wasteful, so let's add a more suitable allocator
for them.
This allows you to keep an arbitrary JS::Value alive without having to
hook visit_edges somewhere, e.g. by being a NativeFunction that
overrides visit_edges.
For example, this allows you to store JS::Handle<JS::Value> as the key
of a HashMap. This will be used to keep arbitrary Values alive in
the key of a temporary HashMap in Array.prototype.groupByToMap.
Co-authored-by: Ali Mohammad Pur <mpfard@serenityos.org>
This abstracts a vector of Cell* with a strongly typed span() accessor
that gives you Span<T*> instead of Span<Cell*>.
It is intended to replace MarkedValueList in situations where you only
need to store pointers to Cell (or an even more specific type of Cell).
The API can definitely be improved, it's just the bare basics for now.
This option is already enabled when building Lagom, so let's enable it
for the main build too. We will no longer be surprised by Lagom Clang
CI builds failing while everything compiles locally.
Furthermore, the stronger `-Wsuggest-override` warning is enabled in
this commit, which enforces the use of the `override` keyword in all
classes, not just those which already have some methods marked as
`override`. This works with both GCC and Clang.
This isn't a complete conversion to ErrorOr<void>, but a good chunk.
The end goal here is to propagate buffer allocation failures to the
caller, and allow the use of TRY() with formatting functions.
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.