Commit graph

781 commits

Author SHA1 Message Date
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
a77c6e15f4 LibJS/Bytecode: Streamline return/yield flow a bit in the interpreter 2024-05-10 15:03:24 +00:00
Andreas Kling
3e1a6fca91 LibJS/Bytecode: Remove exception checks from Return/Await/Yield
These instructions can't throw anyway.
2024-05-10 15:03:24 +00: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
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
601e10d50c LibJS/Bytecode: Make StringTableIndex be a 32-bit index
This makes a bunch of instructions smaller.
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
96511a7d19 LibJS/Bytecode: Avoid unnecessary copy of call expression callee
If the callee is already a temporary register, we don't need to copy it
to *another* temporary before evaluating arguments. None of the
arguments will clobber the existing temporary anyway.
2024-05-10 15:03:24 +00:00
Andreas Kling
56a3ccde1a LibJS/Bytecode: Turn UnaryMinus(NumericLiteral) into a constant 2024-05-10 15:03:24 +00:00
Andreas Kling
e37feaa196 LibJS/Bytecode: Skip unnecessary exception checks in interpreter
Many opcodes cannot ever throw an exception, so we can just avoid
checking it after executing them.
2024-05-10 15:03:24 +00:00
Andreas Kling
34f287087e LibJS/Bytecode: Only copy call/array expression arguments when needed
We only need to make copies of locals here, in case the locals are
modified by something like increment/decrement expressions.

Registers and constants can slip right through, without being Mov'ed
into a temporary first.
2024-05-10 15:03:24 +00:00
Andreas Kling
caf2e675bf LibJS/Bytecode: Don't emit GetGlobal undefined
We know that `undefined` in the global scope is always the proper
undefined value. This commit takes advantage of that by simply emitting
a constant undefined value instead.

Unfortunately we can't be so sure in other scopes.
2024-05-10 15:03:24 +00:00
Andreas Kling
448f837d38 LibJS/Bytecode: Round constant operands of bitwise binary expressions
This helps some of the Cloudflare Turnstile stuff run faster, since they
are deliberately screwing with JS engines by asking us to do a bunch of
bitwise operations on e.g 65535.56

By rounding such values in bytecode generation, the interpreter can stay
on the happy path while executing, and finish quite a bit faster.
2024-05-10 15:03:24 +00:00
Andreas Kling
ca8dc8f61f LibJS/Bytecode: Turn JumpIf true/false into unconditional Jump 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
f164e18a55 LibJS/Bytecode: Bunch all tests together in switch statement codegen
Before this change, switch codegen would interleave bytecode like this:

    (test for case 1)
    (code for case 1)
    (test for case 2)
    (code for case 2)

This meant that we often had to make many large jumps while looking for
the matching case, since code for each case can be huge.

It now looks like this instead:

    (test for case 1)
    (test for case 2)
    (code for case 1)
    (code for case 2)

This way, we can just fall through the tests until we hit one that fits,
without having to make any large jumps.
2024-05-09 09:12:13 +02:00
Andreas Kling
18b8fae85c LibJS/Bytecode: Remove pointless basic block in SwitchStatement codegen 2024-05-09 09:12:13 +02: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
Andreas Kling
f537d0b3cf LibJS/Bytecode: Allow all basic blocks to use cached this from block 0
The first block in every executable will always execute first, so if it
ends up doing a ResolveThisBinding, it's fine for all other blocks
within the same executable to use the same `this` value.
2024-05-09 09:12:13 +02:00
Andreas Kling
161298b5d1 LibJS/Bytecode: Inline indexed property access in GetByVal better 2024-05-09 09:12:13 +02:00
Andreas Kling
0f70ff9a67 LibJS/Bytecode: Only emit ResolveThisBinding once per basic block
Once executed, this instruction will always produce the same result
in subsequent executions, so it's okay to cache it.

