//===- OpenACCOpsTest.cpp - Unit tests for OpenACC 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 "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
#include "mlir/IR/Value.h"
#include "gtest/gtest.h"

using namespace mlir;
using namespace mlir::acc;

//===----------------------------------------------------------------------===//
// Test Fixture
//===----------------------------------------------------------------------===//

class OpenACCOpsTest : public ::testing::Test {
protected:
  OpenACCOpsTest() : b(&context), loc(UnknownLoc::get(&context)) {
    context.loadDialect<acc::OpenACCDialect, arith::ArithDialect,
                        memref::MemRefDialect>();
  }

  MLIRContext context;
  OpBuilder b;
  Location loc;
  llvm::SmallVector<DeviceType> dtypes = {
      DeviceType::None,    DeviceType::Star, DeviceType::Multicore,
      DeviceType::Default, DeviceType::Host, DeviceType::Nvidia,
      DeviceType::Radeon};
  llvm::SmallVector<DeviceType> dtypesWithoutNone = {
      DeviceType::Star, DeviceType::Multicore, DeviceType::Default,
      DeviceType::Host, DeviceType::Nvidia,    DeviceType::Radeon};
};

template <typename Op>
static void testAsyncOnly(OpBuilder &b, MLIRContext &context, Location loc,
                          llvm::SmallVector<DeviceType> &dtypes) {
  OwningOpRef<Op> op = Op::create(b, loc, TypeRange{}, ValueRange{});
  EXPECT_FALSE(op->hasAsyncOnly());
  for (auto d : dtypes)
    EXPECT_FALSE(op->hasAsyncOnly(d));

  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->setAsyncOnlyAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasAsyncOnly());
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::None));
  op->removeAsyncOnlyAttr();

  auto dtypeHost = DeviceTypeAttr::get(&context, DeviceType::Host);
  op->setAsyncOnlyAttr(b.getArrayAttr({dtypeHost}));
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::Host));
  EXPECT_FALSE(op->hasAsyncOnly());
  op->removeAsyncOnlyAttr();

  auto dtypeStar = DeviceTypeAttr::get(&context, DeviceType::Star);
  op->setAsyncOnlyAttr(b.getArrayAttr({dtypeHost, dtypeStar}));
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::Star));
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::Host));
  EXPECT_FALSE(op->hasAsyncOnly());

  op->removeAsyncOnlyAttr();
}

TEST_F(OpenACCOpsTest, asyncOnlyTest) {
  testAsyncOnly<ParallelOp>(b, context, loc, dtypes);
  testAsyncOnly<KernelsOp>(b, context, loc, dtypes);
  testAsyncOnly<SerialOp>(b, context, loc, dtypes);
}

template <typename Op>
static void testAsyncOnlyDataEntry(OpBuilder &b, MLIRContext &context,
                                   Location loc,
                                   llvm::SmallVector<DeviceType> &dtypes) {
  auto memrefTy = MemRefType::get({}, b.getI32Type());
  OwningOpRef<memref::AllocaOp> varPtrOp =
      memref::AllocaOp::create(b, loc, memrefTy);

  TypedValue<PointerLikeType> varPtr =
      cast<TypedValue<PointerLikeType>>(varPtrOp->getResult());
  OwningOpRef<Op> op = Op::create(b, loc, varPtr,
                                  /*structured=*/true, /*implicit=*/true);

  EXPECT_FALSE(op->hasAsyncOnly());
  for (auto d : dtypes)
    EXPECT_FALSE(op->hasAsyncOnly(d));

  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->setAsyncOnlyAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasAsyncOnly());
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::None));
  op->removeAsyncOnlyAttr();

  auto dtypeHost = DeviceTypeAttr::get(&context, DeviceType::Host);
  op->setAsyncOnlyAttr(b.getArrayAttr({dtypeHost}));
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::Host));
  EXPECT_FALSE(op->hasAsyncOnly());
  op->removeAsyncOnlyAttr();

  auto dtypeStar = DeviceTypeAttr::get(&context, DeviceType::Star);
  op->setAsyncOnlyAttr(b.getArrayAttr({dtypeHost, dtypeStar}));
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::Star));
  EXPECT_TRUE(op->hasAsyncOnly(DeviceType::Host));
  EXPECT_FALSE(op->hasAsyncOnly());

  op->removeAsyncOnlyAttr();
}

TEST_F(OpenACCOpsTest, asyncOnlyTestDataEntry) {
  testAsyncOnlyDataEntry<DevicePtrOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<PresentOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<CopyinOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<CreateOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<NoCreateOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<AttachOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<UpdateDeviceOp>(b, context, loc, dtypes);
  testAsyncOnlyDataEntry<UseDeviceOp>(b, context, loc, dtypes);
}

