Commit graph

220 commits

Author SHA1 Message Date
Shannon Booth
716e86f042 LibJS: Use HeapFunction for async body closure in Array.fromAsync 2024-10-30 20:55:45 +01:00
Andreas Kling
cc4b3cbacc Meta: Update my e-mail address everywhere 2024-10-04 13:19:50 +02:00
Andreas Kling
061ad33705 Revert "LibJS: Cache the shape for normal ECMAScriptFunctionObject prototypes"
This reverts commit 749cf2d1b5.

This broke pinterest.com
2024-09-28 19:41:30 +02:00
Andreas Kling
749cf2d1b5 LibJS: Cache the shape for normal ECMAScriptFunctionObject prototypes
Instead of creating a unique new prototype shape every time a function
object is instantiated, we now keep one cached with the intrinsics.

This avoids a whole lot of shape allocations, reducing GC pressure.
2024-09-26 20:23:43 +02:00
Andreas Kling
b3f77e4769 LibJS: Don't copy current program counter into new execution contexts
This didn't make any sense, and was already handled by pushing a new
execution context anyway.

By simply removing these bogus lines of code, we fix a bug where
throwing inside a function whose bytecode was shorter than the calling
function would crash trying to generate an Error stack trace (because
the bytecode offset we were trying to symbolicate was actually from
the longer caller function, and not valid in the callee function.)

This makes --log-all-js-exceptions less crash prone and more helpful.
2024-09-08 11:33:50 +02:00
Andreas Kling
a3782782fa LibJS: Remove two unused members from ExecutionContext 2024-05-31 16:31:33 +02:00
Shannon Booth
f28bb90d9b LibJS: Remove non-spec compliant code from internal_construct
It seems that we are now spec compliant enough to be able to remove this
code block :^)

Diff Tests:
    +2     -2 
2024-05-26 12:29:41 +02:00
Aliaksandr Kalenik
f29ac8517e LibJS: Skip ordinary_call_bind_this() when possible
If during parsing it was found that function won't use `this` then
there is no need to initialise `this_value` during call.
2024-05-23 09:53:31 +02:00
Aliaksandr Kalenik
e934132442 LibJS+LibWeb: Pass function metadata collected in parsing using a struct
By using separate struct we can avoid updating AST node and
ECMAScriptFunctionObject constructors every time there is a need to
add or remove some additional information colllected during parsing.
2024-05-23 09:53:31 +02:00
Aliaksandr Kalenik
ebb3d8025c LibJS: Get this from execution context for non-arrow functions
Allows to skip function environment allocation for non-arrow functions
if the only reason it is needed is to hold `this` binding.

The parser is changed to do following:
- If a function is an arrow function and uses `this` then all functions
  in a scope chain are marked to allocate function environment for
  `this` binding.
- If a function uses `new.target` then all functions in a scope chain
  are marked to allocate function environment.

`ordinary_call_bind_this()` is changed to put `this` value in execution
context when function environment allocation is skipped.

35% improvement in Octane/typescript.js
50% improvement in Octane/deltablue.js
19% improvement in Octane/raytrace.js
2024-05-22 18:30:13 +02:00
Aliaksandr Kalenik
4d37b9e715 LibJS: Do not count arguments as env binding when local variable is used
This is a follow up for 210a5d77dc that
actually allows to skip function environment allocation.
2024-05-21 15:49:29 +00:00
Aliaksandr Kalenik
210a5d77dc LibJS: Use a local variable for arguments object when possible
This allows us to skip allocating a function environment in cases where
it was previously impossible because the arguments object needed a
binding.

This change does not bring visible improvement in Kraken or Octane
benchmarks but seems useful to have anyway.
2024-05-21 11:24:50 +02:00
Aliaksandr Kalenik
d79438a2a6 LibJS: Join locals, constants and registers into single vector
Merging registers, constants and locals into single vector means:
- Better data locality
- No need to check type in Interpreter::get() and Interpreter::set()
  which are very hot functions

Performance improvement is visible in almost all Octane and Kraken
tests.
2024-05-13 19:54:11 +02:00
Aliaksandr Kalenik
a4f70986a0 LibJS: Emit bytecode for function declaration instantiation
By doing that all instructions required for instantiation are emitted
once in compilation and then reused for subsequent calls, instead of
running generic instantiation process for each call.
2024-05-11 11:43:05 +02:00
Andreas Kling
ae11a4de1c LibJS: Remove unused target field from Completion
This shrinks Completion by 16 bytes, which has non-trivial impact
on performance.
2024-05-10 15:03:24 +00:00
Andreas Kling
f6aee2b9e8 LibJS/Bytecode: Flatten bytecode to a contiguous representation
Instead of keeping bytecode as a set of disjoint basic blocks on the
malloc heap, bytecode is now a contiguous sequence of bytes(!)

