blob: a507a9eecd65d7ba13632a57f299a4a55fcbd656 [file] [edit]
// Copyright 2019 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/compiler/Dialect/VM/Transforms/Passes.h"
#include <memory>
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "iree/compiler/Dialect/Util/Transforms/Passes.h"
#include "iree/compiler/Dialect/VM/IR/VMOps.h"
#include "iree/compiler/Transforms/Passes.h"
#include "iree/compiler/Utils/PassUtils.h"
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
#include "mlir/Dialect/Affine/Transforms/Passes.h"
#include "mlir/Dialect/Arith/Transforms/Passes.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/Transforms/Passes.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Pass/PassRegistry.h"
#include "mlir/Transforms/Passes.h"
namespace mlir::iree_compiler::IREE::VM {
using FunctionLikeNest =
MultiOpNest<func::FuncOp, IREE::Util::InitializerOp, IREE::Util::FuncOp>;
//===----------------------------------------------------------------------===//
// Utilities
//===----------------------------------------------------------------------===//
static void addCleanupPatterns(OpPassManager &passManager) {
// TODO(benvanik): run in a fixed-point iteration pipeline.
// Standard MLIR cleanup.
FunctionLikeNest(passManager)
.addPass(mlir::createCanonicalizerPass)
.addPass(mlir::createCSEPass);
// Aggressive MLIR cleanup.
passManager.addNestedPass<IREE::VM::ModuleOp>(
IREE::VM::createDropUnusedCallsPass());
passManager.addPass(mlir::createSymbolDCEPass());
FunctionLikeNest(passManager)
// Simplify util.global accesses; this can help with data flow tracking as
// redundant store-loads are removed.
.addPass(IREE::Util::createSimplifyGlobalAccessesPass)
// Aggressive cleanup.
.addPass(IREE::Util::createApplyPatternsPass);
// Cleanup and canonicalization of util.global (and other util ops).
passManager.addPass(IREE::Util::createFoldGlobalsPass());
passManager.addPass(IREE::Util::createFuseGlobalsPass());
}
static ConversionPassOptions
conversionPassOptionsFromTarget(IREE::VM::TargetOptions targetOptions) {
ConversionPassOptions passOptions;
passOptions.indexBits = targetOptions.indexBits;
passOptions.f32Extension = targetOptions.f32Extension;
passOptions.f64Extension = targetOptions.f64Extension;
passOptions.truncateUnsupportedFloats =
targetOptions.truncateUnsupportedFloats;
passOptions.optimizeForStackSize = targetOptions.optimizeForStackSize;
return passOptions;
}
//===----------------------------------------------------------------------===//
// -iree-vm-transformation-pipeline
//===----------------------------------------------------------------------===//
void buildVMTransformPassPipeline(OpPassManager &passManager,
TargetOptions targetOptions) {
// HACK: we'd really prefer not to inline here but most of the subsequent
// passes are local only. We need to inline before we convert to the vm
// dialect as they all operate on our various input dialects. If we could
// control the inliner a bit more and not have it inline *everything* we could
// do a more conservative inlining here and then the final one after lowering
// to vm below.
passManager.addPass(mlir::createInlinerPass());
passManager.addPass(mlir::createSymbolDCEPass());
// Verify module initialization order - subsequent passes rely on it being
// correct (and we maintain it as correct from this point on, so this is our
// gate).
passManager.addPass(IREE::Util::createVerifyInitializationOrderPass());
// Combine the initializers for all globals to allow us to optimize them
// together.
passManager.addPass(IREE::Util::createCombineInitializersPass());
{
MultiPipelineNest nest(passManager);
nest.nest<func::FuncOp, IREE::Util::InitializerOp, IREE::Util::FuncOp>();
nest.addPass(mlir::createSCFForLoopCanonicalizationPass);
// This pass is sketchy as it can pessimize tight loops due to affine
// treating all indices as signed and the unsigned conversion pass not being
// able to handle that. The scf.for canonicalization does a decent job of
// removing trivial loops above and this catches the rest. It inserts nasty
// rem/div ops that we can never safely remove inside of the hot inner loop
// and that sucks. We still have this here for now as the cost of the
// rem/div are less than the cost of an additional loop that this could
// remove.
nest.addPassFor<func::FuncOp>(affine::createLoopCoalescingPass);
nest.addPass(mlir::createLoopInvariantCodeMotionPass)
.addPass(mlir::createSCFToControlFlowPass)
// TODO: Maybe this should be a part of Affine lowering pass.
// Remove if it is added there.
// https://github.com/llvm/llvm-project/issues/78458
.addPass(createIREEAffineExpandIndexOpsPass)
.addPass(createIREELowerAffinePass);
}
passManager.addPass(mlir::arith::createArithUnsignedWhenEquivalentPass());
// Propagate buffer subranges throughout the program - this should remove any
// remaining subspans and give us a smaller surface area during conversion.
passManager.addPass(IREE::Util::createPropagateSubrangesPass());
addCleanupPatterns(passManager);
// Convert std/util/etc -> VM, along with any other dialects implementing the
// VM conversion dialect interface.
passManager.addPass(
createConversionPass(conversionPassOptionsFromTarget(targetOptions)));
// Hoist globals and get the final set that need to be initialized.
passManager.addNestedPass<IREE::VM::ModuleOp>(createReifyRodataTablesPass());
passManager.addNestedPass<IREE::VM::ModuleOp>(createHoistInlinedRodataPass());
passManager.addNestedPass<IREE::VM::ModuleOp>(createDeduplicateRodataPass());
addCleanupPatterns(passManager);
passManager.addNestedPass<IREE::VM::ModuleOp>(createResolveRodataLoadsPass());
// Catch any inlining opportunities we created during lowering.
passManager.addPass(mlir::createInlinerPass());
passManager.addPass(mlir::createSymbolDCEPass());
// Create global initialization functions (__init/__deinit).
addCleanupPatterns(passManager);
passManager.addNestedPass<IREE::VM::ModuleOp>(
createGlobalInitializationPass());
// Ideally we'd run this as part of a fixed-point iteration: CSE may need the
// canonicalizer to remove ops in order to get the equivalences it needs to
// work while the canonicalizer may require CSE for patterns to kick in (like
// "fold cmp if lhs=%0 && rhs=%0").
passManager.addPass(mlir::createCanonicalizerPass());
passManager.addPass(mlir::createCSEPass());
passManager.addPass(mlir::createCanonicalizerPass());
// Now that we've inlined/canonicalized/etc the initializers we can remove
// them if they are empty to save a few bytes in the binary and avoid a
// runtime initialization call.
passManager.addNestedPass<IREE::VM::ModuleOp>(
createDropEmptyModuleInitializersPass());
// Annotate functions with yield/unwind requirements by analyzing the call
// graph. This must happen after all inlining is complete.
passManager.addNestedPass<IREE::VM::ModuleOp>(createAnnotateFunctionsPass());
// Convert calls to yieldable functions to vm.call.yieldable. This must run
// after AnnotateFunctionsPass which marks functions with vm.yield.
passManager.addNestedPass<IREE::VM::ModuleOp>(
createConvertToYieldableCallsPass());
if (targetOptions.optimizeForStackSize) {
passManager.addNestedPass<IREE::VM::ModuleOp>(createSinkDefiningOpsPass());
}
// Drop vm.optimization_barrier ops now that optimization is complete.
passManager.addNestedPass<IREE::VM::ModuleOp>(
createDropOptimizationBarriersPass());
// Insert explicit discard ops for ref values at their last use points.
// Uses edge-based placement: refs dying on control flow edges get discards
// inserted on those edges, refs dying mid-block get discards after last use.
passManager.addNestedPass<IREE::VM::ModuleOp>(
createMaterializeRefDiscardsPass());
}
//===----------------------------------------------------------------------===//
// Registration
//===----------------------------------------------------------------------===//
namespace {
#define GEN_PASS_REGISTRATION
#include "iree/compiler/Dialect/VM/Transforms/Passes.h.inc" // IWYU pragma: export
} // namespace
void registerVMPasses() {
// Register command line flags.
IREE::VM::TargetOptions::FromFlags::get();
// Generated.
registerPasses();
// Pipelines.
PassPipelineRegistration<> transformPassPipeline(
"iree-vm-transformation-pipeline",
"Runs the full IREE VM dialect transformation pipeline",
[](OpPassManager &passManager) {
buildVMTransformPassPipeline(passManager,
IREE::VM::TargetOptions::FromFlags::get());
});
}
} // namespace mlir::iree_compiler::IREE::VM