blob: 1d2b66ee634e08bf002d9edf5ebaa7bc19dd6198 [file]
// Copyright 2020 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree-dialects/Dialect/LinalgTransform/Passes.h"
#include "iree/compiler/Codegen/Common/CPU/Passes.h"
#include "iree/compiler/Codegen/Common/PassUtils.h"
#include "iree/compiler/Codegen/Common/Passes.h"
#include "iree/compiler/Codegen/Common/TileSizeSelection.h"
#include "iree/compiler/Codegen/LLVMCPU/Passes.h"
#include "iree/compiler/Dialect/LinalgExt/Transforms/Passes.h"
#include "iree/compiler/Dialect/Util/Transforms/Passes.h"
#include "iree/compiler/Utils/PassUtils.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/CommandLine.h"
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
#include "mlir/Conversion/ArithToArmSME/ArithToArmSME.h"
#include "mlir/Conversion/ArmSMEToLLVM/ArmSMEToLLVM.h"
#include "mlir/Conversion/ArmSMEToSCF/ArmSMEToSCF.h"
#include "mlir/Conversion/ComplexToStandard/ComplexToStandard.h"
#include "mlir/Conversion/ReconcileUnrealizedCasts/ReconcileUnrealizedCasts.h"
#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
#include "mlir/Conversion/VectorToArmSME/VectorToArmSME.h"
#include "mlir/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.h"
#include "mlir/Dialect/Arith/Transforms/Passes.h"
#include "mlir/Dialect/ArmSME/Transforms/Passes.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Linalg/Passes.h"
#include "mlir/Dialect/MemRef/Transforms/Passes.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/Passes.h"
#define DEBUG_TYPE "iree-llvmcpu-pass-pipelines"
namespace mlir::iree_compiler {
/// Command line options used purely for development purposes. Not to be relied
/// on in any way.
static llvm::cl::opt<bool> clFailOnOutOfBoundsStackAllocation(
"iree-llvmcpu-fail-on-out-of-bounds-stack-allocation",
llvm::cl::desc("fail if the upper bound of dynamic stack allocation cannot "
"be solved"),
llvm::cl::init(true));
static llvm::cl::opt<bool> clFailOnLargeVector(
"iree-llvmcpu-fail-on-large-vector",
llvm::cl::desc("fail if there are operations with large vectors"),
llvm::cl::init(true));
static llvm::cl::opt<bool> clCheckLinalgVectorization(
"iree-llvmcpu-check-linalg-vectorization",
llvm::cl::desc(
"Runs the pass to check if all the Linalg ops are vectorized"),
llvm::cl::init(false));
static llvm::cl::opt<bool> clUseFastMinMaxOps(
"iree-llvmcpu-use-fast-min-max-ops",
llvm::cl::desc(
"Use `arith.minf/maxf` instead of `arith.minimumf/maximumf` ops"),
llvm::cl::init(false));
static llvm::cl::opt<bool> clEnableReassociateFpReductions(
"iree-llvmcpu-reassociate-fp-reductions",
llvm::cl::desc("Enables reassociation for FP reductions"),
llvm::cl::init(true));
static llvm::cl::opt<bool> clSkipIntermediateRoundings(
"iree-llvmcpu-skip-intermediate-roundings",
llvm::cl::desc(
"Allow skipping intermediate roundings. For example, in f16 matmul "
"kernels on targets with only f32 arithmetic, we have to perform each "
"multiply-accumulate in f32, and if this flag is false, then we have "
"to round those f32 accumulators to the nearest f16 every time, which "
"is slow."),
llvm::cl::init(true));
static llvm::cl::opt<bool> clInstrumentMemoryAccesses{
"iree-llvmcpu-instrument-memory-accesses",
llvm::cl::desc("Instruments memory accesses in dispatches when dispatch "
"instrumentation is enabled."),
llvm::cl::init(false)};
static llvm::cl::opt<bool> clUseSoftmaxInterFusion(
"iree-llvmcpu-use-decompose-softmax-fuse",
llvm::cl::desc("Enables inter-pass fusion for the DecomposeSoftmax pass."),
llvm::cl::init(true));
static llvm::cl::opt<bool> clEnableVectorContractCustomKernels(
"iree-llvmcpu-enable-vector-contract-custom-kernels",
llvm::cl::desc("Enables vector contract custom kernels for "
"LLVMCPUMmt4dVectorLowering pass."),
llvm::cl::init(false));
static llvm::cl::opt<bool> clTileDispatchUsingForall(
"iree-llvmcpu-tile-dispatch-using-forall",
llvm::cl::desc("Enable tile and distribute to workgroups using scf.forall"),
llvm::cl::init(true));
// By default, IREE does not enable the Armv9-A streaming SVE mode in the
// presence of scalable vectors (even when using `+sme`), as currently there's
// no cost model of when it could be beneficial. This flag will effectively make
// IREE/LLVM switch from SVE to SSVE in dispatch regions with supported
// scalable vector operations.
static llvm::cl::opt<bool> clForceArmStreaming(
"iree-llvmcpu-force-arm-streaming",
llvm::cl::desc(
"Enables Armv9-A streaming SVE mode for any dispatch region that "
"contains supported scalable vector operations (i.e., use SSVE rather "
"than SVE). Requires the +sme feature flag."),
llvm::cl::init(false));
// TODO: Enable `TileDispatchUsingForall` for every pipeline.
static void addTileAndDistributePasses(OpPassManager &funcPassManager) {
if (clTileDispatchUsingForall) {
funcPassManager.addPass(
createTileAndDistributeToWorkgroupsUsingForallOpPass());
} else {
funcPassManager.addPass(createTileAndDistributeToWorkgroupsPass());
funcPassManager.addPass(createCSEPass());
funcPassManager.addPass(createConvertToDestinationPassingStylePass());
funcPassManager.addPass(createFoldAffineMinInDistributedLoopsPass());
}
funcPassManager.addPass(createConfigTrackingCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
funcPassManager.addPass(createFuseTensorPadWithConsumerPass());
funcPassManager.addPass(createConcretizePadResultShapePass());
}
//===---------------------------------------------------------------------===//
// Codegen configuration verifications.
//===---------------------------------------------------------------------===//
static bool isValidInterchange(ArrayRef<int64_t> interchange, int numLoops) {
if (interchange.empty())
return true;
llvm::SmallDenseSet<int64_t> s;
s.insert(interchange.begin(), interchange.end());
for (int i = 0; i < numLoops; ++i) {
if (!s.contains(i))
return false;
}
return true;
}
LogicalResult verifyDoubleTilingExpertPassPipelineConfig(
Operation *op, TilingConfig &tilingConfig,
IREE::Codegen::TranslationInfoAttr translationInfo,
ArrayRef<int64_t> workgroupSize) {
if (!workgroupSize.empty()) {
return op->emitOpError(
"expected workgroup size to be empty for CPU pipelines");
}
// Verify that the translation info is using the right pipeline.
if (translationInfo.getDispatchLoweringPassPipeline() !=
IREE::Codegen::DispatchLoweringPassPipeline::CPUDoubleTilingExpert) {
return op->emitOpError("expected pipeline in translation_info to be ")
<< stringifyEnum(IREE::Codegen::DispatchLoweringPassPipeline::
CPUDoubleTilingExpert);
}
if (tilingConfig.getNumTilingLevels() == 6) {
// TODO: update verification.
return success();
}
if (tilingConfig.getNumTilingLevels() != 4) {
return op->emitOpError("expected four tiling levels, got ")
<< tilingConfig.getNumTilingLevels();
}
auto interfaceOp = dyn_cast_or_null<TilingInterface>(op);
if (interfaceOp) {
llvm::SmallDenseSet<unsigned> pLoopsSet;
for (auto [index, iteratorType] :
llvm::enumerate(interfaceOp.getLoopIteratorTypes())) {
if (iteratorType == utils::IteratorType::parallel) {
pLoopsSet.insert(index);
}
}
SmallVector<int64_t> secondLevelTileSizes;
std::tie(secondLevelTileSizes, std::ignore) =
tilingConfig.getVectorCommonParallelSizes();
for (auto [index, tileSize] : llvm::enumerate(secondLevelTileSizes)) {
if (tileSize != 0 && !pLoopsSet.contains(index)) {
return op->emitOpError(
"expected only parallel dims to be set in the second tiling "
"level, got ")
<< index << "-th tile size set";
}
}
SmallVector<int64_t> thirdLevelTileSizes;
std::tie(thirdLevelTileSizes, std::ignore) =
tilingConfig.getVectorReductionSizes();
for (auto [index, tileSize] : llvm::enumerate(thirdLevelTileSizes)) {
if (tileSize != 0 && pLoopsSet.contains(index)) {
return op->emitOpError(
"expected only reduction dims to be set in the third tiling "
"level, got ")
<< index << "-th tile size set";
}
}
}
// Verify interchange
auto tileSizesForLevel = tilingConfig.getTileSizes();
for (int level = 0; level < tilingConfig.getNumTilingLevels(); level++) {
auto interchange = tilingConfig.getTileInterchangeSizes(level);
auto &tileSizes = tileSizesForLevel[level];
if (!isValidInterchange(interchange, tileSizes.size())) {
return op->emitOpError("expected [0, ")
<< tileSizes.size() << ") to be set exactly once in interchange #"
<< level;
}
}
// Verify that native vector size is empty.
SmallVector<int64_t> nativeVectorSize = tilingConfig.getNativeVectorSizes();
if (!nativeVectorSize.empty()) {
return op->emitOpError("native_vector_size must be empty");
}
return success();
}
LogicalResult verifyConvTileAndDecomposeExpertConfig(
Operation *op, TilingConfig &tilingConfig,
IREE::Codegen::TranslationInfoAttr translationInfo,
ArrayRef<int64_t> workgroupSize) {
if (!isa<linalg::ConvolutionOpInterface>(op))
return success();
if (tilingConfig.getNumTilingLevels() == 6) {
// TODO: update verification.
return success();
}
if (tilingConfig.getNumTilingLevels() != 4) {
return op->emitOpError("expected four tiling levels, got ")
<< tilingConfig.getNumTilingLevels();
}
linalg::LinalgOp linalgOp = cast<linalg::LinalgOp>(op);
SmallVector<int64_t> shape = linalgOp.getStaticLoopRanges();
for (auto sizes : tilingConfig.getTileSizes()) {
for (auto [i, size] : llvm::enumerate(sizes)) {
if (size == 1)
shape[i] = 1;
if (shape[i] == -1 || size == 0)
continue;
if (shape[i] % size != 0) {
shape[i] = -1;
} else {
shape[i] = size;
}
}
}
int64_t khSize, kwSize, ohSize, owSize;
auto isSizeExtracted =
TypeSwitch<Operation *, LogicalResult>(op)
.Case<linalg::Conv2DNhwcHwcfOp, linalg::DepthwiseConv2DNhwcHwcOp,
linalg::PoolingNhwcSumOp, linalg::PoolingNhwcMaxOp,
linalg::PoolingNhwcMaxUnsignedOp, linalg::PoolingNhwcMinOp,
linalg::PoolingNhwcMinUnsignedOp>([&](auto) {
// Shape: N, OH, OW, OC, KH, KW, (IC)
khSize = shape[4];
kwSize = shape[5];
ohSize = shape[1];
owSize = shape[2];
return success();
})
.Case<linalg::Conv2DNchwFchwOp>([&](auto) {
// Shape: N, OC, OH, OW, (IC), KH, KW
khSize = shape[5];
kwSize = shape[6];
ohSize = shape[2];
owSize = shape[3];
return success();
})
.Case<linalg::PoolingNchwSumOp, linalg::PoolingNchwMaxOp>([&](auto) {
// Shape: N, OC, OH, OW, KH, KW
khSize = shape[4];
kwSize = shape[5];
ohSize = shape[2];
owSize = shape[3];
return success();
})
.Default([&](auto) { return failure(); });
if (failed(isSizeExtracted)) {
return op->emitOpError("unsupported conv types");
}
bool removeH = (khSize == 1 && ohSize == 1);
bool removeW = (kwSize == 1 && owSize == 1);
if (!removeH && !removeW) {
return op->emitOpError("can't decompose the conv op");
}
return success();
}
//===---------------------------------------------------------------------===//
// Codegen pipelines.
//===---------------------------------------------------------------------===//
void buildLLVMCPUVectorLoweringPipeline(
OpPassManager &funcPassManager,
const LLVMCPUVectorLoweringPassOptions &options) {
funcPassManager.addPass(createDropVectorUnitDimsPass());
funcPassManager.addPass(createLLVMCPUVirtualVectorLoweringPass(
LLVMCPUVirtualVectorLoweringPassOptions{options.splitVectorTransfersTo,
options.enableArmI8mm}));
// Make sure we remove redundant vector ops (e.g., vector tranposes) before we
// lower them and can't be optimized away anymore.
funcPassManager.addPass(createCanonicalizerPass());
LLVMCPUVectorTransferLoweringPassOptions transferLoweringOptions{};
if (!options.enableArmSME) {
// The ArmSME dialect has its own (more specific) lowerings for scalable
// vectors that occur later in the pipeline, so only enable the general
// lowerings if SME is not available.
transferLoweringOptions.enableScalableLowerings = true;
}
funcPassManager.addPass(
createLLVMCPUVectorTransferLoweringPass(transferLoweringOptions));
funcPassManager.addPass(createLLVMCPUVectorTransposeLoweringPass(
LLVMCPUVectorTransposeLoweringPassOptions{
options.lowerVectorTransposeToAVX2}));
// Potentially removes shape_cast and broadcast on unit dims before shape_cast
// lowering.
funcPassManager.addPass(createCanonicalizerPass());
// 'vector.shape_cast' are very expensive operations that are even generated
// by some of the lowerings above (e.g., transpose lowering). There are
// chances to cancel them out if they are not lowered too early so we lower
// them at the very end of the pass.
funcPassManager.addPass(createLLVMCPUVectorShapeCastLoweringPass());
}
void addCPUBufferOpsTileAndVectorizePipeline(
OpPassManager &funcPassManager, TilingConfig &tilingConfig,
LLVMCPUPipelineOptions &pipelineOpt) {
addTileAndDistributePasses(funcPassManager);
// Skip tiling reduction loops because this is expected to apply on copy ops
// only.
funcPassManager.addPass(
createLLVMCPUTilePass(tilingConfig.getVectorCommonParallelLevel()));
funcPassManager.addPass(createLLVMCPUPeelPass());
{
GenericVectorizationPassOptions options;
options.useConfiguredVectorSizes = pipelineOpt.useConfiguredVectorSizes;
options.enableVectorMasking = pipelineOpt.enableVectorMasking;
options.vectorizeGatherAccesses = true;
funcPassManager.addPass(createGenericVectorizationPass(options));
funcPassManager.addPass(createOptimizeTensorInsertExtractSlicesPass());
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
if (clFailOnLargeVector) {
funcPassManager.addPass(createLLVMCPUVerifyVectorSizeLegalityPass());
}
}
// Run IREE specific passes before vector lowering expert.
funcPassManager.addPass(createRemoveSingleIterationLoopPass());
{
LLVMCPUVectorLoweringPassOptions options;
options.lowerVectorTransposeToAVX2 = pipelineOpt.lowerToAVX2;
options.splitVectorTransfersTo = "linalg-copy";
options.enableArmI8mm = pipelineOpt.enableAArch64I8mm;
options.enableArmSME = pipelineOpt.enableAArch64SME;
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
}
}
void addMultiTilingExpertPassPipeline(OpPassManager &funcPassManager,
TilingConfig &tilingConfig,
LLVMCPUPipelineOptions &pipelineOpt) {
addTileAndDistributePasses(funcPassManager);
SmallVector<int64_t> allFusableLevels(tilingConfig.getFusableLevels());
// Apply tile and fuse to all the non-distribution fusable levels. Skip
// distribution level as that level has been fused already.
if (allFusableLevels.size() > 1) {
llvm::SmallSetVector<int64_t, 4> fusableLevels(allFusableLevels.begin(),
allFusableLevels.end());
for (int i = 0; i < tilingConfig.getNumTilingLevels(); ++i) {
if (i == tilingConfig.getDistributionLevel())
continue;
if (fusableLevels.contains(i)) {
funcPassManager.addPass(createLLVMCPUTileAndFusePass(i));
funcPassManager.addPass(createFuseTensorPadWithConsumerPass());
funcPassManager.addPass(createConcretizePadResultShapePass());
continue;
}
if (i == tilingConfig.getVectorReductionLevel()) {
// Run SplitReductionPass before the final reduction Fuse pass, because
// SplitReductionPass takes care of banked-tiling.
funcPassManager.addPass(
createLLVMCPUSplitReductionPass(clEnableReassociateFpReductions));
funcPassManager.addPass(createLLVMCPUTileRootAndFuseInputOperands(i));
continue;
}
funcPassManager.addPass(createLLVMCPUTileRootAndFuseInputOperands(i));
}
}
if (pipelineOpt.enablePeeling) {
funcPassManager.addPass(createLLVMCPUPeelPass());
}
if (pipelineOpt.enableAArch64SME) {
funcPassManager.addPass(createLLVMCPU2DScalableTo1DScalablePass());
}
{
funcPassManager.addPass(createTensorToVectorVectorizePadPass());
if (pipelineOpt.decomposePackUnPackOps) {
funcPassManager.addPass(createDecomposePackUnPackOpsPass());
funcPassManager.addPass(createConfigTrackingCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
}
GenericVectorizationPassOptions options;
options.useConfiguredVectorSizes = pipelineOpt.useConfiguredVectorSizes;
options.enableVectorMasking = pipelineOpt.enableVectorMasking;
options.vectorizePadding = true;
options.vectorizeGatherAccesses = true;
funcPassManager.addPass(createGenericVectorizationPass(options));
funcPassManager.addPass(createOptimizeTensorInsertExtractSlicesPass());
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
if (clFailOnLargeVector) {
funcPassManager.addPass(createLLVMCPUVerifyVectorSizeLegalityPass());
}
}
addCPUBufferizePasses(funcPassManager);
// Run IREE specific passes before vector lowering expert.
funcPassManager.addPass(createRemoveSingleIterationLoopPass());
{
LLVMCPUVectorLoweringPassOptions options;
options.lowerVectorTransposeToAVX2 = pipelineOpt.lowerToAVX2;
options.splitVectorTransfersTo = "linalg-copy";
options.enableArmI8mm = pipelineOpt.enableAArch64I8mm;
options.enableArmSME = pipelineOpt.enableAArch64SME;
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
}
}
void addConvTileAndDecomposeExpertPassPipeline(
OpPassManager &funcPassManager, TilingConfig &tilingConfig,
LLVMCPUPipelineOptions &pipelineOpt) {
addTileAndDistributePasses(funcPassManager);
// Run LLVMTileAndFuse firstly in case that we have fill + conv + generic
// ops. At this stage, we do not apply vectorization. The reduction dim won't
// get tiled if the case is conv + generic op. In this case, we have to tile
// along reduction dim again, which needs them to be Linalg ops form.
funcPassManager.addPass(createLLVMCPUTileAndFusePass(
tilingConfig.getVectorCommonParallelLevel()));
funcPassManager.addPass(createFuseTensorPadWithConsumerPass());
funcPassManager.addPass(createConcretizePadResultShapePass());
funcPassManager.addPass(createLLVMCPUTileRootAndFuseInputOperands(
tilingConfig.getVectorReductionLevel()));
funcPassManager.addPass(
createLLVMCPUTileAndFusePass(tilingConfig.getVectorInnerParallelLevel()));
funcPassManager.addPass(createDecomposeConvolutionToLowerDimOpsPass());
funcPassManager.addPass(createFuseTensorPadWithConsumerPass());
funcPassManager.addPass(createConcretizePadResultShapePass());
if (pipelineOpt.enablePeeling) {
funcPassManager.addPass(createLLVMCPUPeelPass());
}
{
funcPassManager.addPass(createTensorToVectorVectorizePadPass());
GenericVectorizationPassOptions options;
options.useConfiguredVectorSizes = pipelineOpt.useConfiguredVectorSizes;
options.enableVectorMasking = pipelineOpt.enableVectorMasking;
options.vectorizePadding = true;
options.vectorizeGatherAccesses = true;
funcPassManager.addPass(createGenericVectorizationPass(options));
funcPassManager.addPass(createOptimizeTensorInsertExtractSlicesPass());
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
if (clFailOnLargeVector) {
funcPassManager.addPass(createLLVMCPUVerifyVectorSizeLegalityPass());
}
}
// Eliminate redundant transfer_read/write to avoid stack allocations.
funcPassManager.addPass(createOptimizeVectorTransferPass(
OptimizeVectorTransferPassOptions{/*flatten=*/true}));
addCPUBufferizePasses(funcPassManager);
// Run IREE specific passes before vector lowering expert.
funcPassManager.addPass(createRemoveSingleIterationLoopPass());
{
LLVMCPUVectorLoweringPassOptions options;
options.lowerVectorTransposeToAVX2 = pipelineOpt.lowerToAVX2;
options.splitVectorTransfersTo = "shuffle";
options.enableArmI8mm = pipelineOpt.enableAArch64I8mm;
options.enableArmSME = pipelineOpt.enableAArch64SME;
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
}
}
void addMmt4dTilingExpertPassPipeline(OpPassManager &funcPassManager,
TilingConfig &tilingConfig,
LLVMCPUPipelineOptions &pipelineOpt) {
addTileAndDistributePasses(funcPassManager);
funcPassManager.addPass(createLLVMCPUTileRootAndFuseProducerConsumer(
static_cast<int64_t>(tilingConfig.getVectorCommonParallelLevel())));
// The below two passes are nop if the "mmt4d" is explicitly excluded in the
// ukernels attribute.
funcPassManager.addPass(createCPUPrepareUkernelsPass());
funcPassManager.addPass(
createCPULowerToUKernelsPass(clSkipIntermediateRoundings));
funcPassManager.addPass(createLLVMCPUTileRootAndFuseInputOperands(
static_cast<int64_t>(tilingConfig.getVectorReductionLevel())));
{
GenericVectorizationPassOptions options;
options.enableVectorMasking = pipelineOpt.enableVectorMasking;
options.vectorizePadding = true;
options.vectorizeGatherAccesses = true;
funcPassManager.addPass(createGenericVectorizationPass(options));
funcPassManager.addPass(createOptimizeTensorInsertExtractSlicesPass());
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
if (clFailOnLargeVector) {
funcPassManager.addPass(createLLVMCPUVerifyVectorSizeLegalityPass());
}
}
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
addCPUBufferizePasses(funcPassManager);
// Vector lowering of Mmt4d.
funcPassManager.addPass(createLLVMCPUMmt4dVectorLoweringPass(
LLVMCPUMmt4dVectorLoweringPassOptions{
clEnableVectorContractCustomKernels}));
// Generic vector lowering.
LLVMCPUVectorLoweringPassOptions options;
options.lowerVectorTransposeToAVX2 = pipelineOpt.lowerToAVX2;
options.splitVectorTransfersTo = "linalg-copy";
options.enableArmI8mm = pipelineOpt.enableAArch64I8mm;
options.enableArmSME = pipelineOpt.enableAArch64SME;
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
}
void addCPUDataTilingPipeline(OpPassManager &funcPassManager,
TilingConfig &tilingConfig,
LLVMCPUPipelineOptions &pipelineOpt) {
addTileAndDistributePasses(funcPassManager);
// The below two passes are nop if pack/unpack is not specified in ukernels
// attribute. By default, they are disabled.
funcPassManager.addPass(createCPUPrepareUkernelsPass());
funcPassManager.addPass(
createCPULowerToUKernelsPass(clSkipIntermediateRoundings));
funcPassManager.addPass(
createLLVMCPUTilePass(tilingConfig.getVectorCommonParallelLevel()));
if (pipelineOpt.decomposePackUnPackOps) {
funcPassManager.addPass(createDecomposePackUnPackOpsPass());
}
{
GenericVectorizationPassOptions options;
options.useConfiguredVectorSizes = pipelineOpt.useConfiguredVectorSizes;
options.vectorizePadding = true;
options.enableVectorMasking = pipelineOpt.enableVectorMasking;
funcPassManager.addPass(createGenericVectorizationPass(options));
funcPassManager.addPass(createOptimizeTensorInsertExtractSlicesPass());
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
if (clFailOnLargeVector) {
funcPassManager.addPass(createLLVMCPUVerifyVectorSizeLegalityPass());
}
}
addCPUBufferizePasses(funcPassManager);
{
LLVMCPUVectorLoweringPassOptions options;
options.lowerVectorTransposeToAVX2 = pipelineOpt.lowerToAVX2;
options.splitVectorTransfersTo = "linalg-copy";
options.enableArmI8mm = pipelineOpt.enableAArch64I8mm;
options.enableArmSME = pipelineOpt.enableAArch64SME;
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
}
}
void addCPULinalgExtTileAndVectorizePipeline(
OpPassManager &funcPassManager, TilingConfig &tilingConfig,
LLVMCPUPipelineOptions &pipelineOpt) {
addTileAndDistributePasses(funcPassManager);
funcPassManager.addPass(
createLLVMCPUTilePass(tilingConfig.getVectorCommonParallelLevel()));
// TODO: Remove the pass once we have PartialReductionOpInterface implemented
// for AttentionOp.
funcPassManager.addPass(
IREE::LinalgExt::createConvertAttentionToOnlineAttentionPass());
funcPassManager.addPass(
createLLVMCPUTilePass(tilingConfig.getVectorReductionLevel()));
funcPassManager.addPass(
IREE::LinalgExt::createDecomposeWinogradTransformPass());
funcPassManager.addPass(IREE::LinalgExt::createDecomposeAttentionPass());
{
GenericVectorizationPassOptions options;
options.useConfiguredVectorSizes = pipelineOpt.useConfiguredVectorSizes;
options.enableVectorMasking = pipelineOpt.enableVectorMasking;
funcPassManager.addPass(createGenericVectorizationPass(options));
funcPassManager.addPass(createOptimizeTensorInsertExtractSlicesPass());
funcPassManager.addPass(createCanonicalizerPass());
funcPassManager.addPass(createCSEPass());
if (clFailOnLargeVector) {
funcPassManager.addPass(createLLVMCPUVerifyVectorSizeLegalityPass());
}
}
addCPUBufferizePasses(funcPassManager);
{
LLVMCPUVectorLoweringPassOptions options;
options.lowerVectorTransposeToAVX2 = pipelineOpt.lowerToAVX2;
options.splitVectorTransfersTo = "linalg-copy";
options.enableArmI8mm = pipelineOpt.enableAArch64I8mm;
options.enableArmSME = pipelineOpt.enableAArch64SME;
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
}
}
void addCPUDefaultPassPipeline(OpPassManager &funcPassManager,
FailureOr<TilingConfig> &tilingConfig) {
if (succeeded(tilingConfig) &&
tilingConfig.value().getNumTilingLevels() > 1) {
addTileAndDistributePasses(funcPassManager);
funcPassManager.addPass(createLLVMCPUTileAndFusePass(
tilingConfig.value().getVectorCommonParallelLevel()));
}
addCPUBufferizePasses(funcPassManager);
}
static void addLowerToLLVMPasses(OpPassManager &modulePassManager,
bool enableAArch64SME) {
// TODO: Remove the following pass and plumb support for #hal.descriptor_type
// memory space through the stack.
FunctionLikeNest(modulePassManager)
.addPass(createEraseHALDescriptorTypeFromMemRefPass);
// Lower `ukernel.*` ops to function calls
modulePassManager.addPass(createLowerUKernelOpsToCallsPass());
FunctionLikeNest(modulePassManager)
// LinalgExt -> SCF
.addPass(IREE::LinalgExt::createLinalgExtToLoopsPass)
// Linalg -> SCF
.addPass(createMemrefCopyToLinalgPass)
.addPredicatedPass(clCheckLinalgVectorization,
createLLVMCPUEmitVectorizationRemarksPass)
.addPass(createConvertLinalgToLoopsPass)
.addPass(createConvertBf16ArithToF32Pass)
.addPass(createConvertBf16ToUInt16BuffersPass)
.addPass(createCanonicalizerPass)
.addPass(createCSEPass);
// Handled tensor-type constants.
addConstantBufferizePasses(modulePassManager);
FunctionLikeNest(modulePassManager)
.addPass(createFoldTensorExtractOpPass)
// Handle complex operation conversion.
.addPass(createConvertComplexToStandardPass)
// math dialect elementry functions -> polynomial form.
.addPass(createPolynomialApproximationPass)
.addPass(createHoistStaticallyBoundAllocationsPass)
// Use `arith.minf/maxf` instead of `arith.minimumf/maximumf`.
.addPredicatedPass(clUseFastMinMaxOps, createReplaceSlowMinMaxOpsPass);
if (enableAArch64SME) {
modulePassManager.addPass(mlir::arm_sme::createVectorLegalizationPass());
FunctionLikeNest(modulePassManager)
.addPredicatedPass(
clForceArmStreaming,
[] {
// 1. Enable Armv9-A streaming mode without ZA (i.e., SSVE) for
// dispatch regions that contain scalable vectors when forced via
// the --iree-llvmcpu-force-arm-streaming flag.
return mlir::arm_sme::createEnableArmStreamingPass(
mlir::arm_sme::ArmStreamingMode::StreamingLocally,
mlir::arm_sme::ArmZaMode::Disabled,
/*ifRequiredByOps=*/false,
/*ifContainsScalableVectors=*/true);
})
.addPass(createCanonicalizerPass)
.addPass(createCSEPass)
.addPass(mlir::createArithToArmSMEConversionPass)
.addPass(mlir::createConvertVectorToArmSMEPass)
.addPass([] {
// 2. Enable ZA for dispatch regions that contain ArmSME ops (which
// all make use of the ZA state).
return mlir::arm_sme::createEnableArmStreamingPass(
mlir::arm_sme::ArmStreamingMode::StreamingLocally,
mlir::arm_sme::ArmZaMode::NewZA,
/*ifRequiredByOps=*/true);
})
.addPass(mlir::createConvertArmSMEToSCFPass);
}
FunctionLikeNest(modulePassManager)
// Resolve get_buffer_descriptor ops. All structural buffer manipulations
// must conclude before this point.
.addPass(createIREEExpandStridedMetadataPass)
.addPass(createCleanupBufferAllocViewPass)
// Checking stack allocation before converting to CF dialect is easier.
.addPass([&]() {
return createLLVMCPUCheckIRBeforeLLVMConversionPass(
LLVMCPUCheckIRBeforeLLVMConversionPassOptions{
clFailOnOutOfBoundsStackAllocation});
})
// SCF -> CF
.addPass(createConvertSCFToCFPass)
.addPass(createCanonicalizerPass)
.addPass(createCSEPass)
// (HAL, IREE, Linalg, CF) -> LLVM
.addPass(arith::createArithExpandOpsPass)
.addPass(memref::createExpandOpsPass)
.addPass(memref::createFoldMemRefAliasOpsPass)
.addPass(createEmulateNarrowTypePass)
.addPass(createCanonicalizerPass)
.addPass(createCSEPass)
.addPredicatedPass(clInstrumentMemoryAccesses,
createInstrumentMemoryAccessesPass);
if (enableAArch64SME) {
FunctionLikeNest(modulePassManager).addPass([&] {
return createConvertArmSMEToLLVMPass();
});
}
modulePassManager.addPass(
createConvertToLLVMPass(clEnableReassociateFpReductions));
modulePassManager.addPass(createReconcileUnrealizedCastsPass());
// We rely on MLIR symbol visibility being correct after this point and need
// to mirror the LLVM linkage that was assigned during conversion.
modulePassManager.addPass(createLLVMCPUSynchronizeSymbolVisibilityPass());
modulePassManager.addPass(createCanonicalizerPass());
modulePassManager.addPass(createCSEPass());
modulePassManager.addNestedPass<LLVM::LLVMFuncOp>(
createAddFastMathFlagsPass());
}
void buildLLVMCPUCodegenConfigurationPassPipelineImpl(
OpPassManager &modulePassManager) {
{
FunctionLikeNest funcPassManager(modulePassManager);
addCommonTargetExecutablePreprocessingPasses(funcPassManager,
clUseSoftmaxInterFusion);
}
modulePassManager.addPass(createMaterializeUserConfigsPass());
FunctionLikeNest(modulePassManager)
.addPass(createRematerializeParallelOpsPass)
// TODO(#13888): This(createExpandF16OpToF32Pass()) pass is being added
// way to late and should insted be be done during lowering to LLVM.
.addPass(createExpandF16OpToF32Pass)
.addPass(createMaterializeDeviceEncodingPass)
// TODO: Remove the following pass the plumb support for
// #hal.descriptor_type memory space through the stack.
.addPass(createEraseHALDescriptorTypeFromMemRefPass);
modulePassManager.addPass(createLLVMCPUSelectLoweringStrategyPass());
LLVM_DEBUG({
llvm::dbgs() << "LLVMCPU codegen configuration pass pipeline:\n";
modulePassManager.printAsTextualPipeline(llvm::dbgs());
llvm::dbgs() << "\n";
});
}
void buildLLVMCPUCodegenConfigurationPassPipeline(
OpPassManager &variantPassManager) {
OpPassManager &modulePassManager = variantPassManager.nest<ModuleOp>();
buildLLVMCPUCodegenConfigurationPassPipelineImpl(modulePassManager);
}
void buildLLVMCPUCodegenPassPipeline(OpPassManager &variantPassManager,
bool enableAArch64SME) {
{
OpPassManager &modulePassManager = variantPassManager.nest<ModuleOp>();
modulePassManager.addPass(createLowerExecutableUsingTransformDialectPass());
FunctionLikeNest(modulePassManager)
.addPass(createLLVMCPULowerExecutableTargetPass)
.addPass(createVerifyWorkgroupDistributionPass);
}
variantPassManager.addPass(createReconcileTranslationInfoPass());
variantPassManager.addPass(createLowerAffinePass());
variantPassManager.addPass(IREE::Util::createDropCompilerHintsPass());
// Run conversion to LLVM at `ModuleOp` granularity.
{
OpPassManager &modulePassManager = variantPassManager.nest<ModuleOp>();
addLowerToLLVMPasses(modulePassManager, enableAArch64SME);
}
LLVM_DEBUG({
llvm::dbgs() << "LLVMCPU codegen pass pipeline:\n";
variantPassManager.printAsTextualPipeline(llvm::dbgs());
llvm::dbgs() << "\n";
});
}
// NOTE: this runs on the top-level program module containing all
// hal.executable ops.
void buildLLVMCPULinkingPassPipeline(OpPassManager &modulePassManager,
std::optional<std::string> target) {
// Link together executables. This may produce some IR duplication.
LLVMCPULinkExecutablesPassOptions linkOptions;
linkOptions.target = target.value_or("");
modulePassManager.addPass(createLLVMCPULinkExecutablesPass(linkOptions));
// Cleanup IR duplication.
modulePassManager.addNestedPass<IREE::HAL::ExecutableOp>(
mlir::createCanonicalizerPass());
// Assign final executable constant and import ordinals.
auto &variantPassManager = modulePassManager.nest<IREE::HAL::ExecutableOp>()
.nest<IREE::HAL::ExecutableVariantOp>();
variantPassManager.addPass(createLLVMCPUAssignConstantOrdinalsPass());
variantPassManager.addPass(createLLVMCPUAssignImportOrdinalsPass());
}
//===---------------------------------------------------------------------===//
// Register LLVMCPU Passes
//===---------------------------------------------------------------------===//
namespace {
#define GEN_PASS_REGISTRATION
#include "iree/compiler/Codegen/LLVMCPU/Passes.h.inc"
} // namespace
void registerCodegenLLVMCPUPasses() {
// Generated.
registerPasses();
static PassPipelineRegistration<> LLVMCPUConfigPipeline(
"iree-codegen-llvmcpu-configuration-pipeline",
"Runs the translation strategy configuration pipeline on Linalg for CPU",
[](OpPassManager &modulePassManager) {
buildLLVMCPUCodegenConfigurationPassPipeline(modulePassManager);
});
static PassPipelineRegistration<> LLVMCPUBufferizationPipeline(
"iree-codegen-llvmcpu-bufferization-pipeline",
"Runs the bufferization pipeline for CPU",
[](OpPassManager &funcPassManager) {
addCPUBufferizePasses(funcPassManager);
});
static PassPipelineRegistration<> LLVMCPUVectorLoweringPipeline(
"iree-codegen-llvmcpu-vector-lowering-pipeline",
"Runs the translation strategy configuration pipeline on Linalg for CPU",
[](OpPassManager &funcPassManager) {
LLVMCPUVectorLoweringPassOptions options;
options.splitVectorTransfersTo = "linalg-copy";
buildLLVMCPUVectorLoweringPipeline(funcPassManager, options);
});
struct LinalgToLLVMPipelineOptions
: public PassPipelineOptions<LinalgToLLVMPipelineOptions> {
Option<bool> enableArmSME{
*this, "enable-arm-sme",
llvm::cl::desc("Enable the ArmSME lowering pipeline.")};
};
static PassPipelineRegistration<LinalgToLLVMPipelineOptions>
LinalgLLVMPipeline(
"iree-codegen-linalg-to-llvm-pipeline",
"Runs the progressive lowering pipeline from Linalg to LLVM",
[](OpPassManager &variantPassManager,
LinalgToLLVMPipelineOptions const &options) {
buildLLVMCPUCodegenPassPipeline(variantPassManager,
options.enableArmSME);
});
static PassPipelineRegistration<> LLVMCPULinkingPipeline(
"iree-codegen-llvmcpu-linking-pipeline",
"Runs the LLVMCPU HAL executable linking pipeline",
[](OpPassManager &modulePassManager) {
buildLLVMCPULinkingPassPipeline(modulePassManager);
});
}
} // namespace mlir::iree_compiler