aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/CodeGen
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-01 14:50:04 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-01 14:50:04 +0800
commitcfdbcc469f9691bd9dce3b0b86982a63d86ca6b7 (patch)
tree19f4ad856c3e4900a0501ab2cad41981b49569b5 /backend/unittests/CodeGen
parenta69d14b8ebc028dddc689658b08d42d3e83f5e38 (diff)
downloadrcc-project-cfdbcc469f9691bd9dce3b0b86982a63d86ca6b7.tar.gz
rcc-project-cfdbcc469f9691bd9dce3b0b86982a63d86ca6b7.tar.xz
[CodeGen] Add target state and compilation status
Diffstat (limited to 'backend/unittests/CodeGen')
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt30
-rw-r--r--backend/unittests/CodeGen/MachineCompilationStatusTest.cpp164
-rw-r--r--backend/unittests/CodeGen/MachineFunctionClonerTest.cpp177
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp44
-rw-r--r--backend/unittests/CodeGen/TargetFunctionStateTest.cpp251
5 files changed, 666 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt
index eb4dbfe..4a0b9fd 100644
--- a/backend/unittests/CodeGen/CMakeLists.txt
+++ b/backend/unittests/CodeGen/CMakeLists.txt
@@ -4,6 +4,7 @@ set(
RCCBackendCodeGenTestSources
CoreTypesTest.cpp
InstructionDescriptorTest.cpp
+ MachineCompilationStatusTest.cpp
MachineConstantPoolTest.cpp
MachineFrameInfoTest.cpp
MachineFunctionClonerTest.cpp
@@ -13,6 +14,7 @@ set(
MachineVerifierTest.cpp
MachineValueTypesTest.cpp
TargetConstantTest.cpp
+ TargetFunctionStateTest.cpp
)
add_executable(RCCBackendCodeGenCoreTypesTests CoreTypesTest.cpp)
@@ -36,6 +38,21 @@ add_test(
COMMAND RCCBackendCodeGenInstructionDescriptorTests
)
+add_executable(
+ RCCBackendCodeGenMachineCompilationStatusTests
+ MachineCompilationStatusTest.cpp
+)
+rcc_backend_configure_target(RCCBackendCodeGenMachineCompilationStatusTests)
+target_link_libraries(
+ RCCBackendCodeGenMachineCompilationStatusTests
+ PRIVATE RCCBackend::CodeGen
+)
+
+add_test(
+ NAME RCCBackend.CodeGen.MachineCompilationStatus
+ COMMAND RCCBackendCodeGenMachineCompilationStatusTests
+)
+
add_executable(RCCBackendCodeGenMachineConstantPoolTests MachineConstantPoolTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenMachineConstantPoolTests)
target_link_libraries(RCCBackendCodeGenMachineConstantPoolTests PRIVATE RCCBackend::CodeGen)
@@ -129,9 +146,19 @@ add_test(
COMMAND RCCBackendCodeGenTargetConstantTests
)
+add_executable(RCCBackendCodeGenTargetFunctionStateTests TargetFunctionStateTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenTargetFunctionStateTests)
+target_link_libraries(RCCBackendCodeGenTargetFunctionStateTests PRIVATE RCCBackend::CodeGen)
+
+add_test(
+ NAME RCCBackend.CodeGen.TargetFunctionState
+ COMMAND RCCBackendCodeGenTargetFunctionStateTests
+)
+
rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp"
@@ -141,10 +168,12 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp"
)
rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp"
@@ -154,4 +183,5 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp"
)
diff --git a/backend/unittests/CodeGen/MachineCompilationStatusTest.cpp b/backend/unittests/CodeGen/MachineCompilationStatusTest.cpp
new file mode 100644
index 0000000..84cada0
--- /dev/null
+++ b/backend/unittests/CodeGen/MachineCompilationStatusTest.cpp
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineCompilationStatus.h"
+
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <string>
+#include <string_view>
+#include <utility>
+
+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]] rcc::MachineCompilationFailure
+makeFailure(std::string Message = "selection did not match") {
+ rcc::MachineCompilationFailure Failure;
+ Failure.Diagnostic.Severity = rcc::MachineCompilationDiagnosticSeverity::Error;
+ Failure.Diagnostic.Code = rcc::ErrorCode::Unsupported;
+ Failure.Diagnostic.Message = std::move(Message);
+ Failure.Diagnostic.Location = rcc::SourceLocation{"input.mir", 7, 3, 0};
+ Failure.FailurePass = "instruction-select";
+ Failure.FailureInstruction = rcc::MachineInstrStableID::fromValue(17);
+ Failure.FailureStage = rcc::PipelineStage::Selected;
+ Failure.FailureRule = "rv64-test-pattern";
+ Failure.Target = "riscv64-unknown-linux-gnu";
+ Failure.Subtarget = "rv64gc";
+ return Failure;
+}
+
+void testStateNames(TestContext &Context) {
+ constexpr std::array States{
+ rcc::MachineCompilationState::Success,
+ rcc::MachineCompilationState::Unsupported,
+ rcc::MachineCompilationState::MIRParseFailed,
+ rcc::MachineCompilationState::LegalizationFailed,
+ rcc::MachineCompilationState::SelectionFailed,
+ rcc::MachineCompilationState::RegisterAllocationFailed,
+ rcc::MachineCompilationState::CallLoweringFailed,
+ rcc::MachineCompilationState::FrameLayoutFailed,
+ rcc::MachineCompilationState::FinalizationFailed,
+ rcc::MachineCompilationState::AssemblyLoweringFailed,
+ rcc::MachineCompilationState::InternalError,
+ };
+ for (const rcc::MachineCompilationState State : States) {
+ Context.expect(rcc::isKnownMachineCompilationState(State) &&
+ rcc::getMachineCompilationStateName(State) != "Unknown",
+ "every public compilation state must have a stable name");
+ }
+ Context.expect(!rcc::isKnownMachineCompilationState(rcc::MachineCompilationState::Count) &&
+ rcc::getMachineCompilationStateName(rcc::MachineCompilationState::Count) ==
+ "Unknown",
+ "compilation state sentinel must not be accepted as a public state");
+}
+
+void testSuccessfulAndFailedStatus(TestContext &Context) {
+ rcc::MachineCompilationStatus Status;
+ Context.expect(Status.succeeded() && Status.state() == rcc::MachineCompilationState::Success &&
+ Status.failure() == nullptr && Status.verify().has_value(),
+ "default compilation status must represent verified success");
+
+ Context.expect(
+ Status.fail(rcc::MachineCompilationState::SelectionFailed, makeFailure()).has_value(),
+ "valid structured failure must atomically replace success");
+ const rcc::MachineCompilationFailure *Failure = Status.failure();
+ Context.expect(!Status.succeeded() &&
+ Status.state() == rcc::MachineCompilationState::SelectionFailed &&
+ Failure != nullptr &&
+ Failure->FailureInstruction == rcc::MachineInstrStableID::fromValue(17) &&
+ Failure->FailureStage == rcc::PipelineStage::Selected &&
+ Failure->Target == "riscv64-unknown-linux-gnu" && Status.verify().has_value(),
+ "failed compilation status must preserve all structured context");
+
+ Context.expect(
+ !Status.fail(rcc::MachineCompilationState::InternalError, makeFailure("second failure")),
+ "first compilation failure must remain sticky");
+ Context.expect(Status.state() == rcc::MachineCompilationState::SelectionFailed &&
+ Status.failure() != nullptr &&
+ Status.failure()->Diagnostic.Message == "selection did not match",
+ "a rejected later failure must not mutate the original status");
+}
+
+void testInvalidFailures(TestContext &Context) {
+ rcc::MachineCompilationStatus SuccessState;
+ Context.expect(!SuccessState.fail(rcc::MachineCompilationState::Success, makeFailure()),
+ "failure API must reject the success state");
+
+ rcc::MachineCompilationStatus UnknownState;
+ Context.expect(!UnknownState.fail(rcc::MachineCompilationState::Count, makeFailure()),
+ "failure API must reject an unknown state");
+
+ rcc::MachineCompilationStatus EmptyDiagnostic;
+ Context.expect(
+ !EmptyDiagnostic.fail(rcc::MachineCompilationState::SelectionFailed, makeFailure({})),
+ "failure API must reject an empty diagnostic message");
+
+ auto InvalidLocationFailure = makeFailure();
+ InvalidLocationFailure.Diagnostic.Location = rcc::SourceLocation{"", 0, 0, 0};
+ rcc::MachineCompilationStatus InvalidLocation;
+ Context.expect(!InvalidLocation.fail(rcc::MachineCompilationState::SelectionFailed,
+ std::move(InvalidLocationFailure)),
+ "failure API must reject an invalid diagnostic location");
+
+ auto InvalidInstructionFailure = makeFailure();
+ InvalidInstructionFailure.FailureInstruction = rcc::MachineInstrStableID{};
+ rcc::MachineCompilationStatus InvalidInstruction;
+ Context.expect(!InvalidInstruction.fail(rcc::MachineCompilationState::SelectionFailed,
+ std::move(InvalidInstructionFailure)),
+ "failure API must reject an invalid instruction StableID");
+
+ auto InvalidStageFailure = makeFailure();
+ InvalidStageFailure.FailureStage = static_cast<rcc::PipelineStage>(255);
+ rcc::MachineCompilationStatus InvalidStage;
+ Context.expect(!InvalidStage.fail(rcc::MachineCompilationState::SelectionFailed,
+ std::move(InvalidStageFailure)),
+ "failure API must reject an unknown pipeline stage");
+
+ auto EmptyPassFailure = makeFailure();
+ EmptyPassFailure.FailurePass = std::string{};
+ rcc::MachineCompilationStatus EmptyPass;
+ Context.expect(
+ !EmptyPass.fail(rcc::MachineCompilationState::SelectionFailed, std::move(EmptyPassFailure)),
+ "failure API must reject empty optional text fields");
+
+ auto InvalidSeverityFailure = makeFailure();
+ InvalidSeverityFailure.Diagnostic.Severity = rcc::MachineCompilationDiagnosticSeverity::Count;
+ rcc::MachineCompilationStatus InvalidSeverity;
+ Context.expect(!InvalidSeverity.fail(rcc::MachineCompilationState::SelectionFailed,
+ std::move(InvalidSeverityFailure)),
+ "failure API must reject an unknown diagnostic severity");
+
+ auto InvalidCodeFailure = makeFailure();
+ InvalidCodeFailure.Diagnostic.Code = static_cast<rcc::ErrorCode>(255);
+ rcc::MachineCompilationStatus InvalidCode;
+ Context.expect(!InvalidCode.fail(rcc::MachineCompilationState::SelectionFailed,
+ std::move(InvalidCodeFailure)),
+ "failure API must reject an unknown diagnostic code");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testStateNames(Context);
+ testSuccessfulAndFailedStatus(Context);
+ testInvalidFailures(Context);
+ return Context.result();
+}
diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
index f634a57..3d766dc 100644
--- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
+++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp
@@ -6,6 +6,7 @@
#include "rcc/CodeGen/LowLevelType.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
#include "rcc/CodeGen/MachineCFGUpdater.h"
+#include "rcc/CodeGen/MachineCompilationStatus.h"
#include "rcc/CodeGen/MachineConstantPool.h"
#include "rcc/CodeGen/MachineFrameInfo.h"
#include "rcc/CodeGen/MachineFunction.h"
@@ -17,6 +18,7 @@
#include "rcc/CodeGen/MachineOperand.h"
#include "rcc/CodeGen/MachineRegisterInfo.h"
#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetFunctionState.h"
#include <algorithm>
#include <cstddef>
@@ -24,6 +26,7 @@
#include <iostream>
#include <memory>
#include <optional>
+#include <span>
#include <string>
#include <string_view>
#include <utility>
@@ -58,6 +61,105 @@ private:
int FailureCount = 0;
};
+void appendLittleEndian(std::vector<std::byte> &Bytes, std::uint64_t Value, std::size_t Width) {
+ for (std::size_t Index = 0; Index < Width; ++Index)
+ Bytes.push_back(static_cast<std::byte>((Value >> (Index * 8U)) & 0xffU));
+}
+
+[[nodiscard]] std::uint64_t readLittleEndian(std::span<const std::byte> Bytes, std::size_t Offset,
+ std::size_t Width) {
+ std::uint64_t Value = 0;
+ for (std::size_t Index = 0; Index < Width; ++Index) {
+ Value |= std::to_integer<std::uint64_t>(Bytes[Offset + Index]) << (Index * 8U);
+ }
+ return Value;
+}
+
+struct CloneTargetState final {
+ rcc::MachineBlockStableID Block;
+ rcc::MachineInstrStableID Instruction;
+ rcc::Register TrackedRegister;
+ rcc::FrameIndex Frame;
+ rcc::ConstantPoolIndex Constant;
+ rcc::JumpTableIndex JumpTable;
+
+ [[nodiscard]] static constexpr rcc::TargetFunctionStateTypeID stableTypeID() noexcept {
+ return rcc::TargetFunctionStateTypeID::fromValue(0x524343434c4f4e45ULL);
+ }
+
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return 1;
+ }
+
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ if (!Block.isValid() || !Instruction.isValid() || TrackedRegister.isNoRegister()) {
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "clone target state has an invalid identity");
+ }
+ return {};
+ }
+
+ [[nodiscard]] rcc::Expected<CloneTargetState>
+ clone(const rcc::MachineFunctionCloneContext &Context) const {
+ auto MappedBlock = Context.mapBlockStableID(Block);
+ if (!MappedBlock)
+ return std::unexpected(MappedBlock.error());
+ auto MappedInstruction = Context.mapInstructionStableID(Instruction);
+ if (!MappedInstruction)
+ return std::unexpected(MappedInstruction.error());
+ auto MappedRegister = Context.mapRegister(TrackedRegister);
+ if (!MappedRegister)
+ return std::unexpected(MappedRegister.error());
+ auto MappedFrame = Context.mapFrameIndex(Frame);
+ if (!MappedFrame)
+ return std::unexpected(MappedFrame.error());
+ auto MappedConstant = Context.mapConstantPoolIndex(Constant);
+ if (!MappedConstant)
+ return std::unexpected(MappedConstant.error());
+ auto MappedJumpTable = Context.mapJumpTableIndex(JumpTable);
+ if (!MappedJumpTable)
+ return std::unexpected(MappedJumpTable.error());
+ return CloneTargetState{*MappedBlock, *MappedInstruction, *MappedRegister,
+ *MappedFrame, *MappedConstant, *MappedJumpTable};
+ }
+
+ [[nodiscard]] std::string print() const {
+ return "clone-target-state";
+ }
+
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ std::vector<std::byte> Payload;
+ Payload.reserve(32);
+ appendLittleEndian(Payload, Block.value(), 8);
+ appendLittleEndian(Payload, Instruction.value(), 8);
+ appendLittleEndian(Payload, TrackedRegister.opaqueValue(), 4);
+ appendLittleEndian(Payload, Frame.value(), 4);
+ appendLittleEndian(Payload, Constant.value(), 4);
+ appendLittleEndian(Payload, JumpTable.value(), 4);
+ return Payload;
+ }
+
+ [[nodiscard]] static rcc::Expected<CloneTargetState> decode(std::uint32_t Version,
+ std::span<const std::byte> Payload) {
+ if (Version != schemaVersion() || Payload.size() != 32) {
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "clone target state encoding is invalid");
+ }
+ return CloneTargetState{
+ rcc::MachineBlockStableID::fromValue(readLittleEndian(Payload, 0, 8)),
+ rcc::MachineInstrStableID::fromValue(readLittleEndian(Payload, 8, 8)),
+ rcc::Register(static_cast<std::uint32_t>(readLittleEndian(Payload, 16, 4))),
+ rcc::FrameIndex::fromValue(static_cast<std::uint32_t>(readLittleEndian(Payload, 20, 4))),
+ rcc::ConstantPoolIndex::fromValue(
+ static_cast<std::uint32_t>(readLittleEndian(Payload, 24, 4))),
+ rcc::JumpTableIndex::fromValue(
+ static_cast<std::uint32_t>(readLittleEndian(Payload, 28, 4))),
+ };
+ }
+
+ friend bool operator==(const CloneTargetState &Left, const CloneTargetState &Right) = default;
+};
+
[[nodiscard]] rcc::OperandDescriptor registerDescriptor(rcc::RegisterOperandRole Role) {
rcc::OperandDescriptor Descriptor;
Descriptor.Kinds = {rcc::MachineOperand::Kind::Register};
@@ -371,6 +473,30 @@ struct SourceFixture final {
Function.frameInfo().freeze(32, *rcc::Alignment::create(16)).has_value(),
"source frame metadata must be finalized");
+ auto TargetState = rcc::TargetFunctionState::create(
+ CloneTargetState{Fixture.Entry->stableID(), LoadInstruction->stableID(), Fixture.Value,
+ Fixture.StackSlot, Fixture.Constant, Fixture.JumpTable});
+ Context.expect(TargetState.has_value(), "source target function state must be created");
+ if (!TargetState)
+ return false;
+ Context.expect(Function.setTargetState(std::move(*TargetState)).has_value(),
+ "source target function state must be installed");
+
+ rcc::MachineCompilationFailure Failure;
+ Failure.Diagnostic.Code = rcc::ErrorCode::Unsupported;
+ Failure.Diagnostic.Message = "test instruction selection failed";
+ Failure.Diagnostic.Location = rcc::SourceLocation{"source.mir", 12, 5, 0};
+ Failure.FailurePass = "test-instruction-select";
+ Failure.FailureInstruction = LoadInstruction->stableID();
+ Failure.FailureStage = rcc::PipelineStage::Selected;
+ Failure.FailureRule = "test-load-pattern";
+ Failure.Target = "test64";
+ Failure.Subtarget = "test64+feature";
+ Context.expect(Function.compilationStatus()
+ .fail(rcc::MachineCompilationState::SelectionFailed, std::move(Failure))
+ .has_value(),
+ "source structured compilation failure must be recorded");
+
Function.properties().set(rcc::MachineFunctionProperty::IsSSA);
Function.properties().set(rcc::MachineFunctionProperty::TracksLiveness);
Function.properties().set(rcc::MachineFunctionProperty::NoPHIs);
@@ -597,6 +723,41 @@ void testDeepClone(TestContext &Context) {
Context.expect(SourceDef != nullptr && InstructionID != Clone->InstructionStableIDs.end() &&
InstructionID->second == DestinationDef->stableID(),
"instruction StableID mapping must identify cloned instructions");
+
+ rcc::MachineInstr *SourceLoad = findInstruction(*Fixture.Exit, LoadOpcode);
+ const auto LoadID = SourceLoad == nullptr
+ ? Clone->InstructionStableIDs.end()
+ : Clone->InstructionStableIDs.find(SourceLoad->stableID());
+ const CloneTargetState *SourceTarget = Source->getTargetInfo<CloneTargetState>();
+ const CloneTargetState *DestinationTarget = Destination.getTargetInfo<CloneTargetState>();
+ Context.expect(SourceLoad != nullptr && LoadID != Clone->InstructionStableIDs.end() &&
+ SourceTarget != nullptr && DestinationTarget != nullptr &&
+ SourceTarget != DestinationTarget && EntryID != Clone->BlockStableIDs.end() &&
+ FramePosition != Clone->FrameIndices.end() &&
+ ConstantPosition != Clone->ConstantPoolIndices.end() &&
+ JumpTablePosition != Clone->JumpTableIndices.end() &&
+ DestinationTarget->Block == EntryID->second &&
+ DestinationTarget->Instruction == LoadID->second &&
+ DestinationTarget->TrackedRegister == NewValue &&
+ DestinationTarget->Frame == FramePosition->second &&
+ DestinationTarget->Constant == ConstantPosition->second &&
+ DestinationTarget->JumpTable == JumpTablePosition->second,
+ "clone must strongly clone target state through every identity mapping");
+
+ const rcc::MachineCompilationFailure *SourceFailure = Source->compilationStatus().failure();
+ const rcc::MachineCompilationFailure *DestinationFailure =
+ Destination.compilationStatus().failure();
+ Context.expect(SourceFailure != nullptr && DestinationFailure != nullptr &&
+ Destination.compilationStatus().state() ==
+ rcc::MachineCompilationState::SelectionFailed &&
+ DestinationFailure->FailureInstruction == DestinationLoad->stableID() &&
+ DestinationFailure->Diagnostic == SourceFailure->Diagnostic &&
+ DestinationFailure->FailurePass == SourceFailure->FailurePass &&
+ DestinationFailure->FailureStage == SourceFailure->FailureStage &&
+ DestinationFailure->FailureRule == SourceFailure->FailureRule &&
+ DestinationFailure->Target == SourceFailure->Target &&
+ DestinationFailure->Subtarget == SourceFailure->Subtarget,
+ "clone must preserve compilation failure details and remap its instruction");
}
void testCloneFailures(TestContext &Context) {
@@ -628,6 +789,22 @@ void testCloneFailures(TestContext &Context) {
rcc::MachineFunctionStableID::fromValue(5), std::nullopt}),
"clone must verify and reject an invalid source function");
}
+
+ auto UnmappedState = createFunction(Context, "unmapped_target_state", 6);
+ auto Physical = rcc::Register::makePhysical(1);
+ if (UnmappedState && Physical) {
+ auto State = rcc::TargetFunctionState::create(CloneTargetState{
+ rcc::MachineBlockStableID::fromValue(99), rcc::MachineInstrStableID::fromValue(99),
+ *Physical, rcc::FrameIndex::fromValue(0), rcc::ConstantPoolIndex::fromValue(0),
+ rcc::JumpTableIndex::fromValue(0)});
+ Context.expect(State && UnmappedState->setTargetState(std::move(*State)).has_value(),
+ "unmapped target state negative test must be installed");
+ Context.expect(
+ !rcc::cloneMachineFunction(*UnmappedState, *InstructionSet,
+ rcc::MachineFunctionCloneOptions{
+ rcc::MachineFunctionStableID::fromValue(7), std::nullopt}),
+ "clone must reject target state that does not map its identities");
+ }
}
} // namespace
diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp
index 17c4eb4..3cc79d1 100644
--- a/backend/unittests/CodeGen/MachineVerifierTest.cpp
+++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp
@@ -6,6 +6,7 @@
#include "rcc/CodeGen/LowLevelType.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
#include "rcc/CodeGen/MachineCFGUpdater.h"
+#include "rcc/CodeGen/MachineCompilationStatus.h"
#include "rcc/CodeGen/MachineConstantPool.h"
#include "rcc/CodeGen/MachineFunction.h"
#include "rcc/CodeGen/MachineFunctionProperties.h"
@@ -556,6 +557,48 @@ void testTableReferenceFailures(TestContext &Context) {
"verifier must reject a tombstoned jump-table reference");
}
+void testCompilationStatusReferences(TestContext &Context) {
+ auto InstructionSet = createInstructionSet();
+ auto Unknown = createFunction(Context, "unknown_failure_instruction", 18);
+ if (!InstructionSet || !Unknown)
+ return;
+
+ rcc::MachineCompilationFailure UnknownFailure;
+ UnknownFailure.Diagnostic.Code = rcc::ErrorCode::Unsupported;
+ UnknownFailure.Diagnostic.Message = "selection failed";
+ UnknownFailure.FailureInstruction = rcc::MachineInstrStableID::fromValue(99);
+ UnknownFailure.FailureStage = rcc::PipelineStage::Selected;
+ Context.expect(
+ Unknown->compilationStatus()
+ .fail(rcc::MachineCompilationState::SelectionFailed, std::move(UnknownFailure))
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Unknown, *InstructionSet),
+ "verifier must reject compilation status that references an unknown instruction");
+
+ auto Live = createFunction(Context, "live_failure_instruction", 19);
+ rcc::MachineBasicBlock *Block = Live ? createBlock(Context, *Live) : nullptr;
+ if (!Live || Block == nullptr)
+ return;
+ rcc::MachineIRBuilder Builder(*Live, *InstructionSet);
+ Context.expect(Builder.setInsertPoint(*Block).has_value(),
+ "live failure instruction insertion point must be valid");
+ rcc::MachineInstr *Instruction = buildNoOperand(Context, Builder, NopOpcode);
+ if (Instruction == nullptr)
+ return;
+
+ rcc::MachineCompilationFailure LiveFailure;
+ LiveFailure.Diagnostic.Code = rcc::ErrorCode::Unsupported;
+ LiveFailure.Diagnostic.Message = "finalization failed";
+ LiveFailure.FailureInstruction = Instruction->stableID();
+ LiveFailure.FailureStage = rcc::PipelineStage::PostRAFinalized;
+ Context.expect(
+ Live->compilationStatus()
+ .fail(rcc::MachineCompilationState::FinalizationFailed, std::move(LiveFailure))
+ .has_value() &&
+ rcc::MachineVerifier::verify(*Live, *InstructionSet).has_value(),
+ "verifier must accept a structurally valid failed function");
+}
+
} // namespace
int main() {
@@ -566,5 +609,6 @@ int main() {
testSelectedStateAndPseudoDeadline(Context);
testFrameStateFailures(Context);
testTableReferenceFailures(Context);
+ testCompilationStatusReferences(Context);
return Context.result();
}
diff --git a/backend/unittests/CodeGen/TargetFunctionStateTest.cpp b/backend/unittests/CodeGen/TargetFunctionStateTest.cpp
new file mode 100644
index 0000000..a306b8f
--- /dev/null
+++ b/backend/unittests/CodeGen/TargetFunctionStateTest.cpp
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/TargetFunctionState.h"
+
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionCloner.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#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 TestFunctionInfo final {
+ std::uint64_t StackProbeSize = 0;
+ bool UsesShadowCallStack = false;
+ std::string CallingConvention;
+
+ [[nodiscard]] static constexpr rcc::TargetFunctionStateTypeID stableTypeID() noexcept {
+ return rcc::TargetFunctionStateTypeID::fromValue(0x5243435446530001ULL);
+ }
+
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return 3;
+ }
+
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ if (StackProbeSize == 0 || CallingConvention.empty()) {
+ return rcc::makeUnexpected(rcc::ErrorCode::VerificationError,
+ "test function info is incomplete");
+ }
+ return {};
+ }
+
+ [[nodiscard]] rcc::Expected<TestFunctionInfo>
+ clone(const rcc::MachineFunctionCloneContext &) const {
+ return TestFunctionInfo{StackProbeSize, UsesShadowCallStack, CallingConvention};
+ }
+
+ [[nodiscard]] std::string print() const {
+ return CallingConvention + ":" + std::to_string(StackProbeSize) + ":" +
+ (UsesShadowCallStack ? "shadow" : "plain");
+ }
+
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ std::vector<std::byte> Payload;
+ Payload.reserve(9 + CallingConvention.size());
+ for (std::size_t Index = 0; Index < 8; ++Index) {
+ Payload.push_back(static_cast<std::byte>((StackProbeSize >> (Index * 8U)) & 0xffU));
+ }
+ Payload.push_back(static_cast<std::byte>(UsesShadowCallStack ? 1 : 0));
+ for (const char RawCharacter : CallingConvention) {
+ Payload.push_back(static_cast<std::byte>(static_cast<unsigned char>(RawCharacter)));
+ }
+ return Payload;
+ }
+
+ [[nodiscard]] static rcc::Expected<TestFunctionInfo> decode(std::uint32_t Version,
+ std::span<const std::byte> Payload) {
+ if (Version != schemaVersion()) {
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "unsupported test function info version");
+ }
+ if (Payload.size() <= 9 || std::to_integer<unsigned int>(Payload[8]) > 1) {
+ return rcc::makeUnexpected(rcc::ErrorCode::ParseError,
+ "test function info payload is invalid");
+ }
+
+ std::uint64_t StackProbeSize = 0;
+ for (std::size_t Index = 0; Index < 8; ++Index) {
+ StackProbeSize |= std::to_integer<std::uint64_t>(Payload[Index]) << (Index * 8U);
+ }
+ std::string CallingConvention;
+ CallingConvention.reserve(Payload.size() - 9);
+ for (const std::byte Character : Payload.subspan(9)) {
+ CallingConvention.push_back(static_cast<char>(std::to_integer<unsigned char>(Character)));
+ }
+ return TestFunctionInfo{StackProbeSize, std::to_integer<unsigned int>(Payload[8]) != 0,
+ std::move(CallingConvention)};
+ }
+
+ friend bool operator==(const TestFunctionInfo &Left, const TestFunctionInfo &Right) = default;
+};
+
+struct CollidingFunctionInfo final {
+ TestFunctionInfo Value;
+
+ [[nodiscard]] static constexpr rcc::TargetFunctionStateTypeID stableTypeID() noexcept {
+ return TestFunctionInfo::stableTypeID();
+ }
+
+ [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept {
+ return TestFunctionInfo::schemaVersion();
+ }
+
+ [[nodiscard]] rcc::Expected<void> verify() const {
+ return Value.verify();
+ }
+
+ [[nodiscard]] rcc::Expected<CollidingFunctionInfo>
+ clone(const rcc::MachineFunctionCloneContext &) const {
+ return CollidingFunctionInfo{Value};
+ }
+
+ [[nodiscard]] std::string print() const {
+ return Value.print();
+ }
+
+ [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const {
+ return Value.encode();
+ }
+
+ [[nodiscard]] static rcc::Expected<CollidingFunctionInfo>
+ decode(std::uint32_t Version, std::span<const std::byte> Payload) {
+ auto Decoded = TestFunctionInfo::decode(Version, Payload);
+ if (!Decoded)
+ return std::unexpected(Decoded.error());
+ return CollidingFunctionInfo{std::move(*Decoded)};
+ }
+
+ friend bool operator==(const CollidingFunctionInfo &Left,
+ const CollidingFunctionInfo &Right) = default;
+};
+
+[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() {
+ rcc::InstructionDescriptor Descriptor;
+ Descriptor.InstructionOpcode = rcc::Opcode::fromValue(1);
+ Descriptor.Name = "TEST_NOP";
+ Descriptor.Flags = {rcc::InstructionFlag::ProducesCode};
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(Descriptor));
+ return rcc::InstructionSet::create(std::move(Descriptors));
+}
+
+void testTargetFunctionStateContract(TestContext &Context) {
+ auto State = rcc::TargetFunctionState::create(TestFunctionInfo{4096, true, "lp64d"});
+ Context.expect(State.has_value(), "valid target function state must be type-erased");
+ if (!State)
+ return;
+
+ Context.expect(State->typeID() == TestFunctionInfo::stableTypeID() &&
+ State->schemaVersion() == TestFunctionInfo::schemaVersion() &&
+ State->getIf<TestFunctionInfo>() != nullptr &&
+ State->getIf<CollidingFunctionInfo>() == nullptr,
+ "strong query must require stable and local type identity");
+ auto Printed = State->print();
+ Context.expect(State->verify().has_value() && Printed && *Printed == "lp64d:4096:shadow",
+ "type-erased target state must preserve its semantic hooks");
+
+ auto Encoded = State->encode();
+ if (!Encoded)
+ return;
+ auto Decoded = rcc::TargetFunctionState::decode<TestFunctionInfo>(*Encoded);
+ Context.expect(Decoded && State->equals(*Decoded) &&
+ State->getIf<TestFunctionInfo>() != Decoded->getIf<TestFunctionInfo>(),
+ "versioned target function state must round-trip losslessly");
+
+ auto Collision = rcc::TargetFunctionState::create(
+ CollidingFunctionInfo{TestFunctionInfo{4096, true, "lp64d"}});
+ Context.expect(Collision && !State->equals(*Collision),
+ "colliding stable TypeIDs must not defeat local type safety");
+}
+
+void testTargetFunctionStateFailures(TestContext &Context) {
+ rcc::TargetFunctionState Empty;
+ Context.expect(!Empty.verify() && !Empty.print() && !Empty.encode(),
+ "empty target function state must reject semantic operations");
+ Context.expect(!rcc::TargetFunctionState::create(TestFunctionInfo{0, false, ""}),
+ "target function state creation must verify its concrete value");
+
+ auto State = rcc::TargetFunctionState::create(TestFunctionInfo{2048, false, "lp64"});
+ if (!State)
+ return;
+ auto Encoded = State->encode();
+ if (!Encoded)
+ return;
+
+ Encoded->Version = 0;
+ Context.expect(!rcc::TargetFunctionState::decode<TestFunctionInfo>(*Encoded),
+ "target function state decode must reject version zero");
+ Encoded->Version = 99;
+ Context.expect(!rcc::TargetFunctionState::decode<TestFunctionInfo>(*Encoded),
+ "target function state decode must reject unsupported versions");
+ Encoded->Version = TestFunctionInfo::schemaVersion();
+ Encoded->TypeID = rcc::TargetFunctionStateTypeID::fromValue(1);
+ Context.expect(!rcc::TargetFunctionState::decode<TestFunctionInfo>(*Encoded),
+ "target function state decode must reject a mismatched TypeID");
+}
+
+void testMachineFunctionOwnershipAndVerification(TestContext &Context) {
+ auto InstructionSet = createInstructionSet();
+ auto Function =
+ rcc::MachineFunction::create("target_state", rcc::MachineFunctionStableID::fromValue(1));
+ if (!InstructionSet || !Function)
+ return;
+
+ Context.expect(!(*Function)->setTargetState(rcc::TargetFunctionState{}),
+ "machine function must reject an empty target state");
+ auto State = rcc::TargetFunctionState::create(TestFunctionInfo{4096, false, "lp64d"});
+ Context.expect(State && (*Function)->setTargetState(std::move(*State)).has_value(),
+ "machine function must accept one verified target state");
+ Context.expect((*Function)->getTargetInfo<TestFunctionInfo>() != nullptr &&
+ (*Function)->getTargetInfo<CollidingFunctionInfo>() == nullptr &&
+ rcc::MachineVerifier::verify(**Function, *InstructionSet).has_value(),
+ "machine function must expose and verify its strong target state");
+
+ auto Replacement = rcc::TargetFunctionState::create(TestFunctionInfo{8192, true, "lp64d"});
+ Context.expect(Replacement && !(*Function)->setTargetState(std::move(*Replacement)),
+ "machine function must not accept a second current target state");
+
+ TestFunctionInfo *Info = (*Function)->getTargetInfo<TestFunctionInfo>();
+ if (Info == nullptr)
+ return;
+ Info->CallingConvention.clear();
+ Context.expect(!rcc::MachineVerifier::verify(**Function, *InstructionSet),
+ "machine verifier must reject invalid concrete target state");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testTargetFunctionStateContract(Context);
+ testTargetFunctionStateFailures(Context);
+ testMachineFunctionOwnershipAndVerification(Context);
+ return Context.result();
+}