diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-03 19:24:30 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-03 19:24:30 +0800 |
| commit | 432efe39315ed5ad0162f9c08dbf10941bc21dc1 (patch) | |
| tree | 46622290cabfaeb8f4db1729aca6f0725739e08c /backend | |
| parent | 6580ac1c05bd1cbccd2dcd71fd9c2a384ae3db56 (diff) | |
| download | rcc-project-432efe39315ed5ad0162f9c08dbf10941bc21dc1.tar.gz rcc-project-432efe39315ed5ad0162f9c08dbf10941bc21dc1.tar.xz | |
[CodeGen] Add target MIR text schema
Diffstat (limited to 'backend')
20 files changed, 1363 insertions, 50 deletions
diff --git a/backend/include/rcc/CodeGen/MachineFrameInfo.h b/backend/include/rcc/CodeGen/MachineFrameInfo.h index b837ff3..7c42bc2 100644 --- a/backend/include/rcc/CodeGen/MachineFrameInfo.h +++ b/backend/include/rcc/CodeGen/MachineFrameInfo.h @@ -12,6 +12,7 @@ #include <cstdint> #include <optional> #include <span> +#include <string_view> #include <utility> #include <variant> #include <vector> @@ -38,46 +39,58 @@ public: return StackID(DefaultStackValue); } - [[nodiscard]] static constexpr StackID safeStack() noexcept { - return StackID(SafeStackValue); - } - - [[nodiscard]] static constexpr StackID shadowCallStack() noexcept { - return StackID(ShadowCallStackValue); + [[nodiscard]] static constexpr StackID sgprSpillStack() noexcept { + return StackID(SGPRSpillStackValue); } [[nodiscard]] static constexpr StackID scalableVectorStack() noexcept { return StackID(ScalableVectorStackValue); } - [[nodiscard]] static Expected<StackID> targetStack(std::uint32_t Value); + [[nodiscard]] static constexpr StackID wasmLocalStack() noexcept { + return StackID(WasmLocalStackValue); + } + + [[nodiscard]] static constexpr StackID scalablePredicateVectorStack() noexcept { + return StackID(ScalablePredicateVectorStackValue); + } + + [[nodiscard]] static constexpr StackID noAllocStack() noexcept { + return StackID(NoAllocStackValue); + } + + [[nodiscard]] static Expected<StackID> fromValue(std::uint32_t Value); [[nodiscard]] constexpr std::uint32_t value() const noexcept { return Value; } - [[nodiscard]] constexpr bool isTargetStack() const noexcept { - return Value >= FirstTargetStackValue; + [[nodiscard]] constexpr bool isSpecialStack() const noexcept { + return Value != DefaultStackValue; } [[nodiscard]] constexpr bool isValid() const noexcept { - return Value <= ScalableVectorStackValue || isTargetStack(); + return Value <= ScalablePredicateVectorStackValue || Value == NoAllocStackValue; } friend constexpr bool operator==(StackID Left, StackID Right) noexcept = default; private: static constexpr std::uint32_t DefaultStackValue = 0; - static constexpr std::uint32_t SafeStackValue = 1; - static constexpr std::uint32_t ShadowCallStackValue = 2; - static constexpr std::uint32_t ScalableVectorStackValue = 3; - static constexpr std::uint32_t FirstTargetStackValue = 256; + static constexpr std::uint32_t SGPRSpillStackValue = 1; + static constexpr std::uint32_t ScalableVectorStackValue = 2; + static constexpr std::uint32_t WasmLocalStackValue = 3; + static constexpr std::uint32_t ScalablePredicateVectorStackValue = 4; + static constexpr std::uint32_t NoAllocStackValue = 255; explicit constexpr StackID(std::uint32_t Value) noexcept : Value(Value) {} std::uint32_t Value = DefaultStackValue; }; +[[nodiscard]] std::optional<StackID> parseStackIDName(std::string_view Name) noexcept; +[[nodiscard]] std::string_view getStackIDName(StackID ID) noexcept; + struct StackObjectLifetimeRange final { MachineInstrStableID Begin; MachineInstrStableID End; diff --git a/backend/include/rcc/CodeGen/MachineMemOperand.h b/backend/include/rcc/CodeGen/MachineMemOperand.h index 3f1fc64..b6cb07f 100644 --- a/backend/include/rcc/CodeGen/MachineMemOperand.h +++ b/backend/include/rcc/CodeGen/MachineMemOperand.h @@ -182,6 +182,7 @@ struct MachineMemOperandDesc final { std::int64_t Offset = 0; std::optional<MemoryOffsetRange> OffsetRange; std::optional<MemoryAliasSourceID> AliasSource; + std::uint16_t TargetFlags = 0; friend bool operator==(const MachineMemOperandDesc &Left, const MachineMemOperandDesc &Right) = default; diff --git a/backend/include/rcc/CodeGen/MachineRegisterInfo.h b/backend/include/rcc/CodeGen/MachineRegisterInfo.h index da0f530..b6485e3 100644 --- a/backend/include/rcc/CodeGen/MachineRegisterInfo.h +++ b/backend/include/rcc/CodeGen/MachineRegisterInfo.h @@ -83,6 +83,7 @@ struct VirtualRegisterInfo final { std::optional<RegisterBankID> Bank; std::optional<RegisterClassID> RegClass; std::vector<PhysicalRegister> AllocationHints; + std::uint8_t TargetFlags = 0; bool IsTombstone = false; }; @@ -123,6 +124,8 @@ public: [[nodiscard]] Expected<void> setRegisterClass(Register Reg, RegisterClassID RegClass); [[nodiscard]] Expected<void> clearRegisterClass(Register Reg); [[nodiscard]] Expected<void> addAllocationHint(Register Reg, PhysicalRegister Hint); + [[nodiscard]] Expected<void> setTargetRegisterFlags(Register Reg, std::uint8_t Flags); + [[nodiscard]] Expected<void> clearTargetRegisterFlags(Register Reg); void clearAllVirtualRegisterTypes() noexcept; [[nodiscard]] Expected<void> diff --git a/backend/include/rcc/CodeGen/TargetDescription.h b/backend/include/rcc/CodeGen/TargetDescription.h index 51a820a..c2a4133 100644 --- a/backend/include/rcc/CodeGen/TargetDescription.h +++ b/backend/include/rcc/CodeGen/TargetDescription.h @@ -4,6 +4,7 @@ #define RCC_CODEGEN_TARGETDESCRIPTION_H #include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" #include "rcc/CodeGen/TargetRegisterInfo.h" #include "rcc/Support/Error.h" @@ -15,6 +16,8 @@ namespace rcc { class MachineBasicBlock; +class MachineCFIInfo; +class MachineFrameInfo; class MachineInstr; class MachineMemOperand; class MachineRegisterInfo; @@ -29,7 +32,8 @@ public: ~TargetDescription() = default; [[nodiscard]] static Expected<TargetDescription> create(InstructionSet Instructions, - TargetRegisterInfo Registers); + TargetRegisterInfo Registers, + TargetMIRTextInfoDesc MIRText = {}); [[nodiscard]] const InstructionSet &instructions() const noexcept { return Instructions; @@ -39,6 +43,10 @@ public: return Registers; } + [[nodiscard]] const TargetMIRTextInfo &mirText() const noexcept { + return MIRText; + } + [[nodiscard]] const InstructionDescriptor * instructionDescriptor(Opcode InstructionOpcode) const noexcept { return Instructions.descriptor(InstructionOpcode); @@ -61,11 +69,11 @@ public: std::span<const MachineOperandTie> Ties) const; [[nodiscard]] Expected<void> validateMemOperands(Opcode InstructionOpcode, - std::span<const MachineMemOperand *const> MemOperands) const { - return Instructions.validateMemOperands(InstructionOpcode, MemOperands); - } + std::span<const MachineMemOperand *const> MemOperands) const; [[nodiscard]] Expected<void> validateInstruction(const MachineInstr &Instruction, const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected<void> validateCFIInfo(const MachineCFIInfo &Info) const; + [[nodiscard]] Expected<void> validateFrameInfo(const MachineFrameInfo &Info) const; [[nodiscard]] Expected<void> validateRegisterInfo(const MachineRegisterInfo &RegInfo) const; [[nodiscard]] Expected<void> validateBlockLiveIns(const MachineBasicBlock &Block) const; @@ -75,8 +83,10 @@ public: } private: - TargetDescription(InstructionSet Instructions, TargetRegisterInfo Registers) - : Instructions(std::move(Instructions)), Registers(std::move(Registers)) {} + TargetDescription(InstructionSet Instructions, TargetRegisterInfo Registers, + TargetMIRTextInfo MIRText) + : Instructions(std::move(Instructions)), Registers(std::move(Registers)), + MIRText(std::move(MIRText)) {} [[nodiscard]] Expected<void> validateTargetOperand(const OperandDescriptor *Descriptor, const MachineOperand &Operand, @@ -91,6 +101,7 @@ private: InstructionSet Instructions; TargetRegisterInfo Registers; + TargetMIRTextInfo MIRText; }; } // namespace rcc diff --git a/backend/include/rcc/CodeGen/TargetMIRTextInfo.h b/backend/include/rcc/CodeGen/TargetMIRTextInfo.h new file mode 100644 index 0000000..4fdbb3a --- /dev/null +++ b/backend/include/rcc/CodeGen/TargetMIRTextInfo.h @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_TARGETMIRTEXTINFO_H +#define RCC_CODEGEN_TARGETMIRTEXTINFO_H + +#include "rcc/CodeGen/MachineFrameInfo.h" +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/Register.h" +#include "rcc/CodeGen/RegisterMask.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <memory> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc { + +class TargetRegisterInfo; + +struct MIRRegisterMaskDesc final { + std::string Name; + std::shared_ptr<const RegisterMask> Mask; +}; + +struct MIRTargetIndexDesc final { + std::string Name; + TargetIndex Index; +}; + +struct MIROperandFlagDesc final { + std::string Name; + std::uint32_t Value = 0; +}; + +struct MIRMachineMemOperandFlagDesc final { + std::string Name; + std::uint16_t Value = 0; +}; + +struct MIRVirtualRegisterFlagDesc final { + std::string Name; + std::uint8_t Value = 0; +}; + +struct MIRIntrinsicDesc final { + std::string Name; + IntrinsicID ID; +}; + +struct MIRCFIRegisterMappingDesc final { + PhysicalRegister Register; + std::uint32_t DwarfRegister = 0; + bool IsCanonical = true; +}; + +struct TargetMIRTextInfoDesc final { + std::vector<MIRRegisterMaskDesc> RegisterMasks; + std::vector<MIRTargetIndexDesc> TargetIndices; + std::uint32_t DirectOperandFlagMask = 0; + std::vector<MIROperandFlagDesc> DirectOperandFlags; + std::vector<MIROperandFlagDesc> BitmaskOperandFlags; + std::vector<MIRMachineMemOperandFlagDesc> MachineMemOperandFlags; + std::vector<MIRVirtualRegisterFlagDesc> VirtualRegisterFlags; + std::vector<MIRIntrinsicDesc> Intrinsics; + std::vector<MIRCFIRegisterMappingDesc> CFIRegisters; + std::vector<StackID> SupportedStackIDs{ + StackID::defaultStack(), + StackID::noAllocStack(), + }; + std::optional<TargetFunctionStateTypeID> FunctionStateType; +}; + +class TargetMIRTextInfo final { +public: + TargetMIRTextInfo(const TargetMIRTextInfo &) = delete; + TargetMIRTextInfo &operator=(const TargetMIRTextInfo &) = delete; + TargetMIRTextInfo(TargetMIRTextInfo &&) noexcept = default; + TargetMIRTextInfo &operator=(TargetMIRTextInfo &&) noexcept = default; + ~TargetMIRTextInfo() = default; + + [[nodiscard]] static Expected<TargetMIRTextInfo> create(TargetMIRTextInfoDesc Description, + const TargetRegisterInfo &Registers); + + [[nodiscard]] std::span<const MIRRegisterMaskDesc> registerMasks() const noexcept { + return Description.RegisterMasks; + } + + [[nodiscard]] std::span<const MIRTargetIndexDesc> targetIndices() const noexcept { + return Description.TargetIndices; + } + + [[nodiscard]] std::span<const MIROperandFlagDesc> directOperandFlags() const noexcept { + return Description.DirectOperandFlags; + } + + [[nodiscard]] std::span<const MIROperandFlagDesc> bitmaskOperandFlags() const noexcept { + return Description.BitmaskOperandFlags; + } + + [[nodiscard]] std::span<const MIRMachineMemOperandFlagDesc> + machineMemOperandFlags() const noexcept { + return Description.MachineMemOperandFlags; + } + + [[nodiscard]] std::span<const MIRVirtualRegisterFlagDesc> virtualRegisterFlags() const noexcept { + return Description.VirtualRegisterFlags; + } + + [[nodiscard]] std::span<const MIRIntrinsicDesc> intrinsics() const noexcept { + return Description.Intrinsics; + } + + [[nodiscard]] std::shared_ptr<const RegisterMask> + registerMask(std::string_view Name) const noexcept; + [[nodiscard]] std::optional<std::string_view> + registerMaskName(const RegisterMask &Mask) const noexcept; + + [[nodiscard]] std::optional<TargetIndex> targetIndex(std::string_view Name) const noexcept; + [[nodiscard]] std::optional<std::string_view> targetIndexName(TargetIndex Index) const noexcept; + + [[nodiscard]] std::optional<std::uint32_t> + directOperandFlag(std::string_view Name) const noexcept; + [[nodiscard]] std::optional<std::uint32_t> + bitmaskOperandFlag(std::string_view Name) const noexcept; + [[nodiscard]] Expected<std::vector<std::string_view>> operandFlagNames(std::uint32_t Flags) const; + [[nodiscard]] Expected<void> validateOperandFlags(std::uint32_t Flags) const; + + [[nodiscard]] std::optional<std::uint16_t> + machineMemOperandFlag(std::string_view Name) const noexcept; + [[nodiscard]] Expected<std::vector<std::string_view>> + machineMemOperandFlagNames(std::uint16_t Flags) const; + [[nodiscard]] Expected<void> validateMachineMemOperandFlags(std::uint16_t Flags) const; + + [[nodiscard]] std::optional<std::uint8_t> + virtualRegisterFlag(std::string_view Name) const noexcept; + [[nodiscard]] Expected<std::vector<std::string_view>> + virtualRegisterFlagNames(std::uint8_t Flags) const; + [[nodiscard]] Expected<void> validateVirtualRegisterFlags(std::uint8_t Flags) const; + + [[nodiscard]] std::optional<IntrinsicID> intrinsic(std::string_view Name) const noexcept; + [[nodiscard]] std::optional<std::string_view> intrinsicName(IntrinsicID ID) const noexcept; + + [[nodiscard]] std::optional<std::uint32_t> + dwarfRegister(PhysicalRegister Register) const noexcept; + [[nodiscard]] std::optional<PhysicalRegister> + physicalRegister(std::uint32_t DwarfRegister) const noexcept; + + [[nodiscard]] std::span<const StackID> supportedStackIDs() const noexcept { + return Description.SupportedStackIDs; + } + + [[nodiscard]] bool supportsStackID(StackID ID) const noexcept { + return SupportedStackIDs.contains(ID.value()); + } + + [[nodiscard]] std::uint32_t directOperandFlagMask() const noexcept { + return Description.DirectOperandFlagMask; + } + + [[nodiscard]] std::uint32_t serializableOperandFlagMask() const noexcept { + return SerializableOperandFlagMask; + } + + [[nodiscard]] std::uint16_t serializableMachineMemOperandFlagMask() const noexcept { + return SerializableMachineMemOperandFlagMask; + } + + [[nodiscard]] std::uint8_t serializableVirtualRegisterFlagMask() const noexcept { + return SerializableVirtualRegisterFlagMask; + } + + [[nodiscard]] std::optional<TargetFunctionStateTypeID> functionStateType() const noexcept { + return Description.FunctionStateType; + } + +private: + explicit TargetMIRTextInfo(TargetMIRTextInfoDesc Description) + : Description(std::move(Description)) {} + + TargetMIRTextInfoDesc Description; + std::unordered_map<std::string, std::size_t> RegisterMaskNameToIndex; + std::unordered_map<std::string, std::size_t> TargetIndexNameToIndex; + std::unordered_map<TargetIndex, std::size_t> TargetIndexToIndex; + std::unordered_map<std::string, std::size_t> DirectOperandFlagNameToIndex; + std::unordered_map<std::uint32_t, std::size_t> DirectOperandFlagToIndex; + std::unordered_map<std::string, std::size_t> BitmaskOperandFlagNameToIndex; + std::unordered_map<std::string, std::size_t> MachineMemOperandFlagNameToIndex; + std::unordered_map<std::string, std::size_t> VirtualRegisterFlagNameToIndex; + std::unordered_map<std::string, std::size_t> IntrinsicNameToIndex; + std::unordered_map<IntrinsicID, std::size_t> IntrinsicToIndex; + std::unordered_map<PhysicalRegister, std::uint32_t> RegisterToDwarf; + std::unordered_map<std::uint32_t, PhysicalRegister> DwarfToRegister; + std::unordered_set<std::uint32_t> SupportedStackIDs; + std::uint32_t SerializableOperandFlagMask = 0; + std::uint16_t SerializableMachineMemOperandFlagMask = 0; + std::uint8_t SerializableVirtualRegisterFlagMask = 0; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_TARGETMIRTEXTINFO_H diff --git a/backend/include/rcc/CodeGen/TargetRegisterInfo.h b/backend/include/rcc/CodeGen/TargetRegisterInfo.h index 5d7de2c..9357eb2 100644 --- a/backend/include/rcc/CodeGen/TargetRegisterInfo.h +++ b/backend/include/rcc/CodeGen/TargetRegisterInfo.h @@ -129,8 +129,18 @@ public: [[nodiscard]] const PhysicalRegisterDesc *physicalRegister(PhysicalRegister Reg) const noexcept; [[nodiscard]] const PhysicalRegisterDesc *physicalRegister(std::string_view Name) const noexcept; [[nodiscard]] const SubRegIndexDesc *subRegIndex(SubRegIndex Index) const noexcept; + [[nodiscard]] const SubRegIndexDesc *subRegIndex(std::string_view Name) const noexcept; [[nodiscard]] const RegisterClassDesc *registerClass(RegisterClassID ID) const noexcept; + [[nodiscard]] const RegisterClassDesc *registerClass(std::string_view Name) const noexcept; [[nodiscard]] const RegisterBankDesc *registerBank(RegisterBankID ID) const noexcept; + [[nodiscard]] const RegisterBankDesc *registerBank(std::string_view Name) const noexcept; + + [[nodiscard]] std::optional<std::string_view> + physicalRegisterMIRName(PhysicalRegister Reg) const noexcept; + [[nodiscard]] std::optional<std::string_view> + registerClassMIRName(RegisterClassID ID) const noexcept; + [[nodiscard]] std::optional<std::string_view> + registerBankMIRName(RegisterBankID ID) const noexcept; [[nodiscard]] bool isKnownPhysicalRegister(PhysicalRegister Reg) const noexcept { return physicalRegister(Reg) != nullptr; @@ -177,14 +187,20 @@ private: std::unordered_map<PhysicalRegister, std::size_t> RegisterToIndex; std::unordered_map<std::string, std::size_t> RegisterNameToIndex; std::unordered_map<SubRegIndex, std::size_t> SubRegIndexToIndex; + std::unordered_map<std::string, std::size_t> SubRegNameToIndex; std::unordered_map<RegisterClassID, std::size_t> ClassToIndex; + std::unordered_map<std::string, std::size_t> ClassNameToIndex; std::unordered_map<RegisterBankID, std::size_t> BankToIndex; + std::unordered_map<std::string, std::size_t> BankNameToIndex; std::unordered_map<std::uint64_t, PhysicalRegister> SubRegisterMap; std::unordered_map<std::uint64_t, SubRegIndex> CompositionMap; std::unordered_map<std::uint64_t, RegisterClassID> SubRegClassMap; std::unordered_set<std::uint64_t> SubclassRelations; std::vector<std::vector<PhysicalRegister>> Aliases; std::vector<std::vector<PhysicalRegister>> SuperRegisters; + std::vector<std::string> RegisterMIRNames; + std::vector<std::string> ClassMIRNames; + std::vector<std::string> BankMIRNames; }; } // namespace rcc diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index d0f725d..ce53cd2 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -32,6 +32,7 @@ set( TargetConstant.cpp TargetDescription.cpp TargetFunctionState.cpp + TargetMIRTextInfo.cpp TargetRegisterInfo.cpp ) set( @@ -67,6 +68,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetConstant.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetDescription.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetFunctionState.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetMIRTextInfo.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/TargetRegisterInfo.h" ) @@ -129,6 +131,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfo.cpp" ${RCCBackendCodeGenHeaders} ) @@ -163,5 +166,6 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstant.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionState.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfo.cpp" ) diff --git a/backend/lib/CodeGen/MachineFrameInfo.cpp b/backend/lib/CodeGen/MachineFrameInfo.cpp index c4d3cda..23f9852 100644 --- a/backend/lib/CodeGen/MachineFrameInfo.cpp +++ b/backend/lib/CodeGen/MachineFrameInfo.cpp @@ -5,17 +5,49 @@ #include <cstddef> #include <cstdint> #include <limits> +#include <string_view> #include <unordered_set> #include <utility> namespace rcc { -Expected<StackID> StackID::targetStack(std::uint32_t Value) { - if (Value < FirstTargetStackValue) { - return makeUnexpected(ErrorCode::InvalidArgument, - "target stack ID must not overlap a built-in stack ID"); - } - return StackID(Value); +Expected<StackID> StackID::fromValue(std::uint32_t Value) { + StackID Result(Value); + if (!Result.isValid()) + return makeUnexpected(ErrorCode::InvalidArgument, "stack ID is not serializable in LLVM MIR"); + return Result; +} + +std::optional<StackID> parseStackIDName(std::string_view Name) noexcept { + if (Name == "default") + return StackID::defaultStack(); + if (Name == "sgpr-spill") + return StackID::sgprSpillStack(); + if (Name == "scalable-vector") + return StackID::scalableVectorStack(); + if (Name == "scalable-predicate-vector") + return StackID::scalablePredicateVectorStack(); + if (Name == "wasm-local") + return StackID::wasmLocalStack(); + if (Name == "noalloc") + return StackID::noAllocStack(); + return std::nullopt; +} + +std::string_view getStackIDName(StackID ID) noexcept { + if (ID == StackID::defaultStack()) + return "default"; + if (ID == StackID::sgprSpillStack()) + return "sgpr-spill"; + if (ID == StackID::scalableVectorStack()) + return "scalable-vector"; + if (ID == StackID::scalablePredicateVectorStack()) + return "scalable-predicate-vector"; + if (ID == StackID::wasmLocalStack()) + return "wasm-local"; + if (ID == StackID::noAllocStack()) + return "noalloc"; + return "unknown"; } bool StackObject::isSpill() const noexcept { @@ -59,10 +91,11 @@ Expected<void> MachineFrameInfo::verifyDescription(const StackObjectDesc &Descri return makeUnexpected(ErrorCode::InvalidArgument, "fixed ABI stack objects require a fixed offset"); } - if (Description.Kind == StackObjectKind::Target && !Description.Stack.isTargetStack() && + if (Description.Kind == StackObjectKind::Target && !Description.Stack.isSpecialStack() && Description.TargetFlags == 0) { - return makeUnexpected(ErrorCode::InvalidArgument, - "target stack objects require a target stack ID or nonzero target flags"); + return makeUnexpected( + ErrorCode::InvalidArgument, + "target stack objects require a special stack ID or nonzero target flags"); } std::unordered_set<MachineInstrStableID> LifetimeBoundaries; diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp index b769f7d..7dcf5e2 100644 --- a/backend/lib/CodeGen/MachineFunctionCloner.cpp +++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp @@ -214,6 +214,12 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { if (!SetResult) return std::unexpected(SetResult.error()); } + if (SourceInfo.TargetFlags != 0) { + auto SetResult = State.Destination.registerInfo().setTargetRegisterFlags( + *DestinationReg, SourceInfo.TargetFlags); + if (!SetResult) + return std::unexpected(SetResult.error()); + } for (const PhysicalRegister Hint : SourceInfo.AllocationHints) { auto AddResult = State.Destination.registerInfo().addAllocationHint(*DestinationReg, Hint); if (!AddResult) diff --git a/backend/lib/CodeGen/MachineRegisterInfo.cpp b/backend/lib/CodeGen/MachineRegisterInfo.cpp index c31b38b..3784b22 100644 --- a/backend/lib/CodeGen/MachineRegisterInfo.cpp +++ b/backend/lib/CodeGen/MachineRegisterInfo.cpp @@ -94,6 +94,7 @@ Expected<void> MachineRegisterInfo::eraseVirtualRegister(Register Reg) { Info->get().Bank.reset(); Info->get().RegClass.reset(); Info->get().AllocationHints.clear(); + Info->get().TargetFlags = 0; return {}; } @@ -188,6 +189,18 @@ Expected<void> MachineRegisterInfo::addAllocationHint(Register Reg, PhysicalRegi return {}; } +Expected<void> MachineRegisterInfo::setTargetRegisterFlags(Register Reg, std::uint8_t Flags) { + auto Info = virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + Info->get().TargetFlags = Flags; + return {}; +} + +Expected<void> MachineRegisterInfo::clearTargetRegisterFlags(Register Reg) { + return setTargetRegisterFlags(Reg, 0); +} + void MachineRegisterInfo::clearAllVirtualRegisterTypes() noexcept { for (VirtualRegisterInfo &Info : VirtualRegisters) { if (!Info.IsTombstone) diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp index 98fb9f7..fd027fe 100644 --- a/backend/lib/CodeGen/MachineVerifier.cpp +++ b/backend/lib/CodeGen/MachineVerifier.cpp @@ -43,10 +43,17 @@ public: auto CFIResult = Function.cfiInfo().verify(); if (!CFIResult) return failWith("CFI information", CFIResult.error()); + auto TargetCFIResult = Target.validateCFIInfo(Function.cfiInfo()); + if (!TargetCFIResult) + return failWith("target CFI information", TargetCFIResult.error()); if (Function.targetState().hasValue()) { auto TargetStateResult = Function.targetState().verify(); if (!TargetStateResult) return failWith("target function state", TargetStateResult.error()); + const auto RequiredType = Target.mirText().functionStateType(); + if (RequiredType && Function.targetState().typeID() != RequiredType) { + return fail("target function state does not match the target MIR schema"); + } } auto CatalogResult = catalogArenaObjects(); if (!CatalogResult) @@ -54,6 +61,9 @@ public: auto FrameResult = Function.frameInfo().verify(); if (!FrameResult) return failWith("frame info", FrameResult.error()); + auto TargetFrameResult = Target.validateFrameInfo(Function.frameInfo()); + if (!TargetFrameResult) + return failWith("target frame info", TargetFrameResult.error()); auto ConstantPoolResult = Function.constantPool().verify(); if (!ConstantPoolResult) return failWith("constant pool", ConstantPoolResult.error()); @@ -652,7 +662,8 @@ private: for (const VirtualRegisterInfo &Info : RegInfo.virtualRegisters()) { if (Info.IsTombstone) { - if (Info.Type || Info.Bank || Info.RegClass || !Info.AllocationHints.empty()) { + if (Info.Type || Info.Bank || Info.RegClass || !Info.AllocationHints.empty() || + Info.TargetFlags != 0) { return fail("virtual-register tombstone retains semantic state"); } continue; diff --git a/backend/lib/CodeGen/TargetDescription.cpp b/backend/lib/CodeGen/TargetDescription.cpp index a32534d..b9f7606 100644 --- a/backend/lib/CodeGen/TargetDescription.cpp +++ b/backend/lib/CodeGen/TargetDescription.cpp @@ -4,7 +4,10 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineCFIInfo.h" +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -47,9 +50,19 @@ namespace { } // namespace Expected<TargetDescription> TargetDescription::create(InstructionSet Instructions, - TargetRegisterInfo Registers) { + TargetRegisterInfo Registers, + TargetMIRTextInfoDesc MIRTextDescription) { + auto MIRText = TargetMIRTextInfo::create(std::move(MIRTextDescription), Registers); + if (!MIRText) + return std::unexpected(MIRText.error()); + for (const InstructionDescriptor &Instruction : Instructions.descriptors()) { for (const OperandDescriptor &Operand : Instruction.Operands) { + if ((Operand.AllowedTargetFlags & ~MIRText->serializableOperandFlagMask()) != 0) { + return makeUnexpected( + ErrorCode::InvalidArgument, + "instruction operand allows a target flag outside the MIR text schema"); + } if (Operand.RegClass && Registers.registerClass(*Operand.RegClass) == nullptr) { return makeUnexpected(ErrorCode::InvalidArgument, "instruction operand references an unknown target register class"); @@ -103,7 +116,7 @@ Expected<TargetDescription> TargetDescription::create(InstructionSet Instruction } } - return TargetDescription(std::move(Instructions), std::move(Registers)); + return TargetDescription(std::move(Instructions), std::move(Registers), std::move(*MIRText)); } Expected<void> TargetDescription::validateVirtualRegister(Register Reg, @@ -113,6 +126,10 @@ Expected<void> TargetDescription::validateVirtualRegister(Register Reg, "target virtual-register validation received a non-virtual register"); } + auto TargetFlagValidation = MIRText.validateVirtualRegisterFlags(Info.TargetFlags); + if (!TargetFlagValidation) + return std::unexpected(TargetFlagValidation.error()); + const RegisterBankDesc *Bank = nullptr; if (Info.Bank) { Bank = Registers.registerBank(*Info.Bank); @@ -196,6 +213,21 @@ TargetDescription::effectiveRegisterClass(const MachineOperand &Operand, Expected<void> TargetDescription::validateTargetOperand(const OperandDescriptor *Descriptor, const MachineOperand &Operand, const MachineRegisterInfo &RegInfo) const { + auto TargetFlagValidation = MIRText.validateOperandFlags(Operand.targetFlags()); + if (!TargetFlagValidation) + return std::unexpected(TargetFlagValidation.error()); + + if (const auto *Index = Operand.getIf<TargetIndexOperand>(); + Index != nullptr && !MIRText.targetIndexName(Index->Index)) { + return makeUnexpected(ErrorCode::VerificationError, + "target-index operand is not registered for MIR serialization"); + } + if (const auto *Intrinsic = Operand.getIf<IntrinsicOperand>(); + Intrinsic != nullptr && !MIRText.intrinsicName(Intrinsic->ID)) { + return makeUnexpected(ErrorCode::VerificationError, + "intrinsic operand is not registered for MIR serialization"); + } + if (const auto *Mask = Operand.getIf<RegisterMaskOperand>(); Mask != nullptr) { if (!Mask->Mask) { return makeUnexpected(ErrorCode::VerificationError, @@ -301,6 +333,104 @@ Expected<void> TargetDescription::validateTargetOperand(const OperandDescriptor return {}; } +Expected<void> TargetDescription::validateMemOperands( + Opcode InstructionOpcode, std::span<const MachineMemOperand *const> MemOperands) const { + auto DescriptorValidation = Instructions.validateMemOperands(InstructionOpcode, MemOperands); + if (!DescriptorValidation) + return std::unexpected(DescriptorValidation.error()); + for (const MachineMemOperand *MemOperand : MemOperands) { + if (MemOperand == nullptr) + continue; + auto TargetFlagValidation = + MIRText.validateMachineMemOperandFlags(MemOperand->description().TargetFlags); + if (!TargetFlagValidation) + return std::unexpected(TargetFlagValidation.error()); + } + return {}; +} + +Expected<void> TargetDescription::validateCFIInfo(const MachineCFIInfo &Info) const { + auto ValidateRegister = [this](DwarfRegisterNumber Register) -> Expected<void> { + if (!MIRText.physicalRegister(Register.value())) { + return makeUnexpected(ErrorCode::VerificationError, + "CFI instruction references an unmapped DWARF register"); + } + return {}; + }; + + for (const MachineCFIInstruction &Instruction : Info.instructions()) { + if (const auto *Rule = Instruction.getIf<MachineCFIRegisterRule>()) { + auto Result = ValidateRegister(Rule->Register); + if (!Result) + return Result; + } + if (const auto *Rule = Instruction.getIf<MachineCFIRegisterOffsetRule>()) { + auto Result = ValidateRegister(Rule->Register); + if (!Result) + return Result; + } + if (const auto *Rule = Instruction.getIf<MachineCFIRegisterRegisterRule>()) { + auto RegisterResult = ValidateRegister(Rule->Register); + if (!RegisterResult) + return RegisterResult; + auto SourceResult = ValidateRegister(Rule->SourceRegister); + if (!SourceResult) + return SourceResult; + } + if (const auto *Rule = Instruction.getIf<MachineCFIAddressSpaceCFARule>()) { + auto Result = ValidateRegister(Rule->Register); + if (!Result) + return Result; + } + if (const auto *Rule = Instruction.getIf<MachineCFIRegisterPairRule>()) { + for (const DwarfRegisterNumber Register : + {Rule->Register, Rule->FirstRegister, Rule->SecondRegister}) { + auto Result = ValidateRegister(Register); + if (!Result) + return Result; + } + } + if (const auto *Rule = Instruction.getIf<MachineCFIVectorRegistersRule>()) { + auto Result = ValidateRegister(Rule->Register); + if (!Result) + return Result; + for (const MachineCFIVectorRegisterLane &Lane : Rule->VectorRegisters) { + Result = ValidateRegister(Lane.Register); + if (!Result) + return Result; + } + } + if (const auto *Rule = Instruction.getIf<MachineCFIVectorOffsetRule>()) { + auto RegisterResult = ValidateRegister(Rule->Register); + if (!RegisterResult) + return RegisterResult; + auto MaskResult = ValidateRegister(Rule->MaskRegister); + if (!MaskResult) + return MaskResult; + } + if (const auto *Rule = Instruction.getIf<MachineCFIVectorRegisterMaskRule>()) { + for (const DwarfRegisterNumber Register : + {Rule->Register, Rule->SpillRegister, Rule->MaskRegister}) { + auto Result = ValidateRegister(Register); + if (!Result) + return Result; + } + } + } + return {}; +} + +Expected<void> TargetDescription::validateFrameInfo(const MachineFrameInfo &Info) const { + for (const StackObject &Object : Info.objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description != nullptr && !MIRText.supportsStackID(Description->Stack)) { + return makeUnexpected(ErrorCode::VerificationError, + "stack object uses a stack ID unsupported by the target"); + } + } + return {}; +} + Expected<void> TargetDescription::validateExplicitOperands(Opcode InstructionOpcode, std::span<const MachineOperand> Operands, diff --git a/backend/lib/CodeGen/TargetMIRTextInfo.cpp b/backend/lib/CodeGen/TargetMIRTextInfo.cpp new file mode 100644 index 0000000..a349432 --- /dev/null +++ b/backend/lib/CodeGen/TargetMIRTextInfo.cpp @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetMIRTextInfo.h" + +#include "rcc/CodeGen/TargetRegisterInfo.h" + +#include <bit> +#include <cstddef> +#include <cstdint> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc { +namespace { + +[[nodiscard]] std::unexpected<Error> invalidDescription(std::string Message) { + return makeUnexpected(ErrorCode::InvalidArgument, + "target MIR text description: " + std::move(Message)); +} + +[[nodiscard]] bool isASCIIAlpha(char Character) noexcept { + return (Character >= 'a' && Character <= 'z') || (Character >= 'A' && Character <= 'Z'); +} + +[[nodiscard]] bool isASCIIDigit(char Character) noexcept { + return Character >= '0' && Character <= '9'; +} + +[[nodiscard]] bool isMIRIdentifier(std::string_view Name) noexcept { + if (Name.empty() || (!isASCIIAlpha(Name.front()) && Name.front() != '_')) + return false; + for (const char Character : Name) { + if (!isASCIIAlpha(Character) && !isASCIIDigit(Character) && Character != '_' && + Character != '-' && Character != '.' && Character != '$') { + return false; + } + } + return true; +} + +[[nodiscard]] std::string lowerASCII(std::string_view Name) { + std::string Result(Name); + for (char &Character : Result) { + if (Character >= 'A' && Character <= 'Z') + Character = static_cast<char>(Character - 'A' + 'a'); + } + return Result; +} + +} // namespace + +Expected<TargetMIRTextInfo> TargetMIRTextInfo::create(TargetMIRTextInfoDesc Description, + const TargetRegisterInfo &Registers) { + TargetMIRTextInfo Result(std::move(Description)); + + for (std::size_t Index = 0; Index < Result.Description.RegisterMasks.size(); ++Index) { + MIRRegisterMaskDesc &Mask = Result.Description.RegisterMasks[Index]; + Mask.Name = lowerASCII(Mask.Name); + if (!isMIRIdentifier(Mask.Name) || Mask.Mask == nullptr) { + return invalidDescription("named register masks require an identifier and nonnull mask"); + } + auto Validation = Registers.validateRegisterMask(*Mask.Mask); + if (!Validation) + return invalidDescription("named register mask contains an unknown physical register"); + if (!Result.RegisterMaskNameToIndex.emplace(Mask.Name, Index).second) { + return invalidDescription("register mask names must be unique case-insensitively"); + } + for (std::size_t Previous = 0; Previous < Index; ++Previous) { + if (*Result.Description.RegisterMasks[Previous].Mask == *Mask.Mask) { + return invalidDescription("named register mask values must have one canonical name"); + } + } + } + + for (std::size_t Index = 0; Index < Result.Description.TargetIndices.size(); ++Index) { + const MIRTargetIndexDesc &TargetIndex = Result.Description.TargetIndices[Index]; + if (!isMIRIdentifier(TargetIndex.Name)) { + return invalidDescription("target index names must be MIR identifiers"); + } + if (!Result.TargetIndexNameToIndex.emplace(TargetIndex.Name, Index).second || + !Result.TargetIndexToIndex.emplace(TargetIndex.Index, Index).second) { + return invalidDescription("target index names and values must be unique"); + } + } + + if (Result.Description.DirectOperandFlags.empty() != + (Result.Description.DirectOperandFlagMask == 0)) { + return invalidDescription( + "direct operand flags and the direct operand flag mask must be present together"); + } + + std::unordered_set<std::string> OperandFlagNames; + for (std::size_t Index = 0; Index < Result.Description.DirectOperandFlags.size(); ++Index) { + const MIROperandFlagDesc &Flag = Result.Description.DirectOperandFlags[Index]; + if (!isMIRIdentifier(Flag.Name) || Flag.Value == 0 || + (Flag.Value & ~Result.Description.DirectOperandFlagMask) != 0) { + return invalidDescription( + "direct operand flags require an identifier and a nonzero value inside the direct mask"); + } + if (!OperandFlagNames.insert(Flag.Name).second || + !Result.DirectOperandFlagNameToIndex.emplace(Flag.Name, Index).second || + !Result.DirectOperandFlagToIndex.emplace(Flag.Value, Index).second) { + return invalidDescription("direct operand flag names and values must be unique"); + } + } + + std::uint32_t BitmaskOperandFlags = 0; + for (std::size_t Index = 0; Index < Result.Description.BitmaskOperandFlags.size(); ++Index) { + const MIROperandFlagDesc &Flag = Result.Description.BitmaskOperandFlags[Index]; + if (!isMIRIdentifier(Flag.Name) || !std::has_single_bit(Flag.Value) || + (Flag.Value & Result.Description.DirectOperandFlagMask) != 0) { + return invalidDescription( + "bitmask operand flags require an identifier and one bit outside the direct mask"); + } + if (!OperandFlagNames.insert(Flag.Name).second || + !Result.BitmaskOperandFlagNameToIndex.emplace(Flag.Name, Index).second || + (BitmaskOperandFlags & Flag.Value) != 0) { + return invalidDescription("operand flag names and bitmask values must be unique"); + } + BitmaskOperandFlags |= Flag.Value; + } + Result.SerializableOperandFlagMask = + Result.Description.DirectOperandFlagMask | BitmaskOperandFlags; + + for (std::size_t Index = 0; Index < Result.Description.MachineMemOperandFlags.size(); ++Index) { + const MIRMachineMemOperandFlagDesc &Flag = Result.Description.MachineMemOperandFlags[Index]; + if (!isMIRIdentifier(Flag.Name) || !std::has_single_bit(Flag.Value)) { + return invalidDescription( + "machine memory operand flags require an identifier and a single-bit value"); + } + if (!Result.MachineMemOperandFlagNameToIndex.emplace(Flag.Name, Index).second || + (Result.SerializableMachineMemOperandFlagMask & Flag.Value) != 0) { + return invalidDescription("machine memory operand flag names and values must be unique"); + } + Result.SerializableMachineMemOperandFlagMask |= Flag.Value; + } + + for (std::size_t Index = 0; Index < Result.Description.VirtualRegisterFlags.size(); ++Index) { + const MIRVirtualRegisterFlagDesc &Flag = Result.Description.VirtualRegisterFlags[Index]; + if (!isMIRIdentifier(Flag.Name) || !std::has_single_bit(Flag.Value)) { + return invalidDescription( + "virtual register flags require an identifier and a single-bit value"); + } + if (!Result.VirtualRegisterFlagNameToIndex.emplace(Flag.Name, Index).second || + (Result.SerializableVirtualRegisterFlagMask & Flag.Value) != 0) { + return invalidDescription("virtual register flag names and values must be unique"); + } + Result.SerializableVirtualRegisterFlagMask |= Flag.Value; + } + + for (std::size_t Index = 0; Index < Result.Description.Intrinsics.size(); ++Index) { + const MIRIntrinsicDesc &Intrinsic = Result.Description.Intrinsics[Index]; + if (!isMIRIdentifier(Intrinsic.Name) || !Intrinsic.Name.starts_with("llvm.") || + Intrinsic.ID.value() == 0) { + return invalidDescription("intrinsics require an llvm-prefixed MIR name and a nonzero ID"); + } + if (!Result.IntrinsicNameToIndex.emplace(Intrinsic.Name, Index).second || + !Result.IntrinsicToIndex.emplace(Intrinsic.ID, Index).second) { + return invalidDescription("intrinsic names and IDs must be unique"); + } + } + + std::unordered_set<std::uint32_t> ReferencedDwarfRegisters; + for (const MIRCFIRegisterMappingDesc &Mapping : Result.Description.CFIRegisters) { + if (!Registers.isKnownPhysicalRegister(Mapping.Register) || + !Result.RegisterToDwarf.emplace(Mapping.Register, Mapping.DwarfRegister).second) { + return invalidDescription("CFI register mappings require unique, known physical registers"); + } + ReferencedDwarfRegisters.insert(Mapping.DwarfRegister); + if (Mapping.IsCanonical && + !Result.DwarfToRegister.emplace(Mapping.DwarfRegister, Mapping.Register).second) { + return invalidDescription( + "each DWARF register must have exactly one canonical physical register"); + } + } + if (ReferencedDwarfRegisters.size() != Result.DwarfToRegister.size()) { + return invalidDescription( + "every referenced DWARF register requires a canonical physical register"); + } + + for (const StackID ID : Result.Description.SupportedStackIDs) { + if (!ID.isValid() || !Result.SupportedStackIDs.insert(ID.value()).second) + return invalidDescription("supported stack IDs must be valid and unique"); + } + if (!Result.SupportedStackIDs.contains(StackID::defaultStack().value()) || + !Result.SupportedStackIDs.contains(StackID::noAllocStack().value())) { + return invalidDescription("targets must support the default and noalloc stack IDs"); + } + + if (Result.Description.FunctionStateType && Result.Description.FunctionStateType->value() == 0) { + return invalidDescription("target function state TypeID must be nonzero"); + } + + return Result; +} + +std::shared_ptr<const RegisterMask> +TargetMIRTextInfo::registerMask(std::string_view Name) const noexcept { + const auto Position = RegisterMaskNameToIndex.find(std::string(Name)); + if (Position == RegisterMaskNameToIndex.end()) + return {}; + return Description.RegisterMasks[Position->second].Mask; +} + +std::optional<std::string_view> +TargetMIRTextInfo::registerMaskName(const RegisterMask &Mask) const noexcept { + for (const MIRRegisterMaskDesc &NamedMask : Description.RegisterMasks) { + if (*NamedMask.Mask == Mask) + return NamedMask.Name; + } + return std::nullopt; +} + +std::optional<TargetIndex> TargetMIRTextInfo::targetIndex(std::string_view Name) const noexcept { + const auto Position = TargetIndexNameToIndex.find(std::string(Name)); + if (Position == TargetIndexNameToIndex.end()) + return std::nullopt; + return Description.TargetIndices[Position->second].Index; +} + +std::optional<std::string_view> +TargetMIRTextInfo::targetIndexName(TargetIndex Index) const noexcept { + const auto Position = TargetIndexToIndex.find(Index); + if (Position == TargetIndexToIndex.end()) + return std::nullopt; + return Description.TargetIndices[Position->second].Name; +} + +std::optional<std::uint32_t> +TargetMIRTextInfo::directOperandFlag(std::string_view Name) const noexcept { + const auto Position = DirectOperandFlagNameToIndex.find(std::string(Name)); + if (Position == DirectOperandFlagNameToIndex.end()) + return std::nullopt; + return Description.DirectOperandFlags[Position->second].Value; +} + +std::optional<std::uint32_t> +TargetMIRTextInfo::bitmaskOperandFlag(std::string_view Name) const noexcept { + const auto Position = BitmaskOperandFlagNameToIndex.find(std::string(Name)); + if (Position == BitmaskOperandFlagNameToIndex.end()) + return std::nullopt; + return Description.BitmaskOperandFlags[Position->second].Value; +} + +Expected<void> TargetMIRTextInfo::validateOperandFlags(std::uint32_t Flags) const { + if ((Flags & ~SerializableOperandFlagMask) != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "machine operand contains an unknown target flag bit"); + } + const std::uint32_t DirectFlag = Flags & Description.DirectOperandFlagMask; + if (DirectFlag != 0 && !DirectOperandFlagToIndex.contains(DirectFlag)) { + return makeUnexpected(ErrorCode::VerificationError, + "machine operand contains an unregistered direct target flag"); + } + return {}; +} + +Expected<std::vector<std::string_view>> +TargetMIRTextInfo::operandFlagNames(std::uint32_t Flags) const { + auto Validation = validateOperandFlags(Flags); + if (!Validation) + return std::unexpected(Validation.error()); + + std::vector<std::string_view> Names; + const std::uint32_t DirectFlag = Flags & Description.DirectOperandFlagMask; + if (DirectFlag != 0) { + Names.push_back(Description.DirectOperandFlags[DirectOperandFlagToIndex.at(DirectFlag)].Name); + } + for (const MIROperandFlagDesc &Flag : Description.BitmaskOperandFlags) { + if ((Flags & Flag.Value) != 0) + Names.push_back(Flag.Name); + } + return Names; +} + +std::optional<std::uint16_t> +TargetMIRTextInfo::machineMemOperandFlag(std::string_view Name) const noexcept { + const auto Position = MachineMemOperandFlagNameToIndex.find(std::string(Name)); + if (Position == MachineMemOperandFlagNameToIndex.end()) + return std::nullopt; + return Description.MachineMemOperandFlags[Position->second].Value; +} + +Expected<void> TargetMIRTextInfo::validateMachineMemOperandFlags(std::uint16_t Flags) const { + if ((Flags & static_cast<std::uint16_t>(~SerializableMachineMemOperandFlagMask)) != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "machine memory operand contains an unknown target flag"); + } + return {}; +} + +Expected<std::vector<std::string_view>> +TargetMIRTextInfo::machineMemOperandFlagNames(std::uint16_t Flags) const { + auto Validation = validateMachineMemOperandFlags(Flags); + if (!Validation) + return std::unexpected(Validation.error()); + + std::vector<std::string_view> Names; + for (const MIRMachineMemOperandFlagDesc &Flag : Description.MachineMemOperandFlags) { + if ((Flags & Flag.Value) != 0) + Names.push_back(Flag.Name); + } + return Names; +} + +std::optional<std::uint8_t> +TargetMIRTextInfo::virtualRegisterFlag(std::string_view Name) const noexcept { + const auto Position = VirtualRegisterFlagNameToIndex.find(std::string(Name)); + if (Position == VirtualRegisterFlagNameToIndex.end()) + return std::nullopt; + return Description.VirtualRegisterFlags[Position->second].Value; +} + +Expected<void> TargetMIRTextInfo::validateVirtualRegisterFlags(std::uint8_t Flags) const { + if ((Flags & static_cast<std::uint8_t>(~SerializableVirtualRegisterFlagMask)) != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "virtual register contains an unknown target flag"); + } + return {}; +} + +Expected<std::vector<std::string_view>> +TargetMIRTextInfo::virtualRegisterFlagNames(std::uint8_t Flags) const { + auto Validation = validateVirtualRegisterFlags(Flags); + if (!Validation) + return std::unexpected(Validation.error()); + + std::vector<std::string_view> Names; + for (const MIRVirtualRegisterFlagDesc &Flag : Description.VirtualRegisterFlags) { + if ((Flags & Flag.Value) != 0) + Names.push_back(Flag.Name); + } + return Names; +} + +std::optional<IntrinsicID> TargetMIRTextInfo::intrinsic(std::string_view Name) const noexcept { + const auto Position = IntrinsicNameToIndex.find(std::string(Name)); + if (Position == IntrinsicNameToIndex.end()) + return std::nullopt; + return Description.Intrinsics[Position->second].ID; +} + +std::optional<std::string_view> TargetMIRTextInfo::intrinsicName(IntrinsicID ID) const noexcept { + const auto Position = IntrinsicToIndex.find(ID); + if (Position == IntrinsicToIndex.end()) + return std::nullopt; + return Description.Intrinsics[Position->second].Name; +} + +std::optional<std::uint32_t> +TargetMIRTextInfo::dwarfRegister(PhysicalRegister Register) const noexcept { + const auto Position = RegisterToDwarf.find(Register); + if (Position == RegisterToDwarf.end()) + return std::nullopt; + return Position->second; +} + +std::optional<PhysicalRegister> +TargetMIRTextInfo::physicalRegister(std::uint32_t DwarfRegister) const noexcept { + const auto Position = DwarfToRegister.find(DwarfRegister); + if (Position == DwarfToRegister.end()) + return std::nullopt; + return Position->second; +} + +} // namespace rcc diff --git a/backend/lib/CodeGen/TargetRegisterInfo.cpp b/backend/lib/CodeGen/TargetRegisterInfo.cpp index 7c8af82..3ece639 100644 --- a/backend/lib/CodeGen/TargetRegisterInfo.cpp +++ b/backend/lib/CodeGen/TargetRegisterInfo.cpp @@ -26,6 +26,15 @@ namespace { return Value != 0 && std::has_single_bit(Value); } +[[nodiscard]] std::string lowerASCII(std::string_view Name) { + std::string Result(Name); + for (char &Character : Result) { + if (Character >= 'A' && Character <= 'Z') + Character = static_cast<char>(Character - 'A' + 'a'); + } + return Result; +} + template <typename RangeT, typename ValueT> [[nodiscard]] bool contains(const RangeT &Range, const ValueT &Value) { return std::ranges::find(Range, Value) != std::ranges::end(Range); @@ -64,10 +73,13 @@ Expected<TargetRegisterInfo> TargetRegisterInfo::create(TargetRegisterInfoDesc D if (!Register.Reg.isValid() || Register.Name.empty()) { return invalidDescription("physical register requires a valid number and name"); } + std::string MIRName = lowerASCII(Register.Name); if (!Result.RegisterToIndex.emplace(Register.Reg, Index).second || - !Result.RegisterNameToIndex.emplace(Register.Name, Index).second) { - return invalidDescription("physical register numbers and names must be unique"); + !Result.RegisterNameToIndex.emplace(MIRName, Index).second) { + return invalidDescription( + "physical register numbers and names must be unique case-insensitively"); } + Result.RegisterMIRNames.push_back(std::move(MIRName)); } std::unordered_set<std::string> SubRegNames; @@ -81,32 +93,36 @@ Expected<TargetRegisterInfo> TargetRegisterInfo::create(TargetRegisterInfoDesc D "subregister index requires a nonzero ID, name, lanes, and valid bit range"); } if (!Result.SubRegIndexToIndex.emplace(SubReg.ID, Index).second || - !SubRegNames.insert(SubReg.Name).second) { + !SubRegNames.insert(SubReg.Name).second || + !Result.SubRegNameToIndex.emplace(SubReg.Name, Index).second) { return invalidDescription("subregister index IDs and names must be unique"); } } - std::unordered_set<std::string> ClassNames; for (std::size_t Index = 0; Index < Result.Description.RegisterClasses.size(); ++Index) { const RegisterClassDesc &Class = Result.Description.RegisterClasses[Index]; if (Class.ID.value() == 0 || Class.Name.empty()) { return invalidDescription("register class requires a nonzero ID and name"); } + std::string MIRName = lowerASCII(Class.Name); if (!Result.ClassToIndex.emplace(Class.ID, Index).second || - !ClassNames.insert(Class.Name).second) { - return invalidDescription("register class IDs and names must be unique"); + !Result.ClassNameToIndex.emplace(MIRName, Index).second) { + return invalidDescription("register class IDs and names must be unique case-insensitively"); } + Result.ClassMIRNames.push_back(std::move(MIRName)); } - std::unordered_set<std::string> BankNames; for (std::size_t Index = 0; Index < Result.Description.RegisterBanks.size(); ++Index) { const RegisterBankDesc &Bank = Result.Description.RegisterBanks[Index]; if (Bank.ID.value() == 0 || Bank.Name.empty() || Bank.MaximumSizeInBits == 0) { return invalidDescription("register bank requires a nonzero ID, name, and maximum size"); } - if (!Result.BankToIndex.emplace(Bank.ID, Index).second || !BankNames.insert(Bank.Name).second) { - return invalidDescription("register bank IDs and names must be unique"); + std::string MIRName = lowerASCII(Bank.Name); + if (!Result.BankToIndex.emplace(Bank.ID, Index).second || + !Result.BankNameToIndex.emplace(MIRName, Index).second) { + return invalidDescription("register bank IDs and names must be unique case-insensitively"); } + Result.BankMIRNames.push_back(std::move(MIRName)); } std::unordered_set<RegisterUnitID> ReferencedUnits; @@ -443,16 +459,57 @@ const SubRegIndexDesc *TargetRegisterInfo::subRegIndex(SubRegIndex Index) const : &Description.SubRegIndices[Position->second]; } +const SubRegIndexDesc *TargetRegisterInfo::subRegIndex(std::string_view Name) const noexcept { + const auto Position = SubRegNameToIndex.find(std::string(Name)); + return Position == SubRegNameToIndex.end() ? nullptr + : &Description.SubRegIndices[Position->second]; +} + const RegisterClassDesc *TargetRegisterInfo::registerClass(RegisterClassID ID) const noexcept { const auto Position = ClassToIndex.find(ID); return Position == ClassToIndex.end() ? nullptr : &Description.RegisterClasses[Position->second]; } +const RegisterClassDesc *TargetRegisterInfo::registerClass(std::string_view Name) const noexcept { + const auto Position = ClassNameToIndex.find(std::string(Name)); + return Position == ClassNameToIndex.end() ? nullptr + : &Description.RegisterClasses[Position->second]; +} + const RegisterBankDesc *TargetRegisterInfo::registerBank(RegisterBankID ID) const noexcept { const auto Position = BankToIndex.find(ID); return Position == BankToIndex.end() ? nullptr : &Description.RegisterBanks[Position->second]; } +const RegisterBankDesc *TargetRegisterInfo::registerBank(std::string_view Name) const noexcept { + const auto Position = BankNameToIndex.find(std::string(Name)); + return Position == BankNameToIndex.end() ? nullptr : &Description.RegisterBanks[Position->second]; +} + +std::optional<std::string_view> +TargetRegisterInfo::physicalRegisterMIRName(PhysicalRegister Reg) const noexcept { + const auto Position = RegisterToIndex.find(Reg); + if (Position == RegisterToIndex.end()) + return std::nullopt; + return RegisterMIRNames[Position->second]; +} + +std::optional<std::string_view> +TargetRegisterInfo::registerClassMIRName(RegisterClassID ID) const noexcept { + const auto Position = ClassToIndex.find(ID); + if (Position == ClassToIndex.end()) + return std::nullopt; + return ClassMIRNames[Position->second]; +} + +std::optional<std::string_view> +TargetRegisterInfo::registerBankMIRName(RegisterBankID ID) const noexcept { + const auto Position = BankToIndex.find(ID); + if (Position == BankToIndex.end()) + return std::nullopt; + return BankMIRNames[Position->second]; +} + LaneMask TargetRegisterInfo::registerLaneMask(PhysicalRegister Reg) const noexcept { const PhysicalRegisterDesc *Register = physicalRegister(Reg); if (Register == nullptr) diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 69f3646..ef96358 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -22,6 +22,7 @@ set( MachineValueTypesTest.cpp TargetConstantTest.cpp TargetFunctionStateTest.cpp + TargetMIRTextInfoTest.cpp TargetRegisterInfoTest.cpp ) @@ -253,6 +254,18 @@ add_test( COMMAND RCCBackendCodeGenTargetFunctionStateTests ) +add_executable(RCCBackendCodeGenTargetMIRTextInfoTests TargetMIRTextInfoTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenTargetMIRTextInfoTests) +target_link_libraries( + RCCBackendCodeGenTargetMIRTextInfoTests + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget +) + +add_test( + NAME RCCBackend.CodeGen.TargetMIRTextInfo + COMMAND RCCBackendCodeGenTargetMIRTextInfoTests +) + add_executable(RCCBackendCodeGenTargetRegisterInfoTests TargetRegisterInfoTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenTargetRegisterInfoTests) target_link_libraries( @@ -286,6 +299,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp" ) rcc_backend_register_tidy_files( @@ -309,5 +323,6 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetMIRTextInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp" ) diff --git a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp index c9289c7..c67339d 100644 --- a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp +++ b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp @@ -149,15 +149,24 @@ void testInvalidDescriptions(TestContext &Context) { Frame.object(*DebugIndex)->description()->DebugInfo == DebugObject.DebugInfo, "stack object debug information must remain structured"); - Context.expect(!rcc::StackID::targetStack(4), "target stack IDs must not overlap built-in IDs"); - auto TargetStack = rcc::StackID::targetStack(256); + Context.expect( + rcc::StackID::fromValue(0) && rcc::StackID::fromValue(4) && rcc::StackID::fromValue(255) && + !rcc::StackID::fromValue(5) && !rcc::StackID::fromValue(256) && + rcc::parseStackIDName("sgpr-spill") == rcc::StackID::sgprSpillStack() && + rcc::parseStackIDName("scalable-vector") == rcc::StackID::scalableVectorStack() && + rcc::parseStackIDName("scalable-predicate-vector") == + rcc::StackID::scalablePredicateVectorStack() && + rcc::parseStackIDName("wasm-local") == rcc::StackID::wasmLocalStack() && + rcc::parseStackIDName("noalloc") == rcc::StackID::noAllocStack() && + !rcc::parseStackIDName("safe") && + rcc::getStackIDName(rcc::StackID::noAllocStack()) == "noalloc", + "stack IDs and names must match the fixed LLVM MIR enumeration"); + auto Target = fixedObject(8, 8); Target.Kind = rcc::StackObjectKind::Target; - if (TargetStack) - Target.Stack = *TargetStack; + Target.Stack = rcc::StackID::sgprSpillStack(); auto TargetIndex = Frame.createObject(Target); - Context.expect(TargetStack && TargetIndex, - "target stack identity must admit target stack objects"); + Context.expect(TargetIndex.has_value(), "special stack identity must admit target stack objects"); auto Unresolved = Frame.createObject(fixedObject(8, 8)); Context.expect(Unresolved && !Frame.freeze(16, *rcc::Alignment::create(8)) && !Frame.isFrozen() && diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp index d330f6a..a019c8b 100644 --- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -268,6 +268,7 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { Description.Align = *rcc::Alignment::create(8); Description.Base = std::move(Base); Description.Offset = -8; + Description.TargetFlags = 0x3; return Description; } @@ -383,7 +384,8 @@ struct SourceFixture final { const auto RegClass = rcc::RegisterClassID::fromValue(2); for (const rcc::Register Reg : {Fixture.Value, Fixture.CopyValue}) { Context.expect(Function.registerInfo().setRegisterBank(Reg, Bank).has_value() && - Function.registerInfo().setRegisterClass(Reg, RegClass).has_value(), + Function.registerInfo().setRegisterClass(Reg, RegClass).has_value() && + Function.registerInfo().setTargetRegisterFlags(Reg, 0x3).has_value(), "source vreg constraints must be recorded"); } @@ -591,7 +593,8 @@ void testDeepClone(TestContext &Context) { SourceInfo->get().Type == DestinationInfo->get().Type && SourceInfo->get().Bank == DestinationInfo->get().Bank && SourceInfo->get().RegClass == DestinationInfo->get().RegClass && - SourceInfo->get().AllocationHints == DestinationInfo->get().AllocationHints, + SourceInfo->get().AllocationHints == DestinationInfo->get().AllocationHints && + SourceInfo->get().TargetFlags == DestinationInfo->get().TargetFlags, "clone must preserve vreg type and allocation constraints"); Context.expect(Destination.registerInfo().liveIns().size() == 1 && Destination.registerInfo().liveIns()[0].InitialVirtualRegister == NewValue && diff --git a/backend/unittests/CodeGen/TargetMIRTextInfoTest.cpp b/backend/unittests/CodeGen/TargetMIRTextInfoTest.cpp new file mode 100644 index 0000000..d7e2f13 --- /dev/null +++ b/backend/unittests/CodeGen/TargetMIRTextInfoTest.cpp @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetMIRTextInfo.h" + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineCFIInfo.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" + +#include <array> +#include <cstdint> +#include <iostream> +#include <memory> +#include <span> +#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; +}; + +[[nodiscard]] rcc::PhysicalRegister phys(std::uint32_t Number) { + return *rcc::PhysicalRegister::create(Number); +} + +void testTargetRegisterNames(TestContext &Context) { + auto Registers = rcc::test::createTestTargetRegisterInfo(); + Context.expect(Registers.has_value(), "TestTarget register schema must be valid"); + if (!Registers) + return; + + const auto G0 = phys(rcc::test::TestRegister::G0); + Context.expect(Registers->physicalRegister("g0") != nullptr && + Registers->physicalRegister("G0") == nullptr && + Registers->physicalRegisterMIRName(G0) == "g0", + "physical register MIR names must be canonical lowercase names"); + Context.expect(Registers->subRegIndex("low32") != nullptr && + Registers->subRegIndex("LOW32") == nullptr, + "subregister MIR names must retain their exact target spelling"); + Context.expect(Registers->registerClass("gpr64") != nullptr && + Registers->registerClass("GPR64") == nullptr && + Registers->registerClassMIRName(rcc::test::TestRegisterClass::GPR64) == + "gpr64", + "register class MIR names must be canonical lowercase names"); + Context.expect(Registers->registerBank("gpr") != nullptr && + Registers->registerBank("GPR") == nullptr && + Registers->registerBankMIRName(rcc::test::TestRegisterBank::GPR) == "gpr", + "register bank MIR names must be canonical lowercase names"); +} + +void testTextSchemaQueries(TestContext &Context) { + auto Registers = rcc::test::createTestTargetRegisterInfo(); + if (!Registers) + return; + auto Text = rcc::TargetMIRTextInfo::create(rcc::test::createTestTargetMIRTextInfoDescription(), + *Registers); + Context.expect(Text.has_value(), "TestTarget MIR text schema must be valid"); + if (!Text) + return; + + const auto Preserved = rcc::test::createTestCallPreservedMask(); + const auto NamedMask = Text->registerMask("test-preserved"); + Context.expect(NamedMask && *NamedMask == *Preserved && !Text->registerMask("Test-Preserved") && + Text->registerMaskName(*Preserved) == "test-preserved", + "named register masks must use one canonical lowercase spelling"); + + Context.expect(Text->targetIndex("test-index") == rcc::TargetIndex::fromValue(0) && + Text->targetIndexName(rcc::TargetIndex::fromValue(0)) == "test-index", + "target index zero must remain a valid named target index"); + Context.expect(Text->directOperandFlag("test-direct-three") == 0x3 && + Text->bitmaskOperandFlag("test-bit-four") == 0x4, + "direct and bitmask target flags must retain distinct registries"); + + auto OperandNames = Text->operandFlagNames(0xD); + Context.expect(OperandNames && + *OperandNames == std::vector<std::string_view>( + {"test-direct-one", "test-bit-four", "test-bit-eight"}), + "operand flag decomposition must be canonical and declaration ordered"); + Context.expect(!Text->operandFlagNames(0x10), + "operand flag decomposition must reject unknown bits"); + + auto MMONames = Text->machineMemOperandFlagNames(0x3); + auto VRegNames = Text->virtualRegisterFlagNames(0x3); + Context.expect( + MMONames && *MMONames == std::vector<std::string_view>({"test-mmo-one", "test-mmo-two"}) && + VRegNames && + *VRegNames == std::vector<std::string_view>({"test-vreg-one", "test-vreg-two"}) && + !Text->machineMemOperandFlagNames(0x4) && !Text->virtualRegisterFlagNames(0x4), + "MMO and virtual-register flags must reject every unregistered bit"); + + Context.expect(Text->intrinsic("llvm.test.seven") == rcc::IntrinsicID::fromValue(7) && + Text->intrinsicName(rcc::IntrinsicID::fromValue(17)) == "llvm.test.seventeen", + "intrinsic names and strong IDs must round-trip"); + + const auto G0 = phys(rcc::test::TestRegister::G0); + Context.expect(Text->dwarfRegister(G0) == rcc::test::TestRegister::G0 && + Text->physicalRegister(rcc::test::TestRegister::G0) == G0, + "CFI register mappings must support both physical and DWARF lookup"); + Context.expect(Text->supportsStackID(rcc::StackID::defaultStack()) && + Text->supportsStackID(rcc::StackID::noAllocStack()) && + !Text->supportsStackID(rcc::StackID::sgprSpillStack()), + "target stack support must be explicit and include fixed baseline defaults"); +} + +void testTextSchemaRejection(TestContext &Context) { + auto Registers = rcc::test::createTestTargetRegisterInfo(); + if (!Registers) + return; + auto Reject = [&Context, &Registers](rcc::TargetMIRTextInfoDesc Description, + std::string_view Message) { + Context.expect(!rcc::TargetMIRTextInfo::create(std::move(Description), *Registers), Message); + }; + + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.RegisterMasks.push_back( + {"TEST-PRESERVED", + std::make_shared<const rcc::RegisterMask>(std::vector<std::uint64_t>{})}); + Reject(std::move(Description), "register mask names must be unique case-insensitively"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.RegisterMasks.push_back({"other-mask", rcc::test::createTestCallPreservedMask()}); + Reject(std::move(Description), "register mask values must have one canonical name"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.RegisterMasks.front().Mask = std::make_shared<const rcc::RegisterMask>( + std::vector<std::uint64_t>{std::uint64_t{1} << 63U}); + Reject(std::move(Description), "named masks must reject unknown physical registers"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.TargetIndices.push_back({"other-index", rcc::TargetIndex::fromValue(0)}); + Reject(std::move(Description), "target index values must be unique"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.DirectOperandFlagMask = 0; + Reject(std::move(Description), "direct flags require a direct flag mask"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.DirectOperandFlags.front().Value = 0x4; + Reject(std::move(Description), "direct flags must remain inside their direct field"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.BitmaskOperandFlags.front().Value = 0xC; + Reject(std::move(Description), "bitmask operand flags must contain one bit"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.MachineMemOperandFlags.front().Value = 0x3; + Reject(std::move(Description), "MMO target flags must contain one bit"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.VirtualRegisterFlags.back().Value = Description.VirtualRegisterFlags.front().Value; + Reject(std::move(Description), "virtual-register target flag values must be unique"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.Intrinsics.front().Name = "test.not-an-intrinsic"; + Reject(std::move(Description), "intrinsic names must use the llvm namespace"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.CFIRegisters[1].DwarfRegister = Description.CFIRegisters[0].DwarfRegister; + Reject(std::move(Description), + "a DWARF register must not have multiple canonical physical registers"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.CFIRegisters.front().IsCanonical = false; + Reject(std::move(Description), + "every referenced DWARF register must have a canonical physical register"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.CFIRegisters.front().Register = phys(99); + Reject(std::move(Description), "CFI mappings must reject unknown physical registers"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.FunctionStateType = rcc::TargetFunctionStateTypeID{}; + Reject(std::move(Description), "target function state schemas require a nonzero TypeID"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.SupportedStackIDs.pop_back(); + Reject(std::move(Description), "targets must support the noalloc stack ID"); + } + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.SupportedStackIDs.push_back(rcc::StackID::defaultStack()); + Reject(std::move(Description), "supported stack IDs must be unique"); + } + + { + auto Description = rcc::test::createTestTargetMIRTextInfoDescription(); + Description.CFIRegisters[1].DwarfRegister = Description.CFIRegisters[0].DwarfRegister; + Description.CFIRegisters[1].IsCanonical = false; + auto Text = rcc::TargetMIRTextInfo::create(std::move(Description), *Registers); + Context.expect(Text && + Text->dwarfRegister(phys(rcc::test::TestRegister::G0Low32)) == + rcc::test::TestRegister::G0 && + Text->physicalRegister(rcc::test::TestRegister::G0) == + phys(rcc::test::TestRegister::G0), + "noncanonical physical aliases must map to one canonical DWARF register"); + } + + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.PhysicalRegisters[1].Name = "G0"; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "physical register names must be case-insensitively unique"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterClasses[1].Name = "gpr64"; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "register class names must be case-insensitively unique"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterBanks[1].Name = "gpr"; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "register bank names must be case-insensitively unique"); + } +} + +void testSemanticValidation(TestContext &Context) { + rcc::OperandDescriptor Operand; + Operand.Kinds = {rcc::MachineOperand::Kind::Immediate}; + Operand.AllowedTargetFlags = 0xF; + + rcc::InstructionDescriptor Instruction; + Instruction.InstructionOpcode = rcc::Opcode::fromValue(1); + Instruction.Name = "TEST_LOAD_IMM"; + Instruction.Operands.push_back(Operand); + Instruction.Flags = { + rcc::InstructionFlag::ProducesCode, + rcc::InstructionFlag::MayLoad, + }; + Instruction.RequiresMemoryOperands = true; + + auto Instructions = rcc::InstructionSet::create({std::move(Instruction)}); + if (!Instructions) + return; + auto Target = rcc::test::createTestTargetDescription(std::move(*Instructions)); + auto Function = + rcc::MachineFunction::create("schema_validation", rcc::MachineFunctionStableID::fromValue(1)); + Context.expect(Target.has_value() && Function.has_value(), + "semantic target schema fixture must be constructible"); + if (!Target || !Function) + return; + + auto Reg = (*Function)->registerInfo().createVirtualRegister(); + Context.expect(Reg && (*Function)->registerInfo().setTargetRegisterFlags(*Reg, 0x3).has_value() && + Target->validateRegisterInfo((*Function)->registerInfo()).has_value(), + "registered virtual-register target flags must pass target validation"); + if (Reg) { + Context.expect((*Function)->registerInfo().setTargetRegisterFlags(*Reg, 0x4).has_value() && + !Target->validateRegisterInfo((*Function)->registerInfo()), + "unregistered virtual-register target flags must fail target validation"); + Context.expect( + (*Function)->registerInfo().clearTargetRegisterFlags(*Reg).has_value(), + "semantic validation fixture must restore registered virtual-register target flags"); + } + + const rcc::MachineOperand ValidOperand = rcc::MachineOperand::immediate(7, 0x5); + const rcc::MachineOperand InvalidOperand = rcc::MachineOperand::immediate(7, 0x10); + Context.expect( + Target->validateExplicitOperands(rcc::Opcode::fromValue(1), + std::span<const rcc::MachineOperand>(&ValidOperand, 1), + (*Function)->registerInfo()) + .has_value() && + !Target->validateExplicitOperands( + rcc::Opcode::fromValue(1), std::span<const rcc::MachineOperand>(&InvalidOperand, 1), + (*Function)->registerInfo()), + "machine operands must use only descriptor-allowed serializable target flags"); + + rcc::MachineMemOperandDesc MemoryDescription; + MemoryDescription.Accesses = {rcc::MemoryAccessFlag::Load}; + MemoryDescription.Size = *rcc::MemorySize::fixed(8); + MemoryDescription.Align = *rcc::Alignment::create(8); + MemoryDescription.TargetFlags = 0x3; + auto ValidMemory = rcc::MachineMemOperand::create(MemoryDescription); + MemoryDescription.TargetFlags = 0x4; + auto InvalidMemory = rcc::MachineMemOperand::create(MemoryDescription); + if (ValidMemory && InvalidMemory) { + const std::array<const rcc::MachineMemOperand *, 1> ValidOperands{&*ValidMemory}; + const std::array<const rcc::MachineMemOperand *, 1> InvalidOperands{&*InvalidMemory}; + Context.expect( + Target->validateMemOperands(rcc::Opcode::fromValue(1), ValidOperands).has_value() && + !Target->validateMemOperands(rcc::Opcode::fromValue(1), InvalidOperands), + "machine memory operands must use only registered target flags"); + } + + rcc::MachineCFIInfo CFI; + auto CFIIndex = CFI.addInstruction( + rcc::MachineCFIInstruction::sameValue(rcc::DwarfRegisterNumber::fromValue(99))); + Context.expect(CFIIndex && !Target->validateCFIInfo(CFI), + "CFI instructions must reject unmapped DWARF registers"); + + rcc::MachineFrameInfo Frame; + rcc::StackObjectDesc StackObject; + StackObject.Kind = rcc::StackObjectKind::Target; + StackObject.Size = 8; + StackObject.Align = *rcc::Alignment::create(8); + StackObject.Stack = rcc::StackID::sgprSpillStack(); + auto StackIndex = Frame.createObject(StackObject); + Context.expect(StackIndex && !Target->validateFrameInfo(Frame), + "frame objects must reject stack IDs unsupported by the target"); + + rcc::OperandDescriptor BadOperand; + BadOperand.Kinds = {rcc::MachineOperand::Kind::Immediate}; + BadOperand.AllowedTargetFlags = 0x10; + rcc::InstructionDescriptor BadInstruction; + BadInstruction.InstructionOpcode = rcc::Opcode::fromValue(2); + BadInstruction.Name = "TEST_BAD_FLAGS"; + BadInstruction.Operands.push_back(BadOperand); + BadInstruction.Flags = {rcc::InstructionFlag::ProducesCode}; + auto BadInstructions = rcc::InstructionSet::create({std::move(BadInstruction)}); + Context.expect(BadInstructions && + !rcc::test::createTestTargetDescription(std::move(*BadInstructions)), + "target construction must reject operand flag bits outside its text schema"); + + auto PartialDescription = rcc::test::createTestTargetMIRTextInfoDescription(); + PartialDescription.DirectOperandFlags.erase(PartialDescription.DirectOperandFlags.begin() + 1, + PartialDescription.DirectOperandFlags.end()); + auto Registers = rcc::test::createTestTargetRegisterInfo(); + auto PartialText = + Registers ? rcc::TargetMIRTextInfo::create(std::move(PartialDescription), *Registers) + : rcc::Expected<rcc::TargetMIRTextInfo>(std::unexpected(Registers.error())); + Context.expect(PartialText && !PartialText->validateOperandFlags(0x2), + "unused direct-field encodings must remain invalid"); +} + +} // namespace + +int main() { + TestContext Context; + testTargetRegisterNames(Context); + testTextSchemaQueries(Context); + testTextSchemaRejection(Context); + testSemanticValidation(Context); + return Context.result(); +} diff --git a/backend/unittests/Target/TestTarget.cpp b/backend/unittests/Target/TestTarget.cpp index 30fa22f..4d31fb9 100644 --- a/backend/unittests/Target/TestTarget.cpp +++ b/backend/unittests/Target/TestTarget.cpp @@ -156,11 +156,50 @@ Expected<TargetRegisterInfo> createTestTargetRegisterInfo() { return TargetRegisterInfo::create(createTestTargetRegisterInfoDescription()); } +TargetMIRTextInfoDesc createTestTargetMIRTextInfoDescription() { + TargetMIRTextInfoDesc Description; + Description.RegisterMasks = { + {"Test-Preserved", createTestCallPreservedMask()}, + }; + Description.TargetIndices = { + {"test-index", TargetIndex::fromValue(0)}, + }; + Description.DirectOperandFlagMask = 0x3; + Description.DirectOperandFlags = { + {"test-direct-one", 0x1}, + {"test-direct-two", 0x2}, + {"test-direct-three", 0x3}, + }; + Description.BitmaskOperandFlags = { + {"test-bit-four", 0x4}, + {"test-bit-eight", 0x8}, + }; + Description.MachineMemOperandFlags = { + {"test-mmo-one", 0x1}, + {"test-mmo-two", 0x2}, + }; + Description.VirtualRegisterFlags = { + {"test-vreg-one", 0x1}, + {"test-vreg-two", 0x2}, + }; + Description.Intrinsics = { + {"llvm.test.seven", IntrinsicID::fromValue(7)}, + {"llvm.test.seventeen", IntrinsicID::fromValue(17)}, + }; + + for (const PhysicalRegisterDesc &Register : + createTestTargetRegisterInfoDescription().PhysicalRegisters) { + Description.CFIRegisters.push_back({Register.Reg, Register.Reg.number(), true}); + } + return Description; +} + Expected<TargetDescription> createTestTargetDescription(InstructionSet Instructions) { auto Registers = createTestTargetRegisterInfo(); if (!Registers) return std::unexpected(Registers.error()); - return TargetDescription::create(std::move(Instructions), std::move(*Registers)); + return TargetDescription::create(std::move(Instructions), std::move(*Registers), + createTestTargetMIRTextInfoDescription()); } std::shared_ptr<const RegisterMask> createTestCallPreservedMask() { diff --git a/backend/unittests/Target/TestTarget.h b/backend/unittests/Target/TestTarget.h index 3c2aa51..fe434a2 100644 --- a/backend/unittests/Target/TestTarget.h +++ b/backend/unittests/Target/TestTarget.h @@ -6,6 +6,7 @@ #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/RegisterMask.h" #include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" #include "rcc/CodeGen/TargetRegisterInfo.h" #include "rcc/Support/Error.h" @@ -56,6 +57,7 @@ struct TestRegisterClass final { [[nodiscard]] TargetRegisterInfoDesc createTestTargetRegisterInfoDescription(); [[nodiscard]] Expected<TargetRegisterInfo> createTestTargetRegisterInfo(); +[[nodiscard]] TargetMIRTextInfoDesc createTestTargetMIRTextInfoDescription(); [[nodiscard]] Expected<TargetDescription> createTestTargetDescription(InstructionSet Instructions); [[nodiscard]] std::shared_ptr<const RegisterMask> createTestCallPreservedMask(); |
