From cfdbcc469f9691bd9dce3b0b86982a63d86ca6b7 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sat, 1 Aug 2026 14:50:04 +0800 Subject: [CodeGen] Add target state and compilation status --- .../include/rcc/CodeGen/MachineCompilationStatus.h | 107 +++++++++ backend/include/rcc/CodeGen/MachineFunction.h | 27 +++ .../include/rcc/CodeGen/MachineFunctionCloner.h | 45 ++++ backend/include/rcc/CodeGen/TargetFunctionState.h | 210 +++++++++++++++++ backend/lib/CodeGen/CMakeLists.txt | 8 + backend/lib/CodeGen/MachineCompilationStatus.cpp | 195 ++++++++++++++++ backend/lib/CodeGen/MachineFunction.cpp | 11 + backend/lib/CodeGen/MachineFunctionCloner.cpp | 97 ++++++++ backend/lib/CodeGen/MachineVerifier.cpp | 22 ++ backend/lib/CodeGen/TargetFunctionState.cpp | 82 +++++++ backend/unittests/CodeGen/CMakeLists.txt | 30 +++ .../CodeGen/MachineCompilationStatusTest.cpp | 164 ++++++++++++++ .../CodeGen/MachineFunctionClonerTest.cpp | 177 +++++++++++++++ backend/unittests/CodeGen/MachineVerifierTest.cpp | 44 ++++ .../unittests/CodeGen/TargetFunctionStateTest.cpp | 251 +++++++++++++++++++++ 15 files changed, 1470 insertions(+) create mode 100644 backend/include/rcc/CodeGen/MachineCompilationStatus.h create mode 100644 backend/include/rcc/CodeGen/TargetFunctionState.h create mode 100644 backend/lib/CodeGen/MachineCompilationStatus.cpp create mode 100644 backend/lib/CodeGen/TargetFunctionState.cpp create mode 100644 backend/unittests/CodeGen/MachineCompilationStatusTest.cpp create mode 100644 backend/unittests/CodeGen/TargetFunctionStateTest.cpp (limited to 'backend') diff --git a/backend/include/rcc/CodeGen/MachineCompilationStatus.h b/backend/include/rcc/CodeGen/MachineCompilationStatus.h new file mode 100644 index 0000000..f889574 --- /dev/null +++ b/backend/include/rcc/CodeGen/MachineCompilationStatus.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_MACHINECOMPILATIONSTATUS_H +#define RCC_CODEGEN_MACHINECOMPILATIONSTATUS_H + +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/StableID.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include + +namespace rcc { + +class MachineFunctionCloneContext; + +enum class MachineCompilationState : std::uint8_t { + Success, + Unsupported, + MIRParseFailed, + LegalizationFailed, + SelectionFailed, + RegisterAllocationFailed, + CallLoweringFailed, + FrameLayoutFailed, + FinalizationFailed, + AssemblyLoweringFailed, + InternalError, + Count, +}; + +[[nodiscard]] bool isKnownMachineCompilationState(MachineCompilationState State) noexcept; +[[nodiscard]] std::string_view +getMachineCompilationStateName(MachineCompilationState State) noexcept; + +enum class MachineCompilationDiagnosticSeverity : std::uint8_t { + Error, + Fatal, + Count, +}; + +struct MachineCompilationDiagnostic final { + MachineCompilationDiagnosticSeverity Severity = MachineCompilationDiagnosticSeverity::Error; + ErrorCode Code = ErrorCode::InternalError; + std::string Message; + std::optional Location; + + friend bool operator==(const MachineCompilationDiagnostic &Left, + const MachineCompilationDiagnostic &Right) = default; +}; + +struct MachineCompilationFailure final { + MachineCompilationDiagnostic Diagnostic; + std::optional FailurePass; + std::optional FailureInstruction; + std::optional FailureStage; + std::optional FailureRule; + std::optional Target; + std::optional Subtarget; + + friend bool operator==(const MachineCompilationFailure &Left, + const MachineCompilationFailure &Right) = default; +}; + +class MachineCompilationStatus final { +public: + MachineCompilationStatus() = default; + MachineCompilationStatus(const MachineCompilationStatus &) = delete; + MachineCompilationStatus &operator=(const MachineCompilationStatus &) = delete; + MachineCompilationStatus(MachineCompilationStatus &&) noexcept = default; + MachineCompilationStatus &operator=(MachineCompilationStatus &&) noexcept = default; + ~MachineCompilationStatus() = default; + + [[nodiscard]] MachineCompilationState state() const noexcept { + return State; + } + + [[nodiscard]] bool succeeded() const noexcept { + return State == MachineCompilationState::Success; + } + + [[nodiscard]] const MachineCompilationFailure *failure() const noexcept { + if (!Failure) + return nullptr; + return &*Failure; + } + + [[nodiscard]] Expected fail(MachineCompilationState NewState, + MachineCompilationFailure NewFailure); + [[nodiscard]] Expected verify() const; + [[nodiscard]] Expected + clone(const MachineFunctionCloneContext &CloneContext) const; + + friend bool operator==(const MachineCompilationStatus &Left, + const MachineCompilationStatus &Right) = default; + +private: + MachineCompilationState State = MachineCompilationState::Success; + std::optional Failure; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_MACHINECOMPILATIONSTATUS_H diff --git a/backend/include/rcc/CodeGen/MachineFunction.h b/backend/include/rcc/CodeGen/MachineFunction.h index 38d88f9..b687573 100644 --- a/backend/include/rcc/CodeGen/MachineFunction.h +++ b/backend/include/rcc/CodeGen/MachineFunction.h @@ -3,12 +3,14 @@ #ifndef RCC_CODEGEN_MACHINEFUNCTION_H #define RCC_CODEGEN_MACHINEFUNCTION_H +#include "rcc/CodeGen/MachineCompilationStatus.h" #include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/StableID.h" +#include "rcc/CodeGen/TargetFunctionState.h" #include "rcc/Support/Error.h" #include @@ -87,6 +89,29 @@ public: return Properties; } + [[nodiscard]] const TargetFunctionState &targetState() const noexcept { + return TargetState; + } + + [[nodiscard]] Expected setTargetState(TargetFunctionState State); + + template [[nodiscard]] ValueT *getTargetInfo() noexcept { + return TargetState.getIf(); + } + + template + [[nodiscard]] const ValueT *getTargetInfo() const noexcept { + return TargetState.getIf(); + } + + [[nodiscard]] MachineCompilationStatus &compilationStatus() noexcept { + return CompilationStatus; + } + + [[nodiscard]] const MachineCompilationStatus &compilationStatus() const noexcept { + return CompilationStatus; + } + [[nodiscard]] Expected createBasicBlock(); [[nodiscard]] Expected createInstruction(Opcode InstructionOpcode); [[nodiscard]] Expected @@ -141,6 +166,8 @@ private: MachineConstantPool ConstantPool; MachineJumpTableInfo JumpTables; MachineFunctionProperties Properties; + TargetFunctionState TargetState; + MachineCompilationStatus CompilationStatus; MachineBasicBlock *FirstBlock = nullptr; MachineBasicBlock *LastBlock = nullptr; std::vector AllBlocks; diff --git a/backend/include/rcc/CodeGen/MachineFunctionCloner.h b/backend/include/rcc/CodeGen/MachineFunctionCloner.h index 3abdf78..658614f 100644 --- a/backend/include/rcc/CodeGen/MachineFunctionCloner.h +++ b/backend/include/rcc/CodeGen/MachineFunctionCloner.h @@ -16,7 +16,9 @@ namespace rcc { class InstructionSet; +class MachineBasicBlock; class MachineFunction; +class MachineInstr; struct MachineFunctionCloneOptions final { MachineFunctionStableID NewStableID; @@ -40,6 +42,49 @@ struct MachineFunctionCloneResult final { std::unordered_map JumpTableIndices; }; +class MachineFunctionCloneContext final { +public: + [[nodiscard]] const MachineFunction &sourceFunction() const noexcept { + return Source; + } + + [[nodiscard]] MachineFunction &destinationFunction() const noexcept { + return Destination; + } + + [[nodiscard]] Expected mapBlock(const MachineBasicBlock &SourceBlock) const; + [[nodiscard]] Expected + mapInstruction(const MachineInstr &SourceInstruction) const; + [[nodiscard]] Expected + mapBlockStableID(MachineBlockStableID SourceID) const; + [[nodiscard]] Expected + mapInstructionStableID(MachineInstrStableID SourceID) const; + [[nodiscard]] Expected mapRegister(Register SourceRegister) const; + [[nodiscard]] Expected mapFrameIndex(FrameIndex SourceIndex) const; + [[nodiscard]] Expected + mapConstantPoolIndex(ConstantPoolIndex SourceIndex) const; + [[nodiscard]] Expected mapJumpTableIndex(JumpTableIndex SourceIndex) const; + +private: + MachineFunctionCloneContext( + const MachineFunction &Source, MachineFunction &Destination, + const MachineFunctionCloneResult &Result, + const std::unordered_map &Blocks, + const std::unordered_map &Instructions) noexcept + : Source(Source), Destination(Destination), Result(Result), Blocks(Blocks), + Instructions(Instructions) {} + + const MachineFunction &Source; + MachineFunction &Destination; + const MachineFunctionCloneResult &Result; + const std::unordered_map &Blocks; + const std::unordered_map &Instructions; + + friend Expected + cloneMachineFunction(const MachineFunction &Source, const InstructionSet &Instructions, + MachineFunctionCloneOptions Options); +}; + [[nodiscard]] Expected cloneMachineFunction(const MachineFunction &Source, const InstructionSet &Instructions, MachineFunctionCloneOptions Options); diff --git a/backend/include/rcc/CodeGen/TargetFunctionState.h b/backend/include/rcc/CodeGen/TargetFunctionState.h new file mode 100644 index 0000000..b11c91b --- /dev/null +++ b/backend/include/rcc/CodeGen/TargetFunctionState.h @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_TARGETFUNCTIONSTATE_H +#define RCC_CODEGEN_TARGETFUNCTIONSTATE_H + +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class MachineFunctionCloneContext; + +struct EncodedTargetFunctionState final { + TargetFunctionStateTypeID TypeID; + std::uint32_t Version = 0; + std::vector Payload; + + friend bool operator==(const EncodedTargetFunctionState &Left, + const EncodedTargetFunctionState &Right) = default; +}; + +template +concept TargetFunctionStateValue = + std::move_constructible && + requires(const ValueT &Value, const ValueT &Other, + const MachineFunctionCloneContext &CloneContext, std::uint32_t Version, + std::span Payload) { + { ValueT::stableTypeID() } -> std::same_as; + { ValueT::schemaVersion() } -> std::same_as; + { Value.verify() } -> std::same_as>; + { Value.clone(CloneContext) } -> std::same_as>; + { Value.print() } -> std::same_as; + { Value.encode() } -> std::same_as>>; + { Value == Other } -> std::convertible_to; + { ValueT::decode(Version, Payload) } -> std::same_as>; + }; + +class TargetFunctionState final { +public: + TargetFunctionState() = default; + TargetFunctionState(const TargetFunctionState &) = delete; + TargetFunctionState &operator=(const TargetFunctionState &) = delete; + TargetFunctionState(TargetFunctionState &&) noexcept; + TargetFunctionState &operator=(TargetFunctionState &&) noexcept; + ~TargetFunctionState(); + + template + [[nodiscard]] static Expected create(ValueT Value) { + if (ValueT::stableTypeID().value() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "target function state TypeID must be nonzero"); + } + if (ValueT::schemaVersion() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "target function state schema version must be nonzero"); + } + auto Verification = Value.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + + std::unique_ptr NewStorage = std::make_unique>(std::move(Value)); + return TargetFunctionState(std::move(NewStorage)); + } + + template + [[nodiscard]] static Expected + decode(const EncodedTargetFunctionState &Encoded) { + if (Encoded.TypeID != ValueT::stableTypeID()) { + return makeUnexpected( + ErrorCode::ParseError, + "encoded target function state TypeID does not match its target schema"); + } + if (Encoded.Version == 0) { + return makeUnexpected(ErrorCode::ParseError, "target function state version must be nonzero"); + } + auto Value = ValueT::decode(Encoded.Version, Encoded.Payload); + if (!Value) + return std::unexpected(Value.error()); + return create(std::move(*Value)); + } + + [[nodiscard]] bool hasValue() const noexcept { + return Storage != nullptr; + } + + [[nodiscard]] std::optional typeID() const noexcept; + [[nodiscard]] std::optional schemaVersion() const noexcept; + [[nodiscard]] Expected verify() const; + [[nodiscard]] Expected + clone(const MachineFunctionCloneContext &CloneContext) const; + [[nodiscard]] Expected print() const; + [[nodiscard]] Expected encode() const; + [[nodiscard]] bool equals(const TargetFunctionState &Other) const noexcept; + + template [[nodiscard]] ValueT *getIf() noexcept { + if (!hasType()) + return nullptr; + return &static_cast *>(Storage.get())->value(); + } + + template [[nodiscard]] const ValueT *getIf() const noexcept { + if (!hasType()) + return nullptr; + return &static_cast *>(Storage.get())->value(); + } + +private: + class Concept { + public: + Concept() = default; + Concept(const Concept &) = delete; + Concept &operator=(const Concept &) = delete; + Concept(Concept &&) = delete; + Concept &operator=(Concept &&) = delete; + virtual ~Concept() = default; + + [[nodiscard]] virtual TargetFunctionStateTypeID typeID() const noexcept = 0; + [[nodiscard]] virtual std::uint32_t schemaVersion() const noexcept = 0; + [[nodiscard]] virtual const void *localTypeToken() const noexcept = 0; + [[nodiscard]] virtual Expected verify() const = 0; + [[nodiscard]] virtual Expected> + cloneStorage(const MachineFunctionCloneContext &CloneContext) const = 0; + [[nodiscard]] virtual std::string print() const = 0; + [[nodiscard]] virtual Expected> encodePayload() const = 0; + [[nodiscard]] virtual bool equals(const Concept &Other) const noexcept = 0; + }; + + template [[nodiscard]] static const void *typeToken() noexcept { + static const std::byte Token{}; + return &Token; + } + + template class Model final : public Concept { + public: + explicit Model(ValueT Value) : Value(std::move(Value)) {} + + [[nodiscard]] TargetFunctionStateTypeID typeID() const noexcept override { + return ValueT::stableTypeID(); + } + + [[nodiscard]] std::uint32_t schemaVersion() const noexcept override { + return ValueT::schemaVersion(); + } + + [[nodiscard]] const void *localTypeToken() const noexcept override { + return TargetFunctionState::typeToken(); + } + + [[nodiscard]] Expected verify() const override { + return Value.verify(); + } + + [[nodiscard]] Expected> + cloneStorage(const MachineFunctionCloneContext &CloneContext) const override { + auto Cloned = Value.clone(CloneContext); + if (!Cloned) + return std::unexpected(Cloned.error()); + std::unique_ptr Result = std::make_unique>(std::move(*Cloned)); + return Result; + } + + [[nodiscard]] std::string print() const override { + return Value.print(); + } + + [[nodiscard]] Expected> encodePayload() const override { + return Value.encode(); + } + + [[nodiscard]] bool equals(const Concept &Other) const noexcept override { + if (Other.localTypeToken() != TargetFunctionState::typeToken()) + return false; + return Value == static_cast &>(Other).Value; + } + + [[nodiscard]] ValueT &value() noexcept { + return Value; + } + + [[nodiscard]] const ValueT &value() const noexcept { + return Value; + } + + private: + ValueT Value; + }; + + explicit TargetFunctionState(std::unique_ptr Storage) : Storage(std::move(Storage)) {} + + template [[nodiscard]] bool hasType() const noexcept { + return Storage != nullptr && Storage->typeID() == ValueT::stableTypeID() && + Storage->localTypeToken() == typeToken(); + } + + std::unique_ptr Storage; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_TARGETFUNCTIONSTATE_H diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index 3dd17a4..68ebf04 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -4,6 +4,7 @@ set( RCCBackendCodeGenSources InstructionDescriptor.cpp LowLevelType.cpp + MachineCompilationStatus.cpp MachineBasicBlock.cpp MachineCFGUpdater.cpp MachineConstantPool.cpp @@ -22,11 +23,13 @@ set( Register.cpp RegisterMask.cpp TargetConstant.cpp + TargetFunctionState.cpp ) set( RCCBackendCodeGenHeaders "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/InstructionDescriptor.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCompilationStatus.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineBasicBlock.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCFGUpdater.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineConstantPool.h" @@ -46,6 +49,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/RegisterMask.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/StableID.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetConstant.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetFunctionState.h" ) add_library(RCCBackendCodeGen ${RCCBackendCodeGenSources}) @@ -79,6 +83,7 @@ install( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatus.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPool.cpp" @@ -97,11 +102,13 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp" ${RCCBackendCodeGenHeaders} ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatus.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPool.cpp" @@ -120,4 +127,5 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/Register.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RegisterMask.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp" ) diff --git a/backend/lib/CodeGen/MachineCompilationStatus.cpp b/backend/lib/CodeGen/MachineCompilationStatus.cpp new file mode 100644 index 0000000..600deac --- /dev/null +++ b/backend/lib/CodeGen/MachineCompilationStatus.cpp @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineCompilationStatus.h" + +#include "rcc/CodeGen/MachineFunctionCloner.h" + +#include +#include +#include +#include +#include +#include + +namespace rcc { +namespace { + +constexpr std::array StateNames{ + std::string_view{"Success"}, + std::string_view{"Unsupported"}, + std::string_view{"MIRParseFailed"}, + std::string_view{"LegalizationFailed"}, + std::string_view{"SelectionFailed"}, + std::string_view{"RegisterAllocationFailed"}, + std::string_view{"CallLoweringFailed"}, + std::string_view{"FrameLayoutFailed"}, + std::string_view{"FinalizationFailed"}, + std::string_view{"AssemblyLoweringFailed"}, + std::string_view{"InternalError"}, +}; + +static_assert(StateNames.size() == static_cast(MachineCompilationState::Count)); + +[[nodiscard]] bool +isKnownDiagnosticSeverity(MachineCompilationDiagnosticSeverity Severity) noexcept { + return static_cast(Severity) < + static_cast(MachineCompilationDiagnosticSeverity::Count); +} + +[[nodiscard]] bool isKnownErrorCode(ErrorCode Code) noexcept { + switch (Code) { + case ErrorCode::InvalidArgument: + case ErrorCode::ParseError: + case ErrorCode::VerificationError: + case ErrorCode::Unsupported: + case ErrorCode::IoError: + case ErrorCode::InternalError: + return true; + } + return false; +} + +[[nodiscard]] bool isKnownPipelineStage(PipelineStage Stage) noexcept { + switch (Stage) { + case PipelineStage::MachineIRInput: + case PipelineStage::Legalized: + case PipelineStage::RegBankSelected: + case PipelineStage::Selected: + case PipelineStage::SSAEliminated: + case PipelineStage::TiedOpsRewritten: + case PipelineStage::RegisterAllocated: + case PipelineStage::FrameFinalized: + case PipelineStage::PostRAFinalized: + case PipelineStage::Emission: + return true; + } + return false; +} + +[[nodiscard]] Expected verifyOptionalText(const std::optional &Text, + std::string_view FieldName) { + if (Text && Text->empty()) { + return makeUnexpected(ErrorCode::VerificationError, "machine compilation failure " + + std::string(FieldName) + + " cannot be empty"); + } + return {}; +} + +} // namespace + +bool isKnownMachineCompilationState(MachineCompilationState State) noexcept { + return static_cast(State) < StateNames.size(); +} + +std::string_view getMachineCompilationStateName(MachineCompilationState State) noexcept { + const auto Index = static_cast(State); + if (Index >= StateNames.size()) + return "Unknown"; + return StateNames[Index]; +} + +Expected MachineCompilationStatus::fail(MachineCompilationState NewState, + MachineCompilationFailure NewFailure) { + if (!succeeded()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine compilation status already contains a failure"); + } + if (!isKnownMachineCompilationState(NewState) || NewState == MachineCompilationState::Success) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine compilation failure requires a known failure state"); + } + + MachineCompilationStatus Candidate; + Candidate.State = NewState; + Candidate.Failure = std::move(NewFailure); + auto Verification = Candidate.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + + *this = std::move(Candidate); + return {}; +} + +Expected MachineCompilationStatus::verify() const { + if (!isKnownMachineCompilationState(State)) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation status has an unknown state"); + } + if (State == MachineCompilationState::Success) { + if (Failure) { + return makeUnexpected(ErrorCode::VerificationError, + "successful machine compilation status contains failure details"); + } + return {}; + } + if (!Failure) { + return makeUnexpected(ErrorCode::VerificationError, + "failed machine compilation status lacks failure details"); + } + + if (!isKnownDiagnosticSeverity(Failure->Diagnostic.Severity)) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation diagnostic has an unknown severity"); + } + if (!isKnownErrorCode(Failure->Diagnostic.Code)) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation diagnostic has an unknown error code"); + } + if (Failure->Diagnostic.Message.empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation diagnostic message cannot be empty"); + } + if (Failure->Diagnostic.Location && !Failure->Diagnostic.Location->isValid()) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation diagnostic has an invalid source location"); + } + if (Failure->FailureInstruction && !Failure->FailureInstruction->isValid()) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation failure has an invalid instruction StableID"); + } + if (Failure->FailureStage && !isKnownPipelineStage(*Failure->FailureStage)) { + return makeUnexpected(ErrorCode::VerificationError, + "machine compilation failure has an unknown pipeline stage"); + } + + for (const auto &[Text, Name] : + {std::pair{&Failure->FailurePass, std::string_view{"pass"}}, + std::pair{&Failure->FailureRule, std::string_view{"rule or pattern"}}, + std::pair{&Failure->Target, std::string_view{"target"}}, + std::pair{&Failure->Subtarget, std::string_view{"subtarget"}}}) { + auto TextVerification = verifyOptionalText(*Text, Name); + if (!TextVerification) + return TextVerification; + } + return {}; +} + +Expected +MachineCompilationStatus::clone(const MachineFunctionCloneContext &CloneContext) const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (succeeded()) + return MachineCompilationStatus{}; + if (!Failure) { + return makeUnexpected(ErrorCode::InternalError, + "verified failed compilation status has no failure details"); + } + + MachineCompilationFailure ClonedFailure = *Failure; + if (ClonedFailure.FailureInstruction) { + auto Mapped = CloneContext.mapInstructionStableID(*ClonedFailure.FailureInstruction); + if (!Mapped) + return std::unexpected(Mapped.error()); + ClonedFailure.FailureInstruction = *Mapped; + } + + MachineCompilationStatus Result; + auto FailureResult = Result.fail(State, std::move(ClonedFailure)); + if (!FailureResult) + return std::unexpected(FailureResult.error()); + return Result; +} + +} // namespace rcc diff --git a/backend/lib/CodeGen/MachineFunction.cpp b/backend/lib/CodeGen/MachineFunction.cpp index 64b6440..294158b 100644 --- a/backend/lib/CodeGen/MachineFunction.cpp +++ b/backend/lib/CodeGen/MachineFunction.cpp @@ -75,6 +75,17 @@ MachineFunction::~MachineFunction() { } } +Expected MachineFunction::setTargetState(TargetFunctionState State) { + if (TargetState.hasValue()) { + return makeUnexpected(ErrorCode::InvalidArgument, "machine function already has target state"); + } + auto Verification = State.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + TargetState = std::move(State); + return {}; +} + Expected MachineFunction::createBasicBlock() { auto ID = BlockIDGenerator.allocate(); if (!ID) diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp index 5703161..a8fe15e 100644 --- a/backend/lib/CodeGen/MachineFunctionCloner.cpp +++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp @@ -5,6 +5,7 @@ #include "rcc/CodeGen/InstructionDescriptor.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" @@ -14,6 +15,7 @@ #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/MachineVerifier.h" +#include "rcc/CodeGen/TargetFunctionState.h" #include #include @@ -463,6 +465,86 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { } // namespace +Expected +MachineFunctionCloneContext::mapBlock(const MachineBasicBlock &SourceBlock) const { + const auto Position = Blocks.find(&SourceBlock); + if (Position == Blocks.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped basic block"); + } + return Position->second; +} + +Expected +MachineFunctionCloneContext::mapInstruction(const MachineInstr &SourceInstruction) const { + const auto Position = Instructions.find(&SourceInstruction); + if (Position == Instructions.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped instruction"); + } + return Position->second; +} + +Expected +MachineFunctionCloneContext::mapBlockStableID(MachineBlockStableID SourceID) const { + const auto Position = Result.BlockStableIDs.find(SourceID); + if (Position == Result.BlockStableIDs.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped block StableID"); + } + return Position->second; +} + +Expected +MachineFunctionCloneContext::mapInstructionStableID(MachineInstrStableID SourceID) const { + const auto Position = Result.InstructionStableIDs.find(SourceID); + if (Position == Result.InstructionStableIDs.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "function state references an unmapped instruction StableID"); + } + return Position->second; +} + +Expected MachineFunctionCloneContext::mapRegister(Register SourceRegister) const { + if (!SourceRegister.isVirtual()) + return SourceRegister; + const auto Position = Result.VirtualRegisters.find(SourceRegister); + if (Position == Result.VirtualRegisters.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped virtual register"); + } + return Position->second; +} + +Expected MachineFunctionCloneContext::mapFrameIndex(FrameIndex SourceIndex) const { + const auto Position = Result.FrameIndices.find(SourceIndex); + if (Position == Result.FrameIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped frame index"); + } + return Position->second; +} + +Expected +MachineFunctionCloneContext::mapConstantPoolIndex(ConstantPoolIndex SourceIndex) const { + const auto Position = Result.ConstantPoolIndices.find(SourceIndex); + if (Position == Result.ConstantPoolIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped constant pool entry"); + } + return Position->second; +} + +Expected +MachineFunctionCloneContext::mapJumpTableIndex(JumpTableIndex SourceIndex) const { + const auto Position = Result.JumpTableIndices.find(SourceIndex); + if (Position == Result.JumpTableIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state references an unmapped jump table"); + } + return Position->second; +} + MachineFunctionCloneResult::MachineFunctionCloneResult() = default; MachineFunctionCloneResult::MachineFunctionCloneResult(MachineFunctionCloneResult &&) noexcept = default; @@ -517,6 +599,21 @@ Expected cloneMachineFunction(const MachineFunction if (!CFGResult) return std::unexpected(CFGResult.error()); + const MachineFunctionCloneContext CloneContext(Source, *Result.ClonedFunction, Result, + State.Blocks, State.Instructions); + if (Source.targetState().hasValue()) { + auto TargetState = Source.targetState().clone(CloneContext); + if (!TargetState) + return std::unexpected(TargetState.error()); + auto InstallResult = Result.ClonedFunction->setTargetState(std::move(*TargetState)); + if (!InstallResult) + return std::unexpected(InstallResult.error()); + } + + auto CompilationStatus = Source.compilationStatus().clone(CloneContext); + if (!CompilationStatus) + return std::unexpected(CompilationStatus.error()); + Result.ClonedFunction->compilationStatus() = std::move(*CompilationStatus); Result.ClonedFunction->properties() = Source.properties(); auto DestinationVerification = MachineVerifier::verify(*Result.ClonedFunction, Instructions); if (!DestinationVerification) diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp index 50c1001..0c05a07 100644 --- a/backend/lib/CodeGen/MachineVerifier.cpp +++ b/backend/lib/CodeGen/MachineVerifier.cpp @@ -34,6 +34,11 @@ public: if (Function.name().empty() || !Function.stableID().isValid()) return fail("function identity is invalid"); + if (Function.targetState().hasValue()) { + auto TargetStateResult = Function.targetState().verify(); + if (!TargetStateResult) + return failWith("target function state", TargetStateResult.error()); + } auto CatalogResult = catalogArenaObjects(); if (!CatalogResult) return CatalogResult; @@ -46,6 +51,9 @@ public: auto LayoutResult = verifyBlockLayout(); if (!LayoutResult) return LayoutResult; + auto CompilationStatusResult = verifyCompilationStatus(); + if (!CompilationStatusResult) + return CompilationStatusResult; auto JumpTableResult = Function.jumpTableInfo().verify(); if (!JumpTableResult) return failWith("jump table info", JumpTableResult.error()); @@ -319,6 +327,20 @@ private: return {}; } + [[nodiscard]] Expected verifyCompilationStatus() const { + const MachineCompilationStatus &Status = Function.compilationStatus(); + auto Verification = Status.verify(); + if (!Verification) + return failWith("compilation status", Verification.error()); + + const MachineCompilationFailure *Failure = Status.failure(); + if (Failure != nullptr && Failure->FailureInstruction && + !LinkedInstructionIDs.contains(*Failure->FailureInstruction)) { + return fail("compilation failure references a detached or unknown instruction"); + } + return {}; + } + [[nodiscard]] Expected verifyLiveInstruction(const MachineInstr &Instruction) { auto LocalResult = verifyInstructionLocalState(Instruction); if (!LocalResult) diff --git a/backend/lib/CodeGen/TargetFunctionState.cpp b/backend/lib/CodeGen/TargetFunctionState.cpp new file mode 100644 index 0000000..56ff363 --- /dev/null +++ b/backend/lib/CodeGen/TargetFunctionState.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetFunctionState.h" + +#include +#include +#include +#include +#include + +namespace rcc { + +TargetFunctionState::TargetFunctionState(TargetFunctionState &&) noexcept = default; +TargetFunctionState &TargetFunctionState::operator=(TargetFunctionState &&) noexcept = default; +TargetFunctionState::~TargetFunctionState() = default; + +std::optional TargetFunctionState::typeID() const noexcept { + if (Storage == nullptr) + return std::nullopt; + return Storage->typeID(); +} + +std::optional TargetFunctionState::schemaVersion() const noexcept { + if (Storage == nullptr) + return std::nullopt; + return Storage->schemaVersion(); +} + +Expected TargetFunctionState::verify() const { + if (Storage == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "target function state is empty"); + if (Storage->typeID().value() == 0 || Storage->schemaVersion() == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "target function state identity or schema version is invalid"); + } + return Storage->verify(); +} + +Expected +TargetFunctionState::clone(const MachineFunctionCloneContext &CloneContext) const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto ClonedStorage = Storage->cloneStorage(CloneContext); + if (!ClonedStorage) + return std::unexpected(ClonedStorage.error()); + TargetFunctionState Result(std::move(*ClonedStorage)); + auto CloneVerification = Result.verify(); + if (!CloneVerification) + return std::unexpected(CloneVerification.error()); + return Result; +} + +Expected TargetFunctionState::print() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Storage->print(); +} + +Expected TargetFunctionState::encode() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + auto Payload = Storage->encodePayload(); + if (!Payload) + return std::unexpected(Payload.error()); + return EncodedTargetFunctionState{Storage->typeID(), Storage->schemaVersion(), + std::move(*Payload)}; +} + +bool TargetFunctionState::equals(const TargetFunctionState &Other) const noexcept { + if (Storage == nullptr || Other.Storage == nullptr) + return Storage == nullptr && Other.Storage == nullptr; + if (Storage->typeID() != Other.Storage->typeID() || + Storage->localTypeToken() != Other.Storage->localTypeToken()) { + return false; + } + return Storage->equals(*Other.Storage); +} + +} // namespace rcc diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 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 +#include +#include +#include +#include +#include + +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(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(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 #include @@ -24,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +61,105 @@ private: int FailureCount = 0; }; +void appendLittleEndian(std::vector &Bytes, std::uint64_t Value, std::size_t Width) { + for (std::size_t Index = 0; Index < Width; ++Index) + Bytes.push_back(static_cast((Value >> (Index * 8U)) & 0xffU)); +} + +[[nodiscard]] std::uint64_t readLittleEndian(std::span 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(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 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 + 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> encode() const { + std::vector 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 decode(std::uint32_t Version, + std::span 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(readLittleEndian(Payload, 16, 4))), + rcc::FrameIndex::fromValue(static_cast(readLittleEndian(Payload, 20, 4))), + rcc::ConstantPoolIndex::fromValue( + static_cast(readLittleEndian(Payload, 24, 4))), + rcc::JumpTableIndex::fromValue( + static_cast(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(); + const CloneTargetState *DestinationTarget = Destination.getTargetInfo(); + 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 +#include +#include +#include +#include +#include +#include +#include + +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 verify() const { + if (StackProbeSize == 0 || CallingConvention.empty()) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "test function info is incomplete"); + } + return {}; + } + + [[nodiscard]] rcc::Expected + 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> encode() const { + std::vector Payload; + Payload.reserve(9 + CallingConvention.size()); + for (std::size_t Index = 0; Index < 8; ++Index) { + Payload.push_back(static_cast((StackProbeSize >> (Index * 8U)) & 0xffU)); + } + Payload.push_back(static_cast(UsesShadowCallStack ? 1 : 0)); + for (const char RawCharacter : CallingConvention) { + Payload.push_back(static_cast(static_cast(RawCharacter))); + } + return Payload; + } + + [[nodiscard]] static rcc::Expected decode(std::uint32_t Version, + std::span Payload) { + if (Version != schemaVersion()) { + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, + "unsupported test function info version"); + } + if (Payload.size() <= 9 || std::to_integer(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(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(std::to_integer(Character))); + } + return TestFunctionInfo{StackProbeSize, std::to_integer(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 verify() const { + return Value.verify(); + } + + [[nodiscard]] rcc::Expected + clone(const rcc::MachineFunctionCloneContext &) const { + return CollidingFunctionInfo{Value}; + } + + [[nodiscard]] std::string print() const { + return Value.print(); + } + + [[nodiscard]] rcc::Expected> encode() const { + return Value.encode(); + } + + [[nodiscard]] static rcc::Expected + decode(std::uint32_t Version, std::span 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 createInstructionSet() { + rcc::InstructionDescriptor Descriptor; + Descriptor.InstructionOpcode = rcc::Opcode::fromValue(1); + Descriptor.Name = "TEST_NOP"; + Descriptor.Flags = {rcc::InstructionFlag::ProducesCode}; + std::vector 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() != nullptr && + State->getIf() == 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(*Encoded); + Context.expect(Decoded && State->equals(*Decoded) && + State->getIf() != Decoded->getIf(), + "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(*Encoded), + "target function state decode must reject version zero"); + Encoded->Version = 99; + Context.expect(!rcc::TargetFunctionState::decode(*Encoded), + "target function state decode must reject unsupported versions"); + Encoded->Version = TestFunctionInfo::schemaVersion(); + Encoded->TypeID = rcc::TargetFunctionStateTypeID::fromValue(1); + Context.expect(!rcc::TargetFunctionState::decode(*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() != nullptr && + (*Function)->getTargetInfo() == 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(); + 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(); +} -- cgit v1.2.3