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