// 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 <memory>
#include <tuple>

#include "iree/compiler/Dialect/Util/Conversion/ConversionPatterns.h"
#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "iree/compiler/Dialect/VM/Conversion/BuiltinRegistry.h"
#include "iree/compiler/Dialect/VM/Conversion/ConversionDialectInterface.h"
#include "iree/compiler/Dialect/VM/Conversion/ConversionTarget.h"
#include "iree/compiler/Dialect/VM/Conversion/ImportUtils.h"
#include "iree/compiler/Dialect/VM/Conversion/TypeConverter.h"
#include "iree/compiler/Dialect/VM/Transforms/Passes.h"
#include "llvm/ADT/STLExtras.h"
#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Arith/Transforms/Passes.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/Math/IR/Math.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassRegistry.h"
#include "mlir/Transforms/DialectConversion.h"

#include "iree/compiler/Dialect/VM/Conversion/ArithToVM/Patterns.h"
#include "iree/compiler/Dialect/VM/Conversion/MathToVM/Patterns.h"
#include "iree/compiler/Dialect/VM/Conversion/StandardToVM/Patterns.h"
#include "iree/compiler/Dialect/VM/Conversion/UtilToVM/Patterns.h"

namespace mlir::iree_compiler::IREE::VM {

#define GEN_PASS_DEF_CONVERSIONPASS
#include "iree/compiler/Dialect/VM/Transforms/Passes.h.inc"

namespace {
/// Drops util.assume.int ops during VM conversion.
/// These hints have no VM-level meaning and would otherwise fail legalization
/// when their types need conversion (e.g., index -> i32).
struct DropAssumeIntPattern : OpConversionPattern<IREE::Util::AssumeIntOp> {
  using OpConversionPattern::OpConversionPattern;
  LogicalResult
  matchAndRewrite(IREE::Util::AssumeIntOp op, OpAdaptor adaptor,
                  ConversionPatternRewriter &rewriter) const override {
    rewriter.replaceOp(op, adaptor.getOperands());
    return success();
  }
};
} // namespace

// Returns a stably sorted list of dialect interfaces of T for all dialects used
// within the given module.
template <typename T>
static SmallVector<const T *>
gatherUsedDialectInterfaces(mlir::ModuleOp moduleOp) {
  SmallPtrSet<const T *, 4> resultSet;
  moduleOp.walk([&](Operation *op) {
    // Special case for declarations which may reference builtins.
    // TODO(benvanik): add a linking attribute to the module instead to avoid
    // the walk. All dialects could then indicate they want certain modules
    // linked in.
    Dialect *dialect = nullptr;
    if (auto moduleAttr = op->getAttrOfType<StringAttr>("vm.import.module")) {
      // Specified dialect lookup.
      dialect = op->getContext()->getOrLoadDialect(moduleAttr.getValue());
    } else {
      // Generic dialect lookup.
      dialect = op->getDialect();
    }
    if (!dialect) {
      return;
    }
    auto *dialectInterface = dialect->getRegisteredInterface<T>();
    if (!dialectInterface) {
      return;
    }
    resultSet.insert(dialectInterface);
  });

  // NOTE: to ensure deterministic output we sort the result so that imports are
  // always added in a consistent order.
  auto results = llvm::to_vector_of<const T *>(resultSet);
  llvm::sort(
      results, +[](const T *a, const T *b) {
        return a->getDialect()->getNamespace().compare(
                   b->getDialect()->getNamespace()) < 0;
      });
  return results;
}

// Runs conversion with registered input dialects.
class ConversionPass
    : public IREE::VM::impl::ConversionPassBase<ConversionPass> {
  using Base::Base;
  void runOnOperation() override {
    if (getOperation().getBody()->empty()) {
      return;
    }

    auto targetOptions = targetOptionsFromConversionPass();

    auto *context = &getContext();
    VMConversionTarget conversionTarget(context);
    IREE::VM::TypeConverter typeConverter(targetOptions);

    mlir::ModuleOp outerModuleOp, innerModuleOp;
    std::tie(outerModuleOp, innerModuleOp) =
        VMConversionTarget::nestModuleForConversion(getOperation());

    // Append all vm.import ops from used dialects so that we can look them up
    // during conversion.
    auto usedDialects =
        gatherUsedDialectInterfaces<VMConversionDialectInterface>(
            innerModuleOp);
    for (auto *dialectInterface : usedDialects) {
      auto outerImportModuleOp = dialectInterface->getVMImportModule();
      if (!outerImportModuleOp) {
        innerModuleOp.emitError()
            << "unable load the VM import module for dialect '"
            << dialectInterface->getDialect()->getNamespace()
            << "'; possibly a bad file structure or malformed vm.import";
        signalPassFailure();
        return;
      }
      for (auto importModuleOp :
           outerImportModuleOp.getOps<IREE::VM::ModuleOp>()) {
        if (failed(appendImportModule(importModuleOp, innerModuleOp))) {
          importModuleOp.emitError() << "failed to import module";
          return signalPassFailure();
        }
      }
    }

    // Populated below after all type converters are registered.
    ImportTable importTable;
    BuiltinRegistry builtins;

    RewritePatternSet patterns(&getContext());
    populateUtilConversionPatterns(context, conversionTarget, typeConverter,
                                   patterns);
    // Drop util.assume.int - has no VM-level meaning and would fail
    // legalization when types need conversion (e.g., index -> i32).
    conversionTarget.addIllegalOp<IREE::Util::AssumeIntOp>();
    patterns.add<DropAssumeIntPattern>(typeConverter, context);
    // Convert util.optimization_barrier to vm.optimization_barrier.
    conversionTarget.addIllegalOp<IREE::Util::OptimizationBarrierOp>();
    populateUtilToVMPatterns(context, conversionTarget, typeConverter,
                             importTable, builtins, patterns);

    conversionTarget.addIllegalDialect<affine::AffineDialect>();
    populateAffineToStdConversionPatterns(patterns);

    conversionTarget.addIllegalDialect<arith::ArithDialect>();
    arith::populateCeilFloorDivExpandOpsPatterns(patterns);
    populateArithToVMPatterns(context, typeConverter, patterns);

    conversionTarget.addIllegalDialect<math::MathDialect>();
    populateMathToVMPatterns(context, typeConverter, patterns);

    conversionTarget.addIllegalDialect<func::FuncDialect>();
    populateStandardToVMPatterns(context, typeConverter, importTable, patterns);

    // Populate patterns from all used dialects, providing the imports they
    // registered earlier.
    SymbolTable importSymbols(innerModuleOp);
    for (auto *dialectInterface : usedDialects) {
      dialectInterface->populateVMConversionPatterns(
          importSymbols, patterns, conversionTarget, typeConverter);
    }

    // Build an import table so that we can quickly look up import information
    // during conversion.
    if (failed(importTable.build(innerModuleOp, typeConverter))) {
      return signalPassFailure(); // error emitted already
    }

    // Insert declarations for all registered builtin helpers so that
    // conversion patterns can reference them without module-level lookups.
    builtins.declareAll(innerModuleOp);

    if (failed(applyPartialConversion(outerModuleOp, conversionTarget,
                                      std::move(patterns)))) {
      outerModuleOp.emitError() << "conversion to vm.module failed";
      return signalPassFailure();
    }

    // Erase unused builtin declarations and populate used ones with bodies.
    builtins.finalize();
  }

  IREE::VM::TargetOptions targetOptionsFromConversionPass() {
    IREE::VM::TargetOptions targetOptions;
    targetOptions.indexBits = indexBits;
    targetOptions.f32Extension = f32Extension;
    targetOptions.f64Extension = f64Extension;
    targetOptions.truncateUnsupportedFloats = truncateUnsupportedFloats;
    targetOptions.optimizeForStackSize = optimizeForStackSize;
    return targetOptions;
  }
};

} // namespace mlir::iree_compiler::IREE::VM
