//===- sdbm-api-test.cpp - Tests for SDBM expression APIs -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// RUN: mlir-sdbm-api-test | FileCheck %s

#include "mlir/Dialect/SDBM/SDBM.h"
#include "mlir/Dialect/SDBM/SDBMDialect.h"
#include "mlir/Dialect/SDBM/SDBMExpr.h"
#include "mlir/IR/MLIRContext.h"

#include "llvm/Support/raw_ostream.h"

#include "APITest.h"

using namespace mlir;


static MLIRContext *ctx() {
  static thread_local MLIRContext context;
  static thread_local bool once =
      (context.getOrLoadDialect<SDBMDialect>(), true);
  (void)once;
  return &context;
}

static SDBMDialect *dialect() {
  static thread_local SDBMDialect *d = nullptr;
  if (!d) {
    d = ctx()->getOrLoadDialect<SDBMDialect>();
  }
  return d;
}

static SDBMExpr dim(unsigned pos) { return SDBMDimExpr::get(dialect(), pos); }

static SDBMExpr symb(unsigned pos) {
  return SDBMSymbolExpr::get(dialect(), pos);
}

namespace {

using namespace mlir::ops_assertions;

TEST_FUNC(SDBM_SingleConstraint) {
  // Build an SDBM defined by
  //   d0 - 3 <= 0  <=>  d0 <= 3.
  auto sdbm = SDBM::get(dim(0) - 3, llvm::None);

  //      CHECK:       cst   d0
  // CHECK-NEXT: cst   inf    3
  // CHECK-NEXT: d0    inf  inf
  sdbm.print(llvm::outs());
}

TEST_FUNC(SDBM_Equality) {
  // Build an SDBM defined by
  //
  //   d0 - d1 - 3 = 0
  //     <=> {d0 - d1 - 3 <= 0 and d0 - d1 - 3 >= 0}
  //     <=> {d0 - d1 <= 3 and d1 - d0 <= -3}.
  auto sdbm = SDBM::get(llvm::None, dim(0) - dim(1) - 3);

  //      CHECK:       cst   d0   d1
  // CHECK-NEXT: cst   inf  inf  inf
  // CHECK-NEXT: d0    inf  inf   -3
  // CHECK-NEXT: d1    inf    3  inf
  sdbm.print(llvm::outs());
}

TEST_FUNC(SDBM_TrivialSimplification) {
  // Build an SDBM defined by
  //
  //   d0 - 3 <= 0  <=>  d0 <= 3
  //   d0 - 5 <= 0  <=>  d0 <= 5
  //
  // which should get simplified on construction to only the former.
  auto sdbm = SDBM::get({dim(0) - 3, dim(0) - 5}, llvm::None);

  //      CHECK:       cst   d0
  // CHECK-NEXT: cst   inf    3
  // CHECK-NEXT: d0    inf  inf
  sdbm.print(llvm::outs());
}

TEST_FUNC(SDBM_StripeInducedIneqs) {
  // Build an SDBM defined by d1 = d0 # 3, which induces the constraints
  //
  //   d1 - d0 <= 0
  //   d0 - d1 <= 3 - 1 = 2
  auto sdbm = SDBM::get(llvm::None, dim(1) - stripe(dim(0), 3));

  //      CHECK:       cst   d0   d1
  // CHECK-NEXT: cst   inf  inf  inf
  // CHECK-NEXT: d0    inf  inf    0
  // CHECK-NEXT: d1    inf    2    0
  // CHECK-NEXT: d1 = d0 # 3
  sdbm.print(llvm::outs());
}

TEST_FUNC(SDBM_StripeTemporaries) {
  // Build an SDBM defined by d0 # 3 <= 0, which creates a temporary
  // t0 = d0 # 3 leading to a constraint t0 <= 0 and the stripe-induced
  // constraints
  //
  //   t0 - d0 <= 0
  //   d0 - t0 <= 3 - 1 = 2
  auto sdbm = SDBM::get(stripe(dim(0), 3), llvm::None);

  //      CHECK:       cst   d0   t0
  // CHECK-NEXT: cst   inf  inf    0
  // CHECK-NEXT: d0    inf  inf    0
  // CHECK-NEXT: t0    inf    2  inf
  // CHECK-NEXT: t0 = d0 # 3
  sdbm.print(llvm::outs());
}

TEST_FUNC(SDBM_ElideInducedInequalities) {
  // Build an SDBM defined by a single stripe equality d0 = s0 # 3 and make sure
  // the induced inequalities are not present after converting the SDBM back
  // into lists of expressions.
  auto sdbm = SDBM::get(llvm::None, {dim(0) - stripe(symb(0), 3)});

  SmallVector<SDBMExpr, 4> eqs, ineqs;
  sdbm.getSDBMExpressions(dialect(), ineqs, eqs);
  // CHECK-EMPTY:
  for (auto ineq : ineqs)
    ineq.print(llvm::outs() << '\n');
  llvm::outs() << "\n";

  // CHECK: d0 - s0 # 3
  // CHECK-EMPTY:
  for (auto eq : eqs)
    eq.print(llvm::outs() << '\n');
  llvm::outs() << "\n\n";
}

TEST_FUNC(SDBM_StripeTightening) {
  // Build an SDBM defined by
  //
  //   d0 = s0 # 3 # 5
  //   s0 # 3 # 5 - d1 + 42 = 0
  //   s0 # 3 - d0 <= 2
  //
  // where the last inequality is tighter than that induced by the first stripe
  // equality (s0 # 3 - d0 <= 5 - 1 = 4).  Check that the conversion from SDBM
  // back to the lists of constraints conserves both the stripe equality and the
  // tighter inequality.
  auto s = stripe(stripe(symb(0), 3), 5);
  auto tight = stripe(symb(0), 3) - dim(0) - 2;
  auto sdbm = SDBM::get({tight}, {s - dim(0), s - dim(1) + 42});

  SmallVector<SDBMExpr, 4> eqs, ineqs;
  sdbm.getSDBMExpressions(dialect(), ineqs, eqs);
  // CHECK: s0 # 3 + -2 - d0
  // CHECK-EMPTY:
  for (auto ineq : ineqs)
    ineq.print(llvm::outs() << '\n');
  llvm::outs() << "\n";

  // CHECK-DAG: d1 + -42 - d0
  // CHECK-DAG: d0 - s0 # 3 # 5
  for (auto eq : eqs)
    eq.print(llvm::outs() << '\n');
  llvm::outs() << "\n\n";
}

TEST_FUNC(SDBM_StripeTransitive) {
  // Build an SDBM defined by
  //
  //   d0 = d1 # 3
  //   d0 = d2 # 7
  //
  // where the same dimension is declared equal to two stripe expressions over
  // different variables.  This is practically handled by introducing a
  // temporary variable for the second stripe expression and adding an equality
  // constraint between this variable and the original dimension variable.
  auto sdbm = SDBM::get(
      llvm::None, {stripe(dim(1), 3) - dim(0), stripe(dim(2), 7) - dim(0)});

  //      CHECK:       cst   d0   d1   d2   t0
  // CHECK-NEXT: cst   inf  inf  inf  inf  inf
  // CHECK-NEXT: d0    inf    0    2  inf    0
  // CHECK-NEXT: d1    inf    0  inf  inf  inf
  // CHECK-NEXT: d2    inf  inf  inf  inf    0
  // CHECK-NEXT: t0    inf    0  inf    6  inf
  // CHECK-NEXT: t0 = d2 # 7
  // CHECK-NEXT: d0 = d1 # 3
  sdbm.print(llvm::outs());
}

} // end namespace

int main() {
  RUN_TESTS();
  return 0;
}
