MLIR 23.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();
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");
335
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/// Folds a subgroup-level ConvertLayout op with compatible lane layouts.
1669struct SgToLaneConvertLayout
1670 : public OpConversionPattern<xegpu::ConvertLayoutOp> {
1671 using OpConversionPattern<xegpu::ConvertLayoutOp>::OpConversionPattern;
1672
1673 LogicalResult
1674 matchAndRewrite(xegpu::ConvertLayoutOp op, OpAdaptor adaptor,
1675 ConversionPatternRewriter &rewriter) const override {
1676 auto inputLayout = op.getInputLayoutAttr();
1677 auto targetLayout = op.getTargetLayoutAttr();
1678 Type valType = op.getResult().getType();
1679
1680 if (valType.isIntOrFloat()) {
1681 rewriter.replaceOp(op, op.getSource());
1682 return success();
1683 }
1684
1685 auto resShape = cast<VectorType>(valType).getShape();
1686 SmallVector<int64_t> resShapeVec(resShape.begin(), resShape.end());
1687
1688 // Equivalent layouts: the convert_layout is a no-op and folds to its
1689 // source.
1690 if (inputLayout.isCompatibleWith(targetLayout, resShapeVec,
1691 xegpu::LayoutKind::Lane)) {
1692 rewriter.replaceOp(op, adaptor.getSource());
1693 return success();
1694 }
1695
1696 // Handle the special case where the conversion redistributes a value
1697 // across a fraction of the subgroup: the lane_layout shrinks along the
1698 // outer (distributed) dimension while lane_data stays the same. Only a
1699 // pure outer-dimension lane_layout change is supported, so the inner
1700 // lane_layout must be unit (making the outer dim the only distributed one)
1701 // and the outer lane_layout must be genuinely distributed (> 1), which
1702 // also rules out the degenerate [1, 1] layout.
1703 if (inputLayout.getEffectiveOrderAsInt() ==
1704 targetLayout.getEffectiveOrderAsInt() &&
1705 inputLayout.getRank() == 2 && targetLayout.getRank() == 2) {
1706 auto laneLayout = inputLayout.getEffectiveLaneLayoutAsInt();
1707 auto targetLaneLayout = targetLayout.getEffectiveLaneLayoutAsInt();
1708 auto laneData = inputLayout.getEffectiveLaneDataAsInt();
1709 auto targetLaneData = targetLayout.getEffectiveLaneDataAsInt();
1710 if (laneLayout.size() == 2 && targetLaneLayout.size() == 2 &&
1711 laneData == targetLaneData && laneLayout[1] == 1 &&
1712 targetLaneLayout[1] == 1 && laneLayout[0] > 1 &&
1713 laneLayout[0] != targetLaneLayout[0]) {
1714 FailureOr<Value> res = shuffleDataAsLaneLayoutChange(
1715 rewriter, op.getLoc(), adaptor.getSource(), laneLayout[0],
1716 targetLaneLayout[0]);
1717 if (succeeded(res)) {
1718 rewriter.replaceOp(op, *res);
1719 return success();
1720 }
1721 }
1722 }
1723
1724 return rewriter.notifyMatchFailure(
1725 op, "lowering incompatible convert_layout not yet supported");
1726 }
1727};
1728
1729// Trivially distribute `vector.interleave`
1730struct SgToLaneVectorInterleave
1731 : public OpConversionPattern<vector::InterleaveOp> {
1732 using OpConversionPattern<vector::InterleaveOp>::OpConversionPattern;
1733
1734 LogicalResult
1735 matchAndRewrite(vector::InterleaveOp op, OpAdaptor adaptor,
1736 ConversionPatternRewriter &rewriter) const override {
1737
1738 auto newOp = vector::InterleaveOp::create(
1739 rewriter, op.getLoc(), adaptor.getLhs(), adaptor.getRhs());
1740 rewriter.replaceOp(op, newOp.getResult());
1741 return success();
1742 }
1743};
1744
1745// Trivially distribute `vector.deinterleave`
1746struct SgToLaneVectorDeinterleave
1747 : public OpConversionPattern<vector::DeinterleaveOp> {
1748 using OpConversionPattern<vector::DeinterleaveOp>::OpConversionPattern;
1749
1750 LogicalResult
1751 matchAndRewrite(vector::DeinterleaveOp op, OpAdaptor adaptor,
1752 ConversionPatternRewriter &rewriter) const override {
1753
1754 auto newOp = vector::DeinterleaveOp::create(rewriter, op.getLoc(),
1755 adaptor.getSource());
1756 rewriter.replaceOp(op, newOp.getResults());
1757 return success();
1758 }
1759};
1760
1761struct SgToLaneDpasMx : public OpConversionPattern<xegpu::DpasMxOp> {
1762 using OpConversionPattern<xegpu::DpasMxOp>::OpConversionPattern;
1763
1764 LogicalResult
1765 matchAndRewrite(xegpu::DpasMxOp op, OpAdaptor adaptor,
1766 ConversionPatternRewriter &rewriter) const override {
1767 const auto *uArch =
1768 xegpu::uArch::getUArch(xegpu::getChipStr(op).value_or(""));
1769 if (!uArch)
1770 return failure();
1771 if (!uArch->isSupportedInstruction(
1772 xegpu::uArch::InstructionKind::SubgroupScaledMatrixMultiplyAcc))
1773 return rewriter.notifyMatchFailure(
1774 op, "target uArch does not support scaled subgroup mma");
1775 // Check if the op has A, B and CD layouts attached.
1776 auto layoutA = cast<xegpu::LayoutAttr>(op.getLayoutAAttr());
1777 auto layoutB = cast<xegpu::LayoutAttr>(op.getLayoutBAttr());
1778 auto layoutCd = cast<xegpu::LayoutAttr>(op.getLayoutCdAttr());
1779 if (!layoutA || !layoutB || !layoutCd)
1780 return rewriter.notifyMatchFailure(
1781 op, "missing required layout attributes for DpasMxOp distribution");
1782
1783 // Retrieve expected types, according to anchor layouts.
1784 auto expected1DTypeResult =
1785 xegpu::getDistributedVectorType(op.getType(), layoutCd);
1786 auto expected1DTypeA =
1787 xegpu::getDistributedVectorType(op.getA().getType(), layoutA);
1788 auto expected1DTypeB =
1789 xegpu::getDistributedVectorType(op.getB().getType(), layoutB);
1790
1791 VectorType expected1DTypeScaleA, expected1DTypeScaleB;
1792 if (op.getScaleA()) {
1793 auto layoutScaleA = cast<xegpu::LayoutAttr>(op.getLayoutAScaleAttr());
1794 auto expected1DTypeScaleAOrFailure = xegpu::getDistributedVectorType(
1795 cast<VectorType>(op.getScaleA().getType()), layoutScaleA);
1796 if (failed(expected1DTypeScaleAOrFailure))
1797 return rewriter.notifyMatchFailure(
1798 op, "failed to calculate expected 1D vector type for scale A");
1799 expected1DTypeScaleA = expected1DTypeScaleAOrFailure.value();
1800 }
1801 if (op.getScaleB()) {
1802 auto layoutScaleB = cast<xegpu::LayoutAttr>(op.getLayoutBScaleAttr());
1803 auto expected1DTypeScaleBOrFailure = xegpu::getDistributedVectorType(
1804 cast<VectorType>(op.getScaleB().getType()), layoutScaleB);
1805 if (failed(expected1DTypeScaleBOrFailure))
1806 return rewriter.notifyMatchFailure(
1807 op, "failed to calculate expected 1D vector type for scale B");
1808 expected1DTypeScaleB = expected1DTypeScaleBOrFailure.value();
1809 }
1810
1811 auto expectedNDTypeResult =
1812 xegpu::getDistVecTypeBasedOnLaneLayout(layoutCd, op.getType());
1813 if (failed(expected1DTypeResult) || failed(expected1DTypeA) ||
1814 failed(expected1DTypeB))
1815 return rewriter.notifyMatchFailure(
1816 op,
1817 "failed to calculate supported workitem 1D vector types for DpasOp "
1818 "from layouts");
1819 if (failed(expectedNDTypeResult))
1820 return rewriter.notifyMatchFailure(
1821 op, "unable to compute expected workitem vector type for DpasOp from "
1822 "lane layout");
1823
1824 // Validate bit widths match uArch packed format requirements
1825 const auto *uArchInstruction = dyn_cast<
1826 xegpu::uArch::SubgroupScaledMatrixMultiplyAcc>(uArch->getInstruction(
1827 xegpu::uArch::InstructionKind::SubgroupScaledMatrixMultiplyAcc));
1828 assert(uArchInstruction);
1829 auto wiAType = expected1DTypeA.value();
1830 auto wiBType = expected1DTypeB.value();
1831 // Calculate total packed bit width = element bit width * vector size
1832 unsigned aPackedBitWidth =
1833 wiAType.getElementTypeBitWidth() * wiAType.getNumElements();
1834 unsigned bPackedBitWidth =
1835 wiBType.getElementTypeBitWidth() * wiBType.getNumElements();
1836 if (aPackedBitWidth % uArchInstruction->getPackedFormatBitSizeA())
1837 return rewriter.notifyMatchFailure(
1838 op, "A operand packed bit width must be a multiple of uArch packed "
1839 "format requirement");
1840 if (bPackedBitWidth % uArchInstruction->getPackedFormatBitSizeB())
1841 return rewriter.notifyMatchFailure(
1842 op, "B operand packed bit width must be a multiple of uArch packed "
1843 "format requirement");
1844
1845 auto newOp = xegpu::DpasMxOp::create(
1846 rewriter, op->getLoc(), expected1DTypeResult.value(),
1847 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getA()),
1848 expected1DTypeA.value()),
1849 castValueTo(rewriter, cast<TypedValue<VectorType>>(adaptor.getB()),
1850 expected1DTypeB.value()),
1851 op.getAcc()
1852 ? castValueTo(rewriter,
1853 cast<TypedValue<VectorType>>(adaptor.getAcc()),
1854 expected1DTypeResult.value())
1855 : nullptr,
1856
1857 op.getScaleA()
1858 ? castValueTo(rewriter,
1859 cast<TypedValue<VectorType>>(adaptor.getScaleA()),
1860 expected1DTypeScaleA)
1861 : nullptr,
1862 op.getScaleB()
1863 ? castValueTo(rewriter,
1864 cast<TypedValue<VectorType>>(adaptor.getScaleB()),
1865 expected1DTypeScaleB)
1866 : nullptr,
1867 /** layoutA**/ nullptr,
1868 /** layoutB**/ nullptr, /** layoutCd**/ nullptr,
1869 /** layoutAScale**/ nullptr, /** layoutBScale**/ nullptr);
1870 // Explicitly set the new types to enable correct type materializations.
1871 rewriter.replaceOp(op, castValueTo(rewriter, newOp.getResult(),
1872 expectedNDTypeResult.value()));
1873 return success();
1874 }
1875};
1876
1877struct XeGPUSgToLaneDistributePass
1879 XeGPUSgToLaneDistributePass> {
1880 void runOnOperation() override;
1881};
1882
1883} // namespace
1884
1885void XeGPUSgToLaneDistributePass::runOnOperation() {
1886
1887 // Recover temporary operand layouts for usage in patterns.
1888 Operation *root = getOperation();
1889 if (!xegpu::recoverTemporaryLayouts(root)) {
1890 signalPassFailure();
1891 return;
1892 }
1893
1894 // Collect existing UnrealizedConversionCastOps. These must be preserved.
1895 llvm::SmallSetVector<UnrealizedConversionCastOp, 8> existingCasts;
1896 root->walk(
1897 [&](UnrealizedConversionCastOp castOp) { existingCasts.insert(castOp); });
1898 // Perform a structural type conversion to convert structural ops to have WI
1899 // types. This will insert UnrealizedConversionCastOps to make the IR
1900 // valid.
1901 {
1902 ConversionTarget target(getContext());
1903 TypeConverter typeConverter;
1904 RewritePatternSet patterns(&getContext());
1905 // Source (N:1) and target (1:1) materializations using
1906 // UnrealizedConversionCastOp.
1907 auto materializeCast = [](OpBuilder &builder, Type type, ValueRange inputs,
1908 Location loc) -> Value {
1909 return UnrealizedConversionCastOp::create(builder, loc, type, inputs)
1910 .getResult(0);
1911 };
1912 typeConverter.addSourceMaterialization(materializeCast);
1913 typeConverter.addTargetMaterialization(materializeCast);
1916 patterns, target);
1918 typeConverter, patterns, target, root);
1919 target.addLegalOp<UnrealizedConversionCastOp>();
1920 (void)applyPartialConversion(root, target, std::move(patterns));
1921 }
1922 // Fold cancelling cast chains and erase dead casts.
1923 xegpu::cleanupUnrealizedConversionCasts(root, existingCasts);
1924 xegpu::removeTemporaryLayoutAttrs(getOperation());
1925}
1926
1928 TypeConverter &typeConverter, Operation *topLevelOp) {
1929 // Pass through any type by default; more specific conversions registered
1930 // below override this for TensorDescType and (distributing) VectorType.
1931 typeConverter.addConversion([](Type type) -> Type { return type; });
1932 // For TensorDescType, drop the layout attribute if any.
1933 typeConverter.addConversion([](TensorDescType type) -> Type {
1934 if (type.getLayoutAttr()) {
1935 return type.dropLayouts();
1936 }
1937 return type;
1938 });
1939 // For VectorType, distribute based on the lane layout (1:1 shape-changing
1940 // conversion). Uses xegpu::addVectorTypeConversion with a pre-computed
1941 // map for SCF loop block args (see precomputeLoopBlockArgTypes for the
1942 // rationale).
1943 auto getSubShapeAndCount = [](VectorType vecTy,
1944 xegpu::DistributeLayoutAttr layout)
1945 -> std::pair<SmallVector<int64_t>, int> {
1946 auto distTyOrFailure = getDistVecTypeBasedOnLaneLayout(layout, vecTy);
1947 if (failed(distTyOrFailure))
1948 return {{}, 0};
1949 return {SmallVector<int64_t>(distTyOrFailure->getShape()), 1};
1950 };
1951 auto loopArgTypes =
1952 xegpu::precomputeLoopBlockArgTypes(topLevelOp, getSubShapeAndCount);
1953 xegpu::addVectorTypeConversion(typeConverter, getSubShapeAndCount,
1954 std::move(loopArgTypes));
1955}
1956
1958 TypeConverter &typeConverter, RewritePatternSet &patterns,
1959 ConversionTarget &target, Operation *topLevelOp) {
1960 populateXeGPUSgToLaneDistributeTypeConversions(typeConverter, topLevelOp);
1961 // CreateNdDescOp is legal only if its result type has no layout attribute.
1962 target.addDynamicallyLegalOp<xegpu::CreateNdDescOp>(
1963 [&](xegpu::CreateNdDescOp op) { return !op.getType().getLayoutAttr(); });
1964 // Any anchor XeGPU op is legal only if it has no anchor layout.
1965 target.addDynamicallyLegalDialect<xegpu::XeGPUDialect>([](Operation *op) {
1966 if (isa<xegpu::ConvertLayoutOp>(op))
1967 return false;
1968 auto anchorOp = dyn_cast<AnchorLayoutInterface>(op);
1969 if (!anchorOp)
1970 return true;
1971 return !anchorOp.getAnchorLayout();
1972 });
1973 // Arith constants are legal only if they have no temporary layout attribute.
1974 target.addDynamicallyLegalOp<arith::ConstantOp>(
1975 [=](arith::ConstantOp op) -> bool {
1976 // If the result type is not a vector, it's legal.
1977 if (!isa<VectorType>(op.getResult().getType()))
1978 return true;
1979 return !xegpu::getTemporaryLayout(dyn_cast<OpResult>(op.getResult()));
1980 });
1981 // In math and arith dialects, only handle elementwise ops with a single
1982 // result and with a result layout attribute.
1983 target.addDynamicallyLegalDialect<math::MathDialect, arith::ArithDialect>(
1984 [=](Operation *op) -> std::optional<bool> {
1985 // Only handle elementwise mappable ops
1987 return true;
1988 // Only handle ops with single vector result
1989 if (op->getNumResults() != 1)
1990 return true;
1991
1992 VectorType resultType =
1993 dyn_cast<VectorType>(op->getResult(0).getType());
1994 if (!resultType)
1995 return true;
1996
1997 // Check if all operands are vectors of the same shape
1998 for (Value operand : op->getOperands()) {
1999 VectorType operandType = dyn_cast<VectorType>(operand.getType());
2000 if (!operandType || operandType.getShape() != resultType.getShape()) {
2001 return true;
2002 }
2003 }
2004 return !xegpu::getTemporaryLayout(dyn_cast<OpResult>(op->getResult(0)));
2005 });
2006 // vector::ReductionOp is legal only if its source has no distribute layout
2007 // attribute.
2008 target.addDynamicallyLegalOp<vector::ReductionOp>(
2009 [=](vector::ReductionOp op) -> bool {
2010 auto layout = xegpu::getDistributeLayoutAttr(op.getVector());
2011 return !layout;
2012 });
2013 // vector::MultiDimReductionOp op legality.
2014 target.addDynamicallyLegalOp<vector::MultiDimReductionOp>(
2015 [=](vector::MultiDimReductionOp op) -> bool {
2016 return !isValidSubgroupMultiReductionOp(op);
2017 });
2018 target.addDynamicallyLegalOp<vector::CreateMaskOp, vector::ConstantMaskOp,
2019 vector::TransposeOp, vector::BitCastOp,
2020 vector::ShapeCastOp, vector::StepOp,
2021 vector::BroadcastOp>([=](Operation *op) -> bool {
2022 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2023 });
2024 target.addDynamicallyLegalOp<vector::ExtractOp>(
2025 [=](vector::ExtractOp op) -> bool {
2026 if (!isa<VectorType>(op.getType()))
2027 return true;
2028 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2029 });
2030 target.addDynamicallyLegalOp<vector::InsertOp>(
2031 [=](vector::InsertOp op) -> bool {
2032 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2033 });
2034 target.addDynamicallyLegalOp<vector::ExtractStridedSliceOp>(
2035 [=](vector::ExtractStridedSliceOp op) -> bool {
2036 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2037 });
2038 target.addDynamicallyLegalOp<vector::InsertStridedSliceOp>(
2039 [=](vector::InsertStridedSliceOp op) -> bool {
2040 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2041 });
2042 target.addDynamicallyLegalOp<vector::InterleaveOp, vector::DeinterleaveOp>(
2043 [=](Operation *op) -> bool {
2044 return !xegpu::getTemporaryLayout(op->getOpResult(0));
2045 });
2046 target.markUnknownOpDynamicallyLegal([](Operation *op) { return true; });
2047 patterns.add<
2048 SgToLaneCreateNdDesc, SgToLaneLoadNd, SgToLaneStoreNd, SgToLaneDpas,
2049 SgToLaneElementWise, SgToLaneArithConstant, SgToLanePrefetchNd,
2050 SgToLaneLoadGather, SgToLaneStoreScatter, SgToLaneVectorReduction,
2051 SgToLaneMultiDimReduction, SgToLaneVectorExtract, SgToLaneVectorInsert,
2052 SgToLaneVectorExtractStridedSlice, SgToLaneVectorInsertStridedSlice,
2053 SgToLaneLoadMatrix, SgToLaneStoreMatrix, SgToLaneConvertLayout,
2054 SgToLaneVectorTranspose, SgToLaneVectorBitcast, SgToLaneVectorStep,
2055 SgToLaneVectorShapeCast, SgToLaneBroadcast,
2056 SgToLaneCreateMask<vector::CreateMaskOp>,
2057 SgToLaneCreateMask<vector::ConstantMaskOp>, SgToLaneVectorDeinterleave,
2058 SgToLaneVectorInterleave, SgToLaneDpasMx>(typeConverter,
2059 patterns.getContext());
2060}
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:537
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:822
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:384
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:283
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...
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.