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. :^)
This commit is contained in:
Andreas Kling
2024-05-06 06:44:08 +02:00
parent c2d3d9d1d4
commit f6aee2b9e8
21 changed files with 392 additions and 172 deletions

View File

@@ -161,12 +161,14 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
return value.is_empty() ? js_undefined() : value;
};
auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* {
auto generated_continuation = [&](Value value) -> Optional<size_t> {
if (value.is_object()) {
auto number_value = value.as_object().get_without_side_effects("continuation");
return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(number_value.as_double()));
if (number_value.is_null())
return {};
return static_cast<size_t>(number_value.as_double());
}
return nullptr;
return {};
};
auto generated_is_await = [](Value value) -> bool {
@@ -182,16 +184,12 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
auto& bytecode_interpreter = vm.bytecode_interpreter();
auto const* next_block = generated_continuation(m_previous_value);
auto const continuation_address = generated_continuation(m_previous_value);
// We should never enter `execute` again after the generator is complete.
VERIFY(next_block);
VERIFY(continuation_address.has_value());
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
bytecode_interpreter.accumulator() = completion_object;
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block);
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), continuation_address, completion_object);
auto result_value = move(next_result.value);
if (!result_value.is_throw_completion()) {
@@ -209,7 +207,7 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
}
}
bool done = result_value.is_throw_completion() || generated_continuation(m_previous_value) == nullptr;
bool done = result_value.is_throw_completion() || !generated_continuation(m_previous_value).has_value();
if (!done) {
// 27.6.3.8 AsyncGeneratorYield ( value ), https://tc39.es/ecma262/#sec-asyncgeneratoryield
// 1. Let genContext be the running execution context.