template <typename Op>
static void testAsyncValue(OpBuilder &b, MLIRContext &context, Location loc,
                           llvm::SmallVector<DeviceType> &dtypes) {
  OwningOpRef<Op> op = Op::create(b, loc, TypeRange{}, ValueRange{});

  mlir::Value empty;
  EXPECT_EQ(op->getAsyncValue(), empty);
  for (auto d : dtypes)
    EXPECT_EQ(op->getAsyncValue(d), empty);

  OwningOpRef<arith::ConstantIndexOp> val =
      arith::ConstantIndexOp::create(b, loc, 1);
  auto dtypeNvidia = DeviceTypeAttr::get(&context, DeviceType::Nvidia);
  op->setAsyncOperandsDeviceTypeAttr(b.getArrayAttr({dtypeNvidia}));
  op->getAsyncOperandsMutable().assign(val->getResult());
  EXPECT_EQ(op->getAsyncValue(), empty);
  EXPECT_EQ(op->getAsyncValue(DeviceType::Nvidia), val->getResult());

  op->getAsyncOperandsMutable().clear();
  op->removeAsyncOperandsDeviceTypeAttr();
}

TEST_F(OpenACCOpsTest, asyncValueTest) {
  testAsyncValue<ParallelOp>(b, context, loc, dtypes);
  testAsyncValue<KernelsOp>(b, context, loc, dtypes);
  testAsyncValue<SerialOp>(b, context, loc, dtypes);
}

template <typename Op>
static void testAsyncValueDataEntry(OpBuilder &b, MLIRContext &context,
                                    Location loc,
                                    llvm::SmallVector<DeviceType> &dtypes) {
  auto memrefTy = MemRefType::get({}, b.getI32Type());
  OwningOpRef<memref::AllocaOp> varPtrOp =
      memref::AllocaOp::create(b, loc, memrefTy);

  TypedValue<PointerLikeType> varPtr =
      cast<TypedValue<PointerLikeType>>(varPtrOp->getResult());
  OwningOpRef<Op> op = Op::create(b, loc, varPtr,
                                  /*structured=*/true, /*implicit=*/true);

  mlir::Value empty;
  EXPECT_EQ(op->getAsyncValue(), empty);
  for (auto d : dtypes)
    EXPECT_EQ(op->getAsyncValue(d), empty);

  OwningOpRef<arith::ConstantIndexOp> val =
      arith::ConstantIndexOp::create(b, loc, 1);
  auto dtypeNvidia = DeviceTypeAttr::get(&context, DeviceType::Nvidia);
  op->setAsyncOperandsDeviceTypeAttr(b.getArrayAttr({dtypeNvidia}));
  op->getAsyncOperandsMutable().assign(val->getResult());
  EXPECT_EQ(op->getAsyncValue(), empty);
  EXPECT_EQ(op->getAsyncValue(DeviceType::Nvidia), val->getResult());

  op->getAsyncOperandsMutable().clear();
  op->removeAsyncOperandsDeviceTypeAttr();
}

TEST_F(OpenACCOpsTest, asyncValueTestDataEntry) {
  testAsyncValueDataEntry<DevicePtrOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<PresentOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<CopyinOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<CreateOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<NoCreateOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<AttachOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<UpdateDeviceOp>(b, context, loc, dtypes);
  testAsyncValueDataEntry<UseDeviceOp>(b, context, loc, dtypes);
}

template <typename Op>
static void
testNumGangsValues(OpBuilder &b, MLIRContext &context, Location loc,
                   llvm::SmallVector<DeviceType> &dtypes,
                   llvm::SmallVector<DeviceType> &dtypesWithoutNone) {
  OwningOpRef<Op> op = Op::create(b, loc, TypeRange{}, ValueRange{});
  EXPECT_EQ(op->getNumGangsValues().begin(), op->getNumGangsValues().end());

  OwningOpRef<arith::ConstantIndexOp> val1 =
      arith::ConstantIndexOp::create(b, loc, 1);
  OwningOpRef<arith::ConstantIndexOp> val2 =
      arith::ConstantIndexOp::create(b, loc, 4);
  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->getNumGangsMutable().assign(val1->getResult());
  op->setNumGangsDeviceTypeAttr(b.getArrayAttr({dtypeNone}));
  op->setNumGangsSegments(b.getDenseI32ArrayAttr({1}));
  EXPECT_EQ(op->getNumGangsValues().front(), val1->getResult());
  for (auto d : dtypesWithoutNone)
    EXPECT_EQ(op->getNumGangsValues(d).begin(), op->getNumGangsValues(d).end());

  op->getNumGangsMutable().clear();
  op->removeNumGangsDeviceTypeAttr();
  op->removeNumGangsSegmentsAttr();
  for (auto d : dtypes)
    EXPECT_EQ(op->getNumGangsValues(d).begin(), op->getNumGangsValues(d).end());

  op->getNumGangsMutable().append(val1->getResult());
  op->getNumGangsMutable().append(val2->getResult());
  op->setNumGangsDeviceTypeAttr(
      b.getArrayAttr({DeviceTypeAttr::get(&context, DeviceType::Host),
                      DeviceTypeAttr::get(&context, DeviceType::Star)}));
  op->setNumGangsSegments(b.getDenseI32ArrayAttr({1, 1}));
  EXPECT_EQ(op->getNumGangsValues(DeviceType::None).begin(),
            op->getNumGangsValues(DeviceType::None).end());
  EXPECT_EQ(op->getNumGangsValues(DeviceType::Host).front(), val1->getResult());
  EXPECT_EQ(op->getNumGangsValues(DeviceType::Star).front(), val2->getResult());

  op->getNumGangsMutable().clear();
  op->removeNumGangsDeviceTypeAttr();
  op->removeNumGangsSegmentsAttr();
  for (auto d : dtypes)
    EXPECT_EQ(op->getNumGangsValues(d).begin(), op->getNumGangsValues(d).end());

  op->getNumGangsMutable().append(val1->getResult());
  op->getNumGangsMutable().append(val2->getResult());
  op->getNumGangsMutable().append(val1->getResult());
  op->setNumGangsDeviceTypeAttr(
      b.getArrayAttr({DeviceTypeAttr::get(&context, DeviceType::Default),
                      DeviceTypeAttr::get(&context, DeviceType::Multicore)}));
  op->setNumGangsSegments(b.getDenseI32ArrayAttr({2, 1}));
  EXPECT_EQ(op->getNumGangsValues(DeviceType::None).begin(),
            op->getNumGangsValues(DeviceType::None).end());
  EXPECT_EQ(op->getNumGangsValues(DeviceType::Default).front(),
            val1->getResult());
  EXPECT_EQ(op->getNumGangsValues(DeviceType::Default).drop_front().front(),
            val2->getResult());
  EXPECT_EQ(op->getNumGangsValues(DeviceType::Multicore).front(),
            val1->getResult());

  op->getNumGangsMutable().clear();
  op->removeNumGangsDeviceTypeAttr();
  op->removeNumGangsSegmentsAttr();
}

