Skip to content

Commit f22abc2

Browse files
committed
[AIEX] Retry solver with resource exclusion constraints on CheckFixedSchedule failure
When the Z3 solver finds a schedule that satisfies latency and slot constraints but fails CheckFixedSchedule validation due to pipeline resource hazards, identify the conflicting instruction pair and their linear cycle difference, feed it back as a Z3 constraint, and re-solve incrementally. The solver does not model pipeline resource hazards (functional unit occupancy across pipeline stages). Previously, if the solver's solution failed the CheckFixedSchedule resource check, the solver attempt was abandoned entirely. Now, the failing instruction pair is identified by replaying the schedule and isolating pairwise scoreboard conflicts, and an exclusion constraint is added to prevent the same cycle distance between those instructions in future solutions. The retry count is configurable via --aie-postpipeliner-solver-retries (default 3).
1 parent d784567 commit f22abc2

5 files changed

Lines changed: 168 additions & 38 deletions

File tree

llvm/lib/Target/AIE/AIEPostPipeliner.cpp

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "llvm/CodeGen/ScheduleDAGInstrs.h"
2727
#include "llvm/Transforms/Utils/LoopUtils.h"
2828
#include <limits>
29+
#include <numeric>
2930
#include <string>
3031

3132
#define DEBUG_TYPE "postpipeliner"
@@ -48,6 +49,11 @@ static cl::opt<int> PresetII("aie-postpipeliner-target-ii",
4849
cl::desc("II for which to allow the solver"),
4950
cl::init(0), cl::Hidden);
5051

52+
static cl::opt<int>
53+
SolverRetries("aie-postpipeliner-solver-retries",
54+
cl::desc("Number of solver retries with resource exclusions"),
55+
cl::init(3), cl::Hidden);
56+
5157
PipelineScheduleVisitor::~PipelineScheduleVisitor() {}
5258