Unfortunately it may throw, so we can't just hoist it to the top of
every executable, since that would break observable execution order.
2024-05-09 09:12:13 +02:00
Hendiadyoin1
e1aecff2ab LibJS: Fail compilation earlier when trying to emit with wrong arguments
This makes the compilation backtraces a lot nicer, and allows clangd to
see the mistake as well.
2024-05-08 22:01:58 +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
a020a0779d LibJS/Bytecode: Do a stack check when entering run_bytecode()
If we don't have enough stack space, throw an exception while we still
can, and give the caller a chance to recover.

This particular problem will go away once we make calls non-recursive.
2024-05-07 09:15:40 +02:00
Andreas Kling
7b93b8cea7 LibJS/Bytecode: Flatten the interpreter main loop (Clang only)
This means inlining all the things. This yields a 40% speedup on the for
loop microbenchmark, and everything else gets faster as well. :^)

This makes compilation take foreeeever with GCC, so I'm only enabling it
for Clang in this commit. We should figure out how to make GCC compile
this without timing out CI, since the speedup is amazing.
2024-05-07 09:15:40 +02:00
Andreas Kling
f4af056aa9 LibJS/Bytecode: Thread the bytecode interpreter
This commit converts the main loop in Bytecode::Interpreter to use a
label table and computed goto for fast instruction dispatch.

This yields roughly 35% speedup on the for loop microbenchmark,
and makes everything else faster as well. :^)
2024-05-07 09:15:40 +02:00
Andreas Kling
b45f55b199 LibJS/Bytecode: Fix wonky serialization of instruction value lists 2024-05-07 09:15:40 +02:00
Andreas Kling
9cbf17f181 LibJS/Bytecode: Emit do...while body before test in codegen
This makes the code flow naturally and allows jump elision to work.
2024-05-07 09:15:40 +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
37d722f4a6 LibJS/Bytecode: Make IdentifierTableIndex a 32-bit index
This makes a bunch of instructions smaller.
2024-05-07 09:15:40 +02:00
Andreas Kling
95759dcc6d LibJS/Bytecode: Put end block last in for statement codegen 2024-05-07 09:15:40 +02:00
Andreas Kling
f3d57db774 LibJS/Bytecode: Stop emitting unnecessary jump at end of for statement 2024-05-07 09:15:40 +02:00
Andreas Kling
69cc64e94c LibJS/Bytecode: Emit for condition check before update statement
This allows jump elision to eliminate an unnecessary jump since things
now get laid out naturally in memory (normal execution order).
2024-05-07 09:15:40 +02:00
Andreas Kling
24d8b056c7 LibJS/Bytecode: Elide jumps that land in the very next block
Jumping to the next block is effectively a no-op, so let's save time and
space by just not emitting those jumps.
2024-05-07 09:15:40 +02:00
Andreas Kling
3a73eb99ac LibJS/Bytecode: Store labels as basic block index during compilation
Instead of storing a BasicBlock* and forcing the size of Label to be
sizeof(BasicBlock*), we now store the basic block index as a u32.

This means the final version of the bytecode is able to keep labels
at sizeof(u32), shrinking the size of many instructions. :^)
2024-05-07 09:15:40 +02:00
Andreas Kling
5a08544138 LibJS/Bytecode: Keep instruction source mappings in Executable
Instead of storing source offsets with each instruction, we now keep
them in a side table in Executable.

This shrinks each instruction by 8 bytes, further improving locality.
2024-05-07 09:15:40 +02:00
Andreas Kling
4cf4ea92a7 LibJS/Bytecode: Store Instruction length as u32
This makes every instruction 8 bytes smaller.
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
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
Andreas Kling
5cb127819c LibJS: Fix build after merging CallFrame removal and finally fixes 2024-05-02 07:42:09 +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
27b238d9af LibJS: Stop swallowing exceptions in finalizers
This also fixes one of the try-catch-finally tests, and adds a new one.
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
Hendiadyoin1
301a1fc763 LibJS: Propagate finalizers into nested try-catch blocks without them 2024-05-02 07:27:45 +02:00