TEST_F(OpenACCOpsTest, numGangsValuesTest) {
  testNumGangsValues<ParallelOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testNumGangsValues<KernelsOp>(b, context, loc, dtypes, dtypesWithoutNone);
}

template <typename Op>
static void testVectorLength(OpBuilder &b, MLIRContext &context, Location loc,
                             llvm::SmallVector<DeviceType> &dtypes) {
  OwningOpRef<Op> op = Op::create(b, loc, TypeRange{}, ValueRange{});

  mlir::Value empty;
  EXPECT_EQ(op->getVectorLengthValue(), empty);
  for (auto d : dtypes)
    EXPECT_EQ(op->getVectorLengthValue(d), empty);

  OwningOpRef<arith::ConstantIndexOp> val =
      arith::ConstantIndexOp::create(b, loc, 1);
  auto dtypeNvidia = DeviceTypeAttr::get(&context, DeviceType::Nvidia);
  op->setVectorLengthDeviceTypeAttr(b.getArrayAttr({dtypeNvidia}));
  op->getVectorLengthMutable().assign(val->getResult());
  EXPECT_EQ(op->getVectorLengthValue(), empty);
  EXPECT_EQ(op->getVectorLengthValue(DeviceType::Nvidia), val->getResult());

  op->getVectorLengthMutable().clear();
  op->removeVectorLengthDeviceTypeAttr();
}

TEST_F(OpenACCOpsTest, vectorLengthTest) {
  testVectorLength<ParallelOp>(b, context, loc, dtypes);
  testVectorLength<KernelsOp>(b, context, loc, dtypes);
}

template <typename Op>
static void testWaitOnly(OpBuilder &b, MLIRContext &context, Location loc,
                         llvm::SmallVector<DeviceType> &dtypes,
                         llvm::SmallVector<DeviceType> &dtypesWithoutNone) {
  OwningOpRef<Op> op = Op::create(b, loc, TypeRange{}, ValueRange{});
  EXPECT_FALSE(op->hasWaitOnly());
  for (auto d : dtypes)
    EXPECT_FALSE(op->hasWaitOnly(d));

  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->setWaitOnlyAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasWaitOnly());
  EXPECT_TRUE(op->hasWaitOnly(DeviceType::None));
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasWaitOnly(d));
  op->removeWaitOnlyAttr();

  auto dtypeHost = DeviceTypeAttr::get(&context, DeviceType::Host);
  op->setWaitOnlyAttr(b.getArrayAttr({dtypeHost}));
  EXPECT_TRUE(op->hasWaitOnly(DeviceType::Host));
  EXPECT_FALSE(op->hasWaitOnly());
  op->removeWaitOnlyAttr();

  auto dtypeStar = DeviceTypeAttr::get(&context, DeviceType::Star);
  op->setWaitOnlyAttr(b.getArrayAttr({dtypeHost, dtypeStar}));
  EXPECT_TRUE(op->hasWaitOnly(DeviceType::Star));
  EXPECT_TRUE(op->hasWaitOnly(DeviceType::Host));
  EXPECT_FALSE(op->hasWaitOnly());

  op->removeWaitOnlyAttr();
}

TEST_F(OpenACCOpsTest, waitOnlyTest) {
  testWaitOnly<ParallelOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testWaitOnly<KernelsOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testWaitOnly<SerialOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testWaitOnly<UpdateOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testWaitOnly<DataOp>(b, context, loc, dtypes, dtypesWithoutNone);
}

