diff options
Diffstat (limited to 'backend/unittests/CodeGen')
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 44 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionClonerTest.cpp | 37 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 53 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTargetTest.cpp | 485 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 115 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/TargetFunctionStateTest.cpp | 16 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/TargetRegisterInfoTest.cpp | 359 |
7 files changed, 1000 insertions, 109 deletions
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 4a0b9fd..b0bdd13 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -12,9 +12,11 @@ set( MachineIRCoreTest.cpp MachineJumpTableInfoTest.cpp MachineVerifierTest.cpp + MachineVerifierTargetTest.cpp MachineValueTypesTest.cpp TargetConstantTest.cpp TargetFunctionStateTest.cpp + TargetRegisterInfoTest.cpp ) add_executable(RCCBackendCodeGenCoreTypesTests CoreTypesTest.cpp) @@ -78,7 +80,7 @@ add_executable( rcc_backend_configure_target(RCCBackendCodeGenMachineFunctionClonerTests) target_link_libraries( RCCBackendCodeGenMachineFunctionClonerTests - PRIVATE RCCBackend::CodeGen + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget ) add_test( @@ -90,7 +92,7 @@ add_executable(RCCBackendCodeGenMachineIRBuilderTests MachineIRBuilderTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineIRBuilderTests) target_link_libraries( RCCBackendCodeGenMachineIRBuilderTests - PRIVATE RCCBackend::CodeGen + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget ) add_test( @@ -102,7 +104,7 @@ add_executable(RCCBackendCodeGenMachineVerifierTests MachineVerifierTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineVerifierTests) target_link_libraries( RCCBackendCodeGenMachineVerifierTests - PRIVATE RCCBackend::CodeGen + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget ) add_test( @@ -110,6 +112,21 @@ add_test( COMMAND RCCBackendCodeGenMachineVerifierTests ) +add_executable( + RCCBackendCodeGenMachineVerifierTargetTests + MachineVerifierTargetTest.cpp +) +rcc_backend_configure_target(RCCBackendCodeGenMachineVerifierTargetTests) +target_link_libraries( + RCCBackendCodeGenMachineVerifierTargetTests + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget +) + +add_test( + NAME RCCBackend.CodeGen.MachineVerifierTarget + COMMAND RCCBackendCodeGenMachineVerifierTargetTests +) + add_executable(RCCBackendCodeGenMachineValueTypesTests MachineValueTypesTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineValueTypesTests) target_link_libraries(RCCBackendCodeGenMachineValueTypesTests PRIVATE RCCBackend::CodeGen) @@ -148,13 +165,28 @@ add_test( add_executable(RCCBackendCodeGenTargetFunctionStateTests TargetFunctionStateTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenTargetFunctionStateTests) -target_link_libraries(RCCBackendCodeGenTargetFunctionStateTests PRIVATE RCCBackend::CodeGen) +target_link_libraries( + RCCBackendCodeGenTargetFunctionStateTests + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget +) add_test( NAME RCCBackend.CodeGen.TargetFunctionState COMMAND RCCBackendCodeGenTargetFunctionStateTests ) +add_executable(RCCBackendCodeGenTargetRegisterInfoTests TargetRegisterInfoTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenTargetRegisterInfoTests) +target_link_libraries( + RCCBackendCodeGenTargetRegisterInfoTests + PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget +) + +add_test( + NAME RCCBackend.CodeGen.TargetRegisterInfo + COMMAND RCCBackendCodeGenTargetRegisterInfoTests +) + rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" @@ -166,9 +198,11 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTargetTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp" ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" @@ -181,7 +215,9 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTargetTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TargetFunctionStateTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetRegisterInfoTest.cpp" ) diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp index 3d766dc..d330f6a 100644 --- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -2,6 +2,7 @@ #include "rcc/CodeGen/MachineFunctionCloner.h" +#include "Target/TestTarget.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" @@ -176,7 +177,7 @@ struct CloneTargetState final { return Descriptor; } -[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() { +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP"); auto Def = instructionDescriptor(DefOpcode, "TEST_DEF"); @@ -233,7 +234,10 @@ struct CloneTargetState final { Descriptors.push_back(std::move(Load)); Descriptors.push_back(std::move(ConstantPool)); Descriptors.push_back(std::move(JumpTable)); - return rcc::InstructionSet::create(std::move(Descriptors)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); } [[nodiscard]] std::unique_ptr<rcc::MachineFunction> @@ -289,8 +293,7 @@ struct SourceFixture final { }; [[nodiscard]] bool populateSource(TestContext &Context, rcc::MachineFunction &Function, - const rcc::InstructionSet &InstructionSet, - SourceFixture &Fixture) { + const rcc::TargetDescription &Target, SourceFixture &Fixture) { auto HistoricalBlock = Function.createBasicBlock(); auto HistoricalInstruction = Function.createInstruction(rcc::Opcode::fromValue(NopOpcode)); Context.expect(HistoricalBlock && HistoricalInstruction, "source arena history must be created"); @@ -393,7 +396,7 @@ struct SourceFixture final { Fixture.Entry->addLiveIn(LiveIn).has_value(), "source register configuration must be recorded"); - rcc::MachineIRBuilder Builder(Function, InstructionSet); + rcc::MachineIRBuilder Builder(Function, Target); Context.expect(Builder.setInsertPoint(*Fixture.Entry).has_value(), "source entry insertion point must be valid"); auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefOpcode)); @@ -504,17 +507,17 @@ struct SourceFixture final { } void testDeepClone(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Source = createFunction(Context, "source_function", 1); SourceFixture Fixture; - if (!InstructionSet || !Source || !populateSource(Context, *Source, *InstructionSet, Fixture)) { + if (!Target || !Source || !populateSource(Context, *Source, *Target, Fixture)) { return; } - Context.expect(rcc::MachineVerifier::verify(*Source, *InstructionSet).has_value(), + Context.expect(rcc::MachineVerifier::verify(*Source, *Target).has_value(), "source function must verify before cloning"); auto Clone = rcc::cloneMachineFunction( - *Source, *InstructionSet, + *Source, *Target, rcc::MachineFunctionCloneOptions{rcc::MachineFunctionStableID::fromValue(99), std::string("cloned_function")}); Context.expect(Clone.has_value(), "valid source function must deep-clone"); @@ -526,8 +529,8 @@ void testDeepClone(TestContext &Context) { Destination.stableID() == rcc::MachineFunctionStableID::fromValue(99), "clone must use its explicit function identity"); Context.expect(Destination.properties() == Source->properties() && - rcc::MachineVerifier::verify(Destination, *InstructionSet).has_value() && - rcc::MachineVerifier::verify(*Source, *InstructionSet).has_value(), + rcc::MachineVerifier::verify(Destination, *Target).has_value() && + rcc::MachineVerifier::verify(*Source, *Target).has_value(), "source and destination must independently preserve valid properties"); Context.expect(Source->allBlocks().size() == 3 && Destination.allBlocks().size() == 2 && Source->allInstructions().size() == 9 && @@ -761,18 +764,18 @@ void testDeepClone(TestContext &Context) { } void testCloneFailures(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Source = createFunction(Context, "failure_source", 2); - if (!InstructionSet || !Source) + if (!Target || !Source) return; Context.expect( - !rcc::cloneMachineFunction(*Source, *InstructionSet, + !rcc::cloneMachineFunction(*Source, *Target, rcc::MachineFunctionCloneOptions{ rcc::MachineFunctionStableID::fromValue(0), std::nullopt}), "clone must reject an invalid destination StableID"); Context.expect( - !rcc::cloneMachineFunction(*Source, *InstructionSet, + !rcc::cloneMachineFunction(*Source, *Target, rcc::MachineFunctionCloneOptions{ rcc::MachineFunctionStableID::fromValue(3), std::string()}), "clone must reject an explicitly empty destination name"); @@ -784,7 +787,7 @@ void testCloneFailures(TestContext &Context) { Context.expect(Unknown && Block->appendInstruction(**Unknown).has_value(), "invalid clone source must contain an unknown opcode"); Context.expect( - !rcc::cloneMachineFunction(*Invalid, *InstructionSet, + !rcc::cloneMachineFunction(*Invalid, *Target, rcc::MachineFunctionCloneOptions{ rcc::MachineFunctionStableID::fromValue(5), std::nullopt}), "clone must verify and reject an invalid source function"); @@ -800,7 +803,7 @@ void testCloneFailures(TestContext &Context) { 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::cloneMachineFunction(*UnmappedState, *Target, rcc::MachineFunctionCloneOptions{ rcc::MachineFunctionStableID::fromValue(7), std::nullopt}), "clone must reject target state that does not map its identities"); diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp index 2b5c97d..15f6cd2 100644 --- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp +++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp @@ -2,6 +2,7 @@ #include "rcc/CodeGen/MachineIRBuilder.h" +#include "Target/TestTarget.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" @@ -66,7 +67,7 @@ private: return Descriptor; } -[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() { +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { auto Copy = instructionDescriptor(CopyOpcode, "TEST_COPY"); auto CopyDef = registerDescriptor(rcc::RegisterOperandRole::Def); auto CopyUse = registerDescriptor(rcc::RegisterOperandRole::Use); @@ -126,7 +127,10 @@ private: Descriptors.push_back(std::move(Dynamic)); Descriptors.push_back(std::move(ConstantPool)); Descriptors.push_back(std::move(JumpTable)); - return rcc::InstructionSet::create(std::move(Descriptors)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); } [[nodiscard]] std::unique_ptr<rcc::MachineFunction> @@ -172,10 +176,10 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { } void testTransactionalBuild(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = createFunction(Context, "transactional", 1); - Context.expect(InstructionSet.has_value(), "builder instruction set must be valid"); - if (!InstructionSet || !Function) + Context.expect(Target.has_value(), "builder target description must be valid"); + if (!Target || !Function) return; rcc::MachineBasicBlock *Block = createBlock(Context, *Function); if (Block == nullptr) @@ -188,7 +192,7 @@ void testTransactionalBuild(TestContext &Context) { if (!Destination || !Source) return; - rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + rcc::MachineIRBuilder Builder(*Function, *Target); Context.expect(!Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)), "an unset insertion point must reject instruction construction"); Context.expect(Function->allInstructions().empty(), @@ -242,9 +246,8 @@ void testTransactionalBuild(TestContext &Context) { Instruction->sourceLocation() == rcc::SourceLocation{"builder.mir", 7, 3, 1} && Instruction->origin() == rcc::OriginID::fromValue(19), "builder metadata must be preserved on the published instruction"); - Context.expect( - InstructionSet->validateInstruction(*Instruction, Function->registerInfo()).has_value(), - "builder output must satisfy the shared instruction descriptor"); + Context.expect(Target->validateInstruction(*Instruction, Function->registerInfo()).has_value(), + "builder output must satisfy the shared instruction descriptor"); Context.expect(!Complete->build(), "a successful pending builder must be single-use"); Context.expect(!Complete->setFlags({}) && !Complete->clearSourceLocation() && !Complete->clearOrigin(), @@ -264,15 +267,15 @@ void testTransactionalBuild(TestContext &Context) { } void testInsertionOrdering(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = createFunction(Context, "ordering", 2); - if (!InstructionSet || !Function) + if (!Target || !Function) return; rcc::MachineBasicBlock *Block = createBlock(Context, *Function); if (Block == nullptr) return; - rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + rcc::MachineIRBuilder Builder(*Function, *Target); Context.expect(Builder.setInsertPoint(*Block).has_value(), "ordering builder insertion point must be valid"); rcc::MachineInstr *First = buildNop(Context, Builder); @@ -298,10 +301,10 @@ void testInsertionOrdering(TestContext &Context) { } void testImplicitAndMemoryValidation(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = createFunction(Context, "implicit_memory", 3); auto OtherFunction = createFunction(Context, "foreign", 4); - if (!InstructionSet || !Function || !OtherFunction) + if (!Target || !Function || !OtherFunction) return; rcc::MachineBasicBlock *Block = createBlock(Context, *Function); rcc::MachineBasicBlock *OtherBlock = createBlock(Context, *OtherFunction); @@ -315,7 +318,7 @@ void testImplicitAndMemoryValidation(TestContext &Context) { if (!Destination || !Source) return; - rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + rcc::MachineIRBuilder Builder(*Function, *Target); Context.expect(Builder.setInsertPoint(*Block).has_value(), "implicit test insertion point must be accepted"); @@ -334,9 +337,8 @@ void testImplicitAndMemoryValidation(TestContext &Context) { (*BuiltAdd)->operandAt(3)->registerFlags().has(rcc::RegisterOperandFlag::Renamable) && (*BuiltAdd)->operandAt(4)->isImplicit(), "default implicit defs and uses must follow explicit operands"); - Context.expect( - InstructionSet->validateInstruction(**BuiltAdd, Function->registerInfo()).has_value(), - "default implicit builder output must pass shared validation"); + Context.expect(Target->validateInstruction(**BuiltAdd, Function->registerInfo()).has_value(), + "default implicit builder output must pass shared validation"); const std::size_t BeforeRejectedImplicit = Function->allInstructions().size(); auto ExtraImplicit = Builder.buildInstr(rcc::Opcode::fromValue(AddOpcode)); @@ -363,7 +365,7 @@ void testImplicitAndMemoryValidation(TestContext &Context) { auto BuiltDynamic = Dynamic->build(); Context.expect( BuiltDynamic.has_value() && - InstructionSet->validateInstruction(**BuiltDynamic, Function->registerInfo()).has_value(), + Target->validateInstruction(**BuiltDynamic, Function->registerInfo()).has_value(), "dynamic implicit builder output must validate"); const std::size_t BeforeMissingMemory = Function->allInstructions().size(); @@ -444,10 +446,9 @@ void testImplicitAndMemoryValidation(TestContext &Context) { if (!Load) return; auto BuiltLoad = Load->build(); - Context.expect( - BuiltLoad.has_value() && - InstructionSet->validateInstruction(**BuiltLoad, Function->registerInfo()).has_value(), - "matching load MMO must build and validate"); + Context.expect(BuiltLoad.has_value() && + Target->validateInstruction(**BuiltLoad, Function->registerInfo()).has_value(), + "matching load MMO must build and validate"); const std::size_t BeforeStoreMismatch = Function->allInstructions().size(); auto StoreMismatch = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); @@ -468,9 +469,9 @@ void testImplicitAndMemoryValidation(TestContext &Context) { } void testStaleCursorsAndOwnedOperands(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = createFunction(Context, "ownership", 5); - if (!InstructionSet || !Function) + if (!Target || !Function) return; rcc::MachineBasicBlock *Block = createBlock(Context, *Function); if (Block == nullptr) @@ -482,7 +483,7 @@ void testStaleCursorsAndOwnedOperands(TestContext &Context) { if (!Destination || !Source) return; - rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + rcc::MachineIRBuilder Builder(*Function, *Target); Context.expect(Builder.setInsertPoint(*Block).has_value(), "ownership test insertion point must be valid"); auto Copy = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); diff --git a/backend/unittests/CodeGen/MachineVerifierTargetTest.cpp b/backend/unittests/CodeGen/MachineVerifierTargetTest.cpp new file mode 100644 index 0000000..3eb926d --- /dev/null +++ b/backend/unittests/CodeGen/MachineVerifierTargetTest.cpp @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/MachineVerifier.h" +#include "rcc/CodeGen/RegisterMask.h" +#include "rcc/CodeGen/TargetDescription.h" + +#include <cstdint> +#include <iostream> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +constexpr std::uint32_t DefGPR64Opcode = 1; +constexpr std::uint32_t DefGPR32Opcode = 2; +constexpr std::uint32_t EarlyClobberOpcode = 3; +constexpr std::uint32_t TiedAnyOpcode = 4; +constexpr std::uint32_t CallOpcode = 5; +constexpr std::uint32_t RenamableImplicitOpcode = 6; + +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 FunctionFixture final { + std::unique_ptr<rcc::MachineFunction> Function; + rcc::MachineBasicBlock *Block = nullptr; +}; + +[[nodiscard]] rcc::PhysicalRegister phys(std::uint32_t Number) { + return *rcc::PhysicalRegister::create(Number); +} + +[[nodiscard]] rcc::OperandDescriptor +registerOperand(rcc::RegisterOperandRole Role, + std::optional<rcc::RegisterClassID> Class = std::nullopt, + std::optional<rcc::RegisterBankID> Bank = std::nullopt, bool AllowSubReg = false, + bool EarlyClobber = false) { + rcc::OperandDescriptor Operand; + Operand.Kinds = {rcc::MachineOperand::Kind::Register}; + Operand.RegisterRole = Role; + Operand.RegClass = Class; + Operand.RegBank = Bank; + Operand.AllowSubReg = AllowSubReg; + Operand.EarlyClobber = EarlyClobber; + return Operand; +} + +[[nodiscard]] rcc::InstructionDescriptor instruction(std::uint32_t OpcodeValue, std::string Name) { + rcc::InstructionDescriptor Instruction; + Instruction.InstructionOpcode = rcc::Opcode::fromValue(OpcodeValue); + Instruction.Name = std::move(Name); + Instruction.Flags = {rcc::InstructionFlag::ProducesCode}; + return Instruction; +} + +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { + auto DefGPR64 = instruction(DefGPR64Opcode, "TEST_DEF_GPR64"); + DefGPR64.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Def, + rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterBank::GPR, true)); + DefGPR64.NumExplicitDefs = 1; + + auto DefGPR32 = instruction(DefGPR32Opcode, "TEST_DEF_GPR32"); + DefGPR32.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Def, + rcc::test::TestRegisterClass::GPR32, + rcc::test::TestRegisterBank::GPR, true)); + DefGPR32.NumExplicitDefs = 1; + + auto EarlyClobber = instruction(EarlyClobberOpcode, "TEST_EARLY_CLOBBER"); + EarlyClobber.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Def, + rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterBank::GPR, false, true)); + EarlyClobber.Operands.push_back(registerOperand(rcc::RegisterOperandRole::Use, + rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterBank::GPR)); + EarlyClobber.NumExplicitDefs = 1; + + auto TiedAny = instruction(TiedAnyOpcode, "TEST_TIED_ANY"); + auto TiedDef = registerOperand(rcc::RegisterOperandRole::Def); + auto TiedUse = registerOperand(rcc::RegisterOperandRole::Use); + TiedDef.DefaultTiedOperand = 1; + TiedUse.DefaultTiedOperand = 0; + TiedAny.Operands.push_back(TiedDef); + TiedAny.Operands.push_back(TiedUse); + TiedAny.NumExplicitDefs = 1; + + auto Call = instruction(CallOpcode, "TEST_CALL"); + rcc::OperandDescriptor Mask; + Mask.Kinds = {rcc::MachineOperand::Kind::RegisterMask}; + Call.Operands.push_back(Mask); + Call.Flags.set(rcc::InstructionFlag::Call); + Call.Flags.set(rcc::InstructionFlag::HasSideEffects); + + auto RenamableImplicit = instruction(RenamableImplicitOpcode, "TEST_RENAMABLE_IMPLICIT"); + RenamableImplicit.ImplicitDefs.push_back({phys(rcc::test::TestRegister::G0), false, true}); + + std::vector<rcc::InstructionDescriptor> Descriptors; + Descriptors.push_back(std::move(DefGPR64)); + Descriptors.push_back(std::move(DefGPR32)); + Descriptors.push_back(std::move(EarlyClobber)); + Descriptors.push_back(std::move(TiedAny)); + Descriptors.push_back(std::move(Call)); + Descriptors.push_back(std::move(RenamableImplicit)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); +} + +[[nodiscard]] FunctionFixture createFunction(TestContext &Context, std::string Name, + std::uint64_t ID) { + auto Function = + rcc::MachineFunction::create(std::move(Name), rcc::MachineFunctionStableID::fromValue(ID)); + Context.expect(Function.has_value(), "target verifier function must be created"); + if (!Function) + return {}; + + auto Block = (*Function)->createBasicBlock(); + Context.expect(Block && (*Function)->appendBlock(**Block).has_value(), + "target verifier block must be linked"); + if (!Block) + return {}; + return FunctionFixture{std::move(*Function), *Block}; +} + +[[nodiscard]] rcc::Expected<void> classify(rcc::MachineRegisterInfo &RegInfo, rcc::Register Reg, + rcc::RegisterBankID Bank, rcc::RegisterClassID Class) { + auto BankResult = RegInfo.setRegisterBank(Reg, Bank); + if (!BankResult) + return std::unexpected(BankResult.error()); + return RegInfo.setRegisterClass(Reg, Class); +} + +[[nodiscard]] rcc::Expected<rcc::MachineInstr *> +appendInstruction(FunctionFixture &Fixture, std::uint32_t OpcodeValue, + std::vector<rcc::MachineOperand> Operands, + std::optional<rcc::MachineOperandTie> Tie = std::nullopt) { + auto Instruction = Fixture.Function->createInstruction(rcc::Opcode::fromValue(OpcodeValue)); + if (!Instruction) + return std::unexpected(Instruction.error()); + for (rcc::MachineOperand &Operand : Operands) { + auto AddResult = (*Instruction)->addOperand(std::move(Operand)); + if (!AddResult) + return std::unexpected(AddResult.error()); + } + if (Tie) { + auto TieResult = (*Instruction)->tieOperands(Tie->DefOperand, Tie->UseOperand); + if (!TieResult) + return std::unexpected(TieResult.error()); + } + auto AppendResult = Fixture.Block->appendInstruction(**Instruction); + if (!AppendResult) + return std::unexpected(AppendResult.error()); + return *Instruction; +} + +void testValidTargetFacts(TestContext &Context, const rcc::TargetDescription &Target) { + auto Fixture = createFunction(Context, "valid_target_facts", 1); + if (!Fixture.Function) + return; + + auto Type = rcc::LLT::makeInteger(64); + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + if (!Reg) + return; + Context.expect(classify(Fixture.Function->registerInfo(), *Reg, rcc::test::TestRegisterBank::GPR, + rcc::test::TestRegisterClass::GPR64Alloc) + .has_value(), + "valid target verifier vreg must be classified"); + + const auto G0 = phys(rcc::test::TestRegister::G0); + const auto G1 = phys(rcc::test::TestRegister::G1); + Context.expect(Fixture.Function->registerInfo().addLiveIn(G0, *Reg).has_value() && + Fixture.Function->registerInfo() + .reservePhysicalRegister(phys(rcc::test::TestRegister::StackPointer)) + .has_value() && + Fixture.Block->addLiveIn(G0, rcc::LaneMask::fromRaw(0x3)).has_value(), + "valid target live-in and reserved register facts must be recorded"); + + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "valid target builder insertion point must be set"); + auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefGPR64Opcode)); + Context.expect(Def && Def->addDef(*Reg).has_value() && Def->build(), + "register subclass must satisfy a target operand superclass"); + + auto Early = Builder.buildInstr(rcc::Opcode::fromValue(EarlyClobberOpcode)); + Context.expect( + Early && + Early->addDef(rcc::Register::fromPhysical(G0), {rcc::RegisterOperandFlag::EarlyClobber}) + .has_value() && + Early->addUse(rcc::Register::fromPhysical(G1)).has_value() && Early->build(), + "disjoint physical registers must satisfy early-clobber"); + + auto Call = Builder.buildInstr(rcc::Opcode::fromValue(CallOpcode)); + Context.expect(Call && + Call->addRegisterMask(rcc::test::createTestCallPreservedMask()).has_value() && + Call->build(), + "valid target call mask must build"); + Context.expect(rcc::MachineVerifier::verify(*Fixture.Function, Target).has_value(), + "complete valid target facts must pass MachineVerifier"); +} + +void testSubRegisterProjection(TestContext &Context, const rcc::TargetDescription &Target) { + auto Fixture = createFunction(Context, "subregister_projection", 2); + if (!Fixture.Function) + return; + + auto Type = rcc::LLT::makeInteger(64); + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + if (!Reg) + return; + Context.expect(classify(Fixture.Function->registerInfo(), *Reg, rcc::test::TestRegisterBank::GPR, + rcc::test::TestRegisterClass::GPR64) + .has_value(), + "subregister source vreg must be classified"); + + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "subregister builder insertion point must be set"); + auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefGPR32Opcode)); + Context.expect(Def && Def->addDef(*Reg, {}, rcc::test::TestSubReg::Low32).has_value() && + Def->build(), + "projected GPR32Low class must satisfy the GPR32 operand class"); + Context.expect(rcc::MachineVerifier::verify(*Fixture.Function, Target).has_value(), + "valid virtual subregister projection must verify"); +} + +void testUnknownAndMalformedTargetFacts(TestContext &Context, + const rcc::TargetDescription &Target) { + { + auto Fixture = createFunction(Context, "unknown_physreg", 3); + auto Operand = rcc::MachineOperand::registerOperand(rcc::Register::fromPhysical(phys(99)), + {rcc::RegisterOperandFlag::Define}); + if (Fixture.Function && Operand) { + std::vector<rcc::MachineOperand> Operands; + Operands.push_back(std::move(*Operand)); + Context.expect(appendInstruction(Fixture, DefGPR64Opcode, std::move(Operands)) && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject unknown physical registers"); + } + } + { + auto Fixture = createFunction(Context, "physical_subreg", 4); + auto Operand = rcc::MachineOperand::registerOperand( + rcc::Register::fromPhysical(phys(rcc::test::TestRegister::G0)), + {rcc::RegisterOperandFlag::Define}, rcc::test::TestSubReg::Low32); + if (Fixture.Function && Operand) { + std::vector<rcc::MachineOperand> Operands; + Operands.push_back(std::move(*Operand)); + Context.expect(appendInstruction(Fixture, DefGPR64Opcode, std::move(Operands)) && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject physical operands with SubRegIndex"); + } + } + { + auto Fixture = createFunction(Context, "unknown_bank", 5); + auto Type = rcc::LLT::makeInteger(64); + if (!Fixture.Function || !Type) + return; + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + Context.expect(Reg && + Fixture.Function->registerInfo() + .setRegisterBank(*Reg, rcc::RegisterBankID::fromValue(99)) + .has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject unknown virtual register bank IDs"); + } + { + auto Fixture = createFunction(Context, "wide_bank_value", 6); + auto Type = rcc::LLT::makeInteger(128); + if (!Fixture.Function || !Type) + return; + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + Context.expect(Reg && + Fixture.Function->registerInfo() + .setRegisterBank(*Reg, rcc::test::TestRegisterBank::GPR) + .has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject LLTs wider than their register bank"); + } + { + auto Fixture = createFunction(Context, "bad_function_livein", 7); + Context.expect(Fixture.Function && + Fixture.Function->registerInfo().addLiveIn(phys(99)).has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject unknown function physical live-ins"); + } + { + auto Fixture = createFunction(Context, "bad_block_lanes", 8); + Context.expect( + Fixture.Function && + Fixture.Block + ->addLiveIn(phys(rcc::test::TestRegister::G0), rcc::LaneMask::fromRaw(0x10)) + .has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject target-invalid block live-in lanes"); + } + { + auto Fixture = createFunction(Context, "bad_allocation_hint", 9); + auto Type = rcc::LLT::makeInteger(64); + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + Context.expect(Reg && + classify(Fixture.Function->registerInfo(), *Reg, + rcc::test::TestRegisterBank::FPR, + rcc::test::TestRegisterClass::FPR64) + .has_value() && + Fixture.Function->registerInfo() + .addAllocationHint(*Reg, phys(rcc::test::TestRegister::G0)) + .has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject allocation hints outside a vreg class"); + } + { + auto Fixture = createFunction(Context, "reserved_allocation_hint", 14); + auto Type = rcc::LLT::makeInteger(64); + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + const auto G0 = phys(rcc::test::TestRegister::G0); + Context.expect(Reg && + classify(Fixture.Function->registerInfo(), *Reg, + rcc::test::TestRegisterBank::GPR, + rcc::test::TestRegisterClass::GPR64) + .has_value() && + Fixture.Function->registerInfo().addAllocationHint(*Reg, G0).has_value() && + Fixture.Function->registerInfo().reservePhysicalRegister(G0).has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject allocation hints that overlap reserved registers"); + } + { + auto Fixture = createFunction(Context, "incompatible_function_livein", 15); + auto Type = rcc::LLT::makeInteger(64); + auto Reg = Fixture.Function->registerInfo().createVirtualRegister(*Type); + Context.expect( + Reg && + classify(Fixture.Function->registerInfo(), *Reg, rcc::test::TestRegisterBank::FPR, + rcc::test::TestRegisterClass::FPR64) + .has_value() && + Fixture.Function->registerInfo() + .addLiveIn(phys(rcc::test::TestRegister::G0), *Reg) + .has_value() && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject a function live-in outside its initial vreg constraints"); + } +} + +void testRegMaskEarlyClobberAndTies(TestContext &Context, const rcc::TargetDescription &Target) { + { + auto Fixture = createFunction(Context, "bad_regmask", 10); + auto Mask = std::make_shared<const rcc::RegisterMask>( + std::vector<std::uint64_t>{std::uint64_t{1} << 63U}); + auto Operand = rcc::MachineOperand::registerMask(std::move(Mask)); + if (Fixture.Function && Operand) { + std::vector<rcc::MachineOperand> Operands; + Operands.push_back(std::move(*Operand)); + Context.expect(appendInstruction(Fixture, CallOpcode, std::move(Operands)) && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject RegMask bits for unknown registers"); + } + } + { + auto Fixture = createFunction(Context, "early_overlap", 11); + const auto G0 = phys(rcc::test::TestRegister::G0); + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "early-clobber builder insertion point must be set"); + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(EarlyClobberOpcode)); + Context.expect( + Pending && + Pending + ->addDef(rcc::Register::fromPhysical(G0), {rcc::RegisterOperandFlag::EarlyClobber}) + .has_value() && + Pending->addUse(rcc::Register::fromPhysical(G0)).has_value() && !Pending->build() && + Fixture.Function->allInstructions().empty(), + "Builder must reject overlapping early-clobber before publication"); + + auto Def = rcc::MachineOperand::registerOperand( + rcc::Register::fromPhysical(G0), + {rcc::RegisterOperandFlag::Define, rcc::RegisterOperandFlag::EarlyClobber}); + auto Use = rcc::MachineOperand::registerOperand(rcc::Register::fromPhysical(G0), + {rcc::RegisterOperandFlag::Use}); + if (Def && Use) { + std::vector<rcc::MachineOperand> Operands; + Operands.push_back(std::move(*Def)); + Operands.push_back(std::move(*Use)); + Context.expect(appendInstruction(Fixture, EarlyClobberOpcode, std::move(Operands)) && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject overlapping early-clobber"); + } + } + { + auto Fixture = createFunction(Context, "incompatible_tie", 12); + auto Type = rcc::LLT::makeInteger(64); + auto GPR = Fixture.Function->registerInfo().createVirtualRegister(*Type); + auto FPR = Fixture.Function->registerInfo().createVirtualRegister(*Type); + if (!GPR || !FPR) + return; + Context.expect(classify(Fixture.Function->registerInfo(), *GPR, + rcc::test::TestRegisterBank::GPR, rcc::test::TestRegisterClass::GPR64) + .has_value() && + classify(Fixture.Function->registerInfo(), *FPR, + rcc::test::TestRegisterBank::FPR, + rcc::test::TestRegisterClass::FPR64) + .has_value(), + "incompatible tied vregs must have individually valid classes"); + + auto Def = rcc::MachineOperand::registerOperand(*GPR, {rcc::RegisterOperandFlag::Define}); + auto Use = rcc::MachineOperand::registerOperand(*FPR, {rcc::RegisterOperandFlag::Use}); + if (Def && Use) { + std::vector<rcc::MachineOperand> Operands; + Operands.push_back(std::move(*Def)); + Operands.push_back(std::move(*Use)); + Context.expect(appendInstruction(Fixture, TiedAnyOpcode, std::move(Operands), + rcc::MachineOperandTie{0, 1}) && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject tied classes without a physical intersection"); + } + } + { + auto Fixture = createFunction(Context, "reserved_renamable", 13); + const auto G0 = phys(rcc::test::TestRegister::G0); + Context.expect(Fixture.Function->registerInfo().reservePhysicalRegister(G0).has_value(), + "reserved renamable test register must be reserved"); + + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "reserved renamable builder insertion point must be set"); + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(RenamableImplicitOpcode)); + Context.expect(Pending && !Pending->build(), + "Builder must reject a renamable reserved implicit register"); + + const rcc::InstructionDescriptor *Descriptor = + Target.instructionDescriptor(rcc::Opcode::fromValue(RenamableImplicitOpcode)); + if (Descriptor == nullptr) + return; + auto Implicits = Target.createDefaultImplicitOperands(*Descriptor); + if (Implicits) { + Context.expect(appendInstruction(Fixture, RenamableImplicitOpcode, std::move(*Implicits)) && + !rcc::MachineVerifier::verify(*Fixture.Function, Target), + "MachineVerifier must reject a renamable reserved implicit register"); + } + } +} + +} // namespace + +int main() { + TestContext Context; + auto Target = createTarget(); + Context.expect(Target.has_value(), "target-aware verifier test target must be valid"); + if (!Target) + return Context.result(); + + testValidTargetFacts(Context, *Target); + testSubRegisterProjection(Context, *Target); + testUnknownAndMalformedTargetFacts(Context, *Target); + testRegMaskEarlyClobberAndTies(Context, *Target); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp index 3cc79d1..a803c08 100644 --- a/backend/unittests/CodeGen/MachineVerifierTest.cpp +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -2,6 +2,7 @@ #include "rcc/CodeGen/MachineVerifier.h" +#include "Target/TestTarget.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" @@ -71,7 +72,7 @@ private: return Descriptor; } -[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() { +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { auto GenericDef = instructionDescriptor(GenericDefOpcode, "TEST_GENERIC_DEF"); GenericDef.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def)); GenericDef.NumExplicitDefs = 1; @@ -140,7 +141,10 @@ private: Descriptors.push_back(std::move(Nop)); Descriptors.push_back(std::move(ConstantPool)); Descriptors.push_back(std::move(JumpTable)); - return rcc::InstructionSet::create(std::move(Descriptors)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); } [[nodiscard]] std::unique_ptr<rcc::MachineFunction> @@ -178,7 +182,7 @@ buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32 } [[nodiscard]] bool populateValidPreLegalizer(TestContext &Context, rcc::MachineFunction &Function, - const rcc::InstructionSet &InstructionSet) { + const rcc::TargetDescription &Target) { rcc::MachineBasicBlock *Entry = createBlock(Context, Function); rcc::MachineBasicBlock *Exit = createBlock(Context, Function); if (Entry == nullptr || Exit == nullptr) @@ -190,7 +194,7 @@ buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32 if (!Value) return false; - rcc::MachineIRBuilder Builder(Function, InstructionSet); + rcc::MachineIRBuilder Builder(Function, Target); Context.expect(Builder.setInsertPoint(*Entry).has_value(), "entry insertion point must be valid"); auto Def = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode)); Context.expect(Def && Def->addDef(*Value).has_value(), "generic definition must be assembled"); @@ -219,18 +223,17 @@ buildNoOperand(TestContext &Context, rcc::MachineIRBuilder &Builder, std::uint32 } void testValidFunctionAndModes(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = createFunction(Context, "valid", 1); - if (!InstructionSet || !Function || - !populateValidPreLegalizer(Context, *Function, *InstructionSet)) { + if (!Target || !Function || !populateValidPreLegalizer(Context, *Function, *Target)) { return; } - Context.expect(rcc::MachineVerifier::verify(*Function, *InstructionSet).has_value(), + Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(), "well-formed function must pass generic verification"); Context.expect( rcc::MachineVerifier::verify( - *Function, *InstructionSet, + *Function, *Target, rcc::MachineVerifierOptions{rcc::MachineVerifierMode::PreLegalizer, std::nullopt, true}) .has_value(), "typed SSA input must pass pre-legalizer verification"); @@ -239,20 +242,20 @@ void testValidFunctionAndModes(TestContext &Context) { auto DetachedInstruction = Function->createInstruction(rcc::Opcode::fromValue(NopOpcode)); Context.expect(DetachedBlock && DetachedInstruction, "detached arena history must be allocatable"); - Context.expect(rcc::MachineVerifier::verify(*Function, *InstructionSet).has_value(), + Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(), "empty detached arena objects must not invalidate live layout"); Function->properties().set(rcc::MachineFunctionProperty::Legalized); Context.expect( !rcc::MachineVerifier::verify( - *Function, *InstructionSet, + *Function, *Target, rcc::MachineVerifierOptions{rcc::MachineVerifierMode::PreLegalizer, std::nullopt, true}), "pre-legalizer mode must reject later-stage properties"); } void testReachableStructuralFailures(TestContext &Context) { - auto InstructionSet = createInstructionSet(); - if (!InstructionSet) + auto Target = createTarget(); + if (!Target) return; auto Unknown = createFunction(Context, "unknown_opcode", 2); @@ -261,7 +264,7 @@ void testReachableStructuralFailures(TestContext &Context) { auto Instruction = Unknown->createInstruction(rcc::Opcode::fromValue(999)); Context.expect(Instruction && UnknownBlock->appendInstruction(**Instruction).has_value(), "unknown opcode negative test must be linked"); - Context.expect(!rcc::MachineVerifier::verify(*Unknown, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*Unknown, *Target), "live unregistered opcode must fail verification"); } @@ -272,7 +275,7 @@ void testReachableStructuralFailures(TestContext &Context) { auto Instruction = WrongOperands->createInstruction(rcc::Opcode::fromValue(GenericDefOpcode)); Context.expect(Instruction && WrongBlock->appendInstruction(**Instruction).has_value(), "wrong-operand negative test must be linked"); - Context.expect(!rcc::MachineVerifier::verify(*WrongOperands, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*WrongOperands, *Target), "descriptor operand-count mismatch must fail verification"); } @@ -282,7 +285,7 @@ void testReachableStructuralFailures(TestContext &Context) { auto Instruction = DetachedChild->createInstruction(rcc::Opcode::fromValue(NopOpcode)); Context.expect(Block && Instruction && (*Block)->appendInstruction(**Instruction).has_value(), "detached block negative test must contain an instruction"); - Context.expect(!rcc::MachineVerifier::verify(*DetachedChild, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*DetachedChild, *Target), "detached block retaining instructions must fail verification"); } @@ -294,14 +297,14 @@ void testReachableStructuralFailures(TestContext &Context) { .addSuccessor(**Detached, *Linked, rcc::BranchProbability::one()) .has_value(), "detached CFG negative test edge must be created"); - Context.expect(!rcc::MachineVerifier::verify(*DetachedCFG, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*DetachedCFG, *Target), "detached block retaining CFG state must fail verification"); } } void testPropertyFailures(TestContext &Context) { - auto InstructionSet = createInstructionSet(); - if (!InstructionSet) + auto Target = createTarget(); + if (!Target) return; auto Type = rcc::LLT::makeInteger(64); @@ -309,7 +312,7 @@ void testPropertyFailures(TestContext &Context) { rcc::MachineBasicBlock *SSABlock = SSA ? createBlock(Context, *SSA) : nullptr; if (SSA && SSABlock != nullptr) { auto Value = SSA->registerInfo().createVirtualRegister(*Type); - rcc::MachineIRBuilder Builder(*SSA, *InstructionSet); + rcc::MachineIRBuilder Builder(*SSA, *Target); Context.expect(Value && Builder.setInsertPoint(*SSABlock).has_value(), "SSA negative test setup must succeed"); for (int Index = 0; Index != 2 && Value; ++Index) { @@ -318,7 +321,7 @@ void testPropertyFailures(TestContext &Context) { "SSA negative test definition must build"); } SSA->properties().set(rcc::MachineFunctionProperty::IsSSA); - Context.expect(!rcc::MachineVerifier::verify(*SSA, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*SSA, *Target), "IsSSA must reject a multiply-defined virtual register"); } @@ -326,14 +329,14 @@ void testPropertyFailures(TestContext &Context) { rcc::MachineBasicBlock *PHIBlock = PHI ? createBlock(Context, *PHI) : nullptr; if (PHI && PHIBlock != nullptr) { auto Value = PHI->registerInfo().createVirtualRegister(*Type); - rcc::MachineIRBuilder Builder(*PHI, *InstructionSet); + rcc::MachineIRBuilder Builder(*PHI, *Target); Context.expect(Value && Builder.setInsertPoint(*PHIBlock).has_value(), "PHI negative test setup must succeed"); auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(PhiOpcode)); Context.expect(Pending && Pending->addDef(*Value).has_value() && Pending->build(), "PHI negative test instruction must build"); PHI->properties().set(rcc::MachineFunctionProperty::NoPHIs); - Context.expect(!rcc::MachineVerifier::verify(*PHI, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*PHI, *Target), "NoPHIs must reject a PHI instruction"); } @@ -341,7 +344,7 @@ void testPropertyFailures(TestContext &Context) { if (NoVRegs) { rcc::MachineBasicBlock *Block = createBlock(Context, *NoVRegs); auto Value = NoVRegs->registerInfo().createVirtualRegister(*Type); - rcc::MachineIRBuilder Builder(*NoVRegs, *InstructionSet); + rcc::MachineIRBuilder Builder(*NoVRegs, *Target); Context.expect(Block != nullptr && Builder.setInsertPoint(*Block).has_value(), "NoVRegs negative test setup must succeed"); auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(GenericDefOpcode)); @@ -351,12 +354,12 @@ void testPropertyFailures(TestContext &Context) { return; auto Built = Pending->build(); NoVRegs->properties().set(rcc::MachineFunctionProperty::NoVRegs); - Context.expect(Value && Built && !rcc::MachineVerifier::verify(*NoVRegs, *InstructionSet), + Context.expect(Value && Built && !rcc::MachineVerifier::verify(*NoVRegs, *Target), "NoVRegs must reject a live virtual register"); Context.expect(Value && Built && Block != nullptr && Block->removeInstruction(**Built).has_value() && NoVRegs->registerInfo().eraseVirtualRegister(*Value).has_value() && - rcc::MachineVerifier::verify(*NoVRegs, *InstructionSet).has_value(), + rcc::MachineVerifier::verify(*NoVRegs, *Target).has_value(), "NoVRegs must accept tombstoned def-use history"); } @@ -367,7 +370,7 @@ void testPropertyFailures(TestContext &Context) { StackSlot.Size = 8; StackSlot.Align = *rcc::Alignment::create(8); auto FrameIndex = Frame->frameInfo().createObject(StackSlot); - rcc::MachineIRBuilder Builder(*Frame, *InstructionSet); + rcc::MachineIRBuilder Builder(*Frame, *Target); Context.expect(FrameIndex && Builder.setInsertPoint(*FrameBlock).has_value(), "frame-index negative test setup must succeed"); if (!FrameIndex) @@ -376,27 +379,27 @@ void testPropertyFailures(TestContext &Context) { Context.expect(Pending && Pending->addFrameIndex(*FrameIndex).has_value() && Pending->build(), "frame-index negative test instruction must build"); Frame->properties().set(rcc::MachineFunctionProperty::NoFrameIndices); - Context.expect(!rcc::MachineVerifier::verify(*Frame, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*Frame, *Target), "NoFrameIndices must reject a frame-index operand"); } auto Pseudo = createFunction(Context, "pseudo", 10); rcc::MachineBasicBlock *PseudoBlock = Pseudo ? createBlock(Context, *Pseudo) : nullptr; if (Pseudo && PseudoBlock != nullptr) { - rcc::MachineIRBuilder Builder(*Pseudo, *InstructionSet); + rcc::MachineIRBuilder Builder(*Pseudo, *Target); Context.expect(Builder.setInsertPoint(*PseudoBlock).has_value() && buildNoOperand(Context, Builder, PseudoOpcode) != nullptr, "pseudo negative test instruction must build"); Pseudo->properties().set(rcc::MachineFunctionProperty::NoExecutablePseudos); - Context.expect(!rcc::MachineVerifier::verify(*Pseudo, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*Pseudo, *Target), "NoExecutablePseudos must reject an executable pseudo"); } } void testSelectedStateAndPseudoDeadline(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Type = rcc::LLT::makeInteger(64); - if (!InstructionSet) + if (!Target) return; auto Tied = createFunction(Context, "tied", 11); @@ -416,7 +419,7 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) { "selected-state register constraints must be recorded"); } - rcc::MachineIRBuilder Builder(*Tied, *InstructionSet); + rcc::MachineIRBuilder Builder(*Tied, *Target); Context.expect(Builder.setInsertPoint(*TiedBlock).has_value(), "tied-state insertion point must be valid"); auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); @@ -428,30 +431,30 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) { rcc::MachineFunctionProperty::Selected, rcc::MachineFunctionProperty::TiedOpsRewritten}) { Tied->properties().set(Property); } - Context.expect(!rcc::MachineVerifier::verify(*Tied, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*Tied, *Target), "TiedOpsRewritten must reject unequal tied registers"); rcc::MachineInstr *Instruction = TiedBlock->firstInstruction(); Context.expect(Instruction != nullptr && Instruction->operandAt(1)->setRegister(*Def).has_value() && - rcc::MachineVerifier::verify(*Tied, *InstructionSet).has_value(), + rcc::MachineVerifier::verify(*Tied, *Target).has_value(), "equal tied registers must satisfy TiedOpsRewritten"); } auto Pseudo = createFunction(Context, "pseudo_deadline", 12); rcc::MachineBasicBlock *Block = Pseudo ? createBlock(Context, *Pseudo) : nullptr; if (Pseudo && Block != nullptr) { - rcc::MachineIRBuilder Builder(*Pseudo, *InstructionSet); + rcc::MachineIRBuilder Builder(*Pseudo, *Target); Context.expect(Builder.setInsertPoint(*Block).has_value() && buildNoOperand(Context, Builder, PseudoOpcode) != nullptr, "deadline pseudo must build"); Context.expect( - rcc::MachineVerifier::verify(*Pseudo, *InstructionSet, + rcc::MachineVerifier::verify(*Pseudo, *Target, rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic, rcc::PipelineStage::Selected}) .has_value(), "pseudo may remain at its declared deadline"); Context.expect(!rcc::MachineVerifier::verify( - *Pseudo, *InstructionSet, + *Pseudo, *Target, rcc::MachineVerifierOptions{rcc::MachineVerifierMode::Generic, rcc::PipelineStage::RegisterAllocated}), "pseudo must not survive beyond its declared deadline"); @@ -460,23 +463,23 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) { auto Dependency = createFunction(Context, "property_dependency", 13); if (Dependency) { Dependency->properties().set(rcc::MachineFunctionProperty::Selected); - Context.expect(!rcc::MachineVerifier::verify(*Dependency, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*Dependency, *Target), "Selected must require prior pipeline properties"); Dependency->properties().clear(rcc::MachineFunctionProperty::Selected); Dependency->properties().set(rcc::MachineFunctionProperty::FrameFinalized); - Context.expect(!rcc::MachineVerifier::verify(*Dependency, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(*Dependency, *Target), "FrameFinalized must require NoVRegs and NoFrameIndices"); } } void testFrameStateFailures(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = createFunction(Context, "frame_references", 14); rcc::MachineBasicBlock *Block = Function ? createBlock(Context, *Function) : nullptr; - if (!InstructionSet || !Function || Block == nullptr) + if (!Target || !Function || Block == nullptr) return; - rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + rcc::MachineIRBuilder Builder(*Function, *Target); Context.expect(Builder.setInsertPoint(*Block).has_value(), "frame reference insertion point must be valid"); auto Unknown = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); @@ -494,7 +497,7 @@ void testFrameStateFailures(TestContext &Context) { Context.expect(Live && Live->addFrameIndex(*FrameIndex).has_value() && Live->build(), "known live frame index must build"); Context.expect(Function->frameInfo().markObjectDead(*FrameIndex).has_value() && - !rcc::MachineVerifier::verify(*Function, *InstructionSet), + !rcc::MachineVerifier::verify(*Function, *Target), "verifier must reject an instruction that references a dead stack object"); auto Lifetime = createFunction(Context, "frame_lifetime", 15); @@ -506,16 +509,16 @@ void testFrameStateFailures(TestContext &Context) { LifetimeSlot.Lifetime.push_back( {rcc::MachineInstrStableID::fromValue(1), rcc::MachineInstrStableID::fromValue(2)}); Context.expect(Lifetime->frameInfo().createObject(LifetimeSlot).has_value() && - !rcc::MachineVerifier::verify(*Lifetime, *InstructionSet), + !rcc::MachineVerifier::verify(*Lifetime, *Target), "verifier must reject stack lifetimes that reference unknown instructions"); } void testTableReferenceFailures(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto ConstantFunction = createFunction(Context, "constant_reference", 16); rcc::MachineBasicBlock *ConstantBlock = ConstantFunction ? createBlock(Context, *ConstantFunction) : nullptr; - if (!InstructionSet || !ConstantFunction || ConstantBlock == nullptr) + if (!Target || !ConstantFunction || ConstantBlock == nullptr) return; rcc::MachineConstantPoolEntryDesc ConstantDescription; @@ -524,7 +527,7 @@ void testTableReferenceFailures(TestContext &Context) { ConstantDescription.Size = 4; ConstantDescription.Align = *rcc::Alignment::create(4); auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription)); - rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *InstructionSet); + rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *Target); Context.expect(ConstantIndex && ConstantBuilder.setInsertPoint(*ConstantBlock).has_value(), "constant-pool verifier setup must succeed"); if (!ConstantIndex) @@ -534,7 +537,7 @@ void testTableReferenceFailures(TestContext &Context) { ConstantInstruction->addConstantPool(*ConstantIndex).has_value() && ConstantInstruction->build() && ConstantFunction->constantPool().eraseEntry(*ConstantIndex).has_value() && - !rcc::MachineVerifier::verify(*ConstantFunction, *InstructionSet), + !rcc::MachineVerifier::verify(*ConstantFunction, *Target), "verifier must reject a tombstoned constant-pool reference"); auto JumpFunction = createFunction(Context, "jump_reference", 17); @@ -544,7 +547,7 @@ void testTableReferenceFailures(TestContext &Context) { rcc::MachineJumpTableDesc JumpDescription; JumpDescription.Targets = {JumpBlock}; auto JumpIndex = JumpFunction->jumpTableInfo().createTable(std::move(JumpDescription)); - rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *InstructionSet); + rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *Target); Context.expect(JumpIndex && JumpBuilder.setInsertPoint(*JumpBlock).has_value(), "jump-table verifier setup must succeed"); if (!JumpIndex) @@ -553,14 +556,14 @@ void testTableReferenceFailures(TestContext &Context) { Context.expect(JumpInstruction && JumpInstruction->addJumpTable(*JumpIndex).has_value() && JumpInstruction->build() && JumpFunction->jumpTableInfo().eraseTable(*JumpIndex).has_value() && - !rcc::MachineVerifier::verify(*JumpFunction, *InstructionSet), + !rcc::MachineVerifier::verify(*JumpFunction, *Target), "verifier must reject a tombstoned jump-table reference"); } void testCompilationStatusReferences(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Unknown = createFunction(Context, "unknown_failure_instruction", 18); - if (!InstructionSet || !Unknown) + if (!Target || !Unknown) return; rcc::MachineCompilationFailure UnknownFailure; @@ -572,14 +575,14 @@ void testCompilationStatusReferences(TestContext &Context) { Unknown->compilationStatus() .fail(rcc::MachineCompilationState::SelectionFailed, std::move(UnknownFailure)) .has_value() && - !rcc::MachineVerifier::verify(*Unknown, *InstructionSet), + !rcc::MachineVerifier::verify(*Unknown, *Target), "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); + rcc::MachineIRBuilder Builder(*Live, *Target); Context.expect(Builder.setInsertPoint(*Block).has_value(), "live failure instruction insertion point must be valid"); rcc::MachineInstr *Instruction = buildNoOperand(Context, Builder, NopOpcode); @@ -595,7 +598,7 @@ void testCompilationStatusReferences(TestContext &Context) { Live->compilationStatus() .fail(rcc::MachineCompilationState::FinalizationFailed, std::move(LiveFailure)) .has_value() && - rcc::MachineVerifier::verify(*Live, *InstructionSet).has_value(), + rcc::MachineVerifier::verify(*Live, *Target).has_value(), "verifier must accept a structurally valid failed function"); } diff --git a/backend/unittests/CodeGen/TargetFunctionStateTest.cpp b/backend/unittests/CodeGen/TargetFunctionStateTest.cpp index a306b8f..2286400 100644 --- a/backend/unittests/CodeGen/TargetFunctionStateTest.cpp +++ b/backend/unittests/CodeGen/TargetFunctionStateTest.cpp @@ -2,6 +2,7 @@ #include "rcc/CodeGen/TargetFunctionState.h" +#include "Target/TestTarget.h" #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionCloner.h" @@ -146,14 +147,17 @@ struct CollidingFunctionInfo final { const CollidingFunctionInfo &Right) = default; }; -[[nodiscard]] rcc::Expected<rcc::InstructionSet> createInstructionSet() { +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { 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)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); } void testTargetFunctionStateContract(TestContext &Context) { @@ -212,10 +216,10 @@ void testTargetFunctionStateFailures(TestContext &Context) { } void testMachineFunctionOwnershipAndVerification(TestContext &Context) { - auto InstructionSet = createInstructionSet(); + auto Target = createTarget(); auto Function = rcc::MachineFunction::create("target_state", rcc::MachineFunctionStableID::fromValue(1)); - if (!InstructionSet || !Function) + if (!Target || !Function) return; Context.expect(!(*Function)->setTargetState(rcc::TargetFunctionState{}), @@ -225,7 +229,7 @@ void testMachineFunctionOwnershipAndVerification(TestContext &Context) { "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(), + rcc::MachineVerifier::verify(**Function, *Target).has_value(), "machine function must expose and verify its strong target state"); auto Replacement = rcc::TargetFunctionState::create(TestFunctionInfo{8192, true, "lp64d"}); @@ -236,7 +240,7 @@ void testMachineFunctionOwnershipAndVerification(TestContext &Context) { if (Info == nullptr) return; Info->CallingConvention.clear(); - Context.expect(!rcc::MachineVerifier::verify(**Function, *InstructionSet), + Context.expect(!rcc::MachineVerifier::verify(**Function, *Target), "machine verifier must reject invalid concrete target state"); } diff --git a/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp b/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp new file mode 100644 index 0000000..da13e19 --- /dev/null +++ b/backend/unittests/CodeGen/TargetRegisterInfoTest.cpp @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetRegisterInfo.h" + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" + +#include <cstdint> +#include <iostream> +#include <memory> +#include <ranges> +#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; +}; + +[[nodiscard]] rcc::PhysicalRegister phys(std::uint32_t Number) { + return *rcc::PhysicalRegister::create(Number); +} + +[[nodiscard]] rcc::OperandDescriptor registerOperand(rcc::RegisterOperandRole Role, + rcc::RegisterClassID Class, + rcc::RegisterBankID Bank, + bool AllowSubReg = false) { + rcc::OperandDescriptor Operand; + Operand.Kinds = {rcc::MachineOperand::Kind::Register}; + Operand.RegisterRole = Role; + Operand.RegClass = Class; + Operand.RegBank = Bank; + Operand.AllowSubReg = AllowSubReg; + return Operand; +} + +[[nodiscard]] rcc::Expected<rcc::TargetDescription> +createTarget(rcc::TargetRegisterInfoDesc RegisterDescription, + rcc::InstructionDescriptor Instruction) { + auto Registers = rcc::TargetRegisterInfo::create(std::move(RegisterDescription)); + if (!Registers) + return std::unexpected(Registers.error()); + + std::vector<rcc::InstructionDescriptor> Descriptors; + Descriptors.push_back(std::move(Instruction)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::TargetDescription::create(std::move(*Instructions), std::move(*Registers)); +} + +[[nodiscard]] rcc::Expected<rcc::TargetDescription> +createOperandTarget(rcc::OperandDescriptor Operand, + rcc::TargetRegisterInfoDesc RegisterDescription = + rcc::test::createTestTargetRegisterInfoDescription()) { + rcc::InstructionDescriptor Instruction; + Instruction.InstructionOpcode = rcc::Opcode::fromValue(1); + Instruction.Name = "TEST_CONSTRAINED"; + Instruction.Operands.push_back(Operand); + if (Instruction.Operands.front().RegisterRole == rcc::RegisterOperandRole::Def) { + Instruction.NumExplicitDefs = 1; + } + Instruction.Flags = {rcc::InstructionFlag::ProducesCode}; + + return createTarget(std::move(RegisterDescription), std::move(Instruction)); +} + +void testRegisterTopology(TestContext &Context) { + auto Target = rcc::test::createTestTargetRegisterInfo(); + Context.expect(Target.has_value(), "TestTarget register description must be valid"); + if (!Target) + return; + + const auto G0 = phys(rcc::test::TestRegister::G0); + const auto G0Low32 = phys(rcc::test::TestRegister::G0Low32); + const auto G0High32 = phys(rcc::test::TestRegister::G0High32); + const auto G0Low16 = phys(rcc::test::TestRegister::G0Low16); + const auto G1 = phys(rcc::test::TestRegister::G1); + const auto F0 = phys(rcc::test::TestRegister::F0); + + Context.expect(Target->registerUnits().size() == 11 && Target->physicalRegisters().size() == 11 && + Target->registerClasses().size() == 8 && Target->registerBanks().size() == 2, + "TestTarget must expose its complete static register tables"); + const rcc::PhysicalRegisterDesc *Named = Target->physicalRegister("g0"); + Context.expect(Named != nullptr && Named->Reg == G0 && Named->Encoding == 0, + "physical register name lookup must preserve identity and encoding"); + + Context.expect(Target->regsOverlap(G0, G0Low32) && Target->regsOverlap(G0, G0High32) && + Target->regsOverlap(G0Low32, G0Low16) && + !Target->regsOverlap(G0Low32, G0High32) && !Target->regsOverlap(G0, G1), + "physical overlap must be derived from shared register units"); + Context.expect(std::ranges::find(Target->aliases(G0), G0Low32) != Target->aliases(G0).end() && + std::ranges::find(Target->aliases(G0Low32), G0High32) == + Target->aliases(G0Low32).end(), + "alias queries must include only overlapping physical registers"); + + Context.expect(Target->subRegister(G0, rcc::test::TestSubReg::Low32) == G0Low32 && + Target->subRegister(G0Low32, rcc::test::TestSubReg::Low16) == G0Low16 && + Target->isSubRegister(G0, G0Low16) && + std::ranges::find(Target->superRegisters(G0Low16), G0) != + Target->superRegisters(G0Low16).end() && + Target->composeSubRegIndices(rcc::test::TestSubReg::Low32, + rcc::test::TestSubReg::Low16) == + rcc::test::TestSubReg::Low16, + "subregister queries must preserve direct, transitive, and composed mappings"); + Context.expect(Target->isValidLaneMask(G0, rcc::LaneMask::fromRaw(0x3)) && + Target->isValidLaneMask(G0, rcc::LaneMask::all()) && + !Target->isValidLaneMask(G0, rcc::LaneMask::fromRaw(0x10)), + "lane masks must be checked against target register coverage"); + + Context.expect( + Target->isSubclass(rcc::test::TestRegisterClass::GPR64Alloc, + rcc::test::TestRegisterClass::GPR64) && + !Target->isSubclass(rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterClass::GPR64Alloc) && + Target->registerClassesOverlap(rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterClass::GPR64Alloc) && + Target->subRegClass(rcc::test::TestRegisterClass::GPR64, rcc::test::TestSubReg::Low32) == + rcc::test::TestRegisterClass::GPR32Low, + "register class queries must preserve hierarchy and subregister projection"); + Context.expect(Target->bankContainsRegister(rcc::test::TestRegisterBank::GPR, G0) && + !Target->bankContainsRegister(rcc::test::TestRegisterBank::GPR, F0) && + !Target->registerBanksOverlap(rcc::test::TestRegisterBank::GPR, + rcc::test::TestRegisterBank::FPR), + "register bank coverage must remain distinct from register classes"); + + const auto CallMask = rcc::test::createTestCallPreservedMask(); + Context.expect(CallMask->preserves(G1) && + CallMask->preserves(phys(rcc::test::TestRegister::StackPointer)) && + CallMask->preserves(phys(rcc::test::TestRegister::F1)) && + CallMask->clobbers(G0) && Target->validateRegisterMask(*CallMask).has_value(), + "call register mask must use preserved-bit semantics"); + Context.expect(Target->validateRegisterMask(rcc::RegisterMask({})).has_value(), + "an empty register mask must represent clobber-all"); + Context.expect(!Target->validateRegisterMask(rcc::RegisterMask({std::uint64_t{1} << 63U})) && + !Target->validateRegisterMask(rcc::RegisterMask({1})), + "register masks must reject preserved bits for unknown registers and noreg"); +} + +void testDescriptionRejection(TestContext &Context) { + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterUnits.back().ID = Description.RegisterUnits.front().ID; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "duplicate register unit IDs must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.PhysicalSubRegisters.front().SubReg = phys(rcc::test::TestRegister::G1Low32); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "subregister mappings with unrelated units must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterClasses.front().SuperClasses.push_back( + rcc::test::TestRegisterClass::GPR64Alloc); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "cyclic register class hierarchies must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterBanks.front().MaximumSizeInBits = 32; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "register banks smaller than a covered class must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.SubRegClassMappings.pop_back(); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "supported subregisters without class projections must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterClasses.front().Lanes = rcc::LaneMask::fromRaw(0x10); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "register class lanes outside a member register must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterBanks.front().RegisterClasses.erase( + Description.RegisterBanks.front().RegisterClasses.begin()); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "a class default bank must explicitly cover that class"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterUnits.push_back({rcc::RegisterUnitID::fromValue(99), "unused"}); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "unreferenced register units must be rejected"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.SubRegCompositions.push_back( + {rcc::test::TestSubReg::Low16, rcc::test::TestSubReg::Low16, rcc::test::TestSubReg::Low32}); + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "subregister composition bit ranges must be internally consistent"); + } + { + auto Description = rcc::test::createTestTargetRegisterInfoDescription(); + Description.RegisterClasses[3].CoveredBySubRegs = true; + Context.expect(!rcc::TargetRegisterInfo::create(std::move(Description)), + "subregister-covered classes must cover all class lanes"); + } +} + +void testTargetDescriptionConstraints(TestContext &Context) { + auto SubclassTarget = createOperandTarget(registerOperand(rcc::RegisterOperandRole::Def, + rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterBank::GPR)); + auto Function = + rcc::MachineFunction::create("constraints", rcc::MachineFunctionStableID::fromValue(1)); + auto Type = rcc::LLT::makeInteger(64); + if (!SubclassTarget || !Function || !Type) + return; + + auto Reg = (*Function)->registerInfo().createVirtualRegister(*Type); + if (!Reg) + return; + Context.expect((*Function) + ->registerInfo() + .setRegisterBank(*Reg, rcc::test::TestRegisterBank::GPR) + .has_value() && + (*Function) + ->registerInfo() + .setRegisterClass(*Reg, rcc::test::TestRegisterClass::GPR64Alloc) + .has_value(), + "constrained test virtual register must be classified"); + + auto Operand = rcc::MachineOperand::registerOperand(*Reg, {rcc::RegisterOperandFlag::Define}); + Context.expect( + Operand && SubclassTarget + ->validateExplicitOperands(rcc::Opcode::fromValue(1), + std::span<const rcc::MachineOperand>(&*Operand, 1), + (*Function)->registerInfo()) + .has_value(), + "an actual register subclass must satisfy a superclass operand constraint"); + + auto SubRegTarget = createOperandTarget(registerOperand(rcc::RegisterOperandRole::Def, + rcc::test::TestRegisterClass::GPR32, + rcc::test::TestRegisterBank::GPR, true)); + auto BaseClassResult = + (*Function)->registerInfo().setRegisterClass(*Reg, rcc::test::TestRegisterClass::GPR64); + auto SubRegOperand = rcc::MachineOperand::registerOperand( + *Reg, {rcc::RegisterOperandFlag::Define}, rcc::test::TestSubReg::Low32); + Context.expect( + SubRegTarget && BaseClassResult && SubRegOperand && + SubRegTarget + ->validateExplicitOperands(rcc::Opcode::fromValue(1), + std::span<const rcc::MachineOperand>(&*SubRegOperand, 1), + (*Function)->registerInfo()) + .has_value(), + "subregister class projection must satisfy a compatible superclass constraint"); + + auto PhysicalSubReg = rcc::MachineOperand::registerOperand( + rcc::Register::fromPhysical(phys(rcc::test::TestRegister::G0)), + {rcc::RegisterOperandFlag::Define}, rcc::test::TestSubReg::Low32); + Context.expect(PhysicalSubReg && !SubRegTarget->validateExplicitOperands( + rcc::Opcode::fromValue(1), + std::span<const rcc::MachineOperand>(&*PhysicalSubReg, 1), + (*Function)->registerInfo()), + "physical register operands must reject nonzero subregister indices"); + + auto WideType = rcc::LLT::makeInteger(128); + if (!WideType) + return; + auto WideReg = (*Function)->registerInfo().createVirtualRegister(*WideType); + Context.expect(WideReg && + (*Function) + ->registerInfo() + .setRegisterBank(*WideReg, rcc::test::TestRegisterBank::GPR) + .has_value() && + !SubclassTarget->validateRegisterInfo((*Function)->registerInfo()), + "virtual register type must fit its selected bank"); + + auto UnknownClassOperand = + registerOperand(rcc::RegisterOperandRole::Def, rcc::RegisterClassID::fromValue(99), + rcc::test::TestRegisterBank::GPR); + Context.expect(!createOperandTarget(UnknownClassOperand), + "target creation must reject unknown operand register classes"); + auto MismatchedBankOperand = + registerOperand(rcc::RegisterOperandRole::Def, rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterBank::FPR); + Context.expect(!createOperandTarget(MismatchedBankOperand), + "target creation must reject incompatible class and bank constraints"); + + auto ConstantDescription = rcc::test::createTestTargetRegisterInfoDescription(); + ConstantDescription.PhysicalRegisters.front().IsConstant = true; + auto ConstantTarget = createOperandTarget(registerOperand(rcc::RegisterOperandRole::Def, + rcc::test::TestRegisterClass::GPR64, + rcc::test::TestRegisterBank::GPR), + std::move(ConstantDescription)); + auto HintFunction = + rcc::MachineFunction::create("constant_hint", rcc::MachineFunctionStableID::fromValue(2)); + if (ConstantTarget && HintFunction) { + auto HintReg = (*HintFunction)->registerInfo().createVirtualRegister(*Type); + Context.expect(HintReg && + (*HintFunction) + ->registerInfo() + .setRegisterClass(*HintReg, rcc::test::TestRegisterClass::GPR64) + .has_value() && + (*HintFunction) + ->registerInfo() + .addAllocationHint(*HintReg, phys(rcc::test::TestRegister::G0)) + .has_value() && + !ConstantTarget->validateRegisterInfo((*HintFunction)->registerInfo()), + "constant physical registers must be rejected as allocation hints"); + } + + auto RenamableDescription = rcc::test::createTestTargetRegisterInfoDescription(); + RenamableDescription.PhysicalRegisters.front().IsArtificial = true; + rcc::InstructionDescriptor RenamableImplicit; + RenamableImplicit.InstructionOpcode = rcc::Opcode::fromValue(2); + RenamableImplicit.Name = "TEST_BAD_IMPLICIT"; + RenamableImplicit.ImplicitDefs.push_back({phys(rcc::test::TestRegister::G0), false, true}); + Context.expect(!createTarget(std::move(RenamableDescription), std::move(RenamableImplicit)), + "target creation must reject renamable artificial implicit registers"); + + rcc::InstructionDescriptor ConflictingImplicit; + ConflictingImplicit.InstructionOpcode = rcc::Opcode::fromValue(3); + ConflictingImplicit.Name = "TEST_BAD_EARLY_IMPLICIT"; + ConflictingImplicit.ImplicitDefs.push_back({phys(rcc::test::TestRegister::G0), true, false}); + ConflictingImplicit.ImplicitUses.push_back( + {phys(rcc::test::TestRegister::G0Low32), false, false}); + Context.expect(!createTarget(rcc::test::createTestTargetRegisterInfoDescription(), + std::move(ConflictingImplicit)), + "target creation must reject overlapping early-clobber implicit operands"); +} + +} // namespace + +int main() { + TestContext Context; + testRegisterTopology(Context); + testDescriptionRejection(Context); + testTargetDescriptionConstraints(Context); + return Context.result(); +} |
