| // Copyright 2025 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/base/no_destructor.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/log/check.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "elfio/elf_types.hpp" |
| #include "elfio/elfio.hpp" |
| #include "elfio/elfio_strings.hpp" |
| #include "elfio/elfio_symbols.hpp" |
| #include "googlemock/include/gmock/gmock.h" // IWYU pragma: keep |
| #include "googletest/include/gtest/gtest.h" |
| #include "mpact/sim/util/asm/opcode_assembler_interface.h" |
| #include "mpact/sim/util/asm/resolver_interface.h" |
| #include "mpact/sim/util/asm/simple_assembler.h" |
| #include "mpact/sim/util/asm/test/riscv64x_bin_encoder_interface.h" |
| #include "mpact/sim/util/asm/test/riscv64x_encoder.h" |
| #include "re2/re2.h" |
| |
| // This file contains tests for the simple assembler using a very reduced |
| // subset of the RISC-V ISA. |
| |
| namespace { |
| |
| using ::mpact::sim::riscv::isa64::RiscV64XBinEncoderInterface; |
| using ::mpact::sim::riscv::isa64::Riscv64xSlotMatcher; |
| using ::mpact::sim::util::assembler::OpcodeAssemblerInterface; |
| using ::mpact::sim::util::assembler::RelocationInfo; |
| using ::mpact::sim::util::assembler::ResolverInterface; |
| using ::mpact::sim::util::assembler::SimpleAssembler; |
| |
| // This class implements the OpcodeAssemblerInterface using the slot matcher. |
| class RiscV64XAssembler : public OpcodeAssemblerInterface { |
| public: |
| RiscV64XAssembler(Riscv64xSlotMatcher* matcher) |
| : label_re_("^(\\S+)\\s*:"), matcher_(matcher) {}; |
| ~RiscV64XAssembler() override = default; |
| absl::StatusOr<size_t> Encode( |
| uint64_t address, absl::string_view text, |
| AddSymbolCallback add_symbol_callback, ResolverInterface* resolver, |
| std::vector<uint8_t>& bytes, |
| std::vector<RelocationInfo>& relocations) override { |
| // First check to see if there is a label, if so, add it to the symbol table |
| // with the current address. |
| std::string label; |
| if (RE2::Consume(&text, label_re_, &label)) { |
| auto status = |
| add_symbol_callback(label, address, 0, ELFIO::STT_NOTYPE, 0, 0); |
| if (!status.ok()) return status; |
| } |
| // Call the slot matcher to get the encoded value. |
| auto res = matcher_->Encode(address, text, 0, resolver, relocations); |
| if (!res.status().ok()) return res.status(); |
| // Convert the value to a byte array. |
| auto [value, size] = res.value(); |
| union { |
| uint64_t i; |
| uint8_t b[sizeof(uint64_t)]; |
| } u; |
| u.i = value; |
| for (int i = 0; i < size / 8; ++i) { |
| bytes.push_back(u.b[i]); |
| } |
| return bytes.size(); |
| } |
| |
| private: |
| RE2 label_re_; |
| Riscv64xSlotMatcher* matcher_; |
| }; |
| |
| // Sample assembly code. |
| absl::NoDestructor<std::string> kTestAssembly(R"( |
| ; text section |
| .text |
| .global main |
| main: |
| addi a0, zero, 5 |
| lui a1, %hi(semihost_param) |
| addi a1, a1, %lo(semihost_param) |
| addi t0, zero, 2 |
| sd t0, 0(a1) |
| lui t2, %hi(hello) |
| addi t2, t2, %lo(hello) |
| sd t2, 8(a1) |
| addi t0, zero, 12 |
| sd t0, 0x10(a1) |
| jal ra, semihost |
| ; now exit |
| addi a0, zero, 24 |
| lui t0, 0x20026 |
| addi t0, t0, 0x20026 |
| sd t0, 0(a1) |
| jal ra, semihost |
| exit: |
| j exit |
| |
| semihost: |
| slli zero, zero, 0x1f |
| ebreak |
| srai zero, zero, 7 |
| jr ra, 0 |
| |
| ; data section |
| |
| .data |
| .global hello |
| hello: |
| .cstring "Hello World\n" |
| .char '\n' |
| |
| ; bss |
| |
| .bss |
| .global tohost |
| tohost: |
| .space 16 |
| semihost_param: |
| .space 16 |
| )"); |
| |
| // Test fixture. It creates the assembler and parses the assembly code. |
| class RiscV64XAssemblerTest : public ::testing::Test { |
| protected: |
| RiscV64XAssemblerTest() |
| : matcher_(&bin_encoder_interface_), riscv_64x_assembler_(&matcher_) { |
| CHECK_OK(matcher_.Initialize()); |
| // Create the assembler. |
| assembler_ = |
| new SimpleAssembler(";", ELFIO::ELFCLASS64, &riscv_64x_assembler_); |
| assembler_->writer().set_os_abi(ELFIO::ELFOSABI_LINUX); |
| assembler_->writer().set_machine(ELFIO::EM_RISCV); |
| std::istringstream source(*kTestAssembly); |
| // Parse the assembly code. |
| auto status = assembler_->Parse(source); |
| CHECK_OK(status) << status.message(); |
| } |
| |
| ~RiscV64XAssemblerTest() override { delete assembler_; } |
| |
| // Access the ELF writer. |
| ELFIO::elfio& elf() { return assembler_->writer(); } |
| SimpleAssembler* assembler() const { return assembler_; } |
| |
| private: |
| RiscV64XBinEncoderInterface bin_encoder_interface_; |
| Riscv64xSlotMatcher matcher_; |
| RiscV64XAssembler riscv_64x_assembler_; |
| SimpleAssembler* assembler_; |
| }; |
| |
| // Test that the expected sections are present. |
| TEST_F(RiscV64XAssemblerTest, Sections) { |
| auto sections = elf().sections; |
| // Null section and the 6 sections listed below. |
| EXPECT_EQ(sections.size(), 7); |
| EXPECT_NE(sections[".text"], nullptr); |
| EXPECT_NE(sections[".data"], nullptr); |
| EXPECT_NE(sections[".bss"], nullptr); |
| EXPECT_NE(sections[".shstrtab"], nullptr); |
| EXPECT_NE(sections[".strtab"], nullptr); |
| EXPECT_NE(sections[".symtab"], nullptr); |
| } |
| |
| // Verify that the information about the text section is as expected. |
| TEST_F(RiscV64XAssemblerTest, Text) { |
| auto status = assembler()->CreateExecutable(0x1000, "main"); |
| CHECK_OK(status) << status.message(); |
| auto* text = elf().sections[".text"]; |
| EXPECT_EQ(text->get_type(), ELFIO::SHT_PROGBITS); |
| EXPECT_EQ(text->get_flags(), ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR); |
| EXPECT_EQ(text->get_link(), ELFIO::SHN_UNDEF); |
| EXPECT_EQ(text->get_size(), /*num inst*/ 21 * /*bytes per inst*/ 4); |
| } |
| |
| TEST_F(RiscV64XAssemblerTest, Data) { |
| auto status = assembler()->CreateExecutable(0x1000, "main"); |
| CHECK_OK(status) << status.message(); |
| auto* data = elf().sections[".data"]; |
| EXPECT_EQ(data->get_type(), ELFIO::SHT_PROGBITS); |
| EXPECT_EQ(data->get_flags(), ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE); |
| EXPECT_EQ(data->get_link(), ELFIO::SHN_UNDEF); |
| // Hello world is 12 bytes, plus the null terminator. |
| // Add one .char declaration. |
| EXPECT_EQ(data->get_size(), 14); |
| } |
| |
| TEST_F(RiscV64XAssemblerTest, Bss) { |
| auto status = assembler()->CreateExecutable(0x1000, "main"); |
| CHECK_OK(status) << status.message(); |
| auto* bss = elf().sections[".bss"]; |
| EXPECT_EQ(bss->get_type(), ELFIO::SHT_NOBITS); |
| EXPECT_EQ(bss->get_flags(), ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE); |
| EXPECT_EQ(bss->get_link(), ELFIO::SHN_UNDEF); |
| // Two .space declarations, each 16 bytes. |
| EXPECT_EQ(bss->get_size(), 32); |
| } |
| |
| TEST_F(RiscV64XAssemblerTest, RelocatableSymbols) { |
| auto status = assembler()->CreateRelocatable(); |
| CHECK_OK(status) << status.message(); |
| auto* symtab = elf().sections[".symtab"]; |
| ELFIO::symbol_section_accessor symbols(elf(), symtab); |
| ELFIO::Elf64_Addr value; |
| ELFIO::Elf_Xword size; |
| unsigned char bind; |
| unsigned char type; |
| ELFIO::Elf_Half section_index; |
| unsigned char other; |
| int num_symbols = symtab->get_size() / sizeof(ELFIO::Elf64_Sym); |
| auto symspan = absl::MakeSpan( |
| reinterpret_cast<const ELFIO::Elf64_Sym*>(symtab->get_data()), |
| num_symbols); |
| absl::flat_hash_map<std::string, int> symbol_map; |
| auto* string_accessor = |
| new ELFIO::string_section_accessor(elf().sections[".strtab"]); |
| for (int i = 0; i < num_symbols; ++i) { |
| auto name = string_accessor->get_string(symspan[i].st_name); |
| symbol_map.insert({name, i}); |
| } |
| // Verify that main is valued 0x0, global and located in the text section. |
| symbols.get_symbol("main", value, size, bind, type, section_index, other); |
| auto* sym = &symspan[symbol_map["main"]]; |
| EXPECT_EQ(sym->st_value, 0x0); |
| EXPECT_EQ(ELF_ST_BIND(sym->st_info), ELFIO::STB_GLOBAL); |
| EXPECT_EQ(sym->st_shndx, elf().sections[".text"]->get_index()); |
| EXPECT_EQ(ELF_ST_TYPE(sym->st_info), ELFIO::STT_NOTYPE); |
| // Verify that exit is valued 16 * 4, local and located in the text |
| // section. |
| sym = &symspan[symbol_map["exit"]]; |
| EXPECT_EQ(sym->st_value, 16 * 4); |
| EXPECT_EQ(ELF_ST_BIND(sym->st_info), ELFIO::STB_LOCAL); |
| EXPECT_EQ(sym->st_shndx, elf().sections[".text"]->get_index()); |
| EXPECT_EQ(ELF_ST_TYPE(sym->st_info), ELFIO::STT_NOTYPE); |
| // Verify that hello is global and located in the data section at 0x2000. |
| symbols.get_symbol("hello", value, size, bind, type, section_index, other); |
| sym = &symspan[symbol_map["hello"]]; |
| EXPECT_EQ(sym->st_value, 0); |
| EXPECT_EQ(sym->st_shndx, elf().sections[".data"]->get_index()); |
| EXPECT_EQ(ELF_ST_BIND(sym->st_info), ELFIO::STB_GLOBAL); |
| EXPECT_EQ(ELF_ST_TYPE(sym->st_info), ELFIO::STT_NOTYPE); |
| // Verify that semihost_param is global and located in the bss section at |
| // 16 bytes. |
| sym = &symspan[symbol_map["semihost_param"]]; |
| EXPECT_EQ(sym->st_value, 16); |
| EXPECT_EQ(sym->st_shndx, elf().sections[".bss"]->get_index()); |
| EXPECT_EQ(ELF_ST_BIND(sym->st_info), ELFIO::STB_LOCAL); |
| EXPECT_EQ(ELF_ST_TYPE(sym->st_info), ELFIO::STT_NOTYPE); |
| delete string_accessor; |
| } |
| |
| TEST_F(RiscV64XAssemblerTest, ExecutableSymbols) { |
| auto status = assembler()->CreateExecutable(0x1000, "main"); |
| CHECK_OK(status) << status.message(); |
| auto* symtab = elf().sections[".symtab"]; |
| ELFIO::symbol_section_accessor symbols(elf(), symtab); |
| ELFIO::Elf64_Addr value; |
| ELFIO::Elf_Xword size; |
| unsigned char bind; |
| unsigned char type; |
| ELFIO::Elf_Half section_index; |
| unsigned char other; |
| // Verify that main is valued 0x1000, global and located in the text section. |
| symbols.get_symbol("main", value, size, bind, type, section_index, other); |
| EXPECT_EQ(value, 0x1000); |
| EXPECT_EQ(section_index, elf().sections[".text"]->get_index()); |
| EXPECT_EQ(type, ELFIO::STT_NOTYPE); |
| // Verify that exit is valued 0x1000 + 16 * 4, local and located in the text |
| // section. |
| symbols.get_symbol("exit", value, size, bind, type, section_index, other); |
| EXPECT_EQ(value, 0x1000 + 16 * 4); |
| EXPECT_EQ(bind, ELFIO::STB_LOCAL); |
| EXPECT_EQ(section_index, elf().sections[".text"]->get_index()); |
| EXPECT_EQ(type, ELFIO::STT_NOTYPE); |
| // Verify that hello is global and located in the data section at 0x2000. |
| symbols.get_symbol("hello", value, size, bind, type, section_index, other); |
| EXPECT_EQ(value, 0x2000); |
| EXPECT_EQ(section_index, elf().sections[".data"]->get_index()); |
| EXPECT_EQ(bind, ELFIO::STB_GLOBAL); |
| EXPECT_EQ(type, ELFIO::STT_NOTYPE); |
| // Verify that semihost_param is global and located in the bss section at |
| // 0x2000 + 14 + alignment to 16 byte boundary, plus 16 bytes. |
| symbols.get_symbol("semihost_param", value, size, bind, type, section_index, |
| other); |
| EXPECT_EQ(value, 0x2000 + 16 + 16); |
| EXPECT_EQ(section_index, elf().sections[".bss"]->get_index()); |
| EXPECT_EQ(bind, ELFIO::STB_LOCAL); |
| EXPECT_EQ(type, ELFIO::STT_NOTYPE); |
| } |
| |
| // Verify that the first 16 instructions were assembled correctly. |
| TEST_F(RiscV64XAssemblerTest, ExecutableTextContent) { |
| auto status = assembler()->CreateExecutable(0x1000, "main"); |
| CHECK_OK(status) << status.message(); |
| auto* text = elf().sections[".text"]; |
| auto* data = text->get_data(); |
| auto* word_data = reinterpret_cast<const uint32_t*>(data); |
| // Verify the first 16 instructions. |
| EXPECT_EQ(word_data[0], 0x00500513); // addi a0, zero, 5 |
| EXPECT_EQ(word_data[1], 0x000025b7); // lui a1, semihost_param |
| EXPECT_EQ(word_data[2], 0x02058593); // addi a1, a1, semihost_param |
| EXPECT_EQ(word_data[3], 0x00200293); // addi t0, zero, 2 |
| EXPECT_EQ(word_data[4], 0x0055b023); // sd t0, 0(a1) |
| EXPECT_EQ(word_data[5], 0x000023b7); // lui t2, hello |
| EXPECT_EQ(word_data[6], 0x00038393); // addi t2, t2, hello |
| EXPECT_EQ(word_data[7], 0x0075b423); // sd t2, 8(a1) |
| EXPECT_EQ(word_data[8], 0x00c00293); // addi t0, zero, 12 |
| EXPECT_EQ(word_data[9], 0x0055b823); // sd t0, 0x10(a1) |
| EXPECT_EQ(word_data[10], 0x01c000ef); // jal ra, semihost |
| EXPECT_EQ(word_data[11], 0x01800513); // addi a0, zero, 24 |
| EXPECT_EQ(word_data[12], 0x000202b7); // lui t0, 0x20026 |
| EXPECT_EQ(word_data[13], 0x02628293); // addi t0, t0, 0x20026 |
| EXPECT_EQ(word_data[14], 0x0055b023); // sd t0, 0(a1) |
| EXPECT_EQ(word_data[15], 0x008000ef); // jal ra, semihost |
| } |
| |
| // Verify that the first 16 instructions were assembled correctly. |
| TEST_F(RiscV64XAssemblerTest, RelocatableTextContent) { |
| auto status = assembler()->CreateRelocatable(); |
| CHECK_OK(status) << status.message(); |
| auto* text = elf().sections[".text"]; |
| auto* data = text->get_data(); |
| auto* word_data = reinterpret_cast<const uint32_t*>(data); |
| // Verify the first 16 instructions. These will be slightly different from |
| // the executable version since the symbol values are not relocated to their |
| // final memory values. |
| EXPECT_EQ(word_data[0], 0x00500513); // addi a0, zero, 5 |
| EXPECT_EQ(word_data[1], 0x000005b7); // lui a1, semihost_param |
| EXPECT_EQ(word_data[2], 0x01058593); // addi a1, a1, semihost_param |
| EXPECT_EQ(word_data[3], 0x00200293); // addi t0, zero, 2 |
| EXPECT_EQ(word_data[4], 0x0055b023); // sd t0, 0(a1) |
| EXPECT_EQ(word_data[5], 0x000003b7); // lui t2, hello |
| EXPECT_EQ(word_data[6], 0x00038393); // addi t2, t2, hello |
| EXPECT_EQ(word_data[7], 0x0075b423); // sd t2, 8(a1) |
| EXPECT_EQ(word_data[8], 0x00c00293); // addi t0, zero, 12 |
| EXPECT_EQ(word_data[9], 0x0055b823); // sd t0, 0x10(a1) |
| EXPECT_EQ(word_data[10], 0x01c000ef); // jal ra, semihost |
| EXPECT_EQ(word_data[11], 0x01800513); // addi a0, zero, 24 |
| EXPECT_EQ(word_data[12], 0x000202b7); // lui t0, 0x20026 |
| EXPECT_EQ(word_data[13], 0x02628293); // addi t0, t0, 0x20026 |
| EXPECT_EQ(word_data[14], 0x0055b023); // sd t0, 0(a1) |
| EXPECT_EQ(word_data[15], 0x008000ef); // jal ra, semihost |
| } |
| |
| TEST_F(RiscV64XAssemblerTest, TextRelocations) { |
| auto status = assembler()->CreateRelocatable(); |
| CHECK_OK(status) << status.message(); |
| auto* rela_section = elf().sections[".rela.text"]; |
| EXPECT_NE(rela_section, nullptr); |
| auto* rela_data = rela_section->get_data(); |
| auto rela = |
| absl::MakeSpan(reinterpret_cast<const ELFIO::Elf64_Rela*>(rela_data), |
| rela_section->get_size() / sizeof(ELFIO::Elf64_Rela)); |
| EXPECT_EQ(rela.size(), 4); |
| } |
| |
| } // namespace |