Commit graph

226 commits

Author SHA1 Message Date
Andreas Kling
60a05ef414 LibJS/Bytecode: Give TypeofBinding instructions a lookup cache
These can use an EnvironmentCoordinate for caching, just like normal
binding lookups. Saves a bunch of time for repeated typeof checks.
2024-06-14 16:08:02 +02:00
Andreas Kling
4302e07346 LibJS/Bytecode: Rename TypeofVariable => TypeofBinding 2024-06-14 16:08:02 +02:00
Andreas Kling
507f83a615 LibJS/Bytecode: Always resolve this binding into dedicated register
We already have a dedicated register slot for `this`, so instead of
having ResolveThisBinding take a `dst` operand, just write the value
directly into the `this` register every time.
2024-06-01 09:39:50 +02: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
Andreas Kling
448b7ca87b LibJS/Bytecode: Add dedicated instruction for getting length property
By doing this, we can remove all the special-case checks for `length`
from the generic GetById and GetByIdWithThis code paths, making every
other property lookup a bit faster.

Small progressions on most benchmarks, and some larger progressions:

- 12% on Octane/crypto.js
- 6% on Kraken/ai-astar.js
2024-05-20 12:51:56 +02:00
Hendiadyoin1
1de475b404 LibJS: Prepare yield object before re-routing it through finally 2024-05-18 18:11:10 +02:00
Hendiadyoin1
73fdd31124 LibJS: Avoid returning Completions from more Bytecode instructions 2024-05-15 04:25:14 +02:00
Andreas Kling
d22a06d671 LibJS/Bytecode: Remove all the unreachable execute_impl() functions
There are no calls to these anywhere anymore.
2024-05-14 21:46:36 +02:00
Andreas Kling
6ca94bd0b1 LibJS/Bytecode: Rename GetVariable => GetBinding 2024-05-14 21:46:36 +02:00
Andreas Kling
b7c04f999a LibJS/Bytecode: Split SetVariable into four separate instructions
Instead of SetVariable having 2x2 modes for variable/lexical and
initialize/set, those 4 modes are now separate instructions, which
makes each instruction much less branchy.
2024-05-14 21:46:36 +02:00
Aliaksandr Kalenik
caffd485b8 LibJS: Replace SetLocal instruction usage with Mov
No need for separate instuction to set a local.
2024-05-14 06:39:16 +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
d79353a477 LibJS/Bytecode: Add fast paths for compare-and-jump with 2 numbers
When comparing two numbers, we can avoid a lot of implicit type
conversion nonsense and go straight to comparison, saving time in the
most common case.
2024-05-13 17:29:37 +02:00
Andreas Kling
855f6417df LibJS/Bytecode: Move environment variable caches into instructions
These were out-of-line because we had some ideas about marking
instruction streams PROT_READ only, but that seems pretty arbitrary and
there's a lot of performance to be gained by putting these inline.
2024-05-13 09:22:14 +02:00
Aliaksandr Kalenik
6fb1d9e516 LibJS: Stop using execute_ast_node() for class property evaluation
Instead, generate bytecode to execute their AST nodes and save the
resulting operands inside the NewClass instruction.

Moving property expression evaluation to happen before NewClass
execution also moves along creation of new private environment and
its population with private members (private members should be visible
during property evaluation).

Before:
- NewClass

