ladybird/Userland/Libraries/LibJS/Bytecode/Interpreter.h

126 lines
4.1 KiB
C
Raw Normal View History

/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibJS/Runtime/FunctionKind.h>
2022-02-11 22:38:21 +03:30
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
namespace JS::Bytecode {
class InstructionStreamIterator;
struct CallFrame {
void visit_edges(Cell::Visitor& visitor)
{
for (auto const& value : registers)
visitor.visit(value);
for (auto const& environment : saved_lexical_environments)
visitor.visit(environment);
for (auto& context : unwind_contexts) {
visitor.visit(context.lexical_environment);
}
}
Vector<Value> registers;
Vector<GCPtr<Environment>> saved_lexical_environments;
Vector<UnwindInfo> unwind_contexts;
};
class Interpreter {
public:
explicit Interpreter(VM&);
~Interpreter();
Realm& realm();
VM& vm() { return m_vm; }
ThrowCompletionOr<Value> run(Script&, JS::GCPtr<Environment> lexical_environment_override = nullptr);
ThrowCompletionOr<Value> run(SourceTextModule&);
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Bytecode::BasicBlock const* entry_point = nullptr)
{
auto value_and_frame = run_and_return_frame(executable, entry_point);
2022-02-11 22:38:21 +03:30
return move(value_and_frame.value);
}
struct ValueAndFrame {
ThrowCompletionOr<Value> value;
OwnPtr<CallFrame> frame;
};
ValueAndFrame run_and_return_frame(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point, CallFrame* = nullptr);
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
ALWAYS_INLINE Value& saved_return_value() { return reg(Register::saved_return_value()); }
Value& reg(Register const& r) { return registers()[r.index()]; }
auto& saved_lexical_environment_stack() { return call_frame().saved_lexical_environments; }
auto& unwind_contexts() { return call_frame().unwind_contexts; }
void jump(Label const& label)
{
m_pending_jump = &label.block();
}
void schedule_jump(Label const& label)
{
m_scheduled_jump = &label.block();
VERIFY(unwind_contexts().last().finalizer);
jump(Label { *unwind_contexts().last().finalizer });
}
void do_return(Value value)
{
reg(Register::return_value()) = value;
reg(Register::exception()) = {};
}
LibJS: Implement bytecode generation for try..catch..finally 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
2021-06-10 15:04:38 +02:00
void enter_unwind_context(Optional<Label> handler_target, Optional<Label> finalizer_target);
void leave_unwind_context();
ThrowCompletionOr<void> continue_pending_unwind(Label const& resume_label);
LibJS: Implement bytecode generation for try..catch..finally 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
2021-06-10 15:04:38 +02:00
Executable& current_executable() { return *m_current_executable; }
Executable const& current_executable() const { return *m_current_executable; }
BasicBlock const& current_block() const { return *m_current_block; }
auto& instruction_stream_iterator() const { return m_pc; }
void visit_edges(Cell::Visitor&);
private:
CallFrame& call_frame()
{
return m_call_frames.last().visit([](auto& x) -> CallFrame& { return *x; });
}
CallFrame const& call_frame() const
{
return const_cast<Interpreter*>(this)->call_frame();
}
Span<Value> registers() { return m_current_call_frame; }
ReadonlySpan<Value> registers() const { return m_current_call_frame; }
void push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*>, size_t register_count);
[[nodiscard]] Variant<NonnullOwnPtr<CallFrame>, CallFrame*> pop_call_frame();
VM& m_vm;
Vector<Variant<NonnullOwnPtr<CallFrame>, CallFrame*>> m_call_frames;
Span<Value> m_current_call_frame;
Optional<BasicBlock const*> m_pending_jump;
BasicBlock const* m_scheduled_jump { nullptr };
Executable* m_current_executable { nullptr };
BasicBlock const* m_current_block { nullptr };
Optional<InstructionStreamIterator&> m_pc {};
};
extern bool g_dump_bytecode;
ThrowCompletionOr<NonnullOwnPtr<Bytecode::Executable>> compile(VM&, ASTNode const& no, JS::FunctionKind kind, DeprecatedFlyString const& name);
}