diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-09 22:05:14 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-09 22:05:14 +0800 |
| commit | 3a57627e59ba18b6f9f9aae7bb60c390c0d3575a (patch) | |
| tree | 736ca062bfffbd68177a512dfe145b06c16d11b1 /backend/unittests/Target/RISCVMCTest.cpp | |
| parent | 379ff50dcc7ccf977379aee68fa9d10f69065d23 (diff) | |
| download | rcc-project-3a57627e59ba18b6f9f9aae7bb60c390c0d3575a.tar.gz rcc-project-3a57627e59ba18b6f9f9aae7bb60c390c0d3575a.tar.xz | |
[MC] Implement RV64GC object emission
Diffstat (limited to 'backend/unittests/Target/RISCVMCTest.cpp')
| -rw-r--r-- | backend/unittests/Target/RISCVMCTest.cpp | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/backend/unittests/Target/RISCVMCTest.cpp b/backend/unittests/Target/RISCVMCTest.cpp new file mode 100644 index 0000000..1d44c73 --- /dev/null +++ b/backend/unittests/Target/RISCVMCTest.cpp @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/CompilationUnit.h" +#include "rcc/CodeGen/GlobalObject.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/MC/MCInst.h" +#include "rcc/Target/RISCV/RISCVAsmBackend.h" +#include "rcc/Target/RISCV/RISCVFixupKinds.h" +#include "rcc/Target/RISCV/RISCVInstPrinter.h" +#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h" +#include "rcc/Target/RISCV/RISCVObjectFile.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstdint> +#include <cstring> +#include <initializer_list> +#include <iostream> +#include <span> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +int fail(std::string_view Message) { + std::cerr << Message << '\n'; + return 1; +} + +[[nodiscard]] rcc::mc::MCOperand reg(rcc::riscv::PhysicalRegisterID ID) { + return rcc::mc::MCOperand::registerOperand( + rcc::mc::MCRegister::fromValue(static_cast<std::uint32_t>(ID))); +} + +[[nodiscard]] rcc::mc::MCOperand imm(std::int64_t Value) { + return rcc::mc::MCOperand::immediate(Value); +} + +[[nodiscard]] rcc::mc::MCInst instruction(rcc::riscv::InstructionOpcode Opcode, + std::initializer_list<rcc::mc::MCOperand> Operands) { + rcc::mc::MCInst Result(rcc::mc::MCOpcode::fromValue(static_cast<std::uint32_t>(Opcode))); + Result.reserveOperands(Operands.size()); + for (const rcc::mc::MCOperand &Operand : Operands) + Result.addOperand(Operand); + return Result; +} + +[[nodiscard]] bool bytesEqual(std::span<const std::uint8_t> Left, + std::initializer_list<std::uint8_t> Right) { + return Left.size() == Right.size() && + std::equal(Left.begin(), Left.end(), Right.begin(), Right.end()); +} + +[[nodiscard]] bool expectEncoding(const rcc::riscv::RISCVMCCodeEmitter &Emitter, + const rcc::mc::MCInst &Instruction, + std::initializer_list<std::uint8_t> Expected, + std::string_view Name) { + auto Encoded = Emitter.encodeInstruction(Instruction); + if (!Encoded) { + std::cerr << Name << ": " << Encoded.error().message() << '\n'; + return false; + } + if (!Encoded->Fixups.empty() || !bytesEqual(Encoded->Bytes, Expected)) { + std::cerr << Name << ": encoded bytes differ\n"; + return false; + } + return true; +} + +[[nodiscard]] rcc::Expected<rcc::mc::MCExprRef> +symbolExpression(rcc::mc::MCContext &Context, std::string_view Name, std::uint32_t TargetKind = 0) { + auto Symbol = Context.getOrCreateSymbol(std::string(Name), rcc::mc::MCSymbolKind::Generic); + if (!Symbol) + return std::unexpected(Symbol.error()); + auto Expression = rcc::mc::MCExpr::symbol(*Symbol); + if (!Expression) + return std::unexpected(Expression.error()); + if (TargetKind == 0) + return *Expression; + return rcc::mc::MCExpr::target(TargetKind, *Expression); +} + +[[nodiscard]] bool testEncodings(const rcc::riscv::RISCVMCCodeEmitter &Emitter) { + using enum rcc::riscv::InstructionOpcode; + using enum rcc::riscv::PhysicalRegisterID; + return expectEncoding(Emitter, instruction(AUIPC, {reg(X11), imm(7)}), {0x97, 0x75, 0x00, 0x00}, + "AUIPC") && + expectEncoding(Emitter, instruction(ADDI, {reg(X12), reg(X13), imm(-17)}), + {0x13, 0x86, 0xf6, 0xfe}, "ADDI") && + expectEncoding(Emitter, instruction(SRAI, {reg(X14), reg(X15), imm(63)}), + {0x13, 0xd7, 0xf7, 0x43}, "SRAI") && + expectEncoding(Emitter, instruction(SD, {reg(X16), reg(X17), imm(-32)}), + {0x23, 0xb0, 0x08, 0xff}, "SD") && + expectEncoding(Emitter, instruction(BGE, {reg(X18), reg(X19), imm(18)}), + {0x63, 0x59, 0x39, 0x01}, "BGE") && + expectEncoding(Emitter, instruction(JAL, {reg(X20), imm(-20)}), {0x6f, 0xfa, 0xdf, 0xfe}, + "JAL") && + expectEncoding(Emitter, instruction(FENCE, {imm(10), imm(5)}), {0x0f, 0x00, 0x50, 0x0a}, + "FENCE") && + expectEncoding(Emitter, instruction(CSRRWI, {reg(X21), imm(0x305), imm(17)}), + {0xf3, 0xda, 0x58, 0x30}, "CSRRWI") && + expectEncoding(Emitter, instruction(AMOADD_D, {reg(X22), reg(X23), reg(X24)}), + {0x2f, 0x3b, 0x7c, 0x01}, "AMOADD.D") && + expectEncoding(Emitter, instruction(FMADD_D, {reg(F1), reg(F2), reg(F3), reg(F4), imm(2)}), + {0xc3, 0x20, 0x31, 0x22}, "FMADD.D") && + expectEncoding(Emitter, instruction(FADD_S, {reg(F5), reg(F6), reg(F7), imm(3)}), + {0xd3, 0x32, 0x73, 0x00}, "FADD.S") && + expectEncoding(Emitter, instruction(C_ADDI4SPN, {reg(X8), imm(48)}), {0x00, 0x18}, + "C.ADDI4SPN") && + expectEncoding(Emitter, instruction(C_LD, {reg(X9), reg(X8), imm(56)}), {0x04, 0x7c}, + "C.LD") && + expectEncoding(Emitter, instruction(C_SW, {reg(X10), reg(X11), imm(28)}), {0xc8, 0xcd}, + "C.SW") && + expectEncoding(Emitter, instruction(C_ADDI, {reg(X12), imm(-9)}), {0x5d, 0x16}, + "C.ADDI") && + expectEncoding(Emitter, instruction(C_ADDI16SP, {imm(-128)}), {0x19, 0x71}, + "C.ADDI16SP") && + expectEncoding(Emitter, instruction(C_SRAI, {reg(X13), imm(33)}), {0x85, 0x96}, + "C.SRAI") && + expectEncoding(Emitter, instruction(C_SUBW, {reg(X14), reg(X15)}), {0x1d, 0x9f}, + "C.SUBW") && + expectEncoding(Emitter, instruction(C_J, {imm(42)}), {0x2d, 0xa0}, "C.J") && + expectEncoding(Emitter, instruction(C_BNEZ, {reg(X8), imm(-30)}), {0x6d, 0xf0}, + "C.BNEZ") && + expectEncoding(Emitter, instruction(C_LDSP, {reg(X10), imm(248)}), {0x6e, 0x75}, + "C.LDSP") && + expectEncoding(Emitter, instruction(C_SWSP, {reg(X11), imm(124)}), {0xae, 0xde}, + "C.SWSP") && + expectEncoding(Emitter, instruction(C_JALR, {reg(X12)}), {0x02, 0x96}, "C.JALR"); +} + +[[nodiscard]] bool testFixups(const rcc::riscv::RISCVMCCodeEmitter &Emitter, + const rcc::riscv::RISCVAsmBackend &Backend) { + using enum rcc::riscv::InstructionOpcode; + using enum rcc::riscv::PhysicalRegisterID; + rcc::mc::MCContext Context; + + auto HighExpression = + symbolExpression(Context, "destination", + static_cast<std::uint32_t>(rcc::riscv::RISCVMCExprKind::PCRelativeHigh)); + if (!HighExpression) + return false; + auto High = instruction(AUIPC, {reg(X5), rcc::mc::MCOperand::expression(*HighExpression)}); + auto HighEncoding = Emitter.encodeInstruction(High); + if (!HighEncoding || HighEncoding->Fixups.size() != 1 || + HighEncoding->Fixups[0].Kind != + rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::PCRelativeHigh20) || + !HighEncoding->Fixups[0].PCRelative || + !bytesEqual(HighEncoding->Bytes, {0x97, 0x02, 0x00, 0x00})) { + return false; + } + auto HighValue = rcc::mc::evaluateAsRelocatable(**HighExpression); + if (!HighValue) + return false; + auto HighRelocation = Backend.relocationType(HighEncoding->Fixups[0], *HighValue); + if (!HighRelocation || *HighRelocation != 23) + return false; + + auto LowExpression = symbolExpression( + Context, "anchor", static_cast<std::uint32_t>(rcc::riscv::RISCVMCExprKind::PCRelativeLow)); + if (!LowExpression) + return false; + auto Low = instruction(JALR, {reg(X1), reg(X5), rcc::mc::MCOperand::expression(*LowExpression)}); + auto LowEncoding = Emitter.encodeInstruction(Low); + if (!LowEncoding || LowEncoding->Fixups.size() != 1 || + LowEncoding->Fixups[0].Kind != + rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::PCRelativeLow12I)) { + return false; + } + + auto BranchExpression = symbolExpression(Context, "block"); + if (!BranchExpression) + return false; + auto Branch = + instruction(BEQ, {reg(X10), reg(X11), rcc::mc::MCOperand::expression(*BranchExpression)}); + auto BranchEncoding = Emitter.encodeInstruction(Branch); + if (!BranchEncoding || BranchEncoding->Fixups.size() != 1 || + BranchEncoding->Fixups[0].Kind != rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::Branch)) { + return false; + } + auto Applied = Backend.applyFixup(BranchEncoding->Fixups[0].Kind, BranchEncoding->Bytes, 0, 18); + if (!Applied || !bytesEqual(BranchEncoding->Bytes, {0x63, 0x09, 0xb5, 0x00})) + return false; + + std::vector<std::uint8_t> Word(4); + auto OutOfRange = + Backend.applyFixup(rcc::riscv::fixupKind(rcc::riscv::RISCVFixupKind::Branch), Word, 0, 4096); + return !OutOfRange && OutOfRange.error().code() == rcc::ErrorCode::FixupApplicationFailed; +} + +[[nodiscard]] bool testFailures(const rcc::riscv::RISCVMCCodeEmitter &Emitter) { + using enum rcc::riscv::InstructionOpcode; + using enum rcc::riscv::PhysicalRegisterID; + auto BadShift = Emitter.encodeInstruction(instruction(SLLI, {reg(X1), reg(X2), imm(64)})); + auto BadCompactRegister = + Emitter.encodeInstruction(instruction(C_LD, {reg(X1), reg(X8), imm(8)})); + auto BadAlignment = Emitter.encodeInstruction(instruction(C_SDSP, {reg(X10), imm(6)})); + auto Pseudo = Emitter.encodeInstruction(instruction(PseudoCALL, {})); + return !BadShift && BadShift.error().code() == rcc::ErrorCode::InstructionEncodingFailed && + !BadCompactRegister && + BadCompactRegister.error().code() == rcc::ErrorCode::InstructionEncodingFailed && + !BadAlignment && + BadAlignment.error().code() == rcc::ErrorCode::InstructionEncodingFailed && !Pseudo && + Pseudo.error().code() == rcc::ErrorCode::InstructionEncodingFailed; +} + +[[nodiscard]] bool containsBytes(std::span<const std::uint8_t> Bytes, std::string_view Text) { + if (Text.size() > Bytes.size()) + return false; + const auto *Begin = reinterpret_cast<const char *>(Bytes.data()); + return std::string_view(Begin, Bytes.size()).find(Text) != std::string_view::npos; +} + +[[nodiscard]] bool testObjectEmission(const rcc::TargetDescription &Target) { + auto Alignment = rcc::Alignment::create(8); + auto Value = rcc::IntegerImmediate::fromUnsigned(64, 0x1122334455667788ULL); + if (!Alignment || !Value) + return false; + rcc::GlobalObjectDesc Global; + Global.Symbol = "object_value"; + Global.ObjectType = rcc::GenericConstantType::byte(); + Global.Align = *Alignment; + Global.Size = 8; + Global.Initializer.emplace(rcc::GlobalInitializer::integer(std::move(*Value))); + rcc::CompilationUnit Unit; + if (!Unit.addGlobalObject(std::move(Global))) + return false; + auto Object = rcc::riscv::emitObject(Unit, Target); + if (!Object || Object->size() < 64 || (*Object)[0] != 0x7f || (*Object)[1] != 'E' || + (*Object)[2] != 'L' || (*Object)[3] != 'F') { + return false; + } + const std::uint16_t Machine = + static_cast<std::uint16_t>((*Object)[18]) | static_cast<std::uint16_t>((*Object)[19] << 8U); + const std::uint32_t Flags = static_cast<std::uint32_t>((*Object)[48]) | + static_cast<std::uint32_t>((*Object)[49]) << 8U | + static_cast<std::uint32_t>((*Object)[50]) << 16U | + static_cast<std::uint32_t>((*Object)[51]) << 24U; + return Machine == 243 && Flags == 5 && containsBytes(*Object, ".data") && + containsBytes(*Object, ".riscv.attributes") && containsBytes(*Object, "object_value") && + containsBytes(*Object, std::string_view("\x88\x77\x66\x55\x44\x33\x22\x11", 8)); +} + +} // namespace + +int main() { + auto Target = rcc::riscv::createTargetDescription(); + if (!Target) + return fail(Target.error().message()); + rcc::riscv::RISCVMCCodeEmitter Emitter(*Target); + rcc::riscv::RISCVAsmBackend Backend; + if (!testEncodings(Emitter)) + return fail("RISC-V MC encodings differ from the RV64GC reference encodings"); + if (!testFixups(Emitter, Backend)) + return fail("RISC-V MC fixup or relocation behavior is incorrect"); + if (!testFailures(Emitter)) + return fail("RISC-V MC encoding failures are not diagnosed"); + if (!testObjectEmission(*Target)) + return fail("RISC-V ELF object emission is incomplete"); + if (Backend.objectFlags() != 5) + return fail("RISC-V ELF flags do not describe RV64GC LP64D"); + return 0; +} |