After:
- CreatePrivateEnvironment
- AddPrivateName
- ...
- AddPrivateName
- NewClass
- LeavePrivateEnvironment
2024-05-12 19:10:25 +02:00
Aliaksandr Kalenik
3d4b13a01c LibJS: Ensure capacity for created lexical and variable environments
If the minimal amount of required bindings is known in advance, it could
be used to ensure capacity to avoid resizing the internal vector that
holds bindings.
2024-05-11 11:43:05 +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
Aliaksandr Kalenik
89a007327a LibJS: Change NewFunction instruction to accept FunctionNode
Preparation for upcoming changes where NewFunction will have to be used
with FunctionDeclaration node.
2024-05-11 11:43:05 +02:00
Andreas Kling
7bdc207d81 LibJS/Bytecode: Make execute_impl() return void for non-throwing ops
This way we can't accidentally ignore exceptions.
2024-05-10 19:53:15 +02:00
Andreas Kling
810a297626 LibJS/Bytecode: Remove Instruction::execute()
Just make sure everyone calls the instruction-specific execute_impl()
instead. :^)
2024-05-10 15:03:24 +00:00
Andreas Kling
b99f0a7e22 LibJS/Bytecode: Reorder Call instruction members to make it smaller 2024-05-10 15:03:24 +00:00
Andreas Kling
7654da3851 LibJS/Bytecode: Do basic compare-and-jump peephole optimization
We now fuse sequences like [LessThan, JumpIf] to JumpLessThan.
This is only allowed for temporaries (i.e VM registers) with no other
references to them.
2024-05-10 15:03:24 +00:00
Andreas Kling
6873628317 LibJS/Bytecode: Make NewArray a variable-length instruction
This removes a layer of indirection in the bytecode where we had to make
sure all the initializer elements were laid out in sequential registers.

Array expressions no longer clobber registers permanently, and they can
be reused immediately afterwards.
2024-05-09 09:12:13 +02:00
Andreas Kling
cea59b6642 LibJS/Bytecode: Reuse bytecode registers
This patch adds a register freelist to Bytecode::Generator and switches
all operands inside the generator to a new ScopedOperand type that is
ref-counted and automatically frees the register when nothing uses it.

This dramatically reduces the size of bytecode executable register
windows, which were often in the several thousands of registers for
large functions. Most functions now use less than 100 registers.
2024-05-09 09:12:13 +02:00
Hendiadyoin1
af94e4c05d LibJS: Save and restore exceptions on yields in finalizers
Also removes a bunch of related old FIXMEs.
2024-05-08 22:01:58 +02:00
Andreas Kling
68507b7e55 LibJS/Bytecode: Store SetLocal's local index as a u32
Same size as local indexes everywhere else.
2024-05-07 09:15:40 +02:00
Andreas Kling
e43d96f310 LibJS/Bytecode: Remove Instruction::m_length field
Now that the interpreter is unrolled, we can advance the program counter
manually based on the current instruction type.

This makes most instructions a bit smaller. :^)
2024-05-07 09:15:40 +02:00
Andreas Kling
ce93000757 LibJS/Bytecode: Unroll the bytecode interpreter
This commit adds a HANDLE_INSTRUCTION macro that expands to everything
needed to handle a single instruction (invoking the handler function,
checking for exceptions, and advancing the program counter).

This gives a ~15% speed-up on a for loop microbenchmark, and makes
basically everything faster.
2024-05-07 09:15:40 +02:00
Andreas Kling
fae1527a18 LibJS/Bytecode: Turn JumpIf condition,@a,@next into JumpTrue/JumpFalse
If one of the jump targets is the very next block, we can convert the
jump instruction into a smaller JumpTrue or JumpFalse.
2024-05-07 09:15:40 +02: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
Andreas Kling
c2d3d9d1d4 LibJS/Bytecode: Make each Jump instruction inherit Instruction directly
Before this change, all JumpFoo instructions inherited from Jump, which
forced the unconditional Jump to have an unusued "false target" member.
Also, labels were unnecessarily wrapped in Optional<>.

By defining each jump instruction separately, they all shrink in size,
and all ambiguity is removed.
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
Timothy Flynn
22fdcfbc50 LibJS: Include identifier information in nullish property write access
When a PutById / PutByValue bytecode operation results in accessing a
nullish object, we now include the name of the property and the object
being accessed in the exception message (if available). This should make
it easier to debug live websites.

For example, the following errors would all previously produce a generic
error message of "ToObject on null or undefined":

  > foo = null
  > foo.bar = 1
  Uncaught exception:
  [TypeError] Cannot access property "bar" on null object "foo"
      at <unknown>

  > foo = { bar: undefined }
  > foo.bar.baz = 1
  Uncaught exception:
  [TypeError] Cannot access property "baz" on undefined object "foo.bar"
      at <unknown>

