//===- OpenACCClause.h - Classes for OpenACC clauses ------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// \file
// This file defines OpenACC AST classes for clauses.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_OPENACCCLAUSE_H
#define LLVM_CLANG_AST_OPENACCCLAUSE_H

#include "clang/AST/ASTContext.h"
#include "clang/AST/StmtIterator.h"
#include "clang/Basic/OpenACCKinds.h"
#include "llvm/ADT/STLExtras.h"

#include <utility>
#include <variant>

namespace clang {
/// This is the base type for all OpenACC Clauses.
class OpenACCClause {
  OpenACCClauseKind Kind;
  SourceRange Location;

protected:
  OpenACCClause(OpenACCClauseKind K, SourceLocation BeginLoc,
                SourceLocation EndLoc)
      : Kind(K), Location(BeginLoc, EndLoc) {
    assert(!BeginLoc.isInvalid() && !EndLoc.isInvalid() &&
           "Begin and end location must be valid for OpenACCClause");
      }

public:
  OpenACCClauseKind getClauseKind() const { return Kind; }
  SourceLocation getBeginLoc() const { return Location.getBegin(); }
  SourceLocation getEndLoc() const { return Location.getEnd(); }
  SourceRange getSourceRange() const { return Location; }

  static bool classof(const OpenACCClause *) { return true; }

  using child_iterator = StmtIterator;
  using const_child_iterator = ConstStmtIterator;
  using child_range = llvm::iterator_range<child_iterator>;
  using const_child_range = llvm::iterator_range<const_child_iterator>;

  child_range children();
  const_child_range children() const {
    return const_cast<OpenACCClause *>(this)->children();
  }

  virtual ~OpenACCClause() = default;
};

// Represents the 'auto' clause.
class OpenACCAutoClause : public OpenACCClause {
protected:
  OpenACCAutoClause(SourceLocation BeginLoc, SourceLocation EndLoc)
      : OpenACCClause(OpenACCClauseKind::Auto, BeginLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Auto;
  }

  static OpenACCAutoClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc);

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

// Represents the 'finalize' clause.
class OpenACCFinalizeClause : public OpenACCClause {
protected:
  OpenACCFinalizeClause(SourceLocation BeginLoc, SourceLocation EndLoc)
      : OpenACCClause(OpenACCClauseKind::Finalize, BeginLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Finalize;
  }

  static OpenACCFinalizeClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc);

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

// Represents the 'if_present' clause.
class OpenACCIfPresentClause : public OpenACCClause {
protected:
  OpenACCIfPresentClause(SourceLocation BeginLoc, SourceLocation EndLoc)
      : OpenACCClause(OpenACCClauseKind::IfPresent, BeginLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::IfPresent;
  }

  static OpenACCIfPresentClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc);

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

// Represents the 'independent' clause.
class OpenACCIndependentClause : public OpenACCClause {
protected:
  OpenACCIndependentClause(SourceLocation BeginLoc, SourceLocation EndLoc)
      : OpenACCClause(OpenACCClauseKind::Independent, BeginLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Independent;
  }

  static OpenACCIndependentClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc);

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};
// Represents the 'seq' clause.
class OpenACCSeqClause : public OpenACCClause {
protected:
  OpenACCSeqClause(SourceLocation BeginLoc, SourceLocation EndLoc)
      : OpenACCClause(OpenACCClauseKind::Seq, BeginLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Seq;
  }

  static OpenACCSeqClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc);

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};
// Represents the 'nohost' clause.
class OpenACCNoHostClause : public OpenACCClause {
protected:
  OpenACCNoHostClause(SourceLocation BeginLoc, SourceLocation EndLoc)
      : OpenACCClause(OpenACCClauseKind::NoHost, BeginLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::NoHost;
  }
  static OpenACCNoHostClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc, SourceLocation EndLoc);

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

/// Represents a clause that has a list of parameters.
class OpenACCClauseWithParams : public OpenACCClause {
  /// Location of the '('.
  SourceLocation LParenLoc;

protected:
  OpenACCClauseWithParams(OpenACCClauseKind K, SourceLocation BeginLoc,
                          SourceLocation LParenLoc, SourceLocation EndLoc)
      : OpenACCClause(K, BeginLoc, EndLoc), LParenLoc(LParenLoc) {}

public:
  static bool classof(const OpenACCClause *C);

  SourceLocation getLParenLoc() const { return LParenLoc; }

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }
  const_child_range children() const {
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

class OpenACCBindClause final : public OpenACCClauseWithParams {
  std::variant<const StringLiteral *, const IdentifierInfo *> Argument;

  OpenACCBindClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    const clang::StringLiteral *SL, SourceLocation EndLoc)
      : OpenACCClauseWithParams(OpenACCClauseKind::Bind, BeginLoc, LParenLoc,
                                EndLoc),
        Argument(SL) {}
  OpenACCBindClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    const IdentifierInfo *ID, SourceLocation EndLoc)
      : OpenACCClauseWithParams(OpenACCClauseKind::Bind, BeginLoc, LParenLoc,
                                EndLoc),
        Argument(ID) {}

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Bind;
  }
  static OpenACCBindClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   const IdentifierInfo *ID,
                                   SourceLocation EndLoc);
  static OpenACCBindClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   const StringLiteral *SL,
                                   SourceLocation EndLoc);

  bool isStringArgument() const {
    return std::holds_alternative<const StringLiteral *>(Argument);
  }

  const StringLiteral *getStringArgument() const {
    return std::get<const StringLiteral *>(Argument);
  }

  bool isIdentifierArgument() const {
    return std::holds_alternative<const IdentifierInfo *>(Argument);
  }

  const IdentifierInfo *getIdentifierArgument() const {
    return std::get<const IdentifierInfo *>(Argument);
  }
};

