blob: 49212b54b83eb3de909fba29eab4603159a9ce33 [file]
// Copyright 2023 The IREE Authors
//
// Licensed 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 <utility>
#include "iree/compiler/Dialect/Flow/IR/FlowOps.h"
#include "iree/compiler/Dialect/Flow/Transforms/Passes.h"
#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Regex.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/Pass/Pass.h"
namespace mlir::iree_compiler::IREE::Flow {
#define GEN_PASS_DEF_INSERTDEBUGTARGETATORDINALPASS
#define GEN_PASS_DEF_INSERTDEBUGTARGETATSYMBOLPASS
#include "iree/compiler/Dialect/Flow/Transforms/Passes.h.inc"
// Filters out non-tensor values for tracing.
static SmallVector<Value> filterNonTensorValues(ValueRange &&range) {
SmallVector<Value> result;
for (auto value : range) {
if (llvm::isa<TensorType>(value.getType()))
result.push_back(value);
}
return result;
}
// Attempts to interpret a pass arg as @<function_name>:<ordinal>, else returns
// a negative ordinal indicating no match.
static std::tuple<std::string, int>
getOrdinalFromDebugTarget(std::string marker) {
if (marker.empty() || marker[0] != '@')
return std::make_tuple("", -1);
SmallVector<StringRef, 2> parts;
auto cropped = marker.substr(1);
llvm::SplitString(llvm::StringRef(cropped), parts, ":");
if (parts.size() != 2)
return std::make_tuple("", -1);
int ordinal;
if (parts[1].getAsInteger(10, ordinal))
return std::make_tuple("", -1);
return std::make_tuple(parts[0].str(), ordinal);
}
// Inserts flow.tensor.trace ops around the specified dispatch op.
static void traceOpWithName(IREE::Flow::DispatchOp dispatchOp,
std::string name) {
OpBuilder builder(dispatchOp);
// Input tensors:
builder.create<IREE::Flow::TensorTraceOp>(
dispatchOp.getLoc(), builder.getStringAttr(name + " inputs"),
filterNonTensorValues(dispatchOp.getArguments()));
// Output tensors:
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointAfter(dispatchOp);
builder.create<IREE::Flow::TensorTraceOp>(
dispatchOp.getLoc(), builder.getStringAttr(name + " outputs"),
filterNonTensorValues(dispatchOp.getResults()));
}
// Breaks the given function on the specified op by simply returning immediately
// after the op. Updates the function signature to match the return type of the
// target operation.
static LogicalResult replaceReturnWithOpResults(mlir::ModuleOp moduleOp,
IREE::Util::FuncOp funcOp,
Operation *op) {
if (!funcOp->isProperAncestor(op))
return failure();
// TODO: Handle nested function calls.
if (!SymbolTable::symbolKnownUseEmpty(funcOp, moduleOp))
return failure();
// TODO: Handle (nested) control flow.
auto funcBlock = op->getBlock();
if (funcBlock->getParentOp() != funcOp ||
&funcOp.getBody().front() != funcBlock)
return failure();
// Collect the op results and create export ops for any tensor results.
OpBuilder builder(funcOp);
auto context = op->getContext();
auto loc = op->getLoc();
auto oldTerminator = funcBlock->getTerminator();
builder.setInsertionPoint(oldTerminator);
SmallVector<Value> exports;
SmallVector<Type> newTypes;
for (auto retVal : op->getResults()) {
if (llvm::isa<TensorType>(retVal.getType())) {
auto type = IREE::HAL::BufferViewType::get(context);
auto exportOp = builder.create<IREE::HAL::TensorExportOp>(
loc, type, retVal, TypeAttr::get(retVal.getType()), /*name=*/nullptr,
/*affinity=*/nullptr);
exports.push_back(exportOp.getResult());
newTypes.push_back(type);
} else {
exports.push_back(retVal);
newTypes.push_back(retVal.getType());
}
}
// Create the new return and update the function type.
IRRewriter rewriter(builder);
rewriter.replaceOpWithNewOp<IREE::Util::ReturnOp>(oldTerminator, exports);
SmallVector<Type> argTypes;
for (const auto &arg : llvm::enumerate(funcOp.getArguments()))
argTypes.push_back(arg.value().getType());
funcOp.setType(FunctionType::get(context,
/*inputs=*/argTypes, /*results=*/newTypes));
funcOp.removeTiedOperandsAttr();
return success();
}
namespace {
// Insert break/tracing by ordinal for the specified function.
struct InsertDebugTargetAtOrdinalPass
: public IREE::Flow::impl::InsertDebugTargetAtOrdinalPassBase<
InsertDebugTargetAtOrdinalPass> {
using IREE::Flow::impl::InsertDebugTargetAtOrdinalPassBase<
InsertDebugTargetAtOrdinalPass>::InsertDebugTargetAtOrdinalPassBase;
void runOnOperation() override {
auto [breakFname, breakOrdinal] =
getOrdinalFromDebugTarget(breakDebugTarget);
auto [traceFname, traceOrdinal] =
getOrdinalFromDebugTarget(traceDebugTarget);
bool foundBreakFunc = breakFname.empty();
bool foundTraceFunc = traceFname.empty();
for (auto it :
llvm::enumerate(getOperation().getOps<mlir::FunctionOpInterface>())) {
mlir::FunctionOpInterface op = it.value();
Operation *operation = op;
// Only look for dispatches in util func ops.
auto funcOp = llvm::dyn_cast<IREE::Util::FuncOp>(operation);
if (!funcOp)
continue;
std::string fName = funcOp.getName().str();
if (fName != breakFname && fName != traceFname)
continue;
int localBreakOrdinal = -1;
if (fName == breakFname) {
localBreakOrdinal = breakOrdinal;
foundBreakFunc = true;
}
int localTraceOrdinal = -1;
if (fName == traceFname) {
foundTraceFunc = true;
localTraceOrdinal = traceOrdinal;
}
auto &bodyRegion = op.getFunctionBody();
auto dispatchOps =
llvm::to_vector<8>(bodyRegion.getOps<IREE::Flow::DispatchOp>());
// Trace on a valid ordinal.
if (localTraceOrdinal >= 0 && localTraceOrdinal < dispatchOps.size()) {
auto traceTarget = dispatchOps[localTraceOrdinal];
// Append the ordinal to the trace name.
std::string entryPointName = traceTarget.getEntryPointName();
traceOpWithName(traceTarget, entryPointName + std::string("::") +
std::to_string(localTraceOrdinal));
}
// Break on a valid ordinal, updating the function signature in the
// process. Currently only a single ordinal is supported so no need to
// check for overlapping breaks.
if (localBreakOrdinal >= 0 && localBreakOrdinal < dispatchOps.size()) {
auto breakTarget = dispatchOps[localBreakOrdinal];
if (failed(replaceReturnWithOpResults(getOperation(), funcOp,
breakTarget)))
return signalPassFailure();
}
}
if (!foundBreakFunc || !foundTraceFunc) {
getOperation()->emitError()
<< "Failed to find breaking or tracing target function";
return signalPassFailure();
}
}
};
// Break/trace by symbol, after outlining dispatch regions and
// deduplication.
struct InsertDebugTargetAtSymbolPass
: public IREE::Flow::impl::InsertDebugTargetAtSymbolPassBase<
InsertDebugTargetAtSymbolPass> {
using IREE::Flow::impl::InsertDebugTargetAtSymbolPassBase<
InsertDebugTargetAtSymbolPass>::InsertDebugTargetAtSymbolPassBase;
void runOnOperation() override {
bool foundBreakFunc = true;
bool foundTraceFunc = true;
// Setup regex for matching symbol names.
llvm::Regex traceMatcher;
if (!traceDebugTarget.empty()) {
foundTraceFunc = false;
traceMatcher = llvm::Regex(traceDebugTarget);
}
llvm::Regex breakMatcher;
if (!breakDebugTarget.empty()) {
foundBreakFunc = false;
breakMatcher = llvm::Regex(breakDebugTarget);
}
for (auto it :
llvm::enumerate(getOperation().getOps<mlir::FunctionOpInterface>())) {
mlir::FunctionOpInterface funcOp = it.value();
// Find the target dispatch to break on and trace on all matching
// dispatches.
IREE::Flow::DispatchOp breakTarget;
funcOp.walk([&](IREE::Flow::DispatchOp dispatchOp) {
std::string entryPointName = dispatchOp.getEntryPointName();
if (traceMatcher.match(entryPointName)) {
foundTraceFunc = true;
traceOpWithName(dispatchOp, entryPointName);
}
if (!breakTarget && breakMatcher.match(entryPointName)) {
foundBreakFunc = true;
breakTarget = dispatchOp;
}
});
// Break on the selected operation (dispatch). Currently this breaks on
// the first occurance of a dispatch that matches the symbol by assuming
// no control flow within the function. This will fail if the target
// dispatch is not found within the entry block of the function.
if (breakTarget) {
Operation *operation = funcOp;
auto mlirFuncOp = dyn_cast<IREE::Util::FuncOp>(operation);
if (!mlirFuncOp || failed(replaceReturnWithOpResults(
getOperation(), mlirFuncOp, breakTarget)))
return signalPassFailure();
}
}
if (!foundBreakFunc || !foundTraceFunc) {
getOperation()->emitError()
<< "Failed to find any breaking or tracing target dispatch";
return signalPassFailure();
}
}
};
} // namespace
} // namespace mlir::iree_compiler::IREE::Flow