template <typename Op>
static void testWaitValues(OpBuilder &b, MLIRContext &context, Location loc,
                           llvm::SmallVector<DeviceType> &dtypes,
                           llvm::SmallVector<DeviceType> &dtypesWithoutNone) {
  OwningOpRef<Op> op = Op::create(b, loc, TypeRange{}, ValueRange{});
  EXPECT_EQ(op->getWaitValues().begin(), op->getWaitValues().end());

  OwningOpRef<arith::ConstantIndexOp> val1 =
      arith::ConstantIndexOp::create(b, loc, 1);
  OwningOpRef<arith::ConstantIndexOp> val2 =
      arith::ConstantIndexOp::create(b, loc, 4);
  OwningOpRef<arith::ConstantIndexOp> val3 =
      arith::ConstantIndexOp::create(b, loc, 5);
  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->getWaitOperandsMutable().assign(val1->getResult());
  op->setWaitOperandsDeviceTypeAttr(b.getArrayAttr({dtypeNone}));
  op->setWaitOperandsSegments(b.getDenseI32ArrayAttr({1}));
  op->setHasWaitDevnumAttr(b.getBoolArrayAttr({false}));
  EXPECT_EQ(op->getWaitValues().front(), val1->getResult());
  for (auto d : dtypesWithoutNone)
    EXPECT_TRUE(op->getWaitValues(d).empty());

  op->getWaitOperandsMutable().clear();
  op->removeWaitOperandsDeviceTypeAttr();
  op->removeWaitOperandsSegmentsAttr();
  op->removeHasWaitDevnumAttr();
  for (auto d : dtypes)
    EXPECT_TRUE(op->getWaitValues(d).empty());

  op->getWaitOperandsMutable().append(val1->getResult());
  op->getWaitOperandsMutable().append(val2->getResult());
  op->setWaitOperandsDeviceTypeAttr(
      b.getArrayAttr({DeviceTypeAttr::get(&context, DeviceType::Host),
                      DeviceTypeAttr::get(&context, DeviceType::Star)}));
  op->setWaitOperandsSegments(b.getDenseI32ArrayAttr({1, 1}));
  op->setHasWaitDevnumAttr(b.getBoolArrayAttr({false, false}));
  EXPECT_EQ(op->getWaitValues(DeviceType::None).begin(),
            op->getWaitValues(DeviceType::None).end());
  EXPECT_EQ(op->getWaitValues(DeviceType::Host).front(), val1->getResult());
  EXPECT_EQ(op->getWaitValues(DeviceType::Star).front(), val2->getResult());

  op->getWaitOperandsMutable().clear();
  op->removeWaitOperandsDeviceTypeAttr();
  op->removeWaitOperandsSegmentsAttr();
  op->removeHasWaitDevnumAttr();
  for (auto d : dtypes)
    EXPECT_TRUE(op->getWaitValues(d).empty());

  op->getWaitOperandsMutable().append(val1->getResult());
  op->getWaitOperandsMutable().append(val2->getResult());
  op->getWaitOperandsMutable().append(val1->getResult());
  op->setWaitOperandsDeviceTypeAttr(
      b.getArrayAttr({DeviceTypeAttr::get(&context, DeviceType::Default),
                      DeviceTypeAttr::get(&context, DeviceType::Multicore)}));
  op->setWaitOperandsSegments(b.getDenseI32ArrayAttr({2, 1}));
  op->setHasWaitDevnumAttr(b.getBoolArrayAttr({false, false}));
  EXPECT_EQ(op->getWaitValues(DeviceType::None).begin(),
            op->getWaitValues(DeviceType::None).end());
  EXPECT_EQ(op->getWaitValues(DeviceType::Default).front(), val1->getResult());
  EXPECT_EQ(op->getWaitValues(DeviceType::Default).drop_front().front(),
            val2->getResult());
  EXPECT_EQ(op->getWaitValues(DeviceType::Multicore).front(),
            val1->getResult());

  op->getWaitOperandsMutable().clear();
  op->removeWaitOperandsDeviceTypeAttr();
  op->removeWaitOperandsSegmentsAttr();

  op->getWaitOperandsMutable().append(val3->getResult());
  op->getWaitOperandsMutable().append(val2->getResult());
  op->getWaitOperandsMutable().append(val1->getResult());
  op->setWaitOperandsDeviceTypeAttr(
      b.getArrayAttr({DeviceTypeAttr::get(&context, DeviceType::Multicore)}));
  op->setHasWaitDevnumAttr(b.getBoolArrayAttr({true}));
  op->setWaitOperandsSegments(b.getDenseI32ArrayAttr({3}));
  EXPECT_EQ(op->getWaitValues(DeviceType::None).begin(),
            op->getWaitValues(DeviceType::None).end());
  EXPECT_FALSE(op->getWaitDevnum());

  EXPECT_EQ(op->getWaitDevnum(DeviceType::Multicore), val3->getResult());
  EXPECT_EQ(op->getWaitValues(DeviceType::Multicore).front(),
            val2->getResult());
  EXPECT_EQ(op->getWaitValues(DeviceType::Multicore).drop_front().front(),
            val1->getResult());

  op->getWaitOperandsMutable().clear();
  op->removeWaitOperandsDeviceTypeAttr();
  op->removeWaitOperandsSegmentsAttr();
  op->removeHasWaitDevnumAttr();
}

TEST_F(OpenACCOpsTest, waitValuesTest) {
  testWaitValues<KernelsOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testWaitValues<ParallelOp>(b, context, loc, dtypes, dtypesWithoutNone);
  testWaitValues<SerialOp>(b, context, loc, dtypes, dtypesWithoutNone);
}

TEST_F(OpenACCOpsTest, loopOpGangVectorWorkerTest) {
  OwningOpRef<LoopOp> op = LoopOp::create(b, loc, TypeRange{}, ValueRange{});
  EXPECT_FALSE(op->hasGang());
  EXPECT_FALSE(op->hasVector());
  EXPECT_FALSE(op->hasWorker());
  for (auto d : dtypes) {
    EXPECT_FALSE(op->hasGang(d));
    EXPECT_FALSE(op->hasVector(d));
    EXPECT_FALSE(op->hasWorker(d));
  }

  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->setGangAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasGang());
  EXPECT_TRUE(op->hasGang(DeviceType::None));
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasGang(d));
  for (auto d : dtypes) {
    EXPECT_FALSE(op->hasVector(d));
    EXPECT_FALSE(op->hasWorker(d));
  }
  op->removeGangAttr();

  op->setWorkerAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasWorker());
  EXPECT_TRUE(op->hasWorker(DeviceType::None));
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasWorker(d));
  for (auto d : dtypes) {
    EXPECT_FALSE(op->hasGang(d));
    EXPECT_FALSE(op->hasVector(d));
  }
  op->removeWorkerAttr();

  op->setVectorAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasVector());
  EXPECT_TRUE(op->hasVector(DeviceType::None));
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasVector(d));
  for (auto d : dtypes) {
    EXPECT_FALSE(op->hasGang(d));
    EXPECT_FALSE(op->hasWorker(d));
  }
  op->removeVectorAttr();
}

TEST_F(OpenACCOpsTest, routineOpTest) {
  OwningOpRef<RoutineOp> op =
      RoutineOp::create(b, loc, TypeRange{}, ValueRange{});

  EXPECT_FALSE(op->hasSeq());
  EXPECT_FALSE(op->hasVector());
  EXPECT_FALSE(op->hasWorker());

  for (auto d : dtypes) {
    EXPECT_FALSE(op->hasSeq(d));
    EXPECT_FALSE(op->hasVector(d));
    EXPECT_FALSE(op->hasWorker(d));
  }

  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  op->setSeqAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasSeq());
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasSeq(d));
  op->removeSeqAttr();

  op->setVectorAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasVector());
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasVector(d));
  op->removeVectorAttr();

  op->setWorkerAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasWorker());
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasWorker(d));
  op->removeWorkerAttr();

  op->setGangAttr(b.getArrayAttr({dtypeNone}));
  EXPECT_TRUE(op->hasGang());
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->hasGang(d));
  op->removeGangAttr();

  op->setGangDimDeviceTypeAttr(b.getArrayAttr({dtypeNone}));
  op->setGangDimAttr(b.getArrayAttr({b.getIntegerAttr(b.getI64Type(), 8)}));
  EXPECT_TRUE(op->getGangDimValue().has_value());
  EXPECT_EQ(op->getGangDimValue().value(), 8);
  for (auto d : dtypesWithoutNone)
    EXPECT_FALSE(op->getGangDimValue(d).has_value());
  op->removeGangDimDeviceTypeAttr();
  op->removeGangDimAttr();

  op->setBindIdNameDeviceTypeAttr(
      b.getArrayAttr({DeviceTypeAttr::get(&context, DeviceType::Host)}));
  op->setBindStrNameDeviceTypeAttr(b.getArrayAttr({dtypeNone}));
  op->setBindIdNameAttr(
      b.getArrayAttr({SymbolRefAttr::get(&context, "test_symbol")}));
  op->setBindStrNameAttr(b.getArrayAttr({b.getStringAttr("fname")}));
  EXPECT_TRUE(op->getBindNameValue().has_value());
  EXPECT_TRUE(op->getBindNameValue(DeviceType::Host).has_value());
  EXPECT_EQ(std::visit(
                [](const auto &attr) -> std::string {
                  if constexpr (std::is_same_v<std::decay_t<decltype(attr)>,
                                               mlir::StringAttr>) {
                    return attr.str();
                  } else {
                    return attr.getLeafReference().str();
                  }
                },
                op->getBindNameValue().value()),
            "fname");
  EXPECT_EQ(std::visit(
                [](const auto &attr) -> std::string {
                  if constexpr (std::is_same_v<std::decay_t<decltype(attr)>,
                                               mlir::StringAttr>) {
                    return attr.str();
                  } else {
                    return attr.getLeafReference().str();
                  }
                },
                op->getBindNameValue(DeviceType::Host).value()),
            "test_symbol");
  for (auto d : dtypesWithoutNone) {
    if (d != DeviceType::Host)
      EXPECT_FALSE(op->getBindNameValue(d).has_value());
  }
  op->removeBindIdNameDeviceTypeAttr();
  op->removeBindStrNameDeviceTypeAttr();
  op->removeBindIdNameAttr();
  op->removeBindStrNameAttr();
}