bool operator==(const OpenACCBindClause &LHS, const OpenACCBindClause &RHS);
inline bool operator!=(const OpenACCBindClause &LHS,
                       const OpenACCBindClause &RHS) {
  return !(LHS == RHS);
}

using DeviceTypeArgument = IdentifierLoc;
/// A 'device_type' or 'dtype' clause, takes a list of either an 'asterisk' or
/// an identifier. The 'asterisk' means 'the rest'.
class OpenACCDeviceTypeClause final
    : public OpenACCClauseWithParams,
      private llvm::TrailingObjects<OpenACCDeviceTypeClause,
                                   DeviceTypeArgument> {
  friend TrailingObjects;
  // Data stored in trailing objects as IdentifierInfo* /SourceLocation pairs. A
  // nullptr IdentifierInfo* represents an asterisk.
  unsigned NumArchs;
  OpenACCDeviceTypeClause(OpenACCClauseKind K, SourceLocation BeginLoc,
                          SourceLocation LParenLoc,
                          ArrayRef<DeviceTypeArgument> Archs,
                          SourceLocation EndLoc)
      : OpenACCClauseWithParams(K, BeginLoc, LParenLoc, EndLoc),
        NumArchs(Archs.size()) {
    assert(
        (K == OpenACCClauseKind::DeviceType || K == OpenACCClauseKind::DType) &&
        "Invalid clause kind for device-type");

    assert(!llvm::any_of(Archs, [](const DeviceTypeArgument &Arg) {
      return Arg.getLoc().isInvalid();
    }) && "Invalid SourceLocation for an argument");

    assert((Archs.size() == 1 ||
            !llvm::any_of(Archs,
                          [](const DeviceTypeArgument &Arg) {
                            return Arg.getIdentifierInfo() == nullptr;
                          })) &&
           "Only a single asterisk version is permitted, and must be the "
           "only one");

    llvm::uninitialized_copy(Archs, getTrailingObjects());
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::DType ||
           C->getClauseKind() == OpenACCClauseKind::DeviceType;
  }
  bool hasAsterisk() const {
    return getArchitectures().size() > 0 &&
           getArchitectures()[0].getIdentifierInfo() == nullptr;
  }

  ArrayRef<DeviceTypeArgument> getArchitectures() const {
    return getTrailingObjects(NumArchs);
  }

  static OpenACCDeviceTypeClause *
  Create(const ASTContext &C, OpenACCClauseKind K, SourceLocation BeginLoc,
         SourceLocation LParenLoc, ArrayRef<DeviceTypeArgument> Archs,
         SourceLocation EndLoc);
};

/// A 'default' clause, has the optional 'none' or 'present' argument.
class OpenACCDefaultClause : public OpenACCClauseWithParams {
  friend class ASTReaderStmt;
  friend class ASTWriterStmt;

  OpenACCDefaultClauseKind DefaultClauseKind;

protected:
  OpenACCDefaultClause(OpenACCDefaultClauseKind K, SourceLocation BeginLoc,
                       SourceLocation LParenLoc, SourceLocation EndLoc)
      : OpenACCClauseWithParams(OpenACCClauseKind::Default, BeginLoc, LParenLoc,
                                EndLoc),
        DefaultClauseKind(K) {
    assert((DefaultClauseKind == OpenACCDefaultClauseKind::None ||
            DefaultClauseKind == OpenACCDefaultClauseKind::Present) &&
           "Invalid Clause Kind");
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Default;
  }
  OpenACCDefaultClauseKind getDefaultClauseKind() const {
    return DefaultClauseKind;
  }

  static OpenACCDefaultClause *Create(const ASTContext &C,
                                      OpenACCDefaultClauseKind K,
                                      SourceLocation BeginLoc,
                                      SourceLocation LParenLoc,
                                      SourceLocation EndLoc);
};

/// Represents one of the handful of classes that has an optional/required
/// 'condition' expression as an argument.
class OpenACCClauseWithCondition : public OpenACCClauseWithParams {
  Expr *ConditionExpr = nullptr;

protected:
  OpenACCClauseWithCondition(OpenACCClauseKind K, SourceLocation BeginLoc,
                             SourceLocation LParenLoc, Expr *ConditionExpr,
                             SourceLocation EndLoc)
      : OpenACCClauseWithParams(K, BeginLoc, LParenLoc, EndLoc),
        ConditionExpr(ConditionExpr) {}

public:
  static bool classof(const OpenACCClause *C);

  bool hasConditionExpr() const { return ConditionExpr; }
  const Expr *getConditionExpr() const { return ConditionExpr; }
  Expr *getConditionExpr() { return ConditionExpr; }

  child_range children() {
    if (ConditionExpr)
      return child_range(reinterpret_cast<Stmt **>(&ConditionExpr),
                         reinterpret_cast<Stmt **>(&ConditionExpr + 1));
    return child_range(child_iterator(), child_iterator());
  }

  const_child_range children() const {
    if (ConditionExpr)
      return const_child_range(
          reinterpret_cast<Stmt *const *>(&ConditionExpr),
          reinterpret_cast<Stmt *const *>(&ConditionExpr + 1));
    return const_child_range(const_child_iterator(), const_child_iterator());
  }
};

/// An 'if' clause, which has a required condition expression.
class OpenACCIfClause : public OpenACCClauseWithCondition {
protected:
  OpenACCIfClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                  Expr *ConditionExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::If;
  }
  static OpenACCIfClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                 SourceLocation LParenLoc, Expr *ConditionExpr,
                                 SourceLocation EndLoc);
};

