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

68 lines
1.8 KiB
C
Raw Normal View History

/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Badge.h>
#include <AK/String.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Handle.h>
namespace JS::Bytecode {
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
struct UnwindInfo {
JS::GCPtr<Executable const> executable;
JS::GCPtr<Environment> lexical_environment;
bool handler_called { false };
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
};
class BasicBlock {
AK_MAKE_NONCOPYABLE(BasicBlock);
public:
static NonnullOwnPtr<BasicBlock> create(u32 index, String name);
~BasicBlock();
u32 index() const { return m_index; }
ReadonlyBytes instruction_stream() const { return m_buffer.span(); }
u8* data() { return m_buffer.data(); }
u8 const* data() const { return m_buffer.data(); }
size_t size() const { return m_buffer.size(); }
void grow(size_t additional_size);
void terminate(Badge<Generator>) { m_terminated = true; }
bool is_terminated() const { return m_terminated; }
String const& name() const { return m_name; }
void set_handler(BasicBlock const& handler) { m_handler = &handler; }
void set_finalizer(BasicBlock const& finalizer) { m_finalizer = &finalizer; }
BasicBlock const* handler() const { return m_handler; }
BasicBlock const* finalizer() const { return m_finalizer; }
auto const& source_map() const { return m_source_map; }
void add_source_map_entry(size_t bytecode_offset, SourceRecord const& source_record) { m_source_map.set(bytecode_offset, source_record); }
private:
explicit BasicBlock(u32 index, String name);
u32 m_index { 0 };
Vector<u8> m_buffer;
BasicBlock const* m_handler { nullptr };
BasicBlock const* m_finalizer { nullptr };
String m_name;
bool m_terminated { false };
HashMap<size_t, SourceRecord> m_source_map;
};
}