TEST_F(OpenACCOpsTest, routineOpGetBindNameValueOnlyBindStrOrOnlyBindId) {
  // getBindNameValue(DeviceType) must not dereference when only one of bind(id)
  // or bind(name) is set (the other has no device-type array).
  OwningOpRef<RoutineOp> op =
      RoutineOp::create(b, loc, TypeRange{}, ValueRange{});

  auto dtypeNone = DeviceTypeAttr::get(&context, DeviceType::None);
  auto dtypeNvidia = DeviceTypeAttr::get(&context, DeviceType::Nvidia);

  // Only bind(name): no bindIdName/bindIdNameDeviceType. getBindNameValue
  // must not crash when looking up by device type.
  op->setBindStrNameDeviceTypeAttr(b.getArrayAttr({dtypeNvidia}));
  op->setBindStrNameAttr(b.getArrayAttr({b.getStringAttr("only_str_bind")}));
  EXPECT_TRUE(op->getBindNameValue(DeviceType::Nvidia).has_value());
  EXPECT_EQ(std::visit(
                [](const auto &attr) -> std::string {
                  if constexpr (std::is_same_v<std::decay_t<decltype(attr)>,
                                               mlir::StringAttr>) {
                    return attr.str();
                  } else {
                    return attr.getLeafReference().str();
                  }
                },
                op->getBindNameValue(DeviceType::Nvidia).value()),
            "only_str_bind");
  EXPECT_FALSE(op->getBindNameValue(DeviceType::Host).has_value());
  op->removeBindStrNameDeviceTypeAttr();
  op->removeBindStrNameAttr();

  // Only bind(id): no bindStrName/bindStrNameDeviceType. getBindNameValue
  // must not crash when looking up by device type.
  op->setBindIdNameDeviceTypeAttr(b.getArrayAttr({dtypeNone}));
  op->setBindIdNameAttr(
      b.getArrayAttr({SymbolRefAttr::get(&context, "only_id_bind")}));
  EXPECT_TRUE(op->getBindNameValue().has_value());
  EXPECT_FALSE(op->getBindNameValue(DeviceType::Nvidia).has_value());
  op->removeBindIdNameDeviceTypeAttr();
  op->removeBindIdNameAttr();
}

template <typename Op>
static void testShortDataEntryOpBuilders(OpBuilder &b, MLIRContext &context,
                                         Location loc, DataClause dataClause) {
  auto memrefTy = MemRefType::get({}, b.getI32Type());
  OwningOpRef<memref::AllocaOp> varPtrOp =
      memref::AllocaOp::create(b, loc, memrefTy);

  TypedValue<PointerLikeType> varPtr =
      cast<TypedValue<PointerLikeType>>(varPtrOp->getResult());
  OwningOpRef<Op> op = Op::create(b, loc, varPtr,
                                  /*structured=*/true, /*implicit=*/true);

  EXPECT_EQ(op->getVarPtr(), varPtr);
  EXPECT_EQ(op->getType(), memrefTy);
  EXPECT_EQ(op->getDataClause(), dataClause);
  EXPECT_TRUE(op->getImplicit());
  EXPECT_TRUE(op->getStructured());
  EXPECT_TRUE(op->getBounds().empty());
  EXPECT_FALSE(op->getVarPtrPtr());

  OwningOpRef<Op> op2 = Op::create(b, loc, varPtr,
                                   /*structured=*/false, /*implicit=*/false);
  EXPECT_FALSE(op2->getImplicit());
  EXPECT_FALSE(op2->getStructured());

  OwningOpRef<arith::ConstantIndexOp> extent =
      arith::ConstantIndexOp::create(b, loc, 1);
  OwningOpRef<DataBoundsOp> bounds =
      DataBoundsOp::create(b, loc, extent->getResult());
  OwningOpRef<Op> opWithBounds =
      Op::create(b, loc, varPtr,
                 /*structured=*/true, /*implicit=*/true, bounds->getResult());
  EXPECT_FALSE(opWithBounds->getBounds().empty());
  EXPECT_EQ(opWithBounds->getBounds().back(), bounds->getResult());

  OwningOpRef<Op> opWithName =
      Op::create(b, loc, varPtr,
                 /*structured=*/true, /*implicit=*/true, "varName");
  EXPECT_EQ(opWithName->getNameAttr().str(), "varName");
}

TEST_F(OpenACCOpsTest, shortDataEntryOpBuilder) {
  testShortDataEntryOpBuilders<PrivateOp>(b, context, loc,
                                          DataClause::acc_private);
  testShortDataEntryOpBuilders<FirstprivateOp>(b, context, loc,
                                               DataClause::acc_firstprivate);
  testShortDataEntryOpBuilders<ReductionOp>(b, context, loc,
                                            DataClause::acc_reduction);
  testShortDataEntryOpBuilders<DevicePtrOp>(b, context, loc,
                                            DataClause::acc_deviceptr);
  testShortDataEntryOpBuilders<PresentOp>(b, context, loc,
                                          DataClause::acc_present);
  testShortDataEntryOpBuilders<CopyinOp>(b, context, loc,
                                         DataClause::acc_copyin);
  testShortDataEntryOpBuilders<CreateOp>(b, context, loc,
                                         DataClause::acc_create);
  testShortDataEntryOpBuilders<NoCreateOp>(b, context, loc,
                                           DataClause::acc_no_create);
  testShortDataEntryOpBuilders<AttachOp>(b, context, loc,
                                         DataClause::acc_attach);
  testShortDataEntryOpBuilders<GetDevicePtrOp>(b, context, loc,
                                               DataClause::acc_getdeviceptr);
  testShortDataEntryOpBuilders<UpdateDeviceOp>(b, context, loc,
                                               DataClause::acc_update_device);
  testShortDataEntryOpBuilders<UseDeviceOp>(b, context, loc,
                                            DataClause::acc_use_device);
  testShortDataEntryOpBuilders<DeclareDeviceResidentOp>(
      b, context, loc, DataClause::acc_declare_device_resident);
  testShortDataEntryOpBuilders<DeclareLinkOp>(b, context, loc,
                                              DataClause::acc_declare_link);
  testShortDataEntryOpBuilders<CacheOp>(b, context, loc, DataClause::acc_cache);
}

