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// `broadcastDim` (default -1 = none) marks a dimension broadcast across
1071// subgroups rather than distributed (e.g. the K/contraction dim of a DPAS
1072// operand). Its full extent stays in every subgroup, so rule 1 is skipped for
1073// it, but rule 2 (multiple of instData) still applies.
1074//
1075// Example (2D):
1076// wgShape = [128, 64], instData = [8, 16], sgCount = 32
1077// Returns: [[8,4], [16,2]], corresponding to sgData [16,16] and [8,32].
1080 int64_t sgCount, int64_t broadcastDim = -1) {
1081 int64_t rank = wgShape.size();
1082 assert(rank > 0 && "wgShape must be non-empty");
1083 assert(static_cast<int64_t>(instData.size()) == rank &&
1084 "instData rank must match wgShape rank");
1085
1086 // Step 1: Get all N-D factorizations of sgCount.
1087 auto allFactorizations = enumerateFactorizations(sgCount, rank);
1088
1089 // Step 2: Filter to keep only valid candidates.
1091 for (const auto &sgLayout : allFactorizations) {
1092 bool valid = true;
1093 for (int64_t dim = 0; dim < rank; ++dim) {
1094 // A broadcast dim keeps its full extent in every subgroup; others are
1095 // split evenly by sgLayout[dim].
1096 int64_t sgData;
1097 if (dim == broadcastDim) {
1098 sgData = wgShape[dim];
1099 } else {
1100 if (wgShape[dim] % sgLayout[dim] != 0) {
1101 valid = false;
1102 break;
1103 }
1104 sgData = wgShape[dim] / sgLayout[dim];
1105 }
1106 if (sgData % instData[dim] != 0) {
1107 valid = false;
1108 break;
1109 }
1110 }
1111 if (valid)
1112 candidates.push_back(sgLayout);
1113 }
1114
1115 // Step 3: Sort by balance (smallest max-min spread), then lexicographic.
1116 llvm::sort(candidates, [](const LayoutRepresentation &lhs,
1117 const LayoutRepresentation &rhs) {
1118 int64_t spreadLhs = *llvm::max_element(lhs) - *llvm::min_element(lhs);
1119 int64_t spreadRhs = *llvm::max_element(rhs) - *llvm::min_element(rhs);
1120 if (spreadLhs != spreadRhs)
1121 return spreadLhs < spreadRhs;
1122 return lhs < rhs;
1123 });
1124 return candidates;
1125}
1126
1127/// Helper function to compute inst_data vectors for DPAS operands A, B, and
1128/// C/D.
1129static std::optional<SmallVector<int64_t>> get2DBlockIOInstDataLayout(
1130 ArrayRef<int64_t> dataShape, Type elemTy,
1131 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
1132 bool transform = false, bool transpose = false) {
1133 int rank = dataShape.size();
1134 auto blockWHC =
1135 uArchInstruction->getBlockWidthHeightCount(elemTy, transform, transpose);
1136 if (!blockWHC)
1137 return std::nullopt;
1138 auto [bWidths, bHeights, bCounts] = blockWHC.value();
1139 // Compute inst_data from hardware block params. For Nd ops, the lane
1140 // factorization above (laneLayout / laneData) is rigid; inst_data must be
1141 // a multiple of lane_layout * lane_data on each dim (Category A
1142 // invariant).
1143 SmallVector<int64_t> instData(rank, 1);
1144 assert(rank >= 2 && "dataShape must be at least 2D for 2D-block IO");
1145 int instWidth =
1146 xegpu::getLargestDivisor(static_cast<int>(dataShape.back()), bWidths);
1147 int instHeight =
1148 xegpu::getLargestDivisor(static_cast<int>(dataShape[rank - 2]), bHeights);
1149 instData.back() = instWidth;
1150 instData[rank - 2] = instHeight;
1151
1152 return instData;
1153}
1154
1155/// Helper function to compute inst_data vectors for DPAS operands A, B, and
1156/// C/D. Look up the uArch table and search for the largest supported block size
1157/// that divides the data shape
1158static std::optional<std::tuple<SmallVector<int64_t>, SmallVector<int64_t>,
1161 VectorType aTy, VectorType bTy, VectorType cdTy,
1162 const xegpu::uArch::MMAInstructionInterface *uArchInstruction) {
1163
1164 // M dimension is the second-to-last dim of A (handles batch dims).
1165 const unsigned dataALen = aTy.getShape()[aTy.getRank() - 2];
1166 auto supportedALen = uArchInstruction->getSupportedM(aTy.getElementType());
1167 const int maxALen =
1168 xegpu::getLargestDivisor(dataALen, ArrayRef<unsigned>(supportedALen));
1169
1170 // N dimension is the last dim of B.
1171 const unsigned dataBLen = bTy.getShape().back();
1172 auto supportedBLen = uArchInstruction->getSupportedN(bTy.getElementType());
1173 const int maxBLen =
1174 xegpu::getLargestDivisor(dataBLen, ArrayRef<unsigned>(supportedBLen));
1175
1176 auto supportedCLen = uArchInstruction->getSupportedN(cdTy.getElementType());
1177 const int maxCLen =
1178 xegpu::getLargestDivisor(dataBLen, ArrayRef<unsigned>(supportedCLen));
1179 if (maxALen == -1 || maxBLen == -1 || maxCLen == -1)
1180 return std::nullopt;
1181
1182 auto supportedKLen = uArchInstruction->getSupportedK(aTy.getElementType());
1183 if (supportedKLen.empty())
1184 return std::nullopt;
1185 auto kDimSize = supportedKLen[0];
1186
1187 SmallVector<int64_t> instDataA(aTy.getRank(), 1);
1188 instDataA[aTy.getRank() - 2] = maxALen;
1189 instDataA[aTy.getRank() - 1] = kDimSize;
1190 SmallVector<int64_t> instDataB(bTy.getRank(), 1);
1191 instDataB[bTy.getRank() - 2] = kDimSize;
1192 instDataB[bTy.getRank() - 1] = maxBLen;
1193 SmallVector<int64_t> instDataCD(cdTy.getRank(), 1);
1194 instDataCD[cdTy.getRank() - 2] = maxALen;
1195 instDataCD[cdTy.getRank() - 1] = maxCLen;
1196 return std::make_tuple(instDataA, instDataB, instDataCD);
1197}
1198
1199/// Computes lane_layout and lane_data for scatter-style store anchor layouts
1200/// (store scatter, store matrix). Lanes and the per-lane vector both live on
1201/// the innermost dim:
1202/// - laneLayout[innermost] = min(subgroupSize, srcShape[innermost])
1203/// - laneData[innermost] = min(srcShape[innermost] / laneLayout[innermost],
1204/// maxChunkSize)
1205/// All other entries are 1.
1206static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1208 int64_t subgroupSize, int64_t maxChunkSize) {
1209 int64_t rank = instShape.size();
1210 SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
1211 int64_t innermost = rank - 1;
1212 laneLayout[innermost] = std::min(subgroupSize, instShape[innermost]);
1213 laneData[innermost] =
1214 std::min(instShape[innermost] / laneLayout[innermost], maxChunkSize);
1215 return {laneLayout, laneData};
1216}
1217
1218// Computes the per-lane layout and data for a 2D block load/store/prefetch:
1219// lanes are spread across the subgroup along the last dim (or rank-2 if
1220// transposed), and laneData packs sub-bitwidth elements along the packing dim.
1221static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1223 int64_t subgroupSize, int64_t bitwidth,
1224 int64_t packingSize, bool transform = false) {
1225 int64_t rank = instShape.size();
1226 SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
1227 int kDim = transform ? rank - 2 : rank - 1;
1228 unsigned vnniFactor = packingSize / bitwidth;
1229 laneData[kDim] = bitwidth < packingSize ? vnniFactor : 1;
1230 laneLayout.back() =
1231 std::min(subgroupSize, instShape.back() / laneData.back());
1232
1233 // assert that the lane layout and data fit in the inst shape
1234 for (int64_t i = 0; i < rank; ++i) {
1235 int64_t laneProduct = laneLayout[i] * laneData[i];
1236 assert(instShape[i] % laneProduct == 0 &&
1237 "lane_layout * lane_data must evenly divide the inst shape");
1238 (void)laneProduct;
1239 }
1240 return {laneLayout, laneData};
1241}
1242
1243/// Computes the (lane_layout, lane_data) for a multi-reduction's source layout.
1244/// Only the innermost two dims are distributed; leading dims are assumed unit.
1245/// `subgroupSize` lanes go on one dim; up to `maxReduceVectorSize` elements are
1246/// packed into lane_data on the other. To minimize cross-lane reduction, lanes
1247/// are spread across a non-reduction dim when possible so the reduction happens
1248/// within a lane. inst_data is the element-wise product lane_layout *
1249/// lane_data.
1250///
1251/// e.g. with srcShape=[32, 128], subgroupSize=16, maxReduceVectorSize=2:
1252/// - Switch: reductionDims=[1] and consumerReductionDims=[] -> lanes move
1253/// to the non-reduction dim 0: lane_layout=[16, 1], lane_data=[1, 2].
1254/// - Default: reductionDims=[0, 1] (both reduced) -> lanes stay on the
1255/// innermost dim: lane_layout=[1, 16], lane_data=[2, 1].
1256static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1258 ArrayRef<int64_t> reductionDims,
1259 int subgroupSize, int64_t maxReduceVectorSize,
1260 bool verticalLaneLayout = false) {
1261 int srcRank = srcShape.size();
1262 SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
1263
1264 int innermost = srcRank - 1;
1265 int secondInnermost = srcRank - 2;
1266
1267 if (verticalLaneLayout && secondInnermost >= 0) {
1268 std::swap(innermost, secondInnermost);
1269 }
1270 int laneDim = innermost;
1271 int vectorDim = secondInnermost; // negative for rank 1
1272
1273 laneLayout[laneDim] =
1274 std::min(static_cast<int64_t>(subgroupSize), srcShape[laneDim]);
1275 if (vectorDim >= 0)
1276 laneData[vectorDim] = std::min(maxReduceVectorSize, srcShape[vectorDim]);
1277
1278 return {laneLayout, laneData};
1279}
1280
1281//===----------------------------------------------------------------------===//
1282// Result/anchor-layout setup. Each op category derives lane_layout/lane_data
1283// (and inst_data / sgData) differently. Two things vary across ops:
1284//
1285// * Consumer dependence: consumer-driven ops prefer the layout requested by
1286// their downstream uses and fall back to uArch defaults only when it is
1287// absent/invalid; sinks (StoreNd, PrefetchNd) have no consumer and always
1288// pick their own layout from uArch.
1289//
1290// * Derivation direction between inst_data and lane_layout/lane_data. Both
1291// obey the invariant inst_data = k * lane_layout * lane_data, where `k` is
1292// a per-dim integer >= 1 giving how many times each lane repeats its
1293// access to cover one instruction's data tile (k == 1 means one lane
1294// position per element; k > 1 means the instruction loads/stores several
1295// elements per lane along that dim). Ops solve this invariant from
1296// opposite ends:
1297// - Rigid-lane ops (Nd block IO, DPAS): hardware fixes lane_layout /
1298// lane_data first, then inst_data is built as a multiple of their
1299// product (using get2DBlockIOInstDataLayout / getDpasInstDataLayouts).
1300// - inst_data-first ops (scatter load): take inst_data from the consumer
1301// and derive lane_layout/lane_data underneath it.
1302//
1303// - DPAS (+DPAS_MX) : rigid lanes — inst_data from HW block dims; A/B/C/D
1304// lanes/data follow each operand's matmul role; DPAS_MX
1305// additionally lays out the scale operand.
1306// - LoadNd : consumer-driven, rigid lanes — honors the consumer's
1307// inst_data / lane / sg_layout (incl. transpose & VNNI
1308// packing) when it satisfies uArch block constraints,
1309// else falls back to the default 2D-block scheme (lanes
1310// on the last dim, rank-2 if transposed). The fallback
1311// picks the LARGEST uArch block that divides the data
1312// shape, so the resulting inst_data block can be bigger
1313// than what the consumer asked for (fewer, wider
1314// loads).
1315// - StoreNd/PrefetchNd: data sinks, no consumer, rigid lanes — pick the
1316// 2D-block layout directly from uArch (no VNNI
1317// packing).
1318// - Load (scatter) : load_gather / load_matrix, consumer-driven,
1319// inst_data-first — reuse the consumer's inst_data and
1320// derive lane_layout/lane_data, else default to lanes +
1321// per-lane chunk on the innermost dim (chunk capped by
1322// maxChunkSize).
1323// - Store (scatter) : store_scatter / store_matrix — same scatter scheme,
1324// but always self-derived from the scatter default.
1325// - Reduction : (multi_)reduction, consumer-driven — distribute the
1326// inner two dims, with lanes on the innermost dim by
1327// default (reducing across lanes) and switched to a
1328// non-reduction dim only when that keeps the reduction
1329// within a lane. Reuses the consumer's slice layout
1330// when it slices exactly the reduction dims, otherwise
1331// re-derives. See setupMultiReductionResultLayout for
1332// the exact switch condition and worked examples.
1333// - BitCast/Interleave: scale the innermost data field by the bitwidth /
1334// interleave ratio so the source layout divides back
1335// out.
1336// - InsertStridedSlice: clamp lane_data per dim to fit the inserted slice
1337// (Lane kind only; sg/inst layouts unsupported).
1338//===----------------------------------------------------------------------===//
1339
1340/// Helper function to set up subgroup layouts for DPAS operands A, B, and
1341/// C/D. Compute subgroup layout candidates based on wgtile and instData, and
1342/// then pick the best one that satisfies all operands and the consumer (if
1343/// specified).
1344static std::optional<
1345 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1346 xegpu::DistributeLayoutAttr>>
1348 mlir::MLIRContext *context, VectorType aTy, VectorType bTy, VectorType cdTy,
1349 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1351 instDataVecs) {
1352 auto [instDataA, instDataB, instDataCD] = instDataVecs;
1353
1354 std::optional<LayoutRepresentation> consumerSgLayout = std::nullopt;
1355 if (consumerLayout && consumerLayout.isForWorkgroup()) {
1356 consumerSgLayout = consumerLayout.getEffectiveSgLayoutAsInt();
1357 }
1358
1359 // Get all valid layouts for A, B and C/D operands
1360 auto layoutsA = getSgLayoutCandidates(aTy.getShape(), instDataA, numSg,
1361 /*broadcastDim=*/aTy.getRank() - 1);
1362 auto layoutsB = getSgLayoutCandidates(bTy.getShape(), instDataB, numSg,
1363 /*broadcastDim=*/bTy.getRank() - 2);
1364 auto layoutsCD = getSgLayoutCandidates(cdTy.getShape(), instDataCD, numSg);
1365 if (layoutsA.empty() || layoutsB.empty() || layoutsCD.empty())
1366 return std::nullopt;
1367
1368 // Pick the best subgroup layout
1369 std::optional<LayoutRepresentation> bestPick;
1370 for (auto &sgLayout : layoutsB) {
1371 if (llvm::is_contained(layoutsA, sgLayout) &&
1372 llvm::is_contained(layoutsCD, sgLayout)) {
1373 // Is in (A and B and CD) and matches consumer -> best pick
1374 if (consumerSgLayout.has_value() && sgLayout == *consumerSgLayout) {
1375 bestPick = sgLayout;
1376 break;
1377 }
1378 // Is in (A and B and CD) layoutsB is ordered from most
1379 // balanced to least. So the first one we see is the most balanced one,
1380 // remember it and later only update if there is one that matches the
1381 // consumer.
1382 if (!bestPick)
1383 bestPick = sgLayout;
1384 }
1385 }
1386 if (!bestPick)
1387 return std::nullopt;
1388
1389 const auto &picked = *bestPick;
1390
1391 auto dpasALayout = buildSgLayout(context, aTy.getShape(), picked,
1392 /*dimK=*/aTy.getRank() - 1);
1393 auto dpasBLayout = buildSgLayout(context, bTy.getShape(), picked,
1394 /*dimK=*/bTy.getRank() - 2);
1395 auto dpasCDLayout = buildSgLayout(context, cdTy.getShape(), picked);
1396 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout);
1397}
1398
1399/// Sets up the anchor layouts for dpas operands (A, B, and C/D).
1400/// The numSg and consumerLayout (optional) are only used by sg layout
1401/// creation.
1402std::optional<
1403 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1404 xegpu::DistributeLayoutAttr>>
1405xegpu::setupDpasLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
1406 VectorType bTy, VectorType cdTy,
1407 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1408 const xegpu::uArch::uArch *uArch) {
1409 auto context = aTy.getContext();
1410 const auto *uArchInstruction =
1411 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
1413 if (!uArchInstruction)
1414 return std::nullopt;
1415 auto subgroupSize = uArch->getSubgroupSize();
1416
1417 auto [laneLayoutA, laneDataA] = compute2DBlockIOLaneLayoutAndData(
1418 aTy.getShape(), subgroupSize,
1419 aTy.getElementType().getIntOrFloatBitWidth(),
1420 uArchInstruction->getPackedFormatBitSizeA());
1421 auto [laneLayoutB, laneDataB] = compute2DBlockIOLaneLayoutAndData(
1422 bTy.getShape(), subgroupSize,
1423 bTy.getElementType().getIntOrFloatBitWidth(),
1424 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
1425 auto [laneLayoutCD, laneDataCD] = compute2DBlockIOLaneLayoutAndData(
1426 cdTy.getShape(), subgroupSize,
1427 cdTy.getElementType().getIntOrFloatBitWidth(),
1428 cdTy.getElementType().getIntOrFloatBitWidth());
1429
1430 auto instDataVecs = getDpasInstDataLayouts(aTy, bTy, cdTy, uArchInstruction);
1431 if (!instDataVecs)
1432 return std::nullopt;
1433
1434 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1435 assert(numSg > 0 &&
1436 "Number of subgroups must be provided for sg layout creation.");
1437 return getDpasSubgroupLayouts(context, aTy, bTy, cdTy, consumerLayout,
1438 numSg, *instDataVecs);
1439 } else if (layoutKind == xegpu::LayoutKind::InstData) {
1440 auto [instDataA, instDataB, instDataCD] = *instDataVecs;
1441 return std::make_tuple(
1442 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA),
1443 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB),
1444 buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD,
1445 laneDataCD));
1446 } else if (layoutKind == xegpu::LayoutKind::Lane) {
1447 auto aLayout = buildLaneLayout(context, laneLayoutA, laneDataA);
1448 auto bLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
1449 auto cdLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
1450 return std::make_tuple(aLayout, bLayout, cdLayout);
1451 }
1452 return std::nullopt;
1453}
1454
1455/// Helper to create a scale layout derived from a matrix operand layout.
1456/// The scale layout is computed by mapping each dimension of the matrix
1457/// layout to the corresponding scale tensor dimension using the ratio
1458/// between the matrix and scale shapes.
1459static xegpu::DistributeLayoutAttr
1460createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy,
1461 VectorType scaleTy, xegpu::DistributeLayoutAttr matrixLayout,
1462 bool isBScale, const xegpu::uArch::uArch *uArch) {
1463 if (!scaleTy || !matrixLayout)
1464 return nullptr;
1465
1466 // Calculate scaling factor by dividing matrix shape by scale shape
1467 ArrayRef<int64_t> matrixShape = matrixTy.getShape();
1468 ArrayRef<int64_t> scaleShape = scaleTy.getShape();
1469
1470 // Scale shapes can be 1D or 2D, handle both cases
1471 if (scaleShape.empty())
1472 return nullptr;
1473
1474 auto uArchInstruction =
1475 dyn_cast<xegpu::uArch::SubgroupScaledMatrixMultiplyAcc>(
1476 uArch->getInstruction(
1478
1479 int64_t rank = matrixLayout.getRank();
1480 assert(rank >= 2 && "dpas layouts must be at least two dimensions");
1481
1482 SmallVector<int64_t> sgLayout = matrixLayout.getEffectiveSgLayoutAsInt();
1483 SmallVector<int64_t> sgData = matrixLayout.getEffectiveSgDataAsInt();
1484 SmallVector<int64_t> instData = matrixLayout.getEffectiveInstDataAsInt();
1485 SmallVector<int64_t> laneLayout = matrixLayout.getEffectiveLaneLayoutAsInt();
1486 SmallVector<int64_t> laneData = matrixLayout.getEffectiveLaneDataAsInt();
1487 auto order = matrixLayout.getOrder();
1488
1489 SmallVector<int64_t> scaleSgLayout;
1490 SmallVector<int64_t> scaleSgData;
1491 if (!sgLayout.empty() && !sgData.empty()) {
1492 scaleSgLayout.assign(sgLayout.begin(), sgLayout.end());
1493 scaleSgData.assign(sgData.begin(), sgData.end());
1494 scaleSgData[rank - 2] = std::max<int64_t>(
1495 scaleShape[rank - 2] / (matrixShape[rank - 2] / sgData[rank - 2]), 1);
1496 scaleSgData[rank - 1] = std::max<int64_t>(
1497 scaleShape[rank - 1] / (matrixShape[rank - 1] / sgData[rank - 1]), 1);
1498 }
1499
1500 // For DPAS_MX scales: if matrix has inst_data, scale needs adjusted
1501 // inst_data. Scale inst_data is derived from matrix inst_data divided by
1502 // scale factor.
1503 SmallVector<int64_t> scaleInstData;
1504 if (!instData.empty()) {
1505 scaleInstData.assign(instData.begin(), instData.end());
1506 if (isBScale)
1507 scaleInstData[rank - 2] = std::max<int64_t>(
1508 scaleShape[rank - 2] / (matrixShape[rank - 2] / instData[rank - 2]),
1509 1);
1510 else
1511 scaleInstData[rank - 1] = std::max<int64_t>(
1512 scaleShape[rank - 1] / (matrixShape[rank - 1] / instData[rank - 1]),
1513 1);
1514 }
1515
1516 SmallVector<int64_t> scaleLaneLayout;
1517 SmallVector<int64_t> scaleLaneData;
1518 if (!laneLayout.empty() && !laneData.empty()) {
1519 scaleLaneLayout.assign(laneLayout.begin(), laneLayout.end());
1520 scaleLaneData.assign(laneData.size(), 1);
1521
1522 bool isRowMajor = uArchInstruction->isLaneLayoutRowMajorOrder();
1523 if (isBScale ^ isRowMajor)
1524 std::swap(scaleLaneLayout[rank - 2], scaleLaneLayout[rank - 1]);
1525 // Cap lane_layout by the per-instruction tile (inst_data) on each dim.
1526 // Then derive lane_data = inst_data / lane_layout so the Category A
1527 // invariant inst_data = lane_layout * lane_data * k (with k = 1) holds
1528 // for the scale operand's load_nd consumer.
1529 auto layoutCap = scaleInstData.empty() ? scaleShape : scaleInstData;
1530 for (int64_t d = rank - 2; d < rank; ++d)
1531 scaleLaneLayout[d] = std::min<int64_t>(layoutCap[d], scaleLaneLayout[d]);
1532 }
1533 return buildLayout(context, scaleSgLayout, scaleSgData, scaleInstData,
1534 scaleLaneLayout, scaleLaneData, order);
1535}
1536
1537/// Sets up the anchor layouts for dpas_mx operands (A, B, C/D, A_scale, and
1538/// B_scale). The numSg and consumerLayout (optional) are only used by sg
1539/// layout creation.
1540std::optional<
1541 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1542 xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1543 xegpu::DistributeLayoutAttr>>
1544xegpu::setupDpasMxLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
1545 VectorType bTy, VectorType cdTy, VectorType aScaleTy,
1546 VectorType bScaleTy,
1547 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1548 const xegpu::uArch::uArch *uArch) {
1549 auto context = aTy.getContext();
1550 const auto *uArchInstruction =
1551 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
1553 if (!uArchInstruction)
1554 return std::nullopt;
1555 auto subgroupSize = uArch->getSubgroupSize();
1556
1557 auto [laneLayoutA, laneDataA] = compute2DBlockIOLaneLayoutAndData(
1558 aTy.getShape(), subgroupSize,
1559 aTy.getElementType().getIntOrFloatBitWidth(),
1560 uArchInstruction->getPackedFormatBitSizeA());
1561 auto [laneLayoutB, laneDataB] = compute2DBlockIOLaneLayoutAndData(
1562 bTy.getShape(), subgroupSize,
1563 bTy.getElementType().getIntOrFloatBitWidth(),
1564 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
1565 auto [laneLayoutCD, laneDataCD] = compute2DBlockIOLaneLayoutAndData(
1566 cdTy.getShape(), subgroupSize,
1567 cdTy.getElementType().getIntOrFloatBitWidth(),
1568 cdTy.getElementType().getIntOrFloatBitWidth());
1569 auto instDataVecs = getDpasInstDataLayouts(aTy, bTy, cdTy, uArchInstruction);
1570 if (!instDataVecs)
1571 return std::nullopt;
1572
1573 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1574 assert(numSg > 0 &&
1575 "Number of subgroups must be provided for sg layout creation.");
1576 auto dpasLayouts = getDpasSubgroupLayouts(
1577 context, aTy, bTy, cdTy, consumerLayout, numSg, *instDataVecs);
1578 if (!dpasLayouts)
1579 return std::nullopt;
1580
1581 auto [dpasALayout, dpasBLayout, dpasCDLayout] = *dpasLayouts;
1582
1583 // Create scale layouts
1584 auto aScaleLayout =
1585 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1586
1587 auto bScaleLayout =
1588 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1589
1590 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1591 bScaleLayout);
1592 } else if (layoutKind == xegpu::LayoutKind::InstData) {
1593
1594 auto [instDataA, instDataB, instDataCD] = *instDataVecs;
1595
1596 auto dpasALayout =
1597 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA);
1598 auto dpasBLayout =
1599 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB);
1600 auto dpasCDLayout = buildInstDataLayoutWithLane(context, instDataCD,
1601 laneLayoutCD, laneDataCD);
1602
1603 auto aScaleLayout =
1604 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1605 auto bScaleLayout =
1606 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1607
1608 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1609 bScaleLayout);
1610 } else if (layoutKind == xegpu::LayoutKind::Lane) {
1611 auto dpasALayout = buildLaneLayout(context, laneLayoutA, laneDataA);
1612 auto dpasBLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
1613 auto dpasCDLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
1614
1615 auto aScaleLayout =
1616 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1617 auto bScaleLayout =
1618 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1619
1620 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1621 bScaleLayout);
1622 }
1623 return std::nullopt;
1624}
1625
1626/// Sets up the anchor layout for a store_nd operation. StoreNd picks its
1627/// own layout based on uArch block parameters (it does not take a consumer
1628/// layout, since it is a data sink).
1629xegpu::DistributeLayoutAttr
1631 VectorType srcVecTy, int numSg,
1632 const xegpu::uArch::uArch *uArch) {
1633 const auto *uArchInstruction =
1634 dyn_cast<xegpu::uArch::Subgroup2DBlockStoreInstruction>(
1635 uArch->getInstruction(
1637 if (!uArchInstruction)
1638 return nullptr;
1639
1640 auto context = srcVecTy.getContext();
1641 Type elemTy = srcVecTy.getElementType();
1642 auto subgroupSize = uArch->getSubgroupSize();
1643 auto dataShape = srcVecTy.getShape();
1644 [[maybe_unused]] int rank = srcVecTy.getRank();
1645 assert(rank >= 2 && "Expected at least 2D shape for ND op");
1646
1647 // Compute the default 2D block IO lane layout / lane data.
1648 unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
1649 auto [laneLayout, laneData] = compute2DBlockIOLaneLayoutAndData(
1650 dataShape, subgroupSize, bitwidth,
1651 uArchInstruction->getPackedFormatBitSize());
1652
1653 if (layoutKind == xegpu::LayoutKind::Lane)
1654 return buildLaneLayout(context, laneLayout, laneData);
1655
1656 auto instData =
1657 get2DBlockIOInstDataLayout(dataShape, elemTy, uArchInstruction);
1658
1659 if (layoutKind == xegpu::LayoutKind::InstData) {
1660 assert(instData && isValidLaneLayout(*instData, laneLayout, laneData) &&
1661 "Expected the store layout to satisfy uArch block constraints");
1662 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1663 laneData);
1664 }
1665
1666 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1667 assert(numSg > 0 &&
1668 "Number of subgroups must be provided for sg layout creation.");
1669 auto sgLayouts = getSgLayoutCandidates(dataShape, *instData, numSg);
1670 if (sgLayouts.empty())
1671 return nullptr;
1672 return buildSgLayout(context, dataShape, sgLayouts.front(), /*dimK=*/-1);
1673 }
1674
1675 return nullptr;
1676}
1677
1678/// Sets up the anchor layout for a prefetch_nd operation. PrefetchNd has no
1679/// consumer (it produces no value), so it picks its own layout from uArch
1680/// block parameters.
1681xegpu::DistributeLayoutAttr
1683 xegpu::TensorDescType tdescTy, int numSg,
1684 const xegpu::uArch::uArch *uArch) {
1685
1686 const auto *uArchInstruction =
1687 dyn_cast<xegpu::uArch::Subgroup2DBlockPrefetchInstruction>(
1688 uArch->getInstruction(
1690 if (!uArchInstruction)
1691 return nullptr;
1692
1693 auto context = tdescTy.getContext();
1694 Type elemTy = tdescTy.getElementType();
1695 auto subgroupSize = uArch->getSubgroupSize();
1696 auto dataShape = tdescTy.getShape();
1697 [[maybe_unused]] int rank = tdescTy.getRank();
1698 assert(rank >= 2 && "Expected at least 2D shape for ND op");
1699
1700 // Compute the default 2D block IO lane layout / lane data.
1701 unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
1702 auto [laneLayout, laneData] = compute2DBlockIOLaneLayoutAndData(
1703 dataShape, subgroupSize, bitwidth,
1704 uArchInstruction->getPackedFormatBitSize());
1705
1706 if (layoutKind == xegpu::LayoutKind::Lane)
1707 return buildLaneLayout(context, laneLayout, laneData);
1708
1709 auto instData =
1710 get2DBlockIOInstDataLayout(dataShape, elemTy, uArchInstruction);
1711
1712 if (layoutKind == xegpu::LayoutKind::InstData) {
1713 assert(instData && isValidLaneLayout(*instData, laneLayout, laneData) &&
1714 "Expected the prefetch layout to satisfy uArch block constraints");
1715 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1716 laneData);
1717 }
1718
1719 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1720 assert(numSg > 0 &&
1721 "Number of subgroups must be provided for sg layout creation.");
1722 auto sgLayouts = getSgLayoutCandidates(dataShape, *instData, numSg);
1723 if (sgLayouts.empty())
1724 return nullptr;
1725 return buildSgLayout(context, dataShape, sgLayouts.front(), /*dimK=*/-1);
1726 }
1727
1728 return nullptr;
1729}
1730
1731/// Sets up the anchor layout for a load_nd operation. LoadNd takes a
1732/// consumer layout (from its result's downstream uses) and validates it
1733/// against uArch constraints; if valid, the consumer's `inst_data` /
1734/// `sg_layout` are honored. Otherwise the helper falls back to defaults
1735/// derived from uArch block parameters.
1736xegpu::DistributeLayoutAttr
1738 VectorType resVecTy,
1739 xegpu::DistributeLayoutAttr consumerLayout,
1740 int numSg, const xegpu::uArch::uArch *uArch) {
1741
1742 assert(consumerLayout && "Expected a valid consumer layout");
1743 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1744 assert(consumerLayout.isForWorkgroup() &&
1745 "Expected consumer layout to be a complete workgroup-level layout");
1746 return consumerLayout;
1747 }
1748
1749 auto context = resVecTy.getContext();
1750 Type elemTy = resVecTy.getElementType();
1751 auto subgroupSize = uArch->getSubgroupSize();
1752 auto dataShape = resVecTy.getShape();
1753 const auto *uArchInstruction =
1754 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
1755 uArch->getInstruction(
1757 if (!uArchInstruction)
1758 return nullptr;
1759
1760 int rank = resVecTy.getRank();
1761 SmallVector<int64_t> consumerInstData =
1762 consumerLayout.getEffectiveInstDataAsInt();
1763 SmallVector<int64_t> consumerLaneLayout =
1764 consumerLayout.getEffectiveLaneLayoutAsInt();
1765 SmallVector<int64_t> consumerLaneData =
1766 consumerLayout.getEffectiveLaneDataAsInt();
1767 auto consumerOrderAttr = consumerLayout.getOrder();
1768
1769 assert(!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
1770 "Expected consumer layout to have lane_layout and lane_data");
1771
1772 // vertical lane layout means that the blockload must be transposed
1773 // note scaleA on PVC has vertical lane layout even without transposed order
1774 // attr
1775 bool hasTranspose =
1776 consumerLaneLayout[rank - 2] > 1 && consumerLaneLayout[rank - 1] == 1;
1777 bool hasTransform = !hasTranspose && consumerLaneData[rank - 2] > 1 &&
1778 consumerLaneData[rank - 1] == 1;
1779 assert((consumerLaneData[rank - 2] == 1 || consumerLaneData[rank - 1] == 1) &&
1780 "Expected consumer lane data to have at most one non-unit dim");
1781
1782 if (layoutKind == xegpu::LayoutKind::InstData) {
1783 auto blockWHC = uArchInstruction->getBlockWidthHeightCount(
1784 elemTy, hasTransform, hasTranspose,
1785 /*upConv=*/false);
1786 if (!blockWHC)
1787 return nullptr;
1788 auto [bWidths, bHeights, bCounts] = blockWHC.value();
1789
1790 SmallVector<int64_t> laneLayout;
1791 // set the laneLayout to use consumer's LaneLayout as base, but adjust its
1792 // size to match the subgroupsize in case its original value is larger than
1793 // 1
1794 for (int i = 0; i < rank; i++) {
1795 if (consumerLaneLayout[i] > 1)
1796 laneLayout.push_back(std::max(static_cast<int64_t>(subgroupSize),
1797 consumerLaneLayout[i]));
1798 else
1799 laneLayout.push_back(1);
1800 }
1801
1802 // See whether the consumer's inst_data satisfies the block constraints.
1803 int64_t height = consumerInstData[rank - 2];
1804 int64_t width = consumerInstData[rank - 1];
1805 auto maxBlockCount = *llvm::max_element(bCounts);
1806 auto maxWidth = *llvm::max_element(bWidths);
1807 if (llvm::is_contained(bWidths, static_cast<int>(width)) ||
1808 (width % maxWidth == 0 && width / maxWidth < maxBlockCount)) {
1809 if (llvm::is_contained(bHeights, static_cast<int>(height))) {
1810 return buildInstDataLayoutWithLane(context, consumerInstData,
1811 laneLayout, consumerLaneData,
1812 consumerOrderAttr);
1813 }
1814 }
1815
1816 // if consumer instData size too small, try the larger one. like DPAS_MX's
1817 // scale is smaller than block load
1818 auto instData = get2DBlockIOInstDataLayout(
1819 dataShape, elemTy, uArchInstruction, hasTransform, hasTranspose);
1820 // assert instData is valid against consumer layout since
1821 // transform/transpose attribute are derived from consumer layout
1822 assert(instData &&
1823 isValidLaneLayout(*instData, laneLayout, consumerLaneData) &&
1824 "Expected the load layout to satisfy uArch block constraints");
1825 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1826 consumerLaneData, consumerOrderAttr);
1827 }
1828 if (layoutKind == xegpu::LayoutKind::Lane) {
1829 assert(isValidLaneLayout(dataShape, consumerLaneLayout, consumerLaneData) &&
1830 "Expected the lane layout to satisfy uArch block constraints");
1831 return consumerLayout;
1832 }
1833 return nullptr;
1834}
1835
1836/// Sets up the anchor layout for load gather and load matrix operation.
1837/// load matrix lowers to load gather and 1d block load. All of them share the
1838/// same layout setup logic.
1839///
1840/// For Subgroup layout, uses the consumer layout directly.
1841///
1842/// For InstData layout, takes consumer's inst_data as-is. lane_layout and
1843/// lane_data are taken from the consumer when present; otherwise the helper
1844/// derives the standard scatter-style default (subgroupSize lanes on the
1845/// innermost dim, per-lane vector capped by maxChunkSize).
1846///
1847/// For Lane layout, lane_layout/lane_data are taken from the consumer when
1848/// present; otherwise derived from the same default.
1849static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
1850 xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
1851 xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize,
1852 ArrayRef<int64_t> resShape, int subgroupSize) {
1853
1854 if (layoutKind == xegpu::LayoutKind::Subgroup)
1855 return consumerLayout;
1856
1857 SmallVector<int64_t> consumerInstData =
1858 consumerLayout.getEffectiveInstDataAsInt();
1859 SmallVector<int64_t> consumerLaneLayout =
1860 consumerLayout.getEffectiveLaneLayoutAsInt();
1861 SmallVector<int64_t> consumerLaneData =
1862 consumerLayout.getEffectiveLaneDataAsInt();
1863
1864 SmallVector<int64_t> laneLayout;
1865 SmallVector<int64_t> laneData;
1866 assert(!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
1867 "Expected consumer layout to have lane_layout and lane_data");
1868 laneLayout.assign(consumerLaneLayout.begin(), consumerLaneLayout.end());
1869 laneData.assign(consumerLaneData.begin(), consumerLaneData.end());
1870
1871 if (layoutKind == xegpu::LayoutKind::InstData) {
1872 SmallVector<int64_t> instData;
1873 instData.resize(resShape.size());
1874 for (size_t i = 0; i < resShape.size(); ++i)
1875 instData[i] = laneLayout[i] * laneData[i];
1876 return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
1877 }
1878 if (layoutKind == xegpu::LayoutKind::Lane)
1879 return buildLaneLayout(context, laneLayout, laneData);
1880 return nullptr;
1881}
1882
1883/// Sets up the anchor layout for a load gather operation.
1884xegpu::DistributeLayoutAttr xegpu::setupLoadGatherAnchorLayout(
1885 xegpu::LayoutKind layoutKind, VectorType resVecTy, int contigChunkSize,
1886 xegpu::DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch) {
1887
1888 const int subgroupSize = uArch->getSubgroupSize();
1889 ArrayRef<int64_t> resShape = resVecTy.getShape();
1890 auto context = resVecTy.getContext();
1891
1892 const auto *uArchInstruction = dyn_cast<xegpu::uArch::LoadGatherInstruction>(
1893 uArch->getInstruction(xegpu::uArch::InstructionKind::LoadGather));
1894 int maxChunkSize =
1895 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1896
1897 return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
1898 maxChunkSize, resShape, subgroupSize);
1899}
1900
1901/// Sets up the anchor layout for load matrix operation.
1902/// TODO: enhance load matrix to indicate lowering to chunked load or not.
1903xegpu::DistributeLayoutAttr
1905 VectorType resVecTy, int contigChunkSize,
1906 xegpu::DistributeLayoutAttr consumerLayout,
1907 const xegpu::uArch::uArch *uArch) {
1908
1909 const int subgroupSize = uArch->getSubgroupSize();
1910 ArrayRef<int64_t> resShape = resVecTy.getShape();
1911 auto context = resVecTy.getContext();
1912
1913 const auto *uArchInstruction = dyn_cast<xegpu::uArch::LoadGatherInstruction>(
1915 int maxChunkSize =
1916 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1917 return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
1918 maxChunkSize, resShape, subgroupSize);
1919}
1920
1921/// Picks the subgroup layout for a scatter-style store (store_scatter /
1922/// store_matrix): the most balanced `numSg` factorization that divides
1923/// `wgShape` with sg_data a multiple of `instData`. A store has no consumer.
1924static xegpu::DistributeLayoutAttr
1926 ArrayRef<int64_t> instData, int numSg) {
1927 auto candidates = getSgLayoutCandidates(wgShape, instData, numSg);
1928 if (candidates.empty())
1929 return nullptr;
1930 // Candidates are ordered most-balanced first.
1931 return buildSgLayout(context, wgShape, candidates.front(), /*dimK=*/-1);
1932}
1933
1934/// Sets up the anchor layout for store scatter and store matrix operation,
1935/// which share the same logic. Lane layout comes from
1936/// `computeScatterIOLaneLayoutAndData`; inst_data is lane_layout * lane_data.
1937static xegpu::DistributeLayoutAttr setupGenericStoreAnchorLayout(
1938 xegpu::LayoutKind layoutKind, mlir::MLIRContext *context, int maxChunkSize,
1939 ArrayRef<int64_t> srcShape, int subgroupSize, int numSg) {
1940
1941 auto [laneLayout, laneData] =
1942 computeScatterIOLaneLayoutAndData(srcShape, subgroupSize, maxChunkSize);
1943
1944 SmallVector<int64_t> instData(srcShape.size());
1945 for (size_t i = 0; i < srcShape.size(); ++i)
1946 instData[i] = laneLayout[i] * laneData[i];
1947
1948 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1949 assert(numSg > 0 &&
1950 "Number of subgroups must be provided for sg layout creation.");
1951 return getStoreSubgroupLayouts(context, srcShape, instData, numSg);
1952 }
1953 if (layoutKind == xegpu::LayoutKind::InstData) {
1954 return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
1955 }
1956 if (layoutKind == xegpu::LayoutKind::Lane) {
1957 return buildLaneLayout(context, laneLayout, laneData);
1958 }
1959 return nullptr;
1960}
1961
1962/// Sets up the anchor layout for a store scatter operation.
1963xegpu::DistributeLayoutAttr
1965 VectorType srcVecTy, int contigChunkSize,
1966 int numSg, const uArch::uArch *uArch) {
1967
1968 const int subgroupSize = uArch->getSubgroupSize();
1969 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
1970 auto context = srcVecTy.getContext();
1971
1972 const auto *uArchInstruction =
1973 dyn_cast<xegpu::uArch::StoreScatterInstruction>(
1975 int maxChunkSize =
1976 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1977 return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
1978 srcShape, subgroupSize, numSg);
1979}
1980
1981/// Sets up the anchor layout for a store matrix operation.
1982xegpu::DistributeLayoutAttr xegpu::setupStoreMatrixAnchorLayout(
1983 xegpu::LayoutKind layoutKind, VectorType srcVecTy, int contigChunkSize,
1984 int numSg, const xegpu::uArch::uArch *uArch) {
1985
1986 const int subgroupSize = uArch->getSubgroupSize();
1987 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
1988 auto context = srcVecTy.getContext();
1989
1990 const auto *uArchInstruction =
1991 dyn_cast<xegpu::uArch::StoreScatterInstruction>(
1993 int maxChunkSize =
1994 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
1995
1996 return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
1997 srcShape, subgroupSize, numSg);
1998}
1999
2000/// Completes a scatter IO layout by deriving lane_layout and lane_data from
2001/// `specifiedLayout`'s inst_data when they are missing. The layout is returned
2002/// unchanged if `specifiedLayout` is null, carries no inst_data, or already has
2003/// both lane_layout and lane_data.
2004///
2005/// When lane info is absent, inst_data is treated as the effective shape and
2006/// the lane factorization is filled in as follows:
2007/// - If `consumerLayout` is present and its lane_layout / lane_data are a
2008/// valid factorization of inst_data, that consumer lane info is reused so
2009/// the completed layout matches the consumer (avoiding a relayout).
2010/// - Otherwise a standard scatter-style factorization is computed via
2011/// `computeScatterIOLaneLayoutAndData`, bounded by `maxChunkSize` — the
2012/// per-lane load width reported by the uArch's LoadGather instruction
2013/// (`getMaxLaneAccessSizeBytes`).
2014///
2015std::optional<xegpu::DistributeLayoutAttr>
2017 xegpu::DistributeLayoutAttr specifiedLayout,
2018 xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
2019 const xegpu::uArch::LoadGatherInstruction *uArchInstruction,
2020 const int subgroupSize) {
2021 if (!specifiedLayout)
2022 return specifiedLayout;
2023 SmallVector<int64_t> specifiedInstData =
2024 specifiedLayout.getEffectiveInstDataAsInt();
2025 if (specifiedInstData.empty())
2026 return specifiedLayout;
2027 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2028 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2029 return specifiedLayout;
2030
2031 // Reuse the load-side setup with inst_data as the destination shape.
2032 auto *context = specifiedLayout.getContext();
2033 int maxChunkSize = uArchInstruction->getMaxLaneAccessSizeBytes();
2034 if (consumerLayout) {
2035 auto consumerLaneLayout = consumerLayout.getEffectiveLaneLayoutAsInt();
2036 auto consumerLaneData = consumerLayout.getEffectiveLaneDataAsInt();
2037 if (!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
2038 isValidLaneLayout(specifiedInstData, consumerLaneLayout,
2039 consumerLaneData))
2040 return buildInstDataLayoutWithLane(context, specifiedInstData,
2041 consumerLaneLayout, consumerLaneData);
2042 }
2043 auto [defLaneLayout, defLaneData] = computeScatterIOLaneLayoutAndData(
2044 specifiedInstData, subgroupSize, maxChunkSize);
2045 if (!isValidLaneLayout(specifiedInstData, defLaneLayout, defLaneData))
2046 return std::nullopt;
2047 return buildInstDataLayoutWithLane(context, specifiedInstData, defLaneLayout,
2048 defLaneData);
2049}
2050
2051/// Like completeScatterLoadLaneLayoutFromInstData, but for scatter stores. A
2052/// store is a data sink, so lane info is derived purely from inst_data (bounded
2053/// by the uArch's per-lane store width); there is no consumer layout to reuse.
2054std::optional<xegpu::DistributeLayoutAttr>
2056 xegpu::DistributeLayoutAttr specifiedLayout, Type elemTy,
2057 const xegpu::uArch::StoreScatterInstruction *uArchInstruction,
2058 const int subgroupSize) {
2059 if (!specifiedLayout)
2060 return specifiedLayout;
2061 SmallVector<int64_t> specifiedInstData =
2062 specifiedLayout.getEffectiveInstDataAsInt();
2063 if (specifiedInstData.empty())
2064 return specifiedLayout;
2065 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2066 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2067 return specifiedLayout;
2068
2069 // Reuse the store-side setup with inst_data as the source shape.
2070 auto *context = specifiedLayout.getContext();
2071 int maxChunkSize = uArchInstruction->getMaxLaneAccessSizeBytes();
2072 auto [defLaneLayout, defLaneData] = computeScatterIOLaneLayoutAndData(
2073 specifiedInstData, subgroupSize, maxChunkSize);
2074 if (!isValidLaneLayout(specifiedInstData, defLaneLayout, defLaneData))
2075 return std::nullopt;
2076 return buildInstDataLayoutWithLane(context, specifiedInstData, defLaneLayout,
2077 defLaneData);
2078}
2079
2080/// Completes a 2D-block store/prefetch layout from its inst_data. store_nd and
2081/// prefetch_nd are data sinks, so lane info is derived purely from inst_data
2082/// (no consumer to reuse). One helper serves both via
2083/// BlockIOInstructionInterface.
2084std::optional<xegpu::DistributeLayoutAttr>
2086 xegpu::DistributeLayoutAttr specifiedLayout, Type elemTy,
2087 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
2088 const int subgroupSize) {
2089 if (!specifiedLayout)
2090 return specifiedLayout;
2091 SmallVector<int64_t> specifiedInstData =
2092 specifiedLayout.getEffectiveInstDataAsInt();
2093 if (specifiedInstData.empty())
2094 return specifiedLayout;
2095 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2096 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2097 return specifiedLayout;
2098
2099 auto *context = specifiedLayout.getContext();
2100 auto [laneLayout, laneData] = compute2DBlockIOLaneLayoutAndData(
2101 specifiedInstData, subgroupSize, elemTy.getIntOrFloatBitWidth(),
2102 uArchInstruction->getPackedFormatBitSize());
2103 if (!isValidLaneLayout(specifiedInstData, laneLayout, laneData))
2104 return std::nullopt;
2105 return buildInstDataLayoutWithLane(context, specifiedInstData, laneLayout,
2106 laneData);
2107}
2108
2109/// Like completeBlockStoreLaneLayoutFromInstData, but for load_nd. The
2110/// consumer's lane_data and order are reused as-is; lane_layout is rebuilt from
2111/// the consumer's lane_layout, bumping every non-unit dim up to the subgroup
2112/// size. The user-provided inst_data is preserved.
2113std::optional<xegpu::DistributeLayoutAttr>
2115 xegpu::DistributeLayoutAttr specifiedLayout,
2116 xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
2117 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
2118 const int subgroupSize) {
2119 if (!specifiedLayout)
2120 return specifiedLayout;
2121 SmallVector<int64_t> specifiedInstData =
2122 specifiedLayout.getEffectiveInstDataAsInt();
2123 if (specifiedInstData.empty())
2124 return specifiedLayout;
2125 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2126 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2127 return specifiedLayout;
2128 if (!consumerLayout)
2129 return specifiedLayout;
2130 SmallVector<int64_t> consumerLaneLayout =
2131 consumerLayout.getEffectiveLaneLayoutAsInt();
2132 SmallVector<int64_t> consumerLaneData =
2133 consumerLayout.getEffectiveLaneDataAsInt();
2134 if (consumerLaneLayout.empty() || consumerLaneData.empty())
2135 return specifiedLayout;
2136
2137 auto *context = specifiedLayout.getContext();
2138 int rank = specifiedInstData.size();
2139
2140 SmallVector<int64_t> laneLayout;
2141 // set the laneLayout to use consumer's LaneLayout as base, but adjust its
2142 // size to match the subgroupsize in case its original value is larger than 1
2143 for (int i = 0; i < rank; i++) {
2144 if (consumerLaneLayout[i] > 1) {
2145 laneLayout.push_back(
2146 std::max(static_cast<int64_t>(subgroupSize), consumerLaneLayout[i]));
2147 } else {
2148 laneLayout.push_back(1);
2149 }
2150 }
2151
2152 if (!isValidLaneLayout(specifiedInstData, laneLayout, consumerLaneData))
2153 return std::nullopt;
2154 return buildInstDataLayoutWithLane(context, specifiedInstData, laneLayout,
2155 consumerLaneData,
2156 consumerLayout.getOrder());
2157}
2158
2159/// Completes user-provided DPAS A/B/C-D anchors that carry only inst_data by
2160/// filling in lane_layout / lane_data. The lane factorization mirrors the
2161/// InstData branch of `setupDpasLayout` (derived from each operand's shape and
2162/// matmul role, B using VNNI packing); the user's inst_data is preserved.
2163std::optional<
2164 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2165 xegpu::DistributeLayoutAttr>>
2166xegpu::completeDpasLaneLayoutFromInstData(xegpu::DistributeLayoutAttr aLayout,
2167 xegpu::DistributeLayoutAttr bLayout,
2168 xegpu::DistributeLayoutAttr cdLayout,
2169 VectorType aTy, VectorType bTy,
2170 VectorType cdTy,
2171 const xegpu::uArch::uArch *uArch) {
2172 auto context = aTy.getContext();
2173 const auto *uArchInstruction =
2174 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
2176 if (!uArchInstruction)
2177 return std::nullopt;
2178 auto subgroupSize = uArch->getSubgroupSize();
2179 llvm::SmallVector<int64_t> laneLayoutA, laneDataA, laneLayoutB, laneDataB,
2180 laneLayoutCD, laneDataCD;
2181 SmallVector<int64_t> instDataA = aLayout.getEffectiveInstDataAsInt();
2182 SmallVector<int64_t> instDataB = bLayout.getEffectiveInstDataAsInt();
2183 SmallVector<int64_t> instDataCD = cdLayout.getEffectiveInstDataAsInt();
2184
2185 if (isa<xegpu::uArch::Xe2, xegpu::uArch::Xe3>(uArch)) {
2186 std::tie(laneLayoutA, laneDataA) = compute2DBlockIOLaneLayoutAndData(
2187 aTy.getShape(), subgroupSize,
2188 aTy.getElementType().getIntOrFloatBitWidth(),
2189 uArchInstruction->getPackedFormatBitSizeA());
2190 std::tie(laneLayoutB, laneDataB) = compute2DBlockIOLaneLayoutAndData(
2191 bTy.getShape(), subgroupSize,
2192 bTy.getElementType().getIntOrFloatBitWidth(),
2193 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
2194 std::tie(laneLayoutCD, laneDataCD) = compute2DBlockIOLaneLayoutAndData(
2195 cdTy.getShape(), subgroupSize,
2196 cdTy.getElementType().getIntOrFloatBitWidth(),
2197 cdTy.getElementType().getIntOrFloatBitWidth());
2198 } else {
2199 assert(false && "Unsupported uArch for DPAS lane layout completion");
2200 }
2201
2202 if (!isValidLaneLayout(instDataA, laneLayoutA, laneDataA) ||
2203 !isValidLaneLayout(instDataB, laneLayoutB, laneDataB) ||
2204 !isValidLaneLayout(instDataCD, laneLayoutCD, laneDataCD))
2205 return std::nullopt;
2206 return std::make_tuple(
2207 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA,
2208 aLayout.getOrder()),
2209 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB,
2210 bLayout.getOrder()),
2211 buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD, laneDataCD,
2212 cdLayout.getOrder()));
2213}
2214
2215/// Like completeDpasLaneLayoutFromInstData, but for dpas_mx: also re-derives
2216/// the A_scale / B_scale layouts from the completed A / B layouts via
2217/// `createScaleLayout`, matching the default path of `setupDpasMxLayout`.
2218std::optional<
2219 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2220 xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2221 xegpu::DistributeLayoutAttr>>
2223 xegpu::DistributeLayoutAttr aLayout, xegpu::DistributeLayoutAttr bLayout,
2224 xegpu::DistributeLayoutAttr cdLayout, VectorType aTy, VectorType bTy,
2225 VectorType cdTy, VectorType aScaleTy, VectorType bScaleTy,
2226 const xegpu::uArch::uArch *uArch) {
2227 auto completed = completeDpasLaneLayoutFromInstData(
2228 aLayout, bLayout, cdLayout, aTy, bTy, cdTy, uArch);
2229 if (!completed)
2230 return std::nullopt;
2231 auto context = aTy.getContext();
2232 auto [completedA, completedB, completedCD] = *completed;
2233
2234 auto aScaleLayout =
2235 createScaleLayout(context, aTy, aScaleTy, completedA, false, uArch);
2236 auto bScaleLayout =
2237 createScaleLayout(context, bTy, bScaleTy, completedB, true, uArch);
2238
2239 return std::make_tuple(completedA, completedB, completedCD, aScaleLayout,
2240 bScaleLayout);
2241}
2242
2243/// Sets up layout for reduction operations by creating a SliceAttr for the
2244/// result.
2245///
2246/// Algorithm Overview:
2247/// This function attempts to construct a source layout that, when sliced along
2248/// reduction dimensions, produces a result layout compatible with the
2249/// consumer layout.
2250///
2251/// For subgroup layouts, it first tries to align the source layout's subgroup
2252/// layout and data with the consumer's layout on non-reduction dimensions.
2253/// Then, it distributes remaining subgroups across reduction dimensions. This
2254/// avoids subgroup data redistribution overhead between the reduced result and
2255/// its consumer. When the consumer layout is a slice layout, it attempts to
2256/// reuse the slice layout's parent layout for the source to further minimize
2257/// potential data redistribution.
2258///
2259/// This is a best-effort alignment, not a hard constraint: the goal is only to
2260/// pick a *legal* source layout that minimizes redistribution against the
2261/// (single, first-arriving) consumer layout. There is no failure path - when
2262/// the consumer's slice layout cannot be reused as-is (example 2 below), the
2263/// function falls back to distributing all subgroups on the non-reduction
2264/// dimensions first and the remainder on the reduction dimensions, which always
2265/// yields a valid source layout. If the resulting source layout still differs
2266/// from what some consumer expects (e.g. a second, inconsistent consumer), that
2267/// mismatch is reconciled later by the layout conflict resolution process
2268/// (`ResolveLayoutConflicts`), which inserts a `convert_layout` op - this
2269/// function never has to give up.
2270///
2271/// For the InstData and Lane layout kinds only the innermost two dimensions
2272/// are distributed; all leading dimensions are assumed to be unit dimensions.
2273/// This assumption is checked via `leadingDimsAreUnit`. The lane_layout and
2274/// lane_data are computed by `computeReductionLaneLayoutAndData`, which picks
2275/// a layout that minimizes cross-lane reduction (reducing within a lane when
2276/// only one of the innermost two dims is a reduction dim). The inst_data is
2277/// simply the element-wise product lane_layout * lane_data.
2278///
2279/// The function returns the *result* layout (the SliceAttr). The *source*
2280/// layout it decides on is the parent of that slice; both are listed below so
2281/// the relationship is explicit.
2282///
2283/// Examples:
2284/// 1. Subgroup layout - Row reduction on 2D tensor:
2285/// srcShape=[32, 128], reductionDims=[1], resShape=[32], subgroupSize=16,
2286/// NumSg=32
2287/// * Consumer Layout:
2288/// #xegpu.slice<#xegpu.layout<sg_layout=[4, 8], sg_data=[8, 8]>, dims =
2289/// [1]>}
2290/// * Source Layout (decided by this function):
2291/// #xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>
2292/// * Result Layout (returned):
2293/// #xegpu.slice<#xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>, dims =
2294/// [1]>}
2295/// The consumer slices exactly the reduction dim, so its parent layout is
2296/// reused for the source: sg_layout is kept, but the source's sg_data on
2297/// the reduction dim is grown from 8 to 16 (= srcShape[1] / sg_layout[1] =
2298/// 128 / 8) so the source tile is evenly distributed over the reduction
2299/// dim. Slicing that source over dim 1 reproduces the consumer.
2300///
2301/// 2. Subgroup layout - Same shapes as above but consumer doesn't have a
2302/// reusable slice layout, so the algorithm distributes all subgroups on the
2303/// non-reduction dims first and the remainder on the reduction dims.
2304/// 2a. * Consumer Layout:
2305/// #xegpu.layout<sg_layout=[32], sg_data=[1]>
2306/// * Source Layout (decided by this function):
2307/// #xegpu.layout<sg_layout=[32, 1], sg_data=[1, 128]>
2308/// * Result Layout (returned):
2309/// #xegpu.slice<#xegpu.layout<sg_layout=[32, 1], sg_data=[1, 128]>,
2310/// dims = [1]>}
2311/// All 32 subgroups land on the non-reduction dim 0; the reduction dim
2312/// 1 gets the leftover (sg_layout=1, so the whole length 128 lives in
2313/// one subgroup's sg_data).
2314/// 2b. * Consumer Layout:
2315/// #xegpu.slice<#xegpu.layout<sg_layout=[8, 2, 4], sg_data=[4, 64,
2316/// 32]>, dims = [1, 2]>}
2317/// * Source Layout (decided by this function):
2318/// #xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>
2319/// * Result Layout (returned):
2320/// #xegpu.slice<#xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>,
2321/// dims = [1]>}
2322/// The consumer slices dims [1, 2] which do not match this op's
2323/// reductionDims, so it can't be reused as-is; subgroups are
2324/// re-distributed (non-reduction dim first, then reduction dim).
2325///
2326/// 3. Lane layout - Default (lanes on innermost dim):
2327/// srcShape=[32, 64], reductionDims=[0], subgroupSize=16
2328/// * Source Layout (decided by this function):
2329/// laneLayout=[1, 16], laneData=[1, 1] (returned sliced over dim 0).
2330/// The innermost dim is not reduced, so lanes stay on it.
2331///
2332/// 4. Lane layout - Switch (lanes moved off the reduction dim):
2333/// srcShape=[32, 64], reductionDims=[1], subgroupSize=16
2334/// * Source Layout (decided by this function):
2335/// laneLayout=[16, 1], laneData=[1, 1] (returned sliced over dim 1).
2336/// The innermost dim is the sole reduction dim, so lanes move to the
2337/// non-reduction dim to reduce within a lane. This switch only happens
2338/// when the consumer has no reduction dims to broadcast the result back
2339/// along (i.e. the consumer layout is not a slice over this reduction);
2340/// otherwise the default (example 3) is used.
2341///
2342/// 5. Lane layout - No switch when both inner dims are reduced (reduction to
2343/// scalar):
2344/// srcShape=[32, 64], reductionDims=[0, 1], subgroupSize=16
2345/// * Source Layout (decided by this function):
2346/// laneLayout=[1, 16], laneData=[1, 1] (returned sliced over dims
2347/// [0,1]).
2348/// Both dims are reduced, so this is not a *sole* innermost reduction; the
2349/// switch condition (example 4) does not apply and lanes stay on the
2350/// innermost dim. The cross-lane reduction here is unavoidable.
2351///
2352/// 6. Lane layout - No switch when the consumer slices the reduction dim:
2353/// srcShape=[32, 64], reductionDims=[1], subgroupSize=16
2354/// * Consumer Layout:
2355/// #xegpu.slice<#xegpu.layout<laneLayout=[1, 16], laneData=[1, 1]>,
2356/// dims = [1]>}
2357/// * Source Layout (decided by this function):
2358/// #xegpu.layout<laneLayout=[1, 16], laneData=[1, 1]> (the consumer
2359/// slice's parent, reused directly; returned sliced over dim 1).
2360/// Same shape/reductionDims as example 4, but here the consumer is a slice
2361/// over the reduction dim, so it can broadcast the result back along that
2362/// dim. The slice's parent layout is reused as the source (no switch, no
2363/// re-derivation); the inst_data propagation step has already inserted a
2364/// convert_layout if needed, so the lane-level layout can be reused as-is.
2365
2367 xegpu::LayoutKind layoutKind, VectorType srcVecTy,
2368 DistributeLayoutAttr consumerLayout, SmallVector<int64_t> reductionDims,
2369 int numSg, const xegpu::uArch::uArch *uArch) {
2370
2371 auto srcShape = srcVecTy.getShape();
2372 int srcRank = srcShape.size();
2373 auto context = srcVecTy.getContext();
2374
2375 const int subgroupSize = uArch->getSubgroupSize();
2376 int64_t maxReduceVectorSize = 1; // could extend to spirv vector Size
2377 xegpu::DistributeLayoutAttr srcLayout;
2378 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2379 xegpu::SliceAttr consumerSliceLayout =
2380 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2381 if (consumerSliceLayout &&
2382 consumerSliceLayout.getDims().asArrayRef().equals(reductionDims)) {
2383 srcLayout = consumerSliceLayout.getParent();
2384 SmallVector<int64_t> sgLayoutFromConsumer =
2385 srcLayout.getEffectiveSgLayoutAsInt();
2386 auto srcSgData = computeShapeRatio(srcShape, sgLayoutFromConsumer);
2387 if (srcSgData)
2388 for (int dim = 0; dim < srcRank; dim++) {
2389 if (llvm::is_contained(reductionDims, dim))
2390 srcLayout =
2391 srcLayout.setDimData(dim, srcSgData.value()[dim], -1, -1);
2392 }
2393 } else {
2394 SmallVector<int64_t> consumerSgLayout =
2395 consumerLayout ? consumerLayout.getEffectiveSgLayoutAsInt()
2397 SmallVector<int64_t> consumerSgData =
2398 consumerLayout ? consumerLayout.getEffectiveSgDataAsInt()
2400 SmallVector<int64_t> consumerOrder =
2401 consumerLayout ? consumerLayout.getEffectiveOrderAsInt()
2403 DenseI32ArrayAttr orderAttr =
2404 consumerLayout ? consumerLayout.getOrder() : nullptr;
2405 SmallVector<int64_t> sgLayout(srcRank), sgData(srcRank), order(srcRank);
2406 int remainingSgCount =
2407 consumerLayout ? consumerLayout.getNumSubgroups() : numSg;
2408 int consumerIdx = 0;
2409
2410 // First pass: Match consumer's layout on non-reduction dimensions
2411 for (int i = 0; i < srcRank; i++) {
2412 if (!llvm::is_contained(reductionDims, i) &&
2413 consumerIdx < static_cast<int>(consumerSgLayout.size())) {
2414 sgLayout[i] = consumerSgLayout[consumerIdx];
2415 sgData[i] = consumerSgData[consumerIdx];
2416 remainingSgCount /= sgLayout[i];
2417 order[i] = consumerOrder[consumerIdx];
2418 consumerIdx++;
2419 }
2420 }
2421
2422 // Second pass: Distribute remaining subgroups across reduction dimensions
2423 // the reduction to scalar case is handled only by this loop
2424 int64_t remainOrder = consumerSgLayout.size();
2425 for (int i = 0; i < srcRank; i++) {
2426 if (llvm::is_contained(reductionDims, i)) {
2427 sgLayout[i] =
2428 std::min(srcShape[i], static_cast<int64_t>(remainingSgCount));
2429 assert((srcShape[i] % sgLayout[i] == 0) &&
2430 "source shape not divisible by sg_layout");
2431 sgData[i] = srcShape[i] / sgLayout[i];
2432 remainingSgCount /= sgLayout[i];
2433 order[i] = remainOrder++;
2434 }
2435 }
2437 context, SmallVector<int32_t>(order.begin(), order.end()));
2438 if (!orderAttr || orderAttr.empty())
2439 resOrderAttr = nullptr;
2440 assert(remainingSgCount == 1 && "not all subgroups distributed");
2441 srcLayout = buildLayout(context, sgLayout, sgData,
2442 /*instData=*/{}, /*laneLayout=*/{},
2443 /*laneData=*/{}, resOrderAttr);
2444 }
2445 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2446 xegpu::SliceAttr consumerSliceLayout =
2447 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2448 auto consumerReductionDims =
2449 consumerSliceLayout
2450 ? SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef())
2452 // A[i] reduced from A[i, j] is stored out directly, use vertical Lane
2453 // layout like [16, 1]
2454 bool verticalLaneLayout = consumerReductionDims.empty() &&
2455 reductionDims.size() == 1 &&
2456 reductionDims[0] == (srcRank - 1);
2457 auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
2458 srcShape, reductionDims, subgroupSize, maxReduceVectorSize,
2459 verticalLaneLayout);
2460 // inst_data is the per-instruction data, i.e. the element-wise product of
2461 // lane_layout and lane_data.
2462 SmallVector<int64_t> instData(srcRank);
2463 for (int i = 0; i < srcRank; i++)
2464 instData[i] = laneLayout[i] * laneData[i];
2465 srcLayout =
2466 buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
2467 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2468 // Only the innermost two dimensions are distributed; all leading dimensions
2469 // are assumed to be unit dimensions.
2470 assert(leadingDimsAreUnit(srcShape, /*numInnerDims=*/2) &&
2471 "Lane reduction layout assumes all leading (non-innermost-two) "
2472 "dimensions are unit dimensions");
2473 xegpu::SliceAttr consumerSliceLayout =
2474 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2475 auto consumerReductionDims =
2476 consumerSliceLayout
2477 ? SmallVector<int64_t>(consumerSliceLayout.getDims().asArrayRef())
2479 if (consumerSliceLayout &&
2480 consumerSliceLayout.getDims().asArrayRef().equals(reductionDims)) {
2481 // at the lane level, the consumerSliceLayout can be directly reused
2482 // since the inst_data propagation already insert convert_layout if
2483 // the layout is not consistent
2484 srcLayout = consumerSliceLayout.getParent();
2485 } else {
2486 bool verticalLaneLayout = consumerReductionDims.empty() &&
2487 reductionDims.size() == 1 &&
2488 reductionDims[0] == (srcRank - 1);
2489 auto [laneLayout, laneData] = computeReductionLaneLayoutAndData(
2490 srcShape, reductionDims, subgroupSize, maxReduceVectorSize,
2491 verticalLaneLayout);
2492 srcLayout = buildLaneLayout(context, laneLayout, laneData);
2493 }
2494 }
2495
2496 return xegpu::SliceAttr::get(context, srcLayout,
2497 DenseI64ArrayAttr::get(context, reductionDims));
2498}
2499
2500/// Sets up layout for Reduction operations by creating a SliceAttr for the
2501/// result.
2502xegpu::SliceAttr
2504 VectorType srcVecTy,
2505 const xegpu::uArch::uArch *uArch) {
2506
2507 auto srcShape = srcVecTy.getShape();
2508 auto context = srcVecTy.getContext();
2509 auto subgroupSize = uArch->getSubgroupSize();
2510 xegpu::LayoutAttr srcLayout;
2511
2512 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2513 assert(false &&
2514 "subgroup layout assignment not supported for reduction (op "
2515 "is not expected at this level).");
2516 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2517 assert(false &&
2518 "instData layout assignment not supported for reduction (op "
2519 "is not expected at this level).");
2520 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2521 SmallVector<int64_t> laneLayout(1), laneData(1);
2522 laneLayout[0] = std::min(static_cast<int64_t>(subgroupSize), srcShape[0]);
2523 laneData[0] = 1;
2524 srcLayout = buildLaneLayout(context, laneLayout, laneData);
2525 }
2526
2527 auto result = xegpu::SliceAttr::get(context, srcLayout,
2528 DenseI64ArrayAttr::get(context, 0));
2529 return result;
2530}
2531
2532/// Adjusts `consumerLayout`'s innermost-dim data field selected by
2533/// `layoutKind` so that the source layout can be safely inferred by dividing
2534/// that value by `ratio`. Doubles the value until the divisibility constraint
2535/// is met, bounded above by `bound` like result-shape.
2536///
2537/// Used by ops whose source relates to the result by a fixed factor along the
2538/// innermost dim (e.g., bitcast: bitwidth ratio; interleave: 2x).
2539///
2540/// Divisibility constraints per LayoutKind:
2541/// - Subgroup: sgData[innermost] % ratio == 0
2542/// - InstData: instData[innermost] % (laneLayout[innermost] * ratio) == 0
2543/// (laneLayout falls back to subgroupSize if absent)
2544/// - Lane: laneData[innermost] % ratio == 0
2545static xegpu::DistributeLayoutAttr
2546adjustInnermostDimForDivisibility(xegpu::DistributeLayoutAttr consumerLayout,
2547 xegpu::LayoutKind layoutKind,
2548 size_t innerMostDim, int ratio, int64_t bound,
2549 const xegpu::uArch::uArch *uArch) {
2550 SmallVector<int64_t> sgData = consumerLayout.getEffectiveSgDataAsInt();
2551 SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
2552 SmallVector<int64_t> laneData = consumerLayout.getEffectiveLaneDataAsInt();
2553 SmallVector<int64_t> laneLayout =
2554 consumerLayout.getEffectiveLaneLayoutAsInt();
2555
2556 int64_t sgDataValue = -1;
2557 int64_t instDataValue = -1;
2558 int64_t laneDataValue = -1;
2559
2560 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2561 sgDataValue = sgData[innerMostDim];
2562 while ((sgDataValue <= bound) && (sgDataValue % ratio) != 0)
2563 sgDataValue *= 2;
2564 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2565 instDataValue = instData[innerMostDim];
2566 const int innermostDimLaneLayout = laneLayout.empty()
2567 ? uArch->getSubgroupSize()
2568 : laneLayout[innerMostDim];
2569 while ((instDataValue <= bound) &&
2570 (instDataValue % (innermostDimLaneLayout * ratio) != 0))
2571 instDataValue *= 2;
2572 assert((bound % instDataValue) == 0 &&
2573 "bound, instData, and laneLayout for innermost must be 2^n!");
2574 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2575 laneDataValue = laneData[innerMostDim];
2576 while ((laneDataValue <= bound) && (laneDataValue % ratio) != 0)
2577 laneDataValue *= 2;
2578 }
2579
2580 return consumerLayout.setDimData(innerMostDim, sgDataValue, instDataValue,
2581 laneDataValue);
2582}
2583
2584/// Sets up the result layout for a bitcast operation.
2585/// When casting to a smaller bitwidth, adjusts the layout dimensions (sgData,
2586/// instData, or laneData) by multiplying by the bitwidth ratio to ensure the
2587/// result layout can be correctly divided back to the source layout during
2588/// inference.
2589///
2590/// Examples:
2591/// 1. Casting f32 -> f16 (32-bit to 16-bit, bitWidthRatio = 2):
2592/// Consumer layout: instData=[1, 16], subgroupSize=16
2593/// Source shape: [8, 32]
2594/// Result layout: instData=[1, 32] (16 * 2)
2595/// The innermost dimension is multiplied by 2 to maintain consistency.
2596///
2597/// 2. Casting f32 -> i8 (32-bit to 8-bit, bitWidthRatio = 4):
2598/// Consumer instData=[1, 16], subgroupSize=16
2599/// Source shape: [4, 128]
2600/// adjust the instData from [1, 16] to [1, 16 * 4 = 64]
2601///
2602/// 3. Casting i8 -> i32 (8-bit to 32-bit, bitWidthRatio = 1/4):
2603/// Consumer layout: laneLayout=[1, 16], laneData=[1, 4]
2604/// No adjustment needed - returns consumer layout directly.
2605///
2606xegpu::DistributeLayoutAttr xegpu::setupBitCastResultLayout(
2607 xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
2608 DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
2609
2610 int srcElemTyBitWidth = srcVecTy.getElementType().getIntOrFloatBitWidth();
2611 int resElemTyBitWidth = resVecTy.getElementType().getIntOrFloatBitWidth();
2612
2613 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
2614 ArrayRef<int64_t> resShape = resVecTy.getShape();
2615
2616 assert(consumerLayout.getRank() == static_cast<int64_t>(srcShape.size()) &&
2617 "laneData must be available for all dimensions");
2618
2619 // Casting to same/larger element type: result has fewer (or equal) elements
2620 // along the innermost dim, no adjustment needed.
2621 if (srcElemTyBitWidth <= resElemTyBitWidth)
2622 return consumerLayout;
2623
2624 // Casting to smaller element type: result has more elements along innermost
2625 // dim. Adjust the innermost data field upward so the source layout can be
2626 // recovered by dividing by bitWidthRatio.
2627 size_t innerMostDim = srcShape.size() - 1;
2628 int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
2629 return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
2630 innerMostDim, bitWidthRatio,
2631 resShape[innerMostDim], uArch);
2632}
2633
2634/// Sets up the result layout for an interleave operation to ensure the source
2635/// layout can be safely derived. Interleave doubles the innermost dimension,
2636/// so the result layout must ensure that laneData is a multiple
2637/// of 2, and instData must be divisible by innermostDimLaneLayout * 2.
2638///
2639/// Example:
2640/// Interleave: vector<128x256xf4> -> vector<128x512xf4>
2641/// Consumer layout: laneLayout=[1, 16], laneData=[1, 4], instData=[1, 64]
2642/// Result layout adjustment to ensure source can be safely inferred:
2643/// - laneData must be >= 2 and multiple of 2 (so source = laneData/2 is
2644/// valid)
2645/// - instData must be divisible by (16 * 2 = 32) (so source = instData/2 is
2646/// valid)
2647/// - Adjusted instData: ensure (instData % 32 == 0)
2648///
2649xegpu::DistributeLayoutAttr xegpu::setupInterleaveResultLayout(
2650 xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
2651 DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
2652
2653 ArrayRef<int64_t> resShape = resVecTy.getShape();
2654 assert(consumerLayout.getRank() == static_cast<int64_t>(resShape.size()) &&
2655 "consumer layout rank must match source shape rank");
2656
2657 // Interleave doubles the innermost dimension (ratio = 2). Adjust the
2658 // innermost data field so the source layout can be recovered by dividing
2659 // by 2.
2660 const size_t innerMostDim = resShape.size() - 1;
2661 constexpr int ratio = 2;
2662 return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
2663 innerMostDim, ratio,
2664 resShape[innerMostDim], uArch);
2665}
2666
2667/// Sets up the result layout for an insert strided slice operation.
2668/// Creates a result layout based on the specified layout kind (InstData or
2669/// Lane).
2670xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
2671 xegpu::LayoutKind layoutKind, VectorType srcVectorTy,
2672 VectorType resVectorTy, xegpu::DistributeLayoutAttr consumerLayout,
2673 const xegpu::uArch::uArch *uArch) {
2674
2675 xegpu::DistributeLayoutAttr requiredResLayout;
2676 SmallVector<int64_t> consumerInstData =
2677 consumerLayout.getEffectiveInstDataAsInt();
2678 SmallVector<int64_t> consumerLaneData =
2679 consumerLayout.getEffectiveLaneDataAsInt();
2680 SmallVector<int64_t> consumerLaneLayout =
2681 consumerLayout.getEffectiveLaneLayoutAsInt();
2682 ArrayRef<int64_t> srcShape = srcVectorTy.getShape();
2683 int64_t laneDataValue = -1;
2684
2685 requiredResLayout = consumerLayout;
2686 int srcRank = srcShape.size();
2687
2688 if (layoutKind == xegpu::LayoutKind::Subgroup ||
2689 layoutKind == xegpu::LayoutKind::InstData) {
2690 assert(false && "subgroup/instData layout assignment not supported for "
2691 "insertStridedSlice.");
2692 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2693 for (int dim = 0; dim < srcRank; dim++) {
2694 // A size-1 source dim is broadcast across the lanes of that dim.
2695 if (srcShape[dim] == 1) {
2696 laneDataValue = 1;
2697 } else {
2698 assert(srcShape[dim] % consumerLaneLayout[dim] == 0 &&
2699 "srcShape must be divisible by laneLayout for all dimensions");
2700 laneDataValue = std::min(srcShape[dim] / consumerLaneLayout[dim],
2701 consumerLaneData[dim]);
2702 }
2703 requiredResLayout =
2704 requiredResLayout.setDimData(dim, -1, -1, laneDataValue);
2705 }
2706 }
2707 return requiredResLayout;
2708}
2709
2710/// Back-propagates a known result layout to the layout required on `operand`
2711/// for a non-anchor (layout-propagating) vector op. Dispatches on the op kind —
2712/// broadcast, (multi)reduction, bitcast, shape/transpose, insert/extract,
2713/// interleave, etc. — applying the shape/permutation/bitwidth transform to
2714/// derive the source layout; elementwise and pass-through ops reuse resLayout
2715/// as-is. Returns nullptr for unknown ops or an absent result layout.
2716xegpu::DistributeLayoutAttr xegpu::inferSourceLayoutFromResultForNonAnchorOp(
2717 OpOperand &operand, xegpu::DistributeLayoutAttr resLayout) {
2718 if (!resLayout)
2719 return nullptr;
2720 Operation *op = operand.getOwner();
2721 unsigned idx = operand.getOperandNumber();
2722
2723 // For vector::BroadcastOp, infer the source layout from the result layout.
2724 if (auto broadcast = dyn_cast<vector::BroadcastOp>(op)) {
2725 auto srcTy = dyn_cast<VectorType>(broadcast.getSourceType());
2726 if (!srcTy)
2727 return nullptr;
2729 resLayout, broadcast.getResultVectorType().getShape(),
2730 srcTy.getShape());
2731 }
2732
2733 // For vector::MultiDimReductionOp, infer source layout from result layout
2734 // using reduction dims. Acc operand is expected to have the same layout as
2735 // the result.
2736 if (auto reduction = dyn_cast<vector::MultiDimReductionOp>(op)) {
2737 if (idx == 0) {
2738 SmallVector<int64_t> reductionDims(reduction.getReductionDims());
2739 return xegpu::inferMultiReductionSourceLayout(resLayout, reductionDims);
2740 }
2741 if (idx == 1)
2742 return resLayout;
2743 }
2744
2745 if (auto reduction = dyn_cast<vector::ReductionOp>(op))
2746 return xegpu::inferReductionSourceLayout(resLayout);
2747
2748 // For vector::BitCastOp, infer source layout from result layout using
2749 // element type bitwidths.
2750 if (auto bitcast = dyn_cast<vector::BitCastOp>(op)) {
2751 int resElemBitWidth =
2752 bitcast.getResultVectorType().getElementType().getIntOrFloatBitWidth();
2753 int srcElemBitWidth =
2754 bitcast.getSourceVectorType().getElementType().getIntOrFloatBitWidth();
2755 return xegpu::inferBitCastSourceLayout(resLayout, resElemBitWidth,
2756 srcElemBitWidth);
2757 }
2758
2759 // For vector::ShapeCastOp, infer source layout from result layout using
2760 // shapes.
2761 if (auto shapeCast = dyn_cast<vector::ShapeCastOp>(op)) {
2763 resLayout, shapeCast.getResultVectorType().getShape(),
2764 shapeCast.getSourceVectorType().getShape());
2765 }
2766
2767 // For vector::InsertStridedSliceOp, infer source layout from result
2768 // layout. Dest vector must have the same layout as the result.
2769 if (auto insertSlice = dyn_cast<vector::InsertStridedSliceOp>(op)) {
2770 if (idx == 0) {
2772 resLayout, insertSlice.getDestVectorType().getShape(),
2773 insertSlice.getSourceVectorType().getShape());
2774 }
2775 if (idx == 1)
2776 return resLayout;
2777 }
2778
2779 // For vector::Insert Op, infer source layout from result layout using
2780 // shapes.
2781 if (auto insert = dyn_cast<vector::InsertOp>(op)) {
2782 VectorType resVecTy = dyn_cast<VectorType>(insert.getResult().getType());
2783 VectorType valueToStoreTy =
2784 dyn_cast<VectorType>(insert.getValueToStore().getType());
2785
2786 if ((idx == 0) && valueToStoreTy) {
2787 return xegpu::inferInsertSourceLayout(resLayout, resVecTy.getShape(),
2788 valueToStoreTy.getShape());
2789 }
2790 if (idx == 1)
2791 return resLayout;
2792 }
2793
2794 // For vector::Extract Op, infer source layout from result layout using
2795 // shapes.
2796 if (auto extract = dyn_cast<vector::ExtractOp>(op)) {
2797 VectorType srcVecTy = dyn_cast<VectorType>(extract.getSource().getType());
2798 VectorType resVecTy = dyn_cast<VectorType>(extract.getResult().getType());
2799 if (!srcVecTy || !resVecTy)
2800 return nullptr;
2801 return xegpu::inferExtractSourceLayout(resLayout, resVecTy.getShape(),
2802 srcVecTy.getShape());
2803 }
2804
2805 // For vector::TransposeOp, infer source layout from result layout using
2806 // permutation.
2807 if (auto transpose = dyn_cast<vector::TransposeOp>(op)) {
2808 return xegpu::inferTransposeSourceLayout(resLayout,
2809 transpose.getPermutation());
2810 }
2811
2812 // For vector::BitCastOp, infer source layout from result layout using
2813 // element type bitwidths.
2814 if (auto bitcast = dyn_cast<vector::BitCastOp>(op)) {
2815 int resElemBitWidth =
2816 bitcast.getResultVectorType().getElementType().getIntOrFloatBitWidth();
2817 int srcElemBitWidth =
2818 bitcast.getSourceVectorType().getElementType().getIntOrFloatBitWidth();
2819 return xegpu::inferBitCastSourceLayout(resLayout, resElemBitWidth,
2820 srcElemBitWidth);
2821 }
2822
2823 // for vector::interleave
2824 if (auto interleave = dyn_cast<vector::InterleaveOp>(op)) {
2825 return xegpu::inferInterleaveSourceLayout(resLayout);
2826 }
2827
2828 // for vector::deinterleave
2829 if (auto deinterleave = dyn_cast<vector::DeinterleaveOp>(op)) {
2830 return xegpu::inferDeinterleaveSourceLayout(resLayout);
2831 }
2832
2833 // For vector::ExtractStridedSliceOp, simply return result layout
2834 if (dyn_cast<vector::ExtractStridedSliceOp>(op))
2835 return resLayout;
2836
2837 // For elementwise operations, all operands must have the same layout as
2838 // the result.
2840 return resLayout;
2841
2842 return nullptr;
2843}
2844
2845/// Returns the layout required on `operand`: anchor ops report their declared
2846/// per-operand layout directly; non-anchor ops back-derive it from their result
2847/// layout via inferSourceLayoutFromResultForNonAnchorOp.
2848xegpu::DistributeLayoutAttr xegpu::getConsumerLayoutAt(OpOperand &operand) {
2849 Operation *op = operand.getOwner();
2850 // Anchor ops declare the layout they
2851 // require on each operand. Trust that declaration directly so that
2852 // ResolveLayoutConflicts compares producer-vs-declared
2853 if (isa<xegpu::AnchorLayoutInterface>(op))
2854 return xegpu::getDistributeLayoutAttr(operand);
2855 // For non-anchor ops, derive the operand layout from the op's result
2856 // layout via op-specific semantics.
2857 xegpu::DistributeLayoutAttr resLayout;
2858 if (op->getNumResults() == 1 || isa<vector::DeinterleaveOp>(op))
2859 resLayout = xegpu::getDistributeLayoutAttr(op->getResult(0));
2860 return inferSourceLayoutFromResultForNonAnchorOp(operand, resLayout);
2861}
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 SmallVector< LayoutRepresentation > getSgLayoutCandidates(ArrayRef< int64_t > wgShape, ArrayRef< int64_t > instData, int64_t sgCount, int64_t broadcastDim=-1)
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 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