-
Notifications
You must be signed in to change notification settings - Fork 0
/
vm.hpp
127 lines (108 loc) · 4.64 KB
/
vm.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#ifndef ppclox_vm_hpp
#define ppclox_vm_hpp
#include <memory>
#include <map>
#include "chunk.hpp"
#include "object_function.hpp"
#include "object_class.hpp"
#define VALUE_STACK_INIT_CAPACITY 256
class CallFrame {
public:
ObjClosure* m_closure{};
/** If non-null, this is a pointer into the chunk's code */
/** @todo Is there any way to make this safer? Maybe using an iterator? */
const std::uint8_t* m_ip{};
/** Base index into the VM's value stack for this call frame's locals etc. */
std::size_t m_value_stack_base_index{};
CallFrame(ObjClosure* closure, std::size_t value_stack_base_index) :
m_closure(closure),
m_ip(closure->function()->chunk().get_code().data()),
m_value_stack_base_index(value_stack_base_index) {}
std::size_t next_instruction_offset() {
return m_ip - m_closure->function()->chunk().get_code().data();
}
/** Offset of currently executing instruction. Assumes at least 1 instruction has been read. */
std::size_t current_instruction_offset() {
return m_ip - m_closure->function()->chunk().get_code().data() - 1;
}
void disassemble_instruction() {
m_closure->function()->chunk().disassemble_instruction(next_instruction_offset());
}
};
enum class InterpretResult {
OK,
COMPILE_ERROR,
RUNTIME_ERROR
};
class VM {
public:
VM();
~VM();
InterpretResult interpret(const char* source);
void mark_gc_roots();
private:
/**
* There should be a practical limit on the number of stack frames so as to
* detect runaway recursion and avoid memory exhaustion.
*/
static constexpr std::size_t k_max_call_frames = 1024;
std::vector<CallFrame> m_call_stack{};
std::vector<Value> m_stack{};
std::unordered_map<ObjStringRef, Value, ObjStringRefHash> m_globals{};
/**
* Map from value stack index to open upvalue referring to that index
* NOTE! We just use the std::less<std::size_t> compareer, so keys are sorted
* in ascending order. This is opposite of the linked list used by Clox.
* NOTE! The intrusive linked list used by Clox may very well be more efficient,
* but this is obviously simpler. One might have to measure if it really matters.
* We also might want to investigate if std::unordered_map would be faster for typical Lox
* workloads, or perhaps even std::vector.
*/
std::map<std::size_t, ObjUpvalue*> m_open_upvalues{};
ObjString* m_init_string{};
void reset_stack();
void runtime_error(const char* format, ...);
void define_native(const char* name, NativeFn function);
void push(Value value);
// Patch value at given distance from the top of the stack
void patch(Value value, std::size_t distance);
Value pop();
Value peek(std::size_t distance);
bool call_value(Value callee, std::size_t arg_count);
bool invoke_from_class(ObjClass* klass, ObjString* name, std::uint8_t arg_count);
bool invoke(ObjString* name, std::uint8_t arg_count);
bool call(ObjClosure* closure, std::size_t arg_count);
ObjUpvalue* capture_upvalue(std::size_t stack_index);
// Close upvalues starting at the given index and proceeding to the top of the stack
void close_upvalues(std::size_t start_index);
void define_method(ObjString* name);
bool bind_method(ObjClass* klass, ObjString* name);
InterpretResult run();
/**
* Get reference to current call frame.
* NOTE! Caller must ensure that there IS a call frame to get!
*/
CallFrame& current_frame() { return m_call_stack.back(); }
/** Return the current byte pointed to, and increment the IP */
std::uint8_t read_byte() { return *(current_frame().m_ip++); }
/** Return the short pointed to, and increment the IP to after it */
std::uint16_t read_short() {
current_frame().m_ip += 2;
// Read the HO byte, followed by the LO byte
std::uint16_t ho_byte = current_frame().m_ip[-2];
std::uint16_t lo_byte = current_frame().m_ip[-1];
return (ho_byte << 8) | lo_byte;
}
/**
* Read/increment the current byte and assume it is an index into the chunk's
* constants array, returning the constant at that index.
* NOTE! We do not do any bounds checking here to ensure fast execution, so it's
* important that the compiled code produce correct, in-bound indexes.
*/
Value read_constant() { return current_frame().m_closure->function()->chunk().get_constants()[this->read_byte()]; }
ObjString* read_string() { return read_constant().as_string(); }
bool verify_binary_op_types();
};
// Exposes the global g_vm variable from vm.cpp
extern VM g_vm;
#endif