MLIR 24.0.0git
ParallelLoopFusion.cpp
Go to the documentation of this file.
1//===- ParallelLoopFusion.cpp - Code to perform loop fusion ---------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements loop fusion on parallel loops.
10//
11//===----------------------------------------------------------------------===//
12
14
26#include "mlir/IR/Builders.h"
28#include "mlir/IR/IRMapping.h"
29#include "mlir/IR/Matchers.h"
33#include "mlir/IR/Value.h"
35
36#include "llvm/ADT/STLExtras.h"
37#include "llvm/ADT/SetVector.h"
38#include "llvm/ADT/SmallBitVector.h"
39#include "llvm/ADT/TypeSwitch.h"
40#include "llvm/Support/InterleavedRange.h"
41
42#include "llvm/Support/DebugLog.h"
43#include <numeric>
44#include <optional>
45#include <tuple>
46#define DEBUG_TYPE "parallel-loop-fusion"
47
48namespace mlir {
49#define GEN_PASS_DEF_SCFPARALLELLOOPFUSION
50#include "mlir/Dialect/SCF/Transforms/Passes.h.inc"
51} // namespace mlir
52
53using namespace mlir;
54using namespace mlir::scf;
55
56/// Verify there are no nested ParallelOps.
57static bool hasNestedParallelOp(ParallelOp ploop) {
58 auto walkResult =
59 ploop.getBody()->walk([](ParallelOp) { return WalkResult::interrupt(); });
60 return walkResult.wasInterrupted();
61}
62
63/// Verify equal iteration spaces.
64static bool equalIterationSpaces(ParallelOp firstPloop,
65 ParallelOp secondPloop) {
66 if (firstPloop.getNumLoops() != secondPloop.getNumLoops())
67 return false;
68
69 // Two bounds match if they are the same value, or if both are constants
70 // holding the same value. The latter matters because equivalent bounds are
71 // often materialized by distinct `arith.constant` ops, which leaves the
72 // iteration spaces equal even though the SSA values differ.
73 auto matchOperands = [&](const OperandRange &lhs,
74 const OperandRange &rhs) -> bool {
75 // TODO: Extend this to support aliases.
76 return std::equal(lhs.begin(), lhs.end(), rhs.begin(),
77 [](Value lhsValue, Value rhsValue) {
78 if (lhsValue == rhsValue)
79 return true;
80 std::optional<int64_t> lhsConst =
81 getConstantIntValue(lhsValue);
82 std::optional<int64_t> rhsConst =
83 getConstantIntValue(rhsValue);
84 return lhsConst && rhsConst && *lhsConst == *rhsConst;
85 });
86 };
87 return matchOperands(firstPloop.getLowerBound(),
88 secondPloop.getLowerBound()) &&
89 matchOperands(firstPloop.getUpperBound(),
90 secondPloop.getUpperBound()) &&
91 matchOperands(firstPloop.getStep(), secondPloop.getStep());
92}
93
94/// Check if both operations are the same type of memory write op and
95/// write to the same memory location (same buffer and same indices).
97 if (!op1 || !op2 || op1->getName() != op2->getName())
98 return false;
99 if (op1 == op2)
100 return true;
101 // support only these memory-writing ops for now
102 if (!isa<memref::StoreOp, vector::TransferWriteOp, vector::StoreOp>(op1))
103 return false;
104 bool opsAreIdentical =
106 .Case([&](memref::StoreOp storeOp1) {
107 auto storeOp2 = cast<memref::StoreOp>(op2);
108 return (storeOp1.getMemRef() == storeOp2.getMemRef()) &&
109 (storeOp1.getIndices() == storeOp2.getIndices());
110 })
111 .Case([&](vector::TransferWriteOp writeOp1) {
112 auto writeOp2 = cast<vector::TransferWriteOp>(op2);
113 return (writeOp1.getBase() == writeOp2.getBase()) &&
114 (writeOp1.getIndices() == writeOp2.getIndices()) &&
115 (writeOp1.getMask() == writeOp2.getMask()) &&
116 (writeOp1.getValueToStore().getType() ==
117 writeOp2.getValueToStore().getType()) &&
118 (writeOp1.getInBounds() == writeOp2.getInBounds());
119 })
120 .Case([&](vector::StoreOp vecStoreOp1) {
121 auto vecStoreOp2 = cast<vector::StoreOp>(op2);
122 return (vecStoreOp1.getBase() == vecStoreOp2.getBase()) &&
123 (vecStoreOp1.getIndices() == vecStoreOp2.getIndices()) &&
124 (vecStoreOp1.getValueToStore().getType() ==
125 vecStoreOp2.getValueToStore().getType()) &&
126 (vecStoreOp1.getAlignment() == vecStoreOp2.getAlignment()) &&
127 (vecStoreOp1.getNontemporal() ==
128 vecStoreOp2.getNontemporal());
129 })
130 .Default([](Operation *) { return false; });
131 return opsAreIdentical;
132}
133
134/// Check if val1 (from the first parallel loop) and val2 (from the
135/// second) are equivalent, considering the mapping of induction variables from
136/// the first to the second parallel loop.
137static bool valsAreEquivalent(Value val1, Value val2,
138 const IRMapping &loopsIVsMap) {
139 if (val1 == val2 || loopsIVsMap.lookupOrDefault(val1) == val2 ||
140 loopsIVsMap.lookupOrDefault(val2) == val1)
141 return true;
142 Operation *val1DefOp = val1.getDefiningOp();
143 Operation *val2DefOp = val2.getDefiningOp();
144 if (!val1DefOp || !val2DefOp)
145 return false;
146 if (!isMemoryEffectFree(val1DefOp) || !isMemoryEffectFree(val2DefOp))
147 return false;
149 val1DefOp, val2DefOp,
150 [&](Value v1, Value v2) {
151 return success(loopsIVsMap.lookupOrDefault(v1) == v2 ||
152 loopsIVsMap.lookupOrDefault(v2) == v1);
153 },
154 /*markEquivalent=*/nullptr, OperationEquivalence::Flags::IgnoreLocations);
155}
156
157/// If the `expr` value is the result of an integer addition of `base` and a
158/// constant, return the constant.
159static std::optional<int64_t> getAddConstant(Value expr, Value base,
160 const IRMapping &loopsIVsMap) {
161 if (auto addOp = expr.getDefiningOp<arith::AddIOp>()) {
162 if (auto constOp = getConstantIntValue(addOp.getLhs());
163 constOp && valsAreEquivalent(addOp.getRhs(), base, loopsIVsMap))
164 return constOp.value();
165 if (auto constOp = getConstantIntValue(addOp.getRhs());
166 constOp && valsAreEquivalent(addOp.getLhs(), base, loopsIVsMap))
167 return constOp.value();
168 return std::nullopt;
169 }
170
171 if (auto addOp = expr.getDefiningOp<index::AddOp>()) {
172 if (auto constOp = getConstantIntValue(addOp.getLhs());
173 constOp && valsAreEquivalent(addOp.getRhs(), base, loopsIVsMap))
174 return constOp.value();
175 if (auto constOp = getConstantIntValue(addOp.getRhs());
176 constOp && valsAreEquivalent(addOp.getLhs(), base, loopsIVsMap))
177 return constOp.value();
178 return std::nullopt;
179 }
180
181 if (auto applyOp = expr.getDefiningOp<affine::AffineApplyOp>()) {
182 AffineMap map = applyOp.getAffineMap();
183 if (map.getNumResults() != 1 || map.getNumDims() != 1 ||
184 map.getNumSymbols() != 0)
185 return std::nullopt;
186 if (!valsAreEquivalent(applyOp.getOperand(0), base, loopsIVsMap))
187 return std::nullopt;
188 AffineExpr result = map.getResult(0);
189 auto bin = dyn_cast<AffineBinaryOpExpr>(result);
190 if (!bin || bin.getKind() != AffineExprKind::Add)
191 return std::nullopt;
192 auto lhsDim = dyn_cast<AffineDimExpr>(bin.getLHS());
193 auto rhsDim = dyn_cast<AffineDimExpr>(bin.getRHS());
194 auto lhsConst = dyn_cast<AffineConstantExpr>(bin.getLHS());
195 auto rhsConst = dyn_cast<AffineConstantExpr>(bin.getRHS());
196 if (lhsConst && rhsDim)
197 return lhsConst.getValue();
198 if (rhsConst && lhsDim)
199 return rhsConst.getValue();
200 }
201 return std::nullopt;
202}
203
204// Return true if the scalar load index may hit any element covered by a
205// vector.store/transfer_write along a single memref dimension. Supported cases:
206//
207// 1) Direct index match (with optional offset):
208// vector.transfer_write %v, %A[%i] : vector<4xf32>, memref<...>
209// %x = memref.load %A[%i] : memref<...>
210//
211// 2) Loop IV range intersects the write range:
212// vector.transfer_write %v, %A[%c0] : vector<4xf32>, memref<...>
213// scf.for %k = %c0 to %c4 step %c1 { %x = memref.load %A[%k] }
214//
215// 3) Constant index (or IV + constant) within the write range:
216// vector.transfer_write %v, %A[%c0] : vector<4xf32>, memref<...>
217// %x = memref.load %A[%c2] : memref<...>
218// %y = memref.load %A[%i + %c1] : memref<...>
219//
220// Args:
221// - loadIndex: index used by the scalar load for this dimension.
222// - offset: subview offset for the base memref dimension (if any).
223// - writeIndex: index used by the transfer_write for this dimension. Can be
224// null if the dim was dropped by a rank reducing subview, whose result is
225// written by the vector.write.
226// - extent: vector size along this dimension (number of elements written).
227// - loopsIVsMap: IV equivalence map between fused loops.
228static bool loadIndexWithinWriteRange(Value loadIndex, OpFoldResult offset,
229 Value writeIndex, int64_t extent,
230 const IRMapping &loopsIVsMap) {
231 if (extent <= 0)
232 return false;
233
234 // Extract constant loop bounds for loop IVs (e.g. from scf.for).
235 auto getConstLoopBoundsForIV =
236 [](Value index) -> std::optional<std::tuple<int64_t, int64_t, int64_t>> {
237 auto blockArg = dyn_cast<BlockArgument>(index);
238 if (!blockArg)
239 return std::nullopt;
240 auto *parentOp = blockArg.getOwner()->getParentOp();
241 auto loopLike = dyn_cast<LoopLikeOpInterface>(parentOp);
242 if (!loopLike)
243 return std::nullopt;
244 auto ranges = getConstLoopBounds(loopLike);
245 if (ranges.empty())
246 return std::nullopt;
247
248 auto ivs = loopLike.getLoopInductionVars();
249 if (!ivs)
250 return std::nullopt;
251 auto it = llvm::find(*ivs, blockArg);
252 if (it == ivs->end())
253 return std::nullopt;
254 unsigned pos = std::distance(ivs->begin(), it);
255 if (pos >= ranges.size())
256 return std::nullopt;
257 auto [lb, ub, step] = ranges[pos];
258 return std::make_tuple(lb, ub, step);
259 };
260
261 std::optional<int64_t> offsetConst = getConstantIntValue(offset);
262 std::optional<int64_t> writeConst =
263 writeIndex ? getConstantIntValue(writeIndex) : std::optional<int64_t>(0);
264 if (!writeConst && writeIndex) {
265 // Treat single-iteration IVs as constants for matching.
266 if (auto bounds = getConstLoopBoundsForIV(writeIndex)) {
267 auto [lb, ub, step] = *bounds;
268 if (step > 0 && ub == lb + step)
269 writeConst = lb;
270 }
271 }
272
273 // Check whether a loop IV is fully contained in a constant write range.
274 auto loopIVWithinRange = [](int64_t lb, int64_t ub, int64_t step,
275 int64_t rangeStart, int64_t rangeExtent) -> bool {
276 if (rangeExtent <= 0 || step <= 0)
277 return false;
278 if (ub <= lb)
279 return false;
280 int64_t rangeEnd = rangeStart + rangeExtent;
281 return lb >= rangeStart && ub <= rangeEnd;
282 };
283
284 if (offsetConst && writeConst) {
285 // Constant start of the write range; check constant load or loop IV range.
286 int64_t start = *offsetConst + *writeConst;
287 if (auto loadConst = getConstantIntValue(loadIndex))
288 return (*loadConst >= start && *loadConst < start + extent);
289 if (auto bounds = getConstLoopBoundsForIV(loadIndex)) {
290 auto [lb, ub, step] = *bounds;
291 return loopIVWithinRange(lb, ub, step, start, extent);
292 }
293 }
294
295 if (writeIndex) {
296 // Direct IV match (or IV + constant) against the write index.
297 if (offsetConst && *offsetConst == 0 &&
298 valsAreEquivalent(loadIndex, writeIndex, loopsIVsMap))
299 return true;
300 if (auto addConst = getAddConstant(loadIndex, writeIndex, loopsIVsMap)) {
301 // Match load index of the form writeIndex + C within the write extent.
302 if (offsetConst) {
303 int64_t start = *offsetConst;
304 return (*addConst >= start && *addConst < start + extent);
305 }
306 }
307 return false;
308 }
309
310 if (auto offsetVal = dyn_cast<Value>(offset)) {
311 // Exact match when extent is 1 and the load hits the offset value.
312 if (extent == 1 && valsAreEquivalent(loadIndex, offsetVal, loopsIVsMap))
313 return true;
314 }
315
316 return false;
317}
318
319/// Return the base memref value used by the given memory op.
321 // TODO: use the common interface for memory ops once available.
323 .Case([&](memref::LoadOp load) { return load.getMemRef(); })
324 .Case([&](memref::StoreOp store) { return store.getMemRef(); })
325 .Case([&](vector::TransferReadOp read) { return read.getBase(); })
326 .Case([&](vector::TransferWriteOp write) { return write.getBase(); })
327 .Case([&](vector::LoadOp load) { return load.getBase(); })
328 .Case([&](vector::StoreOp store) { return store.getBase(); })
329 .Default([](Operation *) { return Value(); });
330}
331
332/// Recognize scalar memref.load of an element produced by a vector write
333/// (vector.transfer_write or vector.store, optionally through a rank-reducing
334/// unit-stride subview) of the same buffer. This covers the pattern where a
335/// vector write stores a full lane pack and a subsequent scalar load reads an
336/// element from that lane pack. EXAMPLE:
337/// vector.transfer_write %V, %arg[%x, %y, ..., 0] {in_bounds = [true]} :
338/// vector<4xf32>, memref<4xf32, strided<[1], offset: ?>>
339/// scf.for %iter = %c0 to %c4 step %c1 iter_args(...) -> (f32) {
340/// %0 = memref.load %arg[%x, %y, ..., %iter] : memref<1x128x16x4xf32>
341/// ...
342/// }
343///
344static bool isLoadOnWrittenVector(memref::LoadOp loadOp, Value writeBase,
345 ValueRange writeIndices, VectorType vecTy,
346 ArrayRef<int64_t> vectorDimForWriteDim,
347 const IRMapping &ivsMap) {
348 if (!vecTy)
349 return false;
350
351 Value base = writeBase;
352 // The write base if there is no subview, or the subview source otherwise.
353 MemrefValue baseMemref = nullptr;
355 llvm::SmallBitVector droppedDims;
356 bool hasSubview = false;
357 auto *ctx = loadOp.getContext();
358 if (auto subView = base.getDefiningOp<memref::SubViewOp>()) {
359 if (!subView.hasUnitStride())
360 return false;
361 baseMemref = cast<MemrefValue>(subView.getSource());
362 offsets = llvm::to_vector(subView.getMixedOffsets());
363 droppedDims = subView.getDroppedDims();
364 hasSubview = true;
365 } else {
366 baseMemref = dyn_cast<MemrefValue>(base);
367 if (!baseMemref)
368 return false;
369 }
370
371 auto loadIndices = loadOp.getIndices();
372 unsigned baseRank = baseMemref.getType().getRank();
373 if ((loadOp.getMemref() != baseMemref) || (loadIndices.size() != baseRank))
374 return false;
375
376 unsigned writeRank = writeIndices.size();
377 if ((!hasSubview && writeRank != baseRank) ||
378 (hasSubview && offsets.size() != baseRank) ||
379 (vectorDimForWriteDim.size() != writeRank))
380 return false;
381
382 auto zeroAttr = IntegerAttr::get(IndexType::get(ctx), 0);
383 unsigned writeMemrefDim = 0;
384 for (unsigned baseDim : llvm::seq(baseRank)) {
385 bool wasDropped = (hasSubview && droppedDims.test(baseDim));
386 int64_t vectorDim = !wasDropped ? vectorDimForWriteDim[writeMemrefDim] : -1;
387 int64_t extent = 1;
388 if (vectorDim >= 0) {
389 int64_t dimSize = vecTy.getDimSize(vectorDim);
390 if (dimSize == ShapedType::kDynamic)
391 return false;
392 extent = dimSize;
393 }
394 Value writeIndex = !wasDropped ? writeIndices[writeMemrefDim] : Value();
395 OpFoldResult offset =
396 hasSubview ? offsets[baseDim] : OpFoldResult(zeroAttr);
397 if (!loadIndexWithinWriteRange(loadIndices[baseDim], offset, writeIndex,
398 extent, ivsMap))
399 return false;
400 if (!wasDropped)
401 ++writeMemrefDim;
402 }
403
404 return true;
405}
406
407/// Recognize scalar memref.load of an element produced by a
408/// vector.transfer_write
409static bool loadMatchesVectorWrite(memref::LoadOp loadOp,
410 vector::TransferWriteOp writeOp,
411 const IRMapping &ivsMap) {
412 auto vecTy = dyn_cast<VectorType>(writeOp.getVector().getType());
413 if (!vecTy)
414 return false;
415
416 unsigned writeRank = writeOp.getIndices().size();
417 AffineMap permutationMap = writeOp.getPermutationMap();
418 if (!permutationMap.isProjectedPermutation() ||
419 permutationMap.getNumResults() != vecTy.getRank() ||
420 permutationMap.getNumDims() != writeRank)
421 return false;
422
423 SmallVector<int64_t> vectorDimForWriteDim(writeRank, -1);
424 for (unsigned vecDim = 0; vecDim < permutationMap.getNumResults(); ++vecDim) {
425 auto dimExpr = dyn_cast<AffineDimExpr>(permutationMap.getResult(vecDim));
426 if (!dimExpr)
427 return false;
428 unsigned writeDim = dimExpr.getPosition();
429 if (writeDim >= writeRank || vectorDimForWriteDim[writeDim] != -1)
430 return false;
431 vectorDimForWriteDim[writeDim] = vecDim;
432 }
433
434 return isLoadOnWrittenVector(loadOp, writeOp.getBase(), writeOp.getIndices(),
435 vecTy, vectorDimForWriteDim, ivsMap);
436}
437
438/// Recognize scalar memref.load of an element produced by a vector.store
439static bool loadMatchesVectorStore(memref::LoadOp loadOp,
440 vector::StoreOp storeOp,
441 const IRMapping &ivsMap) {
442 auto vecTy = dyn_cast<VectorType>(storeOp.getValueToStore().getType());
443 if (!vecTy)
444 return false;
445
446 unsigned writeRank = storeOp.getIndices().size();
447 if (vecTy.getRank() > writeRank)
448 return false;
449
450 SmallVector<int64_t> vectorDimForWriteDim(writeRank, -1);
451 unsigned vecRank = vecTy.getRank();
452 for (unsigned i = 0; i < vecRank; ++i) {
453 unsigned writeDim = writeRank - vecRank + i;
454 vectorDimForWriteDim[writeDim] = i;
455 }
456
457 return isLoadOnWrittenVector(loadOp, storeOp.getBase(), storeOp.getIndices(),
458 vecTy, vectorDimForWriteDim, ivsMap);
459}
460
461/// Check if both operations access the same positions of the same
462/// buffer, but one of the two does it through a rank-reducing full subview of
463/// the buffer (the other's base). EXAMPLE:
464/// memref.store %a, %buf[%c0, %i, %j] : memref<1x2x2xf32>
465/// %alias = memref.subview %buf[0, 0, 0][1, 2, 2][1, 1, 1]: memref<1x2x2xf32>
466/// to memref<2x2xf32>
467/// %val = memref.load %alias[%i, %j] : memref<2x2xf32>
468template <typename OpTy1, typename OpTy2>
470 OpTy1 op1, OpTy2 op2, const IRMapping &firstToSecondPloopIVsMap,
471 OpBuilder &b) {
472 auto base1 = cast<MemrefValue>(getBaseMemref(op1));
473 auto base2 = cast<MemrefValue>(getBaseMemref(op2));
474 if (!base1 || !base2)
475 return false;
476
477 auto accessThroughTrivialSubviewIsSame =
478 [&b](memref::SubViewOp subView, ValueRange subViewAccess,
479 ValueRange sourceAccess, const IRMapping &ivsMap) -> bool {
480 SmallVector<Value> resolvedSubviewAccess;
481 LogicalResult resolved = resolveSourceIndicesRankReducingSubview(
482 subView.getLoc(), b, subView, subViewAccess, resolvedSubviewAccess);
483 if (failed(resolved) ||
484 (resolvedSubviewAccess.size() != sourceAccess.size()))
485 return false;
486 for (auto [dimIdx, resolvedIndex] :
487 llvm::enumerate(resolvedSubviewAccess)) {
488 if (!matchPattern(resolvedIndex, m_Zero()) &&
489 !valsAreEquivalent(resolvedIndex, sourceAccess[dimIdx], ivsMap))
490 return false;
491 }
492 return true;
493 };
494
495 // Case 1: op1 uses a subview of op2's base.
496 if (auto subView = base1.template getDefiningOp<memref::SubViewOp>();
497 subView &&
499 base2, cast<MemrefValue>(subView.getSource())) &&
500 accessThroughTrivialSubviewIsSame(subView, op1.getIndices(),
501 op2.getIndices(),
502 firstToSecondPloopIVsMap))
503 return true;
504
505 // Case 2: op2 uses a subview of op1's base.
506 if (auto subView = base2.template getDefiningOp<memref::SubViewOp>();
507 subView &&
509 base1, cast<MemrefValue>(subView.getSource())) &&
510 accessThroughTrivialSubviewIsSame(subView, op2.getIndices(),
511 op1.getIndices(),
512 firstToSecondPloopIVsMap))
513 return true;
514
515 return false;
516}
517
518/// Check if both memory read/write operations access the same indices
519/// (considering also the mapping of induction variables from the first to the
520/// second parallel loop).
521template <typename OpTy1, typename OpTy2>
522static bool opsAccessSameIndices(OpTy1 op1, OpTy2 op2,
523 const IRMapping &loopsIVsMap, OpBuilder &b) {
524 auto indices1 = op1.getIndices();
525 auto indices2 = op2.getIndices();
526 if (indices1.size() != indices2.size())
527 return opsAccessSameIndicesViaRankReducingSubview(op1, op2, loopsIVsMap, b);
528 for (auto [idx1, idx2] : llvm::zip(indices1, indices2)) {
529 if (!valsAreEquivalent(idx1, idx2, loopsIVsMap))
530 return false;
531 }
532 return true;
533}
534
535/// Check if the loadOp reads from the same memory location (same buffer,
536/// same indices and same properties) as written by the storeOp.
537static bool
539 const IRMapping &firstToSecondPloopIVsMap,
540 OpBuilder &b) {
541 if (!loadOp || !storeOp)
542 return false;
543 // Support only these memory-reading ops for now
544 if (!isa<memref::LoadOp, vector::TransferReadOp, vector::LoadOp>(loadOp))
545 return false;
546 bool accessSameMemory =
548 .Case([&](memref::LoadOp memLoadOp) {
549 if (auto memStoreOp = dyn_cast<memref::StoreOp>(storeOp))
550 return opsAccessSameIndices(memLoadOp, memStoreOp,
551 firstToSecondPloopIVsMap, b);
552 if (auto vecWriteOp = dyn_cast<vector::TransferWriteOp>(storeOp))
553 return loadMatchesVectorWrite(memLoadOp, vecWriteOp,
554 firstToSecondPloopIVsMap);
555 if (auto vecStoreOp = dyn_cast<vector::StoreOp>(storeOp))
556 return loadMatchesVectorStore(memLoadOp, vecStoreOp,
557 firstToSecondPloopIVsMap);
558 return false;
559 })
560 .Case([&](vector::TransferReadOp vecReadOp) {
561 auto vecWriteOp = dyn_cast<vector::TransferWriteOp>(storeOp);
562 if (!vecWriteOp)
563 return false;
564 return opsAccessSameIndices(vecReadOp, vecWriteOp,
565 firstToSecondPloopIVsMap, b) &&
566 (vecReadOp.getMask() == vecWriteOp.getMask()) &&
567 (vecReadOp.getInBounds() == vecWriteOp.getInBounds());
568 })
569 .Case([&](vector::LoadOp vecLoadOp) {
570 auto vecStoreOp = dyn_cast<vector::StoreOp>(storeOp);
571 if (!vecStoreOp)
572 return false;
573 return opsAccessSameIndices(vecLoadOp, vecStoreOp,
574 firstToSecondPloopIVsMap, b) &&
575 (vecLoadOp.getAlignment() == vecStoreOp.getAlignment());
576 })
577 .Default([](Operation *) { return false; });
578 return accessSameMemory;
579}
580
583 .Case([&](memref::StoreOp storeOp) { return storeOp.getMemRef(); })
584 .Case([&](vector::TransferWriteOp writeOp) { return writeOp.getBase(); })
585 .Case([&](vector::StoreOp vecStoreOp) { return vecStoreOp.getBase(); })
586 .Default([](Operation *) { return Value(); });
587}
588
589/// To be called when `mayAlias(val1, val2)` is true. Check if the potential
590/// aliasing between the loadOp and storeOp can be resolved by analyzing their
591/// access patterns.
592static bool canResolveAlias(Operation *loadOp, Operation *storeOp,
593 const IRMapping &loopsIVsMap) {
594 if (auto transfWriteOp = dyn_cast<vector::TransferWriteOp>(storeOp);
595 transfWriteOp && isa<memref::LoadOp>(loadOp))
596 return loadMatchesVectorWrite(cast<memref::LoadOp>(loadOp), transfWriteOp,
597 loopsIVsMap);
598 if (auto vecStoreOp = dyn_cast<vector::StoreOp>(storeOp);
599 vecStoreOp && isa<memref::LoadOp>(loadOp))
600 return loadMatchesVectorStore(cast<memref::LoadOp>(loadOp), vecStoreOp,
601 loopsIVsMap);
602 return false;
603}
604
605/// Check that the parallel loops have no mixed access to the same buffers.
606/// Return `true` if the second parallel loop does not read or write the buffers
607/// written by the first loop using different indices.
609 ParallelOp firstPloop, ParallelOp secondPloop,
610 const IRMapping &firstToSecondPloopIndices,
612 // Map buffers to their store/write ops in the firstPloop
613 DenseMap<Value, SmallVector<Operation *>> bufferStoresInFirstPloop;
614 // Record all the memory buffers used in store/write ops found in firstPloop
615 llvm::SmallSetVector<Value, 4> buffersWrittenInFirstPloop;
616
617 auto collectStoreOpsInWalk = [&](Operation *op) {
618 auto memOpInterf = dyn_cast_if_present<MemoryEffectOpInterface>(op);
619 // Ignore ops that don't write to memory
620 if (!memOpInterf || (!memOpInterf.hasEffect<MemoryEffects::Write>() &&
621 !memOpInterf.hasEffect<MemoryEffects::Free>()))
622 return WalkResult::advance();
623
624 // Only these memory-writing ops are supported for now:
625 // memref.store, vector.transfer_write, vector.store
626 Value storeOpBase = getStoreOpTargetBuffer(op);
627 if (!storeOpBase)
628 return WalkResult::interrupt();
629
630 // Expect the base operand to be a Memref
631 MemrefValue storeOpBaseMemref = dyn_cast<MemrefValue>(storeOpBase);
632 if (!storeOpBaseMemref)
633 return WalkResult::interrupt();
634 // Get the original memref buffer, skipping full view-like ops
635 Value buffer = memref::skipFullyAliasingOperations(storeOpBaseMemref);
636 bufferStoresInFirstPloop[buffer].push_back(op);
637 buffersWrittenInFirstPloop.insert(buffer);
638 return WalkResult::advance();
639 };
640
641 // Walk the first parallel loop to collect all store/write ops and their
642 // target buffers
643 if (firstPloop.getBody()->walk(collectStoreOpsInWalk).wasInterrupted())
644 return false;
645
646 // Check that this load/read op encountered while walking the second parallel
647 // loop does not have incompatible data dependencies with the store/write ops
648 // collected from the first parallel loop: the loops can be fused only if in
649 // the 2nd loop there are no loads/stores from/to the buffers written in the
650 // 1st loop, except when on the same exact memory location (same indices) as
651 // written in the 1st loop.
652 auto checkLoadInWalkHasNoIncompatibleDataDeps = [&](Operation *loadOp) {
653 auto memOpInterf = dyn_cast_if_present<MemoryEffectOpInterface>(loadOp);
654 // To be conservative, we should stop on ops that don't advertise their
655 // memory effects. However, many ops don't implement MemoryEffectOpInterface
656 // yet, so for now we just skip them.
657 // TODO: once more ops add MemoryEffectOpInterface, interrupt the walk here.
658 if (!memOpInterf &&
659 !loadOp->hasTrait<mlir::OpTrait::HasRecursiveMemoryEffects>())
660 return WalkResult::advance();
661 // Ignore ops that don't read from memory, and wrapping ops that have nested
662 // memory effects (e.g. loops, conditionals) as they will be analyzed when
663 // visiting their nested ops.
664 if ((!memOpInterf &&
665 loadOp->hasTrait<mlir::OpTrait::HasRecursiveMemoryEffects>()) ||
666 (memOpInterf && !memOpInterf.hasEffect<MemoryEffects::Read>()))
667 return WalkResult::advance();
668 // Support only these memory-reading ops for now
669 if (!isa<memref::LoadOp, vector::TransferReadOp, vector::LoadOp>(loadOp) ||
670 !isa<MemrefValue>(loadOp->getOperand(0)))
671 return WalkResult::interrupt();
672
673 MemrefValue loadOpBase = cast<MemrefValue>(loadOp->getOperand(0));
674 MemrefValue loadedOrigBuf = memref::skipFullyAliasingOperations(loadOpBase);
675
676 for (Value storedMem : buffersWrittenInFirstPloop)
677 if ((storedMem != loadedOrigBuf) && mayAlias(storedMem, loadedOrigBuf) &&
678 !llvm::all_of(bufferStoresInFirstPloop[storedMem],
679 [&](Operation *storeOp) {
680 return canResolveAlias(loadOp, storeOp,
681 firstToSecondPloopIndices);
682 })) {
683 return WalkResult::interrupt();
684 }
685
686 auto writeOpsIt = bufferStoresInFirstPloop.find(loadedOrigBuf);
687 if (writeOpsIt == bufferStoresInFirstPloop.end())
688 return WalkResult::advance();
689 // Store/write ops to this buffer in the firstPloop
690 SmallVector<mlir::Operation *> &writeOps = writeOpsIt->second;
691
692 // If the first loop has no writes to this buffer, continue
693 if (writeOps.empty())
694 return WalkResult::advance();
695
696 Operation *writeOp = writeOps.front();
697
698 // In the first parallel loop, multiple writes to the same memref are
699 // allowed only on the same memory location
700 if (!llvm::all_of(writeOps, [&](Operation *otherWriteOp) {
701 return opsWriteSameMemLocation(writeOp, otherWriteOp);
702 })) {
703 return WalkResult::interrupt();
704 }
705
706 // Check that the load in secondPloop reads from the same memory location as
707 // written by the corresponding store in firstPloop
708 if (!loadsFromSameMemoryLocationWrittenBy(loadOp, writeOp,
709 firstToSecondPloopIndices, b)) {
710 return WalkResult::interrupt();
711 }
712
713 return WalkResult::advance();
714 };
715
716 // Walk the second parallel loop to check load/read ops against the stores
717 // collected from the first parallel loop.
718 return !secondPloop.getBody()
719 ->walk(checkLoadInWalkHasNoIncompatibleDataDeps)
720 .wasInterrupted();
721}
722
723/// Check that in each loop there are no read ops on the buffers written
724/// by the other loop, except when reading from the same exact memory location
725/// (same indices) as written in the other loop.
726static bool
727noIncompatibleDataDependencies(ParallelOp firstPloop, ParallelOp secondPloop,
728 const IRMapping &firstToSecondPloopIndices,
730 OpBuilder &b) {
732 firstPloop, secondPloop, firstToSecondPloopIndices, mayAlias, b))
733 return false;
734
735 IRMapping secondToFirstPloopIndices;
736 secondToFirstPloopIndices.map(secondPloop.getBody()->getArguments(),
737 firstPloop.getBody()->getArguments());
739 secondPloop, firstPloop, secondToFirstPloopIndices, mayAlias, b);
740}
741
742/// Check if fusion of the two parallel loops is legal:
743/// i.e. no nested parallel loops, equal iteration spaces,
744/// and no incompatible data dependencies between the loops.
745static bool isFusionLegal(ParallelOp firstPloop, ParallelOp secondPloop,
746 const IRMapping &firstToSecondPloopIndices,
748 OpBuilder &b) {
749 if (hasNestedParallelOp(firstPloop) || hasNestedParallelOp(secondPloop) ||
750 !equalIterationSpaces(firstPloop, secondPloop) ||
751 !noIncompatibleDataDependencies(firstPloop, secondPloop,
752 firstToSecondPloopIndices, mayAlias, b))
753 return false;
754
755 // We are fusing first loop into second, make sure there are no users of the
756 // first loop results between loops.
757 DominanceInfo dom;
758 for (Operation *user : firstPloop->getUsers()) {
759 if (!dom.properlyDominates(secondPloop, user, /*enclosingOpOk*/ false))
760 return false;
761 }
762 return true;
763}
764
765// Returns new parallel loop where two loops matching indices param are
766// interchanged
767static std::optional<ParallelOp>
768interchangeLoops(OpBuilder &builder, ParallelOp &loop,
769 const ArrayRef<int64_t> &indices) {
770 assert(loop.getNumLoops() == indices.size());
771 if (loop.getNumLoops() < 2)
772 return std::nullopt;
773
774 // Replace the parallel loop with the same parallel loop.
775 builder.setInsertionPoint(loop);
776 SmallVector<Value> newLB =
777 applyPermutation(SmallVector<Value>(loop.getLowerBound()), indices);
778 SmallVector<Value> newUB =
779 applyPermutation(SmallVector<Value>(loop.getUpperBound()), indices);
780 SmallVector<Value> newStep =
782 auto newOp = ParallelOp::create(builder, loop.getLoc(), newLB, newUB, newStep,
783 loop.getInitVals(), nullptr);
784 auto ivs = loop.getInductionVars();
786 newOp.getInductionVars(), invertPermutationVector(indices));
787 IRMapping mapping;
788 for (auto [iv, riv] : llvm::zip(ivs, newIvs)) {
789 mapping.map(iv, riv);
790 }
791
792 // Copy parallel loop body
793 auto b = OpBuilder::atBlockBegin(newOp.getBody());
794 for (auto &o : loop.getNumReductions()
795 ? loop.getBodyRegion().front()
796 : loop.getBodyRegion().front().without_terminator()) {
797 b.clone(o, mapping);
798 }
799 return newOp;
800}
801
802struct LoopIV {
804 bool operator!=(LoopIV const &other) const { return !(*this == other); }
805 bool operator==(LoopIV const &other) const {
806 return lBound == other.lBound && uBound == other.uBound &&
807 step == other.step;
808 }
809};
810
811template <>
813 static inline bool isEqual(const LoopIV &lhs, const LoopIV &rhs) {
814 return (lhs == rhs);
815 }
816
817 static inline unsigned getHashValue(const LoopIV &val) {
818 return llvm::hash_combine(
822 }
823};
824
825// Returns vector of candidate permutation indices vectors,
826// can be empty. Caps the number of extra candidate permutations
827// explored to avoid combinatorial explosion. This makes the search
828// intentionally incomplete.
831 ParallelOp &secondPloop,
832 int permBudget = 120) {
833 // Check preconditions
834 if (firstPloop.getNumLoops() < 2 ||
835 firstPloop.getNumLoops() != secondPloop.getNumLoops())
836 return {};
837
838 SmallVector<LoopIV> firstIVs(firstPloop.getNumLoops());
839 SmallVector<LoopIV> secondIVs(secondPloop.getNumLoops());
840 llvm::SmallSetVector<LoopIV, 6> unique;
841 for (unsigned index : llvm::seq(firstPloop.getNumLoops())) {
842 firstIVs[index].lBound = firstPloop.getLowerBound()[index];
843 firstIVs[index].uBound = firstPloop.getUpperBound()[index];
844 firstIVs[index].step = firstPloop.getStep()[index];
845 secondIVs[index].lBound = secondPloop.getLowerBound()[index];
846 secondIVs[index].uBound = secondPloop.getUpperBound()[index];
847 secondIVs[index].step = secondPloop.getStep()[index];
848 unique.insert(firstIVs[index]);
849 }
850
851 SmallVector<bool> diffIVs(firstPloop.getNumLoops());
852 llvm::transform(
853 llvm::zip(firstIVs, secondIVs), diffIVs.begin(),
854 [](auto const &pair) { return std::get<0>(pair) != std::get<1>(pair); });
855
857 for (auto [idx, val] : enumerate(diffIVs))
858 if (val)
859 indices.push_back(idx);
860
861 // Not a permutation shortcut
862 if (indices.size() == 1)
863 return {};
864
865 // Initialize with identity permutations
866 SmallVector<int64_t> basic(firstIVs.size());
867 std::iota(basic.begin(), basic.end(), 0);
868
869 if (indices.empty() && unique.size() == firstIVs.size())
870 return {};
871
872 if (indices.size() > 1) {
873 // Determine whether the iteration space of the first loop is a permutation
874 // of the second and collect remaps.
876 for (auto fIdx : indices) {
877 for (auto sIdx : indices) {
878 // can be remapped
879 if (fIdx != sIdx && firstIVs[fIdx] == secondIVs[sIdx] &&
880 remaps.end() == std::find(remaps.begin(), remaps.end(), sIdx)) {
881 remaps.push_back(sIdx);
882 break;
883 }
884 }
885 }
886
887 // Not a permutation
888 if (indices.size() != remaps.size())
889 return {};
890
891 // compose permutation indices
892 for (auto [from, to] : zip(indices, remaps)) {
893 basic[from] = to;
894 }
895
896 LDBG() << "Collected basic permutations: "
897 << llvm::interleaved_array(basic);
898
899 // All axes are unique, no further permutatons needed
900 if (unique.size() == firstIVs.size()) {
901 return {basic};
902 }
903 }
904
905 //
906 // Permute equal axes
907 assert(unique.size() != firstIVs.size() &&
908 "Expected at least two equal axes");
909
910 // Collect equal axes to groups
911 SmallVector<SmallVector<int64_t>> extraResults{basic};
913 for (auto iv : unique) {
915 for (unsigned index : llvm::seq(firstIVs.size())) {
916 if (firstIVs[index] == iv)
917 group.push_back(index);
918 }
919 if (group.size() > 1)
920 groups.push_back(std::move(group));
921 }
922
923 // Permute axes groups
924 SmallVector<SmallVector<int64_t>> rmpdGroups(groups);
925 bool repeat = true;
926 while (repeat && permBudget) {
927 repeat = false;
928 for (auto const &[group, groupRemaps] : zip(groups, rmpdGroups)) {
929 repeat |= std::next_permutation(groupRemaps.begin(), groupRemaps.end());
930 if (repeat)
931 break;
932 }
933
934 if (repeat) {
935 SmallVector<int64_t> extra(basic);
936 for (auto const &[group, groupRemaps] : zip(groups, rmpdGroups)) {
937 for (auto [from, to] : zip(group, groupRemaps))
938 extra[from] = basic[to];
939 }
940 if (basic != extra) {
941 LDBG() << "Collected extra permutations: "
942 << llvm::interleaved_array(extra);
943
944 extraResults.push_back(std::move(extra));
945 permBudget--;
946 }
947 }
948 }
949
950 return extraResults;
951}
952
953/// Prepend operations of firstPloop's body into secondPloop's body.
954/// Update secondPloop with new loop.
955static void applyLoopFusion(ParallelOp &firstPloop, ParallelOp &secondPloop,
956 OpBuilder &builder) {
957 Block *block1 = firstPloop.getBody();
958 Block *block2 = secondPloop.getBody();
959 ValueRange inits1 = firstPloop.getInitVals();
960 ValueRange inits2 = secondPloop.getInitVals();
961
962 SmallVector<Value> newInitVars(inits1.begin(), inits1.end());
963 newInitVars.append(inits2.begin(), inits2.end());
964
965 IRRewriter b(builder);
966 b.setInsertionPoint(secondPloop);
967 auto newSecondPloop = ParallelOp::create(
968 b, secondPloop.getLoc(), secondPloop.getLowerBound(),
969 secondPloop.getUpperBound(), secondPloop.getStep(), newInitVars);
970
971 Block *newBlock = newSecondPloop.getBody();
972 auto term1 = cast<ReduceOp>(block1->getTerminator());
973 auto term2 = cast<ReduceOp>(block2->getTerminator());
974
975 b.inlineBlockBefore(block2, newBlock, newBlock->begin(),
976 newBlock->getArguments());
977 b.inlineBlockBefore(block1, newBlock, newBlock->begin(),
978 newBlock->getArguments());
979
980 ValueRange results = newSecondPloop.getResults();
981 if (!results.empty()) {
982 b.setInsertionPointToEnd(newBlock);
983
984 ValueRange reduceArgs1 = term1.getOperands();
985 ValueRange reduceArgs2 = term2.getOperands();
986 SmallVector<Value> newReduceArgs(reduceArgs1.begin(), reduceArgs1.end());
987 newReduceArgs.append(reduceArgs2.begin(), reduceArgs2.end());
988
989 auto newReduceOp = scf::ReduceOp::create(b, term2.getLoc(), newReduceArgs);
990
991 for (auto &&[i, reg] : llvm::enumerate(llvm::concat<Region>(
992 term1.getReductions(), term2.getReductions()))) {
993 Block &oldRedBlock = reg.front();
994 Block &newRedBlock = newReduceOp.getReductions()[i].front();
995 b.inlineBlockBefore(&oldRedBlock, &newRedBlock, newRedBlock.begin(),
996 newRedBlock.getArguments());
997 }
998
999 firstPloop.replaceAllUsesWith(results.take_front(inits1.size()));
1000 secondPloop.replaceAllUsesWith(results.take_back(inits2.size()));
1001 }
1002 term1->erase();
1003 term2->erase();
1004 firstPloop.erase();
1005 secondPloop.erase();
1006 secondPloop = newSecondPloop;
1007}
1008
1009/// Check fusion pre-conditions and call fusion if it is possible
1010static void fuseIfLegal(ParallelOp firstPloop, ParallelOp &secondPloop,
1011 OpBuilder builder,
1013 Block *block1 = firstPloop.getBody();
1014 Block *block2 = secondPloop.getBody();
1015 IRMapping firstToSecondPloopIndices;
1016 firstToSecondPloopIndices.map(block1->getArguments(), block2->getArguments());
1017
1018 if (isFusionLegal(firstPloop, secondPloop, firstToSecondPloopIndices,
1019 mayAlias, builder)) {
1020 applyLoopFusion(firstPloop, secondPloop, builder);
1021 return;
1022 }
1023
1024 // If iteration space of the second parallel loop is a permutation of the
1025 // first one then interchange iteration space of the second parallel loop
1026 // and re-asses possibility of fusion.
1027 for (auto &perms :
1028 computeCandidateInterchangePermutations(firstPloop, secondPloop)) {
1029 OpBuilder::InsertionGuard guard(builder);
1030 LDBG() << "Applied permutation: " << llvm::interleaved_array(perms);
1031
1032 auto newLoop = interchangeLoops(builder, secondPloop, perms);
1033 firstToSecondPloopIndices.clear();
1034 firstToSecondPloopIndices.map(block1->getArguments(),
1035 newLoop->getBody()->getArguments());
1036 if (!isFusionLegal(firstPloop, *newLoop, firstToSecondPloopIndices,
1037 mayAlias, builder)) {
1038 LDBG() << "Rejected: " << newLoop;
1039
1040 newLoop->erase();
1041 continue;
1042 }
1043
1044 secondPloop.replaceAllUsesWith(newLoop->getResults());
1045 secondPloop->erase();
1046 secondPloop = *newLoop;
1047 applyLoopFusion(firstPloop, secondPloop, builder);
1048 break;
1049 }
1050}
1051
1053 Region &region, llvm::function_ref<bool(Value, Value)> mayAlias) {
1054 OpBuilder b(region);
1055 // Consider every single block and attempt to fuse adjacent loops.
1057 for (auto &block : region) {
1058 ploopChains.clear();
1059 ploopChains.push_back({});
1060
1061 // Not using `walk()` to traverse only top-level parallel loops and also
1062 // make sure that there are no side-effecting ops between the parallel
1063 // loops.
1064 bool noSideEffects = true;
1065 for (auto &op : block) {
1066 if (auto ploop = dyn_cast<ParallelOp>(op)) {
1067 if (noSideEffects) {
1068 ploopChains.back().push_back(ploop);
1069 } else {
1070 ploopChains.push_back({ploop});
1071 noSideEffects = true;
1072 }
1073 continue;
1074 }
1075 // TODO: Handle region side effects properly.
1076 noSideEffects &= isMemoryEffectFree(&op) && op.getNumRegions() == 0;
1077 }
1078 for (MutableArrayRef<ParallelOp> ploops : ploopChains) {
1079 for (int i = 0, e = ploops.size(); i + 1 < e; ++i)
1080 fuseIfLegal(ploops[i], ploops[i + 1], b, mayAlias);
1081 }
1082 }
1083}
1084
1085namespace {
1086struct ParallelLoopFusion
1087 : public impl::SCFParallelLoopFusionBase<ParallelLoopFusion> {
1088 void runOnOperation() override {
1089 auto &aa = getAnalysis<AliasAnalysis>();
1090
1091 auto mayAlias = [&](Value val1, Value val2) -> bool {
1092 // If the memref is defined in one of the parallel loops body, careful
1093 // alias analysis is needed.
1094 // TODO: check if this is still needed as a separate check.
1095 auto val1Def = val1.getDefiningOp();
1096 auto val2Def = val2.getDefiningOp();
1097 auto val1Loop =
1098 val1Def ? val1Def->getParentOfType<ParallelOp>() : nullptr;
1099 auto val2Loop =
1100 val2Def ? val2Def->getParentOfType<ParallelOp>() : nullptr;
1101 if (val1Loop != val2Loop)
1102 return true;
1103
1104 return !aa.alias(val1, val2).isNo();
1105 };
1106
1107 getOperation()->walk([&](Operation *child) {
1108 for (Region &region : child->getRegions())
1110 });
1111 }
1112};
1113} // namespace
1114
1115std::unique_ptr<Pass> mlir::createParallelLoopFusionPass() {
1116 return std::make_unique<ParallelLoopFusion>();
1117}
return success()
static bool mayAlias(Value first, Value second)
Returns true if two values may be referencing aliasing memory.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
auto load
static bool canResolveAlias(Operation *loadOp, Operation *storeOp, const IRMapping &loopsIVsMap)
To be called when mayAlias(val1, val2) is true.
static std::optional< ParallelOp > interchangeLoops(OpBuilder &builder, ParallelOp &loop, const ArrayRef< int64_t > &indices)
static bool equalIterationSpaces(ParallelOp firstPloop, ParallelOp secondPloop)
Verify equal iteration spaces.
static bool isLoadOnWrittenVector(memref::LoadOp loadOp, Value writeBase, ValueRange writeIndices, VectorType vecTy, ArrayRef< int64_t > vectorDimForWriteDim, const IRMapping &ivsMap)
Recognize scalar memref.load of an element produced by a vector write (vector.transfer_write or vecto...
static bool loadMatchesVectorWrite(memref::LoadOp loadOp, vector::TransferWriteOp writeOp, const IRMapping &ivsMap)
Recognize scalar memref.load of an element produced by a vector.transfer_write.
static std::optional< int64_t > getAddConstant(Value expr, Value base, const IRMapping &loopsIVsMap)
If the expr value is the result of an integer addition of base and a constant, return the constant.
static bool opsAccessSameIndices(OpTy1 op1, OpTy2 op2, const IRMapping &loopsIVsMap, OpBuilder &b)
Check if both memory read/write operations access the same indices (considering also the mapping of i...
static Value getStoreOpTargetBuffer(Operation *op)
static void applyLoopFusion(ParallelOp &firstPloop, ParallelOp &secondPloop, OpBuilder &builder)
Prepend operations of firstPloop's body into secondPloop's body.
static bool haveNoDataDependenciesExceptSameIndex(ParallelOp firstPloop, ParallelOp secondPloop, const IRMapping &firstToSecondPloopIndices, llvm::function_ref< bool(Value, Value)> mayAlias, OpBuilder &b)
Check that the parallel loops have no mixed access to the same buffers.
static Value getBaseMemref(Operation *op)
Return the base memref value used by the given memory op.
static bool loadsFromSameMemoryLocationWrittenBy(Operation *loadOp, Operation *storeOp, const IRMapping &firstToSecondPloopIVsMap, OpBuilder &b)
Check if the loadOp reads from the same memory location (same buffer, same indices and same propertie...
static SmallVector< SmallVector< int64_t > > computeCandidateInterchangePermutations(ParallelOp &firstPloop, ParallelOp &secondPloop, int permBudget=120)
static bool loadIndexWithinWriteRange(Value loadIndex, OpFoldResult offset, Value writeIndex, int64_t extent, const IRMapping &loopsIVsMap)
static bool opsWriteSameMemLocation(Operation *op1, Operation *op2)
Check if both operations are the same type of memory write op and write to the same memory location (...
static bool noIncompatibleDataDependencies(ParallelOp firstPloop, ParallelOp secondPloop, const IRMapping &firstToSecondPloopIndices, llvm::function_ref< bool(Value, Value)> mayAlias, OpBuilder &b)
Check that in each loop there are no read ops on the buffers written by the other loop,...
static bool valsAreEquivalent(Value val1, Value val2, const IRMapping &loopsIVsMap)
Check if val1 (from the first parallel loop) and val2 (from the second) are equivalent,...
static bool isFusionLegal(ParallelOp firstPloop, ParallelOp secondPloop, const IRMapping &firstToSecondPloopIndices, llvm::function_ref< bool(Value, Value)> mayAlias, OpBuilder &b)
Check if fusion of the two parallel loops is legal: i.e.
static bool opsAccessSameIndicesViaRankReducingSubview(OpTy1 op1, OpTy2 op2, const IRMapping &firstToSecondPloopIVsMap, OpBuilder &b)
Check if both operations access the same positions of the same buffer, but one of the two does it thr...
static bool loadMatchesVectorStore(memref::LoadOp loadOp, vector::StoreOp storeOp, const IRMapping &ivsMap)
Recognize scalar memref.load of an element produced by a vector.store.
static bool hasNestedParallelOp(ParallelOp ploop)
Verify there are no nested ParallelOps.
static void fuseIfLegal(ParallelOp firstPloop, ParallelOp &secondPloop, OpBuilder builder, llvm::function_ref< bool(Value, Value)> mayAlias)
Check fusion pre-conditions and call fusion if it is possible.
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumSymbols() const
unsigned getNumDims() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
Block represents an ordered list of Operations.
Definition Block.h:33
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgListType getArguments()
Definition Block.h:111
iterator begin()
Definition Block.h:167
A class for computing basic dominance information.
Definition Dominance.h:143
bool properlyDominates(Operation *a, Operation *b, bool enclosingOpOk=true) const
Return true if operation A properly dominates operation B, i.e.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:65
void clear()
Clears all mappings held by the mapper.
Definition IRMapping.h:79
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
static OpBuilder atBlockBegin(Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to before the first operation in the block but still ins...
Definition Builders.h:243
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
This class represents a single result from folding an operation.
This trait indicates that the memory effects of an operation includes the effects of operations neste...
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
user_range getUsers()
Returns a range of all users.
Definition Operation.h:925
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
MemrefValue skipFullyAliasingOperations(MemrefValue source)
Walk up the source chain until an operation that changes/defines the view of memory is found (i....
bool isSameViewOrTrivialAlias(MemrefValue a, MemrefValue b)
Checks if two (memref) values are the same or statically known to alias the same region of memory.
void naivelyFuseParallelOps(Region &region, llvm::function_ref< bool(Value, Value)> mayAlias)
Fuses all adjacent scf.parallel operations with identical bounds and step into one scf....
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
SmallVector< T > applyPermutation(ArrayRef< T > input, ArrayRef< int64_t > permutation)
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
llvm::SmallVector< std::tuple< int64_t, int64_t, int64_t > > getConstLoopBounds(mlir::LoopLikeOpInterface loopOp)
Get constant loop bounds and steps for each of the induction variables of the given loop operation,...
Definition Utils.cpp:1659
detail::constant_int_predicate_matcher m_Zero()
Matches a constant scalar / vector splat / tensor splat integer zero.
Definition Matchers.h:442
TypedValue< BaseMemRefType > MemrefValue
A value with a memref type.
Definition MemRefUtils.h:26
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
std::unique_ptr< Pass > createParallelLoopFusionPass()
Creates a loop fusion pass which fuses parallel loops.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
bool operator==(LoopIV const &other) const
bool operator!=(LoopIV const &other) const
static bool isEqual(const LoopIV &lhs, const LoopIV &rhs)
static unsigned getHashValue(const LoopIV &val)
The following effect indicates that the operation frees some resource that has been allocated.
The following effect indicates that the operation reads from some resource.
The following effect indicates that the operation writes to some resource.
static bool isEquivalentTo(Operation *lhs, Operation *rhs, function_ref< LogicalResult(Value, Value)> checkEquivalent, function_ref< void(Value, Value)> markEquivalent=nullptr, Flags flags=Flags::None, function_ref< LogicalResult(ValueRange, ValueRange)> checkCommutativeEquivalent=nullptr)
Compare two operations (including their regions) and return if they are equivalent.