aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests
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 /backend/unittests
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.
Diffstat (limited to 'backend/unittests')
-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
7 files changed, 1043 insertions, 26 deletions
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();
+}