// 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
