Commit graph

80 commits

Author SHA1 Message Date
Hendiadyoin1
1de475b404 LibJS: Prepare yield object before re-routing it through finally 2024-05-18 18:11:10 +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
Andreas Kling
6ec4d6f668 LibJS/Bytecode: Cache the running execution context in interpreter 2024-05-13 09:22:14 +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
8eccfdb98c LibJS/Bytecode: Cache a base pointer to executable constants
Instead of fetching it from the current executable whenever we fetch a
constant, just keep a base pointer to the constants array handy.
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
Hendiadyoin1
ada5027163 LibJS: Cleanup unwind state when transferring control out of a finalizer
This does two things:
* Clear exceptions when transferring control out of a finalizer
  Otherwise they would resurface at the end of the next finalizer
  (see test the new test case), or at the end of a function
* Pop one scheduled jump when transferring control out of a finalizer
  This removes one old FIXME
2024-05-02 07:27:45 +02:00
Hendiadyoin1
b4b9c4b383 LibJS: Restore scheduled jumps in catch blocks without finalizers 2024-05-02 07:27:45 +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
Matthew Olsson
8b8ada292e LibJS: Fix some GCVerifier warnings 2024-04-07 07:03:13 +02:00
Andreas Kling
5b69413c4b Revert "LibJS/Bytecode: Bring back the bytecode optimization pipeline"
This reverts commit 5b29974bfa.
2024-03-06 08:39:29 +01:00
Andreas Kling
5b29974bfa LibJS/Bytecode: Bring back the bytecode optimization pipeline
...minus the EliminateLoads pass, since it was not compatible with the
new bytecode format.
2024-03-04 20:54:51 +01:00
Andreas Kling
953573565c LibJS/Bytecode: Cache realm, global object, and more in interpreter
Instead of looking these up in the VM execution context stack whenever
we need them, we now just cache them in the interpreter when entering
a new call frame.
2024-02-28 21:09:09 +01: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
3466771492 LibJS/Bytecode: Add Bytecode::Operand
An Operand is either a register, a local, or a constant (index into the
executable's constant table)
2024-02-19 21:45:27 +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
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
cb7169d73f LibJS/JIT: Support the EnterObjectEnvironment bytecode instruction
We can now use `with` statements in jitted code. :^)
2023-11-12 14:21:41 +01:00
Andreas Kling
cfdb8a2756 LibJS/JIT: Update "unwind context" stack in JIT code
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.
2023-11-12 14:21:41 +01:00
Simon Wanner
68f4d21de2 LibJS: Lazily collect stack trace information
The previous implementation was calling `backtrace()` for every
function call, which is quite slow.

Instead, this implementation provides VM::stack_trace() which unwinds
the native stack, maps it through NativeExecutable::get_source_range
and combines it with source ranges from interpreted call frames.
2023-11-02 07:37:41 +01:00
Simon Wanner
fb7b4b9c59 LibJS/JIT: Provide source location information for JIT code
This works by walking a backtrace until the currently executing
native executable is found, and then mapping the native address
to its bytecode instruction.
2023-10-31 07:07:17 +01:00
Hendiadyoin1
1341f4438d LibJS: Save scheduled jumps when entering unwind contexts
These are then restored upon `ContinuePendingUnwind`.
This stops us from forgetting where we needed to jump when we do extra
try-catches in finally blocks.

Co-Authored-By: Jesús "gsus" Lapastora <cyber.gsuscode@gmail.com>
2023-10-30 13:10:08 +01:00
Hendiadyoin1
f5645e3c9c LibJS: Use static unwind mappings for unwind related functions 2023-10-30 13:10:08 +01:00
Andreas Kling
babdc0a25b LibJS: Work-in-progress JIT compiler :^) 2023-10-27 19:07:22 +02:00
Andreas Kling
c14db6ab12 LibJS: Make Executable ref-counted and let instruction iterator co-own it
This ensures that the instruction stream pointed at by the instruction
iterator remains valid as long as the iterator exists.
2023-10-03 08:23:33 +02:00
Andreas Kling
e5474c384d LibJS: Inline flow control ops in the bytecode interpreter loop
Instead of calling out to helper functions for flow control (and then
checking control flags on every iteration), we now simply inline those
ops in the interpreter loop directly.
2023-09-28 06:09:16 +02:00
Andreas Kling
9fe38245b2 LibJS: Move bytecode interpreter's inner loop to its own function 2023-09-28 06:09:16 +02:00
Andreas Kling
213b835b57 LibJS: Remove Bytecode::Interpreter::debug_position()
This was only used in one place, and that place is already covered
by a VERIFY anyway.
2023-09-28 06:09:16 +02:00
Andreas Kling
0c746366cc LibJS: Keep return value in a call frame register 2023-09-28 06:09:16 +02:00
Andreas Kling
c833885fb5 LibJS: Keep cached this value in a call frame register
Just moving more things to call frame registers..
2023-09-28 06:09:16 +02:00
Andreas Kling
3887b840a3 LibJS: Keep current exception in a call frame register
Instead of keeping it in a Bytecode::Interpreter member, move it into
a dedicated call frame register.
2023-09-28 06:09:16 +02:00
Andreas Kling
3ccac0cf6e LibJS: Remove unused realm parameter from run_and_return_frame() 2023-09-21 16:19:13 +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
Andreas Kling
e2c8d5859e LibJS: Remove ability to disable bytecode VM 2023-08-08 13:07:13 +02:00
Andreas Kling
d1d24569f8 LibJS/Bytecode: Remove unused AST interpreter hacks from bytecode VM 2023-08-08 13:07:13 +02:00
Aliaksandr Kalenik
2bdc69c42c LibJS: Add caching of this value in ResolveThisBinding instruction
Because "this" value cannot be changed during function execution it is
safe to compute it once and then use for future access.

