aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/CodeGen/LiveIntervalsTest.cpp
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-13 04:17:52 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-13 04:17:52 +0800
commita2f8d7c5e22b510965327383410bd576f7cf28e7 (patch)
tree6c0c3d1b80132aaa4932a4adc2fb0ceacdbddd2e /backend/unittests/CodeGen/LiveIntervalsTest.cpp
parentd2cd152d658955a7fb64fb9a1329bc5bee4d8317 (diff)
downloadrcc-project-a2f8d7c5e22b510965327383410bd576f7cf28e7.tar.gz
rcc-project-a2f8d7c5e22b510965327383410bd576f7cf28e7.tar.xz
[CodeGen] Split machine liveness components
Diffstat (limited to 'backend/unittests/CodeGen/LiveIntervalsTest.cpp')
-rw-r--r--backend/unittests/CodeGen/LiveIntervalsTest.cpp265
1 files changed, 265 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/LiveIntervalsTest.cpp b/backend/unittests/CodeGen/LiveIntervalsTest.cpp
new file mode 100644
index 0000000..5c127a2
--- /dev/null
+++ b/backend/unittests/CodeGen/LiveIntervalsTest.cpp
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/LiveIntervals.h"
+
+#include "LiveTestSupport.h"
+#include "rcc/CodeGen/LiveRegUnits.h"
+#include "rcc/CodeGen/LiveVariables.h"
+#include "rcc/Target/RISCV/RISCVInstructions.h"
+#include "rcc/Target/RISCV/RISCVRegisters.h"
+#include "rcc/Target/RISCV/RISCVTarget.h"
+
+namespace {
+
+using namespace rcc::test::liveness;
+
+void testCFGAndValueNumbers(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Fixture = createFunction("cfg-liveness", 2, 8);
+ if (!Fixture) {
+ Context.expect(false, "CFG liveness fixture creation must succeed");
+ return;
+ }
+ auto &Function = *Fixture->Function;
+ auto &Entry = *Fixture->Blocks[0];
+ auto &Left = *Fixture->Blocks[1];
+ auto &Right = *Fixture->Blocks[2];
+ auto &Join = *Fixture->Blocks[3];
+ auto &Header = *Fixture->Blocks[4];
+ auto &Body = *Fixture->Blocks[5];
+ auto &Exit = *Fixture->Blocks[6];
+ auto &Dead = *Fixture->Blocks[7];
+ auto Half = rcc::BranchProbability::fromFraction(1, 2);
+ auto Updater = Function.cfgUpdater();
+ const bool CFGValid = Half && Updater.addSuccessor(Entry, Left, *Half) &&
+ Updater.addSuccessor(Entry, Right, *Half) &&
+ Updater.addSuccessor(Left, Join, rcc::BranchProbability::one()) &&
+ Updater.addSuccessor(Right, Join, rcc::BranchProbability::one()) &&
+ Updater.addSuccessor(Join, Header, rcc::BranchProbability::one()) &&
+ Updater.addSuccessor(Header, Body, *Half) &&
+ Updater.addSuccessor(Header, Exit, *Half) &&
+ Updater.addSuccessor(Body, Header, rcc::BranchProbability::one());
+ Context.expect(CFGValid, "diamond and loop CFG construction must succeed");
+ if (!CFGValid)
+ return;
+
+ auto Across = createVReg(Function);
+ auto Merged = createVReg(Function);
+ auto Unreachable = createVReg(Function);
+ auto Redefined = createVReg(Function);
+ if (!Across || !Merged || !Unreachable || !Redefined) {
+ Context.expect(false, "CFG liveness virtual registers must be created");
+ return;
+ }
+ auto AcrossDef = buildDef(Function, Target, Entry, *Across);
+ auto LeftDef = buildDef(Function, Target, Left, *Merged);
+ auto RightDef = buildDef(Function, Target, Right, *Merged);
+ auto MergedUse = buildUse(Function, Target, Join, *Merged);
+ auto LoopUse = buildUse(Function, Target, Body, *Across);
+ auto DeadUse = buildUse(Function, Target, Dead, *Unreachable);
+ auto FirstRedef = buildDef(Function, Target, Exit, *Redefined);
+ auto SecondRedef = buildDef(Function, Target, Exit, *Redefined);
+ if (!AcrossDef || !LeftDef || !RightDef || !MergedUse || !LoopUse || !DeadUse || !FirstRedef ||
+ !SecondRedef) {
+ Context.expect(false, "CFG liveness instructions must be built");
+ return;
+ }
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerStage11MachineAnalyses(Analyses);
+ auto Variables = Registered ? Analyses.getResult<rcc::LiveVariablesAnalysis>(Function, Target)
+ : rcc::Expected<rcc::LiveVariablesAnalysis::Result *>{
+ std::unexpected(Registered.error())};
+ auto Intervals = Registered ? Analyses.getResult<rcc::LiveIntervalsAnalysis>(Function, Target)
+ : rcc::Expected<rcc::LiveIntervalsAnalysis::Result *>{
+ std::unexpected(Registered.error())};
+ if (!Variables || !Intervals) {
+ Context.expect(false, "CFG liveness analyses must materialize");
+ return;
+ }
+ Context.expect((*Variables)->isLiveOut(Entry, *Across) && (*Variables)->isLiveIn(Left, *Across) &&
+ (*Variables)->isLiveIn(Right, *Across) &&
+ (*Variables)->isLiveIn(Header, *Across) &&
+ (*Variables)->isLiveOut(Body, *Across),
+ "CFG fixed point must propagate a value through a diamond and loop backedge");
+ Context.expect(
+ (*Variables)->isLiveOut(Left, *Merged) && (*Variables)->isLiveOut(Right, *Merged) &&
+ (*Variables)->isLiveIn(Join, *Merged) && (*Variables)->isLiveIn(Dead, *Unreachable),
+ "liveness must merge predecessor definitions and retain unreachable uses");
+
+ const rcc::LiveInterval *MergedInterval = (*Intervals)->interval(*Merged);
+ const rcc::LiveInterval *AcrossInterval = (*Intervals)->interval(*Across);
+ const rcc::LiveInterval *RedefinedInterval = (*Intervals)->interval(*Redefined);
+ const bool HasPhi = MergedInterval != nullptr &&
+ std::ranges::any_of(MergedInterval->MainRange.values(),
+ [](const rcc::LiveValueNumber &Value) {
+ return Value.State == rcc::LiveValueState::Phi;
+ });
+ const bool HasRedefined =
+ RedefinedInterval != nullptr &&
+ std::ranges::any_of(RedefinedInterval->MainRange.values(),
+ [](const rcc::LiveValueNumber &Value) {
+ return Value.State == rcc::LiveValueState::Redefined;
+ });
+ Context.expect(MergedInterval != nullptr && MergedInterval->MainRange.values().size() >= 3 &&
+ HasPhi && HasRedefined,
+ "non-SSA intervals must retain multiple definitions, PHI, and redefined values");
+ Context.expect(AcrossInterval != nullptr && (*Intervals)->interferes(*Across, *Merged),
+ "live intervals must expose CFG-derived interference");
+}
+
+void testPhysicalUnitsAndRegMasks(TestContext &Context, const rcc::TargetDescription &Target) {
+ const rcc::RegisterUnitID MultiLaneUnit = rcc::RegisterUnitID::fromValue(9);
+ rcc::LiveRegUnits PartialLanes;
+ PartialLanes.add(MultiLaneUnit, rcc::LaneMask::fromRaw(0x3));
+ rcc::LiveRegUnits RemovedLanes;
+ RemovedLanes.add(MultiLaneUnit, rcc::LaneMask::fromRaw(0x2));
+ PartialLanes.subtract(RemovedLanes);
+ Context.expect(PartialLanes.lanes(MultiLaneUnit) == rcc::LaneMask::fromRaw(0x1) &&
+ PartialLanes.contains(MultiLaneUnit, rcc::LaneMask::fromRaw(0x1)) &&
+ !PartialLanes.intersects(MultiLaneUnit, rcc::LaneMask::fromRaw(0x2)),
+ "RegisterUnit lane sets must preserve partial union and subtraction");
+
+ auto AliasFixture = createFunction("physical-alias", 4, 1);
+ if (!AliasFixture) {
+ Context.expect(false, "physical-alias fixture creation must succeed");
+ return;
+ }
+ const rcc::Register G0 = rcc::Register::fromPhysical(physical(rcc::test::TestRegister::G0));
+ const rcc::Register G0High =
+ rcc::Register::fromPhysical(physical(rcc::test::TestRegister::G0High32));
+ auto G0Def = buildDef(*AliasFixture->Function, Target, *AliasFixture->Blocks[0], G0);
+ auto G0HighUse = buildUse(*AliasFixture->Function, Target, *AliasFixture->Blocks[0], G0High);
+ if (!G0Def || !G0HighUse) {
+ Context.expect(false, "physical-alias instructions must be built");
+ return;
+ }
+ rcc::MachineAnalysisManager AliasAnalyses;
+ auto AliasRegistered = rcc::registerStage11MachineAnalyses(AliasAnalyses);
+ auto AliasIntervals =
+ AliasRegistered
+ ? AliasAnalyses.getResult<rcc::LiveIntervalsAnalysis>(*AliasFixture->Function, Target)
+ : rcc::Expected<rcc::LiveIntervalsAnalysis::Result *>{
+ std::unexpected(AliasRegistered.error())};
+ const rcc::RegisterUnitID HighUnit = rcc::RegisterUnitID::fromValue(3);
+ Context.expect(AliasIntervals && (*AliasIntervals)->regUnitRange(HighUnit) != nullptr &&
+ (*AliasIntervals)->regUnitRange(HighUnit)->segments().size() == 1,
+ "shared RegisterUnits must connect super-register defs to subregister uses");
+
+ auto MaskFixture = createFunction("regmask", 5, 1);
+ if (!MaskFixture) {
+ Context.expect(false, "RegMask fixture creation must succeed");
+ return;
+ }
+ auto Across = createVReg(*MaskFixture->Function);
+ auto Output = createVReg(*MaskFixture->Function);
+ if (!Across || !Output) {
+ Context.expect(false, "RegMask virtual registers must be created");
+ return;
+ }
+ const auto Mask = rcc::test::createTestCallPreservedMask();
+ auto AcrossDef = buildDef(*MaskFixture->Function, Target, *MaskFixture->Blocks[0], *Across);
+ auto FirstMask = buildMask(*MaskFixture->Function, Target, *MaskFixture->Blocks[0], Mask);
+ auto AcrossUse = buildUse(*MaskFixture->Function, Target, *MaskFixture->Blocks[0], *Across);
+ auto OutputDef =
+ buildMaskedDef(*MaskFixture->Function, Target, *MaskFixture->Blocks[0], *Output, Mask);
+ auto OutputUse = buildUse(*MaskFixture->Function, Target, *MaskFixture->Blocks[0], *Output);
+ if (!AcrossDef || !FirstMask || !AcrossUse || !OutputDef || !OutputUse) {
+ Context.expect(false, "RegMask instructions must be built");
+ return;
+ }
+ rcc::MachineAnalysisManager MaskAnalyses;
+ auto MaskRegistered = rcc::registerStage11MachineAnalyses(MaskAnalyses);
+ auto MaskIntervals =
+ MaskRegistered
+ ? MaskAnalyses.getResult<rcc::LiveIntervalsAnalysis>(*MaskFixture->Function, Target)
+ : rcc::Expected<rcc::LiveIntervalsAnalysis::Result *>{
+ std::unexpected(MaskRegistered.error())};
+ if (!MaskIntervals) {
+ Context.expect(false, "RegMask intervals must materialize");
+ return;
+ }
+ const rcc::PhysicalRegister Candidate = physical(rcc::test::TestRegister::G0);
+ const rcc::LiveInterval *AcrossInterval = (*MaskIntervals)->interval(*Across);
+ const rcc::LiveInterval *OutputInterval = (*MaskIntervals)->interval(*Output);
+ Context.expect((*MaskIntervals)->regMaskPositions().size() == 2 &&
+ std::ranges::all_of((*MaskIntervals)->regMaskPositions(),
+ [](const rcc::RegMaskPosition &Position) {
+ return Position.Position.kind() ==
+ rcc::SlotIndexKind::EarlyClobber;
+ }),
+ "RegMask interference positions must use early-clobber slots");
+ Context.expect(AcrossInterval != nullptr && OutputInterval != nullptr &&
+ (*MaskIntervals)->regMaskClobbers(*AcrossInterval, Candidate) &&
+ !(*MaskIntervals)->regMaskClobbers(*OutputInterval, Candidate),
+ "RegMask queries must reject live-through values but accept same-call outputs");
+ Context.expect((*MaskIntervals)->regUnitRange(rcc::RegisterUnitID::fromValue(1)) == nullptr,
+ "unused RegMask clobbers must not expand into physical live ranges");
+}
+
+void testRISCVLiveness(TestContext &Context) {
+ auto Target = rcc::riscv::createTargetDescription();
+ auto Fixture = createFunction("riscv-liveness", 8, 1);
+ if (!Target || !Fixture) {
+ Context.expect(false, "RV64GC liveness fixture creation must succeed");
+ return;
+ }
+ auto X5 = rcc::riscv::physicalRegister(rcc::riscv::PhysicalRegisterID::X5);
+ auto X6 = rcc::riscv::physicalRegister(rcc::riscv::PhysicalRegisterID::X6);
+ auto X7 = rcc::riscv::physicalRegister(rcc::riscv::PhysicalRegisterID::X7);
+ if (!X5 || !X6 || !X7) {
+ Context.expect(false, "RV64GC physical registers must resolve");
+ return;
+ }
+ auto BuildADDI = [&](rcc::PhysicalRegister Destination, rcc::PhysicalRegister Source,
+ std::int64_t Immediate) -> rcc::Expected<rcc::MachineInstr *> {
+ rcc::MachineIRBuilder Builder(*Fixture->Function, *Target);
+ auto Point = Builder.setInsertPoint(*Fixture->Blocks[0]);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(
+ rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::ADDI)));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ auto Def = Pending->addDef(rcc::Register::fromPhysical(Destination));
+ if (!Def)
+ return std::unexpected(Def.error());
+ auto Use = Pending->addUse(rcc::Register::fromPhysical(Source));
+ if (!Use)
+ return std::unexpected(Use.error());
+ auto Imm = Pending->addImmediate(Immediate);
+ if (!Imm)
+ return std::unexpected(Imm.error());
+ return Pending->build();
+ };
+ auto First = BuildADDI(*X6, *X5, 1);
+ auto Second = BuildADDI(*X7, *X6, 2);
+ if (!First || !Second) {
+ Context.expect(false, "RV64GC ADDI instructions must be built");
+ return;
+ }
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerStage11MachineAnalyses(Analyses);
+ auto Intervals = Registered
+ ? Analyses.getResult<rcc::LiveIntervalsAnalysis>(*Fixture->Function, *Target)
+ : rcc::Expected<rcc::LiveIntervalsAnalysis::Result *>{
+ std::unexpected(Registered.error())};
+ const auto Unit =
+ rcc::RegisterUnitID::fromValue(static_cast<std::uint32_t>(rcc::riscv::RegisterUnit::U_X6));
+ Context.expect(Intervals && (*Intervals)->regUnitRange(Unit) != nullptr &&
+ (*Intervals)->regUnitRange(Unit)->segments().size() == 1,
+ "production RV64GC register units must participate in LiveIntervals");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "liveness TestTarget must be created");
+ if (!Target)
+ return Context.result();
+ testCFGAndValueNumbers(Context, *Target);
+ testPhysicalUnitsAndRegMasks(Context, *Target);
+ testRISCVLiveness(Context);
+ return Context.result();
+}