ladybird/Userland/Libraries/LibJS/Bytecode/Generator.h
Gunnar Beutner 67cc31a74f 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 21:59:46 +02:00

108 lines
3 KiB
C++

/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/OwnPtr.h>
#include <AK/SinglyLinkedList.h>
#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h>
#include <LibJS/Bytecode/StringTable.h>
#include <LibJS/Forward.h>
namespace JS::Bytecode {
struct Executable {
NonnullOwnPtrVector<BasicBlock> basic_blocks;
NonnullOwnPtr<StringTable> string_table;
size_t number_of_registers { 0 };
String const& get_string(StringTableIndex index) const { return string_table->get(index); }
};
class Generator {
public:
static Executable generate(ASTNode const&);
Register allocate_register();
template<typename OpType, typename... Args>
OpType& emit(Args&&... args)
{
VERIFY(!is_current_block_terminated());
void* slot = next_slot();
grow(sizeof(OpType));
new (slot) OpType(forward<Args>(args)...);
if constexpr (OpType::IsTerminator)
m_current_basic_block->terminate({});
return *static_cast<OpType*>(slot);
}
template<typename OpType, typename... Args>
OpType& emit_with_extra_register_slots(size_t extra_register_slots, Args&&... args)
{
VERIFY(!is_current_block_terminated());
void* slot = next_slot();
grow(sizeof(OpType) + extra_register_slots * sizeof(Register));
new (slot) OpType(forward<Args>(args)...);
if constexpr (OpType::IsTerminator)
m_current_basic_block->terminate({});
return *static_cast<OpType*>(slot);
}
void begin_continuable_scope(Label continue_target);
void end_continuable_scope();
void begin_breakable_scope(Label breakable_target);
void end_breakable_scope();
[[nodiscard]] Label nearest_continuable_scope() const;
[[nodiscard]] Label nearest_breakable_scope() const;
void switch_to_basic_block(BasicBlock& block)
{
m_current_basic_block = &block;
}
[[nodiscard]] BasicBlock& current_block() { return *m_current_basic_block; }
BasicBlock& make_block(String name = {})
{
if (name.is_empty())
name = String::number(m_next_block++);
m_root_basic_blocks.append(BasicBlock::create(name));
return m_root_basic_blocks.last();
}
bool is_current_block_terminated() const
{
return m_current_basic_block->is_terminated();
}
StringTableIndex intern_string(StringView const& string)
{
return m_string_table->insert(string);
}
private:
Generator();
~Generator();
void grow(size_t);
void* next_slot();
BasicBlock* m_current_basic_block { nullptr };
NonnullOwnPtrVector<BasicBlock> m_root_basic_blocks;
NonnullOwnPtr<StringTable> m_string_table;
u32 m_next_register { 1 };
u32 m_next_block { 1 };
Vector<Label> m_continuable_scopes;
Vector<Label> m_breakable_scopes;
};
}