aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/CodeGen/PBQPTest.cpp
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 04:40:17 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-09 04:40:17 +0800
commite5fdceb9b869f010a2a75638fd38aaf1867f3008 (patch)
tree023d3e18960956f5ce369739c68aaa2a3e8400e7 /backend/unittests/CodeGen/PBQPTest.cpp
parent6c7cecc81a4c7e06e26027e21edd2cfd95a37a0b (diff)
downloadrcc-project-e5fdceb9b869f010a2a75638fd38aaf1867f3008.tar.gz
rcc-project-e5fdceb9b869f010a2a75638fd38aaf1867f3008.tar.xz
[CodeGen] Complete register allocation infrastructure
Diffstat (limited to 'backend/unittests/CodeGen/PBQPTest.cpp')
-rw-r--r--backend/unittests/CodeGen/PBQPTest.cpp177
1 files changed, 177 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/PBQPTest.cpp b/backend/unittests/CodeGen/PBQPTest.cpp
new file mode 100644
index 0000000..073de16
--- /dev/null
+++ b/backend/unittests/CodeGen/PBQPTest.cpp
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "rcc/CodeGen/PBQP/Graph.h"
+#include "rcc/CodeGen/PBQP/Math.h"
+#include "rcc/CodeGen/PBQP/Solver.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <iostream>
+#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;
+};
+
+void testMath(TestContext &Context) {
+ using namespace rcc::pbqp;
+ Context.expect(
+ addCosts(InfiniteCost, -4) == InfiniteCost && addCosts(InfiniteCost - 1, 2) == InfiniteCost &&
+ addCosts(NegativeInfiniteCost + 1, -2) == NegativeInfiniteCost && addCosts(7, -3) == 4,
+ "PBQP cost arithmetic must saturate deterministically");
+
+ CostVector Vector(std::vector<Cost>{8, -2, 4});
+ Context.expect(Vector.minimumIndex() == 1,
+ "PBQP cost vectors must choose the first stable minimum");
+ CostMatrix Matrix(2, 3);
+ Matrix.at(0, 0) = 1;
+ Matrix.at(0, 1) = 2;
+ Matrix.at(0, 2) = 3;
+ Matrix.at(1, 0) = 4;
+ Matrix.at(1, 1) = 5;
+ Matrix.at(1, 2) = 6;
+ const CostMatrix Transposed = Matrix.transpose();
+ Context.expect(Transposed.rows() == 3 && Transposed.columns() == 2 && Transposed.at(2, 1) == 6 &&
+ Transposed.at(1, 0) == 2,
+ "PBQP matrix transposition must preserve orientation");
+}
+
+void testGraphValidation(TestContext &Context) {
+ using namespace rcc::pbqp;
+ Graph Problem;
+ auto Empty = Problem.addNode(CostVector(0));
+ auto First = Problem.addNode(CostVector(std::vector<Cost>{0, 1}), 20);
+ auto Second = Problem.addNode(CostVector(std::vector<Cost>{2, 0, 3}), 10);
+ Context.expect(!Empty && First && Second,
+ "PBQP graphs must reject empty nodes and accept stable nonempty nodes");
+ if (!First || !Second)
+ return;
+ auto Self = Problem.addEdge(*First, *First, CostMatrix(2, 2));
+ auto WrongShape = Problem.addEdge(*First, *Second, CostMatrix(2, 2));
+ CostMatrix EdgeCosts(2, 3);
+ EdgeCosts.at(1, 2) = 7;
+ auto Edge = Problem.addEdge(*Second, *First, EdgeCosts.transpose(), 99);
+ auto Duplicate = Problem.addEdge(*First, *Second, CostMatrix(2, 3));
+ Context.expect(!Self && !WrongShape && Edge && !Duplicate && Problem.verify().has_value(),
+ "PBQP graph edges must enforce identity, dimensions, orientation, and uniqueness");
+ if (!Edge)
+ return;
+ const rcc::pbqp::Edge *Stored = Problem.edge(*Edge);
+ Context.expect(Stored != nullptr && Stored->First == *First && Stored->Second == *Second &&
+ Stored->Costs.rows() == 2 && Stored->Costs.columns() == 3 &&
+ Stored->Costs.at(1, 2) == 7,
+ "PBQP graph insertion must normalize reversed edge orientation");
+}
+
+void testReductions(TestContext &Context) {
+ using namespace rcc::pbqp;
+ DeterministicSolver Solver;
+
+ Graph DegreeZero;
+ auto Only = DegreeZero.addNode(CostVector(std::vector<Cost>{4, 1, 1}), 3);
+ auto ZeroSolution = Solver.solve(DegreeZero);
+ Context.expect(Only && ZeroSolution && ZeroSolution->selection(*Only) == 1 &&
+ ZeroSolution->totalCost() == 1,
+ "PBQP R0 reduction must select the first minimum-cost option");
+
+ Graph DegreeOne;
+ auto Left = DegreeOne.addNode(CostVector(std::vector<Cost>{3, 0}), 1);
+ auto Right = DegreeOne.addNode(CostVector(std::vector<Cost>{0, 2}), 2);
+ CostMatrix Pair(2, 2);
+ Pair.at(1, 0) = InfiniteCost;
+ auto PairEdge = DegreeOne.addEdge(*Left, *Right, std::move(Pair));
+ auto OneSolution = Solver.solve(DegreeOne);
+ Context.expect(Left && Right && PairEdge && OneSolution && OneSolution->selection(*Left) == 1 &&
+ OneSolution->selection(*Right) == 1 && OneSolution->totalCost() == 2,
+ "PBQP R1 reduction must propagate unary costs to its neighbor");
+
+ Graph DegreeTwo;
+ auto A = DegreeTwo.addNode(CostVector(std::vector<Cost>{0, 2}), 1);
+ auto B = DegreeTwo.addNode(CostVector(std::vector<Cost>{0, 0}), 2);
+ auto C = DegreeTwo.addNode(CostVector(std::vector<Cost>{2, 0}), 3);
+ CostMatrix AB(2, 2);
+ CostMatrix BC(2, 2);
+ AB.at(0, 1) = 5;
+ AB.at(1, 0) = 5;
+ BC.at(0, 1) = 5;
+ BC.at(1, 0) = 5;
+ auto ABEdge = DegreeTwo.addEdge(*A, *B, std::move(AB));
+ auto BCEdge = DegreeTwo.addEdge(*B, *C, std::move(BC));
+ auto TwoSolution = Solver.solve(DegreeTwo);
+ Context.expect(A && B && C && ABEdge && BCEdge && TwoSolution &&
+ TwoSolution->selection(*A) == 0 && TwoSolution->selection(*B) == 0 &&
+ TwoSolution->selection(*C) == 0 && TwoSolution->totalCost() == 2,
+ "PBQP R2 reduction must add the induced neighbor edge cost");
+}
+
+[[nodiscard]] rcc::Expected<rcc::pbqp::Solution> solveClique() {
+ using namespace rcc::pbqp;
+ Graph Problem;
+ std::vector<NodeID> Nodes;
+ for (std::uint64_t Index = 0; Index < 4; ++Index) {
+ auto Node = Problem.addNode(CostVector(std::vector<Cost>{9, 0, 0}), 100 - Index);
+ if (!Node)
+ return std::unexpected(Node.error());
+ Nodes.push_back(*Node);
+ }
+ for (std::size_t Left = 0; Left < Nodes.size(); ++Left) {
+ for (std::size_t Right = Left + 1; Right < Nodes.size(); ++Right) {
+ CostMatrix Costs(3, 3);
+ Costs.at(1, 1) = InfiniteCost;
+ Costs.at(2, 2) = InfiniteCost;
+ auto Edge = Problem.addEdge(Nodes[Left], Nodes[Right], std::move(Costs),
+ static_cast<std::uint64_t>(Left * Nodes.size() + Right));
+ if (!Edge)
+ return std::unexpected(Edge.error());
+ }
+ }
+ return DeterministicSolver{}.solve(Problem);
+}
+
+void testHeuristicAndDeterminism(TestContext &Context) {
+ auto First = solveClique();
+ auto Second = solveClique();
+ Context.expect(First && Second && std::ranges::equal(First->selections(), Second->selections()) &&
+ First->totalCost() == Second->totalCost(),
+ "PBQP higher-degree reduction must be deterministic");
+ if (First) {
+ Context.expect(First->totalCost() == 18,
+ "a four-node two-register clique must spill exactly two nodes");
+ }
+
+ rcc::pbqp::Graph Impossible;
+ auto Node = Impossible.addNode(
+ rcc::pbqp::CostVector(std::vector<rcc::pbqp::Cost>{rcc::pbqp::InfiniteCost}));
+ auto Failure = rcc::pbqp::DeterministicSolver{}.solve(Impossible);
+ Context.expect(Node && !Failure &&
+ Failure.error().code() == rcc::ErrorCode::RegisterAllocationFailed,
+ "PBQP solver must diagnose a problem with no finite assignment");
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testMath(Context);
+ testGraphValidation(Context);
+ testReductions(Context);
+ testHeuristicAndDeterminism(Context);
+ return Context.result();
+}