* Copyright 2023 Huawei Technologies Co., Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "akg/Dialect/Affine/Transforms/UnifyShape.h"
namespace mlir {
#ifndef GEN_PASS_DEF_UNIFYSHAPE
#define GEN_PASS_DEF_UNIFYSHAPE
#include "akg/Dialect/Affine/Passes.h.inc"
#endif
}
#include "llvm/ADT/MapVector.h"
#include "llvm/Support/Debug.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
#define DEBUG_TYPE "unify-shape"
namespace mlir {
static constexpr const int kVectorSizeFour = 4;
static constexpr const int kVectorSizeEight = 8;
struct UnifyShapeInfos {
bool skip = false;
bool dynamicReshape = false;
AffineMap newAccessMap;
SmallVector<int64_t, kVectorSizeFour> deletedDim;
llvm::MapVector<AffineMap, SmallVector<Value, kVectorSizeFour>> newShapeMap;
};
class UnifyShapePass : public mlir::impl::UnifyShapeBase<UnifyShapePass> {
public:
UnifyShapePass() = default;
UnifyShapePass(const UnifyShapePass &pass) = default;
UnifyShapePass(const bool allowNonPolyhedralAccess, const bool keepArg) {
this->allowNonPolyhedralAccess = allowNonPolyhedralAccess;
this->keepArgsShape = keepArg;
}
explicit UnifyShapePass(const UnifyShapeOptions &options) : UnifyShapeBase(options) {}
void argsFindAndEraseCascadeShapeOps(Value arg, Operation *userOp) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - work on operation:\n";
userOp->dump();
});
MemRefType newmemrefType;
SmallVector<AffineMap, kVectorSizeFour> reassociationMap;
if (auto csop = dyn_cast<mlir::memref::CollapseShapeOp>(userOp)) {
newmemrefType = cast<MemRefType>(csop.getType());
reassociationMap = csop.getReassociationMaps();
} else if (auto esop = dyn_cast<mlir::memref::ExpandShapeOp>(userOp)) {
newmemrefType = cast<MemRefType>(esop.getType());
reassociationMap = esop.getReassociationMaps();
} else {
llvm::errs() << DEBUG_TYPE << " - This case may never happen.\n"
<< "Enter argsFindAndEraseCascadeShapeOps when op is not Collapse/Expand\n"
<< userOp;
}
arg.setType(newmemrefType);
for (Value resultValue : userOp->getResults()) {
for (Operation *uop : resultValue.getUsers()) {
if (mlir::memref::DimOp dimOp = dyn_cast<mlir::memref::DimOp>(uop)) {
SmallVector<int64_t, kVectorSizeFour> deletedDim = getDeletedDim(reassociationMap);
updateDimOp(dimOp, deletedDim);
}
}
resultValue.replaceAllUsesWith(arg);
}
userOp->erase();
for (Operation *newUserOp : arg.getUsers()) {
if (!isa<mlir::memref::CollapseShapeOp>(newUserOp) && !isa<mlir::memref::ExpandShapeOp>(newUserOp)) {
continue;
}
argsFindAndEraseCascadeShapeOps(arg, newUserOp);
}
}
void argsFindAndEraseExpandAndCollapseShapeOps(const Value &arg) {
if (arg.hasOneUse()) {
for (Operation *userOp : arg.getUsers()) {
if (!isa<mlir::memref::CollapseShapeOp>(userOp) && !isa<mlir::memref::ExpandShapeOp>(userOp)) {
continue;
}
argsFindAndEraseCascadeShapeOps(arg, userOp);
}
} else {
for (Operation *userOp : arg.getUsers()) {
if (!isa<mlir::memref::CollapseShapeOp>(userOp) && !isa<mlir::memref::ExpandShapeOp>(userOp)) {
continue;
}
LLVM_DEBUG(llvm::dbgs() << DEBUG_TYPE << " Do not manage many usage of an argument that include a reshape, on "
<< arg << "\n");
}
}
}
void eraseOpsUsingBlockArguments(mlir::ModuleOp &m) {
m.walk([&](mlir::func::FuncOp fop) {
FunctionType functionType = fop.getFunctionType();
SmallVector<Type, kVectorSizeEight> newArgTypes;
SmallVector<Type, kVectorSizeFour> resultTypes;
FunctionType newFuncType;
resultTypes = llvm::to_vector<4>(functionType.getResults());
assert(resultTypes.empty() &&
"Function result must be empty due to the call of "
"-buffer-results-to-out-params pass");
for (BlockArgument &bbArg : fop.getArguments()) {
LLVM_DEBUG({
llvm::dbgs() << "\n" << DEBUG_TYPE << " - work with BlockArgument:\n";
bbArg.dump();
});
argsFindAndEraseExpandAndCollapseShapeOps(bbArg);
newArgTypes.push_back(bbArg.getType());
}
newFuncType = FunctionType::get(&getContext(), newArgTypes, resultTypes);
fop.setType(newFuncType);
});
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - After eraseOpsUsingBlockArguments:\n";
m.dump();
});
}
void updateDimOp(mlir::memref::DimOp dimOp, const SmallVector<int64_t, kVectorSizeFour> &deletedDim) const {
Value idx = dimOp.getIndex();
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - updateDimOp work on:\n";
dimOp.dump();
idx.dump();
});
if (auto cop = dyn_cast<mlir::arith::ConstantOp>(idx.getDefiningOp())) {
if (auto attr = dyn_cast<IntegerAttr>(cop.getValue())) {
llvm::APInt indexValue = attr.getValue();
llvm::APInt newIndex = indexValue;
for (uint64_t i : deletedDim) {
if (indexValue.uge(i)) {
--newIndex;
}
}
if (indexValue != newIndex) {
Type type = attr.getType();
mlir::OpBuilder builder(cop);
mlir::arith::ConstantOp newcstOp =
builder.create<mlir::arith::ConstantOp>(cop.getLoc(), type, IntegerAttr::get(type, newIndex));
llvm::dbgs() << DEBUG_TYPE
<< " - BECAREFULL: memref.dim will be updated, may cause a wrong code? (if fused dim "
"are dynamic)\nFrom\n";
dimOp.dump();
dimOp->replaceUsesOfWith(idx, newcstOp.getResult());
llvm::dbgs() << "To\n";
dimOp.dump();
}
} else {
llvm::errs() << DEBUG_TYPE << " - updateDimOp cannot update access for:\n"
<< dimOp << "Unkown kind of attribut for" << cop << "May result to a wrong running code...\n";
}
} else {
llvm::errs() << DEBUG_TYPE << " - updateDimOp cannot update access for:\n"
<< dimOp << "Do not come from a mlir::arith::ConstantOp\n"
<< "May result to a wrong running code...\n";
}
LLVM_DEBUG({
llvm::dbgs() << " new DimOp:\n";
dimOp.dump();
});
}
AffineMap getUpdatedAffineMap(AffineMap &aff, const AffineMap &newMapAccess) const {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - getUpdatedAffineMap:\n";
llvm::dbgs() << " Map to update:\n";
aff.dump();
llvm::dbgs() << " with Map to apply:\n";
newMapAccess.dump();
});
aff = newMapAccess.compose(aff);
LLVM_DEBUG({
llvm::dbgs() << " new map:\n";
aff.dump();
});
return aff;
}
SmallVector<int64_t, kVectorSizeFour> getDeletedDim(const SmallVector<AffineMap, kVectorSizeFour> &reshapeMap) const {
SmallVector<int64_t, kVectorSizeFour> deletedDim;
for (unsigned i = 0; i < reshapeMap.size(); ++i) {
AffineMap map = reshapeMap[i];
for (unsigned j = 1; j < map.getNumResults(); ++j) {
int64_t dim = map.getDimPosition(j);
deletedDim.push_back(dim);
}
}
return deletedDim;
}
bool isDynamicReshape(const ArrayRef<int64_t> &shape,
const SmallVector<AffineMap, kVectorSizeFour> &reshapeMap) const {
for (auto m : reshapeMap) {
if (m.getNumResults() > 1) {
for (unsigned j = 0; j < m.getNumResults(); ++j) {
int64_t dim = m.getDimPosition(j);
auto upperbound = shape[dim];
if (::mlir::ShapedType::isDynamic(upperbound)) {
return true;
}
}
}
}
return false;
}
UnifyShapeInfos getNewAffineMapResults(SmallVector<AffineMap, kVectorSizeFour> reshapeMap, Value referenceValue) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - getNewAffineMapResults:\n";
llvm::dbgs() << " Initial mapping:\n";
for (auto m : reshapeMap) {
m.dump();
}
llvm::dbgs() << " reference Value:\n";
referenceValue.dump();
});
UnifyShapeInfos result;
MemRefType referenceType = cast<MemRefType>(referenceValue.getType());
MLIRContext *context = referenceValue.getContext();
Operation *referenceOp = referenceValue.getDefiningOp();
auto referenceShape = referenceType.getShape();
unsigned nbSymbolExpr = 0;
SmallVector<AffineExpr, kVectorSizeFour> newAccesExpr;
result.dynamicReshape = isDynamicReshape(referenceShape, reshapeMap);
for (unsigned i = 0; i < reshapeMap.size(); ++i) {
SmallVector<Value, kVectorSizeFour> symbolicValue;
AffineMap map = reshapeMap[i];
AffineExpr accessRes = map.getResult(0);
AffineExpr shapeRes = mlir::getAffineConstantExpr(1, context);
for (unsigned j = 0; j < map.getNumResults(); ++j) {
int64_t dim = map.getDimPosition(j);
if (j > 0) {
result.deletedDim.push_back(dim);
}
auto upperbound = referenceShape[dim];
if (::mlir::ShapedType::isDynamic(upperbound)) {
if (j > 0 && !this->allowNonPolyhedralAccess) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE
<< " allow-non-polyhedral-access=false prevent removing an operation that will imply "
"potential non polyhedral access function\n";
});
result.skip = true;
return result;
}
if (memref::AllocOp allocOp = dyn_cast<memref::AllocOp>(referenceOp)) {
unsigned dynIndex = referenceType.getDynamicDimIndex(dim);
auto dynamicVar = allocOp.getDynamicSizes()[dynIndex];
symbolicValue.push_back(dynamicVar);
if (j > 0) {
accessRes = accessRes * mlir::getAffineSymbolExpr(nbSymbolExpr, context);
}
shapeRes = shapeRes * mlir::getAffineSymbolExpr(nbSymbolExpr, context);
nbSymbolExpr++;
} else if (memref::ExpandShapeOp ExpandOp = dyn_cast<memref::ExpandShapeOp>(referenceOp)) {
if (j > 0) {
llvm::errs() << DEBUG_TYPE
<< " WARNING -- ExpandShapeOp - cannot compute new AffineMap Access function "
"because of "
"unknown dynamic/symbolic shape\n";
result.skip = true;
return result;
}
} else {
llvm::errs() << DEBUG_TYPE << " getNewAffineMapResults- WARNING!!! This case may never happen\n";
llvm::dbgs() << DEBUG_TYPE
<< " WARNING -- cannot compute new AffineMap Access function because of unknown "
"dynamic/symbolic shape\n";
result.skip = true;
return result;
}
} else {
if (j > 0) {
accessRes = accessRes * upperbound;
}
shapeRes = shapeRes * upperbound;
}
if (j > 0) {
accessRes = accessRes + map.getResult(j);
}
}
newAccesExpr.push_back(accessRes);
SmallVector<AffineMap, 1> newShapeMap = AffineMap::inferFromExprList({shapeRes}, context);
result.newShapeMap[newShapeMap[0]] = symbolicValue;
}
if (reshapeMap.size() == 0) {
if (isa<memref::ExpandShapeOp>(referenceOp)) {
newAccesExpr.push_back(getAffineConstantExpr(0, context));
}
}
SmallVector<AffineMap, 1> newAccessMap = AffineMap::inferFromExprList(newAccesExpr, context);
assert(newAccessMap.size() == 1 && "Generation from a list of AffineExpr must result in only one AffineMap");
LLVM_DEBUG({
llvm::dbgs() << " new access map function:\n";
newAccessMap[0].dump();
llvm::dbgs() << " new shape map:\n";
for (auto shape : result.newShapeMap) {
llvm::dbgs() << shape.first << " with \n";
for (auto dim : shape.second) {
dim.dump();
}
}
});
result.newAccessMap = newAccessMap[0];
return result;
}
mlir::memref::AllocOp createNewAllocOp(mlir::memref::AllocOp allocOp, MemRefType allocType,
const UnifyShapeInfos &shapeInfo) const {
mlir::OpBuilder builder(allocOp);
if (!shapeInfo.dynamicReshape) {
mlir::memref::AllocOp newAlloc =
builder.create<mlir::memref::AllocOp>(allocOp.getLoc(), allocType, allocOp.getDynamicSizes(),
allocOp.getSymbolOperands(), allocOp.getAlignmentAttr());
return newAlloc;
} else {
SmallVector<Value, kVectorSizeFour> dynamicValue;
for (auto shapeMap : shapeInfo.newShapeMap) {
mlir::affine::AffineApplyOp applyOp =
builder.create<mlir::affine::AffineApplyOp>(allocOp.getLoc(), shapeMap.first, shapeMap.second);
dynamicValue.push_back(applyOp.getResult());
}
mlir::memref::AllocOp newAlloc = builder.create<mlir::memref::AllocOp>(
allocOp.getLoc(), allocType, dynamicValue, allocOp.getSymbolOperands(), allocOp.getAlignmentAttr());
return newAlloc;
}
}
void updateAffineOps(Operation *uop, const UnifyShapeInfos &shapeInfo) const {
if (!shapeInfo.dynamicReshape) {
AffineMap aff = AffineMap::get(uop->getContext());
StringRef attrStrName;
if (isa<mlir::affine::AffineLoadOp>(uop)) {
mlir::affine::AffineLoadOp alop = cast<mlir::affine::AffineLoadOp>(uop);
aff = alop.getAffineMap();
attrStrName = affine::AffineLoadOp::getMapAttrStrName();
} else if (isa<mlir::affine::AffineStoreOp>(uop)) {
mlir::affine::AffineStoreOp asop = cast<mlir::affine::AffineStoreOp>(uop);
aff = asop.getAffineMap();
attrStrName = affine::AffineStoreOp::getMapAttrStrName();
}
if (!aff.isEmpty()) {
aff = getUpdatedAffineMap(aff, shapeInfo.newAccessMap);
uop->setAttr(attrStrName, AffineMapAttr::get(aff));
}
} else {
mlir::OpBuilder builder(uop);
if (isa<mlir::affine::AffineLoadOp>(uop)) {
mlir::affine::AffineLoadOp alop = cast<mlir::affine::AffineLoadOp>(uop);
SmallVector<Value, kVectorSizeFour> operands = uop->getOperands();
for (auto shape : shapeInfo.newShapeMap) {
for (auto value : shape.second) {
operands.push_back(value);
}
}
AffineMap aff = alop.getAffineMap();
auto newOp = builder.create<mlir::affine::AffineLoadOp>(
uop->getLoc(), getUpdatedAffineMap(aff, shapeInfo.newAccessMap), operands);
alop.getResult().replaceAllUsesWith(newOp);
uop->erase();
} else if (isa<mlir::affine::AffineStoreOp>(uop)) {
mlir::affine::AffineStoreOp asop = cast<mlir::affine::AffineStoreOp>(uop);
SmallVector<Value, kVectorSizeFour> operands = asop.getIndices();
for (auto shape : shapeInfo.newShapeMap) {
for (auto value : shape.second) {
operands.push_back(value);
}
}
AffineMap aff = asop.getAffineMap();
(void)builder.create<mlir::affine::AffineStoreOp>(uop->getLoc(), asop.getValue(), asop.getMemref(),
getUpdatedAffineMap(aff, shapeInfo.newAccessMap), operands);
uop->erase();
}
}
}
bool updateCopyOps(Operation *uop, const Operation *reshapesop, const MemRefType &newmemreftype,
const UnifyShapeInfos &unifyShapeInfos) {
if (!isa<mlir::memref::CopyOp>(uop)) {
return true;
}
mlir::memref::CopyOp cop = cast<mlir::memref::CopyOp>(uop);
Value source = cop.getSource();
Value target = cop.getTarget();
Operation *sourceOp = source.getDefiningOp();
Operation *targetOp = target.getDefiningOp();
if (sourceOp == reshapesop) {
if (!target.hasOneUse()) {
llvm::errs() << DEBUG_TYPE << " WARNING -- Unsafe operation: target has multiple uses.\n";
return false;
}
if (BlockArgument barg = dyn_cast<BlockArgument>(target)) {
if (keepArgsShape) {
llvm::dbgs() << DEBUG_TYPE << " WARNING -- keepArgsShape=true, cannot update argument function shape\n";
return false;
}
Block *owner = barg.getOwner();
Operation *pop = owner->getParentOp();
if (mlir::func::FuncOp fop = dyn_cast<mlir::func::FuncOp>(pop)) {
FunctionType functionType = fop.getFunctionType();
SmallVector<Type, kVectorSizeEight> newArgTypes;
SmallVector<Type, kVectorSizeFour> resultTypes;
FunctionType newFuncType;
resultTypes = llvm::to_vector<4>(functionType.getResults());
assert(resultTypes.empty() &&
"Function result must be empty due to the call of "
"-buffer-results-to-out-params pass");
for (BlockArgument &bbarg : fop.getArguments()) {
if (bbarg == barg) {
target.setType(newmemreftype);
newArgTypes.push_back(target.getType());
} else {
newArgTypes.push_back(bbarg.getType());
}
}
newFuncType = FunctionType::get(&getContext(), newArgTypes, resultTypes);
fop.setType(newFuncType);
}
}
else if (mlir::memref::AllocOp oldAllocOp = dyn_cast_or_null<mlir::memref::AllocOp>(targetOp)) {
mlir::memref::AllocOp newAlloc = createNewAllocOp(oldAllocOp, newmemreftype, unifyShapeInfos);
target.replaceAllUsesWith(newAlloc);
targetOp->erase();
}
} else if (targetOp == reshapesop) {
llvm::errs() << DEBUG_TYPE
<< " WARNING -- This operation may be inconsistent. Please declare source with appropriate "
"type directly\n";
return false;
} else {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE
<< " WARNING -- unexpected case. Collapse shape use alloc Operation instead of reshape "
"one? Retry\n";
});
return false;
}
return true;
}
void eraseExpandShapeOps(mlir::ModuleOp &m) {
(void)m.walk([&](mlir::memref::ExpandShapeOp esop) {
LLVM_DEBUG({
llvm::dbgs() << "\n" << DEBUG_TYPE << " - work on operation:\n";
esop.dump();
});
Value initValue = esop.getOperands()[0];
if (!isa<BlockArgument>(initValue)) {
Value resultValue = esop.getResult();
SmallVector<AffineMap, kVectorSizeFour> esopMap = esop.getReassociationMaps();
auto unifyShapeInfos = getNewAffineMapResults(esopMap, resultValue);
if (unifyShapeInfos.skip) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - Cannot remove the following Operation:\n";
esop.dump();
});
return WalkResult::skip();
}
if (unifyShapeInfos.dynamicReshape) {
for (Operation *uop : resultValue.getUsers()) {
if (isa<mlir::memref::DimOp>(uop)) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE
<< " - Cannot remove the following Operation because of dynamic shape "
"that was fused and "
"new dim used:\n";
esop.dump();
uop->dump();
});
return WalkResult::skip();
}
}
}
for (Operation *uop : resultValue.getUsers()) {
if (!updateCopyOps(uop, esop, cast<MemRefType>(initValue.getType()), unifyShapeInfos)) {
llvm::dbgs() << DEBUG_TYPE
<< " WARNING -- failed to update corresponding copy Op, a expandShapeOp will be keept\n";
return WalkResult::skip();
}
updateAffineOps(uop, unifyShapeInfos);
if (mlir::memref::DimOp dimOp = dyn_cast<mlir::memref::DimOp>(uop)) {
updateDimOp(dimOp, unifyShapeInfos.deletedDim);
}
}
resultValue.replaceAllUsesWith(initValue);
esop.erase();
}
return WalkResult::advance();
});
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - After eraseExpandShapeOps:\n";
m.dump();
});
}
void eraseCollapseShapeOps(mlir::ModuleOp &m) {
(void)m.walk([&](mlir::memref::CollapseShapeOp csop) {
LLVM_DEBUG({
llvm::dbgs() << "\n" << DEBUG_TYPE << " - work on operation:\n";
csop.dump();
});
Value initValue = csop.getOperand();
if (!isa<BlockArgument>(initValue)) {
MemRefType resultType = cast<MemRefType>(csop.getType());
SmallVector<AffineMap, kVectorSizeFour> csopMap = csop.getReassociationMaps();
auto unifyShapeInfos = getNewAffineMapResults(csopMap, initValue);
if (unifyShapeInfos.skip) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - Cannot remove the following Operation:\n";
csop.dump();
});
return WalkResult::skip();
}
if (unifyShapeInfos.dynamicReshape) {
for (Operation *uop : initValue.getUsers()) {
if (isa<mlir::memref::DimOp>(uop)) {
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE
<< " - Cannot remove the following Operation because of dynamic shape "
"that was fused and "
"new dim used:\n";
csop.dump();
uop->dump();
});
return WalkResult::skip();
}
}
}
Operation *initAlloc = initValue.getDefiningOp();
if (mlir::memref::AllocOp allocOp = dyn_cast_or_null<mlir::memref::AllocOp>(initAlloc)) {
mlir::memref::AllocOp newAlloc = createNewAllocOp(allocOp, resultType, unifyShapeInfos);
for (Operation *uop : initValue.getUsers()) {
if (!updateCopyOps(uop, csop, resultType, unifyShapeInfos)) {
if (!updateCopyOps(uop, allocOp, resultType, unifyShapeInfos)) {
llvm::dbgs() << DEBUG_TYPE
<< " WARNING -- failed to update corresponding copy Op, a collapseShapeOp "
"will be keept\n";
return WalkResult::skip();
}
}
updateAffineOps(uop, unifyShapeInfos);
if (mlir::memref::DimOp dimOp = dyn_cast<mlir::memref::DimOp>(uop)) {
updateDimOp(dimOp, unifyShapeInfos.deletedDim);
}
}
initValue.replaceAllUsesWith(newAlloc);
allocOp->erase();
Value resultValue = csop.getResult();
resultValue.replaceAllUsesWith(newAlloc);
csop.erase();
} else {
llvm::errs() << DEBUG_TYPE << " - WARNING -- collapseShapeOp will be keept - Untreat DefiningOp:\n";
initAlloc->dump();
return WalkResult::skip();
}
}
return WalkResult::advance();
});
LLVM_DEBUG({
llvm::dbgs() << DEBUG_TYPE << " - After eraseCollapseShapeOps:\n";
m.dump();
});
}
void runOnOperation() override {
mlir::ModuleOp m = getOperation();
if (!keepArgsShape) {
eraseOpsUsingBlockArguments(m);
}
eraseExpandShapeOps(m);
eraseCollapseShapeOps(m);
}
};
}
std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>> mlir::createUnifyShapePass() {
return std::make_unique<mlir::UnifyShapePass>();
}
std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>> mlir::createUnifyShapePass(bool allowNonPolyhedralAccess,
bool keepArg = false) {
return std::make_unique<mlir::UnifyShapePass>(allowNonPolyhedralAccess, keepArg);
}