template <typename Op>
static void testShortDataExitOpBuilders(OpBuilder &b, MLIRContext &context,
                                        Location loc, DataClause dataClause) {
  auto memrefTy = MemRefType::get({}, b.getI32Type());
  OwningOpRef<memref::AllocaOp> varPtrOp =
      memref::AllocaOp::create(b, loc, memrefTy);
  TypedValue<PointerLikeType> varPtr =
      cast<TypedValue<PointerLikeType>>(varPtrOp->getResult());

  OwningOpRef<GetDevicePtrOp> accPtrOp = GetDevicePtrOp::create(
      b, loc, varPtr, /*structured=*/true, /*implicit=*/true);
  TypedValue<PointerLikeType> accPtr =
      cast<TypedValue<PointerLikeType>>(accPtrOp->getResult());

  OwningOpRef<Op> op = Op::create(b, loc, accPtr, varPtr,
                                  /*structured=*/true, /*implicit=*/true);

  EXPECT_EQ(op->getVarPtr(), varPtr);
  EXPECT_EQ(op->getAccPtr(), accPtr);
  EXPECT_EQ(op->getDataClause(), dataClause);
  EXPECT_TRUE(op->getImplicit());
  EXPECT_TRUE(op->getStructured());
  EXPECT_TRUE(op->getBounds().empty());

  OwningOpRef<Op> op2 = Op::create(b, loc, accPtr, varPtr,
                                   /*structured=*/false, /*implicit=*/false);
  EXPECT_FALSE(op2->getImplicit());
  EXPECT_FALSE(op2->getStructured());

  OwningOpRef<arith::ConstantIndexOp> extent =
      arith::ConstantIndexOp::create(b, loc, 1);
  OwningOpRef<DataBoundsOp> bounds =
      DataBoundsOp::create(b, loc, extent->getResult());
  OwningOpRef<Op> opWithBounds =
      Op::create(b, loc, accPtr, varPtr,
                 /*structured=*/true, /*implicit=*/true, bounds->getResult());
  EXPECT_FALSE(opWithBounds->getBounds().empty());
  EXPECT_EQ(opWithBounds->getBounds().back(), bounds->getResult());

  OwningOpRef<Op> opWithName =
      Op::create(b, loc, accPtr, varPtr,
                 /*structured=*/true, /*implicit=*/true, "varName");
  EXPECT_EQ(opWithName->getNameAttr().str(), "varName");
}

TEST_F(OpenACCOpsTest, shortDataExitOpBuilder) {
  testShortDataExitOpBuilders<CopyoutOp>(b, context, loc,
                                         DataClause::acc_copyout);
  testShortDataExitOpBuilders<UpdateHostOp>(b, context, loc,
                                            DataClause::acc_update_host);
}

template <typename Op>
static void
testShortDataExitNoVarPtrOpBuilders(OpBuilder &b, MLIRContext &context,
                                    Location loc, DataClause dataClause) {
  auto memrefTy = MemRefType::get({}, b.getI32Type());
  OwningOpRef<memref::AllocaOp> varPtrOp =
      memref::AllocaOp::create(b, loc, memrefTy);
  TypedValue<PointerLikeType> varPtr =
      cast<TypedValue<PointerLikeType>>(varPtrOp->getResult());

  OwningOpRef<GetDevicePtrOp> accPtrOp = GetDevicePtrOp::create(
      b, loc, varPtr, /*structured=*/true, /*implicit=*/true);
  TypedValue<PointerLikeType> accPtr =
      cast<TypedValue<PointerLikeType>>(accPtrOp->getResult());

  OwningOpRef<Op> op = Op::create(b, loc, accPtr,
                                  /*structured=*/true, /*implicit=*/true);

  EXPECT_EQ(op->getAccPtr(), accPtr);
  EXPECT_EQ(op->getDataClause(), dataClause);
  EXPECT_TRUE(op->getImplicit());
  EXPECT_TRUE(op->getStructured());
  EXPECT_TRUE(op->getBounds().empty());

  OwningOpRef<Op> op2 = Op::create(b, loc, accPtr,
                                   /*structured=*/false, /*implicit=*/false);
  EXPECT_FALSE(op2->getImplicit());
  EXPECT_FALSE(op2->getStructured());

  OwningOpRef<arith::ConstantIndexOp> extent =
      arith::ConstantIndexOp::create(b, loc, 1);
  OwningOpRef<DataBoundsOp> bounds =
      DataBoundsOp::create(b, loc, extent->getResult());
  OwningOpRef<Op> opWithBounds =
      Op::create(b, loc, accPtr,
                 /*structured=*/true, /*implicit=*/true, bounds->getResult());
  EXPECT_FALSE(opWithBounds->getBounds().empty());
  EXPECT_EQ(opWithBounds->getBounds().back(), bounds->getResult());

  OwningOpRef<Op> opWithName =
      Op::create(b, loc, accPtr,
                 /*structured=*/true, /*implicit=*/true, "varName");
  EXPECT_EQ(opWithName->getNameAttr().str(), "varName");
}

