aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-01 14:19:20 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-01 14:19:20 +0800
commita69d14b8ebc028dddc689658b08d42d3e83f5e38 (patch)
tree5bdbb9dbe88de07eeba5caba97a2e54e6b05ad38
parentc9634fa9f529afbdf622a9cd96741bd09d14ef86 (diff)
downloadrcc-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.
-rw-r--r--backend/include/rcc/CodeGen/MachineConstantPool.h183
-rw-r--r--backend/include/rcc/CodeGen/MachineFunction.h20
-rw-r--r--backend/include/rcc/CodeGen/MachineIRBuilder.h2
-rw-r--r--backend/include/rcc/CodeGen/MachineIRTypes.h6
-rw-r--r--backend/include/rcc/CodeGen/MachineJumpTableInfo.h116
-rw-r--r--backend/include/rcc/CodeGen/TargetConstant.h259
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt12
-rw-r--r--backend/lib/CodeGen/MachineConstantPool.cpp329
-rw-r--r--backend/lib/CodeGen/MachineFunction.cpp3
-rw-r--r--backend/lib/CodeGen/MachineFunctionCloner.cpp103
-rw-r--r--backend/lib/CodeGen/MachineIRBuilder.cpp38
-rw-r--r--backend/lib/CodeGen/MachineJumpTableInfo.cpp142
-rw-r--r--backend/lib/CodeGen/MachineVerifier.cpp41
-rw-r--r--backend/lib/CodeGen/TargetConstant.cpp146
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt36
-rw-r--r--backend/unittests/CodeGen/MachineConstantPoolTest.cpp260
-rw-r--r--backend/unittests/CodeGen/MachineFunctionClonerTest.cpp197
-rw-r--r--backend/unittests/CodeGen/MachineIRBuilderTest.cpp63
-rw-r--r--backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp171
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp64
-rw-r--r--backend/unittests/CodeGen/TargetConstantTest.cpp278
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();
+}