CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
321 lines
13 KiB
C++
321 lines
13 KiB
C++
//===- Utils.cpp - Utilities to support the Func 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements utilities for the Func dialect.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Dialect/Func/Utils/Utils.h"
|
|
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
|
#include "mlir/IR/IRMapping.h"
|
|
#include "mlir/IR/PatternMatch.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/Support/DebugLog.h"
|
|
|
|
#define DEBUG_TYPE "func-utils"
|
|
|
|
using namespace mlir;
|
|
|
|
/// This method creates an inverse mapping of the provided map `oldToNew`.
|
|
/// Given an array where `oldIdxToNewIdx[i] = j` means old index `i` maps
|
|
/// to new index `j`,
|
|
/// This method returns a vector where `result[j]` contains all old indices
|
|
/// that map to new index `j`.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// oldIdxToNewIdx = [0, 1, 2, 2, 3]
|
|
/// getInverseMapping(oldIdxToNewIdx) = [[0], [1], [2, 3], [4]]
|
|
/// ```
|
|
///
|
|
static llvm::SmallVector<llvm::SmallVector<int>>
|
|
getInverseMapping(ArrayRef<int> oldIdxToNewIdx) {
|
|
int numOfNewIdxs = 0;
|
|
if (!oldIdxToNewIdx.empty())
|
|
numOfNewIdxs = 1 + *llvm::max_element(oldIdxToNewIdx);
|
|
llvm::SmallVector<llvm::SmallVector<int>> newToOldIdxs(numOfNewIdxs);
|
|
for (auto [oldIdx, newIdx] : llvm::enumerate(oldIdxToNewIdx))
|
|
newToOldIdxs[newIdx].push_back(oldIdx);
|
|
return newToOldIdxs;
|
|
}
|
|
|
|
/// This method returns a new vector of elements that are mapped from the
|
|
/// `origElements` based on the `newIdxToOldIdxs` mapping. This function assumes
|
|
/// that the `newIdxToOldIdxs` mapping is valid, i.e. for each new index, there
|
|
/// is at least one old index that maps to it. Also, It assumes that mapping to
|
|
/// the same old index has the same element in the `origElements` vector.
|
|
template <typename Element>
|
|
static SmallVector<Element> getMappedElements(
|
|
ArrayRef<Element> origElements,
|
|
const llvm::SmallVector<llvm::SmallVector<int>> &newIdxToOldIdxs) {
|
|
SmallVector<Element> newElements;
|
|
for (const auto &oldIdxs : newIdxToOldIdxs) {
|
|
assert(llvm::all_of(oldIdxs,
|
|
[&origElements](int idx) -> bool {
|
|
return idx >= 0 &&
|
|
static_cast<size_t>(idx) < origElements.size();
|
|
}) &&
|
|
"idx must be less than the number of elements in the original "
|
|
"elements");
|
|
assert(!oldIdxs.empty() && "oldIdx must not be empty");
|
|
Element origTypeToCheck = origElements[oldIdxs.front()];
|
|
assert(llvm::all_of(oldIdxs,
|
|
[&](int idx) -> bool {
|
|
return origElements[idx] == origTypeToCheck;
|
|
}) &&
|
|
"all oldIdxs must be equal");
|
|
newElements.push_back(origTypeToCheck);
|
|
}
|
|
return newElements;
|
|
}
|
|
|
|
FailureOr<func::FuncOp>
|
|
func::replaceFuncWithNewMapping(RewriterBase &rewriter, func::FuncOp funcOp,
|
|
ArrayRef<int> oldArgIdxToNewArgIdx,
|
|
ArrayRef<int> oldResIdxToNewResIdx) {
|
|
// Generate an empty new function operation with the same name as the
|
|
// original.
|
|
assert(funcOp.getNumArguments() == oldArgIdxToNewArgIdx.size() &&
|
|
"oldArgIdxToNewArgIdx must match the number of arguments in the "
|
|
"function");
|
|
assert(
|
|
funcOp.getNumResults() == oldResIdxToNewResIdx.size() &&
|
|
"oldResIdxToNewResIdx must match the number of results in the function");
|
|
|
|
if (!funcOp.getBody().hasOneBlock())
|
|
return rewriter.notifyMatchFailure(
|
|
funcOp, "expected function to have exactly one block");
|
|
|
|
// We may have some duplicate arguments in the old function, i.e.
|
|
// in the mapping `newArgIdxToOldArgIdxs` for some new argument index
|
|
// there may be multiple old argument indices.
|
|
llvm::SmallVector<llvm::SmallVector<int>> newArgIdxToOldArgIdxs =
|
|
getInverseMapping(oldArgIdxToNewArgIdx);
|
|
SmallVector<Type> newInputTypes = getMappedElements(
|
|
funcOp.getFunctionType().getInputs(), newArgIdxToOldArgIdxs);
|
|
|
|
SmallVector<Location> locs;
|
|
for (const auto &oldArgIdxs : newArgIdxToOldArgIdxs)
|
|
locs.push_back(funcOp.getArgument(oldArgIdxs.front()).getLoc());
|
|
|
|
llvm::SmallVector<llvm::SmallVector<int>> newResToOldResIdxs =
|
|
getInverseMapping(oldResIdxToNewResIdx);
|
|
SmallVector<Type> newOutputTypes = getMappedElements(
|
|
funcOp.getFunctionType().getResults(), newResToOldResIdxs);
|
|
|
|
rewriter.setInsertionPoint(funcOp);
|
|
auto newFuncOp = func::FuncOp::create(
|
|
rewriter, funcOp.getLoc(), funcOp.getName(),
|
|
rewriter.getFunctionType(newInputTypes, newOutputTypes));
|
|
|
|
Region &newRegion = newFuncOp.getBody();
|
|
rewriter.createBlock(&newRegion, newRegion.begin(), newInputTypes, locs);
|
|
newFuncOp.setVisibility(funcOp.getVisibility());
|
|
|
|
// Map the arguments of the original function to the new function in
|
|
// the new order and adjust the attributes accordingly.
|
|
IRMapping operandMapper;
|
|
SmallVector<DictionaryAttr> argAttrs, resultAttrs;
|
|
funcOp.getAllArgAttrs(argAttrs);
|
|
for (auto [oldArgIdx, newArgIdx] : llvm::enumerate(oldArgIdxToNewArgIdx))
|
|
operandMapper.map(funcOp.getArgument(oldArgIdx),
|
|
newFuncOp.getArgument(newArgIdx));
|
|
for (auto [newArgIdx, oldArgIdx] : llvm::enumerate(newArgIdxToOldArgIdxs))
|
|
newFuncOp.setArgAttrs(newArgIdx, argAttrs[oldArgIdx.front()]);
|
|
|
|
funcOp.getAllResultAttrs(resultAttrs);
|
|
for (auto [newResIdx, oldResIdx] : llvm::enumerate(newResToOldResIdxs))
|
|
newFuncOp.setResultAttrs(newResIdx, resultAttrs[oldResIdx.front()]);
|
|
|
|
// Clone the operations from the original function to the new function.
|
|
rewriter.setInsertionPointToStart(&newFuncOp.getBody().front());
|
|
for (Operation &op : funcOp.getOps())
|
|
rewriter.clone(op, operandMapper);
|
|
|
|
// Handle the return operation.
|
|
auto returnOp = cast<func::ReturnOp>(
|
|
newFuncOp.getFunctionBody().begin()->getTerminator());
|
|
SmallVector<Value> newReturnValues;
|
|
for (const auto &oldResIdxs : newResToOldResIdxs)
|
|
newReturnValues.push_back(returnOp.getOperand(oldResIdxs.front()));
|
|
|
|
rewriter.setInsertionPoint(returnOp);
|
|
func::ReturnOp::create(rewriter, newFuncOp.getLoc(), newReturnValues);
|
|
rewriter.eraseOp(returnOp);
|
|
|
|
rewriter.eraseOp(funcOp);
|
|
|
|
return newFuncOp;
|
|
}
|
|
|
|
func::CallOp
|
|
func::replaceCallOpWithNewMapping(RewriterBase &rewriter, func::CallOp callOp,
|
|
ArrayRef<int> oldArgIdxToNewArgIdx,
|
|
ArrayRef<int> oldResIdxToNewResIdx) {
|
|
assert(callOp.getNumOperands() == oldArgIdxToNewArgIdx.size() &&
|
|
"oldArgIdxToNewArgIdx must match the number of operands in the call "
|
|
"operation");
|
|
assert(callOp.getNumResults() == oldResIdxToNewResIdx.size() &&
|
|
"oldResIdxToNewResIdx must match the number of results in the call "
|
|
"operation");
|
|
|
|
SmallVector<Value> origOperands = callOp.getOperands();
|
|
SmallVector<llvm::SmallVector<int>> newArgIdxToOldArgIdxs =
|
|
getInverseMapping(oldArgIdxToNewArgIdx);
|
|
SmallVector<Value> newOperandsValues =
|
|
getMappedElements<Value>(origOperands, newArgIdxToOldArgIdxs);
|
|
SmallVector<llvm::SmallVector<int>> newResToOldResIdxs =
|
|
getInverseMapping(oldResIdxToNewResIdx);
|
|
SmallVector<Type> origResultTypes = llvm::to_vector(callOp.getResultTypes());
|
|
SmallVector<Type> newResultTypes =
|
|
getMappedElements<Type>(origResultTypes, newResToOldResIdxs);
|
|
|
|
// Replace the kernel call operation with a new one that has the
|
|
// mapped arguments.
|
|
rewriter.setInsertionPoint(callOp);
|
|
auto newCallOp =
|
|
func::CallOp::create(rewriter, callOp.getLoc(), callOp.getCallee(),
|
|
newResultTypes, newOperandsValues);
|
|
newCallOp.setNoInlineAttr(callOp.getNoInlineAttr());
|
|
for (auto &&[oldResIdx, newResIdx] : llvm::enumerate(oldResIdxToNewResIdx))
|
|
rewriter.replaceAllUsesWith(callOp.getResult(oldResIdx),
|
|
newCallOp.getResult(newResIdx));
|
|
rewriter.eraseOp(callOp);
|
|
|
|
return newCallOp;
|
|
}
|
|
|
|
FailureOr<std::pair<func::FuncOp, func::CallOp>>
|
|
func::deduplicateArgsOfFuncOp(RewriterBase &rewriter, func::FuncOp funcOp,
|
|
ModuleOp moduleOp) {
|
|
SmallVector<func::CallOp> callOps;
|
|
auto traversalResult = moduleOp.walk([&](func::CallOp callOp) {
|
|
if (callOp.getCallee() == funcOp.getSymName()) {
|
|
if (!callOps.empty())
|
|
// Only support one callOp for now
|
|
return WalkResult::interrupt();
|
|
callOps.push_back(callOp);
|
|
}
|
|
return WalkResult::advance();
|
|
});
|
|
|
|
if (traversalResult.wasInterrupted()) {
|
|
LDBG() << "function " << funcOp.getName() << " has more than one callOp";
|
|
return failure();
|
|
}
|
|
|
|
if (callOps.empty()) {
|
|
LDBG() << "function " << funcOp.getName() << " does not have any callOp";
|
|
return failure();
|
|
}
|
|
|
|
func::CallOp callOp = callOps.front();
|
|
|
|
// Create mapping for arguments (deduplicate operands)
|
|
SmallVector<int> oldArgIdxToNewArgIdx(callOp.getNumOperands());
|
|
llvm::DenseMap<Value, int> valueToNewArgIdx;
|
|
for (auto [operandIdx, operand] : llvm::enumerate(callOp.getOperands())) {
|
|
auto [iterator, inserted] = valueToNewArgIdx.insert(
|
|
{operand, static_cast<int>(valueToNewArgIdx.size())});
|
|
// Reduce the duplicate operands and maintain the original order.
|
|
oldArgIdxToNewArgIdx[operandIdx] = iterator->second;
|
|
}
|
|
|
|
bool hasDuplicateOperands =
|
|
valueToNewArgIdx.size() != callOp.getNumOperands();
|
|
if (!hasDuplicateOperands) {
|
|
LDBG() << "function " << funcOp.getName()
|
|
<< " does not have duplicate operands";
|
|
return failure();
|
|
}
|
|
|
|
// Create identity mapping for results (no deduplication needed)
|
|
SmallVector<int> oldResIdxToNewResIdx(callOp.getNumResults());
|
|
for (int resultIdx : llvm::seq<int>(0, callOp.getNumResults()))
|
|
oldResIdxToNewResIdx[resultIdx] = resultIdx;
|
|
|
|
// Apply the transformation to create new function and call operations
|
|
FailureOr<func::FuncOp> newFuncOpOrFailure = replaceFuncWithNewMapping(
|
|
rewriter, funcOp, oldArgIdxToNewArgIdx, oldResIdxToNewResIdx);
|
|
if (failed(newFuncOpOrFailure)) {
|
|
LDBG() << "failed to replace function signature with name "
|
|
<< funcOp.getName() << " with new order";
|
|
return failure();
|
|
}
|
|
|
|
func::CallOp newCallOp = replaceCallOpWithNewMapping(
|
|
rewriter, callOp, oldArgIdxToNewArgIdx, oldResIdxToNewResIdx);
|
|
|
|
return std::make_pair(*newFuncOpOrFailure, newCallOp);
|
|
}
|
|
|
|
FailureOr<func::FuncOp>
|
|
func::lookupFnDecl(SymbolOpInterface symTable, StringRef name,
|
|
FunctionType funcT, SymbolTableCollection *symbolTables) {
|
|
FuncOp func;
|
|
if (symbolTables) {
|
|
func = symbolTables->lookupSymbolIn<FuncOp>(
|
|
symTable, StringAttr::get(symTable->getContext(), name));
|
|
} else {
|
|
func = llvm::dyn_cast_or_null<FuncOp>(
|
|
SymbolTable::lookupSymbolIn(symTable, name));
|
|
}
|
|
|
|
if (!func)
|
|
return func;
|
|
|
|
mlir::FunctionType foundFuncT = func.getFunctionType();
|
|
// Assert the signature of the found function is same as expected
|
|
if (funcT != foundFuncT) {
|
|
return func.emitError("matched function '")
|
|
<< name << "' but with different type: " << foundFuncT
|
|
<< " (expected " << funcT << ")";
|
|
}
|
|
return func;
|
|
}
|
|
|
|
func::FuncOp func::createFnDecl(OpBuilder &b, SymbolOpInterface symTable,
|
|
StringRef name, FunctionType funcT,
|
|
bool setPrivate,
|
|
SymbolTableCollection *symbolTables) {
|
|
OpBuilder::InsertionGuard g(b);
|
|
assert(!symTable->getRegion(0).empty() && "expected non-empty region");
|
|
b.setInsertionPointToStart(&symTable->getRegion(0).front());
|
|
func::FuncOp funcOp =
|
|
func::FuncOp::create(b, symTable->getLoc(), name, funcT);
|
|
if (setPrivate)
|
|
funcOp.setPrivate();
|
|
if (symbolTables) {
|
|
SymbolTable &symbolTable = symbolTables->getSymbolTable(symTable);
|
|
symbolTable.insert(funcOp, symTable->getRegion(0).front().begin());
|
|
}
|
|
return funcOp;
|
|
}
|
|
|
|
FailureOr<func::FuncOp>
|
|
func::lookupOrCreateFnDecl(OpBuilder &b, SymbolOpInterface symTable,
|
|
StringRef funcName, TypeRange paramTypes,
|
|
SymbolTableCollection *symbolTables,
|
|
Type resultType) {
|
|
if (!resultType)
|
|
resultType = IntegerType::get(symTable->getContext(), 64);
|
|
auto funcT = FunctionType::get(b.getContext(), paramTypes, {resultType});
|
|
FailureOr<func::FuncOp> func =
|
|
lookupFnDecl(symTable, funcName, funcT, symbolTables);
|
|
// Failed due to type mismatch.
|
|
if (failed(func))
|
|
return func;
|
|
// Successfully matched existing decl.
|
|
if (*func)
|
|
return *func;
|
|
|
|
return createFnDecl(b, symTable, funcName, funcT,
|
|
/*setPrivate=*/true, symbolTables);
|
|
}
|