/// A 'self' clause, which has an optional condition expression, or, in the
/// event of an 'update' directive, contains a 'VarList'.
class OpenACCSelfClause final
    : public OpenACCClauseWithParams,
      private llvm::TrailingObjects<OpenACCSelfClause, Expr *> {
  friend TrailingObjects;
  // Holds whether this HAS a condition expression. Lacks a value if this is NOT
  // a condition-expr self clause.
  std::optional<bool> HasConditionExpr;
  // Holds the number of stored expressions.  In the case of a condition-expr
  // self clause, this is expected to be ONE (and there to be 1 trailing
  // object), whether or not that is null.
  unsigned NumExprs;

  OpenACCSelfClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    Expr *ConditionExpr, SourceLocation EndLoc);
  OpenACCSelfClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    ArrayRef<Expr *> VarList, SourceLocation EndLoc);

  // Intentionally internal, meant to be an implementation detail of everything
  // else. All non-internal uses should go through getConditionExpr/getVarList.
  ArrayRef<Expr *> getExprs() const { return getTrailingObjects(NumExprs); }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Self;
  }

  bool isConditionExprClause() const { return HasConditionExpr.has_value(); }
  bool isVarListClause() const { return !isConditionExprClause(); }
  bool isEmptySelfClause() const {
    return (isConditionExprClause() && !hasConditionExpr()) ||
           (!isConditionExprClause() && getVarList().empty());
  }

  bool hasConditionExpr() const {
    assert(HasConditionExpr.has_value() &&
           "VarList Self Clause asked about condition expression");
    return *HasConditionExpr;
  }

  const Expr *getConditionExpr() const {
    assert(HasConditionExpr.has_value() &&
           "VarList Self Clause asked about condition expression");
    assert(getExprs().size() == 1 &&
           "ConditionExpr Self Clause with too many Exprs");
    return getExprs()[0];
  }

  Expr *getConditionExpr() {
    assert(HasConditionExpr.has_value() &&
           "VarList Self Clause asked about condition expression");
    assert(getExprs().size() == 1 &&
           "ConditionExpr Self Clause with too many Exprs");
    return getExprs()[0];
  }

  ArrayRef<Expr *> getVarList() {
    assert(!HasConditionExpr.has_value() &&
           "Condition Expr self clause asked about var list");
    return getExprs();
  }
  ArrayRef<Expr *> getVarList() const {
    assert(!HasConditionExpr.has_value() &&
           "Condition Expr self clause asked about var list");
    return getExprs();
  }

  child_range children() {
    return child_range(
        reinterpret_cast<Stmt **>(getTrailingObjects()),
        reinterpret_cast<Stmt **>(getTrailingObjects() + NumExprs));
  }

  const_child_range children() const {
    return const_cast<OpenACCSelfClause *>(this)->children();
  }

  static OpenACCSelfClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   Expr *ConditionExpr, SourceLocation EndLoc);
  static OpenACCSelfClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   ArrayRef<Expr *> ConditionExpr,
                                   SourceLocation EndLoc);
};

/// Represents a clause that has one or more expressions associated with it.
class OpenACCClauseWithExprs : public OpenACCClauseWithParams {
  MutableArrayRef<Expr *> Exprs;

protected:
  OpenACCClauseWithExprs(OpenACCClauseKind K, SourceLocation BeginLoc,
                         SourceLocation LParenLoc, SourceLocation EndLoc)
      : OpenACCClauseWithParams(K, BeginLoc, LParenLoc, EndLoc) {}

  /// Used only for initialization, the leaf class can initialize this to
  /// trailing storage.
  void setExprs(MutableArrayRef<Expr *> NewExprs) {
    assert(Exprs.empty() && "Cannot change Exprs list");
    Exprs = NewExprs;
  }

  /// Used only for initialization, the leaf class can initialize this to
  /// trailing storage, and initialize the data in the trailing storage as well.
  void setExprs(MutableArrayRef<Expr *> NewStorage, ArrayRef<Expr *> Exprs) {
    assert(NewStorage.size() == Exprs.size());
    llvm::uninitialized_copy(Exprs, NewStorage.begin());
    setExprs(NewStorage);
  }

  /// Gets the entire list of expressions, but leave it to the
  /// individual clauses to expose this how they'd like.
  ArrayRef<Expr *> getExprs() const { return Exprs; }

public:
  static bool classof(const OpenACCClause *C);
  child_range children() {
    return child_range(reinterpret_cast<Stmt **>(Exprs.begin()),
                       reinterpret_cast<Stmt **>(Exprs.end()));
  }

  const_child_range children() const {
    return const_cast<OpenACCClauseWithExprs *>(this)->children();
  }
};

// Represents the 'devnum' and expressions lists for the 'wait' clause.
class OpenACCWaitClause final
    : public OpenACCClauseWithExprs,
      private llvm::TrailingObjects<OpenACCWaitClause, Expr *> {
  friend TrailingObjects;
  SourceLocation QueuesLoc;
  OpenACCWaitClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    Expr *DevNumExpr, SourceLocation QueuesLoc,
                    ArrayRef<Expr *> QueueIdExprs, SourceLocation EndLoc)
      : OpenACCClauseWithExprs(OpenACCClauseKind::Wait, BeginLoc, LParenLoc,
                               EndLoc),
        QueuesLoc(QueuesLoc) {
    // The first element of the trailing storage is always the devnum expr,
    // whether it is used or not.
    auto *Exprs = getTrailingObjects();
    llvm::uninitialized_copy(ArrayRef(DevNumExpr), Exprs);
    llvm::uninitialized_copy(QueueIdExprs, Exprs + 1);
    setExprs(getTrailingObjects(QueueIdExprs.size() + 1));
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Wait;
  }
  static OpenACCWaitClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc, Expr *DevNumExpr,
                                   SourceLocation QueuesLoc,
                                   ArrayRef<Expr *> QueueIdExprs,
                                   SourceLocation EndLoc);

  bool hasQueuesTag() const { return !QueuesLoc.isInvalid(); }
  SourceLocation getQueuesLoc() const { return QueuesLoc; }
  bool hasDevNumExpr() const { return getExprs()[0]; }
  Expr *getDevNumExpr() const { return getExprs()[0]; }
  ArrayRef<Expr *> getQueueIdExprs() {
    return OpenACCClauseWithExprs::getExprs().drop_front();
  }
  ArrayRef<Expr *> getQueueIdExprs() const {
    return OpenACCClauseWithExprs::getExprs().drop_front();
  }
  // If this is a plain `wait` (no parens) this returns 'false'. Else Sema/Parse
  // ensures we have at least one QueueId expression.
  bool hasExprs() const { return getLParenLoc().isValid(); }
};

