#include "elna/backend/riscv.hpp" #include #include #include namespace elna::riscv { instruction::instruction(base_opcode opcode) { this->representation = static_cast::type>(opcode); } instruction& instruction::i(x_register rd, funct3_t funct3, x_register rs1, std::uint32_t immediate) { this->representation |= (static_cast::type>(rd) << 7) | (static_cast::type>(funct3) << 12) | (static_cast::type>(rs1) << 15) | (immediate << 20); return *this; } instruction& instruction::s(std::uint32_t imm, funct3_t funct3, x_register rs1, x_register rs2) { this->representation |= ((imm & 0x1f) << 7) | (static_cast::type>(funct3) << 12) | (static_cast::type>(rs1) << 15) | (static_cast::type>(rs2) << 20) | ((imm & 0xfe0) << 20); return *this; } instruction& instruction::b(std::uint32_t imm, funct3_t funct3, x_register rs1, x_register rs2) { this->representation |= ((imm & 0x800) >> 4) | ((imm & 0x1e) << 7) | (static_cast::type>(funct3) << 12) | (static_cast::type>(rs1) << 15) | (static_cast::type>(rs2) << 20) | ((imm & 0x7e0) << 20) | ((imm & 0x1000) << 19); return *this; } instruction& instruction::r(x_register rd, funct3_t funct3, x_register rs1, x_register rs2, funct7_t funct7) { this->representation |= (static_cast::type>(rd) << 7) | (static_cast::type>(funct3) << 12) | (static_cast::type>(rs1) << 15) | (static_cast::type>(rs2) << 20) | (static_cast::type>(funct7) << 25); return *this; } instruction& instruction::u(x_register rd, std::uint32_t imm) { this->representation |= (static_cast::type>(rd) << 7) | (imm << 12); return *this; } instruction& instruction::j(x_register rd, std::uint32_t imm) { this->representation |= (static_cast::type>(rd) << 7) | (imm & 0xff000) | ((imm & 0x800) << 9) | ((imm & 0x7fe) << 20) | ((imm & 0x100000) << 11); return *this; } const std::byte *instruction::cbegin() const { return reinterpret_cast(&this->representation); } const std::byte *instruction::cend() const { return reinterpret_cast(&this->representation) + sizeof(this->representation); } static void relocate(std::string_view name, address_t target, std::vector& references, std::vector& instructions, std::shared_ptr> writer) { references.push_back(reference()); references.back().name = name; references.back().offset = writer->size() + instructions.size() * 4; references.back().target = target; } static void prologue(std::vector& instructions) { instructions.push_back(instruction(base_opcode::opImm)); instructions.push_back(instruction(base_opcode::store)); instructions.push_back(instruction(base_opcode::store)); instructions.push_back(instruction(base_opcode::opImm)); } static void epilogue(const std::size_t stack_size, std::vector& instructions) { instructions[0].i(x_register::sp, funct3_t::addi, x_register::sp, -stack_size); instructions[1].s(0, funct3_t::sw, x_register::sp, x_register::s0); instructions[2].s(4, funct3_t::sw, x_register::sp, x_register::ra); instructions[3].i(x_register::s0, funct3_t::addi, x_register::sp, stack_size); // Epilogue. instructions.push_back(instruction(base_opcode::load) .i(x_register::s0, funct3_t::lw, x_register::sp, 0)); instructions.push_back(instruction(base_opcode::load) .i(x_register::ra, funct3_t::lw, x_register::sp, 4)); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::sp, funct3_t::addi, x_register::sp, stack_size)); instructions.push_back(instruction(base_opcode::jalr) .i(x_register::zero, funct3_t::jalr, x_register::ra, 0)); } static void generate_intrinsics(std::shared_ptr> writer, std::vector& references) { writer->sink("printf"); { std::vector instructions; auto format_string = writer->sink(reinterpret_cast("%c\n\0"), 4); prologue(instructions); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::a1, funct3_t::addi, x_register::zero, 't')); instructions.push_back(instruction(base_opcode::branch) .b(8, funct3_t::bne, x_register::zero, x_register::a0)); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::a1, funct3_t::addi, x_register::zero, 'f')); relocate(format_string, address_t::high20, references, instructions, writer); instructions.push_back(instruction(base_opcode::lui).u(x_register::a5, 0)); relocate(format_string, address_t::lower12i, references, instructions, writer); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::a0, funct3_t::addi, x_register::a5, 0)); relocate("printf", address_t::text, references, instructions ,writer); instructions.push_back(instruction(base_opcode::auipc).u(x_register::ra, 0)); instructions.push_back(instruction(base_opcode::jalr) .i(x_register::ra, funct3_t::jalr, x_register::ra, 0)); epilogue(8, instructions); writer->sink("writeb", reinterpret_cast(instructions.data()), instructions.size() * sizeof(instruction)); } { std::vector instructions; auto format_string = writer->sink(reinterpret_cast("%d\n\0"), 4); prologue(instructions); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::a1, funct3_t::addi, x_register::a0, 0)); relocate(format_string, address_t::high20, references, instructions, writer); instructions.push_back(instruction(base_opcode::lui).u(x_register::a5, 0)); relocate(format_string, address_t::lower12i, references, instructions, writer); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::a0, funct3_t::addi, x_register::a5, 0)); relocate("printf", address_t::text, references, instructions, writer); instructions.push_back(instruction(base_opcode::auipc).u(x_register::ra, 0)); instructions.push_back(instruction(base_opcode::jalr) .i(x_register::ra, funct3_t::jalr, x_register::ra, 0)); epilogue(8, instructions); writer->sink("writei", reinterpret_cast(instructions.data()), instructions.size() * sizeof(instruction)); } } static void load_in_register(std::shared_ptr operand, const x_register target, std::shared_ptr procedure_info, std::vector& instructions) { std::shared_ptr integer_operand{ nullptr }; std::shared_ptr variable_operand{ nullptr }; std::shared_ptr temporary_variable{ nullptr }; std::shared_ptr variable_symbol{ nullptr }; std::shared_ptr parameter_symbol{ nullptr }; if ((integer_operand = std::dynamic_pointer_cast(operand)) != nullptr) { instructions.push_back(instruction(base_opcode::opImm) .i(target, funct3_t::addi, x_register::zero, integer_operand->value())); } else if ((variable_operand = std::dynamic_pointer_cast(operand)) != nullptr) { const auto& name = procedure_info->scope()->lookup(variable_operand->name()); if ((variable_symbol = std::dynamic_pointer_cast(name)) != nullptr) { instructions.push_back(instruction(base_opcode::load) .i(target, funct3_t::lw, x_register::s0, variable_symbol->offset)); } else if ((parameter_symbol = std::dynamic_pointer_cast(name)) != nullptr) { instructions.push_back(instruction(base_opcode::load) .i(target, funct3_t::lw, x_register::s0, parameter_symbol->offset)); } } else if ((temporary_variable = std::dynamic_pointer_cast(operand)) != nullptr) { instructions.push_back(instruction(base_opcode::load)); instructions.back().i(target, funct3_t::lw, x_register::s0, procedure_info->local_stack_size + 4 * temporary_variable->counter()); } } static void store_from_register(std::shared_ptr destination, const x_register target, std::shared_ptr procedure_info, std::vector& instructions) { std::shared_ptr variable_operand{ nullptr }; std::shared_ptr temporary_variable{ nullptr }; std::shared_ptr variable_symbol{ nullptr }; if ((variable_operand = std::dynamic_pointer_cast(destination)) != nullptr) { variable_symbol = std::dynamic_pointer_cast( procedure_info->scope()->lookup(variable_operand->name())); instructions.push_back(instruction(base_opcode::store) .s(variable_symbol->offset, funct3_t::sw, x_register::s0, target)); } else if ((temporary_variable = std::dynamic_pointer_cast(destination)) != nullptr) { instructions.push_back(instruction(base_opcode::store)); instructions.back().s(procedure_info->local_stack_size + 4 * temporary_variable->counter(), funct3_t::sw, x_register::s0, target); } } static void perform_binary_operation(const source::binary_operator operation, std::shared_ptr lhs, std::shared_ptr rhs, std::shared_ptr destination, std::shared_ptr procedure_info, std::vector& instructions) { constexpr auto lhs_register = x_register::a0; std::shared_ptr variable_operand{ nullptr }; std::shared_ptr temporary_variable{ nullptr }; std::shared_ptr variable_symbol{ nullptr }; load_in_register(lhs, x_register::a0, procedure_info, instructions); load_in_register(rhs, x_register::t0, procedure_info, instructions); // Calculate the result and assign it to a variable on the stack. switch (operation) { case source::binary_operator::sum: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::add, x_register::a0, x_register::t0)); break; case source::binary_operator::subtraction: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::sub, x_register::a0, x_register::t0, funct7_t::sub)); break; case source::binary_operator::multiplication: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::mul, x_register::a0, x_register::t0, funct7_t::muldiv)); break; case source::binary_operator::division: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::div, x_register::a0, x_register::t0, funct7_t::muldiv)); break; case source::binary_operator::equals: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::sub, x_register::a0, x_register::t0, funct7_t::sub)); instructions.push_back(instruction(base_opcode::opImm) .i(lhs_register, funct3_t::sltiu, lhs_register, 1)); break; case source::binary_operator::not_equals: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::sub, x_register::a0, x_register::t0, funct7_t::sub)); instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::sltu, x_register::zero, lhs_register)); break; case source::binary_operator::less: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::sltu, x_register::a0, x_register::t0)); break; case source::binary_operator::greater_equal: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::sltu, x_register::t0, x_register::a0)); break; case source::binary_operator::greater: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::slt, x_register::a0, x_register::t0)); instructions.push_back(instruction(base_opcode::opImm) .i(lhs_register, funct3_t::xori, lhs_register, 1)); break; case source::binary_operator::less_equal: instructions.push_back(instruction(base_opcode::op) .r(lhs_register, funct3_t::slt, x_register::t0, x_register::a0)); instructions.push_back(instruction(base_opcode::opImm) .i(lhs_register, funct3_t::xori, lhs_register, 1)); break; } store_from_register(destination, lhs_register, procedure_info, instructions); } std::vector generate(source::intermediate_code_generator generator, std::shared_ptr table, std::shared_ptr> writer) { std::vector references; generate_intrinsics(writer, references); for (auto& [identifier, intermediate_code] : generator) { std::vector instructions; auto main_symbol = std::dynamic_pointer_cast(table->lookup(identifier)); std::size_t argument_offset{ 0 }; const auto stack_size = static_cast( intermediate_code.variable_counter() * 4 + 8 + main_symbol->stack_size()); std::unordered_map> missing_labels; std::unordered_map>::iterator missing_label = missing_labels.end(); std::unordered_map local_labels; for (auto& quadruple : intermediate_code) { switch (quadruple.operation()) { case source::quadruple_operator::start: prologue(instructions); break; case source::quadruple_operator::stop: epilogue(stack_size, instructions); break; case source::quadruple_operator::add: perform_binary_operation(source::binary_operator::sum, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::sub: perform_binary_operation(source::binary_operator::subtraction, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::mul: perform_binary_operation(source::binary_operator::multiplication, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::div: perform_binary_operation(source::binary_operator::division, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::eq: perform_binary_operation(source::binary_operator::equals, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::neq: perform_binary_operation(source::binary_operator::not_equals, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::lt: perform_binary_operation(source::binary_operator::less, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::ge: perform_binary_operation(source::binary_operator::greater_equal, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::gt: perform_binary_operation(source::binary_operator::greater, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::le: perform_binary_operation(source::binary_operator::less_equal, quadruple.operand1(), quadruple.operand2(), quadruple.operand3(), main_symbol, instructions); break; case source::quadruple_operator::load: { auto operand_identifier = std::dynamic_pointer_cast(quadruple.operand1())->name(); auto variable_symbol = std::dynamic_pointer_cast( main_symbol->scope()->lookup(operand_identifier)); load_in_register(quadruple.operand1(), x_register::a0, main_symbol, instructions); instructions.push_back(instruction(base_opcode::load) .i(x_register::a0, funct3_t::lw, x_register::a0, 0)); store_from_register(quadruple.operand3(), x_register::a0, main_symbol, instructions); } break; case source::quadruple_operator::ref: { auto operand_identifier = std::dynamic_pointer_cast(quadruple.operand1())->name(); auto variable_symbol = std::dynamic_pointer_cast( main_symbol->scope()->lookup(operand_identifier)); instructions.push_back(instruction(base_opcode::opImm) .i(x_register::a0, funct3_t::addi, x_register::s0, variable_symbol->offset)); store_from_register(quadruple.operand3(), x_register::a0, main_symbol, instructions); } break; case source::quadruple_operator::beqz: load_in_register(quadruple.operand1(), x_register::a0, main_symbol, instructions); instructions.push_back(instruction(base_opcode::branch)); missing_labels.emplace( std::dynamic_pointer_cast(quadruple.operand3())->counter(), [before_branch = instructions.size() - 1, &instructions](std::size_t target) { instructions[before_branch].b((target - before_branch) * 4, funct3_t::beq, x_register::zero, x_register::a0); }); break; case source::quadruple_operator::j: { auto local_label = local_labels.find( std::dynamic_pointer_cast(quadruple.operand3())->counter()); if (local_label != local_labels.end()) { auto offset = -(instructions.size() - local_label->second) * 4; instructions.push_back(instruction(base_opcode::auipc).u(x_register::a0, 0)); instructions.push_back(instruction(base_opcode::jalr) .i(x_register::zero, funct3_t::jalr, x_register::a0, offset)); } } break; case source::quadruple_operator::label: { auto label_counter = std::dynamic_pointer_cast(quadruple.operand3())->counter(); missing_label = missing_labels.find(label_counter); if (missing_label != missing_labels.end()) { missing_label->second(instructions.size()); } local_labels[label_counter] = instructions.size(); } break; case source::quadruple_operator::assign: load_in_register(quadruple.operand1(), x_register::a0, main_symbol, instructions); store_from_register(quadruple.operand3(), x_register::a0, main_symbol, instructions); break; case source::quadruple_operator::param: load_in_register(quadruple.operand1(), x_register::a0, main_symbol, instructions); instructions.push_back(instruction(base_opcode::store) .s(argument_offset, funct3_t::sw, x_register::sp, x_register::a0)); argument_offset += 4; break; case source::quadruple_operator::call: relocate(std::dynamic_pointer_cast(quadruple.operand1())->name(), address_t::text, references, instructions, writer); instructions.push_back(instruction(base_opcode::auipc).u(x_register::ra, 0)); instructions.push_back(instruction(base_opcode::jalr) .i(x_register::ra, funct3_t::jalr, x_register::ra, 0)); argument_offset = 0; break; } } writer->sink(identifier, reinterpret_cast(instructions.data()), instructions.size() * sizeof(instruction)); } return references; } }