summaryrefslogtreecommitdiff
path: root/mlir/lib
diff options
context:
space:
mode:
Diffstat (limited to 'mlir/lib')
-rw-r--r--mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp146
-rw-r--r--mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp14
-rw-r--r--mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp45
-rw-r--r--mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt1
-rw-r--r--mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp7
-rw-r--r--mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp4
-rw-r--r--mlir/lib/Dialect/Affine/IR/AffineOps.cpp2
-rw-r--r--mlir/lib/Dialect/ArmNeon/Transforms/LowerContractionToSMMLAPattern.cpp6
-rw-r--r--mlir/lib/Dialect/ArmSVE/CMakeLists.txt1
-rw-r--r--mlir/lib/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.cpp54
-rw-r--r--mlir/lib/Dialect/ArmSVE/TransformOps/CMakeLists.txt18
-rw-r--r--mlir/lib/Dialect/ArmSVE/Transforms/CMakeLists.txt1
-rw-r--r--mlir/lib/Dialect/ArmSVE/Transforms/LowerContractionToSVEI8MMPattern.cpp365
-rw-r--r--mlir/lib/Dialect/Bufferization/IR/BufferDeallocationOpInterface.cpp4
-rw-r--r--mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp28
-rw-r--r--mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp17
-rw-r--r--mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp100
-rw-r--r--mlir/lib/Dialect/Linalg/Transforms/DataLayoutPropagation.cpp10
-rw-r--r--mlir/lib/Dialect/Linalg/Transforms/Transforms.cpp3
-rw-r--r--mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp319
-rw-r--r--mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp6
-rw-r--r--mlir/lib/Dialect/Tensor/Extensions/AllExtensions.cpp2
-rw-r--r--mlir/lib/Dialect/Tensor/IR/CMakeLists.txt1
-rw-r--r--mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp3
-rw-r--r--mlir/lib/Dialect/Tensor/IR/TensorOps.cpp78
-rw-r--r--mlir/lib/Dialect/Tensor/Transforms/CMakeLists.txt2
-rw-r--r--mlir/lib/Dialect/Tensor/Transforms/ReshapePatterns.cpp6
-rw-r--r--mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp208
-rw-r--r--mlir/lib/Dialect/Tosa/IR/TosaOps.cpp152
-rw-r--r--mlir/lib/Dialect/Tosa/Transforms/TosaInferShapes.cpp2
-rw-r--r--mlir/lib/Dialect/Transform/IR/TransformOps.cpp176
-rw-r--r--mlir/lib/Dialect/Vector/IR/VectorOps.cpp4
-rw-r--r--mlir/lib/Dialect/Vector/Transforms/LowerVectorShapeCast.cpp412
-rw-r--r--mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp74
-rw-r--r--mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp5
-rw-r--r--mlir/lib/ExecutionEngine/ExecutionEngine.cpp3
-rw-r--r--mlir/lib/IR/OperationSupport.cpp21
-rw-r--r--mlir/lib/Interfaces/ViewLikeInterface.cpp6
-rw-r--r--mlir/lib/Pass/Pass.cpp60
-rw-r--r--mlir/lib/Pass/PassCrashRecovery.cpp3
-rw-r--r--mlir/lib/Reducer/OptReductionPass.cpp2
-rw-r--r--mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp8
-rw-r--r--mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp26
43 files changed, 1803 insertions, 602 deletions
diff --git a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
index 0b39d1404249..016e59dcb744 100644
--- a/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/SparseAnalysis.cpp
@@ -128,34 +128,8 @@ AbstractSparseForwardDataFlowAnalysis::visitOperation(Operation *op) {
operandLattices.push_back(operandLattice);
}
- if (auto call = dyn_cast<CallOpInterface>(op)) {
- // If the call operation is to an external function, attempt to infer the
- // results from the call arguments.
- auto callable =
- dyn_cast_if_present<CallableOpInterface>(call.resolveCallable());
- if (!getSolverConfig().isInterprocedural() ||
- (callable && !callable.getCallableRegion())) {
- visitExternalCallImpl(call, operandLattices, resultLattices);
- return success();
- }
-
- // Otherwise, the results of a call operation are determined by the
- // callgraph.
- const auto *predecessors = getOrCreateFor<PredecessorState>(
- getProgramPointAfter(op), getProgramPointAfter(call));
- // If not all return sites are known, then conservatively assume we can't
- // reason about the data-flow.
- if (!predecessors->allPredecessorsKnown()) {
- setAllToEntryStates(resultLattices);
- return success();
- }
- for (Operation *predecessor : predecessors->getKnownPredecessors())
- for (auto &&[operand, resLattice] :
- llvm::zip(predecessor->getOperands(), resultLattices))
- join(resLattice,
- *getLatticeElementFor(getProgramPointAfter(op), operand));
- return success();
- }
+ if (auto call = dyn_cast<CallOpInterface>(op))
+ return visitCallOperation(call, operandLattices, resultLattices);
// Invoke the operation transfer function.
return visitOperationImpl(op, operandLattices, resultLattices);
@@ -183,24 +157,8 @@ void AbstractSparseForwardDataFlowAnalysis::visitBlock(Block *block) {
if (block->isEntryBlock()) {
// Check if this block is the entry block of a callable region.
auto callable = dyn_cast<CallableOpInterface>(block->getParentOp());
- if (callable && callable.getCallableRegion() == block->getParent()) {
- const auto *callsites = getOrCreateFor<PredecessorState>(
- getProgramPointBefore(block), getProgramPointAfter(callable));
- // If not all callsites are known, conservatively mark all lattices as
- // having reached their pessimistic fixpoints.
- if (!callsites->allPredecessorsKnown() ||
- !getSolverConfig().isInterprocedural()) {
- return setAllToEntryStates(argLattices);
- }
- for (Operation *callsite : callsites->getKnownPredecessors()) {
- auto call = cast<CallOpInterface>(callsite);
- for (auto it : llvm::zip(call.getArgOperands(), argLattices))
- join(std::get<1>(it),
- *getLatticeElementFor(getProgramPointBefore(block),
- std::get<0>(it)));
- }
- return;
- }
+ if (callable && callable.getCallableRegion() == block->getParent())
+ return visitCallableOperation(callable, argLattices);
// Check if the lattices can be determined from region control flow.
if (auto branch = dyn_cast<RegionBranchOpInterface>(block->getParentOp())) {
@@ -248,6 +206,59 @@ void AbstractSparseForwardDataFlowAnalysis::visitBlock(Block *block) {
}
}
+LogicalResult AbstractSparseForwardDataFlowAnalysis::visitCallOperation(
+ CallOpInterface call,
+ ArrayRef<const AbstractSparseLattice *> operandLattices,
+ ArrayRef<AbstractSparseLattice *> resultLattices) {
+ // If the call operation is to an external function, attempt to infer the
+ // results from the call arguments.
+ auto callable =
+ dyn_cast_if_present<CallableOpInterface>(call.resolveCallable());
+ if (!getSolverConfig().isInterprocedural() ||
+ (callable && !callable.getCallableRegion())) {
+ visitExternalCallImpl(call, operandLattices, resultLattices);
+ return success();
+ }
+
+ // Otherwise, the results of a call operation are determined by the
+ // callgraph.
+ const auto *predecessors = getOrCreateFor<PredecessorState>(
+ getProgramPointAfter(call), getProgramPointAfter(call));
+ // If not all return sites are known, then conservatively assume we can't
+ // reason about the data-flow.
+ if (!predecessors->allPredecessorsKnown()) {
+ setAllToEntryStates(resultLattices);
+ return success();
+ }
+ for (Operation *predecessor : predecessors->getKnownPredecessors())
+ for (auto &&[operand, resLattice] :
+ llvm::zip(predecessor->getOperands(), resultLattices))
+ join(resLattice,
+ *getLatticeElementFor(getProgramPointAfter(call), operand));
+ return success();
+}
+
+void AbstractSparseForwardDataFlowAnalysis::visitCallableOperation(
+ CallableOpInterface callable,
+ ArrayRef<AbstractSparseLattice *> argLattices) {
+ Block *entryBlock = &callable.getCallableRegion()->front();
+ const auto *callsites = getOrCreateFor<PredecessorState>(
+ getProgramPointBefore(entryBlock), getProgramPointAfter(callable));
+ // If not all callsites are known, conservatively mark all lattices as
+ // having reached their pessimistic fixpoints.
+ if (!callsites->allPredecessorsKnown() ||
+ !getSolverConfig().isInterprocedural()) {
+ return setAllToEntryStates(argLattices);
+ }
+ for (Operation *callsite : callsites->getKnownPredecessors()) {
+ auto call = cast<CallOpInterface>(callsite);
+ for (auto it : llvm::zip(call.getArgOperands(), argLattices))
+ join(std::get<1>(it),
+ *getLatticeElementFor(getProgramPointBefore(entryBlock),
+ std::get<0>(it)));
+ }
+}
+
void AbstractSparseForwardDataFlowAnalysis::visitRegionSuccessors(
ProgramPoint *point, RegionBranchOpInterface branch,
RegionBranchPoint successor, ArrayRef<AbstractSparseLattice *> lattices) {
@@ -512,31 +523,34 @@ AbstractSparseBackwardDataFlowAnalysis::visitOperation(Operation *op) {
if (op->hasTrait<OpTrait::ReturnLike>()) {
// Going backwards, the operands of the return are derived from the
// results of all CallOps calling this CallableOp.
- if (auto callable = dyn_cast<CallableOpInterface>(op->getParentOp())) {
- const PredecessorState *callsites = getOrCreateFor<PredecessorState>(
- getProgramPointAfter(op), getProgramPointAfter(callable));
- if (callsites->allPredecessorsKnown()) {
- for (Operation *call : callsites->getKnownPredecessors()) {
- SmallVector<const AbstractSparseLattice *> callResultLattices =
- getLatticeElementsFor(getProgramPointAfter(op),
- call->getResults());
- for (auto [op, result] :
- llvm::zip(operandLattices, callResultLattices))
- meet(op, *result);
- }
- } else {
- // If we don't know all the callers, we can't know where the
- // returned values go. Note that, in particular, this will trigger
- // for the return ops of any public functions.
- setAllToExitStates(operandLattices);
- }
- return success();
- }
+ if (auto callable = dyn_cast<CallableOpInterface>(op->getParentOp()))
+ return visitCallableOperation(op, callable, operandLattices);
}
return visitOperationImpl(op, operandLattices, resultLattices);
}
+LogicalResult AbstractSparseBackwardDataFlowAnalysis::visitCallableOperation(
+ Operation *op, CallableOpInterface callable,
+ ArrayRef<AbstractSparseLattice *> operandLattices) {
+ const PredecessorState *callsites = getOrCreateFor<PredecessorState>(
+ getProgramPointAfter(op), getProgramPointAfter(callable));
+ if (callsites->allPredecessorsKnown()) {
+ for (Operation *call : callsites->getKnownPredecessors()) {
+ SmallVector<const AbstractSparseLattice *> callResultLattices =
+ getLatticeElementsFor(getProgramPointAfter(op), call->getResults());
+ for (auto [op, result] : llvm::zip(operandLattices, callResultLattices))
+ meet(op, *result);
+ }
+ } else {
+ // If we don't know all the callers, we can't know where the
+ // returned values go. Note that, in particular, this will trigger
+ // for the return ops of any public functions.
+ setAllToExitStates(operandLattices);
+ }
+ return success();
+}
+
void AbstractSparseBackwardDataFlowAnalysis::visitRegionSuccessors(
RegionBranchOpInterface branch,
ArrayRef<AbstractSparseLattice *> operandLattices) {
diff --git a/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp b/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp
index dea3d99704d9..01ca5e99a9af 100644
--- a/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp
+++ b/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp
@@ -543,14 +543,20 @@ LogicalResult GPUPrintfOpToVPrintfLowering::matchAndRewrite(
// the device code, not the host code
auto moduleOp = gpuPrintfOp->getParentOfType<gpu::GPUModuleOp>();
+ // Create a valid global location removing any metadata attached to the
+ // location as debug info metadata inside of a function cannot be used outside
+ // of that function.
+ Location globalLoc = loc->findInstanceOfOrUnknown<FileLineColLoc>();
+
auto vprintfType =
LLVM::LLVMFunctionType::get(rewriter.getI32Type(), {ptrType, ptrType});
- LLVM::LLVMFuncOp vprintfDecl =
- getOrDefineFunction(moduleOp, loc, rewriter, "vprintf", vprintfType);
+ LLVM::LLVMFuncOp vprintfDecl = getOrDefineFunction(
+ moduleOp, globalLoc, rewriter, "vprintf", vprintfType);
// Create the global op or find an existing one.
- LLVM::GlobalOp global = getOrCreateStringConstant(
- rewriter, loc, moduleOp, llvmI8, "printfFormat_", adaptor.getFormat());
+ LLVM::GlobalOp global =
+ getOrCreateStringConstant(rewriter, globalLoc, moduleOp, llvmI8,
+ "printfFormat_", adaptor.getFormat());
// Get a pointer to the format string's first element
Value globalPtr = rewriter.create<LLVM::AddressOfOp>(loc, global);
diff --git a/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp b/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp
index 78e6ebb523a4..46db5d3fdca3 100644
--- a/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp
+++ b/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp
@@ -464,27 +464,39 @@ LogicalResult GPUShuffleConversion::matchAndRewrite(
template <typename UniformOp, typename NonUniformOp>
static Value createGroupReduceOpImpl(OpBuilder &builder, Location loc,
- Value arg, bool isGroup, bool isUniform) {
+ Value arg, bool isGroup, bool isUniform,
+ std::optional<uint32_t> clusterSize) {
Type type = arg.getType();
auto scope = mlir::spirv::ScopeAttr::get(builder.getContext(),
isGroup ? spirv::Scope::Workgroup
: spirv::Scope::Subgroup);
- auto groupOp = spirv::GroupOperationAttr::get(builder.getContext(),
- spirv::GroupOperation::Reduce);
+ auto groupOp = spirv::GroupOperationAttr::get(
+ builder.getContext(), clusterSize.has_value()
+ ? spirv::GroupOperation::ClusteredReduce
+ : spirv::GroupOperation::Reduce);
if (isUniform) {
return builder.create<UniformOp>(loc, type, scope, groupOp, arg)
.getResult();
}
- return builder.create<NonUniformOp>(loc, type, scope, groupOp, arg, Value{})
+
+ Value clusterSizeValue;
+ if (clusterSize.has_value())
+ clusterSizeValue = builder.create<spirv::ConstantOp>(
+ loc, builder.getI32Type(),
+ builder.getIntegerAttr(builder.getI32Type(), *clusterSize));
+
+ return builder
+ .create<NonUniformOp>(loc, type, scope, groupOp, arg, clusterSizeValue)
.getResult();
}
-static std::optional<Value> createGroupReduceOp(OpBuilder &builder,
- Location loc, Value arg,
- gpu::AllReduceOperation opType,
- bool isGroup, bool isUniform) {
+static std::optional<Value>
+createGroupReduceOp(OpBuilder &builder, Location loc, Value arg,
+ gpu::AllReduceOperation opType, bool isGroup,
+ bool isUniform, std::optional<uint32_t> clusterSize) {
enum class ElemType { Float, Boolean, Integer };
- using FuncT = Value (*)(OpBuilder &, Location, Value, bool, bool);
+ using FuncT = Value (*)(OpBuilder &, Location, Value, bool, bool,
+ std::optional<uint32_t>);
struct OpHandler {
gpu::AllReduceOperation kind;
ElemType elemType;
@@ -548,7 +560,7 @@ static std::optional<Value> createGroupReduceOp(OpBuilder &builder,
for (const OpHandler &handler : handlers)
if (handler.kind == opType && elementType == handler.elemType)
- return handler.func(builder, loc, arg, isGroup, isUniform);
+ return handler.func(builder, loc, arg, isGroup, isUniform, clusterSize);
return std::nullopt;
}
@@ -571,7 +583,7 @@ public:
auto result =
createGroupReduceOp(rewriter, op.getLoc(), adaptor.getValue(), *opType,
- /*isGroup*/ true, op.getUniform());
+ /*isGroup*/ true, op.getUniform(), std::nullopt);
if (!result)
return failure();
@@ -589,16 +601,17 @@ public:
LogicalResult
matchAndRewrite(gpu::SubgroupReduceOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
- if (op.getClusterSize())
+ if (op.getClusterStride() > 1) {
return rewriter.notifyMatchFailure(
- op, "lowering for clustered reduce not implemented");
+ op, "lowering for cluster stride > 1 is not implemented");
+ }
if (!isa<spirv::ScalarType>(adaptor.getValue().getType()))
return rewriter.notifyMatchFailure(op, "reduction type is not a scalar");
- auto result = createGroupReduceOp(rewriter, op.getLoc(), adaptor.getValue(),
- adaptor.getOp(),
- /*isGroup=*/false, adaptor.getUniform());
+ auto result = createGroupReduceOp(
+ rewriter, op.getLoc(), adaptor.getValue(), adaptor.getOp(),
+ /*isGroup=*/false, adaptor.getUniform(), op.getClusterSize());
if (!result)
return failure();
diff --git a/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt b/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt
index 330474a718e3..8e2620029c35 100644
--- a/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt
+++ b/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt
@@ -35,6 +35,7 @@ add_mlir_conversion_library(MLIRVectorToLLVMPass
MLIRVectorToLLVM
MLIRArmNeonDialect
+ MLIRArmNeonTransforms
MLIRArmSVEDialect
MLIRArmSVETransforms
MLIRAMXDialect
diff --git a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp
index 0ee6dce9ee94..293e01a5bf4d 100644
--- a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp
+++ b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp
@@ -14,6 +14,7 @@
#include "mlir/Dialect/AMX/Transforms.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/ArmNeon/ArmNeonDialect.h"
+#include "mlir/Dialect/ArmNeon/Transforms.h"
#include "mlir/Dialect/ArmSVE/IR/ArmSVEDialect.h"
#include "mlir/Dialect/ArmSVE/Transforms/Transforms.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
@@ -82,6 +83,12 @@ void ConvertVectorToLLVMPass::runOnOperation() {
populateVectorStepLoweringPatterns(patterns);
populateVectorRankReducingFMAPattern(patterns);
populateVectorGatherLoweringPatterns(patterns);
+ if (armI8MM) {
+ if (armNeon)
+ arm_neon::populateLowerContractionToSMMLAPatternPatterns(patterns);
+ if (armSVE)
+ populateLowerContractionToSVEI8MMPatternPatterns(patterns);
+ }
(void)applyPatternsGreedily(getOperation(), std::move(patterns));
}
diff --git a/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp b/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp
index fe53d0324936..01cc50014838 100644
--- a/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp
@@ -402,9 +402,7 @@ isVectorizableLoopBodyWithOpCond(AffineForOp loop,
return !VectorType::isValidElementType(type);
}))
return true;
- return llvm::any_of(op.getResultTypes(), [](Type type) {
- return !VectorType::isValidElementType(type);
- });
+ return !llvm::all_of(op.getResultTypes(), VectorType::isValidElementType);
});
SmallVector<NestedMatch, 8> opsMatched;
types.match(forOp, &opsMatched);
diff --git a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
index 2364f8957992..8a708eb29210 100644
--- a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
+++ b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
@@ -2667,7 +2667,7 @@ AffineParallelOp mlir::affine::getAffineParallelInductionVarOwner(Value val) {
if (!ivArg || !ivArg.getOwner())
return nullptr;
Operation *containingOp = ivArg.getOwner()->getParentOp();
- auto parallelOp = dyn_cast<AffineParallelOp>(containingOp);
+ auto parallelOp = dyn_cast_if_present<AffineParallelOp>(containingOp);
if (parallelOp && llvm::is_contained(parallelOp.getIVs(), val))
return parallelOp;
return nullptr;
diff --git a/mlir/lib/Dialect/ArmNeon/Transforms/LowerContractionToSMMLAPattern.cpp b/mlir/lib/Dialect/ArmNeon/Transforms/LowerContractionToSMMLAPattern.cpp
index 2a1271dfd6bd..5ce3d2b28aeb 100644
--- a/mlir/lib/Dialect/ArmNeon/Transforms/LowerContractionToSMMLAPattern.cpp
+++ b/mlir/lib/Dialect/ArmNeon/Transforms/LowerContractionToSMMLAPattern.cpp
@@ -56,6 +56,10 @@ public:
// Avoid 0-D vectors and 1-D rhs:
if (!lhsType.hasRank() || !rhsType.hasRank() || rhsType.getRank() < 2)
return failure();
+ // This codegen does not work for scalable vectors. Return failure so this
+ // pattern is not accidentally chosen over patterns that lower to ArmSVE.
+ if (lhsType.isScalable() || rhsType.isScalable())
+ return failure();
auto dimM = lhsType.getRank() == 1 ? 1 : lhsType.getDimSize(0);
auto dimN = rhsType.getDimSize(0);
auto dimK = rhsType.getDimSize(1);
@@ -238,5 +242,5 @@ public:
void mlir::arm_neon::populateLowerContractionToSMMLAPatternPatterns(
RewritePatternSet &patterns) {
MLIRContext *context = patterns.getContext();
- patterns.add<LowerContractionToSMMLAPattern>(context, /*benefit=*/1);
+ patterns.add<LowerContractionToSMMLAPattern>(context, /*benefit=*/2);
}
diff --git a/mlir/lib/Dialect/ArmSVE/CMakeLists.txt b/mlir/lib/Dialect/ArmSVE/CMakeLists.txt
index 9f57627c321f..cb1e9d01821a 100644
--- a/mlir/lib/Dialect/ArmSVE/CMakeLists.txt
+++ b/mlir/lib/Dialect/ArmSVE/CMakeLists.txt
@@ -1,2 +1,3 @@
add_subdirectory(IR)
add_subdirectory(Transforms)
+add_subdirectory(TransformOps)
diff --git a/mlir/lib/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.cpp b/mlir/lib/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.cpp
new file mode 100644
index 000000000000..b2ca4fc1eaa8
--- /dev/null
+++ b/mlir/lib/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.cpp
@@ -0,0 +1,54 @@
+//===- ArmSVEVectorTransformOps.cpp - Implementation transform ops -------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.h"
+
+#include "mlir/Dialect/ArmSVE/IR/ArmSVEDialect.h"
+#include "mlir/Dialect/ArmSVE/Transforms/Transforms.h"
+#include "mlir/Dialect/Transform/IR/TransformDialect.h"
+
+using namespace mlir;
+
+//===----------------------------------------------------------------------===//
+// Apply...PatternsOp
+//===----------------------------------------------------------------------===//
+
+void transform::ApplyArmSVELowerContractionPatternsOp::populatePatterns(
+ RewritePatternSet &patterns) {
+ mlir::populateLowerContractionToSVEI8MMPatternPatterns(patterns);
+}
+
+//===----------------------------------------------------------------------===//
+// Transform op registration
+//===----------------------------------------------------------------------===//
+
+namespace {
+class ArmSVEVectorTransformDialectExtension
+ : public transform::TransformDialectExtension<
+ ArmSVEVectorTransformDialectExtension> {
+public:
+ MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(
+ ArmSVEVectorTransformDialectExtension)
+
+ ArmSVEVectorTransformDialectExtension() {
+ declareGeneratedDialect<arm_sve::ArmSVEDialect>();
+ registerTransformOps<
+#define GET_OP_LIST
+#include "mlir/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.cpp.inc"
+ >();
+ }
+};
+} // namespace
+
+#define GET_OP_CLASSES
+#include "mlir/Dialect/ArmSVE/TransformOps/ArmSVEVectorTransformOps.cpp.inc"
+
+void mlir::arm_sve::registerTransformDialectExtension(
+ DialectRegistry &registry) {
+ registry.addExtensions<ArmSVEVectorTransformDialectExtension>();
+}
diff --git a/mlir/lib/Dialect/ArmSVE/TransformOps/CMakeLists.txt b/mlir/lib/Dialect/ArmSVE/TransformOps/CMakeLists.txt
new file mode 100644
index 000000000000..235fe8f5ca0c
--- /dev/null
+++ b/mlir/lib/Dialect/ArmSVE/TransformOps/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_mlir_dialect_library(MLIRArmSVEVectorTransformOps
+ ArmSVEVectorTransformOps.cpp
+
+ ADDITIONAL_HEADER_DIRS
+ ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/ArmSVE/TransformOps
+
+ DEPENDS
+ MLIRArmSVEVectorTransformOpsIncGen
+
+ LINK_LIBS PUBLIC
+ MLIRIR
+ MLIRLLVMCommonConversion
+ MLIRLLVMDialect
+ MLIRVectorDialect
+ MLIRTransformDialect
+ MLIRArmSVEDialect
+ MLIRArmSVETransforms
+ )
diff --git a/mlir/lib/Dialect/ArmSVE/Transforms/CMakeLists.txt b/mlir/lib/Dialect/ArmSVE/Transforms/CMakeLists.txt
index a70c489a51fe..65f98b44b1b6 100644
--- a/mlir/lib/Dialect/ArmSVE/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/ArmSVE/Transforms/CMakeLists.txt
@@ -1,6 +1,7 @@
add_mlir_dialect_library(MLIRArmSVETransforms
LegalizeForLLVMExport.cpp
LegalizeVectorStorage.cpp
+ LowerContractionToSVEI8MMPattern.cpp
DEPENDS
MLIRArmSVEConversionsIncGen
diff --git a/mlir/lib/Dialect/ArmSVE/Transforms/LowerContractionToSVEI8MMPattern.cpp b/mlir/lib/Dialect/ArmSVE/Transforms/LowerContractionToSVEI8MMPattern.cpp
new file mode 100644
index 000000000000..b1233c5c06eb
--- /dev/null
+++ b/mlir/lib/Dialect/ArmSVE/Transforms/LowerContractionToSVEI8MMPattern.cpp
@@ -0,0 +1,365 @@
+//===- LowerContractionToSMMLAPattern.cpp - Contract to SMMLA ---*- C++ -*-===//
+//
+// 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 lowering patterns from vector.contract to operations
+// that map to instructions from the SVE FEAT_I8MM extension.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/ArmSVE/IR/ArmSVEDialect.h"
+#include "mlir/Dialect/ArmSVE/Transforms/Transforms.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Dialect/Utils/IndexingUtils.h"
+#include "mlir/Dialect/Vector/IR/VectorOps.h"
+#include "mlir/IR/AffineMap.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
+
+#include "mlir/Dialect/UB/IR/UBOps.h"
+
+#define DEBUG_TYPE "lower-contract-to-arm-sve-i8mm"
+
+using namespace mlir;
+
+namespace {
+// Get the operand of a `vector.contract`. This function is intended to abstract
+// away from the particular way a value is extended before feeding it into the
+// `vector.contract` - via zero-extend or an explicit or implicit sign-extend
+// (for implicit sign-extension see `vector.contract` documentation).
+//
+// The template parameter `Op` indicates the extension operation (explicit or
+// implicit) for which we are checking.
+//
+// Return success only for extensions from `i8` to `i32`.
+template <typename Op>
+std::optional<Value> getExtOperand(Value v, Type i8Ty, Type i32Ty) {
+
+ static_assert(llvm::is_one_of<Op, arith::ExtSIOp, arith::ExtUIOp>::value,
+ "Must be instantiated with either sign- or zero- extension op");
+
+ // If the operand is not defined by an explicit extend operation of the
+ // accepted operation type allow for an implicit sign-extension.
+ auto extOp = dyn_cast_or_null<Op>(v.getDefiningOp());
+ if (!extOp) {
+ if constexpr (std::is_same<Op, arith::ExtSIOp>::value) {
+ auto vTy = cast<VectorType>(v.getType());
+ if (vTy.getElementType() != i8Ty)
+ return {};
+ return v;
+ }
+ return {};
+ }
+
+ // If the operand is defined by an explicit extend operation of the accepted
+ // operation type, check it's extended from `i8` to `i32`.
+ auto inOp = extOp.getIn();
+ auto inTy = dyn_cast<VectorType>(inOp.getType());
+ if (!inTy || inTy.getElementType() != i8Ty)
+ return {};
+
+ auto outTy = dyn_cast<VectorType>(extOp.getType());
+ if (!outTy || outTy.getElementType() != i32Ty)
+ return {};
+
+ return inOp;
+}
+
+// Designate the operation (resp. instruction) used to do sub-tile matrix
+// multiplications.
+enum class MMLA {
+ Signed, // smmla
+ Unsigned, // ummla
+ Mixed, // usmmla
+ MixedSwapped // usmmla with LHS and RHS swapped
+};
+
+// Create the matrix mulitply and accumulate operation according to `op`.
+Value createMMLA(PatternRewriter &rewriter, MMLA op, Location loc,
+ mlir::VectorType accType, Value acc, Value lhs, Value rhs) {
+ switch (op) {
+ case MMLA::Signed:
+ return rewriter.create<arm_sve::SmmlaOp>(loc, accType, acc, lhs, rhs);
+ case MMLA::Unsigned:
+ return rewriter.create<arm_sve::UmmlaOp>(loc, accType, acc, lhs, rhs);
+ case MMLA::Mixed:
+ return rewriter.create<arm_sve::UsmmlaOp>(loc, accType, acc, lhs, rhs);
+ case MMLA::MixedSwapped:
+ // The accumulator comes transposed and the result will be transposed
+ // later, so all we have to do here is swap the operands.
+ return rewriter.create<arm_sve::UsmmlaOp>(loc, accType, acc, rhs, lhs);
+ }
+}
+
+/// Lower a contraction operation that performs a matrix multiplication
+/// of two 8-bit integer matrix tiles with logical dimensions <Mx8> and <8x[N]>
+/// for the left-hand side and the right-hand side, respectively,
+/// yielding a <Mx[N]> 32-bit integer result.
+///
+/// The operands' shapes are such that the operands can be evenly split into
+/// sub-tiles with dimensions as expected by the targeted FEAT_I8MM
+/// instructions. The intent is that M and N are chosen (by higher level
+/// transforms) in such a way as to maximise register usage. The main use case
+/// we envision as of now is MMT4D, thus the RHS operand is expected
+/// pre-transposed.
+///
+/// The matrix multiplication is performed by unrolling the usual tiled matrix
+/// multiplication algorithm using sub-tiles with dimensions <2x8> for the LHS,
+/// <8x[2]> for the RHS, and <2x[2]> for the result and the input accumulator.
+///
+/// One way to illustrate the operation is as follows:
+///
+/// RHS<8x[N]>: <8x[2]> <8x[2]> ... <8x[2]>
+/// +-----------------------------
+/// LHS<Mx8>: <2x8> | <2x[2]> <2x[2]> ... <2x[2]>
+/// <2x8> | <2x[2]> <2x[2]> ... <2x[2]>
+/// ... | ... ... ... ...
+/// <2x8> | <2x[2]> <2x[2]> ... <2x[2]>
+///
+/// The RHS operand is unpacked into N/2 values, each representing a sequence of
+/// VSCALE number of sub-tiles with dimensions <8x2>.
+/// The LHS operand is initially unpacked into M/2 values, each representing a
+/// sub-tile with dimensions <2x8>, and then each such sub-tile is replicated
+/// VSCALE times.
+/// Multiplying thus replicated LHS sub-tile by the corresponding RHS sub-tile
+/// correctly computes an entire result sub-tile.
+class LowerContractionToSVEI8MMPattern
+ : public OpRewritePattern<vector::ContractionOp> {
+public:
+ using OpRewritePattern::OpRewritePattern;
+ LogicalResult matchAndRewrite(vector::ContractionOp op,
+ PatternRewriter &rewriter) const override {
+
+ Location loc = op.getLoc();
+ mlir::VectorType lhsType = op.getLhsType();
+ mlir::VectorType rhsType = op.getRhsType();
+
+ // Check the rank the types so we can safely examine their dimensions.
+ if (lhsType.getRank() != 2 || rhsType.getRank() != 2)
+ return rewriter.notifyMatchFailure(op, "non-matching operand shape");
+
+ auto M = lhsType.getDimSize(0);
+ auto N = rhsType.getDimSize(0);
+ auto K = rhsType.getDimSize(1);
+
+ // Check the operands have the expected shape:
+ // * for LHS: fixed vector MxK
+ // * for RHS: scalable vector [N]xK
+ // * K == 8
+ // * M and N even and at least 2
+ if (lhsType.isScalable() || !rhsType.getScalableDims()[0] ||
+ rhsType.getScalableDims()[1] || lhsType.getDimSize(1) != K || K != 8 ||
+ M < 2 || M % 2 != 0 || N < 2 || N % 2 != 0 ||
+ !rhsType.getScalableDims()[0])
+ return rewriter.notifyMatchFailure(op, "non-matching operand shape");
+
+ // Check permutation maps. For now only accept
+ // lhs: (d0, d1, d2) -> (d0, d2)
+ // rhs: (d0, d1, d2) -> (d1, d2)
+ // acc: (d0, d1, d2) -> (d0, d1)
+ // This corresponds to matrix multiplication with transposed RHS.
+ if (op.getIndexingMapsArray()[0] !=
+ AffineMap::getMultiDimMapWithTargets(3, ArrayRef{0u, 2u},
+ op.getContext()) ||
+ op.getIndexingMapsArray()[1] !=
+ AffineMap::getMultiDimMapWithTargets(3, ArrayRef{1u, 2u},
+ op.getContext()) ||
+ op.getIndexingMapsArray()[2] !=
+ AffineMap::getMultiDimMapWithTargets(3, ArrayRef{0u, 1u},
+ op.getContext()))
+ return rewriter.notifyMatchFailure(op, "non-matching permutation maps");
+
+ // Check iterator types for matrix multiplication.
+ auto itTypes = op.getIteratorTypesArray();
+ if (itTypes.size() != 3 || itTypes[0] != vector::IteratorType::parallel ||
+ itTypes[1] != vector::IteratorType::parallel ||
+ itTypes[2] != vector::IteratorType::reduction)
+ return rewriter.notifyMatchFailure(
+ op, "iterator types do not correspond to matrix multiplication");
+
+ // Check the combining kind is addition.
+ if (op.getKind() != vector::CombiningKind::ADD)
+ return rewriter.notifyMatchFailure(op,
+ "combining kind is not an addition");
+
+ // Check the output is a vector of i32 elements.
+ auto outTy = dyn_cast<VectorType>(op.getResultType());
+ if (!outTy || outTy.getElementType() != rewriter.getI32Type())
+ return rewriter.notifyMatchFailure(op,
+ "output type is not a vector of i32");
+
+ // Check inputs are sign-/zero- extensions from i8 to i32. Get the values
+ // before the extension. All four signed/unsigned combinations for input
+ // operands are supported, but they are lowered to different operations.
+ // Determine which is the appropriate operation to lower to.
+ MMLA mmlaOp = MMLA::Signed;
+ auto maybeLhs = getExtOperand<arith::ExtSIOp>(
+ op.getLhs(), rewriter.getI8Type(), rewriter.getI32Type());
+ if (!maybeLhs) {
+ mmlaOp = MMLA::Unsigned;
+ maybeLhs = getExtOperand<arith::ExtUIOp>(
+ op.getLhs(), rewriter.getI8Type(), rewriter.getI32Type());
+ }
+ if (!maybeLhs)
+ return rewriter.notifyMatchFailure(
+ op, "LHS is not a sign- or zero- extended i8");
+
+ auto maybeRhs = getExtOperand<arith::ExtSIOp>(
+ op.getRhs(), rewriter.getI8Type(), rewriter.getI32Type());
+ if (maybeRhs) {
+ if (mmlaOp == MMLA::Unsigned)
+ mmlaOp = MMLA::Mixed;
+ } else {
+ if (mmlaOp == MMLA::Signed)
+ mmlaOp = MMLA::MixedSwapped;
+ maybeRhs = getExtOperand<arith::ExtUIOp>(
+ op.getRhs(), rewriter.getI8Type(), rewriter.getI32Type());
+ }
+ if (!maybeRhs)
+ return rewriter.notifyMatchFailure(
+ op, "RHS is not a sign- or zero- extended i8");
+
+ // One-dimensional vector types for arm_sve.*mmla
+ auto nxv16i8 = VectorType::get(/*shape=*/16, rewriter.getI8Type(),
+ /*scalableDims=*/{true});
+ auto nxv4i32 = VectorType::get(/*shape=*/4, rewriter.getI32Type(),
+ /*scalableDims=*/{true});
+
+ // Extract LHS sub-tiles with logicall shape <2x8>.
+ SmallVector<Value> lhsTile;
+ for (int64_t i = 0; i < M; i += 2) {
+ // Extract two consecutive rows of the LHS tile.
+ auto r0 = rewriter.create<vector::ExtractOp>(loc, *maybeLhs,
+ ArrayRef<int64_t>{i});
+ auto r1 = rewriter.create<vector::ExtractOp>(loc, *maybeLhs,
+ ArrayRef<int64_t>{i + 1});
+ // Concatenate to obtain a 16 x i8 flattened sub-tile.
+ auto t = rewriter.create<vector::ShuffleOp>(
+ loc, r0, r1,
+ llvm::ArrayRef<int64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15});
+ // Turn it into a scalable vector.
+ auto s = rewriter.create<vector::ScalableInsertOp>(
+ loc, t, rewriter.create<ub::PoisonOp>(loc, nxv16i8), 0);
+ // Replicate the sub-tile VSCALE times to fill the entire vector.
+ auto r = rewriter.create<arm_sve::DupQLaneOp>(loc, s, 0);
+ lhsTile.push_back(r);
+ }
+
+ // "Flatten" the RHS tile from <[N]x8> to <[8*N]>.
+ auto rhs = rewriter.create<vector::ShapeCastOp>(
+ maybeRhs->getLoc(),
+ VectorType::get(/*shape=*/8 * N, rewriter.getI8Type(),
+ /*scalableDims=*/{true}),
+ *maybeRhs);
+
+ // Extract the RHS sub-tiles with logical shape <8x[2]>.
+ SmallVector<Value> rhsTile;
+ for (int64_t j = 0; j < N; j += 2)
+ rhsTile.push_back(
+ rewriter.create<vector::ScalableExtractOp>(loc, nxv16i8, rhs, j * 8));
+
+ // Handy types for packing/unpacking of the accumulator tile.
+ auto accRowTy = VectorType::get(/*shape=*/N, rewriter.getI32Type(),
+ /*scalableDims=*/{true});
+ auto accRowX2Ty = VectorType::get(/*shape=*/2 * N, rewriter.getI32Type(),
+ /*scalableDims=*/{true});
+ auto accRow64Ty = VectorType::get(/*shape=*/N / 2, rewriter.getI64Type(),
+ /*scalableDims=*/{true});
+ auto accRowX264Ty = VectorType::get(/*shape=*/N, rewriter.getI64Type(),
+ /*scalableDims=*/{true});
+
+ // Extract and pack the ACC sub-tiles.
+ SmallVector<Value> accTile;
+ for (int64_t i = 0; i < M; i += 2) {
+ // Extract two consecutive rows of the accumulator tile.
+ auto r0 = rewriter.create<vector::ExtractOp>(loc, op.getAcc(),
+ ArrayRef<int64_t>{i});
+ auto r1 = rewriter.create<vector::ExtractOp>(loc, op.getAcc(),
+ ArrayRef<int64_t>{i + 1});
+ Value accTileVec;
+ if (mmlaOp == MMLA::MixedSwapped) {
+ // We need to swap the positions of the LHS and RHS (since we don't have
+ // a signed * unsigned operation), but then each individual 2x2 tile of
+ // the acumulator and (later) the result need to be transposed.
+ accTileVec = rewriter.create<vector::InterleaveOp>(loc, r0, r1);
+ } else {
+ // Bitcast them to 64-bit elements, so subsequent
+ // interleave/deinterleave work on pairs of 32-bit numbers.
+ auto r0I64 = rewriter.create<vector::BitCastOp>(loc, accRow64Ty, r0);
+ auto r1I64 = rewriter.create<vector::BitCastOp>(loc, accRow64Ty, r1);
+
+ // Interleave the rows, effectively flattening each 2x2 tile into 4
+ // consecutive elements.
+ auto intrI64 = rewriter.create<vector::InterleaveOp>(loc, r0I64, r1I64);
+
+ // Bitcast back to 32-bit elements.
+ accTileVec =
+ rewriter.create<vector::BitCastOp>(loc, accRowX2Ty, intrI64);
+ }
+ // Extract ACC sub-tiles.
+ for (int64_t j = 0; j < N; j += 2)
+ accTile.push_back(rewriter.create<vector::ScalableExtractOp>(
+ loc, nxv4i32, accTileVec, j * 2));
+ }
+
+ // Emit sub-tile matrix multiplications.
+ SmallVector<Value> outTile;
+ for (int64_t i = 0; i < M / 2; ++i)
+ for (int64_t j = 0; j < N / 2; ++j) {
+ Value mmla = createMMLA(rewriter, mmlaOp, loc, nxv4i32,
+ accTile[i * N / 2 + j], lhsTile[i], rhsTile[j]);
+ outTile.push_back(mmla);
+ }
+
+ // Unpack the OUT sub-tiles and insert into the result.
+ Value result = rewriter.create<ub::PoisonOp>(loc, op.getResultType());
+ for (int64_t i = 0; i < M / 2; ++i) {
+ // Collect a number of sub-tiles in a row.
+ Value row = rewriter.create<ub::PoisonOp>(loc, accRowX2Ty);
+ for (int64_t j = 0; j < N / 2; ++j)
+ row = rewriter.create<vector::ScalableInsertOp>(
+ loc, outTile[i * N / 2 + j], row, j * 4);
+
+ // Unpack the row to obtain two rows of the output. If we have the out
+ // sub-tiles transposed we obtain two consecutive output rows by
+ // separating even and odd elements, i.e. a simple deinterleave.
+ // Otherwise, the interleave is by pairs.
+ Value out0, out1;
+ if (mmlaOp == MMLA::MixedSwapped) {
+ auto tmp = rewriter.create<vector::DeinterleaveOp>(loc, row);
+ out0 = tmp.getRes1();
+ out1 = tmp.getRes2();
+ } else {
+ // Deinterleave by pairs.
+ auto row64 = rewriter.create<vector::BitCastOp>(loc, accRowX264Ty, row);
+ auto deintr64 = rewriter.create<vector::DeinterleaveOp>(loc, row64);
+
+ // Bitcast back into 32-bit elements and insert into the result.
+ out0 = rewriter.create<vector::BitCastOp>(loc, accRowTy,
+ deintr64.getRes1());
+ out1 = rewriter.create<vector::BitCastOp>(loc, accRowTy,
+ deintr64.getRes2());
+ }
+ result = rewriter.create<vector::InsertOp>(loc, out0, result, i * 2);
+ result = rewriter.create<vector::InsertOp>(loc, out1, result, i * 2 + 1);
+ }
+
+ rewriter.replaceOp(op, result);
+ return success();
+ }
+};
+
+} // namespace
+
+void mlir::populateLowerContractionToSVEI8MMPatternPatterns(
+ RewritePatternSet &patterns) {
+ MLIRContext *context = patterns.getContext();
+ patterns.add<LowerContractionToSVEI8MMPattern>(context, /*benefit=*/2);
+}
diff --git a/mlir/lib/Dialect/Bufferization/IR/BufferDeallocationOpInterface.cpp b/mlir/lib/Dialect/Bufferization/IR/BufferDeallocationOpInterface.cpp
index eed7a56fff8a..ca914df8b789 100644
--- a/mlir/lib/Dialect/Bufferization/IR/BufferDeallocationOpInterface.cpp
+++ b/mlir/lib/Dialect/Bufferization/IR/BufferDeallocationOpInterface.cpp
@@ -94,7 +94,9 @@ void Ownership::combine(Ownership other) { *this = getCombined(other); }
// DeallocationState
//===----------------------------------------------------------------------===//
-DeallocationState::DeallocationState(Operation *op) : liveness(op) {}
+DeallocationState::DeallocationState(Operation *op,
+ SymbolTableCollection &symbolTables)
+ : symbolTable(symbolTables), liveness(op) {}
void DeallocationState::updateOwnership(Value memref, Ownership ownership,
Block *block) {
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
index fc6424a25ac7..d1d106220a38 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
@@ -341,15 +341,25 @@ static LogicalResult getFuncOpsOrderedByCalls(
// Iteratively remove function operations that do not call any of the
// functions remaining in the callCounter map and add them to ordered list.
- while (!numberCallOpsContainedInFuncOp.empty()) {
- auto it = llvm::find_if(numberCallOpsContainedInFuncOp,
- [](auto entry) { return entry.getSecond() == 0; });
- if (it == numberCallOpsContainedInFuncOp.end())
- break;
- orderedFuncOps.push_back(it->getFirst());
- for (auto callee : calledBy[it->getFirst()])
- numberCallOpsContainedInFuncOp[callee]--;
- numberCallOpsContainedInFuncOp.erase(it);
+ SmallVector<func::FuncOp> worklist;
+
+ for (const auto &entry : numberCallOpsContainedInFuncOp) {
+ if (entry.second == 0)
+ worklist.push_back(entry.first);
+ }
+
+ while (!worklist.empty()) {
+ func::FuncOp func = worklist.pop_back_val();
+ orderedFuncOps.push_back(func);
+
+ for (func::FuncOp caller : calledBy[func]) {
+ auto &count = numberCallOpsContainedInFuncOp[caller];
+
+ if (--count == 0)
+ worklist.push_back(caller);
+ }
+
+ numberCallOpsContainedInFuncOp.erase(func);
}
// Put all other functions in the list of remaining functions. These are
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
index c5b2f3020712..1eeafc4df8cf 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
@@ -166,8 +166,9 @@ namespace {
/// program have a corresponding de-allocation.
class BufferDeallocation {
public:
- BufferDeallocation(Operation *op, DeallocationOptions options)
- : state(op), options(options) {}
+ BufferDeallocation(Operation *op, DeallocationOptions options,
+ SymbolTableCollection &symbolTables)
+ : state(op, symbolTables), options(options) {}
/// Performs the actual placement/creation of all dealloc operations.
LogicalResult deallocate(FunctionOpInterface op);
@@ -1027,11 +1028,13 @@ struct OwnershipBasedBufferDeallocationPass
DeallocationOptions options;
options.privateFuncDynamicOwnership = privateFuncDynamicOwnership;
+ mlir::SymbolTableCollection symbolTables;
+
auto status = getOperation()->walk([&](func::FuncOp func) {
if (func.isExternal())
return WalkResult::skip();
- if (failed(deallocateBuffersOwnershipBased(func, options)))
+ if (failed(deallocateBuffersOwnershipBased(func, options, symbolTables)))
return WalkResult::interrupt();
return WalkResult::advance();
@@ -1047,11 +1050,11 @@ struct OwnershipBasedBufferDeallocationPass
// Implement bufferization API
//===----------------------------------------------------------------------===//
-LogicalResult
-bufferization::deallocateBuffersOwnershipBased(FunctionOpInterface op,
- DeallocationOptions options) {
+LogicalResult bufferization::deallocateBuffersOwnershipBased(
+ FunctionOpInterface op, DeallocationOptions options,
+ SymbolTableCollection &symbolTables) {
// Gather all required allocation nodes and prepare the deallocation phase.
- BufferDeallocation deallocation(op, options);
+ BufferDeallocation deallocation(op, options, symbolTables);
// Place all required temporary clone and dealloc nodes.
return deallocation.deallocate(op);
diff --git a/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp
index 648b6b087e59..aaf6b0593c2e 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp
@@ -1205,6 +1205,42 @@ LogicalResult NVVM::VoteSyncOp::verify() {
return success();
}
+LogicalResult NVVM::PrefetchOp::verify() {
+ using MemSpace = NVVM::NVVMMemorySpace;
+ using CacheLevel = NVVM::PrefetchCacheLevel;
+
+ unsigned addressSpace =
+ llvm::cast<LLVM::LLVMPointerType>(getAddr().getType()).getAddressSpace();
+ std::optional<NVVM::CacheEvictionPriority> evictPriority = getEvictPriority();
+
+ if (getUniform()) {
+ if (getCacheLevel() != CacheLevel::L1)
+ return emitOpError("unsupported cache level, the only supported uniform "
+ "cache level is L1");
+
+ if (addressSpace != MemSpace::kGenericMemorySpace)
+ return emitOpError(
+ "prefetch to uniform cache requires a generic pointer");
+ }
+
+ if (evictPriority) {
+ if (getCacheLevel() != CacheLevel::L2)
+ return emitOpError(
+ "cache eviction priority supported only for cache level L2");
+
+ if (addressSpace != MemSpace::kGlobalMemorySpace)
+ return emitOpError("cache eviction priority requires a global pointer");
+
+ if (*evictPriority != NVVM::CacheEvictionPriority::EvictNormal &&
+ *evictPriority != NVVM::CacheEvictionPriority::EvictLast)
+ return emitOpError(
+ "unsupported cache eviction priority, only evict_last and "
+ "evict_normal are supported");
+ }
+
+ return success();
+}
+
/// Packs the given `field` into the `result`.
/// The `result` is 64-bits and each `field` can be 32-bits or narrower.
static llvm::Value *
@@ -1712,6 +1748,70 @@ NVVM::IDArgPair DotAccumulate4WayOp::getIntrinsicIDAndArgs(
return {ids[type], args};
}
+NVVM::IDArgPair DotAccumulate2WayOp::getIntrinsicIDAndArgs(
+ Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
+ auto curOp = cast<NVVM::DotAccumulate2WayOp>(op);
+
+ llvm::SmallVector<llvm::Value *> args;
+ args.push_back(getAsPackedI32(mt.lookupValue(curOp.getA()), builder));
+ args.push_back(getAsPackedI32(mt.lookupValue(curOp.getB()), builder));
+ args.push_back(builder.getInt1(curOp.getBHi()));
+ args.push_back(mt.lookupValue(curOp.getC()));
+
+ bool isASigned = curOp.getAType() == NVVM::DotAccumulateType::SIGNED;
+ bool isBSigned = curOp.getBType() == NVVM::DotAccumulateType::SIGNED;
+ unsigned type = (isASigned << 1) | isBSigned;
+ const llvm::Intrinsic::ID ids[] = {
+ llvm::Intrinsic::nvvm_idp2a_u_u,
+ llvm::Intrinsic::nvvm_idp2a_u_s,
+ llvm::Intrinsic::nvvm_idp2a_s_u,
+ llvm::Intrinsic::nvvm_idp2a_s_s,
+ };
+ return {ids[type], args};
+}
+
+llvm::Intrinsic::ID PrefetchOp::getIntrinsicID(NVVM::PrefetchOp &op) {
+ using MemSpace = NVVM::NVVMMemorySpace;
+ using CacheLevel = NVVM::PrefetchCacheLevel;
+
+ NVVM::PrefetchCacheLevel cacheLevel = op.getCacheLevel();
+ std::optional<NVVM::CacheEvictionPriority> evictPriority =
+ op.getEvictPriority();
+ unsigned addressSpace =
+ llvm::cast<LLVM::LLVMPointerType>(op.getAddr().getType())
+ .getAddressSpace();
+
+ if (op.getUniform() && cacheLevel == CacheLevel::L1)
+ return llvm::Intrinsic::nvvm_prefetchu_L1;
+
+ if (evictPriority && cacheLevel == CacheLevel::L2) {
+ switch (*evictPriority) {
+ case NVVM::CacheEvictionPriority::EvictLast:
+ return llvm::Intrinsic::nvvm_prefetch_global_L2_evict_last;
+ case NVVM::CacheEvictionPriority::EvictNormal:
+ return llvm::Intrinsic::nvvm_prefetch_global_L2_evict_normal;
+ default:
+ llvm_unreachable("Invalid cache eviction priority");
+ }
+ }
+
+ switch (addressSpace) {
+ case MemSpace::kGenericMemorySpace:
+ return cacheLevel == CacheLevel::L1 ? llvm::Intrinsic::nvvm_prefetch_L1
+ : llvm::Intrinsic::nvvm_prefetch_L2;
+ case MemSpace::kGlobalMemorySpace:
+ return cacheLevel == CacheLevel::L1
+ ? llvm::Intrinsic::nvvm_prefetch_global_L1
+ : llvm::Intrinsic::nvvm_prefetch_global_L2;
+ case MemSpace::kLocalMemorySpace:
+ return cacheLevel == CacheLevel::L1
+ ? llvm::Intrinsic::nvvm_prefetch_local_L1
+ : llvm::Intrinsic::nvvm_prefetch_local_L2;
+ default:
+ llvm_unreachable("Invalid pointer address space");
+ }
+}
+
//===----------------------------------------------------------------------===//
// NVVMDialect initialization, type parsing, and registration.
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Linalg/Transforms/DataLayoutPropagation.cpp b/mlir/lib/Dialect/Linalg/Transforms/DataLayoutPropagation.cpp
index dd8ef9608a82..7188987e5e93 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/DataLayoutPropagation.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/DataLayoutPropagation.cpp
@@ -876,11 +876,8 @@ public:
return failure();
}
// Currently only support static inner tile sizes.
- if (llvm::any_of(packOp.getStaticTiles(), [](int64_t size) {
- return ShapedType::isDynamic(size);
- })) {
+ if (llvm::any_of(packOp.getStaticTiles(), ShapedType::isDynamic))
return failure();
- }
// User controlled propagation function.
if (!controlFn(&packOp.getSourceMutable()))
@@ -1002,11 +999,8 @@ public:
return failure();
}
// Currently only support static inner tile sizes.
- if (llvm::any_of(unPackOp.getStaticTiles(), [](int64_t size) {
- return ShapedType::isDynamic(size);
- })) {
+ if (llvm::any_of(unPackOp.getStaticTiles(), ShapedType::isDynamic))
return failure();
- }
Operation *consumerOp = *result.user_begin();
return TypeSwitch<Operation *, LogicalResult>(consumerOp)
diff --git a/mlir/lib/Dialect/Linalg/Transforms/Transforms.cpp b/mlir/lib/Dialect/Linalg/Transforms/Transforms.cpp
index 8718c57b9e86..615d1f66414b 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/Transforms.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/Transforms.cpp
@@ -227,8 +227,7 @@ FailureOr<LowerPackResult> linalg::lowerPack(RewriterBase &rewriter,
// 1. Filter out NYI cases.
auto packedTensorType =
cast<RankedTensorType>(packOp->getResultTypes().front());
- if (llvm::any_of(packOp.getStaticInnerTiles(),
- [](int64_t size) { return ShapedType::isDynamic(size); })) {
+ if (llvm::any_of(packOp.getStaticInnerTiles(), ShapedType::isDynamic)) {
return rewriter.notifyMatchFailure(
packOp,
"non-static shape NYI, needs a more powerful tensor.expand_shape op");
diff --git a/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp b/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp
index c5b62227777a..ff28bd7c4834 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp
@@ -820,9 +820,8 @@ tensorExtractVectorizationPrecondition(Operation *op, bool vectorizeNDExtract) {
return failure();
}
- if (llvm::any_of(extractOp->getResultTypes(), [](Type type) {
- return !VectorType::isValidElementType(type);
- })) {
+ if (!llvm::all_of(extractOp->getResultTypes(),
+ VectorType::isValidElementType)) {
return failure();
}
@@ -1506,101 +1505,192 @@ static SmallVector<int64_t> getTiledPackShape(linalg::PackOp packOp,
return applyPermutation(destShape, linalg::getPackInverseDestPerm(packOp));
}
+/// Determines whether a mask for xfer_write is trivially "all true"
+///
+/// Given all the inputs required to generate a mask (mask sizes and shapes),
+/// and an xfer_write operation (write indices and the destination tensor
+/// shape), determines whether the corresponding mask would be trivially
+/// foldable (i.e., trivially "all true").
+///
+/// Use this method to avoid generating spurious masks and relaying on
+/// vectorization post-processing to remove them.
+///
+/// Pre-conditions for a mask to be trivially foldable:
+/// * All involved shapes (mask + destination tensor) are static.
+/// * All write indices are constant.
+/// * All mask sizes are constant (including `arith.constant`).
+///
+/// If the pre-conditions are met, the method checks for each destination
+/// dimension `d`:
+/// (1) destDimSize[rankDiff + d] <= maskShape[d]
+/// (2) destDimSize[rankDiff + d] <= writeIndex[d] + maskSize[d]
+///
+/// rankDiff = rank(dest) - rank(mask).
+///
+/// This method takes a conservative view: it may return false even if the mask
+/// is technically foldable.
+///
+/// EXAMPLE 1 (trivially foldable, all shapes match, mask sizes match the shape
+/// of the dest tensor):
+/// %c0 = arith.constant 0 : index
+/// %mask = vector.create_mask 5, 1
+/// vector.mask %mask {
+/// vector.transfer_write %vecToStore_1, %dest{[%c0, %c0]
+/// {in_bounds = [true, true]}
+/// : vector<5x1xi32>, tensor<5x1xi32>
+/// }
+///
+/// EXAMPLE 2 (not trivially foldable - vector shape exceeds the tensor shape,
+/// mask is required to avoid out-of-bounds write):
+/// %c0 = arith.constant 0 : index
+/// %mask = vector.create_mask 5, 1
+/// vector.mask %mask {
+/// vector.transfer_write %vecToStore_2, %dest[%c0, %c0]
+/// {in_bounds = [true, true]}
+/// : vector<8x1xi32>, tensor<5x1xi32>
+/// }
+///
+/// TODO: Re-use in createReadOrMaskedRead
+static bool isMaskTriviallyFoldable(SmallVector<OpFoldResult> &maskSizes,
+ SmallVector<Value> &writeIdxs,
+ ArrayRef<int64_t> destShape,
+ ArrayRef<int64_t> maskShape) {
+ // Masking is unavoidable in the case of dynamic tensors.
+ if (ShapedType::isDynamicShape(destShape))
+ return false;
+
+ // Collect all constant mask sizes.
+ SmallVector<int64_t, 4> cstMaskSizes;
+ for (auto [i, dimSize] : llvm::enumerate(maskSizes)) {
+ if (auto intSize = getConstantIntValue(dimSize)) {
+ cstMaskSizes.push_back(*intSize);
+ }
+ }
+
+ // If any of the mask sizes is non-constant, bail out.
+ if (cstMaskSizes.size() != maskShape.size())
+ return false;
+
+ // Collect all constant write indices.
+ SmallVector<int64_t, 4> cstWriteIdxs;
+ for (auto [i, idx] : llvm::enumerate(writeIdxs)) {
+ APSInt intVal;
+ if (matchPattern(idx, m_ConstantInt(&intVal))) {
+ cstWriteIdxs.push_back(intVal.getSExtValue());
+ }
+ }
+
+ // If any of the write indices is non-constant, bail out.
+ if (cstWriteIdxs.size() != destShape.size())
+ return false;
+
+ // Go over all destination dims and check (1) and (2). Take into account that:
+ // * The number of mask sizes will match the rank of the vector to store.
+ // This could be lower than the rank of the destination tensor.
+ // * Mask sizes could be larger than the corresponding mask shape (hence
+ // `clamp`).
+ // TODO: The 2nd item should be rejected by the verifier.
+ int64_t rankDiff = destShape.size() - cstMaskSizes.size();
+ for (auto [i, idx] : llvm::enumerate(cstMaskSizes)) {
+ if (/*(1)*/ maskShape[i] > destShape[rankDiff + i] ||
+ /*(2)*/ destShape[rankDiff + i] <
+ (std::clamp(cstMaskSizes[i], int64_t(0), maskShape[i]) +
+ cstWriteIdxs[i]))
+ return false;
+ }
+
+ return true;
+}
+
/// Creates an optionally masked TransferWriteOp
///
/// Generates the following operation:
-/// %res = vector.transfer_write %vectorToStore into %dest
+/// %res = vector.transfer_write %vecToStore into %dest
///
-/// If the leading N dimensions of the destination tensor do not match
-/// `inputVecSizesForLeadingDims` (N = rank(inputVecSizesForLeadingDims)),
-/// masking is applied to ensure correctness:
+/// If shape(vecToStore) != shape(dest), masking is used to ensure correctness:
///
-/// %mask = vector.create_mask(%destShape)
+/// %mask = vector.create_mask(%destShape) : %vecToStoreShape
/// %res = vector.mask %mask {
-/// vector.transfer_write %vectorToStore into %dest
+/// vector.transfer_write %vecToStore into %dest
/// }
///
+/// The mask shape is identical to `vecToStore` (with the element type ==
+/// i1), and the mask values are based on the shape of the `dest` tensor.
+///
/// If `useInBoundsInsteadOfMasking` is set to `true`, the `in_bounds` attribute
/// is used instead of masking:
///
-/// %write = vector.transfer_write %vectorToStore into %dest
+/// %write = vector.transfer_write %vecToStore into %dest
/// in_bounds_flags = (...)
/// %res = vector.transfer_write %input into %dest
/// {in_bounds = in_bounds_flags}
///
-/// NOTE: All write offsets are set to 0.
-/// TODO: Allow specyfying write offsets.
-/// NOTE: When N < rank(input), the missing vector sizes are effectively
-/// extracted from the trailing sizes of `destSizes`. This means those sizes
-/// must be static.
-/// TODO: Support cases where an arbitrary dim is dynamic - this will require
-/// specifying all the vector sizes.
+/// Finally, `writeIndices` specifies the offsets to use. If empty, all indices
+/// are set to 0.
static Operation *
-createWriteOrMaskedWrite(OpBuilder &builder, Location loc, Value vectorToStore,
- Value dest,
- ArrayRef<int64_t> inputVecSizesForLeadingDims,
+createWriteOrMaskedWrite(OpBuilder &builder, Location loc, Value vecToStore,
+ Value dest, SmallVector<Value> writeIndices = {},
bool useInBoundsInsteadOfMasking = false) {
ShapedType destType = cast<ShapedType>(dest.getType());
- assert(cast<VectorType>(vectorToStore.getType()).getRank() ==
- static_cast<int64_t>(destType.getRank()) &&
- "Rank mismatch!");
- (void)destType;
+ int64_t destRank = destType.getRank();
+ auto destShape = destType.getShape();
- int64_t rank = cast<ShapedType>(dest.getType()).getRank();
- auto destShape = cast<ShapedType>(dest.getType()).getShape();
+ VectorType vecToStoreType = cast<VectorType>(vecToStore.getType());
+ int64_t vecToStoreRank = vecToStoreType.getRank();
+ auto vecToStoreShape = vecToStoreType.getShape();
// Compute the in_bounds attribute
- SmallVector<bool> inBoundsVal(rank, true);
+ SmallVector<bool> inBoundsVal(vecToStoreRank, true);
if (useInBoundsInsteadOfMasking) {
- // In this case, assume that all the required vector sizes have been
- // provided.
- assert(inputVecSizesForLeadingDims.size() ==
- static_cast<size_t>(destType.getRank()) &&
- "Insufficient number of input vector sizes!");
// Update the inBounds attribute.
- for (unsigned i = 0; i < rank; i++)
- inBoundsVal[i] = (destShape[i] == inputVecSizesForLeadingDims[i]) &&
- !ShapedType::isDynamic(destShape[i]);
+ // FIXME: This computation is too weak - it ignores the write indices.
+ for (unsigned i = 0; i < vecToStoreRank; i++)
+ inBoundsVal[i] =
+ (destShape[destRank - vecToStoreRank + i] >= vecToStoreShape[i]) &&
+ !ShapedType::isDynamic(destShape[destRank - vecToStoreRank + i]);
+ }
+
+ // If missing, initialize the write indices to 0.
+ assert(writeIndices.empty() ||
+ writeIndices.size() == static_cast<size_t>(destRank) &&
+ "Invalid number of write indices!");
+ if (writeIndices.empty()) {
+ auto zero = builder.create<arith::ConstantIndexOp>(loc, 0);
+ writeIndices.assign(destRank, zero);
}
// Generate the xfer_write Op
- auto zero = builder.create<arith::ConstantIndexOp>(loc, 0);
- Operation *write = builder.create<vector::TransferWriteOp>(
- loc,
- /*vector=*/vectorToStore,
- /*source=*/dest,
- /*indices=*/SmallVector<Value>(rank, zero),
- /*inBounds=*/inBoundsVal);
- assert(llvm::none_of(
- destShape.drop_front(inputVecSizesForLeadingDims.size()),
- [](int64_t size) { return size == ShapedType::kDynamic; }) &&
- "Only dims aligned with inputVecSizesForLeadingDims may be dynamic");
+ Operation *write =
+ builder.create<vector::TransferWriteOp>(loc,
+ /*vector=*/vecToStore,
+ /*source=*/dest,
+ /*indices=*/writeIndices,
+ /*inBounds=*/inBoundsVal);
// If masking is disabled, exit.
if (useInBoundsInsteadOfMasking)
return write;
- // Check if masking is needed.
- bool needMaskForWrite =
- !llvm::equal(inputVecSizesForLeadingDims,
- destShape.take_front(inputVecSizesForLeadingDims.size()));
+ // Check if masking is needed. If not, exit.
+ if (llvm::equal(vecToStoreShape, destShape.take_back(vecToStoreRank)))
+ return write;
- // If masking is needed, generate the mask and mask the operation.
- if (needMaskForWrite) {
- SmallVector<int64_t> writeMaskShape;
- writeMaskShape.append(inputVecSizesForLeadingDims.begin(),
- inputVecSizesForLeadingDims.end());
- writeMaskShape.append(destShape.begin() +
- inputVecSizesForLeadingDims.size(),
- destShape.end());
- auto writeMaskType = VectorType::get(writeMaskShape, builder.getI1Type());
- Value maskForWrite = builder.create<vector::CreateMaskOp>(
- loc, writeMaskType, tensor::getMixedSizes(builder, loc, dest));
- write = mlir::vector::maskOperation(builder, write, maskForWrite);
- }
+ // Compute the mask and mask the write Op.
+ auto writeMaskType = VectorType::get(vecToStoreShape, builder.getI1Type());
- return write;
+ SmallVector<OpFoldResult> destSizes =
+ tensor::getMixedSizes(builder, loc, dest);
+ SmallVector<OpFoldResult> maskSizes(destSizes.end() - vecToStoreRank,
+ destSizes.end());
+
+ if (isMaskTriviallyFoldable(maskSizes, writeIndices, destShape,
+ vecToStoreShape))
+ return write;
+
+ Value maskForWrite =
+ builder.createOrFold<vector::CreateMaskOp>(loc, writeMaskType, maskSizes);
+ return mlir::vector::maskOperation(builder, write, maskForWrite);
}
/// Vectorize linalg::PackOp with (1) static inner_tiles (2) constant
@@ -1701,9 +1791,7 @@ vectorizeAsTensorPackOp(RewriterBase &rewriter, linalg::PackOp packOp,
loc, reifiedReturnShapes[0],
transposeOp.getResult().getType().getElementType());
Operation *write =
- createWriteOrMaskedWrite(rewriter, loc, transposeOp.getResult(), dest,
- /*inputVecSizesForLeadingDims=*/inputVectorSizes,
- /*useInBoundsInsteadOfMasking=*/false);
+ createWriteOrMaskedWrite(rewriter, loc, transposeOp.getResult(), dest);
newResults.push_back(write->getResult(0));
return success();
}
@@ -1839,10 +1927,9 @@ vectorizeAsTensorUnpackOp(RewriterBase &rewriter, linalg::UnPackOp unpackOp,
Value dest = rewriter.create<tensor::EmptyOp>(
loc, reifiedRetShapes[0],
shapeCastOp.getResult().getType().getElementType());
- Operation *write =
- createWriteOrMaskedWrite(rewriter, loc, shapeCastOp.getResult(), dest,
- /*inputVecSizesForLeadingDims=*/writeVectorSizes,
- useInBoundsInsteadOfMasking);
+ Operation *write = createWriteOrMaskedWrite(
+ rewriter, loc, shapeCastOp.getResult(), dest,
+ /*writeIndices=*/{}, useInBoundsInsteadOfMasking);
newResults.push_back(write->getResult(0));
return success();
}
@@ -1874,10 +1961,7 @@ vectorizeAsTensorPadOp(RewriterBase &rewriter, tensor::PadOp padOp,
// Create Xfer write Op
Value dest = rewriter.create<tensor::EmptyOp>(
loc, reifiedReturnShapes[0], padOp.getResultType().getElementType());
- Operation *write =
- createWriteOrMaskedWrite(rewriter, loc, maskedRead, dest,
- /*inputVecSizesForLeadingDims=*/inputVectorSizes,
- /*useInBoundsInsteadOfMasking=*/false);
+ Operation *write = createWriteOrMaskedWrite(rewriter, loc, maskedRead, dest);
newResults.push_back(write->getResult(0));
return success();
}
@@ -2163,14 +2247,12 @@ static LogicalResult vectorizeLinalgOpPrecondition(
})) {
continue;
}
- if (llvm::any_of(innerOp.getOperandTypes(), [](Type type) {
- return !VectorType::isValidElementType(type);
- })) {
+ if (!llvm::all_of(innerOp.getOperandTypes(),
+ VectorType::isValidElementType)) {
return failure();
}
- if (llvm::any_of(innerOp.getResultTypes(), [](Type type) {
- return !VectorType::isValidElementType(type);
- })) {
+ if (!llvm::all_of(innerOp.getResultTypes(),
+ VectorType::isValidElementType)) {
return failure();
}
}
@@ -2883,22 +2965,14 @@ vectorizeAsInsertSliceOp(RewriterBase &rewriter, tensor::InsertSliceOp sliceOp,
sliceOp.getLoc(), elemType, rewriter.getZeroAttr(elemType));
}
- // 2. Get the vector shape and in-bounds attributes
+ // 2. Get the vector shape
SmallVector<int64_t> vecShape;
- SmallVector<bool> readInBounds;
- SmallVector<bool> writeInBounds;
size_t rankDiff = resultType.getRank() - sourceType.getRank();
for (int64_t i = 0, end = sourceType.getRank(); i < end; ++i) {
if (!inputVectorSizes.empty()) {
vecShape.push_back(inputVectorSizes[i]);
- readInBounds.push_back(false);
- writeInBounds.push_back(false);
} else if (!sourceType.isDynamicDim(i)) {
vecShape.push_back(sourceType.getDimSize(i));
- // Source shape is statically known: Neither read nor write are
- // out-of-bounds.
- readInBounds.push_back(true);
- writeInBounds.push_back(true);
} else if (!resultType.isDynamicDim(i)) {
// Source shape is not statically known, but result shape is.
// Vectorize with size of result shape. This may be larger than the
@@ -2906,69 +2980,30 @@ vectorizeAsInsertSliceOp(RewriterBase &rewriter, tensor::InsertSliceOp sliceOp,
// FIXME: Using rankDiff implies that the source tensor is inserted at
// the end of the destination tensor. However, that's not required.
vecShape.push_back(resultType.getDimSize(rankDiff + i));
- // Read may be out-of-bounds because the result size could be larger
- // than the source size.
- readInBounds.push_back(false);
- // Write will be in-bounds provided that the corresponding write idx is 0.
- // To keep this logic simple, conservatively mark as out-of-bounds.
- writeInBounds.push_back(false);
} else {
// Neither source nor result dim of padOp is static. Cannot vectorize
// the copy.
- // TODO: Add support for masking
return failure();
}
}
auto vecType = VectorType::get(vecShape, sourceType.getElementType());
// 3. Generate TransferReadOp + TransferWriteOp
- ReifiedRankedShapedTypeDims reifiedSrcSizes;
- Value maskOp;
-
- // If vector sizes are user provided, make sure to mask. First, generate the
- // mask.
- if (!inputVectorSizes.empty()) {
- auto *srcDefOp = source.getDefiningOp();
- if (!srcDefOp) {
- LDBG("Unable to get the defining Op of " << sliceOp);
- return failure();
- }
-
- LogicalResult status =
- cast<ReifyRankedShapedTypeOpInterface>(srcDefOp).reifyResultShapes(
- rewriter, reifiedSrcSizes);
- if (status.failed()) {
- LDBG("Unable to reify result shapes of " << srcDefOp);
- return failure();
- }
-
- // Create the mask
- auto readMaskType = VectorType::get(inputVectorSizes, rewriter.getI1Type());
- maskOp = rewriter.create<vector::CreateMaskOp>(
- sliceOp.getLoc(), readMaskType, reifiedSrcSizes[0]);
- }
+ auto loc = sliceOp.getLoc();
+ // Create read
SmallVector<Value> readIndices(
- vecType.getRank(),
- rewriter.create<arith::ConstantIndexOp>(sliceOp.getLoc(), 0));
- Operation *read = rewriter.create<vector::TransferReadOp>(
- sliceOp.getLoc(), vecType, source, readIndices, padValue,
- ArrayRef<bool>{readInBounds});
-
- if (maskOp) {
- read = mlir::vector::maskOperation(rewriter, read, maskOp);
- }
-
- auto writeIndices = getValueOrCreateConstantIndexOp(
- rewriter, sliceOp.getLoc(), sliceOp.getMixedOffsets());
-
- Operation *write = rewriter.create<vector::TransferWriteOp>(
- sliceOp.getLoc(), read->getResult(0), sliceOp.getDest(), writeIndices,
- ArrayRef<bool>{writeInBounds});
-
- if (maskOp) {
- write = mlir::vector::maskOperation(rewriter, write, maskOp);
- }
+ vecType.getRank(), rewriter.create<arith::ConstantIndexOp>(loc, 0));
+ Value read = mlir::vector::createReadOrMaskedRead(
+ rewriter, loc, source, vecType.getShape(), padValue,
+ /*useInBoundsInsteadOfMasking=*/inputVectorSizes.empty());
+
+ // Create write
+ auto writeIndices =
+ getValueOrCreateConstantIndexOp(rewriter, loc, sliceOp.getMixedOffsets());
+ Operation *write =
+ createWriteOrMaskedWrite(rewriter, loc, read, sliceOp.getDest(),
+ writeIndices, inputVectorSizes.empty());
// 4. Finalize
newResults.push_back(write->getResult(0));
diff --git a/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp b/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp
index 219619981629..34ae83b25c39 100644
--- a/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp
+++ b/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp
@@ -799,8 +799,7 @@ LogicalResult SparseTensorEncodingAttr::verify(
"before singleton level";
auto *curCOOEnd = std::find_if_not(it, lvlTypes.end(), isSingletonLT);
- if (!std::all_of(it, curCOOEnd,
- [](LevelType i) { return isSingletonLT(i); }))
+ if (!std::all_of(it, curCOOEnd, isSingletonLT))
return emitError() << "expected all singleton lvlTypes "
"following a singleton level";
// We can potentially support mixed SoA/AoS singleton levels.
@@ -833,8 +832,7 @@ LogicalResult SparseTensorEncodingAttr::verify(
it != std::end(lvlTypes)) {
if (it != lvlTypes.end() - 1)
return emitError() << "expected n_out_of_m to be the last level type";
- if (!std::all_of(lvlTypes.begin(), it,
- [](LevelType i) { return isDenseLT(i); }))
+ if (!std::all_of(lvlTypes.begin(), it, isDenseLT))
return emitError() << "expected all dense lvlTypes "
"before a n_out_of_m level";
if (dimToLvl && (dimToLvl.getNumDims() != dimToLvl.getNumResults())) {
diff --git a/mlir/lib/Dialect/Tensor/Extensions/AllExtensions.cpp b/mlir/lib/Dialect/Tensor/Extensions/AllExtensions.cpp
index 93e1a2021857..0421a6c0ff80 100644
--- a/mlir/lib/Dialect/Tensor/Extensions/AllExtensions.cpp
+++ b/mlir/lib/Dialect/Tensor/Extensions/AllExtensions.cpp
@@ -13,4 +13,4 @@ using namespace mlir;
void mlir::tensor::registerAllExtensions(DialectRegistry &registry) {
registerShardingInterfaceExternalModels(registry);
-} \ No newline at end of file
+}
diff --git a/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt b/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt
index 5425615dac39..66b73f9bead5 100644
--- a/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt
@@ -30,6 +30,7 @@ add_mlir_dialect_library(MLIRTensorDialect
MLIRInferIntRangeInterface
MLIRInferTypeOpInterface
MLIRParallelCombiningOpInterface
+ MLIRRuntimeVerifiableOpInterface
MLIRShapedOpInterfaces
MLIRSideEffectInterfaces
MLIRSupport
diff --git a/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp b/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp
index e7d8f52d309c..5b65e47bc937 100644
--- a/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp
+++ b/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp
@@ -12,6 +12,7 @@
#include "mlir/Dialect/Complex/IR/Complex.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.h"
+#include "mlir/Interfaces/RuntimeVerifiableOpInterface.h"
#include "mlir/Interfaces/SubsetOpInterface.h"
#include "mlir/Transforms/InliningUtils.h"
@@ -58,6 +59,8 @@ void TensorDialect::initialize() {
InsertSliceOp, ReshapeOp>();
declarePromisedInterfaces<ReifyRankedShapedTypeOpInterface, ExpandShapeOp,
CollapseShapeOp, PadOp>();
+ declarePromisedInterfaces<RuntimeVerifiableOpInterface, CastOp, DimOp,
+ ExtractOp, InsertOp, ExtractSliceOp>();
declarePromisedInterfaces<SubsetOpInterface, ExtractSliceOp, InsertSliceOp,
ParallelInsertSliceOp>();
declarePromisedInterfaces<SubsetInsertionOpInterface, InsertSliceOp,
diff --git a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
index 12e8b257ce9f..04242cad9ecb 100644
--- a/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
+++ b/mlir/lib/Dialect/Tensor/IR/TensorOps.cpp
@@ -1061,8 +1061,7 @@ void DimOp::getCanonicalizationPatterns(RewritePatternSet &results,
void EmptyOp::build(OpBuilder &builder, OperationState &result,
ArrayRef<int64_t> staticShape, Type elementType,
Attribute encoding) {
- assert(all_of(staticShape,
- [](int64_t sz) { return !ShapedType::isDynamic(sz); }) &&
+ assert(none_of(staticShape, ShapedType::isDynamic) &&
"expected only static sizes");
build(builder, result, staticShape, elementType, ValueRange{}, encoding);
}
@@ -1624,76 +1623,6 @@ OpFoldResult GatherOp::fold(FoldAdaptor adaptor) {
// InsertOp
//===----------------------------------------------------------------------===//
-namespace {
-
-/// Pattern to fold an insert op of a constant destination and scalar to a new
-/// constant.
-///
-/// Example:
-/// ```
-/// %0 = arith.constant dense<[1.0, 2.0, 3.0, 4.0]> : tensor<4xf32>
-/// %c0 = arith.constant 0 : index
-/// %c4_f32 = arith.constant 4.0 : f32
-/// %1 = tensor.insert %c4_f32 into %0[%c0] : tensor<4xf32>
-/// ```
-/// is rewritten into:
-/// ```
-/// %1 = arith.constant dense<[4.0, 2.0, 3.0, 4.0]> : tensor<4xf32>
-/// ```
-class InsertOpConstantFold final : public OpRewritePattern<InsertOp> {
-public:
- using OpRewritePattern<InsertOp>::OpRewritePattern;
-
- LogicalResult matchAndRewrite(InsertOp insertOp,
- PatternRewriter &rewriter) const override {
- // Requires a ranked tensor type.
- auto destType =
- llvm::dyn_cast<RankedTensorType>(insertOp.getDest().getType());
- if (!destType)
- return failure();
-
- // Pattern requires constant indices
- SmallVector<uint64_t, 8> indices;
- for (OpFoldResult indice : getAsOpFoldResult(insertOp.getIndices())) {
- auto indiceAttr = dyn_cast<Attribute>(indice);
- if (!indiceAttr)
- return failure();
- indices.push_back(llvm::cast<IntegerAttr>(indiceAttr).getInt());
- }
-
- // Requires a constant scalar to insert
- OpFoldResult scalar = getAsOpFoldResult(insertOp.getScalar());
- Attribute scalarAttr = dyn_cast<Attribute>(scalar);
- if (!scalarAttr)
- return failure();
-
- if (auto constantOp = dyn_cast_or_null<arith::ConstantOp>(
- insertOp.getDest().getDefiningOp())) {
- if (auto sourceAttr =
- llvm::dyn_cast<ElementsAttr>(constantOp.getValue())) {
- // Update the attribute at the inserted index.
- auto sourceValues = sourceAttr.getValues<Attribute>();
- auto flattenedIndex = sourceAttr.getFlattenedIndex(indices);
- std::vector<Attribute> updatedValues;
- updatedValues.reserve(sourceAttr.getNumElements());
- for (unsigned i = 0; i < sourceAttr.getNumElements(); ++i) {
- updatedValues.push_back(i == flattenedIndex ? scalarAttr
- : sourceValues[i]);
- }
- rewriter.replaceOpWithNewOp<arith::ConstantOp>(
- insertOp, sourceAttr.getType(),
- DenseElementsAttr::get(cast<ShapedType>(sourceAttr.getType()),
- updatedValues));
- return success();
- }
- }
-
- return failure();
- }
-};
-
-} // namespace
-
void InsertOp::getAsmResultNames(
function_ref<void(Value, StringRef)> setNameFn) {
setNameFn(getResult(), "inserted");
@@ -1717,11 +1646,6 @@ OpFoldResult InsertOp::fold(FoldAdaptor adaptor) {
return {};
}
-void InsertOp::getCanonicalizationPatterns(RewritePatternSet &results,
- MLIRContext *context) {
- results.add<InsertOpConstantFold>(context);
-}
-
//===----------------------------------------------------------------------===//
// GenerateOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Tensor/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Tensor/Transforms/CMakeLists.txt
index 7880d1c5a0c5..99e1c4fec846 100644
--- a/mlir/lib/Dialect/Tensor/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tensor/Transforms/CMakeLists.txt
@@ -8,6 +8,7 @@ add_mlir_dialect_library(MLIRTensorTransforms
MergeConsecutiveInsertExtractSlicePatterns.cpp
ReshapePatterns.cpp
RewriteAsConstant.cpp
+ RuntimeOpVerification.cpp
SwapExtractSliceWithProducerPatterns.cpp
SubsetInsertionOpInterfaceImpl.cpp
@@ -28,6 +29,7 @@ add_mlir_dialect_library(MLIRTensorTransforms
MLIRLinalgDialect
MLIRMemRefDialect
MLIRPass
+ MLIRRuntimeVerifiableOpInterface
MLIRSCFDialect
MLIRSubsetOpInterface
MLIRTensorDialect
diff --git a/mlir/lib/Dialect/Tensor/Transforms/ReshapePatterns.cpp b/mlir/lib/Dialect/Tensor/Transforms/ReshapePatterns.cpp
index 2b229d60c691..657624b817af 100644
--- a/mlir/lib/Dialect/Tensor/Transforms/ReshapePatterns.cpp
+++ b/mlir/lib/Dialect/Tensor/Transforms/ReshapePatterns.cpp
@@ -185,13 +185,11 @@ struct BubbleUpExpandThroughParallelCollapse
ArrayRef<int64_t> collapsedStaticShapes = staticSourceSize.slice(
collapseReassociation.front(), collapseReassociation.size());
int64_t numCollapsedDynamic =
- llvm::count_if(collapsedStaticShapes,
- [](int64_t d) { return ShapedType::isDynamic(d); });
+ llvm::count_if(collapsedStaticShapes, ShapedType::isDynamic);
ArrayRef<int64_t> expandedStaticShapes = staticResultSize.slice(
expandReassociation.front(), expandReassociation.size());
int64_t numExpandedDynamic =
- llvm::count_if(expandedStaticShapes,
- [](int64_t d) { return ShapedType::isDynamic(d); });
+ llvm::count_if(expandedStaticShapes, ShapedType::isDynamic);
if (numCollapsedDynamic > 1 || numExpandedDynamic > 1 ||
collapsedStaticShapes != expandedStaticShapes) {
return failure();
diff --git a/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp b/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp
new file mode 100644
index 000000000000..6138821ee8c6
--- /dev/null
+++ b/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp
@@ -0,0 +1,208 @@
+//===- RuntimeOpVerification.cpp - Op Verification ------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/Tensor/Transforms/RuntimeOpVerification.h"
+
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/Arith/Utils/Utils.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/Tensor/IR/Tensor.h"
+#include "mlir/Dialect/Utils/IndexingUtils.h"
+#include "mlir/Interfaces/RuntimeVerifiableOpInterface.h"
+
+using namespace mlir;
+
+namespace mlir {
+namespace tensor {
+namespace {
+/// Generate a runtime check for lb <= value < ub.
+Value generateInBoundsCheck(OpBuilder &builder, Location loc, Value value,
+ Value lb, Value ub) {
+ Value inBounds1 = builder.createOrFold<arith::CmpIOp>(
+ loc, arith::CmpIPredicate::sge, value, lb);
+ Value inBounds2 = builder.createOrFold<arith::CmpIOp>(
+ loc, arith::CmpIPredicate::slt, value, ub);
+ Value inBounds =
+ builder.createOrFold<arith::AndIOp>(loc, inBounds1, inBounds2);
+ return inBounds;
+}
+
+struct CastOpInterface
+ : public RuntimeVerifiableOpInterface::ExternalModel<CastOpInterface,
+ CastOp> {
+ void generateRuntimeVerification(Operation *op, OpBuilder &builder,
+ Location loc) const {
+ auto castOp = cast<CastOp>(op);
+ auto srcType = cast<TensorType>(castOp.getSource().getType());
+
+ // Nothing to check if the result is an unranked tensor.
+ auto resultType = dyn_cast<RankedTensorType>(castOp.getType());
+ if (!resultType)
+ return;
+
+ if (isa<UnrankedTensorType>(srcType)) {
+ // Check rank.
+ Value srcRank = builder.create<RankOp>(loc, castOp.getSource());
+ Value resultRank =
+ builder.create<arith::ConstantIndexOp>(loc, resultType.getRank());
+ Value isSameRank = builder.create<arith::CmpIOp>(
+ loc, arith::CmpIPredicate::eq, srcRank, resultRank);
+ builder.create<cf::AssertOp>(
+ loc, isSameRank,
+ RuntimeVerifiableOpInterface::generateErrorMessage(op,
+ "rank mismatch"));
+ }
+
+ // Check dimension sizes.
+ for (const auto &it : llvm::enumerate(resultType.getShape())) {
+ // Static dim size -> static/dynamic dim size does not need verification.
+ if (auto rankedSrcType = dyn_cast<RankedTensorType>(srcType))
+ if (!rankedSrcType.isDynamicDim(it.index()))
+ continue;
+
+ // Static/dynamic dim size -> dynamic dim size does not need verification.
+ if (resultType.isDynamicDim(it.index()))
+ continue;
+
+ Value srcDimSz =
+ builder.create<DimOp>(loc, castOp.getSource(), it.index());
+ Value resultDimSz =
+ builder.create<arith::ConstantIndexOp>(loc, it.value());
+ Value isSameSz = builder.create<arith::CmpIOp>(
+ loc, arith::CmpIPredicate::eq, srcDimSz, resultDimSz);
+ builder.create<cf::AssertOp>(
+ loc, isSameSz,
+ RuntimeVerifiableOpInterface::generateErrorMessage(
+ op, "size mismatch of dim " + std::to_string(it.index())));
+ }
+ }
+};
+
+struct DimOpInterface
+ : public RuntimeVerifiableOpInterface::ExternalModel<DimOpInterface,
+ DimOp> {
+ void generateRuntimeVerification(Operation *op, OpBuilder &builder,
+ Location loc) const {
+ auto dimOp = cast<DimOp>(op);
+ Value rank = builder.create<RankOp>(loc, dimOp.getSource());
+ Value zero = builder.create<arith::ConstantIndexOp>(loc, 0);
+ builder.create<cf::AssertOp>(
+ loc, generateInBoundsCheck(builder, loc, dimOp.getIndex(), zero, rank),
+ RuntimeVerifiableOpInterface::generateErrorMessage(
+ op, "index is out of bounds"));
+ }
+};
+
+/// Verifies that the indices on extract/insert ops are in-bounds of the
+/// tensor's index space: 0 <= index#i < dim#i
+template <typename OpTy>
+struct ExtractInsertOpInterface
+ : public RuntimeVerifiableOpInterface::ExternalModel<
+ ExtractInsertOpInterface<OpTy>, OpTy> {
+ void generateRuntimeVerification(Operation *op, OpBuilder &builder,
+ Location loc) const {
+ auto extractInsertOp = cast<OpTy>(op);
+
+ Value tensor;
+ if constexpr (std::is_same_v<OpTy, ExtractOp>) {
+ tensor = extractInsertOp.getTensor();
+ } else if constexpr (std::is_same_v<OpTy, InsertOp>) {
+ tensor = extractInsertOp.getDest();
+ } else {
+ llvm_unreachable("invalid op");
+ }
+ auto tensorType = cast<RankedTensorType>(tensor.getType());
+ auto rank = tensorType.getRank();
+ if (rank == 0) {
+ // Nothing to check for 0-d tensors.
+ return;
+ }
+
+ auto indices = extractInsertOp.getIndices();
+ auto zero = builder.create<arith::ConstantIndexOp>(loc, 0);
+ Value assertCond;
+ for (auto i : llvm::seq<int64_t>(0, rank)) {
+ Value dimOp = builder.createOrFold<tensor::DimOp>(loc, tensor, i);
+ Value inBounds =
+ generateInBoundsCheck(builder, loc, indices[i], zero, dimOp);
+ assertCond =
+ i > 0 ? builder.createOrFold<arith::AndIOp>(loc, assertCond, inBounds)
+ : inBounds;
+ }
+ builder.create<cf::AssertOp>(
+ loc, assertCond,
+ RuntimeVerifiableOpInterface::generateErrorMessage(
+ op, "out-of-bounds access"));
+ }
+};
+
+struct ExtractSliceOpInterface
+ : public RuntimeVerifiableOpInterface::ExternalModel<
+ ExtractSliceOpInterface, ExtractSliceOp> {
+ void generateRuntimeVerification(Operation *op, OpBuilder &builder,
+ Location loc) const {
+ auto extractSliceOp = cast<ExtractSliceOp>(op);
+ RankedTensorType sourceType = extractSliceOp.getSource().getType();
+
+ // For each dimension, assert that:
+ // 0 <= offset < dim_size
+ // 0 <= offset + (size - 1) * stride < dim_size
+ Value zero = builder.create<arith::ConstantIndexOp>(loc, 0);
+ Value one = builder.create<arith::ConstantIndexOp>(loc, 1);
+ for (int64_t i = 0, e = sourceType.getRank(); i < e; ++i) {
+ Value offset = getValueOrCreateConstantIndexOp(
+ builder, loc, extractSliceOp.getMixedOffsets()[i]);
+ Value size = getValueOrCreateConstantIndexOp(
+ builder, loc, extractSliceOp.getMixedSizes()[i]);
+ Value stride = getValueOrCreateConstantIndexOp(
+ builder, loc, extractSliceOp.getMixedStrides()[i]);
+
+ // Verify that offset is in-bounds.
+ Value dimSize = builder.createOrFold<tensor::DimOp>(
+ loc, extractSliceOp.getSource(), i);
+ Value offsetInBounds =
+ generateInBoundsCheck(builder, loc, offset, zero, dimSize);
+ builder.create<cf::AssertOp>(
+ loc, offsetInBounds,
+ RuntimeVerifiableOpInterface::generateErrorMessage(
+ op, "offset " + std::to_string(i) + " is out-of-bounds"));
+
+ // Verify that slice does not run out-of-bounds.
+ Value sizeMinusOne = builder.create<arith::SubIOp>(loc, size, one);
+ Value sizeMinusOneTimesStride =
+ builder.create<arith::MulIOp>(loc, sizeMinusOne, stride);
+ Value lastPos =
+ builder.create<arith::AddIOp>(loc, offset, sizeMinusOneTimesStride);
+ Value lastPosInBounds =
+ generateInBoundsCheck(builder, loc, lastPos, zero, dimSize);
+ builder.create<cf::AssertOp>(
+ loc, lastPosInBounds,
+ RuntimeVerifiableOpInterface::generateErrorMessage(
+ op, "extract_slice runs out-of-bounds along dimension " +
+ std::to_string(i)));
+ }
+ }
+};
+} // namespace
+} // namespace tensor
+} // namespace mlir
+
+void mlir::tensor::registerRuntimeVerifiableOpInterfaceExternalModels(
+ DialectRegistry &registry) {
+ registry.addExtension(+[](MLIRContext *ctx, tensor::TensorDialect *dialect) {
+ CastOp::attachInterface<CastOpInterface>(*ctx);
+ DimOp::attachInterface<DimOpInterface>(*ctx);
+ ExtractOp::attachInterface<ExtractInsertOpInterface<ExtractOp>>(*ctx);
+ ExtractSliceOp::attachInterface<ExtractSliceOpInterface>(*ctx);
+ InsertOp::attachInterface<ExtractInsertOpInterface<InsertOp>>(*ctx);
+
+ // Load additional dialects of which ops may get created.
+ ctx->loadDialect<arith::ArithDialect, cf::ControlFlowDialect>();
+ });
+}
diff --git a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
index a22e6b7aa979..a32e4ccbed59 100644
--- a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
+++ b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
@@ -1857,7 +1857,8 @@ LogicalResult tosa::MulOp::inferReturnTypeComponents(
}
LogicalResult tosa::MulOp::verify() {
- auto resElemType = getElementTypeOrSelf(getOutput());
+ const Value output = getOutput();
+ auto resElemType = getElementTypeOrSelf(output);
// Verify if the element type among operands and result match tosa
// specification.
@@ -1897,59 +1898,39 @@ LogicalResult tosa::MulOp::verify() {
// Verify the op has same ranks for all main operands (excludes extra operands
// such as shift of mul op, so this is the only difference with the built-in
// `SameOperandsAndResultRank` trait) and results types, if known.
-
- // delegate function that returns true if type is a shaped type with known
- // rank
- auto hasRank = [](const Type type) {
- if (auto shaped_type = dyn_cast<ShapedType>(type))
- return shaped_type.hasRank();
-
- return false;
- };
-
- auto rankedOperandTypes =
- llvm::to_vector(llvm::make_filter_range(getOperandTypes(), hasRank));
-
- auto rankedResultTypes =
- llvm::make_filter_range(getOperation()->getResultTypes(), hasRank);
-
- // If all operands and results are unranked, then no further verification.
- if (rankedOperandTypes.empty() && rankedResultTypes.empty())
+ TypeRange operandTypes = getOperandTypes();
+ ShapedType aType = cast<ShapedType>(operandTypes[0]);
+ ShapedType bType = cast<ShapedType>(operandTypes[1]);
+
+ const bool aHasRank = aType.hasRank();
+ const bool bHasRank = bType.hasRank();
+ if (aHasRank && bHasRank) {
+ const int64_t aRank = aType.getRank();
+ const int64_t bRank = bType.getRank();
+ if (aRank != bRank)
+ return emitOpError("a and b operands don't have matching ranks, got ")
+ << aRank << " and " << bRank;
+
+ // check for broadcast compatible shapes
+ SmallVector<int64_t> resultShape;
+ if (!mlir::OpTrait::util::getBroadcastedShape(
+ aType.getShape(), bType.getShape(), resultShape))
+ return emitOpError("a and b operands don't have broadcast-compatible "
+ "shapes, got ")
+ << aType << " and " << bType;
+ }
+
+ ShapedType resultType = cast<ShapedType>(output.getType());
+ if (!resultType.hasRank())
return success();
- // delegate function that returns rank of shaped type with known rank
- auto getRank = [](const Type type) {
- return cast<ShapedType>(type).getRank();
- };
-
- auto rank = !rankedOperandTypes.empty() ? getRank(*rankedOperandTypes.begin())
- : getRank(*rankedResultTypes.begin());
-
- for (size_t i = 0; i < 2; ++i) {
- if (rank != getRank(rankedOperandTypes[i])) {
- return emitOpError("operands don't have matching ranks");
- }
- }
-
- for (const auto type : rankedResultTypes) {
- if (rank != getRank(type)) {
- return emitOpError("result type has different rank than operands");
- }
- }
-
- // check for broadcast compatible shapes in first two operands (ignoring
- // shift)
-
- // delegate function that returns shape of shaped type
- auto getShape = [](const Type type) {
- return mlir::cast<ShapedType>(type).getShape();
- };
- SmallVector<int64_t> resultShape;
- if (!mlir::OpTrait::util::getBroadcastedShape(getShape(rankedOperandTypes[0]),
- getShape(rankedOperandTypes[1]),
- resultShape)) {
- return emitOpError("operands don't have broadcast-compatible shapes");
- }
+ const int64_t resultRank = resultType.getRank();
+ if (aHasRank && resultRank != aType.getRank())
+ return emitOpError("result type has different rank than a, got ")
+ << resultRank << " vs " << aType.getRank();
+ if (bHasRank && resultRank != bType.getRank())
+ return emitOpError("result type has different rank than b, got ")
+ << resultRank << " vs " << bType.getRank();
return success();
}
@@ -2692,6 +2673,73 @@ LogicalResult tosa::ScatterOp::verify() {
.failed()) {
return failure();
}
+
+ const ShapeAdaptor valuesInShape(getValuesIn().getType());
+ const ShapeAdaptor indicesShape(getIndices().getType());
+ const ShapeAdaptor inputShape(getInput().getType());
+ const ShapeAdaptor outputShape(getValuesOut().getType());
+
+ int64_t N = ShapedType::kDynamic;
+ int64_t K = ShapedType::kDynamic;
+ int64_t W = ShapedType::kDynamic;
+ int64_t C = ShapedType::kDynamic;
+ if (valuesInShape.hasRank()) {
+ N = valuesInShape.getDimSize(0);
+ K = valuesInShape.getDimSize(1);
+ C = valuesInShape.getDimSize(2);
+ }
+ if (indicesShape.hasRank()) {
+ const int64_t indicesN = indicesShape.getDimSize(0);
+ W = indicesShape.getDimSize(1);
+ if (N == ShapedType::kDynamic)
+ N = indicesN;
+ else if (indicesN != ShapedType::kDynamic && N != indicesN)
+ return emitOpError() << "requires indices dimension 0 to have size " << N
+ << ", got " << indicesN;
+ }
+ if (inputShape.hasRank()) {
+ const int64_t inputN = inputShape.getDimSize(0);
+ const int64_t inputW = inputShape.getDimSize(1);
+ const int64_t inputC = inputShape.getDimSize(2);
+ if (N == ShapedType::kDynamic)
+ N = inputN;
+ else if (inputN != ShapedType::kDynamic && N != inputN)
+ return emitOpError() << "requires input dimension 0 to have size " << N
+ << ", got " << inputN;
+ if (W == ShapedType::kDynamic)
+ W = inputW;
+ else if (inputW != ShapedType::kDynamic && W != inputW)
+ return emitOpError() << "requires input dimension 1 to have size " << W
+ << ", got " << inputW;
+
+ if (C == ShapedType::kDynamic)
+ C = inputC;
+ else if (inputC != ShapedType::kDynamic && C != inputC)
+ return emitOpError() << "requires input dimension 2 to have size " << C
+ << ", got " << inputC;
+ }
+ if (outputShape.hasRank()) {
+ const int64_t outputN = outputShape.getDimSize(0);
+ const int64_t outputK = outputShape.getDimSize(1);
+ const int64_t outputC = outputShape.getDimSize(2);
+ if (N != ShapedType::kDynamic && outputN != ShapedType::kDynamic &&
+ N != outputN)
+ return emitOpError() << "requires values_out dimension 0 to have size "
+ << N << ", got " << outputN;
+ if (K == ShapedType::kDynamic)
+ K = outputK;
+ else if (outputK != ShapedType::kDynamic && K != outputK)
+ return emitOpError() << "requires values_out dimension 1 to have size "
+ << K << ", got " << outputK;
+ if (C != ShapedType::kDynamic && outputC != ShapedType::kDynamic &&
+ C != outputC)
+ return emitOpError() << "requires values_out dimension 2 to have size "
+ << C << ", got " << outputC;
+ }
+ if (K != ShapedType::kDynamic && W != ShapedType::kDynamic && !(K >= W))
+ return emitOpError() << "requires dimensions K >= W, got K=" << K
+ << " and W=" << W;
+
return success();
}
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaInferShapes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaInferShapes.cpp
index 9aa0051070bd..29ec9f8db261 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaInferShapes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaInferShapes.cpp
@@ -344,4 +344,4 @@ public:
validateSameOperandsAndResultRankTrait(func.getBody());
}
};
-} // namespace \ No newline at end of file
+} // namespace
diff --git a/mlir/lib/Dialect/Transform/IR/TransformOps.cpp b/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
index 673743f22249..a0f9518e3d12 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
@@ -53,6 +53,12 @@
using namespace mlir;
+static ParseResult parseApplyRegisteredPassOptions(
+ OpAsmParser &parser, ArrayAttr &options,
+ SmallVectorImpl<OpAsmParser::UnresolvedOperand> &dynamicOptions);
+static void printApplyRegisteredPassOptions(OpAsmPrinter &printer,
+ Operation *op, ArrayAttr options,
+ ValueRange dynamicOptions);
static ParseResult parseSequenceOpOperands(
OpAsmParser &parser, std::optional<OpAsmParser::UnresolvedOperand> &root,
Type &rootType,
@@ -766,17 +772,53 @@ void transform::ApplyLoopInvariantCodeMotionOp::getEffects(
// ApplyRegisteredPassOp
//===----------------------------------------------------------------------===//
-DiagnosedSilenceableFailure transform::ApplyRegisteredPassOp::applyToOne(
- transform::TransformRewriter &rewriter, Operation *target,
- ApplyToEachResultList &results, transform::TransformState &state) {
- // Make sure that this transform is not applied to itself. Modifying the
- // transform IR while it is being interpreted is generally dangerous. Even
- // more so when applying passes because they may perform a wide range of IR
- // modifications.
- DiagnosedSilenceableFailure payloadCheck =
- ensurePayloadIsSeparateFromTransform(*this, target);
- if (!payloadCheck.succeeded())
- return payloadCheck;
+void transform::ApplyRegisteredPassOp::getEffects(
+ SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
+ consumesHandle(getTargetMutable(), effects);
+ onlyReadsHandle(getDynamicOptionsMutable(), effects);
+ producesHandle(getOperation()->getOpResults(), effects);
+ modifiesPayload(effects);
+}
+
+DiagnosedSilenceableFailure
+transform::ApplyRegisteredPassOp::apply(transform::TransformRewriter &rewriter,
+ transform::TransformResults &results,
+ transform::TransformState &state) {
+ // Obtain a single options-string from options passed statically as
+ // string attributes as well as "dynamically" through params.
+ std::string options;
+ OperandRange dynamicOptions = getDynamicOptions();
+ size_t dynamicOptionsIdx = 0;
+ for (auto [idx, optionAttr] : llvm::enumerate(getOptions())) {
+ if (idx > 0)
+ options += " "; // Interleave options seperator.
+
+ if (auto strAttr = dyn_cast<StringAttr>(optionAttr)) {
+ options += strAttr.getValue();
+ } else if (isa<UnitAttr>(optionAttr)) {
+ assert(dynamicOptionsIdx < dynamicOptions.size() &&
+ "number of dynamic option markers (UnitAttr) in options ArrayAttr "
+ "should be the same as the number of options passed as params");
+ ArrayRef<Attribute> dynamicOption =
+ state.getParams(dynamicOptions[dynamicOptionsIdx++]);
+ if (dynamicOption.size() != 1)
+ return emitSilenceableError() << "options passed as a param must have "
+ "a single value associated, param "
+ << dynamicOptionsIdx - 1 << " associates "
+ << dynamicOption.size();
+
+ if (auto dynamicOptionStr = dyn_cast<StringAttr>(dynamicOption[0])) {
+ options += dynamicOptionStr.getValue();
+ } else {
+ return emitSilenceableError()
+ << "options passed as a param must be a string, got "
+ << dynamicOption[0];
+ }
+ } else {
+ llvm_unreachable(
+ "expected options element to be either StringAttr or UnitAttr");
+ }
+ }
// Get pass or pass pipeline from registry.
const PassRegistryEntry *info = PassPipelineInfo::lookup(getPassName());
@@ -786,9 +828,9 @@ DiagnosedSilenceableFailure transform::ApplyRegisteredPassOp::applyToOne(
return emitDefiniteFailure()
<< "unknown pass or pass pipeline: " << getPassName();
- // Create pass manager and run the pass or pass pipeline.
+ // Create pass manager and add the pass or pass pipeline.
PassManager pm(getContext());
- if (failed(info->addToPipeline(pm, getOptions(), [&](const Twine &msg) {
+ if (failed(info->addToPipeline(pm, options, [&](const Twine &msg) {
emitError(msg);
return failure();
}))) {
@@ -796,16 +838,114 @@ DiagnosedSilenceableFailure transform::ApplyRegisteredPassOp::applyToOne(
<< "failed to add pass or pass pipeline to pipeline: "
<< getPassName();
}
- if (failed(pm.run(target))) {
- auto diag = emitSilenceableError() << "pass pipeline failed";
- diag.attachNote(target->getLoc()) << "target op";
- return diag;
+
+ auto targets = SmallVector<Operation *>(state.getPayloadOps(getTarget()));
+ for (Operation *target : targets) {
+ // Make sure that this transform is not applied to itself. Modifying the
+ // transform IR while it is being interpreted is generally dangerous. Even
+ // more so when applying passes because they may perform a wide range of IR
+ // modifications.
+ DiagnosedSilenceableFailure payloadCheck =
+ ensurePayloadIsSeparateFromTransform(*this, target);
+ if (!payloadCheck.succeeded())
+ return payloadCheck;
+
+ // Run the pass or pass pipeline on the current target operation.
+ if (failed(pm.run(target))) {
+ auto diag = emitSilenceableError() << "pass pipeline failed";
+ diag.attachNote(target->getLoc()) << "target op";
+ return diag;
+ }
}
- results.push_back(target);
+ // The applied pass will have directly modified the payload IR(s).
+ results.set(llvm::cast<OpResult>(getResult()), targets);
return DiagnosedSilenceableFailure::success();
}
+static ParseResult parseApplyRegisteredPassOptions(
+ OpAsmParser &parser, ArrayAttr &options,
+ SmallVectorImpl<OpAsmParser::UnresolvedOperand> &dynamicOptions) {
+ auto dynamicOptionMarker = UnitAttr::get(parser.getContext());
+ SmallVector<Attribute> optionsArray;
+
+ auto parseOperandOrString = [&]() -> OptionalParseResult {
+ OpAsmParser::UnresolvedOperand operand;
+ OptionalParseResult parsedOperand = parser.parseOptionalOperand(operand);
+ if (parsedOperand.has_value()) {
+ if (failed(parsedOperand.value()))
+ return failure();
+
+ dynamicOptions.push_back(operand);
+ optionsArray.push_back(
+ dynamicOptionMarker); // Placeholder for knowing where to
+ // inject the dynamic option-as-param.
+ return success();
+ }
+
+ StringAttr stringAttr;
+ OptionalParseResult parsedStringAttr =
+ parser.parseOptionalAttribute(stringAttr);
+ if (parsedStringAttr.has_value()) {
+ if (failed(parsedStringAttr.value()))
+ return failure();
+ optionsArray.push_back(stringAttr);
+ return success();
+ }
+
+ return std::nullopt;
+ };
+
+ OptionalParseResult parsedOptionsElement = parseOperandOrString();
+ while (parsedOptionsElement.has_value()) {
+ if (failed(parsedOptionsElement.value()))
+ return failure();
+ parsedOptionsElement = parseOperandOrString();
+ }
+
+ if (optionsArray.empty()) {
+ return parser.emitError(parser.getCurrentLocation())
+ << "expected at least one option (either a string or a param)";
+ }
+ options = parser.getBuilder().getArrayAttr(optionsArray);
+ return success();
+}
+
+static void printApplyRegisteredPassOptions(OpAsmPrinter &printer,
+ Operation *op, ArrayAttr options,
+ ValueRange dynamicOptions) {
+ size_t currentDynamicOptionIdx = 0;
+ for (auto [idx, optionAttr] : llvm::enumerate(options)) {
+ if (idx > 0)
+ printer << " "; // Interleave options separator.
+
+ if (isa<UnitAttr>(optionAttr))
+ printer.printOperand(dynamicOptions[currentDynamicOptionIdx++]);
+ else if (auto strAttr = dyn_cast<StringAttr>(optionAttr))
+ printer.printAttribute(strAttr);
+ else
+ llvm_unreachable("each option should be either a StringAttr or UnitAttr");
+ }
+}
+
+LogicalResult transform::ApplyRegisteredPassOp::verify() {
+ size_t numUnitsInOptions = 0;
+ for (Attribute optionsElement : getOptions()) {
+ if (isa<UnitAttr>(optionsElement))
+ numUnitsInOptions++;
+ else if (!isa<StringAttr>(optionsElement))
+ return emitOpError() << "expected each option to be either a StringAttr "
+ << "or a UnitAttr, got " << optionsElement;
+ }
+
+ if (getDynamicOptions().size() != numUnitsInOptions)
+ return emitOpError()
+ << "expected the same number of options passed as params as "
+ << "UnitAttr elements in options ArrayAttr";
+
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// CastOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index fcfb401fd986..3179b4f97540 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -2522,6 +2522,10 @@ void BroadcastOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
setResultRanges(getResult(), argRanges.front());
}
+std::optional<SmallVector<int64_t, 4>> BroadcastOp::getShapeForUnroll() {
+ return llvm::to_vector<4>(getResultVectorType().getShape());
+}
+
/// Return the dimensions of the result vector that were formerly ones in the
/// source tensor and thus correspond to "dim-1" broadcasting.
static llvm::SetVector<int64_t>
diff --git a/mlir/lib/Dialect/Vector/Transforms/LowerVectorShapeCast.cpp b/mlir/lib/Dialect/Vector/Transforms/LowerVectorShapeCast.cpp
index 23324a007377..39c16fab21c4 100644
--- a/mlir/lib/Dialect/Vector/Transforms/LowerVectorShapeCast.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/LowerVectorShapeCast.cpp
@@ -21,177 +21,298 @@
#include "mlir/IR/Location.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/TypeUtilities.h"
+#include <numeric>
#define DEBUG_TYPE "vector-shape-cast-lowering"
using namespace mlir;
-using namespace mlir::vector;
-
-/// Increments n-D `indices` by `step` starting from the innermost dimension.
-static void incIdx(SmallVectorImpl<int64_t> &indices, VectorType vecType,
- int step = 1) {
- for (int dim : llvm::reverse(llvm::seq<int>(0, indices.size()))) {
- assert(indices[dim] < vecType.getDimSize(dim) &&
- "Indices are out of bound");
- indices[dim] += step;
- if (indices[dim] < vecType.getDimSize(dim))
- break;
- indices[dim] = 0;
- step = 1;
+/// Perform the inplace update
+/// rhs <- lhs + rhs
+///
+/// where `rhs` is a number expressed in mixed base `base` with most signficant
+/// dimensions on the left. For example if `rhs` is {a,b,c} and `base` is
+/// {5,3,2} then `rhs` has value a*3*2 + b*2 + c.
+///
+/// Some examples where `base` is {5,3,2}:
+/// rhs = {0,0,0}, lhs = 1 --> rhs = {0,0,1}
+/// rhs = {0,0,1}, lhs = 1 --> rhs = {0,1,0}
+/// rhs = {0,0,0}, lhs = 25 --> rhs = {4,0,1}
+///
+/// Invalid:
+/// rhs = {0,0,2}, lhs = 1 : rhs not in base {5,3,2}
+///
+/// Overflows not handled correctly:
+/// rhs = {4,2,1}, lhs = 2 --> rhs = {0,0,0} (not {0,0,1})
+static void inplaceAdd(int64_t lhs, ArrayRef<int64_t> base,
+ MutableArrayRef<int64_t> rhs) {
+
+ // For dimensions in [numIndices - 1, ..., 3, 2, 1, 0]:
+ for (int dim : llvm::reverse(llvm::seq<int>(0, rhs.size()))) {
+ int64_t dimBase = base[dim];
+ assert(rhs[dim] < dimBase && "rhs not in base");
+
+ int64_t incremented = rhs[dim] + lhs;
+
+ // If the incremented value excedes the dimension base, we must spill to the
+ // next most significant dimension and repeat (we might need to spill to
+ // more significant dimensions multiple times).
+ lhs = incremented / dimBase;
+ rhs[dim] = incremented % dimBase;
+ if (lhs == 0)
+ break;
}
}
namespace {
-/// ShapeOp n-D -> 1-D downcast serves the purpose of flattening N-D to 1-D
-/// vectors progressively. This iterates over the n-1 major dimensions of the
-/// n-D vector and performs rewrites into:
-/// vector.extract from n-D + vector.insert_strided_slice offset into 1-D
-class ShapeCastOpNDDownCastRewritePattern
- : public OpRewritePattern<vector::ShapeCastOp> {
-public:
- using OpRewritePattern::OpRewritePattern;
- LogicalResult matchAndRewrite(vector::ShapeCastOp op,
- PatternRewriter &rewriter) const override {
- auto sourceVectorType = op.getSourceVectorType();
- auto resultVectorType = op.getResultVectorType();
- if (sourceVectorType.isScalable() || resultVectorType.isScalable())
- return failure();
+/// shape_cast is converted to a sequence of extract, extract_strided_slice,
+/// insert_strided_slice, and insert operations. The running example will be:
+///
+/// %0 = vector.shape_cast %arg0 :
+/// vector<2x2x3x4x7x11xi8> to vector<8x6x7x11xi8>
+///
+/// In this example the source and result shapes share a common suffix of 7x11.
+/// This means we can always decompose the shape_cast into extract, insert, and
+/// their strided equivalents, on vectors with shape suffix 7x11.
+///
+/// The greatest common divisor (gcd) of the first dimension preceding the
+/// common suffix is gcd(4,6) = 2. The algorithm implemented here will operate
+/// on vectors with shapes that are `multiples` of (what we define as) the
+/// 'atomic shape', 2x7x11. The atomic shape is `gcd` x `common-suffix`.
+///
+/// vector<2x2x3x4x7x11xi8> to
+/// vector<8x6x7x11xi8>
+/// | ||||
+/// | ++++------------> common suffix of 7x11
+/// +-----------------> gcd(4,6) is 2 | |
+/// | | |
+/// v v v
+/// atomic shape <----- 2x7x11
+///
+///
+///
+/// The decomposition implemented in this pattern consists of a sequence of
+/// repeated steps:
+///
+/// (1) Extract vectors from the suffix of the source.
+/// In our example this is 2x2x3x4x7x11 -> 4x7x11.
+///
+/// (2) Do extract_strided_slice down to the atomic shape.
+/// In our example this is 4x7x11 -> 2x7x11.
+///
+/// (3) Do insert_strided_slice to the suffix of the result.
+/// In our example this is 2x7x11 -> 6x7x11.
+///
+/// (4) insert these vectors into the result vector.
+/// In our example this is 6x7x11 -> 8x6x7x11.
+///
+/// These steps occur with different periods. In this example
+/// (1) occurs 12 times,
+/// (2) and (3) occur 24 times, and
+/// (4) occurs 8 times.
+///
+/// Two special cases are handled independently in this pattern
+/// (i) A shape_cast that just does leading 1 insertion/removal
+/// (ii) A shape_cast where the gcd is 1.
+///
+/// These 2 cases can have more compact IR generated by not using the generic
+/// algorithm described above.
+///
+class ShapeCastOpRewritePattern : public OpRewritePattern<vector::ShapeCastOp> {
+
+ // Case (i) of description.
+ // Assumes source and result shapes are identical up to some leading ones.
+ static LogicalResult leadingOnesLowering(vector::ShapeCastOp shapeCast,
+ PatternRewriter &rewriter) {
+
+ const Location loc = shapeCast.getLoc();
+ const VectorType sourceType = shapeCast.getSourceVectorType();
+ const VectorType resultType = shapeCast.getResultVectorType();
+
+ const int64_t sourceRank = sourceType.getRank();
+ const int64_t resultRank = resultType.getRank();
+ const int64_t delta = sourceRank - resultRank;
+ const int64_t sourceLeading = delta > 0 ? delta : 0;
+ const int64_t resultLeading = delta > 0 ? 0 : -delta;
+
+ const Value source = shapeCast.getSource();
+ const Value poison = rewriter.create<ub::PoisonOp>(loc, resultType);
+ const Value extracted = rewriter.create<vector::ExtractOp>(
+ loc, source, SmallVector<int64_t>(sourceLeading, 0));
+ const Value result = rewriter.create<vector::InsertOp>(
+ loc, extracted, poison, SmallVector<int64_t>(resultLeading, 0));
+
+ rewriter.replaceOp(shapeCast, result);
+ return success();
+ }
- int64_t srcRank = sourceVectorType.getRank();
- int64_t resRank = resultVectorType.getRank();
- if (srcRank < 2 || resRank != 1)
- return failure();
+ // Case (ii) of description.
+ // Assumes a shape_cast where the suffix shape of the source starting at
+ // `sourceDim` and the suffix shape of the result starting at `resultDim` are
+ // identical.
+ static LogicalResult noStridedSliceLowering(vector::ShapeCastOp shapeCast,
+ int64_t sourceDim,
+ int64_t resultDim,
+ PatternRewriter &rewriter) {
- // Compute the number of 1-D vector elements involved in the reshape.
- int64_t numElts = 1;
- for (int64_t dim = 0; dim < srcRank - 1; ++dim)
- numElts *= sourceVectorType.getDimSize(dim);
+ const Location loc = shapeCast.getLoc();
- auto loc = op.getLoc();
- SmallVector<int64_t> srcIdx(srcRank - 1, 0);
- SmallVector<int64_t> resIdx(resRank, 0);
- int64_t extractSize = sourceVectorType.getShape().back();
- Value result = rewriter.create<ub::PoisonOp>(loc, resultVectorType);
+ const Value source = shapeCast.getSource();
+ const ArrayRef<int64_t> sourceShape =
+ shapeCast.getSourceVectorType().getShape();
- // Compute the indices of each 1-D vector element of the source extraction
- // and destination slice insertion and generate such instructions.
- for (int64_t i = 0; i < numElts; ++i) {
- if (i != 0) {
- incIdx(srcIdx, sourceVectorType, /*step=*/1);
- incIdx(resIdx, resultVectorType, /*step=*/extractSize);
- }
+ const VectorType resultType = shapeCast.getResultVectorType();
+ const ArrayRef<int64_t> resultShape = resultType.getShape();
- Value extract =
- rewriter.create<vector::ExtractOp>(loc, op.getSource(), srcIdx);
- result = rewriter.create<vector::InsertStridedSliceOp>(
- loc, extract, result,
- /*offsets=*/resIdx, /*strides=*/1);
- }
+ const int64_t nSlices =
+ std::accumulate(sourceShape.begin(), sourceShape.begin() + sourceDim, 1,
+ std::multiplies<int64_t>());
- rewriter.replaceOp(op, result);
- return success();
- }
-};
+ SmallVector<int64_t> extractIndex(sourceDim, 0);
+ SmallVector<int64_t> insertIndex(resultDim, 0);
+ Value result = rewriter.create<ub::PoisonOp>(loc, resultType);
-/// ShapeOp 1-D -> n-D upcast serves the purpose of unflattening n-D from 1-D
-/// vectors progressively. This iterates over the n-1 major dimension of the n-D
-/// vector and performs rewrites into:
-/// vector.extract_strided_slice from 1-D + vector.insert into n-D
-/// Note that 1-D extract_strided_slice are lowered to efficient vector.shuffle.
-class ShapeCastOpNDUpCastRewritePattern
- : public OpRewritePattern<vector::ShapeCastOp> {
-public:
- using OpRewritePattern::OpRewritePattern;
+ for (int i = 0; i < nSlices; ++i) {
+ Value extracted =
+ rewriter.create<vector::ExtractOp>(loc, source, extractIndex);
- LogicalResult matchAndRewrite(vector::ShapeCastOp op,
- PatternRewriter &rewriter) const override {
- auto sourceVectorType = op.getSourceVectorType();
- auto resultVectorType = op.getResultVectorType();
- if (sourceVectorType.isScalable() || resultVectorType.isScalable())
- return failure();
-
- int64_t srcRank = sourceVectorType.getRank();
- int64_t resRank = resultVectorType.getRank();
- if (srcRank != 1 || resRank < 2)
- return failure();
-
- // Compute the number of 1-D vector elements involved in the reshape.
- int64_t numElts = 1;
- for (int64_t dim = 0; dim < resRank - 1; ++dim)
- numElts *= resultVectorType.getDimSize(dim);
-
- // Compute the indices of each 1-D vector element of the source slice
- // extraction and destination insertion and generate such instructions.
- auto loc = op.getLoc();
- SmallVector<int64_t> srcIdx(srcRank, 0);
- SmallVector<int64_t> resIdx(resRank - 1, 0);
- int64_t extractSize = resultVectorType.getShape().back();
- Value result = rewriter.create<ub::PoisonOp>(loc, resultVectorType);
- for (int64_t i = 0; i < numElts; ++i) {
- if (i != 0) {
- incIdx(srcIdx, sourceVectorType, /*step=*/extractSize);
- incIdx(resIdx, resultVectorType, /*step=*/1);
- }
+ result = rewriter.create<vector::InsertOp>(loc, extracted, result,
+ insertIndex);
- Value extract = rewriter.create<vector::ExtractStridedSliceOp>(
- loc, op.getSource(), /*offsets=*/srcIdx, /*sizes=*/extractSize,
- /*strides=*/1);
- result = rewriter.create<vector::InsertOp>(loc, extract, result, resIdx);
+ inplaceAdd(1, sourceShape.take_front(sourceDim), extractIndex);
+ inplaceAdd(1, resultShape.take_front(resultDim), insertIndex);
}
- rewriter.replaceOp(op, result);
+ rewriter.replaceOp(shapeCast, result);
return success();
}
-};
-// We typically should not lower general shape cast operations into data
-// movement instructions, since the assumption is that these casts are
-// optimized away during progressive lowering. For completeness, however,
-// we fall back to a reference implementation that moves all elements
-// into the right place if we get here.
-class ShapeCastOpRewritePattern : public OpRewritePattern<vector::ShapeCastOp> {
public:
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(vector::ShapeCastOp op,
PatternRewriter &rewriter) const override {
Location loc = op.getLoc();
- auto sourceVectorType = op.getSourceVectorType();
- auto resultVectorType = op.getResultVectorType();
+ VectorType sourceType = op.getSourceVectorType();
+ VectorType resultType = op.getResultVectorType();
+
+ if (sourceType.isScalable() || resultType.isScalable())
+ return rewriter.notifyMatchFailure(
+ op,
+ "shape_cast where vectors are scalable not handled by this pattern");
+
+ const ArrayRef<int64_t> sourceShape = sourceType.getShape();
+ const ArrayRef<int64_t> resultShape = resultType.getShape();
+ const int64_t sourceRank = sourceType.getRank();
+ const int64_t resultRank = resultType.getRank();
+ const int64_t numElms = sourceType.getNumElements();
+ const Value source = op.getSource();
+
+ // Set the first dimension (starting at the end) in the source and result
+ // respectively where the dimension sizes differ. Using the running example:
+ //
+ // dimensions: [0 1 2 3 4 5 ] [0 1 2 3 ]
+ // shapes: (2,2,3,4,7,11) -> (8,6,7,11)
+ // ^ ^
+ // | |
+ // sourceSuffixStartDim is 3 |
+ // |
+ // resultSuffixStartDim is 1
+ int64_t sourceSuffixStartDim = sourceRank - 1;
+ int64_t resultSuffixStartDim = resultRank - 1;
+ while (sourceSuffixStartDim >= 0 && resultSuffixStartDim >= 0 &&
+ (sourceType.getDimSize(sourceSuffixStartDim) ==
+ resultType.getDimSize(resultSuffixStartDim))) {
+ --sourceSuffixStartDim;
+ --resultSuffixStartDim;
+ }
- if (sourceVectorType.isScalable() || resultVectorType.isScalable())
- return failure();
-
- // Special case for n-D / 1-D lowerings with better implementations.
- int64_t srcRank = sourceVectorType.getRank();
- int64_t resRank = resultVectorType.getRank();
- if ((srcRank > 1 && resRank == 1) || (srcRank == 1 && resRank > 1))
- return failure();
-
- // Generic ShapeCast lowering path goes all the way down to unrolled scalar
- // extract/insert chains.
- int64_t numElts = 1;
- for (int64_t r = 0; r < srcRank; r++)
- numElts *= sourceVectorType.getDimSize(r);
- // Replace with data movement operations:
- // x[0,0,0] = y[0,0]
- // x[0,0,1] = y[0,1]
- // x[0,1,0] = y[0,2]
- // etc., incrementing the two index vectors "row-major"
- // within the source and result shape.
- SmallVector<int64_t> srcIdx(srcRank, 0);
- SmallVector<int64_t> resIdx(resRank, 0);
- Value result = rewriter.create<ub::PoisonOp>(loc, resultVectorType);
- for (int64_t i = 0; i < numElts; i++) {
- if (i != 0) {
- incIdx(srcIdx, sourceVectorType);
- incIdx(resIdx, resultVectorType);
+ // This is the case (i) where there are just some leading ones to contend
+ // with in the source or result. It can be handled with a single
+ // extract/insert pair.
+ if (resultSuffixStartDim < 0 || sourceSuffixStartDim < 0)
+ return leadingOnesLowering(op, rewriter);
+
+ const int64_t sourceSuffixStartDimSize =
+ sourceType.getDimSize(sourceSuffixStartDim);
+ const int64_t resultSuffixStartDimSize =
+ resultType.getDimSize(resultSuffixStartDim);
+ const int64_t greatestCommonDivisor =
+ std::gcd(sourceSuffixStartDimSize, resultSuffixStartDimSize);
+ const int64_t stridedSliceRank = sourceRank - sourceSuffixStartDim;
+ const size_t extractPeriod =
+ sourceSuffixStartDimSize / greatestCommonDivisor;
+ const size_t insertPeriod =
+ resultSuffixStartDimSize / greatestCommonDivisor;
+
+ SmallVector<int64_t> atomicShape(sourceShape.begin() + sourceSuffixStartDim,
+ sourceShape.end());
+ atomicShape[0] = greatestCommonDivisor;
+
+ const int64_t numAtomicElms = std::accumulate(
+ atomicShape.begin(), atomicShape.end(), 1, std::multiplies<int64_t>());
+ const size_t nAtomicSlices = numElms / numAtomicElms;
+
+ // This is the case (ii) where the strided dimension size is 1. More compact
+ // IR is generated in this case if we just extract and insert the elements
+ // directly. In other words, we don't use extract_strided_slice and
+ // insert_strided_slice.
+ if (greatestCommonDivisor == 1)
+ return noStridedSliceLowering(op, sourceSuffixStartDim + 1,
+ resultSuffixStartDim + 1, rewriter);
+
+ // The insert_strided_slice result's type
+ const ArrayRef<int64_t> insertStridedShape =
+ resultShape.drop_front(resultSuffixStartDim);
+ const VectorType insertStridedType =
+ VectorType::get(insertStridedShape, resultType.getElementType());
+
+ SmallVector<int64_t> extractIndex(sourceSuffixStartDim, 0);
+ SmallVector<int64_t> insertIndex(resultSuffixStartDim, 0);
+ SmallVector<int64_t> extractOffsets(stridedSliceRank, 0);
+ SmallVector<int64_t> insertOffsets(stridedSliceRank, 0);
+ const SmallVector<int64_t> sizes(stridedSliceRank, 1);
+
+ Value extracted = {};
+ Value extractedStrided = {};
+ Value insertedSlice = {};
+ Value result = rewriter.create<ub::PoisonOp>(loc, resultType);
+ const Value partResult =
+ rewriter.create<ub::PoisonOp>(loc, insertStridedType);
+
+ for (size_t i = 0; i < nAtomicSlices; ++i) {
+
+ const size_t extractStridedPhase = i % extractPeriod;
+ const size_t insertStridedPhase = i % insertPeriod;
+
+ // vector.extract
+ if (extractStridedPhase == 0) {
+ extracted =
+ rewriter.create<vector::ExtractOp>(loc, source, extractIndex);
+ inplaceAdd(1, sourceShape.take_front(sourceSuffixStartDim),
+ extractIndex);
}
- Value extract =
- rewriter.create<vector::ExtractOp>(loc, op.getSource(), srcIdx);
- result = rewriter.create<vector::InsertOp>(loc, extract, result, resIdx);
+ // vector.extract_strided_slice
+ extractOffsets[0] = extractStridedPhase * greatestCommonDivisor;
+ extractedStrided = rewriter.create<vector::ExtractStridedSliceOp>(
+ loc, extracted, extractOffsets, atomicShape, sizes);
+
+ // vector.insert_strided_slice
+ if (insertStridedPhase == 0) {
+ insertedSlice = partResult;
+ }
+ insertOffsets[0] = insertStridedPhase * greatestCommonDivisor;
+ insertedSlice = rewriter.create<vector::InsertStridedSliceOp>(
+ loc, extractedStrided, insertedSlice, insertOffsets, sizes);
+
+ // vector.insert
+ if (insertStridedPhase + 1 == insertPeriod) {
+ result = rewriter.create<vector::InsertOp>(loc, insertedSlice, result,
+ insertIndex);
+ inplaceAdd(1, resultType.getShape().take_front(resultSuffixStartDim),
+ insertIndex);
+ }
}
rewriter.replaceOp(op, result);
return success();
@@ -252,7 +373,8 @@ public:
// from >= 2-D scalable vectors or scalable vectors of fixed vectors.
if (!isTrailingDimScalable(sourceVectorType) ||
!isTrailingDimScalable(resultVectorType)) {
- return failure();
+ return rewriter.notifyMatchFailure(
+ op, "trailing dims are not scalable, not handled by this pattern");
}
// The sizes of the trailing dimension of the source and result vectors, the
@@ -329,8 +451,8 @@ public:
// 4. Increment the insert/extract indices, stepping by minExtractionSize
// for the trailing dimensions.
- incIdx(srcIdx, sourceVectorType, /*step=*/minExtractionSize);
- incIdx(resIdx, resultVectorType, /*step=*/minExtractionSize);
+ inplaceAdd(minExtractionSize, sourceVectorType.getShape(), srcIdx);
+ inplaceAdd(minExtractionSize, resultVectorType.getShape(), resIdx);
}
rewriter.replaceOp(op, result);
@@ -347,8 +469,6 @@ public:
void mlir::vector::populateVectorShapeCastLoweringPatterns(
RewritePatternSet &patterns, PatternBenefit benefit) {
- patterns.add<ShapeCastOpNDDownCastRewritePattern,
- ShapeCastOpNDUpCastRewritePattern, ShapeCastOpRewritePattern,
- ScalableShapeCastOpRewritePattern>(patterns.getContext(),
- benefit);
+ patterns.add<ShapeCastOpRewritePattern, ScalableShapeCastOpRewritePattern>(
+ patterns.getContext(), benefit);
}
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
index 1cc477d9dca9..fc443ab0d138 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
@@ -631,14 +631,78 @@ private:
vector::UnrollVectorOptions options;
};
+struct UnrollBroadcastPattern : public OpRewritePattern<vector::BroadcastOp> {
+ UnrollBroadcastPattern(MLIRContext *context,
+ const vector::UnrollVectorOptions &options,
+ PatternBenefit benefit = 1)
+ : OpRewritePattern<vector::BroadcastOp>(context, benefit),
+ options(options) {}
+
+ LogicalResult matchAndRewrite(vector::BroadcastOp broadcastOp,
+ PatternRewriter &rewriter) const override {
+ auto targetShape = getTargetShape(options, broadcastOp);
+ if (!targetShape)
+ return failure();
+
+ Location loc = broadcastOp.getLoc();
+ VectorType srcType = dyn_cast<VectorType>(broadcastOp.getSourceType());
+ VectorType resType = broadcastOp.getResultVectorType();
+ VectorType targetType =
+ resType.cloneWith(*targetShape, resType.getElementType());
+ Value result = rewriter.create<arith::ConstantOp>(
+ loc, resType, rewriter.getZeroAttr(resType));
+
+ SmallVector<int64_t> originalShape = *broadcastOp.getShapeForUnroll();
+ SmallVector<int64_t> strides(originalShape.size(), 1);
+
+ for (SmallVector<int64_t> offsets :
+ StaticTileOffsetRange(originalShape, *targetShape)) {
+ Value newSrc;
+ if (!srcType) {
+ // Scalar to vector broadcast.
+ newSrc = broadcastOp.getSource();
+ } else {
+ // Vector to vector broadcast.
+ int64_t rank = srcType.getRank();
+ SmallVector<int64_t> srcOffsets(offsets.end() - rank, offsets.end());
+ SmallVector<int64_t> srcShape(targetShape->end() - rank,
+ targetShape->end());
+ SmallVector<int64_t> srcStrides(strides.end() - rank, strides.end());
+ // adjust the offset and shape for src if the corresponding dim is 1.
+ for (int64_t i = 0; i < rank; ++i) {
+ if (srcType.getDimSize(i) == 1) {
+ srcOffsets[i] = 0;
+ srcShape[i] = 1;
+ }
+ }
+ newSrc = rewriter.createOrFold<vector::ExtractStridedSliceOp>(
+ loc, broadcastOp.getSource(), srcOffsets, srcShape, srcStrides);
+ }
+
+ Operation *newOp = cloneOpWithOperandsAndTypes(rewriter, loc, broadcastOp,
+ newSrc, targetType);
+
+ result = rewriter.createOrFold<vector::InsertStridedSliceOp>(
+ loc, newOp->getResult(0), result, offsets, strides);
+ }
+
+ rewriter.replaceOp(broadcastOp, result);
+ return success();
+ }
+
+private:
+ vector::UnrollVectorOptions options;
+};
+
} // namespace
void mlir::vector::populateVectorUnrollPatterns(
RewritePatternSet &patterns, const UnrollVectorOptions &options,
PatternBenefit benefit) {
- patterns.add<UnrollTransferReadPattern, UnrollTransferWritePattern,
- UnrollContractionPattern, UnrollElementwisePattern,
- UnrollReductionPattern, UnrollMultiReductionPattern,
- UnrollTransposePattern, UnrollGatherPattern>(
- patterns.getContext(), options, benefit);
+ patterns
+ .add<UnrollTransferReadPattern, UnrollTransferWritePattern,
+ UnrollContractionPattern, UnrollElementwisePattern,
+ UnrollReductionPattern, UnrollMultiReductionPattern,
+ UnrollTransposePattern, UnrollGatherPattern, UnrollBroadcastPattern>(
+ patterns.getContext(), options, benefit);
}
diff --git a/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp b/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp
index d5dd6f2027be..590d244daef4 100644
--- a/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp
+++ b/mlir/lib/Dialect/Vector/Utils/VectorUtils.cpp
@@ -337,15 +337,16 @@ Value vector::createReadOrMaskedRead(OpBuilder &builder, Location loc,
auto sourceShape = sourceShapedType.getShape();
assert(sourceShape.size() == inputVectorSizes.size() &&
"expected same ranks.");
- auto maskType = VectorType::get(inputVectorSizes, builder.getI1Type());
auto vectorType = VectorType::get(inputVectorSizes, padValue.getType());
assert(padValue.getType() == sourceShapedType.getElementType() &&
"expected same pad element type to match source element type");
int64_t readRank = inputVectorSizes.size();
auto zero = builder.create<arith::ConstantIndexOp>(loc, 0);
SmallVector<bool> inBoundsVal(readRank, true);
+
if (useInBoundsInsteadOfMasking) {
// Update the inBounds attribute.
+ // FIXME: This computation is too weak - it ignores the read indices.
for (unsigned i = 0; i < readRank; i++)
inBoundsVal[i] = (sourceShape[i] == inputVectorSizes[i]) &&
!ShapedType::isDynamic(sourceShape[i]);
@@ -362,6 +363,8 @@ Value vector::createReadOrMaskedRead(OpBuilder &builder, Location loc,
return transferReadOp;
SmallVector<OpFoldResult> mixedSourceDims =
tensor::getMixedSizes(builder, loc, source);
+
+ auto maskType = VectorType::get(inputVectorSizes, builder.getI1Type());
Value mask =
builder.create<vector::CreateMaskOp>(loc, maskType, mixedSourceDims);
return mlir::vector::maskOperation(builder, transferReadOp, mask)
diff --git a/mlir/lib/ExecutionEngine/ExecutionEngine.cpp b/mlir/lib/ExecutionEngine/ExecutionEngine.cpp
index 39f5c003320d..f704fbfbe8ff 100644
--- a/mlir/lib/ExecutionEngine/ExecutionEngine.cpp
+++ b/mlir/lib/ExecutionEngine/ExecutionEngine.cpp
@@ -315,7 +315,8 @@ ExecutionEngine::create(Operation *m, const ExecutionEngineOptions &options,
// process and dynamically linked libraries.
auto objectLinkingLayerCreator = [&](ExecutionSession &session) {
auto objectLayer = std::make_unique<RTDyldObjectLinkingLayer>(
- session, [sectionMemoryMapper = options.sectionMemoryMapper]() {
+ session, [sectionMemoryMapper =
+ options.sectionMemoryMapper](const MemoryBuffer &) {
return std::make_unique<SectionMemoryManager>(sectionMemoryMapper);
});
diff --git a/mlir/lib/IR/OperationSupport.cpp b/mlir/lib/IR/OperationSupport.cpp
index 7c9e6c89d4d8..d3e9aebc4dc2 100644
--- a/mlir/lib/IR/OperationSupport.cpp
+++ b/mlir/lib/IR/OperationSupport.cpp
@@ -680,9 +680,13 @@ llvm::hash_code OperationEquivalence::computeHash(
// - Operation Name
// - Attributes
// - Result Types
+ DictionaryAttr dictAttrs;
+ if (!(flags & Flags::IgnoreDiscardableAttrs))
+ dictAttrs = op->getRawDictionaryAttrs();
llvm::hash_code hash =
- llvm::hash_combine(op->getName(), op->getRawDictionaryAttrs(),
- op->getResultTypes(), op->hashProperties());
+ llvm::hash_combine(op->getName(), dictAttrs, op->getResultTypes());
+ if (!(flags & Flags::IgnoreProperties))
+ hash = llvm::hash_combine(hash, op->hashProperties());
// - Location if required
if (!(flags & Flags::IgnoreLocations))
@@ -836,14 +840,19 @@ OperationEquivalence::isRegionEquivalentTo(Region *lhs, Region *rhs,
return true;
// 1. Compare the operation properties.
+ if (!(flags & IgnoreDiscardableAttrs) &&
+ lhs->getRawDictionaryAttrs() != rhs->getRawDictionaryAttrs())
+ return false;
+
if (lhs->getName() != rhs->getName() ||
- lhs->getRawDictionaryAttrs() != rhs->getRawDictionaryAttrs() ||
lhs->getNumRegions() != rhs->getNumRegions() ||
lhs->getNumSuccessors() != rhs->getNumSuccessors() ||
lhs->getNumOperands() != rhs->getNumOperands() ||
- lhs->getNumResults() != rhs->getNumResults() ||
- !lhs->getName().compareOpProperties(lhs->getPropertiesStorage(),
- rhs->getPropertiesStorage()))
+ lhs->getNumResults() != rhs->getNumResults())
+ return false;
+ if (!(flags & IgnoreProperties) &&
+ !(lhs->getName().compareOpProperties(lhs->getPropertiesStorage(),
+ rhs->getPropertiesStorage())))
return false;
if (!(flags & IgnoreLocations) && lhs->getLoc() != rhs->getLoc())
return false;
diff --git a/mlir/lib/Interfaces/ViewLikeInterface.cpp b/mlir/lib/Interfaces/ViewLikeInterface.cpp
index 70dd7b4aec88..d70cfebbe53e 100644
--- a/mlir/lib/Interfaces/ViewLikeInterface.cpp
+++ b/mlir/lib/Interfaces/ViewLikeInterface.cpp
@@ -27,9 +27,7 @@ LogicalResult mlir::verifyListOfOperandsOrIntegers(Operation *op,
return op->emitError("expected ") << numElements << " " << name
<< " values, got " << staticVals.size();
unsigned expectedNumDynamicEntries =
- llvm::count_if(staticVals, [](int64_t staticVal) {
- return ShapedType::isDynamic(staticVal);
- });
+ llvm::count_if(staticVals, ShapedType::isDynamic);
if (values.size() != expectedNumDynamicEntries)
return op->emitError("expected ")
<< expectedNumDynamicEntries << " dynamic " << name << " values";
@@ -270,5 +268,5 @@ bool mlir::detail::sameOffsetsSizesAndStrides(
unsigned mlir::detail::getNumDynamicEntriesUpToIdx(ArrayRef<int64_t> staticVals,
unsigned idx) {
return std::count_if(staticVals.begin(), staticVals.begin() + idx,
- [&](int64_t val) { return ShapedType::isDynamic(val); });
+ ShapedType::isDynamic);
}
diff --git a/mlir/lib/Pass/Pass.cpp b/mlir/lib/Pass/Pass.cpp
index e0e9b5f54042..d1371d2b508f 100644
--- a/mlir/lib/Pass/Pass.cpp
+++ b/mlir/lib/Pass/Pass.cpp
@@ -18,6 +18,7 @@
#include "mlir/IR/Threading.h"
#include "mlir/IR/Verifier.h"
#include "mlir/Support/FileUtilities.h"
+#include "mlir/Support/IndentedOstream.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
@@ -80,14 +81,19 @@ void Pass::copyOptionValuesFrom(const Pass *other) {
}
/// Prints out the pass in the textual representation of pipelines. If this is
-/// an adaptor pass, print its pass managers.
-void Pass::printAsTextualPipeline(raw_ostream &os) {
+/// an adaptor pass, print its pass managers. When `pretty` is true, the
+/// printed pipeline is formatted for readability.
+void Pass::printAsTextualPipeline(raw_ostream &os, bool pretty) {
// Special case for adaptors to print its pass managers.
if (auto *adaptor = dyn_cast<OpToOpPassAdaptor>(this)) {
llvm::interleave(
adaptor->getPassManagers(),
- [&](OpPassManager &pm) { pm.printAsTextualPipeline(os); },
- [&] { os << ","; });
+ [&](OpPassManager &pm) { pm.printAsTextualPipeline(os, pretty); },
+ [&] {
+ os << ",";
+ if (pretty)
+ os << "\n";
+ });
return;
}
// Otherwise, print the pass argument followed by its options. If the pass
@@ -390,27 +396,51 @@ StringRef OpPassManager::getOpAnchorName() const {
}
/// Prints out the passes of the pass manager as the textual representation
-/// of pipelines.
+/// of pipelines. When `pretty` is true, the printed pipeline is formatted for
+/// readability.
void printAsTextualPipeline(
- raw_ostream &os, StringRef anchorName,
- const llvm::iterator_range<OpPassManager::pass_iterator> &passes) {
+ raw_indented_ostream &os, StringRef anchorName,
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes,
+ bool pretty = false) {
os << anchorName << "(";
+ if (pretty) {
+ os << "\n";
+ os.indent();
+ }
llvm::interleave(
- passes, [&](mlir::Pass &pass) { pass.printAsTextualPipeline(os); },
- [&]() { os << ","; });
+ passes,
+ [&](mlir::Pass &pass) { pass.printAsTextualPipeline(os, pretty); },
+ [&]() {
+ os << ",";
+ if (pretty)
+ os << "\n";
+ });
+ if (pretty) {
+ os << "\n";
+ os.unindent();
+ }
os << ")";
}
-void OpPassManager::printAsTextualPipeline(raw_ostream &os) const {
+void printAsTextualPipeline(
+ raw_ostream &os, StringRef anchorName,
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes,
+ bool pretty) {
+ raw_indented_ostream indentedOS(os);
+ printAsTextualPipeline(indentedOS, anchorName, passes, pretty);
+}
+void OpPassManager::printAsTextualPipeline(raw_ostream &os, bool pretty) const {
StringRef anchorName = getOpAnchorName();
+ raw_indented_ostream indentedOS(os);
::printAsTextualPipeline(
- os, anchorName,
+ indentedOS, anchorName,
{MutableArrayRef<std::unique_ptr<Pass>>{impl->passes}.begin(),
- MutableArrayRef<std::unique_ptr<Pass>>{impl->passes}.end()});
+ MutableArrayRef<std::unique_ptr<Pass>>{impl->passes}.end()},
+ pretty);
}
void OpPassManager::dump() {
llvm::errs() << "Pass Manager with " << impl->passes.size() << " passes:\n";
- printAsTextualPipeline(llvm::errs());
+ printAsTextualPipeline(llvm::errs(), /*pretty=*/true);
llvm::errs() << "\n";
}
@@ -466,7 +496,6 @@ llvm::hash_code OpPassManager::hash() {
return hashCode;
}
-
//===----------------------------------------------------------------------===//
// OpToOpPassAdaptor
//===----------------------------------------------------------------------===//
@@ -871,7 +900,8 @@ LogicalResult PassManager::run(Operation *op) {
// Initialize all of the passes within the pass manager with a new generation.
llvm::hash_code newInitKey = context->getRegistryHash();
llvm::hash_code pipelineKey = hash();
- if (newInitKey != initializationKey || pipelineKey != pipelineInitializationKey) {
+ if (newInitKey != initializationKey ||
+ pipelineKey != pipelineInitializationKey) {
if (failed(initialize(context, impl->initializationGeneration + 1)))
return failure();
initializationKey = newInitKey;
diff --git a/mlir/lib/Pass/PassCrashRecovery.cpp b/mlir/lib/Pass/PassCrashRecovery.cpp
index 8c6d865cb31d..b048ff946239 100644
--- a/mlir/lib/Pass/PassCrashRecovery.cpp
+++ b/mlir/lib/Pass/PassCrashRecovery.cpp
@@ -443,7 +443,8 @@ makeReproducerStreamFactory(StringRef outputFile) {
void printAsTextualPipeline(
raw_ostream &os, StringRef anchorName,
- const llvm::iterator_range<OpPassManager::pass_iterator> &passes);
+ const llvm::iterator_range<OpPassManager::pass_iterator> &passes,
+ bool pretty = false);
std::string mlir::makeReproducer(
StringRef anchorName,
diff --git a/mlir/lib/Reducer/OptReductionPass.cpp b/mlir/lib/Reducer/OptReductionPass.cpp
index fbbe75d30388..7eb29a02702a 100644
--- a/mlir/lib/Reducer/OptReductionPass.cpp
+++ b/mlir/lib/Reducer/OptReductionPass.cpp
@@ -88,4 +88,4 @@ void OptReductionPass::runOnOperation() {
moduleVariant->destroy();
LLVM_DEBUG(llvm::dbgs() << "Pass Complete\n\n");
-} \ No newline at end of file
+}
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 4a6605e9c74c..ec1a9c909c94 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5737,7 +5737,13 @@ convertHostOrTargetOperation(Operation *op, llvm::IRBuilderBase &builder,
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
ompBuilder->createBarrier(builder.saveIP(),
llvm::omp::OMPD_barrier);
- return handleError(afterIP, *op);
+ LogicalResult res = handleError(afterIP, *op);
+ if (res.succeeded()) {
+ // If the barrier generated a cancellation check, the insertion
+ // point might now need to be changed to a new continuation block
+ builder.restoreIP(*afterIP);
+ }
+ return res;
})
.Case([&](omp::TaskyieldOp op) {
if (failed(checkImplementationStatus(*op)))
diff --git a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
index a21d691ae514..3957dbc0db98 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
@@ -1062,12 +1062,30 @@ LogicalResult spirv::Deserializer::processCooperativeMatrixTypeKHR(
<< operands[2];
}
- unsigned rows = getConstantInt(operands[3]).getInt();
- unsigned columns = getConstantInt(operands[4]).getInt();
+ IntegerAttr rowsAttr = getConstantInt(operands[3]);
+ IntegerAttr columnsAttr = getConstantInt(operands[4]);
+ IntegerAttr useAttr = getConstantInt(operands[5]);
+
+ if (!rowsAttr)
+ return emitError(unknownLoc, "OpTypeCooperativeMatrixKHR `Rows` references "
+ "undefined constant <id> ")
+ << operands[3];
+
+ if (!columnsAttr)
+ return emitError(unknownLoc, "OpTypeCooperativeMatrixKHR `Columns` "
+ "references undefined constant <id> ")
+ << operands[4];
+
+ if (!useAttr)
+ return emitError(unknownLoc, "OpTypeCooperativeMatrixKHR `Use` references "
+ "undefined constant <id> ")
+ << operands[5];
+
+ unsigned rows = rowsAttr.getInt();
+ unsigned columns = columnsAttr.getInt();
std::optional<spirv::CooperativeMatrixUseKHR> use =
- spirv::symbolizeCooperativeMatrixUseKHR(
- getConstantInt(operands[5]).getInt());
+ spirv::symbolizeCooperativeMatrixUseKHR(useAttr.getInt());
if (!use) {
return emitError(
unknownLoc,