
EnterUnwindContext pushes an unwind context (exception handler and/or finalizer) onto a stack. LeaveUnwindContext pops the unwind context from that stack. Upon return to the interpreter loop we check whether the VM has an exception pending. If no unwind context is available we return from the loop. If an exception handler is available we clear the VM's exception, put the exception value into the accumulator register, clear the unwind context's handler and jump to the handler. If no handler is available but a finalizer is available we save the exception value + metadata (for later use by ContinuePendingUnwind), clear the VM's exception, pop the unwind context and jump to the finalizer. ContinuePendingUnwind checks whether a saved exception is available. If no saved exception is available it jumps to the resume label. Otherwise it stores the exception into the VM. The Jump after LeaveUnwindContext could be integrated into the LeaveUnwindContext instruction. I've kept them separate for now to make the bytecode more readable. > try { 1; throw "x" } catch (e) { 2 } finally { 3 }; 4 1: [ 0] EnterScope [ 10] EnterUnwindContext handler:@4 finalizer:@3 [ 38] EnterScope [ 48] LoadImmediate 1 [ 60] NewString 1 ("x") [ 70] Throw <for non-terminated blocks: insert LeaveUnwindContext + Jump @3 here> 2: [ 0] LoadImmediate 4 3: [ 0] EnterScope [ 10] LoadImmediate 3 [ 28] ContinuePendingUnwind resume:@2 4: [ 0] SetVariable 0 (e) [ 10] EnterScope [ 20] LoadImmediate 2 [ 38] LeaveUnwindContext [ 3c] Jump @3 String Table: 0: e 1: x
160 lines
4.9 KiB
C++
160 lines
4.9 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/Runtime/GlobalObject.h>
|
|
|
|
namespace JS::Bytecode {
|
|
|
|
static Interpreter* s_current;
|
|
|
|
Interpreter* Interpreter::current()
|
|
{
|
|
return s_current;
|
|
}
|
|
|
|
Interpreter::Interpreter(GlobalObject& global_object)
|
|
: m_vm(global_object.vm())
|
|
, m_global_object(global_object)
|
|
{
|
|
VERIFY(!s_current);
|
|
s_current = this;
|
|
}
|
|
|
|
Interpreter::~Interpreter()
|
|
{
|
|
VERIFY(s_current == this);
|
|
s_current = nullptr;
|
|
}
|
|
|
|
Value Interpreter::run(Executable const& executable)
|
|
{
|
|
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable);
|
|
|
|
TemporaryChange restore_executable { m_current_executable, &executable };
|
|
|
|
vm().set_last_value(Badge<Interpreter> {}, {});
|
|
|
|
CallFrame global_call_frame;
|
|
if (vm().call_stack().is_empty()) {
|
|
global_call_frame.this_value = &global_object();
|
|
static FlyString global_execution_context_name = "(*BC* global execution context)";
|
|
global_call_frame.function_name = global_execution_context_name;
|
|
global_call_frame.scope = &global_object();
|
|
VERIFY(!vm().exception());
|
|
// FIXME: How do we know if we're in strict mode? Maybe the Bytecode::Block should know this?
|
|
// global_call_frame.is_strict_mode = ???;
|
|
vm().push_call_frame(global_call_frame, global_object());
|
|
VERIFY(!vm().exception());
|
|
}
|
|
|
|
auto block = &executable.basic_blocks.first();
|
|
m_register_windows.append(make<RegisterWindow>());
|
|
registers().resize(executable.number_of_registers);
|
|
|
|
for (;;) {
|
|
Bytecode::InstructionStreamIterator pc(block->instruction_stream());
|
|
bool will_jump = false;
|
|
bool will_return = false;
|
|
while (!pc.at_end()) {
|
|
auto& instruction = *pc;
|
|
instruction.execute(*this);
|
|
if (vm().exception()) {
|
|
m_saved_exception = {};
|
|
if (m_unwind_contexts.is_empty())
|
|
break;
|
|
auto& unwind_context = m_unwind_contexts.last();
|
|
if (unwind_context.handler) {
|
|
block = unwind_context.handler;
|
|
unwind_context.handler = nullptr;
|
|
accumulator() = vm().exception()->value();
|
|
vm().clear_exception();
|
|
will_jump = true;
|
|
} else if (unwind_context.finalizer) {
|
|
block = unwind_context.finalizer;
|
|
m_unwind_contexts.take_last();
|
|
will_jump = true;
|
|
m_saved_exception = Handle<Exception>::create(vm().exception());
|
|
vm().clear_exception();
|
|
}
|
|
}
|
|
if (m_pending_jump.has_value()) {
|
|
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 (vm().exception())
|
|
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) {
|
|
String 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);
|
|
}
|
|
}
|
|
|
|
vm().set_last_value(Badge<Interpreter> {}, accumulator());
|
|
|
|
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())
|
|
m_register_windows.last()[0] = return_value;
|
|
|
|
if (vm().call_stack().size() == 1)
|
|
vm().pop_call_frame();
|
|
|
|
return return_value;
|
|
}
|
|
|
|
void Interpreter::enter_unwind_context(Optional<Label> handler_target, Optional<Label> finalizer_target)
|
|
{
|
|
m_unwind_contexts.empend(handler_target.has_value() ? &handler_target->block() : nullptr, finalizer_target.has_value() ? &finalizer_target->block() : nullptr);
|
|
}
|
|
|
|
void Interpreter::leave_unwind_context()
|
|
{
|
|
m_unwind_contexts.take_last();
|
|
}
|
|
|
|
void Interpreter::continue_pending_unwind(Label const& resume_label)
|
|
{
|
|
if (!m_saved_exception.is_null()) {
|
|
vm().set_exception(*m_saved_exception.cell());
|
|
m_saved_exception = {};
|
|
} else {
|
|
jump(resume_label);
|
|
}
|
|
}
|
|
}
|