11#include "mlir/Dialect/SCF/IR/SCF.h"
12#include "mlir/Dialect/Utils/StaticValueUtils.h"
13#include "mlir/IR/Matchers.h"
14#include "mlir/Interfaces/LoopLikeInterface.h"
15#include "mlir/Interfaces/SideEffectInterfaces.h"
16#include "llvm/ADT/STLExtras.h"
17#include "llvm/Support/Debug.h"
18#include "llvm/Support/MathExtras.h"
23#define DEBUG_TYPE "aie-channel-turns"
31constexpr size_t maxPeriodEvents = 4096;
33constexpr int maxPeriods = 256;
36constexpr int64_t runsForever = (int64_t{1} << 24) - 2;
40 std::string &reason) {
41 assert(!ends.empty() &&
"turns between no endpoints");
42 ObjectFifoCoreEndpointOp first = ends.front();
43 Value tile = first.getTile();
44 if (llvm::any_of(ends, [&](ObjectFifoCoreEndpointOp end) {
45 return end.drains() != first.drains() || end.getTile() != tile;
47 reason =
"the endpoints are not all one core's, one way";
51 for (Operation *user : tile.getUsers())
52 if (
auto candidate = dyn_cast<CoreOp>(user))
55 reason =
"no core works through them";
58 SmallVector<StringAttr> names;
59 for (ObjectFifoCoreEndpointOp end : ends)
60 names.push_back(end.getSymNameAttr());
61 return read(core, names, first.drains(), reason);
65 bool coreDrains, std::string &reason) {
66 this->coreDrains = coreDrains;
70 DenseMap<StringAttr, unsigned> index;
71 for (
auto [i, name] : llvm::enumerate(names))
75 coreDrains ? ObjectFifoPort::Consume : ObjectFifoPort::Produce;
76 auto endOf = [&](Operation *op) -> std::optional<unsigned> {
77 FlatSymbolRefAttr name;
78 std::optional<ObjectFifoPort> used;
79 if (
auto acquire = dyn_cast<ObjectFifoAcquireOp>(op)) {
80 name = acquire.getObjFifoNameAttr();
81 used = acquire.getPort();
82 }
else if (
auto release = dyn_cast<ObjectFifoReleaseOp>(op)) {
83 name = release.getObjFifoNameAttr();
84 used = release.getPort();
86 if (!name || (used && *used != port))
88 auto it = index.find(name.getAttr());
89 if (it == index.end())
97 auto onceAfterLoops = [&](Operation *op) {
98 if (!coreDrains || !isa<ObjectFifoReleaseOp>(op) ||
99 op->getParentOfType<LoopLikeOpInterface>())
101 return llvm::none_of(
102 llvm::make_range(std::next(op->getIterator()), op->getBlock()->end()),
103 [&](Operation &later) {
105 .walk([&](ObjectFifoAcquireOp acquire) {
106 return endOf(acquire) ? WalkResult::interrupt()
107 : WalkResult::advance();
112 SmallVector<Operation *> uses, tails;
113 core.walk([&](Operation *op) {
115 (onceAfterLoops(op) ? tails : uses).push_back(op);
118 reason =
"the core never acquires them";
122 auto loopsAround = [&](Operation *op) {
123 SmallVector<Operation *> loops;
124 for (Operation *parent = op->getParentOp(); parent != core;
125 parent = parent->getParentOp())
126 if (isa<LoopLikeOpInterface>(parent))
127 loops.push_back(parent);
130 SmallVector<Operation *> candidates;
131 for (Operation *candidate : loopsAround(uses.front()))
132 if (llvm::all_of(uses, [&](Operation *use) {
133 return candidate->isProperAncestor(use);
135 candidates.push_back(candidate);
136 if (candidates.empty()) {
137 reason =
"no loop repeats the core's use of them";
142 DenseMap<Value, Attribute> counters;
143 std::function<Attribute(Value)> evaluate = [&](Value value) -> Attribute {
144 Attribute attr = counters.lookup(value);
145 Operation *def = value.getDefiningOp();
146 if (attr || !def || matchPattern(value, m_Constant(&attr)))
148 if (!isPure(def) || def->getNumResults() != 1)
150 SmallVector<Attribute> operands;
151 for (Value operand : def->getOperands())
152 if (!operands.emplace_back(evaluate(operand)))
154 SmallVector<OpFoldResult> folded;
155 if (failed(def->fold(operands, folded)) || folded.size() != 1)
157 if (
auto result = dyn_cast<Attribute>(folded.front()))
159 return evaluate(cast<Value>(folded.front()));
162 std::function<LogicalResult(Block &)> unroll = [&](Block &block) {
163 for (Operation &op : block) {
164 if (std::optional<unsigned> end = endOf(&op)) {
165 if (
auto acquire = dyn_cast<ObjectFifoAcquireOp>(op))
166 period.push_back({*end, acquire.acqNumber(),
true, &op});
169 {*end, cast<ObjectFifoReleaseOp>(op).relNumber(),
false, &op});
170 if (period.size() > maxPeriodEvents) {
171 reason =
"the core's period uses them too many times";
177 uses, [&](Operation *use) {
return op.isProperAncestor(use); }))
179 if (
auto ifOp = dyn_cast<scf::IfOp>(op)) {
181 dyn_cast_or_null<BoolAttr>(evaluate(ifOp.getCondition()))) {
183 taken.getValue() ? ifOp.getThenRegion() : ifOp.getElseRegion())
184 if (failed(unroll(branch)))
189 auto forOp = dyn_cast<scf::ForOp>(op);
190 std::optional<APInt> trips =
191 forOp ? forOp.getStaticTripCount() : std::nullopt;
193 reason =
"the core uses them under control flow that depends on data";
196 if (trips->getSExtValue() >
static_cast<int64_t
>(maxPeriodEvents)) {
197 reason =
"the core's period uses them too many times";
200 std::optional<int64_t> lower = getConstantIntValue(forOp.getLowerBound());
201 std::optional<int64_t> step = getConstantIntValue(forOp.getStep());
202 for (int64_t trip = 0; trip < trips->getSExtValue(); trip++) {
204 counters[forOp.getInductionVar()] = IntegerAttr::get(
205 forOp.getInductionVar().getType(), *lower + trip * *step);
206 if (failed(unroll(*forOp.getBody())))
214 for (Operation *candidate : candidates) {
217 if (llvm::all_of(cast<LoopLikeOpInterface>(candidate).getLoopRegions(),
218 [&](Region *region) {
219 return llvm::all_of(*region, [&](Block &block) {
220 return succeeded(unroll(block));
231 llvm::dbgs() <<
"period of " << names.size() <<
" ends in "
232 << loop->getName() <<
" at " << loop->getLoc() <<
": "
233 << period.size() <<
" events:";
234 for (
const Event &event : period)
235 llvm::dbgs() <<
" " << (
event.acquire ?
"a" :
"r") << event.end <<
"x"
237 llvm::dbgs() <<
"\n";
244 SmallVector<int64_t> held(ends, 0);
245 std::map<SmallVector<int64_t>,
int> seen;
246 for (
int round = 0; round < maxPeriods; round++) {
248 SmallVector<int64_t> start = held;
249 for (
const Event &event : period) {
250 int64_t &h = held[
event.end];
253 order.append(std::max<int64_t>(0, event.count - h), event.end);
254 h = std::max(h, event.count);
256 if (event.count > h) {
257 reason =
"the core releases objects it does not hold";
262 order.append(event.count, event.end);
265 if (!seen.emplace(start, round).second) {
268 reason =
"the core's period moves none of their objects";
273 SmallVector<std::pair<ObjectFifoReleaseOp, unsigned>> releases;
274 for (Operation *op : llvm::concat<Operation *const>(uses, tails))
275 if (
auto release = dyn_cast<ObjectFifoReleaseOp>(op))
276 if (std::optional<unsigned> end = endOf(op))
277 releases.push_back({release, *end});
278 return wholeRunKeepsTurns(core, releases, reason);
281 reason =
"the core never settles into a steady order";
285LogicalResult CoreTurns::wholeRunKeepsTurns(
286 CoreOp core, ArrayRef<std::pair<ObjectFifoReleaseOp, unsigned>> releases,
287 std::string &reason)
const {
289 for (Operation *op = loop; op != core; op = op->getParentOp()) {
290 auto whileOp = dyn_cast<scf::WhileOp>(op);
291 auto forOp = dyn_cast<scf::ForOp>(op);
292 std::optional<APInt> trips =
293 forOp ? forOp.getStaticTripCount() : std::nullopt;
295 (trips && trips->getSExtValue() >= runsForever))
304 SmallVector<int64_t> moved(ends, 0);
305 for (
auto [release, end] : releases) {
306 int64_t runs = release.relNumber();
307 for (Operation *parent = release->getParentOp(); parent != core;
308 parent = parent->getParentOp()) {
309 auto forOp = dyn_cast<scf::ForOp>(parent);
310 std::optional<APInt> trips =
311 forOp ? forOp.getStaticTripCount() : std::nullopt;
312 if (!trips || llvm::MulOverflow(runs, trips->getSExtValue(), runs)) {
313 reason =
"the core's whole run does not move a static count of them";
319 SmallVector<int64_t> counts(ends, 0);
320 for (
unsigned end : order)
322 std::optional<int64_t> whole;
323 for (
size_t i = 0; i < ends; i++) {
324 bool fits = counts[i] ? moved[i] % counts[i] == 0 &&
325 (!whole || *whole == moved[i] / counts[i])
328 reason =
"the core's whole run moves them out of step with the turns";
332 whole = moved[i] / counts[i];
338 assert(depths.size() == ends &&
"one depth per endpoint");
342 SmallVector<int64_t> counts(ends, 0);
343 for (
unsigned end : order)
345 for (
size_t length = 1; length < order.size(); length++) {
346 if (order.size() % length)
348 size_t repeats = order.size() / length;
349 if (llvm::any_of(counts, [&](int64_t c) {
return c % repeats; }))
351 std::set<SmallVector<unsigned>> tried;
357 SmallVector<int64_t> runCounts(ends, 0);
358 for (
size_t i = 0; i < length; i++)
359 runCounts[order[i]]++;
360 for (
size_t start = 0; start + length <= order.size(); start++) {
362 runCounts[order[start - 1]]--;
363 runCounts[order[start + length - 1]]++;
365 bool proportional =
true;
366 for (
size_t i = 0; i < ends; i++)
368 runCounts[i] *
static_cast<int64_t
>(repeats) == counts[i];
371 SmallVector<unsigned> run(order.begin() + start,
372 order.begin() + start + length);
373 if (!tried.insert(run).second || !
keepsUp(run, depths))
383 for (
const Event &event : period)
390 ArrayRef<int64_t> depths)
const {
393 SmallVector<int64_t> forCore(ends, 0), forChannel(ends, 0), held(ends, 0);
394 for (
size_t i = 0; i < ends; i++)
395 (coreDrains ? forChannel : forCore)[i] = depths[i];
396 size_t turn = 0,
event = 0;
397 std::map<SmallVector<int64_t>,
int> seen;
398 for (
int round = 0; round < maxPeriods * 4;) {
400 while (forChannel[order[turn]] > 0) {
401 forChannel[order[turn]]--;
402 forCore[order[turn]]++;
403 turn = (turn + 1) % order.size();
407 const Event &e = period[event];
409 int64_t take = std::max<int64_t>(0, e.count - held[e.end]);
410 if (forCore[e.end] < take)
412 forCore[e.end] -= take;
413 held[e.end] = std::max(held[e.end], e.count);
415 held[e.end] -= e.count;
416 forChannel[e.end] += e.count;
419 if (++event == period.size()) {
422 SmallVector<int64_t> state{
static_cast<int64_t
>(turn)};
423 llvm::append_range(state, forCore);
424 llvm::append_range(state, forChannel);
425 llvm::append_range(state, held);
426 if (!seen.emplace(state, round).second)
mlir::LogicalResult read(llvm::ArrayRef< ObjectFifoCoreEndpointOp > ends, std::string &reason)
Reads the order off the program of the core working through ends, all draining (the channel fills the...
bool keepsUp(llvm::ArrayRef< int64_t > depths)
Whether a channel moving objects in turns() order forever, through pools of depths,...
mlir::Operation * periodAcquire() const
The first acquire the period runs, with its branches folded; null when it runs none.
bool isForeverLoop(mlir::scf::WhileOp whileOp)
Whether whileOp repeats forever: its condition is a constant true, and its condition region does noth...