Compare commits

..

8 Commits

Author SHA1 Message Date
CHatingPython c68763f785 chore(disfuras): implement basic disfuras 2026-08-11 16:18:40 +02:00
CHatingPython dcefe20d47 refactor(furas): use new instructions from furvm 2026-08-11 16:18:17 +02:00
CHatingPython 989fcb7199 refactor(furvm): use new instructions in executor 2026-08-11 16:17:24 +02:00
CHatingPython 4cffd27a33 refactor(furvm): use furlang views 2026-08-11 16:16:30 +02:00
CHatingPython f9b910ae13 feat(furlang): add view
Add a basic view (span from C++20).
2026-08-11 16:15:48 +02:00
CHatingPython 7a7a1ec64c feat(furvm): improve instruction abstraction
Add reading and writing to instruction abstraction.
2026-08-11 13:56:34 +02:00
CHatingPython 99dba99db2 refactor(furvm): change instruction definition
Improve instruction definition for instruction abstraction.
2026-08-11 13:36:47 +02:00
CHatingPython cc88d6b6a0 refactor(furc): move IR to middle-end 2026-08-10 19:57:56 +02:00
14 changed files with 637 additions and 316 deletions
+1
View File
@@ -15,6 +15,7 @@ add_subdirectory(furlang)
add_subdirectory(furvm)
add_subdirectory(furc)
add_subdirectory(furas)
add_subdirectory(disfuras)
if(DOXYGEN_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
+5
View File
@@ -0,0 +1,5 @@
file(GLOB_RECURSE DISFURAS_SRCS "src/**.cpp")
file(GLOB_RECURSE DISFURAS_HDRS "include/**.hpp")
add_executable(disfuras ${DISFURAS_SRCS} ${DISFURAS_HDRS})
target_include_directories(disfuras PRIVATE include/)
target_link_libraries(disfuras PUBLIC furlang libfurc libfurvm)
+238
View File
@@ -0,0 +1,238 @@
#include "furvm/instruction.hpp"
#include "furvm/module.hpp"
#include <cassert>
#include <exception>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>
using namespace std::string_literals;
void print_type(const furvm::mod_type& type, const furvm::mod& mod) {
switch (type.type) {
case furvm::mod_type::S8: std::cout << "$s8"; return;
case furvm::mod_type::S16: std::cout << "$s16"; return;
case furvm::mod_type::S32: std::cout << "$s32"; return;
case furvm::mod_type::S64: std::cout << "$s64"; return;
case furvm::mod_type::U8: std::cout << "$u8"; return;
case furvm::mod_type::U16: std::cout << "$u16"; return;
case furvm::mod_type::U32: std::cout << "$u32"; return;
case furvm::mod_type::U64: std::cout << "$u64"; return;
case furvm::mod_type::Ptr:
std::cout << "ptr ";
print_type(*mod.type_at(type.value.typeRef), mod);
return;
case furvm::mod_type::Ref:
std::cout << "ref ";
print_type(*mod.type_at(type.value.typeRef), mod);
return;
case furvm::mod_type::Array:
std::cout << "array ";
print_type(*mod.type_at(type.value.array.typeId), mod);
if (type.value.array.size == 0) {
std::cout << " dynamic";
} else {
std::cout << ' ' << type.value.array.size;
}
return;
case furvm::mod_type::Import: std::cout << "import " << type.value.imprt.modId << "::t" << type.value.imprt.typeId;
case furvm::mod_type::Count: break;
}
assert(false);
}
static const char* typeNames[furvm::instruction::Count] = {
// NoOperation:
"nop",
// PushS8:
"push $__t0",
// PushU8:
"push $__t1",
// PushS16:
"push $__t2",
// PushU16:
"push $__t3",
// PushS32:
"push $__t4",
// PushU32:
"push $__t5",
// 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:
"lengthof",
// Load:
"load",
// Store:
"store",
// Call:
"call",
// Jump:
"jmp",
// JumpNotZero:
"jnz",
// Return:
"ret",
};
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <module.fmod>\n";
return 1;
}
try {
std::ifstream file(argv[1], std::ios::binary | std::ios::in);
furvm::mod mod;
try {
mod = furvm::mod::load(file);
} catch (std::exception ex) {
std::cerr << "Failed to load module " << argv[1] << ": " << ex.what() << '\n';
return 1;
}
std::cout << "; Generated with disfuras\n";
std::cout << "; Types:\n";
for (const auto& ptr : mod.types()) {
const auto& [header, type] = *ptr;
std::cout << "type __t" << header.id() << " = ";
print_type(type, mod);
std::cout << '\n';
}
std::unordered_map<furvm::function_id, std::string> funcNames;
std::size_t unnamedCounter = 0;
std::unordered_map<std::size_t, std::string> labels;
std::unordered_set<std::size_t> deadLabels;
std::size_t labelCounter = 0;
std::cout << "; Functions:\n";
for (const auto& ptr : mod.functions()) {
const auto& [header, func] = *ptr;
auto it = mod.function_map().find(header.id());
if (it != mod.function_map().end()) {
std::cout << "public func " << (funcNames[header.id()] = it->second.first);
} else {
std::cout << "func " << (funcNames[header.id()] = "__f"s + std::to_string(unnamedCounter++));
}
for (const auto& param : func.signature().params) {
std::cout << " $__t" << param.id();
}
std::cout << " = ";
if (func.signature().returnType.has_value())
std::cout << "$__t" << func.signature().returnType->id() << ' '; // NOLINT
switch (func.type()) {
case furvm::function_t::Normal: {
auto it = labels.find(func.position());
if (it == labels.end()) it = labels.emplace(func.position(), funcNames[header.id()]).first;
deadLabels.insert(func.position());
std::cout << '#' << it->second;
} break;
case furvm::function_t::Native: {
std::cout << "native " << func.native();
} break;
case furvm::function_t::Import: {
std::cout << "import " << func.imp().mod << "::" << func.imp().function;
} break;
}
std::cout << '\n';
}
std::cout << "; Bytecode:\n";
// Label pass:
for (std::size_t off = 0; off < mod.bytecode().size();) {
furvm::instruction instr{};
off += instr.read(mod.bytecode_view().subview(off));
if (instr.type != furvm::instruction::Jump && instr.type != furvm::instruction::JumpNotZero) continue;
if (labels.find(off + instr.arg.s8) == labels.end())
labels[off + instr.arg.s8] = "__l"s + std::to_string(labelCounter++);
deadLabels.insert(off + instr.arg.s8);
}
// Actual printing pass:
for (std::size_t off = 0; off < mod.bytecode().size();) {
if (auto it = labels.find(off); it != labels.end()) {
std::cout << it->second << ":\n";
deadLabels.erase(off);
}
furvm::instruction instr{};
off += instr.read(mod.bytecode_view().subview(off));
std::cout << " " << typeNames[instr.type];
switch (instr.arg.type) {
case furvm::instruction_argument::None: break;
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 << ' ' << std::to_string(instr.arg.s8); break;
case furvm::instruction_argument::U32: std::cout << ' ' << std::to_string(instr.arg.u8); break;
case furvm::instruction_argument::Constant: throw std::runtime_error("unimplemented");
case furvm::instruction_argument::Type: std::cout << " $__t" << instr.arg.u16; break;
case furvm::instruction_argument::Variable: std::cout << " %" << instr.arg.u16; break;
case furvm::instruction_argument::Function: std::cout << ' ' << funcNames[instr.arg.s16]; break;
case furvm::instruction_argument::Offset: std::cout << " #" << labels[instr.arg.s16]; break;
case furvm::instruction_argument::Count: break;
}
std::cout << '\n';
}
if (!deadLabels.empty()) {
std::cerr << "Malformed module: invalid jumps\n";
return 1;
}
} catch (const std::exception& ex) {
std::cerr << "Exception uncaught: " << ex.what() << '\n';
return 1;
}
return 0;
}
// TODO: Disassemble into FIR (furlang's IR)
+7
View File
@@ -0,0 +1,7 @@
func println $s32 = native println
public func main = #main
main:
push $s32 69
call $s32 println
ret
+59 -67
View File
@@ -21,48 +21,36 @@ using namespace std::string_literals;
namespace {
struct instruction {
furvm::instruction_t fur{};
enum arg_type {
None = 0,
Type,
Constant,
Variable,
Function,
Label,
} arg = None;
};
// NOLINTBEGIN
std::unordered_map<enum token::type, instruction> instructions = {
{ token::Array, { furvm::instruction_t::Array, instruction::Type } },
{ token::Get, { furvm::instruction_t::Get } },
{ token::Set, { furvm::instruction_t::Set } },
{ token::Drop, { furvm::instruction_t::Drop } },
{ token::Dup, { furvm::instruction_t::Duplicate } },
{ token::Swap, { furvm::instruction_t::Swap } },
{ token::Clone, { furvm::instruction_t::Clone } },
{ token::Ref, { furvm::instruction_t::Reference } },
{ token::Add, { furvm::instruction_t::Add } },
{ token::Sub, { furvm::instruction_t::Sub } },
{ token::Mul, { furvm::instruction_t::Mul } },
{ token::Div, { furvm::instruction_t::Div } },
{ token::Mod, { furvm::instruction_t::Mod } },
{ token::Eq, { furvm::instruction_t::Equals } },
{ token::Neq, { furvm::instruction_t::NotEquals } },
{ token::Lt, { furvm::instruction_t::LessThan } },
{ token::Gt, { furvm::instruction_t::GreaterThan } },
{ token::Le, { furvm::instruction_t::LessEqual } },
{ token::Ge, { furvm::instruction_t::GreaterEqual } },
{ token::Ptrof, { furvm::instruction_t::Pointerof } },
{ token::Sizeof, { furvm::instruction_t::Sizeof } },
{ token::Lenof, { furvm::instruction_t::Lengthof } },
{ token::Load, { furvm::instruction_t::Load, instruction::Variable } },
{ token::Store, { furvm::instruction_t::Store, instruction::Variable } },
{ token::Call, { furvm::instruction_t::Call, instruction::Function } },
{ token::Jmp, { furvm::instruction_t::Jump, instruction::Label } },
{ token::Jnz, { furvm::instruction_t::JumpNotZero, instruction::Label } },
{ token::Ret, { furvm::instruction_t::Return } },
std::unordered_map<enum token::type, furvm::instruction_t> instructions = {
{ token::Array, furvm::instruction_t::Array },
{ token::Get, furvm::instruction_t::Get },
{ token::Set, furvm::instruction_t::Set },
{ token::Drop, furvm::instruction_t::Drop },
{ token::Dup, furvm::instruction_t::Duplicate },
{ token::Swap, furvm::instruction_t::Swap },
{ token::Clone, furvm::instruction_t::Clone },
{ token::Ref, furvm::instruction_t::Reference },
{ token::Add, furvm::instruction_t::Add },
{ token::Sub, furvm::instruction_t::Sub },
{ token::Mul, furvm::instruction_t::Mul },
{ token::Div, furvm::instruction_t::Div },
{ token::Mod, furvm::instruction_t::Mod },
{ token::Eq, furvm::instruction_t::Equals },
{ token::Neq, furvm::instruction_t::NotEquals },
{ token::Lt, furvm::instruction_t::LessThan },
{ token::Gt, furvm::instruction_t::GreaterThan },
{ token::Le, furvm::instruction_t::LessEqual },
{ token::Ge, furvm::instruction_t::GreaterEqual },
{ token::Ptrof, furvm::instruction_t::Pointerof },
{ token::Sizeof, furvm::instruction_t::Sizeof },
{ token::Lenof, furvm::instruction_t::Lengthof },
{ token::Load, furvm::instruction_t::Load },
{ token::Store, furvm::instruction_t::Store },
{ token::Call, furvm::instruction_t::Call },
{ token::Jmp, furvm::instruction_t::Jump },
{ token::Jnz, furvm::instruction_t::JumpNotZero },
{ token::Ret, furvm::instruction_t::Return },
};
// NOLINTEND
@@ -214,12 +202,12 @@ struct mod_context {
handle.dispatch();
}
for (auto unknown : label.unknowns) {
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(label.offset - unknown);
const auto jmpOff = static_cast<std::ptrdiff_t>(label.offset) - static_cast<std::ptrdiff_t>(unknown);
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
assert(false); // TODO: Further jumps are not implemented
}
mod.bytecode()[unknown - 1] = jmpOff;
mod.bytecode()[unknown - 1] = static_cast<std::int8_t>(jmpOff);
}
label.functions = {};
label.unknowns = {};
@@ -453,10 +441,11 @@ struct mod_context {
case token::Ret: {
auto it = instructions.find(result->type);
assert(it != instructions.end());
mod.bytecode().push_back(static_cast<furvm::byte>(it->second.fur));
switch (it->second.arg) {
case instruction::None: break;
case instruction::Type: {
furvm::instruction instr{ it->second };
instr.arg.type = furvm::instruction::s_arguments[instr.type];
switch (instr.arg.type) {
case furvm::instruction_argument::None: break;
case furvm::instruction_argument::Type: {
result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
result = eat_token(lexer, token::Identifier);
@@ -464,25 +453,19 @@ struct mod_context {
auto type = types.find(std::string(result->value.string));
if (type == types.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = type->second.id();
mod.bytecode().push_back((id >> 0) & 0xFF);
mod.bytecode().push_back((id >> 8) & 0xFF);
mod.bytecode().push_back((id >> 16) & 0xFF);
mod.bytecode().push_back((id >> 24) & 0xFF);
instr.arg.u32 = type->second.id();
} break;
case instruction::Constant: {
case furvm::instruction_argument::Constant: {
assert(false); // TODO: Unimplemented
} break;
case instruction::Variable: {
case furvm::instruction_argument::Variable: {
result = eat_token(lexer, token::Percent);
if (!result) return result.error;
result = eat_token(lexer, token::Unsigned);
if (!result) return result.error;
std::uint16_t var = result->value.uint;
mod.bytecode().push_back((var >> 0) & 0xFF);
mod.bytecode().push_back((var >> 8) & 0xFF);
instr.arg.u16 = result->value.uint;
} break;
case instruction::Function: {
case furvm::instruction_argument::Function: {
furvm::function_sig signature;
while ((result = next_token(lexer)).error.type == generator_error::Success &&
result->type == token::Dolar) {
@@ -499,11 +482,10 @@ struct mod_context {
auto func = functions.find(std::make_pair(name, signature));
if (func == functions.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = func->second.id();
mod.bytecode().push_back((id >> 0) & 0xFF);
mod.bytecode().push_back((id >> 8) & 0xFF);
auto id = func->second.id();
instr.arg.u16 = id;
} break;
case instruction::Label: {
case furvm::instruction_argument::Offset: {
result = eat_token(lexer, token::Sha256);
if (!result) return result.error;
result = eat_token(lexer, token::Identifier);
@@ -511,18 +493,28 @@ struct mod_context {
auto& label = labels[std::string(result->value.string)];
auto offset = label.offset;
if (offset == label_context::INVALID) {
label.unknowns.push_back(mod.bytecode().size() + 1);
mod.bytecode().push_back(0);
return { generator_error::Success };
label.unknowns.push_back(mod.bytecode().size() + 2);
instr.arg.s8 = 0;
break;
}
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(offset - mod.bytecode().size() - 1);
const auto jmpOff =
static_cast<std::ptrdiff_t>(offset) - static_cast<std::ptrdiff_t>(mod.bytecode().size()) - 2;
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
assert(false); // TODO: Further jumps are not implemented
}
mod.bytecode().push_back(jmpOff);
instr.arg.s8 = static_cast<std::int8_t>(jmpOff);
} break;
case furvm::instruction_argument::S8:
case furvm::instruction_argument::U8:
case furvm::instruction_argument::S16:
case furvm::instruction_argument::U16:
case furvm::instruction_argument::S32:
case furvm::instruction_argument::U32:
case furvm::instruction_argument::Count:
default: throw std::runtime_error("unreachable");
}
instr.write(mod.bytecode());
return { generator_error::Success };
}
@@ -1,5 +1,5 @@
#ifndef FURC_BACK_IR_HPP
#define FURC_BACK_IR_HPP
#ifndef FURC_MIDDLE_IR_HPP
#define FURC_MIDDLE_IR_HPP
#include "furc/front/ast.hpp"
#include "furlang/arena.hpp"
@@ -403,4 +403,4 @@ private:
} // namespace furc
#endif // FURC_BACK_IR_HPP
#endif // FURC_MIDDLE_IR_HPP
+1 -1
View File
@@ -1,6 +1,6 @@
#include "furc/back/ir.hpp"
#include "furc/front/lexer.hpp"
#include "furc/front/parser.hpp"
#include "furc/middle/ir.hpp"
#include "furlang/arena.hpp"
int main(void) {
@@ -1,4 +1,4 @@
#include "furc/back/ir.hpp"
#include "furc/middle/ir.hpp"
#include "furc/front/ast.hpp"
+38
View File
@@ -0,0 +1,38 @@
#ifndef FURLANG_VIEW_HPP
#define FURLANG_VIEW_HPP
#include <algorithm>
#include <cstddef>
#include <limits>
#include <stdexcept>
namespace furlang {
template <typename T>
class view {
public:
constexpr view() noexcept = default;
constexpr view(const T* data, std::size_t size) noexcept
: m_data(data), m_size(size) {}
public:
constexpr view subview(std::size_t offset, std::size_t count = std::numeric_limits<std::size_t>::max()) {
if (count > 0 && offset >= m_size) throw std::runtime_error("offset too large");
return { m_data + offset, std::min(m_size - offset, count) };
}
const T& operator[](std::size_t offset) const {
if (offset >= m_size) throw std::runtime_error("out of bounds");
return m_data[offset];
}
constexpr const T* data() const { return m_data; }
constexpr std::size_t size() const { return m_size; }
private:
const T* m_data = nullptr;
std::size_t m_size = 0;
};
} // namespace furlang
#endif // FURLANG_VIEW_HPP
+1 -5
View File
@@ -75,11 +75,7 @@ class constant;
// instruction.hpp
/**
* @enum instruction_t
* @brief Furvm's instruction type.
*/
enum class instruction_t : byte;
struct instruction_argument;
/**
* @struct instruction
+81 -202
View File
@@ -1,221 +1,100 @@
#ifndef FURVM_INSTRUCTION_HPP
#define FURVM_INSTRUCTION_HPP
#include "furlang/view.hpp"
#include "furvm/fwd.hpp"
#include <cstddef>
#include <vector>
namespace furvm {
enum class instruction_t : byte {
/**
* @brief No operation.
*/
NoOperation = 0,
struct instruction_argument {
enum type_e {
None = 0,
S8,
U8,
S16,
U16,
S32,
U32,
Constant,
Type,
Variable,
Function,
Offset,
/**
* @brief Pushes an s8 integer from a byte onto the stack.
*/
PushS8,
Count,
} type;
union {
std::int8_t s8;
std::uint8_t u8;
std::int16_t s16;
std::uint16_t u16;
std::int32_t s32;
std::uint32_t u32;
};
/**
* @brief Pushes an u8 integer from a byte onto the stack.
*/
PushU8,
static const std::size_t s_sizes[Count];
static const bool s_signedness[Count];
/**
* @brief Pushes an s16 integer from two byte onto the stack.
*/
PushS16,
/**
* @brief Pushes an u16 integer from two byte onto the stack.
*/
PushU16,
/**
* @brief Pushes an s32 integer from a byte onto the stack.
*/
PushS32,
/**
* @brief Pushes an u32 integer from a byte onto the stack.
*/
PushU32,
/**
* @brief Pushes a constant onto the stack.
*
* Pushes a constant from the constant pool denoted by two next bytes in little-endian onto the stack.
*/
PushConstant,
/**
* @brief Pushes a new array onto the stack.
*
* Type is the next 4 bytes in little-endian.
* If the type is dynamic the array's size will be popped off of the stack.
*/
Array,
/**
* @brief Pushes an element from an array onto the stack.
*/
Get,
/**
* @brief Sets an array element.
*/
Set,
/**
* @brief Pops top element from the stack.
*/
Drop,
/**
* @brief Duplicates top element on the stack.
*/
Duplicate,
/**
* @brief Swaps two top elements of the stack.
*/
Swap,
/**
* @brief Clones top element on the stack.
*/
Clone,
/**
* @brief Pushes a new reference onto the stack.
*
* Pops the top thing from the stack and pushes its reference.
*/
Reference,
/**
* @brief Adds two things together on the stack.
*/
Add,
/**
* @brief Subtracts two things together on the stack.
*/
Sub,
/**
* @brief Multiplies two things together on the stack.
*/
Mul,
/**
* @brief Divides two things together on the stack.
*/
Div,
/**
* @brief Modulos two things together on the stack.
*/
Mod,
/**
* @brief Compares two top-most things from the stack for equality.
*/
Equals,
/**
* @brief Compares two top-most things from the stack for inequality.
*/
NotEquals,
/**
* @brief Compares if the first top-most thing is less than the second top-most thing.
*/
LessThan,
/**
* @brief Compares if the first top-most thing is greater than the second top-most thing.
*/
GreaterThan,
/**
* @brief Compares if the first top-most thing is less than or equal to the second top-most thing.
*/
LessEqual,
/**
* @brief Compares if the first top-most thing is greater than or equal to the second top-most thing.
*/
GreaterEqual,
/**
* @brief Pushes a pointer of popped-off thing onto the stack.
*/
Pointerof,
/**
* @brief Pushes a size of popped-off thing onto the stack.
*/
Sizeof,
/**
* @brief Pushes a length of popped-off thing onto the stack.
*/
Lengthof,
/**
* @brief Pushes a variable onto the stack.
*
* Fetches a variable denoted by next two bytes in little-endian and pushes it onto the stack.
*/
Load,
/**
* @brief Stores an element from the stack in a variable.
*
* Pops a thing from the stack and stores it in a variable denoted by next two bytes in little-endian.
*/
Store,
/**
* @brief Calls a function.
*
* Calls a function denoted by next two bytes in little-endian from current frame's module.
*/
Call,
/**
* @brief Jumps to an instruction relative to the current instruction.
*
* Jumps to an instruction relative to the current instruction with offset denoted by next byte.
*/
Jump,
/**
* @brief Jumps to an instruction relative to the current instruction if top thing on the stack is not zero.
*
* Jumps to an instruction relative to the current instruction with offset denoted by next byte if the top thing on
* the stack is not zero (is true).
*/
JumpNotZero,
/**
* @brief Pops the current call frame.
*/
Return,
std::size_t size() const { return s_sizes[type]; }
bool is_signed() const { return s_signedness[type]; }
};
using instruction_argument_t = instruction_argument::type_e;
struct instruction {
instruction_t type; /**< Instruction type. */
enum type_e : byte {
NoOperation = 0,
PushS8,
PushU8,
PushS16,
PushU16,
PushS32,
PushU32,
PushConstant,
Array,
Get,
Set,
Drop,
Duplicate,
Swap,
Clone,
Reference,
Add,
Sub,
Mul,
Div,
Mod,
Equals,
NotEquals,
LessThan,
GreaterThan,
LessEqual,
GreaterEqual,
Pointerof,
Sizeof,
Lengthof,
Load,
Store,
Call,
Jump,
JumpNotZero,
Return,
/**
* @brief Instruction value.
*/
union value {
constant_index constant; /**< Constant instruction argument. */
} value; /**< Instruction value. */
Count,
} type;
instruction_argument arg;
static const instruction_argument_t s_arguments[Count];
std::size_t read(furlang::view<std::uint8_t> in);
std::size_t write(std::vector<std::uint8_t>& out) const;
};
using instruction_t = instruction::type_e;
} // namespace furvm
#endif // FURVM_INSTRUCTION_HPP
+8 -1
View File
@@ -2,6 +2,7 @@
#define FURVM_MODULE_HPP
#include "furlang/utility/hash.hpp"
#include "furlang/view.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp"
@@ -195,7 +196,7 @@ public:
*
* @return A constant reference to the bytecode.
*/
constexpr const bytecode_t& bytecode() const { return m_bytecode; }
furlang::view<std::uint8_t> bytecode_view() const { return { m_bytecode.data(), m_bytecode.size() }; }
public:
/**
* @brief Emplaces a function in the module's function container.
@@ -284,6 +285,10 @@ public:
m_functionMap.erase(it);
}
}
const handle_container<function_h>& functions() const { return m_functions; }
const auto& function_map() const { return m_functionMap; }
public:
template <typename NameFwd, typename Func>
void set_native_function(NameFwd&& name, Func&& func) {
@@ -341,6 +346,8 @@ public:
void erase_type(Args&&... args) {
m_types.erase(std::forward<Args>(args)...);
}
const handle_container<mod_type_h>& types() const { return m_types; }
public:
/**
* @brief Prints the module in a bytecode form to an output stream.
+16 -36
View File
@@ -1,5 +1,6 @@
#include "furvm/executor.hpp"
#include "furlang/view.hpp"
#include "furvm/context.hpp" // IWYU pragma: keep
#include "furvm/exceptions.hpp"
#include "furvm/function.hpp" // IWYU pragma: keep
@@ -131,45 +132,36 @@ void executor::step() {
struct frame& frame = m_frames.top();
instruction_t instr = static_cast<instruction_t>((*frame.mod).byte_at(frame.position++));
switch (instr) {
instruction instr{};
frame.position += instr.read(frame.mod->bytecode_view().subview(frame.position));
switch (instr.type) {
case instruction_t::NoOperation: break;
case instruction_t::PushS8: {
push_thing({ (struct thing_type){ thing_type::S8 }, m_context->thing_alloc() })->get<thing_type::s8>() =
static_cast<thing_type::s8>(frame.mod->byte_at(frame.position++));
instr.arg.s8;
} break;
case instruction_t::PushU8: {
push_thing({ (struct thing_type){ thing_type::U8 }, m_context->thing_alloc() })->get<thing_type::u8>() =
static_cast<thing_type::u8>(frame.mod->byte_at(frame.position++));
instr.arg.u8;
} break;
case instruction_t::PushS16: {
thing_type::u16 value = frame.mod->byte_at(frame.position++);
value |= static_cast<thing_type::u16>(frame.mod->byte_at(frame.position++) << 8);
push_thing({ (struct thing_type){ thing_type::S16 }, m_context->thing_alloc() })->get<thing_type::s16>() =
static_cast<thing_type::s16>(value);
instr.arg.s16;
} break;
case instruction_t::PushU16: {
thing_type::u16 value = frame.mod->byte_at(frame.position++);
value |= static_cast<thing_type::u16>(frame.mod->byte_at(frame.position++) << 8);
push_thing({ (struct thing_type){ thing_type::U16 }, m_context->thing_alloc() })->get<thing_type::u16>() =
value;
instr.arg.u16;
} break;
case instruction_t::PushS32: {
push_thing({ (struct thing_type){ thing_type::S32 }, m_context->thing_alloc() })->get<thing_type::s32>() =
static_cast<thing_type::s32>(frame.mod->byte_at(frame.position++));
instr.arg.s8; // NOLINT
} break;
case instruction_t::PushU32: {
push_thing({ (struct thing_type){ thing_type::U32 }, m_context->thing_alloc() })->get<thing_type::u32>() =
static_cast<thing_type::u32>(frame.mod->byte_at(frame.position++));
static_cast<thing_type::u32>(instr.arg.u8);
} break;
case instruction_t::Array: {
mod_type_id typeId = static_cast<mod_type_id>(frame.mod->byte_at(frame.position)) |
(static_cast<mod_type_id>(frame.mod->byte_at(frame.position + 1)) << 8) |
(static_cast<mod_type_id>(frame.mod->byte_at(frame.position + 2)) << 16) |
(static_cast<mod_type_id>(frame.mod->byte_at(frame.position + 3)) << 24);
frame.position += 4;
const auto& type = *mod_to_thing_type(frame.mod, *frame.mod->type_at(typeId));
const auto& type = *mod_to_thing_type(frame.mod, *frame.mod->type_at(instr.arg.u32));
if (type.type != thing_type::Array || type.value.array.type == nullptr || type.value.array.type == &type)
throw std::runtime_error("invalid array type");
@@ -304,31 +296,19 @@ void executor::step() {
length->get<thing_type::u64>() = thing->length();
} break;
case instruction_t::Load: {
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte_at(frame.position)) |
(static_cast<std::uint16_t>(frame.mod->byte_at(frame.position + 1)) << 8);
frame.position += 2;
push_thing(load_thing(variable));
push_thing(load_thing(instr.arg.u16));
} break;
case instruction_t::Store: {
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte_at(frame.position)) |
(static_cast<std::uint16_t>(frame.mod->byte_at(frame.position + 1)) << 8);
frame.position += 2;
store_thing(variable, std::move(pop_thing()));
store_thing(instr.arg.u16, std::move(pop_thing()));
} break;
case instruction_t::Call: {
function_id funcId = static_cast<std::uint16_t>(frame.mod->byte_at(frame.position)) |
(static_cast<std::uint16_t>(frame.mod->byte_at(frame.position + 1)) << 8);
frame.position += 2;
push_frame(frame.mod, *frame.mod->function_at(funcId));
push_frame(frame.mod, *frame.mod->function_at(instr.arg.u16));
} break;
case instruction_t::Jump: {
std::int8_t offset = static_cast<std::int8_t>(frame.mod->byte_at(frame.position++));
frame.position += offset;
frame.position += instr.arg.s8;
} break;
case instruction_t::JumpNotZero: {
byte offset = frame.mod->byte_at(frame.position++);
auto cond = pop_thing();
if (cond->integer() != 0) frame.position += (std::int8_t)offset;
if (pop_thing()->integer() != 0) frame.position += instr.arg.s8;
} break;
case instruction_t::Return: {
pop_frame();
+178
View File
@@ -0,0 +1,178 @@
#include "furvm/instruction.hpp"
#include "furlang/view.hpp"
#include <stdexcept>
namespace furvm {
const std::size_t instruction_argument::s_sizes[instruction_argument::Count] = {
// None:
0,
// S8:
1,
// U8:
1,
// S16:
2,
// U16:
2,
// S32
1,
// U32
1,
// Constant:
4,
// Type:
4,
// Variable:
2,
// Function:
4,
// Offset:
1,
};
const bool instruction_argument::s_signedness[instruction_argument::Count] = {
// None:
false,
// S8:
true,
// U8:
false,
// S16:
true,
// U16:
false,
// S32
true,
// U32:
false,
// Constant:
false,
// Type:
false,
// Variable:
false,
// Function:
false,
// Offset:
true,
};
const instruction_argument_t instruction::s_arguments[instruction::Count] = {
// NoOperation:
instruction_argument::None,
// PushS8:
instruction_argument::S8,
// PushU8:
instruction_argument::U8,
// PushS16:
instruction_argument::S16,
// PushU16:
instruction_argument::U16,
// PushS32:
instruction_argument::S32,
// PushU32:
instruction_argument::U32,
// PushConstant:
instruction_argument::Constant,
// Array:
instruction_argument::Type,
// Get:
instruction_argument::None,
// Set:
instruction_argument::None,
// Drop:
instruction_argument::None,
// Duplicate:
instruction_argument::None,
// Swap:
instruction_argument::None,
// Clone:
instruction_argument::None,
// Reference:
instruction_argument::None,
// Add:
instruction_argument::None,
// Sub:
instruction_argument::None,
// Mul:
instruction_argument::None,
// Div:
instruction_argument::None,
// Mod:
instruction_argument::None,
// Equals:
instruction_argument::None,
// NotEquals:
instruction_argument::None,
// LessThan:
instruction_argument::None,
// GreaterThan:
instruction_argument::None,
// LessEqual:
instruction_argument::None,
// GreaterEqual:
instruction_argument::None,
// Pointerof:
instruction_argument::None,
// Sizeof:
instruction_argument::None,
// Lengthof:
instruction_argument::None,
// Load:
instruction_argument::Variable,
// Store:
instruction_argument::Variable,
// Call:
instruction_argument::Function,
// Jump:
instruction_argument::Offset,
// JumpNotZero:
instruction_argument::Offset,
// Return:
instruction_argument::None,
};
std::size_t instruction::read(furlang::view<std::uint8_t> in) {
type = static_cast<type_e>(in[0]);
if (type >= Count) throw std::runtime_error("invalid instruction");
arg.type = s_arguments[type];
furlang::view argView = in.subview(1, arg.size());
if (argView.size() != arg.size()) throw std::runtime_error("invalid bytecode");
switch (argView.size()) {
case 0: break;
case 1: arg.u8 = argView[0]; break;
case 2: arg.u16 = argView[0] | (argView[1] << 8); break;
case 4: arg.u32 = argView[0] | (argView[1] << 8) | (argView[2] << 16) | (argView[3] << 24); break;
default: throw std::runtime_error("unreachable");
}
return 1 + argView.size();
}
std::size_t instruction::write(std::vector<std::uint8_t>& out) const {
if (type >= Count) throw std::runtime_error("invalid instruction");
if (s_arguments[type] != arg.type) throw std::runtime_error("malformed instruction");
out.push_back(static_cast<char>(type));
switch (arg.size()) {
case 0: break;
case 1: out.push_back(arg.u8); break;
case 2:
out.push_back(arg.u16 & 0xFF);
out.push_back((arg.u16 >> 8) & 0xFF);
break;
case 4:
out.push_back(arg.u32 & 0xFF);
out.push_back((arg.u32 >> 8) & 0xFF);
out.push_back((arg.u32 >> 16) & 0xFF);
out.push_back((arg.u32 >> 24) & 0xFF);
break;
default: throw std::runtime_error("unreachable");
}
return 0;
}
} // namespace furvm