
We are already doing this in a good manner via the generated code, doing so in the execution loop as well will cause us to pop contexts multiple times, which is not very good.
242 lines
8.4 KiB
C++
242 lines
8.4 KiB
C++
/*
|
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Debug.h>
|
|
#include <AK/TemporaryChange.h>
|
|
#include <LibJS/Bytecode/BasicBlock.h>
|
|
#include <LibJS/Bytecode/Instruction.h>
|
|
#include <LibJS/Bytecode/Interpreter.h>
|
|
#include <LibJS/Bytecode/Op.h>
|
|
#include <LibJS/Interpreter.h>
|
|
#include <LibJS/Runtime/GlobalEnvironment.h>
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
|
#include <LibJS/Runtime/Realm.h>
|
|
|
|
namespace JS::Bytecode {
|
|
|
|
static Interpreter* s_current;
|
|
bool g_dump_bytecode = false;
|
|
|
|
Interpreter* Interpreter::current()
|
|
{
|
|
return s_current;
|
|
}
|
|
|
|
Interpreter::Interpreter(Realm& realm)
|
|
: m_vm(realm.vm())
|
|
, m_realm(realm)
|
|
{
|
|
VERIFY(!s_current);
|
|
s_current = this;
|
|
}
|
|
|
|
Interpreter::~Interpreter()
|
|
{
|
|
VERIFY(s_current == this);
|
|
s_current = nullptr;
|
|
}
|
|
|
|
Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& executable, BasicBlock const* entry_point, RegisterWindow* in_frame)
|
|
{
|
|
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable);
|
|
|
|
TemporaryChange restore_executable { m_current_executable, &executable };
|
|
VERIFY(m_saved_exception.is_null());
|
|
|
|
bool pushed_execution_context = false;
|
|
ExecutionContext execution_context(vm().heap());
|
|
if (vm().execution_context_stack().is_empty() || !vm().running_execution_context().lexical_environment) {
|
|
// The "normal" interpreter pushes an execution context without environment so in that case we also want to push one.
|
|
execution_context.this_value = &m_realm.global_object();
|
|
static FlyString global_execution_context_name = "(*BC* global execution context)";
|
|
execution_context.function_name = global_execution_context_name;
|
|
execution_context.lexical_environment = &m_realm.global_environment();
|
|
execution_context.variable_environment = &m_realm.global_environment();
|
|
execution_context.realm = &m_realm;
|
|
execution_context.is_strict_mode = executable.is_strict_mode;
|
|
vm().push_execution_context(execution_context);
|
|
pushed_execution_context = true;
|
|
}
|
|
|
|
TemporaryChange restore_current_block { m_current_block, entry_point ?: &executable.basic_blocks.first() };
|
|
|
|
if (in_frame)
|
|
m_register_windows.append(in_frame);
|
|
else
|
|
m_register_windows.append(make<RegisterWindow>(MarkedVector<Value>(vm().heap()), MarkedVector<Environment*>(vm().heap()), MarkedVector<Environment*>(vm().heap()), Vector<UnwindInfo> {}));
|
|
|
|
registers().resize(executable.number_of_registers);
|
|
|
|
for (;;) {
|
|
Bytecode::InstructionStreamIterator pc(m_current_block->instruction_stream());
|
|
TemporaryChange temp_change { m_pc, &pc };
|
|
|
|
bool will_jump = false;
|
|
bool will_return = false;
|
|
while (!pc.at_end()) {
|
|
auto& instruction = *pc;
|
|
auto ran_or_error = instruction.execute(*this);
|
|
if (ran_or_error.is_error()) {
|
|
auto exception_value = *ran_or_error.throw_completion().value();
|
|
m_saved_exception = make_handle(exception_value);
|
|
if (unwind_contexts().is_empty())
|
|
break;
|
|
auto& unwind_context = unwind_contexts().last();
|
|
if (unwind_context.executable != m_current_executable)
|
|
break;
|
|
if (unwind_context.handler) {
|
|
m_current_block = unwind_context.handler;
|
|
unwind_context.handler = nullptr;
|
|
|
|
accumulator() = exception_value;
|
|
m_saved_exception = {};
|
|
will_jump = true;
|
|
break;
|
|
}
|
|
if (unwind_context.finalizer) {
|
|
m_current_block = unwind_context.finalizer;
|
|
will_jump = true;
|
|
break;
|
|
}
|
|
// An unwind context with no handler or finalizer? We have nowhere to jump, and continuing on will make us crash on the next `Call` to a non-native function if there's an exception! So let's crash here instead.
|
|
// If you run into this, you probably forgot to remove the current unwind_context somewhere.
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
if (m_pending_jump.has_value()) {
|
|
m_current_block = m_pending_jump.release_value();
|
|
will_jump = true;
|
|
break;
|
|
}
|
|
if (!m_return_value.is_empty()) {
|
|
will_return = true;
|
|
break;
|
|
}
|
|
++pc;
|
|
}
|
|
|
|
if (will_return)
|
|
break;
|
|
|
|
if (pc.at_end() && !will_jump)
|
|
break;
|
|
|
|
if (!m_saved_exception.is_null())
|
|
break;
|
|
}
|
|
|
|
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &executable);
|
|
|
|
if constexpr (JS_BYTECODE_DEBUG) {
|
|
for (size_t i = 0; i < registers().size(); ++i) {
|
|
DeprecatedString value_string;
|
|
if (registers()[i].is_empty())
|
|
value_string = "(empty)";
|
|
else
|
|
value_string = registers()[i].to_string_without_side_effects();
|
|
dbgln("[{:3}] {}", i, value_string);
|
|
}
|
|
}
|
|
|
|
auto frame = m_register_windows.take_last();
|
|
|
|
auto return_value = m_return_value.value_or(js_undefined());
|
|
m_return_value = {};
|
|
|
|
// NOTE: The return value from a called function is put into $0 in the caller context.
|
|
if (!m_register_windows.is_empty())
|
|
window().registers[0] = return_value;
|
|
|
|
// At this point we may have already run any queued promise jobs via on_call_stack_emptied,
|
|
// in which case this is a no-op.
|
|
vm().run_queued_promise_jobs();
|
|
|
|
if (pushed_execution_context) {
|
|
VERIFY(&vm().running_execution_context() == &execution_context);
|
|
vm().pop_execution_context();
|
|
}
|
|
|
|
vm().finish_execution_generation();
|
|
|
|
if (!m_saved_exception.is_null()) {
|
|
Value thrown_value = m_saved_exception.value();
|
|
m_saved_exception = {};
|
|
if (auto* register_window = frame.get_pointer<NonnullOwnPtr<RegisterWindow>>())
|
|
return { throw_completion(thrown_value), move(*register_window) };
|
|
return { throw_completion(thrown_value), nullptr };
|
|
}
|
|
|
|
if (auto register_window = frame.get_pointer<NonnullOwnPtr<RegisterWindow>>())
|
|
return { return_value, move(*register_window) };
|
|
return { return_value, nullptr };
|
|
}
|
|
|
|
void Interpreter::enter_unwind_context(Optional<Label> handler_target, Optional<Label> finalizer_target)
|
|
{
|
|
unwind_contexts().empend(m_current_executable, handler_target.has_value() ? &handler_target->block() : nullptr, finalizer_target.has_value() ? &finalizer_target->block() : nullptr);
|
|
}
|
|
|
|
void Interpreter::leave_unwind_context()
|
|
{
|
|
unwind_contexts().take_last();
|
|
}
|
|
|
|
ThrowCompletionOr<void> Interpreter::continue_pending_unwind(Label const& resume_label)
|
|
{
|
|
if (!m_saved_exception.is_null()) {
|
|
auto result = throw_completion(m_saved_exception.value());
|
|
m_saved_exception = {};
|
|
return result;
|
|
}
|
|
|
|
jump(resume_label);
|
|
return {};
|
|
}
|
|
|
|
VM::InterpreterExecutionScope Interpreter::ast_interpreter_scope()
|
|
{
|
|
if (!m_ast_interpreter)
|
|
m_ast_interpreter = JS::Interpreter::create_with_existing_realm(m_realm);
|
|
|
|
return { *m_ast_interpreter };
|
|
}
|
|
|
|
AK::Array<OwnPtr<PassManager>, static_cast<UnderlyingType<Interpreter::OptimizationLevel>>(Interpreter::OptimizationLevel::__Count)> Interpreter::s_optimization_pipelines {};
|
|
|
|
Bytecode::PassManager& Interpreter::optimization_pipeline(Interpreter::OptimizationLevel level)
|
|
{
|
|
auto underlying_level = to_underlying(level);
|
|
VERIFY(underlying_level <= to_underlying(Interpreter::OptimizationLevel::__Count));
|
|
auto& entry = s_optimization_pipelines[underlying_level];
|
|
|
|
if (entry)
|
|
return *entry;
|
|
|
|
auto pm = make<PassManager>();
|
|
if (level == OptimizationLevel::None) {
|
|
// No optimization.
|
|
} else if (level == OptimizationLevel::Optimize) {
|
|
pm->add<Passes::GenerateCFG>();
|
|
pm->add<Passes::UnifySameBlocks>();
|
|
pm->add<Passes::GenerateCFG>();
|
|
pm->add<Passes::MergeBlocks>();
|
|
pm->add<Passes::GenerateCFG>();
|
|
pm->add<Passes::UnifySameBlocks>();
|
|
pm->add<Passes::GenerateCFG>();
|
|
pm->add<Passes::MergeBlocks>();
|
|
pm->add<Passes::GenerateCFG>();
|
|
pm->add<Passes::PlaceBlocks>();
|
|
pm->add<Passes::EliminateLoads>();
|
|
} else {
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
auto& passes = *pm;
|
|
entry = move(pm);
|
|
|
|
return passes;
|
|
}
|
|
|
|
}
|