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/include/rcc/CodeGen/MachineConstantPool.h | 183 ++++++++++++ backend/include/rcc/CodeGen/MachineFunction.h | 20 ++ backend/include/rcc/CodeGen/MachineIRBuilder.h | 2 + backend/include/rcc/CodeGen/MachineIRTypes.h | 6 + backend/include/rcc/CodeGen/MachineJumpTableInfo.h | 116 ++++++++ backend/include/rcc/CodeGen/TargetConstant.h | 259 ++++++++++++++++ backend/lib/CodeGen/CMakeLists.txt | 12 + backend/lib/CodeGen/MachineConstantPool.cpp | 329 +++++++++++++++++++++ backend/lib/CodeGen/MachineFunction.cpp | 3 +- backend/lib/CodeGen/MachineFunctionCloner.cpp | 103 ++++++- backend/lib/CodeGen/MachineIRBuilder.cpp | 38 ++- backend/lib/CodeGen/MachineJumpTableInfo.cpp | 142 +++++++++ backend/lib/CodeGen/MachineVerifier.cpp | 41 ++- backend/lib/CodeGen/TargetConstant.cpp | 146 +++++++++ backend/unittests/CodeGen/CMakeLists.txt | 36 +++ .../unittests/CodeGen/MachineConstantPoolTest.cpp | 260 ++++++++++++++++ .../CodeGen/MachineFunctionClonerTest.cpp | 197 ++++++++++-- backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 63 ++++ .../unittests/CodeGen/MachineJumpTableInfoTest.cpp | 171 +++++++++++ backend/unittests/CodeGen/MachineVerifierTest.cpp | 64 ++++ backend/unittests/CodeGen/TargetConstantTest.cpp | 278 +++++++++++++++++ 21 files changed, 2427 insertions(+), 42 deletions(-) create mode 100644 backend/include/rcc/CodeGen/MachineConstantPool.h create mode 100644 backend/include/rcc/CodeGen/MachineJumpTableInfo.h create mode 100644 backend/include/rcc/CodeGen/TargetConstant.h create mode 100644 backend/lib/CodeGen/MachineConstantPool.cpp create mode 100644 backend/lib/CodeGen/MachineJumpTableInfo.cpp create mode 100644 backend/lib/CodeGen/TargetConstant.cpp create mode 100644 backend/unittests/CodeGen/MachineConstantPoolTest.cpp create mode 100644 backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp create mode 100644 backend/unittests/CodeGen/TargetConstantTest.cpp (limited to 'backend') diff --git a/backend/include/rcc/CodeGen/MachineConstantPool.h b/backend/include/rcc/CodeGen/MachineConstantPool.h new file mode 100644 index 0000000..cc9ae04 --- /dev/null +++ b/backend/include/rcc/CodeGen/MachineConstantPool.h @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_MACHINECONSTANTPOOL_H +#define RCC_CODEGEN_MACHINECONSTANTPOOL_H + +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/TargetConstant.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +enum class ConstantPoolEntryKind : std::uint8_t { + Generic, + Target, + SymbolDifference, + BlockAddress, + TargetExpression, +}; + +enum class ConstantPoolMergeKind : std::uint8_t { + Never, + Exact, +}; + +enum class ConstantPoolRelocationMode : std::uint8_t { + None, + Static, + PositionIndependent, + Target, +}; + +enum class ConstantPoolSectionKind : std::uint8_t { + TargetDefault, + ReadOnly, + MergeableReadOnly, + Data, + TextAdjacent, + Target, + Count, +}; + +struct ConstantPoolPlacement final { + ConstantPoolSectionKind Section = ConstantPoolSectionKind::TargetDefault; + bool UniqueSection = false; + std::optional MaximumDistance; + std::uint64_t TargetFlags = 0; + + friend bool operator==(const ConstantPoolPlacement &Left, + const ConstantPoolPlacement &Right) = default; +}; + +struct GenericConstantData final { + std::string TypeIdentity; + std::vector Bytes; + + friend bool operator==(const GenericConstantData &Left, + const GenericConstantData &Right) = default; +}; + +struct SymbolDifferenceConstant final { + std::string Minuend; + std::string Subtrahend; + std::int64_t Addend = 0; + + friend bool operator==(const SymbolDifferenceConstant &Left, + const SymbolDifferenceConstant &Right) = default; +}; + +struct BlockAddressConstant final { + std::string FunctionName; + std::string BlockName; + std::int64_t Addend = 0; + + friend bool operator==(const BlockAddressConstant &Left, + const BlockAddressConstant &Right) = default; +}; + +struct TargetExpressionConstant final { + TargetConstant Expression; +}; + +using MachineConstantPoolValue = + std::variant; + +struct MachineConstantPoolEntryDesc final { + MachineConstantPoolEntryDesc() = default; + MachineConstantPoolEntryDesc(const MachineConstantPoolEntryDesc &) = delete; + MachineConstantPoolEntryDesc &operator=(const MachineConstantPoolEntryDesc &) = delete; + MachineConstantPoolEntryDesc(MachineConstantPoolEntryDesc &&) noexcept = default; + MachineConstantPoolEntryDesc &operator=(MachineConstantPoolEntryDesc &&) noexcept = default; + ~MachineConstantPoolEntryDesc() = default; + + MachineConstantPoolValue Value; + std::uint64_t Size = 0; + Alignment Align; + ConstantPoolMergeKind Merge = ConstantPoolMergeKind::Never; + ConstantPoolRelocationMode Relocation = ConstantPoolRelocationMode::None; + ConstantPoolPlacement Placement; + + [[nodiscard]] ConstantPoolEntryKind kind() const noexcept; + [[nodiscard]] Expected verify() const; + [[nodiscard]] Expected clone() const; + [[nodiscard]] bool semanticallyEquals(const MachineConstantPoolEntryDesc &Other) const noexcept; +}; + +class MachineConstantPoolEntry final { +public: + [[nodiscard]] ConstantPoolIndex index() const noexcept { + return Index; + } + + [[nodiscard]] bool isTombstone() const noexcept { + return std::holds_alternative(Description); + } + + [[nodiscard]] const MachineConstantPoolEntryDesc *description() const noexcept { + return std::get_if(&Description); + } + + [[nodiscard]] Expected cloneDescription() const; + +private: + MachineConstantPoolEntry(ConstantPoolIndex Index, MachineConstantPoolEntryDesc Description) + : Index(Index), Description(std::move(Description)) {} + + ConstantPoolIndex Index; + std::variant Description; + + friend class MachineConstantPool; +}; + +class MachineConstantPool final { +public: + MachineConstantPool() = default; + MachineConstantPool(const MachineConstantPool &) = delete; + MachineConstantPool &operator=(const MachineConstantPool &) = delete; + MachineConstantPool(MachineConstantPool &&) noexcept = default; + MachineConstantPool &operator=(MachineConstantPool &&) noexcept = default; + ~MachineConstantPool() = default; + + [[nodiscard]] Expected addEntry(MachineConstantPoolEntryDesc Description); + [[nodiscard]] Expected updateEntry(ConstantPoolIndex Index, + MachineConstantPoolEntryDesc Description); + [[nodiscard]] Expected eraseEntry(ConstantPoolIndex Index); + + [[nodiscard]] const MachineConstantPoolEntry *entry(ConstantPoolIndex Index) const noexcept; + [[nodiscard]] std::span entries() const noexcept { + return Entries; + } + + [[nodiscard]] std::size_t liveEntryCount() const noexcept; + + [[nodiscard]] bool isFrozen() const noexcept { + return Frozen; + } + + [[nodiscard]] Expected freeze(); + [[nodiscard]] Expected verify() const; + +private: + [[nodiscard]] Expected mutableEntry(ConstantPoolIndex Index); + [[nodiscard]] const MachineConstantPoolEntry * + findEquivalent(const MachineConstantPoolEntryDesc &Description, + std::optional Excluded = std::nullopt) const noexcept; + + std::vector Entries; + bool Frozen = false; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_MACHINECONSTANTPOOL_H diff --git a/backend/include/rcc/CodeGen/MachineFunction.h b/backend/include/rcc/CodeGen/MachineFunction.h index 3e3fecb..38d88f9 100644 --- a/backend/include/rcc/CodeGen/MachineFunction.h +++ b/backend/include/rcc/CodeGen/MachineFunction.h @@ -3,8 +3,10 @@ #ifndef RCC_CODEGEN_MACHINEFUNCTION_H #define RCC_CODEGEN_MACHINEFUNCTION_H +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunctionProperties.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/StableID.h" #include "rcc/Support/Error.h" @@ -61,6 +63,22 @@ public: return FrameInfo; } + [[nodiscard]] MachineConstantPool &constantPool() noexcept { + return ConstantPool; + } + + [[nodiscard]] const MachineConstantPool &constantPool() const noexcept { + return ConstantPool; + } + + [[nodiscard]] MachineJumpTableInfo &jumpTableInfo() noexcept { + return JumpTables; + } + + [[nodiscard]] const MachineJumpTableInfo &jumpTableInfo() const noexcept { + return JumpTables; + } + [[nodiscard]] MachineFunctionProperties &properties() noexcept { return Properties; } @@ -120,6 +138,8 @@ private: MachineFunctionStableID StableID; MachineRegisterInfo RegInfo; MachineFrameInfo FrameInfo; + MachineConstantPool ConstantPool; + MachineJumpTableInfo JumpTables; MachineFunctionProperties Properties; MachineBasicBlock *FirstBlock = nullptr; MachineBasicBlock *LastBlock = nullptr; diff --git a/backend/include/rcc/CodeGen/MachineIRBuilder.h b/backend/include/rcc/CodeGen/MachineIRBuilder.h index 3bd8d67..47fd63c 100644 --- a/backend/include/rcc/CodeGen/MachineIRBuilder.h +++ b/backend/include/rcc/CodeGen/MachineIRBuilder.h @@ -97,6 +97,8 @@ private: [[nodiscard]] Expected ensurePending() const; [[nodiscard]] Expected validateInsertionPoint() const; [[nodiscard]] Expected validateFrameReference(FrameIndex Index) const; + [[nodiscard]] Expected validateConstantPoolReference(ConstantPoolIndex Index) const; + [[nodiscard]] Expected validateJumpTableReference(JumpTableIndex Index) const; [[nodiscard]] Expected validateOperandOwnership(const MachineOperand &Operand) const; [[nodiscard]] std::vector collectOperandTies() const; [[nodiscard]] Expected appendRegister(Register Reg, RegisterOperandFlags Flags, diff --git a/backend/include/rcc/CodeGen/MachineIRTypes.h b/backend/include/rcc/CodeGen/MachineIRTypes.h index de6be5a..058cacb 100644 --- a/backend/include/rcc/CodeGen/MachineIRTypes.h +++ b/backend/include/rcc/CodeGen/MachineIRTypes.h @@ -55,6 +55,9 @@ struct AssemblyFormatIDTag; struct SyncScopeIDTag; struct MemoryAliasSourceIDTag; struct OriginIDTag; +struct TargetConstantTypeIDTag; +struct TargetFunctionStateTypeIDTag; +struct TargetRelocationKindIDTag; } // namespace detail @@ -74,6 +77,9 @@ using AssemblyFormatID = MachineIndex; using SyncScopeID = MachineIndex; using MemoryAliasSourceID = MachineIndex; using OriginID = MachineIndex; +using TargetConstantTypeID = MachineIndex; +using TargetFunctionStateTypeID = MachineIndex; +using TargetRelocationKindID = MachineIndex; class Opcode final { public: diff --git a/backend/include/rcc/CodeGen/MachineJumpTableInfo.h b/backend/include/rcc/CodeGen/MachineJumpTableInfo.h new file mode 100644 index 0000000..6342281 --- /dev/null +++ b/backend/include/rcc/CodeGen/MachineJumpTableInfo.h @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_MACHINEJUMPTABLEINFO_H +#define RCC_CODEGEN_MACHINEJUMPTABLEINFO_H + +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class MachineBasicBlock; +class MachineFunction; + +enum class JumpTableEntryEncoding : std::uint8_t { + AbsoluteAddress, + RelativeToTableBase, + RelativeToEntry, + CompressedOffset, + Target, + Count, +}; + +struct JumpTablePlacement final { + bool InTextSection = true; + bool UniqueSection = false; + std::optional MaximumDistance; + std::uint64_t TargetFlags = 0; + + friend bool operator==(const JumpTablePlacement &Left, const JumpTablePlacement &Right) = default; +}; + +struct MachineJumpTableDesc final { + JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::AbsoluteAddress; + std::uint8_t EntrySize = 8; + Alignment Align; + std::vector Targets; + JumpTablePlacement Placement; + std::uint32_t TargetEncoding = 0; + + friend bool operator==(const MachineJumpTableDesc &Left, + const MachineJumpTableDesc &Right) = default; +}; + +class MachineJumpTableEntry final { +public: + [[nodiscard]] JumpTableIndex index() const noexcept { + return Index; + } + + [[nodiscard]] bool isTombstone() const noexcept { + return std::holds_alternative(Description); + } + + [[nodiscard]] const MachineJumpTableDesc *description() const noexcept { + return std::get_if(&Description); + } + +private: + MachineJumpTableEntry(JumpTableIndex Index, MachineJumpTableDesc Description) + : Index(Index), Description(std::move(Description)) {} + + JumpTableIndex Index; + std::variant Description; + + friend class MachineJumpTableInfo; +}; + +class MachineJumpTableInfo final { +public: + explicit MachineJumpTableInfo(MachineFunction &Owner) noexcept : Owner(&Owner) {} + + MachineJumpTableInfo(const MachineJumpTableInfo &) = delete; + MachineJumpTableInfo &operator=(const MachineJumpTableInfo &) = delete; + MachineJumpTableInfo(MachineJumpTableInfo &&) = delete; + MachineJumpTableInfo &operator=(MachineJumpTableInfo &&) = delete; + ~MachineJumpTableInfo() = default; + + [[nodiscard]] Expected createTable(MachineJumpTableDesc Description); + [[nodiscard]] Expected updateTable(JumpTableIndex Index, MachineJumpTableDesc Description); + [[nodiscard]] Expected eraseTable(JumpTableIndex Index); + + [[nodiscard]] const MachineJumpTableEntry *table(JumpTableIndex Index) const noexcept; + [[nodiscard]] std::span tables() const noexcept { + return Tables; + } + + [[nodiscard]] std::size_t liveTableCount() const noexcept; + + [[nodiscard]] bool isFrozen() const noexcept { + return Frozen; + } + + [[nodiscard]] Expected freeze(); + [[nodiscard]] Expected verify() const; + +private: + [[nodiscard]] Expected verifyDescription(const MachineJumpTableDesc &Description) const; + [[nodiscard]] Expected mutableTable(JumpTableIndex Index); + + MachineFunction *Owner; + std::vector Tables; + bool Frozen = false; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_MACHINEJUMPTABLEINFO_H diff --git a/backend/include/rcc/CodeGen/TargetConstant.h b/backend/include/rcc/CodeGen/TargetConstant.h new file mode 100644 index 0000000..37f352e --- /dev/null +++ b/backend/include/rcc/CodeGen/TargetConstant.h @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_TARGETCONSTANT_H +#define RCC_CODEGEN_TARGETCONSTANT_H + +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +struct TargetConstantFixup final { + std::uint64_t Offset = 0; + TargetRelocationKindID Kind; + std::string Symbol; + std::int64_t Addend = 0; + + friend bool operator==(const TargetConstantFixup &Left, + const TargetConstantFixup &Right) = default; +}; + +struct TargetConstantEmission final { + std::vector Bytes; + std::vector Fixups; + + [[nodiscard]] Expected verify() const; + + friend bool operator==(const TargetConstantEmission &Left, + const TargetConstantEmission &Right) = default; +}; + +struct EncodedTargetConstant final { + TargetConstantTypeID TypeID; + std::uint32_t Version = 0; + std::vector Payload; + + friend bool operator==(const EncodedTargetConstant &Left, + const EncodedTargetConstant &Right) = default; +}; + +template +concept TargetConstantValue = + std::move_constructible && + requires(const ValueT &Value, const ValueT &Other, std::uint32_t Version, + std::span Payload, std::string_view Text) { + { ValueT::stableTypeID() } -> std::same_as; + { ValueT::schemaVersion() } -> std::same_as; + { Value.verify() } -> std::same_as>; + { Value.clone() } -> std::same_as>; + { Value.stableHash() } -> std::same_as; + { Value.print() } -> std::same_as; + { Value.encode() } -> std::same_as>>; + { Value.emit() } -> std::same_as>; + { Value.requiredAlignment() } -> std::same_as; + { Value.relocationKind() } -> std::same_as>; + { Value == Other } -> std::convertible_to; + { ValueT::parse(Text) } -> std::same_as>; + { ValueT::decode(Version, Payload) } -> std::same_as>; + }; + +class TargetConstant final { +public: + TargetConstant() = default; + TargetConstant(const TargetConstant &) = delete; + TargetConstant &operator=(const TargetConstant &) = delete; + TargetConstant(TargetConstant &&) noexcept; + TargetConstant &operator=(TargetConstant &&) noexcept; + ~TargetConstant(); + + template + [[nodiscard]] static Expected create(ValueT Value) { + if (ValueT::stableTypeID().value() == 0) + return makeUnexpected(ErrorCode::InvalidArgument, "target constant TypeID must be nonzero"); + if (ValueT::schemaVersion() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "target constant schema version must be nonzero"); + } + auto Verification = Value.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + + std::unique_ptr NewStorage = std::make_unique>(std::move(Value)); + return TargetConstant(std::move(NewStorage)); + } + + template + [[nodiscard]] static Expected parse(std::string_view Text) { + auto Value = ValueT::parse(Text); + if (!Value) + return std::unexpected(Value.error()); + return create(std::move(*Value)); + } + + template + [[nodiscard]] static Expected decode(const EncodedTargetConstant &Encoded) { + if (Encoded.TypeID != ValueT::stableTypeID()) { + return makeUnexpected(ErrorCode::ParseError, + "encoded target constant TypeID does not match its target schema"); + } + if (Encoded.Version == 0) + return makeUnexpected(ErrorCode::ParseError, "target constant version must be nonzero"); + auto Value = ValueT::decode(Encoded.Version, Encoded.Payload); + if (!Value) + return std::unexpected(Value.error()); + return create(std::move(*Value)); + } + + [[nodiscard]] bool hasValue() const noexcept { + return Storage != nullptr; + } + + [[nodiscard]] std::optional typeID() const noexcept; + [[nodiscard]] std::optional schemaVersion() const noexcept; + [[nodiscard]] Expected verify() const; + [[nodiscard]] Expected clone() const; + [[nodiscard]] Expected stableHash() const; + [[nodiscard]] Expected print() const; + [[nodiscard]] Expected encode() const; + [[nodiscard]] Expected emit() const; + [[nodiscard]] Expected requiredAlignment() const; + [[nodiscard]] Expected> relocationKind() const; + + [[nodiscard]] bool equals(const TargetConstant &Other) const noexcept; + + template [[nodiscard]] ValueT *getIf() noexcept { + if (!hasType()) + return nullptr; + return &static_cast *>(Storage.get())->value(); + } + + template [[nodiscard]] const ValueT *getIf() const noexcept { + if (!hasType()) + return nullptr; + return &static_cast *>(Storage.get())->value(); + } + +private: + class Concept { + public: + Concept() = default; + Concept(const Concept &) = delete; + Concept &operator=(const Concept &) = delete; + Concept(Concept &&) = delete; + Concept &operator=(Concept &&) = delete; + virtual ~Concept() = default; + + [[nodiscard]] virtual TargetConstantTypeID typeID() const noexcept = 0; + [[nodiscard]] virtual std::uint32_t schemaVersion() const noexcept = 0; + [[nodiscard]] virtual const void *localTypeToken() const noexcept = 0; + [[nodiscard]] virtual Expected verify() const = 0; + [[nodiscard]] virtual Expected> cloneStorage() const = 0; + [[nodiscard]] virtual std::uint64_t stableHash() const noexcept = 0; + [[nodiscard]] virtual std::string print() const = 0; + [[nodiscard]] virtual Expected> encodePayload() const = 0; + [[nodiscard]] virtual Expected emit() const = 0; + [[nodiscard]] virtual Alignment requiredAlignment() const noexcept = 0; + [[nodiscard]] virtual std::optional relocationKind() const noexcept = 0; + [[nodiscard]] virtual bool equals(const Concept &Other) const noexcept = 0; + }; + + template [[nodiscard]] static const void *typeToken() noexcept { + static const std::byte Token{}; + return &Token; + } + + template class Model final : public Concept { + public: + explicit Model(ValueT Value) : Value(std::move(Value)) {} + + [[nodiscard]] TargetConstantTypeID typeID() const noexcept override { + return ValueT::stableTypeID(); + } + + [[nodiscard]] std::uint32_t schemaVersion() const noexcept override { + return ValueT::schemaVersion(); + } + + [[nodiscard]] const void *localTypeToken() const noexcept override { + return TargetConstant::typeToken(); + } + + [[nodiscard]] Expected verify() const override { + return Value.verify(); + } + + [[nodiscard]] Expected> cloneStorage() const override { + auto Cloned = Value.clone(); + if (!Cloned) + return std::unexpected(Cloned.error()); + std::unique_ptr Result = std::make_unique>(std::move(*Cloned)); + return Result; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept override { + return Value.stableHash(); + } + + [[nodiscard]] std::string print() const override { + return Value.print(); + } + + [[nodiscard]] Expected> encodePayload() const override { + return Value.encode(); + } + + [[nodiscard]] Expected emit() const override { + return Value.emit(); + } + + [[nodiscard]] Alignment requiredAlignment() const noexcept override { + return Value.requiredAlignment(); + } + + [[nodiscard]] std::optional relocationKind() const noexcept override { + return Value.relocationKind(); + } + + [[nodiscard]] bool equals(const Concept &Other) const noexcept override { + if (Other.localTypeToken() != TargetConstant::typeToken()) + return false; + return Value == static_cast &>(Other).Value; + } + + [[nodiscard]] ValueT &value() noexcept { + return Value; + } + + [[nodiscard]] const ValueT &value() const noexcept { + return Value; + } + + private: + ValueT Value; + }; + + explicit TargetConstant(std::unique_ptr Storage) : Storage(std::move(Storage)) {} + + template [[nodiscard]] bool hasType() const noexcept { + return Storage != nullptr && Storage->typeID() == ValueT::stableTypeID() && + Storage->localTypeToken() == typeToken(); + } + + std::unique_ptr Storage; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_TARGETCONSTANT_H diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index 283727c..3dd17a4 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -6,12 +6,14 @@ set( LowLevelType.cpp MachineBasicBlock.cpp MachineCFGUpdater.cpp + MachineConstantPool.cpp MachineFrameInfo.cpp MachineFunction.cpp MachineFunctionCloner.cpp MachineIRTypes.cpp MachineIRBuilder.cpp MachineInstr.cpp + MachineJumpTableInfo.cpp MachineMemOperand.cpp MachineOperand.cpp MachineRegisterInfo.cpp @@ -19,6 +21,7 @@ set( MachineFunctionProperties.cpp Register.cpp RegisterMask.cpp + TargetConstant.cpp ) set( RCCBackendCodeGenHeaders @@ -26,12 +29,14 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineBasicBlock.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCFGUpdater.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineConstantPool.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFrameInfo.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunction.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunctionCloner.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRTypes.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRBuilder.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineInstr.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineJumpTableInfo.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineMemOperand.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineOperand.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineRegisterInfo.h" @@ -40,6 +45,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/Register.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/RegisterMask.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/StableID.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetConstant.h" ) add_library(RCCBackendCodeGen ${RCCBackendCodeGenSources}) @@ -75,12 +81,14 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPool.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionCloner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilder.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineInstr.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineRegisterInfo.cpp" @@ -88,6 +96,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionProperties.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp" ${RCCBackendCodeGenHeaders} ) rcc_backend_register_tidy_files( @@ -95,12 +104,14 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPool.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionCloner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilder.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineInstr.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineRegisterInfo.cpp" @@ -108,4 +119,5 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionProperties.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp" ) diff --git a/backend/lib/CodeGen/MachineConstantPool.cpp b/backend/lib/CodeGen/MachineConstantPool.cpp new file mode 100644 index 0000000..43f2620 --- /dev/null +++ b/backend/lib/CodeGen/MachineConstantPool.cpp @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineConstantPool.h" + +#include +#include +#include +#include +#include + +namespace rcc { +namespace { + +[[nodiscard]] const TargetConstant * +getTargetConstant(const MachineConstantPoolValue &Value) noexcept { + if (const auto *Target = std::get_if(&Value)) + return Target; + if (const auto *Expression = std::get_if(&Value)) + return &Expression->Expression; + return nullptr; +} + +[[nodiscard]] Expected verifyTargetConstant(const MachineConstantPoolEntryDesc &Description, + const TargetConstant &Constant) { + auto Verification = Constant.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + + auto RequiredAlignment = Constant.requiredAlignment(); + if (!RequiredAlignment) + return std::unexpected(RequiredAlignment.error()); + if (Description.Align.bytes() < RequiredAlignment->bytes()) { + return makeUnexpected(ErrorCode::VerificationError, + "constant pool alignment is weaker than target constant requirement"); + } + + auto Emission = Constant.emit(); + if (!Emission) + return std::unexpected(Emission.error()); + if (Emission->Bytes.size() != Description.Size) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant emitted size does not match its pool entry size"); + } + + auto RelocationKind = Constant.relocationKind(); + if (!RelocationKind) + return std::unexpected(RelocationKind.error()); + const bool HasRelocation = RelocationKind->has_value() || !Emission->Fixups.empty(); + if (HasRelocation == (Description.Relocation == ConstantPoolRelocationMode::None)) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant relocation contract disagrees with its pool entry"); + } + return {}; +} + +} // namespace + +ConstantPoolEntryKind MachineConstantPoolEntryDesc::kind() const noexcept { + switch (Value.index()) { + case 0: + return ConstantPoolEntryKind::Generic; + case 1: + return ConstantPoolEntryKind::Target; + case 2: + return ConstantPoolEntryKind::SymbolDifference; + case 3: + return ConstantPoolEntryKind::BlockAddress; + case 4: + return ConstantPoolEntryKind::TargetExpression; + default: + return ConstantPoolEntryKind::Generic; + } +} + +Expected MachineConstantPoolEntryDesc::verify() const { + if (Size == 0) + return makeUnexpected(ErrorCode::VerificationError, "constant pool entry size must be nonzero"); + if (Placement.Section >= ConstantPoolSectionKind::Count) { + return makeUnexpected(ErrorCode::VerificationError, "constant pool section kind is invalid"); + } + if (Placement.MaximumDistance && *Placement.MaximumDistance == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "constant pool maximum distance must be nonzero"); + } + if ((Placement.Section == ConstantPoolSectionKind::Target) != (Placement.TargetFlags != 0)) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant pool placement requires exactly one target identity"); + } + if (Placement.Section == ConstantPoolSectionKind::MergeableReadOnly && + Merge != ConstantPoolMergeKind::Exact) { + return makeUnexpected(ErrorCode::VerificationError, + "mergeable constant section requires exact entry merging"); + } + + switch (kind()) { + case ConstantPoolEntryKind::Generic: { + const auto *Generic = std::get_if(&Value); + if (Generic == nullptr || Generic->TypeIdentity.empty() || Generic->Bytes.size() != Size) { + return makeUnexpected(ErrorCode::VerificationError, + "generic constant pool entry has invalid type or byte size"); + } + if (Relocation != ConstantPoolRelocationMode::None) { + return makeUnexpected(ErrorCode::VerificationError, + "raw generic constant pool bytes cannot carry relocations"); + } + return {}; + } + case ConstantPoolEntryKind::Target: + case ConstantPoolEntryKind::TargetExpression: { + const TargetConstant *Target = getTargetConstant(Value); + if (Target == nullptr) + return makeUnexpected(ErrorCode::InternalError, "target constant variant is inconsistent"); + return verifyTargetConstant(*this, *Target); + } + case ConstantPoolEntryKind::SymbolDifference: { + const auto *Difference = std::get_if(&Value); + if (Difference == nullptr || Difference->Minuend.empty() || Difference->Subtrahend.empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "symbol-difference constant requires two symbols"); + } + if (Relocation == ConstantPoolRelocationMode::None) { + return makeUnexpected(ErrorCode::VerificationError, + "symbol-difference constant requires a relocation mode"); + } + return {}; + } + case ConstantPoolEntryKind::BlockAddress: { + const auto *Address = std::get_if(&Value); + if (Address == nullptr || Address->FunctionName.empty() || Address->BlockName.empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "block-address constant requires a function and block name"); + } + if (Relocation == ConstantPoolRelocationMode::None) { + return makeUnexpected(ErrorCode::VerificationError, + "block-address constant requires a relocation mode"); + } + return {}; + } + } + return makeUnexpected(ErrorCode::InternalError, "constant pool entry kind is unhandled"); +} + +Expected MachineConstantPoolEntryDesc::clone() const { + MachineConstantPoolEntryDesc Result; + Result.Size = Size; + Result.Align = Align; + Result.Merge = Merge; + Result.Relocation = Relocation; + Result.Placement = Placement; + + if (const auto *Generic = std::get_if(&Value)) { + Result.Value = *Generic; + } else if (const auto *Target = std::get_if(&Value)) { + auto Cloned = Target->clone(); + if (!Cloned) + return std::unexpected(Cloned.error()); + Result.Value = std::move(*Cloned); + } else if (const auto *Difference = std::get_if(&Value)) { + Result.Value = *Difference; + } else if (const auto *Address = std::get_if(&Value)) { + Result.Value = *Address; + } else if (const auto *Expression = std::get_if(&Value)) { + auto Cloned = Expression->Expression.clone(); + if (!Cloned) + return std::unexpected(Cloned.error()); + Result.Value = TargetExpressionConstant{std::move(*Cloned)}; + } else { + return makeUnexpected(ErrorCode::InternalError, "constant pool value is unhandled"); + } + return Result; +} + +bool MachineConstantPoolEntryDesc::semanticallyEquals( + const MachineConstantPoolEntryDesc &Other) const noexcept { + if (Size != Other.Size || Align != Other.Align || Merge != Other.Merge || + Relocation != Other.Relocation || Placement != Other.Placement || kind() != Other.kind()) { + return false; + } + + if (const auto *Generic = std::get_if(&Value)) { + const auto *OtherGeneric = std::get_if(&Other.Value); + return OtherGeneric != nullptr && *Generic == *OtherGeneric; + } + if (const auto *Target = std::get_if(&Value)) { + const auto *OtherTarget = std::get_if(&Other.Value); + return OtherTarget != nullptr && Target->equals(*OtherTarget); + } + if (const auto *Difference = std::get_if(&Value)) { + const auto *OtherDifference = std::get_if(&Other.Value); + return OtherDifference != nullptr && *Difference == *OtherDifference; + } + if (const auto *Address = std::get_if(&Value)) { + const auto *OtherAddress = std::get_if(&Other.Value); + return OtherAddress != nullptr && *Address == *OtherAddress; + } + if (const auto *Expression = std::get_if(&Value)) { + const auto *OtherExpression = std::get_if(&Other.Value); + return OtherExpression != nullptr && Expression->Expression.equals(OtherExpression->Expression); + } + return false; +} + +Expected MachineConstantPoolEntry::cloneDescription() const { + const MachineConstantPoolEntryDesc *Desc = description(); + if (Desc == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "constant pool tombstone has no description to clone"); + } + return Desc->clone(); +} + +const MachineConstantPoolEntry * +MachineConstantPool::findEquivalent(const MachineConstantPoolEntryDesc &Description, + std::optional Excluded) const noexcept { + for (const MachineConstantPoolEntry &Entry : Entries) { + if (Entry.isTombstone() || (Excluded && Entry.index() == *Excluded)) + continue; + const MachineConstantPoolEntryDesc *Existing = Entry.description(); + if (Existing != nullptr && Existing->semanticallyEquals(Description)) + return &Entry; + } + return nullptr; +} + +Expected +MachineConstantPool::addEntry(MachineConstantPoolEntryDesc Description) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "frozen constant pool cannot add entries"); + auto Verification = Description.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (Description.Merge == ConstantPoolMergeKind::Exact) { + if (const MachineConstantPoolEntry *Existing = findEquivalent(Description)) + return Existing->index(); + } + if (Entries.size() > std::numeric_limits::max()) { + return makeUnexpected(ErrorCode::Unsupported, "constant pool index space is exhausted"); + } + + const ConstantPoolIndex Index = + ConstantPoolIndex::fromValue(static_cast(Entries.size())); + Entries.push_back(MachineConstantPoolEntry(Index, std::move(Description))); + return Index; +} + +Expected MachineConstantPool::mutableEntry(ConstantPoolIndex Index) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "frozen constant pool cannot modify entries"); + if (Index.value() >= Entries.size()) + return makeUnexpected(ErrorCode::InvalidArgument, "constant pool index is out of range"); + MachineConstantPoolEntry &Entry = Entries[Index.value()]; + if (Entry.isTombstone()) + return makeUnexpected(ErrorCode::InvalidArgument, "constant pool index is a tombstone"); + return &Entry; +} + +Expected MachineConstantPool::updateEntry(ConstantPoolIndex Index, + MachineConstantPoolEntryDesc Description) { + auto Verification = Description.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto Entry = mutableEntry(Index); + if (!Entry) + return std::unexpected(Entry.error()); + if (Description.Merge == ConstantPoolMergeKind::Exact && + findEquivalent(Description, Index) != nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "constant pool update would create duplicate mergeable entries"); + } + (*Entry)->Description.emplace(std::move(Description)); + return {}; +} + +Expected MachineConstantPool::eraseEntry(ConstantPoolIndex Index) { + auto Entry = mutableEntry(Index); + if (!Entry) + return std::unexpected(Entry.error()); + (*Entry)->Description.emplace(); + return {}; +} + +const MachineConstantPoolEntry *MachineConstantPool::entry(ConstantPoolIndex Index) const noexcept { + if (Index.value() >= Entries.size() || Entries[Index.value()].isTombstone()) + return nullptr; + return &Entries[Index.value()]; +} + +std::size_t MachineConstantPool::liveEntryCount() const noexcept { + std::size_t Count = 0; + for (const MachineConstantPoolEntry &Entry : Entries) { + if (!Entry.isTombstone()) + ++Count; + } + return Count; +} + +Expected MachineConstantPool::freeze() { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "constant pool is already frozen"); + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + Frozen = true; + return {}; +} + +Expected MachineConstantPool::verify() const { + for (std::size_t Position = 0; Position < Entries.size(); ++Position) { + const MachineConstantPoolEntry &Entry = Entries[Position]; + if (Entry.index() != ConstantPoolIndex::fromValue(static_cast(Position))) { + return makeUnexpected(ErrorCode::VerificationError, + "constant pool index does not match its stable table position"); + } + const MachineConstantPoolEntryDesc *Description = Entry.description(); + if (Description == nullptr) + continue; + auto Verification = Description->verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (Description->Merge == ConstantPoolMergeKind::Exact && + findEquivalent(*Description, Entry.index()) != nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "constant pool contains duplicate mergeable entries"); + } + } + return {}; +} + +} // namespace rcc diff --git a/backend/lib/CodeGen/MachineFunction.cpp b/backend/lib/CodeGen/MachineFunction.cpp index fc1508c..64b6440 100644 --- a/backend/lib/CodeGen/MachineFunction.cpp +++ b/backend/lib/CodeGen/MachineFunction.cpp @@ -64,7 +64,8 @@ MachineFunction::create(std::string Name, MachineFunctionStableID StableID) { } MachineFunction::MachineFunction(std::string Name, MachineFunctionStableID StableID) - : Name(std::move(Name)), StableID(StableID), Arena(std::make_unique()) {} + : Name(std::move(Name)), StableID(StableID), JumpTables(*this), + Arena(std::make_unique()) {} MachineFunction::~MachineFunction() { for (MachineBasicBlock *Block = FirstBlock; Block != nullptr; Block = Block->NextBlock) { diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp index 46fed6c..5703161 100644 --- a/backend/lib/CodeGen/MachineFunctionCloner.cpp +++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp @@ -5,9 +5,11 @@ #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineMemOperand.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -100,16 +102,25 @@ struct CloneState final { return makeUnexpected(ErrorCode::VerificationError, "source constant-pool operand is invalid"); } - State.Result.ConstantPoolIndices.emplace(Constant->Index, Constant->Index); - return SourceOperand.cloneDetached(); + const auto Position = State.Result.ConstantPoolIndices.find(Constant->Index); + if (Position == State.Result.ConstantPoolIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source operand references an unmapped constant pool entry"); + } + return MachineOperand::constantPool(Position->second, Constant->Offset, + SourceOperand.targetFlags()); } case MachineOperand::Kind::JumpTableIndex: { const auto *JumpTable = SourceOperand.getIf(); if (JumpTable == nullptr) { return makeUnexpected(ErrorCode::VerificationError, "source jump-table operand is invalid"); } - State.Result.JumpTableIndices.emplace(JumpTable->Index, JumpTable->Index); - return SourceOperand.cloneDetached(); + const auto Position = State.Result.JumpTableIndices.find(JumpTable->Index); + if (Position == State.Result.JumpTableIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source operand references an unmapped jump table"); + } + return MachineOperand::jumpTable(Position->second, SourceOperand.targetFlags()); } case MachineOperand::Kind::Immediate: case MachineOperand::Kind::FloatingImmediate: @@ -126,14 +137,6 @@ struct CloneState final { return makeUnexpected(ErrorCode::InternalError, "source operand kind is not handled"); } -void recordMemoryIndexMappings(const MachineMemOperandDesc &Description, - MachineFunctionCloneResult &Result) { - if (const auto Constant = Description.Base.constantPoolIndex()) - Result.ConstantPoolIndices.emplace(*Constant, *Constant); - if (const auto JumpTable = Description.Base.jumpTableIndex()) - Result.JumpTableIndices.emplace(*JumpTable, *JumpTable); -} - [[nodiscard]] Expected cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { const auto Existing = State.MemOperands.find(&SourceOperand); @@ -141,7 +144,6 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { return Existing->second; MachineMemOperandDesc Description = SourceOperand.description(); - recordMemoryIndexMappings(Description, State.Result); if (const auto Frame = Description.Base.frameIndex()) { const auto Position = State.Result.FrameIndices.find(*Frame); if (Position == State.Result.FrameIndices.end()) { @@ -150,6 +152,22 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { } Description.Base = MemoryBase::frameIndex(Position->second); } + if (const auto Constant = Description.Base.constantPoolIndex()) { + const auto Position = State.Result.ConstantPoolIndices.find(*Constant); + if (Position == State.Result.ConstantPoolIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source MMO references an unmapped constant pool entry"); + } + Description.Base = MemoryBase::constantPool(Position->second); + } + if (const auto JumpTable = Description.Base.jumpTableIndex()) { + const auto Position = State.Result.JumpTableIndices.find(*JumpTable); + if (Position == State.Result.JumpTableIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source MMO references an unmapped jump table"); + } + Description.Base = MemoryBase::jumpTable(Position->second); + } auto DestinationOperand = State.Destination.createMemOperand(std::move(Description)); if (!DestinationOperand) return std::unexpected(DestinationOperand.error()); @@ -235,6 +253,31 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { return {}; } +[[nodiscard]] Expected cloneConstantPool(CloneState &State) { + for (const MachineConstantPoolEntry &SourceEntry : State.Source.constantPool().entries()) { + if (SourceEntry.isTombstone()) + continue; + auto Description = SourceEntry.cloneDescription(); + if (!Description) + return std::unexpected(Description.error()); + if (auto *Address = std::get_if(&Description->Value); + Address != nullptr && Address->FunctionName == State.Source.name()) { + Address->FunctionName = std::string(State.Destination.name()); + } + + auto DestinationIndex = State.Destination.constantPool().addEntry(std::move(*Description)); + if (!DestinationIndex) + return std::unexpected(DestinationIndex.error()); + State.Result.ConstantPoolIndices.emplace(SourceEntry.index(), *DestinationIndex); + } + if (State.Source.constantPool().isFrozen()) { + auto FreezeResult = State.Destination.constantPool().freeze(); + if (!FreezeResult) + return std::unexpected(FreezeResult.error()); + } + return {}; +} + [[nodiscard]] Expected cloneBlocks(CloneState &State) { for (const MachineBasicBlock *SourceBlock = State.Source.firstBlock(); SourceBlock != nullptr; SourceBlock = SourceBlock->next()) { @@ -256,6 +299,34 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { return {}; } +[[nodiscard]] Expected cloneJumpTables(CloneState &State) { + for (const MachineJumpTableEntry &SourceEntry : State.Source.jumpTableInfo().tables()) { + const MachineJumpTableDesc *SourceDescription = SourceEntry.description(); + if (SourceDescription == nullptr) + continue; + MachineJumpTableDesc DestinationDescription = *SourceDescription; + for (MachineBasicBlock *&Target : DestinationDescription.Targets) { + const auto Position = State.Blocks.find(Target); + if (Position == State.Blocks.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source jump table references an unmapped block"); + } + Target = Position->second; + } + auto DestinationIndex = + State.Destination.jumpTableInfo().createTable(std::move(DestinationDescription)); + if (!DestinationIndex) + return std::unexpected(DestinationIndex.error()); + State.Result.JumpTableIndices.emplace(SourceEntry.index(), *DestinationIndex); + } + if (State.Source.jumpTableInfo().isFrozen()) { + auto FreezeResult = State.Destination.jumpTableInfo().freeze(); + if (!FreezeResult) + return std::unexpected(FreezeResult.error()); + } + return {}; +} + [[nodiscard]] Expected cloneInstructions(CloneState &State) { for (const MachineBasicBlock *SourceBlock = State.Source.firstBlock(); SourceBlock != nullptr; SourceBlock = SourceBlock->next()) { @@ -427,9 +498,15 @@ Expected cloneMachineFunction(const MachineFunction auto FrameResult = cloneFrameObjects(State); if (!FrameResult) return std::unexpected(FrameResult.error()); + auto ConstantPoolResult = cloneConstantPool(State); + if (!ConstantPoolResult) + return std::unexpected(ConstantPoolResult.error()); auto BlockResult = cloneBlocks(State); if (!BlockResult) return std::unexpected(BlockResult.error()); + auto JumpTableResult = cloneJumpTables(State); + if (!JumpTableResult) + return std::unexpected(JumpTableResult.error()); auto InstructionResult = cloneInstructions(State); if (!InstructionResult) return std::unexpected(InstructionResult.error()); diff --git a/backend/lib/CodeGen/MachineIRBuilder.cpp b/backend/lib/CodeGen/MachineIRBuilder.cpp index 185b7df..74ca55b 100644 --- a/backend/lib/CodeGen/MachineIRBuilder.cpp +++ b/backend/lib/CodeGen/MachineIRBuilder.cpp @@ -342,6 +342,21 @@ Expected MachineInstrBuilder::validateFrameReference(FrameIndex Index) con return {}; } +Expected MachineInstrBuilder::validateConstantPoolReference(ConstantPoolIndex Index) const { + if (Function->constantPool().entry(Index) == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder references an unknown constant pool entry"); + } + return {}; +} + +Expected MachineInstrBuilder::validateJumpTableReference(JumpTableIndex Index) const { + if (Function->jumpTableInfo().table(Index) == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, "builder references an unknown jump table"); + } + return {}; +} + Expected MachineInstrBuilder::validateOperandOwnership(const MachineOperand &Operand) const { if (Operand.parent() != nullptr) { return makeUnexpected(ErrorCode::InvalidArgument, @@ -364,6 +379,16 @@ Expected MachineInstrBuilder::validateOperandOwnership(const MachineOperan if (!FrameResult) return FrameResult; } + if (const auto *Constant = Operand.getIf(); Constant != nullptr) { + auto ConstantResult = validateConstantPoolReference(Constant->Index); + if (!ConstantResult) + return ConstantResult; + } + if (const auto *JumpTable = Operand.getIf(); JumpTable != nullptr) { + auto JumpTableResult = validateJumpTableReference(JumpTable->Index); + if (!JumpTableResult) + return JumpTableResult; + } return {}; } @@ -405,11 +430,22 @@ Expected MachineInstrBuilder::build() { return std::unexpected(Ownership.error()); } for (const MachineMemOperand *MemOperand : MemOperands) { - if (const auto Frame = MemOperand->description().Base.frameIndex()) { + const MemoryBase &Base = MemOperand->description().Base; + if (const auto Frame = Base.frameIndex()) { auto FrameResult = validateFrameReference(*Frame); if (!FrameResult) return std::unexpected(FrameResult.error()); } + if (const auto Constant = Base.constantPoolIndex()) { + auto ConstantResult = validateConstantPoolReference(*Constant); + if (!ConstantResult) + return std::unexpected(ConstantResult.error()); + } + if (const auto JumpTable = Base.jumpTableIndex()) { + auto JumpTableResult = validateJumpTableReference(*JumpTable); + if (!JumpTableResult) + return std::unexpected(JumpTableResult.error()); + } } auto ExplicitValidation = Instructions->validateExplicitOperands( diff --git a/backend/lib/CodeGen/MachineJumpTableInfo.cpp b/backend/lib/CodeGen/MachineJumpTableInfo.cpp new file mode 100644 index 0000000..4ae0c15 --- /dev/null +++ b/backend/lib/CodeGen/MachineJumpTableInfo.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineJumpTableInfo.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" + +#include +#include +#include +#include +#include + +namespace rcc { + +Expected +MachineJumpTableInfo::verifyDescription(const MachineJumpTableDesc &Description) const { + if (Description.Encoding >= JumpTableEntryEncoding::Count) + return makeUnexpected(ErrorCode::VerificationError, "jump table encoding is invalid"); + if (Description.EntrySize == 0 || Description.EntrySize > 16 || + !std::has_single_bit(Description.EntrySize)) { + return makeUnexpected(ErrorCode::VerificationError, + "jump table entry size must be a power of two no larger than 16 bytes"); + } + if (Description.Targets.empty()) + return makeUnexpected(ErrorCode::VerificationError, "jump table must have at least one target"); + if (Description.Placement.MaximumDistance && *Description.Placement.MaximumDistance == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "jump table maximum distance must be nonzero"); + } + if ((Description.Encoding == JumpTableEntryEncoding::Target) != + (Description.TargetEncoding != 0)) { + return makeUnexpected(ErrorCode::VerificationError, + "target jump table encoding and target encoding ID are inconsistent"); + } + if (Description.Encoding == JumpTableEntryEncoding::CompressedOffset && + Description.EntrySize > 4) { + return makeUnexpected(ErrorCode::VerificationError, + "compressed jump table entries cannot exceed four bytes"); + } + + for (const MachineBasicBlock *Target : Description.Targets) { + if (Target == nullptr || &Target->function() != Owner || !Target->isLinked()) { + return makeUnexpected(ErrorCode::VerificationError, + "jump table target must be linked to its owning function"); + } + } + return {}; +} + +Expected MachineJumpTableInfo::createTable(MachineJumpTableDesc Description) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, + "frozen jump table info cannot create tables"); + auto Verification = verifyDescription(Description); + if (!Verification) + return std::unexpected(Verification.error()); + if (Tables.size() > std::numeric_limits::max()) + return makeUnexpected(ErrorCode::Unsupported, "jump table index space is exhausted"); + + const JumpTableIndex Index = JumpTableIndex::fromValue(static_cast(Tables.size())); + Tables.push_back(MachineJumpTableEntry(Index, std::move(Description))); + return Index; +} + +Expected MachineJumpTableInfo::mutableTable(JumpTableIndex Index) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, + "frozen jump table info cannot modify tables"); + if (Index.value() >= Tables.size()) + return makeUnexpected(ErrorCode::InvalidArgument, "jump table index is out of range"); + MachineJumpTableEntry &Entry = Tables[Index.value()]; + if (Entry.isTombstone()) + return makeUnexpected(ErrorCode::InvalidArgument, "jump table index is a tombstone"); + return &Entry; +} + +Expected MachineJumpTableInfo::updateTable(JumpTableIndex Index, + MachineJumpTableDesc Description) { + auto Verification = verifyDescription(Description); + if (!Verification) + return std::unexpected(Verification.error()); + auto Entry = mutableTable(Index); + if (!Entry) + return std::unexpected(Entry.error()); + (*Entry)->Description.emplace(std::move(Description)); + return {}; +} + +Expected MachineJumpTableInfo::eraseTable(JumpTableIndex Index) { + auto Entry = mutableTable(Index); + if (!Entry) + return std::unexpected(Entry.error()); + (*Entry)->Description.emplace(); + return {}; +} + +const MachineJumpTableEntry *MachineJumpTableInfo::table(JumpTableIndex Index) const noexcept { + if (Index.value() >= Tables.size() || Tables[Index.value()].isTombstone()) + return nullptr; + return &Tables[Index.value()]; +} + +std::size_t MachineJumpTableInfo::liveTableCount() const noexcept { + std::size_t Count = 0; + for (const MachineJumpTableEntry &Entry : Tables) { + if (!Entry.isTombstone()) + ++Count; + } + return Count; +} + +Expected MachineJumpTableInfo::freeze() { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "jump table info is already frozen"); + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + Frozen = true; + return {}; +} + +Expected MachineJumpTableInfo::verify() const { + if (Owner == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "jump table info has no owner"); + for (std::size_t Position = 0; Position < Tables.size(); ++Position) { + const MachineJumpTableEntry &Entry = Tables[Position]; + if (Entry.index() != JumpTableIndex::fromValue(static_cast(Position))) { + return makeUnexpected(ErrorCode::VerificationError, + "jump table index does not match its stable table position"); + } + const MachineJumpTableDesc *Description = Entry.description(); + if (Description == nullptr) + continue; + auto Verification = verifyDescription(*Description); + if (!Verification) + return std::unexpected(Verification.error()); + } + return {}; +} + +} // namespace rcc diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp index 6331366..50c1001 100644 --- a/backend/lib/CodeGen/MachineVerifier.cpp +++ b/backend/lib/CodeGen/MachineVerifier.cpp @@ -40,9 +40,15 @@ public: auto FrameResult = Function.frameInfo().verify(); if (!FrameResult) return failWith("frame info", FrameResult.error()); + auto ConstantPoolResult = Function.constantPool().verify(); + if (!ConstantPoolResult) + return failWith("constant pool", ConstantPoolResult.error()); auto LayoutResult = verifyBlockLayout(); if (!LayoutResult) return LayoutResult; + auto JumpTableResult = Function.jumpTableInfo().verify(); + if (!JumpTableResult) + return failWith("jump table info", JumpTableResult.error()); auto FrameLifetimeResult = verifyFrameLifetimes(); if (!FrameLifetimeResult) return FrameLifetimeResult; @@ -286,6 +292,18 @@ private: return {}; } + [[nodiscard]] Expected verifyConstantPoolIndex(ConstantPoolIndex Index) const { + if (Function.constantPool().entry(Index) == nullptr) + return fail("live instruction references an unknown constant pool entry"); + return {}; + } + + [[nodiscard]] Expected verifyJumpTableIndex(JumpTableIndex Index) const { + if (Function.jumpTableInfo().table(Index) == nullptr) + return fail("live instruction references an unknown jump table"); + return {}; + } + [[nodiscard]] Expected verifyFrameLifetimes() const { for (const StackObject &Object : Function.frameInfo().objects()) { const StackObjectDesc *Description = Object.description(); @@ -334,6 +352,16 @@ private: if (!FrameResult) return FrameResult; } + if (const auto *Constant = Operand.getIf(); Constant != nullptr) { + auto ConstantResult = verifyConstantPoolIndex(Constant->Index); + if (!ConstantResult) + return ConstantResult; + } + if (const auto *JumpTable = Operand.getIf(); JumpTable != nullptr) { + auto JumpTableResult = verifyJumpTableIndex(JumpTable->Index); + if (!JumpTableResult) + return JumpTableResult; + } if (const auto *Block = Operand.getIf(); Block != nullptr && @@ -364,11 +392,22 @@ private: } } for (const MachineMemOperand *MemOperand : Instruction.memOperands()) { - if (const auto Frame = MemOperand->description().Base.frameIndex()) { + const MemoryBase &Base = MemOperand->description().Base; + if (const auto Frame = Base.frameIndex()) { auto FrameResult = verifyFrameIndex(*Frame); if (!FrameResult) return FrameResult; } + if (const auto Constant = Base.constantPoolIndex()) { + auto ConstantResult = verifyConstantPoolIndex(*Constant); + if (!ConstantResult) + return ConstantResult; + } + if (const auto JumpTable = Base.jumpTableIndex()) { + auto JumpTableResult = verifyJumpTableIndex(*JumpTable); + if (!JumpTableResult) + return JumpTableResult; + } } return {}; } diff --git a/backend/lib/CodeGen/TargetConstant.cpp b/backend/lib/CodeGen/TargetConstant.cpp new file mode 100644 index 0000000..8859325 --- /dev/null +++ b/backend/lib/CodeGen/TargetConstant.cpp @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetConstant.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +Expected TargetConstantEmission::verify() const { + if (Bytes.empty()) + return makeUnexpected(ErrorCode::VerificationError, + "target constant emission must contain at least one byte"); + + std::unordered_set FixupOffsets; + for (const TargetConstantFixup &Fixup : Fixups) { + if (Fixup.Offset >= Bytes.size()) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant fixup offset is outside emitted bytes"); + } + if (Fixup.Kind.value() == 0) + return makeUnexpected(ErrorCode::VerificationError, + "target constant fixup relocation kind must be nonzero"); + if (Fixup.Symbol.empty()) + return makeUnexpected(ErrorCode::VerificationError, + "target constant fixup symbol cannot be empty"); + if (!FixupOffsets.insert(Fixup.Offset).second) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant emission has duplicate fixup offsets"); + } + } + return {}; +} + +TargetConstant::TargetConstant(TargetConstant &&) noexcept = default; +TargetConstant &TargetConstant::operator=(TargetConstant &&) noexcept = default; +TargetConstant::~TargetConstant() = default; + +std::optional TargetConstant::typeID() const noexcept { + if (Storage == nullptr) + return std::nullopt; + return Storage->typeID(); +} + +std::optional TargetConstant::schemaVersion() const noexcept { + if (Storage == nullptr) + return std::nullopt; + return Storage->schemaVersion(); +} + +Expected TargetConstant::verify() const { + if (Storage == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "target constant is empty"); + if (Storage->typeID().value() == 0 || Storage->schemaVersion() == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant identity or schema version is invalid"); + } + return Storage->verify(); +} + +Expected TargetConstant::clone() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto ClonedStorage = Storage->cloneStorage(); + if (!ClonedStorage) + return std::unexpected(ClonedStorage.error()); + TargetConstant Result(std::move(*ClonedStorage)); + auto CloneVerification = Result.verify(); + if (!CloneVerification) + return std::unexpected(CloneVerification.error()); + return Result; +} + +Expected TargetConstant::stableHash() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->stableHash(); +} + +Expected TargetConstant::print() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->print(); +} + +Expected TargetConstant::encode() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto Payload = Storage->encodePayload(); + if (!Payload) + return std::unexpected(Payload.error()); + return EncodedTargetConstant{Storage->typeID(), Storage->schemaVersion(), std::move(*Payload)}; +} + +Expected TargetConstant::emit() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto Emission = Storage->emit(); + if (!Emission) + return std::unexpected(Emission.error()); + auto EmissionVerification = Emission->verify(); + if (!EmissionVerification) + return std::unexpected(EmissionVerification.error()); + return Emission; +} + +Expected TargetConstant::requiredAlignment() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->requiredAlignment(); +} + +Expected> TargetConstant::relocationKind() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + const std::optional Kind = Storage->relocationKind(); + if (Kind && Kind->value() == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "target constant relocation kind must be nonzero"); + } + return Kind; +} + +bool TargetConstant::equals(const TargetConstant &Other) const noexcept { + if (Storage == nullptr || Other.Storage == nullptr) + return Storage == nullptr && Other.Storage == nullptr; + if (Storage->typeID() != Other.Storage->typeID() || + Storage->localTypeToken() != Other.Storage->localTypeToken()) { + return false; + } + return Storage->equals(*Other.Storage); +} + +} // namespace rcc diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 18cda7c..eb4dbfe 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -4,12 +4,15 @@ set( RCCBackendCodeGenTestSources CoreTypesTest.cpp InstructionDescriptorTest.cpp + MachineConstantPoolTest.cpp MachineFrameInfoTest.cpp MachineFunctionClonerTest.cpp MachineIRBuilderTest.cpp MachineIRCoreTest.cpp + MachineJumpTableInfoTest.cpp MachineVerifierTest.cpp MachineValueTypesTest.cpp + TargetConstantTest.cpp ) add_executable(RCCBackendCodeGenCoreTypesTests CoreTypesTest.cpp) @@ -33,6 +36,15 @@ add_test( COMMAND RCCBackendCodeGenInstructionDescriptorTests ) +add_executable(RCCBackendCodeGenMachineConstantPoolTests MachineConstantPoolTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineConstantPoolTests) +target_link_libraries(RCCBackendCodeGenMachineConstantPoolTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.MachineConstantPool + COMMAND RCCBackendCodeGenMachineConstantPoolTests +) + add_executable(RCCBackendCodeGenMachineFrameInfoTests MachineFrameInfoTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineFrameInfoTests) target_link_libraries(RCCBackendCodeGenMachineFrameInfoTests PRIVATE RCCBackend::CodeGen) @@ -99,23 +111,47 @@ add_test( COMMAND RCCBackendCodeGenMachineIRCoreTests ) +add_executable(RCCBackendCodeGenMachineJumpTableInfoTests MachineJumpTableInfoTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineJumpTableInfoTests) +target_link_libraries(RCCBackendCodeGenMachineJumpTableInfoTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.MachineJumpTableInfo + COMMAND RCCBackendCodeGenMachineJumpTableInfoTests +) + +add_executable(RCCBackendCodeGenTargetConstantTests TargetConstantTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenTargetConstantTests) +target_link_libraries(RCCBackendCodeGenTargetConstantTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.TargetConstant + COMMAND RCCBackendCodeGenTargetConstantTests +) + rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" ) diff --git a/backend/unittests/CodeGen/MachineConstantPoolTest.cpp b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp new file mode 100644 index 0000000..7343fdb --- /dev/null +++ b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineConstantPool.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 PoolTargetConstant final { + std::uint64_t Value = 0; + std::string Symbol; + + [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept { + return rcc::TargetConstantTypeID::fromValue(0x5253434350430001ULL); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return 1; + } + + [[nodiscard]] rcc::Expected verify() const { + if (Value == 0) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "pool target constant value must be nonzero"); + } + return {}; + } + + [[nodiscard]] rcc::Expected clone() const { + return PoolTargetConstant{Value, Symbol}; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept { + std::uint64_t Hash = Value; + for (const char Character : Symbol) + Hash = (Hash * 16777619U) ^ static_cast(Character); + return Hash; + } + + [[nodiscard]] std::string print() const { + return Symbol.empty() ? std::to_string(Value) : Symbol + "+" + std::to_string(Value); + } + + [[nodiscard]] std::vector bytes() const { + std::vector Bytes(8); + for (std::size_t Index = 0; Index < Bytes.size(); ++Index) + Bytes[Index] = static_cast((Value >> (Index * 8U)) & 0xffU); + return Bytes; + } + + [[nodiscard]] rcc::Expected> encode() const { + std::vector Payload = bytes(); + for (const char Character : Symbol) + Payload.push_back(static_cast(static_cast(Character))); + return Payload; + } + + [[nodiscard]] rcc::Expected emit() const { + rcc::TargetConstantEmission Emission; + Emission.Bytes = bytes(); + if (!Symbol.empty()) { + Emission.Fixups.push_back({0, rcc::TargetRelocationKindID::fromValue(3), Symbol, + std::bit_cast(Value)}); + } + return Emission; + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return *rcc::Alignment::create(8); + } + + [[nodiscard]] std::optional relocationKind() const noexcept { + if (Symbol.empty()) + return std::nullopt; + return rcc::TargetRelocationKindID::fromValue(3); + } + + [[nodiscard]] static rcc::Expected parse(std::string_view Text) { + if (Text.empty()) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "pool constant text is empty"); + return PoolTargetConstant{1, std::string(Text)}; + } + + [[nodiscard]] static rcc::Expected + decode(std::uint32_t Version, std::span Payload) { + if (Version != schemaVersion() || Payload.size() < 8) { + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, + "pool target constant encoding is invalid"); + } + std::uint64_t Value = 0; + for (std::size_t Index = 0; Index < 8; ++Index) + Value |= std::to_integer(Payload[Index]) << (Index * 8U); + std::string Symbol; + for (const std::byte Character : Payload.subspan(8)) + Symbol.push_back(static_cast(std::to_integer(Character))); + return PoolTargetConstant{Value, std::move(Symbol)}; + } + + friend bool operator==(const PoolTargetConstant &Left, const PoolTargetConstant &Right) = default; +}; + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc +genericDescription(rcc::ConstantPoolMergeKind Merge) { + rcc::MachineConstantPoolEntryDesc Description; + Description.Value = + rcc::GenericConstantData{"i32", {std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}}}; + Description.Size = 4; + Description.Align = *rcc::Alignment::create(4); + Description.Merge = Merge; + if (Merge == rcc::ConstantPoolMergeKind::Exact) + Description.Placement.Section = rcc::ConstantPoolSectionKind::MergeableReadOnly; + return Description; +} + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc targetDescription(bool Expression) { + rcc::MachineConstantPoolEntryDesc Description; + auto Constant = rcc::TargetConstant::create( + PoolTargetConstant{Expression ? 17U : 9U, Expression ? "symbol" : ""}); + if (Expression) + Description.Value = rcc::TargetExpressionConstant{std::move(*Constant)}; + else + Description.Value = std::move(*Constant); + Description.Size = 8; + Description.Align = *rcc::Alignment::create(8); + Description.Relocation = + Expression ? rcc::ConstantPoolRelocationMode::Target : rcc::ConstantPoolRelocationMode::None; + return Description; +} + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc symbolDifferenceDescription() { + rcc::MachineConstantPoolEntryDesc Description; + Description.Value = rcc::SymbolDifferenceConstant{"upper", "lower", 4}; + Description.Size = 4; + Description.Align = *rcc::Alignment::create(4); + Description.Relocation = rcc::ConstantPoolRelocationMode::PositionIndependent; + return Description; +} + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc blockAddressDescription() { + rcc::MachineConstantPoolEntryDesc Description; + Description.Value = rcc::BlockAddressConstant{"function", "block", 0}; + Description.Size = 8; + Description.Align = *rcc::Alignment::create(8); + Description.Relocation = rcc::ConstantPoolRelocationMode::Static; + return Description; +} + +void testPoolLifecycle(TestContext &Context) { + rcc::MachineConstantPool Pool; + + auto MergedA = Pool.addEntry(genericDescription(rcc::ConstantPoolMergeKind::Exact)); + auto MergedB = Pool.addEntry(genericDescription(rcc::ConstantPoolMergeKind::Exact)); + auto Unmerged = Pool.addEntry(genericDescription(rcc::ConstantPoolMergeKind::Never)); + Context.expect(MergedA && MergedB && Unmerged && *MergedA == *MergedB && MergedA->value() == 0 && + Unmerged->value() == 1 && Pool.entries().size() == 2 && + Pool.liveEntryCount() == 2, + "exact constants must merge while nonmergeable constants retain identity"); + if (!MergedA || !Unmerged) + return; + + Context.expect( + !Pool.updateEntry(*Unmerged, genericDescription(rcc::ConstantPoolMergeKind::Exact)), + "pool update must reject duplicate mergeable entries"); + Context.expect(Pool.eraseEntry(*Unmerged).has_value() && Pool.entry(*Unmerged) == nullptr, + "constant pool erase must leave a tombstone"); + + auto Target = Pool.addEntry(targetDescription(false)); + auto Difference = Pool.addEntry(symbolDifferenceDescription()); + auto Address = Pool.addEntry(blockAddressDescription()); + auto Expression = Pool.addEntry(targetDescription(true)); + Context.expect(Target && Difference && Address && Expression && Target->value() == 2 && + Expression->value() == 5 && Pool.liveEntryCount() == 5, + "all constant pool value kinds must receive monotonic indices"); + if (!Target || !Expression) + return; + + const rcc::MachineConstantPoolEntry *TargetEntry = Pool.entry(*Target); + auto ClonedDescription = + TargetEntry == nullptr ? rcc::Expected(rcc::makeUnexpected( + rcc::ErrorCode::InternalError, "missing target entry")) + : TargetEntry->cloneDescription(); + Context.expect(ClonedDescription && + TargetEntry->description()->semanticallyEquals(*ClonedDescription), + "target constant pool descriptions must deep-clone"); + + Context.expect(Pool.verify().has_value() && Pool.freeze().has_value() && Pool.isFrozen(), + "valid constant pool must freeze after verification"); + Context.expect(!Pool.addEntry(blockAddressDescription()) && !Pool.eraseEntry(*Target) && + !Pool.updateEntry(*Expression, targetDescription(true)) && !Pool.freeze(), + "frozen constant pool must reject every mutation"); +} + +void testInvalidEntries(TestContext &Context) { + rcc::MachineConstantPool Pool; + + auto SizeMismatch = genericDescription(rcc::ConstantPoolMergeKind::Never); + SizeMismatch.Size = 8; + Context.expect(!Pool.addEntry(std::move(SizeMismatch)), + "generic constant byte size must match its entry size"); + + auto RelocatingBytes = genericDescription(rcc::ConstantPoolMergeKind::Never); + RelocatingBytes.Relocation = rcc::ConstantPoolRelocationMode::Static; + Context.expect(!Pool.addEntry(std::move(RelocatingBytes)), + "raw generic bytes must reject implicit relocation semantics"); + + auto WeakTargetAlignment = targetDescription(false); + WeakTargetAlignment.Align = *rcc::Alignment::create(4); + Context.expect(!Pool.addEntry(std::move(WeakTargetAlignment)), + "target constant must enforce its required alignment"); + + auto MissingTargetRelocation = targetDescription(true); + MissingTargetRelocation.Relocation = rcc::ConstantPoolRelocationMode::None; + Context.expect(!Pool.addEntry(std::move(MissingTargetRelocation)), + "target expression must agree with its relocation contract"); + + auto InvalidPlacement = blockAddressDescription(); + InvalidPlacement.Placement.Section = rcc::ConstantPoolSectionKind::Target; + Context.expect(!Pool.addEntry(std::move(InvalidPlacement)), + "target placement must carry target flags"); + + auto ZeroDistance = blockAddressDescription(); + ZeroDistance.Placement.MaximumDistance = 0; + Context.expect(!Pool.addEntry(std::move(ZeroDistance)), + "constant pool maximum distance must be nonzero"); +} + +} // namespace + +int main() { + TestContext Context; + testPoolLifecycle(Context); + testInvalidEntries(Context); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp index 3b2fce9..f634a57 100644 --- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -6,17 +6,20 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineMemOperand.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/MachineVerifier.h" #include +#include #include #include #include @@ -35,6 +38,8 @@ constexpr std::uint32_t CopyOpcode = 4; constexpr std::uint32_t FrameOpcode = 5; constexpr std::uint32_t BlockAddressOpcode = 6; constexpr std::uint32_t LoadOpcode = 7; +constexpr std::uint32_t ConstantPoolOpcode = 8; +constexpr std::uint32_t JumpTableOpcode = 9; class TestContext final { public: @@ -106,6 +111,16 @@ private: Load.Flags.set(rcc::InstructionFlag::MayLoad); Load.RequiresMemoryOperands = true; + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + std::vector Descriptors; Descriptors.push_back(std::move(Nop)); Descriptors.push_back(std::move(Def)); @@ -114,6 +129,8 @@ private: Descriptors.push_back(std::move(Frame)); Descriptors.push_back(std::move(BlockAddress)); Descriptors.push_back(std::move(Load)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); return rcc::InstructionSet::create(std::move(Descriptors)); } @@ -138,12 +155,12 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { return *Block; } -[[nodiscard]] rcc::MachineMemOperandDesc loadDescription(rcc::FrameIndex Index) { +[[nodiscard]] rcc::MachineMemOperandDesc loadDescription(rcc::MemoryBase Base) { rcc::MachineMemOperandDesc Description; Description.Accesses = {rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Dereferenceable}; Description.Size = *rcc::MemorySize::fixed(8); Description.Align = *rcc::Alignment::create(8); - Description.Base = rcc::MemoryBase::frameIndex(Index); + Description.Base = std::move(Base); Description.Offset = -8; return Description; } @@ -164,7 +181,9 @@ struct SourceFixture final { rcc::Register Value; rcc::Register CopyValue; rcc::FrameIndex StackSlot; - const rcc::MachineMemOperand *LoadMMO = nullptr; + rcc::ConstantPoolIndex Constant; + rcc::JumpTableIndex JumpTable; + std::vector LoadMMOs; }; [[nodiscard]] bool populateSource(TestContext &Context, rcc::MachineFunction &Function, @@ -203,6 +222,50 @@ struct SourceFixture final { if (Fixture.Entry == nullptr || Fixture.Exit == nullptr) return false; + rcc::MachineConstantPoolEntryDesc HistoricalConstant; + HistoricalConstant.Value = + rcc::GenericConstantData{"i32", std::vector(4, std::byte{1})}; + HistoricalConstant.Size = 4; + HistoricalConstant.Align = *rcc::Alignment::create(4); + auto HistoricalConstantIndex = Function.constantPool().addEntry(std::move(HistoricalConstant)); + Context.expect(HistoricalConstantIndex && + Function.constantPool().eraseEntry(*HistoricalConstantIndex).has_value(), + "source constant pool history must contain a tombstone"); + + rcc::MachineConstantPoolEntryDesc Constant; + Constant.Value = rcc::BlockAddressConstant{std::string(Function.name()), "exit", 5}; + Constant.Size = 8; + Constant.Align = *rcc::Alignment::create(8); + Constant.Relocation = rcc::ConstantPoolRelocationMode::Static; + auto ConstantIndex = Function.constantPool().addEntry(std::move(Constant)); + Context.expect(ConstantIndex && Function.constantPool().freeze().has_value(), + "source live constant pool entry must be frozen"); + if (!ConstantIndex) + return false; + Fixture.Constant = *ConstantIndex; + + rcc::MachineJumpTableDesc HistoricalJumpTable; + HistoricalJumpTable.EntrySize = 4; + HistoricalJumpTable.Align = *rcc::Alignment::create(4); + HistoricalJumpTable.Targets = {Fixture.Entry}; + auto HistoricalJumpTableIndex = + Function.jumpTableInfo().createTable(std::move(HistoricalJumpTable)); + Context.expect(HistoricalJumpTableIndex && + Function.jumpTableInfo().eraseTable(*HistoricalJumpTableIndex).has_value(), + "source jump table history must contain a tombstone"); + + rcc::MachineJumpTableDesc JumpTable; + JumpTable.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase; + JumpTable.EntrySize = 4; + JumpTable.Align = *rcc::Alignment::create(4); + JumpTable.Targets = {Fixture.Exit, Fixture.Entry, Fixture.Exit}; + auto JumpTableIndex = Function.jumpTableInfo().createTable(std::move(JumpTable)); + Context.expect(JumpTableIndex && Function.jumpTableInfo().freeze().has_value(), + "source live jump table must be frozen"); + if (!JumpTableIndex) + return false; + Fixture.JumpTable = *JumpTableIndex; + auto Value = Function.registerInfo().createVirtualRegister(*Type); auto CopyValue = Function.registerInfo().createVirtualRegister(*Type); Context.expect(Value && CopyValue, "source live vregs must be created"); @@ -260,20 +323,39 @@ struct SourceFixture final { Context.expect(Frame && Frame->addFrameIndex(Fixture.StackSlot, -8).has_value() && Frame->build(), "source frame-index instruction must build"); + auto ConstantPool = Builder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + Context.expect(ConstantPool && ConstantPool->addConstantPool(Fixture.Constant, 8).has_value() && + ConstantPool->build(), + "source constant-pool instruction must build"); + + auto JumpTableInstruction = Builder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect(JumpTableInstruction && + JumpTableInstruction->addJumpTable(Fixture.JumpTable).has_value() && + JumpTableInstruction->build(), + "source jump-table instruction must build"); + auto Address = Builder.buildInstr(rcc::Opcode::fromValue(BlockAddressOpcode)); Context.expect( Address && Address->addBlockAddress(std::string(Function.name()), "exit", 3).has_value() && Address->build(), "source self block-address instruction must build"); - auto MMO = Function.createMemOperand(loadDescription(Fixture.StackSlot)); - Context.expect(MMO.has_value(), "source MMO must be created"); - if (!MMO) - return false; - Fixture.LoadMMO = *MMO; + for (rcc::MemoryBase Base : {rcc::MemoryBase::frameIndex(Fixture.StackSlot), + rcc::MemoryBase::constantPool(Fixture.Constant), + rcc::MemoryBase::jumpTable(Fixture.JumpTable)}) { + auto MMO = Function.createMemOperand(loadDescription(std::move(Base))); + Context.expect(MMO.has_value(), "source MMO must be created"); + if (!MMO) + return false; + Fixture.LoadMMOs.push_back(*MMO); + } auto Load = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); - Context.expect(Load && Load->addMemOperand(**MMO).has_value() && Load->build(), - "source load with MMO must build"); + if (!Load) + return false; + bool AddedAllMMOs = true; + for (const rcc::MachineMemOperand *MMO : Fixture.LoadMMOs) + AddedAllMMOs &= Load->addMemOperand(*MMO).has_value(); + Context.expect(AddedAllMMOs && Load->build(), "source load with MMOs must build"); rcc::MachineInstr *Definition = findInstruction(*Fixture.Entry, DefOpcode); rcc::MachineInstr *LoadInstruction = findInstruction(*Fixture.Exit, LoadOpcode); @@ -322,13 +404,18 @@ void testDeepClone(TestContext &Context) { rcc::MachineVerifier::verify(*Source, *InstructionSet).has_value(), "source and destination must independently preserve valid properties"); Context.expect(Source->allBlocks().size() == 3 && Destination.allBlocks().size() == 2 && - Source->allInstructions().size() == 7 && - Destination.allInstructions().size() == 6 && + Source->allInstructions().size() == 9 && + Destination.allInstructions().size() == 8 && Source->frameInfo().objects().size() == 2 && - Destination.frameInfo().objects().size() == 1, + Destination.frameInfo().objects().size() == 1 && + Source->constantPool().entries().size() == 2 && + Destination.constantPool().entries().size() == 1 && + Source->jumpTableInfo().tables().size() == 2 && + Destination.jumpTableInfo().tables().size() == 1, "clone must omit detached arena history"); - Context.expect(Clone->BlockStableIDs.size() == 2 && Clone->InstructionStableIDs.size() == 6 && - Clone->VirtualRegisters.size() == 2, + Context.expect(Clone->BlockStableIDs.size() == 2 && Clone->InstructionStableIDs.size() == 8 && + Clone->VirtualRegisters.size() == 2 && Clone->FrameIndices.size() == 1 && + Clone->ConstantPoolIndices.size() == 1 && Clone->JumpTableIndices.size() == 1, "clone must publish complete live identity mappings"); rcc::MachineBasicBlock *DestinationEntry = Destination.firstBlock(); @@ -387,13 +474,18 @@ void testDeepClone(TestContext &Context) { rcc::MachineInstr *DestinationBranch = findInstruction(*DestinationEntry, BranchOpcode); rcc::MachineInstr *DestinationCopy = findInstruction(*DestinationExit, CopyOpcode); rcc::MachineInstr *DestinationFrame = findInstruction(*DestinationExit, FrameOpcode); + rcc::MachineInstr *DestinationConstantPool = + findInstruction(*DestinationExit, ConstantPoolOpcode); + rcc::MachineInstr *DestinationJumpTable = findInstruction(*DestinationExit, JumpTableOpcode); rcc::MachineInstr *DestinationAddress = findInstruction(*DestinationExit, BlockAddressOpcode); rcc::MachineInstr *DestinationLoad = findInstruction(*DestinationExit, LoadOpcode); Context.expect(DestinationDef && DestinationBranch && DestinationCopy && DestinationFrame && - DestinationAddress && DestinationLoad, + DestinationConstantPool && DestinationJumpTable && DestinationAddress && + DestinationLoad, "clone must preserve every live instruction"); if (!DestinationDef || !DestinationBranch || !DestinationCopy || !DestinationFrame || - !DestinationAddress || !DestinationLoad) { + !DestinationConstantPool || !DestinationJumpTable || !DestinationAddress || + !DestinationLoad) { return; } @@ -417,20 +509,73 @@ void testDeepClone(TestContext &Context) { DestinationFrame->operandAt(0)->getIf()->Index == FramePosition->second, "clone must compact and remap frame indices"); + + const auto ConstantPosition = Clone->ConstantPoolIndices.find(Fixture.Constant); + const auto JumpTablePosition = Clone->JumpTableIndices.find(Fixture.JumpTable); + Context.expect( + ConstantPosition != Clone->ConstantPoolIndices.end() && + JumpTablePosition != Clone->JumpTableIndices.end() && + ConstantPosition->second == rcc::ConstantPoolIndex::fromValue(0) && + JumpTablePosition->second == rcc::JumpTableIndex::fromValue(0) && + DestinationConstantPool->operandAt(0)->getIf()->Index == + ConstantPosition->second && + DestinationJumpTable->operandAt(0)->getIf()->Index == + JumpTablePosition->second, + "clone must compact and remap constant-pool and jump-table indices"); + + const rcc::MachineConstantPoolEntry *DestinationConstantEntry = + ConstantPosition == Clone->ConstantPoolIndices.end() + ? nullptr + : Destination.constantPool().entry(ConstantPosition->second); + const auto *DestinationPoolAddress = + DestinationConstantEntry == nullptr + ? nullptr + : std::get_if(&DestinationConstantEntry->description()->Value); + Context.expect(Destination.constantPool().isFrozen() && DestinationPoolAddress != nullptr && + DestinationPoolAddress->FunctionName == "cloned_function" && + DestinationPoolAddress->BlockName == "exit", + "clone must preserve frozen constant pool state and rewrite self addresses"); + + const rcc::MachineJumpTableEntry *DestinationJumpTableEntry = + JumpTablePosition == Clone->JumpTableIndices.end() + ? nullptr + : Destination.jumpTableInfo().table(JumpTablePosition->second); + const rcc::MachineJumpTableDesc *DestinationJumpDescription = + DestinationJumpTableEntry == nullptr ? nullptr : DestinationJumpTableEntry->description(); + Context.expect(Destination.jumpTableInfo().isFrozen() && DestinationJumpDescription != nullptr && + DestinationJumpDescription->Targets.size() == 3 && + DestinationJumpDescription->Targets[0] == DestinationExit && + DestinationJumpDescription->Targets[1] == DestinationEntry && + DestinationJumpDescription->Targets[2] == DestinationExit, + "clone must preserve jump-table order while remapping block targets"); + Context.expect( DestinationAddress->operandAt(0)->getIf()->FunctionName == "cloned_function", "clone must rewrite self block-address function names"); - rcc::MachineMemOperandDesc ExpectedMMO = Fixture.LoadMMO->description(); - if (FramePosition != Clone->FrameIndices.end()) - ExpectedMMO.Base = rcc::MemoryBase::frameIndex(FramePosition->second); - Context.expect(DestinationLoad->memOperands().size() == 1 && - DestinationLoad->memOperands()[0] != Fixture.LoadMMO && - DestinationLoad->memOperands()[0]->description() == ExpectedMMO && - Destination.ownsMemOperand(*DestinationLoad->memOperands()[0]) && - !Source->ownsMemOperand(*DestinationLoad->memOperands()[0]), - "clone must deep-copy MMO ownership while remapping frame semantics"); + bool MMOsMatch = DestinationLoad->memOperands().size() == Fixture.LoadMMOs.size(); + for (std::size_t Index = 0; MMOsMatch && Index < DestinationLoad->memOperands().size(); ++Index) { + rcc::MachineMemOperandDesc ExpectedMMO = Fixture.LoadMMOs[Index]->description(); + if (const auto Frame = ExpectedMMO.Base.frameIndex(); + Frame && FramePosition != Clone->FrameIndices.end()) { + ExpectedMMO.Base = rcc::MemoryBase::frameIndex(FramePosition->second); + } + if (const auto Constant = ExpectedMMO.Base.constantPoolIndex(); + Constant && ConstantPosition != Clone->ConstantPoolIndices.end()) { + ExpectedMMO.Base = rcc::MemoryBase::constantPool(ConstantPosition->second); + } + if (const auto JumpTable = ExpectedMMO.Base.jumpTableIndex(); + JumpTable && JumpTablePosition != Clone->JumpTableIndices.end()) { + ExpectedMMO.Base = rcc::MemoryBase::jumpTable(JumpTablePosition->second); + } + + const rcc::MachineMemOperand *DestinationMMO = DestinationLoad->memOperands()[Index]; + MMOsMatch = + DestinationMMO != Fixture.LoadMMOs[Index] && DestinationMMO->description() == ExpectedMMO && + Destination.ownsMemOperand(*DestinationMMO) && !Source->ownsMemOperand(*DestinationMMO); + } + Context.expect(MMOsMatch, "clone must deep-copy MMO ownership while remapping every table base"); const rcc::StackObject *DestinationStackSlot = FramePosition == Clone->FrameIndices.end() diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp index ba5e3db..2b5c97d 100644 --- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp +++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp @@ -5,8 +5,10 @@ #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineMemOperand.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -28,6 +30,8 @@ constexpr std::uint32_t LoadOpcode = 3; constexpr std::uint32_t NopOpcode = 4; constexpr std::uint32_t BranchOpcode = 5; constexpr std::uint32_t DynamicImplicitOpcode = 6; +constexpr std::uint32_t ConstantPoolOpcode = 7; +constexpr std::uint32_t JumpTableOpcode = 8; class TestContext final { public: @@ -103,6 +107,16 @@ private: auto Dynamic = instructionDescriptor(DynamicImplicitOpcode, "TEST_DYNAMIC_IMPLICIT"); Dynamic.AllowExtraImplicitOperands = true; + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + std::vector Descriptors; Descriptors.push_back(std::move(Copy)); Descriptors.push_back(std::move(Add)); @@ -110,6 +124,8 @@ private: Descriptors.push_back(std::move(Nop)); Descriptors.push_back(std::move(Branch)); Descriptors.push_back(std::move(Dynamic)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); return rcc::InstructionSet::create(std::move(Descriptors)); } @@ -375,6 +391,53 @@ void testImplicitAndMemoryValidation(TestContext &Context) { if (!LoadMemory || !StoreMemory || !ForeignMemory) return; + const std::size_t BeforeUnknownTableReferences = Function->allInstructions().size(); + auto UnknownConstant = Builder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + auto UnknownJumpTable = Builder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect( + UnknownConstant && + UnknownConstant->addConstantPool(rcc::ConstantPoolIndex::fromValue(99)).has_value() && + !UnknownConstant->build() && UnknownJumpTable && + UnknownJumpTable->addJumpTable(rcc::JumpTableIndex::fromValue(99)).has_value() && + !UnknownJumpTable->build() && + Function->allInstructions().size() == BeforeUnknownTableReferences, + "builder must reject unknown constant-pool and jump-table indices before allocation"); + + rcc::MachineConstantPoolEntryDesc ConstantDescription; + ConstantDescription.Value = + rcc::GenericConstantData{"i64", std::vector(8, std::byte{0x2a})}; + ConstantDescription.Size = 8; + ConstantDescription.Align = *rcc::Alignment::create(8); + auto ConstantIndex = Function->constantPool().addEntry(std::move(ConstantDescription)); + rcc::MachineJumpTableDesc JumpDescription; + JumpDescription.Targets = {Block}; + auto JumpIndex = Function->jumpTableInfo().createTable(std::move(JumpDescription)); + Context.expect(ConstantIndex && JumpIndex, "builder table references must have owned entries"); + if (!ConstantIndex || !JumpIndex) + return; + + auto ConstantInstruction = Builder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + auto JumpInstruction = Builder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect( + ConstantInstruction && ConstantInstruction->addConstantPool(*ConstantIndex, 4).has_value() && + ConstantInstruction->build() && JumpInstruction && + JumpInstruction->addJumpTable(*JumpIndex).has_value() && JumpInstruction->build(), + "builder must publish valid owned table references"); + + auto ConstantMemoryDescription = memoryDescription(rcc::MemoryAccessFlag::Load); + ConstantMemoryDescription.Base = rcc::MemoryBase::constantPool(*ConstantIndex); + auto JumpMemoryDescription = memoryDescription(rcc::MemoryAccessFlag::Load); + JumpMemoryDescription.Base = rcc::MemoryBase::jumpTable(*JumpIndex); + auto ConstantMemory = Function->createMemOperand(std::move(ConstantMemoryDescription)); + auto JumpMemory = Function->createMemOperand(std::move(JumpMemoryDescription)); + auto ConstantLoad = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + auto JumpLoad = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + Context.expect(ConstantMemory && JumpMemory && ConstantLoad && + ConstantLoad->addMemOperand(**ConstantMemory).has_value() && + ConstantLoad->build() && JumpLoad && + JumpLoad->addMemOperand(**JumpMemory).has_value() && JumpLoad->build(), + "builder must validate constant-pool and jump-table MMO bases"); + auto Load = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); Context.expect(Load && Load->addMemOperand(**LoadMemory).has_value(), "function-owned load MMO must be accepted"); diff --git a/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp new file mode 100644 index 0000000..0273801 --- /dev/null +++ b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineJumpTableInfo.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" + +#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; +}; + +[[nodiscard]] std::unique_ptr +createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { + auto Function = + rcc::MachineFunction::create(std::move(Name), rcc::MachineFunctionStableID::fromValue(ID)); + Context.expect(Function.has_value(), "machine function creation must succeed"); + if (!Function) + return nullptr; + return std::move(*Function); +} + +[[nodiscard]] rcc::MachineBasicBlock *createBlock(TestContext &Context, + rcc::MachineFunction &Function) { + auto Block = Function.createBasicBlock(); + Context.expect(Block.has_value() && Function.appendBlock(**Block).has_value(), + "jump table test block creation must succeed"); + return Block ? *Block : nullptr; +} + +[[nodiscard]] rcc::MachineJumpTableDesc tableDescription(rcc::MachineBasicBlock &First, + rcc::MachineBasicBlock &Second) { + rcc::MachineJumpTableDesc Description; + Description.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase; + Description.EntrySize = 4; + Description.Align = *rcc::Alignment::create(4); + Description.Targets = {&First, &Second, &Second}; + Description.Placement.MaximumDistance = 4096; + return Description; +} + +void testTableLifecycle(TestContext &Context) { + auto Function = createFunction(Context, "jump_tables", 1); + if (!Function) + return; + rcc::MachineBasicBlock *Entry = createBlock(Context, *Function); + rcc::MachineBasicBlock *Exit = createBlock(Context, *Function); + if (Entry == nullptr || Exit == nullptr) + return; + + auto First = Function->jumpTableInfo().createTable(tableDescription(*Entry, *Exit)); + auto TargetDescription = tableDescription(*Exit, *Entry); + TargetDescription.Encoding = rcc::JumpTableEntryEncoding::Target; + TargetDescription.TargetEncoding = 19; + TargetDescription.Placement.TargetFlags = 7; + auto Target = Function->jumpTableInfo().createTable(std::move(TargetDescription)); + Context.expect(First && Target && First->value() == 0 && Target->value() == 1 && + Function->jumpTableInfo().liveTableCount() == 2, + "valid jump tables must receive monotonic indices"); + if (!First || !Target) + return; + + const rcc::MachineJumpTableEntry *FirstEntry = Function->jumpTableInfo().table(*First); + Context.expect(FirstEntry != nullptr && FirstEntry->description()->Targets.size() == 3 && + FirstEntry->description()->Targets[1] == Exit && + FirstEntry->description()->Targets[2] == Exit, + "jump table target order and duplicate cases must be preserved"); + + Context.expect(Function->jumpTableInfo().eraseTable(*First).has_value() && + Function->jumpTableInfo().table(*First) == nullptr, + "erasing a jump table must leave a tombstone"); + auto CompressedDescription = tableDescription(*Entry, *Exit); + CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset; + CompressedDescription.EntrySize = 2; + auto Compressed = Function->jumpTableInfo().createTable(std::move(CompressedDescription)); + Context.expect(Compressed && Compressed->value() == 2, + "jump table tombstone indices must never be reused"); + + auto Updated = tableDescription(*Entry, *Exit); + Updated.Encoding = rcc::JumpTableEntryEncoding::RelativeToEntry; + Context.expect(Function->jumpTableInfo().updateTable(*Target, std::move(Updated)).has_value() && + Function->jumpTableInfo().verify().has_value() && + Function->jumpTableInfo().freeze().has_value() && + Function->jumpTableInfo().isFrozen(), + "valid jump table updates must verify and freeze"); + Context.expect( + !Function->jumpTableInfo().eraseTable(*Target) && + !Function->jumpTableInfo().updateTable(*Target, tableDescription(*Entry, *Exit)) && + !Function->jumpTableInfo().createTable(tableDescription(*Entry, *Exit)) && + !Function->jumpTableInfo().freeze(), + "frozen jump table info must reject every mutation"); +} + +void testInvalidTables(TestContext &Context) { + auto Function = createFunction(Context, "invalid_tables", 2); + auto Foreign = createFunction(Context, "foreign_tables", 3); + if (!Function || !Foreign) + return; + rcc::MachineBasicBlock *Block = createBlock(Context, *Function); + rcc::MachineBasicBlock *ForeignBlock = createBlock(Context, *Foreign); + if (Block == nullptr || ForeignBlock == nullptr) + return; + + rcc::MachineJumpTableDesc Empty; + Context.expect(!Function->jumpTableInfo().createTable(std::move(Empty)), + "jump table must reject an empty target list"); + + auto ForeignTarget = tableDescription(*Block, *ForeignBlock); + Context.expect(!Function->jumpTableInfo().createTable(std::move(ForeignTarget)), + "jump table must reject a foreign target block"); + + auto InvalidSize = tableDescription(*Block, *Block); + InvalidSize.EntrySize = 3; + Context.expect(!Function->jumpTableInfo().createTable(std::move(InvalidSize)), + "jump table entry size must be a bounded power of two"); + + auto MissingTargetEncoding = tableDescription(*Block, *Block); + MissingTargetEncoding.Encoding = rcc::JumpTableEntryEncoding::Target; + Context.expect(!Function->jumpTableInfo().createTable(std::move(MissingTargetEncoding)), + "target jump table must carry a target encoding ID"); + + auto UnexpectedTargetEncoding = tableDescription(*Block, *Block); + UnexpectedTargetEncoding.TargetEncoding = 1; + Context.expect(!Function->jumpTableInfo().createTable(std::move(UnexpectedTargetEncoding)), + "generic jump table encoding must reject target encoding IDs"); + + auto OversizedCompressed = tableDescription(*Block, *Block); + OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset; + OversizedCompressed.EntrySize = 8; + Context.expect(!Function->jumpTableInfo().createTable(std::move(OversizedCompressed)), + "compressed jump table must reject oversized entries"); + + auto ZeroDistance = tableDescription(*Block, *Block); + ZeroDistance.Placement.MaximumDistance = 0; + Context.expect(!Function->jumpTableInfo().createTable(std::move(ZeroDistance)), + "jump table maximum distance must be nonzero"); + + auto LiveTable = Function->jumpTableInfo().createTable(tableDescription(*Block, *Block)); + Context.expect(LiveTable && Function->removeBlock(*Block).has_value() && + !Function->jumpTableInfo().verify(), + "jump table verification must detect a detached target"); +} + +} // namespace + +int main() { + TestContext Context; + testTableLifecycle(Context); + testInvalidTables(Context); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp index 82ae68b..17c4eb4 100644 --- a/backend/unittests/CodeGen/MachineVerifierTest.cpp +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -6,10 +6,12 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -32,6 +34,8 @@ constexpr std::uint32_t PhiOpcode = 5; constexpr std::uint32_t FrameOpcode = 6; constexpr std::uint32_t PseudoOpcode = 7; constexpr std::uint32_t NopOpcode = 8; +constexpr std::uint32_t ConstantPoolOpcode = 9; +constexpr std::uint32_t JumpTableOpcode = 10; class TestContext final { public: @@ -114,6 +118,16 @@ private: auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP"); + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + std::vector Descriptors; Descriptors.push_back(std::move(GenericDef)); Descriptors.push_back(std::move(GenericUse)); @@ -123,6 +137,8 @@ private: Descriptors.push_back(std::move(Frame)); Descriptors.push_back(std::move(Pseudo)); Descriptors.push_back(std::move(Nop)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); return rcc::InstructionSet::create(std::move(Descriptors)); } @@ -493,6 +509,53 @@ void testFrameStateFailures(TestContext &Context) { "verifier must reject stack lifetimes that reference unknown instructions"); } +void testTableReferenceFailures(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto ConstantFunction = createFunction(Context, "constant_reference", 16); + rcc::MachineBasicBlock *ConstantBlock = + ConstantFunction ? createBlock(Context, *ConstantFunction) : nullptr; + if (!InstructionSet || !ConstantFunction || ConstantBlock == nullptr) + return; + + rcc::MachineConstantPoolEntryDesc ConstantDescription; + ConstantDescription.Value = + rcc::GenericConstantData{"i32", std::vector(4, std::byte{1})}; + ConstantDescription.Size = 4; + ConstantDescription.Align = *rcc::Alignment::create(4); + auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription)); + rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *InstructionSet); + Context.expect(ConstantIndex && ConstantBuilder.setInsertPoint(*ConstantBlock).has_value(), + "constant-pool verifier setup must succeed"); + if (!ConstantIndex) + return; + auto ConstantInstruction = ConstantBuilder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + Context.expect(ConstantInstruction && + ConstantInstruction->addConstantPool(*ConstantIndex).has_value() && + ConstantInstruction->build() && + ConstantFunction->constantPool().eraseEntry(*ConstantIndex).has_value() && + !rcc::MachineVerifier::verify(*ConstantFunction, *InstructionSet), + "verifier must reject a tombstoned constant-pool reference"); + + auto JumpFunction = createFunction(Context, "jump_reference", 17); + rcc::MachineBasicBlock *JumpBlock = JumpFunction ? createBlock(Context, *JumpFunction) : nullptr; + if (!JumpFunction || JumpBlock == nullptr) + return; + rcc::MachineJumpTableDesc JumpDescription; + JumpDescription.Targets = {JumpBlock}; + auto JumpIndex = JumpFunction->jumpTableInfo().createTable(std::move(JumpDescription)); + rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *InstructionSet); + Context.expect(JumpIndex && JumpBuilder.setInsertPoint(*JumpBlock).has_value(), + "jump-table verifier setup must succeed"); + if (!JumpIndex) + return; + auto JumpInstruction = JumpBuilder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect(JumpInstruction && JumpInstruction->addJumpTable(*JumpIndex).has_value() && + JumpInstruction->build() && + JumpFunction->jumpTableInfo().eraseTable(*JumpIndex).has_value() && + !rcc::MachineVerifier::verify(*JumpFunction, *InstructionSet), + "verifier must reject a tombstoned jump-table reference"); +} + } // namespace int main() { @@ -502,5 +565,6 @@ int main() { testPropertyFailures(Context); testSelectedStateAndPseudoDeadline(Context); testFrameStateFailures(Context); + testTableReferenceFailures(Context); return Context.result(); } 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