class OpenACCNumGangsClause final
    : public OpenACCClauseWithExprs,
      private llvm::TrailingObjects<OpenACCNumGangsClause, Expr *> {
  friend TrailingObjects;

  OpenACCNumGangsClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                        ArrayRef<Expr *> IntExprs, SourceLocation EndLoc)
      : OpenACCClauseWithExprs(OpenACCClauseKind::NumGangs, BeginLoc, LParenLoc,
                               EndLoc) {
    setExprs(getTrailingObjects(IntExprs.size()), IntExprs);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::NumGangs;
  }
  static OpenACCNumGangsClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> IntExprs, SourceLocation EndLoc);

  ArrayRef<Expr *> getIntExprs() { return OpenACCClauseWithExprs::getExprs(); }

  ArrayRef<Expr *> getIntExprs() const {
    return OpenACCClauseWithExprs::getExprs();
  }
};

class OpenACCTileClause final
    : public OpenACCClauseWithExprs,
      private llvm::TrailingObjects<OpenACCTileClause, Expr *> {
  friend TrailingObjects;
  OpenACCTileClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    ArrayRef<Expr *> SizeExprs, SourceLocation EndLoc)
      : OpenACCClauseWithExprs(OpenACCClauseKind::Tile, BeginLoc, LParenLoc,
                               EndLoc) {
    setExprs(getTrailingObjects(SizeExprs.size()), SizeExprs);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Tile;
  }
  static OpenACCTileClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   ArrayRef<Expr *> SizeExprs,
                                   SourceLocation EndLoc);
  ArrayRef<Expr *> getSizeExprs() { return OpenACCClauseWithExprs::getExprs(); }

  ArrayRef<Expr *> getSizeExprs() const {
    return OpenACCClauseWithExprs::getExprs();
  }
};

/// Represents one of a handful of clauses that have a single integer
/// expression.
class OpenACCClauseWithSingleIntExpr : public OpenACCClauseWithExprs {
  Expr *IntExpr;

protected:
  OpenACCClauseWithSingleIntExpr(OpenACCClauseKind K, SourceLocation BeginLoc,
                                 SourceLocation LParenLoc, Expr *IntExpr,
                                 SourceLocation EndLoc)
      : OpenACCClauseWithExprs(K, BeginLoc, LParenLoc, EndLoc),
        IntExpr(IntExpr) {
    if (IntExpr)
      setExprs(MutableArrayRef<Expr *>{&this->IntExpr, 1});
  }

public:
  static bool classof(const OpenACCClause *C);
  bool hasIntExpr() const { return !getExprs().empty(); }
  const Expr *getIntExpr() const {
    return hasIntExpr() ? getExprs()[0] : nullptr;
  }

  Expr *getIntExpr() { return hasIntExpr() ? getExprs()[0] : nullptr; };
};

class OpenACCGangClause final
    : public OpenACCClauseWithExprs,
      private llvm::TrailingObjects<OpenACCGangClause, Expr *, OpenACCGangKind> {
  friend TrailingObjects;
protected:
  OpenACCGangClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    ArrayRef<OpenACCGangKind> GangKinds,
                    ArrayRef<Expr *> IntExprs, SourceLocation EndLoc);

  OpenACCGangKind getGangKind(unsigned I) const {
    return getTrailingObjects<OpenACCGangKind>()[I];
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Gang;
  }

  size_t numTrailingObjects(OverloadToken<Expr *>) const {
    return getNumExprs();
  }

  unsigned getNumExprs() const { return getExprs().size(); }
  std::pair<OpenACCGangKind, const Expr *> getExpr(unsigned I) const {
    return {getGangKind(I), getExprs()[I]};
  }

  bool hasExprOfKind(OpenACCGangKind GK) const {
    for (unsigned I = 0; I < getNumExprs(); ++I) {
      if (getGangKind(I) == GK)
        return true;
    }
    return false;
  }

  static OpenACCGangClause *
  Create(const ASTContext &Ctx, SourceLocation BeginLoc,
         SourceLocation LParenLoc, ArrayRef<OpenACCGangKind> GangKinds,
         ArrayRef<Expr *> IntExprs, SourceLocation EndLoc);
};

class OpenACCWorkerClause : public OpenACCClauseWithSingleIntExpr {
protected:
  OpenACCWorkerClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                      Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Worker;
  }

  static OpenACCWorkerClause *Create(const ASTContext &Ctx,
                                     SourceLocation BeginLoc,
                                     SourceLocation LParenLoc, Expr *IntExpr,
                                     SourceLocation EndLoc);
};

class OpenACCVectorClause : public OpenACCClauseWithSingleIntExpr {
protected:
  OpenACCVectorClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                      Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Vector;
  }

  static OpenACCVectorClause *Create(const ASTContext &Ctx,
                                     SourceLocation BeginLoc,
                                     SourceLocation LParenLoc, Expr *IntExpr,
                                     SourceLocation EndLoc);
};

class OpenACCNumWorkersClause : public OpenACCClauseWithSingleIntExpr {
  OpenACCNumWorkersClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                          Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::NumWorkers;
  }
  static OpenACCNumWorkersClause *Create(const ASTContext &C,
                                         SourceLocation BeginLoc,
                                         SourceLocation LParenLoc,
                                         Expr *IntExpr, SourceLocation EndLoc);
};

class OpenACCVectorLengthClause : public OpenACCClauseWithSingleIntExpr {
  OpenACCVectorLengthClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                            Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::VectorLength;
  }
  static OpenACCVectorLengthClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         Expr *IntExpr, SourceLocation EndLoc);
};

