MLIR 24.0.0git
XeGPUUtils.cpp
Go to the documentation of this file.
1//===---- XeGPUUtils.cpp - MLIR Utilities for XeGPUOps ------------------===//
2//
3// Part of the MLIR 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 utility methods for working with the XeGPU dialect.
10//
11//===----------------------------------------------------------------------===//
12
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/BuiltinOps.h"
24#include "mlir/IR/Operation.h"
25#include "mlir/IR/ValueRange.h"
28#include "llvm/Support/Casting.h"
29#include "llvm/Support/FormatVariadic.h"
30#include <cstdint>
31#include <numeric>
32
33using namespace mlir;
34
35/// convert ArrayRef<ValueRange> into SmallVector<Value>
38 for (const auto &vals : values)
39 llvm::append_range(result, vals);
40 return result;
41}
42
43FailureOr<VectorType>
44mlir::xegpu::getDistributedVectorType(xegpu::TensorDescType tdescTy) {
45 auto layout = llvm::dyn_cast_if_present<LayoutAttr>(tdescTy.getLayout());
46 // It only works for subgroup level layout, which only has lane_layout
47 // and lane_data, and is to distribute a SIMD code into SIMT code.
48 if (!layout || !layout.isForSubgroup())
49 return failure();
50
51 SmallVector<int64_t> laneData(layout.getLaneData().asArrayRef());
52 SmallVector<int64_t> laneLayout(layout.getLaneLayout().asArrayRef());
53 auto tdescShape = tdescTy.getShape();
54 auto elementType = tdescTy.getElementType();
55
56 // compute sgSize by multiply elements of laneLayout
57 // e.g. for 2D layout, sgSize = laneLayout[0] * laneLayout[1]
58 // e.g. for 1D layout, sgSize = laneLayout[0]
59 int64_t sgSize = llvm::product_of(laneLayout);
60
61 // Check if the tensor descriptor shape is distributable.
62 int64_t tensorSize = 1;
63 for (auto [tdescDim, laneDim, laneDataDim] :
64 llvm::zip_equal(tdescShape, laneLayout, laneData)) {
65 assert((tdescDim % (laneDim * laneDataDim) == 0) &&
66 "tensor descriptor shape is not distributable");
67 tensorSize *= tdescDim;
68 }
69 // tensorSize must be adjusted for array_length.
70 tensorSize *= tdescTy.getArrayLength();
71
72 return VectorType::get({tensorSize / sgSize}, elementType);
73}
74
75FailureOr<VectorType>
76mlir::xegpu::getDistributedVectorType(VectorType originalType,
77 xegpu::LayoutAttr layout) {
78 int64_t rank = originalType.getRank();
79 if (rank < 1)
80 return failure();
81 ArrayRef<int64_t> shape = originalType.getShape();
82 // For rank > 2, leading dimensions are treated as batch/array dimensions.
83 // Drop them and use the product as arrayLength.
84 int arrayLength = 1;
85 while (shape.size() > 2) {
86 arrayLength *= shape[0];
87 shape = shape.drop_front();
88 }
89 // Drop matching leading dims from layout if the layout rank exceeds the
90 // remaining shape rank.
91 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
92 auto laneData = layout.getEffectiveLaneDataAsInt();
93 while (!laneLayout.empty() && laneLayout.size() > shape.size()) {
94 laneLayout.erase(laneLayout.begin());
95 laneData.erase(laneData.begin());
96 }
97 auto trimmedLayout = xegpu::LayoutAttr::get(
98 layout.getContext(),
99 SmallVector<int32_t>(laneLayout.begin(), laneLayout.end()),
100 SmallVector<int32_t>(laneData.begin(), laneData.end()));
101 auto helperTdescTy = xegpu::TensorDescType::get(
102 shape, originalType.getElementType(), arrayLength,
103 /*boundary_check=*/true,
104 /*memory_space=*/xegpu::MemorySpace::Global, trimmedLayout);
105 return xegpu::getDistributedVectorType(helperTdescTy);
106}
107
108FailureOr<VectorType>
109xegpu::getDistVecTypeBasedOnLaneLayout(xegpu::DistributeLayoutAttr layout,
110 VectorType originalType) {
111 if (!layout)
112 return failure();
113 assert((isa<xegpu::LayoutAttr>(layout) || isa<xegpu::SliceAttr>(layout)) &&
114 "Expecting a valid layout.");
115
116 int64_t vectorRank = originalType.getRank();
117 int64_t layoutRank = layout.getRank();
118 assert(vectorRank >= layoutRank && "Vector rank must be >= layout rank.");
119
120 // When the vector has more dimensions than the layout, only the trailing
121 // dimensions are distributed. Leading dimensions are preserved as-is.
122 int64_t offset = vectorRank - layoutRank;
123 ArrayRef<int64_t> fullShape = originalType.getShape();
124 SmallVector<int64_t> trailingShape(fullShape.begin() + offset,
125 fullShape.end());
126 auto distributedShapeOrFailure =
127 layout.computeDistributedShape(trailingShape);
128 if (failed(distributedShapeOrFailure))
129 return failure();
130
131 SmallVector<int64_t> resultShape(fullShape.begin(),
132 fullShape.begin() + offset);
133 resultShape.append(distributedShapeOrFailure->begin(),
134 distributedShapeOrFailure->end());
135 return VectorType::get(resultShape, originalType.getElementType());
136}
137
138std::string xegpu::getTemporaryLayoutName(const OpOperand &operand) {
139 const StringRef prefix("layout_operand_");
140 unsigned idx = const_cast<OpOperand &>(operand).getOperandNumber();
141 return llvm::formatv("{0}{1}", prefix, idx).str();
142}
143
145 const StringRef prefix = "layout_result_";
146 return llvm::formatv("{0}{1}", prefix, result.getResultNumber()).str();
147}
148
149xegpu::DistributeLayoutAttr xegpu::getDistributeLayoutAttr(const Value value) {
150 if (!value)
151 return nullptr;
152
153 if (auto result = dyn_cast<OpResult>(value)) {
154 Operation *defOp = result.getDefiningOp();
155 assert(defOp && "result must have a defining op");
156
157 if (auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(defOp)) {
158 auto layout = anchorOp.getAnchorLayout();
159 return layout;
160 }
161
162 std::string layoutName = getTemporaryLayoutName(result);
163 if (defOp->hasAttr(layoutName)) {
164 auto layout =
165 defOp->getAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
166 return layout;
167 }
168 }
169
170 if (auto arg = dyn_cast<BlockArgument>(value)) {
171 auto *parentOp = arg.getOwner()->getParentOp();
172 if (auto loop = dyn_cast_if_present<LoopLikeOpInterface>(parentOp)) {
173 OpOperand *tiedInit = loop.getTiedLoopInit(arg);
174 if (tiedInit)
175 return getTemporaryLayout(*tiedInit);
176 }
177 }
178
179 if (auto tdescTy =
180 dyn_cast_if_present<xegpu::TensorDescType>(value.getType()))
181 return tdescTy.getLayoutAttr();
182
183 return nullptr;
184}
185xegpu::DistributeLayoutAttr
187 Operation *op = opr.getOwner();
188 unsigned idx = const_cast<OpOperand &>(opr).getOperandNumber();
189
190 if (auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(op)) {
191 if (auto dpasOp = dyn_cast<xegpu::DpasOp>(op)) {
192 if (idx == 0) {
193 return dpasOp.getLayoutAAttr();
194 } else if (idx == 1) {
195 return dpasOp.getLayoutBAttr();
196 } else if (idx == 2) {
197 return dpasOp.getLayoutCdAttr();
198 }
199 }
200 if (auto dpasMxOp = dyn_cast<xegpu::DpasMxOp>(op)) {
201 // DpasMxOp has operands: a, b, optional acc, optional scale_a, optional
202 // scale_b
203 unsigned currentIdx = 0;
204
205 if (idx == currentIdx++)
206 return dpasMxOp.getLayoutAAttr();
207
208 if (idx == currentIdx++)
209 return dpasMxOp.getLayoutBAttr();
210
211 if (dpasMxOp.getAcc())
212 if (idx == currentIdx++)
213 return dpasMxOp.getLayoutCdAttr();
214
215 if (dpasMxOp.getScaleA())
216 if (idx == currentIdx++)
217 return dpasMxOp.getLayoutAScaleAttr();
218
219 if (dpasMxOp.getScaleB())
220 if (idx == currentIdx++)
221 return dpasMxOp.getLayoutBScaleAttr();
222
223 return nullptr;
224 }
225 if (auto convertOp = dyn_cast<xegpu::ConvertLayoutOp>(op)) {
226 return convertOp.getEffectiveInputLayout();
227 }
228 auto layout = anchorOp.getAnchorLayout();
229
230 if (idx == 0)
231 return layout;
232
233 // For StoreNdOp and StoreMatrixOp,
234 // the layout is valid for the first two operands: value and memref/tdesc.
235 if (isa<xegpu::StoreNdOp, xegpu::StoreMatrixOp>(op) && (idx < 2))
236 return layout;
237
238 if (isa<xegpu::StoreScatterOp>(op)) {
239 xegpu::StoreScatterOp store(op);
240 int chunkSize = store.getChunkSize().value_or(1);
241 if (layout && idx >= 2 && chunkSize > 1)
242 return layout.dropDims(llvm::to_vector(
243 llvm::seq<int64_t>(layout.getRank() - 1, layout.getRank())));
244 return layout;
245 }
246 if (isa<xegpu::LoadGatherOp>(op)) {
247 xegpu::LoadGatherOp load(op);
248 int chunkSize = load.getChunkSize().value_or(1);
249 if (layout && idx >= 1 && chunkSize > 1)
250 return layout.dropDims(llvm::to_vector(
251 llvm::seq<int64_t>(layout.getRank() - 1, layout.getRank())));
252 return layout;
253 }
254 }
255
256 std::string layoutName = xegpu::getTemporaryLayoutName(opr);
257 if (op->hasAttr(layoutName)) {
258 auto layout = op->getAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
259 return layout;
260 }
261
262 return nullptr;
263}
264
265// Returns the permanent layout attribute for the given result if it's
266// available on the defining op. Otherwise returns the provided layout.
267xegpu::DistributeLayoutAttr
268maybePickPermanentLayout(xegpu::DistributeLayoutAttr layout,
269 const OpResult &result, mlir::Operation *owner,
270 const std::string &name) {
271 xegpu::DistributeLayoutAttr candidate = layout;
272
273 if (auto loadOp = dyn_cast<xegpu::LoadGatherOp>(owner)) {
274 if (auto perm = loadOp.getLayoutAttr())
275 candidate = perm;
276 }
277
278 return candidate;
279}
280
281// Returns the permanent layout attribute for the given operand if it's
282// available on the defining op. Otherwise returns the provided layout.
283xegpu::DistributeLayoutAttr
284maybePickPermanentLayout(xegpu::DistributeLayoutAttr layout,
285 const OpOperand &operand, mlir::Operation *owner,
286 const std::string &name) {
287 xegpu::DistributeLayoutAttr candidate = layout;
288 unsigned idx = const_cast<OpOperand &>(operand).getOperandNumber();
289
290 if (auto storeOp = dyn_cast<xegpu::StoreScatterOp>(owner)) {
291 if (idx == 0) {
292 if (auto perm = storeOp.getLayoutAttr())
293 candidate = perm;
294 }
295 }
296
297 return candidate;
298}
299
300// TODO-LayoutRefactor: Remove this function after replacing use
301// with setTemporaryLayout or setAnchorLayout
303 const mlir::OpResult &result,
304 const mlir::xegpu::DistributeLayoutAttr layout) {
305 Operation *owner = result.getOwner();
306
307 if (auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(owner)) {
308 if (anchorOp.getAnchorLayout() == layout)
309 return;
310 anchorOp.setAnchorLayout(layout);
311 return;
312 }
313
314 std::string name = xegpu::getTemporaryLayoutName(result);
315 if (owner->hasAttrOfType<DistributeLayoutAttr>(name)) {
316 return;
317 }
318 if (layout) {
319 owner->setAttr(name, layout);
320 }
321}
322
323// TODO-LayoutRefactor: Remove this function after replacing use
324// with setTemporaryLayout or setAnchorLayout
326 const DistributeLayoutAttr layout) {
327 Operation *owner = operand.getOwner();
328 unsigned idx = const_cast<OpOperand &>(operand).getOperandNumber();
329
330 if (!layout) {
331 return;
332 }
333 if (auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(owner)) {
334 if (auto dpasOp = dyn_cast<xegpu::DpasOp>(owner)) {
335 if (idx == 0) {
336 return dpasOp.setLayoutAAttr(layout);
337 } else if (idx == 1) {
338 return dpasOp.setLayoutBAttr(layout);
339 } else if (idx == 2) {
340 return dpasOp.setLayoutCdAttr(layout);
341 }
342 }
343 if (auto convertOp = dyn_cast<xegpu::ConvertLayoutOp>(owner)) {
344 return convertOp.setInputLayoutAttr(layout);
345 }
346
347 // For store operations (StoreScatterOp, StoreNdOp, StoreMatrixOp),
348 // the layout is valid for the first two operands: value and memref/tdesc.
349 // For other operations, the layout applies to the first operand only.
350 if (isa<xegpu::StoreScatterOp, xegpu::StoreNdOp, xegpu::StoreMatrixOp>(
351 owner)) {
352 if (idx < 2) {
353 anchorOp.setAnchorLayout(layout);
354 }
355 } else {
356 if (idx == 0) {
357 anchorOp.setAnchorLayout(layout);
358 }
359 }
360 }
361
362 std::string name = xegpu::getTemporaryLayoutName(operand);
363 if (owner->hasAttrOfType<DistributeLayoutAttr>(name)) {
364 return;
365 }
366 if (layout) {
367 owner->setAttr(name, layout);
368 }
369}
370
371template <typename T, typename>
372xegpu::DistributeLayoutAttr
373xegpu::getTemporaryLayout(const T &operandOrResult) {
374 Operation *op = operandOrResult.getOwner();
375
376 std::string layoutName = xegpu::getTemporaryLayoutName(operandOrResult);
377 if (op->hasAttr(layoutName)) {
378 auto layout = op->getAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
379 return layout;
380 }
381
382 return nullptr;
383}
384
385template xegpu::DistributeLayoutAttr
387template xegpu::DistributeLayoutAttr
389
390template <typename T, typename>
391void xegpu::setTemporaryLayout(const T &operandOrResult,
392 const xegpu::DistributeLayoutAttr layout) {
393 Operation *owner = operandOrResult.getOwner();
394 std::string name = xegpu::getTemporaryLayoutName(operandOrResult);
395 if (owner->hasAttrOfType<xegpu::DistributeLayoutAttr>(name)) {
396 return;
397 }
398 if (layout) {
399 owner->setAttr(name, layout);
400 }
401}
402
404 const mlir::OpResult &result,
405 const mlir::xegpu::DistributeLayoutAttr layout);
406
408 const mlir::OpOperand &operand,
409 const mlir::xegpu::DistributeLayoutAttr layout);
410
414 auto vecTy = dyn_cast<VectorType>(value.getType());
415 if (!vecTy)
416 return {value};
417
418 ArrayRef<int64_t> srcShape = vecTy.getShape();
419 if (!computeShapeRatio(srcShape, shape))
420 return {value};
421
422 int64_t srcShapeRank = srcShape.size();
423 int64_t targetShapeRank = shape.size();
424
425 SmallVector<int64_t> adjustedTargetShape(srcShape.size());
426 int64_t rankDiff = srcShapeRank - targetShapeRank;
427 std::fill(adjustedTargetShape.begin(), adjustedTargetShape.begin() + rankDiff,
428 1);
429 llvm::copy(shape, adjustedTargetShape.begin() + rankDiff);
430
432 for (SmallVector<int64_t> offsets :
433 StaticTileOffsetRange(srcShape, adjustedTargetShape)) {
434 SmallVector<int64_t> staticStrides(offsets.size(), 1);
435 Value slice = vector::ExtractStridedSliceOp::create(
436 builder, loc, value, offsets, adjustedTargetShape, staticStrides);
437
438 // Reshape to remove leading unit dims if needed
439 if (srcShapeRank > targetShapeRank) {
440 auto targetTy = VectorType::get(shape, vecTy.getElementType());
441 slice = vector::ShapeCastOp::create(builder, loc, targetTy, slice);
442 }
443 result.push_back(slice);
444 }
445
446 return result;
447}
448
450 ValueRange values,
452 VectorType inputTy = dyn_cast<VectorType>(values[0].getType());
453 assert(llvm::all_of(values.getTypes(),
454 [&](Type type) { return type == inputTy; }) &&
455 "values must be of the same VectorType");
456
457 Type elemTy = inputTy.getElementType();
458 ArrayRef<int64_t> tileShape = inputTy.getShape();
459
460 VectorType resultTy = VectorType::get(shape, elemTy);
461 auto zeroAttr = builder.getZeroAttr(elemTy);
462 Value result = arith::ConstantOp::create(
463 builder, loc, resultTy, DenseElementsAttr::get(resultTy, zeroAttr));
464
465 for (auto [src, offsets] :
466 llvm::zip_equal(values, StaticTileOffsetRange(shape, tileShape))) {
467 SmallVector<int64_t> staticStrides(tileShape.size(), 1);
468 result = vector::InsertStridedSliceOp::create(builder, loc, src, result,
469 offsets, staticStrides);
470 }
471 return result;
472}
473
474std::optional<std::string> xegpu::getChipStr(Operation *op) {
475 auto gpuModuleOp = op->getParentOfType<gpu::GPUModuleOp>();
476
477 if (!gpuModuleOp)
478 return std::nullopt;
479
480 auto targetAttrs = gpuModuleOp.getTargets();
481 if (targetAttrs) {
482 for (auto &attr : *targetAttrs) {
483 auto xevmAttr = llvm::dyn_cast<xevm::XeVMTargetAttr>(attr);
484 if (xevmAttr)
485 return xevmAttr.getChip().str();
486 }
487 }
488
489 return std::nullopt;
490}
491
492/// Generates element-wise addition ops of two arrays with same length.
494 Location loc,
497 assert(lhs.size() == rhs.size() && "lhs and rhs must have the same size");
499 for (auto [l, r] : llvm::zip_equal(lhs, rhs)) {
500 auto lval = getValueOrCreateConstantIndexOp(builder, loc, l);
501 auto rval = getValueOrCreateConstantIndexOp(builder, loc, r);
502 results.push_back(builder.createOrFold<arith::AddIOp>(loc, lval, rval));
503 }
504 return results;
505}
506
507/// Generates element-wise addition ops of two arrays with automatic alignment.
508/// When the input arrays have different sizes, the shorter array is
509/// right-aligned with the longer array, and the unmatched leading elements from
510/// the longer array are preserved unchanged. This is commonly used for offset
511/// computation where higher-dimensional offsets need to be added to
512/// lower-dimensional adjustments.
513///
514/// Example:
515/// lhs = [l1, l2, l3], rhs = [r1, r2]
516/// Result: [11, l2+r1, l3+r2]
521 // ensure a is longer than b
522 ArrayRef<OpFoldResult> a = lhs.size() >= rhs.size() ? lhs : rhs;
523 ArrayRef<OpFoldResult> b = lhs.size() >= rhs.size() ? rhs : lhs;
524 SmallVector<OpFoldResult> results(a.take_front(a.size() - b.size()));
525 a = a.slice(a.size() - b.size());
526 results.append(addElementwise(builder, loc, a, b));
527 return results;
528}
529
530template <typename T>
532 ArrayRef<T> candidateMultiples) {
533 static_assert(std::is_integral<T>::value, "T must be an integer type");
534 int largest = -1;
535 SmallVector<T> multiples = {1};
536 if (!candidateMultiples.empty())
537 multiples =
538 SmallVector<T>(candidateMultiples.begin(), candidateMultiples.end());
539 for (T candidate : candidates) {
540 for (T multiple : multiples) {
541 int value = static_cast<int>(candidate * multiple);
542 if (value != 0 && dim % value == 0 && value > largest)
543 largest = value;
544 }
545 }
546 return largest;
547}
548
550 vector::CombiningKind kind, uint32_t size) {
551 // First reduce on a single thread to get per lane reduction value.
552 Value laneVal = vector::ReductionOp::create(builder, loc, kind, input);
553 // Parallel reduction using butterfly shuffles.
554 for (uint64_t i = 1; i < size; i <<= 1) {
555 Value shuffled =
556 gpu::ShuffleOp::create(builder, loc, laneVal, i, /** width = **/ size,
557 /** mode = **/ gpu::ShuffleMode::XOR)
558 .getShuffleResult();
559 laneVal = makeArithReduction(builder, loc, kind, laneVal, shuffled);
560 }
561 return laneVal;
562}
563
566 vector::CombiningKind kind,
567 int64_t reductionDim, Location loc,
568 PatternRewriter &rewriter) {
569 VectorType sourceType = src.getType();
570 int64_t sourceRank = sourceType.getRank();
571 // Expecting at least a 2D source vector. Leading dimensions (all except the
572 // last two) must be unit.
573 assert(sourceRank >= 2 && "expected at least a 2D source vector");
574 for (int64_t i = 0; i < sourceRank - 2; ++i)
575 assert(sourceType.getShape()[i] == 1 &&
576 "expected leading dimensions to be unit");
577 int64_t rowIdx = sourceRank - 2;
578 int64_t columnIdx = sourceRank - 1;
579 int64_t sourceH = sourceType.getShape()[rowIdx];
580 int64_t sourceW = sourceType.getShape()[columnIdx];
581 int nSlices = (reductionDim == rowIdx) ? sourceW : sourceH;
582 // Create a constant vector to hold the result of the reduction.
583 TypedAttr zeroAttr = rewriter.getZeroAttr(sourceType.getElementType());
584 Value reductionResult = arith::ConstantOp::create(
585 rewriter, loc, acc.getType(),
586 DenseElementsAttr::get(acc.getType(), zeroAttr));
587 auto srcLayout = xegpu::getTemporaryLayout(dyn_cast<OpResult>(src));
588 auto accLayout = xegpu::getTemporaryLayout(dyn_cast<OpResult>(acc));
589 // Reduction result should have the same layout as the accumulator.
590 xegpu::setTemporaryLayout(cast<OpResult>(reductionResult), accLayout);
591 // For each slice of the source, extract the slice vector, do a reduction
592 // and, insert the reduced value back to the result vector.
593 int64_t accRank = acc.getType().getRank();
594 for (int i = 0; i < nSlices; ++i) {
595 // Build nD offsets, sizes, and strides. Leading unit dims get
596 // offset=0, size=1. The last two dims are set based on reductionDim.
597 SmallVector<int64_t> sliceOffsets(sourceRank, 0);
598 SmallVector<int64_t> sliceSizes(sourceRank, 1);
599 SmallVector<int64_t> strides(sourceRank, 1);
600 if (reductionDim == columnIdx) {
601 sliceOffsets[rowIdx] = i;
602 sliceSizes[columnIdx] = sourceW;
603 } else {
604 sliceOffsets[columnIdx] = i;
605 sliceSizes[rowIdx] = sourceH;
606 }
607
608 vector::ExtractStridedSliceOp extractOp =
609 vector::ExtractStridedSliceOp::create(rewriter, loc, src, sliceOffsets,
610 sliceSizes, strides);
611 // Extract strided slice has the same layout as src.
612 xegpu::setTemporaryLayout(extractOp->getOpResult(0), srcLayout);
613
614 int64_t nSliceElements = extractOp.getResult().getType().getNumElements();
615
616 vector::ShapeCastOp slice = vector::ShapeCastOp::create(
617 rewriter, loc,
618 VectorType::get({nSliceElements}, sourceType.getElementType()),
619 extractOp.getResult());
620
621 // Shape cast output has the same layout as the accumulator. Shape cast
622 // source has the same layout as the original reduction source.
623 xegpu::setTemporaryLayout(slice->getOpOperand(0), srcLayout);
624 xegpu::setTemporaryLayout(slice->getOpResult(0), accLayout);
625 // Extract and reduction results in scalars, so no result layout is needed.
626 // Build multi-dim index into acc (sourceRank-1 dims, i.e. source shape with
627 // the reduction dim removed). Leading unit dims get index 0.
628 SmallVector<int64_t> accIdx(accRank, 0);
629 accIdx[accRank - 1] = i;
630 Value accExtract = vector::ExtractOp::create(rewriter, loc, acc, accIdx);
631 Value reduction = vector::ReductionOp::create(
632 rewriter, loc, kind, slice.getResult(), accExtract);
633 reductionResult = vector::InsertOp::create(rewriter, loc, reduction,
634 reductionResult, accIdx);
635 // Insert op should have the same layout as the accumulator.
636 xegpu::setTemporaryLayout(cast<OpResult>(reductionResult), accLayout);
637 }
638 return reductionResult;
639}
640
643 vector::CombiningKind kind, int64_t reductionDim, int64_t reductionSize,
644 Location loc, PatternRewriter &rewriter) {
645 VectorType sourceType = src.getType();
646 int64_t sourceRank = sourceType.getRank();
647 // Expecting at least a 2D source vector. Leading dimensions (all except the
648 // last two) must be unit.
649 assert(sourceRank >= 2 && "expected at least a 2D source vector");
650 for (int64_t i = 0; i < sourceRank - 2; ++i)
651 assert(sourceType.getShape()[i] == 1 &&
652 "expected leading dimensions to be unit");
653 int64_t rowIdx = sourceRank - 2;
654 int64_t columnIdx = sourceRank - 1;
655 int64_t sourceH = sourceType.getShape()[rowIdx];
656 int64_t sourceW = sourceType.getShape()[columnIdx];
657
658 // Create a constant vector to hold the result of the reduction.
659 TypedAttr zeroAttr = rewriter.getZeroAttr(sourceType.getElementType());
660 Value reductionResult = arith::ConstantOp::create(
661 rewriter, loc, acc.getType(),
662 DenseElementsAttr::get(acc.getType(), zeroAttr));
663
664 // nSlices is the number of reduction operations needed to reduce the entire
665 // source vector. For example, if reductionDim is the row dim, we are
666 // reducing across rows, and each slice is a column. So the number of slices
667 // is the number of columns, which is sourceW.
668 int nSlices = (reductionDim == rowIdx) ? sourceW : sourceH;
669
670 // For each slice of the source, extract the slice vector, do a reduction
671 // and, insert the reduced value back to the result vector.
672 int64_t accRank = acc.getType().getRank();
673 for (int i = 0; i < nSlices; ++i) {
674 // Build nD offsets, sizes, and strides. Leading unit dims get
675 // offset=0, size=1. The last two dims are set based on reductionDim.
676 SmallVector<int64_t> sliceOffsets(sourceRank, 0);
677 SmallVector<int64_t> sliceSizes(sourceRank, 1);
678 SmallVector<int64_t> strides(sourceRank, 1);
679 if (reductionDim == columnIdx) {
680 sliceOffsets[rowIdx] = i;
681 sliceSizes[columnIdx] = sourceW;
682 } else {
683 sliceOffsets[columnIdx] = i;
684 sliceSizes[rowIdx] = sourceH;
685 }
686
687 vector::ExtractStridedSliceOp extractOp =
688 vector::ExtractStridedSliceOp::create(rewriter, loc, src, sliceOffsets,
689 sliceSizes, strides);
690 int64_t nSliceElements = extractOp.getResult().getType().getNumElements();
691 vector::ShapeCastOp slice = vector::ShapeCastOp::create(
692 rewriter, loc,
693 VectorType::get({nSliceElements}, sourceType.getElementType()),
694 extractOp.getResult());
695
696 SmallVector<int64_t> accIdx(accRank, 0);
697 accIdx[accRank - 1] = i;
698 Value accExtract = vector::ExtractOp::create(rewriter, loc, acc, accIdx);
699 Value fullReduce =
700 xegpu::subgroupReduction(loc, rewriter, slice, kind, reductionSize);
701 fullReduce =
702 vector::makeArithReduction(rewriter, loc, kind, fullReduce, accExtract);
703 reductionResult = vector::InsertOp::create(rewriter, loc, fullReduce,
704 reductionResult, accIdx);
705 }
706 return reductionResult;
707}
708
710 Type type,
711 vector::CombiningKind kind) {
712 auto vecTy = dyn_cast<VectorType>(type);
713 Type elemTy = vecTy ? vecTy.getElementType() : type;
714
715 // Helper to create either a splat vector or scalar constant from an attr.
716 auto makeConst = [&](Attribute scalarAttr) -> Value {
717 if (vecTy)
718 return arith::ConstantOp::create(
719 builder, loc, vecTy, DenseElementsAttr::get(vecTy, scalarAttr));
720 return arith::ConstantOp::create(builder, loc, cast<TypedAttr>(scalarAttr));
721 };
722
723 switch (kind) {
724 case vector::CombiningKind::ADD:
725 case vector::CombiningKind::XOR:
726 case vector::CombiningKind::OR:
727 case vector::CombiningKind::MAXUI:
728 return makeConst(builder.getZeroAttr(elemTy));
729
730 case vector::CombiningKind::MUL:
731 case vector::CombiningKind::AND:
732 return makeConst(builder.getOneAttr(elemTy));
733
734 case vector::CombiningKind::MINSI:
735 if (auto intTy = dyn_cast<IntegerType>(elemTy))
736 return makeConst(builder.getIntegerAttr(
737 elemTy, APInt::getSignedMaxValue(intTy.getWidth())));
738 return nullptr;
739
740 case vector::CombiningKind::MINUI:
741 if (auto intTy = dyn_cast<IntegerType>(elemTy))
742 return makeConst(
743 builder.getIntegerAttr(elemTy, APInt::getMaxValue(intTy.getWidth())));
744 return nullptr;
745
746 case vector::CombiningKind::MAXSI:
747 if (auto intTy = dyn_cast<IntegerType>(elemTy))
748 return makeConst(builder.getIntegerAttr(
749 elemTy, APInt::getSignedMinValue(intTy.getWidth())));
750 return nullptr;
751
752 case vector::CombiningKind::MINNUMF:
753 case vector::CombiningKind::MINIMUMF:
754 if (auto floatTy = dyn_cast<FloatType>(elemTy))
755 return makeConst(builder.getFloatAttr(
756 elemTy, APFloat::getInf(floatTy.getFloatSemantics())));
757 return nullptr;
758
759 case vector::CombiningKind::MAXNUMF:
760 case vector::CombiningKind::MAXIMUMF:
761 if (auto floatTy = dyn_cast<FloatType>(elemTy))
762 return makeConst(builder.getFloatAttr(
763 elemTy, APFloat::getInf(floatTy.getFloatSemantics(), true)));
764 return nullptr;
765 }
766 return nullptr;
767}
768
769/// Explicit instantiations
770template int xegpu::getLargestDivisor<int>(int dim, ArrayRef<int> candidates,
771 ArrayRef<int> candidateMultiples);
772template int
774 ArrayRef<unsigned> candidateMultiples);
775
776std::optional<SmallVector<int64_t>>
778 if (vals.size() < 2)
779 return std::nullopt;
780 if (llvm::any_of(vals.drop_back(2), [](int64_t v) { return v != 1; }))
781 return std::nullopt;
782 return SmallVector<int64_t>(vals.take_back(2));
783}
784
785bool xegpu::requirePacked(const xegpu::DistributeLayoutAttr layout) {
786 if (!layout)
787 return false;
788 auto laneData =
789 getInner2DIfUnitLeadingDims(layout.getEffectiveLaneDataAsInt());
790 return laneData && (*laneData)[0] != 1;
791}
792
793bool xegpu::requireTranspose(const xegpu::DistributeLayoutAttr layout,
794 const xegpu::uArch::uArch *uArch) {
795 // Return false for unsupported targets.
796 // TODO: Add more support or move to target info.
797 if (!isa<xegpu::uArch::Xe2>(uArch) && !isa<xegpu::uArch::Xe3>(uArch))
798 return false;
799 if (!layout)
800 return false;
801 auto laneLayout =
802 getInner2DIfUnitLeadingDims(layout.getEffectiveLaneLayoutAsInt());
803 return laneLayout && (*laneLayout)[0] == uArch->getSubgroupSize() &&
804 (*laneLayout)[1] == 1;
805}
806
807bool xegpu::hasStaticShapeAndStrides(MemRefType type) {
808 if (!type.hasStaticShape())
809 return false;
810 SmallVector<int64_t> strides;
811 int64_t offset;
812 return succeeded(type.getStridesAndOffset(strides, offset)) &&
813 llvm::none_of(strides, ShapedType::isDynamic);
814}
815
816// Check if dst shape is an expansion of src shape by inserting unit dimensions.
817// Returns true if all dimensions in src match corresponding dimensions in dst
818// (after skipping unit dimensions), and populates expandedUnitDims with the
819// indices of the unit dimensions in dst that were added (not present in src).
820// Example: src=[2,3], dst=[1,2,3,1] -> true, expandedUnitDims=[0,3]
822 SmallVector<int64_t> &expandedUnitDims) {
823 // All unit dimensions in dst that don't appear in src are the expanded
824 // unit dimensions
825 size_t srcIdx = 0;
826 for (size_t dstIdx = 0; dstIdx < dst.size(); ++dstIdx)
827 if (srcIdx < src.size() && src[srcIdx] == dst[dstIdx])
828 srcIdx++;
829 else if (dst[dstIdx] == 1)
830 expandedUnitDims.push_back(dstIdx);
831 else
832 return false;
833 return srcIdx == src.size();
834}
835
836// Checks if dst shape is an expansion of src shape where each dimension in src
837// is split into one or more consecutive dimensions in dst whose product equals
838// the original dimension. Populates splitDimGroups with groups of dst indices
839// that correspond to each src dimension. Example: src=[6,4], dst=[2,3,2,2] ->
840// true
843 SmallVector<SmallVector<int64_t>> &splitDimGroups) {
844 // each dim in src can be mapped to one or more dims in dst whose product
845 // equals to the src dim
846 size_t srcIdx = 0;
847 int64_t accumulatedSize = 1;
848 SmallVector<int64_t> currentDstDims;
849
850 splitDimGroups.clear();
851 for (size_t dstIdx = 0; dstIdx < dst.size(); ++dstIdx) {
852 if (srcIdx >= src.size())
853 return false;
854 accumulatedSize *= dst[dstIdx];
855 currentDstDims.push_back(dstIdx);
856
857 if (accumulatedSize == src[srcIdx]) {
858 // Also collect trailing unit dims in destination, if any.
859 // Leading unit dims were implicitly collected.
860 if (srcIdx == src.size() - 1) {
861 while (++dstIdx < dst.size() && dst[dstIdx] == 1)
862 currentDstDims.push_back(dstIdx);
863 }
864 // Record the mapping: srcIdx -> currentDstDims
865 splitDimGroups.push_back(currentDstDims);
866 // move to next src dim
867 srcIdx++;
868 accumulatedSize = 1;
869 currentDstDims.clear();
870 } else if (accumulatedSize > src[srcIdx]) {
871 return false;
872 }
873 }
874 return srcIdx == src.size();
875}
876
877//===----------------------------------------------------------------------===//
878// Context-aware type conversion utilities
879//===----------------------------------------------------------------------===//
880
881// Pre-computes distributed VectorType mappings for every value carried through
882// an SCF region-branch op (scf.while, scf.for, scf.if): block args (iter_args /
883// before-/after-args), op results, and the terminator operands feeding them.
884// These positions share one logical value and must convert identically, so each
885// is derived from a single source -- the layout of the feeding value (loop
886// init, `scf.condition` operand, or `scf.if` result) -- via
887// `getDistributeLayoutAttr(Value)`, and keyed by `Value`. Keying by Value is
888// required because the SCF converters
889// detach/replace the loop body mid-conversion (scf.while detaches before/after
890// blocks -> a detached-arg layout query trips an ilist assertion; scf.for
891// rebuilds the op, which loses the temporary `layout_operand_N` attrs -> the
892// query returns null). Recording results and terminator operands lets a 1:N
893// pass resolve them from the map after stripping the loop op's transient attrs
894// (see XeGPUBlocking).
897 SubShapeAndCountFn getSubShapeAndCount) {
899 // Derive the distributed types from the feeding value's layout (the single
900 // authoritative source) and record them for every value that shares this
901 // loop-carried position.
902 auto recordTypes = [&](Value layoutSrc, ArrayRef<Value> dests) {
903 auto vecTy = dyn_cast<VectorType>(layoutSrc.getType());
904 if (!vecTy)
905 return;
906 auto layout = xegpu::getDistributeLayoutAttr(layoutSrc);
907 if (!layout)
908 return;
909 auto [subShape, count] = getSubShapeAndCount(vecTy, layout);
910 if (count <= 0)
911 return;
912 auto newTy = VectorType::get(subShape, vecTy.getElementType());
913 for (Value dest : dests)
914 loopArgTypes[dest] = SmallVector<Type>(count, newTy);
915 };
916 topLevelOp->walk([&](Operation *op) {
917 if (auto whileOp = dyn_cast<scf::WhileOp>(op)) {
918 // "before" args (and the after-region yield operands that feed them)
919 // correspond to the while `inits` operands.
920 auto yieldOp =
921 cast<scf::YieldOp>(whileOp.getAfterBody()->getTerminator());
922 for (auto [init, beforeArg, yieldVal] :
923 llvm::zip(whileOp.getInits(), whileOp.getBeforeArguments(),
924 yieldOp.getOperands()))
925 recordTypes(init, {beforeArg, yieldVal});
926 // "after" args and the while results correspond to the operands of the
927 // embedded `scf.condition` op (not the `inits`).
928 scf::ConditionOp condOp = whileOp.getConditionOp();
929 for (auto [condArg, afterArg, res] :
930 llvm::zip(condOp.getArgs(), whileOp.getAfterArguments(),
931 whileOp.getResults()))
932 recordTypes(condArg, {afterArg, res});
933 return;
934 }
935 if (auto forOp = dyn_cast<scf::ForOp>(op)) {
936 // Each loop-carried position pairs an init operand with its iter_arg,
937 // its loop result, and the yield operand that feeds the next iteration.
938 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
939 for (auto [init, arg, res, yieldVal] :
940 llvm::zip(forOp.getInitArgs(), forOp.getRegionIterArgs(),
941 forOp.getResults(), yieldOp.getOperands()))
942 recordTypes(init, {arg, res, yieldVal});
943 return;
944 }
945 if (auto ifOp = dyn_cast<scf::IfOp>(op)) {
946 // Each result and its then/else yield operands share one position and
947 // must convert identically; derive all from the result's layout.
948 scf::YieldOp thenYield = ifOp.thenYield();
949 scf::YieldOp elseYield = ifOp.elseBlock() ? ifOp.elseYield() : nullptr;
950 for (auto [idx, res] : llvm::enumerate(ifOp.getResults())) {
951 SmallVector<Value> dests{res, thenYield.getOperand(idx)};
952 if (elseYield)
953 dests.push_back(elseYield.getOperand(idx));
954 recordTypes(res, dests);
955 }
956 return;
957 }
958 });
959 return loopArgTypes;
960}
961
963 TypeConverter &converter, SubShapeAndCountFn getSubShapeAndCount,
964 DenseMap<Value, SmallVector<Type>> loopArgTypes) {
965 // Context-aware VectorType conversion (1:1 shape-changing or 1:N). For
966 // SCF loop block arguments (scf.while, scf.for), uses the pre-computed
967 // map. For all other Values, retrieves the layout directly via
968 // getDistributeLayoutAttr.
969 auto loopArgTypeMap = std::make_shared<DenseMap<Value, SmallVector<Type>>>(
970 std::move(loopArgTypes));
971 converter.addConversion(
972 [loopArgTypeMap, getSubShapeAndCount](
973 Value v,
974 SmallVectorImpl<Type> &result) -> std::optional<LogicalResult> {
975 if (!isa<VectorType>(v.getType()))
976 return std::nullopt;
977
978 // Check the pre-computed map first. It covers every value carried
979 // through an SCF loop (operands, block args, results, yield
980 // operands), all keyed by Value identity.
981 auto it = loopArgTypeMap->find(v);
982 if (it != loopArgTypeMap->end()) {
983 result.append(it->second.begin(), it->second.end());
984 return success();
985 }
986
987 // For all other Values, retrieve the layout directly.
988 auto layout = xegpu::getDistributeLayoutAttr(v);
989 if (!layout)
990 return std::nullopt;
991
992 auto vecType = cast<VectorType>(v.getType());
993 auto [subShape, count] = getSubShapeAndCount(vecType, layout);
994 if (count <= 0)
995 return std::nullopt;
996
997 auto newTy = VectorType::get(subShape, vecType.getElementType());
998 result.append(count, newTy);
999 return success();
1000 });
1001}
1002
1004 Operation *root,
1005 const llvm::SmallSetVector<UnrealizedConversionCastOp, 8> &existingCasts) {
1006 // Structural type conversion can generate some redundant
1007 // UnrealizedConversionCastOps to materialize the original type from the
1008 // type converted (sub-tile) type. These are redundant at this point and
1009 // can be eliminated by either folding the cancelling cast chain or, when
1010 // the original and final shapes differ but their element counts match,
1011 // inserting a vector.shape_cast instead.
1012 //
1013 // Example (shape differs but element count matches -> shape_cast):
1014 // %1 = UnrealizedConversionCastOp %0 : vector<16x1xf32>
1015 // to vector<16x16xf32>
1016 // %2 = UnrealizedConversionCastOp %1 : vector<16x16xf32>
1017 // to vector<16xf32>
1018 // becomes:
1019 // %2 = vector.shape_cast %0 : vector<16x1xf32> to vector<16xf32>
1020 //
1021 // For unpaired casts that emulate a pack (1:N) or unpack (N:1) between a
1022 // single large VectorType and N identically-typed smaller VectorTypes,
1023 // lower to vector.extract_strided_slice / vector.insert_strided_slice.
1024 auto hasIdenticalVectorTypes = [](ValueRange values) {
1025 auto types = values.getTypes();
1026 return !types.empty() && llvm::all_of(types, [&](Type type) {
1027 return isa<VectorType>(type) && type == types.front();
1028 });
1029 };
1030 OpBuilder builder(root);
1031 root->walk([&](UnrealizedConversionCastOp op) {
1032 if (existingCasts.contains(op))
1033 return;
1034 // Handle N:1 cast (N >= 1) where all inputs come from a single 1:N cast.
1035 if (op.getNumResults() == 1 && op.getNumOperands() >= 1) {
1036 auto defOp =
1037 op.getInputs()[0].getDefiningOp<UnrealizedConversionCastOp>();
1038 if (defOp && !existingCasts.contains(defOp) &&
1039 defOp.getNumOperands() == 1 &&
1040 defOp.getNumResults() == op.getNumOperands() &&
1041 llvm::all_of(op.getInputs(),
1042 [&](Value v) { return v.getDefiningOp() == defOp; })) {
1043 Value orig = defOp.getInputs()[0];
1044 auto origTy = dyn_cast<VectorType>(orig.getType());
1045 auto resTy = dyn_cast<VectorType>(op.getResult(0).getType());
1046 if (origTy && resTy &&
1047 origTy.getNumElements() == resTy.getNumElements() &&
1048 origTy != resTy) {
1049 builder.setInsertionPoint(op);
1050 auto shapeCast =
1051 vector::ShapeCastOp::create(builder, op.getLoc(), resTy, orig);
1052 op.replaceAllUsesWith(ValueRange{shapeCast.getResult()});
1053 } else {
1054 op.replaceAllUsesWith(ValueRange{orig});
1055 }
1056 return;
1057 }
1058 // Unpaired N:1 cast emulating unpack: stitch inputs into the output
1059 // shape via vector.insert_strided_slice.
1060 auto outputTy = dyn_cast<VectorType>(op.getResult(0).getType());
1061 if (op.getNumOperands() > 1 && outputTy &&
1062 hasIdenticalVectorTypes(op.getInputs())) {
1063 builder.setInsertionPoint(op);
1065 builder, op.getLoc(), op.getInputs(), outputTy.getShape());
1066 op->replaceAllUsesWith(ValueRange(result));
1067 }
1068 return;
1069 }
1070 // Handle 1:N cast where the single input comes from an N:1 cast.
1071 if (op.getNumOperands() == 1 && op.getNumResults() > 1) {
1072 auto defOp =
1073 op.getInputs()[0].getDefiningOp<UnrealizedConversionCastOp>();
1074 if (defOp && !existingCasts.contains(defOp) &&
1075 defOp.getNumResults() == 1 &&
1076 defOp.getNumOperands() == op.getNumResults() &&
1077 llvm::equal(ValueRange(defOp.getInputs()).getTypes(),
1078 op->getResultTypes())) {
1079 op.replaceAllUsesWith(defOp.getInputs());
1080 return;
1081 }
1082 // Unpaired 1:N cast emulating pack: split the input into the output
1083 // tile shape via vector.extract_strided_slice.
1084 auto tileTy = dyn_cast<VectorType>(op.getResult(0).getType());
1085 if (tileTy && hasIdenticalVectorTypes(op.getResults())) {
1086 builder.setInsertionPoint(op);
1088 builder, op.getLoc(), op.getInputs()[0], tileTy.getShape());
1089 op->replaceAllUsesWith(results);
1090 }
1091 return;
1092 }
1093 });
1094
1095 // Erase dead casts iteratively.
1096 bool changed = true;
1097 while (changed) {
1098 changed = false;
1099 root->walk([&](UnrealizedConversionCastOp op) {
1100 if (existingCasts.contains(op))
1101 return;
1102 if (op.use_empty()) {
1103 op.erase();
1104 changed = true;
1105 }
1106 });
1107 }
1108}
1109
1110// Checks if dst shape is a collapse of src shape where each dim in dst is
1111// produced by one or more consecutive dims in src whose product equals the dst
1112// dim. Populates collapseDims with one group per dst dim listing the src
1113// indices collapsed into it. Unit dims in dst that have no backing src dim
1114// (leading, in-between, or trailing) get empty groups; src unit dims that
1115// fall past the last consumed dst dim are absorbed into the most-recent
1116// non-empty group.
1117// Examples:
1118// src=[8,16,32], dst=[1,4096] -> true, collapseDims=[[],[0,1,2]]
1119// src=[8,16,32], dst=[4096,1] -> true, collapseDims=[[0,1,2],[]]
1120// src=[2,3,4], dst=[6,4] -> true, collapseDims=[[0,1],[2]]
1121// src=[64], dst=[64] -> true, collapseDims=[[0]]
1123 SmallVector<SmallVector<int64_t>> &collapseDims) {
1124 collapseDims.clear();
1125 collapseDims.resize(dst.size());
1126
1127 // Cheap precondition: src and dst must describe the same number of
1128 // elements. Bails out early on mismatched shapes without walking the dims.
1129 int64_t srcProd = std::accumulate(src.begin(), src.end(), int64_t{1},
1130 std::multiplies<int64_t>());
1131 int64_t dstProd = std::accumulate(dst.begin(), dst.end(), int64_t{1},
1132 std::multiplies<int64_t>());
1133 if (srcProd != dstProd)
1134 return false;
1135
1136 // Step 1: validate the partition on the unit-dim-stripped (compact) shapes.
1137 // Unit dims play no role in the matching decision — they only need to be
1138 // placed somewhere in the final groups (handled in step 2).
1139 SmallVector<int64_t> srcCompact, dstCompact;
1140 for (int64_t s : src)
1141 if (s != 1)
1142 srcCompact.push_back(s);
1143 for (int64_t d : dst)
1144 if (d != 1)
1145 dstCompact.push_back(d);
1146
1147 size_t s = 0;
1148 for (int64_t need : dstCompact) {
1149 int64_t acc = 1;
1150 while (s < srcCompact.size() && acc < need)
1151 acc *= srcCompact[s++];
1152 if (acc != need)
1153 return false;
1154 }
1155 if (s != srcCompact.size())
1156 return false;
1157
1158 // Step 2: assign each original src index to the correct original dst group.
1159 // Walk dst in original order, advancing past unit dst dims (they keep their
1160 // pre-initialized empty group). Walk src in original order; non-unit src
1161 // dims accumulate into the current dst group, unit src dims attach to the
1162 // current group when one is open or to the most-recent non-empty group
1163 // after dst is exhausted (leading unit src dims with no group yet are
1164 // dropped).
1165 size_t dstIdx = 0;
1166 while (dstIdx < dst.size() && dst[dstIdx] == 1)
1167 dstIdx++;
1168
1169 int64_t lastNonEmpty = -1;
1170 int64_t acc = 1;
1171 for (size_t srcIdx = 0; srcIdx < src.size(); ++srcIdx) {
1172 if (dstIdx >= dst.size()) {
1173 // dst exhausted; remaining src dims are unit (validated above) and
1174 // attach to the last non-empty group, if any.
1175 if (lastNonEmpty >= 0)
1176 collapseDims[lastNonEmpty].push_back(srcIdx);
1177 continue;
1178 }
1179 acc *= src[srcIdx];
1180 collapseDims[dstIdx].push_back(srcIdx);
1181 lastNonEmpty = dstIdx;
1182 if (acc == dst[dstIdx]) {
1183 acc = 1;
1184 ++dstIdx;
1185 while (dstIdx < dst.size() && dst[dstIdx] == 1)
1186 ++dstIdx;
1187 }
1188 }
1189 return true;
1190}
return success()
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
auto load
xegpu::DistributeLayoutAttr maybePickPermanentLayout(xegpu::DistributeLayoutAttr layout, const OpResult &result, mlir::Operation *owner, const std::string &name)
Attributes are known-constant values of operations.
Definition Attributes.h:25
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
TypedAttr getOneAttr(Type type)
Definition Builders.cpp:351
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
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 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:528
This class represents an operand of an operation.
Definition Value.h:254
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
AttrClass getAttrOfType(StringAttr name)
Definition Operation.h:595
bool hasAttrOfType(NameT &&name)
Definition Operation.h:620
bool hasAttr(StringAttr name)
Return true if the operation has an attribute with the provided name, false otherwise.
Definition Operation.h:605
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
void setAttr(StringAttr name, Attribute value)
If the an attribute exists with the specified name, change it to the new value.
Definition Operation.h:627
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:842
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
A range-style iterator that allows for iterating over the offsets of all potential tiles of size tile...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
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 * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Value makeArithReduction(OpBuilder &b, Location loc, CombiningKind kind, Value v1, Value acc, arith::FastMathFlagsAttr fastmath=nullptr, Value mask=nullptr)
Returns the result value of reducing two scalar/vector values with the corresponding arith operation.
bool matchDimCollapse(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< SmallVector< int64_t > > &collapseDims)
Value createVectorWithShapeFromValues(OpBuilder &builder, Location loc, ValueRange values, ArrayRef< int64_t > shape)
Create a vector of shape from a set of values using vector.insert_stride_slice.
bool requirePacked(const DistributeLayoutAttr layout)
Helper function to check if the layout is packed.
void setTemporaryLayout(const T &operandOrResult, const DistributeLayoutAttr layout)
Value createReductionNeutralValue(OpBuilder &builder, Location loc, Type type, vector::CombiningKind kind)
Creates a constant filled with the neutral (identity) value for the given reduction kind.
void setDistributeLayoutAttr(const OpResult &Result, const DistributeLayoutAttr layout)
[to-be-deprecated] Sets the DistributeLayoutAttr for a given OpResult user should use setAnchorLayout...
Value subgroupReduction(Location loc, OpBuilder &builder, Value input, vector::CombiningKind kind, uint32_t size)
Given an input value representing per-lane data, this function returns the result after performing a ...
bool matchUnitDimExpansion(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< int64_t > &expandedUnitDims)
std::optional< SmallVector< int64_t > > getInner2DIfUnitLeadingDims(ArrayRef< int64_t > vals)
Returns the innermost 2 entries of vals if it is at least 2D and all of its leading entries are unit;...
int getLargestDivisor(T dim, ArrayRef< T > candidates, ArrayRef< T > candidateMultiples={})
Helper Function to find a proper instruction multiple for the user-supplied sg-level data shape (dive...
bool hasStaticShapeAndStrides(MemRefType type)
Returns true if type has a static shape and static strides.
FailureOr< VectorType > getDistVecTypeBasedOnLaneLayout(DistributeLayoutAttr layout, VectorType originalType)
Helper function to get distributed vector type for a source vector type according to the lane_layout.
Value lowerToVectorReductions(TypedValue< VectorType > src, TypedValue< VectorType > acc, vector::CombiningKind kind, int64_t reductionDim, Location loc, PatternRewriter &rewriter)
Given a src and an acc argumments from a vector::MultiDimReductionOp, lower to a set of vector::Reduc...
bool requireTranspose(const DistributeLayoutAttr layout, const uArch::uArch *uArch)
Helper function to check if the layout requires a transpose effect.
bool matchSplitDimExpansion(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< SmallVector< int64_t > > &splitDimGroups)
DistributeLayoutAttr getDistributeLayoutAttr(const Value value)
Retrieves the DistributeLayoutAttr associated with a given Value.
DenseMap< Value, SmallVector< Type > > precomputeLoopBlockArgTypes(Operation *topLevelOp, SubShapeAndCountFn getSubShapeAndCount)
Pre-computes distributed VectorType mappings for every value carried through an SCF loop under topLev...
std::string getTemporaryLayoutName(const OpOperand &operand)
Return the attribute name for the OpOperand to attach DistributeLayoutAttr.
std::optional< std::string > getChipStr(Operation *op)
Retrieves the chip string from the XeVM target attribute of the parent GPU module operation.
void addVectorTypeConversion(TypeConverter &converter, SubShapeAndCountFn getSubShapeAndCount, DenseMap< Value, SmallVector< Type > > loopArgTypes)
Adds a context-aware VectorType conversion to converter (1:1 shape-changing or 1:N,...
SmallVector< Value > extractVectorsWithShapeFromValue(OpBuilder &builder, Location loc, Value value, ArrayRef< int64_t > shape)
Extract a set of small vectors from a value with a given shape using vector.extract_stride_slice.
DistributeLayoutAttr getTemporaryLayout(const T &operandOrResult)
get and set distribute layout attribute for non-anchor operations (and offsets/masks of load/store op...
Value lowerCrossLaneReductionToShuffles(TypedValue< VectorType > src, TypedValue< VectorType > acc, vector::CombiningKind kind, int64_t reductionDim, int64_t reductionSize, Location loc, PatternRewriter &rewriter)
Lowers cross-lane reductions to shuffle operations on a 2D vector.
std::function< std::pair< SmallVector< int64_t >, int >( VectorType, DistributeLayoutAttr)> SubShapeAndCountFn
Callback type for computing sub-shape and count for 1:N (or 1:1 shape-changing) VectorType conversion...
Definition XeGPUUtils.h:244
void cleanupUnrealizedConversionCasts(Operation *root, const llvm::SmallSetVector< UnrealizedConversionCastOp, 8 > &existingCasts)
Cleans up UnrealizedConversionCastOps inserted during SCF structural type conversion and/or XeGPU unr...
SmallVector< Value > flattenValues(ArrayRef< ValueRange > values)
Flatten a set of ValueRange into a single SmallVector<Value>
SmallVector< OpFoldResult > addWithRightAligned(OpBuilder &builder, Location loc, ArrayRef< OpFoldResult > lhs, ArrayRef< OpFoldResult > rhs)
Generates element-wise addition ops of two arrays with automatic alignment.
SmallVector< OpFoldResult > addElementwise(OpBuilder &builder, Location loc, ArrayRef< OpFoldResult > lhs, ArrayRef< OpFoldResult > rhs)
Generates element-wise addition ops of two arrays with same length.
FailureOr< VectorType > getDistributedVectorType(xegpu::TensorDescType tdescTy)
If tensor descriptor has a layout attribute it is used in SIMT mode.
Include the generated interface declarations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:494
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
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.
virtual int getSubgroupSize() const =0