MLIR 24.0.0git
XeGPULayoutImpl.cpp
Go to the documentation of this file.
1//===---- XeGPULayoutImpl.cpp - MLIR Utilities for XeGPUOps
2//------------------===//
3//
4// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements layout utility functions for XeGPU dialect
11// transformation.
12//
13//===----------------------------------------------------------------------===//
14
23#include "mlir/IR/Builders.h"
24#include "mlir/IR/Operation.h"
25#include "mlir/IR/ValueRange.h"
30#include "llvm/ADT/PostOrderIterator.h"
31#include "llvm/Support/FormatVariadic.h"
32#include <cstdint>
33#include <numeric>
34
35using namespace mlir;
36
40 out.reserve(attrs.size());
41
42 for (auto attr : attrs) {
43 if (auto dist = dyn_cast<xegpu::DistributeLayoutAttr>(attr.getValue())) {
44 auto newLayout = dist.dropSgLayoutAndData();
45 if (newLayout)
46 out.emplace_back(attr.getName(), newLayout);
47 } else {
48 out.push_back(attr);
49 }
50 }
51
52 return out;
53}
54
58 out.reserve(attrs.size());
59
60 for (auto attr : attrs) {
61 if (auto dist = dyn_cast<xegpu::DistributeLayoutAttr>(attr.getValue())) {
62 auto newLayout = dist.dropInstData();
63 if (newLayout)
64 out.emplace_back(attr.getName(), newLayout);
65 } else {
66 out.push_back(attr);
67 }
68 }
69
70 return out;
71}
72
73// Sets the layout on a TensorDesc value by updating its type to include
74// the given layout, if the type does not already have a layout attached.
75static void setTensorDescLayout(Value val, xegpu::DistributeLayoutAttr layout) {
76 auto tensorDescTy = dyn_cast<xegpu::TensorDescType>(val.getType());
77 if (!tensorDescTy || tensorDescTy.getLayoutAttr())
78 return;
79 auto typeWithLayout = xegpu::TensorDescType::get(
80 tensorDescTy.getContext(), tensorDescTy.getShape(),
81 tensorDescTy.getElementType(), tensorDescTy.getEncoding(), layout);
82 val.setType(typeWithLayout);
83}
84
85// the walkRegionBackward() is a recursive function
86// the input rootOp is the function operation, which is also a region op.
87// it recursively processes the region op in reverse topological order.
88static void walkRegionBackward(Region &region,
90
91 // Use post-order traversal to process blocks in reverse topological order.
92 // This ensures that use blocks are visited before def blocks, which is
93 // required for backward layout propagation.
94 if (region.empty())
95 return;
96 llvm::ReversePostOrderTraversal<Region *> rpot(&region);
97 SmallVector<Block *> blocks(rpot.begin(), rpot.end());
98 for (Block *block : llvm::reverse(blocks)) {
99 // ops: back -> front
100 for (Operation &op : llvm::reverse(*block)) {
101 // make sure we first visit inside the region op (so yield op first)
102 // and then move to region op itself
103 // Regions are iterated in forward order so that for multi-region ops
104 // like scf.while, earlier regions (e.g., "before/cond") are processed
105 // first. This ensures that when a later region's terminator (e.g., "do"
106 // yield) needs the layout of an earlier region's block args, those
107 // layouts are already available from use points.
108 for (Region &nested : op.getRegions())
109 walkRegionBackward(nested, visit);
110
111 visit(&op);
112 }
113 }
114}
115
116static xegpu::DistributeLayoutAttr getLayoutFromUsePoints(Value result) {
117 xegpu::DistributeLayoutAttr layout = nullptr;
118 for (OpOperand &use : result.getUses()) {
119 if (auto tmpLayout = xegpu::getDistributeLayoutAttr(use)) {
120 if (!layout)
121 layout = tmpLayout;
122 break;
123 }
124 }
125 return layout;
126}
127
128// Returns true if `op` is safe and cheap to clone (no side effects, no
129// regions, and all operands are themselves trivially rematerializable, e.g.
130// block-arg-free pure value generators such as `vector.step`, splat
131// `arith.constant`, or `vector.create_mask` whose operands are constants).
133 if (!op || op->getNumRegions() != 0)
134 return false;
135 if (!isMemoryEffectFree(op))
136 return false;
137 for (Value v : op->getOperands()) {
138 Operation *defOp = v.getDefiningOp();
139 if (!defOp)
140 return false;
141 if (!isTriviallyRematerializable(defOp))
142 return false;
143 }
144 return true;
145}
146
147// For regular operations: First the result layouts are propagated from uses.
148// Then the result layouts are propagated to uses (operands).
150 if (op->getNumResults() == 0)
151 return;
152 if (op->getNumResults() > 1 && !isa<vector::DeinterleaveOp>(op))
153 return;
154 OpResult result = op->getResult(0);
155 xegpu::DistributeLayoutAttr resLayout = getLayoutFromUsePoints(result);
156 Type resultType = result.getType();
157
158 if (!resLayout)
159 return;
160
161 // Recover layout for TensorDesc type results by updating the type to include
162 // the layout. For vector type
163 if (isa<xegpu::TensorDescType>(resultType))
164 setTensorDescLayout(result, resLayout);
165
166 // Recover layout for vector type results, or for multi-reduction ops which
167 // may reduce to a scalar that still needs a layout.
168 if (isa<VectorType>(resultType) || isa<vector::MultiDimReductionOp>(op))
170
171 if (isa<vector::DeinterleaveOp>(op))
172 xegpu::setTemporaryLayout(op->getResult(1), resLayout);
173
174 for (OpOperand &opr : op->getOpOperands()) {
175 xegpu::DistributeLayoutAttr operandLayout =
177 if (isa<VectorType>(opr.get().getType()) && operandLayout)
178 xegpu::setTemporaryLayout(opr, operandLayout);
179 }
180}
181
182// Propagate layout from region op results and sibling region block args
183// to yield/condition operands. For each successor of this terminator:
184// - Parent successor: propagate from parent op's result layouts (use points).
185// - Region successor: propagate from target region's block arg layouts (use
186// points), e.g., scf.yield in "after/do" region propagates to "before/cond"
187// block args.
189 mlir::RegionBranchTerminatorOpInterface yieldOp) {
190 auto regionBranchOp =
191 dyn_cast<RegionBranchOpInterface>(yieldOp->getParentOp());
192 if (!regionBranchOp)
193 return;
194
196 SmallVector<Attribute> operandAttrs(yieldOp->getNumOperands(), nullptr);
197 yieldOp.getSuccessorRegions(operandAttrs, successors);
198
199 for (const RegionSuccessor &successor : successors) {
200 OperandRange succOps = yieldOp.getSuccessorOperands(successor);
201 if (succOps.empty())
202 continue;
203 unsigned beginIdx = succOps.getBeginOperandIndex();
204 ValueRange successorInputs = regionBranchOp.getSuccessorInputs(successor);
205 unsigned count = std::min<unsigned>(succOps.size(), successorInputs.size());
206
207 for (unsigned i = 0; i < count; ++i) {
208 xegpu::DistributeLayoutAttr layout;
209 if (successor.isOperation()) {
210 // For parent successor, get layout from external use points of the
211 // parent op's results.
212 auto regionResult = regionBranchOp->getResult(i);
213 layout = getLayoutFromUsePoints(regionResult);
214 if (layout) {
215 // set layout for the region op, like scf.loop
216 xegpu::setTemporaryLayout(regionResult, layout);
217 if (isa<xegpu::TensorDescType>(regionResult.getType()))
218 setTensorDescLayout(regionResult, layout);
219 }
220 } else {
221 // For region successor, get layout from the target region's block
222 // arg use points (e.g., "before/cond" region args for scf.while
223 // "after/do" yield).
224 layout = getLayoutFromUsePoints(successorInputs[i]);
225 }
226 if (!layout)
227 continue;
228 auto operandType = succOps[i].getType();
229 if (isa<VectorType>(operandType) ||
230 dyn_cast<xegpu::TensorDescType>(operandType))
231 // recover layout for yield op operands
232 xegpu::setTemporaryLayout(yieldOp->getOpOperand(beginIdx + i), layout);
233 }
234 }
235}
236
237/// Assign a layout to a region op's results (e.g. scf.for) using the layout of
238/// the terminator operands that the region forwards to them. For each operand a
239/// terminator (e.g. scf.yield) forwards to a successor input, if that input is
240/// a region op result, the operand's layout is written onto the result.
241/// clang-format off
242/// Example: scf.for ... iter_args(...) -> (out types) {
243/// ...
244/// scf.yield ... : (yield types)
245/// }
246/// clang-format on
247/// Having a layout on the region op result lets a later step attach a
248/// convert_layout as a use to resolve the region op's no-use case.
249/// Block-argument successors are left untouched.
251 mlir::RegionBranchTerminatorOpInterface terminator,
252 xegpu::GetLayoutFnTy getLayoutOfValue) {
253 // Only process if the terminator is inside a region branch op.
254 auto branchOp = dyn_cast<RegionBranchOpInterface>(terminator->getParentOp());
255 if (!branchOp)
256 return success();
257
259 branchOp.getSuccessorOperandInputMapping(mapping,
260 RegionBranchPoint(terminator));
261 for (const auto &[successorOperand, successorInputs] : mapping) {
262 for (Value successorInput : successorInputs) {
263 Type inputType = successorInput.getType();
264 // We only need to operate on vector types.
265 if (!isa<VectorType>(inputType))
266 continue;
267 xegpu::DistributeLayoutAttr successorOperandLayout =
268 getLayoutOfValue(successorOperand->get());
269
270 // The forwarded operand must carry a layout to propagate.
271 if (!successorOperandLayout)
272 return failure();
273 // Assign the yield operand's layout to the region op result it feeds.
274 if (auto result = dyn_cast<OpResult>(successorInput))
275 xegpu::setDistributeLayoutAttr(result, successorOperandLayout);
276 }
277 }
278 return success();
279}
280
281// Propagate layout from region arguments to region op's init operands. This
282// sets the temporary layout for region arguments and init operands.
283LogicalResult
284xegpu::propagateRegionArgsToInits(mlir::RegionBranchOpInterface regionOp,
285 xegpu::GetLayoutFnTy getLayoutOfValue) {
286 // Iterate all regions of the region op. For each block argument that has a
287 // layout (obtained via `getLayoutOfValue`), trace back to find the
288 // corresponding init operand of the regionOp and set the layout on it.
289 // This works generically for scf.for, scf.while, and other
290 // RegionBranchOpInterface ops.
291 for (Region &region : regionOp->getRegions()) {
292 RegionSuccessor regionSuccessor(&region);
293 // Use getSuccessorInputs to get the block arguments that correspond to
294 // predecessor operands. This correctly handles ops like scf.for where
295 // the induction variable is a block arg but not a successor input.
296 ValueRange successorInputs = regionOp.getSuccessorInputs(regionSuccessor);
297 for (auto [inputIdx, regionArg] : llvm::enumerate(successorInputs)) {
298 auto layout = getLayoutOfValue(regionArg);
299 if (!layout)
300 continue;
301
302 // Recover layout for tensor_desc block args by updating the type.
303 if (isa<xegpu::TensorDescType>(regionArg.getType()))
304 setTensorDescLayout(regionArg, layout);
305
306 // Recover layout for region op operands, like scf.for's init operands.
307 // Find all predecessor values that flow into this block argument.
308 SmallVector<Value> predValues;
309 regionOp.getPredecessorValues(regionSuccessor, inputIdx, predValues);
310 for (Value predVal : predValues) {
311 // Match predecessor value to an operand of the regionOp.
312 for (OpOperand &operand : regionOp->getOpOperands()) {
313 if (operand.get() == predVal)
314 xegpu::setTemporaryLayout(operand, layout);
315 }
316 }
317 }
318 }
319 return success();
320}
321
322// Prerequisite for Layout Recovery
323// It relies on the following invariant:
324// 1. there is no layout conflict between different uses of the same definition.
325// 2. each definition has a well-defined layout requirement at its use point.
326// - Every definition must have at least one use that appears after it in
327// topological order.
328// - TODO: If a definition has no such use (e.g., a loop result or region
329// output), an explicit convert_layout operation is inserted to create a
330// use.
331// - Only the result of convert_layout is permitted to have no subsequent
332// use.
333//
334// The recovery proceeds by scanning the operation in reverse topological order
335// as follows:
336// For regular operations: First the result layouts are propagated from uses.
337// Then the result layouts are propagated to operands.
338//
339// For region operations (e.g., loops):
340// - When backward propagation reaches a region op, it sets the layout of
341// the region op’s results according to use points like regular ops.
342// - Then, the result layouts (such as a loop output) are propagated to
343// their corresponding operands in the yield.
344// - When backward propagation reaches the first operation inside the
345// region, the pass examines the region op’s initialization list,
346// propagating from region arguments to the corresponding initialization
347// operands.
348// - This ensures that layouts are consistently propagated
349// across region boundaries while preserving a single well-defined use for
350// each definition at the region-op level.
352 auto processFunc = [&](Region &body, StringRef funcName) {
353 walkRegionBackward(body, [&](Operation *op) {
354 if (auto regionOp = dyn_cast<mlir::RegionBranchOpInterface>(op)) {
357 } else if (auto yieldOp =
358 dyn_cast<mlir::RegionBranchTerminatorOpInterface>(op)) {
360 } else if (!dyn_cast<xegpu::AnchorLayoutInterface>(op)) {
362 }
363 });
364 };
366 rootOp->walk([&](func::FuncOp func) {
367 processFunc(func.getBody(), func.getSymName());
368 });
369 rootOp->walk([&](gpu::GPUFuncOp func) {
370 processFunc(func.getBody(), func.getName());
371 });
372
373 return true;
374}
375
376template <typename T, typename>
377void xegpu::removeLayoutAttr(const T &operandOrResult) {
378 Operation *owner = operandOrResult.getOwner();
379 std::string name = xegpu::getTemporaryLayoutName(operandOrResult);
380 if (owner->hasAttrOfType<DistributeLayoutAttr>(name))
381 owner->removeAttr(name);
382}
383
384// Explicit instantiation for OpResult
385template void
387
388// Explicit instantiation for OpOperand
389template void
391
393 op->walk([&](Operation *nestOp) {
394 // Remove all attributes of DistributeLayoutAttr type
395 SmallVector<StringAttr> attrsToRemove;
396 for (auto namedAttr : nestOp->getAttrs()) {
397 if (isa<DistributeLayoutAttr>(namedAttr.getValue()))
398 attrsToRemove.push_back(namedAttr.getName());
399 }
400 for (auto attrName : attrsToRemove)
401 nestOp->removeAttr(attrName);
402 });
403}
404
406 op->walk([&](Operation *nestOp) {
407 SmallVector<StringAttr> attrsToRemove;
408 for (auto namedAttr : nestOp->getDiscardableAttrs()) {
409 if (isa<xegpu::DistributeLayoutAttr>(namedAttr.getValue()))
410 attrsToRemove.push_back(namedAttr.getName());
411 }
412 for (auto attrName : attrsToRemove)
413 nestOp->removeDiscardableAttr(attrName);
414 });
415}
416
417/// Returns true if every dimension of `shape` except the innermost
418/// `numInnerDims` is a unit (size-1) dimension.
419[[maybe_unused]] static bool leadingDimsAreUnit(ArrayRef<int64_t> shape,
420 int numInnerDims) {
421 int numLeading = static_cast<int>(shape.size()) - numInnerDims;
422 if (numLeading <= 0)
423 return true;
424 return llvm::all_of(shape.take_front(numLeading),
425 [](int64_t dim) { return dim == 1; });
426}
427
428static xegpu::LayoutAttr buildInstDataLayoutWithLane(
429 mlir::MLIRContext *context, ArrayRef<int64_t> instData,
430 ArrayRef<int64_t> laneLayout, ArrayRef<int64_t> laneData,
431 DenseI32ArrayAttr orderAttr = nullptr) {
432 auto toI32Attr = [&](auto range) {
433 SmallVector<int32_t> v(range.begin(), range.end());
434 return DenseI32ArrayAttr::get(context, v);
435 };
436 return xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
437 /*sg_data=*/nullptr, toI32Attr(instData),
438 toI32Attr(laneLayout), toI32Attr(laneData),
439 orderAttr);
440}
441
443 ArrayRef<int64_t> laneLayout,
444 ArrayRef<int64_t> laneData) {
445 return !llvm::any_of(llvm::seq<int>(0, dataShape.size()), [&](int dim) {
446 return dataShape[dim] % (laneLayout[dim] * laneData[dim]) != 0;
447 });
448}
449
450static xegpu::LayoutAttr
452 ArrayRef<int64_t> laneData,
453 DenseI32ArrayAttr orderAttr = nullptr) {
454 auto toI32Attr = [&](auto range) {
455 SmallVector<int32_t> v(range.begin(), range.end());
456 return DenseI32ArrayAttr::get(context, v);
457 };
458 return xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
459 /*sg_data=*/nullptr,
460 /*inst_data=*/nullptr, toI32Attr(laneLayout),
461 toI32Attr(laneData), orderAttr);
462}
463
464static xegpu::LayoutAttr
466 ArrayRef<int64_t> sgData, ArrayRef<int64_t> instData,
467 ArrayRef<int64_t> laneLayout, ArrayRef<int64_t> laneData,
468 DenseI32ArrayAttr orderAttr = nullptr) {
469 auto toI32Attr = [&](auto range) {
470 SmallVector<int32_t> v(range.begin(), range.end());
471 return DenseI32ArrayAttr::get(context, v);
472 };
473 return xegpu::LayoutAttr::get(
474 context, sgLayout.empty() ? nullptr : toI32Attr(sgLayout),
475 sgData.empty() ? nullptr : toI32Attr(sgData),
476 instData.empty() ? nullptr : toI32Attr(instData),
477 laneLayout.empty() ? nullptr : toI32Attr(laneLayout),
478 laneData.empty() ? nullptr : toI32Attr(laneData), orderAttr);
479}
480
481static xegpu::LayoutAttr buildSgLayout(mlir::MLIRContext *context,
482 ArrayRef<int64_t> wgTileShape,
483 ArrayRef<int64_t> sgLayout,
484 int dimK = -1,
485 DenseI32ArrayAttr orderAttr = nullptr) {
486 SmallVector<int64_t> sgData(sgLayout.size());
487 for (int dim = 0; dim < (int)sgLayout.size(); ++dim) {
488 if (dim == dimK)
489 sgData[dim] = wgTileShape[dim];
490 else
491 sgData[dim] = wgTileShape[dim] / sgLayout[dim];
492 }
493 return buildLayout(context, sgLayout, sgData,
494 /*inst_data=*/{}, /*lane_layout=*/{},
495 /*lane_data=*/{}, /*order=*/nullptr);
496}
497
498/// Infers the source layout attribute for a broadcast operation given the
499/// result layout attribute, result shape, source shape.
500xegpu::DistributeLayoutAttr
501xegpu::inferBroadcastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
502 ArrayRef<int64_t> resShape,
503 ArrayRef<int64_t> srcShape) {
504
505 SmallVector<int64_t> bcastDims;
506 size_t dimDiff = resShape.size() - srcShape.size();
507 auto bcastSourceLayout = resLayout;
508
509 // Right-aligned source in result, look for stretched unit dims.
510 for (size_t i = dimDiff; i < resShape.size(); i++) {
511 if ((srcShape[i - dimDiff] == 1) && (resShape[i] != 1))
512 bcastDims.push_back(i);
513 }
514
515 // Case UnitDimStretch (e.g., 1x4 -> 4x4): the source layout data field must
516 // be 1.
517 if (!bcastDims.empty())
518 bcastSourceLayout = bcastSourceLayout.setUnitDimData(bcastDims);
519
520 // Case RankDiff:
521 if (dimDiff) {
522 SmallVector<int64_t> sliceDims;
523 bool isOuterDimDiffUnitDims = llvm::all_of(
524 resShape.take_front(dimDiff), [&](int64_t dim) { return dim == 1; });
525 if (dimDiff && bcastDims.size() == dimDiff && isOuterDimDiffUnitDims) {
526 // Case RankDiffInnerDims (e.g., 1x4 -> 1x16x4):
527 // slice the expanded inner dims
528 sliceDims.assign(bcastDims.begin(), bcastDims.end());
529 } else {
530 // Case RankDiffOuterDims (e.g., 1x4 -> 1x1x4):
531 // slice the outer dims
532 llvm::append_range(sliceDims, llvm::seq<int64_t>(0, dimDiff));
533 }
534 bcastSourceLayout = xegpu::SliceAttr::get(
535 resLayout.getContext(), bcastSourceLayout,
536 DenseI64ArrayAttr::get(resLayout.getContext(), sliceDims));
537 }
538 return bcastSourceLayout;
539}
540
541/// Infers the source layout attribute for a reduction operation given the
542/// result layout attribute and reduced dims.
543xegpu::DistributeLayoutAttr
544xegpu::inferMultiReductionSourceLayout(xegpu::DistributeLayoutAttr resLayout,
545 SmallVector<int64_t> reduceDims) {
546
547 assert(isa<xegpu::SliceAttr>(resLayout) &&
548 "reduction result layout must be slice layout");
549
550 xegpu::SliceAttr sliceLayout = dyn_cast<xegpu::SliceAttr>(resLayout);
551
552 assert((reduceDims == sliceLayout.getDims().asArrayRef()) &&
553 "reduction dims must match with slice dims");
554
555 return sliceLayout.getParent();
556}
557
558xegpu::DistributeLayoutAttr
559xegpu::inferReductionSourceLayout(xegpu::DistributeLayoutAttr resLayout) {
560 return xegpu::inferMultiReductionSourceLayout(resLayout, {0});
561}
562
563/// Infers the source layout attribute for a transpose operation given the
564/// result layout attribute and permutation.
565///
566/// vector.transpose semantics is `result[i] = source[permutation[i]]`, so
567/// `result_layout[i] = source_layout[permutation[i]]`. To recover the source
568/// layout from the result layout we must apply the inverse permutation.
569xegpu::DistributeLayoutAttr
570xegpu::inferTransposeSourceLayout(xegpu::DistributeLayoutAttr resLayout,
571 ArrayRef<int64_t> permutation) {
573 invertPermutationVector(permutation);
574 return resLayout.transposeDims(inversePermutation);
575}
576
577/// Infers the source layout attribute for a bitcast operation given the
578/// result layout attribute, result element type bitwidth, and source element
579/// type bitwidth.
580xegpu::DistributeLayoutAttr
581xegpu::inferBitCastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
582 int resElemTyBitWidth, int srcElemTyBitWidth) {
583
584 SmallVector<int64_t> sgData = resLayout.getEffectiveSgDataAsInt();
585 SmallVector<int64_t> instData = resLayout.getEffectiveInstDataAsInt();
586 SmallVector<int64_t> laneData = resLayout.getEffectiveLaneDataAsInt();
587 size_t sgDataSize = sgData.size();
588 size_t instDataSize = instData.size();
589 size_t laneDataSize = laneData.size();
590 int64_t sgDataValue = -1;
591 int64_t instDataValue = -1;
592 int64_t laneDataValue = -1;
593 int64_t dim = resLayout.getRank() - 1;
594
595 if (srcElemTyBitWidth <= resElemTyBitWidth) {
596 int bitWidthRatio = resElemTyBitWidth / srcElemTyBitWidth;
597 if (sgDataSize)
598 sgDataValue = sgData.back() * bitWidthRatio;
599 if (instDataSize)
600 instDataValue = instData.back() * bitWidthRatio;
601 if (laneDataSize)
602 laneDataValue = laneData.back() * bitWidthRatio;
603 } else {
604 int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
605 if (sgDataSize) {
606 assert((sgData.back() % bitWidthRatio) == 0 &&
607 "sgData not divisible by bitWidthRatio");
608 sgDataValue = sgData.back() / bitWidthRatio;
609 }
610 if (instDataSize) {
611 assert((instData.back() % bitWidthRatio) == 0 &&
612 "instData not divisible by bitWidthRatio");
613 instDataValue = instData.back() / bitWidthRatio;
614 }
615 if (laneDataSize) {
616 assert((laneData.back() % bitWidthRatio) == 0 &&
617 "laneData not divisible by bitWidthRatio");
618 laneDataValue = laneData.back() / bitWidthRatio;
619 }
620 }
621
622 xegpu::DistributeLayoutAttr finalSrcLayout;
623 finalSrcLayout =
624 resLayout.setDimData(dim, sgDataValue, instDataValue, laneDataValue);
625
626 return finalSrcLayout;
627}
628
629/// Infers the source layout attribute for an interleave operation given the
630/// result layout attribute. Interleave doubles the size of the innermost
631/// dimension, so the layout inference is similar to bitcast where the source
632/// element type is larger than the result element type (ratio = 2).
633xegpu::DistributeLayoutAttr
634xegpu::inferInterleaveSourceLayout(xegpu::DistributeLayoutAttr resLayout) {
635
636 SmallVector<int64_t> sgData = resLayout.getEffectiveSgDataAsInt();
637 SmallVector<int64_t> instData = resLayout.getEffectiveInstDataAsInt();
638 SmallVector<int64_t> laneData = resLayout.getEffectiveLaneDataAsInt();
639 size_t sgDataSize = sgData.size();
640 size_t instDataSize = instData.size();
641 size_t laneDataSize = laneData.size();
642 int64_t sgDataValue = -1;
643 int64_t instDataValue = -1;
644 int64_t laneDataValue = -1;
645 int64_t dim = resLayout.getRank() - 1;
646
647 // Interleave doubles the innermost dimension, so we need to halve the
648 // layout values (similar to bitcast with ratio = 2)
649 constexpr int ratio = 2;
650 if (sgDataSize) {
651 assert((sgData.back() % ratio) == 0 &&
652 "sgData not divisible by interleave ratio");
653 sgDataValue = sgData.back() / ratio;
654 }
655 if (instDataSize) {
656 assert((instData.back() % ratio) == 0 &&
657 "instData not divisible by interleave ratio");
658 instDataValue = instData.back() / ratio;
659 }
660 if (laneDataSize) {
661 assert((laneData.back() % ratio) == 0 &&
662 "laneData not divisible by interleave ratio");
663 laneDataValue = laneData.back() / ratio;
664 }
665
666 return resLayout.setDimData(dim, sgDataValue, instDataValue, laneDataValue);
667}
668
669/// Infers the source layout attribute for a deinterleave operation given the
670/// result layout attribute. Deinterleave halves the size of the innermost
671/// dimension, so the layout inference is similar to bitcast where the source
672/// element type is smaller than the result element type (ratio = 2).
673xegpu::DistributeLayoutAttr
674xegpu::inferDeinterleaveSourceLayout(xegpu::DistributeLayoutAttr resLayout) {
675
676 SmallVector<int64_t> sgData = resLayout.getEffectiveSgDataAsInt();
677 SmallVector<int64_t> instData = resLayout.getEffectiveInstDataAsInt();
678 SmallVector<int64_t> laneData = resLayout.getEffectiveLaneDataAsInt();
679 size_t sgDataSize = sgData.size();
680 size_t instDataSize = instData.size();
681 size_t laneDataSize = laneData.size();
682 int64_t sgDataValue = -1;
683 int64_t instDataValue = -1;
684 int64_t laneDataValue = -1;
685 int64_t dim = resLayout.getRank() - 1;
686
687 // Deinterleave halves the innermost dimension, so we need to double the
688 // layout values (similar to bitcast with ratio = 2)
689 constexpr int ratio = 2;
690 if (sgDataSize)
691 sgDataValue = sgData.back() * ratio;
692 if (instDataSize)
693 instDataValue = instData.back() * ratio;
694 if (laneDataSize)
695 laneDataValue = laneData.back() * ratio;
696
697 return resLayout.setDimData(dim, sgDataValue, instDataValue, laneDataValue);
698}
699
700/// Infers the source layout attribute for an insert strided slice operation
701/// given the result layout attribute, result shape, and source shape. Removes
702/// leading dimensions from the result layout to match the source shape size.
703xegpu::DistributeLayoutAttr xegpu::inferInsertStridedSliceSourceLayout(
704 xegpu::DistributeLayoutAttr resLayout, ArrayRef<int64_t> resShape,
705 ArrayRef<int64_t> srcShape) {
706
707 int srcShapeSize = srcShape.size();
708 int resShapeSize = resShape.size();
709 int dimDiff = resShapeSize - srcShapeSize;
710
711 if (dimDiff > 0) {
712 // assert that the leading dimensions being sliced off are not distributed
713 // (i.e. sg_layout and lane_layout for those dimensions are all 1)
714 auto resSgLayout = resLayout.getEffectiveSgLayoutAsInt();
715 auto resLaneLayout = resLayout.getEffectiveLaneLayoutAsInt();
716 for (int i = 0; i < dimDiff; i++) {
717 assert((resSgLayout.size() == 0 || resSgLayout[i] == 1) &&
718 (resLaneLayout.size() == 0 || resLaneLayout[i] == 1) &&
719 "Leading dimensions being sliced off must not be distributed");
720 }
721 return resLayout.dropDims(llvm::to_vector(llvm::seq<int64_t>(0, dimDiff)));
722 }
723 return resLayout;
724}
725
726/// Infers the source layout attribute for an insert operation
727/// given the result layout attribute, result shape, and source shape. Removes
728/// leading dimensions from the result layout to match the source shape size.
729// TODO: add propagation support for insert op
730xegpu::DistributeLayoutAttr
731xegpu::inferInsertSourceLayout(xegpu::DistributeLayoutAttr resLayout,
732 ArrayRef<int64_t> resShape,
733 ArrayRef<int64_t> srcShape) {
734
735 int srcShapeSize = srcShape.size();
736 int resShapeSize = resShape.size();
737 int dimDiff = resShapeSize - srcShapeSize;
738
739 if (dimDiff > 0) {
740 // assert that the leading dimensions being sliced off are not distributed
741 // (i.e. sg_layout and lane_layout for those dimensions are all 1)
742 auto resSgLayout = resLayout.getEffectiveSgLayoutAsInt();
743 auto resLaneLayout = resLayout.getEffectiveLaneLayoutAsInt();
744 for (int i = 0; i < dimDiff; i++) {
745 assert((resSgLayout.size() == 0 || resSgLayout[i] == 1) &&
746 (resLaneLayout.size() == 0 || resLaneLayout[i] == 1) &&
747 "Leading dimensions being sliced off must not be distributed");
748 }
749 return resLayout.dropDims(llvm::to_vector(llvm::seq<int64_t>(0, dimDiff)));
750 }
751 return resLayout;
752}
753
754/// Infers the source layout attribute for extract operation
755/// given the result layout attribute, result shape, and source shape. Adds
756/// leading dimensions to the source layout to match the source shape size.
757// TODO: add layout attribute interface: expandDim() and use it here.
758// TODO: add propagation support for extract op
759xegpu::DistributeLayoutAttr
760xegpu::inferExtractSourceLayout(xegpu::DistributeLayoutAttr resLayout,
761 ArrayRef<int64_t> resShape,
762 ArrayRef<int64_t> srcShape) {
763
764 int srcShapeSize = srcShape.size();
765 int resShapeSize = resShape.size();
766 int dimDiff = srcShapeSize - resShapeSize;
767 auto context = resLayout.getContext();
768 // construct the source layout by adding unit dimensions to the front of
769 // result layout
770 if (dimDiff > 0) {
771 auto sgLayout = resLayout.getEffectiveSgLayoutAsInt();
772 auto sgData = resLayout.getEffectiveSgDataAsInt();
773 auto instData = resLayout.getEffectiveInstDataAsInt();
774 auto laneLayout = resLayout.getEffectiveLaneLayoutAsInt();
775 auto laneData = resLayout.getEffectiveLaneDataAsInt();
776 auto order = resLayout.getEffectiveOrderAsInt();
777
778 // Example: result shape is 3D with order [1, 2, 0], source shape is 5D
779 // (adding 2 leading dimensions). Expected source order: [3, 4, 2, 1, 0]
780 // Step 1: shift existing order by dimDiff: [1, 2, 0] -> [3, 4, 2]
781 // Step 2: append new leading dims in reverse (slowest first): [3, 4, 2, 1,
782 // 0]
783
784 // Shift existing dimension indices in order by dimDiff to account for the
785 // new leading dimensions being added to the source shape
786 for (auto &o : order)
787 o += dimDiff;
788
789 // Add unit dimensions to the front of non-empty layout vectors and append
790 // the new dimension indices to the order array in reverse (slowest
791 // dimension has the lowest index and appears last in the order array)
792 for (int i = 0; i < dimDiff; i++) {
793 if (!sgLayout.empty())
794 sgLayout.insert(sgLayout.begin(), 1);
795 if (!sgData.empty())
796 sgData.insert(sgData.begin(), 1);
797 if (!instData.empty())
798 instData.insert(instData.begin(), 1);
799 if (!laneLayout.empty())
800 laneLayout.insert(laneLayout.begin(), 1);
801 if (!laneData.empty())
802 laneData.insert(laneData.begin(), 1);
803 order.push_back(dimDiff - 1 - i);
804 }
805
807 context, SmallVector<int32_t>(order.begin(), order.end()));
808 if (!resLayout.getOrder())
809 orderAttr = nullptr;
810
811 return buildLayout(context, sgLayout, sgData, instData, laneLayout,
812 laneData, orderAttr);
813 }
814 return resLayout;
815}
816
817/// Infers the source layout attribute for a shape cast operation given the
818/// result layout attribute, result shape, and source shape.
819xegpu::DistributeLayoutAttr
820xegpu::inferShapeCastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
821 ArrayRef<int64_t> resShape,
822 ArrayRef<int64_t> srcShape) {
823
824 // There are three use cases:
825 // 1. expand dims of low-rank dimensions (e.g., 1D to 2D): to set up the
826 // tensor before broadcast
827 // 2. split dim of a high-rank dimension (e.g., 1D to 2D): to setup tensor
828 // for multi-stage reduction
829 // 3. combines all dims to a single dim and put in the innermost dim in 2d as
830 // [1, combinedData] or [combinedData]. Say, [2, 4, 8] -> [1, 64] or [64]
831 // Use cases are only supported after workgroup distribution,
832 // like cross-sg reduction saves multidimension data to
833 // 1D slm buffer, shapecast inserted by cse/canonicalization passes.
834
835 // Use case 1: Shapes only differ by expanding unit dimensions, for broadcast
836 SmallVector<int64_t> expandedUnitDims;
837
838 if (xegpu::matchUnitDimExpansion(srcShape, resShape, expandedUnitDims)) {
839 // create a slice layout for the source by removing the expanded unit dims
840 auto sliceDimsAttr = DenseI64ArrayAttr::get(
841 resLayout.getContext(), ArrayRef<int64_t>(expandedUnitDims));
842 auto srcLayout =
843 xegpu::SliceAttr::get(resLayout.getContext(), resLayout, sliceDimsAttr);
844 return srcLayout;
845 }
846
847 // Use case 2: Dim split from source to result, for multi-stage reduction
848 SmallVector<SmallVector<int64_t>> splitDimGroups;
849 if (xegpu::matchSplitDimExpansion(srcShape, resShape, splitDimGroups)) {
850 auto srcLayout = resLayout;
851 for (const auto &dimGroup : splitDimGroups)
852 srcLayout = srcLayout.collapseDims(dimGroup);
853
854 return srcLayout;
855 }
856
857 // Use case 3: General dim collapse, for cross-sg reduction to SLM and other
858 // shape casts where consecutive src dims fold into a single dst dim.
860 if (xegpu::matchDimCollapse(srcShape, resShape, collapseDims)) {
861 auto srcLayout = resLayout;
862 for (int64_t dstIdx = static_cast<int64_t>(collapseDims.size()) - 1;
863 dstIdx >= 0; --dstIdx) {
864 ArrayRef<int64_t> srcDims = collapseDims[dstIdx];
865 if (srcDims.empty()) {
866 srcLayout = srcLayout.dropDims({dstIdx});
867 continue;
868 }
869 if (srcDims.size() == 1)
870 continue;
871 SmallVector<int64_t> targetShape;
872 targetShape.reserve(srcDims.size());
873 for (int64_t d : srcDims)
874 targetShape.push_back(srcShape[d]);
875 srcLayout = srcLayout.expandDim(dstIdx, targetShape);
876 }
877 return srcLayout;
878 }
879 return nullptr;
880}
881
882//===----------------------------------------------------------------------===//
883// Forward layout inference (source layout -> result layout)
884//===----------------------------------------------------------------------===//
885
886/// Infers the result layout attribute for a transpose operation given the
887/// source layout attribute and permutation.
888///
889/// vector.transpose semantics is `result[i] = source[permutation[i]]`, so
890/// `result_layout[i] = source_layout[permutation[i]]`, which is exactly
891/// `srcLayout.transposeDims(permutation)`. This is the inverse of
892/// inferTransposeSourceLayout (which applies the inverse permutation).
893xegpu::DistributeLayoutAttr
894xegpu::inferTransposeResultLayout(xegpu::DistributeLayoutAttr srcLayout,
895 ArrayRef<int64_t> permutation) {
896 return srcLayout.transposeDims(permutation);
897}
898
899/// Infers the result layout attribute for a shape cast operation given the
900/// source layout attribute, source shape, and result shape. This is the
901/// inverse of inferShapeCastSourceLayout: a dim-split (src -> res) is undone by
902/// collapsing the split groups, and a dim-collapse (src -> res) is undone by
903/// expanding the collapsed groups. The unit-dim-expansion case is not inverted
904/// here because recovering which result dims are the expanded unit dims would
905/// require the SliceAttr the backward direction produces; such patterns return
906/// nullptr (leaving the result un-laid-out).
907xegpu::DistributeLayoutAttr
908xegpu::inferShapeCastResultLayout(xegpu::DistributeLayoutAttr srcLayout,
909 ArrayRef<int64_t> srcShape,
910 ArrayRef<int64_t> resShape) {
911 // Case: source dims were split into result dims (forward of use case 2 in
912 // inferShapeCastSourceLayout). Undo by expanding each source dim into its
913 // group of result dims.
914 SmallVector<SmallVector<int64_t>> splitDimGroups;
915 if (xegpu::matchSplitDimExpansion(srcShape, resShape, splitDimGroups)) {
916 auto resLayout = srcLayout;
917 // Process source dims from innermost to outermost so that expanding a dim
918 // does not shift the indices of dims not yet processed.
919 for (int64_t srcIdx = static_cast<int64_t>(splitDimGroups.size()) - 1;
920 srcIdx >= 0; --srcIdx) {
921 ArrayRef<int64_t> resDims = splitDimGroups[srcIdx];
922 if (resDims.size() <= 1)
923 continue;
924 SmallVector<int64_t> targetShape;
925 targetShape.reserve(resDims.size());
926 for (int64_t d : resDims)
927 targetShape.push_back(resShape[d]);
928 resLayout = resLayout.expandDim(srcIdx, targetShape);
929 }
930 return resLayout;
931 }
932
933 // Case: source dims were collapsed into result dims (forward of use case 3).
934 // Undo by collapsing each group of source dims into its single result dim.
936 if (xegpu::matchDimCollapse(srcShape, resShape, collapseDims)) {
937 auto resLayout = srcLayout;
938 // Process result dims from innermost to outermost so that collapsing a
939 // group does not shift the indices of groups not yet processed.
940 for (int64_t dstIdx = static_cast<int64_t>(collapseDims.size()) - 1;
941 dstIdx >= 0; --dstIdx) {
942 ArrayRef<int64_t> srcDims = collapseDims[dstIdx];
943 // A result dim with no backing source dims is a trailing/leading unit
944 // dim; its forward inference is ambiguous, so bail out.
945 if (srcDims.empty())
946 return nullptr;
947 if (srcDims.size() == 1)
948 continue;
949 resLayout = resLayout.collapseDims(llvm::to_vector(srcDims));
950 }
951 return resLayout;
952 }
953
954 return nullptr;
955}
956
957/// Infers the result layout attribute for a non-anchor operation from the
958/// layouts of its source operands. Forward counterpart of
959/// inferSourceLayoutFromResultForNonAnchorOp.
960xegpu::DistributeLayoutAttr xegpu::inferResultLayoutFromSourceForNonAnchorOp(
962 if (op->getNumResults() != 1)
963 return nullptr;
964
965 // For vector::TransposeOp, infer the result layout from the source layout.
966 if (auto transpose = dyn_cast<vector::TransposeOp>(op)) {
967 if (!operandLayouts[0])
968 return nullptr;
969 return xegpu::inferTransposeResultLayout(operandLayouts[0],
970 transpose.getPermutation());
971 }
972
973 // For vector::ShapeCastOp, infer the result layout from the source layout.
974 if (auto shapeCast = dyn_cast<vector::ShapeCastOp>(op)) {
975 if (!operandLayouts[0])
976 return nullptr;
978 operandLayouts[0], shapeCast.getSourceVectorType().getShape(),
979 shapeCast.getResultVectorType().getShape());
980 }
981
982 // For elementwise operations, all operands and the result share the same
983 // layout. Use the first operand that carries a layout.
985 for (xegpu::DistributeLayoutAttr layout : operandLayouts)
986 if (layout)
987 return layout;
988 return nullptr;
989 }
990
991 // TODO: add forward inference rules for the remaining ops; their result is
992 // left un-laid-out until then.
993 // - vector::BroadcastOp: the forward direction is under-determined. The
994 // backward rule (inferBroadcastSourceLayout) either sets broadcast dims to
995 // unit data (losing the original data on those dims) or wraps the result
996 // in a SliceAttr; neither is generally invertible from the source layout
997 // alone, so a forward rule must decide how to distribute the new/stretched
998 // dims.
999 // - vector::BitCastOp, vector::MultiDimReductionOp / vector::ReductionOp,
1000 // vector::InterleaveOp / vector::DeinterleaveOp, and the insert / extract
1001 // / strided-slice family.
1002 return nullptr;
1003}
1004
1005/// Infers the layout attribute for mask and offset operand for Chunked load
1006/// and store, given the anchor layout attribute for the value being load/store.
1007xegpu::DistributeLayoutAttr xegpu::inferMaskOffsetLayoutForScatterIO(
1008 xegpu::DistributeLayoutAttr payloadLayout, int chunkSize) {
1009 auto rank = payloadLayout.getRank();
1010 if (chunkSize > 1)
1011 return payloadLayout.dropDims(
1012 llvm::to_vector(llvm::seq<int64_t>(rank - 1, rank)));
1013 return payloadLayout;
1014}
1015
1016//===----------------------------------------------------------------------===//
1017// Layout derivation helpers: factorize sgCount into
1018// sg_layout candidates, then
1019// compute per-subgroup (sgData) and per-lane
1020// (lane_layout/lane_data/inst_data).
1021//===----------------------------------------------------------------------===//
1022
1024
1025/// Enumerates all ways to split `total` into `rank` factors whose product
1026/// equals `total`. Returns the list of all such factorizations.
1028 int64_t rank) {
1030 SmallVector<int64_t> current(rank, 0);
1031
1032 // Returns all divisors of `n` in ascending order.
1033 auto getDivisors = [](int64_t n) {
1035 for (int64_t i = 1; i * i <= n; ++i) {
1036 if (n % i == 0) {
1037 divs.push_back(i);
1038 if (i != n / i)
1039 divs.push_back(n / i);
1040 }
1041 }
1042 llvm::sort(divs);
1043 return divs;
1044 };
1045
1046 std::function<void(int64_t, int64_t)> generate = [&](int64_t dim,
1047 int64_t remaining) {
1048 if (dim == rank - 1) {
1049 current[dim] = remaining;
1050 results.push_back(LayoutRepresentation(current));
1051 return;
1052 }
1053 for (int64_t factor : getDivisors(remaining)) {
1054 current[dim] = factor;
1055 generate(dim + 1, remaining / factor);
1056 }
1057 };
1058
1059 generate(0, total);
1060 return results;
1061}
1062
1063// Computes all valid N-dimensional sg_layout candidates for the given
1064// sgCount, whose sgData (= wgShape / sgLayout):
1065// 1. Evenly divides wgShape (i.e., wgShape[d] % sgLayout[d] == 0).
1066// 2. Is a multiple of instData (i.e., sgData[d] % instData[d] == 0).
1067// Results are sorted by balance (smallest max-min spread first), with
1068// lexicographic order as a tiebreaker.
1069//
1070// Example (2D):
1071// wgShape = [128, 64], instData = [8, 16], sgCount = 32
1072// Returns: [[8,4], [16,2]], corresponding to sgData [16,16] and [8,32].
1075 int64_t sgCount) {
1076 int64_t rank = wgShape.size();
1077 assert(rank > 0 && "wgShape must be non-empty");
1078 assert(static_cast<int64_t>(instData.size()) == rank &&
1079 "instData rank must match wgShape rank");
1080
1081 // Step 1: Get all N-D factorizations of sgCount.
1082 auto allFactorizations = enumerateFactorizations(sgCount, rank);
1083
1084 // Step 2: Filter to keep only valid candidates.
1086 for (const auto &sgLayout : allFactorizations) {
1087 bool valid = true;
1088 for (int64_t dim = 0; dim < rank; ++dim) {
1089 if (wgShape[dim] % sgLayout[dim] != 0) {
1090 valid = false;
1091 break;
1092 }
1093 int64_t sgData = wgShape[dim] / sgLayout[dim];
1094 if (sgData % instData[dim] != 0) {
1095 valid = false;
1096 break;
1097 }
1098 }
1099 if (valid)
1100 candidates.push_back(sgLayout);
1101 }
1102
1103 // Step 3: Sort by balance (smallest max-min spread), then lexicographic.
1104 llvm::sort(candidates, [](const LayoutRepresentation &lhs,
1105 const LayoutRepresentation &rhs) {
1106 int64_t spreadLhs = *llvm::max_element(lhs) - *llvm::min_element(lhs);
1107 int64_t spreadRhs = *llvm::max_element(rhs) - *llvm::min_element(rhs);
1108 if (spreadLhs != spreadRhs)
1109 return spreadLhs < spreadRhs;
1110 return lhs < rhs;
1111 });
1112 return candidates;
1113}
1114
1115/// Helper function to compute inst_data vectors for DPAS operands A, B, and
1116/// C/D.
1117static std::optional<SmallVector<int64_t>> get2DBlockIOInstDataLayout(
1118 ArrayRef<int64_t> dataShape, Type elemTy,
1119 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
1120 bool transform = false, bool transpose = false) {
1121 int rank = dataShape.size();
1122 auto blockWHC =
1123 uArchInstruction->getBlockWidthHeightCount(elemTy, transform, transpose);
1124 if (!blockWHC)
1125 return std::nullopt;
1126 auto [bWidths, bHeights, bCounts] = blockWHC.value();
1127 // Compute inst_data from hardware block params. For Nd ops, the lane
1128 // factorization above (laneLayout / laneData) is rigid; inst_data must be
1129 // a multiple of lane_layout * lane_data on each dim (Category A
1130 // invariant).
1131 SmallVector<int64_t> instData(rank, 1);
1132 assert(rank >= 2 && "dataShape must be at least 2D for 2D-block IO");
1133 int instWidth =
1134 xegpu::getLargestDivisor(static_cast<int>(dataShape.back()), bWidths);
1135 int instHeight =
1136 xegpu::getLargestDivisor(static_cast<int>(dataShape[rank - 2]), bHeights);
1137 instData.back() = instWidth;
1138 instData[rank - 2] = instHeight;
1139
1140 return instData;
1141}
1142
1143/// Helper function to compute inst_data vectors for DPAS operands A, B, and
1144/// C/D. Look up the uArch table and search for the largest supported block size
1145/// that divides the data shape
1146static std::optional<std::tuple<SmallVector<int64_t>, SmallVector<int64_t>,
1149 VectorType aTy, VectorType bTy, VectorType cdTy,
1150 const xegpu::uArch::MMAInstructionInterface *uArchInstruction) {
1151
1152 // M dimension is the second-to-last dim of A (handles batch dims).
1153 const unsigned dataALen = aTy.getShape()[aTy.getRank() - 2];
1154 auto supportedALen = uArchInstruction->getSupportedM(aTy.getElementType());
1155 const int maxALen =
1156 xegpu::getLargestDivisor(dataALen, ArrayRef<unsigned>(supportedALen));
1157
1158 // N dimension is the last dim of B.
1159 const unsigned dataBLen = bTy.getShape().back();
1160 auto supportedBLen = uArchInstruction->getSupportedN(bTy.getElementType());
1161 const int maxBLen =
1162 xegpu::getLargestDivisor(dataBLen, ArrayRef<unsigned>(supportedBLen));
1163
1164 auto supportedCLen = uArchInstruction->getSupportedN(cdTy.getElementType());
1165 const int maxCLen =
1166 xegpu::getLargestDivisor(dataBLen, ArrayRef<unsigned>(supportedCLen));
1167 if (maxALen == -1 || maxBLen == -1 || maxCLen == -1)
1168 return std::nullopt;
1169
1170 auto supportedKLen = uArchInstruction->getSupportedK(aTy.getElementType());
1171 if (supportedKLen.empty())
1172 return std::nullopt;
1173 auto kDimSize = supportedKLen[0];
1174
1175 SmallVector<int64_t> instDataA(aTy.getRank(), 1);
1176 instDataA[aTy.getRank() - 2] = maxALen;
1177 instDataA[aTy.getRank() - 1] = kDimSize;
1178 SmallVector<int64_t> instDataB(bTy.getRank(), 1);
1179 instDataB[bTy.getRank() - 2] = kDimSize;
1180 instDataB[bTy.getRank() - 1] = maxBLen;
1181 SmallVector<int64_t> instDataCD(cdTy.getRank(), 1);
1182 instDataCD[cdTy.getRank() - 2] = maxALen;
1183 instDataCD[cdTy.getRank() - 1] = maxCLen;
1184 return std::make_tuple(instDataA, instDataB, instDataCD);
1185}
1186
1187/// Computes lane_layout and lane_data for scatter-style store anchor layouts
1188/// (store scatter, store matrix). Lanes and the per-lane vector both live on
1189/// the innermost dim:
1190/// - laneLayout[innermost] = min(subgroupSize, srcShape[innermost])
1191/// - laneData[innermost] = min(srcShape[innermost] / laneLayout[innermost],
1192/// maxChunkSize)
1193/// All other entries are 1.
1194static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1196 int64_t subgroupSize, int64_t maxChunkSize) {
1197 int64_t rank = instShape.size();
1198 SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
1199 int64_t innermost = rank - 1;
1200 laneLayout[innermost] = std::min(subgroupSize, instShape[innermost]);
1201 laneData[innermost] =
1202 std::min(instShape[innermost] / laneLayout[innermost], maxChunkSize);
1203 return {laneLayout, laneData};
1204}
1205
1206// Computes the per-lane layout and data for a 2D block load/store/prefetch:
1207// lanes are spread across the subgroup along the last dim (or rank-2 if
1208// transposed), and laneData packs sub-bitwidth elements along the packing dim.
1209static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1211 int64_t subgroupSize, int64_t bitwidth,
1212 int64_t packingSize, bool transform = false) {
1213 int64_t rank = instShape.size();
1214 SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
1215 int kDim = transform ? rank - 2 : rank - 1;
1216 unsigned vnniFactor = packingSize / bitwidth;
1217 laneData[kDim] = bitwidth < packingSize ? vnniFactor : 1;
1218 laneLayout.back() =
1219 std::min(subgroupSize, instShape.back() / laneData.back());
1220
1221 // assert that the lane layout and data fit in the inst shape
1222 for (int64_t i = 0; i < rank; ++i) {
1223 int64_t laneProduct = laneLayout[i] * laneData[i];
1224 assert(instShape[i] % laneProduct == 0 &&
1225 "lane_layout * lane_data must evenly divide the inst shape");
1226 (void)laneProduct;
1227 }
1228 return {laneLayout, laneData};
1229}
1230
1231/// Computes the (lane_layout, lane_data) for a multi-reduction's source layout.
1232/// Only the innermost two dims are distributed; leading dims are assumed unit.
1233/// `subgroupSize` lanes go on one dim; up to `maxReduceVectorSize` elements are
1234/// packed into lane_data on the other. To minimize cross-lane reduction, lanes
1235/// are spread across a non-reduction dim when possible so the reduction happens
1236/// within a lane. inst_data is the element-wise product lane_layout *
1237/// lane_data.
1238///
1239/// e.g. with srcShape=[32, 128], subgroupSize=16, maxReduceVectorSize=2:
1240/// - Switch: reductionDims=[1] and consumerReductionDims=[] -> lanes move
1241/// to the non-reduction dim 0: lane_layout=[16, 1], lane_data=[1, 2].
1242/// - Default: reductionDims=[0, 1] (both reduced) -> lanes stay on the
1243/// innermost dim: lane_layout=[1, 16], lane_data=[2, 1].
1244static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1246 ArrayRef<int64_t> reductionDims,
1247 int subgroupSize, int64_t maxReduceVectorSize,
1248 bool verticalLaneLayout = false) {
1249 int srcRank = srcShape.size();
1250 SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
1251
1252 int innermost = srcRank - 1;
1253 int secondInnermost = srcRank - 2;
1254
1255 if (verticalLaneLayout && secondInnermost >= 0) {
1256 std::swap(innermost, secondInnermost);
1257 }
1258 int laneDim = innermost;
1259 int vectorDim = secondInnermost; // negative for rank 1
1260
1261 laneLayout[laneDim] =
1262 std::min(static_cast<int64_t>(subgroupSize), srcShape[laneDim]);
1263 if (vectorDim >= 0)
1264 laneData[vectorDim] = std::min(maxReduceVectorSize, srcShape[vectorDim]);
1265
1266 return {laneLayout, laneData};
1267}
1268
1269//===----------------------------------------------------------------------===//
1270// Result/anchor-layout setup. Each op category derives lane_layout/lane_data
1271// (and inst_data / sgData) differently. Two things vary across ops:
1272//
1273// * Consumer dependence: consumer-driven ops prefer the layout requested by
1274// their downstream uses and fall back to uArch defaults only when it is
1275// absent/invalid; sinks (StoreNd, PrefetchNd) have no consumer and always
1276// pick their own layout from uArch.
1277//
1278// * Derivation direction between inst_data and lane_layout/lane_data. Both
1279// obey the invariant inst_data = k * lane_layout * lane_data, where `k` is
1280// a per-dim integer >= 1 giving how many times each lane repeats its
1281// access to cover one instruction's data tile (k == 1 means one lane
1282// position per element; k > 1 means the instruction loads/stores several
1283// elements per lane along that dim). Ops solve this invariant from
1284// opposite ends:
1285// - Rigid-lane ops (Nd block IO, DPAS): hardware fixes lane_layout /
1286// lane_data first, then inst_data is built as a multiple of their
1287// product (using get2DBlockIOInstDataLayout / getDpasInstDataLayouts).
1288// - inst_data-first ops (scatter load): take inst_data from the consumer
1289// and derive lane_layout/lane_data underneath it.
1290//
1291// - DPAS (+DPAS_MX) : rigid lanes — inst_data from HW block dims; A/B/C/D
1292// lanes/data follow each operand's matmul role; DPAS_MX
1293// additionally lays out the scale operand.
1294// - LoadNd : consumer-driven, rigid lanes — honors the consumer's
1295// inst_data / lane / sg_layout (incl. transpose & VNNI
1296// packing) when it satisfies uArch block constraints,
1297// else falls back to the default 2D-block scheme (lanes
1298// on the last dim, rank-2 if transposed). The fallback
1299// picks the LARGEST uArch block that divides the data
1300// shape, so the resulting inst_data block can be bigger
1301// than what the consumer asked for (fewer, wider
1302// loads).
1303// - StoreNd/PrefetchNd: data sinks, no consumer, rigid lanes — pick the
1304// 2D-block layout directly from uArch (no VNNI
1305// packing).
1306// - Load (scatter) : load_gather / load_matrix, consumer-driven,
1307// inst_data-first — reuse the consumer's inst_data and
1308// derive lane_layout/lane_data, else default to lanes +
1309// per-lane chunk on the innermost dim (chunk capped by
1310// maxChunkSize).
1311// - Store (scatter) : store_scatter / store_matrix — same scatter scheme,
1312// but always self-derived from the scatter default.
1313// - Reduction : (multi_)reduction, consumer-driven — distribute the
1314// inner two dims, with lanes on the innermost dim by
1315// default (reducing across lanes) and switched to a
1316// non-reduction dim only when that keeps the reduction
1317// within a lane. Reuses the consumer's slice layout
1318// when it slices exactly the reduction dims, otherwise
1319// re-derives. See setupMultiReductionResultLayout for
1320// the exact switch condition and worked examples.
1321// - BitCast/Interleave: scale the innermost data field by the bitwidth /
1322// interleave ratio so the source layout divides back
1323// out.
1324// - InsertStridedSlice: clamp lane_data per dim to fit the inserted slice
1325// (Lane kind only; sg/inst layouts unsupported).
1326//===----------------------------------------------------------------------===//
1327
1328/// Helper function to set up subgroup layouts for DPAS operands A, B, and
1329/// C/D. Compute subgroup layout candidates based on wgtile and instData, and
1330/// then pick the best one that satisfies all operands and the consumer (if
1331/// specified).
1332static std::optional<
1333 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1334 xegpu::DistributeLayoutAttr>>
1336 mlir::MLIRContext *context, VectorType aTy, VectorType bTy, VectorType cdTy,
1337 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1339 instDataVecs) {
1340 auto [instDataA, instDataB, instDataCD] = instDataVecs;
1341
1342 std::optional<LayoutRepresentation> consumerSgLayout = std::nullopt;
1343 if (consumerLayout && consumerLayout.isForWorkgroup()) {
1344 consumerSgLayout = consumerLayout.getEffectiveSgLayoutAsInt();
1345 }
1346
1347 // Get all valid layouts for A, B and C/D operands
1348 auto layoutsA = getSgLayoutCandidates(aTy.getShape(), instDataA, numSg);
1349 auto layoutsB = getSgLayoutCandidates(bTy.getShape(), instDataB, numSg);
1350 auto layoutsCD = getSgLayoutCandidates(cdTy.getShape(), instDataCD, numSg);
1351 if (layoutsA.empty() || layoutsB.empty() || layoutsCD.empty())
1352 return std::nullopt;
1353
1354 // Pick the best subgroup layout
1355 std::optional<LayoutRepresentation> bestPick;
1356 auto checkAlignedSgDataAB = [&](const LayoutRepresentation &sgLayout) {
1357 return aTy.getShape().back() / sgLayout[1] ==
1358 bTy.getShape().front() / sgLayout[0];
1359 };
1360 for (auto &sgLayout : layoutsB) {
1361 if (llvm::is_contained(layoutsA, sgLayout) &&
1362 llvm::is_contained(layoutsCD, sgLayout)) {
1363 if (!checkAlignedSgDataAB(sgLayout))
1364 continue;
1365 // Is in (A and B and CD) and matches consumer -> best pick
1366 if (consumerSgLayout.has_value() && sgLayout == *consumerSgLayout) {
1367 bestPick = sgLayout;
1368 break;
1369 }
1370 // Is in (A and B and CD) layoutsB is ordered from most
1371 // balanced to least. So the first one we see is the most balanced one,
1372 // remember it and later only update if there is one that matches the
1373 // consumer.
1374 if (!bestPick)
1375 bestPick = sgLayout;
1376 }
1377 }
1378 if (!bestPick)
1379 return std::nullopt;
1380
1381 const auto &picked = *bestPick;
1382
1383 auto dpasALayout = buildSgLayout(context, aTy.getShape(), picked,
1384 /*dimK=*/aTy.getRank() - 1);
1385 auto dpasBLayout = buildSgLayout(context, bTy.getShape(), picked,
1386 /*dimK=*/bTy.getRank() - 2);
1387 auto dpasCDLayout = buildSgLayout(context, cdTy.getShape(), picked);
1388 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout);
1389}
1390
1391/// Sets up the anchor layouts for dpas operands (A, B, and C/D).
1392/// The numSg and consumerLayout (optional) are only used by sg layout
1393/// creation.
1394std::optional<
1395 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1396 xegpu::DistributeLayoutAttr>>
1397xegpu::setupDpasLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
1398 VectorType bTy, VectorType cdTy,
1399 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1400 const xegpu::uArch::uArch *uArch) {
1401 auto context = aTy.getContext();
1402 const auto *uArchInstruction =
1403 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
1405 if (!uArchInstruction)
1406 return std::nullopt;
1407 auto subgroupSize = uArch->getSubgroupSize();
1408
1409 auto [laneLayoutA, laneDataA] = compute2DBlockIOLaneLayoutAndData(
1410 aTy.getShape(), subgroupSize,
1411 aTy.getElementType().getIntOrFloatBitWidth(),
1412 uArchInstruction->getPackedFormatBitSizeA());
1413 auto [laneLayoutB, laneDataB] = compute2DBlockIOLaneLayoutAndData(
1414 bTy.getShape(), subgroupSize,
1415 bTy.getElementType().getIntOrFloatBitWidth(),
1416 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
1417 auto [laneLayoutCD, laneDataCD] = compute2DBlockIOLaneLayoutAndData(
1418 cdTy.getShape(), subgroupSize,
1419 cdTy.getElementType().getIntOrFloatBitWidth(),
1420 cdTy.getElementType().getIntOrFloatBitWidth());
1421
1422 auto instDataVecs = getDpasInstDataLayouts(aTy, bTy, cdTy, uArchInstruction);
1423 if (!instDataVecs)
1424 return std::nullopt;
1425
1426 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1427 assert(numSg > 0 &&
1428 "Number of subgroups must be provided for sg layout creation.");
1429 return getDpasSubgroupLayouts(context, aTy, bTy, cdTy, consumerLayout,
1430 numSg, *instDataVecs);
1431 } else if (layoutKind == xegpu::LayoutKind::InstData) {
1432 auto [instDataA, instDataB, instDataCD] = *instDataVecs;
1433 return std::make_tuple(
1434 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA),
1435 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB),
1436 buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD,
1437 laneDataCD));
1438 } else if (layoutKind == xegpu::LayoutKind::Lane) {
1439 auto aLayout = buildLaneLayout(context, laneLayoutA, laneDataA);
1440 auto bLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
1441 auto cdLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
1442 return std::make_tuple(aLayout, bLayout, cdLayout);
1443 }
1444 return std::nullopt;
1445}
1446
1447/// Helper to create a scale layout derived from a matrix operand layout.
1448/// The scale layout is computed by mapping each dimension of the matrix
1449/// layout to the corresponding scale tensor dimension using the ratio
1450/// between the matrix and scale shapes.
1451static xegpu::DistributeLayoutAttr
1452createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy,
1453 VectorType scaleTy, xegpu::DistributeLayoutAttr matrixLayout,
1454 bool isBScale, const xegpu::uArch::uArch *uArch) {
1455 if (!scaleTy || !matrixLayout)
1456 return nullptr;
1457
1458 // Calculate scaling factor by dividing matrix shape by scale shape
1459 ArrayRef<int64_t> matrixShape = matrixTy.getShape();
1460 ArrayRef<int64_t> scaleShape = scaleTy.getShape();
1461
1462 // Scale shapes can be 1D or 2D, handle both cases
1463 if (scaleShape.empty())
1464 return nullptr;
1465
1466 auto uArchInstruction =
1467 dyn_cast<xegpu::uArch::SubgroupScaledMatrixMultiplyAcc>(
1468 uArch->getInstruction(
1470
1471 int64_t rank = matrixLayout.getRank();
1472 assert(rank >= 2 && "dpas layouts must be at least two dimensions");
1473
1474 SmallVector<int64_t> sgLayout = matrixLayout.getEffectiveSgLayoutAsInt();
1475 SmallVector<int64_t> sgData = matrixLayout.getEffectiveSgDataAsInt();
1476 SmallVector<int64_t> instData = matrixLayout.getEffectiveInstDataAsInt();
1477 SmallVector<int64_t> laneLayout = matrixLayout.getEffectiveLaneLayoutAsInt();
1478 SmallVector<int64_t> laneData = matrixLayout.getEffectiveLaneDataAsInt();
1479 auto order = matrixLayout.getOrder();
1480
1481 SmallVector<int64_t> scaleSgLayout;
1482 SmallVector<int64_t> scaleSgData;
1483 if (!sgLayout.empty() && !sgData.empty()) {
1484 scaleSgLayout.assign(sgLayout.begin(), sgLayout.end());
1485 scaleSgData.assign(sgData.begin(), sgData.end());
1486 scaleSgData[rank - 2] = std::max<int64_t>(
1487 scaleShape[rank - 2] / (matrixShape[rank - 2] / sgData[rank - 2]), 1);
1488 scaleSgData[rank - 1] = std::max<int64_t>(
1489 scaleShape[rank - 1] / (matrixShape[rank - 1] / sgData[rank - 1]), 1);
1490 }
1491
1492 // For DPAS_MX scales: if matrix has inst_data, scale needs adjusted
1493 // inst_data. Scale inst_data is derived from matrix inst_data divided by
1494 // scale factor.
1495 SmallVector<int64_t> scaleInstData;
1496 if (!instData.empty()) {
1497 scaleInstData.assign(instData.begin(), instData.end());
1498 if (isBScale)
1499 scaleInstData[rank - 2] = std::max<int64_t>(
1500 scaleShape[rank - 2] / (matrixShape[rank - 2] / instData[rank - 2]),
1501 1);
1502 else
1503 scaleInstData[rank - 1] = std::max<int64_t>(
1504 scaleShape[rank - 1] / (matrixShape[rank - 1] / instData[rank - 1]),
1505 1);
1506 }
1507
1508 SmallVector<int64_t> scaleLaneLayout;
1509 SmallVector<int64_t> scaleLaneData;
1510 if (!laneLayout.empty() && !laneData.empty()) {
1511 scaleLaneLayout.assign(laneLayout.begin(), laneLayout.end());
1512 scaleLaneData.assign(laneData.size(), 1);
1513
1514 bool isRowMajor = uArchInstruction->isLaneLayoutRowMajorOrder();
1515 if (isBScale ^ isRowMajor)
1516 std::swap(scaleLaneLayout[rank - 2], scaleLaneLayout[rank - 1]);
1517 // Cap lane_layout by the per-instruction tile (inst_data) on each dim.
1518 // Then derive lane_data = inst_data / lane_layout so the Category A
1519 // invariant inst_data = lane_layout * lane_data * k (with k = 1) holds
1520 // for the scale operand's load_nd consumer.
1521 auto layoutCap = scaleInstData.empty() ? scaleShape : scaleInstData;
1522 for (int64_t d = rank - 2; d < rank; ++d)
1523 scaleLaneLayout[d] = std::min<int64_t>(layoutCap[d], scaleLaneLayout[d]);
1524 }
1525 return buildLayout(context, scaleSgLayout, scaleSgData, scaleInstData,
1526 scaleLaneLayout, scaleLaneData, order);
1527}
1528
1529/// Sets up the anchor layouts for dpas_mx operands (A, B, C/D, A_scale, and
1530/// B_scale). The numSg and consumerLayout (optional) are only used by sg
1531/// layout creation.
1532std::optional<
1533 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1534 xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1535 xegpu::DistributeLayoutAttr>>
1536xegpu::setupDpasMxLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
1537 VectorType bTy, VectorType cdTy, VectorType aScaleTy,
1538 VectorType bScaleTy,
1539 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1540 const xegpu::uArch::uArch *uArch) {
1541 auto context = aTy.getContext();
1542 const auto *uArchInstruction =
1543 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
1545 if (!uArchInstruction)
1546 return std::nullopt;
1547 auto subgroupSize = uArch->getSubgroupSize();
1548
1549 auto [laneLayoutA, laneDataA] = compute2DBlockIOLaneLayoutAndData(
1550 aTy.getShape(), subgroupSize,
1551 aTy.getElementType().getIntOrFloatBitWidth(),
1552 uArchInstruction->getPackedFormatBitSizeA());
1553 auto [laneLayoutB, laneDataB] = compute2DBlockIOLaneLayoutAndData(
1554 bTy.getShape(), subgroupSize,
1555 bTy.getElementType().getIntOrFloatBitWidth(),
1556 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
1557 auto [laneLayoutCD, laneDataCD] = compute2DBlockIOLaneLayoutAndData(
1558 cdTy.getShape(), subgroupSize,
1559 cdTy.getElementType().getIntOrFloatBitWidth(),
1560 cdTy.getElementType().getIntOrFloatBitWidth());
1561 auto instDataVecs = getDpasInstDataLayouts(aTy, bTy, cdTy, uArchInstruction);
1562 if (!instDataVecs)
1563 return std::nullopt;
1564
1565 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1566 assert(numSg > 0 &&
1567 "Number of subgroups must be provided for sg layout creation.");
1568 auto dpasLayouts = getDpasSubgroupLayouts(
1569 context, aTy, bTy, cdTy, consumerLayout, numSg, *instDataVecs);
1570 if (!dpasLayouts)
1571 return std::nullopt;
1572
1573 auto [dpasALayout, dpasBLayout, dpasCDLayout] = *dpasLayouts;
1574
1575 // Create scale layouts
1576 auto aScaleLayout =
1577 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1578
1579 auto bScaleLayout =
1580 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1581
1582 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1583 bScaleLayout);
1584 } else if (layoutKind == xegpu::LayoutKind::InstData) {
1585
1586 auto [instDataA, instDataB, instDataCD] = *instDataVecs;
1587
1588 auto dpasALayout =
1589 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA);
1590 auto dpasBLayout =
1591 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB);
1592 auto dpasCDLayout = buildInstDataLayoutWithLane(context, instDataCD,
1593 laneLayoutCD, laneDataCD);
1594
1595 auto aScaleLayout =
1596 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1597 auto bScaleLayout =
1598 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1599
1600 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1601 bScaleLayout);
1602 } else if (layoutKind == xegpu::LayoutKind::Lane) {
1603 auto dpasALayout = buildLaneLayout(context, laneLayoutA, laneDataA);
1604 auto dpasBLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
1605 auto dpasCDLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
1606
1607 auto aScaleLayout =
1608 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1609 auto bScaleLayout =
1610 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1611
1612 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1613 bScaleLayout);
1614 }
1615 return std::nullopt;
1616}
1617
1618/// Sets up the anchor layout for a store_nd operation. StoreNd picks its
1619/// own layout based on uArch block parameters (it does not take a consumer
1620/// layout, since it is a data sink).
1621xegpu::DistributeLayoutAttr
1623 VectorType srcVecTy, int numSg,
1624 const xegpu::uArch::uArch *uArch) {
1625 const auto *uArchInstruction =
1626 dyn_cast<xegpu::uArch::Subgroup2DBlockStoreInstruction>(
1627 uArch->getInstruction(
1629 if (!uArchInstruction)
1630 return nullptr;
1631
1632 auto context = srcVecTy.getContext();
1633 Type elemTy = srcVecTy.getElementType();
1634 auto subgroupSize = uArch->getSubgroupSize();
1635 auto dataShape = srcVecTy.getShape();
1636 [[maybe_unused]] int rank = srcVecTy.getRank();
1637 assert(rank >= 2 && "Expected at least 2D shape for ND op");
1638
1639 // Compute the default 2D block IO lane layout / lane data.
1640 unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
1641 auto [laneLayout, laneData] = compute2DBlockIOLaneLayoutAndData(
1642 dataShape, subgroupSize, bitwidth,
1643 uArchInstruction->getPackedFormatBitSize());
1644
1645 if (layoutKind == xegpu::LayoutKind::Lane)
1646 return buildLaneLayout(context, laneLayout, laneData);
1647
1648 auto instData =
1649 get2DBlockIOInstDataLayout(dataShape, elemTy, uArchInstruction);
1650
1651 if (layoutKind == xegpu::LayoutKind::InstData) {
1652 assert(instData && isValidLaneLayout(*instData, laneLayout, laneData) &&
1653 "Expected the store layout to satisfy uArch block constraints");
1654 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1655 laneData);
1656 }
1657
1658 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1659 assert(numSg > 0 &&
1660 "Number of subgroups must be provided for sg layout creation.");
1661 auto sgLayouts = getSgLayoutCandidates(dataShape, *instData, numSg);
1662 if (sgLayouts.empty())
1663 return nullptr;
1664 return buildSgLayout(context, dataShape, sgLayouts.front(), /*dimK=*/-1);
1665 }
1666
1667 return nullptr;
1668}
1669
1670/// Sets up the anchor layout for a prefetch_nd operation. PrefetchNd has no
1671/// consumer (it produces no value), so it picks its own layout from uArch
1672/// block parameters.
1673xegpu::DistributeLayoutAttr
1675 xegpu::TensorDescType tdescTy, int numSg,
1676 const xegpu::uArch::uArch *uArch) {
1677
1678 const auto *uArchInstruction =
1679 dyn_cast<xegpu::uArch::Subgroup2DBlockPrefetchInstruction>(
1680 uArch->getInstruction(
1682 if (!uArchInstruction)
1683 return nullptr;
1684
1685 auto context = tdescTy.getContext();
1686 Type elemTy = tdescTy.getElementType();
1687 auto subgroupSize = uArch->getSubgroupSize();
1688 auto dataShape = tdescTy.getShape();
1689 [[maybe_unused]] int rank = tdescTy.getRank();
1690 assert(rank >= 2 && "Expected at least 2D shape for ND op");
1691
1692 // Compute the default 2D block IO lane layout / lane data.
1693 unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
1694 auto [laneLayout, laneData] = compute2DBlockIOLaneLayoutAndData(
1695 dataShape, subgroupSize, bitwidth,
1696 uArchInstruction->getPackedFormatBitSize());
1697
1698 if (layoutKind == xegpu::LayoutKind::Lane)
1699 return buildLaneLayout(context, laneLayout, laneData);
1700
1701 auto instData =
1702 get2DBlockIOInstDataLayout(dataShape, elemTy, uArchInstruction);
1703
1704 if (layoutKind == xegpu::LayoutKind::InstData) {
1705 assert(instData && isValidLaneLayout(*instData, laneLayout, laneData) &&
1706 "Expected the prefetch layout to satisfy uArch block constraints");
1707 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1708 laneData);
1709 }
1710
1711 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1712 assert(numSg > 0 &&
1713 "Number of subgroups must be provided for sg layout creation.");
1714 auto sgLayouts = getSgLayoutCandidates(dataShape, *instData, numSg);
1715 if (sgLayouts.empty())
1716 return nullptr;
1717 return buildSgLayout(context, dataShape, sgLayouts.front(), /*dimK=*/-1);
1718 }
1719
1720 return nullptr;
1721}
1722
1723/// Sets up the anchor layout for a load_nd operation. LoadNd takes a
1724/// consumer layout (from its result's downstream uses) and validates it
1725/// against uArch constraints; if valid, the consumer's `inst_data` /
1726/// `sg_layout` are honored. Otherwise the helper falls back to defaults
1727/// derived from uArch block parameters.
1728xegpu::DistributeLayoutAttr
1730 VectorType resVecTy,
1731 xegpu::DistributeLayoutAttr consumerLayout,
1732 int numSg, const xegpu::uArch::uArch *uArch) {
1733
1734 assert(consumerLayout && "Expected a valid consumer layout");
1735 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1736 assert(consumerLayout.isForWorkgroup() &&
1737 "Expected consumer layout to be a complete workgroup-level layout");
1738 return consumerLayout;
1739 }
1740
1741 auto context = resVecTy.getContext();
1742 Type elemTy = resVecTy.getElementType();
1743 auto subgroupSize = uArch->getSubgroupSize();
1744 auto dataShape = resVecTy.getShape();
1745 const auto *uArchInstruction =
1746 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
1747 uArch->getInstruction(
1749 if (!uArchInstruction)
1750 return nullptr;
1751
1752 int rank = resVecTy.getRank();
1753 SmallVector<int64_t> consumerInstData =
1754 consumerLayout.getEffectiveInstDataAsInt();
1755 SmallVector<int64_t> consumerLaneLayout =
1756 consumerLayout.getEffectiveLaneLayoutAsInt();
1757 SmallVector<int64_t> consumerLaneData =
1758 consumerLayout.getEffectiveLaneDataAsInt();
1759 auto consumerOrderAttr = consumerLayout.getOrder();
1760
1761 assert(!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
1762 "Expected consumer layout to have lane_layout and lane_data");
1763
1764 // vertical lane layout means that the blockload must be transposed
1765 // note scaleA on PVC has vertical lane layout even without transposed order
1766 // attr
1767 bool hasTranspose =
1768 consumerLaneLayout[rank - 2] > 1 && consumerLaneLayout[rank - 1] == 1;
1769 bool hasTransform = !hasTranspose && consumerLaneData[rank - 2] > 1 &&
1770 consumerLaneData[rank - 1] == 1;
1771 assert((consumerLaneData[rank - 2] == 1 || consumerLaneData[rank - 1] == 1) &&
1772 "Expected consumer lane data to have at most one non-unit dim");
1773
1774 if (layoutKind == xegpu::LayoutKind::InstData) {
1775 auto blockWHC = uArchInstruction->getBlockWidthHeightCount(
1776 elemTy, hasTransform, hasTranspose,
1777 /*upConv=*/false);
1778 if (!blockWHC)
1779 return nullptr;
1780 auto [bWidths, bHeights, bCounts] = blockWHC.value();
1781
1782 SmallVector<int64_t> laneLayout;
1783 // set the laneLayout to use consumer's LaneLayout as base, but adjust its
1784 // size to match the subgroupsize in case its original value is larger than
1785 // 1
1786 for (int i = 0; i < rank; i++) {
1787 if (consumerLaneLayout[i] > 1)
1788 laneLayout.push_back(std::max(static_cast<int64_t>(subgroupSize),
1789 consumerLaneLayout[i]));
1790 else
1791 laneLayout.push_back(1);
1792 }
1793
1794 // See whether the consumer's inst_data satisfies the block constraints.
1795 int64_t height = consumerInstData[rank - 2];
1796 int64_t width = consumerInstData[rank - 1];
1797 auto maxBlockCount = *llvm::max_element(bCounts);
1798 auto maxWidth = *llvm::max_element(bWidths);
1799 if (llvm::is_contained(bWidths, static_cast<int>(width)) ||
1800 (width % maxWidth == 0 && width / maxWidth < maxBlockCount)) {
1801 if (llvm::is_contained(bHeights, static_cast<int>(height))) {
1802 return buildInstDataLayoutWithLane(context, consumerInstData,
1803 laneLayout, consumerLaneData,
1804 consumerOrderAttr);
1805 }
1806 }
1807
1808 // if consumer instData size too small, try the larger one. like DPAS_MX's
1809 // scale is smaller than block load
1810 auto instData = get2DBlockIOInstDataLayout(
1811 dataShape, elemTy, uArchInstruction, hasTransform, hasTranspose);
1812 // assert instData is valid against consumer layout since
1813 // transform/transpose attribute are derived from consumer layout
1814 assert(instData &&
1815 isValidLaneLayout(*instData, laneLayout, consumerLaneData) &&
1816 "Expected the load layout to satisfy uArch block constraints");
1817 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1818 consumerLaneData, consumerOrderAttr);
1819 }
1820 if (layoutKind == xegpu::LayoutKind::Lane) {
1821 assert(isValidLaneLayout(dataShape, consumerLaneLayout, consumerLaneData) &&
1822 "Expected the lane layout to satisfy uArch block constraints");
1823 return consumerLayout;
1824 }
1825 return nullptr;
1826}
1827
1828/// Sets up the anchor layout for load gather and load matrix operation.
1829/// load matrix lowers to load gather and 1d block load. All of them share the
1830/// same layout setup logic.
1831///
1832/// For Subgroup layout, uses the consumer layout directly.
1833///
1834/// For InstData layout, takes consumer's inst_data as-is. lane_layout and
1835/// lane_data are taken from the consumer when present; otherwise the helper
1836/// derives the standard scatter-style default (subgroupSize lanes on the
1837/// innermost dim, per-lane vector capped by maxChunkSize).
1838///
1839/// For Lane layout, lane_layout/lane_data are taken from the consumer when
1840/// present; otherwise derived from the same default.
1841static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
1842 xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
1843 xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize,
1844 ArrayRef<int64_t> resShape, int subgroupSize) {
1845
1846 if (layoutKind == xegpu::LayoutKind::Subgroup)
1847 return consumerLayout;
1848
1849 SmallVector<int64_t> consumerInstData =
1850 consumerLayout.getEffectiveInstDataAsInt();
1851 SmallVector<int64_t> consumerLaneLayout =
1852 consumerLayout.getEffectiveLaneLayoutAsInt();
1853 SmallVector<int64_t> consumerLaneData =
1854 consumerLayout.getEffectiveLaneDataAsInt();
1855
1856 SmallVector<int64_t> laneLayout;
1857 SmallVector<int64_t> laneData;
1858 assert(!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
1859 "Expected consumer layout to have lane_layout and lane_data");
1860 laneLayout.assign(consumerLaneLayout.begin(), consumerLaneLayout.end());
1861 laneData.assign(consumerLaneData.begin(), consumerLaneData.end());
1862
1863 if (layoutKind == xegpu::LayoutKind::InstData) {
1864 SmallVector<int64_t> instData;
1865 instData.resize(resShape.size());
1866 for (size_t i = 0; i < resShape.size(); ++i)
1867 instData[i] = laneLayout[i] * laneData[i];
1868 return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
1869 }
1870 if (layoutKind == xegpu::LayoutKind::Lane)
1871 return buildLaneLayout(context, laneLayout, laneData);
1872 return nullptr;
1873}
1874
1875/// Sets up the anchor layout for a load gather operation.
1876xegpu::DistributeLayoutAttr xegpu::setupLoadGatherAnchorLayout(
1877 xegpu::LayoutKind layoutKind, VectorType resVecTy, int contigChunkSize,
1878 xegpu::DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch) {
1879
1880 const int subgroupSize = uArch->getSubgroupSize();
1881 ArrayRef<int64_t> resShape = resVecTy.getShape();
1882 auto context = resVecTy.getContext();
1883
1884 const auto *uArchInstruction = dyn_cast<xegpu::uArch::LoadGatherInstruction>(
1885 uArch->getInstruction(xegpu::uArch::InstructionKind::LoadGather));
1886 int maxChunkSize =
1887 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1888
1889 return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
1890 maxChunkSize, resShape, subgroupSize);
1891}
1892
1893/// Sets up the anchor layout for load matrix operation.
1894/// TODO: enhance load matrix to indicate lowering to chunked load or not.
1895xegpu::DistributeLayoutAttr
1897 VectorType resVecTy, int contigChunkSize,
1898 xegpu::DistributeLayoutAttr consumerLayout,
1899 const xegpu::uArch::uArch *uArch) {
1900
1901 const int subgroupSize = uArch->getSubgroupSize();
1902 ArrayRef<int64_t> resShape = resVecTy.getShape();
1903 auto context = resVecTy.getContext();
1904
1905 const auto *uArchInstruction = dyn_cast<xegpu::uArch::LoadGatherInstruction>(
1907 int maxChunkSize =
1908 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1909 return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
1910 maxChunkSize, resShape, subgroupSize);
1911}
1912
1913/// Picks the subgroup layout for a scatter-style store (store_scatter /
1914/// store_matrix): the most balanced `numSg` factorization that divides
1915/// `wgShape` with sg_data a multiple of `instData`. A store has no consumer.
1916static xegpu::DistributeLayoutAttr
1918 ArrayRef<int64_t> instData, int numSg) {
1919 auto candidates = getSgLayoutCandidates(wgShape, instData, numSg);
1920 if (candidates.empty())
1921 return nullptr;
1922 // Candidates are ordered most-balanced first.
1923 return buildSgLayout(context, wgShape, candidates.front(), /*dimK=*/-1);
1924}
1925
1926/// Sets up the anchor layout for store scatter and store matrix operation,
1927/// which share the same logic. Lane layout comes from
1928/// `computeScatterIOLaneLayoutAndData`; inst_data is lane_layout * lane_data.
1929static xegpu::DistributeLayoutAttr setupGenericStoreAnchorLayout(
1930 xegpu::LayoutKind layoutKind, mlir::MLIRContext *context, int maxChunkSize,
1931 ArrayRef<int64_t> srcShape, int subgroupSize, int numSg) {
1932
1933 auto [laneLayout, laneData] =
1934 computeScatterIOLaneLayoutAndData(srcShape, subgroupSize, maxChunkSize);
1935
1936 SmallVector<int64_t> instData(srcShape.size());
1937 for (size_t i = 0; i < srcShape.size(); ++i)
1938 instData[i] = laneLayout[i] * laneData[i];
1939
1940 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1941 assert(numSg > 0 &&
1942 "Number of subgroups must be provided for sg layout creation.");
1943 return getStoreSubgroupLayouts(context, srcShape, instData, numSg);
1944 }
1945 if (layoutKind == xegpu::LayoutKind::InstData) {
1946 return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
1947 }
1948 if (layoutKind == xegpu::LayoutKind::Lane) {
1949 return buildLaneLayout(context, laneLayout, laneData);
1950 }
1951 return nullptr;
1952}
1953
1954/// Sets up the anchor layout for a store scatter operation.
1955xegpu::DistributeLayoutAttr
1957 VectorType srcVecTy, int contigChunkSize,
1958 int numSg, const uArch::uArch *uArch) {
1959
1960 const int subgroupSize = uArch->getSubgroupSize();
1961 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
1962 auto context = srcVecTy.getContext();
1963
1964 const auto *uArchInstruction =
1965 dyn_cast<xegpu::uArch::StoreScatterInstruction>(
1967 int maxChunkSize =
1968 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1969 return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
1970 srcShape, subgroupSize, numSg);
1971}
1972
1973/// Sets up the anchor layout for a store matrix operation.
1974xegpu::DistributeLayoutAttr xegpu::setupStoreMatrixAnchorLayout(
1975 xegpu::LayoutKind layoutKind, VectorType srcVecTy, int contigChunkSize,
1976 int numSg, const xegpu::uArch::uArch *uArch) {
1977
1978 const int subgroupSize = uArch->getSubgroupSize();
1979 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
1980 auto context = srcVecTy.getContext();
1981
1982 const auto *uArchInstruction =
1983 dyn_cast<xegpu::uArch::StoreScatterInstruction>(
1985 int maxChunkSize =
1986 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1987
1988 return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
1989 srcShape, subgroupSize, numSg);
1990}
1991
1992/// Completes a scatter IO layout by deriving lane_layout and lane_data from
1993/// `specifiedLayout`'s inst_data when they are missing. The layout is returned
1994/// unchanged if `specifiedLayout` is null, carries no inst_data, or already has
1995/// both lane_layout and lane_data.
1996///
1997/// When lane info is absent, inst_data is treated as the effective shape and
1998/// the lane factorization is filled in as follows:
1999/// - If `consumerLayout` is present and its lane_layout / lane_data are a
2000/// valid factorization of inst_data, that consumer lane info is reused so
2001/// the completed layout matches the consumer (avoiding a relayout).
2002/// - Otherwise a standard scatter-style factorization is computed via
2003/// `computeScatterIOLaneLayoutAndData`, bounded by `maxChunkSize` — the
2004/// per-lane load width reported by the uArch's LoadGather instruction
2005/// (`getMaxLaneAccessSizeBytes`).
2006///
2007std::optional<xegpu::DistributeLayoutAttr>
2009 xegpu::DistributeLayoutAttr specifiedLayout,
2010 xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
2011 const xegpu::uArch::LoadGatherInstruction *uArchInstruction,
2012 const int subgroupSize) {
2013 if (!specifiedLayout)
2014 return specifiedLayout;
2015 SmallVector<int64_t> specifiedInstData =
2016 specifiedLayout.getEffectiveInstDataAsInt();
2017 if (specifiedInstData.empty())
2018 return specifiedLayout;
2019 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2020 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2021 return specifiedLayout;
2022
2023 // Reuse the load-side setup with inst_data as the destination shape.
2024 auto *context = specifiedLayout.getContext();
2025 int maxChunkSize = uArchInstruction->getMaxLaneAccessSizeBytes();
2026 if (consumerLayout) {
2027 auto consumerLaneLayout = consumerLayout.getEffectiveLaneLayoutAsInt();
2028 auto consumerLaneData = consumerLayout.getEffectiveLaneDataAsInt();
2029 if (!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
2030 isValidLaneLayout(specifiedInstData, consumerLaneLayout,
2031 consumerLaneData))
2032 return buildInstDataLayoutWithLane(context, specifiedInstData,
2033 consumerLaneLayout, consumerLaneData);
2034 }
2035 auto [defLaneLayout, defLaneData] = computeScatterIOLaneLayoutAndData(
2036 specifiedInstData, subgroupSize, maxChunkSize);
2037 if (!isValidLaneLayout(specifiedInstData, defLaneLayout, defLaneData))
2038 return std::nullopt;
2039 return buildInstDataLayoutWithLane(context, specifiedInstData, defLaneLayout,
2040 defLaneData);
2041}
2042
2043/// Like completeScatterLoadLaneLayoutFromInstData, but for scatter stores. A
2044/// store is a data sink, so lane info is derived purely from inst_data (bounded
2045/// by the uArch's per-lane store width); there is no consumer layout to reuse.
2046std::optional<xegpu::DistributeLayoutAttr>
2048 xegpu::DistributeLayoutAttr specifiedLayout, Type elemTy,
2049 const xegpu::uArch::StoreScatterInstruction *uArchInstruction,
2050 const int subgroupSize) {
2051 if (!specifiedLayout)
2052 return specifiedLayout;
2053 SmallVector<int64_t> specifiedInstData =
2054 specifiedLayout.getEffectiveInstDataAsInt();
2055 if (specifiedInstData.empty())
2056 return specifiedLayout;
2057 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2058 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2059 return specifiedLayout;
2060
2061 // Reuse the store-side setup with inst_data as the source shape.
2062 auto *context = specifiedLayout.getContext();
2063 int maxChunkSize = uArchInstruction->getMaxLaneAccessSizeBytes();
2064 auto [defLaneLayout, defLaneData] = computeScatterIOLaneLayoutAndData(
2065 specifiedInstData, subgroupSize, maxChunkSize);
2066 if (!isValidLaneLayout(specifiedInstData, defLaneLayout, defLaneData))
2067 return std::nullopt;
2068 return buildInstDataLayoutWithLane(context, specifiedInstData, defLaneLayout,
2069 defLaneData);
2070}
2071
2072/// Completes a 2D-block store/prefetch layout from its inst_data. store_nd and
2073/// prefetch_nd are data sinks, so lane info is derived purely from inst_data
2074/// (no consumer to reuse). One helper serves both via
2075/// BlockIOInstructionInterface.
2076std::optional<xegpu::DistributeLayoutAttr>
2078 xegpu::DistributeLayoutAttr specifiedLayout, Type elemTy,
2079 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
2080 const int subgroupSize) {
2081 if (!specifiedLayout)
2082 return specifiedLayout;
2083 SmallVector<int64_t> specifiedInstData =
2084 specifiedLayout.getEffectiveInstDataAsInt();
2085 if (specifiedInstData.empty())
2086 return specifiedLayout;
2087 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2088 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2089 return specifiedLayout;
2090
2091 auto *context = specifiedLayout.getContext();
2092 auto [laneLayout, laneData] = compute2DBlockIOLaneLayoutAndData(
2093 specifiedInstData, subgroupSize, elemTy.getIntOrFloatBitWidth(),
2094 uArchInstruction->getPackedFormatBitSize());
2095 if (!isValidLaneLayout(specifiedInstData, laneLayout, laneData))
2096 return std::nullopt;
2097 return buildInstDataLayoutWithLane(context, specifiedInstData, laneLayout,
2098 laneData);
2099}
2100
2101/// Like completeBlockStoreLaneLayoutFromInstData, but for load_nd. The
2102/// consumer's lane_data and order are reused as-is; lane_layout is rebuilt from
2103/// the consumer's lane_layout, bumping every non-unit dim up to the subgroup
2104/// size. The user-provided inst_data is preserved.
2105std::optional<xegpu::DistributeLayoutAttr>
2107 xegpu::DistributeLayoutAttr specifiedLayout,
2108 xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
2109 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
2110 const int subgroupSize) {
2111 if (!specifiedLayout)
2112 return specifiedLayout;
2113 SmallVector<int64_t> specifiedInstData =
2114 specifiedLayout.getEffectiveInstDataAsInt();
2115 if (specifiedInstData.empty())
2116 return specifiedLayout;
2117 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2118 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2119 return specifiedLayout;
2120 if (!consumerLayout)
2121 return specifiedLayout;
2122 SmallVector<int64_t> consumerLaneLayout =
2123 consumerLayout.getEffectiveLaneLayoutAsInt();
2124 SmallVector<int64_t> consumerLaneData =
2125 consumerLayout.getEffectiveLaneDataAsInt();
2126 if (consumerLaneLayout.empty() || consumerLaneData.empty())
2127 return specifiedLayout;
2128
2129 auto *context = specifiedLayout.getContext();
2130 int rank = specifiedInstData.size();
2131
2132 SmallVector<int64_t> laneLayout;
2133 // set the laneLayout to use consumer's LaneLayout as base, but adjust its
2134 // size to match the subgroupsize in case its original value is larger than 1
2135 for (int i = 0; i < rank; i++) {
2136 if (consumerLaneLayout[i] > 1) {
2137 laneLayout.push_back(
2138 std::max(static_cast<int64_t>(subgroupSize), consumerLaneLayout[i]));
2139 } else {
2140 laneLayout.push_back(1);
2141 }
2142 }
2143
2144 if (!isValidLaneLayout(specifiedInstData, laneLayout, consumerLaneData))
2145 return std::nullopt;
2146 return buildInstDataLayoutWithLane(context, specifiedInstData, laneLayout,
2147 consumerLaneData,
2148 consumerLayout.getOrder());
2149}
2150
2151/// Completes user-provided DPAS A/B/C-D anchors that carry only inst_data by
2152/// filling in lane_layout / lane_data. The lane factorization mirrors the
2153/// InstData branch of `setupDpasLayout` (derived from each operand's shape and
2154/// matmul role, B using VNNI packing); the user's inst_data is preserved.
2155std::optional<
2156 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2157 xegpu::DistributeLayoutAttr>>
2158xegpu::completeDpasLaneLayoutFromInstData(xegpu::DistributeLayoutAttr aLayout,
2159 xegpu::DistributeLayoutAttr bLayout,
2160 xegpu::DistributeLayoutAttr cdLayout,
2161 VectorType aTy, VectorType bTy,
2162 VectorType cdTy,
2163 const xegpu::uArch::uArch *uArch) {
2164 auto context = aTy.getContext();
2165 const auto *uArchInstruction =
2166 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
2168 if (!uArchInstruction)
2169 return std::nullopt;
2170 auto subgroupSize = uArch->getSubgroupSize();
2171 llvm::SmallVector<int64_t> laneLayoutA, laneDataA, laneLayoutB, laneDataB,
2172 laneLayoutCD, laneDataCD;
2173 SmallVector<int64_t> instDataA = aLayout.getEffectiveInstDataAsInt();
2174 SmallVector<int64_t> instDataB = bLayout.getEffectiveInstDataAsInt();
2175 SmallVector<int64_t> instDataCD = cdLayout.getEffectiveInstDataAsInt();
2176
2177 if (isa<xegpu::uArch::Xe2, xegpu::uArch::Xe3>(uArch)) {
2178 std::tie(laneLayoutA, laneDataA) = compute2DBlockIOLaneLayoutAndData(
2179 aTy.getShape(), subgroupSize,
2180 aTy.getElementType().getIntOrFloatBitWidth(),
2181 uArchInstruction->getPackedFormatBitSizeA());
2182 std::tie(laneLayoutB, laneDataB) = compute2DBlockIOLaneLayoutAndData(
2183 bTy.getShape(), subgroupSize,
2184 bTy.getElementType().getIntOrFloatBitWidth(),
2185 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
2186 std::tie(laneLayoutCD, laneDataCD) = compute2DBlockIOLaneLayoutAndData(
2187 cdTy.getShape(), subgroupSize,
2188 cdTy.getElementType().getIntOrFloatBitWidth(),
2189 cdTy.getElementType().getIntOrFloatBitWidth());
2190 } else {
2191 assert(false && "Unsupported uArch for DPAS lane layout completion");
2192 }
2193
2194 if (!isValidLaneLayout(instDataA, laneLayoutA, laneDataA) ||
2195 !isValidLaneLayout(instDataB, laneLayoutB, laneDataB) ||
2196 !isValidLaneLayout(instDataCD, laneLayoutCD, laneDataCD))
2197 return std::nullopt;
2198 return std::make_tuple(
2199 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA,
2200 aLayout.getOrder()),
2201 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB,
2202 bLayout.getOrder()),
2203 buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD, laneDataCD,
2204 cdLayout.getOrder()));
2205}
2206
2207/// Like completeDpasLaneLayoutFromInstData, but for dpas_mx: also re-derives
2208/// the A_scale / B_scale layouts from the completed A / B layouts via
2209/// `createScaleLayout`, matching the default path of `setupDpasMxLayout`.
2210std::optional<
2211 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2212 xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2213 xegpu::DistributeLayoutAttr>>
2215 xegpu::DistributeLayoutAttr aLayout, xegpu::DistributeLayoutAttr bLayout,
2216 xegpu::DistributeLayoutAttr cdLayout, VectorType aTy, VectorType bTy,
2217 VectorType cdTy, VectorType aScaleTy, VectorType bScaleTy,
2218 const xegpu::uArch::uArch *uArch) {
2219 auto completed = completeDpasLaneLayoutFromInstData(
2220 aLayout, bLayout, cdLayout, aTy, bTy, cdTy, uArch);
2221 if (!completed)
2222 return std::nullopt;
2223 auto context = aTy.getContext();
2224 auto [completedA, completedB, completedCD] = *completed;
2225
2226 auto aScaleLayout =
2227 createScaleLayout(context, aTy, aScaleTy, completedA, false, uArch);
2228 auto bScaleLayout =
2229 createScaleLayout(context, bTy, bScaleTy, completedB, true, uArch);
2230
2231 return std::make_tuple(completedA, completedB, completedCD, aScaleLayout,
2232 bScaleLayout);
2233}
2234
2235/// Sets up layout for reduction operations by creating a SliceAttr for the
2236/// result.
2237///
2238/// Algorithm Overview:
2239/// This function attempts to construct a source layout that, when sliced along
2240/// reduction dimensions, produces a result layout compatible with the
2241/// consumer layout.
2242///
2243/// For subgroup layouts, it first tries to align the source layout's subgroup
2244/// layout and data with the consumer's layout on non-reduction dimensions.
2245/// Then, it distributes remaining subgroups across reduction dimensions. This
2246/// avoids subgroup data redistribution overhead between the reduced result and
2247/// its consumer. When the consumer layout is a slice layout, it attempts to
2248/// reuse the slice layout's parent layout for the source to further minimize
2249/// potential data redistribution.
2250///
2251/// This is a best-effort alignment, not a hard constraint: the goal is only to
2252/// pick a *legal* source layout that minimizes redistribution against the
2253/// (single, first-arriving) consumer layout. There is no failure path - when
2254/// the consumer's slice layout cannot be reused as-is (example 2 below), the
2255/// function falls back to distributing all subgroups on the non-reduction
2256/// dimensions first and the remainder on the reduction dimensions, which always
2257/// yields a valid source layout. If the resulting source layout still differs
2258/// from what some consumer expects (e.g. a second, inconsistent consumer), that
2259/// mismatch is reconciled later by the layout conflict resolution process
2260/// (`ResolveLayoutConflicts`), which inserts a `convert_layout` op - this
2261/// function never has to give up.
2262///
2263/// For the InstData and Lane layout kinds only the innermost two dimensions
2264/// are distributed; all leading dimensions are assumed to be unit dimensions.
2265/// This assumption is checked via `leadingDimsAreUnit`. The lane_layout and
2266/// lane_data are computed by `computeReductionLaneLayoutAndData`, which picks
2267/// a layout that minimizes cross-lane reduction (reducing within a lane when
2268/// only one of the innermost two dims is a reduction dim). The inst_data is
2269/// simply the element-wise product lane_layout * lane_data.
2270///
2271/// The function returns the *result* layout (the SliceAttr). The *source*
2272/// layout it decides on is the parent of that slice; both are listed below so
2273/// the relationship is explicit.
2274///
2275/// Examples:
2276/// 1. Subgroup layout - Row reduction on 2D tensor:
2277/// srcShape=[32, 128], reductionDims=[1], resShape=[32], subgroupSize=16,
2278/// NumSg=32
2279/// * Consumer Layout:
2280/// #xegpu.slice<#xegpu.layout<sg_layout=[4, 8], sg_data=[8, 8]>, dims =
2281/// [1]>}
2282/// * Source Layout (decided by this function):
2283/// #xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>
2284/// * Result Layout (returned):
2285/// #xegpu.slice<#xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>, dims =
2286/// [1]>}
2287/// The consumer slices exactly the reduction dim, so its parent layout is
2288/// reused for the source: sg_layout is kept, but the source's sg_data on
2289/// the reduction dim is grown from 8 to 16 (= srcShape[1] / sg_layout[1] =
2290/// 128 / 8) so the source tile is evenly distributed over the reduction
2291/// dim. Slicing that source over dim 1 reproduces the consumer.
2292///
2293/// 2. Subgroup layout - Same shapes as above but consumer doesn't have a
2294/// reusable slice layout, so the algorithm distributes all subgroups on the
2295/// non-reduction dims first and the remainder on the reduction dims.
2296/// 2a. * Consumer Layout:
2297/// #xegpu.layout<sg_layout=[32], sg_data=[1]>
2298/// * Source Layout (decided by this function):
2299/// #xegpu.layout<sg_layout=[32, 1], sg_data=[1, 128]>
2300/// * Result Layout (returned):
2301/// #xegpu.slice<#xegpu.layout<sg_layout=[32, 1], sg_data=[1, 128]>,
2302/// dims = [1]>}
2303/// All 32 subgroups land on the non-reduction dim 0; the reduction dim
2304/// 1 gets the leftover (sg_layout=1, so the whole length 128 lives in
2305/// one subgroup's sg_data).
2306/// 2b. * Consumer Layout:
2307/// #xegpu.slice<#xegpu.layout<sg_layout=[8, 2, 4], sg_data=[4, 64,
2308/// 32]>, dims = [1, 2]>}
2309/// * Source Layout (decided by this function):
2310/// #xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>
2311/// * Result Layout (returned):
2312/// #xegpu.slice<#xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>,
2313/// dims = [1]>}
2314/// The consumer slices dims [1, 2] which do not match this op's
2315/// reductionDims, so it can't be reused as-is; subgroups are
2316/// re-distributed (non-reduction dim first, then reduction dim).
2317///
2318/// 3. Lane layout - Default (lanes on innermost dim):
2319/// srcShape=[32, 64], reductionDims=[0], subgroupSize=16
2320/// * Source Layout (decided by this function):
2321/// laneLayout=[1, 16], laneData=[1, 1] (returned sliced over dim 0).
2322/// The innermost dim is not reduced, so lanes stay on it.
2323///
2324/// 4. Lane layout - Switch (lanes moved off the reduction dim):
2325/// srcShape=[32, 64], reductionDims=[1], subgroupSize=16
2326/// * Source Layout (decided by this function):
2327/// laneLayout=[16, 1], laneData=[1, 1] (returned sliced over dim 1).
2328/// The innermost dim is the sole reduction dim, so lanes move to the
2329/// non-reduction dim to reduce within a lane. This switch only happens
2330/// when the consumer has no reduction dims to broadcast the result back
2331/// along (i.e. the consumer layout is not a slice over this reduction);
2332/// otherwise the default (example 3) is used.
2333///
2334/// 5. Lane layout - No switch when both inner dims are reduced (reduction to
2335/// scalar):
2336/// srcShape=[32, 64], reductionDims=[0, 1], subgroupSize=16
2337/// * Source Layout (decided by this function):
2338/// laneLayout=[1, 16], laneData=[1, 1] (returned sliced over dims
2339/// [0,1]).
2340/// Both dims are reduced, so this is not a *sole* innermost reduction; the
2341/// switch condition (example 4) does not apply and lanes stay on the
2342/// innermost dim. The cross-lane reduction here is unavoidable.
2343///
2344/// 6. Lane layout - No switch when the consumer slices the reduction dim:
2345/// srcShape=[32, 64], reductionDims=[1], subgroupSize=16
2346/// * Consumer Layout:
2347/// #xegpu.slice<#xegpu.layout<laneLayout=[1, 16], laneData=[1, 1]>,
2348/// dims = [1]>}
2349/// * Source Layout (decided by this function):
2350/// #xegpu.layout<laneLayout=[1, 16], laneData=[1, 1]> (the consumer
2351/// slice's parent, reused directly; returned sliced over dim 1).
2352/// Same shape/reductionDims as example 4, but here the consumer is a slice
2353/// over the reduction dim, so it can broadcast the result back along that
2354/// dim. The slice's parent layout is reused as the source (no switch, no
2355/// re-derivation); the inst_data propagation step has already inserted a
2356/// convert_layout if needed, so the lane-level layout can be reused as-is.
2357
2359 xegpu::LayoutKind layoutKind, VectorType srcVecTy,
2360 DistributeLayoutAttr consumerLayout, SmallVector<int64_t> reductionDims,
2361 int numSg, const xegpu::uArch::uArch *uArch) {
2362
2363 auto srcShape = srcVecTy.getShape();
2364 int srcRank = srcShape.size();
2365 auto context = srcVecTy.getContext();
2366
2367 const int subgroupSize = uArch->getSubgroupSize();
2368 int64_t maxReduceVectorSize = 1; // could extend to spirv vector Size
2369 xegpu::DistributeLayoutAttr srcLayout;
2370 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2371 xegpu::SliceAttr consumerSliceLayout =
2372 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2373 if (consumerSliceLayout &&
2374 consumerSliceLayout.getDims().asArrayRef().equals(reductionDims)) {
2375 srcLayout = consumerSliceLayout.getParent();
2376 SmallVector<int64_t> sgLayoutFromConsumer =
2377 srcLayout.getEffectiveSgLayoutAsInt();
2378 auto srcSgData = computeShapeRatio(srcShape, sgLayoutFromConsumer);
2379 if (srcSgData)
2380 for (int dim = 0; dim < srcRank; dim++) {
2381 if (llvm::is_contained(reductionDims, dim))
2382 srcLayout =
2383 srcLayout.setDimData(dim, srcSgData.value()[dim], -1, -1);
2384 }
2385 } else {
2386 SmallVector<int64_t> consumerSgLayout =
2387 consumerLayout ? consumerLayout.getEffectiveSgLayoutAsInt()
2389 SmallVector<int64_t> consumerSgData =
2390 consumerLayout ? consumerLayout.getEffectiveSgDataAsInt()
2392 SmallVector<int64_t> consumerOrder =
2393 consumerLayout ? consumerLayout.getEffectiveOrderAsInt()
2395 DenseI32ArrayAttr orderAttr =
2396 consumerLayout ? consumerLayout.getOrder() : nullptr;
2397 SmallVector<int64_t> sgLayout(srcRank), sgData(srcRank), order(srcRank);
2398 int remainingSgCount =
2399 consumerLayout ? consumerLayout.getNumSubgroups() : numSg;
2400 int consumerIdx = 0;
2401
2402 // First pass: Match consumer's layout on non-reduction dimensions
2403 for (int i = 0; i < srcRank; i++) {
2404 if (!llvm::is_contained(reductionDims, i) &&
2405 consumerIdx < static_cast<int>(consumerSgLayout.size())) {
2406 sgLayout[i] = consumerSgLayout[consumerIdx];
2407 sgData[i] = consumerSgData[consumerIdx];
2408 remainingSgCount /= sgLayout[i];
2409 order[i] = consumerOrder[consumerIdx];
2410 consumerIdx++;
2411 }
2412 }
2413
2414 // Second pass: Distribute remaining subgroups across reduction dimensions
2415 // the reduction to scalar case is handled only by this loop
2416 int64_t remainOrder = consumerSgLayout.size();
2417 for (int i = 0; i < srcRank; i++) {
2418 if (llvm::is_contained(reductionDims, i)) {
2419 sgLayout[i] =
2420 std::min(srcShape[i], static_cast<int64_t>(remainingSgCount));
2421 assert((srcShape[i] % sgLayout[i] == 0) &&
2422 "source shape not divisible by sg_layout");
2423 sgData[i] = srcShape[i] / sgLayout[i];
2424 remainingSgCount /= sgLayout[i];
2425 order[i] = remainOrder++;
2426 }
2427 }
2429 context, SmallVector<int32_t>(order.begin(), order.end()));
2430 if (!orderAttr || orderAttr.empty())
2431 resOrderAttr = nullptr;
2432 assert(remainingSgCount == 1 && "not all subgroups distributed");
2433 srcLayout = buildLayout(context, sgLayout, sgData,
2434 /*instData=*/{}, /*laneLayout=*/{},
2435 /*laneData=*/{}, resOrderAttr);
2436 }
2437 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2438 xegpu::SliceAttr consumerSliceLayout =
2439 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2440 auto consumerReductionDims =
2441 consumerSliceLayout
2442 ? SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef())
2444 // A[i] reduced from A[i, j] is stored out directly, use vertical Lane
2445 // layout like [16, 1]
2446 bool verticalLaneLayout = consumerReductionDims.empty() &&
2447 reductionDims.size() == 1 &&
2448 reductionDims[0] == (srcRank - 1);
2449 auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
2450 srcShape, reductionDims, subgroupSize, maxReduceVectorSize,
2451 verticalLaneLayout);
2452 // inst_data is the per-instruction data, i.e. the element-wise product of
2453 // lane_layout and lane_data.
2454 SmallVector<int64_t> instData(srcRank);
2455 for (int i = 0; i < srcRank; i++)
2456 instData[i] = laneLayout[i] * laneData[i];
2457 srcLayout =
2458 buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
2459 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2460 // Only the innermost two dimensions are distributed; all leading dimensions
2461 // are assumed to be unit dimensions.
2462 assert(leadingDimsAreUnit(srcShape, /*numInnerDims=*/2) &&
2463 "Lane reduction layout assumes all leading (non-innermost-two) "
2464 "dimensions are unit dimensions");
2465 xegpu::SliceAttr consumerSliceLayout =
2466 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2467 auto consumerReductionDims =
2468 consumerSliceLayout
2469 ? SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef())
2471 if (consumerSliceLayout &&
2472 consumerSliceLayout.getDims().asArrayRef().equals(reductionDims)) {
2473 // at the lane level, the consumerSliceLayout can be directly reused
2474 // since the inst_data propagation already insert convert_layout if
2475 // the layout is not consistent
2476 srcLayout = consumerSliceLayout.getParent();
2477 } else {
2478 bool verticalLaneLayout = consumerReductionDims.empty() &&
2479 reductionDims.size() == 1 &&
2480 reductionDims[0] == (srcRank - 1);
2481 auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
2482 srcShape, reductionDims, subgroupSize, maxReduceVectorSize,
2483 verticalLaneLayout);
2484 srcLayout = buildLaneLayout(context, laneLayout, laneData);
2485 }
2486 }
2487
2488 return xegpu::SliceAttr::get(context, srcLayout,
2489 DenseI64ArrayAttr::get(context, reductionDims));
2490}
2491
2492/// Sets up layout for Reduction operations by creating a SliceAttr for the
2493/// result.
2494xegpu::SliceAttr
2496 VectorType srcVecTy,
2497 const xegpu::uArch::uArch *uArch) {
2498
2499 auto srcShape = srcVecTy.getShape();
2500 auto context = srcVecTy.getContext();
2501 auto subgroupSize = uArch->getSubgroupSize();
2502 xegpu::LayoutAttr srcLayout;
2503
2504 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2505 assert(false &&
2506 "subgroup layout assignment not supported for reduction (op "
2507 "is not expected at this level).");
2508 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2509 assert(false &&
2510 "instData layout assignment not supported for reduction (op "
2511 "is not expected at this level).");
2512 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2513 SmallVector<int64_t> laneLayout(1), laneData(1);
2514 laneLayout[0] = std::min(static_cast<int64_t>(subgroupSize), srcShape[0]);
2515 laneData[0] = 1;
2516 srcLayout = buildLaneLayout(context, laneLayout, laneData);
2517 }
2518
2519 auto result = xegpu::SliceAttr::get(context, srcLayout,
2520 DenseI64ArrayAttr::get(context, 0));
2521 return result;
2522}
2523
2524/// Adjusts `consumerLayout`'s innermost-dim data field selected by
2525/// `layoutKind` so that the source layout can be safely inferred by dividing
2526/// that value by `ratio`. Doubles the value until the divisibility constraint
2527/// is met, bounded above by `bound` like result-shape.
2528///
2529/// Used by ops whose source relates to the result by a fixed factor along the
2530/// innermost dim (e.g., bitcast: bitwidth ratio; interleave: 2x).
2531///
2532/// Divisibility constraints per LayoutKind:
2533/// - Subgroup: sgData[innermost] % ratio == 0
2534/// - InstData: instData[innermost] % (laneLayout[innermost] * ratio) == 0
2535/// (laneLayout falls back to subgroupSize if absent)
2536/// - Lane: laneData[innermost] % ratio == 0
2537static xegpu::DistributeLayoutAttr
2538adjustInnermostDimForDivisibility(xegpu::DistributeLayoutAttr consumerLayout,
2539 xegpu::LayoutKind layoutKind,
2540 size_t innerMostDim, int ratio, int64_t bound,
2541 const xegpu::uArch::uArch *uArch) {
2542 SmallVector<int64_t> sgData = consumerLayout.getEffectiveSgDataAsInt();
2543 SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
2544 SmallVector<int64_t> laneData = consumerLayout.getEffectiveLaneDataAsInt();
2545 SmallVector<int64_t> laneLayout =
2546 consumerLayout.getEffectiveLaneLayoutAsInt();
2547
2548 int64_t sgDataValue = -1;
2549 int64_t instDataValue = -1;
2550 int64_t laneDataValue = -1;
2551
2552 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2553 sgDataValue = sgData[innerMostDim];
2554 while ((sgDataValue <= bound) && (sgDataValue % ratio) != 0)
2555 sgDataValue *= 2;
2556 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2557 instDataValue = instData[innerMostDim];
2558 const int innermostDimLaneLayout = laneLayout.empty()
2559 ? uArch->getSubgroupSize()
2560 : laneLayout[innerMostDim];
2561 while ((instDataValue <= bound) &&
2562 (instDataValue % (innermostDimLaneLayout * ratio) != 0))
2563 instDataValue *= 2;
2564 assert((bound % instDataValue) == 0 &&
2565 "bound, instData, and laneLayout for innermost must be 2^n!");
2566 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2567 laneDataValue = laneData[innerMostDim];
2568 while ((laneDataValue <= bound) && (laneDataValue % ratio) != 0)
2569 laneDataValue *= 2;
2570 }
2571
2572 return consumerLayout.setDimData(innerMostDim, sgDataValue, instDataValue,
2573 laneDataValue);
2574}
2575
2576/// Sets up the result layout for a bitcast operation.
2577/// When casting to a smaller bitwidth, adjusts the layout dimensions (sgData,
2578/// instData, or laneData) by multiplying by the bitwidth ratio to ensure the
2579/// result layout can be correctly divided back to the source layout during
2580/// inference.
2581///
2582/// Examples:
2583/// 1. Casting f32 -> f16 (32-bit to 16-bit, bitWidthRatio = 2):
2584/// Consumer layout: instData=[1, 16], subgroupSize=16
2585/// Source shape: [8, 32]
2586/// Result layout: instData=[1, 32] (16 * 2)
2587/// The innermost dimension is multiplied by 2 to maintain consistency.
2588///
2589/// 2. Casting f32 -> i8 (32-bit to 8-bit, bitWidthRatio = 4):
2590/// Consumer instData=[1, 16], subgroupSize=16
2591/// Source shape: [4, 128]
2592/// adjust the instData from [1, 16] to [1, 16 * 4 = 64]
2593///
2594/// 3. Casting i8 -> i32 (8-bit to 32-bit, bitWidthRatio = 1/4):
2595/// Consumer layout: laneLayout=[1, 16], laneData=[1, 4]
2596/// No adjustment needed - returns consumer layout directly.
2597///
2598xegpu::DistributeLayoutAttr xegpu::setupBitCastResultLayout(
2599 xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
2600 DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
2601
2602 int srcElemTyBitWidth = srcVecTy.getElementType().getIntOrFloatBitWidth();
2603 int resElemTyBitWidth = resVecTy.getElementType().getIntOrFloatBitWidth();
2604
2605 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
2606 ArrayRef<int64_t> resShape = resVecTy.getShape();
2607
2608 assert(consumerLayout.getRank() == static_cast<int64_t>(srcShape.size()) &&
2609 "laneData must be available for all dimensions");
2610
2611 // Casting to same/larger element type: result has fewer (or equal) elements
2612 // along the innermost dim, no adjustment needed.
2613 if (srcElemTyBitWidth <= resElemTyBitWidth)
2614 return consumerLayout;
2615
2616 // Casting to smaller element type: result has more elements along innermost
2617 // dim. Adjust the innermost data field upward so the source layout can be
2618 // recovered by dividing by bitWidthRatio.
2619 size_t innerMostDim = srcShape.size() - 1;
2620 int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
2621 return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
2622 innerMostDim, bitWidthRatio,
2623 resShape[innerMostDim], uArch);
2624}
2625
2626/// Sets up the result layout for an interleave operation to ensure the source
2627/// layout can be safely derived. Interleave doubles the innermost dimension,
2628/// so the result layout must ensure that laneData is a multiple
2629/// of 2, and instData must be divisible by innermostDimLaneLayout * 2.
2630///
2631/// Example:
2632/// Interleave: vector<128x256xf4> -> vector<128x512xf4>
2633/// Consumer layout: laneLayout=[1, 16], laneData=[1, 4], instData=[1, 64]
2634/// Result layout adjustment to ensure source can be safely inferred:
2635/// - laneData must be >= 2 and multiple of 2 (so source = laneData/2 is
2636/// valid)
2637/// - instData must be divisible by (16 * 2 = 32) (so source = instData/2 is
2638/// valid)
2639/// - Adjusted instData: ensure (instData % 32 == 0)
2640///
2641xegpu::DistributeLayoutAttr xegpu::setupInterleaveResultLayout(
2642 xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
2643 DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
2644
2645 ArrayRef<int64_t> resShape = resVecTy.getShape();
2646 assert(consumerLayout.getRank() == static_cast<int64_t>(resShape.size()) &&
2647 "consumer layout rank must match source shape rank");
2648
2649 // Interleave doubles the innermost dimension (ratio = 2). Adjust the
2650 // innermost data field so the source layout can be recovered by dividing
2651 // by 2.
2652 const size_t innerMostDim = resShape.size() - 1;
2653 constexpr int ratio = 2;
2654 return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
2655 innerMostDim, ratio,
2656 resShape[innerMostDim], uArch);
2657}
2658
2659/// Sets up the result layout for an insert strided slice operation.
2660/// Creates a result layout based on the specified layout kind (InstData or
2661/// Lane).
2662xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
2663 xegpu::LayoutKind layoutKind, VectorType srcVectorTy,
2664 VectorType resVectorTy, xegpu::DistributeLayoutAttr consumerLayout,
2665 const xegpu::uArch::uArch *uArch) {
2666
2667 xegpu::DistributeLayoutAttr requiredResLayout;
2668 SmallVector<int64_t> consumerInstData =
2669 consumerLayout.getEffectiveInstDataAsInt();
2670 SmallVector<int64_t> consumerLaneData =
2671 consumerLayout.getEffectiveLaneDataAsInt();
2672 SmallVector<int64_t> consumerLaneLayout =
2673 consumerLayout.getEffectiveLaneLayoutAsInt();
2674 ArrayRef<int64_t> srcShape = srcVectorTy.getShape();
2675 int64_t laneDataValue = -1;
2676
2677 requiredResLayout = consumerLayout;
2678 int srcRank = srcShape.size();
2679
2680 if (layoutKind == xegpu::LayoutKind::Subgroup ||
2681 layoutKind == xegpu::LayoutKind::InstData) {
2682 assert(false && "subgroup/instData layout assignment not supported for "
2683 "insertStridedSlice.");
2684 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2685 for (int dim = 0; dim < srcRank; dim++) {
2686 assert(srcShape[dim] % consumerLaneLayout[dim] == 0 &&
2687 "srcShape must be divisible by laneLayout for all dimensions");
2688 laneDataValue = std::min(srcShape[dim] / consumerLaneLayout[dim],
2689 consumerLaneData[dim]);
2690 requiredResLayout =
2691 requiredResLayout.setDimData(dim, -1, -1, laneDataValue);
2692 }
2693 }
2694 return requiredResLayout;
2695}
2696
2697/// Back-propagates a known result layout to the layout required on `operand`
2698/// for a non-anchor (layout-propagating) vector op. Dispatches on the op kind —
2699/// broadcast, (multi)reduction, bitcast, shape/transpose, insert/extract,
2700/// interleave, etc. — applying the shape/permutation/bitwidth transform to
2701/// derive the source layout; elementwise and pass-through ops reuse resLayout
2702/// as-is. Returns nullptr for unknown ops or an absent result layout.
2703xegpu::DistributeLayoutAttr xegpu::inferSourceLayoutFromResultForNonAnchorOp(
2704 OpOperand &operand, xegpu::DistributeLayoutAttr resLayout) {
2705 if (!resLayout)
2706 return nullptr;
2707 Operation *op = operand.getOwner();
2708 unsigned idx = operand.getOperandNumber();
2709
2710 // For vector::BroadcastOp, infer the source layout from the result layout.
2711 if (auto broadcast = dyn_cast<vector::BroadcastOp>(op)) {
2712 auto srcTy = dyn_cast<VectorType>(broadcast.getSourceType());
2713 if (!srcTy)
2714 return nullptr;
2716 resLayout, broadcast.getResultVectorType().getShape(),
2717 srcTy.getShape());
2718 }
2719
2720 // For vector::MultiDimReductionOp, infer source layout from result layout
2721 // using reduction dims. Acc operand is expected to have the same layout as
2722 // the result.
2723 if (auto reduction = dyn_cast<vector::MultiDimReductionOp>(op)) {
2724 if (idx == 0) {
2725 SmallVector<int64_t> reductionDims(reduction.getReductionDims());
2726 return xegpu::inferMultiReductionSourceLayout(resLayout, reductionDims);
2727 }
2728 if (idx == 1)
2729 return resLayout;
2730 }
2731
2732 if (auto reduction = dyn_cast<vector::ReductionOp>(op))
2733 return xegpu::inferReductionSourceLayout(resLayout);
2734
2735 // For vector::BitCastOp, infer source layout from result layout using
2736 // element type bitwidths.
2737 if (auto bitcast = dyn_cast<vector::BitCastOp>(op)) {
2738 int resElemBitWidth =
2739 bitcast.getResultVectorType().getElementType().getIntOrFloatBitWidth();
2740 int srcElemBitWidth =
2741 bitcast.getSourceVectorType().getElementType().getIntOrFloatBitWidth();
2742 return xegpu::inferBitCastSourceLayout(resLayout, resElemBitWidth,
2743 srcElemBitWidth);
2744 }
2745
2746 // For vector::ShapeCastOp, infer source layout from result layout using
2747 // shapes.
2748 if (auto shapeCast = dyn_cast<vector::ShapeCastOp>(op)) {
2750 resLayout, shapeCast.getResultVectorType().getShape(),
2751 shapeCast.getSourceVectorType().getShape());
2752 }
2753
2754 // For vector::InsertStridedSliceOp, infer source layout from result
2755 // layout. Dest vector must have the same layout as the result.
2756 if (auto insertSlice = dyn_cast<vector::InsertStridedSliceOp>(op)) {
2757 if (idx == 0) {
2759 resLayout, insertSlice.getDestVectorType().getShape(),
2760 insertSlice.getSourceVectorType().getShape());
2761 }
2762 if (idx == 1)
2763 return resLayout;
2764 }
2765
2766 // For vector::Insert Op, infer source layout from result layout using
2767 // shapes.
2768 if (auto insert = dyn_cast<vector::InsertOp>(op)) {
2769 VectorType resVecTy = dyn_cast<VectorType>(insert.getResult().getType());
2770 VectorType valueToStoreTy =
2771 dyn_cast<VectorType>(insert.getValueToStore().getType());
2772
2773 if ((idx == 0) && valueToStoreTy) {
2774 return xegpu::inferInsertSourceLayout(resLayout, resVecTy.getShape(),
2775 valueToStoreTy.getShape());
2776 }
2777 if (idx == 1)
2778 return resLayout;
2779 }
2780
2781 // For vector::Extract Op, infer source layout from result layout using
2782 // shapes.
2783 if (auto extract = dyn_cast<vector::ExtractOp>(op)) {
2784 VectorType srcVecTy = dyn_cast<VectorType>(extract.getSource().getType());
2785 VectorType resVecTy = dyn_cast<VectorType>(extract.getResult().getType());
2786 if (!srcVecTy || !resVecTy)
2787 return nullptr;
2788 return xegpu::inferExtractSourceLayout(resLayout, resVecTy.getShape(),
2789 srcVecTy.getShape());
2790 }
2791
2792 // For vector::TransposeOp, infer source layout from result layout using
2793 // permutation.
2794 if (auto transpose = dyn_cast<vector::TransposeOp>(op)) {
2795 return xegpu::inferTransposeSourceLayout(resLayout,
2796 transpose.getPermutation());
2797 }
2798
2799 // For vector::BitCastOp, infer source layout from result layout using
2800 // element type bitwidths.
2801 if (auto bitcast = dyn_cast<vector::BitCastOp>(op)) {
2802 int resElemBitWidth =
2803 bitcast.getResultVectorType().getElementType().getIntOrFloatBitWidth();
2804 int srcElemBitWidth =
2805 bitcast.getSourceVectorType().getElementType().getIntOrFloatBitWidth();
2806 return xegpu::inferBitCastSourceLayout(resLayout, resElemBitWidth,
2807 srcElemBitWidth);
2808 }
2809
2810 // for vector::interleave
2811 if (auto interleave = dyn_cast<vector::InterleaveOp>(op)) {
2812 return xegpu::inferInterleaveSourceLayout(resLayout);
2813 }
2814
2815 // for vector::deinterleave
2816 if (auto deinterleave = dyn_cast<vector::DeinterleaveOp>(op)) {
2817 return xegpu::inferDeinterleaveSourceLayout(resLayout);
2818 }
2819
2820 // For vector::ExtractStridedSliceOp, simply return result layout
2821 if (dyn_cast<vector::ExtractStridedSliceOp>(op))
2822 return resLayout;
2823
2824 // For elementwise operations, all operands must have the same layout as
2825 // the result.
2827 return resLayout;
2828
2829 return nullptr;
2830}
2831
2832/// Returns the layout required on `operand`: anchor ops report their declared
2833/// per-operand layout directly; non-anchor ops back-derive it from their result
2834/// layout via inferSourceLayoutFromResultForNonAnchorOp.
2835xegpu::DistributeLayoutAttr xegpu::getConsumerLayoutAt(OpOperand &operand) {
2836 Operation *op = operand.getOwner();
2837 // Anchor ops declare the layout they
2838 // require on each operand. Trust that declaration directly so that
2839 // ResolveLayoutConflicts compares producer-vs-declared
2840 if (isa<xegpu::AnchorLayoutInterface>(op))
2841 return xegpu::getDistributeLayoutAttr(operand);
2842 // For non-anchor ops, derive the operand layout from the op's result
2843 // layout via op-specific semantics.
2844 xegpu::DistributeLayoutAttr resLayout;
2845 if (op->getNumResults() == 1 || isa<vector::DeinterleaveOp>(op))
2846 resLayout = xegpu::getDistributeLayoutAttr(op->getResult(0));
2847 return inferSourceLayoutFromResultForNonAnchorOp(operand, resLayout);
2848}
return success()
static void visit(Operation *op, DenseSet< Operation * > &visited)
Visits all the pdl.operand(s), pdl.result(s), and pdl.operation(s) connected to the given operation.
Definition PDL.cpp:62
lhs
static Value broadcast(Location loc, Value toBroadcast, unsigned numElements, const TypeConverter &typeConverter, ConversionPatternRewriter &rewriter)
Broadcasts the value to vector with numElements number of elements.
static xegpu::LayoutAttr buildLayout(mlir::MLIRContext *context, ArrayRef< int64_t > sgLayout, ArrayRef< int64_t > sgData, ArrayRef< int64_t > instData, ArrayRef< int64_t > laneLayout, ArrayRef< int64_t > laneData, DenseI32ArrayAttr orderAttr=nullptr)
static xegpu::DistributeLayoutAttr getStoreSubgroupLayouts(mlir::MLIRContext *context, ArrayRef< int64_t > wgShape, ArrayRef< int64_t > instData, int numSg)
Picks the subgroup layout for a scatter-style store (store_scatter / store_matrix): the most balanced...
static xegpu::DistributeLayoutAttr createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy, VectorType scaleTy, xegpu::DistributeLayoutAttr matrixLayout, bool isBScale, const xegpu::uArch::uArch *uArch)
Helper to create a scale layout derived from a matrix operand layout.
static bool leadingDimsAreUnit(ArrayRef< int64_t > shape, int numInnerDims)
Returns true if every dimension of shape except the innermost numInnerDims is a unit (size-1) dimensi...
static xegpu::DistributeLayoutAttr adjustInnermostDimForDivisibility(xegpu::DistributeLayoutAttr consumerLayout, xegpu::LayoutKind layoutKind, size_t innerMostDim, int ratio, int64_t bound, const xegpu::uArch::uArch *uArch)
Adjusts consumerLayout's innermost-dim data field selected by layoutKind so that the source layout ca...
static std::pair< SmallVector< int64_t >, SmallVector< int64_t > > compute2DBlockIOLaneLayoutAndData(ArrayRef< int64_t > instShape, int64_t subgroupSize, int64_t bitwidth, int64_t packingSize, bool transform=false)
static std::pair< SmallVector< int64_t >, SmallVector< int64_t > > computeScatterIOLaneLayoutAndData(ArrayRef< int64_t > instShape, int64_t subgroupSize, int64_t maxChunkSize)
Computes lane_layout and lane_data for scatter-style store anchor layouts (store scatter,...
static xegpu::LayoutAttr buildInstDataLayoutWithLane(mlir::MLIRContext *context, ArrayRef< int64_t > instData, ArrayRef< int64_t > laneLayout, ArrayRef< int64_t > laneData, DenseI32ArrayAttr orderAttr=nullptr)
static xegpu::LayoutAttr buildSgLayout(mlir::MLIRContext *context, ArrayRef< int64_t > wgTileShape, ArrayRef< int64_t > sgLayout, int dimK=-1, DenseI32ArrayAttr orderAttr=nullptr)
static std::pair< SmallVector< int64_t >, SmallVector< int64_t > > computeReductionLaneLayoutAndData(ArrayRef< int64_t > srcShape, ArrayRef< int64_t > reductionDims, int subgroupSize, int64_t maxReduceVectorSize, bool verticalLaneLayout=false)
Computes the (lane_layout, lane_data) for a multi-reduction's source layout.
static std::optional< SmallVector< int64_t > > get2DBlockIOInstDataLayout(ArrayRef< int64_t > dataShape, Type elemTy, const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction, bool transform=false, bool transpose=false)
Helper function to compute inst_data vectors for DPAS operands A, B, and C/D.
static std::optional< std::tuple< xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr > > getDpasSubgroupLayouts(mlir::MLIRContext *context, VectorType aTy, VectorType bTy, VectorType cdTy, xegpu::DistributeLayoutAttr consumerLayout, int numSg, std::tuple< SmallVector< int64_t >, SmallVector< int64_t >, SmallVector< int64_t > > instDataVecs)
Helper function to set up subgroup layouts for DPAS operands A, B, and C/D.
static SmallVector< LayoutRepresentation > enumerateFactorizations(int64_t total, int64_t rank)
Enumerates all ways to split total into rank factors whose product equals total.
static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(xegpu::LayoutKind layoutKind, mlir::MLIRContext *context, xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize, ArrayRef< int64_t > resShape, int subgroupSize)
Sets up the anchor layout for load gather and load matrix operation.
SmallVector< int64_t > LayoutRepresentation
static xegpu::DistributeLayoutAttr getLayoutFromUsePoints(Value result)
static xegpu::DistributeLayoutAttr setupGenericStoreAnchorLayout(xegpu::LayoutKind layoutKind, mlir::MLIRContext *context, int maxChunkSize, ArrayRef< int64_t > srcShape, int subgroupSize, int numSg)
Sets up the anchor layout for store scatter and store matrix operation, which share the same logic.
static SmallVector< LayoutRepresentation > getSgLayoutCandidates(ArrayRef< int64_t > wgShape, ArrayRef< int64_t > instData, int64_t sgCount)
static void propagateResultsToRegularOperands(Operation *op)
static void propagateRegionResultsToYieldOperands(mlir::RegionBranchTerminatorOpInterface yieldOp)
static bool isValidLaneLayout(ArrayRef< int64_t > dataShape, ArrayRef< int64_t > laneLayout, ArrayRef< int64_t > laneData)
static void setTensorDescLayout(Value val, xegpu::DistributeLayoutAttr layout)
static void walkRegionBackward(Region &region, llvm::function_ref< void(Operation *)> visit)
static xegpu::LayoutAttr buildLaneLayout(mlir::MLIRContext *context, ArrayRef< int64_t > laneLayout, ArrayRef< int64_t > laneData, DenseI32ArrayAttr orderAttr=nullptr)
static std::optional< std::tuple< SmallVector< int64_t >, SmallVector< int64_t >, SmallVector< int64_t > > > getDpasInstDataLayouts(VectorType aTy, VectorType bTy, VectorType cdTy, const xegpu::uArch::MMAInstructionInterface *uArchInstruction)
Helper function to compute inst_data vectors for DPAS operands A, B, and C/D.
Block represents an ordered list of Operations.
Definition Block.h:33
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
This is a value defined by a result of an operation.
Definition Value.h:454
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
unsigned getBeginOperandIndex() const
Return the operand index of the first element of this range.
type_range getType() const
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool hasAttrOfType(NameT &&name)
Definition Operation.h:600
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
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:699
MutableArrayRef< OpOperand > getOpOperands()
Definition Operation.h:408
auto getDiscardableAttrs()
Return a range of all of discardable attributes on this operation.
Definition Operation.h:511
Attribute removeDiscardableAttr(StringAttr name)
Remove the discardable attribute with the specified name if it exists.
Definition Operation.h:497
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
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
Attribute removeAttr(StringAttr name)
Remove the attribute with the specified name if it exists.
Definition Operation.h:625
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class represents a point being branched from in the methods of the RegionBranchOpInterface.
This class represents a successor of a region.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
bool empty()
Definition Region.h:60
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class 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
void setType(Type newType)
Mutate the type of this Value to be of the specified type.
Definition Value.h:116
Type getType() const
Return the type of this value.
Definition Value.h:105
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int32_t > content)
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
bool hasElementwiseMappableTraits(Operation *op)
Together, Elementwise, Scalarizable, Vectorizable, and Tensorizable provide an easy way for scalar op...
DistributeLayoutAttr inferShapeCastSourceLayout(DistributeLayoutAttr resLayout, ArrayRef< int64_t > resShape, ArrayRef< int64_t > srcShape)
Infers the source layout attribute for a shape cast operation given the result layout attribute,...
bool matchDimCollapse(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< SmallVector< int64_t > > &collapseDims)
DistributeLayoutAttr setupLoadNdAnchorLayout(LayoutKind layoutKind, VectorType vectorTy, DistributeLayoutAttr consumerLayout, int numSg, const uArch::uArch *uArch)
Sets up the anchor layout for a load_nd operation.
DistributeLayoutAttr inferResultLayoutFromSourceForNonAnchorOp(Operation *op, ArrayRef< DistributeLayoutAttr > operandLayouts)
Infers the result layout attribute for a non-anchor operation from the layouts of its source operands...
DistributeLayoutAttr setupLoadMatrixAnchorLayout(LayoutKind layoutKind, VectorType vectorTy, int contigChunkSize, DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch)
Sets up the anchor layout for load matrix operation.
DistributeLayoutAttr setupInterleaveResultLayout(LayoutKind layoutKind, VectorType srcVectorTy, VectorType resVectorTy, DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch)
Sets up the result layout for an interleave operation to ensure the source layout can be safely deriv...
DistributeLayoutAttr inferTransposeSourceLayout(DistributeLayoutAttr resLayout, ArrayRef< int64_t > permutation)
Infers the source layout attribute for a transpose operation given the result layout attribute and pe...
DistributeLayoutAttr inferInsertSourceLayout(DistributeLayoutAttr resLayout, ArrayRef< int64_t > resShape, ArrayRef< int64_t > srcShape)
Infers the source layout attribute for an insert operation.
std::optional< std::tuple< DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr > > completeDpasMxLaneLayoutFromInstData(DistributeLayoutAttr aLayout, DistributeLayoutAttr bLayout, DistributeLayoutAttr cdLayout, VectorType aTy, VectorType bTy, VectorType cdTy, VectorType aScaleTy, VectorType bScaleTy, const uArch::uArch *uArch)
Like completeDpasLaneLayoutFromInstData, but for dpas_mx: additionally re-derives the A_scale / B_sca...
DistributeLayoutAttr inferInsertStridedSliceSourceLayout(DistributeLayoutAttr resLayout, ArrayRef< int64_t > resShape, ArrayRef< int64_t > srcShape)
Infers the source layout attribute for an insert strided slice operation given the result layout attr...
DistributeLayoutAttr setupStoreMatrixAnchorLayout(LayoutKind layoutKind, VectorType vectorTy, int contigChunkSize, int numSg, const uArch::uArch *uArch)
Sets up the anchor layout for a store matrix operation.
void removeTemporaryLayoutAttrs(Operation *op)
Removes the temporary layout attributes for each OpOperand and OpResult of the given operation.
std::optional< std::tuple< DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr > > completeDpasLaneLayoutFromInstData(DistributeLayoutAttr aLayout, DistributeLayoutAttr bLayout, DistributeLayoutAttr cdLayout, VectorType aTy, VectorType bTy, VectorType cdTy, const uArch::uArch *uArch)
Completes user-provided DPAS A/B/C-D anchors that carry only inst_data by filling in lane_layout / la...
void setTemporaryLayout(const T &operandOrResult, const DistributeLayoutAttr layout)
LayoutKind
Specifies the level of a layout hierarchy for comparison or propagation.
Definition XeGPU.h:32
void setDistributeLayoutAttr(const OpResult &Result, const DistributeLayoutAttr layout)
[to-be-deprecated] Sets the DistributeLayoutAttr for a given OpResult user should use setAnchorLayout...
SmallVector< NamedAttribute > dropInstDataOnAttrs(ArrayRef< NamedAttribute > attrs)
Updates the NamedAttribute sequence by dropping inst-data information from any DistributeLayoutAttr f...
DistributeLayoutAttr inferSourceLayoutFromResultForNonAnchorOp(OpOperand &operand, DistributeLayoutAttr resLayout)
Infers the source layout attribute for an operand using result layout attribute.
DistributeLayoutAttr inferInterleaveSourceLayout(DistributeLayoutAttr resLayout)
Infers the source layout attribute for an interleave operation given the result layout attribute.
bool matchUnitDimExpansion(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< int64_t > &expandedUnitDims)
int getLargestDivisor(T dim, ArrayRef< T > candidates, ArrayRef< T > candidateMultiples={})
Helper Function to find a proper instruction multiple for the user-supplied sg-level data shape (dive...
bool recoverTemporaryLayouts(Operation *rootOp)
Attach layout attributes to all vector-type operands of operations within the given operation's neste...
DistributeLayoutAttr inferBroadcastSourceLayout(DistributeLayoutAttr resLayout, ArrayRef< int64_t > resShape, ArrayRef< int64_t > srcShape)
Infers the source layout attribute for a broadcast operation given the result layout attribute,...
std::optional< std::tuple< DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr > > setupDpasMxLayout(LayoutKind layoutKind, VectorType aTy, VectorType bTy, VectorType cdTy, VectorType aScaleTy, VectorType bScaleTy, DistributeLayoutAttr consumerLayout, int numSg, const uArch::uArch *uArch)
Sets up the anchor layouts for dpas_mx operands (A, B, C/D, A_scale, and B_scale).
SliceAttr setupMultiReductionResultLayout(LayoutKind layoutKind, VectorType srcVectorTy, DistributeLayoutAttr consumerLayout, SmallVector< int64_t > reductionDims, int numSg, const uArch::uArch *uArch)
Note on the consumerLayout argument used by the consumer-driven setup* / complete* helpers below:
DistributeLayoutAttr setupLoadGatherAnchorLayout(LayoutKind layoutKind, VectorType vectorTy, int contigChunkSize, DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch)
Sets up the anchor layout for a load gather operation.
llvm::function_ref< DistributeLayoutAttr(Value)> GetLayoutFnTy
Callable returning the propagated layout for a given Value, used by the layout-propagation helpers be...
std::optional< DistributeLayoutAttr > completeScatterLoadLaneLayoutFromInstData(DistributeLayoutAttr userSpecifiedLayout, DistributeLayoutAttr consumerLayout, Type elemTy, const xegpu::uArch::LoadGatherInstruction *uArchInstruction, const int subgroupSize)
If the consumer layout has only inst_data (no lane_layout/lane_data), completes it by running the cor...
bool matchSplitDimExpansion(ArrayRef< int64_t > src, ArrayRef< int64_t > dst, SmallVector< SmallVector< int64_t > > &splitDimGroups)
DistributeLayoutAttr setupStoreScatterAnchorLayout(LayoutKind layoutKind, VectorType vectorTy, int contigChunkSize, int numSg, const uArch::uArch *uArch)
Sets up the anchor layout for a store scatter operation.
DistributeLayoutAttr setupBitCastResultLayout(LayoutKind layoutKind, VectorType srcVectorTy, VectorType resVectorTy, DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch)
Setup the result layout attribute for a bitcast operation based on element type bitwidths.
void removeLayoutAttr(const T &operandOrResult)
Removes the LayoutAttr for a given OpOperand or OpResult if it exists.
DistributeLayoutAttr inferMaskOffsetLayoutForScatterIO(DistributeLayoutAttr payloadLayout, int chunkSize)
Infers the layout attribute for mask and offset operand for Chunked load and store,...
DistributeLayoutAttr getDistributeLayoutAttr(const Value value)
Retrieves the DistributeLayoutAttr associated with a given Value.
SmallVector< NamedAttribute > dropSgLayoutAndDataOnAttrs(ArrayRef< NamedAttribute > attrs)
Updates the NamedAttribute sequence by dropping sg-layout and sg-data information from any Distribute...
DistributeLayoutAttr setupPrefetchNdAnchorLayout(LayoutKind layoutKind, TensorDescType tdescTy, int numSg, const uArch::uArch *uArch)
Sets up the anchor layout for a prefetch_nd operation.
LogicalResult propagateYieldOperandsToRegionResults(RegionBranchTerminatorOpInterface terminator, GetLayoutFnTy getLayoutOfValue)
Propagate layouts from a region branch terminator's forwarded operands to the matching region results...
DistributeLayoutAttr inferShapeCastResultLayout(DistributeLayoutAttr srcLayout, ArrayRef< int64_t > srcShape, ArrayRef< int64_t > resShape)
Infers the result layout attribute for a shape cast operation given the source layout attribute,...
DistributeLayoutAttr inferExtractSourceLayout(DistributeLayoutAttr resLayout, ArrayRef< int64_t > resShape, ArrayRef< int64_t > srcShape)
Infers the source layout attribute for an extract operation.
std::string getTemporaryLayoutName(const OpOperand &operand)
Return the attribute name for the OpOperand to attach DistributeLayoutAttr.
DistributeLayoutAttr inferBitCastSourceLayout(DistributeLayoutAttr resLayout, int resElemTyBitWidth, int srcElemTyBitWidth)
Infers the source layout attribute for a bitcast operation given the result layout attribute,...
DistributeLayoutAttr setupInsertStridedSliceResultLayout(LayoutKind layoutKind, VectorType srcVectorTy, VectorType resVectorTy, DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch)
Sets up the result layout for an insert strided slice operation.
DistributeLayoutAttr inferReductionSourceLayout(DistributeLayoutAttr resLayout)
Infers the source layout attribute for a reduction operation given the result layout attribute and re...
std::optional< DistributeLayoutAttr > completeScatterStoreLaneLayoutFromInstData(DistributeLayoutAttr specifiedLayout, Type elemTy, const xegpu::uArch::StoreScatterInstruction *uArchInstruction, const int subgroupSize)
Like completeScatterLoadLaneLayoutFromInstData, but for scatter stores (store_scatter / store_matrix)...
std::optional< DistributeLayoutAttr > completeBlockStoreLaneLayoutFromInstData(DistributeLayoutAttr specifiedLayout, Type elemTy, const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction, const int subgroupSize)
Completes a user-provided 2D-block store_nd / prefetch_nd anchor that has only inst_data.
DistributeLayoutAttr inferDeinterleaveSourceLayout(DistributeLayoutAttr resLayout)
Infers the source layout attribute for a deinterleave operation given the result layout attribute.
DistributeLayoutAttr getConsumerLayoutAt(OpOperand &operand)
Gets the expected layout for a given consumer operand.
void removeLayoutAttrs(Operation *op)
Removes the DistributeLayoutAttr for each OpOperand and OpResult of the given operation if they exist...
DistributeLayoutAttr inferMultiReductionSourceLayout(DistributeLayoutAttr resLayout, SmallVector< int64_t > reduceDims)
Infers the source layout attribute for a reduction operation given the result layout attribute and re...
bool isTriviallyRematerializable(Operation *op)
Returns true if op is safe and cheap to clone: it has no side effects, no regions,...
DistributeLayoutAttr setupStoreNdAnchorLayout(LayoutKind layoutKind, VectorType vectorTy, int numSg, const uArch::uArch *uArch)
Sets up the anchor layout for a store_nd operation.
DistributeLayoutAttr inferTransposeResultLayout(DistributeLayoutAttr srcLayout, ArrayRef< int64_t > permutation)
Infers the result layout attribute for a transpose operation given the source layout attribute and pe...
std::optional< DistributeLayoutAttr > completeBlockLoadLaneLayoutFromInstData(DistributeLayoutAttr specifiedLayout, DistributeLayoutAttr consumerLayout, Type elemTy, const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction, const int subgroupSize)
Like completeBlockStoreLaneLayoutFromInstData, but for load_nd.
LogicalResult propagateRegionArgsToInits(RegionBranchOpInterface regionOp, GetLayoutFnTy getLayoutOfValue)
Propagate layouts from a region branch op's region entry block arguments back to its init operands.
std::optional< std::tuple< DistributeLayoutAttr, DistributeLayoutAttr, DistributeLayoutAttr > > setupDpasLayout(LayoutKind layoutKind, VectorType aTy, VectorType bTy, VectorType cdTy, DistributeLayoutAttr consumerLayout, int numSg, const uArch::uArch *uArch)
Sets up the anchor layouts for a dpas operands (A, B, and C/D).
SliceAttr setupReductionResultLayout(LayoutKind layoutKind, VectorType srcVectorTy, const uArch::uArch *uArch)
Sets up layout for Reduction operations by creating a SliceAttr for the result.
Include the generated interface declarations.
DenseMap< OpOperand *, SmallVector< Value > > RegionBranchSuccessorMapping
A mapping from successor operands to successor inputs.
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
detail::DenseArrayAttrImpl< int32_t > DenseI32ArrayAttr
std::optional< SmallVector< int64_t > > computeShapeRatio(ArrayRef< int64_t > shape, ArrayRef< int64_t > subShape)
Return the multi-dimensional integral ratio of subShape to the trailing dimensions of shape.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
virtual int32_t getPackedFormatBitSize() const =0
std::optional< BlockShapes > getBlockWidthHeightCount(Type elemTy, bool hasTransform=false, bool hasTranspose=false, bool upConv=false) const
Definition uArchBase.h:175
int32_t getMaxLaneAccessSizeBytes() const override
Definition uArchBase.h:216
virtual llvm::SmallVector< uint32_t, 8 > getSupportedN(Type type) const =0
virtual llvm::SmallVector< uint32_t, 8 > getSupportedK(Type type) const =0
virtual llvm::SmallVector< uint32_t, 8 > getSupportedM(Type type) const =0
int32_t getMaxLaneAccessSizeBytes() const override
Definition uArchBase.h:221
virtual int getSubgroupSize() const =0
const Instruction * getInstruction(InstructionKind instKind) const
Definition uArchBase.h:115