Skip to content

Commit 377efde

Browse files
committed
[AIEX] Add --aie-postpipeliner-solver alongside --aie-postpipeliner-target-ii
Introduce --aie-postpipeliner-solver as a simple boolean flag that runs the solver as a fallback after heuristics fail at every II. Keep the existing --aie-postpipeliner-target-ii option (and the loop-pragma initiation interval) with stricter semantics: when TargetII is set the solver runs only at that II, heuristics are skipped for it, and the --aie-postpipeliner-maxii cap is bypassed so the requested II is always attempted (one-shot, no fallback to other IIs). Precedence (highest first): 1. --aie-postpipeliner-target-ii=N (CLI) 2. --aie-postpipeliner-solver (CLI) 3. Loop-pragma initiation interval The hail mary and SEF solver paths are preserved unchanged.
1 parent f22abc2 commit 377efde

12 files changed

Lines changed: 384 additions & 59 deletions

llvm/lib/Target/AIE/AIEInterBlockScheduling.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,14 @@ SchedulingStage InterBlockScheduling::updateScheduling(BlockState &BS) {
613613
if (BS.getRegions().size() == 1) {
614614
auto &PostSWP = BS.getPostSWP();
615615
if (PostSWP.isPostPipelineCandidate(*BS.TheBlock)) {
616-
BS.FixPoint.II = PostSWP.getResMII(*BS.TheBlock);
616+
// A CLI --aie-postpipeliner-target-ii is a hard limit: start at
617+
// exactly that II (bypassing --aie-postpipeliner-maxii) and let
618+
// updatePipelining one-shot it. A pragma-driven TargetII is a soft
619+
// hint: start at ResMII and iterate normally; the solver fallback at
620+
// II == TargetII is handled inside the post-pipeliner.
621+
BS.FixPoint.II = PostSWP.isTargetIIHardLimit()
622+
? PostSWP.getTargetII()
623+
: PostSWP.getResMII(*BS.TheBlock);
617624
BS.FixPoint.IITries = 1;
618625
return SchedulingStage::Pipelining;
619626
}
@@ -627,11 +634,17 @@ SchedulingStage InterBlockScheduling::updatePipelining(BlockState &BS) {
627634
return BS.FixPoint.Stage;
628635
}
629636

630-
// Otherwise try a larger II.
631-
// We cut off at larger IIs to prevent excessive compilation time.
632-
if (++BS.FixPoint.II <= PostPipelinerMaxII &&
633-
++BS.FixPoint.IITries <= PostPipelinerMaxTryII) {
634-
return SchedulingStage::Pipelining;
637+
// A CLI --aie-postpipeliner-target-ii is one-shot: try only the requested
638+
// II, even if it exceeds --aie-postpipeliner-maxii. If that attempt
639+
// failed, do not try any other II. A pragma-driven TargetII keeps the
640+
// normal iteration (ResMII..MaxII).
641+
if (!BS.getPostSWP().isTargetIIHardLimit()) {
642+
// Otherwise try a larger II.
643+
// We cut off at larger IIs to prevent excessive compilation time.
644+
if (++BS.FixPoint.II <= PostPipelinerMaxII &&
645+
++BS.FixPoint.IITries <= PostPipelinerMaxTryII) {
646+
return SchedulingStage::Pipelining;
647+
}
635648
}
636649

637650
auto *BB = BS.TheBlock;
@@ -1042,10 +1055,9 @@ int InterBlockScheduling::getCyclesToAvoidResourceConflicts(
10421055
++NopCounter;
10431056
}
10441057

1045-
DEBUG_LOOPAWARE(dbgs() << "Resource conflict avoidance between"
1046-
<< " loop: " << *LoopMBB
1047-
<< " And epilogue: " << EpilogueMBB << " Requires "
1048-
<< NopCounter << " Nops\n");
1058+
DEBUG_LOOPAWARE(dbgs() << "Resource conflict avoidance between" << " loop: "
1059+
<< *LoopMBB << " And epilogue: " << EpilogueMBB
1060+
<< " Requires " << NopCounter << " Nops\n");
10491061

10501062
return NopCounter;
10511063
}

llvm/lib/Target/AIE/AIEPostPipeliner.cpp

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@ static cl::opt<int>
4545
cl::desc("Number of runs for heuristics that converge"),
4646
cl::init(20), cl::Hidden);
4747

48-
static cl::opt<int> PresetII("aie-postpipeliner-target-ii",
49-
cl::desc("II for which to allow the solver"),
50-
cl::init(0), cl::Hidden);
48+
static cl::opt<bool>
49+
UseSolver("aie-postpipeliner-solver",
50+
cl::desc("Use the solver as fallback after heuristics fail"),
51+
cl::init(false), cl::Hidden);
52+
53+
static cl::opt<int>
54+
PresetII("aie-postpipeliner-target-ii",
55+
cl::desc("Run solver-only at this II; bypasses MaxII and "
56+
"skips heuristics"),
57+
cl::init(0), cl::Hidden);
5158

5259
static cl::opt<int>
5360
SolverRetries("aie-postpipeliner-solver-retries",
@@ -166,15 +173,41 @@ bool PostPipeliner::isPostPipelineCandidate(MachineBasicBlock &LoopBlock) {
166173
return false;
167174
}
168175

169-
if (PresetII) {
176+
// Determine TargetII. Precedence (highest first):
177+
// 1. --aie-postpipeliner-target-ii=N (CLI): hard one-shot limit. The
178+
// post-pipeliner attempts exactly N (bypassing
179+
// --aie-postpipeliner-maxii) and runs the solver only, skipping
180+
// heuristics.
181+
// 2. --aie-postpipeliner-solver (CLI): no TargetII. Heuristics still
182+
// run at every II in [ResMII, MaxII]; the solver runs as a fallback
183+
// after heuristics fail at each II. This flag overrides the pragma
184+
// below.
185+
// 3. Loop-pragma initiation interval: soft hint. Heuristics run at
186+
// every II in [ResMII, MaxII] (matches pre-commit behavior); the
187+
// solver additionally runs at II == TargetII as a last resort.
188+
// All three sources require an actual solver backend to do anything
189+
// useful: when the build has no solver compiled in, getSolvers() returns
190+
// an empty vector and every solver call would silently no-op. Treat the
191+
// requests as unsupported in that configuration so we keep the pre-commit
192+
// behavior (heuristics only, full II search up to
193+
// --aie-postpipeliner-maxii).
194+
if (!Solver::hasSolver()) {
195+
if (PresetII || UseSolver || getInitiationInterval(getLoopID(LoopBlock))) {
196+
DEBUG_SUMMARY(
197+
dbgs() << " PostPipeliner: ignoring TargetII/solver request, "
198+
"no solver compiled in\n");
199+
}
200+
} else if (PresetII) {
170201
TargetII = PresetII;
171-
return true;
172-
}
173-
auto ParsedInitiationInterval = getInitiationInterval(getLoopID(LoopBlock));
174-
if (ParsedInitiationInterval) {
175-
TargetII = *ParsedInitiationInterval;
176-
DEBUG_SUMMARY(dbgs() << " PostPipeliner: TargetII=" << TargetII << "\n");
202+
TargetIIIsHardLimit = true;
203+
} else if (!UseSolver) {
204+
if (auto Pragma = getInitiationInterval(getLoopID(LoopBlock)))
205+
TargetII = *Pragma;
177206
}
207+
if (TargetII)
208+
DEBUG_SUMMARY(dbgs() << " PostPipeliner: TargetII=" << TargetII
209+
<< (TargetIIIsHardLimit ? " (hard)" : " (soft)")
210+
<< "\n");
178211

179212
return true;
180213
}
@@ -1439,41 +1472,60 @@ static const ConfigStrategy::Configuration Heuristics[] = {
14391472

14401473
bool PostPipeliner::tryApproaches() {
14411474
DEBUG_SUMMARY(dbgs() << "-- MinLength=" << MinLength << "\n");
1442-
int HeuristicIndex = 0;
1443-
for (const auto &Config : Heuristics) {
1444-
if (Heuristic >= 0 && Heuristic != HeuristicIndex++) {
1445-
continue;
1446-
}
1447-
const int StrategyLength = MinLength + Config.ExtraStages * II;
1448-
ConfigStrategy S(*DAG, Info, StrategyLength, Config.TopDown,
1449-
Config.Alternate, Config.Components, Config.Modifiers);
1450-
resetSchedule(/*FullReset=*/true);
1451-
for (int Run = 0; Run < Config.Runs && Run < HeuristicRuns; Run++) {
1452-
DEBUG_SUMMARY(dbgs() << "--- Strategy " << S.name() << " run=" << Run
1453-
<< " trying II=" << II << "\n");
1454-
if (scheduleWithStrategy(S)) {
1455-
DEBUG_SUMMARY(dbgs()
1456-
<< " Strategy " << S.name() << " run=" << Run
1457-
<< " found NS=" << NStages << " II=" << II << "\n");
1458-
return true;
1475+
1476+
// Decide which strategies to run at this II:
1477+
// - CLI --aie-postpipeliner-target-ii (TargetIIIsHardLimit): solver
1478+
// only, skip heuristics. The CLI flag is a hard request to spend
1479+
// solver time and the user has already seen what heuristics produce.
1480+
// - Pragma TargetII: heuristics on every II, plus solver as a last
1481+
// resort at II == TargetII.
1482+
// - --aie-postpipeliner-solver (UseSolver): heuristics on every II,
1483+
// plus solver as a fallback at every II.
1484+
// Belt-and-braces: isPostPipelineCandidate already drops these requests
1485+
// in builds without a solver backend, but re-check Solver::hasSolver()
1486+
// here so we never call solve() with an empty solver list.
1487+
const bool SolverAvailable = Solver::hasSolver();
1488+
const bool SolverOnly = TargetIIIsHardLimit;
1489+
const bool RunHeuristics = !SolverOnly;
1490+
const bool SolverAtThisII =
1491+
UseSolver || SolverOnly || (TargetII != 0 && II == TargetII);
1492+
const bool RunSolver = SolverAvailable && SolverAtThisII;
1493+
1494+
if (RunHeuristics) {
1495+
int HeuristicIndex = 0;
1496+
for (const auto &Config : Heuristics) {
1497+
if (Heuristic >= 0 && Heuristic != HeuristicIndex++) {
1498+
continue;
14591499
}
1460-
if (!S.checkAndResetChanged()) {
1461-
// If nothing changed, there's no use in rerunning.
1462-
break;
1500+
const int StrategyLength = MinLength + Config.ExtraStages * II;
1501+
ConfigStrategy S(*DAG, Info, StrategyLength, Config.TopDown,
1502+
Config.Alternate, Config.Components, Config.Modifiers);
1503+
resetSchedule(/*FullReset=*/true);
1504+
for (int Run = 0; Run < Config.Runs && Run < HeuristicRuns; Run++) {
1505+
DEBUG_SUMMARY(dbgs() << "--- Strategy " << S.name() << " run=" << Run
1506+
<< " trying II=" << II << "\n");
1507+
if (scheduleWithStrategy(S)) {
1508+
DEBUG_SUMMARY(dbgs()
1509+
<< " Strategy " << S.name() << " run=" << Run
1510+
<< " found NS=" << NStages << " II=" << II << "\n");
1511+
return true;
1512+
}
1513+
if (!S.checkAndResetChanged()) {
1514+
// If nothing changed, there's no use in rerunning.
1515+
break;
1516+
}
1517+
resetSchedule(/*FullReset=*/false);
14631518
}
1464-
resetSchedule(/*FullReset=*/false);
1519+
DEBUG_SUMMARY(dbgs() << " Strategy " << S.name() << " failed\n");
1520+
}
1521+
IterCountSlackStrategy Relaxed(*DAG, Info, MinLength + II);
1522+
resetSchedule(/*FullReset=*/true);
1523+
if (scheduleWithStrategy(Relaxed)) {
1524+
return true;
14651525
}
1466-
DEBUG_SUMMARY(dbgs() << " Strategy " << S.name() << " failed\n");
1467-
}
1468-
IterCountSlackStrategy Relaxed(*DAG, Info, MinLength + II);
1469-
resetSchedule(/*FullReset=*/true);
1470-
if (scheduleWithStrategy(Relaxed)) {
1471-
return true;
14721526
}
14731527

1474-
// TargetII is the OK from the user to spend some time reaching this II.
1475-
// Therefore, if we haven't found a solution yet, bring in the big guns.
1476-
if (II == TargetII) {
1528+
if (RunSolver) {
14771529
const SolverData Data = createSolverData();
14781530
const int NS = MinLength / II;
14791531
if (solve(Data, NS, false)) {

llvm/lib/Target/AIE/AIEPostPipeliner.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,11 +248,24 @@ class PostPipeliner {
248248
/// The minimum tripcount, read from the pragma, or from an LC initialization.
249249
int MinTripCount = 0;
250250

251-
/// The II requested by a pragma. This will trigger expensive algorithms
252-
/// like solvers or exhaustive searches to be run if the heuristic methods
253-
/// don't find a solution.
251+
/// The II requested by --aie-postpipeliner-target-ii or by a loop pragma.
252+
/// At this II the post-pipeliner will additionally run the solver. The
253+
/// strength of the request depends on TargetIIIsHardLimit (see below).
254+
/// Stays 0 in builds without a solver backend (Solver::hasSolver() ==
255+
/// false), even if the user passed --aie-postpipeliner-target-ii or a
256+
/// loop pragma.
254257
int TargetII = 0;
255258

259+
/// When true, TargetII came from --aie-postpipeliner-target-ii and is a
260+
/// hard limit: the post-pipeliner attempts exactly TargetII (one-shot,
261+
/// bypassing --aie-postpipeliner-maxii) and runs the solver only,
262+
/// skipping heuristics.
263+
/// When false, TargetII came from a loop pragma and is a soft hint: the
264+
/// post-pipeliner iterates IIs from ResMII up to --aie-postpipeliner-maxii
265+
/// running heuristics on each, and additionally runs the solver as a
266+
/// fallback at II == TargetII.
267+
bool TargetIIIsHardLimit = false;
268+
256269
/// The Preheader of the loop.
257270
MachineBasicBlock *Preheader = nullptr;
258271

@@ -353,6 +366,15 @@ class PostPipeliner {
353366
/// \pre isPostPipelineCandidate has returned true
354367
int getResMII(MachineBasicBlock &LoopBlock);
355368

369+
/// Return the user/pragma-requested II, or 0 if none was set.
370+
/// \pre isPostPipelineCandidate has returned true
371+
int getTargetII() const { return TargetII; }
372+
373+
/// Return true when TargetII is a hard one-shot limit (CLI
374+
/// --aie-postpipeliner-target-ii), false when it is a soft pragma hint.
375+
/// \pre isPostPipelineCandidate has returned true
376+
bool isTargetIIHardLimit() const { return TargetIIIsHardLimit; }
377+
356378
// Schedule using the given InitiationInterval. Return true when successful.
357379
// In that case calls to the query methods below are legitimate.
358380
bool schedule(ScheduleDAGMI &DAG, int InitiationInterval,

llvm/lib/Target/AIE/AIESWPSolver.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ std::vector<std::unique_ptr<SWPSolver>> getSolvers() {
5858
return Solvers;
5959
}
6060

61+
bool hasSolver() {
62+
#if LLVM_WITH_Z3
63+
return true;
64+
#else
65+
return false;
66+
#endif // LLVM_WITH_Z3
67+
}
68+
6169
Slot &SolverData::addSlot(int N) {
6270
auto It = Slots.emplace(N, Slot(N)).first;
6371
return It->second;

llvm/lib/Target/AIE/AIESWPSolver.h

Lines changed: 6 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

@@ -197,6 +197,11 @@ class SWPSolver {
197197
// Return the set of solvers to try
198198
std::vector<std::unique_ptr<SWPSolver>> getSolvers();
199199

200+
/// Return true if at least one SWP solver backend is compiled into this
201+
/// build. When false, getSolvers() returns an empty vector and any code
202+
/// path that depends on the solver must fall back gracefully.
203+
bool hasSolver();
204+
200205
#if LLVM_WITH_Z3
201206
class Z3Solver : public SWPSolver {
202207
protected:

llvm/test/CodeGen/AIE/aie2p/schedule/postpipeliner/gemm-bfp16-v10-solver.mir

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
# See https://llvm.org/LICENSE.txt for license information.
44
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
55
#
6-
# (c) Copyright 2025 Advanced Micro Devices, Inc. or its affiliates
6+
# (c) Copyright 2025-2026 Advanced Micro Devices, Inc. or its affiliates
77

88
# REQUIRES: enable_z3_solver
99

1010
# RUN: llc -verify-machineinstrs --mtriple=aie2p -O2 %s \
1111
# RUN: --start-before=postmisched \
1212
# RUN: --aie-postpipeliner-heuristic-runs=1 \
13+
# RUN: --aie-postpipeliner-solver \
1314
# RUN: -o - | FileCheck %s
1415

1516

llvm/test/CodeGen/AIE/aie2p/schedule/postpipeliner/gemm-bfp16-v6-solver.mir

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
# See https://llvm.org/LICENSE.txt for license information.
44
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
55
#
6-
# (c) Copyright 2025 Advanced Micro Devices, Inc. or its affiliates
6+
# (c) Copyright 2025-2026 Advanced Micro Devices, Inc. or its affiliates
77

88
# REQUIRES: enable_z3_solver
99

1010
# RUN: llc -verify-machineinstrs --mtriple=aie2p -O2 %s \
1111
# RUN: --start-before=postmisched \
1212
# RUN: --aie-postpipeliner-heuristic-runs=1 \
13+
# RUN: --aie-postpipeliner-solver \
1314
# RUN: -o - | FileCheck %s
1415

1516
# derived from GEMM_Bfp16_opt_0

llvm/test/CodeGen/AIE/aie2p/schedule/postpipeliner/gemm-bfp16-v7-solver.mir

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
# See https://llvm.org/LICENSE.txt for license information.
44
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
55
#
6-
# (c) Copyright 2025 Advanced Micro Devices, Inc. or its affiliates
6+
# (c) Copyright 2025-2026 Advanced Micro Devices, Inc. or its affiliates
77

88
# REQUIRES: enable_z3_solver
99

1010
# RUN: llc -verify-machineinstrs --mtriple=aie2p -O2 %s \
1111
# RUN: --start-before=postmisched \
1212
# RUN: --aie-postpipeliner-heuristic-runs=1 \
13+
# RUN: --aie-postpipeliner-solver \
1314
# RUN: -o - | FileCheck %s
1415

1516

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# RUN: llc -mtriple=aie2ps -O2 --start-before=postmisched %s \
1212
# RUN: --aie-postpipeliner-heuristic-runs=1 \
13-
# RUN: --aie-postpipeliner-target-ii=4 \
13+
# RUN: --aie-postpipeliner-solver \
1414
# RUN: -o - | FileCheck %s
1515

1616
# MaxPool2D inner loop (10 instructions) with VSEL.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# RUN: llc -mtriple=aie2ps -O2 --start-before=postmisched %s \
1212
# RUN: --aie-postpipeliner-heuristic-runs=1 \
13-
# RUN: --aie-postpipeliner-target-ii=4 \
13+
# RUN: --aie-postpipeliner-solver \
1414
# RUN: -o - | FileCheck %s
1515

1616
# MaxPool2D inner loop (9 instructions) without VSEL.

0 commit comments

Comments
 (0)