Phasor 3.3.0
Stack VM based Programming Language
Loading...
Searching...
No Matches
Utility.cpp
Go to the documentation of this file.
1#ifndef CMAKE_PCH
2#include "VM.hpp"
3#endif
4#include <iostream>
5#include <stdexcept>
6#include <format>
7#include <cassert>
8#include "core/core.h"
9
10#ifdef TIMING
11#include <chrono>
12#endif
13
14#ifdef _DEBUG
15#ifdef _WIN32
16#include <windows.h>
17inline bool isDebuggerAttached()
18{
19 return IsDebuggerPresent() == TRUE;
20}
21#else
22#include <unistd.h>
23#include <sys/ptrace.h>
24inline bool isDebuggerAttached()
25{
26 return ptrace(PTRACE_TRACEME, 0, 1, 0) == -1;
27}
28#endif
29#endif
30
31namespace Phasor
32{
33
34std::string VM::getVersion()
35{
36 return PHASOR_VERSION_STRING;
37}
38void VM::setStatus(int newStatus)
39{
40 status = newStatus;
41}
43{
44 status = 0;
45}
47{
48 return status;
49}
50
51void VM::initFFI(const std::filesystem::path &path)
52{
53#ifndef SANDBOXED
54 ffi = std::make_unique<FFI>(path, this);
55#endif
56}
57
58void VM::setup(const Bytecode &bc, const size_t initialPC) {
59 m_bytecode = &bc;
60 pc = initialPC;
61 stack.clear();
62 callStack.clear();
63
64#ifdef TRACING
65 log(std::format("\nVM::{}():\n\n{}\n", __func__, getBytecodeInformation()));
66 flush();
67#endif
68
69 registers.fill(Value());
70 variables.resize(m_bytecode->nextVarIndex);
71}
72
74{
75 while (pc < m_bytecode->instructions.size())
76 {
77 const Instruction &instr = m_bytecode->instructions[pc++];
78
79#ifdef TRACING
80 log(std::format("\nVM::{}(): RUN (pc={})\n", __func__, pc - 1));
81 flush();
82#endif
83
84 operation(instr.op, instr.operand1, instr.operand2, instr.operand3);
85 }
86}
87
88int VM::run(const Bytecode &bc, const size_t startPC)
89{
90 setup(bc, startPC);
91
92#ifdef TRACING
93 log(std::format("\nVM::{}():\n\n", __func__));
94 flush();
95#endif
96
97#ifdef TIMING
98 using clock = std::chrono::high_resolution_clock;
99 auto start = clock::now();
100#endif
101
102 try
103 {
104 evalLoop();
105 return status;
106 }
107 catch (const VM::Halt &)
108 {
109#ifdef TIMING
110 auto end = clock::now();
111 auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
112 log(std::format("VM::{}(): Duration of bytecode execution: {}us\n\n", __func__, us));
113 flush();
114#endif
115#ifdef TRACING
116 log(std::format("\nVM::{}(): HALT (status={})\n\n{}\n", __func__, status, getInformation()));
117 flush();
118#endif
119#ifdef _DEBUG
120 if (isDebuggerAttached())
121 assert(status == 0);
122#endif
123 return status;
124 }
125#if defined(TRACING) || defined(_DEBUG)
126 catch (const std::exception &e)
127#else
128 catch (const std::exception &)
129#endif
130 {
131#ifdef TRACING
132 logerr(std::format("\nVM::{}(): UNCAUGHT EXCEPTION!\n\n{}\n{}\n\n", __func__, e.what(), getInformation()));
133 flusherr();
134#endif
135 status = -1;
136#ifdef _DEBUG
137 logerr(std::format("{}\n", e.what()));
138 assert(false);
139#endif
140 throw;
141 }
142}
143
144Value VM::runFunction(const std::string &name, const Bytecode &bytecode)
145{
146 isDirectCall = true;
147 run(bytecode, bytecode.functionEntries.find(name)->second);
148 Value ret = pop();
149 reset(true, false, true);
150 return ret;
151}
152
154{
155#ifdef TRACING
156 log(std::format("VM::{}()\n", __func__));
157 flush();
158#endif
159 importHandler = handler;
160}
161
163{
164#ifdef TRACING
165 log(std::format("VM::{}()\n", __func__));
166 flush();
167#endif
168 for (auto &i : registers)
169 {
170 i = Value();
171 }
172 for (auto &i : variables)
173 {
174 i = Value();
175 }
176 flush();
177 flusherr();
178 reset(true, true, true);
179}
180
181void VM::reset(const bool &resetStack, const bool &resetFunctions, const bool &resetVariables)
182{
183#ifdef TRACING
184 log(std::format("VM::{}()\n", __func__));
185 flush();
186#endif
187 if (resetStack)
188 {
189 callStack.clear();
190 stack_pool.release();
191 stack = std::pmr::vector<Value>(&stack_pool);
192 }
193 if (resetFunctions)
194 {
195 nativeFunctions.clear();
196 }
197 if (resetVariables)
198 {
199 variables.clear();
200 }
201 pc = 0;
202 status = 0;
203 m_bytecode = nullptr;
204 isDirectCall = false;
205}
206
208{
209 int callStackTop = callStack.empty() ? -1 : callStack.back();
210 std::string info;
211
212 if (!stack.empty())
213 {
214 info = std::format("Stack Top: {:T}\n", peek());
215 }
216
217 std::string registersStr;
218 int regCount = 0;
219
220 for (const auto &reg : registers)
221 {
222 if (reg.getType() != ValueType::Null)
223 {
224 registersStr += std::format("R{}: {:T}\n", regCount, reg);
225 }
226 regCount++;
227 }
228
229 info += std::format("VM INFORMATION:\n{}PC: {}\nCS: {}", registersStr, pc, callStackTop);
230
231 return info;
232}
233
235{
236 std::string info;
237 std::string constants;
238 std::string variables;
239 std::string functions;
240 std::string instructions;
241
242 for (const auto &constant : m_bytecode->constants)
243 {
244 constants += std::format("{:T}\n", constant);
245 }
246 for (const auto &variable : m_bytecode->variables)
247 {
248 variables += std::format("{}\n", variable.first);
249 }
250 for (const auto &function : m_bytecode->functionEntries)
251 {
252 functions += std::format("{}() PC = {}\n", function.first, function.second);
253 }
254#ifdef TRACING
255 for (const auto &instruction : m_bytecode->instructions)
256 {
257 instructions += std::format("{}({}, {}, {})\n", opCodeToString(instruction.op), instruction.operand1,
258 instruction.operand2, instruction.operand3);
259 }
260#endif
261
262 info = std::format(
263 "BYTECODE INFORMATION:\n\nConstants: {}\n{}\nVariables: {}\n{}\nFunctions: {}\n{}\nInstructions: {}\n{}",
264 m_bytecode->constants.size(), constants, m_bytecode->variables.size(), variables,
265 m_bytecode->functionEntries.size(), functions, m_bytecode->instructions.size(), instructions);
266 return info;
267}
268
269void VM::log(const Value &msg)
270{
271 std::string s = msg.toString();
272 c_print_stdout(s.c_str(), (int64_t)s.length());
273}
274
275void VM::logerr(const Value &msg)
276{
277 std::string s = msg.toString();
278 c_print_stderr(s.c_str(), (int64_t)s.length());
279}
280
282{
283 fflush(stdout);
284}
285
287{
288 fflush(stderr);
289}
290} // namespace Phasor
void c_print_stderr(const char *s, int64_t len)
Native print error function.
Definition IO.c:105
void c_print_stdout(const char *s, int64_t len)
Native print function.
Definition IO.c:100
#define msg
Throws when the HALT opcode is reached.
Definition VM.hpp:49
std::array< Value, MAX_REGISTERS > registers
Virtual registers for register-based operations (v2.0).
Definition VM.hpp:249
ImportHandler importHandler
Import handler for loading modules.
Definition VM.hpp:246
Value operation(const OpCode &op, const int &operand1=0, const int &operand2=0, const int &operand3=0)
Execute a single operation.
Definition Operations.cpp:8
std::vector< Value > variables
Variable storage indexed by variable index, or simply: the managed heap.
Definition VM.hpp:259
Value pop()
Pop a value from the stack.
Definition Stack.cpp:17
void setImportHandler(const ImportHandler &handler)
Set the import handler for importing modules.
Definition Utility.cpp:153
std::function< void(const std::filesystem::path &path)> ImportHandler
Definition VM.hpp:70
void logerr(const Value &msg)
Log a Value to stderr.
Definition Utility.cpp:275
std::string getVersion()
Get Phasor VM version.
Definition Utility.cpp:34
void evalLoop()
Definition Utility.cpp:73
std::unique_ptr< FFI > ffi
FFI.
Definition VM.hpp:240
bool isDirectCall
is a direct call to a function
Definition VM.hpp:236
void setStatus(int newStatus)
Set VM exit code.
Definition Utility.cpp:38
std::vector< int > callStack
Call stack for function calls.
Definition VM.hpp:256
std::string getBytecodeInformation()
Get bytecode information for debugging.
Definition Utility.cpp:234
void log(const Value &msg)
Log a Value to stdout.
Definition Utility.cpp:269
int getStatus()
Definition Utility.cpp:46
void flush()
Flush stdout.
Definition Utility.cpp:281
std::pmr::vector< Value > stack
Definition VM.hpp:253
void cleanup()
Clean up the virtual machine.
Definition Utility.cpp:162
size_t pc
Program counter.
Definition VM.hpp:265
void resetStatus()
Definition Utility.cpp:42
int run(const Bytecode &bytecode, const size_t startPC=0)
Run the virtual machine Exits -1 on uncaught exception.
Definition Utility.cpp:88
void initFFI(const std::filesystem::path &path)
Initialize the FFI plugins.
Definition Utility.cpp:51
std::map< std::string, NativeFunction > nativeFunctions
Native function registry.
Definition VM.hpp:268
Value runFunction(const std::string &name, const Bytecode &bytecode)
Run a function from bytecode on the virtual machine.
Definition Utility.cpp:144
const Bytecode * m_bytecode
Bytecode to execute.
Definition VM.hpp:262
void setup(const Bytecode &bc, const size_t initialPC)
Definition Utility.cpp:58
std::pmr::monotonic_buffer_resource stack_pool
Stack.
Definition VM.hpp:252
int status
Exit code.
Definition VM.hpp:243
Value peek()
Peek at the top value on the stack.
Definition Stack.cpp:38
void flusherr()
Flush stderr.
Definition Utility.cpp:286
void reset(const bool &resetStack=true, const bool &resetFunctions=true, const bool &resetVariables=true)
Reset the virtual machine.
Definition Utility.cpp:181
std::string getInformation()
Get VM information for debugging.
Definition Utility.cpp:207
A value in the Phasor VM.
Definition Value.hpp:58
static uint64_t s[2]
Definition random.cpp:6
The Phasor Programming Language and Runtime.
Definition AST.hpp:12
std::string opCodeToString(OpCode op)
Definition map.cpp:125
Complete bytecode structure.
Definition CodeGen.hpp:50
std::unordered_map< std::string, int > functionEntries
Function name -> instruction index mapping.
Definition CodeGen.hpp:54
Instruction with up to 5 operands Format: instruction operand1, operand2, operand3 Each instruction u...
Definition CodeGen.hpp:21
int32_t operand1
First operand.
Definition CodeGen.hpp:23
int32_t operand2
Second operand.
Definition CodeGen.hpp:24
int32_t operand3
Third operand.
Definition CodeGen.hpp:25
OpCode op
Operation code.
Definition CodeGen.hpp:22