aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/CodeGen/MachineAnalysesTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests/CodeGen/MachineAnalysesTest.cpp')
-rw-r--r--backend/unittests/CodeGen/MachineAnalysesTest.cpp477
1 files changed, 477 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/MachineAnalysesTest.cpp b/backend/unittests/CodeGen/MachineAnalysesTest.cpp
new file mode 100644
index 0000000..23c5e19
--- /dev/null
+++ b/backend/unittests/CodeGen/MachineAnalysesTest.cpp
@@ -0,0 +1,477 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/MachineAnalyses.h"
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/CodeGen/MachineCFGUpdater.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineIRBuilder.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineMemOperand.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++Failures;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return Failures == 0 ? 0 : 1;
+ }
+
+private:
+ int Failures = 0;
+};
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ std::vector<rcc::ImmediatePredicate> Predicates;
+ auto Appended = rcc::appendGenericInstructionInfo(Descriptors, Predicates);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
+}
+
+[[nodiscard]] rcc::Expected<rcc::MachineBasicBlock *> appendBlock(rcc::MachineFunction &Function) {
+ auto Block = Function.createBasicBlock();
+ if (!Block)
+ return std::unexpected(Block.error());
+ auto Appended = Function.appendBlock(**Block);
+ if (!Appended)
+ return std::unexpected(Appended.error());
+ return *Block;
+}
+
+void testCFGAnalyses(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function = rcc::MachineFunction::create("cfg", rcc::MachineFunctionStableID::fromValue(1));
+ Context.expect(Function.has_value(), "CFG analysis function creation must succeed");
+ if (!Function)
+ return;
+
+ auto Entry = appendBlock(**Function);
+ auto Header = appendBlock(**Function);
+ auto Body = appendBlock(**Function);
+ auto Exit = appendBlock(**Function);
+ auto Dead = appendBlock(**Function);
+ Context.expect(Entry && Header && Body && Exit && Dead,
+ "CFG analysis blocks must be appended successfully");
+ if (!Entry || !Header || !Body || !Exit || !Dead)
+ return;
+
+ auto ThreeQuarters = rcc::BranchProbability::fromFraction(3, 4);
+ auto OneQuarter = rcc::BranchProbability::fromFraction(1, 4);
+ auto Updater = (*Function)->cfgUpdater();
+ Context.expect(ThreeQuarters && OneQuarter &&
+ Updater.addSuccessor(**Entry, **Header, rcc::BranchProbability::one()) &&
+ Updater.addSuccessor(**Header, **Body, *ThreeQuarters) &&
+ Updater.addSuccessor(**Header, **Exit, *OneQuarter) &&
+ Updater.addSuccessor(**Body, **Header, rcc::BranchProbability::one()),
+ "reducible loop CFG construction must succeed");
+
+ rcc::MachineAnalysisManager Analyses;
+ Context.expect(rcc::registerStage8MachineAnalyses(Analyses).has_value(),
+ "standard Stage 8 analyses must register atomically");
+ auto Dominators = Analyses.getResult<rcc::MachineDominatorTreeAnalysis>(**Function, Target);
+ auto Loops = Analyses.getResult<rcc::MachineLoopAnalysis>(**Function, Target);
+ auto Probabilities =
+ Analyses.getResult<rcc::MachineBranchProbabilityAnalysis>(**Function, Target);
+ auto Frequencies = Analyses.getResult<rcc::MachineBlockFrequencyAnalysis>(**Function, Target);
+ Context.expect(Dominators && Loops && Probabilities && Frequencies,
+ "CFG analyses and their dependencies must materialize");
+ if (!Dominators || !Loops || !Probabilities || !Frequencies)
+ return;
+
+ Context.expect((*Dominators)->dominates(**Entry, **Exit) &&
+ (*Dominators)->dominates(**Header, **Body) &&
+ (*Dominators)->immediateDominator(**Body) == *Header &&
+ (*Dominators)->nearestCommonDominator(**Body, **Exit) == *Header &&
+ !(*Dominators)->isReachable(**Dead),
+ "dominator analysis must model reachability, IDom, and common dominators");
+ Context.expect((*Loops)->loops().size() == 1 && (*Loops)->loopFor(**Header) != nullptr &&
+ (*Loops)->loopFor(**Body) != nullptr && (*Loops)->loopDepth(**Body) == 1,
+ "loop analysis must recover the natural loop and loop depth");
+ Context.expect((*Probabilities)->probability(**Header, **Body) == *ThreeQuarters &&
+ (*Probabilities)->probability(**Header, **Exit) == *OneQuarter,
+ "branch-probability analysis must retain exact integer edge probabilities");
+ Context.expect((*Frequencies)->frequency(**Entry) != 0 &&
+ (*Frequencies)->frequency(**Header) > (*Frequencies)->frequency(**Entry) &&
+ (*Frequencies)->frequency(**Dead) == 0,
+ "block-frequency analysis must distinguish loop and unreachable blocks");
+
+ auto Alias = Analyses.getResult<rcc::MachineAliasAnalysis>(**Function, Target);
+ auto MemoryDependence =
+ Analyses.getResult<rcc::MachineMemoryDependenceAnalysis>(**Function, Target);
+ Context.expect(Alias && MemoryDependence && Analyses.cachedResultCount(**Function) == 6,
+ "the complete Stage 8 analysis set must materialize into one function cache");
+ Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(),
+ {rcc::MachinePassChange::Registers});
+ Context.expect(Analyses.cachedResultCount(**Function) == 6,
+ "register-only changes must preserve CFG and memory analyses");
+ Analyses.invalidate(**Function, rcc::PreservedAnalyses::none(), {rcc::MachinePassChange::CFG});
+ Context.expect(
+ Analyses.cachedResultCount(**Function) == 1 &&
+ Analyses.hasCached(**Function,
+ rcc::MachineAnalysisManager::id<rcc::MachineAliasAnalysis>()),
+ "CFG changes must invalidate CFG dependents and memory dependence but preserve aliasing");
+
+ auto RefreshedDominators =
+ Analyses.getResult<rcc::MachineDominatorTreeAnalysis>(**Function, Target);
+ Context.expect(
+ RefreshedDominators &&
+ Updater.addSuccessor(**Entry, **Exit, rcc::BranchProbability::zero()).has_value() &&
+ (*RefreshedDominators)->updateAfterCFGChange(**Function).has_value() &&
+ (*RefreshedDominators)->immediateDominator(**Exit) == *Entry,
+ "dominator result must support exact updates after CFG edge changes");
+}
+
+void testIrreducibleCFG(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("irreducible", rcc::MachineFunctionStableID::fromValue(2));
+ if (!Function) {
+ Context.expect(false, "irreducible CFG function creation must succeed");
+ return;
+ }
+ auto Entry = appendBlock(**Function);
+ auto Left = appendBlock(**Function);
+ auto Right = appendBlock(**Function);
+ auto Exit = appendBlock(**Function);
+ if (!Entry || !Left || !Right || !Exit) {
+ Context.expect(false, "irreducible CFG blocks must be appended");
+ return;
+ }
+ auto Half = rcc::BranchProbability::fromFraction(1, 2);
+ auto Updater = (*Function)->cfgUpdater();
+ Context.expect(Half && Updater.addSuccessor(**Entry, **Left, *Half) &&
+ Updater.addSuccessor(**Entry, **Right, *Half) &&
+ Updater.addSuccessor(**Left, **Right, *Half) &&
+ Updater.addSuccessor(**Left, **Exit, *Half) &&
+ Updater.addSuccessor(**Right, **Left, *Half) &&
+ Updater.addSuccessor(**Right, **Exit, *Half),
+ "irreducible CFG construction must succeed");
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerStage8MachineAnalyses(Analyses);
+ auto Loops =
+ Registered
+ ? Analyses.getResult<rcc::MachineLoopAnalysis>(**Function, Target)
+ : rcc::Expected<rcc::MachineLoopAnalysis::Result *>{std::unexpected(Registered.error())};
+ Context.expect(Loops && (*Loops)->irreducibleRegions().size() == 1 &&
+ (*Loops)->irreducibleRegions().front().Entries.size() == 2 &&
+ (*Loops)->isIrreducible(**Left) && (*Loops)->isIrreducible(**Right),
+ "loop analysis must explicitly identify multi-entry irreducible regions");
+}
+
+[[nodiscard]] rcc::MachineMemOperandDesc
+memoryDescription(rcc::MemoryBase Base, std::int64_t Offset, rcc::MemoryAccessFlag Access) {
+ rcc::MachineMemOperandDesc Description;
+ Description.Accesses = {Access};
+ Description.Size = *rcc::MemorySize::fixed(8);
+ Description.Align = *rcc::Alignment::create(8);
+ Description.Base = std::move(Base);
+ Description.Offset = Offset;
+ return Description;
+}
+
+void testAliasAnalysis(TestContext &Context) {
+ auto First = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 0, rcc::MemoryAccessFlag::Load));
+ auto Same = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 0, rcc::MemoryAccessFlag::Store));
+ auto Partial = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 4, rcc::MemoryAccessFlag::Store));
+ auto DisjointRange = rcc::MachineMemOperand::create(
+ memoryDescription(rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)), 16,
+ rcc::MemoryAccessFlag::Store));
+ auto DistinctObject = rcc::MachineMemOperand::create(memoryDescription(
+ rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(1)), 0, rcc::MemoryAccessFlag::Store));
+ auto DifferentObjectKind = rcc::MachineMemOperand::create(
+ memoryDescription(rcc::MemoryBase::constantPool(rcc::ConstantPoolIndex::fromValue(0)), 0,
+ rcc::MemoryAccessFlag::Store));
+ Context.expect(First && Same && Partial && DisjointRange && DistinctObject && DifferentObjectKind,
+ "alias-analysis memory operands must be valid");
+ if (!First || !Same || !Partial || !DisjointRange || !DistinctObject || !DifferentObjectKind)
+ return;
+
+ rcc::MachineAliasAnalysis::Result Alias;
+ Context.expect(Alias.alias(*First, *Same) == rcc::MachineAliasResult::MustAlias &&
+ Alias.alias(*First, *Partial) == rcc::MachineAliasResult::PartialAlias &&
+ Alias.alias(*First, *DisjointRange) == rcc::MachineAliasResult::NoAlias &&
+ Alias.alias(*First, *DistinctObject) == rcc::MachineAliasResult::NoAlias &&
+ Alias.alias(*First, *DifferentObjectKind) == rcc::MachineAliasResult::NoAlias,
+ "alias analysis must distinguish exact, partial, range, and object aliasing");
+}
+
+void testMemoryDependence(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("memory", rcc::MachineFunctionStableID::fromValue(3));
+ if (!Function) {
+ Context.expect(false, "memory-dependence function creation must succeed");
+ return;
+ }
+ auto Block = appendBlock(**Function);
+ auto OtherBlock = appendBlock(**Function);
+ auto I64 = rcc::LLT::makeInteger(64);
+ auto Pointer = rcc::LLT::makePointer(0, 64);
+ auto Value = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ auto Address = Pointer ? (*Function)->registerInfo().createVirtualRegister(*Pointer)
+ : rcc::Expected<rcc::Register>{std::unexpected(Pointer.error())};
+ if (!Block || !OtherBlock || !Value || !Address) {
+ Context.expect(false, "memory-dependence MachineIR setup must succeed");
+ return;
+ }
+
+ auto StoreMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 0, rcc::MemoryAccessFlag::Store));
+ auto AliasingLoadMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 0, rcc::MemoryAccessFlag::Load));
+ auto DisjointLoadMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 32, rcc::MemoryAccessFlag::Load));
+ auto VolatileStoreDescription =
+ memoryDescription(rcc::MemoryBase::stack(), 64, rcc::MemoryAccessFlag::Store);
+ VolatileStoreDescription.Accesses.set(rcc::MemoryAccessFlag::Volatile);
+ auto VolatileLoadDescription =
+ memoryDescription(rcc::MemoryBase::stack(), 96, rcc::MemoryAccessFlag::Load);
+ VolatileLoadDescription.Accesses.set(rcc::MemoryAccessFlag::Volatile);
+ auto VolatileStoreMemory = (*Function)->createMemOperand(std::move(VolatileStoreDescription));
+ auto VolatileLoadMemory = (*Function)->createMemOperand(std::move(VolatileLoadDescription));
+ auto OtherBlockLoadMemory = (*Function)->createMemOperand(
+ memoryDescription(rcc::MemoryBase::stack(), 0, rcc::MemoryAccessFlag::Load));
+ rcc::MachineIRBuilder Builder(**Function, Target);
+ Context.expect(StoreMemory && AliasingLoadMemory && DisjointLoadMemory && VolatileStoreMemory &&
+ VolatileLoadMemory && OtherBlockLoadMemory && Builder.setInsertPoint(**Block),
+ "memory-dependence builder setup must succeed");
+ if (!StoreMemory || !AliasingLoadMemory || !DisjointLoadMemory || !VolatileStoreMemory ||
+ !VolatileLoadMemory || !OtherBlockLoadMemory)
+ return;
+
+ auto Store = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_STORE));
+ if (!Store || !Store->addUse(*Value) || !Store->addUse(*Address) ||
+ !Store->addMemOperand(**StoreMemory)) {
+ Context.expect(false, "generic store construction must succeed");
+ return;
+ }
+ auto StoreInstruction = Store->build();
+ auto Load = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!Load || !Load->addDef(*Value) || !Load->addUse(*Address) ||
+ !Load->addMemOperand(**AliasingLoadMemory)) {
+ Context.expect(false, "aliasing generic load construction must succeed");
+ return;
+ }
+ auto LoadInstruction = Load->build();
+ auto DisjointLoad = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!DisjointLoad || !DisjointLoad->addDef(*Value) || !DisjointLoad->addUse(*Address) ||
+ !DisjointLoad->addMemOperand(**DisjointLoadMemory)) {
+ Context.expect(false, "disjoint generic load construction must succeed");
+ return;
+ }
+ auto DisjointLoadInstruction = DisjointLoad->build();
+ auto VolatileStore = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_STORE));
+ if (!VolatileStore || !VolatileStore->addUse(*Value) || !VolatileStore->addUse(*Address) ||
+ !VolatileStore->addMemOperand(**VolatileStoreMemory)) {
+ Context.expect(false, "volatile store construction must succeed");
+ return;
+ }
+ auto VolatileStoreInstruction = VolatileStore->build();
+ auto VolatileLoad = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!VolatileLoad || !VolatileLoad->addDef(*Value) || !VolatileLoad->addUse(*Address) ||
+ !VolatileLoad->addMemOperand(**VolatileLoadMemory)) {
+ Context.expect(false, "volatile load construction must succeed");
+ return;
+ }
+ auto VolatileLoadInstruction = VolatileLoad->build();
+ if (!Builder.setInsertPoint(**OtherBlock)) {
+ Context.expect(false, "second memory region insertion point must be valid");
+ return;
+ }
+ auto OtherBlockLoad = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
+ if (!OtherBlockLoad || !OtherBlockLoad->addDef(*Value) || !OtherBlockLoad->addUse(*Address) ||
+ !OtherBlockLoad->addMemOperand(**OtherBlockLoadMemory)) {
+ Context.expect(false, "second-region generic load construction must succeed");
+ return;
+ }
+ auto OtherBlockLoadInstruction = OtherBlockLoad->build();
+ Context.expect(StoreInstruction && LoadInstruction && DisjointLoadInstruction &&
+ VolatileStoreInstruction && VolatileLoadInstruction &&
+ OtherBlockLoadInstruction,
+ "memory instructions must be published transactionally");
+ if (!StoreInstruction || !LoadInstruction || !DisjointLoadInstruction ||
+ !VolatileStoreInstruction || !VolatileLoadInstruction || !OtherBlockLoadInstruction)
+ return;
+
+ rcc::MachineAnalysisManager Analyses;
+ auto Registered = rcc::registerStage8MachineAnalyses(Analyses);
+ auto Dependencies =
+ Registered ? Analyses.getResult<rcc::MachineMemoryDependenceAnalysis>(**Function, Target)
+ : rcc::Expected<rcc::MachineMemoryDependenceAnalysis::Result *>{
+ std::unexpected(Registered.error())};
+ Context.expect(Dependencies && (*Dependencies)->dependencies(**LoadInstruction).size() == 1 &&
+ (*Dependencies)->dependencies(**LoadInstruction).front().Source ==
+ (*StoreInstruction)->stableID() &&
+ (*Dependencies)->dependencies(**DisjointLoadInstruction).empty(),
+ "memory dependence must add flow edges only for potentially aliasing accesses");
+ Context.expect(
+ Dependencies && (*Dependencies)->regions().size() == 2 &&
+ (*Dependencies)->regionFor(**Block) != nullptr &&
+ (*Dependencies)->regionFor(**Block)->Instructions.size() == 5 &&
+ (*Dependencies)->regionFor(**OtherBlock) != nullptr &&
+ (*Dependencies)->regionFor(**OtherBlock)->Instructions.size() == 1 &&
+ (*Dependencies)->dependencies(**OtherBlockLoadInstruction).empty() &&
+ (*Dependencies)->dependencies(**OtherBlock).empty(),
+ "memory dependence must expose deterministic block regions without cross-CFG edges");
+ bool HasOrderedVolatileDependency = false;
+ if (Dependencies) {
+ HasOrderedVolatileDependency =
+ std::ranges::any_of((*Dependencies)->dependencies(**VolatileLoadInstruction),
+ [&](const rcc::MachineMemoryDependency &Dependency) {
+ return Dependency.Source == (*VolatileStoreInstruction)->stableID();
+ });
+ }
+ Context.expect(HasOrderedVolatileDependency,
+ "ordered memory accesses must retain dependencies across disjoint addresses");
+}
+
+class ConflictingAnalysis final {
+public:
+ struct Result final {};
+ [[nodiscard]] static rcc::Expected<Result>
+ run(rcc::MachineFunction &, const rcc::TargetDescription &, rcc::MachineAnalysisManager &) {
+ return Result{};
+ }
+};
+
+void testAtomicStandardRegistration(TestContext &Context) {
+ rcc::MachineAnalysisManager Analyses;
+ Context.expect(Analyses
+ .registerAnalysis<ConflictingAnalysis>({
+ .Name = "MachineLoopInfo",
+ .Dependencies = {},
+ })
+ .has_value(),
+ "analysis-name conflict setup must succeed");
+ Context.expect(!rcc::registerStage8MachineAnalyses(Analyses) &&
+ !Analyses.isRegistered(
+ rcc::MachineAnalysisManager::id<rcc::MachineDominatorTreeAnalysis>()),
+ "failed standard-analysis registration must not publish a partial set");
+}
+
+void testCFGUpdaterPHIs(TestContext &Context, const rcc::TargetDescription &Target) {
+ auto Function =
+ rcc::MachineFunction::create("cfg_phi", rcc::MachineFunctionStableID::fromValue(4));
+ if (!Function) {
+ Context.expect(false, "CFG PHI function creation must succeed");
+ return;
+ }
+ auto Source = appendBlock(**Function);
+ auto Other = appendBlock(**Function);
+ auto Merge = appendBlock(**Function);
+ auto Unconnected = appendBlock(**Function);
+ if (!Source || !Other || !Merge || !Unconnected) {
+ Context.expect(false, "CFG PHI blocks must be appended");
+ return;
+ }
+ auto Updater = (*Function)->cfgUpdater();
+ if (!Updater.addSuccessor(**Source, **Merge, rcc::BranchProbability::one()) ||
+ !Updater.addSuccessor(**Other, **Merge, rcc::BranchProbability::one())) {
+ Context.expect(false, "CFG PHI predecessor edges must be created");
+ return;
+ }
+
+ auto I64 = rcc::LLT::makeInteger(64);
+ auto Destination = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ auto SourceValue = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ auto OtherValue = I64 ? (*Function)->registerInfo().createVirtualRegister(*I64)
+ : rcc::Expected<rcc::Register>{std::unexpected(I64.error())};
+ if (!Destination || !SourceValue || !OtherValue) {
+ Context.expect(false, "CFG PHI registers must be created");
+ return;
+ }
+
+ rcc::MachineIRBuilder Builder(**Function, Target);
+ if (!Builder.setInsertPoint(**Source))
+ return;
+ auto SourceDefinition = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_IMPLICIT_DEF));
+ if (!SourceDefinition || !SourceDefinition->addDef(*SourceValue) || !SourceDefinition->build()) {
+ Context.expect(false, "CFG PHI source definition must build");
+ return;
+ }
+ auto Branch = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_BR));
+ if (!Branch || !Branch->addBasicBlock(**Merge)) {
+ Context.expect(false, "CFG PHI branch must be assembled");
+ return;
+ }
+ auto BranchInstruction = Branch->build();
+ if (!BranchInstruction || !Builder.setInsertPoint(**Merge)) {
+ Context.expect(false, "CFG PHI branch must publish");
+ return;
+ }
+ auto Phi = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_PHI));
+ if (!Phi || !Phi->addDef(*Destination) || !Phi->addUse(*SourceValue) ||
+ !Phi->addBasicBlock(**Source) || !Phi->addUse(*OtherValue) || !Phi->addBasicBlock(**Other)) {
+ Context.expect(false, "CFG PHI instruction must be assembled");
+ return;
+ }
+ auto PhiInstruction = Phi->build();
+ Context.expect(PhiInstruction && rcc::MachineVerifier::verify(**Function, Target),
+ "initial CFG PHI function must verify");
+ if (!PhiInstruction)
+ return;
+
+ auto Split = Updater.splitBlock(**Source, **BranchInstruction);
+ Context.expect(
+ Split && (*Split)->predecessors().size() == 1 &&
+ (*Split)->predecessors().front() == *Source && (*Split)->successors().size() == 1 &&
+ (*PhiInstruction)->operandAt(2)->getIf<rcc::BasicBlockOperand>()->Block == *Split &&
+ rcc::MachineVerifier::verify(**Function, Target),
+ "block split must redirect CFG edges and PHI predecessor identity");
+ if (!Split)
+ return;
+
+ Context.expect(Updater.removeSuccessor(**Other, **Merge) &&
+ (*PhiInstruction)->operandCount() == 3 &&
+ rcc::MachineVerifier::verify(**Function, Target),
+ "edge deletion must remove the corresponding PHI incoming pair");
+ Context.expect(!Updater.addSuccessor(**Unconnected, **Merge, rcc::BranchProbability::one()),
+ "new PHI predecessors without incoming values must be rejected");
+ Context.expect(Updater.deleteBlock(**Other) && !(*Other)->isLinked() &&
+ rcc::MachineVerifier::verify(**Function, Target),
+ "CFG block deletion must detach a disconnected block cleanly");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ auto Target = createTarget();
+ Context.expect(Target.has_value(), "machine analysis tests require a valid target");
+ if (!Target)
+ return Context.result();
+
+ testCFGAnalyses(Context, *Target);
+ testIrreducibleCFG(Context, *Target);
+ testAliasAnalysis(Context);
+ testMemoryDependence(Context, *Target);
+ testCFGUpdaterPHIs(Context, *Target);
+ testAtomicStandardRegistration(Context);
+ return Context.result();
+}