MLIR 24.0.0git
SparseVectorization.cpp
Go to the documentation of this file.
1//===- SparseVectorization.cpp - Vectorization of sparsified loops --------===//
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// A pass that converts loops generated by the sparsifier into a form that
10// can exploit SIMD instructions of the target architecture. Note that this pass
11// ensures the sparsifier can generate efficient SIMD (including ArmSVE
12// support) with proper separation of concerns as far as sparsification and
13// vectorization is concerned. However, this pass is not the final abstraction
14// level we want, and not the general vectorizer we want either. It forms a good
15// stepping stone for incremental future improvements though.
16//
17//===----------------------------------------------------------------------===//
18
19#include "Utils/CodegenUtils.h"
20#include "Utils/LoopEmitter.h"
21
30#include "mlir/IR/Matchers.h"
31
32using namespace mlir;
33using namespace mlir::sparse_tensor;
34
35namespace {
36
37/// Target SIMD properties:
38/// vectorLength: # packed data elements (viz. vector<16xf32> has length 16)
39/// enableVLAVectorization: enables scalable vectors (viz. ARMSve)
40/// enableSIMDIndex32: uses 32-bit indices in gather/scatter for efficiency
41struct VL {
42 unsigned vectorLength;
43 bool enableVLAVectorization;
44 bool enableSIMDIndex32;
45};
46
47/// Helper test for invariant value (defined outside given block).
48static bool isInvariantValue(Value val, Block *block) {
49 return val.getDefiningOp() && val.getDefiningOp()->getBlock() != block;
50}
51
52/// Helper test for invariant argument (defined outside given block).
53static bool isInvariantArg(BlockArgument arg, Block *block) {
54 return arg.getOwner() != block;
55}
56
57/// Returns true when `mem`'s most minor dimension has a statically known
58/// non-unit stride.
59///
60/// `genVectorLoad/genVectorStore` assume a contiguous
61/// `vector.maskedload/vector.maskedstore` is safe for consecutive loop
62/// indices, which breaks for a strided view extracting one component out
63/// of an interleaved (AoS) COO coordinate buffer.
64///
65/// Example:
66/// A `compressed(nonunique) + singleton` region stores coordinates as
67/// `[row0, col0, row1, col1, ...]`, so a 2-lane masked load of `col[0:2]`
68/// (offset=1) would read physical offsets {1, 2} = `[col0, row1]` instead
69/// of the intended {1, 3} = `[col0, col1]`: a silent miscompile.
70///
71/// NOTE: A stride that can't be proven non-unit by either means is assumed
72/// safe.
73static bool hasKnownNonUnitStride(Value mem) {
74 // sparse_tensor.coordinates isn't lowered to a concrete strided memref
75 // until sparse-tensor-codegen runs, so at this point its type
76 // still has a dynamic stride even when the true stride is already known
77 // from the tensor's encoding -- hence the special case below instead of
78 // trusting the memref type.
79 if (auto toCoords = mem.getDefiningOp<ToCoordinatesOp>()) {
80 SparseTensorType stt = getSparseTensorType(toCoords.getTensor());
81 Level cooStart = stt.getAoSCOOStart();
82 // A single trailing level (lvlRank - cooStart == 1) is not actually
83 // interleaved with anything else, so it degenerates to a contiguous
84 // buffer.
85 if (toCoords.getLevel() >= cooStart)
86 return stt.getLvlRank() - cooStart != 1;
87 return false;
88 }
89
90 auto memTp = dyn_cast<MemRefType>(mem.getType());
91 if (!memTp)
92 return false;
94 int64_t offset;
95 if (failed(memTp.getStridesAndOffset(strides, offset)))
96 return false;
97 return !strides.empty() && !ShapedType::isDynamic(strides.back()) &&
98 strides.back() != 1;
99}
100
101/// Constructs vector type for element type.
102static VectorType vectorType(VL vl, Type etp) {
103 return VectorType::get(vl.vectorLength, etp, vl.enableVLAVectorization);
104}
105
106/// Constructs vector type from a memref value.
107static VectorType vectorType(VL vl, Value mem) {
108 return vectorType(vl, getMemRefType(mem).getElementType());
109}
110
111/// Constructs vector iteration mask.
112static Value genVectorMask(PatternRewriter &rewriter, Location loc, VL vl,
113 Value iv, Value lo, Value hi, Value step) {
114 VectorType mtp = vectorType(vl, rewriter.getI1Type());
115 // Special case if the vector length evenly divides the trip count (for
116 // example, "for i = 0, 128, 16"). A constant all-true mask is generated
117 // so that all subsequent masked memory operations are immediately folded
118 // into unconditional memory operations.
119 IntegerAttr loInt, hiInt, stepInt;
120 if (matchPattern(lo, m_Constant(&loInt)) &&
121 matchPattern(hi, m_Constant(&hiInt)) &&
122 matchPattern(step, m_Constant(&stepInt))) {
123 if (((hiInt.getInt() - loInt.getInt()) % stepInt.getInt()) == 0) {
124 Value trueVal = constantI1(rewriter, loc, true);
125 return vector::BroadcastOp::create(rewriter, loc, mtp, trueVal);
126 }
127 }
128 // Otherwise, generate a vector mask that avoids overrunning the upperbound
129 // during vector execution. Here we rely on subsequent loop optimizations to
130 // avoid executing the mask in all iterations, for example, by splitting the
131 // loop into an unconditional vector loop and a scalar cleanup loop.
132 auto min = AffineMap::get(
133 /*dimCount=*/2, /*symbolCount=*/1,
134 {rewriter.getAffineSymbolExpr(0),
135 rewriter.getAffineDimExpr(0) - rewriter.getAffineDimExpr(1)},
136 rewriter.getContext());
137 Value end = rewriter.createOrFold<affine::AffineMinOp>(
138 loc, min, ValueRange{hi, iv, step});
139 return vector::CreateMaskOp::create(rewriter, loc, mtp, end);
140}
141
142/// Generates a vectorized invariant. Here we rely on subsequent loop
143/// optimizations to hoist the invariant broadcast out of the vector loop.
144static Value genVectorInvariantValue(PatternRewriter &rewriter, VL vl,
145 Value val) {
146 VectorType vtp = vectorType(vl, val.getType());
147 return vector::BroadcastOp::create(rewriter, val.getLoc(), vtp, val);
148}
149
150/// Generates a vectorized load lhs = a[ind[lo:hi]] or lhs = a[lo:hi],
151/// where 'lo' denotes the current index and 'hi = lo + vl - 1'. Note
152/// that the sparsifier can only generate indirect loads in
153/// the last index, i.e. back().
154static Value genVectorLoad(PatternRewriter &rewriter, Location loc, VL vl,
155 Value mem, ArrayRef<Value> idxs, Value vmask) {
156 VectorType vtp = vectorType(vl, mem);
157 Value pass = constantZero(rewriter, loc, vtp);
158 if (llvm::isa<VectorType>(idxs.back().getType())) {
159 SmallVector<Value> scalarArgs(idxs);
160 Value indexVec = idxs.back();
161 scalarArgs.back() = constantIndex(rewriter, loc, 0);
162 return vector::GatherOp::create(rewriter, loc, vtp, mem, scalarArgs,
163 indexVec, vmask, pass);
164 }
165 return vector::MaskedLoadOp::create(rewriter, loc, vtp, mem, idxs, vmask,
166 pass);
167}
168
169/// Generates a vectorized store a[ind[lo:hi]] = rhs or a[lo:hi] = rhs
170/// where 'lo' denotes the current index and 'hi = lo + vl - 1'. Note
171/// that the sparsifier can only generate indirect stores in
172/// the last index, i.e. back().
173static void genVectorStore(PatternRewriter &rewriter, Location loc, Value mem,
174 ArrayRef<Value> idxs, Value vmask, Value rhs) {
175 if (llvm::isa<VectorType>(idxs.back().getType())) {
176 SmallVector<Value> scalarArgs(idxs);
177 Value indexVec = idxs.back();
178 scalarArgs.back() = constantIndex(rewriter, loc, 0);
179 vector::ScatterOp::create(rewriter, loc, /*resultType=*/nullptr, mem,
180 scalarArgs, indexVec, vmask, rhs);
181 return;
182 }
183 vector::MaskedStoreOp::create(rewriter, loc, mem, idxs, vmask, rhs);
184}
185
186/// Detects a vectorizable reduction operations and returns the
187/// combining kind of reduction on success in `kind`.
188static bool isVectorizableReduction(Value red, Value iter,
189 vector::CombiningKind &kind) {
190 if (auto addf = red.getDefiningOp<arith::AddFOp>()) {
191 kind = vector::CombiningKind::ADD;
192 return addf->getOperand(0) == iter || addf->getOperand(1) == iter;
193 }
194 if (auto addi = red.getDefiningOp<arith::AddIOp>()) {
195 kind = vector::CombiningKind::ADD;
196 return addi->getOperand(0) == iter || addi->getOperand(1) == iter;
197 }
198 if (auto subf = red.getDefiningOp<arith::SubFOp>()) {
199 kind = vector::CombiningKind::ADD;
200 return subf->getOperand(0) == iter;
201 }
202 if (auto subi = red.getDefiningOp<arith::SubIOp>()) {
203 kind = vector::CombiningKind::ADD;
204 return subi->getOperand(0) == iter;
205 }
206 if (auto mulf = red.getDefiningOp<arith::MulFOp>()) {
207 kind = vector::CombiningKind::MUL;
208 return mulf->getOperand(0) == iter || mulf->getOperand(1) == iter;
209 }
210 if (auto muli = red.getDefiningOp<arith::MulIOp>()) {
211 kind = vector::CombiningKind::MUL;
212 return muli->getOperand(0) == iter || muli->getOperand(1) == iter;
213 }
214 if (auto andi = red.getDefiningOp<arith::AndIOp>()) {
215 kind = vector::CombiningKind::AND;
216 return andi->getOperand(0) == iter || andi->getOperand(1) == iter;
217 }
218 if (auto ori = red.getDefiningOp<arith::OrIOp>()) {
219 kind = vector::CombiningKind::OR;
220 return ori->getOperand(0) == iter || ori->getOperand(1) == iter;
221 }
222 if (auto xori = red.getDefiningOp<arith::XOrIOp>()) {
223 kind = vector::CombiningKind::XOR;
224 return xori->getOperand(0) == iter || xori->getOperand(1) == iter;
225 }
226 return false;
227}
228
229/// Generates an initial value for a vector reduction, following the scheme
230/// given in Chapter 5 of "The Software Vectorization Handbook", where the
231/// initial scalar value is correctly embedded in the vector reduction value,
232/// and a straightforward horizontal reduction will complete the operation.
233/// Value 'r' denotes the initial value of the reduction outside the loop.
234static Value genVectorReducInit(PatternRewriter &rewriter, Location loc,
235 Value red, Value iter, Value r,
236 VectorType vtp) {
237 vector::CombiningKind kind;
238 if (!isVectorizableReduction(red, iter, kind))
239 llvm_unreachable("unknown reduction");
240 switch (kind) {
241 case vector::CombiningKind::ADD:
242 case vector::CombiningKind::XOR:
243 // Initialize reduction vector to: | 0 | .. | 0 | r |
244 return vector::InsertOp::create(rewriter, loc, r,
245 constantZero(rewriter, loc, vtp),
246 constantIndex(rewriter, loc, 0));
247 case vector::CombiningKind::MUL:
248 // Initialize reduction vector to: | 1 | .. | 1 | r |
249 return vector::InsertOp::create(rewriter, loc, r,
250 constantOne(rewriter, loc, vtp),
251 constantIndex(rewriter, loc, 0));
252 case vector::CombiningKind::AND:
253 case vector::CombiningKind::OR:
254 // Initialize reduction vector to: | r | .. | r | r |
255 return vector::BroadcastOp::create(rewriter, loc, vtp, r);
256 default:
257 break;
258 }
259 llvm_unreachable("unknown reduction kind");
260}
261
262/// This method is called twice to analyze and rewrite the given subscripts.
263/// The first call (!codegen) does the analysis. Then, on success, the second
264/// call (codegen) yields the proper vector form in the output parameter
265/// vector 'idxs'. This mechanism ensures that analysis and rewriting code
266/// stay in sync. Note that the analyis part is simple because the sparsifier
267/// only generates relatively simple subscript expressions.
268///
269/// See https://llvm.org/docs/GetElementPtr.html for some background on
270/// the complications described below.
271///
272/// We need to generate a position/coordinate load from the sparse storage
273/// scheme. Narrower data types need to be zero extended before casting
274/// the value into the `index` type used for looping and indexing.
275///
276/// For the scalar case, subscripts simply zero extend narrower indices
277/// into 64-bit values before casting to an index type without a performance
278/// penalty. Indices that already are 64-bit, in theory, cannot express the
279/// full range since the LLVM backend defines addressing in terms of an
280/// unsigned pointer/signed index pair.
281static bool vectorizeSubscripts(PatternRewriter &rewriter, scf::ForOp forOp,
282 VL vl, ValueRange subs, bool codegen,
283 Value vmask, SmallVectorImpl<Value> &idxs) {
284 unsigned d = 0;
285 unsigned dim = subs.size();
286 Block *block = &forOp.getRegion().front();
287 for (auto sub : subs) {
288 bool innermost = ++d == dim;
289 // Invariant subscripts in outer dimensions simply pass through.
290 // Note that we rely on LICM to hoist loads where all subscripts
291 // are invariant in the innermost loop.
292 // Example:
293 // a[inv][i] for inv
294 if (isInvariantValue(sub, block)) {
295 if (innermost)
296 return false;
297 if (codegen)
298 idxs.push_back(sub);
299 continue; // success so far
300 }
301 // Invariant block arguments (including outer loop indices) in outer
302 // dimensions simply pass through. Direct loop indices in the
303 // innermost loop simply pass through as well.
304 // Example:
305 // a[i][j] for both i and j
306 if (auto arg = llvm::dyn_cast<BlockArgument>(sub)) {
307 if (isInvariantArg(arg, block) == innermost)
308 return false;
309 if (codegen)
310 idxs.push_back(sub);
311 continue; // success so far
312 }
313 // Look under the hood of casting.
314 auto cast = sub;
315 while (true) {
316 if (auto icast = cast.getDefiningOp<arith::IndexCastOp>())
317 cast = icast->getOperand(0);
318 else if (auto ecast = cast.getDefiningOp<arith::ExtUIOp>())
319 cast = ecast->getOperand(0);
320 else
321 break;
322 }
323 // Since the index vector is used in a subsequent gather/scatter
324 // operations, which effectively defines an unsigned pointer + signed
325 // index, we must zero extend the vector to an index width. For 8-bit
326 // and 16-bit values, an 32-bit index width suffices. For 32-bit values,
327 // zero extending the elements into 64-bit loses some performance since
328 // the 32-bit indexed gather/scatter is more efficient than the 64-bit
329 // index variant (if the negative 32-bit index space is unused, the
330 // enableSIMDIndex32 flag can preserve this performance). For 64-bit
331 // values, there is no good way to state that the indices are unsigned,
332 // which creates the potential of incorrect address calculations in the
333 // unlikely case we need such extremely large offsets.
334 // Example:
335 // a[ ind[i] ]
336 if (auto load = cast.getDefiningOp<memref::LoadOp>()) {
337 if (!innermost)
338 return false;
339 if (hasKnownNonUnitStride(load.getMemRef()))
340 return false;
341 if (codegen) {
342 SmallVector<Value> idxs2(load.getIndices()); // no need to analyze
343 Location loc = forOp.getLoc();
344 Value vload =
345 genVectorLoad(rewriter, loc, vl, load.getMemRef(), idxs2, vmask);
346 Type etp = llvm::cast<VectorType>(vload.getType()).getElementType();
347 if (!llvm::isa<IndexType>(etp)) {
348 if (etp.getIntOrFloatBitWidth() < 32)
349 vload = arith::ExtUIOp::create(
350 rewriter, loc, vectorType(vl, rewriter.getI32Type()), vload);
351 else if (etp.getIntOrFloatBitWidth() < 64 && !vl.enableSIMDIndex32)
352 vload = arith::ExtUIOp::create(
353 rewriter, loc, vectorType(vl, rewriter.getI64Type()), vload);
354 }
355 idxs.push_back(vload);
356 }
357 continue; // success so far
358 }
359 // Address calculation 'i = add inv, idx' (after LICM).
360 // Example:
361 // a[base + i]
362 if (auto load = cast.getDefiningOp<arith::AddIOp>()) {
363 Value inv = load.getOperand(0);
364 Value idx = load.getOperand(1);
365 // Swap non-invariant.
366 if (!isInvariantValue(inv, block)) {
367 inv = idx;
368 idx = load.getOperand(0);
369 }
370 // Inspect.
371 if (isInvariantValue(inv, block)) {
372 if (auto arg = llvm::dyn_cast<BlockArgument>(idx)) {
373 if (isInvariantArg(arg, block) || !innermost)
374 return false;
375 if (codegen)
376 idxs.push_back(
377 arith::AddIOp::create(rewriter, forOp.getLoc(), inv, idx));
378 continue; // success so far
379 }
380 }
381 }
382 return false;
383 }
384 return true;
385}
386
387#define UNAOP(xxx) \
388 if (isa<xxx>(def)) { \
389 if (codegen) \
390 vexp = xxx::create(rewriter, loc, vx); \
391 return true; \
392 }
393
394#define TYPEDUNAOP(xxx) \
395 if (auto x = dyn_cast<xxx>(def)) { \
396 if (codegen) { \
397 VectorType vtp = vectorType(vl, x.getType()); \
398 vexp = xxx::create(rewriter, loc, vtp, vx); \
399 } \
400 return true; \
401 }
402
403#define BINOP(xxx) \
404 if (isa<xxx>(def)) { \
405 if (codegen) \
406 vexp = xxx::create(rewriter, loc, vx, vy); \
407 return true; \
408 }
409
410/// This method is called twice to analyze and rewrite the given expression.
411/// The first call (!codegen) does the analysis. Then, on success, the second
412/// call (codegen) yields the proper vector form in the output parameter 'vexp'.
413/// This mechanism ensures that analysis and rewriting code stay in sync. Note
414/// that the analyis part is simple because the sparsifier only generates
415/// relatively simple expressions inside the for-loops.
416static bool vectorizeExpr(PatternRewriter &rewriter, scf::ForOp forOp, VL vl,
417 Value exp, bool codegen, Value vmask, Value &vexp) {
418 Location loc = forOp.getLoc();
419 // Reject unsupported types.
420 if (!VectorType::isValidElementType(exp.getType()))
421 return false;
422 // A block argument is invariant/reduction/index.
423 if (auto arg = llvm::dyn_cast<BlockArgument>(exp)) {
424 if (arg == forOp.getInductionVar()) {
425 // We encountered a single, innermost index inside the computation,
426 // such as a[i] = i, which must convert to [i, i+1, ...].
427 if (codegen) {
428 VectorType vtp = vectorType(vl, arg.getType());
429 Value veci = vector::BroadcastOp::create(rewriter, loc, vtp, arg);
430 Value incr = vector::StepOp::create(rewriter, loc, vtp);
431 vexp = arith::AddIOp::create(rewriter, loc, veci, incr);
432 }
433 return true;
434 }
435 // An invariant or reduction. In both cases, we treat this as an
436 // invariant value, and rely on later replacing and folding to
437 // construct a proper reduction chain for the latter case.
438 if (codegen)
439 vexp = genVectorInvariantValue(rewriter, vl, exp);
440 return true;
441 }
442 // Something defined outside the loop-body is invariant.
443 Operation *def = exp.getDefiningOp();
444 Block *block = &forOp.getRegion().front();
445 if (def->getBlock() != block) {
446 if (codegen)
447 vexp = genVectorInvariantValue(rewriter, vl, exp);
448 return true;
449 }
450 // Proper load operations. These are either values involved in the
451 // actual computation, such as a[i] = b[i] becomes a[lo:hi] = b[lo:hi],
452 // or coordinate values inside the computation that are now fetched from
453 // the sparse storage coordinates arrays, such as a[i] = i becomes
454 // a[lo:hi] = ind[lo:hi], where 'lo' denotes the current index
455 // and 'hi = lo + vl - 1'.
456 if (auto load = dyn_cast<memref::LoadOp>(def)) {
457 if (hasKnownNonUnitStride(load.getMemRef()))
458 return false;
459 auto subs = load.getIndices();
461 if (vectorizeSubscripts(rewriter, forOp, vl, subs, codegen, vmask, idxs)) {
462 if (codegen)
463 vexp = genVectorLoad(rewriter, loc, vl, load.getMemRef(), idxs, vmask);
464 return true;
465 }
466 return false;
467 }
468 // Inside loop-body unary and binary operations. Note that it would be
469 // nicer if we could somehow test and build the operations in a more
470 // concise manner than just listing them all (although this way we know
471 // for certain that they can vectorize).
472 //
473 // TODO: avoid visiting CSEs multiple times
474 //
475 if (def->getNumOperands() == 1) {
476 Value vx;
477 if (vectorizeExpr(rewriter, forOp, vl, def->getOperand(0), codegen, vmask,
478 vx)) {
479 UNAOP(math::AbsFOp)
480 UNAOP(math::AbsIOp)
481 UNAOP(math::CeilOp)
482 UNAOP(math::FloorOp)
483 UNAOP(math::SqrtOp)
484 UNAOP(math::ExpM1Op)
485 UNAOP(math::Log1pOp)
486 UNAOP(math::SinOp)
487 UNAOP(math::TanhOp)
488 UNAOP(arith::NegFOp)
489 TYPEDUNAOP(arith::TruncFOp)
490 TYPEDUNAOP(arith::ExtFOp)
491 TYPEDUNAOP(arith::FPToSIOp)
492 TYPEDUNAOP(arith::FPToUIOp)
493 TYPEDUNAOP(arith::SIToFPOp)
494 TYPEDUNAOP(arith::UIToFPOp)
495 TYPEDUNAOP(arith::ExtSIOp)
496 TYPEDUNAOP(arith::ExtUIOp)
497 TYPEDUNAOP(arith::IndexCastOp)
498 TYPEDUNAOP(arith::TruncIOp)
499 TYPEDUNAOP(arith::BitcastOp)
500 // TODO: complex?
501 }
502 } else if (def->getNumOperands() == 2) {
503 Value vx, vy;
504 if (vectorizeExpr(rewriter, forOp, vl, def->getOperand(0), codegen, vmask,
505 vx) &&
506 vectorizeExpr(rewriter, forOp, vl, def->getOperand(1), codegen, vmask,
507 vy)) {
508 // We only accept shift-by-invariant (where the same shift factor applies
509 // to all packed elements). In the vector dialect, this is still
510 // represented with an expanded vector at the right-hand-side, however,
511 // so that we do not have to special case the code generation.
512 if (isa<arith::ShLIOp>(def) || isa<arith::ShRUIOp>(def) ||
513 isa<arith::ShRSIOp>(def)) {
514 Value shiftFactor = def->getOperand(1);
515 if (!isInvariantValue(shiftFactor, block))
516 return false;
517 }
518 // Generate code.
519 BINOP(arith::MulFOp)
520 BINOP(arith::MulIOp)
521 BINOP(arith::DivFOp)
522 BINOP(arith::DivSIOp)
523 BINOP(arith::DivUIOp)
524 BINOP(arith::AddFOp)
525 BINOP(arith::AddIOp)
526 BINOP(arith::SubFOp)
527 BINOP(arith::SubIOp)
528 BINOP(arith::AndIOp)
529 BINOP(arith::OrIOp)
530 BINOP(arith::XOrIOp)
531 BINOP(arith::ShLIOp)
532 BINOP(arith::ShRUIOp)
533 BINOP(arith::ShRSIOp)
534 // TODO: complex?
535 }
536 }
537 return false;
538}
539
540#undef UNAOP
541#undef TYPEDUNAOP
542#undef BINOP
543
544/// This method is called twice to analyze and rewrite the given for-loop.
545/// The first call (!codegen) does the analysis. Then, on success, the second
546/// call (codegen) rewriters the IR into vector form. This mechanism ensures
547/// that analysis and rewriting code stay in sync.
548static bool vectorizeStmt(PatternRewriter &rewriter, scf::ForOp forOp, VL vl,
549 bool codegen) {
550 Block &block = forOp.getRegion().front();
551 // For loops with single yield statement (as below) could be generated
552 // when custom reduce is used with unary operation.
553 // for (...)
554 // yield c_0
555 if (block.getOperations().size() <= 1)
556 return false;
557
558 Location loc = forOp.getLoc();
559 scf::YieldOp yield = cast<scf::YieldOp>(block.getTerminator());
560 auto &last = *++block.rbegin();
561 scf::ForOp forOpNew;
562
563 // Perform initial set up during codegen (we know that the first analysis
564 // pass was successful). For reductions, we need to construct a completely
565 // new for-loop, since the incoming and outgoing reduction type
566 // changes into SIMD form. For stores, we can simply adjust the stride
567 // and insert in the existing for-loop. In both cases, we set up a vector
568 // mask for all operations which takes care of confining vectors to
569 // the original iteration space (later cleanup loops or other
570 // optimizations can take care of those).
571 Value vmask;
572 if (codegen) {
573 Value step = constantIndex(rewriter, loc, vl.vectorLength);
574 if (vl.enableVLAVectorization) {
575 Value vscale =
576 vector::VectorScaleOp::create(rewriter, loc, rewriter.getIndexType());
577 step = arith::MulIOp::create(rewriter, loc, vscale, step);
578 }
579 if (!yield.getResults().empty()) {
580 Value init = forOp.getInitArgs()[0];
581 VectorType vtp = vectorType(vl, init.getType());
582 Value vinit = genVectorReducInit(rewriter, loc, yield->getOperand(0),
583 forOp.getRegionIterArg(0), init, vtp);
584 forOpNew =
585 scf::ForOp::create(rewriter, loc, forOp.getLowerBound(),
586 forOp.getUpperBound(), step, vinit,
587 /*bodyBuilder=*/nullptr, forOp.getUnsignedCmp());
588 forOpNew->setAttr(
591 rewriter.setInsertionPointToStart(forOpNew.getBody());
592 } else {
593 rewriter.modifyOpInPlace(forOp, [&]() { forOp.setStep(step); });
594 rewriter.setInsertionPoint(yield);
595 }
596 vmask = genVectorMask(rewriter, loc, vl, forOp.getInductionVar(),
597 forOp.getLowerBound(), forOp.getUpperBound(), step);
598 }
599
600 // Sparse for-loops either are terminated by a non-empty yield operation
601 // (reduction loop) or otherwise by a store operation (pararallel loop).
602 if (!yield.getResults().empty()) {
603 // Analyze/vectorize reduction.
604 if (yield->getNumOperands() != 1)
605 return false;
606 Value red = yield->getOperand(0);
607 Value iter = forOp.getRegionIterArg(0);
608 vector::CombiningKind kind;
609 Value vrhs;
610 if (isVectorizableReduction(red, iter, kind) &&
611 vectorizeExpr(rewriter, forOp, vl, red, codegen, vmask, vrhs)) {
612 if (codegen) {
613 Value partial = forOpNew.getResult(0);
614 Value vpass = genVectorInvariantValue(rewriter, vl, iter);
615 Value vred = arith::SelectOp::create(rewriter, loc, vmask, vrhs, vpass);
616 scf::YieldOp::create(rewriter, loc, vred);
617 rewriter.setInsertionPointAfter(forOpNew);
618 Value vres = vector::ReductionOp::create(rewriter, loc, kind, partial);
619 // Now do some relinking (last one is not completely type safe
620 // but all bad ones are removed right away). This also folds away
621 // nop broadcast operations.
622 rewriter.replaceAllUsesWith(forOp.getResult(0), vres);
623 rewriter.replaceAllUsesWith(forOp.getInductionVar(),
624 forOpNew.getInductionVar());
625 rewriter.replaceAllUsesWith(forOp.getRegionIterArg(0),
626 forOpNew.getRegionIterArg(0));
627 rewriter.eraseOp(forOp);
628 }
629 return true;
630 }
631 } else if (auto store = dyn_cast<memref::StoreOp>(last)) {
632 // Analyze/vectorize store operation.
633 if (hasKnownNonUnitStride(store.getMemRef()))
634 return false;
635 auto subs = store.getIndices();
637 Value rhs = store.getValue();
638 Value vrhs;
639 if (vectorizeSubscripts(rewriter, forOp, vl, subs, codegen, vmask, idxs) &&
640 vectorizeExpr(rewriter, forOp, vl, rhs, codegen, vmask, vrhs)) {
641 if (codegen) {
642 genVectorStore(rewriter, loc, store.getMemRef(), idxs, vmask, vrhs);
643 rewriter.eraseOp(store);
644 }
645 return true;
646 }
647 }
648
649 assert(!codegen && "cannot call codegen when analysis failed");
650 return false;
651}
652
653/// Basic for-loop vectorizer.
654struct ForOpRewriter : public OpRewritePattern<scf::ForOp> {
655public:
656 using OpRewritePattern<scf::ForOp>::OpRewritePattern;
657
658 ForOpRewriter(MLIRContext *context, unsigned vectorLength,
659 bool enableVLAVectorization, bool enableSIMDIndex32)
660 : OpRewritePattern(context),
661 vl{vectorLength, enableVLAVectorization, enableSIMDIndex32} {}
662
663 LogicalResult matchAndRewrite(scf::ForOp op,
664 PatternRewriter &rewriter) const override {
665 // Check for single block, unit-stride for-loop that is generated by
666 // sparsifier, which means no data dependence analysis is required,
667 // and its loop-body is very restricted in form.
668 if (!op.getRegion().hasOneBlock() || !isOneInteger(op.getStep()) ||
670 return failure();
671 // Analyze (!codegen) and rewrite (codegen) loop-body.
672 if (vectorizeStmt(rewriter, op, vl, /*codegen=*/false) &&
673 vectorizeStmt(rewriter, op, vl, /*codegen=*/true))
674 return success();
675 return failure();
676 }
677
678private:
679 const VL vl;
680};
681
682static LogicalResult cleanReducChain(PatternRewriter &rewriter, Operation *op,
683 Value inp) {
684 if (auto redOp = inp.getDefiningOp<vector::ReductionOp>()) {
685 if (auto forOp = redOp.getVector().getDefiningOp<scf::ForOp>()) {
686 if (forOp->hasAttr(LoopEmitter::getLoopEmitterLoopAttrName())) {
687 rewriter.replaceOp(op, redOp.getVector());
688 return success();
689 }
690 }
691 }
692 return failure();
693}
694
695/// Reduction chain cleanup.
696/// v = for { }
697/// s = vsum(v) v = for { }
698/// u = broadcast(s) -> for (v) { }
699/// for (u) { }
700struct ReducChainBroadcastRewriter
701 : public OpRewritePattern<vector::BroadcastOp> {
702public:
703 using OpRewritePattern<vector::BroadcastOp>::OpRewritePattern;
704
705 LogicalResult matchAndRewrite(vector::BroadcastOp op,
706 PatternRewriter &rewriter) const override {
707 return cleanReducChain(rewriter, op, op.getSource());
708 }
709};
710
711/// Reduction chain cleanup.
712/// v = for { }
713/// s = vsum(v) v = for { }
714/// u = insert(s) -> for (v) { }
715/// for (u) { }
716struct ReducChainInsertRewriter : public OpRewritePattern<vector::InsertOp> {
717public:
718 using OpRewritePattern<vector::InsertOp>::OpRewritePattern;
719
720 LogicalResult matchAndRewrite(vector::InsertOp op,
721 PatternRewriter &rewriter) const override {
722 return cleanReducChain(rewriter, op, op.getValueToStore());
723 }
724};
725} // namespace
726
727//===----------------------------------------------------------------------===//
728// Public method for populating vectorization rules.
729//===----------------------------------------------------------------------===//
730
731/// Populates the given patterns list with vectorization rules.
733 unsigned vectorLength,
734 bool enableVLAVectorization,
735 bool enableSIMDIndex32) {
736 assert(vectorLength > 0);
738 patterns.add<ForOpRewriter>(patterns.getContext(), vectorLength,
739 enableVLAVectorization, enableSIMDIndex32);
740 patterns.add<ReducChainInsertRewriter, ReducChainBroadcastRewriter>(
741 patterns.getContext());
742}
return success()
static Type getElementType(Type type)
Determine the element type of type.
auto load
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
#define UNAOP(xxx)
#define BINOP(xxx)
#define TYPEDUNAOP(xxx)
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
This class represents an argument of a Block.
Definition Value.h:306
Block * getOwner() const
Returns the block that owns this argument.
Definition Value.h:315
Block represents an ordered list of Operations.
Definition Block.h:33
OpListType & getOperations()
Definition Block.h:161
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
reverse_iterator rbegin()
Definition Block.h:169
AffineExpr getAffineSymbolExpr(unsigned position)
Definition Builders.cpp:377
IntegerType getI64Type()
Definition Builders.cpp:73
IntegerType getI32Type()
Definition Builders.cpp:71
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
IntegerType getI1Type()
Definition Builders.cpp:61
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:529
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
unsigned getNumOperands()
Definition Operation.h:371
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
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
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static constexpr llvm::StringLiteral getLoopEmitterLoopAttrName()
A wrapper around RankedTensorType, which has three goals:
Level getLvlRank() const
Returns the level-rank.
Level getAoSCOOStart() const
Returns the starting level of this sparse tensor type for a trailing COO region that spans at least t...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Value constantIndex(OpBuilder &builder, Location loc, int64_t i)
Generates a constant of index type.
Value constantZero(OpBuilder &builder, Location loc, Type tp)
Generates a 0-valued constant of the given type.
Value constantOne(OpBuilder &builder, Location loc, Type tp)
Generates a 1-valued constant of the given type.
Value constantI1(OpBuilder &builder, Location loc, bool b)
Generates a constant of i1 type.
uint64_t Level
The type of level identifiers and level-ranks.
MemRefType getMemRefType(T &&t)
Convenience method to abbreviate casting getType().
SparseTensorType getSparseTensorType(Value val)
Convenience methods to obtain a SparseTensorType from a Value.
void populateVectorStepLoweringPatterns(RewritePatternSet &patterns, unsigned indexBitwidth=64, PatternBenefit benefit=1)
Populate the pattern set with the following patterns:
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
void populateSparseVectorizationPatterns(RewritePatternSet &patterns, unsigned vectorLength, bool enableVLAVectorization, bool enableSIMDIndex32)
Populates the given patterns list with vectorization rules.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
bool isOneInteger(OpFoldResult v)
Return true if v is an IntegerAttr with value 1.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...