mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-28 20:29:03 +00:00
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. :^)
This commit is contained in:
parent
b45f55b199
commit
f4af056aa9
Notes:
sideshowbarker
2024-07-17 17:06:59 +09:00
Author: https://github.com/awesomekling
Commit: f4af056aa9
Pull-request: https://github.com/SerenityOS/serenity/pull/24240
Reviewed-by: https://github.com/Hendiadyoin1
Reviewed-by: https://github.com/trflynn89 ✅
1 changed files with 268 additions and 211 deletions
|
@ -342,76 +342,111 @@ void Interpreter::run_bytecode(size_t entry_point)
|
||||||
|
|
||||||
TemporaryChange change(m_program_counter, Optional<size_t&>(program_counter));
|
TemporaryChange change(m_program_counter, Optional<size_t&>(program_counter));
|
||||||
|
|
||||||
|
// Declare a lookup table for computed goto with each of the `handle_*` labels
|
||||||
|
// to avoid the overhead of a switch statement.
|
||||||
|
// This is a GCC extension, but it's also supported by Clang.
|
||||||
|
|
||||||
|
static void* const bytecode_dispatch_table[] = {
|
||||||
|
#define SET_UP_LABEL(name) &&handle_##name,
|
||||||
|
ENUMERATE_BYTECODE_OPS(SET_UP_LABEL)
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DISPATCH_NEXT(name) \
|
||||||
|
do { \
|
||||||
|
if constexpr (Op::name::IsVariableLength) \
|
||||||
|
program_counter += instruction.length(); \
|
||||||
|
else \
|
||||||
|
program_counter += sizeof(Op::name); \
|
||||||
|
auto& next_instruction = *reinterpret_cast<Instruction const*>(&bytecode[program_counter]); \
|
||||||
|
goto* bytecode_dispatch_table[static_cast<size_t>(next_instruction.type())]; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
start:
|
start:
|
||||||
bool will_return = false;
|
bool will_return = false;
|
||||||
bool will_yield = false;
|
bool will_yield = false;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto& instruction = *reinterpret_cast<Instruction const*>(&bytecode[program_counter]);
|
goto* bytecode_dispatch_table[static_cast<size_t>((*reinterpret_cast<Instruction const*>(&bytecode[program_counter])).type())];
|
||||||
|
|
||||||
switch (instruction.type()) {
|
handle_SetLocal: {
|
||||||
case Instruction::Type::SetLocal:
|
auto& instruction = *reinterpret_cast<Op::SetLocal const*>(&bytecode[program_counter]);
|
||||||
locals[static_cast<Op::SetLocal const&>(instruction).index()] = get(static_cast<Op::SetLocal const&>(instruction).src());
|
locals[instruction.index()] = get(instruction.src());
|
||||||
program_counter += sizeof(Op::SetLocal);
|
DISPATCH_NEXT(SetLocal);
|
||||||
goto start;
|
}
|
||||||
case Instruction::Type::Mov:
|
|
||||||
set(static_cast<Op::Mov const&>(instruction).dst(), get(static_cast<Op::Mov const&>(instruction).src()));
|
handle_Mov: {
|
||||||
program_counter += sizeof(Op::Mov);
|
auto& instruction = *reinterpret_cast<Op::Mov const*>(&bytecode[program_counter]);
|
||||||
goto start;
|
set(instruction.dst(), get(instruction.src()));
|
||||||
case Instruction::Type::End:
|
DISPATCH_NEXT(Mov);
|
||||||
accumulator = get(static_cast<Op::End const&>(instruction).value());
|
}
|
||||||
|
|
||||||
|
handle_End: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::End const*>(&bytecode[program_counter]);
|
||||||
|
accumulator = get(instruction.value());
|
||||||
return;
|
return;
|
||||||
case Instruction::Type::Jump:
|
}
|
||||||
program_counter = static_cast<Op::Jump const&>(instruction).target().address();
|
|
||||||
|
handle_Jump: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::Jump const*>(&bytecode[program_counter]);
|
||||||
|
program_counter = instruction.target().address();
|
||||||
goto start;
|
goto start;
|
||||||
case Instruction::Type::JumpIf: {
|
}
|
||||||
auto& jump = static_cast<Op::JumpIf const&>(instruction);
|
|
||||||
if (get(jump.condition()).to_boolean())
|
handle_JumpIf: {
|
||||||
program_counter = jump.true_target().address();
|
auto& instruction = *reinterpret_cast<Op::JumpIf const*>(&bytecode[program_counter]);
|
||||||
|
if (get(instruction.condition()).to_boolean())
|
||||||
|
program_counter = instruction.true_target().address();
|
||||||
else
|
else
|
||||||
program_counter = jump.false_target().address();
|
program_counter = instruction.false_target().address();
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
case Instruction::Type::JumpTrue: {
|
|
||||||
auto& jump = static_cast<Op::JumpTrue const&>(instruction);
|
handle_JumpTrue: {
|
||||||
if (get(jump.condition()).to_boolean()) {
|
auto& instruction = *reinterpret_cast<Op::JumpTrue const*>(&bytecode[program_counter]);
|
||||||
program_counter = jump.target().address();
|
if (get(instruction.condition()).to_boolean()) {
|
||||||
|
program_counter = instruction.target().address();
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
program_counter += sizeof(Op::JumpTrue);
|
DISPATCH_NEXT(JumpTrue);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_JumpFalse: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::JumpFalse const*>(&bytecode[program_counter]);
|
||||||
|
if (!get(instruction.condition()).to_boolean()) {
|
||||||
|
program_counter = instruction.target().address();
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
case Instruction::Type::JumpFalse: {
|
DISPATCH_NEXT(JumpFalse);
|
||||||
auto& jump = static_cast<Op::JumpFalse const&>(instruction);
|
|
||||||
if (!get(jump.condition()).to_boolean()) {
|
|
||||||
program_counter = jump.target().address();
|
|
||||||
goto start;
|
|
||||||
}
|
}
|
||||||
program_counter += sizeof(Op::JumpFalse);
|
|
||||||
goto start;
|
handle_JumpNullish: {
|
||||||
}
|
auto& instruction = *reinterpret_cast<Op::JumpNullish const*>(&bytecode[program_counter]);
|
||||||
case Instruction::Type::JumpNullish: {
|
if (get(instruction.condition()).is_nullish())
|
||||||
auto& jump = static_cast<Op::JumpNullish const&>(instruction);
|
program_counter = instruction.true_target().address();
|
||||||
if (get(jump.condition()).is_nullish())
|
|
||||||
program_counter = jump.true_target().address();
|
|
||||||
else
|
else
|
||||||
program_counter = jump.false_target().address();
|
program_counter = instruction.false_target().address();
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
case Instruction::Type::JumpUndefined: {
|
|
||||||
auto& jump = static_cast<Op::JumpUndefined const&>(instruction);
|
handle_JumpUndefined: {
|
||||||
if (get(jump.condition()).is_undefined())
|
auto& instruction = *reinterpret_cast<Op::JumpUndefined const*>(&bytecode[program_counter]);
|
||||||
program_counter = jump.true_target().address();
|
if (get(instruction.condition()).is_undefined())
|
||||||
|
program_counter = instruction.true_target().address();
|
||||||
else
|
else
|
||||||
program_counter = jump.false_target().address();
|
program_counter = instruction.false_target().address();
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
case Instruction::Type::EnterUnwindContext:
|
|
||||||
|
handle_EnterUnwindContext: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::EnterUnwindContext const*>(&bytecode[program_counter]);
|
||||||
enter_unwind_context();
|
enter_unwind_context();
|
||||||
program_counter = static_cast<Op::EnterUnwindContext const&>(instruction).entry_point().address();
|
program_counter = instruction.entry_point().address();
|
||||||
goto start;
|
goto start;
|
||||||
case Instruction::Type::ContinuePendingUnwind: {
|
}
|
||||||
|
|
||||||
|
handle_ContinuePendingUnwind: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::ContinuePendingUnwind const*>(&bytecode[program_counter]);
|
||||||
if (auto exception = reg(Register::exception()); !exception.is_empty()) {
|
if (auto exception = reg(Register::exception()); !exception.is_empty()) {
|
||||||
if (handle_exception(program_counter, exception) == HandleExceptionResponse::ExitFromExecutable)
|
if (handle_exception(program_counter, exception) == HandleExceptionResponse::ExitFromExecutable)
|
||||||
return;
|
return;
|
||||||
|
@ -426,15 +461,17 @@ void Interpreter::run_bytecode(size_t entry_point)
|
||||||
program_counter = m_scheduled_jump.value();
|
program_counter = m_scheduled_jump.value();
|
||||||
m_scheduled_jump = {};
|
m_scheduled_jump = {};
|
||||||
} else {
|
} else {
|
||||||
program_counter = static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target().address();
|
program_counter = instruction.resume_target().address();
|
||||||
// set the scheduled jump to the old value if we continue
|
// set the scheduled jump to the old value if we continue
|
||||||
// where we left it
|
// where we left it
|
||||||
m_scheduled_jump = old_scheduled_jump;
|
m_scheduled_jump = old_scheduled_jump;
|
||||||
}
|
}
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
case Instruction::Type::ScheduleJump: {
|
|
||||||
m_scheduled_jump = static_cast<Op::ScheduleJump const&>(instruction).target().address();
|
handle_ScheduleJump: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::ScheduleJump const*>(&bytecode[program_counter]);
|
||||||
|
m_scheduled_jump = instruction.target().address();
|
||||||
auto finalizer = executable.exception_handlers_for_offset(program_counter).value().finalizer_offset;
|
auto finalizer = executable.exception_handlers_for_offset(program_counter).value().finalizer_offset;
|
||||||
VERIFY(finalizer.has_value());
|
VERIFY(finalizer.has_value());
|
||||||
program_counter = finalizer.value();
|
program_counter = finalizer.value();
|
||||||
|
@ -442,19 +479,18 @@ void Interpreter::run_bytecode(size_t entry_point)
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HANDLE_INSTRUCTION(name) \
|
#define HANDLE_INSTRUCTION(name) \
|
||||||
case Instruction::Type::name: { \
|
handle_##name: \
|
||||||
auto& typed_instruction = static_cast<Op::name const&>(instruction); \
|
{ \
|
||||||
auto result = typed_instruction.execute_impl(*this); \
|
auto& instruction = *reinterpret_cast<Op::name const*>(&bytecode[program_counter]); \
|
||||||
|
{ \
|
||||||
|
auto result = instruction.execute_impl(*this); \
|
||||||
if (result.is_error()) { \
|
if (result.is_error()) { \
|
||||||
if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable) \
|
if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable) \
|
||||||
return; \
|
return; \
|
||||||
goto start; \
|
goto start; \
|
||||||
} \
|
} \
|
||||||
if constexpr (Op::name::IsVariableLength) \
|
} \
|
||||||
program_counter += instruction.length(); \
|
DISPATCH_NEXT(name); \
|
||||||
else \
|
|
||||||
program_counter += sizeof(Op::name); \
|
|
||||||
goto start; \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLE_INSTRUCTION(Add);
|
HANDLE_INSTRUCTION(Add);
|
||||||
|
@ -551,14 +587,8 @@ void Interpreter::run_bytecode(size_t entry_point)
|
||||||
HANDLE_INSTRUCTION(UnaryPlus);
|
HANDLE_INSTRUCTION(UnaryPlus);
|
||||||
HANDLE_INSTRUCTION(UnsignedRightShift);
|
HANDLE_INSTRUCTION(UnsignedRightShift);
|
||||||
|
|
||||||
case Instruction::Type::Await:
|
handle_Await: {
|
||||||
case Instruction::Type::Return:
|
auto& instruction = *reinterpret_cast<Op::Await const*>(&bytecode[program_counter]);
|
||||||
case Instruction::Type::Yield:
|
|
||||||
// Handled delicately below.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto result = instruction.execute(*this);
|
auto result = instruction.execute(*this);
|
||||||
|
|
||||||
if (result.is_error()) {
|
if (result.is_error()) {
|
||||||
|
@ -566,9 +596,35 @@ void Interpreter::run_bytecode(size_t entry_point)
|
||||||
return;
|
return;
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
|
goto may_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
may_return:
|
handle_Return: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::Return const*>(&bytecode[program_counter]);
|
||||||
|
auto result = instruction.execute(*this);
|
||||||
|
|
||||||
|
if (result.is_error()) {
|
||||||
|
if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable)
|
||||||
|
return;
|
||||||
|
goto start;
|
||||||
|
}
|
||||||
|
goto may_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_Yield: {
|
||||||
|
auto& instruction = *reinterpret_cast<Op::Yield const*>(&bytecode[program_counter]);
|
||||||
|
auto result = instruction.execute(*this);
|
||||||
|
|
||||||
|
if (result.is_error()) {
|
||||||
|
if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable)
|
||||||
|
return;
|
||||||
|
goto start;
|
||||||
|
}
|
||||||
|
goto may_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
may_return: {
|
||||||
|
auto& instruction = *reinterpret_cast<Instruction const*>(&bytecode[program_counter]);
|
||||||
if (!reg(Register::return_value()).is_empty()) {
|
if (!reg(Register::return_value()).is_empty()) {
|
||||||
will_return = true;
|
will_return = true;
|
||||||
// Note: A `yield` statement will not go through a finally statement,
|
// Note: A `yield` statement will not go through a finally statement,
|
||||||
|
@ -583,6 +639,7 @@ void Interpreter::run_bytecode(size_t entry_point)
|
||||||
program_counter += instruction.length();
|
program_counter += instruction.length();
|
||||||
goto start;
|
goto start;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!will_yield) {
|
if (!will_yield) {
|
||||||
if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
|
if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue