//===- StmtOpenACC.h - Classes for OpenACC directives  ----------*- 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 statement-level contructs.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_STMTOPENACC_H
#define LLVM_CLANG_AST_STMTOPENACC_H

#include "clang/AST/OpenACCClause.h"
#include "clang/AST/Stmt.h"
#include "clang/Basic/OpenACCKinds.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/STLExtras.h"
#include <memory>

namespace clang {
/// This is the base class for an OpenACC statement-level construct, other
/// construct types are expected to inherit from this.
class OpenACCConstructStmt : public Stmt {
  friend class ASTStmtWriter;
  friend class ASTStmtReader;
  /// The directive kind. Each implementation of this interface should handle
  /// specific kinds.
  OpenACCDirectiveKind Kind = OpenACCDirectiveKind::Invalid;
  /// The location of the directive statement, from the '#' to the last token of
  /// the directive.
  SourceRange Range;
  /// The location of the directive name.
  SourceLocation DirectiveLoc;

  /// The list of clauses.  This is stored here as an ArrayRef, as this is the
  /// most convienient place to access the list, however the list itself should
  /// be stored in leaf nodes, likely in trailing-storage.
  MutableArrayRef<const OpenACCClause *> Clauses;

protected:
  OpenACCConstructStmt(StmtClass SC, OpenACCDirectiveKind K,
                       SourceLocation Start, SourceLocation DirectiveLoc,
                       SourceLocation End)
      : Stmt(SC), Kind(K), Range(Start, End), DirectiveLoc(DirectiveLoc) {}

  // Used only for initialization, the leaf class can initialize this to
  // trailing storage.
  void setClauseList(MutableArrayRef<const OpenACCClause *> NewClauses) {
    assert(Clauses.empty() && "Cannot change clause list");
    Clauses = NewClauses;
  }

public:
  OpenACCDirectiveKind getDirectiveKind() const { return Kind; }

  static bool classof(const Stmt *S) {
    return S->getStmtClass() >= firstOpenACCConstructStmtConstant &&
           S->getStmtClass() <= lastOpenACCConstructStmtConstant;
  }

  SourceLocation getBeginLoc() const { return Range.getBegin(); }
  SourceLocation getEndLoc() const { return Range.getEnd(); }
  SourceLocation getDirectiveLoc() const { return DirectiveLoc; }
  ArrayRef<const OpenACCClause *> clauses() const { return Clauses; }

  child_range children() {
    return child_range(child_iterator(), child_iterator());
  }

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

/// This is a base class for any OpenACC statement-level constructs that have an
/// associated statement. This class is not intended to be instantiated, but is
/// a convenient place to hold the associated statement.
class OpenACCAssociatedStmtConstruct : public OpenACCConstructStmt {
  friend class ASTStmtWriter;
  friend class ASTStmtReader;
  template <typename Derived> friend class RecursiveASTVisitor;
  Stmt *AssociatedStmt = nullptr;

protected:
  OpenACCAssociatedStmtConstruct(StmtClass SC, OpenACCDirectiveKind K,
                                 SourceLocation Start,
                                 SourceLocation DirectiveLoc,
                                 SourceLocation End, Stmt *AssocStmt)
      : OpenACCConstructStmt(SC, K, Start, DirectiveLoc, End),
        AssociatedStmt(AssocStmt) {}

  void setAssociatedStmt(Stmt *S) { AssociatedStmt = S; }
  Stmt *getAssociatedStmt() { return AssociatedStmt; }
  const Stmt *getAssociatedStmt() const {
    return const_cast<OpenACCAssociatedStmtConstruct *>(this)
        ->getAssociatedStmt();
  }

public:
  static bool classof(const Stmt *T) {
    return false;
  }

  child_range children() {
    if (getAssociatedStmt())
      return child_range(&AssociatedStmt, &AssociatedStmt + 1);
    return child_range(child_iterator(), child_iterator());
  }

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

/// This class represents a compute construct, representing a 'Kind' of
/// `parallel', 'serial', or 'kernel'. These constructs are associated with a
/// 'structured block', defined as:
///
///  in C or C++, an executable statement, possibly compound, with a single
///  entry at the top and a single exit at the bottom
///
/// At the moment there is no real motivation to have a different AST node for
/// those three, as they are semantically identical, and have only minor
/// differences in the permitted list of clauses, which can be differentiated by
/// the 'Kind'.
class OpenACCComputeConstruct final
    : public OpenACCAssociatedStmtConstruct,
      private llvm::TrailingObjects<OpenACCComputeConstruct,
                                    const OpenACCClause *> {
  friend class ASTStmtWriter;
  friend class ASTStmtReader;
  friend class ASTContext;
  friend TrailingObjects;
  OpenACCComputeConstruct(unsigned NumClauses)
      : OpenACCAssociatedStmtConstruct(
            OpenACCComputeConstructClass, OpenACCDirectiveKind::Invalid,
            SourceLocation{}, SourceLocation{}, SourceLocation{},
            /*AssociatedStmt=*/nullptr) {
    // We cannot send the TrailingObjects storage to the base class (which holds
    // a reference to the data) until it is constructed, so we have to set it
    // separately here.
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }

  OpenACCComputeConstruct(OpenACCDirectiveKind K, SourceLocation Start,
                          SourceLocation DirectiveLoc, SourceLocation End,
                          ArrayRef<const OpenACCClause *> Clauses,
                          Stmt *StructuredBlock)
      : OpenACCAssociatedStmtConstruct(OpenACCComputeConstructClass, K, Start,
                                       DirectiveLoc, End, StructuredBlock) {
    assert(isOpenACCComputeDirectiveKind(K) &&
           "Only parallel, serial, and kernels constructs should be "
           "represented by this type");

    // Initialize the trailing storage.
    llvm::uninitialized_copy(Clauses, getTrailingObjects());

    setClauseList(getTrailingObjects(Clauses.size()));
  }

  void setStructuredBlock(Stmt *S) { setAssociatedStmt(S); }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCComputeConstructClass;
  }

  static OpenACCComputeConstruct *CreateEmpty(const ASTContext &C,
                                              unsigned NumClauses);
  static OpenACCComputeConstruct *
  Create(const ASTContext &C, OpenACCDirectiveKind K, SourceLocation BeginLoc,
         SourceLocation DirectiveLoc, SourceLocation EndLoc,
         ArrayRef<const OpenACCClause *> Clauses, Stmt *StructuredBlock);

  Stmt *getStructuredBlock() { return getAssociatedStmt(); }
  const Stmt *getStructuredBlock() const {
    return const_cast<OpenACCComputeConstruct *>(this)->getStructuredBlock();
  }
};
/// This class represents a 'loop' construct.  The 'loop' construct applies to a
/// 'for' loop (or range-for loop), and is optionally associated with a Compute
/// Construct.
class OpenACCLoopConstruct final
    : public OpenACCAssociatedStmtConstruct,
      private llvm::TrailingObjects<OpenACCLoopConstruct,
                                   const OpenACCClause *> {
  // The compute/combined construct kind this loop is associated with, or
  // invalid if this is an orphaned loop construct.
  OpenACCDirectiveKind ParentComputeConstructKind =
      OpenACCDirectiveKind::Invalid;

  friend class ASTStmtWriter;
  friend class ASTStmtReader;
  friend class ASTContext;
  friend class OpenACCAssociatedStmtConstruct;
  friend class OpenACCCombinedConstruct;
  friend class OpenACCComputeConstruct;
  friend TrailingObjects;

  OpenACCLoopConstruct(unsigned NumClauses);

  OpenACCLoopConstruct(OpenACCDirectiveKind ParentKind, SourceLocation Start,
                       SourceLocation DirLoc, SourceLocation End,
                       ArrayRef<const OpenACCClause *> Clauses, Stmt *Loop);

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCLoopConstructClass;
  }

  static OpenACCLoopConstruct *CreateEmpty(const ASTContext &C,
                                           unsigned NumClauses);

  static OpenACCLoopConstruct *
  Create(const ASTContext &C, OpenACCDirectiveKind ParentKind,
         SourceLocation BeginLoc, SourceLocation DirLoc, SourceLocation EndLoc,
         ArrayRef<const OpenACCClause *> Clauses, Stmt *Loop);

  Stmt *getLoop() { return getAssociatedStmt(); }
  const Stmt *getLoop() const {
    return const_cast<OpenACCLoopConstruct *>(this)->getLoop();
  }

  /// OpenACC 3.3 2.9:
  /// An orphaned loop construct is a loop construct that is not lexically
  /// enclosed within a compute construct. The parent compute construct of a
  /// loop construct is the nearest compute construct that lexically contains
  /// the loop construct.
  bool isOrphanedLoopConstruct() const {
    return ParentComputeConstructKind == OpenACCDirectiveKind::Invalid;
  }

  OpenACCDirectiveKind getParentComputeConstructKind() const {
    return ParentComputeConstructKind;
  }
};

// This class represents a 'combined' construct, which has a bunch of rules
// shared with both loop and compute constructs.
class OpenACCCombinedConstruct final
    : public OpenACCAssociatedStmtConstruct,
      private llvm::TrailingObjects<OpenACCCombinedConstruct,
                                   const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCCombinedConstruct(unsigned NumClauses)
      : OpenACCAssociatedStmtConstruct(
            OpenACCCombinedConstructClass, OpenACCDirectiveKind::Invalid,
            SourceLocation{}, SourceLocation{}, SourceLocation{},
            /*AssociatedStmt=*/nullptr) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }

  OpenACCCombinedConstruct(OpenACCDirectiveKind K, SourceLocation Start,
                           SourceLocation DirectiveLoc, SourceLocation End,
                           ArrayRef<const OpenACCClause *> Clauses,
                           Stmt *StructuredBlock)
      : OpenACCAssociatedStmtConstruct(OpenACCCombinedConstructClass, K, Start,
                                       DirectiveLoc, End, StructuredBlock) {
    assert(isOpenACCCombinedDirectiveKind(K) &&
           "Only parallel loop, serial loop, and kernels loop constructs "
           "should be represented by this type");

    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }
  void setStructuredBlock(Stmt *S) { setAssociatedStmt(S); }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCCombinedConstructClass;
  }

  static OpenACCCombinedConstruct *CreateEmpty(const ASTContext &C,
                                               unsigned NumClauses);
  static OpenACCCombinedConstruct *
  Create(const ASTContext &C, OpenACCDirectiveKind K, SourceLocation Start,
         SourceLocation DirectiveLoc, SourceLocation End,
         ArrayRef<const OpenACCClause *> Clauses, Stmt *StructuredBlock);
  Stmt *getLoop() { return getAssociatedStmt(); }
  const Stmt *getLoop() const {
    return const_cast<OpenACCCombinedConstruct *>(this)->getLoop();
  }
};

