Compare commits

...

9 Commits

8 changed files with 355 additions and 10 deletions
+20
View File
@@ -36,4 +36,24 @@ public:
void execute(context& ctx, const command_info& info) override;
};
class continue_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
class next_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
class break_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
class info_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
#endif // COMMAND_HPP
+6
View File
@@ -13,8 +13,14 @@ struct context {
furvm::function_h mainFunction;
bool running = true;
bool halt = true;
void kill() const;
void init();
void run();
void print_instruction() const;
};
#endif // CONTEXT_HPP
+137 -1
View File
@@ -1,9 +1,16 @@
#include "command.hpp"
#include "furvm/executor.hpp"
#include "furvm/function.hpp"
#include "furvm/instruction.hpp"
#include "furvm/module.hpp"
#include "furvm/thing.hpp"
#include <cctype>
#include <cstddef>
#include <iostream>
#include <random>
#include <string>
#include <utility>
command_info command::parse(std::string_view line) {
@@ -50,6 +57,135 @@ void quit_command::execute(context& ctx, const command_info& info) {
}
void run_command::execute(context& ctx, const command_info& info) {
if (!ctx.executor->done()) {
std::cout << "A program is currently running, do you want to kill it? (y/N) ";
std::string answer;
if (!std::getline(std::cin, answer)) {
ctx.running = false;
return;
}
if (answer != "y" && answer != "Y" && answer != "yes") return;
ctx.kill();
}
std::cout << "Running the program\n";
ctx.run();
std::cout << "Execution finished\n";
}
void continue_command::execute(context& ctx, const command_info& info) {
ctx.run();
}
void next_command::execute(context& ctx, const command_info& info) {
if (ctx.executor->done()) {
std::cout << "No program's currently running\n";
return;
}
ctx.executor->unsuspend();
ctx.executor->step();
ctx.print_instruction();
}
static void print_type(const furvm::thing_type& type) {
switch (type.type) {
case furvm::thing_type::S8: std::cout << "s8"; break;
case furvm::thing_type::S16: std::cout << "s16"; break;
case furvm::thing_type::S32: std::cout << "s32"; break;
case furvm::thing_type::S64: std::cout << "s64"; break;
case furvm::thing_type::U8: std::cout << "u8"; break;
case furvm::thing_type::U16: std::cout << "u16"; break;
case furvm::thing_type::U32: std::cout << "u32"; break;
case furvm::thing_type::U64: std::cout << "u64"; break;
case furvm::thing_type::Ptr: std::cout << "ptr"; break;
case furvm::thing_type::Ref: std::cout << "ref"; break;
case furvm::thing_type::Array:
std::cout << "array(";
print_type(*type.value.array.type);
std::cout << ", ";
if (type.value.array.size == 0)
std::cout << "dynamic";
else
std::cout << type.value.array.size;
std::cout << ")";
break;
case furvm::thing_type::Count: break;
}
}
static void print_thing(const furvm::thing<>& thing) {
std::cout << '(';
print_type(thing.type());
std::cout << ") ";
switch (thing.type().type) {
case furvm::thing_type::S8: std::cout << std::to_string(thing.get<furvm::thing_type::s8>()); break;
case furvm::thing_type::S16: std::cout << thing.get<furvm::thing_type::s16>(); break;
case furvm::thing_type::S32: std::cout << thing.get<furvm::thing_type::s32>(); break;
case furvm::thing_type::S64: std::cout << thing.get<furvm::thing_type::s64>(); break;
case furvm::thing_type::U8: std::cout << std::to_string(thing.get<furvm::thing_type::u8>()); break;
case furvm::thing_type::U16: std::cout << thing.get<furvm::thing_type::u16>(); break;
case furvm::thing_type::U32: std::cout << thing.get<furvm::thing_type::u32>(); break;
case furvm::thing_type::U64: std::cout << thing.get<furvm::thing_type::u64>(); break;
case furvm::thing_type::Ptr: std::cout << thing.get<const void*>(); break;
case furvm::thing_type::Array: {
if (thing.type().value.array.size == 0) std::cout << thing.length();
std::cout << "{ ";
for (std::size_t i = 0; i < thing.length(); ++i) {
if (i > 0) std::cout << ", ";
print_thing(thing.at(i));
}
std::cout << " }";
} break;
case furvm::thing_type::Ref:
case furvm::thing_type::Count: break;
}
}
static void breakpoint_hit(furvm::executor& executor, void* data) {
std::cout << "Breakpoint hit\n";
context* ctx = reinterpret_cast<context*>(data);
ctx->halt = true;
ctx->print_instruction();
}
void break_command::execute(context& ctx, const command_info& info) {
if (info.args.empty()) {
std::cerr << "Usage: " << info.commandName << " <function name>\n";
return;
}
std::size_t count = 0;
for (const auto& [id, sigPair] : ctx.mod->function_map()) {
if (sigPair.first != info.args[0]) continue;
const auto& func = ctx.mod->function_at(id);
if (func->type() != furvm::function_t::Normal) continue;
count += 1;
ctx.mod->set_breakpoint(func->position(), furvm::breakpoint{ breakpoint_hit, &ctx });
}
if (count == 0) {
std::cerr << "No function \"" << info.args[0] << "\" found!\n";
return;
}
std::cout << "Breakpoint set in " << count << " places\n";
}
void info_command::execute(context& ctx, const command_info& info) {
if (info.args.empty()) {
std::cout << "Possible arguments:\n";
std::cout << "- variables\n";
} else if (info.args[0] == "variables") {
if (ctx.executor->frames().empty()) {
std::cerr << "Not running\n";
return;
}
std::cout << "Variables:\n";
const auto& frame = ctx.executor->top_frame();
for (std::size_t i = 0; i < frame.variables.size(); ++i) {
// TODO: Add debug information to modules
std::cout << "- %" << i << " = ";
print_thing(frame.variables[i]);
std::cout << '\n';
}
} else {
std::cerr << "Unexpected argument \"" << info.args[0] << "\"\n";
}
}
+129 -3
View File
@@ -1,9 +1,135 @@
#include "context.hpp"
void context::run() {
if ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done)
#include <furvm/executor.hpp>
#include <furvm/instruction.hpp>
#include <iostream>
#include <stdexcept>
void context::kill() const {
while (!executor->done())
executor->pop_frame();
}
void context::init() {
kill();
executor->push_frame(mod, *mainFunction);
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
executor->clear_flags();
}
void context::run() {
if (executor->done()) init();
executor->unsuspend();
halt = false;
while (!executor->done() && !halt)
executor->step();
if (executor->done()) {
std::cout << "Execution finished\n";
halt = true;
}
}
void context::print_instruction() const {
static const char* s_instrNames[furvm::instruction::Count] = {
// NoOperation
"nop",
// PushS8
"push $s8",
// PushU8
"push $u8",
// PushS16
"push $s16",
// PushU16
"push $U16",
// PushS32
"push $S32",
// PushU32
"push $U32",
// PushConstant
"push",
// Array
"array",
// Get
"get",
// Set
"set",
// Drop
"drop",
// Duplicate
"dup",
// Swap
"swap",
// Clone
"clone",
// Reference
"ref",
// Add
"add",
// Sub
"sub",
// Mul
"mul",
// Div
"div",
// Mod
"mod",
// Equals
"eq",
// NotEquals
"ne",
// LessThan
"lt",
// GreaterThan
"gt",
// LessEqual
"le",
// GreaterEqual
"ge",
// Pointerof
"pointerof",
// Sizeof
"sizeof",
// Lengthof
"lenof",
// Load
"load",
// Store
"store",
// LoadGlobal
"loadg",
// StoreGlobal
"storeg",
// Call
"call",
// Jump
"jmp",
// JumpNotZero
"jnz",
// Return
"ret",
};
furvm::instruction instr{};
instr.read(mod->bytecode_view().subview(executor->top_frame().position));
std::cout << s_instrNames[instr.type];
if (instr.arg.type != furvm::instruction_argument_t::None) {
std::cout << ' ';
switch (instr.arg.type) {
case furvm::instruction_argument::S8: std::cout << std::to_string(instr.arg.s8); break;
case furvm::instruction_argument::U8: std::cout << std::to_string(instr.arg.u8); break;
case furvm::instruction_argument::S16: std::cout << instr.arg.s16; break;
case furvm::instruction_argument::U16: std::cout << instr.arg.u16; break;
case furvm::instruction_argument::S32: std::cout << instr.arg.s32; break;
case furvm::instruction_argument::U32: std::cout << instr.arg.u32; break;
case furvm::instruction_argument::None:
case furvm::instruction_argument::Count: break;
case furvm::instruction_argument::Constant: throw std::runtime_error("unimplemented");
case furvm::instruction_argument::Type: std::cout << "$__t" << instr.arg.u32; break;
case furvm::instruction_argument::Variable: std::cout << "%" << instr.arg.u16; break;
case furvm::instruction_argument::GlobalVariable: std::cout << "%__g" << instr.arg.u16; break;
case furvm::instruction_argument::Function: std::cout << "<a function>"; break;
case furvm::instruction_argument::Offset: std::cout << std::to_string(instr.arg.s8); break;
}
}
std::cout << '\n';
}
+5 -1
View File
@@ -43,8 +43,12 @@ int main(int argc, char** argv) {
}
static std::unordered_map<std::string_view, command*> s_commands;
s_commands["quit"] = s_commands["q"] = new quit_command();
s_commands["exit"] = s_commands["quit"] = s_commands["q"] = new quit_command();
s_commands["run"] = s_commands["r"] = new run_command();
s_commands["continue"] = s_commands["c"] = new continue_command();
s_commands["next"] = s_commands["n"] = new next_command();
s_commands["break"] = s_commands["b"] = new break_command();
s_commands["info"] = s_commands["i"] = new info_command();
try {
std::ifstream file(argv[1], std::ios::binary | std::ios::in);
+19 -5
View File
@@ -7,7 +7,6 @@
#include <functional>
#include <stack>
#include <utility>
#include <vector>
namespace furvm {
@@ -15,6 +14,8 @@ namespace furvm {
enum class executor_flags : std::uint32_t {
Suspended = (1 << 0), /**< Execution suspended. */
Done = (1 << 1), /**< Execution is finished. */
JustHit = (1 << 16), /**< Executor just hit a breakpoint. */
};
static inline executor_flags operator|(executor_flags lhs, executor_flags rhs) {
@@ -29,13 +30,15 @@ static inline executor_flags operator~(executor_flags flags) {
return executor_flags(~static_cast<std::uint32_t>(flags));
}
using executor_callback = std::function<void(executor&)>;
class executor {
friend class context;
private:
executor(context* context)
: m_context(context) {}
public:
static constexpr executor_flags STATE_FLAGS = executor_flags::JustHit;
using new_frame_callback = std::function<void(executor&)>;
public:
/**
* @brief Executor frame.
@@ -84,6 +87,17 @@ public:
* @return The flags.
*/
executor_flags flags() const { return m_flags; }
bool done() const { return (m_flags & executor_flags::Done) == executor_flags::Done; }
bool suspended() const { return (m_flags & executor_flags::Suspended) == executor_flags::Suspended; }
void unsuspend() { m_flags = m_flags & ~executor_flags::Suspended; }
void clear_flags() {
m_flags = m_flags & STATE_FLAGS;
m_flags = m_frames.empty() ? executor_flags::Done : furvm::executor_flags{ 0 };
}
public:
/**
* @brief Pushes a new frame.
@@ -174,13 +188,13 @@ private:
private:
static bool compare_thing_types(const thing_type& lhs, const thing_type& rhs);
private:
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
executor_flags m_flags = executor_flags::Done;
context* m_context;
std::stack<frame> m_frames;
std::stack<thing<>> m_stack;
executor_callback m_newFrameCb = nullptr;
new_frame_callback m_newFrameCb = nullptr;
};
} // namespace furvm
+16
View File
@@ -145,6 +145,11 @@ struct mod_type {
}
};
struct breakpoint {
std::function<void(executor&, void*)> callback;
void* data = nullptr;
};
class mod {
friend class function;
friend class serializer;
@@ -379,6 +384,15 @@ public:
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
return m_globalVariables[var];
}
public:
template <typename Fwd, typename = std::enable_if_t<std::is_constructible_v<breakpoint, Fwd>>>
void set_breakpoint(bytecode_pos pos, Fwd&& breakpoint) {
m_breakpoints[pos] = std::forward<Fwd>(breakpoint);
}
bool has_breakpoint(bytecode_pos pos) const { return m_breakpoints.find(pos) != m_breakpoints.end(); }
const breakpoint& breakpoint_at(bytecode_pos pos) const { return m_breakpoints.at(pos); }
public:
/**
* @brief Prints the module in a bytecode form to an output stream.
@@ -410,6 +424,8 @@ private:
std::vector<thing<>> m_globalVariables;
std::unordered_map<std::string, native_function> m_nativeFunctions;
std::unordered_map<bytecode_pos, breakpoint> m_breakpoints;
};
} // namespace furvm
+23
View File
@@ -104,6 +104,13 @@ void executor::push_frame(const mod_h& mod, function function) {
default: throw std::runtime_error("unexpected function type");
}
if (m_newFrameCb) m_newFrameCb(*this);
if (!m_frames.empty()) {
m_flags = m_flags & ~executor_flags::Done;
} else {
m_flags = m_flags | executor_flags::Done;
}
m_flags = m_flags & ~executor_flags::JustHit;
}
struct executor::frame executor::pop_frame() {
@@ -117,6 +124,14 @@ struct executor::frame executor::pop_frame() {
}
if (m_stack.size() != frame.stackBase) throw std::runtime_error("unexhausted stack");
if (returnValue.has_value()) push_thing(std::move(returnValue.value()));
if (!m_frames.empty()) {
m_flags = m_flags & ~executor_flags::Done;
} else {
m_flags = m_flags | executor_flags::Done;
}
m_flags = m_flags & ~executor_flags::JustHit;
return frame;
}
@@ -176,6 +191,14 @@ void executor::step() {
struct frame& frame = m_frames.top();
if ((m_flags & executor_flags::JustHit) != executor_flags::JustHit && frame.mod->has_breakpoint(frame.position)) {
m_flags = m_flags | executor_flags::Suspended | executor_flags::JustHit;
const auto& bp = frame.mod->breakpoint_at(frame.position);
bp.callback(*this, bp.data);
return;
}
m_flags = m_flags & ~executor_flags::JustHit;
instruction instr{};
frame.position += instr.read(frame.mod->bytecode_view().subview(frame.position));
switch (instr.type) {