//===- TosaInferShapes.cpp ------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// // // Propogate shapes forward along TOSA operations to resolve dynamic shape // operations. // //===----------------------------------------------------------------------===// #include "mlir/Analysis/DataFlowAnalysis.h" #include "mlir/Dialect/StandardOps/IR/Ops.h" #include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Tosa/IR/TosaOps.h" #include "mlir/Dialect/Tosa/Transforms/PassDetail.h" #include "mlir/Dialect/Tosa/Transforms/Passes.h" #include "mlir/Dialect/Tosa/Utils/ShapeUtils.h" #include "mlir/IR/BlockAndValueMapping.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Matchers.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "llvm/Support/FormatVariadic.h" using namespace mlir; using namespace mlir::tosa; namespace { void propagateShapesInRegion(Region ®ion); void propagateShapesToTosaIf(Operation &op) { tosa::IfOp ifOp = dyn_cast(op); if (!ifOp) return; for (auto ®ion : op.getRegions()) { Block &frontBlock = region.front(); if (frontBlock.getNumArguments() + 1 != ifOp.getNumOperands()) return; for (int i = 0, e = frontBlock.getNumArguments(); i < e; i++) { ValueKnowledge operandKnowledge = ValueKnowledge::getKnowledgeFromType( ifOp.getOperand(i + 1).getType()); ValueKnowledge blockKnowledge = ValueKnowledge::getKnowledgeFromType( frontBlock.getArgument(i).getType()); ValueKnowledge joinedKnowledge = ValueKnowledge::join(operandKnowledge, blockKnowledge); if (!joinedKnowledge) continue; frontBlock.getArgument(i).setType(joinedKnowledge.getType()); } propagateShapesInRegion(region); } return; } void propagateShapesInRegion(Region ®ion) { DenseMap shapesStorage; auto setShapes = [&](Value val, Type t) { if (auto st = t.dyn_cast()) shapesStorage[val] = st; else shapesStorage[val] = t; }; auto operandShape = [&](Value val) -> ShapeAdaptor { // Query the WIP mapping rather than the type if set. auto it = shapesStorage.find(val); if (it == shapesStorage.end()) return nullptr; return it->second; }; for (auto &block : region) { for (Operation &op : block) { if (op.getDialect()->getNamespace() != tosa::TosaDialect::getDialectNamespace()) continue; propagateShapesToTosaIf(op); InferShapedTypeOpInterface shapeInterface = dyn_cast(op); if (!shapeInterface) continue; SmallVector returnedShapes; ValueShapeRange range(op.getOperands(), operandShape); if (shapeInterface .inferReturnTypeComponents(op.getContext(), op.getLoc(), range, op.getAttrDictionary(), op.getRegions(), returnedShapes) .succeeded()) { for (auto it : llvm::zip(op.getResults(), returnedShapes)) { Value result = std::get<0>(it); ShapedTypeComponents predictedShape = std::get<1>(it); // Check whether this use case is replaceable. We define an op as // being replaceable if it is used by a ReturnOp or a TosaOp. bool replaceable = true; for (auto user : result.getUsers()) { if (isa(user)) continue; if (user->getDialect()->getNamespace() == tosa::TosaDialect::getDialectNamespace()) continue; replaceable = false; } // Determine the knowledge based on the output type. // TODO: should also query WIP type probably Type resultTy = result.getType(); auto currentKnowledge = ValueKnowledge::getKnowledgeFromType(resultTy); // Compute the knowledge based on the inferred type. auto inferredKnowledge = ValueKnowledge::getPessimisticValueState(); inferredKnowledge.dtype = resultTy.cast().getElementType(); inferredKnowledge.hasRank = predictedShape.hasRank(); if (predictedShape.hasRank()) { for (auto dim : predictedShape.getDims()) { inferredKnowledge.sizes.push_back(dim); } } if (!replaceable) continue; // Compute the new type based on the joined version. auto newKnowledge = ValueKnowledge::join(currentKnowledge, inferredKnowledge); if (!newKnowledge) continue; setShapes(result, newKnowledge.getType()); } } } } // Actually update types with updated shape knowledge. for (auto it : shapesStorage) { auto result = it.second; if (result.hasRank()) { Type t = it.first.getType().cast().clone(result.getDims()); it.first.setType(t); } } } /// Pass that performs shape propagation across TOSA operations. This includes /// migrating to within the regions of if/while operations. struct TosaInferShapes : public TosaInferShapesBase { public: void runOnFunction() override { FuncOp func = getOperation(); IRRewriter rewriter(func.getContext()); propagateShapesInRegion(func.body()); // Insert UnrealizedConversionCasts to guarantee ReturnOp agress with // the FuncOp type. func.walk([&](ReturnOp op) { FuncOp parent = dyn_cast(op->getParentOp()); if (!parent) return; rewriter.setInsertionPoint(op); FunctionType funcTy = func.getType(); auto resultTys = funcTy.getResults(); bool castAdded = false; SmallVector castedValues; for (auto it : llvm::zip(op->getOperands(), resultTys)) { auto operand = std::get<0>(it); auto currentTy = operand.getType(); auto castTy = std::get<1>(it); if (currentTy == castTy) { castedValues.push_back(operand); continue; } castedValues.push_back( rewriter.create(op.getLoc(), castTy, operand) .getResult()); castAdded = true; } if (castAdded) { rewriter.replaceOpWithNewOp(op, castedValues); } }); } }; } // end anonymous namespace std::unique_ptr mlir::tosa::createTosaInferShapesPass() { return std::make_unique(); }