// This class represents a 'data' construct, which has an associated statement
// and clauses, but is otherwise pretty simple.
class OpenACCDataConstruct final
    : public OpenACCAssociatedStmtConstruct,
      private llvm::TrailingObjects<OpenACCDataConstruct,
                                   const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCDataConstruct(unsigned NumClauses)
      : OpenACCAssociatedStmtConstruct(
            OpenACCDataConstructClass, OpenACCDirectiveKind::Data,
            SourceLocation{}, SourceLocation{}, SourceLocation{},
            /*AssociatedStmt=*/nullptr) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }

  OpenACCDataConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                       SourceLocation End,
                       ArrayRef<const OpenACCClause *> Clauses,
                       Stmt *StructuredBlock)
      : OpenACCAssociatedStmtConstruct(OpenACCDataConstructClass,
                                       OpenACCDirectiveKind::Data, Start,
                                       DirectiveLoc, End, StructuredBlock) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }
  void setStructuredBlock(Stmt *S) { setAssociatedStmt(S); }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCDataConstructClass;
  }

  static OpenACCDataConstruct *CreateEmpty(const ASTContext &C,
                                           unsigned NumClauses);
  static OpenACCDataConstruct *Create(const ASTContext &C, SourceLocation Start,
                                      SourceLocation DirectiveLoc,
                                      SourceLocation End,
                                      ArrayRef<const OpenACCClause *> Clauses,
                                      Stmt *StructuredBlock);
  Stmt *getStructuredBlock() { return getAssociatedStmt(); }
  const Stmt *getStructuredBlock() const {
    return const_cast<OpenACCDataConstruct *>(this)->getStructuredBlock();
  }
};
// This class represents a 'enter data' construct, which JUST has clauses.
class OpenACCEnterDataConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCEnterDataConstruct,
                                   const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCEnterDataConstruct(unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCEnterDataConstructClass,
                             OpenACCDirectiveKind::EnterData, SourceLocation{},
                             SourceLocation{}, SourceLocation{}) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }
  OpenACCEnterDataConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                            SourceLocation End,
                            ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCEnterDataConstructClass,
                             OpenACCDirectiveKind::EnterData, Start,
                             DirectiveLoc, End) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCEnterDataConstructClass;
  }
  static OpenACCEnterDataConstruct *CreateEmpty(const ASTContext &C,
                                                unsigned NumClauses);
  static OpenACCEnterDataConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation End, ArrayRef<const OpenACCClause *> Clauses);
};
// This class represents a 'exit data' construct, which JUST has clauses.
class OpenACCExitDataConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCExitDataConstruct,
                                   const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCExitDataConstruct(unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCExitDataConstructClass,
                             OpenACCDirectiveKind::ExitData, SourceLocation{},
                             SourceLocation{}, SourceLocation{}) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }
  OpenACCExitDataConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                           SourceLocation End,
                           ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCExitDataConstructClass,
                             OpenACCDirectiveKind::ExitData, Start,
                             DirectiveLoc, End) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCExitDataConstructClass;
  }
  static OpenACCExitDataConstruct *CreateEmpty(const ASTContext &C,
                                               unsigned NumClauses);
  static OpenACCExitDataConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation End, ArrayRef<const OpenACCClause *> Clauses);
};
// This class represents a 'host_data' construct, which has an associated
// statement and clauses, but is otherwise pretty simple.
class OpenACCHostDataConstruct final
    : public OpenACCAssociatedStmtConstruct,
      private llvm::TrailingObjects<OpenACCHostDataConstruct,
                                   const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCHostDataConstruct(unsigned NumClauses)
      : OpenACCAssociatedStmtConstruct(
            OpenACCHostDataConstructClass, OpenACCDirectiveKind::HostData,
            SourceLocation{}, SourceLocation{}, SourceLocation{},
            /*AssociatedStmt=*/nullptr) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }
  OpenACCHostDataConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                           SourceLocation End,
                           ArrayRef<const OpenACCClause *> Clauses,
                           Stmt *StructuredBlock)
      : OpenACCAssociatedStmtConstruct(OpenACCHostDataConstructClass,
                                       OpenACCDirectiveKind::HostData, Start,
                                       DirectiveLoc, End, StructuredBlock) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }
  void setStructuredBlock(Stmt *S) { setAssociatedStmt(S); }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCHostDataConstructClass;
  }
  static OpenACCHostDataConstruct *CreateEmpty(const ASTContext &C,
                                               unsigned NumClauses);
  static OpenACCHostDataConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation End, ArrayRef<const OpenACCClause *> Clauses,
         Stmt *StructuredBlock);
  Stmt *getStructuredBlock() { return getAssociatedStmt(); }
  const Stmt *getStructuredBlock() const {
    return const_cast<OpenACCHostDataConstruct *>(this)->getStructuredBlock();
  }
};

