diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 14:50:04 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 14:50:04 +0800 |
| commit | cfdbcc469f9691bd9dce3b0b86982a63d86ca6b7 (patch) | |
| tree | 19f4ad856c3e4900a0501ab2cad41981b49569b5 /backend/unittests/CodeGen | |
| parent | a69d14b8ebc028dddc689658b08d42d3e83f5e38 (diff) | |
| download | rcc-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.txt | 30 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineCompilationStatusTest.cpp | 164 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionClonerTest.cpp | 177 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 44 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/TargetFunctionStateTest.cpp | 251 |
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(); +} |