This optimization makes ai-astar.js run 8% faster.
2023-07-29 04:23:49 +02:00
Andreas Kling
9f06e130a2 LibJS/Bytecode: Keep saved return value in call frame register
This fixes an issue where returning inside a `try` block and then
calling a function inside `finally` would clobber the saved return
value from the `try` block.

Note that we didn't need to change the base of register allocation,
since it was already 1 too high.

With this fixed, https://microsoft.com/edge loads in bytecode mode. :^)

Thanks to Luke for reducing the issue!
2023-07-21 19:15:33 +02:00
Timothy Flynn
77d7f715e3 LibJS+CI: Remove bytecode optimization passes for now
These passes have not been shown to actually optimize any JS, and tests
have become very flaky with optimizations enabled. Until some measurable
benefit is shown, remove the optimization passes to reduce overhead of
maintaining bytecode operations and to reduce CI churn. The framework
for optimizations will live on in git history, and can be restored once
proven useful.
2023-07-21 19:47:36 +03:30
Andreas Kling
6de22ec789 LibJS/Bytecode: Rename RegisterWindow to CallFrame
This is a better name for what it actually represents.
2023-07-21 14:14:00 +02:00
Andreas Kling
de8e4b1853 LibJS/Bytecode: Cache object own property accesses
The instructions GetById and GetByIdWithThis now remember the last-seen
Shape, and if we see the same object again, we reuse the property offset
from last time without doing a new lookup.

This allows us to use Object::get_direct(), bypassing the entire lookup
machinery and saving lots of time.

~23% speed-up on Kraken/ai-astar.js :^)
2023-07-09 12:54:06 +02:00
Andreas Kling
dc884aa0d3 LibJS/Bytecode: Remove unnecessary variable environment stack
The var environments will unwind as needed with the ExecutionContext
and there's no need to include it in the unwind info.

We still need to do this for lexical environments though, since they
can have short local lifetimes inside a function.
2023-07-02 18:30:02 +02:00
Andreas Kling
fb979dcf34 LibJS/Bytecode: Make Bytecode::Interpreter participate in GC marking
Since the relationship between VM and Bytecode::Interpreter is now
clear, we can have VM ask the Interpreter for roots in the GC marking
pass. This avoids having to register and unregister handles and
MarkedVectors over and over.

Since GeneratorObject can also own a RegisterWindow, we share the code
in a RegisterWindow::visit_edges() helper.

~4% speed-up on Kraken/stanford-crypto-ccm.js :^)
2023-07-02 14:50:02 +02:00
Andreas Kling
1efe4b58aa LibJS/Bytecode: Store current interpreter register window as a Span
This avoids a bunch of indirections on every single register access.

~17% speed-up on Kraken/stanford-crypto-aes.js :^)
2023-07-01 20:29:12 +01:00
Daniel Bertalan
cc9ec6693b LibJS: Remove the concept of bytecode optimization levels
While this would be useful in the future for implementing a multi-tiered
optimization strategy, currently a binary on/off is enough for us. This
removes the confusingly on-by-default `OptimizationLevel::None` option
which made the optimization pipeline a no-op even if
`Bytecode::Interpreter::set_optimizations_enabled` had been called.

Fixes #15982
2023-06-27 14:35:23 +03:30
Daniel Bertalan
e012565898 test262-runner+js: Respect the bytecode optimizations enabled flag 2023-06-27 14:35:23 +03:30
Andreas Kling
6537ed8fff LibJS/Bytecode: Simplify Bytecode::Interpreter lifetime model
The JS::VM now owns the one Bytecode::Interpreter. We no longer have
multiple bytecode interpreters, and there is no concept of a "current"
bytecode interpreter.

If you ask for VM::bytecode_interpreter_if_exists(), it will return null
if we're not running the program in "bytecode enabled" mode.

If you ask for VM::bytecode_interpreter(), it will return a bytecode
interpreter in all modes. This is used for situations where even the AST
interpreter switches to bytecode mode (generators, etc.)
2023-06-22 21:16:17 +02:00
Andreas Kling
9c568282dc Ladybird+LibJS: Add CLI option to run browser with LibJS bytecode VM
This required quite a bit of plumbing, but now you can run

    ladybird --use-bytecode
2023-06-17 14:16:45 +02:00
Andreas Kling
d063f35afd LibJS/Bytecode: Leave GlobalDeclarationInstantiation in C++
Don't try to implement this AO in bytecode. Instead, the bytecode
Interpreter class now has a run() API with the same inputs as the AST
interpreter. It sets up the necessary environments etc, including
invoking the GlobalDeclarationInstantiation AO.
2023-06-16 08:40:45 +02:00
Matthew Olsson
7c0c1c8f49 LibJS+LibWeb: Wrap raw JS::Cell*/& fields in GCPtr/NonnullGCPtr 2023-03-15 08:48:49 +01:00