class OpenACCAsyncClause : public OpenACCClauseWithSingleIntExpr {
  OpenACCAsyncClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                     Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Async;
  }
  static OpenACCAsyncClause *Create(const ASTContext &C,
                                    SourceLocation BeginLoc,
                                    SourceLocation LParenLoc, Expr *IntExpr,
                                    SourceLocation EndLoc);
};

class OpenACCDeviceNumClause : public OpenACCClauseWithSingleIntExpr {
  OpenACCDeviceNumClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                     Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::DeviceNum;
  }
  static OpenACCDeviceNumClause *Create(const ASTContext &C,
                                        SourceLocation BeginLoc,
                                        SourceLocation LParenLoc, Expr *IntExpr,
                                        SourceLocation EndLoc);
};

class OpenACCDefaultAsyncClause : public OpenACCClauseWithSingleIntExpr {
  OpenACCDefaultAsyncClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                            Expr *IntExpr, SourceLocation EndLoc);

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::DefaultAsync;
  }
  static OpenACCDefaultAsyncClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         Expr *IntExpr, SourceLocation EndLoc);
};

/// Represents a 'collapse' clause on a 'loop' construct. This clause takes an
/// integer constant expression 'N' that represents how deep to collapse the
/// construct. It also takes an optional 'force' tag that permits intervening
/// code in the loops.
class OpenACCCollapseClause : public OpenACCClauseWithSingleIntExpr {
  bool HasForce = false;

  OpenACCCollapseClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                        bool HasForce, Expr *LoopCount, SourceLocation EndLoc);

public:
  const Expr *getLoopCount() const { return getIntExpr(); }
  Expr *getLoopCount() { return getIntExpr(); }

  bool hasForce() const { return HasForce; }

  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Collapse;
  }

  static OpenACCCollapseClause *Create(const ASTContext &C,
                                       SourceLocation BeginLoc,
                                       SourceLocation LParenLoc, bool HasForce,
                                       Expr *LoopCount, SourceLocation EndLoc);
};

/// Represents a clause with one or more 'var' objects, represented as an expr,
/// as its arguments. Var-list is expected to be stored in trailing storage.
/// For now, we're just storing the original expression in its entirety, unlike
/// OMP which has to do a bunch of work to create a private.
class OpenACCClauseWithVarList : public OpenACCClauseWithExprs {
protected:
  OpenACCClauseWithVarList(OpenACCClauseKind K, SourceLocation BeginLoc,
                           SourceLocation LParenLoc, SourceLocation EndLoc)
      : OpenACCClauseWithExprs(K, BeginLoc, LParenLoc, EndLoc) {}

public:
  static bool classof(const OpenACCClause *C);
  ArrayRef<Expr *> getVarList() { return getExprs(); }
  ArrayRef<Expr *> getVarList() const { return getExprs(); }
};

// Represents all the data needed for recipe generation.  The declaration and
// init are stored separately, because in the case of subscripts, we do the
// alloca at the level of the base, and the init at the element level.
struct OpenACCPrivateRecipe {
  VarDecl *AllocaDecl;

  OpenACCPrivateRecipe(VarDecl *A) : AllocaDecl(A) {}

  bool isSet() const { return AllocaDecl; }

  static OpenACCPrivateRecipe Empty() {
    return OpenACCPrivateRecipe(/*AllocaDecl=*/nullptr);
  }
};

class OpenACCPrivateClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCPrivateClause, Expr *,
                                    OpenACCPrivateRecipe> {
  friend TrailingObjects;

  OpenACCPrivateClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                       ArrayRef<Expr *> VarList,
                       ArrayRef<OpenACCPrivateRecipe> InitRecipes,
                       SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Private, BeginLoc,
                                 LParenLoc, EndLoc) {
    assert(VarList.size() == InitRecipes.size());
    setExprs(getTrailingObjects<Expr *>(VarList.size()), VarList);
    llvm::uninitialized_copy(InitRecipes,
                             getTrailingObjects<OpenACCPrivateRecipe>());
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Private;
  }
  // Gets a list of 'made up' `VarDecl` objects that can be used by codegen to
  // ensure that we properly initialize each of these variables.
  ArrayRef<OpenACCPrivateRecipe> getInitRecipes() {
    return ArrayRef<OpenACCPrivateRecipe>{
        getTrailingObjects<OpenACCPrivateRecipe>(), getExprs().size()};
  }

  ArrayRef<OpenACCPrivateRecipe> getInitRecipes() const {
    return ArrayRef<OpenACCPrivateRecipe>{
        getTrailingObjects<OpenACCPrivateRecipe>(), getExprs().size()};
  }

  static OpenACCPrivateClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, ArrayRef<OpenACCPrivateRecipe> InitRecipes,
         SourceLocation EndLoc);

  size_t numTrailingObjects(OverloadToken<Expr *>) const {
    return getExprs().size();
  }
};

// A 'pair' to stand in for the recipe.  RecipeDecl is the main declaration, and
// InitFromTemporary is the 'temp' declaration we put in to be 'copied from'.
struct OpenACCFirstPrivateRecipe {
  VarDecl *AllocaDecl;
  VarDecl *InitFromTemporary;
  OpenACCFirstPrivateRecipe(VarDecl *A, VarDecl *T)
      : AllocaDecl(A), InitFromTemporary(T) {
    assert(!InitFromTemporary || InitFromTemporary->getInit() == nullptr);
  }

  bool isSet() const { return AllocaDecl; }

  static OpenACCFirstPrivateRecipe Empty() {
    return OpenACCFirstPrivateRecipe(/*AllocaDecl=*/nullptr,
                                     /*InitFromTemporary=*/nullptr);
  }
};

class OpenACCFirstPrivateClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCFirstPrivateClause, Expr *,
                                    OpenACCFirstPrivateRecipe> {
  friend TrailingObjects;

  OpenACCFirstPrivateClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                            ArrayRef<Expr *> VarList,
                            ArrayRef<OpenACCFirstPrivateRecipe> InitRecipes,
                            SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::FirstPrivate, BeginLoc,
                                 LParenLoc, EndLoc) {
    assert(VarList.size() == InitRecipes.size());
    setExprs(getTrailingObjects<Expr *>(VarList.size()), VarList);
    llvm::uninitialized_copy(InitRecipes,
                             getTrailingObjects<OpenACCFirstPrivateRecipe>());
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::FirstPrivate;
  }

  // Gets a list of 'made up' `VarDecl` objects that can be used by codegen to
  // ensure that we properly initialize each of these variables.
  ArrayRef<OpenACCFirstPrivateRecipe> getInitRecipes() {
    return ArrayRef<OpenACCFirstPrivateRecipe>{
        getTrailingObjects<OpenACCFirstPrivateRecipe>(), getExprs().size()};
  }

  ArrayRef<OpenACCFirstPrivateRecipe> getInitRecipes() const {
    return ArrayRef<OpenACCFirstPrivateRecipe>{
        getTrailingObjects<OpenACCFirstPrivateRecipe>(), getExprs().size()};
  }

  static OpenACCFirstPrivateClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList,
         ArrayRef<OpenACCFirstPrivateRecipe> InitRecipes,
         SourceLocation EndLoc);

  size_t numTrailingObjects(OverloadToken<Expr *>) const {
    return getExprs().size();
  }
};

class OpenACCDevicePtrClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCDevicePtrClause, Expr *> {
  friend TrailingObjects;

  OpenACCDevicePtrClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                         ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::DevicePtr, BeginLoc,
                                 LParenLoc, EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::DevicePtr;
  }
  static OpenACCDevicePtrClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCAttachClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCAttachClause, Expr *> {
  friend TrailingObjects;

  OpenACCAttachClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                      ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Attach, BeginLoc, LParenLoc,
                                 EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Attach;
  }
  static OpenACCAttachClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCDetachClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCDetachClause, Expr *> {
  friend TrailingObjects;

  OpenACCDetachClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                      ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Detach, BeginLoc, LParenLoc,
                                 EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Detach;
  }
  static OpenACCDetachClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCDeleteClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCDeleteClause, Expr *> {
  friend TrailingObjects;

  OpenACCDeleteClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                      ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Delete, BeginLoc, LParenLoc,
                                 EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Delete;
  }
  static OpenACCDeleteClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCUseDeviceClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCUseDeviceClause, Expr *> {
  friend TrailingObjects;

  OpenACCUseDeviceClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                         ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::UseDevice, BeginLoc,
                                 LParenLoc, EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::UseDevice;
  }
  static OpenACCUseDeviceClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCNoCreateClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCNoCreateClause, Expr *> {
  friend TrailingObjects;

  OpenACCNoCreateClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                        ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::NoCreate, BeginLoc,
                                 LParenLoc, EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::NoCreate;
  }
  static OpenACCNoCreateClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCPresentClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCPresentClause, Expr *> {
  friend TrailingObjects;

  OpenACCPresentClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                       ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Present, BeginLoc,
                                 LParenLoc, EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Present;
  }
  static OpenACCPresentClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};
class OpenACCHostClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCHostClause, Expr *> {
  friend TrailingObjects;

  OpenACCHostClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Host, BeginLoc, LParenLoc,
                                 EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Host;
  }
  static OpenACCHostClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   ArrayRef<Expr *> VarList,
                                   SourceLocation EndLoc);
};

class OpenACCDeviceClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCDeviceClause, Expr *> {
  friend TrailingObjects;

  OpenACCDeviceClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                      ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Device, BeginLoc, LParenLoc,
                                 EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Device;
  }
  static OpenACCDeviceClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

class OpenACCCopyClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCCopyClause, Expr *> {
  friend TrailingObjects;
  OpenACCModifierKind Modifiers;

  OpenACCCopyClause(OpenACCClauseKind Spelling, SourceLocation BeginLoc,
                    SourceLocation LParenLoc, OpenACCModifierKind Mods,
                    ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(Spelling, BeginLoc, LParenLoc, EndLoc),
        Modifiers(Mods) {
    assert((Spelling == OpenACCClauseKind::Copy ||
            Spelling == OpenACCClauseKind::PCopy ||
            Spelling == OpenACCClauseKind::PresentOrCopy) &&
           "Invalid clause kind for copy-clause");
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Copy ||
           C->getClauseKind() == OpenACCClauseKind::PCopy ||
           C->getClauseKind() == OpenACCClauseKind::PresentOrCopy;
  }
  static OpenACCCopyClause *
  Create(const ASTContext &C, OpenACCClauseKind Spelling,
         SourceLocation BeginLoc, SourceLocation LParenLoc,
         OpenACCModifierKind Mods, ArrayRef<Expr *> VarList,
         SourceLocation EndLoc);

  OpenACCModifierKind getModifierList() const { return Modifiers; }
};

class OpenACCCopyInClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCCopyInClause, Expr *> {
  friend TrailingObjects;
  OpenACCModifierKind Modifiers;

  OpenACCCopyInClause(OpenACCClauseKind Spelling, SourceLocation BeginLoc,
                      SourceLocation LParenLoc, OpenACCModifierKind Mods,
                      ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(Spelling, BeginLoc, LParenLoc, EndLoc),
        Modifiers(Mods) {
    assert((Spelling == OpenACCClauseKind::CopyIn ||
            Spelling == OpenACCClauseKind::PCopyIn ||
            Spelling == OpenACCClauseKind::PresentOrCopyIn) &&
           "Invalid clause kind for copyin-clause");
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::CopyIn ||
           C->getClauseKind() == OpenACCClauseKind::PCopyIn ||
           C->getClauseKind() == OpenACCClauseKind::PresentOrCopyIn;
  }
  OpenACCModifierKind getModifierList() const { return Modifiers; }
  static OpenACCCopyInClause *
  Create(const ASTContext &C, OpenACCClauseKind Spelling,
         SourceLocation BeginLoc, SourceLocation LParenLoc,
         OpenACCModifierKind Mods, ArrayRef<Expr *> VarList,
         SourceLocation EndLoc);
};

class OpenACCCopyOutClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCCopyOutClause, Expr *> {
  friend TrailingObjects;
  OpenACCModifierKind Modifiers;

