// Copyright 2024 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/LinalgExt/Utils/IndexingUtils.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/Support/raw_ostream.h"

namespace mlir::iree_compiler::IREE::LinalgExt {

namespace {

static llvm::SmallDenseSet<int64_t>
findPermutationsIndexingOperand(AffineMap indexingMap) {
  assert(indexingMap.isProjectedPermutation() &&
         "indexing maps for attention must be permutations.");
  llvm::SmallDenseSet<int64_t> res;
  for (AffineExpr e : indexingMap.getResults()) {
    auto d = cast<AffineDimExpr>(e);
    res.insert(d.getPosition());
  }
  return res;
}

}; // namespace

void AttentionOpDetail::inferFromIndexingMaps(AffineMap qMap, AffineMap kMap,
                                              AffineMap vMap, AffineMap oMap) {
  // Q   = B x M x K1
  // K   = B x K2 x K1
  // V   = B x K2 x N
  // O   = B x M x N
  llvm::SmallDenseSet<int64_t> qSet = findPermutationsIndexingOperand(qMap);
  llvm::SmallDenseSet<int64_t> kSet = findPermutationsIndexingOperand(kMap);
  llvm::SmallDenseSet<int64_t> vSet = findPermutationsIndexingOperand(vMap);
  llvm::SmallDenseSet<int64_t> oSet = findPermutationsIndexingOperand(oMap);

  // B = (Q & K & O) U (K & V & O)
  llvm::SmallDenseSet<int64_t> b1Set = qSet;
  llvm::set_intersect(b1Set, kSet);
  llvm::set_intersect(b1Set, oSet);
  llvm::SmallDenseSet<int64_t> b2Set = kSet;
  llvm::set_intersect(b2Set, vSet);
  llvm::set_intersect(b2Set, oSet);
  llvm::SmallDenseSet<int64_t> bSet = b1Set;
  llvm::set_union(bSet, b2Set);

  // K1 = Q & K - B
  llvm::SmallDenseSet<int64_t> k1Set = qSet;
  llvm::set_intersect(k1Set, kSet);
  llvm::set_subtract(k1Set, bSet);

  // K2 = K - B - K1
  llvm::SmallDenseSet<int64_t> k2Set = kSet;
  llvm::set_subtract(k2Set, bSet);
  llvm::set_subtract(k2Set, k1Set);

  // M = Q - B - K1
  llvm::SmallDenseSet<int64_t> mSet = qSet;
  llvm::set_subtract(mSet, bSet);
  llvm::set_subtract(mSet, k1Set);

  // N = V - B - K2
  llvm::SmallDenseSet<int64_t> nSet = vSet;
  llvm::set_subtract(nSet, bSet);
  llvm::set_subtract(nSet, k2Set);

  batch = SmallVector<int64_t>(bSet.begin(), bSet.end());
  m = SmallVector<int64_t>(mSet.begin(), mSet.end());
  k1 = SmallVector<int64_t>(k1Set.begin(), k1Set.end());
  k2 = SmallVector<int64_t>(k2Set.begin(), k2Set.end());
  n = SmallVector<int64_t>(nSet.begin(), nSet.end());

  // Sort to ensure that dims are in outermost to innermost order.
  llvm::sort(batch);
  llvm::sort(m);
  llvm::sort(k1);
  llvm::sort(k2);
  llvm::sort(n);
}

FailureOr<AttentionOpDetail> AttentionOpDetail::get(AffineMap qMap,
                                                    AffineMap kMap,
                                                    AffineMap vMap,
                                                    AffineMap oMap) {
  AttentionOpDetail opInfo;
  opInfo.inferFromIndexingMaps(qMap, kMap, vMap, oMap);
  opInfo.context = qMap.getContext();
  opInfo.domainRank = qMap.getNumDims();
  return opInfo;
}

AffineMap AttentionOpDetail::getSMap() const {
  // We need to create an indexing map for the intermediate result of first
  // matmul. There could be other options, but we choose to create a standard
  // indexing map:
  //   SMap = (batch, m, k1, k2, n) -> (batch, m, k2)
  AffineMap sMap = AffineMap::get(/*dimCount=*/getDomainRank(),
                                  /*symbolCount=*/0, getContext());
  for (auto dim :
       llvm::concat<const int64_t>(getBatchDims(), getMDims(), getK2Dims())) {
    AffineExpr dimExpr = getAffineDimExpr(dim, getContext());
    sMap = sMap.insertResult(dimExpr, sMap.getNumResults());
  }
  return sMap;
}

}; // namespace mlir::iree_compiler::IREE::LinalgExt