The transformation happens at the end of Bytecode::Generator::generate()
and the only really hairy part is rerouting jump labels.

This required solving a few problems:

- The interpreter execution loop had to change quite a bit, since we
  were storing BasicBlock pointers all over the place, and control
  transfer was done by redirecting the interpreter's current block.

- Exception handlers & finalizers are now stored per-bytecode-range
  in a side table in Executable.

- The interpreter now has a plain program counter instead of a stream
  iterator. This actually makes error stack generation a bit nicer
  since we just have to deal with a number instead of reaching into
  the iterator.

This yields a 25% performance improvement on this microbenchmark:

    for (let i = 0; i < 1_000_000; ++i) { }

But basically everything gets faster. :^)
2024-05-07 09:15:40 +02:00
Aliaksandr Kalenik
f21c0f9dcd LibJS: Skip some declarative env allocations in function instantiation
If all lexical declaration use local variables then there is no need
to allocate declarative environment.

With this change we skip ~3x more environment allocations on Github.
2024-05-06 15:09:20 +02:00
Andreas Kling
8ff16c1b57 LibJS: Cache access to properties found in prototype chain
We already had fast access to own properties via shape-based IC.
This patch extends the mechanism to properties on the prototype chain,
using the "validity cell" technique from V8.

- Prototype objects now have unique shape
- Each prototype has an associated PrototypeChainValidity
- When a prototype shape is mutated, every prototype shape "below" it
  in any prototype chain is invalidated.
- Invalidation happens by marking the validity object as invalid,
  and then replacing it with a new validity object.
- Property caches keep a pointer to the last seen valid validity.
  If there is no validity, or the validity is invalid, the cache
  misses and gets repopulated.

This is very helpful when using JavaScript to access DOM objects,
as we frequently have to traverse 4+ prototype objects before finding
the property we're interested in on e.g EventTarget or Node.
2024-05-04 21:42:59 +02:00
Aliaksandr Kalenik
4d5823a5bc LibWeb+LibJS: Skip function environment allocation if possible
If a function has the following properties:
- uses only local variables and registers
- does not use `this`
- does not use `new.target`
- does not use `super`
- does not use direct eval() calls

then it is possible to entirely skip function environment allocation
because it will never be used

This change adds gathering of information whether a function needs to
access `this` from environment and updates `prepare_for_ordinary_call()`
to skip allocation when possible.

For now, this optimisation is too aggressively blocked; e.g. if `this`
is used in a function scope, then all functions in outer scopes have to
allocate an environment. It could be improved in the future, although
this implementation already allows skipping >80% of environment
allocations on Discord, GitHub and Twitter.
2024-05-04 06:48:07 +02:00
Aliaksandr Kalenik
865e651a7d LibJS: Merge CallFrame into ExecutionContext
Before this change both ExecutionContext and CallFrame were created
before executing function/module/script with a couple exceptions:
- executable created for default function argument evaluation has to
  run in function's execution context.
- `execute_ast_node()` where executable compiled for ASTNode has to be
  executed in running execution context.

This change moves all members previously owned by CallFrame into
ExecutionContext, and makes two exceptions where an executable that does
not have a corresponding execution context saves and restores registers
before running.

Now, all execution state lives in a single entity, which makes it a bit
easier to reason about and opens opportunities for optimizations, such
as moving registers and local variables into a single array.
2024-05-02 07:26:13 +02:00
Andreas Kling
53d0dd4a2e LibJS+LibWeb: Use new Cell::Visitor helpers to avoid manual iteration 2024-04-16 07:40:01 +02:00
Andreas Kling
d8b7341050 LibJS: Add missing visit of ECMAScriptFunctionObject::m_name_string 2024-04-02 15:56:05 +02:00
Dan Klishch
8ac0e3f0e5 AK+LibJS: Remove null state from DeprecatedFlyString :^) 2024-02-24 15:06:52 -07:00
Andreas Kling
e46b217e42 LibJS/Bytecode: Move to a new bytecode format
This patch moves us away from the accumulator-based bytecode format to
one with explicit source and destination registers.

The new format has multiple benefits:

