1307cfdf5SNicolas Vasilache //===- Vectorization.cpp - Implementation of linalg Vectorization ---------===// 2307cfdf5SNicolas Vasilache // 3307cfdf5SNicolas Vasilache // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4307cfdf5SNicolas Vasilache // See https://llvm.org/LICENSE.txt for license information. 5307cfdf5SNicolas Vasilache // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6307cfdf5SNicolas Vasilache // 7307cfdf5SNicolas Vasilache //===----------------------------------------------------------------------===// 8307cfdf5SNicolas Vasilache // 9307cfdf5SNicolas Vasilache // This file implements the linalg dialect Vectorization transformations. 10307cfdf5SNicolas Vasilache // 11307cfdf5SNicolas Vasilache //===----------------------------------------------------------------------===// 12307cfdf5SNicolas Vasilache 13307cfdf5SNicolas Vasilache #include "mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h" 14307cfdf5SNicolas Vasilache #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 15307cfdf5SNicolas Vasilache #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 16307cfdf5SNicolas Vasilache #include "mlir/Dialect/Linalg/Utils/Utils.h" 17307cfdf5SNicolas Vasilache #include "mlir/Dialect/StandardOps/EDSC/Intrinsics.h" 18307cfdf5SNicolas Vasilache #include "mlir/Dialect/Utils/StructuredOpsUtils.h" 19307cfdf5SNicolas Vasilache #include "mlir/Dialect/Vector/EDSC/Intrinsics.h" 20307cfdf5SNicolas Vasilache #include "mlir/Dialect/Vector/VectorOps.h" 21307cfdf5SNicolas Vasilache #include "mlir/IR/AffineExpr.h" 22307cfdf5SNicolas Vasilache #include "mlir/IR/Matchers.h" 23307cfdf5SNicolas Vasilache #include "mlir/IR/PatternMatch.h" 24307cfdf5SNicolas Vasilache #include "mlir/Pass/Pass.h" 25307cfdf5SNicolas Vasilache #include "mlir/Support/LLVM.h" 260a2a260aSNicolas Vasilache #include "mlir/Transforms/RegionUtils.h" 270a2a260aSNicolas Vasilache #include "llvm/ADT/ScopeExit.h" 28307cfdf5SNicolas Vasilache #include "llvm/Support/Debug.h" 29307cfdf5SNicolas Vasilache #include "llvm/Support/raw_ostream.h" 30307cfdf5SNicolas Vasilache #include <type_traits> 31307cfdf5SNicolas Vasilache 32307cfdf5SNicolas Vasilache using namespace mlir; 33307cfdf5SNicolas Vasilache using namespace mlir::edsc; 34307cfdf5SNicolas Vasilache using namespace mlir::edsc::intrinsics; 35307cfdf5SNicolas Vasilache using namespace mlir::linalg; 36307cfdf5SNicolas Vasilache 37307cfdf5SNicolas Vasilache using llvm::dbgs; 38307cfdf5SNicolas Vasilache 39307cfdf5SNicolas Vasilache #define DEBUG_TYPE "linalg-vectorization" 40307cfdf5SNicolas Vasilache 41f245b7adSNicolas Vasilache /// Return the unique instance of OpType in `block` if it is indeed unique. 42f245b7adSNicolas Vasilache /// Return null if none or more than 1 instances exist. 43f245b7adSNicolas Vasilache template <typename OpType> 44f245b7adSNicolas Vasilache static OpType getSingleOpOfType(Block &block) { 45f245b7adSNicolas Vasilache OpType res; 46f245b7adSNicolas Vasilache block.walk([&](OpType op) { 47f245b7adSNicolas Vasilache if (res) { 48f245b7adSNicolas Vasilache res = nullptr; 49f245b7adSNicolas Vasilache return WalkResult::interrupt(); 50f245b7adSNicolas Vasilache } 51f245b7adSNicolas Vasilache res = op; 52f245b7adSNicolas Vasilache return WalkResult::advance(); 53f245b7adSNicolas Vasilache }); 54f245b7adSNicolas Vasilache return res; 55f245b7adSNicolas Vasilache } 56f245b7adSNicolas Vasilache 570a2a260aSNicolas Vasilache /// Helper data structure to represent the result of vectorization. 580a2a260aSNicolas Vasilache /// In certain specific cases, like terminators, we do not want to propagate/ 590a2a260aSNicolas Vasilache enum VectorizationStatus { 600a2a260aSNicolas Vasilache /// Op failed to vectorize. 610a2a260aSNicolas Vasilache Failure = 0, 620a2a260aSNicolas Vasilache /// Op vectorized and custom function took care of replacement logic 630a2a260aSNicolas Vasilache NoReplace, 640a2a260aSNicolas Vasilache /// Op vectorized into a new Op whose results will replace original Op's 650a2a260aSNicolas Vasilache /// results. 660a2a260aSNicolas Vasilache NewOp 670a2a260aSNicolas Vasilache // TODO: support values if Op vectorized to Many-Ops whose results we need to 680a2a260aSNicolas Vasilache // aggregate for replacement. 690a2a260aSNicolas Vasilache }; 700a2a260aSNicolas Vasilache struct VectorizationResult { 710a2a260aSNicolas Vasilache /// Return status from vectorizing the current op. 720a2a260aSNicolas Vasilache enum VectorizationStatus status = VectorizationStatus::Failure; 730a2a260aSNicolas Vasilache /// New vectorized operation to replace the current op. 740a2a260aSNicolas Vasilache /// Replacement behavior is specified by `status`. 750a2a260aSNicolas Vasilache Operation *newOp; 760a2a260aSNicolas Vasilache }; 770a2a260aSNicolas Vasilache 780a2a260aSNicolas Vasilache /// Return a vector type of the same shape and element type as the (assumed) 790a2a260aSNicolas Vasilache /// ShapedType of `v`. 800a2a260aSNicolas Vasilache static VectorType extractVectorTypeFromShapedValue(Value v) { 810a2a260aSNicolas Vasilache auto st = v.getType().cast<ShapedType>(); 820a2a260aSNicolas Vasilache if (st.isa<MemRefType>() && st.getShape().empty()) 830a2a260aSNicolas Vasilache return VectorType(); 840a2a260aSNicolas Vasilache return VectorType::get(st.getShape(), st.getElementType()); 850a2a260aSNicolas Vasilache } 860a2a260aSNicolas Vasilache 870a2a260aSNicolas Vasilache /// Build a vector.transfer_read from `source` at indices set to all `0`. 880a2a260aSNicolas Vasilache /// If source has rank zero, build an std.load. 890a2a260aSNicolas Vasilache /// Return the produced value. 900a2a260aSNicolas Vasilache static Value buildVectorRead(OpBuilder &builder, Value source) { 910a2a260aSNicolas Vasilache edsc::ScopedContext scope(builder); 920a2a260aSNicolas Vasilache auto shapedType = source.getType().cast<ShapedType>(); 930a2a260aSNicolas Vasilache if (VectorType vectorType = extractVectorTypeFromShapedValue(source)) { 940a2a260aSNicolas Vasilache SmallVector<Value> indices(shapedType.getRank(), std_constant_index(0)); 950a2a260aSNicolas Vasilache return vector_transfer_read(vectorType, source, indices); 960a2a260aSNicolas Vasilache } 970a2a260aSNicolas Vasilache return std_load(source); 980a2a260aSNicolas Vasilache } 990a2a260aSNicolas Vasilache 1000a2a260aSNicolas Vasilache /// Build a vector.transfer_write of `value` into `dest` at indices set to all 1010a2a260aSNicolas Vasilache /// `0`. If `dest` has null rank, build an std.store. 1020a2a260aSNicolas Vasilache /// Return the produced value or null if no value is produced. 1030a2a260aSNicolas Vasilache static Value buildVectorWrite(OpBuilder &builder, Value value, Value dest) { 1040a2a260aSNicolas Vasilache edsc::ScopedContext scope(builder); 1050a2a260aSNicolas Vasilache Operation *write; 1060a2a260aSNicolas Vasilache auto shapedType = dest.getType().cast<ShapedType>(); 1070a2a260aSNicolas Vasilache if (VectorType vectorType = extractVectorTypeFromShapedValue(dest)) { 1080a2a260aSNicolas Vasilache SmallVector<Value> indices(shapedType.getRank(), std_constant_index(0)); 1090a2a260aSNicolas Vasilache if (vectorType != value.getType()) 1100a2a260aSNicolas Vasilache value = vector_broadcast(vectorType, value); 1110a2a260aSNicolas Vasilache write = vector_transfer_write(value, dest, indices); 1120a2a260aSNicolas Vasilache } else { 1130a2a260aSNicolas Vasilache write = std_store(value, dest); 1140a2a260aSNicolas Vasilache } 1150a2a260aSNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: vectorized op: " << *write); 1160a2a260aSNicolas Vasilache if (!write->getResults().empty()) 1170a2a260aSNicolas Vasilache return write->getResult(0); 1180a2a260aSNicolas Vasilache return Value(); 1190a2a260aSNicolas Vasilache } 1200a2a260aSNicolas Vasilache 1210a2a260aSNicolas Vasilache /// If value of assumed VectorType has a shape different than `shape`, buil and 1220a2a260aSNicolas Vasilache /// return a new vector.broadcast to `shape`. 1230a2a260aSNicolas Vasilache /// Otherwise, just return value. 1240a2a260aSNicolas Vasilache static Value broadcastIfNeeded(OpBuilder &builder, Value value, 1250a2a260aSNicolas Vasilache ArrayRef<int64_t> shape) { 1260a2a260aSNicolas Vasilache auto vecType = value.getType().dyn_cast<VectorType>(); 1270a2a260aSNicolas Vasilache if (shape.empty() || (vecType != nullptr && vecType.getShape() == shape)) 1280a2a260aSNicolas Vasilache return value; 1290a2a260aSNicolas Vasilache auto newVecType = VectorType::get(shape, vecType ? vecType.getElementType() 1300a2a260aSNicolas Vasilache : value.getType()); 1310a2a260aSNicolas Vasilache return builder.create<vector::BroadcastOp>( 1320a2a260aSNicolas Vasilache builder.getInsertionPoint()->getLoc(), newVecType, value); 1330a2a260aSNicolas Vasilache } 1340a2a260aSNicolas Vasilache 1350a2a260aSNicolas Vasilache // Custom vectorization function type. Produce a vector form of Operation* 1360a2a260aSNicolas Vasilache // assuming all its vectorized operands are already in the BlockAndValueMapping. 1370a2a260aSNicolas Vasilache // Return nullptr if the Operation cannot be vectorized. 1380a2a260aSNicolas Vasilache using CustomVectorizationHook = std::function<VectorizationResult( 1390a2a260aSNicolas Vasilache Operation *, const BlockAndValueMapping &)>; 1400a2a260aSNicolas Vasilache 1410a2a260aSNicolas Vasilache /// Helper function to vectorize the terminator of a `linalgOp`. New result 1420a2a260aSNicolas Vasilache /// vector values are appended to `results`. 1430a2a260aSNicolas Vasilache /// Return VectorizationStatus::NoReplace to signal the vectorization algorithm 1440a2a260aSNicolas Vasilache /// that it should not try to map produced operations: this is the purpose of 1450a2a260aSNicolas Vasilache /// the `results` argument to capture such values and make them available for 1460a2a260aSNicolas Vasilache /// RAUW to the vectorization algorithm. 1470a2a260aSNicolas Vasilache /// This function is meant to be used as a CustomVectorizationHook. 1480a2a260aSNicolas Vasilache static VectorizationResult 1490a2a260aSNicolas Vasilache vectorizeLinalgYield(OpBuilder &builder, Operation *op, 1500a2a260aSNicolas Vasilache const BlockAndValueMapping &bvm, LinalgOp linalgOp, 1510a2a260aSNicolas Vasilache SmallVectorImpl<Value> &results) { 1520a2a260aSNicolas Vasilache auto yieldOp = dyn_cast<linalg::YieldOp>(op); 1530a2a260aSNicolas Vasilache if (!yieldOp) 1540a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::Failure, nullptr}; 1550a2a260aSNicolas Vasilache for (auto outputs : llvm::enumerate(yieldOp.values())) { 1560a2a260aSNicolas Vasilache // TODO: Scan for an opportunity for reuse. 1570a2a260aSNicolas Vasilache // TODO: use a map. 1580a2a260aSNicolas Vasilache Value vectorValue = bvm.lookup(outputs.value()); 1590a2a260aSNicolas Vasilache Value result = buildVectorWrite(builder, vectorValue, 1600a2a260aSNicolas Vasilache linalgOp.getOutput(outputs.index())); 1610a2a260aSNicolas Vasilache if (result) 1620a2a260aSNicolas Vasilache results.push_back(result); 1630a2a260aSNicolas Vasilache } 1640a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::NoReplace, nullptr}; 165f245b7adSNicolas Vasilache } 1660a2a260aSNicolas Vasilache 1670a2a260aSNicolas Vasilache /// Generic vectorization for a single operation `op`, given already vectorized 1680a2a260aSNicolas Vasilache /// operands carried by `bvm`. Vectorization occurs as follows: 1690a2a260aSNicolas Vasilache /// 1. Try to apply any of the `customVectorizationHooks` and return its 1700a2a260aSNicolas Vasilache /// result on success. 1710a2a260aSNicolas Vasilache /// 2. Clone any constant in the current scope without vectorization: each 1720a2a260aSNicolas Vasilache /// consumer of the constant will later determine the shape to which the 1730a2a260aSNicolas Vasilache /// constant needs to be broadcast to. 1740a2a260aSNicolas Vasilache /// 3. Fail on any remaining non `ElementwiseMappable` op. It is the purpose 1750a2a260aSNicolas Vasilache /// of the `customVectorizationHooks` to cover such cases. 1760a2a260aSNicolas Vasilache /// 4. Clone `op` in vector form to a vector of shape prescribed by the first 1770a2a260aSNicolas Vasilache /// operand of maximal rank. Other operands have smaller rank and are 1780a2a260aSNicolas Vasilache /// broadcast accordingly. It is assumed this broadcast is always legal, 1790a2a260aSNicolas Vasilache /// otherwise, it means one of the `customVectorizationHooks` is incorrect. 1800a2a260aSNicolas Vasilache /// 1810a2a260aSNicolas Vasilache /// This function assumes all operands of `op` have been vectorized and are in 1820a2a260aSNicolas Vasilache /// the `bvm` mapping. As a consequence, this function is meant to be called on 1830a2a260aSNicolas Vasilache /// a topologically-sorted list of ops. 1840a2a260aSNicolas Vasilache /// This function does not update `bvm` but returns a VectorizationStatus that 1850a2a260aSNicolas Vasilache /// instructs the caller what `bvm` update needs to occur. 1860a2a260aSNicolas Vasilache static VectorizationResult 1870a2a260aSNicolas Vasilache vectorizeOneOp(OpBuilder &builder, Operation *op, 1880a2a260aSNicolas Vasilache const BlockAndValueMapping &bvm, 1890a2a260aSNicolas Vasilache ArrayRef<CustomVectorizationHook> customVectorizationHooks) { 1900a2a260aSNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: vectorize op " << *op); 1910a2a260aSNicolas Vasilache 1920a2a260aSNicolas Vasilache // 1. Try to apply any CustomVectorizationHook. 1930a2a260aSNicolas Vasilache if (!customVectorizationHooks.empty()) { 1940a2a260aSNicolas Vasilache for (auto &customFunc : customVectorizationHooks) { 1950a2a260aSNicolas Vasilache VectorizationResult result = customFunc(op, bvm); 1960a2a260aSNicolas Vasilache if (result.status == VectorizationStatus::Failure) 1970a2a260aSNicolas Vasilache continue; 1980a2a260aSNicolas Vasilache return result; 1990a2a260aSNicolas Vasilache } 2000a2a260aSNicolas Vasilache } 2010a2a260aSNicolas Vasilache 2020a2a260aSNicolas Vasilache // 2. Constant ops don't get vectorized but rather broadcasted at their users. 2030a2a260aSNicolas Vasilache // Clone so that the constant is not confined to the linalgOp block . 2040a2a260aSNicolas Vasilache if (isa<ConstantOp>(op)) 2050a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::NewOp, builder.clone(*op)}; 2060a2a260aSNicolas Vasilache 2070a2a260aSNicolas Vasilache // 3. Only ElementwiseMappable are allowed in the generic vectorization. 2080a2a260aSNicolas Vasilache if (!op->hasTrait<OpTrait::ElementwiseMappable>()) 2090a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::Failure, nullptr}; 2100a2a260aSNicolas Vasilache 2110a2a260aSNicolas Vasilache // 4. Generic vectorization path for ElementwiseMappable ops. 2120a2a260aSNicolas Vasilache // a. first get the first max ranked shape. 2130a2a260aSNicolas Vasilache SmallVector<int64_t, 4> firstMaxRankedShape; 2140a2a260aSNicolas Vasilache for (Value operand : op->getOperands()) { 2150a2a260aSNicolas Vasilache auto vt = bvm.lookup(operand).getType().dyn_cast<VectorType>(); 2160a2a260aSNicolas Vasilache if (vt && firstMaxRankedShape.size() < vt.getShape().size()) 2170a2a260aSNicolas Vasilache firstMaxRankedShape.assign(vt.getShape().begin(), vt.getShape().end()); 2180a2a260aSNicolas Vasilache } 2190a2a260aSNicolas Vasilache // b. broadcast each op if needed. 2200a2a260aSNicolas Vasilache auto vectorizedOperands = llvm::map_range(op->getOperands(), [&](Value v) { 2210a2a260aSNicolas Vasilache return firstMaxRankedShape.empty() 2220a2a260aSNicolas Vasilache ? bvm.lookup(v) 2230a2a260aSNicolas Vasilache : broadcastIfNeeded(builder, bvm.lookup(v), firstMaxRankedShape); 2240a2a260aSNicolas Vasilache }); 2250a2a260aSNicolas Vasilache // c. for elementwise, the result is the vector with the firstMaxRankedShape 2260a2a260aSNicolas Vasilache auto returnTypes = llvm::map_range(op->getResultTypes(), [&](Type t) { 2270a2a260aSNicolas Vasilache return firstMaxRankedShape.empty() 2280a2a260aSNicolas Vasilache ? t 2290a2a260aSNicolas Vasilache : VectorType::get(firstMaxRankedShape, t); 2300a2a260aSNicolas Vasilache }); 2310a2a260aSNicolas Vasilache 2320a2a260aSNicolas Vasilache // Build and return the new op. 2330a2a260aSNicolas Vasilache OperationState state(op->getLoc(), op->getName()); 2340a2a260aSNicolas Vasilache state.addAttributes(op->getAttrs()); 2350a2a260aSNicolas Vasilache state.addOperands(llvm::to_vector<4>(vectorizedOperands)); 2360a2a260aSNicolas Vasilache state.addTypes(llvm::to_vector<4>(returnTypes)); 2370a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::NewOp, 2380a2a260aSNicolas Vasilache builder.createOperation(state)}; 2390a2a260aSNicolas Vasilache } 2400a2a260aSNicolas Vasilache 2410a2a260aSNicolas Vasilache /// Generic vectorization function that rewrites the body of a `linalgOp` into 2420a2a260aSNicolas Vasilache /// vector form. Generic vectorization proceeds as follows: 2430a2a260aSNicolas Vasilache /// 1. The region for the linalg op is created if necessary. 2440a2a260aSNicolas Vasilache /// 2. Values defined above the region are mapped to themselves and will be 2450a2a260aSNicolas Vasilache /// broadcasted on a per-need basis by their consumers. 2460a2a260aSNicolas Vasilache /// 3. Each region argument is vectorized into a vector.transfer_read (or 0-d 2470a2a260aSNicolas Vasilache /// load). 2480a2a260aSNicolas Vasilache /// TODO: Reuse opportunities for RAR dependencies. 2490a2a260aSNicolas Vasilache /// 4. Register CustomVectorizationHook for YieldOp to capture the results. 2500a2a260aSNicolas Vasilache /// 5. Iteratively call vectorizeOneOp on the region operations. 2510fcbbde2SNicolas Vasilache static Optional<VectorizedLinalgOp> vectorizeAsLinalgGeneric( 2520a2a260aSNicolas Vasilache OpBuilder &builder, LinalgOp linalgOp, 2530a2a260aSNicolas Vasilache ArrayRef<CustomVectorizationHook> customVectorizationHooks = {}) { 2540a2a260aSNicolas Vasilache // 1. Certain Linalg ops do not have a region but only a region builder. 2550a2a260aSNicolas Vasilache // If so, build the region so we can vectorize. 2560a2a260aSNicolas Vasilache std::unique_ptr<Region> owningRegion; 2570a2a260aSNicolas Vasilache Region *region; 2580a2a260aSNicolas Vasilache if (linalgOp->getNumRegions() > 0) { 2590a2a260aSNicolas Vasilache region = &linalgOp->getRegion(0); 2600a2a260aSNicolas Vasilache } else { 2610a2a260aSNicolas Vasilache // RAII avoid remaining in block. 2620a2a260aSNicolas Vasilache OpBuilder::InsertionGuard g(builder); 2630a2a260aSNicolas Vasilache owningRegion = std::make_unique<Region>(); 2640a2a260aSNicolas Vasilache region = owningRegion.get(); 2650a2a260aSNicolas Vasilache Block *block = builder.createBlock(region); 2660a2a260aSNicolas Vasilache auto elementTypes = llvm::to_vector<4>( 2670a2a260aSNicolas Vasilache llvm::map_range(linalgOp.getShapedOperandTypes(), 2680a2a260aSNicolas Vasilache [](ShapedType t) { return t.getElementType(); })); 2690a2a260aSNicolas Vasilache block->addArguments(elementTypes); 2700a2a260aSNicolas Vasilache linalgOp.getRegionBuilder()(*block); 2710a2a260aSNicolas Vasilache } 2720a2a260aSNicolas Vasilache Block *block = ®ion->front(); 2730a2a260aSNicolas Vasilache 2740a2a260aSNicolas Vasilache BlockAndValueMapping bvm; 2750a2a260aSNicolas Vasilache // 2. Values defined above the region can only be broadcast for now. Make them 2760a2a260aSNicolas Vasilache // map to themselves. 2770a2a260aSNicolas Vasilache llvm::SetVector<Value> valuesSet; 2780a2a260aSNicolas Vasilache mlir::getUsedValuesDefinedAbove(*region, valuesSet); 2790a2a260aSNicolas Vasilache bvm.map(valuesSet.getArrayRef(), valuesSet.getArrayRef()); 2800a2a260aSNicolas Vasilache 2810a2a260aSNicolas Vasilache // 3. Turn all BBArgs into vector.transfer_read / load. 2820a2a260aSNicolas Vasilache SmallVector<AffineMap> indexings; 2830a2a260aSNicolas Vasilache for (auto bbarg : block->getArguments()) { 2840a2a260aSNicolas Vasilache Value vectorArg = linalgOp.getShapedOperand(bbarg.getArgNumber()); 2850a2a260aSNicolas Vasilache Value vectorRead = buildVectorRead(builder, vectorArg); 2860a2a260aSNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: new vectorized bbarg(" 2870a2a260aSNicolas Vasilache << bbarg.getArgNumber() << "): " << vectorRead); 2880a2a260aSNicolas Vasilache bvm.map(bbarg, vectorRead); 2890a2a260aSNicolas Vasilache bvm.map(vectorArg, vectorRead); 2900a2a260aSNicolas Vasilache } 2910a2a260aSNicolas Vasilache 2920a2a260aSNicolas Vasilache // 4. Register CustomVectorizationHook for yieldOp. 2930a2a260aSNicolas Vasilache SmallVector<Value> results; 2940a2a260aSNicolas Vasilache CustomVectorizationHook vectorizeYield = 2950a2a260aSNicolas Vasilache [&](Operation *op, 2960a2a260aSNicolas Vasilache const BlockAndValueMapping &bvm) -> VectorizationResult { 2970a2a260aSNicolas Vasilache return vectorizeLinalgYield(builder, op, bvm, linalgOp, results); 2980a2a260aSNicolas Vasilache }; 2990a2a260aSNicolas Vasilache // Append the vectorizeYield hook. 3000a2a260aSNicolas Vasilache auto hooks = llvm::to_vector<4>(customVectorizationHooks); 3010a2a260aSNicolas Vasilache hooks.push_back(vectorizeYield); 3020a2a260aSNicolas Vasilache 3030a2a260aSNicolas Vasilache // 5. Iteratively call `vectorizeOneOp` to each op in the slice. 3040a2a260aSNicolas Vasilache for (Operation &op : block->getOperations()) { 3050a2a260aSNicolas Vasilache VectorizationResult result = vectorizeOneOp(builder, &op, bvm, hooks); 3060a2a260aSNicolas Vasilache if (result.status == VectorizationStatus::Failure) { 3070a2a260aSNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: failed to vectorize: " << op); 3080fcbbde2SNicolas Vasilache return llvm::None; 3090a2a260aSNicolas Vasilache } 3100a2a260aSNicolas Vasilache if (result.status == VectorizationStatus::NewOp) { 3110a2a260aSNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: new vector op: " 3120a2a260aSNicolas Vasilache << *result.newOp;); 3130a2a260aSNicolas Vasilache bvm.map(op.getResults(), result.newOp->getResults()); 3140a2a260aSNicolas Vasilache } 3150a2a260aSNicolas Vasilache } 3160a2a260aSNicolas Vasilache 3170fcbbde2SNicolas Vasilache return VectorizedLinalgOp{{results}}; 3180a2a260aSNicolas Vasilache } 3190a2a260aSNicolas Vasilache 3200a2a260aSNicolas Vasilache /// Detect whether `r` has only ConstantOp, ElementwiseMappable and YieldOp. 321c503dc1bSThomas Raoux static bool hasOnlyScalarElementwiseOp(Region &r) { 322c503dc1bSThomas Raoux if (!llvm::hasSingleElement(r)) 323c503dc1bSThomas Raoux return false; 324c503dc1bSThomas Raoux for (Operation &op : r.front()) { 325c503dc1bSThomas Raoux if (!(isa<ConstantOp, linalg::YieldOp>(op) || 326c503dc1bSThomas Raoux op.hasTrait<OpTrait::ElementwiseMappable>()) || 327c503dc1bSThomas Raoux llvm::any_of(op.getResultTypes(), 328c503dc1bSThomas Raoux [](Type type) { return !type.isIntOrIndexOrFloat(); })) 329c503dc1bSThomas Raoux return false; 330c503dc1bSThomas Raoux } 331c503dc1bSThomas Raoux return true; 332c503dc1bSThomas Raoux } 333c503dc1bSThomas Raoux 334c503dc1bSThomas Raoux // Return true if the op is an element-wise linalg op. 335c503dc1bSThomas Raoux static bool isElementwise(Operation *op) { 336c503dc1bSThomas Raoux auto genericOp = dyn_cast<linalg::GenericOp>(op); 337c503dc1bSThomas Raoux if (!genericOp) 338c503dc1bSThomas Raoux return false; 339c503dc1bSThomas Raoux if (genericOp.getNumLoops() != genericOp.getNumParallelLoops()) 340c503dc1bSThomas Raoux return false; 341c503dc1bSThomas Raoux // TODO: relax the restrictions on indexing map. 342c503dc1bSThomas Raoux for (unsigned i = 0, e = genericOp.getNumOutputs(); i < e; i++) { 343c503dc1bSThomas Raoux if (!genericOp.getOutputIndexingMap(i).isIdentity()) 344c503dc1bSThomas Raoux return false; 345c503dc1bSThomas Raoux } 346f245b7adSNicolas Vasilache // Currently bound the input indexing map to minor identity as other 347c503dc1bSThomas Raoux // permutations might require adding transpose ops to convert the vector read 348c503dc1bSThomas Raoux // to the right shape. 349c503dc1bSThomas Raoux for (unsigned i = 0, e = genericOp.getNumInputs(); i < e; i++) { 350c503dc1bSThomas Raoux if (!genericOp.getInputIndexingMap(i).isMinorIdentity()) 351c503dc1bSThomas Raoux return false; 352c503dc1bSThomas Raoux } 353c503dc1bSThomas Raoux return hasOnlyScalarElementwiseOp(genericOp.getRegion()); 354c503dc1bSThomas Raoux } 355c503dc1bSThomas Raoux 3560fcbbde2SNicolas Vasilache static Optional<VectorizedLinalgOp> vectorizeContraction(OpBuilder &builder, 3570fcbbde2SNicolas Vasilache LinalgOp linalgOp) { 358e4a503a2SNicolas Vasilache assert(isaContractionOpInterface(linalgOp) && 359e4a503a2SNicolas Vasilache "expected vectorizeContraction preconditions to be met"); 3600a2a260aSNicolas Vasilache Location loc = linalgOp.getLoc(); 361a1b9fb22SThomas Raoux // Vectorize other ops as vector contraction. 362a1b9fb22SThomas Raoux // TODO: interface. 363e4a503a2SNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 364e4a503a2SNicolas Vasilache << "Rewrite linalg op as vector.contract: "; 365e4a503a2SNicolas Vasilache linalgOp.dump()); 3660a2a260aSNicolas Vasilache // Special function that describes how to vectorize the multiplication op in a 3670a2a260aSNicolas Vasilache // linalg contraction. 3680a2a260aSNicolas Vasilache CustomVectorizationHook vectorizeContraction = 3690a2a260aSNicolas Vasilache [&](Operation *op, 3700a2a260aSNicolas Vasilache const BlockAndValueMapping &bvm) -> VectorizationResult { 3710a2a260aSNicolas Vasilache if (!isa<MulIOp, MulFOp>(op)) 3720a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::Failure, nullptr}; 3730a2a260aSNicolas Vasilache auto outShape = linalgOp.getOutputShapedType(0).getShape(); 3740a2a260aSNicolas Vasilache auto vType = outShape.empty() 3750a2a260aSNicolas Vasilache ? op->getResult(0).getType() 3760a2a260aSNicolas Vasilache : VectorType::get(outShape, op->getResult(0).getType()); 3770a2a260aSNicolas Vasilache auto zero = 3780a2a260aSNicolas Vasilache builder.create<ConstantOp>(loc, vType, builder.getZeroAttr(vType)); 3790a2a260aSNicolas Vasilache Operation *contract = builder.create<vector::ContractionOp>( 3800a2a260aSNicolas Vasilache loc, bvm.lookup(op->getOperand(0)), bvm.lookup(op->getOperand(1)), zero, 3810a2a260aSNicolas Vasilache linalgOp.indexing_maps(), linalgOp.iterator_types()); 3820a2a260aSNicolas Vasilache return VectorizationResult{VectorizationStatus::NewOp, contract}; 3830a2a260aSNicolas Vasilache }; 3840fcbbde2SNicolas Vasilache return vectorizeAsLinalgGeneric(builder, linalgOp, {vectorizeContraction}); 385307cfdf5SNicolas Vasilache } 3861ee11432SNicolas Vasilache 387e4a503a2SNicolas Vasilache LogicalResult mlir::linalg::vectorizeLinalgOpPrecondition(Operation *op) { 388e4a503a2SNicolas Vasilache auto linalgOp = cast<linalg::LinalgOp>(op); 389e4a503a2SNicolas Vasilache // All types must be static shape to go to vector. 390e4a503a2SNicolas Vasilache for (Value operand : linalgOp.getShapedOperands()) 391e4a503a2SNicolas Vasilache if (!operand.getType().cast<ShapedType>().hasStaticShape()) 392e4a503a2SNicolas Vasilache return failure(); 393e4a503a2SNicolas Vasilache for (Type outputTensorType : linalgOp.getOutputTensorTypes()) 394e4a503a2SNicolas Vasilache if (!outputTensorType.cast<ShapedType>().hasStaticShape()) 395e4a503a2SNicolas Vasilache return failure(); 396e4a503a2SNicolas Vasilache 397e4a503a2SNicolas Vasilache if (isa<linalg::FillOp, linalg::CopyOp>(op)) 398e4a503a2SNicolas Vasilache return success(); 399e4a503a2SNicolas Vasilache if (isElementwise(op)) 400e4a503a2SNicolas Vasilache return success(); 401e4a503a2SNicolas Vasilache return success(isaContractionOpInterface(linalgOp)); 402e4a503a2SNicolas Vasilache } 403e4a503a2SNicolas Vasilache 4040fcbbde2SNicolas Vasilache Optional<VectorizedLinalgOp> mlir::linalg::vectorizeLinalgOp(OpBuilder &builder, 4050fcbbde2SNicolas Vasilache Operation *op) { 4060fcbbde2SNicolas Vasilache if (failed(vectorizeLinalgOpPrecondition(op))) 4070fcbbde2SNicolas Vasilache return llvm::None; 408e4a503a2SNicolas Vasilache 409e4a503a2SNicolas Vasilache edsc::ScopedContext scope(builder, op->getLoc()); 410e4a503a2SNicolas Vasilache // In the case of 0-D memrefs, return null and special case to scalar load or 411e4a503a2SNicolas Vasilache // store later. 412e4a503a2SNicolas Vasilache if (auto fillOp = dyn_cast<linalg::FillOp>(op)) { 413e4a503a2SNicolas Vasilache // Vectorize fill as a vector.broadcast. 414e4a503a2SNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 415e4a503a2SNicolas Vasilache << "Rewrite linalg.fill as vector.broadcast: " << *op); 4160fcbbde2SNicolas Vasilache VectorizedLinalgOp res; 4170fcbbde2SNicolas Vasilache if (Value v = buildVectorWrite(builder, fillOp.value(), fillOp.output())) 4180fcbbde2SNicolas Vasilache res.tensorResults.push_back(v); 4190fcbbde2SNicolas Vasilache return res; 420e4a503a2SNicolas Vasilache } 421e4a503a2SNicolas Vasilache if (auto copyOp = dyn_cast<linalg::CopyOp>(op)) { 422e4a503a2SNicolas Vasilache // Vectorize copy as a vector.transfer_read+vector.transfer_write. 423e4a503a2SNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 424e4a503a2SNicolas Vasilache << "Rewrite linalg.copy as vector.transfer_read + " 425e4a503a2SNicolas Vasilache "vector.transfer_write: " 426e4a503a2SNicolas Vasilache << *op); 427e4a503a2SNicolas Vasilache Value vector = buildVectorRead(builder, copyOp.input()); 4280fcbbde2SNicolas Vasilache VectorizedLinalgOp res; 4290fcbbde2SNicolas Vasilache if (Value v = buildVectorWrite(builder, vector, copyOp.output())) 4300fcbbde2SNicolas Vasilache res.tensorResults.push_back(v); 4310fcbbde2SNicolas Vasilache return res; 432e4a503a2SNicolas Vasilache } 433e4a503a2SNicolas Vasilache if (isElementwise(op)) { 434e4a503a2SNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 4350fcbbde2SNicolas Vasilache << "Vectorize linalg op as a generic: " << *op); 4360fcbbde2SNicolas Vasilache return vectorizeAsLinalgGeneric(builder, cast<LinalgOp>(op)); 437e4a503a2SNicolas Vasilache } 438e4a503a2SNicolas Vasilache 4390fcbbde2SNicolas Vasilache // TODO: as soon as Copy and FillOp. get a region builder, replace all the 4400fcbbde2SNicolas Vasilache // above by: 4410fcbbde2SNicolas Vasilache // if (isa<FillOp, CopyOp>(op) || isElementwise(op)) { 4420fcbbde2SNicolas Vasilache // LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 4430fcbbde2SNicolas Vasilache // << "Vectorize linalg op as a generic: " << *op); 4440fcbbde2SNicolas Vasilache // return vectorizeAsLinalgGeneric(builder, cast<LinalgOp>(op)); 4450fcbbde2SNicolas Vasilache // } 4460fcbbde2SNicolas Vasilache 4470fcbbde2SNicolas Vasilache return vectorizeContraction(builder, cast<LinalgOp>(op)); 448e4a503a2SNicolas Vasilache } 449e4a503a2SNicolas Vasilache 450f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 451*bb69de3fSNicolas Vasilache // Misc. vectorization patterns. 452f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 453*bb69de3fSNicolas Vasilache 454*bb69de3fSNicolas Vasilache /// Rewrite a PadTensorOp into a sequence of InitTensorOp, TransferReadOp and 455*bb69de3fSNicolas Vasilache /// TransferWriteOp. For now, this only applies when all low and high paddings 456*bb69de3fSNicolas Vasilache /// are determined to be zero. 457*bb69de3fSNicolas Vasilache LogicalResult PadTensorOpVectorizationPattern::matchAndRewrite( 458*bb69de3fSNicolas Vasilache linalg::PadTensorOp padOp, PatternRewriter &rewriter) const { 459*bb69de3fSNicolas Vasilache // Helper function to determine whether an OpFoldResult is not a zero Index. 460*bb69de3fSNicolas Vasilache auto isNotZeroIndex = [](OpFoldResult ofr) { 461*bb69de3fSNicolas Vasilache if (Attribute attr = ofr.dyn_cast<Attribute>()) 462*bb69de3fSNicolas Vasilache return attr.cast<IntegerAttr>().getInt() != 0; 463*bb69de3fSNicolas Vasilache Value v = ofr.get<Value>(); 464*bb69de3fSNicolas Vasilache if (auto constOp = v.getDefiningOp<ConstantIntOp>()) 465*bb69de3fSNicolas Vasilache return constOp.getValue() != 0; 466*bb69de3fSNicolas Vasilache return true; 467*bb69de3fSNicolas Vasilache }; 468*bb69de3fSNicolas Vasilache 469*bb69de3fSNicolas Vasilache auto resultShapedType = padOp.result().getType().cast<ShapedType>(); 470*bb69de3fSNicolas Vasilache // Bail on non-static shapes. 471*bb69de3fSNicolas Vasilache if (!resultShapedType.hasStaticShape()) 472*bb69de3fSNicolas Vasilache return failure(); 473*bb69de3fSNicolas Vasilache 474*bb69de3fSNicolas Vasilache // If any pad_low is not a static 0, needs a mask. Bail for now. 475*bb69de3fSNicolas Vasilache if (llvm::any_of(padOp.getMixedLowPad(), isNotZeroIndex)) 476*bb69de3fSNicolas Vasilache return failure(); 477*bb69de3fSNicolas Vasilache VectorType vectorType = extractVectorTypeFromShapedValue(padOp.result()); 478*bb69de3fSNicolas Vasilache if (!vectorType) 479*bb69de3fSNicolas Vasilache return failure(); 480*bb69de3fSNicolas Vasilache 481*bb69de3fSNicolas Vasilache // Only support padding with a constant for now, i.e. either: 482*bb69de3fSNicolas Vasilache // 1. A BBarg from a different block. 483*bb69de3fSNicolas Vasilache // 2. A value defined outside of the current block. 484*bb69de3fSNicolas Vasilache Block &block = padOp.region().front(); 485*bb69de3fSNicolas Vasilache auto yieldOp = cast<YieldOp>(block.getTerminator()); 486*bb69de3fSNicolas Vasilache assert(yieldOp.getNumOperands() == 1 && "expected single operand yield"); 487*bb69de3fSNicolas Vasilache Value padValue = yieldOp.values().front(); 488*bb69de3fSNicolas Vasilache Operation *definingOp = padValue.getDefiningOp(); 489*bb69de3fSNicolas Vasilache if (definingOp && definingOp->getBlock() == &block) 490*bb69de3fSNicolas Vasilache return failure(); 491*bb69de3fSNicolas Vasilache if (!definingOp && padValue.cast<BlockArgument>().getOwner() == &block) 492*bb69de3fSNicolas Vasilache return failure(); 493*bb69de3fSNicolas Vasilache 494*bb69de3fSNicolas Vasilache // TODO: if any pad_high is not a static 0, needs a mask. For now, just bail. 495*bb69de3fSNicolas Vasilache if (llvm::any_of(padOp.getMixedHighPad(), 496*bb69de3fSNicolas Vasilache [&](OpFoldResult ofr) { return isNotZeroIndex(ofr); })) 497*bb69de3fSNicolas Vasilache return failure(); 498*bb69de3fSNicolas Vasilache 499*bb69de3fSNicolas Vasilache // Now we can rewrite as InitTensorOp + TransferReadOp@[0..0] + 500*bb69de3fSNicolas Vasilache // TransferWriteOp@[0..0]. 501*bb69de3fSNicolas Vasilache SmallVector<Value> indices( 502*bb69de3fSNicolas Vasilache resultShapedType.getRank(), 503*bb69de3fSNicolas Vasilache rewriter.create<ConstantIndexOp>(padOp.getLoc(), 0)); 504*bb69de3fSNicolas Vasilache Value read = rewriter.create<vector::TransferReadOp>( 505*bb69de3fSNicolas Vasilache padOp.getLoc(), vectorType, padOp.source(), indices, padValue); 506*bb69de3fSNicolas Vasilache Value init = 507*bb69de3fSNicolas Vasilache rewriter.create<InitTensorOp>(padOp.getLoc(), resultShapedType.getShape(), 508*bb69de3fSNicolas Vasilache resultShapedType.getElementType()); 509*bb69de3fSNicolas Vasilache rewriter.replaceOpWithNewOp<vector::TransferWriteOp>(padOp, read, init, 510*bb69de3fSNicolas Vasilache indices); 511*bb69de3fSNicolas Vasilache 512*bb69de3fSNicolas Vasilache return success(); 513*bb69de3fSNicolas Vasilache } 514*bb69de3fSNicolas Vasilache 515*bb69de3fSNicolas Vasilache // TODO: cleanup all the convolution vectorization patterns. 516f245b7adSNicolas Vasilache template <class ConvOp, int N> 517f245b7adSNicolas Vasilache LogicalResult ConvOpVectorization<ConvOp, N>::matchAndRewrite( 518f245b7adSNicolas Vasilache ConvOp op, PatternRewriter &rewriter) const { 519f245b7adSNicolas Vasilache Location loc = op.getLoc(); 520f245b7adSNicolas Vasilache MLIRContext *context = op.getContext(); 521f245b7adSNicolas Vasilache edsc::ScopedContext scope(rewriter, loc); 522f245b7adSNicolas Vasilache 523f245b7adSNicolas Vasilache ShapedType inShapeType = op.getInputShapedType(0); 524f245b7adSNicolas Vasilache ShapedType kShapeType = op.getInputShapedType(1); 525f245b7adSNicolas Vasilache 526f245b7adSNicolas Vasilache ArrayRef<int64_t> inShape = inShapeType.getShape(); 527f245b7adSNicolas Vasilache ArrayRef<int64_t> kShape = kShapeType.getShape(); 528f245b7adSNicolas Vasilache 529f245b7adSNicolas Vasilache if (!inShapeType.hasStaticShape() || !kShapeType.hasStaticShape()) 530f245b7adSNicolas Vasilache return failure(); 531f245b7adSNicolas Vasilache 532f245b7adSNicolas Vasilache SmallVector<AffineExpr, 4> mapping; 533f245b7adSNicolas Vasilache SmallVector<int64_t, 4> vectorDims; 534f245b7adSNicolas Vasilache // Fail to apply when the size of not vectorized dimension is not 1. 535f245b7adSNicolas Vasilache for (unsigned i = 0; i < N; i++) { 536f245b7adSNicolas Vasilache if (!mask[i] && (inShape[i] != 1 || kShape[i] != 1)) 537f245b7adSNicolas Vasilache return failure(); 538f245b7adSNicolas Vasilache 539f245b7adSNicolas Vasilache if (mask[i] && inShape[i] != kShape[i]) 540f245b7adSNicolas Vasilache return failure(); 541f245b7adSNicolas Vasilache 542f245b7adSNicolas Vasilache if (mask[i]) { 543f245b7adSNicolas Vasilache mapping.push_back(getAffineDimExpr(i, context)); 544f245b7adSNicolas Vasilache vectorDims.push_back(inShape[i]); 545f245b7adSNicolas Vasilache } 546f245b7adSNicolas Vasilache } 547f245b7adSNicolas Vasilache 548f245b7adSNicolas Vasilache Value input = op.getInput(0); 549f245b7adSNicolas Vasilache Value kernel = op.getInput(1); 550f245b7adSNicolas Vasilache Value output = op.getOutputBuffer(0); 551f245b7adSNicolas Vasilache 552f245b7adSNicolas Vasilache unsigned rank = inShapeType.getRank(); 553f245b7adSNicolas Vasilache unsigned numDims = mapping.size(); 554f245b7adSNicolas Vasilache Type elemType = inShapeType.getElementType(); 555f245b7adSNicolas Vasilache 556f245b7adSNicolas Vasilache auto map = AffineMap::get(rank, 0, mapping, context); 557f245b7adSNicolas Vasilache SmallVector<Value, 4> zeros(rank, std_constant_index(0)); 558f245b7adSNicolas Vasilache auto vecType = VectorType::get(vectorDims, elemType); 559f245b7adSNicolas Vasilache 560f245b7adSNicolas Vasilache auto inputVec = vector_transfer_read(vecType, input, zeros, map); 561f245b7adSNicolas Vasilache auto kernelVec = vector_transfer_read(vecType, kernel, zeros, map); 562f245b7adSNicolas Vasilache 563f245b7adSNicolas Vasilache auto acc = std_constant(elemType, rewriter.getZeroAttr(elemType)); 564f245b7adSNicolas Vasilache 565f245b7adSNicolas Vasilache std::array<AffineMap, 3> indexingMaps{ 566f245b7adSNicolas Vasilache AffineMap::getMultiDimIdentityMap(numDims, context), 567f245b7adSNicolas Vasilache AffineMap::getMultiDimIdentityMap(numDims, context), 568f245b7adSNicolas Vasilache AffineMap::get(numDims, 0, {}, context)}; 569f245b7adSNicolas Vasilache 570f245b7adSNicolas Vasilache std::vector<StringRef> iteratorTypes(numDims, "reduction"); 571f245b7adSNicolas Vasilache 572f245b7adSNicolas Vasilache auto result = rewriter.create<vector::ContractionOp>( 573f245b7adSNicolas Vasilache loc, inputVec, kernelVec, acc, 574f245b7adSNicolas Vasilache rewriter.getAffineMapArrayAttr(indexingMaps), 575f245b7adSNicolas Vasilache rewriter.getStrArrayAttr(iteratorTypes)); 576f245b7adSNicolas Vasilache 577f245b7adSNicolas Vasilache rewriter.create<StoreOp>(loc, result, output, ValueRange(zeros)); 578f245b7adSNicolas Vasilache rewriter.eraseOp(op); 579f245b7adSNicolas Vasilache return success(); 580f245b7adSNicolas Vasilache } 581f245b7adSNicolas Vasilache 582f245b7adSNicolas Vasilache using ConvOpConst = ConvOpVectorization<ConvWOp, 1>; 583f245b7adSNicolas Vasilache 584f245b7adSNicolas Vasilache /// Inserts tiling, promotion and vectorization pattern for ConvOp 585f245b7adSNicolas Vasilache /// conversion into corresponding pattern lists. 586f245b7adSNicolas Vasilache template <typename ConvOp, unsigned N> 587f245b7adSNicolas Vasilache static void 588f245b7adSNicolas Vasilache populateVectorizationPatterns(OwningRewritePatternList &tilingPatterns, 589f245b7adSNicolas Vasilache OwningRewritePatternList &promotionPatterns, 590f245b7adSNicolas Vasilache OwningRewritePatternList &vectorizationPatterns, 591f245b7adSNicolas Vasilache ArrayRef<int64_t> tileSizes, 592f245b7adSNicolas Vasilache MLIRContext *context) { 593f245b7adSNicolas Vasilache if (tileSizes.size() < N) 594f245b7adSNicolas Vasilache return; 595f245b7adSNicolas Vasilache 596f245b7adSNicolas Vasilache constexpr static StringRef kTiledMarker = "TILED"; 597f245b7adSNicolas Vasilache constexpr static StringRef kPromotedMarker = "PROMOTED"; 598f245b7adSNicolas Vasilache tilingPatterns.insert<LinalgTilingPattern<ConvOp>>( 599f245b7adSNicolas Vasilache context, LinalgTilingOptions().setTileSizes(tileSizes), 600f245b7adSNicolas Vasilache LinalgTransformationFilter(ArrayRef<Identifier>{}, 601f245b7adSNicolas Vasilache Identifier::get(kTiledMarker, context))); 602f245b7adSNicolas Vasilache 603f245b7adSNicolas Vasilache promotionPatterns.insert<LinalgPromotionPattern<ConvOp>>( 604f245b7adSNicolas Vasilache context, LinalgPromotionOptions().setUseFullTileBuffersByDefault(true), 605f245b7adSNicolas Vasilache LinalgTransformationFilter(Identifier::get(kTiledMarker, context), 606f245b7adSNicolas Vasilache Identifier::get(kPromotedMarker, context))); 607f245b7adSNicolas Vasilache 608f245b7adSNicolas Vasilache SmallVector<bool, 4> mask(N); 609f245b7adSNicolas Vasilache int offset = tileSizes.size() - N; 610f245b7adSNicolas Vasilache std::transform(tileSizes.begin() + offset, tileSizes.end(), mask.begin(), 611f245b7adSNicolas Vasilache [](int64_t i) -> bool { return i > 1; }); 612f245b7adSNicolas Vasilache 613f245b7adSNicolas Vasilache vectorizationPatterns.insert<ConvOpVectorization<ConvOp, N>>(context, mask); 614f245b7adSNicolas Vasilache } 615f245b7adSNicolas Vasilache 616f245b7adSNicolas Vasilache void mlir::linalg::populateConvVectorizationPatterns( 617f245b7adSNicolas Vasilache MLIRContext *context, SmallVectorImpl<OwningRewritePatternList> &patterns, 618f245b7adSNicolas Vasilache ArrayRef<int64_t> tileSizes) { 619f245b7adSNicolas Vasilache OwningRewritePatternList tiling, promotion, vectorization; 620f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvWOp, 1>(tiling, promotion, vectorization, 621f245b7adSNicolas Vasilache tileSizes, context); 622f245b7adSNicolas Vasilache 623f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNWCOp, 3>(tiling, promotion, vectorization, 624f245b7adSNicolas Vasilache tileSizes, context); 625f245b7adSNicolas Vasilache 626f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNCWOp, 3>(tiling, promotion, vectorization, 627f245b7adSNicolas Vasilache tileSizes, context); 628f245b7adSNicolas Vasilache 629f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvHWOp, 2>(tiling, promotion, vectorization, 630f245b7adSNicolas Vasilache tileSizes, context); 631f245b7adSNicolas Vasilache 632f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNHWCOp, 4>(tiling, promotion, vectorization, 633f245b7adSNicolas Vasilache tileSizes, context); 634f245b7adSNicolas Vasilache 635f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNCHWOp, 4>(tiling, promotion, vectorization, 636f245b7adSNicolas Vasilache tileSizes, context); 637f245b7adSNicolas Vasilache 638f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvDHWOp, 3>(tiling, promotion, vectorization, 639f245b7adSNicolas Vasilache tileSizes, context); 640f245b7adSNicolas Vasilache 641f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNDHWCOp, 5>( 642f245b7adSNicolas Vasilache tiling, promotion, vectorization, tileSizes, context); 643f245b7adSNicolas Vasilache 644f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNCDHWOp, 5>( 645f245b7adSNicolas Vasilache tiling, promotion, vectorization, tileSizes, context); 646f245b7adSNicolas Vasilache 647f245b7adSNicolas Vasilache patterns.push_back(std::move(tiling)); 648f245b7adSNicolas Vasilache patterns.push_back(std::move(promotion)); 649f245b7adSNicolas Vasilache patterns.push_back(std::move(vectorization)); 650f245b7adSNicolas Vasilache } 651f245b7adSNicolas Vasilache 652f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 653f245b7adSNicolas Vasilache // Forwarding patterns 654f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 655f245b7adSNicolas Vasilache 6561ee11432SNicolas Vasilache /// Check whether there is any interleaved use of any `values` between `firstOp` 6571ee11432SNicolas Vasilache /// and `secondOp`. Conservatively return `true` if any op or value is in a 6581ee11432SNicolas Vasilache /// different block. 6591ee11432SNicolas Vasilache static bool mayExistInterleavedUses(Operation *firstOp, Operation *secondOp, 6601ee11432SNicolas Vasilache ValueRange values) { 6611ee11432SNicolas Vasilache if (firstOp->getBlock() != secondOp->getBlock() || 6621ee11432SNicolas Vasilache !firstOp->isBeforeInBlock(secondOp)) { 663e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 664e4a503a2SNicolas Vasilache << "interleavedUses precondition failed, firstOp: " 6651ee11432SNicolas Vasilache << *firstOp << ", second op: " << *secondOp); 6661ee11432SNicolas Vasilache return true; 6671ee11432SNicolas Vasilache } 6681ee11432SNicolas Vasilache for (auto v : values) { 6691ee11432SNicolas Vasilache for (auto &u : v.getUses()) { 6701ee11432SNicolas Vasilache Operation *owner = u.getOwner(); 6711ee11432SNicolas Vasilache if (owner == firstOp || owner == secondOp) 6721ee11432SNicolas Vasilache continue; 6731ee11432SNicolas Vasilache // TODO: this is too conservative, use dominance info in the future. 6741ee11432SNicolas Vasilache if (owner->getBlock() == firstOp->getBlock() && 6751ee11432SNicolas Vasilache (owner->isBeforeInBlock(firstOp) || secondOp->isBeforeInBlock(owner))) 6761ee11432SNicolas Vasilache continue; 6771ee11432SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() 678e4a503a2SNicolas Vasilache << "\n[" DEBUG_TYPE "]: " 679e4a503a2SNicolas Vasilache << " found interleaved op " << *owner 6801ee11432SNicolas Vasilache << ", firstOp: " << *firstOp << ", second op: " << *secondOp); 6811ee11432SNicolas Vasilache return true; 6821ee11432SNicolas Vasilache } 6831ee11432SNicolas Vasilache } 6841ee11432SNicolas Vasilache return false; 6851ee11432SNicolas Vasilache } 6861ee11432SNicolas Vasilache 6871ee11432SNicolas Vasilache /// Return the unique subview use of `v` if it is indeed unique, null otherwise. 6881ee11432SNicolas Vasilache static SubViewOp getSubViewUseIfUnique(Value v) { 6891ee11432SNicolas Vasilache SubViewOp subViewOp; 6901ee11432SNicolas Vasilache for (auto &u : v.getUses()) { 6911ee11432SNicolas Vasilache if (auto newSubViewOp = dyn_cast<SubViewOp>(u.getOwner())) { 6921ee11432SNicolas Vasilache if (subViewOp) 6931ee11432SNicolas Vasilache return SubViewOp(); 6941ee11432SNicolas Vasilache subViewOp = newSubViewOp; 6951ee11432SNicolas Vasilache } 6961ee11432SNicolas Vasilache } 6971ee11432SNicolas Vasilache return subViewOp; 6981ee11432SNicolas Vasilache } 6991ee11432SNicolas Vasilache 7001ee11432SNicolas Vasilache /// TODO: use interfaces, side-effects and aliasing analysis as appropriate, 7011ee11432SNicolas Vasilache /// when available. 7021ee11432SNicolas Vasilache LogicalResult LinalgCopyVTRForwardingPattern::matchAndRewrite( 7031ee11432SNicolas Vasilache vector::TransferReadOp xferOp, PatternRewriter &rewriter) const { 7041ee11432SNicolas Vasilache 7051ee11432SNicolas Vasilache // Transfer into `view`. 70626c8f908SThomas Raoux Value viewOrAlloc = xferOp.source(); 7071ee11432SNicolas Vasilache if (!viewOrAlloc.getDefiningOp<ViewOp>() && 7081ee11432SNicolas Vasilache !viewOrAlloc.getDefiningOp<AllocOp>()) 7091ee11432SNicolas Vasilache return failure(); 7101ee11432SNicolas Vasilache 711e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " << viewOrAlloc); 7121ee11432SNicolas Vasilache 7131ee11432SNicolas Vasilache // Ensure there is exactly one subview of `viewOrAlloc` defining `subView`. 7141ee11432SNicolas Vasilache SubViewOp subViewOp = getSubViewUseIfUnique(viewOrAlloc); 7151ee11432SNicolas Vasilache if (!subViewOp) 7161ee11432SNicolas Vasilache return failure(); 7171ee11432SNicolas Vasilache Value subView = subViewOp.getResult(); 718e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 719e4a503a2SNicolas Vasilache << "with subView " << subView); 7201ee11432SNicolas Vasilache 7211ee11432SNicolas Vasilache // Find the copy into `subView` without interleaved uses. 7221ee11432SNicolas Vasilache CopyOp copyOp; 7231ee11432SNicolas Vasilache for (auto &u : subView.getUses()) { 7241ee11432SNicolas Vasilache if (auto newCopyOp = dyn_cast<CopyOp>(u.getOwner())) { 7251ee11432SNicolas Vasilache if (newCopyOp.getOutputBuffer(0) != subView) 7261ee11432SNicolas Vasilache continue; 727e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 728e4a503a2SNicolas Vasilache << "copy candidate " << *newCopyOp); 7291ee11432SNicolas Vasilache if (mayExistInterleavedUses(newCopyOp, xferOp, {viewOrAlloc, subView})) 7301ee11432SNicolas Vasilache continue; 7311ee11432SNicolas Vasilache copyOp = newCopyOp; 7321ee11432SNicolas Vasilache break; 7331ee11432SNicolas Vasilache } 7341ee11432SNicolas Vasilache } 7351ee11432SNicolas Vasilache if (!copyOp) 7361ee11432SNicolas Vasilache return failure(); 737e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 738e4a503a2SNicolas Vasilache << "with copy " << *copyOp); 7391ee11432SNicolas Vasilache 7401ee11432SNicolas Vasilache // Find the fill into `viewOrAlloc` without interleaved uses before the copy. 7411ee11432SNicolas Vasilache FillOp maybeFillOp; 7421ee11432SNicolas Vasilache for (auto &u : viewOrAlloc.getUses()) { 7431ee11432SNicolas Vasilache if (auto newFillOp = dyn_cast<FillOp>(u.getOwner())) { 7441ee11432SNicolas Vasilache if (newFillOp.getOutputBuffer(0) != viewOrAlloc) 7451ee11432SNicolas Vasilache continue; 746e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 747e4a503a2SNicolas Vasilache << "fill candidate " << *newFillOp); 7481ee11432SNicolas Vasilache if (mayExistInterleavedUses(newFillOp, copyOp, {viewOrAlloc, subView})) 7491ee11432SNicolas Vasilache continue; 7501ee11432SNicolas Vasilache maybeFillOp = newFillOp; 7511ee11432SNicolas Vasilache break; 7521ee11432SNicolas Vasilache } 7531ee11432SNicolas Vasilache } 7541ee11432SNicolas Vasilache // Ensure padding matches. 7551ee11432SNicolas Vasilache if (maybeFillOp && xferOp.padding() != maybeFillOp.value()) 7561ee11432SNicolas Vasilache return failure(); 7571ee11432SNicolas Vasilache if (maybeFillOp) 758e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 759e4a503a2SNicolas Vasilache << "with maybeFillOp " << *maybeFillOp); 7601ee11432SNicolas Vasilache 7611ee11432SNicolas Vasilache // `in` is the subview that linalg.copy reads. Replace it. 7621ee11432SNicolas Vasilache Value in = copyOp.getInput(0); 7631ee11432SNicolas Vasilache 764512da70bSNicolas Vasilache // linalg.copy + linalg.fill can be used to create a padded local buffer. 765512da70bSNicolas Vasilache // The `masked` attribute is only valid on this padded buffer. 766512da70bSNicolas Vasilache // When forwarding to vector.transfer_read, the attribute must be reset 767512da70bSNicolas Vasilache // conservatively. 7681ee11432SNicolas Vasilache Value res = rewriter.create<vector::TransferReadOp>( 7691ee11432SNicolas Vasilache xferOp.getLoc(), xferOp.getVectorType(), in, xferOp.indices(), 770512da70bSNicolas Vasilache xferOp.permutation_map(), xferOp.padding(), ArrayAttr()); 7711ee11432SNicolas Vasilache 7721ee11432SNicolas Vasilache if (maybeFillOp) 7731ee11432SNicolas Vasilache rewriter.eraseOp(maybeFillOp); 7741ee11432SNicolas Vasilache rewriter.eraseOp(copyOp); 7751ee11432SNicolas Vasilache rewriter.replaceOp(xferOp, res); 7761ee11432SNicolas Vasilache 7771ee11432SNicolas Vasilache return success(); 7781ee11432SNicolas Vasilache } 7791ee11432SNicolas Vasilache 7801ee11432SNicolas Vasilache /// TODO: use interfaces, side-effects and aliasing analysis as appropriate, 7811ee11432SNicolas Vasilache /// when available. 7821ee11432SNicolas Vasilache LogicalResult LinalgCopyVTWForwardingPattern::matchAndRewrite( 7831ee11432SNicolas Vasilache vector::TransferWriteOp xferOp, PatternRewriter &rewriter) const { 7841ee11432SNicolas Vasilache // Transfer into `viewOrAlloc`. 78526c8f908SThomas Raoux Value viewOrAlloc = xferOp.source(); 7861ee11432SNicolas Vasilache if (!viewOrAlloc.getDefiningOp<ViewOp>() && 7871ee11432SNicolas Vasilache !viewOrAlloc.getDefiningOp<AllocOp>()) 7881ee11432SNicolas Vasilache return failure(); 7891ee11432SNicolas Vasilache 7901ee11432SNicolas Vasilache // Ensure there is exactly one subview of `viewOrAlloc` defining `subView`. 7911ee11432SNicolas Vasilache SubViewOp subViewOp = getSubViewUseIfUnique(viewOrAlloc); 7921ee11432SNicolas Vasilache if (!subViewOp) 7931ee11432SNicolas Vasilache return failure(); 7941ee11432SNicolas Vasilache Value subView = subViewOp.getResult(); 7951ee11432SNicolas Vasilache 7961ee11432SNicolas Vasilache // Find the copy from `subView` without interleaved uses. 7971ee11432SNicolas Vasilache CopyOp copyOp; 7981ee11432SNicolas Vasilache for (auto &u : subViewOp.getResult().getUses()) { 7991ee11432SNicolas Vasilache if (auto newCopyOp = dyn_cast<CopyOp>(u.getOwner())) { 8001ee11432SNicolas Vasilache if (newCopyOp.getInput(0) != subView) 8011ee11432SNicolas Vasilache continue; 8021ee11432SNicolas Vasilache if (mayExistInterleavedUses(xferOp, newCopyOp, {viewOrAlloc, subView})) 8031ee11432SNicolas Vasilache continue; 8041ee11432SNicolas Vasilache copyOp = newCopyOp; 8051ee11432SNicolas Vasilache break; 8061ee11432SNicolas Vasilache } 8071ee11432SNicolas Vasilache } 8081ee11432SNicolas Vasilache if (!copyOp) 8091ee11432SNicolas Vasilache return failure(); 8101ee11432SNicolas Vasilache 8111ee11432SNicolas Vasilache // `out` is the subview copied into that we replace. 8121ee11432SNicolas Vasilache Value out = copyOp.getOutputBuffer(0); 8131ee11432SNicolas Vasilache 8141ee11432SNicolas Vasilache // Forward vector.transfer into copy. 815512da70bSNicolas Vasilache // linalg.copy + linalg.fill can be used to create a padded local buffer. 816512da70bSNicolas Vasilache // The `masked` attribute is only valid on this padded buffer. 817512da70bSNicolas Vasilache // When forwarding to vector.transfer_write, the attribute must be reset 818512da70bSNicolas Vasilache // conservatively. 8191ee11432SNicolas Vasilache rewriter.create<vector::TransferWriteOp>( 8201ee11432SNicolas Vasilache xferOp.getLoc(), xferOp.vector(), out, xferOp.indices(), 821512da70bSNicolas Vasilache xferOp.permutation_map(), ArrayAttr()); 8221ee11432SNicolas Vasilache 8231ee11432SNicolas Vasilache rewriter.eraseOp(copyOp); 8241ee11432SNicolas Vasilache rewriter.eraseOp(xferOp); 8251ee11432SNicolas Vasilache 8261ee11432SNicolas Vasilache return success(); 8271ee11432SNicolas Vasilache } 828