From 924dcd36ebed84f04729de05ec08b311a36a04c1 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sat, 1 Aug 2026 16:07:24 +0800 Subject: [CodeGen] Add target register descriptions Introduce an immutable register topology for units, subregisters, classes, banks, and preserved masks. Bind instruction descriptors to this topology through TargetDescription so builders, cloning, and verification share one target-aware contract. Add the test-only TestTarget model and positive and negative target fact coverage. --- backend/unittests/CodeGen/CMakeLists.txt | 44 +- .../CodeGen/MachineFunctionClonerTest.cpp | 37 +- backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 53 +-- .../CodeGen/MachineVerifierTargetTest.cpp | 485 +++++++++++++++++++++ backend/unittests/CodeGen/MachineVerifierTest.cpp | 115 ++--- .../unittests/CodeGen/TargetFunctionStateTest.cpp | 16 +- .../unittests/CodeGen/TargetRegisterInfoTest.cpp | 359 +++++++++++++++ 7 files changed, 1000 insertions(+), 109 deletions(-) create mode 100644 backend/unittests/CodeGen/MachineVerifierTargetTest.cpp create mode 100644 backend/unittests/CodeGen/TargetRegisterInfoTest.cpp (limited to 'backend/unittests/CodeGen') 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 createInstructionSet() { +[[nodiscard]] rcc::Expected 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 @@ -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 createInstructionSet() { +[[nodiscard]] rcc::Expected 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 @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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 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 Class = std::nullopt, + std::optional 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 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 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 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 +appendInstruction(FunctionFixture &Fixture, std::uint32_t OpcodeValue, + std::vector Operands, + std::optional 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 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 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( + std::vector{std::uint64_t{1} << 63U}); + auto Operand = rcc::MachineOperand::registerMask(std::move(Mask)); + if (Fixture.Function && Operand) { + std::vector 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 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 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 createInstructionSet() { +[[nodiscard]] rcc::Expected 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 @@ -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 createInstructionSet() { +[[nodiscard]] rcc::Expected createTarget() { rcc::InstructionDescriptor Descriptor; Descriptor.InstructionOpcode = rcc::Opcode::fromValue(1); Descriptor.Name = "TEST_NOP"; Descriptor.Flags = {rcc::InstructionFlag::ProducesCode}; std::vector Descriptors; Descriptors.push_back(std::move(Descriptor)); - return rcc::InstructionSet::create(std::move(Descriptors)); + 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() != nullptr && (*Function)->getTargetInfo() == 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++FailureCount; + } + + [[nodiscard]] int result() const noexcept { + return FailureCount == 0 ? 0 : 1; + } + +private: + int FailureCount = 0; +}; + +[[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 +createTarget(rcc::TargetRegisterInfoDesc RegisterDescription, + rcc::InstructionDescriptor Instruction) { + auto Registers = rcc::TargetRegisterInfo::create(std::move(RegisterDescription)); + if (!Registers) + return std::unexpected(Registers.error()); + + std::vector 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 +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(&*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(&*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(&*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(); +} -- cgit v1.2.3