MLIR-AIE 1.4.5.dev
AIE2PShufflePlanner.cpp
Go to the documentation of this file.
1//===- AIE2PShufflePlanner.cpp - Static shuffles on AIE2P -------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7// Plans static shuffles on 512-bit registers out of vshuffle modes and splats.
8// Two bounded searches run: in rounds, where all registers go through the same
9// mode (transposes, interleaves, reversals), and per result register, a few
10// modes deep (repetitions, broadcasts, gathers). A plan that needs fewer modes
11// can win at a higher shuffle count, since each mode costs a scalar register.
12// A plan is used only if it beats LLVM's instruction count and running it on
13// lane labels reproduces the requested result.
14//===----------------------------------------------------------------------===//
15
16#include "AIE2PShufflePlanner.h"
17
19
20#include "mlir/Dialect/UB/IR/UBOps.h"
21#include "llvm/ADT/STLExtras.h"
22#include "llvm/Support/Debug.h"
23#include "llvm/Support/MathExtras.h"
24
25#include <limits>
26#include <tuple>
27#include <unordered_map>
28#include <unordered_set>
29
30#define DEBUG_TYPE "aie2p-shuffle-planner"
31
32using namespace mlir;
33using namespace xilinx;
34using namespace xilinx::aievec;
35
36namespace {
37
38using Lanes = SmallVector<int32_t, 64>;
39using Node = ShufflePlan::Node;
40
41constexpr unsigned kInf = std::numeric_limits<unsigned>::max() / 4;
42// Depth of the per-register search, and of the round search with all modes
43// and with one pair of modes.
44constexpr unsigned kMaxRegisterDepth = 3;
45constexpr unsigned kMaxRounds = 3;
46constexpr unsigned kMaxRoundsOnePair = 4;
47// Bounds on the work of one search.
48constexpr unsigned kMaxRoundStates = 50000;
49constexpr unsigned kMaxSolveSteps = 200000;
50// Registers in the round search.
51constexpr unsigned kMaxRoundRegisters = 8;
52
53std::string bytesOf(ArrayRef<int32_t> v) {
54 return std::string(reinterpret_cast<const char *>(v.data()),
55 v.size() * sizeof(int32_t));
56}
57
58// A mode seen as a gather of whole lanes of `elemBits` bits: result lane i
59// takes lane src[i] of lhs:rhs. Modes that would split a lane are left out.
60struct LaneMode {
61 ShuffleMode mode;
62 bool twoInput;
63 SmallVector<uint8_t, 64> src;
64};
65
66SmallVector<LaneMode> getLaneModes(unsigned elemBits) {
67 SmallVector<LaneMode> modes;
68 unsigned eb = elemBits / 8, lanes = 512 / elemBits;
69 for (uint32_t m = 0; m <= getMaxEnumValForShuffleMode(); ++m) {
70 auto mode = symbolizeShuffleMode(m);
71 if (!mode)
72 continue;
73 std::array<uint8_t, 64> perm = getShuffleModeBytePermutation(*mode);
74 LaneMode lm{*mode, getShuffleModeShape(*mode).isTwoInput(), {}};
75 bool wholeLanes = true;
76 for (unsigned i = 0; i < lanes && wholeLanes; ++i) {
77 unsigned first = perm[i * eb];
78 wholeLanes = first % eb == 0;
79 for (unsigned b = 1; b < eb && wholeLanes; ++b)
80 wholeLanes = perm[i * eb + b] == first + b;
81 lm.src.push_back(first / eb);
82 }
83 if (!wholeLanes)
84 continue;
85 // A copy of one operand does nothing.
86 bool copiesLhs = true, copiesRhs = true;
87 for (unsigned i = 0; i < lanes; ++i) {
88 copiesLhs &= lm.src[i] == i;
89 copiesRhs &= lm.src[i] == lanes + i;
90 }
91 if (copiesLhs || copiesRhs)
92 continue;
93 modes.push_back(std::move(lm));
94 }
95 // Searches keep the first of equally good choices. Trying narrow modes
96 // first makes related registers tend to use the same few modes.
97 llvm::stable_sort(modes, [](const LaneMode &a, const LaneMode &b) {
98 return getShuffleModeShape(a.mode).elemBits <
100 });
101 return modes;
102}
103
104// The registers both searches work on, interned by contents. A lane holds a
105// label, or -1 when its contents are unknown.
106class RegisterSearch {
107public:
108 // With `only`, the searches use those modes alone.
109 RegisterSearch(const ShuffleGather &gather, ArrayRef<ShuffleMode> only = {},
110 unsigned maxRounds = kMaxRounds)
111 : g(gather), lanes(512 / gather.elemBits),
112 modes(getLaneModes(gather.elemBits)), maxRounds(maxRounds) {
113 if (!only.empty())
114 llvm::erase_if(modes, [&](const LaneMode &lm) {
115 return !llvm::is_contained(only, lm.mode);
116 });
117 for (unsigned k = 0; k < g.sourceLanes.size(); ++k) {
118 if (g.sourceIsSplat[k]) {
119 sources.push_back(splat(g.sourceOffset(k)));
120 continue;
121 }
122 int64_t n = g.sourceLanes[k];
123 for (int64_t c = 0; c * lanes < std::max<int64_t>(n, 1); ++c) {
124 Lanes l(lanes, -1);
125 for (unsigned j = 0; j < lanes && c * lanes + j < n; ++j)
126 l[j] = g.sourceOffset(k) + c * lanes + j;
127 Node node{Node::Source};
128 node.source = k;
129 node.chunk = c;
130 sources.push_back(intern(l, node));
131 }
132 }
133 int64_t m = g.target.size();
134 for (int64_t r = 0; r * lanes < m; ++r) {
135 Lanes t(lanes, -1);
136 for (unsigned j = 0; j < lanes && r * lanes + j < m; ++j)
137 t[j] = g.target[r * lanes + j];
138 targets.push_back(t);
139 }
140 // Modes paired as the lo and hi halves of one result.
141 for (unsigned i = 0; i < modes.size(); ++i) {
142 ShuffleModeShape a = getShuffleModeShape(modes[i].mode);
143 if (!modes[i].twoInput || a.hi)
144 continue;
145 for (unsigned j = 0; j < modes.size(); ++j) {
146 ShuffleModeShape b = getShuffleModeShape(modes[j].mode);
147 if (b.hi && b.elemBits == a.elemBits && b.rows == a.rows &&
148 b.cols == a.cols)
149 loHi.push_back({i, j});
150 }
151 }
152 }
153
154 std::optional<ShufflePlan> searchRounds();
155 std::optional<ShufflePlan> searchPerRegister();
156 // The lo and hi modes that make up the two halves of one result.
157 SmallVector<std::pair<ShuffleMode, ShuffleMode>> modePairs() const {
158 SmallVector<std::pair<ShuffleMode, ShuffleMode>> pairs;
159 for (auto [lo, hi] : loHi)
160 pairs.push_back({modes[lo].mode, modes[hi].mode});
161 return pairs;
162 }
163 // Result registers that no source register already holds; each costs at
164 // least one instruction.
165 unsigned lowerBound() const;
166
167private:
168 struct Register {
169 Lanes lanes;
170 Node node;
171 };
172
173 bool matches(ArrayRef<int32_t> have, ArrayRef<int32_t> want) const {
174 for (unsigned i = 0; i < lanes; ++i)
175 if (want[i] >= 0 && have[i] != want[i])
176 return false;
177 return true;
178 }
179 static bool isAny(ArrayRef<int32_t> spec) {
180 return llvm::all_of(spec, [](int32_t l) { return l < 0; });
181 }
182 // The single label of `spec`, if it has exactly one.
183 static std::optional<int32_t> singleLabel(ArrayRef<int32_t> spec) {
184 std::optional<int32_t> label;
185 for (int32_t l : spec) {
186 if (l < 0)
187 continue;
188 if (label && *label != l)
189 return std::nullopt;
190 label = l;
191 }
192 return label;
193 }
194 bool isSplatSourceLabel(int32_t label) const {
195 for (unsigned k = 0; k < g.sourceLanes.size(); ++k)
196 if (g.sourceIsSplat[k] && g.sourceOffset(k) == label)
197 return true;
198 return false;
199 }
200 bool canSplat(int32_t label) const {
201 return g.canSplat || isSplatSourceLabel(label);
202 }
203
204 unsigned intern(ArrayRef<int32_t> l, const Node &node) {
205 auto [it, inserted] = index.try_emplace(bytesOf(l), registers.size());
206 if (inserted)
207 registers.push_back({Lanes(l.begin(), l.end()), node});
208 return it->second;
209 }
210 unsigned splat(int32_t label) {
211 Node node{Node::Splat};
212 node.label = label;
213 return intern(Lanes(lanes, label), node);
214 }
215 Lanes applyLanes(unsigned modeIdx, ArrayRef<int32_t> lhs,
216 ArrayRef<int32_t> rhs) const {
217 const LaneMode &lm = modes[modeIdx];
218 Lanes out(lanes);
219 for (unsigned i = 0; i < lanes; ++i) {
220 unsigned s = lm.src[i];
221 out[i] = s < lanes ? lhs[s] : rhs[s - lanes];
222 }
223 return out;
224 }
225 unsigned apply(unsigned modeIdx, unsigned lhs, unsigned rhs) {
226 const LaneMode &lm = modes[modeIdx];
227 Lanes out = applyLanes(modeIdx, registers[lhs].lanes, registers[rhs].lanes);
228 Node node{Node::Shuffle};
229 node.mode = lm.mode;
230 node.lhs = lhs;
231 node.rhs = lm.twoInput ? rhs : lhs;
232 return intern(out, node);
233 }
234 // The lanes of lhs and rhs that `spec` needs through mode `modeIdx`, or
235 // false if two result lanes need one source lane to hold different labels.
236 bool invert(unsigned modeIdx, ArrayRef<int32_t> spec, Lanes &lhs,
237 Lanes &rhs) const {
238 const LaneMode &lm = modes[modeIdx];
239 lhs.assign(lanes, -1);
240 rhs.assign(lanes, -1);
241 for (unsigned i = 0; i < lanes; ++i) {
242 if (spec[i] < 0)
243 continue;
244 unsigned s = lm.src[i];
245 int32_t &slot = s < lanes ? lhs[s] : rhs[s - lanes];
246 if (slot >= 0 && slot != spec[i])
247 return false;
248 slot = spec[i];
249 }
250 return true;
251 }
252
253 ShufflePlan extract(ArrayRef<int> results) const;
254
255 // Per-register search. Among choices of equal cost, the one whose new
256 // registers hold more distinct labels wins: such registers are more likely
257 // to serve later result registers too. Then the one that needs fewer modes
258 // the plan does not use yet, as each mode takes a scalar register.
259 struct Choice {
260 unsigned cost = kInf;
261 unsigned info = 0;
262 SmallVector<ShuffleMode, 4> newModes;
263 enum { None, Existing, Splat, Mode } kind = None;
264 unsigned reg = 0;
265 int32_t label = -1;
266 unsigned modeIdx = 0;
267 // The depth the operands were searched with.
268 unsigned sub = 0;
269 // Merged: lhs and rhs are one register meeting both specs.
270 bool merged = false;
271 Lanes lhs, rhs;
272 // The lanes the choice produces.
273 Lanes result;
274 bool isBetterThan(const Choice &other) const {
275 return std::make_tuple(cost, -static_cast<int>(info), newModes.size()) <
276 std::make_tuple(other.cost, -static_cast<int>(other.info),
277 other.newModes.size());
278 }
279 };
280 const Choice &solve(ArrayRef<int32_t> spec, unsigned depth);
281 unsigned build(ArrayRef<int32_t> spec, unsigned depth);
282
283 const ShuffleGather &g;
284 unsigned lanes;
285 SmallVector<LaneMode> modes;
286 unsigned maxRounds;
287 SmallVector<std::pair<unsigned, unsigned>> loHi;
288 std::vector<Register> registers;
289 std::unordered_map<std::string, unsigned> index;
290 SmallVector<unsigned> sources;
291 SmallVector<Lanes, 4> targets;
292 // Registers the per-register search has built so far, and their modes.
293 SmallVector<unsigned> built;
294 SmallVector<ShuffleMode> usedModes;
295 std::unordered_map<std::string, Choice> memo;
296 unsigned steps = 0;
297};
298
299unsigned RegisterSearch::lowerBound() const {
300 unsigned n = 0;
301 for (const Lanes &t : targets)
302 if (!isAny(t) && llvm::none_of(sources, [&](unsigned s) {
303 return registers[s].node.kind == Node::Source &&
304 matches(registers[s].lanes, t);
305 }))
306 ++n;
307 return n;
308}
309
310// Collects the registers the results depend on, operands first, and counts
311// the instructions among them.
312ShufflePlan RegisterSearch::extract(ArrayRef<int> results) const {
313 ShufflePlan plan;
314 DenseMap<unsigned, int> newIndex;
315 std::function<int(unsigned)> visit = [&](unsigned r) -> int {
316 if (auto it = newIndex.find(r); it != newIndex.end())
317 return it->second;
318 Node node = registers[r].node;
319 if (node.kind == Node::Shuffle) {
320 node.lhs = visit(node.lhs);
321 node.rhs = visit(node.rhs);
322 }
323 if (node.kind != Node::Source)
324 ++plan.cost;
325 plan.nodes.push_back(node);
326 return newIndex[r] = plan.nodes.size() - 1;
327 };
328 for (int r : results)
329 plan.results.push_back(r < 0 ? -1 : visit(r));
330 return plan;
331}
332
333std::optional<ShufflePlan> RegisterSearch::searchRounds() {
334 unsigned n = llvm::PowerOf2Ceil(std::max(sources.size(), targets.size()));
335 if (n > kMaxRoundRegisters)
336 return std::nullopt;
337 unsigned bits = llvm::Log2_32(n);
338
339 // A round, as a function from the old registers to the new ones.
340 using State = SmallVector<unsigned, 8>;
341 SmallVector<std::function<State(const State &)>> rounds;
342 for (unsigned mi = 0; mi < modes.size(); ++mi) {
343 if (modes[mi].twoInput)
344 continue;
345 rounds.push_back([this, mi](const State &s) {
346 State t;
347 for (unsigned r : s)
348 t.push_back(apply(mi, r, r));
349 return t;
350 });
351 }
352 for (auto [lo, hi] : loHi) {
353 // Each register with itself, all lo or all hi.
354 for (unsigned mi : {lo, hi})
355 rounds.push_back([this, mi](const State &s) {
356 State t;
357 for (unsigned r : s)
358 t.push_back(apply(mi, r, r));
359 return t;
360 });
361 for (unsigned b = 0; b < bits; ++b) {
362 unsigned bit = 1u << b;
363 // Registers i and i | bit as lhs:rhs; lo to i, hi to i | bit.
364 rounds.push_back([this, lo, hi, bit](const State &s) {
365 State t(s.size());
366 for (unsigned i = 0; i < s.size(); ++i)
367 if (!(i & bit)) {
368 t[i] = apply(lo, s[i], s[i | bit]);
369 t[i | bit] = apply(hi, s[i], s[i | bit]);
370 }
371 return t;
372 });
373 // Each register with itself; lo where the bit is clear, else hi.
374 rounds.push_back([this, lo, hi, bit](const State &s) {
375 State t;
376 for (unsigned i = 0; i < s.size(); ++i)
377 t.push_back(apply(i & bit ? hi : lo, s[i], s[i]));
378 return t;
379 });
380 }
381 }
382
383 // The register of the state that holds each result, if all are there.
384 auto resultsOf = [&](const State &s) -> std::optional<SmallVector<int>> {
385 SmallVector<int> results;
386 for (const Lanes &t : targets) {
387 if (isAny(t)) {
388 results.push_back(-1);
389 continue;
390 }
391 auto it = llvm::find_if(
392 s, [&](unsigned r) { return matches(registers[r].lanes, t); });
393 if (it == s.end())
394 return std::nullopt;
395 results.push_back(*it);
396 }
397 return results;
398 };
399
400 State start;
401 for (unsigned i = 0; i < n; ++i)
402 start.push_back(sources[i % sources.size()]);
403 std::optional<ShufflePlan> best;
404 // Fewer distinct modes break ties: each takes a scalar register.
405 auto numModes = [](const ShufflePlan &p) {
406 SmallVector<ShuffleMode> used;
407 for (const Node &node : p.nodes)
408 if (node.kind == Node::Shuffle && !llvm::is_contained(used, node.mode))
409 used.push_back(node.mode);
410 return used.size();
411 };
412 auto consider = [&](const State &s) {
413 if (auto results = resultsOf(s)) {
414 ShufflePlan plan = extract(*results);
415 if (!best || plan.cost < best->cost ||
416 (plan.cost == best->cost && numModes(plan) < numModes(*best)))
417 best = std::move(plan);
418 }
419 };
420 consider(start);
421 if (best)
422 return best;
423
424 std::unordered_set<std::string> seen{
425 bytesOf(SmallVector<int32_t>(start.begin(), start.end()))};
426 std::vector<State> frontier{start};
427 unsigned states = 0;
428 for (unsigned depth = 1; depth <= maxRounds && !best; ++depth) {
429 std::vector<State> next;
430 for (const State &s : frontier) {
431 for (auto &round : rounds) {
432 if (++states > kMaxRoundStates)
433 return best;
434 State t = round(s);
435 if (!seen.insert(bytesOf(SmallVector<int32_t>(t.begin(), t.end())))
436 .second)
437 continue;
438 consider(t);
439 if (depth < maxRounds)
440 next.push_back(std::move(t));
441 }
442 }
443 frontier = std::move(next);
444 }
445 return best;
446}
447
448static unsigned countLabels(ArrayRef<int32_t> lanes) {
449 SmallVector<int32_t, 64> labels;
450 for (int32_t l : lanes)
451 if (l >= 0 && !llvm::is_contained(labels, l))
452 labels.push_back(l);
453 return labels.size();
454}
455
456// The cheapest way to build a register meeting `spec` from what is built
457// already, splats and up to `depth` modes in a chain.
458const RegisterSearch::Choice &RegisterSearch::solve(ArrayRef<int32_t> spec,
459 unsigned depth) {
460 std::string key = bytesOf(spec);
461 key.push_back(static_cast<char>(depth));
462 if (auto it = memo.find(key); it != memo.end())
463 return it->second;
464
465 Choice best;
466 for (unsigned r : built)
467 if (matches(registers[r].lanes, spec)) {
468 best.cost = 0;
469 best.kind = Choice::Existing;
470 best.reg = r;
471 best.result = registers[r].lanes;
472 break;
473 }
474 if (best.cost > 0)
475 if (auto label = singleLabel(spec); label && canSplat(*label)) {
476 best.cost = 1;
477 best.info = 1;
478 best.kind = Choice::Splat;
479 best.label = *label;
480 best.result.assign(lanes, *label);
481 }
482 if (depth > 0 && best.cost > 0) {
483 Lanes lhs, rhs;
484 for (unsigned mi = 0; mi < modes.size(); ++mi) {
485 if (++steps > kMaxSolveSteps)
486 break;
487 if (!invert(mi, spec, lhs, rhs))
488 continue;
489 // An operand costs at least one instruction per mode in its chain, so
490 // only chains shorter than the best cost can match it.
491 unsigned sub = std::min(depth - 1, best.cost - 1);
492 // Copies, since solve may grow the memo and move what it returned.
493 auto consider = [&](const Choice &l, const Choice *r) {
494 if (l.cost >= kInf || (r && r->cost >= kInf))
495 return;
496 Choice c;
497 c.cost = 1 + l.cost + (r ? r->cost : 0);
498 c.kind = Choice::Mode;
499 c.modeIdx = mi;
500 c.sub = sub;
501 c.merged = !r;
502 c.lhs = lhs;
503 c.rhs = rhs;
504 c.result = applyLanes(mi, l.result, r ? r->result : l.result);
505 c.info = l.info + (r ? r->info : 0) + countLabels(c.result);
506 c.newModes = l.newModes;
507 if (r)
508 for (ShuffleMode m : r->newModes)
509 if (!llvm::is_contained(c.newModes, m))
510 c.newModes.push_back(m);
511 if (!llvm::is_contained(usedModes, modes[mi].mode) &&
512 !llvm::is_contained(c.newModes, modes[mi].mode))
513 c.newModes.push_back(modes[mi].mode);
514 if (c.isBetterThan(best))
515 best = std::move(c);
516 };
517 bool lhsAny = isAny(lhs), rhsAny = isAny(rhs);
518 if (!modes[mi].twoInput || rhsAny) {
519 Choice l = solve(lhs, sub);
520 rhs = lhs;
521 consider(l, nullptr);
522 continue;
523 }
524 if (lhsAny) {
525 Choice r = solve(rhs, sub);
526 lhs = rhs;
527 consider(r, nullptr);
528 continue;
529 }
530 // One register for both operands, when their lanes agree.
531 Lanes both = lhs;
532 bool agree = true;
533 for (unsigned i = 0; i < lanes && agree; ++i) {
534 if (rhs[i] < 0)
535 continue;
536 agree = both[i] < 0 || both[i] == rhs[i];
537 both[i] = rhs[i];
538 }
539 if (agree) {
540 Lanes l = lhs, r = rhs;
541 lhs = rhs = both;
542 Choice m = solve(both, sub);
543 consider(m, nullptr);
544 lhs = l;
545 rhs = r;
546 }
547 Choice l = solve(lhs, sub);
548 if (1 + l.cost <= best.cost) {
549 Choice r = solve(rhs, sub);
550 consider(l, &r);
551 }
552 }
553 }
554 return memo[key] = std::move(best);
555}
556
557// Builds the register `solve` chose for `spec` and adds it to `built`.
558unsigned RegisterSearch::build(ArrayRef<int32_t> spec, unsigned depth) {
559 std::string key = bytesOf(spec);
560 key.push_back(static_cast<char>(depth));
561 Choice choice = memo.at(key);
562 unsigned r;
563 switch (choice.kind) {
564 case Choice::Existing:
565 return choice.reg;
566 case Choice::Splat:
567 r = splat(choice.label);
568 break;
569 case Choice::Mode: {
570 unsigned lhs = build(choice.lhs, choice.sub);
571 unsigned rhs = choice.merged ? lhs : build(choice.rhs, choice.sub);
572 r = apply(choice.modeIdx, lhs, rhs);
573 if (!llvm::is_contained(usedModes, modes[choice.modeIdx].mode))
574 usedModes.push_back(modes[choice.modeIdx].mode);
575 break;
576 }
577 case Choice::None:
578 llvm_unreachable("building a register without a plan");
579 }
580 if (!llvm::is_contained(built, r))
581 built.push_back(r);
582 return r;
583}
584
585std::optional<ShufflePlan> RegisterSearch::searchPerRegister() {
586 for (unsigned s : sources)
587 if (registers[s].node.kind == Node::Source)
588 built.push_back(s);
589 SmallVector<int> results;
590 for (const Lanes &t : targets) {
591 if (isAny(t)) {
592 results.push_back(-1);
593 continue;
594 }
595 // What is cheapest depends on what is built, so start afresh. Nothing
596 // beats a single instruction; otherwise a deeper search may find a plan
597 // as cheap whose steps serve later registers too.
598 memo.clear();
599 steps = 0;
600 unsigned depth = 0;
601 while (solve(t, depth).cost > 1 && depth < kMaxRegisterDepth)
602 ++depth;
603 if (solve(t, depth).cost >= kInf)
604 return std::nullopt;
605 results.push_back(build(t, depth));
606 }
607 return extract(results);
608}
609
610} // namespace
611
612int32_t ShuffleGather::sourceOffset(unsigned k) const {
613 int32_t offset = 0;
614 for (unsigned i = 0; i < k; ++i)
615 offset += sourceIsSplat[i] ? 1 : sourceLanes[i];
616 return offset;
617}
618
619std::string ShuffleGather::key() const {
620 SmallVector<int32_t> v{static_cast<int32_t>(elemBits), canSplat,
621 static_cast<int32_t>(sourceLanes.size())};
622 for (unsigned k = 0; k < sourceLanes.size(); ++k) {
623 v.push_back(sourceLanes[k]);
624 v.push_back(sourceIsSplat[k]);
625 }
626 v.append(target.begin(), target.end());
627 return bytesOf(v);
628}
629
631 unsigned w = g.elemBits, lanes = 512 / w;
632 int64_t m = g.target.size();
633 int64_t half = std::min<int64_t>(256 / w, m);
634 // The source and lane behind a label, for sources that are not splats.
635 auto laneOf =
636 [&](int32_t label) -> std::optional<std::pair<unsigned, int64_t>> {
637 for (unsigned k = 0; k < g.sourceLanes.size(); ++k) {
638 int64_t lane = label - g.sourceOffset(k);
639 if (!g.sourceIsSplat[k] && lane >= 0 && lane < g.sourceLanes[k])
640 return std::make_pair(k, lane);
641 }
642 return std::nullopt;
643 };
644 // Whether lanes [begin, end) are poison or consecutive lanes of one source
645 // starting at a multiple of `align`. `start` receives the first source
646 // lane, if any lane is not poison.
647 auto isRun = [&](int64_t begin, int64_t end, int64_t align,
648 std::optional<int64_t> *start = nullptr) {
649 std::optional<std::pair<unsigned, int64_t>> origin;
650 for (int64_t i = begin; i < end; ++i) {
651 if (g.target[i] < 0)
652 continue;
653 auto at = laneOf(g.target[i]);
654 if (!at)
655 return false;
656 int64_t first = at->second - (i - begin);
657 if (first < 0 || first % align != 0 ||
658 (origin && *origin != std::make_pair(at->first, first)))
659 return false;
660 origin = {at->first, first};
661 }
662 if (start && origin)
663 *start = origin->second;
664 return true;
665 };
666
667 unsigned cost = 0;
668 for (int64_t begin = 0; begin < m; begin += lanes) {
669 int64_t end = std::min<int64_t>(begin + lanes, m);
670 // A register equal to an earlier one is that register again.
671 bool repeated = false;
672 for (int64_t prev = 0; prev < begin && !repeated; prev += lanes)
673 repeated = std::equal(g.target.begin() + begin, g.target.begin() + end,
674 g.target.begin() + prev);
675 if (repeated)
676 continue;
677 SmallVector<int32_t> labels;
678 unsigned used = 0;
679 for (int64_t i = begin; i < end; ++i)
680 if (g.target[i] >= 0) {
681 ++used;
682 if (!llvm::is_contained(labels, g.target[i]))
683 labels.push_back(g.target[i]);
684 }
685 if (labels.empty())
686 continue;
687 // A splat of one lane.
688 if (labels.size() == 1) {
689 cost += 1;
690 continue;
691 }
692 // Aligned 256-bit halves of the sources are subregisters. One that lands
693 // in the other half of a 512-bit result takes a move.
694 bool moves = true;
695 unsigned crossings = 0;
696 for (int64_t c = begin; c < end && moves; c += half) {
697 std::optional<int64_t> start;
698 moves = isRun(c, std::min(c + half, end), half, &start);
699 if (moves && start && m >= lanes &&
700 (c - begin) / half % 2 != *start / half % 2)
701 ++crossings;
702 }
703 if (moves) {
704 cost += crossings;
705 continue;
706 }
707 // Consecutive lanes of one source: a shift.
708 if (isRun(begin, end, 1)) {
709 cost += 1;
710 continue;
711 }
712 // Each lane from its own position in one of the sources: a select, or a
713 // few lanes inserted into one source.
714 unsigned misplaced = 0;
715 SmallVector<unsigned> from;
716 for (int64_t i = begin; i < end; ++i) {
717 if (g.target[i] < 0)
718 continue;
719 auto at = laneOf(g.target[i]);
720 if (!at || at->second != i) {
721 ++misplaced;
722 continue;
723 }
724 if (!llvm::is_contained(from, at->first))
725 from.push_back(at->first);
726 }
727 if (misplaced == 0 && from.size() <= 2) {
728 cost += 1;
729 continue;
730 }
731 // Otherwise each lane is extracted once and pushed in.
732 cost += std::min<unsigned>(used + labels.size(),
733 from.size() <= 1 ? 2 * misplaced : ~0u);
734 }
735 return cost;
736}
737
739 const ShufflePlan &plan) {
740 unsigned eb = g.elemBits / 8, lanes = 512 / g.elemBits;
741 // Byte b of lane label l is l * eb + b; unknown bytes are -1.
742 SmallVector<std::array<int32_t, 64>> regs;
743 for (const Node &node : plan.nodes) {
744 std::array<int32_t, 64> bytes;
745 bytes.fill(-1);
746 switch (node.kind) {
747 case Node::Source: {
748 if (node.source >= g.sourceLanes.size() || g.sourceIsSplat[node.source])
749 return false;
750 for (unsigned j = 0; j < lanes; ++j) {
751 int64_t lane = node.chunk * lanes + j;
752 if (lane < g.sourceLanes[node.source])
753 for (unsigned b = 0; b < eb; ++b)
754 bytes[j * eb + b] = (g.sourceOffset(node.source) + lane) * eb + b;
755 }
756 break;
757 }
758 case Node::Splat: {
759 bool fromSplat = false;
760 for (unsigned k = 0; k < g.sourceLanes.size(); ++k)
761 fromSplat |= g.sourceIsSplat[k] && g.sourceOffset(k) == node.label;
762 if (node.label < 0 || node.label >= g.numLabels() ||
763 (!fromSplat && !g.canSplat))
764 return false;
765 for (unsigned i = 0; i < 64; ++i)
766 bytes[i] = node.label * eb + i % eb;
767 break;
768 }
769 case Node::Shuffle: {
770 if (node.lhs < 0 || node.rhs < 0 ||
771 static_cast<size_t>(std::max(node.lhs, node.rhs)) >= regs.size())
772 return false;
773 std::array<uint8_t, 64> perm = getShuffleModeBytePermutation(node.mode);
774 for (unsigned i = 0; i < 64; ++i)
775 bytes[i] = perm[i] < 64 ? regs[node.lhs][perm[i]]
776 : regs[node.rhs][perm[i] - 64];
777 break;
778 }
779 }
780 regs.push_back(bytes);
781 }
782 int64_t m = g.target.size();
783 if (static_cast<int64_t>(plan.results.size()) * lanes < m)
784 return false;
785 for (int64_t i = 0; i < m; ++i) {
786 if (g.target[i] < 0)
787 continue;
788 int r = plan.results[i / lanes];
789 if (r < 0 || static_cast<size_t>(r) >= regs.size())
790 return false;
791 for (unsigned b = 0; b < eb; ++b)
792 if (regs[r][(i % lanes) * eb + b] !=
793 g.target[i] * int32_t(eb) + int32_t(b))
794 return false;
795 }
796 return true;
797}
798
799SmallVector<ShuffleMode> ShufflePlan::modes() const {
800 SmallVector<ShuffleMode> used;
801 for (const Node &node : nodes)
802 if (node.kind == Node::Shuffle && !llvm::is_contained(used, node.mode))
803 used.push_back(node.mode);
804 return used;
805}
806
807const SmallVector<ShufflePlan, 2> &
809 auto [it, inserted] = cache.try_emplace(g.key());
810 if (!inserted)
811 return it->second;
812 SmallVector<ShufflePlan, 2> found;
813 // The search keeps its own account of lane contents; a plan is kept only if
814 // running it through the mode table agrees.
815 auto keep = [&](std::optional<ShufflePlan> p, StringRef what) {
816 LLVM_DEBUG(if (p) {
817 llvm::dbgs() << what << ": " << p->cost << " instructions, modes";
818 for (ShuffleMode m : p->modes())
819 llvm::dbgs() << " " << stringifyShuffleMode(m);
820 llvm::dbgs() << "\n";
821 });
822 if (p && simulateShufflePlan(g, *p))
823 found.push_back(std::move(*p));
824 };
825 RegisterSearch search(g);
826 keep(search.searchPerRegister(), "per-register search");
827 if (found.empty() || found.front().cost > search.lowerBound())
828 keep(RegisterSearch(g).searchRounds(), "round search");
829 // Plans that need fewer modes, which another plan may already have put in
830 // scalar registers.
831 for (auto [lo, hi] : search.modePairs()) {
832 SmallVector<ShuffleMode> pair{lo, hi};
833 keep(RegisterSearch(g, pair, kMaxRoundsOnePair).searchRounds(),
834 "round search with one pair");
835 keep(RegisterSearch(g, pair).searchPerRegister(),
836 "per-register search with one pair");
837 }
838 // Keep the plans no other plan beats in both cost and modes.
839 SmallVector<unsigned> kept;
840 for (unsigned i = 0; i < found.size(); ++i) {
841 SmallVector<ShuffleMode> mi = found[i].modes();
842 bool dominated = false;
843 for (unsigned j = 0; j < found.size() && !dominated; ++j) {
844 if (i == j)
845 continue;
846 SmallVector<ShuffleMode> mj = found[j].modes();
847 bool fewerModes = llvm::all_of(
848 mj, [&](ShuffleMode m) { return llvm::is_contained(mi, m); });
849 bool sameModes = fewerModes && mj.size() == mi.size();
850 bool sameCost = found[j].cost == found[i].cost;
851 dominated = found[j].cost <= found[i].cost && fewerModes &&
852 (!sameModes || !sameCost || j < i);
853 }
854 if (!dominated)
855 kept.push_back(i);
856 }
857 SmallVector<ShufflePlan, 2> &plans = it->second;
858 for (unsigned i : kept)
859 plans.push_back(std::move(found[i]));
860 llvm::stable_sort(plans, [](const ShufflePlan &a, const ShufflePlan &b) {
861 return a.cost < b.cost;
862 });
863 return plans;
864}
865
866//===----------------------------------------------------------------------===//
867// From and to vector.shuffle
868//===----------------------------------------------------------------------===//
869
870namespace {
871
872// Builds the gather of a request. With `fold`, a vector.shuffle operand used
873// only by the request's user is looked through, so the two become one gather.
874// `leaves` receives the value behind each source.
875std::optional<ShuffleGather> gatherOf(const ShuffleRequest &req, bool fold,
876 SmallVector<Value> &leaves) {
877 VectorType resTy = req.resultType;
878 Type elemTy = resTy.getElementType();
879 if (resTy.getRank() != 1 || !elemTy.isIntOrFloat())
880 return std::nullopt;
881 unsigned w = elemTy.getIntOrFloatBitWidth();
882 if (w != 8 && w != 16 && w != 32 && w != 64)
883 return std::nullopt;
884 int64_t m = resTy.getNumElements();
885 // emitPlan concatenates the result registers in pairs.
886 if (m * w > 512 && (m * w % 512 != 0 || !llvm::isPowerOf2_64(m * w / 512)))
887 return std::nullopt;
888
890 g.elemBits = w;
891 g.canSplat = elemTy.isBF16() || elemTy.isF32() ||
892 elemTy.isSignlessInteger(8) || elemTy.isSignlessInteger(16) ||
893 elemTy.isSignlessInteger(32);
894 leaves.clear();
895
896 // Source and lane behind each lane of `v`, or -1 for a poison lane.
897 std::function<std::optional<SmallVector<std::pair<int, int64_t>>>(
898 Value, Operation *, unsigned)>
899 lanesOf = [&](Value v, Operation *user, unsigned depth)
900 -> std::optional<SmallVector<std::pair<int, int64_t>>> {
901 auto vTy = cast<VectorType>(v.getType());
902 if (vTy.getRank() != 1)
903 return std::nullopt;
904 int64_t n = vTy.getNumElements();
905 SmallVector<std::pair<int, int64_t>> lanes;
906 if (v.getDefiningOp<ub::PoisonOp>()) {
907 lanes.assign(n, {-1, 0});
908 return lanes;
909 }
910 auto producer = v.getDefiningOp<vector::ShuffleOp>();
911 if (fold && depth > 0 && producer &&
912 llvm::all_of(producer->getUsers(),
913 [&](Operation *u) { return u == user; })) {
914 auto a = lanesOf(producer.getV1(), producer, depth - 1);
915 auto b = lanesOf(producer.getV2(), producer, depth - 1);
916 if (!a || !b)
917 return std::nullopt;
918 int64_t n1 = a->size();
919 for (int64_t idx : producer.getMask())
920 lanes.push_back(idx < 0 ? std::make_pair(-1, int64_t(0))
921 : idx < n1 ? (*a)[idx]
922 : (*b)[idx - n1]);
923 return lanes;
924 }
925 if (n * w > 512 && n * w % 512 != 0)
926 return std::nullopt;
927 int k = llvm::find(leaves, v) - leaves.begin();
928 if (k == static_cast<int>(leaves.size())) {
929 leaves.push_back(v);
930 auto bcast = v.getDefiningOp<vector::BroadcastOp>();
931 g.sourceIsSplat.push_back(g.canSplat && bcast &&
932 !isa<VectorType>(bcast.getSourceType()));
933 g.sourceLanes.push_back(n);
934 }
935 for (int64_t i = 0; i < n; ++i)
936 lanes.push_back({k, i});
937 return lanes;
938 };
939
940 auto a = lanesOf(req.v1, req.user, 3);
941 auto b = lanesOf(req.v2, req.user, 3);
942 if (!a || !b)
943 return std::nullopt;
944 int64_t n1 = a->size();
945 for (int64_t idx : req.mask) {
946 std::pair<int, int64_t> src = idx < 0
947 ? std::make_pair(-1, int64_t(0))
948 : (idx < n1 ? (*a)[idx] : (*b)[idx - n1]);
949 if (src.first < 0) {
950 g.target.push_back(-1);
951 continue;
952 }
953 g.target.push_back(g.sourceOffset(src.first) +
954 (g.sourceIsSplat[src.first] ? 0 : src.second));
955 }
956 return g;
957}
958
959bool isShuffleElemType(Type t, unsigned bits) {
960 if (t.getIntOrFloatBitWidth() != bits)
961 return false;
962 return t.isSignlessInteger() || t.isBF16() || t.isF32();
963}
964
965Value emitPlan(ConversionPatternRewriter &rewriter, Location loc,
966 const ShuffleGather &g, const ShufflePlan &plan,
967 ArrayRef<Value> leaves, VectorType resTy) {
968 Type elemTy = resTy.getElementType();
969 unsigned w = g.elemBits;
970 int64_t lanes = 512 / w;
971 auto regTy = VectorType::get({lanes}, elemTy);
972 SmallVector<Value> vals;
973 for (const Node &node : plan.nodes) {
974 Value v;
975 switch (node.kind) {
976 case Node::Source: {
977 Value leaf = rewriter.getRemappedValue(leaves[node.source]);
978 int64_t n = g.sourceLanes[node.source];
979 if (n == lanes) {
980 v = leaf;
981 break;
982 }
983 // The 512-bit part, or the whole source padded with poison lanes.
984 SmallVector<int64_t> mask;
985 for (int64_t j = 0; j < lanes; ++j) {
986 int64_t lane = node.chunk * lanes + j;
987 mask.push_back(lane < n ? lane : vector::ShuffleOp::kPoisonIndex);
988 }
989 v = vector::ShuffleOp::create(rewriter, loc, leaf, leaf, mask);
990 break;
991 }
992 case Node::Splat: {
993 Value scalar;
994 for (unsigned k = 0; k < g.sourceLanes.size() && !scalar; ++k) {
995 int32_t lane = node.label - g.sourceOffset(k);
996 if (lane < 0 || lane >= (g.sourceIsSplat[k] ? 1 : g.sourceLanes[k]))
997 continue;
998 if (g.sourceIsSplat[k]) {
999 scalar = rewriter.getRemappedValue(
1000 leaves[k].getDefiningOp<vector::BroadcastOp>().getSource());
1001 } else {
1002 scalar = vector::ExtractOp::create(
1003 rewriter, loc, rewriter.getRemappedValue(leaves[k]), lane);
1004 }
1005 }
1006 v = aievec::BroadcastScalarOp::create(rewriter, loc, regTy, scalar);
1007 break;
1008 }
1009 case Node::Shuffle: {
1010 unsigned mw = getShuffleModeShape(node.mode).elemBits;
1011 Type opElemTy = isShuffleElemType(elemTy, mw)
1012 ? elemTy
1013 : Type(rewriter.getIntegerType(mw));
1014 auto opTy = VectorType::get({512 / mw}, opElemTy);
1015 auto cast = [&](Value x) -> Value {
1016 if (x.getType() == opTy)
1017 return x;
1018 return vector::BitCastOp::create(rewriter, loc, opTy, x);
1019 };
1020 Value lhs = cast(vals[node.lhs]);
1021 Value rhs = getShuffleModeShape(node.mode).isTwoInput()
1022 ? cast(vals[node.rhs])
1023 : Value();
1024 v = aievec::ShuffleOp::create(rewriter, loc, opTy, lhs, rhs, node.mode);
1025 if (opTy != regTy)
1026 v = vector::BitCastOp::create(rewriter, loc, regTy, v);
1027 break;
1028 }
1029 }
1030 vals.push_back(v);
1031 }
1032
1033 SmallVector<Value> parts;
1034 for (int r : plan.results)
1035 parts.push_back(r >= 0 ? vals[r]
1036 : ub::PoisonOp::create(rewriter, loc, regTy));
1037 int64_t m = resTy.getNumElements();
1038 if (m < lanes) {
1039 auto mask = llvm::to_vector(llvm::seq<int64_t>(0, m));
1040 return vector::ShuffleOp::create(rewriter, loc, parts[0], parts[0], mask);
1041 }
1042 while (parts.size() > 1) {
1043 SmallVector<Value> next;
1044 for (size_t i = 0; i < parts.size(); i += 2) {
1045 int64_t len = cast<VectorType>(parts[i].getType()).getNumElements();
1046 auto mask = llvm::to_vector(llvm::seq<int64_t>(0, 2 * len));
1047 next.push_back(vector::ShuffleOp::create(rewriter, loc, parts[i],
1048 parts[i + 1], mask));
1049 }
1050 parts = std::move(next);
1051 }
1052 return parts[0];
1053}
1054
1055ShuffleRequest requestOf(vector::ShuffleOp op) {
1056 return {op.getV1(), op.getV2(), llvm::to_vector(op.getMask()),
1057 op.getResultVectorType(), op};
1058}
1059
1060// vector.interleave as a shuffle of its two operands.
1061std::optional<ShuffleRequest> requestOf(vector::InterleaveOp op) {
1062 VectorType srcTy = op.getSourceVectorType();
1063 if (srcTy.getRank() != 1 || srcTy.isScalable())
1064 return std::nullopt;
1065 int64_t n = srcTy.getNumElements();
1066 SmallVector<int64_t> mask;
1067 for (int64_t i = 0; i < 2 * n; ++i)
1068 mask.push_back(i / 2 + (i % 2) * n);
1069 return ShuffleRequest{op.getLhs(), op.getRhs(), mask,
1070 op.getResultVectorType(), op};
1071}
1072
1073// One result of vector.deinterleave as a shuffle of its source.
1074std::optional<ShuffleRequest> requestOf(vector::DeinterleaveOp op,
1075 unsigned result) {
1076 VectorType srcTy = op.getSourceVectorType();
1077 if (srcTy.getRank() != 1 || srcTy.isScalable())
1078 return std::nullopt;
1079 SmallVector<int64_t> mask;
1080 for (int64_t i = result; i < srcTy.getNumElements(); i += 2)
1081 mask.push_back(i);
1082 return ShuffleRequest{op.getSource(), op.getSource(), mask,
1083 op.getResultVectorType(), op};
1084}
1085
1086Value lower(ConversionPatternRewriter &rewriter, AIE2PShufflePlanner &planner,
1087 const ShuffleRequest &req, const ShufflePlan &plan,
1088 const ShuffleGather &g, ArrayRef<Value> leaves) {
1089 Value v =
1090 emitPlan(rewriter, req.user->getLoc(), g, plan, leaves, req.resultType);
1091 LLVM_DEBUG({
1092 llvm::dbgs() << "lowered with " << plan.cost << " instructions, modes";
1093 for (ShuffleMode m : plan.modes())
1094 llvm::dbgs() << " " << stringifyShuffleMode(m);
1095 llvm::dbgs() << "\n";
1096 });
1097 planner.noteUsed(plan);
1098 return v;
1099}
1100
1101// A static vector.shuffle as the plan the planner found for it.
1102struct LowerStaticShuffleAIE2pPattern : OpConversionPattern<vector::ShuffleOp> {
1103 LowerStaticShuffleAIE2pPattern(MLIRContext *ctx,
1104 std::shared_ptr<AIE2PShufflePlanner> planner)
1105 : OpConversionPattern(ctx, /*benefit=*/3), planner(std::move(planner)) {}
1106
1107 LogicalResult
1108 matchAndRewrite(vector::ShuffleOp op, OpAdaptor adaptor,
1109 ConversionPatternRewriter &rewriter) const override {
1110 ShuffleRequest req = requestOf(op);
1111 ShuffleGather g;
1112 SmallVector<Value> leaves;
1113 std::optional<ShufflePlan> plan = planner->planFor(req, &g, &leaves);
1114 if (!plan)
1115 return rewriter.notifyMatchFailure(op, "no plan beats LLVM's lowering");
1116 rewriter.replaceOp(op, lower(rewriter, *planner, req, *plan, g, leaves));
1117 return success();
1118 }
1119
1120 std::shared_ptr<AIE2PShufflePlanner> planner;
1121};
1122
1123struct LowerInterleaveAIE2pPattern : OpConversionPattern<vector::InterleaveOp> {
1124 LowerInterleaveAIE2pPattern(MLIRContext *ctx,
1125 std::shared_ptr<AIE2PShufflePlanner> planner)
1126 : OpConversionPattern(ctx), planner(std::move(planner)) {}
1127
1128 LogicalResult
1129 matchAndRewrite(vector::InterleaveOp op, OpAdaptor adaptor,
1130 ConversionPatternRewriter &rewriter) const override {
1131 std::optional<ShuffleRequest> req = requestOf(op);
1132 ShuffleGather g;
1133 SmallVector<Value> leaves;
1134 std::optional<ShufflePlan> plan;
1135 if (req)
1136 plan = planner->planFor(*req, &g, &leaves);
1137 if (!plan)
1138 return rewriter.notifyMatchFailure(op, "no plan beats LLVM's lowering");
1139 rewriter.replaceOp(op, lower(rewriter, *planner, *req, *plan, g, leaves));
1140 return success();
1141 }
1142
1143 std::shared_ptr<AIE2PShufflePlanner> planner;
1144};
1145
1146// Each result of vector.deinterleave the planner has a plan for is lowered
1147// with it; the other becomes a vector.shuffle.
1148struct LowerDeinterleaveAIE2pPattern
1149 : OpConversionPattern<vector::DeinterleaveOp> {
1150 LowerDeinterleaveAIE2pPattern(MLIRContext *ctx,
1151 std::shared_ptr<AIE2PShufflePlanner> planner)
1152 : OpConversionPattern(ctx), planner(std::move(planner)) {}
1153
1154 LogicalResult
1155 matchAndRewrite(vector::DeinterleaveOp op, OpAdaptor adaptor,
1156 ConversionPatternRewriter &rewriter) const override {
1157 SmallVector<ShuffleRequest, 2> reqs;
1158 SmallVector<std::optional<ShufflePlan>, 2> plans;
1159 SmallVector<ShuffleGather, 2> gathers(2);
1160 SmallVector<SmallVector<Value>, 2> leaves(2);
1161 for (unsigned r = 0; r < 2; ++r) {
1162 std::optional<ShuffleRequest> req = requestOf(op, r);
1163 if (!req)
1164 return failure();
1165 reqs.push_back(*req);
1166 plans.push_back(planner->planFor(*req, &gathers[r], &leaves[r]));
1167 }
1168 if (!plans[0] && !plans[1])
1169 return rewriter.notifyMatchFailure(op, "no plan beats LLVM's lowering");
1170 SmallVector<Value, 2> results;
1171 for (unsigned r = 0; r < 2; ++r)
1172 results.push_back(plans[r]
1173 ? lower(rewriter, *planner, reqs[r], *plans[r],
1174 gathers[r], leaves[r])
1175 : vector::ShuffleOp::create(
1176 rewriter, op.getLoc(), adaptor.getSource(),
1177 adaptor.getSource(), reqs[r].mask)
1178 .getResult());
1179 rewriter.replaceOp(op, results);
1180 return success();
1181 }
1182
1183 std::shared_ptr<AIE2PShufflePlanner> planner;
1184};
1185
1186// A vector.transpose as a vector.shuffle of the flattened vector, which the
1187// planner or LLVM then lowers.
1188struct LowerVectorTransposeToShuffleAIE2pPattern
1189 : OpConversionPattern<vector::TransposeOp> {
1190 using OpConversionPattern::OpConversionPattern;
1191
1192 LogicalResult
1193 matchAndRewrite(vector::TransposeOp op, OpAdaptor adaptor,
1194 ConversionPatternRewriter &rewriter) const override {
1195 VectorType srcTy = op.getSourceVectorType();
1196 VectorType resTy = op.getResultVectorType();
1197 if (srcTy.isScalable())
1198 return failure();
1199 ArrayRef<int64_t> perm = op.getPermutation();
1200 ArrayRef<int64_t> srcShape = srcTy.getShape();
1201 SmallVector<int64_t> srcStrides(srcShape.size(), 1);
1202 for (int64_t d = srcShape.size() - 2; d >= 0; --d)
1203 srcStrides[d] = srcStrides[d + 1] * srcShape[d + 1];
1204 int64_t n = srcTy.getNumElements();
1205 SmallVector<int64_t> mask;
1206 SmallVector<int64_t> idx(resTy.getRank(), 0);
1207 for (int64_t i = 0; i < n; ++i) {
1208 // Result index idx reads source index s with s[perm[d]] = idx[d].
1209 int64_t s = 0;
1210 for (int64_t d = 0; d < resTy.getRank(); ++d)
1211 s += idx[d] * srcStrides[perm[d]];
1212 mask.push_back(s);
1213 for (int64_t d = resTy.getRank() - 1; d >= 0; --d) {
1214 if (++idx[d] < resTy.getDimSize(d))
1215 break;
1216 idx[d] = 0;
1217 }
1218 }
1219 Location loc = op.getLoc();
1220 auto flatTy = VectorType::get({n}, srcTy.getElementType());
1221 Value flat =
1222 vector::ShapeCastOp::create(rewriter, loc, flatTy, adaptor.getVector());
1223 Value shuffled = vector::ShuffleOp::create(rewriter, loc, flat, flat, mask);
1224 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, resTy, shuffled);
1225 return success();
1226 }
1227};
1228
1229} // namespace
1230
1231std::optional<ShufflePlan>
1233 SmallVector<Value> *leaves) {
1234 SmallVector<Value> asIsLeaves;
1235 std::optional<ShuffleGather> asIs = gatherOf(req, /*fold=*/false, asIsLeaves);
1236 if (!asIs)
1237 return std::nullopt;
1238 unsigned llvmCost = estimateLLVMShuffleCost(*asIs);
1239 if (llvmCost == 0)
1240 return std::nullopt;
1241 LLVM_DEBUG(llvm::dbgs() << "planning " << *req.user
1242 << "\nLLVM estimate: " << llvmCost << "\n");
1243
1244 // A mode costs one instruction to load into a scalar register, unless an
1245 // earlier plan has loaded it already. Then fewer new modes and fewer
1246 // instructions break ties.
1247 auto score = [&](const ShufflePlan &p) {
1248 unsigned fresh = llvm::count_if(p.modes(), [&](ShuffleMode m) {
1249 return !llvm::is_contained(usedModes, m);
1250 });
1251 return std::make_tuple(p.cost + fresh, fresh, p.cost);
1252 };
1253 std::optional<ShufflePlan> best;
1254 auto consider = [&](const ShuffleGather &g, SmallVector<Value> &ls) {
1255 for (const ShufflePlan &p : candidates(g)) {
1256 if (p.cost >= llvmCost || (best && score(p) >= score(*best)))
1257 continue;
1258 best = p;
1259 if (gather)
1260 *gather = g;
1261 if (leaves)
1262 *leaves = ls;
1263 }
1264 };
1265 SmallVector<Value> foldedLeaves;
1266 std::optional<ShuffleGather> folded =
1267 gatherOf(req, /*fold=*/true, foldedLeaves);
1268 if (folded && folded->key() != asIs->key())
1269 consider(*folded, foldedLeaves);
1270 consider(*asIs, asIsLeaves);
1271 return best;
1272}
1273
1275 for (ShuffleMode m : plan.modes())
1276 if (!llvm::is_contained(usedModes, m))
1277 usedModes.push_back(m);
1278}
1279
1281 RewritePatternSet &patterns, std::shared_ptr<AIE2PShufflePlanner> planner) {
1282 patterns.add<LowerStaticShuffleAIE2pPattern, LowerInterleaveAIE2pPattern,
1283 LowerDeinterleaveAIE2pPattern>(patterns.getContext(), planner);
1284 // Ahead of the AIE2 lowering, which takes 512-bit transposes only.
1285 patterns.add<LowerVectorTransposeToShuffleAIE2pPattern>(patterns.getContext(),
1286 /*benefit=*/2);
1287}
1288
1290 ConversionTarget &target, std::shared_ptr<AIE2PShufflePlanner> planner) {
1291 target.addDynamicallyLegalOp<vector::ShuffleOp>(
1292 [planner](vector::ShuffleOp op) {
1293 return !planner->planFor(requestOf(op)).has_value();
1294 });
1295 target.addDynamicallyLegalOp<vector::InterleaveOp>(
1296 [planner](vector::InterleaveOp op) {
1297 std::optional<ShuffleRequest> req = requestOf(op);
1298 return !req || !planner->planFor(*req).has_value();
1299 });
1300 target.addDynamicallyLegalOp<vector::DeinterleaveOp>(
1301 [planner](vector::DeinterleaveOp op) {
1302 for (unsigned r = 0; r < 2; ++r) {
1303 std::optional<ShuffleRequest> req = requestOf(op, r);
1304 if (req && planner->planFor(*req))
1305 return false;
1306 }
1307 return true;
1308 });
1309}
std::optional< ShufflePlan > planFor(const ShuffleRequest &request, ShuffleGather *gather=nullptr, llvm::SmallVector< mlir::Value > *leaves=nullptr)
const llvm::SmallVector< ShufflePlan, 2 > & candidates(const ShuffleGather &gather)
void noteUsed(const ShufflePlan &plan)
void populateAIE2PShufflePlannerPatterns(mlir::RewritePatternSet &patterns, std::shared_ptr< AIE2PShufflePlanner > planner)
ShuffleModeShape getShuffleModeShape(ShuffleMode mode)
unsigned estimateLLVMShuffleCost(const ShuffleGather &gather)
void configureAIE2PShufflePlannerLegality(mlir::ConversionTarget &target, std::shared_ptr< AIE2PShufflePlanner > planner)
std::array< uint8_t, 64 > getShuffleModeBytePermutation(ShuffleMode mode)
bool simulateShufflePlan(const ShuffleGather &gather, const ShufflePlan &plan)
llvm::SmallVector< int64_t > sourceLanes
int32_t sourceOffset(unsigned k) const
llvm::SmallVector< int32_t > target
llvm::SmallVector< bool > sourceIsSplat
llvm::SmallVector< int > results
llvm::SmallVector< Node > nodes
llvm::SmallVector< ShuffleMode > modes() const
llvm::SmallVector< int64_t > mask