// This class represents a 'wait' construct, which has some expressions plus a
// clause list.
class OpenACCWaitConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCWaitConstruct, Expr *,
                                    OpenACCClause *> {
  // FIXME: We should be storing a `const OpenACCClause *` to be consistent with
  // the rest of the constructs, but TrailingObjects doesn't allow for mixing
  // constness in its implementation of `getTrailingObjects`.

  friend TrailingObjects;
  friend class ASTStmtWriter;
  friend class ASTStmtReader;
  // Locations of the left and right parens of the 'wait-argument'
  // expression-list.
  SourceLocation LParenLoc, RParenLoc;
  // Location of the 'queues' keyword, if present.
  SourceLocation QueuesLoc;

  // Number of the expressions being represented.  Index '0' is always the
  // 'devnum' expression, even if it not present.
  unsigned NumExprs = 0;

  OpenACCWaitConstruct(unsigned NumExprs, unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCWaitConstructClass,
                             OpenACCDirectiveKind::Wait, SourceLocation{},
                             SourceLocation{}, SourceLocation{}),
        NumExprs(NumExprs) {
    assert(NumExprs >= 1 &&
           "NumExprs should always be >= 1 because the 'devnum' "
           "expr is represented by a null if necessary");
    std::uninitialized_value_construct_n(getExprPtr(), NumExprs);
    std::uninitialized_value_construct_n(getTrailingObjects<OpenACCClause *>(),
                                         NumClauses);
    setClauseList(MutableArrayRef(const_cast<const OpenACCClause **>(
                                      getTrailingObjects<OpenACCClause *>()),
                                  NumClauses));
  }

  OpenACCWaitConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                       SourceLocation LParenLoc, Expr *DevNumExpr,
                       SourceLocation QueuesLoc, ArrayRef<Expr *> QueueIdExprs,
                       SourceLocation RParenLoc, SourceLocation End,
                       ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCWaitConstructClass,
                             OpenACCDirectiveKind::Wait, Start, DirectiveLoc,
                             End),
        LParenLoc(LParenLoc), RParenLoc(RParenLoc), QueuesLoc(QueuesLoc),
        NumExprs(QueueIdExprs.size() + 1) {
    assert(NumExprs >= 1 &&
           "NumExprs should always be >= 1 because the 'devnum' "
           "expr is represented by a null if necessary");

    llvm::uninitialized_copy(ArrayRef(DevNumExpr), getExprPtr());
    llvm::uninitialized_copy(QueueIdExprs, getExprPtr() + 1);
    std::uninitialized_copy(const_cast<OpenACCClause **>(Clauses.begin()),
                            const_cast<OpenACCClause **>(Clauses.end()),
                            getTrailingObjects<OpenACCClause *>());
    setClauseList(MutableArrayRef(const_cast<const OpenACCClause **>(
                                      getTrailingObjects<OpenACCClause *>()),
                                  Clauses.size()));
  }

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

  Expr **getExprPtr() const {
    return const_cast<Expr**>(getTrailingObjects<Expr *>());
  }

  ArrayRef<Expr *> getExprs() const { return {getExprPtr(), NumExprs}; }

  ArrayRef<Expr *> getExprs() { return {getExprPtr(), NumExprs}; }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCWaitConstructClass;
  }

  static OpenACCWaitConstruct *
  CreateEmpty(const ASTContext &C, unsigned NumExprs, unsigned NumClauses);

  static OpenACCWaitConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation LParenLoc, Expr *DevNumExpr, SourceLocation QueuesLoc,
         ArrayRef<Expr *> QueueIdExprs, SourceLocation RParenLoc,
         SourceLocation End, ArrayRef<const OpenACCClause *> Clauses);

  SourceLocation getLParenLoc() const { return LParenLoc; }
  SourceLocation getRParenLoc() const { return RParenLoc; }
  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 getExprs().drop_front(); }
  ArrayRef<Expr *> getQueueIdExprs() const { return getExprs().drop_front(); }

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

  const_child_range children() const {
    Stmt *const *Begin =
        reinterpret_cast<Stmt *const *>(getExprPtr());
    return const_child_range(Begin, Begin + NumExprs);
  }
};

class OpenACCCacheConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCCacheConstruct, Expr *> {
  friend TrailingObjects;
  friend class ASTStmtWriter;
  friend class ASTStmtReader;
  // Locations of the left and right parens of the 'var-list'
  // expression-list.
  SourceRange ParensLoc;
  SourceLocation ReadOnlyLoc;

  unsigned NumVars = 0;

  OpenACCCacheConstruct(unsigned NumVars)
      : OpenACCConstructStmt(OpenACCCacheConstructClass,
                             OpenACCDirectiveKind::Cache, SourceLocation{},
                             SourceLocation{}, SourceLocation{}),
        NumVars(NumVars) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumVars);
  }
  OpenACCCacheConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                        SourceLocation LParenLoc, SourceLocation ReadOnlyLoc,
                        ArrayRef<Expr *> VarList, SourceLocation RParenLoc,
                        SourceLocation End)
      : OpenACCConstructStmt(OpenACCCacheConstructClass,
                             OpenACCDirectiveKind::Cache, Start, DirectiveLoc,
                             End),
        ParensLoc(LParenLoc, RParenLoc), ReadOnlyLoc(ReadOnlyLoc),
        NumVars(VarList.size()) {

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

public:
  ArrayRef<Expr *> getVarList() const { return getTrailingObjects(NumVars); }

  MutableArrayRef<Expr *> getVarList() { return getTrailingObjects(NumVars); }

  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCCacheConstructClass;
  }

  static OpenACCCacheConstruct *CreateEmpty(const ASTContext &C,
                                            unsigned NumVars);
  static OpenACCCacheConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation LParenLoc, SourceLocation ReadOnlyLoc,
         ArrayRef<Expr *> VarList, SourceLocation RParenLoc,
         SourceLocation End);

  SourceLocation getLParenLoc() const { return ParensLoc.getBegin(); }
  SourceLocation getRParenLoc() const { return ParensLoc.getEnd(); }
  bool hasReadOnly() const { return !ReadOnlyLoc.isInvalid(); }
  SourceLocation getReadOnlyLoc() const { return ReadOnlyLoc; }

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

  const_child_range children() const {
    Stmt *const *Begin = reinterpret_cast<Stmt *const *>(getTrailingObjects());
    return const_child_range(Begin, Begin + NumVars);
  }
};

// This class represents an 'init' construct, which has just a clause list.
class OpenACCInitConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCInitConstruct,
                                    const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCInitConstruct(unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCInitConstructClass,
                             OpenACCDirectiveKind::Init, SourceLocation{},
                             SourceLocation{}, SourceLocation{}) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }
  OpenACCInitConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                       SourceLocation End,
                       ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCInitConstructClass,
                             OpenACCDirectiveKind::Init, Start, DirectiveLoc,
                             End) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCInitConstructClass;
  }
  static OpenACCInitConstruct *CreateEmpty(const ASTContext &C,
                                           unsigned NumClauses);
  static OpenACCInitConstruct *Create(const ASTContext &C, SourceLocation Start,
                                      SourceLocation DirectiveLoc,
                                      SourceLocation End,
                                      ArrayRef<const OpenACCClause *> Clauses);
};

