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