| // Copyright 2019 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 | |
| // Implements IREE-specific preprocessing for XLA inputs. | |
| #include "compiler/plugins/input/StableHLO/Conversion/Preprocessing/Passes.h" | |
| #include "compiler/plugins/input/StableHLO/Conversion/Preprocessing/Rewriters.h" | |
| #include "llvm/ADT/TypeSwitch.h" | |
| #include "mlir/Dialect/Arith/IR/Arith.h" | |
| #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" | |
| #include "mlir/Dialect/Func/IR/FuncOps.h" | |
| #include "mlir/IR/BuiltinOps.h" | |
| #include "mlir/IR/BuiltinTypes.h" | |
| #include "mlir/IR/IRMapping.h" | |
| #include "mlir/IR/MLIRContext.h" | |
| #include "mlir/IR/Operation.h" | |
| #include "mlir/IR/PatternMatch.h" | |
| #include "mlir/Support/LogicalResult.h" | |
| #include "mlir/Transforms/GreedyPatternRewriteDriver.h" | |
| #include "stablehlo/dialect/StablehloOps.h" | |
| namespace mlir::iree_compiler::stablehlo { | |
| #define GEN_PASS_DEF_FLATTENTUPLESINCFG | |
| #include "compiler/plugins/input/StableHLO/Conversion/Preprocessing/Passes.h.inc" | |
| namespace { | |
| // Given a set of types, unpack to a list of a types, removing all tuples. | |
| void untupleTypes(TypeRange types, llvm::SmallVectorImpl<Type> &newTypes) { | |
| for (Type type : types) { | |
| if (auto tupleTy = dyn_cast<TupleType>(type)) { | |
| untupleTypes(tupleTy.getTypes(), newTypes); | |
| } else { | |
| newTypes.push_back(type); | |
| } | |
| } | |
| } | |
| template <typename T> | |
| bool hasTuples(T values) { | |
| bool isTuple = false; | |
| for (auto val : values) { | |
| isTuple |= isa<TupleType>(val.getType()); | |
| } | |
| return isTuple; | |
| } | |
| Value processTuple(Type type, Location loc, Block &block, OpBuilder &builder) { | |
| auto tupleType = dyn_cast<TupleType>(type); | |
| if (!tupleType) { | |
| return block.addArgument(type, loc); | |
| } | |
| llvm::SmallVector<Value> values; | |
| values.reserve(tupleType.size()); | |
| for (Type subtype : tupleType.getTypes()) { | |
| values.push_back(processTuple(subtype, loc, block, builder)); | |
| } | |
| return builder.create<mlir::stablehlo::TupleOp>(loc, tupleType, values); | |
| } | |
| void copyOperationAttrs(Operation *oldOp, Operation *newOp) { | |
| for (NamedAttribute oldAttr : oldOp->getAttrs()) { | |
| // Don't copy segment attributes as these correspond to the number operands, | |
| // which may be different. | |
| if (oldAttr.getName() == "operandSegmentSizes" || | |
| oldAttr.getName() == "resultSegmentSizes") | |
| continue; | |
| newOp->setAttr(oldAttr.getName(), oldAttr.getValue()); | |
| } | |
| } | |
| void recursiveUntuple(Value value, Location loc, OpBuilder &builder, | |
| llvm::SmallVectorImpl<Value> &newValues) { | |
| auto tupleType = dyn_cast<TupleType>(value.getType()); | |
| if (!tupleType) { | |
| // We can return the value as is. | |
| newValues.push_back(value); | |
| return; | |
| } | |
| for (auto [idx, subType] : llvm::enumerate(tupleType.getTypes())) { | |
| auto elementOp = builder.create<mlir::stablehlo::GetTupleElementOp>( | |
| loc, subType, value, builder.getI32IntegerAttr(idx)); | |
| recursiveUntuple(elementOp.getResult(), loc, builder, newValues); | |
| } | |
| } | |
| Value recursiveRetuple(Type oldType, Operation::result_range *values, | |
| OpBuilder &builder, Location loc) { | |
| auto tupleType = dyn_cast<TupleType>(oldType); | |
| if (!tupleType) { | |
| Value returnValue = *values->begin(); | |
| *values = {values->begin() + 1, values->end()}; | |
| return returnValue; | |
| } | |
| llvm::SmallVector<Value> subValues; | |
| for (Type subType : tupleType.getTypes()) { | |
| subValues.push_back(recursiveRetuple(subType, values, builder, loc)); | |
| } | |
| return builder.create<mlir::stablehlo::TupleOp>(loc, tupleType, subValues) | |
| .getResult(); | |
| } | |
| template <typename T> | |
| LogicalResult untupleAndLookupValues(T values, | |
| llvm::SmallVectorImpl<Value> &newValues, | |
| OpBuilder &builder, Location loc) { | |
| IRMapping mapping; | |
| for (auto operand : values) { | |
| recursiveUntuple(operand, loc, builder, newValues); | |
| } | |
| return success(); | |
| } | |
| class DetupleReturnOp : public OpRewritePattern<func::ReturnOp> { | |
| using OpRewritePattern::OpRewritePattern; | |
| LogicalResult matchAndRewrite(func::ReturnOp op, | |
| PatternRewriter &builder) const override { | |
| if (!hasTuples(op.getOperands())) | |
| return builder.notifyMatchFailure(op, "No detupling required"); | |
| llvm::SmallVector<Value> newOperands; | |
| if (failed(untupleAndLookupValues(op.getOperands(), newOperands, builder, | |
| op.getLoc()))) { | |
| return builder.notifyMatchFailure(op, "failed to untuple"); | |
| } | |
| builder.create<mlir::func::ReturnOp>(op->getLoc(), newOperands); | |
| builder.eraseOp(op); | |
| return success(); | |
| } | |
| }; | |
| class DetupleCallOp : public OpRewritePattern<func::CallOp> { | |
| using OpRewritePattern::OpRewritePattern; | |
| LogicalResult matchAndRewrite(func::CallOp oldOp, | |
| PatternRewriter &builder) const override { | |
| if (!hasTuples(oldOp.getOperands()) && !hasTuples(oldOp.getResults())) | |
| return builder.notifyMatchFailure(oldOp, "No detupling required"); | |
| llvm::SmallVector<Value> newArgs; | |
| if (failed(untupleAndLookupValues(oldOp.getOperands(), newArgs, builder, | |
| oldOp.getLoc()))) { | |
| return builder.notifyMatchFailure(oldOp, "failed to untuple values"); | |
| } | |
| SmallVector<Type> resultTypes; | |
| untupleTypes(oldOp.getResultTypes(), resultTypes); | |
| auto newOp = builder.create<func::CallOp>( | |
| oldOp->getLoc(), oldOp.getCallee(), resultTypes, newArgs); | |
| copyOperationAttrs(oldOp, newOp); | |
| auto newResults = newOp.getResults(); | |
| llvm::SmallVector<Value> retupledResults; | |
| for (auto oldResult : oldOp.getResults()) { | |
| auto newResult = recursiveRetuple(oldResult.getType(), &newResults, | |
| builder, oldOp->getLoc()); | |
| retupledResults.push_back(newResult); | |
| } | |
| builder.replaceOp(oldOp, retupledResults); | |
| return success(); | |
| } | |
| }; | |
| class DetupleIndirectCallOp : public OpRewritePattern<func::CallIndirectOp> { | |
| using OpRewritePattern::OpRewritePattern; | |
| LogicalResult matchAndRewrite(func::CallIndirectOp oldOp, | |
| PatternRewriter &builder) const override { | |
| if (!hasTuples(oldOp.getOperands()) && !hasTuples(oldOp.getResults())) | |
| return builder.notifyMatchFailure(oldOp, "No detupling required"); | |
| llvm::SmallVector<Value> newArgs; | |
| if (failed(untupleAndLookupValues(oldOp.getOperands(), newArgs, builder, | |
| oldOp.getLoc()))) { | |
| return builder.notifyMatchFailure(oldOp, "failed to untuple values"); | |
| } | |
| auto newOp = builder.create<func::CallIndirectOp>( | |
| oldOp.getLoc(), oldOp.getCallee(), newArgs); | |
| copyOperationAttrs(oldOp, newOp); | |
| builder.replaceOp(oldOp, newOp.getResults()); | |
| return success(); | |
| } | |
| }; | |
| class DetupleBranchOp : public OpRewritePattern<cf::BranchOp> { | |
| using OpRewritePattern::OpRewritePattern; | |
| LogicalResult matchAndRewrite(cf::BranchOp oldOp, | |
| PatternRewriter &builder) const override { | |
| if (!hasTuples(oldOp.getOperands())) | |
| return builder.notifyMatchFailure(oldOp, "No detupling required"); | |
| llvm::SmallVector<Value> newArgs; | |
| if (failed(untupleAndLookupValues(oldOp.getOperands(), newArgs, builder, | |
| oldOp.getLoc()))) { | |
| return builder.notifyMatchFailure(oldOp, "failed to untuple values"); | |
| } | |
| auto newOp = | |
| builder.create<cf::BranchOp>(oldOp.getLoc(), oldOp.getDest(), newArgs); | |
| copyOperationAttrs(oldOp, newOp); | |
| builder.eraseOp(oldOp); | |
| return success(); | |
| } | |
| }; | |
| class DetupleConditionOp : public OpRewritePattern<cf::CondBranchOp> { | |
| using OpRewritePattern::OpRewritePattern; | |
| LogicalResult matchAndRewrite(cf::CondBranchOp oldOp, | |
| PatternRewriter &builder) const override { | |
| if (!hasTuples(oldOp.getOperands())) | |
| return builder.notifyMatchFailure(oldOp, "No detupling required"); | |
| llvm::SmallVector<Value> trueArgs; | |
| if (failed(untupleAndLookupValues(oldOp.getTrueOperands(), trueArgs, | |
| builder, oldOp.getLoc()))) { | |
| return builder.notifyMatchFailure(oldOp, "Failed to detuple true args"); | |
| } | |
| llvm::SmallVector<Value> falseArgs; | |
| if (failed(untupleAndLookupValues(oldOp.getFalseOperands(), falseArgs, | |
| builder, oldOp.getLoc()))) { | |
| return builder.notifyMatchFailure(oldOp, "Failed to detuple false args"); | |
| } | |
| auto newOp = builder.create<cf::CondBranchOp>( | |
| oldOp.getLoc(), oldOp.getCondition(), oldOp.getTrueDest(), trueArgs, | |
| oldOp.getFalseDest(), falseArgs); | |
| copyOperationAttrs(oldOp, newOp); | |
| builder.eraseOp(oldOp); | |
| return success(); | |
| } | |
| }; | |
| LogicalResult convertFunction(func::FuncOp oldFunction, | |
| func::FuncOp newFunction) { | |
| OpBuilder builder(newFunction.getBody()); | |
| IRMapping mapping; | |
| // Check whether has tuple in signature. | |
| bool hasTupleSig = (oldFunction.getArgumentTypes().size() != | |
| newFunction.getArgumentTypes().size()) || | |
| (oldFunction.getResultTypes().size() != | |
| newFunction.getResultTypes().size()); | |
| auto xlaAbiParam = StringAttr::get(newFunction.getContext(), | |
| "xla_entry_computation_parameter_layouts"); | |
| auto xlaAbiLayout = StringAttr::get(newFunction.getContext(), | |
| "xla_entry_computation_result_layout"); | |
| for (NamedAttribute attr : oldFunction->getAttrs()) { | |
| // Currently skipping all arg, result and XLA specific ABI attributes. | |
| if (llvm::is_contained( | |
| {oldFunction.getFunctionTypeAttrName(), xlaAbiParam, xlaAbiLayout}, | |
| attr.getName())) { | |
| continue; | |
| } | |
| // If it has tuples in sig, then skip arg and res attrs. None of the | |
| // existing ones along path that produces tuples are used further, so just | |
| // remove instead of flattening. | |
| if (hasTupleSig && (attr.getName() == oldFunction.getArgAttrsAttrName() || | |
| attr.getName() == oldFunction.getResAttrsAttrName())) | |
| continue; | |
| newFunction->setAttr(attr.getName(), attr.getValue()); | |
| } | |
| newFunction.getBlocks().clear(); | |
| for (Block &oldBlock : oldFunction.getBlocks()) { | |
| Block *newBlock = builder.createBlock(&newFunction.getBody()); | |
| for (BlockArgument oldArg : oldBlock.getArguments()) { | |
| llvm::SmallVector<Type> newTypes; | |
| untupleTypes(oldArg.getType(), newTypes); | |
| Value newTuple = processTuple(oldArg.getType(), oldFunction.getLoc(), | |
| *newBlock, builder); | |
| if (!newTuple) { | |
| return failure(); | |
| } | |
| mapping.map(oldArg, newTuple); | |
| } | |
| mapping.map(&oldBlock, newBlock); | |
| } | |
| // Convert all ops in the blocks. | |
| for (Block &oldBlock : oldFunction.getBlocks()) { | |
| builder.setInsertionPointToEnd(mapping.lookupOrNull(&oldBlock)); | |
| for (Operation &oldOp : oldBlock.getOperations()) { | |
| builder.clone(oldOp, mapping); | |
| } | |
| } | |
| return success(); | |
| } | |
| struct FlattenTuplesInCFG final | |
| : impl::FlattenTuplesInCFGBase<FlattenTuplesInCFG> { | |
| void runOnOperation() override { | |
| ModuleOp module = getOperation(); | |
| MLIRContext *ctx = module.getContext(); | |
| Builder builder(ctx); | |
| // Build a list of (oldFunction, newFunction) for all functions we need to | |
| // replace. This will ensure that when we go to convert function bodies we | |
| // have only new functions defined. | |
| SmallVector<std::pair<func::FuncOp, func::FuncOp>> convertedFunctions; | |
| for (auto oldFunction : module.getOps<func::FuncOp>()) { | |
| FunctionType oldFunctionType = oldFunction.getFunctionType(); | |
| llvm::SmallVector<Type> newInputTypes; | |
| untupleTypes(oldFunctionType.getInputs(), newInputTypes); | |
| llvm::SmallVector<Type> newResultTypes; | |
| untupleTypes(oldFunctionType.getResults(), newResultTypes); | |
| FunctionType newFunctionType = | |
| builder.getFunctionType(newInputTypes, newResultTypes); | |
| func::FuncOp newFunction = | |
| func::FuncOp::create(oldFunction.getLoc(), oldFunction.getName(), | |
| newFunctionType, oldFunction->getDialectAttrs()); | |
| convertedFunctions.push_back({oldFunction, newFunction}); | |
| // Perform the actual body conversion now that we have proper signatures. | |
| if (failed(convertFunction(oldFunction, newFunction))) { | |
| return signalPassFailure(); | |
| } | |
| } | |
| // Replace functions in the module. | |
| for (auto [oldFunction, newFunction] : convertedFunctions) { | |
| oldFunction.erase(); | |
| module.push_back(newFunction); | |
| } | |
| // Run canonicalization patterns to cancel out remaining tuple ops. We need | |
| // to run these manually here because StableHLO does not define | |
| // folds/canonicalization patterns for its ops. | |
| RewritePatternSet patterns(ctx); | |
| patterns.insert<DetupleCallOp, DetupleIndirectCallOp, DetupleConditionOp, | |
| DetupleReturnOp, DetupleBranchOp>(ctx); | |
| populateCanonicalizationPatterns(ctx, &patterns); | |
| if (failed(applyPatternsGreedily(getOperation(), std::move(patterns)))) { | |
| return signalPassFailure(); | |
| } | |
| } | |
| }; | |
| } // namespace | |
| } // namespace mlir::iree_compiler::stablehlo |