Note we certainly don't capture all possible nullish property write
accesses here. This just covers cases I've seen most on live websites;
we can cover more cases as they arise.
2024-03-29 21:57:19 +01:00
Timothy Flynn
9bbd3103a8 LibJS: Include identifier information in nullish property read access
When a GetById / GetByValue bytecode operation results in accessing a
nullish object, we now include the name of the property and the object
being accessed in the exception message (if available). This should make
it easier to debug live websites.

For example, the following errors would all previously produce a generic
error message of "ToObject on null or undefined":

  > foo = null
  > foo.bar
  Uncaught exception:
  [TypeError] Cannot access property "bar" on null object "foo"
      at <unknown>

  > foo = { bar: undefined }
  > foo.bar.baz
  Uncaught exception:
  [TypeError] Cannot access property "baz" on undefined object "foo.bar"
      at <unknown>

Note we certainly don't capture all possible nullish property read
accesses here. This just covers cases I've seen most on live websites;
we can cover more cases as they arise.
2024-03-29 21:57:19 +01: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
cf81bf48c6 Revert "LibJS/Bytecode: Add peephole optimization pass and fuse compare+jump"
This reverts commit 4438ec481c.

Fixes #23480.
2024-03-06 08:39:29 +01:00
Andreas Kling
c4a0afbe28 Revert "LibJS/Bytecode: Fuse [Not, JumpIf] instructions into JumpIfNot"
This reverts commit 795149e585.
2024-03-06 08:39:29 +01:00
Andreas Kling
a5e1e66abc LibJS/Bytecode: Add fast path for LeftShift with Int32 operands 2024-03-04 20:54:51 +01:00
Andreas Kling
795149e585 LibJS/Bytecode: Fuse [Not, JumpIf] instructions into JumpIfNot 2024-03-04 20:54:51 +01:00
Andreas Kling
4438ec481c LibJS/Bytecode: Add peephole optimization pass and fuse compare+jump
This patch adds a new "Peephole" pass for performing small, local
optimizations to bytecode.

We also introduce the first such optimization, fusing a sequence of
some comparison instruction FooCompare followed by a JumpIf into a
new set of JumpFooCompare instructions.

This gives a ~50% speed-up on the following microbenchmark:

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

But more traditional benchmarks see a pretty sizable speed-up as well,
for example 15% on Kraken/ai-astar.js and 16% on Kraken/audio-dft.js :^)
2024-03-04 20:54:51 +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
60a555e364 LibJS/Bytecode: Make NewPrimitiveArray a variable-length instruction
Instead of having a FixedArray with a separate heap allocation, we can
just bake the primitive values into the instruction itself.
2024-03-03 22:27:44 +01:00
Andreas Kling
5813df21c8 LibJS/Bytecode: Make primitive bigints be constants
Instead of emitting a NewBigInt instruction to construct a primitive
bigint from a parsed literal, we now instantiate the BigInt on the heap
during codegen.
2024-03-03 22:27:44 +01:00
Andreas Kling
46d209c55b LibJS/Bytecode: Make primitive strings be constants
Instead of emitting a NewString instruction to construct a primitive
string from a parsed literal, we now instantiate the PrimitiveString on
the heap during codegen.
2024-03-03 22:27:44 +01:00
Andreas Kling
9a0a5a79f4 LibJS/Bytecode: Put arguments directly in the Call instruction
Instead of having Call refer to a range of VM registers, it now has
a trailing list of argument operands as part of the instruction.

This means we no longer have to shuffle every argument value into
a register before making a call, making bytecode smaller & faster. :^)
2024-02-20 21:25:18 +01:00
Andreas Kling
da107ec9fb LibJS/Bytecode: Add fast paths for many binary expression instructions
By handling common cases like Int32 arithmetic directly in the
instruction handler, we can avoid the cost of calling the generic helper
functions in Value.cpp.
2024-02-20 21:25:18 +01:00
Andreas Kling
9d9b737a58 LibJS/Bytecode: Dedicated instructions for postfix increment/decrement
Instead of splitting the postfix variants into ToNumeric + Inc/Dec,
we now have dedicated PostfixIncrement and PostfixDecrement instructions
that handle both outputs in one go.
2024-02-20 21:25:18 +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
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