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
438static xegpu::LayoutAttr buildInstDataLayoutWithLane(
439 mlir::MLIRContext *context, ArrayRef<int64_t> instData,
440 ArrayRef<int64_t> laneLayout, ArrayRef<int64_t> laneData,
441 DenseI32ArrayAttr orderAttr = nullptr) {
442 auto toI32Attr = [&](auto range) {
443 SmallVector<int32_t> v(range.begin(), range.end());
444 return DenseI32ArrayAttr::get(context, v);
445 };
446 return xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
447 /*sg_data=*/nullptr, toI32Attr(instData),
448 toI32Attr(laneLayout), toI32Attr(laneData),
449 orderAttr);
450}
451
453 ArrayRef<int64_t> laneLayout,
454 ArrayRef<int64_t> laneData) {
455 return !llvm::any_of(llvm::seq<int>(0, dataShape.size()), [&](int dim) {
456 return dataShape[dim] % (laneLayout[dim] * laneData[dim]) != 0;
457 });
458}
459
460static xegpu::LayoutAttr
462 ArrayRef<int64_t> laneData,
463 DenseI32ArrayAttr orderAttr = nullptr) {
464 auto toI32Attr = [&](auto range) {
465 SmallVector<int32_t> v(range.begin(), range.end());
466 return DenseI32ArrayAttr::get(context, v);
467 };
468 return xegpu::LayoutAttr::get(context, /*sg_layout=*/nullptr,
469 /*sg_data=*/nullptr,
470 /*inst_data=*/nullptr, toI32Attr(laneLayout),
471 toI32Attr(laneData), orderAttr);
472}
473
474static xegpu::LayoutAttr
476 ArrayRef<int64_t> sgData, ArrayRef<int64_t> instData,
477 ArrayRef<int64_t> laneLayout, ArrayRef<int64_t> laneData,
478 DenseI32ArrayAttr orderAttr = nullptr) {
479 auto toI32Attr = [&](auto range) {
480 SmallVector<int32_t> v(range.begin(), range.end());
481 return DenseI32ArrayAttr::get(context, v);
482 };
483 return xegpu::LayoutAttr::get(
484 context, sgLayout.empty() ? nullptr : toI32Attr(sgLayout),
485 sgData.empty() ? nullptr : toI32Attr(sgData),
486 instData.empty() ? nullptr : toI32Attr(instData),
487 laneLayout.empty() ? nullptr : toI32Attr(laneLayout),
488 laneData.empty() ? nullptr : toI32Attr(laneData), orderAttr);
489}
490
491static xegpu::LayoutAttr buildSgLayout(mlir::MLIRContext *context,
492 ArrayRef<int64_t> wgTileShape,
493 ArrayRef<int64_t> sgLayout,
494 int dimK = -1,
495 DenseI32ArrayAttr orderAttr = nullptr) {
496 SmallVector<int64_t> sgData(sgLayout.size());
497 for (int dim = 0; dim < (int)sgLayout.size(); ++dim) {
498 if (dim == dimK)
499 sgData[dim] = wgTileShape[dim];
500 else
501 sgData[dim] = wgTileShape[dim] / sgLayout[dim];
502 }
503 return buildLayout(context, sgLayout, sgData,
504 /*inst_data=*/{}, /*lane_layout=*/{},
505 /*lane_data=*/{}, /*order=*/nullptr);
506}
507
508/// Infers the source layout attribute for a broadcast operation given the
509/// result layout attribute, result shape, source shape.
510xegpu::DistributeLayoutAttr
511xegpu::inferBroadcastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
512 ArrayRef<int64_t> resShape,
513 ArrayRef<int64_t> srcShape) {
514
515 SmallVector<int64_t> bcastDims;
516 size_t dimDiff = resShape.size() - srcShape.size();
517 auto bcastSourceLayout = resLayout;
518
519 // Right-aligned source in result, look for stretched unit dims.
520 for (size_t i = dimDiff; i < resShape.size(); i++) {
521 if ((srcShape[i - dimDiff] == 1) && (resShape[i] != 1))
522 bcastDims.push_back(i);
523 }
524
525 // Case UnitDimStretch (e.g., 1x4 -> 4x4): the source layout data field must
526 // be 1.
527 if (!bcastDims.empty())
528 bcastSourceLayout = bcastSourceLayout.setUnitDimData(bcastDims);
529
530 // Case RankDiff:
531 if (dimDiff) {
532 SmallVector<int64_t> sliceDims;
533 bool isOuterDimDiffUnitDims = llvm::all_of(
534 resShape.take_front(dimDiff), [&](int64_t dim) { return dim == 1; });
535 if (dimDiff && bcastDims.size() == dimDiff && isOuterDimDiffUnitDims) {
536 // Case RankDiffInnerDims (e.g., 1x4 -> 1x16x4):
537 // slice the expanded inner dims
538 sliceDims.assign(bcastDims.begin(), bcastDims.end());
539 } else {
540 // Case RankDiffOuterDims (e.g., 1x4 -> 1x1x4):
541 // slice the outer dims
542 llvm::append_range(sliceDims, llvm::seq<int64_t>(0, dimDiff));
543 }
544 bcastSourceLayout = xegpu::SliceAttr::get(
545 resLayout.getContext(), bcastSourceLayout,
546 DenseI64ArrayAttr::get(resLayout.getContext(), sliceDims));
547 }
548 return bcastSourceLayout;
549}
550
551/// Infers the source layout attribute for a reduction operation given the
552/// result layout attribute and reduced dims.
553xegpu::DistributeLayoutAttr
554xegpu::inferMultiReductionSourceLayout(xegpu::DistributeLayoutAttr resLayout,
555 SmallVector<int64_t> reduceDims) {
556
557 assert(isa<xegpu::SliceAttr>(resLayout) &&
558 "reduction result layout must be slice layout");
559
560 xegpu::SliceAttr sliceLayout = dyn_cast<xegpu::SliceAttr>(resLayout);
561
562 assert((reduceDims == sliceLayout.getDims().asArrayRef()) &&
563 "reduction dims must match with slice dims");
564
565 return sliceLayout.getParent();
566}
567
568xegpu::DistributeLayoutAttr
569xegpu::inferReductionSourceLayout(xegpu::DistributeLayoutAttr resLayout) {
570 return xegpu::inferMultiReductionSourceLayout(resLayout, {0});
571}
572
573/// Infers the source layout attribute for a transpose operation given the
574/// result layout attribute and permutation.
575///
576/// vector.transpose semantics is `result[i] = source[permutation[i]]`, so
577/// `result_layout[i] = source_layout[permutation[i]]`. To recover the source
578/// layout from the result layout we must apply the inverse permutation.
579xegpu::DistributeLayoutAttr
580xegpu::inferTransposeSourceLayout(xegpu::DistributeLayoutAttr resLayout,
581 ArrayRef<int64_t> permutation) {
583 invertPermutationVector(permutation);
584 return resLayout.transposeDims(inversePermutation);
585}
586
587/// Infers the source layout attribute for a bitcast operation given the
588/// result layout attribute, result element type bitwidth, and source element
589/// type bitwidth.
590xegpu::DistributeLayoutAttr
591xegpu::inferBitCastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
592 int resElemTyBitWidth, int srcElemTyBitWidth) {
593
594 SmallVector<int64_t> sgData = resLayout.getEffectiveSgDataAsInt();
595 SmallVector<int64_t> instData = resLayout.getEffectiveInstDataAsInt();
596 SmallVector<int64_t> laneData = resLayout.getEffectiveLaneDataAsInt();
597 size_t sgDataSize = sgData.size();
598 size_t instDataSize = instData.size();
599 size_t laneDataSize = laneData.size();
600 int64_t sgDataValue = -1;
601 int64_t instDataValue = -1;
602 int64_t laneDataValue = -1;
603 int64_t dim = resLayout.getRank() - 1;
604
605 if (srcElemTyBitWidth <= resElemTyBitWidth) {
606 int bitWidthRatio = resElemTyBitWidth / srcElemTyBitWidth;
607 if (sgDataSize)
608 sgDataValue = sgData.back() * bitWidthRatio;
609 if (instDataSize)
610 instDataValue = instData.back() * bitWidthRatio;
611 if (laneDataSize)
612 laneDataValue = laneData.back() * bitWidthRatio;
613 } else {
614 int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
615 if (sgDataSize) {
616 assert((sgData.back() % bitWidthRatio) == 0 &&
617 "sgData not divisible by bitWidthRatio");
618 sgDataValue = sgData.back() / bitWidthRatio;
619 }
620 if (instDataSize) {
621 assert((instData.back() % bitWidthRatio) == 0 &&
622 "instData not divisible by bitWidthRatio");
623 instDataValue = instData.back() / bitWidthRatio;
624 }
625 if (laneDataSize) {
626 assert((laneData.back() % bitWidthRatio) == 0 &&
627 "laneData not divisible by bitWidthRatio");
628 laneDataValue = laneData.back() / bitWidthRatio;
629 }
630 }
631
632 xegpu::DistributeLayoutAttr finalSrcLayout;
633 finalSrcLayout =
634 resLayout.setDimData(dim, sgDataValue, instDataValue, laneDataValue);
635
636 return finalSrcLayout;
637}
638
639/// Infers the source layout attribute for an interleave operation given the
640/// result layout attribute. Interleave doubles the size of the innermost
641/// dimension, so the layout inference is similar to bitcast where the source
642/// element type is larger than the result element type (ratio = 2).
643xegpu::DistributeLayoutAttr
644xegpu::inferInterleaveSourceLayout(xegpu::DistributeLayoutAttr resLayout) {
645
646 SmallVector<int64_t> sgData = resLayout.getEffectiveSgDataAsInt();
647 SmallVector<int64_t> instData = resLayout.getEffectiveInstDataAsInt();
648 SmallVector<int64_t> laneData = resLayout.getEffectiveLaneDataAsInt();
649 size_t sgDataSize = sgData.size();
650 size_t instDataSize = instData.size();
651 size_t laneDataSize = laneData.size();
652 int64_t sgDataValue = -1;
653 int64_t instDataValue = -1;
654 int64_t laneDataValue = -1;
655 int64_t dim = resLayout.getRank() - 1;
656
657 // Interleave doubles the innermost dimension, so we need to halve the
658 // layout values (similar to bitcast with ratio = 2)
659 constexpr int ratio = 2;
660 if (sgDataSize) {
661 assert((sgData.back() % ratio) == 0 &&
662 "sgData not divisible by interleave ratio");
663 sgDataValue = sgData.back() / ratio;
664 }
665 if (instDataSize) {
666 assert((instData.back() % ratio) == 0 &&
667 "instData not divisible by interleave ratio");
668 instDataValue = instData.back() / ratio;
669 }
670 if (laneDataSize) {
671 assert((laneData.back() % ratio) == 0 &&
672 "laneData not divisible by interleave ratio");
673 laneDataValue = laneData.back() / ratio;
674 }
675
676 return resLayout.setDimData(dim, sgDataValue, instDataValue, laneDataValue);
677}
678
679/// Infers the source layout attribute for a deinterleave operation given the
680/// result layout attribute. Deinterleave halves the size of the innermost
681/// dimension, so the layout inference is similar to bitcast where the source
682/// element type is smaller than the result element type (ratio = 2).
683xegpu::DistributeLayoutAttr
684xegpu::inferDeinterleaveSourceLayout(xegpu::DistributeLayoutAttr resLayout) {
685
686 SmallVector<int64_t> sgData = resLayout.getEffectiveSgDataAsInt();
687 SmallVector<int64_t> instData = resLayout.getEffectiveInstDataAsInt();
688 SmallVector<int64_t> laneData = resLayout.getEffectiveLaneDataAsInt();
689 size_t sgDataSize = sgData.size();
690 size_t instDataSize = instData.size();
691 size_t laneDataSize = laneData.size();
692 int64_t sgDataValue = -1;
693 int64_t instDataValue = -1;
694 int64_t laneDataValue = -1;
695 int64_t dim = resLayout.getRank() - 1;
696
697 // Deinterleave halves the innermost dimension, so we need to double the
698 // layout values (similar to bitcast with ratio = 2)
699 constexpr int ratio = 2;
700 if (sgDataSize)
701 sgDataValue = sgData.back() * ratio;
702 if (instDataSize)
703 instDataValue = instData.back() * ratio;
704 if (laneDataSize)
705 laneDataValue = laneData.back() * ratio;
706
707 return resLayout.setDimData(dim, sgDataValue, instDataValue, laneDataValue);
708}
709
710/// Infers the source layout attribute for an insert strided slice operation
711/// given the result layout attribute, result shape, and source shape. Removes
712/// leading dimensions from the result layout to match the source shape size.
713xegpu::DistributeLayoutAttr xegpu::inferInsertStridedSliceSourceLayout(
714 xegpu::DistributeLayoutAttr resLayout, ArrayRef<int64_t> resShape,
715 ArrayRef<int64_t> srcShape) {
716
717 int srcShapeSize = srcShape.size();
718 int resShapeSize = resShape.size();
719 int dimDiff = resShapeSize - srcShapeSize;
720
721 if (dimDiff > 0) {
722 // assert that the leading dimensions being sliced off are not distributed
723 // (i.e. sg_layout and lane_layout for those dimensions are all 1)
724 auto resSgLayout = resLayout.getEffectiveSgLayoutAsInt();
725 auto resLaneLayout = resLayout.getEffectiveLaneLayoutAsInt();
726 for (int i = 0; i < dimDiff; i++) {
727 assert((resSgLayout.size() == 0 || resSgLayout[i] == 1) &&
728 (resLaneLayout.size() == 0 || resLaneLayout[i] == 1) &&
729 "Leading dimensions being sliced off must not be distributed");
730 }
731 return resLayout.dropDims(llvm::to_vector(llvm::seq<int64_t>(0, dimDiff)));
732 }
733 return resLayout;
734}
735
736/// Infers the source layout attribute for an insert operation
737/// given the result layout attribute, result shape, and source shape. Removes
738/// leading dimensions from the result layout to match the source shape size.
739// TODO: add propagation support for insert op
740xegpu::DistributeLayoutAttr
741xegpu::inferInsertSourceLayout(xegpu::DistributeLayoutAttr resLayout,
742 ArrayRef<int64_t> resShape,
743 ArrayRef<int64_t> srcShape) {
744
745 int srcShapeSize = srcShape.size();
746 int resShapeSize = resShape.size();
747 int dimDiff = resShapeSize - srcShapeSize;
748
749 if (dimDiff > 0) {
750 // assert that the leading dimensions being sliced off are not distributed
751 // (i.e. sg_layout and lane_layout for those dimensions are all 1)
752 auto resSgLayout = resLayout.getEffectiveSgLayoutAsInt();
753 auto resLaneLayout = resLayout.getEffectiveLaneLayoutAsInt();
754 for (int i = 0; i < dimDiff; i++) {
755 assert((resSgLayout.size() == 0 || resSgLayout[i] == 1) &&
756 (resLaneLayout.size() == 0 || resLaneLayout[i] == 1) &&
757 "Leading dimensions being sliced off must not be distributed");
758 }
759 return resLayout.dropDims(llvm::to_vector(llvm::seq<int64_t>(0, dimDiff)));
760 }
761 return resLayout;
762}
763
764/// Infers the source layout attribute for extract operation
765/// given the result layout attribute, result shape, and source shape. Adds
766/// leading dimensions to the source layout to match the source shape size.
767// TODO: add layout attribute interface: expandDim() and use it here.
768// TODO: add propagation support for extract op
769xegpu::DistributeLayoutAttr
770xegpu::inferExtractSourceLayout(xegpu::DistributeLayoutAttr resLayout,
771 ArrayRef<int64_t> resShape,
772 ArrayRef<int64_t> srcShape) {
773
774 int srcShapeSize = srcShape.size();
775 int resShapeSize = resShape.size();
776 int dimDiff = srcShapeSize - resShapeSize;
777 auto context = resLayout.getContext();
778 // construct the source layout by adding unit dimensions to the front of
779 // result layout
780 if (dimDiff > 0) {
781 auto sgLayout = resLayout.getEffectiveSgLayoutAsInt();
782 auto sgData = resLayout.getEffectiveSgDataAsInt();
783 auto instData = resLayout.getEffectiveInstDataAsInt();
784 auto laneLayout = resLayout.getEffectiveLaneLayoutAsInt();
785 auto laneData = resLayout.getEffectiveLaneDataAsInt();
786 auto order = resLayout.getEffectiveOrderAsInt();
787
788 // Example: result shape is 3D with order [1, 2, 0], source shape is 5D
789 // (adding 2 leading dimensions). Expected source order: [3, 4, 2, 1, 0]
790 // Step 1: shift existing order by dimDiff: [1, 2, 0] -> [3, 4, 2]
791 // Step 2: append new leading dims in reverse (slowest first): [3, 4, 2, 1,
792 // 0]
793
794 // Shift existing dimension indices in order by dimDiff to account for the
795 // new leading dimensions being added to the source shape
796 for (auto &o : order)
797 o += dimDiff;
798
799 // Add unit dimensions to the front of non-empty layout vectors and append
800 // the new dimension indices to the order array in reverse (slowest
801 // dimension has the lowest index and appears last in the order array)
802 for (int i = 0; i < dimDiff; i++) {
803 if (!sgLayout.empty())
804 sgLayout.insert(sgLayout.begin(), 1);
805 if (!sgData.empty())
806 sgData.insert(sgData.begin(), 1);
807 if (!instData.empty())
808 instData.insert(instData.begin(), 1);
809 if (!laneLayout.empty())
810 laneLayout.insert(laneLayout.begin(), 1);
811 if (!laneData.empty())
812 laneData.insert(laneData.begin(), 1);
813 order.push_back(dimDiff - 1 - i);
814 }
815
817 context, SmallVector<int32_t>(order.begin(), order.end()));
818 if (!resLayout.getOrder())
819 orderAttr = nullptr;
820
821 return buildLayout(context, sgLayout, sgData, instData, laneLayout,
822 laneData, orderAttr);
823 }
824 return resLayout;
825}
826
827/// Infers the source layout attribute for a shape cast operation given the
828/// result layout attribute, result shape, and source shape.
829xegpu::DistributeLayoutAttr
830xegpu::inferShapeCastSourceLayout(xegpu::DistributeLayoutAttr resLayout,
831 ArrayRef<int64_t> resShape,
832 ArrayRef<int64_t> srcShape) {
833
834 // There are three use cases:
835 // 1. expand dims of low-rank dimensions (e.g., 1D to 2D): to set up the
836 // tensor before broadcast
837 // 2. split dim of a high-rank dimension (e.g., 1D to 2D): to setup tensor
838 // for multi-stage reduction
839 // 3. combines all dims to a single dim and put in the innermost dim in 2d as
840 // [1, combinedData] or [combinedData]. Say, [2, 4, 8] -> [1, 64] or [64]
841 // Use cases are only supported after workgroup distribution,
842 // like cross-sg reduction saves multidimension data to
843 // 1D slm buffer, shapecast inserted by cse/canonicalization passes.
844
845 // Use case 1: Shapes only differ by expanding unit dimensions, for broadcast
846 SmallVector<int64_t> expandedUnitDims;
847
848 if (xegpu::matchUnitDimExpansion(srcShape, resShape, expandedUnitDims)) {
849 // create a slice layout for the source by removing the expanded unit dims
850 auto sliceDimsAttr = DenseI64ArrayAttr::get(
851 resLayout.getContext(), ArrayRef<int64_t>(expandedUnitDims));
852 auto srcLayout =
853 xegpu::SliceAttr::get(resLayout.getContext(), resLayout, sliceDimsAttr);
854 return srcLayout;
855 }
856
857 // Use case 2: Dim split from source to result, for multi-stage reduction
858 SmallVector<SmallVector<int64_t>> splitDimGroups;
859 if (xegpu::matchSplitDimExpansion(srcShape, resShape, splitDimGroups)) {
860 auto srcLayout = resLayout;
861 for (const auto &dimGroup : splitDimGroups)
862 srcLayout = srcLayout.collapseDims(dimGroup);
863
864 return srcLayout;
865 }
866
867 // Use case 3: General dim collapse, for cross-sg reduction to SLM and other
868 // shape casts where consecutive src dims fold into a single dst dim.
870 if (xegpu::matchDimCollapse(srcShape, resShape, collapseDims)) {
871 auto srcLayout = resLayout;
872 for (int64_t dstIdx = static_cast<int64_t>(collapseDims.size()) - 1;
873 dstIdx >= 0; --dstIdx) {
874 ArrayRef<int64_t> srcDims = collapseDims[dstIdx];
875 if (srcDims.empty()) {
876 srcLayout = srcLayout.dropDims({dstIdx});
877 continue;
878 }
879 if (srcDims.size() == 1)
880 continue;
881 SmallVector<int64_t> targetShape;
882 targetShape.reserve(srcDims.size());
883 for (int64_t d : srcDims)
884 targetShape.push_back(srcShape[d]);
885 srcLayout = srcLayout.expandDim(dstIdx, targetShape);
886 }
887 return srcLayout;
888 }
889 return nullptr;
890}
891
892//===----------------------------------------------------------------------===//
893// Forward layout inference (source layout -> result layout)
894//===----------------------------------------------------------------------===//
895
896/// Infers the result layout attribute for a transpose operation given the
897/// source layout attribute and permutation.
898///
899/// vector.transpose semantics is `result[i] = source[permutation[i]]`, so
900/// `result_layout[i] = source_layout[permutation[i]]`, which is exactly
901/// `srcLayout.transposeDims(permutation)`. This is the inverse of
902/// inferTransposeSourceLayout (which applies the inverse permutation).
903xegpu::DistributeLayoutAttr
904xegpu::inferTransposeResultLayout(xegpu::DistributeLayoutAttr srcLayout,
905 ArrayRef<int64_t> permutation) {
906 return srcLayout.transposeDims(permutation);
907}
908
909/// Infers the result layout attribute for a shape cast operation given the
910/// source layout attribute, source shape, and result shape. This is the
911/// inverse of inferShapeCastSourceLayout: a dim-split (src -> res) is undone by
912/// collapsing the split groups, and a dim-collapse (src -> res) is undone by
913/// expanding the collapsed groups. The unit-dim-expansion case is not inverted
914/// here because recovering which result dims are the expanded unit dims would
915/// require the SliceAttr the backward direction produces; such patterns return
916/// nullptr (leaving the result un-laid-out).
917xegpu::DistributeLayoutAttr
918xegpu::inferShapeCastResultLayout(xegpu::DistributeLayoutAttr srcLayout,
919 ArrayRef<int64_t> srcShape,
920 ArrayRef<int64_t> resShape) {
921 // Case: source dims were split into result dims (forward of use case 2 in
922 // inferShapeCastSourceLayout). Undo by expanding each source dim into its
923 // group of result dims.
924 SmallVector<SmallVector<int64_t>> splitDimGroups;
925 if (xegpu::matchSplitDimExpansion(srcShape, resShape, splitDimGroups)) {
926 auto resLayout = srcLayout;
927 // Process source dims from innermost to outermost so that expanding a dim
928 // does not shift the indices of dims not yet processed.
929 for (int64_t srcIdx = static_cast<int64_t>(splitDimGroups.size()) - 1;
930 srcIdx >= 0; --srcIdx) {
931 ArrayRef<int64_t> resDims = splitDimGroups[srcIdx];
932 if (resDims.size() <= 1)
933 continue;
934 SmallVector<int64_t> targetShape;
935 targetShape.reserve(resDims.size());
936 for (int64_t d : resDims)
937 targetShape.push_back(resShape[d]);
938 resLayout = resLayout.expandDim(srcIdx, targetShape);
939 }
940 return resLayout;
941 }
942
943 // Case: source dims were collapsed into result dims (forward of use case 3).
944 // Undo by collapsing each group of source dims into its single result dim.
946 if (xegpu::matchDimCollapse(srcShape, resShape, collapseDims)) {
947 auto resLayout = srcLayout;
948 // Process result dims from innermost to outermost so that collapsing a
949 // group does not shift the indices of groups not yet processed.
950 for (int64_t dstIdx = static_cast<int64_t>(collapseDims.size()) - 1;
951 dstIdx >= 0; --dstIdx) {
952 ArrayRef<int64_t> srcDims = collapseDims[dstIdx];
953 // A result dim with no backing source dims is a trailing/leading unit
954 // dim; its forward inference is ambiguous, so bail out.
955 if (srcDims.empty())
956 return nullptr;
957 if (srcDims.size() == 1)
958 continue;
959 resLayout = resLayout.collapseDims(llvm::to_vector(srcDims));
960 }
961 return resLayout;
962 }
963
964 return nullptr;
965}
966
967/// Infers the result layout attribute for a non-anchor operation from the
968/// layouts of its source operands. Forward counterpart of
969/// inferSourceLayoutFromResultForNonAnchorOp.
970xegpu::DistributeLayoutAttr xegpu::inferResultLayoutFromSourceForNonAnchorOp(
972 if (op->getNumResults() != 1)
973 return nullptr;
974
975 // For vector::TransposeOp, infer the result layout from the source layout.
976 if (auto transpose = dyn_cast<vector::TransposeOp>(op)) {
977 if (!operandLayouts[0])
978 return nullptr;
979 return xegpu::inferTransposeResultLayout(operandLayouts[0],
980 transpose.getPermutation());
981 }
982
983 // For vector::ShapeCastOp, infer the result layout from the source layout.
984 if (auto shapeCast = dyn_cast<vector::ShapeCastOp>(op)) {
985 if (!operandLayouts[0])
986 return nullptr;
988 operandLayouts[0], shapeCast.getSourceVectorType().getShape(),
989 shapeCast.getResultVectorType().getShape());
990 }
991
992 // For elementwise operations, all operands and the result share the same
993 // layout. Use the first operand that carries a layout.
995 for (xegpu::DistributeLayoutAttr layout : operandLayouts)
996 if (layout)
997 return layout;
998 return nullptr;
999 }
1000
1001 // TODO: add forward inference rules for the remaining ops; their result is
1002 // left un-laid-out until then.
1003 // - vector::BroadcastOp: the forward direction is under-determined. The
1004 // backward rule (inferBroadcastSourceLayout) either sets broadcast dims to
1005 // unit data (losing the original data on those dims) or wraps the result
1006 // in a SliceAttr; neither is generally invertible from the source layout
1007 // alone, so a forward rule must decide how to distribute the new/stretched
1008 // dims.
1009 // - vector::BitCastOp, vector::MultiDimReductionOp / vector::ReductionOp,
1010 // vector::InterleaveOp / vector::DeinterleaveOp, and the insert / extract
1011 // / strided-slice family.
1012 return nullptr;
1013}
1014
1015//===----------------------------------------------------------------------===//
1016// Layout derivation helpers: factorize sgCount into
1017// sg_layout candidates, then
1018// compute per-subgroup (sgData) and per-lane
1019// (lane_layout/lane_data/inst_data).
1020//===----------------------------------------------------------------------===//
1021
1023
1024/// Enumerates all ways to split `total` into `rank` factors whose product
1025/// equals `total`. Returns the list of all such factorizations.
1027 int64_t rank) {
1029 SmallVector<int64_t> current(rank, 0);
1030
1031 // Returns all divisors of `n` in ascending order.
1032 auto getDivisors = [](int64_t n) {
1034 for (int64_t i = 1; i * i <= n; ++i) {
1035 if (n % i == 0) {
1036 divs.push_back(i);
1037 if (i != n / i)
1038 divs.push_back(n / i);
1039 }
1040 }
1041 llvm::sort(divs);
1042 return divs;
1043 };
1044
1045 std::function<void(int64_t, int64_t)> generate = [&](int64_t dim,
1046 int64_t remaining) {
1047 if (dim == rank - 1) {
1048 current[dim] = remaining;
1049 results.push_back(LayoutRepresentation(current));
1050 return;
1051 }
1052 for (int64_t factor : getDivisors(remaining)) {
1053 current[dim] = factor;
1054 generate(dim + 1, remaining / factor);
1055 }
1056 };
1057
1058 generate(0, total);
1059 return results;
1060}
1061
1062// Computes all valid N-dimensional sg_layout candidates for the given
1063// sgCount, whose sgData (= wgShape / sgLayout):
1064// 1. Evenly divides wgShape (i.e., wgShape[d] % sgLayout[d] == 0).
1065// 2. Is a multiple of instData (i.e., sgData[d] % instData[d] == 0).
1066// Results are sorted by balance (smallest max-min spread first), with
1067// lexicographic order as a tiebreaker.
1068//
1069// `broadcastDim` (default -1 = none) marks a dimension broadcast across
1070// subgroups rather than distributed (e.g. the K/contraction dim of a DPAS
1071// operand). Its full extent stays in every subgroup, so rule 1 is skipped for
1072// it, but rule 2 (multiple of instData) still applies.
1073//
1074// Example (2D):
1075// wgShape = [128, 64], instData = [8, 16], sgCount = 32
1076// Returns: [[8,4], [16,2]], corresponding to sgData [16,16] and [8,32].
1079 int64_t sgCount, int64_t broadcastDim = -1) {
1080 int64_t rank = wgShape.size();
1081 assert(rank > 0 && "wgShape must be non-empty");
1082 assert(static_cast<int64_t>(instData.size()) == rank &&
1083 "instData rank must match wgShape rank");
1084
1085 // Step 1: Get all N-D factorizations of sgCount.
1086 auto allFactorizations = enumerateFactorizations(sgCount, rank);
1087
1088 // Step 2: Filter to keep only valid candidates.
1090 for (const auto &sgLayout : allFactorizations) {
1091 bool valid = true;
1092 for (int64_t dim = 0; dim < rank; ++dim) {
1093 // A broadcast dim keeps its full extent in every subgroup; others are
1094 // split evenly by sgLayout[dim].
1095 int64_t sgData;
1096 if (dim == broadcastDim) {
1097 sgData = wgShape[dim];
1098 } else {
1099 if (wgShape[dim] % sgLayout[dim] != 0) {
1100 valid = false;
1101 break;
1102 }
1103 sgData = wgShape[dim] / sgLayout[dim];
1104 }
1105 if (sgData % instData[dim] != 0) {
1106 valid = false;
1107 break;
1108 }
1109 }
1110 if (valid)
1111 candidates.push_back(sgLayout);
1112 }
1113
1114 // Step 3: Sort by balance (smallest max-min spread), then lexicographic.
1115 llvm::sort(candidates, [](const LayoutRepresentation &lhs,
1116 const LayoutRepresentation &rhs) {
1117 int64_t spreadLhs = *llvm::max_element(lhs) - *llvm::min_element(lhs);
1118 int64_t spreadRhs = *llvm::max_element(rhs) - *llvm::min_element(rhs);
1119 if (spreadLhs != spreadRhs)
1120 return spreadLhs < spreadRhs;
1121 return lhs < rhs;
1122 });
1123 return candidates;
1124}
1125
1126/// Helper function to compute inst_data vectors for DPAS operands A, B, and
1127/// C/D.
1128static std::optional<SmallVector<int64_t>> get2DBlockIOInstDataLayout(
1129 ArrayRef<int64_t> dataShape, Type elemTy,
1130 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
1131 bool transform = false, bool transpose = false) {
1132 int rank = dataShape.size();
1133 auto blockWHC =
1134 uArchInstruction->getBlockWidthHeightCount(elemTy, transform, transpose);
1135 if (!blockWHC)
1136 return std::nullopt;
1137 auto [bWidths, bHeights, bCounts] = blockWHC.value();
1138 // Compute inst_data from hardware block params. For Nd ops, the lane
1139 // factorization above (laneLayout / laneData) is rigid; inst_data must be
1140 // a multiple of lane_layout * lane_data on each dim (Category A
1141 // invariant).
1142 SmallVector<int64_t> instData(rank, 1);
1143 assert(rank >= 2 && "dataShape must be at least 2D for 2D-block IO");
1144 int instWidth =
1145 xegpu::getLargestDivisor(static_cast<int>(dataShape.back()), bWidths);
1146 int instHeight =
1147 xegpu::getLargestDivisor(static_cast<int>(dataShape[rank - 2]), bHeights);
1148 // No supported hardware block size divides the data dim (e.g. innermost dim
1149 // of 1 vs. minimum block width 16): not realizable as a 2D-block instruction.
1150 if (instWidth < 0 || instHeight < 0)
1151 return std::nullopt;
1152 instData.back() = instWidth;
1153 instData[rank - 2] = instHeight;
1154
1155 return instData;
1156}
1157
1158/// Helper function to compute inst_data vectors for DPAS operands A, B, and
1159/// C/D. Look up the uArch table and search for the largest supported block size
1160/// that divides the data shape
1161static std::optional<std::tuple<SmallVector<int64_t>, SmallVector<int64_t>,
1164 VectorType aTy, VectorType bTy, VectorType cdTy,
1165 const xegpu::uArch::MMAInstructionInterface *uArchInstruction) {
1166
1167 // M dimension is the second-to-last dim of A (handles batch dims).
1168 const unsigned dataALen = aTy.getShape()[aTy.getRank() - 2];
1169 auto supportedALen = uArchInstruction->getSupportedM(aTy.getElementType());
1170 const int maxALen =
1171 xegpu::getLargestDivisor(dataALen, ArrayRef<unsigned>(supportedALen));
1172
1173 // N dimension is the last dim of B.
1174 const unsigned dataBLen = bTy.getShape().back();
1175 auto supportedBLen = uArchInstruction->getSupportedN(bTy.getElementType());
1176 const int maxBLen =
1177 xegpu::getLargestDivisor(dataBLen, ArrayRef<unsigned>(supportedBLen));
1178
1179 auto supportedCLen = uArchInstruction->getSupportedN(cdTy.getElementType());
1180 const int maxCLen =
1181 xegpu::getLargestDivisor(dataBLen, ArrayRef<unsigned>(supportedCLen));
1182 if (maxALen == -1 || maxBLen == -1 || maxCLen == -1)
1183 return std::nullopt;
1184
1185 auto supportedKLen = uArchInstruction->getSupportedK(aTy.getElementType());
1186 if (supportedKLen.empty())
1187 return std::nullopt;
1188 auto kDimSize = supportedKLen[0];
1189
1190 SmallVector<int64_t> instDataA(aTy.getRank(), 1);
1191 instDataA[aTy.getRank() - 2] = maxALen;
1192 instDataA[aTy.getRank() - 1] = kDimSize;
1193 SmallVector<int64_t> instDataB(bTy.getRank(), 1);
1194 instDataB[bTy.getRank() - 2] = kDimSize;
1195 instDataB[bTy.getRank() - 1] = maxBLen;
1196 SmallVector<int64_t> instDataCD(cdTy.getRank(), 1);
1197 instDataCD[cdTy.getRank() - 2] = maxALen;
1198 instDataCD[cdTy.getRank() - 1] = maxCLen;
1199 return std::make_tuple(instDataA, instDataB, instDataCD);
1200}
1201
1202/// Computes lane_layout and lane_data for scatter-style store anchor layouts
1203/// (store scatter, store matrix). Lanes and the per-lane vector both live on
1204/// the innermost dim:
1205/// - laneLayout[innermost] = min(subgroupSize, srcShape[innermost])
1206/// - laneData[innermost] = min(srcShape[innermost] / laneLayout[innermost],
1207/// maxChunkSize)
1208/// All other entries are 1.
1209static std::pair<SmallVector<int64_t>, SmallVector<int64_t>>
1211 int64_t subgroupSize, int64_t maxChunkSize) {
1212 int64_t rank = instShape.size();
1213 SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1);
1214 int64_t innermost = rank - 1;
1215 laneLayout[innermost] = std::min(subgroupSize, instShape[innermost]);
1216 laneData[innermost] =
1217 std::min(instShape[innermost] / laneLayout[innermost], maxChunkSize);
1218 return {laneLayout, laneData};
1219}
1220
1221// Computes the (lane_layout, lane_data, order) a 2D block instruction can
1222// deliver over `instShape`, from the element `bitwidth` and the variant's
1223// `packingSize`. lane_data packs elements narrower than the packing size along
1224// the packing dim: the innermost one, or the one above it when transformed
1225// (VNNI). Lanes are spread along the innermost dim, or the one above it when
1226// transposed, which makes that dim fastest-changing and so returns a reversed
1227// `order`. `order` is empty for the variants that keep the default. Leading
1228// dims are unit.
1229static std::tuple<SmallVector<int64_t>, SmallVector<int64_t>,
1232 int64_t bitwidth, int64_t packingSize,
1233 bool transform = false, bool transpose = false) {
1234 int64_t rank = instShape.size();
1235 assert(rank >= 2 && "Expected at least a 2D shape for a 2D block op");
1236 SmallVector<int64_t> laneLayout(rank, 1), laneData(rank, 1), order;
1237
1238 int64_t packDim = transform ? rank - 2 : rank - 1;
1239 laneData[packDim] = llvm::divideCeil(packingSize, bitwidth);
1240
1241 int64_t laneDim = transpose ? rank - 2 : rank - 1;
1242 laneLayout[laneDim] =
1243 std::min(subgroupSize, instShape[laneDim] / laneData[laneDim]);
1244
1245 if (transpose) {
1246 for (int64_t i = 0; i < rank; ++i)
1247 order.push_back(rank - 1 - i);
1248 std::swap(order[0], order[1]);
1249 }
1250
1251 // assert that the lane layout and data fit in the inst shape
1252 for (int64_t i = 0; i < rank; ++i) {
1253 int64_t laneProduct = laneLayout[i] * laneData[i];
1254 assert(instShape[i] % laneProduct == 0 &&
1255 "lane_layout * lane_data must evenly divide the inst shape");
1256 (void)laneProduct;
1257 }
1258 return {laneLayout, laneData, order};
1259}
1260
1261/// Computes a multi_reduction's source layout as (lane_layout, lane_data,
1262/// inst_data), where inst_data = lane_layout * lane_data on every dim.
1263///
1264/// `maxReduceVectorSize` is the per-lane vector budget.
1265///
1266/// 1. Take lane_layout and lane_data from the consumer. A consumer sliced over
1267/// exactly `reductionDims` is a view of a source-rank parent, and that
1268/// parent is reused as-is. Any other consumer has the result's rank: its
1269/// lane_layout and lane_data hold one element per surviving dim, taken in
1270/// order. Reduced dims keep lane_layout 1 for now.
1271///
1272/// 2. Limit lane_layout by `srcShape`: a dim can spread over no more lanes than
1273/// it has elements to give.
1274///
1275/// Then hand any unused lanes to the innermost dim, if that dim is reduced.
1276/// This is a heuristic: the innermost dim of the source typically already
1277/// carries several lanes, whether the source is loaded from memory or is a
1278/// dpas result, so putting them there matches what the producer did.
1279///
1280/// 3. Raise lane_data on the innermost dim, so the lane holds a run of elements
1281/// along it and reduces the run with one vector op instead of one element at
1282/// a time. The run is at most `maxReduceVectorSize` long. The dim must also
1283/// be reduced and have lane_layout 1.
1284///
1285/// How much: `maxReduceVectorSize / product(lane_data)`, multiplied onto the
1286/// dim's existing lane_data and capped by its extent.
1287///
1288/// e.g. src=[16,32,32] reduce [2], consumer lane=[1,2] ->
1289/// lane_layout=[1,2,8], lane_data=[1,1,1], inst_data=[1,2,8].
1290/// e.g. src=[16,16] reduce [1], consumer lane=[16] ->
1291/// lane_layout=[16,1], lane_data=[1,16], inst_data=[16,16].
1292static std::tuple<SmallVector<int64_t>, SmallVector<int64_t>,
1295 ArrayRef<int64_t> srcShape, ArrayRef<int64_t> reductionDims,
1296 int subgroupSize, int64_t maxReduceVectorSize,
1297 xegpu::DistributeLayoutAttr consumerLayout = nullptr) {
1298 int srcRank = srcShape.size();
1299 SmallVector<int64_t> laneLayout(srcRank, 1), laneData(srcRank, 1);
1300 auto isReduced = [&](int dim) {
1301 return llvm::is_contained(reductionDims, dim);
1302 };
1303
1304 // Rule 1. Only a parent that covers the source rank and spends the whole
1305 // subgroup is trusted; one spending less distributes less than the producer
1306 // already does.
1307 auto consumerSlice = dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
1308 bool reusedParent = false;
1309 if (consumerSlice &&
1310 consumerSlice.getDims().asArrayRef().equals(reductionDims)) {
1311 auto parent = consumerSlice.getParent();
1312 SmallVector<int64_t> parentLaneLayout =
1313 parent.getEffectiveLaneLayoutAsInt();
1314 SmallVector<int64_t> parentLaneData = parent.getEffectiveLaneDataAsInt();
1315 if (static_cast<int>(parentLaneLayout.size()) == srcRank &&
1316 static_cast<int>(parentLaneData.size()) == srcRank &&
1317 computeProduct(parentLaneLayout) == subgroupSize) {
1318 laneLayout = std::move(parentLaneLayout);
1319 laneData = std::move(parentLaneData);
1320 reusedParent = true;
1321 }
1322 }
1323
1324 if (!reusedParent) {
1325 SmallVector<int64_t> consumerLaneLayout, consumerLaneData;
1326 if (consumerLayout) {
1327 consumerLaneLayout = consumerLayout.getEffectiveLaneLayoutAsInt();
1328 consumerLaneData = consumerLayout.getEffectiveLaneDataAsInt();
1329 }
1330 for (int i = 0, c = 0; i < srcRank; ++i) {
1331 if (isReduced(i))
1332 continue;
1333 if (c < static_cast<int>(consumerLaneLayout.size()))
1334 laneLayout[i] = consumerLaneLayout[c];
1335 if (c < static_cast<int>(consumerLaneData.size()))
1336 laneData[i] = consumerLaneData[c];
1337 ++c;
1338 }
1339 }
1340
1341 // Rule 2, applied to a reused parent as much as a derived layout.
1342 SmallVector<int64_t> maxLanes(srcRank);
1343 for (int i = 0; i < srcRank; ++i) {
1344 maxLanes[i] = std::max<int64_t>(1, srcShape[i] / laneData[i]);
1345 laneLayout[i] = std::min(laneLayout[i], maxLanes[i]);
1346 }
1347
1348 // Lanes the consumer never claimed, plus any the bound above freed, go to the
1349 // innermost dim -- and only when it is reduced.
1350 // TODO: spend these lanes on an outer reduced dim once that is lowerable --
1351 // they sit idle today. Widening it to srcRank-2 breaks
1352 // reduction_2D_scalar_cross_sg_intra_lane.
1353 int innermost = srcRank - 1;
1354 int64_t spareLanes =
1355 std::max<int64_t>(1, subgroupSize / computeProduct(laneLayout));
1356 if (spareLanes > 1 && isReduced(innermost))
1357 laneLayout[innermost] =
1358 std::min(laneLayout[innermost] * spareLanes, maxLanes[innermost]);
1359
1360 // Rule 3. What lane_data already packs comes off the budget first.
1361 int64_t vectorBudget =
1362 maxReduceVectorSize /
1363 std::min(computeProduct(laneData), maxReduceVectorSize);
1364
1365 bool canCarryVector = laneLayout[innermost] == 1 && isReduced(innermost) &&
1366 srcShape[innermost] > 1;
1367
1368 if (vectorBudget > 1 && canCarryVector)
1369 laneData[innermost] =
1370 std::min(vectorBudget * laneData[innermost], srcShape[innermost]);
1371
1372 SmallVector<int64_t> instData(srcRank);
1373 for (int i = 0; i < srcRank; ++i)
1374 instData[i] = laneLayout[i] * laneData[i];
1375
1376 return {laneLayout, laneData, instData};
1377}
1378
1379//===----------------------------------------------------------------------===//
1380// Result/anchor-layout setup. Each op category derives lane_layout/lane_data
1381// (and inst_data / sgData) differently. Two things vary across ops:
1382//
1383// * Consumer dependence: consumer-driven ops prefer the layout requested by
1384// their downstream uses and fall back to uArch defaults only when it is
1385// absent/invalid; sinks (StoreNd, PrefetchNd) have no consumer and always
1386// pick their own layout from uArch.
1387//
1388// * Derivation direction between inst_data and lane_layout/lane_data. Both
1389// obey the invariant inst_data = k * lane_layout * lane_data, where `k` is
1390// a per-dim integer >= 1 giving how many times each lane repeats its
1391// access to cover one instruction's data tile (k == 1 means one lane
1392// position per element; k > 1 means the instruction loads/stores several
1393// elements per lane along that dim). Ops solve this invariant from
1394// opposite ends:
1395// - Rigid-lane ops (Nd block IO, DPAS): hardware fixes lane_layout /
1396// lane_data first, then inst_data is built as a multiple of their
1397// product (using get2DBlockIOInstDataLayout / getDpasInstDataLayouts).
1398// - inst_data-first ops (scatter load): take inst_data from the consumer
1399// and derive lane_layout/lane_data underneath it.
1400//
1401// - DPAS (+DPAS_MX) : rigid lanes — inst_data from HW block dims; A/B/C/D
1402// lanes/data follow each operand's matmul role; DPAS_MX
1403// additionally lays out the scale operand.
1404// - LoadNd : consumer-driven, rigid lanes — honors the consumer's
1405// inst_data / lane / sg_layout (incl. transpose & VNNI
1406// packing) when it satisfies uArch block constraints,
1407// else falls back to the default 2D-block scheme (lanes
1408// on the last dim, rank-2 if transposed). The fallback
1409// picks the LARGEST uArch block that divides the data
1410// shape, so the resulting inst_data block can be bigger
1411// than what the consumer asked for (fewer, wider
1412// loads).
1413// - StoreNd/PrefetchNd: data sinks, no consumer, rigid lanes — pick the
1414// 2D-block layout directly from uArch (no VNNI
1415// packing).
1416// - Load (scatter) : load_gather / load_matrix, consumer-driven,
1417// inst_data-first — reuse the consumer's inst_data and
1418// derive lane_layout/lane_data, else default to lanes +
1419// per-lane chunk on the innermost dim (chunk capped by
1420// maxChunkSize).
1421// - Store (scatter) : store_scatter / store_matrix — same scatter scheme,
1422// but always self-derived from the scatter default.
1423// - Reduction : (multi_)reduction, consumer-driven — distribute the
1424// inner two dims, with lanes on the innermost dim by
1425// default (reducing across lanes) and switched to a
1426// non-reduction dim only when that keeps the reduction
1427// within a lane. Reuses the consumer's slice layout
1428// when it slices exactly the reduction dims, otherwise
1429// re-derives. See setupMultiReductionResultLayout for
1430// the exact switch condition and worked examples.
1431// - BitCast/Interleave: scale the innermost data field by the bitwidth /
1432// interleave ratio so the source layout divides back
1433// out.
1434// - InsertStridedSlice: clamp lane_data per dim to fit the inserted slice
1435// (Lane kind only; sg/inst layouts unsupported).
1436//===----------------------------------------------------------------------===//
1437
1438/// Helper function to set up subgroup layouts for DPAS operands A, B, and
1439/// C/D. Compute subgroup layout candidates based on wgtile and instData, and
1440/// then pick the best one that satisfies all operands and the consumer (if
1441/// specified).
1442static std::optional<
1443 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1444 xegpu::DistributeLayoutAttr>>
1446 mlir::MLIRContext *context, VectorType aTy, VectorType bTy, VectorType cdTy,
1447 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1449 instDataVecs) {
1450 auto [instDataA, instDataB, instDataCD] = instDataVecs;
1451
1452 std::optional<LayoutRepresentation> consumerSgLayout = std::nullopt;
1453 if (consumerLayout && consumerLayout.isForWorkgroup()) {
1454 consumerSgLayout = consumerLayout.getEffectiveSgLayoutAsInt();
1455 }
1456
1457 // Get all valid layouts for A, B and C/D operands
1458 auto layoutsA = getSgLayoutCandidates(aTy.getShape(), instDataA, numSg,
1459 /*broadcastDim=*/aTy.getRank() - 1);
1460 auto layoutsB = getSgLayoutCandidates(bTy.getShape(), instDataB, numSg,
1461 /*broadcastDim=*/bTy.getRank() - 2);
1462 auto layoutsCD = getSgLayoutCandidates(cdTy.getShape(), instDataCD, numSg);
1463 if (layoutsA.empty() || layoutsB.empty() || layoutsCD.empty())
1464 return std::nullopt;
1465
1466 // Pick the best subgroup layout
1467 std::optional<LayoutRepresentation> bestPick;
1468 for (auto &sgLayout : layoutsB) {
1469 if (llvm::is_contained(layoutsA, sgLayout) &&
1470 llvm::is_contained(layoutsCD, sgLayout)) {
1471 // Is in (A and B and CD) and matches consumer -> best pick
1472 if (consumerSgLayout.has_value() && sgLayout == *consumerSgLayout) {
1473 bestPick = sgLayout;
1474 break;
1475 }
1476 // Is in (A and B and CD) layoutsB is ordered from most
1477 // balanced to least. So the first one we see is the most balanced one,
1478 // remember it and later only update if there is one that matches the
1479 // consumer.
1480 if (!bestPick)
1481 bestPick = sgLayout;
1482 }
1483 }
1484 if (!bestPick)
1485 return std::nullopt;
1486
1487 const auto &picked = *bestPick;
1488
1489 auto dpasALayout = buildSgLayout(context, aTy.getShape(), picked,
1490 /*dimK=*/aTy.getRank() - 1);
1491 auto dpasBLayout = buildSgLayout(context, bTy.getShape(), picked,
1492 /*dimK=*/bTy.getRank() - 2);
1493 auto dpasCDLayout = buildSgLayout(context, cdTy.getShape(), picked);
1494 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout);
1495}
1496
1497/// Sets up the anchor layouts for dpas operands (A, B, and C/D).
1498/// The numSg and consumerLayout (optional) are only used by sg layout
1499/// creation.
1500std::optional<
1501 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1502 xegpu::DistributeLayoutAttr>>
1503xegpu::setupDpasLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
1504 VectorType bTy, VectorType cdTy,
1505 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1506 const xegpu::uArch::uArch *uArch) {
1507 auto context = aTy.getContext();
1508 const auto *uArchInstruction =
1509 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
1511 if (!uArchInstruction)
1512 return std::nullopt;
1513 auto subgroupSize = uArch->getSubgroupSize();
1514
1515 auto [laneLayoutA, laneDataA, orderA] =
1516 compute2DBlockIOLaneLayout(aTy.getShape(), subgroupSize,
1517 aTy.getElementType().getIntOrFloatBitWidth(),
1518 uArchInstruction->getPackedFormatBitSizeA());
1519 auto [laneLayoutB, laneDataB, orderB] = compute2DBlockIOLaneLayout(
1520 bTy.getShape(), subgroupSize,
1521 bTy.getElementType().getIntOrFloatBitWidth(),
1522 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
1523 auto [laneLayoutCD, laneDataCD, orderCD] =
1524 compute2DBlockIOLaneLayout(cdTy.getShape(), subgroupSize,
1525 cdTy.getElementType().getIntOrFloatBitWidth(),
1526 cdTy.getElementType().getIntOrFloatBitWidth());
1527
1528 auto instDataVecs = getDpasInstDataLayouts(aTy, bTy, cdTy, uArchInstruction);
1529 if (!instDataVecs)
1530 return std::nullopt;
1531
1532 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1533 assert(numSg > 0 &&
1534 "Number of subgroups must be provided for sg layout creation.");
1535 return getDpasSubgroupLayouts(context, aTy, bTy, cdTy, consumerLayout,
1536 numSg, *instDataVecs);
1537 } else if (layoutKind == xegpu::LayoutKind::InstData) {
1538 auto [instDataA, instDataB, instDataCD] = *instDataVecs;
1539 return std::make_tuple(
1540 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA),
1541 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB),
1542 buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD,
1543 laneDataCD));
1544 } else if (layoutKind == xegpu::LayoutKind::Lane) {
1545 auto aLayout = buildLaneLayout(context, laneLayoutA, laneDataA);
1546 auto bLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
1547 auto cdLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
1548 return std::make_tuple(aLayout, bLayout, cdLayout);
1549 }
1550 return std::nullopt;
1551}
1552
1553/// Helper to create a scale layout derived from a matrix operand layout.
1554/// The scale layout is computed by mapping each dimension of the matrix
1555/// layout to the corresponding scale tensor dimension using the ratio
1556/// between the matrix and scale shapes.
1557static xegpu::DistributeLayoutAttr
1558createScaleLayout(mlir::MLIRContext *context, VectorType matrixTy,
1559 VectorType scaleTy, xegpu::DistributeLayoutAttr matrixLayout,
1560 bool isBScale, const xegpu::uArch::uArch *uArch) {
1561 if (!scaleTy || !matrixLayout)
1562 return nullptr;
1563
1564 // Calculate scaling factor by dividing matrix shape by scale shape
1565 ArrayRef<int64_t> matrixShape = matrixTy.getShape();
1566 ArrayRef<int64_t> scaleShape = scaleTy.getShape();
1567
1568 // Scale shapes can be 1D or 2D, handle both cases
1569 if (scaleShape.empty())
1570 return nullptr;
1571
1572 auto uArchInstruction =
1573 dyn_cast<xegpu::uArch::SubgroupScaledMatrixMultiplyAcc>(
1574 uArch->getInstruction(
1576
1577 int64_t rank = matrixLayout.getRank();
1578 assert(rank >= 2 && "dpas layouts must be at least two dimensions");
1579
1580 SmallVector<int64_t> sgLayout = matrixLayout.getEffectiveSgLayoutAsInt();
1581 SmallVector<int64_t> sgData = matrixLayout.getEffectiveSgDataAsInt();
1582 SmallVector<int64_t> instData = matrixLayout.getEffectiveInstDataAsInt();
1583 SmallVector<int64_t> laneLayout = matrixLayout.getEffectiveLaneLayoutAsInt();
1584 SmallVector<int64_t> laneData = matrixLayout.getEffectiveLaneDataAsInt();
1585 auto order = matrixLayout.getOrder();
1586
1587 SmallVector<int64_t> scaleSgLayout;
1588 SmallVector<int64_t> scaleSgData;
1589 if (!sgLayout.empty() && !sgData.empty()) {
1590 scaleSgLayout.assign(sgLayout.begin(), sgLayout.end());
1591 scaleSgData.assign(sgData.begin(), sgData.end());
1592 scaleSgData[rank - 2] = std::max<int64_t>(
1593 scaleShape[rank - 2] / (matrixShape[rank - 2] / sgData[rank - 2]), 1);
1594 scaleSgData[rank - 1] = std::max<int64_t>(
1595 scaleShape[rank - 1] / (matrixShape[rank - 1] / sgData[rank - 1]), 1);
1596 }
1597
1598 // For DPAS_MX scales: if matrix has inst_data, scale needs adjusted
1599 // inst_data. Scale inst_data is derived from matrix inst_data divided by
1600 // scale factor.
1601 SmallVector<int64_t> scaleInstData;
1602 if (!instData.empty()) {
1603 scaleInstData.assign(instData.begin(), instData.end());
1604 if (isBScale)
1605 scaleInstData[rank - 2] = std::max<int64_t>(
1606 scaleShape[rank - 2] / (matrixShape[rank - 2] / instData[rank - 2]),
1607 1);
1608 else
1609 scaleInstData[rank - 1] = std::max<int64_t>(
1610 scaleShape[rank - 1] / (matrixShape[rank - 1] / instData[rank - 1]),
1611 1);
1612 }
1613
1614 SmallVector<int64_t> scaleLaneLayout;
1615 SmallVector<int64_t> scaleLaneData;
1616 if (!laneLayout.empty() && !laneData.empty()) {
1617 scaleLaneLayout.assign(laneLayout.begin(), laneLayout.end());
1618 scaleLaneData.assign(laneData.size(), 1);
1619
1620 bool isRowMajor = uArchInstruction->isLaneLayoutRowMajorOrder();
1621 if (isBScale ^ isRowMajor)
1622 std::swap(scaleLaneLayout[rank - 2], scaleLaneLayout[rank - 1]);
1623 // Cap lane_layout by the per-instruction tile (inst_data) on each dim.
1624 // Then derive lane_data = inst_data / lane_layout so the Category A
1625 // invariant inst_data = lane_layout * lane_data * k (with k = 1) holds
1626 // for the scale operand's load_nd consumer.
1627 auto layoutCap = scaleInstData.empty() ? scaleShape : scaleInstData;
1628 for (int64_t d = rank - 2; d < rank; ++d)
1629 scaleLaneLayout[d] = std::min<int64_t>(layoutCap[d], scaleLaneLayout[d]);
1630 }
1631 return buildLayout(context, scaleSgLayout, scaleSgData, scaleInstData,
1632 scaleLaneLayout, scaleLaneData, order);
1633}
1634
1635/// Sets up the anchor layouts for dpas_mx operands (A, B, C/D, A_scale, and
1636/// B_scale). The numSg and consumerLayout (optional) are only used by sg
1637/// layout creation.
1638std::optional<
1639 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1640 xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
1641 xegpu::DistributeLayoutAttr>>
1642xegpu::setupDpasMxLayout(xegpu::LayoutKind layoutKind, VectorType aTy,
1643 VectorType bTy, VectorType cdTy, VectorType aScaleTy,
1644 VectorType bScaleTy,
1645 xegpu::DistributeLayoutAttr consumerLayout, int numSg,
1646 const xegpu::uArch::uArch *uArch) {
1647 auto context = aTy.getContext();
1648 const auto *uArchInstruction =
1649 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
1651 if (!uArchInstruction)
1652 return std::nullopt;
1653 auto subgroupSize = uArch->getSubgroupSize();
1654
1655 auto [laneLayoutA, laneDataA, orderA] =
1656 compute2DBlockIOLaneLayout(aTy.getShape(), subgroupSize,
1657 aTy.getElementType().getIntOrFloatBitWidth(),
1658 uArchInstruction->getPackedFormatBitSizeA());
1659 auto [laneLayoutB, laneDataB, orderB] = compute2DBlockIOLaneLayout(
1660 bTy.getShape(), subgroupSize,
1661 bTy.getElementType().getIntOrFloatBitWidth(),
1662 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
1663 auto [laneLayoutCD, laneDataCD, orderCD] =
1664 compute2DBlockIOLaneLayout(cdTy.getShape(), subgroupSize,
1665 cdTy.getElementType().getIntOrFloatBitWidth(),
1666 cdTy.getElementType().getIntOrFloatBitWidth());
1667 auto instDataVecs = getDpasInstDataLayouts(aTy, bTy, cdTy, uArchInstruction);
1668 if (!instDataVecs)
1669 return std::nullopt;
1670
1671 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1672 assert(numSg > 0 &&
1673 "Number of subgroups must be provided for sg layout creation.");
1674 auto dpasLayouts = getDpasSubgroupLayouts(
1675 context, aTy, bTy, cdTy, consumerLayout, numSg, *instDataVecs);
1676 if (!dpasLayouts)
1677 return std::nullopt;
1678
1679 auto [dpasALayout, dpasBLayout, dpasCDLayout] = *dpasLayouts;
1680
1681 // Create scale layouts
1682 auto aScaleLayout =
1683 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1684
1685 auto bScaleLayout =
1686 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1687
1688 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1689 bScaleLayout);
1690 } else if (layoutKind == xegpu::LayoutKind::InstData) {
1691
1692 auto [instDataA, instDataB, instDataCD] = *instDataVecs;
1693
1694 auto dpasALayout =
1695 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA);
1696 auto dpasBLayout =
1697 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB);
1698 auto dpasCDLayout = buildInstDataLayoutWithLane(context, instDataCD,
1699 laneLayoutCD, laneDataCD);
1700
1701 auto aScaleLayout =
1702 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1703 auto bScaleLayout =
1704 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1705
1706 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1707 bScaleLayout);
1708 } else if (layoutKind == xegpu::LayoutKind::Lane) {
1709 auto dpasALayout = buildLaneLayout(context, laneLayoutA, laneDataA);
1710 auto dpasBLayout = buildLaneLayout(context, laneLayoutB, laneDataB);
1711 auto dpasCDLayout = buildLaneLayout(context, laneLayoutCD, laneDataCD);
1712
1713 auto aScaleLayout =
1714 createScaleLayout(context, aTy, aScaleTy, dpasALayout, false, uArch);
1715 auto bScaleLayout =
1716 createScaleLayout(context, bTy, bScaleTy, dpasBLayout, true, uArch);
1717
1718 return std::make_tuple(dpasALayout, dpasBLayout, dpasCDLayout, aScaleLayout,
1719 bScaleLayout);
1720 }
1721 return std::nullopt;
1722}
1723
1724/// Sets up the anchor layout for a store_nd operation. StoreNd picks its
1725/// own layout based on uArch block parameters (it does not take a consumer
1726/// layout, since it is a data sink).
1727xegpu::DistributeLayoutAttr
1729 VectorType srcVecTy, int numSg,
1730 const xegpu::uArch::uArch *uArch) {
1731 const auto *uArchInstruction =
1732 dyn_cast<xegpu::uArch::Subgroup2DBlockStoreInstruction>(
1733 uArch->getInstruction(
1735 if (!uArchInstruction)
1736 return nullptr;
1737
1738 auto context = srcVecTy.getContext();
1739 Type elemTy = srcVecTy.getElementType();
1740 auto subgroupSize = uArch->getSubgroupSize();
1741 auto dataShape = srcVecTy.getShape();
1742 [[maybe_unused]] int rank = srcVecTy.getRank();
1743 assert(rank >= 2 && "Expected at least 2D shape for ND op");
1744
1745 // Compute the default 2D block IO lane layout / lane data.
1746 unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
1747 auto [laneLayout, laneData, order] =
1748 compute2DBlockIOLaneLayout(dataShape, subgroupSize, bitwidth,
1749 uArchInstruction->getPackedFormatBitSize());
1750
1751 if (layoutKind == xegpu::LayoutKind::Lane)
1752 return buildLaneLayout(context, laneLayout, laneData);
1753
1754 auto instData =
1755 get2DBlockIOInstDataLayout(dataShape, elemTy, uArchInstruction);
1756 // Shape not realizable as a 2D-block instruction; let the caller report it.
1757 if (!instData)
1758 return nullptr;
1759
1760 if (layoutKind == xegpu::LayoutKind::InstData) {
1761 assert(isValidLaneLayout(*instData, laneLayout, laneData) &&
1762 "Expected the store layout to satisfy uArch block constraints");
1763 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1764 laneData);
1765 }
1766
1767 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1768 assert(numSg > 0 &&
1769 "Number of subgroups must be provided for sg layout creation.");
1770 auto sgLayouts = getSgLayoutCandidates(dataShape, *instData, numSg);
1771 if (sgLayouts.empty())
1772 return nullptr;
1773 return buildSgLayout(context, dataShape, sgLayouts.front(), /*dimK=*/-1);
1774 }
1775
1776 return nullptr;
1777}
1778
1779/// Sets up the anchor layout for a prefetch_nd operation. PrefetchNd has no
1780/// consumer (it produces no value), so it picks its own layout from uArch
1781/// block parameters.
1782xegpu::DistributeLayoutAttr
1784 xegpu::TensorDescType tdescTy, int numSg,
1785 const xegpu::uArch::uArch *uArch) {
1786
1787 const auto *uArchInstruction =
1788 dyn_cast<xegpu::uArch::Subgroup2DBlockPrefetchInstruction>(
1789 uArch->getInstruction(
1791 if (!uArchInstruction)
1792 return nullptr;
1793
1794 auto context = tdescTy.getContext();
1795 Type elemTy = tdescTy.getElementType();
1796 auto subgroupSize = uArch->getSubgroupSize();
1797 auto dataShape = tdescTy.getShape();
1798 [[maybe_unused]] int rank = tdescTy.getRank();
1799 assert(rank >= 2 && "Expected at least 2D shape for ND op");
1800
1801 // Compute the default 2D block IO lane layout / lane data.
1802 unsigned bitwidth = elemTy.getIntOrFloatBitWidth();
1803 auto [laneLayout, laneData, order] =
1804 compute2DBlockIOLaneLayout(dataShape, subgroupSize, bitwidth,
1805 uArchInstruction->getPackedFormatBitSize());
1806
1807 if (layoutKind == xegpu::LayoutKind::Lane)
1808 return buildLaneLayout(context, laneLayout, laneData);
1809
1810 auto instData =
1811 get2DBlockIOInstDataLayout(dataShape, elemTy, uArchInstruction);
1812 // Shape not realizable as a 2D-block instruction; let the caller report it.
1813 if (!instData)
1814 return nullptr;
1815
1816 if (layoutKind == xegpu::LayoutKind::InstData) {
1817 assert(isValidLaneLayout(*instData, laneLayout, laneData) &&
1818 "Expected the prefetch layout to satisfy uArch block constraints");
1819 return buildInstDataLayoutWithLane(context, *instData, laneLayout,
1820 laneData);
1821 }
1822
1823 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1824 assert(numSg > 0 &&
1825 "Number of subgroups must be provided for sg layout creation.");
1826 auto sgLayouts = getSgLayoutCandidates(dataShape, *instData, numSg);
1827 if (sgLayouts.empty())
1828 return nullptr;
1829 return buildSgLayout(context, dataShape, sgLayouts.front(), /*dimK=*/-1);
1830 }
1831
1832 return nullptr;
1833}
1834
1835/// Sets up the anchor layout for a load_nd operation. LoadNd takes a
1836/// consumer layout (from its result's downstream uses) and validates it
1837/// against uArch constraints; if valid, the consumer's `inst_data` /
1838/// `sg_layout` are honored. Otherwise the helper falls back to defaults
1839/// derived from uArch block parameters.
1840xegpu::DistributeLayoutAttr
1842 VectorType resVecTy,
1843 xegpu::DistributeLayoutAttr consumerLayout,
1844 int numSg, const xegpu::uArch::uArch *uArch) {
1845
1846 assert(consumerLayout && "Expected a valid consumer layout");
1847 if (layoutKind == xegpu::LayoutKind::Subgroup) {
1848 assert(consumerLayout.isForWorkgroup() &&
1849 "Expected consumer layout to be a complete workgroup-level layout");
1850 return consumerLayout;
1851 }
1852
1853 auto context = resVecTy.getContext();
1854 Type elemTy = resVecTy.getElementType();
1855 auto subgroupSize = uArch->getSubgroupSize();
1856 auto dataShape = resVecTy.getShape();
1857 const auto *uArchInstruction =
1858 dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
1859 uArch->getInstruction(
1861 if (!uArchInstruction)
1862 return nullptr;
1863
1864 int rank = resVecTy.getRank();
1865 SmallVector<int64_t> consumerInstData =
1866 consumerLayout.getEffectiveInstDataAsInt();
1867 SmallVector<int64_t> consumerLaneLayout =
1868 consumerLayout.getEffectiveLaneLayoutAsInt();
1869 SmallVector<int64_t> consumerLaneData =
1870 consumerLayout.getEffectiveLaneDataAsInt();
1871
1872 assert(!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
1873 "Expected consumer layout to have lane_layout and lane_data");
1874
1875 // vertical lane layout means that the blockload must be transposed
1876 // note scaleA on PVC has vertical lane layout even without transposed order
1877 // attr
1878 bool hasTranspose =
1879 consumerLaneLayout[rank - 2] > 1 && consumerLaneLayout[rank - 1] == 1;
1880 // VNNI exists only below 32 bits.
1881 bool canTransform =
1882 elemTy.isIntOrFloat() &&
1884 bool hasTransform = !hasTranspose && canTransform &&
1885 consumerLaneData[rank - 2] > 1 &&
1886 consumerLaneData[rank - 1] == 1;
1887 assert((consumerLaneData[rank - 2] == 1 || consumerLaneData[rank - 1] == 1) &&
1888 "Expected consumer lane data to have at most one non-unit dim");
1889
1890 if (layoutKind == xegpu::LayoutKind::InstData) {
1891 auto blockWHC = uArchInstruction->getBlockWidthHeightCount(
1892 elemTy, hasTransform, hasTranspose,
1893 /*upConv=*/false);
1894 if (!blockWHC)
1895 return nullptr;
1896 auto [bWidths, bHeights, bCounts] = blockWHC.value();
1897
1898 // lane_layout and lane_data are the block load's own, not the consumer's: a
1899 // consumer may ask for more elements per lane than the hardware can pack,
1900 // e.g. an f32 multi_reduction wanting 16.
1901 unsigned packingSize = hasTransform || hasTranspose
1903 : uArchInstruction->getPackedFormatBitSize();
1904 auto [laneLayout, laneData, order] = compute2DBlockIOLaneLayout(
1905 dataShape, subgroupSize, elemTy.getIntOrFloatBitWidth(), packingSize,
1906 hasTransform, hasTranspose);
1907 DenseI32ArrayAttr orderAttr =
1908 order.empty()
1909 ? nullptr
1911 context, SmallVector<int32_t>(order.begin(), order.end()));
1912
1913 // See whether the consumer's inst_data satisfies the block constraints.
1914 int64_t height = consumerInstData[rank - 2];
1915 int64_t width = consumerInstData[rank - 1];
1916 auto maxBlockCount = *llvm::max_element(bCounts);
1917 auto maxWidth = *llvm::max_element(bWidths);
1918 if (llvm::is_contained(bWidths, static_cast<int>(width)) ||
1919 (width % maxWidth == 0 && width / maxWidth < maxBlockCount)) {
1920 if (llvm::is_contained(bHeights, static_cast<int>(height))) {
1921 assert(isValidLaneLayout(consumerInstData, laneLayout, laneData) &&
1922 "Expected the load layout to satisfy uArch block constraints");
1923 return buildInstDataLayoutWithLane(context, consumerInstData,
1924 laneLayout, laneData, orderAttr);
1925 }
1926 }
1927
1928 // The consumer's inst_data is not a usable block, so take the uArch's.
1929 // DPAS_MX scales land here. A scale whose innermost dim is too narrow to
1930 // spread packed lanes over (width 16 for an 8-bit scale needing 16 lanes of
1931 // 2 elements) cannot be loaded regularly; transformed, it packs down the
1932 // column instead. A vertical lane_layout is already transposed.
1933 if (!hasTranspose && !hasTransform &&
1934 consumerInstData[rank - 1] %
1935 (laneLayout[rank - 1] * laneData[rank - 1]) !=
1936 0) {
1937 hasTransform = true;
1938 std::tie(laneLayout, laneData, order) = compute2DBlockIOLaneLayout(
1939 dataShape, subgroupSize, elemTy.getIntOrFloatBitWidth(),
1940 uArch->getGeneralPackedFormatBitSize(), hasTransform, hasTranspose);
1941 }
1942
1943 auto instData = get2DBlockIOInstDataLayout(
1944 dataShape, elemTy, uArchInstruction, hasTransform, hasTranspose);
1945 // Shape not realizable as a 2D-block instruction; let the caller report it.
1946 if (!instData)
1947 return nullptr;
1948 assert(isValidLaneLayout(*instData, laneLayout, laneData) &&
1949 "Expected the load layout to satisfy uArch block constraints");
1950 return buildInstDataLayoutWithLane(context, *instData, laneLayout, laneData,
1951 orderAttr);
1952 }
1953 if (layoutKind == xegpu::LayoutKind::Lane) {
1954 assert(isValidLaneLayout(dataShape, consumerLaneLayout, consumerLaneData) &&
1955 "Expected the lane layout to satisfy uArch block constraints");
1956 return consumerLayout;
1957 }
1958 return nullptr;
1959}
1960
1961/// Sets up the anchor layout for load gather and load matrix operation.
1962/// load matrix lowers to load gather and 1d block load. All of them share the
1963/// same layout setup logic.
1964///
1965/// For Subgroup layout, uses the consumer layout directly.
1966///
1967/// For InstData layout, takes consumer's inst_data as-is; lane_layout and
1968/// lane_data are taken from the consumer.
1969///
1970/// For Lane layout, lane_layout/lane_data are taken from the consumer.
1971///
1972/// A consumer layout that carries lane_layout and lane_data is required.
1973/// `maxChunkSize` is not read yet: the consumer's lane_data already fixes the
1974/// per-lane chunk.
1975///
1976/// TODO: derive lane_layout/lane_data here when the consumer has none, capped
1977/// by `maxChunkSize`, the way `setupGenericStoreAnchorLayout` does via
1978/// `computeScatterIOLaneLayoutAndData`. That path is missing today, so the
1979/// assert below stands in for it.
1980static xegpu::DistributeLayoutAttr setupGenericLoadAnchorLayout(
1981 xegpu::LayoutKind layoutKind, mlir::MLIRContext *context,
1982 xegpu::DistributeLayoutAttr consumerLayout, int maxChunkSize,
1983 ArrayRef<int64_t> resShape, int subgroupSize) {
1984
1985 if (layoutKind == xegpu::LayoutKind::Subgroup)
1986 return consumerLayout;
1987
1988 SmallVector<int64_t> consumerInstData =
1989 consumerLayout.getEffectiveInstDataAsInt();
1990 SmallVector<int64_t> consumerLaneLayout =
1991 consumerLayout.getEffectiveLaneLayoutAsInt();
1992 SmallVector<int64_t> consumerLaneData =
1993 consumerLayout.getEffectiveLaneDataAsInt();
1994
1995 assert(!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
1996 "Expected consumer layout to have lane_layout and lane_data");
1997 SmallVector<int64_t> laneLayout(consumerLaneLayout);
1998
1999 // A matrix/gather load reads one contiguous span per lane, so a lane can pack
2000 // only along the innermost dim, and only up to maxChunkSize.
2001 SmallVector<int64_t> laneData(consumerLaneData.size(), 1);
2002 laneData.back() =
2003 std::min(consumerLaneData.back(), static_cast<int64_t>(maxChunkSize));
2004
2005 if (layoutKind == xegpu::LayoutKind::InstData) {
2006 SmallVector<int64_t> instData;
2007 instData.resize(resShape.size());
2008 for (size_t i = 0; i < resShape.size(); ++i)
2009 instData[i] = laneLayout[i] * laneData[i];
2010 return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
2011 }
2012 if (layoutKind == xegpu::LayoutKind::Lane)
2013 return buildLaneLayout(context, laneLayout, laneData);
2014 return nullptr;
2015}
2016
2017/// Sets up the anchor layout for a load gather operation.
2018xegpu::DistributeLayoutAttr xegpu::setupLoadGatherAnchorLayout(
2019 xegpu::LayoutKind layoutKind, VectorType resVecTy, int contigChunkSize,
2020 xegpu::DistributeLayoutAttr consumerLayout, const uArch::uArch *uArch) {
2021
2022 const int subgroupSize = uArch->getSubgroupSize();
2023 ArrayRef<int64_t> resShape = resVecTy.getShape();
2024 auto context = resVecTy.getContext();
2025
2026 // The per-lane chunk is bounded by what the offsets prove contiguous
2027 // (`contigChunkSize`) and by what one lane can access in one instruction.
2028 const auto *uArchInstruction = dyn_cast<xegpu::uArch::LoadGatherInstruction>(
2029 uArch->getInstruction(xegpu::uArch::InstructionKind::LoadGather));
2030 int maxChunkSize =
2031 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
2032
2033 return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
2034 maxChunkSize, resShape, subgroupSize);
2035}
2036
2037/// Sets up the anchor layout for load matrix operation.
2038/// TODO: enhance load matrix to indicate lowering to chunked load or not.
2039xegpu::DistributeLayoutAttr
2041 VectorType resVecTy, int contigChunkSize,
2042 xegpu::DistributeLayoutAttr consumerLayout,
2043 const xegpu::uArch::uArch *uArch) {
2044
2045 const int subgroupSize = uArch->getSubgroupSize();
2046 ArrayRef<int64_t> resShape = resVecTy.getShape();
2047 auto context = resVecTy.getContext();
2048
2049 const auto *uArchInstruction = dyn_cast<xegpu::uArch::LoadGatherInstruction>(
2051 int maxChunkSize =
2052 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
2053 return setupGenericLoadAnchorLayout(layoutKind, context, consumerLayout,
2054 maxChunkSize, resShape, subgroupSize);
2055}
2056
2057/// Picks the subgroup layout for a scatter-style store (store_scatter /
2058/// store_matrix): the most balanced `numSg` factorization that divides
2059/// `wgShape` with sg_data a multiple of `instData`. A store has no consumer.
2060static xegpu::DistributeLayoutAttr
2062 ArrayRef<int64_t> instData, int numSg) {
2063 auto candidates = getSgLayoutCandidates(wgShape, instData, numSg);
2064 if (candidates.empty())
2065 return nullptr;
2066 // Candidates are ordered most-balanced first.
2067 return buildSgLayout(context, wgShape, candidates.front(), /*dimK=*/-1);
2068}
2069
2070/// Sets up the anchor layout for store scatter and store matrix operation,
2071/// which share the same logic. Lane layout comes from
2072/// `computeScatterIOLaneLayoutAndData`; inst_data is lane_layout * lane_data.
2073static xegpu::DistributeLayoutAttr setupGenericStoreAnchorLayout(
2074 xegpu::LayoutKind layoutKind, mlir::MLIRContext *context, int maxChunkSize,
2075 ArrayRef<int64_t> srcShape, int subgroupSize, int numSg) {
2076
2077 auto [laneLayout, laneData] =
2078 computeScatterIOLaneLayoutAndData(srcShape, subgroupSize, maxChunkSize);
2079
2080 SmallVector<int64_t> instData(srcShape.size());
2081 for (size_t i = 0; i < srcShape.size(); ++i)
2082 instData[i] = laneLayout[i] * laneData[i];
2083
2084 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2085 assert(numSg > 0 &&
2086 "Number of subgroups must be provided for sg layout creation.");
2087 return getStoreSubgroupLayouts(context, srcShape, instData, numSg);
2088 }
2089 if (layoutKind == xegpu::LayoutKind::InstData) {
2090 return buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
2091 }
2092 if (layoutKind == xegpu::LayoutKind::Lane) {
2093 return buildLaneLayout(context, laneLayout, laneData);
2094 }
2095 return nullptr;
2096}
2097
2098/// Sets up the anchor layout for a store scatter operation.
2099xegpu::DistributeLayoutAttr
2101 VectorType srcVecTy, int contigChunkSize,
2102 int numSg, const uArch::uArch *uArch) {
2103
2104 const int subgroupSize = uArch->getSubgroupSize();
2105 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
2106 auto context = srcVecTy.getContext();
2107
2108 // The per-lane chunk is bounded by what the offsets prove contiguous
2109 // (`contigChunkSize`) and by what one lane can access in one instruction.
2110 const auto *uArchInstruction =
2111 dyn_cast<xegpu::uArch::StoreScatterInstruction>(
2113 int maxChunkSize =
2114 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
2115 return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
2116 srcShape, subgroupSize, numSg);
2117}
2118
2119/// Sets up the anchor layout for a store matrix operation.
2120xegpu::DistributeLayoutAttr xegpu::setupStoreMatrixAnchorLayout(
2121 xegpu::LayoutKind layoutKind, VectorType srcVecTy, int contigChunkSize,
2122 int numSg, const xegpu::uArch::uArch *uArch) {
2123
2124 const int subgroupSize = uArch->getSubgroupSize();
2125 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
2126 auto context = srcVecTy.getContext();
2127
2128 const auto *uArchInstruction =
2129 dyn_cast<xegpu::uArch::StoreScatterInstruction>(
2131 int maxChunkSize =
2132 std::min(uArchInstruction->getMaxLaneAccessSizeBytes(), contigChunkSize);
2133
2134 return setupGenericStoreAnchorLayout(layoutKind, context, maxChunkSize,
2135 srcShape, subgroupSize, numSg);
2136}
2137
2138/// Completes a scatter IO layout by deriving lane_layout and lane_data from
2139/// `specifiedLayout`'s inst_data when they are missing. The layout is returned
2140/// unchanged if `specifiedLayout` is null, carries no inst_data, or already has
2141/// both lane_layout and lane_data.
2142///
2143/// When lane info is absent, inst_data is treated as the effective shape and
2144/// the lane factorization is filled in as follows:
2145/// - If `consumerLayout` is present and its lane_layout / lane_data are a
2146/// valid factorization of inst_data, that consumer lane info is reused so
2147/// the completed layout matches the consumer (avoiding a relayout).
2148/// - Otherwise a standard scatter-style factorization is computed via
2149/// `computeScatterIOLaneLayoutAndData`, bounded by `maxChunkSize` — the
2150/// per-lane load width reported by the uArch's LoadGather instruction
2151/// (`getMaxLaneAccessSizeBytes`).
2152///
2153std::optional<xegpu::DistributeLayoutAttr>
2155 xegpu::DistributeLayoutAttr specifiedLayout,
2156 xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
2157 const xegpu::uArch::LoadGatherInstruction *uArchInstruction,
2158 const int subgroupSize) {
2159 if (!specifiedLayout)
2160 return specifiedLayout;
2161 SmallVector<int64_t> specifiedInstData =
2162 specifiedLayout.getEffectiveInstDataAsInt();
2163 if (specifiedInstData.empty())
2164 return specifiedLayout;
2165 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2166 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2167 return specifiedLayout;
2168
2169 // Reuse the load-side setup with inst_data as the destination shape.
2170 auto *context = specifiedLayout.getContext();
2171 int maxChunkSize = uArchInstruction->getMaxLaneAccessSizeBytes();
2172 if (consumerLayout) {
2173 auto consumerLaneLayout = consumerLayout.getEffectiveLaneLayoutAsInt();
2174 auto consumerLaneData = consumerLayout.getEffectiveLaneDataAsInt();
2175 if (!consumerLaneLayout.empty() && !consumerLaneData.empty() &&
2176 isValidLaneLayout(specifiedInstData, consumerLaneLayout,
2177 consumerLaneData))
2178 return buildInstDataLayoutWithLane(context, specifiedInstData,
2179 consumerLaneLayout, consumerLaneData);
2180 }
2181 auto [defLaneLayout, defLaneData] = computeScatterIOLaneLayoutAndData(
2182 specifiedInstData, subgroupSize, maxChunkSize);
2183 if (!isValidLaneLayout(specifiedInstData, defLaneLayout, defLaneData))
2184 return std::nullopt;
2185 return buildInstDataLayoutWithLane(context, specifiedInstData, defLaneLayout,
2186 defLaneData);
2187}
2188
2189/// Like completeScatterLoadLaneLayoutFromInstData, but for scatter stores. A
2190/// store is a data sink, so lane info is derived purely from inst_data (bounded
2191/// by the uArch's per-lane store width); there is no consumer layout to reuse.
2192std::optional<xegpu::DistributeLayoutAttr>
2194 xegpu::DistributeLayoutAttr specifiedLayout, Type elemTy,
2195 const xegpu::uArch::StoreScatterInstruction *uArchInstruction,
2196 const int subgroupSize) {
2197 if (!specifiedLayout)
2198 return specifiedLayout;
2199 SmallVector<int64_t> specifiedInstData =
2200 specifiedLayout.getEffectiveInstDataAsInt();
2201 if (specifiedInstData.empty())
2202 return specifiedLayout;
2203 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2204 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2205 return specifiedLayout;
2206
2207 // Reuse the store-side setup with inst_data as the source shape.
2208 auto *context = specifiedLayout.getContext();
2209 int maxChunkSize = uArchInstruction->getMaxLaneAccessSizeBytes();
2210 auto [defLaneLayout, defLaneData] = computeScatterIOLaneLayoutAndData(
2211 specifiedInstData, subgroupSize, maxChunkSize);
2212 if (!isValidLaneLayout(specifiedInstData, defLaneLayout, defLaneData))
2213 return std::nullopt;
2214 return buildInstDataLayoutWithLane(context, specifiedInstData, defLaneLayout,
2215 defLaneData);
2216}
2217
2218/// Completes a 2D-block store/prefetch layout from its inst_data. store_nd and
2219/// prefetch_nd are data sinks, so lane info is derived purely from inst_data
2220/// (no consumer to reuse). One helper serves both via
2221/// BlockIOInstructionInterface.
2222std::optional<xegpu::DistributeLayoutAttr>
2224 xegpu::DistributeLayoutAttr specifiedLayout, Type elemTy,
2225 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
2226 const int subgroupSize) {
2227 if (!specifiedLayout)
2228 return specifiedLayout;
2229 SmallVector<int64_t> specifiedInstData =
2230 specifiedLayout.getEffectiveInstDataAsInt();
2231 if (specifiedInstData.empty())
2232 return specifiedLayout;
2233 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2234 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2235 return specifiedLayout;
2236
2237 auto *context = specifiedLayout.getContext();
2238 auto [laneLayout, laneData, order] = compute2DBlockIOLaneLayout(
2239 specifiedInstData, subgroupSize, elemTy.getIntOrFloatBitWidth(),
2240 uArchInstruction->getPackedFormatBitSize());
2241 if (!isValidLaneLayout(specifiedInstData, laneLayout, laneData))
2242 return std::nullopt;
2243 return buildInstDataLayoutWithLane(context, specifiedInstData, laneLayout,
2244 laneData);
2245}
2246
2247/// Like completeBlockStoreLaneLayoutFromInstData, but for load_nd. The
2248/// consumer's lane_data and order are reused as-is; lane_layout is rebuilt from
2249/// the consumer's lane_layout, bumping every non-unit dim up to the subgroup
2250/// size. The user-provided inst_data is preserved.
2251std::optional<xegpu::DistributeLayoutAttr>
2253 xegpu::DistributeLayoutAttr specifiedLayout,
2254 xegpu::DistributeLayoutAttr consumerLayout, Type elemTy,
2255 const xegpu::uArch::BlockIOInstructionInterface *uArchInstruction,
2256 const int subgroupSize) {
2257 if (!specifiedLayout)
2258 return specifiedLayout;
2259 SmallVector<int64_t> specifiedInstData =
2260 specifiedLayout.getEffectiveInstDataAsInt();
2261 if (specifiedInstData.empty())
2262 return specifiedLayout;
2263 if (!specifiedLayout.getEffectiveLaneLayoutAsInt().empty() &&
2264 !specifiedLayout.getEffectiveLaneDataAsInt().empty())
2265 return specifiedLayout;
2266 if (!consumerLayout)
2267 return specifiedLayout;
2268 SmallVector<int64_t> consumerLaneLayout =
2269 consumerLayout.getEffectiveLaneLayoutAsInt();
2270 SmallVector<int64_t> consumerLaneData =
2271 consumerLayout.getEffectiveLaneDataAsInt();
2272 if (consumerLaneLayout.empty() || consumerLaneData.empty())
2273 return specifiedLayout;
2274
2275 auto *context = specifiedLayout.getContext();
2276 int rank = specifiedInstData.size();
2277
2278 SmallVector<int64_t> laneLayout;
2279 // set the laneLayout to use consumer's LaneLayout as base, but adjust its
2280 // size to match the subgroupsize in case its original value is larger than 1
2281 for (int i = 0; i < rank; i++) {
2282 if (consumerLaneLayout[i] > 1) {
2283 laneLayout.push_back(
2284 std::max(static_cast<int64_t>(subgroupSize), consumerLaneLayout[i]));
2285 } else {
2286 laneLayout.push_back(1);
2287 }
2288 }
2289
2290 if (!isValidLaneLayout(specifiedInstData, laneLayout, consumerLaneData))
2291 return std::nullopt;
2292 return buildInstDataLayoutWithLane(context, specifiedInstData, laneLayout,
2293 consumerLaneData,
2294 consumerLayout.getOrder());
2295}
2296
2297/// Completes user-provided DPAS A/B/C-D anchors that carry only inst_data by
2298/// filling in lane_layout / lane_data. The lane factorization mirrors the
2299/// InstData branch of `setupDpasLayout` (derived from each operand's shape and
2300/// matmul role, B using VNNI packing); the user's inst_data is preserved.
2301std::optional<
2302 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2303 xegpu::DistributeLayoutAttr>>
2304xegpu::completeDpasLaneLayoutFromInstData(xegpu::DistributeLayoutAttr aLayout,
2305 xegpu::DistributeLayoutAttr bLayout,
2306 xegpu::DistributeLayoutAttr cdLayout,
2307 VectorType aTy, VectorType bTy,
2308 VectorType cdTy,
2309 const xegpu::uArch::uArch *uArch) {
2310 auto context = aTy.getContext();
2311 const auto *uArchInstruction =
2312 dyn_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc>(uArch->getInstruction(
2314 if (!uArchInstruction)
2315 return std::nullopt;
2316 auto subgroupSize = uArch->getSubgroupSize();
2317 llvm::SmallVector<int64_t> laneLayoutA, laneDataA, laneLayoutB, laneDataB,
2318 laneLayoutCD, laneDataCD;
2319 // DPAS operands are never transposed, so the order comes back empty.
2320 llvm::SmallVector<int64_t> orderA, orderB, orderCD;
2321 SmallVector<int64_t> instDataA = aLayout.getEffectiveInstDataAsInt();
2322 SmallVector<int64_t> instDataB = bLayout.getEffectiveInstDataAsInt();
2323 SmallVector<int64_t> instDataCD = cdLayout.getEffectiveInstDataAsInt();
2324
2325 if (isa<xegpu::uArch::Xe2, xegpu::uArch::Xe3>(uArch)) {
2326 std::tie(laneLayoutA, laneDataA, orderA) =
2327 compute2DBlockIOLaneLayout(aTy.getShape(), subgroupSize,
2328 aTy.getElementType().getIntOrFloatBitWidth(),
2329 uArchInstruction->getPackedFormatBitSizeA());
2330 std::tie(laneLayoutB, laneDataB, orderB) = compute2DBlockIOLaneLayout(
2331 bTy.getShape(), subgroupSize,
2332 bTy.getElementType().getIntOrFloatBitWidth(),
2333 uArchInstruction->getPackedFormatBitSizeB(), /*vnni=*/true);
2334 std::tie(laneLayoutCD, laneDataCD, orderCD) = compute2DBlockIOLaneLayout(
2335 cdTy.getShape(), subgroupSize,
2336 cdTy.getElementType().getIntOrFloatBitWidth(),
2337 cdTy.getElementType().getIntOrFloatBitWidth());
2338 } else {
2339 assert(false && "Unsupported uArch for DPAS lane layout completion");
2340 }
2341
2342 if (!isValidLaneLayout(instDataA, laneLayoutA, laneDataA) ||
2343 !isValidLaneLayout(instDataB, laneLayoutB, laneDataB) ||
2344 !isValidLaneLayout(instDataCD, laneLayoutCD, laneDataCD))
2345 return std::nullopt;
2346 return std::make_tuple(
2347 buildInstDataLayoutWithLane(context, instDataA, laneLayoutA, laneDataA,
2348 aLayout.getOrder()),
2349 buildInstDataLayoutWithLane(context, instDataB, laneLayoutB, laneDataB,
2350 bLayout.getOrder()),
2351 buildInstDataLayoutWithLane(context, instDataCD, laneLayoutCD, laneDataCD,
2352 cdLayout.getOrder()));
2353}
2354
2355/// Like completeDpasLaneLayoutFromInstData, but for dpas_mx: also re-derives
2356/// the A_scale / B_scale layouts from the completed A / B layouts via
2357/// `createScaleLayout`, matching the default path of `setupDpasMxLayout`.
2358std::optional<
2359 std::tuple<xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2360 xegpu::DistributeLayoutAttr, xegpu::DistributeLayoutAttr,
2361 xegpu::DistributeLayoutAttr>>
2363 xegpu::DistributeLayoutAttr aLayout, xegpu::DistributeLayoutAttr bLayout,
2364 xegpu::DistributeLayoutAttr cdLayout, VectorType aTy, VectorType bTy,
2365 VectorType cdTy, VectorType aScaleTy, VectorType bScaleTy,
2366 const xegpu::uArch::uArch *uArch) {
2367 auto completed = completeDpasLaneLayoutFromInstData(
2368 aLayout, bLayout, cdLayout, aTy, bTy, cdTy, uArch);
2369 if (!completed)
2370 return std::nullopt;
2371 auto context = aTy.getContext();
2372 auto [completedA, completedB, completedCD] = *completed;
2373
2374 auto aScaleLayout =
2375 createScaleLayout(context, aTy, aScaleTy, completedA, false, uArch);
2376 auto bScaleLayout =
2377 createScaleLayout(context, bTy, bScaleTy, completedB, true, uArch);
2378
2379 return std::make_tuple(completedA, completedB, completedCD, aScaleLayout,
2380 bScaleLayout);
2381}
2382
2383/// Sets up layout for reduction operations by creating a SliceAttr for the
2384/// result.
2385///
2386/// Algorithm Overview:
2387/// This function attempts to construct a source layout that, when sliced along
2388/// reduction dimensions, produces a result layout compatible with the
2389/// consumer layout.
2390///
2391/// For subgroup layouts, it first tries to align the source layout's subgroup
2392/// layout and data with the consumer's layout on non-reduction dimensions.
2393/// Then, it distributes remaining subgroups across reduction dimensions. This
2394/// avoids subgroup data redistribution overhead between the reduced result and
2395/// its consumer. When the consumer layout is a slice layout, it attempts to
2396/// reuse the slice layout's parent layout for the source to further minimize
2397/// potential data redistribution.
2398///
2399/// This is a best-effort alignment, not a hard constraint: the goal is only to
2400/// pick a *legal* source layout that minimizes redistribution against the
2401/// (single, first-arriving) consumer layout. There is no failure path - when
2402/// the consumer's slice layout cannot be reused as-is (example 2 below), the
2403/// function falls back to distributing all subgroups on the non-reduction
2404/// dimensions first and the remainder on the reduction dimensions, which always
2405/// yields a valid source layout. If the resulting source layout still differs
2406/// from what some consumer expects (e.g. a second, inconsistent consumer), that
2407/// mismatch is reconciled later by the layout conflict resolution process
2408/// (`ResolveLayoutConflicts`), which inserts a `convert_layout` op - this
2409/// function never has to give up.
2410///
2411/// InstData kind: `computeReductionLaneLayoutAndData` derives lane_layout and
2412/// lane_data, and inst_data is their element-wise product.
2413///
2414/// Lane kind: a consumer sliced over exactly `reductionDims` is reused verbatim
2415/// rather than re-derived, so this stage does not re-decide packing an earlier
2416/// run already committed to. Any other consumer goes through the same
2417/// derivation.
2418///
2419/// The function returns the *result* layout (the SliceAttr). The *source*
2420/// layout it decides on is the parent of that slice; both are listed below so
2421/// the relationship is explicit.
2422///
2423/// Examples:
2424/// 1. Subgroup layout - Row reduction on 2D tensor:
2425/// srcShape=[32, 128], reductionDims=[1], resShape=[32], subgroupSize=16,
2426/// NumSg=32
2427/// * Consumer Layout:
2428/// #xegpu.slice<#xegpu.layout<sg_layout=[4, 8], sg_data=[8, 8]>, dims =
2429/// [1]>}
2430/// * Source Layout (decided by this function):
2431/// #xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>
2432/// * Result Layout (returned):
2433/// #xegpu.slice<#xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>, dims =
2434/// [1]>}
2435/// The consumer slices exactly the reduction dim, so its parent layout is
2436/// reused for the source: sg_layout is kept, but the source's sg_data on
2437/// the reduction dim is grown from 8 to 16 (= srcShape[1] / sg_layout[1] =
2438/// 128 / 8) so the source tile is evenly distributed over the reduction
2439/// dim. Slicing that source over dim 1 reproduces the consumer.
2440///
2441/// 2. Subgroup layout - Same shapes as above but consumer doesn't have a
2442/// reusable slice layout, so the algorithm distributes all subgroups on the
2443/// non-reduction dims first and the remainder on the reduction dims.
2444/// 2a. * Consumer Layout:
2445/// #xegpu.layout<sg_layout=[32], sg_data=[1]>
2446/// * Source Layout (decided by this function):
2447/// #xegpu.layout<sg_layout=[32, 1], sg_data=[1, 128]>
2448/// * Result Layout (returned):
2449/// #xegpu.slice<#xegpu.layout<sg_layout=[32, 1], sg_data=[1, 128]>,
2450/// dims = [1]>}
2451/// All 32 subgroups land on the non-reduction dim 0; the reduction dim
2452/// 1 gets the leftover (sg_layout=1, so the whole length 128 lives in
2453/// one subgroup's sg_data).
2454/// 2b. * Consumer Layout:
2455/// #xegpu.slice<#xegpu.layout<sg_layout=[8, 2, 4], sg_data=[4, 64,
2456/// 32]>, dims = [1, 2]>}
2457/// * Source Layout (decided by this function):
2458/// #xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>
2459/// * Result Layout (returned):
2460/// #xegpu.slice<#xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>,
2461/// dims = [1]>}
2462/// The consumer slices dims [1, 2] which do not match this op's
2463/// reductionDims, so it can't be reused as-is; subgroups are
2464/// re-distributed (non-reduction dim first, then reduction dim).
2465///
2466/// 3. Lane layout - Default (lanes on innermost dim):
2467/// srcShape=[32, 64], reductionDims=[0], subgroupSize=16
2468/// * Source Layout (decided by this function):
2469/// laneLayout=[1, 16], laneData=[1, 1] (returned sliced over dim 0).
2470/// The innermost dim is not reduced, so lanes stay on it. Reduced dim 0 is
2471/// then lane-free, but it is not the innermost dim, so it takes no reduce
2472/// vector and the reduction runs one element at a time.
2473///
2474/// 4. Lane layout - Switch (lanes moved off the reduction dim):
2475/// srcShape=[32, 64], reductionDims=[1], subgroupSize=16
2476/// * Source Layout (decided by this function):
2477/// laneLayout=[16, 1], laneData=[1, 16] (returned sliced over dim 1).
2478/// The innermost dim is the sole reduction dim, so lanes move to the
2479/// non-reduction dim to reduce within a lane, and the now lane-free
2480/// reduction dim takes the per-lane reduce vector (16 of its 64 elements).
2481/// This switch only happens when the consumer has no reduction dims to
2482/// broadcast the result back along (i.e. the consumer layout is not a
2483/// slice over this reduction); otherwise the default (example 3) is used.
2484///
2485/// 5. Lane layout - No switch when both inner dims are reduced (reduction to
2486/// scalar):
2487/// srcShape=[32, 64], reductionDims=[0, 1], subgroupSize=16
2488/// * Source Layout (decided by this function):
2489/// laneLayout=[1, 16], laneData=[1, 1] (returned sliced over dims
2490/// [0,1]).
2491/// Both dims are reduced, so this is not a *sole* innermost reduction; the
2492/// switch condition (example 4) does not apply and lanes stay on the
2493/// innermost dim. The cross-lane reduction over dim 1 is unavoidable.
2494///
2495/// 6. Lane layout - No switch when the consumer slices the reduction dim:
2496/// srcShape=[32, 64], reductionDims=[1], subgroupSize=16
2497/// * Consumer Layout:
2498/// #xegpu.slice<#xegpu.layout<laneLayout=[1, 16], laneData=[1, 1]>,
2499/// dims = [1]>}
2500/// * Source Layout (decided by this function):
2501/// #xegpu.layout<laneLayout=[1, 16], laneData=[1, 1]> (the consumer
2502/// slice's parent, reused directly; returned sliced over dim 1).
2503/// Same shape/reductionDims as example 4, but here the consumer is a slice
2504/// over the reduction dim, so it can broadcast the result back along that
2505/// dim. The slice's parent layout is reused as the source (no switch, no
2506/// re-derivation); the inst_data propagation step has already inserted a
2507/// convert_layout if needed, so the lane-level layout can be reused as-is.
2508
2510 xegpu::LayoutKind layoutKind, VectorType srcVecTy,
2511 DistributeLayoutAttr consumerLayout, SmallVector<int64_t> reductionDims,
2512 int numSg, const xegpu::uArch::uArch *uArch) {
2513
2514 auto srcShape = srcVecTy.getShape();
2515 int srcRank = srcShape.size();
2516 auto context = srcVecTy.getContext();
2517
2518 const int subgroupSize = uArch->getSubgroupSize();
2519 int64_t maxReduceVectorSize = 16; // extend to spirv vector Size
2520 xegpu::DistributeLayoutAttr srcLayout;
2521 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2522 xegpu::SliceAttr consumerSliceLayout =
2523 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2524 if (consumerSliceLayout &&
2525 consumerSliceLayout.getDims().asArrayRef().equals(reductionDims)) {
2526 srcLayout = consumerSliceLayout.getParent();
2527 SmallVector<int64_t> sgLayoutFromConsumer =
2528 srcLayout.getEffectiveSgLayoutAsInt();
2529 auto srcSgData = computeShapeRatio(srcShape, sgLayoutFromConsumer);
2530 if (srcSgData)
2531 for (int dim = 0; dim < srcRank; dim++) {
2532 if (llvm::is_contained(reductionDims, dim))
2533 srcLayout =
2534 srcLayout.setDimData(dim, srcSgData.value()[dim], -1, -1);
2535 }
2536 } else {
2537 SmallVector<int64_t> consumerSgLayout =
2538 consumerLayout ? consumerLayout.getEffectiveSgLayoutAsInt()
2540 SmallVector<int64_t> consumerSgData =
2541 consumerLayout ? consumerLayout.getEffectiveSgDataAsInt()
2543 SmallVector<int64_t> consumerOrder =
2544 consumerLayout ? consumerLayout.getEffectiveOrderAsInt()
2546 DenseI32ArrayAttr orderAttr =
2547 consumerLayout ? consumerLayout.getOrder() : nullptr;
2548 SmallVector<int64_t> sgLayout(srcRank), sgData(srcRank), order(srcRank);
2549 int remainingSgCount =
2550 consumerLayout ? consumerLayout.getNumSubgroups() : numSg;
2551 int consumerIdx = 0;
2552
2553 // First pass: match the consumer's layout on non-reduction dims.
2554 for (int i = 0; i < srcRank; i++) {
2555 if (!llvm::is_contained(reductionDims, i) &&
2556 consumerIdx < static_cast<int>(consumerSgLayout.size())) {
2557 sgLayout[i] = consumerSgLayout[consumerIdx];
2558 sgData[i] = consumerSgData[consumerIdx];
2559 remainingSgCount /= sgLayout[i];
2560 consumerIdx++;
2561 }
2562 }
2563
2564 // Second pass: distribute remaining subgroups across the reduction dims.
2565 // The reduction-to-scalar case is handled only by this loop.
2566 for (int i = 0; i < srcRank; i++) {
2567 if (llvm::is_contained(reductionDims, i)) {
2568 sgLayout[i] =
2569 std::min(srcShape[i], static_cast<int64_t>(remainingSgCount));
2570 assert((srcShape[i] % sgLayout[i] == 0) &&
2571 "source shape not divisible by sg_layout");
2572 sgData[i] = srcShape[i] / sgLayout[i];
2573 remainingSgCount /= sgLayout[i];
2574 }
2575 }
2576 // The reduction dims are assumed to be row-major contiguous with the
2577 // left-hand neighbor.
2578 DenseI32ArrayAttr resOrderAttr = nullptr;
2579 int numRetainedDims = srcRank - static_cast<int>(reductionDims.size());
2580 if (orderAttr && !orderAttr.empty() &&
2581 static_cast<int>(consumerOrder.size()) == numRetainedDims) {
2582 // Fill known order
2583 int retainedRank = 0;
2584 for (int dim = 0; dim < srcRank; dim++)
2585 if (!llvm::is_contained(reductionDims, dim))
2586 order[dim] = consumerOrder[retainedRank++];
2587 // Each new order is placed as close as possible to its neighbors,
2588 // row-major. Re-index the order of higher walk orders.
2589 for (int reducedDim = 0; reducedDim < srcRank; reducedDim++) {
2590 if (!llvm::is_contained(reductionDims, reducedDim))
2591 continue;
2592 int64_t insertRank = 0;
2593 // Add as an inner dim to the outer neighbor
2594 if (reducedDim)
2595 insertRank = order[reducedDim - 1];
2596 // Add as an outer dim to the inner neighbor if no left neighbor
2597 // exists.
2598 else if (srcRank > 1)
2599 insertRank = order[reducedDim + 1] + 1;
2600 for (int otherDim = 0; otherDim < srcRank; otherDim++)
2601 if (order[otherDim] >= insertRank)
2602 order[otherDim]++;
2603 order[reducedDim] = insertRank;
2604 }
2605 resOrderAttr = DenseI32ArrayAttr::get(
2606 context, SmallVector<int32_t>(order.begin(), order.end()));
2607 }
2608 assert(remainingSgCount == 1 && "not all subgroups distributed");
2609 srcLayout = buildLayout(context, sgLayout, sgData,
2610 /*instData=*/{}, /*laneLayout=*/{},
2611 /*laneData=*/{}, resOrderAttr);
2612 }
2613 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2614 auto [laneLayout, laneData, instData] =
2615 computeReductionLaneLayoutAndData(srcShape, reductionDims, subgroupSize,
2616 maxReduceVectorSize, consumerLayout);
2617 srcLayout =
2618 buildInstDataLayoutWithLane(context, instData, laneLayout, laneData);
2619 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2620 xegpu::SliceAttr consumerSliceLayout =
2621 dyn_cast_if_present<xegpu::SliceAttr>(consumerLayout);
2622 if (consumerSliceLayout &&
2623 consumerSliceLayout.getDims().asArrayRef().equals(reductionDims)) {
2624 // at the lane level, the consumerSliceLayout can be directly reused
2625 // since the inst_data propagation already insert convert_layout if
2626 // the layout is not consistent
2627 srcLayout = consumerSliceLayout.getParent();
2628 } else {
2629 auto [laneLayout, laneData, instData] = computeReductionLaneLayoutAndData(
2630 srcShape, reductionDims, subgroupSize, maxReduceVectorSize,
2631 consumerLayout);
2632 (void)instData;
2633 srcLayout = buildLaneLayout(context, laneLayout, laneData);
2634 }
2635 }
2636
2637 return xegpu::SliceAttr::get(context, srcLayout,
2638 DenseI64ArrayAttr::get(context, reductionDims));
2639}
2640
2641/// Sets up layout for Reduction operations by creating a SliceAttr for the
2642/// result.
2643xegpu::SliceAttr
2645 VectorType srcVecTy,
2646 const xegpu::uArch::uArch *uArch) {
2647
2648 auto srcShape = srcVecTy.getShape();
2649 auto context = srcVecTy.getContext();
2650 auto subgroupSize = uArch->getSubgroupSize();
2651 xegpu::LayoutAttr srcLayout;
2652
2653 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2654 assert(false &&
2655 "subgroup layout assignment not supported for reduction (op "
2656 "is not expected at this level).");
2657 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2658 assert(false &&
2659 "instData layout assignment not supported for reduction (op "
2660 "is not expected at this level).");
2661 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2662 SmallVector<int64_t> laneLayout(1), laneData(1);
2663 laneLayout[0] = std::min(static_cast<int64_t>(subgroupSize), srcShape[0]);
2664 laneData[0] = 1;
2665 srcLayout = buildLaneLayout(context, laneLayout, laneData);
2666 }
2667
2668 auto result = xegpu::SliceAttr::get(context, srcLayout,
2669 DenseI64ArrayAttr::get(context, 0));
2670 return result;
2671}
2672
2673/// Adjusts `consumerLayout`'s innermost-dim data field selected by
2674/// `layoutKind` so that the source layout can be safely inferred by dividing
2675/// that value by `ratio`. Doubles the value until the divisibility constraint
2676/// is met, bounded above by `bound` like result-shape.
2677///
2678/// Used by ops whose source relates to the result by a fixed factor along the
2679/// innermost dim (e.g., bitcast: bitwidth ratio; interleave: 2x).
2680///
2681/// Divisibility constraints per LayoutKind:
2682/// - Subgroup: sgData[innermost] % ratio == 0
2683/// - InstData: instData[innermost] % (laneLayout[innermost] * ratio) == 0
2684/// (laneLayout falls back to subgroupSize if absent)
2685/// - Lane: laneData[innermost] % ratio == 0
2686static xegpu::DistributeLayoutAttr
2687adjustInnermostDimForDivisibility(xegpu::DistributeLayoutAttr consumerLayout,
2688 xegpu::LayoutKind layoutKind,
2689 size_t innerMostDim, int ratio, int64_t bound,
2690 const xegpu::uArch::uArch *uArch) {
2691 SmallVector<int64_t> sgData = consumerLayout.getEffectiveSgDataAsInt();
2692 SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
2693 SmallVector<int64_t> laneData = consumerLayout.getEffectiveLaneDataAsInt();
2694 SmallVector<int64_t> laneLayout =
2695 consumerLayout.getEffectiveLaneLayoutAsInt();
2696
2697 int64_t sgDataValue = -1;
2698 int64_t instDataValue = -1;
2699 int64_t laneDataValue = -1;
2700
2701 if (layoutKind == xegpu::LayoutKind::Subgroup) {
2702 sgDataValue = sgData[innerMostDim];
2703 while ((sgDataValue <= bound) && (sgDataValue % ratio) != 0)
2704 sgDataValue *= 2;
2705 } else if (layoutKind == xegpu::LayoutKind::InstData) {
2706 instDataValue = instData[innerMostDim];
2707 const int innermostDimLaneLayout = laneLayout.empty()
2708 ? uArch->getSubgroupSize()
2709 : laneLayout[innerMostDim];
2710 while ((instDataValue <= bound) &&
2711 (instDataValue % (innermostDimLaneLayout * ratio) != 0))
2712 instDataValue *= 2;
2713 assert((bound % instDataValue) == 0 &&
2714 "bound, instData, and laneLayout for innermost must be 2^n!");
2715 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2716 laneDataValue = laneData[innerMostDim];
2717 while ((laneDataValue <= bound) && (laneDataValue % ratio) != 0)
2718 laneDataValue *= 2;
2719 }
2720
2721 return consumerLayout.setDimData(innerMostDim, sgDataValue, instDataValue,
2722 laneDataValue);
2723}
2724
2725/// Sets up the result layout for a bitcast operation.
2726/// When casting to a smaller bitwidth, adjusts the layout dimensions (sgData,
2727/// instData, or laneData) by multiplying by the bitwidth ratio to ensure the
2728/// result layout can be correctly divided back to the source layout during
2729/// inference.
2730///
2731/// Examples:
2732/// 1. Casting f32 -> f16 (32-bit to 16-bit, bitWidthRatio = 2):
2733/// Consumer layout: instData=[1, 16], subgroupSize=16
2734/// Source shape: [8, 32]
2735/// Result layout: instData=[1, 32] (16 * 2)
2736/// The innermost dimension is multiplied by 2 to maintain consistency.
2737///
2738/// 2. Casting f32 -> i8 (32-bit to 8-bit, bitWidthRatio = 4):
2739/// Consumer instData=[1, 16], subgroupSize=16
2740/// Source shape: [4, 128]
2741/// adjust the instData from [1, 16] to [1, 16 * 4 = 64]
2742///
2743/// 3. Casting i8 -> i32 (8-bit to 32-bit, bitWidthRatio = 1/4):
2744/// Consumer layout: laneLayout=[1, 16], laneData=[1, 4]
2745/// No adjustment needed - returns consumer layout directly.
2746///
2747xegpu::DistributeLayoutAttr xegpu::setupBitCastResultLayout(
2748 xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
2749 DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
2750
2751 int srcElemTyBitWidth = srcVecTy.getElementType().getIntOrFloatBitWidth();
2752 int resElemTyBitWidth = resVecTy.getElementType().getIntOrFloatBitWidth();
2753
2754 ArrayRef<int64_t> srcShape = srcVecTy.getShape();
2755 ArrayRef<int64_t> resShape = resVecTy.getShape();
2756
2757 assert(consumerLayout.getRank() == static_cast<int64_t>(srcShape.size()) &&
2758 "laneData must be available for all dimensions");
2759
2760 // Casting to same/larger element type: result has fewer (or equal) elements
2761 // along the innermost dim, no adjustment needed.
2762 if (srcElemTyBitWidth <= resElemTyBitWidth)
2763 return consumerLayout;
2764
2765 // Casting to smaller element type: result has more elements along innermost
2766 // dim. Adjust the innermost data field upward so the source layout can be
2767 // recovered by dividing by bitWidthRatio.
2768 size_t innerMostDim = srcShape.size() - 1;
2769 int bitWidthRatio = srcElemTyBitWidth / resElemTyBitWidth;
2770 return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
2771 innerMostDim, bitWidthRatio,
2772 resShape[innerMostDim], uArch);
2773}
2774
2775/// Sets up the result layout for an interleave operation to ensure the source
2776/// layout can be safely derived. Interleave doubles the innermost dimension,
2777/// so the result layout must ensure that laneData is a multiple
2778/// of 2, and instData must be divisible by innermostDimLaneLayout * 2.
2779///
2780/// Example:
2781/// Interleave: vector<128x256xf4> -> vector<128x512xf4>
2782/// Consumer layout: laneLayout=[1, 16], laneData=[1, 4], instData=[1, 64]
2783/// Result layout adjustment to ensure source can be safely inferred:
2784/// - laneData must be >= 2 and multiple of 2 (so source = laneData/2 is
2785/// valid)
2786/// - instData must be divisible by (16 * 2 = 32) (so source = instData/2 is
2787/// valid)
2788/// - Adjusted instData: ensure (instData % 32 == 0)
2789///
2790xegpu::DistributeLayoutAttr xegpu::setupInterleaveResultLayout(
2791 xegpu::LayoutKind layoutKind, VectorType srcVecTy, VectorType resVecTy,
2792 DistributeLayoutAttr consumerLayout, const xegpu::uArch::uArch *uArch) {
2793
2794 ArrayRef<int64_t> resShape = resVecTy.getShape();
2795 assert(consumerLayout.getRank() == static_cast<int64_t>(resShape.size()) &&
2796 "consumer layout rank must match source shape rank");
2797
2798 // Interleave doubles the innermost dimension (ratio = 2). Adjust the
2799 // innermost data field so the source layout can be recovered by dividing
2800 // by 2.
2801 const size_t innerMostDim = resShape.size() - 1;
2802 constexpr int ratio = 2;
2803 return adjustInnermostDimForDivisibility(consumerLayout, layoutKind,
2804 innerMostDim, ratio,
2805 resShape[innerMostDim], uArch);
2806}
2807
2808/// Checks that the tiles a split group is cut into walk the collapsed source
2809/// dim with one constant stride, which is what `collapseDims` assumes when it
2810/// multiplies a layout field across the group.
2811///
2812/// Along each dim the group is cut into `counts[dim]` tiles of `tileShape[dim]`
2813/// elements (empty for single-element tiles) taken from `shape[dim]`, which
2814/// gives the row-major strides. Walking from the innermost dim out, a dim cut
2815/// into several tiles steps by `tileShape[dim] * stride(dim)` and has to pick
2816/// up where the inner dims left off, `expectedStride`; a dim cut into one tile
2817/// does not step at all, so it only contributes its size.
2820 ArrayRef<int64_t> counts,
2821 ArrayRef<int64_t> tileShape,
2822 int64_t expectedStride) {
2823 int64_t stride = 1;
2824 for (int64_t dim : llvm::reverse(dimGroup)) {
2825 int64_t tileSize = tileShape.empty() ? 1 : tileShape[dim];
2826 if (counts[dim] != 1) {
2827 if (stride * tileSize != expectedStride)
2828 return false;
2829 expectedStride = stride * tileSize * counts[dim];
2830 }
2831 stride *= shape[dim];
2832 }
2833 return true;
2834}
2835
2836xegpu::DistributeLayoutAttr
2838 VectorType srcVecTy, VectorType resVecTy,
2839 DistributeLayoutAttr consumerLayout) {
2840 // TODO: work out the subgroup level rule; leave such layouts alone for now.
2841 if (!consumerLayout || layoutKind == xegpu::LayoutKind::Subgroup)
2842 return consumerLayout;
2843
2844 ArrayRef<int64_t> resShape = resVecTy.getShape();
2845 SmallVector<SmallVector<int64_t>> splitDimGroups;
2846 if (!xegpu::matchSplitDimExpansion(srcVecTy.getShape(), resShape,
2847 splitDimGroups))
2848 return consumerLayout;
2849
2850 SmallVector<int64_t> laneLayout =
2851 consumerLayout.getEffectiveLaneLayoutAsInt();
2852 SmallVector<int64_t> instData = consumerLayout.getEffectiveInstDataAsInt();
2853
2854 xegpu::DistributeLayoutAttr resLayout = [&]() -> DistributeLayoutAttr {
2855 DistributeLayoutAttr layout = consumerLayout;
2856 for (const SmallVector<int64_t> &dimGroup : splitDimGroups) {
2857 size_t i = 0;
2858 while (i < dimGroup.size() && laneLayout[dimGroup[i]] == 1)
2859 ++i;
2860
2861 for (++i; i < dimGroup.size(); ++i) {
2862 int64_t dim = dimGroup[i];
2863 int64_t dimSize = resShape[dim];
2864 int64_t lanes = laneLayout[dim];
2865 if (lanes <= 0 || dimSize % lanes != 0)
2866 return consumerLayout;
2867 layout =
2868 layout.setDimData(dim, /*sgData=*/-1,
2869 instData.empty() ? -1 : dimSize, dimSize / lanes);
2870 }
2871 }
2872 return layout;
2873 }();
2874
2875 // Stretching does not make every layout collapsible; reject the rest.
2876 SmallVector<int64_t> resInstData = resLayout.getEffectiveInstDataAsInt();
2877 SmallVector<int64_t> resLaneLayout = resLayout.getEffectiveLaneLayoutAsInt();
2878 SmallVector<int64_t> resLaneData = resLayout.getEffectiveLaneDataAsInt();
2879
2880 for (const SmallVector<int64_t> &dimGroup : splitDimGroups) {
2881 // One lane's elements must be a single contiguous run of the collapsed dim.
2882 if (!isContiguousTiling(dimGroup, resShape, resLaneData, /*tileShape=*/{},
2883 /*expectedStride=*/1))
2884 return nullptr;
2885
2886 // Consecutive lanes must be exactly one such run apart.
2887 int64_t collapsedLaneData = 1;
2888 for (int64_t dim : dimGroup)
2889 collapsedLaneData *= resLaneData[dim];
2890 if (!isContiguousTiling(dimGroup, resShape, resLaneLayout, resLaneData,
2891 /*expectedStride=*/collapsedLaneData))
2892 return nullptr;
2893
2894 // The inst tile must walk the result shape the same way.
2895 if (!resInstData.empty() &&
2896 !isContiguousTiling(dimGroup, resShape, resInstData, /*tileShape=*/{},
2897 /*expectedStride=*/1))
2898 return nullptr;
2899 }
2900 return resLayout;
2901}
2902
2903/// Sets up the result layout for an insert strided slice operation.
2904/// Creates a result layout based on the specified layout kind (InstData or
2905/// Lane).
2906xegpu::DistributeLayoutAttr xegpu::setupInsertStridedSliceResultLayout(
2907 xegpu::LayoutKind layoutKind, VectorType srcVectorTy,
2908 VectorType resVectorTy, xegpu::DistributeLayoutAttr consumerLayout,
2909 const xegpu::uArch::uArch *uArch) {
2910
2911 xegpu::DistributeLayoutAttr requiredResLayout;
2912 SmallVector<int64_t> consumerInstData =
2913 consumerLayout.getEffectiveInstDataAsInt();
2914 SmallVector<int64_t> consumerLaneData =
2915 consumerLayout.getEffectiveLaneDataAsInt();
2916 SmallVector<int64_t> consumerLaneLayout =
2917 consumerLayout.getEffectiveLaneLayoutAsInt();
2918 ArrayRef<int64_t> srcShape = srcVectorTy.getShape();
2919 int64_t laneDataValue = -1;
2920
2921 requiredResLayout = consumerLayout;
2922 int srcRank = srcShape.size();
2923
2924 if (layoutKind == xegpu::LayoutKind::Subgroup ||
2925 layoutKind == xegpu::LayoutKind::InstData) {
2926 assert(false && "subgroup/instData layout assignment not supported for "
2927 "insertStridedSlice.");
2928 } else if (layoutKind == xegpu::LayoutKind::Lane) {
2929 for (int dim = 0; dim < srcRank; dim++) {
2930 // A size-1 source dim is broadcast across the lanes of that dim.
2931 if (srcShape[dim] == 1) {
2932 laneDataValue = 1;
2933 } else {
2934 assert(srcShape[dim] % consumerLaneLayout[dim] == 0 &&
2935 "srcShape must be divisible by laneLayout for all dimensions");
2936 laneDataValue = std::min(srcShape[dim] / consumerLaneLayout[dim],
2937 consumerLaneData[dim]);
2938 }
2939 requiredResLayout =
2940 requiredResLayout.setDimData(dim, -1, -1, laneDataValue);
2941 }
2942 }
2943 return requiredResLayout;
2944}
2945
2946/// Back-propagates a known result layout to the layout required on `operand`
2947/// for a non-anchor (layout-propagating) vector op. Dispatches on the op kind —
2948/// broadcast, (multi)reduction, bitcast, shape/transpose, insert/extract,
2949/// interleave, etc. — applying the shape/permutation/bitwidth transform to
2950/// derive the source layout; elementwise and pass-through ops reuse resLayout
2951/// as-is. Returns nullptr for unknown ops or an absent result layout.
2952xegpu::DistributeLayoutAttr xegpu::inferSourceLayoutFromResultForNonAnchorOp(
2953 OpOperand &operand, xegpu::DistributeLayoutAttr resLayout) {
2954 if (!resLayout)
2955 return nullptr;
2956 Operation *op = operand.getOwner();
2957 unsigned idx = operand.getOperandNumber();
2958
2959 // For vector::BroadcastOp, infer the source layout from the result layout.
2960 if (auto broadcast = dyn_cast<vector::BroadcastOp>(op)) {
2961 auto srcTy = dyn_cast<VectorType>(broadcast.getSourceType());
2962 if (!srcTy)
2963 return nullptr;
2965 resLayout, broadcast.getResultVectorType().getShape(),
2966 srcTy.getShape());
2967 }
2968
2969 // For vector::MultiDimReductionOp, infer source layout from result layout
2970 // using reduction dims. Acc operand is expected to have the same layout as
2971 // the result.
2972 if (auto reduction = dyn_cast<vector::MultiDimReductionOp>(op)) {
2973 if (idx == 0) {
2974 SmallVector<int64_t> reductionDims(reduction.getReductionDims());
2975 return xegpu::inferMultiReductionSourceLayout(resLayout, reductionDims);
2976 }
2977 if (idx == 1)
2978 return resLayout;
2979 }
2980
2981 if (auto reduction = dyn_cast<vector::ReductionOp>(op))
2982 return xegpu::inferReductionSourceLayout(resLayout);
2983
2984 // For vector::BitCastOp, infer source layout from result layout using
2985 // element type bitwidths.
2986 if (auto bitcast = dyn_cast<vector::BitCastOp>(op)) {
2987 int resElemBitWidth =
2988 bitcast.getResultVectorType().getElementType().getIntOrFloatBitWidth();
2989 int srcElemBitWidth =
2990 bitcast.getSourceVectorType().getElementType().getIntOrFloatBitWidth();
2991 return xegpu::inferBitCastSourceLayout(resLayout, resElemBitWidth,
2992 srcElemBitWidth);
2993 }
2994
2995 // For vector::ShapeCastOp, infer source layout from result layout using
2996 // shapes.
2997 if (auto shapeCast = dyn_cast<vector::ShapeCastOp>(op)) {
2999 resLayout, shapeCast.getResultVectorType().getShape(),
3000 shapeCast.getSourceVectorType().getShape());
3001 }
3002
3003 // For vector::InsertStridedSliceOp, infer source layout from result
3004 // layout. Dest vector must have the same layout as the result.
3005 if (auto insertSlice = dyn_cast<vector::InsertStridedSliceOp>(op)) {
3006 if (idx == 0) {
3008 resLayout, insertSlice.getDestVectorType().getShape(),
3009 insertSlice.getSourceVectorType().getShape());
3010 }
3011 if (idx == 1)
3012 return resLayout;
3013 }
3014
3015 // For vector::Insert Op, infer source layout from result layout using
3016 // shapes.
3017 if (auto insert = dyn_cast<vector::InsertOp>(op)) {
3018 VectorType resVecTy = dyn_cast<VectorType>(insert.getResult().getType());
3019 VectorType valueToStoreTy =
3020 dyn_cast<VectorType>(insert.getValueToStore().getType());
3021
3022 if ((idx == 0) && valueToStoreTy) {
3023 return xegpu::inferInsertSourceLayout(resLayout, resVecTy.getShape(),
3024 valueToStoreTy.getShape());
3025 }
3026 if (idx == 1)
3027 return resLayout;
3028 }
3029
3030 // For vector::Extract Op, infer source layout from result layout using
3031 // shapes.
3032 if (auto extract = dyn_cast<vector::ExtractOp>(op)) {
3033 VectorType srcVecTy = dyn_cast<VectorType>(extract.getSource().getType());
3034 VectorType resVecTy = dyn_cast<VectorType>(extract.getResult().getType());
3035 if (!srcVecTy || !resVecTy)
3036 return nullptr;
3037 return xegpu::inferExtractSourceLayout(resLayout, resVecTy.getShape(),
3038 srcVecTy.getShape());
3039 }
3040
3041 // For vector::TransposeOp, infer source layout from result layout using
3042 // permutation.
3043 if (auto transpose = dyn_cast<vector::TransposeOp>(op)) {
3044 return xegpu::inferTransposeSourceLayout(resLayout,
3045 transpose.getPermutation());
3046 }
3047
3048 // For vector::BitCastOp, infer source layout from result layout using
3049 // element type bitwidths.
3050 if (auto bitcast = dyn_cast<vector::BitCastOp>(op)) {
3051 int resElemBitWidth =
3052 bitcast.getResultVectorType().getElementType().getIntOrFloatBitWidth();
3053 int srcElemBitWidth =
3054 bitcast.getSourceVectorType().getElementType().getIntOrFloatBitWidth();
3055 return xegpu::inferBitCastSourceLayout(resLayout, resElemBitWidth,
3056 srcElemBitWidth);
3057 }
3058
3059 // for vector::interleave
3060 if (auto interleave = dyn_cast<vector::InterleaveOp>(op)) {
3061 return xegpu::inferInterleaveSourceLayout(resLayout);
3062 }
3063
3064 // for vector::deinterleave
3065 if (auto deinterleave = dyn_cast<vector::DeinterleaveOp>(op)) {
3066 return xegpu::inferDeinterleaveSourceLayout(resLayout);
3067 }
3068
3069 // For vector::ExtractStridedSliceOp, simply return result layout
3070 if (dyn_cast<vector::ExtractStridedSliceOp>(op))
3071 return resLayout;
3072
3073 // For elementwise operations, all operands must have the same layout as
3074 // the result.
3076 return resLayout;
3077
3078 return nullptr;
3079}
3080
3081// For a loop terminator operand (scf.for's scf.yield, scf.while's
3082// scf.condition), returns the layout of the region iter_arg it forwards into,
3083// which is the authoritative loop-carried layout, or nullptr when that position
3084// was never assigned a layout.
3085static xegpu::DistributeLayoutAttr getLoopCarriedLayoutForYieldOperand(
3086 RegionBranchTerminatorOpInterface terminator, OpOperand &operand) {
3087 auto branch = dyn_cast<RegionBranchOpInterface>(terminator->getParentOp());
3088 if (!branch)
3089 return nullptr;
3091 branch.getSuccessorOperandInputMapping(mapping,
3092 RegionBranchPoint(terminator));
3093 auto it = mapping.find(&operand);
3094 if (it == mapping.end())
3095 return nullptr;
3096 xegpu::DistributeLayoutAttr iterArgLayout;
3097 for (auto arg : llvm::make_isa_range<BlockArgument>(it->second)) {
3098 xegpu::DistributeLayoutAttr layout = xegpu::getDistributeLayoutAttr(arg);
3099 assert((!iterArgLayout || !layout || iterArgLayout.isEqualTo(layout)) &&
3100 "region inputs fed by one terminator operand disagree on layout");
3101 if (!iterArgLayout)
3102 iterArgLayout = layout;
3103 }
3104 return iterArgLayout;
3105}
3106
3107// For the terminator of a region op that carries nothing back into its regions
3108// (scf.if), returns the layout of the parent result the operand feeds.
3109static xegpu::DistributeLayoutAttr getParentResultLayoutForYieldOperand(
3110 RegionBranchTerminatorOpInterface terminator, OpOperand &operand) {
3111 auto branch = dyn_cast<RegionBranchOpInterface>(terminator->getParentOp());
3112 if (!branch)
3113 return nullptr;
3115 branch.getSuccessorOperandInputMapping(mapping,
3116 RegionBranchPoint(terminator));
3117 auto it = mapping.find(&operand);
3118 if (it == mapping.end())
3119 return nullptr;
3120 for (Value input : it->second)
3121 if (auto result = dyn_cast<OpResult>(input))
3123 return nullptr;
3124}
3125
3126/// Returns the layout required on `operand`: anchor ops report their declared
3127/// per-operand layout directly; non-anchor ops back-derive it from their result
3128/// layout via inferSourceLayoutFromResultForNonAnchorOp.
3129xegpu::DistributeLayoutAttr xegpu::getConsumerLayoutAt(OpOperand &operand) {
3130 Operation *op = operand.getOwner();
3131 // Anchor ops declare the layout they
3132 // require on each operand. Trust that declaration directly so that
3133 // ResolveLayoutConflicts compares producer-vs-declared
3134 if (isa<xegpu::AnchorLayoutInterface>(op))
3135 return xegpu::getDistributeLayoutAttr(operand);
3136 // Region ops with forwarded operands (scf.for's and scf.while's inits) carry
3137 // the required operand layout as the layout_operand_N that
3138 // propagateRegionArgsToInits back-propagated from the region argument. Do not
3139 // re-derive it from that argument here: conflict resolution inserts
3140 // convert_layout ops as it walks, rewriting the argument's uses, so what
3141 // those uses require depends on how far the walk has progressed.
3142 if (isa<RegionBranchOpInterface>(op))
3143 return xegpu::getDistributeLayoutAttr(operand);
3144 // A region terminator requires the layout of the successor input its operand
3145 // feeds: the region iter_arg for a loop, and the parent result for a region
3146 // op with no loop-carried values (scf.if).
3147 if (auto terminator = dyn_cast<RegionBranchTerminatorOpInterface>(op)) {
3148 if (isa<LoopLikeOpInterface>(op->getParentOp()))
3149 return getLoopCarriedLayoutForYieldOperand(terminator, operand);
3150 return getParentResultLayoutForYieldOperand(terminator, operand);
3151 }
3152 // For non-anchor ops, derive the operand layout from the op's result
3153 // layout via op-specific semantics.
3154 xegpu::DistributeLayoutAttr resLayout;
3155 if (op->getNumResults() == 1 || isa<vector::DeinterleaveOp>(op))
3156 resLayout = xegpu::getDistributeLayoutAttr(op->getResult(0));
3157 return inferSourceLayoutFromResultForNonAnchorOp(operand, resLayout);
3158}
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
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 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 > > 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::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.
static bool isContiguousTiling(ArrayRef< int64_t > dimGroup, ArrayRef< int64_t > shape, ArrayRef< int64_t > counts, ArrayRef< int64_t > tileShape, int64_t expectedStride)
Checks that the tiles a split group is cut into walk the collapsed source dim with one constant strid...
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 std::tuple< SmallVector< int64_t >, SmallVector< int64_t >, SmallVector< int64_t > > compute2DBlockIOLaneLayout(ArrayRef< int64_t > instShape, int64_t subgroupSize, int64_t bitwidth, int64_t packingSize, bool transform=false, bool transpose=false)
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 std::tuple< SmallVector< int64_t >, SmallVector< int64_t >, SmallVector< int64_t > > computeReductionLaneLayoutAndData(ArrayRef< int64_t > srcShape, ArrayRef< int64_t > reductionDims, int subgroupSize, int64_t maxReduceVectorSize, xegpu::DistributeLayoutAttr consumerLayout=nullptr)
Computes a multi_reduction's source layout as (lane_layout, lane_data, inst_data),...
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:34
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
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
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 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 setupShapeCastResultLayout(LayoutKind layoutKind, VectorType srcVectorTy, VectorType resVectorTy, DistributeLayoutAttr consumerLayout)
Sets up the result layout for a shape cast that splits one source dim into several consecutive result...
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.
int64_t computeProduct(ArrayRef< int64_t > basis)
Self-explicit.
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:185
int32_t getMaxLaneAccessSizeBytes() const override
Definition uArchBase.h:226
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:231
virtual unsigned getGeneralPackedFormatBitSize() const =0
virtual int getSubgroupSize() const =0
const Instruction * getInstruction(InstructionKind instKind) const
Definition uArchBase.h:115