diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 14:19:20 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 14:19:20 +0800 |
| commit | a69d14b8ebc028dddc689658b08d42d3e83f5e38 (patch) | |
| tree | 5bdbb9dbe88de07eeba5caba97a2e54e6b05ad38 /backend | |
| parent | c9634fa9f529afbdf622a9cd96741bd09d14ef86 (diff) | |
| download | rcc-project-a69d14b8ebc028dddc689658b08d42d3e83f5e38.tar.gz rcc-project-a69d14b8ebc028dddc689658b08d42d3e83f5e38.tar.xz | |
[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.
Diffstat (limited to 'backend')
21 files changed, 2427 insertions, 42 deletions
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 <cstddef> +#include <cstdint> +#include <optional> +#include <span> +#include <string> +#include <utility> +#include <variant> +#include <vector> + +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<std::uint64_t> MaximumDistance; + std::uint64_t TargetFlags = 0; + + friend bool operator==(const ConstantPoolPlacement &Left, + const ConstantPoolPlacement &Right) = default; +}; + +struct GenericConstantData final { + std::string TypeIdentity; + std::vector<std::byte> 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<GenericConstantData, TargetConstant, SymbolDifferenceConstant, + BlockAddressConstant, TargetExpressionConstant>; + +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<void> verify() const; + [[nodiscard]] Expected<MachineConstantPoolEntryDesc> 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<std::monostate>(Description); + } + + [[nodiscard]] const MachineConstantPoolEntryDesc *description() const noexcept { + return std::get_if<MachineConstantPoolEntryDesc>(&Description); + } + + [[nodiscard]] Expected<MachineConstantPoolEntryDesc> cloneDescription() const; + +private: + MachineConstantPoolEntry(ConstantPoolIndex Index, MachineConstantPoolEntryDesc Description) + : Index(Index), Description(std::move(Description)) {} + + ConstantPoolIndex Index; + std::variant<std::monostate, MachineConstantPoolEntryDesc> 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<ConstantPoolIndex> addEntry(MachineConstantPoolEntryDesc Description); + [[nodiscard]] Expected<void> updateEntry(ConstantPoolIndex Index, + MachineConstantPoolEntryDesc Description); + [[nodiscard]] Expected<void> eraseEntry(ConstantPoolIndex Index); + + [[nodiscard]] const MachineConstantPoolEntry *entry(ConstantPoolIndex Index) const noexcept; + [[nodiscard]] std::span<const MachineConstantPoolEntry> entries() const noexcept { + return Entries; + } + + [[nodiscard]] std::size_t liveEntryCount() const noexcept; + + [[nodiscard]] bool isFrozen() const noexcept { + return Frozen; + } + + [[nodiscard]] Expected<void> freeze(); + [[nodiscard]] Expected<void> verify() const; + +private: + [[nodiscard]] Expected<MachineConstantPoolEntry *> mutableEntry(ConstantPoolIndex Index); + [[nodiscard]] const MachineConstantPoolEntry * + findEquivalent(const MachineConstantPoolEntryDesc &Description, + std::optional<ConstantPoolIndex> Excluded = std::nullopt) const noexcept; + + std::vector<MachineConstantPoolEntry> 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<void> ensurePending() const; [[nodiscard]] Expected<void> validateInsertionPoint() const; [[nodiscard]] Expected<void> validateFrameReference(FrameIndex Index) const; + [[nodiscard]] Expected<void> validateConstantPoolReference(ConstantPoolIndex Index) const; + [[nodiscard]] Expected<void> validateJumpTableReference(JumpTableIndex Index) const; [[nodiscard]] Expected<void> validateOperandOwnership(const MachineOperand &Operand) const; [[nodiscard]] std::vector<MachineOperandTie> collectOperandTies() const; [[nodiscard]] Expected<void> 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<detail::AssemblyFormatIDTag>; using SyncScopeID = MachineIndex<detail::SyncScopeIDTag>; using MemoryAliasSourceID = MachineIndex<detail::MemoryAliasSourceIDTag, std::uint64_t>; using OriginID = MachineIndex<detail::OriginIDTag, std::uint64_t>; +using TargetConstantTypeID = MachineIndex<detail::TargetConstantTypeIDTag, std::uint64_t>; +using TargetFunctionStateTypeID = MachineIndex<detail::TargetFunctionStateTypeIDTag, std::uint64_t>; +using TargetRelocationKindID = MachineIndex<detail::TargetRelocationKindIDTag>; 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 <cstddef> +#include <cstdint> +#include <optional> +#include <span> +#include <utility> +#include <variant> +#include <vector> + +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<std::uint64_t> 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<MachineBasicBlock *> 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<std::monostate>(Description); + } + + [[nodiscard]] const MachineJumpTableDesc *description() const noexcept { + return std::get_if<MachineJumpTableDesc>(&Description); + } + +private: + MachineJumpTableEntry(JumpTableIndex Index, MachineJumpTableDesc Description) + : Index(Index), Description(std::move(Description)) {} + + JumpTableIndex Index; + std::variant<std::monostate, MachineJumpTableDesc> 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<JumpTableIndex> createTable(MachineJumpTableDesc Description); + [[nodiscard]] Expected<void> updateTable(JumpTableIndex Index, MachineJumpTableDesc Description); + [[nodiscard]] Expected<void> eraseTable(JumpTableIndex Index); + + [[nodiscard]] const MachineJumpTableEntry *table(JumpTableIndex Index) const noexcept; + [[nodiscard]] std::span<const MachineJumpTableEntry> tables() const noexcept { + return Tables; + } + + [[nodiscard]] std::size_t liveTableCount() const noexcept; + + [[nodiscard]] bool isFrozen() const noexcept { + return Frozen; + } + + [[nodiscard]] Expected<void> freeze(); + [[nodiscard]] Expected<void> verify() const; + +private: + [[nodiscard]] Expected<void> verifyDescription(const MachineJumpTableDesc &Description) const; + [[nodiscard]] Expected<MachineJumpTableEntry *> mutableTable(JumpTableIndex Index); + + MachineFunction *Owner; + std::vector<MachineJumpTableEntry> 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 <concepts> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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<std::byte> Bytes; + std::vector<TargetConstantFixup> Fixups; + + [[nodiscard]] Expected<void> verify() const; + + friend bool operator==(const TargetConstantEmission &Left, + const TargetConstantEmission &Right) = default; +}; + +struct EncodedTargetConstant final { + TargetConstantTypeID TypeID; + std::uint32_t Version = 0; + std::vector<std::byte> Payload; + + friend bool operator==(const EncodedTargetConstant &Left, + const EncodedTargetConstant &Right) = default; +}; + +template <typename ValueT> +concept TargetConstantValue = + std::move_constructible<ValueT> && + requires(const ValueT &Value, const ValueT &Other, std::uint32_t Version, + std::span<const std::byte> Payload, std::string_view Text) { + { ValueT::stableTypeID() } -> std::same_as<TargetConstantTypeID>; + { ValueT::schemaVersion() } -> std::same_as<std::uint32_t>; + { Value.verify() } -> std::same_as<Expected<void>>; + { Value.clone() } -> std::same_as<Expected<ValueT>>; + { Value.stableHash() } -> std::same_as<std::uint64_t>; + { Value.print() } -> std::same_as<std::string>; + { Value.encode() } -> std::same_as<Expected<std::vector<std::byte>>>; + { Value.emit() } -> std::same_as<Expected<TargetConstantEmission>>; + { Value.requiredAlignment() } -> std::same_as<Alignment>; + { Value.relocationKind() } -> std::same_as<std::optional<TargetRelocationKindID>>; + { Value == Other } -> std::convertible_to<bool>; + { ValueT::parse(Text) } -> std::same_as<Expected<ValueT>>; + { ValueT::decode(Version, Payload) } -> std::same_as<Expected<ValueT>>; + }; + +class TargetConstant final { +public: + TargetConstant() = default; + TargetConstant(const TargetConstant &) = delete; + TargetConstant &operator=(const TargetConstant &) = delete; + TargetConstant(TargetConstant &&) noexcept; + TargetConstant &operator=(TargetConstant &&) noexcept; + ~TargetConstant(); + + template <TargetConstantValue ValueT> + [[nodiscard]] static Expected<TargetConstant> 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<Concept> NewStorage = std::make_unique<Model<ValueT>>(std::move(Value)); + return TargetConstant(std::move(NewStorage)); + } + + template <TargetConstantValue ValueT> + [[nodiscard]] static Expected<TargetConstant> parse(std::string_view Text) { + auto Value = ValueT::parse(Text); + if (!Value) + return std::unexpected(Value.error()); + return create<ValueT>(std::move(*Value)); + } + + template <TargetConstantValue ValueT> + [[nodiscard]] static Expected<TargetConstant> 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<ValueT>(std::move(*Value)); + } + + [[nodiscard]] bool hasValue() const noexcept { + return Storage != nullptr; + } + + [[nodiscard]] std::optional<TargetConstantTypeID> typeID() const noexcept; + [[nodiscard]] std::optional<std::uint32_t> schemaVersion() const noexcept; + [[nodiscard]] Expected<void> verify() const; + [[nodiscard]] Expected<TargetConstant> clone() const; + [[nodiscard]] Expected<std::uint64_t> stableHash() const; + [[nodiscard]] Expected<std::string> print() const; + [[nodiscard]] Expected<EncodedTargetConstant> encode() const; + [[nodiscard]] Expected<TargetConstantEmission> emit() const; + [[nodiscard]] Expected<Alignment> requiredAlignment() const; + [[nodiscard]] Expected<std::optional<TargetRelocationKindID>> relocationKind() const; + + [[nodiscard]] bool equals(const TargetConstant &Other) const noexcept; + + template <TargetConstantValue ValueT> [[nodiscard]] ValueT *getIf() noexcept { + if (!hasType<ValueT>()) + return nullptr; + return &static_cast<Model<ValueT> *>(Storage.get())->value(); + } + + template <TargetConstantValue ValueT> [[nodiscard]] const ValueT *getIf() const noexcept { + if (!hasType<ValueT>()) + return nullptr; + return &static_cast<const Model<ValueT> *>(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<void> verify() const = 0; + [[nodiscard]] virtual Expected<std::unique_ptr<Concept>> cloneStorage() const = 0; + [[nodiscard]] virtual std::uint64_t stableHash() const noexcept = 0; + [[nodiscard]] virtual std::string print() const = 0; + [[nodiscard]] virtual Expected<std::vector<std::byte>> encodePayload() const = 0; + [[nodiscard]] virtual Expected<TargetConstantEmission> emit() const = 0; + [[nodiscard]] virtual Alignment requiredAlignment() const noexcept = 0; + [[nodiscard]] virtual std::optional<TargetRelocationKindID> relocationKind() const noexcept = 0; + [[nodiscard]] virtual bool equals(const Concept &Other) const noexcept = 0; + }; + + template <typename ValueT> [[nodiscard]] static const void *typeToken() noexcept { + static const std::byte Token{}; + return &Token; + } + + template <TargetConstantValue ValueT> 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<ValueT>(); + } + + [[nodiscard]] Expected<void> verify() const override { + return Value.verify(); + } + + [[nodiscard]] Expected<std::unique_ptr<Concept>> cloneStorage() const override { + auto Cloned = Value.clone(); + if (!Cloned) + return std::unexpected(Cloned.error()); + std::unique_ptr<Concept> Result = std::make_unique<Model<ValueT>>(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<std::vector<std::byte>> encodePayload() const override { + return Value.encode(); + } + + [[nodiscard]] Expected<TargetConstantEmission> emit() const override { + return Value.emit(); + } + + [[nodiscard]] Alignment requiredAlignment() const noexcept override { + return Value.requiredAlignment(); + } + + [[nodiscard]] std::optional<TargetRelocationKindID> relocationKind() const noexcept override { + return Value.relocationKind(); + } + + [[nodiscard]] bool equals(const Concept &Other) const noexcept override { + if (Other.localTypeToken() != TargetConstant::typeToken<ValueT>()) + return false; + return Value == static_cast<const Model<ValueT> &>(Other).Value; + } + + [[nodiscard]] ValueT &value() noexcept { + return Value; + } + + [[nodiscard]] const ValueT &value() const noexcept { + return Value; + } + + private: + ValueT Value; + }; + + explicit TargetConstant(std::unique_ptr<Concept> Storage) : Storage(std::move(Storage)) {} + + template <TargetConstantValue ValueT> [[nodiscard]] bool hasType() const noexcept { + return Storage != nullptr && Storage->typeID() == ValueT::stableTypeID() && + Storage->localTypeToken() == typeToken<ValueT>(); + } + + std::unique_ptr<Concept> 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 <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <utility> + +namespace rcc { +namespace { + +[[nodiscard]] const TargetConstant * +getTargetConstant(const MachineConstantPoolValue &Value) noexcept { + if (const auto *Target = std::get_if<TargetConstant>(&Value)) + return Target; + if (const auto *Expression = std::get_if<TargetExpressionConstant>(&Value)) + return &Expression->Expression; + return nullptr; +} + +[[nodiscard]] Expected<void> 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<void> 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<GenericConstantData>(&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<SymbolDifferenceConstant>(&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<BlockAddressConstant>(&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> 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<GenericConstantData>(&Value)) { + Result.Value = *Generic; + } else if (const auto *Target = std::get_if<TargetConstant>(&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<SymbolDifferenceConstant>(&Value)) { + Result.Value = *Difference; + } else if (const auto *Address = std::get_if<BlockAddressConstant>(&Value)) { + Result.Value = *Address; + } else if (const auto *Expression = std::get_if<TargetExpressionConstant>(&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<GenericConstantData>(&Value)) { + const auto *OtherGeneric = std::get_if<GenericConstantData>(&Other.Value); + return OtherGeneric != nullptr && *Generic == *OtherGeneric; + } + if (const auto *Target = std::get_if<TargetConstant>(&Value)) { + const auto *OtherTarget = std::get_if<TargetConstant>(&Other.Value); + return OtherTarget != nullptr && Target->equals(*OtherTarget); + } + if (const auto *Difference = std::get_if<SymbolDifferenceConstant>(&Value)) { + const auto *OtherDifference = std::get_if<SymbolDifferenceConstant>(&Other.Value); + return OtherDifference != nullptr && *Difference == *OtherDifference; + } + if (const auto *Address = std::get_if<BlockAddressConstant>(&Value)) { + const auto *OtherAddress = std::get_if<BlockAddressConstant>(&Other.Value); + return OtherAddress != nullptr && *Address == *OtherAddress; + } + if (const auto *Expression = std::get_if<TargetExpressionConstant>(&Value)) { + const auto *OtherExpression = std::get_if<TargetExpressionConstant>(&Other.Value); + return OtherExpression != nullptr && Expression->Expression.equals(OtherExpression->Expression); + } + return false; +} + +Expected<MachineConstantPoolEntryDesc> 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<ConstantPoolIndex> 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<ConstantPoolIndex> +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<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "constant pool index space is exhausted"); + } + + const ConstantPoolIndex Index = + ConstantPoolIndex::fromValue(static_cast<std::uint32_t>(Entries.size())); + Entries.push_back(MachineConstantPoolEntry(Index, std::move(Description))); + return Index; +} + +Expected<MachineConstantPoolEntry *> 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<void> 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<MachineConstantPoolEntryDesc>(std::move(Description)); + return {}; +} + +Expected<void> MachineConstantPool::eraseEntry(ConstantPoolIndex Index) { + auto Entry = mutableEntry(Index); + if (!Entry) + return std::unexpected(Entry.error()); + (*Entry)->Description.emplace<std::monostate>(); + 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<void> 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<void> 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<std::uint32_t>(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<MachineArena>()) {} + : Name(std::move(Name)), StableID(StableID), JumpTables(*this), + Arena(std::make_unique<MachineArena>()) {} 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<JumpTableOperand>(); 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<const MachineMemOperand *> 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<void> 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<BlockAddressConstant>(&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<void> 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<void> 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<void> cloneInstructions(CloneState &State) { for (const MachineBasicBlock *SourceBlock = State.Source.firstBlock(); SourceBlock != nullptr; SourceBlock = SourceBlock->next()) { @@ -427,9 +498,15 @@ Expected<MachineFunctionCloneResult> 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<void> MachineInstrBuilder::validateFrameReference(FrameIndex Index) con return {}; } +Expected<void> MachineInstrBuilder::validateConstantPoolReference(ConstantPoolIndex Index) const { + if (Function->constantPool().entry(Index) == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder references an unknown constant pool entry"); + } + return {}; +} + +Expected<void> MachineInstrBuilder::validateJumpTableReference(JumpTableIndex Index) const { + if (Function->jumpTableInfo().table(Index) == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, "builder references an unknown jump table"); + } + return {}; +} + Expected<void> MachineInstrBuilder::validateOperandOwnership(const MachineOperand &Operand) const { if (Operand.parent() != nullptr) { return makeUnexpected(ErrorCode::InvalidArgument, @@ -364,6 +379,16 @@ Expected<void> MachineInstrBuilder::validateOperandOwnership(const MachineOperan if (!FrameResult) return FrameResult; } + if (const auto *Constant = Operand.getIf<ConstantPoolOperand>(); Constant != nullptr) { + auto ConstantResult = validateConstantPoolReference(Constant->Index); + if (!ConstantResult) + return ConstantResult; + } + if (const auto *JumpTable = Operand.getIf<JumpTableOperand>(); JumpTable != nullptr) { + auto JumpTableResult = validateJumpTableReference(JumpTable->Index); + if (!JumpTableResult) + return JumpTableResult; + } return {}; } @@ -405,11 +430,22 @@ Expected<MachineInstr *> 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 <bit> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <utility> + +namespace rcc { + +Expected<void> +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<JumpTableIndex> 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<std::uint32_t>::max()) + return makeUnexpected(ErrorCode::Unsupported, "jump table index space is exhausted"); + + const JumpTableIndex Index = JumpTableIndex::fromValue(static_cast<std::uint32_t>(Tables.size())); + Tables.push_back(MachineJumpTableEntry(Index, std::move(Description))); + return Index; +} + +Expected<MachineJumpTableEntry *> 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<void> 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<MachineJumpTableDesc>(std::move(Description)); + return {}; +} + +Expected<void> MachineJumpTableInfo::eraseTable(JumpTableIndex Index) { + auto Entry = mutableTable(Index); + if (!Entry) + return std::unexpected(Entry.error()); + (*Entry)->Description.emplace<std::monostate>(); + 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<void> 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<void> 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<std::uint32_t>(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<void> verifyConstantPoolIndex(ConstantPoolIndex Index) const { + if (Function.constantPool().entry(Index) == nullptr) + return fail("live instruction references an unknown constant pool entry"); + return {}; + } + + [[nodiscard]] Expected<void> verifyJumpTableIndex(JumpTableIndex Index) const { + if (Function.jumpTableInfo().table(Index) == nullptr) + return fail("live instruction references an unknown jump table"); + return {}; + } + [[nodiscard]] Expected<void> 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<ConstantPoolOperand>(); Constant != nullptr) { + auto ConstantResult = verifyConstantPoolIndex(Constant->Index); + if (!ConstantResult) + return ConstantResult; + } + if (const auto *JumpTable = Operand.getIf<JumpTableOperand>(); JumpTable != nullptr) { + auto JumpTableResult = verifyJumpTableIndex(JumpTable->Index); + if (!JumpTableResult) + return JumpTableResult; + } if (const auto *Block = Operand.getIf<BasicBlockOperand>(); 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 <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <unordered_set> +#include <utility> + +namespace rcc { + +Expected<void> TargetConstantEmission::verify() const { + if (Bytes.empty()) + return makeUnexpected(ErrorCode::VerificationError, + "target constant emission must contain at least one byte"); + + std::unordered_set<std::uint64_t> 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<TargetConstantTypeID> TargetConstant::typeID() const noexcept { + if (Storage == nullptr) + return std::nullopt; + return Storage->typeID(); +} + +std::optional<std::uint32_t> TargetConstant::schemaVersion() const noexcept { + if (Storage == nullptr) + return std::nullopt; + return Storage->schemaVersion(); +} + +Expected<void> 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> 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<std::uint64_t> TargetConstant::stableHash() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->stableHash(); +} + +Expected<std::string> TargetConstant::print() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->print(); +} + +Expected<EncodedTargetConstant> 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<TargetConstantEmission> 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<Alignment> TargetConstant::requiredAlignment() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->requiredAlignment(); +} + +Expected<std::optional<TargetRelocationKindID>> TargetConstant::relocationKind() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + const std::optional<TargetRelocationKindID> 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 <bit> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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<void> verify() const { + if (Value == 0) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "pool target constant value must be nonzero"); + } + return {}; + } + + [[nodiscard]] rcc::Expected<PoolTargetConstant> 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<unsigned char>(Character); + return Hash; + } + + [[nodiscard]] std::string print() const { + return Symbol.empty() ? std::to_string(Value) : Symbol + "+" + std::to_string(Value); + } + + [[nodiscard]] std::vector<std::byte> bytes() const { + std::vector<std::byte> Bytes(8); + for (std::size_t Index = 0; Index < Bytes.size(); ++Index) + Bytes[Index] = static_cast<std::byte>((Value >> (Index * 8U)) & 0xffU); + return Bytes; + } + + [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const { + std::vector<std::byte> Payload = bytes(); + for (const char Character : Symbol) + Payload.push_back(static_cast<std::byte>(static_cast<unsigned char>(Character))); + return Payload; + } + + [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const { + rcc::TargetConstantEmission Emission; + Emission.Bytes = bytes(); + if (!Symbol.empty()) { + Emission.Fixups.push_back({0, rcc::TargetRelocationKindID::fromValue(3), Symbol, + std::bit_cast<std::int64_t>(Value)}); + } + return Emission; + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return *rcc::Alignment::create(8); + } + + [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const noexcept { + if (Symbol.empty()) + return std::nullopt; + return rcc::TargetRelocationKindID::fromValue(3); + } + + [[nodiscard]] static rcc::Expected<PoolTargetConstant> 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<PoolTargetConstant> + decode(std::uint32_t Version, std::span<const std::byte> 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<std::uint64_t>(Payload[Index]) << (Index * 8U); + std::string Symbol; + for (const std::byte Character : Payload.subspan(8)) + Symbol.push_back(static_cast<char>(std::to_integer<unsigned char>(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::MachineConstantPoolEntryDesc>(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 <algorithm> +#include <cstddef> #include <cstdint> #include <iostream> #include <memory> @@ -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<rcc::InstructionDescriptor> 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<const rcc::MachineMemOperand *> 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<std::byte>(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<rcc::FrameIndexOperand>()->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<rcc::ConstantPoolOperand>()->Index == + ConstantPosition->second && + DestinationJumpTable->operandAt(0)->getIf<rcc::JumpTableOperand>()->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<rcc::BlockAddressConstant>(&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<rcc::BlockAddressOperand>()->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<rcc::InstructionDescriptor> 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<std::byte>(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 <cstdint> +#include <iostream> +#include <memory> +#include <string> +#include <string_view> + +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<rcc::MachineFunction> +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<rcc::InstructionDescriptor> 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<std::byte>(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 <bit> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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<void> verify() const { + if (Symbol.empty()) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "test target constant symbol cannot be empty"); + } + return {}; + } + + [[nodiscard]] rcc::Expected<TestRelocConstant> 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<unsigned char>(RawCharacter); + Hash ^= Character; + Hash *= 1099511628211ULL; + } + Hash ^= static_cast<std::uint64_t>(Addend); + return Hash * 1099511628211ULL; + } + + [[nodiscard]] std::string print() const { + return Symbol + "+" + std::to_string(Addend); + } + + [[nodiscard]] std::vector<std::byte> addendBytes() const { + std::vector<std::byte> Bytes(8); + const std::uint64_t Bits = static_cast<std::uint64_t>(Addend); + for (std::size_t Index = 0; Index < Bytes.size(); ++Index) + Bytes[Index] = static_cast<std::byte>((Bits >> (Index * 8U)) & 0xffU); + return Bytes; + } + + [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const { + std::vector<std::byte> Payload = addendBytes(); + for (const char RawCharacter : Symbol) { + const auto Character = static_cast<unsigned char>(RawCharacter); + Payload.push_back(static_cast<std::byte>(Character)); + } + return Payload; + } + + [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> 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<rcc::TargetRelocationKindID> relocationKind() const noexcept { + return rcc::TargetRelocationKindID::fromValue(9); + } + + [[nodiscard]] static rcc::Expected<TestRelocConstant> 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<TestRelocConstant> decode(std::uint32_t Version, + std::span<const std::byte> 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<std::uint64_t>(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<char>(std::to_integer<unsigned char>(Character))); + return TestRelocConstant{std::bit_cast<std::int64_t>(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<void> verify() const { + return Value.verify(); + } + + [[nodiscard]] rcc::Expected<CollidingConstant> 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<std::vector<std::byte>> encode() const { + return Value.encode(); + } + + [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const { + return Value.emit(); + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return Value.requiredAlignment(); + } + + [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const noexcept { + return Value.relocationKind(); + } + + [[nodiscard]] static rcc::Expected<CollidingConstant> 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<CollidingConstant> decode(std::uint32_t Version, + std::span<const std::byte> 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<TestRelocConstant>() != nullptr && + Constant->getIf<CollidingConstant>() == 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<TestRelocConstant>() != Constant->getIf<TestRelocConstant>(), + "target constant clone must own an equal independent value"); + + auto Encoded = Constant->encode(); + if (!Encoded) + return; + auto Decoded = rcc::TargetConstant::decode<TestRelocConstant>(*Encoded); + Context.expect(Encoded && Decoded && Constant->equals(*Decoded), + "versioned target constant encoding must round-trip losslessly"); + + auto Parsed = rcc::TargetConstant::parse<TestRelocConstant>("parsed"); + const TestRelocConstant *ParsedValue = Parsed ? Parsed->getIf<TestRelocConstant>() : 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<TestRelocConstant>(*Encoded), + "target constant decode must reject unsupported versions"); + Encoded->Version = TestRelocConstant::schemaVersion(); + Encoded->TypeID = rcc::TargetConstantTypeID::fromValue(1); + Context.expect(!rcc::TargetConstant::decode<TestRelocConstant>(*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(); +} |