// This class represents a 'shutdown' construct, which has just a clause list.
class OpenACCShutdownConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCShutdownConstruct,
                                    const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCShutdownConstruct(unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCShutdownConstructClass,
                             OpenACCDirectiveKind::Shutdown, SourceLocation{},
                             SourceLocation{}, SourceLocation{}) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }
  OpenACCShutdownConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                           SourceLocation End,
                           ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCShutdownConstructClass,
                             OpenACCDirectiveKind::Shutdown, Start,
                             DirectiveLoc, End) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCShutdownConstructClass;
  }
  static OpenACCShutdownConstruct *CreateEmpty(const ASTContext &C,
                                               unsigned NumClauses);
  static OpenACCShutdownConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation End, ArrayRef<const OpenACCClause *> Clauses);
};

// This class represents a 'set' construct, which has just a clause list.
class OpenACCSetConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCSetConstruct,
                                    const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCSetConstruct(unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCSetConstructClass,
                             OpenACCDirectiveKind::Set, SourceLocation{},
                             SourceLocation{}, SourceLocation{}) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }

  OpenACCSetConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                      SourceLocation End,
                      ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCSetConstructClass,
                             OpenACCDirectiveKind::Set, Start, DirectiveLoc,
                             End) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCSetConstructClass;
  }
  static OpenACCSetConstruct *CreateEmpty(const ASTContext &C,
                                          unsigned NumClauses);
  static OpenACCSetConstruct *Create(const ASTContext &C, SourceLocation Start,
                                     SourceLocation DirectiveLoc,
                                     SourceLocation End,
                                     ArrayRef<const OpenACCClause *> Clauses);
};
// This class represents an 'update' construct, which has just a clause list.
class OpenACCUpdateConstruct final
    : public OpenACCConstructStmt,
      private llvm::TrailingObjects<OpenACCUpdateConstruct,
                                    const OpenACCClause *> {
  friend TrailingObjects;
  OpenACCUpdateConstruct(unsigned NumClauses)
      : OpenACCConstructStmt(OpenACCUpdateConstructClass,
                             OpenACCDirectiveKind::Update, SourceLocation{},
                             SourceLocation{}, SourceLocation{}) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }

  OpenACCUpdateConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                         SourceLocation End,
                         ArrayRef<const OpenACCClause *> Clauses)
      : OpenACCConstructStmt(OpenACCUpdateConstructClass,
                             OpenACCDirectiveKind::Update, Start, DirectiveLoc,
                             End) {
    llvm::uninitialized_copy(Clauses, getTrailingObjects());
    setClauseList(getTrailingObjects(Clauses.size()));
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCUpdateConstructClass;
  }
  static OpenACCUpdateConstruct *CreateEmpty(const ASTContext &C,
                                             unsigned NumClauses);
  static OpenACCUpdateConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         SourceLocation End, ArrayRef<const OpenACCClause *> Clauses);
};

// This class represents the 'atomic' construct, which has an associated
// statement, but no clauses.
class OpenACCAtomicConstruct final
    : public OpenACCAssociatedStmtConstruct,
      private llvm::TrailingObjects<OpenACCAtomicConstruct,
                                    const OpenACCClause *> {

  friend class ASTStmtReader;
  friend TrailingObjects;
  OpenACCAtomicKind AtomicKind = OpenACCAtomicKind::None;

  OpenACCAtomicConstruct(unsigned NumClauses)
      : OpenACCAssociatedStmtConstruct(
            OpenACCAtomicConstructClass, OpenACCDirectiveKind::Atomic,
            SourceLocation{}, SourceLocation{}, SourceLocation{},
            /*AssociatedStmt=*/nullptr) {
    std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses);
    setClauseList(getTrailingObjects(NumClauses));
  }

  OpenACCAtomicConstruct(SourceLocation Start, SourceLocation DirectiveLoc,
                         OpenACCAtomicKind AtKind, SourceLocation End,
                         ArrayRef<const OpenACCClause *> Clauses,
                         Stmt *AssociatedStmt)
      : OpenACCAssociatedStmtConstruct(OpenACCAtomicConstructClass,
                                       OpenACCDirectiveKind::Atomic, Start,
                                       DirectiveLoc, End, AssociatedStmt),
        AtomicKind(AtKind) {
    // Initialize the trailing storage.
    llvm::uninitialized_copy(Clauses, getTrailingObjects());

    setClauseList(getTrailingObjects(Clauses.size()));
  }

  void setAssociatedStmt(Stmt *S) {
    OpenACCAssociatedStmtConstruct::setAssociatedStmt(S);
  }

