From 0bb35fa2306a66f1f0baf118619b5c1640ed9c01 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Wed, 12 Aug 2026 21:56:37 +0800 Subject: [GlobalISel] Split the generic pipeline components --- .../unittests/CodeGen/GlobalISel/LegalizerTest.cpp | 205 +++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 backend/unittests/CodeGen/GlobalISel/LegalizerTest.cpp (limited to 'backend/unittests/CodeGen/GlobalISel/LegalizerTest.cpp') diff --git a/backend/unittests/CodeGen/GlobalISel/LegalizerTest.cpp b/backend/unittests/CodeGen/GlobalISel/LegalizerTest.cpp new file mode 100644 index 0000000..6280182 --- /dev/null +++ b/backend/unittests/CodeGen/GlobalISel/LegalizerTest.cpp @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/GlobalISel/Legalizer.h" + +#include "GISelMITest.h" +#include "rcc/Target/RISCV/RISCVCodeGen.h" + +#include +#include +#include + +namespace { + +using rcc::test::buildAdd; +using rcc::test::createFixture; +using rcc::test::createFloatRegister; +using rcc::test::createRegister; +using rcc::test::TestContext; + +class TogglingLegalizer final : public rcc::LegalizerInfo { +public: + [[nodiscard]] rcc::Expected + classify(const rcc::MachineInstr &, const rcc::MachineFunction &, + const rcc::TargetDescription &) const override { + return rcc::LegalizationAction::Lower; + } + + [[nodiscard]] rcc::Expected lower(rcc::MachineInstr &Instruction, rcc::MachineFunction &, + const rcc::TargetDescription &, + rcc::LegalizationAction) const override { + const auto Generic = static_cast(Instruction.opcode().value()); + return Instruction.setOpcode(rcc::opcode(Generic == rcc::GenericOpcode::G_ADD + ? rcc::GenericOpcode::G_SUB + : rcc::GenericOpcode::G_ADD)); + } +}; + +class BackwardInvalidatingLegalizer final : public rcc::LegalizerInfo { +public: + [[nodiscard]] rcc::Expected + classify(const rcc::MachineInstr &Instruction, const rcc::MachineFunction &, + const rcc::TargetDescription &) const override { + return Instruction.opcode() == rcc::opcode(rcc::GenericOpcode::G_SUB) + ? rcc::LegalizationAction::Custom + : rcc::LegalizationAction::Legal; + } + + [[nodiscard]] rcc::Expected lower(rcc::MachineInstr &Instruction, + rcc::MachineFunction &Function, + const rcc::TargetDescription &, + rcc::LegalizationAction) const override { + for (rcc::MachineInstr *Candidate : Function.allInstructions()) { + if (Candidate != &Instruction && + Candidate->opcode() == rcc::opcode(rcc::GenericOpcode::G_ADD)) { + auto Invalidated = Candidate->setOpcode(rcc::opcode(rcc::GenericOpcode::G_SUB)); + if (!Invalidated) + return std::unexpected(Invalidated.error()); + break; + } + } + return Instruction.setOpcode( + rcc::Opcode::fromValue(static_cast(rcc::riscv::InstructionOpcode::ADD))); + } +}; + +void testNullAndMissingContracts(TestContext &Context) { + auto Target = rcc::riscv::createTargetDescription(); + auto Fixture = createFixture(Context, "pipeline_contracts", 1); + Context.expect(Target.has_value() && Fixture.Function != nullptr && Fixture.Entry != nullptr, + "pipeline contract fixture must be complete"); + if (!Target || !Fixture.Function) + return; + + rcc::Legalizer NullPass(nullptr); + rcc::MachineAnalysisManager Analyses; + auto NullResult = NullPass.run(*Fixture.Function, *Target, Analyses); + Context.expect(!NullResult && NullResult.error().code() == rcc::ErrorCode::InvalidArgument, + "a null legalizer interface must be rejected"); + Context.expect(Fixture.Function->properties().empty(), + "a rejected pass must not publish function properties"); + + rcc::Legalizer ValidPass(rcc::riscv::legalizerInfo()); + auto MissingResult = ValidPass.run(*Fixture.Function, *Target, Analyses); + Context.expect(!MissingResult && + MissingResult.error().code() == rcc::ErrorCode::VerificationError, + "a pass with missing prerequisites must be rejected"); +} + +void testUnsupportedRollback(TestContext &Context) { + auto Target = rcc::riscv::createTargetDescription(); + if (!Target) + return; + auto Unsupported = createFixture(Context, "pipeline_unsupported", 3); + Context.expect(Unsupported.Function != nullptr && Unsupported.Entry != nullptr, + "unsupported pipeline fixture must be complete"); + if (!Unsupported.Function || Unsupported.Entry == nullptr) + return; + const rcc::Register UnsupportedReg = createFloatRegister(Context, *Unsupported.Function); + const rcc::Register UnsupportedSource = createFloatRegister(Context, *Unsupported.Function); + rcc::MachineIRBuilder UnsupportedBuilder(*Unsupported.Function, *Target); + Context.expect(UnsupportedBuilder.setInsertPoint(*Unsupported.Entry).has_value(), + "unsupported pipeline insertion point must be valid"); + if (UnsupportedReg.isNoRegister() || UnsupportedSource.isNoRegister()) + return; + auto Pending = UnsupportedBuilder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FEXP)); + Context.expect(Pending.has_value(), "unsupported generic instruction must be constructible"); + if (!Pending) + return; + auto Built = Pending->addDef(UnsupportedReg); + Context.expect(Built.has_value(), "unsupported generic definition must be accepted"); + if (!Built) + return; + Context.expect(Pending->addUse(UnsupportedSource).has_value() && Pending->build().has_value(), + "unsupported floating operation must be constructible"); + Unsupported.Function->properties() = {rcc::MachineFunctionProperty::IsSSA, + rcc::MachineFunctionProperty::TracksLiveness}; + rcc::MachinePassManager OnlyLegalizer; + Context.expect( + OnlyLegalizer.addPass(std::make_unique(rcc::riscv::legalizerInfo())) + .has_value(), + "standalone legalizer must be accepted"); + rcc::MachineAnalysisManager UnsupportedAnalyses; + auto Failure = OnlyLegalizer.run(*Unsupported.Function, *Target, UnsupportedAnalyses); + Context.expect(!Failure && Failure.error().code() == rcc::ErrorCode::Unsupported, + "unsupported generic instructions must produce a target diagnostic"); + Context.expect(!Failure && Failure.error().message().find("G_FEXP") != std::string::npos, + "unsupported legalization diagnostics must name the rejected opcode"); + Context.expect( + Unsupported.Function->properties().has(rcc::MachineFunctionProperty::IsSSA) && + !Unsupported.Function->properties().has(rcc::MachineFunctionProperty::Legalized), + "failed legalizer execution must not publish Legalized"); +} + +void testLegalizerCycleDiagnostic(TestContext &Context) { + auto Target = rcc::riscv::createTargetDescription(); + auto Fixture = createFixture(Context, "pipeline_legalizer_cycle", 6); + Context.expect(Target.has_value() && Fixture.Function != nullptr && Fixture.Entry != nullptr, + "legalizer cycle fixture must be complete"); + if (!Target || !Fixture.Function || Fixture.Entry == nullptr) + return; + const rcc::Register Left = createRegister(Context, *Fixture.Function, 64); + const rcc::Register Right = createRegister(Context, *Fixture.Function, 64); + const rcc::Register Result = createRegister(Context, *Fixture.Function, 64); + rcc::MachineIRBuilder Builder(*Fixture.Function, *Target); + if (Left.isNoRegister() || Right.isNoRegister() || Result.isNoRegister() || + !Builder.setInsertPoint(*Fixture.Entry) || !buildAdd(Context, Builder, Result, Left, Right)) + return; + Fixture.Function->properties() = {rcc::MachineFunctionProperty::IsSSA, + rcc::MachineFunctionProperty::TracksLiveness}; + TogglingLegalizer Legalizer; + rcc::Legalizer StablePass(Legalizer); + rcc::MachineAnalysisManager Analyses; + auto ResultRun = StablePass.run(*Fixture.Function, *Target, Analyses); + Context.expect(!ResultRun && ResultRun.error().code() == rcc::ErrorCode::InternalError, + "repeated legalization states must report InternalError"); + Context.expect(Fixture.Entry->firstInstruction() != nullptr && + Fixture.Entry->firstInstruction()->opcode() == + rcc::opcode(rcc::GenericOpcode::G_ADD), + "failed legalization preflight must preserve the original instruction"); + Context.expect(!Fixture.Function->properties().has(rcc::MachineFunctionProperty::Legalized), + "a failed legalization cycle must not publish Legalized"); +} + +void testLegalizerRevisitsInvalidatedInstructions(TestContext &Context) { + auto Target = rcc::riscv::createTargetDescription(); + auto Fixture = createFixture(Context, "pipeline_legalizer_revisit", 7); + if (!Target || !Fixture.Function || Fixture.Entry == nullptr) + return; + const rcc::Register Left = createRegister(Context, *Fixture.Function, 64); + const rcc::Register Right = createRegister(Context, *Fixture.Function, 64); + const rcc::Register FirstResult = createRegister(Context, *Fixture.Function, 64); + const rcc::Register SecondResult = createRegister(Context, *Fixture.Function, 64); + rcc::MachineIRBuilder Builder(*Fixture.Function, *Target); + if (Left.isNoRegister() || Right.isNoRegister() || FirstResult.isNoRegister() || + SecondResult.isNoRegister() || !Builder.setInsertPoint(*Fixture.Entry) || + !buildAdd(Context, Builder, FirstResult, Left, Right) || + !buildAdd(Context, Builder, SecondResult, Left, Right)) + return; + rcc::MachineInstr *First = Fixture.Entry->firstInstruction(); + rcc::MachineInstr *Second = First == nullptr ? nullptr : First->next(); + if (Second == nullptr || !Second->setOpcode(rcc::opcode(rcc::GenericOpcode::G_SUB))) + return; + + Fixture.Function->properties() = {rcc::MachineFunctionProperty::IsSSA, + rcc::MachineFunctionProperty::TracksLiveness}; + BackwardInvalidatingLegalizer Legalizer; + rcc::Legalizer Pass(Legalizer); + rcc::MachineAnalysisManager Analyses; + auto Run = Pass.run(*Fixture.Function, *Target, Analyses); + const rcc::Opcode LegalOpcode = + rcc::Opcode::fromValue(static_cast(rcc::riscv::InstructionOpcode::ADD)); + Context.expect(Run && First->opcode() == LegalOpcode && Second->opcode() == LegalOpcode, + "legalizer must revisit an earlier instruction invalidated by a later lowering"); +} + +} // namespace + +int main() { + TestContext Context; + testNullAndMissingContracts(Context); + testUnsupportedRollback(Context); + testLegalizerCycleDiagnostic(Context); + testLegalizerRevisitsInvalidatedInstructions(Context); + return Context.result(); +} -- cgit v1.2.3