5359
std::optional<int> PostPipelinerStrategy::fitInInterval(
@@ -1533,34 +1539,120 @@ SolverData PostPipeliner::createSolverData() {
15331539
return Data;
15341540
}
15351541

1542+
std::optional<ResourceExclusion> PostPipeliner::identifyResourceConflict(
1543+
const std::vector<int> &Schedule) const {
1544+
// Sort instruction indices by ascending cycle (matching CheckFixedSchedule
1545+
// ordering). Break ties by ascending NodeNum.
1546+
SmallVector<int, 16> Order(NInstr);
1547+
std::iota(Order.begin(), Order.end(), 0);
1548+
std::sort(Order.begin(), Order.end(), [&](int A, int B) {
1549+
if (Schedule[A] != Schedule[B])
1550+
return Schedule[A] < Schedule[B];
1551+
return A < B;
1552+
});
1553+
1554+
// Replay the scoreboard build, matching scheduleFirstIteration.
1555+
const int PipelineDepth = HR.getPipelineDepth();
1556+
const int Horizon =
1557+
std::min(II + PipelineDepth, ScoreboardSize - PipelineDepth);
1558+
1559+
ResourceScoreboard<FuncUnitWrapper> ReplayBoard;
1560+
ReplayBoard.config(0, ScoreboardSize - 1);
1561+
1562+
for (int K = 0; K < NInstr; K++) {
1563+
const int N = Order[K];
1564+
const int ModCycleN = Schedule[N] % II;
1565+
MachineInstr &MI = *DAG->SUnits[N].getInstr();
1566+
1567+
// Check if N conflicts with the current replay scoreboard.
1568+
if (HR.checkConflict(ReplayBoard, MI, ModCycleN)) {
1569+
// Identify which previously-placed instruction caused the conflict.
1570+
for (int P = 0; P < K; P++) {
1571+
const int M = Order[P];
1572+
const int ModCycleM = Schedule[M] % II;
1573+
MachineInstr &MIM = *DAG->SUnits[M].getInstr();
1574+
1575+
// Build a single-instruction scoreboard for M.
1576+
ResourceScoreboard<FuncUnitWrapper> PairBoard;
1577+
PairBoard.config(0, ScoreboardSize - 1);
1578+
int Cycle = ModCycleM;
1579+
while (Cycle < Horizon) {
1580+
HR.emitInScoreboard(PairBoard, MIM, MIM.getDesc(), Cycle);
1581+
Cycle += II;
1582+
}
1583+
1584+
if (HR.checkConflict(PairBoard, MI, ModCycleN)) {
1585+
const int Delta = Schedule[N] - Schedule[M];
1586+
LLVM_DEBUG(dbgs() << "Resource conflict: SU" << N << " @"
1587+
<< Schedule[N] << " vs SU" << M << " @"
1588+
<< Schedule[M] << " (delta=" << Delta << ")\n");
1589+
return ResourceExclusion{M, N, Delta};
1590+
}
1591+
}
1592+
// Conflict exists but cannot be attributed to a single pair.
1593+
LLVM_DEBUG(dbgs() << "Resource conflict at SU" << N
1594+
<< " but no pairwise culprit found\n");
1595+
return std::nullopt;
1596+
}
1597+
1598+
// Emit N into the replay scoreboard with multi-iteration copies.
1599+
int Cycle = ModCycleN;
1600+
while (Cycle < Horizon) {
1601+
HR.emitInScoreboard(ReplayBoard, MI, MI.getDesc(), Cycle);
1602+
Cycle += II;
1603+
}
1604+
}
1605+
1606+
// No conflict found (failure was not a resource issue).
1607+
return std::nullopt;
1608+
}
1609+
15361610
bool PostPipeliner::applySolver(const SolverData &Data, SWPSolver &Solver,
15371611
int NS, bool SEFStage) {
15381612

15391613
// We don't model the resource hazards. They would be very tedious to express,
15401614
// since resource uses are offset relative to the instruction cycle. We would
15411615
// need to interpret raw itinerary data, and the modulo constraints on those
1542-
// would lead to very awkard expressions.
1616+
// would lead to very awkward expressions.
1617+
// Instead, we solve an optimistic model and validate with CheckFixedSchedule.
1618+
// If validation fails due to resource conflicts, we feed back pairwise
1619+
// exclusion constraints and re-solve incrementally.
15431620
Solver.setScheduleSize(II, NS);
15441621
Solver.genModel(Data, SEFStage);
1545-
if (!Solver.solveModel()) {
1546-
return false;
1547-
}
15481622

1549-
// We have a solution of our model, but this is missing some constraints, in
1550-
// order to save solver time. We extract the cycles, and make a final check
1551-
// for all constraints using a dedicated strategy.
1552-
auto Schedule = Solver.getSUCycles();
1553-
DEBUG_SUMMARY(dbgs() << "Solver found "; for (auto C
1554-
: Schedule) dbgs()
1555-
<< C << ", ";
1556-
dbgs() << "\n";);
1557-
CheckFixedSchedule S{*DAG, Info, II * NS, Schedule};
1558-
resetSchedule(/*FullReset=*/true);
1559-
DEBUG_SUMMARY(dbgs() << "--- Strategy " << S.name() << "\n");
1560-
if (scheduleWithStrategy(S)) {
1561-
DEBUG_SUMMARY(dbgs() << " Strategy " << S.name() << " found II=" << II
1562-
<< "\n");
1563-
return true;
1623+
const int MaxAttempts = 1 + SolverRetries;
1624+
for (int Attempt = 0; Attempt < MaxAttempts; Attempt++) {
1625+
if (!Solver.solveModel())
1626+
return false;
1627+
1628+
auto Schedule = Solver.getSUCycles();
1629+
DEBUG_SUMMARY(dbgs() << "Solver found ";
1630+
for (auto C : Schedule) dbgs() << C << ", "; dbgs() << "\n";);
1631+
DEBUG_SUMMARY(dumpCycles(Info, II));
1632+
CheckFixedSchedule S{*DAG, Info, II * NS, Schedule};
1633+
resetSchedule(/*FullReset=*/true);
1634+
DEBUG_SUMMARY(dbgs() << "--- Strategy " << S.name() << "\n");
1635+
if (scheduleWithStrategy(S)) {
1636+
DEBUG_SUMMARY(dbgs() << " Strategy " << S.name() << " found II=" << II
1637+
<< "\n");
1638+
return true;
1639+
}
1640+
1641+
// Validation failed. Try to identify a pairwise resource conflict
1642+
// to feed back to the solver.
1643+
auto Exclusion = identifyResourceConflict(Schedule);
1644+
if (!Exclusion) {
1645+
LLVM_DEBUG(dbgs() << "Solver: cannot identify resource conflict, "
1646+
<< "stopping retries\n");
1647+
return false;
1648+
}
1649+
1650+
DEBUG_SUMMARY(dbgs() << "Solver: retry " << (Attempt + 1) << "/"
1651+
<< SolverRetries << " excluding cycle(SU"
1652+
<< Exclusion->InstrB << ") - cycle(SU"
1653+
<< Exclusion->InstrA
1654+
<< ") == " << Exclusion->CycleDelta << "\n");
1655+
Solver.genResourceExclusion(*Exclusion);
15641656
}
15651657

15661658
return false;

llvm/lib/Target/AIE/AIEPostPipeliner.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace llvm::AIE {
3131
namespace Solver {
3232
class SolverData;
3333
class SWPSolver;
34+
struct ResourceExclusion;
3435
} // namespace Solver
3536

3637
/// This is a dedicated softwarepipeliner. Its schedule method takes an
@@ -312,6 +313,12 @@ class PostPipeliner {
312313
bool applySolver(const Solver::SolverData &Data, Solver::SWPSolver &Solver,
313314
int NS, bool SEFStage);
314315

316+
/// Replay a solver schedule to identify the pairwise resource conflict.
317+
/// Returns the conflicting pair and their linear cycle delta, or nullopt
318+
/// if the conflict cannot be attributed to a single instruction pair.
319+
std::optional<Solver::ResourceExclusion>
320+
identifyResourceConflict(const std::vector<int> &Schedule) const;
321+
315322
/// Try all approaches to arrive at a SWP schedule for the current II.
316323
/// If it returns true, a valid schedule is laid down in Info.
317324
bool tryApproaches();

llvm/lib/Target/AIE/AIESWPSolver.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// See https://llvm.org/LICENSE.txt for license information.
55
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66
//
7-
// (c) Copyright 2025 Advanced Micro Devices, Inc. or its affiliates
7+
// (c) Copyright 2025-2026 Advanced Micro Devices, Inc. or its affiliates
88
//
99
//===----------------------------------------------------------------------===//
1010
// This file contains an interface to create constraints to model a software
@@ -208,6 +208,14 @@ std::vector<int> Z3Solver::getSUCycles() {
208208
return Cycles;
209209
}
210210

211+
void Z3Solver::genResourceExclusion(const ResourceExclusion &Excl) {
212+
Solver.add(CycleExprs[Excl.InstrB] - CycleExprs[Excl.InstrA] !=
213+
Context.int_val(Excl.CycleDelta));
214+
LLVM_DEBUG(dbgs() << "Solver: added resource exclusion: cycle(SU"
215+
<< Excl.InstrB << ") - cycle(SU" << Excl.InstrA
216+
<< ") != " << Excl.CycleDelta << "\n");
217+
}
218+
211219
void Z3Solver::genModel(const SolverData &Data, bool SEFStage) {
212220
// Compute the stage count to make every instruction's interval fit
213221
int Length = 0;

llvm/lib/Target/AIE/AIESWPSolver.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626

2727
namespace llvm::AIE::Solver {
2828

29+
/// Represents a pairwise resource conflict discovered during
30+
/// CheckFixedSchedule validation. The constraint says: the linear cycle
31+
/// difference between InstrB and InstrA must not equal CycleDelta.
32+
struct ResourceExclusion {
33+
int InstrA;
34+
int InstrB;
35+
/// Linear cycle difference: Schedule[InstrB] - Schedule[InstrA].
36+
int CycleDelta;
37+
};
38+
2939
// These classes describe the feature of the ISA that model the constraints
3040
// Each instruction has a slot. Dependences between two instructions carry a
3141
// latency.
@@ -176,6 +186,10 @@ class SWPSolver {
176186
virtual void genModel(const SolverData &Data, bool SEFStage) = 0;
177187
// Call the solver on the model. Return whether it was satisfiable.
178188
virtual bool solveModel() = 0;
189+
// Add a resource exclusion constraint discovered from a failed
190+
// CheckFixedSchedule validation. The solver can then be re-solved
191+
// incrementally.
192+
virtual void genResourceExclusion(const ResourceExclusion &Excl) = 0;
179193
// Generate further instruction conflict constraints
180194
void conflicts(const SolverData &Data);
181195
};
@@ -230,6 +244,7 @@ class Z3Solver : public SWPSolver {
230244
void genModel(const SolverData &Data, bool SEFStage) override;
231245
bool solveModel() override;
232246
std::vector<int> getSUCycles() override;
247+
void genResourceExclusion(const ResourceExclusion &Excl) override;
233248
};
234249

235250
// In the binary formulation, we have a lot of binary variables,

llvm/test/CodeGen/AIE/aie2ps/schedule/postpipeliner/maxpool-10instr-solver.mir

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,44 @@
1717
# Similar to the 9-instr variant but with an extra VSEL_8 between
1818
# VSHIFT and VMAX_LT_8 in the compute chain.
1919
# The Z3 solver finds a solution at II=4 NS=4 but it fails
20-
# CheckFixedSchedule due to pipeline resource hazards.
21-
# Without the retry mechanism the heuristic falls back to II=7.
20+
# CheckFixedSchedule due to pipeline resource hazards. The resource
21+
# exclusion retry feeds back the conflicting pair as a Z3 constraint
22+
# and re-solves to find a valid II=4 schedule.
2223

2324
--- |
2425
target triple = "aie2ps"
2526

2627
define void @maxpool_inner_10instr(ptr addrspace(5) noalias %data, ptr addrspace(6) noalias %weights, ptr addrspace(7) noalias %out, i32 %n) {
2728
; CHECK-LABEL: maxpool_inner_10instr:
2829
; CHECK: // %bb.0: // %preheader
29-
; CHECK-NEXT: nopa ; movs p3, p1; nopx ; mov p0, p6
30-
; CHECK-NEXT: vlda wl7, [p3], #32; vldb wh9, [p0, #32]; movs p5, p6; mov r24, p0
31-
; CHECK-NEXT: vldb.3d wl9, [p5], d0; and r26, r24, r18
32-
; CHECK-NEXT: movs p0, p5
33-
; CHECK-NEXT: nop
34-
; CHECK-NEXT: movxm ls, #.LBB0_1
35-
; CHECK-NEXT: movxm le, #.L_LEnd0
36-
; CHECK-NEXT: nopa ; nopb ; nops ; add.nc lc, r2, #-1; mov p4, p2; nopv
30+
; CHECK-NEXT: nopa ; nopb ; nopx ; mov p5, p6
31+
; CHECK-NEXT: vldb.3d wl9, [p5], d0
32+
; CHECK-NEXT: mov p0, p6
33+
; CHECK-NEXT: vldb wh9, [p0, #32]; mov r24, p0
34+
; CHECK-NEXT: movs p0, p5; mov p3, p1
35+
; CHECK-NEXT: vlda wl7, [p3], #32; vldb.3d wl9, [p5], d0
36+
; CHECK-NEXT: and r26, r24, r18
37+
; CHECK-NEXT: vldb wh9, [p0, #32]; add.nc lc, r2, #-3; mov r24, p0
38+
; CHECK-NEXT: movs p0, p5; movxm ls, #.LBB0_1
39+
; CHECK-NEXT: vlda wl7, [p3], #32; vldb.3d wl9, [p5], d0; nops ; movxm le, #.L_LEnd0; nopv
40+
; CHECK-NEXT: nopa ; nopb ; nops ; and r26, r24, r18; vshift x5, x9, x0, r26; nopv
41+
; CHECK-NEXT: nopa ; vldb wh9, [p0, #32]; movs p4, p2; nopx ; mov r24, p0; nopv
42+
; CHECK-NEXT: nopa ; nopb ; movs p0, p5; nopx ; vsel.8 x3, x0, x5, r5:r4; nopv
3743
; CHECK-NEXT: .LBB0_1: // %loop_body
3844
; CHECK-NEXT: // =>This Inner Loop Header: Depth=1
39-
; CHECK-NEXT: vlda wl7, [p3], #32; vldb wh9, [p0, #32]; nops ; nopx ; mov r24, p0; nopv
40-
; CHECK-NEXT: nopa ; vldb.3d wl9, [p5], d0; nops ; and r26, r24, r18; vshift x5, x9, x0, r26; nopv
41-
; CHECK-NEXT: nopa ; nopb ; movs p0, p5; nopx ; vsel.8 x3, x0, x5, r5:r4; nopv
42-
; CHECK-NEXT: nopa ; nopb ; nops ; nopx ; vmax_lt.8 x1, r17:r16, x7, x3, vaddsign1; nopv
43-
; CHECK-NEXT: nopa ; nopb ; nops ; nopxm ; nopv
44-
; CHECK-NEXT: nopa ; nopb ; vst wl1, [p4], #32; nopxm ; nopv
45+
; CHECK-NEXT: vlda wl7, [p3], #32; vldb.3d wl9, [p5], d0; nops ; nopx ; vmax_lt.8 x1, r17:r16, x7, x3, vaddsign1; nopv
46+
; CHECK-NEXT: nopa ; nopb ; nops ; and r26, r24, r18; vshift x5, x9, x0, r26; nopv
47+
; CHECK-NEXT: nopa ; vldb wh9, [p0, #32]; vst wl1, [p4], #32; nopx ; mov r24, p0; nopv
4548
; CHECK-NEXT: .L_LEnd0:
46-
; CHECK-NEXT: nopa ; nopb ; nops ; nopxm ; nopv
49+
; CHECK-NEXT: nopa ; nopb ; movs p0, p5; nopx ; vsel.8 x3, x0, x5, r5:r4; nopv
4750
; CHECK-NEXT: // %bb.2: // %exit
48-
; CHECK-NEXT: nopa ; nopb ; nops ; nopxm ; nopv
51+
; CHECK-NEXT: vlda wl7, [p3], #32; nopx ; vmax_lt.8 x1, r17:r16, x7, x3, vaddsign1
52+
; CHECK-NEXT: and r26, r24, r18; vshift x5, x9, x0, r26
53+
; CHECK-NEXT: vst wl1, [p4], #32
54+
; CHECK-NEXT: vsel.8 x3, x0, x5, r5:r4
55+
; CHECK-NEXT: vmax_lt.8 x1, r17:r16, x7, x3, vaddsign1
4956
; CHECK-NEXT: vshift x5, x9, x0, r26
57+
; CHECK-NEXT: vst wl1, [p4], #32
5058
; CHECK-NEXT: vsel.8 x3, x0, x5, r5:r4
5159
; CHECK-NEXT: vmax_lt.8 x1, r17:r16, x7, x3, vaddsign1
5260
; CHECK-NEXT: nop

0 commit comments

Comments
 (0)