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. 251*0fcbbde2SNicolas 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); 308*0fcbbde2SNicolas 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 317*0fcbbde2SNicolas 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 356*0fcbbde2SNicolas Vasilache static Optional<VectorizedLinalgOp> vectorizeContraction(OpBuilder &builder, 357*0fcbbde2SNicolas 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 }; 384*0fcbbde2SNicolas 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 404*0fcbbde2SNicolas Vasilache Optional<VectorizedLinalgOp> mlir::linalg::vectorizeLinalgOp(OpBuilder &builder, 405*0fcbbde2SNicolas Vasilache Operation *op) { 406*0fcbbde2SNicolas Vasilache if (failed(vectorizeLinalgOpPrecondition(op))) 407*0fcbbde2SNicolas 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); 416*0fcbbde2SNicolas Vasilache VectorizedLinalgOp res; 417*0fcbbde2SNicolas Vasilache if (Value v = buildVectorWrite(builder, fillOp.value(), fillOp.output())) 418*0fcbbde2SNicolas Vasilache res.tensorResults.push_back(v); 419*0fcbbde2SNicolas 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()); 428*0fcbbde2SNicolas Vasilache VectorizedLinalgOp res; 429*0fcbbde2SNicolas Vasilache if (Value v = buildVectorWrite(builder, vector, copyOp.output())) 430*0fcbbde2SNicolas Vasilache res.tensorResults.push_back(v); 431*0fcbbde2SNicolas Vasilache return res; 432e4a503a2SNicolas Vasilache } 433e4a503a2SNicolas Vasilache if (isElementwise(op)) { 434e4a503a2SNicolas Vasilache LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 435*0fcbbde2SNicolas Vasilache << "Vectorize linalg op as a generic: " << *op); 436*0fcbbde2SNicolas Vasilache return vectorizeAsLinalgGeneric(builder, cast<LinalgOp>(op)); 437e4a503a2SNicolas Vasilache } 438e4a503a2SNicolas Vasilache 439*0fcbbde2SNicolas Vasilache // TODO: as soon as Copy and FillOp. get a region builder, replace all the 440*0fcbbde2SNicolas Vasilache // above by: 441*0fcbbde2SNicolas Vasilache // if (isa<FillOp, CopyOp>(op) || isElementwise(op)) { 442*0fcbbde2SNicolas Vasilache // LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE "]: " 443*0fcbbde2SNicolas Vasilache // << "Vectorize linalg op as a generic: " << *op); 444*0fcbbde2SNicolas Vasilache // return vectorizeAsLinalgGeneric(builder, cast<LinalgOp>(op)); 445*0fcbbde2SNicolas Vasilache // } 446*0fcbbde2SNicolas Vasilache 447*0fcbbde2SNicolas Vasilache return vectorizeContraction(builder, cast<LinalgOp>(op)); 448e4a503a2SNicolas Vasilache } 449e4a503a2SNicolas Vasilache 450f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 451f245b7adSNicolas Vasilache // Misc. conv vectorization patterns. 452f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 453f245b7adSNicolas Vasilache // TODO: cleanup all this. 454f245b7adSNicolas Vasilache template <class ConvOp, int N> 455f245b7adSNicolas Vasilache LogicalResult ConvOpVectorization<ConvOp, N>::matchAndRewrite( 456f245b7adSNicolas Vasilache ConvOp op, PatternRewriter &rewriter) const { 457f245b7adSNicolas Vasilache Location loc = op.getLoc(); 458f245b7adSNicolas Vasilache MLIRContext *context = op.getContext(); 459f245b7adSNicolas Vasilache edsc::ScopedContext scope(rewriter, loc); 460f245b7adSNicolas Vasilache 461f245b7adSNicolas Vasilache ShapedType inShapeType = op.getInputShapedType(0); 462f245b7adSNicolas Vasilache ShapedType kShapeType = op.getInputShapedType(1); 463f245b7adSNicolas Vasilache 464f245b7adSNicolas Vasilache ArrayRef<int64_t> inShape = inShapeType.getShape(); 465f245b7adSNicolas Vasilache ArrayRef<int64_t> kShape = kShapeType.getShape(); 466f245b7adSNicolas Vasilache 467f245b7adSNicolas Vasilache if (!inShapeType.hasStaticShape() || !kShapeType.hasStaticShape()) 468f245b7adSNicolas Vasilache return failure(); 469f245b7adSNicolas Vasilache 470f245b7adSNicolas Vasilache SmallVector<AffineExpr, 4> mapping; 471f245b7adSNicolas Vasilache SmallVector<int64_t, 4> vectorDims; 472f245b7adSNicolas Vasilache // Fail to apply when the size of not vectorized dimension is not 1. 473f245b7adSNicolas Vasilache for (unsigned i = 0; i < N; i++) { 474f245b7adSNicolas Vasilache if (!mask[i] && (inShape[i] != 1 || kShape[i] != 1)) 475f245b7adSNicolas Vasilache return failure(); 476f245b7adSNicolas Vasilache 477f245b7adSNicolas Vasilache if (mask[i] && inShape[i] != kShape[i]) 478f245b7adSNicolas Vasilache return failure(); 479f245b7adSNicolas Vasilache 480f245b7adSNicolas Vasilache if (mask[i]) { 481f245b7adSNicolas Vasilache mapping.push_back(getAffineDimExpr(i, context)); 482f245b7adSNicolas Vasilache vectorDims.push_back(inShape[i]); 483f245b7adSNicolas Vasilache } 484f245b7adSNicolas Vasilache } 485f245b7adSNicolas Vasilache 486f245b7adSNicolas Vasilache Value input = op.getInput(0); 487f245b7adSNicolas Vasilache Value kernel = op.getInput(1); 488f245b7adSNicolas Vasilache Value output = op.getOutputBuffer(0); 489f245b7adSNicolas Vasilache 490f245b7adSNicolas Vasilache unsigned rank = inShapeType.getRank(); 491f245b7adSNicolas Vasilache unsigned numDims = mapping.size(); 492f245b7adSNicolas Vasilache Type elemType = inShapeType.getElementType(); 493f245b7adSNicolas Vasilache 494f245b7adSNicolas Vasilache auto map = AffineMap::get(rank, 0, mapping, context); 495f245b7adSNicolas Vasilache SmallVector<Value, 4> zeros(rank, std_constant_index(0)); 496f245b7adSNicolas Vasilache auto vecType = VectorType::get(vectorDims, elemType); 497f245b7adSNicolas Vasilache 498f245b7adSNicolas Vasilache auto inputVec = vector_transfer_read(vecType, input, zeros, map); 499f245b7adSNicolas Vasilache auto kernelVec = vector_transfer_read(vecType, kernel, zeros, map); 500f245b7adSNicolas Vasilache 501f245b7adSNicolas Vasilache auto acc = std_constant(elemType, rewriter.getZeroAttr(elemType)); 502f245b7adSNicolas Vasilache 503f245b7adSNicolas Vasilache std::array<AffineMap, 3> indexingMaps{ 504f245b7adSNicolas Vasilache AffineMap::getMultiDimIdentityMap(numDims, context), 505f245b7adSNicolas Vasilache AffineMap::getMultiDimIdentityMap(numDims, context), 506f245b7adSNicolas Vasilache AffineMap::get(numDims, 0, {}, context)}; 507f245b7adSNicolas Vasilache 508f245b7adSNicolas Vasilache std::vector<StringRef> iteratorTypes(numDims, "reduction"); 509f245b7adSNicolas Vasilache 510f245b7adSNicolas Vasilache auto result = rewriter.create<vector::ContractionOp>( 511f245b7adSNicolas Vasilache loc, inputVec, kernelVec, acc, 512f245b7adSNicolas Vasilache rewriter.getAffineMapArrayAttr(indexingMaps), 513f245b7adSNicolas Vasilache rewriter.getStrArrayAttr(iteratorTypes)); 514f245b7adSNicolas Vasilache 515f245b7adSNicolas Vasilache rewriter.create<StoreOp>(loc, result, output, ValueRange(zeros)); 516f245b7adSNicolas Vasilache rewriter.eraseOp(op); 517f245b7adSNicolas Vasilache return success(); 518f245b7adSNicolas Vasilache } 519f245b7adSNicolas Vasilache 520f245b7adSNicolas Vasilache using ConvOpConst = ConvOpVectorization<ConvWOp, 1>; 521f245b7adSNicolas Vasilache 522f245b7adSNicolas Vasilache /// Inserts tiling, promotion and vectorization pattern for ConvOp 523f245b7adSNicolas Vasilache /// conversion into corresponding pattern lists. 524f245b7adSNicolas Vasilache template <typename ConvOp, unsigned N> 525f245b7adSNicolas Vasilache static void 526f245b7adSNicolas Vasilache populateVectorizationPatterns(OwningRewritePatternList &tilingPatterns, 527f245b7adSNicolas Vasilache OwningRewritePatternList &promotionPatterns, 528f245b7adSNicolas Vasilache OwningRewritePatternList &vectorizationPatterns, 529f245b7adSNicolas Vasilache ArrayRef<int64_t> tileSizes, 530f245b7adSNicolas Vasilache MLIRContext *context) { 531f245b7adSNicolas Vasilache if (tileSizes.size() < N) 532f245b7adSNicolas Vasilache return; 533f245b7adSNicolas Vasilache 534f245b7adSNicolas Vasilache constexpr static StringRef kTiledMarker = "TILED"; 535f245b7adSNicolas Vasilache constexpr static StringRef kPromotedMarker = "PROMOTED"; 536f245b7adSNicolas Vasilache tilingPatterns.insert<LinalgTilingPattern<ConvOp>>( 537f245b7adSNicolas Vasilache context, LinalgTilingOptions().setTileSizes(tileSizes), 538f245b7adSNicolas Vasilache LinalgTransformationFilter(ArrayRef<Identifier>{}, 539f245b7adSNicolas Vasilache Identifier::get(kTiledMarker, context))); 540f245b7adSNicolas Vasilache 541f245b7adSNicolas Vasilache promotionPatterns.insert<LinalgPromotionPattern<ConvOp>>( 542f245b7adSNicolas Vasilache context, LinalgPromotionOptions().setUseFullTileBuffersByDefault(true), 543f245b7adSNicolas Vasilache LinalgTransformationFilter(Identifier::get(kTiledMarker, context), 544f245b7adSNicolas Vasilache Identifier::get(kPromotedMarker, context))); 545f245b7adSNicolas Vasilache 546f245b7adSNicolas Vasilache SmallVector<bool, 4> mask(N); 547f245b7adSNicolas Vasilache int offset = tileSizes.size() - N; 548f245b7adSNicolas Vasilache std::transform(tileSizes.begin() + offset, tileSizes.end(), mask.begin(), 549f245b7adSNicolas Vasilache [](int64_t i) -> bool { return i > 1; }); 550f245b7adSNicolas Vasilache 551f245b7adSNicolas Vasilache vectorizationPatterns.insert<ConvOpVectorization<ConvOp, N>>(context, mask); 552f245b7adSNicolas Vasilache } 553f245b7adSNicolas Vasilache 554f245b7adSNicolas Vasilache void mlir::linalg::populateConvVectorizationPatterns( 555f245b7adSNicolas Vasilache MLIRContext *context, SmallVectorImpl<OwningRewritePatternList> &patterns, 556f245b7adSNicolas Vasilache ArrayRef<int64_t> tileSizes) { 557f245b7adSNicolas Vasilache OwningRewritePatternList tiling, promotion, vectorization; 558f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvWOp, 1>(tiling, promotion, vectorization, 559f245b7adSNicolas Vasilache tileSizes, context); 560f245b7adSNicolas Vasilache 561f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNWCOp, 3>(tiling, promotion, vectorization, 562f245b7adSNicolas Vasilache tileSizes, context); 563f245b7adSNicolas Vasilache 564f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNCWOp, 3>(tiling, promotion, vectorization, 565f245b7adSNicolas Vasilache tileSizes, context); 566f245b7adSNicolas Vasilache 567f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvHWOp, 2>(tiling, promotion, vectorization, 568f245b7adSNicolas Vasilache tileSizes, context); 569f245b7adSNicolas Vasilache 570f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNHWCOp, 4>(tiling, promotion, vectorization, 571f245b7adSNicolas Vasilache tileSizes, context); 572f245b7adSNicolas Vasilache 573f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNCHWOp, 4>(tiling, promotion, vectorization, 574f245b7adSNicolas Vasilache tileSizes, context); 575f245b7adSNicolas Vasilache 576f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvDHWOp, 3>(tiling, promotion, vectorization, 577f245b7adSNicolas Vasilache tileSizes, context); 578f245b7adSNicolas Vasilache 579f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNDHWCOp, 5>( 580f245b7adSNicolas Vasilache tiling, promotion, vectorization, tileSizes, context); 581f245b7adSNicolas Vasilache 582f245b7adSNicolas Vasilache populateVectorizationPatterns<ConvNCDHWOp, 5>( 583f245b7adSNicolas Vasilache tiling, promotion, vectorization, tileSizes, context); 584f245b7adSNicolas Vasilache 585f245b7adSNicolas Vasilache patterns.push_back(std::move(tiling)); 586f245b7adSNicolas Vasilache patterns.push_back(std::move(promotion)); 587f245b7adSNicolas Vasilache patterns.push_back(std::move(vectorization)); 588f245b7adSNicolas Vasilache } 589f245b7adSNicolas Vasilache 590f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 591f245b7adSNicolas Vasilache // Forwarding patterns 592f245b7adSNicolas Vasilache //----------------------------------------------------------------------------// 593f245b7adSNicolas Vasilache 5941ee11432SNicolas Vasilache /// Check whether there is any interleaved use of any `values` between `firstOp` 5951ee11432SNicolas Vasilache /// and `secondOp`. Conservatively return `true` if any op or value is in a 5961ee11432SNicolas Vasilache /// different block. 5971ee11432SNicolas Vasilache static bool mayExistInterleavedUses(Operation *firstOp, Operation *secondOp, 5981ee11432SNicolas Vasilache ValueRange values) { 5991ee11432SNicolas Vasilache if (firstOp->getBlock() != secondOp->getBlock() || 6001ee11432SNicolas Vasilache !firstOp->isBeforeInBlock(secondOp)) { 601e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 602e4a503a2SNicolas Vasilache << "interleavedUses precondition failed, firstOp: " 6031ee11432SNicolas Vasilache << *firstOp << ", second op: " << *secondOp); 6041ee11432SNicolas Vasilache return true; 6051ee11432SNicolas Vasilache } 6061ee11432SNicolas Vasilache for (auto v : values) { 6071ee11432SNicolas Vasilache for (auto &u : v.getUses()) { 6081ee11432SNicolas Vasilache Operation *owner = u.getOwner(); 6091ee11432SNicolas Vasilache if (owner == firstOp || owner == secondOp) 6101ee11432SNicolas Vasilache continue; 6111ee11432SNicolas Vasilache // TODO: this is too conservative, use dominance info in the future. 6121ee11432SNicolas Vasilache if (owner->getBlock() == firstOp->getBlock() && 6131ee11432SNicolas Vasilache (owner->isBeforeInBlock(firstOp) || secondOp->isBeforeInBlock(owner))) 6141ee11432SNicolas Vasilache continue; 6151ee11432SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() 616e4a503a2SNicolas Vasilache << "\n[" DEBUG_TYPE "]: " 617e4a503a2SNicolas Vasilache << " found interleaved op " << *owner 6181ee11432SNicolas Vasilache << ", firstOp: " << *firstOp << ", second op: " << *secondOp); 6191ee11432SNicolas Vasilache return true; 6201ee11432SNicolas Vasilache } 6211ee11432SNicolas Vasilache } 6221ee11432SNicolas Vasilache return false; 6231ee11432SNicolas Vasilache } 6241ee11432SNicolas Vasilache 6251ee11432SNicolas Vasilache /// Return the unique subview use of `v` if it is indeed unique, null otherwise. 6261ee11432SNicolas Vasilache static SubViewOp getSubViewUseIfUnique(Value v) { 6271ee11432SNicolas Vasilache SubViewOp subViewOp; 6281ee11432SNicolas Vasilache for (auto &u : v.getUses()) { 6291ee11432SNicolas Vasilache if (auto newSubViewOp = dyn_cast<SubViewOp>(u.getOwner())) { 6301ee11432SNicolas Vasilache if (subViewOp) 6311ee11432SNicolas Vasilache return SubViewOp(); 6321ee11432SNicolas Vasilache subViewOp = newSubViewOp; 6331ee11432SNicolas Vasilache } 6341ee11432SNicolas Vasilache } 6351ee11432SNicolas Vasilache return subViewOp; 6361ee11432SNicolas Vasilache } 6371ee11432SNicolas Vasilache 6381ee11432SNicolas Vasilache /// TODO: use interfaces, side-effects and aliasing analysis as appropriate, 6391ee11432SNicolas Vasilache /// when available. 6401ee11432SNicolas Vasilache LogicalResult LinalgCopyVTRForwardingPattern::matchAndRewrite( 6411ee11432SNicolas Vasilache vector::TransferReadOp xferOp, PatternRewriter &rewriter) const { 6421ee11432SNicolas Vasilache 6431ee11432SNicolas Vasilache // Transfer into `view`. 64426c8f908SThomas Raoux Value viewOrAlloc = xferOp.source(); 6451ee11432SNicolas Vasilache if (!viewOrAlloc.getDefiningOp<ViewOp>() && 6461ee11432SNicolas Vasilache !viewOrAlloc.getDefiningOp<AllocOp>()) 6471ee11432SNicolas Vasilache return failure(); 6481ee11432SNicolas Vasilache 649e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " << viewOrAlloc); 6501ee11432SNicolas Vasilache 6511ee11432SNicolas Vasilache // Ensure there is exactly one subview of `viewOrAlloc` defining `subView`. 6521ee11432SNicolas Vasilache SubViewOp subViewOp = getSubViewUseIfUnique(viewOrAlloc); 6531ee11432SNicolas Vasilache if (!subViewOp) 6541ee11432SNicolas Vasilache return failure(); 6551ee11432SNicolas Vasilache Value subView = subViewOp.getResult(); 656e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 657e4a503a2SNicolas Vasilache << "with subView " << subView); 6581ee11432SNicolas Vasilache 6591ee11432SNicolas Vasilache // Find the copy into `subView` without interleaved uses. 6601ee11432SNicolas Vasilache CopyOp copyOp; 6611ee11432SNicolas Vasilache for (auto &u : subView.getUses()) { 6621ee11432SNicolas Vasilache if (auto newCopyOp = dyn_cast<CopyOp>(u.getOwner())) { 6631ee11432SNicolas Vasilache if (newCopyOp.getOutputBuffer(0) != subView) 6641ee11432SNicolas Vasilache continue; 665e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 666e4a503a2SNicolas Vasilache << "copy candidate " << *newCopyOp); 6671ee11432SNicolas Vasilache if (mayExistInterleavedUses(newCopyOp, xferOp, {viewOrAlloc, subView})) 6681ee11432SNicolas Vasilache continue; 6691ee11432SNicolas Vasilache copyOp = newCopyOp; 6701ee11432SNicolas Vasilache break; 6711ee11432SNicolas Vasilache } 6721ee11432SNicolas Vasilache } 6731ee11432SNicolas Vasilache if (!copyOp) 6741ee11432SNicolas Vasilache return failure(); 675e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 676e4a503a2SNicolas Vasilache << "with copy " << *copyOp); 6771ee11432SNicolas Vasilache 6781ee11432SNicolas Vasilache // Find the fill into `viewOrAlloc` without interleaved uses before the copy. 6791ee11432SNicolas Vasilache FillOp maybeFillOp; 6801ee11432SNicolas Vasilache for (auto &u : viewOrAlloc.getUses()) { 6811ee11432SNicolas Vasilache if (auto newFillOp = dyn_cast<FillOp>(u.getOwner())) { 6821ee11432SNicolas Vasilache if (newFillOp.getOutputBuffer(0) != viewOrAlloc) 6831ee11432SNicolas Vasilache continue; 684e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 685e4a503a2SNicolas Vasilache << "fill candidate " << *newFillOp); 6861ee11432SNicolas Vasilache if (mayExistInterleavedUses(newFillOp, copyOp, {viewOrAlloc, subView})) 6871ee11432SNicolas Vasilache continue; 6881ee11432SNicolas Vasilache maybeFillOp = newFillOp; 6891ee11432SNicolas Vasilache break; 6901ee11432SNicolas Vasilache } 6911ee11432SNicolas Vasilache } 6921ee11432SNicolas Vasilache // Ensure padding matches. 6931ee11432SNicolas Vasilache if (maybeFillOp && xferOp.padding() != maybeFillOp.value()) 6941ee11432SNicolas Vasilache return failure(); 6951ee11432SNicolas Vasilache if (maybeFillOp) 696e4a503a2SNicolas Vasilache LLVM_DEBUG(llvm::dbgs() << "\n[" DEBUG_TYPE "]: " 697e4a503a2SNicolas Vasilache << "with maybeFillOp " << *maybeFillOp); 6981ee11432SNicolas Vasilache 6991ee11432SNicolas Vasilache // `in` is the subview that linalg.copy reads. Replace it. 7001ee11432SNicolas Vasilache Value in = copyOp.getInput(0); 7011ee11432SNicolas Vasilache 702512da70bSNicolas Vasilache // linalg.copy + linalg.fill can be used to create a padded local buffer. 703512da70bSNicolas Vasilache // The `masked` attribute is only valid on this padded buffer. 704512da70bSNicolas Vasilache // When forwarding to vector.transfer_read, the attribute must be reset 705512da70bSNicolas Vasilache // conservatively. 7061ee11432SNicolas Vasilache Value res = rewriter.create<vector::TransferReadOp>( 7071ee11432SNicolas Vasilache xferOp.getLoc(), xferOp.getVectorType(), in, xferOp.indices(), 708512da70bSNicolas Vasilache xferOp.permutation_map(), xferOp.padding(), ArrayAttr()); 7091ee11432SNicolas Vasilache 7101ee11432SNicolas Vasilache if (maybeFillOp) 7111ee11432SNicolas Vasilache rewriter.eraseOp(maybeFillOp); 7121ee11432SNicolas Vasilache rewriter.eraseOp(copyOp); 7131ee11432SNicolas Vasilache rewriter.replaceOp(xferOp, res); 7141ee11432SNicolas Vasilache 7151ee11432SNicolas Vasilache return success(); 7161ee11432SNicolas Vasilache } 7171ee11432SNicolas Vasilache 7181ee11432SNicolas Vasilache /// TODO: use interfaces, side-effects and aliasing analysis as appropriate, 7191ee11432SNicolas Vasilache /// when available. 7201ee11432SNicolas Vasilache LogicalResult LinalgCopyVTWForwardingPattern::matchAndRewrite( 7211ee11432SNicolas Vasilache vector::TransferWriteOp xferOp, PatternRewriter &rewriter) const { 7221ee11432SNicolas Vasilache // Transfer into `viewOrAlloc`. 72326c8f908SThomas Raoux Value viewOrAlloc = xferOp.source(); 7241ee11432SNicolas Vasilache if (!viewOrAlloc.getDefiningOp<ViewOp>() && 7251ee11432SNicolas Vasilache !viewOrAlloc.getDefiningOp<AllocOp>()) 7261ee11432SNicolas Vasilache return failure(); 7271ee11432SNicolas Vasilache 7281ee11432SNicolas Vasilache // Ensure there is exactly one subview of `viewOrAlloc` defining `subView`. 7291ee11432SNicolas Vasilache SubViewOp subViewOp = getSubViewUseIfUnique(viewOrAlloc); 7301ee11432SNicolas Vasilache if (!subViewOp) 7311ee11432SNicolas Vasilache return failure(); 7321ee11432SNicolas Vasilache Value subView = subViewOp.getResult(); 7331ee11432SNicolas Vasilache 7341ee11432SNicolas Vasilache // Find the copy from `subView` without interleaved uses. 7351ee11432SNicolas Vasilache CopyOp copyOp; 7361ee11432SNicolas Vasilache for (auto &u : subViewOp.getResult().getUses()) { 7371ee11432SNicolas Vasilache if (auto newCopyOp = dyn_cast<CopyOp>(u.getOwner())) { 7381ee11432SNicolas Vasilache if (newCopyOp.getInput(0) != subView) 7391ee11432SNicolas Vasilache continue; 7401ee11432SNicolas Vasilache if (mayExistInterleavedUses(xferOp, newCopyOp, {viewOrAlloc, subView})) 7411ee11432SNicolas Vasilache continue; 7421ee11432SNicolas Vasilache copyOp = newCopyOp; 7431ee11432SNicolas Vasilache break; 7441ee11432SNicolas Vasilache } 7451ee11432SNicolas Vasilache } 7461ee11432SNicolas Vasilache if (!copyOp) 7471ee11432SNicolas Vasilache return failure(); 7481ee11432SNicolas Vasilache 7491ee11432SNicolas Vasilache // `out` is the subview copied into that we replace. 7501ee11432SNicolas Vasilache Value out = copyOp.getOutputBuffer(0); 7511ee11432SNicolas Vasilache 7521ee11432SNicolas Vasilache // Forward vector.transfer into copy. 753512da70bSNicolas Vasilache // linalg.copy + linalg.fill can be used to create a padded local buffer. 754512da70bSNicolas Vasilache // The `masked` attribute is only valid on this padded buffer. 755512da70bSNicolas Vasilache // When forwarding to vector.transfer_write, the attribute must be reset 756512da70bSNicolas Vasilache // conservatively. 7571ee11432SNicolas Vasilache rewriter.create<vector::TransferWriteOp>( 7581ee11432SNicolas Vasilache xferOp.getLoc(), xferOp.vector(), out, xferOp.indices(), 759512da70bSNicolas Vasilache xferOp.permutation_map(), ArrayAttr()); 7601ee11432SNicolas Vasilache 7611ee11432SNicolas Vasilache rewriter.eraseOp(copyOp); 7621ee11432SNicolas Vasilache rewriter.eraseOp(xferOp); 7631ee11432SNicolas Vasilache 7641ee11432SNicolas Vasilache return success(); 7651ee11432SNicolas Vasilache } 766