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