Instead of returning HeapBlock memory to the kernel (or a non-type
specific shared cache), we now keep a BlockAllocator per CellAllocator
and implement "deallocation" by basically informing the kernel that we
don't need the physical memory right now.
This is done with MADV_FREE or MADV_DONTNEED if available, but for other
platforms (including SerenityOS) we munmap and then re-mmap the memory
to achieve the same effect. It's definitely clunky, so I've added a
FIXME about implementing the madvise options on SerenityOS too.
The important outcome of this change is that GC types that use a
type-specific allocator become immune to use-after-free type confusion
attacks, since their virtual addresses will only ever be re-used for
the same exact type again and again.
Fixes#22274
When resolving a rope, we've already taken care to resolve it to
a UTF-8 byte stream. There's no need to do a separate pass just for
validating the data again.
This was noticeable in some profiles. I made a simple microbenchmark
that gets a 30% speed-up:
("x" + "y".repeat(100_000_000)).trimStart()
Instead of using a StringBuilder, add a String::repeated(String, N)
overload that takes advantage of knowing it's already all UTF-8.
This makes the following microbenchmark go 4x faster:
"foo".repeat(100_000_000)
And for single character strings, we can even go 10x faster:
"x".repeat(100_000_000)
We can now implement steps related to resizable ArrayBuffer objects. We
can also implement a couple of missing SharedArrayBuffer checks.
The original implementation of this proposal did not have any tests, so
tests are added here for the whole implementation.
In: https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object
The spec says:
> is a constructor function object that all of the TypedArray
> constructor objects inherit from.
From what I understand from this, it effectively just means is that the
prototype for the constructor should simply be set to
TypedArrayConstructor. We _were_ doing that, but also inheriting from
it in C++.
This meant we were invoking TypedArrayConstructor::initialize for each
of the typed arrays. This is not actually what we want, since it means
that the 'of' and 'from' functions were being defined as native
properties in both the concrete typed array (e.g Uint8Array), and the
abstract TypedArray. Instead, the properties should only be defined and
inherited from the abstract TypedArray class.
Diff Tests:
+4 ✅ -4 ❌
Co-Authored-By: Andreas Kling <kling@serenityos.org>
This is (part of) a normative change in the ECMA-262 spec. See:
a9ae96e
This implements just support for resizing ArrayBuffer objects. This does
not implement the SharedArrayBuffer changes, as we do not have enough
support to do so.
The IsValidIntegerIndex AO performs the checks we are interested in. The
manual implementation we currently have will no longer compile once the
resizable ArrayBuffer spec is implemented. The AO will be updated with
the spec implementation, so let's use it now to avoid breakage.
This renames IntegerIndexedElementGet to TypedArrayGetElement, and
IntegerIndexedElementSet to TypedArraySetElement.
This also renames the indexedPosition variable inside these method
definitions to byteIndexInBuffer.
These are part of a couple editorial changes in the ECMA-262 spec. See:
03e4410a1a4d48
The remainder of the changes in those commits apply to the resizable
ArrayBuffer spec, which is not implemented in LibJS as of this commit.
After the resizable ArrayBuffer proposal was merged, a bunch of methods
had their spec numbers/headers changed. This patch just updates existing
methods to make future patches easier to grok.
This was a normative change in the Array.fromAsync spec which we already
implemented :^). See:
https://github.com/tc39/proposal-array-from-async/commit/689d0d
I've left an existing FIXME in there for some other bug which we have
which is causing some test262 test cases to fail.
Due to a `requires` mistake, we were always using the fallback
size-based cell allocators.
Also, now that we start using them, make them NeverDestroyed so
we don't try to deallocate them on program exit.
In a bunch of cases, this actually ends up simplifying the code as
to_number will handle something such as:
```
Optional<I> opt;
if constexpr (IsSigned<I>)
opt = view.to_int<I>();
else
opt = view.to_uint<I>();
```
For us.
The main goal here however is to have a single generic number conversion
API between all of the String classes.
When calling `running_execution_context` from other VM APIs, and the
execution context stack is empty, the verification message is inlined
from AK::Vector. Add a specific VERIFY to `running_execution_context` to
help diagnose this issue better.
This commit un-deprecates DeprecatedString, and repurposes it as a byte
string.
As the null state has already been removed, there are no other
particularly hairy blockers in repurposing this type as a byte string
(what it _really_ is).
This commit is auto-generated:
$ xs=$(ack -l \bDeprecatedString\b\|deprecated_string AK Userland \
Meta Ports Ladybird Tests Kernel)
$ perl -pie 's/\bDeprecatedString\b/ByteString/g;
s/deprecated_string/byte_string/g' $xs
$ clang-format --style=file -i \
$(git diff --name-only | grep \.cpp\|\.h)
$ gn format $(git ls-files '*.gn' '*.gni')
This is similar to "unique" shapes, which were removed in commit
3d92c26445.
The key difference is that dictionary shapes don't have a serial number,
but instead have a "cacheable" flag.
Shapes become dictionaries after 64 transitions have occurred, at which
point no further transitions occur.
As long as properties are only added to a dictionary shape, it remains
cacheable. (Since if we've cached the shape pointer in an IC somewhere,
we know the IC is still valid.)
Deleting a property from a dictionary shape causes it to become an
uncacheable dictionary.
Note that deleting a property from a non-dictionary shape still performs
a delete transition.
This fixes an issue on Discord where Object.freeze() would eventually
OOM us, since they add more than 16000 properties to a single object
before freezing it.
It also yields a 15% speedup on Octane/pdfjs.js :^)
All the keys in a property table are guaranteed to be marked via
Shape::m_property_key in each step of the transition chain that leads
up to the Shape.
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.
These should really be weakly held by the Shape, but we don't have a
mechanism for weak hashmap keys at the moment, so let's just mark
these for now so they don't go stale.
These are just like Uint8Array, except Put values have to be clamped
in the 0..255 range.
Takes CPU usage from 40% to 30% on the "Canvas Cycle" demo at
http://www.effectgames.com/demos/canvascycle/ :^)
When iterating over vanilla objects/arrays with normal property storage,
we can skip the generic Get mechanism in favor of looking directly at
property storage. This is essentially what we do in the bytecode path.
Instead of going through the steps of creating an empty new object,
and adding two properties ("value" and "done") to it, we can pre-bake
a shape object and cache the property offsets.
This makes creating iterator result objects in the runtime much faster.
47% speedup on this microbenchmark:
function go(a) {
for (const p of a) {
}
}
const a = [];
a.length = 1_000_000;
go(a);
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);
When all the variables in a for..in/of block's lexical scope have been
turned into locals, we don't need to create and immediately abandon an
empty environment for them.
This avoid environment allocation in cases like this:
function foo(a) {
for (const x of a) {
}
}