  OpenACCCopyOutClause(OpenACCClauseKind Spelling, SourceLocation BeginLoc,
                       SourceLocation LParenLoc, OpenACCModifierKind Mods,
                       ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(Spelling, BeginLoc, LParenLoc, EndLoc),
        Modifiers(Mods) {
    assert((Spelling == OpenACCClauseKind::CopyOut ||
            Spelling == OpenACCClauseKind::PCopyOut ||
            Spelling == OpenACCClauseKind::PresentOrCopyOut) &&
           "Invalid clause kind for copyout-clause");
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::CopyOut ||
           C->getClauseKind() == OpenACCClauseKind::PCopyOut ||
           C->getClauseKind() == OpenACCClauseKind::PresentOrCopyOut;
  }
  OpenACCModifierKind getModifierList() const { return Modifiers; }
  static OpenACCCopyOutClause *
  Create(const ASTContext &C, OpenACCClauseKind Spelling,
         SourceLocation BeginLoc, SourceLocation LParenLoc,
         OpenACCModifierKind Mods, ArrayRef<Expr *> VarList,
         SourceLocation EndLoc);
};

class OpenACCCreateClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCCreateClause, Expr *> {
  friend TrailingObjects;
  OpenACCModifierKind Modifiers;

  OpenACCCreateClause(OpenACCClauseKind Spelling, SourceLocation BeginLoc,
                      SourceLocation LParenLoc, OpenACCModifierKind Mods,
                      ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(Spelling, BeginLoc, LParenLoc, EndLoc),
        Modifiers(Mods) {
    assert((Spelling == OpenACCClauseKind::Create ||
            Spelling == OpenACCClauseKind::PCreate ||
            Spelling == OpenACCClauseKind::PresentOrCreate) &&
           "Invalid clause kind for create-clause");
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Create ||
           C->getClauseKind() == OpenACCClauseKind::PCreate ||
           C->getClauseKind() == OpenACCClauseKind::PresentOrCreate;
  }
  OpenACCModifierKind getModifierList() const { return Modifiers; }
  static OpenACCCreateClause *
  Create(const ASTContext &C, OpenACCClauseKind Spelling,
         SourceLocation BeginLoc, SourceLocation LParenLoc,
         OpenACCModifierKind Mods, ArrayRef<Expr *> VarList,
         SourceLocation EndLoc);
};

// A structure to stand in for the recipe on a reduction.  RecipeDecl is the
// 'main' declaration used for initializaiton, which is fixed.
struct OpenACCReductionRecipe {
  VarDecl *AllocaDecl;

  // A combiner recipe is represented by an operation expression.  However, in
  // order to generate these properly, we have to make up a LHS and a RHS
  // expression for the purposes of generation.
  struct CombinerRecipe {
    VarDecl *LHS;
    VarDecl *RHS;
    Expr *Op;
  };

  // Contains a collection of the recipe elements we need for the combiner:
  // -For Scalars, there will be 1 element, just the combiner for that scalar.
  // -For a struct with a valid operator, this will be 1 element, just that
  //  call.
  // -For a struct without the operator, this will be 1 element per field, which
  //  should be the combiner for that element.
  // -For an array of any of the above, it will be the above for the element.
  // Note: These are necessarily stored in either Trailing Storage (when in the
  // AST), or in a separate collection when being semantically analyzed.
  llvm::ArrayRef<CombinerRecipe> CombinerRecipes;

  bool isSet() const { return AllocaDecl; }

private:
  friend class OpenACCReductionClause;
  OpenACCReductionRecipe(VarDecl *A, llvm::ArrayRef<CombinerRecipe> Combiners)
      : AllocaDecl(A), CombinerRecipes(Combiners) {}
};

// A version of the above that is used for semantic analysis, at a time before
// the OpenACCReductionClause node has been created.  This one has storage for
// the CombinerRecipe, since Trailing storage for it doesn't exist yet.
struct OpenACCReductionRecipeWithStorage {
  VarDecl *AllocaDecl;
  llvm::SmallVector<OpenACCReductionRecipe::CombinerRecipe, 1> CombinerRecipes;

  OpenACCReductionRecipeWithStorage(
      VarDecl *A,
      llvm::ArrayRef<OpenACCReductionRecipe::CombinerRecipe> Combiners)
      : AllocaDecl(A), CombinerRecipes(Combiners) {}

  static OpenACCReductionRecipeWithStorage Empty() {
    return OpenACCReductionRecipeWithStorage(/*AllocaDecl=*/nullptr, {});
  }
};

class OpenACCReductionClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCReductionClause, Expr *,
                                    OpenACCReductionRecipe,
                                    OpenACCReductionRecipe::CombinerRecipe> {
  friend TrailingObjects;
  OpenACCReductionOperator Op;

  OpenACCReductionClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                         OpenACCReductionOperator Operator,
                         ArrayRef<Expr *> VarList,
                         ArrayRef<OpenACCReductionRecipeWithStorage> Recipes,
                         SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Reduction, BeginLoc,
                                 LParenLoc, EndLoc),
        Op(Operator) {
    assert(VarList.size() == Recipes.size());
    setExprs(getTrailingObjects<Expr *>(VarList.size()), VarList);

    // Since we're using trailing storage on this node to store the 'combiner'
    // recipes of the Reduction Recipes (which have a 1:M relationship), we need
    // to ensure we get the ArrayRef of each of our combiner 'correct'.
    OpenACCReductionRecipe::CombinerRecipe *CurCombinerLoc =
        getTrailingObjects<OpenACCReductionRecipe::CombinerRecipe>();
    for (const auto &[Idx, R] : llvm::enumerate(Recipes)) {

      // ArrayRef to the 'correct' data location in trailing storage.
      llvm::MutableArrayRef<OpenACCReductionRecipe::CombinerRecipe>
          NewCombiners{CurCombinerLoc, R.CombinerRecipes.size()};
      CurCombinerLoc += R.CombinerRecipes.size();

      llvm::uninitialized_copy(R.CombinerRecipes, NewCombiners.begin());

      // Placement new into the correct location in trailng storage.
      new (&getTrailingObjects<OpenACCReductionRecipe>()[Idx])
          OpenACCReductionRecipe(R.AllocaDecl, NewCombiners);
    }
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Reduction;
  }

