19#include "llvm/ADT/SmallVectorExtras.h"
23#include "llvm/Support/DebugLog.h"
24#define DEBUG_TYPE "xegpu-transforms"
37 if (
auto attr = dyn_cast<Attribute>(ofr)) {
38 if (
auto intAttr = dyn_cast<IntegerAttr>(attr)) {
39 result.push_back(intAttr.getInt());
42 return transformOp.emitDefiniteFailure() <<
"expected IntegerAttr";
46 Value transformValue = cast<Value>(ofr);
47 if (isa<TransformParamTypeInterface>(transformValue.
getType())) {
49 if (params.size() != 1)
50 return transformOp.emitDefiniteFailure()
51 <<
"requires exactly one parameter associated";
53 cast<IntegerAttr>(params.front()).getValue().getSExtValue());
59 if (!llvm::hasSingleElement(payloadOps)) {
61 transformOp.emitSilenceableError()
62 <<
"handle must be mapped to exactly one payload op";
64 <<
"mapped to " << llvm::range_size(payloadOps) <<
" payload ops";
71 transformOp.emitSilenceableError()
72 <<
"payload op must have exactly 1 index result";
80 return transformOp.emitSilenceableError()
81 <<
"requires param or handle to be the result of a constant like "
84 result.push_back(intAttr.getInt());
95 TransformOpInterface transformOp,
98 if (isa<TransformParamTypeInterface>(packedHandle.
getType())) {
100 auto intAttr = dyn_cast<IntegerAttr>(param);
102 return transformOp.emitDefiniteFailure()
103 <<
"expected the parameter to be associated with an integer "
105 result.push_back(intAttr.getValue().getSExtValue());
112 if (op->getNumResults() != 1 || !op->getResult(0).getType().isIndex()) {
114 transformOp.emitSilenceableError()
115 <<
"payload op must have exactly 1 index result";
116 diag.attachNote(op->getLoc())
117 <<
"has " << op->getNumResults() <<
" results";
123 return transformOp.emitSilenceableError()
124 <<
"requires param or handle to be the result of a constant like "
127 result.push_back(intAttr.getInt());
136 Value currentValue = val;
140 LDBG() <<
"Failed to find producer op, value has no uses.";
143 auto userOp = val.
getUsers().begin();
144 auto parentLoop = userOp->getParentOfType<LoopLikeOpInterface>();
146 LDBG() <<
"Failed to find producer op, not in a loop.";
150 if (
auto iterArg = llvm::dyn_cast<BlockArgument>(currentValue)) {
151 auto numInductionVars = parentLoop.getLoopInductionVars()->size();
152 iterArgIdx = iterArg.getArgNumber() - numInductionVars;
153 currentValue = parentLoop.getInits()[iterArgIdx];
155 LDBG() <<
"Failed to find producer op, value not in init values.";
161 if (
auto matchingOp = dyn_cast<T>(producerOp))
174 return xegpu::LayoutAttr::get(
186 TransformOpInterface transformOp,
192 xegpu::LayoutAttr &layoutAttr) {
203 auto status = convert(packedSgLayout, mixedSgLayout, sgLayout);
204 if (!status.succeeded())
207 status = convert(packedSgData, mixedSgData, sgData);
208 if (!status.succeeded())
211 status = convert(packedInstData, mixedInstData, instData);
212 if (!status.succeeded())
214 auto maybeInstData = instData.empty()
216 : std::optional<ArrayRef<int32_t>>(instData);
228 if (!llvm::hasSingleElement(targetValues)) {
230 <<
"requires exactly one target value handle (got "
231 << llvm::range_size(targetValues) <<
")";
238 loadOp = maybeLoadNdOp->getOperation();
243 loadOp = maybeLoadOp->getOperation();
246 <<
"Could not find a matching xegpu.load_nd or xegpu.load op when "
248 "producer chain of the first operand.";
252 results.
set(llvm::cast<OpResult>(getResult()), {loadOp});
256void transform::SetAnchorLayoutOp::build(
266 build(builder, ostate,
target.getType(),
282LogicalResult transform::SetAnchorLayoutOp::verify() {
283 if (getPackedSgLayout() && !getMixedSgLayout().empty())
284 return emitOpError(
"sg_layout and packed_sg_layout are mutually exclusive");
285 if (getPackedSgData() && !getMixedSgData().empty())
286 return emitOpError(
"sg_data and packed_sg_data are mutually exclusive");
287 if (getPackedInstData() && !getMixedInstData().empty())
288 return emitOpError(
"inst_data and packed_inst_data are mutually exclusive");
300 xegpu::LayoutAttr layoutAttr =
nullptr;
302 getContext(), state, (*
this), getMixedSgLayout(), getMixedSgData(),
303 getMixedInstData(), getPackedSgLayout(), getPackedSgData(),
304 getPackedInstData(), getOrder(), layoutAttr);
305 if (!status.succeeded())
308 xegpu::DistributeLayoutAttr layout = layoutAttr;
309 auto sliceDims = getSliceDims();
310 if (sliceDims.size() > 0) {
312 layout = xegpu::SliceAttr::get(
319 if (
auto dpasOp = dyn_cast<xegpu::DpasOp>(
target)) {
322 dpasOp.getProperties().layout_a = layout;
324 dpasOp.getProperties().layout_b = layout;
326 dpasOp.getProperties().layout_cd = layout;
329 <<
"Invalid index for setting dpas op layout: " <<
index;
330 diag.attachNote(
target->getLoc()) <<
"target op";
335 auto anchorOp = dyn_cast<xegpu::AnchorLayoutInterface>(
target);
338 <<
"Cannot set anchor layout to op: " <<
target->getName();
339 diag.attachNote(
target->getLoc()) <<
"target op";
342 anchorOp.setAnchorLayout(layout);
348void transform::SetAnchorLayoutOp::getEffects(
360void transform::SetGPULaunchThreadsOp::build(
366 build(builder, ostate,
target.getType(),
377 if (!llvm::hasSingleElement(targetOps)) {
379 << llvm::range_size(targetOps) <<
")";
383 auto launchOp = dyn_cast<gpu::LaunchOp>(
target);
386 <<
"Expected a gpu.launch op, but got: " <<
target->getName();
387 diag.attachNote(
target->getLoc()) <<
"target op";
397 if (threads.size() != 3) {
399 <<
"Expected threads argument to consist of three values (got "
400 << threads.size() <<
")";
404 auto createConstValue = [&](
int value) {
409 launchOp.getBlockSizeXMutable().assign(createConstValue(threads[0]));
410 launchOp.getBlockSizeYMutable().assign(createConstValue(threads[1]));
411 launchOp.getBlockSizeZMutable().assign(createConstValue(threads[2]));
416void transform::SetGPULaunchThreadsOp::getEffects(
428 if (!llvm::hasSingleElement(targetOps))
430 <<
"requires exactly one target op handle (got "
431 << llvm::range_size(targetOps) <<
")";
432 auto target = *targetOps.begin();
434 int64_t nbPrefetch = getStaticNbPrefetch();
435 if (getDynamicNbPrefetch()) {
439 {getDynamicNbPrefetch()});
442 if (dynamicNbPrefetch.size() != 1)
444 <<
"requires exactly one value for dynamic_nb_prefetch";
445 nbPrefetch = dynamicNbPrefetch[0];
449 <<
"nb_prefetch must be a positive integer.";
452 auto maybeLoadOp = dyn_cast<xegpu::LoadNdOp>(
target);
455 <<
"Expected xegpu.load_nd op, got " <<
target->getName();
457 auto loadOp = maybeLoadOp;
458 if (loadOp.getMixedOffsets().size() == 0) {
460 <<
"Load op must have offsets.";
461 diag.attachNote(loadOp.
getLoc()) <<
"load op";
469 <<
"Load op is not contained in a scf.for loop.";
470 diag.attachNote(loadOp.
getLoc()) <<
"load op";
479 auto descOp = *maybeDescOp;
489 return op->getBlock() == forOp.getBody();
491 for (
Value offset : loadOp.getOffsets())
494 <<
"Could not trace load offset computation.";
497 auto isAvailable = [&](
Value value) {
498 return value == forOp.getInductionVar() ||
499 (value.getDefiningOp() &&
500 offsetSlice.contains(value.getDefiningOp())) ||
504 if (!
isPure(op) || op->getNumRegions() != 0)
506 <<
"Offset computation cannot be safely replicated before the "
508 if (!llvm::all_of(op->getOperands(), isAvailable))
510 <<
"Load offset depends on a value unavailable before the loop.";
512 if (!llvm::all_of(loadOp.getOffsets(), isAvailable))
514 <<
"Load offset depends on a value unavailable before the loop.";
519 cast<xegpu::CreateNdDescOp>(rewriter.
clone(*descOp.getOperation()));
526 forOp.getLoc(), nbPrefetchCst, forOp.getStep());
527 auto initUpBound = rewriter.
createOrFold<arith::AddIOp>(
528 forOp.getLoc(), forOp.getLowerBound(), nbStep);
530 scf::ForOp::create(rewriter, forOp.getLoc(), forOp.getLowerBound(),
531 initUpBound, forOp.getStep());
535 xegpu::CachePolicyAttr::get(ctx, xegpu::CachePolicy::CACHED);
540 auto getPrefetchOffsets =
543 mapping.
map(forOp.getInductionVar(), replacementVal);
545 rewriter.
clone(*op, mapping);
547 llvm::map_to_vector(loadOp.getOffsets(), [&](
Value v) {
548 return mapping.lookupOrDefault(v);
550 auto constOffsets = loadOp.getConstOffsets();
557 xegpu::PrefetchNdOp::create(rewriter, newDescOp.getLoc(),
558 newDescOp.getResult(),
559 getPrefetchOffsets(initForOp.getInductionVar()),
560 readCacheHint, readCacheHint, readCacheHint,
566 auto prefetchOffset = arith::AddIOp::create(rewriter, forOp.getLoc(),
567 forOp.getInductionVar(), nbStep);
569 xegpu::PrefetchNdOp::create(rewriter, newDescOp.getLoc(),
570 newDescOp.getResult(),
571 getPrefetchOffsets(prefetchOffset), readCacheHint,
572 readCacheHint, readCacheHint,
nullptr);
578 results.
set(llvm::cast<OpResult>(getResult()), {newDescOp});
583void transform::InsertPrefetchOp::getEffects(
591void transform::ConvertLayoutOp::build(
602 dynamicInputInstData;
604 staticInputSgLayout);
608 staticInputInstData);
610 staticTargetInstData;
612 dynamicTargetInstData;
614 staticTargetSgLayout);
618 staticTargetInstData);
619 build(builder, ostate,
target.getType(),
621 dynamicInputSgLayout,
623 dynamicInputInstData,
624 dynamicTargetSgLayout,
626 dynamicTargetInstData,
631 staticTargetSgLayout,
633 staticTargetInstData,
642 if (!llvm::hasSingleElement(targetValues))
644 <<
"requires exactly one target value handle (got "
645 << llvm::range_size(targetValues) <<
")";
646 auto value = *targetValues.begin();
649 xegpu::LayoutAttr inputLayoutAttr =
nullptr;
651 getContext(), state, (*
this), getMixedInputSgLayout(),
652 getMixedInputSgData(), getMixedInputInstData(),
659 xegpu::LayoutAttr targetLayoutAttr =
nullptr;
661 getContext(), state, (*
this), getMixedTargetSgLayout(),
662 getMixedTargetSgData(), getMixedTargetInstData(),
670 if (value.use_empty())
672 <<
"Value has no users to insert layout conversion.";
678 xegpu::ConvertLayoutOp::create(rewriter, value.getLoc(), value.getType(),
679 value, inputLayoutAttr, targetLayoutAttr);
682 value, convLayoutOp.getResult(), [&](
OpOperand &use) {
683 return use.getOwner() != convLayoutOp.getOperation();
686 results.
set(llvm::cast<OpResult>(getResult()), {convLayoutOp});
690void transform::ConvertLayoutOp::getEffects(
704class XeGPUTransformDialectExtension
706 XeGPUTransformDialectExtension> {
715void XeGPUTransformDialectExtension::init() {
716 declareGeneratedDialect<scf::SCFDialect>();
717 declareGeneratedDialect<arith::ArithDialect>();
718 declareGeneratedDialect<xegpu::XeGPUDialect>();
720 registerTransformOps<
722#include "mlir/Dialect/XeGPU/TransformOps/XeGPUTransformOps.cpp.inc"
727#define GET_OP_CLASSES
728#include "mlir/Dialect/XeGPU/TransformOps/XeGPUTransformOps.cpp.inc"
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Attributes are known-constant values of operations.
MLIRContext * getContext() const
The result of a transform IR operation application.
static DiagnosedSilenceableFailure success()
Constructs a DiagnosedSilenceableFailure in the success state.
bool succeeded() const
Returns true if this is a success.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
void addExtensions()
Add the given extensions to the registry.
A class for computing basic dominance information.
bool dominates(Operation *a, Operation *b) const
Return true if operation A dominates operation B, i.e.
This is a utility class for mapping one set of IR entities to another.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
MLIRContext is the top-level object for a collection of MLIR operations.
This class helps build Operations.
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Operation is the basic unit of execution within MLIR.
Value getOperand(unsigned idx)
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Location getLoc()
The source location the operation was defined or derived from.
unsigned getNumOperands()
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
user_range getUsers()
Returns a range of all users.
unsigned getNumResults()
Return the number of results held by this operation.
virtual void replaceUsesWithIf(Value from, Value to, function_ref< bool(OpOperand &)> functor, bool *allUsesReplaced=nullptr)
Find uses of from and replace them with to if the functor returns true.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
unsigned getNumUses() const
This method computes the number of uses of this Value.
user_range getUsers() const
Location getLoc() const
Return the location of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int32_t > content)
Base class for extensions of the Transform dialect that supports injecting operations into the Transf...
DynamicAPInt getIndex(const ConeV &cone)
Get the index of a cone, i.e., the volume of the parallelepiped spanned by its generators,...
void registerTransformDialectExtension(DialectRegistry ®istry)
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
LogicalResult getBackwardSlice(Operation *op, SetVector< Operation * > *backwardSlice, const BackwardSliceOptions &options={})
Fills backwardSlice with the computed backward slice (i.e.
DiagnosedSilenceableFailure emitSilenceableFailure(Location loc, const Twine &message={})
Emits a silenceable failure with the given message.
LogicalResult loopUnrollFull(scf::ForOp forOp)
Unrolls this loop completely.
bool isPure(Operation *op)
Returns true if the given operation is pure, i.e., is speculatable that does not touch memory.
llvm::SetVector< T, Vector, Set, N > SetVector
DiagnosedDefiniteFailure emitDefiniteFailure(Location loc, const Twine &message={})
Emits a definite failure with the given message.
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
bool omitBlockArguments
When omitBlockArguments is true, the backward slice computation omits traversing any block arguments.
This represents an operation in an abstracted form, suitable for use with the builder APIs.
bool inclusive
Include the top level op in the slice.