- ~25% faster on the Kraken and Octane benchmarks :^)
- Fewer instructions to accomplish the same thing
- Much easier for humans to read(!)

Because this change requires a fundamental shift in how bytecode is
generated, it is quite comprehensive.

Main implementation mechanism: generate_bytecode() virtual function now
takes an optional "preferred dst" operand, which allows callers to
communicate when they have an operand that would be optimal for the
result to go into. It also returns an optional "actual dst" operand,
which is where the completion value (if any) of the AST node is stored
after the node has "executed".

One thing of note that's new: because instructions can now take locals
as operands, this means we got rid of the GetLocal instruction.
A side-effect of that is we have to think about the temporal deadzone
(TDZ) a bit differently for locals (GetLocal would previously check
for empty values and interpret that as a TDZ access and throw).
We now insert special ThrowIfTDZ instructions in places where a local
access may be in the TDZ, to maintain the correct behavior.

There are a number of progressions and regressions from this test:

A number of async generator tests have been accidentally fixed while
converting the implementation to the new bytecode format. It didn't
seem useful to preserve bugs in the original code when converting it.

Some "does eval() return the correct completion value" tests have
regressed, in particular ones related to propagating the appropriate
completion after control flow statements like continue and break.
These are all fairly obscure issues, and I believe we can continue
working on them separately.

The net test262 result is a progression though. :^)
2024-02-19 21:45:27 +01:00
Andreas Kling
1a07205c33 LibJS: Don't reserve space in function environment for parameter locals
3% speed-up on Octane/typescript.js (but basically improves performance
on most JS.)
2024-01-21 21:03:59 +01:00
Andreas Kling
bed78eb3cc LibJS: Don't add uncaptured parameter bindings to environment
For parameters that exist strictly as "locals", we can save time and
space by not adding them to the function environment.

This is a speed-up across the board on basically every test.
For example, ~11% on Octane/typescript.js :^)
2024-01-21 21:03:59 +01:00
Ali Mohammad Pur
5e1499d104 Everywhere: Rename {Deprecated => Byte}String
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')
2023-12-17 18:25:10 +03:30
Andreas Kling
463931384d LibJS: Don't use Handle<Value> for JS::Object private fields
There's no reason to use handles here, we can just mark private element
values from objects that store them.
2023-12-10 09:44:26 +01:00
Andreas Kling
a2c3db8367 LibJS: Add basic support for module code with top-level await
For now, we handle this by creating a synthetic async function to wrap
the top-level module code. This allows us to piggyback on the async
function driver wrapper mechanism.
2023-12-06 12:58:04 +01:00
Andreas Kling
be8952b89d LibJS: Remove pointless Vector copy in FDI 2023-11-29 09:48:18 +01:00
Andreas Kling
aaa7b50f06 LibJS: Skip lexical declaration traversal in FDI if there are none 2023-11-29 09:48:18 +01:00
Andreas Kling
3b6785245f LibJS: Don't call shrink_to_fit() on same environment twice in FDI 2023-11-29 09:48:18 +01:00
Andreas Kling
3fc0333ee6 LibJS: Put Bytecode::CallFrame + register slots in a single allocation
The number of registers in a call frame never changes, so we can
allocate it at the end of the CallFrame object and save ourselves the
cost of allocating separate Vector storage for every call frame.
2023-11-29 09:48:18 +01:00
Andreas Kling
3dc5f467a8 LibJS: Always allocate ExecutionContext objects on the malloc heap
Instead of allocating these in a mixture of ways, we now always put
them on the malloc heap, and keep an intrusive linked list of them
that we can iterate for GC marking purposes.
2023-11-29 09:48:18 +01:00
Andreas Kling
845da3901d LibJS: Make ExecutionContext::function_name a GCPtr<PrimitiveString>
This required setting things up so that all function objects can plop
a PrimitiveString there instead of an AK string.

