blob: 895d7facad6dfb3af733706b5335128903ae7b16 [file] [edit]
//===- XeGPUPropagateLayout.cpp - XeGPU Layout Propagation ------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
#include "mlir/Analysis/DataFlow/SparseAnalysis.h"
#include "mlir/Analysis/DataFlow/Utils.h"
#include "mlir/Analysis/DataFlowFramework.h"
#include "mlir/Dialect/GPU/IR/GPUDialect.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/Vector/IR/VectorOps.h"
#include "mlir/Dialect/XeGPU/IR/XeGPU.h"
#include "mlir/Dialect/XeGPU/Transforms/Passes.h"
#include "mlir/Dialect/XeGPU/Transforms/XeGPULayoutImpl.h"
#include "mlir/Dialect/XeGPU/Utils/XeGPUUtils.h"
#include "mlir/Dialect/XeGPU/uArch/uArchCommon.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/Value.h"
#include "mlir/IR/Visitors.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/FunctionInterfaces.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/LogicalResult.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include <limits>
namespace mlir {
namespace xegpu {
#define GEN_PASS_DEF_XEGPUPROPAGATELAYOUT
#include "mlir/Dialect/XeGPU/Transforms/Passes.h.inc"
} // namespace xegpu
} // namespace mlir
#define DEBUG_TYPE "xegpu-propagate-layout"
#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "]: ")
using namespace mlir;
using namespace mlir::dataflow;
namespace {
//===----------------------------------------------------------------------===//
// LayoutInfo
//===----------------------------------------------------------------------===//
/// Helper class for tracking the analysis state of an mlir value. For layout
/// propagation, the analysis state is simply the distribution layout of
/// each value. The distribution layout information is encapsulated using
/// xegpu::DistributeLayoutAttr class which can hold information about any type
/// of distribution layout that XeGPU dialect supports. Purpose of this analysis
/// to propagate some unique distribution layout for each value in the program
/// starting from a set of anchor operations (like DPAS, StoreNd, etc.). Note
/// that analysis will reach a fixed point when all values are reached some
/// layout and, analysis does not try to modify any already assigned layouts.
///
/// Given this, LayoutInfo satisifies the following properties:
/// 1) A LayoutInfo value can be in one of two states - `assigned` or `not
/// assigned`.
/// 2) Two LayoutInfo values are equal if they are both not assigned, or both
/// assigned with the same layout.
/// 3) The meet operator works as follows:
/// - If only one side is assigned, return that side.
/// - If both sides are assigned, prefer the layout demanded by the user
/// that is nearer to the producer in program order (smaller
/// `programOrder`); on a tie keep lhs.
///
/// The `programOrder` field records the program-order index of the consumer op
/// that demanded the layout (stamped via
/// `LayoutInfoPropagation::makeLayoutInfo` from
/// `LayoutInfoPropagation::currentProgramOrder`). During this backward analysis
/// a value can be demanded by several users; keeping the nearest one tends to
/// preserve a consumer's layout as far up the def chain as possible, minimizing
/// layout conversions. This is a hint, not an optimum. `programOrder` is never
/// propagated up the chain - each visited op stamps its own index - so it is
/// excluded from `operator==`.
struct LayoutInfo {
private:
xegpu::DistributeLayoutAttr storage = nullptr;
// Program-order index of the consumer op that demanded this layout. Smaller
// means nearer to the producer. Unassigned/unknown demands sort last.
int64_t programOrder = std::numeric_limits<int64_t>::max();
public:
LayoutInfo() = default;
LayoutInfo(const xegpu::DistributeLayoutAttr &layout, int64_t programOrder)
: storage(layout), programOrder(programOrder) {}
// Equality by assignment state and, when both assigned, by the layout:
// - one assigned, the other not -> not equal;
// - both unassigned -> equal;
// - both assigned -> equal iff the layouts match.
bool operator==(const LayoutInfo &other) const {
if (isAssigned() != other.isAssigned())
return false;
if (!isAssigned())
return true;
return storage.isEqualTo(other.storage);
}
static LayoutInfo meet(const LayoutInfo &lhs, const LayoutInfo &rhs);
static LayoutInfo join(const LayoutInfo &lhs, const LayoutInfo &rhs);
void print(raw_ostream &os) const;
bool isAssigned() const { return storage != nullptr; }
SmallVector<int> getLaneLayout() const;
SmallVector<int> getLaneData() const;
SmallVector<int> getInstData() const;
SmallVector<int> getSgLayout() const;
SmallVector<int> getSgData() const;
SmallVector<int> getOrder() const;
bool isSliceLayout() const {
if (!isAssigned())
return false;
return isa<xegpu::SliceAttr>(storage);
}
int64_t getRank() const {
if (!isAssigned())
return -1;
return storage.getRank();
}
Attribute get() { return storage; }
void set(const xegpu::DistributeLayoutAttr &layout) { storage = layout; }
};
void LayoutInfo::print(raw_ostream &os) const {
if (isAssigned()) {
os << storage;
} else {
os << "Not assigned.";
}
}
LayoutInfo LayoutInfo::meet(const LayoutInfo &lhs, const LayoutInfo &rhs) {
if (!lhs.isAssigned())
return rhs;
if (!rhs.isAssigned())
return lhs;
// Prefer the demand from the user nearer to the producer in program order.
// Distinct users always have distinct indices, so this decides every
// real conflict; on a tie (same op, or both unknown) keep lhs.
if (rhs.programOrder < lhs.programOrder)
return rhs;
return lhs;
}
/// Since this is a backward analysis, join method is not used.
LayoutInfo LayoutInfo::join(const LayoutInfo &lhs, const LayoutInfo &rhs) {
llvm_unreachable("Join should not be triggered by layout propagation.");
}
//===----------------------------------------------------------------------===//
// LayoutInfoLattice
//===----------------------------------------------------------------------===//
/// Lattice holding the LayoutInfo for each value.
struct LayoutInfoLattice : public Lattice<LayoutInfo> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LayoutInfoLattice)
using Lattice::Lattice;
};
//===----------------------------------------------------------------------===//
// LayoutInfoPropagation
//===----------------------------------------------------------------------===//
/// Backward data flow analysis to propagate the lane_layout and lane_data of
/// each value in the program. Currently, the layouts for operands DPAS,
/// StoreNd, and StoreScatter are fixed (known before propagation). Purpose of
/// this analysis is to propagate those known layouts to all their producers and
/// (other) consumers.
class LayoutInfoPropagation
: public SparseBackwardDataFlowAnalysis<LayoutInfoLattice> {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LayoutInfoPropagation)
private:
xegpu::LayoutKind layoutKind;
unsigned indexBitWidth;
// Program-order index of every op, built lazily on first use via a pre-order
// walk of the top-level module/function (matching printed-IR order). Used to
// tell which consumer of a value is nearer to its producer.
DenseMap<Operation *, int64_t> programOrder;
// Returns the program-order index of `op`, populating `programOrder` from
// `op`'s top-level ancestor on first call.
int64_t getProgramOrder(Operation *op);
int64_t currentProgramOrder = std::numeric_limits<int64_t>::max();
LayoutInfo makeLayoutInfo(const xegpu::DistributeLayoutAttr &layout) {
return LayoutInfo(layout, currentProgramOrder);
}
void visitDpasOp(xegpu::DpasOp dpas, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitDpasMxOp(xegpu::DpasMxOp dpasMx,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitStoreNdOp(xegpu::StoreNdOp store,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitStoreScatterOp(xegpu::StoreScatterOp storeScatter,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitLoadNdOp(xegpu::LoadNdOp load,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitLoadGatherOp(xegpu::LoadGatherOp load,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitTransposeOp(vector::TransposeOp transpose,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitVectorBitcastOp(vector::BitCastOp bitcast,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitVectorInterleaveOp(vector::InterleaveOp interleave,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitVectorDeinterleaveOp(vector::DeinterleaveOp deinterleave,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitPrefetchNdOp(xegpu::PrefetchNdOp prefetch,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitVectorMultiReductionOp(vector::MultiDimReductionOp reduction,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitVectorReductionOp(vector::ReductionOp reduction,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitVectorBroadCastOp(vector::BroadcastOp broadcast,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitShapeCastOp(vector::ShapeCastOp shapeCast,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void
visitInsertStridedSliceOp(vector::InsertStridedSliceOp insertStridedSlice,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitLoadMatrixOp(xegpu::LoadMatrixOp load,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitStoreMatrixOp(xegpu::StoreMatrixOp store,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitLoadGatherOp(xegpu::LoadMatrixOp load,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitStoreScatterOp(xegpu::StoreMatrixOp store,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
void visitConvertLayoutOp(xegpu::ConvertLayoutOp convertLayout,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results);
bool hasParamsOfLayoutKind(xegpu::DistributeLayoutAttr anchorLayout);
// Number of subgroups for `op`. Missing count is fatal in subgroup mode.
FailureOr<int64_t> getNumSgOrFail(Operation *op, int sgSize,
xegpu::DistributeLayoutAttr consumerLayout);
// Channel to surface hard failures out of the void visit callbacks.
bool propagationFailed = false;
// Reserved for the anchor ops that are the sources of the propagation
// (store/dpas), whose layout must be correct.
void markFailure(Operation *op, const llvm::Twine &message) {
op->emitError(message);
propagationFailed = true;
}
public:
bool hasFailed() const { return propagationFailed; }
LayoutInfoPropagation(DataFlowSolver &solver,
SymbolTableCollection &symbolTable,
xegpu::LayoutKind layoutKind, unsigned indexBitWidth)
: SparseBackwardDataFlowAnalysis(solver, symbolTable),
layoutKind(layoutKind), indexBitWidth(indexBitWidth) {}
using SparseBackwardDataFlowAnalysis::SparseBackwardDataFlowAnalysis;
LogicalResult
visitOperation(Operation *op, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) override;
void visitBranchOperand(OpOperand &operand) override {};
void visitCallOperand(OpOperand &operand) override {};
void
visitNonControlFlowArguments(RegionSuccessor &successor,
ArrayRef<BlockArgument> arguments) override {};
void visitExternalCall(CallOpInterface call,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) override {
};
void setToExitState(LayoutInfoLattice *lattice) override {
(void)lattice->meet(LayoutInfo());
}
};
} // namespace
int64_t LayoutInfoPropagation::getProgramOrder(Operation *op) {
auto it = programOrder.find(op);
if (it != programOrder.end())
return it->second;
// First time we see this op's tree: number every op under its top-level
// ancestor in pre-order (i.e. printed-IR order). Nested ops (e.g. inside an
// scf.for body) get an index between their parent and the parent's next
// sibling, so a use inside a loop is "nearer" than a use after it.
Operation *root = op;
while (root->getParentOp())
root = root->getParentOp();
int64_t counter = 0;
root->walk<WalkOrder::PreOrder>(
[&](Operation *o) { programOrder[o] = counter++; });
return programOrder.lookup(op);
}
LogicalResult LayoutInfoPropagation::visitOperation(
Operation *op, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// Stamp demands pushed by this op with its program-order index so `meet` can
// prefer the nearest consumer.
currentProgramOrder = getProgramOrder(op);
TypeSwitch<Operation *>(op)
.Case(
[&](xegpu::DpasOp dpasOp) { visitDpasOp(dpasOp, operands, results); })
.Case([&](xegpu::DpasMxOp dpasMxOp) {
visitDpasMxOp(dpasMxOp, operands, results);
})
.Case([&](xegpu::StoreNdOp storeNdOp) {
visitStoreNdOp(storeNdOp, operands, results);
})
.Case([&](xegpu::StoreScatterOp storeScatterOp) {
visitStoreScatterOp(storeScatterOp, operands, results);
})
.Case([&](xegpu::LoadNdOp loadNdOp) {
visitLoadNdOp(loadNdOp, operands, results);
})
.Case([&](xegpu::LoadGatherOp loadGatherOp) {
visitLoadGatherOp(loadGatherOp, operands, results);
})
.Case([&](xegpu::PrefetchNdOp prefetchNdOp) {
visitPrefetchNdOp(prefetchNdOp, operands, results);
})
.Case([&](vector::TransposeOp transposeOp) {
visitTransposeOp(transposeOp, operands, results);
})
.Case([&](vector::BitCastOp bitcastOp) {
visitVectorBitcastOp(bitcastOp, operands, results);
})
.Case([&](vector::InterleaveOp interleaveOp) {
visitVectorInterleaveOp(interleaveOp, operands, results);
})
.Case([&](vector::DeinterleaveOp deinterleaveOp) {
visitVectorDeinterleaveOp(deinterleaveOp, operands, results);
})
.Case([&](vector::MultiDimReductionOp reductionOp) {
visitVectorMultiReductionOp(reductionOp, operands, results);
})
.Case([&](vector::ReductionOp reductionOp) {
visitVectorReductionOp(reductionOp, operands, results);
})
.Case([&](vector::BroadcastOp broadcastOp) {
visitVectorBroadCastOp(broadcastOp, operands, results);
})
.Case([&](vector::ShapeCastOp shapeCastOp) {
visitShapeCastOp(shapeCastOp, operands, results);
})
.Case([&](vector::InsertStridedSliceOp insertStridedSliceOp) {
visitInsertStridedSliceOp(insertStridedSliceOp, operands, results);
})
.Case([&](xegpu::LoadMatrixOp loadMatrixOp) {
visitLoadMatrixOp(loadMatrixOp, operands, results);
})
.Case([&](xegpu::StoreMatrixOp storeMatrixOp) {
visitStoreMatrixOp(storeMatrixOp, operands, results);
})
.Case([&](xegpu::ConvertLayoutOp convertLayoutOp) {
visitConvertLayoutOp(convertLayoutOp, operands, results);
})
// All other ops.
.Default([&](Operation *op) {
for (const LayoutInfoLattice *resultInfo : results) {
if (!resultInfo->getValue().isAssigned())
continue;
for (auto [operandInfo, operand] :
llvm::zip(operands, op->getOpOperands())) {
// If the operand type is not a vector or tensor descriptor, skip
// it.
if (!isa<xegpu::TensorDescType, VectorType>(
operand.get().getType()))
continue;
// Propagate the result layout to the operand.
meet(operandInfo, *resultInfo);
}
}
});
return success();
}
bool LayoutInfoPropagation::hasParamsOfLayoutKind(
xegpu::DistributeLayoutAttr anchorLayout) {
if (anchorLayout == nullptr) {
return false;
}
if (layoutKind == xegpu::LayoutKind::InstData) {
return !(anchorLayout.getEffectiveInstDataAsInt().empty());
}
if (layoutKind == xegpu::LayoutKind::Lane) {
return !(anchorLayout.getEffectiveLaneLayoutAsInt().empty() ||
anchorLayout.getEffectiveLaneDataAsInt().empty());
}
if (layoutKind == xegpu::LayoutKind::Subgroup) {
return !(anchorLayout.getEffectiveSgLayoutAsInt().empty() ||
anchorLayout.getEffectiveSgDataAsInt().empty());
}
return false;
}
FailureOr<int64_t> LayoutInfoPropagation::getNumSgOrFail(
Operation *op, int sgSize, xegpu::DistributeLayoutAttr consumerLayout) {
// The consumer's sg_layout, when present, dictates the count.
if (consumerLayout) {
auto sgLayout = consumerLayout.getEffectiveSgLayoutAsInt();
if (!sgLayout.empty())
return llvm::product_of(sgLayout);
}
// Otherwise fall back to the kernel's known_block_size.
if (auto gpuFunc = op->getParentOfType<gpu::GPUFuncOp>()) {
std::optional<ArrayRef<int32_t>> knownBlockSize =
gpuFunc.getKnownBlockSize();
if (knownBlockSize) {
bool isPowerOf2Block = llvm::all_of(*knownBlockSize, [](int32_t dim) {
return dim > 0 && llvm::isPowerOf2_32(dim);
});
int64_t numSg = llvm::product_of(*knownBlockSize) / sgSize;
if (isPowerOf2Block && numSg > 0)
return numSg;
}
}
// Only subgroup mode needs the count; elsewhere a missing one is benign.
if (layoutKind == xegpu::LayoutKind::Subgroup) {
markFailure(op, "Unable to determine the number of subgroups for the "
"operation. Please check @known_block_size is properly "
"attached as kernel attributes, with power-of-two "
"dimensions covering at least one subgroup.");
return failure();
}
return int64_t{0};
}
void LayoutInfoPropagation::visitPrefetchNdOp(
xegpu::PrefetchNdOp prefetch, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
LayoutInfo prefetchLayout;
const auto *uArch = xegpu::uArch::getUArch(getChipStr(prefetch).value_or(""));
if (!uArch)
return;
xegpu::DistributeLayoutAttr anchorLayout = prefetch.getLayoutAttr();
if (hasParamsOfLayoutKind(anchorLayout)) {
prefetchLayout = makeLayoutInfo(anchorLayout);
if (layoutKind == xegpu::LayoutKind::InstData) {
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::Subgroup2DBlockPrefetchInstruction>(
uArch->getInstruction(
xegpu::uArch::InstructionKind::Subgroup2DBlockPrefetch));
if (!uArchInstruction)
return;
auto completed = xegpu::completeBlockStoreLaneLayoutFromInstData(
anchorLayout, prefetch.getTensorDescType().getElementType(),
uArchInstruction, uArch->getSubgroupSize());
if (!completed) {
prefetch.emitWarning(
"Failed to identify lane layouts for the specified inst_data.");
return;
}
prefetch.setLayoutAttr(*completed);
prefetchLayout = makeLayoutInfo(*completed);
}
} else {
auto tdescTy = prefetch.getTensorDescType();
auto numSgOrErr =
getNumSgOrFail(prefetch, uArch->getSubgroupSize(), nullptr);
if (failed(numSgOrErr))
return;
auto layoutAttr = xegpu::setupPrefetchNdAnchorLayout(
layoutKind, tdescTy, numSgOrErr.value_or(0), uArch);
if (!layoutAttr) {
prefetch.emitWarning(
"Failed to determine required layout for prefetch_nd.");
return;
}
prefetchLayout = makeLayoutInfo(layoutAttr);
prefetch.setLayoutAttr(layoutAttr);
}
// Propagate the layout to the source tensor descriptor.
propagateIfChanged(operands[0], operands[0]->meet(prefetchLayout));
}
void LayoutInfoPropagation::visitVectorMultiReductionOp(
vector::MultiDimReductionOp reduction,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
Type resultTy = reduction.getDestType();
// The layout of the result must be present.
LayoutInfo resLayoutInfo = results[0]->getValue();
xegpu::DistributeLayoutAttr consumerLayoutAttr;
if (!resultTy.isIntOrFloat()) {
if (!resLayoutInfo.isAssigned())
return;
consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
}
VectorType sourceTy = reduction.getSourceVectorType();
SmallVector<int64_t> reductionDims(reduction.getReductionDims());
const auto *uArch =
xegpu::uArch::getUArch(xegpu::getChipStr(reduction).value_or(""));
if (!uArch)
return;
auto numSgOrErr =
getNumSgOrFail(reduction, uArch->getSubgroupSize(), consumerLayoutAttr);
if (failed(numSgOrErr))
return;
// The result layout represents the layout requirements of the operation.
// it is recorded to anchor layout or temporary layout.
// it must be honored for current op and may conflict with the layout
// propagated from consumer op, the conflict is resolved in later phase by
// converting the required result layout to the consumer layout
auto requiredResLayoutAttr = xegpu::setupMultiReductionResultLayout(
layoutKind, sourceTy, consumerLayoutAttr, reductionDims,
numSgOrErr.value_or(0), uArch);
xegpu::setTemporaryLayout(reduction->getResult(0), requiredResLayoutAttr);
// derive the source layout from the dominant layout and reduction dims
auto srcLayoutAttr = xegpu::inferMultiReductionSourceLayout(
requiredResLayoutAttr, reductionDims);
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
// Accumulator should have the same layout as the result.
propagateIfChanged(operands[1],
operands[1]->meet(makeLayoutInfo(requiredResLayoutAttr)));
}
void LayoutInfoPropagation::visitVectorReductionOp(
vector::ReductionOp reduction, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
VectorType sourceTy = reduction.getSourceVectorType();
const auto *uArch =
xegpu::uArch::getUArch(xegpu::getChipStr(reduction).value_or(""));
if (!uArch)
return;
auto requiredResLayoutAttr =
xegpu::setupReductionResultLayout(layoutKind, sourceTy, uArch);
xegpu::setTemporaryLayout(reduction->getResult(0), requiredResLayoutAttr);
auto srcLayoutAttr = xegpu::inferReductionSourceLayout(requiredResLayoutAttr);
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
if (reduction.getAcc())
propagateIfChanged(
operands[1], operands[1]->meet(makeLayoutInfo(requiredResLayoutAttr)));
}
void LayoutInfoPropagation::visitVectorBroadCastOp(
vector::BroadcastOp broadcast, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// The layout of the result must be present.
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
// Only consider vector to vector broadcasts for now.
VectorType resultTy = broadcast.getResultVectorType();
VectorType sourceTy = dyn_cast<VectorType>(broadcast.getSourceType());
// skip layout propagation for non-vector source operand.
if (!sourceTy)
return;
auto srcShape = sourceTy.getShape();
auto resShape = resultTy.getShape();
auto resultLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
xegpu::DistributeLayoutAttr srcLayoutAttr =
xegpu::inferBroadcastSourceLayout(resultLayoutAttr, resShape, srcShape);
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
}
void LayoutInfoPropagation::visitShapeCastOp(
vector::ShapeCastOp shapeCast, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// The layout of the result must be present.
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
ArrayRef<int64_t> resShape = shapeCast.getResultVectorType().getShape();
ArrayRef<int64_t> srcShape = shapeCast.getSourceVectorType().getShape();
auto resultLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
xegpu::DistributeLayoutAttr srcLayoutAttr =
xegpu::inferShapeCastSourceLayout(resultLayoutAttr, resShape, srcShape);
// shape_cast is not an anchor op: another consumer of the source value may
// still supply a valid layout, so warn instead of stopping the propagation.
if (!srcLayoutAttr) {
shapeCast.emitWarning("Failed to infer source layout for shape_cast; "
"unsupported shape-cast pattern.");
return;
}
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
}
/// Set the layouts for DPAS A, B, and C operands.
void LayoutInfoPropagation::visitDpasOp(
xegpu::DpasOp dpas, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
LayoutInfo dpasALayout;
LayoutInfo dpasBLayout;
LayoutInfo dpasCDLayout;
const auto *uArch = xegpu::uArch::getUArch(getChipStr(dpas).value_or(""));
if (!uArch)
return;
VectorType aTy = dpas.getLhsType();
VectorType bTy = dpas.getRhsType();
VectorType cdTy = dpas.getResultType();
xegpu::DistributeLayoutAttr anchorLayoutCD = dpas.getLayoutCdAttr();
if (hasParamsOfLayoutKind(anchorLayoutCD)) {
xegpu::DistributeLayoutAttr anchorLayoutA = dpas.getLayoutAAttr();
xegpu::DistributeLayoutAttr anchorLayoutB = dpas.getLayoutBAttr();
assert(hasParamsOfLayoutKind(anchorLayoutA) &&
"Expected anchor layout for DPAS A operand.");
assert(hasParamsOfLayoutKind(anchorLayoutB) &&
"Expected anchor layout for DPAS B operand.");
dpasALayout = makeLayoutInfo(anchorLayoutA);
dpasBLayout = makeLayoutInfo(anchorLayoutB);
dpasCDLayout = makeLayoutInfo(anchorLayoutCD);
if (layoutKind == xegpu::LayoutKind::InstData) {
auto completed = xegpu::completeDpasLaneLayoutFromInstData(
anchorLayoutA, anchorLayoutB, anchorLayoutCD, aTy, bTy, cdTy, uArch);
if (!completed) {
markFailure(
dpas,
"Failed to identify lane layouts for the specified inst_data.");
return;
}
auto [completedA, completedB, completedCD] = *completed;
dpas.setLayoutAAttr(completedA);
dpas.setLayoutBAttr(completedB);
dpas.setLayoutCdAttr(completedCD);
dpasALayout = makeLayoutInfo(completedA);
dpasBLayout = makeLayoutInfo(completedB);
dpasCDLayout = makeLayoutInfo(completedCD);
}
} else {
xegpu::DistributeLayoutAttr consumerLayoutAttr = nullptr;
xegpu::DistributeLayoutAttr requiredCDLayoutAttr, requiredALayout,
requiredBLayout;
LayoutInfo consumerLayout = results[0]->getValue();
if (!consumerLayout.isAssigned())
return;
consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(consumerLayout.get());
auto numSgOrErr =
getNumSgOrFail(dpas, uArch->getSubgroupSize(), consumerLayoutAttr);
if (failed(numSgOrErr))
return;
auto layouts =
xegpu::setupDpasLayout(layoutKind, aTy, bTy, cdTy, consumerLayoutAttr,
numSgOrErr.value_or(0), uArch);
if (!layouts.has_value()) {
markFailure(dpas,
"Failed to determine required layouts for DPAS operands.");
return;
}
std::tie(requiredALayout, requiredBLayout, requiredCDLayoutAttr) = *layouts;
dpas.setLayoutAAttr(requiredALayout);
dpas.setLayoutBAttr(requiredBLayout);
dpas.setLayoutCdAttr(requiredCDLayoutAttr);
dpasALayout = makeLayoutInfo(requiredALayout);
dpasBLayout = makeLayoutInfo(requiredBLayout);
dpasCDLayout = makeLayoutInfo(requiredCDLayoutAttr);
}
propagateIfChanged(operands[0], operands[0]->meet(dpasALayout));
propagateIfChanged(operands[1], operands[1]->meet(dpasBLayout));
if (operands.size() > 2)
propagateIfChanged(operands[2], operands[2]->meet(dpasCDLayout));
}
/// Propagate layout for DpasMxOp operands using the layout attributes.
/// DpasMxOp has operands: a, b, acc (optional), scale_a (optional), scale_b
/// (optional)
void LayoutInfoPropagation::visitDpasMxOp(
xegpu::DpasMxOp dpasMx, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// Initialize layout variables
LayoutInfo dpasMxALayout, dpasMxBLayout, dpasMxCDLayout;
LayoutInfo dpasMxAScaleLayout, dpasMxBScaleLayout;
// Get existing layout attributes from the operation
xegpu::DistributeLayoutAttr anchorLayoutA = dpasMx.getLayoutAAttr();
xegpu::DistributeLayoutAttr anchorLayoutB = dpasMx.getLayoutBAttr();
xegpu::DistributeLayoutAttr anchorLayoutCD = dpasMx.getLayoutCdAttr();
const auto *uArch = xegpu::uArch::getUArch(getChipStr(dpasMx).value_or(""));
if (!uArch)
return;
VectorType aTy = dpasMx.getAType();
VectorType bTy = dpasMx.getBType();
VectorType cdTy = dpasMx.getResultType();
// Get scale types if present
VectorType aScaleTy;
VectorType bScaleTy;
Value scaleA = dpasMx.getScaleA();
Value scaleB = dpasMx.getScaleB();
if (scaleA)
aScaleTy = dyn_cast<VectorType>(scaleA.getType());
if (scaleB)
bScaleTy = dyn_cast<VectorType>(scaleB.getType());
// Check if all layouts are already set
if (anchorLayoutA && anchorLayoutB && anchorLayoutCD &&
hasParamsOfLayoutKind(anchorLayoutA) &&
hasParamsOfLayoutKind(anchorLayoutB) &&
hasParamsOfLayoutKind(anchorLayoutCD)) {
dpasMxALayout = makeLayoutInfo(anchorLayoutA);
dpasMxBLayout = makeLayoutInfo(anchorLayoutB);
dpasMxCDLayout = makeLayoutInfo(anchorLayoutCD);
// Get scale layouts if available
xegpu::DistributeLayoutAttr anchorLayoutAScale =
dpasMx.getLayoutAScaleAttr();
xegpu::DistributeLayoutAttr anchorLayoutBScale =
dpasMx.getLayoutBScaleAttr();
if (anchorLayoutAScale)
dpasMxAScaleLayout = makeLayoutInfo(anchorLayoutAScale);
if (anchorLayoutBScale)
dpasMxBScaleLayout = makeLayoutInfo(anchorLayoutBScale);
if (layoutKind == xegpu::LayoutKind::InstData) {
auto completed = xegpu::completeDpasMxLaneLayoutFromInstData(
anchorLayoutA, anchorLayoutB, anchorLayoutCD, aTy, bTy, cdTy,
aScaleTy, bScaleTy, uArch);
if (!completed) {
markFailure(
dpasMx,
"Failed to identify lane layouts for the specified inst_data.");
return;
}
auto [completedA, completedB, completedCD, completedAScale,
completedBScale] = *completed;
dpasMx.setLayoutAAttr(completedA);
dpasMx.setLayoutBAttr(completedB);
dpasMx.setLayoutCdAttr(completedCD);
dpasMxALayout = makeLayoutInfo(completedA);
dpasMxBLayout = makeLayoutInfo(completedB);
dpasMxCDLayout = makeLayoutInfo(completedCD);
if (completedAScale) {
dpasMx.setLayoutAScaleAttr(completedAScale);
dpasMxAScaleLayout = makeLayoutInfo(completedAScale);
}
if (completedBScale) {
dpasMx.setLayoutBScaleAttr(completedBScale);
dpasMxBScaleLayout = makeLayoutInfo(completedBScale);
}
}
} else {
xegpu::DistributeLayoutAttr consumerLayoutAttr = nullptr;
xegpu::DistributeLayoutAttr requiredCDLayoutAttr, requiredALayout,
requiredBLayout, requiredAScaleLayout, requiredBScaleLayout;
LayoutInfo consumerLayout = results[0]->getValue();
if (!consumerLayout.isAssigned())
return;
consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(consumerLayout.get());
auto numSgOrErr =
getNumSgOrFail(dpasMx, uArch->getSubgroupSize(), consumerLayoutAttr);
if (failed(numSgOrErr))
return;
auto layouts = xegpu::setupDpasMxLayout(
layoutKind, aTy, bTy, cdTy, aScaleTy, bScaleTy, consumerLayoutAttr,
numSgOrErr.value_or(0), uArch);
if (!layouts.has_value()) {
markFailure(dpasMx,
"Failed to determine required layouts for DPAS_MX operands.");
return;
}
std::tie(requiredALayout, requiredBLayout, requiredCDLayoutAttr,
requiredAScaleLayout, requiredBScaleLayout) = *layouts;
dpasMx.setLayoutAAttr(requiredALayout);
dpasMx.setLayoutBAttr(requiredBLayout);
dpasMx.setLayoutCdAttr(requiredCDLayoutAttr);
if (requiredAScaleLayout)
dpasMx.setLayoutAScaleAttr(requiredAScaleLayout);
if (requiredBScaleLayout)
dpasMx.setLayoutBScaleAttr(requiredBScaleLayout);
dpasMxALayout = makeLayoutInfo(requiredALayout);
dpasMxBLayout = makeLayoutInfo(requiredBLayout);
dpasMxCDLayout = makeLayoutInfo(requiredCDLayoutAttr);
if (requiredAScaleLayout)
dpasMxAScaleLayout = makeLayoutInfo(requiredAScaleLayout);
if (requiredBScaleLayout)
dpasMxBScaleLayout = makeLayoutInfo(requiredBScaleLayout);
}
// Propagate layouts to operands. Because acc, scale_a, scale_b are all
// optional (AttrSizedOperandSegments), the index of each present operand in
// `operands` depends on which optionals are actually supplied. Use the
// op's accessors to determine the correct positional index.
propagateIfChanged(operands[0], operands[0]->meet(dpasMxALayout));
propagateIfChanged(operands[1], operands[1]->meet(dpasMxBLayout));
unsigned idx = 2;
if (dpasMx.getAcc()) {
propagateIfChanged(operands[idx], operands[idx]->meet(dpasMxCDLayout));
++idx;
}
if (dpasMx.getScaleA()) {
if (dpasMxAScaleLayout.isAssigned())
propagateIfChanged(operands[idx],
operands[idx]->meet(dpasMxAScaleLayout));
++idx;
}
if (dpasMx.getScaleB()) {
if (dpasMxBScaleLayout.isAssigned())
propagateIfChanged(operands[idx],
operands[idx]->meet(dpasMxBScaleLayout));
++idx;
}
}
/// Set the layout for the value and tensor descriptor operands in StoreNdOp.
void LayoutInfoPropagation::visitStoreNdOp(
xegpu::StoreNdOp store, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
LayoutInfo storeLayout;
const auto *uArch = xegpu::uArch::getUArch(getChipStr(store).value_or(""));
if (!uArch)
return;
xegpu::DistributeLayoutAttr anchorLayout = store.getLayoutAttr();
if (hasParamsOfLayoutKind(anchorLayout)) {
storeLayout = makeLayoutInfo(anchorLayout);
if (layoutKind == xegpu::LayoutKind::InstData) {
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::Subgroup2DBlockStoreInstruction>(
uArch->getInstruction(
xegpu::uArch::InstructionKind::Subgroup2DBlockStore));
if (!uArchInstruction)
return;
auto completed = xegpu::completeBlockStoreLaneLayoutFromInstData(
anchorLayout, store.getValueType().getElementType(), uArchInstruction,
uArch->getSubgroupSize());
if (!completed) {
markFailure(
store,
"Failed to identify lane layouts for the specified inst_data.");
return;
}
store.setLayoutAttr(*completed);
storeLayout = makeLayoutInfo(*completed);
}
} else {
auto numSgOrErr = getNumSgOrFail(store, uArch->getSubgroupSize(), nullptr);
if (failed(numSgOrErr))
return;
auto layoutAttr = xegpu::setupStoreNdAnchorLayout(
layoutKind, store.getValueType(), numSgOrErr.value_or(0), uArch);
if (!layoutAttr) {
markFailure(store, "Failed to determine required layout for store_nd.");
return;
}
storeLayout = makeLayoutInfo(layoutAttr);
store.setLayoutAttr(layoutAttr);
}
// Propagate the layout to the value operand.
// Both operands should have the same layout
for (LayoutInfoLattice *operand : operands)
propagateIfChanged(operand, operand->meet(storeLayout));
}
/// Propagate the layout of the value to the tensor descriptor operand in
/// LoadNdOp.
void LayoutInfoPropagation::visitLoadNdOp(
xegpu::LoadNdOp load, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
LayoutInfo loadLayout;
const auto *uArch = xegpu::uArch::getUArch(getChipStr(load).value_or(""));
if (!uArch)
return;
LayoutInfo valueLayout = results[0]->getValue();
if (!valueLayout.isAssigned())
return;
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(valueLayout.get());
xegpu::DistributeLayoutAttr anchorLayout = load.getLayoutAttr();
if (hasParamsOfLayoutKind(anchorLayout)) {
loadLayout = makeLayoutInfo(anchorLayout);
if (layoutKind == xegpu::LayoutKind::InstData &&
!consumerLayoutAttr.getEffectiveLaneLayoutAsInt().empty()) {
const auto *uArchInstruction =
dyn_cast<xegpu::uArch::Subgroup2DBlockLoadInstruction>(
uArch->getInstruction(
xegpu::uArch::InstructionKind::Subgroup2DBlockLoad));
if (!uArchInstruction)
return;
auto completed = xegpu::completeBlockLoadLaneLayoutFromInstData(
anchorLayout, consumerLayoutAttr, load.getType().getElementType(),
uArchInstruction, uArch->getSubgroupSize());
if (!completed) {
load.emitWarning(
"Failed to identify lane layouts for the specified inst_data.");
return;
}
load.setLayoutAttr(*completed);
loadLayout = makeLayoutInfo(*completed);
}
} else {
auto numSgOrErr =
getNumSgOrFail(load, uArch->getSubgroupSize(), consumerLayoutAttr);
if (failed(numSgOrErr))
return;
auto layoutAttr = xegpu::setupLoadNdAnchorLayout(
layoutKind, load.getType(), consumerLayoutAttr, numSgOrErr.value_or(0),
uArch);
if (!layoutAttr) {
load.emitWarning("Failed to determine required layout for load_nd.");
return;
}
loadLayout = makeLayoutInfo(layoutAttr);
load.setLayoutAttr(layoutAttr);
}
// Propagate the new layout to the tensor descriptor operand.
propagateIfChanged(operands[0], operands[0]->meet(loadLayout));
}
/// Propagate the layout of the value to the tensor descriptor operand in
/// ConvertLayoutOp.
void LayoutInfoPropagation::visitConvertLayoutOp(
xegpu::ConvertLayoutOp convert, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
LayoutInfo resultLayout = results[0]->getValue();
// TODO: fix if one of the layouts is a slice layout
auto targetLayoutAttr =
dyn_cast<xegpu::LayoutAttr>(convert.getTargetLayoutAttr());
// input_layout is optional, so it may be null.
auto inputLayoutAttr =
dyn_cast_if_present<xegpu::LayoutAttr>(convert.getInputLayoutAttr());
// The result's propagated layout is authoritative for the converted value.
// Fill the lane_layout / lane_data / order parameters the target_layout is
// missing from it (sg_layout / sg_data / inst_data are left as-is), so the
// target stays consistent with what is actually propagated downstream.
auto resultLayoutAttr = resultLayout.isAssigned()
? dyn_cast<xegpu::LayoutAttr>(resultLayout.get())
: nullptr;
if (resultLayoutAttr && targetLayoutAttr) {
if (layoutKind == xegpu::LayoutKind::InstData &&
!targetLayoutAttr.getLaneLayout()) {
targetLayoutAttr = xegpu::LayoutAttr::get(
convert.getContext(), targetLayoutAttr.getSgLayout(),
targetLayoutAttr.getSgData(), targetLayoutAttr.getInstData(),
resultLayoutAttr.getLaneLayout(), resultLayoutAttr.getLaneData(),
resultLayoutAttr.getOrder());
convert.setTargetLayoutAttr(targetLayoutAttr);
}
}
// Fill only the lane_layout / lane_data / order parameters the input_layout
// is missing from the target_layout (sg_layout / sg_data / inst_data are left
// as-is), so the producer side receives a fully-populated lane layout.
if (inputLayoutAttr && targetLayoutAttr) {
if (layoutKind == xegpu::LayoutKind::InstData &&
!inputLayoutAttr.getLaneLayout()) {
auto merged = xegpu::LayoutAttr::get(
convert.getContext(), inputLayoutAttr.getSgLayout(),
inputLayoutAttr.getSgData(), inputLayoutAttr.getInstData(),
targetLayoutAttr.getLaneLayout(), targetLayoutAttr.getLaneData(),
targetLayoutAttr.getOrder());
convert.setInputLayoutAttr(merged);
}
}
xegpu::DistributeLayoutAttr anchorLayout = convert.getEffectiveInputLayout();
LayoutInfo convertLayout = makeLayoutInfo(anchorLayout);
// Propagate the new layout to the tensor descriptor operand.
propagateIfChanged(operands[0], operands[0]->meet(convertLayout));
}
/// For vector::TransposeOp, the layout of the result is transposed and
/// propagated to the operand.
void LayoutInfoPropagation::visitTransposeOp(
vector::TransposeOp transpose, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// Need the layout of transpose result to propagate to the operands.
LayoutInfo resultLayout = results[0]->getValue();
if (!resultLayout.isAssigned())
return;
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resultLayout.get());
auto srcLayoutAttr = xegpu::inferTransposeSourceLayout(
consumerLayoutAttr, transpose.getPermutation());
// Propagate the new layout to the vector operand.
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
}
/// For vector::BitCastOp, the lane_data of the source layout is changed based
/// on the bit width of the source and result types.
void LayoutInfoPropagation::visitVectorBitcastOp(
vector::BitCastOp bitcast, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// Need the layout of bitcast result to propagate to the operands.
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
auto srcVecType = bitcast.getSourceVectorType();
auto resVecType = bitcast.getResultVectorType();
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
const auto *uArch =
xegpu::uArch::getUArch(xegpu::getChipStr(bitcast).value_or(""));
if (!uArch)
return;
auto requiredResLayoutAttr = setupBitCastResultLayout(
layoutKind, srcVecType, resVecType, consumerLayoutAttr, uArch);
xegpu::setTemporaryLayout(bitcast->getResult(0), requiredResLayoutAttr);
int inElemTyBitWidth = srcVecType.getElementType().getIntOrFloatBitWidth();
int outElemTyBitWidth = resVecType.getElementType().getIntOrFloatBitWidth();
// derive the source layout from the dominant layout and reduction dims
auto srcLayoutAttr = xegpu::inferBitCastSourceLayout(
requiredResLayoutAttr, outElemTyBitWidth, inElemTyBitWidth);
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
}
/// For vector::InterleaveOp, the result has double the innermost dimension
/// size compared to each source operand. The layout is propagated from result
/// to sources, adjusting for the 2x size increase.
void LayoutInfoPropagation::visitVectorInterleaveOp(
vector::InterleaveOp interleave, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// Need the layout of interleave result to propagate to the operands.
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
auto srcVecType = interleave.getSourceVectorType();
auto resVecType = interleave.getResultVectorType();
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
const auto *uArch =
xegpu::uArch::getUArch(xegpu::getChipStr(interleave).value_or(""));
if (!uArch)
return;
// Setup the result layout to ensure the source layout can be safely derived
auto requiredResLayoutAttr = setupInterleaveResultLayout(
layoutKind, srcVecType, resVecType, consumerLayoutAttr, uArch);
xegpu::setTemporaryLayout(interleave->getResult(0), requiredResLayoutAttr);
// Derive the source layout from the result layout (halve the innermost dim)
auto srcLayoutAttr =
xegpu::inferInterleaveSourceLayout(requiredResLayoutAttr);
// Both operands (lhs and rhs) get the same source layout
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
propagateIfChanged(operands[1],
operands[1]->meet(makeLayoutInfo(srcLayoutAttr)));
}
/// For vector::DeinterleaveOp, the source has double the innermost dimension
/// size compared to each result. The layout is propagated from results to
/// source, adjusting for the 2x size decrease in results.
void LayoutInfoPropagation::visitVectorDeinterleaveOp(
vector::DeinterleaveOp deinterleave, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// Need the layout of deinterleave results to propagate to the operand.
// Use the first result's layout (both results should have the same layout)
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
// Derive the source layout from the result layout (double the innermost
// dim) No setup function needed - just infer directly
auto srcLayoutAttr = xegpu::inferDeinterleaveSourceLayout(consumerLayoutAttr);
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
}
void LayoutInfoPropagation::visitInsertStridedSliceOp(
vector::InsertStridedSliceOp insertStridedSlice,
ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
// The layout of the result must be present.
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
auto srcVecType = insertStridedSlice.getSourceVectorType();
auto resVecType = insertStridedSlice.getDestVectorType();
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
const auto *uArch = xegpu::uArch::getUArch(
xegpu::getChipStr(insertStridedSlice).value_or(""));
if (!uArch)
return;
auto requiredResLayoutAttr = xegpu::setupInsertStridedSliceResultLayout(
layoutKind, srcVecType, resVecType, consumerLayoutAttr, uArch);
xegpu::setTemporaryLayout(insertStridedSlice->getResult(0),
requiredResLayoutAttr);
auto srcLayoutAttr = xegpu::inferInsertStridedSliceSourceLayout(
requiredResLayoutAttr, resVecType.getShape(), srcVecType.getShape());
propagateIfChanged(operands[0],
operands[0]->meet(makeLayoutInfo(srcLayoutAttr)));
propagateIfChanged(operands[1],
operands[1]->meet(makeLayoutInfo(requiredResLayoutAttr)));
}
/// Propagate the layout of the result to the tensor descriptor, mask and
/// offset operands in LoadGatherOp.
void LayoutInfoPropagation::visitLoadGatherOp(
xegpu::LoadGatherOp load, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
xegpu::DistributeLayoutAttr requiredAnchorLayoutAttr;
xegpu::DistributeLayoutAttr anchorLayoutAttr = load.getLayoutAttr();
const auto *uArch = xegpu::uArch::getUArch(getChipStr(load).value_or(""));
if (!uArch)
return;
VectorType resVecTy = load.getValueType();
int chunkSize = load.getChunkSize().value_or(1);
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
if (hasParamsOfLayoutKind(anchorLayoutAttr)) {
requiredAnchorLayoutAttr = anchorLayoutAttr;
if (layoutKind == xegpu::LayoutKind::InstData &&
!consumerLayoutAttr.getEffectiveLaneLayoutAsInt().empty()) {
const auto uArchInstruction =
dyn_cast<xegpu::uArch::LoadGatherInstruction>(
uArch->getInstruction(xegpu::uArch::InstructionKind::LoadGather));
if (!uArchInstruction)
return;
auto completed = xegpu::completeScatterLoadLaneLayoutFromInstData(
anchorLayoutAttr, consumerLayoutAttr, resVecTy.getElementType(),
uArchInstruction, uArch->getSubgroupSize());
if (!completed) {
load.emitWarning(
"Failed to identify lane layouts for the specified inst_data.");
return;
}
requiredAnchorLayoutAttr = *completed;
load.setLayoutAttr(requiredAnchorLayoutAttr);
}
} else {
if (!resVecTy) {
load.emitWarning("Not propagating, non-vector payload supplied.");
return;
}
requiredAnchorLayoutAttr = xegpu::setupLoadGatherAnchorLayout(
layoutKind, resVecTy, chunkSize, consumerLayoutAttr, uArch);
load.setLayoutAttr(requiredAnchorLayoutAttr);
}
assert((chunkSize <= 1) || (layoutKind != xegpu::LayoutKind::Subgroup));
auto maskLayoutAttr = xegpu::inferMaskOffsetLayoutForScatterIO(
requiredAnchorLayoutAttr, chunkSize);
LayoutInfo maskLayoutInfo = makeLayoutInfo(maskLayoutAttr);
auto loadLayoutInfo = makeLayoutInfo(requiredAnchorLayoutAttr);
// Propagate the new layout to the tensor descriptor operand.
if (isa<xegpu::TensorDescType>(load.getSourceType()))
propagateIfChanged(operands[0], operands[0]->meet(loadLayoutInfo));
// Propagate the new layout to the offset and mask operands.
propagateIfChanged(operands[1], operands[1]->meet(maskLayoutInfo));
propagateIfChanged(operands[2], operands[2]->meet(maskLayoutInfo));
}
/// Set the layout for the value, tensor descriptor, offset and mask operands
/// in the StoreScatterOp.
void LayoutInfoPropagation::visitStoreScatterOp(
xegpu::StoreScatterOp storeScatter, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
xegpu::DistributeLayoutAttr requiredAnchorLayoutAttr;
xegpu::DistributeLayoutAttr anchorLayoutAttr = storeScatter.getLayoutAttr();
const auto *uArch =
xegpu::uArch::getUArch(getChipStr(storeScatter).value_or(""));
if (!uArch)
return;
VectorType srcVecTy = storeScatter.getValueType();
int chunkSize = storeScatter.getChunkSize().value_or(1);
if (hasParamsOfLayoutKind(anchorLayoutAttr)) {
requiredAnchorLayoutAttr = anchorLayoutAttr;
if (layoutKind == xegpu::LayoutKind::InstData) {
const auto uArchInstruction =
dyn_cast<xegpu::uArch::StoreScatterInstruction>(uArch->getInstruction(
xegpu::uArch::InstructionKind::StoreScatter));
if (!uArchInstruction)
return;
auto completed = xegpu::completeScatterStoreLaneLayoutFromInstData(
anchorLayoutAttr, srcVecTy.getElementType(), uArchInstruction,
uArch->getSubgroupSize());
if (!completed) {
markFailure(
storeScatter,
"Failed to identify lane layouts for the specified inst_data.");
return;
}
requiredAnchorLayoutAttr = *completed;
storeScatter.setLayoutAttr(requiredAnchorLayoutAttr);
}
} else {
if (!srcVecTy) {
storeScatter.emitWarning("Not propagating, non-vector payload supplied.");
return;
}
auto numSgOrErr =
getNumSgOrFail(storeScatter, uArch->getSubgroupSize(), nullptr);
if (failed(numSgOrErr))
return;
requiredAnchorLayoutAttr = xegpu::setupStoreScatterAnchorLayout(
layoutKind, srcVecTy, chunkSize, numSgOrErr.value_or(0), uArch);
if (!requiredAnchorLayoutAttr) {
markFailure(storeScatter,
"Failed to determine required layout for store scatter.");
return;
}
storeScatter.setLayoutAttr(requiredAnchorLayoutAttr);
}
LayoutInfo srcLayoutInfo = makeLayoutInfo(requiredAnchorLayoutAttr);
assert((chunkSize <= 1) || (layoutKind != xegpu::LayoutKind::Subgroup));
auto maskLayoutAttr = xegpu::inferMaskOffsetLayoutForScatterIO(
requiredAnchorLayoutAttr, chunkSize);
LayoutInfo maskLayoutInfo = makeLayoutInfo(maskLayoutAttr);
// Propagate the payload operand layout
propagateIfChanged(operands[0], operands[0]->meet(srcLayoutInfo));
// Propagate the destination (if tdesc) operand layout
if (isa<xegpu::TensorDescType>(storeScatter.getDestType()))
propagateIfChanged(operands[1], operands[1]->meet(srcLayoutInfo));
// Propagate the new layout to the offset and mask operands.
propagateIfChanged(operands[2], operands[2]->meet(maskLayoutInfo));
propagateIfChanged(operands[3], operands[3]->meet(maskLayoutInfo));
}
void LayoutInfoPropagation::visitLoadMatrixOp(
xegpu::LoadMatrixOp loadMatrixOp, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
LayoutInfo resLayoutInfo = results[0]->getValue();
if (!resLayoutInfo.isAssigned())
return;
auto consumerLayoutAttr =
dyn_cast<xegpu::DistributeLayoutAttr>(resLayoutInfo.get());
xegpu::DistributeLayoutAttr anchorLayout = loadMatrixOp.getLayoutAttr();
// only need to set anchor layout, no need to porpagate to memdesc and
// offset
if (!hasParamsOfLayoutKind(anchorLayout)) {
VectorType resVecTy =
llvm::cast<VectorType>(loadMatrixOp.getRes().getType());
const auto *uArch =
xegpu::uArch::getUArch(getChipStr(loadMatrixOp).value_or(""));
if (!uArch)
return;
int chunkSize =
1; // placeHolder for future use when LoadMatrix supports coalescing
auto requiredAnchorLayoutAttr = xegpu::setupLoadMatrixAnchorLayout(
layoutKind, resVecTy, chunkSize, consumerLayoutAttr, uArch);
loadMatrixOp.setLayoutAttr(requiredAnchorLayoutAttr);
}
}
void LayoutInfoPropagation::visitStoreMatrixOp(
xegpu::StoreMatrixOp storeMatrix, ArrayRef<LayoutInfoLattice *> operands,
ArrayRef<const LayoutInfoLattice *> results) {
xegpu::DistributeLayoutAttr requiredAnchorLayoutAttr;
xegpu::DistributeLayoutAttr anchorLayoutAttr = storeMatrix.getLayoutAttr();
LayoutInfo layout;
VectorType srcVecTy = llvm::cast<VectorType>(storeMatrix.getData().getType());
const auto *uArch =
xegpu::uArch::getUArch(getChipStr(storeMatrix).value_or(""));
if (!uArch)
return;
if (hasParamsOfLayoutKind(anchorLayoutAttr)) {
requiredAnchorLayoutAttr = anchorLayoutAttr;
if (layoutKind == xegpu::LayoutKind::InstData) {
const auto uArchInstruction =
dyn_cast<xegpu::uArch::StoreScatterInstruction>(uArch->getInstruction(
xegpu::uArch::InstructionKind::StoreScatter));
if (!uArchInstruction)
return;
auto completed = xegpu::completeScatterStoreLaneLayoutFromInstData(
anchorLayoutAttr, srcVecTy.getElementType(), uArchInstruction,
uArch->getSubgroupSize());
if (!completed) {
markFailure(
storeMatrix,
"Failed to identify lane layouts for the specified inst_data.");
return;
}
requiredAnchorLayoutAttr = *completed;
storeMatrix.setLayoutAttr(requiredAnchorLayoutAttr);
}
} else {
int chunkSize =
1; // placeHolder for future use when StoreMatrix supports coalescing
auto numSgOrErr =
getNumSgOrFail(storeMatrix, uArch->getSubgroupSize(), nullptr);
if (failed(numSgOrErr))
return;
requiredAnchorLayoutAttr = xegpu::setupStoreMatrixAnchorLayout(
layoutKind, srcVecTy, chunkSize, numSgOrErr.value_or(0), uArch);
if (!requiredAnchorLayoutAttr) {
markFailure(storeMatrix,
"Failed to determine required layout for store matrix.");
return;
}
storeMatrix.setLayoutAttr(requiredAnchorLayoutAttr);
}
layout = makeLayoutInfo(requiredAnchorLayoutAttr);
propagateIfChanged(operands[0], operands[0]->meet(layout));
}
namespace {
//===----------------------------------------------------------------------===//
// RunLayoutInfoPropagation
//===----------------------------------------------------------------------===//
/// Driver class for running the LayoutInfoPropagation analysis.
class RunLayoutInfoPropagation {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(RunLayoutInfoPropagation)
RunLayoutInfoPropagation(Operation *op, xegpu::LayoutKind layoutKind,
unsigned indexBitWidth)
: target(op) {
SymbolTableCollection symbolTable;
loadBaselineAnalyses(solver);
analysis = solver.load<LayoutInfoPropagation>(symbolTable, layoutKind,
indexBitWidth);
(void)solver.initializeAndRun(op);
}
LayoutInfo getLayoutInfo(Value val);
void printAnalysisResult(llvm::raw_ostream &os);
bool hasFailed() const { return analysis && analysis->hasFailed(); }
private:
DataFlowSolver solver;
const Operation *target;
LayoutInfoPropagation *analysis = nullptr;
};
} // namespace
LayoutInfo RunLayoutInfoPropagation::getLayoutInfo(Value val) {
auto *state = solver.lookupState<LayoutInfoLattice>(val);
if (!state)
return {};
return state->getValue();
}
// Print the analysis result for debugging purposes.
void RunLayoutInfoPropagation::printAnalysisResult(llvm::raw_ostream &os) {
auto printFunctionResult = [&](FunctionOpInterface funcOp) {
os << "function: " << funcOp.getName() << ":\n";
// Function arguments
for (BlockArgument arg : funcOp.getArguments()) {
LayoutInfo layout = getLayoutInfo(arg);
os << "argument: " << arg << "\n";
os << "layout : ";
layout.print(os);
os << "\n";
}
// Function ops
funcOp.walk([&](Operation *op) {
// Skip ops that do not have results
if (op->getResults().empty())
return;
os << "op : ";
// For control-flow ops, print the op name only.
if (isa<BranchOpInterface>(op) || isa<RegionBranchOpInterface>(op))
os << op->getName();
else
op->print(os);
os << "\n";
// Print the layout for each result.
for (auto [i, r] : llvm::enumerate(op->getResults())) {
LayoutInfo layout = getLayoutInfo(r);
os << "layout for result #" << i << ": ";
layout.print(os);
os << "\n";
}
});
};
SmallVector<FunctionOpInterface> funcOps;
if (auto modOp = dyn_cast<ModuleOp>(target)) {
for (auto funcOp : modOp.getOps<FunctionOpInterface>())
funcOps.push_back(funcOp);
// Collect all GpuFuncOps in the module.
for (auto gpuModOp : modOp.getOps<gpu::GPUModuleOp>()) {
for (auto gpuFuncOp : gpuModOp.getOps<FunctionOpInterface>())
funcOps.push_back(gpuFuncOp);
}
}
// Print the analysis result for each function.
for (FunctionOpInterface funcOp : funcOps)
printFunctionResult(funcOp);
}
namespace {
//===----------------------------------------------------------------------===//
// ResolveLayoutConflicts
//===----------------------------------------------------------------------===//
/// Helper to get the defining CreateNdDescOp of a tensor descriptor value.
/// This function tries to find the defining CreateNdDescOp recursively
/// accross control-flow boundaries.
static xegpu::CreateNdDescOp getDefiningCreateNdDescOp(Value tdescValue) {
// Try to get the defining CreateNdDescOp of the tensor descriptor.
auto definingOp = tdescValue.getDefiningOp<xegpu::CreateNdDescOp>();
if (definingOp)
return definingOp;
// If tdescValue is an argument, try to get the tied init value from the
// parent loop-like op.
if (auto arg = dyn_cast<BlockArgument>(tdescValue)) {
auto *parentOp = arg.getOwner()->getParentOp();
if (auto loop = dyn_cast<LoopLikeOpInterface>(parentOp)) {
OpOperand *tiedInit = loop.getTiedLoopInit(arg);
if (tiedInit)
return getDefiningCreateNdDescOp(tiedInit->get());
}
}
// If not found, return null.
return nullptr;
}
struct ResolveLayoutConflicts {
ResolveLayoutConflicts(Operation *parentOp)
: parentOp(parentOp), builder(parentOp->getContext()) {}
LogicalResult run();
private:
Operation *parentOp;
OpBuilder builder;
LogicalResult resolveTensorDescConsumer(OpOperand &operand);
LogicalResult resolveVectorConsumer(OpOperand &operand);
LogicalResult assignResultLayout(OpResult &result);
};
} // namespace
LogicalResult ResolveLayoutConflicts::run() {
// Scan all operations in the parent op and resolve layout conflicts at
// tensor descriptor and vector use points.
auto r = parentOp->walk([&](Operation *op) -> WalkResult {
for (OpResult result : op->getResults()) {
// if the operation inputs vector and output scalar, like multi-reduction
// we need to check if the result has layout and add a convert_layout to
// serve as anchor op for the reduction op's layout.
if (result.getType().isIntOrFloat() &&
(isa<vector::MultiDimReductionOp>(op) ||
isa<vector::ReductionOp>(op))) {
auto res = assignResultLayout(result);
if (failed(res)) {
DBGS() << "Failed to assign layout for scalar consumer of reduction "
<< *op << "\n";
return WalkResult::interrupt();
}
}
// If the op is a region branch op with a vector result that has no uses,
// we need to add a convert_layout to serve as an anchor op for the
// result's layout.
if (isa<VectorType>(result.getType()) && result.use_empty() &&
isa<RegionBranchOpInterface>(op)) {
auto res = assignResultLayout(result);
if (failed(res)) {
DBGS() << "Failed to assign layout for vector consumer of region op "
<< *op << "\n";
return WalkResult::interrupt();
}
}
}
for (OpOperand &operand : op->getOpOperands()) {
// Handle conflicts in tensor descriptor operands.
Type operandType = operand.get().getType();
if (isa<xegpu::AnchorLayoutInterface>(op) &&
isa<xegpu::TensorDescType>(operandType)) {
auto res = resolveTensorDescConsumer(operand);
if (failed(res)) {
DBGS() << "Failed to resolve tensor descriptor consumer: " << *op
<< "\n";
return WalkResult::interrupt();
}
}
// Handle conflicts in vector operands.
if (isa<VectorType>(operandType)) {
auto res = resolveVectorConsumer(operand);
if (failed(res)) {
DBGS() << "Failed to resolve vector consumer: " << *op << "\n";
return WalkResult::interrupt();
}
}
}
return WalkResult::advance();
});
LLVM_DEBUG({
DBGS() << "IR after resolving layout conflicts:\n";
parentOp->dump();
});
return r.wasInterrupted() ? failure() : success();
}
LogicalResult ResolveLayoutConflicts::assignResultLayout(OpResult &result) {
Operation *producerOp = result.getDefiningOp();
auto producerLayout = xegpu::getDistributeLayoutAttr(result);
// Insert a convert_layout op to assign the layout.
builder.setInsertionPointAfterValue(result);
auto convertOp = xegpu::ConvertLayoutOp::create(
builder, producerOp->getLoc(), result.getType(), result, producerLayout,
producerLayout);
result.replaceAllUsesExcept(convertOp.getResult(), convertOp);
return success();
}
LogicalResult
ResolveLayoutConflicts::resolveVectorConsumer(OpOperand &operand) {
Value vectorValue = operand.get();
Operation *consumerOp = operand.getOwner();
// Get the current layout of the vector value.
auto producerLayout = xegpu::getDistributeLayoutAttr(vectorValue);
if (!producerLayout) {
if (auto vectorTy = dyn_cast<VectorType>(vectorValue.getType());
vectorTy && vectorTy.getRank() > 1)
consumerOp->emitWarning("Expected layout for non-1D vectors.");
return success(); // uniform non-tensor-data vector does not require
// layout
}
// getConsumerLayoutAt also covers region-carried operands (loop init and
// yield operands), so a layout conflict there is reconciled below rather than
// silently trusted to region forwarding.
auto consumerLayout = xegpu::getConsumerLayoutAt(operand);
if (!consumerLayout) {
// TODO: handle scf.while's "after" region arguments. They are tied to no
// init operand, so nothing records the layout they require, and the
// conflict on the scf.condition operand feeding them is left unresolved
// rather than converted.
if (isa<RegionBranchTerminatorOpInterface>(consumerOp))
return success();
return consumerOp->emitError(
"No consumer layout found for vector operand.");
}
// If layouts are same, no conflict exists, return success.
if (consumerLayout.isEqualTo(producerLayout))
return success();
// Consumer is a convert_layout: retarget its input_layout to the producer
// instead of chaining a second convert. Always safe (single source
// operand).
if (auto consumerConvert = dyn_cast<xegpu::ConvertLayoutOp>(consumerOp)) {
consumerConvert.setInputLayoutAttr(producerLayout);
return success();
}
// Producer is a convert_layout feeding only this use: retarget its
// target_layout to the consumer instead of appending another convert.
if (auto producerConvert =
vectorValue.getDefiningOp<xegpu::ConvertLayoutOp>();
producerConvert && vectorValue.hasOneUse()) {
// Pin the effective input before retargeting target, else an omitted
// input_layout would follow target and make the conversion a no-op.
producerConvert.setInputLayoutAttr(
producerConvert.getEffectiveInputLayout());
producerConvert.setTargetLayoutAttr(consumerLayout);
return success();
}
// If the producer is trivially rematerializable (e.g. `vector.step`, splat
// `arith.constant`), clone it and stamp the consumer's expected layout on
// the clone instead of inserting a `xegpu.convert_layout`. The convert
// would otherwise lower to a cross-subgroup data movement through SLM at
// WG-to-SG distribution time, which is more expensive than
// recomputing a pure value generator.
if (auto *producerOp = vectorValue.getDefiningOp();
producerOp && producerOp->getNumResults() == 1 &&
isa<OpResult>(vectorValue) &&
xegpu::isTriviallyRematerializable(producerOp)) {
builder.setInsertionPointAfter(producerOp);
Operation *clone = builder.clone(*producerOp);
OpResult cloneResult = clone->getResult(0);
// Drop the inherited producer layout so the new layout takes effect
xegpu::removeLayoutAttr(cloneResult);
xegpu::setDistributeLayoutAttr(cloneResult, consumerLayout);
operand.set(cloneResult);
return success();
}
// Insert a convert_layout op to resolve the conflict.
builder.setInsertionPointAfterValue(vectorValue);
auto convertOp = xegpu::ConvertLayoutOp::create(
builder, consumerOp->getLoc(), vectorValue.getType(), vectorValue,
producerLayout, consumerLayout);
// Update the operand to use the converted value.
operand.set(convertOp.getResult());
return success();
}
LogicalResult
ResolveLayoutConflicts::resolveTensorDescConsumer(OpOperand &operand) {
Operation *consumerOp = operand.getOwner();
Value tdescValue = operand.get();
auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(consumerOp);
auto currTDescType = dyn_cast<xegpu::TensorDescType>(tdescValue.getType());
assert(anchorOp && currTDescType &&
"Expected anchor layout op and tensor descriptor consumer.");
Attribute currLayout = currTDescType.getLayout();
Attribute expectedLayout = anchorOp.getAnchorLayout();
// A conflict exists in tensor descriptor operand if tensor descriptor's
// layout is different from the anchor layout expected by the consumer.
if (expectedLayout && currLayout && expectedLayout != currLayout) {
// Try to get the defining CreateNdDescOp of the tensor descriptor.
auto conflictingCreateNdOp = getDefiningCreateNdDescOp(tdescValue);
if (!conflictingCreateNdOp) {
DBGS() << "Unable to find defining CreateNdDescOp for tensor descriptor: "
<< tdescValue << "\n";
return failure();
}
// Duplicate the CreateNdDescOp with the expected layout.
builder.setInsertionPointAfter(conflictingCreateNdOp);
auto newTensorDescType = xegpu::TensorDescType::get(
conflictingCreateNdOp.getContext(), currTDescType.getShape(),
currTDescType.getElementType(), currTDescType.getEncoding(),
expectedLayout);
xegpu::CreateNdDescOp newOp = xegpu::CreateNdDescOp::create(
builder, consumerOp->getLoc(), newTensorDescType,
conflictingCreateNdOp->getOperands(),
conflictingCreateNdOp->getAttrs());
// Replace the tensor descriptor operand in the consumer op with the new
// tensor descriptor.
consumerOp->replaceUsesOfWith(tdescValue, newOp.getResult());
}
return success();
}
using GetLayoutFnTy = function_ref<xegpu::DistributeLayoutAttr(Value)>;
/// Update an operation with the layout of its results. For a vector result a
/// temporary layout attribute is added to the op; for a tensor descriptor
/// result the layout is written into its type.
///
/// If the global propagation left a result without a layout, forward-fill it
/// locally from the operand layouts.
static LogicalResult updateOpWithForwardFill(mlir::OpBuilder &builder,
mlir::Operation *op,
GetLayoutFnTy getLayoutOfValue) {
// Iterate over all the results.
for (OpResult result : op->getResults()) {
Type resultType = result.getType();
// Layouts are needed only for vector and tensor descriptor types.
if (!isa<VectorType, xegpu::TensorDescType>(resultType))
continue;
// If the result has no layout but has users, emit a warning and continue.
xegpu::DistributeLayoutAttr layout = getLayoutOfValue(result);
if (!layout) {
// Gather operand layouts, indexed by operand number.
SmallVector<xegpu::DistributeLayoutAttr> srcLayouts;
srcLayouts.reserve(op->getNumOperands());
bool anyAssigned = false;
for (Value operand : op->getOperands()) {
auto srclayout = xegpu::getDistributeLayoutAttr(operand);
srcLayouts.push_back(srclayout);
anyAssigned |= (srclayout != nullptr);
}
if (anyAssigned) {
layout =
xegpu::inferResultLayoutFromSourceForNonAnchorOp(op, srcLayouts);
}
}
if (!layout && result.getNumUses() > 0) {
op->emitWarning("op has users but no layout assigned for its result");
}
// If the result is a tensor descriptor type, update the tensor desc type
// with layout.
if (auto tensorDescTy = dyn_cast<xegpu::TensorDescType>(resultType)) {
auto typeWithLayout = xegpu::TensorDescType::get(
tensorDescTy.getContext(), tensorDescTy.getShape(),
tensorDescTy.getElementType(), tensorDescTy.getEncoding(), layout);
result.setType(typeWithLayout);
continue;
}
// If the result is a vector type, add a temporary layout attribute to the
// op.
xegpu::setDistributeLayoutAttr(result, layout);
}
return success();
}
/// Update the function arguments and results with the layouts.
static LogicalResult updateFunctionOpInterface(mlir::OpBuilder &builder,
mlir::FunctionOpInterface funcOp,
GetLayoutFnTy getLayoutOfValue) {
// Only process functions whose type is a standard MLIR FunctionType.
// Functions using a different type representation (e.g. llvm.func with
// LLVMFunctionType) are not targets for XeGPU layout propagation, and
// calling setType(FunctionType{}) on them would corrupt their type.
if (!isa<FunctionType>(funcOp.getFunctionType()))
return success();
SmallVector<Type> newArgTypes;
// Update the function arguments.
for (BlockArgument arg : funcOp.getArguments()) {
Type argType = arg.getType();
newArgTypes.push_back(argType);
if (!isa<VectorType, xegpu::TensorDescType>(argType))
continue;
xegpu::DistributeLayoutAttr layout = getLayoutOfValue(arg);
if (!layout) {
LLVM_DEBUG(DBGS() << "Expecting layout for function argument: " << arg
<< " but got none.\n");
return failure();
}
if (auto tensorDescTy = dyn_cast<xegpu::TensorDescType>(argType)) {
auto newTdescTy = xegpu::TensorDescType::get(
tensorDescTy.getContext(), tensorDescTy.getShape(),
tensorDescTy.getElementType(), tensorDescTy.getEncoding(), layout);
arg.setType(newTdescTy);
newArgTypes.back() = newTdescTy;
}
}
// Update the function type with the new argument types.
// NOTE: We assume that function results are not expected to have layouts.
funcOp.setType(FunctionType::get(funcOp.getContext(), newArgTypes,
funcOp.getResultTypes()));
return success();
}
namespace {
struct XeGPUPropagateLayoutPass final
: public xegpu::impl::XeGPUPropagateLayoutBase<XeGPUPropagateLayoutPass> {
XeGPUPropagateLayoutPass() = default;
XeGPUPropagateLayoutPass(const XeGPUPropagateLayoutPass &other) = default;
XeGPUPropagateLayoutPass(xegpu::XeGPUPropagateLayoutOptions options)
: XeGPUPropagateLayoutBase(std::move(options)) {}
void runOnOperation() override;
};
} // namespace
LogicalResult xegpu::propagateLayouts(OpBuilder &builder, Operation *target,
LayoutKind layoutKind,
unsigned indexBitWidth, bool printOnly) {
RunLayoutInfoPropagation analysis(target, layoutKind, indexBitWidth);
// Print the analysis result and exit. (for debugging purposes)
if (printOnly) {
auto &os = llvm::outs();
analysis.printAnalysisResult(os);
return success();
}
// An op with no determinable layout cannot be lowered; stop before the update
// walk fabricates degenerate layouts from the unlabeled values.
if (analysis.hasFailed())
return failure();
// Helper to convert LayoutInfo to xegpu::LayoutAttr.
auto getLayoutFromPropagation =
[&](Value val) -> xegpu::DistributeLayoutAttr {
LayoutInfo layout = analysis.getLayoutInfo(val);
if (auto opResult = dyn_cast<OpResult>(val)) {
Operation *defOp = opResult.getDefiningOp();
if (auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(defOp)) {
auto anchorLayout = anchorOp.getAnchorLayout();
if (anchorLayout != nullptr)
return anchorLayout;
}
xegpu::DistributeLayoutAttr requiredResLayoutAttr =
xegpu::getTemporaryLayout(opResult);
if (requiredResLayoutAttr != nullptr)
return requiredResLayoutAttr;
}
if (!layout.isAssigned())
return {};
xegpu::DistributeLayoutAttr layoutAttr =
cast<xegpu::DistributeLayoutAttr>(layout.get());
if (layout.isSliceLayout())
return cast<xegpu::SliceAttr>(layoutAttr);
return cast<xegpu::LayoutAttr>(layoutAttr);
};
Operation *op = target;
auto walkResult = op->walk([&](mlir::Block *block) -> WalkResult {
for (mlir::Operation &op : block->getOperations()) {
LogicalResult r = success();
TypeSwitch<Operation *>(&op)
.Case([&](mlir::RegionBranchTerminatorOpInterface branchTermOp) {
r = xegpu::propagateYieldOperandsToRegionResults(
branchTermOp, getLayoutFromPropagation);
})
.Case([&](mlir::RegionBranchOpInterface branchOp) {
r = xegpu::propagateRegionArgsToInits(branchOp,
getLayoutFromPropagation);
})
.Case([&](mlir::FunctionOpInterface funcOp) {
r = updateFunctionOpInterface(builder, funcOp,
getLayoutFromPropagation);
})
.Default([&](Operation *op) {
r = updateOpWithForwardFill(builder, op, getLayoutFromPropagation);
});
if (failed(r)) {
op.emitError("Failed to update operation with the layout.");
return WalkResult::interrupt();
}
}
return WalkResult::advance();
});
if (walkResult.wasInterrupted())
return failure();
return success();
}
LogicalResult xegpu::resolveLayoutConflicts(Operation *target) {
ResolveLayoutConflicts resolver(target);
return resolver.run();
}
void XeGPUPropagateLayoutPass::runOnOperation() {
xegpu::removeTemporaryLayoutAttrs(getOperation());
xegpu::LayoutKind layoutKind;
if (this->layoutKind == "lane") {
layoutKind = xegpu::LayoutKind::Lane;
} else if (this->layoutKind == "inst") {
layoutKind = xegpu::LayoutKind::InstData;
} else if (this->layoutKind == "subgroup") {
layoutKind = xegpu::LayoutKind::Subgroup;
} else {
getOperation()->emitError("Unsupported layout kind option: " +
this->layoutKind);
signalPassFailure();
return;
}
OpBuilder builder(&getContext());
if (failed(xegpu::propagateLayouts(builder, getOperation(), layoutKind,
this->indexBitWidth, this->printOnly))) {
signalPassFailure();
return;
}
// Resolve layout conflicts if any.
if (failed(xegpu::resolveLayoutConflicts(getOperation()))) {
signalPassFailure();
return;
}
}