// Copyright 2022 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/Flow/Transforms/FormDispatchRegions.h"

#include "iree/compiler/Dialect/Flow/Transforms/ConvertRegionToWorkgroups.h"
#include "iree/compiler/Dialect/Flow/Transforms/RegionOpUtils.h"
#include "llvm/Support/Casting.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/Utils/StructuredOpsUtils.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Support/LLVM.h"

//===----------------------------------------------------------------------===//
// Definition of TensorDimTrackingRewriter
//===----------------------------------------------------------------------===//

namespace mlir {

TensorDimTrackingRewriter::TensorDimTrackingRewriter(Operation *op)
    : IRRewriter(op->getContext()) {
  setListener(this);
  op->walk([&](tensor::DimOp dimOp) { dimOps.insert(dimOp.getOperation()); });
}
SmallVector<tensor::DimOp> TensorDimTrackingRewriter::getTensorDimOps() {
  SmallVector<tensor::DimOp> result;
  for (Operation *op : dimOps) {
    result.push_back(cast<tensor::DimOp>(op));
  }
  return result;
}
void TensorDimTrackingRewriter::notifyOperationErased(Operation *op) {
  IRRewriter::Listener::notifyOperationErased(op);
  if (isa<tensor::DimOp>(op)) {
    dimOps.erase(op);
  }
}

void TensorDimTrackingRewriter::notifyOperationInserted(Operation *op,
                                                        InsertPoint previous) {
  IRRewriter::Listener::notifyOperationInserted(op, previous);
  auto dimOp = dyn_cast<tensor::DimOp>(op);
  if (dimOp && isa<OpResult>(dimOp.getSource())) {
    dimOps.insert(op);
  }
}

} // namespace mlir

namespace mlir::iree_compiler::IREE::Flow {

LogicalResult simplifyDimOps(RewriterBase &rewriter,
                             const SmallVector<tensor::DimOp> &dimOps) {
  for (tensor::DimOp dimOp : dimOps) {
    // Only DimOps with static indices are supported.
    std::optional<int64_t> idx = dimOp.getConstantIndex();
    if (!idx.has_value()) {
      continue;
    }

    if (isa<BlockArgument>(dimOp.getSource())) {
      continue;
    }

    // Only DimOps with ranked tensors are supported.
    auto tensorType = dyn_cast<RankedTensorType>(dimOp.getSource().getType());
    if (!tensorType) {
      continue;
    }

    OpBuilder::InsertionGuard g(rewriter);
    rewriter.setInsertionPoint(dimOp);
    if (!tensorType.isDynamicDim(*idx)) {
      // Rewrite static dimension with constant.
      int64_t size = tensorType.getShape()[*idx];
      rewriter.replaceOpWithNewOp<arith::ConstantIndexOp>(dimOp, size);
      continue;
    }

    // Try to simplify dynamic dims.
    SmallVector<Value> dynamicDims;
    if (succeeded(IREE::Flow::getOptimizedDynamicResultDims(
            rewriter, dimOp.getSource(), dynamicDims))) {
      unsigned ctr = 0;
      for (int64_t i = 0; i < *dimOp.getConstantIndex(); ++i) {
        if (tensorType.isDynamicDim(i)) {
          ++ctr;
        }
      }
      rewriter.replaceOp(dimOp, dynamicDims[ctr]);
    }
  }

  return success();
}

} // namespace mlir::iree_compiler::IREE::Flow