TEST_F(OpenACCOpsTest, shortDataExitOpNoVarPtrBuilder) {
  testShortDataExitNoVarPtrOpBuilders<DeleteOp>(b, context, loc,
                                                DataClause::acc_delete);
  testShortDataExitNoVarPtrOpBuilders<DetachOp>(b, context, loc,
                                                DataClause::acc_detach);
}

template <typename Op>
static void
testShortDataEntryOpBuildersMappableVar(OpBuilder &b, MLIRContext &context,
                                        Location loc, DataClause dataClause) {
  auto int64Ty = b.getI64Type();
  auto memrefTy = MemRefType::get({}, int64Ty);
  OwningOpRef<memref::AllocaOp> varPtrOp =
      memref::AllocaOp::create(b, loc, memrefTy);
  SmallVector<Value> indices;
  OwningOpRef<memref::LoadOp> loadVarOp =
      memref::LoadOp::create(b, loc, int64Ty, varPtrOp->getResult(), indices);

  EXPECT_TRUE(isMappableType(loadVarOp->getResult().getType()));
  TypedValue<MappableType> var =
      cast<TypedValue<MappableType>>(loadVarOp->getResult());
  OwningOpRef<Op> op = Op::create(b, loc, var,
                                  /*structured=*/true, /*implicit=*/true);

  EXPECT_EQ(op->getVar(), var);
  EXPECT_EQ(op->getVarPtr(), nullptr);
  EXPECT_EQ(op->getType(), int64Ty);
  EXPECT_EQ(op->getVarType(), int64Ty);
  EXPECT_EQ(op->getDataClause(), dataClause);
  EXPECT_TRUE(op->getImplicit());
  EXPECT_TRUE(op->getStructured());
  EXPECT_TRUE(op->getBounds().empty());
  EXPECT_FALSE(op->getVarPtrPtr());
}

struct IntegerOpenACCMappableModel
    : public mlir::acc::MappableType::ExternalModel<IntegerOpenACCMappableModel,
                                                    IntegerType> {
  bool hasUnknownDimensions(mlir::Type type) const { return false; }
};

TEST_F(OpenACCOpsTest, mappableTypeBuilderDataEntry) {
  // First, set up the test by attaching MappableInterface to IntegerType.
  IntegerType i64ty = IntegerType::get(&context, 8);
  ASSERT_FALSE(isMappableType(i64ty));
  IntegerType::attachInterface<IntegerOpenACCMappableModel>(context);
  ASSERT_TRUE(isMappableType(i64ty));

  testShortDataEntryOpBuildersMappableVar<PrivateOp>(b, context, loc,
                                                     DataClause::acc_private);
  testShortDataEntryOpBuildersMappableVar<FirstprivateOp>(
      b, context, loc, DataClause::acc_firstprivate);
  testShortDataEntryOpBuildersMappableVar<ReductionOp>(
      b, context, loc, DataClause::acc_reduction);
  testShortDataEntryOpBuildersMappableVar<DevicePtrOp>(
      b, context, loc, DataClause::acc_deviceptr);
  testShortDataEntryOpBuildersMappableVar<PresentOp>(b, context, loc,
                                                     DataClause::acc_present);
  testShortDataEntryOpBuildersMappableVar<CopyinOp>(b, context, loc,
                                                    DataClause::acc_copyin);
  testShortDataEntryOpBuildersMappableVar<CreateOp>(b, context, loc,
                                                    DataClause::acc_create);
  testShortDataEntryOpBuildersMappableVar<NoCreateOp>(
      b, context, loc, DataClause::acc_no_create);
  testShortDataEntryOpBuildersMappableVar<AttachOp>(b, context, loc,
                                                    DataClause::acc_attach);
  testShortDataEntryOpBuildersMappableVar<GetDevicePtrOp>(
      b, context, loc, DataClause::acc_getdeviceptr);
  testShortDataEntryOpBuildersMappableVar<UpdateDeviceOp>(
      b, context, loc, DataClause::acc_update_device);
  testShortDataEntryOpBuildersMappableVar<UseDeviceOp>(
      b, context, loc, DataClause::acc_use_device);
  testShortDataEntryOpBuildersMappableVar<DeclareDeviceResidentOp>(
      b, context, loc, DataClause::acc_declare_device_resident);
  testShortDataEntryOpBuildersMappableVar<DeclareLinkOp>(
      b, context, loc, DataClause::acc_declare_link);
  testShortDataEntryOpBuildersMappableVar<CacheOp>(b, context, loc,
                                                   DataClause::acc_cache);
}
