MLIR 24.0.0git
XeGPUSgToLaneDistribute.cpp
Go to the documentation of this file.
1//===- XeGPUSgToLaneDistribute.cpp - XeGPU SG to Lane 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//===----------------------------------------------------------------------===//
21#include "mlir/IR/Builders.h"
23#include "mlir/IR/BuiltinOps.h"
25#include "mlir/IR/MLIRContext.h"
26#include "mlir/IR/Operation.h"
27#include "mlir/IR/Value.h"
28#include "mlir/IR/ValueRange.h"
30#include "llvm/ADT/SetVector.h"
31#include "llvm/Support/LogicalResult.h"
32#include "llvm/Support/raw_ostream.h"
33#include <optional>
34
35namespace mlir {
36namespace xegpu {
37#define GEN_PASS_DEF_XEGPUSGTOLANEDISTRIBUTE
38#include "mlir/Dialect/XeGPU/Transforms/Passes.h.inc"
39} // namespace xegpu
40} // namespace mlir
41
42using namespace mlir;
43
44#define DEBUG_TYPE "xegpu-sg-to-lane-distribute"
45#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "]: ")
46
47namespace {
48
49/// Casts the given vector value `v` to the expected vector type `expectedTy`.
50static Value castValueTo(ConversionPatternRewriter &rewriter,
51 TypedValue<VectorType> v, VectorType expectedTy) {
52 // If the type matches, simply return the value itself.
53 if (v.getType() == expectedTy)
54 return v;
55 // If only shape differs, use shape cast.
56 if (isa<VectorType>(v.getType()) &&
57 v.getType().getNumElements() == expectedTy.getNumElements())
58 return vector::ShapeCastOp::create(rewriter, v.getLoc(), expectedTy, v);
59
60 // Else create an unrealized cast.
61 auto newOp = UnrealizedConversionCastOp::create(rewriter, v.getLoc(),
62 expectedTy, ValueRange{v});
63 return newOp.getResult(0);
64}
65
66/// A vector::MultiDimReductionOp at subgroup level in expected form if, it has
67/// exactly 1 reduction dimension, it had valid result layout attribute, and
68/// result type can be distributed to lanes using the layout.
69static bool isValidSubgroupMultiReductionOp(vector::MultiDimReductionOp op) {
70 auto resLayout = xegpu::getTemporaryLayout(op->getOpResult(0));
71 // If no layout, not valid.
72 if (!resLayout || !resLayout.isForSubgroup())
73 return false;
74 // Scalar result (e.g., vector<32xf32> to f32) is valid.
75 if (op.getType().isIntOrFloat())
76 return op.getReductionDims().size() == 1;
77 VectorType resTy = dyn_cast<VectorType>(op.getType());
78 if (!resTy)
79 return false;
80 // Compute the distributed result vector type based on the layout.
81 FailureOr<VectorType> resDistTypeOrFailure =
82 getDistVecTypeBasedOnLaneLayout(resLayout, resTy);
83 if (failed(resDistTypeOrFailure))
84 return false;
85 return op.getReductionDims().size() == 1;
86}
87
88/// A vector::MultiDimReductionOp is doing lane-local reduction if each lane
89/// is doing its own local reduction. In this case the result layout ensures
90/// that result vector is distributed to lanes, i.e. the result vector type is
91/// different from the distributed result vector type.
92static bool isReductionLaneLocal(vector::MultiDimReductionOp op) {
93 // Must be valid MultiDimReductionOp.
94 assert(isValidSubgroupMultiReductionOp(op) && "Expecting a valid subgroup "
95 "MultiDimReductionOp");
96 auto resLayout = xegpu::getTemporaryLayout(op->getOpResult(0));
97 VectorType resTy = dyn_cast<VectorType>(op.getType());
98 auto resDistTypeOrFailure = getDistVecTypeBasedOnLaneLayout(resLayout, resTy);
99 return resTy != resDistTypeOrFailure.value();
100}
101
102/// Given a vector type and its distributed vector type, return the list of
103/// dimensions that are distributed.
104static SmallVector<int64_t> getDistributedDims(VectorType originalType,
105 VectorType distributedType) {
106 assert(originalType.getRank() == distributedType.getRank() &&
107 "original and distributed vector types must have the same rank");
108 SmallVector<int64_t> distributedDims;
109 for (int64_t i = 0; i < originalType.getRank(); ++i) {
110 if (distributedType.getDimSize(i) != originalType.getDimSize(i))
111 distributedDims.push_back(i);
112 }
113 return distributedDims;
114}
115
116/// Distributes a subgroup-level CreateNdDesc op to lane-level CreateNdDesc
117/// op. This simply drops the layout attribute from the tensor descriptor type.
118struct SgToLaneCreateNdDesc
119 : public OpConversionPattern<xegpu::CreateNdDescOp> {
120 using OpConversionPattern<xegpu::CreateNdDescOp>::OpConversionPattern;
121
122 LogicalResult
123 matchAndRewrite(xegpu::CreateNdDescOp op, OpAdaptor adaptor,
124 ConversionPatternRewriter &rewriter) const override {
125 xegpu::TensorDescType resultType = op.getType();
126 // If no layout, nothing to do.
127 if (!resultType.getLayout())
128 return failure();
129
130 auto newOp = xegpu::CreateNdDescOp::create(
131 rewriter, op.getLoc(), TypeRange{resultType.dropLayouts()},
132 op.getOperands(), op.getProperties(),
133 op->getDiscardableAttrDictionary().getValue());
134 rewriter.replaceOp(op, newOp.getResult());
135 return success();
136 }
137};
138
139/// Distributes a subgroup-level LoadNd op to lane-level LoadNd op. Output
140/// of lane-level LoadNd op is 1D. ShapeCast is added to restore the
141/// original rank.
142struct SgToLaneLoadNd : public OpConversionPattern<xegpu::LoadNdOp> {
143 using OpConversionPattern<xegpu::LoadNdOp>::OpConversionPattern;
144
145 LogicalResult
146 matchAndRewrite(xegpu::LoadNdOp op, OpAdaptor adaptor,
147 ConversionPatternRewriter &rewriter) const override {
148 xegpu::DistributeLayoutAttr layout = op.getAnchorLayout();
149 // If no layout, nothing to do.
150 if (!layout)
151 return failure();
152 // Check if the layout attached to the tensor descriptor is same as the
153 // anchor layout. Otherwise, this is a conflict.
154 if (op.getTensorDescType().getLayout() != layout)
155 return rewriter.notifyMatchFailure(
156 op, "conflicting layout attributes on tensor descriptor and anchor");
157 const auto *uArch =
159 if (!uArch)
160 return rewriter.notifyMatchFailure(
161 op, "xegpu::LoadNdOp require target attribute attached to "
162 "determine transpose "
163 "requirement");
164 auto supportedLaneResultTyOrFailure =
165 xegpu::getDistributedVectorType(op.getTensorDescType());
166 auto expectedLaneResultTyOrFailure =
167 xegpu::getDistVecTypeBasedOnLaneLayout(layout, op.getType());
168 if (failed(supportedLaneResultTyOrFailure))
169 return rewriter.notifyMatchFailure(
170 op, "unable to compute the lane vector type for LoadNdOp");
171 if (failed(expectedLaneResultTyOrFailure))
172 return rewriter.notifyMatchFailure(
173 op, "unable to compute expected lane vector type from lane layout");
174 auto newOp = xegpu::LoadNdOp::create(
175 rewriter, op.getLoc(), supportedLaneResultTyOrFailure.value(),
176 adaptor.getTensorDesc(), op.getMixedOffsets(), op.getPackedAttr(),
177 op.getTransposeAttr(), op.getL1HintAttr(), op.getL2HintAttr(),
178 op.getL3HintAttr(), /**layout**/ nullptr);
179 // Set the packed attribute if the layout requires it.
180 newOp.setPacked(xegpu::requirePacked(cast<xegpu::LayoutAttr>(layout)));
181 // Set the transpose attribute if the layout requires it.
182 if (xegpu::requireTranspose(cast<xegpu::LayoutAttr>(layout), uArch))
183 newOp.setTranspose(DenseI64ArrayAttr::get(rewriter.getContext(), {1, 0}));
184 rewriter.replaceOp(op, castValueTo(rewriter, newOp.getResult(),
185 expectedLaneResultTyOrFailure.value()));
186 return success();
187 }
188};
189
190/// Distributes a subgroup-level StoreNd op to lane-level StoreNd op. Stored
191/// value in lane-level StoreNd op is 1D. ShapeCast is added to cast the
192/// incoming value to 1D.
193struct SgToLaneStoreNd : public OpConversionPattern<xegpu::StoreNdOp> {
194 using OpConversionPattern<xegpu::StoreNdOp>::OpConversionPattern;
195
196 LogicalResult
197 matchAndRewrite(xegpu::StoreNdOp op, OpAdaptor adaptor,
198 ConversionPatternRewriter &rewriter) const override {
199 xegpu::DistributeLayoutAttr layout = op.getAnchorLayout();
200 // If no layout, nothing to do.
201 if (!layout)
202 return failure();
203 // Check if the layout attached to the tensor descriptor and value layout is
204 // same as the anchor layout. Otherwise, this is a conflict.
205 if (op.getTensorDescType().getLayout() != layout)
206 return rewriter.notifyMatchFailure(
207 op, "conflicting layout attributes on tensor descriptor and anchor");
208 auto valueLayout = xegpu::getDistributeLayoutAttr(op->getOpOperand(0));
209 if (valueLayout != layout)
210 return rewriter.notifyMatchFailure(
211 op, "conflicting layout attributes on value and anchor");
212 auto supportedLaneValueTyOrFailure =
213 xegpu::getDistributedVectorType(op.getTensorDescType());
214 if (failed(supportedLaneValueTyOrFailure))
215 return rewriter.notifyMatchFailure(
216 op,
217 "unable to compute lane vector type for StoreNdOp value from tensor "
218 "descriptor");
219
220 xegpu::StoreNdOp::create(
221 rewriter, op.getLoc(),
222 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getValue()),
223 supportedLaneValueTyOrFailure.value()),
224 adaptor.getTensorDesc(), op.getMixedOffsets(), op.getL1HintAttr(),
225 op.getL2HintAttr(), op.getL3HintAttr(), /**layout**/ nullptr);
226 rewriter.eraseOp(op);
227 return success();
228 }
229};
230
231/// Distributes a subgroup-level Dpas op to lane-level Dpas op. All inpputs
232/// and output of lane-level Dpas op are 1D. Necessary casts are added to
233/// convert the inputs and output to/from 1D.
234struct SgToLaneDpas : public OpConversionPattern<xegpu::DpasOp> {
235 using OpConversionPattern<xegpu::DpasOp>::OpConversionPattern;
236
237 LogicalResult
238 matchAndRewrite(xegpu::DpasOp op, OpAdaptor adaptor,
239 ConversionPatternRewriter &rewriter) const override {
240 // Check if the op has A, B and CD layouts attached.
241 auto layoutA = cast<xegpu::LayoutAttr>(op.getLayoutAAttr());
242 auto layoutB = cast<xegpu::LayoutAttr>(op.getLayoutBAttr());
243 auto layoutCd = cast<xegpu::LayoutAttr>(op.getLayoutCdAttr());
244 if (!layoutA || !layoutB || !layoutCd)
245 return failure();
246 auto laneResultTyOrFailure =
247 xegpu::getDistributedVectorType(op.getType(), layoutCd);
248 auto laneATypeOrFailure =
249 xegpu::getDistributedVectorType(op.getLhs().getType(), layoutA);
250 auto laneBTypeOrFailure =
251 xegpu::getDistributedVectorType(op.getRhs().getType(), layoutB);
252 auto expectedLaneResultTyOrFailure =
253 xegpu::getDistVecTypeBasedOnLaneLayout(layoutCd, op.getType());
254 if (failed(laneResultTyOrFailure) || failed(laneATypeOrFailure) ||
255 failed(laneBTypeOrFailure))
256 return rewriter.notifyMatchFailure(
257 op, "failed to calculate supported lane vector types for DpasOp "
258 "from layouts");
259 if (failed(expectedLaneResultTyOrFailure))
260 return rewriter.notifyMatchFailure(
261 op, "unable to compute expected lane vector type for DpasOp from "
262 "lane layout");
263
264 // Validate bit widths match uArch packed format requirements
265 const auto *uArch =
267 if (uArch) {
268 const auto *uArchInstruction =
269 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(
270 uArch->getInstruction(
272 if (uArchInstruction) {
273 auto laneAType = laneATypeOrFailure.value();
274 auto laneBType = laneBTypeOrFailure.value();
275 // Calculate total packed bit width = element bit width * vector size
276 unsigned aPackedBitWidth =
277 laneAType.getElementTypeBitWidth() * laneAType.getNumElements();
278 unsigned bPackedBitWidth =
279 laneBType.getElementTypeBitWidth() * laneBType.getNumElements();
280 unsigned expectedABitSize = uArchInstruction->getPackedFormatBitSizeA();
281 unsigned expectedBBitSize = uArchInstruction->getPackedFormatBitSizeB();
282
283 if (aPackedBitWidth % expectedABitSize != 0)
284 return rewriter.notifyMatchFailure(
285 op,
286 "A operand packed bit width must be a multiple of uArch packed "
287 "format requirement");
288 if (bPackedBitWidth % expectedBBitSize != 0)
289 return rewriter.notifyMatchFailure(
290 op,
291 "B operand packed bit width must be a multiple of uArch packed "
292 "format requirement");
293 }
294 }
295
296 auto newOp = xegpu::DpasOp::create(
297 rewriter, op->getLoc(), laneResultTyOrFailure.value(),
298 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getLhs()),
299 laneATypeOrFailure.value()),
300 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getRhs()),
301 laneBTypeOrFailure.value()),
302 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getAcc()),
303 laneResultTyOrFailure.value()),
304 /** layoutA**/ nullptr,
305 /** layoutB**/ nullptr, /** layoutCd**/ nullptr);
306 // Explicitly set the new types to enable correct type materializations.
307 rewriter.replaceOp(op, castValueTo(rewriter, newOp.getResult(),
308 expectedLaneResultTyOrFailure.value()));
309 return success();
310 }
311};
312
313/// Distributes elementwise ops to lane-level elementwise ops. This
314/// currently handles elementwise ops with single result only.
315struct SgToLaneElementWise : public ConversionPattern {
316 SgToLaneElementWise(TypeConverter &typeConverter, MLIRContext *ctx)
317 : ConversionPattern(MatchAnyOpTypeTag(), /*benefit=*/1, ctx) {}
318
319 LogicalResult
320 matchAndRewrite(Operation *op, ArrayRef<Value> operands,
321 ConversionPatternRewriter &rewriter) const override {
322 // Only match ops with elementwise trait and single result.
324 return failure();
325
326 auto resultType = dyn_cast<VectorType>(op->getResult(0).getType());
327 if (!resultType)
328 return rewriter.notifyMatchFailure(
329 op, "operation result is not a vector type");
330
331 xegpu::DistributeLayoutAttr layout =
332 xegpu::getTemporaryLayout(llvm::cast<OpResult>(op->getResult(0)));
333 if (!layout || !layout.isForSubgroup())
334 return rewriter.notifyMatchFailure(
335 op, "operation result does not have subgroup distribute layout");
336
337 auto laneShapeOrFailure =
338 xegpu::getDistVecTypeBasedOnLaneLayout(layout, resultType);
339
340 if (failed(laneShapeOrFailure))
341 return rewriter.notifyMatchFailure(
342 op, "unable to compute lane vector type from the layout");
343
344 VectorType newResultType = laneShapeOrFailure.value();
345 OperationState state(op->getLoc(), op->getName());
346 state.addOperands(operands);
347 state.addTypes(newResultType);
348 // Copy all attributes except for DistributeLayoutAttr.
349 for (auto attr : op->getDiscardableAttrDictionary().getValue()) {
350 if (!isa<xegpu::DistributeLayoutAttr>(attr.getValue()))
351 state.addAttribute(attr.getName(), attr.getValue());
352 }
354 Operation *newOp = rewriter.create(state);
355
356 rewriter.replaceOp(op, newOp->getResult(0));
357 return success();
358 }
359};
360
361/// Distributes a subgroup-level arith ConstantOp to lane-level arith
362/// ConstantOp.
363///
364/// Splat constants are rebuilt with the lane-local vector type. Non-splat
365/// constants are distributed by extracting each lane_data-sized block from
366/// the full constant and inserting it at the correct position in the
367/// distributed vector using insert_strided_slice.
368struct SgToLaneArithConstant : public OpConversionPattern<arith::ConstantOp> {
369 using OpConversionPattern<arith::ConstantOp>::OpConversionPattern;
370
371 LogicalResult
372 matchAndRewrite(arith::ConstantOp op, OpAdaptor adaptor,
373 ConversionPatternRewriter &rewriter) const override {
374 auto resultType = dyn_cast<VectorType>(op.getType());
375 if (!resultType)
376 return failure();
377
378 // Only handle dense vector constants.
379 auto denseAttr = dyn_cast<DenseElementsAttr>(op.getValue());
380 if (!denseAttr)
381 return rewriter.notifyMatchFailure(
382 op, "only dense vector constants are supported");
383
384 xegpu::DistributeLayoutAttr layout =
385 xegpu::getTemporaryLayout(llvm::cast<OpResult>(op.getResult()));
386 if (!layout || !layout.isForSubgroup())
387 return rewriter.notifyMatchFailure(
388 op, "operation result does not have subgroup distribute layout");
389
390 auto laneShapeOrFailure =
391 xegpu::getDistVecTypeBasedOnLaneLayout(layout, resultType);
392
393 if (failed(laneShapeOrFailure))
394 return rewriter.notifyMatchFailure(
395 op, "unable to compute lane vector type from the layout");
396
397 VectorType newResultType = laneShapeOrFailure.value();
398 Location loc = op.getLoc();
399
400 // Splat constants: every lane gets the same value, so just rebuild the
401 // splat with the distributed type.
402 if (denseAttr.isSplat()) {
403 auto scalarValue = denseAttr.getSplatValue<Attribute>();
404 auto newDenseAttr = DenseElementsAttr::get(newResultType, scalarValue);
405 auto newOp =
406 arith::ConstantOp::create(rewriter, loc, newResultType, newDenseAttr);
407 rewriter.replaceOp(op, newOp.getResult());
408 return success();
409 }
410
411 // Non-splat constants: each lane extracts the elements it owns from the
412 // full constant using the distributed coordinates from the layout.
413 auto fullConst =
414 arith::ConstantOp::create(rewriter, loc, resultType, denseAttr);
415
416 Value laneId = gpu::LaneIdOp::create(rewriter, loc, rewriter.getIndexType(),
417 /*upperBound=*/mlir::IntegerAttr());
418 auto maybeCoordsVec = layout.computeDistributedCoords(
419 rewriter, loc, laneId, resultType.getShape());
420 if (failed(maybeCoordsVec))
421 return rewriter.notifyMatchFailure(
422 op, "failed to compute distributed coordinates from layout");
423
424 SmallVector<SmallVector<Value>> coordsVec = maybeCoordsVec.value();
425 SmallVector<int64_t> laneData = layout.getEffectiveLaneDataAsInt();
426 ArrayRef<int64_t> distShape = newResultType.getShape();
427 int64_t rank = newResultType.getRank();
428
429 // Each lane owns one lane_data-sized block per distribution unit.
430 // computeDistributedCoords returns those block starts in row-major order
431 // over the block grid (distShape / laneData).
432 SmallVector<int64_t> blockGridShape(rank);
433 for (int64_t d = 0; d < rank; d++)
434 blockGridShape[d] = distShape[d] / laneData[d];
435 SmallVector<int64_t> blockGridStrides = computeStrides(blockGridShape);
436
437 auto blockType = VectorType::get(laneData, newResultType.getElementType());
438 SmallVector<int64_t> unitTile(rank, 1);
439 SmallVector<int64_t> strides(rank, 1);
440
441 Value result = arith::ConstantOp::create(
442 rewriter, loc, newResultType, rewriter.getZeroAttr(newResultType));
443
444 for (auto [blockIdx, blockStart] : llvm::enumerate(coordsVec)) {
445 // Gather the block's elements from the full constant. The block start is
446 // lane-dynamic, so extract element-by-element (row-major over lane_data)
447 // instead.
448 SmallVector<Value> blockElems;
449 for (SmallVector<int64_t> off :
450 StaticTileOffsetRange(laneData, unitTile)) {
452 for (int64_t d = 0; d < rank; d++)
453 pos[d] = getAsOpFoldResult(arith::AddIOp::create(
454 rewriter, loc, blockStart[d],
455 arith::ConstantIndexOp::create(rewriter, loc, off[d])));
456 blockElems.push_back(vector::ExtractOp::create(
457 rewriter, loc, fullConst.getResult(), pos));
458 }
459
460 // Rebuild the block keeping its lane_data shape, then place it with
461 // insert_strided_slice so the block keeps its orientation in the
462 // distributed vector (e.g. a [2, 1] block stays a vertical 2x1 slice).
463 Value block =
464 vector::FromElementsOp::create(rewriter, loc, blockType, blockElems);
465 SmallVector<int64_t> blockGridPos =
466 delinearize(blockIdx, blockGridStrides);
467 SmallVector<int64_t> offsets(rank);
468 for (int64_t d = 0; d < rank; d++)
469 offsets[d] = blockGridPos[d] * laneData[d];
470 result = vector::InsertStridedSliceOp::create(rewriter, loc, block,
471 result, offsets, strides);
472 }
473
474 rewriter.replaceOp(op, result);
475 return success();
476 }
477};
478
479/// Distributes a subgroup-level PrefetchNd op to lane-level PrefetchNd op.
480struct SgToLanePrefetchNd : public OpConversionPattern<xegpu::PrefetchNdOp> {
481 using OpConversionPattern<xegpu::PrefetchNdOp>::OpConversionPattern;
482
483 LogicalResult
484 matchAndRewrite(xegpu::PrefetchNdOp op, OpAdaptor adaptor,
485 ConversionPatternRewriter &rewriter) const override {
486 xegpu::DistributeLayoutAttr layout = op.getAnchorLayout();
487 // If no layout, nothing to do.
488 if (!layout)
489 return failure();
490
491 xegpu::PrefetchNdOp::create(rewriter, op.getLoc(), adaptor.getTensorDesc(),
492 op.getMixedOffsets(), op.getL1HintAttr(),
493 op.getL2HintAttr(), op.getL3HintAttr(),
494 /**layout**/ nullptr);
495 rewriter.eraseOp(op);
496 return success();
497 }
498};
499
500/// Distributes a subgroup-level LoadGather (xegpu.load) op to lane-level.
501///
502/// Example 1 (1D, no chunk size):
503/// layout = #xegpu.layout<lane_layout = [16], lane_data = [1]>
504/// %mask = producer_op : vector<16xi1>
505/// %offset = producer_op : vector<16xindex>
506/// %0 = xegpu.load %src[%offset], %mask : memref<256xf16>,
507/// vector<16xindex>, vector<16xi1> -> vector<16xf16>
508/// Distributed to:
509/// %mask = producer_op : vector<1xi1>
510/// %offset = producer_op : vector<1xindex>
511/// %0 = xegpu.load %src[%offset], %mask : memref<256xf16>,
512/// vector<1xindex>, vector<1xi1> -> vector<1xf16>
513///
514/// Example 2 (2D with chunk size, same mask & offset):
515/// layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 1]>
516/// %0 = xegpu.load %src[%offset], %mask <{chunk_size=8}> :
517/// memref<256xf16>, vector<16xindex>, vector<16xi1> -> vector<16x8xf16>
518/// Distributed to:
519/// %0 = xegpu.load %src[%offset], %mask <{chunk_size=8}> :
520/// memref<256xf16>, vector<1xindex>, vector<1xi1> -> vector<8xf16>
521///
522/// Example 3 (3D with leading unit dims):
523/// layout = #xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>
524/// %mask = producer_op : vector<1x1x16xi1>
525/// %offset = producer_op : vector<1x1x16xindex>
526/// %0 = xegpu.load %src[%offset], %mask : memref<256xf16>,
527/// vector<1x1x16xindex>, vector<1x1x16xi1> -> vector<1x1x16xf16>
528/// Distributed to:
529/// %mask = producer_op : vector<1x1x1xi1>
530/// %offset = producer_op : vector<1x1x1xindex>
531/// %0 = xegpu.load %src[%offset], %mask : memref<256xf16>,
532/// vector<1xindex>, vector<1xi1> -> vector<1xf16>
533struct SgToLaneLoadGather : public OpConversionPattern<xegpu::LoadGatherOp> {
534 using OpConversionPattern<xegpu::LoadGatherOp>::OpConversionPattern;
535
536 LogicalResult
537 matchAndRewrite(xegpu::LoadGatherOp op, OpAdaptor adaptor,
538 ConversionPatternRewriter &rewriter) const override {
539 xegpu::DistributeLayoutAttr layout = op.getAnchorLayout();
540 if (!layout)
541 return failure();
542
543 VectorType origResultTy = op.getValueType();
544 if (!origResultTy)
545 return failure();
546
547 // Check that leading dimensions are unit.
548 int chunkSize = op.getChunkSize().value_or(1);
549 int effectiveVecRank = (chunkSize == 1) ? 1 : 2;
550 ArrayRef<int64_t> shape = origResultTy.getShape();
551 if (llvm::any_of(
552 shape.take_front(origResultTy.getRank() - effectiveVecRank),
553 [](int64_t d) { return d != 1; }))
554 return rewriter.notifyMatchFailure(
555 op, "Only unit dimensions allowed for the leading "
556 "dimensions of the load vector!");
557
558 auto distResultTyOrFailure =
559 xegpu::getDistVecTypeBasedOnLaneLayout(layout, origResultTy);
560 if (failed(distResultTyOrFailure))
561 return rewriter.notifyMatchFailure(
562 op, "unable to compute expected lane vector type from lane layout");
563
564 VectorType distResultTy = distResultTyOrFailure.value();
565 VectorType distResultTy1D = VectorType::get({distResultTy.getNumElements()},
566 distResultTy.getElementType());
567
568 // Flatten offsets and mask to 1D to match the 1D result type.
569 Value distOffsets = adaptor.getOffsets();
570 auto distOffsetsTy = cast<VectorType>(distOffsets.getType());
571 VectorType offsetsTy1D = VectorType::get({distOffsetsTy.getNumElements()},
572 distOffsetsTy.getElementType());
573 distOffsets = castValueTo(
574 rewriter, cast<TypedValue<VectorType>>(distOffsets), offsetsTy1D);
575
576 Value distMask = adaptor.getMask();
577 auto distMaskTy = cast<VectorType>(distMask.getType());
578 VectorType maskTy1D = VectorType::get({distMaskTy.getNumElements()},
579 distMaskTy.getElementType());
580 distMask =
581 castValueTo(rewriter, cast<TypedValue<VectorType>>(distMask), maskTy1D);
582
583 Value distSource = adaptor.getSource();
584 auto newOp = xegpu::LoadGatherOp::create(
585 rewriter, op.getLoc(), distResultTy1D, distSource, distOffsets,
586 distMask, op.getChunkSizeAttr(), op.getL1HintAttr(), op.getL2HintAttr(),
587 op.getL3HintAttr(), /*layout=*/nullptr, /*contiguity=*/nullptr);
588
589 Value result = newOp->getResult(0);
590 if (distResultTy1D != distResultTy)
591 result = castValueTo(rewriter, cast<TypedValue<VectorType>>(result),
592 distResultTy);
593 rewriter.replaceOp(op, result);
594 return success();
595 }
596};
597
598/// This pattern distributes a subgroup-level vector.reduction op to
599/// lane-level. This require shuffling the data across the lanes (using
600/// gpu::ShuffleOp) and reducing in stages until all lanes have the final
601/// result.
602struct SgToLaneVectorReduction
603 : public OpConversionPattern<vector::ReductionOp> {
604 using OpConversionPattern<vector::ReductionOp>::OpConversionPattern;
605
606 LogicalResult
607 matchAndRewrite(vector::ReductionOp op, OpAdaptor adaptor,
608 ConversionPatternRewriter &rewriter) const override {
609 auto layout = xegpu::getDistributeLayoutAttr(op.getVector());
610
611 // If no layout, nothing to do.
612 if (!layout || !layout.isForSubgroup())
613 return failure();
614
615 VectorType srcVecType = op.getSourceVectorType();
616 // Only rank 1 vectors supported.
617 if (srcVecType.getRank() != 1)
618 return rewriter.notifyMatchFailure(
619 op, "Only rank 1 reductions can be distributed.");
620 // Lane layout must have the same rank as the vector.
621 if (layout.getRank() != srcVecType.getRank())
622 return rewriter.notifyMatchFailure(
623 op, "Layout rank does not match vector rank.");
624
625 // Get the subgroup size from the layout.
626 int64_t sgSize = layout.getEffectiveLaneLayoutAsInt()[0];
627 const auto *uArch =
629 if (!uArch)
630 return rewriter.notifyMatchFailure(
631 op, "xegpu::ReductionOp require target attribute attached to "
632 "determine subgroup size");
633
634 // Only subgroup-sized vectors supported.
635 if (sgSize != uArch->getSubgroupSize() ||
636 srcVecType.getShape()[0] % sgSize != 0)
637 return rewriter.notifyMatchFailure(op,
638 "Invalid layout or reduction vector "
639 "dimension must match subgroup size.");
640
641 if (!op.getType().isIntOrFloat())
642 return rewriter.notifyMatchFailure(
643 op, "Reduction distribution currently only supports floats and "
644 "integer types.");
645
646 // Get the distributed vector (per lane portion).
647 Value laneValVec = adaptor.getVector();
648
649 // Distribute and reduce across lanes in the subgroup.
650 Value fullReduce = xegpu::subgroupReduction(
651 op.getLoc(), rewriter, laneValVec, op.getKind(), sgSize);
652
653 // If there's an accumulator, combine it with the reduced value.
654 if (adaptor.getAcc())
655 fullReduce = vector::makeArithReduction(
656 rewriter, op.getLoc(), op.getKind(), fullReduce, adaptor.getAcc());
657
658 rewriter.replaceOp(op, fullReduce);
659 return success();
660 }
661};
662
663/// This pattern distributes a subgroup-level vector.multi_reduction op to
664/// lane-level only if the reduction is lane-local. This means that
665/// reduction dimension is not distributed to lanes and each lane does its own
666/// local reduction.
667struct SgToLaneMultiDimReduction
668 : public OpConversionPattern<vector::MultiDimReductionOp> {
669 using OpConversionPattern<vector::MultiDimReductionOp>::OpConversionPattern;
670
671 LogicalResult
672 matchAndRewrite(vector::MultiDimReductionOp op, OpAdaptor adaptor,
673 ConversionPatternRewriter &rewriter) const override {
675 ArrayRef<int64_t> reductionDims = op.getReductionDims();
676 assert(reductionDims.size() == 1 &&
677 "Expecting single reduction dimension for subgroup multi "
678 "reduction op");
679 // For rank > 2, ensure leading dimensions are unit.
680 VectorType sourceType = op.getSourceVectorType();
681 int64_t rank = sourceType.getRank();
682 if (rank > 2) {
683 ArrayRef<int64_t> shape = sourceType.getShape();
684 if (llvm::any_of(shape.take_front(rank - 2),
685 [](int64_t d) { return d != 1; }))
686 return rewriter.notifyMatchFailure(
687 op, "only unit leading dimensions are supported for "
688 "multi_reduction with rank > 2");
689 }
690 // Handle scalar result: full reduction of a distributed vector to a
691 // scalar. First do a local vector reduction, then cross-lane shuffles.
692 if (op.getType().isIntOrFloat()) {
693 auto reductionDim = reductionDims[0];
694 VectorType origSourceType = op.getSourceVectorType();
695 int64_t reductionDimSize = origSourceType.getShape()[reductionDim];
696 // Local reduction to scalar, then cross-lane butterfly shuffles.
697 result =
698 xegpu::subgroupReduction(op.getLoc(), rewriter, adaptor.getSource(),
699 op.getKind(), reductionDimSize);
700 // Combine with accumulator if present.
701 if (adaptor.getAcc())
702 result = vector::makeArithReduction(rewriter, op.getLoc(), op.getKind(),
703 result, adaptor.getAcc());
704 } else if (isReductionLaneLocal(op)) {
705 // For lane-local reduction, lower to a sequence of vector.reduction ops
706 // over 1D slices extracted from the distributed source vector. This is
707 // required so we dont have 2D source vectors at xegpu-linearize.
708 auto reductionDim = reductionDims[0];
710 cast<TypedValue<VectorType>>(adaptor.getSource()),
711 cast<TypedValue<VectorType>>(adaptor.getAcc()), op.getKind(),
712 reductionDim, op.getLoc(), rewriter);
713 } else {
714 auto reductionDim = reductionDims[0];
715 VectorType sourceType = op.getSourceVectorType();
716 int64_t reductionDimSize = sourceType.getShape()[reductionDim];
718 cast<TypedValue<VectorType>>(adaptor.getSource()),
719 cast<TypedValue<VectorType>>(adaptor.getAcc()), op.getKind(),
720 reductionDim, reductionDimSize, op.getLoc(), rewriter);
721 }
722 rewriter.replaceOp(op, result);
723 return success();
724 }
725};
726
727/// Helper to compute distributed coordinates for matrix ops.
728/// When not using subgroup_block_io, each lane computes its own
729/// coordinates based on the layout and lane ID.
730static SmallVector<Value> computeDistributedCoordsForMatrixOp(
731 ConversionPatternRewriter &rewriter, Location loc,
732 xegpu::DistributeLayoutAttr layout, ArrayRef<int64_t> payloadShape,
733 ValueRange origOffsets) {
734 Value laneId = gpu::LaneIdOp::create(rewriter, loc, rewriter.getIndexType(),
735 /*upperBound=*/mlir::IntegerAttr());
736 auto maybeCoords =
737 layout.computeDistributedCoords(rewriter, loc, laneId, payloadShape);
738 if (failed(maybeCoords))
739 return {};
740 assert(maybeCoords.value().size() == 1 &&
741 "Expected one set of distributed offsets");
743 rewriter, loc, getAsOpFoldResult(maybeCoords.value()[0]),
744 getAsOpFoldResult(origOffsets));
745 return llvm::map_to_vector(ofrVec, llvm::CastTo<Value>);
746}
747
748/// This pattern distributes a subgroup-level LoadMatrix op to lane-level.
749struct SgToLaneLoadMatrix : public OpConversionPattern<xegpu::LoadMatrixOp> {
750 using OpConversionPattern<xegpu::LoadMatrixOp>::OpConversionPattern;
751
752 LogicalResult
753 matchAndRewrite(xegpu::LoadMatrixOp op, OpAdaptor adaptor,
754 ConversionPatternRewriter &rewriter) const override {
755 auto layout = op.getLayoutAttr();
756 // If no layout, nothing to do.
757 if (!layout)
758 return failure();
759
760 VectorType sgPayloadTy = dyn_cast<VectorType>(op.getResult().getType());
761 if (!sgPayloadTy)
762 return rewriter.notifyMatchFailure(
763 op, "the matrix op payload must be a vector type");
764
765 auto loc = op.getLoc();
766 auto offsets = op.getMixedOffsets();
767 if (offsets.empty())
768 return rewriter.notifyMatchFailure(op, "the load op must have offsets");
769
770 FailureOr<VectorType> distPayloadTyOrFailure =
771 getDistVecTypeBasedOnLaneLayout(layout, sgPayloadTy);
772 if (failed(distPayloadTyOrFailure))
773 return rewriter.notifyMatchFailure(
774 op, "Failed to distribute matrix op payload based on layout.");
775
776 SmallVector<Value> offsetsAsValues =
777 vector::getAsValues(rewriter, loc, offsets);
778
779 SmallVector<Value> newCoords = offsetsAsValues;
780 if (!op.getSubgroupBlockIoAttr()) {
781 newCoords = computeDistributedCoordsForMatrixOp(
782 rewriter, loc, layout, sgPayloadTy.getShape(), offsetsAsValues);
783 if (newCoords.empty())
784 return rewriter.notifyMatchFailure(
785 op, "Failed to compute distributed coordinates.");
786 }
787
788 SmallVector<int64_t> newConstOffsets(op.getConstOffsets().size(),
789 ShapedType::kDynamic);
790 DenseI64ArrayAttr newConstOffsetsAttr =
791 rewriter.getDenseI64ArrayAttr(newConstOffsets);
792
793 auto newOp = xegpu::LoadMatrixOp::create(
794 rewriter, loc, *distPayloadTyOrFailure, adaptor.getMemDesc(),
795 ValueRange(newCoords), newConstOffsetsAttr, op.getSubgroupBlockIoAttr(),
796 xegpu::DistributeLayoutAttr{});
797 rewriter.replaceOp(op, newOp.getResult());
798 return success();
799 }
800};
801
802/// Distributes a subgroup-level vector.transpose op to lane-level.
803struct SgToLaneVectorTranspose
804 : public OpConversionPattern<vector::TransposeOp> {
805 using OpConversionPattern<vector::TransposeOp>::OpConversionPattern;
806
807 LogicalResult
808 matchAndRewrite(vector::TransposeOp op, OpAdaptor adaptor,
809 ConversionPatternRewriter &rewriter) const override {
810 xegpu::DistributeLayoutAttr sourceLayout =
811 xegpu::getTemporaryLayout(op->getOpOperand(0));
812 xegpu::DistributeLayoutAttr resultLayout =
813 xegpu::getTemporaryLayout(op->getOpResult(0));
814 if (!sourceLayout || !resultLayout)
815 return rewriter.notifyMatchFailure(
816 op, "the source or result vector of the transpose op lacks layout "
817 "attribute");
818 ArrayRef<int64_t> perm = op.getPermutation();
819 // Result layout must be a transpose of source layout.
820 if (!resultLayout.isTransposeOf(sourceLayout, perm,
822 return rewriter.notifyMatchFailure(
823 op, "the source or result vector layouts must be transposes of "
824 "each other");
825 FailureOr<VectorType> distributedResultTypeOrFailure =
826 getDistVecTypeBasedOnLaneLayout(resultLayout, op.getResultVectorType());
827 if (failed(distributedResultTypeOrFailure))
828 return rewriter.notifyMatchFailure(
829 op, "Failed to distribute the result vector type in "
830 "vector::Transpose op");
831 auto newOp = vector::TransposeOp::create(rewriter, op.getLoc(),
832 adaptor.getVector(), perm);
833 rewriter.replaceOp(op, castValueTo(rewriter, newOp.getResult(),
834 distributedResultTypeOrFailure.value()));
835 return success();
836 }
837};
838
839/// Distributes a subgroup-level vector.bitcast op to lane-level.
840/// Bitcast only impacts the innermost dimension of the source/result vectors.
841struct SgToLaneVectorBitcast : public OpConversionPattern<vector::BitCastOp> {
842 using OpConversionPattern<vector::BitCastOp>::OpConversionPattern;
843
844 LogicalResult
845 matchAndRewrite(vector::BitCastOp op, OpAdaptor adaptor,
846 ConversionPatternRewriter &rewriter) const override {
847 xegpu::DistributeLayoutAttr resultLayout =
848 xegpu::getTemporaryLayout(op->getOpResult(0));
849 if (!resultLayout)
850 return rewriter.notifyMatchFailure(
851 op, "result vector of the bitcast op lacks layout attribute");
852 FailureOr<VectorType> distributedResultTypeOrFailure =
853 getDistVecTypeBasedOnLaneLayout(resultLayout, op.getResultVectorType());
854 if (failed(distributedResultTypeOrFailure))
855 return rewriter.notifyMatchFailure(
856 op, "Failed to distribute the result vector type in "
857 "vector::BitCast op");
858 auto newOp = vector::BitCastOp::create(
859 rewriter, op.getLoc(), distributedResultTypeOrFailure.value(),
860 adaptor.getSource());
861 rewriter.replaceOp(op, newOp.getResult());
862 return success();
863 }
864};
865
866/// Distributes a subgroup-level vector.create_mask or vector.constant_mask op
867/// to lane-level. Uses `computeDistributedCoords()` to obtain the
868/// coordinates each lane owns, then compares each coordinate against the
869/// original mask bounds using `arith.cmpi slt`. The per-element boolean
870/// results are assembled into the distributed mask vector.
871///
872/// For multi-dimensional masks, the element is in-bounds when ALL dimensions
873/// satisfy `coord[i] < bound[i]`.
874///
875/// Example (1D):
876/// layout = #xegpu.layout<lane_layout = [16], lane_data = [1]>
877/// %mask = vector.create_mask %m0 : vector<16xi1>
878/// For lane k, computeDistributedCoords gives coord = [k], so:
879/// %in_bounds = arith.cmpi slt, %coord, %m0 → i1
880/// %mask = vector.broadcast %in_bounds : i1 to vector<1xi1>
881///
882/// Example (2D):
883/// layout = #xegpu.layout<lane_layout = [8, 2], lane_data = [1, 1]>
884/// %mask = vector.create_mask %m0, %m1 : vector<8x4xi1>
885/// Each WI owns a 1x2 slice. computeDistributedCoords returns 2 coords:
886/// [[r0, c0], [r0, c1]]
887/// For each coord: in_bounds = (r < m0) && (c < m1)
888/// %mask = vector.from_elements %bit0, %bit1 : vector<1x2xi1>
889template <typename OpType,
890 typename = std::enable_if_t<llvm::is_one_of<
891 OpType, vector::CreateMaskOp, vector::ConstantMaskOp>::value>>
892struct SgToLaneCreateMask : public OpConversionPattern<OpType> {
893 using OpConversionPattern<OpType>::OpConversionPattern;
894
895 LogicalResult
896 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
897 ConversionPatternRewriter &rewriter) const override {
898 xegpu::DistributeLayoutAttr layout =
899 xegpu::getTemporaryLayout(op->getOpResult(0));
900 if (!layout || !layout.isForSubgroup())
901 return rewriter.notifyMatchFailure(
902 op, "operation result does not have subgroup distribute layout");
903
904 VectorType origType = op.getType();
905 FailureOr<VectorType> distTypeOrFailure =
906 getDistVecTypeBasedOnLaneLayout(layout, origType);
907 if (failed(distTypeOrFailure))
908 return rewriter.notifyMatchFailure(
909 op, "unable to compute lane vector type from the layout");
910
911 VectorType distType = distTypeOrFailure.value();
912 Location loc = op.getLoc();
913
914 // Materialize the original mask bounds as Values.
915 SmallVector<Value> origBounds;
916 if constexpr (std::is_same_v<OpType, vector::CreateMaskOp>) {
917 origBounds.append(op.getOperands().begin(), op.getOperands().end());
918 } else {
919 auto dimSizes = op.getMaskDimSizesAttr().asArrayRef();
920 for (auto dimSize : dimSizes)
921 origBounds.push_back(
922 arith::ConstantIndexOp::create(rewriter, loc, dimSize).getResult());
923 }
924
925 ArrayRef<int64_t> origShape = origType.getShape();
926
927 // Use computeDistributedCoords to get the coordinates each WI owns.
928 Value laneId = gpu::LaneIdOp::create(rewriter, loc, rewriter.getIndexType(),
929 /*upperBound=*/mlir::IntegerAttr());
930 auto maybeCoordsVec =
931 layout.computeDistributedCoords(rewriter, loc, laneId, origShape);
932 if (failed(maybeCoordsVec))
933 return rewriter.notifyMatchFailure(
934 op, "failed to compute distributed coordinates from layout");
935
936 SmallVector<SmallVector<Value>> coordsVec = maybeCoordsVec.value();
937 int64_t numElements = distType.getNumElements();
938 assert(static_cast<int64_t>(coordsVec.size()) == numElements &&
939 "number of coordinate sets must match number of distributed "
940 "elements");
941
942 // For each element, compare all coordinates against bounds.
943 Value trueVal =
944 arith::ConstantIntOp::create(rewriter, loc, /*value=*/1, /*width=*/1);
945 SmallVector<Value> maskBits;
946 for (auto &coords : coordsVec) {
947 Value inBounds = trueVal;
948 for (size_t i = 0; i < coords.size(); ++i) {
949 Value cmp = arith::CmpIOp::create(
950 rewriter, loc, arith::CmpIPredicate::slt, coords[i], origBounds[i]);
951 inBounds = arith::AndIOp::create(rewriter, loc, inBounds, cmp);
952 }
953 maskBits.push_back(inBounds);
954 }
955
956 // Build the distributed mask vector.
958 if (numElements == 1) {
959 result =
960 vector::BroadcastOp::create(rewriter, loc, distType, maskBits[0]);
961 } else {
962 result =
963 vector::FromElementsOp::create(rewriter, loc, distType, maskBits);
964 }
965 rewriter.replaceOp(op, result);
966 return success();
967 }
968};
969
970/// This pattern distributes a subgroup-level StoreMatrix op to lane-level.
971struct SgToLaneStoreMatrix : public OpConversionPattern<xegpu::StoreMatrixOp> {
972 using OpConversionPattern<xegpu::StoreMatrixOp>::OpConversionPattern;
973
974 LogicalResult
975 matchAndRewrite(xegpu::StoreMatrixOp op, OpAdaptor adaptor,
976 ConversionPatternRewriter &rewriter) const override {
977 auto layout = op.getLayoutAttr();
978 // If no layout, nothing to do.
979 if (!layout)
980 return failure();
981
982 VectorType sgPayloadTy = dyn_cast<VectorType>(op.getData().getType());
983 if (!sgPayloadTy)
984 return rewriter.notifyMatchFailure(
985 op, "the matrix op payload must be a vector type");
986
987 auto loc = op.getLoc();
988 auto offsets = op.getMixedOffsets();
989 if (offsets.empty())
990 return rewriter.notifyMatchFailure(op, "the store op must have offsets");
991
992 FailureOr<VectorType> distPayloadTyOrFailure =
993 getDistVecTypeBasedOnLaneLayout(layout, sgPayloadTy);
994 if (failed(distPayloadTyOrFailure))
995 return rewriter.notifyMatchFailure(
996 op, "Failed to distribute matrix op payload based on layout.");
997
998 SmallVector<Value> offsetsAsValues =
999 vector::getAsValues(rewriter, loc, offsets);
1000
1001 SmallVector<Value> newCoords = offsetsAsValues;
1002 if (!op.getSubgroupBlockIoAttr()) {
1003 newCoords = computeDistributedCoordsForMatrixOp(
1004 rewriter, loc, layout, sgPayloadTy.getShape(), offsetsAsValues);
1005 if (newCoords.empty())
1006 return rewriter.notifyMatchFailure(
1007 op, "Failed to compute distributed coordinates.");
1008 }
1009
1010 SmallVector<int64_t> newConstOffsets(op.getConstOffsets().size(),
1011 ShapedType::kDynamic);
1012 DenseI64ArrayAttr newConstOffsetsAttr =
1013 rewriter.getDenseI64ArrayAttr(newConstOffsets);
1014
1015 xegpu::StoreMatrixOp::create(
1016 rewriter, loc, TypeRange{},
1017 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getData()),
1018 distPayloadTyOrFailure.value()),
1019 adaptor.getMemDesc(), ValueRange(newCoords), newConstOffsetsAttr,
1020 op.getSubgroupBlockIoAttr(), xegpu::DistributeLayoutAttr{});
1021 rewriter.eraseOp(op);
1022 return success();
1023 }
1024};
1025
1026/// Distributes a subgroup-level StoreScatter (xegpu.store) op to
1027/// lane-level.
1028///
1029/// Example 1 (1D, no chunk size):
1030/// layout = #xegpu.layout<lane_layout = [16], lane_data = [1]>
1031/// %mask = producer_op : vector<16xi1>
1032/// %offset = producer_op : vector<16xindex>
1033/// xegpu.store %payload, %src[%offset], %mask : vector<16xf16>,
1034/// memref<256xf16>, vector<16xindex>, vector<16xi1>
1035/// Distributed to:
1036/// %mask = producer_op : vector<1xi1>
1037/// %offset = producer_op : vector<1xindex>
1038/// xegpu.store %payload, %src[%offset], %mask : vector<1xf16>,
1039/// memref<256xf16>, vector<1xindex>, vector<1xi1>
1040///
1041/// Example 2 (2D with chunk size, same mask & offset):
1042/// layout = #xegpu.layout<lane_layout = [16, 1], lane_data = [1, 1]>
1043/// xegpu.store %payload, %src[%offset], %mask <{chunk_size=8}> :
1044/// vector<16x8xf16>, memref<256xf16>, vector<16xindex>, vector<16xi1>
1045/// Distributed to:
1046/// xegpu.store %payload, %src[%offset], %mask <{chunk_size=8}> :
1047/// vector<8xf16>, memref<256xf16>, vector<1xindex>, vector<1xi1>
1048///
1049/// Example 3 (3D with leading unit dims):
1050/// layout = #xegpu.layout<lane_layout = [1, 1, 16], lane_data = [1, 1, 1]>
1051/// %mask = producer_op : vector<1x1x16xi1>
1052/// %offset = producer_op : vector<1x1x16xindex>
1053/// xegpu.store %payload, %src[%offset], %mask : vector<1x1x16xf16>,
1054/// memref<256xf16>, vector<1x1x16xindex>, vector<1x1x16xi1>
1055/// Distributed to:
1056/// %mask = producer_op : vector<1x1x1xi1>
1057/// %offset = producer_op : vector<1x1x1xindex>
1058/// xegpu.store %payload, %src[%offset], %mask : vector<1xf16>,
1059/// memref<256xf16>, vector<1xindex>, vector<1xi1>
1060struct SgToLaneStoreScatter
1061 : public OpConversionPattern<xegpu::StoreScatterOp> {
1062 using OpConversionPattern<xegpu::StoreScatterOp>::OpConversionPattern;
1063
1064 LogicalResult
1065 matchAndRewrite(xegpu::StoreScatterOp op, OpAdaptor adaptor,
1066 ConversionPatternRewriter &rewriter) const override {
1067 xegpu::DistributeLayoutAttr layout = op.getAnchorLayout();
1068 if (!layout)
1069 return failure();
1070
1071 VectorType origValueTy = op.getValueType();
1072 if (!origValueTy)
1073 return failure();
1074
1075 // Check that all leading dimensions are unit dimensions.
1076 int chunkSize = op.getChunkSize().value_or(1);
1077 int effectiveVecRank = (chunkSize == 1) ? 1 : 2;
1078 ArrayRef<int64_t> shape = origValueTy.getShape();
1079 if (llvm::any_of(shape.take_front(origValueTy.getRank() - effectiveVecRank),
1080 [](int64_t d) { return d != 1; }))
1081 return rewriter.notifyMatchFailure(
1082 op, "Only unit dimensions allowed for the leading "
1083 "dimensions of the store vector!");
1084
1085 auto distValueTyOrFailure =
1086 xegpu::getDistVecTypeBasedOnLaneLayout(layout, origValueTy);
1087 if (failed(distValueTyOrFailure))
1088 return rewriter.notifyMatchFailure(
1089 op, "unable to compute expected lane vector type from lane layout");
1090
1091 VectorType distValueTy = distValueTyOrFailure.value();
1092 VectorType distValueTy1D = VectorType::get({distValueTy.getNumElements()},
1093 distValueTy.getElementType());
1094
1095 Value distValue = adaptor.getValue();
1096 if (distValue.getType() != distValueTy1D)
1097 distValue = castValueTo(rewriter, cast<TypedValue<VectorType>>(distValue),
1098 distValueTy1D);
1099
1100 // Flatten offsets and mask to 1D to match the 1D value type.
1101 Value distOffsets = adaptor.getOffsets();
1102 auto distOffsetsTy = cast<VectorType>(distOffsets.getType());
1103 VectorType offsetsTy1D = VectorType::get({distOffsetsTy.getNumElements()},
1104 distOffsetsTy.getElementType());
1105 distOffsets = castValueTo(
1106 rewriter, cast<TypedValue<VectorType>>(distOffsets), offsetsTy1D);
1107
1108 Value distMask = adaptor.getMask();
1109 auto distMaskTy = cast<VectorType>(distMask.getType());
1110 VectorType maskTy1D = VectorType::get({distMaskTy.getNumElements()},
1111 distMaskTy.getElementType());
1112 distMask =
1113 castValueTo(rewriter, cast<TypedValue<VectorType>>(distMask), maskTy1D);
1114
1115 Value distDest = adaptor.getDest();
1116 xegpu::StoreScatterOp::create(rewriter, op.getLoc(), distValue, distDest,
1117 distOffsets, distMask, op.getChunkSizeAttr(),
1118 op.getL1HintAttr(), op.getL2HintAttr(),
1119 op.getL3HintAttr(), /*layout=*/nullptr,
1120 /*contiguity=*/nullptr);
1121 rewriter.eraseOp(op);
1122 return success();
1123 }
1124};
1125
1126/// Distribute a vector::StepOp to lane-level.
1127/// The layout must have exactly 1 effective lane dimension.
1128/// We completely resolve the vector::StepOp by computing the lane_data-sized
1129/// subranges.
1130struct SgToLaneVectorStep : public OpConversionPattern<vector::StepOp> {
1131 using OpConversionPattern<vector::StepOp>::OpConversionPattern;
1132
1133 LogicalResult
1134 matchAndRewrite(vector::StepOp op, OpAdaptor adaptor,
1135 ConversionPatternRewriter &rewriter) const override {
1136 xegpu::DistributeLayoutAttr resultLayout =
1137 xegpu::getTemporaryLayout(op->getResult(0));
1138 if (!resultLayout || !resultLayout.isForSubgroup())
1139 return rewriter.notifyMatchFailure(
1140 op, "the result vector of the step op lacks subgroup layout");
1141
1142 auto loc = op.getLoc();
1143 auto stepResultVecTy = op.getResult().getType();
1144 auto laneShapeOrFailure =
1145 xegpu::getDistVecTypeBasedOnLaneLayout(resultLayout, stepResultVecTy);
1146 if (failed(laneShapeOrFailure))
1147 return rewriter.notifyMatchFailure(
1148 op, "unable to compute lane vector type from the layout");
1149 VectorType newVecTy = laneShapeOrFailure.value();
1150
1151 Value laneId = gpu::LaneIdOp::create(rewriter, loc, rewriter.getIndexType(),
1152 /*upperBound=*/mlir::IntegerAttr());
1153 auto laneDataBlockCoords = resultLayout.computeDistributedCoords(
1154 rewriter, loc, laneId, stepResultVecTy.getShape());
1155 if (failed(laneDataBlockCoords))
1156 return rewriter.notifyMatchFailure(
1157 op, "failed to compute lane data block coordinates");
1158
1159 auto laneDataBlockCoordsVec = laneDataBlockCoords.value();
1160 auto laneDataBlockLength = resultLayout.getEffectiveLaneDataAsInt()[0];
1161 assert(static_cast<int64_t>(laneDataBlockCoordsVec.size()) ==
1162 newVecTy.getNumElements() / laneDataBlockLength);
1163 SmallVector<Value> stepVals;
1164 // For each lane_data block, reconstruct its sub-range
1165 // from the range of SG-level vector.step.Example: vector.step
1166 // {slice<layout<lane_layout=[2,4,2], lane_data=[1,2,1]>, dims=[0,2]>} :
1167 // vector<16xindex>
1168 // Each logical lane holds 4 elements as 2 blocks of 2 elements each.
1169 // The blocks are round-robin distributed, so logical lane id 0
1170 // holds values [0,1, 8,9].
1171 for (auto &laneDataBlockCoords : laneDataBlockCoordsVec) {
1172 auto laneDataBlockStartCoord = laneDataBlockCoords[0];
1173 stepVals.push_back(laneDataBlockStartCoord);
1174 for (int i = 1; i < laneDataBlockLength; ++i) {
1175 auto offset = arith::ConstantIndexOp::create(rewriter, loc, i);
1176 stepVals.push_back(arith::AddIOp::create(
1177 rewriter, loc, laneDataBlockStartCoord, offset));
1178 }
1179 }
1180 assert(static_cast<int64_t>(stepVals.size()) == newVecTy.getNumElements() &&
1181 "Expecting the number of step values to match the number of "
1182 "elements in the vector");
1183 auto stepOpVal =
1184 vector::FromElementsOp::create(rewriter, loc, newVecTy, stepVals);
1185 rewriter.replaceOp(op, stepOpVal);
1186 return success();
1187 }
1188};
1189
1190/// Distributes a subgroup-level vector.extract op to lane-level. Only
1191/// handles sub-vector extraction (result is VectorType, not scalar).
1192struct SgToLaneVectorExtract : public OpConversionPattern<vector::ExtractOp> {
1193 using OpConversionPattern<vector::ExtractOp>::OpConversionPattern;
1194
1195 LogicalResult
1196 matchAndRewrite(vector::ExtractOp op, OpAdaptor adaptor,
1197 ConversionPatternRewriter &rewriter) const override {
1198 // Only handle vector results (not scalar extraction).
1199 auto resultType = dyn_cast<VectorType>(op.getType());
1200 if (!resultType)
1201 return rewriter.notifyMatchFailure(op, "scalar extract not supported");
1202
1203 xegpu::DistributeLayoutAttr layout =
1204 xegpu::getTemporaryLayout(op->getOpResult(0));
1205 if (!layout || !layout.isForSubgroup())
1206 return failure();
1207
1208 // This implementation assumes distribution only happens on the innermost
1209 // dimension. Verify that lane_layout[0...n-2] are all unit.
1210 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
1211 if (llvm::any_of(ArrayRef<int64_t>(laneLayout).drop_back(1),
1212 [](int64_t v) { return v != 1; }))
1213 return rewriter.notifyMatchFailure(
1214 op, "only innermost dimension distribution is supported for "
1215 "vector.extract");
1216
1217 auto newOp = vector::ExtractOp::create(
1218 rewriter, op.getLoc(), adaptor.getSource(), op.getMixedPosition());
1219 rewriter.replaceOp(op, newOp.getResult());
1220 return success();
1221 }
1222};
1223
1224/// This pattern distributes a subgroup-level ShapeCast op to lane-level.
1225struct SgToLaneVectorShapeCast
1226 : public OpConversionPattern<vector::ShapeCastOp> {
1227 using OpConversionPattern<vector::ShapeCastOp>::OpConversionPattern;
1228
1229 LogicalResult
1230 matchAndRewrite(vector::ShapeCastOp op, OpAdaptor adaptor,
1231 ConversionPatternRewriter &rewriter) const override {
1232 xegpu::DistributeLayoutAttr resultLayout =
1233 xegpu::getTemporaryLayout(op->getOpResult(0));
1234 if (!resultLayout || !resultLayout.isForSubgroup())
1235 return rewriter.notifyMatchFailure(
1236 op, "the result vector of the shape_cast op lacks subgroup layout");
1237
1238 auto resultDistTypeOrFailure = xegpu::getDistVecTypeBasedOnLaneLayout(
1239 resultLayout, op.getResultVectorType());
1240 if (failed(resultDistTypeOrFailure))
1241 return rewriter.notifyMatchFailure(
1242 op, "failed to get distributed vector type for result");
1243
1244 Value source = adaptor.getSource();
1245 auto newShapeCast = vector::ShapeCastOp::create(
1246 rewriter, op.getLoc(), resultDistTypeOrFailure.value(), source);
1247 rewriter.replaceOp(op, newShapeCast);
1248 return success();
1249 }
1250};
1251
1252/// Distributes a subgroup-level vector.extract_strided_slice op to
1253/// lane-level. If the result is distributed, the offsets and sizes are
1254/// adjusted to match the distributed types.
1255struct SgToLaneVectorExtractStridedSlice
1256 : public OpConversionPattern<vector::ExtractStridedSliceOp> {
1257 using OpConversionPattern<vector::ExtractStridedSliceOp>::OpConversionPattern;
1258
1259 LogicalResult
1260 matchAndRewrite(vector::ExtractStridedSliceOp op, OpAdaptor adaptor,
1261 ConversionPatternRewriter &rewriter) const override {
1262 xegpu::DistributeLayoutAttr resultLayout =
1263 xegpu::getTemporaryLayout(op->getOpResult(0));
1264 if (!resultLayout || !resultLayout.isForSubgroup())
1265 return failure();
1266
1267 VectorType resultType = op.getType();
1268 auto distResultTyOrFailure =
1269 xegpu::getDistVecTypeBasedOnLaneLayout(resultLayout, resultType);
1270 if (failed(distResultTyOrFailure))
1271 return rewriter.notifyMatchFailure(
1272 op, "unable to compute distributed vector type from lane layout");
1273 VectorType distResultTy = *distResultTyOrFailure;
1274
1275 SmallVector<int64_t> distributedDims =
1276 getDistributedDims(resultType, distResultTy);
1277
1278 // Collect updated sizes, offsets, strides. Pad to full source rank.
1279 int64_t sourceRank = op.getSourceVectorType().getRank();
1280 SmallVector<Attribute> updatedSizes =
1281 llvm::map_to_vector(op.getSizes(), [](Attribute attr) { return attr; });
1282 SmallVector<Attribute> updatedOffsets = llvm::map_to_vector(
1283 op.getOffsets(), [](Attribute attr) { return attr; });
1284 SmallVector<Attribute> updatedStrides = llvm::map_to_vector(
1285 op.getStrides(), [](Attribute attr) { return attr; });
1286 for (int64_t i = op.getSizes().size(); i < sourceRank; ++i) {
1287 updatedSizes.push_back(
1288 rewriter.getI64IntegerAttr(op.getSourceVectorType().getDimSize(i)));
1289 updatedOffsets.push_back(rewriter.getI64IntegerAttr(0));
1290 updatedStrides.push_back(rewriter.getI64IntegerAttr(1));
1291 }
1292
1293 // If the result is distributed, adjust offsets and sizes in the
1294 // distributed dimension.
1295 if (!distributedDims.empty()) {
1296 if (distributedDims.size() != 1)
1297 return rewriter.notifyMatchFailure(
1298 op, "only single dimension distribution is supported");
1299 int64_t distDim = distributedDims[0];
1300 const auto *uArch =
1301 xegpu::uArch::getUArch(xegpu::getChipStr(op).value_or(""));
1302 if (!uArch)
1303 return rewriter.notifyMatchFailure(
1304 op, "target attribute required to determine subgroup size");
1305 int subgroupSize = uArch->getSubgroupSize();
1306 auto sourceLayout = xegpu::getTemporaryLayout(op->getOpOperand(0));
1307 if (!sourceLayout || sourceLayout.getEffectiveLaneLayoutAsInt().empty())
1308 return rewriter.notifyMatchFailure(
1309 op, "source of extract_strided_slice lacks distribution layout");
1310 int sourceDistrDimSize = op.getSourceVectorType().getShape()[distDim];
1311 auto laneLayout = sourceLayout.getEffectiveLaneLayoutAsInt();
1312 // Effective subgroup size needs to be adjusted if laneLayout along
1313 // the distributed dimension is smaller than subgroup size.
1314 if (laneLayout[distDim] < subgroupSize &&
1315 subgroupSize % laneLayout[distDim] == 0)
1316 subgroupSize = laneLayout[distDim];
1317 if (sourceDistrDimSize % subgroupSize != 0)
1318 return rewriter.notifyMatchFailure(
1319 op, "source size along distributed dim is not a multiple of "
1320 "subgroup size");
1321 auto sourceLaneData = sourceLayout.getEffectiveLaneDataAsInt();
1322 // Only check lane_data for the distributed dimension. Non-distributed
1323 // dimensions may have non-unit lane_data (e.g., packed layouts).
1324 if (distDim < static_cast<int64_t>(sourceLaneData.size()) &&
1325 sourceLaneData[distDim] != 1)
1326 return rewriter.notifyMatchFailure(
1327 op, "expecting unit lane data along the distributed dimension");
1328 int64_t distrDimOffset =
1329 cast<IntegerAttr>(updatedOffsets[distDim]).getInt();
1330 if (distrDimOffset % subgroupSize != 0)
1331 return rewriter.notifyMatchFailure(
1332 op, "offset along distributed dim is not a multiple of "
1333 "subgroup size");
1334 // Adjust sizes and offsets for the distributed dimension.
1335 updatedSizes[distDim] =
1336 rewriter.getI64IntegerAttr(distResultTy.getDimSize(distDim));
1337 updatedOffsets[distDim] =
1338 rewriter.getI64IntegerAttr(distrDimOffset / subgroupSize);
1339 }
1340
1341 auto newOp = vector::ExtractStridedSliceOp::create(
1342 rewriter, op.getLoc(), distResultTy, adaptor.getSource(),
1343 ArrayAttr::get(rewriter.getContext(), updatedOffsets),
1344 ArrayAttr::get(rewriter.getContext(), updatedSizes),
1345 ArrayAttr::get(rewriter.getContext(), updatedStrides));
1346 rewriter.replaceOp(op, newOp.getResult());
1347 return success();
1348 }
1349};
1350
1351/// This pattern distributes a subgroup-level `vector.broadcast` op to
1352/// lane-level. The pattern supports three cases:
1353///
1354/// 1) Broadcast a low-rank vector to high-rank vector: The low-rank input
1355/// vector must have a slice layout of the result. If the distributed source
1356/// and target vector types are identical, this lowers to a no-op; otherwise,
1357/// it remains a broadcast but operates on distributed vectors.
1358///
1359/// 2) Broadcast a same-rank vector with identical layouts for source and
1360/// target: The source vector must have unit dimensions, and lane_data must
1361/// be unit size for those unit dims. This always lowers to a no-op.
1362///
1363/// 3) Broadcast a scalar with no layout: This always lowers to a broadcast
1364/// from scalar to distributed result type.
1365///
1366/// Example 1 (low-rank to high-rank broadcast):
1367/// ```
1368/// %0 = "some_op"() {layout_result_0 =
1369/// #xegpu.slice<#xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>,
1370/// dims = [0]>} : () -> vector<16xf16>
1371/// %1 = vector.broadcast %0 {layout_result_0 =
1372/// #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
1373/// : vector<16xf16> to vector<16x16xf16>
1374/// ```
1375/// is distributed to:
1376/// ```
1377/// %0 = "some_op"() : () -> vector<1xf16>
1378/// %1 = vector.broadcast %0 : vector<1xf16> to vector<16x1xf16>
1379/// ```
1380///
1381/// Example 2 (same-rank broadcast, no-op):
1382/// ```
1383/// %0 = "some_op"() {layout_result_0 =
1384/// #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
1385/// : () -> vector<16x1xf16>
1386/// %1 = vector.broadcast %0 {layout_result_0 =
1387/// #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
1388/// : vector<16x1xf16> to vector<16x16xf16>
1389/// ```
1390/// is distributed to (no-op, source already matches distributed result type):
1391/// ```
1392/// %0 = "some_op"() : () -> vector<16x1xf16>
1393/// // broadcast is eliminated, %0 is used directly
1394/// ```
1395///
1396/// Example 3 (scalar to vector broadcast):
1397/// ```
1398/// %0 = "some_op"() : () -> f16
1399/// %1 = vector.broadcast %0 {layout_result_0 =
1400/// #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
1401/// : f16 to vector<16x16xf16>
1402/// ```
1403/// is distributed to:
1404/// ```
1405/// %0 = "some_op"() : f16
1406/// %1 = vector.broadcast %0 : f16 to vector<16x1xf16>
1407/// ```
1408struct SgToLaneBroadcast : public OpConversionPattern<vector::BroadcastOp> {
1409 using OpConversionPattern<vector::BroadcastOp>::OpConversionPattern;
1410
1411 LogicalResult
1412 matchAndRewrite(vector::BroadcastOp op, OpAdaptor adaptor,
1413 ConversionPatternRewriter &rewriter) const override {
1414 xegpu::DistributeLayoutAttr resultLayout =
1415 xegpu::getTemporaryLayout(cast<OpResult>(op.getResult()));
1416 if (!resultLayout || !resultLayout.isForSubgroup())
1417 return rewriter.notifyMatchFailure(
1418 op, "result does not have subgroup distribute layout");
1419
1420 VectorType destType = op.getResultVectorType();
1421 VectorType sourceType = dyn_cast<VectorType>(op.getSourceType());
1422
1423 xegpu::DistributeLayoutAttr sourceLayout =
1424 xegpu::getTemporaryLayout(op->getOpOperand(0));
1425
1426 if (sourceType) {
1427 int64_t rankDiff = destType.getRank() - sourceType.getRank();
1428 if (rankDiff > 0) {
1429 // Case 1: Low-rank to high-rank broadcast.
1430 if (!sourceLayout || !sourceLayout.isSliceOf(resultLayout))
1431 op.emitWarning(
1432 "broadcast source layout must be a slice of result layout");
1433 } else if (rankDiff == 0) {
1434 // Case 2: Same-rank broadcast.
1435 auto broadcastUnitDimsSet = op.computeBroadcastedUnitDims();
1436 SmallVector<int64_t> broadcastUnitDims(broadcastUnitDimsSet.begin(),
1437 broadcastUnitDimsSet.end());
1438 assert(sourceLayout.isEqualTo(
1439 sourceLayout.setUnitDimData(broadcastUnitDims)) &&
1440 "The sg_data for unit dimensions should be set as 1");
1441 sourceLayout = sourceLayout.setUnitDimLayout(broadcastUnitDims);
1442 }
1443 } else {
1444 // Case 3: Scalar to vector broadcast.
1445 if (sourceLayout)
1446 return rewriter.notifyMatchFailure(
1447 op, "broadcast from scalar must not have a layout attribute");
1448 }
1449
1450 auto destDistType =
1451 xegpu::getDistVecTypeBasedOnLaneLayout(resultLayout, destType);
1452 if (failed(destDistType))
1453 return rewriter.notifyMatchFailure(
1454 op, "failed to distribute the result vector type");
1455
1456 Value source = adaptor.getSource();
1457 // If the adapted source already matches the dest dist type, it's a no-op.
1458 if (source.getType() == destDistType.value()) {
1459 rewriter.replaceOp(op, source);
1460 return success();
1461 }
1462
1463 auto newOp = vector::BroadcastOp::create(rewriter, op.getLoc(),
1464 destDistType.value(), source);
1465 rewriter.replaceOp(op, newOp);
1466 return success();
1467 }
1468};
1469
1470/// Distributes a subgroup-level vector.insert_strided_slice op to
1471/// lane-level. If the dest is distributed, the offsets are adjusted to
1472/// match the distributed types.
1473struct SgToLaneVectorInsertStridedSlice
1474 : public OpConversionPattern<vector::InsertStridedSliceOp> {
1475 using OpConversionPattern<vector::InsertStridedSliceOp>::OpConversionPattern;
1476
1477 LogicalResult
1478 matchAndRewrite(vector::InsertStridedSliceOp op, OpAdaptor adaptor,
1479 ConversionPatternRewriter &rewriter) const override {
1480 xegpu::DistributeLayoutAttr resultLayout =
1481 xegpu::getTemporaryLayout(op->getOpResult(0));
1482 if (!resultLayout || !resultLayout.isForSubgroup())
1483 return failure();
1484
1485 VectorType destType = op.getDestVectorType();
1486 auto distDestTyOrFailure =
1487 xegpu::getDistVecTypeBasedOnLaneLayout(resultLayout, destType);
1488 if (failed(distDestTyOrFailure))
1489 return rewriter.notifyMatchFailure(
1490 op, "unable to compute distributed vector type from lane layout");
1491 VectorType distDestTy = *distDestTyOrFailure;
1492
1493 SmallVector<int64_t> destDistributedDims =
1494 getDistributedDims(destType, distDestTy);
1495
1496 SmallVector<Attribute> updatedOffsets = llvm::map_to_vector(
1497 op.getOffsets(), [](Attribute attr) { return attr; });
1498
1499 if (!destDistributedDims.empty()) {
1500 if (destDistributedDims.size() != 1)
1501 return rewriter.notifyMatchFailure(
1502 op, "only single dimension distribution is supported");
1503 int64_t destDistDim = destDistributedDims[0];
1504
1505 const auto *uArch =
1506 xegpu::uArch::getUArch(xegpu::getChipStr(op).value_or(""));
1507 if (!uArch)
1508 return rewriter.notifyMatchFailure(
1509 op, "target attribute required to determine subgroup size");
1510 int subgroupSize = uArch->getSubgroupSize();
1511
1512 VectorType srcType = op.getSourceVectorType();
1513 // The distributed dim must be in the last k (source rank) dims of dest.
1514 int64_t sourceDistDim =
1515 destDistDim - (destType.getRank() - srcType.getRank());
1516 if (sourceDistDim < 0)
1517 return rewriter.notifyMatchFailure(
1518 op, "distributed dimension must be in the last k dims of dest");
1519
1520 auto destLayout = xegpu::getTemporaryLayout(op->getOpOperand(1));
1521 auto sourceLayout = xegpu::getTemporaryLayout(op->getOpOperand(0));
1522 if (!destLayout || !sourceLayout ||
1523 destLayout.getEffectiveLaneLayoutAsInt().empty() ||
1524 sourceLayout.getEffectiveLaneLayoutAsInt().empty())
1525 return rewriter.notifyMatchFailure(
1526 op, "source or dest of insert_strided_slice lacks distribution "
1527 "layout");
1528
1529 auto destLaneData = destLayout.getEffectiveLaneDataAsInt();
1530 auto sourceLaneData = sourceLayout.getEffectiveLaneDataAsInt();
1531 // Only check lane_data for the distributed dimension. Non-distributed
1532 // dimensions may have non-unit lane_data (e.g., packed layouts).
1533 if ((destDistDim < static_cast<int64_t>(destLaneData.size()) &&
1534 destLaneData[destDistDim] != 1) ||
1535 (sourceDistDim < static_cast<int64_t>(sourceLaneData.size()) &&
1536 sourceLaneData[sourceDistDim] != 1))
1537 return rewriter.notifyMatchFailure(
1538 op, "expecting unit lane data along the distributed dimension");
1539
1540 int64_t srcDistrDimSize = srcType.getDimSize(sourceDistDim);
1541 if (srcDistrDimSize % subgroupSize != 0)
1542 return rewriter.notifyMatchFailure(
1543 op, "source distributed dim size is not a multiple of "
1544 "subgroup size");
1545
1546 int64_t destDistrDimOffset =
1547 cast<IntegerAttr>(op.getOffsets()[destDistDim]).getInt();
1548 if (destDistrDimOffset % subgroupSize != 0)
1549 return rewriter.notifyMatchFailure(
1550 op, "offset along distributed dim is not a multiple of "
1551 "subgroup size");
1552 // Adjust offset for the distributed dimension.
1553 updatedOffsets[destDistDim] =
1554 rewriter.getI64IntegerAttr(destDistrDimOffset / subgroupSize);
1555 }
1556
1557 auto newOp = vector::InsertStridedSliceOp::create(
1558 rewriter, op.getLoc(), distDestTy, adaptor.getValueToStore(),
1559 adaptor.getDest(),
1560 ArrayAttr::get(rewriter.getContext(), updatedOffsets), op.getStrides());
1561 rewriter.replaceOp(op, newOp.getResult());
1562 return success();
1563 }
1564};
1565
1566/// Distributes a subgroup-level vector.insert op to lane-level. Only
1567/// handles sub-vector insertion (value to store is VectorType, not scalar).
1568struct SgToLaneVectorInsert : public OpConversionPattern<vector::InsertOp> {
1569 using OpConversionPattern<vector::InsertOp>::OpConversionPattern;
1570
1571 LogicalResult
1572 matchAndRewrite(vector::InsertOp op, OpAdaptor adaptor,
1573 ConversionPatternRewriter &rewriter) const override {
1574 // Only handle vector value-to-store (not scalar insertion).
1575 auto valueType = dyn_cast<VectorType>(op.getValueToStoreType());
1576 if (!valueType)
1577 return rewriter.notifyMatchFailure(op, "scalar insert not supported");
1578
1579 xegpu::DistributeLayoutAttr layout =
1580 xegpu::getTemporaryLayout(op->getOpResult(0));
1581 if (!layout || !layout.isForSubgroup())
1582 return failure();
1583
1584 // verify that the outer k dimensions (for offsets)
1585 // don't have non-unit lane_layout.
1586 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
1587 if (llvm::any_of(ArrayRef<int64_t>(laneLayout).drop_back(1),
1588 [](int64_t v) { return v != 1; }))
1589 return rewriter.notifyMatchFailure(
1590 op, "only innermost dimension distribution is supported for "
1591 "vector.insert");
1592
1593 auto newOp = vector::InsertOp::create(
1594 rewriter, op.getLoc(), adaptor.getValueToStore(), adaptor.getDest(),
1595 op.getMixedPosition());
1596 rewriter.replaceOp(op, newOp.getResult());
1597 return success();
1598 }
1599};
1600
1601/// Redistributes `src` for a `convert_layout` that changes only the
1602/// `lane_layout` along the outer (distributed) dimension, shrinking it from
1603/// `currentLaneNum` to `targetLaneNum` lanes (a partial-subgroup
1604/// distribution). Because the data is no longer replicated across all lanes,
1605/// each surviving lane must gather the values that previously lived in the
1606/// lanes that are dropped. The values are gathered with `gpu.shuffle` and
1607/// concatenated with the lane-local data using `vector.shuffle`, which doubles
1608/// the distributed outer dimension when the lane count is halved.
1609///
1610/// Only halving the lane count (a factor of two) is currently supported.
1611/// Returns the redistributed value on success, or failure if `src` cannot be
1612/// shuffled (e.g. it is not a rank-2 vector or its bit width is not a multiple
1613/// of 32).
1614static FailureOr<Value>
1615shuffleDataAsLaneLayoutChange(ConversionPatternRewriter &rewriter, Location loc,
1616 Value src, int64_t currentLaneNum,
1617 int64_t targetLaneNum) {
1618 VectorType srcTy = dyn_cast<VectorType>(src.getType());
1619 if (!srcTy || srcTy.getRank() != 2)
1620 return failure();
1621 // Only halving the lane count (factor of two) is supported for now.
1622 if (targetLaneNum <= 0 || currentLaneNum != targetLaneNum * 2)
1623 return failure();
1624 // gpu.shuffle operates on i32, so the data must be a multiple of 32 bits.
1625 int64_t vectorBitWidth =
1626 srcTy.getNumElements() * srcTy.getElementTypeBitWidth();
1627 if (vectorBitWidth % 32 != 0)
1628 return failure();
1629
1630 // A vector cannot be shuffled across lanes directly:
1631 // -- cast the source to a 1D vector of i32
1632 // -- create a temp 1D vector of i32 initialized to zero
1633 // -- for each i32 element:
1634 // ---- extract it from the source bundle
1635 // ---- gpu.shuffle to gather the value from the partner lane
1636 // ---- insert it into the temp bundle
1637 // -- cast the temp back to the source vector type
1638 // -- vector.shuffle the source and temp to concatenate along the outer dim
1639 Type shuffleElemTy = rewriter.getI32Type();
1640 int64_t numShuffles = vectorBitWidth / 32;
1641 VectorType shuffleBundleTy = VectorType::get({numShuffles}, shuffleElemTy);
1642 // Initialize temp to zero.
1643 Value temp = arith::ConstantOp::create(
1644 rewriter, loc,
1645 DenseElementsAttr::get(shuffleBundleTy,
1646 IntegerAttr::get(shuffleElemTy, 0)));
1647 VectorType flatSrcTy =
1648 VectorType::get({srcTy.getNumElements()}, srcTy.getElementType());
1649 Value flatSrc = vector::ShapeCastOp::create(rewriter, loc, flatSrcTy, src);
1650 Value shuffleBundle =
1651 vector::BitCastOp::create(rewriter, loc, shuffleBundleTy, flatSrc);
1652 for (int64_t i = 0; i < numShuffles; i++) {
1653 Value shuffleElem =
1654 vector::ExtractOp::create(rewriter, loc, shuffleBundle, i);
1655 shuffleElem = gpu::ShuffleOp::create(rewriter, loc, shuffleElem, 0,
1656 targetLaneNum, gpu::ShuffleMode::UP)
1657 .getResult(0);
1658 temp = vector::InsertOp::create(rewriter, loc, shuffleElem, temp, i);
1659 }
1660 temp = vector::BitCastOp::create(rewriter, loc, flatSrcTy, temp);
1661 temp = vector::ShapeCastOp::create(rewriter, loc, srcTy, temp);
1662
1663 // Concatenate the lane-local and gathered data along the outer dimension.
1664 SmallVector<int64_t> indices(srcTy.getShape()[0] * 2);
1665 std::iota(indices.begin(), indices.end(), 0);
1666 Value res = vector::ShuffleOp::create(rewriter, loc, src, temp, indices);
1667 return res;
1668}
1669
1670/// Repacks `src`'s `lane_data` along `repackDim` between round-robin and
1671/// contiguous form with an `xegpu.lane_shuffle`, which moves each lane's run of
1672/// `k` elements across lanes while preserving the element type.
1673///
1674/// `inputData`/`targetData` are the `repackDim` `lane_data` of the input and
1675/// target layouts; exactly one must be 1 (round-robin) and the other `k`
1676/// (contiguous). Returns failure if that does not hold.
1677static FailureOr<Value> repackLaneData(ConversionPatternRewriter &rewriter,
1678 Location loc, Value src,
1679 int64_t repackDim, int64_t inputData,
1680 int64_t targetData) {
1681 auto srcTy = dyn_cast<VectorType>(src.getType());
1682 if (!srcTy)
1683 return failure();
1684 int64_t rank = srcTy.getRank();
1685 Type elemTy = srcTy.getElementType();
1686 int64_t k = srcTy.getShape()[repackDim];
1687
1688 bool roundRobinToContig = inputData == 1 && targetData == k;
1689 bool contigToRoundRobin = inputData == k && targetData == 1;
1690 if (!roundRobinToContig && !contigToRoundRobin)
1691 return failure();
1692
1693 // Round-robin -> contiguous gathers a lane's strided elements into
1694 // consecutive positions (pack); the reverse scatters them back (unpack).
1695 xegpu::LaneShuffleMode mode = roundRobinToContig
1696 ? xegpu::LaneShuffleMode::Pack
1697 : xegpu::LaneShuffleMode::Unpack;
1698 VectorType runTy = VectorType::get({k}, elemTy);
1699
1700 // Common case: the lane fragment is a single run (every dimension other than
1701 // `repackDim` is unit), so collapse it to 1D, shuffle once, and restore it.
1702 if (srcTy.getNumElements() == k) {
1703 if (rank == 1)
1704 return Value(
1705 xegpu::LaneShuffleOp::create(rewriter, loc, runTy, src, mode));
1706 Value flat = vector::ShapeCastOp::create(rewriter, loc, runTy, src);
1707 Value shuffled =
1708 xegpu::LaneShuffleOp::create(rewriter, loc, runTy, flat, mode);
1709 return Value(vector::ShapeCastOp::create(rewriter, loc, srcTy, shuffled));
1710 }
1711
1712 // When `repackDim` is innermost each run is a contiguous sub-vector, so it is
1713 // extracted and re-inserted as a whole.
1714 if (repackDim == rank - 1) {
1715 SmallVector<int64_t> outerShape(srcTy.getShape().drop_back());
1716 int64_t numRuns = computeProduct(outerShape);
1717 SmallVector<int64_t> outerStrides = computeStrides(outerShape);
1718 Value result = arith::ConstantOp::create(rewriter, loc, srcTy,
1719 rewriter.getZeroAttr(srcTy));
1720 for (int64_t i = 0; i < numRuns; ++i) {
1721 SmallVector<int64_t> pos = delinearize(i, outerStrides);
1722 Value run = vector::ExtractOp::create(rewriter, loc, src, pos);
1723 Value shuffled =
1724 xegpu::LaneShuffleOp::create(rewriter, loc, runTy, run, mode);
1725 result = vector::InsertOp::create(rewriter, loc, shuffled, result, pos);
1726 }
1727 return result;
1728 }
1729
1730 // Otherwise each run is strided along `repackDim`: extract the `k`-long slice
1731 // (a sub-vector that is unit along every other dim), flatten it to 1D,
1732 // shuffle, and insert it back.
1733 SmallVector<int64_t> keptShape;
1734 SmallVector<int64_t> keptDims;
1735 for (int64_t d = 0; d < rank; ++d)
1736 if (d != repackDim) {
1737 keptShape.push_back(srcTy.getShape()[d]);
1738 keptDims.push_back(d);
1739 }
1740 int64_t numRuns = computeProduct(keptShape);
1741 SmallVector<int64_t> keptStrides = computeStrides(keptShape);
1742 SmallVector<int64_t> sliceSizes(rank, 1);
1743 sliceSizes[repackDim] = k;
1744 SmallVector<int64_t> sliceStrides(rank, 1);
1745 VectorType sliceTy = VectorType::get(sliceSizes, elemTy);
1746 Value result = arith::ConstantOp::create(rewriter, loc, srcTy,
1747 rewriter.getZeroAttr(srcTy));
1748 for (int64_t i = 0; i < numRuns; ++i) {
1749 SmallVector<int64_t> keptPos = delinearize(i, keptStrides);
1750 SmallVector<int64_t> offsets(rank, 0);
1751 for (auto [dim, coord] : llvm::zip_equal(keptDims, keptPos))
1752 offsets[dim] = coord;
1753 Value slice = vector::ExtractStridedSliceOp::create(
1754 rewriter, loc, src, offsets, sliceSizes, sliceStrides);
1755 Value run = vector::ShapeCastOp::create(rewriter, loc, runTy, slice);
1756 Value repacked =
1757 xegpu::LaneShuffleOp::create(rewriter, loc, runTy, run, mode);
1758 Value repackedSlice =
1759 vector::ShapeCastOp::create(rewriter, loc, sliceTy, repacked);
1760 result = vector::InsertStridedSliceOp::create(
1761 rewriter, loc, repackedSlice, result, offsets, sliceStrides);
1762 }
1763 return result;
1764}
1765
1766/// Folds a subgroup-level ConvertLayout op with compatible lane layouts.
1767struct SgToLaneConvertLayout
1768 : public OpConversionPattern<xegpu::ConvertLayoutOp> {
1769 using OpConversionPattern<xegpu::ConvertLayoutOp>::OpConversionPattern;
1770
1771 LogicalResult
1772 matchAndRewrite(xegpu::ConvertLayoutOp op, OpAdaptor adaptor,
1773 ConversionPatternRewriter &rewriter) const override {
1774 auto inputLayout = op.getEffectiveInputLayout();
1775 auto targetLayout = op.getTargetLayoutAttr();
1776 Type valType = op.getResult().getType();
1777
1778 if (valType.isIntOrFloat()) {
1779 rewriter.replaceOp(op, op.getSource());
1780 return success();
1781 }
1782
1783 auto resShape = cast<VectorType>(valType).getShape();
1784 SmallVector<int64_t> resShapeVec(resShape.begin(), resShape.end());
1785
1786 // Equivalent layouts: the convert_layout is a no-op and folds to its
1787 // source.
1788 if (inputLayout.isCompatibleWith(targetLayout, resShapeVec,
1790 rewriter.replaceOp(op, adaptor.getSource());
1791 return success();
1792 }
1793
1794 // Handle the special case where the conversion redistributes a value
1795 // across a fraction of the subgroup: the lane_layout shrinks along the
1796 // outer (distributed) dimension while lane_data stays the same. Only a
1797 // pure outer-dimension lane_layout change is supported, so the inner
1798 // lane_layout must be unit (making the outer dim the only distributed one)
1799 // and the outer lane_layout must be genuinely distributed (> 1), which
1800 // also rules out the degenerate [1, 1] layout.
1801 if (inputLayout.getEffectiveOrderAsInt() ==
1802 targetLayout.getEffectiveOrderAsInt() &&
1803 inputLayout.getRank() == 2 && targetLayout.getRank() == 2) {
1804 auto laneLayout = inputLayout.getEffectiveLaneLayoutAsInt();
1805 auto targetLaneLayout = targetLayout.getEffectiveLaneLayoutAsInt();
1806 auto laneData = inputLayout.getEffectiveLaneDataAsInt();
1807 auto targetLaneData = targetLayout.getEffectiveLaneDataAsInt();
1808 if (laneLayout.size() == 2 && targetLaneLayout.size() == 2 &&
1809 laneData == targetLaneData && laneLayout[1] == 1 &&
1810 targetLaneLayout[1] == 1 && laneLayout[0] > 1 &&
1811 laneLayout[0] != targetLaneLayout[0]) {
1812 FailureOr<Value> res = shuffleDataAsLaneLayoutChange(
1813 rewriter, op.getLoc(), adaptor.getSource(), laneLayout[0],
1814 targetLaneLayout[0]);
1815 if (succeeded(res)) {
1816 rewriter.replaceOp(op, *res);
1817 return success();
1818 }
1819 }
1820 }
1821
1822 // Handle a pure `lane_data` repack: `lane_layout` and `order` are unchanged
1823 // and exactly one dimension's `lane_data` switches between round-robin
1824 // (lane_data 1) and contiguous (lane_data == run length). The elements per
1825 // lane are unchanged, but their assignment to lanes is not, so the data is
1826 // moved across lanes with `xegpu.lane_shuffle`. The changed dimension must
1827 // be one of the two innermost ones, since sg-to-lane distribution is 2D.
1828 if (inputLayout.getEffectiveOrderAsInt() ==
1829 targetLayout.getEffectiveOrderAsInt() &&
1830 inputLayout.getEffectiveLaneLayoutAsInt() ==
1831 targetLayout.getEffectiveLaneLayoutAsInt()) {
1832 auto laneLayout = inputLayout.getEffectiveLaneLayoutAsInt();
1833 auto laneData = inputLayout.getEffectiveLaneDataAsInt();
1834 auto targetLaneData = targetLayout.getEffectiveLaneDataAsInt();
1835 // Find the single dimension whose lane_data changed; bail out if more
1836 // than one differs.
1837 int64_t rank = laneData.size();
1838 int64_t repackDim = -1;
1839 bool multipleChanged = false;
1840 for (int64_t d = 0; d < rank; ++d)
1841 if (laneData[d] != targetLaneData[d]) {
1842 if (repackDim != -1)
1843 multipleChanged = true;
1844 repackDim = d;
1845 }
1846
1847 // `repackDim` must be the distributed dim (lane_layout != 1) and the
1848 // other innermost dim non-distributed (lane_layout == 1).
1849 int64_t otherDim = repackDim == rank - 1 ? rank - 2 : rank - 1;
1850 bool laneLayoutOk = repackDim != -1 && laneLayout[repackDim] != 1 &&
1851 (rank < 2 || laneLayout[otherDim] == 1);
1852
1853 // Exactly one dimension must change, and it must be one of the two
1854 // innermost (>= rank - 2).
1855 if (repackDim != -1 && repackDim >= rank - 2 && !multipleChanged &&
1856 laneLayoutOk) {
1857 FailureOr<Value> res = repackLaneData(
1858 rewriter, op.getLoc(), adaptor.getSource(), repackDim,
1859 laneData[repackDim], targetLaneData[repackDim]);
1860 if (succeeded(res)) {
1861 rewriter.replaceOp(op, *res);
1862 return success();
1863 }
1864 }
1865 }
1866
1867 return rewriter.notifyMatchFailure(
1868 op, "lowering incompatible convert_layout not yet supported");
1869 }
1870};
1871
1872// Trivially distribute `vector.interleave`
1873struct SgToLaneVectorInterleave
1874 : public OpConversionPattern<vector::InterleaveOp> {
1875 using OpConversionPattern<vector::InterleaveOp>::OpConversionPattern;
1876
1877 LogicalResult
1878 matchAndRewrite(vector::InterleaveOp op, OpAdaptor adaptor,
1879 ConversionPatternRewriter &rewriter) const override {
1880
1881 auto newOp = vector::InterleaveOp::create(
1882 rewriter, op.getLoc(), adaptor.getLhs(), adaptor.getRhs());
1883 rewriter.replaceOp(op, newOp.getResult());
1884 return success();
1885 }
1886};
1887
1888// Trivially distribute `vector.deinterleave`
1889struct SgToLaneVectorDeinterleave
1890 : public OpConversionPattern<vector::DeinterleaveOp> {
1891 using OpConversionPattern<vector::DeinterleaveOp>::OpConversionPattern;
1892
1893 LogicalResult
1894 matchAndRewrite(vector::DeinterleaveOp op, OpAdaptor adaptor,
1895 ConversionPatternRewriter &rewriter) const override {
1896
1897 auto newOp = vector::DeinterleaveOp::create(rewriter, op.getLoc(),
1898 adaptor.getSource());
1899 rewriter.replaceOp(op, newOp.getResults());
1900 return success();
1901 }
1902};
1903
1904struct SgToLaneDpasMx : public OpConversionPattern<xegpu::DpasMxOp> {
1905 using OpConversionPattern<xegpu::DpasMxOp>::OpConversionPattern;
1906
1907 LogicalResult
1908 matchAndRewrite(xegpu::DpasMxOp op, OpAdaptor adaptor,
1909 ConversionPatternRewriter &rewriter) const override {
1910 const auto *uArch =
1911 xegpu::uArch::getUArch(xegpu::getChipStr(op).value_or(""));
1912 if (!uArch)
1913 return failure();
1914 if (!uArch->isSupportedInstruction(
1916 return rewriter.notifyMatchFailure(
1917 op, "target uArch does not support scaled subgroup mma");
1918 // Check if the op has A, B and CD layouts attached.
1919 auto layoutA = cast<xegpu::LayoutAttr>(op.getLayoutAAttr());
1920 auto layoutB = cast<xegpu::LayoutAttr>(op.getLayoutBAttr());
1921 auto layoutCd = cast<xegpu::LayoutAttr>(op.getLayoutCdAttr());
1922 if (!layoutA || !layoutB || !layoutCd)
1923 return rewriter.notifyMatchFailure(
1924 op, "missing required layout attributes for DpasMxOp distribution");
1925
1926 // Retrieve expected types, according to anchor layouts.
1927 auto expected1DTypeResult =
1928 xegpu::getDistributedVectorType(op.getType(), layoutCd);
1929 auto expected1DTypeA =
1930 xegpu::getDistributedVectorType(op.getA().getType(), layoutA);
1931 auto expected1DTypeB =
1932 xegpu::getDistributedVectorType(op.getB().getType(), layoutB);
1933
1934 VectorType expected1DTypeScaleA, expected1DTypeScaleB;
1935 if (op.getScaleA()) {
1936 auto layoutScaleA = cast<xegpu::LayoutAttr>(op.getLayoutAScaleAttr());
1937 auto expected1DTypeScaleAOrFailure = xegpu::getDistributedVectorType(
1938 cast<VectorType>(op.getScaleA().getType()), layoutScaleA);
1939 if (failed(expected1DTypeScaleAOrFailure))
1940 return rewriter.notifyMatchFailure(
1941 op, "failed to calculate expected 1D vector type for scale A");
1942 expected1DTypeScaleA = expected1DTypeScaleAOrFailure.value();
1943 }
1944 if (op.getScaleB()) {
1945 auto layoutScaleB = cast<xegpu::LayoutAttr>(op.getLayoutBScaleAttr());
1946 auto expected1DTypeScaleBOrFailure = xegpu::getDistributedVectorType(
1947 cast<VectorType>(op.getScaleB().getType()), layoutScaleB);
1948 if (failed(expected1DTypeScaleBOrFailure))
1949 return rewriter.notifyMatchFailure(
1950 op, "failed to calculate expected 1D vector type for scale B");
1951 expected1DTypeScaleB = expected1DTypeScaleBOrFailure.value();
1952 }
1953
1954 auto expectedNDTypeResult =
1955 xegpu::getDistVecTypeBasedOnLaneLayout(layoutCd, op.getType());
1956 if (failed(expected1DTypeResult) || failed(expected1DTypeA) ||
1957 failed(expected1DTypeB))
1958 return rewriter.notifyMatchFailure(
1959 op,
1960 "failed to calculate supported workitem 1D vector types for DpasOp "
1961 "from layouts");
1962 if (failed(expectedNDTypeResult))
1963 return rewriter.notifyMatchFailure(
1964 op, "unable to compute expected workitem vector type for DpasOp from "
1965 "lane layout");
1966
1967 // Validate bit widths match uArch packed format requirements
1968 const auto *uArchInstruction = dyn_cast<
1969 xegpu::uArch::SubgroupScaledMatrixMultiplyAcc>(uArch->getInstruction(
1971 assert(uArchInstruction);
1972 auto wiAType = expected1DTypeA.value();
1973 auto wiBType = expected1DTypeB.value();
1974 // Calculate total packed bit width = element bit width * vector size
1975 unsigned aPackedBitWidth =
1976 wiAType.getElementTypeBitWidth() * wiAType.getNumElements();
1977 unsigned bPackedBitWidth =
1978 wiBType.getElementTypeBitWidth() * wiBType.getNumElements();
1979 if (aPackedBitWidth % uArchInstruction->getPackedFormatBitSizeA())
1980 return rewriter.notifyMatchFailure(
1981 op, "A operand packed bit width must be a multiple of uArch packed "
1982 "format requirement");
1983 if (bPackedBitWidth % uArchInstruction->getPackedFormatBitSizeB())
1984 return rewriter.notifyMatchFailure(
1985 op, "B operand packed bit width must be a multiple of uArch packed "
1986 "format requirement");
1987
1988 auto newOp = xegpu::DpasMxOp::create(
1989 rewriter, op->getLoc(), expected1DTypeResult.value(),
1990 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getA()),
1991 expected1DTypeA.value()),
1992 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getB()),
1993 expected1DTypeB.value()),
1994 op.getAcc()
1995 ? castValueTo(rewriter,
1996 cast<TypedValue<VectorType>>(adaptor.getAcc()),
1997 expected1DTypeResult.value())
1998 : nullptr,
1999
2000 op.getScaleA()
2001 ? castValueTo(rewriter,
2002 cast<TypedValue<VectorType>>(adaptor.getScaleA()),
2003 expected1DTypeScaleA)
2004 : nullptr,
2005 op.getScaleB()
2006 ? castValueTo(rewriter,
2007 cast<TypedValue<VectorType>>(adaptor.getScaleB()),
2008 expected1DTypeScaleB)
2009 : nullptr,
2010 /** layoutA**/ nullptr,
2011 /** layoutB**/ nullptr, /** layoutCd**/ nullptr,
2012 /** layoutAScale**/ nullptr, /** layoutBScale**/ nullptr);
2013 // Explicitly set the new types to enable correct type materializations.
2014 rewriter.replaceOp(op, castValueTo(rewriter, newOp.getResult(),
2015 expectedNDTypeResult.value()));
2016 return success();
2017 }
2018};
2019
2020struct XeGPUSgToLaneDistributePass
2021 : public xegpu::impl::XeGPUSgToLaneDistributeBase<
2022 XeGPUSgToLaneDistributePass> {
2023 void runOnOperation() override;
2024};
2025
2026} // namespace
2027
2028void XeGPUSgToLaneDistributePass::runOnOperation() {
2029
2030 // Recover temporary operand layouts for usage in patterns.
2031 Operation *root = getOperation();
2032 if (!xegpu::recoverTemporaryLayouts(root)) {
2033 signalPassFailure();
2034 return;
2035 }
2036
2037 // Collect existing UnrealizedConversionCastOps. These must be preserved.
2038 llvm::SmallSetVector<UnrealizedConversionCastOp, 8> existingCasts;
2039 root->walk(
2040 [&](UnrealizedConversionCastOp castOp) { existingCasts.insert(castOp); });
2041 // Perform a structural type conversion to convert structural ops to have WI
2042 // types. This will insert UnrealizedConversionCastOps to make the IR
2043 // valid.
2044 {
2045 ConversionTarget target(getContext());
2046 TypeConverter typeConverter;
2047 RewritePatternSet patterns(&getContext());
2048 // Source (N:1) and target (1:1) materializations using
2049 // UnrealizedConversionCastOp.
2050 auto materializeCast = [](OpBuilder &builder, Type type, ValueRange inputs,
2051 Location loc) -> Value {
2052 return UnrealizedConversionCastOp::create(builder, loc, type, inputs)
2053 .getResult(0);
2054 };
2055 typeConverter.addSourceMaterialization(materializeCast);
2056 typeConverter.addTargetMaterialization(materializeCast);
2059 patterns, target);
2061 typeConverter, patterns, target, root);
2062 target.addLegalOp<UnrealizedConversionCastOp>();
2063 (void)applyPartialConversion(root, target, std::move(patterns));
2064 }
2065 // Fold cancelling cast chains and erase dead casts.
2066 xegpu::cleanupUnrealizedConversionCasts(root, existingCasts);
2067 xegpu::removeTemporaryLayoutAttrs(getOperation());
2068}
2069
2071 TypeConverter &typeConverter, Operation *topLevelOp) {
2072 // Pass through any type by default; more specific conversions registered
2073 // below override this for TensorDescType and (distributing) VectorType.
2074 typeConverter.addConversion([](Type type) -> Type { return type; });
2075 // For TensorDescType, drop the layout attribute if any.
2076 typeConverter.addConversion([](TensorDescType type) -> Type {
2077 if (type.getLayoutAttr()) {
2078 return type.dropLayouts();
2079 }
2080 return type;
2081 });
2082 // For VectorType, distribute based on the lane layout (1:1 shape-changing
2083 // conversion). Uses xegpu::addVectorTypeConversion with a pre-computed
2084 // map for SCF loop block args (see precomputeLoopBlockArgTypes for the
2085 // rationale).
2086 auto getSubShapeAndCount = [](VectorType vecTy,
2087 xegpu::DistributeLayoutAttr layout)
2088 -> std::pair<SmallVector<int64_t>, int> {
2089 auto distTyOrFailure = getDistVecTypeBasedOnLaneLayout(layout, vecTy);
2090 if (failed(distTyOrFailure))
2091 return {{}, 0};
2092 return {SmallVector<int64_t>(distTyOrFailure->getShape()), 1};
2093 };
2094 auto loopArgTypes =
2095 xegpu::precomputeLoopBlockArgTypes(topLevelOp, getSubShapeAndCount);
2096 xegpu::addVectorTypeConversion(typeConverter, getSubShapeAndCount,
2097 std::move(loopArgTypes));
2098}
2099
2101 TypeConverter &typeConverter, RewritePatternSet &patterns,
2102 ConversionTarget &target, Operation *topLevelOp) {
2103 populateXeGPUSgToLaneDistributeTypeConversions(typeConverter, topLevelOp);
2104 // CreateNdDescOp is legal only if its result type has no layout attribute.
2105 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
2106 [&](xegpu::CreateNdDescOp op) { return !op.getType().getLayoutAttr(); });
2107 // Any anchor XeGPU op is legal only if it has no anchor layout.
2108 target.addDynamicallyLegalDialect<xegpu::XeGPUDialect>([](Operation *op) {
2109 if (isa<xegpu::ConvertLayoutOp>(op))
2110 return false;
2111 auto anchorOp = dyn_cast<AnchorLayoutInterface>(op);
2112 if (!anchorOp)
2113 return true;
2114 return !anchorOp.getAnchorLayout();
2115 });
2116 // Arith constants are legal only if they have no temporary layout attribute.
2117 target.addDynamicallyLegalOp<arith::ConstantOp>(
2118 [=](arith::ConstantOp op) -> bool {
2119 // If the result type is not a vector, it's legal.
2120 if (!isa<VectorType>(op.getResult().getType()))
2121 return true;
2122 return !xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
2123 });
2124 // In math and arith dialects, only handle elementwise ops with a single
2125 // result and with a result layout attribute.
2126 target.addDynamicallyLegalDialect<math::MathDialect, arith::ArithDialect>(
2127 [=](Operation *op) -> std::optional<bool> {
2128 // Only handle elementwise mappable ops
2130 return true;
2131 // Only handle ops with single vector result
2132 if (op->getNumResults() != 1)
2133 return true;
2134
2135 VectorType resultType =
2136 dyn_cast<VectorType>(op->getResult(0).getType());
2137 if (!resultType)
2138 return true;
2139
2140 // Check if all operands are vectors of the same shape
2141 for (Value operand : op->getOperands()) {
2142 VectorType operandType = dyn_cast<VectorType>(operand.getType());
2143 if (!operandType || operandType.getShape() != resultType.getShape()) {
2144 return true;
2145 }
2146 }
2147 return !xegpu::getTemporaryLayout(dyn_cast<OpResult>(op->getResult(0)));
2148 });
2149 // vector::ReductionOp is legal only if its source has no distribute layout
2150 // attribute.
2151 target.addDynamicallyLegalOp<vector::ReductionOp>(
2152 [=](vector::ReductionOp op) -> bool {
2153 auto layout = xegpu::getDistributeLayoutAttr(op.getVector());
2154 return !layout;
2155 });
2156 // vector::MultiDimReductionOp op legality.
2157 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
2158 [=](vector::MultiDimReductionOp op) -> bool {
2159 return !isValidSubgroupMultiReductionOp(op);
2160 });
2161 target.addDynamicallyLegalOp<vector::CreateMaskOp, vector::ConstantMaskOp,
2162 vector::TransposeOp, vector::BitCastOp,
2163 vector::ShapeCastOp, vector::StepOp,
2164 vector::BroadcastOp>([=](Operation *op) -> bool {
2165 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2166 });
2167 target.addDynamicallyLegalOp<vector::ExtractOp>(
2168 [=](vector::ExtractOp op) -> bool {
2169 if (!isa<VectorType>(op.getType()))
2170 return true;
2171 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2172 });
2173 target.addDynamicallyLegalOp<vector::InsertOp>(
2174 [=](vector::InsertOp op) -> bool {
2175 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2176 });
2177 target.addDynamicallyLegalOp<vector::ExtractStridedSliceOp>(
2178 [=](vector::ExtractStridedSliceOp op) -> bool {
2179 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2180 });
2181 target.addDynamicallyLegalOp<vector::InsertStridedSliceOp>(
2182 [=](vector::InsertStridedSliceOp op) -> bool {
2183 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2184 });
2185 target.addDynamicallyLegalOp<vector::InterleaveOp, vector::DeinterleaveOp>(
2186 [=](Operation *op) -> bool {
2187 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2188 });
2189 target.markUnknownOpDynamicallyLegal([](Operation *op) { return true; });
2190 patterns.add<
2191 SgToLaneCreateNdDesc, SgToLaneLoadNd, SgToLaneStoreNd, SgToLaneDpas,
2192 SgToLaneElementWise, SgToLaneArithConstant, SgToLanePrefetchNd,
2193 SgToLaneLoadGather, SgToLaneStoreScatter, SgToLaneVectorReduction,
2194 SgToLaneMultiDimReduction, SgToLaneVectorExtract, SgToLaneVectorInsert,
2195 SgToLaneVectorExtractStridedSlice, SgToLaneVectorInsertStridedSlice,
2196 SgToLaneLoadMatrix, SgToLaneStoreMatrix, SgToLaneConvertLayout,
2197 SgToLaneVectorTranspose, SgToLaneVectorBitcast, SgToLaneVectorStep,
2198 SgToLaneVectorShapeCast, SgToLaneBroadcast,
2199 SgToLaneCreateMask<vector::CreateMaskOp>,
2200 SgToLaneCreateMask<vector::ConstantMaskOp>, SgToLaneVectorDeinterleave,
2201 SgToLaneVectorInterleave, SgToLaneDpasMx>(typeConverter,
2202 patterns.getContext());
2203}
return success()
b getContext())
Attributes are known-constant values of operations.
Definition Attributes.h:25
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 is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
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.
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
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
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.
A range-style iterator that allows for iterating over the offsets of all potential tiles of size tile...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:297
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int64_t > content)
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
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.
SmallVector< Value > getAsValues(OpBuilder &builder, Location loc, ArrayRef< OpFoldResult > foldResults)
Convert foldResults into Values.
const uArch * getUArch(llvm::StringRef archName)
Definition uArchCommon.h:24
void populateXeGPUSgToLaneDistributeTypeConversionAndLegality(TypeConverter &typeConverter, RewritePatternSet &patterns, ConversionTarget &target, Operation *topLevelOp)
Defines type conversions and legality for XeGPU subgroup to lane distribution and appends the require...
bool requirePacked(const DistributeLayoutAttr layout)
Helper function to check if the layout is packed.
void removeTemporaryLayoutAttrs(Operation *op)
Removes the temporary layout attributes for each OpOperand and OpResult of the given operation.
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 recoverTemporaryLayouts(Operation *rootOp)
Attach layout attributes to all vector-type operands of operations within the given operation's neste...
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.
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::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,...
DistributeLayoutAttr getTemporaryLayout(const T &operandOrResult)
get and set distribute layout attribute for non-anchor operations (and offsets/masks of load/store op...
void populateXeGPUSgToLaneDistributeTypeConversions(TypeConverter &typeConverter, Operation *topLevelOp)
Define only the type conversions needed for XeGPU subgroup to lane distribution.
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.
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.
FailureOr< VectorType > getDistributedVectorType(xegpu::TensorDescType tdescTy)
If tensor descriptor has a layout attribute it is used in SIMT mode.
Include the generated interface declarations.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
SmallVector< int64_t > delinearize(int64_t linearIndex, ArrayRef< int64_t > strides)
Given the strides together with a linear index in the dimension space, return the vector-space offset...
int64_t computeProduct(ArrayRef< int64_t > basis)
Self-explicit.
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
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
This represents an operation in an abstracted form, suitable for use with the builder APIs.
void addOperands(ValueRange newOperands)
void addAttribute(StringRef name, Attribute attr)
Add an attribute with the specified name.
void addTypes(ArrayRef< Type > newTypes)
Attribute propertiesAttr
This Attribute is used to opaquely construct the properties of the operation.