This is a step towards making ExecutionContext easier to allocate.
2023-11-29 09:48:18 +01:00
Andreas Kling
ecfcc9aef3 LibJS: Make Bytecode::Executable GC-allocated
This is a step towards making ExecutionContext easier to allocate.
2023-11-29 09:48:18 +01:00
Andreas Kling
ece961f882 LibJS: Pass call/construct argument lists as ReadonlySpan<Value>
(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.
2023-11-29 09:48:18 +01:00
Simon Wanner
ae8c98104a LibJS: Cache bytecode executables on the corresponding AST nodes
This greatly reduces the number of compilations necessary when functions
declaring local functions are re-executed.

For example Octane/typescript.js goes from 58080 bytecode executables
to 960.
2023-11-20 14:28:16 +01:00
Andreas Kling
3c74dc9f4d LibJS: Segregate GC-allocated objects by type
This patch adds two macros to declare per-type allocators:

- JS_DECLARE_ALLOCATOR(TypeName)
- JS_DEFINE_ALLOCATOR(TypeName)

When used, they add a type-specific CellAllocator that the Heap will
delegate allocation requests to.

The result of this is that GC objects of the same type always end up
within the same HeapBlock, drastically reducing the ability to perform
type confusion attacks.

It also improves HeapBlock utilization, since each block now has cells
sized exactly to the type used within that block. (Previously we only
had a handful of block sizes available, and most GC allocations ended
up with a large amount of slack in their tails.)

There is a small performance hit from this, but I'm sure we can make
up for it elsewhere.

Note that the old size-based allocators still exist, and we fall back
to them for any type that doesn't have its own CellAllocator.
2023-11-19 12:10:31 +01:00
Shannon Booth
6a2a7cad61 LibWeb/LibJS: Avoid GC visit of raw pointers where possible
This is mostly motivated for aesthetics, but also helps avoid some null
checks when we have a NonnullGCPtr<T> or in some cases a T&.
2023-11-19 08:05:45 +00:00
Andreas Kling
4f8e915ef8 LibJS: Remove accidentally-committed bytecode caching
This was a hack cache I got from Luke, not ready for prime-time :^)
2023-11-09 21:06:19 +01:00
Andreas Kling
b1b2ca1485 LibJS: Add basic monomorphic caching for PutById property access
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.
2023-11-09 16:02:14 +01:00
Aliaksandr Kalenik
42e9dfedc2 LibJS: Pre-calculate the number of bindings for function environments
We can use `ensure_capacity` for binding vectors if we know their sizes
in advance. This ensures that binding vectors aren't reallocated during
the `function_declaration_instantiation` execution.

With this change, `try_grow_capacity()` and `shrink_to_fit()` are no
longer visible in the `function_declaration_instantiation()` profiles
when running React-Redux-TodoMVC from Speedometer.
2023-09-28 15:33:43 +02:00
Aliaksandr Kalenik
98f479318a LibJS: Do not create environment bindings for local variables
If variable is local it is not stored in an environment so we don't
need a binding.
2023-09-28 15:33:43 +02:00
Andreas Kling
3ccac0cf6e LibJS: Remove unused realm parameter from run_and_return_frame() 2023-09-21 16:19:13 +02:00
Aliaksandr Kalenik
4ff4ac11b9 LibJS: Remove alreadyDeclared check in FunctionDeclarationInstantiation
We don't need to check if a function parameter is already declared
while creating bindings for them because we deduplicate their names by
storing them in a hash table in one of the previous steps.

This change makes React-Redux-TodoMVC test in Speedometer run 2%
faster.
2023-09-19 16:58:25 +02:00
Andreas Kling
e03f43e1b5 LibJS: Remove unused HashTable in function_declaration_instantiation() 2023-09-19 07:06:50 +02:00
Aliaksandr Kalenik
edd2f8b37f LibJS: Run reusable steps of FunctionDeclarationInstantiation only once
This change moves steps that can be executed only once and then reused
in subsequent function instantiations from
`function_declaration_instantiation` to the ECMAScriptFunctionObject:
- Determine if there are any parameters with duplicate names.
- Determine if there are any parameters with expressions.
- Determine if an arguments object needs to be created.
- Create a list of distinct function names for which bindings need to
  be created.
- Create a list of distinct variable names for which bindings need to
  be created.

This change makes React-Redux-TodoMVC test in Speedometer
run 10% faster :)
2023-09-19 06:54:10 +02:00
Aliaksandr Kalenik
a4a94de942 LibJS: Get initial_value from local variables if id represents a local
If identifier represents local variable we should get its value from
`local_variables` in `ExecutionContext` instead of environment.
2023-09-18 17:57:56 +02:00
Andreas Kling
1c06111cbd LibJS: Add file & line number to bytecode VM stack traces :^)
This works by adding source start/end offset to every bytecode
instruction. In the future we can make this more efficient by keeping
a map of bytecode ranges to source ranges in the Executable instead,
but let's just get traces working first.

Co-Authored-By: Andrew Kaster <akaster@serenityos.org>
2023-09-02 15:37:53 +02:00