aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/include/rcc/CodeGen/MachineCompilationStatus.h107
-rw-r--r--backend/include/rcc/CodeGen/MachineFunction.h27
-rw-r--r--backend/include/rcc/CodeGen/MachineFunctionCloner.h45
-rw-r--r--backend/include/rcc/CodeGen/TargetFunctionState.h210
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt8
-rw-r--r--backend/lib/CodeGen/MachineCompilationStatus.cpp195
-rw-r--r--backend/lib/CodeGen/MachineFunction.cpp11
-rw-r--r--backend/lib/CodeGen/MachineFunctionCloner.cpp97
-rw-r--r--backend/lib/CodeGen/MachineVerifier.cpp22
-rw-r--r--backend/lib/CodeGen/TargetFunctionState.cpp82
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt30
-rw-r--r--backend/unittests/CodeGen/MachineCompilationStatusTest.cpp164
-rw-r--r--backend/unittests/CodeGen/MachineFunctionClonerTest.cpp177
-rw-r--r--backend/unittests/CodeGen/MachineVerifierTest.cpp44
-rw-r--r--backend/unittests/CodeGen/TargetFunctionStateTest.cpp251
15 files changed, 1470 insertions, 0 deletions
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 <cstdint>
+#include <optional>
+#include <string>
+#include <string_view>
+
+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<SourceLocation> Location;
+
+ friend bool operator==(const MachineCompilationDiagnostic &Left,
+ const MachineCompilationDiagnostic &Right) = default;
+};
+
+struct MachineCompilationFailure final {
+ MachineCompilationDiagnostic Diagnostic;
+ std::optional<std::string> FailurePass;
+ std::optional<MachineInstrStableID> FailureInstruction;
+ std::optional<PipelineStage> FailureStage;
+ std::optional<std::string> FailureRule;
+ std::optional<std::string> Target;
+ std::optional<std::string> 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<void> fail(MachineCompilationState NewState,
+ MachineCompilationFailure NewFailure);
+ [[nodiscard]] Expected<void> verify() const;
+ [[nodiscard]] Expected<MachineCompilationStatus>
+ clone(const MachineFunctionCloneContext &CloneContext) const;
+
+ friend bool operator==(const MachineCompilationStatus &Left,
+ const MachineCompilationStatus &Right) = default;
+
+private:
+ MachineCompilationState State = MachineCompilationState::Success;
+ std::optional<MachineCompilationFailure> 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 <cstddef>
@@ -87,6 +89,29 @@ public:
return Properties;
}
+ [[nodiscard]] const TargetFunctionState &targetState() const noexcept {
+ return TargetState;
+ }
+
+ [[nodiscard]] Expected<void> setTargetState(TargetFunctionState State);
+
+ template <TargetFunctionStateValue ValueT> [[nodiscard]] ValueT *getTargetInfo() noexcept {
+ return TargetState.getIf<ValueT>();
+ }
+
+ template <TargetFunctionStateValue ValueT>
+ [[nodiscard]] const ValueT *getTargetInfo() const noexcept {
+ return TargetState.getIf<ValueT>();
+ }
+
+ [[nodiscard]] MachineCompilationStatus &compilationStatus() noexcept {
+ return CompilationStatus;
+ }
+
+ [[nodiscard]] const MachineCompilationStatus &compilationStatus() const noexcept {
+ return CompilationStatus;
+ }
+
[[nodiscard]] Expected<MachineBasicBlock *> createBasicBlock();
[[nodiscard]] Expected<MachineInstr *> createInstruction(Opcode InstructionOpcode);
[[nodiscard]] Expected<const MachineMemOperand *>
@@ -141,6 +166,8 @@ private:
MachineConstantPool ConstantPool;
MachineJumpTableInfo JumpTables;
MachineFunctionProperties Properties;
+ TargetFunctionState TargetState;
+ MachineCompilationStatus CompilationStatus;
MachineBasicBlock *FirstBlock = nullptr;
MachineBasicBlock *LastBlock = nullptr;
std::vector<MachineBasicBlock *> 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<JumpTableIndex, JumpTableIndex> JumpTableIndices;
};
+class MachineFunctionCloneContext final {
+public:
+ [[nodiscard]] const MachineFunction &sourceFunction() const noexcept {
+ return Source;
+ }
+
+ [[nodiscard]] MachineFunction &destinationFunction() const noexcept {
+ return Destination;
+ }
+
+ [[nodiscard]] Expected<MachineBasicBlock *> mapBlock(const MachineBasicBlock &SourceBlock) const;
+ [[nodiscard]] Expected<MachineInstr *>
+ mapInstruction(const MachineInstr &SourceInstruction) const;
+ [[nodiscard]] Expected<MachineBlockStableID>
+ mapBlockStableID(MachineBlockStableID SourceID) const;
+ [[nodiscard]] Expected<MachineInstrStableID>
+ mapInstructionStableID(MachineInstrStableID SourceID) const;
+ [[nodiscard]] Expected<Register> mapRegister(Register SourceRegister) const;
+ [[nodiscard]] Expected<FrameIndex> mapFrameIndex(FrameIndex SourceIndex) const;
+ [[nodiscard]] Expected<ConstantPoolIndex>
+ mapConstantPoolIndex(ConstantPoolIndex SourceIndex) const;
+ [[nodiscard]] Expected<JumpTableIndex> mapJumpTableIndex(JumpTableIndex SourceIndex) const;
+
+private:
+ MachineFunctionCloneContext(
+ const MachineFunction &Source, MachineFunction &Destination,
+ const MachineFunctionCloneResult &Result,
+ const std::unordered_map<const MachineBasicBlock *, MachineBasicBlock *> &Blocks,
+ const std::unordered_map<const MachineInstr *, MachineInstr *> &Instructions) noexcept
+ : Source(Source), Destination(Destination), Result(Result), Blocks(Blocks),
+ Instructions(Instructions) {}
+
+ const MachineFunction &Source;
+ MachineFunction &Destination;
+ const MachineFunctionCloneResult &Result;
+ const std::unordered_map<const MachineBasicBlock *, MachineBasicBlock *> &Blocks;
+ const std::unordered_map<const MachineInstr *, MachineInstr *> &Instructions;
+
+ friend Expected<MachineFunctionCloneResult>
+ cloneMachineFunction(const MachineFunction &Source, const InstructionSet &Instructions,
+ MachineFunctionCloneOptions Options);
+};
+
[[nodiscard]] Expected<MachineFunctionCloneResult>
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 <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <span>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace rcc {
+
+class MachineFunctionCloneContext;
+
+struct EncodedTargetFunctionState final {
+ TargetFunctionStateTypeID TypeID;
+ std::uint32_t Version = 0;
+ std::vector<std::byte> Payload;
+
+ friend bool operator==(const EncodedTargetFunctionState &Left,
+ const EncodedTargetFunctionState &Right) = default;
+};
+
+template <typename ValueT>
+concept TargetFunctionStateValue =
+ std::move_constructible<ValueT> &&
+ requires(const ValueT &Value, const ValueT &Other,
+ const MachineFunctionCloneContext &CloneContext, std::uint32_t Version,
+ std::span<const std::byte> Payload) {
+ { ValueT::stableTypeID() } -> std::same_as<TargetFunctionStateTypeID>;
+ { ValueT::schemaVersion() } -> std::same_as<std::uint32_t>;
+ { Value.verify() } -> std::same_as<Expected<void>>;
+ { Value.clone(CloneContext) } -> std::same_as<Expected<ValueT>>;
+ { Value.print() } -> std::same_as<std::string>;
+ { Value.encode() } -> std::same_as<Expected<std::vector<std::byte>>>;
+ { Value == Other } -> std::convertible_to<bool>;
+ { ValueT::decode(Version, Payload) } -> std::same_as<Expected<ValueT>>;
+ };
+
+class TargetFunctionState final {
+public:
+ TargetFunctionState() = default;
+ TargetFunctionState(const TargetFunctionState &) = delete;
+ TargetFunctionState &operator=(const TargetFunctionState &) = delete;
+ TargetFunctionState(TargetFunctionState &&) noexcept;
+ TargetFunctionState &operator=(TargetFunctionState &&) noexcept;
+ ~TargetFunctionState();
+
+ template <TargetFunctionStateValue ValueT>
+ [[nodiscard]] static Expected<TargetFunctionState> 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<Concept> NewStorage = std::make_unique<Model<ValueT>>(std::move(Value));
+ return TargetFunctionState(std::move(NewStorage));
+ }
+
+ template <TargetFunctionStateValue ValueT>
+ [[nodiscard]] static Expected<TargetFunctionState>
+ 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<ValueT>(std::move(*Value));
+ }
+
+ [[nodiscard]] bool hasValue() const noexcept {
+ return Storage != nullptr;
+ }
+
+ [[nodiscard]] std::optional<TargetFunctionStateTypeID> typeID() const noexcept;
+ [[nodiscard]] std::optional<std::uint32_t> schemaVersion() const noexcept;
+ [[nodiscard]] Expected<void> verify() const;
+ [[nodiscard]] Expected<TargetFunctionState>
+ clone(const MachineFunctionCloneContext &CloneContext) const;
+ [[nodiscard]] Expected<std::string> print() const;
+ [[nodiscard]] Expected<EncodedTargetFunctionState> encode() const;
+ [[nodiscard]] bool equals(const TargetFunctionState &Other) const noexcept;
+
+ template <TargetFunctionStateValue ValueT> [[nodiscard]] ValueT *getIf() noexcept {
+ if (!hasType<ValueT>())
+ return nullptr;
+ return &static_cast<Model<ValueT> *>(Storage.get())->value();
+ }
+
+ template <TargetFunctionStateValue ValueT> [[nodiscard]] const ValueT *getIf() const noexcept {
+ if (!hasType<ValueT>())
+ return nullptr;
+ return &static_cast<const Model<ValueT> *>(Storage.get())->value();
+ }
+
+private:
+ class Concept {
+ public:
+ Concept() = default;
+ Concept(const Concept &) = delete;
+ Concept &operator=(const Concept &) = delete;
+ Concept(Concept &&) = delete;
+ Concept &operator=(Concept &&) = delete;
+ virtual ~Concept() = default;
+
+ [[nodiscard]] virtual 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<void> verify() const = 0;
+ [[nodiscard]] virtual Expected<std::unique_ptr<Concept>>
+ cloneStorage(const MachineFunctionCloneContext &CloneContext) const = 0;
+ [[nodiscard]] virtual std::string print() const = 0;
+ [[nodiscard]] virtual Expected<std::vector<std::byte>> encodePayload() const = 0;
+ [[nodiscard]] virtual bool equals(const Concept &Other) const noexcept = 0;
+ };
+
+ template <typename ValueT> [[nodiscard]] static const void *typeToken() noexcept {
+ static const std::byte Token{};
+ return &Token;
+ }
+
+ template <TargetFunctionStateValue ValueT> 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<ValueT>();
+ }
+
+ [[nodiscard]] Expected<void> verify() const override {
+ return Value.verify();
+ }
+
+ [[nodiscard]] Expected<std::unique_ptr<Concept>>
+ cloneStorage(const MachineFunctionCloneContext &CloneContext) const override {
+ auto Cloned = Value.clone(CloneContext);
+ if (!Cloned)
+ return std::unexpected(Cloned.error());
+ std::unique_ptr<Concept> Result = std::make_unique<Model<ValueT>>(std::move(*Cloned));
+ return Result;
+ }
+
+ [[nodiscard]] std::string print() const override {
+ return Value.print();
+ }
+
+ [[nodiscard]] Expected<std::vector<std::byte>> encodePayload() const override {
+ return Value.encode();
+ }
+
+ [[nodiscard]] bool equals(const Concept &Other) const noexcept override {
+ if (Other.localTypeToken() != TargetFunctionState::typeToken<ValueT>())
+ return false;
+ return Value == static_cast<const Model<ValueT> &>(Other).Value;
+ }
+
+ [[nodiscard]] ValueT &value() noexcept {
+ return Value;
+ }
+
+ [[nodiscard]] const ValueT &value() const noexcept {
+ return Value;
+ }
+
+ private:
+ ValueT Value;
+ };
+
+ explicit TargetFunctionState(std::unique_ptr<Concept> Storage) : Storage(std::move(Storage)) {}
+
+ template <TargetFunctionStateValue ValueT> [[nodiscard]] bool hasType() const noexcept {
+ return Storage != nullptr && Storage->typeID() == ValueT::stableTypeID() &&
+ Storage->localTypeToken() == typeToken<ValueT>();
+ }
+
+ std::unique_ptr<Concept> Storage;
+};
+
+} // namespace rcc
+
+#endif // RCC_CODEGEN_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 <array>
+#include <cstddef>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <utility>
+
+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<std::size_t>(MachineCompilationState::Count));
+
+[[nodiscard]] bool
+isKnownDiagnosticSeverity(MachineCompilationDiagnosticSeverity Severity) noexcept {
+ return static_cast<std::size_t>(Severity) <
+ static_cast<std::size_t>(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<void> verifyOptionalText(const std::optional<std::string> &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<std::size_t>(State) < StateNames.size();
+}
+
+std::string_view getMachineCompilationStateName(MachineCompilationState State) noexcept {
+ const auto Index = static_cast<std::size_t>(State);
+ if (Index >= StateNames.size())
+ return "Unknown";
+ return StateNames[Index];
+}
+
+Expected<void> 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<void> 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>
+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<void> 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<MachineBasicBlock *> 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 <cstddef>
#include <cstdint>
@@ -463,6 +465,86 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) {
} // namespace
+Expected<MachineBasicBlock *>
+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<MachineInstr *>
+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<MachineBlockStableID>
+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<MachineInstrStableID>
+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<Register> 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<FrameIndex> 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<ConstantPoolIndex>
+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<JumpTableIndex>
+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<MachineFunctionCloneResult> 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<void> 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<void> 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 <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+namespace rcc {
+
+TargetFunctionState::TargetFunctionState(TargetFunctionState &&) noexcept = default;
+TargetFunctionState &TargetFunctionState::operator=(TargetFunctionState &&) noexcept = default;
+TargetFunctionState::~TargetFunctionState() = default;
+
+std::optional<TargetFunctionStateTypeID> TargetFunctionState::typeID() const noexcept {
+ if (Storage == nullptr)
+ return std::nullopt;
+ return Storage->typeID();
+}
+
+std::optional<std::uint32_t> TargetFunctionState::schemaVersion() const noexcept {
+ if (Storage == nullptr)
+ return std::nullopt;
+ return Storage->schemaVersion();
+}
+
+Expected<void> 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>
+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<std::string> TargetFunctionState::print() const {
+ auto Verification = verify();
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ return Storage->print();
+}
+
+Expected<EncodedTargetFunctionState> 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 <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();
+}