public:
  static bool classof(const Stmt *T) {
    return T->getStmtClass() == OpenACCAtomicConstructClass;
  }

  static OpenACCAtomicConstruct *CreateEmpty(const ASTContext &C,
                                             unsigned NumClauses);
  static OpenACCAtomicConstruct *
  Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc,
         OpenACCAtomicKind AtKind, SourceLocation End,
         ArrayRef<const OpenACCClause *> Clauses, Stmt *AssociatedStmt);

  OpenACCAtomicKind getAtomicKind() const { return AtomicKind; }
  const Stmt *getAssociatedStmt() const {
    return OpenACCAssociatedStmtConstruct::getAssociatedStmt();
  }
  Stmt *getAssociatedStmt() {
    return OpenACCAssociatedStmtConstruct::getAssociatedStmt();
  }

  // A struct to represent a broken-down version of the associated statement,
  // providing the information specified in OpenACC3.3 Section 2.12.
  struct SingleStmtInfo {
    // Holds the entire expression for this. In the case of a normal
    // read/write/update, this should just be the associated statement.  in the
    // case of an update, this is going to be the sub-expression this
    // represents.
    const Expr *WholeExpr;
    const Expr *V;
    const Expr *X;
    // Listed as 'expr' in the standard, this is typically a generic expression
    // as a component.
    const Expr *RefExpr;
    // If this is an 'update', records whether this is a post-fix
    // increment/decrement.  In the case where we have a single-line variant of
    // 'capture' we have to form the IR differently if this is the case to make
    // sure the old value is 'read' in the 2nd step.
    bool IsPostfixIncDec = false;
    static SingleStmtInfo Empty() {
      return {nullptr, nullptr, nullptr, nullptr, false};
    }

    static SingleStmtInfo createRead(const Expr *WholeExpr, const Expr *V,
                                     const Expr *X) {
      return {WholeExpr, V, X, /*RefExpr=*/nullptr};
    }
    static SingleStmtInfo createWrite(const Expr *WholeExpr, const Expr *X,
                                      const Expr *RefExpr) {
      return {WholeExpr, /*V=*/nullptr, X, RefExpr};
    }
    static SingleStmtInfo createUpdate(const Expr *WholeExpr, const Expr *X,
                                       bool PostfixIncDec) {
      return {WholeExpr, /*V=*/nullptr, X, /*RefExpr=*/nullptr, PostfixIncDec};
    }
  };

  struct StmtInfo {
    enum class StmtForm {
      Read,
      Write,
      Update,
      ReadWrite,
      ReadUpdate,
      UpdateRead
    } Form;
    SingleStmtInfo First, Second;

    static StmtInfo createUpdateRead(SingleStmtInfo First,
                                     SingleStmtInfo Second) {
      return {StmtForm::UpdateRead, First, Second};
    }
    static StmtInfo createReadWrite(SingleStmtInfo First,
                                    SingleStmtInfo Second) {
      return {StmtForm::ReadWrite, First, Second};
    }
    static StmtInfo createReadUpdate(SingleStmtInfo First,
                                     SingleStmtInfo Second) {
      return {StmtForm::ReadUpdate, First, Second};
    }
  };

  const StmtInfo getAssociatedStmtInfo() const;
};

} // namespace clang
#endif // LLVM_CLANG_AST_STMTOPENACC_H
