//===- ReifyValueBounds.cpp --- Reify value bounds with arith ops -------*-===//
//
// Part of the LLVM Project, 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 <utility>

#include "mlir/Dialect/Arith/Transforms/Transforms.h"

#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Interfaces/ValueBoundsOpInterface.h"

using namespace mlir;
using namespace mlir::arith;

/// Build Arith IR for the given affine map and its operands.
static Value buildArithValue(OpBuilder &b, Location loc, AffineMap map,
                             ValueRange operands) {
  assert(map.getNumResults() == 1 && "multiple results not supported yet");
  std::function<Value(AffineExpr)> buildExpr = [&](AffineExpr e) -> Value {
    switch (e.getKind()) {
    case AffineExprKind::Constant:
      return ConstantIndexOp::create(b, loc,
                                     cast<AffineConstantExpr>(e).getValue());
    case AffineExprKind::DimId:
      return operands[cast<AffineDimExpr>(e).getPosition()];
    case AffineExprKind::SymbolId:
      return operands[cast<AffineSymbolExpr>(e).getPosition() +
                      map.getNumDims()];
    case AffineExprKind::Add: {
      auto binaryExpr = cast<AffineBinaryOpExpr>(e);
      return AddIOp::create(b, loc, buildExpr(binaryExpr.getLHS()),
                            buildExpr(binaryExpr.getRHS()));
    }
    case AffineExprKind::Mul: {
      auto binaryExpr = cast<AffineBinaryOpExpr>(e);
      return MulIOp::create(b, loc, buildExpr(binaryExpr.getLHS()),
                            buildExpr(binaryExpr.getRHS()));
    }
    case AffineExprKind::FloorDiv: {
      auto binaryExpr = cast<AffineBinaryOpExpr>(e);
      return DivSIOp::create(b, loc, buildExpr(binaryExpr.getLHS()),
                             buildExpr(binaryExpr.getRHS()));
    }
    case AffineExprKind::CeilDiv: {
      auto binaryExpr = cast<AffineBinaryOpExpr>(e);
      return CeilDivSIOp::create(b, loc, buildExpr(binaryExpr.getLHS()),
                                 buildExpr(binaryExpr.getRHS()));
    }
    case AffineExprKind::Mod: {
      auto binaryExpr = cast<AffineBinaryOpExpr>(e);
      return RemSIOp::create(b, loc, buildExpr(binaryExpr.getLHS()),
                             buildExpr(binaryExpr.getRHS()));
    }
    }
    llvm_unreachable("unsupported AffineExpr kind");
  };
  return buildExpr(map.getResult(0));
}

FailureOr<OpFoldResult> mlir::arith::reifyValueBound(
    OpBuilder &b, Location loc, presburger::BoundType type,
    const ValueBoundsConstraintSet::Variable &var,
    ValueBoundsConstraintSet::StopConditionFn stopCondition, bool closedUB) {
  // Compute bound.
  AffineMap boundMap;
  ValueDimList mapOperands;
  if (failed(ValueBoundsConstraintSet::computeBound(
          boundMap, mapOperands, type, var, std::move(stopCondition),
          closedUB)))
    return failure();

  // Materialize tensor.dim/memref.dim ops.
  SmallVector<Value> operands;
  for (auto valueDim : mapOperands) {
    Value value = valueDim.first;
    std::optional<int64_t> dim = valueDim.second;

    if (!dim.has_value()) {
      // This is an index-typed value.
      assert(value.getType().isIndex() && "expected index type");
      operands.push_back(value);
      continue;
    }

    assert(cast<ShapedType>(value.getType()).isDynamicDim(*dim) &&
           "expected dynamic dim");
    if (isa<RankedTensorType>(value.getType())) {
      // A tensor dimension is used: generate a tensor.dim.
      operands.push_back(tensor::DimOp::create(b, loc, value, *dim));
    } else if (isa<MemRefType>(value.getType())) {
      // A memref dimension is used: generate a memref.dim.
      operands.push_back(memref::DimOp::create(b, loc, value, *dim));
    } else {
      llvm_unreachable("cannot generate DimOp for unsupported shaped type");
    }
  }

  // Check for special cases where no arith ops are needed.
  if (boundMap.isSingleConstant()) {
    // Bound is a constant: return an IntegerAttr.
    return static_cast<OpFoldResult>(
        b.getIndexAttr(boundMap.getSingleConstantResult()));
  }
  // No arith ops are needed if the bound is a single SSA value.
  if (auto expr = dyn_cast<AffineDimExpr>(boundMap.getResult(0)))
    return static_cast<OpFoldResult>(operands[expr.getPosition()]);
  if (auto expr = dyn_cast<AffineSymbolExpr>(boundMap.getResult(0)))
    return static_cast<OpFoldResult>(
        operands[expr.getPosition() + boundMap.getNumDims()]);
  // General case: build Arith ops.
  return static_cast<OpFoldResult>(buildArithValue(b, loc, boundMap, operands));
}

FailureOr<OpFoldResult> mlir::arith::reifyShapedValueDimBound(
    OpBuilder &b, Location loc, presburger::BoundType type, Value value,
    int64_t dim, const ValueBoundsConstraintSet::StopConditionFn &stopCondition,
    bool closedUB) {
  auto reifyToOperands = [&](Value v, std::optional<int64_t> d,
                             ValueBoundsConstraintSet &cstr) {
    // We are trying to reify a bound for `value` in terms of the owning op's
    // operands. Construct a stop condition that evaluates to "true" for any SSA
    // value expect for `value`. I.e., the bound will be computed in terms of
    // any SSA values expect for `value`. The first such values are operands of
    // the owner of `value`.
    return v != value;
  };
  return reifyValueBound(b, loc, type, {value, dim},
                         stopCondition ? stopCondition : reifyToOperands,
                         closedUB);
}

FailureOr<OpFoldResult> mlir::arith::reifyIndexValueBound(
    OpBuilder &b, Location loc, presburger::BoundType type, Value value,
    const ValueBoundsConstraintSet::StopConditionFn &stopCondition,
    bool closedUB) {
  auto reifyToOperands = [&](Value v, std::optional<int64_t> d,
                             ValueBoundsConstraintSet &cstr) {
    return v != value;
  };
  return reifyValueBound(b, loc, type, value,
                         stopCondition ? stopCondition : reifyToOperands,
                         closedUB);
}
