diff --git a/.clang-tidy b/.clang-tidy index a37eb72..2ea4be6 100755 --- a/.clang-tidy +++ b/.clang-tidy @@ -30,7 +30,8 @@ Checks: > -cppcoreguidelines-owning-memory, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, - -cppcoreguidelines-pro-bounds-array-to-pointer-decay + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -bugprone-forward-declaration-namespace WarningsAsErrors: "*" diff --git a/furc/CMakeLists.txt b/furc/CMakeLists.txt index e89257e..cc6876c 100644 --- a/furc/CMakeLists.txt +++ b/furc/CMakeLists.txt @@ -4,7 +4,7 @@ add_library(libfurc ${FURC_SRCS} ${FURC_HDRS}) target_include_directories(libfurc PUBLIC include/) target_compile_definitions(libfurc PRIVATE LIBFURC) set_target_properties(libfurc PROPERTIES PREFIX "") -target_link_libraries(libfurc PUBLIC furlang) +target_link_libraries(libfurc PUBLIC furlang libfurvm) add_executable(furc src/main.cpp) target_link_libraries(furc PRIVATE libfurc) @@ -13,4 +13,4 @@ include(GoogleTest) file(GLOB_RECURSE FURC_TESTS "test/**.cpp") add_executable(furc_tests ${FURC_TESTS}) target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main) -gtest_discover_tests(furc_tests) \ No newline at end of file +gtest_discover_tests(furc_tests) diff --git a/furc/include/furc/back/furvm.hpp b/furc/include/furc/back/furvm.hpp new file mode 100644 index 0000000..5da948f --- /dev/null +++ b/furc/include/furc/back/furvm.hpp @@ -0,0 +1,43 @@ +#ifndef FURC_BACK_FURVM_HPP +#define FURC_BACK_FURVM_HPP + +#include "furlang/ir/operand.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace furc { +namespace back { + +class furvm_generator { +public: + furvm_generator() = default; +public: + static furvm::mod generate(furlang::ir::mod& mod); +private: + static void generate_function(furvm::mod& mod, const furlang::ir::function& function); + + struct function_context { + std::unordered_map blockOffsets; + std::unordered_map> incompleteJumps; + + std::unordered_map variables; + furvm::variable_t variableCounter{ 0 }; + }; + + static void generate_instruction(furvm::mod& mod, function_context& ctx, const furlang::ir::instruction& instr); + static void generate_operand(furvm::mod& mod, function_context& ctx, const furlang::ir::operand& operand); + + static void generate_jump(furvm::mod& mod, function_context& ctx, furlang::ir::block_index block, bool conditional); +}; + +} // namespace back +} // namespace furc + +#endif // FURC_BACK_FURVM_HPP diff --git a/furc/src/back/furvm.cpp b/furc/src/back/furvm.cpp new file mode 100644 index 0000000..7dd73f8 --- /dev/null +++ b/furc/src/back/furvm.cpp @@ -0,0 +1,143 @@ +#include "furc/back/furvm.hpp" + +#include "furlang/ir/instruction.hpp" + +#include +#include + +namespace furc::back { + +furvm::mod furvm_generator::generate(furlang::ir::mod& mod) { + furvm::mod vmMod; + + for (const auto& function : mod.functions()) { + generate_function(vmMod, *function); + } + + return vmMod; +} + +void furvm_generator::generate_function(furvm::mod& mod, const furlang::ir::function& function) { + auto func = mod.emplace_function(function.name(), mod.bytecode().size()); + func.dispatch(); + + function_context ctx; + for (furlang::ir::block_index idx = 0; idx < function.blocks().size(); ++idx) { + if (auto it = ctx.incompleteJumps.find(idx); it != ctx.incompleteJumps.end()) { + for (std::size_t offset : it->second) { + mod.bytecode()[offset] = mod.bytecode().size() - offset - 1; + } + ctx.incompleteJumps.erase(it); + } + + ctx.blockOffsets[idx] = mod.bytecode().size(); + for (const auto& instr : function.blocks()[idx]->instructions()) + generate_instruction(mod, ctx, *instr); + generate_instruction(mod, ctx, *function.blocks()[idx]->exit()); + } +} + +static inline furvm::instruction_t binary_op_type(furlang::ir::binary_op_instruction_t type) { + switch (type) { + case furlang::ir::binary_op_instruction_t::Add: return furvm::instruction_t::Add; + case furlang::ir::binary_op_instruction_t::Sub: return furvm::instruction_t::Sub; + case furlang::ir::binary_op_instruction_t::Mul: return furvm::instruction_t::Mul; + case furlang::ir::binary_op_instruction_t::Div: return furvm::instruction_t::Div; + case furlang::ir::binary_op_instruction_t::Mod: return furvm::instruction_t::Mod; + case furlang::ir::binary_op_instruction_t::Eq: return furvm::instruction_t::Equals; + case furlang::ir::binary_op_instruction_t::NotEq: return furvm::instruction_t::NotEquals; + case furlang::ir::binary_op_instruction_t::LessThan: return furvm::instruction_t::LessThan; + case furlang::ir::binary_op_instruction_t::GreaterThan: return furvm::instruction_t::GreaterThan; + case furlang::ir::binary_op_instruction_t::LessEq: return furvm::instruction_t::LessEqual; + case furlang::ir::binary_op_instruction_t::GreaterEq: return furvm::instruction_t::GreaterEqual; + } + throw std::runtime_error("unreachable"); +} + +void furvm_generator::generate_instruction(furvm::mod& mod, + function_context& ctx, + const furlang::ir::instruction& instr) { + for (const auto& operand : instr.sources()) + generate_operand(mod, ctx, *operand); + + switch (instr.type()) { + case furlang::ir::instruction_t::Assign: { + if (ctx.variables.find(instr.destination().reg()) == ctx.variables.end()) { + ctx.variables[instr.destination().reg()] = ctx.variableCounter++; + } + auto var = ctx.variables[instr.destination().reg()]; + mod.bytecode().push_back(static_cast(furvm::instruction_t::Store)); + mod.bytecode().push_back((var >> 0) & 0xFF); + mod.bytecode().push_back((var >> 8) & 0xFF); + static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed"); + } break; + case furlang::ir::instruction_t::BinaryOp: { + const auto& op = dynamic_cast(instr); + mod.bytecode().push_back(static_cast(binary_op_type(op.op_type()))); + + if (ctx.variables.find(instr.destination().reg()) == ctx.variables.end()) { + ctx.variables[instr.destination().reg()] = ctx.variableCounter++; + } + auto var = ctx.variables[instr.destination().reg()]; + mod.bytecode().push_back(static_cast(furvm::instruction_t::Store)); + mod.bytecode().push_back((var >> 0) & 0xFF); + mod.bytecode().push_back((var >> 8) & 0xFF); + static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed"); + } break; + case furlang::ir::instruction_t::Branch: { + const auto& branch = dynamic_cast(instr); + generate_jump(mod, ctx, branch.block(), false); + } break; + case furlang::ir::instruction_t::BranchCond: { + const auto& branch = dynamic_cast(instr); + generate_jump(mod, ctx, branch.if_block(), true); + generate_jump(mod, ctx, branch.else_block(), false); + } break; + case furlang::ir::instruction_t::Return: { + if (instr.sources().empty()) { + mod.bytecode().push_back(static_cast(furvm::instruction_t::Return)); + } else { + mod.bytecode().push_back(static_cast(furvm::instruction_t::ReturnValue)); + } + } break; + case furlang::ir::instruction_t::Call: + case furlang::ir::instruction_t::Alloca: throw std::runtime_error("unimplemented instruction"); + case furlang::ir::instruction_t::Phi: throw std::runtime_error("unreachable"); + } +} + +void furvm_generator::generate_operand(furvm::mod& mod, function_context& ctx, const furlang::ir::operand& operand) { + switch (operand.type()) { + case furlang::ir::operand_t::Register: { + if (ctx.variables.find(operand.reg()) == ctx.variables.end()) throw std::runtime_error("unregistered register"); + auto var = ctx.variables[operand.reg()]; + mod.bytecode().push_back(static_cast(furvm::instruction_t::Load)); + mod.bytecode().push_back((var >> 0) & 0xFF); + mod.bytecode().push_back((var >> 8) & 0xFF); + static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed"); + } break; + case furlang::ir::operand_t::Integer: { + mod.bytecode().push_back(static_cast(furvm::instruction_t::PushB2I)); + mod.bytecode().push_back(operand.integer()); + } break; + case furlang::ir::operand_t::Variable: + case furlang::ir::operand_t::String: throw std::runtime_error("unimplemented operand"); + case furlang::ir::operand_t::None: throw std::runtime_error("unreachable"); + } +} + +void furvm_generator::generate_jump(furvm::mod& mod, + function_context& ctx, + furlang::ir::block_index block, + bool conditional) { + mod.bytecode().push_back( + static_cast(conditional ? furvm::instruction_t::JumpNotZero : furvm::instruction_t::Jump)); + if (auto it = ctx.blockOffsets.find(block); it != ctx.blockOffsets.end()) { + mod.bytecode().push_back(it->second - mod.bytecode().size() - 1); + } else { + ctx.incompleteJumps[block].push_back(mod.bytecode().size()); + mod.bytecode().push_back(0); + } +} + +} // namespace furc::back diff --git a/furc/src/main.cpp b/furc/src/main.cpp index 8865671..9aef4da 100644 --- a/furc/src/main.cpp +++ b/furc/src/main.cpp @@ -1,11 +1,13 @@ #ifndef LIBFURC #include "furc/ast/program.hpp" +#include "furc/back/furvm.hpp" #include "furc/front/ir_generator.hpp" #include "furc/front/parser.hpp" #include "furc/front/post_process.hpp" #include "furlang/arena.hpp" +#include #include int main(void) { @@ -54,6 +56,9 @@ int main(void) { } } + std::ofstream file("./a.fmod", std::ios::binary); + furc::back::furvm_generator::generate(mod).serialize(file); + return 0; } catch (...) { std::cerr << "Caught an exception in main!\n";