  ArrayRef<OpenACCReductionRecipe> getRecipes() {
    return ArrayRef<OpenACCReductionRecipe>{
        getTrailingObjects<OpenACCReductionRecipe>(), getExprs().size()};
  }

  ArrayRef<OpenACCReductionRecipe> getRecipes() const {
    return ArrayRef<OpenACCReductionRecipe>{
        getTrailingObjects<OpenACCReductionRecipe>(), getExprs().size()};
  }

  static OpenACCReductionClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         OpenACCReductionOperator Operator, ArrayRef<Expr *> VarList,
         ArrayRef<OpenACCReductionRecipeWithStorage> Recipes,
         SourceLocation EndLoc);

  OpenACCReductionOperator getReductionOp() const { return Op; }

  size_t numTrailingObjects(OverloadToken<Expr *>) const {
    return getExprs().size();
  }
  size_t numTrailingObjects(OverloadToken<OpenACCReductionRecipe>) const {
    return getExprs().size();
  }
};

class OpenACCLinkClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCLinkClause, Expr *> {
  friend TrailingObjects;

  OpenACCLinkClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                    ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::Link, BeginLoc, LParenLoc,
                                 EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::Link;
  }

  static OpenACCLinkClause *Create(const ASTContext &C, SourceLocation BeginLoc,
                                   SourceLocation LParenLoc,
                                   ArrayRef<Expr *> VarList,
                                   SourceLocation EndLoc);
};

class OpenACCDeviceResidentClause final
    : public OpenACCClauseWithVarList,
      private llvm::TrailingObjects<OpenACCDeviceResidentClause, Expr *> {
  friend TrailingObjects;

  OpenACCDeviceResidentClause(SourceLocation BeginLoc, SourceLocation LParenLoc,
                              ArrayRef<Expr *> VarList, SourceLocation EndLoc)
      : OpenACCClauseWithVarList(OpenACCClauseKind::DeviceResident, BeginLoc,
                                 LParenLoc, EndLoc) {
    setExprs(getTrailingObjects(VarList.size()), VarList);
  }

public:
  static bool classof(const OpenACCClause *C) {
    return C->getClauseKind() == OpenACCClauseKind::DeviceResident;
  }

  static OpenACCDeviceResidentClause *
  Create(const ASTContext &C, SourceLocation BeginLoc, SourceLocation LParenLoc,
         ArrayRef<Expr *> VarList, SourceLocation EndLoc);
};

template <class Impl> class OpenACCClauseVisitor {
  Impl &getDerived() { return static_cast<Impl &>(*this); }

public:
  void VisitClauseList(ArrayRef<const OpenACCClause *> List) {
    for (const OpenACCClause *Clause : List)
      Visit(Clause);
  }

  void Visit(const OpenACCClause *C) {
    if (!C)
      return;

    switch (C->getClauseKind()) {
#define VISIT_CLAUSE(CLAUSE_NAME)                                              \
  case OpenACCClauseKind::CLAUSE_NAME:                                         \
    getDerived().Visit##CLAUSE_NAME##Clause(                                   \
        *cast<OpenACC##CLAUSE_NAME##Clause>(C));                               \
    return;
#define CLAUSE_ALIAS(ALIAS_NAME, CLAUSE_NAME, DEPRECATED)                      \
  case OpenACCClauseKind::ALIAS_NAME:                                          \
    getDerived().Visit##CLAUSE_NAME##Clause(                                   \
        *cast<OpenACC##CLAUSE_NAME##Clause>(C));                               \
    return;
#include "clang/Basic/OpenACCClauses.def"

    default:
      llvm_unreachable("Clause visitor not yet implemented");
    }
    llvm_unreachable("Invalid Clause kind");
  }

#define VISIT_CLAUSE(CLAUSE_NAME)                                              \
  void Visit##CLAUSE_NAME##Clause(                                             \
      const OpenACC##CLAUSE_NAME##Clause &Clause) {                            \
    return getDerived().VisitClause(Clause);                                   \
  }

#include "clang/Basic/OpenACCClauses.def"
};

class OpenACCClausePrinter final
    : public OpenACCClauseVisitor<OpenACCClausePrinter> {
  raw_ostream &OS;
  const PrintingPolicy &Policy;

  void printExpr(const Expr *E);

public:
  void VisitClauseList(ArrayRef<const OpenACCClause *> List) {
    for (const OpenACCClause *Clause : List) {
      Visit(Clause);

      if (Clause != List.back())
        OS << ' ';
    }
  }
  OpenACCClausePrinter(raw_ostream &OS, const PrintingPolicy &Policy)
      : OS(OS), Policy(Policy) {}

#define VISIT_CLAUSE(CLAUSE_NAME)                                              \
  void Visit##CLAUSE_NAME##Clause(const OpenACC##CLAUSE_NAME##Clause &Clause);
#include "clang/Basic/OpenACCClauses.def"
};

} // namespace clang

#endif // LLVM_CLANG_AST_OPENACCCLAUSE_H
