diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-09 17:03:19 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-09 17:03:19 +0800 |
| commit | 495c675771f43cfa12ab7a426a538be72fec1805 (patch) | |
| tree | 4107223d6c6ef2e74d56064aa21b196307502342 /backend/unittests/CodeGen/MachineSchedulingTest.cpp | |
| parent | da8bc76b4bf7d1a7b025d6c8ef5809551467dcc7 (diff) | |
| download | rcc-project-495c675771f43cfa12ab7a426a538be72fec1805.tar.gz rcc-project-495c675771f43cfa12ab7a426a538be72fec1805.tar.xz | |
[CodeGen] Implement machine optimization and scheduling
Diffstat (limited to 'backend/unittests/CodeGen/MachineSchedulingTest.cpp')
| -rw-r--r-- | backend/unittests/CodeGen/MachineSchedulingTest.cpp | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/MachineSchedulingTest.cpp b/backend/unittests/CodeGen/MachineSchedulingTest.cpp new file mode 100644 index 0000000..a485146 --- /dev/null +++ b/backend/unittests/CodeGen/MachineSchedulingTest.cpp @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineLiveness.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/MachineScheduler.h" +#include "rcc/CodeGen/PostRAScheduler.h" +#include "rcc/CodeGen/ScheduleDAG.h" +#include "rcc/CodeGen/ScheduleHazardRecognizer.h" +#include "rcc/CodeGen/TargetSchedulingModel.h" +#include "rcc/Target/RISCV/RISCVCodeGen.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <array> +#include <cstdint> +#include <iostream> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <vector> + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++Failures; + } + + [[nodiscard]] int result() const noexcept { + return Failures == 0 ? 0 : 1; + } + +private: + int Failures = 0; +}; + +struct Fixture final { + std::unique_ptr<rcc::MachineFunction> Function; + rcc::MachineBasicBlock *Block = nullptr; +}; + +class OnePerCycleHazard final : public rcc::TargetScheduleHazardRecognizer { +public: + [[nodiscard]] std::unique_ptr<rcc::TargetScheduleHazardRecognizer> clone() const override { + return std::make_unique<OnePerCycleHazard>(*this); + } + + [[nodiscard]] rcc::Expected<rcc::ScheduleHazardType> + getHazardType(const rcc::ScheduleUnit &) const override { + return Emitted ? rcc::ScheduleHazardType::Hazard : rcc::ScheduleHazardType::NoHazard; + } + + [[nodiscard]] rcc::Expected<void> emitInstruction(const rcc::ScheduleUnit &) override { + Emitted = true; + return {}; + } + + [[nodiscard]] rcc::Expected<void> advanceCycle() override { + Emitted = false; + return {}; + } + + [[nodiscard]] rcc::Expected<void> recedeCycle() override { + Emitted = false; + return {}; + } + + [[nodiscard]] rcc::Expected<void> reset() override { + Emitted = false; + return {}; + } + +private: + bool Emitted = false; +}; + +class TestSchedulingHooks final : public rcc::TargetSchedulingHooks { +public: + bool MakeADDIBoundary = false; + bool AddMutation = false; + bool PreferSecondUnit = false; + bool EnableDynamicHazards = false; + + [[nodiscard]] bool isSchedulingBoundary(const rcc::MachineInstr &Instruction, + const rcc::TargetDescription &) const noexcept override { + return MakeADDIBoundary && + Instruction.opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>( + rcc::riscv::InstructionOpcode::ADDI)); + } + + [[nodiscard]] rcc::Expected<void> mutateScheduleDAG(rcc::ScheduleDAG &DAG, + const rcc::MachineFunction &, + const rcc::TargetDescription &, + rcc::ScheduleDAGMode) const override { + if (!AddMutation || DAG.units().size() < 2) + return {}; + return DAG.addArtificialDependency(0, 1, 2); + } + + [[nodiscard]] std::int64_t candidatePreference(const rcc::ScheduleUnit &Unit, + rcc::ScheduleDirection, + const rcc::MachineBasicBlock &, + std::uint64_t) const noexcept override { + return PreferSecondUnit && Unit.ID == 1 ? 100 : 0; + } + + [[nodiscard]] std::unique_ptr<rcc::TargetScheduleHazardRecognizer> + createHazardRecognizer(rcc::ScheduleDirection) const override { + return EnableDynamicHazards ? std::make_unique<OnePerCycleHazard>() : nullptr; + } +}; + +[[nodiscard]] rcc::Opcode opcode(rcc::riscv::InstructionOpcode Value) noexcept { + return rcc::Opcode::fromValue(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] rcc::Expected<rcc::TargetSchedulingModel> +makeSchedulingModel(const rcc::TargetSchedulingHooks *Hooks = nullptr) { + rcc::TargetSchedulingModelDesc Description; + Description.IssueWidth = 2; + Description.DispatchLimit = 2; + for (const rcc::riscv::SchedulingClassInfo &Class : rcc::riscv::schedulingClasses()) { + Description.SchedulingClasses.push_back({ + .ID = Class.ID, + .Name = std::string(Class.Name), + .MicroOps = Class.MicroOps, + .Latency = Class.Latency, + .Resources = {}, + .WriteLatencies = {{0, Class.Latency}}, + .ReadAdvances = {}, + }); + } + Description.PressureSets.push_back({ + .ID = rcc::RegisterPressureSetID::fromValue(1), + .Name = "gpr", + .RegisterClasses = + { + rcc::RegisterClassID::fromValue( + static_cast<std::uint32_t>(rcc::riscv::RegisterClass::GPR)), + }, + .Limit = 27, + }); + Description.Hooks = Hooks; + return rcc::TargetSchedulingModel::create(std::move(Description)); +} + +[[nodiscard]] rcc::Register physical(TestContext &Context, rcc::riscv::PhysicalRegisterID ID) { + auto Value = rcc::riscv::physicalRegister(ID); + Context.expect(Value.has_value(), "physical register creation must succeed"); + return Value ? rcc::Register::fromPhysical(*Value) : rcc::Register{}; +} + +[[nodiscard]] Fixture makeFixture(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(), "scheduler function creation must succeed"); + if (!Function) + return {}; + auto Block = (*Function)->createBasicBlock(); + Context.expect(Block.has_value() && (*Function)->appendBlock(**Block).has_value(), + "scheduler block creation must succeed"); + if (!Block) + return {}; + (*Function)->properties().set(rcc::MachineFunctionProperty::Legalized); + (*Function)->properties().set(rcc::MachineFunctionProperty::RegBankSelected); + (*Function)->properties().set(rcc::MachineFunctionProperty::Selected); + (*Function)->properties().set(rcc::MachineFunctionProperty::NoPHIs); + (*Function)->properties().set(rcc::MachineFunctionProperty::TiedOpsRewritten); + return {std::move(*Function), *Block}; +} + +[[nodiscard]] rcc::Register makeGPR(TestContext &Context, rcc::MachineFunction &Function) { + auto Type = rcc::LLT::makeInteger(64); + auto Reg = Type ? Function.registerInfo().createVirtualRegister(*Type) + : rcc::Expected<rcc::Register>(std::unexpected( + rcc::Error(rcc::ErrorCode::InternalError, "test LLT creation failed"))); + Context.expect(Reg.has_value(), "scheduler virtual register creation must succeed"); + if (!Reg) + return {}; + const auto Bank = + rcc::RegisterBankID::fromValue(static_cast<std::uint32_t>(rcc::riscv::RegisterBank::GPR)); + const auto Class = + rcc::RegisterClassID::fromValue(static_cast<std::uint32_t>(rcc::riscv::RegisterClass::GPR)); + Context.expect(Function.registerInfo().setRegisterBank(*Reg, Bank).has_value() && + Function.registerInfo().setRegisterClass(*Reg, Class).has_value(), + "scheduler virtual register constraints must be assigned"); + return *Reg; +} + +[[nodiscard]] rcc::MachineInstr *buildADDI(TestContext &Context, rcc::MachineIRBuilder &Builder, + rcc::Register Destination, rcc::Register Source, + std::int64_t Immediate) { + auto Pending = Builder.buildInstr(opcode(rcc::riscv::InstructionOpcode::ADDI)); + if (!Pending || !Pending->addDef(Destination) || !Pending->addUse(Source) || + !Pending->addImmediate(Immediate)) { + Context.expect(false, "scheduler ADDI construction must succeed"); + return nullptr; + } + auto Built = Pending->build(); + Context.expect(Built.has_value(), "scheduler ADDI insertion must succeed"); + return Built.value_or(nullptr); +} + +[[nodiscard]] rcc::MachineInstr *buildBinary(TestContext &Context, rcc::MachineIRBuilder &Builder, + rcc::riscv::InstructionOpcode Opcode, + rcc::Register Destination, rcc::Register Left, + rcc::Register Right) { + auto Pending = Builder.buildInstr(opcode(Opcode)); + if (!Pending || !Pending->addDef(Destination) || !Pending->addUse(Left) || + !Pending->addUse(Right)) { + Context.expect(false, "scheduler binary instruction construction must succeed"); + return nullptr; + } + auto Built = Pending->build(); + Context.expect(Built.has_value(), "scheduler binary instruction insertion must succeed"); + return Built.value_or(nullptr); +} + +[[nodiscard]] bool hasDependency(const rcc::ScheduleUnit &Unit, std::size_t Destination, + rcc::ScheduleDependencyKind Kind) { + for (const rcc::ScheduleDependency &Dependency : Unit.Successors) { + if (Dependency.Destination == Destination && Dependency.Kind == Kind) + return true; + } + return false; +} + +void testDAGRegisterDependencies(TestContext &Context, const rcc::TargetDescription &Target) { + auto Fixture = makeFixture(Context, "schedule_dag", 1); + if (!Fixture.Function || Fixture.Block == nullptr) + return; + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "schedule DAG insertion point must be established"); + const rcc::Register X0 = physical(Context, rcc::riscv::PhysicalRegisterID::X0); + const rcc::Register X10 = physical(Context, rcc::riscv::PhysicalRegisterID::X10); + const rcc::Register Value = makeGPR(Context, *Fixture.Function); + const rcc::Register Consumer = makeGPR(Context, *Fixture.Function); + rcc::MachineInstr *Definition = buildADDI(Context, Builder, Value, X0, 1); + rcc::MachineInstr *Use = + buildBinary(Context, Builder, rcc::riscv::InstructionOpcode::ADD, Consumer, Value, X10); + rcc::MachineInstr *Redefinition = buildADDI(Context, Builder, Value, X0, 2); + std::array<rcc::MachineInstr *, 3> Instructions{Definition, Use, Redefinition}; + auto DAG = rcc::ScheduleDAG::build(*Fixture.Block, Instructions, *Fixture.Function, Target, + rcc::riscv::schedulingModel(), + rcc::ScheduleDAGMode::PreRegisterAllocation); + if (!DAG) + std::cerr << "schedule DAG failure: " << DAG.error().message() << '\n'; + Context.expect(DAG.has_value() && DAG->units().size() == 3, + "schedule DAG must create one unit per instruction"); + if (!DAG) + return; + Context.expect(hasDependency(DAG->units()[0], 1, rcc::ScheduleDependencyKind::Data), + "schedule DAG must model virtual-register data dependencies"); + Context.expect(hasDependency(DAG->units()[0], 2, rcc::ScheduleDependencyKind::Output), + "schedule DAG must model virtual-register output dependencies"); + Context.expect(hasDependency(DAG->units()[1], 2, rcc::ScheduleDependencyKind::Anti), + "schedule DAG must model virtual-register anti dependencies"); + Context.expect(DAG->units()[0].Latency != 0 && DAG->units()[0].MicroOps != 0, + "schedule DAG must attach target latency and resource facts"); +} + +void testHazardScoreboard(TestContext &Context) { + auto Hazards = rcc::ScheduleHazardRecognizer::create(rcc::riscv::schedulingModel()); + Context.expect(Hazards.has_value(), "hazard recognizer construction must succeed"); + if (!Hazards) + return; + const auto Memory = rcc::ProcessorResourceID::fromValue(3); + rcc::ScheduleUnit Load; + Load.MicroOps = 1; + Load.Resources = {{Memory, 1}}; + Context.expect(Hazards->issue(Load, 0).has_value(), + "first memory operation must issue in cycle zero"); + auto SecondCycle = Hazards->earliestIssueCycle(Load, 0); + Context.expect(SecondCycle.has_value() && *SecondCycle == 1, + "single-unit memory resource must serialize operations"); + + rcc::ScheduleUnit Wide; + Wide.MicroOps = 5; + auto Fresh = rcc::ScheduleHazardRecognizer::create(rcc::riscv::schedulingModel()); + Context.expect(Fresh.has_value() && Fresh->issue(Wide, 0).has_value(), + "multi-cycle dispatch must accept instructions wider than issue width"); + auto AfterWide = Fresh ? Fresh->earliestIssueCycle(Load, 0) + : rcc::Expected<std::uint64_t>(std::unexpected( + rcc::Error(rcc::ErrorCode::InternalError, "no recognizer"))); + Context.expect(AfterWide.has_value() && *AfterWide == 3, + "multi-cycle dispatch must block the complete dispatch interval"); +} + +void testOperandLatencyAndModelValidation(TestContext &Context) { + const rcc::SchedulingClassID ALU = rcc::SchedulingClassID::fromValue( + static_cast<std::uint32_t>(rcc::riscv::SchedulingClass::ALU)); + const rcc::SchedulingClassID Load = rcc::SchedulingClassID::fromValue( + static_cast<std::uint32_t>(rcc::riscv::SchedulingClass::LOAD)); + auto Forwarded = rcc::riscv::schedulingModel().operandLatency(Load, 0, ALU, 1); + auto Unforwarded = rcc::riscv::schedulingModel().operandLatency(Load, 0, ALU, 0); + auto ALUChain = rcc::riscv::schedulingModel().operandLatency(ALU, 0, ALU, 1); + Context.expect(Forwarded.has_value() && *Forwarded == 2 && Unforwarded.has_value() && + *Unforwarded == 3 && ALUChain.has_value() && *ALUChain == 1, + "operand latency must combine write latency with exact read advance"); + + rcc::TargetSchedulingModelDesc Invalid; + Invalid.SchedulingClasses.push_back({ + .ID = ALU, + .Name = "invalid", + .MicroOps = 1, + .Latency = 1, + .Resources = {}, + .WriteLatencies = {{0, 1}, {0, 2}}, + .ReadAdvances = {}, + }); + auto Rejected = rcc::TargetSchedulingModel::create(std::move(Invalid)); + Context.expect(!Rejected && Rejected.error().code() == rcc::ErrorCode::InvalidArgument, + "scheduling model must reject duplicate operand latency facts"); +} + +void testDynamicTargetHazards(TestContext &Context) { + TestSchedulingHooks Hooks; + Hooks.EnableDynamicHazards = true; + auto Model = makeSchedulingModel(&Hooks); + Context.expect(Model.has_value(), "dynamic-hazard scheduling model construction must succeed"); + if (!Model) + return; + + rcc::ScheduleUnit First; + First.ID = 1; + First.MicroOps = 1; + rcc::ScheduleUnit Second; + Second.ID = 2; + Second.MicroOps = 1; + auto Top = rcc::ScheduleHazardRecognizer::create(*Model, rcc::ScheduleDirection::TopDown); + Context.expect(Top.has_value() && Top->issue(First, 0).has_value(), + "target dynamic hazard must accept the first top-down issue"); + auto TopCycle = Top ? Top->earliestIssueCycle(Second, 0) + : rcc::Expected<std::uint64_t>(std::unexpected( + rcc::Error(rcc::ErrorCode::InternalError, "no top-down recognizer"))); + Context.expect(TopCycle.has_value() && *TopCycle == 1, + "target dynamic hazard must advance the top-down cycle"); + + auto Bottom = rcc::ScheduleHazardRecognizer::create(*Model, rcc::ScheduleDirection::BottomUp); + Context.expect(Bottom.has_value() && Bottom->issue(First, 0).has_value(), + "target dynamic hazard must accept the first bottom-up issue"); + auto BottomCycle = Bottom ? Bottom->earliestIssueCycle(Second, 0) + : rcc::Expected<std::uint64_t>(std::unexpected(rcc::Error( + rcc::ErrorCode::InternalError, "no bottom-up recognizer"))); + Context.expect(BottomCycle.has_value() && *BottomCycle == 1, + "target dynamic hazard must recede the bottom-up cycle"); +} + +void testTargetSchedulingHooks(TestContext &Context, const rcc::TargetDescription &Target) { + TestSchedulingHooks MutationHooks; + MutationHooks.AddMutation = true; + auto MutationModel = makeSchedulingModel(&MutationHooks); + auto Fixture = makeFixture(Context, "schedule_mutation", 4); + if (!MutationModel || !Fixture.Function || Fixture.Block == nullptr) { + Context.expect(false, "target schedule mutation fixture must be created"); + return; + } + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "target schedule mutation insertion point must be established"); + const rcc::Register X0 = physical(Context, rcc::riscv::PhysicalRegisterID::X0); + rcc::MachineInstr *First = + buildADDI(Context, Builder, makeGPR(Context, *Fixture.Function), X0, 1); + rcc::MachineInstr *Second = + buildADDI(Context, Builder, makeGPR(Context, *Fixture.Function), X0, 2); + std::array<rcc::MachineInstr *, 2> Instructions{First, Second}; + auto DAG = rcc::ScheduleDAG::build(*Fixture.Block, Instructions, *Fixture.Function, Target, + *MutationModel, rcc::ScheduleDAGMode::PreRegisterAllocation); + Context.expect(DAG.has_value() && + hasDependency(DAG->units()[0], 1, rcc::ScheduleDependencyKind::Artificial), + "target schedule mutation must add a checked artificial dependency"); + + TestSchedulingHooks PreferenceHooks; + PreferenceHooks.PreferSecondUnit = true; + auto PreferenceModel = makeSchedulingModel(&PreferenceHooks); + if (!PreferenceModel) { + Context.expect(false, "target scheduling preference model must be created"); + return; + } + rcc::MachineAnalysisManager Analyses; + rcc::MachineSchedulerPass PreferencePass( + *PreferenceModel, {.MaximumRegionSize = 8, .EnableBidirectionalScheduling = false}); + auto Preferred = PreferencePass.run(*Fixture.Function, Target, Analyses); + Context.expect(Preferred.has_value() && Preferred->Changed && + Fixture.Block->firstInstruction() == Second, + "target candidate preference must affect the production scheduler"); + + auto BoundaryFixture = makeFixture(Context, "schedule_boundary", 5); + TestSchedulingHooks BoundaryHooks; + BoundaryHooks.MakeADDIBoundary = true; + auto BoundaryModel = makeSchedulingModel(&BoundaryHooks); + if (!BoundaryFixture.Function || BoundaryFixture.Block == nullptr || !BoundaryModel) { + Context.expect(false, "target schedule boundary fixture must be created"); + return; + } + rcc::MachineIRBuilder BoundaryBuilder(*BoundaryFixture.Function, Target); + Context.expect(BoundaryBuilder.setInsertPoint(*BoundaryFixture.Block).has_value(), + "target schedule boundary insertion point must be established"); + rcc::MachineInstr *BoundaryFirst = + buildADDI(Context, BoundaryBuilder, makeGPR(Context, *BoundaryFixture.Function), X0, 1); + (void)buildADDI(Context, BoundaryBuilder, makeGPR(Context, *BoundaryFixture.Function), X0, 2); + rcc::MachineAnalysisManager BoundaryAnalyses; + rcc::MachineSchedulerPass BoundaryPass( + *BoundaryModel, {.MaximumRegionSize = 8, .EnableBidirectionalScheduling = false}); + auto BoundaryResult = BoundaryPass.run(*BoundaryFixture.Function, Target, BoundaryAnalyses); + Context.expect(BoundaryResult.has_value() && !BoundaryResult->Changed && + BoundaryFixture.Block->firstInstruction() == BoundaryFirst, + "target scheduling boundary must split production schedule regions"); +} + +void buildSchedulingChain(TestContext &Context, Fixture &Fixture, + const rcc::TargetDescription &Target, bool PhysicalRegisters, + rcc::MachineInstr *&Short, rcc::MachineInstr *&Long) { + rcc::MachineIRBuilder Builder(*Fixture.Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Block).has_value(), + "scheduler chain insertion point must be established"); + const rcc::Register X0 = physical(Context, rcc::riscv::PhysicalRegisterID::X0); + const rcc::Register X10 = physical(Context, rcc::riscv::PhysicalRegisterID::X10); + const rcc::Register X11 = physical(Context, rcc::riscv::PhysicalRegisterID::X11); + const rcc::Register ShortValue = PhysicalRegisters + ? physical(Context, rcc::riscv::PhysicalRegisterID::X12) + : makeGPR(Context, *Fixture.Function); + const rcc::Register LongValue = PhysicalRegisters + ? physical(Context, rcc::riscv::PhysicalRegisterID::X13) + : makeGPR(Context, *Fixture.Function); + const rcc::Register LongResult = PhysicalRegisters + ? physical(Context, rcc::riscv::PhysicalRegisterID::X14) + : makeGPR(Context, *Fixture.Function); + const rcc::Register ShortResult = PhysicalRegisters + ? physical(Context, rcc::riscv::PhysicalRegisterID::X15) + : makeGPR(Context, *Fixture.Function); + Short = buildADDI(Context, Builder, ShortValue, X0, 1); + Long = buildBinary(Context, Builder, rcc::riscv::InstructionOpcode::DIV, LongValue, X10, X11); + (void)buildBinary(Context, Builder, rcc::riscv::InstructionOpcode::ADD, LongResult, LongValue, + X10); + (void)buildBinary(Context, Builder, rcc::riscv::InstructionOpcode::ADD, ShortResult, ShortValue, + X11); +} + +void testPreRAScheduler(TestContext &Context, const rcc::TargetDescription &Target) { + auto Fixture = makeFixture(Context, "pre_ra_schedule", 2); + if (!Fixture.Function || Fixture.Block == nullptr) + return; + rcc::MachineInstr *Short = nullptr; + rcc::MachineInstr *Long = nullptr; + buildSchedulingChain(Context, Fixture, Target, false, Short, Long); + Context.expect(Fixture.Block->firstInstruction() == Short, + "pre-RA fixture must begin in intentionally suboptimal order"); + rcc::MachineAnalysisManager Analyses; + rcc::MachineSchedulerPass Pass(rcc::riscv::schedulingModel(), + {.MaximumRegionSize = 64, .EnableBidirectionalScheduling = false}); + auto Result = Pass.run(*Fixture.Function, Target, Analyses); + if (!Result) + std::cerr << "pre-RA scheduler failure: " << Result.error().message() << '\n'; + Context.expect(Result.has_value() && Result->Changed, + "pre-RA scheduler must reorder the latency-critical chain"); + Context.expect(Fixture.Block->firstInstruction() == Long, + "pre-RA scheduler must prioritize the long critical path"); +} + +void testPostRASchedulerAndLivenessRepair(TestContext &Context, + const rcc::TargetDescription &Target) { + auto Fixture = makeFixture(Context, "post_ra_schedule", 3); + if (!Fixture.Function || Fixture.Block == nullptr) + return; + Fixture.Function->properties().set(rcc::MachineFunctionProperty::NoVRegs); + Fixture.Function->properties().set(rcc::MachineFunctionProperty::TracksLiveness); + rcc::MachineInstr *Short = nullptr; + rcc::MachineInstr *Long = nullptr; + buildSchedulingChain(Context, Fixture, Target, true, Short, Long); + auto Rebuilt = rcc::rebuildMachineLiveness(*Fixture.Function, Target); + Context.expect(Rebuilt.has_value(), "post-RA fixture liveness rebuild must succeed"); + if (!Rebuilt) + return; + + rcc::MachineAnalysisManager Analyses; + rcc::PostRASchedulerPass Pass(rcc::riscv::schedulingModel(), + {.MaximumRegionSize = 64, .EnableBidirectionalScheduling = false}); + auto Result = Pass.run(*Fixture.Function, Target, Analyses); + if (!Result) + std::cerr << "post-RA scheduler failure: " << Result.error().message() << '\n'; + Context.expect(Result.has_value() && Result->Changed, + "post-RA scheduler must reorder the latency-critical chain"); + Context.expect(Fixture.Block->firstInstruction() == Long, + "post-RA scheduler must prioritize the long critical path"); + auto Verified = rcc::verifyTrackedMachineLiveness(*Fixture.Function, Target); + if (!Verified) + std::cerr << "post-RA liveness verification failure: " << Verified.error().message() << '\n'; + Context.expect(Verified.has_value(), + "post-RA scheduler must locally repair exact kill/dead state"); +} + +} // namespace + +int main() { + TestContext Context; + auto Target = rcc::riscv::createTargetDescription(); + Context.expect(Target.has_value(), "scheduler target creation must succeed"); + if (!Target) + return Context.result(); + testDAGRegisterDependencies(Context, *Target); + testHazardScoreboard(Context); + testOperandLatencyAndModelValidation(Context); + testDynamicTargetHazards(Context); + testTargetSchedulingHooks(Context, *Target); + testPreRAScheduler(Context, *Target); + testPostRASchedulerAndLivenessRepair(Context, *Target); + return Context.result(); +} |
