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