Instead of recursing through 5 native stack frames per JS function call (execute_call -> internal_call -> ordinary_call_evaluate_body -> run_executable -> run_bytecode), handle Call and CallConstruct for normal ECMAScript functions directly in the dispatch loop. The fast path allocates the callee's execution context on the InterpreterStack, copies arguments, sets up the environment, and jumps to the callee's bytecode entry point. Return and End unwind inline frames by restoring the caller's state. Exception unwinding walks through inline frames to find handlers. The fast path code is kept in NEVER_INLINE helper functions (try_inline_call, try_inline_call_construct, pop_inline_frame) to minimize register pressure in the dispatch loop. handle_exception takes program_counter by value to avoid forcing it onto the stack. Reloading of bytecode/program_counter after frame switches is done inline at each call site via RELOAD_AND_GOTO_START to preserve a single dispatch entry point for optimal indirect branch prediction.
115 lines
4.2 KiB
C++
115 lines
4.2 KiB
C++
/*
|
|
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <LibJS/Bytecode/BuiltinAbstractOperationsEnabled.h>
|
|
#include <LibJS/Bytecode/Executable.h>
|
|
#include <LibJS/Bytecode/Label.h>
|
|
#include <LibJS/Bytecode/Register.h>
|
|
#include <LibJS/Export.h>
|
|
#include <LibJS/Forward.h>
|
|
#include <LibJS/Heap/Cell.h>
|
|
#include <LibJS/Runtime/FunctionKind.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
#include <LibJS/Runtime/Value.h>
|
|
|
|
namespace JS::Bytecode {
|
|
|
|
class InstructionStreamIterator;
|
|
|
|
class JS_API Interpreter {
|
|
public:
|
|
Interpreter();
|
|
~Interpreter();
|
|
|
|
[[nodiscard]] Realm& realm() { return *m_running_execution_context->realm; }
|
|
[[nodiscard]] Object& global_object() { return *m_running_execution_context->global_object; }
|
|
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment() { return *m_running_execution_context->global_declarative_environment; }
|
|
static VM& vm() { return VM::the(); }
|
|
|
|
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
|
|
ThrowCompletionOr<Value> run(SourceTextModule&);
|
|
|
|
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Executable&, Optional<size_t> entry_point);
|
|
|
|
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Executable& executable, Optional<size_t> entry_point, Value initial_accumulator_value)
|
|
{
|
|
context.registers_and_constants_and_locals_and_arguments_span()[0] = initial_accumulator_value;
|
|
return run_executable(context, executable, entry_point);
|
|
}
|
|
|
|
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
|
|
Value& reg(Register const& r)
|
|
{
|
|
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
|
|
}
|
|
Value reg(Register const& r) const
|
|
{
|
|
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
|
|
}
|
|
|
|
[[nodiscard]] Value get(Operand) const;
|
|
void set(Operand, Value);
|
|
|
|
Value do_yield(Value value, Optional<Label> continuation);
|
|
void do_return(Value value)
|
|
{
|
|
if (value.is_special_empty_value())
|
|
value = js_undefined();
|
|
reg(Register::return_value()) = value;
|
|
reg(Register::exception()) = js_special_empty_value();
|
|
}
|
|
|
|
void catch_exception(Operand dst);
|
|
|
|
Executable& current_executable() { return *m_running_execution_context->executable; }
|
|
Executable const& current_executable() const { return *m_running_execution_context->executable; }
|
|
|
|
ExecutionContext& running_execution_context() { return *m_running_execution_context; }
|
|
|
|
[[nodiscard]] Utf16FlyString const& get_identifier(IdentifierTableIndex) const;
|
|
[[nodiscard]] Optional<Utf16FlyString const&> get_identifier(Optional<IdentifierTableIndex> index) const
|
|
{
|
|
if (!index.has_value())
|
|
return {};
|
|
return get_identifier(*index);
|
|
}
|
|
|
|
[[nodiscard]] PropertyKey const& get_property_key(PropertyKeyTableIndex) const;
|
|
|
|
private:
|
|
void run_bytecode(size_t entry_point);
|
|
|
|
enum class HandleExceptionResponse {
|
|
ExitFromExecutable,
|
|
ContinueInThisExecutable,
|
|
};
|
|
[[nodiscard]] COLD HandleExceptionResponse handle_exception(u32 program_counter, Value exception);
|
|
|
|
[[nodiscard]] NEVER_INLINE bool try_inline_call(Instruction const&, u32 current_pc);
|
|
[[nodiscard]] NEVER_INLINE bool try_inline_call_construct(Instruction const&, u32 current_pc);
|
|
NEVER_INLINE void pop_inline_frame(Value return_value);
|
|
|
|
ExecutionContext* push_inline_frame(
|
|
ECMAScriptFunctionObject& callee_function,
|
|
Executable& callee_executable,
|
|
ReadonlySpan<Operand> arguments,
|
|
u32 return_pc,
|
|
u32 dst_raw,
|
|
Value this_value,
|
|
Object* new_target,
|
|
bool is_construct);
|
|
|
|
ExecutionContext* m_running_execution_context { nullptr };
|
|
};
|
|
|
|
JS_API extern bool g_dump_bytecode;
|
|
|
|
GC::Ref<Bytecode::Executable> compile(VM&, ASTNode const&, JS::FunctionKind kind, Utf16FlyString const& name);
|
|
GC::Ref<Bytecode::Executable> compile(VM&, GC::Ref<SharedFunctionInstanceData const>, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled);
|
|
|
|
}
|