From a69d14b8ebc028dddc689658b08d42d3e83f5e38 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sat, 1 Aug 2026 14:19:20 +0800 Subject: [CodeGen] Add constant and jump table state Add stable, tombstone-preserving MachineConstantPool and MachineJumpTableInfo ownership. Define the versioned TargetConstant contract and integrate all table references with builders, verification, and deterministic deep cloning. --- backend/unittests/CodeGen/TargetConstantTest.cpp | 278 +++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 backend/unittests/CodeGen/TargetConstantTest.cpp (limited to 'backend/unittests/CodeGen/TargetConstantTest.cpp') diff --git a/backend/unittests/CodeGen/TargetConstantTest.cpp b/backend/unittests/CodeGen/TargetConstantTest.cpp new file mode 100644 index 0000000..77338b7 --- /dev/null +++ b/backend/unittests/CodeGen/TargetConstantTest.cpp @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetConstant.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++FailureCount; + } + + [[nodiscard]] int result() const noexcept { + return FailureCount == 0 ? 0 : 1; + } + +private: + int FailureCount = 0; +}; + +struct TestRelocConstant final { + std::int64_t Addend = 0; + std::string Symbol; + + [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept { + return rcc::TargetConstantTypeID::fromValue(0x5253434354430001ULL); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return 2; + } + + [[nodiscard]] rcc::Expected verify() const { + if (Symbol.empty()) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "test target constant symbol cannot be empty"); + } + return {}; + } + + [[nodiscard]] rcc::Expected clone() const { + return TestRelocConstant{Addend, Symbol}; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept { + std::uint64_t Hash = 1469598103934665603ULL; + for (const char RawCharacter : Symbol) { + const auto Character = static_cast(RawCharacter); + Hash ^= Character; + Hash *= 1099511628211ULL; + } + Hash ^= static_cast(Addend); + return Hash * 1099511628211ULL; + } + + [[nodiscard]] std::string print() const { + return Symbol + "+" + std::to_string(Addend); + } + + [[nodiscard]] std::vector addendBytes() const { + std::vector Bytes(8); + const std::uint64_t Bits = static_cast(Addend); + for (std::size_t Index = 0; Index < Bytes.size(); ++Index) + Bytes[Index] = static_cast((Bits >> (Index * 8U)) & 0xffU); + return Bytes; + } + + [[nodiscard]] rcc::Expected> encode() const { + std::vector Payload = addendBytes(); + for (const char RawCharacter : Symbol) { + const auto Character = static_cast(RawCharacter); + Payload.push_back(static_cast(Character)); + } + return Payload; + } + + [[nodiscard]] rcc::Expected emit() const { + rcc::TargetConstantEmission Emission; + Emission.Bytes = addendBytes(); + Emission.Fixups.push_back({0, rcc::TargetRelocationKindID::fromValue(9), Symbol, Addend}); + return Emission; + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return *rcc::Alignment::create(8); + } + + [[nodiscard]] std::optional relocationKind() const noexcept { + return rcc::TargetRelocationKindID::fromValue(9); + } + + [[nodiscard]] static rcc::Expected parse(std::string_view Text) { + if (Text.empty()) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "test constant text is empty"); + return TestRelocConstant{7, std::string(Text)}; + } + + [[nodiscard]] static rcc::Expected decode(std::uint32_t Version, + std::span Payload) { + if (Version != schemaVersion()) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, + "unsupported test constant schema version"); + if (Payload.size() <= 8) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "test constant payload is incomplete"); + + std::uint64_t Bits = 0; + for (std::size_t Index = 0; Index < 8; ++Index) + Bits |= std::to_integer(Payload[Index]) << (Index * 8U); + std::string Symbol; + Symbol.reserve(Payload.size() - 8); + for (const std::byte Character : Payload.subspan(8)) + Symbol.push_back(static_cast(std::to_integer(Character))); + return TestRelocConstant{std::bit_cast(Bits), std::move(Symbol)}; + } + + friend bool operator==(const TestRelocConstant &Left, const TestRelocConstant &Right) = default; +}; + +struct CollidingConstant final { + TestRelocConstant Value; + + [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept { + return TestRelocConstant::stableTypeID(); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return TestRelocConstant::schemaVersion(); + } + + [[nodiscard]] rcc::Expected verify() const { + return Value.verify(); + } + + [[nodiscard]] rcc::Expected clone() const { + return CollidingConstant{Value}; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept { + return Value.stableHash(); + } + + [[nodiscard]] std::string print() const { + return Value.print(); + } + + [[nodiscard]] rcc::Expected> encode() const { + return Value.encode(); + } + + [[nodiscard]] rcc::Expected emit() const { + return Value.emit(); + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return Value.requiredAlignment(); + } + + [[nodiscard]] std::optional relocationKind() const noexcept { + return Value.relocationKind(); + } + + [[nodiscard]] static rcc::Expected parse(std::string_view Text) { + auto Parsed = TestRelocConstant::parse(Text); + if (!Parsed) + return std::unexpected(Parsed.error()); + return CollidingConstant{std::move(*Parsed)}; + } + + [[nodiscard]] static rcc::Expected decode(std::uint32_t Version, + std::span Payload) { + auto Decoded = TestRelocConstant::decode(Version, Payload); + if (!Decoded) + return std::unexpected(Decoded.error()); + return CollidingConstant{std::move(*Decoded)}; + } + + friend bool operator==(const CollidingConstant &Left, const CollidingConstant &Right) = default; +}; + +void testTargetConstantContract(TestContext &Context) { + auto Constant = rcc::TargetConstant::create(TestRelocConstant{11, "symbol"}); + Context.expect(Constant.has_value(), "valid target constant must be type-erased"); + if (!Constant) + return; + + Context.expect(Constant->typeID() == TestRelocConstant::stableTypeID() && + Constant->schemaVersion() == TestRelocConstant::schemaVersion() && + Constant->getIf() != nullptr && + Constant->getIf() == nullptr, + "strong query must require both stable and local type identity"); + auto Printed = Constant->print(); + auto RequiredAlignment = Constant->requiredAlignment(); + auto RelocationKind = Constant->relocationKind(); + Context.expect(Constant->verify().has_value() && Printed && *Printed == "symbol+11" && + RequiredAlignment && *RequiredAlignment == *rcc::Alignment::create(8) && + RelocationKind && *RelocationKind == rcc::TargetRelocationKindID::fromValue(9), + "target constant semantic hooks must remain available through erasure"); + + auto Cloned = Constant->clone(); + Context.expect(Cloned && Constant->equals(*Cloned) && + Cloned->getIf() != Constant->getIf(), + "target constant clone must own an equal independent value"); + + auto Encoded = Constant->encode(); + if (!Encoded) + return; + auto Decoded = rcc::TargetConstant::decode(*Encoded); + Context.expect(Encoded && Decoded && Constant->equals(*Decoded), + "versioned target constant encoding must round-trip losslessly"); + + auto Parsed = rcc::TargetConstant::parse("parsed"); + const TestRelocConstant *ParsedValue = Parsed ? Parsed->getIf() : nullptr; + Context.expect(ParsedValue != nullptr && ParsedValue->Addend == 7 && + ParsedValue->Symbol == "parsed", + "target constant text parser must construct the same strong type"); + + auto Emission = Constant->emit(); + Context.expect(Emission && Emission->Bytes.size() == 8 && Emission->Fixups.size() == 1 && + Emission->Fixups[0].Symbol == "symbol" && + Emission->Fixups[0].Kind == rcc::TargetRelocationKindID::fromValue(9), + "target constant emission must preserve bytes and structured fixups"); +} + +void testTargetConstantFailures(TestContext &Context) { + rcc::TargetConstant Empty; + Context.expect(!Empty.verify() && !Empty.clone() && !Empty.encode() && !Empty.emit(), + "empty target constant must reject every semantic operation"); + Context.expect(!rcc::TargetConstant::create(TestRelocConstant{0, ""}), + "target constant creation must verify its value"); + + auto Constant = rcc::TargetConstant::create(TestRelocConstant{3, "symbol"}); + if (!Constant) + return; + auto Encoded = Constant->encode(); + if (!Encoded) + return; + Encoded->Version = 99; + Context.expect(!rcc::TargetConstant::decode(*Encoded), + "target constant decode must reject unsupported versions"); + Encoded->Version = TestRelocConstant::schemaVersion(); + Encoded->TypeID = rcc::TargetConstantTypeID::fromValue(1); + Context.expect(!rcc::TargetConstant::decode(*Encoded), + "target constant decode must reject a mismatched TypeID"); + + rcc::TargetConstantEmission Invalid; + Invalid.Bytes.resize(4); + Invalid.Fixups.push_back({4, rcc::TargetRelocationKindID::fromValue(1), "symbol", 0}); + Context.expect(!Invalid.verify(), "target emission must reject out-of-range fixups"); + Invalid.Fixups[0].Offset = 0; + Invalid.Fixups[0].Kind = {}; + Context.expect(!Invalid.verify(), "target emission must reject invalid relocation kinds"); + Invalid.Fixups[0].Kind = rcc::TargetRelocationKindID::fromValue(1); + Invalid.Fixups.push_back(Invalid.Fixups[0]); + Context.expect(!Invalid.verify(), "target emission must reject duplicate fixup offsets"); +} + +} // namespace + +int main() { + TestContext Context; + testTargetConstantContract(Context); + testTargetConstantFailures(Context); + return Context.result(); +} -- cgit v1.2.3