Mehdi Amini 41f65666f6
[MLIR] Revamp RegionBranchOpInterface (#165429)
This is still somehow a WIP, we have some issues with this interface
that are not trivial to solve. This patch tries to make the concepts of
RegionBranchPoint and RegionSuccessor more robust and aligned with their
definition:
- A `RegionBranchPoint` is either the parent (`RegionBranchOpInterface`)
op or a `RegionBranchTerminatorOpInterface` operation in a nested
region.
- A `RegionSuccessor` is either one of the nested region or the parent
`RegionBranchOpInterface`

Some new methods with reasonnable default implementation are added to
help resolving the flow of values across the RegionBranchOpInterface.

It is still not trivial in the current state to walk the def-use chain
backward with this interface. For example when you have the 3rd block
argument in the entry block of a for-loop, finding the matching operands
requires to know about the hidden loop iterator block argument and where
the iterargs start. The API is designed around forward-tracking of the
chain unfortunately.

Try to reland #161575 ; I suspect a buildbot incremental build issue.
2025-10-28 09:53:56 -07:00

245 lines
9.4 KiB
C++

//===- TuneExtensionOps.cpp - Tune extension for the Transform dialect ----===//
//
// 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/Dialect/Transform/IR/TransformOps.h"
#include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "llvm/Support/Debug.h"
#include "mlir/Dialect/Transform/TuneExtension/TuneExtensionOps.h"
using namespace mlir;
static ParseResult parseAlternativesOpSelectedRegion(
OpAsmParser &parser, IntegerAttr &selectedRegionAttr,
std::optional<OpAsmParser::UnresolvedOperand> &selectedRegionParam);
static void printAlternativesOpSelectedRegion(OpAsmPrinter &printer,
Operation *op,
IntegerAttr selectedRegionAttr,
Value selectedRegionParam);
#define GET_OP_CLASSES
#include "mlir/Dialect/Transform/TuneExtension/TuneExtensionOps.cpp.inc"
#define DEBUG_TYPE "transform-tune"
#define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE "] ")
//===----------------------------------------------------------------------===//
// KnobOp
//===----------------------------------------------------------------------===//
void transform::tune::KnobOp::getEffects(
SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
producesHandle(getOperation()->getOpResults(), effects);
onlyReadsPayload(effects);
}
DiagnosedSilenceableFailure
transform::tune::KnobOp::apply(transform::TransformRewriter &rewriter,
transform::TransformResults &results,
transform::TransformState &state) {
if (getSelected()) {
results.setParams(llvm::cast<OpResult>(getResult()), *getSelected());
return DiagnosedSilenceableFailure::success();
}
return emitDefiniteFailure()
<< "non-deterministic choice " << getName()
<< " is only resolved through providing a `selected` attr";
}
LogicalResult transform::tune::KnobOp::verify() {
if (auto selected = getSelected()) {
if (auto optionsArray = dyn_cast<ArrayAttr>(getOptions())) {
if (!llvm::is_contained(optionsArray, selected))
return emitOpError("provided `selected` attribute is not an element of "
"`options` array of attributes");
} else
LLVM_DEBUG(DBGS() << "cannot verify `selected` attribute " << selected
<< " is an element of `options` attribute "
<< getOptions());
}
return success();
}
//===----------------------------------------------------------------------===//
// AlternativesOp
//===----------------------------------------------------------------------===//
static ParseResult parseAlternativesOpSelectedRegion(
OpAsmParser &parser, IntegerAttr &selectedRegionAttr,
std::optional<OpAsmParser::UnresolvedOperand> &selectedRegionParam) {
size_t selectedRegionIdx;
OptionalParseResult attrParseRes =
parser.parseOptionalInteger(selectedRegionIdx);
if (attrParseRes.has_value()) {
if (failed(*attrParseRes))
return failure();
selectedRegionAttr = parser.getBuilder().getIndexAttr(selectedRegionIdx);
return success();
}
OpAsmParser::UnresolvedOperand param;
auto paramParseRes = parser.parseOptionalOperand(param);
if (paramParseRes.has_value()) {
if (failed(*paramParseRes))
return failure();
selectedRegionParam = param;
return success();
}
return parser.emitError(parser.getCurrentLocation())
<< "expected either an integer attribute or a transform.param operand";
}
static void printAlternativesOpSelectedRegion(OpAsmPrinter &printer,
Operation *op,
IntegerAttr selectedRegionAttr,
Value selectedRegionParam) {
if (selectedRegionAttr)
printer << selectedRegionAttr.getValue();
if (selectedRegionParam)
printer << selectedRegionParam;
}
OperandRange transform::tune::AlternativesOp::getEntrySuccessorOperands(
RegionSuccessor successor) {
// No operands will be forwarded to the region(s).
return getOperands().slice(0, 0);
}
void transform::tune::AlternativesOp::getSuccessorRegions(
RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
if (point.isParent())
if (auto selectedRegionIdx = getSelectedRegionAttr())
regions.emplace_back(
&getAlternatives()[selectedRegionIdx->getSExtValue()],
Block::BlockArgListType());
else
for (Region &alternative : getAlternatives())
regions.emplace_back(&alternative, Block::BlockArgListType());
else
regions.emplace_back(getOperation(), getOperation()->getResults());
}
void transform::tune::AlternativesOp::getRegionInvocationBounds(
ArrayRef<Attribute> operands, SmallVectorImpl<InvocationBounds> &bounds) {
(void)operands;
bounds.reserve(getNumRegions());
if (auto selectedRegionIdx = getSelectedRegionAttr()) {
bounds.resize(getNumRegions(), InvocationBounds(0, 0));
bounds[selectedRegionIdx->getSExtValue()] = InvocationBounds(1, 1);
} else {
bounds.resize(getNumRegions(), InvocationBounds(0, 1));
}
}
void transform::tune::AlternativesOp::getEffects(
SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
onlyReadsHandle(getSelectedRegionParamMutable(), effects);
producesHandle(getOperation()->getOpResults(), effects);
// TODO: should effects from regions be forwarded?
}
DiagnosedSilenceableFailure
transform::tune::AlternativesOp::apply(transform::TransformRewriter &rewriter,
transform::TransformResults &results,
transform::TransformState &state) {
std::optional<size_t> selectedRegionIdx;
if (auto selectedRegionAttr = getSelectedRegionAttr())
selectedRegionIdx = selectedRegionAttr->getSExtValue();
if (Value selectedRegionParam = getSelectedRegionParam()) {
ArrayRef<Attribute> associatedAttrs = state.getParams(selectedRegionParam);
IntegerAttr selectedRegionAttr;
if (associatedAttrs.size() != 1 ||
!(selectedRegionAttr = dyn_cast<IntegerAttr>(associatedAttrs[0])))
return emitDefiniteFailure()
<< "param should hold exactly one integer attribute, got: "
<< associatedAttrs[0];
selectedRegionIdx = selectedRegionAttr.getValue().getSExtValue();
}
if (!selectedRegionIdx)
return emitDefiniteFailure() << "non-deterministic choice " << getName()
<< " is only resolved through providing a "
"`selected_region` attr/param";
if (*selectedRegionIdx < 0 || *selectedRegionIdx >= getNumRegions())
return emitDefiniteFailure()
<< "'selected_region' attribute/param specifies region at index "
<< *selectedRegionIdx << " while op has only " << getNumRegions()
<< " regions";
Region &selectedRegion = getRegion(*selectedRegionIdx);
auto scope = state.make_region_scope(selectedRegion);
Block &block = selectedRegion.front();
// Apply the region's ops one by one.
for (Operation &transform : block.without_terminator()) {
DiagnosedSilenceableFailure result =
state.applyTransform(cast<transform::TransformOpInterface>(transform));
if (result.isDefiniteFailure())
return result;
if (result.isSilenceableFailure()) {
for (const auto &res : getResults())
results.set(res, {});
return result;
}
}
// Forward the operation mapping for values yielded from the region to the
// values produced by the alternatives op.
transform::detail::forwardTerminatorOperands(&block, state, results);
return DiagnosedSilenceableFailure::success();
}
LogicalResult transform::tune::AlternativesOp::verify() {
for (auto *region : getRegions()) {
auto yieldTerminator =
llvm::dyn_cast_if_present<transform::YieldOp>(region->front().back());
if (!yieldTerminator)
return emitOpError() << "expected '"
<< transform::YieldOp::getOperationName()
<< "' as terminator";
if (yieldTerminator->getNumOperands() != getNumResults())
return yieldTerminator.emitOpError()
<< "expected terminator to have as many operands as the parent op "
"has results";
for (auto [i, operandType, resultType] : llvm::zip_equal(
llvm::seq<unsigned>(0, yieldTerminator->getNumOperands()),
yieldTerminator->getOperands().getType(), getResultTypes())) {
if (operandType == resultType)
continue;
return yieldTerminator.emitOpError()
<< "the type of the terminator operand #" << i
<< " must match the type of the corresponding parent op result ("
<< operandType << " vs " << resultType << ")";
}
}
if (auto selectedRegionAttr = getSelectedRegionAttr()) {
size_t regionIdx = selectedRegionAttr->getSExtValue();
if (regionIdx < 0 || regionIdx >= getNumRegions())
return emitOpError()
<< "'selected_region' attribute specifies region at index "
<< regionIdx << " while op has only " << getNumRegions()
<< " regions";
}
return success();
}