We previously had a concept of unique shapes, which meant that they
couldn't be shared between multiple objects.
Object shapes became unique in three situations:
- They were the shape of the global object.
- They had more than 100 properties added to them.
- They had one or more properties deleted from them.
Unfortunately, unique shapes presented an annoying problem for inline
caches, and we added a "unique shape serial number" for being able to
tell that a unique shape had been mutated.
This patch gets rid of the concept of unique shapes, simplifying all
the caching code, since inline caches can now simply perform a shape
check and then we're good.
To make this possible, we now have the concept of delete transitions,
which occur when a property is deleted from a shape.
Note that this patch by itself introduces a performance regression in
some situtations, since we now create a lot more shapes, and marking
their property keys can be very heavy. This will be addressed in a
subsequent patch.
Using the code that it has just produced, the JIT::Compiler can build an
ELF image so that we can attach meaningful symbols to JITted code, and
thus enable GDB to display more information about the code that we're
running.
When iterating over an iterable, we get back a JS object with the fields
"value" and "done".
Before this change, we've had two dedicated instructions for retrieving
the two fields: IteratorResultValue and IteratorResultDone. These had no
fast path whatsoever and just did a generic [[Get]] access to fetch the
corresponding property values.
By replacing the instructions with GetById("value") and GetById("done"),
they instantly get caching and JIT fast paths for free, making iterating
over iterables much faster. :^)
26% speed-up on this microbenchmark:
function go(a) {
for (const p of a) {
}
}
const a = [];
a.length = 1_000_000;
go(a);
This patch makes IteratorRecord an Object. Although it's not exposed to
author code, this does allow us to store it in a VM register.
Now that we can store it in a VM register, we don't need to convert it
back and forth between IteratorRecord and Object when accessing it from
bytecode.
The big win here is avoiding 3 [[Get]] accesses on every iteration step
of for..of loops. There are also a bunch of smaller efficiencies gained.
20% speed-up on this microbenchmark:
function go(a) {
for (const p of a) {
}
}
const a = [];
a.length = 1_000_000;
go(a);
(Instead of MarkedVector<Value>.) This is a step towards not storing
argument lists in MarkedVector<Value> at all. Note that they still end
up in MarkedVectors since that's what ExecutionContext has.
This will not meaningfully affect short array literals, but it does
give us a bit of extra perf when evaluating huge array expressions like
in Kraken/imaging-darkroom.js
Array.length is magical (since it has to reflect the number of elements
in the object's property storage).
We now handle it specially in jitted code, giving us a massive speed-up
on Kraken/ai-astar.js (and probably many other things as well) :^)
Until now, the unwind context stack has not been maintained by jitted
code, which meant we were unable to support the `with` statement.
This is a first step towards supporting that by making jitted code
call out to C++ to update the unwind context stack when entering/leaving
unwind contexts.
We also introduce a new "Catch" bytecode instruction that moves the
current exception into the accumulator. It's always emitted at the start
of a "catch" block.
This patch makes it possible for JS::Object::internal_set() to populate
a CacheablePropertyMetadata, and uses this to implement a basic
monomorphic cache for the most common form of property write access.