mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
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
This commit is contained in:
parent
d2f1476428
commit
67cc31a74f
Notes:
sideshowbarker
2024-07-18 12:28:54 +09:00
Author: https://github.com/gunnarbeutner Commit: https://github.com/SerenityOS/serenity/commit/67cc31a74f0 Pull-request: https://github.com/SerenityOS/serenity/pull/7969
@ -1227,6 +1227,7 @@ public:
|
||||
|
||||
virtual void dump(int indent) const override;
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
private:
|
||||
NonnullRefPtr<BlockStatement> m_block;
|
||||
|
@ -798,4 +798,58 @@ void BreakStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{});
|
||||
}
|
||||
|
||||
void TryStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
auto& saved_block = generator.current_block();
|
||||
|
||||
Optional<Bytecode::Label> handler_target;
|
||||
Optional<Bytecode::Label> finalizer_target;
|
||||
|
||||
Bytecode::BasicBlock* next_block { nullptr };
|
||||
|
||||
if (m_finalizer) {
|
||||
auto& finalizer_block = generator.make_block();
|
||||
generator.switch_to_basic_block(finalizer_block);
|
||||
m_finalizer->generate_bytecode(generator);
|
||||
if (!generator.is_current_block_terminated()) {
|
||||
next_block = &generator.make_block();
|
||||
auto next_target = Bytecode::Label { *next_block };
|
||||
generator.emit<Bytecode::Op::ContinuePendingUnwind>(next_target);
|
||||
}
|
||||
finalizer_target = Bytecode::Label { finalizer_block };
|
||||
}
|
||||
|
||||
if (m_handler) {
|
||||
auto& handler_block = generator.make_block();
|
||||
generator.switch_to_basic_block(handler_block);
|
||||
if (!m_finalizer)
|
||||
generator.emit<Bytecode::Op::LeaveUnwindContext>();
|
||||
if (!m_handler->parameter().is_empty()) {
|
||||
// FIXME: We need a separate LexicalEnvironment here
|
||||
generator.emit<Bytecode::Op::SetVariable>(generator.intern_string(m_handler->parameter()));
|
||||
}
|
||||
m_handler->body().generate_bytecode(generator);
|
||||
handler_target = Bytecode::Label { handler_block };
|
||||
if (!generator.is_current_block_terminated()) {
|
||||
if (m_finalizer) {
|
||||
generator.emit<Bytecode::Op::LeaveUnwindContext>();
|
||||
generator.emit<Bytecode::Op::Jump>(finalizer_target);
|
||||
} else {
|
||||
VERIFY(!next_block);
|
||||
next_block = &generator.make_block();
|
||||
auto next_target = Bytecode::Label { *next_block };
|
||||
generator.emit<Bytecode::Op::Jump>(next_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generator.switch_to_basic_block(saved_block);
|
||||
generator.emit<Bytecode::Op::EnterUnwindContext>(handler_target, finalizer_target);
|
||||
m_block->generate_bytecode(generator);
|
||||
if (m_finalizer && !generator.is_current_block_terminated())
|
||||
generator.emit<Bytecode::Op::Jump>(finalizer_target);
|
||||
|
||||
generator.switch_to_basic_block(next_block ? *next_block : saved_block);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,11 @@ private:
|
||||
size_t m_offset { 0 };
|
||||
};
|
||||
|
||||
struct UnwindInfo {
|
||||
BasicBlock const* handler;
|
||||
BasicBlock const* finalizer;
|
||||
};
|
||||
|
||||
class BasicBlock {
|
||||
public:
|
||||
static NonnullOwnPtr<BasicBlock> create(String name);
|
||||
|
@ -68,6 +68,8 @@ public:
|
||||
m_current_basic_block = █
|
||||
}
|
||||
|
||||
[[nodiscard]] BasicBlock& current_block() { return *m_current_basic_block; }
|
||||
|
||||
BasicBlock& make_block(String name = {})
|
||||
{
|
||||
if (name.is_empty())
|
||||
|
@ -57,7 +57,10 @@
|
||||
O(ConcatString) \
|
||||
O(Increment) \
|
||||
O(Decrement) \
|
||||
O(Throw)
|
||||
O(Throw) \
|
||||
O(EnterUnwindContext) \
|
||||
O(LeaveUnwindContext) \
|
||||
O(ContinuePendingUnwind)
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
|
@ -67,8 +67,25 @@ Value Interpreter::run(Executable const& executable)
|
||||
while (!pc.at_end()) {
|
||||
auto& instruction = *pc;
|
||||
instruction.execute(*this);
|
||||
if (vm().exception())
|
||||
break;
|
||||
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;
|
||||
@ -121,4 +138,23 @@ Value Interpreter::run(Executable const& executable)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <LibJS/Bytecode/Register.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Heap/Handle.h>
|
||||
#include <LibJS/Runtime/Exception.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS::Bytecode {
|
||||
@ -39,6 +41,10 @@ public:
|
||||
}
|
||||
void do_return(Value return_value) { m_return_value = return_value; }
|
||||
|
||||
void enter_unwind_context(Optional<Label> handler_target, Optional<Label> finalizer_target);
|
||||
void leave_unwind_context();
|
||||
void continue_pending_unwind(Label const& resume_label);
|
||||
|
||||
Executable const& current_executable() { return *m_current_executable; }
|
||||
|
||||
private:
|
||||
@ -50,6 +56,8 @@ private:
|
||||
Optional<BasicBlock const*> m_pending_jump;
|
||||
Value m_return_value;
|
||||
Executable const* m_current_executable { nullptr };
|
||||
Vector<UnwindInfo> m_unwind_contexts;
|
||||
Handle<Exception> m_saved_exception;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -261,6 +261,21 @@ void Throw::execute(Bytecode::Interpreter& interpreter) const
|
||||
interpreter.vm().throw_exception(interpreter.global_object(), interpreter.accumulator());
|
||||
}
|
||||
|
||||
void EnterUnwindContext::execute(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
interpreter.enter_unwind_context(m_handler_target, m_finalizer_target);
|
||||
}
|
||||
|
||||
void LeaveUnwindContext::execute(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
interpreter.leave_unwind_context();
|
||||
}
|
||||
|
||||
void ContinuePendingUnwind::execute(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
interpreter.continue_pending_unwind(m_resume_target);
|
||||
}
|
||||
|
||||
String Load::to_string(Bytecode::Executable const&) const
|
||||
{
|
||||
return String::formatted("Load {}", m_src);
|
||||
@ -394,4 +409,21 @@ String Throw::to_string(Bytecode::Executable const&) const
|
||||
return "Throw";
|
||||
}
|
||||
|
||||
String EnterUnwindContext::to_string(Bytecode::Executable const&) const
|
||||
{
|
||||
auto handler_string = m_handler_target.has_value() ? String::formatted("{}", *m_handler_target) : "<empty>";
|
||||
auto finalizer_string = m_finalizer_target.has_value() ? String::formatted("{}", *m_finalizer_target) : "<empty>";
|
||||
return String::formatted("EnterUnwindContext handler:{} finalizer:{}", handler_string, finalizer_string);
|
||||
}
|
||||
|
||||
String LeaveUnwindContext::to_string(Bytecode::Executable const&) const
|
||||
{
|
||||
return "LeaveUnwindContext";
|
||||
}
|
||||
|
||||
String ContinuePendingUnwind::to_string(Bytecode::Executable const&) const
|
||||
{
|
||||
return String::formatted("ContinuePendingUnwind resume:{}", m_resume_target);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -409,6 +409,50 @@ public:
|
||||
String to_string(Bytecode::Executable const&) const;
|
||||
};
|
||||
|
||||
class EnterUnwindContext final : public Instruction {
|
||||
public:
|
||||
EnterUnwindContext(Optional<Label> handler_target, Optional<Label> finalizer_target)
|
||||
: Instruction(Type::EnterUnwindContext)
|
||||
, m_handler_target(handler_target)
|
||||
, m_finalizer_target(finalizer_target)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(Bytecode::Interpreter&) const;
|
||||
String to_string(Bytecode::Executable const&) const;
|
||||
|
||||
private:
|
||||
Optional<Label> m_handler_target;
|
||||
Optional<Label> m_finalizer_target;
|
||||
};
|
||||
|
||||
class LeaveUnwindContext final : public Instruction {
|
||||
public:
|
||||
LeaveUnwindContext()
|
||||
: Instruction(Type::LeaveUnwindContext)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(Bytecode::Interpreter&) const;
|
||||
String to_string(Bytecode::Executable const&) const;
|
||||
};
|
||||
|
||||
class ContinuePendingUnwind final : public Instruction {
|
||||
public:
|
||||
constexpr static bool IsTerminator = true;
|
||||
|
||||
ContinuePendingUnwind(Label const& resume_target)
|
||||
: Instruction(Type::ContinuePendingUnwind)
|
||||
, m_resume_target(resume_target)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(Bytecode::Interpreter&) const;
|
||||
String to_string(Bytecode::Executable const&) const;
|
||||
|
||||
private:
|
||||
Label m_resume_target;
|
||||
};
|
||||
}
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
Loading…
Reference in New Issue
Block a user