MLIR 24.0.0git
XeGPUWgToSgDistribute.cpp
Go to the documentation of this file.
1//===- XeGPUWgToSgDistribute.cpp - XeGPU Workgroup to Subgroup Pass -------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
9
25#include "llvm/ADT/SetVector.h"
26#include <optional>
27
28namespace mlir {
29namespace xegpu {
30#define GEN_PASS_DEF_XEGPUWGTOSGDISTRIBUTE
31#include "mlir/Dialect/XeGPU/Transforms/Passes.h.inc"
32} // namespace xegpu
33} // namespace mlir
34
35using namespace mlir;
36
37namespace {
38
39// Retrieve the RangeAttr if it is specified.
40static xegpu::RangeAttr getRangeSpecAttr(Operation *op) {
41 Operation *parent = op->getParentOfType<scf::IfOp>();
42 while (parent) {
43 if (auto attr = llvm::dyn_cast_if_present<xegpu::RangeAttr>(
44 parent->getDiscardableAttr("sg_id_range")))
45 return attr;
46 parent = parent->getParentOfType<scf::IfOp>();
47 }
48 return {};
49}
50
51static std::pair<SmallVector<int64_t>, int>
52getSgShapeAndCount(ArrayRef<int64_t> shape,
53 xegpu::DistributeLayoutAttr layout) {
54 int count = 1;
56 auto distributedShape = layout.computeDistributedShape(
57 SmallVector<int64_t>(shape.begin(), shape.end()));
58 if (failed(distributedShape))
59 return std::make_pair(sgShape, count);
60 auto sgData = layout.getEffectiveSgDataAsInt();
61 count = computeProduct(distributedShape.value()) / computeProduct(sgData);
62 return std::make_pair(sgData, count);
63}
64
65/// Utility helper for deriving a list of offsets for each sub-TensorDescs
66/// or sub-MemDescs to be accessed by current subgroup (sgId) based on the
67/// associated distribute layout attribute, the shape, subgroup id and the
68/// original offsets of the op
69template <typename OpType,
70 typename = std::enable_if_t<llvm::is_one_of<
71 OpType, xegpu::LoadNdOp, xegpu::StoreNdOp, xegpu::PrefetchNdOp,
72 xegpu::LoadMatrixOp, xegpu::StoreMatrixOp>::value>>
73static LogicalResult
74genOffsetsList(ConversionPatternRewriter &rewriter, OpType op,
76 Location loc = op.getLoc();
77 SmallVector<OpFoldResult> origOffsets = op.getMixedOffsets();
78 // not applicable to ops without offsets operands.
79 if (origOffsets.empty())
80 return failure();
81
82 // if op is xegpu::CreateNdDescOp, call op.getDescLayoutAttr()
83 xegpu::DistributeLayoutAttr layout;
84 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp> ||
85 std::is_same_v<OpType, xegpu::StoreMatrixOp>) {
86 layout = op.getLayoutAttr();
87 } else {
88 layout = op.getDescLayoutAttr();
89 }
90
91 // not applicable to ops without workgroup layout attributes
92 if (!layout || !layout.isForWorkgroup())
93 return failure();
94
95 Value sgId =
96 gpu::SubgroupIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
97
98 // verify and adjust the sgId if the range specifier is present
99 xegpu::RangeAttr sgIdRange = getRangeSpecAttr(op);
100 if (sgIdRange) {
101 int64_t startOfRange = sgIdRange.getStart().getInt();
102 int64_t endOfRange = sgIdRange.getEnd().getInt();
103 // verify the RangeAttr against the layout attribute
104 if (layout.getNumSubgroups() != endOfRange - startOfRange)
105 return rewriter.notifyMatchFailure(
106 op, "sg_layout size must match the sg_id_range");
107 // adjust the sgId if necessary
108 if (startOfRange > 0) {
109 Value startOfRangeVal =
110 arith::ConstantIndexOp::create(rewriter, loc, startOfRange);
111 sgId = index::SubOp::create(rewriter, loc, sgId, startOfRangeVal);
112 }
113 }
114
115 // Compute the list of subgroup-relative offsets for sub-tensors or sub-memory
116 // descriptors to be accessed, based on the layout information.
117 ArrayRef<int64_t> wgShape = op.getDataShape();
118 auto maybeDescOffsets =
119 layout.computeDistributedCoords(rewriter, loc, sgId, wgShape);
120 if (failed(maybeDescOffsets))
121 return failure();
122
123 // Compute the final global offsets for each accessed sub-tensor
124 // or sub-memory descriptor.
125 for (const auto &sgOffsets : *maybeDescOffsets) {
127 rewriter, loc, getAsOpFoldResult(sgOffsets), origOffsets);
128 offsetsList.push_back(std::move(newOffsets));
129 }
130
131 // callback(offsetsList);
132 return success();
133}
134
135/// This pattern transforms the CreateNdDescOp to create a subgroup descriptor
136/// from a workgroup descriptor. It replaces the offsets and sizes with
137/// appropriate values for the subgroup.
138/// It uses round-robin assignment to distribute the work to the subgroups.
139/// Following create_nd_desc operation:
140/// %tdesc = xegpu.create_nd_tdesc %src : memref<24x24xf32>
141/// -> !xegpu.tensor_desc<24x24xf32, #xegpu.layout<sg_layout = [4, 4],
142/// sg_data = [2, 2], lane_layout = [2, 2], lane_data = [1, 1]>>
143/// is converted to 9 subgroup level operations based on the sg_layout &
144/// sg_data:
145/// %tdesc = xegpu.create_nd_tdesc %src : memref<24x24xf32> ->
146/// !xegpu.tensor_desc<2x2xf32, #xegpu.layout<lane_layout = [2, 2],
147/// lane_data = [1, 1]>>
148///
149/// The sg_layout and sg_data attributes are dropped after the pass as they are
150/// no longer needed.
151///
152/// 24x24 matrix distribution example:
153/// sg_layout = [4, 4], sg_data = [2, 2]
154/// Each 8x8 matrix within the 24x24 matrix is called a distribution unit.
155/// dist_unit_shape = [8, 8] --> sg_layout[i] * sg_data[i]
156///
157/// +------------------------+
158/// | 8x8 | 8x8 | 8x8 | <- 3 tiles across
159/// |-----+-----+-----|
160/// | 8x8 | 8x8 | 8x8 | <- 3 tiles down
161/// |-----+-----+-----|
162/// | 8x8 | 8x8 | 8x8 |
163/// +------------------------+
164///
165/// Each 8x8 tile is further subdivided among subgroups:
166/// +------------------------+
167/// | 2x2 2x2 2x2 2x2 | <- 4 subgroups across (each handles 2 columns)
168/// | 2x2 2x2 2x2 2x2 | <- 4 subgroups down (each handles 2 rows)
169/// | 2x2 2x2 2x2 2x2 |
170/// | 2x2 2x2 2x2 2x2 |
171/// +------------------------+
172///
173/// Since the 24x24 matrix is divided into 8x8 distribution units, there will be
174/// 9 distribution units (3x3) in total. Hence the 9 subgroup level operations.
175
176/// The pass currently has entire distribution logic in the WgToSgCreateNdOp
177/// pattern and all the other ops just follow.
178/// TODO: Decouple the distribution logic from WgToSgCreateNdOp for all the
179/// ops in the pass.
180// This pattern transforms the CreateNdDescOp to create a
181// subgroup descriptor from a workgroup descriptor.
182struct WgToSgCreateNdOp : public OpConversionPattern<xegpu::CreateNdDescOp> {
183 using OpConversionPattern<xegpu::CreateNdDescOp>::OpConversionPattern;
184
185 LogicalResult
186 matchAndRewrite(xegpu::CreateNdDescOp op, OneToNOpAdaptor adaptor,
187 ConversionPatternRewriter &rewriter) const override {
188
189 Location loc = op.getLoc();
190 MLIRContext *ctx = op.getContext();
191 xegpu::TensorDescType tdescTy = op.getType();
192 auto layout = dyn_cast<xegpu::DistributeLayoutAttr>(tdescTy.getLayout());
193 if (!layout || !layout.isForWorkgroup())
194 return failure();
195
196 Type elemTy = tdescTy.getElementType();
197 ArrayRef<int64_t> wgShape = tdescTy.getShape();
198
199 SmallVector<int64_t> sgShape;
200 int count;
201 std::tie(sgShape, count) = getSgShapeAndCount(wgShape, layout);
202 xegpu::TensorDescType newTdescTy =
203 xegpu::TensorDescType::get(ctx, sgShape, elemTy, tdescTy.getEncoding(),
204 layout.dropSgLayoutAndData());
205
206 Value src = op.getSource();
207 SmallVector<Value> newCreateNdOps(count);
208 std::generate(newCreateNdOps.begin(), newCreateNdOps.end(), [&]() -> Value {
209 if (isa<MemRefType>(src.getType()))
210 return xegpu::CreateNdDescOp::create(rewriter, loc, newTdescTy,
211 cast<TypedValue<MemRefType>>(src));
212 return xegpu::CreateNdDescOp::create(rewriter, loc, newTdescTy, src,
213 op.getMixedSizes(),
214 op.getMixedStrides());
215 });
216
217 rewriter.replaceOpWithMultiple(op, {newCreateNdOps});
218 return success();
219 }
220};
221
222/// This pattern transforms the LoadNdOp to load subgroup data.
223struct WgToSgLoadNdOp : public OpConversionPattern<xegpu::LoadNdOp> {
224 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
225 LogicalResult
226 matchAndRewrite(xegpu::LoadNdOp op, OneToNOpAdaptor adaptor,
227 ConversionPatternRewriter &rewriter) const override {
228
229 SmallVector<SmallVector<OpFoldResult>> offsetsList;
230 if (failed(genOffsetsList(rewriter, op, offsetsList)))
231 return failure();
232
233 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
234 if (layout)
235 layout = layout.dropSgLayoutAndData();
236 SmallVector<Value> newOps;
237 for (auto [tdesc, offsets] :
238 llvm::zip(adaptor.getTensorDesc(), offsetsList)) {
239 auto tdescTy = dyn_cast<xegpu::TensorDescType>(tdesc.getType());
240 VectorType newResTy =
241 VectorType::get(tdescTy.getShape(), tdescTy.getElementType());
242 auto newOp = xegpu::LoadNdOp::create(
243 rewriter, op.getLoc(), newResTy, tdesc, offsets,
244 /*packed = */ nullptr, /*transpose = */ nullptr, op.getL1HintAttr(),
245 op.getL2HintAttr(), op.getL3HintAttr(), layout);
246 newOps.push_back(newOp);
247 }
248 rewriter.replaceOpWithMultiple(op, {newOps});
249
250 return success();
251 }
252};
253
254/// This pattern transforms the StoreNdOp to store subgroup data.
255struct WgToSgStoreNdOp : public OpConversionPattern<xegpu::StoreNdOp> {
256 using OpConversionPattern<xegpu::StoreNdOp>::OpConversionPattern;
257 LogicalResult
258 matchAndRewrite(xegpu::StoreNdOp op, OneToNOpAdaptor adaptor,
259 ConversionPatternRewriter &rewriter) const override {
260 SmallVector<SmallVector<OpFoldResult>> offsetsList;
261 if (failed(genOffsetsList(rewriter, op, offsetsList)))
262 return failure();
263
264 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
265 if (layout)
266 layout = layout.dropSgLayoutAndData();
267 for (auto [v, tdesc, offsets] :
268 llvm::zip(adaptor.getValue(), adaptor.getTensorDesc(), offsetsList)) {
269 xegpu::StoreNdOp::create(rewriter, op.getLoc(), v, tdesc, offsets,
270 op.getL1HintAttr(), op.getL2HintAttr(),
271 op.getL3HintAttr(), layout);
272 }
273 rewriter.eraseOp(op);
274
275 return success();
276 }
277};
278
279/// This pattern transforms the PrefetchNdOp to prefetch subgroup data.
280struct WgToSgPrefetchNdOp : public OpConversionPattern<xegpu::PrefetchNdOp> {
281 using OpConversionPattern<xegpu::PrefetchNdOp>::OpConversionPattern;
282 LogicalResult
283 matchAndRewrite(xegpu::PrefetchNdOp op, OneToNOpAdaptor adaptor,
284 ConversionPatternRewriter &rewriter) const override {
285 SmallVector<SmallVector<OpFoldResult>> offsetsList;
286 if (failed(genOffsetsList(rewriter, op, offsetsList)))
287 return failure();
288
289 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
290 if (layout)
291 layout = layout.dropSgLayoutAndData();
292 for (auto [tdesc, offsets] :
293 llvm::zip(adaptor.getTensorDesc(), offsetsList)) {
294 xegpu::PrefetchNdOp::create(rewriter, op.getLoc(), tdesc, offsets,
295 op.getL1HintAttr(), op.getL2HintAttr(),
296 op.getL3HintAttr(), layout);
297 }
298 rewriter.eraseOp(op);
299
300 return success();
301 }
302};
303
304/// This pattern transforms the DpasOp to work at subgroup level.
305struct WgToSgDpasOp : public OpConversionPattern<xegpu::DpasOp> {
306 using OpConversionPattern<xegpu::DpasOp>::OpConversionPattern;
307 LogicalResult
308 matchAndRewrite(xegpu::DpasOp op, OneToNOpAdaptor adaptor,
309 ConversionPatternRewriter &rewriter) const override {
310 Location loc = op.getLoc();
311 VectorType resultTy = op.getResult().getType();
312 if (resultTy.getRank() < 2)
313 return failure();
314
315 auto layoutCd = op.getLayoutCdAttr();
316 auto layoutA = op.getLayoutAAttr();
317 auto layoutB = op.getLayoutBAttr();
318 if (!layoutCd || !layoutA || !layoutB)
319 return failure();
320 size_t i = 0;
321 SmallVector<Value> newDpasOps;
322 for (auto aVec : adaptor.getLhs()) {
323 for (auto bVec : adaptor.getRhs()) {
324
325 Value tmpC;
326 if (op.getAcc())
327 tmpC = adaptor.getAcc()[i++];
328
329 ArrayRef<int64_t> aVecShape =
330 cast<VectorType>(aVec.getType()).getShape();
331 ArrayRef<int64_t> bVecShape =
332 cast<VectorType>(bVec.getType()).getShape();
333 // Build result shape: batch dims from A + [M, N] from last dims of
334 // A and B.
335 SmallVector<int64_t> resShape(aVecShape.drop_back(2));
336 resShape.push_back(aVecShape[aVecShape.size() - 2]);
337 resShape.push_back(bVecShape[bVecShape.size() - 1]);
338 VectorType resTy = VectorType::get(resShape, resultTy.getElementType());
339 auto newDpasOp = xegpu::DpasOp::create(
340 rewriter, loc, resTy, aVec, bVec, tmpC,
341 /*layout_a=*/nullptr, /*layout_b=*/nullptr, /*layout_cd=*/nullptr);
342 newDpasOp.setLayoutCdAttr(layoutCd.dropSgLayoutAndData());
343 newDpasOp.setLayoutAAttr(layoutA.dropSgLayoutAndData());
344 newDpasOp.setLayoutBAttr(layoutB.dropSgLayoutAndData());
345
346 newDpasOps.push_back(newDpasOp);
347 }
348 }
349 rewriter.replaceOpWithMultiple(op, {newDpasOps});
350 return success();
351 }
352};
353
354/// This pattern transforms the DpasMxOp to work at subgroup level.
355struct WgToSgDpasMxOp : public OpConversionPattern<xegpu::DpasMxOp> {
356 using OpConversionPattern<xegpu::DpasMxOp>::OpConversionPattern;
357 LogicalResult
358 matchAndRewrite(xegpu::DpasMxOp op, OneToNOpAdaptor adaptor,
359 ConversionPatternRewriter &rewriter) const override {
360
361 Location loc = op.getLoc();
362 VectorType resultTy = op.getResult().getType();
363
364 if (resultTy.getRank() < 2)
365 return failure();
366
367 auto layoutCd = op.getLayoutCdAttr();
368 auto layoutA = op.getLayoutAAttr();
369 auto layoutB = op.getLayoutBAttr();
370 auto layoutAScale = op.getLayoutAScaleAttr();
371 auto layoutBScale = op.getLayoutBScaleAttr();
372
373 if (!layoutCd || !layoutA || !layoutB || !layoutAScale || !layoutBScale)
374 return failure();
375
376 size_t index_c = 0;
377 SmallVector<Value> newDpasMxOps;
378 for (auto [index_a, aVec] : llvm::enumerate(adaptor.getA())) {
379 for (auto [index_b, bVec] : llvm::enumerate(adaptor.getB())) {
380 Value accVal = (op.getAcc()) ? adaptor.getAcc()[index_c++] : Value();
381 Value scaleAVal =
382 (op.getScaleA()) ? adaptor.getScaleA()[index_a] : Value();
383 Value scaleBVal =
384 (op.getScaleB()) ? adaptor.getScaleB()[index_b] : Value();
385
386 ArrayRef<int64_t> aVecShape =
387 cast<VectorType>(aVec.getType()).getShape();
388 ArrayRef<int64_t> bVecShape =
389 cast<VectorType>(bVec.getType()).getShape();
390 // Build result shape: batch dims from A + [M, N]
391 SmallVector<int64_t> resShape(aVecShape.drop_back(2));
392 resShape.push_back(aVecShape[aVecShape.size() - 2]);
393 resShape.push_back(bVecShape[bVecShape.size() - 1]);
394 VectorType resTy = VectorType::get(resShape, resultTy.getElementType());
395 auto newDpasMxOp = xegpu::DpasMxOp::create(
396 rewriter, loc, resTy, aVec, bVec, accVal, scaleAVal, scaleBVal,
397 layoutA.dropSgLayoutAndData(), layoutB.dropSgLayoutAndData(),
398 layoutCd.dropSgLayoutAndData(), layoutAScale.dropSgLayoutAndData(),
399 layoutBScale.dropSgLayoutAndData());
400
401 newDpasMxOps.push_back(newDpasMxOp);
402 }
403 }
404 rewriter.replaceOpWithMultiple(op, {newDpasMxOps});
405 return success();
406 }
407};
408
409/// This pattern transforms vector.broadcast ops to work at subgroup level.
410struct WgToSgVectorBroadcastOp
411 : public OpConversionPattern<vector::BroadcastOp> {
412 using OpConversionPattern<vector::BroadcastOp>::OpConversionPattern;
413
414 LogicalResult
415 matchAndRewrite(vector::BroadcastOp op, OneToNOpAdaptor adaptor,
416 ConversionPatternRewriter &rewriter) const override {
417
418 VectorType resultType = op.getResult().getType();
419 ArrayRef<int64_t> wgShape = resultType.getShape();
420
421 xegpu::DistributeLayoutAttr layout =
422 xegpu::getTemporaryLayout(llvm::cast<OpResult>(op.getResult()));
423 if (!layout || !layout.isForWorkgroup())
424 return failure();
425
426 SmallVector<int64_t> sgShape;
427 int count;
428 std::tie(sgShape, count) = getSgShapeAndCount(wgShape, layout);
429 VectorType newResultType =
430 VectorType::get(sgShape, resultType.getElementType());
431
432 SmallVector<Value> newBroadcastOps;
433 auto distSource = adaptor.getOperands().front();
434 int numDistributions = count / distSource.size();
435 for (int i = 0; i < numDistributions; ++i) {
436 for (auto operand : distSource) {
437 auto newBroadcast = vector::BroadcastOp::create(rewriter, op.getLoc(),
438 newResultType, operand);
439
440 newBroadcastOps.push_back(newBroadcast.getResult());
441 }
442 }
443 rewriter.replaceOpWithMultiple(op, {newBroadcastOps});
444 return success();
445 }
446};
447
448// This pattern transforms elementwise ops to work at subgroup level.
449struct WgToSgElementwiseOp : public ConversionPattern {
450 WgToSgElementwiseOp(MLIRContext *ctx)
451 : ConversionPattern(MatchAnyOpTypeTag(), /*benefit=*/1, ctx) {}
452
453 LogicalResult
454 matchAndRewrite(Operation *op, ArrayRef<ValueRange> operands,
455 ConversionPatternRewriter &rewriter) const override {
456 // Only match ops with elementwise trait and single result.
458 return failure();
459
460 auto resultType = dyn_cast<VectorType>(op->getResult(0).getType());
461 assert(resultType && "Expected result to be a VectorType");
462
463 ArrayRef<int64_t> wgShape = resultType.getShape();
464
465 xegpu::DistributeLayoutAttr layout =
466 xegpu::getTemporaryLayout(llvm::cast<OpResult>(op->getResult(0)));
467 if (!layout || !layout.isForWorkgroup())
468 return failure();
469
470 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
471
472 size_t numVariants = operands.empty() ? 0 : operands.front().size();
473
474 if (llvm::any_of(operands, [&](const ValueRange &operandVec) {
475 return operandVec.size() != numVariants;
476 }))
477 return failure();
478
479 SmallVector<Value> newResults;
480 VectorType newResultType =
481 VectorType::get(sgShape, resultType.getElementType());
482
483 for (size_t i = 0; i < numVariants; ++i) {
484 SmallVector<Value> opOperands;
485 for (auto &operandVec : operands)
486 opOperands.push_back(operandVec[i]);
487
488 OperationState state(op->getLoc(), op->getName());
489 state.addOperands(opOperands);
490 state.addTypes(newResultType);
491 state.addAttributes(op->getDiscardableAttrDictionary().getValue());
492 state.propertiesAttr = op->getPropertiesAsAttribute();
493 Operation *newOp = rewriter.create(state);
495 newResults.push_back(newOp->getResult(0));
496 }
497
498 rewriter.replaceOpWithMultiple(op, {newResults});
499 return success();
500 }
501};
502
503// clang-format off
504// Pattern for lowering ConvertLayoutOp based on sg_layout and sg_data.
505// If input_layout and target_layout have identical sg_layout and sg_data,
506// the op is rewritten to a subgroup-level ConvertLayoutOp with these fields
507// dropped. For example:
508// #a = #xegpu.layout<sg_layout = [2, 2], sg_data = [16, 16], inst_data = [16, 16]>
509// #b = #xegpu.layout<sg_layout = [2, 2], sg_data = [16, 16], inst_data = [8, 16]>
510// xegpu.convert_layout %1 <{input_layout = #a, target_layout = #b}> : vector<32x64xf32>
511// becomes:
512// #a = #xegpu.layout<inst_data = [16, 16]>
513// #b = #xegpu.layout<inst_data = [8, 16]>
514// xegpu.convert_layout %1 <{input_layout = #a, target_layout = #b}> : vector<16x16xf32>
515// (vector<16x16xf32> is determined by sg_data = [16, 16])
516//
517// If sg_layout or sg_data differ, SLM is used to redistribute data across subgroups.
518// For example:
519// #a = #xegpu.layout<sg_layout = [1, 4], sg_data = [32, 16], inst_data = [16, 16]>
520// #b = #xegpu.layout<sg_layout = [2, 2], sg_data = [16, 32], inst_data = [8, 16]>
521// xegpu.convert_layout %1 <{input_layout = #a, target_layout = #b}> : vector<32x64xf32>
522// is lowered to:
523// #a = #xegpu.layout<inst_data = [16, 16]>
524// #b = #xegpu.layout<inst_data = [8, 16]>
525// store_matrix %1, %slm <{layout_input_0 = #a}> : vector<32x16>, mem_desc<32x64xf32>
526// %d = load_matrix %slm <{layout_result_0 = #a}> : mem_desc<32x64xf32> -> vector<16x32xf32>
527// xegpu.convert_layout %d <{input_layout = #a, target_layout = #b}> : vector<16x32xf32>
528// clang-format on
529struct WgToSgConvertLayoutOp
530 : public OpConversionPattern<xegpu::ConvertLayoutOp> {
531 using OpConversionPattern<xegpu::ConvertLayoutOp>::OpConversionPattern;
532
533 LogicalResult
534 matchAndRewrite(xegpu::ConvertLayoutOp op, OneToNOpAdaptor adaptor,
535 ConversionPatternRewriter &rewriter) const override {
536 Location loc = op.getLoc();
537 auto inputLayout = op.getEffectiveInputLayout();
538 auto targetLayout = op.getTargetLayout();
539
540 if (!inputLayout || !targetLayout || !inputLayout.isForWorkgroup() ||
541 !targetLayout.isForWorkgroup())
542 return rewriter.notifyMatchFailure(
543 op, "Input and target layouts must have subgroup layout");
544
545 Type resultType = op.getResult().getType();
546 if (resultType.isIntOrFloat()) {
547 rewriter.replaceOp(op, op.getSource());
548 assert(!inputLayout.dropSgLayoutAndData() &&
549 !targetLayout.dropSgLayoutAndData() &&
550 "unexpected layout attributes for scalar type");
551 return success();
552 }
553
554 ArrayRef<int64_t> wgShape = cast<VectorType>(resultType).getShape();
555 SmallVector<int64_t> inputSgLayout =
556 inputLayout.getEffectiveSgLayoutAsInt();
557 SmallVector<int64_t> inputSgData = inputLayout.getEffectiveSgDataAsInt();
558 SmallVector<int64_t> targetSgLayout =
559 targetLayout.getEffectiveSgLayoutAsInt();
560 SmallVector<int64_t> targetSgData = targetLayout.getEffectiveSgDataAsInt();
561
562 // Fast path: if sg_layout and sg_data are identical, no SLM needed
563 SmallVector<int64_t> wgShapeVec(wgShape.begin(), wgShape.end());
564 if (inputLayout.isCompatibleWith(targetLayout, wgShapeVec,
565 xegpu::LayoutKind::Subgroup)) {
566 inputLayout = inputLayout.dropSgLayoutAndData();
567 targetLayout = targetLayout.dropSgLayoutAndData();
568
569 SmallVector<Value> newOps(adaptor.getSource());
570 if (inputLayout && targetLayout) {
571 for (auto [i, src] : llvm::enumerate(adaptor.getSource())) {
572 auto newOp = xegpu::ConvertLayoutOp::create(
573 rewriter, loc, src.getType(), src, inputLayout, targetLayout);
574 newOps[i] = newOp;
575 }
576 }
577 rewriter.replaceOpWithMultiple(op, {newOps});
578 return success();
579 }
580
581 // SLM path: layouts differ, need cross-subgroup data redistribution
582 Type elemTy = cast<VectorType>(op.getSource().getType()).getElementType();
583
584 SmallVector<int64_t> slmShape = llvm::to_vector(wgShape);
585
586 // Calculate SLM size requirements
587 auto bitWidth = elemTy.getIntOrFloatBitWidth();
588 auto bytesPerElement = bitWidth / 8;
589 auto slmSize = computeProduct(slmShape) * bytesPerElement;
590
591 // Allocate SLM
592 auto slmTy = MemRefType::get({slmSize}, rewriter.getI8Type(), {}, 3);
593 auto slm = memref::AllocaOp::create(rewriter, loc, slmTy);
594
595 auto memDescType = xegpu::MemDescType::get(rewriter.getContext(), slmShape,
596 elemTy, nullptr);
597 auto memDesc =
598 xegpu::CreateMemDescOp::create(rewriter, loc, memDescType, slm);
599
600 auto sgId = gpu::SubgroupIdOp::create(rewriter, loc,
601 rewriter.getIndexType(), nullptr);
602
603 // STORE PHASE: Each subgroup stores in SLM using input layout
604 auto storeCoords = inputLayout.computeDistributedCoords(
605 rewriter, loc, sgId.getResult(), wgShape);
606 if (failed(storeCoords))
607 return failure();
608
609 // Store to SLM
610 for (auto [src, coords] : llvm::zip(adaptor.getSource(), *storeCoords)) {
611 SmallVector<OpFoldResult> storeMatrixOffsets;
612 for (Value coord : coords) {
613 storeMatrixOffsets.push_back(coord);
614 }
615 xegpu::StoreMatrixOp::create(rewriter, loc, src, memDesc.getResult(),
616 storeMatrixOffsets, nullptr /*layout*/);
617 }
618
619 gpu::BarrierOp::create(rewriter, loc);
620
621 // LOAD PHASE: Each target subgroup loads from SLM using target layout
622 auto loadCoords = targetLayout.computeDistributedCoords(
623 rewriter, loc, sgId.getResult(), wgShape);
624 if (failed(loadCoords))
625 return failure();
626
627 VectorType loadType = VectorType::get(targetSgData, elemTy);
628
629 // Load vectors from SLM
630 SmallVector<Value> finalResults;
631 for (auto coords : *loadCoords) {
632 SmallVector<OpFoldResult> loadMatrixOffsets;
633 for (Value coord : coords) {
634 loadMatrixOffsets.push_back(coord);
635 }
636 auto loadOp = xegpu::LoadMatrixOp::create(
637 rewriter, loc, loadType, memDesc.getResult(), loadMatrixOffsets,
638 targetLayout.dropSgLayoutAndData());
639
640 finalResults.push_back(loadOp.getResult());
641 }
642
643 rewriter.replaceOpWithMultiple(op, {finalResults});
644 return success();
645 }
646};
647
648// This pattern distributes arith.constant op into subgroup-level constants
649struct WgToSgArithConstantOp : public OpConversionPattern<arith::ConstantOp> {
650 using OpConversionPattern<arith::ConstantOp>::OpConversionPattern;
651
652 LogicalResult
653 matchAndRewrite(arith::ConstantOp op, OneToNOpAdaptor adaptor,
654 ConversionPatternRewriter &rewriter) const override {
655 auto vecAttr = dyn_cast<DenseElementsAttr>(op.getValue());
656 auto vecType = dyn_cast<VectorType>(op.getType());
657 if (!vecAttr || !vecType)
658 return failure();
659
660 xegpu::DistributeLayoutAttr layout =
661 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
662 if (!layout || !layout.isForWorkgroup())
663 return failure();
664
665 ArrayRef<int64_t> wgShape = vecType.getShape();
666 SmallVector<int64_t> sgShape;
667 int count;
668 std::tie(sgShape, count) = getSgShapeAndCount(wgShape, layout);
669
670 auto newType = VectorType::get(sgShape, vecType.getElementType());
671 Location loc = op.getLoc();
672 auto eltType = vecType.getElementType();
673
674 if (vecAttr.isSplat()) {
675 // Splat: single value for all subgroups
676 Attribute singleVal = vecAttr.getSplatValue<Attribute>();
677 auto sgAttr = DenseElementsAttr::get(newType, singleVal);
678 SmallVector<Value> newConstOps;
679 for (int i = 0; i < count; ++i) {
680 auto cstOp = arith::ConstantOp::create(rewriter, loc, newType, sgAttr);
681 newConstOps.push_back(cstOp);
682 }
683 rewriter.replaceOpWithMultiple(op, {newConstOps});
684 return success();
685 } else if (sgShape == wgShape) { // if the entire vector is shared by all
686 // subgroups, don't distribute
687 auto newConstOp =
688 arith::ConstantOp::create(rewriter, op.getLoc(), vecType, vecAttr);
689 rewriter.replaceOp(op, newConstOp);
690 return success();
691 } else {
692 // Non-splat constant
693 // Only supports 1D & 2D
694 // TODO: support other cases that require SLM access
695 if (!eltType.isIndex())
696 return rewriter.notifyMatchFailure(
697 op, "Unsupported element type for non-splat constant op.");
698
699 if (wgShape.size() > 2)
700 return rewriter.notifyMatchFailure(
701 op, "Only 1D & 2D vector constant supported");
702
703 SmallVector<Attribute> values(vecAttr.getValues<Attribute>());
704 int64_t rowStride = 0, colStride = 0;
705 int64_t rows = wgShape.size() == 1 ? 1 : wgShape[0];
706 int64_t cols = wgShape.size() == 1 ? wgShape[0] : wgShape[1];
707
708 // Compute colStride and rowStride, and check for constant strides.
709 if (cols > 1) {
710 colStride = cast<IntegerAttr>(values[1]).getInt() -
711 cast<IntegerAttr>(values[0]).getInt();
712 }
713 if (rows > 1) {
714 rowStride = cast<IntegerAttr>(values[cols]).getInt() -
715 cast<IntegerAttr>(values[0]).getInt();
716 }
717
718 for (int64_t r = 0; r < rows; ++r) {
719 for (int64_t c = 0; c < cols; ++c) {
720 int64_t idx = r * cols + c;
721 // Check column stride
722 if (c > 0 && cols > 1) {
723 int64_t prevIdx = r * cols + (c - 1);
724 int64_t diff = cast<IntegerAttr>(values[idx]).getInt() -
725 cast<IntegerAttr>(values[prevIdx]).getInt();
726 if (diff != colStride)
727 return rewriter.notifyMatchFailure(
728 op, "Non-constant column stride in constant op.");
729 }
730 // Check row stride
731 if (r > 0 && rows > 1) {
732 int64_t prevIdx = (r - 1) * cols + c;
733 int64_t diff = cast<IntegerAttr>(values[idx]).getInt() -
734 cast<IntegerAttr>(values[prevIdx]).getInt();
735 if (diff != rowStride)
736 return rewriter.notifyMatchFailure(
737 op, "Non-constant row stride in constant op.");
738 }
739 }
740 }
741
742 // Create a constant for the base tile.
743 // For 2D case, extract the top-left sgShape[0] x sgShape[1] submatrix.
744 // For 1D case, extract the first sgShape[0] elements.
745 SmallVector<Attribute> baseTileValues;
746 int baseTileCols = sgShape[sgShape.size() - 1];
747 int64_t baseTileRows = sgShape.size() == 1 ? 1 : sgShape[0];
748 for (int64_t r = 0; r < baseTileRows; ++r) {
749 for (int64_t c = 0; c < baseTileCols; ++c) {
750 baseTileValues.push_back(values[r * cols + c]);
751 }
752 }
753
754 auto tileAttr = DenseElementsAttr::get(VectorType::get(sgShape, eltType),
755 baseTileValues);
756 auto baseConstVec = arith::ConstantOp::create(rewriter, loc, tileAttr);
757
758 // Get subgroup id
759 Value sgId =
760 gpu::SubgroupIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
761 auto sgOffsets =
762 layout.computeDistributedCoords(rewriter, loc, sgId, wgShape);
763 if (failed(sgOffsets))
764 return failure();
765
766 SmallVector<Value, 2> strideConsts;
767 strideConsts.push_back(
768 arith::ConstantIndexOp::create(rewriter, loc, colStride));
769 if (rows > 1)
770 strideConsts.insert(
771 strideConsts.begin(),
772 arith::ConstantIndexOp::create(rewriter, loc, rowStride));
773
774 SmallVector<Value> newConstOps;
775 for (auto offsets : *sgOffsets) {
776 // Multiply offset with stride, broadcast it and add to baseConstVec
777 Value mulOffset = arith::ConstantIndexOp::create(rewriter, loc, 0);
778 for (size_t i = 0; i < strideConsts.size(); ++i) {
779 Value mul =
780 arith::MulIOp::create(rewriter, loc, rewriter.getIndexType(),
781 offsets[i], strideConsts[i]);
782 mulOffset = arith::AddIOp::create(
783 rewriter, loc, rewriter.getIndexType(), mulOffset, mul);
784 }
785 // Broadcast to baseConstVec size
786 auto bcastOffset = vector::BroadcastOp::create(
787 rewriter, loc, baseConstVec.getType(), mulOffset);
788 auto finalConst =
789 arith::AddIOp::create(rewriter, loc, baseConstVec, bcastOffset);
790 newConstOps.push_back(finalConst);
791 }
792 rewriter.replaceOpWithMultiple(op, {newConstOps});
793 return success();
794 }
795 }
796};
797
798// This pattern transforms the LoadGatherOp with explicit offsets to load
799// subgroup data
800struct WgToSgLoadGatherOp : public OpConversionPattern<xegpu::LoadGatherOp> {
801 using OpConversionPattern<xegpu::LoadGatherOp>::OpConversionPattern;
802 LogicalResult
803 matchAndRewrite(xegpu::LoadGatherOp op, OneToNOpAdaptor adaptor,
804 ConversionPatternRewriter &rewriter) const override {
805
806 Location loc = op.getLoc();
807 VectorType resultType = dyn_cast<VectorType>(op.getResult().getType());
808 if (!resultType)
809 return failure();
810 ArrayRef<int64_t> wgShape = resultType.getShape();
811
812 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
813
814 if (!layout || !layout.isForWorkgroup())
815 return failure();
816
817 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
818
819 // The offsets need to be distributed
820 auto offsetsVecType =
821 dyn_cast<VectorType>(adaptor.getOffsets().front().getType());
822 auto maskVecType =
823 dyn_cast<VectorType>(adaptor.getMask().front().getType());
824 if (!offsetsVecType || !maskVecType ||
825 offsetsVecType.getShape() != maskVecType.getShape()) {
826 return rewriter.notifyMatchFailure(op,
827 "offsets have not been distributed");
828 }
829
830 SmallVector<Value> newLoadOps;
831 auto chunkSizeAttr =
832 rewriter.getI64IntegerAttr(op.getChunkSize().value_or(1));
833 VectorType newTy = VectorType::get(sgShape, resultType.getElementType());
834 for (auto [offsets, mask] :
835 llvm::zip(adaptor.getOffsets(), adaptor.getMask())) {
836 auto newLayout = layout.dropSgLayoutAndData();
837 auto newLoadOp = xegpu::LoadGatherOp::create(
838 rewriter, loc, newTy, op.getSource(), offsets, mask, chunkSizeAttr,
839 op.getL1HintAttr(), op.getL2HintAttr(), op.getL3HintAttr(), newLayout,
840 /*contiguity=*/nullptr);
841 newLoadOps.push_back(newLoadOp);
842 }
843 rewriter.replaceOpWithMultiple(op, {newLoadOps});
844 return success();
845 }
846};
847
848// This pattern transforms the StoreScatterOp with explicit offsets to store
849// subgroup data
850struct WgToSgStoreScatterOp
851 : public OpConversionPattern<xegpu::StoreScatterOp> {
852 using OpConversionPattern<xegpu::StoreScatterOp>::OpConversionPattern;
853 LogicalResult
854 matchAndRewrite(xegpu::StoreScatterOp op, OneToNOpAdaptor adaptor,
855 ConversionPatternRewriter &rewriter) const override {
856
857 Location loc = op.getLoc();
858 VectorType valueType = dyn_cast<VectorType>(op.getValue().getType());
859 if (!valueType)
860 return failure();
861
862 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
863
864 if (!layout || !layout.isForWorkgroup())
865 return failure();
866
867 // The offsets need to be distributed
868 auto offsetsVecType =
869 dyn_cast<VectorType>(adaptor.getOffsets().front().getType());
870 auto maskVecType =
871 dyn_cast<VectorType>(adaptor.getMask().front().getType());
872 if (!offsetsVecType || !maskVecType ||
873 offsetsVecType.getShape() != maskVecType.getShape()) {
874 return rewriter.notifyMatchFailure(op,
875 "offsets have not been distributed");
876 }
877
878 auto chunkSizeOpt = op.getChunkSize();
879 int64_t chunkSize = chunkSizeOpt ? static_cast<int64_t>(*chunkSizeOpt) : 1;
880 auto chunkSizeAttr = rewriter.getI64IntegerAttr(chunkSize);
881 for (auto [val, offs, mask] : llvm::zip(
882 adaptor.getValue(), adaptor.getOffsets(), adaptor.getMask())) {
883 xegpu::StoreScatterOp::create(rewriter, loc, val, op.getDest(), offs,
884 mask, chunkSizeAttr, op.getL1HintAttr(),
885 op.getL2HintAttr(), op.getL3HintAttr(),
886 layout.dropSgLayoutAndData(),
887 /*contiguity=*/nullptr);
888 }
889 rewriter.eraseOp(op);
890 return success();
891 }
892};
893
894struct WgToSgLoadMatrixOp : public OpConversionPattern<xegpu::LoadMatrixOp> {
895 using OpConversionPattern<xegpu::LoadMatrixOp>::OpConversionPattern;
896 LogicalResult
897 matchAndRewrite(xegpu::LoadMatrixOp op, OneToNOpAdaptor adaptor,
898 ConversionPatternRewriter &rewriter) const override {
899
900 SmallVector<SmallVector<OpFoldResult>> offsetsList;
901 if (failed(genOffsetsList(rewriter, op, offsetsList)))
902 return failure();
903
904 ArrayRef<int64_t> wgShape = op.getDataShape();
905 VectorType valueTy = llvm::dyn_cast<VectorType>(op.getRes().getType());
906 assert(valueTy && "the value type must be vector type!");
907 Type elemTy = valueTy.getElementType();
908
909 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
910 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
911 VectorType newResTy = VectorType::get(sgShape, elemTy);
912 SmallVector<Value> newOps;
913 for (auto offsets : offsetsList) {
914 auto newOp = xegpu::LoadMatrixOp::create(rewriter, op.getLoc(), newResTy,
915 op.getMemDesc(), offsets,
916 layout.dropSgLayoutAndData());
917 newOps.push_back(newOp);
918 }
919 rewriter.replaceOpWithMultiple(op, {newOps});
920
921 return success();
922 }
923};
924
925struct WgToSgStoreMatrixOp : public OpConversionPattern<xegpu::StoreMatrixOp> {
926 using OpConversionPattern<xegpu::StoreMatrixOp>::OpConversionPattern;
927 LogicalResult
928 matchAndRewrite(xegpu::StoreMatrixOp op, OneToNOpAdaptor adaptor,
929 ConversionPatternRewriter &rewriter) const override {
930
931 SmallVector<SmallVector<OpFoldResult>> offsetsList;
932 if (failed(genOffsetsList(rewriter, op, offsetsList)))
933 return failure();
934
935 xegpu::DistributeLayoutAttr layout = op.getLayoutAttr();
936 for (auto [v, offsets] : llvm::zip(adaptor.getData(), offsetsList))
937 xegpu::StoreMatrixOp::create(rewriter, op.getLoc(), v, op.getMemDesc(),
938 offsets, layout.dropSgLayoutAndData());
939 rewriter.eraseOp(op);
940 return success();
941 }
942};
943
944// This pattern distributes the vector.step ops to work at subgroup level
945struct WgToSgVectorStepOp : public OpConversionPattern<vector::StepOp> {
946 using OpConversionPattern<vector::StepOp>::OpConversionPattern;
947 LogicalResult
948 matchAndRewrite(vector::StepOp op, OneToNOpAdaptor adaptor,
949 ConversionPatternRewriter &rewriter) const override {
950 xegpu::DistributeLayoutAttr layout =
951 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
952 if (!layout || !layout.isForWorkgroup())
953 return failure();
954
955 Location loc = op.getLoc();
956 VectorType type = op.getResult().getType();
957 auto wgShape = type.getShape();
958 std::optional<SmallVector<int64_t>> sgShape =
959 getSgShapeAndCount(wgShape, layout).first;
960 if (!sgShape)
961 return failure();
962
963 Value sgId =
964 gpu::SubgroupIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
965 auto sgOffsets =
966 layout.computeDistributedCoords(rewriter, loc, sgId, wgShape);
967 if (failed(sgOffsets))
968 return failure();
969
970 VectorType newTy = type.cloneWith(*sgShape, type.getElementType());
971 auto steps = vector::StepOp::create(rewriter, loc, newTy);
972 SmallVector<Value> newOps;
973 for (auto offsets : *sgOffsets) {
974 // Broadcast the offset scalar to a vector & add to the base steps
975 auto bcastOffset =
976 vector::BroadcastOp::create(rewriter, loc, newTy, offsets[0]);
977 auto finalSteps =
978 arith::AddIOp::create(rewriter, loc, steps, bcastOffset);
979 newOps.push_back(finalSteps);
980 }
981
982 rewriter.replaceOpWithMultiple(op, {newOps});
983 return success();
984 }
985};
986
987// This pattern transforms vector.shape_cast ops to work at subgroup level.
988struct WgToSgVectorShapeCastOp
989 : public OpConversionPattern<vector::ShapeCastOp> {
990 using OpConversionPattern<vector::ShapeCastOp>::OpConversionPattern;
991
992 LogicalResult
993 matchAndRewrite(vector::ShapeCastOp op, OneToNOpAdaptor adaptor,
994 ConversionPatternRewriter &rewriter) const override {
995
996 VectorType resultType = dyn_cast<VectorType>(op.getResult().getType());
997 if (!resultType)
998 return failure();
999
1000 ArrayRef<int64_t> wgShape = resultType.getShape();
1001 xegpu::DistributeLayoutAttr layout =
1002 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1003 if (!layout || !layout.isForWorkgroup())
1004 return failure();
1005
1006 // Check that srcShape and destShape, if they differ, only differ by
1007 // expand of unit dimensions.
1008 auto srcType = dyn_cast<VectorType>(op.getSource().getType());
1009 if (!srcType)
1010 return failure();
1011
1012 ArrayRef<int64_t> srcShape = srcType.getShape();
1013
1014 xegpu::DistributeLayoutAttr layoutToDistribute = layout;
1015 SmallVector<int64_t> expandedUnitDims;
1016 if (xegpu::matchUnitDimExpansion(srcShape, wgShape, expandedUnitDims)) {
1017 xegpu::DistributeLayoutAttr sourceLayout =
1018 xegpu::getTemporaryLayout(op->getOpOperand(0));
1019
1020 if (!sourceLayout.isSliceOf(layout))
1021 return rewriter.notifyMatchFailure(
1022 op, "The ShapeCast op only expands dimensions, the input layout "
1023 "must be a slice of the result layout.");
1024
1025 assert(layoutToDistribute.isEqualTo(
1026 layoutToDistribute.setUnitDimData(expandedUnitDims)) &&
1027 "The sg_data for unit dimensions should be set as 1");
1028 }
1029
1030 SmallVector<int64_t> sgShape =
1031 getSgShapeAndCount(wgShape, layoutToDistribute).first;
1032 VectorType newResultType =
1033 VectorType::get(sgShape, resultType.getElementType());
1034
1035 SmallVector<Value> newShapeCastOps;
1036 for (auto src : adaptor.getSource()) {
1037 auto newShapeCast = vector::ShapeCastOp::create(rewriter, op.getLoc(),
1038 newResultType, src);
1039 newShapeCastOps.push_back(newShapeCast.getResult());
1040 }
1041
1042 rewriter.replaceOpWithMultiple(op, {newShapeCastOps});
1043 return success();
1044 }
1045};
1046
1047/// This pattern transforms vector.multi_dim_reduction operations from
1048/// workgroup-level to subgroup-level execution with support for multiple
1049/// reduction dimensions.
1050///
1051/// Steps include:
1052/// 1. LOCAL REDUCTION :
1053/// - Each subgroup performs local reduction on its data slice
1054/// - Uses ZERO accumulator to avoid double-counting during cross-subgroup
1055/// phase
1056///
1057/// 2. CROSS-SUBGROUP :
1058/// - Determines if cross-subgroup reduction is needed (when sg_layout > 1 in
1059/// reduction dims & sgData[reduction dims] < wgData[reduction dims])
1060/// - If not needed, adds original accumulator and returns local results
1061///
1062/// 3. SHARED LOCAL MEMORY (SLM) PHASE (when cross-subgroup reduction needed):
1063/// a) SLM Layout Design:
1064/// - Rows: subgroups participating in reduction (product of sg_layout in
1065/// reduction dims)
1066/// - Cols: total result elements across non-reduction dimensions
1067///
1068/// b) Store Phase:
1069/// - Each subgroup stores its local reduction result to SLM
1070/// - Row offset: linearized index of subgroup in reduction dimensions
1071/// - Col offset: linearized index of subgroup in non-reduction dimensions
1072///
1073/// c) Load and Final Reduction Phase:
1074/// - Each subgroup loads a column of data (all reduction participants for
1075/// its position)
1076/// - Performs final reduction along the loaded dimension
1077/// - Adds original accumulator to get final result
1078///
1079struct WgToSgMultiDimReductionOp
1080 : public OpConversionPattern<vector::MultiDimReductionOp> {
1081 using OpConversionPattern<vector::MultiDimReductionOp>::OpConversionPattern;
1082
1083 LogicalResult
1084 matchAndRewrite(vector::MultiDimReductionOp op, OneToNOpAdaptor adaptor,
1085 ConversionPatternRewriter &rewriter) const override {
1086 Location loc = op.getLoc();
1087
1088 VectorType srcType = op.getSourceVectorType();
1089 Type resultTy = op.getResult().getType();
1090 VectorType dstVecType = dyn_cast<VectorType>(resultTy);
1091 bool isScalarResult = !dstVecType;
1092
1093 auto originalSrcShape = srcType.getShape();
1094 Type elemTy = srcType.getElementType();
1095
1096 xegpu::DistributeLayoutAttr layout =
1097 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1098 if (!layout || !layout.isForWorkgroup())
1099 return failure();
1100
1101 auto reductionDims = llvm::to_vector(op.getReductionDims());
1102
1103 // Get sg_layout and sg_data from the parent layout
1104 SmallVector<int64_t> sgLayout;
1105 SmallVector<int64_t> sgData;
1106 xegpu::DistributeLayoutAttr parentLayout;
1107 if (auto sliceAttr = dyn_cast<xegpu::SliceAttr>(layout)) {
1108 parentLayout = sliceAttr.getParent();
1109 sgLayout = parentLayout.getEffectiveSgLayoutAsInt();
1110 sgData = parentLayout.getEffectiveSgDataAsInt();
1111 } else
1112 return rewriter.notifyMatchFailure(
1113 op, "Reduction should have SliceAttr layout");
1114
1115 // Step 1: perform local subgroup reductions with neutral accumulator
1116 SmallVector<Value> localReductions;
1117 auto sgSrcs = adaptor.getSource();
1118 auto sgSrcType = dyn_cast<VectorType>(sgSrcs.front().getType());
1119 SmallVector<int64_t> sgSrcShape(sgSrcType.getShape().begin(),
1120 sgSrcType.getShape().end());
1121
1122 // Determine the SG-level destination type.
1123 // For scalar results (all dims reduced), the sg result is also scalar.
1124 // For vector results, compute the sg destination shape from layout.
1125 Type sgDstType;
1126 if (dstVecType) {
1127 auto originalDstShape = dstVecType.getShape();
1128 SmallVector<int64_t> sgDstShape =
1129 getSgShapeAndCount(originalDstShape, layout).first;
1130 sgDstType = VectorType::get(sgDstShape, elemTy);
1131 } else {
1132 sgDstType = elemTy;
1133 }
1134
1135 for (auto sgSrc : sgSrcs) {
1136 // Create neutral accumulator for local reduction
1137 Value neutralLocalAcc = xegpu::createReductionNeutralValue(
1138 rewriter, loc, sgDstType, op.getKind());
1139 // Local reduction with neutral accumulator
1140 auto localReduce = vector::MultiDimReductionOp::create(
1141 rewriter, loc, sgDstType, op.getKind(), sgSrc, neutralLocalAcc,
1142 reductionDims);
1143 localReductions.push_back(localReduce.getResult());
1144 }
1145
1146 // Check if cross-subgroup reduction is needed for any reduction dimension
1147 SmallVector<int64_t> crossSgReductionDims;
1148 for (int64_t reductionDim : reductionDims) {
1149 bool needsCrossSubgroupReduction =
1150 (sgLayout[reductionDim] > 1) &&
1151 (sgData[reductionDim] < originalSrcShape[reductionDim]);
1152
1153 if (needsCrossSubgroupReduction) {
1154 crossSgReductionDims.push_back(reductionDim);
1155 }
1156 }
1157
1158 // If no cross-subgroup reduction needed, add accumulator and return
1159 if (crossSgReductionDims.empty()) {
1160 SmallVector<Value> results;
1161 for (auto localResult : localReductions) {
1162 auto finalResult = vector::makeArithReduction(
1163 rewriter, loc, op.getKind(), localResult, adaptor.getAcc()[0]);
1164 results.push_back(finalResult);
1165 }
1166 rewriter.replaceOpWithMultiple(op, {results});
1167 return success();
1168 }
1169
1170 // Step 2: cross-subgroup reduction using SLM - allocating slm memory
1171 auto slmStoreDataShape = sgSrcShape;
1172 for (int64_t dim : reductionDims)
1173 slmStoreDataShape[dim] = 1;
1174 VectorType slmStoreDataType = VectorType::get(slmStoreDataShape, elemTy);
1175 SmallVector<Value> slmStoreData;
1176 for (auto localResult : localReductions) {
1177 if (isScalarResult) {
1178 // Scalar result: broadcast scalar to vector<1x...x1> for SLM store
1179 slmStoreData.push_back(vector::BroadcastOp::create(
1180 rewriter, loc, slmStoreDataType, localResult));
1181 } else {
1182 slmStoreData.push_back(vector::ShapeCastOp::create(
1183 rewriter, loc, slmStoreDataType, localResult));
1184 }
1185 }
1186 // for reduction dimension, SLM stores partial results from each subgroup
1187 SmallVector<int64_t> slmShape(originalSrcShape.begin(),
1188 originalSrcShape.end());
1189 SmallVector<int> slmSgData(sgData.begin(), sgData.end());
1190 SmallVector<int> slmSgLayout(sgLayout.begin(), sgLayout.end());
1191 for (int dim : reductionDims) {
1192 slmShape[dim] = sgLayout[dim];
1193 slmSgData[dim] = 1;
1194 }
1195 xegpu::LayoutAttr slmStoreLayout =
1196 xegpu::LayoutAttr::get(rewriter.getContext(), slmSgLayout, slmSgData);
1197
1198 // Allocate SLM
1199 auto bitWidth = elemTy.getIntOrFloatBitWidth();
1200 auto bytesPerElement = bitWidth / 8;
1201 auto slmSize = computeProduct(slmShape) * bytesPerElement;
1202 auto slmTy = MemRefType::get({slmSize}, rewriter.getI8Type(), {}, 3);
1203 auto slm = memref::AllocaOp::create(rewriter, loc, slmTy);
1204
1205 auto memDescType = xegpu::MemDescType::get(rewriter.getContext(), slmShape,
1206 elemTy, nullptr);
1207 auto memDesc =
1208 xegpu::CreateMemDescOp::create(rewriter, loc, memDescType, slm);
1209
1210 // Step 3: Store local results to SLM
1211 auto sgId = gpu::SubgroupIdOp::create(rewriter, loc,
1212 rewriter.getIndexType(), nullptr);
1213
1214 auto slmStoreCoords =
1215 slmStoreLayout.computeDistributedCoords(rewriter, loc, sgId, slmShape);
1216 if (failed(slmStoreCoords))
1217 return failure();
1218 for (auto [data, coord] : llvm::zip(slmStoreData, *slmStoreCoords)) {
1219 SmallVector<OpFoldResult> coordOfr(coord.begin(), coord.end());
1220 xegpu::StoreMatrixOp::create(rewriter, loc, data, memDesc.getResult(),
1221 coordOfr,
1222 /*layout=*/nullptr);
1223 }
1224
1225 gpu::BarrierOp::create(rewriter, loc);
1226
1227 // Step 4: Load from SLM for final reduction
1228 SmallVector<int64_t> slmLoadDataShape(sgSrcShape.begin(), sgSrcShape.end());
1229 for (int64_t dim : reductionDims) {
1230 slmLoadDataShape[dim] = slmShape[dim];
1231 slmSgData[dim] = slmShape[dim];
1232 }
1233 xegpu::LayoutAttr slmLoadLayout =
1234 xegpu::LayoutAttr::get(rewriter.getContext(), slmSgLayout, slmSgData);
1235 auto slmLoadCoords =
1236 slmLoadLayout.computeDistributedCoords(rewriter, loc, sgId, slmShape);
1237 if (failed(slmLoadCoords))
1238 return failure();
1239
1240 VectorType slmLoadType = VectorType::get(slmLoadDataShape, elemTy);
1241 SmallVector<Value> slmLoadData;
1242 for (auto coord : *slmLoadCoords) {
1243 SmallVector<OpFoldResult> coordOfr(coord.begin(), coord.end());
1244 slmLoadData.push_back(xegpu::LoadMatrixOp::create(
1245 rewriter, loc, slmLoadType, memDesc.getResult(), coordOfr,
1246 /*layout=*/nullptr));
1247 }
1248
1249 // Step 5: Perform final reduction with neutral accumulator and add the
1250 // original accumulator at the end
1251 Value neutralFinalAcc = xegpu::createReductionNeutralValue(
1252 rewriter, loc, sgDstType, op.getKind());
1253
1254 SmallVector<Value> finalResults;
1255 for (size_t i = 0; i < slmLoadData.size(); ++i) {
1256 auto loaded = slmLoadData[i];
1257 auto finalReduce = vector::MultiDimReductionOp::create(
1258 rewriter, loc, sgDstType, op.getKind(), loaded, neutralFinalAcc,
1259 reductionDims);
1260 finalResults.push_back(vector::makeArithReduction(
1261 rewriter, loc, op.getKind(), finalReduce.getResult(),
1262 adaptor.getAcc()[i]));
1263 }
1264 rewriter.replaceOpWithMultiple(op, {finalResults});
1265 return success();
1266 }
1267};
1268
1269// This pattern transforms vector.transpose ops to work at subgroup level.
1270struct WgToSgVectorTransposeOp
1271 : public OpConversionPattern<vector::TransposeOp> {
1272 using OpConversionPattern<vector::TransposeOp>::OpConversionPattern;
1273
1274 LogicalResult
1275 matchAndRewrite(vector::TransposeOp op, OneToNOpAdaptor adaptor,
1276 ConversionPatternRewriter &rewriter) const override {
1277 VectorType resultType = op.getResultVectorType();
1278
1279 ArrayRef<int64_t> wgShape = resultType.getShape();
1280 xegpu::DistributeLayoutAttr layout =
1281 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1282 if (!layout || !layout.isForWorkgroup())
1283 return failure();
1284 xegpu::DistributeLayoutAttr sourceLayout =
1285 xegpu::getTemporaryLayout(op->getOpOperand(0));
1286 if (!sourceLayout || !sourceLayout.isForWorkgroup())
1287 return failure();
1288
1289 SmallVector<int64_t> sourceSgLayout =
1290 sourceLayout.getEffectiveSgLayoutAsInt();
1291 SmallVector<int64_t> resultSgLayout = layout.getEffectiveSgLayoutAsInt();
1292
1293 ArrayRef<int64_t> permutation = op.getPermutation();
1294 size_t permutationSize = permutation.size();
1295 if (sourceSgLayout.size() != permutationSize ||
1296 resultSgLayout.size() != permutationSize) {
1297 return rewriter.notifyMatchFailure(
1298 op, "Layouts and permutation must have the same rank");
1299 }
1300
1301 // Check that sgLayout, sgData & order are properly transposed for source
1302 // and result
1303 if (!layout.isTransposeOf(sourceLayout, permutation,
1304 xegpu::LayoutKind::Subgroup))
1305 return rewriter.notifyMatchFailure(
1306 op, "Result layout is not a valid transpose of source layout "
1307 "according to permutation");
1308
1309 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
1310 VectorType newResultType =
1311 VectorType::get(sgShape, resultType.getElementType());
1312
1313 SmallVector<Value> newTransposeOps;
1314 for (auto src : adaptor.getVector()) {
1315 auto newTranspose = vector::TransposeOp::create(
1316 rewriter, op.getLoc(), newResultType, src, permutation);
1317 newTransposeOps.push_back(newTranspose.getResult());
1318 }
1319 rewriter.replaceOpWithMultiple(op, {newTransposeOps});
1320 return success();
1321 }
1322};
1323
1324// Distribute vector mask ops to work at subgroup level.
1325template <typename MaskOpType>
1326struct WgToSgVectorMaskOp : public OpConversionPattern<MaskOpType> {
1327 using OpConversionPattern<MaskOpType>::OpConversionPattern;
1328
1329 LogicalResult matchAndRewrite(
1330 MaskOpType op,
1331 typename OpConversionPattern<MaskOpType>::OneToNOpAdaptor adaptor,
1332 ConversionPatternRewriter &rewriter) const override {
1333 xegpu::DistributeLayoutAttr layout =
1334 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1335 if (!layout || !layout.isForWorkgroup())
1336 return failure();
1337
1338 Location loc = op.getLoc();
1339 VectorType type = op.getResult().getType();
1340 auto wgShape = type.getShape();
1341
1342 SmallVector<Value> wgMaskDimSizes;
1343 if constexpr (std::is_same_v<MaskOpType, vector::ConstantMaskOp>) {
1344 for (int64_t maskSize : op.getMaskDimSizes()) {
1345 wgMaskDimSizes.push_back(
1346 arith::ConstantIndexOp::create(rewriter, loc, maskSize));
1347 }
1348 } else if constexpr (std::is_same_v<MaskOpType, vector::CreateMaskOp>) {
1349 wgMaskDimSizes = llvm::to_vector(op.getOperands());
1350 }
1351
1352 Value sgId =
1353 gpu::SubgroupIdOp::create(rewriter, loc, /*upper_bound=*/nullptr);
1354 auto sgOffsets =
1355 layout.computeDistributedCoords(rewriter, loc, sgId, wgShape);
1356 if (failed(sgOffsets))
1357 return failure();
1358
1359 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
1360 VectorType resultType = VectorType::get(sgShape, type.getElementType());
1361
1362 // In each dimension, each subgroup computes its local mask size as:
1363 // min(max(wgMaskDimSize[d] - offset[d], 0), sgDimSize[d])
1364 SmallVector<Value> newCreateMaskOps;
1365 for (auto offsetSet : *sgOffsets) {
1366 SmallVector<Value> maskOperands;
1367
1368 for (auto [i, wgMaskDimSize] : llvm::enumerate(wgMaskDimSizes)) {
1369 Value dimSizeVal =
1370 arith::ConstantIndexOp::create(rewriter, loc, sgShape[i]);
1371 Value offset = offsetSet[i];
1372 Value adjustedMaskSize =
1373 arith::SubIOp::create(rewriter, loc, wgMaskDimSize, offset);
1374 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
1375 Value nonNegative =
1376 arith::MaxSIOp::create(rewriter, loc, adjustedMaskSize, zero);
1377 Value sgMaskSize =
1378 arith::MinSIOp::create(rewriter, loc, nonNegative, dimSizeVal);
1379 maskOperands.push_back(sgMaskSize);
1380 }
1381
1382 auto newCreateMaskOp =
1383 vector::CreateMaskOp::create(rewriter, loc, resultType, maskOperands);
1384 newCreateMaskOps.push_back(newCreateMaskOp.getResult());
1385 }
1386
1387 rewriter.replaceOpWithMultiple(op, {newCreateMaskOps});
1388 return success();
1389 }
1390};
1391
1392using WgToSgVectorConstantMaskOp = WgToSgVectorMaskOp<vector::ConstantMaskOp>;
1393using WgToSgVectorCreateMaskOp = WgToSgVectorMaskOp<vector::CreateMaskOp>;
1394
1395// This pattern transforms vector.bitcast ops to work at subgroup level.
1396struct WgToSgVectorBitCastOp : public OpConversionPattern<vector::BitCastOp> {
1397 using OpConversionPattern<vector::BitCastOp>::OpConversionPattern;
1398
1399 LogicalResult
1400 matchAndRewrite(vector::BitCastOp op, OneToNOpAdaptor adaptor,
1401 ConversionPatternRewriter &rewriter) const override {
1402 VectorType resultType = op.getResultVectorType();
1403
1404 ArrayRef<int64_t> wgShape = resultType.getShape();
1405 xegpu::DistributeLayoutAttr layout =
1406 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1407 if (!layout || !layout.isForWorkgroup())
1408 return failure();
1409
1410 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
1411 VectorType newResultType =
1412 VectorType::get(sgShape, resultType.getElementType());
1413
1414 SmallVector<Value> newBitCastOps;
1415 for (auto src : adaptor.getSource()) {
1416 auto newBitCast =
1417 vector::BitCastOp::create(rewriter, op.getLoc(), newResultType, src);
1418 newBitCastOps.push_back(newBitCast.getResult());
1419 }
1420
1421 rewriter.replaceOpWithMultiple(op, {newBitCastOps});
1422 return success();
1423 }
1424};
1425
1426// This pattern transforms vector.interleave ops to work at subgroup level.
1427struct WgToSgVectorInterleaveOp
1428 : public OpConversionPattern<vector::InterleaveOp> {
1429 using OpConversionPattern<vector::InterleaveOp>::OpConversionPattern;
1430
1431 LogicalResult
1432 matchAndRewrite(vector::InterleaveOp op, OneToNOpAdaptor adaptor,
1433 ConversionPatternRewriter &rewriter) const override {
1434 VectorType resultType = op.getResultVectorType();
1435
1436 ArrayRef<int64_t> wgShape = resultType.getShape();
1437 xegpu::DistributeLayoutAttr layout =
1438 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1439 if (!layout || !layout.isForWorkgroup())
1440 return failure();
1441
1442 SmallVector<int64_t> sgShape = getSgShapeAndCount(wgShape, layout).first;
1443 VectorType newResultType =
1444 VectorType::get(sgShape, resultType.getElementType());
1445
1446 SmallVector<Value> newInterleaveOps;
1447 // Interleave operates pairwise: each lhs value is interleaved with
1448 // corresponding rhs value
1449 for (auto [lhs, rhs] : llvm::zip(adaptor.getLhs(), adaptor.getRhs())) {
1450 auto newInterleave = vector::InterleaveOp::create(
1451 rewriter, op.getLoc(), newResultType, lhs, rhs);
1452 newInterleaveOps.push_back(newInterleave.getResult());
1453 }
1454
1455 rewriter.replaceOpWithMultiple(op, {newInterleaveOps});
1456 return success();
1457 }
1458};
1459
1460// This pattern transforms vector.deinterleave ops to work at subgroup level.
1461struct WgToSgVectorDeinterleaveOp
1462 : public OpConversionPattern<vector::DeinterleaveOp> {
1463 using OpConversionPattern<vector::DeinterleaveOp>::OpConversionPattern;
1464
1465 LogicalResult
1466 matchAndRewrite(vector::DeinterleaveOp op, OneToNOpAdaptor adaptor,
1467 ConversionPatternRewriter &rewriter) const override {
1468 SmallVector<Value> newRes1Ops;
1469 SmallVector<Value> newRes2Ops;
1470
1471 for (auto src : adaptor.getSource()) {
1472 auto newDeinterleave =
1473 vector::DeinterleaveOp::create(rewriter, op.getLoc(), src);
1474 newRes1Ops.push_back(newDeinterleave.getRes1());
1475 newRes2Ops.push_back(newDeinterleave.getRes2());
1476 }
1477
1478 SmallVector<SmallVector<Value>> results = {newRes1Ops, newRes2Ops};
1479 rewriter.replaceOpWithMultiple(op, results);
1480 return success();
1481 }
1482};
1483
1484} // namespace
1485
1486namespace mlir {
1487namespace xegpu {
1489 Operation *topLevelOp) {
1490 // Pass through all types by default.
1491 converter.addConversion([](Type type) -> Type { return type; });
1492
1493 // For TensorDescType, convert WG-level tensor descs to N SG-level descs.
1494 converter.addConversion(
1495 [](xegpu::TensorDescType type,
1496 SmallVectorImpl<Type> &result) -> std::optional<LogicalResult> {
1497 xegpu::DistributeLayoutAttr layout = type.getLayoutAttr();
1498 if (!layout || !layout.isForWorkgroup())
1499 return std::nullopt;
1500
1501 Type elemTy = type.getElementType();
1502 ArrayRef<int64_t> shape = type.getShape();
1503
1504 int count;
1505 SmallVector<int64_t> subShape;
1506 std::tie(subShape, count) = getSgShapeAndCount(shape, layout);
1507
1508 layout = layout.dropSgLayoutAndData();
1509
1510 auto newTy = xegpu::TensorDescType::get(
1511 type.getContext(), subShape, elemTy, type.getEncoding(), layout);
1512 result.append(count, newTy);
1513 return success();
1514 });
1515
1516 // Context-aware VectorType conversion based on sg_layout/sg_data
1517 // (1:1 shape-changing or 1:N).
1518 auto getSubShapeAndCount = [](VectorType vecTy,
1519 xegpu::DistributeLayoutAttr layout)
1520 -> std::pair<SmallVector<int64_t>, int> {
1521 if (!layout.isForWorkgroup())
1522 return {{}, 0};
1523 return getSgShapeAndCount(vecTy.getShape(), layout);
1524 };
1525 auto loopArgTypes =
1526 xegpu::precomputeLoopBlockArgTypes(topLevelOp, getSubShapeAndCount);
1527 xegpu::addVectorTypeConversion(converter, getSubShapeAndCount,
1528 std::move(loopArgTypes));
1529}
1530
1532 patterns.add<WgToSgCreateNdOp, WgToSgLoadNdOp, WgToSgStoreNdOp, WgToSgDpasOp,
1533 WgToSgDpasMxOp, WgToSgPrefetchNdOp, WgToSgElementwiseOp,
1534 WgToSgVectorBroadcastOp, WgToSgConvertLayoutOp,
1535 WgToSgArithConstantOp, WgToSgLoadGatherOp, WgToSgStoreScatterOp,
1536 WgToSgLoadMatrixOp, WgToSgStoreMatrixOp, WgToSgVectorStepOp,
1537 WgToSgVectorShapeCastOp, WgToSgMultiDimReductionOp,
1538 WgToSgVectorTransposeOp, WgToSgVectorConstantMaskOp,
1539 WgToSgVectorCreateMaskOp, WgToSgVectorBitCastOp,
1540 WgToSgVectorInterleaveOp, WgToSgVectorDeinterleaveOp>(
1541 patterns.getContext());
1542}
1543} // namespace xegpu
1544} // namespace mlir
1545
1546namespace {
1547struct XeGPUWgToSgDistributePass
1548 : public xegpu::impl::XeGPUWgToSgDistributeBase<XeGPUWgToSgDistributePass> {
1549 void runOnOperation() override;
1550};
1551} // namespace
1552
1553void XeGPUWgToSgDistributePass::runOnOperation() {
1554
1555 Operation *op = getOperation();
1557 signalPassFailure();
1558 return;
1559 }
1560
1561 // Collect existing UnrealizedConversionCastOps. These must be preserved.
1562 llvm::SmallSetVector<UnrealizedConversionCastOp, 8> existingCasts;
1563 getOperation()->walk(
1564 [&](UnrealizedConversionCastOp castOp) { existingCasts.insert(castOp); });
1565
1566 // Perform workgroup to subgroup distribution for TensorDesc and Vector
1567 // values, as well as XeGPU, Arith, and Vector operations. Uses a
1568 // context-aware type converter that inspects Values to retrieve the
1569 // distribute layout attribute for 1:N type conversion.
1570 MLIRContext *ctx = &getContext();
1571 RewritePatternSet patterns(ctx);
1572 ConversionTarget target(*ctx);
1573 TypeConverter converter;
1574 // Source (N:1) and target (1:1) materializations using
1575 // UnrealizedConversionCastOp.
1576 auto materializeCast = [](OpBuilder &builder, Type type, ValueRange inputs,
1577 Location loc) -> Value {
1578 return UnrealizedConversionCastOp::create(builder, loc, type, inputs)
1579 .getResult(0);
1580 };
1581 converter.addSourceMaterialization(materializeCast);
1582 converter.addTargetMaterialization(materializeCast);
1584 getOperation());
1585
1586 auto getTensorDescType = [](Operation *op) -> xegpu::TensorDescType {
1587 if (auto createOp = dyn_cast<xegpu::CreateNdDescOp>(op))
1588 return createOp.getType();
1589 if (auto loadOp = dyn_cast<xegpu::LoadNdOp>(op))
1590 return loadOp.getTensorDescType();
1591 if (auto storeOp = dyn_cast<xegpu::StoreNdOp>(op))
1592 return storeOp.getTensorDescType();
1593 if (auto prefetchOp = dyn_cast<xegpu::PrefetchNdOp>(op))
1594 return prefetchOp.getTensorDescType();
1595 return xegpu::TensorDescType();
1596 };
1597
1598 auto isLegal = [&](xegpu::DistributeLayoutAttr layout) -> bool {
1599 return !layout || !layout.isForWorkgroup();
1600 };
1601
1602 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp, xegpu::LoadNdOp,
1603 xegpu::StoreNdOp, xegpu::PrefetchNdOp>(
1604 [=](Operation *op) -> bool {
1605 auto tdescTy = getTensorDescType(op);
1606 auto layout = dyn_cast_if_present<xegpu::DistributeLayoutAttr>(
1607 tdescTy.getLayout());
1608 return isLegal(layout);
1609 });
1610
1611 target.addDynamicallyLegalOp<xegpu::DpasOp>([=](xegpu::DpasOp op) -> bool {
1612 auto layout = op.getLayoutCdAttr();
1613 return isLegal(layout);
1614 });
1615
1616 target.addDynamicallyLegalOp<xegpu::DpasMxOp>(
1617 [=](xegpu::DpasMxOp op) -> bool {
1618 auto layout = op.getLayoutCdAttr();
1619 return isLegal(layout);
1620 });
1621
1622 target.addDynamicallyLegalOp<xegpu::LoadMatrixOp>(
1623 [=](xegpu::LoadMatrixOp op) -> bool {
1624 return isLegal(op.getLayoutAttr());
1625 });
1626
1627 target.addDynamicallyLegalOp<xegpu::StoreMatrixOp>(
1628 [=](xegpu::StoreMatrixOp op) -> bool {
1629 return isLegal(op.getLayoutAttr());
1630 });
1631
1632 target.addDynamicallyLegalOp<arith::ConstantOp>(
1633 [=](arith::ConstantOp op) -> bool {
1634 auto vecType = dyn_cast<VectorType>(op.getType());
1635 if (!vecType)
1636 return true;
1637
1638 auto layout =
1639 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1640 return isLegal(layout);
1641 });
1642
1643 target.addDynamicallyLegalOp<
1644 vector::ShapeCastOp, vector::StepOp, vector::TransposeOp,
1645 vector::BroadcastOp, vector::MultiDimReductionOp, vector::ConstantMaskOp,
1646 vector::CreateMaskOp, vector::BitCastOp, vector::InterleaveOp,
1647 vector::DeinterleaveOp>([=](Operation *op) -> bool {
1648 // Check for either a SliceAttr or LayoutAttr on the result.
1649 auto layout =
1650 xegpu::getTemporaryLayout(dyn_cast<OpResult>(op->getResult(0)));
1651 return isLegal(layout);
1652 });
1653
1654 target.addDynamicallyLegalOp<xegpu::LoadGatherOp>(
1655 [=](xegpu::LoadGatherOp op) -> bool {
1656 auto layout = op.getLayoutAttr();
1657 return isLegal(layout);
1658 });
1659
1660 target.addDynamicallyLegalOp<xegpu::StoreScatterOp>(
1661 [=](xegpu::StoreScatterOp op) -> bool {
1662 auto layout = op.getLayoutAttr();
1663 return isLegal(layout);
1664 });
1665
1666 target.addDynamicallyLegalOp<xegpu::ConvertLayoutOp>(
1667 [=](xegpu::ConvertLayoutOp op) -> bool {
1668 return isLegal(op.getEffectiveInputLayout()) &&
1669 isLegal(op.getTargetLayout());
1670 });
1671
1672 target.addDynamicallyLegalDialect<math::MathDialect, arith::ArithDialect>(
1673 [=](Operation *op) -> std::optional<bool> {
1674 // Only handle elementwise mappable ops
1676 return true;
1677
1678 VectorType resultType =
1679 dyn_cast<VectorType>(op->getResult(0).getType());
1680 if (!resultType)
1681 return true;
1682
1683 // Check if all operands are vectors of the same shape
1684 // TODO: Support other types.
1685 for (Value operand : op->getOperands()) {
1686 VectorType operandType = dyn_cast<VectorType>(operand.getType());
1687 if (!operandType || operandType.getShape() != resultType.getShape()) {
1688 return true;
1689 }
1690 }
1691
1692 xegpu::DistributeLayoutAttr layout =
1694 return isLegal(layout);
1695 });
1696
1697 target.addLegalOp<UnrealizedConversionCastOp>();
1698
1699 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
1700
1702 target);
1704 if (failed(
1705 applyPartialConversion(getOperation(), target, std::move(patterns))))
1706 return signalPassFailure();
1707
1708 // Fold cancelling cast chains and erase dead casts.
1709 xegpu::cleanupUnrealizedConversionCasts(getOperation(), existingCasts);
1710 xegpu::removeTemporaryLayoutAttrs(getOperation());
1711}
return success()
lhs
b getContext())
#define mul(a, b)
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
MLIRContext * getContext() const
Return the context this location is uniqued in.
Definition Location.h:86
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Attribute getDiscardableAttr(StringRef name)
Access a discardable attribute by name, returns a null Attribute if the discardable attribute does no...
Definition Operation.h:485
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Attribute getPropertiesAsAttribute()
Return the properties converted to an attribute.
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class 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
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
void populateSCFStructuralTypeConversionsAndLegality(const TypeConverter &typeConverter, RewritePatternSet &patterns, ConversionTarget &target, PatternBenefit benefit=1)
Populates patterns for SCF structural type conversions and sets up the provided ConversionTarget with...
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.
void removeTemporaryLayoutAttrs(Operation *op)
Removes the temporary layout attributes for each OpOperand and OpResult of the given operation.
void populateXeGPUWgToSgDistributeTypeConversions(TypeConverter &converter, Operation *topLevelOp)
Define the type conversions needed for XeGPU workgroup to subgroup distribution.
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.
bool matchUnitDimExpansion(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< int64_t > &expandedUnitDims)
bool recoverTemporaryLayouts(Operation *rootOp)
Attach layout attributes to all vector-type operands of operations within the given operation's neste...
DenseMap< Value, SmallVector< Type > > precomputeLoopBlockArgTypes(Operation *topLevelOp, SubShapeAndCountFn getSubShapeAndCount)
Pre-computes distributed VectorType mappings for every value carried through an SCF loop under topLev...
void populateXeGPUWgToSgDistributePatterns(RewritePatternSet &patterns)
Appends patterns for XeGPU workgroup to subgroup distribution into patterns.
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,...
DistributeLayoutAttr getTemporaryLayout(const T &operandOrResult)
get and set distribute layout attribute for non-anchor operations (and offsets/masks of load/store op...
void removeLayoutAttrs(Operation *op)
Removes the DistributeLayoutAttr for each OpOperand and OpResult of the given operation if they exist...
void cleanupUnrealizedConversionCasts(Operation *root, const llvm::SmallSetVector< UnrealizedConversionCastOp, 8 > &existingCasts)
Cleans up UnrealizedConversionCastOps inserted during SCF structural type conversion and/or XeGPU unr...
SmallVector< OpFoldResult > addWithRightAligned(OpBuilder &builder, Location loc, ArrayRef< OpFoldResult > lhs, ArrayRef< OpFoldResult > rhs)
Generates element-wise addition ops of two arrays with automatic alignment.
Include the generated interface declarations.
int64_t computeProduct(ArrayRef< int64_t > basis)
Self-explicit.
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.