MLIR 24.0.0git
VectorContractMultiLevelUnroll.cpp
Go to the documentation of this file.
1//===- VectorContractMultiLevelUnroll.cpp ---------------------------------===//
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
16
20
21#define DEBUG_TYPE "x86-vector-contract-multi-level-unroll"
22
23using namespace mlir;
24
25using llvm::all_of;
26using llvm::drop_begin;
27
28// Discardable attribute to control vector unroll patterns.
29static constexpr auto nativeShapeAttrName = "x86_vcmlu_native_shape";
30
31namespace {
32// Shared state for the pattern match and IR transformation.
33struct MLUCandidate {
34 // Anchor operation.
35 vector::ContractionOp contract;
36 bool isInVnniLayout = false;
37
38 // Shape of the original contraction.
39 VectorType accType;
40 VectorType lhsType;
41 VectorType rhsType;
42 int64_t contractK;
43
44 // Shape of the register-tiled contraction. We'll insert a loop nest to cover
45 // the original shape.
46 VectorType accRegTileType;
47 VectorType lhsRegTileType;
48 VectorType rhsRegTileType;
49
50 // Shape of the native contraction (for the X86 nanokernel patterns). We'll
51 // unroll the register-tiled contraction to this shape.
52 VectorType accNativeType;
53 VectorType lhsNativeType;
54 VectorType rhsNativeType;
55 SmallVector<int64_t> contractNativeShape;
56
57 // Bookkeeping for the accumulation loop and operands.
58 scf::ForOp accLoop;
59 Value accInitVal;
60
61 bool accIsZeroInit = false;
62 vector::TransferReadOp lhsRead;
63 vector::TransferReadOp rhsRead;
64
65 // Memory backing the accumulator.
66 Value accMemIn;
67 Value accMemOut;
68
69 // Reconstructed accumulation loop(s) with native-shaped iter_args.
70 scf::ForOp slicedAccLoop;
71 scf::ForOp slicedInnerAccLoop;
72
73 // Keep track of operations that should be deleted after transformation
74 // (depending on which ops could be integrated into the new loop nest).
75 SmallVector<Operation *> opsToDelete;
76};
77
78// A target-specific function that inspects the given types and shape of a
79// candidate contraction, and tries to compute a decomposition strategy encoded
80// as the desired register-tiled and native shapes. If successful, the result is
81// filled into the `contractRegTileShape` and `contractNativeShape` containers.
82using MatchTypesAndShapeFn = std::function<LogicalResult(
83 Type inputElemType, Type accumulatorElemType, bool isInVnniLayout,
84 ArrayRef<int64_t> contractShape,
85 SmallVectorImpl<int64_t> &contractRegTileShape,
86 SmallVectorImpl<int64_t> &contractNativeShape)>;
87} // anonymous namespace
88
89//----------------------------------------------------------------------------//
90// Forward declarations //
91//----------------------------------------------------------------------------//
92
93static void buildMNLoopBody(MLUCandidate &candidate, OpBuilder &b, Location loc,
94 Value ivM, Value ivN);
95static SmallVector<Value> buildKLLoopBody(MLUCandidate &candidate, OpBuilder &b,
96 Location loc, Value ivM, Value ivN,
97 Value ivK, Value ivL,
98 ValueRange iterArgs);
99
100//----------------------------------------------------------------------------//
101// Utilities //
102//----------------------------------------------------------------------------//
103
104// Return true if the argument is an all-zero vector.
105static bool isZeroVector(Value v) {
106 auto constOp = v.getDefiningOp<arith::ConstantOp>();
107 if (!constOp)
108 return false;
109 auto denseAttr = dyn_cast<DenseElementsAttr>(constOp.getValue());
110 if (!denseAttr || !denseAttr.isSplat())
111 return false;
112
113 return isZeroIntegerOrFloat(denseAttr.getSplatValue<Attribute>());
114}
115
116// Return true if given vector.transfer_read/write has a default permutation
117// map, no mask and no out-of-bounds check.
118static bool isSimpleTransferOp(VectorTransferOpInterface op) {
119 return op.getPermutationMap().isMinorIdentity() && !op.getMask() &&
120 !op.hasOutOfBoundsDim();
121}
122
123// Return true if start and end are in the same block and there are no ops with
124// memory effects between them.
125static bool noMemoryEffectsBetween(Operation *start, Operation *end) {
126 if (start->getBlock() != end->getBlock() || !start->isBeforeInBlock(end))
127 return false;
128
129 for (Operation *op = start->getNextNode(); op && op != end;
130 op = op->getNextNode()) {
131 if (!isMemoryEffectFree(op))
132 return false;
133 }
134 return true;
135}
136
137// Create a rank-reducing subview of a memref that matches the target vector
138// type, e.g. with targetType = vector<64x32xbf16>:
139//
140// memref.subview %A[%block_m, %block_k, 0, 0] [1, 1, 64, 32] [1, 1, 1, 1]
141// : memref<?x?x64x32xbf16> to
142// memref<64x32xbf16, strided<[32, 1], offset: ?>>
145 VectorType targetType) {
146 auto *ctx = b.getContext();
147
148 auto baseType = dyn_cast<MemRefType>(base.getType());
149 assert(baseType && "base must be a MemRefType");
150
151 int64_t baseRank = baseType.getRank();
152 int64_t targetRank = targetType.getRank();
153 assert(targetRank <= baseRank && "target shape's rank > memref's rank");
154
155 ArrayRef<int64_t> targetShape = targetType.getShape();
156
157 SmallVector<int64_t> sizesInt(baseType.getRank(), 1);
158 copy(targetShape, sizesInt.begin() + baseRank - targetRank);
159 SmallVector<int64_t> stridesInt(baseRank, 1);
160
161 auto offsets = getAsOpFoldResult(indices);
162 auto sizes = getAsIndexOpFoldResult(ctx, sizesInt);
163 auto strides = getAsIndexOpFoldResult(ctx, stridesInt);
164
165 MemRefType rankReducedType = memref::SubViewOp::inferRankReducedResultType(
166 targetShape, baseType, offsets, sizes, strides);
167 return memref::SubViewOp::create(b, loc, rankReducedType, base, offsets,
168 sizes, strides);
169}
170
171// Slice a 2D vector into smaller tiles of the given native type.
173 VectorType nativeType) {
174 VectorType regTileType = dyn_cast<VectorType>(value.getType());
175 assert(regTileType && "can only slice vector values");
176 ArrayRef<int64_t> regTileShape = regTileType.getShape();
177 ArrayRef<int64_t> nativeShape = nativeType.getShape();
178
179 assert(regTileShape.size() == 2 && nativeShape.size() == 2 &&
180 "Only 2D shapes are supported");
181
182 int64_t regTileM = regTileShape[0];
183 int64_t regTileN = regTileShape[1];
184 int64_t nativeM = nativeShape[0];
185 int64_t nativeN = nativeShape[1];
186
188 result.reserve((regTileM / nativeM) * (regTileN / nativeN));
189
190 for (int64_t m = 0; m < regTileM; m += nativeM)
191 for (int64_t n = 0; n < regTileN; n += nativeN)
192 result.push_back(vector::ExtractStridedSliceOp::create(
193 b, loc, value, ArrayRef<int64_t>{m, n}, nativeShape,
194 ArrayRef<int64_t>{1, 1}));
195
196 return result;
197}
198
199// Splice smaller tiles back into a 2D vector of the given register tile type.
201 VectorType regTileType) {
202 auto types = values.getTypes();
203 assert(!types.empty() && all_equal(types) &&
204 "All values must have the same type");
205
206 VectorType nativeType = dyn_cast<VectorType>(types.front());
207 assert(nativeType && "can only splice vector values");
208
209 ArrayRef<int64_t> nativeShape = nativeType.getShape();
210 ArrayRef<int64_t> regTileShape = regTileType.getShape();
211
212 assert(nativeShape.size() == 2 && regTileShape.size() == 2 &&
213 "Only 2D shapes are supported");
214
215 int64_t nativeM = nativeShape[0];
216 int64_t nativeN = nativeShape[1];
217 int64_t regTileM = regTileShape[0];
218 int64_t regTileN = regTileShape[1];
219
220 assert((regTileM / nativeM) * (regTileN / nativeN) ==
221 static_cast<int64_t>(values.size()) &&
222 "The number of values must match the number of slices");
223
224 Value result = ub::PoisonOp::create(b, loc, regTileType);
225
226 auto valueIt = values.begin();
227 for (int64_t m = 0; m < regTileM; m += nativeM)
228 for (int64_t n = 0; n < regTileN; n += nativeN) {
229 result = vector::InsertStridedSliceOp::create(b, loc, *valueIt, result,
230 ArrayRef<int64_t>{m, n},
231 ArrayRef<int64_t>{1, 1});
232 ++valueIt;
233 }
234
235 return result;
236}
237
238static std::optional<SmallVector<int64_t>>
241 if (!attr)
242 return std::nullopt;
243 return SmallVector<int64_t>(attr.asArrayRef());
244}
245
246static void setNativeShapeAttr(VectorTransferOpInterface op,
247 VectorType nativeType) {
248 if (op.getVectorType() != nativeType)
249 op->setAttr(
251 DenseI64ArrayAttr::get(nativeType.getContext(), nativeType.getShape()));
252}
253
254//----------------------------------------------------------------------------//
255// Pattern match and target shape computation //
256//----------------------------------------------------------------------------//
257
258// Establish whether the candidate contraction is a canonical matrix
259// multiplication on flat or VNNI-packed vectors. If successful, the discovered
260// operand/accumulator types and the extent of the main reduction dimension are
261// recorded in the candidate struct.
262//
263// Example (flat):
264// vector.contract {
265// kind = #vector.kind<add>, indexing_maps = [
266// affine_map<(dm, dn, dk) -> (dm, dk)>,
267// affine_map<(dm, dn, dk) -> (dk, dn)>,
268// affine_map<(dm, dn, dk) -> (dm, dn)>],
269// iterator_types = ["parallel", "parallel", "reduction"],
270// } %lhs, %rhs, %acc : !lhsType, !rhsType into !accType
271//
272// Example (VNNI-packed):
273// vector.contract {
274// kind = #vector.kind<add>, indexing_maps = [
275// affine_map<(dm, dn, dk, dvnni) -> (dm, dk, dvnni)>,
276// affine_map<(dm, dn, dk, dvnni) -> (dk, dn, dvnni)>,
277// affine_map<(dm, dn, dk, dvnni) -> (dm, dn)>],
278// iterator_types = ["parallel", "parallel", "reduction", "reduction"],
279// } %lhs, %rhs, %acc : !lhsType, !rhsType into !accType
280static LogicalResult matchCanonicalMatmul(MLUCandidate &candidate,
281 PatternRewriter &rewriter) {
282 vector::ContractionOp contract = candidate.contract;
283 if (contract.getKind() != vector::CombiningKind::ADD)
284 return rewriter.notifyMatchFailure(contract,
285 "not using ADD combining kind");
286
287 VectorType accType = dyn_cast<VectorType>(contract.getAccType());
288 if (!accType || accType.getRank() != 2)
289 return rewriter.notifyMatchFailure(contract,
290 "accumulator is not a 2D vector");
291
292 VectorType lhsType = contract.getLhsType();
293 VectorType rhsType = contract.getRhsType();
294 if (lhsType.getElementType() != rhsType.getElementType())
295 return rewriter.notifyMatchFailure(
296 contract, "LHS and RHS element types do not match");
297
298 auto iteratorTypes = contract.getIteratorTypesArray();
299 auto indexingMaps = contract.getIndexingMapsArray();
300 assert(indexingMaps.size() == 3 && "expected 3 indexing maps");
301 bool isInVnniLayout = x86::isInVnniLayout(contract, indexingMaps);
302
303 auto par = vector::IteratorType::parallel;
304 auto red = vector::IteratorType::reduction;
305 auto m = rewriter.getAffineDimExpr(0);
306 auto n = rewriter.getAffineDimExpr(1);
307 auto k = rewriter.getAffineDimExpr(2);
308 auto vnni = rewriter.getAffineDimExpr(3);
309
310 if (!isInVnniLayout) {
311 if (!equal(indexingMaps[0].getResults(), ArrayRef{m, k}) ||
312 !equal(indexingMaps[1].getResults(), ArrayRef{k, n}) ||
313 !equal(indexingMaps[2].getResults(), ArrayRef{m, n}))
314 return rewriter.notifyMatchFailure(contract,
315 "indexing maps are not canonical");
316
317 if (!equal(iteratorTypes, ArrayRef{par, par, red}))
318 return rewriter.notifyMatchFailure(contract,
319 "iterator types are not canonical");
320 } else {
321 if (!equal(indexingMaps[0].getResults(), ArrayRef{m, k, vnni}) ||
322 !equal(indexingMaps[1].getResults(), ArrayRef{k, n, vnni}) ||
323 !equal(indexingMaps[2].getResults(), ArrayRef{m, n}))
324 return rewriter.notifyMatchFailure(
325 contract, "indexing maps are not canonical (VNNI)");
326
327 if (!equal(iteratorTypes, ArrayRef{par, par, red, red}))
328 return rewriter.notifyMatchFailure(
329 contract, "iterator types are not canonical (VNNI)");
330 }
331
332 candidate.isInVnniLayout = isInVnniLayout;
333 candidate.accType = accType;
334 candidate.lhsType = lhsType;
335 candidate.rhsType = rhsType;
336 candidate.contractK = lhsType.getShape()[1];
337
338 return success();
339}
340
341// Invoke the target-specific hook to compute a tiling and unrolling strategy
342// for the contraction, and check that it is consistent and compatible with the
343// current capabilities of this pattern. The result is recorded in the
344// `*regTileType`, `*nativeType`, and `nativeContractShape` fields of the
345// candidate struct.
346static LogicalResult
347matchTypesAndShape(MLUCandidate &candidate,
348 const MatchTypesAndShapeFn &matchTypesAndShapeForTarget,
349 PatternRewriter &rewriter) {
350 vector::ContractionOp contract = candidate.contract;
351
352 Type inpElemType = candidate.lhsType.getElementType();
353 Type accElemType = candidate.accType.getElementType();
354
355 // Contraction shape is {m, n, k} or {m, n, k, vnni}.
356 ArrayRef<int64_t> rhsShape = candidate.rhsType.getShape();
357 ArrayRef<int64_t> lhsShape = candidate.lhsType.getShape();
358 SmallVector<int64_t, 4> contractShape = {lhsShape[0], rhsShape[1],
359 lhsShape[1]};
360 if (candidate.isInVnniLayout)
361 contractShape.push_back(lhsShape[2]);
362
363 SmallVector<int64_t, 4> contractRegTileShape, contractNativeShape;
364 if (failed(matchTypesAndShapeForTarget(
365 inpElemType, accElemType, candidate.isInVnniLayout, contractShape,
366 contractRegTileShape, contractNativeShape)) ||
367 contractRegTileShape.size() < contractShape.size() ||
368 contractNativeShape.size() < contractShape.size())
369 return rewriter.notifyMatchFailure(
370 contract, "no tiling and unrolling strategy found");
371
372 auto maybeTileShapeRatio =
373 computeShapeRatio(contractShape, contractRegTileShape);
374 auto maybeUnrollShapeRatio =
375 computeShapeRatio(contractRegTileShape, contractNativeShape);
376
377 if (!maybeTileShapeRatio || !maybeUnrollShapeRatio)
378 return rewriter.notifyMatchFailure(
379 contract, "incompatible or inconsistent tiling and unrolling strategy");
380
381 auto isOne = [](int64_t v) { return v == 1; };
382 if (all_of(*maybeTileShapeRatio, isOne) &&
383 all_of(*maybeUnrollShapeRatio, isOne))
384 return rewriter.notifyMatchFailure(
385 contract, "contraction is already in native shape");
386
387 if (!all_of(drop_begin(*maybeUnrollShapeRatio, 2), isOne))
388 return rewriter.notifyMatchFailure(
389 contract, "cannot unroll in the reduction dimensions");
390
391 if (candidate.isInVnniLayout && (maybeTileShapeRatio->back() != 1))
392 return rewriter.notifyMatchFailure(contract,
393 "cannot tile in the VNNI dimension");
394
395 int64_t regTileM = contractRegTileShape[0];
396 int64_t regTileN = contractRegTileShape[1];
397 int64_t regTileK = contractRegTileShape[2];
398
399 int64_t nativeM = contractNativeShape[0];
400 int64_t nativeN = contractNativeShape[1];
401 int64_t nativeK = contractNativeShape[2];
402
403 candidate.accRegTileType = VectorType::get({regTileM, regTileN}, accElemType);
404 candidate.accNativeType = VectorType::get({nativeM, nativeN}, accElemType);
405
406 auto getOperandType = [&](ArrayRef<int64_t> shape) {
407 if (candidate.isInVnniLayout) {
408 SmallVector<int64_t, 3> newShape(shape.begin(), shape.end());
409 newShape.push_back(contractNativeShape[3]);
410 return VectorType::get(newShape, inpElemType);
411 }
412 return VectorType::get(shape, inpElemType);
413 };
414
415 candidate.lhsRegTileType = getOperandType({regTileM, regTileK});
416 candidate.rhsRegTileType = getOperandType({regTileK, regTileN});
417
418 candidate.lhsNativeType = getOperandType({nativeM, nativeK});
419 candidate.rhsNativeType = getOperandType({nativeK, nativeN});
420
421 candidate.contractNativeShape.append(contractNativeShape.begin(),
422 contractNativeShape.end());
423
424 return success();
425}
426
427// Check constraints on the accumulation loop. We're looking for an index-based
428// for-loop with a step of 1 or K, a single accumulator block argument. The body
429// shall only contain the transfer_reads for the operands, the contraction and
430// the yield operation. This restriction is in place because the pattern
431// rebuilds the accumulation loop from scratch with the known operations.
432//
433// For the operand reads, we require that they have a minor identity permutation
434// map, impose no bounds checks and don't carry a mask. In addition, they shall
435// use the induction variable as exactly one of their indices. This covers plain
436// 2D memrefs as well as block-based layouts.
437//
438// If all these conditions are met, the loop op, initial value of the
439// accumulator and the operand reads are recorded in the candidate struct.
440//
441// Example:
442// !accType = vector<64x128xf32>
443// !lhsType = vector<64x32xbf16>
444// !rhsType = vector<32x128xbf16>
445// ...
446// %res = scf.for %k = %k_start to %k_end step %c1 iter_args(%acc = %c)
447// -> (!accType) {
448// %a = vector.transfer_read %A[%m, %k, %c0, %c0], %c0_0
449// {in_bounds = [true, true]} : memref<?x?x64x32xbf16>, !lhsType
450// %b = vector.transfer_read %B[%n, %k, %c0, %c0], %c0_0
451// {in_bounds = [true, true]} : memref<?x?x32x128xbf16>, !rhsType
452// %d = vector.contract { ... } %a, %b, %acc
453// : !lhsType, !rhsType into !accType
454// scf.yield %d : !accType
455// }
456static LogicalResult matchAccumulationLoop(MLUCandidate &candidate,
457 PatternRewriter &rewriter) {
458 auto &contract = candidate.contract;
459
460 auto accLoop = dyn_cast_if_present<scf::ForOp>(contract->getParentOp());
461 if (!accLoop || !accLoop.getInductionVar().getType().isIndex())
462 return rewriter.notifyMatchFailure(contract,
463 "is not in an index-based for-loop");
464
465 if (auto step = getConstantIntValue(accLoop.getStep());
466 !step || (*step != 1 && *step != candidate.contractK))
467 return rewriter.notifyMatchFailure(contract,
468 "accumulation loop step is not 1 or K");
469
470 BlockArgument accIterArg = dyn_cast<BlockArgument>(contract.getAcc());
471 if (!accIterArg || accIterArg.getOwner() != accLoop.getBody())
472 return rewriter.notifyMatchFailure(contract,
473 "accumulator is not a block argument");
474
475 if (accLoop.getNumRegionIterArgs() != 1 ||
476 accLoop.getBody()->getOperations().size() != 4)
477 return rewriter.notifyMatchFailure(
478 contract, "accumulation loop has additional iter_args or operations");
479
480 if (!contract->getResult(0).hasOneUse())
481 return rewriter.notifyMatchFailure(contract,
482 "result does not have exactly one use");
483
484 Operation *user = *contract->getResult(0).getUsers().begin();
485 if (!isa<scf::YieldOp>(user))
486 return rewriter.notifyMatchFailure(contract,
487 "accumulator is not loop-carried");
488
489 Value accInitVal = accLoop.getTiedLoopInit(accIterArg)->get();
490 bool accIsZeroInit = isZeroVector(accInitVal);
491
492 auto lhsRead = contract.getLhs().getDefiningOp<vector::TransferReadOp>();
493 auto rhsRead = contract.getRhs().getDefiningOp<vector::TransferReadOp>();
494 if (!lhsRead || !rhsRead)
495 return rewriter.notifyMatchFailure(
496 contract, "LHS/RHS are not vector.transfer_read ops");
497
498 auto checkOperandRead = [&](vector::TransferReadOp read, unsigned kVecDim) {
499 if (!isSimpleTransferOp(read))
500 return rewriter.notifyMatchFailure(
501 contract, "operand read has non-identity permutation, mask, or "
502 "out-of-bounds check");
503
504 auto indices = read.getIndices();
505 if (candidate.isInVnniLayout && !isZeroInteger(indices.back()))
506 return rewriter.notifyMatchFailure(
507 contract, "operand read VNNI dimension offset must be zero");
508
509 auto ivIt =
510 std::find(indices.begin(), indices.end(), accLoop.getInductionVar());
511 if (ivIt == indices.end())
512 return rewriter.notifyMatchFailure(
513 contract, "operand read does not use loop index");
514 if (std::find(ivIt + 1, indices.end(), accLoop.getInductionVar()) !=
515 indices.end())
516 return rewriter.notifyMatchFailure(
517 contract, "operand read uses loop index more than once");
518
519 // The following situation is a limitation in the implementation, so detect
520 // and bail out:
521 //
522 // If the loop index selects a block in a blocked layout, and tiling of the
523 // contraction dimension is required, we have to construct a new innermost
524 // tiling loop over the result vector's K dimension (called "L-loop" in the
525 // transformation later). Now if the operand read has a non-zero offset in
526 // that dimension, we would have to insert an addition into the new loop,
527 // which currently would prevent application of the nanokernel pattern.
528 //
529 // Sketch:
530 // non-zero offset in the vector's K dimension
531 // IV used in non-vector dimension, |
532 // selects a block |
533 // | |
534 // v v
535 // vector.transfer_read %A[%block_M, %IV, %c0, %offset_k] ...
536 // : memref<?x?x?x?xi8>, vector<64x64xi8>
537 unsigned firstVecDimPos = read.getLeadingShapedRank();
538 unsigned ivPos = std::distance(indices.begin(), ivIt);
539 if (ivPos < firstVecDimPos &&
540 candidate.contractK > candidate.contractNativeShape[2] &&
541 !isZeroInteger(indices[firstVecDimPos + kVecDim]))
542 return rewriter.notifyMatchFailure(contract,
543 "operand read from blocked layout has "
544 "offset in vector's K dimension");
545
546 return success();
547 };
548
549 if (failed(checkOperandRead(lhsRead, /*[m, k] -> kVecDim=*/1)) ||
550 failed(checkOperandRead(rhsRead, /*[k, n] -> kVecDim=*/0)))
551 return failure();
552
553 candidate.accLoop = accLoop;
554 candidate.accInitVal = accInitVal;
555 candidate.accIsZeroInit = accIsZeroInit;
556 candidate.lhsRead = lhsRead;
557 candidate.rhsRead = rhsRead;
558
559 return success();
560}
561
562//----------------------------------------------------------------------------//
563// Transformation
564//----------------------------------------------------------------------------//
565
566// Ensure that the initial accumulator value can be sliced into register-tile
567// shaped vectors. Unless it is zero-initialized, we need it to be backed
568// by memory so that the slice can be loaded via a vector.transfer_read inside
569// the loop nest.
570//
571// If the initial value is produced by a simple vector.transfer_read, we can
572// simply derive a subview from its base and indices. Otherwise, we fall back to
573// allocating a new buffer on stack and store the initial value into it.
574static void makeAccInitializationBackedByMemory(MLUCandidate &candidate,
575 PatternRewriter &rewriter) {
576 if (candidate.accIsZeroInit)
577 return;
578
579 OpBuilder::InsertionGuard guard(rewriter);
580 rewriter.setInsertionPoint(candidate.accLoop);
581 auto loc = candidate.accLoop.getLoc();
582
583 if (auto accRead =
584 candidate.accInitVal.getDefiningOp<vector::TransferReadOp>();
585 accRead && isSimpleTransferOp(accRead) &&
586 noMemoryEffectsBetween(accRead, candidate.accLoop)) {
587 candidate.accMemIn =
588 makeRankReducingSubview(rewriter, loc, accRead.getBase(),
589 accRead.getIndices(), accRead.getVectorType());
590 return;
591 }
592
593 auto accBufferType = MemRefType::get(candidate.accType.getShape(),
594 candidate.accType.getElementType());
595 candidate.accMemIn = memref::AllocaOp::create(rewriter, loc, accBufferType);
596
597 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
598 SmallVector<Value, 2> zeroIndices(candidate.accType.getRank(), c0);
599 vector::TransferWriteOp::create(rewriter, loc, candidate.accInitVal,
600 candidate.accMemIn, zeroIndices);
601}
602
603// Ensure that slices of the result of the accumulator loop can be stored, so
604// again we need it to be backed by memory.
605//
606// If the only user of the result is a simple vector.transfer_write, and its
607// base and indices are defined before the accumulator loop, we can derive a
608// subview. The transfer_write will be deleted after building the loop nest,
609// which at that point incorporates its effect.
610//
611// Otherwise, we fall back to buffering the result and rematerializing it after
612// the loop. If we already had a stack allocation for the initial value, we
613// reuse it.
614static void makeAccResultBackedByMemory(MLUCandidate &candidate,
615 PatternRewriter &rewriter) {
616 OpBuilder::InsertionGuard guard(rewriter);
617 rewriter.setInsertionPoint(candidate.accLoop);
618 auto loc = candidate.accLoop.getLoc();
619
620 auto accRes = candidate.accLoop.getResult(0);
621 vector::TransferWriteOp accWrite;
622 if (accRes.hasOneUse())
623 accWrite = dyn_cast<vector::TransferWriteOp>(*accRes.getUsers().begin());
624
625 Block *accLoopBlock = candidate.accLoop->getBlock();
626 if (accWrite && isSimpleTransferOp(accWrite) &&
627 noMemoryEffectsBetween(candidate.accLoop, accWrite)) {
628
629 Value accWriteBase = accWrite.getBase();
630 ValueRange accWriteIndices = accWrite.getIndices();
631
632 auto isSafeToUseView = [&]() {
633 if (candidate.accIsZeroInit)
634 return true;
635 auto accMemInView = candidate.accMemIn.getDefiningOp<memref::SubViewOp>();
636 if (!accMemInView)
637 return true;
638
639 return accMemInView.getSource() == accWriteBase &&
640 isEqualConstantIntOrValueArray(accMemInView.getMixedOffsets(),
641 getAsOpFoldResult(accWriteIndices));
642 };
643
644 auto isDefinedBeforeAccLoop = [&](Value v) {
645 Operation *defOp = v.getDefiningOp();
646 // No need for full dominance check, as we already know that accLoop and
647 // accWrite are in the same block.
648 return !defOp || defOp->getBlock() != accLoopBlock ||
649 defOp->isBeforeInBlock(candidate.accLoop);
650 };
651
652 if (isSafeToUseView() && isDefinedBeforeAccLoop(accWriteBase) &&
653 all_of(accWriteIndices, isDefinedBeforeAccLoop)) {
654 candidate.accMemOut =
655 makeRankReducingSubview(rewriter, loc, accWriteBase, accWriteIndices,
656 accWrite.getVectorType());
657 candidate.opsToDelete.push_back(accWrite);
658 candidate.opsToDelete.push_back(candidate.accLoop);
659 return;
660 }
661 }
662
663 if (!candidate.accIsZeroInit &&
664 isa<memref::AllocaOp>(candidate.accMemIn.getDefiningOp())) {
665 candidate.accMemOut = candidate.accMemIn;
666 } else {
667 auto accBufferType = MemRefType::get(candidate.accType.getShape(),
668 candidate.accType.getElementType());
669 candidate.accMemOut =
670 memref::AllocaOp::create(rewriter, loc, accBufferType);
671 }
672
673 rewriter.setInsertionPointAfter(candidate.accLoop);
674 Value c0 = arith::ConstantIndexOp::create(rewriter, loc, 0);
675 SmallVector<Value, 2> zeroIndices(candidate.accType.getRank(), c0);
676 auto accRemat = vector::TransferReadOp::create(
677 rewriter, loc, candidate.accType, candidate.accMemOut, zeroIndices,
678 /*padding=*/std::nullopt);
679
680 rewriter.replaceAllUsesWith(candidate.accLoop.getResult(0), accRemat);
681 candidate.opsToDelete.push_back(candidate.accLoop);
682}
683
684// Create the loop nest for iterating over the M and N dimensions of the
685// accumulator. Construction continues in buildMNLoopBody.
686static void buildLoopNestForCandidate(MLUCandidate &candidate,
687 PatternRewriter &rewriter) {
688 OpBuilder::InsertionGuard guard(rewriter);
689 rewriter.setInsertionPoint(candidate.accLoop);
690 auto loc = candidate.accLoop.getLoc();
691
692 ArrayRef<int64_t> accShape = candidate.accType.getShape();
693 ArrayRef<int64_t> accRegTileShape = candidate.accRegTileType.getShape();
694
695 auto makeIndexConst = [&](int64_t v) {
696 return arith::ConstantIndexOp::create(rewriter, loc, v);
697 };
698
699 Value c0 = makeIndexConst(0);
700 Value ubM = makeIndexConst(accShape[0]);
701 Value stepM = makeIndexConst(accRegTileShape[0]);
702 Value ubN = makeIndexConst(accShape[1]);
703 Value stepN = makeIndexConst(accRegTileShape[1]);
704
705 scf::ForOp::create(
706 rewriter, loc, c0, ubM, stepM, {},
707 [&](OpBuilder &b, Location loc, Value ivM, ValueRange iterArgs) {
708 scf::ForOp::create(
709 b, loc, c0, ubN, stepN, {},
710 [&](OpBuilder &b, Location loc, Value ivN, ValueRange iterArgs) {
711 buildMNLoopBody(candidate, b, loc, ivM, ivN);
712 scf::YieldOp::create(b, loc);
713 });
714 scf::YieldOp::create(b, loc);
715 });
716}
717
718// Create the body for M-N-loop nest according to the following scheme:
719//
720// 1. Load a accRegTileType-shaped slice of the accumulator from memory (or make
721// a zero constant).
722// 2. Split the initial value into accNativeType-shaped slices.
723// 3. Create the K-loop with the initial value slices as loop-carried arguments,
724// i.e. the iter_args have accNativeType. Tiling along the K-dimension
725// depends on the step:
726// K: Use the same bounds but smaller steps/more iterations.
727// 1: Nest an additional L-loop to traverse the block in smaller steps.
728// 4. -> buildKLLoopBody
729// 5. Splice the final value slices back into an accRegTileType-shaped value and
730// write it back to memory.
731//
732// The accRegTileType-shaped operations are annotated with an attribute in order
733// to unroll them with the vector-dialect's unroll patterns.
734static void buildMNLoopBody(MLUCandidate &candidate, OpBuilder &b, Location loc,
735 Value ivM, Value ivN) {
736 ArrayRef<int64_t> accRegTileShape = candidate.accRegTileType.getShape();
737
739
740 SmallVector<OpFoldResult, 2> accViewOffsets = {ivM, ivN};
741 SmallVector<OpFoldResult, 2> accViewSizes = {
742 b.getI64IntegerAttr(accRegTileShape[0]),
743 b.getI64IntegerAttr(accRegTileShape[1])};
744 SmallVector<OpFoldResult, 2> accViewStrides = {b.getI64IntegerAttr(1),
745 b.getI64IntegerAttr(1)};
746
747 Operation *accInit;
748 if (candidate.accIsZeroInit) {
749 accInit = arith::ConstantOp::create(
750 b, loc, b.getZeroAttr(candidate.accRegTileType));
751 } else {
752 Value accViewIn =
753 memref::SubViewOp::create(b, loc, candidate.accMemIn, accViewOffsets,
754 accViewSizes, accViewStrides);
755 auto accInitRead = vector::TransferReadOp::create(
756 b, loc, candidate.accRegTileType, accViewIn, ValueRange{c0, c0},
757 /*padding=*/std::nullopt);
758 setNativeShapeAttr(accInitRead, candidate.accNativeType);
759 accInit = accInitRead;
760 }
761
762 SmallVector<Value> accInitSliced =
763 slice(b, loc, accInit->getResult(0), candidate.accNativeType);
764
765 // Iteration space for accumulation loop (nest).
766 Value lbK = candidate.accLoop.getLowerBound();
767 Value ubK = candidate.accLoop.getUpperBound();
768 Value stepK = candidate.accLoop.getStep();
769 Value lbL;
770 Value ubL;
771 Value stepL;
772
773 // If we need to tile the K dimension, the simplest way is to adjust the
774 // step to the native K extent. However, if the original accumulation loop
775 // uses unit steps, we have to insert an inner loop to handle the tiling
776 // explicitly.
777 //
778 // Note: Using _native_ shape here as we never unroll along the reduction
779 // dimension, so the K extents of the register-tile and native shapes match.
780 int64_t contractNativeK = candidate.contractNativeShape[2];
781 bool needToTileK = candidate.contractK > contractNativeK;
782 bool needLLoop = false;
783 if (needToTileK) {
784 needLLoop = *getConstantIntValue(stepK) % contractNativeK != 0;
785 Value cContractNativeK =
786 arith::ConstantIndexOp::create(b, loc, contractNativeK);
787 if (needLLoop) {
788 lbL = c0;
789 ubL = arith::ConstantIndexOp::create(b, loc, candidate.contractK);
790 stepL = cContractNativeK;
791 } else {
792 stepK = cContractNativeK;
793 }
794 }
795
796 candidate.slicedAccLoop = scf::ForOp::create(
797 b, loc, lbK, ubK, stepK, accInitSliced,
798 [&](OpBuilder &b, Location loc, Value ivK, ValueRange iterArgsK) {
799 if (!needLLoop) {
801 candidate, b, loc, ivM, ivN, ivK, /*ivL=*/Value{}, iterArgsK);
802 scf::YieldOp::create(b, loc, newIterArgs);
803 return;
804 }
805
806 candidate.slicedInnerAccLoop = scf::ForOp::create(
807 b, loc, lbL, ubL, stepL, iterArgsK,
808 [&](OpBuilder &b, Location loc, Value ivL, ValueRange iterArgsL) {
810 candidate, b, loc, ivM, ivN, ivK, ivL, iterArgsL);
811 scf::YieldOp::create(b, loc, newIterArgs);
812 });
813 scf::YieldOp::create(b, loc, candidate.slicedInnerAccLoop.getResults());
814 });
815
816 Value accSpliced = splice(b, loc, candidate.slicedAccLoop.getResults(),
817 candidate.accRegTileType);
818 Value accViewOut =
819 memref::SubViewOp::create(b, loc, candidate.accMemOut, accViewOffsets,
820 accViewSizes, accViewStrides);
821 auto accWrite = vector::TransferWriteOp::create(
822 b, loc, accSpliced, accViewOut, ValueRange{c0, c0});
823
824 setNativeShapeAttr(accWrite, candidate.accNativeType);
825}
826
827// Rebuild the accumulation loop with [lhs|rhs]RegTileType-shaped operations,
828// replacing the induction variable and taking the offsets from the
829// M-N-(L)-loop nest into account. The accumulator iter_args are spliced into a
830// accRegTileType-shaped value, and the contraction result is split into
831// accNativeType-shaped yield operands. Again, the operations are annotated with
832// an attribute for unrolling, after which the splice/split logic will fold away
833// completely.
834static SmallVector<Value> buildKLLoopBody(MLUCandidate &candidate, OpBuilder &b,
835 Location loc, Value ivM, Value ivN,
836 Value ivK, Value ivL,
837 ValueRange iterArgs) {
838 Value accSpliced = splice(b, loc, iterArgs, candidate.accRegTileType);
839
840 SmallVector<Value, 4> lhsIndices = candidate.lhsRead.getIndices();
841 SmallVector<Value, 4> rhsIndices = candidate.rhsRead.getIndices();
842
843 Value accLoopIV = candidate.accLoop.getInductionVar();
844 replace(lhsIndices, accLoopIV, ivK);
845 replace(rhsIndices, accLoopIV, ivK);
846
847 unsigned mPos = lhsIndices.size() - 2 - candidate.isInVnniLayout;
848 unsigned nPos = rhsIndices.size() - 1 - candidate.isInVnniLayout;
849 lhsIndices[mPos] = arith::AddIOp::create(b, loc, lhsIndices[mPos], ivM);
850 rhsIndices[nPos] = arith::AddIOp::create(b, loc, rhsIndices[nPos], ivN);
851
852 if (ivL) {
853 unsigned lPosL = lhsIndices.size() - 1 - candidate.isInVnniLayout;
854 unsigned lPosR = rhsIndices.size() - 2 - candidate.isInVnniLayout;
855 // Checked in matchAccumulationLoop.
856 assert(isZeroInteger(lhsIndices[lPosL]) &&
857 isZeroInteger(rhsIndices[lPosR]) &&
858 "Expected zero offsets for L dimension");
859 lhsIndices[lPosL] = ivL;
860 rhsIndices[lPosR] = ivL;
861 }
862
863 Value lhsView = makeRankReducingSubview(b, loc, candidate.lhsRead.getBase(),
864 lhsIndices, candidate.lhsRegTileType);
865 Value rhsView = makeRankReducingSubview(b, loc, candidate.rhsRead.getBase(),
866 rhsIndices, candidate.rhsRegTileType);
867
868 SmallVector<Value, 3> zeroIndices(2 + candidate.isInVnniLayout,
870 auto lhsRead = vector::TransferReadOp::create(
871 b, candidate.lhsRead.getLoc(), candidate.lhsRegTileType, lhsView,
872 zeroIndices, /*padding=*/std::nullopt);
873 auto rhsRead = vector::TransferReadOp::create(
874 b, candidate.rhsRead.getLoc(), candidate.rhsRegTileType, rhsView,
875 zeroIndices, /*padding=*/std::nullopt);
876 auto contract = vector::ContractionOp::create(
877 b, candidate.contract.getLoc(), lhsRead, rhsRead, accSpliced,
878 candidate.contract.getIndexingMaps(),
879 candidate.contract.getIteratorTypes());
880
881 setNativeShapeAttr(lhsRead, candidate.lhsNativeType);
882 setNativeShapeAttr(rhsRead, candidate.rhsNativeType);
884 b.getDenseI64ArrayAttr(candidate.contractNativeShape));
885
886 SmallVector<Value> accSliced =
887 slice(b, loc, contract, candidate.accNativeType);
888 return accSliced;
889}
890
891//----------------------------------------------------------------------------//
892// Pattern implementation //
893//----------------------------------------------------------------------------//
894
895namespace {
896// Prepares a contraction inside an accumulation loop for the nanokernel
897// lowerings.
898//
899// The pattern determines a register-tile shape for the contraction that can be
900// fully unrolled to a target-specific native shape without exceeding the
901// target's vector registers or AMX tile registers. It then introduces a loop
902// nest to iterate over such register tiles, and applies the vector dialect's
903// unroll patterns to produce native-shaped operations that the nanokernel
904// patterns can match.
905//
906// Currently, the match is deliberately strict (see the helpers above):
907// The contraction must be a canonical matrix multiplication, have
908// target-compatible types and shapes that can be tiled and unrolled cleanly,
909// and be embedded in an accumulation loop containing only the operand reads and
910// itself. This lets the rewrite regenerate the loop from scratch instead of
911// transforming it in place.
912struct VectorContractMultiLevelUnroll
913 : public OpRewritePattern<vector::ContractionOp> {
914 VectorContractMultiLevelUnroll(
915 MLIRContext *context,
916 const MatchTypesAndShapeFn &matchTypesAndShapeForTarget,
917 PatternBenefit benefit = 1)
918 : OpRewritePattern<vector::ContractionOp>(context, benefit),
919 matchTypesAndShapeForTarget(matchTypesAndShapeForTarget) {}
920
921 LogicalResult matchAndRewrite(vector::ContractionOp contract,
922 PatternRewriter &rewriter) const override {
923 // Bail out early if this op is the result of a previous application of the
924 // pattern.
925 if (contract->hasAttr(nativeShapeAttrName))
926 return failure();
927
928 MLUCandidate candidate;
929 candidate.contract = contract;
930 if (failed(matchCanonicalMatmul(candidate, rewriter)) ||
931 failed(matchTypesAndShape(candidate, matchTypesAndShapeForTarget,
932 rewriter)) ||
933 failed(matchAccumulationLoop(candidate, rewriter)))
934 return failure();
935
936 makeAccInitializationBackedByMemory(candidate, rewriter);
937 makeAccResultBackedByMemory(candidate, rewriter);
938 buildLoopNestForCandidate(candidate, rewriter);
939 if (candidate.slicedInnerAccLoop)
940 moveLoopInvariantCode(candidate.slicedInnerAccLoop);
941 moveLoopInvariantCode(candidate.slicedAccLoop);
942 for (auto *op : candidate.opsToDelete)
943 rewriter.eraseOp(op);
944
945 return success();
946 }
947
948private:
949 MatchTypesAndShapeFn matchTypesAndShapeForTarget;
950};
951} // namespace
952
953//----------------------------------------------------------------------------//
954// Target-specific matchers //
955//----------------------------------------------------------------------------//
956
957// Tiling strategy for AMX tile dot-product: The preferred native layout is
958// to keep the accumulator on a 2x2 grid of tile registers, though 1x2 and
959// VNNI-prepacked 1x1 and 2x1 configurations are also supported (NB: online
960// packing requires two neighboring tiles in the N-dimension).
961static LogicalResult
962matchTypesAndShapeForAMX(Type inputElemType, Type accumulatorElemType,
963 bool isInVnniLayout, ArrayRef<int64_t> contractShape,
964 SmallVectorImpl<int64_t> &contractRegTileShape,
965 SmallVectorImpl<int64_t> &contractNativeShape) {
966
967 if (!((inputElemType.isBF16() && accumulatorElemType.isF32()) ||
968 (inputElemType.isInteger(8) && accumulatorElemType.isInteger(32))))
969 return failure();
970
971 int64_t origM = contractShape[0];
972 int64_t origN = contractShape[1];
973
974 int64_t regTileM, regTileN, regTileK;
975 int64_t nativeM, nativeN, nativeK, vnni;
976
977 vnni = 32 / inputElemType.getIntOrFloatBitWidth();
978 nativeM = nativeN = 16;
979 nativeK = isInVnniLayout ? 16 : 16 * vnni;
980
981 // Prefer 2x2 tiling if shapes allow. Online packing requires a contract pair
982 // in the N-dimension.
983 regTileM = origM % (2 * nativeM) == 0 ? 2 * nativeM : nativeM;
984 regTileN =
985 origN % (2 * nativeN) == 0 || !isInVnniLayout ? 2 * nativeN : nativeN;
986 regTileK = nativeK;
987
988 contractRegTileShape.append({regTileM, regTileN, regTileK});
989 contractNativeShape.append({nativeM, nativeN, nativeK});
990 if (isInVnniLayout) {
991 contractRegTileShape.push_back(vnni);
992 contractNativeShape.push_back(vnni);
993 }
994
995 return success();
996}
997
998//----------------------------------------------------------------------------//
999// Public interface //
1000//----------------------------------------------------------------------------//
1001
1003 RewritePatternSet &patterns) {
1004 patterns.add<VectorContractMultiLevelUnroll>(patterns.getContext(),
1006 vector::UnrollVectorOptions nativeUnrollOptions;
1007 nativeUnrollOptions.setNativeShapeFn(getFromNativeShapeAttr);
1008 vector::populateVectorUnrollPatterns(patterns, nativeUnrollOptions);
1009}
return success()
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static void contract(RootOrderingGraph &graph, ArrayRef< Value > cycle, const DenseMap< Value, unsigned > &parentDepths, DenseMap< Value, Value > &actualSource, DenseMap< Value, Value > &actualTarget)
Contracts the specified cycle in the given graph in-place.
static void buildLoopNestForCandidate(MLUCandidate &candidate, PatternRewriter &rewriter)
static std::optional< SmallVector< int64_t > > getFromNativeShapeAttr(Operation *op)
static Value makeRankReducingSubview(OpBuilder &b, Location loc, Value base, ValueRange indices, VectorType targetType)
static void makeAccInitializationBackedByMemory(MLUCandidate &candidate, PatternRewriter &rewriter)
static Value splice(OpBuilder &b, Location loc, ValueRange values, VectorType regTileType)
static void buildMNLoopBody(MLUCandidate &candidate, OpBuilder &b, Location loc, Value ivM, Value ivN)
static void makeAccResultBackedByMemory(MLUCandidate &candidate, PatternRewriter &rewriter)
static SmallVector< Value > buildKLLoopBody(MLUCandidate &candidate, OpBuilder &b, Location loc, Value ivM, Value ivN, Value ivK, Value ivL, ValueRange iterArgs)
static void setNativeShapeAttr(VectorTransferOpInterface op, VectorType nativeType)
static bool isZeroVector(Value v)
static constexpr auto nativeShapeAttrName
static LogicalResult matchTypesAndShape(MLUCandidate &candidate, const MatchTypesAndShapeFn &matchTypesAndShapeForTarget, PatternRewriter &rewriter)
static LogicalResult matchAccumulationLoop(MLUCandidate &candidate, PatternRewriter &rewriter)
static bool noMemoryEffectsBetween(Operation *start, Operation *end)
static LogicalResult matchTypesAndShapeForAMX(Type inputElemType, Type accumulatorElemType, bool isInVnniLayout, ArrayRef< int64_t > contractShape, SmallVectorImpl< int64_t > &contractRegTileShape, SmallVectorImpl< int64_t > &contractNativeShape)
static LogicalResult matchCanonicalMatmul(MLUCandidate &candidate, PatternRewriter &rewriter)
static SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
static bool isSimpleTransferOp(VectorTransferOpInterface op)
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:308
Block * getOwner() const
Returns the block that owns this argument.
Definition Value.h:317
Block represents an ordered list of Operations.
Definition Block.h:34
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
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
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
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
AttrClass getAttrOfType(StringAttr name)
Definition Operation.h:604
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:434
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 eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
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
bool isF32() const
Definition Types.cpp:40
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
bool isBF16() const
Definition Types.cpp:37
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
type_range getTypes() const
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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int64_t > content)
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:744
void populateVectorContractMultiLevelUnrollForAMXPatterns(RewritePatternSet &patterns)
bool isInVnniLayout(Operation *op, llvm::ArrayRef< AffineMap > indexingMaps, std::optional< unsigned > blockingFactor=std::nullopt)
Definition X86Utils.cpp:42
Include the generated interface declarations.
OpFoldResult getAsIndexOpFoldResult(MLIRContext *ctx, int64_t val)
Convert int64_t to integer attributes of index type and return them as OpFoldResult.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
bool isZeroIntegerOrFloat(OpFoldResult v)
Return "true" if v is an integer/float value/attribute with constant value zero.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
bool isEqualConstantIntOrValueArray(ArrayRef< OpFoldResult > ofrs1, ArrayRef< OpFoldResult > ofrs2)
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
std::optional< SmallVector< int64_t > > computeShapeRatio(ArrayRef< int64_t > shape, ArrayRef< int64_t > subShape)
Return the multi-dimensional integral ratio of subShape to the trailing dimensions of shape.
size_t moveLoopInvariantCode(ArrayRef< Region * > regions, function_ref< bool(Value, Region *)> isDefinedOutsideRegion, function_ref< bool(Operation *, Region *)> shouldMoveOutOfRegion, function_ref< void(Operation *, Region *)> moveOutOfRegion)
Given a list of regions, perform loop-invariant code motion.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Options that control the vector unrolling.
UnrollVectorOptions & setNativeShapeFn(NativeShapeFnType fn)